From fa9d4579c804274c862476019bc85fc9a5054ada Mon Sep 17 00:00:00 2001 From: Linghong Date: Tue, 1 Sep 2026 08:28:02 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=A9=E5=B1=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=88=9D=E6=AC=A1=E5=8A=A0=E8=BD=BD=E7=A9=BA=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加客户端扩展列表 loading、ready 和 error 状态 初次异步读取期间显示加载提示,读取成功为空时才显示空列表提示 补充运行时设置界面回归测试并同步技术方案 --- .../runtime-config/RuntimeConfigDialog.tsx | 26 ++++++++- .../appSurface/runtime-settings.suite.ts | 53 +++++++++++++++++++ ...roject客户端Skill与MCP扩展导入方案-2026-08-31.md | 2 + 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx b/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx index 81f34e29b..49e57894c 100644 --- a/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx +++ b/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx @@ -100,6 +100,8 @@ type RuntimeConfigToast = { message: string; }; +type ClientExtensionsLoadState = 'loading' | 'ready' | 'error'; + const runtimeSettingsSections = [ { id: 'general', @@ -399,6 +401,8 @@ export function RuntimeConfigDialog({ ClientExtensionItem[] >([]); const [clientExtensionsBusy, setClientExtensionsBusy] = useState(false); + const [clientExtensionsLoadState, setClientExtensionsLoadState] = + useState('loading'); const [clientExtensionsStatus, setClientExtensionsStatus] = useState(''); const [editingExtensionId, setEditingExtensionId] = useState( null, @@ -430,17 +434,21 @@ export function RuntimeConfigDialog({ async function readClientExtensions() { const invoke = resolveTauriInvoke(); if (!invoke) { + setClientExtensionsLoadState('error'); setClientExtensionsStatus('需要在 Tauri App 内运行'); return; } + setClientExtensionsLoadState('loading'); setClientExtensionsBusy(true); try { const result = await invoke( 'list_client_extensions', ); setClientExtensions(result); + setClientExtensionsLoadState('ready'); setClientExtensionsStatus(''); } catch (error) { + setClientExtensionsLoadState('error'); setClientExtensionsStatus( error instanceof Error ? error.message : String(error), ); @@ -477,6 +485,7 @@ export function RuntimeConfigDialog({ 'list_client_extensions', ); setClientExtensions(refreshed); + setClientExtensionsLoadState('ready'); const importedCount = result.imported.length; setClientExtensionsStatus( importedCount > 0 @@ -1478,12 +1487,25 @@ export function RuntimeConfigDialog({ className="runtime-settings-section runtime-settings-extensions" aria-label="客户端扩展" > - {clientExtensionsStatus ? ( + {clientExtensionsStatus && + clientExtensionsLoadState !== 'error' ? (

{clientExtensionsStatus}

) : null} - {clientExtensions.length > 0 ? ( + {clientExtensionsLoadState === 'loading' ? ( +
+ 正在加载扩展 + 正在读取已导入的 Skill 和 MCP。 +
+ ) : clientExtensionsLoadState === 'error' ? ( +
+ 扩展列表加载失败 + + {clientExtensionsStatus || '暂时无法读取扩展列表。'} + +
+ ) : clientExtensions.length > 0 ? (
{clientExtensions.map((item) => { const editing = editingExtensionId === item.id; diff --git a/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts index e67edd306..8ba0967cd 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts @@ -255,6 +255,59 @@ export function registerAgentStatusDerivationTests() { } export function registerRuntimeSettingsTests() { + it('distinguishes loading the client extension list from an empty list', async () => { + let resolveExtensions!: (items: unknown[]) => void; + const extensionsRead = new Promise((resolve) => { + resolveExtensions = resolve; + }); + const invoke = vi.fn((command: string) => { + if (command === 'read_game_creator_app_config') { + return Promise.resolve({ + path: '/home/test/AppData/game-creator.config.json', + config: { + agentMode: 'codex_app_server', + llm: { + apiKey: '', + baseUrl: 'https://llm.example.test/v1', + model: 'gpt-settings-extensions', + apiKind: 'openai_responses', + reasoningEffort: 'high', + stream: false, + webSearchEnabled: false, + requestTimeoutMs: 180000, + maxRetries: 0, + retryBackoffMs: 500, + }, + editorApi: { + baseUrl: 'http://127.0.0.1:8082', + apiKey: '', + }, + }, + }); + } + if (command === 'list_client_extensions') { + return extensionsRead; + } + throw new Error(`unexpected invoke ${command}`); + }); + window.__TAURI__ = { core: { invoke } }; + renderLauncherAt('/?launcher'); + + fireEvent.click(screen.getByRole('button', { name: '配置' })); + await screen.findByRole('dialog', { name: '运行时配置' }); + fireEvent.click(screen.getByRole('button', { name: /^扩展/ })); + + expect(await screen.findByText('正在加载扩展')).not.toBeNull(); + expect(screen.queryByText('还没有导入扩展')).toBeNull(); + + await act(async () => { + resolveExtensions([]); + }); + + expect(await screen.findByText('还没有导入扩展')).not.toBeNull(); + expect(screen.queryByText('正在加载扩展')).toBeNull(); + }); + it('shows the client version on the About settings page', async () => { const invoke = vi.fn(async (command: string) => { if (command === 'read_game_creator_app_config') { diff --git a/docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md b/docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md index 4327cc6b0..a1554b879 100644 --- a/docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md +++ b/docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md @@ -280,6 +280,8 @@ MCP · 来自 my-plugin.zip 启动失败 - 当前不可用; - 启动失败。 +扩展列表首次读取完成前显示加载态;只有读取成功且结果为空时才显示“还没有导入扩展”,读取失败时显示加载错误,不把未完成的异步读取当作空列表。 + “完整列表”指所有用户导入的独立扩展项,不指 zip 内每个普通文件或每个 MCP tool。 ### 7.3 导入后的默认状态