@dungsil/dsh-plugin-ui-usage 0.0.0 → 0.1.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 ADDED
@@ -0,0 +1,20 @@
1
+ # Usage Dashboard · 사용량 대시보드
2
+
3
+ ![사용량 대시보드의 라이트 모드와 다크 모드를 꼭짓점 대각선으로 나눈 미리보기](./assets/light-dark-preview.webp)
4
+
5
+ DSH Web 사이드바에서 세션에 기록된 토큰 사용량, 일별 추이와 제공자·모델별 사용량을 확인합니다. 청구서나 제공자 계정의 잔액 조회 화면은 아닙니다.
6
+
7
+ 위 이미지는 최신 빌드의 실제 플러그인 컴포넌트에 예시 사용량 응답과 공식 DSH 테마·UI primitives를 제공한 독립 브라우저 화면을 각각 캡처해 합성했습니다. 실제 DSH 세션 화면은 아니며, 모델·날짜·수치는 모두 예시입니다.
8
+
9
+ ## 사용 방법
10
+
11
+ 1. DSH 플러그인 설치 화면에서 `@dungsil/dsh-plugin-ui-usage`를 설치하고 활성화합니다.
12
+ 2. 사이드바의 **사용량**을 엽니다. **최근 7일**, **최근 30일**, **전체 기간** 중 조회 범위를 선택합니다.
13
+ 3. 일별 막대를 눌러 해당 날짜의 토큰 수를 확인합니다. 최신 기록을 다시 읽으려면 **새로고침**을 누릅니다.
14
+
15
+ ## 집계 범위와 호환성
16
+
17
+ - DSH `0.2.1-rc.5` 이상 `0.3.0` 미만의 Web UI를 지원합니다. 한국어·영어·중국어(간체)와 DSH 테마를 따릅니다.
18
+ - 세션에 기록된 호출만 집계하며 하위 에이전트와 재시도를 포함합니다. 분기에서 상속된 기록은 한 번만 셉니다. 삭제한 세션과 제목 생성 등 세션 밖 호출은 제외합니다.
19
+ - 입력은 캐시 읽기·쓰기를 제외하고, 추론 토큰은 출력에 포함합니다. 제공자가 전체 토큰 수를 보고하면 해당 값을 사용합니다. 토큰 수 미보고 호출이나 읽지 못한 세션은 별도로 알립니다.
20
+ - 날짜는 브라우저 시간대를 사용합니다. 전체 기간을 선택해도 일별 차트는 최근 30일을 표시합니다. 자동 실시간 갱신 대신 새로고침으로 기록을 다시 조회합니다.
Binary file
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 _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
5
4
  let react_jsx_runtime = require("react/jsx-runtime");
5
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
6
6
  // ../../tools/ui/src/client.ts
7
7
  function useLocaleSnapshot(locale) {
8
8
  const subscribe = (0, react.useCallback)((listener) => locale.subscribe(listener), [locale]);
@@ -25,6 +25,7 @@ const copy = {
25
25
  month: "Last 30 days",
26
26
  all: "All time",
27
27
  refresh: "Refresh",
28
+ refreshing: "Refreshing…",
28
29
  loading: "Loading usage…",
29
30
  error: "Could not load usage. Try refreshing.",
30
31
  empty: "No recorded model calls in this period.",
@@ -52,6 +53,7 @@ const copy = {
52
53
  month: "최근 30일",
53
54
  all: "전체 기간",
54
55
  refresh: "새로고침",
56
+ refreshing: "새로고침 중…",
55
57
  loading: "사용량을 불러오는 중입니다…",
56
58
  error: "사용량을 불러오지 못했습니다. 새로고침해 주세요.",
57
59
  empty: "이 기간에 기록된 모델 호출이 없습니다.",
@@ -79,6 +81,7 @@ const copy = {
79
81
  month: "最近 30 天",
80
82
  all: "全部时间",
81
83
  refresh: "刷新",
84
+ refreshing: "正在刷新…",
82
85
  loading: "正在加载用量…",
83
86
  error: "无法加载用量,请刷新重试。",
84
87
  empty: "此时间范围内没有记录的模型调用。",
@@ -111,6 +114,7 @@ const inject = [
111
114
  "locale",
112
115
  "slots"
113
116
  ];
117
+ const cachedViews = /* @__PURE__ */ new WeakMap();
114
118
  const styles = `
115
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)); }
116
120
  .dsh-usage-page > .dsh-usage { max-width: 960px; margin: 0 auto; }
@@ -118,6 +122,10 @@ const styles = `
118
122
  .dsh-usage { container-type: inline-size; overflow-wrap: anywhere; color: var(--dsw-alias-label-primary); font: 14px/22px var(--dsw-font-family, system-ui); }
119
123
  .dsh-usage-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
120
124
  .dsh-usage-status { display: flex; align-items: center; gap: 6px; }
125
+ @keyframes dsh-usage-spin { to { transform: rotate(360deg); } }
126
+ @media (prefers-reduced-motion: no-preference) {
127
+ .dsh-usage-refresh[aria-busy=true] svg { animation: dsh-usage-spin .8s linear infinite; }
128
+ }
121
129
  .dsh-usage-muted, .dsh-usage dt { color: var(--dsw-alias-label-secondary); font-size: 12px; }
122
130
  .dsh-usage h3 { margin: 24px 0 12px; font-size: 14px; font-weight: 500; }
123
131
  .dsh-usage dl { margin: 0; }
@@ -155,27 +163,32 @@ function Usage({ ctx }) {
155
163
  const [period, setPeriod] = (0, react.useState)(7);
156
164
  const [periodOpen, setPeriodOpen] = (0, react.useState)(false);
157
165
  const [refresh, setRefresh] = (0, react.useState)(0);
158
- const [view, setView] = (0, react.useState)();
166
+ const connection = ctx.get("connection");
167
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
168
+ const cacheKey = JSON.stringify([period, timeZone]);
169
+ let cache = cachedViews.get(connection);
170
+ if (!cache) cachedViews.set(connection, cache = /* @__PURE__ */ new Map());
171
+ const [view, setView] = (0, react.useState)(() => cache.get(cacheKey));
159
172
  const [busy, setBusy] = (0, react.useState)(true);
160
173
  const [error, setError] = (0, react.useState)(false);
161
- const [selectedDay, setSelectedDay] = (0, react.useState)("");
162
- const connection = ctx.get("connection");
174
+ const [selectedDay, setSelectedDay] = (0, react.useState)(() => view?.days.at(-1)?.day ?? "");
163
175
  (0, react.useEffect)(() => {
164
176
  const controller = new AbortController();
165
177
  setBusy(true);
166
178
  setError(false);
167
- setView(void 0);
179
+ setView(cache.get(cacheKey));
168
180
  (async () => {
169
181
  try {
170
182
  const result = await connection.rpc.call(channel, method, {
171
183
  days: period,
172
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
184
+ timeZone
173
185
  }, controller.signal);
174
186
  if (controller.signal.aborted) return;
175
187
  if (!result.ok) throw new Error("Usage unavailable");
176
188
  const next = result.value;
189
+ cache.set(cacheKey, next);
177
190
  setView(next);
178
- setSelectedDay(next.days.at(-1)?.day ?? "");
191
+ setSelectedDay((day) => next.days.some((entry) => entry.day === day) ? day : next.days.at(-1)?.day ?? "");
179
192
  } catch {
180
193
  if (!controller.signal.aborted) setError(true);
181
194
  } finally {
@@ -186,6 +199,9 @@ function Usage({ ctx }) {
186
199
  }, [
187
200
  connection,
188
201
  period,
202
+ timeZone,
203
+ cache,
204
+ cacheKey,
189
205
  refresh
190
206
  ]);
191
207
  const metrics = [
@@ -243,13 +259,15 @@ function Usage({ ctx }) {
243
259
  })
244
260
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
245
261
  variant: "outline",
262
+ className: "dsh-usage-refresh",
263
+ "aria-busy": busy,
246
264
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {}),
247
265
  disabled: busy,
248
266
  onClick: () => setRefresh((value) => value + 1),
249
- children: text.refresh
267
+ children: busy ? text.refreshing : text.refresh
250
268
  })]
251
269
  }),
252
- busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
270
+ busy && !view && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
253
271
  role: "status",
254
272
  className: "dsh-usage-status",
255
273
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }), text.loading]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungsil/dsh-plugin-ui-usage",
3
- "version": "0.0.0",
3
+ "version": "0.1.1",
4
4
  "description": "Recorded session token usage dashboard for DeepSeek Harness.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,6 +11,7 @@
11
11
  "files": [
12
12
  "dist",
13
13
  "locale",
14
+ "assets",
14
15
  "cordis.patch.yml"
15
16
  ],
16
17
  "type": "module",
@@ -28,7 +29,6 @@
28
29
  "@deepseek-ai/dsh-client-connection": "0.2.1-rc.5",
29
30
  "@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
30
31
  "@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.5",
31
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5",
32
32
  "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
33
33
  "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.5",
34
34
  "@deepseek-ai/dsh-credentials-local": "0.2.1-rc.5",
@@ -38,7 +38,9 @@
38
38
  "@dungsil/dsh-tsdown": "0.0.0",
39
39
  "@dungsil/dsh-ui": "0.0.0",
40
40
  "@types/react": "18.3.31",
41
+ "happy-dom": "21.0.4",
41
42
  "react": "18.3.1",
43
+ "react-dom": "18.3.1",
42
44
  "tsdown": "0.23.4"
43
45
  },
44
46
  "peerDependencies": {