dsh-custom-mode 1.11.6 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +17 -13
- package/README.zh.md +17 -12
- package/atomic.mjs +31 -1
- package/client.js +223 -88
- package/composition.mjs +39 -5
- package/index.mjs +38 -10
- package/locales.mjs +39 -11
- package/package.json +3 -3
- package/preset/prompt-tool.mjs +4 -4
- package/preset-backend/declarative.mjs +24 -5
package/client.js
CHANGED
|
@@ -81,7 +81,7 @@ try {
|
|
|
81
81
|
"btn.resetHint": "把编辑器里的内容换成出厂模板(新建助手时得到的那一份)。它同样只改草稿,点保存才落盘 —— 误点可以用「重新读取」撤销。",
|
|
82
82
|
"btn.delete": "删除这个助手",
|
|
83
83
|
"btn.cancel": "取消",
|
|
84
|
-
"msg.created": "
|
|
84
|
+
"msg.created": "已创建。现在可以为它写系统提示词。新建会话的模式菜单要刷新一次才会出现它。",
|
|
85
85
|
"msg.duplicated": "已复制。两份配置彼此独立。",
|
|
86
86
|
"msg.deleted": "已删除。",
|
|
87
87
|
"msg.createFailed": "创建失败",
|
|
@@ -120,7 +120,8 @@ try {
|
|
|
120
120
|
"meta.versionHint": "安装时不钉版本号会受 pnpm 发布冷却期影响(默认 24 小时),可能装到较旧的版本。要换版本请按 README 的钉版本命令重装,然后重启 DSH。",
|
|
121
121
|
"api.saved": "已保存({name},基础模式 {mode})。新建会话即生效,当前会话保持原配置。",
|
|
122
122
|
"api.savedWithExclusiveRows": "已保存({name},基础模式 {mode})。两套壳注册同名工具、不能同时启用 —— 已自动关掉:{rows}。新建会话即生效,当前会话保持原配置。",
|
|
123
|
-
"api.created": "已创建「{name}
|
|
123
|
+
"api.created": "已创建「{name}」。现在可以为它写系统提示词。新建会话的模式菜单要刷新一次才会出现它。",
|
|
124
|
+
"api.savedPrompt": "已保存系统提示词({name})。下一步模型调用即生效。",
|
|
124
125
|
"api.duplicated": "已复制自「{from}」。两份配置彼此独立。",
|
|
125
126
|
"api.deleted": "已删除「{name}」。正在使用它的会话不受影响;新建会话时它不再出现。",
|
|
126
127
|
"api.reordered": "顺序已保存:新建会话时的模式选择器按这个顺序排列。",
|
|
@@ -153,6 +154,9 @@ try {
|
|
|
153
154
|
"warn.approvalGateMissing": "审批闸门未启用:本机这个 DSH 版本没有 tools/pre-execute 事件,会话内改写系统提示词不会弹审批。见「详情」。",
|
|
154
155
|
"warn.unresolvableRows": "有行在本机这条 DSH 线上跑不起来。平台会因此判整个模式为 broken,把它从新会话的选择器里丢掉(不报错)。点下方「按本线修复」只关掉那几行,其它选择不动。",
|
|
155
156
|
"warn.presetBroken": "平台判这个模式为 broken,它会从新会话的选择器里消失 —— 而上面的「无法解析」检查解释不了原因(多半是某一行依赖的服务没起来)。点下方「按本线修复」把那几行关掉;平台自己给的原因见下面那行。",
|
|
157
|
+
"warn.badPromptVariable": "系统提示词里有未注册或写错的变量引用,这个模式的每次请求都会失败。改掉那一处后再保存。",
|
|
158
|
+
"warn.duplicateRowIds": "组成文件里有重复的行 id。开关只作用于第一处,请删掉重复的那一行。",
|
|
159
|
+
"warn.predicateFailed": "有一行的平台条件求值失败,这一行已按关闭处理,避免整个模式无法挂载。",
|
|
156
160
|
"warn.exclusiveRowsActive": "两条互斥的壳同时开着(它们注册同名工具),平台会判整个模式为 broken 并从新会话的选择器里丢掉。关掉其中一条即可;在设置页保存一次也会自动避让。",
|
|
157
161
|
"warn.baseCompositionUnavailable": "取不到本机这条 DSH 线的出厂组成:基础模式与插件开关暂时不能改(系统提示词不受影响,照常可保存)。已尝试的途径写在宿主日志里。",
|
|
158
162
|
"warn.personaOffWithPrompt": "「身份(系统提示词)」这一行是关的,所以 prompt.md 不会被注入 —— 所写的提示词不会生效。打开这一行,或清空提示词。",
|
|
@@ -170,7 +174,7 @@ try {
|
|
|
170
174
|
"msg.importTooLarge": "文件太大(上限 1MB)。",
|
|
171
175
|
"msg.exported": "已导出为文件。",
|
|
172
176
|
"msg.exportFailed": "导出失败",
|
|
173
|
-
"delete.title": "
|
|
177
|
+
"delete.title": "永久删除「{name}」?",
|
|
174
178
|
"delete.description": "这会删除磁盘上的模式目录,连同它的系统提示词一起消失,无法撤销。正在使用它的会话不受影响;新建会话时它不再出现在选择器里。({id})",
|
|
175
179
|
"delete.acknowledge": "我明白这个助手的提示词会被永久删除",
|
|
176
180
|
"delete.confirm": "永久删除",
|
|
@@ -181,8 +185,13 @@ try {
|
|
|
181
185
|
"name.placeholder": "自定义模式",
|
|
182
186
|
"name.descriptionPlaceholder": "模式描述(显示在模式选择器里,可留空)",
|
|
183
187
|
"mode.heading": "基础模式",
|
|
184
|
-
"mode.hint": "
|
|
188
|
+
"mode.hint": "基础模式只决定有哪些行、有哪些工具。本模式的身份始终用这一页的提示词,不继承基础模式的提示语。保存后新建会话生效,无需重启。",
|
|
185
189
|
"rows.heading": "插件开关",
|
|
190
|
+
"rows.filterPlaceholder": "筛选行(名称或 id)",
|
|
191
|
+
"cap.heading": "能力",
|
|
192
|
+
"cap.expand": "展开能力",
|
|
193
|
+
"cap.collapse": "收起能力",
|
|
194
|
+
"cap.hint": "基础模式决定有哪些行;插件开关逐行生效。这两项从新建会话起生效。",
|
|
186
195
|
"rows.hint": "逐行控制本模式挂载的插件。未改动的行保持官方默认(含平台判断);手动设置后以手动值为准。",
|
|
187
196
|
"prompt.heading": "系统提示词",
|
|
188
197
|
"prompt.hint": "每次模型调用前重新读取:保存后下一步生效,仅影响使用本助手的会话。「导入」读文件进编辑器(保存前不写入);「导出」存为 .md;「恢复出厂提示词」填回出厂模板(同样需保存)。",
|
|
@@ -195,6 +204,7 @@ try {
|
|
|
195
204
|
"btn.saving": "处理中…",
|
|
196
205
|
"btn.reload": "重新读取",
|
|
197
206
|
"btn.reloadDiscard": "放弃修改并重新读取",
|
|
207
|
+
"btn.reloadConfirm": "放弃未保存的修改并重新读取?",
|
|
198
208
|
"msg.unsaved": "有未保存的修改",
|
|
199
209
|
"msg.readFailed": "读取失败",
|
|
200
210
|
"msg.saveFailed": "保存失败",
|
|
@@ -230,6 +240,10 @@ try {
|
|
|
230
240
|
"row.tool-subagent-codex.note": "需要先安装对应 Bundle 才能用;出厂状态见「详情」",
|
|
231
241
|
"row.tool-subagent-claude-code.label": "Claude Code 子代理",
|
|
232
242
|
"row.tool-subagent-claude-code.note": "需要先安装对应 Bundle 才能用;出厂状态见「详情」",
|
|
243
|
+
"row.time-context.label": "时间上下文(模型看得到时间)",
|
|
244
|
+
"row.time-context.note": "在合适的步骤里附上当前时间、浏览器时区与距上一条消息的间隔;默认 10 分钟内不重复注入",
|
|
245
|
+
"row.tool-schedule.label": "定时提醒工具",
|
|
246
|
+
"row.tool-schedule.note": "让 agent 创建/列出/修改/删除持久提醒;存储与投递由宿主的 schedule 服务负责",
|
|
233
247
|
"row.workflow-ptc.label": "工作流引擎",
|
|
234
248
|
"row.workflow-worker-thread.label": "工作流 Worker 线程",
|
|
235
249
|
"row.workflow-worker-thread.note": "把工作流跑在独立的 worker 线程里",
|
|
@@ -269,7 +283,7 @@ try {
|
|
|
269
283
|
// 同一个模式在一页里有两个名字:用户点了「Cordis 模式」药丸,读到的说明却说「创造模式」。
|
|
270
284
|
"base.cordis.label": "创造模式",
|
|
271
285
|
"base.cordis.note": "标准模式 + 读写运行时的 Cordis 工具集,可让 agent 自己改 harness",
|
|
272
|
-
"base.all.label": "
|
|
286
|
+
"base.all.label": "全部出厂行",
|
|
273
287
|
"base.all.note": "不继承任何单一原生模式:以标准模式的全套行为底,补上只有 PTC / 极简 / 创造模式才有的行,全部都能逐行开关"
|
|
274
288
|
}
|
|
275
289
|
|
|
@@ -296,7 +310,7 @@ try {
|
|
|
296
310
|
"btn.resetHint": "Puts the shipped template back into the editor (the text a new assistant starts from). Like every other edit it only changes the draft — save to apply, or use Reload to discard.",
|
|
297
311
|
"btn.delete": "Delete this assistant",
|
|
298
312
|
"btn.cancel": "Cancel",
|
|
299
|
-
"msg.created": "Created.
|
|
313
|
+
"msg.created": "Created. You can write its system prompt now. Refresh once before the new-session mode menu will list it.",
|
|
300
314
|
"msg.duplicated": "Duplicated. The two are independent from here on.",
|
|
301
315
|
"msg.deleted": "Deleted.",
|
|
302
316
|
"msg.createFailed": "Could not create",
|
|
@@ -335,7 +349,8 @@ try {
|
|
|
335
349
|
"meta.versionHint": "Installing without a pinned version is subject to pnpm’s release cooldown (24 h by default) and can land on an older release. To change version, reinstall with the pinned command from the README and restart DSH.",
|
|
336
350
|
"api.saved": "Saved ({name}, base mode {mode}). A new session picks it up; the current one keeps its configuration.",
|
|
337
351
|
"api.savedWithExclusiveRows": "Saved ({name}, base mode {mode}). The two shell implementations register the same tool names and cannot both be on — turned off automatically: {rows}. A new session picks it up; the current one keeps its configuration.",
|
|
338
|
-
"api.created": "Created {name}. You can write its system prompt now.",
|
|
352
|
+
"api.created": "Created {name}. You can write its system prompt now. Refresh once before the new-session mode menu will list it.",
|
|
353
|
+
"api.savedPrompt": "System prompt saved ({name}). It applies on the next model step.",
|
|
339
354
|
"api.duplicated": "Copied from {from}. The two are independent from now on.",
|
|
340
355
|
"api.deleted": "Deleted {name}. Sessions already using it keep running; it no longer appears for new sessions.",
|
|
341
356
|
"api.reordered": "Order saved: the new-session mode picker follows it.",
|
|
@@ -370,7 +385,10 @@ try {
|
|
|
370
385
|
"warn.noDescription": "No description: the new-session mode picker will show it as \"no description yet\".",
|
|
371
386
|
"warn.approvalGateMissing": "The approval gate is off: this DSH build has no tools/pre-execute event, so in-session prompt rewrites do not ask for approval. See the details.",
|
|
372
387
|
"warn.unresolvableRows": "Some rows cannot be resolved on this DSH line: the platform marks the whole mode broken and silently drops it from the new-session picker. Click Fix for this line — it only turns those rows off and leaves your other choices alone.",
|
|
373
|
-
"warn.presetBroken": "The platform marks this mode broken, so it disappears from the new-session picker — and the \"unresolvable\" check above cannot explain why (usually a row whose service or engine never started). Click Fix for this line to turn those rows off. The host's own reason is
|
|
388
|
+
"warn.presetBroken": "The platform marks this mode broken, so it disappears from the new-session picker — and the \"unresolvable\" check above cannot explain why (usually a row whose service or engine never started). Click Fix for this line to turn those rows off. The host's own reason is on the line below.",
|
|
389
|
+
"warn.badPromptVariable": "The system prompt contains an unknown or invalid variable reference, so every request in this mode fails. Fix it, then save.",
|
|
390
|
+
"warn.duplicateRowIds": "The composition repeats a row id. A switch applies only to the first copy. Remove the duplicate.",
|
|
391
|
+
"warn.predicateFailed": "A platform condition failed to evaluate. That row is treated as off so the mode can still mount.",
|
|
374
392
|
"warn.exclusiveRowsActive": "Both shells are on, and they register the same tool names: the platform marks the whole mode broken and silently drops it from the new-session picker. Turn one of them off — saving from the settings page also resolves it automatically.",
|
|
375
393
|
"warn.noName": "No name: the mode picker will show the directory id (e.g. custom).",
|
|
376
394
|
"warn.baseCompositionUnavailable": "This DSH line exposes no shipped composition: the base mode and the plugin switches cannot be changed right now (the system prompt is unaffected and still saves). The routes that were tried are in the host log.",
|
|
@@ -385,7 +403,7 @@ try {
|
|
|
385
403
|
"msg.importTooLarge": "That file is too large (1 MB limit).",
|
|
386
404
|
"msg.exported": "Exported to a file.",
|
|
387
405
|
"msg.exportFailed": "Export failed",
|
|
388
|
-
"delete.title": "Delete
|
|
406
|
+
"delete.title": "Delete \"{name}\" permanently?",
|
|
389
407
|
"delete.description": "This removes the mode directory from disk, system prompt included, and cannot be undone. Sessions already using it keep running; new sessions no longer offer it. ({id})",
|
|
390
408
|
"delete.acknowledge": "I understand this assistant's prompt will be deleted permanently",
|
|
391
409
|
"delete.confirm": "Delete permanently",
|
|
@@ -396,8 +414,13 @@ try {
|
|
|
396
414
|
"name.placeholder": "Custom mode",
|
|
397
415
|
"name.descriptionPlaceholder": "Mode description (shown in the mode picker, optional)",
|
|
398
416
|
"mode.heading": "Base mode",
|
|
399
|
-
"mode.hint": "The base decides
|
|
417
|
+
"mode.hint": "The base decides which rows and tools this mode has. Its identity is always the prompt on this page, not the base mode's own wording. A save applies to the next new session; no restart is required.",
|
|
400
418
|
"rows.heading": "Plugin switches",
|
|
419
|
+
"rows.filterPlaceholder": "Filter rows by name or id",
|
|
420
|
+
"cap.heading": "Capabilities",
|
|
421
|
+
"cap.expand": "Show capabilities",
|
|
422
|
+
"cap.collapse": "Hide capabilities",
|
|
423
|
+
"cap.hint": "The base mode chooses the row set. Each switch applies to one row. Both take effect on the next new session.",
|
|
401
424
|
"rows.hint": "Control row by row which plugins this mode mounts. Untouched rows keep the official default (platform conditions included); an explicit choice wins.",
|
|
402
425
|
"prompt.heading": "System prompt",
|
|
403
426
|
"prompt.hint": "This text is re-read before every model call, so a save applies on the next step and only affects sessions on this assistant. \"Import\" reads a file into the editor (nothing is written until you save); \"Export\" saves the current text as a .md file; \"Reset to factory prompt\" puts the shipped template back into the editor (also saved only when you save).",
|
|
@@ -410,6 +433,7 @@ try {
|
|
|
410
433
|
"btn.saving": "Working…",
|
|
411
434
|
"btn.reload": "Reload",
|
|
412
435
|
"btn.reloadDiscard": "Discard edits and reload",
|
|
436
|
+
"btn.reloadConfirm": "Discard unsaved edits and reload?",
|
|
413
437
|
"msg.unsaved": "Unsaved changes",
|
|
414
438
|
"msg.readFailed": "Load failed",
|
|
415
439
|
"msg.saveFailed": "Save failed",
|
|
@@ -445,6 +469,10 @@ try {
|
|
|
445
469
|
"row.tool-subagent-codex.note": "Needs its Bundle installed first; the shipped state is in the details",
|
|
446
470
|
"row.tool-subagent-claude-code.label": "Claude Code subagent",
|
|
447
471
|
"row.tool-subagent-claude-code.note": "Needs its Bundle installed first; the shipped state is in the details",
|
|
472
|
+
"row.time-context.label": "Time context (the model can see the clock)",
|
|
473
|
+
"row.time-context.note": "On eligible steps, appends the current time, the browser zone and the gap since the previous model-visible message; 10-minute minimum interval by default",
|
|
474
|
+
"row.tool-schedule.label": "Scheduled reminders tool",
|
|
475
|
+
"row.tool-schedule.note": "Lets the agent create, list, edit and delete durable reminders; the host schedule service owns storage and delivery",
|
|
448
476
|
"row.workflow-ptc.label": "Workflow engine",
|
|
449
477
|
"row.workflow-worker-thread.label": "Workflow worker thread",
|
|
450
478
|
"row.workflow-worker-thread.note": "Runs workflows on a separate worker thread",
|
|
@@ -482,7 +510,7 @@ try {
|
|
|
482
510
|
"base.minimal.note": "Shell and terminal only, 7 rows; no files, search, skills or subagents. The system prompt is not minimal's either (this mode replaces the persona row)",
|
|
483
511
|
"base.cordis.label": "Creator mode",
|
|
484
512
|
"base.cordis.note": "Standard plus the Cordis toolset, letting the agent modify its own harness",
|
|
485
|
-
"base.all.label": "
|
|
513
|
+
"base.all.label": "All shipped rows",
|
|
486
514
|
"base.all.note": "Inherits no single shipped mode: standard's full row list plus the rows only PTC / Minimal / Creator declare, every one of them switchable"
|
|
487
515
|
}
|
|
488
516
|
|
|
@@ -724,7 +752,7 @@ try {
|
|
|
724
752
|
".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
|
|
725
753
|
".cpfe-row-detail{display:flex;flex-direction:column;gap:4px;margin:0 0 12px;padding:0 0 0 16px;border:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
|
|
726
754
|
".cpfe-detail-line{display:flex;gap:8px;min-width:0}",
|
|
727
|
-
".cpfe-detail-key{flex:0 0
|
|
755
|
+
".cpfe-detail-key{flex:0 0 9.5em;color:var(--dsw-alias-label-secondary)}",
|
|
728
756
|
".cpfe-detail-value{min-width:0;overflow-wrap:anywhere}",
|
|
729
757
|
".cpfe-sec-head{display:flex;align-items:center;justify-content:space-between;gap:var(--g);flex-wrap:wrap}",
|
|
730
758
|
".cpfe-newinput{flex:1;min-width:200px}",
|
|
@@ -741,6 +769,9 @@ try {
|
|
|
741
769
|
".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap;height:18px}",
|
|
742
770
|
".cpfe-editor{box-sizing:border-box;width:100%;min-height:240px;resize:vertical;padding:12px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:20px}",
|
|
743
771
|
".cpfe-bar{display:flex;align-items:center;gap:var(--g);flex-wrap:wrap}",
|
|
772
|
+
".cpfe-savebar{position:sticky;bottom:0;z-index:2;padding:8px 0;background:var(--dsw-alias-bg-base)}",
|
|
773
|
+
".cpfe-cap-toggle{appearance:none;cursor:pointer;border:0;background:transparent;padding:0;font:inherit;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}",
|
|
774
|
+
".cpfe-filter{box-sizing:border-box;width:100%;height:36px;margin:0 0 8px;padding:0 12px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}",
|
|
744
775
|
".cpfe-status{font-size:13px;line-height:20px}",
|
|
745
776
|
".cpfe-ok{color:var(--dsw-alias-label-secondary)}",
|
|
746
777
|
".cpfe-err{color:var(--dsw-alias-state-error-primary)}",
|
|
@@ -762,7 +793,7 @@ try {
|
|
|
762
793
|
// 光拦状态还不够:DOM 会先显示用户敲进去的字/翻过去的开关,等下一次重渲染才被拉回来 ——
|
|
763
794
|
// 那就是"我明明改了,它自己弹回去了"。`pointer-events` 让点击根本到不了控件,
|
|
764
795
|
// 视觉上也给一个明确的"此刻不能动"。
|
|
765
|
-
".cpfe-busy .cpfe-switch,.cpfe-busy .cpfe-row-toggle,.cpfe-busy .cpfe-input,.cpfe-busy .cpfe-field,.cpfe-busy .cpfe-editor{pointer-events:none;opacity:.65}",
|
|
796
|
+
".cpfe-busy .cpfe-switch,.cpfe-busy .cpfe-row-switch,.cpfe-busy .cpfe-row-toggle,.cpfe-busy .cpfe-input,.cpfe-busy .cpfe-field,.cpfe-busy .cpfe-editor,.cpfe-busy .cpfe-pill,.cpfe-busy .cpfe-selector{pointer-events:none;opacity:.65}",
|
|
766
797
|
".cpfe-busy .cpfe-editor{cursor:progress}",
|
|
767
798
|
".cpfe-tag{font-size:12px;line-height:18px;padding:0 5px;border-radius:4px;border:.5px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary)}",
|
|
768
799
|
".cpfe-tag-success,.cpfe-tag-info{color:var(--dsw-alias-state-success-primary)}",
|
|
@@ -877,6 +908,7 @@ try {
|
|
|
877
908
|
// 改动历史:列表来自 state(只有元数据),正文点「载入这一版」时按需取。
|
|
878
909
|
history: Array.isArray(state.history) ? state.history : [],
|
|
879
910
|
historyPick: "",
|
|
911
|
+
broken: typeof state.broken === "string" && state.broken !== "" ? state.broken : null,
|
|
880
912
|
// 「配置了却不生效」的告警码;文案按当前语言渲染。
|
|
881
913
|
warnings: Array.isArray(state.warnings) ? state.warnings : [],
|
|
882
914
|
}
|
|
@@ -927,6 +959,20 @@ try {
|
|
|
927
959
|
* this change: 33 rows at 274px wide and 84–117px tall with a bare `tool-bash` line each, i.e. uneven
|
|
928
960
|
* heights and a lot of vertical noise for information most users never read.
|
|
929
961
|
*/
|
|
962
|
+
function filterRows(rows, query, translateRow) {
|
|
963
|
+
const needle = String(query || "").trim().toLowerCase()
|
|
964
|
+
if (needle === "") return rows
|
|
965
|
+
const out = []
|
|
966
|
+
for (const row of rows) {
|
|
967
|
+
const title = String(translateRow("row." + row.id + ".label", row.label)).toLowerCase()
|
|
968
|
+
const self = title.includes(needle) || row.id.toLowerCase().includes(needle)
|
|
969
|
+
const children = Array.isArray(row.children) ? row.children : []
|
|
970
|
+
const kids = self ? children : filterRows(children, query, translateRow)
|
|
971
|
+
if (self || kids.length > 0) out.push(self ? row : { ...row, children: kids })
|
|
972
|
+
}
|
|
973
|
+
return out
|
|
974
|
+
}
|
|
975
|
+
|
|
930
976
|
function RowList(props) {
|
|
931
977
|
const { rows, overrides, onToggle, expanded, onToggleExpand, depth, t } = props
|
|
932
978
|
return react.createElement(
|
|
@@ -990,6 +1036,7 @@ try {
|
|
|
990
1036
|
),
|
|
991
1037
|
react.createElement(A.Switch, {
|
|
992
1038
|
checked: effective,
|
|
1039
|
+
disabled: props.disabled === true,
|
|
993
1040
|
onChange: (next) => onToggle(row.id, next),
|
|
994
1041
|
// `Switch` renders NO text: its `label` is the accessible name only, so the
|
|
995
1042
|
// visible title is drawn next to it (an unlabelled toggle is unusable).
|
|
@@ -1060,6 +1107,7 @@ try {
|
|
|
1060
1107
|
onToggleExpand: onToggleExpand,
|
|
1061
1108
|
depth: depth + 1,
|
|
1062
1109
|
t: t,
|
|
1110
|
+
disabled: props.disabled === true,
|
|
1063
1111
|
})
|
|
1064
1112
|
: null,
|
|
1065
1113
|
)
|
|
@@ -1107,10 +1155,19 @@ try {
|
|
|
1107
1155
|
* 词典里没有这个 id 时(旧宿主 / 未来新增)退回机器名,宁可难看也不要空。
|
|
1108
1156
|
*/
|
|
1109
1157
|
const params = result.params
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1158
|
+
let shown = params
|
|
1159
|
+
if (params !== null && typeof params === "object") {
|
|
1160
|
+
shown = { ...params }
|
|
1161
|
+
if (typeof params.mode === "string") {
|
|
1162
|
+
shown.mode = translate("base." + params.mode + ".label", params.mode, shellT)
|
|
1163
|
+
}
|
|
1164
|
+
if (typeof params.rows === "string") {
|
|
1165
|
+
shown.rows = params.rows
|
|
1166
|
+
.split(",")
|
|
1167
|
+
.map((id) => translate("row." + id.trim() + ".label", id.trim(), shellT))
|
|
1168
|
+
.join(", ")
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1114
1171
|
return fillPlaceholders(template, shown)
|
|
1115
1172
|
}
|
|
1116
1173
|
}
|
|
@@ -1141,6 +1198,9 @@ try {
|
|
|
1141
1198
|
// 两个官方风格的下拉(壳的 `Menu`):助手切换与改动历史。壳不提供 Menu 时退回原生控件。
|
|
1142
1199
|
const [assistantOpen, setAssistantOpen] = react.useState(false)
|
|
1143
1200
|
const [historyOpen, setHistoryOpen] = react.useState(false)
|
|
1201
|
+
const [listFailed, setListFailed] = react.useState(false)
|
|
1202
|
+
const [capOpen, setCapOpen] = react.useState(false)
|
|
1203
|
+
const [rowQuery, setRowQuery] = react.useState("")
|
|
1144
1204
|
/** 本机这条线上有无法解析的行时,一键把它们关掉(服务端复用保存同一条排版手术)。 */
|
|
1145
1205
|
const repairRowsNow = async () => {
|
|
1146
1206
|
if (draft === null) return
|
|
@@ -1250,10 +1310,12 @@ try {
|
|
|
1250
1310
|
const result = await fetchList()
|
|
1251
1311
|
if (result === null || result === undefined || result.ok !== true) {
|
|
1252
1312
|
setFailed(true)
|
|
1313
|
+
setListFailed(true)
|
|
1253
1314
|
setStatus(apiText(result, "msg.readFailed"))
|
|
1254
1315
|
return
|
|
1255
1316
|
}
|
|
1256
1317
|
const assistants = Array.isArray(result.assistants) ? result.assistants : []
|
|
1318
|
+
setListFailed(false)
|
|
1257
1319
|
setList(assistants)
|
|
1258
1320
|
const wanted =
|
|
1259
1321
|
typeof preferId === "string" && preferId !== ""
|
|
@@ -1343,7 +1405,18 @@ try {
|
|
|
1343
1405
|
}
|
|
1344
1406
|
|
|
1345
1407
|
const pick = async (id) => {
|
|
1346
|
-
if (id === selected)
|
|
1408
|
+
if (id === selected) {
|
|
1409
|
+
setBusy(true)
|
|
1410
|
+
try {
|
|
1411
|
+
await open(id, { force: true })
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
setFailed(true)
|
|
1414
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.readFailed"), detail: describeError(error) }))
|
|
1415
|
+
} finally {
|
|
1416
|
+
setBusy(false)
|
|
1417
|
+
}
|
|
1418
|
+
return
|
|
1419
|
+
}
|
|
1347
1420
|
setSelected(id)
|
|
1348
1421
|
setDeleteOpen(false)
|
|
1349
1422
|
setAcknowledged(false)
|
|
@@ -1361,6 +1434,7 @@ try {
|
|
|
1361
1434
|
|
|
1362
1435
|
/** Create a new assistant, or duplicate the selected one. */
|
|
1363
1436
|
const create = async (options) => {
|
|
1437
|
+
if (busy === true) return
|
|
1364
1438
|
const from = options !== null && typeof options === "object" ? options.from : undefined
|
|
1365
1439
|
const name =
|
|
1366
1440
|
options !== null && typeof options === "object" && typeof options.name === "string"
|
|
@@ -1541,7 +1615,7 @@ try {
|
|
|
1541
1615
|
* confirmation instead of losing the guardrail entirely.
|
|
1542
1616
|
*/
|
|
1543
1617
|
const askDelete = () => {
|
|
1544
|
-
if (
|
|
1618
|
+
if (renderable(A.RiskConfirmation)) {
|
|
1545
1619
|
setAcknowledged(false)
|
|
1546
1620
|
setDeleteOpen(true)
|
|
1547
1621
|
return
|
|
@@ -1569,7 +1643,13 @@ try {
|
|
|
1569
1643
|
})
|
|
1570
1644
|
if (result !== null && result !== undefined && result.ok === true) {
|
|
1571
1645
|
setFailed(false)
|
|
1572
|
-
|
|
1646
|
+
const compositionChanged =
|
|
1647
|
+
entry !== undefined &&
|
|
1648
|
+
(draft.mode !== entry.saved.mode ||
|
|
1649
|
+
JSON.stringify(draft.overrides) !== JSON.stringify(entry.saved.overrides))
|
|
1650
|
+
const spoken =
|
|
1651
|
+
result.code === "saved" && compositionChanged !== true ? { ...result, code: "savedPrompt" } : result
|
|
1652
|
+
setStatus(apiText(spoken, "msg.saved"))
|
|
1573
1653
|
// The server normalises what it stores (trimmed name, recomputed overrides),
|
|
1574
1654
|
// so the just-saved draft is replaced by what it actually wrote. Without this
|
|
1575
1655
|
// the page would show 「已保存」 and 「未保存」 at the same time.
|
|
@@ -1612,7 +1692,14 @@ try {
|
|
|
1612
1692
|
react.createElement("h2", { className: "cpfe-h" }, t("assistant.heading")),
|
|
1613
1693
|
react.createElement(SectionHint, { text: t("assistant.hint") }),
|
|
1614
1694
|
list === null
|
|
1615
|
-
? react.createElement(
|
|
1695
|
+
? react.createElement(
|
|
1696
|
+
"div",
|
|
1697
|
+
null,
|
|
1698
|
+
react.createElement("p", { className: "cpfe-sub" }, listFailed ? t("msg.readFailed") : t("assistant.loadingList")),
|
|
1699
|
+
listFailed
|
|
1700
|
+
? react.createElement(A.Button, { disabled: busy, onClick: () => reload("") }, t("btn.reload"))
|
|
1701
|
+
: null,
|
|
1702
|
+
)
|
|
1616
1703
|
: assistants.length === 0
|
|
1617
1704
|
? react.createElement("p", { className: "cpfe-sub" }, t("assistant.empty"))
|
|
1618
1705
|
: react.createElement(
|
|
@@ -1708,7 +1795,7 @@ try {
|
|
|
1708
1795
|
placeholder: t("assistant.newPlaceholder"),
|
|
1709
1796
|
"aria-label": t("assistant.newPlaceholder"),
|
|
1710
1797
|
onKeyDown: (event) => {
|
|
1711
|
-
if (event.key === "Enter") create()
|
|
1798
|
+
if (event.key === "Enter" && busy !== true) create()
|
|
1712
1799
|
},
|
|
1713
1800
|
onChange: (event) => {
|
|
1714
1801
|
setNewName(event.target.value)
|
|
@@ -1726,6 +1813,60 @@ try {
|
|
|
1726
1813
|
busy ? t("btn.creating") : t("btn.create"),
|
|
1727
1814
|
),
|
|
1728
1815
|
),
|
|
1816
|
+
editorReady
|
|
1817
|
+
? react.createElement(
|
|
1818
|
+
"div",
|
|
1819
|
+
{ className: "cpfe-actions" },
|
|
1820
|
+
react.createElement(
|
|
1821
|
+
A.Button,
|
|
1822
|
+
{
|
|
1823
|
+
variant: "outline",
|
|
1824
|
+
size: "sm",
|
|
1825
|
+
icon: A.IconChevronUpOutline14 === undefined ? null : react.createElement(A.IconChevronUpOutline14),
|
|
1826
|
+
disabled: busy || position <= 0,
|
|
1827
|
+
onClick: () => move("up"),
|
|
1828
|
+
},
|
|
1829
|
+
t("btn.moveUp"),
|
|
1830
|
+
),
|
|
1831
|
+
react.createElement(
|
|
1832
|
+
A.Button,
|
|
1833
|
+
{
|
|
1834
|
+
variant: "outline",
|
|
1835
|
+
size: "sm",
|
|
1836
|
+
icon: A.IconChevronDownOutline14 === undefined ? null : react.createElement(A.IconChevronDownOutline14),
|
|
1837
|
+
disabled: busy || position === -1 || position >= assistants.length - 1,
|
|
1838
|
+
onClick: () => move("down"),
|
|
1839
|
+
},
|
|
1840
|
+
t("btn.moveDown"),
|
|
1841
|
+
),
|
|
1842
|
+
react.createElement(
|
|
1843
|
+
A.Button,
|
|
1844
|
+
{
|
|
1845
|
+
variant: "outline",
|
|
1846
|
+
size: "sm",
|
|
1847
|
+
icon: A.IconCopyOutline16 === undefined ? null : react.createElement(A.IconCopyOutline16),
|
|
1848
|
+
disabled: busy,
|
|
1849
|
+
onClick: duplicate,
|
|
1850
|
+
},
|
|
1851
|
+
t("btn.duplicate"),
|
|
1852
|
+
),
|
|
1853
|
+
react.createElement(
|
|
1854
|
+
A.Button,
|
|
1855
|
+
{
|
|
1856
|
+
variant: "ghost",
|
|
1857
|
+
size: "sm",
|
|
1858
|
+
className: "cpfe-danger",
|
|
1859
|
+
icon: A.IconTrashOutline16 === undefined ? null : react.createElement(A.IconTrashOutline16),
|
|
1860
|
+
disabled: busy,
|
|
1861
|
+
onClick: askDelete,
|
|
1862
|
+
},
|
|
1863
|
+
t("btn.delete"),
|
|
1864
|
+
),
|
|
1865
|
+
)
|
|
1866
|
+
: null,
|
|
1867
|
+
typeof (assistants.find((item) => item.id === selected) || {}).broken === "string"
|
|
1868
|
+
? react.createElement("p", { className: "cpfe-note" }, t("assistant.brokenHint"))
|
|
1869
|
+
: null,
|
|
1729
1870
|
react.createElement("p", { className: "cpfe-note" }, t("msg.readOnlyHint")),
|
|
1730
1871
|
)
|
|
1731
1872
|
|
|
@@ -1758,55 +1899,6 @@ try {
|
|
|
1758
1899
|
"aria-label": t("name.descriptionPlaceholder"),
|
|
1759
1900
|
onChange: (event) => update({ description: event.target.value }),
|
|
1760
1901
|
}),
|
|
1761
|
-
react.createElement(
|
|
1762
|
-
"div",
|
|
1763
|
-
{ className: "cpfe-actions" },
|
|
1764
|
-
react.createElement(
|
|
1765
|
-
A.Button,
|
|
1766
|
-
{
|
|
1767
|
-
variant: "outline",
|
|
1768
|
-
size: "sm",
|
|
1769
|
-
icon: A.IconChevronUpOutline14 === undefined ? null : react.createElement(A.IconChevronUpOutline14),
|
|
1770
|
-
disabled: busy || position <= 0,
|
|
1771
|
-
onClick: () => move("up"),
|
|
1772
|
-
},
|
|
1773
|
-
t("btn.moveUp"),
|
|
1774
|
-
),
|
|
1775
|
-
react.createElement(
|
|
1776
|
-
A.Button,
|
|
1777
|
-
{
|
|
1778
|
-
variant: "outline",
|
|
1779
|
-
size: "sm",
|
|
1780
|
-
icon: A.IconChevronDownOutline14 === undefined ? null : react.createElement(A.IconChevronDownOutline14),
|
|
1781
|
-
disabled: busy || position === -1 || position >= assistants.length - 1,
|
|
1782
|
-
onClick: () => move("down"),
|
|
1783
|
-
},
|
|
1784
|
-
t("btn.moveDown"),
|
|
1785
|
-
),
|
|
1786
|
-
react.createElement(
|
|
1787
|
-
A.Button,
|
|
1788
|
-
{
|
|
1789
|
-
variant: "outline",
|
|
1790
|
-
size: "sm",
|
|
1791
|
-
icon: A.IconCopyOutline16 === undefined ? null : react.createElement(A.IconCopyOutline16),
|
|
1792
|
-
disabled: busy,
|
|
1793
|
-
onClick: duplicate,
|
|
1794
|
-
},
|
|
1795
|
-
t("btn.duplicate"),
|
|
1796
|
-
),
|
|
1797
|
-
react.createElement(
|
|
1798
|
-
A.Button,
|
|
1799
|
-
{
|
|
1800
|
-
variant: "ghost",
|
|
1801
|
-
size: "sm",
|
|
1802
|
-
className: "cpfe-danger",
|
|
1803
|
-
icon: A.IconTrashOutline16 === undefined ? null : react.createElement(A.IconTrashOutline16),
|
|
1804
|
-
disabled: busy,
|
|
1805
|
-
onClick: askDelete,
|
|
1806
|
-
},
|
|
1807
|
-
t("btn.delete"),
|
|
1808
|
-
),
|
|
1809
|
-
),
|
|
1810
1902
|
),
|
|
1811
1903
|
react.createElement(
|
|
1812
1904
|
"section",
|
|
@@ -1823,7 +1915,13 @@ try {
|
|
|
1823
1915
|
payload.modes.map((mode) =>
|
|
1824
1916
|
react.createElement(
|
|
1825
1917
|
A.Pill,
|
|
1826
|
-
{
|
|
1918
|
+
{
|
|
1919
|
+
key: mode.id,
|
|
1920
|
+
active: draft.mode === mode.id,
|
|
1921
|
+
disabled: busy,
|
|
1922
|
+
"aria-pressed": draft.mode === mode.id,
|
|
1923
|
+
onClick: () => pickMode(mode.id),
|
|
1924
|
+
},
|
|
1827
1925
|
t("base." + mode.id + ".label", mode.label),
|
|
1828
1926
|
),
|
|
1829
1927
|
),
|
|
@@ -1851,15 +1949,30 @@ try {
|
|
|
1851
1949
|
react.createElement(SectionHint, { text: t("rows.hint") }),
|
|
1852
1950
|
draft.baseUnavailable !== null
|
|
1853
1951
|
? react.createElement("p", { className: "cpfe-note" }, t("rows.unavailable"))
|
|
1854
|
-
: react.createElement(
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1952
|
+
: react.createElement(
|
|
1953
|
+
"div",
|
|
1954
|
+
null,
|
|
1955
|
+
react.createElement("input", {
|
|
1956
|
+
className: "cpfe-filter",
|
|
1957
|
+
value: rowQuery,
|
|
1958
|
+
placeholder: t("rows.filterPlaceholder"),
|
|
1959
|
+
"aria-label": t("rows.filterPlaceholder"),
|
|
1960
|
+
onChange: (event) => setRowQuery(event.target.value),
|
|
1961
|
+
}),
|
|
1962
|
+
react.createElement(RowList, {
|
|
1963
|
+
expanded: expandedRows,
|
|
1964
|
+
onToggleExpand: toggleRowExpanded,
|
|
1965
|
+
rows: filterRows(payload.rows, rowQuery, t),
|
|
1966
|
+
overrides: draft.overrides,
|
|
1967
|
+
onToggle: (id, next) => {
|
|
1968
|
+
if (draft.mode !== savedMode) return
|
|
1969
|
+
update({ overrides: { ...draft.overrides, [id]: next } })
|
|
1970
|
+
},
|
|
1971
|
+
depth: 0,
|
|
1972
|
+
t: t,
|
|
1973
|
+
disabled: busy === true || draft.mode !== savedMode,
|
|
1974
|
+
}),
|
|
1975
|
+
),
|
|
1863
1976
|
),
|
|
1864
1977
|
react.createElement(
|
|
1865
1978
|
"section",
|
|
@@ -2076,10 +2189,10 @@ try {
|
|
|
2076
2189
|
),
|
|
2077
2190
|
),
|
|
2078
2191
|
),
|
|
2079
|
-
...editorSections,
|
|
2192
|
+
...(editorReady ? editorSections.filter((section) => section.key === "name" || section.key === "prompt") : []),
|
|
2080
2193
|
react.createElement(
|
|
2081
2194
|
"div",
|
|
2082
|
-
{ className: "cpfe-bar" },
|
|
2195
|
+
{ className: "cpfe-bar cpfe-savebar" },
|
|
2083
2196
|
react.createElement(
|
|
2084
2197
|
A.Button,
|
|
2085
2198
|
{
|
|
@@ -2095,11 +2208,14 @@ try {
|
|
|
2095
2208
|
{
|
|
2096
2209
|
variant: "outline",
|
|
2097
2210
|
icon: A.IconRefreshOutline16 === undefined ? null : react.createElement(A.IconRefreshOutline16),
|
|
2098
|
-
disabled: busy
|
|
2211
|
+
disabled: busy,
|
|
2099
2212
|
// Explicitly destructive of the local draft, so it says so while there is
|
|
2100
2213
|
// one: switching assistants never discards edits, and this button is the
|
|
2101
2214
|
// one place that does.
|
|
2102
|
-
onClick: () =>
|
|
2215
|
+
onClick: () => {
|
|
2216
|
+
if (dirty && typeof window !== "undefined" && window.confirm(t("btn.reloadConfirm")) !== true) return
|
|
2217
|
+
reload(selected, { discard: true })
|
|
2218
|
+
},
|
|
2103
2219
|
},
|
|
2104
2220
|
dirty ? t("btn.reloadDiscard") : t("btn.reload"),
|
|
2105
2221
|
),
|
|
@@ -2119,13 +2235,32 @@ try {
|
|
|
2119
2235
|
react.createElement("span", { className: "cpfe-path" }, editorReady ? payload.compositionPath : ""),
|
|
2120
2236
|
),
|
|
2121
2237
|
),
|
|
2238
|
+
editorReady
|
|
2239
|
+
? react.createElement(
|
|
2240
|
+
"section",
|
|
2241
|
+
null,
|
|
2242
|
+
react.createElement(
|
|
2243
|
+
"button",
|
|
2244
|
+
{
|
|
2245
|
+
type: "button",
|
|
2246
|
+
"data-cap": "toggle",
|
|
2247
|
+
className: "cpfe-h",
|
|
2248
|
+
"aria-expanded": capOpen,
|
|
2249
|
+
onClick: () => setCapOpen((open) => open !== true),
|
|
2250
|
+
},
|
|
2251
|
+
(capOpen ? t("cap.collapse") : t("cap.expand")) + " · " + t("cap.heading"),
|
|
2252
|
+
),
|
|
2253
|
+
react.createElement(SectionHint, { text: t("cap.hint") }),
|
|
2254
|
+
capOpen ? editorSections.filter((section) => section.key === "mode" || section.key === "rows") : null,
|
|
2255
|
+
)
|
|
2256
|
+
: null,
|
|
2122
2257
|
// The confirmation is a portal: rendering it here keeps every piece of this page's
|
|
2123
2258
|
// state in one component.
|
|
2124
|
-
|
|
2259
|
+
renderable(A.RiskConfirmation) && editorReady
|
|
2125
2260
|
? react.createElement(A.RiskConfirmation, {
|
|
2126
2261
|
open: deleteOpen,
|
|
2127
|
-
title: t("delete.title"),
|
|
2128
|
-
description: fillPlaceholders(t("delete.description"), { id: draft.id }),
|
|
2262
|
+
title: fillPlaceholders(t("delete.title"), { name: draft.name || draft.id }),
|
|
2263
|
+
description: fillPlaceholders(t("delete.description"), { id: draft.id, name: draft.name || draft.id }),
|
|
2129
2264
|
acknowledgeLabel: t("delete.acknowledge"),
|
|
2130
2265
|
cancelLabel: t("btn.cancel"),
|
|
2131
2266
|
closeLabel: t("delete.close"),
|