dsh-prompt-customizer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,896 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-prompt-customizer",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ //#region src/client/locales.ts
9
+ /** Locale dictionaries for the prompt-customizer panel. */
10
+ const DICT = {
11
+ zh: {
12
+ nav: "提示词定制",
13
+ loading: "加载中…",
14
+ unavailable: "当前环境未启用本命名空间。",
15
+ tabsSections: "提示词",
16
+ tabsTools: "工具",
17
+ tabsPresets: "预设",
18
+ refresh: "刷新",
19
+ blockedOff: "未屏蔽",
20
+ blockedOn: "已屏蔽",
21
+ replaced: "已替换",
22
+ replace: "替换",
23
+ clearReplace: "清除替换",
24
+ injectNew: "注入新段",
25
+ name: "名称",
26
+ order: "顺序",
27
+ text: "文本",
28
+ add: "添加",
29
+ hiddenOff: "可见",
30
+ hiddenOn: "已隐藏",
31
+ includeMode: "白名单模式:仅保留勾选项",
32
+ excludeMode: "黑名单模式:勾选 = 隐藏",
33
+ empty: "(空)",
34
+ dynamic: "<动态生成>",
35
+ drag: "拖动排序",
36
+ moveUp: "上移",
37
+ moveDown: "下移",
38
+ savePreset: "保存当前为预设",
39
+ presetName: "预设名称",
40
+ save: "保存",
41
+ importPreset: "导入预设",
42
+ import: "导入",
43
+ apply: "应用",
44
+ export: "导出",
45
+ delete: "删除",
46
+ active: "使用中",
47
+ preset: "预设"
48
+ },
49
+ en: {
50
+ nav: "Prompt Customizer",
51
+ loading: "Loading…",
52
+ unavailable: "Namespace unavailable in this environment.",
53
+ tabsSections: "Sections",
54
+ tabsTools: "Tools",
55
+ tabsPresets: "Presets",
56
+ refresh: "Refresh",
57
+ blockedOff: "Active",
58
+ blockedOn: "Blocked",
59
+ replaced: "Replaced",
60
+ replace: "Replace",
61
+ clearReplace: "Clear",
62
+ injectNew: "Inject section",
63
+ name: "Name",
64
+ order: "Order",
65
+ text: "Text",
66
+ add: "Add",
67
+ hiddenOff: "Visible",
68
+ hiddenOn: "Hidden",
69
+ includeMode: "Whitelist: keep only checked",
70
+ excludeMode: "Blacklist: check to hide",
71
+ empty: "(empty)",
72
+ dynamic: "<dynamic>",
73
+ drag: "Drag to reorder",
74
+ moveUp: "Move up",
75
+ moveDown: "Move down",
76
+ savePreset: "Save current as preset",
77
+ presetName: "Preset name",
78
+ save: "Save",
79
+ importPreset: "Import preset",
80
+ import: "Import",
81
+ apply: "Apply",
82
+ export: "Export",
83
+ delete: "Delete",
84
+ active: "Active",
85
+ preset: "Preset"
86
+ }
87
+ };
88
+ //#endregion
89
+ //#region src/client/styles.ts
90
+ /** Inline styles shared by the prompt-customizer panel components. */
91
+ const s = (() => {
92
+ const flex = {
93
+ display: "flex",
94
+ alignItems: "center"
95
+ };
96
+ return {
97
+ root: {
98
+ fontFamily: "inherit",
99
+ fontSize: 13,
100
+ lineHeight: 1.5
101
+ },
102
+ bar: {
103
+ ...flex,
104
+ gap: 6,
105
+ marginBottom: 8,
106
+ flexWrap: "wrap"
107
+ },
108
+ tab: {
109
+ padding: "4px 10px",
110
+ border: "1px solid rgba(128,128,128,.35)",
111
+ borderRadius: 6,
112
+ background: "transparent",
113
+ cursor: "pointer",
114
+ color: "inherit",
115
+ opacity: .75
116
+ },
117
+ tabActive: {
118
+ padding: "4px 10px",
119
+ border: "1px solid rgba(128,128,128,.7)",
120
+ borderRadius: 6,
121
+ background: "rgba(128,128,128,.18)",
122
+ cursor: "pointer",
123
+ color: "inherit"
124
+ },
125
+ refresh: {
126
+ marginLeft: "auto",
127
+ padding: "4px 10px",
128
+ border: "1px solid rgba(128,128,128,.35)",
129
+ borderRadius: 6,
130
+ background: "transparent",
131
+ cursor: "pointer",
132
+ color: "inherit"
133
+ },
134
+ list: {
135
+ display: "flex",
136
+ flexDirection: "column",
137
+ gap: 6
138
+ },
139
+ row: {
140
+ ...flex,
141
+ gap: 8,
142
+ padding: "6px 8px",
143
+ border: "1px solid rgba(128,128,128,.2)",
144
+ borderRadius: 8,
145
+ background: "rgba(128,128,128,.06)"
146
+ },
147
+ switchWrap: {
148
+ ...flex,
149
+ gap: 6,
150
+ minWidth: 90
151
+ },
152
+ rowBody: {
153
+ flex: 1,
154
+ minWidth: 0
155
+ },
156
+ rowTitle: {
157
+ ...flex,
158
+ gap: 6,
159
+ alignItems: "baseline",
160
+ flexWrap: "wrap"
161
+ },
162
+ code: {
163
+ fontFamily: "monospace",
164
+ fontSize: 12,
165
+ wordBreak: "break-all"
166
+ },
167
+ orderTag: {
168
+ color: "rgba(128,128,128,.8)",
169
+ fontSize: 11
170
+ },
171
+ preview: {
172
+ color: "rgba(128,128,128,.85)",
173
+ fontSize: 12,
174
+ overflow: "hidden",
175
+ textOverflow: "ellipsis",
176
+ whiteSpace: "nowrap"
177
+ },
178
+ badgeOk: {
179
+ color: "#3fb950",
180
+ fontSize: 11,
181
+ border: "1px solid rgba(63,185,80,.5)",
182
+ borderRadius: 999,
183
+ padding: "0 6px"
184
+ },
185
+ badgeBlocked: {
186
+ color: "#f85149",
187
+ fontSize: 11,
188
+ border: "1px solid rgba(248,81,73,.5)",
189
+ borderRadius: 999,
190
+ padding: "0 6px"
191
+ },
192
+ badgeReplaced: {
193
+ color: "#d29922",
194
+ fontSize: 11,
195
+ border: "1px solid rgba(210,153,34,.5)",
196
+ borderRadius: 999,
197
+ padding: "0 6px"
198
+ },
199
+ mini: {
200
+ padding: "3px 8px",
201
+ border: "1px solid rgba(128,128,128,.35)",
202
+ borderRadius: 6,
203
+ background: "transparent",
204
+ cursor: "pointer",
205
+ color: "inherit",
206
+ whiteSpace: "nowrap"
207
+ },
208
+ arrowCol: {
209
+ display: "flex",
210
+ flexDirection: "column",
211
+ gap: 2
212
+ },
213
+ arrow: {
214
+ padding: "0 6px",
215
+ border: "1px solid rgba(128,128,128,.3)",
216
+ borderRadius: 4,
217
+ background: "transparent",
218
+ cursor: "pointer",
219
+ color: "inherit",
220
+ fontSize: 11,
221
+ lineHeight: 1.4,
222
+ opacity: .8
223
+ },
224
+ rowDragging: { opacity: .4 },
225
+ rowDropAbove: { boxShadow: "0 -2px 0 0 #58a6ff" },
226
+ rowDropBelow: { boxShadow: "0 2px 0 0 #58a6ff" },
227
+ dragHandle: {
228
+ cursor: "grab",
229
+ color: "rgba(128,128,128,.7)",
230
+ fontSize: 13,
231
+ padding: "0 2px",
232
+ userSelect: "none"
233
+ },
234
+ injectBox: {
235
+ marginTop: 10,
236
+ padding: "8px",
237
+ border: "1px dashed rgba(128,128,128,.4)",
238
+ borderRadius: 8,
239
+ display: "flex",
240
+ flexDirection: "column",
241
+ gap: 6
242
+ },
243
+ injectRow: {
244
+ ...flex,
245
+ gap: 6
246
+ },
247
+ editBox: {
248
+ display: "flex",
249
+ flexDirection: "column",
250
+ gap: 6,
251
+ marginTop: 4
252
+ },
253
+ editInput: {
254
+ padding: "6px",
255
+ border: "1px solid rgba(128,128,128,.35)",
256
+ borderRadius: 6,
257
+ background: "transparent",
258
+ color: "inherit",
259
+ fontFamily: "inherit",
260
+ fontSize: 12,
261
+ resize: "vertical",
262
+ width: "100%",
263
+ boxSizing: "border-box"
264
+ },
265
+ input: {
266
+ padding: "4px 6px",
267
+ border: "1px solid rgba(128,128,128,.35)",
268
+ borderRadius: 6,
269
+ background: "transparent",
270
+ color: "inherit"
271
+ },
272
+ muted: {
273
+ color: "rgba(128,128,128,.75)",
274
+ fontSize: 12
275
+ },
276
+ error: {
277
+ color: "#f85149",
278
+ fontSize: 12,
279
+ marginBottom: 6
280
+ }
281
+ };
282
+ })();
283
+ //#endregion
284
+ //#region src/client/SectionsTab.tsx
285
+ /** Prompt-sections tab: block / replace / inject / reorder prompt sections. */
286
+ function SectionsTab({ cfg, inv, scope, t }) {
287
+ const replace = cfg.replace ?? {};
288
+ const blockedNames = new Set(cfg.sections ?? []);
289
+ const [editing, setEditing] = (0, react.useState)(null);
290
+ const [draft, setDraft] = (0, react.useState)("");
291
+ const [dragName, setDragName] = (0, react.useState)(null);
292
+ const [dropTarget, setDropTarget] = (0, react.useState)(null);
293
+ const merged = (() => {
294
+ const map = /* @__PURE__ */ new Map();
295
+ for (const sec of inv?.sections ?? []) map.set(sec.name, sec);
296
+ for (const item of cfg.inject ?? []) {
297
+ const existing = map.get(item.name);
298
+ if (existing) map.set(item.name, {
299
+ ...existing,
300
+ order: item.order
301
+ });
302
+ else map.set(item.name, {
303
+ name: item.name,
304
+ order: item.order,
305
+ text: item.text || "<动态生成>",
306
+ active: !blockedNames.has(item.name),
307
+ replaced: false
308
+ });
309
+ }
310
+ return [...map.values()].sort((a, b) => a.order - b.order);
311
+ })();
312
+ const toggleBlock = (name, currentlyBlocked) => {
313
+ const list = (cfg.sections ?? []).slice();
314
+ if (currentlyBlocked) {
315
+ const i = list.indexOf(name);
316
+ if (i >= 0) list.splice(i, 1);
317
+ } else list.push(name);
318
+ scope.set("sections", list);
319
+ };
320
+ const startReplace = (name, original) => {
321
+ setEditing(name);
322
+ setDraft(replace[name] ?? original);
323
+ };
324
+ const commitReplace = (name) => {
325
+ const next = { ...replace };
326
+ if (draft.trim() === "") delete next[name];
327
+ else next[name] = draft;
328
+ scope.set("replace", next);
329
+ setEditing(null);
330
+ };
331
+ const clearReplace = (name) => {
332
+ const next = { ...replace };
333
+ delete next[name];
334
+ scope.set("replace", next);
335
+ };
336
+ const persistOrder = (ordered) => {
337
+ const list = ordered.map((sec, i) => {
338
+ const existing = (cfg.inject ?? []).find((x) => x.name === sec.name);
339
+ return {
340
+ name: sec.name,
341
+ order: i,
342
+ text: existing ? existing.text : ""
343
+ };
344
+ });
345
+ scope.set("inject", list);
346
+ };
347
+ const moveOrder = (index, dir) => {
348
+ const target = index + dir;
349
+ if (target < 0 || target >= merged.length) return;
350
+ const next = merged.slice();
351
+ const [item] = next.splice(index, 1);
352
+ next.splice(target, 0, item);
353
+ persistOrder(next);
354
+ };
355
+ const onDragStart = (e, name) => {
356
+ setDragName(name);
357
+ e.dataTransfer.effectAllowed = "move";
358
+ try {
359
+ e.dataTransfer.setData("text/plain", name);
360
+ } catch {}
361
+ };
362
+ const onDragOver = (e, name) => {
363
+ if (!dragName || dragName === name) return;
364
+ e.preventDefault();
365
+ e.dataTransfer.dropEffect = "move";
366
+ const rect = e.currentTarget.getBoundingClientRect();
367
+ const pos = e.clientY < rect.top + rect.height / 2 ? "above" : "below";
368
+ setDropTarget({
369
+ name,
370
+ pos
371
+ });
372
+ };
373
+ const onDrop = (e, target) => {
374
+ e.preventDefault();
375
+ if (!dragName || dragName === target.name) {
376
+ setDragName(null);
377
+ setDropTarget(null);
378
+ return;
379
+ }
380
+ const dragged = merged.find((x) => x.name === dragName);
381
+ if (!dragged) {
382
+ setDragName(null);
383
+ setDropTarget(null);
384
+ return;
385
+ }
386
+ const next = merged.filter((x) => x.name !== dragName);
387
+ const idx = next.findIndex((x) => x.name === target.name);
388
+ const pos = dropTarget?.name === target.name ? dropTarget.pos : "below";
389
+ next.splice(pos === "above" ? idx : idx + 1, 0, dragged);
390
+ persistOrder(next);
391
+ setDragName(null);
392
+ setDropTarget(null);
393
+ };
394
+ const onDragEnd = () => {
395
+ setDragName(null);
396
+ setDropTarget(null);
397
+ };
398
+ const addSection = (name, order, text) => {
399
+ const next = merged.slice();
400
+ const entry = {
401
+ name,
402
+ order,
403
+ text: text || "<动态生成>",
404
+ active: true,
405
+ replaced: false
406
+ };
407
+ const existing = next.findIndex((x) => x.name === name);
408
+ if (existing >= 0) next[existing] = entry;
409
+ else {
410
+ const idx = next.findIndex((x) => x.order > order);
411
+ if (idx < 0) next.push(entry);
412
+ else next.splice(idx, 0, entry);
413
+ }
414
+ persistOrder(next);
415
+ };
416
+ return (0, react.createElement)("div", { style: s.list }, [
417
+ merged.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
418
+ merged.map((sec, index) => {
419
+ const blocked = blockedNames.has(sec.name);
420
+ const isEditing = editing === sec.name;
421
+ const isDragging = dragName === sec.name;
422
+ const isDropTarget = dropTarget?.name === sec.name;
423
+ const rowStyle = {
424
+ ...s.row,
425
+ ...isDragging ? s.rowDragging : {},
426
+ ...isDropTarget && dropTarget.pos === "above" ? s.rowDropAbove : {},
427
+ ...isDropTarget && dropTarget.pos === "below" ? s.rowDropBelow : {}
428
+ };
429
+ return (0, react.createElement)("div", {
430
+ key: sec.name,
431
+ style: rowStyle,
432
+ draggable: true,
433
+ onDragStart: (e) => onDragStart(e, sec.name),
434
+ onDragOver: (e) => onDragOver(e, sec.name),
435
+ onDrop: (e) => onDrop(e, sec),
436
+ onDragEnd
437
+ }, [
438
+ (0, react.createElement)("span", {
439
+ style: s.dragHandle,
440
+ title: t("drag")
441
+ }, "⠿"),
442
+ (0, react.createElement)("label", { style: s.switchWrap }, [(0, react.createElement)("input", {
443
+ type: "checkbox",
444
+ checked: blocked,
445
+ onChange: () => toggleBlock(sec.name, blocked)
446
+ }), (0, react.createElement)("span", { style: blocked ? s.badgeBlocked : s.badgeOk }, blocked ? t("blockedOn") : t("blockedOff"))]),
447
+ (0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [
448
+ (0, react.createElement)("span", { style: s.code }, sec.name),
449
+ (0, react.createElement)("span", { style: s.orderTag }, "#" + index),
450
+ sec.replaced ? (0, react.createElement)("span", { style: s.badgeReplaced }, t("replaced")) : null
451
+ ]), isEditing ? (0, react.createElement)("div", { style: s.editBox }, [(0, react.createElement)("textarea", {
452
+ style: s.editInput,
453
+ value: draft,
454
+ onChange: (e) => setDraft(e.target.value),
455
+ rows: 3
456
+ }), (0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("button", {
457
+ style: s.mini,
458
+ onClick: () => commitReplace(sec.name)
459
+ }, t("add")), (0, react.createElement)("button", {
460
+ style: s.mini,
461
+ onClick: () => setEditing(null)
462
+ }, t("clearReplace"))])]) : (0, react.createElement)("div", { style: s.preview }, String(sec.text ?? "").slice(0, 140) || t("dynamic"))]),
463
+ (0, react.createElement)("div", { style: s.arrowCol }, [(0, react.createElement)("button", {
464
+ style: s.arrow,
465
+ disabled: index === 0,
466
+ onClick: () => moveOrder(index, -1),
467
+ title: t("moveUp")
468
+ }, "↑"), (0, react.createElement)("button", {
469
+ style: s.arrow,
470
+ disabled: index === merged.length - 1,
471
+ onClick: () => moveOrder(index, 1),
472
+ title: t("moveDown")
473
+ }, "↓")]),
474
+ (0, react.createElement)("button", {
475
+ style: s.mini,
476
+ onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? ""))
477
+ }, replace[sec.name] ? t("clearReplace") : t("replace"))
478
+ ]);
479
+ }),
480
+ (0, react.createElement)("div", { style: s.injectBox }, [(0, react.createElement)("div", { style: s.rowTitle }, t("injectNew")), (0, react.createElement)(InjectForm, {
481
+ onAdd: addSection,
482
+ t
483
+ })])
484
+ ]);
485
+ }
486
+ function InjectForm({ onAdd, t }) {
487
+ const [name, setName] = (0, react.useState)("");
488
+ const [order, setOrder] = (0, react.useState)("120");
489
+ const [text, setText] = (0, react.useState)("");
490
+ const submit = () => {
491
+ if (!name.trim()) return;
492
+ onAdd(name.trim(), Number(order) || 120, text);
493
+ setName("");
494
+ setText("");
495
+ setOrder("120");
496
+ };
497
+ return (0, react.createElement)("div", {}, [(0, react.createElement)("div", { style: s.injectRow }, [
498
+ (0, react.createElement)("input", {
499
+ style: {
500
+ ...s.input,
501
+ width: "22%"
502
+ },
503
+ placeholder: t("name"),
504
+ value: name,
505
+ onChange: (e) => setName(e.target.value)
506
+ }),
507
+ (0, react.createElement)("input", {
508
+ style: {
509
+ ...s.input,
510
+ width: "12%"
511
+ },
512
+ type: "number",
513
+ placeholder: t("order"),
514
+ value: order,
515
+ onChange: (e) => setOrder(e.target.value)
516
+ }),
517
+ (0, react.createElement)("input", {
518
+ style: {
519
+ ...s.input,
520
+ flex: 1
521
+ },
522
+ placeholder: t("text"),
523
+ value: text,
524
+ onChange: (e) => setText(e.target.value)
525
+ }),
526
+ (0, react.createElement)("button", {
527
+ style: s.mini,
528
+ onClick: submit
529
+ }, t("add"))
530
+ ])]);
531
+ }
532
+ //#endregion
533
+ //#region src/client/ToolsTab.tsx
534
+ /** Tools tab: hide tools from the model catalog by exclude/include. */
535
+ function ToolsTab({ cfg, inv, scope, t }) {
536
+ const tools = inv?.tools ?? [];
537
+ const toolsCfg = cfg.tools ?? {
538
+ exclude: [],
539
+ include: []
540
+ };
541
+ const exclude = toolsCfg.exclude ?? [];
542
+ const include = toolsCfg.include ?? [];
543
+ const includeMode = include.length > 0;
544
+ const toggle = (name, currentlyHidden) => {
545
+ if (includeMode) {
546
+ const next = include.slice();
547
+ if (currentlyHidden) next.push(name);
548
+ else {
549
+ const i = next.indexOf(name);
550
+ if (i >= 0) next.splice(i, 1);
551
+ }
552
+ scope.set("tools", {
553
+ ...toolsCfg,
554
+ include: next
555
+ });
556
+ } else {
557
+ const next = exclude.slice();
558
+ if (currentlyHidden) {
559
+ const i = next.indexOf(name);
560
+ if (i >= 0) next.splice(i, 1);
561
+ } else next.push(name);
562
+ scope.set("tools", {
563
+ ...toolsCfg,
564
+ exclude: next
565
+ });
566
+ }
567
+ };
568
+ const setIncludeMode = (on) => {
569
+ scope.set("tools", on ? {
570
+ exclude: exclude.filter((n) => tools.some((tool) => tool.name === n)),
571
+ include: tools.filter((tool) => !tool.hidden).map((tool) => tool.name)
572
+ } : {
573
+ exclude: ensureOnlyKnown(exclude, tools),
574
+ include: []
575
+ });
576
+ };
577
+ return (0, react.createElement)("div", {}, [(0, react.createElement)("div", { style: s.bar }, [(0, react.createElement)("button", {
578
+ style: includeMode ? s.tabActive : s.tab,
579
+ onClick: () => setIncludeMode(true)
580
+ }, t("includeMode")), (0, react.createElement)("button", {
581
+ style: includeMode ? s.tab : s.tabActive,
582
+ onClick: () => setIncludeMode(false)
583
+ }, 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) => {
584
+ const hidden = includeMode ? !include.includes(tool.name) : exclude.includes(tool.name);
585
+ return (0, react.createElement)("div", {
586
+ key: tool.name,
587
+ style: s.row
588
+ }, [(0, react.createElement)("label", { style: s.switchWrap }, [(0, react.createElement)("input", {
589
+ type: "checkbox",
590
+ checked: hidden,
591
+ onChange: () => toggle(tool.name, hidden)
592
+ }), (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))])]);
593
+ })])]);
594
+ }
595
+ function ensureOnlyKnown(list, known) {
596
+ const names = known.map((tool) => tool.name);
597
+ return list.filter((n) => names.includes(n));
598
+ }
599
+ //#endregion
600
+ //#region src/client/PresetsTab.tsx
601
+ /** Presets tab: save / apply / export / import complete customization snapshots. */
602
+ function PresetsTab({ cfg, inv, scope, t }) {
603
+ const presets = cfg.presets ?? [];
604
+ const [name, setName] = (0, react.useState)("");
605
+ const fileRef = (0, react.useRef)(null);
606
+ const currentNames = (() => {
607
+ const set = /* @__PURE__ */ new Set();
608
+ for (const sec of inv?.sections ?? []) set.add(sec.name);
609
+ for (const item of cfg.inject ?? []) set.add(item.name);
610
+ return set;
611
+ })();
612
+ const saveCurrent = () => {
613
+ const presetName = name.trim() || `${t("preset")} ${presets.length + 1}`;
614
+ const inject = cfg.inject ?? [];
615
+ const order = inject.map((sec, i) => ({
616
+ name: sec.name,
617
+ after: i > 0 ? inject[i - 1].name : void 0,
618
+ text: sec.text
619
+ }));
620
+ const data = {
621
+ sections: cfg.sections,
622
+ replace: cfg.replace,
623
+ order,
624
+ tools: cfg.tools
625
+ };
626
+ scope.set("presets", [...presets, {
627
+ id: genId(),
628
+ name: presetName,
629
+ data
630
+ }]);
631
+ setName("");
632
+ };
633
+ const applyPreset = (preset) => {
634
+ const presetOrder = preset.data.order ?? [];
635
+ const presetNames = new Set(presetOrder.map((x) => x.name));
636
+ const blocked = new Set(preset.data.sections ?? []);
637
+ for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name);
638
+ for (const name of presetNames) blocked.delete(name);
639
+ scope.set("inject", resolveOrder(presetOrder));
640
+ scope.set("replace", {
641
+ ...cfg.replace ?? {},
642
+ ...preset.data.replace ?? {}
643
+ });
644
+ scope.set("sections", [...blocked]);
645
+ scope.set("tools", preset.data.tools ?? {
646
+ exclude: [],
647
+ include: []
648
+ });
649
+ scope.set("activePreset", preset.id);
650
+ };
651
+ const deletePreset = (id) => {
652
+ scope.set("presets", presets.filter((p) => p.id !== id));
653
+ if (cfg.activePreset === id) scope.unset("activePreset");
654
+ };
655
+ const exportPreset = (preset) => {
656
+ const blob = new Blob([JSON.stringify({
657
+ name: preset.name,
658
+ data: preset.data
659
+ }, null, 2)], { type: "application/json" });
660
+ const url = URL.createObjectURL(blob);
661
+ const a = document.createElement("a");
662
+ a.href = url;
663
+ a.download = `${preset.name}.json`;
664
+ a.click();
665
+ URL.revokeObjectURL(url);
666
+ };
667
+ const onImportFile = (e) => {
668
+ const file = e.target.files?.[0];
669
+ if (!file) return;
670
+ const reader = new FileReader();
671
+ reader.onload = () => {
672
+ try {
673
+ const parsed = JSON.parse(String(reader.result));
674
+ const incoming = Array.isArray(parsed) ? parsed : [parsed];
675
+ const existing = new Set(presets.map((p) => p.name));
676
+ const added = incoming.filter((p) => p && typeof p.name === "string" && !existing.has(p.name)).map((p) => ({
677
+ id: genId(),
678
+ name: p.name,
679
+ data: p.data ?? {}
680
+ }));
681
+ if (added.length > 0) scope.set("presets", [...presets, ...added]);
682
+ } catch {}
683
+ };
684
+ reader.readAsText(file);
685
+ e.target.value = "";
686
+ };
687
+ return (0, react.createElement)("div", { style: s.list }, [
688
+ (0, react.createElement)("div", { style: s.injectBox }, [(0, react.createElement)("div", { style: s.rowTitle }, t("savePreset")), (0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("input", {
689
+ style: {
690
+ ...s.input,
691
+ flex: 1
692
+ },
693
+ placeholder: t("presetName"),
694
+ value: name,
695
+ onChange: (e) => setName(e.target.value)
696
+ }), (0, react.createElement)("button", {
697
+ style: s.mini,
698
+ onClick: saveCurrent
699
+ }, t("save"))])]),
700
+ (0, react.createElement)("div", { style: s.injectBox }, [(0, react.createElement)("div", { style: s.rowTitle }, t("importPreset")), (0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("button", {
701
+ style: s.mini,
702
+ onClick: () => fileRef.current?.click()
703
+ }, t("import")), (0, react.createElement)("input", {
704
+ ref: fileRef,
705
+ type: "file",
706
+ accept: ".json,application/json",
707
+ style: { display: "none" },
708
+ onChange: onImportFile
709
+ })])]),
710
+ presets.length === 0 ? (0, react.createElement)("div", { style: s.muted }, t("empty")) : null,
711
+ presets.map((preset) => {
712
+ const active = cfg.activePreset === preset.id;
713
+ return (0, react.createElement)("div", {
714
+ key: preset.id,
715
+ style: s.row
716
+ }, [
717
+ (0, react.createElement)("div", { style: s.rowBody }, [(0, react.createElement)("div", { style: s.rowTitle }, [(0, react.createElement)("span", { style: s.code }, preset.name), active ? (0, react.createElement)("span", { style: s.badgeOk }, t("active")) : null])]),
718
+ (0, react.createElement)("button", {
719
+ style: s.mini,
720
+ onClick: () => applyPreset(preset)
721
+ }, t("apply")),
722
+ (0, react.createElement)("button", {
723
+ style: s.mini,
724
+ onClick: () => exportPreset(preset)
725
+ }, t("export")),
726
+ (0, react.createElement)("button", {
727
+ style: s.mini,
728
+ onClick: () => deletePreset(preset.id)
729
+ }, t("delete"))
730
+ ]);
731
+ })
732
+ ]);
733
+ }
734
+ function genId() {
735
+ return "p_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
736
+ }
737
+ /**
738
+ * Resolve a preset's relative order into an absolute ordered list (0..n-1).
739
+ * Sections with no anchor (or whose anchor is absent) come first in preset
740
+ * order; each anchored section is inserted right after its anchor; any
741
+ * remaining sections (cycles) are appended.
742
+ */
743
+ function resolveOrder(presetOrder) {
744
+ const afterMap = /* @__PURE__ */ new Map();
745
+ const textMap = /* @__PURE__ */ new Map();
746
+ for (const sec of presetOrder) {
747
+ afterMap.set(sec.name, sec.after);
748
+ textMap.set(sec.name, sec.text);
749
+ }
750
+ const result = [];
751
+ const placed = /* @__PURE__ */ new Set();
752
+ for (const sec of presetOrder) {
753
+ const anchor = afterMap.get(sec.name);
754
+ if (!anchor || !afterMap.has(anchor)) {
755
+ result.push(sec.name);
756
+ placed.add(sec.name);
757
+ }
758
+ }
759
+ let changed = true;
760
+ while (changed) {
761
+ changed = false;
762
+ for (const sec of presetOrder) {
763
+ if (placed.has(sec.name)) continue;
764
+ const anchor = afterMap.get(sec.name);
765
+ if (anchor && placed.has(anchor)) {
766
+ result.splice(result.indexOf(anchor) + 1, 0, sec.name);
767
+ placed.add(sec.name);
768
+ changed = true;
769
+ }
770
+ }
771
+ }
772
+ for (const sec of presetOrder) if (!placed.has(sec.name)) {
773
+ result.push(sec.name);
774
+ placed.add(sec.name);
775
+ }
776
+ return result.map((name, i) => ({
777
+ name,
778
+ order: i,
779
+ text: textMap.get(name) ?? ""
780
+ }));
781
+ }
782
+ //#endregion
783
+ //#region src/client/index.ts
784
+ /**
785
+ * dsh-prompt-customizer — browser half (TypeScript source).
786
+ *
787
+ * Registers a "提示词定制" settings section (sidebar entry) and a plugin
788
+ * settings card. The panel fetches the effective section / tool inventory
789
+ * from the host `/api/prompt-customizer/inventory` and lets the user block,
790
+ * replace, inject prompt sections and hide tools. All writes go through the
791
+ * bound settings scope (`prompt-customizer` namespace) and apply live.
792
+ *
793
+ * Built by tsdown into the __ModuleLoader__ factory bundle at client/client.js;
794
+ * the only external is the loader module table's react entry.
795
+ */
796
+ const NS = "prompt-customizer";
797
+ const INVENTORY_URL = "/api/prompt-customizer/inventory";
798
+ const name = "prompt-customizer";
799
+ const inject = ["slots", "locale"];
800
+ function apply(ctx) {
801
+ ctx.effect(() => ctx.locale.register(NS, DICT), "prompt-customizer: locale");
802
+ const t = ctx.locale.bind(NS);
803
+ ctx.inject(["settingsScope"], (scoped) => {
804
+ const scope = scoped.settingsScope.bind({ namespace: NS });
805
+ scoped.slots.inject("settings.section", () => scoped.slots.register({
806
+ name: "settings.section",
807
+ id: "prompt-customizer",
808
+ order: 50,
809
+ label: () => t("nav"),
810
+ locale: NS,
811
+ inject: () => ({ t })
812
+ }, () => (0, react.createElement)(Panel, {
813
+ scope,
814
+ t
815
+ })));
816
+ scoped.slots.inject("settings.plugin.item", () => scoped.slots.register({
817
+ name: "settings.plugin.item",
818
+ key: NS,
819
+ locale: NS,
820
+ inject: () => ({ t })
821
+ }, () => (0, react.createElement)(Panel, {
822
+ scope,
823
+ t
824
+ })));
825
+ });
826
+ }
827
+ function Panel({ scope, t }) {
828
+ const [snap, setSnap] = (0, react.useState)(() => scope.getSnapshot());
829
+ const [inv, setInv] = (0, react.useState)(null);
830
+ const [tab, setTab] = (0, react.useState)("sections");
831
+ const [error, setError] = (0, react.useState)(null);
832
+ (0, react.useEffect)(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope]);
833
+ const refresh = () => {
834
+ fetch(INVENTORY_URL).then((r) => r.json()).then((data) => {
835
+ setInv(data);
836
+ setError(null);
837
+ }).catch((e) => setError(String(e instanceof Error ? e.message : e)));
838
+ };
839
+ (0, react.useEffect)(refresh, []);
840
+ if (snap.status !== "ready" && snap.value === void 0) return (0, react.createElement)("div", { style: s.muted }, t("loading"));
841
+ const cfg = snap.value && typeof snap.value === "object" ? snap.value : {
842
+ sections: [],
843
+ replace: {},
844
+ inject: [],
845
+ tools: {
846
+ exclude: [],
847
+ include: []
848
+ }
849
+ };
850
+ return (0, react.createElement)("div", { style: s.root }, [
851
+ (0, react.createElement)("div", { style: s.bar }, [
852
+ (0, react.createElement)("button", {
853
+ style: tab === "sections" ? s.tabActive : s.tab,
854
+ onClick: () => setTab("sections")
855
+ }, t("tabsSections")),
856
+ (0, react.createElement)("button", {
857
+ style: tab === "tools" ? s.tabActive : s.tab,
858
+ onClick: () => setTab("tools")
859
+ }, t("tabsTools")),
860
+ (0, react.createElement)("button", {
861
+ style: tab === "presets" ? s.tabActive : s.tab,
862
+ onClick: () => setTab("presets")
863
+ }, t("tabsPresets")),
864
+ (0, react.createElement)("button", {
865
+ style: s.refresh,
866
+ onClick: refresh
867
+ }, t("refresh"))
868
+ ]),
869
+ error ? (0, react.createElement)("div", { style: s.error }, String(error)) : null,
870
+ tab === "sections" ? (0, react.createElement)(SectionsTab, {
871
+ cfg,
872
+ inv,
873
+ scope,
874
+ t
875
+ }) : tab === "tools" ? (0, react.createElement)(ToolsTab, {
876
+ cfg,
877
+ inv,
878
+ scope,
879
+ t
880
+ }) : (0, react.createElement)(PresetsTab, {
881
+ cfg,
882
+ inv,
883
+ scope,
884
+ t
885
+ })
886
+ ]);
887
+ }
888
+ //#endregion
889
+ exports.apply = apply;
890
+ exports.inject = inject;
891
+ exports.name = name;
892
+ return module.exports;
893
+ }
894
+ });
895
+
896
+ //# sourceMappingURL=client.js.map