opencode-go-usage-tui 1.2.0 → 1.3.1
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/README.md +39 -6
- package/README_EN.md +39 -6
- package/dist/tui.js +907 -58
- package/package.json +1 -1
- package/src/i18n.ts +88 -0
- package/src/index.tsx +433 -45
- package/src/pricing.ts +348 -0
package/dist/tui.js
CHANGED
|
@@ -9,8 +9,8 @@ import { insertNode as _$insertNode } from "@opentui/solid";
|
|
|
9
9
|
import { insert as _$insert } from "@opentui/solid";
|
|
10
10
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
11
11
|
import { createSignal, createEffect, onMount, onCleanup, Show } from "solid-js";
|
|
12
|
-
import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from "node:fs";
|
|
13
|
-
import { join, dirname } from "node:path";
|
|
12
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, renameSync, existsSync as existsSync2 } from "node:fs";
|
|
13
|
+
import { join as join2, dirname as dirname2 } from "node:path";
|
|
14
14
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
15
15
|
|
|
16
16
|
// src/i18n.ts
|
|
@@ -49,7 +49,51 @@ var ZH_T = {
|
|
|
49
49
|
onboardLangDesc: "Choose display language / \u9009\u62E9\u663E\u793A\u8BED\u8A00",
|
|
50
50
|
onboardConfigTitle: "Welcome to Go Usage / \u6B22\u8FCE\u4F7F\u7528 Go \u7528\u91CF",
|
|
51
51
|
onboardConfigDesc: "First-time setup / \u9996\u6B21\u4F7F\u7528\u9700\u914D\u7F6E\u8D26\u53F7",
|
|
52
|
-
setupCanceled: "\u5DF2\u53D6\u6D88\u914D\u7F6E\uFF0C\u8F93\u5165 /go-config \u53EF\u91CD\u65B0\u8BBE\u7F6E"
|
|
52
|
+
setupCanceled: "\u5DF2\u53D6\u6D88\u914D\u7F6E\uFF0C\u8F93\u5165 /go-config \u53EF\u91CD\u65B0\u8BBE\u7F6E",
|
|
53
|
+
// ── 模型价格表 ──
|
|
54
|
+
priceTitle: "\u6A21\u578B\u4EF7\u683C",
|
|
55
|
+
priceLoading: "\u6293\u53D6\u4EF7\u683C\u4E2D...",
|
|
56
|
+
priceError: "\u4EF7\u683C\u6293\u53D6\u5931\u8D25",
|
|
57
|
+
priceUpdated: "\u66F4\u65B0\u65F6\u95F4",
|
|
58
|
+
priceChanged: "\u4EF7\u683C\u6709\u53D8\u5316",
|
|
59
|
+
priceNoChange: "\u65E0\u53D8\u5316",
|
|
60
|
+
priceChangesSummary: "{t} \u9879\u53D8\u5316\uFF1A{a} \u65B0\u589E / {r} \u4E0B\u67B6 / {p} \u4EF7\u683C / {l} \u9650\u989D",
|
|
61
|
+
priceVariantTag: "{n}\u4E2A\u6863\u4F4D",
|
|
62
|
+
priceInput: "\u8F93\u5165",
|
|
63
|
+
priceOutput: "\u8F93\u51FA",
|
|
64
|
+
priceCacheRead: "\u7F13\u5B58\u8BFB",
|
|
65
|
+
priceCacheWrite: "\u7F13\u5B58\u5199",
|
|
66
|
+
priceUsage: "\u6708\u9650\u989D",
|
|
67
|
+
priceReq5h: "5h\u8BF7\u6C42",
|
|
68
|
+
priceReqWeek: "\u5468\u8BF7\u6C42",
|
|
69
|
+
priceReqMonth: "\u6708\u8BF7\u6C42",
|
|
70
|
+
priceModelCol: "\u6A21\u578B",
|
|
71
|
+
priceReqs: "\u8BF7\u6C42\u6570(5h/\u5468/\u6708)",
|
|
72
|
+
priceSdk: "SDK",
|
|
73
|
+
priceDiffUp: "\u2191{v}",
|
|
74
|
+
priceDiffDown: "\u2193{v}",
|
|
75
|
+
priceDiffSame: "=",
|
|
76
|
+
priceChartTitle: "\u4EF7\u683C\u53D8\u5316\u8BB0\u5F55",
|
|
77
|
+
priceChartEmpty: "\u6682\u65E0\u53D8\u5316\u8BB0\u5F55",
|
|
78
|
+
priceChartAdded: "\u65B0\u589E: {n}",
|
|
79
|
+
priceChartChanged: "\u53D8\u5316: {n}",
|
|
80
|
+
priceChartRemoved: "\u79FB\u9664: {n}",
|
|
81
|
+
priceCollapse: "\u4EF7\u683C\u8868",
|
|
82
|
+
priceExpand: "\u4EF7\u683C\u8868",
|
|
83
|
+
settingsTitle: "Go \u7528\u91CF\u8BBE\u7F6E",
|
|
84
|
+
settingsDesc: "\u5173\u6CE8\u6A21\u578B\u3001\u5237\u65B0\u95F4\u9694\u8BBE\u7F6E",
|
|
85
|
+
settingsFocus: "\u5173\u6CE8\u7684\u6A21\u578B",
|
|
86
|
+
settingsRefresh: "\u5237\u65B0\u95F4\u9694 (\u79D2)",
|
|
87
|
+
settingsRefreshUsage: "\u7528\u91CF\u5237\u65B0\u95F4\u9694 (\u79D2)",
|
|
88
|
+
settingsRefreshPricing: "\u4EF7\u683C\u5237\u65B0\u95F4\u9694 (\u79D2)",
|
|
89
|
+
settingsSaved: "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
|
|
90
|
+
settingsCanceled: "\u5DF2\u53D6\u6D88\u8BBE\u7F6E",
|
|
91
|
+
focusAll: "\u5168\u90E8\u9009\u62E9",
|
|
92
|
+
focusNone: "\u6E05\u7A7A\u9009\u62E9",
|
|
93
|
+
focusModelsHint: "\u9009\u62E9\u8981\u5173\u6CE8\u7684\u6A21\u578B\uFF08\u7559\u7A7A\u663E\u793A\u5168\u90E8\uFF09",
|
|
94
|
+
focusDone: "\u5B8C\u6210",
|
|
95
|
+
focusToggle: "\u5207\u6362\u9009\u4E2D",
|
|
96
|
+
focusSelected: "\u5DF2\u9009 {n}"
|
|
53
97
|
};
|
|
54
98
|
var EN_T = {
|
|
55
99
|
panelTitle: "Go Usage",
|
|
@@ -86,7 +130,51 @@ var EN_T = {
|
|
|
86
130
|
onboardLangDesc: "Choose display language / \u9009\u62E9\u663E\u793A\u8BED\u8A00",
|
|
87
131
|
onboardConfigTitle: "Welcome to Go Usage / \u6B22\u8FCE\u4F7F\u7528 Go \u7528\u91CF",
|
|
88
132
|
onboardConfigDesc: "First-time setup / \u9996\u6B21\u4F7F\u7528\u9700\u914D\u7F6E\u8D26\u53F7",
|
|
89
|
-
setupCanceled: "Setup canceled, run /go-config to reconfigure"
|
|
133
|
+
setupCanceled: "Setup canceled, run /go-config to reconfigure",
|
|
134
|
+
// ── 模型价格表 ──
|
|
135
|
+
priceTitle: "Model Pricing",
|
|
136
|
+
priceLoading: "Fetching pricing...",
|
|
137
|
+
priceError: "Pricing fetch failed",
|
|
138
|
+
priceUpdated: "Pricing updated",
|
|
139
|
+
priceChanged: "Pricing changed",
|
|
140
|
+
priceNoChange: "No changes",
|
|
141
|
+
priceChangesSummary: "{t} change(s): {a} added / {r} removed / {p} price / {l} limit",
|
|
142
|
+
priceVariantTag: "{n} tiers",
|
|
143
|
+
priceInput: "In",
|
|
144
|
+
priceOutput: "Out",
|
|
145
|
+
priceCacheRead: "Cache In",
|
|
146
|
+
priceCacheWrite: "Cache Wr",
|
|
147
|
+
priceUsage: "Usage",
|
|
148
|
+
priceReq5h: "5h req",
|
|
149
|
+
priceReqWeek: "Wk req",
|
|
150
|
+
priceReqMonth: "Mo req",
|
|
151
|
+
priceModelCol: "Model",
|
|
152
|
+
priceReqs: "Requests (5h/wk/mo)",
|
|
153
|
+
priceSdk: "SDK",
|
|
154
|
+
priceDiffUp: "\u2191{v}",
|
|
155
|
+
priceDiffDown: "\u2193{v}",
|
|
156
|
+
priceDiffSame: "=",
|
|
157
|
+
priceChartTitle: "Price change history",
|
|
158
|
+
priceChartEmpty: "No change history yet",
|
|
159
|
+
priceChartAdded: "Added: {n}",
|
|
160
|
+
priceChartChanged: "Changed: {n}",
|
|
161
|
+
priceChartRemoved: "Removed: {n}",
|
|
162
|
+
priceCollapse: "Pricing",
|
|
163
|
+
priceExpand: "Pricing",
|
|
164
|
+
settingsTitle: "Go Usage Settings",
|
|
165
|
+
settingsDesc: "Focused models, refresh interval settings",
|
|
166
|
+
settingsFocus: "Focused models",
|
|
167
|
+
settingsRefresh: "Refresh interval (sec)",
|
|
168
|
+
settingsRefreshUsage: "Usage refresh interval (sec)",
|
|
169
|
+
settingsRefreshPricing: "Pricing refresh interval (sec)",
|
|
170
|
+
settingsSaved: "Settings saved",
|
|
171
|
+
settingsCanceled: "Settings canceled",
|
|
172
|
+
focusAll: "Select all",
|
|
173
|
+
focusNone: "Clear selection",
|
|
174
|
+
focusModelsHint: "Select models to focus (empty shows all)",
|
|
175
|
+
focusDone: "Done",
|
|
176
|
+
focusToggle: "Toggle select",
|
|
177
|
+
focusSelected: "{n} selected"
|
|
90
178
|
};
|
|
91
179
|
var LANGS = { zh: ZH_T, en: EN_T };
|
|
92
180
|
var LANG_META = [
|
|
@@ -113,27 +201,285 @@ function detectLang() {
|
|
|
113
201
|
}
|
|
114
202
|
}
|
|
115
203
|
|
|
204
|
+
// src/pricing.ts
|
|
205
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
206
|
+
import { join, dirname } from "node:path";
|
|
207
|
+
var DOCS_URL = "https://opencode.ai/docs/go";
|
|
208
|
+
var PRICING_DIR = process?.env?.OPENCODE_CONFIG_DIR || (process?.env?.USERPROFILE ? `${process.env.USERPROFILE}\\.config\\opencode` : "") || process?.env?.HOME + "/.config/opencode";
|
|
209
|
+
var PRICING_FILE = join(PRICING_DIR, "go-pricing-data.json");
|
|
210
|
+
function cleanText(html) {
|
|
211
|
+
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ").replace(/'|'/g, "'").trim();
|
|
212
|
+
}
|
|
213
|
+
function parsePrice(str) {
|
|
214
|
+
const s = (str || "").replace(/[$\s]/g, "");
|
|
215
|
+
if (!s || s === "-" || s === "null" || s === "\u2014" || s === "\u2013") return null;
|
|
216
|
+
const num = parseFloat(s.replace(/,/g, ""));
|
|
217
|
+
return isNaN(num) ? null : num;
|
|
218
|
+
}
|
|
219
|
+
function parseCount(str) {
|
|
220
|
+
const s = (str || "").replace(/[,\s]/g, "");
|
|
221
|
+
if (!s || s === "-" || s === "\u2014" || s === "\u2013") return 0;
|
|
222
|
+
const m = s.match(/^([\d.]+)k$/i);
|
|
223
|
+
if (m) return Math.round(parseFloat(m[1]) * 1e3);
|
|
224
|
+
const num = parseInt(s, 10);
|
|
225
|
+
return isNaN(num) ? 0 : num;
|
|
226
|
+
}
|
|
227
|
+
function normalizeName(name) {
|
|
228
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
229
|
+
}
|
|
230
|
+
function generateId(name) {
|
|
231
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
232
|
+
}
|
|
233
|
+
function extractTables(html) {
|
|
234
|
+
const tables = [];
|
|
235
|
+
const re = /<table[^>]*>([\s\S]*?)<\/table>/gi;
|
|
236
|
+
let m;
|
|
237
|
+
while ((m = re.exec(html)) !== null) tables.push(m[1]);
|
|
238
|
+
return tables;
|
|
239
|
+
}
|
|
240
|
+
function extractRows(tableHtml) {
|
|
241
|
+
const rows = [];
|
|
242
|
+
const rowRe = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
|
|
243
|
+
let rm;
|
|
244
|
+
while ((rm = rowRe.exec(tableHtml)) !== null) {
|
|
245
|
+
const cells = [];
|
|
246
|
+
const cellRe = /<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi;
|
|
247
|
+
let cm;
|
|
248
|
+
while ((cm = cellRe.exec(rm[1])) !== null) cells.push(cleanText(cm[1]));
|
|
249
|
+
if (cells.length > 0) rows.push(cells);
|
|
250
|
+
}
|
|
251
|
+
return rows;
|
|
252
|
+
}
|
|
253
|
+
function extractLinks(html) {
|
|
254
|
+
const links = [];
|
|
255
|
+
const re = /<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
256
|
+
let m;
|
|
257
|
+
while ((m = re.exec(html)) !== null) {
|
|
258
|
+
const href = m[1];
|
|
259
|
+
if (href.startsWith("http") || href.startsWith("/")) links.push(href);
|
|
260
|
+
}
|
|
261
|
+
return links;
|
|
262
|
+
}
|
|
263
|
+
async function fetchPricing() {
|
|
264
|
+
const res = await fetch(DOCS_URL, {
|
|
265
|
+
headers: {
|
|
266
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
267
|
+
"accept": "text/html"
|
|
268
|
+
},
|
|
269
|
+
signal: AbortSignal.timeout(15e3)
|
|
270
|
+
});
|
|
271
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
272
|
+
const html = await res.text();
|
|
273
|
+
const tables = extractTables(html);
|
|
274
|
+
if (tables.length < 4) throw new Error("Required tables not found");
|
|
275
|
+
const priceTable = tables[1];
|
|
276
|
+
const limitTable = tables[0];
|
|
277
|
+
const endpointTable = tables[2];
|
|
278
|
+
const retentionTable = tables[3];
|
|
279
|
+
const priceRows = extractRows(priceTable);
|
|
280
|
+
const limitRows = extractRows(limitTable);
|
|
281
|
+
const endpointRows = extractRows(endpointTable);
|
|
282
|
+
const retentionRows = extractRows(retentionTable);
|
|
283
|
+
const limitMap = /* @__PURE__ */ new Map();
|
|
284
|
+
for (const row of limitRows.slice(1)) {
|
|
285
|
+
if (row.length >= 4) limitMap.set(normalizeName(row[0]), row);
|
|
286
|
+
}
|
|
287
|
+
const endpointMap = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const row of endpointRows.slice(1)) {
|
|
289
|
+
if (row.length >= 4) endpointMap.set(normalizeName(row[0]), row);
|
|
290
|
+
}
|
|
291
|
+
const retentionMap = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const row of retentionRows.slice(1)) {
|
|
293
|
+
if (row.length >= 3) retentionMap.set(normalizeName(row[0]), row);
|
|
294
|
+
}
|
|
295
|
+
const priceGroupMap = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const row of priceRows.slice(1)) {
|
|
297
|
+
if (row.length < 5) continue;
|
|
298
|
+
const rawName = row[0];
|
|
299
|
+
const m = rawName.match(/^(.*?)\s*[((]\s*([^(()]*?)\s*[))]\s*$/);
|
|
300
|
+
const baseName = m ? m[1].trim() : rawName.trim();
|
|
301
|
+
const variant = m ? m[2].trim() : "";
|
|
302
|
+
const key = normalizeName(baseName);
|
|
303
|
+
if (!priceGroupMap.has(key)) priceGroupMap.set(key, []);
|
|
304
|
+
priceGroupMap.get(key).push({ baseName, variant, row });
|
|
305
|
+
}
|
|
306
|
+
const models = [];
|
|
307
|
+
for (const row of limitRows.slice(1)) {
|
|
308
|
+
if (row.length < 4) continue;
|
|
309
|
+
const name = row[0];
|
|
310
|
+
const key = normalizeName(name);
|
|
311
|
+
const id = generateId(name);
|
|
312
|
+
const group = priceGroupMap.get(key);
|
|
313
|
+
const endpointRow = endpointMap.get(key);
|
|
314
|
+
const retentionRow = retentionMap.get(key);
|
|
315
|
+
const variants = (group ?? []).map((g) => ({
|
|
316
|
+
label: g.variant || "default",
|
|
317
|
+
pricing: {
|
|
318
|
+
input: parsePrice(g.row[1]),
|
|
319
|
+
output: parsePrice(g.row[2]),
|
|
320
|
+
cachedRead: parsePrice(g.row[3]),
|
|
321
|
+
cachedWrite: g.row.length > 4 ? parsePrice(g.row[4]) : null
|
|
322
|
+
}
|
|
323
|
+
}));
|
|
324
|
+
const allInputs = variants.map((v) => v.pricing.input).filter((v) => v !== null);
|
|
325
|
+
const allOutputs = variants.map((v) => v.pricing.output).filter((v) => v !== null);
|
|
326
|
+
const allReads = variants.map((v) => v.pricing.cachedRead).filter((v) => v !== null);
|
|
327
|
+
const allWrites = variants.map((v) => v.pricing.cachedWrite).filter((v) => v !== null);
|
|
328
|
+
models.push({
|
|
329
|
+
id,
|
|
330
|
+
name,
|
|
331
|
+
variants,
|
|
332
|
+
pricing: {
|
|
333
|
+
input: variants.length === 1 ? variants[0].pricing.input : allInputs.length ? Math.min(...allInputs) : null,
|
|
334
|
+
output: variants.length === 1 ? variants[0].pricing.output : allOutputs.length ? Math.min(...allOutputs) : null,
|
|
335
|
+
cachedRead: variants.length === 1 ? variants[0].pricing.cachedRead : allReads.length ? Math.min(...allReads) : null,
|
|
336
|
+
cachedWrite: variants.length === 1 ? variants[0].pricing.cachedWrite : allWrites.length ? Math.min(...allWrites) : null
|
|
337
|
+
},
|
|
338
|
+
limits: {
|
|
339
|
+
fiveHour: parseCount(row[1]),
|
|
340
|
+
weekly: parseCount(row[2]),
|
|
341
|
+
monthly: parseCount(row[3])
|
|
342
|
+
},
|
|
343
|
+
usageLimit: group && group[0].row.length > 5 ? parsePrice(group[0].row[5]) : null,
|
|
344
|
+
endpoint: endpointRow ? extractLinks(endpointRow.join(" "))[0] || "" : "",
|
|
345
|
+
sdk: endpointRow ? endpointRow[3] || "" : "",
|
|
346
|
+
training: retentionRow ? retentionRow[1] || "" : "",
|
|
347
|
+
retention: retentionRow ? retentionRow[2] || "" : ""
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return { fetchTime: (/* @__PURE__ */ new Date()).toISOString(), models };
|
|
351
|
+
}
|
|
352
|
+
function comparePricing(oldData, newData) {
|
|
353
|
+
const changes = [];
|
|
354
|
+
if (!oldData || oldData.models.length === 0) {
|
|
355
|
+
return {
|
|
356
|
+
hasChanges: true,
|
|
357
|
+
changes: newData.models.map((m) => ({ type: "added", modelId: m.id, modelName: m.name, newValue: m })),
|
|
358
|
+
summary: { total: newData.models.length, added: newData.models.length, removed: 0, priceChanges: 0, limitChanges: 0 }
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const oldIndex = new Map(oldData.models.map((m) => [m.id, m]));
|
|
362
|
+
for (const newModel of newData.models) {
|
|
363
|
+
const oldModel = oldIndex.get(newModel.id);
|
|
364
|
+
if (!oldModel) {
|
|
365
|
+
changes.push({ type: "added", modelId: newModel.id, modelName: newModel.name, newValue: newModel });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const priceChanges = [];
|
|
369
|
+
const limitChanges = [];
|
|
370
|
+
for (const field of ["input", "output", "cachedRead", "cachedWrite"]) {
|
|
371
|
+
if (oldModel.pricing[field] !== newModel.pricing[field]) {
|
|
372
|
+
priceChanges.push({
|
|
373
|
+
field: `pricing.${field}`,
|
|
374
|
+
oldValue: oldModel.pricing[field],
|
|
375
|
+
newValue: newModel.pricing[field],
|
|
376
|
+
changeType: changeType(oldModel.pricing[field], newModel.pricing[field])
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (priceChanges.length > 0) {
|
|
381
|
+
changes.push({ type: "pricing", modelId: newModel.id, modelName: newModel.name, changes: priceChanges });
|
|
382
|
+
}
|
|
383
|
+
for (const field of ["fiveHour", "weekly", "monthly"]) {
|
|
384
|
+
if (oldModel.limits[field] !== newModel.limits[field]) {
|
|
385
|
+
limitChanges.push({
|
|
386
|
+
field: `limits.${field}`,
|
|
387
|
+
oldValue: oldModel.limits[field],
|
|
388
|
+
newValue: newModel.limits[field],
|
|
389
|
+
changeType: changeType(oldModel.limits[field], newModel.limits[field])
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (limitChanges.length > 0) {
|
|
394
|
+
changes.push({ type: "limits", modelId: newModel.id, modelName: newModel.name, changes: limitChanges });
|
|
395
|
+
}
|
|
396
|
+
if (oldModel.usageLimit !== newModel.usageLimit) {
|
|
397
|
+
changes.push({
|
|
398
|
+
type: "usageLimit",
|
|
399
|
+
modelId: newModel.id,
|
|
400
|
+
modelName: newModel.name,
|
|
401
|
+
changes: [{
|
|
402
|
+
field: "usageLimit",
|
|
403
|
+
oldValue: oldModel.usageLimit,
|
|
404
|
+
newValue: newModel.usageLimit,
|
|
405
|
+
changeType: changeType(oldModel.usageLimit, newModel.usageLimit)
|
|
406
|
+
}]
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
oldIndex.delete(newModel.id);
|
|
410
|
+
}
|
|
411
|
+
for (const [id, oldModel] of oldIndex) {
|
|
412
|
+
changes.push({ type: "removed", modelId: id, modelName: oldModel.name, oldValue: oldModel });
|
|
413
|
+
}
|
|
414
|
+
const summary = { total: changes.length, added: 0, removed: 0, priceChanges: 0, limitChanges: 0 };
|
|
415
|
+
for (const c of changes) {
|
|
416
|
+
if (c.type === "added") summary.added++;
|
|
417
|
+
else if (c.type === "removed") summary.removed++;
|
|
418
|
+
else if (c.type === "pricing") summary.priceChanges++;
|
|
419
|
+
else if (c.type === "limits" || c.type === "usageLimit") summary.limitChanges++;
|
|
420
|
+
}
|
|
421
|
+
return { hasChanges: changes.length > 0, changes, summary };
|
|
422
|
+
}
|
|
423
|
+
function changeType(oldVal, newVal) {
|
|
424
|
+
if (oldVal === null && newVal !== null) return "added";
|
|
425
|
+
if (oldVal !== null && newVal === null) return "removed";
|
|
426
|
+
if (newVal !== null && oldVal !== null && newVal > oldVal) return "increase";
|
|
427
|
+
if (newVal !== null && oldVal !== null && newVal < oldVal) return "decrease";
|
|
428
|
+
return "unchanged";
|
|
429
|
+
}
|
|
430
|
+
function loadPricingStore() {
|
|
431
|
+
try {
|
|
432
|
+
const data = JSON.parse(readFileSync(PRICING_FILE, "utf8"));
|
|
433
|
+
if (data && Array.isArray(data.current?.models)) return data;
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
return { lastFetch: "", current: { fetchTime: "", models: [] }, previous: null, history: [] };
|
|
437
|
+
}
|
|
438
|
+
function savePricingStore(store) {
|
|
439
|
+
try {
|
|
440
|
+
mkdirSync(dirname(PRICING_FILE), { recursive: true });
|
|
441
|
+
writeFileSync(PRICING_FILE, JSON.stringify(store, null, 2), "utf8");
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function updatePricingStore(newData) {
|
|
446
|
+
const store = loadPricingStore();
|
|
447
|
+
const compare = comparePricing(store.current.models.length > 0 ? store.current : null, newData);
|
|
448
|
+
const prev = store.current;
|
|
449
|
+
const newStore = {
|
|
450
|
+
lastFetch: newData.fetchTime,
|
|
451
|
+
current: newData,
|
|
452
|
+
previous: prev.models.length > 0 ? prev : null,
|
|
453
|
+
history: [
|
|
454
|
+
{ fetchTime: newData.fetchTime, changes: compare.changes, summary: compare.summary },
|
|
455
|
+
...store.history
|
|
456
|
+
].slice(0, 10)
|
|
457
|
+
};
|
|
458
|
+
savePricingStore(newStore);
|
|
459
|
+
return newStore;
|
|
460
|
+
}
|
|
461
|
+
|
|
116
462
|
// src/index.tsx
|
|
117
|
-
var VERSION = "1.
|
|
463
|
+
var VERSION = "1.3.1";
|
|
118
464
|
var CHECK_INTERVAL = Number(process?.env?.OPENCODE_GO_CHECK_INTERVAL ?? 6e4);
|
|
119
465
|
var WARN_THRESHOLD = Number(process?.env?.OPENCODE_GO_WARN_THRESHOLD ?? 0.8);
|
|
120
466
|
var CONFIG_DIR = process?.env?.OPENCODE_CONFIG_DIR || (process?.env?.USERPROFILE ? `${process.env.USERPROFILE}\\.config\\opencode` : "") || process?.env?.HOME + "/.config/opencode";
|
|
121
|
-
var CONFIG_FILE =
|
|
122
|
-
var COOKIE_ENC_FILE =
|
|
123
|
-
var KEY_FILE =
|
|
467
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "go-usage-config.json");
|
|
468
|
+
var COOKIE_ENC_FILE = join2(CONFIG_DIR, "go-auth-cookie.enc");
|
|
469
|
+
var KEY_FILE = join2(CONFIG_DIR, ".encryption-key");
|
|
124
470
|
var COOKIE_PLAIN_FILE = `${CONFIG_DIR}\\go-auth-cookie.txt`;
|
|
125
471
|
var KV_PREFIX = "go-usage";
|
|
126
472
|
function loadEncryptionKey() {
|
|
127
473
|
try {
|
|
128
|
-
const hex2 =
|
|
474
|
+
const hex2 = readFileSync2(KEY_FILE, "utf8").trim();
|
|
129
475
|
if (hex2) return Buffer.from(hex2, "hex");
|
|
130
476
|
} catch {
|
|
131
477
|
}
|
|
132
478
|
const key = randomBytes(32);
|
|
133
|
-
|
|
479
|
+
mkdirSync2(dirname2(KEY_FILE), {
|
|
134
480
|
recursive: true
|
|
135
481
|
});
|
|
136
|
-
|
|
482
|
+
writeFileSync2(KEY_FILE, key.toString("hex"), {
|
|
137
483
|
encoding: "utf8",
|
|
138
484
|
mode: 384
|
|
139
485
|
});
|
|
@@ -164,7 +510,7 @@ function decryptText(encJson) {
|
|
|
164
510
|
}
|
|
165
511
|
function readJsonFile(file) {
|
|
166
512
|
try {
|
|
167
|
-
return JSON.parse(
|
|
513
|
+
return JSON.parse(readFileSync2(file, "utf8"));
|
|
168
514
|
} catch {
|
|
169
515
|
return null;
|
|
170
516
|
}
|
|
@@ -175,16 +521,26 @@ function loadConfig() {
|
|
|
175
521
|
if (env.OPENCODE_GO_AUTH_COOKIE) {
|
|
176
522
|
return {
|
|
177
523
|
workspaceId: env.OPENCODE_GO_WORKSPACE_ID || fileCfg.workspace_id || "",
|
|
178
|
-
authCookie: env.OPENCODE_GO_AUTH_COOKIE
|
|
524
|
+
authCookie: env.OPENCODE_GO_AUTH_COOKIE,
|
|
525
|
+
usagePanel: fileCfg.ui?.usage_panel ?? true,
|
|
526
|
+
pricePanel: fileCfg.ui?.price_panel ?? true,
|
|
527
|
+
focusModels: fileCfg.focus_models ?? [],
|
|
528
|
+
refreshIntervalMs: (fileCfg.refresh_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3)) * 1e3,
|
|
529
|
+
pricingIntervalMs: (fileCfg.pricing_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3 * 30)) * 1e3
|
|
179
530
|
};
|
|
180
531
|
}
|
|
181
532
|
try {
|
|
182
|
-
if (
|
|
183
|
-
const cookie = decryptText(
|
|
533
|
+
if (existsSync2(COOKIE_ENC_FILE)) {
|
|
534
|
+
const cookie = decryptText(readFileSync2(COOKIE_ENC_FILE, "utf8"));
|
|
184
535
|
if (cookie) {
|
|
185
536
|
return {
|
|
186
537
|
workspaceId: env.OPENCODE_GO_WORKSPACE_ID || fileCfg.workspace_id || "",
|
|
187
|
-
authCookie: cookie
|
|
538
|
+
authCookie: cookie,
|
|
539
|
+
usagePanel: fileCfg.ui?.usage_panel ?? true,
|
|
540
|
+
pricePanel: fileCfg.ui?.price_panel ?? true,
|
|
541
|
+
focusModels: fileCfg.focus_models ?? [],
|
|
542
|
+
refreshIntervalMs: (fileCfg.refresh_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3)) * 1e3,
|
|
543
|
+
pricingIntervalMs: (fileCfg.pricing_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3 * 30)) * 1e3
|
|
188
544
|
};
|
|
189
545
|
}
|
|
190
546
|
}
|
|
@@ -192,38 +548,48 @@ function loadConfig() {
|
|
|
192
548
|
}
|
|
193
549
|
const legacyCookie = fileCfg.auth_cookie || fileCfg.cookie || (() => {
|
|
194
550
|
try {
|
|
195
|
-
return
|
|
551
|
+
return readFileSync2(COOKIE_PLAIN_FILE, "utf8").trim();
|
|
196
552
|
} catch {
|
|
197
553
|
return "";
|
|
198
554
|
}
|
|
199
555
|
})();
|
|
200
556
|
if (legacyCookie) {
|
|
201
557
|
const enc = encryptText(legacyCookie);
|
|
202
|
-
|
|
558
|
+
mkdirSync2(CONFIG_DIR, {
|
|
203
559
|
recursive: true
|
|
204
560
|
});
|
|
205
|
-
|
|
561
|
+
writeFileSync2(COOKIE_ENC_FILE, enc, "utf8");
|
|
206
562
|
const cleanCfg = {
|
|
207
563
|
...fileCfg
|
|
208
564
|
};
|
|
209
565
|
delete cleanCfg.cookie;
|
|
210
566
|
delete cleanCfg.auth_cookie;
|
|
211
567
|
try {
|
|
212
|
-
|
|
568
|
+
writeFileSync2(CONFIG_FILE, JSON.stringify(cleanCfg, null, 2), "utf8");
|
|
213
569
|
} catch {
|
|
214
570
|
}
|
|
215
571
|
try {
|
|
216
|
-
if (
|
|
572
|
+
if (existsSync2(COOKIE_PLAIN_FILE)) renameSync(COOKIE_PLAIN_FILE, COOKIE_PLAIN_FILE + ".bak");
|
|
217
573
|
} catch {
|
|
218
574
|
}
|
|
219
575
|
return {
|
|
220
576
|
workspaceId: env.OPENCODE_GO_WORKSPACE_ID || fileCfg.workspace_id || "",
|
|
221
|
-
authCookie: legacyCookie
|
|
577
|
+
authCookie: legacyCookie,
|
|
578
|
+
usagePanel: fileCfg.ui?.usage_panel ?? true,
|
|
579
|
+
pricePanel: fileCfg.ui?.price_panel ?? true,
|
|
580
|
+
focusModels: fileCfg.focus_models ?? [],
|
|
581
|
+
refreshIntervalMs: (fileCfg.refresh_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3)) * 1e3,
|
|
582
|
+
pricingIntervalMs: (fileCfg.pricing_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3 * 30)) * 1e3
|
|
222
583
|
};
|
|
223
584
|
}
|
|
224
585
|
return {
|
|
225
586
|
workspaceId: env.OPENCODE_GO_WORKSPACE_ID || fileCfg.workspace_id || "",
|
|
226
|
-
authCookie: ""
|
|
587
|
+
authCookie: "",
|
|
588
|
+
usagePanel: fileCfg.ui?.usage_panel ?? true,
|
|
589
|
+
pricePanel: fileCfg.ui?.price_panel ?? true,
|
|
590
|
+
focusModels: fileCfg.focus_models ?? [],
|
|
591
|
+
refreshIntervalMs: (fileCfg.refresh_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3)) * 1e3,
|
|
592
|
+
pricingIntervalMs: (fileCfg.pricing_interval_sec ?? Math.round(CHECK_INTERVAL / 1e3 * 30)) * 1e3
|
|
227
593
|
};
|
|
228
594
|
}
|
|
229
595
|
function saveConfig(patch) {
|
|
@@ -231,34 +597,44 @@ function saveConfig(patch) {
|
|
|
231
597
|
if (patch.workspace_id !== void 0) fileCfg.workspace_id = patch.workspace_id;
|
|
232
598
|
if (patch.cookie !== void 0) {
|
|
233
599
|
if (patch.cookie) {
|
|
234
|
-
|
|
600
|
+
mkdirSync2(CONFIG_DIR, {
|
|
235
601
|
recursive: true
|
|
236
602
|
});
|
|
237
|
-
|
|
603
|
+
writeFileSync2(COOKIE_ENC_FILE, encryptText(patch.cookie), "utf8");
|
|
238
604
|
delete fileCfg.cookie;
|
|
239
605
|
delete fileCfg.auth_cookie;
|
|
240
606
|
try {
|
|
241
|
-
if (
|
|
607
|
+
if (existsSync2(COOKIE_PLAIN_FILE)) renameSync(COOKIE_PLAIN_FILE, COOKIE_PLAIN_FILE + ".bak");
|
|
242
608
|
} catch {
|
|
243
609
|
}
|
|
244
610
|
} else {
|
|
245
611
|
try {
|
|
246
|
-
if (
|
|
612
|
+
if (existsSync2(COOKIE_ENC_FILE)) renameSync(COOKIE_ENC_FILE, COOKIE_ENC_FILE + ".bak");
|
|
247
613
|
} catch {
|
|
248
614
|
}
|
|
249
615
|
}
|
|
250
616
|
}
|
|
617
|
+
if (patch.ui !== void 0) {
|
|
618
|
+
fileCfg.ui = {
|
|
619
|
+
...fileCfg.ui ?? {},
|
|
620
|
+
...patch.ui
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (patch.focus_models !== void 0) fileCfg.focus_models = patch.focus_models;
|
|
624
|
+
if (patch.refresh_interval_sec !== void 0) fileCfg.refresh_interval_sec = patch.refresh_interval_sec;
|
|
625
|
+
if (patch.pricing_interval_sec !== void 0) fileCfg.pricing_interval_sec = patch.pricing_interval_sec;
|
|
251
626
|
try {
|
|
252
|
-
|
|
627
|
+
mkdirSync2(dirname2(CONFIG_FILE), {
|
|
253
628
|
recursive: true
|
|
254
629
|
});
|
|
255
|
-
|
|
630
|
+
writeFileSync2(CONFIG_FILE, JSON.stringify(fileCfg, null, 2), "utf8");
|
|
256
631
|
} catch {
|
|
257
632
|
}
|
|
258
633
|
}
|
|
259
634
|
var [configTick, setConfigTick] = createSignal(0);
|
|
260
635
|
var [langCode, setLangCode] = createSignal(detectLang());
|
|
261
636
|
var t = createT(() => langCode());
|
|
637
|
+
var [sharedPricing, setSharedPricing] = createSignal(null);
|
|
262
638
|
function kvTryGet(api, key) {
|
|
263
639
|
try {
|
|
264
640
|
return api.kv.get(key);
|
|
@@ -272,7 +648,8 @@ function kvTrySet(api, key, val) {
|
|
|
272
648
|
} catch {
|
|
273
649
|
}
|
|
274
650
|
}
|
|
275
|
-
|
|
651
|
+
var REQUEST_TIMEOUT = 1e4;
|
|
652
|
+
async function fetchUsage(workspaceId, authCookie, signal) {
|
|
276
653
|
if (!workspaceId || !authCookie) return {
|
|
277
654
|
error: "not_configured"
|
|
278
655
|
};
|
|
@@ -299,7 +676,8 @@ async function fetchUsage(workspaceId, authCookie) {
|
|
|
299
676
|
},
|
|
300
677
|
f: 31,
|
|
301
678
|
m: []
|
|
302
|
-
})
|
|
679
|
+
}),
|
|
680
|
+
signal
|
|
303
681
|
});
|
|
304
682
|
if (!res.ok) {
|
|
305
683
|
if (res.status === 302) return {
|
|
@@ -314,7 +692,7 @@ async function fetchUsage(workspaceId, authCookie) {
|
|
|
314
692
|
};
|
|
315
693
|
}
|
|
316
694
|
const extract = (key) => {
|
|
317
|
-
const m = text.match(new RegExp(`${key}:[^}]*?\\{status:"
|
|
695
|
+
const m = text.match(new RegExp(`${key}:[^}]*?\\{status:"[^"]*",resetInSec:(\\d+),usagePercent:(\\d+)\\}`));
|
|
318
696
|
if (!m) return null;
|
|
319
697
|
return {
|
|
320
698
|
resetInSec: Number(m[1]),
|
|
@@ -389,36 +767,70 @@ function GoUsagePanel(props) {
|
|
|
389
767
|
const [configured, setConfigured] = createSignal(true);
|
|
390
768
|
const [lastUpdated, setLastUpdated] = createSignal("");
|
|
391
769
|
const [panelWidth, setPanelWidth] = createSignal(24);
|
|
770
|
+
const [cfg, setCfg] = createSignal(loadConfig());
|
|
771
|
+
createEffect(() => {
|
|
772
|
+
void configTick();
|
|
773
|
+
setCfg(loadConfig());
|
|
774
|
+
});
|
|
392
775
|
let boxEl;
|
|
776
|
+
let abortController = null;
|
|
777
|
+
let timeoutId = null;
|
|
778
|
+
let isRefreshing = false;
|
|
779
|
+
let resizeTimeout2 = null;
|
|
780
|
+
function cleanupPendingRequest() {
|
|
781
|
+
if (timeoutId) {
|
|
782
|
+
clearTimeout(timeoutId);
|
|
783
|
+
timeoutId = null;
|
|
784
|
+
}
|
|
785
|
+
if (abortController) {
|
|
786
|
+
abortController.abort();
|
|
787
|
+
abortController = null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
393
790
|
async function refresh() {
|
|
394
|
-
|
|
395
|
-
|
|
791
|
+
if (isRefreshing) return;
|
|
792
|
+
const cfg2 = loadConfig();
|
|
793
|
+
if (!cfg2.workspaceId || !cfg2.authCookie) {
|
|
396
794
|
setConfigured(false);
|
|
397
795
|
setUsage(null);
|
|
398
796
|
setError("");
|
|
399
797
|
return;
|
|
400
798
|
}
|
|
401
799
|
setConfigured(true);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
800
|
+
cleanupPendingRequest();
|
|
801
|
+
abortController = new AbortController();
|
|
802
|
+
const signal = abortController.signal;
|
|
803
|
+
timeoutId = setTimeout(() => abortController?.abort(), REQUEST_TIMEOUT);
|
|
804
|
+
isRefreshing = true;
|
|
805
|
+
try {
|
|
806
|
+
const u = await fetchUsage(cfg2.workspaceId, cfg2.authCookie, signal);
|
|
807
|
+
if (signal.aborted) return;
|
|
808
|
+
if (u === null) {
|
|
809
|
+
setError(t("queryFailed"));
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (u.error === "cookie_expired") {
|
|
813
|
+
setError(t("cookieExpired"));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (u.error === "not_configured") {
|
|
817
|
+
setConfigured(false);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
setUsage(u);
|
|
821
|
+
setLastUpdated((/* @__PURE__ */ new Date()).toLocaleTimeString());
|
|
822
|
+
setError("");
|
|
823
|
+
} finally {
|
|
824
|
+
if (timeoutId) {
|
|
825
|
+
clearTimeout(timeoutId);
|
|
826
|
+
timeoutId = null;
|
|
827
|
+
}
|
|
828
|
+
isRefreshing = false;
|
|
414
829
|
}
|
|
415
|
-
setUsage(u);
|
|
416
|
-
setLastUpdated((/* @__PURE__ */ new Date()).toLocaleTimeString());
|
|
417
|
-
setError("");
|
|
418
830
|
}
|
|
419
831
|
onMount(() => {
|
|
420
832
|
refresh();
|
|
421
|
-
const timer = setInterval(refresh,
|
|
833
|
+
const timer = setInterval(refresh, loadConfig().refreshIntervalMs);
|
|
422
834
|
const stopWatch = createEffect(() => {
|
|
423
835
|
void configTick();
|
|
424
836
|
refresh();
|
|
@@ -426,6 +838,11 @@ function GoUsagePanel(props) {
|
|
|
426
838
|
onCleanup(() => {
|
|
427
839
|
clearInterval(timer);
|
|
428
840
|
stopWatch();
|
|
841
|
+
cleanupPendingRequest();
|
|
842
|
+
if (resizeTimeout2) {
|
|
843
|
+
clearTimeout(resizeTimeout2);
|
|
844
|
+
resizeTimeout2 = null;
|
|
845
|
+
}
|
|
429
846
|
});
|
|
430
847
|
});
|
|
431
848
|
const [pal, setPal] = createSignal({
|
|
@@ -505,8 +922,11 @@ function GoUsagePanel(props) {
|
|
|
505
922
|
_$setProp(_el$1, "flexDirection", "column");
|
|
506
923
|
_$setProp(_el$1, "gap", 0);
|
|
507
924
|
_$setProp(_el$1, "onSizeChange", () => {
|
|
508
|
-
|
|
509
|
-
|
|
925
|
+
if (resizeTimeout2) clearTimeout(resizeTimeout2);
|
|
926
|
+
resizeTimeout2 = setTimeout(() => {
|
|
927
|
+
const w = boxEl ? Math.max(20, boxEl.width ?? 24) : 24;
|
|
928
|
+
setPanelWidth(w);
|
|
929
|
+
}, 100);
|
|
510
930
|
});
|
|
511
931
|
_$insertNode(_el$10, _el$11);
|
|
512
932
|
_$insertNode(_el$10, _el$12);
|
|
@@ -647,6 +1067,259 @@ function GoUsagePanel(props) {
|
|
|
647
1067
|
return _el$1;
|
|
648
1068
|
})();
|
|
649
1069
|
}
|
|
1070
|
+
function PricePanel(props) {
|
|
1071
|
+
const [open, setOpen] = createSignal(true);
|
|
1072
|
+
const [pricingStore, setPricingStore] = createSignal(null);
|
|
1073
|
+
const [priceError, setPriceError] = createSignal("");
|
|
1074
|
+
const [priceLoading, setPriceLoading] = createSignal(false);
|
|
1075
|
+
const [panelWidth, setPanelWidth] = createSignal(24);
|
|
1076
|
+
const [cfg, setCfg] = createSignal(loadConfig());
|
|
1077
|
+
createEffect(() => {
|
|
1078
|
+
void configTick();
|
|
1079
|
+
setCfg(loadConfig());
|
|
1080
|
+
});
|
|
1081
|
+
let boxEl;
|
|
1082
|
+
let priceController = null;
|
|
1083
|
+
createEffect(() => {
|
|
1084
|
+
if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
|
|
1085
|
+
setPanelWidth(Math.max(20, boxEl.width));
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
async function refreshPricing() {
|
|
1089
|
+
if (priceLoading()) return;
|
|
1090
|
+
setPriceLoading(true);
|
|
1091
|
+
setPriceError("");
|
|
1092
|
+
priceController?.abort();
|
|
1093
|
+
priceController = new AbortController();
|
|
1094
|
+
try {
|
|
1095
|
+
const data = await fetchPricing();
|
|
1096
|
+
const store = updatePricingStore(data);
|
|
1097
|
+
setSharedPricing(data);
|
|
1098
|
+
setPricingStore(store);
|
|
1099
|
+
const prev = store.previous;
|
|
1100
|
+
if (prev && prev.fetchTime !== data.fetchTime) {
|
|
1101
|
+
const diff = comparePricing(prev, data);
|
|
1102
|
+
if (diff.hasChanges) {
|
|
1103
|
+
props.api.toast({
|
|
1104
|
+
variant: "info",
|
|
1105
|
+
title: t("priceChanged"),
|
|
1106
|
+
message: t("priceChangesSummary", {
|
|
1107
|
+
t: diff.summary.total,
|
|
1108
|
+
a: diff.summary.added,
|
|
1109
|
+
r: diff.summary.removed,
|
|
1110
|
+
p: diff.summary.priceChanges,
|
|
1111
|
+
l: diff.summary.limitChanges
|
|
1112
|
+
})
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
} catch {
|
|
1117
|
+
setPriceError(t("priceError"));
|
|
1118
|
+
} finally {
|
|
1119
|
+
setPriceLoading(false);
|
|
1120
|
+
priceController = null;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function formatCount(n) {
|
|
1124
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|
|
1125
|
+
return String(n);
|
|
1126
|
+
}
|
|
1127
|
+
function visualPadEnd(s, w) {
|
|
1128
|
+
const cur = visualWidth(s);
|
|
1129
|
+
if (cur >= w) return s;
|
|
1130
|
+
return s + " ".repeat(w - cur);
|
|
1131
|
+
}
|
|
1132
|
+
function abbreviateName(name, maxLen) {
|
|
1133
|
+
if (visualWidth(name) <= maxLen) return name;
|
|
1134
|
+
let out = "";
|
|
1135
|
+
let w = 0;
|
|
1136
|
+
for (const c of name) {
|
|
1137
|
+
const cw = visualWidth(c);
|
|
1138
|
+
if (w + cw > maxLen - 1) break;
|
|
1139
|
+
out += c;
|
|
1140
|
+
w += cw;
|
|
1141
|
+
}
|
|
1142
|
+
return out + "\u2026";
|
|
1143
|
+
}
|
|
1144
|
+
function justify(label, value) {
|
|
1145
|
+
const outer = boxEl && typeof boxEl.width === "number" && boxEl.width > 0 ? boxEl.width : panelWidth();
|
|
1146
|
+
const gauge = Math.max(10, outer - 4);
|
|
1147
|
+
const used = visualWidth(label) + visualWidth(value);
|
|
1148
|
+
const gap = Math.max(1, gauge - used);
|
|
1149
|
+
return label + " ".repeat(gap) + value;
|
|
1150
|
+
}
|
|
1151
|
+
function renderLimitRow(m) {
|
|
1152
|
+
const name = abbreviateName(m.name, 18);
|
|
1153
|
+
const req = `${formatCount(m.limits.fiveHour)}/${formatCount(m.limits.weekly)}/${formatCount(m.limits.monthly)}`;
|
|
1154
|
+
return (() => {
|
|
1155
|
+
var _el$30 = _$createElement("text"), _el$31 = _$createElement("span");
|
|
1156
|
+
_$insertNode(_el$30, _el$31);
|
|
1157
|
+
_$insert(_el$31, () => justify(name, req));
|
|
1158
|
+
_$effect((_$p) => _$setProp(_el$31, "style", {
|
|
1159
|
+
fg: colors().text
|
|
1160
|
+
}, _$p));
|
|
1161
|
+
return _el$30;
|
|
1162
|
+
})();
|
|
1163
|
+
}
|
|
1164
|
+
const [pal, setPal] = createSignal({
|
|
1165
|
+
...FALLBACK
|
|
1166
|
+
});
|
|
1167
|
+
createEffect(() => {
|
|
1168
|
+
const th = props.theme;
|
|
1169
|
+
const p = {
|
|
1170
|
+
...FALLBACK
|
|
1171
|
+
};
|
|
1172
|
+
for (const k of Object.keys(FALLBACK)) {
|
|
1173
|
+
const h = hex(th?.[k]);
|
|
1174
|
+
if (h) p[k] = h;
|
|
1175
|
+
}
|
|
1176
|
+
setPal(p);
|
|
1177
|
+
});
|
|
1178
|
+
const colors = () => pal();
|
|
1179
|
+
const sep = () => {
|
|
1180
|
+
const outer = boxEl && typeof boxEl.width === "number" && boxEl.width > 0 ? boxEl.width : panelWidth();
|
|
1181
|
+
return "\u2500".repeat(Math.max(1, outer - 4));
|
|
1182
|
+
};
|
|
1183
|
+
setTimeout(() => {
|
|
1184
|
+
const store = loadPricingStore();
|
|
1185
|
+
if (store.current.models.length > 0) {
|
|
1186
|
+
setSharedPricing(store.current);
|
|
1187
|
+
setPricingStore(store);
|
|
1188
|
+
}
|
|
1189
|
+
refreshPricing();
|
|
1190
|
+
setInterval(() => refreshPricing(), loadConfig().pricingIntervalMs);
|
|
1191
|
+
}, 2e3);
|
|
1192
|
+
return (() => {
|
|
1193
|
+
var _el$32 = _$createElement("box"), _el$33 = _$createElement("text"), _el$34 = _$createElement("span"), _el$35 = _$createElement("span"), _el$36 = _$createElement("b"), _el$40 = _$createElement("span");
|
|
1194
|
+
_$insertNode(_el$32, _el$33);
|
|
1195
|
+
var _ref$2 = boxEl;
|
|
1196
|
+
typeof _ref$2 === "function" ? _$use(_ref$2, _el$32) : boxEl = _el$32;
|
|
1197
|
+
_$setProp(_el$32, "border", true);
|
|
1198
|
+
_$setProp(_el$32, "paddingLeft", 1);
|
|
1199
|
+
_$setProp(_el$32, "paddingRight", 1);
|
|
1200
|
+
_$setProp(_el$32, "flexDirection", "column");
|
|
1201
|
+
_$setProp(_el$32, "gap", 0);
|
|
1202
|
+
_$setProp(_el$32, "onSizeChange", () => {
|
|
1203
|
+
if (resizeTimeout) clearTimeout(resizeTimeout);
|
|
1204
|
+
resizeTimeout = setTimeout(() => {
|
|
1205
|
+
const w = boxEl ? Math.max(20, boxEl.width ?? 24) : 24;
|
|
1206
|
+
setPanelWidth(w);
|
|
1207
|
+
}, 100);
|
|
1208
|
+
});
|
|
1209
|
+
_$insertNode(_el$33, _el$34);
|
|
1210
|
+
_$insertNode(_el$33, _el$35);
|
|
1211
|
+
_$insertNode(_el$33, _el$40);
|
|
1212
|
+
_$setProp(_el$33, "onMouseUp", () => setOpen((o) => !o));
|
|
1213
|
+
_$insert(_el$34, () => open() ? "\u25BC " : "\u25B6 ");
|
|
1214
|
+
_$insertNode(_el$35, _el$36);
|
|
1215
|
+
_$insert(_el$36, () => t("priceTitle"));
|
|
1216
|
+
_$insert(_el$33, _$createComponent(Show, {
|
|
1217
|
+
get when() {
|
|
1218
|
+
return sharedPricing();
|
|
1219
|
+
},
|
|
1220
|
+
get children() {
|
|
1221
|
+
var _el$37 = _$createElement("span"), _el$38 = _$createTextNode(` (`), _el$39 = _$createTextNode(`)`);
|
|
1222
|
+
_$insertNode(_el$37, _el$38);
|
|
1223
|
+
_$insertNode(_el$37, _el$39);
|
|
1224
|
+
_$insert(_el$37, () => sharedPricing().models.length, _el$39);
|
|
1225
|
+
_$effect((_$p) => _$setProp(_el$37, "style", {
|
|
1226
|
+
fg: colors().muted
|
|
1227
|
+
}, _$p));
|
|
1228
|
+
return _el$37;
|
|
1229
|
+
}
|
|
1230
|
+
}), _el$40);
|
|
1231
|
+
_$insert(_el$40, () => sep().slice(visualWidth((open() ? "\u25BC " : "\u25B6 ") + t("priceTitle") + (sharedPricing() ? ` (${sharedPricing().models.length})` : ""))));
|
|
1232
|
+
_$insert(_el$32, _$createComponent(Show, {
|
|
1233
|
+
get when() {
|
|
1234
|
+
return open();
|
|
1235
|
+
},
|
|
1236
|
+
get children() {
|
|
1237
|
+
return [(() => {
|
|
1238
|
+
var _el$41 = _$createElement("text");
|
|
1239
|
+
_$insert(_el$41, () => justify(t("priceModelCol"), t("priceReqs")));
|
|
1240
|
+
_$effect((_$p) => _$setProp(_el$41, "fg", colors().muted, _$p));
|
|
1241
|
+
return _el$41;
|
|
1242
|
+
})(), (() => {
|
|
1243
|
+
var _el$42 = _$createElement("text");
|
|
1244
|
+
_$insert(_el$42, sep);
|
|
1245
|
+
_$effect((_$p) => _$setProp(_el$42, "fg", colors().muted, _$p));
|
|
1246
|
+
return _el$42;
|
|
1247
|
+
})(), _$createComponent(Show, {
|
|
1248
|
+
get when() {
|
|
1249
|
+
return priceLoading() || !sharedPricing();
|
|
1250
|
+
},
|
|
1251
|
+
get fallback() {
|
|
1252
|
+
return _$createComponent(Show, {
|
|
1253
|
+
get when() {
|
|
1254
|
+
return sharedPricing();
|
|
1255
|
+
},
|
|
1256
|
+
get fallback() {
|
|
1257
|
+
return (() => {
|
|
1258
|
+
var _el$46 = _$createElement("text");
|
|
1259
|
+
_$insert(_el$46, () => t("priceLoading"));
|
|
1260
|
+
_$effect((_$p) => _$setProp(_el$46, "fg", colors().muted, _$p));
|
|
1261
|
+
return _el$46;
|
|
1262
|
+
})();
|
|
1263
|
+
},
|
|
1264
|
+
get children() {
|
|
1265
|
+
return [_$memo(() => sharedPricing().models.filter((m) => {
|
|
1266
|
+
return cfg().focusModels.length === 0 || cfg().focusModels.includes(m.id);
|
|
1267
|
+
}).map((m) => renderLimitRow(m))), (() => {
|
|
1268
|
+
var _el$44 = _$createElement("text");
|
|
1269
|
+
_$insert(_el$44, () => justify(t("priceUpdated"), new Date(sharedPricing().fetchTime).toLocaleTimeString()));
|
|
1270
|
+
_$effect((_$p) => _$setProp(_el$44, "fg", colors().muted, _$p));
|
|
1271
|
+
return _el$44;
|
|
1272
|
+
})(), _$createComponent(Show, {
|
|
1273
|
+
get when() {
|
|
1274
|
+
return _$memo(() => !!pricingStore())() && pricingStore().history.length > 0;
|
|
1275
|
+
},
|
|
1276
|
+
get children() {
|
|
1277
|
+
var _el$45 = _$createElement("text");
|
|
1278
|
+
_$insert(_el$45, () => t("priceChangesSummary", {
|
|
1279
|
+
t: pricingStore().history[0].summary.total,
|
|
1280
|
+
a: pricingStore().history[0].summary.added,
|
|
1281
|
+
r: pricingStore().history[0].summary.removed,
|
|
1282
|
+
p: pricingStore().history[0].summary.priceChanges,
|
|
1283
|
+
l: pricingStore().history[0].summary.limitChanges
|
|
1284
|
+
}));
|
|
1285
|
+
_$effect((_$p) => _$setProp(_el$45, "fg", colors().warning, _$p));
|
|
1286
|
+
return _el$45;
|
|
1287
|
+
}
|
|
1288
|
+
})];
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
},
|
|
1292
|
+
get children() {
|
|
1293
|
+
var _el$43 = _$createElement("text");
|
|
1294
|
+
_$insert(_el$43, () => t("priceLoading"));
|
|
1295
|
+
_$effect((_$p) => _$setProp(_el$43, "fg", colors().muted, _$p));
|
|
1296
|
+
return _el$43;
|
|
1297
|
+
}
|
|
1298
|
+
})];
|
|
1299
|
+
}
|
|
1300
|
+
}), null);
|
|
1301
|
+
_$effect((_p$) => {
|
|
1302
|
+
var _v$1 = colors().border, _v$10 = {
|
|
1303
|
+
fg: colors().muted
|
|
1304
|
+
}, _v$11 = {
|
|
1305
|
+
fg: colors().primary
|
|
1306
|
+
}, _v$12 = {
|
|
1307
|
+
fg: colors().muted
|
|
1308
|
+
};
|
|
1309
|
+
_v$1 !== _p$.e && (_p$.e = _$setProp(_el$32, "borderColor", _v$1, _p$.e));
|
|
1310
|
+
_v$10 !== _p$.t && (_p$.t = _$setProp(_el$34, "style", _v$10, _p$.t));
|
|
1311
|
+
_v$11 !== _p$.a && (_p$.a = _$setProp(_el$35, "style", _v$11, _p$.a));
|
|
1312
|
+
_v$12 !== _p$.o && (_p$.o = _$setProp(_el$40, "style", _v$12, _p$.o));
|
|
1313
|
+
return _p$;
|
|
1314
|
+
}, {
|
|
1315
|
+
e: void 0,
|
|
1316
|
+
t: void 0,
|
|
1317
|
+
a: void 0,
|
|
1318
|
+
o: void 0
|
|
1319
|
+
});
|
|
1320
|
+
return _el$32;
|
|
1321
|
+
})();
|
|
1322
|
+
}
|
|
650
1323
|
function createSidebarSlot(api) {
|
|
651
1324
|
return {
|
|
652
1325
|
order: 60,
|
|
@@ -662,15 +1335,30 @@ function createSidebarSlot(api) {
|
|
|
662
1335
|
}
|
|
663
1336
|
};
|
|
664
1337
|
}
|
|
1338
|
+
function createPricingSlot(api) {
|
|
1339
|
+
return {
|
|
1340
|
+
order: 61,
|
|
1341
|
+
slots: {
|
|
1342
|
+
sidebar_content(ctx, _input) {
|
|
1343
|
+
return _$createComponent(PricePanel, {
|
|
1344
|
+
get theme() {
|
|
1345
|
+
return ctx.theme.current;
|
|
1346
|
+
},
|
|
1347
|
+
api
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
665
1353
|
function runConfigDialog(api, dialog) {
|
|
666
1354
|
dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
|
|
667
1355
|
get title() {
|
|
668
1356
|
return t("wsPromptTitle");
|
|
669
1357
|
},
|
|
670
1358
|
description: () => (() => {
|
|
671
|
-
var _el$
|
|
672
|
-
_$insert(_el$
|
|
673
|
-
return _el$
|
|
1359
|
+
var _el$47 = _$createElement("text");
|
|
1360
|
+
_$insert(_el$47, () => t("wsPromptDesc"));
|
|
1361
|
+
return _el$47;
|
|
674
1362
|
})(),
|
|
675
1363
|
get placeholder() {
|
|
676
1364
|
return t("wsPlaceholder");
|
|
@@ -689,9 +1377,9 @@ function runConfigDialog(api, dialog) {
|
|
|
689
1377
|
return t("cookiePromptTitle");
|
|
690
1378
|
},
|
|
691
1379
|
description: () => (() => {
|
|
692
|
-
var _el$
|
|
693
|
-
_$insert(_el$
|
|
694
|
-
return _el$
|
|
1380
|
+
var _el$48 = _$createElement("text");
|
|
1381
|
+
_$insert(_el$48, () => t("cookiePromptDesc"));
|
|
1382
|
+
return _el$48;
|
|
695
1383
|
})(),
|
|
696
1384
|
get placeholder() {
|
|
697
1385
|
return t("cookiePlaceholder");
|
|
@@ -746,8 +1434,161 @@ function runLangDialog(api, dialog) {
|
|
|
746
1434
|
onCancel: () => dialog?.clear()
|
|
747
1435
|
}));
|
|
748
1436
|
}
|
|
1437
|
+
function runSettingsDialog(api, dialog) {
|
|
1438
|
+
let lastSelected;
|
|
1439
|
+
const renderSettingsDialog = (dialog2) => {
|
|
1440
|
+
const cfg = loadConfig();
|
|
1441
|
+
const options = [{
|
|
1442
|
+
title: `${t("settingsFocus")} (${cfg.focusModels.length} \u4E2A)`,
|
|
1443
|
+
value: "focus-models"
|
|
1444
|
+
}, {
|
|
1445
|
+
title: `${t("settingsRefreshUsage")} (${Math.round(cfg.refreshIntervalMs / 1e3)}s)`,
|
|
1446
|
+
value: "refresh-usage"
|
|
1447
|
+
}, {
|
|
1448
|
+
title: `${t("settingsRefreshPricing")} (${Math.round(cfg.pricingIntervalMs / 1e3)}s)`,
|
|
1449
|
+
value: "refresh-pricing"
|
|
1450
|
+
}];
|
|
1451
|
+
dialog2?.replace(() => _$createComponent(api.ui.DialogSelect, {
|
|
1452
|
+
get title() {
|
|
1453
|
+
return t("settingsTitle");
|
|
1454
|
+
},
|
|
1455
|
+
options,
|
|
1456
|
+
onSelect: (opt) => {
|
|
1457
|
+
const value = opt.value;
|
|
1458
|
+
if (value === "focus-models") {
|
|
1459
|
+
const renderFocusDialog = (dialog3) => {
|
|
1460
|
+
const cur = loadConfig().focusModels;
|
|
1461
|
+
const allModels = sharedPricing()?.models ?? [];
|
|
1462
|
+
const modelOptions = allModels.map((m) => ({
|
|
1463
|
+
title: `${cur.includes(m.id) ? "\u2611" : "\u2610"} ${m.name} (${m.pricing.input ?? "-"}/${m.pricing.output ?? "-"})`,
|
|
1464
|
+
value: m.id
|
|
1465
|
+
}));
|
|
1466
|
+
const focusOptions = [...modelOptions, {
|
|
1467
|
+
title: `${t("focusAll")} (${cur.length} \u4E2A\u5DF2\u9009)`,
|
|
1468
|
+
value: "select-all"
|
|
1469
|
+
}, {
|
|
1470
|
+
title: t("focusNone"),
|
|
1471
|
+
value: "clear-all"
|
|
1472
|
+
}, {
|
|
1473
|
+
title: `${t("focusDone")} (${t("focusSelected", {
|
|
1474
|
+
n: cur.length
|
|
1475
|
+
})})`,
|
|
1476
|
+
value: "done"
|
|
1477
|
+
}];
|
|
1478
|
+
dialog3?.replace(() => _$createComponent(api.ui.DialogSelect, {
|
|
1479
|
+
get title() {
|
|
1480
|
+
return t("settingsFocus");
|
|
1481
|
+
},
|
|
1482
|
+
options: focusOptions,
|
|
1483
|
+
current: lastSelected,
|
|
1484
|
+
onSelect: (opt2) => {
|
|
1485
|
+
const v2 = opt2.value;
|
|
1486
|
+
if (v2 === "done") {
|
|
1487
|
+
dialog3?.clear();
|
|
1488
|
+
renderSettingsDialog(dialog2);
|
|
1489
|
+
} else if (v2 === "select-all") {
|
|
1490
|
+
const all = allModels.map((m) => m.id);
|
|
1491
|
+
saveConfig({
|
|
1492
|
+
focus_models: all
|
|
1493
|
+
});
|
|
1494
|
+
setConfigTick((v) => v + 1);
|
|
1495
|
+
api.ui.toast({
|
|
1496
|
+
variant: "success",
|
|
1497
|
+
message: t("settingsSaved")
|
|
1498
|
+
});
|
|
1499
|
+
renderFocusDialog(dialog3);
|
|
1500
|
+
} else if (v2 === "clear-all") {
|
|
1501
|
+
saveConfig({
|
|
1502
|
+
focus_models: []
|
|
1503
|
+
});
|
|
1504
|
+
setConfigTick((v) => v + 1);
|
|
1505
|
+
api.ui.toast({
|
|
1506
|
+
variant: "success",
|
|
1507
|
+
message: t("settingsSaved")
|
|
1508
|
+
});
|
|
1509
|
+
renderFocusDialog(dialog3);
|
|
1510
|
+
} else {
|
|
1511
|
+
const next = cur.includes(v2) ? cur.filter((id) => id !== v2) : [...cur, v2];
|
|
1512
|
+
saveConfig({
|
|
1513
|
+
focus_models: next
|
|
1514
|
+
});
|
|
1515
|
+
setConfigTick((v) => v + 1);
|
|
1516
|
+
lastSelected = v2;
|
|
1517
|
+
renderFocusDialog(dialog3);
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
onCancel: () => dialog3?.clear()
|
|
1521
|
+
}));
|
|
1522
|
+
};
|
|
1523
|
+
renderFocusDialog(dialog2);
|
|
1524
|
+
} else if (value === "refresh-usage") {
|
|
1525
|
+
dialog2?.replace(() => _$createComponent(api.ui.DialogPrompt, {
|
|
1526
|
+
get title() {
|
|
1527
|
+
return t("settingsRefreshUsage");
|
|
1528
|
+
},
|
|
1529
|
+
description: () => (() => {
|
|
1530
|
+
var _el$49 = _$createElement("text");
|
|
1531
|
+
_$insertNode(_el$49, _$createTextNode(`\u8F93\u5165\u79D2\u6570\uFF08\u9ED8\u8BA4 60\uFF09`));
|
|
1532
|
+
return _el$49;
|
|
1533
|
+
})(),
|
|
1534
|
+
placeholder: "60",
|
|
1535
|
+
onConfirm: (val) => {
|
|
1536
|
+
const sec = Number(val.trim());
|
|
1537
|
+
if (!sec || sec <= 0) {
|
|
1538
|
+
dialog2?.clear();
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
saveConfig({
|
|
1542
|
+
refresh_interval_sec: sec
|
|
1543
|
+
});
|
|
1544
|
+
setConfigTick((v) => v + 1);
|
|
1545
|
+
api.ui.toast({
|
|
1546
|
+
variant: "success",
|
|
1547
|
+
message: t("settingsSaved")
|
|
1548
|
+
});
|
|
1549
|
+
renderSettingsDialog(dialog2);
|
|
1550
|
+
},
|
|
1551
|
+
onCancel: () => dialog2?.clear()
|
|
1552
|
+
}));
|
|
1553
|
+
} else if (value === "refresh-pricing") {
|
|
1554
|
+
dialog2?.replace(() => _$createComponent(api.ui.DialogPrompt, {
|
|
1555
|
+
get title() {
|
|
1556
|
+
return t("settingsRefreshPricing");
|
|
1557
|
+
},
|
|
1558
|
+
description: () => (() => {
|
|
1559
|
+
var _el$51 = _$createElement("text");
|
|
1560
|
+
_$insertNode(_el$51, _$createTextNode(`\u8F93\u5165\u79D2\u6570\uFF08\u9ED8\u8BA4 1800\uFF09`));
|
|
1561
|
+
return _el$51;
|
|
1562
|
+
})(),
|
|
1563
|
+
placeholder: "1800",
|
|
1564
|
+
onConfirm: (val) => {
|
|
1565
|
+
const sec = Number(val.trim());
|
|
1566
|
+
if (!sec || sec <= 0) {
|
|
1567
|
+
dialog2?.clear();
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
saveConfig({
|
|
1571
|
+
pricing_interval_sec: sec
|
|
1572
|
+
});
|
|
1573
|
+
setConfigTick((v) => v + 1);
|
|
1574
|
+
api.ui.toast({
|
|
1575
|
+
variant: "success",
|
|
1576
|
+
message: t("settingsSaved")
|
|
1577
|
+
});
|
|
1578
|
+
renderSettingsDialog(dialog2);
|
|
1579
|
+
},
|
|
1580
|
+
onCancel: () => dialog2?.clear()
|
|
1581
|
+
}));
|
|
1582
|
+
}
|
|
1583
|
+
},
|
|
1584
|
+
onCancel: () => dialog2?.clear()
|
|
1585
|
+
}));
|
|
1586
|
+
};
|
|
1587
|
+
renderSettingsDialog(dialog);
|
|
1588
|
+
}
|
|
749
1589
|
var tui = async (api) => {
|
|
750
1590
|
api.slots.register(createSidebarSlot(api));
|
|
1591
|
+
api.slots.register(createPricingSlot(api));
|
|
751
1592
|
const savedLang = kvTryGet(api, `${KV_PREFIX}.lang`);
|
|
752
1593
|
if (savedLang === "zh" || savedLang === "en") setLangCode(savedLang);
|
|
753
1594
|
else setLangCode(detectLang());
|
|
@@ -777,6 +1618,14 @@ var tui = async (api) => {
|
|
|
777
1618
|
name: "go-lang"
|
|
778
1619
|
},
|
|
779
1620
|
onSelect: (dialog) => runLangDialog(api, dialog)
|
|
1621
|
+
}, {
|
|
1622
|
+
title: t("settingsTitle"),
|
|
1623
|
+
value: "go-usage.settings",
|
|
1624
|
+
description: t("settingsDesc"),
|
|
1625
|
+
slash: {
|
|
1626
|
+
name: "go-settings"
|
|
1627
|
+
},
|
|
1628
|
+
onSelect: (dialog) => runSettingsDialog(api, dialog)
|
|
780
1629
|
}]);
|
|
781
1630
|
};
|
|
782
1631
|
var mod = {
|