dsh-custom-mode 1.11.6 → 1.12.1
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 +12 -48
- package/README.zh.md +12 -45
- package/atomic.mjs +31 -1
- package/client.js +243 -100
- package/composition.mjs +39 -5
- package/index.mjs +38 -10
- package/locales.mjs +43 -17
- package/package.json +8 -4
- package/preset/prompt-tool.mjs +4 -4
- package/preset-backend/declarative.mjs +24 -5
package/client.js
CHANGED
|
@@ -61,7 +61,7 @@ try {
|
|
|
61
61
|
const ZH = {
|
|
62
62
|
"nav": "自定义模式",
|
|
63
63
|
"assistant.heading": "助手",
|
|
64
|
-
"assistant.hint": "
|
|
64
|
+
"assistant.hint": "每个助手是一套独立模式,有自己的提示词、基础模式和插件开关。新建会话时在模式菜单里选用。",
|
|
65
65
|
"assistant.empty": "还没有助手,用下面的输入框新建一个。",
|
|
66
66
|
"assistant.loadingList": "正在读取助手列表…",
|
|
67
67
|
"assistant.newPlaceholder": "新助手的名字(例如:写作助手)",
|
|
@@ -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,11 +185,16 @@ 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
|
-
"prompt.hint": "
|
|
197
|
+
"prompt.hint": "保存后,下一次模型调用就会用这段文字。导入只进编辑器;导出为 Markdown。恢复出厂后仍要点保存。",
|
|
189
198
|
"status.enabled": "已启用",
|
|
190
199
|
"status.disabled": "已停用",
|
|
191
200
|
"status.changed": "已改",
|
|
@@ -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,14 +283,14 @@ 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
|
|
|
276
290
|
const EN = {
|
|
277
291
|
"nav": "Custom mode",
|
|
278
292
|
"assistant.heading": "Assistants",
|
|
279
|
-
"assistant.hint": "Each assistant is
|
|
293
|
+
"assistant.hint": "Each assistant is its own mode, with its own prompt, base mode and plugin switches. Pick one from the mode menu when you start a session.",
|
|
280
294
|
"assistant.empty": "No assistants yet — create one with the field below.",
|
|
281
295
|
"assistant.loadingList": "Loading assistants…",
|
|
282
296
|
"assistant.newPlaceholder": "Name of the new assistant (e.g. Writing assistant)",
|
|
@@ -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,11 +414,16 @@ 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 tools. Each switch applies to one row. Both start 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
|
-
"prompt.hint": "
|
|
426
|
+
"prompt.hint": "After you save, the next model step uses this text. Import only fills the editor. Export writes Markdown. Reset to factory still needs a save.",
|
|
404
427
|
"status.enabled": "Enabled",
|
|
405
428
|
"status.disabled": "Disabled",
|
|
406
429
|
"status.changed": "changed",
|
|
@@ -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
|
|
|
@@ -500,14 +528,22 @@ try {
|
|
|
500
528
|
}
|
|
501
529
|
|
|
502
530
|
function activeLanguage(locale) {
|
|
531
|
+
let id = ""
|
|
503
532
|
try {
|
|
504
533
|
const snapshot = locale !== undefined && typeof locale.getLocale === "function" ? locale.getLocale() : undefined
|
|
505
|
-
|
|
506
|
-
if (id !== "") return id.toLowerCase().startsWith("zh") ? "zh" : "en"
|
|
534
|
+
if (snapshot !== undefined && typeof snapshot.active === "string") id = snapshot.active
|
|
507
535
|
} catch (error) {
|
|
508
|
-
/* a shell without a readable snapshot falls through
|
|
536
|
+
/* a shell without a readable snapshot falls through */
|
|
509
537
|
}
|
|
510
|
-
|
|
538
|
+
if (id === "" && typeof navigator !== "undefined" && typeof navigator.language === "string") id = navigator.language
|
|
539
|
+
const norm = id.toLowerCase()
|
|
540
|
+
if (norm.startsWith("zh")) return "zh"
|
|
541
|
+
if (norm.startsWith("en")) return "en"
|
|
542
|
+
const primary = norm.split("-")[0]
|
|
543
|
+
if (Object.prototype.hasOwnProperty.call(TRANSLATIONS, norm)) return norm
|
|
544
|
+
if (primary !== "" && Object.prototype.hasOwnProperty.call(TRANSLATIONS, primary)) return primary
|
|
545
|
+
// 壳目前内置 zh / en,语言包可以再注册别的 id。没有对应词典时用英文,避免整页中文或裸键。
|
|
546
|
+
return "en"
|
|
511
547
|
}
|
|
512
548
|
|
|
513
549
|
/**
|
|
@@ -685,7 +721,7 @@ try {
|
|
|
685
721
|
* grids, the status line).
|
|
686
722
|
*/
|
|
687
723
|
const CSS = [
|
|
688
|
-
".cpfe{--g:8px;display:flex;flex-direction:column;gap:24px;width:100%;max-width:900px;box-sizing:border-box;padding-bottom:16px}",
|
|
724
|
+
".cpfe{--g:8px;display:flex;flex-direction:column;gap:24px;width:100%;max-width:900px;box-sizing:border-box;padding-bottom:16px;font:inherit;color:inherit}",
|
|
689
725
|
// 分节标题:度量抄官方设置面板里的同类文本。
|
|
690
726
|
// 实测(**0.1.7-rc.2 与 0.2.0-rc.2 各量一次,结论相同**):官方 `_title`(设置行标题)
|
|
691
727
|
// 与 `groupTitle`(插件页分组标题)都是 14px/22px **w400**;官方唯一的 14px w500 是插件
|
|
@@ -702,7 +738,7 @@ try {
|
|
|
702
738
|
".cpfe-desc{box-sizing:border-box;min-height:56px;max-height:200px;resize:vertical;padding:8px 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;line-height:20px;margin-bottom:8px}",
|
|
703
739
|
".cpfe-base-pending{color:var(--dsw-alias-state-warn-primary)}",
|
|
704
740
|
".cpfe-note{display:block;margin:8px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
|
|
705
|
-
".cpfe-mono{display:block;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
|
|
741
|
+
".cpfe-mono{display:block;margin:0;font-family:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
|
|
706
742
|
".cpfe-pills{display:flex;flex-wrap:wrap;gap:6px;align-items:center}",
|
|
707
743
|
".cpfe-picker{display:flex;align-items:center;gap:8px;margin:2px 0 4px}",
|
|
708
744
|
// 壳的菜单浮层靠一个独立的 backing 元素上色(实测在 0.1.7-rc.2 + headless 下那块是透明的,
|
|
@@ -724,7 +760,7 @@ try {
|
|
|
724
760
|
".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
|
|
725
761
|
".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
762
|
".cpfe-detail-line{display:flex;gap:8px;min-width:0}",
|
|
727
|
-
".cpfe-detail-key{flex:0 0
|
|
763
|
+
".cpfe-detail-key{flex:0 0 9.5em;color:var(--dsw-alias-label-secondary)}",
|
|
728
764
|
".cpfe-detail-value{min-width:0;overflow-wrap:anywhere}",
|
|
729
765
|
".cpfe-sec-head{display:flex;align-items:center;justify-content:space-between;gap:var(--g);flex-wrap:wrap}",
|
|
730
766
|
".cpfe-newinput{flex:1;min-width:200px}",
|
|
@@ -739,8 +775,11 @@ try {
|
|
|
739
775
|
".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:0;padding:16px 0;border:0;border-radius:0;background:none}",
|
|
740
776
|
".cpfe-row-meta{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}",
|
|
741
777
|
".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap;height:18px}",
|
|
742
|
-
".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}",
|
|
778
|
+
".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:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:13px;line-height:20px}",
|
|
743
779
|
".cpfe-bar{display:flex;align-items:center;gap:var(--g);flex-wrap:wrap}",
|
|
780
|
+
".cpfe-savebar{position:sticky;bottom:0;z-index:2;padding:8px 0;background:var(--dsw-alias-bg-base)}",
|
|
781
|
+
".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)}",
|
|
782
|
+
".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
783
|
".cpfe-status{font-size:13px;line-height:20px}",
|
|
745
784
|
".cpfe-ok{color:var(--dsw-alias-label-secondary)}",
|
|
746
785
|
".cpfe-err{color:var(--dsw-alias-state-error-primary)}",
|
|
@@ -748,7 +787,7 @@ try {
|
|
|
748
787
|
".cpfe-danger{color:var(--dsw-alias-state-error-primary)}",
|
|
749
788
|
".cpfe-meta-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;min-width:0}",
|
|
750
789
|
".cpfe-version{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:nowrap}",
|
|
751
|
-
".cpfe-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
|
|
790
|
+
".cpfe-path{font-family:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
|
|
752
791
|
// ── fallback-path controls (unused when the shell provides the atoms) ──
|
|
753
792
|
".cpfe-btn{appearance:none;cursor:pointer;padding:0 14px;height:36px;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:14px}",
|
|
754
793
|
".cpfe-btn:disabled{opacity:.4;cursor:not-allowed}",
|
|
@@ -762,7 +801,7 @@ try {
|
|
|
762
801
|
// 光拦状态还不够:DOM 会先显示用户敲进去的字/翻过去的开关,等下一次重渲染才被拉回来 ——
|
|
763
802
|
// 那就是"我明明改了,它自己弹回去了"。`pointer-events` 让点击根本到不了控件,
|
|
764
803
|
// 视觉上也给一个明确的"此刻不能动"。
|
|
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}",
|
|
804
|
+
".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
805
|
".cpfe-busy .cpfe-editor{cursor:progress}",
|
|
767
806
|
".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
807
|
".cpfe-tag-success,.cpfe-tag-info{color:var(--dsw-alias-state-success-primary)}",
|
|
@@ -877,6 +916,7 @@ try {
|
|
|
877
916
|
// 改动历史:列表来自 state(只有元数据),正文点「载入这一版」时按需取。
|
|
878
917
|
history: Array.isArray(state.history) ? state.history : [],
|
|
879
918
|
historyPick: "",
|
|
919
|
+
broken: typeof state.broken === "string" && state.broken !== "" ? state.broken : null,
|
|
880
920
|
// 「配置了却不生效」的告警码;文案按当前语言渲染。
|
|
881
921
|
warnings: Array.isArray(state.warnings) ? state.warnings : [],
|
|
882
922
|
}
|
|
@@ -927,6 +967,20 @@ try {
|
|
|
927
967
|
* this change: 33 rows at 274px wide and 84–117px tall with a bare `tool-bash` line each, i.e. uneven
|
|
928
968
|
* heights and a lot of vertical noise for information most users never read.
|
|
929
969
|
*/
|
|
970
|
+
function filterRows(rows, query, translateRow) {
|
|
971
|
+
const needle = String(query || "").trim().toLowerCase()
|
|
972
|
+
if (needle === "") return rows
|
|
973
|
+
const out = []
|
|
974
|
+
for (const row of rows) {
|
|
975
|
+
const title = String(translateRow("row." + row.id + ".label", row.label)).toLowerCase()
|
|
976
|
+
const self = title.includes(needle) || row.id.toLowerCase().includes(needle)
|
|
977
|
+
const children = Array.isArray(row.children) ? row.children : []
|
|
978
|
+
const kids = self ? children : filterRows(children, query, translateRow)
|
|
979
|
+
if (self || kids.length > 0) out.push(self ? row : { ...row, children: kids })
|
|
980
|
+
}
|
|
981
|
+
return out
|
|
982
|
+
}
|
|
983
|
+
|
|
930
984
|
function RowList(props) {
|
|
931
985
|
const { rows, overrides, onToggle, expanded, onToggleExpand, depth, t } = props
|
|
932
986
|
return react.createElement(
|
|
@@ -990,6 +1044,7 @@ try {
|
|
|
990
1044
|
),
|
|
991
1045
|
react.createElement(A.Switch, {
|
|
992
1046
|
checked: effective,
|
|
1047
|
+
disabled: props.disabled === true,
|
|
993
1048
|
onChange: (next) => onToggle(row.id, next),
|
|
994
1049
|
// `Switch` renders NO text: its `label` is the accessible name only, so the
|
|
995
1050
|
// visible title is drawn next to it (an unlabelled toggle is unusable).
|
|
@@ -1060,6 +1115,7 @@ try {
|
|
|
1060
1115
|
onToggleExpand: onToggleExpand,
|
|
1061
1116
|
depth: depth + 1,
|
|
1062
1117
|
t: t,
|
|
1118
|
+
disabled: props.disabled === true,
|
|
1063
1119
|
})
|
|
1064
1120
|
: null,
|
|
1065
1121
|
)
|
|
@@ -1107,10 +1163,19 @@ try {
|
|
|
1107
1163
|
* 词典里没有这个 id 时(旧宿主 / 未来新增)退回机器名,宁可难看也不要空。
|
|
1108
1164
|
*/
|
|
1109
1165
|
const params = result.params
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1166
|
+
let shown = params
|
|
1167
|
+
if (params !== null && typeof params === "object") {
|
|
1168
|
+
shown = { ...params }
|
|
1169
|
+
if (typeof params.mode === "string") {
|
|
1170
|
+
shown.mode = translate("base." + params.mode + ".label", params.mode, shellT)
|
|
1171
|
+
}
|
|
1172
|
+
if (typeof params.rows === "string") {
|
|
1173
|
+
shown.rows = params.rows
|
|
1174
|
+
.split(",")
|
|
1175
|
+
.map((id) => translate("row." + id.trim() + ".label", id.trim(), shellT))
|
|
1176
|
+
.join(", ")
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1114
1179
|
return fillPlaceholders(template, shown)
|
|
1115
1180
|
}
|
|
1116
1181
|
}
|
|
@@ -1141,6 +1206,9 @@ try {
|
|
|
1141
1206
|
// 两个官方风格的下拉(壳的 `Menu`):助手切换与改动历史。壳不提供 Menu 时退回原生控件。
|
|
1142
1207
|
const [assistantOpen, setAssistantOpen] = react.useState(false)
|
|
1143
1208
|
const [historyOpen, setHistoryOpen] = react.useState(false)
|
|
1209
|
+
const [listFailed, setListFailed] = react.useState(false)
|
|
1210
|
+
const [capOpen, setCapOpen] = react.useState(false)
|
|
1211
|
+
const [rowQuery, setRowQuery] = react.useState("")
|
|
1144
1212
|
/** 本机这条线上有无法解析的行时,一键把它们关掉(服务端复用保存同一条排版手术)。 */
|
|
1145
1213
|
const repairRowsNow = async () => {
|
|
1146
1214
|
if (draft === null) return
|
|
@@ -1250,10 +1318,12 @@ try {
|
|
|
1250
1318
|
const result = await fetchList()
|
|
1251
1319
|
if (result === null || result === undefined || result.ok !== true) {
|
|
1252
1320
|
setFailed(true)
|
|
1321
|
+
setListFailed(true)
|
|
1253
1322
|
setStatus(apiText(result, "msg.readFailed"))
|
|
1254
1323
|
return
|
|
1255
1324
|
}
|
|
1256
1325
|
const assistants = Array.isArray(result.assistants) ? result.assistants : []
|
|
1326
|
+
setListFailed(false)
|
|
1257
1327
|
setList(assistants)
|
|
1258
1328
|
const wanted =
|
|
1259
1329
|
typeof preferId === "string" && preferId !== ""
|
|
@@ -1343,7 +1413,18 @@ try {
|
|
|
1343
1413
|
}
|
|
1344
1414
|
|
|
1345
1415
|
const pick = async (id) => {
|
|
1346
|
-
if (id === selected)
|
|
1416
|
+
if (id === selected) {
|
|
1417
|
+
setBusy(true)
|
|
1418
|
+
try {
|
|
1419
|
+
await open(id, { force: true })
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
setFailed(true)
|
|
1422
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.readFailed"), detail: describeError(error) }))
|
|
1423
|
+
} finally {
|
|
1424
|
+
setBusy(false)
|
|
1425
|
+
}
|
|
1426
|
+
return
|
|
1427
|
+
}
|
|
1347
1428
|
setSelected(id)
|
|
1348
1429
|
setDeleteOpen(false)
|
|
1349
1430
|
setAcknowledged(false)
|
|
@@ -1361,6 +1442,7 @@ try {
|
|
|
1361
1442
|
|
|
1362
1443
|
/** Create a new assistant, or duplicate the selected one. */
|
|
1363
1444
|
const create = async (options) => {
|
|
1445
|
+
if (busy === true) return
|
|
1364
1446
|
const from = options !== null && typeof options === "object" ? options.from : undefined
|
|
1365
1447
|
const name =
|
|
1366
1448
|
options !== null && typeof options === "object" && typeof options.name === "string"
|
|
@@ -1541,7 +1623,7 @@ try {
|
|
|
1541
1623
|
* confirmation instead of losing the guardrail entirely.
|
|
1542
1624
|
*/
|
|
1543
1625
|
const askDelete = () => {
|
|
1544
|
-
if (
|
|
1626
|
+
if (renderable(A.RiskConfirmation)) {
|
|
1545
1627
|
setAcknowledged(false)
|
|
1546
1628
|
setDeleteOpen(true)
|
|
1547
1629
|
return
|
|
@@ -1569,7 +1651,13 @@ try {
|
|
|
1569
1651
|
})
|
|
1570
1652
|
if (result !== null && result !== undefined && result.ok === true) {
|
|
1571
1653
|
setFailed(false)
|
|
1572
|
-
|
|
1654
|
+
const compositionChanged =
|
|
1655
|
+
entry !== undefined &&
|
|
1656
|
+
(draft.mode !== entry.saved.mode ||
|
|
1657
|
+
JSON.stringify(draft.overrides) !== JSON.stringify(entry.saved.overrides))
|
|
1658
|
+
const spoken =
|
|
1659
|
+
result.code === "saved" && compositionChanged !== true ? { ...result, code: "savedPrompt" } : result
|
|
1660
|
+
setStatus(apiText(spoken, "msg.saved"))
|
|
1573
1661
|
// The server normalises what it stores (trimmed name, recomputed overrides),
|
|
1574
1662
|
// so the just-saved draft is replaced by what it actually wrote. Without this
|
|
1575
1663
|
// the page would show 「已保存」 and 「未保存」 at the same time.
|
|
@@ -1612,7 +1700,14 @@ try {
|
|
|
1612
1700
|
react.createElement("h2", { className: "cpfe-h" }, t("assistant.heading")),
|
|
1613
1701
|
react.createElement(SectionHint, { text: t("assistant.hint") }),
|
|
1614
1702
|
list === null
|
|
1615
|
-
? react.createElement(
|
|
1703
|
+
? react.createElement(
|
|
1704
|
+
"div",
|
|
1705
|
+
null,
|
|
1706
|
+
react.createElement("p", { className: "cpfe-sub" }, listFailed ? t("msg.readFailed") : t("assistant.loadingList")),
|
|
1707
|
+
listFailed
|
|
1708
|
+
? react.createElement(A.Button, { disabled: busy, onClick: () => reload("") }, t("btn.reload"))
|
|
1709
|
+
: null,
|
|
1710
|
+
)
|
|
1616
1711
|
: assistants.length === 0
|
|
1617
1712
|
? react.createElement("p", { className: "cpfe-sub" }, t("assistant.empty"))
|
|
1618
1713
|
: react.createElement(
|
|
@@ -1708,7 +1803,7 @@ try {
|
|
|
1708
1803
|
placeholder: t("assistant.newPlaceholder"),
|
|
1709
1804
|
"aria-label": t("assistant.newPlaceholder"),
|
|
1710
1805
|
onKeyDown: (event) => {
|
|
1711
|
-
if (event.key === "Enter") create()
|
|
1806
|
+
if (event.key === "Enter" && busy !== true) create()
|
|
1712
1807
|
},
|
|
1713
1808
|
onChange: (event) => {
|
|
1714
1809
|
setNewName(event.target.value)
|
|
@@ -1726,6 +1821,60 @@ try {
|
|
|
1726
1821
|
busy ? t("btn.creating") : t("btn.create"),
|
|
1727
1822
|
),
|
|
1728
1823
|
),
|
|
1824
|
+
editorReady
|
|
1825
|
+
? react.createElement(
|
|
1826
|
+
"div",
|
|
1827
|
+
{ className: "cpfe-actions" },
|
|
1828
|
+
react.createElement(
|
|
1829
|
+
A.Button,
|
|
1830
|
+
{
|
|
1831
|
+
variant: "outline",
|
|
1832
|
+
size: "sm",
|
|
1833
|
+
icon: A.IconChevronUpOutline14 === undefined ? null : react.createElement(A.IconChevronUpOutline14),
|
|
1834
|
+
disabled: busy || position <= 0,
|
|
1835
|
+
onClick: () => move("up"),
|
|
1836
|
+
},
|
|
1837
|
+
t("btn.moveUp"),
|
|
1838
|
+
),
|
|
1839
|
+
react.createElement(
|
|
1840
|
+
A.Button,
|
|
1841
|
+
{
|
|
1842
|
+
variant: "outline",
|
|
1843
|
+
size: "sm",
|
|
1844
|
+
icon: A.IconChevronDownOutline14 === undefined ? null : react.createElement(A.IconChevronDownOutline14),
|
|
1845
|
+
disabled: busy || position === -1 || position >= assistants.length - 1,
|
|
1846
|
+
onClick: () => move("down"),
|
|
1847
|
+
},
|
|
1848
|
+
t("btn.moveDown"),
|
|
1849
|
+
),
|
|
1850
|
+
react.createElement(
|
|
1851
|
+
A.Button,
|
|
1852
|
+
{
|
|
1853
|
+
variant: "outline",
|
|
1854
|
+
size: "sm",
|
|
1855
|
+
icon: A.IconCopyOutline16 === undefined ? null : react.createElement(A.IconCopyOutline16),
|
|
1856
|
+
disabled: busy,
|
|
1857
|
+
onClick: duplicate,
|
|
1858
|
+
},
|
|
1859
|
+
t("btn.duplicate"),
|
|
1860
|
+
),
|
|
1861
|
+
react.createElement(
|
|
1862
|
+
A.Button,
|
|
1863
|
+
{
|
|
1864
|
+
variant: "ghost",
|
|
1865
|
+
size: "sm",
|
|
1866
|
+
className: "cpfe-danger",
|
|
1867
|
+
icon: A.IconTrashOutline16 === undefined ? null : react.createElement(A.IconTrashOutline16),
|
|
1868
|
+
disabled: busy,
|
|
1869
|
+
onClick: askDelete,
|
|
1870
|
+
},
|
|
1871
|
+
t("btn.delete"),
|
|
1872
|
+
),
|
|
1873
|
+
)
|
|
1874
|
+
: null,
|
|
1875
|
+
typeof (assistants.find((item) => item.id === selected) || {}).broken === "string"
|
|
1876
|
+
? react.createElement("p", { className: "cpfe-note" }, t("assistant.brokenHint"))
|
|
1877
|
+
: null,
|
|
1729
1878
|
react.createElement("p", { className: "cpfe-note" }, t("msg.readOnlyHint")),
|
|
1730
1879
|
)
|
|
1731
1880
|
|
|
@@ -1758,55 +1907,6 @@ try {
|
|
|
1758
1907
|
"aria-label": t("name.descriptionPlaceholder"),
|
|
1759
1908
|
onChange: (event) => update({ description: event.target.value }),
|
|
1760
1909
|
}),
|
|
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
1910
|
),
|
|
1811
1911
|
react.createElement(
|
|
1812
1912
|
"section",
|
|
@@ -1823,7 +1923,13 @@ try {
|
|
|
1823
1923
|
payload.modes.map((mode) =>
|
|
1824
1924
|
react.createElement(
|
|
1825
1925
|
A.Pill,
|
|
1826
|
-
{
|
|
1926
|
+
{
|
|
1927
|
+
key: mode.id,
|
|
1928
|
+
active: draft.mode === mode.id,
|
|
1929
|
+
disabled: busy,
|
|
1930
|
+
"aria-pressed": draft.mode === mode.id,
|
|
1931
|
+
onClick: () => pickMode(mode.id),
|
|
1932
|
+
},
|
|
1827
1933
|
t("base." + mode.id + ".label", mode.label),
|
|
1828
1934
|
),
|
|
1829
1935
|
),
|
|
@@ -1851,15 +1957,30 @@ try {
|
|
|
1851
1957
|
react.createElement(SectionHint, { text: t("rows.hint") }),
|
|
1852
1958
|
draft.baseUnavailable !== null
|
|
1853
1959
|
? react.createElement("p", { className: "cpfe-note" }, t("rows.unavailable"))
|
|
1854
|
-
: react.createElement(
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1960
|
+
: react.createElement(
|
|
1961
|
+
"div",
|
|
1962
|
+
null,
|
|
1963
|
+
react.createElement("input", {
|
|
1964
|
+
className: "cpfe-filter",
|
|
1965
|
+
value: rowQuery,
|
|
1966
|
+
placeholder: t("rows.filterPlaceholder"),
|
|
1967
|
+
"aria-label": t("rows.filterPlaceholder"),
|
|
1968
|
+
onChange: (event) => setRowQuery(event.target.value),
|
|
1969
|
+
}),
|
|
1970
|
+
react.createElement(RowList, {
|
|
1971
|
+
expanded: expandedRows,
|
|
1972
|
+
onToggleExpand: toggleRowExpanded,
|
|
1973
|
+
rows: filterRows(payload.rows, rowQuery, t),
|
|
1974
|
+
overrides: draft.overrides,
|
|
1975
|
+
onToggle: (id, next) => {
|
|
1976
|
+
if (draft.mode !== savedMode) return
|
|
1977
|
+
update({ overrides: { ...draft.overrides, [id]: next } })
|
|
1978
|
+
},
|
|
1979
|
+
depth: 0,
|
|
1980
|
+
t: t,
|
|
1981
|
+
disabled: busy === true || draft.mode !== savedMode,
|
|
1982
|
+
}),
|
|
1983
|
+
),
|
|
1863
1984
|
),
|
|
1864
1985
|
react.createElement(
|
|
1865
1986
|
"section",
|
|
@@ -2076,10 +2197,10 @@ try {
|
|
|
2076
2197
|
),
|
|
2077
2198
|
),
|
|
2078
2199
|
),
|
|
2079
|
-
...editorSections,
|
|
2200
|
+
...(editorReady ? editorSections.filter((section) => section.key === "name" || section.key === "prompt") : []),
|
|
2080
2201
|
react.createElement(
|
|
2081
2202
|
"div",
|
|
2082
|
-
{ className: "cpfe-bar" },
|
|
2203
|
+
{ className: "cpfe-bar cpfe-savebar" },
|
|
2083
2204
|
react.createElement(
|
|
2084
2205
|
A.Button,
|
|
2085
2206
|
{
|
|
@@ -2095,11 +2216,14 @@ try {
|
|
|
2095
2216
|
{
|
|
2096
2217
|
variant: "outline",
|
|
2097
2218
|
icon: A.IconRefreshOutline16 === undefined ? null : react.createElement(A.IconRefreshOutline16),
|
|
2098
|
-
disabled: busy
|
|
2219
|
+
disabled: busy,
|
|
2099
2220
|
// Explicitly destructive of the local draft, so it says so while there is
|
|
2100
2221
|
// one: switching assistants never discards edits, and this button is the
|
|
2101
2222
|
// one place that does.
|
|
2102
|
-
onClick: () =>
|
|
2223
|
+
onClick: () => {
|
|
2224
|
+
if (dirty && typeof window !== "undefined" && window.confirm(t("btn.reloadConfirm")) !== true) return
|
|
2225
|
+
reload(selected, { discard: true })
|
|
2226
|
+
},
|
|
2103
2227
|
},
|
|
2104
2228
|
dirty ? t("btn.reloadDiscard") : t("btn.reload"),
|
|
2105
2229
|
),
|
|
@@ -2119,13 +2243,32 @@ try {
|
|
|
2119
2243
|
react.createElement("span", { className: "cpfe-path" }, editorReady ? payload.compositionPath : ""),
|
|
2120
2244
|
),
|
|
2121
2245
|
),
|
|
2246
|
+
editorReady
|
|
2247
|
+
? react.createElement(
|
|
2248
|
+
"section",
|
|
2249
|
+
null,
|
|
2250
|
+
react.createElement(
|
|
2251
|
+
"button",
|
|
2252
|
+
{
|
|
2253
|
+
type: "button",
|
|
2254
|
+
"data-cap": "toggle",
|
|
2255
|
+
className: "cpfe-h",
|
|
2256
|
+
"aria-expanded": capOpen,
|
|
2257
|
+
onClick: () => setCapOpen((open) => open !== true),
|
|
2258
|
+
},
|
|
2259
|
+
(capOpen ? t("cap.collapse") : t("cap.expand")) + " · " + t("cap.heading"),
|
|
2260
|
+
),
|
|
2261
|
+
react.createElement(SectionHint, { text: t("cap.hint") }),
|
|
2262
|
+
capOpen ? editorSections.filter((section) => section.key === "mode" || section.key === "rows") : null,
|
|
2263
|
+
)
|
|
2264
|
+
: null,
|
|
2122
2265
|
// The confirmation is a portal: rendering it here keeps every piece of this page's
|
|
2123
2266
|
// state in one component.
|
|
2124
|
-
|
|
2267
|
+
renderable(A.RiskConfirmation) && editorReady
|
|
2125
2268
|
? react.createElement(A.RiskConfirmation, {
|
|
2126
2269
|
open: deleteOpen,
|
|
2127
|
-
title: t("delete.title"),
|
|
2128
|
-
description: fillPlaceholders(t("delete.description"), { id: draft.id }),
|
|
2270
|
+
title: fillPlaceholders(t("delete.title"), { name: draft.name || draft.id }),
|
|
2271
|
+
description: fillPlaceholders(t("delete.description"), { id: draft.id, name: draft.name || draft.id }),
|
|
2129
2272
|
acknowledgeLabel: t("delete.acknowledge"),
|
|
2130
2273
|
cancelLabel: t("btn.cancel"),
|
|
2131
2274
|
closeLabel: t("delete.close"),
|