dsh-prompt-customizer 0.2.0 → 0.2.2
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 +213 -148
- package/client/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/PresetsTab.tsx +18 -92
- package/src/client/SectionsTab.tsx +2 -24
- package/src/client/ToolsTab.tsx +5 -23
- package/src/client/presets.ts +198 -0
package/client/client.js
CHANGED
|
@@ -281,6 +281,201 @@ window.__ModuleLoader__.load({
|
|
|
281
281
|
};
|
|
282
282
|
})();
|
|
283
283
|
//#endregion
|
|
284
|
+
//#region src/client/presets.ts
|
|
285
|
+
/** Merge the inventory sections with the user's inject list, sorted by order. */
|
|
286
|
+
function mergeSections(inv, cfg, blockedNames) {
|
|
287
|
+
const map = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const sec of inv?.sections ?? []) map.set(sec.name, sec);
|
|
289
|
+
for (const item of cfg.inject ?? []) {
|
|
290
|
+
const existing = map.get(item.name);
|
|
291
|
+
if (existing) map.set(item.name, {
|
|
292
|
+
...existing,
|
|
293
|
+
order: item.order
|
|
294
|
+
});
|
|
295
|
+
else map.set(item.name, {
|
|
296
|
+
name: item.name,
|
|
297
|
+
order: item.order,
|
|
298
|
+
text: item.text || "<动态生成>",
|
|
299
|
+
active: !blockedNames.has(item.name),
|
|
300
|
+
replaced: false
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return [...map.values()].sort((a, b) => a.order - b.order);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Resolve a preset's relative order into an absolute ordered list (0..n-1).
|
|
307
|
+
* Sections with no anchor (or whose anchor is absent) come first in preset
|
|
308
|
+
* order; each anchored section is inserted right after its anchor; any
|
|
309
|
+
* remaining sections (cycles) are appended.
|
|
310
|
+
*/
|
|
311
|
+
function resolveOrder(presetOrder) {
|
|
312
|
+
const list = presetOrder ?? [];
|
|
313
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
314
|
+
const textMap = /* @__PURE__ */ new Map();
|
|
315
|
+
for (const sec of list) {
|
|
316
|
+
afterMap.set(sec.name, sec.after);
|
|
317
|
+
textMap.set(sec.name, sec.text);
|
|
318
|
+
}
|
|
319
|
+
const result = [];
|
|
320
|
+
const placed = /* @__PURE__ */ new Set();
|
|
321
|
+
for (const sec of list) {
|
|
322
|
+
const anchor = afterMap.get(sec.name);
|
|
323
|
+
if (!anchor || !afterMap.has(anchor)) {
|
|
324
|
+
result.push(sec.name);
|
|
325
|
+
placed.add(sec.name);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
let changed = true;
|
|
329
|
+
while (changed) {
|
|
330
|
+
changed = false;
|
|
331
|
+
for (const sec of list) {
|
|
332
|
+
if (placed.has(sec.name)) continue;
|
|
333
|
+
const anchor = afterMap.get(sec.name);
|
|
334
|
+
if (anchor && placed.has(anchor)) {
|
|
335
|
+
result.splice(result.indexOf(anchor) + 1, 0, sec.name);
|
|
336
|
+
placed.add(sec.name);
|
|
337
|
+
changed = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
for (const sec of list) if (!placed.has(sec.name)) {
|
|
342
|
+
result.push(sec.name);
|
|
343
|
+
placed.add(sec.name);
|
|
344
|
+
}
|
|
345
|
+
return result.map((name, i) => ({
|
|
346
|
+
name,
|
|
347
|
+
order: i,
|
|
348
|
+
text: textMap.get(name) ?? ""
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Build a preset snapshot from the current config. The order list is derived
|
|
353
|
+
* from the MERGED section list (not just `cfg.inject`) so that a preset saved
|
|
354
|
+
* after only blocking sections (without reordering) still carries the full
|
|
355
|
+
* ordered set, and thus a meaningful "active list" when applied.
|
|
356
|
+
*/
|
|
357
|
+
function buildPresetData(cfg, merged) {
|
|
358
|
+
const order = merged.map((sec, i) => ({
|
|
359
|
+
name: sec.name,
|
|
360
|
+
after: i > 0 ? merged[i - 1].name : void 0,
|
|
361
|
+
text: sec.text ?? ""
|
|
362
|
+
}));
|
|
363
|
+
return {
|
|
364
|
+
sections: cfg.sections,
|
|
365
|
+
replace: cfg.replace,
|
|
366
|
+
order,
|
|
367
|
+
tools: cfg.tools
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Compute the config patch when applying a preset:
|
|
372
|
+
* - same-name sections are overridden (the order list drives this at runtime)
|
|
373
|
+
* - preset sections missing from the current prompt are added
|
|
374
|
+
* - current sections NOT in the preset's ordered list are disabled by default
|
|
375
|
+
* - only the preset's ACTIVE sections (in the order list but not in its block
|
|
376
|
+
* list) are unblocked; the preset's own blocked sections stay blocked.
|
|
377
|
+
*/
|
|
378
|
+
function applyPresetData(data, cfg, currentNames) {
|
|
379
|
+
const presetOrder = data.order ?? [];
|
|
380
|
+
const presetNames = new Set(presetOrder.map((x) => x.name));
|
|
381
|
+
const blocked = new Set(data.sections ?? []);
|
|
382
|
+
if (presetOrder.length > 0) {
|
|
383
|
+
const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)));
|
|
384
|
+
for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name);
|
|
385
|
+
for (const name of activeNames) blocked.delete(name);
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
sections: [...blocked],
|
|
389
|
+
replace: {
|
|
390
|
+
...cfg.replace ?? {},
|
|
391
|
+
...data.replace ?? {}
|
|
392
|
+
},
|
|
393
|
+
inject: resolveOrder(presetOrder),
|
|
394
|
+
tools: {
|
|
395
|
+
exclude: data.tools?.exclude ?? [],
|
|
396
|
+
include: data.tools?.include ?? []
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/** Whether a tool is hidden under the current include/exclude config. */
|
|
401
|
+
function isToolHidden(name, toolsCfg) {
|
|
402
|
+
const include = toolsCfg?.include ?? [];
|
|
403
|
+
const exclude = toolsCfg?.exclude ?? [];
|
|
404
|
+
return include.length > 0 ? !include.includes(name) : exclude.includes(name);
|
|
405
|
+
}
|
|
406
|
+
/** Toggle a tool's hidden state, returning a new tools config. */
|
|
407
|
+
function toggleTool(name, currentlyHidden, toolsCfg) {
|
|
408
|
+
const include = toolsCfg?.include ?? [];
|
|
409
|
+
const exclude = toolsCfg?.exclude ?? [];
|
|
410
|
+
if (include.length > 0) {
|
|
411
|
+
const next = include.slice();
|
|
412
|
+
if (currentlyHidden) next.push(name);
|
|
413
|
+
else {
|
|
414
|
+
const i = next.indexOf(name);
|
|
415
|
+
if (i >= 0) next.splice(i, 1);
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
exclude,
|
|
419
|
+
include: next
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const next = exclude.slice();
|
|
423
|
+
if (currentlyHidden) {
|
|
424
|
+
const i = next.indexOf(name);
|
|
425
|
+
if (i >= 0) next.splice(i, 1);
|
|
426
|
+
} else next.push(name);
|
|
427
|
+
return {
|
|
428
|
+
exclude: next,
|
|
429
|
+
include
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Switch between include (whitelist) and exclude (blacklist) modes. Turning
|
|
434
|
+
* include mode on discards names unknown to the current tool set and seeds the
|
|
435
|
+
* include list from the currently non-excluded tools; turning it off clears the
|
|
436
|
+
* include list (keep only known exclude names).
|
|
437
|
+
*/
|
|
438
|
+
function setToolMode(on, toolsCfg, toolNames) {
|
|
439
|
+
const known = new Set(toolNames);
|
|
440
|
+
const exclude = (toolsCfg?.exclude ?? []).filter((n) => known.has(n));
|
|
441
|
+
if (on) return {
|
|
442
|
+
exclude,
|
|
443
|
+
include: toolNames.filter((n) => !exclude.includes(n))
|
|
444
|
+
};
|
|
445
|
+
return {
|
|
446
|
+
exclude,
|
|
447
|
+
include: []
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/** Serialize a preset for export (JSON-safe full snapshot). */
|
|
451
|
+
function serializePreset(preset) {
|
|
452
|
+
return {
|
|
453
|
+
name: preset.name,
|
|
454
|
+
data: preset.data
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Import preset(s): same-name presets are skipped, only distinct names are
|
|
459
|
+
* added. Returns a new presets list (or the original if nothing changed).
|
|
460
|
+
*/
|
|
461
|
+
function addImportedPresets(existing, parsed, makeId) {
|
|
462
|
+
const incoming = Array.isArray(parsed) ? parsed : [parsed];
|
|
463
|
+
const existingNames = new Set(existing.map((p) => p.name));
|
|
464
|
+
const added = incoming.filter((p) => p && typeof p.name === "string" && !existingNames.has(p.name)).map((p) => ({
|
|
465
|
+
id: makeId(),
|
|
466
|
+
name: p.name,
|
|
467
|
+
data: p.data ?? {}
|
|
468
|
+
}));
|
|
469
|
+
return added.length > 0 ? [...existing, ...added] : existing;
|
|
470
|
+
}
|
|
471
|
+
/** Delete a preset, clearing the active id if it pointed at the removed one. */
|
|
472
|
+
function removePreset(presets, id, activeId) {
|
|
473
|
+
return {
|
|
474
|
+
presets: presets.filter((p) => p.id !== id),
|
|
475
|
+
activeId: activeId === id ? void 0 : activeId
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
284
479
|
//#region src/client/SectionsTab.tsx
|
|
285
480
|
/** Prompt-sections tab: block / replace / inject / reorder prompt sections. */
|
|
286
481
|
function SectionsTab({ cfg, inv, scope, t }) {
|
|
@@ -290,25 +485,7 @@ window.__ModuleLoader__.load({
|
|
|
290
485
|
const [draft, setDraft] = (0, react.useState)("");
|
|
291
486
|
const [dragName, setDragName] = (0, react.useState)(null);
|
|
292
487
|
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
|
-
})();
|
|
488
|
+
const merged = mergeSections(inv, cfg, blockedNames);
|
|
312
489
|
const toggleBlock = (name, currentlyBlocked) => {
|
|
313
490
|
const list = (cfg.sections ?? []).slice();
|
|
314
491
|
if (currentlyBlocked) {
|
|
@@ -538,41 +715,12 @@ window.__ModuleLoader__.load({
|
|
|
538
715
|
exclude: [],
|
|
539
716
|
include: []
|
|
540
717
|
};
|
|
541
|
-
const
|
|
542
|
-
const include = toolsCfg.include ?? [];
|
|
543
|
-
const includeMode = include.length > 0;
|
|
718
|
+
const includeMode = (toolsCfg.include ?? []).length > 0;
|
|
544
719
|
const toggle = (name, currentlyHidden) => {
|
|
545
|
-
|
|
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
|
-
}
|
|
720
|
+
scope.set("tools", toggleTool(name, currentlyHidden, toolsCfg));
|
|
567
721
|
};
|
|
568
722
|
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
|
-
});
|
|
723
|
+
scope.set("tools", setToolMode(on, toolsCfg, tools.map((tool) => tool.name)));
|
|
576
724
|
};
|
|
577
725
|
return (0, react.createElement)("div", {}, [(0, react.createElement)("div", { style: s.bar }, [(0, react.createElement)("button", {
|
|
578
726
|
style: includeMode ? s.tabActive : s.tab,
|
|
@@ -581,7 +729,7 @@ window.__ModuleLoader__.load({
|
|
|
581
729
|
style: includeMode ? s.tab : s.tabActive,
|
|
582
730
|
onClick: () => setIncludeMode(false)
|
|
583
731
|
}, 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 =
|
|
732
|
+
const hidden = isToolHidden(tool.name, toolsCfg);
|
|
585
733
|
return (0, react.createElement)("div", {
|
|
586
734
|
key: tool.name,
|
|
587
735
|
style: s.row
|
|
@@ -592,10 +740,6 @@ window.__ModuleLoader__.load({
|
|
|
592
740
|
}), (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
741
|
})])]);
|
|
594
742
|
}
|
|
595
|
-
function ensureOnlyKnown(list, known) {
|
|
596
|
-
const names = known.map((tool) => tool.name);
|
|
597
|
-
return list.filter((n) => names.includes(n));
|
|
598
|
-
}
|
|
599
743
|
//#endregion
|
|
600
744
|
//#region src/client/PresetsTab.tsx
|
|
601
745
|
/** Presets tab: save / apply / export / import complete customization snapshots. */
|
|
@@ -603,26 +747,11 @@ window.__ModuleLoader__.load({
|
|
|
603
747
|
const presets = cfg.presets ?? [];
|
|
604
748
|
const [name, setName] = (0, react.useState)("");
|
|
605
749
|
const fileRef = (0, react.useRef)(null);
|
|
606
|
-
const
|
|
607
|
-
|
|
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
|
-
})();
|
|
750
|
+
const merged = mergeSections(inv, cfg, new Set(cfg.sections ?? []));
|
|
751
|
+
const currentNames = new Set(merged.map((sec) => sec.name));
|
|
612
752
|
const saveCurrent = () => {
|
|
613
753
|
const presetName = name.trim() || `${t("preset")} ${presets.length + 1}`;
|
|
614
|
-
const
|
|
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
|
-
};
|
|
754
|
+
const data = buildPresetData(cfg, merged);
|
|
626
755
|
scope.set("presets", [...presets, {
|
|
627
756
|
id: genId(),
|
|
628
757
|
name: presetName,
|
|
@@ -631,32 +760,20 @@ window.__ModuleLoader__.load({
|
|
|
631
760
|
setName("");
|
|
632
761
|
};
|
|
633
762
|
const applyPreset = (preset) => {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
});
|
|
763
|
+
const patch = applyPresetData(preset.data, cfg, currentNames);
|
|
764
|
+
scope.set("inject", patch.inject);
|
|
765
|
+
scope.set("replace", patch.replace);
|
|
766
|
+
scope.set("sections", patch.sections);
|
|
767
|
+
scope.set("tools", patch.tools);
|
|
649
768
|
scope.set("activePreset", preset.id);
|
|
650
769
|
};
|
|
651
770
|
const deletePreset = (id) => {
|
|
652
|
-
|
|
653
|
-
|
|
771
|
+
const next = removePreset(presets, id, cfg.activePreset);
|
|
772
|
+
scope.set("presets", next.presets);
|
|
773
|
+
if (next.activeId === void 0 && cfg.activePreset === id) scope.unset("activePreset");
|
|
654
774
|
};
|
|
655
775
|
const exportPreset = (preset) => {
|
|
656
|
-
const blob = new Blob([JSON.stringify({
|
|
657
|
-
name: preset.name,
|
|
658
|
-
data: preset.data
|
|
659
|
-
}, null, 2)], { type: "application/json" });
|
|
776
|
+
const blob = new Blob([JSON.stringify(serializePreset(preset), null, 2)], { type: "application/json" });
|
|
660
777
|
const url = URL.createObjectURL(blob);
|
|
661
778
|
const a = document.createElement("a");
|
|
662
779
|
a.href = url;
|
|
@@ -671,14 +788,7 @@ window.__ModuleLoader__.load({
|
|
|
671
788
|
reader.onload = () => {
|
|
672
789
|
try {
|
|
673
790
|
const parsed = JSON.parse(String(reader.result));
|
|
674
|
-
|
|
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]);
|
|
791
|
+
scope.set("presets", addImportedPresets(presets, parsed, genId));
|
|
682
792
|
} catch {}
|
|
683
793
|
};
|
|
684
794
|
reader.readAsText(file);
|
|
@@ -734,51 +844,6 @@ window.__ModuleLoader__.load({
|
|
|
734
844
|
function genId() {
|
|
735
845
|
return "p_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
736
846
|
}
|
|
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
847
|
//#endregion
|
|
783
848
|
//#region src/client/index.ts
|
|
784
849
|
/**
|
package/client/client.js.map
CHANGED
|
@@ -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/SectionsTab.tsx","../src/client/ToolsTab.tsx","../src/client/PresetsTab.tsx","../src/client/index.ts"],"sourcesContent":["/** Locale dictionaries for the prompt-customizer panel. */\nexport const DICT = {\n zh: {\n nav: '提示词定制',\n loading: '加载中…',\n unavailable: '当前环境未启用本命名空间。',\n tabsSections: '提示词',\n tabsTools: '工具',\n tabsPresets: '预设',\n refresh: '刷新',\n blockedOff: '未屏蔽',\n blockedOn: '已屏蔽',\n replaced: '已替换',\n replace: '替换',\n clearReplace: '清除替换',\n injectNew: '注入新段',\n name: '名称',\n order: '顺序',\n text: '文本',\n add: '添加',\n hiddenOff: '可见',\n hiddenOn: '已隐藏',\n includeMode: '白名单模式:仅保留勾选项',\n excludeMode: '黑名单模式:勾选 = 隐藏',\n empty: '(空)',\n dynamic: '<动态生成>',\n drag: '拖动排序',\n moveUp: '上移',\n moveDown: '下移',\n savePreset: '保存当前为预设',\n presetName: '预设名称',\n save: '保存',\n importPreset: '导入预设',\n import: '导入',\n apply: '应用',\n export: '导出',\n delete: '删除',\n active: '使用中',\n preset: '预设',\n },\n en: {\n nav: 'Prompt Customizer',\n loading: 'Loading…',\n unavailable: 'Namespace unavailable in this environment.',\n tabsSections: 'Sections',\n tabsTools: 'Tools',\n tabsPresets: 'Presets',\n refresh: 'Refresh',\n blockedOff: 'Active',\n blockedOn: 'Blocked',\n replaced: 'Replaced',\n replace: 'Replace',\n clearReplace: 'Clear',\n injectNew: 'Inject section',\n name: 'Name',\n order: 'Order',\n text: 'Text',\n add: 'Add',\n hiddenOff: 'Visible',\n hiddenOn: 'Hidden',\n includeMode: 'Whitelist: keep only checked',\n excludeMode: 'Blacklist: check to hide',\n empty: '(empty)',\n dynamic: '<dynamic>',\n drag: 'Drag to reorder',\n moveUp: 'Move up',\n moveDown: 'Move down',\n savePreset: 'Save current as preset',\n presetName: 'Preset name',\n save: 'Save',\n importPreset: 'Import preset',\n import: 'Import',\n apply: 'Apply',\n export: 'Export',\n delete: 'Delete',\n active: 'Active',\n preset: 'Preset',\n },\n} as const\n\nexport type Translate = (key: string) => string\n","/** Inline styles shared by the prompt-customizer panel components. */\nexport const s = (() => {\n const flex = { display: 'flex', alignItems: 'center' }\n return {\n root: { fontFamily: 'inherit', fontSize: 13, lineHeight: 1.5 },\n bar: { ...flex, gap: 6, marginBottom: 8, flexWrap: 'wrap' },\n tab: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', opacity: 0.75 },\n tabActive: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.7)', borderRadius: 6, background: 'rgba(128,128,128,.18)', cursor: 'pointer', color: 'inherit' },\n refresh: { marginLeft: 'auto', padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit' },\n list: { display: 'flex', flexDirection: 'column', gap: 6 },\n row: { ...flex, gap: 8, padding: '6px 8px', border: '1px solid rgba(128,128,128,.2)', borderRadius: 8, background: 'rgba(128,128,128,.06)' },\n switchWrap: { ...flex, gap: 6, minWidth: 90 },\n rowBody: { flex: 1, minWidth: 0 },\n rowTitle: { ...flex, gap: 6, alignItems: 'baseline', flexWrap: 'wrap' },\n code: { fontFamily: 'monospace', fontSize: 12, wordBreak: 'break-all' },\n orderTag: { color: 'rgba(128,128,128,.8)', fontSize: 11 },\n preview: { color: 'rgba(128,128,128,.85)', fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n badgeOk: { color: '#3fb950', fontSize: 11, border: '1px solid rgba(63,185,80,.5)', borderRadius: 999, padding: '0 6px' },\n badgeBlocked: { color: '#f85149', fontSize: 11, border: '1px solid rgba(248,81,73,.5)', borderRadius: 999, padding: '0 6px' },\n badgeReplaced: { color: '#d29922', fontSize: 11, border: '1px solid rgba(210,153,34,.5)', borderRadius: 999, padding: '0 6px' },\n mini: { padding: '3px 8px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', whiteSpace: 'nowrap' },\n arrowCol: { display: 'flex', flexDirection: 'column', gap: 2 },\n arrow: { padding: '0 6px', border: '1px solid rgba(128,128,128,.3)', borderRadius: 4, background: 'transparent', cursor: 'pointer', color: 'inherit', fontSize: 11, lineHeight: 1.4, opacity: 0.8 },\n rowDragging: { opacity: 0.4 },\n rowDropAbove: { boxShadow: '0 -2px 0 0 #58a6ff' },\n rowDropBelow: { boxShadow: '0 2px 0 0 #58a6ff' },\n dragHandle: { cursor: 'grab', color: 'rgba(128,128,128,.7)', fontSize: 13, padding: '0 2px', userSelect: 'none' },\n injectBox: { marginTop: 10, padding: '8px', border: '1px dashed rgba(128,128,128,.4)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 },\n injectRow: { ...flex, gap: 6 },\n editBox: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 },\n editInput: { padding: '6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit', fontFamily: 'inherit', fontSize: 12, resize: 'vertical', width: '100%', boxSizing: 'border-box' },\n input: { padding: '4px 6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit' },\n muted: { color: 'rgba(128,128,128,.75)', fontSize: 12 },\n error: { color: '#f85149', fontSize: 12, marginBottom: 6 },\n }\n})()\n","/** 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 { s } from './styles.ts'\n\ninterface Section {\n name: string\n order: number\n text?: string\n active: boolean\n replaced: boolean\n}\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[] = (() => {\n const map = new Map<string, Section>()\n for (const sec of inv?.sections ?? []) map.set(sec.name, sec)\n for (const item of cfg.inject ?? []) {\n const existing = map.get(item.name)\n if (existing) map.set(item.name, { ...existing, order: item.order })\n else map.set(item.name, {\n name: item.name,\n order: item.order,\n text: item.text || '<动态生成>',\n active: !blockedNames.has(item.name),\n replaced: false,\n })\n }\n return [...map.values()].sort((a, b) => a.order - b.order)\n })()\n\n const toggleBlock = (name: string, currentlyBlocked: boolean): void => {\n const list = (cfg.sections ?? []).slice()\n if (currentlyBlocked) { const i = list.indexOf(name); if (i >= 0) list.splice(i, 1) }\n else list.push(name)\n scope.set('sections', list)\n }\n const startReplace = (name: string, original: string): void => {\n // Echo the ORIGINAL prompt text so the user edits from the current value.\n setEditing(name)\n setDraft(replace[name] ?? original)\n }\n const commitReplace = (name: string): void => {\n const next = { ...replace }\n if (draft.trim() === '') delete next[name]\n else next[name] = draft\n scope.set('replace', next)\n setEditing(null)\n }\n const clearReplace = (name: string): void => {\n const next = { ...replace }\n delete next[name]\n scope.set('replace', next)\n }\n\n // Persist the full ordered list. We treat the sorted list as an array and\n // re-index every section with a sequential integer order (0, 1, 2, …), so\n // there are never duplicate or fractional orders. text is left empty for\n // original sections (keeps their text); injected sections keep their own.\n const persistOrder = (ordered: Section[]): void => {\n const list = ordered.map((sec, i) => {\n const existing = (cfg.inject ?? []).find((x) => x.name === sec.name)\n return { name: sec.name, order: i, text: existing ? existing.text : '' }\n })\n scope.set('inject', list)\n }\n\n // Move a section up/down one slot (array-index swap), then re-index.\n const moveOrder = (index: number, dir: -1 | 1): void => {\n const target = index + dir\n if (target < 0 || target >= merged.length) return\n const next = merged.slice()\n const [item] = next.splice(index, 1)\n next.splice(target, 0, item)\n persistOrder(next)\n }\n\n const onDragStart = (e: DragEvent, name: string): void => {\n setDragName(name)\n e.dataTransfer.effectAllowed = 'move'\n try { e.dataTransfer.setData('text/plain', name) } catch { /* ignore */ }\n }\n const onDragOver = (e: DragEvent, name: string): void => {\n if (!dragName || dragName === name) return\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n const rect = e.currentTarget.getBoundingClientRect()\n const pos = e.clientY < rect.top + rect.height / 2 ? 'above' : 'below'\n setDropTarget({ name, pos })\n }\n const onDrop = (e: DragEvent, target: Section): void => {\n e.preventDefault()\n if (!dragName || dragName === target.name) { setDragName(null); setDropTarget(null); return }\n const dragged = merged.find((x) => x.name === dragName)\n if (!dragged) { setDragName(null); setDropTarget(null); return }\n const next = merged.filter((x) => x.name !== dragName)\n const idx = next.findIndex((x) => x.name === target.name)\n const pos = dropTarget?.name === target.name ? dropTarget.pos : 'below'\n next.splice(pos === 'above' ? idx : idx + 1, 0, dragged)\n persistOrder(next)\n setDragName(null)\n setDropTarget(null)\n }\n const onDragEnd = (): void => { setDragName(null); setDropTarget(null) }\n\n // Add a new injected section, placed by its order hint, then re-index.\n const addSection = (name: string, order: number, text: string): void => {\n const next = merged.slice()\n const entry: Section = { name, order, text: text || '<动态生成>', active: true, replaced: false }\n const existing = next.findIndex((x) => x.name === name)\n if (existing >= 0) next[existing] = entry\n else {\n const idx = next.findIndex((x) => x.order > order)\n if (idx < 0) next.push(entry)\n else next.splice(idx, 0, entry)\n }\n persistOrder(next)\n }\n\n return h('div', { style: s.list }, [\n merged.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n merged.map((sec, index) => {\n const blocked = blockedNames.has(sec.name)\n const isEditing = editing === sec.name\n const isDragging = dragName === sec.name\n const isDropTarget = dropTarget?.name === sec.name\n const rowStyle = {\n ...s.row,\n ...(isDragging ? s.rowDragging : {}),\n ...(isDropTarget && dropTarget.pos === 'above' ? s.rowDropAbove : {}),\n ...(isDropTarget && dropTarget.pos === 'below' ? s.rowDropBelow : {}),\n }\n return h('div', {\n key: sec.name,\n style: rowStyle,\n draggable: true,\n onDragStart: (e: DragEvent) => onDragStart(e, sec.name),\n onDragOver: (e: DragEvent) => onDragOver(e, sec.name),\n onDrop: (e: DragEvent) => onDrop(e, sec),\n onDragEnd,\n }, [\n h('span', { style: s.dragHandle, title: t('drag') }, '⠿'),\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: blocked, onChange: () => toggleBlock(sec.name, blocked) }),\n h('span', { style: blocked ? s.badgeBlocked : s.badgeOk }, blocked ? t('blockedOn') : t('blockedOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, sec.name),\n h('span', { style: s.orderTag }, '#' + index),\n sec.replaced ? h('span', { style: s.badgeReplaced }, t('replaced')) : null,\n ]),\n isEditing\n ? h('div', { style: s.editBox }, [\n h('textarea', { style: s.editInput, value: draft, onChange: (e: ChangeEvent<HTMLTextAreaElement>) => setDraft(e.target.value), rows: 3 }),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => commitReplace(sec.name) }, t('add')),\n h('button', { style: s.mini, onClick: () => setEditing(null) }, t('clearReplace')),\n ]),\n ])\n : h('div', { style: s.preview }, String(sec.text ?? '').slice(0, 140) || t('dynamic')),\n ]),\n h('div', { style: s.arrowCol }, [\n h('button', { style: s.arrow, disabled: index === 0, onClick: () => moveOrder(index, -1), title: t('moveUp') }, '↑'),\n h('button', { style: s.arrow, disabled: index === merged.length - 1, onClick: () => moveOrder(index, 1), title: t('moveDown') }, '↓'),\n ]),\n h('button', { style: s.mini, onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? '')) }, replace[sec.name] ? t('clearReplace') : t('replace')),\n ])\n }),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('injectNew')),\n h(InjectForm, { onAdd: addSection, t }),\n ]),\n ])\n}\n\nfunction InjectForm({ onAdd, t }: { onAdd: (name: string, order: number, text: string) => void; t: Translate }): ReactElement {\n const [name, setName] = useState('')\n const [order, setOrder] = useState('120')\n const [text, setText] = useState('')\n\n const submit = (): void => {\n if (!name.trim()) return\n onAdd(name.trim(), Number(order) || 120, text)\n setName(''); setText(''); setOrder('120')\n }\n\n return h('div', {}, [\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, width: '22%' }, placeholder: t('name'), value: name, onChange: (e) => setName(e.target.value) }),\n h('input', { style: { ...s.input, width: '12%' }, type: 'number', placeholder: t('order'), value: order, onChange: (e) => setOrder(e.target.value) }),\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('text'), value: text, onChange: (e) => setText(e.target.value) }),\n h('button', { style: s.mini, onClick: submit }, t('add')),\n ]),\n ])\n}\n","/** Tools tab: hide tools from the model catalog by exclude/include. */\nimport { createElement as h, type ReactElement } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { 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 exclude = toolsCfg.exclude ?? []\n const include = toolsCfg.include ?? []\n const includeMode = include.length > 0\n\n const toggle = (name: string, currentlyHidden: boolean): void => {\n if (includeMode) {\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 scope.set('tools', { ...toolsCfg, include: next })\n } else {\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', { ...toolsCfg, exclude: next })\n }\n }\n const setIncludeMode = (on: boolean): void => {\n scope.set('tools', on\n ? { exclude: exclude.filter((n) => tools.some((tool) => tool.name === n)), include: tools.filter((tool) => !tool.hidden).map((tool) => tool.name) }\n : { exclude: ensureOnlyKnown(exclude, tools), include: [] })\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 = includeMode ? !include.includes(tool.name) : exclude.includes(tool.name)\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\nfunction ensureOnlyKnown(list: string[], known: Array<{ name: string }>): string[] {\n const names = known.map((tool) => tool.name)\n return list.filter((n) => names.includes(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 { 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 // Current section names (inventory + injected), used to decide what to block.\n const currentNames = (() => {\n const set = new Set<string>()\n for (const sec of inv?.sections ?? []) set.add(sec.name)\n for (const item of cfg.inject ?? []) set.add(item.name)\n return set\n })()\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 inject = cfg.inject ?? []\n const order = inject.map((sec, i) => ({\n name: sec.name,\n after: i > 0 ? inject[i - 1].name : undefined,\n text: sec.text,\n }))\n const data: PresetData = {\n sections: cfg.sections,\n replace: cfg.replace,\n order,\n tools: cfg.tools,\n }\n scope.set('presets', [...presets, { id: genId(), name: presetName, data }])\n setName('')\n }\n\n // Apply a preset. The preset's section list defines the active set:\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 // - the active sections are arranged by the preset's relative order\n const applyPreset = (preset: Preset): void => {\n const presetOrder = preset.data.order ?? []\n const presetNames = new Set(presetOrder.map((x) => x.name))\n\n // Blocked list: preset's own blocked names, plus any current section that\n // is not part of the preset's active set; preset's active names unblocked.\n const blocked = new Set(preset.data.sections ?? [])\n for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name)\n for (const name of presetNames) blocked.delete(name)\n\n scope.set('inject', resolveOrder(presetOrder))\n scope.set('replace', { ...(cfg.replace ?? {}), ...(preset.data.replace ?? {}) })\n scope.set('sections', [...blocked])\n scope.set('tools', preset.data.tools ?? { exclude: [], include: [] })\n scope.set('activePreset', preset.id)\n }\n\n const deletePreset = (id: string): void => {\n scope.set('presets', presets.filter((p) => p.id !== id))\n if (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({ name: preset.name, data: preset.data }, 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 const incoming = Array.isArray(parsed) ? parsed : [parsed]\n const existing = new Set(presets.map((p) => p.name))\n const added = incoming\n .filter((p) => p && typeof p.name === 'string' && !existing.has(p.name))\n .map((p) => ({ id: genId(), name: p.name, data: (p.data ?? {}) as PresetData }))\n if (added.length > 0) scope.set('presets', [...presets, ...added])\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/**\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 */\nfunction resolveOrder(presetOrder: Array<{ name: string; after?: string; text: string }>): Array<{ name: string; order: number; text: string }> {\n const afterMap = new Map<string, string | undefined>()\n const textMap = new Map<string, string>()\n for (const sec of presetOrder) {\n afterMap.set(sec.name, sec.after)\n textMap.set(sec.name, sec.text)\n }\n\n const result: string[] = []\n const placed = new Set<string>()\n\n // Roots: no anchor, or anchor not part of the preset's own set.\n for (const sec of presetOrder) {\n const anchor = afterMap.get(sec.name)\n if (!anchor || !afterMap.has(anchor)) {\n result.push(sec.name)\n placed.add(sec.name)\n }\n }\n\n // Chain resolution: place each section right after its (already placed) anchor.\n let changed = true\n while (changed) {\n changed = false\n for (const sec of presetOrder) {\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 // Any remaining (cycle / unresolved) → append in preset order.\n for (const sec of presetOrder) {\n if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '' }))\n}\n","/**\n * dsh-prompt-customizer — browser half (TypeScript source).\n *\n * Registers a \"提示词定制\" settings section (sidebar entry) and a plugin\n * settings card. The panel fetches the effective section / tool inventory\n * from the host `/api/prompt-customizer/inventory` and lets the user block,\n * replace, inject prompt sections and hide tools. All writes go through the\n * bound settings scope (`prompt-customizer` namespace) and apply live.\n *\n * Built by tsdown into the __ModuleLoader__ factory bundle at client/client.js;\n * the only external is the loader module table's react entry.\n */\nimport { createElement as h, useEffect, useState, type ReactElement } from 'react'\nimport { DICT, type Translate } from './locales.ts'\nimport { s } from './styles.ts'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport { SectionsTab } from './SectionsTab.tsx'\nimport { ToolsTab } from './ToolsTab.tsx'\nimport { PresetsTab } from './PresetsTab.tsx'\n\nconst NS = 'prompt-customizer'\nconst INVENTORY_URL = '/api/prompt-customizer/inventory'\n\nexport const name = 'prompt-customizer'\nexport const inject = ['slots', 'locale']\n\n/** The subset of the client cordis context this plugin touches. */\ninterface ClientContext {\n effect(callback: () => unknown, label?: string): void\n locale: {\n register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): Translate\n }\n inject(services: string[], callback: (scoped: SettingsScopeHost) => void): void\n}\n\n/** The host surface the settings card needs (settingsScope transport). */\ninterface SettingsScopeHost {\n settingsScope: {\n bind(options: { namespace: string }): SettingsScope\n }\n slots: {\n inject(name: string, register: () => unknown): void\n register(meta: Record<string, unknown>, render: () => unknown): unknown\n }\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, DICT), 'prompt-customizer: locale')\n const t = ctx.locale.bind(NS)\n\n // Register a standalone settings section (sidebar menu entry) so the panel\n // only appears under \"提示词定制\", not as a card inside the \"插件\" settings\n // list. Goes through a NESTED inject of settingsScope so the panel can\n // read/write the namespace config; on hosts without the settings transport\n // it stays hidden instead of unmounting the whole plugin.\n ctx.inject(['settingsScope'], (scoped) => {\n const scope = scoped.settingsScope.bind({ namespace: NS })\n\n scoped.slots.inject('settings.section', () => scoped.slots.register({\n name: 'settings.section',\n id: 'prompt-customizer',\n order: 50,\n label: () => t('nav'),\n locale: NS,\n inject: () => ({ t }),\n }, () => h(Panel, { scope, t })))\n })\n}\n\n// ── Root panel ──────────────────────────────────────────────────────────────\n\nfunction Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {\n const [snap, setSnap] = useState(() => scope.getSnapshot())\n const [inv, setInv] = useState<Inventory | null>(null)\n const [tab, setTab] = useState<'sections' | 'tools' | 'presets'>('sections')\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope])\n\n const refresh = (): void => {\n fetch(INVENTORY_URL)\n .then((r) => r.json())\n .then((data: Inventory) => { setInv(data); setError(null) })\n .catch((e: unknown) => setError(String(e instanceof Error ? e.message : e)))\n }\n useEffect(refresh, [])\n\n if (snap.status !== 'ready' && snap.value === undefined) {\n return h('div', { style: s.muted }, t('loading'))\n }\n\n const cfg: Config = snap.value && typeof snap.value === 'object'\n ? snap.value as Config\n : { sections: [], replace: {}, inject: [], tools: { exclude: [], include: [] } }\n\n return h('div', { style: s.root }, [\n h('div', { style: s.bar }, [\n h('button', { style: tab === 'sections' ? s.tabActive : s.tab, onClick: () => setTab('sections') }, t('tabsSections')),\n h('button', { style: tab === 'tools' ? s.tabActive : s.tab, onClick: () => setTab('tools') }, t('tabsTools')),\n h('button', { style: tab === 'presets' ? s.tabActive : s.tab, onClick: () => setTab('presets') }, t('tabsPresets')),\n h('button', { style: s.refresh, onClick: refresh }, t('refresh')),\n ]),\n error ? h('div', { style: s.error }, String(error)) : null,\n tab === 'sections'\n ? h(SectionsTab, { cfg, inv, scope, t })\n : tab === 'tools'\n ? h(ToolsTab, { cfg, inv, scope, t })\n : h(PresetsTab, { cfg, inv, scope, t }),\n ])\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","h","h","useState","useRef","h","h","useState"],"sources":["../src/client/locales.ts","../src/client/styles.ts","../src/client/presets.ts","../src/client/SectionsTab.tsx","../src/client/ToolsTab.tsx","../src/client/PresetsTab.tsx","../src/client/index.ts"],"sourcesContent":["/** Locale dictionaries for the prompt-customizer panel. */\nexport const DICT = {\n zh: {\n nav: '提示词定制',\n loading: '加载中…',\n unavailable: '当前环境未启用本命名空间。',\n tabsSections: '提示词',\n tabsTools: '工具',\n tabsPresets: '预设',\n refresh: '刷新',\n blockedOff: '未屏蔽',\n blockedOn: '已屏蔽',\n replaced: '已替换',\n replace: '替换',\n clearReplace: '清除替换',\n injectNew: '注入新段',\n name: '名称',\n order: '顺序',\n text: '文本',\n add: '添加',\n hiddenOff: '可见',\n hiddenOn: '已隐藏',\n includeMode: '白名单模式:仅保留勾选项',\n excludeMode: '黑名单模式:勾选 = 隐藏',\n empty: '(空)',\n dynamic: '<动态生成>',\n drag: '拖动排序',\n moveUp: '上移',\n moveDown: '下移',\n savePreset: '保存当前为预设',\n presetName: '预设名称',\n save: '保存',\n importPreset: '导入预设',\n import: '导入',\n apply: '应用',\n export: '导出',\n delete: '删除',\n active: '使用中',\n preset: '预设',\n },\n en: {\n nav: 'Prompt Customizer',\n loading: 'Loading…',\n unavailable: 'Namespace unavailable in this environment.',\n tabsSections: 'Sections',\n tabsTools: 'Tools',\n tabsPresets: 'Presets',\n refresh: 'Refresh',\n blockedOff: 'Active',\n blockedOn: 'Blocked',\n replaced: 'Replaced',\n replace: 'Replace',\n clearReplace: 'Clear',\n injectNew: 'Inject section',\n name: 'Name',\n order: 'Order',\n text: 'Text',\n add: 'Add',\n hiddenOff: 'Visible',\n hiddenOn: 'Hidden',\n includeMode: 'Whitelist: keep only checked',\n excludeMode: 'Blacklist: check to hide',\n empty: '(empty)',\n dynamic: '<dynamic>',\n drag: 'Drag to reorder',\n moveUp: 'Move up',\n moveDown: 'Move down',\n savePreset: 'Save current as preset',\n presetName: 'Preset name',\n save: 'Save',\n importPreset: 'Import preset',\n import: 'Import',\n apply: 'Apply',\n export: 'Export',\n delete: 'Delete',\n active: 'Active',\n preset: 'Preset',\n },\n} as const\n\nexport type Translate = (key: string) => string\n","/** Inline styles shared by the prompt-customizer panel components. */\nexport const s = (() => {\n const flex = { display: 'flex', alignItems: 'center' }\n return {\n root: { fontFamily: 'inherit', fontSize: 13, lineHeight: 1.5 },\n bar: { ...flex, gap: 6, marginBottom: 8, flexWrap: 'wrap' },\n tab: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', opacity: 0.75 },\n tabActive: { padding: '4px 10px', border: '1px solid rgba(128,128,128,.7)', borderRadius: 6, background: 'rgba(128,128,128,.18)', cursor: 'pointer', color: 'inherit' },\n refresh: { marginLeft: 'auto', padding: '4px 10px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit' },\n list: { display: 'flex', flexDirection: 'column', gap: 6 },\n row: { ...flex, gap: 8, padding: '6px 8px', border: '1px solid rgba(128,128,128,.2)', borderRadius: 8, background: 'rgba(128,128,128,.06)' },\n switchWrap: { ...flex, gap: 6, minWidth: 90 },\n rowBody: { flex: 1, minWidth: 0 },\n rowTitle: { ...flex, gap: 6, alignItems: 'baseline', flexWrap: 'wrap' },\n code: { fontFamily: 'monospace', fontSize: 12, wordBreak: 'break-all' },\n orderTag: { color: 'rgba(128,128,128,.8)', fontSize: 11 },\n preview: { color: 'rgba(128,128,128,.85)', fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n badgeOk: { color: '#3fb950', fontSize: 11, border: '1px solid rgba(63,185,80,.5)', borderRadius: 999, padding: '0 6px' },\n badgeBlocked: { color: '#f85149', fontSize: 11, border: '1px solid rgba(248,81,73,.5)', borderRadius: 999, padding: '0 6px' },\n badgeReplaced: { color: '#d29922', fontSize: 11, border: '1px solid rgba(210,153,34,.5)', borderRadius: 999, padding: '0 6px' },\n mini: { padding: '3px 8px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', cursor: 'pointer', color: 'inherit', whiteSpace: 'nowrap' },\n arrowCol: { display: 'flex', flexDirection: 'column', gap: 2 },\n arrow: { padding: '0 6px', border: '1px solid rgba(128,128,128,.3)', borderRadius: 4, background: 'transparent', cursor: 'pointer', color: 'inherit', fontSize: 11, lineHeight: 1.4, opacity: 0.8 },\n rowDragging: { opacity: 0.4 },\n rowDropAbove: { boxShadow: '0 -2px 0 0 #58a6ff' },\n rowDropBelow: { boxShadow: '0 2px 0 0 #58a6ff' },\n dragHandle: { cursor: 'grab', color: 'rgba(128,128,128,.7)', fontSize: 13, padding: '0 2px', userSelect: 'none' },\n injectBox: { marginTop: 10, padding: '8px', border: '1px dashed rgba(128,128,128,.4)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 },\n injectRow: { ...flex, gap: 6 },\n editBox: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 },\n editInput: { padding: '6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit', fontFamily: 'inherit', fontSize: 12, resize: 'vertical', width: '100%', boxSizing: 'border-box' },\n input: { padding: '4px 6px', border: '1px solid rgba(128,128,128,.35)', borderRadius: 6, background: 'transparent', color: 'inherit' },\n muted: { color: 'rgba(128,128,128,.75)', fontSize: 12 },\n error: { color: '#f85149', fontSize: 12, marginBottom: 6 },\n }\n})()\n","/** Pure preset / section helpers shared by the client UI and the node tests. */\nimport type { Config, Inventory, Preset, PresetData } from './types.ts'\n\nexport interface Section {\n name: string\n order: number\n text?: string\n active: boolean\n replaced: boolean\n}\n\n/** The config fields a preset apply must write back to the settings scope. */\nexport interface ConfigPatch {\n sections: string[]\n replace: Record<string, string>\n inject: Array<{ name: string; order: number; text: string }>\n tools: { exclude: string[]; include: string[] }\n}\n\n/** Merge the inventory sections with the user's inject list, sorted by order. */\nexport function mergeSections(inv: Inventory | null, cfg: Config, blockedNames: ReadonlySet<string>): Section[] {\n const map = new Map<string, Section>()\n for (const sec of inv?.sections ?? []) map.set(sec.name, sec)\n for (const item of cfg.inject ?? []) {\n const existing = map.get(item.name)\n if (existing) map.set(item.name, { ...existing, order: item.order })\n else map.set(item.name, {\n name: item.name,\n order: item.order,\n text: item.text || '<动态生成>',\n active: !blockedNames.has(item.name),\n replaced: false,\n })\n }\n return [...map.values()].sort((a, b) => a.order - b.order)\n}\n\n/**\n * Resolve a preset's relative order into an absolute ordered list (0..n-1).\n * Sections with no anchor (or whose anchor is absent) come first in preset\n * order; each anchored section is inserted right after its anchor; any\n * remaining sections (cycles) are appended.\n */\nexport function resolveOrder(presetOrder: PresetData['order']): Array<{ name: string; order: number; text: string }> {\n const list = presetOrder ?? []\n const afterMap = new Map<string, string | undefined>()\n const textMap = new Map<string, string>()\n for (const sec of list) {\n afterMap.set(sec.name, sec.after)\n textMap.set(sec.name, sec.text)\n }\n\n const result: string[] = []\n const placed = new Set<string>()\n\n for (const sec of list) {\n const anchor = afterMap.get(sec.name)\n if (!anchor || !afterMap.has(anchor)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n let changed = true\n while (changed) {\n changed = false\n for (const sec of list) {\n if (placed.has(sec.name)) continue\n const anchor = afterMap.get(sec.name)\n if (anchor && placed.has(anchor)) {\n result.splice(result.indexOf(anchor) + 1, 0, sec.name)\n placed.add(sec.name)\n changed = true\n }\n }\n }\n\n for (const sec of list) {\n if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }\n }\n\n return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '' }))\n}\n\n/**\n * Build a preset snapshot from the current config. The order list is derived\n * from the MERGED section list (not just `cfg.inject`) so that a preset saved\n * after only blocking sections (without reordering) still carries the full\n * ordered set, and thus a meaningful \"active list\" when applied.\n */\nexport function buildPresetData(cfg: Config, merged: Section[]): PresetData {\n const order = merged.map((sec, i) => ({\n name: sec.name,\n after: i > 0 ? merged[i - 1].name : undefined,\n text: sec.text ?? '',\n }))\n return {\n sections: cfg.sections,\n replace: cfg.replace,\n order,\n tools: cfg.tools,\n }\n}\n\n/**\n * Compute the config patch when applying a preset:\n * - same-name sections are overridden (the order list drives this at runtime)\n * - preset sections missing from the current prompt are added\n * - current sections NOT in the preset's ordered list are disabled by default\n * - only the preset's ACTIVE sections (in the order list but not in its block\n * list) are unblocked; the preset's own blocked sections stay blocked.\n */\nexport function applyPresetData(data: PresetData, cfg: Config, currentNames: ReadonlySet<string>): ConfigPatch {\n const presetOrder = data.order ?? []\n const presetNames = new Set(presetOrder.map((x) => x.name))\n const blocked = new Set(data.sections ?? [])\n\n // Only enforce the \"active set\" when the preset actually defines an order.\n // An empty order (e.g. a hand-authored / imported preset that only blocks a\n // few sections) must not default-disable every current section.\n if (presetOrder.length > 0) {\n const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)))\n for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name)\n for (const name of activeNames) blocked.delete(name)\n }\n\n return {\n sections: [...blocked],\n replace: { ...(cfg.replace ?? {}), ...(data.replace ?? {}) },\n inject: resolveOrder(presetOrder),\n tools: { exclude: data.tools?.exclude ?? [], include: data.tools?.include ?? [] },\n }\n}\n\n// ── Tool filtering ──────────────────────────────────────────────────────────\n\nexport type ToolsCfg = { exclude?: string[]; include?: string[] }\n\n/** Whether a tool is hidden under the current include/exclude config. */\nexport function isToolHidden(name: string, toolsCfg: ToolsCfg | undefined): boolean {\n const include = toolsCfg?.include ?? []\n const exclude = toolsCfg?.exclude ?? []\n return include.length > 0 ? !include.includes(name) : exclude.includes(name)\n}\n\n/** Toggle a tool's hidden state, returning a new tools config. */\nexport function toggleTool(name: string, currentlyHidden: boolean, toolsCfg: ToolsCfg | undefined): ToolsCfg {\n const include = toolsCfg?.include ?? []\n const exclude = toolsCfg?.exclude ?? []\n if (include.length > 0) {\n const next = include.slice()\n if (currentlyHidden) next.push(name)\n else { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }\n return { exclude, include: next }\n }\n const next = exclude.slice()\n if (currentlyHidden) { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }\n else next.push(name)\n return { exclude: next, include }\n}\n\n/**\n * Switch between include (whitelist) and exclude (blacklist) modes. Turning\n * include mode on discards names unknown to the current tool set and seeds the\n * include list from the currently non-excluded tools; turning it off clears the\n * include list (keep only known exclude names).\n */\nexport function setToolMode(on: boolean, toolsCfg: ToolsCfg | undefined, toolNames: string[]): ToolsCfg {\n const known = new Set(toolNames)\n const exclude = (toolsCfg?.exclude ?? []).filter((n) => known.has(n))\n if (on) return { exclude, include: toolNames.filter((n) => !exclude.includes(n)) }\n return { exclude, include: [] }\n}\n\n// ── Preset list operations ──────────────────────────────────────────────────\n\n/** Serialize a preset for export (JSON-safe full snapshot). */\nexport function serializePreset(preset: Preset): { name: string; data: PresetData } {\n return { name: preset.name, data: preset.data }\n}\n\n/**\n * Import preset(s): same-name presets are skipped, only distinct names are\n * added. Returns a new presets list (or the original if nothing changed).\n */\nexport function addImportedPresets(existing: Preset[], parsed: unknown, makeId: () => string): Preset[] {\n const incoming = Array.isArray(parsed) ? parsed : [parsed]\n const existingNames = new Set(existing.map((p) => p.name))\n const added = incoming\n .filter((p) => p && typeof (p as { name?: unknown }).name === 'string' && !existingNames.has((p as Preset).name))\n .map((p) => ({ id: makeId(), name: (p as Preset).name, data: ((p as Preset).data ?? {}) as PresetData }))\n return added.length > 0 ? [...existing, ...added] : existing\n}\n\n/** Delete a preset, clearing the active id if it pointed at the removed one. */\nexport function removePreset(presets: Preset[], id: string, activeId?: string): { presets: Preset[]; activeId?: string } {\n return {\n presets: presets.filter((p) => p.id !== id),\n activeId: activeId === id ? undefined : activeId,\n }\n}\n","/** Prompt-sections tab: block / replace / inject / reorder prompt sections. */\nimport { createElement as h, useState, type ReactElement, type DragEvent, type ChangeEvent } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { mergeSections, type Section } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const replace = cfg.replace ?? {}\n const blockedNames = new Set(cfg.sections ?? [])\n const [editing, setEditing] = useState<string | null>(null)\n const [draft, setDraft] = useState('')\n const [dragName, setDragName] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<{ name: string; pos: 'above' | 'below' } | null>(null)\n\n // Merge inventory sections with user-injected sections, sorted by order.\n // Injected sections that don't exist in the original prompt appear here too,\n // positioned by their order value.\n const merged: Section[] = mergeSections(inv, cfg, blockedNames)\n\n const toggleBlock = (name: string, currentlyBlocked: boolean): void => {\n const list = (cfg.sections ?? []).slice()\n if (currentlyBlocked) { const i = list.indexOf(name); if (i >= 0) list.splice(i, 1) }\n else list.push(name)\n scope.set('sections', list)\n }\n const startReplace = (name: string, original: string): void => {\n // Echo the ORIGINAL prompt text so the user edits from the current value.\n setEditing(name)\n setDraft(replace[name] ?? original)\n }\n const commitReplace = (name: string): void => {\n const next = { ...replace }\n if (draft.trim() === '') delete next[name]\n else next[name] = draft\n scope.set('replace', next)\n setEditing(null)\n }\n const clearReplace = (name: string): void => {\n const next = { ...replace }\n delete next[name]\n scope.set('replace', next)\n }\n\n // Persist the full ordered list. We treat the sorted list as an array and\n // re-index every section with a sequential integer order (0, 1, 2, …), so\n // there are never duplicate or fractional orders. text is left empty for\n // original sections (keeps their text); injected sections keep their own.\n const persistOrder = (ordered: Section[]): void => {\n const list = ordered.map((sec, i) => {\n const existing = (cfg.inject ?? []).find((x) => x.name === sec.name)\n return { name: sec.name, order: i, text: existing ? existing.text : '' }\n })\n scope.set('inject', list)\n }\n\n // Move a section up/down one slot (array-index swap), then re-index.\n const moveOrder = (index: number, dir: -1 | 1): void => {\n const target = index + dir\n if (target < 0 || target >= merged.length) return\n const next = merged.slice()\n const [item] = next.splice(index, 1)\n next.splice(target, 0, item)\n persistOrder(next)\n }\n\n const onDragStart = (e: DragEvent, name: string): void => {\n setDragName(name)\n e.dataTransfer.effectAllowed = 'move'\n try { e.dataTransfer.setData('text/plain', name) } catch { /* ignore */ }\n }\n const onDragOver = (e: DragEvent, name: string): void => {\n if (!dragName || dragName === name) return\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n const rect = e.currentTarget.getBoundingClientRect()\n const pos = e.clientY < rect.top + rect.height / 2 ? 'above' : 'below'\n setDropTarget({ name, pos })\n }\n const onDrop = (e: DragEvent, target: Section): void => {\n e.preventDefault()\n if (!dragName || dragName === target.name) { setDragName(null); setDropTarget(null); return }\n const dragged = merged.find((x) => x.name === dragName)\n if (!dragged) { setDragName(null); setDropTarget(null); return }\n const next = merged.filter((x) => x.name !== dragName)\n const idx = next.findIndex((x) => x.name === target.name)\n const pos = dropTarget?.name === target.name ? dropTarget.pos : 'below'\n next.splice(pos === 'above' ? idx : idx + 1, 0, dragged)\n persistOrder(next)\n setDragName(null)\n setDropTarget(null)\n }\n const onDragEnd = (): void => { setDragName(null); setDropTarget(null) }\n\n // Add a new injected section, placed by its order hint, then re-index.\n const addSection = (name: string, order: number, text: string): void => {\n const next = merged.slice()\n const entry: Section = { name, order, text: text || '<动态生成>', active: true, replaced: false }\n const existing = next.findIndex((x) => x.name === name)\n if (existing >= 0) next[existing] = entry\n else {\n const idx = next.findIndex((x) => x.order > order)\n if (idx < 0) next.push(entry)\n else next.splice(idx, 0, entry)\n }\n persistOrder(next)\n }\n\n return h('div', { style: s.list }, [\n merged.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n merged.map((sec, index) => {\n const blocked = blockedNames.has(sec.name)\n const isEditing = editing === sec.name\n const isDragging = dragName === sec.name\n const isDropTarget = dropTarget?.name === sec.name\n const rowStyle = {\n ...s.row,\n ...(isDragging ? s.rowDragging : {}),\n ...(isDropTarget && dropTarget.pos === 'above' ? s.rowDropAbove : {}),\n ...(isDropTarget && dropTarget.pos === 'below' ? s.rowDropBelow : {}),\n }\n return h('div', {\n key: sec.name,\n style: rowStyle,\n draggable: true,\n onDragStart: (e: DragEvent) => onDragStart(e, sec.name),\n onDragOver: (e: DragEvent) => onDragOver(e, sec.name),\n onDrop: (e: DragEvent) => onDrop(e, sec),\n onDragEnd,\n }, [\n h('span', { style: s.dragHandle, title: t('drag') }, '⠿'),\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: blocked, onChange: () => toggleBlock(sec.name, blocked) }),\n h('span', { style: blocked ? s.badgeBlocked : s.badgeOk }, blocked ? t('blockedOn') : t('blockedOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, sec.name),\n h('span', { style: s.orderTag }, '#' + index),\n sec.replaced ? h('span', { style: s.badgeReplaced }, t('replaced')) : null,\n ]),\n isEditing\n ? h('div', { style: s.editBox }, [\n h('textarea', { style: s.editInput, value: draft, onChange: (e: ChangeEvent<HTMLTextAreaElement>) => setDraft(e.target.value), rows: 3 }),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => commitReplace(sec.name) }, t('add')),\n h('button', { style: s.mini, onClick: () => setEditing(null) }, t('clearReplace')),\n ]),\n ])\n : h('div', { style: s.preview }, String(sec.text ?? '').slice(0, 140) || t('dynamic')),\n ]),\n h('div', { style: s.arrowCol }, [\n h('button', { style: s.arrow, disabled: index === 0, onClick: () => moveOrder(index, -1), title: t('moveUp') }, '↑'),\n h('button', { style: s.arrow, disabled: index === merged.length - 1, onClick: () => moveOrder(index, 1), title: t('moveDown') }, '↓'),\n ]),\n h('button', { style: s.mini, onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? '')) }, replace[sec.name] ? t('clearReplace') : t('replace')),\n ])\n }),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('injectNew')),\n h(InjectForm, { onAdd: addSection, t }),\n ]),\n ])\n}\n\nfunction InjectForm({ onAdd, t }: { onAdd: (name: string, order: number, text: string) => void; t: Translate }): ReactElement {\n const [name, setName] = useState('')\n const [order, setOrder] = useState('120')\n const [text, setText] = useState('')\n\n const submit = (): void => {\n if (!name.trim()) return\n onAdd(name.trim(), Number(order) || 120, text)\n setName(''); setText(''); setOrder('120')\n }\n\n return h('div', {}, [\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, width: '22%' }, placeholder: t('name'), value: name, onChange: (e) => setName(e.target.value) }),\n h('input', { style: { ...s.input, width: '12%' }, type: 'number', placeholder: t('order'), value: order, onChange: (e) => setOrder(e.target.value) }),\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('text'), value: text, onChange: (e) => setText(e.target.value) }),\n h('button', { style: s.mini, onClick: submit }, t('add')),\n ]),\n ])\n}\n","/** Tools tab: hide tools from the model catalog by exclude/include. */\nimport { createElement as h, type ReactElement } from 'react'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { isToolHidden, setToolMode, toggleTool } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const tools = inv?.tools ?? []\n const toolsCfg = cfg.tools ?? { exclude: [], include: [] }\n const includeMode = (toolsCfg.include ?? []).length > 0\n\n const toggle = (name: string, currentlyHidden: boolean): void => {\n scope.set('tools', toggleTool(name, currentlyHidden, toolsCfg))\n }\n const setIncludeMode = (on: boolean): void => {\n scope.set('tools', setToolMode(on, toolsCfg, tools.map((tool) => tool.name)))\n }\n\n return h('div', {}, [\n h('div', { style: s.bar }, [\n h('button', { style: includeMode ? s.tabActive : s.tab, onClick: () => setIncludeMode(true) }, t('includeMode')),\n h('button', { style: includeMode ? s.tab : s.tabActive, onClick: () => setIncludeMode(false) }, t('excludeMode')),\n ]),\n h('div', { style: s.list }, [\n tools.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n tools.map((tool) => {\n // Hidden state comes from the CONFIG (exclude/include lists), not the\n // inventory snapshot, so toggling updates the checkbox immediately.\n const hidden = isToolHidden(tool.name, toolsCfg)\n return h('div', { key: tool.name, style: s.row }, [\n h('label', { style: s.switchWrap }, [\n h('input', { type: 'checkbox', checked: hidden, onChange: () => toggle(tool.name, hidden) }),\n h('span', { style: hidden ? s.badgeBlocked : s.badgeOk }, hidden ? t('hiddenOn') : t('hiddenOff')),\n ]),\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, h('span', { style: s.code }, tool.name)),\n h('div', { style: s.preview }, String(tool.description ?? '').slice(0, 140)),\n ]),\n ])\n }),\n ]),\n ])\n}\n","/** Presets tab: save / apply / export / import complete customization snapshots. */\nimport { createElement as h, useRef, useState, type ReactElement, type ChangeEvent } from 'react'\nimport type { Config, Inventory, Preset, PresetData, SettingsScope } from './types.ts'\nimport type { Translate } from './locales.ts'\nimport { addImportedPresets, applyPresetData, buildPresetData, mergeSections, removePreset, serializePreset } from './presets.ts'\nimport { s } from './styles.ts'\n\nexport function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {\n const presets = cfg.presets ?? []\n const [name, setName] = useState('')\n const fileRef = useRef<HTMLInputElement | null>(null)\n\n const blockedNames = new Set(cfg.sections ?? [])\n const merged = mergeSections(inv, cfg, blockedNames)\n const currentNames = new Set(merged.map((sec) => sec.name))\n\n // Capture the current customization as a new preset (full snapshot). The\n // absolute order is converted to a relative form (each section records the\n // section it should follow) so the preset stays portable across prompts.\n const saveCurrent = (): void => {\n const presetName = name.trim() || `${t('preset')} ${presets.length + 1}`\n const data = buildPresetData(cfg, merged)\n scope.set('presets', [...presets, { id: genId(), name: presetName, data }])\n setName('')\n }\n\n // Apply a preset. The patch is computed by the pure helper:\n // - same-name sections are overridden (content + order)\n // - preset sections missing from the current prompt are added\n // - current sections NOT in the preset list are disabled (blocked)\n // - only the preset's ACTIVE sections are unblocked (its own blocked list\n // is preserved)\n const applyPreset = (preset: Preset): void => {\n const patch = applyPresetData(preset.data, cfg, currentNames)\n scope.set('inject', patch.inject)\n scope.set('replace', patch.replace)\n scope.set('sections', patch.sections)\n scope.set('tools', patch.tools)\n scope.set('activePreset', preset.id)\n }\n\n const deletePreset = (id: string): void => {\n const next = removePreset(presets, id, cfg.activePreset)\n scope.set('presets', next.presets)\n if (next.activeId === undefined && cfg.activePreset === id) scope.unset('activePreset')\n }\n\n // Serialize a preset to a downloadable JSON file.\n const exportPreset = (preset: Preset): void => {\n const blob = new Blob([JSON.stringify(serializePreset(preset), null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = `${preset.name}.json`\n a.click()\n URL.revokeObjectURL(url)\n }\n\n // Import preset(s) from a JSON file. Same-name presets are skipped; only\n // presets with new names are added to the local list.\n const onImportFile = (e: ChangeEvent<HTMLInputElement>): void => {\n const file = e.target.files?.[0]\n if (!file) return\n const reader = new FileReader()\n reader.onload = () => {\n try {\n const parsed = JSON.parse(String(reader.result))\n scope.set('presets', addImportedPresets(presets, parsed, genId))\n } catch { /* ignore invalid json */ }\n }\n reader.readAsText(file)\n e.target.value = ''\n }\n\n return h('div', { style: s.list }, [\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('savePreset')),\n h('div', { style: s.injectRow }, [\n h('input', { style: { ...s.input, flex: 1 }, placeholder: t('presetName'), value: name, onChange: (e: ChangeEvent<HTMLInputElement>) => setName(e.target.value) }),\n h('button', { style: s.mini, onClick: saveCurrent }, t('save')),\n ]),\n ]),\n h('div', { style: s.injectBox }, [\n h('div', { style: s.rowTitle }, t('importPreset')),\n h('div', { style: s.injectRow }, [\n h('button', { style: s.mini, onClick: () => fileRef.current?.click() }, t('import')),\n h('input', { ref: fileRef, type: 'file', accept: '.json,application/json', style: { display: 'none' }, onChange: onImportFile }),\n ]),\n ]),\n presets.length === 0 ? h('div', { style: s.muted }, t('empty')) : null,\n presets.map((preset) => {\n const active = cfg.activePreset === preset.id\n return h('div', { key: preset.id, style: s.row }, [\n h('div', { style: s.rowBody }, [\n h('div', { style: s.rowTitle }, [\n h('span', { style: s.code }, preset.name),\n active ? h('span', { style: s.badgeOk }, t('active')) : null,\n ]),\n ]),\n h('button', { style: s.mini, onClick: () => applyPreset(preset) }, t('apply')),\n h('button', { style: s.mini, onClick: () => exportPreset(preset) }, t('export')),\n h('button', { style: s.mini, onClick: () => deletePreset(preset.id) }, t('delete')),\n ])\n }),\n ])\n}\n\nfunction genId(): string {\n return 'p_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7)\n}\n","/**\n * dsh-prompt-customizer — browser half (TypeScript source).\n *\n * Registers a \"提示词定制\" settings section (sidebar entry) and a plugin\n * settings card. The panel fetches the effective section / tool inventory\n * from the host `/api/prompt-customizer/inventory` and lets the user block,\n * replace, inject prompt sections and hide tools. All writes go through the\n * bound settings scope (`prompt-customizer` namespace) and apply live.\n *\n * Built by tsdown into the __ModuleLoader__ factory bundle at client/client.js;\n * the only external is the loader module table's react entry.\n */\nimport { createElement as h, useEffect, useState, type ReactElement } from 'react'\nimport { DICT, type Translate } from './locales.ts'\nimport { s } from './styles.ts'\nimport type { Config, Inventory, SettingsScope } from './types.ts'\nimport { SectionsTab } from './SectionsTab.tsx'\nimport { ToolsTab } from './ToolsTab.tsx'\nimport { PresetsTab } from './PresetsTab.tsx'\n\nconst NS = 'prompt-customizer'\nconst INVENTORY_URL = '/api/prompt-customizer/inventory'\n\nexport const name = 'prompt-customizer'\nexport const inject = ['slots', 'locale']\n\n/** The subset of the client cordis context this plugin touches. */\ninterface ClientContext {\n effect(callback: () => unknown, label?: string): void\n locale: {\n register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): Translate\n }\n inject(services: string[], callback: (scoped: SettingsScopeHost) => void): void\n}\n\n/** The host surface the settings card needs (settingsScope transport). */\ninterface SettingsScopeHost {\n settingsScope: {\n bind(options: { namespace: string }): SettingsScope\n }\n slots: {\n inject(name: string, register: () => unknown): void\n register(meta: Record<string, unknown>, render: () => unknown): unknown\n }\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, DICT), 'prompt-customizer: locale')\n const t = ctx.locale.bind(NS)\n\n // Register a standalone settings section (sidebar menu entry) so the panel\n // only appears under \"提示词定制\", not as a card inside the \"插件\" settings\n // list. Goes through a NESTED inject of settingsScope so the panel can\n // read/write the namespace config; on hosts without the settings transport\n // it stays hidden instead of unmounting the whole plugin.\n ctx.inject(['settingsScope'], (scoped) => {\n const scope = scoped.settingsScope.bind({ namespace: NS })\n\n scoped.slots.inject('settings.section', () => scoped.slots.register({\n name: 'settings.section',\n id: 'prompt-customizer',\n order: 50,\n label: () => t('nav'),\n locale: NS,\n inject: () => ({ t }),\n }, () => h(Panel, { scope, t })))\n })\n}\n\n// ── Root panel ──────────────────────────────────────────────────────────────\n\nfunction Panel({ scope, t }: { scope: SettingsScope; t: Translate }): ReactElement {\n const [snap, setSnap] = useState(() => scope.getSnapshot())\n const [inv, setInv] = useState<Inventory | null>(null)\n const [tab, setTab] = useState<'sections' | 'tools' | 'presets'>('sections')\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope])\n\n const refresh = (): void => {\n fetch(INVENTORY_URL)\n .then((r) => r.json())\n .then((data: Inventory) => { setInv(data); setError(null) })\n .catch((e: unknown) => setError(String(e instanceof Error ? e.message : e)))\n }\n useEffect(refresh, [])\n\n if (snap.status !== 'ready' && snap.value === undefined) {\n return h('div', { style: s.muted }, t('loading'))\n }\n\n const cfg: Config = snap.value && typeof snap.value === 'object'\n ? snap.value as Config\n : { sections: [], replace: {}, inject: [], tools: { exclude: [], include: [] } }\n\n return h('div', { style: s.root }, [\n h('div', { style: s.bar }, [\n h('button', { style: tab === 'sections' ? s.tabActive : s.tab, onClick: () => setTab('sections') }, t('tabsSections')),\n h('button', { style: tab === 'tools' ? s.tabActive : s.tab, onClick: () => setTab('tools') }, t('tabsTools')),\n h('button', { style: tab === 'presets' ? s.tabActive : s.tab, onClick: () => setTab('presets') }, t('tabsPresets')),\n h('button', { style: s.refresh, onClick: refresh }, t('refresh')),\n ]),\n error ? h('div', { style: s.error }, String(error)) : null,\n tab === 'sections'\n ? h(SectionsTab, { cfg, inv, scope, t })\n : tab === 'tools'\n ? h(ToolsTab, { cfg, inv, scope, t })\n : h(PresetsTab, { cfg, inv, scope, t }),\n ])\n}\n"],"mappings":"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"}
|
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
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createElement as h, useRef, useState, type ReactElement, type ChangeEvent } from 'react'
|
|
3
3
|
import type { Config, Inventory, Preset, PresetData, SettingsScope } from './types.ts'
|
|
4
4
|
import type { Translate } from './locales.ts'
|
|
5
|
+
import { addImportedPresets, applyPresetData, buildPresetData, mergeSections, removePreset, serializePreset } from './presets.ts'
|
|
5
6
|
import { s } from './styles.ts'
|
|
6
7
|
|
|
7
8
|
export function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {
|
|
@@ -9,65 +10,44 @@ export function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory
|
|
|
9
10
|
const [name, setName] = useState('')
|
|
10
11
|
const fileRef = useRef<HTMLInputElement | null>(null)
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
for (const sec of inv?.sections ?? []) set.add(sec.name)
|
|
16
|
-
for (const item of cfg.inject ?? []) set.add(item.name)
|
|
17
|
-
return set
|
|
18
|
-
})()
|
|
13
|
+
const blockedNames = new Set(cfg.sections ?? [])
|
|
14
|
+
const merged = mergeSections(inv, cfg, blockedNames)
|
|
15
|
+
const currentNames = new Set(merged.map((sec) => sec.name))
|
|
19
16
|
|
|
20
17
|
// Capture the current customization as a new preset (full snapshot). The
|
|
21
18
|
// absolute order is converted to a relative form (each section records the
|
|
22
19
|
// section it should follow) so the preset stays portable across prompts.
|
|
23
20
|
const saveCurrent = (): void => {
|
|
24
21
|
const presetName = name.trim() || `${t('preset')} ${presets.length + 1}`
|
|
25
|
-
const
|
|
26
|
-
const order = inject.map((sec, i) => ({
|
|
27
|
-
name: sec.name,
|
|
28
|
-
after: i > 0 ? inject[i - 1].name : undefined,
|
|
29
|
-
text: sec.text,
|
|
30
|
-
}))
|
|
31
|
-
const data: PresetData = {
|
|
32
|
-
sections: cfg.sections,
|
|
33
|
-
replace: cfg.replace,
|
|
34
|
-
order,
|
|
35
|
-
tools: cfg.tools,
|
|
36
|
-
}
|
|
22
|
+
const data = buildPresetData(cfg, merged)
|
|
37
23
|
scope.set('presets', [...presets, { id: genId(), name: presetName, data }])
|
|
38
24
|
setName('')
|
|
39
25
|
}
|
|
40
26
|
|
|
41
|
-
// Apply a preset. The
|
|
27
|
+
// Apply a preset. The patch is computed by the pure helper:
|
|
42
28
|
// - same-name sections are overridden (content + order)
|
|
43
29
|
// - preset sections missing from the current prompt are added
|
|
44
30
|
// - current sections NOT in the preset list are disabled (blocked)
|
|
45
|
-
// - the
|
|
31
|
+
// - only the preset's ACTIVE sections are unblocked (its own blocked list
|
|
32
|
+
// is preserved)
|
|
46
33
|
const applyPreset = (preset: Preset): void => {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const blocked = new Set(preset.data.sections ?? [])
|
|
53
|
-
for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name)
|
|
54
|
-
for (const name of presetNames) blocked.delete(name)
|
|
55
|
-
|
|
56
|
-
scope.set('inject', resolveOrder(presetOrder))
|
|
57
|
-
scope.set('replace', { ...(cfg.replace ?? {}), ...(preset.data.replace ?? {}) })
|
|
58
|
-
scope.set('sections', [...blocked])
|
|
59
|
-
scope.set('tools', preset.data.tools ?? { exclude: [], include: [] })
|
|
34
|
+
const patch = applyPresetData(preset.data, cfg, currentNames)
|
|
35
|
+
scope.set('inject', patch.inject)
|
|
36
|
+
scope.set('replace', patch.replace)
|
|
37
|
+
scope.set('sections', patch.sections)
|
|
38
|
+
scope.set('tools', patch.tools)
|
|
60
39
|
scope.set('activePreset', preset.id)
|
|
61
40
|
}
|
|
62
41
|
|
|
63
42
|
const deletePreset = (id: string): void => {
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
const next = removePreset(presets, id, cfg.activePreset)
|
|
44
|
+
scope.set('presets', next.presets)
|
|
45
|
+
if (next.activeId === undefined && cfg.activePreset === id) scope.unset('activePreset')
|
|
66
46
|
}
|
|
67
47
|
|
|
68
48
|
// Serialize a preset to a downloadable JSON file.
|
|
69
49
|
const exportPreset = (preset: Preset): void => {
|
|
70
|
-
const blob = new Blob([JSON.stringify(
|
|
50
|
+
const blob = new Blob([JSON.stringify(serializePreset(preset), null, 2)], { type: 'application/json' })
|
|
71
51
|
const url = URL.createObjectURL(blob)
|
|
72
52
|
const a = document.createElement('a')
|
|
73
53
|
a.href = url
|
|
@@ -85,12 +65,7 @@ export function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory
|
|
|
85
65
|
reader.onload = () => {
|
|
86
66
|
try {
|
|
87
67
|
const parsed = JSON.parse(String(reader.result))
|
|
88
|
-
|
|
89
|
-
const existing = new Set(presets.map((p) => p.name))
|
|
90
|
-
const added = incoming
|
|
91
|
-
.filter((p) => p && typeof p.name === 'string' && !existing.has(p.name))
|
|
92
|
-
.map((p) => ({ id: genId(), name: p.name, data: (p.data ?? {}) as PresetData }))
|
|
93
|
-
if (added.length > 0) scope.set('presets', [...presets, ...added])
|
|
68
|
+
scope.set('presets', addImportedPresets(presets, parsed, genId))
|
|
94
69
|
} catch { /* ignore invalid json */ }
|
|
95
70
|
}
|
|
96
71
|
reader.readAsText(file)
|
|
@@ -133,52 +108,3 @@ export function PresetsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory
|
|
|
133
108
|
function genId(): string {
|
|
134
109
|
return 'p_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7)
|
|
135
110
|
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Resolve a preset's relative order into an absolute ordered list (0..n-1).
|
|
139
|
-
* Sections with no anchor (or whose anchor is absent) come first in preset
|
|
140
|
-
* order; each anchored section is inserted right after its anchor; any
|
|
141
|
-
* remaining sections (cycles) are appended.
|
|
142
|
-
*/
|
|
143
|
-
function resolveOrder(presetOrder: Array<{ name: string; after?: string; text: string }>): Array<{ name: string; order: number; text: string }> {
|
|
144
|
-
const afterMap = new Map<string, string | undefined>()
|
|
145
|
-
const textMap = new Map<string, string>()
|
|
146
|
-
for (const sec of presetOrder) {
|
|
147
|
-
afterMap.set(sec.name, sec.after)
|
|
148
|
-
textMap.set(sec.name, sec.text)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const result: string[] = []
|
|
152
|
-
const placed = new Set<string>()
|
|
153
|
-
|
|
154
|
-
// Roots: no anchor, or anchor not part of the preset's own set.
|
|
155
|
-
for (const sec of presetOrder) {
|
|
156
|
-
const anchor = afterMap.get(sec.name)
|
|
157
|
-
if (!anchor || !afterMap.has(anchor)) {
|
|
158
|
-
result.push(sec.name)
|
|
159
|
-
placed.add(sec.name)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Chain resolution: place each section right after its (already placed) anchor.
|
|
164
|
-
let changed = true
|
|
165
|
-
while (changed) {
|
|
166
|
-
changed = false
|
|
167
|
-
for (const sec of presetOrder) {
|
|
168
|
-
if (placed.has(sec.name)) continue
|
|
169
|
-
const anchor = afterMap.get(sec.name)
|
|
170
|
-
if (anchor && placed.has(anchor)) {
|
|
171
|
-
result.splice(result.indexOf(anchor) + 1, 0, sec.name)
|
|
172
|
-
placed.add(sec.name)
|
|
173
|
-
changed = true
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Any remaining (cycle / unresolved) → append in preset order.
|
|
179
|
-
for (const sec of presetOrder) {
|
|
180
|
-
if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '' }))
|
|
184
|
-
}
|
|
@@ -2,16 +2,9 @@
|
|
|
2
2
|
import { createElement as h, useState, type ReactElement, type DragEvent, type ChangeEvent } from 'react'
|
|
3
3
|
import type { Config, Inventory, SettingsScope } from './types.ts'
|
|
4
4
|
import type { Translate } from './locales.ts'
|
|
5
|
+
import { mergeSections, type Section } from './presets.ts'
|
|
5
6
|
import { s } from './styles.ts'
|
|
6
7
|
|
|
7
|
-
interface Section {
|
|
8
|
-
name: string
|
|
9
|
-
order: number
|
|
10
|
-
text?: string
|
|
11
|
-
active: boolean
|
|
12
|
-
replaced: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
8
|
export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {
|
|
16
9
|
const replace = cfg.replace ?? {}
|
|
17
10
|
const blockedNames = new Set(cfg.sections ?? [])
|
|
@@ -23,22 +16,7 @@ export function SectionsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventor
|
|
|
23
16
|
// Merge inventory sections with user-injected sections, sorted by order.
|
|
24
17
|
// Injected sections that don't exist in the original prompt appear here too,
|
|
25
18
|
// positioned by their order value.
|
|
26
|
-
const merged: Section[] = (
|
|
27
|
-
const map = new Map<string, Section>()
|
|
28
|
-
for (const sec of inv?.sections ?? []) map.set(sec.name, sec)
|
|
29
|
-
for (const item of cfg.inject ?? []) {
|
|
30
|
-
const existing = map.get(item.name)
|
|
31
|
-
if (existing) map.set(item.name, { ...existing, order: item.order })
|
|
32
|
-
else map.set(item.name, {
|
|
33
|
-
name: item.name,
|
|
34
|
-
order: item.order,
|
|
35
|
-
text: item.text || '<动态生成>',
|
|
36
|
-
active: !blockedNames.has(item.name),
|
|
37
|
-
replaced: false,
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
return [...map.values()].sort((a, b) => a.order - b.order)
|
|
41
|
-
})()
|
|
19
|
+
const merged: Section[] = mergeSections(inv, cfg, blockedNames)
|
|
42
20
|
|
|
43
21
|
const toggleBlock = (name: string, currentlyBlocked: boolean): void => {
|
|
44
22
|
const list = (cfg.sections ?? []).slice()
|
package/src/client/ToolsTab.tsx
CHANGED
|
@@ -2,32 +2,19 @@
|
|
|
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'
|
|
5
6
|
import { s } from './styles.ts'
|
|
6
7
|
|
|
7
8
|
export function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory | null; scope: SettingsScope; t: Translate }): ReactElement {
|
|
8
9
|
const tools = inv?.tools ?? []
|
|
9
10
|
const toolsCfg = cfg.tools ?? { exclude: [], include: [] }
|
|
10
|
-
const
|
|
11
|
-
const include = toolsCfg.include ?? []
|
|
12
|
-
const includeMode = include.length > 0
|
|
11
|
+
const includeMode = (toolsCfg.include ?? []).length > 0
|
|
13
12
|
|
|
14
13
|
const toggle = (name: string, currentlyHidden: boolean): void => {
|
|
15
|
-
|
|
16
|
-
const next = include.slice()
|
|
17
|
-
if (currentlyHidden) next.push(name)
|
|
18
|
-
else { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }
|
|
19
|
-
scope.set('tools', { ...toolsCfg, include: next })
|
|
20
|
-
} else {
|
|
21
|
-
const next = exclude.slice()
|
|
22
|
-
if (currentlyHidden) { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }
|
|
23
|
-
else next.push(name)
|
|
24
|
-
scope.set('tools', { ...toolsCfg, exclude: next })
|
|
25
|
-
}
|
|
14
|
+
scope.set('tools', toggleTool(name, currentlyHidden, toolsCfg))
|
|
26
15
|
}
|
|
27
16
|
const setIncludeMode = (on: boolean): void => {
|
|
28
|
-
scope.set('tools', on
|
|
29
|
-
? { exclude: exclude.filter((n) => tools.some((tool) => tool.name === n)), include: tools.filter((tool) => !tool.hidden).map((tool) => tool.name) }
|
|
30
|
-
: { exclude: ensureOnlyKnown(exclude, tools), include: [] })
|
|
17
|
+
scope.set('tools', setToolMode(on, toolsCfg, tools.map((tool) => tool.name)))
|
|
31
18
|
}
|
|
32
19
|
|
|
33
20
|
return h('div', {}, [
|
|
@@ -40,7 +27,7 @@ export function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory |
|
|
|
40
27
|
tools.map((tool) => {
|
|
41
28
|
// Hidden state comes from the CONFIG (exclude/include lists), not the
|
|
42
29
|
// inventory snapshot, so toggling updates the checkbox immediately.
|
|
43
|
-
const hidden =
|
|
30
|
+
const hidden = isToolHidden(tool.name, toolsCfg)
|
|
44
31
|
return h('div', { key: tool.name, style: s.row }, [
|
|
45
32
|
h('label', { style: s.switchWrap }, [
|
|
46
33
|
h('input', { type: 'checkbox', checked: hidden, onChange: () => toggle(tool.name, hidden) }),
|
|
@@ -55,8 +42,3 @@ export function ToolsTab({ cfg, inv, scope, t }: { cfg: Config; inv: Inventory |
|
|
|
55
42
|
]),
|
|
56
43
|
])
|
|
57
44
|
}
|
|
58
|
-
|
|
59
|
-
function ensureOnlyKnown(list: string[], known: Array<{ name: string }>): string[] {
|
|
60
|
-
const names = known.map((tool) => tool.name)
|
|
61
|
-
return list.filter((n) => names.includes(n))
|
|
62
|
-
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/** Pure preset / section helpers shared by the client UI and the node tests. */
|
|
2
|
+
import type { Config, Inventory, Preset, PresetData } from './types.ts'
|
|
3
|
+
|
|
4
|
+
export interface Section {
|
|
5
|
+
name: string
|
|
6
|
+
order: number
|
|
7
|
+
text?: string
|
|
8
|
+
active: boolean
|
|
9
|
+
replaced: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The config fields a preset apply must write back to the settings scope. */
|
|
13
|
+
export interface ConfigPatch {
|
|
14
|
+
sections: string[]
|
|
15
|
+
replace: Record<string, string>
|
|
16
|
+
inject: Array<{ name: string; order: number; text: string }>
|
|
17
|
+
tools: { exclude: string[]; include: string[] }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Merge the inventory sections with the user's inject list, sorted by order. */
|
|
21
|
+
export function mergeSections(inv: Inventory | null, cfg: Config, blockedNames: ReadonlySet<string>): Section[] {
|
|
22
|
+
const map = new Map<string, Section>()
|
|
23
|
+
for (const sec of inv?.sections ?? []) map.set(sec.name, sec)
|
|
24
|
+
for (const item of cfg.inject ?? []) {
|
|
25
|
+
const existing = map.get(item.name)
|
|
26
|
+
if (existing) map.set(item.name, { ...existing, order: item.order })
|
|
27
|
+
else map.set(item.name, {
|
|
28
|
+
name: item.name,
|
|
29
|
+
order: item.order,
|
|
30
|
+
text: item.text || '<动态生成>',
|
|
31
|
+
active: !blockedNames.has(item.name),
|
|
32
|
+
replaced: false,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
return [...map.values()].sort((a, b) => a.order - b.order)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a preset's relative order into an absolute ordered list (0..n-1).
|
|
40
|
+
* Sections with no anchor (or whose anchor is absent) come first in preset
|
|
41
|
+
* order; each anchored section is inserted right after its anchor; any
|
|
42
|
+
* remaining sections (cycles) are appended.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveOrder(presetOrder: PresetData['order']): Array<{ name: string; order: number; text: string }> {
|
|
45
|
+
const list = presetOrder ?? []
|
|
46
|
+
const afterMap = new Map<string, string | undefined>()
|
|
47
|
+
const textMap = new Map<string, string>()
|
|
48
|
+
for (const sec of list) {
|
|
49
|
+
afterMap.set(sec.name, sec.after)
|
|
50
|
+
textMap.set(sec.name, sec.text)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result: string[] = []
|
|
54
|
+
const placed = new Set<string>()
|
|
55
|
+
|
|
56
|
+
for (const sec of list) {
|
|
57
|
+
const anchor = afterMap.get(sec.name)
|
|
58
|
+
if (!anchor || !afterMap.has(anchor)) { result.push(sec.name); placed.add(sec.name) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let changed = true
|
|
62
|
+
while (changed) {
|
|
63
|
+
changed = false
|
|
64
|
+
for (const sec of list) {
|
|
65
|
+
if (placed.has(sec.name)) continue
|
|
66
|
+
const anchor = afterMap.get(sec.name)
|
|
67
|
+
if (anchor && placed.has(anchor)) {
|
|
68
|
+
result.splice(result.indexOf(anchor) + 1, 0, sec.name)
|
|
69
|
+
placed.add(sec.name)
|
|
70
|
+
changed = true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const sec of list) {
|
|
76
|
+
if (!placed.has(sec.name)) { result.push(sec.name); placed.add(sec.name) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result.map((name, i) => ({ name, order: i, text: textMap.get(name) ?? '' }))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build a preset snapshot from the current config. The order list is derived
|
|
84
|
+
* from the MERGED section list (not just `cfg.inject`) so that a preset saved
|
|
85
|
+
* after only blocking sections (without reordering) still carries the full
|
|
86
|
+
* ordered set, and thus a meaningful "active list" when applied.
|
|
87
|
+
*/
|
|
88
|
+
export function buildPresetData(cfg: Config, merged: Section[]): PresetData {
|
|
89
|
+
const order = merged.map((sec, i) => ({
|
|
90
|
+
name: sec.name,
|
|
91
|
+
after: i > 0 ? merged[i - 1].name : undefined,
|
|
92
|
+
text: sec.text ?? '',
|
|
93
|
+
}))
|
|
94
|
+
return {
|
|
95
|
+
sections: cfg.sections,
|
|
96
|
+
replace: cfg.replace,
|
|
97
|
+
order,
|
|
98
|
+
tools: cfg.tools,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compute the config patch when applying a preset:
|
|
104
|
+
* - same-name sections are overridden (the order list drives this at runtime)
|
|
105
|
+
* - preset sections missing from the current prompt are added
|
|
106
|
+
* - current sections NOT in the preset's ordered list are disabled by default
|
|
107
|
+
* - only the preset's ACTIVE sections (in the order list but not in its block
|
|
108
|
+
* list) are unblocked; the preset's own blocked sections stay blocked.
|
|
109
|
+
*/
|
|
110
|
+
export function applyPresetData(data: PresetData, cfg: Config, currentNames: ReadonlySet<string>): ConfigPatch {
|
|
111
|
+
const presetOrder = data.order ?? []
|
|
112
|
+
const presetNames = new Set(presetOrder.map((x) => x.name))
|
|
113
|
+
const blocked = new Set(data.sections ?? [])
|
|
114
|
+
|
|
115
|
+
// Only enforce the "active set" when the preset actually defines an order.
|
|
116
|
+
// An empty order (e.g. a hand-authored / imported preset that only blocks a
|
|
117
|
+
// few sections) must not default-disable every current section.
|
|
118
|
+
if (presetOrder.length > 0) {
|
|
119
|
+
const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)))
|
|
120
|
+
for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name)
|
|
121
|
+
for (const name of activeNames) blocked.delete(name)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
sections: [...blocked],
|
|
126
|
+
replace: { ...(cfg.replace ?? {}), ...(data.replace ?? {}) },
|
|
127
|
+
inject: resolveOrder(presetOrder),
|
|
128
|
+
tools: { exclude: data.tools?.exclude ?? [], include: data.tools?.include ?? [] },
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Tool filtering ──────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
export type ToolsCfg = { exclude?: string[]; include?: string[] }
|
|
135
|
+
|
|
136
|
+
/** Whether a tool is hidden under the current include/exclude config. */
|
|
137
|
+
export function isToolHidden(name: string, toolsCfg: ToolsCfg | undefined): boolean {
|
|
138
|
+
const include = toolsCfg?.include ?? []
|
|
139
|
+
const exclude = toolsCfg?.exclude ?? []
|
|
140
|
+
return include.length > 0 ? !include.includes(name) : exclude.includes(name)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Toggle a tool's hidden state, returning a new tools config. */
|
|
144
|
+
export function toggleTool(name: string, currentlyHidden: boolean, toolsCfg: ToolsCfg | undefined): ToolsCfg {
|
|
145
|
+
const include = toolsCfg?.include ?? []
|
|
146
|
+
const exclude = toolsCfg?.exclude ?? []
|
|
147
|
+
if (include.length > 0) {
|
|
148
|
+
const next = include.slice()
|
|
149
|
+
if (currentlyHidden) next.push(name)
|
|
150
|
+
else { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }
|
|
151
|
+
return { exclude, include: next }
|
|
152
|
+
}
|
|
153
|
+
const next = exclude.slice()
|
|
154
|
+
if (currentlyHidden) { const i = next.indexOf(name); if (i >= 0) next.splice(i, 1) }
|
|
155
|
+
else next.push(name)
|
|
156
|
+
return { exclude: next, include }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Switch between include (whitelist) and exclude (blacklist) modes. Turning
|
|
161
|
+
* include mode on discards names unknown to the current tool set and seeds the
|
|
162
|
+
* include list from the currently non-excluded tools; turning it off clears the
|
|
163
|
+
* include list (keep only known exclude names).
|
|
164
|
+
*/
|
|
165
|
+
export function setToolMode(on: boolean, toolsCfg: ToolsCfg | undefined, toolNames: string[]): ToolsCfg {
|
|
166
|
+
const known = new Set(toolNames)
|
|
167
|
+
const exclude = (toolsCfg?.exclude ?? []).filter((n) => known.has(n))
|
|
168
|
+
if (on) return { exclude, include: toolNames.filter((n) => !exclude.includes(n)) }
|
|
169
|
+
return { exclude, include: [] }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Preset list operations ──────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/** Serialize a preset for export (JSON-safe full snapshot). */
|
|
175
|
+
export function serializePreset(preset: Preset): { name: string; data: PresetData } {
|
|
176
|
+
return { name: preset.name, data: preset.data }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Import preset(s): same-name presets are skipped, only distinct names are
|
|
181
|
+
* added. Returns a new presets list (or the original if nothing changed).
|
|
182
|
+
*/
|
|
183
|
+
export function addImportedPresets(existing: Preset[], parsed: unknown, makeId: () => string): Preset[] {
|
|
184
|
+
const incoming = Array.isArray(parsed) ? parsed : [parsed]
|
|
185
|
+
const existingNames = new Set(existing.map((p) => p.name))
|
|
186
|
+
const added = incoming
|
|
187
|
+
.filter((p) => p && typeof (p as { name?: unknown }).name === 'string' && !existingNames.has((p as Preset).name))
|
|
188
|
+
.map((p) => ({ id: makeId(), name: (p as Preset).name, data: ((p as Preset).data ?? {}) as PresetData }))
|
|
189
|
+
return added.length > 0 ? [...existing, ...added] : existing
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Delete a preset, clearing the active id if it pointed at the removed one. */
|
|
193
|
+
export function removePreset(presets: Preset[], id: string, activeId?: string): { presets: Preset[]; activeId?: string } {
|
|
194
|
+
return {
|
|
195
|
+
presets: presets.filter((p) => p.id !== id),
|
|
196
|
+
activeId: activeId === id ? undefined : activeId,
|
|
197
|
+
}
|
|
198
|
+
}
|