Skip to content

Commit 983c036

Browse files
committed
feat(settings): per-provider "Reasoning depth" dropdown
New UI control in Settings → Providers, rendered on every active / key-present provider row directly below the model selector. Writes through `config:v1:update-provider` with tri-state semantics: - "Default (auto)" → null → clears override; core picks the default - any level string → sets ProviderEntry.reasoningLevel - the field flows into GenerateInput.reasoningLevel at call time Claude Code imports pre-seed "medium" so consumer-tier proxies work out of the box; users on higher-tier plans can now raise it to high / xhigh without editing config.toml by hand. - preload updateProvider gains reasoningLevel?: ReasoningLevel | null - Settings.tsx: ReasoningDepthSelector component (dropdown + save) - i18n en / zh-CN: settings.providers.reasoning.* + toast strings
1 parent cbfe08e commit 983c036

4 files changed

Lines changed: 108 additions & 12 deletions

File tree

apps/desktop/src/preload/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ const api = {
290290
wire?: WireApi;
291291
httpHeaders?: Record<string, string>;
292292
queryParams?: Record<string, string>;
293+
/** `null` explicitly clears the override and falls back to the model
294+
* default; a level string sets it; omit to leave untouched. */
295+
reasoningLevel?: ReasoningLevel | null;
293296
}) => ipcRenderer.invoke('config:v1:update-provider', input) as Promise<OnboardingState>,
294297
removeProvider: (id: string) =>
295298
ipcRenderer.invoke('config:v1:remove-provider', id) as Promise<OnboardingState>,

apps/desktop/src/renderer/src/components/Settings.tsx

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { setLocale as applyLocale, useT } from '@open-codesign/i18n';
2-
import type { OnboardingState } from '@open-codesign/shared';
2+
import type { OnboardingState, ReasoningLevel } from '@open-codesign/shared';
33
import {
44
PROVIDER_SHORTLIST as SHORTLIST,
55
isSupportedOnboardingProvider,
@@ -355,6 +355,18 @@ function ProviderCard({
355355
{row.isActive && !hasError && config !== null && (
356356
<ActiveModelSelector config={config} provider={row.provider} />
357357
)}
358+
{!hasError && row.hasKey !== false && (
359+
<ReasoningDepthSelector
360+
provider={row.provider}
361+
value={row.reasoningLevel}
362+
onUpdated={(nextRow) => {
363+
// Fire-and-forget refresh of parent rows list is handled by the
364+
// callback set in ProvidersList; this component just needs to
365+
// let the parent know a write succeeded.
366+
void nextRow;
367+
}}
368+
/>
369+
)}
358370
</div>
359371
);
360372
}
@@ -444,6 +456,70 @@ function ActiveModelSelector({
444456
);
445457
}
446458

459+
type ReasoningOption = '' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
460+
461+
function ReasoningDepthSelector({
462+
provider,
463+
value,
464+
onUpdated,
465+
}: {
466+
provider: string;
467+
value: ReasoningLevel | undefined;
468+
onUpdated: (row: ProviderRow) => void;
469+
}) {
470+
const t = useT();
471+
const pushToast = useCodesignStore((s) => s.pushToast);
472+
const [saving, setSaving] = useState(false);
473+
474+
async function handleChange(next: ReasoningOption) {
475+
if (!window.codesign?.config?.updateProvider) return;
476+
setSaving(true);
477+
try {
478+
// '' means "clear the per-provider override and fall back to the
479+
// model-family default"; any other string is an explicit set.
480+
const payload = { id: provider, reasoningLevel: next === '' ? null : next } as const;
481+
await window.codesign.config.updateProvider(payload);
482+
pushToast({ variant: 'success', title: t('settings.providers.toast.reasoningSaved') });
483+
if (window.codesign?.settings?.listProviders) {
484+
const rows = await window.codesign.settings.listProviders();
485+
const row = rows.find((r) => r.provider === provider);
486+
if (row) onUpdated(row);
487+
}
488+
} catch (err) {
489+
pushToast({
490+
variant: 'error',
491+
title: t('settings.providers.toast.reasoningSaveFailed'),
492+
description: err instanceof Error ? err.message : t('settings.common.unknownError'),
493+
});
494+
} finally {
495+
setSaving(false);
496+
}
497+
}
498+
499+
const current: ReasoningOption = value ?? '';
500+
const options: Array<{ value: ReasoningOption; label: string }> = [
501+
{ value: '', label: t('settings.providers.reasoning.default') },
502+
{ value: 'minimal', label: t('settings.providers.reasoning.minimal') },
503+
{ value: 'low', label: t('settings.providers.reasoning.low') },
504+
{ value: 'medium', label: t('settings.providers.reasoning.medium') },
505+
{ value: 'high', label: t('settings.providers.reasoning.high') },
506+
{ value: 'xhigh', label: t('settings.providers.reasoning.xhigh') },
507+
];
508+
509+
return (
510+
<div className="mt-[var(--space-2)] flex items-center gap-[var(--space-2)] text-[var(--text-xs)] text-[var(--color-text-muted)]">
511+
<Sliders className="w-3 h-3 shrink-0" />
512+
<span>{t('settings.providers.reasoning.label')}</span>
513+
<NativeSelect
514+
value={current}
515+
onChange={(v) => void handleChange(v as ReasoningOption)}
516+
options={options}
517+
disabled={saving}
518+
/>
519+
</div>
520+
);
521+
}
522+
447523
const DISMISSED_BANNER_PREFIX = 'open-codesign:settings:dismissed-import-banner:';
448524
function readDismissed(kind: 'codex' | 'claudeCode'): boolean {
449525
try {

packages/i18n/src/locales/en.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,16 @@
6262
"error": {
6363
"title": "Generation failed",
6464
"body": "Something went wrong while generating your design.",
65-
"copyError": "Copy error details"
65+
"copyError": "Copy error details",
66+
"brokenJsx": "This design has a syntax error, likely an incomplete early save. Regenerate or edit to fix.",
67+
"undefinedRef": "The design references an undefined variable or component. Likely a mid-run abort — try regenerating."
6668
},
6769
"ready": "Preview",
6870
"noDesign": "No design yet",
6971
"clickToComment": "Click any element in the preview to leave an inline comment.",
7072
"commentMode": "Comment mode",
7173
"commentModeHint": "Click any element to comment",
7274
"runtimeError": "Preview runtime error",
73-
"error": {
74-
"brokenJsx": "This design has a syntax error, likely an incomplete early save. Regenerate or edit to fix.",
75-
"undefinedRef": "The design references an undefined variable or component. Likely a mid-run abort — try regenerating."
76-
},
7775
"dismissErrors": "Dismiss preview errors",
7876
"zoom": "Zoom"
7977
},
@@ -234,8 +232,19 @@
234232
"switchFailed": "Switch failed",
235233
"saved": "Provider saved",
236234
"modelSaveFailed": "Failed to save model selection",
235+
"reasoningSaved": "Reasoning depth saved",
236+
"reasoningSaveFailed": "Failed to save reasoning depth",
237237
"connectionOk": "Connection OK",
238238
"connectionFailed": "Connection failed"
239+
},
240+
"reasoning": {
241+
"label": "Reasoning depth",
242+
"default": "Default (auto)",
243+
"minimal": "Minimal",
244+
"low": "Low",
245+
"medium": "Medium",
246+
"high": "High",
247+
"xhigh": "Extra high"
239248
}
240249
},
241250
"appearance": {

packages/i18n/src/locales/zh-CN.json

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"previewHint": "缩略预览 · 双击文件名或点击「在新标签打开」可在独立标签查看",
4040
"tabsAriaLabel": "已打开文件",
4141
"closeTab": "关闭 {{name}}",
42-
"closeTab": "关闭 {{name}}",
4342
"files": {
4443
"sectionTitle": "设计文件",
4544
"sectionSubtitle": "共 {{count}} 个文件",
@@ -64,18 +63,16 @@
6463
"error": {
6564
"title": "生成失败",
6665
"body": "生成过程中出错了。",
67-
"copyError": "复制错误详情"
66+
"copyError": "复制错误详情",
67+
"brokenJsx": "此设计的代码有语法错误,可能是早期版本保存的不完整内容。重新生成或编辑修复即可。",
68+
"undefinedRef": "设计引用了未定义的变量或组件。可能是生成中途中断——尝试重新生成。"
6869
},
6970
"ready": "预览",
7071
"noDesign": "还没有设计",
7172
"clickToComment": "点击预览中的任意元素即可留下局部评论。",
7273
"commentMode": "评论模式",
7374
"commentModeHint": "点击任意元素留评",
7475
"runtimeError": "预览运行时错误",
75-
"error": {
76-
"brokenJsx": "此设计的代码有语法错误,可能是早期版本保存的不完整内容。重新生成或编辑修复即可。",
77-
"undefinedRef": "设计引用了未定义的变量或组件。可能是生成中途中断——尝试重新生成。"
78-
},
7976
"dismissErrors": "关闭预览错误",
8077
"zoom": "缩放"
8178
},
@@ -238,8 +235,19 @@
238235
"switchFailed": "切换失败",
239236
"saved": "服务已保存",
240237
"modelSaveFailed": "保存模型选择失败",
238+
"reasoningSaved": "已保存推理深度",
239+
"reasoningSaveFailed": "保存推理深度失败",
241240
"connectionOk": "连接正常",
242241
"connectionFailed": "连接失败"
242+
},
243+
"reasoning": {
244+
"label": "推理深度",
245+
"default": "默认(自动)",
246+
"minimal": "极低",
247+
"low": "",
248+
"medium": "",
249+
"high": "",
250+
"xhigh": "最高"
243251
}
244252
},
245253
"appearance": {

0 commit comments

Comments
 (0)