dsh-composer-ux 0.1.4 → 0.2.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,32 @@ 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_LABEL_MAX = 40;
64
+ var QUICK_TEXT_MAX = 4e3;
65
+ var OPTIMIZER_API_PATH = "/composer-ux/optimize";
66
+ var OPTIMIZER_TIERS = [
67
+ { 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" },
68
+ { 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" },
69
+ { 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" }
70
+ ];
71
+ var DEFAULT_OPTIMIZER_TIER = "advanced";
72
+ var DEFAULT_QUICK_PROMPTS = [
73
+ { 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 },
74
+ { 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 },
75
+ { 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 },
76
+ { 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 },
77
+ { 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 },
78
+ { 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 },
79
+ { 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 },
80
+ { id: "builtin-8", label: "\u5199\u6D4B\u8BD5", prompt: "\u8BF7\u4E3A\u4E0B\u9762\u7684\u4EE3\u7801\u7F16\u5199\u5355\u5143\u6D4B\u8BD5\u3002", always: false },
81
+ { 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 }
82
+ ];
83
+ function newQuickPromptId() {
84
+ return `qp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
85
+ }
60
86
  var HEADER_ENABLED_FIELD = "headerEnabled";
61
87
  var HEADER_NAME_FIELD = "headerName";
62
88
  var HEADER_VALUE_FIELD = "headerValue";
@@ -87,7 +113,9 @@ var DEFAULT_SETTINGS = {
87
113
  headerRoutes: "",
88
114
  headerAppliedName: "",
89
115
  headerAppliedValue: "",
90
- headerStatus: ""
116
+ headerStatus: "",
117
+ quickPrompts: DEFAULT_QUICK_PROMPTS,
118
+ optimizerTier: DEFAULT_OPTIMIZER_TIER
91
119
  };
92
120
  var SEND_PRESETS = ["Enter", "Ctrl+Enter", "Alt+Enter", "Shift+Enter"];
93
121
  var NEWLINE_PRESETS = ["Shift+Enter", "Enter", "Ctrl+Enter", "Alt+Enter"];
@@ -127,6 +155,29 @@ function sanitizeSettings(value) {
127
155
  const v = source[field];
128
156
  return typeof v === "number" && Number.isFinite(v) && v >= 320 && v <= 4e3 ? Math.round(v) : void 0;
129
157
  };
158
+ const asQuickPrompts = () => {
159
+ const raw = source[QUICK_PROMPTS_FIELD];
160
+ if (!Array.isArray(raw)) return DEFAULT_QUICK_PROMPTS;
161
+ const seen = /* @__PURE__ */ new Set();
162
+ const out = [];
163
+ for (const item of raw) {
164
+ if (out.length >= QUICK_PROMPT_MAX) break;
165
+ if (typeof item !== "object" || item === null) continue;
166
+ const row2 = item;
167
+ const prompt = typeof row2.prompt === "string" ? row2.prompt.slice(0, QUICK_TEXT_MAX).trim() : "";
168
+ if (prompt === "") continue;
169
+ const label = typeof row2.label === "string" ? row2.label.slice(0, QUICK_LABEL_MAX).trim() : "";
170
+ const id = typeof row2.id === "string" && row2.id !== "" ? row2.id.slice(0, 64) : newQuickPromptId();
171
+ if (seen.has(id)) continue;
172
+ seen.add(id);
173
+ out.push({ id, label: label === "" ? prompt.slice(0, 12) : label, prompt, always: row2.always === true });
174
+ }
175
+ return out;
176
+ };
177
+ const asTier = () => {
178
+ const v = source[OPTIMIZER_TIER_FIELD];
179
+ return v === "basic" || v === "advanced" || v === "extreme" ? v : DEFAULT_OPTIMIZER_TIER;
180
+ };
130
181
  return {
131
182
  enabled: asBool(ENABLED_FIELD),
132
183
  sendKey: asString(SEND_KEY_FIELD),
@@ -151,7 +202,9 @@ function sanitizeSettings(value) {
151
202
  headerRoutes: asText(HEADER_ROUTES_FIELD, HEADER_VALUE_MAX),
152
203
  headerAppliedName: asText(HEADER_APPLIED_NAME_FIELD, HEADER_NAME_MAX),
153
204
  headerAppliedValue: asText(HEADER_APPLIED_VALUE_FIELD, HEADER_VALUE_MAX),
154
- headerStatus: asText(HEADER_STATUS_FIELD, HEADER_VALUE_MAX)
205
+ headerStatus: asText(HEADER_STATUS_FIELD, HEADER_VALUE_MAX),
206
+ quickPrompts: asQuickPrompts(),
207
+ optimizerTier: asTier()
155
208
  };
156
209
  }
157
210
 
@@ -255,6 +308,118 @@ function displayChord(chord) {
255
308
  return chord === "" ? "\u65E0" : chord;
256
309
  }
257
310
 
311
+ // src/client/quick-commands.ts
312
+ var COMPOSER_SELECTOR = "[data-composer-input]";
313
+ var CARD_SELECTOR = "[data-composer-card]";
314
+ var state = { actions: null, draft: "", sessionId: "" };
315
+ function publishInputBridge(next) {
316
+ state.actions = next.actions;
317
+ state.draft = next.draft;
318
+ state.sessionId = next.sessionId;
319
+ }
320
+ function releaseInputBridge(sessionId) {
321
+ if (state.sessionId !== sessionId) return;
322
+ state.actions = null;
323
+ state.draft = "";
324
+ state.sessionId = "";
325
+ }
326
+ function draftFromDom() {
327
+ const el = document.querySelector(COMPOSER_SELECTOR);
328
+ if (!(el instanceof HTMLElement)) return "";
329
+ return el.innerText.replace(/\n+$/, "");
330
+ }
331
+ function currentDraft() {
332
+ return state.actions === null ? draftFromDom() : state.draft;
333
+ }
334
+ function alwaysPrompts(prompts) {
335
+ return prompts.filter((item) => item.always === true && item.prompt.trim() !== "");
336
+ }
337
+ function withAlwaysPrompts(draft, prompts) {
338
+ const picked = alwaysPrompts(prompts);
339
+ if (picked.length === 0) return null;
340
+ const base = draft.replace(/\s+$/, "");
341
+ if (base === "") return null;
342
+ const suffix = picked.map((item) => item.prompt.trim()).join("\n\n");
343
+ if (base.endsWith(suffix)) return null;
344
+ return `${base}
345
+
346
+ ${suffix}`;
347
+ }
348
+ function applyAlwaysPrompts(prompts) {
349
+ const actions = state.actions;
350
+ if (actions === null) return false;
351
+ const next = withAlwaysPrompts(currentDraft(), prompts);
352
+ if (next === null) return false;
353
+ actions.setDraft(next);
354
+ return true;
355
+ }
356
+ function insertIntoDraft(text) {
357
+ const actions = state.actions;
358
+ const body = text.trim();
359
+ if (actions === null || body === "") return false;
360
+ const draft = currentDraft();
361
+ const next = draft.trim() === "" ? body : `${draft.replace(/\s+$/, "")}
362
+ ${body}`;
363
+ actions.setDraft(next);
364
+ return true;
365
+ }
366
+ function replaceDraft(text) {
367
+ const actions = state.actions;
368
+ if (actions === null) return false;
369
+ actions.setDraft(text);
370
+ return true;
371
+ }
372
+ function focusComposer() {
373
+ const el = document.querySelector(COMPOSER_SELECTOR);
374
+ if (el instanceof HTMLElement) el.focus({ preventScroll: true });
375
+ }
376
+ function sendButtonOf(target) {
377
+ if (!(target instanceof Element)) return null;
378
+ const card = target.closest(CARD_SELECTOR);
379
+ if (card === null) return null;
380
+ const buttons = card.querySelectorAll("button");
381
+ if (buttons.length === 0) return null;
382
+ const last = buttons[buttons.length - 1];
383
+ if (!(last instanceof HTMLButtonElement)) return null;
384
+ if (last !== target && !last.contains(target)) return null;
385
+ if (last.disabled) return null;
386
+ if (last.querySelector("svg rect") !== null) return null;
387
+ if (last.querySelector("svg path") === null) return null;
388
+ return last;
389
+ }
390
+ async function optimizeDraft(text, tier) {
391
+ const body = text.trim();
392
+ if (body === "") return { ok: false, error: "\u8F93\u5165\u6846\u662F\u7A7A\u7684\uFF0C\u5148\u5199\u70B9\u4EC0\u4E48\u518D\u4F18\u5316" };
393
+ let response;
394
+ try {
395
+ response = await fetch(OPTIMIZER_API_PATH, {
396
+ method: "POST",
397
+ headers: { "content-type": "application/json" },
398
+ body: JSON.stringify({ text: body, tier })
399
+ });
400
+ } catch (error) {
401
+ return { ok: false, error: `\u8FDE\u4E0D\u4E0A\u5BBF\u4E3B\u534A\u7684\u4F18\u5316\u63A5\u53E3\uFF1A${error instanceof Error ? error.message : String(error)}` };
402
+ }
403
+ if (!response.ok) {
404
+ return { ok: false, error: `\u5BBF\u4E3B\u534A\u8FD4\u56DE HTTP ${response.status}\uFF08\u63D2\u4EF6\u53EF\u80FD\u8FD8\u6CA1\u91CD\u542F\u751F\u6548\uFF09` };
405
+ }
406
+ let payload;
407
+ try {
408
+ payload = await response.json();
409
+ } catch {
410
+ return { ok: false, error: "\u5BBF\u4E3B\u534A\u8FD4\u56DE\u7684\u4E0D\u662F JSON" };
411
+ }
412
+ const record = typeof payload === "object" && payload !== null ? payload : {};
413
+ if (record.ok !== true) {
414
+ const message = typeof record.error === "string" && record.error !== "" ? record.error : "\u4F18\u5316\u5931\u8D25";
415
+ return { ok: false, error: message };
416
+ }
417
+ const optimized = typeof record.text === "string" ? record.text.trim() : "";
418
+ if (optimized === "") return { ok: false, error: "\u6A21\u578B\u6CA1\u6709\u4EA7\u51FA\u4EFB\u4F55\u5185\u5BB9" };
419
+ const route = `${String(record.provider ?? "")}/${String(record.model ?? "")}`;
420
+ return { ok: true, text: optimized, route };
421
+ }
422
+
258
423
  // src/client/interceptors.ts
259
424
  var INPUT_SELECTOR = "[data-composer-input]";
260
425
  var OVERLAY_SELECTOR = "[data-trigger-menu], [data-conversation-composer-overlay]";
@@ -266,6 +431,7 @@ function overlayOpen() {
266
431
  return document.querySelector(OVERLAY_SELECTOR) !== null;
267
432
  }
268
433
  var replaying = false;
434
+ var replayingSendClick = false;
269
435
  var pasteCache = null;
270
436
  function prefetchClipboard() {
271
437
  pasteCache = null;
@@ -306,6 +472,7 @@ function installInterceptors(deps) {
306
472
  event.stopPropagation();
307
473
  if (event.repeat) return;
308
474
  if (gesture === "send") {
475
+ applyAlwaysPrompts(settings.quickPrompts);
309
476
  dispatchKey(root, chordToInit("Enter"));
310
477
  } else if (gesture === "newline") {
311
478
  dispatchKey(root, chordToInit("Shift+Enter"));
@@ -329,11 +496,27 @@ function installInterceptors(deps) {
329
496
  deps.setMenu({ x: event.clientX, y: event.clientY, hasSelection });
330
497
  prefetchClipboard();
331
498
  };
499
+ const onClickSend = (event) => {
500
+ if (replayingSendClick) return;
501
+ const button = sendButtonOf(event.target);
502
+ if (button === null) return;
503
+ if (!applyAlwaysPrompts(deps.settings().quickPrompts)) return;
504
+ event.preventDefault();
505
+ event.stopPropagation();
506
+ replayingSendClick = true;
507
+ try {
508
+ button.click();
509
+ } finally {
510
+ replayingSendClick = false;
511
+ }
512
+ };
332
513
  window.addEventListener("keydown", onKeyDown, true);
333
514
  window.addEventListener("contextmenu", onContextMenu, true);
515
+ window.addEventListener("click", onClickSend, true);
334
516
  return () => {
335
517
  window.removeEventListener("keydown", onKeyDown, true);
336
518
  window.removeEventListener("contextmenu", onContextMenu, true);
519
+ window.removeEventListener("click", onClickSend, true);
337
520
  };
338
521
  }
339
522
  async function runMenuAction(id) {
@@ -444,6 +627,36 @@ async function pasteText(root) {
444
627
 
445
628
  // src/client/panel.ts
446
629
  var PANEL_SELECTOR = '[role="dialog"][aria-modal="true"]:has(> nav)';
630
+ var RESIZE_LAYER_CLASS = "dsh-ux-resize-layer";
631
+ var RESIZE_OUTLINE_CLASS = "dsh-ux-resize-outline";
632
+ var RESIZE_EDGE_CLASS = "dsh-ux-resize-edge";
633
+ var RESIZE_GRIP_CLASS = "dsh-ux-resize-grip";
634
+ var BAND = 8;
635
+ var INSET = 14;
636
+ var GRIP = 26;
637
+ function handleBox(edge) {
638
+ switch (edge) {
639
+ case "left":
640
+ return { left: 0, top: INSET, bottom: INSET, width: BAND, cursor: "ew-resize" };
641
+ case "right":
642
+ return { right: 0, top: INSET, bottom: INSET, width: BAND, cursor: "ew-resize" };
643
+ case "top":
644
+ return { top: 0, left: INSET, right: INSET, height: BAND, cursor: "ns-resize" };
645
+ case "bottom":
646
+ return { bottom: 0, left: INSET, right: INSET, height: BAND, cursor: "ns-resize" };
647
+ case "br":
648
+ return { right: 8, bottom: 8, width: GRIP, height: GRIP, cursor: "nwse-resize" };
649
+ }
650
+ }
651
+ var RESIZE_OUTLINE_STYLE = {
652
+ position: "absolute",
653
+ inset: 0,
654
+ // 跟随面板自身的圆角(官方 .panel 是 32px)——用 inherit 而不是写死数字,
655
+ // 官方改圆角时这里自动跟上。
656
+ borderRadius: "inherit",
657
+ border: "1px solid rgba(127, 127, 137, .28)",
658
+ pointerEvents: "none"
659
+ };
447
660
  var PANEL_CSS = `
448
661
  html.dsh-ux-panel-scroll [role="dialog"][aria-modal="true"]:has(> nav) { min-height: 320px; }
449
662
  html.dsh-ux-panel-scroll [role="dialog"][aria-modal="true"]:has(> nav) > nav {
@@ -467,10 +680,32 @@ html.dsh-ux-panel-scroll [role="dialog"][aria-modal="true"]:has(> nav) > nav > d
467
680
  background: var(--dsh-scrollbar-thumb-hover, var(--dsw-alias-scrollbar-hover-l2));
468
681
  }
469
682
  `;
683
+ var RESIZE_CSS = `
684
+ .${RESIZE_LAYER_CLASS} { position: absolute; inset: 0; pointer-events: none; }
685
+ .${RESIZE_EDGE_CLASS}, .${RESIZE_GRIP_CLASS} {
686
+ position: absolute;
687
+ pointer-events: auto;
688
+ background: transparent;
689
+ border-radius: 6px;
690
+ transition: background-color .12s ease, color .12s ease;
691
+ touch-action: none;
692
+ }
693
+ .${RESIZE_EDGE_CLASS}:hover { background: rgba(127, 127, 137, .22); }
694
+ .${RESIZE_EDGE_CLASS}[data-active="true"] { background: rgba(127, 127, 137, .3); }
695
+ .${RESIZE_GRIP_CLASS} {
696
+ display: flex;
697
+ align-items: flex-end;
698
+ justify-content: flex-end;
699
+ padding: 3px;
700
+ color: rgba(127, 127, 137, .55);
701
+ }
702
+ .${RESIZE_GRIP_CLASS}:hover { color: rgba(127, 127, 137, .95); background: rgba(127, 127, 137, .16); }
703
+ `;
470
704
  function installPanelStyle(settings, subscribe) {
471
705
  const tag = document.createElement("style");
472
706
  tag.dataset.plugin = "dsh-composer-ux-panel";
473
- tag.textContent = PANEL_CSS;
707
+ tag.textContent = `${PANEL_CSS}
708
+ ${RESIZE_CSS}`;
474
709
  document.head.appendChild(tag);
475
710
  const applyClass = () => {
476
711
  document.documentElement.classList.toggle("dsh-ux-panel-scroll", settings());
@@ -576,6 +811,45 @@ function installSettingsCardStyle() {
576
811
  };
577
812
  }
578
813
 
814
+ // src/client/quick-style.ts
815
+ var QUICK_BUTTON_CLASS = "composer-ux-quick-button";
816
+ var QUICK_BUTTON_CSS = `
817
+ .${QUICK_BUTTON_CLASS} {
818
+ display: inline-flex;
819
+ align-items: center;
820
+ gap: 4px;
821
+ height: 24px;
822
+ border: 1px solid rgba(127, 127, 137, .35);
823
+ background: transparent;
824
+ color: inherit;
825
+ border-radius: 999px;
826
+ padding: 0 9px;
827
+ font-size: 12px;
828
+ line-height: 1;
829
+ cursor: pointer;
830
+ opacity: .75;
831
+ white-space: nowrap;
832
+ font-variant-numeric: tabular-nums;
833
+ transition: opacity .12s ease, background-color .12s ease;
834
+ }
835
+ .${QUICK_BUTTON_CLASS}:hover { opacity: 1; background: rgba(127, 127, 137, .12); }
836
+ .${QUICK_BUTTON_CLASS}[aria-expanded="true"] {
837
+ opacity: 1;
838
+ background: rgba(127, 127, 137, .18);
839
+ border-color: rgba(127, 127, 137, .6);
840
+ }
841
+ .${QUICK_BUTTON_CLASS} svg { flex: 0 0 auto; }
842
+ `;
843
+ function installQuickButtonStyle() {
844
+ const tag = document.createElement("style");
845
+ tag.dataset.plugin = "dsh-composer-ux-quick-button";
846
+ tag.textContent = QUICK_BUTTON_CSS;
847
+ document.head.appendChild(tag);
848
+ return () => {
849
+ tag.remove();
850
+ };
851
+ }
852
+
579
853
  // src/client/ContextMenuHost.tsx
580
854
  var import_react = __toESM(require("react"), 1);
581
855
 
@@ -708,6 +982,160 @@ var menuNote = {
708
982
  lineHeight: 1.4,
709
983
  padding: "4px 10px 2px"
710
984
  };
985
+ var quickPanel = {
986
+ position: "fixed",
987
+ zIndex: 9999,
988
+ width: 420,
989
+ maxWidth: "calc(100vw - 24px)",
990
+ maxHeight: "min(60vh, 520px)",
991
+ display: "flex",
992
+ flexDirection: "column",
993
+ background: "var(--dsw-specific-menu)",
994
+ border: "0.5px solid var(--dsw-alias-border-l1)",
995
+ borderRadius: 12,
996
+ boxShadow: "var(--dsw-elevation-prominent)",
997
+ pointerEvents: "auto",
998
+ overflow: "hidden"
999
+ };
1000
+ var quickPanelHead = {
1001
+ padding: "10px 12px 8px",
1002
+ borderBottom: "0.5px solid var(--dsw-alias-border-l2)",
1003
+ display: "flex",
1004
+ flexDirection: "column",
1005
+ gap: 8
1006
+ };
1007
+ var quickPanelTitleRow = {
1008
+ display: "flex",
1009
+ alignItems: "center",
1010
+ justifyContent: "space-between",
1011
+ gap: 8
1012
+ };
1013
+ var quickPanelTitle = {
1014
+ color: "var(--dsw-alias-label-primary)",
1015
+ fontSize: 13,
1016
+ fontWeight: 600
1017
+ };
1018
+ var quickTierRow = {
1019
+ display: "inline-flex",
1020
+ gap: 2,
1021
+ padding: 2,
1022
+ borderRadius: 999,
1023
+ background: "var(--dsw-alias-bg-layer-1)",
1024
+ border: "0.5px solid var(--dsw-alias-border-l2)"
1025
+ };
1026
+ var quickTierButton = {
1027
+ border: "none",
1028
+ background: "transparent",
1029
+ color: "var(--dsw-alias-label-tertiary)",
1030
+ fontSize: 11,
1031
+ lineHeight: 1,
1032
+ padding: "4px 8px",
1033
+ borderRadius: 999,
1034
+ cursor: "pointer",
1035
+ whiteSpace: "nowrap"
1036
+ };
1037
+ var quickTierButtonActive = {
1038
+ ...quickTierButton,
1039
+ background: "var(--dsw-alias-button-primary-fill)",
1040
+ color: "var(--dsw-alias-label-primary-foreground)"
1041
+ };
1042
+ var quickPrimaryButton = {
1043
+ display: "inline-flex",
1044
+ alignItems: "center",
1045
+ justifyContent: "center",
1046
+ gap: 6,
1047
+ width: "100%",
1048
+ padding: "7px 12px",
1049
+ borderRadius: 8,
1050
+ border: "0.5px solid var(--dsw-alias-button-primary-fill)",
1051
+ background: "var(--dsw-alias-button-primary-fill)",
1052
+ color: "var(--dsw-alias-label-primary-foreground)",
1053
+ fontSize: 12,
1054
+ cursor: "pointer"
1055
+ };
1056
+ var quickPrimaryButtonDisabled = {
1057
+ ...quickPrimaryButton,
1058
+ opacity: 0.4,
1059
+ cursor: "default"
1060
+ };
1061
+ var quickList = {
1062
+ overflowY: "auto",
1063
+ padding: "6px 6px 8px",
1064
+ display: "flex",
1065
+ flexDirection: "column",
1066
+ gap: 2
1067
+ };
1068
+ var quickItem = {
1069
+ display: "flex",
1070
+ alignItems: "center",
1071
+ gap: 8,
1072
+ padding: "5px 8px",
1073
+ borderRadius: 7,
1074
+ cursor: "pointer",
1075
+ background: "transparent",
1076
+ border: "none",
1077
+ flex: "1 1 auto",
1078
+ minWidth: 0,
1079
+ textAlign: "left"
1080
+ };
1081
+ var quickItemLabel = {
1082
+ color: "var(--dsw-alias-label-primary)",
1083
+ fontSize: 12,
1084
+ flex: "0 0 auto",
1085
+ whiteSpace: "nowrap"
1086
+ };
1087
+ var quickItemPreview = {
1088
+ color: "var(--dsw-alias-label-tertiary)",
1089
+ fontSize: 11,
1090
+ flex: "1 1 auto",
1091
+ minWidth: 0,
1092
+ overflow: "hidden",
1093
+ textOverflow: "ellipsis",
1094
+ whiteSpace: "nowrap"
1095
+ };
1096
+ var quickAlwaysLabel = {
1097
+ display: "inline-flex",
1098
+ alignItems: "center",
1099
+ gap: 4,
1100
+ flex: "0 0 auto",
1101
+ color: "var(--dsw-alias-label-tertiary)",
1102
+ fontSize: 11,
1103
+ whiteSpace: "nowrap",
1104
+ cursor: "pointer"
1105
+ };
1106
+ var quickAlwaysBox = {
1107
+ margin: 0,
1108
+ cursor: "pointer"
1109
+ };
1110
+ var quickPanelFoot = {
1111
+ padding: "5px 12px 7px",
1112
+ borderTop: "0.5px solid var(--dsw-alias-border-l2)",
1113
+ display: "flex",
1114
+ alignItems: "center",
1115
+ justifyContent: "space-between",
1116
+ gap: 8
1117
+ };
1118
+ var quickNotice = {
1119
+ color: "var(--dsw-alias-state-warn-label)",
1120
+ fontSize: 11,
1121
+ lineHeight: 1.4,
1122
+ minWidth: 0,
1123
+ overflow: "hidden",
1124
+ textOverflow: "ellipsis"
1125
+ };
1126
+ var quickFootHint = {
1127
+ color: "var(--dsw-alias-label-tertiary)",
1128
+ fontSize: 11,
1129
+ whiteSpace: "nowrap",
1130
+ flex: "0 0 auto"
1131
+ };
1132
+ var quickEmpty = {
1133
+ color: "var(--dsw-alias-label-tertiary)",
1134
+ fontSize: 12,
1135
+ lineHeight: 1.6,
1136
+ padding: "14px 10px",
1137
+ textAlign: "center"
1138
+ };
711
1139
 
712
1140
  // src/client/ContextMenuHost.tsx
713
1141
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -844,54 +1272,9 @@ function ContextMenuHost({ useMenu, useLive, actions }) {
844
1272
 
845
1273
  // src/client/PanelResizeHandles.tsx
846
1274
  var import_react2 = require("react");
1275
+ var import_react_dom = require("react-dom");
847
1276
  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
- }
1277
+ var EDGES = ["left", "right", "top", "bottom", "br"];
895
1278
  function PanelResizeHandles({ useLive, actions }) {
896
1279
  const settings = useLive((value) => ({
897
1280
  enabled: value.enabled,
@@ -899,130 +1282,126 @@ function PanelResizeHandles({ useLive, actions }) {
899
1282
  panelWidth: value.panelWidth,
900
1283
  panelHeight: value.panelHeight
901
1284
  }));
902
- const settingsRef = (0, import_react2.useRef)(settings);
903
- settingsRef.current = settings;
904
1285
  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);
1286
+ const [panel, setPanel] = (0, import_react2.useState)(null);
1287
+ const [active, setActive] = (0, import_react2.useState)(null);
1288
+ const dragRef = (0, import_react2.useRef)(null);
907
1289
  (0, import_react2.useEffect)(() => {
908
1290
  if (!enabled) {
909
- setRect(null);
1291
+ setPanel(null);
910
1292
  return;
911
1293
  }
1294
+ let current = null;
912
1295
  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());
1296
+ const found = findSettingsPanel();
1297
+ if (found === current) return;
1298
+ current = found;
1299
+ setPanel(found);
927
1300
  };
928
1301
  probe();
929
1302
  const timer = setInterval(probe, 250);
930
- const onResize = () => {
931
- setRect((current) => current === null ? null : findSettingsPanel()?.getBoundingClientRect() ?? null);
932
- };
933
- window.addEventListener("resize", onResize);
934
1303
  return () => {
935
1304
  clearInterval(timer);
936
- window.removeEventListener("resize", onResize);
937
1305
  };
938
1306
  }, [enabled]);
939
- if (!enabled || rect === null) return null;
1307
+ (0, import_react2.useEffect)(() => {
1308
+ if (panel === null) return;
1309
+ const width = settings.panelWidth;
1310
+ const height = settings.panelHeight;
1311
+ if (width === void 0) panel.style.removeProperty("width");
1312
+ else panel.style.width = `${clampPanelWidth(width)}px`;
1313
+ if (height === void 0) panel.style.removeProperty("height");
1314
+ else panel.style.height = `${clampPanelHeight(height)}px`;
1315
+ }, [panel, settings.panelWidth, settings.panelHeight]);
1316
+ if (!enabled || panel === null) return null;
940
1317
  const onPointerDown = (edge, event) => {
941
1318
  event.preventDefault();
942
1319
  event.stopPropagation();
943
- const panel = findSettingsPanel();
944
- if (panel === null) return;
1320
+ const el = panel;
1321
+ const rect = el.getBoundingClientRect();
1322
+ el.style.position = "fixed";
1323
+ el.style.margin = "0";
1324
+ el.style.left = `${rect.left}px`;
1325
+ el.style.top = `${rect.top}px`;
1326
+ el.style.width = `${rect.width}px`;
1327
+ el.style.height = `${rect.height}px`;
945
1328
  const start = {
946
1329
  edge,
947
- startX: event.clientX,
948
- startY: event.clientY,
949
- startWidth: panel.clientWidth,
950
- startHeight: panel.clientHeight
1330
+ x: event.clientX,
1331
+ y: event.clientY,
1332
+ left: rect.left,
1333
+ top: rect.top,
1334
+ width: rect.width,
1335
+ height: rect.height
951
1336
  };
1337
+ dragRef.current = start;
1338
+ setActive(edge);
1339
+ document.body.style.cursor = String(handleBox(edge).cursor ?? "default");
1340
+ document.body.style.userSelect = "none";
952
1341
  const onMove = (move) => {
953
- if (dragStartRef.current !== start || dragStartRef.current === null) return;
1342
+ if (dragRef.current !== start) return;
954
1343
  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);
1344
+ const dx = move.clientX - start.x;
1345
+ const dy = move.clientY - start.y;
1346
+ let { left, top, width, height } = start;
1347
+ if (edge === "right" || edge === "br") width = clampPanelWidth(start.width + dx);
1348
+ if (edge === "bottom" || edge === "br") height = clampPanelHeight(start.height + dy);
1349
+ if (edge === "left") {
1350
+ width = clampPanelWidth(start.width - dx);
1351
+ left = start.left + (start.width - width);
963
1352
  }
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);
1353
+ if (edge === "top") {
1354
+ height = clampPanelHeight(start.height - dy);
1355
+ top = start.top + (start.height - height);
966
1356
  }
967
- panelEl.style.width = `${Math.round(clampPanelWidth(width))}px`;
968
- panelEl.style.height = `${Math.round(clampPanelHeight(height))}px`;
969
- setRect(panelEl.getBoundingClientRect());
1357
+ el.style.left = `${Math.round(left)}px`;
1358
+ el.style.top = `${Math.round(top)}px`;
1359
+ el.style.width = `${Math.round(width)}px`;
1360
+ el.style.height = `${Math.round(height)}px`;
970
1361
  };
971
1362
  const onUp = () => {
972
1363
  window.removeEventListener("pointermove", onMove);
973
1364
  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
- }
1365
+ dragRef.current = null;
1366
+ setActive(null);
1367
+ const box = el.getBoundingClientRect();
1368
+ actions.setField(PANEL_WIDTH_FIELD, Math.round(clampPanelWidth(box.width)));
1369
+ actions.setField(PANEL_HEIGHT_FIELD, Math.round(clampPanelHeight(box.height)));
980
1370
  document.body.style.removeProperty("cursor");
981
1371
  document.body.style.removeProperty("user-select");
982
1372
  };
983
- dragStartRef.current = start;
984
- document.body.style.cursor = stripRect(edge, rect).cursor;
985
- document.body.style.userSelect = "none";
986
1373
  window.addEventListener("pointermove", onMove);
987
1374
  window.addEventListener("pointerup", onUp);
988
1375
  };
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";
1376
+ return (0, import_react_dom.createPortal)(
1377
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: RESIZE_LAYER_CLASS, "data-composer-ux-resize": true, children: [
1378
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: RESIZE_OUTLINE_CLASS, style: RESIZE_OUTLINE_STYLE }),
1379
+ EDGES.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1380
+ "div",
1381
+ {
1382
+ className: edge === "br" ? RESIZE_GRIP_CLASS : RESIZE_EDGE_CLASS,
1383
+ "data-active": active === edge ? "true" : void 0,
1384
+ title: edge === "br" ? "\u62D6\u52A8\u6539\u9762\u677F\u5927\u5C0F" : void 0,
1385
+ style: handleBox(edge),
1386
+ onPointerDown: (event) => {
1387
+ onPointerDown(edge, event);
1388
+ },
1389
+ 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)(
1390
+ "path",
1391
+ {
1392
+ d: "M11 1 1 11M11 6 6 11",
1393
+ stroke: "currentColor",
1394
+ strokeWidth: "1.3",
1395
+ strokeLinecap: "round"
1396
+ }
1397
+ ) })
1005
1398
  },
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
- }) });
1399
+ edge
1400
+ ))
1401
+ ] }),
1402
+ panel
1403
+ );
1024
1404
  }
1025
- var dragStartRef = { current: null };
1026
1405
 
1027
1406
  // src/client/SettingsSection.tsx
1028
1407
  var import_react3 = require("react");
@@ -1247,6 +1626,145 @@ function TextFieldRow(props) {
1247
1626
  ] })
1248
1627
  ] });
1249
1628
  }
1629
+ function QuickPromptsEditor(props) {
1630
+ const [rows, setRows] = (0, import_react3.useState)(() => props.items.map((item) => ({ ...item })));
1631
+ const [dirty, setDirty] = (0, import_react3.useState)(false);
1632
+ const update = (id, patch) => {
1633
+ setDirty(true);
1634
+ setRows((current) => current.map((row2) => row2.id === id ? { ...row2, ...patch } : row2));
1635
+ };
1636
+ const remove = (id) => {
1637
+ setDirty(true);
1638
+ setRows((current) => current.filter((row2) => row2.id !== id));
1639
+ };
1640
+ const add = () => {
1641
+ if (rows.length >= QUICK_PROMPT_MAX) return;
1642
+ setDirty(true);
1643
+ setRows((current) => current.concat([{
1644
+ id: newQuickPromptId(),
1645
+ label: "",
1646
+ prompt: "",
1647
+ always: false
1648
+ }]));
1649
+ };
1650
+ const move = (index, delta) => {
1651
+ const target = index + delta;
1652
+ if (target < 0 || target >= rows.length) return;
1653
+ setDirty(true);
1654
+ setRows((current) => {
1655
+ const next = [...current];
1656
+ const [row2] = next.splice(index, 1);
1657
+ next.splice(target, 0, row2);
1658
+ return next;
1659
+ });
1660
+ };
1661
+ const save = () => {
1662
+ const cleaned = rows.map((row2) => ({
1663
+ ...row2,
1664
+ prompt: row2.prompt.trim(),
1665
+ label: row2.label.trim() === "" ? row2.prompt.trim().slice(0, 12) : row2.label.trim()
1666
+ })).filter((row2) => row2.prompt !== "");
1667
+ props.onSave(cleaned);
1668
+ setRows(cleaned);
1669
+ setDirty(false);
1670
+ };
1671
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1672
+ rows.map((row2, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1673
+ "div",
1674
+ {
1675
+ style: {
1676
+ display: "flex",
1677
+ flexDirection: "column",
1678
+ gap: 6,
1679
+ padding: 8,
1680
+ borderRadius: 8,
1681
+ border: "0.5px solid var(--dsw-alias-border-l2)",
1682
+ background: "var(--dsw-alias-bg-layer-1)"
1683
+ },
1684
+ children: [
1685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1686
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1687
+ "input",
1688
+ {
1689
+ type: "text",
1690
+ value: row2.label,
1691
+ placeholder: "\u540D\u79F0\uFF0C\u5982\uFF1A\u4EC5\u8BF4\u660E\u539F\u56E0",
1692
+ maxLength: QUICK_LABEL_MAX,
1693
+ spellCheck: false,
1694
+ onChange: (event) => {
1695
+ update(row2.id, { label: event.target.value });
1696
+ },
1697
+ style: { ...textInput, flex: "0 0 150px" }
1698
+ }
1699
+ ),
1700
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: 4, ...rowDesc, margin: 0, flex: "0 0 auto" }, children: [
1701
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1702
+ "input",
1703
+ {
1704
+ type: "checkbox",
1705
+ checked: row2.always,
1706
+ style: { margin: 0, cursor: "pointer" },
1707
+ onChange: (event) => {
1708
+ update(row2.id, { always: event.target.checked });
1709
+ }
1710
+ }
1711
+ ),
1712
+ "\u9ED8\u8BA4\u63D2\u5165"
1713
+ ] }),
1714
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 } }),
1715
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u4E0A\u79FB", onClick: () => {
1716
+ move(index, -1);
1717
+ }, children: "\u2191" }),
1718
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u4E0B\u79FB", onClick: () => {
1719
+ move(index, 1);
1720
+ }, children: "\u2193" }),
1721
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, title: "\u5220\u9664\u8FD9\u6761", onClick: () => {
1722
+ remove(row2.id);
1723
+ }, children: "\u2715" })
1724
+ ] }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1726
+ "textarea",
1727
+ {
1728
+ value: row2.prompt,
1729
+ 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",
1730
+ maxLength: QUICK_TEXT_MAX,
1731
+ spellCheck: false,
1732
+ rows: Math.min(4, Math.max(2, Math.ceil(row2.prompt.length / 46))),
1733
+ onChange: (event) => {
1734
+ update(row2.id, { prompt: event.target.value });
1735
+ },
1736
+ style: { ...textInput, resize: "vertical", fontFamily: "inherit", lineHeight: 1.5 }
1737
+ }
1738
+ )
1739
+ ]
1740
+ },
1741
+ row2.id
1742
+ )),
1743
+ rows.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: rowDesc, children: "\u8FD8\u6CA1\u6709\u6761\u76EE\u3002\u70B9\u4E0B\u9762\u7684\u300C+ \u6DFB\u52A0\u4E00\u6761\u300D\u5F00\u59CB\u5EFA\u81EA\u5DF1\u7684\u5FEB\u6377\u6307\u4EE4\u3002" }),
1744
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: [
1745
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", style: pill, onClick: add, children: "+ \u6DFB\u52A0\u4E00\u6761" }),
1746
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1747
+ "button",
1748
+ {
1749
+ type: "button",
1750
+ style: dirty ? pillActive : pill,
1751
+ onClick: save,
1752
+ disabled: !dirty,
1753
+ children: dirty ? "\u4FDD\u5B58\u4FEE\u6539" : "\u5DF2\u4FDD\u5B58"
1754
+ }
1755
+ ),
1756
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: rowDesc, children: [
1757
+ "\u5171 ",
1758
+ rows.length,
1759
+ " / ",
1760
+ QUICK_PROMPT_MAX,
1761
+ " \u6761 \xB7 \u52FE\u4E86\u300C\u9ED8\u8BA4\u63D2\u5165\u300D\u7684\uFF1A",
1762
+ rows.filter((row2) => row2.always).length,
1763
+ " \u6761"
1764
+ ] })
1765
+ ] })
1766
+ ] });
1767
+ }
1250
1768
  function SettingsSection({ useLive, actions }) {
1251
1769
  const settings = useLive((item) => item);
1252
1770
  const [conflict, setConflict] = (0, import_react3.useState)(null);
@@ -1265,6 +1783,8 @@ function SettingsSection({ useLive, actions }) {
1265
1783
  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
1784
  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
1785
  const headerSummary = settings.headerEnabled ? settings.headerStatus === "" ? "\u5DF2\u542F\u7528" : settings.headerStatus : "\u672A\u542F\u7528";
1786
+ const alwaysCount = settings.quickPrompts.filter((item) => item.always).length;
1787
+ const quickSummary = `${settings.quickPrompts.length} \u6761 \xB7 \u9ED8\u8BA4\u63D2\u5165 ${alwaysCount} \u6761 \xB7 \u4F18\u5316\u6863\u4F4D ${OPTIMIZER_TIERS.find((item) => item.id === settings.optimizerTier)?.label ?? "\u9AD8\u7EA7"}`;
1268
1788
  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
1789
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: "4px 2px" }, children: [
1270
1790
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "dsh-ux-card", children: [
@@ -1372,6 +1892,54 @@ function SettingsSection({ useLive, actions }) {
1372
1892
  ) })
1373
1893
  ] })
1374
1894
  ] }),
1895
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(FoldCard, { name: "\u5FEB\u6377\u6307\u4EE4", summary: quickSummary, children: [
1896
+ /* @__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" }),
1897
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...row, borderTop: "none", flexDirection: "column", alignItems: "stretch", gap: 8 }, children: [
1898
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: rowText, children: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowTitle, children: "\u4F18\u5316\u5F3A\u5EA6" }),
1900
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowDesc, children: OPTIMIZER_TIERS.find((item) => item.id === settings.optimizerTier)?.hint ?? "" })
1901
+ ] }),
1902
+ /* @__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)(
1903
+ "button",
1904
+ {
1905
+ type: "button",
1906
+ title: item.hint,
1907
+ style: settings.optimizerTier === item.id ? pillActive : pill,
1908
+ onClick: () => {
1909
+ actions.setField(OPTIMIZER_TIER_FIELD, item.id);
1910
+ },
1911
+ children: item.label
1912
+ },
1913
+ item.id
1914
+ )) })
1915
+ ] }),
1916
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...row, borderTop: "none", flexDirection: "column", alignItems: "stretch", gap: 8 }, children: [
1917
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: rowText, children: [
1918
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowTitle, children: "\u5FEB\u6377\u6307\u4EE4\u6E05\u5355" }),
1919
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: rowDesc, children: "\u5185\u7F6E 9 \u6761\u53EF\u4EE5\u76F4\u63A5\u7528\uFF1B\u6539\u5B8C\u8BB0\u5F97\u70B9\u300C\u4FDD\u5B58\u4FEE\u6539\u300D\u3002" })
1920
+ ] }),
1921
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1922
+ QuickPromptsEditor,
1923
+ {
1924
+ items: settings.quickPrompts,
1925
+ onSave: (next) => {
1926
+ actions.setField(QUICK_PROMPTS_FIELD, next);
1927
+ }
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1931
+ "button",
1932
+ {
1933
+ type: "button",
1934
+ style: pill,
1935
+ onClick: () => {
1936
+ actions.clearField(QUICK_PROMPTS_FIELD);
1937
+ },
1938
+ children: "\u6062\u590D\u5185\u7F6E 9 \u6761"
1939
+ }
1940
+ ) })
1941
+ ] })
1942
+ ] }),
1375
1943
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(FoldCard, { name: "\u8BBE\u7F6E\u9762\u677F", summary: panelSummary, children: [
1376
1944
  /* @__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
1945
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1487,9 +2055,9 @@ function SettingsSection({ useLive, actions }) {
1487
2055
  TextFieldRow,
1488
2056
  {
1489
2057
  title: "\u4F5C\u7528\u8DEF\u7531",
1490
- desc: "\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694\uFF1B\u7559\u7A7A = \u81EA\u52A8\u5339\u914D opencode \u7CFB\u8DEF\u7531",
2058
+ desc: "\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694\uFF1B\u7559\u7A7A = \u81EA\u52A8\u5339\u914D OpenCode \u8DEF\u7531\uFF08\u540D\u5B57\u4EE5 opencode \u5F00\u5934\uFF0C\u6216 baseURL \u6307\u5411 opencode.ai\uFF09",
1491
2059
  value: settings.headerRoutes,
1492
- placeholder: "\u7559\u7A7A = \u81EA\u52A8\uFF08opencode*\uFF09",
2060
+ placeholder: "\u7559\u7A7A = \u81EA\u52A8\uFF08opencode* \u6216 opencode.ai \u7AEF\u70B9\uFF09",
1493
2061
  maxLength: HEADER_VALUE_MAX,
1494
2062
  onChange: (next) => {
1495
2063
  actions.setField(HEADER_ROUTES_FIELD, next);
@@ -1505,6 +2073,232 @@ function SettingsSection({ useLive, actions }) {
1505
2073
  ] });
1506
2074
  }
1507
2075
 
2076
+ // src/client/QuickCommandsButton.tsx
2077
+ var import_react4 = require("react");
2078
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2079
+ function QuickCommandsButton({
2080
+ useInput,
2081
+ inputActions,
2082
+ sessionId,
2083
+ useLive,
2084
+ usePanel,
2085
+ actions
2086
+ }) {
2087
+ const settings = useLive((item) => item);
2088
+ const anchor = usePanel((item) => item);
2089
+ const draft = useInput((state2) => state2.draft);
2090
+ const ref = (0, import_react4.useRef)(null);
2091
+ (0, import_react4.useEffect)(() => {
2092
+ const sid = typeof sessionId === "string" ? sessionId : "";
2093
+ publishInputBridge({
2094
+ actions: inputActions ?? null,
2095
+ draft: typeof draft === "string" ? draft : "",
2096
+ sessionId: sid
2097
+ });
2098
+ return () => {
2099
+ releaseInputBridge(sid);
2100
+ };
2101
+ }, [inputActions, draft, sessionId]);
2102
+ if (settings.enabled !== true) return null;
2103
+ const open = anchor !== null;
2104
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2105
+ "button",
2106
+ {
2107
+ ref,
2108
+ type: "button",
2109
+ className: QUICK_BUTTON_CLASS,
2110
+ "aria-haspopup": "dialog",
2111
+ "aria-expanded": open,
2112
+ 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",
2113
+ onMouseDown: (event) => {
2114
+ event.preventDefault();
2115
+ },
2116
+ onClick: () => {
2117
+ const rect = ref.current?.getBoundingClientRect();
2118
+ actions.toggle({
2119
+ left: rect?.left ?? 0,
2120
+ bottom: rect?.bottom ?? 0,
2121
+ width: rect?.width ?? 0
2122
+ });
2123
+ },
2124
+ children: [
2125
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2126
+ "svg",
2127
+ {
2128
+ viewBox: "0 0 16 16",
2129
+ width: "12",
2130
+ height: "12",
2131
+ "aria-hidden": true,
2132
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2133
+ "path",
2134
+ {
2135
+ d: "M9.2 1.2 3.4 9.1h3.5l-0.9 5.7 6-8.1H8.4l0.8-5.5Z",
2136
+ fill: "currentColor"
2137
+ }
2138
+ )
2139
+ }
2140
+ ),
2141
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u5FEB\u6377\u6307\u4EE4" })
2142
+ ]
2143
+ }
2144
+ );
2145
+ }
2146
+
2147
+ // src/client/QuickCommandsPanel.tsx
2148
+ var import_react5 = require("react");
2149
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2150
+ var MARGIN = 8;
2151
+ var GAP = 8;
2152
+ function QuickCommandsPanel({
2153
+ useLive,
2154
+ usePanel,
2155
+ useBusy,
2156
+ useNotice,
2157
+ actions
2158
+ }) {
2159
+ const settings = useLive((item) => item);
2160
+ const anchor = usePanel((item) => item);
2161
+ const busy = useBusy((item) => item);
2162
+ const notice = useNotice((item) => item);
2163
+ const ref = (0, import_react5.useRef)(null);
2164
+ const [position, setPosition] = (0, import_react5.useState)(null);
2165
+ (0, import_react5.useLayoutEffect)(() => {
2166
+ if (anchor === null) {
2167
+ setPosition(null);
2168
+ return;
2169
+ }
2170
+ const el = ref.current;
2171
+ const height = el === null ? 320 : el.getBoundingClientRect().height;
2172
+ const width = el === null ? 420 : el.getBoundingClientRect().width;
2173
+ const left = Math.max(MARGIN, Math.min(anchor.left, window.innerWidth - width - MARGIN));
2174
+ const above = anchor.bottom - height - GAP;
2175
+ const top = above >= MARGIN ? above : Math.min(anchor.bottom + GAP, window.innerHeight - height - MARGIN);
2176
+ setPosition({ left, top: Math.max(MARGIN, top) });
2177
+ }, [anchor?.left, anchor?.bottom, settings.quickPrompts.length, busy]);
2178
+ (0, import_react5.useEffect)(() => {
2179
+ if (anchor === null) return;
2180
+ const onPointerDown = (event) => {
2181
+ if (ref.current !== null && event.target instanceof Node && ref.current.contains(event.target)) return;
2182
+ if (event.target instanceof Element && event.target.closest(".composer-ux-quick-button") !== null) return;
2183
+ actions.close();
2184
+ };
2185
+ const onEscape = (event) => {
2186
+ if (event.key !== "Escape") return;
2187
+ event.preventDefault();
2188
+ event.stopPropagation();
2189
+ actions.close();
2190
+ };
2191
+ window.addEventListener("pointerdown", onPointerDown, true);
2192
+ window.addEventListener("keydown", onEscape, true);
2193
+ window.addEventListener("resize", actions.close);
2194
+ return () => {
2195
+ window.removeEventListener("pointerdown", onPointerDown, true);
2196
+ window.removeEventListener("keydown", onEscape, true);
2197
+ window.removeEventListener("resize", actions.close);
2198
+ };
2199
+ }, [anchor === null, actions]);
2200
+ if (anchor === null) return null;
2201
+ const items = settings.quickPrompts;
2202
+ const tier = settings.optimizerTier;
2203
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2204
+ "div",
2205
+ {
2206
+ ref,
2207
+ role: "dialog",
2208
+ "aria-label": "\u5FEB\u6377\u6307\u4EE4",
2209
+ style: { ...quickPanel, left: position?.left ?? anchor.left, top: position?.top ?? anchor.bottom },
2210
+ children: [
2211
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickPanelHead, children: [
2212
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickPanelTitleRow, children: [
2213
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickPanelTitle, children: "\u5FEB\u6377\u6307\u4EE4" }),
2214
+ /* @__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)(
2215
+ "button",
2216
+ {
2217
+ type: "button",
2218
+ title: item.hint,
2219
+ "aria-pressed": tier === item.id,
2220
+ style: tier === item.id ? quickTierButtonActive : quickTierButton,
2221
+ onMouseDown: (event) => {
2222
+ event.preventDefault();
2223
+ },
2224
+ onClick: () => {
2225
+ actions.setTier(item.id);
2226
+ },
2227
+ children: item.label
2228
+ },
2229
+ item.id
2230
+ )) })
2231
+ ] }),
2232
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2233
+ "button",
2234
+ {
2235
+ type: "button",
2236
+ disabled: busy,
2237
+ 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",
2238
+ style: busy ? quickPrimaryButtonDisabled : quickPrimaryButton,
2239
+ onMouseDown: (event) => {
2240
+ event.preventDefault();
2241
+ },
2242
+ onClick: () => {
2243
+ actions.optimize();
2244
+ },
2245
+ children: [
2246
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": true, children: "\u2728" }),
2247
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: busy ? "\u4F18\u5316\u4E2D\u2026" : "\u4F18\u5316\u63D0\u793A\u8BCD" })
2248
+ ]
2249
+ }
2250
+ )
2251
+ ] }),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickList, children: [
2253
+ items.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickEmpty, children: [
2254
+ "\u8FD8\u6CA1\u6709\u5FEB\u6377\u6307\u4EE4\u3002",
2255
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("br", {}),
2256
+ "\u5728\u300C\u8BBE\u7F6E \u2192 \u8F93\u5165\u4F53\u9A8C \u2192 \u5FEB\u6377\u6307\u4EE4\u300D\u91CC\u6DFB\u52A0\u3002"
2257
+ ] }),
2258
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
2259
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2260
+ "button",
2261
+ {
2262
+ type: "button",
2263
+ title: item.prompt,
2264
+ style: quickItem,
2265
+ onMouseDown: (event) => {
2266
+ event.preventDefault();
2267
+ },
2268
+ onClick: () => {
2269
+ actions.insert(item.prompt);
2270
+ },
2271
+ children: [
2272
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickItemLabel, children: item.label }),
2273
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickItemPreview, children: item.prompt.replace(/\s+/g, " ") })
2274
+ ]
2275
+ }
2276
+ ),
2277
+ /* @__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", children: [
2278
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2279
+ "input",
2280
+ {
2281
+ type: "checkbox",
2282
+ style: quickAlwaysBox,
2283
+ checked: item.always,
2284
+ onChange: (event) => {
2285
+ actions.setAlways(item.id, event.target.checked);
2286
+ }
2287
+ }
2288
+ ),
2289
+ "\u9ED8\u8BA4\u63D2\u5165"
2290
+ ] })
2291
+ ] }, item.id))
2292
+ ] }),
2293
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickPanelFoot, children: [
2294
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickNotice, role: "status", children: notice }),
2295
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: quickFootHint, children: "\u8BBE\u7F6E \u2192 \u8F93\u5165\u4F53\u9A8C" })
2296
+ ] })
2297
+ ]
2298
+ }
2299
+ );
2300
+ }
2301
+
1508
2302
  // src/client.tsx
1509
2303
  var name = "composer-ux";
1510
2304
  var inject = ["slots", "settingsScope"];
@@ -1513,6 +2307,9 @@ function apply(ctx) {
1513
2307
  const scope = ctx.settingsScope.bind({ namespace: NAMESPACE });
1514
2308
  const live = (0, import_dsh_client_store.createSnapshotStore)({ ...DEFAULT_SETTINGS });
1515
2309
  const menu2 = (0, import_dsh_client_store.createSnapshotStore)(null);
2310
+ const panel = (0, import_dsh_client_store.createSnapshotStore)(null);
2311
+ const optimizing = (0, import_dsh_client_store.createSnapshotStore)(false);
2312
+ const panelNotice = (0, import_dsh_client_store.createSnapshotStore)("");
1516
2313
  const sync = () => {
1517
2314
  const snapshot = scope.getSnapshot();
1518
2315
  live.set(snapshot.status === "ready" && snapshot.value !== void 0 ? { ...DEFAULT_SETTINGS, ...sanitizeSettings(snapshot.value) } : { ...DEFAULT_SETTINGS });
@@ -1544,12 +2341,81 @@ function apply(ctx) {
1544
2341
  HEADER_ENABLED_FIELD,
1545
2342
  HEADER_NAME_FIELD,
1546
2343
  HEADER_VALUE_FIELD,
1547
- HEADER_ROUTES_FIELD
2344
+ HEADER_ROUTES_FIELD,
2345
+ QUICK_PROMPTS_FIELD,
2346
+ OPTIMIZER_TIER_FIELD
1548
2347
  ].map((field) => ({ op: "unset", path: [field] }))
1549
2348
  ).catch((error) => {
1550
2349
  console.error("[composer-ux] settings reset failed", error);
1551
2350
  });
1552
2351
  };
2352
+ let noticeTimer;
2353
+ const note = (message, ms = 4500) => {
2354
+ panelNotice.set(message);
2355
+ if (noticeTimer !== void 0) clearTimeout(noticeTimer);
2356
+ if (message === "") return;
2357
+ noticeTimer = setTimeout(() => {
2358
+ panelNotice.set("");
2359
+ }, ms);
2360
+ };
2361
+ ctx.effect(() => () => {
2362
+ if (noticeTimer !== void 0) clearTimeout(noticeTimer);
2363
+ }, "composer-ux: quick notice timer");
2364
+ const writeQuickPrompts = (next) => {
2365
+ setField(QUICK_PROMPTS_FIELD, next);
2366
+ };
2367
+ const quickActions = {
2368
+ toggle: (anchor) => {
2369
+ if (panel.getSnapshot() !== null) {
2370
+ panel.set(null);
2371
+ panelNotice.set("");
2372
+ return;
2373
+ }
2374
+ panelNotice.set("");
2375
+ panel.set(anchor);
2376
+ },
2377
+ close: () => {
2378
+ panel.set(null);
2379
+ panelNotice.set("");
2380
+ },
2381
+ insert: (text) => {
2382
+ if (insertIntoDraft(text)) focusComposer();
2383
+ },
2384
+ optimize: () => {
2385
+ if (optimizing.getSnapshot()) return;
2386
+ const draft = currentDraft();
2387
+ if (draft.trim() === "") {
2388
+ note("\u8F93\u5165\u6846\u662F\u7A7A\u7684\uFF1A\u5148\u5199\u70B9\u4EC0\u4E48\uFF0C\u518D\u70B9\u4F18\u5316");
2389
+ return;
2390
+ }
2391
+ optimizing.set(true);
2392
+ note("\u6B63\u5728\u4F18\u5316\u2026");
2393
+ void optimizeDraft(draft, live.getSnapshot().optimizerTier).then(
2394
+ (result) => {
2395
+ optimizing.set(false);
2396
+ if (!result.ok) {
2397
+ note(`\u4F18\u5316\u5931\u8D25\uFF1A${result.error ?? "\u672A\u77E5\u539F\u56E0"}`);
2398
+ return;
2399
+ }
2400
+ replaceDraft(result.text ?? "");
2401
+ focusComposer();
2402
+ note(`\u5DF2\u5199\u56DE\u8F93\u5165\u6846\uFF08${result.route}\uFF09\xB7 Ctrl+Z \u53EF\u8FD8\u539F`);
2403
+ },
2404
+ (error) => {
2405
+ optimizing.set(false);
2406
+ note(`\u4F18\u5316\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
2407
+ }
2408
+ );
2409
+ },
2410
+ setTier: (tier) => {
2411
+ setField(OPTIMIZER_TIER_FIELD, tier);
2412
+ },
2413
+ setAlways: (id, value) => {
2414
+ writeQuickPrompts(live.getSnapshot().quickPrompts.map(
2415
+ (item) => item.id === id ? { ...item, always: value } : item
2416
+ ));
2417
+ }
2418
+ };
1553
2419
  slots.inject("settings.section", () => slots.register({
1554
2420
  name: "settings.section",
1555
2421
  id: "composer-ux",
@@ -1583,7 +2449,26 @@ function apply(ctx) {
1583
2449
  id: "composer-ux-panel-resize",
1584
2450
  inject: () => ({ hooks: { live }, actions: { setField } })
1585
2451
  }, PanelResizeHandles));
2452
+ slots.inject("conversation.input.right", () => slots.register({
2453
+ name: "conversation.input.right",
2454
+ id: "composer-ux-quick",
2455
+ order: 89,
2456
+ label: "\u5FEB\u6377\u6307\u4EE4",
2457
+ inject: () => ({
2458
+ hooks: { live, panel },
2459
+ actions: { toggle: quickActions.toggle }
2460
+ })
2461
+ }, QuickCommandsButton));
2462
+ slots.inject("shell.overlay", () => slots.register({
2463
+ name: "shell.overlay",
2464
+ id: "composer-ux-quick-panel",
2465
+ inject: () => ({
2466
+ hooks: { live, panel, busy: optimizing, notice: panelNotice },
2467
+ actions: quickActions
2468
+ })
2469
+ }, QuickCommandsPanel));
1586
2470
  ctx.effect(() => installSettingsCardStyle(), "composer-ux: settings card style");
2471
+ ctx.effect(() => installQuickButtonStyle(), "composer-ux: quick button style");
1587
2472
  ctx.effect(
1588
2473
  () => installPanelStyle(
1589
2474
  () => {
@@ -1601,8 +2486,8 @@ function apply(ctx) {
1601
2486
  if (enabled && dispose === null) {
1602
2487
  dispose = installInterceptors({
1603
2488
  settings: () => live.getSnapshot(),
1604
- setMenu: (state) => {
1605
- menu2.set(state);
2489
+ setMenu: (state2) => {
2490
+ menu2.set(state2);
1606
2491
  },
1607
2492
  menuOpen: () => menu2.getSnapshot() !== null
1608
2493
  });
@@ -1610,8 +2495,10 @@ function apply(ctx) {
1610
2495
  dispose?.();
1611
2496
  dispose = null;
1612
2497
  menu2.set(null);
2498
+ panel.set(null);
1613
2499
  }
1614
2500
  if (live.getSnapshot().menuNative) menu2.set(null);
2501
+ if (!live.getSnapshot().enabled) panel.set(null);
1615
2502
  };
1616
2503
  syncInterceptors();
1617
2504
  const unsubscribe = live.subscribe(syncInterceptors);