dsh-prompt-customizer 0.4.2 → 0.5.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.md +41 -35
- package/client/client.js +1655 -781
- package/client/client.js.map +1 -1
- package/lib/effective.js +2 -2
- package/lib/index.js +139 -100
- package/lib/promotion.js +58 -62
- package/lib/sectionOps.d.mts +35 -0
- package/lib/sectionOps.mjs +140 -0
- package/lib/zh/cordis.mjs +110 -0
- package/lib/zh/core.mjs +47 -0
- package/lib/zh/index.d.mts +8 -0
- package/lib/zh/index.mjs +23 -0
- package/lib/zh/sdk.mjs +22 -0
- package/lib/zh/teams.mjs +19 -0
- package/lib/zh/undo.mjs +18 -0
- package/package.json +9 -10
- package/src/client/Panel.tsx +532 -0
- package/src/client/{PresetsTab.tsx → PresetsPane.tsx} +146 -129
- package/src/client/PreviewPane.tsx +73 -0
- package/src/client/SectionsPane.tsx +331 -0
- package/src/client/ToolsPane.tsx +230 -0
- package/src/client/error-boundary.tsx +48 -0
- package/src/client/index.ts +69 -406
- package/src/client/locales.ts +44 -8
- package/src/client/nav.tsx +284 -0
- package/src/client/shell.tsx +179 -0
- package/src/client/styles.ts +24 -14
- package/src/client/types.ts +1 -4
- package/src/client/PreviewTab.tsx +0 -92
- package/src/client/SectionsTab.tsx +0 -539
- package/src/client/ToolsTab.tsx +0 -293
package/client/client.js
CHANGED
|
@@ -5,6 +5,9 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
+
let react_dom_client = require("react-dom/client");
|
|
9
|
+
let react_dom = require("react-dom");
|
|
10
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
11
|
//#region src/client/locales.ts
|
|
9
12
|
/** Locale dictionaries for the prompt-customizer panel. */
|
|
10
13
|
const DICT = {
|
|
@@ -66,7 +69,7 @@ window.__ModuleLoader__.load({
|
|
|
66
69
|
targetGlobal: "目标:全局默认",
|
|
67
70
|
targetHint: "选择编辑目标 —— 选中某个 agent 预设后,改动只对该预设生效(字段级覆盖)。",
|
|
68
71
|
syncAllPhases: "三态同步",
|
|
69
|
-
syncAllPhasesHint: "勾选后,屏蔽 /
|
|
72
|
+
syncAllPhasesHint: "勾选后,屏蔽 / 解除屏蔽、替换文本(提示词)与拖入 / 拖出(工具的加减)对三个阶段一起生效,只作用于同名的那一项;某阶段没有的名字保持原样。默认关闭:三个阶段需逐一处理。",
|
|
70
73
|
broken: "损坏",
|
|
71
74
|
brokenPreset: "该 agent 预设当前无法挂载,仍可编辑其定制(修复预设后自动生效)。",
|
|
72
75
|
phaseBootstrap: "仅引导阶段",
|
|
@@ -96,6 +99,8 @@ window.__ModuleLoader__.load({
|
|
|
96
99
|
toolCopied: "已把「{name}」复制到{to}:{from}那份保持不动,隐藏/启用状态一起带过去。",
|
|
97
100
|
toolShown: "「{name}」已在{to}显示。",
|
|
98
101
|
toolHiddenIn: "「{name}」已在{to}隐藏。",
|
|
102
|
+
toolSyncShown: "「{name}」已在三个阶段显示(该阶段目录里没有的已写进各自的加回名单)。",
|
|
103
|
+
toolSyncHidden: "「{name}」已在三个阶段隐藏 / 移除。",
|
|
99
104
|
sectionsTakenOver: "「{name}」段声明了 complete 整段接管(宿主在装配瀑布流之后强制还原为那一条段)。本插件默认的 forceSections 已绕开该机制,段级屏蔽 / 替换 / 注入 / 排序照常生效;此提示只会在 forceSections 关闭或强制覆盖失效时出现,开启 forceSections 即可恢复。",
|
|
100
105
|
sectionsLost: "本插件在该阶段产出的 {emitted} 个段里,只有 {survived} 个进入最终提示词:其余被下游装配规则丢弃,段级定制不会完全生效(工具过滤不受影响)。",
|
|
101
106
|
sectionBlockedStrip: "(本阶段已屏蔽,模型不可见)",
|
|
@@ -115,6 +120,11 @@ window.__ModuleLoader__.load({
|
|
|
115
120
|
forceHint: "开启后所有预设的提示词段一律以本插件为准(绕过 complete 整段接管与预设阶段裁段);关闭后退回瀑布流内过滤,预设可能压过定制。「恢复初始状态」会自动关闭它。",
|
|
116
121
|
forceOn: "已开启",
|
|
117
122
|
forceOff: "已关闭",
|
|
123
|
+
zhSwitch: "中文提示词",
|
|
124
|
+
zhHint: "开关。开启:把名字对得上译本的段一次性覆盖为中文(写入编辑草稿,保存后生效);关闭:清除这些段的替换文本,回归英文原文。自定义段与对不上名字的段不受影响。",
|
|
125
|
+
zhApplied: "已把中文写入编辑草稿:点「保存」生效;对不上名字的段保持原样。",
|
|
126
|
+
zhReverted: "已把英文原文写回编辑草稿(清除中文替换),点「保存」生效。",
|
|
127
|
+
zhNotReady: "三阶段装配尚未加载完成,请先点「刷新」或稍后再试。",
|
|
118
128
|
envBlockTitle: "环境变量黑名单(envBlocklist)",
|
|
119
129
|
envBlockHint: "系统环境变量(process.env)已全量注册为提示词变量:段文本里写 {{env_键名小写}} 引用(如 {{env_path}});命中黑名单的键不注册——env 里常有密钥,进了提示词就会随请求发给模型。条目支持 * 通配、大小写不敏感。内置变量 {{date}} {{time}} {{datetime}} {{weekday}} {{hostname}} {{platform}} {{arch}} {{username}} {{home}} {{shell}} {{locale}} {{node_version}} 恒可用。",
|
|
120
130
|
envBlockAdd: "键名或通配,如 GITHUB_TOKEN 或 *_KEY*",
|
|
@@ -130,12 +140,22 @@ window.__ModuleLoader__.load({
|
|
|
130
140
|
resetOk: "已恢复初始状态:所有 AI 对话不再受本插件影响",
|
|
131
141
|
resetFail: "恢复初始状态失败",
|
|
132
142
|
scopeFallback: "该 agent 预设的常驻 scope 未能挂载,以下显示的是全局层内容(并非该预设的原生段与工具)。",
|
|
133
|
-
previewDegraded: "伪会话触发了某个预设插件的异常,本次预览已降级为无会话装配——预设原生的阶段裁剪规则未参与运行。",
|
|
134
143
|
previewFail: "该阶段装配失败(scope 挂载失败或预设插件异常),请切换目标或查看服务日志。",
|
|
135
144
|
phaseVisible: "阶段可见",
|
|
136
145
|
previewToolsHint: "模型实际可见的工具目录(按所选阶段运行预设的全部装配规则,含原生阶段裁剪):",
|
|
137
146
|
previewToolsCount: "注册表",
|
|
138
|
-
saveAsPreset: "存为预设"
|
|
147
|
+
saveAsPreset: "存为预设",
|
|
148
|
+
navShort: "提示词",
|
|
149
|
+
close: "关闭",
|
|
150
|
+
filterAll: "全部",
|
|
151
|
+
filterOn: "已启用",
|
|
152
|
+
filterOff: "已停用",
|
|
153
|
+
targetAllTab: "全部 Agent",
|
|
154
|
+
poolAddTitle: "加入{phase}",
|
|
155
|
+
settingsTitle: "全局设置",
|
|
156
|
+
libraryTitle: "配置快照库",
|
|
157
|
+
draftBadge: "预览含未保存草稿",
|
|
158
|
+
previewSyncFail: "实时预览同步失败(下次编辑会自动重试,或点「刷新」)"
|
|
139
159
|
},
|
|
140
160
|
en: {
|
|
141
161
|
nav: "Prompt Customizer",
|
|
@@ -195,7 +215,7 @@ window.__ModuleLoader__.load({
|
|
|
195
215
|
targetGlobal: "Target: global default",
|
|
196
216
|
targetHint: "Pick an edit target — changes apply only to the chosen agent preset (field-level override).",
|
|
197
217
|
syncAllPhases: "Sync phases",
|
|
198
|
-
syncAllPhasesHint: "When checked, blocking / unblocking
|
|
218
|
+
syncAllPhasesHint: "When checked, blocking / unblocking, text replacement (sections), and drag-in / drag-out (adding / removing tools) apply to the same-named item across all three phases at once; names a phase does not have are left untouched. Unchecked (default): handle each phase separately.",
|
|
199
219
|
broken: "broken",
|
|
200
220
|
brokenPreset: "This agent preset cannot mount right now; its customization is still editable and applies once fixed.",
|
|
201
221
|
phaseBootstrap: "Bootstrap only",
|
|
@@ -225,6 +245,8 @@ window.__ModuleLoader__.load({
|
|
|
225
245
|
toolCopied: "Copied \"{name}\" to {to}: the copy in {from} is left untouched, and its hidden/enabled state carries over.",
|
|
226
246
|
toolShown: "\"{name}\" is now visible in {to}.",
|
|
227
247
|
toolHiddenIn: "\"{name}\" is now hidden in {to}.",
|
|
248
|
+
toolSyncShown: "\"{name}\" is now visible in all three phases (periods without it in their catalog got it added back).",
|
|
249
|
+
toolSyncHidden: "\"{name}\" is now hidden / removed in all three phases.",
|
|
228
250
|
sectionsTakenOver: "The \"{name}\" section declares a complete takeover (the host restores that single section after the assembly waterfall). This plugin’s forceSections (default on) bypasses that mechanism, so section-level blocking / replacement / injection / ordering still applies; this notice only appears when forceSections is off or the override fails — enable it to restore.",
|
|
229
251
|
sectionsLost: "Only {survived} of the {emitted} sections this plugin emitted for this phase reach the final prompt — the rest are dropped by downstream assembly rules, so section-level customization cannot fully apply (tool filtering is unaffected).",
|
|
230
252
|
sectionBlockedStrip: " (blocked in this period; invisible to the model)",
|
|
@@ -244,6 +266,11 @@ window.__ModuleLoader__.load({
|
|
|
244
266
|
forceHint: "When on, every preset’s prompt sections always follow this plugin (bypassing complete takeover and preset phase pruning); when off, filtering falls back to the waterfall, where presets may override your customization. \"Restore initial state\" turns this off automatically.",
|
|
245
267
|
forceOn: "On",
|
|
246
268
|
forceOff: "Off",
|
|
269
|
+
zhSwitch: "Chinese sections",
|
|
270
|
+
zhHint: "Toggle. On: overwrite sections matching the bundled translation with Chinese (written to the edit draft, applies on save). Off: clear those replacements, restoring the English originals. Custom sections and unmatched names are untouched.",
|
|
271
|
+
zhApplied: "Chinese written to the edit draft — press Save to apply; unmatched sections stay as-is.",
|
|
272
|
+
zhReverted: "English originals written back to the edit draft (Chinese replacements cleared) — press Save to apply.",
|
|
273
|
+
zhNotReady: "Phase assemblies are not loaded yet — press Refresh or try again shortly.",
|
|
247
274
|
envBlockTitle: "Environment variable blocklist (envBlocklist)",
|
|
248
275
|
envBlockHint: "All system environment variables (process.env) are registered as prompt variables: reference them in section text as {{env_lowercase_key}} (e.g. {{env_path}}); keys hit by the blocklist are not registered — env often carries secrets, and anything in the prompt is sent to the model. Entries support * wildcards, case-insensitive. Built-ins {{date}} {{time}} {{datetime}} {{weekday}} {{hostname}} {{platform}} {{arch}} {{username}} {{home}} {{shell}} {{locale}} {{node_version}} are always available.",
|
|
249
276
|
envBlockAdd: "Key or wildcard, e.g. GITHUB_TOKEN or *_KEY*",
|
|
@@ -259,12 +286,22 @@ window.__ModuleLoader__.load({
|
|
|
259
286
|
resetOk: "Initial state restored: no AI conversation is affected by this plugin anymore",
|
|
260
287
|
resetFail: "Failed to restore initial state",
|
|
261
288
|
scopeFallback: "This agent preset's standing scope could not be mounted; what you see is the global layer (not the preset's native sections/tools).",
|
|
262
|
-
previewDegraded: "A preset plugin threw on the synthetic session; this preview fell back to an agent-less assembly — native phase rules did not run.",
|
|
263
289
|
previewFail: "Assembly for this phase failed (scope mount error or a preset plugin threw); switch target or check the server log.",
|
|
264
290
|
phaseVisible: "visible in phase",
|
|
265
291
|
previewToolsHint: "Tool catalog as the model actually sees it (all assembly rules run for the selected phase, including native phase narrowing):",
|
|
266
292
|
previewToolsCount: "of registry",
|
|
267
|
-
saveAsPreset: "Save as preset"
|
|
293
|
+
saveAsPreset: "Save as preset",
|
|
294
|
+
navShort: "Prompts",
|
|
295
|
+
close: "Close",
|
|
296
|
+
filterAll: "All",
|
|
297
|
+
filterOn: "Enabled",
|
|
298
|
+
filterOff: "Disabled",
|
|
299
|
+
targetAllTab: "All agents",
|
|
300
|
+
poolAddTitle: "Add to {phase}",
|
|
301
|
+
settingsTitle: "Global settings",
|
|
302
|
+
libraryTitle: "Config snapshots",
|
|
303
|
+
draftBadge: "Preview includes unsaved draft",
|
|
304
|
+
previewSyncFail: "Live preview sync failed (retries on next edit, or press Refresh)"
|
|
268
305
|
}
|
|
269
306
|
};
|
|
270
307
|
//#endregion
|
|
@@ -276,42 +313,169 @@ window.__ModuleLoader__.load({
|
|
|
276
313
|
alignItems: "center"
|
|
277
314
|
};
|
|
278
315
|
return {
|
|
279
|
-
|
|
316
|
+
pRoot: {
|
|
317
|
+
height: "100%",
|
|
318
|
+
display: "flex",
|
|
319
|
+
flexDirection: "column",
|
|
280
320
|
fontFamily: "inherit",
|
|
281
321
|
fontSize: 13,
|
|
282
|
-
lineHeight: 1.5
|
|
322
|
+
lineHeight: 1.5,
|
|
323
|
+
minHeight: 0
|
|
283
324
|
},
|
|
284
|
-
|
|
325
|
+
head: {
|
|
285
326
|
...flex,
|
|
286
|
-
gap:
|
|
287
|
-
|
|
288
|
-
flexWrap: "wrap"
|
|
327
|
+
gap: 10,
|
|
328
|
+
padding: "10px 14px 8px",
|
|
329
|
+
flexWrap: "wrap",
|
|
330
|
+
borderBottom: "1px solid rgba(128,128,128,.18)"
|
|
289
331
|
},
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
332
|
+
headTitle: {
|
|
333
|
+
...flex,
|
|
334
|
+
gap: 8,
|
|
335
|
+
fontSize: 15,
|
|
336
|
+
fontWeight: 600,
|
|
337
|
+
whiteSpace: "nowrap"
|
|
338
|
+
},
|
|
339
|
+
seg: {
|
|
340
|
+
...flex,
|
|
341
|
+
gap: 2,
|
|
342
|
+
padding: 2,
|
|
343
|
+
border: "1px solid rgba(128,128,128,.28)",
|
|
344
|
+
borderRadius: 8
|
|
345
|
+
},
|
|
346
|
+
segBtn: {
|
|
347
|
+
padding: "3px 12px",
|
|
348
|
+
border: "none",
|
|
293
349
|
borderRadius: 6,
|
|
294
350
|
background: "transparent",
|
|
295
351
|
cursor: "pointer",
|
|
296
352
|
color: "inherit",
|
|
297
|
-
opacity: .
|
|
353
|
+
opacity: .72,
|
|
354
|
+
fontSize: 13,
|
|
355
|
+
whiteSpace: "nowrap"
|
|
298
356
|
},
|
|
299
|
-
|
|
300
|
-
padding: "
|
|
301
|
-
border: "
|
|
357
|
+
segBtnActive: {
|
|
358
|
+
padding: "3px 12px",
|
|
359
|
+
border: "none",
|
|
302
360
|
borderRadius: 6,
|
|
303
|
-
background: "rgba(
|
|
361
|
+
background: "rgba(88,166,255,.22)",
|
|
304
362
|
cursor: "pointer",
|
|
305
|
-
color: "inherit"
|
|
363
|
+
color: "inherit",
|
|
364
|
+
fontWeight: 600,
|
|
365
|
+
fontSize: 13,
|
|
366
|
+
whiteSpace: "nowrap"
|
|
306
367
|
},
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
368
|
+
grow: {
|
|
369
|
+
flex: 1,
|
|
370
|
+
minWidth: 0
|
|
371
|
+
},
|
|
372
|
+
headActions: {
|
|
373
|
+
...flex,
|
|
374
|
+
gap: 6,
|
|
375
|
+
marginLeft: "auto"
|
|
376
|
+
},
|
|
377
|
+
iconBtn: {
|
|
378
|
+
display: "inline-flex",
|
|
379
|
+
alignItems: "center",
|
|
380
|
+
justifyContent: "center",
|
|
381
|
+
width: 28,
|
|
382
|
+
height: 28,
|
|
383
|
+
border: "none",
|
|
384
|
+
borderRadius: 8,
|
|
312
385
|
background: "transparent",
|
|
313
386
|
cursor: "pointer",
|
|
314
|
-
color: "inherit"
|
|
387
|
+
color: "inherit",
|
|
388
|
+
opacity: .75
|
|
389
|
+
},
|
|
390
|
+
subhead: {
|
|
391
|
+
...flex,
|
|
392
|
+
gap: 8,
|
|
393
|
+
padding: "8px 14px",
|
|
394
|
+
flexWrap: "wrap",
|
|
395
|
+
borderBottom: "1px solid rgba(128,128,128,.14)"
|
|
396
|
+
},
|
|
397
|
+
targetRow: {
|
|
398
|
+
...flex,
|
|
399
|
+
gap: 4,
|
|
400
|
+
overflowX: "auto",
|
|
401
|
+
maxWidth: "100%",
|
|
402
|
+
scrollbarWidth: "thin"
|
|
403
|
+
},
|
|
404
|
+
targetTab: {
|
|
405
|
+
...flex,
|
|
406
|
+
gap: 6,
|
|
407
|
+
padding: "4px 12px",
|
|
408
|
+
border: "1px solid rgba(128,128,128,.28)",
|
|
409
|
+
borderRadius: 999,
|
|
410
|
+
background: "transparent",
|
|
411
|
+
cursor: "pointer",
|
|
412
|
+
color: "inherit",
|
|
413
|
+
opacity: .78,
|
|
414
|
+
fontSize: 12,
|
|
415
|
+
whiteSpace: "nowrap"
|
|
416
|
+
},
|
|
417
|
+
targetTabActive: {
|
|
418
|
+
...flex,
|
|
419
|
+
gap: 6,
|
|
420
|
+
padding: "4px 12px",
|
|
421
|
+
border: "1px solid rgba(88,166,255,.65)",
|
|
422
|
+
borderRadius: 999,
|
|
423
|
+
background: "rgba(88,166,255,.18)",
|
|
424
|
+
cursor: "pointer",
|
|
425
|
+
color: "inherit",
|
|
426
|
+
fontWeight: 600,
|
|
427
|
+
fontSize: 12,
|
|
428
|
+
whiteSpace: "nowrap"
|
|
429
|
+
},
|
|
430
|
+
body: {
|
|
431
|
+
flex: 1,
|
|
432
|
+
minHeight: 0,
|
|
433
|
+
display: "flex",
|
|
434
|
+
alignItems: "stretch"
|
|
435
|
+
},
|
|
436
|
+
colLeft: {
|
|
437
|
+
width: 420,
|
|
438
|
+
flex: "none",
|
|
439
|
+
display: "flex",
|
|
440
|
+
flexDirection: "column",
|
|
441
|
+
minHeight: 0,
|
|
442
|
+
borderRight: "1px solid rgba(128,128,128,.18)",
|
|
443
|
+
boxSizing: "border-box"
|
|
444
|
+
},
|
|
445
|
+
colScroll: {
|
|
446
|
+
flex: 1,
|
|
447
|
+
minHeight: 0,
|
|
448
|
+
overflowY: "auto",
|
|
449
|
+
padding: "10px 12px"
|
|
450
|
+
},
|
|
451
|
+
colFoot: {
|
|
452
|
+
flex: "none",
|
|
453
|
+
...flex,
|
|
454
|
+
gap: 8,
|
|
455
|
+
padding: "8px 12px",
|
|
456
|
+
borderTop: "1px solid rgba(128,128,128,.18)",
|
|
457
|
+
flexWrap: "wrap"
|
|
458
|
+
},
|
|
459
|
+
colRight: {
|
|
460
|
+
flex: 1,
|
|
461
|
+
minWidth: 0,
|
|
462
|
+
display: "flex",
|
|
463
|
+
flexDirection: "column",
|
|
464
|
+
minHeight: 0
|
|
465
|
+
},
|
|
466
|
+
colRightScroll: {
|
|
467
|
+
flex: 1,
|
|
468
|
+
minHeight: 0,
|
|
469
|
+
overflowY: "auto",
|
|
470
|
+
padding: "10px 14px"
|
|
471
|
+
},
|
|
472
|
+
groupHead: {
|
|
473
|
+
...flex,
|
|
474
|
+
gap: 8,
|
|
475
|
+
marginBottom: 6,
|
|
476
|
+
color: "rgba(128,128,128,.95)",
|
|
477
|
+
fontSize: 12,
|
|
478
|
+
fontWeight: 600
|
|
315
479
|
},
|
|
316
480
|
list: {
|
|
317
481
|
display: "flex",
|
|
@@ -363,7 +527,8 @@ window.__ModuleLoader__.load({
|
|
|
363
527
|
lineHeight: 1.6,
|
|
364
528
|
whiteSpace: "pre-wrap",
|
|
365
529
|
wordBreak: "break-word",
|
|
366
|
-
|
|
530
|
+
flex: 1,
|
|
531
|
+
minHeight: 0,
|
|
367
532
|
overflowY: "auto",
|
|
368
533
|
padding: 10,
|
|
369
534
|
border: "1px solid rgba(128,128,128,.2)",
|
|
@@ -445,16 +610,7 @@ window.__ModuleLoader__.load({
|
|
|
445
610
|
lineHeight: 1.4,
|
|
446
611
|
opacity: .8
|
|
447
612
|
},
|
|
448
|
-
|
|
449
|
-
rowDropAbove: { boxShadow: "0 -2px 0 0 #58a6ff" },
|
|
450
|
-
rowDropBelow: { boxShadow: "0 2px 0 0 #58a6ff" },
|
|
451
|
-
dragHandle: {
|
|
452
|
-
cursor: "grab",
|
|
453
|
-
color: "rgba(128,128,128,.7)",
|
|
454
|
-
fontSize: 13,
|
|
455
|
-
padding: "0 2px",
|
|
456
|
-
userSelect: "none"
|
|
457
|
-
},
|
|
613
|
+
rowBlocked: { opacity: .55 },
|
|
458
614
|
injectBox: {
|
|
459
615
|
marginTop: 10,
|
|
460
616
|
padding: "8px",
|
|
@@ -493,10 +649,6 @@ window.__ModuleLoader__.load({
|
|
|
493
649
|
background: "transparent",
|
|
494
650
|
color: "inherit"
|
|
495
651
|
},
|
|
496
|
-
option: {
|
|
497
|
-
backgroundColor: "Canvas",
|
|
498
|
-
color: "CanvasText"
|
|
499
|
-
},
|
|
500
652
|
muted: {
|
|
501
653
|
color: "rgba(128,128,128,.75)",
|
|
502
654
|
fontSize: 12
|
|
@@ -516,15 +668,6 @@ window.__ModuleLoader__.load({
|
|
|
516
668
|
fontSize: 12,
|
|
517
669
|
marginBottom: 6
|
|
518
670
|
},
|
|
519
|
-
groupTitle: {
|
|
520
|
-
display: "flex",
|
|
521
|
-
gap: 8,
|
|
522
|
-
alignItems: "baseline",
|
|
523
|
-
marginTop: 8,
|
|
524
|
-
marginBottom: 2,
|
|
525
|
-
color: "rgba(128,128,128,.9)",
|
|
526
|
-
fontSize: 12
|
|
527
|
-
},
|
|
528
671
|
saveBtn: {
|
|
529
672
|
padding: "4px 12px",
|
|
530
673
|
border: "1px solid rgba(128,128,128,.35)",
|
|
@@ -827,10 +970,6 @@ window.__ModuleLoader__.load({
|
|
|
827
970
|
add
|
|
828
971
|
};
|
|
829
972
|
}
|
|
830
|
-
/** 某个工具在当前黑名单配置下是否被隐藏。 */
|
|
831
|
-
function isToolHidden(name, toolsCfg) {
|
|
832
|
-
return (toolsCfg?.exclude ?? []).includes(name);
|
|
833
|
-
}
|
|
834
973
|
/**
|
|
835
974
|
* 编辑目标的「显示视图」:全局目标返回原配置;agent 预设目标把该预设
|
|
836
975
|
* override 的字段叠在全局值之上展示(空缺字段显示全局继承值)。`presets`
|
|
@@ -984,21 +1123,6 @@ window.__ModuleLoader__.load({
|
|
|
984
1123
|
return { [field]: cur };
|
|
985
1124
|
}
|
|
986
1125
|
/**
|
|
987
|
-
* 重排/插入一个阶段的有序行列表:把 dragName 从列表中移除后,插入到
|
|
988
|
-
* targetName 行之上(pos='above')或之下(pos='below');返回新列表。
|
|
989
|
-
* 拖入的新段由调用方以 newRow 提供,插入后保留在列表中(其余行保持原样)。
|
|
990
|
-
*/
|
|
991
|
-
function reorderInsert(rows, dragName, targetName, pos, newRow = null) {
|
|
992
|
-
const base = rows.filter((row) => row.name !== dragName);
|
|
993
|
-
const idx = base.findIndex((row) => row.name === targetName);
|
|
994
|
-
if (idx < 0) return null;
|
|
995
|
-
const insertAt = Math.max(0, Math.min(pos === "above" ? idx : idx + 1, base.length));
|
|
996
|
-
const entry = newRow ?? rows.find((row) => row.name === dragName);
|
|
997
|
-
if (!entry) return null;
|
|
998
|
-
base.splice(insertAt, 0, entry);
|
|
999
|
-
return base;
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
1126
|
* 持久化一个阶段的有序列表(v1 persistOrder 的逐阶段版):把 rows(有序行)
|
|
1003
1127
|
* 写成该阶段的注入条目,order = 连续整数(0,1,2,…)——「虚拟 order 决定注入
|
|
1004
1128
|
* 顺序」;文本优先级 = 本阶段的用户替换文本(row.override)→ 自定义段自带的
|
|
@@ -1018,146 +1142,391 @@ window.__ModuleLoader__.load({
|
|
|
1018
1142
|
}));
|
|
1019
1143
|
return [...others, ...entries];
|
|
1020
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* 三态同步持久化:rowsByKey 给出三个阶段各自的有序行集合(bootstrap /
|
|
1147
|
+
* active / compaction),生成完整 inject 列表 —— always 条目原样保留,三个
|
|
1148
|
+
* 阶段各自写成连续虚拟 order 的注入条目(条目形状与 phaseInjectEntries 完全
|
|
1149
|
+
* 一致)。用于「三态同步」的一次写入:edit 对 inject 是整体替换,逐阶段各写
|
|
1150
|
+
* 一次会互相覆盖只留下最后一次。
|
|
1151
|
+
*/
|
|
1152
|
+
function mergedPhaseInjectEntries(cfg, rowsByKey) {
|
|
1153
|
+
const always = (cfg.inject ?? []).filter((item) => (item.phase ?? "always") === "always");
|
|
1154
|
+
const entries = [
|
|
1155
|
+
"bootstrap",
|
|
1156
|
+
"active",
|
|
1157
|
+
"compaction"
|
|
1158
|
+
].flatMap((key) => {
|
|
1159
|
+
const phase = injectPhaseOf(key);
|
|
1160
|
+
return (rowsByKey[key] ?? []).map((row, i) => ({
|
|
1161
|
+
name: row.name,
|
|
1162
|
+
order: i,
|
|
1163
|
+
text: row.override || (row.custom ? row.text ?? "" : ""),
|
|
1164
|
+
phase,
|
|
1165
|
+
custom: row.custom
|
|
1166
|
+
}));
|
|
1167
|
+
});
|
|
1168
|
+
return [...always, ...entries];
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* 一个阶段部分的全部行(提示词 Tab 每行):行集合 = 「真实进入该阶段装配的段」
|
|
1172
|
+
*(post 视图,与预览一致 —— 预设原生阶段插件裁剪掉的段不显示)∪「被屏蔽的段」
|
|
1173
|
+
*(屏蔽名单含未保存草稿,随时可反选)∪「配置里属于该阶段的自定义注入段」。
|
|
1174
|
+
* 顺序以 base(预过滤)视图的骨架为底,再叠加「该阶段的注入 order」草稿序
|
|
1175
|
+
* —— 未保存的重排立即反映到界面。文本 post 优先(替换 / 注入结果),回退
|
|
1176
|
+
* base 原文与注入文本。身份只认注入条目的 custom 隐藏标记,绝不互相转换。
|
|
1177
|
+
* `view` = 三阶段预览装配里的对应阶段(可为 null = 视为空)。从 SectionsTab
|
|
1178
|
+
* 的 rowsOf 原样迁出 —— 纯函数,UI 与 node --test 单测共用同一份实现。
|
|
1179
|
+
*/
|
|
1180
|
+
function phaseRows(cfg, view, key) {
|
|
1181
|
+
const replace = cfg.replace ?? {};
|
|
1182
|
+
const denied = new Set(deniedNames(cfg, key));
|
|
1183
|
+
const postByName = new Map((view?.sections ?? []).map((sec) => [sec.name, sec]));
|
|
1184
|
+
const baseByName = new Map((view?.baseSections ?? []).map((sec) => [sec.name, sec]));
|
|
1185
|
+
const { phase, names: injectedHere, custom: customNames, text, order: phaseOrder } = injectedAt(cfg, key);
|
|
1186
|
+
const names = [];
|
|
1187
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1188
|
+
const push = (name) => {
|
|
1189
|
+
if (!seen.has(name)) {
|
|
1190
|
+
seen.add(name);
|
|
1191
|
+
names.push(name);
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
for (const sec of view?.baseSections ?? []) if (postByName.has(sec.name) || denied.has(sec.name) || sec.blocked === true) push(sec.name);
|
|
1195
|
+
for (const name of postByName.keys()) if (baseByName.has(name) || injectedHere.has(name)) push(name);
|
|
1196
|
+
for (const item of cfg.inject ?? []) {
|
|
1197
|
+
if (item === null || typeof item !== "object" || typeof item.name !== "string") continue;
|
|
1198
|
+
if ((item.phase ?? "always") !== phase) continue;
|
|
1199
|
+
if (item.custom === true || text.get(item.name) || baseByName.has(item.name) || postByName.has(item.name)) push(item.name);
|
|
1200
|
+
}
|
|
1201
|
+
for (const name of customNames) push(name);
|
|
1202
|
+
const rows = names.map((name) => {
|
|
1203
|
+
const override = text.get(name) ?? "";
|
|
1204
|
+
return {
|
|
1205
|
+
name,
|
|
1206
|
+
text: postByName.get(name)?.text ?? baseByName.get(name)?.text ?? "",
|
|
1207
|
+
replaced: override !== "" || Object.hasOwn(replace, name),
|
|
1208
|
+
custom: customNames.has(name),
|
|
1209
|
+
override,
|
|
1210
|
+
blocked: denied.has(name)
|
|
1211
|
+
};
|
|
1212
|
+
});
|
|
1213
|
+
if (phaseOrder.size > 0) {
|
|
1214
|
+
const fallback = new Map(rows.map((row, i) => [row.name, i]));
|
|
1215
|
+
rows.sort((a, b) => (phaseOrder.get(a.name) ?? fallback.get(a.name) ?? 0) - (phaseOrder.get(b.name) ?? fallback.get(b.name) ?? 0));
|
|
1216
|
+
}
|
|
1217
|
+
return rows;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* 「应用中文」(开关开启方向,等效手动编辑):把三个阶段各自的行里名字命中
|
|
1221
|
+
* zhMap 的**非自定义**行打上中文文本补丁,再经 mergedPhaseInjectEntries 生成
|
|
1222
|
+
* 完整 inject 列表(调用方写入编辑草稿,保存后生效)。zhMap 条目为字符串
|
|
1223
|
+
*(整段替换)或函数(入参该行原文,返回空值 = 放弃替换,如 tools:sdk 的围栏
|
|
1224
|
+
* 守卫);未命中的行与阶段原样保留,段数与注入不增不减。自定义段是用户自撰
|
|
1225
|
+
* 内容,绝不覆盖。`views` = 三阶段预览装配(键可为 null —— 该阶段行集视为空)。
|
|
1226
|
+
*/
|
|
1227
|
+
function zhMergedInjectEntries(cfg, views, zhMap) {
|
|
1228
|
+
const patched = {};
|
|
1229
|
+
for (const key of [
|
|
1230
|
+
"bootstrap",
|
|
1231
|
+
"active",
|
|
1232
|
+
"compaction"
|
|
1233
|
+
]) patched[key] = phaseRows(cfg, views?.[key] ?? null, key).map((row) => {
|
|
1234
|
+
if (row.custom) return row;
|
|
1235
|
+
const entry = zhMap?.[row.name];
|
|
1236
|
+
if (entry === void 0) return row;
|
|
1237
|
+
const zh = typeof entry === "function" ? entry(row.text) : entry;
|
|
1238
|
+
return zh ? {
|
|
1239
|
+
...row,
|
|
1240
|
+
override: zh,
|
|
1241
|
+
text: zh
|
|
1242
|
+
} : row;
|
|
1243
|
+
});
|
|
1244
|
+
return mergedPhaseInjectEntries(cfg, patched);
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* 「回归英文」(开关关闭方向):把三个阶段行里命中译本的非自定义行的替换
|
|
1248
|
+
* 文本清空 —— 系统段的注入条目回落为仅 order 覆盖,服务端恢复注册表英文
|
|
1249
|
+
* 原文。与 zhMergedInjectEntries 严格对称:开关独占这些段的文本所有权,
|
|
1250
|
+
* 关闭即整体回英文(用户在开启期间的手工微调一并清除)。自定义段不碰。
|
|
1251
|
+
*/
|
|
1252
|
+
function zhRevertInjectEntries(cfg, views, zhMap) {
|
|
1253
|
+
const patched = {};
|
|
1254
|
+
for (const key of [
|
|
1255
|
+
"bootstrap",
|
|
1256
|
+
"active",
|
|
1257
|
+
"compaction"
|
|
1258
|
+
]) patched[key] = phaseRows(cfg, views?.[key] ?? null, key).map((row) => {
|
|
1259
|
+
if (row.custom || row.override === "" || zhMap?.[row.name] === void 0) return row;
|
|
1260
|
+
return {
|
|
1261
|
+
...row,
|
|
1262
|
+
override: "",
|
|
1263
|
+
text: ""
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
return mergedPhaseInjectEntries(cfg, patched);
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* 开关状态探测:任一注入条目的文本与**字符串译本**逐字相等即视为已应用。
|
|
1270
|
+
* 只探测字符串译本(函数译本无法从译文反推),而应用时所有命中行都会写入、
|
|
1271
|
+
* 字符串译本必然存在 —— 足以作为探针。混合状态(用户手动还原了个别段)仍算
|
|
1272
|
+
* 开启,关闭方向会整体回归英文。
|
|
1273
|
+
*/
|
|
1274
|
+
function zhApplied(cfg, zhMap) {
|
|
1275
|
+
for (const item of cfg?.inject ?? []) {
|
|
1276
|
+
if (item === null || typeof item !== "object" || typeof item.name !== "string") continue;
|
|
1277
|
+
const zh = zhMap?.[item.name];
|
|
1278
|
+
if (typeof zh === "string" && zh !== "" && item.text === zh) return true;
|
|
1279
|
+
}
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
//#endregion
|
|
1283
|
+
//#region lib/zh/core.mjs
|
|
1284
|
+
/**
|
|
1285
|
+
* 中文段译本(专属定制)—— 小段集合。
|
|
1286
|
+
* 规则:`{{...}}` 提示词变量、工具名、参数名、路径、URL、代码一律原样保留,
|
|
1287
|
+
* 只译散文。plan:policy 是动态段(清单里拿不到稳定文本),不设译本条目,
|
|
1288
|
+
* 按「名字对不上就跳过」规则自然豁免。
|
|
1289
|
+
*/
|
|
1290
|
+
const CORE = {
|
|
1291
|
+
"harness:identity": `你是一个由 DeepSeek Harness 驱动的 AI 智能体。`,
|
|
1292
|
+
"harness:source": `DeepSeek Harness 的实现代码检出位于 C:\\nvm\\v22.22.0\\node_modules\\@deepseek-ai\\dsh\\。检出位置与当前工作目录是两个独立的值,可能不同;绝不要根据该路径推断工作目录。请用 pwd 确定当前工作目录。该检出只用于查看或扩展 DSH 本身。`,
|
|
1293
|
+
"app:web-surface": `你正在通过 DeepSeek Harness 的 Web 图形界面(http://127.0.0.1:3080)与用户交互。当用户提到「这个页面」「这个 GUI」「这个应用」而没有指名其他目标时,指的就是这个 GUI。浏览器不提供任何隐式的 DOM、路由或截图上下文。客户端插件的 HMR 接收器处于激活状态,但只有当 \`pnpm run dev:web\` 也在同一检出里运行以重建其包时,客户端插件的改动才能免刷新重载;在承诺自动更新之前,先确认该监视进程在运行。其他任何改动 —— apps/web 外壳与普通包 —— 都需要重新构建受影响的 Web 产物,并在页面刷新后用这个既有 URL 验证。另起一个服务器不会更新这个 GUI。apps/web 的 Vite 入口只构建外壳,不是独立应用,因为只有 dsh web 会注入 window.__DSH_BOOT__。除非用户要求,不要启动替代服务器;确有必要时,用受管后台作业运行,并核实其确切 URL。`,
|
|
1294
|
+
"deployment:persona": `你是一个由 {{model}} 模型驱动的编码智能体。你的工作目录是 {{cwd}}。`,
|
|
1295
|
+
"tool:read": `使用 read 工具(而不是 cat 之类的 shell 命令)查看文本文件。结果带有行号;对大文件可用 offset 与 limit 继续读取。`,
|
|
1296
|
+
"tool:write": `使用 write 工具新建文件或整体替换文件内容。已有文件会被覆盖,因此先 read 现有文件(默认的 fs-observation-policy 有此要求),并优先用 edit 做针对性修改。`,
|
|
1297
|
+
"tool:edit": `使用 edit 工具对现有的 UTF-8 文本文件做针对性修改。它把字面量 old_string 替换为 new_string;默认要求 old_string 恰好出现一次。若 old_string 出现多次,请提供更具体的 old_string,或将 replace_all 设为 true。先 read 文件(默认的 fs-observation-policy 有此要求),除非你在本会话中刚创建或刚编辑过它。`,
|
|
1298
|
+
"tool:glob": `使用 glob 工具(而不是 shell 的 find)按路径模式发现文件。不含 "/" 的模式在任意深度匹配基本名,因此 "*" 匹配的是整棵树里的每个文件,而不只是顶层。结果只含文件、绝不含目录,且包含隐藏与被忽略的文件:完全符合的结果按修改时间顺序返回;结果超量时,保留按修改时间排序的前缀。`,
|
|
1299
|
+
"tool:grep": `使用 grep 工具(而不是 shell 的 grep 或 rg)搜索文件内容。需要上下文时,用 read 读取匹配到的文件。`,
|
|
1300
|
+
"tool:pwsh": `非零退出码会以 \`[exit code: N]\` 标记呈现;继续之前先排查失败。在 Windows 上,被杀掉的进程以 \`[exit code: 1]\` 收尾且没有信号标记;把中断之后的裸退出码 1 视为进程被终止,而不是命令失败。`,
|
|
1301
|
+
"tool:jobs": `跟踪你启动的每一个后台作业 id。作业完成时你会在会话内收到通知 —— 不要忙轮询或空等某个作业;继续做独立的步骤,不要重复正在运行的作业的工作。给出最终答复之前,用 job_output 收集每一个仍然相关的作业(只在确实被它阻塞时才设 wait: true),并用 job_kill 终止已经不再要紧的作业。`,
|
|
1302
|
+
"tool:web_search": `使用 web_search 工具在网络上发现最新信息。必填的 queries 数组接受 1–4 条非空搜索查询;单次搜索用只含一项的数组。它返回一个可选的 answer 和一份来源 URL 列表。有来源摘要时尽量使用,并以 Markdown 链接的形式引用相关 URL。`,
|
|
1303
|
+
"tool:web_fetch": `使用 web_fetch 工具获取特定 HTTP(S) URL 的内容(例如来自 web_search 的某条结果)。它返回解码为文本的页面内容。使用其内容时,以 Markdown 链接的形式引用该 URL。`,
|
|
1304
|
+
"tool:goal": `goal 工具用于当前会话中的一个长期运行的完成目标。create_goal 可以从任何语言的直接人类请求中推断目标意图;常规的单轮工作不要创建目标。调用 update_goal 之前先调用 get_goal,并逐字复制它的 goal_id 与 revision。会话恢复或 fork 之后,活跃目标会被解除武装:当人类以任何措辞、任何语言要求继续或恢复时,用 update_goal 的 resume 动作重新武装它。只有目标真正达成才标记 complete。同一个阻塞条件至少连续 3 个目标轮次仍未解除,才可标记 blocked,并在 blocked_reason 里写明那个具体条件;困难、不确定或还有剩余工作都不算阻塞。`,
|
|
1305
|
+
"tool:workflow": `只有当用户明确要求 workflow 或大规模多智能体编排时,才使用 workflow 工具:你编写一个 JavaScript 脚本(工具描述里写明确切格式),把工作扇出到大量子智能体,带阶段与结构化结果。只有一两次委派时,优先用普通的 subagent 调用。`,
|
|
1306
|
+
"tool:ralph": `只有当直接人类明确要求 Ralph 循环或全新智能体迭代执行时,才使用 ralph 工具。每一轮 Ralph 都启动一个不带对话种子的全新子智能体,并把共享工作区当作持久记忆。完成与阻塞只是 worker 的报告,不是独立评估。普通的长目标用同会话的 goal 工具;有边界的委派与扇出用普通 subagent 或 workflow。`,
|
|
1307
|
+
"tool:subagent": `默认在后台使用 subagent。把相互独立的委派放在同一条助手消息里一起启动,并在它们运行期间继续做有用的工作。只有当你的下一步动作依赖该 subagent 的结果时,才设 \`run_in_background: false\`。后台运行尘埃落定时,运行时会向你发送一条通知,内含其结果与最后一条助手消息。`,
|
|
1308
|
+
"tool:subagent_fork": `默认在后台使用 subagent_fork。把相互独立的委派放在同一条助手消息里一起启动,并在它们运行期间继续做有用的工作。只有当你的下一步动作依赖该 subagent_fork 的结果时,才设 \`run_in_background: false\`。后台运行尘埃落定时,运行时会向你发送一条通知,内含其结果与最后一条助手消息。`,
|
|
1309
|
+
"ui:file-review-references": `当你成功创建或修改了文件时,在最终答复里提到主要产物。要让这些文件以及其它被改动文件的引用在 Web 里可以点击,用 Markdown 行内代码的格式书写,路径使用文件工具的确切路径;若在该轮改动的文件中基本名唯一,也可以只写基本名。`,
|
|
1310
|
+
"tools:code-only": `\`run_code\` 是你唯一能直接调用的工具 —— 指名任何其它工具的工具调用都会失败。下面 SDK 声明的所有工具都要从程序内部调用。`
|
|
1311
|
+
};
|
|
1312
|
+
//#endregion
|
|
1313
|
+
//#region lib/zh/undo.mjs
|
|
1314
|
+
/**
|
|
1315
|
+
* 中文段译本(专属定制)—— tool:dsh-undo-savepoint。
|
|
1316
|
+
* 用户触发短语("撤销上一步"、"redo" 等)、"⚠️ Previous DSH run did not
|
|
1317
|
+
* finish starting" 告警前缀、主动提示的固定话术(配置已自动保存为快照…)
|
|
1318
|
+
* 都是匹配 / 输出用的精确字符串,逐字保留。
|
|
1319
|
+
*/
|
|
1320
|
+
const UNDO = `## 撤销 / 回退(dsh-undo-savepoint)
|
|
1321
|
+
当用户要求撤销上一步操作("撤销上一步"、"回退"、"恢复"、"redo"、"保存快照"、"查看快照")—— 通常发生在安装插件、应用皮肤或更改设置之后 —— 不要靠猜、也不要手动改配置文件:
|
|
1322
|
+
1. 调用 undo_list 展示可用快照(配置变更时自动创建,外加手动快照)。
|
|
1323
|
+
2. 调用 undo_restore:mode "undo" 撤销最近一次变更,mode "redo" 重新应用上一次 undo 之前保存的状态,或 mode "id" 配合 undo_list 里的快照 id 恢复。拿不准时先用 undo_diff 预览。
|
|
1324
|
+
3. undo_restore 绝不销毁当前状态(会先存为 pre-restore 快照),并会重新确保 dsh-undo-savepoint 挂载本身。
|
|
1325
|
+
4. 手动快照与自动快照分开存放(settings: manualDir / autoDir)。
|
|
1326
|
+
5. 主动提示:只要用户提到或执行了配置变更(安装插件、应用皮肤、修改设置),主动告诉用户"配置已自动保存为快照,改错了随时可以撤销/回退",并主动提出可以用 undo_list 展示最近的快照。不要等用户来问。
|
|
1327
|
+
6. 崩溃告警:如果 undo_list 的输出以 "⚠️ Previous DSH run did not finish starting" 开头,主动建议回退到最近一次良好状态(undo_restore mode "undo"),并说明上一次运行在本插件启动完成之前就崩溃了。
|
|
1328
|
+
7. 配置状态困惑:当用户对当前配置感到困惑(某个插件/皮肤/设置突然消失或变了样,或陷入漫长而无果的调试循环),先调用 undo_recent 检查最近是否有回退能解释它;有就明确告诉用户哪些文件在何时被回退。回退可能发生在另一个会话或经由离线工具执行,用户/AI 可能没有亲眼见到。
|
|
1329
|
+
8. 插件代码:快照也包含用户插件的 CODE 文件(node_modules 下的 junction 目标,例如 D:\\dsh\\plugins\\*,以及 router-global.mjs 之类 profile 本地文件)。插件代码被改坏(例如 "yield* (intermediate value) is not async iterable")同样可以回滚,即使没有任何配置文件变化 —— undo_list 的行会显示插件文件数。
|
|
1330
|
+
9. 安全模式(SAFE MODE):当 DSH 完全无法启动或某个插件破坏了启动流程时,用 undo_safe_mode 的 action "on" 停用除 undo 之外的所有用户插件,然后重启 DSH 再排查;action "off" 恢复之前的插件集合(需再次重启)。undo_list 的崩溃告警会指名一个具体的最近良好快照用于恢复(undo_restore mode "id")。
|
|
1331
|
+
注意:本系统只回滚 DSH 的配置/插件/皮肤状态,不回滚聊天记录。`;
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region lib/zh/teams.mjs
|
|
1334
|
+
/**
|
|
1335
|
+
* 中文段译本(专属定制)—— agent-teams:usage。
|
|
1336
|
+
* agent_teams_* 工具名、参数名(approval= / profile= / attempt_id 等)、
|
|
1337
|
+
* 任务 kind 与 verdict 的取值、"Approve & Run"、.agent-teams 状态目录名
|
|
1338
|
+
* 都是运行时契约,逐字保留。
|
|
1339
|
+
*/
|
|
1340
|
+
const TEAMS = `当用户要求用 AgentTeams 运行某件事(例如"用 AgentTeams 做 X"),或 /agent-teams 斜杠命令的激活消息到达时,你就是多智能体团队的队长。遵循以下协议:
|
|
1341
|
+
1. 调用 agent_teams_create,带上团队名、作为 description 的目标,以及 approval="required"。这只会创建一个待审计划,绝不能生成成员或排期工作。只有用户明确要求跳过评审、立即执行时,才用 approval="automatic"。
|
|
1342
|
+
2. 按目标所需的角色逐个调用 agent_teams_add_member(研究员、工程师、评审……)。在 staging 阶段它们只是可编辑的花名册条目,不是运行中的子智能体。默认情况下成员会快照你当前的 provider/model/reasoning 路由;只有目标或用户要求时才改用别的路由。
|
|
1343
|
+
3. 在 staging 阶段分析目标,创建最小可用的任务 DAG。每次 agent_teams_create_task 调用都必须带非空 subject,验证与评审任务也不例外。独立的工作应当并行;依赖只留给真正的前置关系。把完整的花名册与 DAG 建完后,告诉用户 Web 计划已就绪,然后结束本轮。规划轮里绝不要调用 agent_teams_approve。用户可以点击 Approve & Run、在之后的用户轮里明确批准、回到聊天要求修改,或丢弃该计划。评审 UI 会为返回/丢弃动作注入权威控制消息:严格照办,绝不要自行推断某个缺失或暂停的团队需要重建。用户回到聊天时,先问一个简短的澄清问题,期间不编辑、不重建;得到答复后,用 agent_teams_edit_plan 一次性提交有序的原子批次,先更新下游依赖/负责人再执行删除,概述这次修订,然后再次等待评审。绝不要通过查看或编辑 .agent-teams 状态文件或插件源码来修订计划。只有明确批准才允许调用 agent_teams_approve。
|
|
1344
|
+
4. 批准之后,最终成员配置被原子式生成,调度器开始安排就绪的工作。以委派带队:用 agent_teams_status 监控,用 agent_teams_send_message 传达指导,让空闲的队友执行就绪的工作。不要因为某个队友的回合较慢就亲手重复它的工作。如果用户要求每个成员都出力或汇报,就为每项要求创建一个任务(或直接给每位成员发消息);绝不要等待一个从未被分配工作的成员产出成果。
|
|
1345
|
+
5. 如果用户明确要求暂停某个运行中的成员,其未完成的尝试在打断后会保持搁置;回答用户之后,用 agent_teams_send_message 给同一位成员传达指导,让它继续同一次尝试。普通用户问题若没有要求暂停,不要打断成员。如果工作必须换人、从头重启或被接管,先调用 agent_teams_reassign_task。优先选另一位空闲成员,或让同一位成员重试。assignee=captain 只用于你将在本轮亲手推进到终态的那一个就绪任务;绝不在还有一个队长接管未完成时启动第二个,也绝不在还有队长负责的工作未收尾时结束回合。重派会撤销旧尝试并等待该成员静止,防止迟到的结果覆盖新尝试。
|
|
1346
|
+
6. 任务携带 attempt_id 能力凭证。成员更新时必须使用当前 attempt_id;stale-attempt 错误说明所有权已变更。收到进度通知后持续检查状态,直到每项要求的任务都到终态、每位成员都空闲/就绪;不要忙轮询,也不要向没有被分配工作的成员索要汇报。
|
|
1347
|
+
7. 如果用户点名了某个已配置的 profile / template / 固定花名册,把该名字作为 profile= 传给 agent_teams_create。用 profile 成功创建后,不要重建相同的成员。种子 profile 自带模板任务;队长规划 profile 只提供花名册与护栏,DAG 要你在 staging 阶段自行设计。评审/测试失败时添加修复或重试任务,但绝不让新任务依赖失败的任务。不要用 send_message 去启动下一阶段;批准后调度器会分配就绪的工作。删除团队之前,盯到每项要求的任务都到终态。未经用户明确确认,绝不要执行真实的部署。
|
|
1348
|
+
8. 质量类任务(requirements、implementation、verification、review、repair、integration)必须带契约:非空 objective 与 acceptance;implementation/repair 还需要 inScope 与 verify。review/requirements 只有 verdict=pass 才能完成;needs_revision/reject 必须带 findings 失败收场。系统随后会打开依赖成功来源(绝不依赖失败的评审)的修复 + 下一次评审。不要批准你自己的实现。create_task 不再静默恢复已停摆(halted)的团队 —— 调用 agent_teams_resume 并附原因,或 create_task({resume:true, resumeReason})。
|
|
1349
|
+
9. 当用户明确要求完整的质量模式规划时,除非约束禁止某个阶段,按此顺序:requirements → implementation → verification → review → integration。在团队还处于 staging 时就把整张 DAG 建好:当某个实现的依赖链包含那个 requirements 任务时,允许它在 requirements 完成前创建。这是受支持的行为;不要等 requirements 跑完,也不要靠查看插件源码来确认。staged 的 integration 任务可以依赖第 1 轮 review。若该评审随后返回 needs_revision,系统会自动把仍未开始的下游依赖改接到生成的修复 + 下一次评审门上,所以把 integration 留在原计划里,不要省略或手工重建。从真实工作区或明确的 profile 推导 inScope 与验证命令;绝不要假设 src/ 或 pnpm test。给每个质量任务契约。评审的 acceptance 要评判最新的实现,而不是门会不会拒掉 needs_revision。不要把冒烟测试脚本写进任务。不要让评审者故意提交 needs_revision。除非用户要求队长接管,不要自称实现或评审。评审失败后,等待自动修复 + 下一次评审,不要手工重建这个循环。halted 表示人类停止了团队;创建更多工作前先调用 agent_teams_resume。escalated 表示自动评审循环撞到了上限;那不是 halt。
|
|
1350
|
+
10. 把团队成果呈现给用户,然后 agent_teams_delete 该团队,除非用户还想继续用它。停止团队会同时中止队长的当前回合和成员的工作;只有之后明确的用户轮才能恢复。
|
|
1351
|
+
|
|
1352
|
+
工具:agent_teams_create, agent_teams_approve, agent_teams_edit_plan, agent_teams_add_member, agent_teams_remove_member, agent_teams_create_task, agent_teams_reassign_task, agent_teams_claim_task, agent_teams_update_task, agent_teams_send_message, agent_teams_status, agent_teams_resume, agent_teams_delete`;
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region lib/zh/cordis.mjs
|
|
1355
|
+
/**
|
|
1356
|
+
* 中文段译本(专属定制)—— tool:cordis。
|
|
1357
|
+
* cordis_* 工具名、pluginId / packageId / pluginRunId / currentPackageId /
|
|
1358
|
+
* nextPackageId 等运行时标识、@pluginId 引用语法、示例代码块逐字保留。
|
|
1359
|
+
*/
|
|
1360
|
+
const CORDIS = `# 动态 Cordis 插件
|
|
1361
|
+
|
|
1362
|
+
动态 Cordis 插件临时扩展当前 DSH 进程。插件用 apply(ctx) 消费服务、监听事件、提供服务、注册模型工具,或在槽位(Slot)中注册浏览器 UI。
|
|
1363
|
+
|
|
1364
|
+
- 插件与包(Package)的定义只存在于当前进程。define 本身不修改仓库源码、配置或磁盘,定义也不会在进程重启后保留。
|
|
1365
|
+
- 受限执行环境防的是误用,不是针对恶意代码的安全边界。动态代码取得的服务连接的是真实运行时。
|
|
1366
|
+
|
|
1367
|
+
## 先把面向用户的计划说清楚
|
|
1368
|
+
|
|
1369
|
+
- 动态 Cordis 插件是可选的实现机制之一,不是所有请求的默认选项。只有当用户打算设计或创造某样东西,或一个临时界面能实质性帮助当前工作时,才考虑用它。这些指令或相关工具的存在,以及关于 Cordis 本身的讨论,都不意味着请求就是动态插件任务。
|
|
1370
|
+
- 当 Cordis 可能合适时,从请求与对话推断意图中的工作目标与生命周期。只有当成果属于当前运行中的 Harness、且应作为临时运行时扩展交付时才使用它。如果这个区分实质性地模糊,最多问一个关于预期结果或生命周期的简短问题;否则按匹配的工作流继续,不要要求用户了解或选择 Cordis 作为实现机制。
|
|
1371
|
+
- 一旦确定动态插件合适,判断任务是创建新插件,还是用 @pluginId 修改用户指名的插件。目标明确时直接进行,不要反复请求确认。
|
|
1372
|
+
- 从请求的成果出发选择宿主(Host)、客户端(Client)或两者。任务不需要可见的页面行为时,不要提议客户端/浏览器 UI;当请求的成果是可视化、可交互或依赖页面状态时,也不要回避客户端。宿主还是客户端是实现选择,不要让用户去选。
|
|
1373
|
+
- 当设计方向或一个潜在有用的界面会实质性影响结果时,最多问一个关于成果或创作偏好的简短问题,并给出几个候选方向;否则直接进行,不要搞多轮访谈或复杂问卷。
|
|
1374
|
+
- cordis_define 只定义并呈现代码,不会运行它。定义之后,说明宿主返回的 pluginId 与 packageId,以及下一步是 run 还是 update。
|
|
1375
|
+
- cordis_run 可能需要用户批准。返回 awaiting-approval 时,说明用户需要在 UI 里允许或拒绝。不要等待、重试或声称它已在运行。
|
|
1376
|
+
- 返回 starting 时,说明请求已进入异步流程,客户端仍在激活。starting 不代表成功。等系统通过 steering 上下文报告最终结果。
|
|
1377
|
+
- 用户拒绝后不要再请求批准。技术失败后,从该插件的诊断出发修复同一个插件;不要悄悄另建替代插件。
|
|
1378
|
+
|
|
1379
|
+
## 推荐工作流与工具
|
|
1380
|
+
|
|
1381
|
+
创建、修改或修复插件之前,先加载 cordis-plugin-development 技能(Skill)。该技能提供需求导航、能力组合、完整示例与故障排查。把 Inspect Provider 的结果当作确切 API 的唯一事实来源。
|
|
1382
|
+
|
|
1383
|
+
1. cordis_inspect_list:发现当前宿主与客户端的 Provider 及其只读查询方法。
|
|
1384
|
+
2. cordis_inspect_query:用返回的 platform、provider、method 与 schema 查询确切的服务、事件、内建(Builtin)、槽位、主题 token 或工具信息。
|
|
1385
|
+
3. cordis_inspect_self:查看当前会话的插件、包、版本指针、源码与诊断。只有同时给出 pluginId 与 packageId 才返回源码。
|
|
1386
|
+
4. cordis_define:为新插件创建第一个包,或向既有插件追加一个不可变包。它定义代码但不运行。
|
|
1387
|
+
5. cordis_run:激活确切的包。首次激活、重启当前或回滚用 run;切换版本用 update。
|
|
1388
|
+
6. cordis_stop:移除当前 Run 与待批准请求,但保留定义、授权与版本指针。
|
|
1389
|
+
7. cordis_undefine:永久停用并删除一个插件及其所有包。只在确认用户不再需要后使用。
|
|
1390
|
+
|
|
1391
|
+
- 在写代码之前,Inspect 与目录(Catalog)数据只用于确认能力、名字、签名、类型与注册协议;它们不替代业务 API。
|
|
1392
|
+
- 不带输入调用 Service.listService 与 Event.listEvents,从紧凑的签名目录里挑选,然后再精确查询目标服务或事件。精确查询返回结构化契约及其引用的类型。
|
|
1393
|
+
- 运行时,插件必须调用真实服务、监听真实事件。不要缓存、展示 Inspect 结果,也不要把它当业务数据依赖。
|
|
1394
|
+
|
|
1395
|
+
## 身份、版本与批准
|
|
1396
|
+
|
|
1397
|
+
- pluginId 标识一个可以随时间修改的插件。新建插件只提交 3–6 个小写英文字母的语义化 idPrefix;最终 ID 由宿主分配。
|
|
1398
|
+
- packageId 标识插件下一个不可变的宿主/客户端源码版本。要改代码就定义新包,绝不覆盖旧版本。
|
|
1399
|
+
- pluginRunId 标识一次激活尝试,关联其批准、宿主/客户端加载、私有 RPC、Run 卡片与错误。
|
|
1400
|
+
- currentPackageId 是最近一次完全成功的包。停止、启动更新或更新失败都不会清除它。
|
|
1401
|
+
- nextPackageId 是等待批准、正在尝试、等待客户端激活或最近失败的目标包。
|
|
1402
|
+
- 单个对勾只批准当前包;双对勾批准同一插件未来的版本。技术失败后授权仍然有效。
|
|
1403
|
+
- 更新会在启动目标包之前停止旧 Run。失败不会自动重启旧版本;之后用 update 重试,或用 run 回滚到 current。
|
|
1404
|
+
|
|
1405
|
+
当用户输入 @pluginId 时,系统注入身份、默认基础包、版本指针与运行状态,但不注入源码:
|
|
1406
|
+
|
|
1407
|
+
1. 调用 cordis_inspect_self(pluginId, packageId) 读取目标源码。
|
|
1408
|
+
2. 以既有模式使用 cordis_define,向同一插件追加包。
|
|
1409
|
+
3. 按版本关系以 run 或 update 模式调用 cordis_run。
|
|
1410
|
+
|
|
1411
|
+
绝不为 @pluginId 悄悄另建插件。如果该引用因为被删除、属于别的会话或随进程重启丢失而不可用,直接告诉用户。
|
|
1412
|
+
|
|
1413
|
+
## 必须避免的高频错误
|
|
1414
|
+
|
|
1415
|
+
### 服务:ctx.get 与 inject
|
|
1416
|
+
|
|
1417
|
+
- 默认用 ctx.get('serviceName') 读取可选服务,并处理 undefined。
|
|
1418
|
+
- 只有当该服务是硬依赖、且插件必须进入等待(直到 Cordis 在服务出现后重新激活它)时,才在返回的插件对象上声明 inject: ['serviceName']。
|
|
1419
|
+
- 只有在 inject 里声明过某服务后,才能以 ctx.serviceName 属性方式读取。绝不要以 ctx 属性访问未声明的服务。
|
|
1420
|
+
|
|
1421
|
+
\`\`\`js
|
|
1422
|
+
return {
|
|
1423
|
+
inject: ['requiredService'],
|
|
1424
|
+
apply(ctx) {
|
|
1425
|
+
ctx.requiredService.someMethod()
|
|
1426
|
+
const optionalService = ctx.get('optionalService')
|
|
1427
|
+
if (optionalService !== undefined) optionalService.someMethod()
|
|
1428
|
+
},
|
|
1429
|
+
}
|
|
1430
|
+
\`\`\`
|
|
1431
|
+
|
|
1432
|
+
### 代码:只用普通 JavaScript
|
|
1433
|
+
|
|
1434
|
+
- 宿主与客户端代码不经 TypeScript、JSX 或打包器转换。
|
|
1435
|
+
- 不要用 TypeScript 类型、as、装饰器、import、require 或 JSX。
|
|
1436
|
+
- 客户端 React 代码必须用 React.createElement(...);绝不写 <Component />。
|
|
1437
|
+
- 不要假设 process、Buffer、window、document、fetch、原生定时器或任何其它全局可用。先查询对应平台的 Builtins 与服务。
|
|
1438
|
+
|
|
1439
|
+
### 数据:不要序列化活数据
|
|
1440
|
+
|
|
1441
|
+
- 服务、事件、槽位、会话及其衍生的 Cordis/DSH 对象是内部活数据,不是可以倾倒的普通 JSON。
|
|
1442
|
+
- 不要对活数据使用 JSON.stringify、structuredClone、递归枚举、整体复制或整对象展示。
|
|
1443
|
+
- 只读取任务所需的叶子字段,然后构造不带宿主引用的最小自有数据对象。
|
|
1444
|
+
|
|
1445
|
+
### 生命周期:每个副作用都必须可逆
|
|
1446
|
+
|
|
1447
|
+
- 服务、事件、工具、处理器、定时器、槽位、样式与主题覆盖都必须属于当前 Fiber。
|
|
1448
|
+
- 用 ctx.effect()、ctx.on() 或返回 disposer 的官方 API,保证 stop、update 或 undefine 能移除每个副作用。
|
|
1449
|
+
- cordis-plugin-development 技能包含定时器、瀑布流、槽位、主题、工具、RPC 与 React 的完整示例与故障排查。
|
|
1450
|
+
|
|
1451
|
+
## 宿主与客户端
|
|
1452
|
+
|
|
1453
|
+
- 宿主运行在 DSH 的 Node.js 进程里,适合文件、网络、命令、Agent/会话访问、宿主事件、服务、模型工具,以及可被客户端调用的 JSON 方法。
|
|
1454
|
+
- 客户端运行在浏览器页面里,适合主题、布局、当前页面状态、工具卡片与槽位 UI。
|
|
1455
|
+
- 宿主与客户端通过包私有的 JSON 方法通信:宿主用 harness.handle(method, handler),客户端用 host.call(method, args)。方向是客户端→宿主,且只有无损 JSON 可以跨越它。
|
|
1456
|
+
- 客户端 UI 必须注册在查询到的槽位里;apply() 不能直接返回 React Element。不带 root 查询 Slots.listSubTree,从紧凑的目的/拓扑树里挑选,然后再精确查询目标 root 的完整注册契约与 props,之后才写代码。
|
|
1457
|
+
- Run 专属面板与确切的槽位注册模式见技能与 Inspect Provider。
|
|
1458
|
+
|
|
1459
|
+
## 异步结果与恢复
|
|
1460
|
+
|
|
1461
|
+
- 不要在工具里等待只有当前回合结束后才可能发生的批准或浏览器工作。
|
|
1462
|
+
- 异步成功、拒绝与运行时错误会更新 Run 状态,并通过 steering 上下文通知你。
|
|
1463
|
+
- 技术失败后,用 cordis_inspect_self 读取确切包源码及其 message/stack。在同一插件下定义一个修正后的包并自主重试。
|
|
1464
|
+
- 其它失败原因、修复流程与完整扩展模式,使用 cordis-plugin-development 技能。`;
|
|
1465
|
+
//#endregion
|
|
1466
|
+
//#region lib/zh/sdk.mjs
|
|
1467
|
+
/**
|
|
1468
|
+
* 中文段译本(专属定制)—— tools:sdk。
|
|
1469
|
+
* 该段 95% 是 TS 类型声明代码块(工具签名契约),逐字翻译既无意义又有抄写
|
|
1470
|
+
* 风险 —— 译本因此是**函数形态**:只替换前导散文,代码块从原文的 \`\`\`ts
|
|
1471
|
+
* 围栏起逐字节保留(运行时取自原文,绝不手抄)。引擎约定:函数返回
|
|
1472
|
+
* null/undefined = 放弃替换、保持原文。
|
|
1473
|
+
* ponytail: 依赖 \`\`\`ts 围栏标记存在;宿主若改掉围栏风格,函数返回 null
|
|
1474
|
+
* 自动退回英文原文,升级路径是把整段改成纯静态译本。
|
|
1475
|
+
*/
|
|
1476
|
+
const SDK_ZH = (original) => {
|
|
1477
|
+
const at = String(original ?? "").indexOf("```ts");
|
|
1478
|
+
if (at < 0) return null;
|
|
1479
|
+
return "## 为 run_code 编写代码\n\n`run_code` 接受两个必填参数:`code` —— 一个异步 TypeScript 函数的函数体(只允许可擦除语法 —— 不用 `enum` 与命名空间;类型注解仅供参考,运行时会剥离类型)—— 和 `description`,一段说明该程序做什么的简短摘要。在程序内部:\n\n- 用 `await tools.name(args)` 调用工具 —— 名称古怪时用引号访问:`tools[\"my-tool\"](args)`。每次调用都会解析为该工具的规范化 JSON 值。工具参数必须是无损 JSON。\n- 失败的工具调用会以 `ToolCallError` reject,其 `toolName` 标识失败的工具,`message` 人类可读 —— 用 `try/catch` 捕获后继续。\n- 相互独立的只读调用可以在 `Promise.all` 下并行(安全调用并发执行;会改动状态的调用单独按提交顺序执行)。有依赖的工作用 `await` 串行。\n- 用 `return` 和/或 `console.log(...)` 输出结果。只有你打印或返回的内容才算程序输出。包含图片的成功工具结果会在运行结束后附上,供你下一步查看;其它所有中间结果都不会进入对话,因此只提取你需要的部分。\n\n可用工具:\n\n" + original.slice(at);
|
|
1480
|
+
};
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region lib/zh/index.mjs
|
|
1483
|
+
/**
|
|
1484
|
+
* 中文段译本(专属定制)—— 汇总入口。
|
|
1485
|
+
* 引擎(lib/effective.js 的 applySectionPolicy)在 zhSections 开启时按段名
|
|
1486
|
+
* 精确匹配替换文本:对得上号的段整段换成译本,对不上的段保持原文 —— 段数
|
|
1487
|
+
* 与注入不增不减。取值是字符串(整段替换)或函数((原文) => 译本 | null,
|
|
1488
|
+
* 见 sdk.mjs;返回 null = 放弃替换)。
|
|
1489
|
+
* 安全边界:{{...}} 提示词变量(deployment:persona 的 {{model}} / {{cwd}}、
|
|
1490
|
+
* env_* 等)由宿主在装配后渲染,译本里逐字保留;黑名单挡下的密钥类 env 变量
|
|
1491
|
+
* 本来就不会被注册,替换文本不引入任何新的变量引用。
|
|
1492
|
+
*/
|
|
1493
|
+
const ZH_SECTIONS = {
|
|
1494
|
+
...CORE,
|
|
1495
|
+
"tool:dsh-undo-savepoint": UNDO,
|
|
1496
|
+
"agent-teams:usage": TEAMS,
|
|
1497
|
+
"tool:cordis": CORDIS,
|
|
1498
|
+
"tools:sdk": SDK_ZH
|
|
1499
|
+
};
|
|
1021
1500
|
//#endregion
|
|
1022
|
-
//#region src/client/
|
|
1023
|
-
/**
|
|
1024
|
-
*
|
|
1501
|
+
//#region src/client/SectionsPane.tsx
|
|
1502
|
+
/**
|
|
1503
|
+
* SectionsPane — 独立面板的左栏(提示词模式)。
|
|
1504
|
+
*
|
|
1505
|
+
* 顶部三个阶段 Tab(引导期 / 常驻期 / 压缩受控期,恒定全部可选),与右栏
|
|
1506
|
+
* 预览的阶段按钮**联动**:两边切的是同一个阶段,编辑哪个阶段就看哪个阶段的
|
|
1507
|
+
* 装配。每个阶段列出该阶段装配里的段:勾选框 = 该阶段是否注入模型(勾上 =
|
|
1508
|
+
* 启用),被停用的行半透明原位保留、随时可勾回。行内:↑/↓ 重排、编辑(按
|
|
1509
|
+
* 阶段替换文本)、还原、删除(仅自定义段);「+ 注入」展开注入新段表单。
|
|
1510
|
+
*
|
|
1511
|
+
* 底部固定条:三态过滤(全部 / 已启用 / 已停用)——像宿主能力管理页一样按
|
|
1512
|
+
* 勾选状态筛选当前阶段的列表。
|
|
1025
1513
|
*
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1029
|
-
* 顺序以 base 骨架为底再叠加草稿序;整行可拖拽,拖到某行上方/下方出现
|
|
1030
|
-
* 插入指示线,松手即在该位置重排。
|
|
1031
|
-
* - 被屏蔽的段与普通段一模一样:留在列表原位、一样可操作(拖拽/箭头/替换/
|
|
1032
|
-
* 恢复/删除),随时可互相转换,唯一区别是屏蔽勾选框与「已屏蔽」徽标 ——
|
|
1033
|
-
* 屏蔽只意味着不注入模型,不剥夺任何编辑能力(v1 语义)。屏蔽勾选直接
|
|
1034
|
-
* 绑定该阶段自己的名单(deniedNames 三态互相独立,含未保存草稿),
|
|
1035
|
-
* 点击立即反馈 —— 屏蔽一个阶段绝不连带屏蔽另一个阶段。
|
|
1036
|
-
* - 重排(拖入/箭头)走 `persistPhase`:把该阶段全部行重写为连续整数
|
|
1037
|
-
* 虚拟 order(系统段写空文本 = 仅 order 覆盖,服务端保留原文;custom 段
|
|
1038
|
-
* 保留文本)—— UI 行序叠加同一份 order 立即重排,拖动即时可见。
|
|
1039
|
-
* - 文本编辑(「编辑」/「还原」)同样按阶段生效:写的就是本阶段注入条目的
|
|
1040
|
-
* 文本,三个阶段各改各的、互不影响(服务端让本阶段注入文本优先于全局
|
|
1041
|
-
* replace,否则一份文本会同时改坏三个阶段)。全局 replace 字典自此不再由
|
|
1042
|
-
* 界面写入,只在「还原」时顺带清除遗留条目 —— 新界面没有别的出口能恢复它。
|
|
1043
|
-
* - 拖放搬移保留行对象(system/custom 身份与文本原样带走),绝不重建行 ——
|
|
1044
|
-
* 重建会把系统段误标成 custom 并清空内容。只有从其它部分/全部池拖入的
|
|
1045
|
-
* 新段才构造新行(身份取自拖拽源)。
|
|
1046
|
-
* - 拖放事件在行上处理并 stopPropagation,避免与阶段框二次处理对冲。
|
|
1047
|
-
* - 全部提示词 → 上三段:插入该段到目标位置(已存在则只是移动排序;该段
|
|
1048
|
-
* 在目标阶段被屏蔽则同步解除屏蔽 —— 行立即出现)。上三段 → 全部:从该
|
|
1049
|
-
* 阶段移除 —— custom 段删除注入条目;系统段等价于屏蔽该阶段(系统段是
|
|
1050
|
-
* 装配的一部分,无法真正删除)。
|
|
1051
|
-
* - 屏蔽 / 解除屏蔽的写入严格落在当前编辑目标的草稿里(全局默认 → 全局名单;
|
|
1052
|
-
* agent 预设 → 该预设自己的 override,显式空名单也是接管语义)—— 预设之间
|
|
1053
|
-
* 互相独立:在 A 预设里的任何屏蔽 / 解除屏蔽对 B 预设与全局默认零影响。
|
|
1054
|
-
* - 每个阶段部分按自己那一阶段的装配结果给出警示:该 scope 有 complete 段
|
|
1055
|
-
* 整段接管最终提示词,或本插件产出的段被下游装配规则丢弃时,明确写出
|
|
1056
|
-
* 「段级定制不会(完全)进入模型看到的提示词」,避免提示词 Tab 与预览 Tab
|
|
1057
|
-
* 各说各话。
|
|
1058
|
-
* - 「三态同步」勾选(默认关):屏蔽勾选对三个阶段一起生效,只作用于同名
|
|
1059
|
-
* 的那一段;拖拽 / 排序仍按各自阶段的副本语义只动本阶段。
|
|
1514
|
+
* 「本系统全部提示词」只读池收在列表末尾的折叠区(不分阶段):每行三个小
|
|
1515
|
+
* 按钮把该段加入对应阶段(替代旧版的拖拽搬移;运行时动态段加不进,会给出
|
|
1516
|
+
* 说明)。
|
|
1060
1517
|
*
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1518
|
+
* 全部阶段状态逻辑来自 lib/sectionOps.mjs(纯函数,node --test 单测直接
|
|
1519
|
+
* 覆盖同一份代码)。
|
|
1063
1520
|
*/
|
|
1064
1521
|
/** 编辑态的键:段名之外还要带阶段,否则三个部分里同名的行会同时展开编辑器。 */
|
|
1065
1522
|
const editKey = (key, name) => `${key}:${name}`;
|
|
1066
|
-
function
|
|
1523
|
+
function SectionsPane({ cfg, inv, phases, phase, syncAll, t, write }) {
|
|
1524
|
+
const [filter, setFilter] = (0, react.useState)("all");
|
|
1067
1525
|
const [editing, setEditing] = (0, react.useState)(null);
|
|
1068
1526
|
const [draft, setDraft] = (0, react.useState)("");
|
|
1069
|
-
const [
|
|
1070
|
-
const [dragSource, setDragSource] = (0, react.useState)(null);
|
|
1071
|
-
const [dropTarget, setDropTarget] = (0, react.useState)(null);
|
|
1527
|
+
const [addOpen, setAddOpen] = (0, react.useState)(false);
|
|
1072
1528
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
1073
|
-
const
|
|
1074
|
-
raf: 0,
|
|
1075
|
-
clientY: 0
|
|
1076
|
-
});
|
|
1077
|
-
const stopAutoScroll = () => {
|
|
1078
|
-
if (scrollRef.current.raf !== 0) {
|
|
1079
|
-
cancelAnimationFrame(scrollRef.current.raf);
|
|
1080
|
-
scrollRef.current.raf = 0;
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
const autoScrollTick = () => {
|
|
1084
|
-
scrollRef.current.raf = 0;
|
|
1085
|
-
const edge = 48;
|
|
1086
|
-
const y = scrollRef.current.clientY;
|
|
1087
|
-
const inner = window.innerHeight;
|
|
1088
|
-
let node = rootRef.current?.parentElement;
|
|
1089
|
-
while (node !== null && node !== void 0) {
|
|
1090
|
-
if (node.scrollHeight > node.clientHeight) {
|
|
1091
|
-
if (y < edge) node.scrollTop -= Math.max(6, (edge - y) / 2);
|
|
1092
|
-
else if (y > inner - edge) node.scrollTop += Math.max(6, (y - (inner - edge)) / 2);
|
|
1093
|
-
else return;
|
|
1094
|
-
scrollRef.current.raf = requestAnimationFrame(autoScrollTick);
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
node = node.parentElement;
|
|
1098
|
-
}
|
|
1099
|
-
if (y < edge) window.scrollBy({ top: -Math.max(6, (edge - y) / 2) });
|
|
1100
|
-
else if (y > inner - edge) window.scrollBy({ top: Math.max(6, (y - (inner - edge)) / 2) });
|
|
1101
|
-
if (y < edge || y > inner - edge) scrollRef.current.raf = requestAnimationFrame(autoScrollTick);
|
|
1102
|
-
};
|
|
1103
|
-
const rootRef = (0, react.useRef)(null);
|
|
1104
|
-
const rootDragOver = (e) => {
|
|
1105
|
-
if (!dragName) return;
|
|
1106
|
-
scrollRef.current.clientY = e.clientY;
|
|
1107
|
-
if (scrollRef.current.raf === 0) scrollRef.current.raf = requestAnimationFrame(autoScrollTick);
|
|
1108
|
-
};
|
|
1109
|
-
const parts = PART_ORDER;
|
|
1110
|
-
const partNote = (key) => {
|
|
1111
|
-
const view = phases?.[key];
|
|
1112
|
-
if (view === void 0 || view === null) return null;
|
|
1113
|
-
if (cfg.forceSections !== false) return null;
|
|
1114
|
-
if (view.takenOverBy !== void 0) return (0, react.createElement)("div", { style: s.noticeWarn }, t("sectionsTakenOver", { name: view.takenOverBy }));
|
|
1115
|
-
const lost = view.lostSections;
|
|
1116
|
-
if (lost !== void 0) return (0, react.createElement)("div", { style: s.noticeWarn }, t("sectionsLost", {
|
|
1117
|
-
emitted: lost.emitted,
|
|
1118
|
-
survived: lost.survived
|
|
1119
|
-
}));
|
|
1120
|
-
return null;
|
|
1121
|
-
};
|
|
1122
|
-
const rowsOf = (key) => {
|
|
1123
|
-
const replace = cfg.replace ?? {};
|
|
1124
|
-
const view = phases?.[key];
|
|
1125
|
-
const denied = new Set(deniedNames(cfg, key));
|
|
1126
|
-
const postByName = new Map((view?.sections ?? []).map((sec) => [sec.name, sec]));
|
|
1127
|
-
const baseByName = new Map((view?.baseSections ?? []).map((sec) => [sec.name, sec]));
|
|
1128
|
-
const { phase, names: injectedHere, custom: customNames, text, order: phaseOrder } = injectedAt(cfg, key);
|
|
1129
|
-
const names = [];
|
|
1130
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1131
|
-
const push = (name) => {
|
|
1132
|
-
if (!seen.has(name)) {
|
|
1133
|
-
seen.add(name);
|
|
1134
|
-
names.push(name);
|
|
1135
|
-
}
|
|
1136
|
-
};
|
|
1137
|
-
for (const sec of view?.baseSections ?? []) if (postByName.has(sec.name) || denied.has(sec.name) || sec.blocked === true) push(sec.name);
|
|
1138
|
-
for (const name of postByName.keys()) if (baseByName.has(name) || injectedHere.has(name)) push(name);
|
|
1139
|
-
for (const item of cfg.inject ?? []) {
|
|
1140
|
-
if ((item.phase ?? "always") !== phase) continue;
|
|
1141
|
-
if (item.custom === true || text.get(item.name) || baseByName.has(item.name) || postByName.has(item.name)) push(item.name);
|
|
1142
|
-
}
|
|
1143
|
-
for (const name of customNames) push(name);
|
|
1144
|
-
const rows = names.map((name) => {
|
|
1145
|
-
const override = text.get(name) ?? "";
|
|
1146
|
-
return {
|
|
1147
|
-
name,
|
|
1148
|
-
text: postByName.get(name)?.text ?? baseByName.get(name)?.text ?? "",
|
|
1149
|
-
replaced: override !== "" || Object.hasOwn(replace, name),
|
|
1150
|
-
custom: customNames.has(name),
|
|
1151
|
-
override,
|
|
1152
|
-
blocked: denied.has(name)
|
|
1153
|
-
};
|
|
1154
|
-
});
|
|
1155
|
-
if (phaseOrder.size > 0) {
|
|
1156
|
-
const fallback = new Map(rows.map((row, i) => [row.name, i]));
|
|
1157
|
-
rows.sort((a, b) => (phaseOrder.get(a.name) ?? fallback.get(a.name) ?? 0) - (phaseOrder.get(b.name) ?? fallback.get(b.name) ?? 0));
|
|
1158
|
-
}
|
|
1159
|
-
return rows;
|
|
1160
|
-
};
|
|
1529
|
+
const rowsOf = (key) => phaseRows(cfg, phases?.[key] ?? null, key);
|
|
1161
1530
|
const applyBlock = (key, name, blocked) => {
|
|
1162
1531
|
const patch = blockPatch(cfg, key, name, blocked);
|
|
1163
1532
|
for (const [field, value] of Object.entries(patch)) write(field, value);
|
|
@@ -1180,11 +1549,11 @@ window.__ModuleLoader__.load({
|
|
|
1180
1549
|
persistPhase(key, next);
|
|
1181
1550
|
};
|
|
1182
1551
|
const removeFromPart = (key, name) => {
|
|
1183
|
-
const
|
|
1552
|
+
const phaseOfItem = injectPhaseOf(key);
|
|
1184
1553
|
write("inject", (cfg.inject ?? []).filter((x) => {
|
|
1185
1554
|
if (x.name !== name) return true;
|
|
1186
1555
|
const itemPhase = x.phase ?? "always";
|
|
1187
|
-
return !(itemPhase ===
|
|
1556
|
+
return !(itemPhase === phaseOfItem || itemPhase === "always");
|
|
1188
1557
|
}));
|
|
1189
1558
|
applyBlock(key, name, false);
|
|
1190
1559
|
};
|
|
@@ -1193,12 +1562,19 @@ window.__ModuleLoader__.load({
|
|
|
1193
1562
|
setDraft(row.override || row.text);
|
|
1194
1563
|
};
|
|
1195
1564
|
const commitReplace = (key, row) => {
|
|
1196
|
-
const
|
|
1565
|
+
const patchRow = (r) => r.name !== row.name ? r : {
|
|
1197
1566
|
...r,
|
|
1198
1567
|
override: draft,
|
|
1199
1568
|
text: draft
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1569
|
+
};
|
|
1570
|
+
if (syncAll) {
|
|
1571
|
+
const rowsByKey = {};
|
|
1572
|
+
for (const k of PART_ORDER) {
|
|
1573
|
+
const rows = rowsOf(k);
|
|
1574
|
+
rowsByKey[k] = k === key || rows.some((r) => r.name === row.name) ? rows.map(patchRow) : rows;
|
|
1575
|
+
}
|
|
1576
|
+
write("inject", mergedPhaseInjectEntries(cfg, rowsByKey));
|
|
1577
|
+
} else persistPhase(key, rowsOf(key).map(patchRow));
|
|
1202
1578
|
setEditing(null);
|
|
1203
1579
|
};
|
|
1204
1580
|
const restoreReplace = (key, row) => {
|
|
@@ -1225,159 +1601,65 @@ window.__ModuleLoader__.load({
|
|
|
1225
1601
|
});
|
|
1226
1602
|
write("inject", inject);
|
|
1227
1603
|
};
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
e.dataTransfer.effectAllowed = "move";
|
|
1232
|
-
try {
|
|
1233
|
-
e.dataTransfer.setData("text/plain", name);
|
|
1234
|
-
} catch {}
|
|
1235
|
-
};
|
|
1236
|
-
const rowDragOver = (e, key, name) => {
|
|
1237
|
-
if (!dragName || dragName === name) return;
|
|
1238
|
-
e.preventDefault();
|
|
1239
|
-
e.dataTransfer.dropEffect = "move";
|
|
1240
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
1241
|
-
const pos = e.clientY < rect.top + rect.height / 2 ? "above" : "below";
|
|
1242
|
-
setDropTarget({
|
|
1243
|
-
part: key,
|
|
1244
|
-
name,
|
|
1245
|
-
pos
|
|
1246
|
-
});
|
|
1247
|
-
};
|
|
1248
|
-
const carryFor = (key, source, name) => {
|
|
1249
|
-
if (source.custom) return {
|
|
1250
|
-
text: "",
|
|
1251
|
-
blocked: false
|
|
1252
|
-
};
|
|
1253
|
-
if (inBaseOf(key, name)) return {
|
|
1254
|
-
text: "",
|
|
1255
|
-
blocked: false
|
|
1256
|
-
};
|
|
1257
|
-
if (source.text === "" || source.text.startsWith("<")) return {
|
|
1258
|
-
text: "",
|
|
1259
|
-
blocked: true
|
|
1260
|
-
};
|
|
1261
|
-
return {
|
|
1262
|
-
text: source.text,
|
|
1263
|
-
blocked: false
|
|
1264
|
-
};
|
|
1265
|
-
};
|
|
1266
|
-
const insertInto = (key, targetName, pos) => {
|
|
1267
|
-
if (!dragName || !dragSource) return;
|
|
1268
|
-
const carry = carryFor(key, dragSource, dragName);
|
|
1269
|
-
if (carry.blocked) {
|
|
1270
|
-
setNotice(t("sectionDynamicNoAdd", { name: dragName }));
|
|
1604
|
+
const addFromPool = (key, name, text) => {
|
|
1605
|
+
if (text === "" || text.startsWith("<")) {
|
|
1606
|
+
setNotice(t("sectionDynamicNoAdd", { name }));
|
|
1271
1607
|
return;
|
|
1272
1608
|
}
|
|
1609
|
+
setNotice(null);
|
|
1273
1610
|
const rows = rowsOf(key);
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1611
|
+
const existing = rows.find((row) => row.name === name);
|
|
1612
|
+
const next = existing !== void 0 ? [...rows.filter((row) => row.name !== name), existing] : [...rows, {
|
|
1613
|
+
name,
|
|
1614
|
+
text: "",
|
|
1615
|
+
replaced: Object.hasOwn(cfg.replace ?? {}, name),
|
|
1616
|
+
custom: false,
|
|
1617
|
+
override: text,
|
|
1280
1618
|
blocked: false
|
|
1281
|
-
};
|
|
1282
|
-
|
|
1283
|
-
if (next === null) return;
|
|
1284
|
-
if (dragSource.key !== key && inBaseOf(key, dragName) && deniedNames(cfg, key).includes(dragName)) applyBlock(key, dragName, false);
|
|
1619
|
+
}];
|
|
1620
|
+
if (existing === void 0 && inBaseOf(key, name) && deniedNames(cfg, key).includes(name)) applyBlock(key, name, false);
|
|
1285
1621
|
persistPhase(key, next);
|
|
1286
1622
|
};
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
if (
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
e.preventDefault();
|
|
1300
|
-
if (dragName && dragSource) {
|
|
1301
|
-
const carry = carryFor(key, dragSource, dragName);
|
|
1302
|
-
if (!carry.blocked) {
|
|
1303
|
-
const rows = rowsOf(key);
|
|
1304
|
-
const existing = rows.find((row) => row.name === dragName);
|
|
1305
|
-
const next = rows.filter((row) => row.name !== dragName);
|
|
1306
|
-
next.push(existing ?? {
|
|
1307
|
-
name: dragName,
|
|
1308
|
-
text: dragSource.custom ? dragSource.text : "",
|
|
1309
|
-
replaced: Object.hasOwn(cfg.replace ?? {}, dragName),
|
|
1310
|
-
custom: dragSource.custom,
|
|
1311
|
-
override: carry.text,
|
|
1312
|
-
blocked: false
|
|
1313
|
-
});
|
|
1314
|
-
if (dragSource.key !== key && inBaseOf(key, dragName) && deniedNames(cfg, key).includes(dragName)) applyBlock(key, dragName, false);
|
|
1315
|
-
persistPhase(key, next);
|
|
1316
|
-
} else setNotice(t("sectionDynamicNoAdd", { name: dragName }));
|
|
1317
|
-
}
|
|
1318
|
-
setDragName(null);
|
|
1319
|
-
setDragSource(null);
|
|
1320
|
-
setDropTarget(null);
|
|
1321
|
-
};
|
|
1322
|
-
const allBoxDrop = (e) => {
|
|
1323
|
-
e.preventDefault();
|
|
1324
|
-
if (dragName && dragSource?.kind === "part" && dragSource.key) {
|
|
1325
|
-
const src = dragSource.key;
|
|
1326
|
-
const row = rowsOf(src).find((r) => r.name === dragName);
|
|
1327
|
-
const addedSystem = !!row && !row.custom && !inBaseOf(src, dragName) && row.override !== "";
|
|
1328
|
-
if (row && !row.custom && !addedSystem) applyBlock(src, dragName, true);
|
|
1329
|
-
else removeFromPart(src, dragName);
|
|
1330
|
-
}
|
|
1331
|
-
setDragName(null);
|
|
1332
|
-
setDragSource(null);
|
|
1333
|
-
setDropTarget(null);
|
|
1623
|
+
const stageLabel = (key) => key === "bootstrap" ? t("phaseStageGuide") : key === "compaction" ? t("phaseStageControlled") : t("phaseStageResident");
|
|
1624
|
+
const partNote = (key) => {
|
|
1625
|
+
const view = phases?.[key];
|
|
1626
|
+
if (view === void 0 || view === null) return null;
|
|
1627
|
+
if (cfg.forceSections !== false) return null;
|
|
1628
|
+
if (view.takenOverBy !== void 0) return (0, react.createElement)("div", { style: s.noticeWarn }, t("sectionsTakenOver", { name: view.takenOverBy }));
|
|
1629
|
+
const lost = view.lostSections;
|
|
1630
|
+
if (lost !== void 0) return (0, react.createElement)("div", { style: s.noticeWarn }, t("sectionsLost", {
|
|
1631
|
+
emitted: lost.emitted,
|
|
1632
|
+
survived: lost.survived
|
|
1633
|
+
}));
|
|
1634
|
+
return null;
|
|
1334
1635
|
};
|
|
1335
|
-
const endDrag = () => {
|
|
1336
|
-
stopAutoScroll();
|
|
1337
|
-
setDragName(null);
|
|
1338
|
-
setDragSource(null);
|
|
1339
|
-
setDropTarget(null);
|
|
1340
|
-
};
|
|
1341
|
-
const partNoun = (key) => key === "bootstrap" ? t("sectionsPartGuide") : key === "compaction" ? t("sectionsPartControlled") : t("sectionsPartResident");
|
|
1342
1636
|
const isEditing = (key, row) => editing === editKey(key, row.name);
|
|
1343
1637
|
const renderRow = (key, row, index, total) => {
|
|
1344
|
-
|
|
1345
|
-
const isDropTarget = dropTarget?.part === key && dropTarget.name === row.name;
|
|
1346
|
-
const rowStyle = {
|
|
1347
|
-
...s.row,
|
|
1348
|
-
...isDragging ? s.rowDragging : {},
|
|
1349
|
-
...isDropTarget && dropTarget && dropTarget.pos === "above" ? s.rowDropAbove : {},
|
|
1350
|
-
...isDropTarget && dropTarget && dropTarget.pos === "below" ? s.rowDropBelow : {},
|
|
1351
|
-
cursor: "move"
|
|
1352
|
-
};
|
|
1638
|
+
if (!rowVisible(row)) return null;
|
|
1353
1639
|
return (0, react.createElement)("div", {
|
|
1354
1640
|
key: row.name,
|
|
1355
|
-
style:
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
key,
|
|
1360
|
-
text: row.override || row.text,
|
|
1361
|
-
custom: row.custom
|
|
1362
|
-
}),
|
|
1363
|
-
onDragOver: (e) => rowDragOver(e, key, row.name),
|
|
1364
|
-
onDrop: (e) => rowDrop(e, key, row.name),
|
|
1365
|
-
onDragEnd: endDrag
|
|
1641
|
+
style: {
|
|
1642
|
+
...s.row,
|
|
1643
|
+
...row.blocked ? s.rowBlocked : {}
|
|
1644
|
+
}
|
|
1366
1645
|
}, [
|
|
1367
|
-
(0, react.createElement)("
|
|
1368
|
-
style: s.dragHandle,
|
|
1369
|
-
title: t("drag")
|
|
1370
|
-
}, "⠿"),
|
|
1371
|
-
(0, react.createElement)("label", { style: s.switchWrap }, [(0, react.createElement)("input", {
|
|
1646
|
+
(0, react.createElement)("input", {
|
|
1372
1647
|
type: "checkbox",
|
|
1373
|
-
checked: row.blocked,
|
|
1374
|
-
onChange: () => toggleBlocked(key, row.name)
|
|
1375
|
-
|
|
1648
|
+
checked: !row.blocked,
|
|
1649
|
+
onChange: () => toggleBlocked(key, row.name),
|
|
1650
|
+
title: row.blocked ? t("blockedOn") : t("blockedOff"),
|
|
1651
|
+
style: {
|
|
1652
|
+
margin: 0,
|
|
1653
|
+
cursor: "pointer",
|
|
1654
|
+
flex: "none"
|
|
1655
|
+
}
|
|
1656
|
+
}),
|
|
1376
1657
|
(0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [
|
|
1377
1658
|
(0, react.createElement)("span", { style: s.code }, row.name),
|
|
1378
1659
|
(0, react.createElement)("span", { style: s.orderTag }, "#" + index),
|
|
1379
1660
|
(0, react.createElement)("span", { style: row.custom ? s.badgeCustom : s.badgeSystem }, row.custom ? t("manual") : t("system")),
|
|
1380
|
-
row.replaced ? (0, react.createElement)("span", { style: s.badgeReplaced }, t("replaced")) : null
|
|
1661
|
+
row.replaced ? (0, react.createElement)("span", { style: s.badgeReplaced }, t("replaced")) : null,
|
|
1662
|
+
row.blocked ? (0, react.createElement)("span", { style: s.badgeBlocked }, t("blockedOn")) : null
|
|
1381
1663
|
]), isEditing(key, row) ? (0, react.createElement)("div", { style: s.editBox }, [(0, react.createElement)("textarea", {
|
|
1382
1664
|
style: s.editInput,
|
|
1383
1665
|
value: draft,
|
|
@@ -1423,72 +1705,72 @@ window.__ModuleLoader__.load({
|
|
|
1423
1705
|
}, t("delete")) : null
|
|
1424
1706
|
]);
|
|
1425
1707
|
};
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
if (dragName) {
|
|
1433
|
-
e.preventDefault();
|
|
1434
|
-
e.dataTransfer.dropEffect = "move";
|
|
1435
|
-
}
|
|
1436
|
-
},
|
|
1437
|
-
onDrop: (e) => partDropEnd(e, key)
|
|
1438
|
-
}, [
|
|
1439
|
-
(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", null, partNoun(key)), (0, react.createElement)("span", { style: s.orderTag }, `${t("phaseVisible")} ${rows.length}`)]),
|
|
1440
|
-
partNote(key),
|
|
1441
|
-
rows.map((row, i) => renderRow(key, row, i, rows.length)),
|
|
1442
|
-
(0, react.createElement)(InjectForm, {
|
|
1443
|
-
onAdd: (name, text) => addSection(name, text, injectPhaseOf(key)),
|
|
1444
|
-
lockedPhase: injectPhaseOf(key),
|
|
1445
|
-
t
|
|
1446
|
-
})
|
|
1447
|
-
]);
|
|
1448
|
-
};
|
|
1449
|
-
return (0, react.createElement)("div", {
|
|
1450
|
-
style: s.list,
|
|
1451
|
-
ref: rootRef,
|
|
1452
|
-
onDragOver: rootDragOver
|
|
1453
|
-
}, [
|
|
1708
|
+
const rows = rowsOf(phase);
|
|
1709
|
+
const onCount = rows.filter((row) => !row.blocked).length;
|
|
1710
|
+
const offCount = rows.length - onCount;
|
|
1711
|
+
const rowVisible = (row) => filter === "all" || (filter === "on" ? !row.blocked : row.blocked);
|
|
1712
|
+
const poolSections = inv?.sections ?? [];
|
|
1713
|
+
return (0, react.createElement)("div", { style: s.colLeft }, [(0, react.createElement)("div", { style: s.colScroll }, [
|
|
1454
1714
|
notice ? (0, react.createElement)("div", { style: s.noticeWarn }, notice) : null,
|
|
1455
|
-
|
|
1456
|
-
(
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
}
|
|
1715
|
+
partNote(phase),
|
|
1716
|
+
rows.map((row, i) => renderRow(phase, row, i, rows.length)),
|
|
1717
|
+
rows.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
|
|
1718
|
+
addOpen ? (0, react.createElement)(InjectForm, {
|
|
1719
|
+
onAdd: (name, text) => {
|
|
1720
|
+
addSection(name, text, injectPhaseOf(phase));
|
|
1721
|
+
setAddOpen(false);
|
|
1463
1722
|
},
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1723
|
+
phaseLabel: stageLabel(phase),
|
|
1724
|
+
t
|
|
1725
|
+
}) : null,
|
|
1726
|
+
(0, react.createElement)("details", { style: s.injectBox }, [
|
|
1727
|
+
(0, react.createElement)("summary", { style: {
|
|
1728
|
+
...s.muted,
|
|
1729
|
+
cursor: "pointer"
|
|
1730
|
+
} }, `${t("allSectionsTitle")} (${poolSections.length})`),
|
|
1467
1731
|
(0, react.createElement)("div", { style: {
|
|
1468
1732
|
...s.muted,
|
|
1469
1733
|
marginBottom: 4
|
|
1470
1734
|
} }, t("sectionsFourHint")),
|
|
1471
|
-
|
|
1472
|
-
|
|
1735
|
+
poolSections.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
|
|
1736
|
+
poolSections.map((sec) => (0, react.createElement)("div", {
|
|
1473
1737
|
key: sec.name,
|
|
1474
1738
|
style: {
|
|
1475
1739
|
...s.row,
|
|
1476
|
-
cursor: "copy",
|
|
1477
1740
|
opacity: .92
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1741
|
+
}
|
|
1742
|
+
}, [(0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, (0, react.createElement)("span", { style: s.code }, sec.name)), (0, react.createElement)("div", { style: s.preview }, String(sec.text ?? "").slice(0, 140) || t("dynamic"))]), ...PART_ORDER.map((key) => (0, react.createElement)("button", {
|
|
1743
|
+
key,
|
|
1744
|
+
style: s.arrow,
|
|
1745
|
+
title: t("poolAddTitle", { phase: stageLabel(key) }),
|
|
1746
|
+
onClick: () => addFromPool(key, sec.name, sec.text ?? "")
|
|
1747
|
+
}, t(key === "bootstrap" ? "phaseShortGuide" : key === "active" ? "phaseShortResident" : "phaseShortControlled")))]))
|
|
1748
|
+
]),
|
|
1749
|
+
(0, react.createElement)("div", { style: {
|
|
1750
|
+
...s.injectRow,
|
|
1751
|
+
marginTop: 8
|
|
1752
|
+
} }, [(0, react.createElement)("button", {
|
|
1753
|
+
style: s.mini,
|
|
1754
|
+
onClick: () => setAddOpen(!addOpen),
|
|
1755
|
+
title: t("injectNew")
|
|
1756
|
+
}, addOpen ? `× ${t("clearInput")}` : `+ ${t("injectNew")}`)])
|
|
1757
|
+
]), (0, react.createElement)("div", { style: s.colFoot }, [(0, react.createElement)("div", { style: s.seg }, [
|
|
1758
|
+
(0, react.createElement)("button", {
|
|
1759
|
+
style: filter === "all" ? s.segBtnActive : s.segBtn,
|
|
1760
|
+
onClick: () => setFilter("all")
|
|
1761
|
+
}, `${t("filterAll")} ${rows.length}`),
|
|
1762
|
+
(0, react.createElement)("button", {
|
|
1763
|
+
style: filter === "on" ? s.segBtnActive : s.segBtn,
|
|
1764
|
+
onClick: () => setFilter("on")
|
|
1765
|
+
}, `${t("filterOn")} ${onCount}`),
|
|
1766
|
+
(0, react.createElement)("button", {
|
|
1767
|
+
style: filter === "off" ? s.segBtnActive : s.segBtn,
|
|
1768
|
+
onClick: () => setFilter("off")
|
|
1769
|
+
}, `${t("filterOff")} ${offCount}`)
|
|
1770
|
+
])])]);
|
|
1489
1771
|
}
|
|
1490
|
-
/**
|
|
1491
|
-
function InjectForm({ onAdd,
|
|
1772
|
+
/** 注入新段表单:「+ 注入」展开,阶段锁定为当前 Tab 的阶段。 */
|
|
1773
|
+
function InjectForm({ onAdd, phaseLabel, t }) {
|
|
1492
1774
|
const [name, setName] = (0, react.useState)("");
|
|
1493
1775
|
const [text, setText] = (0, react.useState)("");
|
|
1494
1776
|
const submit = () => {
|
|
@@ -1497,12 +1779,11 @@ window.__ModuleLoader__.load({
|
|
|
1497
1779
|
setName("");
|
|
1498
1780
|
setText("");
|
|
1499
1781
|
};
|
|
1500
|
-
const phaseLabel = lockedPhase === "bootstrap" ? t("phaseStageGuide") : lockedPhase === "compaction" ? t("phaseStageControlled") : t("phaseStageResident");
|
|
1501
1782
|
return (0, react.createElement)("div", { style: s.injectRow }, [
|
|
1502
1783
|
(0, react.createElement)("input", {
|
|
1503
1784
|
style: {
|
|
1504
1785
|
...s.input,
|
|
1505
|
-
width: "
|
|
1786
|
+
width: "30%"
|
|
1506
1787
|
},
|
|
1507
1788
|
placeholder: t("name"),
|
|
1508
1789
|
value: name,
|
|
@@ -1524,26 +1805,23 @@ window.__ModuleLoader__.load({
|
|
|
1524
1805
|
]);
|
|
1525
1806
|
}
|
|
1526
1807
|
//#endregion
|
|
1527
|
-
//#region src/client/
|
|
1528
|
-
/**
|
|
1529
|
-
*
|
|
1530
|
-
*
|
|
1531
|
-
*
|
|
1532
|
-
*
|
|
1533
|
-
*
|
|
1534
|
-
*
|
|
1535
|
-
*
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
const [dragFrom, setDragFrom] = (0, react.useState)(null);
|
|
1545
|
-
const [draggedName, setDraggedName] = (0, react.useState)(null);
|
|
1546
|
-
const [dropHint, setDropHint] = (0, react.useState)(null);
|
|
1808
|
+
//#region src/client/ToolsPane.tsx
|
|
1809
|
+
/**
|
|
1810
|
+
* ToolsPane — 独立面板的左栏(工具模式)。
|
|
1811
|
+
*
|
|
1812
|
+
* 顶部三个阶段 Tab(引导期 / 常驻期 / 压缩受控期),与右栏预览的阶段按钮
|
|
1813
|
+
* **联动**。每个阶段列出该阶段进入过滤的目录 ∪ 用户加回的工具:勾选框 =
|
|
1814
|
+
* 该阶段对模型可见;隐藏后仍在列表可反选。三份名单互不继承。
|
|
1815
|
+
*
|
|
1816
|
+
* 底部固定条:三态过滤(全部 / 已启用 = 可见 / 已停用 = 隐藏),只统计当前
|
|
1817
|
+
* 阶段。
|
|
1818
|
+
*
|
|
1819
|
+
* 「本系统全部工具」只读池收在折叠区(不分阶段):每行三个小按钮把该工具
|
|
1820
|
+
* 加入对应阶段(替代旧版拖拽)——被该阶段默认裁掉、但注册表里仍有的会写进
|
|
1821
|
+
* add 名单加回;注册表里根本没有的(别的预设独有)加不进来,界面明确说明。
|
|
1822
|
+
*/
|
|
1823
|
+
function ToolsPane({ cfg, inv, phases, phase, syncAll, t, write }) {
|
|
1824
|
+
const [filter, setFilter] = (0, react.useState)("all");
|
|
1547
1825
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
1548
1826
|
const excludeOf = (key) => {
|
|
1549
1827
|
const tools = cfg.tools ?? {};
|
|
@@ -1556,13 +1834,21 @@ window.__ModuleLoader__.load({
|
|
|
1556
1834
|
const writeLists = (key, exclude, add) => {
|
|
1557
1835
|
write("tools", withPhaseAdd(withPhaseExclude(cfg.tools ?? {}, key, exclude), key, add));
|
|
1558
1836
|
};
|
|
1837
|
+
const writeSynced = (apply) => {
|
|
1838
|
+
let tools = cfg.tools;
|
|
1839
|
+
for (const k of PART_ORDER) {
|
|
1840
|
+
const next = apply(k, excludeOf(k), addOf(k));
|
|
1841
|
+
if (next === null) continue;
|
|
1842
|
+
tools = withPhaseExclude(withPhaseAdd(tools, k, next.add), k, next.exclude);
|
|
1843
|
+
}
|
|
1844
|
+
write("tools", tools);
|
|
1845
|
+
};
|
|
1559
1846
|
const catalogOf = (key) => (phases?.[key]?.baseTools ?? []).map((tool) => tool.name);
|
|
1560
1847
|
const isInCatalog = (key, name) => catalogOf(key).includes(name);
|
|
1561
1848
|
const registry = (() => {
|
|
1562
1849
|
const list = PART_ORDER.map((key) => phases?.[key]?.registryTools).find((x) => Array.isArray(x) && x.length > 0) ?? [];
|
|
1563
1850
|
return new Set(list);
|
|
1564
1851
|
})();
|
|
1565
|
-
const isInRegistry = (name) => registry.has(name);
|
|
1566
1852
|
const addToPhase = (key, name, hidden) => {
|
|
1567
1853
|
let exclude = excludeOf(key);
|
|
1568
1854
|
let add = addOf(key);
|
|
@@ -1571,11 +1857,11 @@ window.__ModuleLoader__.load({
|
|
|
1571
1857
|
writeLists(key, exclude, add);
|
|
1572
1858
|
};
|
|
1573
1859
|
const rowsOf = (key) => {
|
|
1574
|
-
const
|
|
1860
|
+
const exclude = excludeOf(key);
|
|
1575
1861
|
const catalog = (phases?.[key]?.baseTools ?? []).map((tool) => ({
|
|
1576
1862
|
name: tool.name,
|
|
1577
1863
|
description: tool.description ?? "",
|
|
1578
|
-
hidden:
|
|
1864
|
+
hidden: exclude.includes(tool.name),
|
|
1579
1865
|
added: false
|
|
1580
1866
|
}));
|
|
1581
1867
|
const names = new Set(catalog.map((row) => row.name));
|
|
@@ -1589,15 +1875,20 @@ window.__ModuleLoader__.load({
|
|
|
1589
1875
|
};
|
|
1590
1876
|
const toggleHide = (key, name, currentlyHidden) => {
|
|
1591
1877
|
if (syncAll) {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1878
|
+
writeSynced((_k, exclude, add) => {
|
|
1879
|
+
if (currentlyHidden) return {
|
|
1880
|
+
exclude: exclude.filter((x) => x !== name),
|
|
1881
|
+
add
|
|
1882
|
+
};
|
|
1883
|
+
if (add.includes(name)) return {
|
|
1884
|
+
exclude,
|
|
1885
|
+
add: add.filter((x) => x !== name)
|
|
1886
|
+
};
|
|
1887
|
+
return exclude.includes(name) ? null : {
|
|
1888
|
+
exclude: [...exclude, name],
|
|
1889
|
+
add
|
|
1890
|
+
};
|
|
1891
|
+
});
|
|
1601
1892
|
setNotice(null);
|
|
1602
1893
|
return;
|
|
1603
1894
|
}
|
|
@@ -1611,164 +1902,109 @@ window.__ModuleLoader__.load({
|
|
|
1611
1902
|
}
|
|
1612
1903
|
setNotice(null);
|
|
1613
1904
|
};
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
if (!isInCatalog(dest, name) && !isInRegistry(name)) {
|
|
1905
|
+
const addFromPool = (key, name) => {
|
|
1906
|
+
if (!isInCatalog(key, name) && !registry.has(name)) {
|
|
1617
1907
|
setNotice({
|
|
1618
1908
|
kind: "warn",
|
|
1619
1909
|
text: t("toolNotInRegistry", { name })
|
|
1620
1910
|
});
|
|
1621
1911
|
return;
|
|
1622
1912
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1913
|
+
if (syncAll) {
|
|
1914
|
+
writeSynced((k, exclude, add) => ({
|
|
1915
|
+
exclude: exclude.filter((x) => x !== name),
|
|
1916
|
+
add: !isInCatalog(k, name) && !add.includes(name) ? [...add, name] : add
|
|
1917
|
+
}));
|
|
1918
|
+
setNotice({
|
|
1919
|
+
kind: "ok",
|
|
1920
|
+
text: t("toolSyncShown", { name })
|
|
1921
|
+
});
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
addToPhase(key, name, false);
|
|
1626
1925
|
setNotice({
|
|
1627
1926
|
kind: "ok",
|
|
1628
|
-
text:
|
|
1927
|
+
text: isInCatalog(key, name) ? t("toolShown", {
|
|
1629
1928
|
name,
|
|
1630
|
-
|
|
1631
|
-
to: phaseNoun(dest)
|
|
1632
|
-
}) : isInCatalog(dest, name) ? t("toolShown", {
|
|
1633
|
-
name,
|
|
1634
|
-
to: phaseNoun(dest)
|
|
1929
|
+
to: stageLabel(key)
|
|
1635
1930
|
}) : t("toolAdded", {
|
|
1636
1931
|
name,
|
|
1637
|
-
phase:
|
|
1932
|
+
phase: stageLabel(key)
|
|
1638
1933
|
})
|
|
1639
1934
|
});
|
|
1640
1935
|
};
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
writeLists(key, exclude, add);
|
|
1649
|
-
setNotice({
|
|
1650
|
-
kind: "ok",
|
|
1651
|
-
text: t("toolHiddenIn", {
|
|
1652
|
-
name,
|
|
1653
|
-
to: phaseNoun(key)
|
|
1654
|
-
})
|
|
1655
|
-
});
|
|
1656
|
-
};
|
|
1657
|
-
const visibleCount = (key) => {
|
|
1658
|
-
const rows = rowsOf(key);
|
|
1659
|
-
return `${rows.filter((row) => !row.hidden).length} / ${rows.length}`;
|
|
1660
|
-
};
|
|
1661
|
-
const partNoun = (key) => key === "bootstrap" ? t("toolsPartGuide") : key === "compaction" ? t("toolsPartControlled") : t("toolsPartResident");
|
|
1662
|
-
const startDrag = (e, from, name) => {
|
|
1663
|
-
setDragFrom(from);
|
|
1664
|
-
setDraggedName(name);
|
|
1665
|
-
e.dataTransfer.effectAllowed = "copy";
|
|
1666
|
-
try {
|
|
1667
|
-
e.dataTransfer.setData("text/plain", name);
|
|
1668
|
-
} catch {}
|
|
1669
|
-
};
|
|
1670
|
-
const zoneDragOver = (e, zone) => {
|
|
1671
|
-
if (!dragFrom || !draggedName) return;
|
|
1672
|
-
const same = zone === "all" ? dragFrom.kind === "all" : dragFrom.kind === "part" && dragFrom.key === zone.replace("part:", "");
|
|
1673
|
-
if (zone === "all" && dragFrom.kind === "all") {
|
|
1674
|
-
e.preventDefault();
|
|
1675
|
-
return;
|
|
1676
|
-
}
|
|
1677
|
-
if (same) return;
|
|
1678
|
-
e.preventDefault();
|
|
1679
|
-
e.dataTransfer.dropEffect = "copy";
|
|
1680
|
-
setDropHint(zone);
|
|
1681
|
-
};
|
|
1682
|
-
const zoneDrop = (e, zone) => {
|
|
1683
|
-
e.preventDefault();
|
|
1684
|
-
if (dragFrom && draggedName) {
|
|
1685
|
-
if (zone === "all") dropOnAll(draggedName, dragFrom);
|
|
1686
|
-
else {
|
|
1687
|
-
const dest = zone.replace("part:", "");
|
|
1688
|
-
if (PART_ORDER.includes(dest)) dropOnPart(dest, draggedName, dragFrom);
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
setDragFrom(null);
|
|
1692
|
-
setDraggedName(null);
|
|
1693
|
-
setDropHint(null);
|
|
1694
|
-
};
|
|
1695
|
-
const endDrag = () => {
|
|
1696
|
-
setDragFrom(null);
|
|
1697
|
-
setDraggedName(null);
|
|
1698
|
-
setDropHint(null);
|
|
1699
|
-
};
|
|
1700
|
-
const zoneStyle = (zone) => dropHint === zone ? {
|
|
1701
|
-
outline: "2px dashed #7aa7ff",
|
|
1702
|
-
outlineOffset: 2
|
|
1703
|
-
} : void 0;
|
|
1704
|
-
const allTools = (inv?.tools ?? []).map((tool) => ({
|
|
1705
|
-
name: tool.name,
|
|
1706
|
-
description: typeof tool === "string" ? "" : tool.description ?? ""
|
|
1707
|
-
}));
|
|
1708
|
-
const renderPhasePart = (key) => {
|
|
1709
|
-
const rows = rowsOf(key);
|
|
1710
|
-
const zoneKey = `part:${key}`;
|
|
1936
|
+
const stageLabel = (key) => key === "bootstrap" ? t("phaseStageGuide") : key === "compaction" ? t("phaseStageControlled") : t("phaseStageResident");
|
|
1937
|
+
const rows = rowsOf(phase);
|
|
1938
|
+
const onCount = rows.filter((row) => !row.hidden).length;
|
|
1939
|
+
const offCount = rows.length - onCount;
|
|
1940
|
+
const rowVisible = (row) => filter === "all" || (filter === "on" ? !row.hidden : row.hidden);
|
|
1941
|
+
const renderRow = (row) => {
|
|
1942
|
+
if (!rowVisible(row)) return null;
|
|
1711
1943
|
return (0, react.createElement)("div", {
|
|
1712
|
-
key: zoneKey,
|
|
1713
|
-
style: s.injectBox,
|
|
1714
|
-
onDragOver: (e) => zoneDragOver(e, zoneKey),
|
|
1715
|
-
onDrop: (e) => zoneDrop(e, zoneKey),
|
|
1716
|
-
...zoneStyle(zoneKey) ?? {}
|
|
1717
|
-
}, [(0, react.createElement)("div", { style: s.rowTitle }, [
|
|
1718
|
-
(0, react.createElement)("span", null, partNoun(key)),
|
|
1719
|
-
(0, react.createElement)("span", { style: s.orderTag }, `${t("phaseVisible")} ${visibleCount(key)}`),
|
|
1720
|
-
rows.length === 0 ? (0, react.createElement)("span", { style: s.muted }, t("dragHint")) : null
|
|
1721
|
-
]), (0, react.createElement)("div", { style: s.toolWrap }, rows.map((row) => (0, react.createElement)("span", {
|
|
1722
1944
|
key: row.name,
|
|
1723
1945
|
style: {
|
|
1724
|
-
...s.
|
|
1725
|
-
|
|
1726
|
-
display: "inline-flex",
|
|
1727
|
-
alignItems: "center",
|
|
1728
|
-
gap: 4
|
|
1946
|
+
...s.row,
|
|
1947
|
+
...row.hidden ? s.rowBlocked : {}
|
|
1729
1948
|
},
|
|
1730
|
-
title: row.description.slice(0, 120)
|
|
1731
|
-
draggable: true,
|
|
1732
|
-
onDragStart: (e) => startDrag(e, {
|
|
1733
|
-
kind: "part",
|
|
1734
|
-
key
|
|
1735
|
-
}, row.name),
|
|
1736
|
-
onDragEnd: endDrag
|
|
1949
|
+
title: row.description.slice(0, 120)
|
|
1737
1950
|
}, [(0, react.createElement)("input", {
|
|
1738
1951
|
type: "checkbox",
|
|
1739
1952
|
checked: !row.hidden,
|
|
1740
|
-
onChange: () => toggleHide(
|
|
1953
|
+
onChange: () => toggleHide(phase, row.name, row.hidden),
|
|
1954
|
+
title: row.hidden ? t("hiddenOn") : t("hiddenOff"),
|
|
1741
1955
|
style: {
|
|
1742
1956
|
margin: 0,
|
|
1743
|
-
cursor: "pointer"
|
|
1957
|
+
cursor: "pointer",
|
|
1958
|
+
flex: "none"
|
|
1744
1959
|
}
|
|
1745
|
-
}), (0, react.createElement)("
|
|
1960
|
+
}), (0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [
|
|
1961
|
+
(0, react.createElement)("span", { style: s.code }, row.name),
|
|
1962
|
+
row.added ? (0, react.createElement)("span", { style: s.badgeCustom }, t("toolAddedTag")) : null,
|
|
1963
|
+
row.hidden ? (0, react.createElement)("span", { style: s.badgeBlocked }, t("hiddenOn")) : null
|
|
1964
|
+
]), row.description !== "" ? (0, react.createElement)("div", { style: s.preview }, row.description.slice(0, 120)) : null])]);
|
|
1746
1965
|
};
|
|
1747
|
-
|
|
1966
|
+
const allTools = (inv?.tools ?? []).map((tool) => ({
|
|
1967
|
+
name: tool.name,
|
|
1968
|
+
description: typeof tool === "string" ? "" : tool.description ?? ""
|
|
1969
|
+
}));
|
|
1970
|
+
return (0, react.createElement)("div", { style: s.colLeft }, [(0, react.createElement)("div", { style: s.colScroll }, [
|
|
1748
1971
|
(0, react.createElement)("div", { style: s.muted }, t("toolsFourHint")),
|
|
1749
1972
|
notice ? (0, react.createElement)("div", { style: notice.kind === "ok" ? s.noticeOk : s.noticeWarn }, notice.text) : null,
|
|
1750
|
-
|
|
1751
|
-
(0, react.createElement)("div", {
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
(0, react.createElement)("div", { style: s.rowTitle }, [t("allToolsTitle"), (0, react.createElement)("span", { style: s.orderTag }, `${allTools.length}`)]),
|
|
1973
|
+
rows.map(renderRow),
|
|
1974
|
+
rows.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
|
|
1975
|
+
(0, react.createElement)("details", { style: s.injectBox }, [
|
|
1976
|
+
(0, react.createElement)("summary", { style: {
|
|
1977
|
+
...s.muted,
|
|
1978
|
+
cursor: "pointer"
|
|
1979
|
+
} }, `${t("allToolsTitle")} (${allTools.length})`),
|
|
1758
1980
|
allTools.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
|
|
1759
|
-
|
|
1981
|
+
allTools.map((tool) => (0, react.createElement)("div", {
|
|
1760
1982
|
key: tool.name,
|
|
1761
1983
|
style: {
|
|
1762
|
-
...s.
|
|
1763
|
-
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1984
|
+
...s.row,
|
|
1985
|
+
opacity: .92
|
|
1986
|
+
}
|
|
1987
|
+
}, [(0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, (0, react.createElement)("span", { style: s.code }, tool.name)), (0, react.createElement)("div", { style: s.preview }, tool.description.slice(0, 120))]), ...PART_ORDER.map((key) => (0, react.createElement)("button", {
|
|
1988
|
+
key,
|
|
1989
|
+
style: s.arrow,
|
|
1990
|
+
title: t("poolAddTitle", { phase: stageLabel(key) }),
|
|
1991
|
+
onClick: () => addFromPool(key, tool.name)
|
|
1992
|
+
}, t(key === "bootstrap" ? "phaseShortGuide" : key === "active" ? "phaseShortResident" : "phaseShortControlled")))]))
|
|
1770
1993
|
])
|
|
1771
|
-
])
|
|
1994
|
+
]), (0, react.createElement)("div", { style: s.colFoot }, [(0, react.createElement)("div", { style: s.seg }, [
|
|
1995
|
+
(0, react.createElement)("button", {
|
|
1996
|
+
style: filter === "all" ? s.segBtnActive : s.segBtn,
|
|
1997
|
+
onClick: () => setFilter("all")
|
|
1998
|
+
}, `${t("filterAll")} ${rows.length}`),
|
|
1999
|
+
(0, react.createElement)("button", {
|
|
2000
|
+
style: filter === "on" ? s.segBtnActive : s.segBtn,
|
|
2001
|
+
onClick: () => setFilter("on")
|
|
2002
|
+
}, `${t("filterOn")} ${onCount}`),
|
|
2003
|
+
(0, react.createElement)("button", {
|
|
2004
|
+
style: filter === "off" ? s.segBtnActive : s.segBtn,
|
|
2005
|
+
onClick: () => setFilter("off")
|
|
2006
|
+
}, `${t("filterOff")} ${offCount}`)
|
|
2007
|
+
])])]);
|
|
1772
2008
|
}
|
|
1773
2009
|
//#endregion
|
|
1774
2010
|
//#region src/client/preset-io.ts
|
|
@@ -1934,22 +2170,23 @@ window.__ModuleLoader__.load({
|
|
|
1934
2170
|
return { kind: "unavailable" };
|
|
1935
2171
|
}
|
|
1936
2172
|
//#endregion
|
|
1937
|
-
//#region src/client/
|
|
1938
|
-
/**
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2173
|
+
//#region src/client/PresetsPane.tsx
|
|
2174
|
+
/**
|
|
2175
|
+
* PresetsPane — 独立面板的预设模式(左右双栏)。
|
|
2176
|
+
*
|
|
2177
|
+
* 两种「预设」分开,各占一栏:
|
|
2178
|
+
* - 左栏(PresetsPane):配置快照库 —— 保存当前配置 / 导入 / 应用 / 导出 /
|
|
2179
|
+
* 删除。快照只改本插件内的 presets 库,不会变成可选的 agent 预设;
|
|
2180
|
+
* - 右栏(SettingsPane):存为 agent 预设(整体 fork 当前编辑目标的预设目录,
|
|
2181
|
+
* 并把当前定制写进它的覆盖项)+ 全局设置(强制覆盖开关、环境变量黑名单、
|
|
2182
|
+
* 提示词变量参考、恢复初始状态)。
|
|
2183
|
+
*
|
|
2184
|
+
* 导入导出经 preset-io 双端适配:Tauri 2 桌面走原生对话框,Web 走下载 /
|
|
2185
|
+
* 文件选择;成功 / 失败 / 取消都有面板内消息条提示。
|
|
2186
|
+
*/
|
|
2187
|
+
/** 4 秒自动消失的面板内消息条(左右两栏共用行为)。 */
|
|
2188
|
+
function useNotice() {
|
|
1951
2189
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
1952
|
-
const fileRef = (0, react.useRef)(null);
|
|
1953
2190
|
const noticeTimer = (0, react.useRef)(null);
|
|
1954
2191
|
(0, react.useEffect)(() => () => {
|
|
1955
2192
|
if (noticeTimer.current !== null) clearTimeout(noticeTimer.current);
|
|
@@ -1962,6 +2199,16 @@ window.__ModuleLoader__.load({
|
|
|
1962
2199
|
});
|
|
1963
2200
|
noticeTimer.current = setTimeout(() => setNotice(null), 4e3);
|
|
1964
2201
|
};
|
|
2202
|
+
return {
|
|
2203
|
+
notice,
|
|
2204
|
+
show
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function PresetsPane({ cfg, inv, phases, t, writePatch, writeGlobal, envBlocklist }) {
|
|
2208
|
+
const presets = cfg.presets ?? [];
|
|
2209
|
+
const [name, setName] = (0, react.useState)("");
|
|
2210
|
+
const { notice, show } = useNotice();
|
|
2211
|
+
const fileRef = (0, react.useRef)(null);
|
|
1965
2212
|
const blockedNames = new Set(cfg.sections ?? []);
|
|
1966
2213
|
const assemblyNames = /* @__PURE__ */ new Set();
|
|
1967
2214
|
for (const key of PART_ORDER) for (const sec of phases?.[key]?.baseSections ?? []) assemblyNames.add(sec.name);
|
|
@@ -2039,6 +2286,62 @@ window.__ModuleLoader__.load({
|
|
|
2039
2286
|
reader.onerror = () => show("error", t("importFail"));
|
|
2040
2287
|
reader.readAsText(file);
|
|
2041
2288
|
};
|
|
2289
|
+
return (0, react.createElement)("div", { style: s.colLeft }, [(0, react.createElement)("div", { style: s.colScroll }, [
|
|
2290
|
+
notice ? (0, react.createElement)("div", { style: notice.kind === "ok" ? s.noticeOk : s.error }, notice.text) : null,
|
|
2291
|
+
(0, react.createElement)("div", { style: s.groupHead }, t("libraryTitle")),
|
|
2292
|
+
(0, react.createElement)("div", { style: s.injectBox }, [(0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("input", {
|
|
2293
|
+
style: {
|
|
2294
|
+
...s.input,
|
|
2295
|
+
flex: 1
|
|
2296
|
+
},
|
|
2297
|
+
placeholder: t("presetName"),
|
|
2298
|
+
value: name,
|
|
2299
|
+
onChange: (e) => setName(e.target.value)
|
|
2300
|
+
}), (0, react.createElement)("button", {
|
|
2301
|
+
style: s.mini,
|
|
2302
|
+
onClick: saveCurrent
|
|
2303
|
+
}, t("save"))]), (0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("button", {
|
|
2304
|
+
style: s.mini,
|
|
2305
|
+
onClick: () => {
|
|
2306
|
+
importPreset();
|
|
2307
|
+
}
|
|
2308
|
+
}, t("import")), (0, react.createElement)("input", {
|
|
2309
|
+
ref: fileRef,
|
|
2310
|
+
type: "file",
|
|
2311
|
+
accept: ".json,application/json",
|
|
2312
|
+
style: { display: "none" },
|
|
2313
|
+
onChange: onImportFile
|
|
2314
|
+
})])]),
|
|
2315
|
+
presets.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
|
|
2316
|
+
presets.map((preset) => {
|
|
2317
|
+
const active = cfg.activePreset === preset.id;
|
|
2318
|
+
return (0, react.createElement)("div", {
|
|
2319
|
+
key: preset.id,
|
|
2320
|
+
style: s.row
|
|
2321
|
+
}, [
|
|
2322
|
+
(0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.code }, preset.name), active ? (0, react.createElement)("span", { style: s.badgeOk }, t("active")) : null])]),
|
|
2323
|
+
(0, react.createElement)("button", {
|
|
2324
|
+
style: s.mini,
|
|
2325
|
+
onClick: () => applyPreset(preset)
|
|
2326
|
+
}, t("apply")),
|
|
2327
|
+
(0, react.createElement)("button", {
|
|
2328
|
+
style: s.mini,
|
|
2329
|
+
onClick: () => {
|
|
2330
|
+
exportPreset(preset);
|
|
2331
|
+
}
|
|
2332
|
+
}, t("export")),
|
|
2333
|
+
(0, react.createElement)("button", {
|
|
2334
|
+
style: s.mini,
|
|
2335
|
+
onClick: () => deletePreset(preset.id)
|
|
2336
|
+
}, t("delete"))
|
|
2337
|
+
]);
|
|
2338
|
+
})
|
|
2339
|
+
])]);
|
|
2340
|
+
}
|
|
2341
|
+
function SettingsPane({ cfg, inv, t, writeGlobal, saveAsPreset, forkSource, onReset, envBlocklist }) {
|
|
2342
|
+
const [agentName, setAgentName] = (0, react.useState)("");
|
|
2343
|
+
const [creating, setCreating] = (0, react.useState)(false);
|
|
2344
|
+
const [blockInput, setBlockInput] = (0, react.useState)("");
|
|
2042
2345
|
const createAgentPreset = async () => {
|
|
2043
2346
|
const trimmed = agentName.trim();
|
|
2044
2347
|
if (trimmed.length === 0 || creating) return;
|
|
@@ -2055,20 +2358,7 @@ window.__ModuleLoader__.load({
|
|
|
2055
2358
|
if (entry === "" || envBlocklist.includes(entry)) return;
|
|
2056
2359
|
writeGlobal("envBlocklist", [...envBlocklist, entry]);
|
|
2057
2360
|
};
|
|
2058
|
-
return (0, react.createElement)("div", { style: s.
|
|
2059
|
-
notice ? (0, react.createElement)("div", { style: notice.kind === "ok" ? s.noticeOk : s.error }, notice.text) : null,
|
|
2060
|
-
(0, react.createElement)("div", { style: s.injectBox }, [(0, react.createElement)("div", { style: s.rowTitle }, t("savePreset")), (0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("input", {
|
|
2061
|
-
style: {
|
|
2062
|
-
...s.input,
|
|
2063
|
-
flex: 1
|
|
2064
|
-
},
|
|
2065
|
-
placeholder: t("presetName"),
|
|
2066
|
-
value: name,
|
|
2067
|
-
onChange: (e) => setName(e.target.value)
|
|
2068
|
-
}), (0, react.createElement)("button", {
|
|
2069
|
-
style: s.mini,
|
|
2070
|
-
onClick: saveCurrent
|
|
2071
|
-
}, t("save"))])]),
|
|
2361
|
+
return (0, react.createElement)("div", { style: s.colRight }, [(0, react.createElement)("div", { style: s.colRightScroll }, [
|
|
2072
2362
|
(0, react.createElement)("div", { style: s.injectBox }, [
|
|
2073
2363
|
(0, react.createElement)("div", { style: s.rowTitle }, t("saveAsPresetCard")),
|
|
2074
2364
|
(0, react.createElement)("div", { style: s.muted }, t("saveAsPresetHint", { name: forkSource ?? t("forkSourceDefault") })),
|
|
@@ -2088,18 +2378,7 @@ window.__ModuleLoader__.load({
|
|
|
2088
2378
|
}
|
|
2089
2379
|
}, t("saveAsPreset"))])
|
|
2090
2380
|
]),
|
|
2091
|
-
(0, react.createElement)("div", { style: s.
|
|
2092
|
-
style: s.mini,
|
|
2093
|
-
onClick: () => {
|
|
2094
|
-
importPreset();
|
|
2095
|
-
}
|
|
2096
|
-
}, t("import")), (0, react.createElement)("input", {
|
|
2097
|
-
ref: fileRef,
|
|
2098
|
-
type: "file",
|
|
2099
|
-
accept: ".json,application/json",
|
|
2100
|
-
style: { display: "none" },
|
|
2101
|
-
onChange: onImportFile
|
|
2102
|
-
})])]),
|
|
2381
|
+
(0, react.createElement)("div", { style: s.groupHead }, t("settingsTitle")),
|
|
2103
2382
|
(0, react.createElement)("div", { style: s.injectBox }, [
|
|
2104
2383
|
(0, react.createElement)("div", { style: s.rowTitle }, t("forceTitle")),
|
|
2105
2384
|
(0, react.createElement)("div", { style: s.muted }, t("forceHint")),
|
|
@@ -2157,10 +2436,10 @@ window.__ModuleLoader__.load({
|
|
|
2157
2436
|
(0, react.createElement)("div", { style: {
|
|
2158
2437
|
...s.toolWrap,
|
|
2159
2438
|
marginTop: 4
|
|
2160
|
-
} }, (inv?.variables ?? []).map((
|
|
2161
|
-
key:
|
|
2439
|
+
} }, (inv?.variables ?? []).map((varName) => (0, react.createElement)("span", {
|
|
2440
|
+
key: varName,
|
|
2162
2441
|
style: s.toolChip
|
|
2163
|
-
}, `{{${
|
|
2442
|
+
}, `{{${varName}}}`)))
|
|
2164
2443
|
])
|
|
2165
2444
|
]),
|
|
2166
2445
|
(0, react.createElement)("div", { style: s.injectBox }, [
|
|
@@ -2172,32 +2451,8 @@ window.__ModuleLoader__.load({
|
|
|
2172
2451
|
if (window.confirm(t("resetConfirm"))) onReset();
|
|
2173
2452
|
}
|
|
2174
2453
|
}, t("resetAction"))])
|
|
2175
|
-
])
|
|
2176
|
-
|
|
2177
|
-
presets.map((preset) => {
|
|
2178
|
-
const active = cfg.activePreset === preset.id;
|
|
2179
|
-
return (0, react.createElement)("div", {
|
|
2180
|
-
key: preset.id,
|
|
2181
|
-
style: s.row
|
|
2182
|
-
}, [
|
|
2183
|
-
(0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.code }, preset.name), active ? (0, react.createElement)("span", { style: s.badgeOk }, t("active")) : null])]),
|
|
2184
|
-
(0, react.createElement)("button", {
|
|
2185
|
-
style: s.mini,
|
|
2186
|
-
onClick: () => applyPreset(preset)
|
|
2187
|
-
}, t("apply")),
|
|
2188
|
-
(0, react.createElement)("button", {
|
|
2189
|
-
style: s.mini,
|
|
2190
|
-
onClick: () => {
|
|
2191
|
-
exportPreset(preset);
|
|
2192
|
-
}
|
|
2193
|
-
}, t("export")),
|
|
2194
|
-
(0, react.createElement)("button", {
|
|
2195
|
-
style: s.mini,
|
|
2196
|
-
onClick: () => deletePreset(preset.id)
|
|
2197
|
-
}, t("delete"))
|
|
2198
|
-
]);
|
|
2199
|
-
})
|
|
2200
|
-
]);
|
|
2454
|
+
])
|
|
2455
|
+
])]);
|
|
2201
2456
|
}
|
|
2202
2457
|
//#endregion
|
|
2203
2458
|
//#region src/client/PreviewTools.tsx
|
|
@@ -2222,67 +2477,48 @@ window.__ModuleLoader__.load({
|
|
|
2222
2477
|
})]);
|
|
2223
2478
|
}
|
|
2224
2479
|
//#endregion
|
|
2225
|
-
//#region src/client/
|
|
2226
|
-
/**
|
|
2227
|
-
*
|
|
2228
|
-
*
|
|
2229
|
-
*
|
|
2230
|
-
*
|
|
2231
|
-
*
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2480
|
+
//#region src/client/PreviewPane.tsx
|
|
2481
|
+
/**
|
|
2482
|
+
* PreviewPane — 独立面板的右栏(提示词 / 工具模式常驻)。
|
|
2483
|
+
*
|
|
2484
|
+
* 只读预览,没有任何切换控件:看提示词还是工具由顶部模式决定(选「提示词」
|
|
2485
|
+
* = 提示词编辑 + 提示词预览;选「工具」= 工具编辑 + 工具预览),阶段由头部
|
|
2486
|
+
* 的阶段按钮统一控制(与左栏联动)。数据来自 Panel 并行拉取的三阶段装配,
|
|
2487
|
+
* 编辑草稿后由 Panel 防抖叠加同步 —— 所见即模型所见。本视图绝不修改配置。
|
|
2488
|
+
*/
|
|
2489
|
+
function PreviewPane({ t, phases, phase, sub }) {
|
|
2235
2490
|
const data = phases?.[phase] ?? null;
|
|
2236
2491
|
const lossNote = data === null || data === void 0 ? null : data.takenOverBy !== void 0 ? t("sectionsTakenOver", { name: data.takenOverBy }) : data.lostSections !== void 0 ? t("sectionsLost", {
|
|
2237
2492
|
emitted: data.lostSections.emitted,
|
|
2238
2493
|
survived: data.lostSections.survived
|
|
2239
2494
|
}) : null;
|
|
2240
|
-
|
|
2241
|
-
return (0, react.createElement)("div", { style: s.list }, [
|
|
2242
|
-
(0, react.createElement)("div", { style: s.bar }, [
|
|
2243
|
-
(0, react.createElement)("button", {
|
|
2244
|
-
style: sub === "prompt" ? s.tabActive : s.tab,
|
|
2245
|
-
onClick: () => setSub("prompt")
|
|
2246
|
-
}, t("previewPrompt")),
|
|
2247
|
-
(0, react.createElement)("button", {
|
|
2248
|
-
style: sub === "tools" ? s.tabActive : s.tab,
|
|
2249
|
-
onClick: () => setSub("tools")
|
|
2250
|
-
}, t("previewTools")),
|
|
2251
|
-
(0, react.createElement)("div", { style: {
|
|
2252
|
-
marginLeft: "auto",
|
|
2253
|
-
display: "flex",
|
|
2254
|
-
gap: 4
|
|
2255
|
-
} }, PART_ORDER.map((key) => (0, react.createElement)("button", {
|
|
2256
|
-
key,
|
|
2257
|
-
style: phase === key ? s.tabActive : s.tab,
|
|
2258
|
-
onClick: () => setPhase(key)
|
|
2259
|
-
}, stageLabel(key))))
|
|
2260
|
-
]),
|
|
2495
|
+
return (0, react.createElement)("div", { style: s.colRight }, [(0, react.createElement)("div", { style: s.colRightScroll }, [
|
|
2261
2496
|
lossNote !== null ? (0, react.createElement)("div", { style: s.noticeWarn }, lossNote) : null,
|
|
2262
2497
|
data !== null && data.scopeResolved === false ? (0, react.createElement)("div", { style: s.noticeWarn }, t("scopeFallback")) : null,
|
|
2263
|
-
data !== null && data.degraded === true ? (0, react.createElement)("div", { style: s.noticeWarn }, t("previewDegraded")) : null,
|
|
2264
2498
|
phases === null ? (0, react.createElement)("div", { style: s.muted }, t("loading")) : null,
|
|
2265
2499
|
phases !== null && data === null ? (0, react.createElement)("div", { style: s.error }, t("previewFail")) : null,
|
|
2266
2500
|
sub === "prompt" ? data ? [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.muted }, t("previewHint")), (0, react.createElement)("span", { style: s.orderTag }, `${data.sections.length} ${t("previewSections")}`)]), (0, react.createElement)("pre", { style: s.previewText }, data.text || t("empty"))] : null : data ? [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.muted }, t("previewToolsHint")), (0, react.createElement)("span", { style: s.orderTag }, `${data.tools.length} / ${data.registryTotal ?? "?"} ${t("previewToolsCount")}`)]), (0, react.createElement)(PreviewTools, {
|
|
2267
2501
|
tools: data.tools,
|
|
2268
2502
|
t
|
|
2269
2503
|
})] : null
|
|
2270
|
-
]);
|
|
2504
|
+
])]);
|
|
2271
2505
|
}
|
|
2272
2506
|
//#endregion
|
|
2273
|
-
//#region src/client/
|
|
2507
|
+
//#region src/client/Panel.tsx
|
|
2274
2508
|
/**
|
|
2275
|
-
*
|
|
2509
|
+
* Panel — 提示词管理面板(独立抽屉的根组件)。
|
|
2276
2510
|
*
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2511
|
+
* 拉取配置与清单、维护模式与编辑目标状态,把左右两栏装配起来:
|
|
2512
|
+
* - 头部:标题 + 模式切换(提示词 / 工具 / 预设)+ 保存 / 刷新 / 关闭;
|
|
2513
|
+
* - 第二行:agent 预设 Tab(全部 Agent + 各预设,点击即切换编辑目标,
|
|
2514
|
+
* 像宿主能力管理页的预设切换)+ 三态同步 / 中文提示词开关;
|
|
2515
|
+
* - 主体分栏:左栏 = 勾选列表(提示词或工具,底部三态过滤),右栏 =
|
|
2516
|
+
* 最终装配预览;预设模式下左栏是快照库、右栏是全局设置。
|
|
2281
2517
|
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2518
|
+
* 配置来自插件自有 /config 路由;清单与三阶段预览来自 /inventory 与
|
|
2519
|
+
* /preview?phase=…。编辑只改内存草稿,由「保存」按钮经 /config/apply 一次
|
|
2520
|
+
* 写盘(写错的配置不点保存就不会进文件)。
|
|
2284
2521
|
*/
|
|
2285
|
-
const NS = "prompt-customizer";
|
|
2286
2522
|
const INVENTORY_URL = "/api/prompt-customizer/inventory";
|
|
2287
2523
|
const CONFIG_SET_URL = "/api/prompt-customizer/config/set";
|
|
2288
2524
|
const CONFIG_UNSET_URL = "/api/prompt-customizer/config/unset";
|
|
@@ -2296,33 +2532,28 @@ window.__ModuleLoader__.load({
|
|
|
2296
2532
|
"compaction",
|
|
2297
2533
|
"active"
|
|
2298
2534
|
];
|
|
2299
|
-
|
|
2300
|
-
const
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
label: () => t("nav"),
|
|
2310
|
-
locale: NS,
|
|
2311
|
-
inject: () => ({ t })
|
|
2312
|
-
}, () => (0, react.createElement)(Panel, { t })));
|
|
2313
|
-
});
|
|
2314
|
-
}
|
|
2315
|
-
function Panel({ t }) {
|
|
2535
|
+
/** 编辑草稿里可能出现的字段(与服务端 /config/apply 的 APPLY_FIELDS 一致)。 */
|
|
2536
|
+
const DRAFT_FIELDS = [
|
|
2537
|
+
"sections",
|
|
2538
|
+
"sectionsBootstrap",
|
|
2539
|
+
"sectionsCompaction",
|
|
2540
|
+
"replace",
|
|
2541
|
+
"inject",
|
|
2542
|
+
"tools"
|
|
2543
|
+
];
|
|
2544
|
+
function Panel({ t, onClose }) {
|
|
2316
2545
|
const [cfg, setCfg] = (0, react.useState)(null);
|
|
2317
2546
|
const [inv, setInv] = (0, react.useState)(null);
|
|
2318
2547
|
const [phases, setPhases] = (0, react.useState)(null);
|
|
2319
2548
|
const [agentPresets, setAgentPresets] = (0, react.useState)([]);
|
|
2320
|
-
const [
|
|
2549
|
+
const [mode, setMode] = (0, react.useState)("sections");
|
|
2321
2550
|
const [error, setError] = (0, react.useState)(null);
|
|
2322
|
-
const [
|
|
2551
|
+
const [version, setVersion] = (0, react.useState)(0);
|
|
2552
|
+
const [phase, setPhase] = (0, react.useState)("bootstrap");
|
|
2553
|
+
const syncSeq = (0, react.useRef)(0);
|
|
2323
2554
|
const [target, setTarget] = (0, react.useState)(void 0);
|
|
2324
2555
|
const [syncAll, setSyncAll] = (0, react.useState)(false);
|
|
2325
|
-
const [
|
|
2556
|
+
const [previewSub, setPreviewSub] = (0, react.useState)("prompt");
|
|
2326
2557
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
2327
2558
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
2328
2559
|
const [flash, setFlash] = (0, react.useState)(null);
|
|
@@ -2340,7 +2571,7 @@ window.__ModuleLoader__.load({
|
|
|
2340
2571
|
const qs = target ? `?scope=${encodeURIComponent(target)}` : "";
|
|
2341
2572
|
const params = (phase) => `${qs}${qs ? "&" : "?"}phase=${phase}&t=${Date.now()}`;
|
|
2342
2573
|
const grab = (phase) => fetch(PREVIEW_URL + params(phase)).then((r) => r.json()).then((body) => body?.ok === false ? null : body).catch(() => null);
|
|
2343
|
-
Promise.all([...VIEW_KEYS.map(grab), fetch(INVENTORY_URL).then((r) => r.json())]).then(([boot, comp, act, inventoryData]) => {
|
|
2574
|
+
return Promise.all([...VIEW_KEYS.map(grab), fetch(INVENTORY_URL).then((r) => r.json())]).then(([boot, comp, act, inventoryData]) => {
|
|
2344
2575
|
setPhases({
|
|
2345
2576
|
bootstrap: boot,
|
|
2346
2577
|
compaction: comp,
|
|
@@ -2348,23 +2579,69 @@ window.__ModuleLoader__.load({
|
|
|
2348
2579
|
});
|
|
2349
2580
|
setInv(inventoryData);
|
|
2350
2581
|
setError(null);
|
|
2351
|
-
setRefreshId((n) => n + 1);
|
|
2352
2582
|
}).catch((e) => setError(String(e instanceof Error ? e.message : e)));
|
|
2353
2583
|
};
|
|
2354
|
-
(0, react.useEffect)(
|
|
2584
|
+
(0, react.useEffect)(() => {
|
|
2585
|
+
refresh();
|
|
2586
|
+
}, [target, version]);
|
|
2587
|
+
const syncDraftPreview = () => {
|
|
2588
|
+
if (draft === null || !draft.dirty) return;
|
|
2589
|
+
const patch = {};
|
|
2590
|
+
const source = draft;
|
|
2591
|
+
for (const field of DRAFT_FIELDS) if (Object.hasOwn(source, field)) patch[field] = source[field];
|
|
2592
|
+
if (Object.keys(patch).length === 0) return;
|
|
2593
|
+
const seq = ++syncSeq.current;
|
|
2594
|
+
fetch(PREVIEW_URL, {
|
|
2595
|
+
method: "POST",
|
|
2596
|
+
headers: { "content-type": "application/json" },
|
|
2597
|
+
body: JSON.stringify({
|
|
2598
|
+
target,
|
|
2599
|
+
patch,
|
|
2600
|
+
phase
|
|
2601
|
+
})
|
|
2602
|
+
}).then((r) => r.json()).then((body) => {
|
|
2603
|
+
if (body?.ok === false) throw new Error(body?.error ?? "preview failed");
|
|
2604
|
+
if (seq !== syncSeq.current) return;
|
|
2605
|
+
setPhases((prev) => prev === null ? prev : {
|
|
2606
|
+
...prev,
|
|
2607
|
+
[phase]: body
|
|
2608
|
+
});
|
|
2609
|
+
setError(null);
|
|
2610
|
+
}).catch((e) => {
|
|
2611
|
+
setError(`${t("previewSyncFail")}:${e instanceof Error ? e.message : String(e)}`);
|
|
2612
|
+
});
|
|
2613
|
+
};
|
|
2614
|
+
(0, react.useEffect)(() => {
|
|
2615
|
+
if (draft === null || !draft.dirty) return void 0;
|
|
2616
|
+
const timer = setTimeout(syncDraftPreview, 600);
|
|
2617
|
+
return () => {
|
|
2618
|
+
clearTimeout(timer);
|
|
2619
|
+
};
|
|
2620
|
+
}, [
|
|
2621
|
+
draft,
|
|
2622
|
+
phase,
|
|
2623
|
+
target
|
|
2624
|
+
]);
|
|
2355
2625
|
const fetchPresets = () => {
|
|
2356
2626
|
fetch(`/api/prompt-customizer/agent-presets?t=${Date.now()}`).then((r) => r.json()).then((body) => setAgentPresets(Array.isArray(body?.presets) ? body.presets : [])).catch(() => setAgentPresets([]));
|
|
2357
2627
|
};
|
|
2358
2628
|
(0, react.useEffect)(() => {
|
|
2359
2629
|
fetchPresets();
|
|
2360
2630
|
}, []);
|
|
2361
|
-
if (cfg === null) return (0, react.createElement)("div", { style: s.muted }, t("loading"));
|
|
2362
2631
|
const showFlash = (text, kind = "ok") => {
|
|
2363
2632
|
if (flashTimer.current !== null) clearTimeout(flashTimer.current);
|
|
2364
2633
|
setFlash(text);
|
|
2365
2634
|
setFlashKind(kind);
|
|
2366
2635
|
flashTimer.current = setTimeout(() => setFlash(null), 3200);
|
|
2367
2636
|
};
|
|
2637
|
+
const clearDraft = () => {
|
|
2638
|
+
syncSeq.current += 1;
|
|
2639
|
+
setDraft(null);
|
|
2640
|
+
};
|
|
2641
|
+
if (cfg === null) return (0, react.createElement)("div", { style: {
|
|
2642
|
+
...s.pRoot,
|
|
2643
|
+
padding: 16
|
|
2644
|
+
} }, t("loading"));
|
|
2368
2645
|
const base = editView(cfg, target);
|
|
2369
2646
|
const view = draft ? {
|
|
2370
2647
|
...base,
|
|
@@ -2420,7 +2697,7 @@ window.__ModuleLoader__.load({
|
|
|
2420
2697
|
}).then((r) => r.json()).then((body) => {
|
|
2421
2698
|
if (body?.ok !== true) throw new Error(body?.error ?? "save failed");
|
|
2422
2699
|
setCfg(body.config);
|
|
2423
|
-
|
|
2700
|
+
clearDraft();
|
|
2424
2701
|
setError(null);
|
|
2425
2702
|
setVersion((n) => n + 1);
|
|
2426
2703
|
showFlash(t("saveOk"));
|
|
@@ -2442,6 +2719,15 @@ window.__ModuleLoader__.load({
|
|
|
2442
2719
|
}).catch((e) => setError(String(e instanceof Error ? e.message : e)));
|
|
2443
2720
|
};
|
|
2444
2721
|
const writeGlobal = writeField;
|
|
2722
|
+
const zhOn = zhApplied(view, ZH_SECTIONS);
|
|
2723
|
+
const toggleZh = () => {
|
|
2724
|
+
if (phases === null || phases.bootstrap === null || phases.active === null || phases.compaction === null) {
|
|
2725
|
+
showFlash(t("zhNotReady"), "err");
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
edit("inject", zhOn ? zhRevertInjectEntries(view, phases, ZH_SECTIONS) : zhMergedInjectEntries(view, phases, ZH_SECTIONS));
|
|
2729
|
+
showFlash(zhOn ? t("zhReverted") : t("zhApplied"));
|
|
2730
|
+
};
|
|
2445
2731
|
const resetAll = () => {
|
|
2446
2732
|
fetch(CONFIG_RESET_URL, {
|
|
2447
2733
|
method: "POST",
|
|
@@ -2449,22 +2735,23 @@ window.__ModuleLoader__.load({
|
|
|
2449
2735
|
}).then((r) => r.json()).then((body) => {
|
|
2450
2736
|
if (body?.ok !== true) throw new Error(body?.error ?? "reset failed");
|
|
2451
2737
|
setCfg(body.config);
|
|
2452
|
-
|
|
2738
|
+
clearDraft();
|
|
2453
2739
|
setError(null);
|
|
2454
2740
|
setVersion((n) => n + 1);
|
|
2455
2741
|
showFlash(t("resetOk"));
|
|
2456
2742
|
}).catch((e) => setError(`${t("resetFail")}: ${e instanceof Error ? e.message : String(e)}`));
|
|
2457
2743
|
};
|
|
2458
|
-
const
|
|
2459
|
-
if ((
|
|
2744
|
+
const switchMode = (next) => {
|
|
2745
|
+
if ((mode === "sections" || mode === "tools") && next === "presets") {
|
|
2460
2746
|
if (draft?.dirty && !window.confirm(t("discardConfirm"))) return;
|
|
2461
|
-
|
|
2747
|
+
clearDraft();
|
|
2462
2748
|
}
|
|
2463
|
-
|
|
2749
|
+
setMode(next);
|
|
2750
|
+
setPreviewSub(next === "tools" ? "tools" : "prompt");
|
|
2464
2751
|
};
|
|
2465
2752
|
const switchTarget = (next) => {
|
|
2466
2753
|
if (draft?.dirty && !window.confirm(t("discardConfirm"))) return;
|
|
2467
|
-
|
|
2754
|
+
clearDraft();
|
|
2468
2755
|
setTarget(next);
|
|
2469
2756
|
};
|
|
2470
2757
|
const saveAsPreset = (presetName) => {
|
|
@@ -2496,110 +2783,697 @@ window.__ModuleLoader__.load({
|
|
|
2496
2783
|
return false;
|
|
2497
2784
|
});
|
|
2498
2785
|
};
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
}, [
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
},
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2786
|
+
const modeBtn = (key, label) => (0, react.createElement)("button", {
|
|
2787
|
+
key,
|
|
2788
|
+
style: mode === key ? s.segBtnActive : s.segBtn,
|
|
2789
|
+
onClick: () => switchMode(key)
|
|
2790
|
+
}, label);
|
|
2791
|
+
const stageLabel = (key) => key === "bootstrap" ? t("phaseStageGuide") : key === "compaction" ? t("phaseStageControlled") : t("phaseStageResident");
|
|
2792
|
+
const draftDirty = draft?.dirty === true;
|
|
2793
|
+
const targetTab = (id, label, broken) => (0, react.createElement)("button", {
|
|
2794
|
+
key: id ?? "__global__",
|
|
2795
|
+
style: target === id ? s.targetTabActive : s.targetTab,
|
|
2796
|
+
onClick: () => switchTarget(id),
|
|
2797
|
+
title: broken !== void 0 && broken !== "" ? `${label} — ${t("broken")}` : id === void 0 ? t("targetHint") : label
|
|
2798
|
+
}, [label, broken !== void 0 && broken !== "" ? (0, react.createElement)("span", { style: s.badgeBlocked }, t("broken")) : null]);
|
|
2799
|
+
return (0, react.createElement)("div", { style: s.pRoot }, [
|
|
2800
|
+
(0, react.createElement)("div", { style: s.head }, [
|
|
2801
|
+
(0, react.createElement)("span", { style: s.headTitle }, [(0, react.createElement)("svg", {
|
|
2802
|
+
width: 16,
|
|
2803
|
+
height: 16,
|
|
2804
|
+
viewBox: "0 0 24 24",
|
|
2805
|
+
fill: "none",
|
|
2806
|
+
stroke: "currentColor",
|
|
2807
|
+
strokeWidth: 1.8,
|
|
2808
|
+
strokeLinecap: "round",
|
|
2809
|
+
strokeLinejoin: "round",
|
|
2810
|
+
"aria-hidden": "true",
|
|
2811
|
+
style: { flex: "none" }
|
|
2812
|
+
}, [
|
|
2813
|
+
(0, react.createElement)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
2814
|
+
(0, react.createElement)("path", { d: "M14 2v6h6" }),
|
|
2815
|
+
(0, react.createElement)("path", { d: "M16 13H8" }),
|
|
2816
|
+
(0, react.createElement)("path", { d: "M16 17H8" }),
|
|
2817
|
+
(0, react.createElement)("path", { d: "M10 9H8" })
|
|
2818
|
+
]), t("nav")]),
|
|
2819
|
+
(0, react.createElement)("div", { style: s.seg }, [
|
|
2820
|
+
modeBtn("sections", t("tabsSections")),
|
|
2821
|
+
modeBtn("tools", t("tabsTools")),
|
|
2822
|
+
modeBtn("presets", t("tabsPresets"))
|
|
2823
|
+
]),
|
|
2824
|
+
(0, react.createElement)("div", { style: s.headActions }, [
|
|
2825
|
+
mode === "sections" || mode === "tools" ? (0, react.createElement)("div", { style: s.seg }, VIEW_KEYS.map((key) => (0, react.createElement)("button", {
|
|
2826
|
+
key,
|
|
2827
|
+
style: phase === key ? s.segBtnActive : s.segBtn,
|
|
2828
|
+
onClick: () => setPhase(key)
|
|
2829
|
+
}, stageLabel(key)))) : null,
|
|
2830
|
+
draftDirty && (mode === "sections" || mode === "tools") ? (0, react.createElement)("span", {
|
|
2831
|
+
style: s.badgeReplaced,
|
|
2832
|
+
title: t("draftBadge")
|
|
2833
|
+
}, t("draftBadge")) : null,
|
|
2834
|
+
mode === "sections" || mode === "tools" ? (0, react.createElement)("label", {
|
|
2835
|
+
style: {
|
|
2836
|
+
...s.muted,
|
|
2837
|
+
display: "inline-flex",
|
|
2838
|
+
alignItems: "center",
|
|
2839
|
+
gap: 4,
|
|
2840
|
+
cursor: "pointer",
|
|
2841
|
+
whiteSpace: "nowrap"
|
|
2842
|
+
},
|
|
2843
|
+
title: t("syncAllPhasesHint")
|
|
2844
|
+
}, [(0, react.createElement)("input", {
|
|
2845
|
+
type: "checkbox",
|
|
2846
|
+
checked: syncAll,
|
|
2847
|
+
onChange: (e) => setSyncAll(e.target.checked),
|
|
2848
|
+
style: {
|
|
2849
|
+
margin: 0,
|
|
2850
|
+
cursor: "pointer"
|
|
2851
|
+
}
|
|
2852
|
+
}), t("syncAllPhases")]) : null,
|
|
2853
|
+
mode === "sections" ? (0, react.createElement)("label", {
|
|
2854
|
+
style: {
|
|
2855
|
+
...s.muted,
|
|
2856
|
+
display: "inline-flex",
|
|
2857
|
+
alignItems: "center",
|
|
2858
|
+
gap: 4,
|
|
2859
|
+
cursor: "pointer",
|
|
2860
|
+
whiteSpace: "nowrap"
|
|
2861
|
+
},
|
|
2862
|
+
title: t("zhHint")
|
|
2863
|
+
}, [(0, react.createElement)("input", {
|
|
2864
|
+
type: "checkbox",
|
|
2865
|
+
checked: zhOn,
|
|
2866
|
+
onChange: toggleZh,
|
|
2867
|
+
style: {
|
|
2868
|
+
margin: 0,
|
|
2869
|
+
cursor: "pointer"
|
|
2870
|
+
}
|
|
2871
|
+
}), t("zhSwitch")]) : null,
|
|
2872
|
+
(0, react.createElement)("button", {
|
|
2873
|
+
style: draft?.dirty ? s.saveBtnDirty : s.saveBtn,
|
|
2874
|
+
disabled: !draft?.dirty || saving,
|
|
2875
|
+
onClick: save
|
|
2876
|
+
}, t("save")),
|
|
2877
|
+
(0, react.createElement)("button", {
|
|
2878
|
+
style: s.saveBtn,
|
|
2879
|
+
onClick: () => {
|
|
2880
|
+
refresh().then(() => syncDraftPreview());
|
|
2881
|
+
}
|
|
2882
|
+
}, t("refresh")),
|
|
2883
|
+
(0, react.createElement)("button", {
|
|
2884
|
+
style: s.iconBtn,
|
|
2885
|
+
onClick: onClose,
|
|
2886
|
+
"aria-label": t("close"),
|
|
2887
|
+
title: t("close")
|
|
2888
|
+
}, (0, react.createElement)("svg", {
|
|
2889
|
+
width: 15,
|
|
2890
|
+
height: 15,
|
|
2891
|
+
viewBox: "0 0 16 16",
|
|
2892
|
+
fill: "none",
|
|
2893
|
+
"aria-hidden": "true"
|
|
2894
|
+
}, [(0, react.createElement)("path", {
|
|
2895
|
+
d: "M4 4l8 8M12 4l-8 8",
|
|
2896
|
+
stroke: "currentColor",
|
|
2897
|
+
strokeWidth: 1.6,
|
|
2898
|
+
strokeLinecap: "round"
|
|
2899
|
+
})]))
|
|
2900
|
+
])
|
|
2562
2901
|
]),
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2902
|
+
(0, react.createElement)("div", { style: s.subhead }, [(0, react.createElement)("div", { style: s.targetRow }, [targetTab(void 0, t("targetAllTab")), ...agentPresets.map((p) => targetTab(p.id, p.name, p.broken))])]),
|
|
2903
|
+
(0, react.createElement)("div", { style: { padding: "0 14px" } }, [
|
|
2904
|
+
error ? (0, react.createElement)("div", { style: s.error }, String(error)) : null,
|
|
2905
|
+
flash ? (0, react.createElement)("div", { style: flashKind === "err" ? s.error : s.noticeOk }, flash) : null,
|
|
2906
|
+
target && agentPresets.find((p) => p.id === target)?.broken ? (0, react.createElement)("div", { style: s.error }, t("brokenPreset")) : null,
|
|
2907
|
+
inv?.scopeResolved === false && mode !== "presets" ? (0, react.createElement)("div", { style: s.noticeWarn }, t("scopeFallback")) : null
|
|
2908
|
+
]),
|
|
2909
|
+
(0, react.createElement)("div", {
|
|
2910
|
+
key: "body",
|
|
2911
|
+
style: s.body
|
|
2912
|
+
}, mode === "sections" ? [(0, react.createElement)(SectionsPane, {
|
|
2913
|
+
key: "sections",
|
|
2568
2914
|
cfg: view,
|
|
2569
2915
|
inv,
|
|
2570
2916
|
phases,
|
|
2917
|
+
phase,
|
|
2571
2918
|
syncAll,
|
|
2572
2919
|
t,
|
|
2573
2920
|
write: edit
|
|
2574
|
-
})
|
|
2921
|
+
}), (0, react.createElement)(PreviewPane, {
|
|
2922
|
+
key: "preview",
|
|
2923
|
+
t,
|
|
2924
|
+
phases,
|
|
2925
|
+
phase,
|
|
2926
|
+
sub: previewSub
|
|
2927
|
+
})] : mode === "tools" ? [(0, react.createElement)(ToolsPane, {
|
|
2928
|
+
key: "tools",
|
|
2575
2929
|
cfg: view,
|
|
2576
2930
|
inv,
|
|
2577
2931
|
phases,
|
|
2932
|
+
phase,
|
|
2933
|
+
syncAll,
|
|
2934
|
+
t,
|
|
2935
|
+
write: edit
|
|
2936
|
+
}), (0, react.createElement)(PreviewPane, {
|
|
2937
|
+
key: "preview",
|
|
2578
2938
|
t,
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2939
|
+
phases,
|
|
2940
|
+
phase,
|
|
2941
|
+
sub: previewSub
|
|
2942
|
+
})] : [(0, react.createElement)(PresetsPane, {
|
|
2943
|
+
key: "presets",
|
|
2582
2944
|
cfg: view,
|
|
2583
2945
|
inv,
|
|
2584
2946
|
phases,
|
|
2585
2947
|
t,
|
|
2586
2948
|
writePatch,
|
|
2587
2949
|
writeGlobal,
|
|
2950
|
+
envBlocklist: cfg.envBlocklist ?? []
|
|
2951
|
+
}), (0, react.createElement)(SettingsPane, {
|
|
2952
|
+
key: "settings",
|
|
2953
|
+
cfg: view,
|
|
2954
|
+
inv,
|
|
2955
|
+
t,
|
|
2956
|
+
writeGlobal,
|
|
2588
2957
|
saveAsPreset,
|
|
2589
2958
|
forkSource: target ? agentPresets.find((p) => p.id === target)?.name ?? target : void 0,
|
|
2590
2959
|
onReset: resetAll,
|
|
2591
2960
|
envBlocklist: cfg.envBlocklist ?? []
|
|
2592
|
-
})
|
|
2593
|
-
t,
|
|
2594
|
-
active: tab === "preview",
|
|
2595
|
-
refreshId,
|
|
2596
|
-
target,
|
|
2597
|
-
version,
|
|
2598
|
-
phases
|
|
2599
|
-
})
|
|
2961
|
+
})])
|
|
2600
2962
|
]);
|
|
2601
2963
|
}
|
|
2602
2964
|
//#endregion
|
|
2965
|
+
//#region src/client/error-boundary.tsx
|
|
2966
|
+
/**
|
|
2967
|
+
* error-boundary — 把「面板渲染崩溃」关进笼子。
|
|
2968
|
+
*
|
|
2969
|
+
* 侧边栏入口是 createRoot 挂出来的独立 React 根,React 18 对渲染期异常的
|
|
2970
|
+
* 默认处理是卸载整个根——面板里一个组件抛错,导航行按钮会跟着一起消失。
|
|
2971
|
+
* 因此面板单独包一层:崩了只收起面板,导航按钮照常留着,真实错误打到控制台。
|
|
2972
|
+
*/
|
|
2973
|
+
/** 只兜渲染/生命周期异常的错误边界(不兜事件回调与异步)。 */
|
|
2974
|
+
var ErrorBoundary = class extends react.Component {
|
|
2975
|
+
state = { error: null };
|
|
2976
|
+
static getDerivedStateFromError(error) {
|
|
2977
|
+
return { error };
|
|
2978
|
+
}
|
|
2979
|
+
componentDidCatch(error, info) {
|
|
2980
|
+
console.error(`[dsh-prompt-customizer] ${this.props.label} 渲染崩溃:`, error, info.componentStack ?? "");
|
|
2981
|
+
try {
|
|
2982
|
+
this.props.onError?.(error);
|
|
2983
|
+
} catch (callbackError) {
|
|
2984
|
+
console.error("[dsh-prompt-customizer] 错误边界回调失败:", callbackError);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
render() {
|
|
2988
|
+
if (this.state.error !== null) return this.props.fallback ?? null;
|
|
2989
|
+
return this.props.children;
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
//#endregion
|
|
2993
|
+
//#region src/client/nav.tsx
|
|
2994
|
+
/**
|
|
2995
|
+
* nav — 侧边栏导航入口(独立于设置栏的面板入口)。
|
|
2996
|
+
*
|
|
2997
|
+
* 在 sidebar 底部的「设置」行(`[data-slot="sidebar.settings"]`,harness
|
|
2998
|
+
* SidebarRoot 的稳定插槽)正上方插一个 host,host 内放一个 `data-nav-slot`
|
|
2999
|
+
* 槽位容器;入口组件经 `useNavSlot` 轮询拿到槽位后 `createPortal` 进去。
|
|
3000
|
+
* 选底部而不选工作区上方:顶部区域是插件抢座重灾区(任务看板等组件都往
|
|
3001
|
+
* 新会话/工作区一带插),settings 插槽由宿主自有渲染树持有,没人争。
|
|
3002
|
+
*
|
|
3003
|
+
* host 是手工插进 DSH 自有 React 树的裸节点(React 不认识它),因此用
|
|
3004
|
+
* MutationObserver 盯父节点补位 + 低频轮询兜底重挂(HMR、React 重建侧边栏
|
|
3005
|
+
* 时入口不会消失)。
|
|
3006
|
+
*
|
|
3007
|
+
* rail 折叠态由 `useRail` 观察框架容器的 `data-sidebar-collapsed` 属性切换,
|
|
3008
|
+
* 折叠时按钮收缩为图标。
|
|
3009
|
+
*
|
|
3010
|
+
* 结构与 dsh-triad 的 sidebar-nav 同款(各自独立创建 host,互不依赖)。
|
|
3011
|
+
*/
|
|
3012
|
+
/** nav host id(本模块创建)。 */
|
|
3013
|
+
const HOST_ID = "dsh-prompt-customizer-nav-host";
|
|
3014
|
+
/** slots 渲染器的稳定锚点(SidebarRoot 暴露的 `sidebar.settings` slot)。 */
|
|
3015
|
+
const ANCHOR_SELECTOR = "[data-slot=\"sidebar.settings\"]";
|
|
3016
|
+
/** 侧边栏折叠观察:框架容器选择在所有 sidebar 状态(wide/rail)下都唯一。 */
|
|
3017
|
+
const FRAME_SELECTOR = "div:has(> [data-shell-overlay])";
|
|
3018
|
+
/** 建一个槽位容器(portal 目标)。 */
|
|
3019
|
+
function makeSlot(name) {
|
|
3020
|
+
const slot = document.createElement("div");
|
|
3021
|
+
slot.dataset.navSlot = name;
|
|
3022
|
+
return slot;
|
|
3023
|
+
}
|
|
3024
|
+
let started = false;
|
|
3025
|
+
let pollTimer = 0;
|
|
3026
|
+
let hostObserver;
|
|
3027
|
+
/** 确保 host 已创建并插到 `sidebar.settings` slot 之前(幂等)。 */
|
|
3028
|
+
function ensureHostPlaced() {
|
|
3029
|
+
const anchor = document.querySelector(ANCHOR_SELECTOR);
|
|
3030
|
+
if (anchor === null) return false;
|
|
3031
|
+
const parent = anchor.parentElement;
|
|
3032
|
+
if (parent === null) return false;
|
|
3033
|
+
let host = document.getElementById(HOST_ID);
|
|
3034
|
+
if (host === null) {
|
|
3035
|
+
host = document.createElement("div");
|
|
3036
|
+
host.id = HOST_ID;
|
|
3037
|
+
host.dataset.plugin = "dsh-prompt-customizer";
|
|
3038
|
+
host.appendChild(makeSlot("prompt"));
|
|
3039
|
+
}
|
|
3040
|
+
if (!(host.parentElement === parent && (anchor.compareDocumentPosition(host) & Node.DOCUMENT_POSITION_PRECEDING) !== 0)) parent.insertBefore(host, anchor);
|
|
3041
|
+
return true;
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* 盯住宿主的直接父节点,宿主一被摘掉立刻补位。
|
|
3045
|
+
*
|
|
3046
|
+
* host 是手工 insertBefore 进 DSH 自有 React 树的裸节点——侧边栏任何一次
|
|
3047
|
+
* children 重排都可能把它回收掉。只观察父节点的 childList(不开 subtree);
|
|
3048
|
+
* 父节点整体被替换时观察会失联,由轮询兜底重挂。补位后 ensureHostPlaced
|
|
3049
|
+
* 不再改动 DOM,不会自激。
|
|
3050
|
+
*/
|
|
3051
|
+
function watchHostParent() {
|
|
3052
|
+
const parent = document.getElementById(HOST_ID)?.parentElement;
|
|
3053
|
+
if (parent === void 0 || parent === null) return;
|
|
3054
|
+
hostObserver?.disconnect();
|
|
3055
|
+
hostObserver = new MutationObserver(() => {
|
|
3056
|
+
const before = document.getElementById(HOST_ID)?.parentElement;
|
|
3057
|
+
ensureHostPlaced();
|
|
3058
|
+
if (document.getElementById(HOST_ID)?.parentElement !== before) watchHostParent();
|
|
3059
|
+
});
|
|
3060
|
+
hostObserver.observe(parent, { childList: true });
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* 挂载导航区 host(幂等单例)。首次调用者持有清理权(停轮询、移除 host),
|
|
3064
|
+
* 后续调用返回 no-op。
|
|
3065
|
+
*/
|
|
3066
|
+
function ensureNavMount() {
|
|
3067
|
+
if (typeof document === "undefined") return () => {};
|
|
3068
|
+
if (started) return () => {};
|
|
3069
|
+
started = true;
|
|
3070
|
+
ensureHostPlaced();
|
|
3071
|
+
watchHostParent();
|
|
3072
|
+
pollTimer = window.setInterval(() => {
|
|
3073
|
+
ensureHostPlaced();
|
|
3074
|
+
if (hostObserver === void 0) watchHostParent();
|
|
3075
|
+
}, 1500);
|
|
3076
|
+
return () => {
|
|
3077
|
+
window.clearInterval(pollTimer);
|
|
3078
|
+
pollTimer = 0;
|
|
3079
|
+
hostObserver?.disconnect();
|
|
3080
|
+
hostObserver = void 0;
|
|
3081
|
+
started = false;
|
|
3082
|
+
document.getElementById(HOST_ID)?.remove();
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
/** 轮询获取指定槽位容器(未就位时返回 null,组件据此暂不渲染)。
|
|
3086
|
+
*
|
|
3087
|
+
* **永不停止**:未就位时 100ms 阶梯快查(10 次后退 400ms);找到后退化为
|
|
3088
|
+
* 800ms 慢速校验——同一节点 setSlot 被 React 直接跳过,零渲染开销。这样
|
|
3089
|
+
* 槽位一旦被移除/替换(HMR、React 重建 host),portal 会自动迁到新槽。
|
|
3090
|
+
*/
|
|
3091
|
+
function useNavSlot(name) {
|
|
3092
|
+
const [slot, setSlot] = (0, react.useState)(null);
|
|
3093
|
+
(0, react.useEffect)(() => {
|
|
3094
|
+
let timer = 0;
|
|
3095
|
+
let tries = 0;
|
|
3096
|
+
const poll = () => {
|
|
3097
|
+
const found = document.querySelector(`[data-nav-slot='${name}']`);
|
|
3098
|
+
if (found !== null) tries = 0;
|
|
3099
|
+
else tries += 1;
|
|
3100
|
+
setSlot(found);
|
|
3101
|
+
timer = window.setTimeout(poll, found !== null ? 800 : tries <= 10 ? 100 : 400);
|
|
3102
|
+
};
|
|
3103
|
+
poll();
|
|
3104
|
+
return () => {
|
|
3105
|
+
window.clearTimeout(timer);
|
|
3106
|
+
};
|
|
3107
|
+
}, [name]);
|
|
3108
|
+
return slot;
|
|
3109
|
+
}
|
|
3110
|
+
/** 侧边栏折叠态(rail = 只显示图标)。
|
|
3111
|
+
*
|
|
3112
|
+
* 观察 body 子树的 data-sidebar-collapsed(框架容器可能在折叠时被 React
|
|
3113
|
+
* 重挂,盯单节点会失联)+ 低频兜底重读。值不变时 React 自动跳过渲染。
|
|
3114
|
+
*/
|
|
3115
|
+
function useRail() {
|
|
3116
|
+
const [rail, setRail] = (0, react.useState)(() => document.querySelector(FRAME_SELECTOR)?.hasAttribute("data-sidebar-collapsed") ?? false);
|
|
3117
|
+
(0, react.useEffect)(() => {
|
|
3118
|
+
const read = () => {
|
|
3119
|
+
setRail(document.querySelector(FRAME_SELECTOR)?.hasAttribute("data-sidebar-collapsed") ?? false);
|
|
3120
|
+
};
|
|
3121
|
+
read();
|
|
3122
|
+
const observer = new MutationObserver(read);
|
|
3123
|
+
observer.observe(document.body, {
|
|
3124
|
+
attributes: true,
|
|
3125
|
+
attributeFilter: ["data-sidebar-collapsed"],
|
|
3126
|
+
subtree: true
|
|
3127
|
+
});
|
|
3128
|
+
const timer = window.setInterval(read, 1500);
|
|
3129
|
+
return () => {
|
|
3130
|
+
observer.disconnect();
|
|
3131
|
+
window.clearInterval(timer);
|
|
3132
|
+
};
|
|
3133
|
+
}, []);
|
|
3134
|
+
return rail;
|
|
3135
|
+
}
|
|
3136
|
+
const STYLE_ID$1 = "dsh-prompt-customizer-nav-styles";
|
|
3137
|
+
const SHEET$1 = `
|
|
3138
|
+
/* 导航行:与侧边栏原生行同款几何(透明底 + hover 高亮 + 文字省略) */
|
|
3139
|
+
.dsh-pc-nav-btn{position:relative;display:flex;align-items:center;gap:8px;width:calc(100% - 4px);height:34px;padding:0 10px;margin:0 2px 4px;box-sizing:border-box;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,#eee);font-size:14px;line-height:20px;font-family:inherit;cursor:pointer;text-align:left;user-select:none;overflow:hidden;transition:background 120ms ease}
|
|
3140
|
+
.dsh-pc-nav-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}
|
|
3141
|
+
.dsh-pc-nav-btn[data-open='true']{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}
|
|
3142
|
+
.dsh-pc-nav-btn>svg{flex:none;color:var(--dsw-alias-label-secondary,#bbb)}
|
|
3143
|
+
.dsh-pc-nav-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3144
|
+
/* 折叠 rail 态:只留图标 */
|
|
3145
|
+
.dsh-pc-nav-btn[data-rail='true']{width:36px;height:36px;padding:0;margin:0 0 8px;justify-content:center;border-radius:8px}
|
|
3146
|
+
/* nav host:槽位 display:contents,按钮直接撑满整行。 */
|
|
3147
|
+
#dsh-prompt-customizer-nav-host{display:flex;flex-direction:column;align-items:stretch;width:100%}
|
|
3148
|
+
#dsh-prompt-customizer-nav-host>[data-nav-slot]{display:contents}
|
|
3149
|
+
`;
|
|
3150
|
+
/** 注入导航行样式(幂等)。 */
|
|
3151
|
+
function ensureNavStyles() {
|
|
3152
|
+
if (typeof document === "undefined") return;
|
|
3153
|
+
if (document.getElementById(STYLE_ID$1) !== null) return;
|
|
3154
|
+
const tag = document.createElement("style");
|
|
3155
|
+
tag.id = STYLE_ID$1;
|
|
3156
|
+
tag.dataset.plugin = "dsh-prompt-customizer";
|
|
3157
|
+
tag.textContent = SHEET$1;
|
|
3158
|
+
document.head.appendChild(tag);
|
|
3159
|
+
}
|
|
3160
|
+
/** 渲染一条导航行按钮。 */
|
|
3161
|
+
function NavButton({ icon, label, rail = false, expanded = false, ariaLabel, onClick }) {
|
|
3162
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3163
|
+
type: "button",
|
|
3164
|
+
className: "dsh-pc-nav-btn",
|
|
3165
|
+
"data-rail": rail || void 0,
|
|
3166
|
+
"data-open": expanded || void 0,
|
|
3167
|
+
"aria-label": ariaLabel ?? label,
|
|
3168
|
+
"aria-expanded": expanded,
|
|
3169
|
+
title: rail ? ariaLabel ?? label : void 0,
|
|
3170
|
+
onClick,
|
|
3171
|
+
children: [icon, !rail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3172
|
+
className: "dsh-pc-nav-label",
|
|
3173
|
+
children: label
|
|
3174
|
+
})]
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
/** 便捷组合:portal 到指定槽位(slot 未就位时不渲染)。 */
|
|
3178
|
+
function NavPortal({ name, children }) {
|
|
3179
|
+
const slot = useNavSlot(name);
|
|
3180
|
+
if (slot === null) return null;
|
|
3181
|
+
return (0, react_dom.createPortal)(children, slot);
|
|
3182
|
+
}
|
|
3183
|
+
/** 面板互斥 + 切会话自动收(与 dsh-triad 的面板互相礼让)。
|
|
3184
|
+
*
|
|
3185
|
+
* - 互斥:打开时广播自己的事件,并监听本插件与 dsh-triad 的同类广播,
|
|
3186
|
+
* 别的面板打开时自动收回(主区同时只被一个面板占住);
|
|
3187
|
+
* - 切会话自动收:面板盖住会话主区、无遮罩,侧栏保持可点;侧栏会话区内
|
|
3188
|
+
* 的点击(自己导航行与面板本体除外)直接收面板。
|
|
3189
|
+
*/
|
|
3190
|
+
const PANEL_OPEN_EVENT = "dsh-prompt-customizer:panel-open";
|
|
3191
|
+
/** dsh-triad 的面板广播(装了 triad 时互相礼让;detail 永远不等于本插件名)。 */
|
|
3192
|
+
const TRIAD_PANEL_OPEN_EVENT = "dsh-triad:panel-open";
|
|
3193
|
+
/** 点击是否落在侧栏列内(按几何判定,不依赖宿主的哈希类名)。 */
|
|
3194
|
+
function clickInSidebar(target) {
|
|
3195
|
+
let node = target;
|
|
3196
|
+
while (node !== null && node !== document.body) {
|
|
3197
|
+
const rect = node.getBoundingClientRect();
|
|
3198
|
+
if (rect.height >= window.innerHeight * .7 && rect.left <= 8 && rect.right <= window.innerWidth * .6) return true;
|
|
3199
|
+
node = node.parentElement;
|
|
3200
|
+
}
|
|
3201
|
+
return false;
|
|
3202
|
+
}
|
|
3203
|
+
function usePanelAutoClose(open, requestClose) {
|
|
3204
|
+
(0, react.useEffect)(() => {
|
|
3205
|
+
if (!open) return;
|
|
3206
|
+
window.dispatchEvent(new CustomEvent(PANEL_OPEN_EVENT, { detail: "prompt-customizer" }));
|
|
3207
|
+
}, [open]);
|
|
3208
|
+
(0, react.useEffect)(() => {
|
|
3209
|
+
if (!open) return void 0;
|
|
3210
|
+
const onSiblingOpen = (event) => {
|
|
3211
|
+
if (event.detail !== "prompt-customizer") requestClose();
|
|
3212
|
+
};
|
|
3213
|
+
const onDocClick = (event) => {
|
|
3214
|
+
const target = event.target;
|
|
3215
|
+
if (!(target instanceof Element)) return;
|
|
3216
|
+
if (target.closest(`#${HOST_ID}, .pcsh-card`) !== null) return;
|
|
3217
|
+
if (clickInSidebar(target)) requestClose();
|
|
3218
|
+
};
|
|
3219
|
+
window.addEventListener(PANEL_OPEN_EVENT, onSiblingOpen);
|
|
3220
|
+
window.addEventListener(TRIAD_PANEL_OPEN_EVENT, onSiblingOpen);
|
|
3221
|
+
document.addEventListener("click", onDocClick, true);
|
|
3222
|
+
return () => {
|
|
3223
|
+
window.removeEventListener(PANEL_OPEN_EVENT, onSiblingOpen);
|
|
3224
|
+
window.removeEventListener(TRIAD_PANEL_OPEN_EVENT, onSiblingOpen);
|
|
3225
|
+
document.removeEventListener("click", onDocClick, true);
|
|
3226
|
+
};
|
|
3227
|
+
}, [open, requestClose]);
|
|
3228
|
+
}
|
|
3229
|
+
//#endregion
|
|
3230
|
+
//#region src/client/shell.tsx
|
|
3231
|
+
/**
|
|
3232
|
+
* shell — 「覆盖会话主区」的面板外壳(提示词管理面板)。
|
|
3233
|
+
*
|
|
3234
|
+
* 跟点会话一致的行为(与 dsh-triad 的 popover-shell 同款交互,各自独立实现):
|
|
3235
|
+
* - drawer 模式:直接盖住会话主区(侧栏右缘 → 视口右缘,全高,无遮罩),
|
|
3236
|
+
* 自右向左滑入,关闭反向收回;侧栏保持可点,随时切会话(切会话自动收面板);
|
|
3237
|
+
* - 窄视口回退全屏 sheet(上滑入场,带遮罩);
|
|
3238
|
+
* - Esc 关闭走 props.onClose。
|
|
3239
|
+
*
|
|
3240
|
+
* z 层级:mask 999 / card 1000——与宿主 Modal 同层;面板内部 portal 到 body
|
|
3241
|
+
* 的二级弹窗 DOM 顺序更靠后,自然浮于本壳之上。
|
|
3242
|
+
*/
|
|
3243
|
+
const STYLE_ID = "dsh-prompt-customizer-shell-styles";
|
|
3244
|
+
/** 会话主区左缘回退值(px):侧栏实测失败时盖住 280px 右侧全部区域。 */
|
|
3245
|
+
const FALLBACK_MAIN_LEFT = 280;
|
|
3246
|
+
/** 窄屏阈值(px):低于该宽度回退全屏 sheet。 */
|
|
3247
|
+
const NARROW_VP = 768;
|
|
3248
|
+
/** 读会话主区左缘 = 侧栏列右缘(跟随侧栏折叠变化;失败回退 280)。 */
|
|
3249
|
+
function readMainLeft() {
|
|
3250
|
+
try {
|
|
3251
|
+
const host = document.getElementById("dsh-prompt-customizer-nav-host");
|
|
3252
|
+
if (host !== null) {
|
|
3253
|
+
const hostRight = host.getBoundingClientRect().right;
|
|
3254
|
+
let node = host.parentElement;
|
|
3255
|
+
while (node !== null && node !== document.body) {
|
|
3256
|
+
const rect = node.getBoundingClientRect();
|
|
3257
|
+
if (rect.height >= window.innerHeight * .7 && rect.left <= 8 && rect.right >= hostRight - 4) return Math.round(rect.right);
|
|
3258
|
+
node = node.parentElement;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
} catch {}
|
|
3262
|
+
return FALLBACK_MAIN_LEFT;
|
|
3263
|
+
}
|
|
3264
|
+
const SHEET = `
|
|
3265
|
+
@keyframes pc-modal-drawer-in{from{opacity:0;transform:translateX(56px)}to{opacity:1;transform:translateX(0)}}
|
|
3266
|
+
@keyframes pc-modal-drawer-out{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(40px)}}
|
|
3267
|
+
@keyframes pc-modal-slide-in{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}
|
|
3268
|
+
@keyframes pc-modal-slide-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(24px)}}
|
|
3269
|
+
@keyframes pc-modal-mask-in{from{opacity:0}to{opacity:1}}
|
|
3270
|
+
@keyframes pc-modal-mask-out{from{opacity:1}to{opacity:0}}
|
|
3271
|
+
/* ── 遮罩:淡入淡出(仅窄屏 sheet 模式有遮罩) ── */
|
|
3272
|
+
.pcsh-mask{position:fixed;inset:0;z-index:999;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.45))}
|
|
3273
|
+
.pcsh-mask[data-anim='in']{animation:pc-modal-mask-in 240ms ease both}
|
|
3274
|
+
.pcsh-mask[data-anim='out']{animation:pc-modal-mask-out 240ms ease both}
|
|
3275
|
+
/* ── 卡片:会话式右侧抽屉 / 窄屏全屏 sheet 回退 ── */
|
|
3276
|
+
.pcsh-card{position:fixed;z-index:1000;display:flex;flex-direction:column;box-sizing:border-box;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,#16181d));color:var(--dsw-alias-label-primary,#eee);overflow:hidden}
|
|
3277
|
+
.pcsh-card[data-mode='drawer']{top:0;right:0;bottom:0;height:100vh;height:100dvh;border:none;border-left:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.18));box-shadow:none;transition:left 240ms cubic-bezier(.2,.8,.2,1)}
|
|
3278
|
+
/* in 动画不得带 fill-mode(both/forwards 会残留 to 帧 transform,使卡片成为
|
|
3279
|
+
后代 position:fixed 元素的包含块);out 需要 forwards 保持隐藏态直到卸载。 */
|
|
3280
|
+
.pcsh-card[data-mode='drawer'][data-anim='in']{animation:pc-modal-drawer-in 240ms cubic-bezier(.2,.8,.2,1)}
|
|
3281
|
+
.pcsh-card[data-mode='drawer'][data-anim='out']{animation:pc-modal-drawer-out 240ms cubic-bezier(.4,0,.2,1) both}
|
|
3282
|
+
.pcsh-card[data-mode='sheet'][data-anim='in']{animation:pc-modal-slide-in 240ms cubic-bezier(.2,.8,.2,1)}
|
|
3283
|
+
.pcsh-card[data-mode='sheet'][data-anim='out']{animation:pc-modal-slide-out 240ms cubic-bezier(.4,0,.2,1) both}
|
|
3284
|
+
/* ── 窄屏:任何模式强制全屏 sheet(!important 压过内联 left/width)。 ── */
|
|
3285
|
+
@media (max-width: 767.98px){
|
|
3286
|
+
.pcsh-card{
|
|
3287
|
+
left:0 !important;top:0 !important;right:auto !important;bottom:auto !important;
|
|
3288
|
+
width:100vw !important;max-width:100vw !important;
|
|
3289
|
+
height:100vh !important;height:100dvh !important;max-height:100vh !important;max-height:100dvh !important;
|
|
3290
|
+
border-radius:0 !important;transform:none !important;
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
@media (prefers-reduced-motion:reduce){
|
|
3294
|
+
.pcsh-mask,.pcsh-card{animation:none!important}
|
|
3295
|
+
.pcsh-card{transition:none!important}
|
|
3296
|
+
}
|
|
3297
|
+
`;
|
|
3298
|
+
/** 注入外壳样式(幂等)。 */
|
|
3299
|
+
function ensureShellStyles() {
|
|
3300
|
+
if (typeof document === "undefined") return;
|
|
3301
|
+
if (document.getElementById(STYLE_ID) !== null) return;
|
|
3302
|
+
const tag = document.createElement("style");
|
|
3303
|
+
tag.id = STYLE_ID;
|
|
3304
|
+
tag.dataset.plugin = "dsh-prompt-customizer";
|
|
3305
|
+
tag.textContent = SHEET;
|
|
3306
|
+
document.head.appendChild(tag);
|
|
3307
|
+
}
|
|
3308
|
+
/** 渲染覆盖会话主区的抽屉面板(portal 到 body;窄屏回退全屏 sheet + 遮罩)。 */
|
|
3309
|
+
function PanelShell({ closing, onClose, ariaLabel, children }) {
|
|
3310
|
+
const [vw, setVw] = (0, react.useState)(window.innerWidth);
|
|
3311
|
+
const [mainLeft, setMainLeft] = (0, react.useState)(readMainLeft);
|
|
3312
|
+
(0, react.useEffect)(() => {
|
|
3313
|
+
const reread = () => {
|
|
3314
|
+
setVw(window.innerWidth);
|
|
3315
|
+
setMainLeft(readMainLeft());
|
|
3316
|
+
};
|
|
3317
|
+
reread();
|
|
3318
|
+
window.addEventListener("resize", reread);
|
|
3319
|
+
const observer = new MutationObserver(reread);
|
|
3320
|
+
observer.observe(document.body, {
|
|
3321
|
+
attributes: true,
|
|
3322
|
+
attributeFilter: ["data-sidebar-collapsed"],
|
|
3323
|
+
subtree: true
|
|
3324
|
+
});
|
|
3325
|
+
const timer = window.setInterval(reread, 1500);
|
|
3326
|
+
return () => {
|
|
3327
|
+
window.removeEventListener("resize", reread);
|
|
3328
|
+
observer.disconnect();
|
|
3329
|
+
window.clearInterval(timer);
|
|
3330
|
+
};
|
|
3331
|
+
}, []);
|
|
3332
|
+
const anim = closing ? "out" : "in";
|
|
3333
|
+
const narrow = vw < NARROW_VP;
|
|
3334
|
+
const mode = narrow ? "sheet" : "drawer";
|
|
3335
|
+
const style = narrow ? void 0 : { left: mainLeft };
|
|
3336
|
+
(0, react.useEffect)(() => {
|
|
3337
|
+
if (closing) return void 0;
|
|
3338
|
+
const onKey = (event) => {
|
|
3339
|
+
if (event.key === "Escape") onClose();
|
|
3340
|
+
};
|
|
3341
|
+
document.addEventListener("keydown", onKey);
|
|
3342
|
+
return () => {
|
|
3343
|
+
document.removeEventListener("keydown", onKey);
|
|
3344
|
+
};
|
|
3345
|
+
}, [closing, onClose]);
|
|
3346
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [narrow && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3347
|
+
className: "pcsh-mask",
|
|
3348
|
+
"data-anim": anim,
|
|
3349
|
+
"aria-hidden": "true",
|
|
3350
|
+
onClick: onClose
|
|
3351
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3352
|
+
className: "pcsh-card",
|
|
3353
|
+
"data-anim": anim,
|
|
3354
|
+
"data-mode": mode,
|
|
3355
|
+
style,
|
|
3356
|
+
role: "dialog",
|
|
3357
|
+
"aria-modal": "true",
|
|
3358
|
+
"aria-label": ariaLabel,
|
|
3359
|
+
children
|
|
3360
|
+
})] }), document.body);
|
|
3361
|
+
}
|
|
3362
|
+
/**
|
|
3363
|
+
* 面板关闭动画状态机:先置 closing 播放收回动画,结束后再真正 onClose。
|
|
3364
|
+
* `open` 再次变 true 时重置 closing(否则上一次收回动画会把 closing 卡在 true)。
|
|
3365
|
+
*/
|
|
3366
|
+
function usePanelClose(open, onClose, durationMs = 240) {
|
|
3367
|
+
const [closing, setClosing] = (0, react.useState)(false);
|
|
3368
|
+
(0, react.useEffect)(() => {
|
|
3369
|
+
if (open) setClosing(false);
|
|
3370
|
+
}, [open]);
|
|
3371
|
+
(0, react.useEffect)(() => {
|
|
3372
|
+
if (!closing) return void 0;
|
|
3373
|
+
const timer = window.setTimeout(() => onClose(), durationMs);
|
|
3374
|
+
return () => {
|
|
3375
|
+
window.clearTimeout(timer);
|
|
3376
|
+
};
|
|
3377
|
+
}, [
|
|
3378
|
+
closing,
|
|
3379
|
+
onClose,
|
|
3380
|
+
durationMs
|
|
3381
|
+
]);
|
|
3382
|
+
const requestClose = () => setClosing(true);
|
|
3383
|
+
return {
|
|
3384
|
+
closing,
|
|
3385
|
+
requestClose
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
//#endregion
|
|
3389
|
+
//#region src/client/index.ts
|
|
3390
|
+
/**
|
|
3391
|
+
* dsh-prompt-customizer — 浏览器端(TypeScript 源码)。
|
|
3392
|
+
*
|
|
3393
|
+
* 在侧边栏注册一个「提示词」入口按钮(不再挤在设置栏里),点击打开覆盖
|
|
3394
|
+
* 会话主区的「提示词管理」面板:左栏勾选提示词 / 工具,右栏实时预览最终
|
|
3395
|
+
* 装配;顶部 Tab 切换 agent 预设(编辑目标)。面板从宿主的
|
|
3396
|
+
* `/api/prompt-customizer/inventory` 拉取当前生效的段 / 工具清单,屏蔽、
|
|
3397
|
+
* 替换、注入提示词段并隐藏工具;所有写入走插件自有路由(宿主端的
|
|
3398
|
+
* config.yaml),保存按钮一次落盘。
|
|
3399
|
+
*
|
|
3400
|
+
* 由 tsdown 打包为 client/client.js(__ModuleLoader__ factory bundle);
|
|
3401
|
+
* 外部依赖只有 loader 模块表中的 react 入口。
|
|
3402
|
+
*/
|
|
3403
|
+
const NS = "prompt-customizer";
|
|
3404
|
+
const name = "prompt-customizer";
|
|
3405
|
+
const inject = ["locale"];
|
|
3406
|
+
/** 提示词入口:导航行按钮 + 点击打开覆盖主区的面板。 */
|
|
3407
|
+
function PromptEntry({ t }) {
|
|
3408
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
3409
|
+
const { closing, requestClose } = usePanelClose(open, () => {
|
|
3410
|
+
setOpen(false);
|
|
3411
|
+
});
|
|
3412
|
+
const rail = useRail();
|
|
3413
|
+
usePanelAutoClose(open, requestClose);
|
|
3414
|
+
return (0, react.createElement)("div", null, [(0, react.createElement)(NavButton, {
|
|
3415
|
+
icon: (0, react.createElement)("svg", {
|
|
3416
|
+
width: rail ? 18 : 16,
|
|
3417
|
+
height: rail ? 18 : 16,
|
|
3418
|
+
viewBox: "0 0 24 24",
|
|
3419
|
+
fill: "none",
|
|
3420
|
+
stroke: "currentColor",
|
|
3421
|
+
strokeWidth: 1.8,
|
|
3422
|
+
strokeLinecap: "round",
|
|
3423
|
+
strokeLinejoin: "round",
|
|
3424
|
+
"aria-hidden": "true"
|
|
3425
|
+
}, [
|
|
3426
|
+
(0, react.createElement)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
3427
|
+
(0, react.createElement)("path", { d: "M14 2v6h6" }),
|
|
3428
|
+
(0, react.createElement)("path", { d: "M16 13H8" }),
|
|
3429
|
+
(0, react.createElement)("path", { d: "M16 17H8" }),
|
|
3430
|
+
(0, react.createElement)("path", { d: "M10 9H8" })
|
|
3431
|
+
]),
|
|
3432
|
+
label: t("navShort"),
|
|
3433
|
+
rail,
|
|
3434
|
+
expanded: open,
|
|
3435
|
+
onClick: () => {
|
|
3436
|
+
setOpen(true);
|
|
3437
|
+
}
|
|
3438
|
+
}), open ? (0, react.createElement)(ErrorBoundary, {
|
|
3439
|
+
key: "panel",
|
|
3440
|
+
label: "提示词管理面板",
|
|
3441
|
+
fallback: null,
|
|
3442
|
+
onError: () => requestClose(),
|
|
3443
|
+
children: (0, react.createElement)(PanelShell, {
|
|
3444
|
+
closing,
|
|
3445
|
+
onClose: requestClose,
|
|
3446
|
+
ariaLabel: t("nav"),
|
|
3447
|
+
children: (0, react.createElement)(Panel, {
|
|
3448
|
+
t,
|
|
3449
|
+
onClose: requestClose
|
|
3450
|
+
})
|
|
3451
|
+
})
|
|
3452
|
+
}) : null]);
|
|
3453
|
+
}
|
|
3454
|
+
/** 导航行应用:入口 portal 到自建 nav host 的槽位。 */
|
|
3455
|
+
function PromptNavApp({ t }) {
|
|
3456
|
+
return (0, react.createElement)("div", null, (0, react.createElement)(NavPortal, {
|
|
3457
|
+
name: "prompt",
|
|
3458
|
+
children: (0, react.createElement)(PromptEntry, { t })
|
|
3459
|
+
}));
|
|
3460
|
+
}
|
|
3461
|
+
function apply(ctx) {
|
|
3462
|
+
ctx.effect(() => ctx.locale.register(NS, DICT), "prompt-customizer: locale");
|
|
3463
|
+
const t = ctx.locale.bind(NS);
|
|
3464
|
+
ctx.effect(() => {
|
|
3465
|
+
ensureNavMount();
|
|
3466
|
+
ensureNavStyles();
|
|
3467
|
+
ensureShellStyles();
|
|
3468
|
+
const holder = document.createElement("div");
|
|
3469
|
+
const root = (0, react_dom_client.createRoot)(holder);
|
|
3470
|
+
root.render((0, react.createElement)(PromptNavApp, { t }));
|
|
3471
|
+
return () => {
|
|
3472
|
+
root.unmount();
|
|
3473
|
+
};
|
|
3474
|
+
}, "prompt-customizer: sidebar nav entry");
|
|
3475
|
+
}
|
|
3476
|
+
//#endregion
|
|
2603
3477
|
exports.apply = apply;
|
|
2604
3478
|
exports.inject = inject;
|
|
2605
3479
|
exports.name = name;
|