dsh-composer-ux 0.2.0 → 0.4.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/CHANGELOG.md +76 -0
- package/README.md +46 -7
- package/lib/client.js +1206 -348
- package/lib/client.js.map +4 -4
- package/lib/index.js +347 -17
- package/lib/index.js.map +4 -4
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -804,24 +804,134 @@ var MENU_FIELDS = [
|
|
|
804
804
|
"menuDelete",
|
|
805
805
|
"menuSelectAll"
|
|
806
806
|
];
|
|
807
|
+
var MENU_MODE_FIELD = "menuMode";
|
|
807
808
|
var MENU_NATIVE_FIELD = "menuNative";
|
|
808
809
|
var QUICK_PROMPTS_FIELD = "quickPrompts";
|
|
809
810
|
var OPTIMIZER_TIER_FIELD = "optimizerTier";
|
|
810
|
-
var
|
|
811
|
+
var QUICK_PROMPT_MAX = 60;
|
|
812
|
+
var QUICK_CATEGORY_MAX = 20;
|
|
813
|
+
var QUICK_CATEGORY_NAME_MAX = 40;
|
|
814
|
+
var QUICK_LABEL_MAX = 40;
|
|
815
|
+
var QUICK_TEXT_MAX = 2e5;
|
|
816
|
+
var OPTIMIZE_TEXT_MAX = 8e3;
|
|
817
|
+
var QUICK_PROMPTS_API_PATH = "/composer-ux/prompts";
|
|
818
|
+
var QUICK_BOOK_VERSION = 2;
|
|
819
|
+
var DEFAULT_CATEGORY_NAME = "\u9ED8\u8BA4";
|
|
811
820
|
var OPTIMIZE_OUTPUT_MAX = 12e3;
|
|
812
821
|
var OPTIMIZER_API_PATH = "/composer-ux/optimize";
|
|
813
822
|
var DEFAULT_OPTIMIZER_TIER = "advanced";
|
|
814
823
|
var DEFAULT_QUICK_PROMPTS = [
|
|
815
|
-
{ 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 },
|
|
816
|
-
{ 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 },
|
|
817
|
-
{ 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 },
|
|
818
|
-
{ 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 },
|
|
819
|
-
{ 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 },
|
|
820
|
-
{ 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 },
|
|
821
|
-
{ 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 },
|
|
822
|
-
{ id: "builtin-8", label: "\u5199\u6D4B\u8BD5", prompt: "\u8BF7\u4E3A\u4E0B\u9762\u7684\u4EE3\u7801\u7F16\u5199\u5355\u5143\u6D4B\u8BD5\u3002", always: false },
|
|
823
|
-
{ 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 }
|
|
824
|
+
{ 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, firstOnly: false },
|
|
825
|
+
{ 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, firstOnly: false },
|
|
826
|
+
{ 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, firstOnly: false },
|
|
827
|
+
{ 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, firstOnly: false },
|
|
828
|
+
{ 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, firstOnly: false },
|
|
829
|
+
{ 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, firstOnly: false },
|
|
830
|
+
{ 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, firstOnly: false },
|
|
831
|
+
{ id: "builtin-8", label: "\u5199\u6D4B\u8BD5", prompt: "\u8BF7\u4E3A\u4E0B\u9762\u7684\u4EE3\u7801\u7F16\u5199\u5355\u5143\u6D4B\u8BD5\u3002", always: false, firstOnly: false },
|
|
832
|
+
{ 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, firstOnly: false }
|
|
824
833
|
];
|
|
834
|
+
function newQuickPromptId() {
|
|
835
|
+
return `qp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
836
|
+
}
|
|
837
|
+
function newQuickCategoryId() {
|
|
838
|
+
return `qc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
839
|
+
}
|
|
840
|
+
function defaultQuickBook() {
|
|
841
|
+
return {
|
|
842
|
+
version: QUICK_BOOK_VERSION,
|
|
843
|
+
categories: [{ id: "cat-default", name: DEFAULT_CATEGORY_NAME, prompts: DEFAULT_QUICK_PROMPTS }]
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function asPromptRow(value) {
|
|
847
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
848
|
+
const row = value;
|
|
849
|
+
const either = (first, second) => {
|
|
850
|
+
const picked = typeof first === "string" && first !== "" ? first : second;
|
|
851
|
+
return typeof picked === "string" ? picked : "";
|
|
852
|
+
};
|
|
853
|
+
const prompt = either(row.prompt, row.text).slice(0, QUICK_TEXT_MAX).trim();
|
|
854
|
+
if (prompt === "") return void 0;
|
|
855
|
+
const label = either(row.label, row.title).slice(0, QUICK_LABEL_MAX).trim();
|
|
856
|
+
const id = typeof row.id === "string" && row.id !== "" ? row.id.slice(0, 64) : newQuickPromptId();
|
|
857
|
+
const order = typeof row.order === "number" && Number.isFinite(row.order) ? Math.max(1, Math.round(row.order)) : void 0;
|
|
858
|
+
return {
|
|
859
|
+
prompt: {
|
|
860
|
+
id,
|
|
861
|
+
label: label === "" ? prompt.slice(0, 12) : label,
|
|
862
|
+
prompt,
|
|
863
|
+
always: row.always === true || row.autoSend === true,
|
|
864
|
+
firstOnly: row.firstOnly === true || row.autoSendFirst === true
|
|
865
|
+
},
|
|
866
|
+
order
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function toPromptList(values) {
|
|
870
|
+
const rows = [];
|
|
871
|
+
for (const value of values) {
|
|
872
|
+
const row = asPromptRow(value);
|
|
873
|
+
if (row !== void 0) rows.push({ ...row, index: rows.length });
|
|
874
|
+
}
|
|
875
|
+
rows.sort((a, b) => {
|
|
876
|
+
const left = a.order ?? Number.MAX_SAFE_INTEGER;
|
|
877
|
+
const right = b.order ?? Number.MAX_SAFE_INTEGER;
|
|
878
|
+
return left === right ? a.index - b.index : left - right;
|
|
879
|
+
});
|
|
880
|
+
const seen = /* @__PURE__ */ new Set();
|
|
881
|
+
const out = [];
|
|
882
|
+
for (const row of rows) {
|
|
883
|
+
if (out.length >= QUICK_PROMPT_MAX) break;
|
|
884
|
+
if (seen.has(row.prompt.id)) continue;
|
|
885
|
+
seen.add(row.prompt.id);
|
|
886
|
+
out.push(row.prompt);
|
|
887
|
+
}
|
|
888
|
+
return out;
|
|
889
|
+
}
|
|
890
|
+
function sanitizeBook(value) {
|
|
891
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
892
|
+
const source = value;
|
|
893
|
+
const flat = Array.isArray(value) ? value : Array.isArray(source.prompts) ? source.prompts : void 0;
|
|
894
|
+
if (flat !== void 0) {
|
|
895
|
+
const prompts = toPromptList(flat);
|
|
896
|
+
return {
|
|
897
|
+
version: QUICK_BOOK_VERSION,
|
|
898
|
+
categories: prompts.length === 0 ? [] : [{ id: "cat-default", name: DEFAULT_CATEGORY_NAME, prompts }]
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
if (!Array.isArray(source.categories)) return void 0;
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
const categories = [];
|
|
904
|
+
for (const raw of source.categories) {
|
|
905
|
+
if (categories.length >= QUICK_CATEGORY_MAX) break;
|
|
906
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
907
|
+
const row = raw;
|
|
908
|
+
const prompts = toPromptList(Array.isArray(row.prompts) ? row.prompts : []);
|
|
909
|
+
const wanted = typeof row.id === "string" && row.id !== "" ? row.id.slice(0, 64) : newQuickCategoryId();
|
|
910
|
+
const id = seen.has(wanted) ? newQuickCategoryId() : wanted;
|
|
911
|
+
seen.add(id);
|
|
912
|
+
const name2 = (typeof row.name === "string" ? row.name : "").slice(0, QUICK_CATEGORY_NAME_MAX).trim();
|
|
913
|
+
categories.push({ id, name: name2 === "" ? DEFAULT_CATEGORY_NAME : name2, prompts });
|
|
914
|
+
}
|
|
915
|
+
return { version: QUICK_BOOK_VERSION, categories };
|
|
916
|
+
}
|
|
917
|
+
function bookToFile(book) {
|
|
918
|
+
return {
|
|
919
|
+
version: QUICK_BOOK_VERSION,
|
|
920
|
+
categories: book.categories.map((category) => ({
|
|
921
|
+
id: category.id,
|
|
922
|
+
name: category.name,
|
|
923
|
+
prompts: category.prompts.map((prompt, index) => ({
|
|
924
|
+
id: prompt.id,
|
|
925
|
+
title: prompt.label,
|
|
926
|
+
text: prompt.prompt,
|
|
927
|
+
autoSend: prompt.always,
|
|
928
|
+
// 「仅首次」是我们加的键,只在为真时写出来:文件形状尽量贴近参考实现。
|
|
929
|
+
...prompt.firstOnly ? { autoSendFirst: true } : {},
|
|
930
|
+
order: index + 1
|
|
931
|
+
}))
|
|
932
|
+
}))
|
|
933
|
+
};
|
|
934
|
+
}
|
|
825
935
|
var HEADER_ENABLED_FIELD = "headerEnabled";
|
|
826
936
|
var HEADER_NAME_FIELD = "headerName";
|
|
827
937
|
var HEADER_VALUE_FIELD = "headerValue";
|
|
@@ -846,7 +956,7 @@ var DEFAULT_SETTINGS = {
|
|
|
846
956
|
menuPaste: true,
|
|
847
957
|
menuDelete: true,
|
|
848
958
|
menuSelectAll: true,
|
|
849
|
-
|
|
959
|
+
menuMode: "official",
|
|
850
960
|
panelScroll: true,
|
|
851
961
|
panelResize: true,
|
|
852
962
|
headerEnabled: false,
|
|
@@ -967,6 +1077,141 @@ function buildOptimizeUser(original) {
|
|
|
967
1077
|
].join("\n");
|
|
968
1078
|
}
|
|
969
1079
|
|
|
1080
|
+
// src/quick-store.ts
|
|
1081
|
+
import { mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
1082
|
+
import { homedir } from "node:os";
|
|
1083
|
+
import { dirname, join } from "node:path";
|
|
1084
|
+
var QUICK_STORE_FILE = "quick-prompts.json";
|
|
1085
|
+
var LOCK_RETRY_MS = 50;
|
|
1086
|
+
var LOCK_RETRY_MAX = 40;
|
|
1087
|
+
var LOCK_STALE_MS = 3e4;
|
|
1088
|
+
function dshHome() {
|
|
1089
|
+
const fromEnv = process.env.DSH_HOME?.trim();
|
|
1090
|
+
return fromEnv !== void 0 && fromEnv !== "" ? fromEnv : join(homedir(), ".dsh");
|
|
1091
|
+
}
|
|
1092
|
+
function quickStorePath() {
|
|
1093
|
+
return join(dshHome(), QUICK_STORE_FILE);
|
|
1094
|
+
}
|
|
1095
|
+
function errorText(error) {
|
|
1096
|
+
return error instanceof Error ? error.message : String(error);
|
|
1097
|
+
}
|
|
1098
|
+
function hasCode(error, code) {
|
|
1099
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
1100
|
+
}
|
|
1101
|
+
function delay(ms) {
|
|
1102
|
+
return new Promise((resolve2) => {
|
|
1103
|
+
setTimeout(resolve2, ms);
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
async function quarantine(file) {
|
|
1107
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1108
|
+
const target = `${file}.bad-${stamp}`;
|
|
1109
|
+
try {
|
|
1110
|
+
await rename(file, target);
|
|
1111
|
+
return target;
|
|
1112
|
+
} catch {
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
async function readQuickBook(file = quickStorePath()) {
|
|
1117
|
+
let raw;
|
|
1118
|
+
try {
|
|
1119
|
+
raw = await readFile(file, "utf8");
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
if (hasCode(error, "ENOENT")) return { kind: "missing" };
|
|
1122
|
+
return { kind: "broken", error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${errorText(error)}` };
|
|
1123
|
+
}
|
|
1124
|
+
if (raw.trim() === "") return { kind: "broken", error: "\u6587\u4EF6\u662F\u7A7A\u7684", quarantined: await quarantine(file) };
|
|
1125
|
+
let parsed;
|
|
1126
|
+
try {
|
|
1127
|
+
parsed = JSON.parse(raw);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
return { kind: "broken", error: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${errorText(error)}`, quarantined: await quarantine(file) };
|
|
1130
|
+
}
|
|
1131
|
+
const book = sanitizeBook(parsed);
|
|
1132
|
+
if (book === void 0) {
|
|
1133
|
+
return {
|
|
1134
|
+
kind: "broken",
|
|
1135
|
+
error: "\u7ED3\u6784\u8BA4\u4E0D\u51FA\uFF08\u65E2\u4E0D\u662F categories \u4E5F\u4E0D\u662F\u5E73\u94FA prompts\uFF09",
|
|
1136
|
+
quarantined: await quarantine(file)
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
return { kind: "ok", book };
|
|
1140
|
+
}
|
|
1141
|
+
async function acquireLock(file) {
|
|
1142
|
+
const lock = `${file}.lock`;
|
|
1143
|
+
for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt += 1) {
|
|
1144
|
+
try {
|
|
1145
|
+
const handle = await open(lock, "wx", 384);
|
|
1146
|
+
await handle.close();
|
|
1147
|
+
return async () => {
|
|
1148
|
+
try {
|
|
1149
|
+
await unlink(lock);
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
if (!hasCode(error, "EEXIST")) throw error;
|
|
1155
|
+
let ageMs;
|
|
1156
|
+
try {
|
|
1157
|
+
const info = await open(lock, "r");
|
|
1158
|
+
try {
|
|
1159
|
+
const stat = await info.stat();
|
|
1160
|
+
ageMs = Date.now() - stat.mtimeMs;
|
|
1161
|
+
} finally {
|
|
1162
|
+
await info.close();
|
|
1163
|
+
}
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
if (ageMs !== void 0 && ageMs > LOCK_STALE_MS) {
|
|
1167
|
+
throw new Error(
|
|
1168
|
+
`\u5199\u5165\u9501 ${lock} \u5DF2\u5B58\u5728 ${Math.round(ageMs / 1e3)} \u79D2\uFF0C\u50CF\u662F\u4E0A\u6B21\u5199\u5165\u5D29\u6E83\u7559\u4E0B\u7684\uFF1B\u786E\u8BA4\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684 DSH \u540E\u5220\u9664\u5B83\u518D\u91CD\u8BD5`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
await delay(LOCK_RETRY_MS);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
throw new Error(`\u53E6\u4E00\u4E2A\u8FDB\u7A0B\u6B63\u5728\u5199\u5165 ${file}\uFF08\u7B49\u5F85 ${String(LOCK_RETRY_MS * LOCK_RETRY_MAX / 1e3)} \u79D2\u4ECD\u672A\u62FF\u5230\u9501\uFF09`);
|
|
1175
|
+
}
|
|
1176
|
+
async function writeQuickBook(book, file = quickStorePath()) {
|
|
1177
|
+
await mkdir(dirname(file), { recursive: true });
|
|
1178
|
+
const release = await acquireLock(file);
|
|
1179
|
+
try {
|
|
1180
|
+
await writeLocked(book, file);
|
|
1181
|
+
} finally {
|
|
1182
|
+
await release();
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
async function writeLocked(book, file) {
|
|
1186
|
+
const safe = sanitizeBook(book) ?? { version: QUICK_BOOK_VERSION, categories: [] };
|
|
1187
|
+
const tmp = `${file}.${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
1188
|
+
const handle = await open(tmp, "w", 384);
|
|
1189
|
+
try {
|
|
1190
|
+
await handle.writeFile(`${JSON.stringify(bookToFile(safe), null, 2)}
|
|
1191
|
+
`, "utf8");
|
|
1192
|
+
await handle.sync();
|
|
1193
|
+
} finally {
|
|
1194
|
+
await handle.close();
|
|
1195
|
+
}
|
|
1196
|
+
await rename(tmp, file);
|
|
1197
|
+
}
|
|
1198
|
+
async function ensureQuickBook(seed, file = quickStorePath()) {
|
|
1199
|
+
const current = await readQuickBook(file);
|
|
1200
|
+
if (current.kind !== "missing") return current;
|
|
1201
|
+
await mkdir(dirname(file), { recursive: true });
|
|
1202
|
+
const release = await acquireLock(file);
|
|
1203
|
+
try {
|
|
1204
|
+
const again = await readQuickBook(file);
|
|
1205
|
+
if (again.kind !== "missing") return again;
|
|
1206
|
+
const migrated = seed === void 0 ? void 0 : sanitizeBook({ prompts: seed });
|
|
1207
|
+
const book = migrated !== void 0 && migrated.categories.length > 0 ? migrated : defaultQuickBook();
|
|
1208
|
+
await writeLocked(book, file);
|
|
1209
|
+
return { kind: "ok", book };
|
|
1210
|
+
} finally {
|
|
1211
|
+
await release();
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
970
1215
|
// src/host.ts
|
|
971
1216
|
var name = "composer-ux";
|
|
972
1217
|
var PANEL_MIN = 560;
|
|
@@ -1013,7 +1258,7 @@ function pickRoutes(providers, listed) {
|
|
|
1013
1258
|
}
|
|
1014
1259
|
return listed.filter((key) => Object.prototype.hasOwnProperty.call(providers, key));
|
|
1015
1260
|
}
|
|
1016
|
-
function
|
|
1261
|
+
function errorText2(error) {
|
|
1017
1262
|
if (error instanceof Error && error.message !== "") return error.message;
|
|
1018
1263
|
return String(error);
|
|
1019
1264
|
}
|
|
@@ -1078,7 +1323,7 @@ async function mirrorHeader(settings) {
|
|
|
1078
1323
|
await settings.mutate(LLM_NAMESPACE, providerOps);
|
|
1079
1324
|
} catch (error) {
|
|
1080
1325
|
failed = true;
|
|
1081
|
-
status = `\u5199\u5165\u5931\u8D25\uFF1A${
|
|
1326
|
+
status = `\u5199\u5165\u5931\u8D25\uFF1A${errorText2(error)}`;
|
|
1082
1327
|
}
|
|
1083
1328
|
}
|
|
1084
1329
|
const nextAppliedName = !failed && applied.length > 0 ? name2 : "";
|
|
@@ -1107,7 +1352,13 @@ function apply(ctx) {
|
|
|
1107
1352
|
field,
|
|
1108
1353
|
Schema.boolean().default(DEFAULT_SETTINGS[field])
|
|
1109
1354
|
])),
|
|
1110
|
-
|
|
1355
|
+
// 右键菜单模式(0.5.0 起三档)。**故意不给默认值、且声明成可选**:
|
|
1356
|
+
// 「文档里没有这个键」本身就是迁移要用的信息——净化据此按旧布尔 menuNative
|
|
1357
|
+
// 推断(见 menuModeFrom)。给了默认值就再也分不出「从没设置过」与「明确设成了它」。
|
|
1358
|
+
[MENU_MODE_FIELD]: Schema.string().required(false),
|
|
1359
|
+
// 旧的布尔字段:只作迁移线索,故同样不给默认值——旧文档里的 true / false
|
|
1360
|
+
// 都要保住原意(true = 浏览器菜单、false = 明确选过自定义菜单)。
|
|
1361
|
+
[MENU_NATIVE_FIELD]: Schema.boolean().required(false),
|
|
1111
1362
|
[PANEL_SCROLL_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.panelScroll),
|
|
1112
1363
|
[PANEL_RESIZE_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.panelResize),
|
|
1113
1364
|
// schemastery 无 .optional():可选键用 .required(false)。
|
|
@@ -1160,7 +1411,7 @@ function apply(ctx) {
|
|
|
1160
1411
|
ctx.inject(["webServer", "llm"], (optCtx) => {
|
|
1161
1412
|
const LLM_TIMEOUT_MS = 18e4;
|
|
1162
1413
|
const BODY_MAX_BYTES = 1e6;
|
|
1163
|
-
const TEXT_MAX =
|
|
1414
|
+
const TEXT_MAX = OPTIMIZE_TEXT_MAX;
|
|
1164
1415
|
const sendJson = (res, code, payload) => {
|
|
1165
1416
|
res.writeHead(code, { "content-type": "application/json; charset=utf-8" });
|
|
1166
1417
|
res.end(JSON.stringify(payload));
|
|
@@ -1197,7 +1448,7 @@ function apply(ctx) {
|
|
|
1197
1448
|
try {
|
|
1198
1449
|
payload = objectOf(JSON.parse(await readBody(req))) ?? {};
|
|
1199
1450
|
} catch (error) {
|
|
1200
|
-
sendJson(res, 400, { ok: false, error: `\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${
|
|
1451
|
+
sendJson(res, 400, { ok: false, error: `\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${errorText2(error)}` });
|
|
1201
1452
|
return;
|
|
1202
1453
|
}
|
|
1203
1454
|
const text = textOf(payload.text).trim();
|
|
@@ -1248,7 +1499,7 @@ function apply(ctx) {
|
|
|
1248
1499
|
}
|
|
1249
1500
|
}
|
|
1250
1501
|
} catch (error) {
|
|
1251
|
-
failure =
|
|
1502
|
+
failure = errorText2(error);
|
|
1252
1503
|
} finally {
|
|
1253
1504
|
clearTimeout(timer);
|
|
1254
1505
|
}
|
|
@@ -1274,6 +1525,85 @@ function apply(ctx) {
|
|
|
1274
1525
|
handler: handle
|
|
1275
1526
|
}), "composer-ux: prompt optimizer route");
|
|
1276
1527
|
});
|
|
1528
|
+
ctx.inject(["webServer"], (storeCtx) => {
|
|
1529
|
+
const BODY_MAX_BYTES = 8e6;
|
|
1530
|
+
const sendJson = (res, code, payload) => {
|
|
1531
|
+
res.writeHead(code, { "content-type": "application/json; charset=utf-8" });
|
|
1532
|
+
res.end(JSON.stringify(payload));
|
|
1533
|
+
};
|
|
1534
|
+
const readBody = async (req) => {
|
|
1535
|
+
const chunks = [];
|
|
1536
|
+
let total = 0;
|
|
1537
|
+
for await (const chunk of req) {
|
|
1538
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
1539
|
+
total += buf.length;
|
|
1540
|
+
if (total > BODY_MAX_BYTES) throw new Error("\u8BF7\u6C42\u4F53\u8FC7\u5927");
|
|
1541
|
+
chunks.push(buf);
|
|
1542
|
+
}
|
|
1543
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1544
|
+
};
|
|
1545
|
+
const legacyPrompts = () => {
|
|
1546
|
+
try {
|
|
1547
|
+
const settings = storeCtx.get("settings");
|
|
1548
|
+
const row = objectOf(settings?.get(NAMESPACE));
|
|
1549
|
+
const list = row?.[QUICK_PROMPTS_FIELD];
|
|
1550
|
+
return Array.isArray(list) ? list : void 0;
|
|
1551
|
+
} catch {
|
|
1552
|
+
return void 0;
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
const handle = async (req, res) => {
|
|
1556
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
1557
|
+
if (method !== "GET" && method !== "POST") {
|
|
1558
|
+
sendJson(res, 405, { ok: false, error: "\u53EA\u63A5\u53D7 GET / POST" });
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const file = quickStorePath();
|
|
1562
|
+
const outcome = await ensureQuickBook(legacyPrompts(), file);
|
|
1563
|
+
if (outcome.kind === "broken") {
|
|
1564
|
+
sendJson(res, 200, {
|
|
1565
|
+
ok: false,
|
|
1566
|
+
file,
|
|
1567
|
+
error: `\u5FEB\u6377\u6307\u4EE4\u6587\u4EF6\u8BFB\u4E0D\u4E86\uFF1A${outcome.error}`,
|
|
1568
|
+
quarantined: outcome.quarantined ?? ""
|
|
1569
|
+
});
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (method === "GET") {
|
|
1573
|
+
sendJson(res, 200, { ok: true, file, book: outcome.book });
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
let payload;
|
|
1577
|
+
try {
|
|
1578
|
+
payload = objectOf(JSON.parse(await readBody(req))) ?? {};
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
sendJson(res, 400, { ok: false, error: `\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${errorText2(error)}` });
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
const candidate = payload.book !== void 0 ? payload.book : payload;
|
|
1584
|
+
const next = sanitizeBook(candidate);
|
|
1585
|
+
if (next === void 0) {
|
|
1586
|
+
sendJson(res, 400, {
|
|
1587
|
+
ok: false,
|
|
1588
|
+
error: "\u63D0\u4EA4\u7684\u7ED3\u6784\u8BA4\u4E0D\u51FA\uFF08\u671F\u671B { book: { categories: [...] } } \u6216 { categories: [...] }\uFF09"
|
|
1589
|
+
});
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
try {
|
|
1593
|
+
await writeQuickBook(next, file);
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
sendJson(res, 500, { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${errorText2(error)}` });
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const verified = await readQuickBook(file);
|
|
1599
|
+
sendJson(res, 200, { ok: true, file, book: verified.kind === "ok" ? verified.book : next });
|
|
1600
|
+
};
|
|
1601
|
+
storeCtx.effect(() => storeCtx.webServer.register({
|
|
1602
|
+
kind: "exact",
|
|
1603
|
+
path: QUICK_PROMPTS_API_PATH,
|
|
1604
|
+
handler: handle
|
|
1605
|
+
}), "composer-ux: quick prompt store route");
|
|
1606
|
+
});
|
|
1277
1607
|
}
|
|
1278
1608
|
export {
|
|
1279
1609
|
apply,
|