mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-17 10:52:14 +08:00
♻️ refactor(settings/ai): 聊天入口打开 AI 设置改走设置中心
- 从 AI 聊天面板打开设置时进入设置中心服务-AI 页 - 移除独立 AISettingsModal 挂载,复用 AISettingsContent - 安全更新修复跳转同步走设置中心,关闭后可回到修复流程 - 更新设置中心与工具中心相关源码级测试断言
This commit is contained in:
@@ -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('<AISettingsModal');
|
||||
});
|
||||
|
||||
it('opens the about group directly instead of showing a one-item list', () => {
|
||||
expect(appSource).toContain('const resolveSettingsCenterGroupInitialPane = (group: SettingsCenterGroupKey): SettingsCenterPaneState | null => (');
|
||||
expect(appSource).toContain("group === 'about' ? { key: 'about-go-navi', group: 'about' } : null");
|
||||
|
||||
@@ -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('<AISettingsModal');
|
||||
it('loads editable AI provider details inside settings-center AI pane content', () => {
|
||||
// 聊天/入口打开 AI 配置走设置中心 AISettingsContent,不再挂独立 AISettingsModal
|
||||
expect(appSource).toContain('<AISettingsContent');
|
||||
expect(appSource).toContain("activeSettingsCenterPane.key === 'ai'");
|
||||
expect(appSource).not.toContain('<AISettingsModal');
|
||||
const modalSource = readFileSync(new URL('./components/AISettingsModal.tsx', import.meta.url), 'utf8');
|
||||
expect(modalSource).toContain("typeof Service?.AIGetEditableProvider === 'function'");
|
||||
expect(modalSource).toContain('await Service.AIGetEditableProvider(p.id)');
|
||||
|
||||
@@ -9,6 +9,7 @@ import { BrowserOpenURL, Environment, EventsOn, WindowFullscreen, WindowGetPosit
|
||||
import Sidebar from './components/Sidebar';
|
||||
import TabManager from './components/TabManager';
|
||||
import FloatingWorkbenchWindows from './components/FloatingWorkbenchWindows';
|
||||
import FloatingAIChatWindow from './components/FloatingAIChatWindow';
|
||||
import FloatingQueryResultWindows from './components/FloatingQueryResultWindows';
|
||||
import ConnectionModal from './components/ConnectionModal';
|
||||
import SnippetSettingsModal from './components/SnippetSettingsModal';
|
||||
@@ -18,7 +19,7 @@ import { type DataSyncEntryMode } from './components/dataSyncEntryMode';
|
||||
import DriverManagerModal from './components/DriverManagerModal';
|
||||
import LinuxCJKFontBanner from './components/LinuxCJKFontBanner';
|
||||
import LogPanel from './components/LogPanel';
|
||||
import AISettingsModal, { AISettingsContent } from './components/AISettingsModal';
|
||||
import { AISettingsContent } from './components/AISettingsModal';
|
||||
import AIChatPanel from './components/AIChatPanel';
|
||||
import AIPanelErrorBoundary from './components/ai/AIPanelErrorBoundary';
|
||||
import SecurityUpdateBanner from './components/SecurityUpdateBanner';
|
||||
@@ -836,6 +837,9 @@ function App() {
|
||||
const sidebarWidth = useStore(state => 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 && (
|
||||
<div
|
||||
className={aiPanelOverlayActive ? 'gn-v2-ai-panel-overlay' : undefined}
|
||||
style={aiPanelOverlayActive
|
||||
@@ -6601,13 +6621,33 @@ function App() {
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<AIChatPanel width={aiPanelRenderWidth} darkMode={darkMode} bgColor={bgContent} onClose={() => setAIPanelVisible(false)} onOpenSettings={() => {
|
||||
handleOpenAISettings();
|
||||
}} overlayTheme={overlayTheme} />
|
||||
<AIChatPanel
|
||||
width={aiPanelRenderWidth}
|
||||
darkMode={darkMode}
|
||||
bgColor={bgContent}
|
||||
presentation="dock"
|
||||
onClose={() => setAIPanelVisible(false)}
|
||||
onDetach={() => detachAIChatPanel()}
|
||||
onOpenSettings={() => {
|
||||
handleOpenAISettings();
|
||||
}}
|
||||
overlayTheme={overlayTheme}
|
||||
/>
|
||||
</AIPanelErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{aiPanelVisible && aiChatDetached && (
|
||||
<FloatingAIChatWindow
|
||||
darkMode={darkMode}
|
||||
bgColor={bgContent}
|
||||
overlayTheme={overlayTheme}
|
||||
renderNonce={aiPanelRenderNonce}
|
||||
onOpenSettings={() => handleOpenAISettings()}
|
||||
onRenderError={handleAIPanelRenderError}
|
||||
onRetryRender={handleRetryAIPanelRender}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isV2Ui && isLogPanelOpen && (
|
||||
<LogPanel
|
||||
@@ -7282,10 +7322,7 @@ function App() {
|
||||
<Modal
|
||||
title={renderUtilityModalTitle(<SettingOutlined />, 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 && (
|
||||
<AISettingsModal
|
||||
open={isAISettingsOpen}
|
||||
onClose={handleCloseAISettings}
|
||||
darkMode={darkMode}
|
||||
overlayTheme={overlayTheme}
|
||||
focusProviderId={focusedAIProviderId}
|
||||
onBeforeExternalMCPUse={handlePrepareExternalMCPUse}
|
||||
/>
|
||||
)}
|
||||
<ConnectionPackagePasswordModal
|
||||
open={connectionPackageDialog.open && !(isToolsModalOpen && activeToolCenterPane?.key === 'connection-package')}
|
||||
title={connectionPackageDialog.mode === 'export'
|
||||
|
||||
Reference in New Issue
Block a user