feat: refresh dashboard settings UX and Codex feature toggles (#153)

* feat: refresh dashboard settings UX and Codex feature toggles

Improve settings/tasks mobile layout, model effort validation by agent type, and preset models for GPT 5.5 and Opus 4.7. Store Codex fast mode and goals in config.toml with Claude fastMode session sync and updated docs for Codex 0.133.

Co-authored-by: Cursor <cursoragent@cursor.com>

* style: apply pre-commit formatting after dashboard settings commit

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Eyejoker
2026-05-23 12:18:42 +09:00
committed by GitHub
parent 58e5197dc6
commit fd3145e2a7
37 changed files with 3785 additions and 1910 deletions

View File

@@ -8,8 +8,10 @@ import {
fetchMoaSettings,
updateMoaSettings,
} from './api';
import { type Messages } from './i18n';
import { SettingsSaveBar, SettingsSectionHeading } from './SettingsPanelChrome';
export function MoaSettingsPanel() {
export function MoaSettingsPanel({ t }: { t: Messages }) {
const [config, setConfig] = useState<MoaSettingsSnapshot | null>(null);
const [draft, setDraft] = useState<MoaSettingsSnapshot | null>(null);
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
@@ -17,6 +19,7 @@ export function MoaSettingsPanel() {
const [checking, setChecking] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [savedAt, setSavedAt] = useState<number | null>(null);
const section = t.settings.sections.moa;
useEffect(() => {
let cancelled = false;
@@ -119,15 +122,15 @@ export function MoaSettingsPanel() {
id="settings-moa"
role="tabpanel"
>
<header className="settings-section-head">
<span>Arbiter references</span>
<h3>MoA </h3>
<p>Kimi, GLM .</p>
</header>
<SettingsSectionHeading
description={section.description}
detail={section.kicker}
title={section.title}
/>
{error ? <p className="settings-error">{error}</p> : null}
{!draft ? (
<p className="settings-hint">
{busy ? '불러오는 중…' : 'MoA 설정 없음'}
{busy ? t.settings.common.loading : t.settings.moa.empty}
</p>
) : (
<MoaSettingsContent
@@ -148,6 +151,7 @@ export function MoaSettingsPanel() {
)
}
savedAt={savedAt}
t={t}
/>
)}
</section>
@@ -166,6 +170,7 @@ function MoaSettingsContent({
onTest,
onToggle,
savedAt,
t,
}: {
apiKeys: Record<string, string>;
busy: boolean;
@@ -181,6 +186,7 @@ function MoaSettingsContent({
onTest: (name: string) => Promise<void>;
onToggle: () => void;
savedAt: number | null;
t: Messages;
}) {
return (
<>
@@ -188,8 +194,9 @@ function MoaSettingsContent({
busy={busy}
enabled={draft.enabled}
onToggle={onToggle}
t={t}
/>
<ul className="settings-account-list">
<ul className="settings-moa-list">
{draft.models.map((model) => (
<MoaModelRow
apiKeyValue={apiKeys[model.name] ?? ''}
@@ -201,47 +208,54 @@ function MoaSettingsContent({
onApiKeyChange={onApiKeyChange}
onModelChange={onModelChange}
onTest={onTest}
t={t}
/>
))}
</ul>
<MoaSettingsActions
busy={busy}
checking={checking}
<SettingsSaveBar
busy={busy || checking !== null}
dirty={dirty}
onSave={onSave}
savedAt={savedAt}
label={t.settings.moa.save}
onSave={() => void onSave()}
savedHint={t.settings.common.savedRestartHint}
savingLabel={t.settings.common.saving}
showSavedHint={savedAt !== null && !dirty}
/>
</>
);
}
function formatMoaStatus(status: MoaReferenceStatus | null): string {
if (!status) return '연결 테스트 전';
function formatMoaStatus(
status: MoaReferenceStatus | null,
t: Messages,
): string {
if (!status) return t.settings.moa.notTested;
const at = new Date(status.checkedAt);
const checkedAt = Number.isNaN(at.getTime())
? status.checkedAt
: at.toLocaleString('ko-KR');
if (status.ok) return `정상 · ${checkedAt}`;
return `실패 · ${checkedAt} · ${status.error ?? 'unknown error'}`;
if (status.ok) {
return `${t.settings.moa.statusOk} · ${checkedAt}`;
}
return `${t.settings.moa.statusFail} · ${checkedAt} · ${status.error ?? 'unknown error'}`;
}
function MoaMasterToggle({
busy,
enabled,
onToggle,
t,
}: {
busy: boolean;
enabled: boolean;
onToggle: () => void;
t: Messages;
}) {
return (
<label className="settings-toggle-row">
<span className="settings-toggle-label">
<span className="settings-toggle-title">MoA </span>
<small className="settings-hint">
Arbiter .
.
</small>
<span className="settings-toggle-title">{t.settings.moa.master}</span>
<small className="settings-hint">{t.settings.moa.masterHint}</small>
</span>
<input
checked={enabled}
@@ -253,38 +267,6 @@ function MoaMasterToggle({
);
}
function MoaSettingsActions({
busy,
checking,
dirty,
onSave,
savedAt,
}: {
busy: boolean;
checking: string | null;
dirty: boolean;
onSave: () => Promise<void>;
savedAt: number | null;
}) {
return (
<div className="settings-actions">
<button
className="settings-save"
disabled={!dirty || busy || checking !== null}
onClick={() => void onSave()}
type="button"
>
{busy ? '저장 중…' : 'MoA 저장'}
</button>
{savedAt && !dirty ? (
<small className="settings-hint">
. .
</small>
) : null}
</div>
);
}
function MoaModelRow({
apiKeyValue,
busy,
@@ -294,6 +276,7 @@ function MoaModelRow({
onApiKeyChange,
onModelChange,
onTest,
t,
}: {
apiKeyValue: string;
busy: boolean;
@@ -306,10 +289,11 @@ function MoaModelRow({
patch: Partial<MoaModelSettingsSnapshot>,
) => void;
onTest: (name: string) => Promise<void>;
t: Messages;
}) {
return (
<li className="settings-account-row settings-moa-row">
<div className="settings-moa-grid">
<li className="settings-moa-card">
<header className="settings-moa-card-head">
<label className="settings-moa-name">
<input
checked={model.enabled}
@@ -321,49 +305,69 @@ function MoaModelRow({
/>
<span className="settings-account-tag">{model.name}</span>
</label>
<input
aria-label={`${model.name} MoA model`}
onChange={(e) => onModelChange(model.name, { model: e.target.value })}
placeholder="model"
type="text"
value={model.model}
/>
<input
aria-label={`${model.name} MoA base URL`}
onChange={(e) =>
onModelChange(model.name, { baseUrl: e.target.value })
}
placeholder="base URL"
type="text"
value={model.baseUrl}
/>
<select
aria-label={`${model.name} MoA API format`}
onChange={(e) =>
onModelChange(model.name, {
apiFormat: e.target.value as 'openai' | 'anthropic',
})
}
value={model.apiFormat}
>
<option value="anthropic">anthropic</option>
<option value="openai">openai</option>
</select>
<input
aria-label={`${model.name} MoA API key`}
onChange={(e) => onApiKeyChange(model.name, e.target.value)}
placeholder={model.apiKeyConfigured ? 'API key set' : 'new API key'}
type="password"
value={apiKeyValue}
/>
<span
className={`settings-account-badge ${
model.lastStatus?.ok === false ? 'is-expired' : 'is-active'
}`}
title={model.lastStatus?.error ?? undefined}
>
{formatMoaStatus(model.lastStatus)}
{formatMoaStatus(model.lastStatus, t)}
</span>
</header>
<div className="settings-moa-fields">
<label className="settings-row">
<span className="settings-label">{t.settings.moa.modelLabel}</span>
<input
aria-label={`${model.name} MoA model`}
onChange={(e) =>
onModelChange(model.name, { model: e.target.value })
}
placeholder="model"
type="text"
value={model.model}
/>
</label>
<label className="settings-row">
<span className="settings-label">{t.settings.moa.baseUrlLabel}</span>
<input
aria-label={`${model.name} MoA base URL`}
onChange={(e) =>
onModelChange(model.name, { baseUrl: e.target.value })
}
placeholder="https://…"
type="text"
value={model.baseUrl}
/>
</label>
<label className="settings-row">
<span className="settings-label">{t.settings.moa.formatLabel}</span>
<select
aria-label={`${model.name} MoA API format`}
onChange={(e) =>
onModelChange(model.name, {
apiFormat: e.target.value as 'openai' | 'anthropic',
})
}
value={model.apiFormat}
>
<option value="anthropic">anthropic</option>
<option value="openai">openai</option>
</select>
</label>
<label className="settings-row">
<span className="settings-label">API key</span>
<input
aria-label={`${model.name} MoA API key`}
onChange={(e) => onApiKeyChange(model.name, e.target.value)}
placeholder={
model.apiKeyConfigured
? t.settings.moa.apiKeySet
: t.settings.moa.apiKeyPlaceholder
}
type="password"
value={apiKeyValue}
/>
</label>
</div>
<div className="settings-account-actions">
<button
@@ -375,10 +379,10 @@ function MoaModelRow({
type="button"
>
{checking === model.name
? '테스트중…'
? t.settings.moa.testing
: dirty
? '저장 후 테스트'
: '연결 테스트'}
? t.settings.moa.testAfterSave
: t.settings.moa.test}
</button>
</div>
</li>