@jxgame2020/dsh-token-quota 0.1.11 → 0.1.13
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 +343 -89
- package/lib/index.js +62 -3
- package/lib/types/client/TokenQuotaPanel.d.ts +3 -1
- package/lib/types/client/TokenQuotaPanel.js +36 -2
- package/lib/types/client/index.js +36 -1
- package/lib/types/client/locales.d.ts +24 -0
- package/lib/types/client/locales.js +24 -0
- package/lib/types/client/store.d.ts +24 -0
- package/lib/types/client/store.js +16 -0
- package/lib/types/index.d.ts +10 -0
- package/lib/types/index.js +69 -3
- package/lib/types/types.d.ts +2 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -225,6 +225,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
225
225
|
checkUpdates = true;
|
|
226
226
|
/** Cached upgrade availability; recomputed by {@link refreshUpgrade}. */
|
|
227
227
|
upgrade = null;
|
|
228
|
+
/** Error message from the most recent update check; null = no error. */
|
|
229
|
+
upgradeError = null;
|
|
228
230
|
/** Timer for the periodic update check. */
|
|
229
231
|
updateTimer;
|
|
230
232
|
/** In-flight update check, to coalesce the periodic and manual triggers. */
|
|
@@ -243,6 +245,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
243
245
|
disposeRouteLog;
|
|
244
246
|
/** Disposer for the optional manual-check route (`POST /token-quota/check-updates`). */
|
|
245
247
|
disposeRouteCheck;
|
|
248
|
+
/** Disposer for the optional clear-log route (`POST /token-quota/clear-log`). */
|
|
249
|
+
disposeRouteClear;
|
|
246
250
|
/** Disposer for the webServer-arrival watcher when the service mounts later. */
|
|
247
251
|
disposeRouteWatcher;
|
|
248
252
|
/**
|
|
@@ -353,6 +357,41 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
353
357
|
});
|
|
354
358
|
}
|
|
355
359
|
});
|
|
360
|
+
this.disposeRouteClear = server.register({
|
|
361
|
+
kind: "exact",
|
|
362
|
+
path: "/token-quota/clear-log",
|
|
363
|
+
handler: (req, res) => {
|
|
364
|
+
if (req.method !== "POST") {
|
|
365
|
+
res.writeHead(405);
|
|
366
|
+
res.end();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const chunks = [];
|
|
370
|
+
req.on("data", (chunk) => {
|
|
371
|
+
chunks.push(Buffer.from(chunk));
|
|
372
|
+
});
|
|
373
|
+
req.on("end", () => {
|
|
374
|
+
let before = "";
|
|
375
|
+
try {
|
|
376
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
377
|
+
if (raw.length > 0) {
|
|
378
|
+
const payload = JSON.parse(raw);
|
|
379
|
+
before = typeof payload.before === "string" ? payload.before : "";
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
383
|
+
res.end(JSON.stringify({ error: "bad payload" }));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const updated = this.clearLogBefore(before);
|
|
387
|
+
res.writeHead(200, {
|
|
388
|
+
"content-type": "application/json",
|
|
389
|
+
"cache-control": "no-store"
|
|
390
|
+
});
|
|
391
|
+
res.end(JSON.stringify(updated));
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
});
|
|
356
395
|
}
|
|
357
396
|
/** Query the npm registry and rebuild the cached upgrade info (coalesced). */
|
|
358
397
|
refreshUpgrade() {
|
|
@@ -367,7 +406,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
367
406
|
return this.upgradeCheck;
|
|
368
407
|
}
|
|
369
408
|
async doRefreshUpgrade() {
|
|
370
|
-
const current = "0.1.
|
|
409
|
+
const current = "0.1.13";
|
|
371
410
|
let latest;
|
|
372
411
|
try {
|
|
373
412
|
const response = await fetch(REGISTRY_LATEST_URL, {
|
|
@@ -376,16 +415,20 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
376
415
|
});
|
|
377
416
|
if (!response.ok) {
|
|
378
417
|
this.upgrade = null;
|
|
418
|
+
this.upgradeError = `registry responded ${response.status} ${response.statusText}`;
|
|
379
419
|
return;
|
|
380
420
|
}
|
|
381
421
|
const manifest = await response.json();
|
|
382
422
|
latest = typeof manifest.version === "string" ? manifest.version : void 0;
|
|
383
|
-
} catch {
|
|
423
|
+
} catch (err) {
|
|
384
424
|
this.upgrade = null;
|
|
425
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
426
|
+
this.upgradeError = msg.includes("fetch failed") ? "无法访问 npm registry(网络超时或被墙)" : msg.includes("aborted") ? "请求超时(10s)" : msg;
|
|
385
427
|
return;
|
|
386
428
|
}
|
|
387
429
|
if (latest === void 0 || !isNewer(latest, current)) {
|
|
388
430
|
this.upgrade = null;
|
|
431
|
+
this.upgradeError = null;
|
|
389
432
|
return;
|
|
390
433
|
}
|
|
391
434
|
const profile = findProfile();
|
|
@@ -395,6 +438,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
395
438
|
commands,
|
|
396
439
|
installKind: profile?.installKind ?? "registry"
|
|
397
440
|
};
|
|
441
|
+
this.upgradeError = null;
|
|
398
442
|
}
|
|
399
443
|
/** Start or stop the periodic update check to match the current setting. */
|
|
400
444
|
syncUpdateChecking() {
|
|
@@ -442,6 +486,19 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
442
486
|
entries.sort((left, right) => right.day.localeCompare(left.day) || left.key.localeCompare(right.key));
|
|
443
487
|
return { entries };
|
|
444
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Remove all historical cycles whose day portion strictly precedes `before`
|
|
491
|
+
* (format `YYYY-MM-DD`). The current cycle is never cleared. Returns the
|
|
492
|
+
* trimmed log so the client can refresh immediately.
|
|
493
|
+
*/
|
|
494
|
+
clearLogBefore(before) {
|
|
495
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(before)) return this.readLog();
|
|
496
|
+
const kept = {};
|
|
497
|
+
for (const [cycle, usageByKey] of Object.entries(this.history)) if (cycle.slice(0, 10) >= before) kept[cycle] = usageByKey;
|
|
498
|
+
this.history = kept;
|
|
499
|
+
this.scheduleWrite();
|
|
500
|
+
return this.readLog();
|
|
501
|
+
}
|
|
445
502
|
/** Read the current snapshot (also used by tests and inspection). */
|
|
446
503
|
readSnapshot() {
|
|
447
504
|
this.rollCycleIfNeeded();
|
|
@@ -462,7 +519,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
462
519
|
return {
|
|
463
520
|
day: this.cycle,
|
|
464
521
|
entries,
|
|
465
|
-
upgrade: this.upgrade
|
|
522
|
+
upgrade: this.upgrade,
|
|
523
|
+
upgradeError: this.upgradeError
|
|
466
524
|
};
|
|
467
525
|
}
|
|
468
526
|
/** Today's used tokens for one model key, or `0`. */
|
|
@@ -644,6 +702,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
644
702
|
if (this.disposeRoute !== void 0) this.disposeRoute();
|
|
645
703
|
if (this.disposeRouteLog !== void 0) this.disposeRouteLog();
|
|
646
704
|
if (this.disposeRouteCheck !== void 0) this.disposeRouteCheck();
|
|
705
|
+
if (this.disposeRouteClear !== void 0) this.disposeRouteClear();
|
|
647
706
|
this.flush();
|
|
648
707
|
}
|
|
649
708
|
safeSplit(key) {
|
|
@@ -20,6 +20,8 @@ export interface TokenQuotaPanelInjected {
|
|
|
20
20
|
setCheckUpdates: (enabled: boolean) => void;
|
|
21
21
|
/** Ask the Host to check the registry right now, then refresh the snapshot. */
|
|
22
22
|
checkUpdatesNow: () => void;
|
|
23
|
+
/** Ask the Host to clear log entries before a date, then refresh the log. */
|
|
24
|
+
clearLogBefore: (before: string) => void;
|
|
23
25
|
}
|
|
24
26
|
/** Full component props: runtime + store + locale + injected face. */
|
|
25
27
|
export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & PropsStore<ReturnType<typeof createTokenQuotaPanelStore>> & PropsLocale<'token-quota'> & TokenQuotaPanelInjected;
|
|
@@ -28,5 +30,5 @@ export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & Prop
|
|
|
28
30
|
* @param props - composed slot props.
|
|
29
31
|
* @returns the panel element tree.
|
|
30
32
|
*/
|
|
31
|
-
export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
|
|
33
|
+
export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
|
|
32
34
|
//# sourceMappingURL=TokenQuotaPanel.d.ts.map
|
|
@@ -86,7 +86,7 @@ function loadPanelPos() {
|
|
|
86
86
|
* @param props - composed slot props.
|
|
87
87
|
* @returns the panel element tree.
|
|
88
88
|
*/
|
|
89
|
-
export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, useStore, actions, useSessions, }) {
|
|
89
|
+
export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, useStore, actions, useSessions, }) {
|
|
90
90
|
const [collapsed, setCollapsed] = useState(false);
|
|
91
91
|
const [drafts, setDrafts] = useState({});
|
|
92
92
|
const [editingKey, setEditingKey] = useState(null);
|
|
@@ -117,10 +117,18 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
117
117
|
const upgrade = useStore(s => s.upgrade);
|
|
118
118
|
const upgradeDismissed = useStore(s => s.upgradeDismissed);
|
|
119
119
|
const checkingUpdates = useStore(s => s.checkingUpdates);
|
|
120
|
+
const lastCheckResult = useStore(s => s.lastCheckResult);
|
|
121
|
+
const upgradeError = useStore(s => s.upgradeError);
|
|
120
122
|
const reset = useStore(s => s.reset);
|
|
121
123
|
const dialogOpen = useStore(s => s.dialogOpen);
|
|
122
124
|
const logOpen = useStore(s => s.logOpen);
|
|
123
125
|
const log = useStore(s => s.log);
|
|
126
|
+
const logSearch = useStore(s => s.logSearch);
|
|
127
|
+
const logPage = useStore(s => s.logPage);
|
|
128
|
+
const logPageSize = useStore(s => s.logPageSize);
|
|
129
|
+
const logClearOpen = useStore(s => s.logClearOpen);
|
|
130
|
+
const logClearBefore = useStore(s => s.logClearBefore);
|
|
131
|
+
const logClearing = useStore(s => s.logClearing);
|
|
124
132
|
const fullNotice = useStore(s => s.fullNotice);
|
|
125
133
|
const loading = useStore(s => s.loading);
|
|
126
134
|
const error = useStore(s => s.error);
|
|
@@ -136,6 +144,23 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
136
144
|
name: model.name,
|
|
137
145
|
}))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
|
|
138
146
|
const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
|
|
147
|
+
// Log dialog: filter by search, then paginate.
|
|
148
|
+
const filteredLogEntries = useMemo(() => {
|
|
149
|
+
if (log === null)
|
|
150
|
+
return [];
|
|
151
|
+
const q = logSearch.trim().toLowerCase();
|
|
152
|
+
if (q === '')
|
|
153
|
+
return log.entries;
|
|
154
|
+
return log.entries.filter(e => e.key.toLowerCase().includes(q)
|
|
155
|
+
|| e.provider.toLowerCase().includes(q)
|
|
156
|
+
|| e.model.toLowerCase().includes(q));
|
|
157
|
+
}, [log, logSearch]);
|
|
158
|
+
const logTotalPages = Math.max(1, Math.ceil(filteredLogEntries.length / logPageSize));
|
|
159
|
+
const logCurrentPage = Math.min(logPage, logTotalPages - 1);
|
|
160
|
+
const logPageEntries = useMemo(() => {
|
|
161
|
+
const start = logCurrentPage * logPageSize;
|
|
162
|
+
return filteredLogEntries.slice(start, start + logPageSize);
|
|
163
|
+
}, [filteredLogEntries, logCurrentPage, logPageSize]);
|
|
139
164
|
/** Copy text to the clipboard (clipboard API with execCommand fallback). */
|
|
140
165
|
const copyToClipboard = async (text) => {
|
|
141
166
|
try {
|
|
@@ -237,6 +262,15 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
237
262
|
hour: reset?.hour ?? 0,
|
|
238
263
|
minute: Number(event.target.value),
|
|
239
264
|
});
|
|
240
|
-
}, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.
|
|
265
|
+
}, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("div", { className: css.checkUpdatesRow, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: checkUpdates, onChange: (event) => { setCheckUpdates(event.target.checked); } }), _jsx("span", { children: t('checkUpdatesLabel') })] }), _jsx("button", { type: "button", className: css.checkBtn, disabled: checkingUpdates, onClick: checkUpdatesNow, children: checkingUpdates
|
|
266
|
+
? t('checkingUpdates')
|
|
267
|
+
: lastCheckResult === 'up-to-date'
|
|
268
|
+
? t('checkUpToDate')
|
|
269
|
+
: lastCheckResult === 'error'
|
|
270
|
+
? t('checkFailed')
|
|
271
|
+
: t('checkUpdatesNow') })] }), lastCheckResult === 'error' && upgradeError !== null && (_jsx("div", { className: css.checkErrorHint, role: "alert", children: upgradeError }))] })] })), logOpen && (_jsxs("div", { ref: logRef, className: `${css.dialog} ${css.logDialog}`, style: logPos !== null ? { left: logPos.x, top: logPos.y, transform: 'none' } : undefined, children: [_jsxs("div", { className: css.logHeader, onPointerDown: (event) => { beginDrag(event, logRef.current, setLogPos); }, children: [_jsx("div", { className: css.dialogTitle, children: t('logsTitle') }), _jsxs("div", { className: css.logToolbar, onPointerDown: (event) => { event.stopPropagation(); }, children: [_jsx("input", { type: "search", className: css.logSearch, placeholder: t('logSearchPlaceholder'), value: logSearch, onChange: (event) => { actions.setLogSearch(event.target.value); } }), _jsx("button", { type: "button", className: css.logClearBtn, onClick: () => { actions.setLogClearOpen(true); }, children: t('logClear') })] }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setLogOpen(false); }, children: "\u00D7" })] }), log !== null && filteredLogEntries.length === 0 && (_jsx("div", { className: css.notice, children: logSearch.trim() !== '' ? t('logNoMatch') : t('logEmpty') })), _jsx("div", { className: css.logScroll, children: _jsxs("table", { className: css.logTable, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('logDay') }), _jsx("th", { children: t('logModel') }), _jsx("th", { className: css.logUsedCol, children: t('logUsed') })] }) }), _jsx("tbody", { children: logPageEntries.map(entry => (_jsxs("tr", { children: [_jsx("td", { className: css.logDayCol, children: entry.day }), _jsx("td", { className: css.logModelCol, title: entry.key, children: entry.key }), _jsx("td", { className: css.logUsedCol, children: formatTokens(entry.used) })] }, `${entry.day}/${entry.key}`))) })] }) }), _jsxs("div", { className: css.logPager, children: [_jsx("span", { className: css.logPagerInfo, children: t('logPagerInfo')
|
|
272
|
+
.replace('{from}', String(logCurrentPage * logPageSize + 1))
|
|
273
|
+
.replace('{to}', String(Math.min((logCurrentPage + 1) * logPageSize, filteredLogEntries.length)))
|
|
274
|
+
.replace('{total}', String(filteredLogEntries.length)) }), _jsxs("div", { className: css.logPagerControls, children: [_jsx("button", { type: "button", className: css.logPagerBtn, disabled: logCurrentPage <= 0, onClick: () => { actions.setLogPage(logCurrentPage - 1); }, children: "\u2039" }), _jsxs("span", { className: css.logPagerPages, children: [logCurrentPage + 1, " / ", logTotalPages] }), _jsx("button", { type: "button", className: css.logPagerBtn, disabled: logCurrentPage >= logTotalPages - 1, onClick: () => { actions.setLogPage(logCurrentPage + 1); }, children: "\u203A" }), _jsx("select", { className: css.logPageSizeSelect, value: logPageSize, onChange: (event) => { actions.setLogPageSize(Number(event.target.value)); }, children: [10, 15, 20, 50, 100].map(size => (_jsxs("option", { value: size, children: [size, t('logPerPage')] }, size))) })] })] })] })), logClearOpen && (_jsx("div", { className: css.dialogOverlay, children: _jsxs("div", { className: `${css.dialog} ${css.logClearDialog}`, children: [_jsx("div", { className: css.dialogTitle, children: t('logClearTitle') }), _jsx("div", { className: css.dialogBody, children: t('logClearHint') }), _jsx("input", { type: "date", className: css.logClearDate, value: logClearBefore, onChange: (event) => { actions.setLogClearBefore(event.target.value); } }), _jsxs("div", { className: css.dialogActions, children: [_jsx("button", { type: "button", className: css.checkBtn, disabled: logClearBefore === '' || logClearing, onClick: () => { clearLogBefore(logClearBefore); }, children: logClearing ? t('logClearing') : t('logClearConfirm') }), _jsx("button", { type: "button", className: css.checkBtn, onClick: () => { actions.setLogClearOpen(false); }, children: t('logClearCancel') })] })] }) }))] }));
|
|
241
275
|
}
|
|
242
276
|
//# sourceMappingURL=TokenQuotaPanel.js.map
|
|
@@ -185,6 +185,7 @@ export function apply(ctx) {
|
|
|
185
185
|
return;
|
|
186
186
|
bound?.setSnapshot(snapshot);
|
|
187
187
|
bound?.setUpgrade(snapshot.upgrade ?? null);
|
|
188
|
+
bound?.setUpgradeError(snapshot.upgradeError ?? null);
|
|
188
189
|
actOnFull(snapshot);
|
|
189
190
|
});
|
|
190
191
|
if (pullCount % 2 === 0)
|
|
@@ -236,21 +237,33 @@ export function apply(ctx) {
|
|
|
236
237
|
/** Ask the Host to check the registry right now, then refresh the snapshot. */
|
|
237
238
|
const checkUpdatesNow = () => {
|
|
238
239
|
bound?.setCheckingUpdates(true);
|
|
240
|
+
bound?.setLastCheckResult('idle');
|
|
239
241
|
void fetch('/token-quota/check-updates', { method: 'POST' }).then((response) => {
|
|
240
242
|
if (!response.ok) {
|
|
241
243
|
bound?.setError(`check-updates route: ${String(response.status)}`);
|
|
244
|
+
bound?.setLastCheckResult('error');
|
|
242
245
|
return undefined;
|
|
243
246
|
}
|
|
244
247
|
return response.json();
|
|
245
248
|
}, () => {
|
|
246
249
|
bound?.setError('update check failed');
|
|
250
|
+
bound?.setLastCheckResult('error');
|
|
247
251
|
}).then((snapshot) => {
|
|
248
252
|
if (snapshot !== undefined) {
|
|
249
253
|
bound?.setSnapshot(snapshot);
|
|
250
254
|
bound?.setUpgrade(snapshot.upgrade ?? null);
|
|
255
|
+
bound?.setUpgradeError(snapshot.upgradeError ?? null);
|
|
251
256
|
actOnFull(snapshot);
|
|
257
|
+
if (snapshot.upgrade === null) {
|
|
258
|
+
// No newer version known: distinguish "checked OK, up to date"
|
|
259
|
+
// from "check failed" via the host-provided error message.
|
|
260
|
+
bound?.setLastCheckResult(snapshot.upgradeError !== null ? 'error' : 'up-to-date');
|
|
261
|
+
}
|
|
252
262
|
}
|
|
253
263
|
bound?.setCheckingUpdates(false);
|
|
264
|
+
// Clear the transient result hint after a short delay so the button
|
|
265
|
+
// returns to its idle label. The error message stays until the next check.
|
|
266
|
+
setTimeout(() => { bound?.setLastCheckResult('idle'); }, 4000);
|
|
254
267
|
});
|
|
255
268
|
};
|
|
256
269
|
const setReset = (reset) => {
|
|
@@ -276,11 +289,33 @@ export function apply(ctx) {
|
|
|
276
289
|
bound?.setError('model switch failed');
|
|
277
290
|
});
|
|
278
291
|
};
|
|
292
|
+
/** Ask the Host to clear log entries before a given date, then refresh. */
|
|
293
|
+
const clearLogBefore = (before) => {
|
|
294
|
+
bound?.setLogClearing(true);
|
|
295
|
+
void fetch('/token-quota/clear-log', {
|
|
296
|
+
method: 'POST',
|
|
297
|
+
headers: { 'content-type': 'application/json' },
|
|
298
|
+
body: JSON.stringify({ before }),
|
|
299
|
+
}).then((response) => {
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
bound?.setError(`clear-log route: ${String(response.status)}`);
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
return response.json();
|
|
305
|
+
}, () => {
|
|
306
|
+
bound?.setError('clear log failed');
|
|
307
|
+
}).then((log) => {
|
|
308
|
+
if (log !== undefined)
|
|
309
|
+
bound?.setLog(log);
|
|
310
|
+
bound?.setLogClearing(false);
|
|
311
|
+
bound?.setLogClearOpen(false);
|
|
312
|
+
});
|
|
313
|
+
};
|
|
279
314
|
const injected = (actions) => {
|
|
280
315
|
bound = actions;
|
|
281
316
|
return {
|
|
282
317
|
load, setLimit, selectModel, setMonitored, setOnFull, setReset,
|
|
283
|
-
setCheckUpdates, checkUpdatesNow,
|
|
318
|
+
setCheckUpdates, checkUpdatesNow, clearLogBefore,
|
|
284
319
|
};
|
|
285
320
|
};
|
|
286
321
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
@@ -41,6 +41,16 @@ export declare const zh: {
|
|
|
41
41
|
logs: string;
|
|
42
42
|
logsTitle: string;
|
|
43
43
|
logEmpty: string;
|
|
44
|
+
logNoMatch: string;
|
|
45
|
+
logSearchPlaceholder: string;
|
|
46
|
+
logClear: string;
|
|
47
|
+
logClearTitle: string;
|
|
48
|
+
logClearHint: string;
|
|
49
|
+
logClearConfirm: string;
|
|
50
|
+
logClearCancel: string;
|
|
51
|
+
logClearing: string;
|
|
52
|
+
logPagerInfo: string;
|
|
53
|
+
logPerPage: string;
|
|
44
54
|
logDay: string;
|
|
45
55
|
logModel: string;
|
|
46
56
|
logUsed: string;
|
|
@@ -55,6 +65,8 @@ export declare const zh: {
|
|
|
55
65
|
checkUpdatesLabel: string;
|
|
56
66
|
checkUpdatesNow: string;
|
|
57
67
|
checkingUpdates: string;
|
|
68
|
+
checkUpToDate: string;
|
|
69
|
+
checkFailed: string;
|
|
58
70
|
};
|
|
59
71
|
/** Dictionary keys owned by the token-quota panel (a string-literal union). */
|
|
60
72
|
export type TokenQuotaKey = keyof typeof zh;
|
|
@@ -95,6 +107,16 @@ export declare const en: {
|
|
|
95
107
|
logs: string;
|
|
96
108
|
logsTitle: string;
|
|
97
109
|
logEmpty: string;
|
|
110
|
+
logNoMatch: string;
|
|
111
|
+
logSearchPlaceholder: string;
|
|
112
|
+
logClear: string;
|
|
113
|
+
logClearTitle: string;
|
|
114
|
+
logClearHint: string;
|
|
115
|
+
logClearConfirm: string;
|
|
116
|
+
logClearCancel: string;
|
|
117
|
+
logClearing: string;
|
|
118
|
+
logPagerInfo: string;
|
|
119
|
+
logPerPage: string;
|
|
98
120
|
logDay: string;
|
|
99
121
|
logModel: string;
|
|
100
122
|
logUsed: string;
|
|
@@ -109,5 +131,7 @@ export declare const en: {
|
|
|
109
131
|
checkUpdatesLabel: string;
|
|
110
132
|
checkUpdatesNow: string;
|
|
111
133
|
checkingUpdates: string;
|
|
134
|
+
checkUpToDate: string;
|
|
135
|
+
checkFailed: string;
|
|
112
136
|
};
|
|
113
137
|
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -41,6 +41,16 @@ export const zh = {
|
|
|
41
41
|
logs: '日志',
|
|
42
42
|
logsTitle: '用量日志',
|
|
43
43
|
logEmpty: '暂无记录',
|
|
44
|
+
logNoMatch: '无匹配记录',
|
|
45
|
+
logSearchPlaceholder: '搜索模型…',
|
|
46
|
+
logClear: '清除',
|
|
47
|
+
logClearTitle: '清除历史日志',
|
|
48
|
+
logClearHint: '将删除所选日期之前的所有记录(不含当日)',
|
|
49
|
+
logClearConfirm: '确认清除',
|
|
50
|
+
logClearCancel: '取消',
|
|
51
|
+
logClearing: '清除中…',
|
|
52
|
+
logPagerInfo: '第 {from}-{to} 条 / 共 {total} 条',
|
|
53
|
+
logPerPage: '条/页',
|
|
44
54
|
logDay: '日期',
|
|
45
55
|
logModel: '模型',
|
|
46
56
|
logUsed: '用量',
|
|
@@ -55,6 +65,8 @@ export const zh = {
|
|
|
55
65
|
checkUpdatesLabel: '自动检查更新',
|
|
56
66
|
checkUpdatesNow: '立即检查',
|
|
57
67
|
checkingUpdates: '检查中…',
|
|
68
|
+
checkUpToDate: '已是最新',
|
|
69
|
+
checkFailed: '检查失败',
|
|
58
70
|
};
|
|
59
71
|
/** English copy. */
|
|
60
72
|
export const en = {
|
|
@@ -93,6 +105,16 @@ export const en = {
|
|
|
93
105
|
logs: 'Logs',
|
|
94
106
|
logsTitle: 'Usage Log',
|
|
95
107
|
logEmpty: 'No records',
|
|
108
|
+
logNoMatch: 'No matching records',
|
|
109
|
+
logSearchPlaceholder: 'Search model…',
|
|
110
|
+
logClear: 'Clear',
|
|
111
|
+
logClearTitle: 'Clear historical log',
|
|
112
|
+
logClearHint: 'This deletes all records before the selected date (the date itself is kept).',
|
|
113
|
+
logClearConfirm: 'Confirm',
|
|
114
|
+
logClearCancel: 'Cancel',
|
|
115
|
+
logClearing: 'Clearing…',
|
|
116
|
+
logPagerInfo: '{from}-{to} of {total}',
|
|
117
|
+
logPerPage: '/page',
|
|
96
118
|
logDay: 'Day',
|
|
97
119
|
logModel: 'Model',
|
|
98
120
|
logUsed: 'Used',
|
|
@@ -107,5 +129,7 @@ export const en = {
|
|
|
107
129
|
checkUpdatesLabel: 'Check for updates automatically',
|
|
108
130
|
checkUpdatesNow: 'Check now',
|
|
109
131
|
checkingUpdates: 'Checking…',
|
|
132
|
+
checkUpToDate: 'Up to date',
|
|
133
|
+
checkFailed: 'Check failed',
|
|
110
134
|
};
|
|
111
135
|
//# sourceMappingURL=locales.js.map
|
|
@@ -54,6 +54,10 @@ export interface TokenQuotaPanelState {
|
|
|
54
54
|
upgradeDismissed: boolean;
|
|
55
55
|
/** Whether a manual update check is in flight. */
|
|
56
56
|
checkingUpdates: boolean;
|
|
57
|
+
/** Result of the most recent manual check; 'idle' = nothing to show. */
|
|
58
|
+
lastCheckResult: 'idle' | 'up-to-date' | 'error';
|
|
59
|
+
/** Error message from the most recent update check (shown when lastCheckResult='error'). */
|
|
60
|
+
upgradeError: string | null;
|
|
57
61
|
/** Whether the settings dialog is open. */
|
|
58
62
|
dialogOpen: boolean;
|
|
59
63
|
/** Full-quota notice shown for the `'stop'` strategy; null when none. */
|
|
@@ -64,6 +68,18 @@ export interface TokenQuotaPanelState {
|
|
|
64
68
|
logOpen: boolean;
|
|
65
69
|
/** Latest usage history; null before the first fetch. */
|
|
66
70
|
log: TokenQuotaLog | null;
|
|
71
|
+
/** Filter string for the log table (matches provider/model key). */
|
|
72
|
+
logSearch: string;
|
|
73
|
+
/** Current page index (0-based) in the filtered log table. */
|
|
74
|
+
logPage: number;
|
|
75
|
+
/** Rows per page in the log table. */
|
|
76
|
+
logPageSize: number;
|
|
77
|
+
/** Whether the clear-log confirmation dialog is open. */
|
|
78
|
+
logClearOpen: boolean;
|
|
79
|
+
/** The `YYYY-MM-DD` date before which log entries will be cleared. */
|
|
80
|
+
logClearBefore: string;
|
|
81
|
+
/** Whether a clear-log request is in flight. */
|
|
82
|
+
logClearing: boolean;
|
|
67
83
|
}
|
|
68
84
|
/** Declared write surface. */
|
|
69
85
|
export type TokenQuotaPanelActions = {
|
|
@@ -76,11 +92,19 @@ export type TokenQuotaPanelActions = {
|
|
|
76
92
|
setUpgrade: (d: TokenQuotaPanelState, upgrade: TokenQuotaUpgrade | null) => void;
|
|
77
93
|
setUpgradeDismissed: (d: TokenQuotaPanelState, dismissed: boolean) => void;
|
|
78
94
|
setCheckingUpdates: (d: TokenQuotaPanelState, checking: boolean) => void;
|
|
95
|
+
setLastCheckResult: (d: TokenQuotaPanelState, result: 'idle' | 'up-to-date' | 'error') => void;
|
|
96
|
+
setUpgradeError: (d: TokenQuotaPanelState, error: string | null) => void;
|
|
79
97
|
setDialogOpen: (d: TokenQuotaPanelState, open: boolean) => void;
|
|
80
98
|
setFullNotice: (d: TokenQuotaPanelState, notice: string | null) => void;
|
|
81
99
|
setReset: (d: TokenQuotaPanelState, reset: TokenQuotaReset | null) => void;
|
|
82
100
|
setLogOpen: (d: TokenQuotaPanelState, open: boolean) => void;
|
|
83
101
|
setLog: (d: TokenQuotaPanelState, log: TokenQuotaLog | null) => void;
|
|
102
|
+
setLogSearch: (d: TokenQuotaPanelState, search: string) => void;
|
|
103
|
+
setLogPage: (d: TokenQuotaPanelState, page: number) => void;
|
|
104
|
+
setLogPageSize: (d: TokenQuotaPanelState, size: number) => void;
|
|
105
|
+
setLogClearOpen: (d: TokenQuotaPanelState, open: boolean) => void;
|
|
106
|
+
setLogClearBefore: (d: TokenQuotaPanelState, before: string) => void;
|
|
107
|
+
setLogClearing: (d: TokenQuotaPanelState, clearing: boolean) => void;
|
|
84
108
|
};
|
|
85
109
|
/**
|
|
86
110
|
* Declares the panel state and write surface.
|
|
@@ -30,11 +30,19 @@ export function createTokenQuotaPanelStore() {
|
|
|
30
30
|
upgrade: null,
|
|
31
31
|
upgradeDismissed: false,
|
|
32
32
|
checkingUpdates: false,
|
|
33
|
+
lastCheckResult: 'idle',
|
|
34
|
+
upgradeError: null,
|
|
33
35
|
dialogOpen: false,
|
|
34
36
|
fullNotice: null,
|
|
35
37
|
reset: null,
|
|
36
38
|
logOpen: false,
|
|
37
39
|
log: null,
|
|
40
|
+
logSearch: '',
|
|
41
|
+
logPage: 0,
|
|
42
|
+
logPageSize: 15,
|
|
43
|
+
logClearOpen: false,
|
|
44
|
+
logClearBefore: '',
|
|
45
|
+
logClearing: false,
|
|
38
46
|
}),
|
|
39
47
|
actions: {
|
|
40
48
|
setSnapshot: (d, snapshot) => { d.snapshot = snapshot; },
|
|
@@ -53,11 +61,19 @@ export function createTokenQuotaPanelStore() {
|
|
|
53
61
|
},
|
|
54
62
|
setUpgradeDismissed: (d, dismissed) => { d.upgradeDismissed = dismissed; },
|
|
55
63
|
setCheckingUpdates: (d, checking) => { d.checkingUpdates = checking; },
|
|
64
|
+
setLastCheckResult: (d, result) => { d.lastCheckResult = result; },
|
|
65
|
+
setUpgradeError: (d, error) => { d.upgradeError = error; },
|
|
56
66
|
setDialogOpen: (d, open) => { d.dialogOpen = open; },
|
|
57
67
|
setFullNotice: (d, notice) => { d.fullNotice = notice; },
|
|
58
68
|
setReset: (d, reset) => { d.reset = reset; },
|
|
59
69
|
setLogOpen: (d, open) => { d.logOpen = open; },
|
|
60
70
|
setLog: (d, log) => { d.log = log; },
|
|
71
|
+
setLogSearch: (d, search) => { d.logSearch = search; d.logPage = 0; },
|
|
72
|
+
setLogPage: (d, page) => { d.logPage = page; },
|
|
73
|
+
setLogPageSize: (d, size) => { d.logPageSize = size; d.logPage = 0; },
|
|
74
|
+
setLogClearOpen: (d, open) => { d.logClearOpen = open; },
|
|
75
|
+
setLogClearBefore: (d, before) => { d.logClearBefore = before; },
|
|
76
|
+
setLogClearing: (d, clearing) => { d.logClearing = clearing; },
|
|
61
77
|
},
|
|
62
78
|
});
|
|
63
79
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ export declare class TokenQuotaService extends Service {
|
|
|
59
59
|
private checkUpdates;
|
|
60
60
|
/** Cached upgrade availability; recomputed by {@link refreshUpgrade}. */
|
|
61
61
|
private upgrade;
|
|
62
|
+
/** Error message from the most recent update check; null = no error. */
|
|
63
|
+
private upgradeError;
|
|
62
64
|
/** Timer for the periodic update check. */
|
|
63
65
|
private updateTimer;
|
|
64
66
|
/** In-flight update check, to coalesce the periodic and manual triggers. */
|
|
@@ -77,6 +79,8 @@ export declare class TokenQuotaService extends Service {
|
|
|
77
79
|
private disposeRouteLog;
|
|
78
80
|
/** Disposer for the optional manual-check route (`POST /token-quota/check-updates`). */
|
|
79
81
|
private disposeRouteCheck;
|
|
82
|
+
/** Disposer for the optional clear-log route (`POST /token-quota/clear-log`). */
|
|
83
|
+
private disposeRouteClear;
|
|
80
84
|
/** Disposer for the webServer-arrival watcher when the service mounts later. */
|
|
81
85
|
private disposeRouteWatcher;
|
|
82
86
|
/**
|
|
@@ -93,6 +97,12 @@ export declare class TokenQuotaService extends Service {
|
|
|
93
97
|
private syncUpdateChecking;
|
|
94
98
|
/** Read the full per-cycle usage history (log dialog data). */
|
|
95
99
|
readLog(): TokenQuotaLog;
|
|
100
|
+
/**
|
|
101
|
+
* Remove all historical cycles whose day portion strictly precedes `before`
|
|
102
|
+
* (format `YYYY-MM-DD`). The current cycle is never cleared. Returns the
|
|
103
|
+
* trimmed log so the client can refresh immediately.
|
|
104
|
+
*/
|
|
105
|
+
clearLogBefore(before: string): TokenQuotaLog;
|
|
96
106
|
/** Read the current snapshot (also used by tests and inspection). */
|
|
97
107
|
readSnapshot(): TokenQuotaSnapshot;
|
|
98
108
|
/** Today's used tokens for one model key, or `0`. */
|