dsh-token-gauge 0.1.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/lib/client.js ADDED
@@ -0,0 +1,886 @@
1
+ // src/styles.ts
2
+ var STYLES = [
3
+ { id: "ev", labelZh: "\u73B0\u4EE3\u7535\u8F66\u79D1\u6280", labelEn: "Modern EV Tech", className: "tg-style-ev" },
4
+ { id: "raw", labelZh: "\u539F\u59CB\u673A\u8F66", labelEn: "Raw Motorcycle", className: "tg-style-raw" },
5
+ { id: "luxury", labelZh: "\u8C6A\u534E\u8DD1\u8F66", labelEn: "Luxury Sports", className: "tg-style-luxury" },
6
+ { id: "cute", labelZh: "\u53EF\u7231\u5C0F\u8F66", labelEn: "Cute Car", className: "tg-style-cute" },
7
+ { id: "cyber", labelZh: "\u8D5B\u535A\u670B\u514B", labelEn: "Cyberpunk", className: "tg-style-cyber" },
8
+ { id: "retro", labelZh: "\u590D\u53E4\u673A\u68B0\u8868\u76D8", labelEn: "Retro Dial", className: "tg-style-retro" }
9
+ ];
10
+ var PALETTES = [
11
+ {
12
+ id: "amber",
13
+ labelZh: "\u7425\u73C0\u91D1",
14
+ labelEn: "Amber Gold",
15
+ vars: {
16
+ "--c-bg": "#1c1407",
17
+ "--c-panel": "#2b2009",
18
+ "--c-main": "#f5a623",
19
+ "--c-track": "#3a2c0e",
20
+ "--c-tick": "#f5c451",
21
+ "--c-needle": "#ffcf5c",
22
+ "--c-num": "#ffe39a",
23
+ "--c-danger": "#ff5a3c",
24
+ "--c-glow": "rgba(245,166,35,0.55)",
25
+ "--c-sub": "#caa85e",
26
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
27
+ }
28
+ },
29
+ {
30
+ id: "neon",
31
+ labelZh: "\u9713\u8679\u84DD",
32
+ labelEn: "Neon Blue",
33
+ vars: {
34
+ "--c-bg": "#04111f",
35
+ "--c-panel": "#072438",
36
+ "--c-main": "#22d3ee",
37
+ "--c-track": "#0b3550",
38
+ "--c-tick": "#7fe9ff",
39
+ "--c-needle": "#22d3ee",
40
+ "--c-num": "#d6fbff",
41
+ "--c-danger": "#ff4d6d",
42
+ "--c-glow": "rgba(34,211,238,0.55)",
43
+ "--c-sub": "#7fb6cf",
44
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
45
+ }
46
+ },
47
+ {
48
+ id: "track-red",
49
+ labelZh: "\u8D5B\u9053\u7EA2",
50
+ labelEn: "Track Red",
51
+ vars: {
52
+ "--c-bg": "#1a0608",
53
+ "--c-panel": "#2a0a0e",
54
+ "--c-main": "#ff3b4e",
55
+ "--c-track": "#3a0d12",
56
+ "--c-tick": "#ff8a96",
57
+ "--c-needle": "#ff3b4e",
58
+ "--c-num": "#ffd9dd",
59
+ "--c-danger": "#ff7a00",
60
+ "--c-glow": "rgba(255,59,78,0.5)",
61
+ "--c-sub": "#cf7a84",
62
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
63
+ }
64
+ },
65
+ {
66
+ id: "aurora",
67
+ labelZh: "\u6781\u5149\u7EFF",
68
+ labelEn: "Aurora Green",
69
+ vars: {
70
+ "--c-bg": "#04140d",
71
+ "--c-panel": "#07241a",
72
+ "--c-main": "#34e0a1",
73
+ "--c-track": "#0b3a29",
74
+ "--c-tick": "#8af3cd",
75
+ "--c-needle": "#34e0a1",
76
+ "--c-num": "#d8ffee",
77
+ "--c-danger": "#ffb020",
78
+ "--c-glow": "rgba(52,224,161,0.5)",
79
+ "--c-sub": "#79c7a6",
80
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
81
+ }
82
+ },
83
+ {
84
+ id: "sakura",
85
+ labelZh: "\u6A31\u7C89",
86
+ labelEn: "Sakura Pink",
87
+ vars: {
88
+ "--c-bg": "#2a111d",
89
+ "--c-panel": "#3d1a2c",
90
+ "--c-main": "#ff9ecb",
91
+ "--c-track": "#4a2238",
92
+ "--c-tick": "#ffc7e2",
93
+ "--c-needle": "#ff9ecb",
94
+ "--c-num": "#ffe6f3",
95
+ "--c-danger": "#ff5d8f",
96
+ "--c-glow": "rgba(255,158,203,0.5)",
97
+ "--c-sub": "#d29bbb",
98
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
99
+ }
100
+ },
101
+ {
102
+ id: "night-purple",
103
+ labelZh: "\u6697\u591C\u7D2B",
104
+ labelEn: "Night Purple",
105
+ vars: {
106
+ "--c-bg": "#120a26",
107
+ "--c-panel": "#1d1238",
108
+ "--c-main": "#a78bfa",
109
+ "--c-track": "#2a1c52",
110
+ "--c-tick": "#cdbcff",
111
+ "--c-needle": "#a78bfa",
112
+ "--c-num": "#ece4ff",
113
+ "--c-danger": "#ff5a8a",
114
+ "--c-glow": "rgba(167,139,250,0.5)",
115
+ "--c-sub": "#9b86d6",
116
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
117
+ }
118
+ },
119
+ {
120
+ id: "titanium",
121
+ labelZh: "\u949B\u94F6",
122
+ labelEn: "Titanium Silver",
123
+ vars: {
124
+ "--c-bg": "#0e1116",
125
+ "--c-panel": "#1a1f27",
126
+ "--c-main": "#c7d0db",
127
+ "--c-track": "#2b323d",
128
+ "--c-tick": "#9aa6b4",
129
+ "--c-needle": "#e6edf5",
130
+ "--c-num": "#f2f6fb",
131
+ "--c-danger": "#ff5a3c",
132
+ "--c-glow": "rgba(199,208,219,0.35)",
133
+ "--c-sub": "#8b95a3",
134
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
135
+ }
136
+ },
137
+ {
138
+ id: "volcano",
139
+ labelZh: "\u706B\u5C71\u6A59",
140
+ labelEn: "Volcano Orange",
141
+ vars: {
142
+ "--c-bg": "#1f0d04",
143
+ "--c-panel": "#321507",
144
+ "--c-main": "#ff7a18",
145
+ "--c-track": "#45200c",
146
+ "--c-tick": "#ffb070",
147
+ "--c-needle": "#ff7a18",
148
+ "--c-num": "#ffe2cc",
149
+ "--c-danger": "#ff3b2f",
150
+ "--c-glow": "rgba(255,122,24,0.5)",
151
+ "--c-sub": "#cf9468",
152
+ "--c-font": '"Segoe UI", system-ui, sans-serif'
153
+ }
154
+ }
155
+ ];
156
+ function getStyle(id) {
157
+ return STYLES.find((s) => s.id === id) || STYLES[0];
158
+ }
159
+ function getPalette(id) {
160
+ return PALETTES.find((p) => p.id === id) || PALETTES[1];
161
+ }
162
+ function applyPalette(el, paletteId) {
163
+ const p = getPalette(paletteId);
164
+ for (const [k, v] of Object.entries(p.vars)) {
165
+ el.style.setProperty(k, v);
166
+ }
167
+ }
168
+ function buildCss() {
169
+ return `
170
+ .tg-root {
171
+ --c-bg:#04111f; --c-panel:#072438; --c-main:#22d3ee; --c-track:#0b3550;
172
+ --c-tick:#7fe9ff; --c-needle:#22d3ee; --c-num:#d6fbff; --c-danger:#ff4d6d;
173
+ --c-glow:rgba(34,211,238,0.55); --c-sub:#7fb6cf; --c-font:"Segoe UI",system-ui,sans-serif;
174
+ box-sizing:border-box; color:var(--c-num); font-family:var(--c-font);
175
+ height:100%; display:flex; flex-direction:column; padding:10px; gap:6px;
176
+ background:linear-gradient(160deg, var(--c-bg), var(--c-panel));
177
+ border-radius:14px; overflow:auto; position:relative;
178
+ }
179
+ .tg-root * { box-sizing:border-box; }
180
+ .tg-head { display:flex; align-items:center; justify-content:space-between; }
181
+ .tg-title { font-size:13px; font-weight:700; letter-spacing:.5px; color:var(--c-num); }
182
+ .tg-gear {
183
+ cursor:pointer; border:1px solid var(--c-track); background:var(--c-panel); color:var(--c-main);
184
+ border-radius:8px; width:28px; height:28px; font-size:14px; line-height:1; display:flex;
185
+ align-items:center; justify-content:center;
186
+ }
187
+ .tg-gear:hover { box-shadow:0 0 10px var(--c-glow); }
188
+ .tg-gauge-wrap { display:flex; flex-direction:column; align-items:center; gap:0; }
189
+ .tg-gauge { width:100%; max-width:200px; height:auto; display:block; filter:drop-shadow(0 0 6px var(--c-glow)); }
190
+ .tg-track { fill:none; stroke:var(--c-track); stroke-width:14; stroke-linecap:round; }
191
+ .tg-danger { fill:none; stroke:var(--c-danger); stroke-width:14; stroke-linecap:round; opacity:0; }
192
+ .tg-tick { stroke:var(--c-tick); stroke-width:2; }
193
+ .tg-ticklabel { fill:var(--c-sub); font-size:9px; font-family:var(--c-font); }
194
+ .tg-hub { fill:var(--c-main); }
195
+ .tg-needle { fill:var(--c-needle); transition:transform .6s cubic-bezier(.34,1.4,.5,1); transform-box:view-box; transform-origin:100px 100px; }
196
+ .tg-needle.tg-over { fill:var(--c-danger); }
197
+ .tg-extrashadow { fill:rgba(0,0,0,.25); }
198
+ .tg-readout { text-align:center; width:100%; margin-top:-16px; }
199
+ .tg-usage { font-size:22px; font-weight:800; color:var(--c-num); line-height:1.1; margin-top:1px; }
200
+ .tg-usage small { font-size:12px; color:var(--c-sub); font-weight:600; margin-left:4px; }
201
+ .tg-cost { font-size:17px; font-weight:700; color:var(--c-main); margin-top:0; }
202
+ .tg-cards { display:grid; grid-template-columns:1fr 1fr; gap:6px; width:100%; margin-top:4px; }
203
+ .tg-card { background:var(--c-panel); border:1px solid var(--c-track); border-radius:8px; padding:6px 8px; }
204
+ .tg-card .k { font-size:10px; color:var(--c-sub); }
205
+ .tg-card .v { font-size:13px; font-weight:700; color:var(--c-num); }
206
+ .tg-empty { color:var(--c-sub); font-size:12px; text-align:center; padding:20px 0; }
207
+
208
+ /* \u8BBE\u7F6E\u5F39\u7A97 */
209
+ .tg-modal-mask {
210
+ position:fixed; inset:0; background:rgba(0,0,0,.5); display:flex; align-items:center; justify-content:center;
211
+ z-index:9999; padding:16px;
212
+ }
213
+ .tg-modal {
214
+ width:min(420px, 100%); max-height:88%; overflow:auto; background:var(--c-panel); color:var(--c-num);
215
+ border:1px solid var(--c-main); border-radius:14px; padding:16px; font-family:var(--c-font);
216
+ box-shadow:0 0 24px var(--c-glow);
217
+ }
218
+ .tg-modal h3 { margin:0 0 10px; font-size:15px; color:var(--c-main); }
219
+ .tg-field { margin-bottom:12px; }
220
+ .tg-field > label { display:block; font-size:12px; color:var(--c-sub); margin-bottom:5px; }
221
+ .tg-row { display:flex; flex-wrap:wrap; gap:6px; }
222
+ .tg-chip {
223
+ cursor:pointer; border:1px solid var(--c-track); background:var(--c-bg); color:var(--c-num);
224
+ border-radius:999px; padding:5px 10px; font-size:12px; user-select:none;
225
+ }
226
+ .tg-chip.on { border-color:var(--c-main); color:var(--c-main); box-shadow:0 0 8px var(--c-glow); }
227
+ .tg-input {
228
+ width:100%; background:var(--c-bg); border:1px solid var(--c-track); color:var(--c-num);
229
+ border-radius:8px; padding:7px 9px; font-size:13px; font-family:var(--c-font);
230
+ }
231
+ .tg-input:focus { outline:none; border-color:var(--c-main); }
232
+ .tg-toggle { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--c-sub); margin:3px 0; }
233
+ .tg-actions { display:flex; gap:8px; margin-top:8px; }
234
+ .tg-btn {
235
+ flex:1; cursor:pointer; border:1px solid var(--c-main); background:var(--c-main); color:#06121c;
236
+ border-radius:9px; padding:9px; font-size:13px; font-weight:700; font-family:var(--c-font);
237
+ }
238
+ .tg-btn.ghost { background:transparent; color:var(--c-num); border-color:var(--c-track); }
239
+ .tg-btn:hover { filter:brightness(1.08); }
240
+ .tg-hint { font-size:11px; color:var(--c-sub); margin-top:4px; }
241
+
242
+ /* ---- \u98CE\u683C\u5DEE\u5F02\u5316\u88C5\u9970 ---- */
243
+ .tg-style-ev .tg-gauge-wrap { backdrop-filter:blur(2px); }
244
+ .tg-style-raw {
245
+ background:
246
+ repeating-linear-gradient(90deg, rgba(255,255,255,.04) 0 2px, rgba(0,0,0,.06) 2px 4px),
247
+ linear-gradient(160deg, #2a2a2a, #4a4a4a);
248
+ border:3px solid #6b6b6b;
249
+ }
250
+ .tg-style-raw .tg-panel-deco::before,
251
+ .tg-style-raw .tg-panel-deco::after {
252
+ content:""; position:absolute; width:8px; height:8px; border-radius:50%;
253
+ background:radial-gradient(circle at 30% 30%, #fff, #888); top:8px;
254
+ }
255
+ .tg-style-raw .tg-panel-deco::before { left:8px; }
256
+ .tg-style-raw .tg-panel-deco::after { right:8px; }
257
+ .tg-style-luxury {
258
+ background:
259
+ repeating-linear-gradient(45deg, #0c0c0c 0 6px, #161616 6px 12px),
260
+ linear-gradient(160deg, #1a0a0c, #2a0f12);
261
+ border:2px solid #d4af37;
262
+ }
263
+ .tg-style-luxury .tg-title { color:#f3d27a; }
264
+ .tg-style-cute {
265
+ background:linear-gradient(160deg, #fff0f6, #ffe1ee); color:#7a3b56;
266
+ border:3px solid #ffc2dd; border-radius:22px;
267
+ }
268
+ .tg-style-cute .tg-usage, .tg-style-cute .tg-cost, .tg-style-cute .tg-title, .tg-style-cute .tg-card .v { color:#a6446f; }
269
+ .tg-style-cute .tg-card { background:#fff6fb; border-color:#ffd0e6; }
270
+ .tg-style-cute .tg-card .k { color:#c98bb0; }
271
+ .tg-style-cute .tg-track { stroke:#ffd0e6; }
272
+ .tg-style-cyber {
273
+ background:linear-gradient(160deg, #0a0014, #16002a);
274
+ border:2px solid #ff2bd6;
275
+ }
276
+ .tg-style-cyber .tg-title { color:#22e7ff; text-shadow:1px 0 #ff2bd6, -1px 0 #22e7ff; }
277
+ .tg-style-cyber .tg-gauge { filter:drop-shadow(0 0 8px #22e7ff) drop-shadow(0 0 4px #ff2bd6); }
278
+ .tg-style-retro {
279
+ background:linear-gradient(160deg, #f3e9cf, #e7d8b0); color:#5b3a1a;
280
+ border:3px double #8a6a3a; border-radius:10px;
281
+ }
282
+ .tg-style-retro .tg-usage, .tg-style-retro .tg-cost, .tg-style-retro .tg-title, .tg-style-retro .tg-card .v { color:#5b3a1a; }
283
+ .tg-style-retro .tg-title { font-family:Georgia, "Times New Roman", serif; }
284
+ .tg-style-retro .tg-card { background:#efe3c4; border-color:#b89a64; }
285
+ .tg-style-retro .tg-card .k { color:#8a6a3a; }
286
+ .tg-style-retro .tg-track { stroke:#c9b58a; }
287
+ .tg-style-retro .tg-tick { stroke:#8a6a3a; }
288
+ .tg-style-retro .tg-ticklabel { fill:#8a6a3a; }
289
+ `;
290
+ }
291
+
292
+ // src/util.ts
293
+ function formatInt(n) {
294
+ return Math.round(n || 0).toLocaleString("en-US");
295
+ }
296
+ function formatMoney(n) {
297
+ return "\xA5" + (n || 0).toFixed(2);
298
+ }
299
+ function formatCompact(n) {
300
+ const v = Math.round(n || 0);
301
+ if (v >= 1e6) return (v / 1e6).toFixed(v >= 1e7 ? 0 : 1) + "M";
302
+ if (v >= 1e3) return (v / 1e3).toFixed(v >= 1e4 ? 0 : 1) + "K";
303
+ return String(v);
304
+ }
305
+ function clamp(v, lo, hi) {
306
+ return Math.max(lo, Math.min(hi, v));
307
+ }
308
+ function autoMax(value) {
309
+ const v = Math.max(0, value);
310
+ for (let exp = 3; exp <= 12; exp++) {
311
+ for (const m of [1, 2, 5]) {
312
+ const cand = m * Math.pow(10, exp);
313
+ if (cand >= v) return cand;
314
+ }
315
+ }
316
+ return Math.pow(10, 12);
317
+ }
318
+
319
+ // src/gauge.ts
320
+ var SVGNS = "http://www.w3.org/2000/svg";
321
+ var CX = 100;
322
+ var CY = 100;
323
+ var R = 78;
324
+ var START = 150;
325
+ var END = 390;
326
+ var SWEEP = END - START;
327
+ function polar(angleDeg, r) {
328
+ const a = angleDeg * Math.PI / 180;
329
+ return [CX + r * Math.cos(a), CY + r * Math.sin(a)];
330
+ }
331
+ function arcPath(r, a0, a1) {
332
+ const [sx, sy] = polar(a0, r);
333
+ const [ex, ey] = polar(a1, r);
334
+ const large = a1 - a0 > 180 ? 1 : 0;
335
+ return `M ${sx.toFixed(2)} ${sy.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${ex.toFixed(2)} ${ey.toFixed(2)}`;
336
+ }
337
+ function svg(tag, attrs) {
338
+ const el = document.createElementNS(SVGNS, tag);
339
+ for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
340
+ return el;
341
+ }
342
+ function createGauge() {
343
+ const wrap = document.createElement("div");
344
+ wrap.className = "tg-gauge-wrap";
345
+ const s = svg("svg", { class: "tg-gauge", viewBox: "0 0 200 200", role: "img" });
346
+ s.appendChild(svg("path", { class: "tg-track", d: arcPath(R, START, END) }));
347
+ const dangerEl = svg("path", { class: "tg-danger", d: arcPath(R, START + SWEEP * 0.85, END) });
348
+ s.appendChild(dangerEl);
349
+ const labelEls = [];
350
+ for (let i = 0; i <= 10; i++) {
351
+ const frac = i / 10;
352
+ const ang = START + SWEEP * frac;
353
+ const major = i % 5 === 0;
354
+ const rOut = R + (major ? 9 : 5);
355
+ const rIn = R - 2;
356
+ const [x1, y1] = polar(ang, rOut);
357
+ const [x2, y2] = polar(ang, rIn);
358
+ s.appendChild(svg("line", { class: "tg-tick", x1: x1.toFixed(2), y1: y1.toFixed(2), x2: x2.toFixed(2), y2: y2.toFixed(2), "stroke-width": major ? "2.5" : "1.5" }));
359
+ if (major) {
360
+ const [lx, ly] = polar(ang, R - 18);
361
+ const txt = svg("text", { class: "tg-ticklabel", x: lx.toFixed(2), y: (ly + 3).toFixed(2), "text-anchor": "middle" });
362
+ txt.textContent = String(Math.round(frac * 100));
363
+ s.appendChild(txt);
364
+ labelEls.push({ el: txt, frac });
365
+ }
366
+ }
367
+ const needle = svg("g", { class: "tg-needle" });
368
+ const tipY = CY - (R - 14);
369
+ const poly = svg("polygon", {
370
+ points: `${CX},${tipY} ${CX - 4},${CY + 6} ${CX},${CY + 14} ${CX + 4},${CY + 6}`
371
+ });
372
+ needle.appendChild(poly);
373
+ s.appendChild(needle);
374
+ s.appendChild(svg("circle", { class: "tg-hub", cx: String(CX), cy: String(CY), r: "6" }));
375
+ wrap.appendChild(s);
376
+ const readout = document.createElement("div");
377
+ readout.className = "tg-readout";
378
+ const costEl = document.createElement("div");
379
+ costEl.className = "tg-cost";
380
+ const usageEl = document.createElement("div");
381
+ usageEl.className = "tg-usage";
382
+ const cards = document.createElement("div");
383
+ cards.className = "tg-cards";
384
+ readout.append(costEl, usageEl, cards);
385
+ wrap.appendChild(readout);
386
+ const update = (p) => {
387
+ const max = p.limit && p.limit > 0 ? p.limit : autoMax(p.value);
388
+ const ratio = max > 0 ? p.value / max : 0;
389
+ const visual = clamp(ratio, 0, 1);
390
+ const needleAngle = START + visual * SWEEP;
391
+ const deg = needleAngle - 270;
392
+ needle.style.transform = `rotate(${deg.toFixed(2)}deg)`;
393
+ for (const { el, frac } of labelEls) el.textContent = formatCompact(max * frac);
394
+ const over = ratio >= 1;
395
+ const proximity = clamp((ratio - 0.7) / 0.3, 0, 1);
396
+ dangerEl.style.opacity = (proximity * 0.95).toFixed(3);
397
+ needle.setAttribute("class", over ? "tg-needle tg-over" : "tg-needle");
398
+ if (p.display.cost) {
399
+ costEl.style.display = "";
400
+ costEl.textContent = formatMoney(p.cost);
401
+ } else costEl.style.display = "none";
402
+ if (p.display.usage) {
403
+ usageEl.style.display = "";
404
+ usageEl.innerHTML = `${(p.value / 1e3).toLocaleString("en-US", { maximumFractionDigits: 1 })}<small>k tokens</small>`;
405
+ } else usageEl.style.display = "none";
406
+ cards.innerHTML = "";
407
+ const addCard = (k, v) => {
408
+ const c = document.createElement("div");
409
+ c.className = "tg-card";
410
+ c.innerHTML = `<div class="k">${k}</div><div class="v">${v}</div>`;
411
+ cards.appendChild(c);
412
+ };
413
+ if (p.display.io) {
414
+ addCard(p.t("input"), formatInt(p.state?.global?.input || 0));
415
+ addCard(p.t("output"), formatInt(p.state?.global?.output || 0));
416
+ }
417
+ if (p.display.cacheMiss) {
418
+ const hit = p.state?.global?.cacheRead || 0;
419
+ const input = p.state?.global?.input || 0;
420
+ const miss = Math.max(0, input - hit);
421
+ addCard(p.t("cacheHit"), formatInt(hit));
422
+ addCard(p.t("cacheMiss"), formatInt(miss));
423
+ }
424
+ };
425
+ return { el: wrap, update };
426
+ }
427
+
428
+ // src/i18n.ts
429
+ var I18N = {
430
+ title: { zh: "Token \u4EEA\u8868\u76D8", en: "Token Gauge" },
431
+ settings: { zh: "\u8BBE\u7F6E", en: "Settings" },
432
+ style: { zh: "\u98CE\u683C", en: "Style" },
433
+ palette: { zh: "\u914D\u8272", en: "Palette" },
434
+ display: { zh: "\u663E\u793A\u5185\u5BB9", en: "Display" },
435
+ metric: { zh: "\u7528\u91CF\u53E3\u5F84", en: "Metric" },
436
+ limit: { zh: "\u9650\u989D", en: "Limit" },
437
+ limitGlobal: { zh: "\u5168\u5C40\u9650\u989D", en: "Global limit" },
438
+ limitDaily: { zh: "\u65E5\u9650\u989D", en: "Daily limit" },
439
+ limitWeekly: { zh: "\u5468\u9650\u989D", en: "Weekly limit" },
440
+ limitMonthly: { zh: "\u6708\u9650\u989D", en: "Monthly limit" },
441
+ usage: { zh: "\u7528\u91CF", en: "Usage" },
442
+ cost: { zh: "\u8D39\u7528", en: "Cost" },
443
+ input: { zh: "\u8F93\u5165", en: "Input" },
444
+ output: { zh: "\u8F93\u51FA", en: "Output" },
445
+ cacheMiss: { zh: "\u7F13\u5B58\u672A\u547D\u4E2D", en: "Cache miss" },
446
+ lang: { zh: "\u8BED\u8A00", en: "Language" },
447
+ export: { zh: "\u5BFC\u51FA\u62A5\u8868", en: "Export report" },
448
+ exportCsv: { zh: "CSV", en: "CSV" },
449
+ exportJson: { zh: "JSON", en: "JSON" },
450
+ close: { zh: "\u5173\u95ED", en: "Close" },
451
+ save: { zh: "\u4FDD\u5B58", en: "Save" },
452
+ reset: { zh: "\u6062\u590D\u9ED8\u8BA4", en: "Reset" },
453
+ clear: { zh: "\u6E05\u7A7A\u7528\u91CF", en: "Clear usage" },
454
+ advanced: { zh: "\u9AD8\u7EA7\xB7\u5B9A\u4EF7(\xA5/\u767E\u4E07token)", en: "Advanced \xB7 Pricing (\xA5/M tokens)" },
455
+ overLimit: { zh: "\u5DF2\u8D85\u51FA\u9650\u989D\uFF01", en: "Over limit!" },
456
+ noData: { zh: "\u6682\u65E0\u7528\u91CF\u6570\u636E\uFF0C\u5F00\u59CB\u5BF9\u8BDD\u540E\u6B64\u5904\u4F1A\u5B9E\u65F6\u66F4\u65B0\u3002", en: "No usage yet. Start a chat to see live updates." },
457
+ autoScale: { zh: "\u81EA\u52A8\u91CF\u7A0B", en: "auto" },
458
+ metricGlobal: { zh: "\u5168\u5C40\u7D2F\u8BA1", en: "Global" },
459
+ metricSession: { zh: "\u5F53\u524D\u4F1A\u8BDD", en: "Session" },
460
+ metricToday: { zh: "\u4ECA\u65E5", en: "Today" },
461
+ metricDaily: { zh: "\u6BCF\u65E5", en: "Daily" },
462
+ metricWeekly: { zh: "\u6BCF\u5468", en: "Weekly" },
463
+ metricMonthly: { zh: "\u6BCF\u6708", en: "Monthly" },
464
+ inputTokens: { zh: "\u8F93\u5165 Token", en: "Input tokens" },
465
+ outputTokens: { zh: "\u8F93\u51FA Token", en: "Output tokens" },
466
+ cacheHit: { zh: "\u7F13\u5B58\u547D\u4E2D", en: "Cache hit" },
467
+ cacheWrite: { zh: "\u7F13\u5B58\u5199\u5165", en: "Cache write" },
468
+ model: { zh: "\u6A21\u578B", en: "Model" },
469
+ period: { zh: "\u5468\u671F", en: "Period" },
470
+ totalCost: { zh: "\u603B\u8D39\u7528", en: "Total cost" },
471
+ totalTokens: { zh: "\u603B Token", en: "Total tokens" },
472
+ calls: { zh: "\u8C03\u7528\u6B21\u6570", en: "Calls" },
473
+ hintLimit: { zh: "\u7559\u7A7A\u8868\u793A\u4E0D\u9650\uFF08\u4EEA\u8868\u76D8\u81EA\u52A8\u91CF\u7A0B\uFF09\u3002", en: "Leave empty for no limit (auto scale)." }
474
+ };
475
+ function t(lang, key) {
476
+ const e = I18N[key];
477
+ if (!e) return key;
478
+ return e[lang] ?? e.zh;
479
+ }
480
+ function metricLabel(lang, metric) {
481
+ return t(lang, "metric" + metric.charAt(0).toUpperCase() + metric.slice(1));
482
+ }
483
+
484
+ // src/export.ts
485
+ function total(b) {
486
+ return (b.input || 0) + (b.cacheRead || 0) + (b.cacheWrite || 0) + (b.output || 0);
487
+ }
488
+ function num(v) {
489
+ return String(Math.round(v || 0));
490
+ }
491
+ function rowOf(key, b) {
492
+ return [key, num(b.input), num(b.cacheRead), num(b.cacheWrite), num(b.output), num(total(b)), (b.cost || 0).toFixed(4), num(b.calls)];
493
+ }
494
+ function buildCsv(state, lang) {
495
+ const L = (k) => t(lang, k);
496
+ const lines = [];
497
+ lines.push(`# ${L("title")} - ${(/* @__PURE__ */ new Date()).toLocaleString(lang === "zh" ? "zh-CN" : "en-US")}`);
498
+ lines.push("");
499
+ lines.push(`# ${L("global")}`);
500
+ lines.push(["", L("inputTokens"), L("cacheHit"), L("cacheWrite"), L("outputTokens"), L("totalTokens"), L("totalCost") + "(\xA5)", L("calls")].join(","));
501
+ lines.push(rowOf(L("global"), state.global || {}).join(","));
502
+ lines.push("");
503
+ lines.push(`# ${L("model")}`);
504
+ lines.push(["", L("inputTokens"), L("cacheHit"), L("cacheWrite"), L("outputTokens"), L("totalTokens"), L("totalCost") + "(\xA5)", L("calls")].join(","));
505
+ for (const [k, v] of Object.entries(state.models || {})) lines.push(rowOf(k, v).join(","));
506
+ for (const period of ["daily", "weekly", "monthly", "sessions"]) {
507
+ const map = state[period] || {};
508
+ const title = period === "daily" ? L("metricDaily") : period === "weekly" ? L("metricWeekly") : period === "monthly" ? L("metricMonthly") : L("sessions");
509
+ lines.push("");
510
+ lines.push(`# ${title}`);
511
+ lines.push(["", L("inputTokens"), L("cacheHit"), L("cacheWrite"), L("outputTokens"), L("totalTokens"), L("totalCost") + "(\xA5)", L("calls")].join(","));
512
+ for (const [k, v] of Object.entries(map)) lines.push(rowOf(k, v).join(","));
513
+ }
514
+ return lines.join("\n");
515
+ }
516
+ function buildJson(state) {
517
+ return JSON.stringify(
518
+ {
519
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
520
+ global: state.global || {},
521
+ models: state.models || {},
522
+ daily: state.daily || {},
523
+ weekly: state.weekly || {},
524
+ monthly: state.monthly || {},
525
+ sessions: state.sessions || {},
526
+ settings: state.settings || {}
527
+ },
528
+ null,
529
+ 2
530
+ );
531
+ }
532
+ function downloadReport(state, lang, format) {
533
+ const content = format === "csv" ? buildCsv(state, lang) : buildJson(state);
534
+ const mime = format === "csv" ? "text/csv;charset=utf-8" : "application/json";
535
+ const filename = `token-gauge-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.${format}`;
536
+ const blob = new Blob([content], { type: mime });
537
+ const url = URL.createObjectURL(blob);
538
+ const a = document.createElement("a");
539
+ a.href = url;
540
+ a.download = filename;
541
+ document.body.appendChild(a);
542
+ a.click();
543
+ document.body.removeChild(a);
544
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
545
+ }
546
+
547
+ // src/settings.ts
548
+ function chip(label, on, onClick) {
549
+ const c = document.createElement("div");
550
+ c.className = "tg-chip" + (on ? " on" : "");
551
+ c.textContent = label;
552
+ c.addEventListener("click", onClick);
553
+ return c;
554
+ }
555
+ function createSettingsModal(opts) {
556
+ const mask = document.createElement("div");
557
+ mask.className = "tg-modal-mask";
558
+ mask.style.display = "none";
559
+ const box = document.createElement("div");
560
+ box.className = "tg-modal";
561
+ mask.appendChild(box);
562
+ let openState = null;
563
+ let isOpen = false;
564
+ const render = (state) => {
565
+ const lang = opts.getLang();
566
+ const s = state.settings;
567
+ box.innerHTML = "";
568
+ const title = document.createElement("h3");
569
+ title.textContent = t(lang, "settings");
570
+ box.appendChild(title);
571
+ const langField = document.createElement("div");
572
+ langField.className = "tg-field";
573
+ langField.innerHTML = `<label>${t(lang, "lang")}</label>`;
574
+ const langRow = document.createElement("div");
575
+ langRow.className = "tg-row";
576
+ langRow.append(
577
+ chip("\u4E2D\u6587", s.lang === "zh", () => opts.onSettings({ lang: "zh" })),
578
+ chip("English", s.lang === "en", () => opts.onSettings({ lang: "en" }))
579
+ );
580
+ langField.appendChild(langRow);
581
+ box.appendChild(langField);
582
+ const styleField = document.createElement("div");
583
+ styleField.className = "tg-field";
584
+ styleField.innerHTML = `<label>${t(lang, "style")}</label>`;
585
+ const styleRow = document.createElement("div");
586
+ styleRow.className = "tg-row";
587
+ for (const st of STYLES) {
588
+ styleRow.appendChild(
589
+ chip(lang === "zh" ? st.labelZh : st.labelEn, s.style === st.id, () => opts.onSettings({ style: st.id }))
590
+ );
591
+ }
592
+ styleField.appendChild(styleRow);
593
+ box.appendChild(styleField);
594
+ const palField = document.createElement("div");
595
+ palField.className = "tg-field";
596
+ palField.innerHTML = `<label>${t(lang, "palette")}</label>`;
597
+ const palRow = document.createElement("div");
598
+ palRow.className = "tg-row";
599
+ for (const p of PALETTES) {
600
+ palRow.appendChild(
601
+ chip(lang === "zh" ? p.labelZh : p.labelEn, s.palette === p.id, () => opts.onSettings({ palette: p.id }))
602
+ );
603
+ }
604
+ palField.appendChild(palRow);
605
+ box.appendChild(palField);
606
+ const metricField = document.createElement("div");
607
+ metricField.className = "tg-field";
608
+ metricField.innerHTML = `<label>${t(lang, "metric")}</label>`;
609
+ const metricRow = document.createElement("div");
610
+ metricRow.className = "tg-row";
611
+ for (const m of ["global", "session", "today", "daily", "weekly", "monthly"]) {
612
+ metricRow.appendChild(chip(metricLabel(lang, m), s.metric === m, () => opts.onSettings({ metric: m })));
613
+ }
614
+ metricField.appendChild(metricRow);
615
+ box.appendChild(metricField);
616
+ const dispField = document.createElement("div");
617
+ dispField.className = "tg-field";
618
+ dispField.innerHTML = `<label>${t(lang, "display")}</label>`;
619
+ const dispKeys = [
620
+ { key: "usage", label: t(lang, "usage") },
621
+ { key: "cost", label: t(lang, "cost") },
622
+ { key: "io", label: t(lang, "input") + "/" + t(lang, "output") },
623
+ { key: "cacheMiss", label: t(lang, "cacheMiss") }
624
+ ];
625
+ for (const d of dispKeys) {
626
+ const row = document.createElement("label");
627
+ row.className = "tg-toggle";
628
+ const cb = document.createElement("input");
629
+ cb.type = "checkbox";
630
+ cb.checked = !!s.display[d.key];
631
+ cb.addEventListener("change", () => {
632
+ const next = { ...s.display, [d.key]: cb.checked };
633
+ opts.onSettings({ display: next });
634
+ });
635
+ row.append(cb, document.createTextNode(d.label));
636
+ dispField.appendChild(row);
637
+ }
638
+ box.appendChild(dispField);
639
+ const limitField = document.createElement("div");
640
+ limitField.className = "tg-field";
641
+ limitField.innerHTML = `<label>${t(lang, "limit")}</label>`;
642
+ const limitDefs = [
643
+ { key: "global", label: t(lang, "limitGlobal") },
644
+ { key: "daily", label: t(lang, "limitDaily") },
645
+ { key: "weekly", label: t(lang, "limitWeekly") },
646
+ { key: "monthly", label: t(lang, "limitMonthly") }
647
+ ];
648
+ for (const ld of limitDefs) {
649
+ const wrap = document.createElement("div");
650
+ const lab = document.createElement("label");
651
+ lab.className = "tg-toggle";
652
+ lab.textContent = ld.label;
653
+ const inp = document.createElement("input");
654
+ inp.className = "tg-input";
655
+ inp.type = "number";
656
+ inp.min = "0";
657
+ inp.placeholder = "\u221E";
658
+ inp.value = s.limits[ld.key] == null ? "" : String(s.limits[ld.key]);
659
+ inp.addEventListener("change", () => {
660
+ const v = inp.value.trim();
661
+ const val = v === "" ? null : Math.max(0, Number(v));
662
+ if (v !== "" && Number.isNaN(Number(v))) return;
663
+ opts.onSettings({ limits: { ...s.limits, [ld.key]: val } });
664
+ });
665
+ wrap.append(lab, inp);
666
+ limitField.appendChild(wrap);
667
+ }
668
+ const hint = document.createElement("div");
669
+ hint.className = "tg-hint";
670
+ hint.textContent = t(lang, "hintLimit");
671
+ limitField.appendChild(hint);
672
+ box.appendChild(limitField);
673
+ const advField = document.createElement("div");
674
+ advField.className = "tg-field";
675
+ advField.innerHTML = `<label>${t(lang, "advanced")}</label>`;
676
+ const ta = document.createElement("textarea");
677
+ ta.className = "tg-input";
678
+ ta.rows = 5;
679
+ ta.style.fontFamily = "monospace";
680
+ ta.value = JSON.stringify(s.pricing || {}, null, 2);
681
+ advField.appendChild(ta);
682
+ const advErr = document.createElement("div");
683
+ advErr.className = "tg-hint";
684
+ advErr.style.color = "var(--c-danger)";
685
+ const savePrice = document.createElement("button");
686
+ savePrice.className = "tg-btn ghost";
687
+ savePrice.style.marginTop = "6px";
688
+ savePrice.textContent = t(lang, "save");
689
+ savePrice.addEventListener("click", () => {
690
+ try {
691
+ const parsed = JSON.parse(ta.value || "{}");
692
+ opts.onSettings({ pricing: parsed });
693
+ advErr.textContent = "";
694
+ } catch (e) {
695
+ advErr.textContent = String(e);
696
+ }
697
+ });
698
+ advField.append(savePrice, advErr);
699
+ box.appendChild(advField);
700
+ const expField = document.createElement("div");
701
+ expField.className = "tg-field";
702
+ expField.innerHTML = `<label>${t(lang, "export")}</label>`;
703
+ const expRow = document.createElement("div");
704
+ expRow.className = "tg-actions";
705
+ const csvBtn = document.createElement("button");
706
+ csvBtn.className = "tg-btn ghost";
707
+ csvBtn.textContent = "CSV";
708
+ csvBtn.addEventListener("click", () => opts.onExport("csv"));
709
+ const jsonBtn = document.createElement("button");
710
+ jsonBtn.className = "tg-btn ghost";
711
+ jsonBtn.textContent = "JSON";
712
+ jsonBtn.addEventListener("click", () => opts.onExport("json"));
713
+ expRow.append(csvBtn, jsonBtn);
714
+ expField.appendChild(expRow);
715
+ box.appendChild(expField);
716
+ const act = document.createElement("div");
717
+ act.className = "tg-actions";
718
+ const clearBtn = document.createElement("button");
719
+ clearBtn.className = "tg-btn ghost";
720
+ clearBtn.textContent = t(lang, "clear");
721
+ clearBtn.addEventListener("click", () => opts.onClear());
722
+ const resetBtn = document.createElement("button");
723
+ resetBtn.className = "tg-btn ghost";
724
+ resetBtn.textContent = t(lang, "reset");
725
+ resetBtn.addEventListener("click", () => opts.onReset());
726
+ const closeBtn = document.createElement("button");
727
+ closeBtn.className = "tg-btn";
728
+ closeBtn.textContent = t(lang, "close");
729
+ closeBtn.addEventListener("click", () => exports.close());
730
+ act.append(clearBtn, resetBtn, closeBtn);
731
+ box.appendChild(act);
732
+ };
733
+ const exports = {
734
+ el: mask,
735
+ open: (state) => {
736
+ openState = state;
737
+ isOpen = true;
738
+ render(state);
739
+ mask.style.display = "flex";
740
+ },
741
+ close: () => {
742
+ isOpen = false;
743
+ mask.style.display = "none";
744
+ },
745
+ sync: (state) => {
746
+ openState = state;
747
+ if (isOpen) render(state);
748
+ }
749
+ };
750
+ mask.addEventListener("click", (e) => {
751
+ if (e.target === mask) exports.close();
752
+ });
753
+ return exports;
754
+ }
755
+
756
+ // src/client.ts
757
+ var PLUGIN_ID = "dsh-token-gauge";
758
+ function totalOf(b) {
759
+ return (b?.input || 0) + (b?.cacheRead || 0) + (b?.cacheWrite || 0) + (b?.output || 0);
760
+ }
761
+ function todayKey(d = /* @__PURE__ */ new Date()) {
762
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
763
+ }
764
+ function monthKey(d = /* @__PURE__ */ new Date()) {
765
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
766
+ }
767
+ function weekKey(d = /* @__PURE__ */ new Date()) {
768
+ const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
769
+ const dayNum = (date.getUTCDay() + 6) % 7;
770
+ date.setUTCDate(date.getUTCDate() - dayNum + 3);
771
+ const firstThursday = new Date(Date.UTC(date.getUTCFullYear(), 0, 4));
772
+ const week = 1 + Math.round(
773
+ ((date.getTime() - firstThursday.getTime()) / 864e5 - 3 + (firstThursday.getUTCDay() + 6) % 7) / 7
774
+ );
775
+ return `${d.getFullYear()}-W${String(week).padStart(2, "0")}`;
776
+ }
777
+ function fallbackState() {
778
+ return {
779
+ global: {},
780
+ daily: {},
781
+ weekly: {},
782
+ monthly: {},
783
+ sessions: {},
784
+ models: {},
785
+ activeSession: "",
786
+ settings: {
787
+ style: "ev",
788
+ palette: "neon",
789
+ lang: "zh",
790
+ metric: "global",
791
+ display: { usage: true, cost: true, io: true, cacheMiss: true },
792
+ limits: { global: null, daily: null, weekly: null, monthly: null },
793
+ pricing: {}
794
+ }
795
+ };
796
+ }
797
+ var name = PLUGIN_ID;
798
+ var inject = ["webview", "events"];
799
+ function apply(ctx) {
800
+ if (!document.getElementById("tg-gauge-style")) {
801
+ const st = document.createElement("style");
802
+ st.id = "tg-gauge-style";
803
+ st.textContent = buildCss();
804
+ document.head.appendChild(st);
805
+ }
806
+ let state = null;
807
+ const tr = (k) => t(state?.settings?.lang || "zh", k);
808
+ const root = document.createElement("div");
809
+ root.className = "tg-root tg-style-ev";
810
+ const head = document.createElement("div");
811
+ head.className = "tg-head";
812
+ const titleEl = document.createElement("div");
813
+ titleEl.className = "tg-title";
814
+ titleEl.textContent = "Token \u4EEA\u8868\u76D8";
815
+ const gear = document.createElement("div");
816
+ gear.className = "tg-gear";
817
+ gear.textContent = "\u2699";
818
+ gear.title = "\u8BBE\u7F6E";
819
+ head.append(titleEl, gear);
820
+ root.appendChild(head);
821
+ const empty = document.createElement("div");
822
+ empty.className = "tg-empty";
823
+ empty.style.display = "none";
824
+ root.appendChild(empty);
825
+ const gauge = createGauge();
826
+ root.appendChild(gauge.el);
827
+ const modal = createSettingsModal({
828
+ getLang: () => state?.settings?.lang || "zh",
829
+ onSettings: (patch) => ctx.events.emit(`${PLUGIN_ID}:cmd`, { type: "setSettings", payload: patch }),
830
+ onExport: (format) => {
831
+ if (state) downloadReport(state, state.settings.lang || "zh", format);
832
+ },
833
+ onClear: () => ctx.events.emit(`${PLUGIN_ID}:cmd`, { type: "clearUsage" }),
834
+ onReset: () => ctx.events.emit(`${PLUGIN_ID}:cmd`, { type: "resetSettings" })
835
+ });
836
+ root.appendChild(modal.el);
837
+ gear.addEventListener("click", () => modal.open(state || fallbackState()));
838
+ const computeMetric = (s) => {
839
+ const metric = s.settings.metric;
840
+ let entry = s.global;
841
+ let limit = s.settings.limits.global;
842
+ if (metric === "session") {
843
+ entry = s.sessions[s.activeSession] || {};
844
+ limit = s.settings.limits.daily;
845
+ } else if (metric === "today" || metric === "daily") {
846
+ entry = s.daily[todayKey()] || {};
847
+ limit = s.settings.limits.daily;
848
+ } else if (metric === "weekly") {
849
+ entry = s.weekly[weekKey()] || {};
850
+ limit = s.settings.limits.weekly;
851
+ } else if (metric === "monthly") {
852
+ entry = s.monthly[monthKey()] || {};
853
+ limit = s.settings.limits.monthly;
854
+ }
855
+ return { value: totalOf(entry), limit, cost: entry.cost || 0 };
856
+ };
857
+ const applyState = (s) => {
858
+ state = s;
859
+ root.className = "tg-root " + getStyle(s.settings.style).className;
860
+ applyPalette(root, s.settings.palette);
861
+ titleEl.textContent = tr("title");
862
+ const m = computeMetric(s);
863
+ gauge.update({ value: m.value, limit: m.limit, cost: m.cost, state: s, display: s.settings.display, t: tr, lang: s.settings.lang });
864
+ const hasData = totalOf(s.global) > 0;
865
+ empty.style.display = hasData ? "none" : "";
866
+ empty.textContent = tr("noData");
867
+ modal.sync(s);
868
+ };
869
+ ctx.effect(() => {
870
+ ctx.events.on(`${PLUGIN_ID}:state`, applyState);
871
+ ctx.events.emit(`${PLUGIN_ID}:cmd`, { type: "getState" });
872
+ return () => ctx.events.off(`${PLUGIN_ID}:state`, applyState);
873
+ });
874
+ ctx.webview.registerSidebarTab({
875
+ id: PLUGIN_ID,
876
+ label: "Token \u4EEA\u8868\u76D8",
877
+ render(container) {
878
+ container.appendChild(root);
879
+ }
880
+ });
881
+ }
882
+ export {
883
+ apply,
884
+ inject,
885
+ name
886
+ };