Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/presenter/configPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { DEFAULT_PROVIDERS } from './providers'
import path from 'path'
import { app, nativeTheme, shell, ipcMain } from 'electron'
import fs from 'fs'
import { CONFIG_EVENTS, SYSTEM_EVENTS, FLOATING_BUTTON_EVENTS } from '@/events'
import { CONFIG_EVENTS, SYSTEM_EVENTS, FLOATING_BUTTON_EVENTS, SESSION_EVENTS } from '@/events'
import { McpConfHelper } from './mcpConfHelper'
import { presenter } from '@/presenter'
import { compare } from 'compare-versions'
Expand Down Expand Up @@ -1329,6 +1329,7 @@ export class ConfigPresenter implements IConfigPresenter {
private notifyAcpAgentsChanged() {
console.log('[ACP] notifyAcpAgentsChanged: sending MODEL_LIST_CHANGED event for provider "acp"')
eventBus.sendToRenderer(CONFIG_EVENTS.MODEL_LIST_CHANGED, SendTarget.ALL_WINDOWS, 'acp')
eventBus.sendToRenderer(SESSION_EVENTS.LIST_UPDATED, SendTarget.ALL_WINDOWS)
}

// Provide getMcpConfHelper method to get MCP configuration helper
Expand Down
50 changes: 33 additions & 17 deletions src/main/presenter/newAgentPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,14 +365,10 @@ export class NewAgentPresenter {
const enriched: SessionWithState[] = []

for (const record of records) {
const agent = await this.resolveAgentImplementation(record.agentId)
const state = await agent.getSessionState(record.id)
enriched.push({
...record,
status: state?.status ?? 'idle',
providerId: state?.providerId ?? '',
modelId: state?.modelId ?? ''
})
const session = await this.tryBuildSessionWithState(record)
if (session) {
enriched.push(session)
}
}

return enriched
Expand All @@ -381,14 +377,7 @@ export class NewAgentPresenter {
async getSession(sessionId: string): Promise<SessionWithState | null> {
const record = this.sessionManager.get(sessionId)
if (!record) return null
const agent = await this.resolveAgentImplementation(record.agentId)
const state = await agent.getSessionState(sessionId)
return {
...record,
status: state?.status ?? 'idle',
providerId: state?.providerId ?? '',
modelId: state?.modelId ?? ''
}
return await this.tryBuildSessionWithState(record)
}

async getMessages(sessionId: string): Promise<ChatMessageRecord[]> {
Expand Down Expand Up @@ -556,7 +545,11 @@ export class NewAgentPresenter {
async getActiveSession(webContentsId: number): Promise<SessionWithState | null> {
const sessionId = this.sessionManager.getActiveSessionId(webContentsId)
if (!sessionId) return null
return this.getSession(sessionId)
const session = await this.getSession(sessionId)
if (!session) {
this.sessionManager.unbindWindow(webContentsId)
}
return session
}

async getAgents(): Promise<Agent[]> {
Expand Down Expand Up @@ -891,6 +884,29 @@ export class NewAgentPresenter {
return false
}

private async buildSessionWithState(record: SessionRecord): Promise<SessionWithState> {
const agent = await this.resolveAgentImplementation(record.agentId)
const state = await agent.getSessionState(record.id)
return {
...record,
status: state?.status ?? 'idle',
providerId: state?.providerId ?? '',
modelId: state?.modelId ?? ''
}
}

private async tryBuildSessionWithState(record: SessionRecord): Promise<SessionWithState | null> {
try {
return await this.buildSessionWithState(record)
} catch (error) {
console.warn(
`[NewAgentPresenter] Skipping unavailable session id=${record.id} agent=${record.agentId}:`,
error
)
return null
}
}

private async resolveAgentImplementation(agentId: string): Promise<IAgentImplementation> {
if (this.agentRegistry.has(agentId)) {
return this.agentRegistry.resolve(agentId)
Expand Down
4 changes: 4 additions & 0 deletions src/preload/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ declare global {
toRelativePath?(filePath: string, baseDir?: string): string
formatPathForInput?(filePath: string): string
}
__deepchatDev?: {
goToWelcome(): boolean
clearWelcomeOverride(): boolean
}
floatingButtonAPI: typeof floatingButtonAPI
}
}
37 changes: 37 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
import { exposeElectronAPI } from '@electron-toolkit/preload'

const ALLOWED_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:', 'deepchat:']
const isDevHiddenApiEnabled =
process.env.NODE_ENV === 'development' || Boolean(process.env.ELECTRON_RENDERER_URL)
const DEV_WELCOME_OVERRIDE_KEY = '__deepchat_dev_force_welcome'

const isValidExternalUrl = (url: string): boolean => {
try {
Expand Down Expand Up @@ -101,6 +104,33 @@ const api = {
return `'${filePath.replace(/'/g, `'\\''`)}'`
}
}

const setDevWelcomeOverride = (enabled: boolean) => {
try {
if (enabled) {
window.sessionStorage.setItem(DEV_WELCOME_OVERRIDE_KEY, '1')
} else {
window.sessionStorage.removeItem(DEV_WELCOME_OVERRIDE_KEY)
}
} catch (error) {
console.warn('Preload: Failed to update dev welcome override:', error)
}
}

const deepchatDevApi = isDevHiddenApiEnabled
? {
goToWelcome: () => {
setDevWelcomeOverride(true)
window.location.hash = '/welcome'
return true
},
clearWelcomeOverride: () => {
setDevWelcomeOverride(false)
return true
}
}
: undefined

exposeElectronAPI()

// Use `contextBridge` APIs to expose Electron APIs to
Expand All @@ -109,12 +139,19 @@ exposeElectronAPI()
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('api', api)
if (deepchatDevApi) {
contextBridge.exposeInMainWorld('__deepchatDev', deepchatDevApi)
}
} catch (error) {
console.error('Preload: Failed to expose API via contextBridge:', error)
}
} else {
// @ts-ignore (define in dts)
window.api = api
if (deepchatDevApi) {
// @ts-ignore (define in dts)
window.__deepchatDev = deepchatDevApi
}
}
window.addEventListener('DOMContentLoaded', () => {
cachedWebContentsId = ipcRenderer.sendSync('get-web-contents-id')
Expand Down
34 changes: 30 additions & 4 deletions src/renderer/settings/components/ProviderModelList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="getItemSizeDependencies(item)"
:size-dependencies="getScrollerItemSizeDependencies(item)"
>
<div v-if="item.type === 'label'" class="px-3 py-2 text-xs text-muted-foreground">
<div v-if="isLabelItem(item)" class="px-3 py-2 text-xs text-muted-foreground">
{{ item.label }}
</div>
<div
v-else-if="item.type === 'provider-actions'"
v-else-if="isProviderActionsItem(item)"
class="flex flex-wrap items-center justify-between gap-3 px-3 py-2 bg-muted/30"
>
<div class="text-sm font-medium">{{ getProviderName(item.providerId) }}</div>
Expand All @@ -94,7 +94,7 @@
</Button>
</div>
</div>
<div v-else class="bg-card">
<div v-else-if="isModelItem(item)" class="bg-card">
<ModelConfigItem
:model-name="item.model.name"
:model-id="item.model.id"
Expand Down Expand Up @@ -246,6 +246,28 @@ const virtualItems = computed<VirtualModelListItem[]>(() => {
return items
})

const isLabelItem = (item: unknown): item is Extract<VirtualModelListItem, { type: 'label' }> => {
return (
typeof item === 'object' && item !== null && (item as VirtualModelListItem).type === 'label'
)
}

const isProviderActionsItem = (
item: unknown
): item is Extract<VirtualModelListItem, { type: 'provider-actions' }> => {
return (
typeof item === 'object' &&
item !== null &&
(item as VirtualModelListItem).type === 'provider-actions'
)
}

const isModelItem = (item: unknown): item is Extract<VirtualModelListItem, { type: 'model' }> => {
return (
typeof item === 'object' && item !== null && (item as VirtualModelListItem).type === 'model'
)
}

const getItemSizeDependencies = (item: VirtualModelListItem) => {
if (item.type === 'model') {
return [
Expand All @@ -270,6 +292,10 @@ const getItemSizeDependencies = (item: VirtualModelListItem) => {
return [item.label]
}

const getScrollerItemSizeDependencies = (item: unknown) => {
return getItemSizeDependencies(item as VirtualModelListItem)
}

const getProviderName = (providerId: string) => {
const provider = props.providers.find((p) => p.id === providerId)
return provider?.name || providerId
Expand Down
48 changes: 42 additions & 6 deletions src/renderer/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import AppBar from '@/components/AppBar.vue'
import { useDeviceVersion } from '@/composables/useDeviceVersion'
import WindowSideBar from './components/WindowSideBar.vue'

const DEV_WELCOME_OVERRIDE_KEY = '__deepchat_dev_force_welcome'

const route = useRoute()
const configPresenter = usePresenter('configPresenter')
const artifactStore = useArtifactStore()
Expand Down Expand Up @@ -144,11 +146,45 @@ const handleErrorClosed = () => {

const router = useRouter()
const activeTab = ref('chat')
const isStartupRouteReady = ref(false)

const isDevWelcomeOverrideEnabled = () => {
if (!import.meta.env.DEV) return false

try {
return window.sessionStorage.getItem(DEV_WELCOME_OVERRIDE_KEY) === '1'
} catch {
return false
}
}

const ensureStartupWelcomeState = async () => {
try {
await router.isReady()

const currentRoute = router.currentRoute.value
const isWelcomeRoute = currentRoute.name === 'welcome' || currentRoute.path === '/welcome'

const getInitComplete = async () => {
const initComplete = await configPresenter.getSetting('init_complete')
if (!initComplete) {
router.push({ name: 'welcome' })
if (isDevWelcomeOverrideEnabled()) {
if (!isWelcomeRoute) {
await router.replace({ name: 'welcome' })
}
return
}

const initComplete = Boolean(await configPresenter.getSetting('init_complete'))
if (!initComplete) {
if (!isWelcomeRoute) {
await router.replace({ name: 'welcome' })
}
return
}

if (isWelcomeRoute) {
await router.replace({ name: 'chat' })
}
} finally {
isStartupRouteReady.value = true
}
}

Expand Down Expand Up @@ -192,7 +228,7 @@ const handleEscKey = (event: KeyboardEvent) => {
}
}

getInitComplete()
void ensureStartupWelcomeState()

onMounted(() => {
// Set initial body class
Expand Down Expand Up @@ -332,7 +368,7 @@ onBeforeUnmount(() => {
<div
class="flex-1 min-w-0 bg-background overflow-hidden rounded-tl-xl border-black/20 dark:border-white/10 border-l border-t"
>
<RouterView />
<RouterView v-if="isStartupRouteReady" />
</div>
</div>
</div>
Expand Down
Loading
Loading