美化 AGC 客户端设置页

收敛账号与角色协作展示文案

增加账号状态检测动画和状态卡片样式

将流式输出与联网检索整理为单列

更新设置页回归测试与配置护栏
This commit is contained in:
2026-09-03 13:39:32 +08:00
parent ac0f6bb783
commit 4f79a275a8
4 changed files with 201 additions and 61 deletions
@@ -1757,7 +1757,8 @@ for (const snippet of [
"'read_game_creator_app_config'",
"'write_game_creator_app_config'",
'aria-label="运行时配置"',
'官方账号服务(固定)',
'智能创作',
'账号状态',
'External Editor Base URL',
'External Editor API Key',
'runtime_config.save',
@@ -124,7 +124,7 @@ const runtimeSettingsSections = [
{
id: 'agents',
label: 'Agent 分工',
description: '查看各角色运行状态',
description: '角色协作状态',
icon: Bot,
},
{
@@ -258,6 +258,7 @@ export function RuntimeConfigDialog({
const [runtimeConfigBusy, setRuntimeConfigBusy] = useState(false);
const [effectiveLlmConfigStatus, setEffectiveLlmConfigStatus] =
useState<GameCreatorLlmConfigStatus | null>(llmConfigStatus);
const [llmConfigChecking, setLlmConfigChecking] = useState(false);
const [activeSection, setActiveSection] =
useState<RuntimeSettingsSection>('general');
const [clientExtensions, setClientExtensions] = useState<
@@ -502,6 +503,7 @@ export function RuntimeConfigDialog({
);
setRuntimeConfigDraft(config);
setRuntimeConfigStatus(`已读取:${result.path}`);
setLlmConfigChecking(true);
try {
const status = await invoke<GameCreatorLlmConfigStatus>(
'check_game_creator_llm_config',
@@ -510,6 +512,8 @@ export function RuntimeConfigDialog({
} catch {
// Older test fixtures and non-Tauri previews may not expose the
// diagnostic command; the config read itself remains usable.
} finally {
setLlmConfigChecking(false);
}
onLog?.('runtime_config.read');
} catch (error) {
@@ -730,13 +734,7 @@ export function RuntimeConfigDialog({
<>
<div className="runtime-settings-readonly-field">
<span></span>
<strong></strong>
<small></small>
</div>
<div className="runtime-settings-readonly-field">
<span></span>
<strong></strong>
<small>使</small>
<strong></strong>
</div>
{(() => {
const credential = formatAccountCredentialState(
@@ -744,17 +742,28 @@ export function RuntimeConfigDialog({
);
return (
<div
className="runtime-settings-readonly-field"
className={`runtime-settings-readonly-field runtime-settings-account-state${
llmConfigChecking ? ' is-checking' : ''
}`}
data-tone={credential.tone}
data-checking={llmConfigChecking ? 'true' : 'false'}
data-testid="runtime-settings-account-credential-state"
>
<span></span>
<strong>{credential.label}</strong>
<small>
{credential.tone === 'success'
? '当前账号已具备智能创作权限。'
: '请返回登录页完成登录或重新登录;普通用户无需填写任何智能服务凭据。'}
</small>
<span></span>
<strong>
{llmConfigChecking ? (
<>
<LoaderCircle
className="is-spinning"
size={14}
aria-hidden="true"
/>
</>
) : (
credential.label
)}
</strong>
</div>
);
})()}
@@ -780,34 +789,36 @@ export function RuntimeConfigDialog({
))}
</select>
</label>
<label className="settings-checkbox">
<input
aria-label="流式输出"
type="checkbox"
checked={runtimeConfigDraft.llm.stream}
onChange={(event) =>
updateRuntimeLlmConfig(
'stream',
event.currentTarget.checked,
)
}
/>
</label>
<label className="settings-checkbox">
<input
aria-label="联网检索"
type="checkbox"
checked={runtimeConfigDraft.llm.webSearchEnabled}
onChange={(event) =>
updateRuntimeLlmConfig(
'webSearchEnabled',
event.currentTarget.checked,
)
}
/>
</label>
<div className="runtime-settings-option-stack">
<label className="settings-checkbox">
<input
aria-label="流式输出"
type="checkbox"
checked={runtimeConfigDraft.llm.stream}
onChange={(event) =>
updateRuntimeLlmConfig(
'stream',
event.currentTarget.checked,
)
}
/>
</label>
<label className="settings-checkbox">
<input
aria-label="联网检索"
type="checkbox"
checked={runtimeConfigDraft.llm.webSearchEnabled}
onChange={(event) =>
updateRuntimeLlmConfig(
'webSearchEnabled',
event.currentTarget.checked,
)
}
/>
</label>
</div>
</>
) : null}
</>
@@ -912,9 +923,8 @@ export function RuntimeConfigDialog({
) : null}
{activeSection === 'agents' ? (
<div className="runtime-settings-readonly-field">
<span></span>
<strong>使</strong>
<small></small>
<span></span>
<strong></strong>
</div>
) : null}
{activeSection === 'connections' ? (
+63
View File
@@ -3901,6 +3901,69 @@ h2 {
align-content: start;
}
.runtime-settings-readonly-field,
.runtime-settings-option-stack > label {
display: grid;
gap: 7px;
min-width: 0;
padding: 13px;
border: 1px solid var(--platform-subpanel-border);
border-radius: 12px;
background: linear-gradient(
145deg,
var(--runtime-settings-subpanel-fill),
color-mix(in srgb, var(--runtime-settings-subpanel-fill) 78%, #f0e7dc)
);
color: var(--platform-text-base);
}
.runtime-settings-readonly-field > span,
.runtime-settings-option-stack > label {
font-size: 11px;
}
.runtime-settings-readonly-field > span {
color: var(--platform-text-soft);
}
.runtime-settings-readonly-field > strong {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 20px;
color: var(--platform-text-strong);
font-size: 13px;
font-weight: 800;
}
.runtime-settings-readonly-field[data-tone='success'] {
border-color: color-mix(
in srgb,
var(--platform-success-border) 70%,
transparent
);
background: linear-gradient(145deg, #fbfffc, #f1faf2);
}
.runtime-settings-readonly-field[data-checking='true'] {
border-color: var(--platform-nav-active-border);
background: linear-gradient(145deg, #fffdf9, #f8f1e7);
}
.runtime-settings-account-state[data-checking='true'] > strong {
color: var(--platform-accent);
}
.runtime-settings-readonly-field .is-spinning {
animation: runtime-settings-spin 0.9s linear infinite;
}
.runtime-settings-option-stack {
display: grid;
gap: 10px;
min-width: 0;
}
.runtime-settings-fields > label {
padding: 13px;
border: 1px solid var(--platform-subpanel-border);
@@ -255,6 +255,69 @@ export function registerAgentStatusDerivationTests() {
}
export function registerRuntimeSettingsTests() {
it('shows a checking animation while account status is being verified', async () => {
let resolveAccountCheck!: (status: unknown) => void;
const accountCheck = new Promise<unknown>((resolve) => {
resolveAccountCheck = 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: '',
model: '',
apiKind: 'openai_responses',
reasoningEffort: 'high',
stream: true,
webSearchEnabled: true,
contextWindowTokens: 128000,
autoCompactTokenLimit: 64000,
toolOutputTokenLimit: 12000,
requestTimeoutMs: 180000,
maxRetries: 2,
retryBackoffMs: 500,
},
agentLlm: {},
editorApi: { baseUrl: '', apiKey: '' },
},
});
}
if (command === 'check_game_creator_llm_config') {
return accountCheck;
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
const accountState = await screen.findByTestId(
'runtime-settings-account-credential-state',
);
await waitFor(() => {
expect(accountState.getAttribute('data-checking')).toBe('true');
});
expect(accountState.querySelector('.is-spinning')).not.toBeNull();
expect(screen.getByText('正在检测')).not.toBeNull();
await act(async () => {
resolveAccountCheck({
accountCredentialState: 'ready',
configured: true,
});
});
await waitFor(() => {
expect(accountState.getAttribute('data-checking')).toBe('false');
expect(accountState.querySelector('.is-spinning')).toBeNull();
});
expect(screen.getByText('账号权限已就绪')).not.toBeNull();
});
it('distinguishes loading the client extension list from an empty list', async () => {
let resolveExtensions!: (items: unknown[]) => void;
const extensionsRead = new Promise<unknown[]>((resolve) => {
@@ -404,9 +467,11 @@ export function registerRuntimeSettingsTests() {
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
expect(await screen.findByText('陶泥儿智能创作(固定)')).not.toBeNull();
expect(await screen.findByText('智能创作')).not.toBeNull();
expect(screen.queryByLabelText('Agent 模式')).toBeNull();
expect(screen.getByText('官方账号服务(固定)')).not.toBeNull();
expect(screen.getByText('账号状态')).not.toBeNull();
expect(screen.queryByText('官方账号服务(固定)')).toBeNull();
expect(screen.queryByText('普通用户无需填写任何智能服务凭据。')).toBeNull();
expect(
screen.queryByText(/router\.genarrative\.world|gpt-5\.6-sol/),
).toBeNull();
@@ -477,7 +542,7 @@ export function registerRuntimeSettingsTests() {
renderLauncherAt('/?launcher');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
expect(await screen.findByText('陶泥儿智能创作(固定)')).not.toBeNull();
expect(await screen.findByText('智能创作')).not.toBeNull();
expect(screen.queryByLabelText('Agent 模式')).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '保存' }));
@@ -531,7 +596,7 @@ export function registerRuntimeSettingsTests() {
fireEvent.click(screen.getByRole('button', { name: '配置' }));
const dialog = await screen.findByRole('dialog', { name: '运行时配置' });
expect(screen.getByText('官方账号服务(固定)')).not.toBeNull();
expect(screen.getByText('账号状态')).not.toBeNull();
expect(
screen.queryByText(/router\.genarrative\.world|gpt-5\.6-sol/),
).toBeNull();
@@ -822,7 +887,7 @@ export function registerPublishedRuntimeSettingsTests() {
fireEvent.click(screen.getByRole('button', { name: '配置' }));
expect(await screen.findByText('官方账号服务(固定)')).not.toBeNull();
expect(await screen.findByText('账号状态')).not.toBeNull();
expect(
screen.queryByText(/router\.genarrative\.world|gpt-5\.6-sol/),
).toBeNull();
@@ -847,7 +912,8 @@ export function registerPublishedRuntimeSettingsTests() {
expect(screen.queryByLabelText('External Editor Base URL')).toBeNull();
expect(screen.queryByLabelText('External Editor API Key')).toBeNull();
fireEvent.click(screen.getByRole('button', { name: /Agent 分工/ }));
expect(screen.getByText('所有角色使用统一账号服务')).not.toBeNull();
expect(screen.getByText('已启用')).not.toBeNull();
expect(screen.queryByText('所有角色使用统一账号服务')).toBeNull();
fireEvent.click(screen.getByRole('button', { name: /常用设置/ }));
expect(screen.getByLabelText('联网检索')).toHaveProperty('checked', false);
fireEvent.click(screen.getByRole('button', { name: /高级参数/ }));
@@ -864,7 +930,7 @@ export function registerPublishedRuntimeSettingsTests() {
'12000',
);
fireEvent.click(screen.getByRole('button', { name: /Agent 分工/ }));
expect(screen.getByText('所有角色使用统一账号服务')).not.toBeNull();
expect(screen.getByText('已启用')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: /常用设置/ }));
expect(screen.getByLabelText('推理档')).toHaveProperty('value', 'medium');
@@ -895,7 +961,7 @@ export function registerPublishedRuntimeSettingsTests() {
target: { value: '800' },
});
fireEvent.click(screen.getByRole('button', { name: /Agent 分工/ }));
expect(screen.getByText('所有角色使用统一账号服务')).not.toBeNull();
expect(screen.getByText('已启用')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: /连接与工具/ }));
expect(screen.queryByLabelText('External Editor Base URL')).toBeNull();
expect(screen.queryByLabelText('External Editor API Key')).toBeNull();
@@ -943,7 +1009,7 @@ export function registerPublishedRuntimeSettingsTests() {
fireEvent.click(screen.getByRole('button', { name: /常用设置/ }));
expect(screen.getByLabelText('联网检索')).toHaveProperty('checked', true);
fireEvent.click(screen.getByRole('button', { name: /Agent 分工/ }));
expect(screen.getByText('所有角色使用统一账号服务')).not.toBeNull();
expect(screen.getByText('已启用')).not.toBeNull();
expect(screen.getByLabelText('聊天').textContent).not.toContain(
'unit-new-secret-value',
);
@@ -983,9 +1049,9 @@ export function registerPublishedRuntimeSettingsTests() {
expect(screen.getByText('已恢复默认配置,保存后生效')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: /常用设置/ }));
expect(screen.getByText('陶泥儿智能创作(固定)')).not.toBeNull();
expect(screen.getByText('智能创作')).not.toBeNull();
expect(screen.queryByLabelText('Agent 模式')).toBeNull();
expect(screen.getByText('官方账号服务(固定)')).not.toBeNull();
expect(screen.getByText('账号状态')).not.toBeNull();
expect(
screen.queryByText(/router\.genarrative\.world|gpt-5\.6-sol/),
).toBeNull();
@@ -995,7 +1061,7 @@ export function registerPublishedRuntimeSettingsTests() {
expect(screen.getByLabelText('推理档')).toHaveProperty('value', 'max');
expect(screen.getByLabelText('联网检索')).toHaveProperty('checked', true);
fireEvent.click(screen.getByRole('button', { name: /Agent 分工/ }));
expect(screen.getByText('所有角色使用统一账号服务')).not.toBeNull();
expect(screen.getByText('已启用')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: /高级参数/ }));
expect(screen.getByLabelText('上下文窗口 tokens')).toHaveProperty(
'value',
@@ -1086,7 +1152,7 @@ export function registerPublishedRuntimeSettingsTests() {
renderAppAt('/');
fireEvent.click(screen.getByRole('button', { name: '配置' }));
expect(await screen.findByText('官方账号服务(固定)')).not.toBeNull();
expect(await screen.findByText('账号状态')).not.toBeNull();
expect(screen.queryByLabelText('LLM Provider')).toBeNull();
expect(screen.queryByLabelText('LLM API Key')).toBeNull();
expect(screen.queryByLabelText('LLM Base URL')).toBeNull();