@dungsil/dsh-plugin-ui-usage 0.3.1 → 0.4.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 +134 -6
- package/dist/index.js +16 -5
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -71,6 +71,13 @@ const copy = {
|
|
|
71
71
|
week: "Last 7 days",
|
|
72
72
|
month: "Last 30 days",
|
|
73
73
|
all: "All time",
|
|
74
|
+
custom: "Custom range",
|
|
75
|
+
startDate: "Start date",
|
|
76
|
+
endDate: "End date",
|
|
77
|
+
apply: "Apply",
|
|
78
|
+
showing: "Showing",
|
|
79
|
+
rangeHint: "Includes both dates, up to 366 days. Uses your browser time zone.",
|
|
80
|
+
rangeError: "Enter valid dates in order, no more than 366 days apart including both dates.",
|
|
74
81
|
refresh: "Refresh",
|
|
75
82
|
refreshing: "Refreshing…",
|
|
76
83
|
loading: "Loading usage…",
|
|
@@ -109,6 +116,13 @@ const copy = {
|
|
|
109
116
|
week: "최근 7일",
|
|
110
117
|
month: "최근 30일",
|
|
111
118
|
all: "전체 기간",
|
|
119
|
+
custom: "사용자 지정",
|
|
120
|
+
startDate: "시작일",
|
|
121
|
+
endDate: "종료일",
|
|
122
|
+
apply: "적용",
|
|
123
|
+
showing: "조회 중인 기간",
|
|
124
|
+
rangeHint: "시작일과 종료일을 포함해 최대 366일까지 조회합니다. 브라우저 시간대를 사용합니다.",
|
|
125
|
+
rangeError: "시작일과 종료일을 올바르게 입력해 주세요. 종료일은 시작일과 같거나 늦어야 하며, 두 날짜를 포함해 366일 이내여야 합니다.",
|
|
112
126
|
refresh: "새로고침",
|
|
113
127
|
refreshing: "새로고침 중…",
|
|
114
128
|
loading: "사용량을 불러오는 중입니다…",
|
|
@@ -147,6 +161,13 @@ const copy = {
|
|
|
147
161
|
week: "最近 7 天",
|
|
148
162
|
month: "最近 30 天",
|
|
149
163
|
all: "全部时间",
|
|
164
|
+
custom: "自定义范围",
|
|
165
|
+
startDate: "开始日期",
|
|
166
|
+
endDate: "结束日期",
|
|
167
|
+
apply: "应用",
|
|
168
|
+
showing: "当前查询范围",
|
|
169
|
+
rangeHint: "包含起止日期,最多 366 天。使用浏览器时区。",
|
|
170
|
+
rangeError: "请输入有效且顺序正确的日期,包含起止日期的范围不得超过 366 天。",
|
|
150
171
|
refresh: "刷新",
|
|
151
172
|
refreshing: "正在刷新…",
|
|
152
173
|
loading: "正在加载用量…",
|
|
@@ -184,6 +205,16 @@ const localizedCopy = (language) => copy[language.startsWith("ko") ? "ko" : lang
|
|
|
184
205
|
// src/protocol.ts
|
|
185
206
|
const channel = "/api";
|
|
186
207
|
const method = "dungsil-usage/describe";
|
|
208
|
+
function isValidDateRange(startDate, endDate) {
|
|
209
|
+
const parse = (value) => {
|
|
210
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return NaN;
|
|
211
|
+
const time = Date.parse(`${value}T00:00:00Z`);
|
|
212
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value ? time : NaN;
|
|
213
|
+
};
|
|
214
|
+
const start = parse(startDate);
|
|
215
|
+
const inclusiveDays = (parse(endDate) - start) / 864e5 + 1;
|
|
216
|
+
return inclusiveDays >= 1 && inclusiveDays <= 366;
|
|
217
|
+
}
|
|
187
218
|
// src/client.tsx
|
|
188
219
|
const name = "dungsil-ui-usage";
|
|
189
220
|
const inject = [
|
|
@@ -192,10 +223,20 @@ const inject = [
|
|
|
192
223
|
"slots"
|
|
193
224
|
];
|
|
194
225
|
const cachedViews = /* @__PURE__ */ new WeakMap();
|
|
226
|
+
const savedPeriods = /* @__PURE__ */ new WeakMap();
|
|
227
|
+
function localDay(offset = 0) {
|
|
228
|
+
const date = /* @__PURE__ */ new Date();
|
|
229
|
+
date.setDate(date.getDate() + offset);
|
|
230
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
231
|
+
}
|
|
195
232
|
const styles = `${pageStyles}
|
|
196
233
|
.dsh-usage { container-type: inline-size; overflow-wrap: anywhere; color: var(--dsw-alias-label-primary); font: 14px/22px var(--dsw-font-family, system-ui); }
|
|
197
234
|
.dsh-usage-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
198
235
|
.dsh-usage-status { display: flex; align-items: center; gap: 6px; }
|
|
236
|
+
.dsh-usage-range { display: flex; align-items: end; flex-wrap: wrap; gap: 12px; margin-top: 16px; }
|
|
237
|
+
.dsh-usage-range label { display: flex; flex: 1 1 160px; min-width: 0; flex-direction: column; gap: 6px; }
|
|
238
|
+
.dsh-usage-range input { width: 100%; min-width: 0; box-sizing: border-box; color-scheme: light dark; }
|
|
239
|
+
.dsh-usage-range-note { flex-basis: 100%; margin: 0; }
|
|
199
240
|
@keyframes dsh-usage-spin { to { transform: rotate(360deg); } }
|
|
200
241
|
@media (prefers-reduced-motion: no-preference) {
|
|
201
242
|
.dsh-usage-refresh[aria-busy=true] svg { animation: dsh-usage-spin .8s linear infinite; }
|
|
@@ -245,10 +286,19 @@ function Usage({ ctx }) {
|
|
|
245
286
|
notation: "compact",
|
|
246
287
|
maximumFractionDigits: 1
|
|
247
288
|
});
|
|
248
|
-
const
|
|
289
|
+
const connection = ctx.get("connection");
|
|
290
|
+
const [period, setPeriod] = (0, react.useState)(() => savedPeriods.get(connection) ?? { days: 7 });
|
|
291
|
+
const [editingRange, setEditingRange] = (0, react.useState)(() => !("days" in period));
|
|
292
|
+
const [startDate, setStartDate] = (0, react.useState)(() => "startDate" in period ? period.startDate : localDay(-6));
|
|
293
|
+
const [endDate, setEndDate] = (0, react.useState)(() => "endDate" in period ? period.endDate : localDay());
|
|
294
|
+
const [rangeError, setRangeError] = (0, react.useState)(false);
|
|
295
|
+
const rangeId = (0, react.useId)();
|
|
296
|
+
const applyPeriod = (next) => {
|
|
297
|
+
savedPeriods.set(connection, next);
|
|
298
|
+
setPeriod(next);
|
|
299
|
+
};
|
|
249
300
|
const [refresh, setRefresh] = (0, react.useState)(0);
|
|
250
301
|
const refreshRequested = (0, react.useRef)(false);
|
|
251
|
-
const connection = ctx.get("connection");
|
|
252
302
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
253
303
|
const cacheKey = JSON.stringify([period, timeZone]);
|
|
254
304
|
let cache = cachedViews.get(connection);
|
|
@@ -272,7 +322,7 @@ function Usage({ ctx }) {
|
|
|
272
322
|
(async () => {
|
|
273
323
|
try {
|
|
274
324
|
const result = await connection.rpc.call(channel, method, {
|
|
275
|
-
|
|
325
|
+
...period,
|
|
276
326
|
timeZone
|
|
277
327
|
}, controller.signal);
|
|
278
328
|
if (controller.signal.aborted) return;
|
|
@@ -314,6 +364,10 @@ function Usage({ ctx }) {
|
|
|
314
364
|
{
|
|
315
365
|
id: "0",
|
|
316
366
|
label: text.all
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "custom",
|
|
370
|
+
label: text.custom
|
|
317
371
|
}
|
|
318
372
|
];
|
|
319
373
|
const selected = view?.days.find(({ day }) => day === selectedDay);
|
|
@@ -334,9 +388,13 @@ function Usage({ ctx }) {
|
|
|
334
388
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonPicker, {
|
|
335
389
|
variant: "outline",
|
|
336
390
|
label: text.period,
|
|
337
|
-
value: String(period),
|
|
391
|
+
value: editingRange || !("days" in period) ? "custom" : String(period.days),
|
|
338
392
|
items: periods,
|
|
339
|
-
onChange: (id) =>
|
|
393
|
+
onChange: (id) => {
|
|
394
|
+
setEditingRange(id === "custom");
|
|
395
|
+
setRangeError(false);
|
|
396
|
+
if (id !== "custom") applyPeriod({ days: Number(id) });
|
|
397
|
+
}
|
|
340
398
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
341
399
|
variant: "outline",
|
|
342
400
|
className: "dsh-usage-refresh",
|
|
@@ -350,6 +408,76 @@ function Usage({ ctx }) {
|
|
|
350
408
|
children: busy ? text.refreshing : text.refresh
|
|
351
409
|
})]
|
|
352
410
|
}),
|
|
411
|
+
editingRange && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
412
|
+
className: "dsh-usage-range",
|
|
413
|
+
noValidate: true,
|
|
414
|
+
onSubmit: (event) => {
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
if (!isValidDateRange(startDate, endDate)) {
|
|
417
|
+
setRangeError(true);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
setRangeError(false);
|
|
421
|
+
applyPeriod({
|
|
422
|
+
startDate,
|
|
423
|
+
endDate
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
children: [
|
|
427
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [text.startDate, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
428
|
+
type: "date",
|
|
429
|
+
value: startDate,
|
|
430
|
+
required: true,
|
|
431
|
+
"aria-invalid": rangeError,
|
|
432
|
+
"aria-describedby": `${rangeId}-hint${rangeError ? ` ${rangeId}-error` : ""}`,
|
|
433
|
+
onChange: (event) => {
|
|
434
|
+
setStartDate(event.target.value);
|
|
435
|
+
setRangeError(false);
|
|
436
|
+
}
|
|
437
|
+
})] }),
|
|
438
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [text.endDate, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
439
|
+
type: "date",
|
|
440
|
+
value: endDate,
|
|
441
|
+
required: true,
|
|
442
|
+
"aria-invalid": rangeError,
|
|
443
|
+
"aria-describedby": `${rangeId}-hint${rangeError ? ` ${rangeId}-error` : ""}`,
|
|
444
|
+
onChange: (event) => {
|
|
445
|
+
setEndDate(event.target.value);
|
|
446
|
+
setRangeError(false);
|
|
447
|
+
}
|
|
448
|
+
})] }),
|
|
449
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
450
|
+
type: "submit",
|
|
451
|
+
variant: "outline",
|
|
452
|
+
children: text.apply
|
|
453
|
+
}),
|
|
454
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
455
|
+
id: `${rangeId}-hint`,
|
|
456
|
+
className: "dsh-usage-muted dsh-usage-range-note",
|
|
457
|
+
children: [
|
|
458
|
+
text.rangeHint,
|
|
459
|
+
" (",
|
|
460
|
+
timeZone,
|
|
461
|
+
")"
|
|
462
|
+
]
|
|
463
|
+
}),
|
|
464
|
+
rangeError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
465
|
+
id: `${rangeId}-error`,
|
|
466
|
+
className: "dsh-usage-range-note",
|
|
467
|
+
role: "alert",
|
|
468
|
+
children: text.rangeError
|
|
469
|
+
})
|
|
470
|
+
]
|
|
471
|
+
}),
|
|
472
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
473
|
+
className: "dsh-usage-muted",
|
|
474
|
+
children: [
|
|
475
|
+
text.showing,
|
|
476
|
+
":",
|
|
477
|
+
" ",
|
|
478
|
+
"days" in period ? periods.find(({ id }) => id === String(period.days))?.label : `${period.startDate} – ${period.endDate}`
|
|
479
|
+
]
|
|
480
|
+
}),
|
|
353
481
|
busy && !view && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
354
482
|
role: "status",
|
|
355
483
|
className: "dsh-usage-status",
|
|
@@ -403,7 +531,7 @@ function Usage({ ctx }) {
|
|
|
403
531
|
role: "status",
|
|
404
532
|
children: text.empty
|
|
405
533
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
406
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: period === 0 ? text.recent : text.daily }),
|
|
534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "days" in period && period.days === 0 ? text.recent : text.daily }),
|
|
407
535
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
408
536
|
className: "dsh-usage-chart",
|
|
409
537
|
role: "group",
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,16 @@ import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
|
2
2
|
import { lastAssistantStreamChunk } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
// src/protocol.ts
|
|
4
4
|
const method = "dungsil-usage/describe";
|
|
5
|
+
function isValidDateRange(startDate, endDate) {
|
|
6
|
+
const parse = (value) => {
|
|
7
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return NaN;
|
|
8
|
+
const time = Date.parse(`${value}T00:00:00Z`);
|
|
9
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value ? time : NaN;
|
|
10
|
+
};
|
|
11
|
+
const start = parse(startDate);
|
|
12
|
+
const inclusiveDays = (parse(endDate) - start) / 864e5 + 1;
|
|
13
|
+
return inclusiveDays >= 1 && inclusiveDays <= 366;
|
|
14
|
+
}
|
|
5
15
|
// src/index.ts
|
|
6
16
|
const name = "dungsil-ui-usage";
|
|
7
17
|
const inject = ["connection", "sessionQuery"];
|
|
@@ -46,11 +56,11 @@ function apply(ctx) {
|
|
|
46
56
|
const body = clientRequestSchema.parse(await request.json());
|
|
47
57
|
rpcId = body.rpcId;
|
|
48
58
|
const input = body.payload;
|
|
49
|
-
if (body.method !== "dungsil-usage/describe" || !input || ![
|
|
59
|
+
if (body.method !== "dungsil-usage/describe" || !input || typeof input !== "object" || Array.isArray(input) || typeof input.timeZone !== "string" || ("startDate" in input || "endDate" in input ? "days" in input || typeof input.startDate !== "string" || typeof input.endDate !== "string" || !isValidDateRange(input.startDate, input.endDate) : ![
|
|
50
60
|
7,
|
|
51
61
|
30,
|
|
52
62
|
0
|
|
53
|
-
].includes(input.days)
|
|
63
|
+
].includes(input.days))) return Response.json({
|
|
54
64
|
type: "server-response",
|
|
55
65
|
rpcId,
|
|
56
66
|
result: failure("invalid")
|
|
@@ -73,7 +83,8 @@ function apply(ctx) {
|
|
|
73
83
|
const signal = AbortSignal.any([request.signal, AbortSignal.timeout(6e4)]);
|
|
74
84
|
const updatedAt = Date.now();
|
|
75
85
|
const today = formatter.format(updatedAt);
|
|
76
|
-
const
|
|
86
|
+
const end = typeof input.endDate === "string" ? input.endDate : today;
|
|
87
|
+
const start = typeof input.startDate === "string" ? input.startDate : input.days === 0 ? "" : (/* @__PURE__ */ new Date(Date.parse(`${today}T00:00:00Z`) - (input.days - 1) * 864e5)).toISOString().slice(0, 10);
|
|
77
88
|
const view = {
|
|
78
89
|
totals: empty(),
|
|
79
90
|
models: [],
|
|
@@ -106,7 +117,7 @@ function apply(ctx) {
|
|
|
106
117
|
if (event.seq < observation.inheritedEventCount || event.time > updatedAt) continue;
|
|
107
118
|
if (event.type !== "assistant/message" && event.type !== "assistant/attempt") continue;
|
|
108
119
|
const day = formatter.format(event.time);
|
|
109
|
-
if (day < start || day >
|
|
120
|
+
if (day < start || day > end) continue;
|
|
110
121
|
const usage = event.type === "assistant/message" ? event.data.usage : lastAssistantStreamChunk(event.data.stream, "usage")?.usage;
|
|
111
122
|
const route = event.type === "assistant/message" ? event.data.message.source : {
|
|
112
123
|
provider,
|
|
@@ -148,7 +159,7 @@ function apply(ctx) {
|
|
|
148
159
|
}));
|
|
149
160
|
view.models = [...models.values()].sort((a, b) => b.total - a.total || a.provider.localeCompare(b.provider) || a.model.localeCompare(b.model));
|
|
150
161
|
const chartStart = start || (/* @__PURE__ */ new Date(Date.parse(`${today}T00:00:00Z`) - 25056e5)).toISOString().slice(0, 10);
|
|
151
|
-
for (let date = Date.parse(`${chartStart}T00:00:00Z`); date <= Date.parse(`${
|
|
162
|
+
for (let date = Date.parse(`${chartStart}T00:00:00Z`); date <= Date.parse(`${end}T00:00:00Z`); date += 864e5) {
|
|
152
163
|
const day = new Date(date).toISOString().slice(0, 10);
|
|
153
164
|
view.days.push({
|
|
154
165
|
day,
|