dsh-prompt-customizer 0.2.3 → 0.2.4

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/client/client.js CHANGED
@@ -19,8 +19,10 @@ window.__ModuleLoader__.load({
19
19
  blockedOff: "未屏蔽",
20
20
  blockedOn: "已屏蔽",
21
21
  replaced: "已替换",
22
- replace: "替换",
23
- clearReplace: "清除替换",
22
+ manual: "手动",
23
+ system: "系统",
24
+ replace: "编辑",
25
+ clearReplace: "清除",
24
26
  injectNew: "注入新段",
25
27
  name: "名称",
26
28
  order: "顺序",
@@ -57,6 +59,8 @@ window.__ModuleLoader__.load({
57
59
  blockedOff: "Active",
58
60
  blockedOn: "Blocked",
59
61
  replaced: "Replaced",
62
+ manual: "Manual",
63
+ system: "System",
60
64
  replace: "Replace",
61
65
  clearReplace: "Clear",
62
66
  injectNew: "Inject section",
@@ -196,6 +200,20 @@ window.__ModuleLoader__.load({
196
200
  borderRadius: 999,
197
201
  padding: "0 6px"
198
202
  },
203
+ badgeCustom: {
204
+ color: "#58a6ff",
205
+ fontSize: 11,
206
+ border: "1px solid rgba(88,166,255,.5)",
207
+ borderRadius: 999,
208
+ padding: "0 6px"
209
+ },
210
+ badgeSystem: {
211
+ color: "rgba(128,128,128,.85)",
212
+ fontSize: 11,
213
+ border: "1px solid rgba(128,128,128,.4)",
214
+ borderRadius: 999,
215
+ padding: "0 6px"
216
+ },
199
217
  mini: {
200
218
  padding: "3px 8px",
201
219
  border: "1px solid rgba(128,128,128,.35)",
@@ -282,22 +300,41 @@ window.__ModuleLoader__.load({
282
300
  })();
283
301
  //#endregion
284
302
  //#region src/client/presets.ts
285
- /** Merge the inventory sections with the user's inject list, sorted by order. */
303
+ /**
304
+ * Merge the inventory sections with the user's inject list, sorted by order.
305
+ * Sections read from the inventory (other plugins) are `system`; sections this
306
+ * plugin generated carry a hidden `custom` marker and are `custom`. The source
307
+ * is decided by the hidden marker — never by name collisions — so a custom
308
+ * section keeps its identity (and its deletability) even when its name happens
309
+ * to match an inventory section, and across preset switches. Custom sections
310
+ * always render their own text; they can never be "<动态生成>".
311
+ */
286
312
  function mergeSections(inv, cfg, blockedNames) {
287
313
  const map = /* @__PURE__ */ new Map();
288
- for (const sec of inv?.sections ?? []) map.set(sec.name, sec);
314
+ for (const sec of inv?.sections ?? []) map.set(sec.name, {
315
+ ...sec,
316
+ source: "system"
317
+ });
289
318
  for (const item of cfg.inject ?? []) {
319
+ const isCustom = item.custom === true;
290
320
  const existing = map.get(item.name);
291
- if (existing) map.set(item.name, {
321
+ if (existing) map.set(item.name, isCustom ? {
292
322
  ...existing,
293
- order: item.order
323
+ order: item.order,
324
+ text: item.text ?? "",
325
+ source: "custom"
326
+ } : {
327
+ ...existing,
328
+ order: item.order,
329
+ source: "system"
294
330
  });
295
331
  else map.set(item.name, {
296
332
  name: item.name,
297
333
  order: item.order,
298
- text: item.text || "<动态生成>",
334
+ text: item.text ?? "",
299
335
  active: !blockedNames.has(item.name),
300
- replaced: false
336
+ replaced: false,
337
+ source: isCustom ? "custom" : "system"
301
338
  });
302
339
  }
303
340
  return [...map.values()].sort((a, b) => a.order - b.order);
@@ -312,9 +349,11 @@ window.__ModuleLoader__.load({
312
349
  const list = presetOrder ?? [];
313
350
  const afterMap = /* @__PURE__ */ new Map();
314
351
  const textMap = /* @__PURE__ */ new Map();
352
+ const customMap = /* @__PURE__ */ new Map();
315
353
  for (const sec of list) {
316
354
  afterMap.set(sec.name, sec.after);
317
355
  textMap.set(sec.name, sec.text);
356
+ customMap.set(sec.name, sec.custom === true);
318
357
  }
319
358
  const result = [];
320
359
  const placed = /* @__PURE__ */ new Set();
@@ -345,7 +384,8 @@ window.__ModuleLoader__.load({
345
384
  return result.map((name, i) => ({
346
385
  name,
347
386
  order: i,
348
- text: textMap.get(name) ?? ""
387
+ text: textMap.get(name) ?? "",
388
+ custom: customMap.get(name)
349
389
  }));
350
390
  }
351
391
  /**
@@ -358,7 +398,8 @@ window.__ModuleLoader__.load({
358
398
  const order = merged.map((sec, i) => ({
359
399
  name: sec.name,
360
400
  after: i > 0 ? merged[i - 1].name : void 0,
361
- text: sec.text ?? ""
401
+ text: sec.source === "custom" ? sec.text ?? "" : "",
402
+ custom: sec.source === "custom"
362
403
  }));
363
404
  return {
364
405
  sections: cfg.sections,
@@ -374,11 +415,29 @@ window.__ModuleLoader__.load({
374
415
  * - current sections NOT in the preset's ordered list are disabled by default
375
416
  * - only the preset's ACTIVE sections (in the order list but not in its block
376
417
  * list) are unblocked; the preset's own blocked sections stay blocked.
418
+ *
419
+ * Custom injected sections that exist in the current config but are NOT in the
420
+ * preset's ordered list are preserved in the resulting inject list (so they
421
+ * stay visible) and disabled (added to the blocked set), rather than silently
422
+ * dropped — a preset must not make the user's own injected sections vanish.
377
423
  */
378
424
  function applyPresetData(data, cfg, currentNames) {
379
425
  const presetOrder = data.order ?? [];
380
426
  const presetNames = new Set(presetOrder.map((x) => x.name));
381
427
  const blocked = new Set(data.sections ?? []);
428
+ const inject = resolveOrder(presetOrder);
429
+ const kept = new Set(inject.map((x) => x.name));
430
+ let order = inject.length;
431
+ for (const item of cfg.inject ?? []) {
432
+ if (!item || !item.name || presetNames.has(item.name) || kept.has(item.name)) continue;
433
+ kept.add(item.name);
434
+ inject.push({
435
+ name: item.name,
436
+ order: order++,
437
+ text: item.text ?? "",
438
+ custom: item.custom === true
439
+ });
440
+ }
382
441
  if (presetOrder.length > 0) {
383
442
  const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)));
384
443
  for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name);
@@ -390,13 +449,30 @@ window.__ModuleLoader__.load({
390
449
  ...cfg.replace ?? {},
391
450
  ...data.replace ?? {}
392
451
  },
393
- inject: resolveOrder(presetOrder),
452
+ inject,
394
453
  tools: {
395
454
  exclude: data.tools?.exclude ?? [],
396
455
  include: data.tools?.include ?? []
397
456
  }
398
457
  };
399
458
  }
459
+ /**
460
+ * Remove a custom (plugin-added) section from the config: it disappears from
461
+ * the inject list, is no longer forced into the blocked set, and any replace
462
+ * text for it is cleared. System sections read from the host inventory cannot
463
+ * be removed this way (they would just come back on the next inventory read).
464
+ */
465
+ function removeSection(name, cfg) {
466
+ const sections = (cfg.sections ?? []).filter((n) => n !== name);
467
+ const inject = (cfg.inject ?? []).filter((item) => item.name !== name);
468
+ const replace = { ...cfg.replace ?? {} };
469
+ delete replace[name];
470
+ return {
471
+ sections,
472
+ inject,
473
+ replace
474
+ };
475
+ }
400
476
  /** Whether a tool is hidden under the current include/exclude config. */
401
477
  function isToolHidden(name, toolsCfg) {
402
478
  const include = toolsCfg?.include ?? [];
@@ -513,14 +589,22 @@ window.__ModuleLoader__.load({
513
589
  const persistOrder = (ordered) => {
514
590
  const list = ordered.map((sec, i) => {
515
591
  const existing = (cfg.inject ?? []).find((x) => x.name === sec.name);
592
+ const isCustom = sec.source === "custom";
516
593
  return {
517
594
  name: sec.name,
518
595
  order: i,
519
- text: existing ? existing.text : ""
596
+ text: isCustom ? sec.text ?? "" : existing?.text ?? "",
597
+ custom: isCustom
520
598
  };
521
599
  });
522
600
  scope.set("inject", list);
523
601
  };
602
+ const removeCustom = (name) => {
603
+ const patch = removeSection(name, cfg);
604
+ scope.set("sections", patch.sections);
605
+ scope.set("inject", patch.inject);
606
+ scope.set("replace", patch.replace);
607
+ };
524
608
  const moveOrder = (index, dir) => {
525
609
  const target = index + dir;
526
610
  if (target < 0 || target >= merged.length) return;
@@ -577,9 +661,10 @@ window.__ModuleLoader__.load({
577
661
  const entry = {
578
662
  name,
579
663
  order,
580
- text: text || "<动态生成>",
664
+ text,
581
665
  active: true,
582
- replaced: false
666
+ replaced: false,
667
+ source: "custom"
583
668
  };
584
669
  const existing = next.findIndex((x) => x.name === name);
585
670
  if (existing >= 0) next[existing] = entry;
@@ -624,6 +709,7 @@ window.__ModuleLoader__.load({
624
709
  (0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [
625
710
  (0, react.createElement)("span", { style: s.code }, sec.name),
626
711
  (0, react.createElement)("span", { style: s.orderTag }, "#" + index),
712
+ (0, react.createElement)("span", { style: sec.source === "custom" ? s.badgeCustom : s.badgeSystem }, sec.source === "custom" ? t("manual") : t("system")),
627
713
  sec.replaced ? (0, react.createElement)("span", { style: s.badgeReplaced }, t("replaced")) : null
628
714
  ]), isEditing ? (0, react.createElement)("div", { style: s.editBox }, [(0, react.createElement)("textarea", {
629
715
  style: s.editInput,
@@ -636,7 +722,7 @@ window.__ModuleLoader__.load({
636
722
  }, t("add")), (0, react.createElement)("button", {
637
723
  style: s.mini,
638
724
  onClick: () => setEditing(null)
639
- }, t("clearReplace"))])]) : (0, react.createElement)("div", { style: s.preview }, String(sec.text ?? "").slice(0, 140) || t("dynamic"))]),
725
+ }, t("clearReplace"))])]) : (0, react.createElement)("div", { style: s.preview }, String(sec.text ?? "").slice(0, 140) || (sec.source === "custom" ? t("empty") : t("dynamic")))]),
640
726
  (0, react.createElement)("div", { style: s.arrowCol }, [(0, react.createElement)("button", {
641
727
  style: s.arrow,
642
728
  disabled: index === 0,
@@ -651,7 +737,12 @@ window.__ModuleLoader__.load({
651
737
  (0, react.createElement)("button", {
652
738
  style: s.mini,
653
739
  onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? ""))
654
- }, replace[sec.name] ? t("clearReplace") : t("replace"))
740
+ }, replace[sec.name] ? t("clearReplace") : t("replace")),
741
+ sec.source === "custom" ? (0, react.createElement)("button", {
742
+ style: s.mini,
743
+ onClick: () => removeCustom(sec.name),
744
+ title: t("delete")
745
+ }, t("delete")) : null
655
746
  ]);
656
747
  }),
657
748
  (0, react.createElement)("div", { style: s.injectBox }, [(0, react.createElement)("div", { style: s.rowTitle }, t("injectNew")), (0, react.createElement)(InjectForm, {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useState","h","h","useState","useRef","h","h","useState"],"sources":["../src/client/locales.ts","../src/client/styles.ts","../src/client/presets.ts","../src/client/SectionsTab.tsx","../src/client/ToolsTab.tsx","../src/client/PresetsTab.tsx","../src/client/index.ts"],"sourcesContent":["/** Locale dictionaries for the prompt-customizer panel. */\nexport const DICT = {\n zh: {\n nav: '提示词定制',\n loading: '加载中…',\n unavailable: '当前环境未启用本命名空间。',\n tabsSections: '提示词',\n tabsTools: '工具',\n tabsPresets: '预设',\n refresh: '刷新',\n blockedOff: '未屏蔽',\n blockedOn: '已屏蔽',\n replaced: '已替换',\n replace: '替换',\n clearReplace: '清除替换',\n injectNew: '注入新段',\n name: '名称',\n order: '顺序',\n text: '文本',\n add: '添加',\n hiddenOff: '可见',\n hiddenOn: '已隐藏',\n includeMode: '白名单模式:仅保留勾选项',\n excludeMode: '黑名单模式:勾选 = 隐藏',\n empty: '(空)',\n dynamic: '<动态生成>',\n drag: '拖动排序',\n moveUp: '上移',\n moveDown: '下移',\n savePreset: '保存当前为预设',\n presetName: '预设名称',\n save: '保存',\n importPreset: '导入预设',\n import: '导入',\n apply: '应用',\n export: '导出',\n delete: '删除',\n active: '使用中',\n preset: '预设',\n },\n en: {\n nav: 'Prompt Customizer',\n loading: 'Loading…',\n unavailable: 'Namespace unavailable in this environment.',\n tabsSections: 'Sections',\n tabsTools: 'Tools',\n tabsPresets: 'Presets',\n refresh: 'Refresh',\n blockedOff: 'Active',\n blockedOn: 'Blocked',\n replaced: 'Replaced',\n replace: 'Replace',\n clearReplace: 'Clear',\n injectNew: 'Inject section',\n name: 'Name',\n order: 'Order',\n text: 'Text',\n add: 'Add',\n hiddenOff: 'Visible',\n hiddenOn: 'Hidden',\n includeMode: 'Whitelist: keep only checked',\n excludeMode: 'Blacklist: check to hide',\n empty: '(empty)',\n dynamic: '<dynamic>',\n drag: 'Drag to reorder',\n moveUp: 'Move up',\n moveDown: 'Move down',\n savePreset: 'Save current as preset',\n presetName: 'Preset name',\n save: 'Save',\n importPreset: 'Import preset',\n import: 'Import',\n apply: 'Apply',\n export: 'Export',\n delete: 'Delete',\n active: 'Active',\n preset: 'Preset',\n },\n} as const\n\nexport type Translate = (key: string) => string\n","/** Inline styles shared by the prompt-customizer panel components. */\nexport const s = (() => {\n const flex = { display: 'flex', alignItems: 'center' }\n return {\n root: { fontFamily: 'inherit', fontSize: 13, lineHeight: 1.5 },\n bar: { ...flex, gap: 6, marginBottom: 8, flexWrap: 'wrap' },\n tab: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', opacity: 0.75 },\n tabActive: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.7)', borderRadius: 6, background: 'rgba(128,128,128,.18)', cursor: 'pointer', color: 'inherit' },\n refresh: { marginLeft: 'auto', padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit' },\n list: { display: 'flex', flexDirection: 'column', gap: 6 },\n row: { ...flex, gap: 8, padding: '6px 8px', border: '1px solid rgba(128,128,128,.2)', borderRadius: 8, background: 'rgba(128,128,128,.06)' },\n switchWrap: { ...flex, gap: 6, minWidth: 90 },\n rowBody: { flex: 1, minWidth: 0 },\n rowTitle: { ...flex, gap: 6, alignItems: 'baseline', flexWrap: 'wrap' },\n code: { fontFamily: 'monospace', fontSize: 12, wordBreak: 'break-all' },\n orderTag: { color: 'rgba(128,128,128,.8)', fontSize: 11 },\n preview: { color: 'rgba(128,128,128,.85)', fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n badgeOk: { color: '#3fb950', fontSize: 11, border: '1px solid rgba(63,185,80,.5)', borderRadius: 999, padding: '0 6px' },\n badgeBlocked: { color: '#f85149', fontSize: 11, border: '1px solid rgba(248,81,73,.5)', borderRadius: 999, padding: '0 6px' },\n badgeReplaced: { color: '#d29922', fontSize: 11, border: '1px solid rgba(210,153,34,.5)', borderRadius: 999, padding: '0 6px' },\n mini: { padding: '3px 8px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', whiteSpace: 'nowrap' },\n arrowCol: { display: 'flex', flexDirection: 'column', gap: 2 },\n arrow: { padding: '0 6px', border: '1px solid rgba(128,128,128,.3)', borderRadius: 4, background: 'transparent', cursor: 'pointer', color: 'inherit', fontSize: 11, lineHeight: 1.4, opacity: 0.8 },\n rowDragging: { opacity: 0.4 },\n rowDropAbove: { boxShadow: '0 -2px 0 0 #58a6ff' },\n rowDropBelow: { boxShadow: '0 2px 0 0 #58a6ff' },\n dragHandle: { cursor: 'grab', color: 'rgba(128,128,128,.7)', fontSize: 13, padding: '0 2px', userSelect: 'none' },\n injectBox: { marginTop: 10, padding: '8px', border: '1px dashed rgba(128,128,128,.4)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 },\n injectRow: { ...flex, gap: 6 },\n editBox: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 },\n editInput: { padding: '6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit', fontFamily: 'inherit', fontSize: 12, resize: 'vertical', width: '100%', boxSizing: 'border-box' },\n input: { padding: '4px 6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit' },\n muted: { color: 'rgba(128,128,128,.75)', fontSize: 12 },\n error: { color: '#f85149', fontSize: 12, marginBottom: 6 },\n }\n})()\n","/** Pure preset / section helpers shared by the client UI and the node tests. */\nimport type { Config, Inventory, Preset, PresetData } from './types.ts'\n\nexport interface Section {\n name: string\n order: number\n text?: string\n active: boolean\n replaced: boolean\n}\n\n/** The config fields a preset apply must write back to the settings scope. */\nexport interface ConfigPatch {\n sections: string[]\n replace: Record<string, string>\n inject: Array<{ name: string; order: number; text: string }>\n tools: { exclude: string[]; include: string[] }\n}\n\n/** Merge the inventory sections with the user's inject list, sorted by order. */\nexport function mergeSections(inv: Inventory | null, cfg: Config, blockedNames: ReadonlySet<string>): Section[] {\n const map = new Map<string, Section>()\n for (const sec of inv?.sections ?? []) map.set(sec.name, sec)\n for (const item of cfg.inject ?? []) {\n const existing = map.get(item.name)\n if (existing) map.set(item.name, { ...existing, order: item.order })\n else map.set(item.name, {\n name: item.name,\n order: item.order,\n text: item.text || '<动态生成>',\n active: !blockedNames.has(item.name),\n replaced: false,\n })\n }\n return [...map.values()].sort((a, b) => a.order - b.order)\n}\n\n/**\n * Resolve a preset's relative order into an absolute ordered list (0..n-1).\n * Sections with no anchor (or whose anchor is absent) come first in preset\n * order; each anchored section is inserted right after its anchor; any\n * remaining sections (cycles) are appended.\n */\nexport function resolveOrder(presetOrder: PresetData['order']): Array<{ name: string; order: number; text: string }> {\n const list = presetOrder ?? []\n const afterMap = new Map<string, string | undefined>()\n const textMap = new Map<string, string>()\n for (const sec of list) {\n afterMap.set(sec.name, sec.after)\n textMap.set(sec.name, sec.text)\n }\n\n const result: string[] = []\n const placed = new Set<string>()\n\n for (const sec of list) {\n const anchor = afterMap.get(sec.name)\n if (!anchor || !afterMap.has(anchor)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n let changed = true\n while (changed) {\n changed = false\n for (const sec of list) {\n if (placed.has(sec.name)) continue\n const anchor = afterMap.get(sec.name)\n if (anchor && placed.has(anchor)) {\n result.splice(result.indexOf(anchor) + 1, 0, sec.name)\n placed.add(sec.name)\n changed = true\n }\n }\n }\n\n for (const sec of list) {\n if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '' }))\n}\n\n/**\n * Build a preset snapshot from the current config. The order list is derived\n * from the MERGED section list (not just `cfg.inject`) so that a preset saved\n * after only blocking sections (without reordering) still carries the full\n * ordered set, and thus a meaningful \"active list\" when applied.\n */\nexport function buildPresetData(cfg: Config, merged: Section[]): PresetData {\n const order = merged.map((sec, i) => ({\n name: sec.name,\n after: i > 0 ? merged[i - 1].name : undefined,\n text: sec.text ?? '',\n }))\n return {\n sections: cfg.sections,\n replace: cfg.replace,\n order,\n tools: cfg.tools,\n }\n}\n\n/**\n * Compute the config patch when applying a preset:\n * - same-name sections are overridden (the order list drives this at runtime)\n * - preset sections missing from the current prompt are added\n * - current sections NOT in the preset's ordered list are disabled by default\n * - only the preset's ACTIVE sections (in the order list but not in its block\n * list) are unblocked; the preset's own blocked sections stay blocked.\n */\nexport function applyPresetData(data: PresetData, cfg: Config, currentNames: ReadonlySet<string>): ConfigPatch {\n const presetOrder = data.order ?? []\n const presetNames = new Set(presetOrder.map((x) => x.name))\n const blocked = new Set(data.sections ?? [])\n\n // Only enforce the \"active set\" when the preset actually defines an order.\n // An empty order (e.g. a hand-authored / imported preset that only blocks a\n // few sections) must not default-disable every current section.\n if (presetOrder.length > 0) {\n const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)))\n for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name)\n for (const name of activeNames) blocked.delete(name)\n }\n\n return {\n sections: [...blocked],\n replace: { ...(cfg.replace ?? {}), ...(data.replace ?? {}) },\n inject: resolveOrder(presetOrder),\n tools: { exclude: data.tools?.exclude ?? [], include: data.tools?.include ?? [] },\n }\n}\n\n// ── Tool filtering ──────────────────────────────────────────────────────────\n\nexport type ToolsCfg = { exclude?: string[]; include?: string[] }\n\n/** Whether a tool is hidden under the current include/exclude config. */\nexport function isToolHidden(name: string, toolsCfg: ToolsCfg | undefined): boolean {\n const include = toolsCfg?.include ?? []\n const exclude = toolsCfg?.exclude ?? []\n return include.length > 0 ? !include.includes(name) : exclude.includes(name)\n}\n\n/** Toggle a tool's hidden state, returning a new tools config. */\nexport function toggleTool(name: string, currentlyHidden: boolean, toolsCfg: ToolsCfg | undefined): ToolsCfg {\n const include = toolsCfg?.include ?? []\n const exclude = toolsCfg?.exclude ?? []\n if (include.length > 0) {\n const next = include.slice()\n if (currentlyHidden) next.push(name)\n else { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }\n return { exclude, include: next }\n }\n const next = exclude.slice()\n if (currentlyHidden) { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }\n else next.push(name)\n return { exclude: next, include }\n}\n\n/**\n * Switch between include (whitelist) and exclude (blacklist) modes. Turning\n * include mode on discards names unknown to the current tool set and seeds the\n * include list from the currently non-excluded tools; turning it off clears the\n * include list (keep only known exclude names).\n */\nexport function setToolMode(on: boolean, toolsCfg: ToolsCfg | undefined, toolNames: string[]): ToolsCfg {\n const known = new Set(toolNames)\n const exclude = (toolsCfg?.exclude ?? []).filter((n) => known.has(n))\n if (on) return { exclude, include: toolNames.filter((n) => !exclude.includes(n)) }\n return { exclude, include: [] }\n}\n\n// ── Preset list operations ──────────────────────────────────────────────────\n\n/** Serialize a preset for export (JSON-safe full snapshot). */\nexport function serializePreset(preset: Preset): { name: string; data: PresetData } {\n return { name: preset.name, data: preset.data }\n}\n\n/**\n * Import preset(s): same-name presets are skipped, only distinct names are\n * added. Returns a new presets list (or the original if nothing changed).\n */\nexport function addImportedPresets(existing: Preset[], parsed: unknown, makeId: () => string): Preset[] {\n const incoming = Array.isArray(parsed) ? parsed : [parsed]\n const existingNames = new Set(existing.map((p) => p.name))\n const added = incoming\n .filter((p) => p && typeof (p as { name?: unknown }).name === 'string' && !existingNames.has((p as Preset).name))\n .map((p) => ({ id: makeId(), name: (p as Preset).name, data: ((p as Preset).data ?? {}) as PresetData }))\n return added.length > 0 ? [...existing, ...added] : existing\n}\n\n/** Delete a preset, clearing the active id if it pointed at the removed one. */\nexport function removePreset(presets: Preset[], id: string, activeId?: string): { presets: Preset[]; activeId?: string } {\n return {\n presets: presets.filter((p) => p.id !== id),\n activeId: activeId === id ? undefined : activeId,\n }\n}\n","/** Prompt-sections tab: block / replace / inject / reorder prompt sections. */\nimport { createElement as h, useState, type ReactElement, type DragEvent, type ChangeEvent } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { mergeSections, type Section } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const replace = cfg.replace ?? {}\n const blockedNames = new Set(cfg.sections ?? [])\n const [editing, setEditing] = useState<string | null>(null)\n const [draft, setDraft] = useState('')\n const [dragName, setDragName] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<{ name: string; pos: 'above' | 'below' } | null>(null)\n\n // Merge inventory sections with user-injected sections, sorted by order.\n // Injected sections that don't exist in the original prompt appear here too,\n // positioned by their order value.\n const merged: Section[] = mergeSections(inv, cfg, blockedNames)\n\n const toggleBlock = (name: string, currentlyBlocked: boolean): void => {\n const list = (cfg.sections ?? []).slice()\n if (currentlyBlocked) { const i = list.indexOf(name); if (i >= 0) list.splice(i, 1) }\n else list.push(name)\n scope.set('sections', list)\n }\n const startReplace = (name: string, original: string): void => {\n // Echo the ORIGINAL prompt text so the user edits from the current value.\n setEditing(name)\n setDraft(replace[name] ?? original)\n }\n const commitReplace = (name: string): void => {\n const next = { ...replace }\n if (draft.trim() === '') delete next[name]\n else next[name] = draft\n scope.set('replace', next)\n setEditing(null)\n }\n const clearReplace = (name: string): void => {\n const next = { ...replace }\n delete next[name]\n scope.set('replace', next)\n }\n\n // Persist the full ordered list. We treat the sorted list as an array and\n // re-index every section with a sequential integer order (0, 1, 2, …), so\n // there are never duplicate or fractional orders. text is left empty for\n // original sections (keeps their text); injected sections keep their own.\n const persistOrder = (ordered: Section[]): void => {\n const list = ordered.map((sec, i) => {\n const existing = (cfg.inject ?? []).find((x) => x.name === sec.name)\n return { name: sec.name, order: i, text: existing ? existing.text : '' }\n })\n scope.set('inject', list)\n }\n\n // Move a section up/down one slot (array-index swap), then re-index.\n const moveOrder = (index: number, dir: -1 | 1): void => {\n const target = index + dir\n if (target < 0 || target >= merged.length) return\n const next = merged.slice()\n const [item] = next.splice(index, 1)\n next.splice(target, 0, item)\n persistOrder(next)\n }\n\n const onDragStart = (e: DragEvent, name: string): void => {\n setDragName(name)\n e.dataTransfer.effectAllowed = 'move'\n try { e.dataTransfer.setData('text/plain', name) } catch { /* ignore */ }\n }\n const onDragOver = (e: DragEvent, name: string): void => {\n if (!dragName || dragName === name) return\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n const rect = e.currentTarget.getBoundingClientRect()\n const pos = e.clientY < rect.top + rect.height / 2 ? 'above' : 'below'\n setDropTarget({ name, pos })\n }\n const onDrop = (e: DragEvent, target: Section): void => {\n e.preventDefault()\n if (!dragName || dragName === target.name) { setDragName(null); setDropTarget(null); return }\n const dragged = merged.find((x) => x.name === dragName)\n if (!dragged) { setDragName(null); setDropTarget(null); return }\n const next = merged.filter((x) => x.name !== dragName)\n const idx = next.findIndex((x) => x.name === target.name)\n const pos = dropTarget?.name === target.name ? dropTarget.pos : 'below'\n next.splice(pos === 'above' ? idx : idx + 1, 0, dragged)\n persistOrder(next)\n setDragName(null)\n setDropTarget(null)\n }\n const onDragEnd = (): void => { setDragName(null); setDropTarget(null) }\n\n // Add a new injected section, placed by its order hint, then re-index.\n const addSection = (name: string, order: number, text: string): void => {\n const next = merged.slice()\n const entry: Section = { name, order, text: text || '<动态生成>', active: true, replaced: false }\n const existing = next.findIndex((x) => x.name === name)\n if (existing >= 0) next[existing] = entry\n else {\n const idx = next.findIndex((x) => x.order > order)\n if (idx < 0) next.push(entry)\n else next.splice(idx, 0, entry)\n }\n persistOrder(next)\n }\n\n return h('div', { style: s.list }, [\n merged.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n merged.map((sec, index) => {\n const blocked = blockedNames.has(sec.name)\n const isEditing = editing === sec.name\n const isDragging = dragName === sec.name\n const isDropTarget = dropTarget?.name === sec.name\n const rowStyle = {\n ...s.row,\n ...(isDragging ? s.rowDragging : {}),\n ...(isDropTarget && dropTarget.pos === 'above' ? s.rowDropAbove : {}),\n ...(isDropTarget && dropTarget.pos === 'below' ? s.rowDropBelow : {}),\n }\n return h('div', {\n key: sec.name,\n style: rowStyle,\n draggable: true,\n onDragStart: (e: DragEvent) => onDragStart(e, sec.name),\n onDragOver: (e: DragEvent) => onDragOver(e, sec.name),\n onDrop: (e: DragEvent) => onDrop(e, sec),\n onDragEnd,\n }, [\n h('span', { style: s.dragHandle, title: t('drag') }, '⠿'),\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: blocked, onChange: () => toggleBlock(sec.name, blocked) }),\n h('span', { style: blocked ? s.badgeBlocked : s.badgeOk }, blocked ? t('blockedOn') : t('blockedOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, sec.name),\n h('span', { style: s.orderTag }, '#' + index),\n sec.replaced ? h('span', { style: s.badgeReplaced }, t('replaced')) : null,\n ]),\n isEditing\n ? h('div', { style: s.editBox }, [\n h('textarea', { style: s.editInput, value: draft, onChange: (e: ChangeEvent<HTMLTextAreaElement>) => setDraft(e.target.value), rows: 3 }),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => commitReplace(sec.name) }, t('add')),\n h('button', { style: s.mini, onClick: () => setEditing(null) }, t('clearReplace')),\n ]),\n ])\n : h('div', { style: s.preview }, String(sec.text ?? '').slice(0, 140) || t('dynamic')),\n ]),\n h('div', { style: s.arrowCol }, [\n h('button', { style: s.arrow, disabled: index === 0, onClick: () => moveOrder(index, -1), title: t('moveUp') }, '↑'),\n h('button', { style: s.arrow, disabled: index === merged.length - 1, onClick: () => moveOrder(index, 1), title: t('moveDown') }, '↓'),\n ]),\n h('button', { style: s.mini, onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? '')) }, replace[sec.name] ? t('clearReplace') : t('replace')),\n ])\n }),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('injectNew')),\n h(InjectForm, { onAdd: addSection, t }),\n ]),\n ])\n}\n\nfunction InjectForm({ onAdd, t }: { onAdd: (name: string, order: number, text: string) => void; t: Translate }): ReactElement {\n const [name, setName] = useState('')\n const [order, setOrder] = useState('120')\n const [text, setText] = useState('')\n\n const submit = (): void => {\n if (!name.trim()) return\n onAdd(name.trim(), Number(order) || 120, text)\n setName(''); setText(''); setOrder('120')\n }\n\n return h('div', {}, [\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, width: '22%' }, placeholder: t('name'), value: name, onChange: (e) => setName(e.target.value) }),\n h('input', { style: { ...s.input, width: '12%' }, type: 'number', placeholder: t('order'), value: order, onChange: (e) => setOrder(e.target.value) }),\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('text'), value: text, onChange: (e) => setText(e.target.value) }),\n h('button', { style: s.mini, onClick: submit }, t('add')),\n ]),\n ])\n}\n","/** Tools tab: hide tools from the model catalog by exclude/include. */\nimport { createElement as h, type ReactElement } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { isToolHidden, setToolMode, toggleTool } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const tools = inv?.tools ?? []\n const toolsCfg = cfg.tools ?? { exclude: [], include: [] }\n const includeMode = (toolsCfg.include ?? []).length > 0\n\n const toggle = (name: string, currentlyHidden: boolean): void => {\n scope.set('tools', toggleTool(name, currentlyHidden, toolsCfg))\n }\n const setIncludeMode = (on: boolean): void => {\n scope.set('tools', setToolMode(on, toolsCfg, tools.map((tool) => tool.name)))\n }\n\n return h('div', {}, [\n h('div', { style: s.bar }, [\n h('button', { style: includeMode ? s.tabActive : s.tab, onClick: () => setIncludeMode(true) }, t('includeMode')),\n h('button', { style: includeMode ? s.tab : s.tabActive, onClick: () => setIncludeMode(false) }, t('excludeMode')),\n ]),\n h('div', { style: s.list }, [\n tools.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n tools.map((tool) => {\n // Hidden state comes from the CONFIG (exclude/include lists), not the\n // inventory snapshot, so toggling updates the checkbox immediately.\n const hidden = isToolHidden(tool.name, toolsCfg)\n return h('div', { key: tool.name, style: s.row }, [\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: hidden, onChange: () => toggle(tool.name, hidden) }),\n h('span', { style: hidden ? s.badgeBlocked : s.badgeOk }, hidden ? t('hiddenOn') : t('hiddenOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, h('span', { style: s.code }, tool.name)),\n h('div', { style: s.preview }, String(tool.description ?? '').slice(0, 140)),\n ]),\n ])\n }),\n ]),\n ])\n}\n","/** Presets tab: save / apply / export / import complete customization snapshots. */\nimport { createElement as h, useRef, useState, type ReactElement, type ChangeEvent } from 'react'\nimport type { Config, Inventory, Preset, PresetData, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { addImportedPresets, applyPresetData, buildPresetData, mergeSections, removePreset, serializePreset } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const presets = cfg.presets ?? []\n const [name, setName] = useState('')\n const fileRef = useRef<HTMLInputElement | null>(null)\n\n const blockedNames = new Set(cfg.sections ?? [])\n const merged = mergeSections(inv, cfg, blockedNames)\n const currentNames = new Set(merged.map((sec) => sec.name))\n\n // Capture the current customization as a new preset (full snapshot). The\n // absolute order is converted to a relative form (each section records the\n // section it should follow) so the preset stays portable across prompts.\n const saveCurrent = (): void => {\n const presetName = name.trim() || `${t('preset')} ${presets.length + 1}`\n const data = buildPresetData(cfg, merged)\n scope.set('presets', [...presets, { id: genId(), name: presetName, data }])\n setName('')\n }\n\n // Apply a preset. The patch is computed by the pure helper:\n // - same-name sections are overridden (content + order)\n // - preset sections missing from the current prompt are added\n // - current sections NOT in the preset list are disabled (blocked)\n // - only the preset's ACTIVE sections are unblocked (its own blocked list\n // is preserved)\n const applyPreset = (preset: Preset): void => {\n const patch = applyPresetData(preset.data, cfg, currentNames)\n scope.set('inject', patch.inject)\n scope.set('replace', patch.replace)\n scope.set('sections', patch.sections)\n scope.set('tools', patch.tools)\n scope.set('activePreset', preset.id)\n }\n\n const deletePreset = (id: string): void => {\n const next = removePreset(presets, id, cfg.activePreset)\n scope.set('presets', next.presets)\n if (next.activeId === undefined && cfg.activePreset === id) scope.unset('activePreset')\n }\n\n // Serialize a preset to a downloadable JSON file.\n const exportPreset = (preset: Preset): void => {\n const blob = new Blob([JSON.stringify(serializePreset(preset), null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = `${preset.name}.json`\n a.click()\n URL.revokeObjectURL(url)\n }\n\n // Import preset(s) from a JSON file. Same-name presets are skipped; only\n // presets with new names are added to the local list.\n const onImportFile = (e: ChangeEvent<HTMLInputElement>): void => {\n const file = e.target.files?.[0]\n if (!file) return\n const reader = new FileReader()\n reader.onload = () => {\n try {\n const parsed = JSON.parse(String(reader.result))\n scope.set('presets', addImportedPresets(presets, parsed, genId))\n } catch { /* ignore invalid json */ }\n }\n reader.readAsText(file)\n e.target.value = ''\n }\n\n return h('div', { style: s.list }, [\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('savePreset')),\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('presetName'), value: name, onChange: (e: ChangeEvent<HTMLInputElement>) => setName(e.target.value) }),\n h('button', { style: s.mini, onClick: saveCurrent }, t('save')),\n ]),\n ]),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('importPreset')),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => fileRef.current?.click() }, t('import')),\n h('input', { ref: fileRef, type: 'file', accept: '.json,application/json', style: { display: 'none' }, onChange: onImportFile }),\n ]),\n ]),\n presets.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n presets.map((preset) => {\n const active = cfg.activePreset === preset.id\n return h('div', { key: preset.id, style: s.row }, [\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, preset.name),\n active ? h('span', { style: s.badgeOk }, t('active')) : null,\n ]),\n ]),\n h('button', { style: s.mini, onClick: () => applyPreset(preset) }, t('apply')),\n h('button', { style: s.mini, onClick: () => exportPreset(preset) }, t('export')),\n h('button', { style: s.mini, onClick: () => deletePreset(preset.id) }, t('delete')),\n ])\n }),\n ])\n}\n\nfunction genId(): string {\n return 'p_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7)\n}\n","/**\n * dsh-prompt-customizer — browser half (TypeScript source).\n *\n * Registers a \"提示词定制\" settings section (sidebar entry) and a plugin\n * settings card. The panel fetches the effective section / tool inventory\n * from the host `/api/prompt-customizer/inventory` and lets the user block,\n * replace, inject prompt sections and hide tools. All writes go through the\n * bound settings scope (`prompt-customizer` namespace) and apply live.\n *\n * Built by tsdown into the __ModuleLoader__ factory bundle at client/client.js;\n * the only external is the loader module table's react entry.\n */\nimport { createElement as h, useEffect, useState, type ReactElement } from 'react'\nimport { DICT, type Translate } from './locales.ts'\nimport { s } from './styles.ts'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport { SectionsTab } from './SectionsTab.tsx'\nimport { ToolsTab } from './ToolsTab.tsx'\nimport { PresetsTab } from './PresetsTab.tsx'\n\nconst NS = 'prompt-customizer'\nconst INVENTORY_URL = '/api/prompt-customizer/inventory'\n\nexport const name = 'prompt-customizer'\nexport const inject = ['slots', 'locale']\n\n/** The subset of the client cordis context this plugin touches. */\ninterface ClientContext {\n effect(callback: () => unknown, label?: string): void\n locale: {\n register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): Translate\n }\n inject(services: string[], callback: (scoped: SettingsScopeHost) => void): void\n}\n\n/** The host surface the settings card needs (settingsScope transport). */\ninterface SettingsScopeHost {\n settingsScope: {\n bind(options: { namespace: string }): SettingsScope\n }\n slots: {\n inject(name: string, register: () => unknown): void\n register(meta: Record<string, unknown>, render: () => unknown): unknown\n }\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, DICT), 'prompt-customizer: locale')\n const t = ctx.locale.bind(NS)\n\n // Register a standalone settings section (sidebar menu entry) so the panel\n // only appears under \"提示词定制\", not as a card inside the \"插件\" settings\n // list. Goes through a NESTED inject of settingsScope so the panel can\n // read/write the namespace config; on hosts without the settings transport\n // it stays hidden instead of unmounting the whole plugin.\n ctx.inject(['settingsScope'], (scoped) => {\n const scope = scoped.settingsScope.bind({ namespace: NS })\n\n scoped.slots.inject('settings.section', () => scoped.slots.register({\n name: 'settings.section',\n id: 'prompt-customizer',\n order: 50,\n label: () => t('nav'),\n locale: NS,\n inject: () => ({ t }),\n }, () => h(Panel, { scope, t })))\n })\n}\n\n// ── Root panel ──────────────────────────────────────────────────────────────\n\nfunction Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {\n const [snap, setSnap] = useState(() => scope.getSnapshot())\n const [inv, setInv] = useState<Inventory | null>(null)\n const [tab, setTab] = useState<'sections' | 'tools' | 'presets'>('sections')\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope])\n\n const refresh = (): void => {\n fetch(INVENTORY_URL)\n .then((r) => r.json())\n .then((data: Inventory) => { setInv(data); setError(null) })\n .catch((e: unknown) => setError(String(e instanceof Error ? e.message : e)))\n }\n useEffect(refresh, [])\n\n if (snap.status !== 'ready' && snap.value === undefined) {\n return h('div', { style: s.muted }, t('loading'))\n }\n\n const cfg: Config = snap.value && typeof snap.value === 'object'\n ? snap.value as Config\n : { sections: [], replace: {}, inject: [], tools: { exclude: [], include: [] } }\n\n return h('div', { style: s.root }, [\n h('div', { style: s.bar }, [\n h('button', { style: tab === 'sections' ? s.tabActive : s.tab, onClick: () => setTab('sections') }, t('tabsSections')),\n h('button', { style: tab === 'tools' ? s.tabActive : s.tab, onClick: () => setTab('tools') }, t('tabsTools')),\n h('button', { style: tab === 'presets' ? s.tabActive : s.tab, onClick: () => setTab('presets') }, t('tabsPresets')),\n h('button', { style: s.refresh, onClick: refresh }, t('refresh')),\n ]),\n error ? h('div', { style: s.error }, String(error)) : null,\n tab === 'sections'\n ? h(SectionsTab, { cfg, inv, scope, t })\n : tab === 'tools'\n ? h(ToolsTab, { cfg, inv, scope, t })\n : h(PresetsTab, { cfg, inv, scope, t }),\n ])\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useState","h","h","useState","useRef","h","h","useState"],"sources":["../src/client/locales.ts","../src/client/styles.ts","../src/client/presets.ts","../src/client/SectionsTab.tsx","../src/client/ToolsTab.tsx","../src/client/PresetsTab.tsx","../src/client/index.ts"],"sourcesContent":["/** Locale dictionaries for the prompt-customizer panel. */\nexport const DICT = {\n zh: {\n nav: '提示词定制',\n loading: '加载中…',\n unavailable: '当前环境未启用本命名空间。',\n tabsSections: '提示词',\n tabsTools: '工具',\n tabsPresets: '预设',\n refresh: '刷新',\n blockedOff: '未屏蔽',\n blockedOn: '已屏蔽',\n replaced: '已替换',\n manual: '手动',\n system: '系统',\n replace: '编辑',\n clearReplace: '清除',\n injectNew: '注入新段',\n name: '名称',\n order: '顺序',\n text: '文本',\n add: '添加',\n hiddenOff: '可见',\n hiddenOn: '已隐藏',\n includeMode: '白名单模式:仅保留勾选项',\n excludeMode: '黑名单模式:勾选 = 隐藏',\n empty: '(空)',\n dynamic: '<动态生成>',\n drag: '拖动排序',\n moveUp: '上移',\n moveDown: '下移',\n savePreset: '保存当前为预设',\n presetName: '预设名称',\n save: '保存',\n importPreset: '导入预设',\n import: '导入',\n apply: '应用',\n export: '导出',\n delete: '删除',\n active: '使用中',\n preset: '预设',\n },\n en: {\n nav: 'Prompt Customizer',\n loading: 'Loading…',\n unavailable: 'Namespace unavailable in this environment.',\n tabsSections: 'Sections',\n tabsTools: 'Tools',\n tabsPresets: 'Presets',\n refresh: 'Refresh',\n blockedOff: 'Active',\n blockedOn: 'Blocked',\n replaced: 'Replaced',\n manual: 'Manual',\n system: 'System',\n replace: 'Replace',\n clearReplace: 'Clear',\n injectNew: 'Inject section',\n name: 'Name',\n order: 'Order',\n text: 'Text',\n add: 'Add',\n hiddenOff: 'Visible',\n hiddenOn: 'Hidden',\n includeMode: 'Whitelist: keep only checked',\n excludeMode: 'Blacklist: check to hide',\n empty: '(empty)',\n dynamic: '<dynamic>',\n drag: 'Drag to reorder',\n moveUp: 'Move up',\n moveDown: 'Move down',\n savePreset: 'Save current as preset',\n presetName: 'Preset name',\n save: 'Save',\n importPreset: 'Import preset',\n import: 'Import',\n apply: 'Apply',\n export: 'Export',\n delete: 'Delete',\n active: 'Active',\n preset: 'Preset',\n },\n} as const\n\nexport type Translate = (key: string) => string\n","/** Inline styles shared by the prompt-customizer panel components. */\nexport const s = (() => {\n const flex = { display: 'flex', alignItems: 'center' }\n return {\n root: { fontFamily: 'inherit', fontSize: 13, lineHeight: 1.5 },\n bar: { ...flex, gap: 6, marginBottom: 8, flexWrap: 'wrap' },\n tab: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', opacity: 0.75 },\n tabActive: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.7)', borderRadius: 6, background: 'rgba(128,128,128,.18)', cursor: 'pointer', color: 'inherit' },\n refresh: { marginLeft: 'auto', padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit' },\n list: { display: 'flex', flexDirection: 'column', gap: 6 },\n row: { ...flex, gap: 8, padding: '6px 8px', border: '1px solid rgba(128,128,128,.2)', borderRadius: 8, background: 'rgba(128,128,128,.06)' },\n switchWrap: { ...flex, gap: 6, minWidth: 90 },\n rowBody: { flex: 1, minWidth: 0 },\n rowTitle: { ...flex, gap: 6, alignItems: 'baseline', flexWrap: 'wrap' },\n code: { fontFamily: 'monospace', fontSize: 12, wordBreak: 'break-all' },\n orderTag: { color: 'rgba(128,128,128,.8)', fontSize: 11 },\n preview: { color: 'rgba(128,128,128,.85)', fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n badgeOk: { color: '#3fb950', fontSize: 11, border: '1px solid rgba(63,185,80,.5)', borderRadius: 999, padding: '0 6px' },\n badgeBlocked: { color: '#f85149', fontSize: 11, border: '1px solid rgba(248,81,73,.5)', borderRadius: 999, padding: '0 6px' },\n badgeReplaced: { color: '#d29922', fontSize: 11, border: '1px solid rgba(210,153,34,.5)', borderRadius: 999, padding: '0 6px' },\n badgeCustom: { color: '#58a6ff', fontSize: 11, border: '1px solid rgba(88,166,255,.5)', borderRadius: 999, padding: '0 6px' },\n badgeSystem: { color: 'rgba(128,128,128,.85)', fontSize: 11, border: '1px solid rgba(128,128,128,.4)', borderRadius: 999, padding: '0 6px' },\n mini: { padding: '3px 8px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', whiteSpace: 'nowrap' },\n arrowCol: { display: 'flex', flexDirection: 'column', gap: 2 },\n arrow: { padding: '0 6px', border: '1px solid rgba(128,128,128,.3)', borderRadius: 4, background: 'transparent', cursor: 'pointer', color: 'inherit', fontSize: 11, lineHeight: 1.4, opacity: 0.8 },\n rowDragging: { opacity: 0.4 },\n rowDropAbove: { boxShadow: '0 -2px 0 0 #58a6ff' },\n rowDropBelow: { boxShadow: '0 2px 0 0 #58a6ff' },\n dragHandle: { cursor: 'grab', color: 'rgba(128,128,128,.7)', fontSize: 13, padding: '0 2px', userSelect: 'none' },\n injectBox: { marginTop: 10, padding: '8px', border: '1px dashed rgba(128,128,128,.4)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 },\n injectRow: { ...flex, gap: 6 },\n editBox: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 },\n editInput: { padding: '6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit', fontFamily: 'inherit', fontSize: 12, resize: 'vertical', width: '100%', boxSizing: 'border-box' },\n input: { padding: '4px 6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit' },\n muted: { color: 'rgba(128,128,128,.75)', fontSize: 12 },\n error: { color: '#f85149', fontSize: 12, marginBottom: 6 },\n }\n})()\n","/** Pure preset / section helpers shared by the client UI and the node tests. */\nimport type { Config, Inventory, Preset, PresetData } from './types.ts'\n\nexport interface Section {\n name: string\n order: number\n text?: string\n active: boolean\n replaced: boolean\n /** `system` = read from the host prompt inventory (not deletable); `custom` = added by this plugin (deletable). */\n source?: 'system' | 'custom'\n}\n\n/** The config fields a preset apply must write back to the settings scope. */\nexport interface ConfigPatch {\n sections: string[]\n replace: Record<string, string>\n inject: Array<{ name: string; order: number; text: string }>\n tools: { exclude: string[]; include: string[] }\n}\n\n/**\n * Merge the inventory sections with the user's inject list, sorted by order.\n * Sections read from the inventory (other plugins) are `system`; sections this\n * plugin generated carry a hidden `custom` marker and are `custom`. The source\n * is decided by the hidden marker — never by name collisions — so a custom\n * section keeps its identity (and its deletability) even when its name happens\n * to match an inventory section, and across preset switches. Custom sections\n * always render their own text; they can never be \"<动态生成>\".\n */\nexport function mergeSections(inv: Inventory | null, cfg: Config, blockedNames: ReadonlySet<string>): Section[] {\n const map = new Map<string, Section>()\n for (const sec of inv?.sections ?? []) map.set(sec.name, { ...sec, source: 'system' })\n for (const item of cfg.inject ?? []) {\n const isCustom = item.custom === true\n const existing = map.get(item.name)\n if (existing) {\n map.set(item.name, isCustom\n ? { ...existing, order: item.order, text: item.text ?? '', source: 'custom' }\n : { ...existing, order: item.order, source: 'system' })\n } else {\n map.set(item.name, {\n name: item.name,\n order: item.order,\n text: item.text ?? '',\n active: !blockedNames.has(item.name),\n replaced: false,\n source: isCustom ? 'custom' : 'system',\n })\n }\n }\n return [...map.values()].sort((a, b) => a.order - b.order)\n}\n\n/**\n * Resolve a preset's relative order into an absolute ordered list (0..n-1).\n * Sections with no anchor (or whose anchor is absent) come first in preset\n * order; each anchored section is inserted right after its anchor; any\n * remaining sections (cycles) are appended.\n */\nexport function resolveOrder(presetOrder: PresetData['order']): Array<{ name: string; order: number; text: string; custom?: boolean }> {\n const list = presetOrder ?? []\n const afterMap = new Map<string, string | undefined>()\n const textMap = new Map<string, string>()\n const customMap = new Map<string, boolean>()\n for (const sec of list) {\n afterMap.set(sec.name, sec.after)\n textMap.set(sec.name, sec.text)\n customMap.set(sec.name, sec.custom === true)\n }\n\n const result: string[] = []\n const placed = new Set<string>()\n\n for (const sec of list) {\n const anchor = afterMap.get(sec.name)\n if (!anchor || !afterMap.has(anchor)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n let changed = true\n while (changed) {\n changed = false\n for (const sec of list) {\n if (placed.has(sec.name)) continue\n const anchor = afterMap.get(sec.name)\n if (anchor && placed.has(anchor)) {\n result.splice(result.indexOf(anchor) + 1, 0, sec.name)\n placed.add(sec.name)\n changed = true\n }\n }\n }\n\n for (const sec of list) {\n if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '', custom: customMap.get(name) }))\n}\n\n/**\n * Build a preset snapshot from the current config. The order list is derived\n * from the MERGED section list (not just `cfg.inject`) so that a preset saved\n * after only blocking sections (without reordering) still carries the full\n * ordered set, and thus a meaningful \"active list\" when applied.\n */\nexport function buildPresetData(cfg: Config, merged: Section[]): PresetData {\n // Only custom (plugin-added) sections carry their own text into the preset's\n // order list. System sections keep an empty text so applying the preset only\n // reorders them and never freezes their dynamically generated content.\n const order = merged.map((sec, i) => ({\n name: sec.name,\n after: i > 0 ? merged[i - 1].name : undefined,\n text: sec.source === 'custom' ? (sec.text ?? '') : '',\n custom: sec.source === 'custom',\n }))\n return {\n sections: cfg.sections,\n replace: cfg.replace,\n order,\n tools: cfg.tools,\n }\n}\n\n/**\n * Compute the config patch when applying a preset:\n * - same-name sections are overridden (the order list drives this at runtime)\n * - preset sections missing from the current prompt are added\n * - current sections NOT in the preset's ordered list are disabled by default\n * - only the preset's ACTIVE sections (in the order list but not in its block\n * list) are unblocked; the preset's own blocked sections stay blocked.\n *\n * Custom injected sections that exist in the current config but are NOT in the\n * preset's ordered list are preserved in the resulting inject list (so they\n * stay visible) and disabled (added to the blocked set), rather than silently\n * dropped — a preset must not make the user's own injected sections vanish.\n */\nexport function applyPresetData(data: PresetData, cfg: Config, currentNames: ReadonlySet<string>): ConfigPatch {\n const presetOrder = data.order ?? []\n const presetNames = new Set(presetOrder.map((x) => x.name))\n const blocked = new Set(data.sections ?? [])\n\n // Inject list = the preset's resolved order, then any custom injected\n // sections present in the current config but absent from the preset list.\n // Keeping them in `inject` makes them visible; they are disabled below.\n const inject = resolveOrder(presetOrder)\n const kept = new Set(inject.map((x) => x.name))\n let order = inject.length\n for (const item of cfg.inject ?? []) {\n if (!item || !item.name || presetNames.has(item.name) || kept.has(item.name)) continue\n kept.add(item.name)\n inject.push({ name: item.name, order: order++, text: item.text ?? '', custom: item.custom === true })\n }\n\n // Only enforce the \"active set\" when the preset actually defines an order.\n // An empty order (e.g. a hand-authored / imported preset that only blocks a\n // few sections) must not default-disable every current section.\n if (presetOrder.length > 0) {\n const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)))\n for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name)\n for (const name of activeNames) blocked.delete(name)\n }\n\n return {\n sections: [...blocked],\n replace: { ...(cfg.replace ?? {}), ...(data.replace ?? {}) },\n inject,\n tools: { exclude: data.tools?.exclude ?? [], include: data.tools?.include ?? [] },\n }\n}\n\n/**\n * Remove a custom (plugin-added) section from the config: it disappears from\n * the inject list, is no longer forced into the blocked set, and any replace\n * text for it is cleared. System sections read from the host inventory cannot\n * be removed this way (they would just come back on the next inventory read).\n */\nexport function removeSection(name: string, cfg: Config): Pick<Config, 'sections' | 'inject' | 'replace'> {\n const sections = (cfg.sections ?? []).filter((n) => n !== name)\n const inject = (cfg.inject ?? []).filter((item) => item.name !== name)\n const replace = { ...(cfg.replace ?? {}) }\n delete replace[name]\n return { sections, inject, replace }\n}\n\n// ── Tool filtering ──────────────────────────────────────────────────────────\n\nexport type ToolsCfg = { exclude?: string[]; include?: string[] }\n\n/** Whether a tool is hidden under the current include/exclude config. */\nexport function isToolHidden(name: string, toolsCfg: ToolsCfg | undefined): boolean {\n const include = toolsCfg?.include ?? []\n const exclude = toolsCfg?.exclude ?? []\n return include.length > 0 ? !include.includes(name) : exclude.includes(name)\n}\n\n/** Toggle a tool's hidden state, returning a new tools config. */\nexport function toggleTool(name: string, currentlyHidden: boolean, toolsCfg: ToolsCfg | undefined): ToolsCfg {\n const include = toolsCfg?.include ?? []\n const exclude = toolsCfg?.exclude ?? []\n if (include.length > 0) {\n const next = include.slice()\n if (currentlyHidden) next.push(name)\n else { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }\n return { exclude, include: next }\n }\n const next = exclude.slice()\n if (currentlyHidden) { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }\n else next.push(name)\n return { exclude: next, include }\n}\n\n/**\n * Switch between include (whitelist) and exclude (blacklist) modes. Turning\n * include mode on discards names unknown to the current tool set and seeds the\n * include list from the currently non-excluded tools; turning it off clears the\n * include list (keep only known exclude names).\n */\nexport function setToolMode(on: boolean, toolsCfg: ToolsCfg | undefined, toolNames: string[]): ToolsCfg {\n const known = new Set(toolNames)\n const exclude = (toolsCfg?.exclude ?? []).filter((n) => known.has(n))\n if (on) return { exclude, include: toolNames.filter((n) => !exclude.includes(n)) }\n return { exclude, include: [] }\n}\n\n// ── Preset list operations ──────────────────────────────────────────────────\n\n/** Serialize a preset for export (JSON-safe full snapshot). */\nexport function serializePreset(preset: Preset): { name: string; data: PresetData } {\n return { name: preset.name, data: preset.data }\n}\n\n/**\n * Import preset(s): same-name presets are skipped, only distinct names are\n * added. Returns a new presets list (or the original if nothing changed).\n */\nexport function addImportedPresets(existing: Preset[], parsed: unknown, makeId: () => string): Preset[] {\n const incoming = Array.isArray(parsed) ? parsed : [parsed]\n const existingNames = new Set(existing.map((p) => p.name))\n const added = incoming\n .filter((p) => p && typeof (p as { name?: unknown }).name === 'string' && !existingNames.has((p as Preset).name))\n .map((p) => ({ id: makeId(), name: (p as Preset).name, data: ((p as Preset).data ?? {}) as PresetData }))\n return added.length > 0 ? [...existing, ...added] : existing\n}\n\n/** Delete a preset, clearing the active id if it pointed at the removed one. */\nexport function removePreset(presets: Preset[], id: string, activeId?: string): { presets: Preset[]; activeId?: string } {\n return {\n presets: presets.filter((p) => p.id !== id),\n activeId: activeId === id ? undefined : activeId,\n }\n}\n","/** Prompt-sections tab: block / replace / inject / reorder prompt sections. */\nimport { createElement as h, useState, type ReactElement, type DragEvent, type ChangeEvent } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { mergeSections, removeSection, type Section } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const replace = cfg.replace ?? {}\n const blockedNames = new Set(cfg.sections ?? [])\n const [editing, setEditing] = useState<string | null>(null)\n const [draft, setDraft] = useState('')\n const [dragName, setDragName] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<{ name: string; pos: 'above' | 'below' } | null>(null)\n\n // Merge inventory sections with user-injected sections, sorted by order.\n // Injected sections that don't exist in the original prompt appear here too,\n // positioned by their order value.\n const merged: Section[] = mergeSections(inv, cfg, blockedNames)\n\n const toggleBlock = (name: string, currentlyBlocked: boolean): void => {\n const list = (cfg.sections ?? []).slice()\n if (currentlyBlocked) { const i = list.indexOf(name); if (i >= 0) list.splice(i, 1) }\n else list.push(name)\n scope.set('sections', list)\n }\n const startReplace = (name: string, original: string): void => {\n // Echo the ORIGINAL prompt text so the user edits from the current value.\n setEditing(name)\n setDraft(replace[name] ?? original)\n }\n const commitReplace = (name: string): void => {\n const next = { ...replace }\n if (draft.trim() === '') delete next[name]\n else next[name] = draft\n scope.set('replace', next)\n setEditing(null)\n }\n const clearReplace = (name: string): void => {\n const next = { ...replace }\n delete next[name]\n scope.set('replace', next)\n }\n\n // Persist the full ordered list. We treat the sorted list as an array and\n // re-index every section with a sequential integer order (0, 1, 2, …), so\n // there are never duplicate or fractional orders. System sections keep empty\n // text (so we never freeze their dynamically generated content); custom\n // (plugin-generated) sections keep their own text and the hidden `custom`\n // marker, so they render real content and stay identifiable/deletable.\n const persistOrder = (ordered: Section[]): void => {\n const list = ordered.map((sec, i) => {\n const existing = (cfg.inject ?? []).find((x) => x.name === sec.name)\n const isCustom = sec.source === 'custom'\n return { name: sec.name, order: i, text: isCustom ? (sec.text ?? '') : (existing?.text ?? ''), custom: isCustom }\n })\n scope.set('inject', list)\n }\n\n // Delete a custom (plugin-added) section: remove it from inject / sections /\n // replace so it no longer appears in the panel at all.\n const removeCustom = (name: string): void => {\n const patch = removeSection(name, cfg)\n scope.set('sections', patch.sections)\n scope.set('inject', patch.inject)\n scope.set('replace', patch.replace)\n }\n\n // Move a section up/down one slot (array-index swap), then re-index.\n const moveOrder = (index: number, dir: -1 | 1): void => {\n const target = index + dir\n if (target < 0 || target >= merged.length) return\n const next = merged.slice()\n const [item] = next.splice(index, 1)\n next.splice(target, 0, item)\n persistOrder(next)\n }\n\n const onDragStart = (e: DragEvent, name: string): void => {\n setDragName(name)\n e.dataTransfer.effectAllowed = 'move'\n try { e.dataTransfer.setData('text/plain', name) } catch { /* ignore */ }\n }\n const onDragOver = (e: DragEvent, name: string): void => {\n if (!dragName || dragName === name) return\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n const rect = e.currentTarget.getBoundingClientRect()\n const pos = e.clientY < rect.top + rect.height / 2 ? 'above' : 'below'\n setDropTarget({ name, pos })\n }\n const onDrop = (e: DragEvent, target: Section): void => {\n e.preventDefault()\n if (!dragName || dragName === target.name) { setDragName(null); setDropTarget(null); return }\n const dragged = merged.find((x) => x.name === dragName)\n if (!dragged) { setDragName(null); setDropTarget(null); return }\n const next = merged.filter((x) => x.name !== dragName)\n const idx = next.findIndex((x) => x.name === target.name)\n const pos = dropTarget?.name === target.name ? dropTarget.pos : 'below'\n next.splice(pos === 'above' ? idx : idx + 1, 0, dragged)\n persistOrder(next)\n setDragName(null)\n setDropTarget(null)\n }\n const onDragEnd = (): void => { setDragName(null); setDropTarget(null) }\n\n // Add a new injected section, placed by its order hint, then re-index. The\n // section is marked `custom` (the hidden marker) so it is identified as\n // plugin-generated and thus deletable, and its own text is preserved.\n const addSection = (name: string, order: number, text: string): void => {\n const next = merged.slice()\n const entry: Section = { name, order, text, active: true, replaced: false, source: 'custom' }\n const existing = next.findIndex((x) => x.name === name)\n if (existing >= 0) next[existing] = entry\n else {\n const idx = next.findIndex((x) => x.order > order)\n if (idx < 0) next.push(entry)\n else next.splice(idx, 0, entry)\n }\n persistOrder(next)\n }\n\n return h('div', { style: s.list }, [\n merged.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n merged.map((sec, index) => {\n const blocked = blockedNames.has(sec.name)\n const isEditing = editing === sec.name\n const isDragging = dragName === sec.name\n const isDropTarget = dropTarget?.name === sec.name\n const rowStyle = {\n ...s.row,\n ...(isDragging ? s.rowDragging : {}),\n ...(isDropTarget && dropTarget.pos === 'above' ? s.rowDropAbove : {}),\n ...(isDropTarget && dropTarget.pos === 'below' ? s.rowDropBelow : {}),\n }\n return h('div', {\n key: sec.name,\n style: rowStyle,\n draggable: true,\n onDragStart: (e: DragEvent) => onDragStart(e, sec.name),\n onDragOver: (e: DragEvent) => onDragOver(e, sec.name),\n onDrop: (e: DragEvent) => onDrop(e, sec),\n onDragEnd,\n }, [\n h('span', { style: s.dragHandle, title: t('drag') }, '⠿'),\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: blocked, onChange: () => toggleBlock(sec.name, blocked) }),\n h('span', { style: blocked ? s.badgeBlocked : s.badgeOk }, blocked ? t('blockedOn') : t('blockedOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, sec.name),\n h('span', { style: s.orderTag }, '#' + index),\n h('span', { style: sec.source === 'custom' ? s.badgeCustom : s.badgeSystem }, sec.source === 'custom' ? t('manual') : t('system')),\n sec.replaced ? h('span', { style: s.badgeReplaced }, t('replaced')) : null,\n ]),\n isEditing\n ? h('div', { style: s.editBox }, [\n h('textarea', { style: s.editInput, value: draft, onChange: (e: ChangeEvent<HTMLTextAreaElement>) => setDraft(e.target.value), rows: 3 }),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => commitReplace(sec.name) }, t('add')),\n h('button', { style: s.mini, onClick: () => setEditing(null) }, t('clearReplace')),\n ]),\n ])\n : h('div', { style: s.preview }, String(sec.text ?? '').slice(0, 140) || (sec.source === 'custom' ? t('empty') : t('dynamic'))),\n ]),\n h('div', { style: s.arrowCol }, [\n h('button', { style: s.arrow, disabled: index === 0, onClick: () => moveOrder(index, -1), title: t('moveUp') }, '↑'),\n h('button', { style: s.arrow, disabled: index === merged.length - 1, onClick: () => moveOrder(index, 1), title: t('moveDown') }, '↓'),\n ]),\n h('button', { style: s.mini, onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? '')) }, replace[sec.name] ? t('clearReplace') : t('replace')),\n sec.source === 'custom' ? h('button', { style: s.mini, onClick: () => removeCustom(sec.name), title: t('delete') }, t('delete')) : null,\n ])\n }),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('injectNew')),\n h(InjectForm, { onAdd: addSection, t }),\n ]),\n ])\n}\n\nfunction InjectForm({ onAdd, t }: { onAdd: (name: string, order: number, text: string) => void; t: Translate }): ReactElement {\n const [name, setName] = useState('')\n const [order, setOrder] = useState('120')\n const [text, setText] = useState('')\n\n const submit = (): void => {\n if (!name.trim()) return\n onAdd(name.trim(), Number(order) || 120, text)\n setName(''); setText(''); setOrder('120')\n }\n\n return h('div', {}, [\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, width: '22%' }, placeholder: t('name'), value: name, onChange: (e) => setName(e.target.value) }),\n h('input', { style: { ...s.input, width: '12%' }, type: 'number', placeholder: t('order'), value: order, onChange: (e) => setOrder(e.target.value) }),\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('text'), value: text, onChange: (e) => setText(e.target.value) }),\n h('button', { style: s.mini, onClick: submit }, t('add')),\n ]),\n ])\n}\n","/** Tools tab: hide tools from the model catalog by exclude/include. */\nimport { createElement as h, type ReactElement } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { isToolHidden, setToolMode, toggleTool } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const tools = inv?.tools ?? []\n const toolsCfg = cfg.tools ?? { exclude: [], include: [] }\n const includeMode = (toolsCfg.include ?? []).length > 0\n\n const toggle = (name: string, currentlyHidden: boolean): void => {\n scope.set('tools', toggleTool(name, currentlyHidden, toolsCfg))\n }\n const setIncludeMode = (on: boolean): void => {\n scope.set('tools', setToolMode(on, toolsCfg, tools.map((tool) => tool.name)))\n }\n\n return h('div', {}, [\n h('div', { style: s.bar }, [\n h('button', { style: includeMode ? s.tabActive : s.tab, onClick: () => setIncludeMode(true) }, t('includeMode')),\n h('button', { style: includeMode ? s.tab : s.tabActive, onClick: () => setIncludeMode(false) }, t('excludeMode')),\n ]),\n h('div', { style: s.list }, [\n tools.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n tools.map((tool) => {\n // Hidden state comes from the CONFIG (exclude/include lists), not the\n // inventory snapshot, so toggling updates the checkbox immediately.\n const hidden = isToolHidden(tool.name, toolsCfg)\n return h('div', { key: tool.name, style: s.row }, [\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: hidden, onChange: () => toggle(tool.name, hidden) }),\n h('span', { style: hidden ? s.badgeBlocked : s.badgeOk }, hidden ? t('hiddenOn') : t('hiddenOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, h('span', { style: s.code }, tool.name)),\n h('div', { style: s.preview }, String(tool.description ?? '').slice(0, 140)),\n ]),\n ])\n }),\n ]),\n ])\n}\n","/** Presets tab: save / apply / export / import complete customization snapshots. */\nimport { createElement as h, useRef, useState, type ReactElement, type ChangeEvent } from 'react'\nimport type { Config, Inventory, Preset, PresetData, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { addImportedPresets, applyPresetData, buildPresetData, mergeSections, removePreset, serializePreset } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const presets = cfg.presets ?? []\n const [name, setName] = useState('')\n const fileRef = useRef<HTMLInputElement | null>(null)\n\n const blockedNames = new Set(cfg.sections ?? [])\n const merged = mergeSections(inv, cfg, blockedNames)\n const currentNames = new Set(merged.map((sec) => sec.name))\n\n // Capture the current customization as a new preset (full snapshot). The\n // absolute order is converted to a relative form (each section records the\n // section it should follow) so the preset stays portable across prompts.\n const saveCurrent = (): void => {\n const presetName = name.trim() || `${t('preset')} ${presets.length + 1}`\n const data = buildPresetData(cfg, merged)\n scope.set('presets', [...presets, { id: genId(), name: presetName, data }])\n setName('')\n }\n\n // Apply a preset. The patch is computed by the pure helper:\n // - same-name sections are overridden (content + order)\n // - preset sections missing from the current prompt are added\n // - current sections NOT in the preset list are disabled (blocked)\n // - only the preset's ACTIVE sections are unblocked (its own blocked list\n // is preserved)\n const applyPreset = (preset: Preset): void => {\n const patch = applyPresetData(preset.data, cfg, currentNames)\n scope.set('inject', patch.inject)\n scope.set('replace', patch.replace)\n scope.set('sections', patch.sections)\n scope.set('tools', patch.tools)\n scope.set('activePreset', preset.id)\n }\n\n const deletePreset = (id: string): void => {\n const next = removePreset(presets, id, cfg.activePreset)\n scope.set('presets', next.presets)\n if (next.activeId === undefined && cfg.activePreset === id) scope.unset('activePreset')\n }\n\n // Serialize a preset to a downloadable JSON file.\n const exportPreset = (preset: Preset): void => {\n const blob = new Blob([JSON.stringify(serializePreset(preset), null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = `${preset.name}.json`\n a.click()\n URL.revokeObjectURL(url)\n }\n\n // Import preset(s) from a JSON file. Same-name presets are skipped; only\n // presets with new names are added to the local list.\n const onImportFile = (e: ChangeEvent<HTMLInputElement>): void => {\n const file = e.target.files?.[0]\n if (!file) return\n const reader = new FileReader()\n reader.onload = () => {\n try {\n const parsed = JSON.parse(String(reader.result))\n scope.set('presets', addImportedPresets(presets, parsed, genId))\n } catch { /* ignore invalid json */ }\n }\n reader.readAsText(file)\n e.target.value = ''\n }\n\n return h('div', { style: s.list }, [\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('savePreset')),\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('presetName'), value: name, onChange: (e: ChangeEvent<HTMLInputElement>) => setName(e.target.value) }),\n h('button', { style: s.mini, onClick: saveCurrent }, t('save')),\n ]),\n ]),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('importPreset')),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => fileRef.current?.click() }, t('import')),\n h('input', { ref: fileRef, type: 'file', accept: '.json,application/json', style: { display: 'none' }, onChange: onImportFile }),\n ]),\n ]),\n presets.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n presets.map((preset) => {\n const active = cfg.activePreset === preset.id\n return h('div', { key: preset.id, style: s.row }, [\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, preset.name),\n active ? h('span', { style: s.badgeOk }, t('active')) : null,\n ]),\n ]),\n h('button', { style: s.mini, onClick: () => applyPreset(preset) }, t('apply')),\n h('button', { style: s.mini, onClick: () => exportPreset(preset) }, t('export')),\n h('button', { style: s.mini, onClick: () => deletePreset(preset.id) }, t('delete')),\n ])\n }),\n ])\n}\n\nfunction genId(): string {\n return 'p_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7)\n}\n","/**\n * dsh-prompt-customizer — browser half (TypeScript source).\n *\n * Registers a \"提示词定制\" settings section (sidebar entry) and a plugin\n * settings card. The panel fetches the effective section / tool inventory\n * from the host `/api/prompt-customizer/inventory` and lets the user block,\n * replace, inject prompt sections and hide tools. All writes go through the\n * bound settings scope (`prompt-customizer` namespace) and apply live.\n *\n * Built by tsdown into the __ModuleLoader__ factory bundle at client/client.js;\n * the only external is the loader module table's react entry.\n */\nimport { createElement as h, useEffect, useState, type ReactElement } from 'react'\nimport { DICT, type Translate } from './locales.ts'\nimport { s } from './styles.ts'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport { SectionsTab } from './SectionsTab.tsx'\nimport { ToolsTab } from './ToolsTab.tsx'\nimport { PresetsTab } from './PresetsTab.tsx'\n\nconst NS = 'prompt-customizer'\nconst INVENTORY_URL = '/api/prompt-customizer/inventory'\n\nexport const name = 'prompt-customizer'\nexport const inject = ['slots', 'locale']\n\n/** The subset of the client cordis context this plugin touches. */\ninterface ClientContext {\n effect(callback: () => unknown, label?: string): void\n locale: {\n register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): Translate\n }\n inject(services: string[], callback: (scoped: SettingsScopeHost) => void): void\n}\n\n/** The host surface the settings card needs (settingsScope transport). */\ninterface SettingsScopeHost {\n settingsScope: {\n bind(options: { namespace: string }): SettingsScope\n }\n slots: {\n inject(name: string, register: () => unknown): void\n register(meta: Record<string, unknown>, render: () => unknown): unknown\n }\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, DICT), 'prompt-customizer: locale')\n const t = ctx.locale.bind(NS)\n\n // Register a standalone settings section (sidebar menu entry) so the panel\n // only appears under \"提示词定制\", not as a card inside the \"插件\" settings\n // list. Goes through a NESTED inject of settingsScope so the panel can\n // read/write the namespace config; on hosts without the settings transport\n // it stays hidden instead of unmounting the whole plugin.\n ctx.inject(['settingsScope'], (scoped) => {\n const scope = scoped.settingsScope.bind({ namespace: NS })\n\n scoped.slots.inject('settings.section', () => scoped.slots.register({\n name: 'settings.section',\n id: 'prompt-customizer',\n order: 50,\n label: () => t('nav'),\n locale: NS,\n inject: () => ({ t }),\n }, () => h(Panel, { scope, t })))\n })\n}\n\n// ── Root panel ──────────────────────────────────────────────────────────────\n\nfunction Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {\n const [snap, setSnap] = useState(() => scope.getSnapshot())\n const [inv, setInv] = useState<Inventory | null>(null)\n const [tab, setTab] = useState<'sections' | 'tools' | 'presets'>('sections')\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope])\n\n const refresh = (): void => {\n fetch(INVENTORY_URL)\n .then((r) => r.json())\n .then((data: Inventory) => { setInv(data); setError(null) })\n .catch((e: unknown) => setError(String(e instanceof Error ? e.message : e)))\n }\n useEffect(refresh, [])\n\n if (snap.status !== 'ready' && snap.value === undefined) {\n return h('div', { style: s.muted }, t('loading'))\n }\n\n const cfg: Config = snap.value && typeof snap.value === 'object'\n ? snap.value as Config\n : { sections: [], replace: {}, inject: [], tools: { exclude: [], include: [] } }\n\n return h('div', { style: s.root }, [\n h('div', { style: s.bar }, [\n h('button', { style: tab === 'sections' ? s.tabActive : s.tab, onClick: () => setTab('sections') }, t('tabsSections')),\n h('button', { style: tab === 'tools' ? s.tabActive : s.tab, onClick: () => setTab('tools') }, t('tabsTools')),\n h('button', { style: tab === 'presets' ? s.tabActive : s.tab, onClick: () => setTab('presets') }, t('tabsPresets')),\n h('button', { style: s.refresh, onClick: refresh }, t('refresh')),\n ]),\n error ? h('div', { style: s.error }, String(error)) : null,\n tab === 'sections'\n ? h(SectionsTab, { cfg, inv, scope, t })\n : tab === 'tools'\n ? h(ToolsTab, { cfg, inv, scope, t })\n : h(PresetsTab, { cfg, inv, scope, t }),\n ])\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -84,12 +84,27 @@ export function apply(ctx, entry = {}) {
84
84
  }
85
85
  }
86
86
 
87
- /** Resolve the `standard` agent's standing scope key when a roster is present. */
87
+ /** Resolve `value` but never wait longer than `ms`; fall back to `fallback`. */
88
+ function withTimeout(value, ms, fallback) {
89
+ return Promise.race([
90
+ Promise.resolve(value),
91
+ new Promise((resolve) => setTimeout(() => resolve(fallback), ms)),
92
+ ])
93
+ }
94
+
95
+ /**
96
+ * Resolve the `standard` agent's standing scope key when a roster is present.
97
+ *
98
+ * `standingKeyFor` mounts a whole agent preset (compose plugins) which can be
99
+ * slow or depend on services that are not ready yet — for a read-only inventory
100
+ * browse we must never let that hang the request. If it does not settle within
101
+ * a short budget we fall back to the global layer (scopeKey = undefined).
102
+ */
88
103
  async function resolveStandardScope(ctx) {
89
104
  try {
90
105
  const presets = ctx.get('agentPresets')
91
106
  if (presets && typeof presets.standingKeyFor === 'function') {
92
- return await presets.standingKeyFor('standard')
107
+ return await withTimeout(presets.standingKeyFor('standard'), 1500, undefined)
93
108
  }
94
109
  } catch { /* no roster — fall back to the global layer */ }
95
110
  return undefined
@@ -110,11 +125,15 @@ async function buildInventory(ctx, cfg) {
110
125
  // Resolve a section's text. Dynamic sections register a function; call it
111
126
  // with a best-effort context (same shape the assemble passes) so the
112
127
  // inventory can echo the real generated content for replacement. Fall back
113
- // to a marker if the function throws.
128
+ // to a marker if the function throws or returns a pending value.
114
129
  const resolveText = (section) => {
115
130
  if (typeof section.text === 'function') {
116
- try { return String(section.text({ scope: scopeKey }) ?? '') }
117
- catch { return '<动态生成>' }
131
+ try {
132
+ const value = section.text({ scope: scopeKey })
133
+ // Async dynamic sections return a thenable we cannot echo synchronously.
134
+ if (value && typeof value.then === 'function') return '<动态生成>'
135
+ return String(value ?? '')
136
+ } catch { return '<动态生成>' }
118
137
  }
119
138
  return String(section.text ?? '')
120
139
  }
@@ -131,15 +150,25 @@ async function buildInventory(ctx, cfg) {
131
150
  .sort((a, b) => a.order - b.order)
132
151
 
133
152
  let tools = []
153
+ // Enumerate tools from the resolved scope, falling back to the global layer
154
+ // when the scope key was unavailable (e.g. the standing mount timed out), so
155
+ // the catalog is never empty just because agent composition was not ready.
134
156
  try {
135
- const schemas = ctx.tools.schemas(scopeKey) ?? []
136
- tools = schemas
157
+ const byName = new Map()
158
+ for (const scope of [scopeKey, undefined]) {
159
+ let schemas = []
160
+ try { schemas = ctx.tools.schemas(scope) ?? [] } catch { schemas = [] }
161
+ for (const tool of schemas) {
162
+ if (tool.name && tool.name !== undefined && !byName.has(tool.name)) byName.set(tool.name, tool)
163
+ }
164
+ if (byName.size > 0) break
165
+ }
166
+ tools = [...byName.values()]
137
167
  .map((tool) => ({
138
168
  name: tool.name,
139
169
  description: typeof tool.description === 'string' ? tool.description : '',
140
170
  hidden: include ? !include.has(tool.name) : exclude.has(tool.name),
141
171
  }))
142
- .filter((tool) => tool.name && tool.name !== undefined)
143
172
  .sort((a, b) => a.name.localeCompare(b.name))
144
173
  } catch { /* tools registry unresolved — return empty list */ }
145
174
 
package/lib/schema.js CHANGED
@@ -12,11 +12,14 @@ export const Config = Schema.object({
12
12
  sections: Schema.array(Schema.string()).default([]),
13
13
  // Replace a section's text by name (original order kept).
14
14
  replace: Schema.dict(Schema.string()).default({}),
15
- // Inject / overwrite sections (by name + order + text).
15
+ // Inject / overwrite sections (by name + order + text). `custom` marks a
16
+ // section this plugin generated (a hidden marker so it can be identified as
17
+ // deletable, even across presets, without relying on name collisions).
16
18
  inject: Schema.array(Schema.object({
17
19
  name: Schema.string().required(),
18
20
  order: Schema.number().default(120),
19
21
  text: Schema.string().required(),
22
+ custom: Schema.boolean().default(false),
20
23
  })).default([]),
21
24
  // Tool catalog filtering: exclude hides listed tools; include (when non-empty)
22
25
  // keeps only listed tools and wins over exclude.
@@ -36,6 +39,7 @@ export const Config = Schema.object({
36
39
  name: Schema.string().required(),
37
40
  after: Schema.string(),
38
41
  text: Schema.string(),
42
+ custom: Schema.boolean().default(false),
39
43
  })),
40
44
  tools: Schema.object({
41
45
  exclude: Schema.array(Schema.string()),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-prompt-customizer",
3
3
  "description": "System-prompt & tool customizer for DeepSeek Harness with a settings-panel UI: block / replace / inject prompt sections by name, and hide tools from the model catalog by exclude/include. Config lives in the dsh settings document (namespace prompt-customizer).",
4
- "version": "0.2.3",
4
+ "version": "0.2.4",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "main": "lib/index.js",
@@ -2,7 +2,7 @@
2
2
  import { createElement as h, useState, type ReactElement, type DragEvent, type ChangeEvent } from 'react'
3
3
  import type { Config, Inventory, SettingsScope } from './types.ts'
4
4
  import type { Translate } from './locales.ts'
5
- import { mergeSections, type Section } from './presets.ts'
5
+ import { mergeSections, removeSection, type Section } from './presets.ts'
6
6
  import { s } from './styles.ts'
7
7
 
8
8
  export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {
@@ -44,16 +44,28 @@ export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventor
44
44
 
45
45
  // Persist the full ordered list. We treat the sorted list as an array and
46
46
  // re-index every section with a sequential integer order (0, 1, 2, …), so
47
- // there are never duplicate or fractional orders. text is left empty for
48
- // original sections (keeps their text); injected sections keep their own.
47
+ // there are never duplicate or fractional orders. System sections keep empty
48
+ // text (so we never freeze their dynamically generated content); custom
49
+ // (plugin-generated) sections keep their own text and the hidden `custom`
50
+ // marker, so they render real content and stay identifiable/deletable.
49
51
  const persistOrder = (ordered: Section[]): void => {
50
52
  const list = ordered.map((sec, i) => {
51
53
  const existing = (cfg.inject ?? []).find((x) => x.name === sec.name)
52
- return { name: sec.name, order: i, text: existing ? existing.text : '' }
54
+ const isCustom = sec.source === 'custom'
55
+ return { name: sec.name, order: i, text: isCustom ? (sec.text ?? '') : (existing?.text ?? ''), custom: isCustom }
53
56
  })
54
57
  scope.set('inject', list)
55
58
  }
56
59
 
60
+ // Delete a custom (plugin-added) section: remove it from inject / sections /
61
+ // replace so it no longer appears in the panel at all.
62
+ const removeCustom = (name: string): void => {
63
+ const patch = removeSection(name, cfg)
64
+ scope.set('sections', patch.sections)
65
+ scope.set('inject', patch.inject)
66
+ scope.set('replace', patch.replace)
67
+ }
68
+
57
69
  // Move a section up/down one slot (array-index swap), then re-index.
58
70
  const moveOrder = (index: number, dir: -1 | 1): void => {
59
71
  const target = index + dir
@@ -92,10 +104,12 @@ export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventor
92
104
  }
93
105
  const onDragEnd = (): void => { setDragName(null); setDropTarget(null) }
94
106
 
95
- // Add a new injected section, placed by its order hint, then re-index.
107
+ // Add a new injected section, placed by its order hint, then re-index. The
108
+ // section is marked `custom` (the hidden marker) so it is identified as
109
+ // plugin-generated and thus deletable, and its own text is preserved.
96
110
  const addSection = (name: string, order: number, text: string): void => {
97
111
  const next = merged.slice()
98
- const entry: Section = { name, order, text: text || '<动态生成>', active: true, replaced: false }
112
+ const entry: Section = { name, order, text, active: true, replaced: false, source: 'custom' }
99
113
  const existing = next.findIndex((x) => x.name === name)
100
114
  if (existing >= 0) next[existing] = entry
101
115
  else {
@@ -137,6 +151,7 @@ export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventor
137
151
  h('div', { style: s.rowTitle }, [
138
152
  h('span', { style: s.code }, sec.name),
139
153
  h('span', { style: s.orderTag }, '#' + index),
154
+ h('span', { style: sec.source === 'custom' ? s.badgeCustom : s.badgeSystem }, sec.source === 'custom' ? t('manual') : t('system')),
140
155
  sec.replaced ? h('span', { style: s.badgeReplaced }, t('replaced')) : null,
141
156
  ]),
142
157
  isEditing
@@ -147,13 +162,14 @@ export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventor
147
162
  h('button', { style: s.mini, onClick: () => setEditing(null) }, t('clearReplace')),
148
163
  ]),
149
164
  ])
150
- : h('div', { style: s.preview }, String(sec.text ?? '').slice(0, 140) || t('dynamic')),
165
+ : h('div', { style: s.preview }, String(sec.text ?? '').slice(0, 140) || (sec.source === 'custom' ? t('empty') : t('dynamic'))),
151
166
  ]),
152
167
  h('div', { style: s.arrowCol }, [
153
168
  h('button', { style: s.arrow, disabled: index === 0, onClick: () => moveOrder(index, -1), title: t('moveUp') }, '↑'),
154
169
  h('button', { style: s.arrow, disabled: index === merged.length - 1, onClick: () => moveOrder(index, 1), title: t('moveDown') }, '↓'),
155
170
  ]),
156
171
  h('button', { style: s.mini, onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? '')) }, replace[sec.name] ? t('clearReplace') : t('replace')),
172
+ sec.source === 'custom' ? h('button', { style: s.mini, onClick: () => removeCustom(sec.name), title: t('delete') }, t('delete')) : null,
157
173
  ])
158
174
  }),
159
175
  h('div', { style: s.injectBox }, [
@@ -11,8 +11,10 @@ export const DICT = {
11
11
  blockedOff: '未屏蔽',
12
12
  blockedOn: '已屏蔽',
13
13
  replaced: '已替换',
14
- replace: '替换',
15
- clearReplace: '清除替换',
14
+ manual: '手动',
15
+ system: '系统',
16
+ replace: '编辑',
17
+ clearReplace: '清除',
16
18
  injectNew: '注入新段',
17
19
  name: '名称',
18
20
  order: '顺序',
@@ -49,6 +51,8 @@ export const DICT = {
49
51
  blockedOff: 'Active',
50
52
  blockedOn: 'Blocked',
51
53
  replaced: 'Replaced',
54
+ manual: 'Manual',
55
+ system: 'System',
52
56
  replace: 'Replace',
53
57
  clearReplace: 'Clear',
54
58
  injectNew: 'Inject section',
@@ -7,6 +7,8 @@ export interface Section {
7
7
  text?: string
8
8
  active: boolean
9
9
  replaced: boolean
10
+ /** `system` = read from the host prompt inventory (not deletable); `custom` = added by this plugin (deletable). */
11
+ source?: 'system' | 'custom'
10
12
  }
11
13
 
12
14
  /** The config fields a preset apply must write back to the settings scope. */
@@ -17,20 +19,35 @@ export interface ConfigPatch {
17
19
  tools: { exclude: string[]; include: string[] }
18
20
  }
19
21
 
20
- /** Merge the inventory sections with the user's inject list, sorted by order. */
22
+ /**
23
+ * Merge the inventory sections with the user's inject list, sorted by order.
24
+ * Sections read from the inventory (other plugins) are `system`; sections this
25
+ * plugin generated carry a hidden `custom` marker and are `custom`. The source
26
+ * is decided by the hidden marker — never by name collisions — so a custom
27
+ * section keeps its identity (and its deletability) even when its name happens
28
+ * to match an inventory section, and across preset switches. Custom sections
29
+ * always render their own text; they can never be "<动态生成>".
30
+ */
21
31
  export function mergeSections(inv: Inventory | null, cfg: Config, blockedNames: ReadonlySet<string>): Section[] {
22
32
  const map = new Map<string, Section>()
23
- for (const sec of inv?.sections ?? []) map.set(sec.name, sec)
33
+ for (const sec of inv?.sections ?? []) map.set(sec.name, { ...sec, source: 'system' })
24
34
  for (const item of cfg.inject ?? []) {
35
+ const isCustom = item.custom === true
25
36
  const existing = map.get(item.name)
26
- if (existing) map.set(item.name, { ...existing, order: item.order })
27
- else map.set(item.name, {
28
- name: item.name,
29
- order: item.order,
30
- text: item.text || '<动态生成>',
31
- active: !blockedNames.has(item.name),
32
- replaced: false,
33
- })
37
+ if (existing) {
38
+ map.set(item.name, isCustom
39
+ ? { ...existing, order: item.order, text: item.text ?? '', source: 'custom' }
40
+ : { ...existing, order: item.order, source: 'system' })
41
+ } else {
42
+ map.set(item.name, {
43
+ name: item.name,
44
+ order: item.order,
45
+ text: item.text ?? '',
46
+ active: !blockedNames.has(item.name),
47
+ replaced: false,
48
+ source: isCustom ? 'custom' : 'system',
49
+ })
50
+ }
34
51
  }
35
52
  return [...map.values()].sort((a, b) => a.order - b.order)
36
53
  }
@@ -41,13 +58,15 @@ export function mergeSections(inv: Inventory | null, cfg: Config, blockedNames:
41
58
  * order; each anchored section is inserted right after its anchor; any
42
59
  * remaining sections (cycles) are appended.
43
60
  */
44
- export function resolveOrder(presetOrder: PresetData['order']): Array<{ name: string; order: number; text: string }> {
61
+ export function resolveOrder(presetOrder: PresetData['order']): Array<{ name: string; order: number; text: string; custom?: boolean }> {
45
62
  const list = presetOrder ?? []
46
63
  const afterMap = new Map<string, string | undefined>()
47
64
  const textMap = new Map<string, string>()
65
+ const customMap = new Map<string, boolean>()
48
66
  for (const sec of list) {
49
67
  afterMap.set(sec.name, sec.after)
50
68
  textMap.set(sec.name, sec.text)
69
+ customMap.set(sec.name, sec.custom === true)
51
70
  }
52
71
 
53
72
  const result: string[] = []
@@ -76,7 +95,7 @@ export function resolveOrder(presetOrder: PresetData['order']): Array<{ name: st
76
95
  if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }
77
96
  }
78
97
 
79
- return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '' }))
98
+ return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '', custom: customMap.get(name) }))
80
99
  }
81
100
 
82
101
  /**
@@ -86,10 +105,14 @@ export function resolveOrder(presetOrder: PresetData['order']): Array<{ name: st
86
105
  * ordered set, and thus a meaningful "active list" when applied.
87
106
  */
88
107
  export function buildPresetData(cfg: Config, merged: Section[]): PresetData {
108
+ // Only custom (plugin-added) sections carry their own text into the preset's
109
+ // order list. System sections keep an empty text so applying the preset only
110
+ // reorders them and never freezes their dynamically generated content.
89
111
  const order = merged.map((sec, i) => ({
90
112
  name: sec.name,
91
113
  after: i > 0 ? merged[i - 1].name : undefined,
92
- text: sec.text ?? '',
114
+ text: sec.source === 'custom' ? (sec.text ?? '') : '',
115
+ custom: sec.source === 'custom',
93
116
  }))
94
117
  return {
95
118
  sections: cfg.sections,
@@ -106,12 +129,29 @@ export function buildPresetData(cfg: Config, merged: Section[]): PresetData {
106
129
  * - current sections NOT in the preset's ordered list are disabled by default
107
130
  * - only the preset's ACTIVE sections (in the order list but not in its block
108
131
  * list) are unblocked; the preset's own blocked sections stay blocked.
132
+ *
133
+ * Custom injected sections that exist in the current config but are NOT in the
134
+ * preset's ordered list are preserved in the resulting inject list (so they
135
+ * stay visible) and disabled (added to the blocked set), rather than silently
136
+ * dropped — a preset must not make the user's own injected sections vanish.
109
137
  */
110
138
  export function applyPresetData(data: PresetData, cfg: Config, currentNames: ReadonlySet<string>): ConfigPatch {
111
139
  const presetOrder = data.order ?? []
112
140
  const presetNames = new Set(presetOrder.map((x) => x.name))
113
141
  const blocked = new Set(data.sections ?? [])
114
142
 
143
+ // Inject list = the preset's resolved order, then any custom injected
144
+ // sections present in the current config but absent from the preset list.
145
+ // Keeping them in `inject` makes them visible; they are disabled below.
146
+ const inject = resolveOrder(presetOrder)
147
+ const kept = new Set(inject.map((x) => x.name))
148
+ let order = inject.length
149
+ for (const item of cfg.inject ?? []) {
150
+ if (!item || !item.name || presetNames.has(item.name) || kept.has(item.name)) continue
151
+ kept.add(item.name)
152
+ inject.push({ name: item.name, order: order++, text: item.text ?? '', custom: item.custom === true })
153
+ }
154
+
115
155
  // Only enforce the "active set" when the preset actually defines an order.
116
156
  // An empty order (e.g. a hand-authored / imported preset that only blocks a
117
157
  // few sections) must not default-disable every current section.
@@ -124,11 +164,25 @@ export function applyPresetData(data: PresetData, cfg: Config, currentNames: Rea
124
164
  return {
125
165
  sections: [...blocked],
126
166
  replace: { ...(cfg.replace ?? {}), ...(data.replace ?? {}) },
127
- inject: resolveOrder(presetOrder),
167
+ inject,
128
168
  tools: { exclude: data.tools?.exclude ?? [], include: data.tools?.include ?? [] },
129
169
  }
130
170
  }
131
171
 
172
+ /**
173
+ * Remove a custom (plugin-added) section from the config: it disappears from
174
+ * the inject list, is no longer forced into the blocked set, and any replace
175
+ * text for it is cleared. System sections read from the host inventory cannot
176
+ * be removed this way (they would just come back on the next inventory read).
177
+ */
178
+ export function removeSection(name: string, cfg: Config): Pick<Config, 'sections' | 'inject' | 'replace'> {
179
+ const sections = (cfg.sections ?? []).filter((n) => n !== name)
180
+ const inject = (cfg.inject ?? []).filter((item) => item.name !== name)
181
+ const replace = { ...(cfg.replace ?? {}) }
182
+ delete replace[name]
183
+ return { sections, inject, replace }
184
+ }
185
+
132
186
  // ── Tool filtering ──────────────────────────────────────────────────────────
133
187
 
134
188
  export type ToolsCfg = { exclude?: string[]; include?: string[] }
@@ -18,6 +18,8 @@ export const s = (() => {
18
18
  badgeOk: { color: '#3fb950', fontSize: 11, border: '1px solid rgba(63,185,80,.5)', borderRadius: 999, padding: '0 6px' },
19
19
  badgeBlocked: { color: '#f85149', fontSize: 11, border: '1px solid rgba(248,81,73,.5)', borderRadius: 999, padding: '0 6px' },
20
20
  badgeReplaced: { color: '#d29922', fontSize: 11, border: '1px solid rgba(210,153,34,.5)', borderRadius: 999, padding: '0 6px' },
21
+ badgeCustom: { color: '#58a6ff', fontSize: 11, border: '1px solid rgba(88,166,255,.5)', borderRadius: 999, padding: '0 6px' },
22
+ badgeSystem: { color: 'rgba(128,128,128,.85)', fontSize: 11, border: '1px solid rgba(128,128,128,.4)', borderRadius: 999, padding: '0 6px' },
21
23
  mini: { padding: '3px 8px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', whiteSpace: 'nowrap' },
22
24
  arrowCol: { display: 'flex', flexDirection: 'column', gap: 2 },
23
25
  arrow: { padding: '0 6px', border: '1px solid rgba(128,128,128,.3)', borderRadius: 4, background: 'transparent', cursor: 'pointer', color: 'inherit', fontSize: 11, lineHeight: 1.4, opacity: 0.8 },
@@ -18,7 +18,7 @@ export interface Inventory {
18
18
  export interface Config {
19
19
  sections?: string[]
20
20
  replace?: Record<string, string>
21
- inject?: Array<{ name: string; order: number; text: string }>
21
+ inject?: Array<{ name: string; order: number; text: string; custom?: boolean }>
22
22
  tools?: { exclude?: string[]; include?: string[] }
23
23
  presets?: Preset[]
24
24
  activePreset?: string
@@ -36,6 +36,6 @@ export interface PresetData {
36
36
  sections?: string[]
37
37
  replace?: Record<string, string>
38
38
  /** Relative order: each section records the section it should follow. */
39
- order?: Array<{ name: string; after?: string; text: string }>
39
+ order?: Array<{ name: string; after?: string; text: string; custom?: boolean }>
40
40
  tools?: { exclude?: string[]; include?: string[] }
41
41
  }