@cassius0924/dsh-usage-dashboard 0.5.0 → 0.6.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 +30 -11
- package/lib/client.js +190 -58
- package/lib/client.js.map +4 -4
- package/lib/index.js +227 -28
- package/lib/index.js.map +3 -3
- package/lib/types/client/dom.d.ts +18 -1
- package/lib/types/client/locales.d.ts +19 -9
- package/lib/types/client/sessions.d.ts +25 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/client/widget.d.ts +2 -5
- package/lib/types/context.d.ts +64 -1
- package/lib/types/contract.d.ts +50 -10
- package/lib/types/pricing.d.ts +46 -15
- package/lib/types/usage.d.ts +2 -2
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -60,32 +60,71 @@ function isTrustedApiRequest(req) {
|
|
|
60
60
|
|
|
61
61
|
// src/pricing.ts
|
|
62
62
|
var PEAK_PRICING_FROM_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
|
|
63
|
+
var V41_FLASH_PRICING_FROM_MS = Date.UTC(2026, 8, 10, 4, 0, 0);
|
|
64
|
+
var PRO_ROUTED_TO_FLASH_FROM_MS = Date.UTC(2026, 8, 14, 4, 0, 0);
|
|
63
65
|
var PEAK_WINDOWS = [[9, 12], [14, 18]];
|
|
64
66
|
var BEIJING_OFFSET_MS = 8 * 36e5;
|
|
65
|
-
var
|
|
67
|
+
var FLAT_RATES = {
|
|
66
68
|
pro: { cacheHit: 0.025, input: 3, output: 6 },
|
|
67
|
-
flash: { cacheHit: 0.02, input: 1, output: 2 }
|
|
69
|
+
flash: { cacheHit: 0.02, input: 1, output: 2 },
|
|
70
|
+
// The vision model billed at flash rates from day one, so its pre-switch
|
|
71
|
+
// history is costed at the same flat prices as flash.
|
|
72
|
+
vision: { cacheHit: 0.02, input: 1, output: 2 }
|
|
68
73
|
};
|
|
69
|
-
var
|
|
74
|
+
var PEAK_RATES_08_17 = {
|
|
70
75
|
pro: { cacheHit: 0.3, input: 9, output: 27 },
|
|
71
|
-
flash: { cacheHit: 0.1, input: 3, output: 9 }
|
|
76
|
+
flash: { cacheHit: 0.1, input: 3, output: 9 },
|
|
77
|
+
// Official policy: the vision model is priced identically to flash; images
|
|
78
|
+
// cost tokens, not their own multiplier.
|
|
79
|
+
vision: { cacheHit: 0.1, input: 3, output: 9 }
|
|
72
80
|
};
|
|
81
|
+
var PEAK_RATES_09_10 = {
|
|
82
|
+
pro: { cacheHit: 0.3, input: 9, output: 27 },
|
|
83
|
+
flash: { cacheHit: 0.04, input: 2, output: 8 },
|
|
84
|
+
vision: { cacheHit: 0.04, input: 2, output: 8 }
|
|
85
|
+
};
|
|
86
|
+
var PRICE_ERAS = [
|
|
87
|
+
{ fromMs: PEAK_PRICING_FROM_MS, peak: PEAK_RATES_08_17, weekdaysOnly: false },
|
|
88
|
+
{ fromMs: V41_FLASH_PRICING_FROM_MS, peak: PEAK_RATES_09_10, weekdaysOnly: true }
|
|
89
|
+
];
|
|
90
|
+
function eraAt(timeMs) {
|
|
91
|
+
let era = PRICE_ERAS[0];
|
|
92
|
+
for (const candidate of PRICE_ERAS) if (timeMs >= candidate.fromMs) era = candidate;
|
|
93
|
+
return era;
|
|
94
|
+
}
|
|
73
95
|
var halved = (rates) => ({
|
|
74
96
|
cacheHit: rates.cacheHit / 2,
|
|
75
97
|
input: rates.input / 2,
|
|
76
98
|
output: rates.output / 2
|
|
77
99
|
});
|
|
78
100
|
function tierOf(model) {
|
|
79
|
-
|
|
101
|
+
const name2 = model.toLowerCase();
|
|
102
|
+
if (name2.includes("vision")) return "vision";
|
|
103
|
+
if (name2.includes("flash")) return "flash";
|
|
104
|
+
return "pro";
|
|
105
|
+
}
|
|
106
|
+
function billedTierOf(timeMs, model) {
|
|
107
|
+
const tier = tierOf(model);
|
|
108
|
+
return tier === "pro" && timeMs >= PRO_ROUTED_TO_FLASH_FROM_MS ? "flash" : tier;
|
|
109
|
+
}
|
|
110
|
+
function isBeijingWeekday(timeMs) {
|
|
111
|
+
const day = new Date(timeMs + BEIJING_OFFSET_MS).getUTCDay();
|
|
112
|
+
return day >= 1 && day <= 5;
|
|
113
|
+
}
|
|
114
|
+
function estimateImageTokens(width, height) {
|
|
115
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return 0;
|
|
116
|
+
const area = width * height;
|
|
117
|
+
const scaledArea = Math.min(800 * 800, Math.max(384 * 384, area));
|
|
118
|
+
return 384 * (scaledArea / (800 * 800));
|
|
80
119
|
}
|
|
81
120
|
function isPeak(timeMs) {
|
|
121
|
+
if (eraAt(timeMs).weekdaysOnly && !isBeijingWeekday(timeMs)) return false;
|
|
82
122
|
const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS) % 864e5 + 864e5) % 864e5 / 36e5);
|
|
83
123
|
return PEAK_WINDOWS.some(([from, to]) => hour >= from && hour < to);
|
|
84
124
|
}
|
|
85
125
|
function ratesAt(timeMs, model) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const peak = PEAK_RATES[tier];
|
|
126
|
+
if (timeMs < PEAK_PRICING_FROM_MS) return FLAT_RATES[tierOf(model)];
|
|
127
|
+
const peak = eraAt(timeMs).peak[billedTierOf(timeMs, model)];
|
|
89
128
|
return isPeak(timeMs) ? peak : halved(peak);
|
|
90
129
|
}
|
|
91
130
|
function cacheSavingOf(timeMs, model, cache) {
|
|
@@ -97,23 +136,19 @@ function costOf(timeMs, model, input, cache, output) {
|
|
|
97
136
|
return applyRates(ratesAt(timeMs, model), input, cache, output);
|
|
98
137
|
}
|
|
99
138
|
function costUnderPeakEra(timeMs, model, input, cache, output, forceOffPeak = false) {
|
|
100
|
-
const peak =
|
|
139
|
+
const peak = PRICE_ERAS[PRICE_ERAS.length - 1].peak[billedTierOf(timeMs, model)];
|
|
101
140
|
const rates = !forceOffPeak && isPeak(timeMs) ? peak : halved(peak);
|
|
102
141
|
return applyRates(rates, input, cache, output);
|
|
103
142
|
}
|
|
104
143
|
function pricingInfo(nowMs) {
|
|
105
|
-
const
|
|
144
|
+
const era = eraAt(nowMs);
|
|
145
|
+
const row = (model, tier) => ({ model, peak: era.peak[tier], offPeak: halved(era.peak[tier]) });
|
|
106
146
|
return {
|
|
107
147
|
currency: "CNY",
|
|
108
148
|
switchDate: "2026-08-17",
|
|
109
|
-
|
|
110
|
-
inPeakNow: split && isPeak(nowMs),
|
|
149
|
+
inPeakNow: isPeak(nowMs),
|
|
111
150
|
peakWindows: ["09:00\u201312:00", "14:00\u201318:00"],
|
|
112
|
-
tiers: ["
|
|
113
|
-
model: tier === "pro" ? "deepseek-v4-pro" : "deepseek-v4-flash",
|
|
114
|
-
peak: split ? PEAK_RATES[tier] : LEGACY_RATES[tier],
|
|
115
|
-
offPeak: split ? halved(PEAK_RATES[tier]) : null
|
|
116
|
-
}))
|
|
151
|
+
tiers: [row("deepseek-flash", "flash"), row("deepseek-v4-pro", "pro")]
|
|
117
152
|
};
|
|
118
153
|
}
|
|
119
154
|
|
|
@@ -122,7 +157,47 @@ var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
|
122
157
|
function errorMessage(err) {
|
|
123
158
|
return err?.message ?? String(err);
|
|
124
159
|
}
|
|
125
|
-
|
|
160
|
+
var ACCOUNT_CLIENT = {
|
|
161
|
+
version: process.env.DSH_CLIENT_VERSION ?? "0.0.0",
|
|
162
|
+
locale: "zh-CN",
|
|
163
|
+
timezoneOffsetSeconds: -(/* @__PURE__ */ new Date()).getTimezoneOffset() * 60
|
|
164
|
+
};
|
|
165
|
+
function accountBalances(toppedUpWallets, grantedWallets) {
|
|
166
|
+
const byCurrency = /* @__PURE__ */ new Map();
|
|
167
|
+
const add = (wallets, field) => {
|
|
168
|
+
for (const wallet of wallets) {
|
|
169
|
+
const currency = wallet.currency ?? "";
|
|
170
|
+
const amount = Number(wallet.balance ?? "0");
|
|
171
|
+
if (currency === "" || !Number.isFinite(amount)) continue;
|
|
172
|
+
const acc = byCurrency.get(currency) ?? { total: 0, granted: 0, toppedUp: 0 };
|
|
173
|
+
acc[field] += amount;
|
|
174
|
+
acc.total += amount;
|
|
175
|
+
byCurrency.set(currency, acc);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
add(toppedUpWallets, "toppedUp");
|
|
179
|
+
add(grantedWallets, "granted");
|
|
180
|
+
return [...byCurrency].map(([currency, acc]) => ({
|
|
181
|
+
currency,
|
|
182
|
+
total: acc.total.toFixed(2),
|
|
183
|
+
granted: acc.granted.toFixed(2),
|
|
184
|
+
toppedUp: acc.toppedUp.toFixed(2)
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
async function readAccountBalance(account) {
|
|
188
|
+
try {
|
|
189
|
+
const result = await account.getBalance(ACCOUNT_CLIENT);
|
|
190
|
+
if (result === null) return { ok: false, reason: "signed-out" };
|
|
191
|
+
if (result.status !== "ready") return { ok: false, reason: "failed" };
|
|
192
|
+
const balances = accountBalances(result.value ?? [], result.bonusWallets ?? []);
|
|
193
|
+
return { ok: true, data: { isAvailable: balances.length > 0, balances } };
|
|
194
|
+
} catch {
|
|
195
|
+
return { ok: false, reason: "failed" };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function fetchBalance(credentials, account) {
|
|
199
|
+
const fromAccount = account === void 0 ? void 0 : await readAccountBalance(account);
|
|
200
|
+
if (fromAccount?.ok === true) return { ok: true, data: fromAccount.data };
|
|
126
201
|
if (credentials === void 0) return { ok: false, error: "\u51ED\u8BC1\u670D\u52A1\u4E0D\u53EF\u7528" };
|
|
127
202
|
let cred;
|
|
128
203
|
try {
|
|
@@ -131,7 +206,10 @@ async function fetchBalance(credentials) {
|
|
|
131
206
|
return { ok: false, error: `\u8BFB\u53D6 API Key \u5931\u8D25\uFF1A${errorMessage(err)}` };
|
|
132
207
|
}
|
|
133
208
|
if (cred === void 0 || cred.value === "") {
|
|
134
|
-
return {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: fromAccount?.reason === "failed" ? "\u8D26\u53F7\u4F59\u989D\u63A5\u53E3\u4E0D\u53EF\u7528\uFF0C\u4E14\u672A\u914D\u7F6E DEEPSEEK_API_KEY" : "\u672A\u914D\u7F6E DEEPSEEK_API_KEY\uFF08\u53EF\u5728\u300C\u8BBE\u7F6E \u2192 \u6A21\u578B\u300D\u4E2D\u586B\u5199\uFF09"
|
|
212
|
+
};
|
|
135
213
|
}
|
|
136
214
|
let res;
|
|
137
215
|
try {
|
|
@@ -175,6 +253,18 @@ function bump(map, key, input, output, cache, cost) {
|
|
|
175
253
|
map.set(key, bucket);
|
|
176
254
|
}
|
|
177
255
|
var emptyOverall = () => ({ ...emptyBucket(), reasoning: 0, cacheSavings: 0 });
|
|
256
|
+
var emptyVision = () => ({ images: 0, imageTokens: 0, cost: 0, bytes: 0 });
|
|
257
|
+
function bumpVisionInto(acc, images, imageTokens, cost, bytes) {
|
|
258
|
+
acc.images += images;
|
|
259
|
+
acc.imageTokens += imageTokens;
|
|
260
|
+
acc.cost += cost;
|
|
261
|
+
acc.bytes += bytes;
|
|
262
|
+
}
|
|
263
|
+
function bumpVisionMap(map, key, images, imageTokens, cost, bytes) {
|
|
264
|
+
const acc = map.get(key) ?? emptyVision();
|
|
265
|
+
bumpVisionInto(acc, images, imageTokens, cost, bytes);
|
|
266
|
+
map.set(key, acc);
|
|
267
|
+
}
|
|
178
268
|
var SESSION_TOP_N = 6;
|
|
179
269
|
function titleOf(events, id) {
|
|
180
270
|
let title = "";
|
|
@@ -217,6 +307,7 @@ function addUsageEvent(events, onEvent) {
|
|
|
217
307
|
if (events === void 0) return result;
|
|
218
308
|
let provider = "";
|
|
219
309
|
let model = "";
|
|
310
|
+
const pendingImages = [];
|
|
220
311
|
for (const ev of events) {
|
|
221
312
|
if (ev?.type === "request/header") {
|
|
222
313
|
const cfg = ev.data?.header?.config;
|
|
@@ -226,6 +317,7 @@ function addUsageEvent(events, onEvent) {
|
|
|
226
317
|
}
|
|
227
318
|
continue;
|
|
228
319
|
}
|
|
320
|
+
pendingImages.push(...imagesInEvent(ev));
|
|
229
321
|
if (ev?.type !== "assistant/message") continue;
|
|
230
322
|
const usage = ev.data?.usage;
|
|
231
323
|
if (usage === void 0) continue;
|
|
@@ -239,11 +331,56 @@ function addUsageEvent(events, onEvent) {
|
|
|
239
331
|
const messageId = typeof rawMessageId === "string" && rawMessageId !== "" ? rawMessageId : void 0;
|
|
240
332
|
const rawTurn = ev.data?.turn;
|
|
241
333
|
const turn = typeof rawTurn === "number" && Number.isFinite(rawTurn) ? rawTurn : void 0;
|
|
242
|
-
onEvent(ev.time, input, output, cache, reasoning, provider, model, messageId, turn);
|
|
334
|
+
onEvent(ev.time, input, output, cache, reasoning, provider, model, messageId, turn, pendingImages);
|
|
335
|
+
pendingImages.length = 0;
|
|
243
336
|
result.usageRecords += 1;
|
|
244
337
|
}
|
|
245
338
|
return result;
|
|
246
339
|
}
|
|
340
|
+
function imageBlocks(blocks) {
|
|
341
|
+
const found = [];
|
|
342
|
+
const walkAll = (items) => {
|
|
343
|
+
for (const item of items ?? []) {
|
|
344
|
+
if (item?.type === "image" && item.attachment !== void 0 && item.attachment !== null) {
|
|
345
|
+
found.push(item.attachment);
|
|
346
|
+
}
|
|
347
|
+
if (Array.isArray(item?.content)) walkAll(item.content);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
walkAll(blocks);
|
|
351
|
+
return found;
|
|
352
|
+
}
|
|
353
|
+
function imagesInEvent(ev) {
|
|
354
|
+
if (ev === void 0) return [];
|
|
355
|
+
return [
|
|
356
|
+
...imageBlocks(ev.data?.content),
|
|
357
|
+
...imageBlocks(ev.data?.message?.content)
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
async function readSessionEvents(persistence, id) {
|
|
361
|
+
if (typeof persistence.open === "function") {
|
|
362
|
+
const handle = await persistence.open(id, "read");
|
|
363
|
+
try {
|
|
364
|
+
const events2 = [];
|
|
365
|
+
for (; ; ) {
|
|
366
|
+
const slice = await handle.read(events2.length);
|
|
367
|
+
const batch = slice.events ?? [];
|
|
368
|
+
if (batch.length === 0) return events2;
|
|
369
|
+
for (const event of batch) events2.push(event);
|
|
370
|
+
}
|
|
371
|
+
} finally {
|
|
372
|
+
try {
|
|
373
|
+
await handle.close();
|
|
374
|
+
} catch {
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (persistence.readFrom === void 0) {
|
|
379
|
+
throw new Error("\u5BBF\u4E3B sessionPersistence \u7F3A\u5C11 open() \u4E0E readFrom()\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7");
|
|
380
|
+
}
|
|
381
|
+
const { events } = await persistence.readFrom(id, 0);
|
|
382
|
+
return events;
|
|
383
|
+
}
|
|
247
384
|
async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
248
385
|
if (persistence === void 0) return { ok: false, error: "\u4F1A\u8BDD\u6301\u4E45\u5316\u670D\u52A1\u4E0D\u53EF\u7528" };
|
|
249
386
|
let headers;
|
|
@@ -259,6 +396,9 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
259
396
|
const modelDays = /* @__PURE__ */ new Map();
|
|
260
397
|
const modelHours = /* @__PURE__ */ new Map();
|
|
261
398
|
const sessions = [];
|
|
399
|
+
const vision = emptyVision();
|
|
400
|
+
const visionDays = /* @__PURE__ */ new Map();
|
|
401
|
+
const visionSessions = /* @__PURE__ */ new Map();
|
|
262
402
|
const coverage = {
|
|
263
403
|
scope: "local-dsh-session-logs",
|
|
264
404
|
listedSessions: 0,
|
|
@@ -287,7 +427,10 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
287
427
|
modelTotals: /* @__PURE__ */ new Map(),
|
|
288
428
|
modelDays: /* @__PURE__ */ new Map(),
|
|
289
429
|
modelHours: /* @__PURE__ */ new Map(),
|
|
290
|
-
sessions: []
|
|
430
|
+
sessions: [],
|
|
431
|
+
vision: emptyVision(),
|
|
432
|
+
visionDays: /* @__PURE__ */ new Map(),
|
|
433
|
+
visionSessions: /* @__PURE__ */ new Map()
|
|
291
434
|
}));
|
|
292
435
|
const bumpOverall = (bucket, input, output, cache, reasoning, cost, cacheSavings) => {
|
|
293
436
|
bucket.input += input;
|
|
@@ -320,15 +463,17 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
320
463
|
session.calls += 1;
|
|
321
464
|
if (time > session.lastActive) session.lastActive = time;
|
|
322
465
|
};
|
|
323
|
-
const sessionIds = headers.map((header) => header.id ?? header.sessionId).filter((sid) => sid !== void 0 && sid !== "");
|
|
466
|
+
const sessionIds = headers.map((header) => header.id ?? header.sessionId ?? header.header?.id ?? header.header?.sessionId).filter((sid) => sid !== void 0 && sid !== "");
|
|
324
467
|
coverage.listedSessions = sessionIds.length;
|
|
325
468
|
for (const sid of sessionIds) {
|
|
326
469
|
try {
|
|
327
|
-
const
|
|
470
|
+
const events = await readSessionEvents(persistence, sid);
|
|
328
471
|
coverage.scannedSessions += 1;
|
|
329
472
|
const session = { id: sid, title: "", total: 0, cost: 0, calls: 0, lastActive: 0 };
|
|
330
473
|
const windowSessions = windowAggregates.map(() => ({ id: sid, title: "", total: 0, cost: 0, calls: 0, lastActive: 0 }));
|
|
331
|
-
const
|
|
474
|
+
const sessionVision = { ...emptyVision(), title: "" };
|
|
475
|
+
const windowVisionSessions = windowAggregates.map(() => ({ ...emptyVision(), title: "" }));
|
|
476
|
+
const scanned = addUsageEvent(events, (time, input, output, cache, reasoning, provider, model, _messageId, _turn, images) => {
|
|
332
477
|
if (coverage.earliestAt === null || time < coverage.earliestAt) coverage.earliestAt = time;
|
|
333
478
|
if (coverage.latestAt === null || time > coverage.latestAt) coverage.latestAt = time;
|
|
334
479
|
const d = new Date(time);
|
|
@@ -343,6 +488,29 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
343
488
|
bumpModel(modelTotals, modelDays, modelHours, modelKey, dayKey, hourKey, input, output, cache, cost);
|
|
344
489
|
bumpOverall(overall, input, output, cache, reasoning, cost, cacheSavings);
|
|
345
490
|
bumpSession(session, time, eventTotal, cost);
|
|
491
|
+
if (images.length > 0) {
|
|
492
|
+
let imageTokensSum = 0;
|
|
493
|
+
let imageCostSum = 0;
|
|
494
|
+
let imageBytesSum = 0;
|
|
495
|
+
for (const image of images) {
|
|
496
|
+
const width = typeof image.width === "number" ? image.width : 0;
|
|
497
|
+
const height = typeof image.height === "number" ? image.height : 0;
|
|
498
|
+
const tokens = estimateImageTokens(width, height);
|
|
499
|
+
imageTokensSum += tokens;
|
|
500
|
+
imageCostSum += costOf(time, model, tokens, 0, 0);
|
|
501
|
+
imageBytesSum += typeof image.bytes === "number" ? image.bytes : 0;
|
|
502
|
+
}
|
|
503
|
+
bumpVisionInto(vision, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
504
|
+
bumpVisionMap(visionDays, dayKey, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
505
|
+
bumpVisionInto(sessionVision, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
506
|
+
for (let i = 0; i < windowAggregates.length; i++) {
|
|
507
|
+
const aggregate = windowAggregates[i];
|
|
508
|
+
if (dayKey < aggregate.startKey || dayKey > aggregate.endKey) continue;
|
|
509
|
+
bumpVisionInto(aggregate.vision, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
510
|
+
bumpVisionMap(aggregate.visionDays, dayKey, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
511
|
+
bumpVisionInto(windowVisionSessions[i], images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
346
514
|
for (let i = 0; i < windowAggregates.length; i++) {
|
|
347
515
|
const aggregate = windowAggregates[i];
|
|
348
516
|
if (dayKey < aggregate.startKey || dayKey > aggregate.endKey) continue;
|
|
@@ -372,6 +540,16 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
372
540
|
windowAggregates[i].sessions.push(windowSession);
|
|
373
541
|
}
|
|
374
542
|
}
|
|
543
|
+
if (sessionVision.images > 0) {
|
|
544
|
+
sessionVision.title = titleOf(events, sid);
|
|
545
|
+
visionSessions.set(sid, sessionVision);
|
|
546
|
+
for (let i = 0; i < windowAggregates.length; i++) {
|
|
547
|
+
const windowVision = windowVisionSessions[i];
|
|
548
|
+
if (windowVision.images === 0) continue;
|
|
549
|
+
windowVision.title = sessionVision.title;
|
|
550
|
+
windowAggregates[i].visionSessions.set(sid, windowVision);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
375
553
|
} catch {
|
|
376
554
|
coverage.failedSessions += 1;
|
|
377
555
|
}
|
|
@@ -427,6 +605,22 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
427
605
|
const b = dayMap.get(key) ?? emptyBucket();
|
|
428
606
|
heatmap.push({ date: key, total: b.total, cost: b.cost, calls: b.calls });
|
|
429
607
|
}
|
|
608
|
+
const makeVisionDaily = (map, count) => {
|
|
609
|
+
const result = [];
|
|
610
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
611
|
+
const key = dayKeyOf(dateBefore(i));
|
|
612
|
+
const b = map.get(key) ?? emptyVision();
|
|
613
|
+
result.push({ date: key, images: b.images, imageTokens: b.imageTokens, cost: b.cost });
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
};
|
|
617
|
+
const makeVisionSessions = (map) => [...map.entries()].map(([id, acc]) => ({
|
|
618
|
+
id,
|
|
619
|
+
title: acc.title !== "" ? acc.title : `\u4F1A\u8BDD ${id.slice(0, 8)}`,
|
|
620
|
+
images: acc.images,
|
|
621
|
+
imageTokens: acc.imageTokens,
|
|
622
|
+
cost: acc.cost
|
|
623
|
+
})).sort((a, b) => b.images - a.images).slice(0, SESSION_TOP_N);
|
|
430
624
|
const models = makeModels(modelTotals, modelDays, modelHours, 30);
|
|
431
625
|
const windows = windowAggregates.map((aggregate) => ({
|
|
432
626
|
days: aggregate.days,
|
|
@@ -435,7 +629,10 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
435
629
|
totals: { ...aggregate.overall },
|
|
436
630
|
models: makeModels(aggregate.modelTotals, aggregate.modelDays, aggregate.modelHours, aggregate.days),
|
|
437
631
|
sessions: [...aggregate.sessions].sort((a, b) => b.cost - a.cost).slice(0, SESSION_TOP_N),
|
|
438
|
-
sessionCount: aggregate.sessions.length
|
|
632
|
+
sessionCount: aggregate.sessions.length,
|
|
633
|
+
vision: { ...aggregate.vision },
|
|
634
|
+
visionDaily: makeVisionDaily(aggregate.visionDays, aggregate.days),
|
|
635
|
+
visionSessions: makeVisionSessions(aggregate.visionSessions)
|
|
439
636
|
}));
|
|
440
637
|
return {
|
|
441
638
|
ok: true,
|
|
@@ -451,6 +648,9 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
451
648
|
sessionCount: sessions.length,
|
|
452
649
|
coverage,
|
|
453
650
|
windows,
|
|
651
|
+
vision: { ...vision },
|
|
652
|
+
visionDaily: makeVisionDaily(visionDays, 30),
|
|
653
|
+
visionSessions: makeVisionSessions(visionSessions),
|
|
454
654
|
totals: {
|
|
455
655
|
input: overall.input,
|
|
456
656
|
output: overall.output,
|
|
@@ -470,8 +670,7 @@ async function fetchSessionUsage(persistence, sessionId) {
|
|
|
470
670
|
if (!isValidSessionId(sessionId)) return { ok: false, error: "\u4F1A\u8BDD id \u683C\u5F0F\u4E0D\u5408\u6CD5" };
|
|
471
671
|
let events;
|
|
472
672
|
try {
|
|
473
|
-
|
|
474
|
-
events = result.events;
|
|
673
|
+
events = await readSessionEvents(persistence, sessionId);
|
|
475
674
|
} catch (err) {
|
|
476
675
|
return { ok: false, error: `\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A${errorMessage(err)}` };
|
|
477
676
|
}
|
|
@@ -545,7 +744,7 @@ var inject = ["webServer", "credentials", "sessionPersistence"];
|
|
|
545
744
|
var BALANCE_TTL_MS = 6e4;
|
|
546
745
|
var USAGE_TTL_MS = 5 * 6e4;
|
|
547
746
|
function apply(ctx) {
|
|
548
|
-
const balanceMemo = memoize(BALANCE_TTL_MS, () => fetchBalance(ctx.credentials));
|
|
747
|
+
const balanceMemo = memoize(BALANCE_TTL_MS, () => fetchBalance(ctx.credentials, ctx.get("deepseekAccount")));
|
|
549
748
|
const usageMemo = memoize(USAGE_TTL_MS, () => fetchUsage(ctx.sessionPersistence));
|
|
550
749
|
ctx.effect(() => ctx.webServer.register({
|
|
551
750
|
kind: "prefix",
|