dsh-prompt-enhance 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/README.zh-CN.md +13 -3
- package/lib/client.js +206 -90
- package/lib/index.js +118 -84
- package/lib/index.js.map +3 -3
- package/package.json +4 -1
- package/src/client/EnhanceButton.tsx +20 -12
- package/src/client/ResultPanel.tsx +62 -4
- package/src/client/locales.ts +12 -2
- package/src/client/shortcut.ts +4 -1
- package/src/client/styles.ts +78 -37
- package/src/client/ui-state.ts +19 -1
- package/src/config.ts +19 -7
- package/src/enhance-command.ts +5 -23
- package/src/enhance-routes.ts +26 -71
- package/src/enhancer.ts +12 -2
- package/src/http.ts +10 -6
- package/src/loopback.ts +32 -7
- package/src/orchestrate.ts +89 -0
- package/src/prompts.ts +5 -3
- package/src/shared/protocol.ts +7 -1
package/README.md
CHANGED
|
@@ -110,7 +110,8 @@ Everything lives in the `prompt-enhance` settings namespace, edited from the web
|
|
|
110
110
|
| `maxInputChars` | `12000` | Input character cap; over-limit drafts are **rejected, never truncated** |
|
|
111
111
|
| `timeoutMs` | `60000` | End-to-end deadline of one call |
|
|
112
112
|
| `systemPrompt` | built-in strategy | Replace the whole enhancement strategy if you prefer your own |
|
|
113
|
-
| `shortcut` | `ctrl+alt+e` | Global shortcut spec (
|
|
113
|
+
| `shortcut` | `ctrl+alt+e` | Global shortcut spec (at least one modifier + one alphanumeric/function key — bare keys are ignored so normal typing can never be swallowed); empty disables it |
|
|
114
|
+
| `provider` + `model` values | — | Match the harness settings: each key under `llm-pi-ai.providers` (e.g. `zhipu`, `muyuu`) is a provider and each `models[].id` under it (e.g. `glm-5.3-flash`) is a model. Example pair: `provider: zhipu` + `model: glm-5.3-flash` |
|
|
114
115
|
|
|
115
116
|
**Model routing precedence:** explicit settings pair → the route recorded in the current session's request header → the harness-wide default model (`agent-default-model`). If none of them names a route (e.g. a fresh session with no default model), the plugin fails with an actionable message instead of guessing.
|
|
116
117
|
|
|
@@ -147,6 +148,15 @@ Set `systemPrompt` in the settings to replace all of this with your own strategy
|
|
|
147
148
|
|
|
148
149
|
Every failure surfaces inside the plugin's own panel; the composer draft is never modified by a failed call, so manual input continues undisturbed.
|
|
149
150
|
|
|
151
|
+
## Security model
|
|
152
|
+
|
|
153
|
+
The enhance route is served by your own dsh host and reachable **only from this machine**:
|
|
154
|
+
|
|
155
|
+
- **Socket fence** — requests from non-loopback addresses are refused (`127.0.0.1` / `::1` only). Note this means *any local process* can call the route; it carries no user authentication.
|
|
156
|
+
- **Host allowlist** — the route also validates the `Host` header against `localhost` / `127.0.0.1` / `[::1]`, which defeats DNS-rebinding (a rebound attacker domain keeps the loopback socket address but carries the attacker's hostname and is refused). Responses are `cache-control: no-store`.
|
|
157
|
+
- **Not for reverse-proxy exposure** — if you put dsh web behind a proxy that listens on the LAN, external callers appear as loopback to the route and the fence is moot. Do not expose a proxied host without adding your own authentication at the proxy.
|
|
158
|
+
- **Prompt-injection boundary** — the draft is framed between `<raw_prompt>` tags, literal closing tags inside the draft are neutralized, and the strategy prompt treats the framed text as pure data. Enhancements run with your own credentials and the result is only ever shown back to you.
|
|
159
|
+
|
|
150
160
|
## Architecture
|
|
151
161
|
|
|
152
162
|
```
|
|
@@ -183,12 +193,12 @@ Build outputs: `lib/index.js` (host half, ESM, package imports kept external) an
|
|
|
183
193
|
```bash
|
|
184
194
|
npm install
|
|
185
195
|
npm run typecheck # tsc --noEmit
|
|
186
|
-
npm test #
|
|
196
|
+
npm test # unit + real-http + component suites
|
|
187
197
|
npm run build # typecheck + both halves
|
|
188
198
|
npm run watch # esbuild watch for both halves
|
|
189
199
|
```
|
|
190
200
|
|
|
191
|
-
**Test coverage:** input validation
|
|
201
|
+
**Test coverage:** input validation, output normalization, the enhancer against stubbed `ctx.llm` streams, a real-`node:http` route suite (loopback Host fence, disconnect abort, error envelopes), the loopback/Host fence units, prompt framing, config resolution, the undo stack, shortcut parsing, client settings, and React component tests locking the enhance → apply → undo flow (guards, stale marking, diverged-draft undo semantics).
|
|
192
202
|
|
|
193
203
|
**Release process (maintainers):**
|
|
194
204
|
|
package/README.zh-CN.md
CHANGED
|
@@ -110,7 +110,8 @@ dsh plugin --profile web remove dsh-prompt-enhance
|
|
|
110
110
|
| `maxInputChars` | `12000` | 输入字数上限;超限**拒绝而不截断** |
|
|
111
111
|
| `timeoutMs` | `60000` | 单次调用的端到端超时 |
|
|
112
112
|
| `systemPrompt` | 内置策略 | 想用自己的增强策略时整体替换 |
|
|
113
|
-
| `shortcut` | `ctrl+alt+e` | 快捷键规格(
|
|
113
|
+
| `shortcut` | `ctrl+alt+e` | 快捷键规格(至少一个修饰键 + 单个字母/数字/功能键——纯裸键会被忽略,绝不会吞掉正常打字);留空禁用 |
|
|
114
|
+
| `provider` + `model` 取值 | — | 与 harness 设置一致:设置文件 `llm-pi-ai.providers` 下的键就是 provider(如 `zhipu`、`muyuu`),其 `models[].id` 就是 model(如 `glm-5.3-flash`)。示例:`provider: zhipu` + `model: glm-5.3-flash` |
|
|
114
115
|
|
|
115
116
|
**模型路由优先级:** 设置成对覆盖 → 当前会话请求头中记录的路由 → 全局默认模型(`agent-default-model`)。三者都指不出路由时(例如全新会话且无默认模型),插件给出可操作的报错而不是乱猜。
|
|
116
117
|
|
|
@@ -125,7 +126,7 @@ dsh plugin --profile web remove dsh-prompt-enhance
|
|
|
125
126
|
5. **验收标准** —— 写明怎样判断结果正确。
|
|
126
127
|
6. **边界条件** —— 列出边界情况与信息缺失时的做法。
|
|
127
128
|
|
|
128
|
-
硬性规则:精确保留原意(
|
|
129
|
+
硬性规则:精确保留原意(不删除、不篡改、不与用户信息冲突);绝不编造——未知细节插入显式占位符如 `(待补充:…)` / `(TBD: …)`;保持任务范围不变;**只输出**改写后的正文(无解释、无围栏、无客套);镜像输入语言;长度约为原文 1–3 倍;已经成形的提示词只做轻度润色而不注水。
|
|
129
130
|
|
|
130
131
|
在设置中填写 `systemPrompt` 即可整体替换为你的策略。
|
|
131
132
|
|
|
@@ -147,6 +148,15 @@ dsh plugin --profile web remove dsh-prompt-enhance
|
|
|
147
148
|
|
|
148
149
|
所有失败只出现在插件自己的面板里;失败的调用绝不修改输入框草稿,手动输入不受任何干扰。
|
|
149
150
|
|
|
151
|
+
## 安全模型
|
|
152
|
+
|
|
153
|
+
增强路由由你自己的 dsh 宿主提供服务,**仅限本机访问**:
|
|
154
|
+
|
|
155
|
+
- **套接字栅栏** —— 非回环地址(`127.0.0.1` / `::1` 之外)的请求一律拒绝。注意这意味着*本机任意进程*都可以调用该路由;路由本身不带用户鉴权。
|
|
156
|
+
- **Host 白名单** —— 路由同时校验 `Host` 头是否为 `localhost` / `127.0.0.1` / `[::1]`,可防御 DNS 重绑定(重绑定的攻击者域名虽然落在回环套接字上,但携带的是攻击者主机名,会被拒绝)。响应均为 `cache-control: no-store`。
|
|
157
|
+
- **不要经反向代理暴露** —— 若把 dsh web 放在监听局域网的代理后面,外部调用者在路由看来就是回环地址,栅栏形同虚设。除非在代理层自行加鉴权,否则不要暴露。
|
|
158
|
+
- **提示注入边界** —— 草稿被框在 `<raw_prompt>` 标签之间,草稿内的字面闭合标签会被中和,策略提示词把框内文本视为纯数据。增强只使用你自己的凭据,结果也只回显给你本人。
|
|
159
|
+
|
|
150
160
|
## 架构
|
|
151
161
|
|
|
152
162
|
```
|
|
@@ -182,7 +192,7 @@ src/
|
|
|
182
192
|
```bash
|
|
183
193
|
npm install
|
|
184
194
|
npm run typecheck # tsc --noEmit
|
|
185
|
-
npm test #
|
|
195
|
+
npm test # 单元 + 真实 HTTP + 组件测试套件
|
|
186
196
|
npm run build # 类型检查 + 双半区构建
|
|
187
197
|
npm run watch # esbuild 监听构建双半区
|
|
188
198
|
```
|
package/lib/client.js
CHANGED
|
@@ -100,19 +100,56 @@ async function requestEnhance(body, signal) {
|
|
|
100
100
|
var import_react = require("react");
|
|
101
101
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
102
102
|
var retryable = (code) => code === "upstream" || code === "timeout" || code === "internal";
|
|
103
|
+
function localizedErrorMessage(t, code) {
|
|
104
|
+
switch (code) {
|
|
105
|
+
case "rejected":
|
|
106
|
+
return t("error.rejected");
|
|
107
|
+
case "timeout":
|
|
108
|
+
return t("error.timeout");
|
|
109
|
+
case "unconfigured":
|
|
110
|
+
return t("error.unconfigured");
|
|
111
|
+
case "upstream":
|
|
112
|
+
return t("error.upstream");
|
|
113
|
+
default:
|
|
114
|
+
return t("error.internal");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
103
117
|
function ResultPanel(props) {
|
|
104
118
|
const { state, t, onApply, onCancel, onRetry } = props;
|
|
105
119
|
const [copied, setCopied] = (0, import_react.useState)("idle");
|
|
120
|
+
const panelRef = (0, import_react.useRef)(null);
|
|
106
121
|
(0, import_react.useEffect)(() => {
|
|
107
122
|
const onKeyDown = (event) => {
|
|
123
|
+
if (event.isComposing) return;
|
|
108
124
|
if (event.key === "Escape" && !event.defaultPrevented) {
|
|
109
125
|
event.preventDefault();
|
|
110
126
|
onCancel();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (event.key === "Tab") {
|
|
130
|
+
const panel = panelRef.current;
|
|
131
|
+
if (panel === null) return;
|
|
132
|
+
const focusables = panel.querySelectorAll('button:not([disabled]), [href], input, [tabindex]:not([tabindex="-1"])');
|
|
133
|
+
const first = focusables[0];
|
|
134
|
+
const last = focusables[focusables.length - 1];
|
|
135
|
+
if (first === void 0 || last === void 0) return;
|
|
136
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
last.focus();
|
|
139
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
first.focus();
|
|
142
|
+
}
|
|
111
143
|
}
|
|
112
144
|
};
|
|
113
145
|
document.addEventListener("keydown", onKeyDown);
|
|
114
146
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
115
147
|
}, [onCancel]);
|
|
148
|
+
(0, import_react.useEffect)(() => {
|
|
149
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
150
|
+
panelRef.current?.focus();
|
|
151
|
+
return () => previous?.focus();
|
|
152
|
+
}, []);
|
|
116
153
|
const copy = (0, import_react.useCallback)(() => {
|
|
117
154
|
if (state.result === void 0) return;
|
|
118
155
|
navigator.clipboard.writeText(state.result.text).then(() => setCopied("ok"), () => setCopied("failed"));
|
|
@@ -120,45 +157,63 @@ function ResultPanel(props) {
|
|
|
120
157
|
const formatMs = (ms) => ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
121
158
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-overlay", onMouseDown: (event) => {
|
|
122
159
|
if (event.target === event.currentTarget) onCancel();
|
|
123
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
161
|
+
"section",
|
|
162
|
+
{
|
|
163
|
+
ref: panelRef,
|
|
164
|
+
className: "dsh-pe-panel",
|
|
165
|
+
role: "dialog",
|
|
166
|
+
"aria-modal": "true",
|
|
167
|
+
"aria-label": t("panel.title"),
|
|
168
|
+
tabIndex: -1,
|
|
169
|
+
children: [
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "dsh-pe-head", children: [
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
172
|
+
"\u2728 ",
|
|
173
|
+
t("panel.title")
|
|
174
|
+
] }),
|
|
175
|
+
state.phase === "result" && state.result !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-pe-meta", children: [
|
|
176
|
+
t("panel.model", { provider: state.result.provider, model: state.result.model }),
|
|
177
|
+
" \xB7 ",
|
|
178
|
+
t("panel.elapsed", { ms: formatMs(state.result.elapsedMs) })
|
|
179
|
+
] }),
|
|
180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-close", "aria-label": t("panel.close"), onClick: onCancel, children: "\u2715" })
|
|
181
|
+
] }),
|
|
182
|
+
state.phase === "loading" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-loading", children: [
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-spin", "aria-hidden": true }),
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: t("panel.loading") }),
|
|
185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-hint", children: t("panel.loading.hint") })
|
|
186
|
+
] }),
|
|
187
|
+
state.phase === "error" && state.error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-error", children: [
|
|
188
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: state.error.localized ?? localizedErrorMessage(t, state.error.code) }),
|
|
189
|
+
state.error.localized !== state.error.message && state.error.message !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-error-detail", children: state.error.message })
|
|
190
|
+
] }),
|
|
191
|
+
state.phase === "result" && state.result !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-body", children: [
|
|
192
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-col", children: [
|
|
193
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-col-title", children: t("panel.original") }),
|
|
194
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "dsh-pe-col-text", children: state.original })
|
|
195
|
+
] }),
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-col", children: [
|
|
197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-col-title", children: t("panel.enhanced") }),
|
|
198
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "dsh-pe-col-text", children: state.result.text })
|
|
199
|
+
] })
|
|
200
|
+
] }),
|
|
201
|
+
state.phase === "result" && state.stale && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-stale", children: [
|
|
202
|
+
"\u26A0 ",
|
|
203
|
+
t("panel.stale.warn")
|
|
204
|
+
] }),
|
|
205
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "dsh-pe-foot", children: [
|
|
206
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-pe-grow" }),
|
|
207
|
+
state.phase === "error" && retryable(state.error?.code) && onRetry !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-btn-action plain", onClick: onRetry, children: t("panel.retry") }),
|
|
208
|
+
state.phase === "result" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-btn-action plain", onClick: copy, children: copied === "ok" ? t("panel.copied") : copied === "failed" ? t("panel.copyFailed") : t("panel.copy") }),
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-btn-action primary", onClick: onApply, children: t("panel.apply") })
|
|
211
|
+
] }),
|
|
212
|
+
state.phase !== "result" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-btn-action plain", onClick: onCancel, children: state.phase === "loading" ? t("panel.cancel") : t("panel.close") })
|
|
213
|
+
] })
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
) });
|
|
162
217
|
}
|
|
163
218
|
|
|
164
219
|
// src/client/undo-stack.ts
|
|
@@ -244,6 +299,11 @@ function openError(sessionId, original, error) {
|
|
|
244
299
|
panelState = { sessionId, phase: "error", original, error };
|
|
245
300
|
notify();
|
|
246
301
|
}
|
|
302
|
+
function setStale(sessionId, stale) {
|
|
303
|
+
if (panelState?.sessionId !== sessionId || panelState.phase !== "result" || panelState.stale === stale) return;
|
|
304
|
+
panelState = { ...panelState, stale };
|
|
305
|
+
notify();
|
|
306
|
+
}
|
|
247
307
|
function closePanel() {
|
|
248
308
|
panelState?.abort?.();
|
|
249
309
|
if (panelState === void 0) return;
|
|
@@ -256,7 +316,10 @@ function registerSession(sessionId, entry) {
|
|
|
256
316
|
notify();
|
|
257
317
|
return () => {
|
|
258
318
|
if (sessions.get(sessionId) === entry) sessions.delete(sessionId);
|
|
259
|
-
if (lastMountedSession === sessionId)
|
|
319
|
+
if (lastMountedSession === sessionId) {
|
|
320
|
+
const keys = [...sessions.keys()];
|
|
321
|
+
lastMountedSession = keys[keys.length - 1];
|
|
322
|
+
}
|
|
260
323
|
if (panelState?.sessionId === sessionId) closePanel();
|
|
261
324
|
undoStore.clear(sessionId);
|
|
262
325
|
};
|
|
@@ -324,30 +387,27 @@ function EnhanceButton(props) {
|
|
|
324
387
|
const busy = panel !== void 0 && panel.sessionId === sessionId && panel.phase === "loading";
|
|
325
388
|
const anyBusy = panel !== void 0 && panel.phase === "loading";
|
|
326
389
|
const start = (0, import_react2.useCallback)(() => {
|
|
327
|
-
if (anyBusy)
|
|
328
|
-
openError(sessionId, draft, { code: "rejected", message: t("error.busy") });
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
390
|
+
if (anyBusy) return;
|
|
331
391
|
if (!settings.enabled) {
|
|
332
|
-
openError(sessionId, draft, { code: "rejected", message: t("error.disabled") });
|
|
392
|
+
openError(sessionId, draft, { code: "rejected", message: t("error.disabled"), localized: t("error.disabled") });
|
|
333
393
|
return;
|
|
334
394
|
}
|
|
335
395
|
if (imageCount > 0 && draft.trim() === "") {
|
|
336
|
-
openError(sessionId, draft, { code: "rejected", message: t("error.imagesOnly") });
|
|
396
|
+
openError(sessionId, draft, { code: "rejected", message: t("error.imagesOnly"), localized: t("error.imagesOnly") });
|
|
337
397
|
return;
|
|
338
398
|
}
|
|
339
399
|
const check = checkInputText(draft, settings.maxInputChars);
|
|
340
400
|
if (!check.ok) {
|
|
341
401
|
const message = check.code === "empty" ? t("error.empty") : t("error.tooLong", { count: check.count, max: check.max });
|
|
342
|
-
openError(sessionId, draft, { code: "rejected", message });
|
|
402
|
+
openError(sessionId, draft, { code: "rejected", message, localized: message });
|
|
343
403
|
return;
|
|
344
404
|
}
|
|
345
405
|
if (occurrenceCount > 0) {
|
|
346
|
-
openError(sessionId, draft, { code: "rejected", message: t("error.occurrences") });
|
|
406
|
+
openError(sessionId, draft, { code: "rejected", message: t("error.occurrences"), localized: t("error.occurrences") });
|
|
347
407
|
return;
|
|
348
408
|
}
|
|
349
409
|
if (phase !== "plain") {
|
|
350
|
-
openError(sessionId, draft, { code: "rejected", message: t("error.phase") });
|
|
410
|
+
openError(sessionId, draft, { code: "rejected", message: t("error.phase"), localized: t("error.phase") });
|
|
351
411
|
return;
|
|
352
412
|
}
|
|
353
413
|
const controller = new AbortController();
|
|
@@ -369,12 +429,17 @@ function EnhanceButton(props) {
|
|
|
369
429
|
};
|
|
370
430
|
return registerSession(sessionId, entry);
|
|
371
431
|
}, [sessionId]);
|
|
432
|
+
(0, import_react2.useEffect)(() => {
|
|
433
|
+
if (panel !== void 0 && panel.sessionId === sessionId && panel.phase === "result") {
|
|
434
|
+
setStale(sessionId, draft !== panel.original);
|
|
435
|
+
}
|
|
436
|
+
}, [draft, panel, sessionId]);
|
|
372
437
|
const apply2 = (0, import_react2.useCallback)(() => {
|
|
373
438
|
if (panel === void 0 || panel.phase !== "result" || panel.result === void 0) return;
|
|
374
|
-
pushUndo(sessionId, { original:
|
|
439
|
+
pushUndo(sessionId, { original: draft, applied: panel.result.text });
|
|
375
440
|
inputActions.setDraft(panel.result.text);
|
|
376
441
|
closePanel();
|
|
377
|
-
}, [inputActions, panel, sessionId]);
|
|
442
|
+
}, [draft, inputActions, panel, sessionId]);
|
|
378
443
|
if (!settings.enabled) return null;
|
|
379
444
|
const owned = panel !== void 0 && panel.sessionId === sessionId ? panel : void 0;
|
|
380
445
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
@@ -451,6 +516,7 @@ var zh = {
|
|
|
451
516
|
"panel.copyFailed": "\u590D\u5236\u5931\u8D25",
|
|
452
517
|
"panel.close": "\u5173\u95ED",
|
|
453
518
|
"panel.retry": "\u91CD\u8BD5",
|
|
519
|
+
"panel.stale.warn": "\u8349\u7A3F\u5728\u589E\u5F3A\u671F\u95F4\u6709\u6539\u52A8\u2014\u2014\u589E\u5F3A\u7ED3\u679C\u57FA\u4E8E\u589E\u5F3A\u524D\u7684\u6587\u672C\u3002\u56DE\u586B\u5C06\u8986\u76D6\u4F60\u7684\u6700\u65B0\u7F16\u8F91(\u64A4\u9500\u53EF\u6062\u590D\u56DE\u586B\u524D\u7684\u8349\u7A3F)\u3002",
|
|
454
520
|
"panel.model": "\u6A21\u578B\uFF1A{provider} / {model}",
|
|
455
521
|
"panel.elapsed": "\u8017\u65F6 {ms}",
|
|
456
522
|
"undo.applied": "\u5DF2\u7528\u589E\u5F3A\u7ED3\u679C\u66FF\u6362\u539F\u63D0\u793A\u8BCD",
|
|
@@ -460,9 +526,13 @@ var zh = {
|
|
|
460
526
|
"error.tooLong": "\u5185\u5BB9\u5171 {count} \u5B57\uFF0C\u8D85\u8FC7 {max} \u5B57\u4E0A\u9650\u3002\u4E3A\u907F\u514D\u6539\u53D8\u539F\u610F\u4E0D\u4F1A\u81EA\u52A8\u622A\u65AD\uFF0C\u8BF7\u7CBE\u7B80\u540E\u518D\u8BD5\u3002",
|
|
461
527
|
"error.imagesOnly": "\u5F53\u524D\u53EA\u9644\u52A0\u4E86\u56FE\u7247\uFF0C\u4EC5\u652F\u6301\u589E\u5F3A\u6587\u672C\u5185\u5BB9\u3002",
|
|
462
528
|
"error.occurrences": "\u8F93\u5165\u5185\u5BB9\u5305\u542B\u547D\u4EE4\u6216\u6587\u4EF6\u5F15\u7528\uFF0C\u6682\u4E0D\u652F\u6301\u589E\u5F3A\uFF1B\u8BF7\u5148\u79FB\u9664\u540E\u518D\u8BD5\u3002",
|
|
463
|
-
"error.busy": "\u6B63\u5728\u589E\u5F3A\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u6216\u5148\u53D6\u6D88\u3002",
|
|
464
529
|
"error.phase": "\u5F53\u524D\u8F93\u5165\u6B63\u88AB\u5360\u7528\uFF08\u63D0\u4EA4\u4E2D\uFF09\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
|
|
465
|
-
"error.disabled": "\u63D0\u793A\u8BCD\u589E\u5F3A\u5DF2\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u5173\u95ED\u3002"
|
|
530
|
+
"error.disabled": "\u63D0\u793A\u8BCD\u589E\u5F3A\u5DF2\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u5173\u95ED\u3002",
|
|
531
|
+
"error.rejected": "\u589E\u5F3A\u8BF7\u6C42\u88AB\u62D2\u7EDD\u3002",
|
|
532
|
+
"error.timeout": "\u589E\u5F3A\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5\uFF1B\u539F\u8F93\u5165\u672A\u6539\u52A8\u3002",
|
|
533
|
+
"error.unconfigured": "\u5C1A\u672A\u786E\u5B9A\u589E\u5F3A\u7528\u7684\u6A21\u578B\uFF1A\u8BF7\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u6210\u5BF9\u586B\u5199 provider/model\u3002",
|
|
534
|
+
"error.upstream": "\u6A21\u578B\u670D\u52A1\u8FD4\u56DE\u9519\u8BEF\uFF0C\u8BF7\u91CD\u8BD5\uFF1B\u539F\u8F93\u5165\u672A\u6539\u52A8\u3002",
|
|
535
|
+
"error.internal": "\u589E\u5F3A\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\uFF1B\u539F\u8F93\u5165\u672A\u6539\u52A8\u3002"
|
|
466
536
|
};
|
|
467
537
|
var en = {
|
|
468
538
|
"button.title": "Enhance prompt (rewrite into a structured prompt)",
|
|
@@ -479,6 +549,7 @@ var en = {
|
|
|
479
549
|
"panel.copyFailed": "Copy failed",
|
|
480
550
|
"panel.close": "Close",
|
|
481
551
|
"panel.retry": "Retry",
|
|
552
|
+
"panel.stale.warn": "The draft changed while enhancing \u2014 the result is based on the pre-enhance text. Applying will overwrite your latest edits (undo restores the draft as it was before applying).",
|
|
482
553
|
"panel.model": "Model: {provider} / {model}",
|
|
483
554
|
"panel.elapsed": "{ms} elapsed",
|
|
484
555
|
"undo.applied": "Original prompt replaced by the enhanced version",
|
|
@@ -488,9 +559,13 @@ var en = {
|
|
|
488
559
|
"error.tooLong": "The draft is {count} characters, above the {max} cap. It is never auto-truncated (that would change your meaning) \u2014 please shorten it.",
|
|
489
560
|
"error.imagesOnly": "Only images are attached; prompt enhance supports text only.",
|
|
490
561
|
"error.occurrences": "The draft contains commands or file references, which are not supported yet \u2014 remove them first.",
|
|
491
|
-
"error.busy": "An enhancement is already running \u2014 wait or cancel it first.",
|
|
492
562
|
"error.phase": "The input box is busy (submitting) \u2014 try again in a moment.",
|
|
493
|
-
"error.disabled": "Prompt enhance is disabled in the plugin settings."
|
|
563
|
+
"error.disabled": "Prompt enhance is disabled in the plugin settings.",
|
|
564
|
+
"error.rejected": "The enhance request was rejected.",
|
|
565
|
+
"error.timeout": "The enhancement timed out. Retry; your draft is untouched.",
|
|
566
|
+
"error.unconfigured": "No model resolved for the enhancement: pair provider/model in the plugin settings.",
|
|
567
|
+
"error.upstream": "The model provider returned an error. Retry; your draft is untouched.",
|
|
568
|
+
"error.internal": "Enhancement failed. Retry; your draft is untouched."
|
|
494
569
|
};
|
|
495
570
|
var dictionaries = { zh, en };
|
|
496
571
|
var NS = "prompt-enhance";
|
|
@@ -498,32 +573,10 @@ var NS = "prompt-enhance";
|
|
|
498
573
|
// src/client/styles.ts
|
|
499
574
|
var STYLE_ID = "dsh-prompt-enhance-styles";
|
|
500
575
|
var CSS = `
|
|
501
|
-
.dsh-pe-btn {
|
|
502
|
-
display: inline-flex;
|
|
503
|
-
align-items: center;
|
|
504
|
-
gap: 4px;
|
|
505
|
-
height: 28px;
|
|
506
|
-
padding: 0 8px;
|
|
507
|
-
border: none;
|
|
508
|
-
border-radius: 6px;
|
|
509
|
-
background: transparent;
|
|
510
|
-
color: inherit;
|
|
511
|
-
opacity: 0.75;
|
|
512
|
-
font-size: 12px;
|
|
513
|
-
line-height: 1;
|
|
514
|
-
cursor: pointer;
|
|
515
|
-
white-space: nowrap;
|
|
516
|
-
}
|
|
517
|
-
.dsh-pe-btn:hover:not(:disabled) { opacity: 1; background: rgba(127, 127, 127, 0.18); }
|
|
518
|
-
.dsh-pe-btn:disabled { opacity: 0.45; cursor: default; }
|
|
519
|
-
.dsh-pe-btn .dsh-pe-btn-icon { font-size: 14px; }
|
|
520
|
-
.dsh-pe-btn.is-busy .dsh-pe-btn-icon { animation: dsh-pe-spin 1s linear infinite; }
|
|
521
|
-
@keyframes dsh-pe-spin { to { transform: rotate(360deg); } }
|
|
522
|
-
|
|
523
576
|
.dsh-pe-overlay {
|
|
524
577
|
position: fixed;
|
|
525
578
|
inset: 0;
|
|
526
|
-
z-index: 1000;
|
|
579
|
+
z-index: var(--dsh-pe-z-index, 1000);
|
|
527
580
|
display: flex;
|
|
528
581
|
align-items: flex-end;
|
|
529
582
|
justify-content: center;
|
|
@@ -531,23 +584,48 @@ var CSS = `
|
|
|
531
584
|
padding: 24px 16px;
|
|
532
585
|
}
|
|
533
586
|
.dsh-pe-panel {
|
|
587
|
+
--pe-bg: #1f2127;
|
|
588
|
+
--pe-fg: #e6e8ee;
|
|
589
|
+
--pe-fg-dim: rgba(230, 232, 238, 0.55);
|
|
590
|
+
--pe-border: rgba(127, 127, 127, 0.35);
|
|
591
|
+
--pe-separator: rgba(127, 127, 127, 0.25);
|
|
592
|
+
--pe-hover: rgba(127, 127, 127, 0.18);
|
|
593
|
+
--pe-danger: #ffb4a8;
|
|
594
|
+
--pe-warn-fg: #e8c07d;
|
|
595
|
+
--pe-warn-border: rgba(230, 158, 60, 0.35);
|
|
596
|
+
--pe-warn-bg: rgba(230, 158, 60, 0.12);
|
|
534
597
|
display: flex;
|
|
535
598
|
flex-direction: column;
|
|
536
599
|
width: min(880px, 100%);
|
|
537
600
|
max-height: min(70vh, 640px);
|
|
538
601
|
border-radius: 12px;
|
|
539
|
-
border: 1px solid
|
|
540
|
-
background:
|
|
541
|
-
color:
|
|
602
|
+
border: 1px solid var(--pe-border);
|
|
603
|
+
background: var(--pe-bg);
|
|
604
|
+
color: var(--pe-fg);
|
|
542
605
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
|
|
543
606
|
overflow: hidden;
|
|
607
|
+
outline: none;
|
|
608
|
+
}
|
|
609
|
+
@media (prefers-color-scheme: light) {
|
|
610
|
+
.dsh-pe-panel {
|
|
611
|
+
--pe-bg: #ffffff;
|
|
612
|
+
--pe-fg: #1c1e24;
|
|
613
|
+
--pe-fg-dim: rgba(28, 30, 36, 0.55);
|
|
614
|
+
--pe-border: rgba(0, 0, 0, 0.18);
|
|
615
|
+
--pe-separator: rgba(0, 0, 0, 0.12);
|
|
616
|
+
--pe-hover: rgba(0, 0, 0, 0.08);
|
|
617
|
+
--pe-danger: #a63a2e;
|
|
618
|
+
--pe-warn-fg: #8a5a00;
|
|
619
|
+
--pe-warn-border: rgba(176, 122, 0, 0.4);
|
|
620
|
+
--pe-warn-bg: rgba(230, 158, 60, 0.15);
|
|
621
|
+
}
|
|
544
622
|
}
|
|
545
623
|
.dsh-pe-head {
|
|
546
624
|
display: flex;
|
|
547
625
|
align-items: center;
|
|
548
626
|
gap: 8px;
|
|
549
627
|
padding: 10px 14px;
|
|
550
|
-
border-bottom: 1px solid
|
|
628
|
+
border-bottom: 1px solid var(--pe-separator);
|
|
551
629
|
font-size: 13px;
|
|
552
630
|
font-weight: 600;
|
|
553
631
|
}
|
|
@@ -556,7 +634,7 @@ var CSS = `
|
|
|
556
634
|
border: none; background: transparent; color: inherit; opacity: 0.6;
|
|
557
635
|
font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 4px;
|
|
558
636
|
}
|
|
559
|
-
.dsh-pe-close:hover { opacity: 1; background:
|
|
637
|
+
.dsh-pe-close:hover { opacity: 1; background: var(--pe-hover); }
|
|
560
638
|
.dsh-pe-body { display: flex; flex: 1; min-height: 0; }
|
|
561
639
|
.dsh-pe-col {
|
|
562
640
|
flex: 1 1 0;
|
|
@@ -564,7 +642,7 @@ var CSS = `
|
|
|
564
642
|
display: flex;
|
|
565
643
|
flex-direction: column;
|
|
566
644
|
}
|
|
567
|
-
.dsh-pe-col + .dsh-pe-col { border-left: 1px solid
|
|
645
|
+
.dsh-pe-col + .dsh-pe-col { border-left: 1px solid var(--pe-separator); }
|
|
568
646
|
.dsh-pe-col-title {
|
|
569
647
|
padding: 8px 14px 6px;
|
|
570
648
|
font-size: 11px;
|
|
@@ -586,9 +664,15 @@ var CSS = `
|
|
|
586
664
|
padding: 20px 16px;
|
|
587
665
|
font-size: 13px;
|
|
588
666
|
line-height: 1.6;
|
|
589
|
-
color:
|
|
667
|
+
color: var(--pe-danger);
|
|
590
668
|
white-space: pre-wrap;
|
|
591
669
|
}
|
|
670
|
+
.dsh-pe-error .dsh-pe-error-detail {
|
|
671
|
+
margin-top: 8px;
|
|
672
|
+
font-size: 12px;
|
|
673
|
+
opacity: 0.75;
|
|
674
|
+
color: inherit;
|
|
675
|
+
}
|
|
592
676
|
.dsh-pe-loading {
|
|
593
677
|
display: flex; flex-direction: column; gap: 8px;
|
|
594
678
|
align-items: center; justify-content: center;
|
|
@@ -597,7 +681,7 @@ var CSS = `
|
|
|
597
681
|
.dsh-pe-loading .dsh-pe-hint { font-size: 11px; opacity: 0.55; }
|
|
598
682
|
.dsh-pe-spin {
|
|
599
683
|
width: 22px; height: 22px; border-radius: 50%;
|
|
600
|
-
border: 2px solid
|
|
684
|
+
border: 2px solid var(--pe-separator); border-top-color: #6ea8ff;
|
|
601
685
|
animation: dsh-pe-spin 0.9s linear infinite;
|
|
602
686
|
}
|
|
603
687
|
.dsh-pe-foot {
|
|
@@ -605,7 +689,7 @@ var CSS = `
|
|
|
605
689
|
align-items: center;
|
|
606
690
|
gap: 8px;
|
|
607
691
|
padding: 10px 14px;
|
|
608
|
-
border-top: 1px solid
|
|
692
|
+
border-top: 1px solid var(--pe-separator);
|
|
609
693
|
}
|
|
610
694
|
.dsh-pe-foot .dsh-pe-grow { flex: 1; }
|
|
611
695
|
.dsh-pe-btn-action {
|
|
@@ -614,8 +698,39 @@ var CSS = `
|
|
|
614
698
|
}
|
|
615
699
|
.dsh-pe-btn-action.primary { background: #3f76e1; color: #fff; }
|
|
616
700
|
.dsh-pe-btn-action.primary:hover { background: #4d82ec; }
|
|
617
|
-
.dsh-pe-btn-action.plain { background:
|
|
618
|
-
.dsh-pe-btn-action.plain:hover { background: rgba(127,127,127,0.3); }
|
|
701
|
+
.dsh-pe-btn-action.plain { background: var(--pe-hover); color: inherit; }
|
|
702
|
+
.dsh-pe-btn-action.plain:hover { background: rgba(127, 127, 127, 0.3); }
|
|
703
|
+
|
|
704
|
+
.dsh-pe-stale {
|
|
705
|
+
padding: 8px 14px;
|
|
706
|
+
font-size: 12px;
|
|
707
|
+
line-height: 1.5;
|
|
708
|
+
border-top: 1px solid var(--pe-warn-border);
|
|
709
|
+
background: var(--pe-warn-bg);
|
|
710
|
+
color: var(--pe-warn-fg);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.dsh-pe-btn {
|
|
714
|
+
display: inline-flex;
|
|
715
|
+
align-items: center;
|
|
716
|
+
gap: 4px;
|
|
717
|
+
height: 28px;
|
|
718
|
+
padding: 0 8px;
|
|
719
|
+
border: none;
|
|
720
|
+
border-radius: 6px;
|
|
721
|
+
background: transparent;
|
|
722
|
+
color: inherit;
|
|
723
|
+
opacity: 0.75;
|
|
724
|
+
font-size: 12px;
|
|
725
|
+
line-height: 1;
|
|
726
|
+
cursor: pointer;
|
|
727
|
+
white-space: nowrap;
|
|
728
|
+
}
|
|
729
|
+
.dsh-pe-btn:hover:not(:disabled) { opacity: 1; background: var(--pe-hover); }
|
|
730
|
+
.dsh-pe-btn:disabled { opacity: 0.45; cursor: default; }
|
|
731
|
+
.dsh-pe-btn .dsh-pe-btn-icon { font-size: 14px; }
|
|
732
|
+
.dsh-pe-btn.is-busy .dsh-pe-btn-icon { animation: dsh-pe-spin 1s linear infinite; }
|
|
733
|
+
@keyframes dsh-pe-spin { to { transform: rotate(360deg); } }
|
|
619
734
|
|
|
620
735
|
.dsh-pe-undo {
|
|
621
736
|
display: flex;
|
|
@@ -628,7 +743,7 @@ var CSS = `
|
|
|
628
743
|
}
|
|
629
744
|
.dsh-pe-undo .dsh-pe-undo-check { color: #7ed491; }
|
|
630
745
|
.dsh-pe-undo-link {
|
|
631
|
-
border: none; background:
|
|
746
|
+
border: none; background: var(--pe-hover); color: inherit;
|
|
632
747
|
border-radius: 5px; padding: 3px 10px; font-size: 12px; cursor: pointer;
|
|
633
748
|
}
|
|
634
749
|
.dsh-pe-undo-link:hover { background: rgba(127,127,127,0.3); }
|
|
@@ -673,6 +788,7 @@ function parseShortcut(spec) {
|
|
|
673
788
|
if (modifier === void 0) return null;
|
|
674
789
|
modifiers[modifier] = true;
|
|
675
790
|
}
|
|
791
|
+
if (!modifiers.ctrl && !modifiers.alt && !modifiers.shift && !modifiers.meta) return null;
|
|
676
792
|
return { ...modifiers, key: last };
|
|
677
793
|
}
|
|
678
794
|
function matchesShortcut(event, combo) {
|