@dungsil/dsh-plugin-ui-usage 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +181 -36
- package/dist/index.js +15 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({ id: "@dungsil/dsh-plugin-ui-usage", factory(require) { const module = { exports: {} }; const exports = module.exports;
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
let react = require("react");
|
|
4
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
4
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
6
|
// ../../tools/ui/src/client.ts
|
|
7
7
|
function useLocaleSnapshot(locale) {
|
|
8
8
|
const subscribe = (0, react.useCallback)((listener) => locale.subscribe(listener), [locale]);
|
|
@@ -16,6 +16,53 @@ function installStyles(css) {
|
|
|
16
16
|
document.head.append(style);
|
|
17
17
|
return () => style.remove();
|
|
18
18
|
}
|
|
19
|
+
// ../../tools/ui/src/button-picker.tsx
|
|
20
|
+
function ButtonPicker({ label, value, items, onChange, disabled = false, menuClassName, compact, ...buttonProps }) {
|
|
21
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
22
|
+
(0, react.useEffect)(() => {
|
|
23
|
+
if (disabled) setOpen(false);
|
|
24
|
+
}, [disabled]);
|
|
25
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
26
|
+
open: open && !disabled,
|
|
27
|
+
portal: true,
|
|
28
|
+
compact,
|
|
29
|
+
className: menuClassName,
|
|
30
|
+
selectedId: value,
|
|
31
|
+
items,
|
|
32
|
+
onClose: () => setOpen(false),
|
|
33
|
+
onSelect: (id) => {
|
|
34
|
+
setOpen(false);
|
|
35
|
+
const item = items.find((item) => item.id === id && !item.disabled);
|
|
36
|
+
if (!disabled && item) onChange(item.id);
|
|
37
|
+
},
|
|
38
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
39
|
+
...buttonProps,
|
|
40
|
+
type: "button",
|
|
41
|
+
"aria-label": label,
|
|
42
|
+
"aria-haspopup": "menu",
|
|
43
|
+
"aria-expanded": open && !disabled,
|
|
44
|
+
disabled,
|
|
45
|
+
onClick: () => setOpen((current) => !current),
|
|
46
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: items.find((item) => item.id === value)?.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { "aria-hidden": "true" })]
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// ../../tools/ui/src/page.tsx
|
|
51
|
+
function PluginPage({ children, className = "", contentClassName = "" }) {
|
|
52
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
53
|
+
className: `dsh-plugin-page ${className}`,
|
|
54
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
55
|
+
className: `dsh-plugin-page-content ${contentClassName}`,
|
|
56
|
+
children
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// ../../tools/ui/src/page-styles.ts
|
|
61
|
+
const pageStyles = `
|
|
62
|
+
.dsh-plugin-page { box-sizing: border-box; height: 100%; min-height: 0; overflow: auto; padding: 24px; padding-inline-start: max(24px, var(--dsh-frame-leading-clearance, 0px)); }
|
|
63
|
+
.dsh-plugin-page-content { max-width: 960px; margin: 0 auto; }
|
|
64
|
+
.dsh-plugin-page h1 { margin: 0 0 24px; font-size: 20px; line-height: 28px; font-weight: 500; }
|
|
65
|
+
`;
|
|
19
66
|
// src/copy.ts
|
|
20
67
|
const copy = {
|
|
21
68
|
en: {
|
|
@@ -39,6 +86,16 @@ const copy = {
|
|
|
39
86
|
daily: "Daily tokens",
|
|
40
87
|
recent: "Daily tokens · last 30 days",
|
|
41
88
|
models: "By provider / model",
|
|
89
|
+
requests: "Model call log",
|
|
90
|
+
requestScope: "Most recent 100 settled calls in this period, newest first. Times are settlement times. Retries are separate calls. Request and response bodies are not shown.",
|
|
91
|
+
time: "Recorded at",
|
|
92
|
+
session: "Session",
|
|
93
|
+
status: "Status",
|
|
94
|
+
success: "Succeeded",
|
|
95
|
+
failure: "Failed",
|
|
96
|
+
interrupted: "Interrupted",
|
|
97
|
+
unknownStatus: "Unconfirmed",
|
|
98
|
+
notReported: "Not reported",
|
|
42
99
|
unknown: "Unknown",
|
|
43
100
|
updated: "Updated",
|
|
44
101
|
unreported: "Calls without token counts",
|
|
@@ -67,6 +124,16 @@ const copy = {
|
|
|
67
124
|
daily: "일별 토큰",
|
|
68
125
|
recent: "일별 토큰 · 최근 30일",
|
|
69
126
|
models: "제공자 / 모델별",
|
|
127
|
+
requests: "모델 호출 로그",
|
|
128
|
+
requestScope: "선택한 기간에 기록이 확정된 최근 100건을 최신순으로 표시합니다. 시각은 기록 확정 시각이며, 재시도는 각각 표시합니다. 요청·응답 본문은 표시하지 않습니다.",
|
|
129
|
+
time: "기록 시각",
|
|
130
|
+
session: "세션",
|
|
131
|
+
status: "상태",
|
|
132
|
+
success: "성공",
|
|
133
|
+
failure: "실패",
|
|
134
|
+
interrupted: "중단",
|
|
135
|
+
unknownStatus: "미확정",
|
|
136
|
+
notReported: "미보고",
|
|
70
137
|
unknown: "알 수 없음",
|
|
71
138
|
updated: "갱신 시각",
|
|
72
139
|
unreported: "토큰 수가 보고되지 않은 호출",
|
|
@@ -95,6 +162,16 @@ const copy = {
|
|
|
95
162
|
daily: "每日令牌数",
|
|
96
163
|
recent: "每日令牌数 · 最近 30 天",
|
|
97
164
|
models: "按提供方 / 模型",
|
|
165
|
+
requests: "模型调用日志",
|
|
166
|
+
requestScope: "按时间倒序显示所选时间范围内最近 100 次已结算调用。时间为记录结算时间,重试单独列出。不显示请求和响应正文。",
|
|
167
|
+
time: "记录时间",
|
|
168
|
+
session: "会话",
|
|
169
|
+
status: "状态",
|
|
170
|
+
success: "成功",
|
|
171
|
+
failure: "失败",
|
|
172
|
+
interrupted: "已中断",
|
|
173
|
+
unknownStatus: "未确认",
|
|
174
|
+
notReported: "未报告",
|
|
98
175
|
unknown: "未知",
|
|
99
176
|
updated: "更新时间",
|
|
100
177
|
unreported: "未报告令牌数的调用",
|
|
@@ -115,10 +192,7 @@ const inject = [
|
|
|
115
192
|
"slots"
|
|
116
193
|
];
|
|
117
194
|
const cachedViews = /* @__PURE__ */ new WeakMap();
|
|
118
|
-
const styles =
|
|
119
|
-
.dsh-usage-page { box-sizing: border-box; height: 100%; overflow: auto; padding: 24px; padding-inline-start: max(24px, var(--dsh-frame-leading-clearance, 0px)); }
|
|
120
|
-
.dsh-usage-page > .dsh-usage { max-width: 960px; margin: 0 auto; }
|
|
121
|
-
.dsh-usage h1 { margin: 0 0 24px; font-size: 20px; line-height: 28px; font-weight: 500; }
|
|
195
|
+
const styles = `${pageStyles}
|
|
122
196
|
.dsh-usage { container-type: inline-size; overflow-wrap: anywhere; color: var(--dsw-alias-label-primary); font: 14px/22px var(--dsw-font-family, system-ui); }
|
|
123
197
|
.dsh-usage-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
124
198
|
.dsh-usage-status { display: flex; align-items: center; gap: 6px; }
|
|
@@ -146,6 +220,13 @@ const styles = `
|
|
|
146
220
|
.dsh-usage-model-heading > span:first-child { min-width: 0; }
|
|
147
221
|
.dsh-usage-model-heading > span:last-child { flex: none; font-variant-numeric: tabular-nums; }
|
|
148
222
|
.dsh-usage-model dl { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin-top: 12px; font-size: 12px; }
|
|
223
|
+
.dsh-usage-calls { list-style: none; margin: 0; padding: 0 12px; max-height: 520px; overflow: auto; border: .5px solid var(--dsw-alias-border-l2); border-radius: 8px; }
|
|
224
|
+
.dsh-usage-calls:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
|
|
225
|
+
.dsh-usage-calls .dsh-usage-model:last-child { border-bottom: none; }
|
|
226
|
+
.dsh-usage-calls dl { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
227
|
+
.dsh-usage-call-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 6px; }
|
|
228
|
+
.dsh-usage-call-status { flex: none; font-size: 12px; }
|
|
229
|
+
.dsh-usage-call-status[data-status=failure] { color: var(--dsw-alias-state-error-primary, #b54424); }
|
|
149
230
|
.dsh-usage [role=alert] { color: var(--dsw-alias-state-error-primary, #b54424); }
|
|
150
231
|
@container (max-width: 480px) {
|
|
151
232
|
.dsh-usage-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
@@ -160,23 +241,34 @@ function Usage({ ctx }) {
|
|
|
160
241
|
const locale = useLocaleSnapshot(ctx.locale);
|
|
161
242
|
const text = localizedCopy(locale.active);
|
|
162
243
|
const number = new Intl.NumberFormat(locale.active);
|
|
244
|
+
const tokens = new Intl.NumberFormat(locale.active, {
|
|
245
|
+
notation: "compact",
|
|
246
|
+
maximumFractionDigits: 1
|
|
247
|
+
});
|
|
163
248
|
const [period, setPeriod] = (0, react.useState)(7);
|
|
164
|
-
const [periodOpen, setPeriodOpen] = (0, react.useState)(false);
|
|
165
249
|
const [refresh, setRefresh] = (0, react.useState)(0);
|
|
250
|
+
const refreshRequested = (0, react.useRef)(false);
|
|
166
251
|
const connection = ctx.get("connection");
|
|
167
252
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
168
253
|
const cacheKey = JSON.stringify([period, timeZone]);
|
|
169
254
|
let cache = cachedViews.get(connection);
|
|
170
255
|
if (!cache) cachedViews.set(connection, cache = /* @__PURE__ */ new Map());
|
|
171
256
|
const [view, setView] = (0, react.useState)(() => cache.get(cacheKey));
|
|
172
|
-
const [busy, setBusy] = (0, react.useState)(
|
|
257
|
+
const [busy, setBusy] = (0, react.useState)(() => !cache.has(cacheKey));
|
|
173
258
|
const [error, setError] = (0, react.useState)(false);
|
|
174
259
|
const [selectedDay, setSelectedDay] = (0, react.useState)(() => view?.days.at(-1)?.day ?? "");
|
|
175
260
|
(0, react.useEffect)(() => {
|
|
261
|
+
const cached = cache.get(cacheKey);
|
|
262
|
+
setError(false);
|
|
263
|
+
setView(cached);
|
|
264
|
+
if (cached && !refreshRequested.current) {
|
|
265
|
+
setBusy(false);
|
|
266
|
+
setSelectedDay((day) => cached.days.some((entry) => entry.day === day) ? day : cached.days.at(-1)?.day ?? "");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
refreshRequested.current = false;
|
|
176
270
|
const controller = new AbortController();
|
|
177
271
|
setBusy(true);
|
|
178
|
-
setError(false);
|
|
179
|
-
setView(cache.get(cacheKey));
|
|
180
272
|
(async () => {
|
|
181
273
|
try {
|
|
182
274
|
const result = await connection.rpc.call(channel, method, {
|
|
@@ -239,31 +331,22 @@ function Usage({ ctx }) {
|
|
|
239
331
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: text.nav }),
|
|
240
332
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
241
333
|
className: "dsh-usage-toolbar",
|
|
242
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
334
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonPicker, {
|
|
335
|
+
variant: "outline",
|
|
336
|
+
label: text.period,
|
|
337
|
+
value: String(period),
|
|
246
338
|
items: periods,
|
|
247
|
-
|
|
248
|
-
setPeriodOpen(false);
|
|
249
|
-
setPeriod(Number(id));
|
|
250
|
-
},
|
|
251
|
-
portal: true,
|
|
252
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
253
|
-
variant: "outline",
|
|
254
|
-
"aria-label": text.period,
|
|
255
|
-
"aria-haspopup": "menu",
|
|
256
|
-
"aria-expanded": periodOpen,
|
|
257
|
-
onClick: () => setPeriodOpen((open) => !open),
|
|
258
|
-
children: [periods.find(({ id }) => id === String(period)).label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})]
|
|
259
|
-
})
|
|
339
|
+
onChange: (id) => setPeriod(Number(id))
|
|
260
340
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
261
341
|
variant: "outline",
|
|
262
342
|
className: "dsh-usage-refresh",
|
|
263
343
|
"aria-busy": busy,
|
|
264
344
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {}),
|
|
265
345
|
disabled: busy,
|
|
266
|
-
onClick: () =>
|
|
346
|
+
onClick: () => {
|
|
347
|
+
refreshRequested.current = true;
|
|
348
|
+
setRefresh((value) => value + 1);
|
|
349
|
+
},
|
|
267
350
|
children: busy ? text.refreshing : text.refresh
|
|
268
351
|
})]
|
|
269
352
|
}),
|
|
@@ -281,8 +364,12 @@ function Usage({ ctx }) {
|
|
|
281
364
|
className: "dsh-usage-summary",
|
|
282
365
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text.total }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
283
366
|
className: "dsh-usage-total",
|
|
284
|
-
|
|
285
|
-
|
|
367
|
+
title: number.format(view.totals.total),
|
|
368
|
+
children: tokens.format(view.totals.total)
|
|
369
|
+
})] }), metrics.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text[key] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
370
|
+
title: number.format(view.totals[key]),
|
|
371
|
+
children: tokens.format(view.totals[key])
|
|
372
|
+
})] }, key))]
|
|
286
373
|
}),
|
|
287
374
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
288
375
|
className: "dsh-usage-meta dsh-usage-muted",
|
|
@@ -337,7 +424,62 @@ function Usage({ ctx }) {
|
|
|
337
424
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
338
425
|
className: "dsh-usage-detail",
|
|
339
426
|
"aria-live": "polite",
|
|
340
|
-
|
|
427
|
+
title: selected && number.format(selected.total),
|
|
428
|
+
children: selected && `${date(selected.day)} · ${text.total}: ${tokens.format(selected.total)}`
|
|
429
|
+
}),
|
|
430
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: text.requests }),
|
|
431
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
432
|
+
className: "dsh-usage-muted",
|
|
433
|
+
children: text.requestScope
|
|
434
|
+
}),
|
|
435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
436
|
+
className: "dsh-usage-calls",
|
|
437
|
+
tabIndex: 0,
|
|
438
|
+
"aria-label": text.requests,
|
|
439
|
+
children: view.calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
440
|
+
className: "dsh-usage-model",
|
|
441
|
+
children: [
|
|
442
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
443
|
+
className: "dsh-usage-call-meta",
|
|
444
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("time", {
|
|
445
|
+
className: "dsh-usage-muted",
|
|
446
|
+
dateTime: new Date(call.time).toISOString(),
|
|
447
|
+
children: [
|
|
448
|
+
text.time,
|
|
449
|
+
":",
|
|
450
|
+
" ",
|
|
451
|
+
new Intl.DateTimeFormat(locale.active, {
|
|
452
|
+
dateStyle: "medium",
|
|
453
|
+
timeStyle: "medium",
|
|
454
|
+
timeZone: view.timeZone
|
|
455
|
+
}).format(call.time)
|
|
456
|
+
]
|
|
457
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
458
|
+
className: "dsh-usage-call-status",
|
|
459
|
+
"data-status": call.status,
|
|
460
|
+
children: [
|
|
461
|
+
text.status,
|
|
462
|
+
": ",
|
|
463
|
+
text[call.status]
|
|
464
|
+
]
|
|
465
|
+
})]
|
|
466
|
+
}),
|
|
467
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
468
|
+
call.provider || text.unknown,
|
|
469
|
+
" / ",
|
|
470
|
+
call.model || text.unknown
|
|
471
|
+
] }),
|
|
472
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
473
|
+
className: "dsh-usage-muted",
|
|
474
|
+
children: [
|
|
475
|
+
text.session,
|
|
476
|
+
": ",
|
|
477
|
+
call.session
|
|
478
|
+
]
|
|
479
|
+
}),
|
|
480
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", { children: ["total", ...metrics].map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text[key] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: call.unreported ? text.notReported : number.format(call[key]) })] }, key)) })
|
|
481
|
+
]
|
|
482
|
+
}, JSON.stringify([call.session, call.seq])))
|
|
341
483
|
}),
|
|
342
484
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: text.models }),
|
|
343
485
|
view.models.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -348,8 +490,14 @@ function Usage({ ctx }) {
|
|
|
348
490
|
row.provider || text.unknown,
|
|
349
491
|
" / ",
|
|
350
492
|
row.model || text.unknown
|
|
351
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
352
|
-
|
|
493
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
494
|
+
title: number.format(row.total),
|
|
495
|
+
children: tokens.format(row.total)
|
|
496
|
+
})]
|
|
497
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", { children: ["calls", ...metrics].map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text[key] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
498
|
+
title: number.format(row[key]),
|
|
499
|
+
children: key === "calls" ? number.format(row[key]) : tokens.format(row[key])
|
|
500
|
+
})] }, key)) })]
|
|
353
501
|
}, JSON.stringify([row.provider, row.model])))
|
|
354
502
|
] }),
|
|
355
503
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
@@ -387,10 +535,7 @@ function apply(ctx) {
|
|
|
387
535
|
name: "main",
|
|
388
536
|
key: name,
|
|
389
537
|
inject: () => ({ ctx })
|
|
390
|
-
}, ({ ctx }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
391
|
-
className: "dsh-usage-page",
|
|
392
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Usage, { ctx })
|
|
393
|
-
})));
|
|
538
|
+
}, ({ ctx }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginPage, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Usage, { ctx }) })));
|
|
394
539
|
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
395
540
|
name: "sidebar.panellist",
|
|
396
541
|
id: name,
|
package/dist/index.js
CHANGED
|
@@ -77,6 +77,7 @@ function apply(ctx) {
|
|
|
77
77
|
const view = {
|
|
78
78
|
totals: empty(),
|
|
79
79
|
models: [],
|
|
80
|
+
calls: [],
|
|
80
81
|
days: [],
|
|
81
82
|
sessions: 0,
|
|
82
83
|
unreadable: 0,
|
|
@@ -121,6 +122,20 @@ function apply(ctx) {
|
|
|
121
122
|
const before = row.total;
|
|
122
123
|
add(row, usage);
|
|
123
124
|
add(view.totals, usage);
|
|
125
|
+
const finish = lastAssistantStreamChunk(event.data.stream, "finish")?.reason.kind;
|
|
126
|
+
const call = {
|
|
127
|
+
...empty(),
|
|
128
|
+
session: record.header.id,
|
|
129
|
+
seq: event.seq,
|
|
130
|
+
time: event.time,
|
|
131
|
+
provider: route.provider,
|
|
132
|
+
model: route.model,
|
|
133
|
+
status: finish === "error" ? "failure" : finish === "aborted" || event.type === "assistant/message" && event.data.interrupted ? "interrupted" : event.type === "assistant/message" ? "success" : "unknownStatus"
|
|
134
|
+
};
|
|
135
|
+
add(call, usage);
|
|
136
|
+
view.calls.push(call);
|
|
137
|
+
view.calls.sort((a, b) => b.time - a.time || a.session.localeCompare(b.session) || b.seq - a.seq);
|
|
138
|
+
if (view.calls.length > 100) view.calls.pop();
|
|
124
139
|
days.set(day, (days.get(day) ?? 0) + row.total - before);
|
|
125
140
|
included = true;
|
|
126
141
|
}
|