dsh-system-prompt-insertion 0.0.1 → 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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +174 -2
- package/cordis.patch.yml +18 -0
- package/lib/client.js +1414 -0
- package/lib/index.d.ts +324 -0
- package/lib/index.js +902 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +100 -4
package/lib/client.js
ADDED
|
@@ -0,0 +1,1414 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-system-prompt-insertion",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
// src/client.ts
|
|
8
|
+
/**
|
|
9
|
+
* `dsh-system-prompt-insertion` — browser half.
|
|
10
|
+
*
|
|
11
|
+
* ## What this module is
|
|
12
|
+
*
|
|
13
|
+
* A sidebar panel for the user-owned text the host half registers as sections —
|
|
14
|
+
* not a settings page. The shell's own panel family works through two seats that
|
|
15
|
+
* this module fills together:
|
|
16
|
+
*
|
|
17
|
+
* - `sidebar.panellist` gives us a row in the left column (the rail the Plugins,
|
|
18
|
+
* Schedules, Market and Context entries live in). The shell owns the button,
|
|
19
|
+
* the label, the tooltip, the active highlight and the collapsed rail; all we
|
|
20
|
+
* draw is the glyph, at the size and selection state it asks for.
|
|
21
|
+
* - `main` renders the panel body once the row with the same id is selected.
|
|
22
|
+
* Reserving `conversation` is not our business: any other key receives no
|
|
23
|
+
* Session binding, so this page works with no session open.
|
|
24
|
+
*
|
|
25
|
+
* Both registrations go through `ctx.slots.inject`, which fires only after the
|
|
26
|
+
* owning shell entry declares the seat, so load order between this package and
|
|
27
|
+
* the shell does not matter.
|
|
28
|
+
*
|
|
29
|
+
* It is loaded as a lazy-CJS factory (`window.__ModuleLoader__.load`) whose id
|
|
30
|
+
* is the package name; `react` comes from the page's module table, and NOTHING
|
|
31
|
+
* else is imported — no harness client package, no component kit. The panel
|
|
32
|
+
* therefore cannot break when an internal client API moves, and it is
|
|
33
|
+
* theme-consistent through `--dsw-alias-*` tokens rather than by copying host
|
|
34
|
+
* components.
|
|
35
|
+
*
|
|
36
|
+
* ## Shape of the page
|
|
37
|
+
*
|
|
38
|
+
* One column, top to bottom: heading, preview, section editor. The preview
|
|
39
|
+
* comes first because it is the outcome the editor exists to shape. Save,
|
|
40
|
+
* discard and Add section live at the end of the editor rather than in a pinned
|
|
41
|
+
* footer, so the whole page scrolls as one document. The preview sits above
|
|
42
|
+
* instead of beside the editor: at a 960px content width two columns forced the
|
|
43
|
+
* prompt text into a third of the window, and the list of assembled sections
|
|
44
|
+
* was clipped. A single column keeps every control at the width the shell gives
|
|
45
|
+
* its own pages.
|
|
46
|
+
*
|
|
47
|
+
* The panel borrows the shell's visual grammar deliberately: a 960px content
|
|
48
|
+
* column behind responsive gutters, 20px/500 page title, 0.5px hairline
|
|
49
|
+
* borders, pill-shaped primary actions, the theme's switch geometry (36×20),
|
|
50
|
+
* focus rings from `--dsw-focus-ring-*`, and hover fills from
|
|
51
|
+
* `--dsw-alias-interactive-*`. Nothing hard-codes a colour: the stylesheet
|
|
52
|
+
* below only reads theme tokens, so light, dark and skin themes all follow.
|
|
53
|
+
*
|
|
54
|
+
* ## Data flow
|
|
55
|
+
*
|
|
56
|
+
* The panel reads and writes the same JSON API the host half serves
|
|
57
|
+
* (`/dsh-system-prompt-insertion/state`, `/preview`). Edits stage locally, so a
|
|
58
|
+
* half-typed rule never reaches a running session; Save commits the whole
|
|
59
|
+
* document at once. The master switch is the exception: it applies immediately,
|
|
60
|
+
* because the preview must agree with the control. Preview calls the host's
|
|
61
|
+
* real `assemble()`+`renderPrompt()`, which is why it can honestly list the
|
|
62
|
+
* host's own sections and flag our text as hidden under a complete-prompt
|
|
63
|
+
* preset. Entry, Save and switch changes refresh it automatically; the refresh
|
|
64
|
+
* control beside its heading covers prompt changes made by other plugins.
|
|
65
|
+
*
|
|
66
|
+
* @module dsh-system-prompt-insertion/client
|
|
67
|
+
*/
|
|
68
|
+
/** Locale namespace owned by this panel. */
|
|
69
|
+
const NS = "dsh-system-prompt-insertion";
|
|
70
|
+
/** URL prefix served by the host half (kept in sync with `routes.ts`). */
|
|
71
|
+
const API = "/dsh-system-prompt-insertion";
|
|
72
|
+
/**
|
|
73
|
+
* The one panel id shared by the sidebar row and the `main` occupant.
|
|
74
|
+
*
|
|
75
|
+
* The shell projects the row's label and dispatches `main` by this exact key, so
|
|
76
|
+
* both registrations read it instead of repeating a literal.
|
|
77
|
+
*/
|
|
78
|
+
const PANEL_ID = "system-prompt-insertion";
|
|
79
|
+
/**
|
|
80
|
+
* Position among the shell's global panel rows.
|
|
81
|
+
*
|
|
82
|
+
* Shipped rows sit at 0 (Plugins), 10 (Schedules) and 20 (Context); 30 keeps
|
|
83
|
+
* this entry after them without claiming a shipped slot.
|
|
84
|
+
*/
|
|
85
|
+
const PANEL_ORDER = 30;
|
|
86
|
+
/** Placement bands, mirroring the host's `POSITIONS`. */
|
|
87
|
+
const POSITIONS = [
|
|
88
|
+
"head",
|
|
89
|
+
"body",
|
|
90
|
+
"tail"
|
|
91
|
+
];
|
|
92
|
+
/** Copy, in the two languages the harness ships. */
|
|
93
|
+
const zh = {
|
|
94
|
+
nav: "提示词注入",
|
|
95
|
+
title: "提示词注入",
|
|
96
|
+
description: "把你自己写的文字放到系统提示词的开头、中间或末尾。保存后下一个请求生效,不用重启。",
|
|
97
|
+
enabled: "启用",
|
|
98
|
+
enabledHint: "关闭后所有段落都不再进入系统提示词,不必逐段停用;切换会立即保存。",
|
|
99
|
+
add: "添加段落",
|
|
100
|
+
addFirst: "添加第一个段落",
|
|
101
|
+
save: "保存",
|
|
102
|
+
saving: "保存中…",
|
|
103
|
+
saveHint: "保存到状态文件;也可以按 {keys}",
|
|
104
|
+
revert: "撤销修改",
|
|
105
|
+
refreshPreview: "刷新预览",
|
|
106
|
+
previewing: "组装中…",
|
|
107
|
+
statusSaved: "已保存",
|
|
108
|
+
statusDirty: "有未保存的修改",
|
|
109
|
+
pathLabel: "状态文件",
|
|
110
|
+
originFile: "来自文件",
|
|
111
|
+
originMissing: "文件尚未创建",
|
|
112
|
+
originMemory: "刚保存",
|
|
113
|
+
originUnreadable: "文件无法读取",
|
|
114
|
+
tailBase: "宿主末段 {order}",
|
|
115
|
+
tailBaseHint: "宿主最后一个段落的 order;本插件末尾段从它 +300 开始,预览里能看到每段的最终 order。",
|
|
116
|
+
section: "段落",
|
|
117
|
+
sectionsTitle: "段落",
|
|
118
|
+
sectionsSummary: "{count} 段 · 已注册 {registered}",
|
|
119
|
+
sectionTitle: "名称",
|
|
120
|
+
sectionTitleOf: "第 {index} 段名称",
|
|
121
|
+
sectionTextOf: "第 {index} 段正文",
|
|
122
|
+
positionOf: "第 {index} 段位置",
|
|
123
|
+
moveUpNamed: "上移「{title}」",
|
|
124
|
+
moveDownNamed: "下移「{title}」",
|
|
125
|
+
textPlaceholder: "例如:回答前先复述我的目标;不确定的事实先检索再回答;代码注释保持简短。",
|
|
126
|
+
positionHead: "开头",
|
|
127
|
+
positionBody: "中间",
|
|
128
|
+
positionTail: "末尾",
|
|
129
|
+
positionHintHead: "紧跟人格设定,排在计划策略与工具说明之前。",
|
|
130
|
+
positionHintBody: "排在所有工具说明之后、交付物引用之前。",
|
|
131
|
+
positionHintTail: "排在宿主最后一段之后,是提示词真正的结尾。",
|
|
132
|
+
interpolate: "解析 {{变量}}",
|
|
133
|
+
interpolateHint: "关闭时正文原样进入提示词;开启后可引用 {{model}} / {{provider}},未知变量会让请求失败。",
|
|
134
|
+
removeNamed: "删除「{title}」",
|
|
135
|
+
toggleNamed: "启停「{title}」",
|
|
136
|
+
bytes: "{bytes} 字节",
|
|
137
|
+
bytesShort: "字节",
|
|
138
|
+
orderShort: "order",
|
|
139
|
+
emptyListTitle: "还没有段落",
|
|
140
|
+
emptyListHint: "写下的文字只进入你自己的系统提示词,保存后从下一个请求开始生效。",
|
|
141
|
+
emptyChip: "正文为空",
|
|
142
|
+
disabledChip: "已停用",
|
|
143
|
+
previewTitle: "当前系统提示词",
|
|
144
|
+
previewSummary: "共 {count} 段,{bytes} 字节",
|
|
145
|
+
previewStale: "预览是已保存的版本;保存后会自动刷新。",
|
|
146
|
+
previewNone: "还没有预览。点标题旁的刷新按钮,看看下一次请求真正发出去的完整提示词。",
|
|
147
|
+
warnings: "检查结果",
|
|
148
|
+
noWarnings: "没有发现问题。",
|
|
149
|
+
ours: "本插件",
|
|
150
|
+
variables: "可用变量",
|
|
151
|
+
loading: "加载中…",
|
|
152
|
+
loadFailed: "读取失败",
|
|
153
|
+
retry: "重试",
|
|
154
|
+
saveFailed: "保存失败",
|
|
155
|
+
previewFailed: "预览失败",
|
|
156
|
+
dismiss: "关闭",
|
|
157
|
+
copy: "复制全文",
|
|
158
|
+
copied: "已复制",
|
|
159
|
+
copyFailed: "复制失败",
|
|
160
|
+
copyFailedHint: "浏览器拒绝了剪贴板访问,请手动选择全文。",
|
|
161
|
+
warnEmpty: "「{title}」已启用但正文为空,不会被注册。",
|
|
162
|
+
warnOversize: "「{title}」{bytes} 字节,超过软上限 {cap} 字节。",
|
|
163
|
+
warnDuplicate: "「{title}」有一行与「{other}」重复。",
|
|
164
|
+
warnUnknownVariable: "「{title}」引用了 {{name}},当前组装没有可用值(已知:{known})。",
|
|
165
|
+
warnHidden: "有段落已注册但不在当前组装里——当前预设可能用 complete: true 接管了整份提示词。",
|
|
166
|
+
warnStoreError: "状态文件读取失败,仍在使用上一份内容:{message}",
|
|
167
|
+
warnRenderError: "渲染失败:{message}",
|
|
168
|
+
warnPreviewPartial: "预览没有带上 agent scope,{names} 这类按 agent 取值的变量会按原样显示。"
|
|
169
|
+
};
|
|
170
|
+
const en = {
|
|
171
|
+
nav: "Prompt insertion",
|
|
172
|
+
title: "Prompt insertion",
|
|
173
|
+
description: "Place your own text at the head, middle, or tail of the system prompt. Saved text applies on the next request, with no restart.",
|
|
174
|
+
enabled: "Enabled",
|
|
175
|
+
enabledHint: "Turning this off keeps every section out of the prompt without disabling them one by one. The switch is saved immediately.",
|
|
176
|
+
add: "Add section",
|
|
177
|
+
addFirst: "Add the first section",
|
|
178
|
+
save: "Save",
|
|
179
|
+
saving: "Saving…",
|
|
180
|
+
saveHint: "Save to the state file; {keys} works too",
|
|
181
|
+
revert: "Discard changes",
|
|
182
|
+
refreshPreview: "Refresh preview",
|
|
183
|
+
previewing: "Assembling…",
|
|
184
|
+
statusSaved: "Saved",
|
|
185
|
+
statusDirty: "Unsaved changes",
|
|
186
|
+
pathLabel: "State file",
|
|
187
|
+
originFile: "from file",
|
|
188
|
+
originMissing: "file not created yet",
|
|
189
|
+
originMemory: "just saved",
|
|
190
|
+
originUnreadable: "file unreadable",
|
|
191
|
+
tailBase: "Host tail {order}",
|
|
192
|
+
tailBaseHint: "The order of the host's last section; our tail sections start 300 after it. The preview shows each final order.",
|
|
193
|
+
section: "Section",
|
|
194
|
+
sectionsTitle: "Sections",
|
|
195
|
+
sectionsSummary: "{count} total · {registered} registered",
|
|
196
|
+
sectionTitle: "Name",
|
|
197
|
+
sectionTitleOf: "Name of section {index}",
|
|
198
|
+
sectionTextOf: "Body of section {index}",
|
|
199
|
+
positionOf: "Placement of section {index}",
|
|
200
|
+
moveUpNamed: "Move \"{title}\" up",
|
|
201
|
+
moveDownNamed: "Move \"{title}\" down",
|
|
202
|
+
textPlaceholder: "For example: restate my goal before answering; verify uncertain facts before asserting them; keep code comments short.",
|
|
203
|
+
positionHead: "Head",
|
|
204
|
+
positionBody: "Middle",
|
|
205
|
+
positionTail: "Tail",
|
|
206
|
+
positionHintHead: "Right after the persona, before plan policy and tool guidance.",
|
|
207
|
+
positionHintBody: "After every tool section, before deliverable references.",
|
|
208
|
+
positionHintTail: "After the host's own last section (persona suffix) — the true end of the prompt.",
|
|
209
|
+
interpolate: "Resolve {{variables}}",
|
|
210
|
+
interpolateHint: "Off keeps the body literal; on lets you use {{model}} / {{provider}}, and an unknown name fails the request.",
|
|
211
|
+
removeNamed: "Remove \"{title}\"",
|
|
212
|
+
toggleNamed: "Toggle \"{title}\"",
|
|
213
|
+
bytes: "{bytes} bytes",
|
|
214
|
+
bytesShort: "Bytes",
|
|
215
|
+
orderShort: "order",
|
|
216
|
+
emptyListTitle: "No sections yet",
|
|
217
|
+
emptyListHint: "Text you write here enters your own system prompt only, starting with the first request after you save.",
|
|
218
|
+
emptyChip: "Empty body",
|
|
219
|
+
disabledChip: "Off",
|
|
220
|
+
previewTitle: "The current system prompt",
|
|
221
|
+
previewSummary: "{count} sections, {bytes} bytes",
|
|
222
|
+
previewStale: "The preview shows what is SAVED; it refreshes after you save.",
|
|
223
|
+
previewNone: "Nothing previewed yet. Use the refresh button next to the title to see the full prompt the next request sends.",
|
|
224
|
+
warnings: "Checks",
|
|
225
|
+
noWarnings: "Nothing looks wrong.",
|
|
226
|
+
ours: "this plugin",
|
|
227
|
+
variables: "Variables",
|
|
228
|
+
loading: "Loading…",
|
|
229
|
+
loadFailed: "Could not load",
|
|
230
|
+
retry: "Retry",
|
|
231
|
+
saveFailed: "Could not save",
|
|
232
|
+
previewFailed: "Could not preview",
|
|
233
|
+
dismiss: "Dismiss",
|
|
234
|
+
copy: "Copy all",
|
|
235
|
+
copied: "Copied",
|
|
236
|
+
copyFailed: "Copy failed",
|
|
237
|
+
copyFailedHint: "The browser refused clipboard access; select the text and copy it manually.",
|
|
238
|
+
warnEmpty: "\"{title}\" is enabled but empty, so it is not registered.",
|
|
239
|
+
warnOversize: "\"{title}\" is {bytes} bytes, above the {cap}-byte soft cap.",
|
|
240
|
+
warnDuplicate: "\"{title}\" repeats a line already present in \"{other}\".",
|
|
241
|
+
warnUnknownVariable: "\"{title}\" references {{name}}, which has no value in this assembly (known: {known}).",
|
|
242
|
+
warnHidden: "A section is registered but absent from the current assembly — the active preset may be taking over the whole prompt with complete: true.",
|
|
243
|
+
warnStoreError: "The state file could not be read; the previous content is still live: {message}",
|
|
244
|
+
warnRenderError: "Rendering failed: {message}",
|
|
245
|
+
warnPreviewPartial: "Previewed without an agent scope, so per-agent variables such as {names} stayed literal."
|
|
246
|
+
};
|
|
247
|
+
/** Localize one preview warning, falling back to the host's English message. */
|
|
248
|
+
function warningText(t, warning) {
|
|
249
|
+
const key = {
|
|
250
|
+
empty: "warnEmpty",
|
|
251
|
+
oversize: "warnOversize",
|
|
252
|
+
duplicate: "warnDuplicate",
|
|
253
|
+
"unknown-variable": "warnUnknownVariable",
|
|
254
|
+
hidden: "warnHidden",
|
|
255
|
+
"store-error": "warnStoreError",
|
|
256
|
+
"render-error": "warnRenderError",
|
|
257
|
+
"preview-partial": "warnPreviewPartial"
|
|
258
|
+
}[warning.code];
|
|
259
|
+
if (key === void 0) return warning.message;
|
|
260
|
+
const template = t(key);
|
|
261
|
+
if (template === key) return warning.message;
|
|
262
|
+
return format(template, {
|
|
263
|
+
...warning.params ?? {},
|
|
264
|
+
message: warning.message
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** Fill `{name}` placeholders in a locale string. */
|
|
268
|
+
function format(template, values) {
|
|
269
|
+
return template.replace(/\{([a-z][a-z0-9]*)\}/gi, (match, key) => String(values[key] ?? match));
|
|
270
|
+
}
|
|
271
|
+
/** Monospace stack for prompt text, following the shell's configured code font. */
|
|
272
|
+
const MONO = "var(--dsh-font-family-code, \"SF Mono\", \"JetBrains Mono\", Menlo, Consolas, monospace)";
|
|
273
|
+
/** UTF-8 size, matching how the host counts section bytes. */
|
|
274
|
+
function byteLength(text) {
|
|
275
|
+
return new TextEncoder().encode(text).length;
|
|
276
|
+
}
|
|
277
|
+
/** The hint for one placement band. */
|
|
278
|
+
function placementHint(t, position) {
|
|
279
|
+
return position === "head" ? t("positionHintHead") : position === "body" ? t("positionHintBody") : t("positionHintTail");
|
|
280
|
+
}
|
|
281
|
+
/** Human-facing state of the backing file. */
|
|
282
|
+
function originLabel(t, origin) {
|
|
283
|
+
if (origin === "file") return t("originFile");
|
|
284
|
+
if (origin === "memory") return t("originMemory");
|
|
285
|
+
if (origin === "unreadable") return t("originUnreadable");
|
|
286
|
+
return t("originMissing");
|
|
287
|
+
}
|
|
288
|
+
/** Localize one API error envelope. */
|
|
289
|
+
function errorText(body, fallback) {
|
|
290
|
+
const field = body.error;
|
|
291
|
+
if (typeof field === "string" && field !== "") return `${fallback}: ${field}`;
|
|
292
|
+
if (field !== null && typeof field === "object") {
|
|
293
|
+
const detail = field;
|
|
294
|
+
const text = detail.message ?? detail.code;
|
|
295
|
+
if (text !== void 0) return `${fallback}: ${String(text)}`;
|
|
296
|
+
}
|
|
297
|
+
return fallback;
|
|
298
|
+
}
|
|
299
|
+
/** Build the panel payload from one `/state` response body. */
|
|
300
|
+
function payloadFrom(body, fallbackOrigin) {
|
|
301
|
+
return {
|
|
302
|
+
state: body.state,
|
|
303
|
+
path: String(body.path ?? ""),
|
|
304
|
+
origin: body.origin ?? fallbackOrigin,
|
|
305
|
+
...typeof body.error === "string" ? { error: body.error } : {},
|
|
306
|
+
orderBases: body.orderBases ?? { tailBase: 10200 },
|
|
307
|
+
registered: body.registered ?? []
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** Remove a leading localized prefix so a titled card does not repeat itself. */
|
|
311
|
+
function withoutPrefix(text, prefix) {
|
|
312
|
+
if (text === void 0) return void 0;
|
|
313
|
+
const lead = `${prefix}: `;
|
|
314
|
+
return text.startsWith(lead) ? text.slice(lead.length) : text;
|
|
315
|
+
}
|
|
316
|
+
/** Read a JSON response, tolerating an HTML error page. */
|
|
317
|
+
async function readJson(response) {
|
|
318
|
+
const text = await response.text();
|
|
319
|
+
try {
|
|
320
|
+
return JSON.parse(text);
|
|
321
|
+
} catch {
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
error: {
|
|
325
|
+
code: `http-${response.status}`,
|
|
326
|
+
message: text.slice(0, 300)
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/** Make a fresh section id that satisfies the host's `SECTION_ID` pattern. */
|
|
332
|
+
function newId() {
|
|
333
|
+
return `s-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* The panel stylesheet.
|
|
337
|
+
*
|
|
338
|
+
* One scoped block instead of dozens of inline style objects: hover, focus,
|
|
339
|
+
* `:disabled` and media-query states cannot be expressed inline, and every
|
|
340
|
+
* global panel is judged against the shell's own pages. Every colour comes from
|
|
341
|
+
* a `--dsw-*` token (the accompanying test refuses literals), so a theme or a
|
|
342
|
+
* skin changes the panel without touching it.
|
|
343
|
+
*/
|
|
344
|
+
const PANEL_CSS = `
|
|
345
|
+
.dspi-root {
|
|
346
|
+
--dspi-gutter: clamp(24px, 4vw, 48px);
|
|
347
|
+
--dspi-content: 960px;
|
|
348
|
+
box-sizing: border-box;
|
|
349
|
+
display: flex;
|
|
350
|
+
flex-direction: column;
|
|
351
|
+
width: 100%;
|
|
352
|
+
min-width: 0;
|
|
353
|
+
height: 100%;
|
|
354
|
+
min-height: 0;
|
|
355
|
+
overflow: hidden;
|
|
356
|
+
background: var(--dsw-alias-bg-base);
|
|
357
|
+
color: var(--dsw-alias-label-primary);
|
|
358
|
+
font-size: 14px;
|
|
359
|
+
line-height: 1.6;
|
|
360
|
+
}
|
|
361
|
+
.dspi-root *, .dspi-root *:before, .dspi-root *:after { box-sizing: border-box; }
|
|
362
|
+
.dspi-root button, .dspi-root input, .dspi-root select, .dspi-root textarea { font-family: inherit; }
|
|
363
|
+
.dspi-root button, .dspi-root input, .dspi-root select, .dspi-root textarea, .dspi-root pre { -webkit-app-region: no-drag; }
|
|
364
|
+
.dspi-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; scrollbar-gutter: stable; }
|
|
365
|
+
.dspi-content { width: 100%; max-width: var(--dspi-content); margin: 0 auto; padding: 0 var(--dspi-gutter) 36px; }
|
|
366
|
+
.dspi-head {
|
|
367
|
+
display: flex;
|
|
368
|
+
align-items: flex-start;
|
|
369
|
+
justify-content: space-between;
|
|
370
|
+
gap: 16px 24px;
|
|
371
|
+
flex-wrap: wrap;
|
|
372
|
+
padding-top: 28px;
|
|
373
|
+
margin-bottom: 22px;
|
|
374
|
+
}
|
|
375
|
+
[data-platform='darwin'] .dspi-head { padding-top: calc(28px + var(--dsh-frame-top-clearance, 0px)); }
|
|
376
|
+
.dspi-head-main { flex: 1 1 360px; min-width: 0; }
|
|
377
|
+
.dspi-title { margin: 0; font-size: 20px; font-weight: 500; line-height: 28px; color: var(--dsw-alias-label-primary); }
|
|
378
|
+
.dspi-subtitle { max-width: 640px; margin: 4px 0 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-tertiary); }
|
|
379
|
+
.dspi-meta {
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
flex-wrap: wrap;
|
|
383
|
+
gap: 2px 14px;
|
|
384
|
+
margin-top: 10px;
|
|
385
|
+
font-size: 12px;
|
|
386
|
+
line-height: 18px;
|
|
387
|
+
color: var(--dsw-alias-label-caption);
|
|
388
|
+
}
|
|
389
|
+
.dspi-meta code {
|
|
390
|
+
max-width: min(100%, 420px);
|
|
391
|
+
overflow: hidden;
|
|
392
|
+
text-overflow: ellipsis;
|
|
393
|
+
white-space: nowrap;
|
|
394
|
+
font-family: ${MONO};
|
|
395
|
+
font-size: 12px;
|
|
396
|
+
}
|
|
397
|
+
.dspi-origin[data-origin='missing'] { color: var(--dsw-alias-state-warn-label); }
|
|
398
|
+
.dspi-origin[data-origin='unreadable'] { color: var(--dsw-alias-state-error-primary); }
|
|
399
|
+
.dspi-head-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
400
|
+
.dspi-master {
|
|
401
|
+
display: inline-flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
gap: 8px;
|
|
404
|
+
font-size: 13px;
|
|
405
|
+
line-height: 20px;
|
|
406
|
+
color: var(--dsw-alias-label-secondary);
|
|
407
|
+
user-select: none;
|
|
408
|
+
}
|
|
409
|
+
.dspi-btn {
|
|
410
|
+
display: inline-flex;
|
|
411
|
+
align-items: center;
|
|
412
|
+
justify-content: center;
|
|
413
|
+
gap: 6px;
|
|
414
|
+
height: 32px;
|
|
415
|
+
padding: 0 14px;
|
|
416
|
+
border: 0;
|
|
417
|
+
border-radius: 16px;
|
|
418
|
+
background: transparent;
|
|
419
|
+
color: var(--dsw-alias-label-primary);
|
|
420
|
+
font-size: 13px;
|
|
421
|
+
line-height: 20px;
|
|
422
|
+
white-space: nowrap;
|
|
423
|
+
cursor: pointer;
|
|
424
|
+
transition: background-color .15s, border-color .15s, opacity .15s;
|
|
425
|
+
}
|
|
426
|
+
.dspi-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
427
|
+
.dspi-btn:disabled { opacity: .4; cursor: not-allowed; }
|
|
428
|
+
.dspi-btn--primary { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }
|
|
429
|
+
.dspi-btn--primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }
|
|
430
|
+
.dspi-btn--outline { box-shadow: inset 0 0 0 .5px var(--dsw-alias-border-l3); }
|
|
431
|
+
.dspi-btn--sm { height: 28px; padding: 0 10px; border-radius: 8px; font-size: 12px; line-height: 18px; }
|
|
432
|
+
.dspi-switch {
|
|
433
|
+
position: relative;
|
|
434
|
+
flex: none;
|
|
435
|
+
width: 36px;
|
|
436
|
+
height: 20px;
|
|
437
|
+
padding: 2px;
|
|
438
|
+
border: 0;
|
|
439
|
+
border-radius: 999px;
|
|
440
|
+
background: var(--dsw-alias-border-l3);
|
|
441
|
+
cursor: pointer;
|
|
442
|
+
transition: background-color .15s;
|
|
443
|
+
}
|
|
444
|
+
.dspi-switch[aria-checked='true'] { background: var(--dsw-alias-brand-primary); }
|
|
445
|
+
.dspi-switch:disabled { opacity: .5; cursor: default; }
|
|
446
|
+
.dspi-switch-thumb {
|
|
447
|
+
display: block;
|
|
448
|
+
width: 16px;
|
|
449
|
+
height: 16px;
|
|
450
|
+
border-radius: 50%;
|
|
451
|
+
background: var(--dsw-alias-label-primary-foreground);
|
|
452
|
+
transition: transform .12s ease;
|
|
453
|
+
}
|
|
454
|
+
.dspi-switch[aria-checked='false'] .dspi-switch-thumb { background: var(--dsw-alias-switch-thumb); }
|
|
455
|
+
.dspi-switch[aria-checked='true'] .dspi-switch-thumb { transform: translateX(16px); }
|
|
456
|
+
.dspi-root button:focus-visible,
|
|
457
|
+
.dspi-root select:focus-visible,
|
|
458
|
+
.dspi-root input[type='checkbox']:focus-visible,
|
|
459
|
+
.dspi-root pre:focus-visible {
|
|
460
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
461
|
+
outline-offset: 2px;
|
|
462
|
+
}
|
|
463
|
+
.dspi-switch:focus-visible {
|
|
464
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
465
|
+
outline-offset: 2px;
|
|
466
|
+
}
|
|
467
|
+
.dspi-banner {
|
|
468
|
+
display: flex;
|
|
469
|
+
align-items: flex-start;
|
|
470
|
+
gap: 8px;
|
|
471
|
+
margin: 0 0 16px;
|
|
472
|
+
padding: 10px 12px;
|
|
473
|
+
border-radius: 12px;
|
|
474
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
475
|
+
color: var(--dsw-alias-label-secondary);
|
|
476
|
+
font-size: 13px;
|
|
477
|
+
line-height: 20px;
|
|
478
|
+
}
|
|
479
|
+
.dspi-banner--error { background: var(--dsw-alias-interactive-bg-hover-danger); color: var(--dsw-alias-state-error-primary); }
|
|
480
|
+
.dspi-banner--warn { background: var(--dsw-alias-state-warn-tertiary); color: var(--dsw-alias-state-warn-label); }
|
|
481
|
+
.dspi-banner-icon { flex: none; margin-top: 3px; }
|
|
482
|
+
.dspi-banner-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
|
|
483
|
+
.dspi-banner .dspi-btn { flex: none; margin-left: auto; }
|
|
484
|
+
.dspi-section { margin-top: 26px; }
|
|
485
|
+
.dspi-section--preview { margin-top: 0; }
|
|
486
|
+
.dspi-section-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
|
|
487
|
+
.dspi-section-title { margin: 0; font-size: 13px; font-weight: 600; line-height: 20px; color: var(--dsw-alias-label-secondary); }
|
|
488
|
+
.dspi-section-meta { font-size: 12px; line-height: 20px; color: var(--dsw-alias-label-caption); }
|
|
489
|
+
.dspi-section-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
|
|
490
|
+
.dspi-icon-btn.dspi-preview-refresh[data-busy='true'] { opacity: .55; }
|
|
491
|
+
.dspi-list { display: flex; flex-direction: column; gap: 10px; margin: 0; padding: 0; list-style: none; }
|
|
492
|
+
.dspi-list-item { display: block; min-width: 0; }
|
|
493
|
+
.dspi-add {
|
|
494
|
+
display: flex;
|
|
495
|
+
align-items: center;
|
|
496
|
+
justify-content: center;
|
|
497
|
+
gap: 6px;
|
|
498
|
+
width: 100%;
|
|
499
|
+
height: 40px;
|
|
500
|
+
margin-top: 10px;
|
|
501
|
+
border: 1px dashed var(--dsw-alias-border-l3);
|
|
502
|
+
border-radius: 12px;
|
|
503
|
+
background: transparent;
|
|
504
|
+
color: var(--dsw-alias-label-secondary);
|
|
505
|
+
font-size: 13px;
|
|
506
|
+
line-height: 20px;
|
|
507
|
+
cursor: pointer;
|
|
508
|
+
transition: background-color .15s, border-color .15s, color .15s;
|
|
509
|
+
}
|
|
510
|
+
.dspi-add:hover {
|
|
511
|
+
border-color: var(--dsw-alias-border-l4);
|
|
512
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
513
|
+
color: var(--dsw-alias-label-primary);
|
|
514
|
+
}
|
|
515
|
+
.dspi-card {
|
|
516
|
+
padding: 12px 14px 10px;
|
|
517
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
518
|
+
border-radius: 16px;
|
|
519
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
520
|
+
transition: border-color .15s;
|
|
521
|
+
}
|
|
522
|
+
.dspi-card:hover, .dspi-card:focus-within { border-color: var(--dsw-alias-border-l4); }
|
|
523
|
+
.dspi-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 32px; }
|
|
524
|
+
.dspi-card-head-main { display: flex; align-items: center; gap: 8px; flex: 1 1 220px; min-width: 0; }
|
|
525
|
+
.dspi-card-head-side { display: flex; align-items: center; gap: 4px; flex: none; margin-left: auto; }
|
|
526
|
+
.dspi-title-input {
|
|
527
|
+
flex: 1 1 auto;
|
|
528
|
+
min-width: 0;
|
|
529
|
+
height: 28px;
|
|
530
|
+
padding: 0 2px;
|
|
531
|
+
border: 0;
|
|
532
|
+
border-bottom: 1px solid transparent;
|
|
533
|
+
border-radius: 0;
|
|
534
|
+
background: transparent;
|
|
535
|
+
color: var(--dsw-alias-label-primary);
|
|
536
|
+
font-size: 14px;
|
|
537
|
+
font-weight: 500;
|
|
538
|
+
line-height: 22px;
|
|
539
|
+
}
|
|
540
|
+
.dspi-title-input:hover { border-bottom-color: var(--dsw-alias-border-l3); }
|
|
541
|
+
.dspi-title-input:focus { outline: none; border-bottom-color: var(--dsw-alias-state-business-primary); }
|
|
542
|
+
.dspi-title-input::placeholder { color: var(--dsw-alias-label-caption); font-weight: 400; }
|
|
543
|
+
.dspi-card[data-enabled='false'] .dspi-title-input { color: var(--dsw-alias-label-secondary); }
|
|
544
|
+
.dspi-select-wrap { position: relative; display: inline-flex; align-items: center; flex: none; }
|
|
545
|
+
.dspi-select {
|
|
546
|
+
height: 28px;
|
|
547
|
+
padding: 0 24px 0 10px;
|
|
548
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
549
|
+
border-radius: 8px;
|
|
550
|
+
background: transparent;
|
|
551
|
+
color: var(--dsw-alias-label-secondary);
|
|
552
|
+
font-size: 12px;
|
|
553
|
+
line-height: 18px;
|
|
554
|
+
cursor: pointer;
|
|
555
|
+
appearance: none;
|
|
556
|
+
}
|
|
557
|
+
.dspi-select:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
558
|
+
.dspi-caret { position: absolute; right: 8px; top: 50%; width: 12px; height: 12px; transform: translateY(-50%); color: var(--dsw-alias-label-tertiary); pointer-events: none; }
|
|
559
|
+
.dspi-icon-btn {
|
|
560
|
+
display: inline-flex;
|
|
561
|
+
align-items: center;
|
|
562
|
+
justify-content: center;
|
|
563
|
+
width: 28px;
|
|
564
|
+
height: 28px;
|
|
565
|
+
padding: 0;
|
|
566
|
+
border: 0;
|
|
567
|
+
border-radius: 8px;
|
|
568
|
+
background: transparent;
|
|
569
|
+
color: var(--dsw-alias-label-tertiary);
|
|
570
|
+
cursor: pointer;
|
|
571
|
+
transition: background-color .15s, color .15s;
|
|
572
|
+
}
|
|
573
|
+
.dspi-icon-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
574
|
+
.dspi-icon-btn:disabled { opacity: .3; cursor: default; }
|
|
575
|
+
.dspi-icon-btn--danger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover-danger); color: var(--dsw-alias-state-error-primary); }
|
|
576
|
+
.dspi-textarea {
|
|
577
|
+
display: block;
|
|
578
|
+
width: 100%;
|
|
579
|
+
min-height: 96px;
|
|
580
|
+
max-height: 360px;
|
|
581
|
+
margin-top: 6px;
|
|
582
|
+
padding: 10px 12px;
|
|
583
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
584
|
+
border-radius: 12px;
|
|
585
|
+
background: var(--dsw-alias-bg-base);
|
|
586
|
+
color: var(--dsw-alias-label-primary);
|
|
587
|
+
font-family: ${MONO};
|
|
588
|
+
font-size: 12.5px;
|
|
589
|
+
line-height: 1.7;
|
|
590
|
+
resize: vertical;
|
|
591
|
+
field-sizing: content;
|
|
592
|
+
transition: border-color .15s;
|
|
593
|
+
}
|
|
594
|
+
.dspi-textarea:hover { border-color: var(--dsw-alias-border-l2); }
|
|
595
|
+
.dspi-textarea:focus { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
596
|
+
.dspi-textarea::placeholder { color: var(--dsw-alias-label-caption); }
|
|
597
|
+
.dspi-card-meta { display: flex; align-items: center; gap: 4px 12px; flex-wrap: wrap; margin-top: 6px; font-size: 12px; line-height: 20px; color: var(--dsw-alias-label-caption); }
|
|
598
|
+
.dspi-card-hint { flex: 1 1 220px; min-width: 0; color: var(--dsw-alias-label-tertiary); }
|
|
599
|
+
.dspi-check { display: inline-flex; align-items: center; gap: 6px; flex: none; margin-left: auto; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 20px; cursor: pointer; white-space: nowrap; }
|
|
600
|
+
.dspi-check input { width: 14px; height: 14px; margin: 0; accent-color: var(--dsw-alias-brand-primary); cursor: inherit; }
|
|
601
|
+
.dspi-chip {
|
|
602
|
+
display: inline-flex;
|
|
603
|
+
align-items: center;
|
|
604
|
+
gap: 4px;
|
|
605
|
+
height: 20px;
|
|
606
|
+
padding: 0 8px;
|
|
607
|
+
border-radius: 10px;
|
|
608
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
609
|
+
color: var(--dsw-alias-label-secondary);
|
|
610
|
+
font-size: 11px;
|
|
611
|
+
line-height: 20px;
|
|
612
|
+
white-space: nowrap;
|
|
613
|
+
}
|
|
614
|
+
.dspi-chip--warn { background: var(--dsw-alias-state-warn-tertiary); color: var(--dsw-alias-state-warn-label); }
|
|
615
|
+
.dspi-empty {
|
|
616
|
+
display: flex;
|
|
617
|
+
flex-direction: column;
|
|
618
|
+
align-items: center;
|
|
619
|
+
gap: 4px;
|
|
620
|
+
padding: 36px 20px;
|
|
621
|
+
border: 1px dashed var(--dsw-alias-border-l3);
|
|
622
|
+
border-radius: 16px;
|
|
623
|
+
text-align: center;
|
|
624
|
+
}
|
|
625
|
+
.dspi-empty-glyph { margin-bottom: 6px; color: var(--dsw-alias-label-caption); }
|
|
626
|
+
.dspi-empty-title { margin: 0; font-size: 14px; font-weight: 500; line-height: 22px; color: var(--dsw-alias-label-secondary); }
|
|
627
|
+
.dspi-empty-hint { margin: 0; max-width: 440px; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-tertiary); }
|
|
628
|
+
.dspi-empty-action { margin-top: 14px; }
|
|
629
|
+
.dspi-panel {
|
|
630
|
+
display: flex;
|
|
631
|
+
flex-direction: column;
|
|
632
|
+
gap: 12px;
|
|
633
|
+
padding: 14px;
|
|
634
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
635
|
+
border-radius: 16px;
|
|
636
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
637
|
+
}
|
|
638
|
+
.dspi-panel--idle { padding: 20px; color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 20px; }
|
|
639
|
+
.dspi-panel--idle p { margin: 0; }
|
|
640
|
+
.dspi-note { margin: 0; font-size: 12px; line-height: 20px; color: var(--dsw-alias-label-tertiary); }
|
|
641
|
+
.dspi-note--warn { color: var(--dsw-alias-state-warn-label); }
|
|
642
|
+
.dspi-subhead { margin: 0; font-size: 12px; font-weight: 600; line-height: 20px; color: var(--dsw-alias-label-secondary); }
|
|
643
|
+
.dspi-inline { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12px; line-height: 20px; color: var(--dsw-alias-label-tertiary); }
|
|
644
|
+
.dspi-var {
|
|
645
|
+
padding: 1px 6px;
|
|
646
|
+
border-radius: 6px;
|
|
647
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
648
|
+
color: var(--dsw-alias-label-secondary);
|
|
649
|
+
font-family: ${MONO};
|
|
650
|
+
font-size: 11.5px;
|
|
651
|
+
}
|
|
652
|
+
.dspi-checks { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }
|
|
653
|
+
.dspi-check-row { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; line-height: 20px; color: var(--dsw-alias-state-warn-label); }
|
|
654
|
+
.dspi-check-dot { flex: none; width: 5px; height: 5px; margin-top: 7px; border-radius: 50%; background: currentColor; }
|
|
655
|
+
.dspi-check-row--ok { color: var(--dsw-alias-state-success-primary); }
|
|
656
|
+
.dspi-check-row .dspi-check-icon { flex: none; margin-top: 4px; }
|
|
657
|
+
.dspi-table-scroll {
|
|
658
|
+
overflow: hidden;
|
|
659
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
660
|
+
border-radius: 12px;
|
|
661
|
+
background: var(--dsw-alias-bg-base);
|
|
662
|
+
}
|
|
663
|
+
.dspi-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-family: ${MONO}; font-size: 11.5px; }
|
|
664
|
+
.dspi-table th {
|
|
665
|
+
position: sticky;
|
|
666
|
+
top: 0;
|
|
667
|
+
z-index: 1;
|
|
668
|
+
padding: 6px 8px;
|
|
669
|
+
border-bottom: .5px solid var(--dsw-alias-border-l3);
|
|
670
|
+
background: var(--dsw-alias-bg-base);
|
|
671
|
+
color: var(--dsw-alias-label-caption);
|
|
672
|
+
font-weight: 500;
|
|
673
|
+
text-align: left;
|
|
674
|
+
}
|
|
675
|
+
.dspi-table td {
|
|
676
|
+
padding: 5px 8px;
|
|
677
|
+
border-bottom: .5px solid var(--dsw-alias-border-l1);
|
|
678
|
+
color: var(--dsw-alias-label-tertiary);
|
|
679
|
+
white-space: nowrap;
|
|
680
|
+
overflow: hidden;
|
|
681
|
+
text-overflow: ellipsis;
|
|
682
|
+
}
|
|
683
|
+
.dspi-table tr:last-child td { border-bottom: 0; }
|
|
684
|
+
.dspi-table tr[data-ours='true'] td { color: var(--dsw-alias-label-primary); }
|
|
685
|
+
.dspi-table .dspi-col-index { width: 38px; }
|
|
686
|
+
.dspi-table .dspi-col-order { width: 64px; }
|
|
687
|
+
.dspi-table .dspi-col-bytes { width: 64px; text-align: right; }
|
|
688
|
+
.dspi-name-cell { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
689
|
+
.dspi-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
690
|
+
.dspi-tag {
|
|
691
|
+
flex: none;
|
|
692
|
+
height: 18px;
|
|
693
|
+
padding: 0 6px;
|
|
694
|
+
border-radius: 9px;
|
|
695
|
+
background: var(--dsw-alias-state-business-tertiary);
|
|
696
|
+
color: var(--dsw-alias-state-business-primary);
|
|
697
|
+
font-family: var(--dsw-font-family);
|
|
698
|
+
font-size: 10.5px;
|
|
699
|
+
line-height: 18px;
|
|
700
|
+
}
|
|
701
|
+
.dspi-pre {
|
|
702
|
+
margin: 0;
|
|
703
|
+
max-height: 340px;
|
|
704
|
+
overflow: auto;
|
|
705
|
+
padding: 12px;
|
|
706
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
707
|
+
border-radius: 12px;
|
|
708
|
+
background: var(--dsw-alias-bg-base);
|
|
709
|
+
color: var(--dsw-alias-label-secondary);
|
|
710
|
+
font-family: ${MONO};
|
|
711
|
+
font-size: 12px;
|
|
712
|
+
line-height: 1.7;
|
|
713
|
+
white-space: pre-wrap;
|
|
714
|
+
overflow-wrap: anywhere;
|
|
715
|
+
}
|
|
716
|
+
.dspi-settings-actions {
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: center;
|
|
719
|
+
gap: 8px;
|
|
720
|
+
flex-wrap: wrap;
|
|
721
|
+
margin-top: 14px;
|
|
722
|
+
}
|
|
723
|
+
.dspi-settings-actions .dspi-status { margin-right: auto; }
|
|
724
|
+
.dspi-status { display: inline-flex; align-items: center; gap: 6px; flex: 0 1 auto; min-width: 0; font-size: 12px; line-height: 20px; color: var(--dsw-alias-label-tertiary); }
|
|
725
|
+
.dspi-status-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--dsw-alias-state-success-primary); }
|
|
726
|
+
.dspi-status-dot[data-dirty='true'] { background: var(--dsw-alias-state-warn-primary); }
|
|
727
|
+
.dspi-loading { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-tertiary); }
|
|
728
|
+
.dspi-spinner {
|
|
729
|
+
width: 14px;
|
|
730
|
+
height: 14px;
|
|
731
|
+
border: 2px solid var(--dsw-alias-border-l3);
|
|
732
|
+
border-top-color: var(--dsw-alias-state-business-primary);
|
|
733
|
+
border-radius: 50%;
|
|
734
|
+
animation: dspi-spin .8s linear infinite;
|
|
735
|
+
}
|
|
736
|
+
@keyframes dspi-spin { to { transform: rotate(360deg); } }
|
|
737
|
+
.dspi-spin { display: inline-flex; animation: dspi-spin 1s linear infinite; }
|
|
738
|
+
.dspi-skeleton { height: 118px; margin-top: 12px; border-radius: 16px; background: var(--dsw-alias-bg-skeleton, var(--dsw-alias-bg-layer-2)); }
|
|
739
|
+
.dspi-state { display: flex; align-items: flex-start; gap: 12px; padding: 16px; border: .5px solid var(--dsw-alias-border-l3); border-radius: 16px; background: var(--dsw-alias-bg-layer-1); }
|
|
740
|
+
.dspi-state-icon { flex: none; margin-top: 1px; color: var(--dsw-alias-state-error-primary); }
|
|
741
|
+
.dspi-state-body { display: flex; flex-direction: column; gap: 4px; flex: 1 1 auto; min-width: 0; }
|
|
742
|
+
.dspi-state-title { margin: 0; font-size: 14px; font-weight: 500; line-height: 22px; color: var(--dsw-alias-label-primary); }
|
|
743
|
+
.dspi-state-hint { margin: 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-tertiary); overflow-wrap: anywhere; }
|
|
744
|
+
@media (max-width: 720px) {
|
|
745
|
+
.dspi-head { gap: 12px; }
|
|
746
|
+
.dspi-head-actions { width: 100%; }
|
|
747
|
+
}
|
|
748
|
+
@media (max-width: 560px) {
|
|
749
|
+
.dspi-card { padding: 10px 12px 8px; }
|
|
750
|
+
.dspi-card-head-main { flex-basis: 100%; }
|
|
751
|
+
.dspi-card-head-side { margin-left: 0; }
|
|
752
|
+
.dspi-check { margin-left: 0; }
|
|
753
|
+
.dspi-meta code { max-width: 100%; }
|
|
754
|
+
}
|
|
755
|
+
@media (prefers-reduced-motion: reduce) {
|
|
756
|
+
.dspi-spinner { animation: none; }
|
|
757
|
+
.dspi-card, .dspi-btn, .dspi-switch, .dspi-switch-thumb, .dspi-icon-btn { transition: none; }
|
|
758
|
+
}
|
|
759
|
+
`;
|
|
760
|
+
/**
|
|
761
|
+
* One 16×16 stroke glyph.
|
|
762
|
+
*
|
|
763
|
+
* @param h - React's createElement.
|
|
764
|
+
* @param name - which path set to draw.
|
|
765
|
+
* @param size - rendered square edge.
|
|
766
|
+
* @returns the SVG element.
|
|
767
|
+
*/
|
|
768
|
+
function glyph(h, name, size = 14) {
|
|
769
|
+
const svg = (children) => h("svg", {
|
|
770
|
+
viewBox: "0 0 16 16",
|
|
771
|
+
width: size,
|
|
772
|
+
height: size,
|
|
773
|
+
fill: "none",
|
|
774
|
+
stroke: "currentColor",
|
|
775
|
+
strokeWidth: "1.3",
|
|
776
|
+
strokeLinecap: "round",
|
|
777
|
+
strokeLinejoin: "round",
|
|
778
|
+
"aria-hidden": "true"
|
|
779
|
+
}, ...children);
|
|
780
|
+
switch (name) {
|
|
781
|
+
case "up": return svg([h("path", { d: "M4 9.5 8 5.5l4 4" })]);
|
|
782
|
+
case "down": return svg([h("path", { d: "M4 6.5 8 10.5l4-4" })]);
|
|
783
|
+
case "trash": return svg([h("path", { d: "M3 4.5h10M6.2 4.5V3.3h3.6v1.2M4.6 4.5l.5 8.2h5.8l.5-8.2M6.8 6.8v3.6M9.2 6.8v3.6" })]);
|
|
784
|
+
case "warning": return svg([h("path", { d: "M8 2.2 14 13H2L8 2.2Z" }), h("path", { d: "M8 6.3v3.3M8 11.6v.1" })]);
|
|
785
|
+
case "check": return svg([h("path", { d: "M3.2 8.4 6.4 11.6 12.8 4.8" })]);
|
|
786
|
+
case "close": return svg([h("path", { d: "M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6" })]);
|
|
787
|
+
case "copy": return svg([h("rect", {
|
|
788
|
+
x: "5.5",
|
|
789
|
+
y: "5.5",
|
|
790
|
+
width: "7",
|
|
791
|
+
height: "7",
|
|
792
|
+
rx: "1.2"
|
|
793
|
+
}), h("path", { d: "M10.5 5.5V3.8a1 1 0 0 0-1-1H4.3a1.5 1.5 0 0 0-1.5 1.5v5.2a1 1 0 0 0 1 1h1.7" })]);
|
|
794
|
+
case "refresh": return svg([h("path", { d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9" }), h("path", { d: "M13.5 3v4h-4" })]);
|
|
795
|
+
default: return svg([h("rect", {
|
|
796
|
+
x: "2",
|
|
797
|
+
y: "2.5",
|
|
798
|
+
width: "12",
|
|
799
|
+
height: "11",
|
|
800
|
+
rx: "1.5"
|
|
801
|
+
}), h("path", { d: "M4.9 6h6.2M4.9 8.4h6.2M4.9 10.8h3.4" })]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* The sidebar row's glyph.
|
|
806
|
+
*
|
|
807
|
+
* The shell draws the row box, the label, the tooltip and the collapsed rail,
|
|
808
|
+
* then asks this component for a `size`-square, `currentColor` glyph; `active`
|
|
809
|
+
* is the shell's selection state and the shell already paints it, so the glyph
|
|
810
|
+
* stays decorative. `data-dsh-panel-entry` marks the only DOM this plugin owns
|
|
811
|
+
* inside that row, which is how skins and diagnostics identify the entry.
|
|
812
|
+
*
|
|
813
|
+
* @param props - the shell's icon share: square edge and selection state.
|
|
814
|
+
* @returns the decorative section-list glyph.
|
|
815
|
+
*/
|
|
816
|
+
function PanelIcon(props) {
|
|
817
|
+
const h = require("react").createElement;
|
|
818
|
+
return h("svg", {
|
|
819
|
+
"data-dsh-panel-entry": PANEL_ID,
|
|
820
|
+
viewBox: "0 0 16 16",
|
|
821
|
+
width: props.size,
|
|
822
|
+
height: props.size,
|
|
823
|
+
fill: "none",
|
|
824
|
+
stroke: "currentColor",
|
|
825
|
+
strokeWidth: "1.3",
|
|
826
|
+
strokeLinecap: "round",
|
|
827
|
+
strokeLinejoin: "round",
|
|
828
|
+
"aria-hidden": "true"
|
|
829
|
+
}, h("rect", {
|
|
830
|
+
x: "2",
|
|
831
|
+
y: "2.5",
|
|
832
|
+
width: "12",
|
|
833
|
+
height: "11",
|
|
834
|
+
rx: "1.5"
|
|
835
|
+
}), h("path", { d: "M4.9 6h6.2M4.9 8.4h6.2M4.9 10.8h3.4" }));
|
|
836
|
+
}
|
|
837
|
+
/** The shell's switch geometry, drawn from theme tokens. */
|
|
838
|
+
function Switch(props) {
|
|
839
|
+
const h = require("react").createElement;
|
|
840
|
+
return h("button", {
|
|
841
|
+
type: "button",
|
|
842
|
+
role: "switch",
|
|
843
|
+
className: "dspi-switch",
|
|
844
|
+
"aria-checked": props.checked,
|
|
845
|
+
"aria-label": props.label,
|
|
846
|
+
...props.title === void 0 ? {} : { title: props.title },
|
|
847
|
+
...props.disabled === true ? { disabled: true } : {},
|
|
848
|
+
onClick: () => props.onChange(!props.checked)
|
|
849
|
+
}, h("span", {
|
|
850
|
+
className: "dspi-switch-thumb",
|
|
851
|
+
"aria-hidden": "true"
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
/** One section editor card. */
|
|
855
|
+
function SectionCard(props) {
|
|
856
|
+
const h = require("react").createElement;
|
|
857
|
+
const { section, t } = props;
|
|
858
|
+
const bytes = byteLength(section.text);
|
|
859
|
+
const hint = placementHint(t, section.position);
|
|
860
|
+
const empty = section.text.trim() === "";
|
|
861
|
+
return h("div", {
|
|
862
|
+
className: "dspi-card",
|
|
863
|
+
"data-enabled": section.enabled ? "true" : "false",
|
|
864
|
+
"data-section-id": section.id
|
|
865
|
+
}, h("div", { className: "dspi-card-head" }, h("div", { className: "dspi-card-head-main" }, h(Switch, {
|
|
866
|
+
checked: section.enabled,
|
|
867
|
+
label: format(t("toggleNamed"), { title: section.title || t("section") }),
|
|
868
|
+
title: section.enabled ? t("enabled") : t("disabledChip"),
|
|
869
|
+
onChange: (next) => props.onChange({
|
|
870
|
+
...section,
|
|
871
|
+
enabled: next
|
|
872
|
+
})
|
|
873
|
+
}), h("input", {
|
|
874
|
+
className: "dspi-title-input",
|
|
875
|
+
value: section.title,
|
|
876
|
+
maxLength: 80,
|
|
877
|
+
autoFocus: props.autoFocus,
|
|
878
|
+
placeholder: t("sectionTitle"),
|
|
879
|
+
"aria-label": format(t("sectionTitleOf"), { index: props.index + 1 }),
|
|
880
|
+
onChange: (event) => props.onChange({
|
|
881
|
+
...section,
|
|
882
|
+
title: event.target.value
|
|
883
|
+
})
|
|
884
|
+
})), h("div", { className: "dspi-card-head-side" }, h("span", { className: "dspi-select-wrap" }, h("select", {
|
|
885
|
+
className: "dspi-select",
|
|
886
|
+
value: section.position,
|
|
887
|
+
title: hint,
|
|
888
|
+
"aria-label": format(t("positionOf"), { index: props.index + 1 }),
|
|
889
|
+
onChange: (event) => props.onChange({
|
|
890
|
+
...section,
|
|
891
|
+
position: event.target.value
|
|
892
|
+
})
|
|
893
|
+
}, h("option", { value: "head" }, t("positionHead")), h("option", { value: "body" }, t("positionBody")), h("option", { value: "tail" }, t("positionTail"))), h("svg", {
|
|
894
|
+
className: "dspi-caret",
|
|
895
|
+
viewBox: "0 0 12 12",
|
|
896
|
+
fill: "none",
|
|
897
|
+
stroke: "currentColor",
|
|
898
|
+
strokeWidth: "1.2",
|
|
899
|
+
strokeLinecap: "round",
|
|
900
|
+
strokeLinejoin: "round",
|
|
901
|
+
"aria-hidden": "true"
|
|
902
|
+
}, h("path", { d: "M3 4.6 6 7.6 9 4.6" }))), h("button", {
|
|
903
|
+
type: "button",
|
|
904
|
+
className: "dspi-icon-btn",
|
|
905
|
+
title: format(t("moveUpNamed"), { title: section.title || t("section") }),
|
|
906
|
+
"aria-label": format(t("moveUpNamed"), { title: section.title || t("section") }),
|
|
907
|
+
disabled: props.index === 0,
|
|
908
|
+
onClick: () => props.onMove(-1)
|
|
909
|
+
}, glyph(h, "up", 15)), h("button", {
|
|
910
|
+
type: "button",
|
|
911
|
+
className: "dspi-icon-btn",
|
|
912
|
+
title: format(t("moveDownNamed"), { title: section.title || t("section") }),
|
|
913
|
+
"aria-label": format(t("moveDownNamed"), { title: section.title || t("section") }),
|
|
914
|
+
disabled: props.index === props.total - 1,
|
|
915
|
+
onClick: () => props.onMove(1)
|
|
916
|
+
}, glyph(h, "down", 15)), h("button", {
|
|
917
|
+
type: "button",
|
|
918
|
+
className: "dspi-icon-btn dspi-icon-btn--danger",
|
|
919
|
+
title: format(t("removeNamed"), { title: section.title || t("section") }),
|
|
920
|
+
"aria-label": format(t("removeNamed"), { title: section.title || t("section") }),
|
|
921
|
+
onClick: props.onRemove
|
|
922
|
+
}, glyph(h, "trash", 15)))), h("textarea", {
|
|
923
|
+
className: "dspi-textarea",
|
|
924
|
+
value: section.text,
|
|
925
|
+
rows: Math.min(18, Math.max(5, section.text.split("\n").length + 2)),
|
|
926
|
+
placeholder: t("textPlaceholder"),
|
|
927
|
+
"aria-label": format(t("sectionTextOf"), { index: props.index + 1 }),
|
|
928
|
+
spellCheck: false,
|
|
929
|
+
onChange: (event) => props.onChange({
|
|
930
|
+
...section,
|
|
931
|
+
text: event.target.value
|
|
932
|
+
})
|
|
933
|
+
}), h("div", { className: "dspi-card-meta" }, h("span", null, format(t("bytes"), { bytes })), h("span", { className: "dspi-card-hint" }, hint), section.enabled ? empty ? h("span", { className: "dspi-chip dspi-chip--warn" }, t("emptyChip")) : null : h("span", { className: "dspi-chip" }, t("disabledChip")), h("label", {
|
|
934
|
+
className: "dspi-check",
|
|
935
|
+
title: t("interpolateHint")
|
|
936
|
+
}, h("input", {
|
|
937
|
+
type: "checkbox",
|
|
938
|
+
checked: section.interpolate,
|
|
939
|
+
onChange: (event) => props.onChange({
|
|
940
|
+
...section,
|
|
941
|
+
interpolate: event.target.checked
|
|
942
|
+
})
|
|
943
|
+
}), t("interpolate"))));
|
|
944
|
+
}
|
|
945
|
+
/** The section list, including its empty state. */
|
|
946
|
+
function SectionList(props) {
|
|
947
|
+
const h = require("react").createElement;
|
|
948
|
+
const { sections, t } = props;
|
|
949
|
+
if (sections.length === 0) return h("div", { className: "dspi-empty" }, h("span", { className: "dspi-empty-glyph" }, glyph(h, "sections", 28)), h("p", { className: "dspi-empty-title" }, t("emptyListTitle")), h("p", { className: "dspi-empty-hint" }, t("emptyListHint")), h("div", { className: "dspi-empty-action" }, h("button", {
|
|
950
|
+
type: "button",
|
|
951
|
+
className: "dspi-btn dspi-btn--primary",
|
|
952
|
+
onClick: props.onAdd
|
|
953
|
+
}, `+ ${t("addFirst")}`)));
|
|
954
|
+
return h("ol", { className: "dspi-list" }, sections.map((section, index) => h("li", {
|
|
955
|
+
key: section.id,
|
|
956
|
+
className: "dspi-list-item"
|
|
957
|
+
}, h(SectionCard, {
|
|
958
|
+
section,
|
|
959
|
+
index,
|
|
960
|
+
total: sections.length,
|
|
961
|
+
autoFocus: section.id === props.justAdded,
|
|
962
|
+
t,
|
|
963
|
+
onChange: (next) => props.onChange(index, next),
|
|
964
|
+
onRemove: () => props.onRemove(index),
|
|
965
|
+
onMove: (delta) => props.onMove(index, delta)
|
|
966
|
+
}))));
|
|
967
|
+
}
|
|
968
|
+
/** The assembled-prompt preview, shown above the editor. */
|
|
969
|
+
function PreviewPanel(props) {
|
|
970
|
+
const React = require("react");
|
|
971
|
+
const h = React.createElement;
|
|
972
|
+
const { preview, t } = props;
|
|
973
|
+
const copyLabel = props.copying === "copied" ? t("copied") : props.copying === "failed" ? t("copyFailed") : t("copy");
|
|
974
|
+
const copyGlyph = props.copying === "copied" ? "check" : props.copying === "failed" ? "warning" : "copy";
|
|
975
|
+
return h(React.Fragment, null, h("div", {
|
|
976
|
+
className: "dspi-section-head",
|
|
977
|
+
id: "dsh-system-prompt-insertion-preview"
|
|
978
|
+
}, h("h2", { className: "dspi-section-title" }, t("previewTitle")), h("button", {
|
|
979
|
+
type: "button",
|
|
980
|
+
className: "dspi-icon-btn dspi-preview-refresh",
|
|
981
|
+
onClick: props.onRefresh,
|
|
982
|
+
disabled: props.previewing,
|
|
983
|
+
"data-busy": props.previewing ? "true" : "false",
|
|
984
|
+
title: t("refreshPreview"),
|
|
985
|
+
"aria-label": t("refreshPreview")
|
|
986
|
+
}, h("span", {
|
|
987
|
+
className: props.previewing ? "dspi-spin" : void 0,
|
|
988
|
+
"aria-hidden": "true"
|
|
989
|
+
}, glyph(h, "refresh", 15))), preview === void 0 ? null : h("span", { className: "dspi-section-meta" }, format(t("previewSummary"), {
|
|
990
|
+
count: preview.sections.length,
|
|
991
|
+
bytes: preview.bytes
|
|
992
|
+
})), preview === void 0 ? null : h("div", { className: "dspi-section-actions" }, h("button", {
|
|
993
|
+
type: "button",
|
|
994
|
+
className: "dspi-btn dspi-btn--sm",
|
|
995
|
+
onClick: props.onCopy,
|
|
996
|
+
disabled: preview.rendered === "",
|
|
997
|
+
title: props.copying === "failed" ? t("copyFailedHint") : t("copy")
|
|
998
|
+
}, glyph(h, copyGlyph, 12), copyLabel))), props.previewError === void 0 ? null : h("div", {
|
|
999
|
+
className: "dspi-banner dspi-banner--error",
|
|
1000
|
+
role: "alert"
|
|
1001
|
+
}, h("span", { className: "dspi-banner-icon" }, glyph(h, "warning")), h("div", { className: "dspi-banner-text" }, props.previewError), h("button", {
|
|
1002
|
+
type: "button",
|
|
1003
|
+
className: "dspi-btn dspi-btn--sm",
|
|
1004
|
+
onClick: props.onRefresh
|
|
1005
|
+
}, t("retry"))), preview === void 0 ? props.previewError === void 0 ? h("div", { className: "dspi-panel dspi-panel--idle" }, props.previewing ? h("div", { className: "dspi-loading" }, h("span", {
|
|
1006
|
+
className: "dspi-spinner",
|
|
1007
|
+
"aria-hidden": "true"
|
|
1008
|
+
}), h("span", null, t("previewing"))) : h("p", null, t("previewNone"))) : null : h("div", { className: "dspi-panel" }, props.dirty ? h("p", { className: "dspi-note dspi-note--warn" }, t("previewStale")) : null, h("div", { className: "dspi-inline" }, h("span", null, t("variables")), preview.variables.length === 0 ? h("span", null, "—") : preview.variables.map((name) => h("code", {
|
|
1009
|
+
key: name,
|
|
1010
|
+
className: "dspi-var"
|
|
1011
|
+
}, name))), h("div", null, h("h3", { className: "dspi-subhead" }, t("warnings")), preview.warnings.length === 0 ? h("div", { className: "dspi-check-row dspi-check-row--ok" }, h("span", { className: "dspi-check-icon" }, glyph(h, "check", 12)), h("span", null, t("noWarnings"))) : h("ul", { className: "dspi-checks" }, preview.warnings.map((warning, index) => h("li", {
|
|
1012
|
+
key: `${warning.code}-${warning.sectionId ?? index}`,
|
|
1013
|
+
className: "dspi-check-row"
|
|
1014
|
+
}, h("span", {
|
|
1015
|
+
className: "dspi-check-dot",
|
|
1016
|
+
"aria-hidden": "true"
|
|
1017
|
+
}), h("span", null, warningText(t, warning)))))), h("div", { className: "dspi-table-scroll" }, h("table", { className: "dspi-table" }, h("thead", null, h("tr", null, h("th", {
|
|
1018
|
+
scope: "col",
|
|
1019
|
+
className: "dspi-col-index"
|
|
1020
|
+
}, "#"), h("th", {
|
|
1021
|
+
scope: "col",
|
|
1022
|
+
className: "dspi-col-order"
|
|
1023
|
+
}, t("orderShort")), h("th", { scope: "col" }, t("section")), h("th", {
|
|
1024
|
+
scope: "col",
|
|
1025
|
+
className: "dspi-col-bytes"
|
|
1026
|
+
}, t("bytesShort")))), h("tbody", null, preview.sections.map((section) => h("tr", {
|
|
1027
|
+
key: `${section.name}-${section.index}`,
|
|
1028
|
+
"data-ours": section.ours ? "true" : "false"
|
|
1029
|
+
}, h("td", null, String(section.index + 1).padStart(2, "0")), h("td", null, section.order === void 0 ? "—" : String(section.order)), h("td", null, h("span", { className: "dspi-name-cell" }, h("span", { className: "dspi-name" }, section.name), section.ours ? h("span", { className: "dspi-tag" }, t("ours")) : null)), h("td", { className: "dspi-col-bytes" }, `${section.bytes}B`)))))), h("pre", {
|
|
1030
|
+
className: "dspi-pre",
|
|
1031
|
+
tabIndex: 0,
|
|
1032
|
+
"aria-label": t("previewTitle")
|
|
1033
|
+
}, preview.rendered === "" ? "—" : preview.rendered)));
|
|
1034
|
+
}
|
|
1035
|
+
/** The sidebar panel body. */
|
|
1036
|
+
function PromptInsertionPanel(props) {
|
|
1037
|
+
const React = require("react");
|
|
1038
|
+
const h = React.createElement;
|
|
1039
|
+
const t = props.t ?? ((key) => key);
|
|
1040
|
+
const saveKeys = typeof document !== "undefined" && document.documentElement.dataset.platform === "darwin" ? "⌘S" : "Ctrl+S";
|
|
1041
|
+
const [phase, setPhase] = React.useState("loading");
|
|
1042
|
+
const [payload, setPayload] = React.useState(void 0);
|
|
1043
|
+
const [draft, setDraft] = React.useState(void 0);
|
|
1044
|
+
const [error, setError] = React.useState(void 0);
|
|
1045
|
+
const [saving, setSaving] = React.useState(false);
|
|
1046
|
+
const [preview, setPreview] = React.useState(void 0);
|
|
1047
|
+
const [previewing, setPreviewing] = React.useState(false);
|
|
1048
|
+
const [previewError, setPreviewError] = React.useState(void 0);
|
|
1049
|
+
const [copying, setCopying] = React.useState("idle");
|
|
1050
|
+
const [justAdded, setJustAdded] = React.useState(void 0);
|
|
1051
|
+
const adopt = React.useCallback((next) => {
|
|
1052
|
+
setPayload(next);
|
|
1053
|
+
setDraft(next.state);
|
|
1054
|
+
setPhase("ready");
|
|
1055
|
+
setError(void 0);
|
|
1056
|
+
}, []);
|
|
1057
|
+
/** Bumped on every preview request; only the newest answer may land. */
|
|
1058
|
+
const previewTicket = React.useRef(0);
|
|
1059
|
+
const runPreview = React.useCallback(() => {
|
|
1060
|
+
const ticket = ++previewTicket.current;
|
|
1061
|
+
setPreviewing(true);
|
|
1062
|
+
setPreviewError(void 0);
|
|
1063
|
+
fetch(`${API}/preview`, { headers: { accept: "application/json" } }).then((response) => readJson(response)).then((body) => {
|
|
1064
|
+
if (ticket !== previewTicket.current) return;
|
|
1065
|
+
setPreviewing(false);
|
|
1066
|
+
if (body.ok === true) {
|
|
1067
|
+
setPreview(body);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
setPreviewError(errorText(body, t("previewFailed")));
|
|
1071
|
+
}).catch((cause) => {
|
|
1072
|
+
if (ticket !== previewTicket.current) return;
|
|
1073
|
+
setPreviewing(false);
|
|
1074
|
+
setPreviewError(`${t("previewFailed")}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1075
|
+
});
|
|
1076
|
+
}, [t]);
|
|
1077
|
+
const refresh = React.useCallback(() => {
|
|
1078
|
+
setPhase("loading");
|
|
1079
|
+
fetch(`${API}/state`, { headers: { accept: "application/json" } }).then((response) => readJson(response)).then((body) => {
|
|
1080
|
+
if (body.ok === true) {
|
|
1081
|
+
adopt(payloadFrom(body, "missing"));
|
|
1082
|
+
runPreview();
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
setPhase("error");
|
|
1086
|
+
setError(errorText(body, t("loadFailed")));
|
|
1087
|
+
}).catch((cause) => {
|
|
1088
|
+
setPhase("error");
|
|
1089
|
+
setError(`${t("loadFailed")}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1090
|
+
});
|
|
1091
|
+
}, [
|
|
1092
|
+
adopt,
|
|
1093
|
+
runPreview,
|
|
1094
|
+
t
|
|
1095
|
+
]);
|
|
1096
|
+
React.useEffect(() => {
|
|
1097
|
+
refresh();
|
|
1098
|
+
}, [refresh]);
|
|
1099
|
+
const dirty = draft !== void 0 && payload !== void 0 && JSON.stringify(draft) !== JSON.stringify(payload.state);
|
|
1100
|
+
const save = React.useCallback(() => {
|
|
1101
|
+
if (draft === void 0 || saving) return;
|
|
1102
|
+
setSaving(true);
|
|
1103
|
+
setError(void 0);
|
|
1104
|
+
fetch(`${API}/state`, {
|
|
1105
|
+
method: "POST",
|
|
1106
|
+
headers: {
|
|
1107
|
+
"content-type": "application/json",
|
|
1108
|
+
accept: "application/json"
|
|
1109
|
+
},
|
|
1110
|
+
body: JSON.stringify(draft)
|
|
1111
|
+
}).then((response) => readJson(response)).then((body) => {
|
|
1112
|
+
setSaving(false);
|
|
1113
|
+
if (body.ok === true) {
|
|
1114
|
+
adopt(payloadFrom(body, "memory"));
|
|
1115
|
+
runPreview();
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
setError(errorText(body, t("saveFailed")));
|
|
1119
|
+
}).catch((cause) => {
|
|
1120
|
+
setSaving(false);
|
|
1121
|
+
setError(`${t("saveFailed")}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1122
|
+
});
|
|
1123
|
+
}, [
|
|
1124
|
+
adopt,
|
|
1125
|
+
draft,
|
|
1126
|
+
runPreview,
|
|
1127
|
+
saving,
|
|
1128
|
+
t
|
|
1129
|
+
]);
|
|
1130
|
+
/**
|
|
1131
|
+
* Apply the master switch at once instead of staging it.
|
|
1132
|
+
*
|
|
1133
|
+
* The switch controls what the host registers, so a staged toggle would leave
|
|
1134
|
+
* the preview disagreeing with the control until Save. Only the saved
|
|
1135
|
+
* document's `enabled` field is sent; unsaved section edits stay staged.
|
|
1136
|
+
*/
|
|
1137
|
+
const toggleEnabled = React.useCallback((next) => {
|
|
1138
|
+
if (payload === void 0 || draft === void 0 || saving) return;
|
|
1139
|
+
const previous = payload.state.enabled;
|
|
1140
|
+
setSaving(true);
|
|
1141
|
+
setError(void 0);
|
|
1142
|
+
setDraft({
|
|
1143
|
+
...draft,
|
|
1144
|
+
enabled: next
|
|
1145
|
+
});
|
|
1146
|
+
fetch(`${API}/state`, {
|
|
1147
|
+
method: "POST",
|
|
1148
|
+
headers: {
|
|
1149
|
+
"content-type": "application/json",
|
|
1150
|
+
accept: "application/json"
|
|
1151
|
+
},
|
|
1152
|
+
body: JSON.stringify({
|
|
1153
|
+
...payload.state,
|
|
1154
|
+
enabled: next
|
|
1155
|
+
})
|
|
1156
|
+
}).then((response) => readJson(response)).then((body) => {
|
|
1157
|
+
setSaving(false);
|
|
1158
|
+
if (body.ok === true) {
|
|
1159
|
+
const applied = payloadFrom(body, "memory");
|
|
1160
|
+
setPayload(applied);
|
|
1161
|
+
setDraft((current) => current === void 0 ? applied.state : {
|
|
1162
|
+
...current,
|
|
1163
|
+
enabled: next
|
|
1164
|
+
});
|
|
1165
|
+
runPreview();
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
setDraft((current) => current === void 0 ? current : {
|
|
1169
|
+
...current,
|
|
1170
|
+
enabled: previous
|
|
1171
|
+
});
|
|
1172
|
+
setError(errorText(body, t("saveFailed")));
|
|
1173
|
+
}).catch((cause) => {
|
|
1174
|
+
setSaving(false);
|
|
1175
|
+
setDraft((current) => current === void 0 ? current : {
|
|
1176
|
+
...current,
|
|
1177
|
+
enabled: previous
|
|
1178
|
+
});
|
|
1179
|
+
setError(`${t("saveFailed")}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1180
|
+
});
|
|
1181
|
+
}, [
|
|
1182
|
+
draft,
|
|
1183
|
+
payload,
|
|
1184
|
+
runPreview,
|
|
1185
|
+
saving,
|
|
1186
|
+
t
|
|
1187
|
+
]);
|
|
1188
|
+
React.useEffect(() => {
|
|
1189
|
+
if (!dirty) return;
|
|
1190
|
+
const onKeyDown = (event) => {
|
|
1191
|
+
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "s") return;
|
|
1192
|
+
if (document.querySelector("[role=\"dialog\"], [role=\"alertdialog\"]") !== null) return;
|
|
1193
|
+
event.preventDefault();
|
|
1194
|
+
save();
|
|
1195
|
+
};
|
|
1196
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1197
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
1198
|
+
}, [dirty, save]);
|
|
1199
|
+
const updateSection = React.useCallback((index, next) => {
|
|
1200
|
+
setDraft((current) => {
|
|
1201
|
+
if (current === void 0) return current;
|
|
1202
|
+
const sections = [...current.sections];
|
|
1203
|
+
sections[index] = next;
|
|
1204
|
+
return {
|
|
1205
|
+
...current,
|
|
1206
|
+
sections
|
|
1207
|
+
};
|
|
1208
|
+
});
|
|
1209
|
+
}, []);
|
|
1210
|
+
const removeSection = React.useCallback((index) => {
|
|
1211
|
+
setDraft((current) => current === void 0 ? current : {
|
|
1212
|
+
...current,
|
|
1213
|
+
sections: current.sections.filter((_, position) => position !== index)
|
|
1214
|
+
});
|
|
1215
|
+
}, []);
|
|
1216
|
+
const moveSection = React.useCallback((index, delta) => {
|
|
1217
|
+
setDraft((current) => {
|
|
1218
|
+
if (current === void 0) return current;
|
|
1219
|
+
const target = index + delta;
|
|
1220
|
+
if (target < 0 || target >= current.sections.length) return current;
|
|
1221
|
+
const sections = [...current.sections];
|
|
1222
|
+
const [moved] = sections.splice(index, 1);
|
|
1223
|
+
if (moved === void 0) return current;
|
|
1224
|
+
sections.splice(target, 0, moved);
|
|
1225
|
+
return {
|
|
1226
|
+
...current,
|
|
1227
|
+
sections
|
|
1228
|
+
};
|
|
1229
|
+
});
|
|
1230
|
+
}, []);
|
|
1231
|
+
const addSection = React.useCallback(() => {
|
|
1232
|
+
const id = newId();
|
|
1233
|
+
setJustAdded(id);
|
|
1234
|
+
setDraft((current) => current === void 0 ? current : {
|
|
1235
|
+
...current,
|
|
1236
|
+
sections: [...current.sections, {
|
|
1237
|
+
id,
|
|
1238
|
+
title: `${t("section")} ${current.sections.length + 1}`,
|
|
1239
|
+
text: "",
|
|
1240
|
+
position: "tail",
|
|
1241
|
+
order: null,
|
|
1242
|
+
enabled: true,
|
|
1243
|
+
interpolate: false
|
|
1244
|
+
}]
|
|
1245
|
+
});
|
|
1246
|
+
}, [t]);
|
|
1247
|
+
const copyRendered = React.useCallback(() => {
|
|
1248
|
+
if (preview === void 0) return;
|
|
1249
|
+
const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
|
|
1250
|
+
if (clipboard === void 0) {
|
|
1251
|
+
setCopying("failed");
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
clipboard.writeText(preview.rendered).then(() => {
|
|
1255
|
+
setCopying("copied");
|
|
1256
|
+
window.setTimeout(() => setCopying("idle"), 1600);
|
|
1257
|
+
}, () => setCopying("failed"));
|
|
1258
|
+
}, [preview]);
|
|
1259
|
+
const shell = (content) => h("div", { className: "dspi-root" }, h("style", { "data-dsh-system-prompt-insertion": "styles" }, PANEL_CSS), h("div", { className: "dspi-scroll" }, h("div", { className: "dspi-content" }, ...content)));
|
|
1260
|
+
const heading = (extra, actions) => h("header", { className: "dspi-head" }, h("div", { className: "dspi-head-main" }, h("h1", { className: "dspi-title" }, t("title")), h("p", { className: "dspi-subtitle" }, t("description")), extra ?? null), actions ?? null);
|
|
1261
|
+
if (phase === "loading") return shell([
|
|
1262
|
+
heading(),
|
|
1263
|
+
h("div", {
|
|
1264
|
+
className: "dspi-loading",
|
|
1265
|
+
role: "status"
|
|
1266
|
+
}, h("span", {
|
|
1267
|
+
className: "dspi-spinner",
|
|
1268
|
+
"aria-hidden": "true"
|
|
1269
|
+
}), h("span", null, t("loading"))),
|
|
1270
|
+
h("div", {
|
|
1271
|
+
className: "dspi-skeleton",
|
|
1272
|
+
"aria-hidden": "true"
|
|
1273
|
+
}),
|
|
1274
|
+
h("div", {
|
|
1275
|
+
className: "dspi-skeleton",
|
|
1276
|
+
"aria-hidden": "true"
|
|
1277
|
+
})
|
|
1278
|
+
]);
|
|
1279
|
+
if (phase === "error" || draft === void 0 || payload === void 0) return shell([heading(), h("div", { className: "dspi-state" }, h("span", { className: "dspi-state-icon" }, glyph(h, "warning", 16)), h("div", { className: "dspi-state-body" }, h("p", { className: "dspi-state-title" }, t("loadFailed")), h("p", { className: "dspi-state-hint" }, withoutPrefix(error, t("loadFailed")) ?? t("loadFailed"))), h("button", {
|
|
1280
|
+
type: "button",
|
|
1281
|
+
className: "dspi-btn dspi-btn--primary",
|
|
1282
|
+
onClick: refresh
|
|
1283
|
+
}, t("retry")))]);
|
|
1284
|
+
const origin = originLabel(t, payload.origin);
|
|
1285
|
+
const registered = payload.registered.length;
|
|
1286
|
+
const showStoreWarning = payload.error !== void 0;
|
|
1287
|
+
const saveHint = format(t("saveHint"), { keys: saveKeys });
|
|
1288
|
+
return shell([
|
|
1289
|
+
heading(h("div", { className: "dspi-meta" }, h("span", null, `${t("pathLabel")} `, h("code", { title: payload.path }, payload.path)), h("span", {
|
|
1290
|
+
className: "dspi-origin",
|
|
1291
|
+
"data-origin": payload.origin
|
|
1292
|
+
}, origin), h("span", { title: t("tailBaseHint") }, format(t("tailBase"), { order: payload.orderBases.tailBase }))), h("div", { className: "dspi-head-actions" }, h("div", {
|
|
1293
|
+
className: "dspi-master",
|
|
1294
|
+
title: t("enabledHint")
|
|
1295
|
+
}, h("span", null, t("enabled")), h(Switch, {
|
|
1296
|
+
checked: draft.enabled,
|
|
1297
|
+
label: t("enabled"),
|
|
1298
|
+
title: t("enabledHint"),
|
|
1299
|
+
disabled: saving,
|
|
1300
|
+
onChange: toggleEnabled
|
|
1301
|
+
})))),
|
|
1302
|
+
error === void 0 ? null : h("div", {
|
|
1303
|
+
className: "dspi-banner dspi-banner--error",
|
|
1304
|
+
role: "alert"
|
|
1305
|
+
}, h("span", { className: "dspi-banner-icon" }, glyph(h, "warning")), h("div", { className: "dspi-banner-text" }, error), h("button", {
|
|
1306
|
+
type: "button",
|
|
1307
|
+
className: "dspi-icon-btn",
|
|
1308
|
+
"aria-label": t("dismiss"),
|
|
1309
|
+
title: t("dismiss"),
|
|
1310
|
+
onClick: () => setError(void 0)
|
|
1311
|
+
}, glyph(h, "close"))),
|
|
1312
|
+
showStoreWarning ? h("div", {
|
|
1313
|
+
className: "dspi-banner dspi-banner--warn",
|
|
1314
|
+
role: "status"
|
|
1315
|
+
}, h("span", { className: "dspi-banner-icon" }, glyph(h, "warning")), h("div", { className: "dspi-banner-text" }, format(t("warnStoreError"), { message: payload.error ?? "" }))) : null,
|
|
1316
|
+
h("section", { className: "dspi-section dspi-section--preview" }, h(PreviewPanel, {
|
|
1317
|
+
preview,
|
|
1318
|
+
previewing,
|
|
1319
|
+
previewError,
|
|
1320
|
+
dirty,
|
|
1321
|
+
copying,
|
|
1322
|
+
t,
|
|
1323
|
+
onCopy: copyRendered,
|
|
1324
|
+
onRefresh: runPreview
|
|
1325
|
+
})),
|
|
1326
|
+
h("section", { className: "dspi-section" }, h("div", { className: "dspi-section-head" }, h("h2", { className: "dspi-section-title" }, t("sectionsTitle")), h("span", { className: "dspi-section-meta" }, format(t("sectionsSummary"), {
|
|
1327
|
+
count: draft.sections.length,
|
|
1328
|
+
registered
|
|
1329
|
+
}))), h(SectionList, {
|
|
1330
|
+
sections: draft.sections,
|
|
1331
|
+
justAdded,
|
|
1332
|
+
t,
|
|
1333
|
+
onAdd: addSection,
|
|
1334
|
+
onChange: updateSection,
|
|
1335
|
+
onRemove: removeSection,
|
|
1336
|
+
onMove: moveSection
|
|
1337
|
+
}), draft.sections.length === 0 ? null : h("button", {
|
|
1338
|
+
type: "button",
|
|
1339
|
+
className: "dspi-add",
|
|
1340
|
+
onClick: addSection
|
|
1341
|
+
}, `+ ${t("add")}`), h("div", { className: "dspi-settings-actions" }, h("div", {
|
|
1342
|
+
className: "dspi-status",
|
|
1343
|
+
role: "status",
|
|
1344
|
+
"aria-live": "polite"
|
|
1345
|
+
}, h("span", {
|
|
1346
|
+
className: "dspi-status-dot",
|
|
1347
|
+
"data-dirty": dirty || saving ? "true" : "false",
|
|
1348
|
+
"aria-hidden": "true"
|
|
1349
|
+
}), h("span", null, saving ? t("saving") : dirty ? t("statusDirty") : t("statusSaved"))), dirty ? h("button", {
|
|
1350
|
+
type: "button",
|
|
1351
|
+
className: "dspi-btn",
|
|
1352
|
+
disabled: saving,
|
|
1353
|
+
onClick: () => setDraft(payload.state)
|
|
1354
|
+
}, t("revert")) : null, h("button", {
|
|
1355
|
+
type: "button",
|
|
1356
|
+
className: "dspi-btn dspi-btn--primary",
|
|
1357
|
+
onClick: save,
|
|
1358
|
+
disabled: !dirty || saving,
|
|
1359
|
+
title: saveHint
|
|
1360
|
+
}, saving ? t("saving") : t("save"))))
|
|
1361
|
+
]);
|
|
1362
|
+
}
|
|
1363
|
+
/** Locale + slot registration. */
|
|
1364
|
+
const inject = ["slots", "locale"];
|
|
1365
|
+
/**
|
|
1366
|
+
* Register this package's sidebar row and main-column panel.
|
|
1367
|
+
*
|
|
1368
|
+
* The two seats are declared by shell plugins, so each registration is wrapped
|
|
1369
|
+
* in `ctx.slots.inject`: the callback runs only after the owning entry declares
|
|
1370
|
+
* the seat, and a shell that cannot serve the seats leaves the panel simply
|
|
1371
|
+
* absent instead of failing boot.
|
|
1372
|
+
*
|
|
1373
|
+
* @param ctx - the client context carrying `slots` and `locale`.
|
|
1374
|
+
*/
|
|
1375
|
+
function apply(ctx) {
|
|
1376
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1377
|
+
zh,
|
|
1378
|
+
en
|
|
1379
|
+
}), "dsh-system-prompt-insertion: dictionaries");
|
|
1380
|
+
const t = ctx.locale.bind(NS);
|
|
1381
|
+
ctx.effect(() => {
|
|
1382
|
+
const offRow = ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
1383
|
+
name: "sidebar.panellist",
|
|
1384
|
+
id: PANEL_ID,
|
|
1385
|
+
order: 30,
|
|
1386
|
+
label: () => t("nav")
|
|
1387
|
+
}, PanelIcon));
|
|
1388
|
+
const offPanel = ctx.slots.inject("main", () => ctx.slots.register({
|
|
1389
|
+
name: "main",
|
|
1390
|
+
key: PANEL_ID,
|
|
1391
|
+
inject: () => ({ t })
|
|
1392
|
+
}, PromptInsertionPanel));
|
|
1393
|
+
return () => {
|
|
1394
|
+
offPanel();
|
|
1395
|
+
offRow();
|
|
1396
|
+
};
|
|
1397
|
+
}, "dsh-system-prompt-insertion: sidebar panel");
|
|
1398
|
+
}
|
|
1399
|
+
exports.NS = NS;
|
|
1400
|
+
exports.PANEL_CSS = PANEL_CSS;
|
|
1401
|
+
exports.PANEL_ID = PANEL_ID;
|
|
1402
|
+
exports.PANEL_ORDER = PANEL_ORDER;
|
|
1403
|
+
exports.POSITIONS = POSITIONS;
|
|
1404
|
+
exports.PanelIcon = PanelIcon;
|
|
1405
|
+
exports.PreviewPanel = PreviewPanel;
|
|
1406
|
+
exports.PromptInsertionPanel = PromptInsertionPanel;
|
|
1407
|
+
exports.SectionCard = SectionCard;
|
|
1408
|
+
exports.SectionList = SectionList;
|
|
1409
|
+
exports.Switch = Switch;
|
|
1410
|
+
exports.apply = apply;
|
|
1411
|
+
exports.inject = inject;
|
|
1412
|
+
return module.exports;
|
|
1413
|
+
}
|
|
1414
|
+
});
|