dsh-composer-ux 0.1.5 → 0.3.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 CHANGED
@@ -57,6 +57,117 @@ var MENU_FIELDS = [
57
57
  "menuSelectAll"
58
58
  ];
59
59
  var MENU_NATIVE_FIELD = "menuNative";
60
+ var QUICK_PROMPTS_FIELD = "quickPrompts";
61
+ var OPTIMIZER_TIER_FIELD = "optimizerTier";
62
+ var QUICK_PROMPT_MAX = 60;
63
+ var QUICK_CATEGORY_MAX = 20;
64
+ var QUICK_CATEGORY_NAME_MAX = 40;
65
+ var QUICK_LABEL_MAX = 40;
66
+ var QUICK_TEXT_MAX = 2e5;
67
+ var QUICK_PROMPTS_API_PATH = "/composer-ux/prompts";
68
+ var QUICK_BOOK_VERSION = 2;
69
+ var DEFAULT_CATEGORY_NAME = "\u9ED8\u8BA4";
70
+ var OPTIMIZER_API_PATH = "/composer-ux/optimize";
71
+ var OPTIMIZER_TIERS = [
72
+ { id: "basic", label: "\u666E\u901A", hint: "\u53EA\u505A\u8BED\u8A00\u5C42\u4FEE\u590D\uFF1A\u75C5\u53E5\u3001\u9519\u522B\u5B57\u3001\u6307\u4EE3\u4E0E\u542B\u7CCA\u8BCD\uFF0C\u4E0D\u65B0\u589E\u4EFB\u4F55\u9700\u6C42\uFF0C\u7BC7\u5E45\u4E0E\u539F\u6587\u76F8\u5F53\u3002" },
73
+ { id: "advanced", label: "\u9AD8\u7EA7", hint: "\u5728\u4E0D\u52A8\u76EE\u6807\u7684\u524D\u63D0\u4E0B\uFF0C\u628A\u300C\u4F60\u663E\u7136\u60F3\u8981\u3001\u4F46\u6CA1\u8BF4\u51FA\u53E3\u300D\u7684\u5FC5\u8981\u8981\u6C42\u8865\u6210\u5BF9 AI \u7684\u8981\u6C42\uFF0C\u8BA9\u5B83\u4E00\u6B21\u505A\u5BF9\u3002" },
74
+ { id: "extreme", label: "\u6781\u7AEF", hint: "\u6309\u590D\u6742\u4EFB\u52A1\u5904\u7406\uFF1A\u56FA\u5316\u547D\u4EE4\u7ED3\u6784 + \u5206\u9636\u6BB5\u6267\u884C\u8BA1\u5212 + 2~4 \u79CD\u60C5\u51B5\u7684\u9884\u6848\u3002" }
75
+ ];
76
+ var DEFAULT_OPTIMIZER_TIER = "advanced";
77
+ var DEFAULT_QUICK_PROMPTS = [
78
+ { id: "builtin-1", label: "\u4E00\u95EE\u4E00\u7B54", prompt: "\u4F60\u4E0D\u61C2\u7684\u5C31\u95EE\u6211\uFF0C\u4E00\u95EE\u4E00\u7B54\uFF1B\u540C\u65F6\u8BF4\u6E05\u695A\u4F60\u4E3A\u4EC0\u4E48\u8981\u95EE\u8BE5\u95EE\u9898\uFF1B\u76F4\u5230\u4F60\u5BF9\u6211\u7684\u76EE\u6807\u6709\u660E\u786E\u8BA4\u77E5\u540E\u518D\u5F00\u59CB\u5E72\u6D3B\u3002", always: false },
79
+ { id: "builtin-2", label: "\u4EA4\u63A5\u6587\u6863", prompt: "\u628A\u8FD9\u6B21\u4EFB\u52A1\u3001\u5DF2\u5B8C\u6210\u5185\u5BB9\u3001\u5F53\u524D\u5361\u70B9\u3001\u4E0B\u4E00\u6B65\u8BA1\u5212\u3001\u8E29\u8FC7\u7684\u5751\uFF0C\u6574\u7406\u6210\u4E00\u4EFD\u4EA4\u63A5\u6587\u6863\uFF0C\u5199\u7ED9\u65B0\u4F1A\u8BDD\u770B\u3002", always: false },
80
+ { id: "builtin-3", label: "\u4EC5\u8BF4\u660E\u539F\u56E0", prompt: "\u4EC5\u8BF4\u660E\u539F\u56E0\uFF0C\u4E0D\u8981\u505A\u5176\u4ED6\u52A8\u4F5C\u3002", always: false },
81
+ { id: "builtin-4", label: "\u5206\u6790\u540E\u76F4\u63A5\u5E72", prompt: "\u5206\u6790\u539F\u56E0\uFF0C\u7136\u540E\u76F4\u63A5\u5F00\u59CB\u5E72\u6D3B\uFF0C\u4E0D\u9700\u8981\u8FC7\u95EE\u6211\u3002", always: false },
82
+ { id: "builtin-5", label: "\u63D0\u4EA4\u4EE3\u7801", prompt: "\u8BF7\u5E2E\u6211\u63D0\u4EA4\u4EE3\u7801\uFF1A\u68C0\u67E5\u5F53\u524D git \u53D8\u66F4\uFF0C\u751F\u6210\u89C4\u8303\u7684 commit message \u5E76\u6267\u884C\u63D0\u4EA4\u3002", always: false },
83
+ { id: "builtin-6", label: "\u7ED9\u65B9\u6848", prompt: "\u8BF7\u9488\u5BF9\u4E0A\u9762\u7684\u95EE\u9898\u7ED9\u51FA\u4E00\u4E2A\u5B8C\u6574\u65B9\u6848\uFF0C\u5305\u62EC\u601D\u8DEF\u3001\u6B65\u9AA4\u3001\u6CE8\u610F\u4E8B\u9879\u548C\u98CE\u9669\u3002", always: false },
84
+ { id: "builtin-7", label: "\u89E3\u91CA\u4EE3\u7801", prompt: "\u8BF7\u89E3\u91CA\u8FD9\u6BB5\u4EE3\u7801\u7684\u4F5C\u7528\u548C\u5B9E\u73B0\u601D\u8DEF\u3002", always: false },
85
+ { id: "builtin-8", label: "\u5199\u6D4B\u8BD5", prompt: "\u8BF7\u4E3A\u4E0B\u9762\u7684\u4EE3\u7801\u7F16\u5199\u5355\u5143\u6D4B\u8BD5\u3002", always: false },
86
+ { id: "builtin-9", label: "\u4EE3\u7801\u5BA1\u67E5", prompt: "\u8BF7\u5BF9\u4E0B\u9762\u7684\u4EE3\u7801\u8FDB\u884C\u4EE3\u7801\u5BA1\u67E5\uFF0C\u6307\u51FA\u95EE\u9898\u5E76\u7ED9\u51FA\u6539\u8FDB\u5EFA\u8BAE\u3002", always: false }
87
+ ];
88
+ function newQuickPromptId() {
89
+ return `qp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
90
+ }
91
+ function newQuickCategoryId() {
92
+ return `qc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
93
+ }
94
+ function defaultQuickBook() {
95
+ return {
96
+ version: QUICK_BOOK_VERSION,
97
+ categories: [{ id: "cat-default", name: DEFAULT_CATEGORY_NAME, prompts: DEFAULT_QUICK_PROMPTS }]
98
+ };
99
+ }
100
+ function asPromptRow(value) {
101
+ if (typeof value !== "object" || value === null) return void 0;
102
+ const row2 = value;
103
+ const either = (first, second) => {
104
+ const picked = typeof first === "string" && first !== "" ? first : second;
105
+ return typeof picked === "string" ? picked : "";
106
+ };
107
+ const prompt = either(row2.prompt, row2.text).slice(0, QUICK_TEXT_MAX).trim();
108
+ if (prompt === "") return void 0;
109
+ const label = either(row2.label, row2.title).slice(0, QUICK_LABEL_MAX).trim();
110
+ const id = typeof row2.id === "string" && row2.id !== "" ? row2.id.slice(0, 64) : newQuickPromptId();
111
+ const order = typeof row2.order === "number" && Number.isFinite(row2.order) ? Math.max(1, Math.round(row2.order)) : void 0;
112
+ return {
113
+ prompt: { id, label: label === "" ? prompt.slice(0, 12) : label, prompt, always: row2.always === true || row2.autoSend === true },
114
+ order
115
+ };
116
+ }
117
+ function toPromptList(values) {
118
+ const rows = [];
119
+ for (const value of values) {
120
+ const row2 = asPromptRow(value);
121
+ if (row2 !== void 0) rows.push({ ...row2, index: rows.length });
122
+ }
123
+ rows.sort((a, b) => {
124
+ const left = a.order ?? Number.MAX_SAFE_INTEGER;
125
+ const right = b.order ?? Number.MAX_SAFE_INTEGER;
126
+ return left === right ? a.index - b.index : left - right;
127
+ });
128
+ const seen = /* @__PURE__ */ new Set();
129
+ const out = [];
130
+ for (const row2 of rows) {
131
+ if (out.length >= QUICK_PROMPT_MAX) break;
132
+ if (seen.has(row2.prompt.id)) continue;
133
+ seen.add(row2.prompt.id);
134
+ out.push(row2.prompt);
135
+ }
136
+ return out;
137
+ }
138
+ function sanitizeBook(value) {
139
+ if (typeof value !== "object" || value === null) return void 0;
140
+ const source = value;
141
+ const flat = Array.isArray(value) ? value : Array.isArray(source.prompts) ? source.prompts : void 0;
142
+ if (flat !== void 0) {
143
+ const prompts = toPromptList(flat);
144
+ return {
145
+ version: QUICK_BOOK_VERSION,
146
+ categories: prompts.length === 0 ? [] : [{ id: "cat-default", name: DEFAULT_CATEGORY_NAME, prompts }]
147
+ };
148
+ }
149
+ if (!Array.isArray(source.categories)) return void 0;
150
+ const seen = /* @__PURE__ */ new Set();
151
+ const categories = [];
152
+ for (const raw of source.categories) {
153
+ if (categories.length >= QUICK_CATEGORY_MAX) break;
154
+ if (typeof raw !== "object" || raw === null) continue;
155
+ const row2 = raw;
156
+ const prompts = toPromptList(Array.isArray(row2.prompts) ? row2.prompts : []);
157
+ const wanted = typeof row2.id === "string" && row2.id !== "" ? row2.id.slice(0, 64) : newQuickCategoryId();
158
+ const id = seen.has(wanted) ? newQuickCategoryId() : wanted;
159
+ seen.add(id);
160
+ const name2 = (typeof row2.name === "string" ? row2.name : "").slice(0, QUICK_CATEGORY_NAME_MAX).trim();
161
+ categories.push({ id, name: name2 === "" ? DEFAULT_CATEGORY_NAME : name2, prompts });
162
+ }
163
+ return { version: QUICK_BOOK_VERSION, categories };
164
+ }
165
+ function flattenQuickPrompts(book) {
166
+ return book.categories.flatMap((category) => category.prompts);
167
+ }
168
+ function alwaysQuickPrompts(book) {
169
+ return flattenQuickPrompts(book).filter((prompt) => prompt.always);
170
+ }
60
171
  var HEADER_ENABLED_FIELD = "headerEnabled";
61
172
  var HEADER_NAME_FIELD = "headerName";
62
173
  var HEADER_VALUE_FIELD = "headerValue";
@@ -87,7 +198,9 @@ var DEFAULT_SETTINGS = {
87
198
  headerRoutes: "",
88
199
  headerAppliedName: "",
89
200
  headerAppliedValue: "",
90
- headerStatus: ""
201
+ headerStatus: "",
202
+ quickPrompts: DEFAULT_QUICK_PROMPTS,
203
+ optimizerTier: DEFAULT_OPTIMIZER_TIER
91
204
  };
92
205
  var SEND_PRESETS = ["Enter", "Ctrl+Enter", "Alt+Enter", "Shift+Enter"];
93
206
  var NEWLINE_PRESETS = ["Shift+Enter", "Enter", "Ctrl+Enter", "Alt+Enter"];
@@ -127,6 +240,29 @@ function sanitizeSettings(value) {
127
240
  const v = source[field];
128
241
  return typeof v === "number" && Number.isFinite(v) && v >= 320 && v <= 4e3 ? Math.round(v) : void 0;
129
242
  };
243
+ const asQuickPrompts = () => {
244
+ const raw = source[QUICK_PROMPTS_FIELD];
245
+ if (!Array.isArray(raw)) return DEFAULT_QUICK_PROMPTS;
246
+ const seen = /* @__PURE__ */ new Set();
247
+ const out = [];
248
+ for (const item of raw) {
249
+ if (out.length >= QUICK_PROMPT_MAX) break;
250
+ if (typeof item !== "object" || item === null) continue;
251
+ const row2 = item;
252
+ const prompt = typeof row2.prompt === "string" ? row2.prompt.slice(0, QUICK_TEXT_MAX).trim() : "";
253
+ if (prompt === "") continue;
254
+ const label = typeof row2.label === "string" ? row2.label.slice(0, QUICK_LABEL_MAX).trim() : "";
255
+ const id = typeof row2.id === "string" && row2.id !== "" ? row2.id.slice(0, 64) : newQuickPromptId();
256
+ if (seen.has(id)) continue;
257
+ seen.add(id);
258
+ out.push({ id, label: label === "" ? prompt.slice(0, 12) : label, prompt, always: row2.always === true });
259
+ }
260
+ return out;
261
+ };
262
+ const asTier = () => {
263
+ const v = source[OPTIMIZER_TIER_FIELD];
264
+ return v === "basic" || v === "advanced" || v === "extreme" ? v : DEFAULT_OPTIMIZER_TIER;
265
+ };
130
266
  return {
131
267
  enabled: asBool(ENABLED_FIELD),
132
268
  sendKey: asString(SEND_KEY_FIELD),
@@ -151,10 +287,136 @@ function sanitizeSettings(value) {
151
287
  headerRoutes: asText(HEADER_ROUTES_FIELD, HEADER_VALUE_MAX),
152
288
  headerAppliedName: asText(HEADER_APPLIED_NAME_FIELD, HEADER_NAME_MAX),
153
289
  headerAppliedValue: asText(HEADER_APPLIED_VALUE_FIELD, HEADER_VALUE_MAX),
154
- headerStatus: asText(HEADER_STATUS_FIELD, HEADER_VALUE_MAX)
290
+ headerStatus: asText(HEADER_STATUS_FIELD, HEADER_VALUE_MAX),
291
+ quickPrompts: asQuickPrompts(),
292
+ optimizerTier: asTier()
155
293
  };
156
294
  }
157
295
 
296
+ // src/client/prompt-book.ts
297
+ async function requestBook(method, body) {
298
+ try {
299
+ const response = await fetch(QUICK_PROMPTS_API_PATH, {
300
+ method,
301
+ headers: body === void 0 ? void 0 : { "content-type": "application/json" },
302
+ body: body === void 0 ? void 0 : JSON.stringify(body)
303
+ });
304
+ const text = await response.text();
305
+ let parsed;
306
+ try {
307
+ parsed = JSON.parse(text);
308
+ } catch {
309
+ return { ok: false, error: `\u670D\u52A1\u7AEF\u8FD4\u56DE\u7684\u4E0D\u662F JSON\uFF08HTTP ${String(response.status)}\uFF09` };
310
+ }
311
+ const row2 = typeof parsed === "object" && parsed !== null ? parsed : {};
312
+ const book = sanitizeBook(row2.book);
313
+ const textOf = (value) => typeof value === "string" && value !== "" ? value : void 0;
314
+ return {
315
+ ok: row2.ok === true,
316
+ ...book === void 0 ? {} : { book },
317
+ ...textOf(row2.error) === void 0 ? {} : { error: textOf(row2.error) },
318
+ ...textOf(row2.file) === void 0 ? {} : { file: textOf(row2.file) },
319
+ ...textOf(row2.quarantined) === void 0 ? {} : { quarantined: textOf(row2.quarantined) }
320
+ };
321
+ } catch (error) {
322
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
323
+ }
324
+ }
325
+ function loadPromptBook() {
326
+ return requestBook("GET");
327
+ }
328
+ function savePromptBook(book) {
329
+ return requestBook("POST", { book });
330
+ }
331
+ function categoryOf(book, categoryId) {
332
+ return book.categories.find((category) => category.id === categoryId);
333
+ }
334
+ function findPrompt(book, promptId) {
335
+ for (const category of book.categories) {
336
+ const prompt = category.prompts.find((item) => item.id === promptId);
337
+ if (prompt !== void 0) return { category, prompt };
338
+ }
339
+ return void 0;
340
+ }
341
+ function bookCounts(book) {
342
+ const prompts = book.categories.flatMap((category) => category.prompts);
343
+ return {
344
+ categories: book.categories.length,
345
+ prompts: prompts.length,
346
+ always: prompts.filter((prompt) => prompt.always).length
347
+ };
348
+ }
349
+ function mapCategory(book, categoryId, edit) {
350
+ return {
351
+ version: book.version,
352
+ categories: book.categories.map((category) => category.id === categoryId ? edit(category) : category)
353
+ };
354
+ }
355
+ function withCategoryAdded(book, name2) {
356
+ if (book.categories.length >= QUICK_CATEGORY_MAX) return book;
357
+ const clean = name2.trim().slice(0, QUICK_CATEGORY_NAME_MAX);
358
+ return {
359
+ version: book.version,
360
+ categories: [...book.categories, { id: newQuickCategoryId(), name: clean === "" ? DEFAULT_CATEGORY_NAME : clean, prompts: [] }]
361
+ };
362
+ }
363
+ function withCategoryRenamed(book, categoryId, name2) {
364
+ const clean = name2.trim().slice(0, QUICK_CATEGORY_NAME_MAX);
365
+ return mapCategory(book, categoryId, (category) => ({ ...category, name: clean === "" ? DEFAULT_CATEGORY_NAME : clean }));
366
+ }
367
+ function withCategoryRemoved(book, categoryId) {
368
+ return { version: book.version, categories: book.categories.filter((category) => category.id !== categoryId) };
369
+ }
370
+ function withCategoryMoved(book, categoryId, delta) {
371
+ const index = book.categories.findIndex((category) => category.id === categoryId);
372
+ const next = index + delta;
373
+ if (index < 0 || next < 0 || next >= book.categories.length) return book;
374
+ const categories = [...book.categories];
375
+ const [moved] = categories.splice(index, 1);
376
+ categories.splice(next, 0, moved);
377
+ return { version: book.version, categories };
378
+ }
379
+ function withPromptAdded(book, categoryId) {
380
+ return mapCategory(book, categoryId, (category) => {
381
+ if (category.prompts.length >= QUICK_PROMPT_MAX) return category;
382
+ return { ...category, prompts: [...category.prompts, { id: newQuickPromptId(), label: "\u65B0\u6307\u4EE4", prompt: "", always: false }] };
383
+ });
384
+ }
385
+ function withPromptPatched(book, categoryId, promptId, patch) {
386
+ return mapCategory(book, categoryId, (category) => ({
387
+ ...category,
388
+ prompts: category.prompts.map((prompt) => {
389
+ if (prompt.id !== promptId) return prompt;
390
+ const label = patch.label === void 0 ? prompt.label : patch.label.slice(0, QUICK_LABEL_MAX);
391
+ const text = patch.prompt === void 0 ? prompt.prompt : patch.prompt.slice(0, QUICK_TEXT_MAX);
392
+ const always = patch.always === void 0 ? prompt.always : patch.always;
393
+ return { id: prompt.id, label, prompt: text, always };
394
+ })
395
+ }));
396
+ }
397
+ function withPromptRemoved(book, categoryId, promptId) {
398
+ return mapCategory(book, categoryId, (category) => ({
399
+ ...category,
400
+ prompts: category.prompts.filter((prompt) => prompt.id !== promptId)
401
+ }));
402
+ }
403
+ function withPromptMoved(book, categoryId, promptId, delta) {
404
+ const category = categoryOf(book, categoryId);
405
+ if (category === void 0) return book;
406
+ const index = category.prompts.findIndex((prompt) => prompt.id === promptId);
407
+ const next = index + delta;
408
+ if (index < 0 || next < 0 || next >= category.prompts.length) return book;
409
+ const prompts = [...category.prompts];
410
+ const [moved] = prompts.splice(index, 1);
411
+ prompts.splice(next, 0, moved);
412
+ return mapCategory(book, categoryId, (target) => ({ ...target, prompts }));
413
+ }
414
+ function withAlwaysToggled(book, promptId, value) {
415
+ const hit = findPrompt(book, promptId);
416
+ if (hit === void 0) return book;
417
+ return withPromptPatched(book, hit.category.id, promptId, { always: value });
418
+ }
419
+
158
420
  // src/client/chords.ts
159
421
  var MODIFIER_KEYS = /* @__PURE__ */ new Set(["Control", "Alt", "Shift", "Meta", "AltGraph"]);
160
422
  var CODE_LABELS = {
@@ -255,6 +517,118 @@ function displayChord(chord) {
255
517
  return chord === "" ? "\u65E0" : chord;
256
518
  }
257
519
 
520
+ // src/client/quick-commands.ts
521
+ var COMPOSER_SELECTOR = "[data-composer-input]";
522
+ var CARD_SELECTOR = "[data-composer-card]";
523
+ var state = { actions: null, draft: "", sessionId: "" };
524
+ function publishInputBridge(next) {
525
+ state.actions = next.actions;
526
+ state.draft = next.draft;
527
+ state.sessionId = next.sessionId;
528
+ }
529
+ function releaseInputBridge(sessionId) {
530
+ if (state.sessionId !== sessionId) return;
531
+ state.actions = null;
532
+ state.draft = "";
533
+ state.sessionId = "";
534
+ }
535
+ function draftFromDom() {
536
+ const el = document.querySelector(COMPOSER_SELECTOR);
537
+ if (!(el instanceof HTMLElement)) return "";
538
+ return el.innerText.replace(/\n+$/, "");
539
+ }
540
+ function currentDraft() {
541
+ return state.actions === null ? draftFromDom() : state.draft;
542
+ }
543
+ function alwaysPrompts(prompts) {
544
+ return prompts.filter((item) => item.always === true && item.prompt.trim() !== "");
545
+ }
546
+ function withAlwaysPrompts(draft, prompts) {
547
+ const picked = alwaysPrompts(prompts);
548
+ if (picked.length === 0) return null;
549
+ const base = draft.replace(/\s+$/, "");
550
+ if (base === "") return null;
551
+ const suffix = picked.map((item) => item.prompt.trim()).join("\n\n");
552
+ if (base.endsWith(suffix)) return null;
553
+ return `${base}
554
+
555
+ ${suffix}`;
556
+ }
557
+ function applyAlwaysPrompts(prompts) {
558
+ const actions = state.actions;
559
+ if (actions === null) return false;
560
+ const next = withAlwaysPrompts(currentDraft(), prompts);
561
+ if (next === null) return false;
562
+ actions.setDraft(next);
563
+ return true;
564
+ }
565
+ function insertIntoDraft(text) {
566
+ const actions = state.actions;
567
+ const body = text.trim();
568
+ if (actions === null || body === "") return false;
569
+ const draft = currentDraft();
570
+ const next = draft.trim() === "" ? body : `${draft.replace(/\s+$/, "")}
571
+ ${body}`;
572
+ actions.setDraft(next);
573
+ return true;
574
+ }
575
+ function replaceDraft(text) {
576
+ const actions = state.actions;
577
+ if (actions === null) return false;
578
+ actions.setDraft(text);
579
+ return true;
580
+ }
581
+ function focusComposer() {
582
+ const el = document.querySelector(COMPOSER_SELECTOR);
583
+ if (el instanceof HTMLElement) el.focus({ preventScroll: true });
584
+ }
585
+ function sendButtonOf(target) {
586
+ if (!(target instanceof Element)) return null;
587
+ const card = target.closest(CARD_SELECTOR);
588
+ if (card === null) return null;
589
+ const buttons = card.querySelectorAll("button");
590
+ if (buttons.length === 0) return null;
591
+ const last = buttons[buttons.length - 1];
592
+ if (!(last instanceof HTMLButtonElement)) return null;
593
+ if (last !== target && !last.contains(target)) return null;
594
+ if (last.disabled) return null;
595
+ if (last.querySelector("svg rect") !== null) return null;
596
+ if (last.querySelector("svg path") === null) return null;
597
+ return last;
598
+ }
599
+ async function optimizeDraft(text, tier) {
600
+ const body = text.trim();
601
+ if (body === "") return { ok: false, error: "\u8F93\u5165\u6846\u662F\u7A7A\u7684\uFF0C\u5148\u5199\u70B9\u4EC0\u4E48\u518D\u4F18\u5316" };
602
+ let response;
603
+ try {
604
+ response = await fetch(OPTIMIZER_API_PATH, {
605
+ method: "POST",
606
+ headers: { "content-type": "application/json" },
607
+ body: JSON.stringify({ text: body, tier })
608
+ });
609
+ } catch (error) {
610
+ return { ok: false, error: `\u8FDE\u4E0D\u4E0A\u5BBF\u4E3B\u534A\u7684\u4F18\u5316\u63A5\u53E3\uFF1A${error instanceof Error ? error.message : String(error)}` };
611
+ }
612
+ if (!response.ok) {
613
+ return { ok: false, error: `\u5BBF\u4E3B\u534A\u8FD4\u56DE HTTP ${response.status}\uFF08\u63D2\u4EF6\u53EF\u80FD\u8FD8\u6CA1\u91CD\u542F\u751F\u6548\uFF09` };
614
+ }
615
+ let payload;
616
+ try {
617
+ payload = await response.json();
618
+ } catch {
619
+ return { ok: false, error: "\u5BBF\u4E3B\u534A\u8FD4\u56DE\u7684\u4E0D\u662F JSON" };
620
+ }
621
+ const record = typeof payload === "object" && payload !== null ? payload : {};
622
+ if (record.ok !== true) {
623
+ const message = typeof record.error === "string" && record.error !== "" ? record.error : "\u4F18\u5316\u5931\u8D25";
624
+ return { ok: false, error: message };
625
+ }
626
+ const optimized = typeof record.text === "string" ? record.text.trim() : "";
627
+ if (optimized === "") return { ok: false, error: "\u6A21\u578B\u6CA1\u6709\u4EA7\u51FA\u4EFB\u4F55\u5185\u5BB9" };
628
+ const route = `${String(record.provider ?? "")}/${String(record.model ?? "")}`;
629
+ return { ok: true, text: optimized, route };
630
+ }
631
+
258
632
  // src/client/interceptors.ts
259
633
  var INPUT_SELECTOR = "[data-composer-input]";
260
634
  var OVERLAY_SELECTOR = "[data-trigger-menu], [data-conversation-composer-overlay]";
@@ -266,6 +640,7 @@ function overlayOpen() {
266
640
  return document.querySelector(OVERLAY_SELECTOR) !== null;
267
641
  }
268
642
  var replaying = false;
643
+ var replayingSendClick = false;
269
644
  var pasteCache = null;
270
645
  function prefetchClipboard() {
271
646
  pasteCache = null;
@@ -306,6 +681,7 @@ function installInterceptors(deps) {
306
681
  event.stopPropagation();
307
682
  if (event.repeat) return;
308
683
  if (gesture === "send") {
684
+ applyAlwaysPrompts(deps.alwaysPrompts());
309
685
  dispatchKey(root, chordToInit("Enter"));
310
686
  } else if (gesture === "newline") {
311
687
  dispatchKey(root, chordToInit("Shift+Enter"));
@@ -329,11 +705,27 @@ function installInterceptors(deps) {
329
705
  deps.setMenu({ x: event.clientX, y: event.clientY, hasSelection });
330
706
  prefetchClipboard();
331
707
  };
708
+ const onClickSend = (event) => {
709
+ if (replayingSendClick) return;
710
+ const button = sendButtonOf(event.target);
711
+ if (button === null) return;
712
+ if (!applyAlwaysPrompts(deps.alwaysPrompts())) return;
713
+ event.preventDefault();
714
+ event.stopPropagation();
715
+ replayingSendClick = true;
716
+ try {
717
+ button.click();
718
+ } finally {
719
+ replayingSendClick = false;
720
+ }
721
+ };
332
722
  window.addEventListener("keydown", onKeyDown, true);
333
723
  window.addEventListener("contextmenu", onContextMenu, true);
724
+ window.addEventListener("click", onClickSend, true);
334
725
  return () => {
335
726
  window.removeEventListener("keydown", onKeyDown, true);
336
727
  window.removeEventListener("contextmenu", onContextMenu, true);
728
+ window.removeEventListener("click", onClickSend, true);
337
729
  };
338
730
  }
339
731
  async function runMenuAction(id) {
@@ -444,6 +836,36 @@ async function pasteText(root) {
444
836
 
445
837
  // src/client/panel.ts
446
838
  var PANEL_SELECTOR = '[role="dialog"][aria-modal="true"]:has(> nav)';
839
+ var RESIZE_LAYER_CLASS = "dsh-ux-resize-layer";
840
+ var RESIZE_OUTLINE_CLASS = "dsh-ux-resize-outline";
841
+ var RESIZE_EDGE_CLASS = "dsh-ux-resize-edge";
842
+ var RESIZE_GRIP_CLASS = "dsh-ux-resize-grip";
843
+ var BAND = 8;
844
+ var INSET = 14;
845
+ var GRIP = 26;
846
+ function handleBox(edge) {
847
+ switch (edge) {
848
+ case "left":
849
+ return { left: 0, top: INSET, bottom: INSET, width: BAND, cursor: "ew-resize" };
850
+ case "right":
851
+ return { right: 0, top: INSET, bottom: INSET, width: BAND, cursor: "ew-resize" };
852
+ case "top":
853
+ return { top: 0, left: INSET, right: INSET, height: BAND, cursor: "ns-resize" };
854
+ case "bottom":
855
+ return { bottom: 0, left: INSET, right: INSET, height: BAND, cursor: "ns-resize" };
856
+ case "br":
857
+ return { right: 8, bottom: 8, width: GRIP, height: GRIP, cursor: "nwse-resize" };
858
+ }
859
+ }
860
+ var RESIZE_OUTLINE_STYLE = {
861
+ position: "absolute",
862
+ inset: 0,
863
+ // 跟随面板自身的圆角(官方 .panel 是 32px)——用 inherit 而不是写死数字,
864
+ // 官方改圆角时这里自动跟上。
865
+ borderRadius: "inherit",
866
+ border: "1px solid rgba(127, 127, 137, .28)",
867
+ pointerEvents: "none"
868
+ };
447
869
  var PANEL_CSS = `
448
870
  html.dsh-ux-panel-scroll [role="dialog"][aria-modal="true"]:has(> nav) { min-height: 320px; }
449
871
  html.dsh-ux-panel-scroll [role="dialog"][aria-modal="true"]:has(> nav) > nav {
@@ -467,10 +889,32 @@ html.dsh-ux-panel-scroll [role="dialog"][aria-modal="true"]:has(> nav) > nav > d
467
889
  background: var(--dsh-scrollbar-thumb-hover, var(--dsw-alias-scrollbar-hover-l2));
468
890
  }
469
891
  `;
892
+ var RESIZE_CSS = `
893
+ .${RESIZE_LAYER_CLASS} { position: absolute; inset: 0; pointer-events: none; }
894
+ .${RESIZE_EDGE_CLASS}, .${RESIZE_GRIP_CLASS} {
895
+ position: absolute;
896
+ pointer-events: auto;
897
+ background: transparent;
898
+ border-radius: 6px;
899
+ transition: background-color .12s ease, color .12s ease;
900
+ touch-action: none;
901
+ }
902
+ .${RESIZE_EDGE_CLASS}:hover { background: rgba(127, 127, 137, .22); }
903
+ .${RESIZE_EDGE_CLASS}[data-active="true"] { background: rgba(127, 127, 137, .3); }
904
+ .${RESIZE_GRIP_CLASS} {
905
+ display: flex;
906
+ align-items: flex-end;
907
+ justify-content: flex-end;
908
+ padding: 3px;
909
+ color: rgba(127, 127, 137, .55);
910
+ }
911
+ .${RESIZE_GRIP_CLASS}:hover { color: rgba(127, 127, 137, .95); background: rgba(127, 127, 137, .16); }
912
+ `;
470
913
  function installPanelStyle(settings, subscribe) {
471
914
  const tag = document.createElement("style");
472
915
  tag.dataset.plugin = "dsh-composer-ux-panel";
473
- tag.textContent = PANEL_CSS;
916
+ tag.textContent = `${PANEL_CSS}
917
+ ${RESIZE_CSS}`;
474
918
  document.head.appendChild(tag);
475
919
  const applyClass = () => {
476
920
  document.documentElement.classList.toggle("dsh-ux-panel-scroll", settings());
@@ -576,6 +1020,45 @@ function installSettingsCardStyle() {
576
1020
  };
577
1021
  }
578
1022
 
1023
+ // src/client/quick-style.ts
1024
+ var QUICK_BUTTON_CLASS = "composer-ux-quick-button";
1025
+ var QUICK_BUTTON_CSS = `
1026
+ .${QUICK_BUTTON_CLASS} {
1027
+ display: inline-flex;
1028
+ align-items: center;
1029
+ gap: 4px;
1030
+ height: 24px;
1031
+ border: 1px solid rgba(127, 127, 137, .35);
1032
+ background: transparent;
1033
+ color: inherit;
1034
+ border-radius: 999px;
1035
+ padding: 0 9px;
1036
+ font-size: 12px;
1037
+ line-height: 1;
1038
+ cursor: pointer;
1039
+ opacity: .75;
1040
+ white-space: nowrap;
1041
+ font-variant-numeric: tabular-nums;
1042
+ transition: opacity .12s ease, background-color .12s ease;
1043
+ }
1044
+ .${QUICK_BUTTON_CLASS}:hover { opacity: 1; background: rgba(127, 127, 137, .12); }
1045
+ .${QUICK_BUTTON_CLASS}[aria-expanded="true"] {
1046
+ opacity: 1;
1047
+ background: rgba(127, 127, 137, .18);
1048
+ border-color: rgba(127, 127, 137, .6);
1049
+ }
1050
+ .${QUICK_BUTTON_CLASS} svg { flex: 0 0 auto; }
1051
+ `;
1052
+ function installQuickButtonStyle() {
1053
+ const tag = document.createElement("style");
1054
+ tag.dataset.plugin = "dsh-composer-ux-quick-button";
1055
+ tag.textContent = QUICK_BUTTON_CSS;
1056
+ document.head.appendChild(tag);
1057
+ return () => {
1058
+ tag.remove();
1059
+ };
1060
+ }
1061
+
579
1062
  // src/client/ContextMenuHost.tsx
580
1063
  var import_react = __toESM(require("react"), 1);
581
1064
 
@@ -708,6 +1191,192 @@ var menuNote = {
708
1191
  lineHeight: 1.4,
709
1192
  padding: "4px 10px 2px"
710
1193
  };
1194
+ var quickPanel = {
1195
+ position: "fixed",
1196
+ zIndex: 9999,
1197
+ width: 420,
1198
+ maxWidth: "calc(100vw - 24px)",
1199
+ maxHeight: "min(60vh, 520px)",
1200
+ display: "flex",
1201
+ flexDirection: "column",
1202
+ background: "var(--dsw-specific-menu)",
1203
+ border: "0.5px solid var(--dsw-alias-border-l1)",
1204
+ borderRadius: 12,
1205
+ boxShadow: "var(--dsw-elevation-prominent)",
1206
+ pointerEvents: "auto",
1207
+ overflow: "hidden"
1208
+ };
1209
+ var quickPanelHead = {
1210
+ padding: "10px 12px 8px",
1211
+ borderBottom: "0.5px solid var(--dsw-alias-border-l2)",
1212
+ display: "flex",
1213
+ flexDirection: "column",
1214
+ gap: 8
1215
+ };
1216
+ var quickPanelTitleRow = {
1217
+ display: "flex",
1218
+ alignItems: "center",
1219
+ justifyContent: "space-between",
1220
+ gap: 8
1221
+ };
1222
+ var quickPanelTitle = {
1223
+ color: "var(--dsw-alias-label-primary)",
1224
+ fontSize: 13,
1225
+ fontWeight: 600
1226
+ };
1227
+ var quickTierRow = {
1228
+ display: "inline-flex",
1229
+ gap: 2,
1230
+ padding: 2,
1231
+ borderRadius: 999,
1232
+ background: "var(--dsw-alias-bg-layer-1)",
1233
+ border: "0.5px solid var(--dsw-alias-border-l2)"
1234
+ };
1235
+ var quickTierButton = {
1236
+ border: "none",
1237
+ background: "transparent",
1238
+ color: "var(--dsw-alias-label-tertiary)",
1239
+ fontSize: 11,
1240
+ lineHeight: 1,
1241
+ padding: "4px 8px",
1242
+ borderRadius: 999,
1243
+ cursor: "pointer",
1244
+ whiteSpace: "nowrap"
1245
+ };
1246
+ var quickTierButtonActive = {
1247
+ ...quickTierButton,
1248
+ background: "var(--dsw-alias-button-primary-fill)",
1249
+ color: "var(--dsw-alias-label-primary-foreground)"
1250
+ };
1251
+ var quickPrimaryButton = {
1252
+ display: "inline-flex",
1253
+ alignItems: "center",
1254
+ justifyContent: "center",
1255
+ gap: 6,
1256
+ width: "100%",
1257
+ padding: "7px 12px",
1258
+ borderRadius: 8,
1259
+ border: "0.5px solid var(--dsw-alias-button-primary-fill)",
1260
+ background: "var(--dsw-alias-button-primary-fill)",
1261
+ color: "var(--dsw-alias-label-primary-foreground)",
1262
+ fontSize: 12,
1263
+ cursor: "pointer"
1264
+ };
1265
+ var quickPrimaryButtonDisabled = {
1266
+ ...quickPrimaryButton,
1267
+ opacity: 0.4,
1268
+ cursor: "default"
1269
+ };
1270
+ var quickList = {
1271
+ overflowY: "auto",
1272
+ padding: "6px 6px 8px",
1273
+ display: "flex",
1274
+ flexDirection: "column",
1275
+ gap: 2
1276
+ };
1277
+ var quickItem = {
1278
+ display: "flex",
1279
+ alignItems: "center",
1280
+ gap: 8,
1281
+ padding: "5px 8px",
1282
+ borderRadius: 7,
1283
+ cursor: "pointer",
1284
+ background: "transparent",
1285
+ border: "none",
1286
+ flex: "1 1 auto",
1287
+ minWidth: 0,
1288
+ textAlign: "left"
1289
+ };
1290
+ var quickItemLabel = {
1291
+ color: "var(--dsw-alias-label-primary)",
1292
+ fontSize: 12,
1293
+ flex: "0 0 auto",
1294
+ whiteSpace: "nowrap"
1295
+ };
1296
+ var quickItemPreview = {
1297
+ color: "var(--dsw-alias-label-tertiary)",
1298
+ fontSize: 11,
1299
+ flex: "1 1 auto",
1300
+ minWidth: 0,
1301
+ overflow: "hidden",
1302
+ textOverflow: "ellipsis",
1303
+ whiteSpace: "nowrap"
1304
+ };
1305
+ var quickAlwaysLabel = {
1306
+ display: "inline-flex",
1307
+ alignItems: "center",
1308
+ gap: 4,
1309
+ flex: "0 0 auto",
1310
+ color: "var(--dsw-alias-label-tertiary)",
1311
+ fontSize: 11,
1312
+ whiteSpace: "nowrap",
1313
+ cursor: "pointer"
1314
+ };
1315
+ var quickAlwaysBox = {
1316
+ margin: 0,
1317
+ cursor: "pointer"
1318
+ };
1319
+ var quickPanelFoot = {
1320
+ padding: "5px 12px 7px",
1321
+ borderTop: "0.5px solid var(--dsw-alias-border-l2)",
1322
+ display: "flex",
1323
+ alignItems: "center",
1324
+ justifyContent: "space-between",
1325
+ gap: 8
1326
+ };
1327
+ var quickNotice = {
1328
+ color: "var(--dsw-alias-state-warn-label)",
1329
+ fontSize: 11,
1330
+ lineHeight: 1.4,
1331
+ minWidth: 0,
1332
+ overflow: "hidden",
1333
+ textOverflow: "ellipsis"
1334
+ };
1335
+ var quickFootHint = {
1336
+ color: "var(--dsw-alias-label-tertiary)",
1337
+ fontSize: 11,
1338
+ whiteSpace: "nowrap",
1339
+ flex: "0 0 auto"
1340
+ };
1341
+ var quickEmpty = {
1342
+ color: "var(--dsw-alias-label-tertiary)",
1343
+ fontSize: 12,
1344
+ lineHeight: 1.6,
1345
+ padding: "14px 10px",
1346
+ textAlign: "center"
1347
+ };
1348
+ var quickCategoryRow = {
1349
+ display: "flex",
1350
+ alignItems: "center",
1351
+ gap: 4,
1352
+ flexWrap: "wrap"
1353
+ };
1354
+ var quickCategoryTab = {
1355
+ border: "0.5px solid var(--dsw-alias-border-l2)",
1356
+ background: "transparent",
1357
+ color: "var(--dsw-alias-label-tertiary)",
1358
+ fontSize: 11,
1359
+ lineHeight: 1,
1360
+ padding: "4px 8px",
1361
+ borderRadius: 999,
1362
+ cursor: "pointer",
1363
+ whiteSpace: "nowrap",
1364
+ maxWidth: 160,
1365
+ overflow: "hidden",
1366
+ textOverflow: "ellipsis"
1367
+ };
1368
+ var quickCategoryTabActive = {
1369
+ ...quickCategoryTab,
1370
+ border: "0.5px solid var(--dsw-alias-brand-primary)",
1371
+ color: "var(--dsw-alias-label-primary)",
1372
+ fontWeight: 600
1373
+ };
1374
+ var quickCategoryAdd = {
1375
+ ...quickCategoryTab,
1376
+ color: "var(--dsw-alias-label-tertiary)",
1377
+ padding: "4px 7px",
1378
+ cursor: "pointer"
1379
+ };
711
1380
 
712
1381
  // src/client/ContextMenuHost.tsx
713
1382
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -844,54 +1513,9 @@ function ContextMenuHost({ useMenu, useLive, actions }) {
844
1513
 
845
1514
  // src/client/PanelResizeHandles.tsx
846
1515
  var import_react2 = require("react");
1516
+ var import_react_dom = require("react-dom");
847
1517
  var import_jsx_runtime2 = require("react/jsx-runtime");
848
- function stripRect(edge, rect) {
849
- const horizontal = (left, width) => ({
850
- left,
851
- top: rect.top + 12,
852
- width,
853
- height: Math.max(0, rect.height - 24),
854
- cursor: "ew-resize"
855
- });
856
- const vertical = (top, height) => ({
857
- left: rect.left + 16,
858
- top,
859
- width: Math.max(0, rect.width - 32),
860
- height,
861
- cursor: "ns-resize"
862
- });
863
- const corner = (left, top, horizontalCursor, verticalCursor) => ({
864
- left,
865
- top,
866
- width: 18,
867
- height: 18,
868
- cursor: horizontalCursor
869
- });
870
- const corners = {
871
- tl: "nwse-resize",
872
- tr: "nesw-resize",
873
- bl: "nesw-resize",
874
- br: "nwse-resize"
875
- };
876
- switch (edge) {
877
- case "left":
878
- return horizontal(rect.left - 4, 8);
879
- case "right":
880
- return horizontal(rect.right - 4, 8);
881
- case "top":
882
- return vertical(rect.top - 4, 8);
883
- case "bottom":
884
- return vertical(rect.bottom - 4, 8);
885
- case "tl":
886
- return corner(rect.left - 6, rect.top - 6, corners.tl, "nwse");
887
- case "tr":
888
- return corner(rect.right - 12, rect.top - 6, corners.tr, "nesw");
889
- case "bl":
890
- return corner(rect.left - 6, rect.bottom - 14, corners.bl, "nesw");
891
- case "br":
892
- return corner(rect.right - 12, rect.bottom - 14, corners.br, "nwse");
893
- }
894
- }
1518
+ var EDGES = ["left", "right", "top", "bottom", "br"];
895
1519
  function PanelResizeHandles({ useLive, actions }) {
896
1520
  const settings = useLive((value) => ({
897
1521
  enabled: value.enabled,
@@ -899,130 +1523,126 @@ function PanelResizeHandles({ useLive, actions }) {
899
1523
  panelWidth: value.panelWidth,
900
1524
  panelHeight: value.panelHeight
901
1525
  }));
902
- const settingsRef = (0, import_react2.useRef)(settings);
903
- settingsRef.current = settings;
904
1526
  const enabled = settings.enabled === true && settings.panelResize === true;
905
- const [rect, setRect] = (0, import_react2.useState)(null);
906
- const appliedRef = (0, import_react2.useRef)(false);
1527
+ const [panel, setPanel] = (0, import_react2.useState)(null);
1528
+ const [active, setActive] = (0, import_react2.useState)(null);
1529
+ const dragRef = (0, import_react2.useRef)(null);
907
1530
  (0, import_react2.useEffect)(() => {
908
1531
  if (!enabled) {
909
- setRect(null);
1532
+ setPanel(null);
910
1533
  return;
911
1534
  }
1535
+ let current = null;
912
1536
  const probe = () => {
913
- const panel = findSettingsPanel();
914
- if (panel === null) {
915
- appliedRef.current = false;
916
- setRect((current) => current === null ? null : null);
917
- return;
918
- }
919
- if (!appliedRef.current) {
920
- appliedRef.current = true;
921
- const width = settingsRef.current.panelWidth;
922
- const height = settingsRef.current.panelHeight;
923
- if (width !== void 0) panel.style.width = `${clampPanelWidth(width)}px`;
924
- if (height !== void 0) panel.style.height = `${clampPanelHeight(height)}px`;
925
- }
926
- setRect(panel.getBoundingClientRect());
1537
+ const found = findSettingsPanel();
1538
+ if (found === current) return;
1539
+ current = found;
1540
+ setPanel(found);
927
1541
  };
928
1542
  probe();
929
1543
  const timer = setInterval(probe, 250);
930
- const onResize = () => {
931
- setRect((current) => current === null ? null : findSettingsPanel()?.getBoundingClientRect() ?? null);
932
- };
933
- window.addEventListener("resize", onResize);
934
1544
  return () => {
935
1545
  clearInterval(timer);
936
- window.removeEventListener("resize", onResize);
937
1546
  };
938
1547
  }, [enabled]);
939
- if (!enabled || rect === null) return null;
1548
+ (0, import_react2.useEffect)(() => {
1549
+ if (panel === null) return;
1550
+ const width = settings.panelWidth;
1551
+ const height = settings.panelHeight;
1552
+ if (width === void 0) panel.style.removeProperty("width");
1553
+ else panel.style.width = `${clampPanelWidth(width)}px`;
1554
+ if (height === void 0) panel.style.removeProperty("height");
1555
+ else panel.style.height = `${clampPanelHeight(height)}px`;
1556
+ }, [panel, settings.panelWidth, settings.panelHeight]);
1557
+ if (!enabled || panel === null) return null;
940
1558
  const onPointerDown = (edge, event) => {
941
1559
  event.preventDefault();
942
1560
  event.stopPropagation();
943
- const panel = findSettingsPanel();
944
- if (panel === null) return;
1561
+ const el = panel;
1562
+ const rect = el.getBoundingClientRect();
1563
+ el.style.position = "fixed";
1564
+ el.style.margin = "0";
1565
+ el.style.left = `${rect.left}px`;
1566
+ el.style.top = `${rect.top}px`;
1567
+ el.style.width = `${rect.width}px`;
1568
+ el.style.height = `${rect.height}px`;
945
1569
  const start = {
946
1570
  edge,
947
- startX: event.clientX,
948
- startY: event.clientY,
949
- startWidth: panel.clientWidth,
950
- startHeight: panel.clientHeight
1571
+ x: event.clientX,
1572
+ y: event.clientY,
1573
+ left: rect.left,
1574
+ top: rect.top,
1575
+ width: rect.width,
1576
+ height: rect.height
951
1577
  };
1578
+ dragRef.current = start;
1579
+ setActive(edge);
1580
+ document.body.style.cursor = String(handleBox(edge).cursor ?? "default");
1581
+ document.body.style.userSelect = "none";
952
1582
  const onMove = (move) => {
953
- if (dragStartRef.current !== start || dragStartRef.current === null) return;
1583
+ if (dragRef.current !== start) return;
954
1584
  move.preventDefault();
955
- const panelEl = findSettingsPanel();
956
- if (panelEl === null) return;
957
- const dx = move.clientX - start.startX;
958
- const dy = move.clientY - start.startY;
959
- let width = start.startWidth;
960
- let height = start.startHeight;
961
- if (edge === "left" || edge === "right" || edge === "tl" || edge === "bl" || edge === "tr" || edge === "br") {
962
- width = start.startWidth + (edge === "left" || edge === "tl" || edge === "bl" ? -dx : dx);
1585
+ const dx = move.clientX - start.x;
1586
+ const dy = move.clientY - start.y;
1587
+ let { left, top, width, height } = start;
1588
+ if (edge === "right" || edge === "br") width = clampPanelWidth(start.width + dx);
1589
+ if (edge === "bottom" || edge === "br") height = clampPanelHeight(start.height + dy);
1590
+ if (edge === "left") {
1591
+ width = clampPanelWidth(start.width - dx);
1592
+ left = start.left + (start.width - width);
963
1593
  }
964
- if (edge === "top" || edge === "bottom" || edge === "tl" || edge === "bl" || edge === "tr" || edge === "br") {
965
- height = start.startHeight + (edge === "top" || edge === "tl" || edge === "tr" ? -dy : dy);
1594
+ if (edge === "top") {
1595
+ height = clampPanelHeight(start.height - dy);
1596
+ top = start.top + (start.height - height);
966
1597
  }
967
- panelEl.style.width = `${Math.round(clampPanelWidth(width))}px`;
968
- panelEl.style.height = `${Math.round(clampPanelHeight(height))}px`;
969
- setRect(panelEl.getBoundingClientRect());
1598
+ el.style.left = `${Math.round(left)}px`;
1599
+ el.style.top = `${Math.round(top)}px`;
1600
+ el.style.width = `${Math.round(width)}px`;
1601
+ el.style.height = `${Math.round(height)}px`;
970
1602
  };
971
1603
  const onUp = () => {
972
1604
  window.removeEventListener("pointermove", onMove);
973
1605
  window.removeEventListener("pointerup", onUp);
974
- dragStartRef.current = null;
975
- const panelEl = findSettingsPanel();
976
- if (panelEl !== null) {
977
- actions.setField(PANEL_WIDTH_FIELD, Math.round(clampPanelWidth(panelEl.clientWidth)));
978
- actions.setField(PANEL_HEIGHT_FIELD, Math.round(clampPanelHeight(panelEl.clientHeight)));
979
- }
1606
+ dragRef.current = null;
1607
+ setActive(null);
1608
+ const box = el.getBoundingClientRect();
1609
+ actions.setField(PANEL_WIDTH_FIELD, Math.round(clampPanelWidth(box.width)));
1610
+ actions.setField(PANEL_HEIGHT_FIELD, Math.round(clampPanelHeight(box.height)));
980
1611
  document.body.style.removeProperty("cursor");
981
1612
  document.body.style.removeProperty("user-select");
982
1613
  };
983
- dragStartRef.current = start;
984
- document.body.style.cursor = stripRect(edge, rect).cursor;
985
- document.body.style.userSelect = "none";
986
1614
  window.addEventListener("pointermove", onMove);
987
1615
  window.addEventListener("pointerup", onUp);
988
1616
  };
989
- const edges = ["left", "right", "top", "bottom", "tl", "tr", "bl", "br"];
990
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: edges.map((edge) => {
991
- const pos = stripRect(edge, rect);
992
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
993
- "div",
994
- {
995
- onPointerDown: (event) => {
996
- onPointerDown(edge, event);
997
- },
998
- onPointerEnter: (event) => {
999
- event.currentTarget.style.background = "var(--dsw-alias-state-business-primary)";
1000
- event.currentTarget.style.opacity = "0.75";
1001
- },
1002
- onPointerLeave: (event) => {
1003
- event.currentTarget.style.background = "transparent";
1004
- event.currentTarget.style.opacity = "0";
1617
+ return (0, import_react_dom.createPortal)(
1618
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: RESIZE_LAYER_CLASS, "data-composer-ux-resize": true, children: [
1619
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: RESIZE_OUTLINE_CLASS, style: RESIZE_OUTLINE_STYLE }),
1620
+ EDGES.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1621
+ "div",
1622
+ {
1623
+ className: edge === "br" ? RESIZE_GRIP_CLASS : RESIZE_EDGE_CLASS,
1624
+ "data-active": active === edge ? "true" : void 0,
1625
+ title: edge === "br" ? "\u62D6\u52A8\u6539\u9762\u677F\u5927\u5C0F" : void 0,
1626
+ style: handleBox(edge),
1627
+ onPointerDown: (event) => {
1628
+ onPointerDown(edge, event);
1629
+ },
1630
+ children: edge === "br" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1631
+ "path",
1632
+ {
1633
+ d: "M11 1 1 11M11 6 6 11",
1634
+ stroke: "currentColor",
1635
+ strokeWidth: "1.3",
1636
+ strokeLinecap: "round"
1637
+ }
1638
+ ) })
1005
1639
  },
1006
- style: {
1007
- position: "fixed",
1008
- zIndex: 9998,
1009
- left: pos.left,
1010
- top: pos.top,
1011
- width: pos.width,
1012
- height: pos.height,
1013
- cursor: pos.cursor,
1014
- background: "transparent",
1015
- opacity: 0,
1016
- borderRadius: 6,
1017
- pointerEvents: "auto",
1018
- touchAction: "none"
1019
- }
1020
- },
1021
- edge
1022
- );
1023
- }) });
1640
+ edge
1641
+ ))
1642
+ ] }),
1643
+ panel
1644
+ );
1024
1645
  }
1025
- var dragStartRef = { current: null };
1026
1646
 
1027
1647
  // src/client/SettingsSection.tsx
1028
1648
  var import_react3 = require("react");
@@ -1247,8 +1867,237 @@ function TextFieldRow(props) {
1247
1867
  ] })
1248
1868
  ] });
1249
1869
  }
1250
- function SettingsSection({ useLive, actions }) {
1870
+ function QuickPromptsEditor(props) {
1871
+ const [draft, setDraft] = (0, import_react3.useState)(() => props.book);
1872
+ const [activeId, setActiveId] = (0, import_react3.useState)(() => props.book.categories[0]?.id ?? "");
1873
+ const [dirty, setDirty] = (0, import_react3.useState)(false);
1874
+ const [newName, setNewName] = (0, import_react3.useState)("");
1875
+ const [note, setNote] = (0, import_react3.useState)("");
1876
+ (0, import_react3.useEffect)(() => {
1877
+ if (dirty) return;
1878
+ setDraft(props.book);
1879
+ if (!props.book.categories.some((category) => category.id === activeId)) {
1880
+ setActiveId(props.book.categories[0]?.id ?? "");
1881
+ }
1882
+ }, [props.book, dirty, activeId]);
1883
+ const edit = (next) => {
1884
+ setDirty(true);
1885
+ setDraft(next);
1886
+ };
1887
+ const current = draft.categories.find((category) => category.id === activeId) ?? draft.categories[0];
1888
+ const counts = bookCounts(draft);
1889
+ const saving = props.status === "saving";
1890
+ const failed = props.status !== "" && props.status !== "saving";
1891
+ const rowBox = {
1892
+ display: "flex",
1893
+ flexDirection: "column",
1894
+ gap: 6,
1895
+ padding: 8,
1896
+ borderRadius: 8,
1897
+ border: "0.5px solid var(--dsw-alias-border-l2)",
1898
+ background: "var(--dsw-alias-bg-layer-1)"
1899
+ };
1900
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1901
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4, flexWrap: "wrap" }, children: [
1902
+ draft.categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1903
+ "button",
1904
+ {
1905
+ type: "button",
1906
+ style: category.id === current?.id ? pillActive : pill,
1907
+ onClick: () => {
1908
+ setActiveId(category.id);
1909
+ },
1910
+ children: [
1911
+ category.name,
1912
+ "\uFF08",
1913
+ category.prompts.length,
1914
+ "\uFF09"
1915
+ ]
1916
+ },
1917
+ category.id
1918
+ )),
1919
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1920
+ "input",
1921
+ {
1922
+ type: "text",
1923
+ value: newName,
1924
+ placeholder: "\u65B0\u5206\u7C7B\u540D",
1925
+ maxLength: QUICK_CATEGORY_NAME_MAX,
1926
+ spellCheck: false,
1927
+ onChange: (event) => {
1928
+ setNewName(event.target.value);
1929
+ },
1930
+ style: { ...textInput, flex: "0 0 110px" }
1931
+ }
1932
+ ),
1933
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1934
+ "button",
1935
+ {
1936
+ type: "button",
1937
+ style: pill,
1938
+ disabled: draft.categories.length >= QUICK_CATEGORY_MAX,
1939
+ onClick: () => {
1940
+ edit(withCategoryAdded(draft, newName === "" ? DEFAULT_CATEGORY_NAME : newName));
1941
+ setNewName("");
1942
+ },
1943
+ children: "+ \u52A0\u5206\u7C7B"
1944
+ }
1945
+ )
1946
+ ] }),
1947
+ current === void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: rowDesc, children: "\u8FD8\u6CA1\u6709\u5206\u7C7B\u3002\u5148\u52A0\u4E00\u4E2A\u5206\u7C7B\uFF0C\u518D\u5F80\u91CC\u653E\u6761\u76EE\u3002" }),
1948
+ current !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1949
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }, children: [
1950
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1951
+ "input",
1952
+ {
1953
+ type: "text",
1954
+ value: current.name,
1955
+ "aria-label": "\u5206\u7C7B\u540D",
1956
+ maxLength: QUICK_CATEGORY_NAME_MAX,
1957
+ spellCheck: false,
1958
+ onChange: (event) => {
1959
+ edit(withCategoryRenamed(draft, current.id, event.target.value));
1960
+ },
1961
+ style: { ...textInput, flex: "0 0 150px" }
1962
+ }
1963
+ ),
1964
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u5206\u7C7B\u4E0A\u79FB", onClick: () => {
1965
+ edit(withCategoryMoved(draft, current.id, -1));
1966
+ }, children: "\u2191" }),
1967
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u5206\u7C7B\u4E0B\u79FB", onClick: () => {
1968
+ edit(withCategoryMoved(draft, current.id, 1));
1969
+ }, children: "\u2193" }),
1970
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1971
+ "button",
1972
+ {
1973
+ type: "button",
1974
+ style: pill,
1975
+ title: "\u5220\u9664\u8FD9\u4E2A\u5206\u7C7B\uFF08\u8FDE\u540C\u5B83\u91CC\u9762\u7684\u6761\u76EE\uFF09",
1976
+ onClick: () => {
1977
+ edit(withCategoryRemoved(draft, current.id));
1978
+ },
1979
+ children: "\u2715 \u5220\u9664\u5206\u7C7B"
1980
+ }
1981
+ ),
1982
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: rowDesc, children: [
1983
+ current.prompts.length,
1984
+ " / ",
1985
+ QUICK_PROMPT_MAX,
1986
+ " \u6761"
1987
+ ] })
1988
+ ] }),
1989
+ current.prompts.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: rowBox, children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1991
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1992
+ "input",
1993
+ {
1994
+ type: "text",
1995
+ value: prompt.label,
1996
+ placeholder: "\u540D\u79F0\uFF0C\u5982\uFF1A\u4EC5\u8BF4\u660E\u539F\u56E0",
1997
+ maxLength: QUICK_LABEL_MAX,
1998
+ spellCheck: false,
1999
+ onChange: (event) => {
2000
+ edit(withPromptPatched(draft, current.id, prompt.id, { label: event.target.value }));
2001
+ },
2002
+ style: { ...textInput, flex: "0 0 150px" }
2003
+ }
2004
+ ),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: 4, ...rowDesc, margin: 0, flex: "0 0 auto" }, children: [
2006
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2007
+ "input",
2008
+ {
2009
+ type: "checkbox",
2010
+ checked: prompt.always,
2011
+ style: { margin: 0, cursor: "pointer" },
2012
+ onChange: (event) => {
2013
+ edit(withPromptPatched(draft, current.id, prompt.id, { always: event.target.checked }));
2014
+ }
2015
+ }
2016
+ ),
2017
+ "\u9ED8\u8BA4\u63D2\u5165"
2018
+ ] }),
2019
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 } }),
2020
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u4E0A\u79FB", onClick: () => {
2021
+ edit(withPromptMoved(draft, current.id, prompt.id, -1));
2022
+ }, children: "\u2191" }),
2023
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u4E0B\u79FB", onClick: () => {
2024
+ edit(withPromptMoved(draft, current.id, prompt.id, 1));
2025
+ }, children: "\u2193" }),
2026
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u5220\u9664\u8FD9\u6761", onClick: () => {
2027
+ edit(withPromptRemoved(draft, current.id, prompt.id));
2028
+ }, children: "\u2715" })
2029
+ ] }),
2030
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2031
+ "textarea",
2032
+ {
2033
+ value: prompt.prompt,
2034
+ placeholder: "\u63D0\u793A\u8BCD\u6B63\u6587\uFF1A\u70B9\u51FB\u8BE5\u6761\u76EE\u65F6\u63D2\u5165\u8F93\u5165\u6846\uFF1B\u52FE\u4E86\u300C\u9ED8\u8BA4\u63D2\u5165\u300D\u5219\u5728\u53D1\u9001\u65F6\u81EA\u52A8\u9644\u52A0\u5230\u6D88\u606F\u672B\u5C3E",
2035
+ maxLength: QUICK_TEXT_MAX,
2036
+ spellCheck: false,
2037
+ rows: Math.min(4, Math.max(2, Math.ceil(prompt.prompt.length / 46))),
2038
+ onChange: (event) => {
2039
+ edit(withPromptPatched(draft, current.id, prompt.id, { prompt: event.target.value }));
2040
+ },
2041
+ style: { ...textInput, resize: "vertical", fontFamily: "inherit", lineHeight: 1.5 }
2042
+ }
2043
+ )
2044
+ ] }, prompt.id)),
2045
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, onClick: () => {
2046
+ edit(withPromptAdded(draft, current.id));
2047
+ }, children: "+ \u6DFB\u52A0\u4E00\u6761" }) })
2048
+ ] }),
2049
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: [
2050
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2051
+ "button",
2052
+ {
2053
+ type: "button",
2054
+ style: dirty ? pillActive : pill,
2055
+ disabled: !dirty || saving,
2056
+ onClick: () => {
2057
+ const next = {
2058
+ version: draft.version,
2059
+ categories: draft.categories.map((category) => ({
2060
+ ...category,
2061
+ prompts: category.prompts.filter((prompt) => prompt.prompt.trim() !== "")
2062
+ }))
2063
+ };
2064
+ const skipped = counts.prompts - bookCounts(next).prompts;
2065
+ setNote(skipped > 0 ? `\u5DF2\u8DF3\u8FC7 ${String(skipped)} \u6761\u6B63\u6587\u4E3A\u7A7A\u7684\u6761\u76EE` : "");
2066
+ props.onSave(next);
2067
+ setDirty(false);
2068
+ },
2069
+ children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : dirty ? "\u4FDD\u5B58\u4FEE\u6539" : "\u5DF2\u4FDD\u5B58"
2070
+ }
2071
+ ),
2072
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, onClick: () => {
2073
+ props.onReload();
2074
+ }, children: "\u91CD\u65B0\u8BFB\u53D6" }),
2075
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, onClick: () => {
2076
+ props.onReset();
2077
+ }, children: "\u6062\u590D\u5185\u7F6E 9 \u6761" }),
2078
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: rowDesc, children: [
2079
+ "\u5171 ",
2080
+ counts.categories,
2081
+ " \u4E2A\u5206\u7C7B \xB7 ",
2082
+ counts.prompts,
2083
+ " \u6761 \xB7 \u9ED8\u8BA4\u63D2\u5165 ",
2084
+ counts.always,
2085
+ " \u6761",
2086
+ note === "" ? "" : ` \xB7 ${note}`,
2087
+ failed ? ` \xB7 ${props.status}` : ""
2088
+ ] })
2089
+ ] }),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: rowDesc, children: [
2091
+ "\u6570\u636E\u5B58\u5728 ",
2092
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "~/.dsh/quick-prompts.json" }),
2093
+ "\uFF08\u4E0E\u4F1A\u8BDD\u3001\u9879\u76EE\u65E0\u5173\uFF0C\u53EF\u5355\u72EC\u5907\u4EFD\u6216\u624B\u5DE5\u7F16\u8F91\uFF0C \u6539\u5B8C\u70B9\u300C\u91CD\u65B0\u8BFB\u53D6\u300D\u5373\u53EF\u52A0\u8F7D\uFF09\u3002"
2094
+ ] })
2095
+ ] });
2096
+ }
2097
+ function SettingsSection({ useLive, useBook, useBookStatus, actions }) {
1251
2098
  const settings = useLive((item) => item);
2099
+ const book = useBook((item) => item);
2100
+ const bookStatus = useBookStatus((item) => item);
1252
2101
  const [conflict, setConflict] = (0, import_react3.useState)(null);
1253
2102
  const setKey = (side, chord) => {
1254
2103
  const other = side === "send" ? settings.newlineKey : settings.sendKey;
@@ -1265,6 +2114,8 @@ function SettingsSection({ useLive, actions }) {
1265
2114
  const menuSummary = settings.menuNative ? "\u5F53\u524D\uFF1A\u7CFB\u7EDF\u539F\u751F\u83DC\u5355\uFF08\u7C98\u8D34\u514D\u6388\u6743\uFF09" : `\u5F53\u524D\uFF1A\u81EA\u5B9A\u4E49\u83DC\u5355 \xB7 ${menuEnabled} / ${MENU_ITEMS.length} \u9879\u5F00\u542F`;
1266
2115
  const panelSummary = `\u5BFC\u822A\u6EDA\u52A8 ${settings.panelScroll ? "\u5F00" : "\u5173"} \xB7 \u8FB9\u7F18\u7F29\u653E ${settings.panelResize ? "\u5F00" : "\u5173"} \xB7 ${settings.panelWidth}\xD7${settings.panelHeight}`;
1267
2116
  const headerSummary = settings.headerEnabled ? settings.headerStatus === "" ? "\u5DF2\u542F\u7528" : settings.headerStatus : "\u672A\u542F\u7528";
2117
+ const quickCounts = bookCounts(book);
2118
+ const quickSummary = `${quickCounts.categories} \u4E2A\u5206\u7C7B \xB7 ${quickCounts.prompts} \u6761 \xB7 \u9ED8\u8BA4\u63D2\u5165 ${quickCounts.always} \u6761 \xB7 \u4F18\u5316\u6863\u4F4D ${OPTIMIZER_TIERS.find((item) => item.id === settings.optimizerTier)?.label ?? "\u9AD8\u7EA7"}`;
1268
2119
  const headerStatusText = settings.headerEnabled ? settings.headerStatus === "" ? "\u7B49\u5F85\u9996\u6B21\u5199\u5165\u2026" : settings.headerStatus : "\u672A\u542F\u7528\uFF08\u6253\u5F00\u4E0A\u65B9\u5F00\u5173\u5373\u5199\u5165\uFF09";
1269
2120
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: "4px 2px" }, children: [
1270
2121
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "dsh-ux-card", children: [
@@ -1372,6 +2223,50 @@ function SettingsSection({ useLive, actions }) {
1372
2223
  ) })
1373
2224
  ] })
1374
2225
  ] }),
2226
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(FoldCard, { name: "\u5FEB\u6377\u6307\u4EE4", summary: quickSummary, children: [
2227
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: bodyLead, children: "\u8F93\u5165\u6846\u5DE5\u5177\u884C\u91CC\u90A3\u4E2A\u300C\u5FEB\u6377\u6307\u4EE4\u300D\u6309\u94AE\u70B9\u5F00\u5C31\u662F\u8FD9\u5F20\u6E05\u5355\uFF1A\u70B9\u6761\u76EE\u628A\u5185\u5BB9\u63D2\u5165\u8F93\u5165\u6846\uFF1B \u6761\u76EE\u53F3\u4FA7\u52FE\u4E0A\u300C\u9ED8\u8BA4\u63D2\u5165\u300D\uFF0C\u5219\u5728\u4F60**\u70B9\u53D1\u9001\u65F6**\u628A\u8FD9\u6761\u63D0\u793A\u8BCD\u81EA\u52A8\u9644\u52A0\u5230\u6D88\u606F**\u672B\u5C3E** \u4E00\u8D77\u53D1\u51FA\u53BB\uFF08\u591A\u6761\u6309\u5217\u8868\u987A\u5E8F\u62FC\u63A5\uFF0C\u8F93\u5165\u6846\u91CC\u4E0D\u63D0\u524D\u663E\u793A\uFF09\u3002 \u300C\u4F18\u5316\u63D0\u793A\u8BCD\u300D\u4F1A\u7528\u53E6\u4E00\u4E2A AI \u628A\u8F93\u5165\u6846\u91CC\u7684\u8BDD\u6574\u7406\u6210\u4E00\u6761\u80FD\u76F4\u63A5\u53D1\u51FA\u53BB\u7684\u6E05\u6670\u6307\u4EE4\uFF0C \u7ED3\u679C\u76F4\u63A5\u5199\u56DE\u8F93\u5165\u6846\uFF08Ctrl+Z \u53EF\u8FD8\u539F\uFF09\u2014\u2014\u4E0D\u4F1A\u6C61\u67D3\u5F53\u524D\u5BF9\u8BDD\uFF0C\u4E5F\u4E0D\u5360\u4F60\u7684\u5BF9\u8BDD\u8F6E\u6B21\u3002" }),
2228
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...row, borderTop: "none", flexDirection: "column", alignItems: "stretch", gap: 8 }, children: [
2229
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: rowText, children: [
2230
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowTitle, children: "\u4F18\u5316\u5F3A\u5EA6" }),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowDesc, children: OPTIMIZER_TIERS.find((item) => item.id === settings.optimizerTier)?.hint ?? "" })
2232
+ ] }),
2233
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: OPTIMIZER_TIERS.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2234
+ "button",
2235
+ {
2236
+ type: "button",
2237
+ title: item.hint,
2238
+ style: settings.optimizerTier === item.id ? pillActive : pill,
2239
+ onClick: () => {
2240
+ actions.setField(OPTIMIZER_TIER_FIELD, item.id);
2241
+ },
2242
+ children: item.label
2243
+ },
2244
+ item.id
2245
+ )) })
2246
+ ] }),
2247
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...row, borderTop: "none", flexDirection: "column", alignItems: "stretch", gap: 8 }, children: [
2248
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: rowText, children: [
2249
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowTitle, children: "\u5FEB\u6377\u6307\u4EE4\u6E05\u5355" }),
2250
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowDesc, children: "\u5206\u7C7B\u5728\u8FD9\u91CC\u7BA1\u7406\uFF08\u9762\u677F\u91CC\u53EA\u505A\u5207\u6362\uFF09\uFF1B\u5185\u7F6E 9 \u6761\u53EF\u4EE5\u76F4\u63A5\u7528\uFF0C\u6539\u5B8C\u8BB0\u5F97\u70B9\u300C\u4FDD\u5B58\u4FEE\u6539\u300D\u3002" })
2251
+ ] }),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2253
+ QuickPromptsEditor,
2254
+ {
2255
+ book,
2256
+ status: bookStatus,
2257
+ onSave: (next) => {
2258
+ actions.saveBook(next);
2259
+ },
2260
+ onReload: () => {
2261
+ actions.reloadBook();
2262
+ },
2263
+ onReset: () => {
2264
+ actions.resetBook();
2265
+ }
2266
+ }
2267
+ )
2268
+ ] })
2269
+ ] }),
1375
2270
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(FoldCard, { name: "\u8BBE\u7F6E\u9762\u677F", summary: panelSummary, children: [
1376
2271
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: bodyLead, children: "\u63D2\u4EF6\u88C5\u5F97\u591A\u65F6\u8BBE\u7F6E\u6761\u76EE\u5F88\u957F\uFF1A\u5F00\u542F\u300C\u5BFC\u822A\u53EF\u6EDA\u52A8\u300D\u540E\uFF0C\u5DE6\u4FA7\u5BFC\u822A\u5728\u6EA2\u51FA\u65F6\u4F1A\u51FA\u73B0\u6EDA\u52A8\u6761\u3002 \u5F00\u542F\u300C\u8FB9\u7F18\u8C03\u6574\u5927\u5C0F\u300D\u540E\uFF0C\u628A\u9F20\u6807\u79FB\u5230\u8BBE\u7F6E\u9762\u677F\u7684\u8FB9\u6216\u89D2\u4E0A\uFF08\u51FA\u73B0\u9AD8\u4EAE\u6216\u5149\u6807\u53D8\u5316\uFF09\u62D6\u52A8\u5373\u53EF\u6539\u53D8\u9762\u677F\u5927\u5C0F\uFF0C \u5C3A\u5BF8\u4F1A\u8BB0\u4F4F\uFF0C\u4E0B\u6B21\u6253\u5F00\u4FDD\u6301\u3002" }),
1377
2272
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1505,6 +2400,288 @@ function SettingsSection({ useLive, actions }) {
1505
2400
  ] });
1506
2401
  }
1507
2402
 
2403
+ // src/client/QuickCommandsButton.tsx
2404
+ var import_react4 = require("react");
2405
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2406
+ function QuickCommandsButton({
2407
+ useInput,
2408
+ inputActions,
2409
+ sessionId,
2410
+ useLive,
2411
+ usePanel,
2412
+ actions
2413
+ }) {
2414
+ const settings = useLive((item) => item);
2415
+ const anchor = usePanel((item) => item);
2416
+ const draft = useInput((state2) => state2.draft);
2417
+ const ref = (0, import_react4.useRef)(null);
2418
+ (0, import_react4.useEffect)(() => {
2419
+ const sid = typeof sessionId === "string" ? sessionId : "";
2420
+ publishInputBridge({
2421
+ actions: inputActions ?? null,
2422
+ draft: typeof draft === "string" ? draft : "",
2423
+ sessionId: sid
2424
+ });
2425
+ return () => {
2426
+ releaseInputBridge(sid);
2427
+ };
2428
+ }, [inputActions, draft, sessionId]);
2429
+ if (settings.enabled !== true) return null;
2430
+ const open = anchor !== null;
2431
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2432
+ "button",
2433
+ {
2434
+ ref,
2435
+ type: "button",
2436
+ className: QUICK_BUTTON_CLASS,
2437
+ "aria-haspopup": "dialog",
2438
+ "aria-expanded": open,
2439
+ title: "\u5FEB\u6377\u6307\u4EE4\uFF1A\u4E00\u952E\u63D2\u5165\u5E38\u7528\u63D0\u793A\u8BCD\uFF0C\u6216\u628A\u8F93\u5165\u6846\u91CC\u7684\u8BDD\u4F18\u5316\u6210\u4E00\u6761\u6E05\u6670\u7684\u6307\u4EE4",
2440
+ onMouseDown: (event) => {
2441
+ event.preventDefault();
2442
+ },
2443
+ onClick: () => {
2444
+ const rect = ref.current?.getBoundingClientRect();
2445
+ actions.toggle({
2446
+ left: rect?.left ?? 0,
2447
+ bottom: rect?.bottom ?? 0,
2448
+ width: rect?.width ?? 0
2449
+ });
2450
+ },
2451
+ children: [
2452
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2453
+ "svg",
2454
+ {
2455
+ viewBox: "0 0 16 16",
2456
+ width: "12",
2457
+ height: "12",
2458
+ "aria-hidden": true,
2459
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2460
+ "path",
2461
+ {
2462
+ d: "M9.2 1.2 3.4 9.1h3.5l-0.9 5.7 6-8.1H8.4l0.8-5.5Z",
2463
+ fill: "currentColor"
2464
+ }
2465
+ )
2466
+ }
2467
+ ),
2468
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u5FEB\u6377\u6307\u4EE4" })
2469
+ ]
2470
+ }
2471
+ );
2472
+ }
2473
+
2474
+ // src/client/QuickCommandsPanel.tsx
2475
+ var import_react5 = require("react");
2476
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2477
+ var MARGIN = 8;
2478
+ var GAP = 8;
2479
+ function QuickCommandsPanel({
2480
+ useLive,
2481
+ usePanel,
2482
+ useBusy,
2483
+ useNotice,
2484
+ useBook,
2485
+ useBookStatus,
2486
+ actions
2487
+ }) {
2488
+ const settings = useLive((item) => item);
2489
+ const anchor = usePanel((item) => item);
2490
+ const busy = useBusy((item) => item);
2491
+ const notice = useNotice((item) => item);
2492
+ const book = useBook((item) => item);
2493
+ const status = useBookStatus((item) => item);
2494
+ const ref = (0, import_react5.useRef)(null);
2495
+ const [position, setPosition] = (0, import_react5.useState)(null);
2496
+ const [activeId, setActiveId] = (0, import_react5.useState)("");
2497
+ const counts = bookCounts(book);
2498
+ const categories = book.categories;
2499
+ const active = categories.find((category) => category.id === activeId) ?? categories[0];
2500
+ (0, import_react5.useLayoutEffect)(() => {
2501
+ if (anchor === null) {
2502
+ setPosition(null);
2503
+ return;
2504
+ }
2505
+ const el = ref.current;
2506
+ const height = el === null ? 320 : el.getBoundingClientRect().height;
2507
+ const width = el === null ? 420 : el.getBoundingClientRect().width;
2508
+ const left = Math.max(MARGIN, Math.min(anchor.left, window.innerWidth - width - MARGIN));
2509
+ const above = anchor.bottom - height - GAP;
2510
+ const top = above >= MARGIN ? above : Math.min(anchor.bottom + GAP, window.innerHeight - height - MARGIN);
2511
+ setPosition({ left, top: Math.max(MARGIN, top) });
2512
+ }, [anchor?.left, anchor?.bottom, counts.prompts, categories.length, busy]);
2513
+ (0, import_react5.useEffect)(() => {
2514
+ if (anchor === null) return;
2515
+ const onPointerDown = (event) => {
2516
+ if (ref.current !== null && event.target instanceof Node && ref.current.contains(event.target)) return;
2517
+ if (event.target instanceof Element && event.target.closest(".composer-ux-quick-button") !== null) return;
2518
+ actions.close();
2519
+ };
2520
+ const onEscape = (event) => {
2521
+ if (event.key !== "Escape") return;
2522
+ event.preventDefault();
2523
+ event.stopPropagation();
2524
+ actions.close();
2525
+ };
2526
+ window.addEventListener("pointerdown", onPointerDown, true);
2527
+ window.addEventListener("keydown", onEscape, true);
2528
+ window.addEventListener("resize", actions.close);
2529
+ return () => {
2530
+ window.removeEventListener("pointerdown", onPointerDown, true);
2531
+ window.removeEventListener("keydown", onEscape, true);
2532
+ window.removeEventListener("resize", actions.close);
2533
+ };
2534
+ }, [anchor === null, actions]);
2535
+ if (anchor === null) return null;
2536
+ const tier = settings.optimizerTier;
2537
+ const items = active?.prompts ?? [];
2538
+ const failure = status !== "" && status !== "saving" ? status : "";
2539
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2540
+ "div",
2541
+ {
2542
+ ref,
2543
+ role: "dialog",
2544
+ "aria-label": "\u5FEB\u6377\u6307\u4EE4",
2545
+ style: { ...quickPanel, left: position?.left ?? anchor.left, top: position?.top ?? anchor.bottom },
2546
+ children: [
2547
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickPanelHead, children: [
2548
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickPanelTitleRow, children: [
2549
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickPanelTitle, children: "\u5FEB\u6377\u6307\u4EE4" }),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: quickTierRow, role: "group", "aria-label": "\u4F18\u5316\u5F3A\u5EA6", children: OPTIMIZER_TIERS.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2551
+ "button",
2552
+ {
2553
+ type: "button",
2554
+ title: item.hint,
2555
+ "aria-pressed": tier === item.id,
2556
+ style: tier === item.id ? quickTierButtonActive : quickTierButton,
2557
+ onMouseDown: (event) => {
2558
+ event.preventDefault();
2559
+ },
2560
+ onClick: () => {
2561
+ actions.setTier(item.id);
2562
+ },
2563
+ children: item.label
2564
+ },
2565
+ item.id
2566
+ )) })
2567
+ ] }),
2568
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2569
+ "button",
2570
+ {
2571
+ type: "button",
2572
+ disabled: busy,
2573
+ title: "\u628A\u8F93\u5165\u6846\u91CC\u7684\u8BDD\u4EA4\u7ED9\u53E6\u4E00\u4E2A AI \u6574\u7406\u6210\u4E00\u6761\u53EF\u4EE5\u76F4\u63A5\u53D1\u51FA\u53BB\u7684\u6E05\u6670\u6307\u4EE4\uFF0C\u7ED3\u679C\u76F4\u63A5\u5199\u56DE\u8F93\u5165\u6846",
2574
+ style: busy ? quickPrimaryButtonDisabled : quickPrimaryButton,
2575
+ onMouseDown: (event) => {
2576
+ event.preventDefault();
2577
+ },
2578
+ onClick: () => {
2579
+ actions.optimize();
2580
+ },
2581
+ children: [
2582
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": true, children: "\u2728" }),
2583
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: busy ? "\u4F18\u5316\u4E2D\u2026" : "\u4F18\u5316\u63D0\u793A\u8BCD" })
2584
+ ]
2585
+ }
2586
+ ),
2587
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickCategoryRow, children: [
2588
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2589
+ "button",
2590
+ {
2591
+ type: "button",
2592
+ title: `${category.name}\uFF08${String(category.prompts.length)} \u6761\uFF09`,
2593
+ "aria-pressed": category.id === active?.id,
2594
+ style: category.id === active?.id ? quickCategoryTabActive : quickCategoryTab,
2595
+ onMouseDown: (event) => {
2596
+ event.preventDefault();
2597
+ },
2598
+ onClick: () => {
2599
+ setActiveId(category.id);
2600
+ },
2601
+ children: category.name
2602
+ },
2603
+ category.id
2604
+ )),
2605
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2606
+ "button",
2607
+ {
2608
+ type: "button",
2609
+ style: quickCategoryAdd,
2610
+ disabled: categories.length >= QUICK_CATEGORY_MAX,
2611
+ title: "\u65B0\u589E\u4E00\u4E2A\u5206\u7C7B\uFF08\u540D\u5B57\u5230\u300C\u8BBE\u7F6E \u2192 \u8F93\u5165\u4F53\u9A8C\u300D\u91CC\u6539\uFF09",
2612
+ onMouseDown: (event) => {
2613
+ event.preventDefault();
2614
+ },
2615
+ onClick: () => {
2616
+ actions.addCategory(DEFAULT_CATEGORY_NAME);
2617
+ },
2618
+ children: "\uFF0B"
2619
+ }
2620
+ )
2621
+ ] })
2622
+ ] }),
2623
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickList, children: [
2624
+ items.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickEmpty, children: [
2625
+ "\u8FD9\u4E2A\u5206\u7C7B\u91CC\u8FD8\u6CA1\u6709\u6761\u76EE\u3002",
2626
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("br", {}),
2627
+ "\u5728\u300C\u8BBE\u7F6E \u2192 \u8F93\u5165\u4F53\u9A8C \u2192 \u5FEB\u6377\u6307\u4EE4\u6E05\u5355\u300D\u91CC\u6DFB\u52A0\u3002"
2628
+ ] }),
2629
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2631
+ "button",
2632
+ {
2633
+ type: "button",
2634
+ title: item.prompt,
2635
+ style: quickItem,
2636
+ onMouseDown: (event) => {
2637
+ event.preventDefault();
2638
+ },
2639
+ onClick: () => {
2640
+ actions.insert(item.prompt);
2641
+ },
2642
+ children: [
2643
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickItemLabel, children: item.label }),
2644
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickItemPreview, children: item.prompt.replace(/\s+/g, " ") })
2645
+ ]
2646
+ }
2647
+ ),
2648
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: quickAlwaysLabel, title: "\u52FE\u4E0A\u540E\uFF0C\u70B9\u53D1\u9001\u65F6\u8FD9\u6761\u63D0\u793A\u8BCD\u4F1A\u81EA\u52A8\u9644\u52A0\u5230\u4F60\u7684\u6D88\u606F\u672B\u5C3E\uFF08\u5BF9\u6240\u6709\u5206\u7C7B\u751F\u6548\uFF09", children: [
2649
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2650
+ "input",
2651
+ {
2652
+ type: "checkbox",
2653
+ style: quickAlwaysBox,
2654
+ checked: item.always,
2655
+ onChange: (event) => {
2656
+ actions.setAlways(item.id, event.target.checked);
2657
+ }
2658
+ }
2659
+ ),
2660
+ "\u9ED8\u8BA4\u63D2\u5165"
2661
+ ] })
2662
+ ] }, item.id))
2663
+ ] }),
2664
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickPanelFoot, children: [
2665
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickNotice, role: "status", children: notice !== "" ? notice : failure }),
2666
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2667
+ "span",
2668
+ {
2669
+ style: quickFootHint,
2670
+ title: "\u6570\u636E\u5728 ~/.dsh/quick-prompts.json",
2671
+ children: [
2672
+ counts.categories,
2673
+ " \u5206\u7C7B \xB7 ",
2674
+ counts.prompts,
2675
+ " \u6761"
2676
+ ]
2677
+ }
2678
+ )
2679
+ ] })
2680
+ ]
2681
+ }
2682
+ );
2683
+ }
2684
+
1508
2685
  // src/client.tsx
1509
2686
  var name = "composer-ux";
1510
2687
  var inject = ["slots", "settingsScope"];
@@ -1513,6 +2690,9 @@ function apply(ctx) {
1513
2690
  const scope = ctx.settingsScope.bind({ namespace: NAMESPACE });
1514
2691
  const live = (0, import_dsh_client_store.createSnapshotStore)({ ...DEFAULT_SETTINGS });
1515
2692
  const menu2 = (0, import_dsh_client_store.createSnapshotStore)(null);
2693
+ const panel = (0, import_dsh_client_store.createSnapshotStore)(null);
2694
+ const optimizing = (0, import_dsh_client_store.createSnapshotStore)(false);
2695
+ const panelNotice = (0, import_dsh_client_store.createSnapshotStore)("");
1516
2696
  const sync = () => {
1517
2697
  const snapshot = scope.getSnapshot();
1518
2698
  live.set(snapshot.status === "ready" && snapshot.value !== void 0 ? { ...DEFAULT_SETTINGS, ...sanitizeSettings(snapshot.value) } : { ...DEFAULT_SETTINGS });
@@ -1529,6 +2709,73 @@ function apply(ctx) {
1529
2709
  console.error("[composer-ux] settings clear failed", error);
1530
2710
  });
1531
2711
  };
2712
+ const book = (0, import_dsh_client_store.createSnapshotStore)(defaultQuickBook());
2713
+ const bookStatus = (0, import_dsh_client_store.createSnapshotStore)("");
2714
+ const reloadBook = async () => {
2715
+ const reply = await loadPromptBook();
2716
+ if (reply.ok && reply.book !== void 0) {
2717
+ book.set(reply.book);
2718
+ bookStatus.set("");
2719
+ return;
2720
+ }
2721
+ bookStatus.set(reply.error ?? "\u8BFB\u53D6\u5FEB\u6377\u6307\u4EE4\u5931\u8D25");
2722
+ };
2723
+ const commitBook = (next) => {
2724
+ book.set(next);
2725
+ bookStatus.set("saving");
2726
+ void savePromptBook(next).then(
2727
+ (reply) => {
2728
+ if (reply.ok && reply.book !== void 0) {
2729
+ book.set(reply.book);
2730
+ bookStatus.set("");
2731
+ return;
2732
+ }
2733
+ bookStatus.set(reply.error ?? "\u4FDD\u5B58\u5931\u8D25");
2734
+ void reloadBook();
2735
+ },
2736
+ (error) => {
2737
+ bookStatus.set(error instanceof Error ? error.message : String(error));
2738
+ void reloadBook();
2739
+ }
2740
+ );
2741
+ };
2742
+ void reloadBook();
2743
+ const bookActions = {
2744
+ reload: () => {
2745
+ void reloadBook();
2746
+ },
2747
+ addCategory: (name2) => {
2748
+ commitBook(withCategoryAdded(book.getSnapshot(), name2));
2749
+ },
2750
+ renameCategory: (id, name2) => {
2751
+ commitBook(withCategoryRenamed(book.getSnapshot(), id, name2));
2752
+ },
2753
+ removeCategory: (id) => {
2754
+ commitBook(withCategoryRemoved(book.getSnapshot(), id));
2755
+ },
2756
+ moveCategory: (id, delta) => {
2757
+ commitBook(withCategoryMoved(book.getSnapshot(), id, delta));
2758
+ },
2759
+ addPrompt: (categoryId) => {
2760
+ commitBook(withPromptAdded(book.getSnapshot(), categoryId));
2761
+ },
2762
+ patchPrompt: (categoryId, id, patch) => {
2763
+ commitBook(withPromptPatched(book.getSnapshot(), categoryId, id, patch));
2764
+ },
2765
+ removePrompt: (categoryId, id) => {
2766
+ commitBook(withPromptRemoved(book.getSnapshot(), categoryId, id));
2767
+ },
2768
+ movePrompt: (categoryId, id, delta) => {
2769
+ commitBook(withPromptMoved(book.getSnapshot(), categoryId, id, delta));
2770
+ },
2771
+ resetBook: () => {
2772
+ commitBook(defaultQuickBook());
2773
+ },
2774
+ /** 设置页「保存」:把编辑器里的整本一次写回(而不是每敲一个字就写盘)。 */
2775
+ saveBook: (next) => {
2776
+ commitBook(next);
2777
+ }
2778
+ };
1532
2779
  const resetAll = () => {
1533
2780
  void scope.mutate(
1534
2781
  [
@@ -1544,11 +2791,79 @@ function apply(ctx) {
1544
2791
  HEADER_ENABLED_FIELD,
1545
2792
  HEADER_NAME_FIELD,
1546
2793
  HEADER_VALUE_FIELD,
1547
- HEADER_ROUTES_FIELD
2794
+ HEADER_ROUTES_FIELD,
2795
+ OPTIMIZER_TIER_FIELD
1548
2796
  ].map((field) => ({ op: "unset", path: [field] }))
1549
2797
  ).catch((error) => {
1550
2798
  console.error("[composer-ux] settings reset failed", error);
1551
2799
  });
2800
+ bookActions.resetBook();
2801
+ };
2802
+ let noticeTimer;
2803
+ const note = (message, ms = 4500) => {
2804
+ panelNotice.set(message);
2805
+ if (noticeTimer !== void 0) clearTimeout(noticeTimer);
2806
+ if (message === "") return;
2807
+ noticeTimer = setTimeout(() => {
2808
+ panelNotice.set("");
2809
+ }, ms);
2810
+ };
2811
+ ctx.effect(() => () => {
2812
+ if (noticeTimer !== void 0) clearTimeout(noticeTimer);
2813
+ }, "composer-ux: quick notice timer");
2814
+ const quickActions = {
2815
+ toggle: (anchor) => {
2816
+ if (panel.getSnapshot() !== null) {
2817
+ panel.set(null);
2818
+ panelNotice.set("");
2819
+ return;
2820
+ }
2821
+ panelNotice.set("");
2822
+ panel.set(anchor);
2823
+ },
2824
+ close: () => {
2825
+ panel.set(null);
2826
+ panelNotice.set("");
2827
+ },
2828
+ insert: (text) => {
2829
+ if (insertIntoDraft(text)) focusComposer();
2830
+ },
2831
+ optimize: () => {
2832
+ if (optimizing.getSnapshot()) return;
2833
+ const draft = currentDraft();
2834
+ if (draft.trim() === "") {
2835
+ note("\u8F93\u5165\u6846\u662F\u7A7A\u7684\uFF1A\u5148\u5199\u70B9\u4EC0\u4E48\uFF0C\u518D\u70B9\u4F18\u5316");
2836
+ return;
2837
+ }
2838
+ optimizing.set(true);
2839
+ note("\u6B63\u5728\u4F18\u5316\u2026");
2840
+ void optimizeDraft(draft, live.getSnapshot().optimizerTier).then(
2841
+ (result) => {
2842
+ optimizing.set(false);
2843
+ if (!result.ok) {
2844
+ note(`\u4F18\u5316\u5931\u8D25\uFF1A${result.error ?? "\u672A\u77E5\u539F\u56E0"}`);
2845
+ return;
2846
+ }
2847
+ replaceDraft(result.text ?? "");
2848
+ focusComposer();
2849
+ note(`\u5DF2\u5199\u56DE\u8F93\u5165\u6846\uFF08${result.route}\uFF09\xB7 Ctrl+Z \u53EF\u8FD8\u539F`);
2850
+ },
2851
+ (error) => {
2852
+ optimizing.set(false);
2853
+ note(`\u4F18\u5316\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
2854
+ }
2855
+ );
2856
+ },
2857
+ setTier: (tier) => {
2858
+ setField(OPTIMIZER_TIER_FIELD, tier);
2859
+ },
2860
+ /**
2861
+ * 「默认插入」勾选:按 id 跨分类找那一条,直接写盘。
2862
+ * 这个标记就是「点发送时自动附加到消息末尾」的依据(见 interceptors.ts)。
2863
+ */
2864
+ setAlways: (id, value) => {
2865
+ commitBook(withAlwaysToggled(book.getSnapshot(), id, value));
2866
+ }
1552
2867
  };
1553
2868
  slots.inject("settings.section", () => slots.register({
1554
2869
  name: "settings.section",
@@ -1556,8 +2871,15 @@ function apply(ctx) {
1556
2871
  order: 40,
1557
2872
  label: "\u8F93\u5165\u4F53\u9A8C",
1558
2873
  inject: () => ({
1559
- hooks: { live },
1560
- actions: { setField, clearField, resetAll }
2874
+ hooks: { live, book, bookStatus },
2875
+ actions: {
2876
+ setField,
2877
+ clearField,
2878
+ resetAll,
2879
+ saveBook: bookActions.saveBook,
2880
+ reloadBook: bookActions.reload,
2881
+ resetBook: bookActions.resetBook
2882
+ }
1561
2883
  })
1562
2884
  }, SettingsSection));
1563
2885
  slots.inject("shell.overlay", () => slots.register({
@@ -1583,7 +2905,39 @@ function apply(ctx) {
1583
2905
  id: "composer-ux-panel-resize",
1584
2906
  inject: () => ({ hooks: { live }, actions: { setField } })
1585
2907
  }, PanelResizeHandles));
2908
+ slots.inject("conversation.input.right", () => slots.register({
2909
+ name: "conversation.input.right",
2910
+ id: "composer-ux-quick",
2911
+ order: 89,
2912
+ label: "\u5FEB\u6377\u6307\u4EE4",
2913
+ inject: () => ({
2914
+ hooks: { live, panel },
2915
+ actions: { toggle: quickActions.toggle }
2916
+ })
2917
+ }, QuickCommandsButton));
2918
+ slots.inject("shell.overlay", () => slots.register({
2919
+ name: "shell.overlay",
2920
+ id: "composer-ux-quick-panel",
2921
+ inject: () => ({
2922
+ hooks: { live, panel, busy: optimizing, notice: panelNotice, book, bookStatus },
2923
+ actions: {
2924
+ toggle: quickActions.toggle,
2925
+ close: quickActions.close,
2926
+ insert: quickActions.insert,
2927
+ optimize: quickActions.optimize,
2928
+ setTier: quickActions.setTier,
2929
+ setAlways: quickActions.setAlways,
2930
+ toggleAlways: quickActions.setAlways,
2931
+ addCategory: bookActions.addCategory,
2932
+ renameCategory: bookActions.renameCategory,
2933
+ removeCategory: bookActions.removeCategory,
2934
+ moveCategory: bookActions.moveCategory,
2935
+ reloadBook: bookActions.reload
2936
+ }
2937
+ })
2938
+ }, QuickCommandsPanel));
1586
2939
  ctx.effect(() => installSettingsCardStyle(), "composer-ux: settings card style");
2940
+ ctx.effect(() => installQuickButtonStyle(), "composer-ux: quick button style");
1587
2941
  ctx.effect(
1588
2942
  () => installPanelStyle(
1589
2943
  () => {
@@ -1601,8 +2955,9 @@ function apply(ctx) {
1601
2955
  if (enabled && dispose === null) {
1602
2956
  dispose = installInterceptors({
1603
2957
  settings: () => live.getSnapshot(),
1604
- setMenu: (state) => {
1605
- menu2.set(state);
2958
+ alwaysPrompts: () => alwaysQuickPrompts(book.getSnapshot()),
2959
+ setMenu: (state2) => {
2960
+ menu2.set(state2);
1606
2961
  },
1607
2962
  menuOpen: () => menu2.getSnapshot() !== null
1608
2963
  });
@@ -1610,8 +2965,10 @@ function apply(ctx) {
1610
2965
  dispose?.();
1611
2966
  dispose = null;
1612
2967
  menu2.set(null);
2968
+ panel.set(null);
1613
2969
  }
1614
2970
  if (live.getSnapshot().menuNative) menu2.set(null);
2971
+ if (!live.getSnapshot().enabled) panel.set(null);
1615
2972
  };
1616
2973
  syncInterceptors();
1617
2974
  const unsubscribe = live.subscribe(syncInterceptors);