dsh-prompt-enhance 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,750 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-prompt-enhance",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ "use strict";
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
+
27
+ // src/client/index.tsx
28
+ var index_exports = {};
29
+ __export(index_exports, {
30
+ NS: () => NS,
31
+ apply: () => apply,
32
+ inject: () => inject
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/client/EnhanceButton.tsx
37
+ var import_react2 = require("react");
38
+ var import_react_dom = require("react-dom");
39
+
40
+ // src/shared/validate.ts
41
+ var INVISIBLE_CHARS = /[\u200B-\u200D\uFEFF\u202A-\u202E\u2066-\u2069]/g;
42
+ function checkInputText(text, maxChars) {
43
+ const stripped = text.replace(INVISIBLE_CHARS, "");
44
+ if (stripped.trim().length === 0) {
45
+ return { ok: false, code: "empty" };
46
+ }
47
+ if (text.length > maxChars) {
48
+ return { ok: false, code: "too-long", count: text.length, max: maxChars };
49
+ }
50
+ return { ok: true };
51
+ }
52
+
53
+ // src/shared/protocol.ts
54
+ var ENHANCE_ENDPOINT = "/prompt-enhance/enhance";
55
+
56
+ // src/client/enhance-client.ts
57
+ var EnhanceClientError = class extends Error {
58
+ constructor(detail) {
59
+ super(detail.message);
60
+ this.detail = detail;
61
+ }
62
+ };
63
+ async function readEnvelope(response) {
64
+ let parsed;
65
+ try {
66
+ parsed = await response.json();
67
+ } catch {
68
+ throw new EnhanceClientError({ code: "internal", message: "\u5BBF\u4E3B\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\u3002" });
69
+ }
70
+ const envelope = parsed;
71
+ if (envelope !== null && typeof envelope === "object" && envelope.ok === true) {
72
+ const value = envelope.value;
73
+ if (value !== void 0 && typeof value.text === "string" && value.text !== "") return value;
74
+ }
75
+ if (envelope !== null && typeof envelope === "object" && envelope.error !== void 0 && typeof envelope.error.message === "string") {
76
+ throw new EnhanceClientError(envelope.error);
77
+ }
78
+ throw new EnhanceClientError({ code: "internal", message: `\u5BBF\u4E3B\u670D\u52A1\u8FD4\u56DE\u5F02\u5E38\uFF08HTTP ${response.status}\uFF09\u3002` });
79
+ }
80
+ async function requestEnhance(body, signal) {
81
+ let response;
82
+ try {
83
+ response = await fetch(ENHANCE_ENDPOINT, {
84
+ method: "POST",
85
+ headers: { "content-type": "application/json" },
86
+ body: JSON.stringify(body),
87
+ signal
88
+ });
89
+ } catch (error) {
90
+ if (signal?.aborted) {
91
+ throw new EnhanceClientError({ code: "internal", message: "\u5DF2\u53D6\u6D88\u589E\u5F3A\uFF1B\u539F\u8F93\u5165\u672A\u6539\u52A8\u3002" });
92
+ }
93
+ void error;
94
+ throw new EnhanceClientError({ code: "internal", message: "\u65E0\u6CD5\u8FDE\u63A5\u5BBF\u4E3B\u670D\u52A1\uFF0C\u8BF7\u786E\u8BA4 dsh web \u6B63\u5728\u8FD0\u884C\u540E\u91CD\u8BD5\u3002" });
95
+ }
96
+ return readEnvelope(response);
97
+ }
98
+
99
+ // src/client/ResultPanel.tsx
100
+ var import_react = require("react");
101
+ var import_jsx_runtime = require("react/jsx-runtime");
102
+ var retryable = (code) => code === "upstream" || code === "timeout" || code === "internal";
103
+ function ResultPanel(props) {
104
+ const { state, t, onApply, onCancel, onRetry } = props;
105
+ const [copied, setCopied] = (0, import_react.useState)("idle");
106
+ (0, import_react.useEffect)(() => {
107
+ const onKeyDown = (event) => {
108
+ if (event.key === "Escape" && !event.defaultPrevented) {
109
+ event.preventDefault();
110
+ onCancel();
111
+ }
112
+ };
113
+ document.addEventListener("keydown", onKeyDown);
114
+ return () => document.removeEventListener("keydown", onKeyDown);
115
+ }, [onCancel]);
116
+ const copy = (0, import_react.useCallback)(() => {
117
+ if (state.result === void 0) return;
118
+ navigator.clipboard.writeText(state.result.text).then(() => setCopied("ok"), () => setCopied("failed"));
119
+ }, [state.result]);
120
+ const formatMs = (ms) => ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
121
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-overlay", onMouseDown: (event) => {
122
+ if (event.target === event.currentTarget) onCancel();
123
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-pe-panel", role: "dialog", "aria-label": t("panel.title"), children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "dsh-pe-head", children: [
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
126
+ "\u2728 ",
127
+ t("panel.title")
128
+ ] }),
129
+ state.phase === "result" && state.result !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-pe-meta", children: [
130
+ t("panel.model", { provider: state.result.provider, model: state.result.model }),
131
+ " \xB7 ",
132
+ t("panel.elapsed", { ms: formatMs(state.result.elapsedMs) })
133
+ ] }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-close", "aria-label": t("panel.close"), onClick: onCancel, children: "\u2715" })
135
+ ] }),
136
+ state.phase === "loading" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-loading", children: [
137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-spin", "aria-hidden": true }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: t("panel.loading") }),
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-hint", children: t("panel.loading.hint") })
140
+ ] }),
141
+ state.phase === "error" && state.error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-error", children: state.error.message }),
142
+ state.phase === "result" && state.result !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-body", children: [
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-col", children: [
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-col-title", children: t("panel.original") }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "dsh-pe-col-text", children: state.original })
146
+ ] }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pe-col", children: [
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pe-col-title", children: t("panel.enhanced") }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "dsh-pe-col-text", children: state.result.text })
150
+ ] })
151
+ ] }),
152
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "dsh-pe-foot", children: [
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-pe-grow" }),
154
+ 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") }),
155
+ state.phase === "result" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
156
+ /* @__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") }),
157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-pe-btn-action primary", onClick: onApply, children: t("panel.apply") })
158
+ ] }),
159
+ 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") })
160
+ ] })
161
+ ] }) });
162
+ }
163
+
164
+ // src/client/undo-stack.ts
165
+ function createUndoStack(maxDepth = 3) {
166
+ const stacks = /* @__PURE__ */ new Map();
167
+ return {
168
+ push(sessionId, entry) {
169
+ const stack = stacks.get(sessionId) ?? [];
170
+ stack.push(entry);
171
+ while (stack.length > maxDepth) stack.shift();
172
+ stacks.set(sessionId, stack);
173
+ },
174
+ peek(sessionId) {
175
+ const stack = stacks.get(sessionId);
176
+ return stack === void 0 || stack.length === 0 ? void 0 : stack[stack.length - 1];
177
+ },
178
+ pop(sessionId) {
179
+ const stack = stacks.get(sessionId);
180
+ const entry = stack?.pop();
181
+ if (stack !== void 0 && stack.length === 0) stacks.delete(sessionId);
182
+ return entry;
183
+ },
184
+ clear(sessionId) {
185
+ stacks.delete(sessionId);
186
+ },
187
+ depth(sessionId) {
188
+ return stacks.get(sessionId)?.length ?? 0;
189
+ }
190
+ };
191
+ }
192
+
193
+ // src/client/ui-state.ts
194
+ var listeners = /* @__PURE__ */ new Set();
195
+ var panelState;
196
+ var version = 0;
197
+ function notify() {
198
+ version++;
199
+ for (const listener of listeners) listener();
200
+ }
201
+ var undoStore = createUndoStack(3);
202
+ function pushUndo(sessionId, entry) {
203
+ undoStore.push(sessionId, entry);
204
+ notify();
205
+ }
206
+ function peekUndo(sessionId) {
207
+ return undoStore.peek(sessionId);
208
+ }
209
+ function popUndo(sessionId) {
210
+ const entry = undoStore.pop(sessionId);
211
+ notify();
212
+ return entry;
213
+ }
214
+ var sessions = /* @__PURE__ */ new Map();
215
+ var lastMountedSession;
216
+ function subscribe(listener) {
217
+ listeners.add(listener);
218
+ return () => {
219
+ listeners.delete(listener);
220
+ };
221
+ }
222
+ function getVersion() {
223
+ return version;
224
+ }
225
+ function getPanel() {
226
+ return panelState;
227
+ }
228
+ function openLoading(state) {
229
+ panelState = { ...state, phase: "loading" };
230
+ notify();
231
+ }
232
+ function settleResult(sessionId, result) {
233
+ if (panelState?.sessionId !== sessionId || panelState.phase !== "loading") return;
234
+ panelState = { sessionId, phase: "result", original: panelState.original, result };
235
+ notify();
236
+ }
237
+ function settleError(sessionId, error) {
238
+ if (panelState?.sessionId !== sessionId || panelState.phase !== "loading") return;
239
+ panelState = { sessionId, phase: "error", original: panelState.original, error };
240
+ notify();
241
+ }
242
+ function openError(sessionId, original, error) {
243
+ panelState?.abort?.();
244
+ panelState = { sessionId, phase: "error", original, error };
245
+ notify();
246
+ }
247
+ function closePanel() {
248
+ panelState?.abort?.();
249
+ if (panelState === void 0) return;
250
+ panelState = void 0;
251
+ notify();
252
+ }
253
+ function registerSession(sessionId, entry) {
254
+ sessions.set(sessionId, entry);
255
+ lastMountedSession = sessionId;
256
+ notify();
257
+ return () => {
258
+ if (sessions.get(sessionId) === entry) sessions.delete(sessionId);
259
+ if (lastMountedSession === sessionId) lastMountedSession = void 0;
260
+ if (panelState?.sessionId === sessionId) closePanel();
261
+ undoStore.clear(sessionId);
262
+ };
263
+ }
264
+ function shortcutTarget() {
265
+ const active = document.activeElement;
266
+ if (active !== null && active instanceof Element) {
267
+ let node = active;
268
+ for (let depth = 0; depth < 8 && node !== null; depth++) {
269
+ for (const entry of sessions.values()) {
270
+ if (entry.root !== null && (node === entry.root || node.contains(entry.root))) {
271
+ const found = entry;
272
+ return () => found.run();
273
+ }
274
+ }
275
+ node = node.parentElement;
276
+ }
277
+ }
278
+ const fallback = lastMountedSession !== void 0 ? sessions.get(lastMountedSession) : void 0;
279
+ return fallback === void 0 ? void 0 : () => fallback.run();
280
+ }
281
+
282
+ // src/client/settings.ts
283
+ var DEFAULT_CLIENT_SETTINGS = {
284
+ enabled: true,
285
+ maxInputChars: 12e3,
286
+ shortcut: "ctrl+alt+e"
287
+ };
288
+ var listeners2 = /* @__PURE__ */ new Set();
289
+ var current = DEFAULT_CLIENT_SETTINGS;
290
+ function setClientSettings(next) {
291
+ current = next;
292
+ for (const listener of listeners2) listener();
293
+ }
294
+ function getClientSettings() {
295
+ return current;
296
+ }
297
+ function subscribeClientSettings(listener) {
298
+ listeners2.add(listener);
299
+ return () => {
300
+ listeners2.delete(listener);
301
+ };
302
+ }
303
+ function decodeClientSettings(section) {
304
+ const record = section;
305
+ if (record === null || typeof record !== "object") return DEFAULT_CLIENT_SETTINGS;
306
+ return {
307
+ enabled: typeof record.enabled === "boolean" ? record.enabled : DEFAULT_CLIENT_SETTINGS.enabled,
308
+ maxInputChars: typeof record.maxInputChars === "number" && Number.isFinite(record.maxInputChars) && record.maxInputChars > 0 ? Math.floor(record.maxInputChars) : DEFAULT_CLIENT_SETTINGS.maxInputChars,
309
+ shortcut: typeof record.shortcut === "string" ? record.shortcut : DEFAULT_CLIENT_SETTINGS.shortcut
310
+ };
311
+ }
312
+
313
+ // src/client/EnhanceButton.tsx
314
+ var import_jsx_runtime2 = require("react/jsx-runtime");
315
+ function EnhanceButton(props) {
316
+ const { t, sessionId, useInput, inputActions } = props;
317
+ const draft = useInput((state) => state.draft);
318
+ const phase = useInput((state) => state.phase);
319
+ const occurrenceCount = useInput((state) => state.occurrences.length);
320
+ const imageCount = useInput((state) => state.imageIds.length);
321
+ const settings = (0, import_react2.useSyncExternalStore)(subscribeClientSettings, getClientSettings);
322
+ const panel = (0, import_react2.useSyncExternalStore)(subscribe, getPanel);
323
+ const rootRef = (0, import_react2.useRef)(null);
324
+ const busy = panel !== void 0 && panel.sessionId === sessionId && panel.phase === "loading";
325
+ const anyBusy = panel !== void 0 && panel.phase === "loading";
326
+ const start = (0, import_react2.useCallback)(() => {
327
+ if (anyBusy) {
328
+ openError(sessionId, draft, { code: "rejected", message: t("error.busy") });
329
+ return;
330
+ }
331
+ if (!settings.enabled) {
332
+ openError(sessionId, draft, { code: "rejected", message: t("error.disabled") });
333
+ return;
334
+ }
335
+ if (imageCount > 0 && draft.trim() === "") {
336
+ openError(sessionId, draft, { code: "rejected", message: t("error.imagesOnly") });
337
+ return;
338
+ }
339
+ const check = checkInputText(draft, settings.maxInputChars);
340
+ if (!check.ok) {
341
+ const message = check.code === "empty" ? t("error.empty") : t("error.tooLong", { count: check.count, max: check.max });
342
+ openError(sessionId, draft, { code: "rejected", message });
343
+ return;
344
+ }
345
+ if (occurrenceCount > 0) {
346
+ openError(sessionId, draft, { code: "rejected", message: t("error.occurrences") });
347
+ return;
348
+ }
349
+ if (phase !== "plain") {
350
+ openError(sessionId, draft, { code: "rejected", message: t("error.phase") });
351
+ return;
352
+ }
353
+ const controller = new AbortController();
354
+ openLoading({ sessionId, original: draft, abort: () => controller.abort() });
355
+ requestEnhance({ sessionId, text: draft }, controller.signal).then(
356
+ (result) => settleResult(sessionId, result),
357
+ (error) => {
358
+ const detail = error instanceof EnhanceClientError ? error.detail : { code: "internal", message: error instanceof Error ? error.message : String(error) };
359
+ settleError(sessionId, detail);
360
+ }
361
+ );
362
+ }, [anyBusy, draft, imageCount, occurrenceCount, phase, sessionId, settings, t]);
363
+ const runRef = (0, import_react2.useRef)(start);
364
+ runRef.current = start;
365
+ (0, import_react2.useEffect)(() => {
366
+ const entry = {
367
+ root: rootRef.current,
368
+ run: () => runRef.current()
369
+ };
370
+ return registerSession(sessionId, entry);
371
+ }, [sessionId]);
372
+ const apply2 = (0, import_react2.useCallback)(() => {
373
+ if (panel === void 0 || panel.phase !== "result" || panel.result === void 0) return;
374
+ pushUndo(sessionId, { original: panel.original, applied: panel.result.text });
375
+ inputActions.setDraft(panel.result.text);
376
+ closePanel();
377
+ }, [inputActions, panel, sessionId]);
378
+ if (!settings.enabled) return null;
379
+ const owned = panel !== void 0 && panel.sessionId === sessionId ? panel : void 0;
380
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
381
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
382
+ "button",
383
+ {
384
+ ref: rootRef,
385
+ type: "button",
386
+ className: `dsh-pe-btn${busy ? " is-busy" : ""}`,
387
+ title: busy ? t("button.busy") : t("button.title"),
388
+ "aria-label": t("button.title"),
389
+ disabled: anyBusy && !busy,
390
+ onClick: start,
391
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-pe-btn-icon", "aria-hidden": true, children: busy ? "\u25CC" : "\u2728" })
392
+ }
393
+ ),
394
+ owned !== void 0 && (0, import_react_dom.createPortal)(
395
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
396
+ ResultPanel,
397
+ {
398
+ state: owned,
399
+ t,
400
+ onApply: apply2,
401
+ onCancel: () => closePanel(),
402
+ onRetry: owned.phase === "error" ? start : void 0
403
+ }
404
+ ),
405
+ document.body
406
+ )
407
+ ] });
408
+ }
409
+
410
+ // src/client/UndoBar.tsx
411
+ var import_react3 = require("react");
412
+ var import_react4 = require("react");
413
+ var import_jsx_runtime3 = require("react/jsx-runtime");
414
+ function UndoBar(props) {
415
+ const { t, sessionId, useInput, inputActions } = props;
416
+ const draft = useInput((state) => state.draft);
417
+ (0, import_react4.useSyncExternalStore)(subscribe, getVersion);
418
+ const entry = peekUndo(sessionId);
419
+ (0, import_react3.useEffect)(() => {
420
+ if (entry !== void 0 && entry.applied !== draft) popUndo(sessionId);
421
+ }, [draft, entry, sessionId]);
422
+ if (entry === void 0 || entry.applied !== draft) return null;
423
+ const undo = () => {
424
+ inputActions.setDraft(entry.original);
425
+ popUndo(sessionId);
426
+ };
427
+ const dismiss = () => {
428
+ popUndo(sessionId);
429
+ };
430
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-pe-undo", children: [
431
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-pe-undo-check", "aria-hidden": true, children: "\u2713" }),
432
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: t("undo.applied") }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "dsh-pe-undo-link", onClick: undo, children: t("undo.undo") }),
434
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "dsh-pe-undo-x", "aria-label": t("undo.dismiss"), onClick: dismiss, children: "\u2715" })
435
+ ] });
436
+ }
437
+
438
+ // src/client/locales.ts
439
+ var zh = {
440
+ "button.title": "\u63D0\u793A\u8BCD\u589E\u5F3A\uFF08\u91CD\u5199\u4E3A\u7ED3\u6784\u5316\u63D0\u793A\u8BCD\uFF09",
441
+ "button.busy": "\u6B63\u5728\u589E\u5F3A\u2026",
442
+ "panel.title": "\u63D0\u793A\u8BCD\u589E\u5F3A",
443
+ "panel.loading": "\u6B63\u5728\u589E\u5F3A\uFF0C\u7A0D\u5019\u2026",
444
+ "panel.loading.hint": "\u539F\u6587\u4FDD\u7559\u5728\u8F93\u5165\u6846\u4E2D\uFF0C\u4E0D\u4F1A\u88AB\u52A8\u4FEE\u6539\u3002",
445
+ "panel.cancel": "\u53D6\u6D88",
446
+ "panel.original": "\u539F\u59CB\u63D0\u793A\u8BCD",
447
+ "panel.enhanced": "\u589E\u5F3A\u7ED3\u679C",
448
+ "panel.apply": "\u56DE\u586B\u5230\u8F93\u5165\u6846",
449
+ "panel.copy": "\u590D\u5236\u7ED3\u679C",
450
+ "panel.copied": "\u5DF2\u590D\u5236",
451
+ "panel.copyFailed": "\u590D\u5236\u5931\u8D25",
452
+ "panel.close": "\u5173\u95ED",
453
+ "panel.retry": "\u91CD\u8BD5",
454
+ "panel.model": "\u6A21\u578B\uFF1A{provider} / {model}",
455
+ "panel.elapsed": "\u8017\u65F6 {ms}",
456
+ "undo.applied": "\u5DF2\u7528\u589E\u5F3A\u7ED3\u679C\u66FF\u6362\u539F\u63D0\u793A\u8BCD",
457
+ "undo.undo": "\u64A4\u9500",
458
+ "undo.dismiss": "\u5173\u95ED\u63D0\u793A",
459
+ "error.empty": "\u8F93\u5165\u6846\u4E3A\u7A7A\uFF0C\u8BF7\u5148\u8F93\u5165\u8981\u589E\u5F3A\u7684\u63D0\u793A\u8BCD\u3002",
460
+ "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
+ "error.imagesOnly": "\u5F53\u524D\u53EA\u9644\u52A0\u4E86\u56FE\u7247\uFF0C\u4EC5\u652F\u6301\u589E\u5F3A\u6587\u672C\u5185\u5BB9\u3002",
462
+ "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
+ "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"
466
+ };
467
+ var en = {
468
+ "button.title": "Enhance prompt (rewrite into a structured prompt)",
469
+ "button.busy": "Enhancing\u2026",
470
+ "panel.title": "Prompt Enhance",
471
+ "panel.loading": "Enhancing, please wait\u2026",
472
+ "panel.loading.hint": "Your draft stays untouched until you apply the result.",
473
+ "panel.cancel": "Cancel",
474
+ "panel.original": "Original prompt",
475
+ "panel.enhanced": "Enhanced result",
476
+ "panel.apply": "Fill into input box",
477
+ "panel.copy": "Copy result",
478
+ "panel.copied": "Copied",
479
+ "panel.copyFailed": "Copy failed",
480
+ "panel.close": "Close",
481
+ "panel.retry": "Retry",
482
+ "panel.model": "Model: {provider} / {model}",
483
+ "panel.elapsed": "{ms} elapsed",
484
+ "undo.applied": "Original prompt replaced by the enhanced version",
485
+ "undo.undo": "Undo",
486
+ "undo.dismiss": "Dismiss",
487
+ "error.empty": "The input box is empty \u2014 type a prompt to enhance first.",
488
+ "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
+ "error.imagesOnly": "Only images are attached; prompt enhance supports text only.",
490
+ "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
+ "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."
494
+ };
495
+ var dictionaries = { zh, en };
496
+ var NS = "prompt-enhance";
497
+
498
+ // src/client/styles.ts
499
+ var STYLE_ID = "dsh-prompt-enhance-styles";
500
+ 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
+ .dsh-pe-overlay {
524
+ position: fixed;
525
+ inset: 0;
526
+ z-index: 1000;
527
+ display: flex;
528
+ align-items: flex-end;
529
+ justify-content: center;
530
+ background: rgba(0, 0, 0, 0.32);
531
+ padding: 24px 16px;
532
+ }
533
+ .dsh-pe-panel {
534
+ display: flex;
535
+ flex-direction: column;
536
+ width: min(880px, 100%);
537
+ max-height: min(70vh, 640px);
538
+ border-radius: 12px;
539
+ border: 1px solid rgba(127, 127, 127, 0.35);
540
+ background: #1f2127;
541
+ color: #e6e8ee;
542
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
543
+ overflow: hidden;
544
+ }
545
+ .dsh-pe-head {
546
+ display: flex;
547
+ align-items: center;
548
+ gap: 8px;
549
+ padding: 10px 14px;
550
+ border-bottom: 1px solid rgba(127, 127, 127, 0.25);
551
+ font-size: 13px;
552
+ font-weight: 600;
553
+ }
554
+ .dsh-pe-head .dsh-pe-meta { margin-left: auto; font-weight: 400; font-size: 11px; opacity: 0.6; }
555
+ .dsh-pe-close {
556
+ border: none; background: transparent; color: inherit; opacity: 0.6;
557
+ font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 4px;
558
+ }
559
+ .dsh-pe-close:hover { opacity: 1; background: rgba(127,127,127,0.2); }
560
+ .dsh-pe-body { display: flex; flex: 1; min-height: 0; }
561
+ .dsh-pe-col {
562
+ flex: 1 1 0;
563
+ min-width: 0;
564
+ display: flex;
565
+ flex-direction: column;
566
+ }
567
+ .dsh-pe-col + .dsh-pe-col { border-left: 1px solid rgba(127, 127, 127, 0.25); }
568
+ .dsh-pe-col-title {
569
+ padding: 8px 14px 6px;
570
+ font-size: 11px;
571
+ letter-spacing: 0.04em;
572
+ opacity: 0.55;
573
+ }
574
+ .dsh-pe-col-text {
575
+ flex: 1;
576
+ margin: 0;
577
+ padding: 0 14px 12px;
578
+ overflow: auto;
579
+ white-space: pre-wrap;
580
+ word-break: break-word;
581
+ font-family: inherit;
582
+ font-size: 13px;
583
+ line-height: 1.6;
584
+ }
585
+ .dsh-pe-error {
586
+ padding: 20px 16px;
587
+ font-size: 13px;
588
+ line-height: 1.6;
589
+ color: #ffb4a8;
590
+ white-space: pre-wrap;
591
+ }
592
+ .dsh-pe-loading {
593
+ display: flex; flex-direction: column; gap: 8px;
594
+ align-items: center; justify-content: center;
595
+ flex: 1; padding: 28px 16px; font-size: 13px;
596
+ }
597
+ .dsh-pe-loading .dsh-pe-hint { font-size: 11px; opacity: 0.55; }
598
+ .dsh-pe-spin {
599
+ width: 22px; height: 22px; border-radius: 50%;
600
+ border: 2px solid rgba(127,127,127,0.3); border-top-color: #6ea8ff;
601
+ animation: dsh-pe-spin 0.9s linear infinite;
602
+ }
603
+ .dsh-pe-foot {
604
+ display: flex;
605
+ align-items: center;
606
+ gap: 8px;
607
+ padding: 10px 14px;
608
+ border-top: 1px solid rgba(127, 127, 127, 0.25);
609
+ }
610
+ .dsh-pe-foot .dsh-pe-grow { flex: 1; }
611
+ .dsh-pe-btn-action {
612
+ border: none; border-radius: 6px; padding: 6px 14px;
613
+ font-size: 12px; cursor: pointer; line-height: 1;
614
+ }
615
+ .dsh-pe-btn-action.primary { background: #3f76e1; color: #fff; }
616
+ .dsh-pe-btn-action.primary:hover { background: #4d82ec; }
617
+ .dsh-pe-btn-action.plain { background: rgba(127,127,127,0.18); color: inherit; }
618
+ .dsh-pe-btn-action.plain:hover { background: rgba(127,127,127,0.3); }
619
+
620
+ .dsh-pe-undo {
621
+ display: flex;
622
+ align-items: center;
623
+ gap: 8px;
624
+ padding: 2px 4px;
625
+ font-size: 12px;
626
+ color: inherit;
627
+ opacity: 0.85;
628
+ }
629
+ .dsh-pe-undo .dsh-pe-undo-check { color: #7ed491; }
630
+ .dsh-pe-undo-link {
631
+ border: none; background: rgba(127,127,127,0.18); color: inherit;
632
+ border-radius: 5px; padding: 3px 10px; font-size: 12px; cursor: pointer;
633
+ }
634
+ .dsh-pe-undo-link:hover { background: rgba(127,127,127,0.3); }
635
+ .dsh-pe-undo-x {
636
+ border: none; background: transparent; color: inherit; opacity: 0.55;
637
+ cursor: pointer; padding: 0 4px; font-size: 12px;
638
+ }
639
+ .dsh-pe-undo-x:hover { opacity: 1; }
640
+ `;
641
+ function ensureStyles() {
642
+ if (document.getElementById(STYLE_ID) !== null) return false;
643
+ const element = document.createElement("style");
644
+ element.id = STYLE_ID;
645
+ element.textContent = CSS;
646
+ document.head.appendChild(element);
647
+ return true;
648
+ }
649
+
650
+ // src/client/shortcut.ts
651
+ var MODIFIER_ALIASES = /* @__PURE__ */ new Map([
652
+ ["ctrl", "ctrl"],
653
+ ["control", "ctrl"],
654
+ ["alt", "alt"],
655
+ ["option", "alt"],
656
+ ["shift", "shift"],
657
+ ["meta", "meta"],
658
+ ["cmd", "meta"],
659
+ ["command", "meta"],
660
+ ["win", "meta"],
661
+ ["super", "meta"]
662
+ ]);
663
+ var isKeyToken = (token) => /^[a-z0-9]$/.test(token) || /^f([1-9]|1[0-2])$/.test(token);
664
+ function parseShortcut(spec) {
665
+ if (spec === void 0) return null;
666
+ const tokens = spec.trim().toLowerCase().split("+").map((token) => token.trim()).filter((token) => token !== "");
667
+ if (tokens.length === 0 || tokens.length > 5) return null;
668
+ const modifiers = { ctrl: false, alt: false, shift: false, meta: false };
669
+ const last = tokens[tokens.length - 1] ?? "";
670
+ if (!isKeyToken(last)) return null;
671
+ for (const token of tokens.slice(0, -1)) {
672
+ const modifier = MODIFIER_ALIASES.get(token);
673
+ if (modifier === void 0) return null;
674
+ modifiers[modifier] = true;
675
+ }
676
+ return { ...modifiers, key: last };
677
+ }
678
+ function matchesShortcut(event, combo) {
679
+ if (combo === null) return false;
680
+ return event.ctrlKey === combo.ctrl && event.altKey === combo.alt && event.shiftKey === combo.shift && event.metaKey === combo.meta && event.key.toLowerCase() === combo.key;
681
+ }
682
+
683
+ // src/client/index.tsx
684
+ var inject = ["slots", "settingsScope", "locale"];
685
+ function apply(ctx) {
686
+ ensureStyles();
687
+ ctx.effect(() => {
688
+ try {
689
+ return ctx.locale.register(NS, dictionaries);
690
+ } catch {
691
+ return () => {
692
+ };
693
+ }
694
+ }, "dsh-prompt-enhance: dictionaries");
695
+ ctx.inject(["settingsScope"], (settingsCtx) => {
696
+ const scope = settingsCtx.settingsScope.bind({ namespace: NS, decode: decodeClientSettings });
697
+ const sync = () => {
698
+ setClientSettings(scope.getSnapshot().value ?? decodeClientSettings(void 0));
699
+ };
700
+ sync();
701
+ ctx.effect(() => scope.subscribe(sync), "dsh-prompt-enhance: settings mirror");
702
+ });
703
+ ctx.inject(["slots"], (slotsCtx) => {
704
+ const slots = slotsCtx.slots;
705
+ return slots.inject("conversation.input.right", () => {
706
+ try {
707
+ return slots.register(
708
+ { name: "conversation.input.right", id: "prompt-enhance", order: 60, locale: NS },
709
+ EnhanceButton
710
+ );
711
+ } catch {
712
+ return () => {
713
+ };
714
+ }
715
+ });
716
+ });
717
+ ctx.inject(["slots"], (slotsCtx) => {
718
+ const slots = slotsCtx.slots;
719
+ return slots.inject("conversation.input.dock", () => {
720
+ try {
721
+ return slots.register(
722
+ { name: "conversation.input.dock", id: "prompt-enhance-undo", order: 90, locale: NS },
723
+ UndoBar
724
+ );
725
+ } catch {
726
+ return () => {
727
+ };
728
+ }
729
+ });
730
+ });
731
+ ctx.effect(() => {
732
+ const onKeyDown = (event) => {
733
+ if (event.defaultPrevented || event.isComposing) return;
734
+ const combo = parseShortcut(getClientSettings().shortcut);
735
+ if (!matchesShortcut(event, combo)) return;
736
+ const target = shortcutTarget();
737
+ if (target === void 0) return;
738
+ event.preventDefault();
739
+ event.stopPropagation();
740
+ target();
741
+ };
742
+ document.addEventListener("keydown", onKeyDown, true);
743
+ return () => document.removeEventListener("keydown", onKeyDown, true);
744
+ }, "dsh-prompt-enhance: shortcut");
745
+ }
746
+
747
+ return module.exports;
748
+ }
749
+ });
750
+