dsh-prompt-customizer 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +114 -0
- package/README.i18n.yaml +2 -0
- package/README.md +59 -52
- package/client/client.js +124 -106
- package/client/client.js.map +1 -1
- package/lib/index.js +13 -1
- package/lib/schema.js +21 -0
- package/package.json +3 -2
- package/src/client/PresetsTab.tsx +13 -83
- package/src/client/SectionsTab.tsx +2 -24
- package/src/client/index.ts +5 -11
- package/src/client/presets.ts +125 -0
- package/README.zh-CN.md +0 -107
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DreamsTOF
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# dsh-prompt-customizer
|
|
2
|
+
|
|
3
|
+
> **English** | [简体中文](README.md)
|
|
4
|
+
|
|
5
|
+
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) plugin that lets you control the **system prompt** and the **tool catalog** from a settings-panel UI.
|
|
6
|
+
|
|
7
|
+
Plugin-injected prompt sections (from other plugins) can pollute your system prompt. This plugin lets you **block**, **replace**, **inject**, and **reorder** prompt sections by name, and **hide tools** from the model catalog — all live, without touching the other plugins.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Prompt sections**
|
|
12
|
+
- **Block** a section by name (removed from the assembled system prompt).
|
|
13
|
+
- **Replace** a section's text (the original text is echoed for editing, including dynamically generated sections).
|
|
14
|
+
- **Inject** brand-new sections.
|
|
15
|
+
- **Reorder** sections with ↑/↓ arrows or **drag & drop** (HTML5). Order is stored as a virtual 0-based index, so there are never duplicate or fractional orders.
|
|
16
|
+
- **Tools**
|
|
17
|
+
- **Blacklist** (`exclude`): hide the listed tools.
|
|
18
|
+
- **Whitelist** (`include`): keep only the listed tools (wins over exclude).
|
|
19
|
+
- Only the model-facing catalog is affected — tools and routes keep working.
|
|
20
|
+
- **Presets**
|
|
21
|
+
- **Save** the current customization as a preset (full snapshot).
|
|
22
|
+
- **Apply** a preset: same-name sections are overridden, preset-only sections are added, and current sections outside the preset list are disabled by default.
|
|
23
|
+
- **Export / Import** presets as JSON (full snapshot).
|
|
24
|
+
- Presets store **relative order** (each section records the section it follows), so they stay portable across prompts with different section sets.
|
|
25
|
+
- Multiple presets can be stored locally; only one is active at a time.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Install from npm:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dsh plugin add dsh-prompt-customizer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install from a local checkout:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dsh plugin add /path/to/dsh-prompt-customizer
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After installation, open the dsh web UI → **Settings** → **提示词定制** (Prompt Customizer) in the sidebar.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Prompt sections tab
|
|
46
|
+
|
|
47
|
+
Each row shows a prompt section with:
|
|
48
|
+
|
|
49
|
+
- A **checkbox** to block/unblock it.
|
|
50
|
+
- A **replace** button to edit its text (the original text is pre-filled).
|
|
51
|
+
- **↑/↓ arrows** and a **drag handle** to reorder it.
|
|
52
|
+
- The `#N` badge shows the section's virtual order (0-based position).
|
|
53
|
+
|
|
54
|
+
The **注入新段** (Inject section) box at the bottom adds a brand-new section by name, order, and text.
|
|
55
|
+
|
|
56
|
+
### Tools tab
|
|
57
|
+
|
|
58
|
+
Toggle tools to hide them (blacklist), or switch to whitelist mode to keep only the checked tools.
|
|
59
|
+
|
|
60
|
+
### Presets tab
|
|
61
|
+
|
|
62
|
+
- **Save current as preset** — capture the current sections/tools customization.
|
|
63
|
+
- **Apply** — activate a preset (overrides same-name sections, adds preset-only sections, disables current sections outside the preset).
|
|
64
|
+
- **Export** — download the preset as JSON.
|
|
65
|
+
- **Import** — load a preset JSON file (same-name presets are skipped).
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
The plugin stores its config in the dsh settings document under the `prompt-customizer` namespace (`~/.dsh/settings.yaml`). You can edit it directly:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
prompt-customizer:
|
|
73
|
+
# Block these prompt sections by name.
|
|
74
|
+
sections:
|
|
75
|
+
- plugin:some-plugin
|
|
76
|
+
# Replace a section's text by name (original order kept).
|
|
77
|
+
replace:
|
|
78
|
+
system: 'You are a helpful coding assistant.'
|
|
79
|
+
# Inject / override sections (name + order + text).
|
|
80
|
+
inject:
|
|
81
|
+
- name: tool:read
|
|
82
|
+
order: 0
|
|
83
|
+
text: ''
|
|
84
|
+
# Tool catalog filtering.
|
|
85
|
+
tools:
|
|
86
|
+
exclude: []
|
|
87
|
+
include: []
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Field | Type | Description |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `sections` | `string[]` | Prompt section names to block. |
|
|
93
|
+
| `replace` | `Record<string, string>` | Replacement text per section name. |
|
|
94
|
+
| `inject` | `{name, order, text}[]` | Sections to inject or override (order controls splice position). |
|
|
95
|
+
| `tools.exclude` | `string[]` | Tool names to hide. |
|
|
96
|
+
| `tools.include` | `string[]` | When non-empty, keep only these tools (wins over `exclude`). |
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm install
|
|
102
|
+
npm run check # typecheck + build
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The browser half is built with [tsdown](https://github.com/rolldown/tsdown) into `client/client.js` (a `__ModuleLoader__` factory bundle). The host half lives in `lib/`.
|
|
106
|
+
|
|
107
|
+
## Known limitations
|
|
108
|
+
|
|
109
|
+
- **Replacing a dynamically generated section freezes its content**: sections like `app:web-surface` are generated live at assembly time (e.g. they embed the current Web server URL). After replacing, the text becomes fixed at the value you edited and no longer tracks runtime state such as the port. To keep following runtime changes, block the section instead of replacing it.
|
|
110
|
+
- **Dynamic text resolution is best-effort**: the panel tries to call a dynamic section's generator function (with a minimal context) to echo its real content. A few dynamic sections that depend on richer context and throw when called fall back to showing `<动态生成>`; the rest of the features are unaffected.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
[MIT](LICENSE) © 2026 DreamsTOF
|
package/README.i18n.yaml
ADDED
package/README.md
CHANGED
|
@@ -1,107 +1,114 @@
|
|
|
1
1
|
# dsh-prompt-customizer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> [English](README.en.md) | **简体中文**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) 插件,让你通过设置面板 UI 控制**系统提示词**和**工具目录**。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
其他插件注入的提示词段可能会污染你的系统提示词。本插件让你可以按名称**屏蔽**、**替换**、**注入**和**排序**提示词段,并从模型目录中**隐藏工具**——全部实时生效,无需改动其他插件。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **Block** a section by name (removed from the assembled system prompt).
|
|
11
|
-
- **Replace** a section's text (the original text is echoed for editing).
|
|
12
|
-
- **Inject** brand-new sections.
|
|
13
|
-
- **Reorder** sections with ↑/↓ arrows or **drag & drop** (HTML5). Order is stored as a virtual 0-based index, so there are never duplicate or fractional orders.
|
|
14
|
-
- **Tools**
|
|
15
|
-
- **Blacklist** (`exclude`): hide the listed tools.
|
|
16
|
-
- **Whitelist** (`include`): keep only the listed tools (wins over exclude).
|
|
17
|
-
- Only the model-facing catalog is affected — tools and routes keep working.
|
|
18
|
-
- **Presets**
|
|
19
|
-
- **Save** the current customization as a preset (full snapshot).
|
|
20
|
-
- **Apply** a preset: same-name sections are overridden, preset-only sections are added, and current sections outside the preset list are disabled by default.
|
|
21
|
-
- **Export / Import** presets as JSON (full snapshot).
|
|
22
|
-
- Presets store **relative order** (each section records the section it follows), so they stay portable across prompts with different section sets.
|
|
23
|
-
- Multiple presets can be stored locally; only one is active at a time.
|
|
9
|
+
## 功能特性
|
|
24
10
|
|
|
25
|
-
|
|
11
|
+
- **提示词段**
|
|
12
|
+
- **屏蔽**指定名称的段(从组装后的系统提示词中移除)。
|
|
13
|
+
- **替换**某段的文本(编辑时会回显原始文本,包括动态生成的段)。
|
|
14
|
+
- **注入**全新的段。
|
|
15
|
+
- 通过 **↑/↓ 箭头**或**拖拽**(HTML5)排序。顺序以虚拟的 0 起始下标存储,因此不会出现重复或小数 order。
|
|
16
|
+
- **工具**
|
|
17
|
+
- **黑名单**(`exclude`):隐藏列出的工具。
|
|
18
|
+
- **白名单**(`include`):仅保留列出的工具(优先于 exclude)。
|
|
19
|
+
- 只影响面向模型的目录,工具与路由仍正常工作。
|
|
20
|
+
- **预设**
|
|
21
|
+
- **保存**当前定制为预设(完整快照)。
|
|
22
|
+
- **应用**预设:同名段被覆盖、预设独有段被添加、预设名单之外的当前段默认被屏蔽。
|
|
23
|
+
- **导出 / 导入**预设为 JSON(完整快照)。
|
|
24
|
+
- 预设存储**相对顺序**(每段记录它跟在哪个段之后),因此可跨不同段集合的提示词移植。
|
|
25
|
+
- 本地可保存多个预设,同一时间只能激活一个。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
从 npm 安装:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
32
|
dsh plugin add dsh-prompt-customizer
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
或从本地目录安装:
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
38
|
dsh plugin add /path/to/dsh-prompt-customizer
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
安装后,打开 dsh Web UI → **设置** → 侧边栏中的 **提示词定制**。
|
|
40
42
|
|
|
41
|
-
##
|
|
43
|
+
## 使用说明
|
|
42
44
|
|
|
43
|
-
###
|
|
45
|
+
### 提示词 Tab
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
每一行代表一个提示词段,包含:
|
|
46
48
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
- **↑/↓
|
|
50
|
-
-
|
|
49
|
+
- **复选框**:屏蔽 / 取消屏蔽。
|
|
50
|
+
- **替换**按钮:编辑该段文本(默认回显原始文本)。
|
|
51
|
+
- **↑/↓ 箭头**和**拖拽手柄**:调整顺序。
|
|
52
|
+
- `#N` 徽标显示该段的虚拟顺序(0 起始的位置)。
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
底部的 **注入新段** 区域可按名称、顺序和文本添加全新段。
|
|
53
55
|
|
|
54
|
-
###
|
|
56
|
+
### 工具 Tab
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
勾选工具以隐藏(黑名单),或切换到白名单模式仅保留勾选的工具。
|
|
57
59
|
|
|
58
|
-
###
|
|
60
|
+
### 预设 Tab
|
|
59
61
|
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
62
|
+
- **保存当前为预设**:捕获当前的提示词 / 工具定制。
|
|
63
|
+
- **应用**:激活某个预设(覆盖同名段、添加预设独有段、屏蔽预设名单之外的当前段)。
|
|
64
|
+
- **导出**:将预设下载为 JSON。
|
|
65
|
+
- **导入**:加载预设 JSON 文件(同名预设会被跳过)。
|
|
64
66
|
|
|
65
|
-
##
|
|
67
|
+
## 配置
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
插件配置存储在 dsh 设置文档的 `prompt-customizer` 命名空间下(`~/.dsh/settings.yaml`)。你也可以直接编辑:
|
|
68
70
|
|
|
69
71
|
```yaml
|
|
70
72
|
prompt-customizer:
|
|
71
|
-
#
|
|
73
|
+
# 按名称屏蔽这些提示词段。
|
|
72
74
|
sections:
|
|
73
75
|
- plugin:some-plugin
|
|
74
|
-
#
|
|
76
|
+
# 按名称替换某段文本(保留原始顺序)。
|
|
75
77
|
replace:
|
|
76
78
|
system: 'You are a helpful coding assistant.'
|
|
77
|
-
#
|
|
79
|
+
# 注入 / 覆盖段(名称 + 顺序 + 文本)。
|
|
78
80
|
inject:
|
|
79
81
|
- name: tool:read
|
|
80
82
|
order: 0
|
|
81
83
|
text: ''
|
|
82
|
-
#
|
|
84
|
+
# 工具目录过滤。
|
|
83
85
|
tools:
|
|
84
86
|
exclude: []
|
|
85
87
|
include: []
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
|
|
|
90
|
+
| 字段 | 类型 | 说明 |
|
|
89
91
|
| --- | --- | --- |
|
|
90
|
-
| `sections` | `string[]` |
|
|
91
|
-
| `replace` | `Record<string, string>` |
|
|
92
|
-
| `inject` | `{name, order, text}[]` |
|
|
93
|
-
| `tools.exclude` | `string[]` |
|
|
94
|
-
| `tools.include` | `string[]` |
|
|
92
|
+
| `sections` | `string[]` | 要屏蔽的提示词段名称。 |
|
|
93
|
+
| `replace` | `Record<string, string>` | 每个段名称对应的替换文本。 |
|
|
94
|
+
| `inject` | `{name, order, text}[]` | 要注入或覆盖的段(order 控制拼接位置)。 |
|
|
95
|
+
| `tools.exclude` | `string[]` | 要隐藏的工具名称。 |
|
|
96
|
+
| `tools.include` | `string[]` | 非空时仅保留这些工具(优先于 `exclude`)。 |
|
|
95
97
|
|
|
96
|
-
##
|
|
98
|
+
## 开发
|
|
97
99
|
|
|
98
100
|
```bash
|
|
99
101
|
npm install
|
|
100
102
|
npm run check # typecheck + build
|
|
101
103
|
```
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
浏览器端使用 [tsdown](https://github.com/rolldown/tsdown) 打包为 `client/client.js`(`__ModuleLoader__` factory bundle)。宿主端代码位于 `lib/`。
|
|
106
|
+
|
|
107
|
+
## 已知限制
|
|
108
|
+
|
|
109
|
+
- **替换动态生成的段会固定其生成内容**:像 `app:web-surface` 这类段在装配时实时生成(如嵌入当前 Web 服务器 URL)。替换后,文本会固定为您编辑时的值,不再随端口等运行时状态自动更新;如需跟随运行时变化,请勿替换该段,改用屏蔽。
|
|
110
|
+
- **动态文本解析是尽力而为**:面板读取动态段时会尝试调用其生成函数并传入最小上下文,以便回显真实内容。个别依赖更复杂上下文、调用时会抛错的动态段会回退显示 `<动态生成>`,不影响其余功能。
|
|
104
111
|
|
|
105
112
|
## License
|
|
106
113
|
|
|
107
|
-
MIT
|
|
114
|
+
[MIT](LICENSE) © 2026 DreamsTOF
|
package/client/client.js
CHANGED
|
@@ -281,6 +281,121 @@ 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
|
+
const activeNames = new Set([...presetNames].filter((n) => !blocked.has(n)));
|
|
383
|
+
for (const name of currentNames) if (!presetNames.has(name)) blocked.add(name);
|
|
384
|
+
for (const name of activeNames) blocked.delete(name);
|
|
385
|
+
return {
|
|
386
|
+
sections: [...blocked],
|
|
387
|
+
replace: {
|
|
388
|
+
...cfg.replace ?? {},
|
|
389
|
+
...data.replace ?? {}
|
|
390
|
+
},
|
|
391
|
+
inject: resolveOrder(presetOrder),
|
|
392
|
+
tools: {
|
|
393
|
+
exclude: data.tools?.exclude ?? [],
|
|
394
|
+
include: data.tools?.include ?? []
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
//#endregion
|
|
284
399
|
//#region src/client/SectionsTab.tsx
|
|
285
400
|
/** Prompt-sections tab: block / replace / inject / reorder prompt sections. */
|
|
286
401
|
function SectionsTab({ cfg, inv, scope, t }) {
|
|
@@ -290,25 +405,7 @@ window.__ModuleLoader__.load({
|
|
|
290
405
|
const [draft, setDraft] = (0, react.useState)("");
|
|
291
406
|
const [dragName, setDragName] = (0, react.useState)(null);
|
|
292
407
|
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
|
-
})();
|
|
408
|
+
const merged = mergeSections(inv, cfg, blockedNames);
|
|
312
409
|
const toggleBlock = (name, currentlyBlocked) => {
|
|
313
410
|
const list = (cfg.sections ?? []).slice();
|
|
314
411
|
if (currentlyBlocked) {
|
|
@@ -603,26 +700,11 @@ window.__ModuleLoader__.load({
|
|
|
603
700
|
const presets = cfg.presets ?? [];
|
|
604
701
|
const [name, setName] = (0, react.useState)("");
|
|
605
702
|
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
|
-
})();
|
|
703
|
+
const merged = mergeSections(inv, cfg, new Set(cfg.sections ?? []));
|
|
704
|
+
const currentNames = new Set(merged.map((sec) => sec.name));
|
|
612
705
|
const saveCurrent = () => {
|
|
613
706
|
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
|
-
};
|
|
707
|
+
const data = buildPresetData(cfg, merged);
|
|
626
708
|
scope.set("presets", [...presets, {
|
|
627
709
|
id: genId(),
|
|
628
710
|
name: presetName,
|
|
@@ -631,21 +713,11 @@ window.__ModuleLoader__.load({
|
|
|
631
713
|
setName("");
|
|
632
714
|
};
|
|
633
715
|
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
|
-
});
|
|
716
|
+
const patch = applyPresetData(preset.data, cfg, currentNames);
|
|
717
|
+
scope.set("inject", patch.inject);
|
|
718
|
+
scope.set("replace", patch.replace);
|
|
719
|
+
scope.set("sections", patch.sections);
|
|
720
|
+
scope.set("tools", patch.tools);
|
|
649
721
|
scope.set("activePreset", preset.id);
|
|
650
722
|
};
|
|
651
723
|
const deletePreset = (id) => {
|
|
@@ -734,51 +806,6 @@ window.__ModuleLoader__.load({
|
|
|
734
806
|
function genId() {
|
|
735
807
|
return "p_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
736
808
|
}
|
|
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
809
|
//#endregion
|
|
783
810
|
//#region src/client/index.ts
|
|
784
811
|
/**
|
|
@@ -813,15 +840,6 @@ window.__ModuleLoader__.load({
|
|
|
813
840
|
scope,
|
|
814
841
|
t
|
|
815
842
|
})));
|
|
816
|
-
scoped.slots.inject("settings.plugin.item", () => scoped.slots.register({
|
|
817
|
-
name: "settings.plugin.item",
|
|
818
|
-
key: NS,
|
|
819
|
-
locale: NS,
|
|
820
|
-
inject: () => ({ t })
|
|
821
|
-
}, () => (0, react.createElement)(Panel, {
|
|
822
|
-
scope,
|
|
823
|
-
t
|
|
824
|
-
})));
|
|
825
843
|
});
|
|
826
844
|
}
|
|
827
845
|
function Panel({ scope, t }) {
|