From 15166868d01f335ecc7af0ecf0c0eb1a61823877 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Tue, 10 Mar 2026 22:04:18 +0800 Subject: [PATCH 1/4] fix(session): skip unavailable agent sessions --- src/main/presenter/configPresenter/index.ts | 3 +- src/main/presenter/newAgentPresenter/index.ts | 50 ++-- src/renderer/src/stores/ui/session.ts | 8 +- .../newAgentPresenter.test.ts | 258 ++++++++++++++---- test/renderer/stores/sessionStore.test.ts | 48 +++- 5 files changed, 292 insertions(+), 75 deletions(-) diff --git a/src/main/presenter/configPresenter/index.ts b/src/main/presenter/configPresenter/index.ts index 3c123abae..889f2e0ff 100644 --- a/src/main/presenter/configPresenter/index.ts +++ b/src/main/presenter/configPresenter/index.ts @@ -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' @@ -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 diff --git a/src/main/presenter/newAgentPresenter/index.ts b/src/main/presenter/newAgentPresenter/index.ts index 6f290d6dd..b1b1b91f8 100644 --- a/src/main/presenter/newAgentPresenter/index.ts +++ b/src/main/presenter/newAgentPresenter/index.ts @@ -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 @@ -381,14 +377,7 @@ export class NewAgentPresenter { async getSession(sessionId: string): Promise { 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 { @@ -556,7 +545,11 @@ export class NewAgentPresenter { async getActiveSession(webContentsId: number): Promise { 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 { @@ -891,6 +884,29 @@ export class NewAgentPresenter { return false } + private async buildSessionWithState(record: SessionRecord): Promise { + 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 { + 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 { if (this.agentRegistry.has(agentId)) { return this.agentRegistry.resolve(agentId) diff --git a/src/renderer/src/stores/ui/session.ts b/src/renderer/src/stores/ui/session.ts index 2049bb237..fbbd62b1a 100644 --- a/src/renderer/src/stores/ui/session.ts +++ b/src/renderer/src/stores/ui/session.ts @@ -171,6 +171,7 @@ export const useSessionStore = defineStore('session', () => { error.value = null try { const webContentsId = window.api.getWebContentsId() + const previousActiveSessionId = activeSessionId.value const [result, activeSession] = await Promise.all([ newAgentPresenter.getSessionList(), newAgentPresenter.getActiveSession(webContentsId) @@ -178,12 +179,15 @@ export const useSessionStore = defineStore('session', () => { sessions.value = result.map(mapToUISession) const nextActiveSessionId = activeSession?.id ?? null - if (activeSessionId.value !== nextActiveSessionId) { - if (activeSessionId.value && activeSessionId.value !== nextActiveSessionId) { + if (previousActiveSessionId !== nextActiveSessionId) { + if (previousActiveSessionId && previousActiveSessionId !== nextActiveSessionId) { messageStore.clearStreamingState() } activeSessionId.value = nextActiveSessionId } + if (previousActiveSessionId && !nextActiveSessionId && pageRouter.currentRoute === 'chat') { + pageRouter.goToNewThread() + } } catch (e) { error.value = `Failed to load sessions: ${e}` } finally { diff --git a/test/main/presenter/newAgentPresenter/newAgentPresenter.test.ts b/test/main/presenter/newAgentPresenter/newAgentPresenter.test.ts index 089149d59..9c0e89b68 100644 --- a/test/main/presenter/newAgentPresenter/newAgentPresenter.test.ts +++ b/test/main/presenter/newAgentPresenter/newAgentPresenter.test.ts @@ -192,12 +192,16 @@ describe('NewAgentPresenter', () => { expect(result.title).toBe('Hello world') expect(result.projectDir).toBe('/tmp/proj') expect(result.status).toBe('idle') - expect(deepChatAgent.initSession).toHaveBeenCalledWith('mock-session-id', { - providerId: 'openai', - modelId: 'gpt-4', - projectDir: '/tmp/proj', - permissionMode: 'full_access' - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + 'mock-session-id', + expect.objectContaining({ + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4', + projectDir: '/tmp/proj', + permissionMode: 'full_access' + }) + ) await new Promise((r) => setTimeout(r, 0)) expect(deepChatAgent.processMessage).toHaveBeenCalledWith( 'mock-session-id', @@ -224,12 +228,16 @@ describe('NewAgentPresenter', () => { it('calls agent.initSession and processMessage', async () => { await presenter.createSession({ agentId: 'deepchat', message: 'Hello' }, 1) - expect(deepChatAgent.initSession).toHaveBeenCalledWith('mock-session-id', { - providerId: 'openai', - modelId: 'gpt-4', - projectDir: null, - permissionMode: 'full_access' - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + 'mock-session-id', + expect.objectContaining({ + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4', + projectDir: null, + permissionMode: 'full_access' + }) + ) // processMessage is called non-blocking, so we give it a tick await new Promise((r) => setTimeout(r, 0)) expect(deepChatAgent.processMessage).toHaveBeenCalledWith( @@ -254,12 +262,16 @@ describe('NewAgentPresenter', () => { it('uses default provider/model from config when not specified', async () => { await presenter.createSession({ agentId: 'deepchat', message: 'Hi' }, 1) - expect(deepChatAgent.initSession).toHaveBeenCalledWith(expect.any(String), { - providerId: 'openai', - modelId: 'gpt-4', - projectDir: null, - permissionMode: 'full_access' - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4', + projectDir: null, + permissionMode: 'full_access' + }) + ) }) it('uses input provider/model when specified', async () => { @@ -268,12 +280,16 @@ describe('NewAgentPresenter', () => { 1 ) - expect(deepChatAgent.initSession).toHaveBeenCalledWith(expect.any(String), { - providerId: 'anthropic', - modelId: 'claude-3', - projectDir: null, - permissionMode: 'full_access' - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + agentId: 'deepchat', + providerId: 'anthropic', + modelId: 'claude-3', + projectDir: null, + permissionMode: 'full_access' + }) + ) }) it('uses input permission mode when specified', async () => { @@ -288,12 +304,16 @@ describe('NewAgentPresenter', () => { 1 ) - expect(deepChatAgent.initSession).toHaveBeenCalledWith(expect.any(String), { - providerId: 'anthropic', - modelId: 'claude-3', - projectDir: null, - permissionMode: 'default' - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + agentId: 'deepchat', + providerId: 'anthropic', + modelId: 'claude-3', + projectDir: null, + permissionMode: 'default' + }) + ) }) it('passes generationSettings to agent.initSession', async () => { @@ -312,19 +332,23 @@ describe('NewAgentPresenter', () => { 1 ) - expect(deepChatAgent.initSession).toHaveBeenCalledWith(expect.any(String), { - providerId: 'openai', - modelId: 'gpt-4', - projectDir: null, - permissionMode: 'full_access', - generationSettings: { - systemPrompt: 'Custom prompt', - temperature: 1.1, - contextLength: 8192, - maxTokens: 2048, - reasoningEffort: 'low' - } - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4', + projectDir: null, + permissionMode: 'full_access', + generationSettings: { + systemPrompt: 'Custom prompt', + temperature: 1.1, + contextLength: 8192, + maxTokens: 2048, + reasoningEffort: 'low' + } + }) + ) }) it('throws when no provider/model available', async () => { @@ -524,12 +548,16 @@ describe('NewAgentPresenter', () => { projectDir: '/tmp/workspace' }) - expect(deepChatAgent.initSession).toHaveBeenCalledWith('mock-session-id', { - providerId: 'acp', - modelId: 'acp-coder', - projectDir: '/tmp/workspace', - permissionMode: 'full_access' - }) + expect(deepChatAgent.initSession).toHaveBeenCalledWith( + 'mock-session-id', + expect.objectContaining({ + agentId: 'acp-coder', + providerId: 'acp', + modelId: 'acp-coder', + projectDir: '/tmp/workspace', + permissionMode: 'full_access' + }) + ) expect(llmProviderPresenter.prepareAcpSession).toHaveBeenCalledWith( 'mock-session-id', 'acp-coder', @@ -601,6 +629,85 @@ describe('NewAgentPresenter', () => { expect(sessions[0].providerId).toBe('openai') expect(sessions[0].modelId).toBe('gpt-4') }) + + it('skips sessions whose agent implementation cannot be resolved', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + sqlitePresenter.newSessionsTable.list.mockReturnValue([ + { + id: 'missing-agent', + agent_id: 'disabled-agent', + title: 'Disabled', + project_dir: null, + is_pinned: 0, + created_at: 1000, + updated_at: 2000 + }, + { + id: 's1', + agent_id: 'deepchat', + title: 'Chat 1', + project_dir: null, + is_pinned: 0, + created_at: 1000, + updated_at: 2000 + } + ]) + + const sessions = await presenter.getSessionList() + + expect(sessions).toHaveLength(1) + expect(sessions[0].id).toBe('s1') + expect(warnSpy).toHaveBeenCalledWith( + '[NewAgentPresenter] Skipping unavailable session id=missing-agent agent=disabled-agent:', + expect.any(Error) + ) + warnSpy.mockRestore() + }) + + it('skips sessions whose state loading fails', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + sqlitePresenter.newSessionsTable.list.mockReturnValue([ + { + id: 'broken-state', + agent_id: 'deepchat', + title: 'Broken', + project_dir: null, + is_pinned: 0, + created_at: 1000, + updated_at: 2000 + }, + { + id: 'healthy-state', + agent_id: 'deepchat', + title: 'Healthy', + project_dir: null, + is_pinned: 0, + created_at: 1001, + updated_at: 2001 + } + ]) + deepChatAgent.getSessionState.mockImplementation(async (sessionId: string) => { + if (sessionId === 'broken-state') { + throw new Error('state failed') + } + return { + status: 'idle', + providerId: 'openai', + modelId: 'gpt-4', + permissionMode: 'full_access' + } + }) + + const sessions = await presenter.getSessionList() + + expect(sessions).toHaveLength(1) + expect(sessions[0].id).toBe('healthy-state') + expect(warnSpy).toHaveBeenCalledWith( + '[NewAgentPresenter] Skipping unavailable session id=broken-state agent=deepchat:', + expect.any(Error) + ) + warnSpy.mockRestore() + }) }) describe('getSession', () => { @@ -624,6 +731,26 @@ describe('NewAgentPresenter', () => { sqlitePresenter.newSessionsTable.get.mockReturnValue(undefined) expect(await presenter.getSession('unknown')).toBeNull() }) + + it('returns null when session agent is unavailable', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + sqlitePresenter.newSessionsTable.get.mockReturnValue({ + id: 's-disabled', + agent_id: 'disabled-agent', + title: 'Disabled', + project_dir: null, + is_pinned: 0, + created_at: 1000, + updated_at: 2000 + }) + + expect(await presenter.getSession('s-disabled')).toBeNull() + expect(warnSpy).toHaveBeenCalledWith( + '[NewAgentPresenter] Skipping unavailable session id=s-disabled agent=disabled-agent:', + expect.any(Error) + ) + warnSpy.mockRestore() + }) }) describe('getSessionCompactionState', () => { @@ -1126,5 +1253,38 @@ describe('NewAgentPresenter', () => { expect(session).not.toBeNull() expect(session!.id).toBe('s1') }) + + it('returns null and clears binding when bound session becomes unavailable', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + await presenter.activateSession(1, 's-disabled') + sqlitePresenter.newSessionsTable.get.mockReturnValueOnce({ + id: 's-disabled', + agent_id: 'disabled-agent', + title: 'Disabled', + project_dir: null, + is_pinned: 0, + created_at: 1000, + updated_at: 2000 + }) + + await expect(presenter.getActiveSession(1)).resolves.toBeNull() + + sqlitePresenter.newSessionsTable.get.mockReturnValue({ + id: 's-disabled', + agent_id: 'deepchat', + title: 'Recovered', + project_dir: null, + is_pinned: 0, + created_at: 1000, + updated_at: 2000 + }) + await expect(presenter.getActiveSession(1)).resolves.toBeNull() + expect(warnSpy).toHaveBeenCalledWith( + '[NewAgentPresenter] Skipping unavailable session id=s-disabled agent=disabled-agent:', + expect.any(Error) + ) + warnSpy.mockRestore() + }) }) }) diff --git a/test/renderer/stores/sessionStore.test.ts b/test/renderer/stores/sessionStore.test.ts index 56747fe9b..1f53e30bf 100644 --- a/test/renderer/stores/sessionStore.test.ts +++ b/test/renderer/stores/sessionStore.test.ts @@ -20,6 +20,12 @@ const setupStore = async () => { onRendererTabReady: vi.fn(), onRendererTabActivated: vi.fn() } + const pageRouter = { + goToChat: vi.fn(), + goToNewThread: vi.fn(), + currentRoute: 'chat' + } + const listeners = new Map void>>() vi.doMock('pinia', () => ({ defineStore: (_id: string, setup: () => unknown) => setup @@ -30,10 +36,7 @@ const setupStore = async () => { })) vi.doMock('@/stores/ui/pageRouter', () => ({ - usePageRouterStore: () => ({ - goToChat: vi.fn(), - goToNewThread: vi.fn() - }) + usePageRouterStore: () => pageRouter })) const clearStreamingState = vi.fn() vi.doMock('@/stores/ui/message', () => ({ @@ -44,7 +47,11 @@ const setupStore = async () => { })) ;(window as any).electron = { ipcRenderer: { - on: vi.fn(), + on: vi.fn((event: string, handler: (...args: any[]) => void) => { + const handlers = listeners.get(event) ?? [] + handlers.push(handler) + listeners.set(event, handlers) + }), removeListener: vi.fn() } } @@ -53,8 +60,14 @@ const setupStore = async () => { } const { useSessionStore } = await import('@/stores/ui/session') + const { SESSION_EVENTS } = await import('@/events') const store = useSessionStore() - return { store, clearStreamingState, newAgentPresenter } + const emitIpc = (event: string, payload?: unknown) => { + for (const handler of listeners.get(event) ?? []) { + handler(undefined, payload) + } + } + return { store, clearStreamingState, newAgentPresenter, pageRouter, emitIpc, SESSION_EVENTS } } describe('sessionStore.getFilteredGroups', () => { @@ -191,4 +204,27 @@ describe('sessionStore streaming cleanup', () => { expect(clearStreamingState).toHaveBeenCalledTimes(1) expect(store.activeSessionId.value).toBe('session-b') }) + + it('returns to new thread when active session becomes unavailable', async () => { + const { store, clearStreamingState, newAgentPresenter, pageRouter } = await setupStore() + store.activeSessionId.value = 'session-a' + pageRouter.currentRoute = 'chat' + newAgentPresenter.getActiveSession.mockResolvedValueOnce(null) + + await store.fetchSessions() + + expect(clearStreamingState).toHaveBeenCalledTimes(1) + expect(store.activeSessionId.value).toBeNull() + expect(pageRouter.goToNewThread).toHaveBeenCalledTimes(1) + }) + + it('reloads sessions when the session list update event fires', async () => { + const { newAgentPresenter, emitIpc, SESSION_EVENTS } = await setupStore() + + emitIpc(SESSION_EVENTS.LIST_UPDATED) + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(newAgentPresenter.getSessionList).toHaveBeenCalledTimes(1) + expect(newAgentPresenter.getActiveSession).toHaveBeenCalledTimes(1) + }) }) From 0234faddb169e6ea7cd79233b102b05d45677ad8 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Tue, 10 Mar 2026 22:58:37 +0800 Subject: [PATCH 2/4] fix(welcome): restore init flag flow --- src/preload/index.d.ts | 4 + src/preload/index.ts | 37 ++ src/renderer/src/App.vue | 48 +- src/renderer/src/pages/WelcomePage.vue | 21 +- src/renderer/src/router/index.ts | 2 +- src/renderer/src/stores/ui/pageRouter.ts | 39 +- src/renderer/src/views/ChatTabView.vue | 35 +- src/renderer/src/views/WelcomeView.vue | 450 ------------------- test/renderer/components/App.startup.test.ts | 208 +++++++++ test/renderer/components/WelcomePage.test.ts | 168 +++++++ test/renderer/stores/pageRouter.test.ts | 81 +++- 11 files changed, 559 insertions(+), 534 deletions(-) delete mode 100644 src/renderer/src/views/WelcomeView.vue create mode 100644 test/renderer/components/App.startup.test.ts create mode 100644 test/renderer/components/WelcomePage.test.ts diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index c44294752..6036ffb1c 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -14,6 +14,10 @@ declare global { toRelativePath?(filePath: string, baseDir?: string): string formatPathForInput?(filePath: string): string } + __deepchatDev?: { + goToWelcome(): boolean + clearWelcomeOverride(): boolean + } floatingButtonAPI: typeof floatingButtonAPI } } diff --git a/src/preload/index.ts b/src/preload/index.ts index b789aba1b..8d0b5c4e5 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -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 { @@ -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 @@ -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') diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index c8c0a10fc..8a1794677 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -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() @@ -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 } } @@ -192,7 +228,7 @@ const handleEscKey = (event: KeyboardEvent) => { } } -getInitComplete() +void ensureStartupWelcomeState() onMounted(() => { // Set initial body class @@ -332,7 +368,7 @@ onBeforeUnmount(() => {
- +
diff --git a/src/renderer/src/pages/WelcomePage.vue b/src/renderer/src/pages/WelcomePage.vue index c8cefa02a..222a44e75 100644 --- a/src/renderer/src/pages/WelcomePage.vue +++ b/src/renderer/src/pages/WelcomePage.vue @@ -60,12 +60,18 @@ diff --git a/src/renderer/src/router/index.ts b/src/renderer/src/router/index.ts index 584fcd814..c0ba8b401 100644 --- a/src/renderer/src/router/index.ts +++ b/src/renderer/src/router/index.ts @@ -19,7 +19,7 @@ const router = createRouter({ { path: '/welcome', name: 'welcome', - component: () => import('@/views/WelcomeView.vue'), + component: () => import('@/pages/WelcomePage.vue'), meta: { titleKey: 'routes.welcome', icon: 'lucide:message-square' diff --git a/src/renderer/src/stores/ui/pageRouter.ts b/src/renderer/src/stores/ui/pageRouter.ts index 547f741f2..1959c23de 100644 --- a/src/renderer/src/stores/ui/pageRouter.ts +++ b/src/renderer/src/stores/ui/pageRouter.ts @@ -1,15 +1,10 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { usePresenter } from '@/composables/usePresenter' -import { CONFIG_EVENTS } from '@/events' -export type PageRoute = - | { name: 'welcome' } - | { name: 'newThread' } - | { name: 'chat'; sessionId: string } +export type PageRoute = { name: 'newThread' } | { name: 'chat'; sessionId: string } export const usePageRouterStore = defineStore('pageRouter', () => { - const configPresenter = usePresenter('configPresenter') const newAgentPresenter = usePresenter('newAgentPresenter') // --- State --- @@ -20,14 +15,7 @@ export const usePageRouterStore = defineStore('pageRouter', () => { async function initialize(): Promise { try { - // 1. Check if any provider is enabled - const enabledProviders = configPresenter.getEnabledProviders() - if (!enabledProviders || enabledProviders.length === 0) { - route.value = { name: 'welcome' } - return - } - - // 2. Check for active new-agent session on this window content first + // 1. Check for active new-agent session on this window content first const webContentsId = window.api.getWebContentsId() const activeNewSession = await newAgentPresenter.getActiveSession(webContentsId) if (activeNewSession) { @@ -35,7 +23,7 @@ export const usePageRouterStore = defineStore('pageRouter', () => { return } - // 3. Default to new thread + // 2. Default to new thread route.value = { name: 'newThread' } } catch (e) { error.value = String(e) @@ -43,10 +31,6 @@ export const usePageRouterStore = defineStore('pageRouter', () => { } } - function goToWelcome(): void { - route.value = { name: 'welcome' } - } - function goToNewThread(): void { route.value = { name: 'newThread' } } @@ -60,27 +44,10 @@ export const usePageRouterStore = defineStore('pageRouter', () => { const currentRoute = computed(() => route.value.name) const chatSessionId = computed(() => (route.value.name === 'chat' ? route.value.sessionId : null)) - // --- Event Listeners --- - - window.electron.ipcRenderer.on(CONFIG_EVENTS.PROVIDER_CHANGED, async () => { - try { - const enabledProviders = configPresenter.getEnabledProviders() - if (!enabledProviders || enabledProviders.length === 0) { - goToWelcome() - } else if (route.value.name === 'welcome') { - // Providers became available, go to new thread - goToNewThread() - } - } catch (e) { - error.value = String(e) - } - }) - return { route, error, initialize, - goToWelcome, goToNewThread, goToChat, currentRoute, diff --git a/src/renderer/src/views/ChatTabView.vue b/src/renderer/src/views/ChatTabView.vue index b3e619726..790b7b3e6 100644 --- a/src/renderer/src/views/ChatTabView.vue +++ b/src/renderer/src/views/ChatTabView.vue @@ -1,12 +1,13 @@