style(usage): show reasoning effort under the model name

Drop the separate reasoning-effort column from request details and render the raw effort, or auto, directly beneath the model name. Include it in the model tooltip, reset saved layouts that still use the old default column, and keep the field in CSV export.
This commit is contained in:
lzt404 committed 2026-10-06 01:22:41 +08:00
1 parent 56a88ccb6f
commit 6e34850780
22 files changed
+1130 -384

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
import { describe, expect, it } from 'bun:test';
import { renderToStaticMarkup } from 'react-dom/server';
import { CredentialAdvancedFields, CredentialHeadersEditor } from '../src/components/CredentialAdvancedFields';
import { I18nProvider } from '../src/i18n';
const renderAdvanced = (name: string, advanced: Record<string, unknown>, provider = '') => renderToStaticMarkup(
<I18nProvider>
<CredentialAdvancedFields name={name} provider={provider} advanced={advanced} onChange={() => {}} />
</I18nProvider>,
);
describe('credential settings interaction', () => {
it('lets a Codex credential edit aliases directly and hides Claude cloaking', () => {
const html = renderAdvanced('codex-cbb7bd85-user-team.json', {});
expect(html).toContain('Add alias');
expect(html).toContain('Credential timezone');
expect(html).toContain('Asia/Shanghai');
expect(html).not.toContain('Request cloaking');
expect(html).not.toContain('Inherit / not set');
expect(html).not.toContain('Model aliases and Claude compatibility');
});
it('shows Claude controls as normal fields, including an existing override on another provider', () => {
expect(renderAdvanced('claude-user.json', {})).toContain('Request cloaking');
const overridden = renderAdvanced('codex-user.json', { cloak_mode: 'always' });
expect(overridden).toContain('Request cloaking');
expect(overridden).toContain('value="always" selected');
});
it('edits headers as rows instead of an empty JSON object', () => {
const html = renderToStaticMarkup(<I18nProvider><CredentialHeadersEditor value="{}" onChange={() => {}} /></I18nProvider>);
expect(html).toContain('No custom headers.');
expect(html).toContain('Add header');
expect(html).not.toContain('<textarea');
});
});
+20
View File
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'bun:test';
import { renderToStaticMarkup } from 'react-dom/server';
import { CredentialAdvancedFields } from '../src/components/CredentialAdvancedFields';
import { I18nProvider } from '../src/i18n';
const html = renderToStaticMarkup(
<I18nProvider>
<CredentialAdvancedFields name="codex-user.json" advanced={{ model_aliases: [{ name: 'gpt-5', alias: '' }] }}
models={[{ name: 'gpt-5', displayName: 'GPT 5' }]} onChange={() => {}} />
</I18nProvider>,
);
describe('credential alias model picker', () => {
it('uses the searchable model picker for both model fields', () => {
expect(html.match(/role="combobox"/g)).toHaveLength(2);
expect(html).toContain('value="gpt-5"');
expect(html).toContain('Search or enter a model');
expect(html).not.toContain('<textarea');
});
});
+5
View File
@@ -0,0 +1,5 @@
<!doctype html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Credential settings fixture</title></head>
<body><div id="root"></div><script type="module" src="./credential-settings.tsx"></script></body>
</html>
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { mockIPC } from '@tauri-apps/api/mocks';
import { I18nProvider } from '../../src/i18n';
import { AuthFileSettingsDialog } from '../../src/components/AuthFileSettingsDialog';
import '../../src/styles/index.css';
localStorage.setItem('easy-cli-proxy-api.locale', 'zh-CN');
const models = Array.from({ length: 20 }, (_, index) => ({ id: `gpt-${index + 1}`, display_name: `GPT ${index + 1}` }));
mockIPC(async (cmd, rawArgs) => {
if (cmd === 'set_app_locale') return null;
if (cmd !== 'management_request') return null;
const request = (rawArgs as { request?: { path?: string } }).request;
if (request?.path === '/credentials/download') {
return { prefix: '', proxy_url: 'socks5://127.0.0.1:1080', priority: 0, weight: 1, excluded_models: ['gpt-example', 'claude-*'], headers: {}, note: '' };
}
if (request?.path === '/credentials/models') return { models };
return { status: 'ok' };
});
createRoot(document.getElementById('root')!).render(
<I18nProvider>
<AuthFileSettingsDialog name="codex-cbb7bd85-lzt404rum@gmail.com-team.json" provider="codex" onClose={() => {}} onSaved={() => {}} />
</I18nProvider>,
);
+10 -8
View File
@@ -35,13 +35,13 @@ function componentFiles(directory: string): string[] {
}
describe('UI localization boundaries', () => {
it('preserves the raw reasoning effort in usage text and tooltips', () => {
it('preserves the raw reasoning effort under the usage model name', () => {
const file = join(sourceRoot, 'pages', 'UsageEventsView.tsx');
const source = ts.createSourceFile(file, readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
let effortCase: ts.CaseClause | undefined;
let modelCase: ts.CaseClause | undefined;
const visit = (node: ts.Node) => {
if (ts.isCaseClause(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'effort') {
effortCase = node;
if (ts.isCaseClause(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'model') {
modelCase = node;
} else {
ts.forEachChild(node, visit);
}
@@ -60,10 +60,12 @@ describe('UI localization boundaries', () => {
}
ts.forEachChild(node, inspectEffort);
};
expect(effortCase).toBeDefined();
if (effortCase) inspectEffort(effortCase);
expect(displayedExpressions).toContain("record.reasoning_effort || 'auto'");
expect(titleExpressions).toContain("{record.reasoning_effort || 'auto'}");
expect(modelCase).toBeDefined();
if (modelCase) inspectEffort(modelCase);
expect(displayedExpressions).toContain('effort');
expect(titleExpressions).toContain('{effort}');
expect(source.getText()).toContain("const effort = record.reasoning_effort || 'auto'");
expect(source.getText()).not.toContain("case 'effort'");
});
it('routes visible prose and accessibility labels through translations', () => {