Skip to content

Commit 80c6007

Browse files
fengzhi09s39-dev
andauthored
fix(webui): stop narrating the engine layer in the interface (#128)
"Use engine default" told the user which layer was deciding. The user picks an effort level, not a component — the interface should not narrate the engine. Two more said the same thing: a switch that did not land reported that "the engine is still on a different session", and a truncated diff pointed at the engine instead of saying where the rest of it is. Both of those came from earlier work here, which is the point: the habit of describing the mechanism instead of the user's situation is easy to pick up and easy to leave in. The i18n comment now says so. Four literal references to the old labels move with them, and the two documentation lines that quoted them. Co-authored-by: s39-dev <s39-dev@local>
1 parent 48dac17 commit 80c6007

6 files changed

Lines changed: 19 additions & 17 deletions

File tree

‎docs/webui.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,12 @@ Contract details:
238238

239239
- `POST /api/set-model` `{model, thinking?}` records `thinking` in `cs.model.thinking` whatever the channel; `thinkingSynced` reports the pick actually reaching the engine — for the variant channel it is the model push carrying the level, and `mcodeSynced`/`thinkingSynced` describe that one push from both angles.
240240
- Session boot replays the pick (`applyRecordedModel`): effort models push model-then-effort; variant models push one variant-carrying model selection and skip the effort push. The order is load-bearing — the engine rejects a `thinkingEffort` set while no model is selected (`Select a Session model before changing thinking effort.`, engine `agent.ts#1003`), so reversing it silently drops the level. The `POST /api/set-model` path repeats the same model-then-effort order. A stale recorded level that the new model does not list is cleared by the composer on model switch (ticket 11 wire half).
241-
- `default_value` from `thinking_config` is not a response field. The control's initial state is "Use engine default" (`thinkingPicker.none`) until the user picks; for variant models an unpicked boot selects the engine's default variant (`default_value: 'true'` → thinking on).
241+
- `default_value` from `thinking_config` is not a response field. The control's initial state is "Default" (`thinkingPicker.none`) until the user picks; for variant models an unpicked boot selects the engine's default variant (`default_value: 'true'` → thinking on).
242242
- Engine-session entries appear in variant wire form (`m:...:v:thinking` / `:v:none-thinking`) because that is what the engine advertises for switchable models; both carry the same `thinkingLevels`.
243243
- A pick while a turn is running takes effect on the next turn (same semantics as a model switch mid-run).
244244
- A local pick owns its field for `PICK_DEFER_WINDOW_MS` (4 s): `applyConfigOptionUpdate` does not overwrite that field with the engine's wire-form `currentValue` inside the window, so an optimistic pick is not clobbered a few ms later. Model and thinking are stamped independently (`modelPickedAt` / `thinkingPickedAt`), so a thinking-only pick does not block a later cross-client model mirror. The window is defence-in-depth — the per-cid snapshot `revision` is the primary guard against wire reordering.
245245
- An operator's providers-config entry with the same id as a builtin wins wholesale (existing merge rule); such an entry shows levels only if the operator wrote them.
246-
- Ticket 49 batches 1–2 added a fourth display and a second editing entry inside the picker. The panel-bottom detail area renders the ACTIVE model's `thinkingLevels` as read-only badges. When a provider cascade is open, the fly-out renders as the reference picker's two-column popover — model rows on the left, a follow-focus settings column on the right — and that column's level control is **editable and shape-adaptive**: exactly `["off","on"]` renders one toggle switch; any other level list renders a radio group whose FIRST entry is always "Use engine default" (submitted as the empty string). Form only — the wire semantics stay the local `thinkingLevels` + `""` contract, never the reference's effortOptions/variant derivation (A9 was scoped out). A pick commits immediately without closing the menu, through the same `{thinking}` payload the composer-level control sends; a focused-but-not-active model renders the control disabled with a "preview" marker. A recorded level the target model does not support highlights nothing — the row-badge anti-stale rule (B11) applied to the control.
246+
- Ticket 49 batches 1–2 added a fourth display and a second editing entry inside the picker. The panel-bottom detail area renders the ACTIVE model's `thinkingLevels` as read-only badges. When a provider cascade is open, the fly-out renders as the reference picker's two-column popover — model rows on the left, a follow-focus settings column on the right — and that column's level control is **editable and shape-adaptive**: exactly `["off","on"]` renders one toggle switch; any other level list renders a radio group whose FIRST entry is always "Default" (submitted as the empty string). Form only — the wire semantics stay the local `thinkingLevels` + `""` contract, never the reference's effortOptions/variant derivation (A9 was scoped out). A pick commits immediately without closing the menu, through the same `{thinking}` payload the composer-level control sends; a focused-but-not-active model renders the control disabled with a "preview" marker. A recorded level the target model does not support highlights nothing — the row-badge anti-stale rule (B11) applied to the control.
247247

248248
The operator-facing view — which models show what control, and why MiniMax-M3 only has on/off — is the thinking section of [`webui.zh-CN.md`](webui.zh-CN.md).
249249

‎docs/webui.zh-CN.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,16 @@ ACP 握手是双向的,两个方向都由同一份 `initialize` 载荷决定
233233

234234
为什么 M3 只有开/关,不是低/中/高:这不是 UI 偷懒。引擎给 M3 的配置就是两个变体——「不思考」与「思考」(自适应),没有中间档。控件如实展示两档;编造四档选择器会让用户以为在调深度,实际引擎根本不区分。等引擎给 M3 开出真正的深度档位,`/api/models` 会原样带出来,控件自动跟着变。
235235

236-
初始状态是「沿用引擎默认」:M3 的引擎默认是思考开启,M3.1-Flash 的默认深度是 default,用户不主动选就一直沿用,不会替用户做选择。
236+
初始状态是「默认」:M3 的引擎默认是思考开启,M3.1-Flash 的默认深度是 default,用户不主动选就一直沿用,不会替用户做选择。
237237

238238
等级在界面上有四处可见、两处可编辑:① 模型选择器级联里当前模型行上的等级徽标;② 输入框旁模型名后的等级后缀;③ 输入框旁的思考等级控件(编辑入口,位置未变);④ 模型选择器里的两个详情区——面板底部详情区把**当前模型**的等级行展示为只读徽标,级联打开时旁边并排的设置列把**聚焦模型**的等级渲染为**可编辑的自适应控件**(第二个编辑入口)。
239239

240240
自适应控件的形态由模型的档位决定,只做前端形态、不做任何语义推导:
241241

242242
| 档位形状 | 控件形态 | 「归位引擎默认」入口 |
243243
| --- | --- | --- |
244-
| 恰为 `["off","on"]`(如 MiniMax-M3) | 一个开关(开=on、关=off) | 开关无归位项;用输入框旁控件的「沿用引擎默认」 |
245-
| 其他任何多档(如 low/medium/high/max) | 单选组,**首项恒为「沿用引擎默认」**(提交空串) | 单选组首项 |
244+
| 恰为 `["off","on"]`(如 MiniMax-M3) | 一个开关(开=on、关=off) | 开关无归位项;用输入框旁控件的「默认」 |
245+
| 其他任何多档(如 low/medium/high/max) | 单选组,**首项恒为「默认」**(提交空串) | 单选组首项 |
246246

247247
在设置列里改等级会**立即提交且菜单不关闭**,可以连续调;提交走的还是 `{thinking}` 这条请求,与输入框旁的控件完全同一条通道。聚焦的行不是当前模型时,设置列进入预览态:控件照常展示但禁用并标注「预览」——等级记录属于当前模型,替一个还没选中的模型提交没有契约意义。一条防错规则如实说明:当前模型记录的等级如果不被它自己支持(跨模型切换残留),控件不高亮任何一项,也不假装「引擎默认」被选中。
248248

‎packages/webui/webapp/components/composer.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2009,7 +2009,7 @@ function ModelSettingsDetail({
20092009
* switch. No `default` entry here — the reference
20102010
* treats switchable thinking as pure on/off; the
20112011
* engine-default reset stays reachable through the
2012-
* composer-level control's "Use engine default" row
2012+
* composer-level control's "Default" row
20132013
* and the radio group's `default` option elsewhere. */
20142014
<div className="flex items-center gap-2 px-0.5 py-0.5">
20152015
<button
@@ -2542,7 +2542,7 @@ const CascadeSubmenu = forwardRef<
25422542
* Thinking-effort picker.
25432543
*
25442544
* Same shell and panel as the other selectors. The trigger is the
2545-
* active level ("High" / "Medium" / …) or "Use engine default" when
2545+
* active level ("High" / "Medium" / …) or "Default" when
25462546
* the user has not picked one (the recorded value is empty).
25472547
*
25482548
* The levels array comes from the active model's catalogue entry; the

‎packages/webui/webapp/lib/effort-control.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function effortControlShape(levels: string[]): "switch" | "radiogroup" |
3232
/**
3333
* The radio group's option list.
3434
*
35-
* `default` (UI label "Use engine default", submitted as the empty
35+
* `default` (UI label "Default", submitted as the empty
3636
* string) is always a legal choice — the reference prepends it the
3737
* same way, and the local wire treats `""` as "no override; the engine
3838
* picks". A defensive filter keeps a catalogue that ever ships a

‎packages/webui/webapp/lib/i18n.ts‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,12 @@ const en = {
130130
"modelSelector.modalityBadge.video": "video",
131131
"modelSelector.modalityBadge.file": "file",
132132
// Thinking-effort picker (off / low / medium / high). The
133-
// `thinkingPicker.none` key is the "no level recorded" placeholder;
134-
// it surfaces only between picking a model and the picker closing.
133+
// `thinkingPicker.none` key is the "no level picked" placeholder; it
134+
// surfaces only between picking a model and the picker closing.
135+
// Wording is the user's word ("Default"), not the engine's — an
136+
// interface should not narrate which layer is deciding.
135137
"thinkingPicker.label": "Thinking effort",
136-
"thinkingPicker.none": "Use engine default",
138+
"thinkingPicker.none": "Default",
137139
"thinkingPicker.off": "Off",
138140
"thinkingPicker.on": "On",
139141
"thinkingPicker.low": "Low",
@@ -520,7 +522,7 @@ const en = {
520522
// webui-parity 63 (defect E): a switch that lands on a session other than
521523
// the row the user clicked used to be silent. This is the banner's detail
522524
// line, so it reads as the cause under the "Open session — failed" head.
523-
"sidebar.switchMismatch": "the engine is still on a different session",
525+
"sidebar.switchMismatch": "the switch did not land — try again",
524526
// webui-parity 47 (C3): the rail toggle's label flips with its state,
525527
// mirroring the reference shell's 展开/收起 pair. "navigation bar", not
526528
// "sidebar", because that is what the reference copy names.
@@ -629,7 +631,7 @@ const en = {
629631
"git.file.openDiff": "View diff",
630632
"git.file.diff.empty": "No diff for this file",
631633
"git.file.diff.truncated":
632-
"Diff truncated — full diff available on the engine",
634+
"Diff truncated — open the file to see the rest",
633635
"git.file.diff.loading": "Loading diff…",
634636
"git.file.diff.failed": "Could not load diff",
635637
"git.refresh": "Refresh",
@@ -1246,7 +1248,7 @@ const zh: Record<MessageKey, string> = {
12461248
"modelSelector.modalityBadge.file": "文件",
12471249
/* 思考等级选择器 (off / low / medium / high)。 */
12481250
"thinkingPicker.label": "思考等级",
1249-
"thinkingPicker.none": "沿用引擎默认",
1251+
"thinkingPicker.none": "默认",
12501252
"thinkingPicker.off": "关闭",
12511253
"thinkingPicker.on": "开启",
12521254
"thinkingPicker.low": "低",
@@ -1588,7 +1590,7 @@ const zh: Record<MessageKey, string> = {
15881590
"sidebar.projects": "项目",
15891591
"action.failed": "失败",
15901592
"sidebar.openSession": "打开会话",
1591-
"sidebar.switchMismatch": "引擎仍停留在另一个会话",
1593+
"sidebar.switchMismatch": "切换没有生效,请重试",
15921594
"sidebar.collapse": "收起导航栏",
15931595
"sidebar.expand": "展开导航栏",
15941596
"sidebar.loadError": "无法加载会话:",
@@ -1676,7 +1678,7 @@ const zh: Record<MessageKey, string> = {
16761678
"git.files.untracked": "未跟踪",
16771679
"git.file.openDiff": "查看 diff",
16781680
"git.file.diff.empty": "该文件无 diff",
1679-
"git.file.diff.truncated": "diff 已截断 — 完整内容请在引擎中查看",
1681+
"git.file.diff.truncated": "diff 已截断 — 打开文件可看完整内容",
16801682
"git.file.diff.loading": "加载 diff 中…",
16811683
"git.file.diff.failed": "diff 加载失败",
16821684
"git.refresh": "刷新",

‎packages/webui/webapp/test/composer-models.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ describe("setModel payload — wiring the composer sends", () => {
900900
assert.equal("model" in payload, false, "no model field echoed on effort-only update");
901901
});
902902

903-
test("'Use engine default' sends thinking:'' (clear the override)", () => {
903+
test("'Default' sends thinking:'' (clear the override)", () => {
904904
const payload = { thinking: "" };
905905
assert.equal(payload.thinking, "");
906906
});

0 commit comments

Comments
 (0)