@dungsil/dsh-plugin-ui-usage 0.2.0 → 0.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.
Files changed (3) hide show
  1. package/dist/client.js +146 -27
  2. package/dist/index.js +15 -0
  3. package/package.json +15 -15
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)); }
@@ -165,7 +246,6 @@ function Usage({ ctx }) {
165
246
  maximumFractionDigits: 1
166
247
  });
167
248
  const [period, setPeriod] = (0, react.useState)(7);
168
- const [periodOpen, setPeriodOpen] = (0, react.useState)(false);
169
249
  const [refresh, setRefresh] = (0, react.useState)(0);
170
250
  const refreshRequested = (0, react.useRef)(false);
171
251
  const connection = ctx.get("connection");
@@ -251,24 +331,12 @@ function Usage({ ctx }) {
251
331
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: text.nav }),
252
332
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
253
333
  className: "dsh-usage-toolbar",
254
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
255
- open: periodOpen,
256
- onClose: () => setPeriodOpen(false),
257
- selectedId: String(period),
334
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonPicker, {
335
+ variant: "outline",
336
+ label: text.period,
337
+ value: String(period),
258
338
  items: periods,
259
- onSelect: (id) => {
260
- setPeriodOpen(false);
261
- setPeriod(Number(id));
262
- },
263
- portal: true,
264
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
265
- variant: "outline",
266
- "aria-label": text.period,
267
- "aria-haspopup": "menu",
268
- "aria-expanded": periodOpen,
269
- onClick: () => setPeriodOpen((open) => !open),
270
- children: [periods.find(({ id }) => id === String(period)).label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})]
271
- })
339
+ onChange: (id) => setPeriod(Number(id))
272
340
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
273
341
  variant: "outline",
274
342
  className: "dsh-usage-refresh",
@@ -359,6 +427,60 @@ function Usage({ ctx }) {
359
427
  title: selected && number.format(selected.total),
360
428
  children: selected && `${date(selected.day)} · ${text.total}: ${tokens.format(selected.total)}`
361
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])))
483
+ }),
362
484
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: text.models }),
363
485
  view.models.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
364
486
  className: "dsh-usage-model",
@@ -413,10 +535,7 @@ function apply(ctx) {
413
535
  name: "main",
414
536
  key: name,
415
537
  inject: () => ({ ctx })
416
- }, ({ ctx }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
417
- className: "dsh-usage-page",
418
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Usage, { ctx })
419
- })));
538
+ }, ({ ctx }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginPage, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Usage, { ctx }) })));
420
539
  ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
421
540
  name: "sidebar.panellist",
422
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungsil/dsh-plugin-ui-usage",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Recorded session token usage dashboard for DeepSeek Harness.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,15 +25,15 @@
25
25
  "access": "public"
26
26
  },
27
27
  "devDependencies": {
28
- "@deepseek-ai/cordis": "4.0.5",
29
- "@deepseek-ai/dsh-client-connection": "0.2.1-rc.5",
30
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
31
- "@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.5",
32
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
33
- "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.5",
34
- "@deepseek-ai/dsh-credentials-local": "0.2.1-rc.5",
35
- "@deepseek-ai/dsh-llm": "0.2.1-rc.5",
36
- "@deepseek-ai/dsh-session-query": "0.2.1-rc.5",
28
+ "@deepseek-ai/cordis": "4.1.0",
29
+ "@deepseek-ai/dsh-client-connection": "0.2.2-rc.1",
30
+ "@deepseek-ai/dsh-client-locale": "0.2.2-rc.1",
31
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.2-rc.1",
32
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.2-rc.1",
33
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.2-rc.1",
34
+ "@deepseek-ai/dsh-credentials-local": "0.2.2-rc.1",
35
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
36
+ "@deepseek-ai/dsh-session-query": "0.2.2-rc.1",
37
37
  "@dungsil/dsh-tsconfig": "0.0.0",
38
38
  "@dungsil/dsh-tsdown": "0.0.0",
39
39
  "@dungsil/dsh-ui": "0.0.0",
@@ -44,13 +44,13 @@
44
44
  "tsdown": "0.23.4"
45
45
  },
46
46
  "peerDependencies": {
47
- "@deepseek-ai/dsh-client-connection": "^0.2.1-rc.5",
48
- "@deepseek-ai/dsh-client-ui-primitives": "^0.2.1-rc.5",
49
- "@deepseek-ai/dsh-llm": "^0.2.1-rc.5",
50
- "@deepseek-ai/dsh-session-query": "^0.2.1-rc.5"
47
+ "@deepseek-ai/dsh-client-connection": "^0.2.1-rc.5 || ^0.2.2-rc.1",
48
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.2.1-rc.5 || ^0.2.2-rc.1",
49
+ "@deepseek-ai/dsh-llm": "^0.2.1-rc.5 || ^0.2.2-rc.1",
50
+ "@deepseek-ai/dsh-session-query": "^0.2.1-rc.5 || ^0.2.2-rc.1"
51
51
  },
52
52
  "engines": {
53
- "dsh": "^0.2.1-rc.5",
53
+ "dsh": "^0.2.1-rc.5 || ^0.2.2-rc.1",
54
54
  "node": ">=24.9.0"
55
55
  },
56
56
  "dsh": {