修复扩展列表初次加载空态
增加客户端扩展列表 loading、ready 和 error 状态 初次异步读取期间显示加载提示,读取成功为空时才显示空列表提示 补充运行时设置界面回归测试并同步技术方案
This commit is contained in:
@@ -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<ClientExtensionsLoadState>('loading');
|
||||
const [clientExtensionsStatus, setClientExtensionsStatus] = useState('');
|
||||
const [editingExtensionId, setEditingExtensionId] = useState<string | null>(
|
||||
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<ClientExtensionItem[]>(
|
||||
'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' ? (
|
||||
<p className="runtime-settings-inline-status" role="status">
|
||||
{clientExtensionsStatus}
|
||||
</p>
|
||||
) : null}
|
||||
{clientExtensions.length > 0 ? (
|
||||
{clientExtensionsLoadState === 'loading' ? (
|
||||
<div className="runtime-settings-empty-state" role="status">
|
||||
<strong>正在加载扩展</strong>
|
||||
<span>正在读取已导入的 Skill 和 MCP。</span>
|
||||
</div>
|
||||
) : clientExtensionsLoadState === 'error' ? (
|
||||
<div className="runtime-settings-empty-state" role="alert">
|
||||
<strong>扩展列表加载失败</strong>
|
||||
<span>
|
||||
{clientExtensionsStatus || '暂时无法读取扩展列表。'}
|
||||
</span>
|
||||
</div>
|
||||
) : clientExtensions.length > 0 ? (
|
||||
<div className="runtime-settings-extension-list">
|
||||
{clientExtensions.map((item) => {
|
||||
const editing = editingExtensionId === item.id;
|
||||
|
||||
@@ -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<unknown[]>((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') {
|
||||
|
||||
@@ -280,6 +280,8 @@ MCP · 来自 my-plugin.zip 启动失败
|
||||
- 当前不可用;
|
||||
- 启动失败。
|
||||
|
||||
扩展列表首次读取完成前显示加载态;只有读取成功且结果为空时才显示“还没有导入扩展”,读取失败时显示加载错误,不把未完成的异步读取当作空列表。
|
||||
|
||||
“完整列表”指所有用户导入的独立扩展项,不指 zip 内每个普通文件或每个 MCP tool。
|
||||
|
||||
### 7.3 导入后的默认状态
|
||||
|
||||
Reference in New Issue
Block a user