diff --git a/frontend/src/App.settings-center.test.ts b/frontend/src/App.settings-center.test.ts index ef4ce2bd..e9228eee 100644 --- a/frontend/src/App.settings-center.test.ts +++ b/frontend/src/App.settings-center.test.ts @@ -105,6 +105,14 @@ describe('settings center layout', () => { expect(appSource).toContain('renderSettingsCenterAboutPane()'); }); + it('opens AI settings from the chat panel via settings center instead of a standalone modal', () => { + expect(appSource).toContain('const handleOpenAISettings = useCallback((providerId?: string) => {'); + expect(appSource).toContain("setActiveSettingsCenterPane({ key: 'ai', group: 'services' })"); + expect(appSource).toContain('setIsSettingsModalOpen(true)'); + expect(appSource).not.toContain('setIsAISettingsOpen(true)'); + expect(appSource).not.toContain(' { expect(appSource).toContain('const resolveSettingsCenterGroupInitialPane = (group: SettingsCenterGroupKey): SettingsCenterPaneState | null => ('); expect(appSource).toContain("group === 'about' ? { key: 'about-go-navi', group: 'about' } : null"); diff --git a/frontend/src/App.tool-center.test.ts b/frontend/src/App.tool-center.test.ts index 10febf88..d68bf875 100644 --- a/frontend/src/App.tool-center.test.ts +++ b/frontend/src/App.tool-center.test.ts @@ -304,7 +304,7 @@ describe('tool center menu entries', () => { expect(appSource).toContain('{isSettingsModalOpen && ('); expect(appSource).toContain('{isThemeModalOpen && ('); expect(appSource).toContain('{isShortcutModalOpen && ('); - expect(appSource).toContain('{isAISettingsOpen && ('); + expect(appSource).not.toContain('{isAISettingsOpen && ('); expect(appSource).toContain('{isDriverModalOpen && ('); expect(appSource).toContain('{isSyncModalOpen && ('); }); @@ -320,8 +320,11 @@ describe('tool center menu entries', () => { expect(appSource).toContain('setIsModalOpen(true);'); }); - it('loads editable AI provider details before opening the edit modal so stored api keys can be shown', () => { - expect(appSource).toContain(' { + // 聊天/入口打开 AI 配置走设置中心 AISettingsContent,不再挂独立 AISettingsModal + expect(appSource).toContain(' state.sidebarWidth); const setSidebarWidth = useStore(state => state.setSidebarWidth); const aiPanelVisible = useStore(state => state.aiPanelVisible); + const detachedAIChatWindow = useStore(state => state.detachedAIChatWindow); + const detachAIChatPanel = useStore(state => state.detachAIChatPanel); + const aiChatDetached = Boolean(detachedAIChatWindow); const toggleAIPanel = useStore(state => state.toggleAIPanel); const setAIPanelVisible = useStore(state => state.setAIPanelVisible); const windowDiagSequenceRef = React.useRef(0); @@ -1905,7 +1909,9 @@ function App() { setIsSecurityUpdateSettingsOpen(false); setSecurityUpdateRepairSource(repairEntry.repairSource); setFocusedAIProviderId(repairEntry.providerId); - setIsAISettingsOpen(true); + setActiveSettingsCenterGroupKey('services'); + setActiveSettingsCenterPane({ key: 'ai', group: 'services' }); + setIsSettingsModalOpen(true); return; } if (repairEntry.type === 'retry') { @@ -2636,7 +2642,7 @@ function App() { const [selectedDataRootPath, setSelectedDataRootPath] = useState(''); const [dataRootLoading, setDataRootLoading] = useState(false); const [dataRootApplying, setDataRootApplying] = useState(false); - const [isAISettingsOpen, setIsAISettingsOpen] = useState(false); + const aiEntryPlacement = resolveAIEntryPlacement(); const legacyAiEdgeHandleAttachment = resolveLegacyAIEdgeHandleAttachment(aiPanelVisible); const aiPanelOverlayActive = aiPanelVisible && shouldOverlayAIPanel({ @@ -2892,15 +2898,36 @@ function App() { setActiveSettingsCenterPane({ key, group }); setIsSettingsModalOpen(true); }, []); + const finalizeSecurityRepairReturnFromAISettings = useCallback(() => { + const reopenSecurityUpdateDetails = shouldReopenSecurityUpdateDetails(securityUpdateRepairSource); + setFocusedAIProviderId(undefined); + setSecurityUpdateRepairSource(null); + if (reopenSecurityUpdateDetails) { + setIsSecurityUpdateSettingsOpen(true); + } + }, [securityUpdateRepairSource]); const handleBackFromSettingsCenterPane = useCallback(() => { + const leavingAI = activeSettingsCenterPane?.key === 'ai'; const returnGroup = activeSettingsCenterPane?.group ?? activeSettingsCenterGroupKey; setActiveSettingsCenterGroupKey(returnGroup); setActiveSettingsCenterPane(null); - }, [activeSettingsCenterGroupKey, activeSettingsCenterPane?.group]); + if (leavingAI) { + finalizeSecurityRepairReturnFromAISettings(); + } + }, [ + activeSettingsCenterGroupKey, + activeSettingsCenterPane?.group, + activeSettingsCenterPane?.key, + finalizeSecurityRepairReturnFromAISettings, + ]); const handleCancelSettingsCenterPane = useCallback(() => { + const leavingAI = activeSettingsCenterPane?.key === 'ai'; setActiveSettingsCenterPane(null); setIsSettingsModalOpen(false); - }, []); + if (leavingAI) { + finalizeSecurityRepairReturnFromAISettings(); + } + }, [activeSettingsCenterPane?.key, finalizeSecurityRepairReturnFromAISettings]); const isSettingsAboutPaneOpen = isSettingsModalOpen && activeSettingsCenterPane?.key === 'about-go-navi'; const isSettingsAboutPaneOpenRef = useRef(false); useEffect(() => { @@ -3245,10 +3272,13 @@ function App() { } }, [securityUpdateRepairSource]); + /** 从聊天面板等入口打开 AI 配置:走设置中心,不再弹独立 AISettingsModal */ const handleOpenAISettings = useCallback((providerId?: string) => { setSecurityUpdateRepairSource(null); setFocusedAIProviderId(providerId); - setIsAISettingsOpen(true); + setActiveSettingsCenterGroupKey('services'); + setActiveSettingsCenterPane({ key: 'ai', group: 'services' }); + setIsSettingsModalOpen(true); }, []); const handleAIPanelRenderError = useCallback((error: Error, errorInfo: React.ErrorInfo) => { @@ -3268,16 +3298,6 @@ function App() { setAiPanelRenderNonce((current) => current + 1); }, []); - const handleCloseAISettings = useCallback(() => { - const reopenSecurityUpdateDetails = shouldReopenSecurityUpdateDetails(securityUpdateRepairSource); - setIsAISettingsOpen(false); - setFocusedAIProviderId(undefined); - setSecurityUpdateRepairSource(null); - if (reopenSecurityUpdateDetails) { - setIsSecurityUpdateSettingsOpen(true); - } - }, [securityUpdateRepairSource]); - const handleWebLogout = useCallback(async () => { try { await fetch('/__gonavi/auth/logout', { @@ -6498,7 +6518,7 @@ function App() { )} )} - {aiPanelVisible && ( + {aiPanelVisible && !aiChatDetached && (
)} > - setAIPanelVisible(false)} onOpenSettings={() => { - handleOpenAISettings(); - }} overlayTheme={overlayTheme} /> + setAIPanelVisible(false)} + onDetach={() => detachAIChatPanel()} + onOpenSettings={() => { + handleOpenAISettings(); + }} + overlayTheme={overlayTheme} + />
)} + {aiPanelVisible && aiChatDetached && ( + handleOpenAISettings()} + onRenderError={handleAIPanelRenderError} + onRetryRender={handleRetryAIPanelRender} + /> + )} {!isV2Ui && isLogPanelOpen && ( , t('app.settings.title'), t('app.settings.description'))} open={isSettingsModalOpen} - onCancel={() => { - setActiveSettingsCenterPane(null); - setIsSettingsModalOpen(false); - }} + onCancel={handleCancelSettingsCenterPane} footer={null} centered width={1080} @@ -7632,16 +7669,6 @@ function App() { overlayTheme={overlayTheme} surfaceOpacity={effectiveOpacity} /> - {isAISettingsOpen && ( - - )}