dsh-composer-ux 0.2.0 → 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
@@ -60,8 +60,13 @@ var MENU_NATIVE_FIELD = "menuNative";
60
60
  var QUICK_PROMPTS_FIELD = "quickPrompts";
61
61
  var OPTIMIZER_TIER_FIELD = "optimizerTier";
62
62
  var QUICK_PROMPT_MAX = 60;
63
+ var QUICK_CATEGORY_MAX = 20;
64
+ var QUICK_CATEGORY_NAME_MAX = 40;
63
65
  var QUICK_LABEL_MAX = 40;
64
- var QUICK_TEXT_MAX = 4e3;
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";
65
70
  var OPTIMIZER_API_PATH = "/composer-ux/optimize";
66
71
  var OPTIMIZER_TIERS = [
67
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" },
@@ -83,6 +88,86 @@ var DEFAULT_QUICK_PROMPTS = [
83
88
  function newQuickPromptId() {
84
89
  return `qp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
85
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
+ }
86
171
  var HEADER_ENABLED_FIELD = "headerEnabled";
87
172
  var HEADER_NAME_FIELD = "headerName";
88
173
  var HEADER_VALUE_FIELD = "headerValue";
@@ -208,6 +293,130 @@ function sanitizeSettings(value) {
208
293
  };
209
294
  }
210
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
+
211
420
  // src/client/chords.ts
212
421
  var MODIFIER_KEYS = /* @__PURE__ */ new Set(["Control", "Alt", "Shift", "Meta", "AltGraph"]);
213
422
  var CODE_LABELS = {
@@ -472,7 +681,7 @@ function installInterceptors(deps) {
472
681
  event.stopPropagation();
473
682
  if (event.repeat) return;
474
683
  if (gesture === "send") {
475
- applyAlwaysPrompts(settings.quickPrompts);
684
+ applyAlwaysPrompts(deps.alwaysPrompts());
476
685
  dispatchKey(root, chordToInit("Enter"));
477
686
  } else if (gesture === "newline") {
478
687
  dispatchKey(root, chordToInit("Shift+Enter"));
@@ -500,7 +709,7 @@ function installInterceptors(deps) {
500
709
  if (replayingSendClick) return;
501
710
  const button = sendButtonOf(event.target);
502
711
  if (button === null) return;
503
- if (!applyAlwaysPrompts(deps.settings().quickPrompts)) return;
712
+ if (!applyAlwaysPrompts(deps.alwaysPrompts())) return;
504
713
  event.preventDefault();
505
714
  event.stopPropagation();
506
715
  replayingSendClick = true;
@@ -1136,6 +1345,38 @@ var quickEmpty = {
1136
1345
  padding: "14px 10px",
1137
1346
  textAlign: "center"
1138
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
+ };
1139
1380
 
1140
1381
  // src/client/ContextMenuHost.tsx
1141
1382
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -1627,146 +1868,236 @@ function TextFieldRow(props) {
1627
1868
  ] });
1628
1869
  }
1629
1870
  function QuickPromptsEditor(props) {
1630
- const [rows, setRows] = (0, import_react3.useState)(() => props.items.map((item) => ({ ...item })));
1871
+ const [draft, setDraft] = (0, import_react3.useState)(() => props.book);
1872
+ const [activeId, setActiveId] = (0, import_react3.useState)(() => props.book.categories[0]?.id ?? "");
1631
1873
  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;
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) => {
1653
1884
  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
- });
1885
+ setDraft(next);
1660
1886
  };
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);
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)"
1670
1899
  };
1671
1900
  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)"
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
+ ]
1683
1916
  },
1684
- children: [
1685
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
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: [
1686
2006
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1687
2007
  "input",
1688
2008
  {
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,
2009
+ type: "checkbox",
2010
+ checked: prompt.always,
2011
+ style: { margin: 0, cursor: "pointer" },
1694
2012
  onChange: (event) => {
1695
- update(row2.id, { label: event.target.value });
1696
- },
1697
- style: { ...textInput, flex: "0 0 150px" }
2013
+ edit(withPromptPatched(draft, current.id, prompt.id, { always: event.target.checked }));
2014
+ }
1698
2015
  }
1699
2016
  ),
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" })
2017
+ "\u9ED8\u8BA4\u63D2\u5165"
1724
2018
  ] }),
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" }),
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
+ ] }),
1744
2049
  /* @__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
2050
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1747
2051
  "button",
1748
2052
  {
1749
2053
  type: "button",
1750
2054
  style: dirty ? pillActive : pill,
1751
- onClick: save,
1752
- disabled: !dirty,
1753
- children: dirty ? "\u4FDD\u5B58\u4FEE\u6539" : "\u5DF2\u4FDD\u5B58"
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"
1754
2070
  }
1755
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" }),
1756
2078
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: rowDesc, children: [
1757
2079
  "\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"
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}` : ""
1764
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"
1765
2094
  ] })
1766
2095
  ] });
1767
2096
  }
1768
- function SettingsSection({ useLive, actions }) {
2097
+ function SettingsSection({ useLive, useBook, useBookStatus, actions }) {
1769
2098
  const settings = useLive((item) => item);
2099
+ const book = useBook((item) => item);
2100
+ const bookStatus = useBookStatus((item) => item);
1770
2101
  const [conflict, setConflict] = (0, import_react3.useState)(null);
1771
2102
  const setKey = (side, chord) => {
1772
2103
  const other = side === "send" ? settings.newlineKey : settings.sendKey;
@@ -1783,8 +2114,8 @@ function SettingsSection({ useLive, actions }) {
1783
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`;
1784
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}`;
1785
2116
  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"}`;
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"}`;
1788
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";
1789
2120
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: "4px 2px" }, children: [
1790
2121
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "dsh-ux-card", children: [
@@ -1916,28 +2247,24 @@ function SettingsSection({ useLive, actions }) {
1916
2247
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...row, borderTop: "none", flexDirection: "column", alignItems: "stretch", gap: 8 }, children: [
1917
2248
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: rowText, children: [
1918
2249
  /* @__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" })
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" })
1920
2251
  ] }),
1921
2252
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1922
2253
  QuickPromptsEditor,
1923
2254
  {
1924
- items: settings.quickPrompts,
2255
+ book,
2256
+ status: bookStatus,
1925
2257
  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);
2258
+ actions.saveBook(next);
2259
+ },
2260
+ onReload: () => {
2261
+ actions.reloadBook();
1937
2262
  },
1938
- children: "\u6062\u590D\u5185\u7F6E 9 \u6761"
2263
+ onReset: () => {
2264
+ actions.resetBook();
2265
+ }
1939
2266
  }
1940
- ) })
2267
+ )
1941
2268
  ] })
1942
2269
  ] }),
1943
2270
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(FoldCard, { name: "\u8BBE\u7F6E\u9762\u677F", summary: panelSummary, children: [
@@ -2154,14 +2481,22 @@ function QuickCommandsPanel({
2154
2481
  usePanel,
2155
2482
  useBusy,
2156
2483
  useNotice,
2484
+ useBook,
2485
+ useBookStatus,
2157
2486
  actions
2158
2487
  }) {
2159
2488
  const settings = useLive((item) => item);
2160
2489
  const anchor = usePanel((item) => item);
2161
2490
  const busy = useBusy((item) => item);
2162
2491
  const notice = useNotice((item) => item);
2492
+ const book = useBook((item) => item);
2493
+ const status = useBookStatus((item) => item);
2163
2494
  const ref = (0, import_react5.useRef)(null);
2164
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];
2165
2500
  (0, import_react5.useLayoutEffect)(() => {
2166
2501
  if (anchor === null) {
2167
2502
  setPosition(null);
@@ -2174,7 +2509,7 @@ function QuickCommandsPanel({
2174
2509
  const above = anchor.bottom - height - GAP;
2175
2510
  const top = above >= MARGIN ? above : Math.min(anchor.bottom + GAP, window.innerHeight - height - MARGIN);
2176
2511
  setPosition({ left, top: Math.max(MARGIN, top) });
2177
- }, [anchor?.left, anchor?.bottom, settings.quickPrompts.length, busy]);
2512
+ }, [anchor?.left, anchor?.bottom, counts.prompts, categories.length, busy]);
2178
2513
  (0, import_react5.useEffect)(() => {
2179
2514
  if (anchor === null) return;
2180
2515
  const onPointerDown = (event) => {
@@ -2198,8 +2533,9 @@ function QuickCommandsPanel({
2198
2533
  };
2199
2534
  }, [anchor === null, actions]);
2200
2535
  if (anchor === null) return null;
2201
- const items = settings.quickPrompts;
2202
2536
  const tier = settings.optimizerTier;
2537
+ const items = active?.prompts ?? [];
2538
+ const failure = status !== "" && status !== "saving" ? status : "";
2203
2539
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2204
2540
  "div",
2205
2541
  {
@@ -2247,13 +2583,48 @@ function QuickCommandsPanel({
2247
2583
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: busy ? "\u4F18\u5316\u4E2D\u2026" : "\u4F18\u5316\u63D0\u793A\u8BCD" })
2248
2584
  ]
2249
2585
  }
2250
- )
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
+ ] })
2251
2622
  ] }),
2252
2623
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickList, children: [
2253
2624
  items.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: quickEmpty, children: [
2254
- "\u8FD8\u6CA1\u6709\u5FEB\u6377\u6307\u4EE4\u3002",
2625
+ "\u8FD9\u4E2A\u5206\u7C7B\u91CC\u8FD8\u6CA1\u6709\u6761\u76EE\u3002",
2255
2626
  /* @__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"
2627
+ "\u5728\u300C\u8BBE\u7F6E \u2192 \u8F93\u5165\u4F53\u9A8C \u2192 \u5FEB\u6377\u6307\u4EE4\u6E05\u5355\u300D\u91CC\u6DFB\u52A0\u3002"
2257
2628
  ] }),
2258
2629
  items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
2259
2630
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
@@ -2274,7 +2645,7 @@ function QuickCommandsPanel({
2274
2645
  ]
2275
2646
  }
2276
2647
  ),
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: [
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: [
2278
2649
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2279
2650
  "input",
2280
2651
  {
@@ -2291,8 +2662,20 @@ function QuickCommandsPanel({
2291
2662
  ] }, item.id))
2292
2663
  ] }),
2293
2664
  /* @__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" })
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
+ )
2296
2679
  ] })
2297
2680
  ]
2298
2681
  }
@@ -2326,6 +2709,73 @@ function apply(ctx) {
2326
2709
  console.error("[composer-ux] settings clear failed", error);
2327
2710
  });
2328
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
+ };
2329
2779
  const resetAll = () => {
2330
2780
  void scope.mutate(
2331
2781
  [
@@ -2342,12 +2792,12 @@ function apply(ctx) {
2342
2792
  HEADER_NAME_FIELD,
2343
2793
  HEADER_VALUE_FIELD,
2344
2794
  HEADER_ROUTES_FIELD,
2345
- QUICK_PROMPTS_FIELD,
2346
2795
  OPTIMIZER_TIER_FIELD
2347
2796
  ].map((field) => ({ op: "unset", path: [field] }))
2348
2797
  ).catch((error) => {
2349
2798
  console.error("[composer-ux] settings reset failed", error);
2350
2799
  });
2800
+ bookActions.resetBook();
2351
2801
  };
2352
2802
  let noticeTimer;
2353
2803
  const note = (message, ms = 4500) => {
@@ -2361,9 +2811,6 @@ function apply(ctx) {
2361
2811
  ctx.effect(() => () => {
2362
2812
  if (noticeTimer !== void 0) clearTimeout(noticeTimer);
2363
2813
  }, "composer-ux: quick notice timer");
2364
- const writeQuickPrompts = (next) => {
2365
- setField(QUICK_PROMPTS_FIELD, next);
2366
- };
2367
2814
  const quickActions = {
2368
2815
  toggle: (anchor) => {
2369
2816
  if (panel.getSnapshot() !== null) {
@@ -2410,10 +2857,12 @@ function apply(ctx) {
2410
2857
  setTier: (tier) => {
2411
2858
  setField(OPTIMIZER_TIER_FIELD, tier);
2412
2859
  },
2860
+ /**
2861
+ * 「默认插入」勾选:按 id 跨分类找那一条,直接写盘。
2862
+ * 这个标记就是「点发送时自动附加到消息末尾」的依据(见 interceptors.ts)。
2863
+ */
2413
2864
  setAlways: (id, value) => {
2414
- writeQuickPrompts(live.getSnapshot().quickPrompts.map(
2415
- (item) => item.id === id ? { ...item, always: value } : item
2416
- ));
2865
+ commitBook(withAlwaysToggled(book.getSnapshot(), id, value));
2417
2866
  }
2418
2867
  };
2419
2868
  slots.inject("settings.section", () => slots.register({
@@ -2422,8 +2871,15 @@ function apply(ctx) {
2422
2871
  order: 40,
2423
2872
  label: "\u8F93\u5165\u4F53\u9A8C",
2424
2873
  inject: () => ({
2425
- hooks: { live },
2426
- 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
+ }
2427
2883
  })
2428
2884
  }, SettingsSection));
2429
2885
  slots.inject("shell.overlay", () => slots.register({
@@ -2463,8 +2919,21 @@ function apply(ctx) {
2463
2919
  name: "shell.overlay",
2464
2920
  id: "composer-ux-quick-panel",
2465
2921
  inject: () => ({
2466
- hooks: { live, panel, busy: optimizing, notice: panelNotice },
2467
- actions: quickActions
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
+ }
2468
2937
  })
2469
2938
  }, QuickCommandsPanel));
2470
2939
  ctx.effect(() => installSettingsCardStyle(), "composer-ux: settings card style");
@@ -2486,6 +2955,7 @@ function apply(ctx) {
2486
2955
  if (enabled && dispose === null) {
2487
2956
  dispose = installInterceptors({
2488
2957
  settings: () => live.getSnapshot(),
2958
+ alwaysPrompts: () => alwaysQuickPrompts(book.getSnapshot()),
2489
2959
  setMenu: (state2) => {
2490
2960
  menu2.set(state2);
2491
2961
  },