dsh-prompt-customizer 0.2.4 → 0.2.6

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
@@ -15,6 +15,7 @@ window.__ModuleLoader__.load({
15
15
  tabsSections: "提示词",
16
16
  tabsTools: "工具",
17
17
  tabsPresets: "预设",
18
+ tabsPreview: "预览",
18
19
  refresh: "刷新",
19
20
  blockedOff: "未屏蔽",
20
21
  blockedOn: "已屏蔽",
@@ -30,13 +31,18 @@ window.__ModuleLoader__.load({
30
31
  add: "添加",
31
32
  hiddenOff: "可见",
32
33
  hiddenOn: "已隐藏",
33
- includeMode: "白名单模式:仅保留勾选项",
34
- excludeMode: "黑名单模式:勾选 = 隐藏",
34
+ selectAll: "全选",
35
+ selectNone: "全不选",
35
36
  empty: "(空)",
36
37
  dynamic: "<动态生成>",
37
38
  drag: "拖动排序",
38
39
  moveUp: "上移",
39
40
  moveDown: "下移",
41
+ previewHint: "以下为屏蔽/替换/注入后的最终系统提示词:",
42
+ previewPrompt: "提示词预览",
43
+ previewSections: "段",
44
+ previewTools: "工具预览",
45
+ previewToolCount: "个工具",
40
46
  savePreset: "保存当前为预设",
41
47
  presetName: "预设名称",
42
48
  save: "保存",
@@ -55,6 +61,7 @@ window.__ModuleLoader__.load({
55
61
  tabsSections: "Sections",
56
62
  tabsTools: "Tools",
57
63
  tabsPresets: "Presets",
64
+ tabsPreview: "Preview",
58
65
  refresh: "Refresh",
59
66
  blockedOff: "Active",
60
67
  blockedOn: "Blocked",
@@ -70,13 +77,18 @@ window.__ModuleLoader__.load({
70
77
  add: "Add",
71
78
  hiddenOff: "Visible",
72
79
  hiddenOn: "Hidden",
73
- includeMode: "Whitelist: keep only checked",
74
- excludeMode: "Blacklist: check to hide",
80
+ selectAll: "Select all",
81
+ selectNone: "Select none",
75
82
  empty: "(empty)",
76
83
  dynamic: "<dynamic>",
77
84
  drag: "Drag to reorder",
78
85
  moveUp: "Move up",
79
86
  moveDown: "Move down",
87
+ previewHint: "Final system prompt after block/replace/inject:",
88
+ previewPrompt: "Prompt preview",
89
+ previewSections: "sections",
90
+ previewTools: "Tools preview",
91
+ previewToolCount: "tools",
80
92
  savePreset: "Save current as preset",
81
93
  presetName: "Preset name",
82
94
  save: "Save",
@@ -179,6 +191,34 @@ window.__ModuleLoader__.load({
179
191
  textOverflow: "ellipsis",
180
192
  whiteSpace: "nowrap"
181
193
  },
194
+ previewText: {
195
+ fontFamily: "monospace",
196
+ fontSize: 12,
197
+ lineHeight: 1.6,
198
+ whiteSpace: "pre-wrap",
199
+ wordBreak: "break-word",
200
+ maxHeight: 480,
201
+ overflowY: "auto",
202
+ padding: 10,
203
+ border: "1px solid rgba(128,128,128,.2)",
204
+ borderRadius: 8,
205
+ background: "rgba(0,0,0,.18)",
206
+ margin: 0
207
+ },
208
+ toolWrap: {
209
+ display: "flex",
210
+ flexWrap: "wrap",
211
+ gap: 6
212
+ },
213
+ toolChip: {
214
+ fontFamily: "monospace",
215
+ fontSize: 12,
216
+ color: "rgba(128,128,128,.95)",
217
+ border: "1px solid rgba(128,128,128,.3)",
218
+ borderRadius: 6,
219
+ padding: "2px 6px",
220
+ cursor: "help"
221
+ },
182
222
  badgeOk: {
183
223
  color: "#3fb950",
184
224
  fontSize: 11,
@@ -459,12 +499,17 @@ window.__ModuleLoader__.load({
459
499
  /**
460
500
  * Remove a custom (plugin-added) section from the config: it disappears from
461
501
  * 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).
502
+ * text for it is cleared. The remaining inject entries are re-indexed to a
503
+ * contiguous 0..n-1 order (the virtual index model), so removal never leaves
504
+ * holes. System sections read from the host inventory cannot be removed this
505
+ * way (they would just come back on the next inventory read).
464
506
  */
465
507
  function removeSection(name, cfg) {
466
508
  const sections = (cfg.sections ?? []).filter((n) => n !== name);
467
- const inject = (cfg.inject ?? []).filter((item) => item.name !== name);
509
+ const inject = (cfg.inject ?? []).filter((item) => item.name !== name).map((item, i) => ({
510
+ ...item,
511
+ order: i
512
+ }));
468
513
  const replace = { ...cfg.replace ?? {} };
469
514
  delete replace[name];
470
515
  return {
@@ -473,56 +518,6 @@ window.__ModuleLoader__.load({
473
518
  replace
474
519
  };
475
520
  }
476
- /** Whether a tool is hidden under the current include/exclude config. */
477
- function isToolHidden(name, toolsCfg) {
478
- const include = toolsCfg?.include ?? [];
479
- const exclude = toolsCfg?.exclude ?? [];
480
- return include.length > 0 ? !include.includes(name) : exclude.includes(name);
481
- }
482
- /** Toggle a tool's hidden state, returning a new tools config. */
483
- function toggleTool(name, currentlyHidden, toolsCfg) {
484
- const include = toolsCfg?.include ?? [];
485
- const exclude = toolsCfg?.exclude ?? [];
486
- if (include.length > 0) {
487
- const next = include.slice();
488
- if (currentlyHidden) next.push(name);
489
- else {
490
- const i = next.indexOf(name);
491
- if (i >= 0) next.splice(i, 1);
492
- }
493
- return {
494
- exclude,
495
- include: next
496
- };
497
- }
498
- const next = exclude.slice();
499
- if (currentlyHidden) {
500
- const i = next.indexOf(name);
501
- if (i >= 0) next.splice(i, 1);
502
- } else next.push(name);
503
- return {
504
- exclude: next,
505
- include
506
- };
507
- }
508
- /**
509
- * Switch between include (whitelist) and exclude (blacklist) modes. Turning
510
- * include mode on discards names unknown to the current tool set and seeds the
511
- * include list from the currently non-excluded tools; turning it off clears the
512
- * include list (keep only known exclude names).
513
- */
514
- function setToolMode(on, toolsCfg, toolNames) {
515
- const known = new Set(toolNames);
516
- const exclude = (toolsCfg?.exclude ?? []).filter((n) => known.has(n));
517
- if (on) return {
518
- exclude,
519
- include: toolNames.filter((n) => !exclude.includes(n))
520
- };
521
- return {
522
- exclude,
523
- include: []
524
- };
525
- }
526
521
  /** Serialize a preset for export (JSON-safe full snapshot). */
527
522
  function serializePreset(preset) {
528
523
  return {
@@ -799,36 +794,44 @@ window.__ModuleLoader__.load({
799
794
  }
800
795
  //#endregion
801
796
  //#region src/client/ToolsTab.tsx
802
- /** Tools tab: hide tools from the model catalog by exclude/include. */
797
+ /** Tools tab: hide tools from the model catalog (blacklist only — check = hide). */
803
798
  function ToolsTab({ cfg, inv, scope, t }) {
804
799
  const tools = inv?.tools ?? [];
805
- const toolsCfg = cfg.tools ?? {
806
- exclude: [],
807
- include: []
808
- };
809
- const includeMode = (toolsCfg.include ?? []).length > 0;
800
+ const exclude = cfg.tools?.exclude ?? [];
801
+ const hidden = (name) => exclude.includes(name);
810
802
  const toggle = (name, currentlyHidden) => {
811
- scope.set("tools", toggleTool(name, currentlyHidden, toolsCfg));
803
+ const next = exclude.slice();
804
+ if (currentlyHidden) {
805
+ const i = next.indexOf(name);
806
+ if (i >= 0) next.splice(i, 1);
807
+ } else next.push(name);
808
+ scope.set("tools", {
809
+ exclude: next,
810
+ include: []
811
+ });
812
812
  };
813
- const setIncludeMode = (on) => {
814
- scope.set("tools", setToolMode(on, toolsCfg, tools.map((tool) => tool.name)));
813
+ const setAll = (hideAll) => {
814
+ scope.set("tools", {
815
+ exclude: hideAll ? tools.map((tool) => tool.name) : [],
816
+ include: []
817
+ });
815
818
  };
816
819
  return (0, react.createElement)("div", {}, [(0, react.createElement)("div", { style: s.bar }, [(0, react.createElement)("button", {
817
- style: includeMode ? s.tabActive : s.tab,
818
- onClick: () => setIncludeMode(true)
819
- }, t("includeMode")), (0, react.createElement)("button", {
820
- style: includeMode ? s.tab : s.tabActive,
821
- onClick: () => setIncludeMode(false)
822
- }, t("excludeMode"))]), (0, react.createElement)("div", { style: s.list }, [tools.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null, tools.map((tool) => {
823
- const hidden = isToolHidden(tool.name, toolsCfg);
820
+ style: s.mini,
821
+ onClick: () => setAll(true)
822
+ }, t("selectAll")), (0, react.createElement)("button", {
823
+ style: s.mini,
824
+ onClick: () => setAll(false)
825
+ }, t("selectNone"))]), (0, react.createElement)("div", { style: s.list }, [tools.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null, tools.map((tool) => {
826
+ const isHidden = hidden(tool.name);
824
827
  return (0, react.createElement)("div", {
825
828
  key: tool.name,
826
829
  style: s.row
827
830
  }, [(0, react.createElement)("label", { style: s.switchWrap }, [(0, react.createElement)("input", {
828
831
  type: "checkbox",
829
- checked: hidden,
830
- onChange: () => toggle(tool.name, hidden)
831
- }), (0, react.createElement)("span", { style: hidden ? s.badgeBlocked : s.badgeOk }, hidden ? t("hiddenOn") : t("hiddenOff"))]), (0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, (0, react.createElement)("span", { style: s.code }, tool.name)), (0, react.createElement)("div", { style: s.preview }, String(tool.description ?? "").slice(0, 140))])]);
832
+ checked: isHidden,
833
+ onChange: () => toggle(tool.name, isHidden)
834
+ }), (0, react.createElement)("span", { style: isHidden ? s.badgeBlocked : s.badgeOk }, isHidden ? t("hiddenOn") : t("hiddenOff"))]), (0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, (0, react.createElement)("span", { style: s.code }, tool.name)), (0, react.createElement)("div", { style: s.preview }, String(tool.description ?? "").slice(0, 140))])]);
832
835
  })])]);
833
836
  }
834
837
  //#endregion
@@ -936,6 +939,69 @@ window.__ModuleLoader__.load({
936
939
  return "p_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
937
940
  }
938
941
  //#endregion
942
+ //#region src/client/PreviewTools.tsx
943
+ /** Tools preview: read-only whitelist-style list of the tools the current
944
+ * preset will inject (the FINAL model-visible catalog after include/exclude).
945
+ * A tool being listed means it is visible — there is no "blocked" state here.
946
+ * This view never edits the blacklist/whitelist config. */
947
+ function norm(tool) {
948
+ return typeof tool === "string" ? {
949
+ name: tool,
950
+ description: ""
951
+ } : tool;
952
+ }
953
+ function PreviewTools({ tools, t }) {
954
+ if (tools.length === 0) return (0, react.createElement)("div", { style: s.muted }, t("empty"));
955
+ return (0, react.createElement)("div", { style: s.list }, [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.orderTag }, `${tools.length} ${t("previewToolCount")}`)]), tools.map((tool) => {
956
+ const { name, description } = norm(tool);
957
+ return (0, react.createElement)("div", {
958
+ key: name,
959
+ style: s.row
960
+ }, [(0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, (0, react.createElement)("span", { style: s.code }, name)), (0, react.createElement)("div", { style: s.preview }, String(description ?? "").slice(0, 140))])]);
961
+ })]);
962
+ }
963
+ //#endregion
964
+ //#region src/client/PreviewTab.tsx
965
+ /** Preview tab: two read-only sub-tabs — the FINAL assembled system prompt
966
+ * after every plugin's filter, and the tools the current preset will inject
967
+ * (whitelist-style, after include/exclude). Neither view edits config. */
968
+ const PREVIEW_URL = "/api/prompt-customizer/preview";
969
+ function PreviewTab({ scope, t }) {
970
+ const [sub, setSub] = (0, react.useState)("prompt");
971
+ const [data, setData] = (0, react.useState)(null);
972
+ const [error, setError] = (0, react.useState)(null);
973
+ const inFlight = (0, react.useRef)(false);
974
+ const load = () => {
975
+ if (inFlight.current) return;
976
+ inFlight.current = true;
977
+ setData(null);
978
+ fetch(PREVIEW_URL).then((r) => r.json()).then((body) => {
979
+ if (body && body.ok === false) throw new Error(body.error ?? "preview failed");
980
+ setData(body);
981
+ setError(null);
982
+ }).catch((e) => setError(String(e instanceof Error ? e.message : e))).finally(() => {
983
+ inFlight.current = false;
984
+ });
985
+ };
986
+ (0, react.useEffect)(load, []);
987
+ (0, react.useEffect)(() => scope.subscribe(load), [scope]);
988
+ return (0, react.createElement)("div", { style: s.list }, [
989
+ (0, react.createElement)("div", { style: s.bar }, [(0, react.createElement)("button", {
990
+ style: sub === "prompt" ? s.tabActive : s.tab,
991
+ onClick: () => setSub("prompt")
992
+ }, t("previewPrompt")), (0, react.createElement)("button", {
993
+ style: sub === "tools" ? s.tabActive : s.tab,
994
+ onClick: () => setSub("tools")
995
+ }, t("previewTools"))]),
996
+ error ? (0, react.createElement)("div", { style: s.error }, String(error)) : null,
997
+ data === null && !error ? (0, react.createElement)("div", { style: s.muted }, t("loading")) : null,
998
+ sub === "prompt" ? data ? [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.muted }, t("previewHint")), (0, react.createElement)("span", { style: s.orderTag }, `${data.sections.length} ${t("previewSections")}`)]), (0, react.createElement)("pre", { style: s.previewText }, data.text || t("empty"))] : null : data ? (0, react.createElement)(PreviewTools, {
999
+ tools: data.tools,
1000
+ t
1001
+ }) : null
1002
+ ]);
1003
+ }
1004
+ //#endregion
939
1005
  //#region src/client/index.ts
940
1006
  /**
941
1007
  * dsh-prompt-customizer — browser half (TypeScript source).
@@ -1008,6 +1074,10 @@ window.__ModuleLoader__.load({
1008
1074
  style: tab === "presets" ? s.tabActive : s.tab,
1009
1075
  onClick: () => setTab("presets")
1010
1076
  }, t("tabsPresets")),
1077
+ (0, react.createElement)("button", {
1078
+ style: tab === "preview" ? s.tabActive : s.tab,
1079
+ onClick: () => setTab("preview")
1080
+ }, t("tabsPreview")),
1011
1081
  (0, react.createElement)("button", {
1012
1082
  style: s.refresh,
1013
1083
  onClick: refresh
@@ -1024,11 +1094,14 @@ window.__ModuleLoader__.load({
1024
1094
  inv,
1025
1095
  scope,
1026
1096
  t
1027
- }) : (0, react.createElement)(PresetsTab, {
1097
+ }) : tab === "presets" ? (0, react.createElement)(PresetsTab, {
1028
1098
  cfg,
1029
1099
  inv,
1030
1100
  scope,
1031
1101
  t
1102
+ }) : (0, react.createElement)(PreviewTab, {
1103
+ scope,
1104
+ t
1032
1105
  })
1033
1106
  ]);
1034
1107
  }
@@ -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 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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"}
1
+ {"version":3,"file":"client.js","names":["useState","h","h","useState","useRef","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/PreviewTools.tsx","../src/client/PreviewTab.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 tabsPreview: '预览',\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 selectAll: '全选',\n selectNone: '全不选',\n empty: '(空)',\n dynamic: '<动态生成>',\n drag: '拖动排序',\n moveUp: '上移',\n moveDown: '下移',\n previewHint: '以下为屏蔽/替换/注入后的最终系统提示词:',\n previewPrompt: '提示词预览',\n previewSections: '段',\n previewTools: '工具预览',\n previewToolCount: '个工具',\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 tabsPreview: 'Preview',\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 selectAll: 'Select all',\n selectNone: 'Select none',\n empty: '(empty)',\n dynamic: '<dynamic>',\n drag: 'Drag to reorder',\n moveUp: 'Move up',\n moveDown: 'Move down',\n previewHint: 'Final system prompt after block/replace/inject:',\n previewPrompt: 'Prompt preview',\n previewSections: 'sections',\n previewTools: 'Tools preview',\n previewToolCount: 'tools',\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 previewText: { fontFamily: 'monospace', fontSize: 12, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 480, overflowY: 'auto', padding: 10, border: '1px solid rgba(128,128,128,.2)', borderRadius: 8, background: 'rgba(0,0,0,.18)', margin: 0 },\n toolWrap: { display: 'flex', flexWrap: 'wrap', gap: 6 },\n toolChip: { fontFamily: 'monospace', fontSize: 12, color: 'rgba(128,128,128,.95)', border: '1px solid rgba(128,128,128,.3)', borderRadius: 6, padding: '2px 6px', cursor: 'help' },\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 // Kept customs continue after the preset's own indexes; if the preset covers\n // only a small subset they may tie with the inventory's native orders — the\n // merge view sorts stably (inventory first), so ordering stays deterministic,\n // and the next reorder/persist re-indexes everything contiguously anyway.\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. The remaining inject entries are re-indexed to a\n * contiguous 0..n-1 order (the virtual index model), so removal never leaves\n * holes. System sections read from the host inventory cannot be removed this\n * 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 ?? [])\n .filter((item) => item.name !== name)\n .map((item, i) => ({ ...item, order: i }))\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) {\n next.push(name)\n // Re-showing a tool in whitelist mode must also clear any stale blacklist\n // entry, otherwise the name lingers in both lists and reappears as hidden\n // if the user later switches back to blacklist mode.\n const nextExclude = exclude.filter((n) => n !== name)\n return { exclude: nextExclude, include: next }\n }\n const i = next.indexOf(name)\n 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 (blacklist only — check = hide). */\nimport { createElement as h, type ReactElement } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.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 exclude = cfg.tools?.exclude ?? []\n\n // Blacklist-only: a tool is hidden iff it is in the exclude list. Any stale\n // whitelist (include) is discarded on write so mode switching is gone.\n const hidden = (name: string): boolean => exclude.includes(name)\n\n const toggle = (name: string, currentlyHidden: boolean): void => {\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 scope.set('tools', { exclude: next, include: [] })\n }\n\n const setAll = (hideAll: boolean): void => {\n scope.set('tools', { exclude: hideAll ? tools.map((tool) => tool.name) : [], include: [] })\n }\n\n return h('div', {}, [\n h('div', { style: s.bar }, [\n h('button', { style: s.mini, onClick: () => setAll(true) }, t('selectAll')),\n h('button', { style: s.mini, onClick: () => setAll(false) }, t('selectNone')),\n ]),\n h('div', { style: s.list }, [\n tools.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n tools.map((tool) => {\n const isHidden = hidden(tool.name)\n return h('div', { key: tool.name, style: s.row }, [\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: isHidden, onChange: () => toggle(tool.name, isHidden) }),\n h('span', { style: isHidden ? s.badgeBlocked : s.badgeOk }, isHidden ? 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","/** Tools preview: read-only whitelist-style list of the tools the current\n * preset will inject (the FINAL model-visible catalog after include/exclude).\n * A tool being listed means it is visible — there is no \"blocked\" state here.\n * This view never edits the blacklist/whitelist config. */\nimport { createElement as h, type ReactElement } from 'react'\nimport type { Translate } from './locales.ts'\nimport { s } from './styles.ts'\n\nexport type PreviewTool = { name: string; description: string } | string\n\nfunction norm(tool: PreviewTool): { name: string; description: string } {\n return typeof tool === 'string' ? { name: tool, description: '' } : tool\n}\n\nexport function PreviewTools({ tools, t }: { tools: PreviewTool[]; t: Translate }): ReactElement {\n if (tools.length === 0) return h('div', { style: s.muted }, t('empty'))\n return h('div', { style: s.list }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.orderTag }, `${tools.length} ${t('previewToolCount')}`),\n ]),\n tools.map((tool) => {\n const { name, description } = norm(tool)\n return h('div', { key: name, style: s.row }, [\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, h('span', { style: s.code }, name)),\n h('div', { style: s.preview }, String(description ?? '').slice(0, 140)),\n ]),\n ])\n }),\n ])\n}\n","/** Preview tab: two read-only sub-tabs — the FINAL assembled system prompt\n * after every plugin's filter, and the tools the current preset will inject\n * (whitelist-style, after include/exclude). Neither view edits config. */\nimport { createElement as h, useEffect, useRef, useState, type ReactElement } from 'react'\nimport type { Preview, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { s } from './styles.ts'\nimport { PreviewTools } from './PreviewTools.tsx'\n\nconst PREVIEW_URL = '/api/prompt-customizer/preview'\n\nexport function PreviewTab({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {\n const [sub, setSub] = useState<'prompt' | 'tools'>('prompt')\n const [data, setData] = useState<Preview | null>(null)\n const [error, setError] = useState<string | null>(null)\n\n // Reload whenever the namespace config changes (block/replace/inject), so the\n // section count and final text always reflect the current customization —\n // not just on mount.\n const inFlight = useRef(false)\n const load = (): void => {\n if (inFlight.current) return\n inFlight.current = true\n setData(null)\n fetch(PREVIEW_URL)\n .then((r) => r.json())\n .then((body: Preview) => {\n if (body && body.ok === false) throw new Error((body as { error?: string }).error ?? 'preview failed')\n setData(body)\n setError(null)\n })\n .catch((e: unknown) => setError(String(e instanceof Error ? e.message : e)))\n .finally(() => { inFlight.current = false })\n }\n useEffect(load, [])\n useEffect(() => scope.subscribe(load), [scope])\n\n return h('div', { style: s.list }, [\n h('div', { style: s.bar }, [\n h('button', { style: sub === 'prompt' ? s.tabActive : s.tab, onClick: () => setSub('prompt') }, t('previewPrompt')),\n h('button', { style: sub === 'tools' ? s.tabActive : s.tab, onClick: () => setSub('tools') }, t('previewTools')),\n ]),\n error ? h('div', { style: s.error }, String(error)) : null,\n data === null && !error ? h('div', { style: s.muted }, t('loading')) : null,\n sub === 'prompt'\n ? data\n ? [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.muted }, t('previewHint')),\n h('span', { style: s.orderTag }, `${data.sections.length} ${t('previewSections')}`),\n ]),\n h('pre', { style: s.previewText }, data.text || t('empty')),\n ]\n : null\n : data\n ? h(PreviewTools, { tools: data.tools, t })\n : null,\n ])\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'\nimport { PreviewTab } from './PreviewTab.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' | 'preview'>('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: tab === 'preview' ? s.tabActive : s.tab, onClick: () => setTab('preview') }, t('tabsPreview')),\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 : tab === 'presets'\n ? h(PresetsTab, { cfg, inv, scope, t })\n : h(PreviewTab, { scope, t }),\n ])\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -81,6 +81,28 @@ export function apply(ctx, entry = {}) {
81
81
  },
82
82
  })
83
83
  ctx.effect(() => dispose, 'prompt-customizer: inventory route')
84
+
85
+ // Preview route: render the FINAL assembled system prompt (after every
86
+ // plugin, including our own block/replace/inject filters) so the user can
87
+ // confirm no injected section from other plugins pollutes it.
88
+ // Optional `?scope=<agentPresetId>` previews a specific agent preset's
89
+ // assembly; omitted, it uses the standard agent (same as the inventory).
90
+ const disposePreview = webserver.register({
91
+ kind: 'exact',
92
+ path: '/api/prompt-customizer/preview',
93
+ handler: async (req, res) => {
94
+ try {
95
+ const scopeKey = await previewScope(ctx, req)
96
+ writeJson(res, 200, await buildPreview(ctx, scopeKey))
97
+ } catch (error) {
98
+ writeJson(res, 500, {
99
+ ok: false,
100
+ error: String(error && error.message ? error.message : error),
101
+ })
102
+ }
103
+ },
104
+ })
105
+ ctx.effect(() => disposePreview, 'prompt-customizer: preview route')
84
106
  }
85
107
  }
86
108
 
@@ -175,6 +197,58 @@ async function buildInventory(ctx, cfg) {
175
197
  return { sections, tools, config: cfg }
176
198
  }
177
199
 
200
+ /**
201
+ * Resolve the scope key a preview request targets. An explicit `?scope=<id>`
202
+ * requests that agent preset's standing scope; otherwise fall back to the
203
+ * `standard` agent (same default the inventory uses). Always resolves within
204
+ * the same short timeout so a slow standing mount cannot hang the request.
205
+ */
206
+ async function previewScope(ctx, req) {
207
+ const id = req.query && req.query.scope
208
+ if (typeof id === 'string' && id.length > 0) {
209
+ try {
210
+ const presets = ctx.get('agentPresets')
211
+ if (presets && typeof presets.standingKeyFor === 'function') {
212
+ const key = await withTimeout(presets.standingKeyFor(id), 1500, undefined)
213
+ if (key) return key
214
+ }
215
+ } catch { /* unknown preset — fall back to the standard agent */ }
216
+ }
217
+ return resolveStandardScope(ctx)
218
+ }
219
+
220
+ /**
221
+ * Render the FINAL system prompt for a scope: run the full assemble waterfall
222
+ * (so every plugin's filter, including our own, is applied), then render the
223
+ * surviving sections to text. This is exactly what the model would see.
224
+ */
225
+ async function buildPreview(ctx, scopeKey) {
226
+ const assembled = await ctx.systemPrompt.assemble({ scope: scopeKey })
227
+ const sections = Array.isArray(assembled.sections)
228
+ ? assembled.sections.map((section) => ({ name: section.name, text: String(section.text ?? '') }))
229
+ : []
230
+ return {
231
+ ok: true,
232
+ scope: scopeKey,
233
+ sections,
234
+ text: renderPreviewText(assembled),
235
+ tools: Array.isArray(assembled.tools)
236
+ ? assembled.tools.map((tool) => ({
237
+ name: typeof tool.name === 'string' ? tool.name : String(tool.name),
238
+ description: typeof tool.description === 'string' ? tool.description : '',
239
+ }))
240
+ : [],
241
+ }
242
+ }
243
+
244
+ /** Join the surviving sections into the final prompt text (like dsh's renderPrompt). */
245
+ function renderPreviewText(assembled) {
246
+ return (Array.isArray(assembled.sections) ? assembled.sections : [])
247
+ .map((section) => String(section.text ?? '').trim())
248
+ .filter((text) => text.length > 0)
249
+ .join('\n\n')
250
+ }
251
+
178
252
  function writeJson(res, status, body) {
179
253
  res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
180
254
  res.end(JSON.stringify(body))
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.4",
4
+ "version": "0.2.6",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "main": "lib/index.js",
@@ -0,0 +1,59 @@
1
+ /** Preview tab: two read-only sub-tabs — the FINAL assembled system prompt
2
+ * after every plugin's filter, and the tools the current preset will inject
3
+ * (whitelist-style, after include/exclude). Neither view edits config. */
4
+ import { createElement as h, useEffect, useRef, useState, type ReactElement } from 'react'
5
+ import type { Preview, SettingsScope } from './types.ts'
6
+ import type { Translate } from './locales.ts'
7
+ import { s } from './styles.ts'
8
+ import { PreviewTools } from './PreviewTools.tsx'
9
+
10
+ const PREVIEW_URL = '/api/prompt-customizer/preview'
11
+
12
+ export function PreviewTab({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {
13
+ const [sub, setSub] = useState<'prompt' | 'tools'>('prompt')
14
+ const [data, setData] = useState<Preview | null>(null)
15
+ const [error, setError] = useState<string | null>(null)
16
+
17
+ // Reload whenever the namespace config changes (block/replace/inject), so the
18
+ // section count and final text always reflect the current customization —
19
+ // not just on mount.
20
+ const inFlight = useRef(false)
21
+ const load = (): void => {
22
+ if (inFlight.current) return
23
+ inFlight.current = true
24
+ setData(null)
25
+ fetch(PREVIEW_URL)
26
+ .then((r) => r.json())
27
+ .then((body: Preview) => {
28
+ if (body && body.ok === false) throw new Error((body as { error?: string }).error ?? 'preview failed')
29
+ setData(body)
30
+ setError(null)
31
+ })
32
+ .catch((e: unknown) => setError(String(e instanceof Error ? e.message : e)))
33
+ .finally(() => { inFlight.current = false })
34
+ }
35
+ useEffect(load, [])
36
+ useEffect(() => scope.subscribe(load), [scope])
37
+
38
+ return h('div', { style: s.list }, [
39
+ h('div', { style: s.bar }, [
40
+ h('button', { style: sub === 'prompt' ? s.tabActive : s.tab, onClick: () => setSub('prompt') }, t('previewPrompt')),
41
+ h('button', { style: sub === 'tools' ? s.tabActive : s.tab, onClick: () => setSub('tools') }, t('previewTools')),
42
+ ]),
43
+ error ? h('div', { style: s.error }, String(error)) : null,
44
+ data === null && !error ? h('div', { style: s.muted }, t('loading')) : null,
45
+ sub === 'prompt'
46
+ ? data
47
+ ? [
48
+ h('div', { style: s.rowTitle }, [
49
+ h('span', { style: s.muted }, t('previewHint')),
50
+ h('span', { style: s.orderTag }, `${data.sections.length} ${t('previewSections')}`),
51
+ ]),
52
+ h('pre', { style: s.previewText }, data.text || t('empty')),
53
+ ]
54
+ : null
55
+ : data
56
+ ? h(PreviewTools, { tools: data.tools, t })
57
+ : null,
58
+ ])
59
+ }
@@ -0,0 +1,31 @@
1
+ /** Tools preview: read-only whitelist-style list of the tools the current
2
+ * preset will inject (the FINAL model-visible catalog after include/exclude).
3
+ * A tool being listed means it is visible — there is no "blocked" state here.
4
+ * This view never edits the blacklist/whitelist config. */
5
+ import { createElement as h, type ReactElement } from 'react'
6
+ import type { Translate } from './locales.ts'
7
+ import { s } from './styles.ts'
8
+
9
+ export type PreviewTool = { name: string; description: string } | string
10
+
11
+ function norm(tool: PreviewTool): { name: string; description: string } {
12
+ return typeof tool === 'string' ? { name: tool, description: '' } : tool
13
+ }
14
+
15
+ export function PreviewTools({ tools, t }: { tools: PreviewTool[]; t: Translate }): ReactElement {
16
+ if (tools.length === 0) return h('div', { style: s.muted }, t('empty'))
17
+ return h('div', { style: s.list }, [
18
+ h('div', { style: s.rowTitle }, [
19
+ h('span', { style: s.orderTag }, `${tools.length} ${t('previewToolCount')}`),
20
+ ]),
21
+ tools.map((tool) => {
22
+ const { name, description } = norm(tool)
23
+ return h('div', { key: name, style: s.row }, [
24
+ h('div', { style: s.rowBody }, [
25
+ h('div', { style: s.rowTitle }, h('span', { style: s.code }, name)),
26
+ h('div', { style: s.preview }, String(description ?? '').slice(0, 140)),
27
+ ]),
28
+ ])
29
+ }),
30
+ ])
31
+ }
@@ -1,37 +1,41 @@
1
- /** Tools tab: hide tools from the model catalog by exclude/include. */
1
+ /** Tools tab: hide tools from the model catalog (blacklist only — check = hide). */
2
2
  import { createElement as h, type ReactElement } from 'react'
3
3
  import type { Config, Inventory, SettingsScope } from './types.ts'
4
4
  import type { Translate } from './locales.ts'
5
- import { isToolHidden, setToolMode, toggleTool } from './presets.ts'
6
5
  import { s } from './styles.ts'
7
6
 
8
7
  export function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {
9
8
  const tools = inv?.tools ?? []
10
- const toolsCfg = cfg.tools ?? { exclude: [], include: [] }
11
- const includeMode = (toolsCfg.include ?? []).length > 0
9
+ const exclude = cfg.tools?.exclude ?? []
10
+
11
+ // Blacklist-only: a tool is hidden iff it is in the exclude list. Any stale
12
+ // whitelist (include) is discarded on write so mode switching is gone.
13
+ const hidden = (name: string): boolean => exclude.includes(name)
12
14
 
13
15
  const toggle = (name: string, currentlyHidden: boolean): void => {
14
- scope.set('tools', toggleTool(name, currentlyHidden, toolsCfg))
16
+ const next = exclude.slice()
17
+ if (currentlyHidden) { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }
18
+ else next.push(name)
19
+ scope.set('tools', { exclude: next, include: [] })
15
20
  }
16
- const setIncludeMode = (on: boolean): void => {
17
- scope.set('tools', setToolMode(on, toolsCfg, tools.map((tool) => tool.name)))
21
+
22
+ const setAll = (hideAll: boolean): void => {
23
+ scope.set('tools', { exclude: hideAll ? tools.map((tool) => tool.name) : [], include: [] })
18
24
  }
19
25
 
20
26
  return h('div', {}, [
21
27
  h('div', { style: s.bar }, [
22
- h('button', { style: includeMode ? s.tabActive : s.tab, onClick: () => setIncludeMode(true) }, t('includeMode')),
23
- h('button', { style: includeMode ? s.tab : s.tabActive, onClick: () => setIncludeMode(false) }, t('excludeMode')),
28
+ h('button', { style: s.mini, onClick: () => setAll(true) }, t('selectAll')),
29
+ h('button', { style: s.mini, onClick: () => setAll(false) }, t('selectNone')),
24
30
  ]),
25
31
  h('div', { style: s.list }, [
26
32
  tools.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,
27
33
  tools.map((tool) => {
28
- // Hidden state comes from the CONFIG (exclude/include lists), not the
29
- // inventory snapshot, so toggling updates the checkbox immediately.
30
- const hidden = isToolHidden(tool.name, toolsCfg)
34
+ const isHidden = hidden(tool.name)
31
35
  return h('div', { key: tool.name, style: s.row }, [
32
36
  h('label', { style: s.switchWrap }, [
33
- h('input', { type: 'checkbox', checked: hidden, onChange: () => toggle(tool.name, hidden) }),
34
- h('span', { style: hidden ? s.badgeBlocked : s.badgeOk }, hidden ? t('hiddenOn') : t('hiddenOff')),
37
+ h('input', { type: 'checkbox', checked: isHidden, onChange: () => toggle(tool.name, isHidden) }),
38
+ h('span', { style: isHidden ? s.badgeBlocked : s.badgeOk }, isHidden ? t('hiddenOn') : t('hiddenOff')),
35
39
  ]),
36
40
  h('div', { style: s.rowBody }, [
37
41
  h('div', { style: s.rowTitle }, h('span', { style: s.code }, tool.name)),
@@ -17,6 +17,7 @@ import type { Config, Inventory, SettingsScope } from './types.ts'
17
17
  import { SectionsTab } from './SectionsTab.tsx'
18
18
  import { ToolsTab } from './ToolsTab.tsx'
19
19
  import { PresetsTab } from './PresetsTab.tsx'
20
+ import { PreviewTab } from './PreviewTab.tsx'
20
21
 
21
22
  const NS = 'prompt-customizer'
22
23
  const INVENTORY_URL = '/api/prompt-customizer/inventory'
@@ -73,7 +74,7 @@ export function apply(ctx: ClientContext): void {
73
74
  function Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {
74
75
  const [snap, setSnap] = useState(() => scope.getSnapshot())
75
76
  const [inv, setInv] = useState<Inventory | null>(null)
76
- const [tab, setTab] = useState<'sections' | 'tools' | 'presets'>('sections')
77
+ const [tab, setTab] = useState<'sections' | 'tools' | 'presets' | 'preview'>('sections')
77
78
  const [error, setError] = useState<string | null>(null)
78
79
 
79
80
  useEffect(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope])
@@ -99,6 +100,7 @@ function Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactEleme
99
100
  h('button', { style: tab === 'sections' ? s.tabActive : s.tab, onClick: () => setTab('sections') }, t('tabsSections')),
100
101
  h('button', { style: tab === 'tools' ? s.tabActive : s.tab, onClick: () => setTab('tools') }, t('tabsTools')),
101
102
  h('button', { style: tab === 'presets' ? s.tabActive : s.tab, onClick: () => setTab('presets') }, t('tabsPresets')),
103
+ h('button', { style: tab === 'preview' ? s.tabActive : s.tab, onClick: () => setTab('preview') }, t('tabsPreview')),
102
104
  h('button', { style: s.refresh, onClick: refresh }, t('refresh')),
103
105
  ]),
104
106
  error ? h('div', { style: s.error }, String(error)) : null,
@@ -106,6 +108,8 @@ function Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactEleme
106
108
  ? h(SectionsTab, { cfg, inv, scope, t })
107
109
  : tab === 'tools'
108
110
  ? h(ToolsTab, { cfg, inv, scope, t })
109
- : h(PresetsTab, { cfg, inv, scope, t }),
111
+ : tab === 'presets'
112
+ ? h(PresetsTab, { cfg, inv, scope, t })
113
+ : h(PreviewTab, { scope, t }),
110
114
  ])
111
115
  }
@@ -7,6 +7,7 @@ export const DICT = {
7
7
  tabsSections: '提示词',
8
8
  tabsTools: '工具',
9
9
  tabsPresets: '预设',
10
+ tabsPreview: '预览',
10
11
  refresh: '刷新',
11
12
  blockedOff: '未屏蔽',
12
13
  blockedOn: '已屏蔽',
@@ -22,13 +23,18 @@ export const DICT = {
22
23
  add: '添加',
23
24
  hiddenOff: '可见',
24
25
  hiddenOn: '已隐藏',
25
- includeMode: '白名单模式:仅保留勾选项',
26
- excludeMode: '黑名单模式:勾选 = 隐藏',
26
+ selectAll: '全选',
27
+ selectNone: '全不选',
27
28
  empty: '(空)',
28
29
  dynamic: '<动态生成>',
29
30
  drag: '拖动排序',
30
31
  moveUp: '上移',
31
32
  moveDown: '下移',
33
+ previewHint: '以下为屏蔽/替换/注入后的最终系统提示词:',
34
+ previewPrompt: '提示词预览',
35
+ previewSections: '段',
36
+ previewTools: '工具预览',
37
+ previewToolCount: '个工具',
32
38
  savePreset: '保存当前为预设',
33
39
  presetName: '预设名称',
34
40
  save: '保存',
@@ -47,6 +53,7 @@ export const DICT = {
47
53
  tabsSections: 'Sections',
48
54
  tabsTools: 'Tools',
49
55
  tabsPresets: 'Presets',
56
+ tabsPreview: 'Preview',
50
57
  refresh: 'Refresh',
51
58
  blockedOff: 'Active',
52
59
  blockedOn: 'Blocked',
@@ -62,13 +69,18 @@ export const DICT = {
62
69
  add: 'Add',
63
70
  hiddenOff: 'Visible',
64
71
  hiddenOn: 'Hidden',
65
- includeMode: 'Whitelist: keep only checked',
66
- excludeMode: 'Blacklist: check to hide',
72
+ selectAll: 'Select all',
73
+ selectNone: 'Select none',
67
74
  empty: '(empty)',
68
75
  dynamic: '<dynamic>',
69
76
  drag: 'Drag to reorder',
70
77
  moveUp: 'Move up',
71
78
  moveDown: 'Move down',
79
+ previewHint: 'Final system prompt after block/replace/inject:',
80
+ previewPrompt: 'Prompt preview',
81
+ previewSections: 'sections',
82
+ previewTools: 'Tools preview',
83
+ previewToolCount: 'tools',
72
84
  savePreset: 'Save current as preset',
73
85
  presetName: 'Preset name',
74
86
  save: 'Save',
@@ -143,6 +143,10 @@ export function applyPresetData(data: PresetData, cfg: Config, currentNames: Rea
143
143
  // Inject list = the preset's resolved order, then any custom injected
144
144
  // sections present in the current config but absent from the preset list.
145
145
  // Keeping them in `inject` makes them visible; they are disabled below.
146
+ // Kept customs continue after the preset's own indexes; if the preset covers
147
+ // only a small subset they may tie with the inventory's native orders — the
148
+ // merge view sorts stably (inventory first), so ordering stays deterministic,
149
+ // and the next reorder/persist re-indexes everything contiguously anyway.
146
150
  const inject = resolveOrder(presetOrder)
147
151
  const kept = new Set(inject.map((x) => x.name))
148
152
  let order = inject.length
@@ -172,12 +176,16 @@ export function applyPresetData(data: PresetData, cfg: Config, currentNames: Rea
172
176
  /**
173
177
  * Remove a custom (plugin-added) section from the config: it disappears from
174
178
  * 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).
179
+ * text for it is cleared. The remaining inject entries are re-indexed to a
180
+ * contiguous 0..n-1 order (the virtual index model), so removal never leaves
181
+ * holes. System sections read from the host inventory cannot be removed this
182
+ * way (they would just come back on the next inventory read).
177
183
  */
178
184
  export function removeSection(name: string, cfg: Config): Pick<Config, 'sections' | 'inject' | 'replace'> {
179
185
  const sections = (cfg.sections ?? []).filter((n) => n !== name)
180
- const inject = (cfg.inject ?? []).filter((item) => item.name !== name)
186
+ const inject = (cfg.inject ?? [])
187
+ .filter((item) => item.name !== name)
188
+ .map((item, i) => ({ ...item, order: i }))
181
189
  const replace = { ...(cfg.replace ?? {}) }
182
190
  delete replace[name]
183
191
  return { sections, inject, replace }
@@ -200,8 +208,16 @@ export function toggleTool(name: string, currentlyHidden: boolean, toolsCfg: Too
200
208
  const exclude = toolsCfg?.exclude ?? []
201
209
  if (include.length > 0) {
202
210
  const next = include.slice()
203
- if (currentlyHidden) next.push(name)
204
- else { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }
211
+ if (currentlyHidden) {
212
+ next.push(name)
213
+ // Re-showing a tool in whitelist mode must also clear any stale blacklist
214
+ // entry, otherwise the name lingers in both lists and reappears as hidden
215
+ // if the user later switches back to blacklist mode.
216
+ const nextExclude = exclude.filter((n) => n !== name)
217
+ return { exclude: nextExclude, include: next }
218
+ }
219
+ const i = next.indexOf(name)
220
+ if (i >= 0) next.splice(i, 1)
205
221
  return { exclude, include: next }
206
222
  }
207
223
  const next = exclude.slice()
@@ -15,6 +15,9 @@ export const s = (() => {
15
15
  code: { fontFamily: 'monospace', fontSize: 12, wordBreak: 'break-all' },
16
16
  orderTag: { color: 'rgba(128,128,128,.8)', fontSize: 11 },
17
17
  preview: { color: 'rgba(128,128,128,.85)', fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
18
+ previewText: { fontFamily: 'monospace', fontSize: 12, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 480, overflowY: 'auto', padding: 10, border: '1px solid rgba(128,128,128,.2)', borderRadius: 8, background: 'rgba(0,0,0,.18)', margin: 0 },
19
+ toolWrap: { display: 'flex', flexWrap: 'wrap', gap: 6 },
20
+ toolChip: { fontFamily: 'monospace', fontSize: 12, color: 'rgba(128,128,128,.95)', border: '1px solid rgba(128,128,128,.3)', borderRadius: 6, padding: '2px 6px', cursor: 'help' },
18
21
  badgeOk: { color: '#3fb950', fontSize: 11, border: '1px solid rgba(63,185,80,.5)', borderRadius: 999, padding: '0 6px' },
19
22
  badgeBlocked: { color: '#f85149', fontSize: 11, border: '1px solid rgba(248,81,73,.5)', borderRadius: 999, padding: '0 6px' },
20
23
  badgeReplaced: { color: '#d29922', fontSize: 11, border: '1px solid rgba(210,153,34,.5)', borderRadius: 999, padding: '0 6px' },
@@ -14,6 +14,15 @@ export interface Inventory {
14
14
  tools: Array<{ name: string; description?: string; hidden: boolean }>
15
15
  }
16
16
 
17
+ /** Final-assembly preview payload from the host route. */
18
+ export interface Preview {
19
+ ok: boolean
20
+ scope?: unknown
21
+ sections: Array<{ name: string; text: string }>
22
+ text: string
23
+ tools: Array<{ name: string; description: string } | string>
24
+ }
25
+
17
26
  /** The resolved config object (namespace value). */
18
27
  export interface Config {
19
28
  sections?: string[]