opencode-visual-cache 1.2.15-beta.0 → 1.2.15-beta.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/dist/tui.js ADDED
@@ -0,0 +1,1533 @@
1
+ // src/index.tsx
2
+ import { createComponent as _$createComponent } from "@opentui/solid";
3
+ import { effect as _$effect } from "@opentui/solid";
4
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
5
+ import { insertNode as _$insertNode } from "@opentui/solid";
6
+ import { insert as _$insert } from "@opentui/solid";
7
+ import { setProp as _$setProp } from "@opentui/solid";
8
+ import { use as _$use } from "@opentui/solid";
9
+ import { spread as _$spread } from "@opentui/solid";
10
+ import { mergeProps as _$mergeProps } from "@opentui/solid";
11
+ import { memo as _$memo } from "@opentui/solid";
12
+ import { createElement as _$createElement } from "@opentui/solid";
13
+ import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
14
+
15
+ // src/_version.ts
16
+ var PLUGIN_VERSION = "1.2.14";
17
+
18
+ // src/index.tsx
19
+ function charColumns(c) {
20
+ const code = c.codePointAt(0) ?? 0;
21
+ if (code < 32) return 0;
22
+ if (code < 127) return 1;
23
+ if (code < 160) return 0;
24
+ if (code >= 4352 && code <= 4447 || // Hangul Jamo
25
+ code >= 11904 && code <= 42191 || // CJK Radicals … Yi
26
+ code >= 44032 && code <= 55203 || // Hangul
27
+ code >= 63744 && code <= 64255 || // CJK Compat
28
+ code >= 65040 && code <= 65135 || // Vertical / Compat
29
+ code >= 65281 && code <= 65376 || // Fullwidth
30
+ code >= 65504 && code <= 65510 || // Fullwidth signs
31
+ code >= 127744 && code <= 128591 || // Misc Symbols (emoji)
32
+ code >= 131072 && code <= 262141)
33
+ return 2;
34
+ return 1;
35
+ }
36
+ function visualWidth(s) {
37
+ let w = 0;
38
+ for (const c of s) w += charColumns(c);
39
+ return w;
40
+ }
41
+ function truncateVisual(s, maxCols) {
42
+ if (visualWidth(s) <= maxCols) return s;
43
+ let result = "", w = 0;
44
+ for (const c of s) {
45
+ const cw = charColumns(c);
46
+ if (w + cw > maxCols - 1) {
47
+ result += "\u2026";
48
+ break;
49
+ }
50
+ result += c;
51
+ w += cw;
52
+ }
53
+ return result;
54
+ }
55
+ var DEBUG_LANG = typeof process !== "undefined" ? process.env?.CACHE_TUI_LANG : void 0;
56
+ var LANG_ZH = DEBUG_LANG ? DEBUG_LANG === "zh" : (() => {
57
+ try {
58
+ return Intl.DateTimeFormat().resolvedOptions().locale.startsWith("zh");
59
+ } catch {
60
+ return false;
61
+ }
62
+ })();
63
+ var ZH_T = {
64
+ title: "\u7F13\u5B58\u7EDF\u8BA1",
65
+ hit: "\u547D\u4E2D\u7387",
66
+ totalHit: "\u603B\u547D\u4E2D:",
67
+ read: "\u7F13\u5B58\u8BFB:",
68
+ write: "\u7F13\u5B58\u5199:",
69
+ miss: "\u672A\u547D\u4E2D:",
70
+ out: "\u8F93\u51FA:",
71
+ cost: "\u8D39\u7528:",
72
+ saved: "\u7D2F\u8BA1\u8282\u7701:",
73
+ model: "\u6A21\u578B:",
74
+ provider: "\u63D0\u4F9B\u5546:",
75
+ rate: "\u5355\u4EF7:",
76
+ hitFolded: "\u547D\u4E2D",
77
+ inputRate: "\u8F93\u5165",
78
+ cacheRate: "\u7F13\u5B58",
79
+ writeRate: "\u5199\u5165",
80
+ noData: "\u7B49\u5F85\u7F13\u5B58\u6570\u636E...",
81
+ tok: "tok",
82
+ distTitle: "\u4F30\u7B97 Token \u5206\u5E03",
83
+ distSys: "\u7CFB\u7EDF\u63D0\u793A:",
84
+ distUser: "\u7528\u6237:",
85
+ distAgent: "Agent \u6307\u4EE4:",
86
+ distTool: "Tool \u8C03\u7528:",
87
+ distRes: "Tool \u7ED3\u679C:",
88
+ distTotal: "\u603B\u8BA1:",
89
+ distOut: "\u8F93\u51FA:",
90
+ secDetail: "\u660E\u7EC6",
91
+ secModel: "\u6A21\u578B",
92
+ secSkills: "\u5DF2\u52A0\u8F7D\u6280\u80FD"
93
+ };
94
+ var EN_T = {
95
+ title: "Token Cache",
96
+ hit: "Hit",
97
+ totalHit: "Total Hit:",
98
+ read: "Read:",
99
+ write: "Write:",
100
+ miss: "Miss:",
101
+ out: "Out:",
102
+ cost: "Cost:",
103
+ saved: "Total Saved:",
104
+ model: "Model:",
105
+ provider: "Provider:",
106
+ rate: "Rate:",
107
+ hitFolded: "hit",
108
+ inputRate: "in",
109
+ cacheRate: "cache",
110
+ writeRate: "write",
111
+ noData: "Waiting for cache data...",
112
+ tok: "tok",
113
+ distTitle: "Estimated Token Dist.",
114
+ distSys: "System:",
115
+ distUser: "User:",
116
+ distAgent: "Agent Instr:",
117
+ distTool: "Tool Call:",
118
+ distRes: "Tool Result:",
119
+ distTotal: "Total:",
120
+ distOut: "Output:",
121
+ secDetail: "Detail",
122
+ secModel: "Model",
123
+ secSkills: "Loaded Skills"
124
+ };
125
+ function rgb(raw) {
126
+ if (typeof raw === "string" && raw.startsWith("#")) {
127
+ const h = raw.slice(1);
128
+ return {
129
+ r: parseInt(h.slice(0, 2), 16),
130
+ g: parseInt(h.slice(2, 4), 16),
131
+ b: parseInt(h.slice(4, 6), 16)
132
+ };
133
+ }
134
+ if (raw && typeof raw === "object") {
135
+ const o = raw;
136
+ if (typeof o.r === "number" && typeof o.g === "number" && typeof o.b === "number") {
137
+ const scale = o.r > 1 || o.g > 1 || o.b > 1 ? 1 : 255;
138
+ return {
139
+ r: Math.round(o.r * scale),
140
+ g: Math.round(o.g * scale),
141
+ b: Math.round(o.b * scale)
142
+ };
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ function saturation(r, g, b) {
148
+ const max = Math.max(r, g, b) / 255;
149
+ const min = Math.min(r, g, b) / 255;
150
+ const delta = max - min;
151
+ if (delta === 0) return 0;
152
+ const L = (max + min) / 2;
153
+ return L <= 0.5 ? delta / (max + min) : delta / (2 - max - min);
154
+ }
155
+ function desaturateTo(raw, maxSat, fallback) {
156
+ const c = rgb(raw);
157
+ if (!c) return fallback;
158
+ const sat = saturation(c.r, c.g, c.b);
159
+ if (sat <= maxSat) {
160
+ return "#" + [c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, "0")).join("");
161
+ }
162
+ const luma = c.r * 0.299 + c.g * 0.587 + c.b * 0.114;
163
+ let lo = 0, hi = 1;
164
+ for (let i = 0; i < 12; i++) {
165
+ const mid = (lo + hi) / 2;
166
+ const nr2 = Math.round(c.r + (luma - c.r) * mid);
167
+ const ng2 = Math.round(c.g + (luma - c.g) * mid);
168
+ const nb2 = Math.round(c.b + (luma - c.b) * mid);
169
+ if (saturation(nr2, ng2, nb2) > maxSat) lo = mid;
170
+ else hi = mid;
171
+ }
172
+ const nr = Math.round(c.r + (luma - c.r) * hi);
173
+ const ng = Math.round(c.g + (luma - c.g) * hi);
174
+ const nb = Math.round(c.b + (luma - c.b) * hi);
175
+ return "#" + [nr, ng, nb].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
176
+ }
177
+ var FALLBACK = {
178
+ primary: "#8B9DAF",
179
+ text: "#C5C5BB",
180
+ muted: "#7A7A72",
181
+ success: "#9CAF8B",
182
+ warning: "#C5B88D",
183
+ error: "#B08A8A",
184
+ border: "#6B6B63"
185
+ };
186
+ var MAX_SAT = 0.28;
187
+ function progressBar(percent, width) {
188
+ const clamped = Math.max(0, Math.min(100, percent));
189
+ const filled = Math.round(clamped / 100 * width);
190
+ const empty = Math.max(0, width - filled);
191
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
192
+ }
193
+ function fmt(n) {
194
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
195
+ if (n >= 1e4) return (n / 1e3).toFixed(1) + "K";
196
+ return n.toLocaleString("en-US");
197
+ }
198
+ function num(v) {
199
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
200
+ }
201
+ function fmtCost(n, symbol = "$", rate = 1) {
202
+ const v = n * rate;
203
+ if (v >= 1) return symbol + v.toFixed(2);
204
+ if (v >= 0.01) return symbol + v.toFixed(3);
205
+ return symbol + v.toFixed(4);
206
+ }
207
+ function estimateTokens(text) {
208
+ if (!text || text.length === 0) return 0;
209
+ let ascii = 0;
210
+ let cjk = 0;
211
+ for (const c of text) {
212
+ const code = c.codePointAt(0) ?? 0;
213
+ if (code >= 19968 && code <= 40959) cjk++;
214
+ else if (code >= 12352 && code <= 12543) cjk++;
215
+ else if (code >= 44032 && code <= 55203) cjk++;
216
+ else if (code >= 4352 && code <= 4607) cjk++;
217
+ else if (code >= 11904 && code <= 12031) cjk++;
218
+ else ascii++;
219
+ }
220
+ const trimmed = text.trimStart();
221
+ const strippedFence = trimmed.replace(/^\x60{3}\w*\s*\n?/, "");
222
+ const jsonLike = (strippedFence.startsWith("{") || strippedFence.startsWith("[")) && /"[^"]+"\s*:/.test(text);
223
+ const codeLike = !jsonLike && /```|^import |^export |^function |^const |^let |^var |^class |^interface |^type |^def |^fn |^pub |^use |^mod |^package /m.test(text);
224
+ const asciiPerToken = jsonLike ? 3.5 : codeLike ? 3.5 : 4;
225
+ return Math.max(1, Math.ceil(ascii / asciiPerToken + cjk / 1));
226
+ }
227
+ var CURRENCIES = {
228
+ USD: "$",
229
+ CNY: "\xA5",
230
+ EUR: "\u20AC",
231
+ JPY: "JP\xA5",
232
+ GBP: "\xA3",
233
+ KRW: "\u20A9"
234
+ };
235
+ var DEFAULT_RATES = {
236
+ USD: 1,
237
+ CNY: 7.2,
238
+ EUR: 0.92,
239
+ JPY: 150,
240
+ GBP: 0.79,
241
+ KRW: 1350
242
+ };
243
+ var MIN_PANEL_WIDTH = 20;
244
+ var DEFAULT_PANEL_WIDTH = 26;
245
+ var LABEL_GAP = 1;
246
+ var BAR_BRACKETS = 2;
247
+ var BAR_GAP = 1;
248
+ var PCT_FIXED_WIDTH = 5;
249
+ var HEADER_PREFIX = 2;
250
+ var UNIT_GAP = 1;
251
+ function TokenCachePanel(props) {
252
+ const [panelWidth, setPanelWidth] = createSignal(DEFAULT_PANEL_WIDTH);
253
+ const [open, setOpen] = createSignal(true);
254
+ const [detailOpen, setDetailOpen] = createSignal(true);
255
+ const [modelOpen, setModelOpen] = createSignal(true);
256
+ const [distOpen, setDistOpen] = createSignal(false);
257
+ const [skillsOpen, setSkillsOpen] = createSignal(true);
258
+ let boxEl;
259
+ const {
260
+ currencySymbol,
261
+ setCurrencySymbol,
262
+ exchangeRate,
263
+ setExchangeRate,
264
+ langZH,
265
+ setLangZH,
266
+ sectionDetail,
267
+ setSectionDetail,
268
+ sectionModel,
269
+ setSectionModel,
270
+ sectionDist,
271
+ setSectionDist,
272
+ sectionSkills,
273
+ setSectionSkills,
274
+ borderVisible,
275
+ setBorderVisible
276
+ } = props.signals;
277
+ const t = createMemo(() => langZH() ? ZH_T : EN_T);
278
+ const [lastDist, setLastDist] = createSignal({
279
+ system: 0,
280
+ user: 0,
281
+ agent: 0,
282
+ toolCall: 0,
283
+ toolResult: 0,
284
+ output: 0,
285
+ apiOutput: 0,
286
+ apiInput: 0,
287
+ stepCost: 0
288
+ });
289
+ const [lastHasDist, setLastHasDist] = createSignal(false);
290
+ const [dataSignal, setDataSignal] = createSignal({
291
+ hitRate: 0,
292
+ read: 0,
293
+ write: 0,
294
+ freshInput: 0,
295
+ output: 0,
296
+ cost: 0,
297
+ saved: 0,
298
+ model: "",
299
+ inputRate: 0,
300
+ cacheReadRate: 0,
301
+ cacheWriteRate: 0,
302
+ hasPricing: false,
303
+ hasData: false,
304
+ trend: 0,
305
+ hasTrendData: false,
306
+ providerName: "",
307
+ sessionHitRate: 0,
308
+ dist: {
309
+ system: 0,
310
+ user: 0,
311
+ agent: 0,
312
+ toolCall: 0,
313
+ toolResult: 0,
314
+ output: 0,
315
+ apiOutput: 0,
316
+ apiInput: 0,
317
+ stepCost: 0
318
+ },
319
+ hasDistData: false,
320
+ skills: [],
321
+ hasSkills: false
322
+ });
323
+ const [refreshTick, setRefreshTick] = createSignal(0);
324
+ createEffect(() => {
325
+ const sid = props.sessionId;
326
+ void refreshTick();
327
+ void partVersion();
328
+ const msgs = props.api.state.session.messages(sid);
329
+ const session = typeof props.api.state.session.get === "function" ? props.api.state.session.get(sid) : void 0;
330
+ let input = session?.tokens?.input ?? 0;
331
+ let read = session?.tokens?.cache?.read ?? 0;
332
+ let write = session?.tokens?.cache?.write ?? 0;
333
+ let output = session?.tokens?.output ?? 0;
334
+ let cost = session?.cost ?? 0;
335
+ let pid = session?.model?.providerID ?? "";
336
+ let mid = session?.model?.id ?? "";
337
+ const fallbackTokens = session?.tokens == null;
338
+ const fallbackCost = session?.cost == null;
339
+ const fallbackModel = !pid || !mid;
340
+ let prevMsgHitRate = -1, lastMsgHitRate = -1;
341
+ for (const msg of msgs) {
342
+ if (msg.role !== "assistant") continue;
343
+ const t2 = msg.tokens;
344
+ if (!t2) continue;
345
+ const mit = num(t2.input) + num(t2.cache?.read), mrt = num(t2.cache?.read);
346
+ if (mit > 0) {
347
+ prevMsgHitRate = lastMsgHitRate;
348
+ lastMsgHitRate = mrt / mit * 100;
349
+ }
350
+ if (fallbackTokens) {
351
+ input += num(t2.input);
352
+ read += num(t2.cache?.read);
353
+ write += num(t2.cache?.write);
354
+ output += num(t2.output);
355
+ }
356
+ if (fallbackCost) {
357
+ cost += num(msg.cost);
358
+ }
359
+ if (fallbackModel && msg.providerID && msg.modelID) {
360
+ pid = msg.providerID;
361
+ mid = msg.modelID;
362
+ }
363
+ }
364
+ let saved = 0, inputRate = 0, cacheReadRate = 0, cacheWriteRate = 0;
365
+ if (read > 0 && pid && mid && Array.isArray(props.api.state.provider)) for (const provider of props.api.state.provider) {
366
+ if (provider.id !== pid) continue;
367
+ const model2 = provider.models[mid];
368
+ if (!model2?.cost) continue;
369
+ inputRate = num(model2.cost.input);
370
+ cacheReadRate = num(model2.cost.cache?.read);
371
+ cacheWriteRate = num(model2.cost.cache?.write);
372
+ if (inputRate > cacheReadRate) saved = read * (inputRate - cacheReadRate) / 1e6;
373
+ break;
374
+ }
375
+ const hitRate = lastMsgHitRate >= 0 ? lastMsgHitRate : 0;
376
+ const freshTotal = input + read, sessionHitRate = freshTotal > 0 ? read / freshTotal * 100 : 0;
377
+ const model = mid.split("/").pop() ?? mid, hasPricing = inputRate > 0 || cacheReadRate > 0 || cacheWriteRate > 0;
378
+ const hasTrendData = prevMsgHitRate >= 0 && lastMsgHitRate >= 0;
379
+ const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName = pid || "";
380
+ const distData = untrack(() => {
381
+ let dist = {
382
+ system: 0,
383
+ user: 0,
384
+ agent: 0,
385
+ toolCall: 0,
386
+ toolResult: 0,
387
+ output: 0,
388
+ apiOutput: 0,
389
+ apiInput: 0,
390
+ stepCost: 0
391
+ };
392
+ let hasDistData = false;
393
+ const loadedSkills = /* @__PURE__ */ new Map();
394
+ try {
395
+ const cfg = props.api.state.config;
396
+ const agentName = String(session?.agent ?? cfg?.default_agent ?? "build");
397
+ const agents = cfg?.agent;
398
+ const agentCfg = agents?.[agentName];
399
+ const sysPrompt = typeof agentCfg?.prompt === "string" ? agentCfg.prompt : "";
400
+ if (sysPrompt) dist.system = estimateTokens(sysPrompt);
401
+ let lastAssMsg;
402
+ for (const msg of msgs) {
403
+ if (msg.role === "user") {
404
+ const um = msg;
405
+ if (um.system) dist.system += estimateTokens(um.system);
406
+ let parts = [];
407
+ try {
408
+ parts = props.api.state.part(msg.id);
409
+ } catch {
410
+ }
411
+ for (const p of parts) {
412
+ if (p.type === "text" && !p.synthetic && !p.ignored) dist.user += estimateTokens(p.text);
413
+ else if (p.type === "file") {
414
+ const fp = p;
415
+ if (fp.source?.text?.value) dist.user += estimateTokens(fp.source.text.value);
416
+ }
417
+ }
418
+ } else if (msg.role === "assistant") {
419
+ const am = msg;
420
+ dist.output += num(am.tokens?.output);
421
+ let parts = [];
422
+ try {
423
+ parts = props.api.state.part(msg.id);
424
+ } catch {
425
+ }
426
+ for (const p of parts) {
427
+ if (p.type === "tool") {
428
+ const tp = p;
429
+ let rawInput = "";
430
+ try {
431
+ rawInput = tp.state.raw ?? (tp.state.input != null ? JSON.stringify(tp.state.input) : "");
432
+ } catch {
433
+ }
434
+ if (rawInput) dist.toolCall += estimateTokens(rawInput);
435
+ if (tp.state.status === "completed") {
436
+ const c = tp.state;
437
+ if (c.output) dist.toolResult += estimateTokens(c.output);
438
+ } else if (tp.state.status === "error") {
439
+ const e = tp.state;
440
+ if (e.error) dist.toolResult += estimateTokens(e.error);
441
+ }
442
+ if (tp.tool === "skill" && tp.state.status === "completed") {
443
+ let name = tp.state.metadata?.name;
444
+ if (typeof name !== "string") {
445
+ const m = typeof tp.state.output === "string" ? tp.state.output.match(/^#{1,2}\s*Skill:\s*(.+)/m) : null;
446
+ if (m) name = m[1].trim();
447
+ }
448
+ if (typeof name === "string") {
449
+ const tokens = typeof tp.state.output === "string" ? estimateTokens(tp.state.output) : 0;
450
+ const existing = loadedSkills.get(name);
451
+ if (!existing || existing.tokens < tokens) {
452
+ loadedSkills.set(name, {
453
+ name,
454
+ tokens
455
+ });
456
+ }
457
+ }
458
+ }
459
+ } else if (p.type === "reasoning") dist.agent += estimateTokens(p.text);
460
+ else if (p.type === "subtask") {
461
+ const sub = p;
462
+ dist.agent += estimateTokens(sub.prompt || sub.description || "");
463
+ }
464
+ }
465
+ }
466
+ }
467
+ for (let i = msgs.length - 1; i >= 0; i--) {
468
+ if (msgs[i].role !== "assistant") continue;
469
+ const t2 = msgs[i].tokens;
470
+ if (t2 && (t2.input > 0 || (t2.cache?.read ?? 0) > 0)) {
471
+ lastAssMsg = msgs[i];
472
+ break;
473
+ }
474
+ }
475
+ dist.apiInput = num(lastAssMsg?.tokens?.input) + num(lastAssMsg?.tokens?.cache?.read);
476
+ dist.apiOutput = num(lastAssMsg?.tokens?.output);
477
+ hasDistData = dist.system + dist.user + dist.agent + dist.toolCall + dist.toolResult > 0 || dist.apiOutput > 0 || dist.apiInput > 0;
478
+ } catch {
479
+ }
480
+ const finalDist = hasDistData ? dist : lastDist(), finalHasDist = hasDistData || lastHasDist();
481
+ const skills = [...loadedSkills.values()];
482
+ return {
483
+ finalDist,
484
+ finalHasDist,
485
+ skills
486
+ };
487
+ });
488
+ setDataSignal({
489
+ hitRate,
490
+ read,
491
+ write,
492
+ freshInput: input,
493
+ output,
494
+ cost,
495
+ saved,
496
+ model,
497
+ inputRate,
498
+ cacheReadRate,
499
+ cacheWriteRate,
500
+ hasPricing,
501
+ hasData: read > 0 || write > 0 || input > 0 || output > 0 || cost > 0,
502
+ trend,
503
+ hasTrendData,
504
+ providerName,
505
+ sessionHitRate,
506
+ dist: distData.finalDist,
507
+ hasDistData: distData.finalHasDist,
508
+ skills: distData.skills,
509
+ hasSkills: distData.skills.length > 0
510
+ });
511
+ });
512
+ const data = createMemo(() => {
513
+ return dataSignal();
514
+ });
515
+ createEffect(() => {
516
+ const d = data();
517
+ if (d.hasDistData) {
518
+ setLastDist({
519
+ ...d.dist
520
+ });
521
+ setLastHasDist(true);
522
+ try {
523
+ props.api.kv.set(`${KV_PREFIX}.dist_snapshot`, {
524
+ ...d.dist
525
+ });
526
+ } catch {
527
+ }
528
+ }
529
+ });
530
+ const [partVersion, setPartVersion] = createSignal(0);
531
+ const KV_PREFIX = "cache_panel";
532
+ const persistFold = (key, val) => {
533
+ try {
534
+ props.api.kv.set(`${KV_PREFIX}.${key}`, val);
535
+ } catch {
536
+ }
537
+ };
538
+ onMount(() => {
539
+ setPanelWidth(DEFAULT_PANEL_WIDTH);
540
+ try {
541
+ setOpen(Boolean(props.api.kv.get(`${KV_PREFIX}.open`, false)));
542
+ setDetailOpen(Boolean(props.api.kv.get(`${KV_PREFIX}.detail`, true)));
543
+ setModelOpen(Boolean(props.api.kv.get(`${KV_PREFIX}.model`, true)));
544
+ setDistOpen(Boolean(props.api.kv.get(`${KV_PREFIX}.dist`, false)));
545
+ setSkillsOpen(Boolean(props.api.kv.get(`${KV_PREFIX}.skills`, true)));
546
+ } catch {
547
+ }
548
+ const doRestore = () => {
549
+ try {
550
+ const sym = props.api.kv.get(`${KV_PREFIX}.currency`);
551
+ const rate = props.api.kv.get(`${KV_PREFIX}.rate`);
552
+ if (typeof sym === "string") setCurrencySymbol(sym);
553
+ if (typeof rate === "number" && rate > 0) setExchangeRate(rate);
554
+ setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)));
555
+ setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)));
556
+ setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)));
557
+ setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX}.section.skills`, true)));
558
+ const bv = props.api.kv.get(`${KV_PREFIX}.border`, true);
559
+ setBorderVisible(bv !== false);
560
+ const savedLang = props.api.kv.get(`${KV_PREFIX}.lang`);
561
+ if (savedLang === "zh" || savedLang === "en") {
562
+ setLangZH(savedLang === "zh");
563
+ }
564
+ const cachedDist = props.api.kv.get(`${KV_PREFIX}.dist_snapshot`);
565
+ if (cachedDist) {
566
+ setLastDist(cachedDist);
567
+ setLastHasDist(true);
568
+ }
569
+ } catch {
570
+ }
571
+ if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
572
+ setPanelWidth(Math.max(MIN_PANEL_WIDTH, boxEl.width));
573
+ }
574
+ };
575
+ if (props.api.kv.ready) {
576
+ doRestore();
577
+ } else {
578
+ const MAX_POLL = 100;
579
+ let tries = 0;
580
+ const pollRestore = () => {
581
+ if (!props.api.kv.ready) {
582
+ if (++tries > MAX_POLL) {
583
+ doRestore();
584
+ return;
585
+ }
586
+ setTimeout(pollRestore, 10);
587
+ return;
588
+ }
589
+ doRestore();
590
+ };
591
+ pollRestore();
592
+ }
593
+ let partTimer;
594
+ const bumpPartVersion = () => {
595
+ clearTimeout(partTimer);
596
+ partTimer = setTimeout(() => setPartVersion((v) => v + 1), 100);
597
+ };
598
+ const unsubPart = props.api.event.on("message.part.updated", () => {
599
+ bumpPartVersion();
600
+ setRefreshTick((v) => v + 1);
601
+ });
602
+ const unsubMsg = props.api.event.on("message.updated", () => {
603
+ bumpPartVersion();
604
+ setRefreshTick((v) => v + 1);
605
+ });
606
+ const unsubSession = props.api.event.on("session.updated", () => {
607
+ setRefreshTick((v) => v + 1);
608
+ });
609
+ setRefreshTick((v) => v + 1);
610
+ onCleanup(() => {
611
+ clearTimeout(partTimer);
612
+ unsubPart();
613
+ unsubMsg();
614
+ unsubSession();
615
+ });
616
+ });
617
+ const pal = createMemo(() => {
618
+ const t2 = props.theme;
619
+ const sat = (k, fb) => desaturateTo(t2[k], MAX_SAT, fb);
620
+ return {
621
+ primary: sat("primary", FALLBACK.primary),
622
+ text: sat("text", FALLBACK.text),
623
+ muted: sat("textMuted", FALLBACK.muted),
624
+ success: sat("success", FALLBACK.success),
625
+ warning: sat("warning", FALLBACK.warning),
626
+ error: sat("error", FALLBACK.error),
627
+ border: sat("border", FALLBACK.border)
628
+ };
629
+ });
630
+ const hitColor = createMemo(() => {
631
+ const r = data().hitRate;
632
+ if (r >= 85) return pal().success;
633
+ if (r >= 70) return pal().warning;
634
+ return pal().error;
635
+ });
636
+ const gutter = createMemo(() => borderVisible() ? 6 : 0);
637
+ const sep = createMemo(() => "\u2500".repeat(Math.max(1, panelWidth() - gutter())));
638
+ function trendLabel(t2) {
639
+ return (t2 > 0 ? "\u2191" : t2 < 0 ? "\u2193" : "-") + (t2 !== 0 ? Math.abs(t2).toFixed(1) + "%" : "");
640
+ }
641
+ const barW = createMemo(() => {
642
+ const trendSpace = data().hasTrendData ? LABEL_GAP + visualWidth(trendLabel(data().trend)) : 0;
643
+ const overhead = visualWidth(t().hit) + LABEL_GAP + BAR_BRACKETS + BAR_GAP + PCT_FIXED_WIDTH + trendSpace + gutter();
644
+ return Math.max(3, panelWidth() - overhead);
645
+ });
646
+ const bar = createMemo(() => progressBar(data().hitRate, barW()));
647
+ const pct = createMemo(() => (Math.floor(data().hitRate * 10) / 10).toFixed(1) + "%");
648
+ createEffect(() => {
649
+ borderVisible();
650
+ if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
651
+ const w = Math.max(MIN_PANEL_WIDTH, boxEl.width);
652
+ setPanelWidth((prev) => prev === w ? prev : w);
653
+ }
654
+ });
655
+ const justify = (label, value, unit = "") => {
656
+ const gauge = panelWidth() - gutter();
657
+ const used = visualWidth(label) + visualWidth(value) + (unit ? visualWidth(unit) + UNIT_GAP : 0);
658
+ const gap = Math.max(1, gauge - used);
659
+ return label + " ".repeat(gap) + value + (unit ? " " + unit : "");
660
+ };
661
+ return (() => {
662
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("span"), _el$4 = _$createElement("span"), _el$5 = _$createElement("b");
663
+ _$insertNode(_el$, _el$2);
664
+ var _ref$ = boxEl;
665
+ typeof _ref$ === "function" ? _$use(_ref$, _el$) : boxEl = _el$;
666
+ _$spread(_el$, _$mergeProps({
667
+ get border() {
668
+ return borderVisible();
669
+ }
670
+ }, () => borderVisible() ? {
671
+ borderColor: pal().border
672
+ } : {}, {
673
+ "paddingTop": 0,
674
+ "paddingBottom": 0,
675
+ get paddingLeft() {
676
+ return borderVisible() ? 2 : 0;
677
+ },
678
+ get paddingRight() {
679
+ return borderVisible() ? 2 : 0;
680
+ },
681
+ "flexDirection": "column",
682
+ "gap": 0,
683
+ "onSizeChange": () => {
684
+ const w = boxEl ? Math.max(MIN_PANEL_WIDTH, boxEl.width ?? 0) : DEFAULT_PANEL_WIDTH;
685
+ setPanelWidth((prev) => prev === w ? prev : w);
686
+ }
687
+ }), true);
688
+ _$insertNode(_el$2, _el$3);
689
+ _$insertNode(_el$2, _el$4);
690
+ _$setProp(_el$2, "onMouseUp", () => setOpen((o) => {
691
+ const n = !o;
692
+ persistFold("open", n);
693
+ return n;
694
+ }));
695
+ _$insert(_el$3, () => open() ? "\u25BC " : "\u25B6 ");
696
+ _$insertNode(_el$4, _el$5);
697
+ _$insert(_el$5, () => t().title);
698
+ _$insert(_el$4, _$createComponent(Show, {
699
+ get when() {
700
+ return open();
701
+ },
702
+ get children() {
703
+ var _el$6 = _$createElement("span"), _el$7 = _$createTextNode(` (v`), _el$8 = _$createTextNode(`)`);
704
+ _$insertNode(_el$6, _el$7);
705
+ _$insertNode(_el$6, _el$8);
706
+ _$insert(_el$6, PLUGIN_VERSION, _el$8);
707
+ _$effect((_$p) => _$setProp(_el$6, "style", {
708
+ fg: pal().muted
709
+ }, _$p));
710
+ return _el$6;
711
+ }
712
+ }), null);
713
+ _$insert(_el$2, _$createComponent(Show, {
714
+ get when() {
715
+ return _$memo(() => !!!open())() && data().hasData;
716
+ },
717
+ get children() {
718
+ return [_$createComponent(Show, {
719
+ get when() {
720
+ return data().hasTrendData;
721
+ },
722
+ get children() {
723
+ return [(() => {
724
+ var _el$9 = _$createElement("span");
725
+ _$insert(_el$9, () => " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t().title) - visualWidth(pct() + " " + t().hitFolded + " " + trendLabel(data().trend)))));
726
+ return _el$9;
727
+ })(), (() => {
728
+ var _el$0 = _$createElement("span"), _el$1 = _$createTextNode(` `);
729
+ _$insertNode(_el$0, _el$1);
730
+ _$insert(_el$0, pct, _el$1);
731
+ _$insert(_el$0, () => t().hitFolded, null);
732
+ _$effect((_$p) => _$setProp(_el$0, "style", {
733
+ fg: hitColor()
734
+ }, _$p));
735
+ return _el$0;
736
+ })(), (() => {
737
+ var _el$10 = _$createElement("span"), _el$11 = _$createTextNode(` `);
738
+ _$insertNode(_el$10, _el$11);
739
+ _$insert(_el$10, () => trendLabel(data().trend), null);
740
+ _$effect((_$p) => _$setProp(_el$10, "style", {
741
+ fg: data().trend !== 0 ? data().trend > 0 ? pal().success : pal().error : pal().text
742
+ }, _$p));
743
+ return _el$10;
744
+ })()];
745
+ }
746
+ }), _$createComponent(Show, {
747
+ get when() {
748
+ return !data().hasTrendData;
749
+ },
750
+ get children() {
751
+ return [(() => {
752
+ var _el$12 = _$createElement("span");
753
+ _$insert(_el$12, () => " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t().title) - visualWidth(pct() + " " + t().hitFolded))));
754
+ return _el$12;
755
+ })(), (() => {
756
+ var _el$13 = _$createElement("span"), _el$14 = _$createTextNode(` `);
757
+ _$insertNode(_el$13, _el$14);
758
+ _$insert(_el$13, pct, _el$14);
759
+ _$insert(_el$13, () => t().hitFolded, null);
760
+ _$effect((_$p) => _$setProp(_el$13, "style", {
761
+ fg: hitColor()
762
+ }, _$p));
763
+ return _el$13;
764
+ })()];
765
+ }
766
+ })];
767
+ }
768
+ }), null);
769
+ _$insert(_el$, _$createComponent(Show, {
770
+ get when() {
771
+ return open();
772
+ },
773
+ get children() {
774
+ return _$createComponent(Show, {
775
+ get when() {
776
+ return data().hasData;
777
+ },
778
+ get fallback() {
779
+ return [(() => {
780
+ var _el$52 = _$createElement("text");
781
+ _$insert(_el$52, sep);
782
+ _$effect((_$p) => _$setProp(_el$52, "fg", pal().muted, _$p));
783
+ return _el$52;
784
+ })(), (() => {
785
+ var _el$53 = _$createElement("text"), _el$54 = _$createElement("span"), _el$56 = _$createElement("span");
786
+ _$insertNode(_el$53, _el$54);
787
+ _$insertNode(_el$53, _el$56);
788
+ _$insertNode(_el$54, _$createTextNode(`> `));
789
+ _$insert(_el$56, () => t().noData);
790
+ _$effect((_p$) => {
791
+ var _v$1 = {
792
+ fg: pal().muted
793
+ }, _v$10 = {
794
+ fg: pal().muted
795
+ };
796
+ _v$1 !== _p$.e && (_p$.e = _$setProp(_el$54, "style", _v$1, _p$.e));
797
+ _v$10 !== _p$.t && (_p$.t = _$setProp(_el$56, "style", _v$10, _p$.t));
798
+ return _p$;
799
+ }, {
800
+ e: void 0,
801
+ t: void 0
802
+ });
803
+ return _el$53;
804
+ })()];
805
+ },
806
+ get children() {
807
+ return [(() => {
808
+ var _el$15 = _$createElement("text");
809
+ _$insert(_el$15, sep);
810
+ _$effect((_$p) => _$setProp(_el$15, "fg", pal().muted, _$p));
811
+ return _el$15;
812
+ })(), (() => {
813
+ var _el$16 = _$createElement("text"), _el$17 = _$createElement("span"), _el$18 = _$createTextNode(` `), _el$19 = _$createElement("span"), _el$20 = _$createTextNode(`[`), _el$21 = _$createTextNode(`] `), _el$22 = _$createElement("span");
814
+ _$insertNode(_el$16, _el$17);
815
+ _$insertNode(_el$16, _el$19);
816
+ _$insertNode(_el$16, _el$22);
817
+ _$insertNode(_el$17, _el$18);
818
+ _$insert(_el$17, () => t().hit, _el$18);
819
+ _$insertNode(_el$19, _el$20);
820
+ _$insertNode(_el$19, _el$21);
821
+ _$insert(_el$19, bar, _el$21);
822
+ _$insert(_el$22, pct);
823
+ _$insert(_el$16, _$createComponent(Show, {
824
+ get when() {
825
+ return data().hasTrendData;
826
+ },
827
+ get children() {
828
+ var _el$23 = _$createElement("span"), _el$24 = _$createTextNode(` `);
829
+ _$insertNode(_el$23, _el$24);
830
+ _$insert(_el$23, () => trendLabel(data().trend), null);
831
+ _$effect((_$p) => _$setProp(_el$23, "style", {
832
+ fg: data().trend !== 0 ? data().trend > 0 ? pal().success : pal().error : pal().text
833
+ }, _$p));
834
+ return _el$23;
835
+ }
836
+ }), null);
837
+ _$effect((_p$) => {
838
+ var _v$ = {
839
+ fg: pal().text
840
+ }, _v$2 = {
841
+ fg: hitColor()
842
+ }, _v$3 = {
843
+ fg: pal().text
844
+ };
845
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$17, "style", _v$, _p$.e));
846
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$19, "style", _v$2, _p$.t));
847
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$22, "style", _v$3, _p$.a));
848
+ return _p$;
849
+ }, {
850
+ e: void 0,
851
+ t: void 0,
852
+ a: void 0
853
+ });
854
+ return _el$16;
855
+ })(), (() => {
856
+ var _el$25 = _$createElement("text");
857
+ _$insert(_el$25, () => justify(t().totalHit, (Math.floor(data().sessionHitRate * 10) / 10).toFixed(1) + "%"));
858
+ _$effect((_$p) => _$setProp(_el$25, "fg", pal().muted, _$p));
859
+ return _el$25;
860
+ })(), _$createComponent(Show, {
861
+ get when() {
862
+ return sectionDetail();
863
+ },
864
+ get children() {
865
+ return [(() => {
866
+ var _el$26 = _$createElement("text"), _el$27 = _$createElement("span"), _el$28 = _$createElement("span"), _el$29 = _$createElement("b"), _el$30 = _$createElement("span");
867
+ _$insertNode(_el$26, _el$27);
868
+ _$insertNode(_el$26, _el$28);
869
+ _$insertNode(_el$26, _el$30);
870
+ _$setProp(_el$26, "onMouseUp", () => setDetailOpen((o) => {
871
+ const n = !o;
872
+ persistFold("detail", n);
873
+ return n;
874
+ }));
875
+ _$insert(_el$27, () => detailOpen() ? "\u25BC " : "\u25B6 ");
876
+ _$insertNode(_el$28, _el$29);
877
+ _$insert(_el$29, () => t().secDetail);
878
+ _$insert(_el$30, () => sep().slice(visualWidth((detailOpen() ? "\u25BC " : "\u25B6 ") + t().secDetail)));
879
+ _$effect((_p$) => {
880
+ var _v$4 = {
881
+ fg: pal().muted
882
+ }, _v$5 = {
883
+ fg: pal().primary
884
+ }, _v$6 = {
885
+ fg: pal().muted
886
+ };
887
+ _v$4 !== _p$.e && (_p$.e = _$setProp(_el$27, "style", _v$4, _p$.e));
888
+ _v$5 !== _p$.t && (_p$.t = _$setProp(_el$28, "style", _v$5, _p$.t));
889
+ _v$6 !== _p$.a && (_p$.a = _$setProp(_el$30, "style", _v$6, _p$.a));
890
+ return _p$;
891
+ }, {
892
+ e: void 0,
893
+ t: void 0,
894
+ a: void 0
895
+ });
896
+ return _el$26;
897
+ })(), _$createComponent(Show, {
898
+ get when() {
899
+ return detailOpen();
900
+ },
901
+ get children() {
902
+ return [_$createComponent(Show, {
903
+ get when() {
904
+ return data().read > 0;
905
+ },
906
+ get children() {
907
+ var _el$31 = _$createElement("text");
908
+ _$insert(_el$31, () => justify(t().read, fmt(data().read), t().tok));
909
+ _$effect((_$p) => _$setProp(_el$31, "fg", pal().muted, _$p));
910
+ return _el$31;
911
+ }
912
+ }), _$createComponent(Show, {
913
+ get when() {
914
+ return data().write > 0;
915
+ },
916
+ get children() {
917
+ var _el$32 = _$createElement("text");
918
+ _$insert(_el$32, () => justify(t().write, fmt(data().write), t().tok));
919
+ _$effect((_$p) => _$setProp(_el$32, "fg", pal().muted, _$p));
920
+ return _el$32;
921
+ }
922
+ }), (() => {
923
+ var _el$33 = _$createElement("text");
924
+ _$insert(_el$33, () => justify(t().miss, fmt(data().freshInput), t().tok));
925
+ _$effect((_$p) => _$setProp(_el$33, "fg", pal().muted, _$p));
926
+ return _el$33;
927
+ })(), (() => {
928
+ var _el$34 = _$createElement("text");
929
+ _$insert(_el$34, () => justify(t().out, fmt(data().output), t().tok));
930
+ _$effect((_$p) => _$setProp(_el$34, "fg", pal().muted, _$p));
931
+ return _el$34;
932
+ })(), _$createComponent(Show, {
933
+ get when() {
934
+ return data().saved > 0;
935
+ },
936
+ get children() {
937
+ var _el$35 = _$createElement("text"), _el$36 = _$createElement("span"), _el$37 = _$createElement("span"), _el$38 = _$createElement("span"), _el$39 = _$createTextNode(`~`);
938
+ _$insertNode(_el$35, _el$36);
939
+ _$insertNode(_el$35, _el$37);
940
+ _$insertNode(_el$35, _el$38);
941
+ _$insert(_el$36, () => t().saved);
942
+ _$insert(_el$37, () => " ".repeat(Math.max(1, panelWidth() - gutter() - visualWidth(t().saved) - visualWidth("~" + fmtCost(data().saved, currencySymbol(), exchangeRate())))));
943
+ _$insertNode(_el$38, _el$39);
944
+ _$insert(_el$38, () => fmtCost(data().saved, currencySymbol(), exchangeRate()), null);
945
+ _$effect((_p$) => {
946
+ var _v$7 = {
947
+ fg: pal().muted
948
+ }, _v$8 = {
949
+ fg: pal().success
950
+ };
951
+ _v$7 !== _p$.e && (_p$.e = _$setProp(_el$36, "style", _v$7, _p$.e));
952
+ _v$8 !== _p$.t && (_p$.t = _$setProp(_el$38, "style", _v$8, _p$.t));
953
+ return _p$;
954
+ }, {
955
+ e: void 0,
956
+ t: void 0
957
+ });
958
+ return _el$35;
959
+ }
960
+ })];
961
+ }
962
+ })];
963
+ }
964
+ }), _$createComponent(Show, {
965
+ get when() {
966
+ return sectionModel();
967
+ },
968
+ get children() {
969
+ return [_$memo(() => (() => {
970
+ var _el$57 = _$createElement("text"), _el$58 = _$createElement("span"), _el$59 = _$createElement("span"), _el$60 = _$createElement("b"), _el$61 = _$createElement("span");
971
+ _$insertNode(_el$57, _el$58);
972
+ _$insertNode(_el$57, _el$59);
973
+ _$insertNode(_el$57, _el$61);
974
+ _$setProp(_el$57, "onMouseUp", () => setModelOpen((o) => {
975
+ const n = !o;
976
+ persistFold("model", n);
977
+ return n;
978
+ }));
979
+ _$insert(_el$58, () => modelOpen() ? "\u25BC " : "\u25B6 ");
980
+ _$insertNode(_el$59, _el$60);
981
+ _$insert(_el$60, () => t().secModel);
982
+ _$insert(_el$61, () => sep().slice(visualWidth((modelOpen() ? "\u25BC " : "\u25B6 ") + t().secModel)));
983
+ _$effect((_p$) => {
984
+ var _v$11 = {
985
+ fg: pal().muted
986
+ }, _v$12 = {
987
+ fg: pal().primary
988
+ }, _v$13 = {
989
+ fg: pal().muted
990
+ };
991
+ _v$11 !== _p$.e && (_p$.e = _$setProp(_el$58, "style", _v$11, _p$.e));
992
+ _v$12 !== _p$.t && (_p$.t = _$setProp(_el$59, "style", _v$12, _p$.t));
993
+ _v$13 !== _p$.a && (_p$.a = _$setProp(_el$61, "style", _v$13, _p$.a));
994
+ return _p$;
995
+ }, {
996
+ e: void 0,
997
+ t: void 0,
998
+ a: void 0
999
+ });
1000
+ return _el$57;
1001
+ })()), _$createComponent(Show, {
1002
+ get when() {
1003
+ return modelOpen();
1004
+ },
1005
+ get children() {
1006
+ return [(() => {
1007
+ var _el$40 = _$createElement("text");
1008
+ _$insert(_el$40, () => justify(t().cost, fmtCost(data().cost, currencySymbol(), exchangeRate())));
1009
+ _$effect((_$p) => _$setProp(_el$40, "fg", pal().text, _$p));
1010
+ return _el$40;
1011
+ })(), _$createComponent(Show, {
1012
+ get when() {
1013
+ return data().providerName;
1014
+ },
1015
+ get children() {
1016
+ var _el$41 = _$createElement("text");
1017
+ _$insert(_el$41, () => justify(t().provider, data().providerName));
1018
+ _$effect((_$p) => _$setProp(_el$41, "fg", pal().muted, _$p));
1019
+ return _el$41;
1020
+ }
1021
+ }), (() => {
1022
+ var _el$42 = _$createElement("text");
1023
+ _$insert(_el$42, () => justify(t().model, data().model));
1024
+ _$effect((_$p) => _$setProp(_el$42, "fg", pal().muted, _$p));
1025
+ return _el$42;
1026
+ })(), _$createComponent(Show, {
1027
+ get when() {
1028
+ return data().hasPricing;
1029
+ },
1030
+ get children() {
1031
+ return [(() => {
1032
+ var _el$43 = _$createElement("text");
1033
+ _$insert(_el$43, () => justify(t().rate, currencySymbol() + (data().inputRate * exchangeRate()).toFixed(2) + "/M " + t().inputRate));
1034
+ _$effect((_$p) => _$setProp(_el$43, "fg", pal().muted, _$p));
1035
+ return _el$43;
1036
+ })(), _$createComponent(Show, {
1037
+ get when() {
1038
+ return data().cacheReadRate > 0;
1039
+ },
1040
+ get children() {
1041
+ var _el$44 = _$createElement("text");
1042
+ _$insert(_el$44, () => justify("", currencySymbol() + (data().cacheReadRate * exchangeRate()).toFixed(2) + "/M " + t().cacheRate));
1043
+ _$effect((_$p) => _$setProp(_el$44, "fg", pal().muted, _$p));
1044
+ return _el$44;
1045
+ }
1046
+ }), _$createComponent(Show, {
1047
+ get when() {
1048
+ return data().cacheWriteRate > 0;
1049
+ },
1050
+ get children() {
1051
+ var _el$45 = _$createElement("text");
1052
+ _$insert(_el$45, () => justify("", currencySymbol() + (data().cacheWriteRate * exchangeRate()).toFixed(2) + "/M " + t().writeRate));
1053
+ _$effect((_$p) => _$setProp(_el$45, "fg", pal().muted, _$p));
1054
+ return _el$45;
1055
+ }
1056
+ })];
1057
+ }
1058
+ })];
1059
+ }
1060
+ })];
1061
+ }
1062
+ }), _$createComponent(Show, {
1063
+ get when() {
1064
+ return sectionDist();
1065
+ },
1066
+ get children() {
1067
+ return _$createComponent(Show, {
1068
+ get when() {
1069
+ return data().hasDistData;
1070
+ },
1071
+ get children() {
1072
+ return [_$memo(() => (() => {
1073
+ var _el$62 = _$createElement("text"), _el$63 = _$createElement("span"), _el$64 = _$createElement("span"), _el$65 = _$createElement("b"), _el$66 = _$createElement("span");
1074
+ _$insertNode(_el$62, _el$63);
1075
+ _$insertNode(_el$62, _el$64);
1076
+ _$insertNode(_el$62, _el$66);
1077
+ _$setProp(_el$62, "onMouseUp", () => setDistOpen((o) => {
1078
+ const n = !o;
1079
+ persistFold("dist", n);
1080
+ return n;
1081
+ }));
1082
+ _$insert(_el$63, () => distOpen() ? "\u25BC " : "\u25B6 ");
1083
+ _$insertNode(_el$64, _el$65);
1084
+ _$insert(_el$65, () => t().distTitle);
1085
+ _$insert(_el$66, () => sep().slice(visualWidth((distOpen() ? "\u25BC " : "\u25B6 ") + t().distTitle)));
1086
+ _$effect((_p$) => {
1087
+ var _v$14 = {
1088
+ fg: pal().muted
1089
+ }, _v$15 = {
1090
+ fg: pal().primary
1091
+ }, _v$16 = {
1092
+ fg: pal().muted
1093
+ };
1094
+ _v$14 !== _p$.e && (_p$.e = _$setProp(_el$63, "style", _v$14, _p$.e));
1095
+ _v$15 !== _p$.t && (_p$.t = _$setProp(_el$64, "style", _v$15, _p$.t));
1096
+ _v$16 !== _p$.a && (_p$.a = _$setProp(_el$66, "style", _v$16, _p$.a));
1097
+ return _p$;
1098
+ }, {
1099
+ e: void 0,
1100
+ t: void 0,
1101
+ a: void 0
1102
+ });
1103
+ return _el$62;
1104
+ })()), _$createComponent(Show, {
1105
+ get when() {
1106
+ return distOpen();
1107
+ },
1108
+ get children() {
1109
+ return [_$createComponent(Show, {
1110
+ get when() {
1111
+ return data().dist.system > 0;
1112
+ },
1113
+ get children() {
1114
+ var _el$46 = _$createElement("text");
1115
+ _$insert(_el$46, () => justify(t().distSys, fmt(data().dist.system), t().tok));
1116
+ _$effect((_$p) => _$setProp(_el$46, "fg", pal().muted, _$p));
1117
+ return _el$46;
1118
+ }
1119
+ }), _$createComponent(Show, {
1120
+ get when() {
1121
+ return data().dist.user > 0;
1122
+ },
1123
+ get children() {
1124
+ var _el$47 = _$createElement("text");
1125
+ _$insert(_el$47, () => justify(t().distUser, fmt(data().dist.user), t().tok));
1126
+ _$effect((_$p) => _$setProp(_el$47, "fg", pal().muted, _$p));
1127
+ return _el$47;
1128
+ }
1129
+ }), _$createComponent(Show, {
1130
+ get when() {
1131
+ return data().dist.agent > 0;
1132
+ },
1133
+ get children() {
1134
+ var _el$48 = _$createElement("text");
1135
+ _$insert(_el$48, () => justify(t().distAgent, fmt(data().dist.agent), t().tok));
1136
+ _$effect((_$p) => _$setProp(_el$48, "fg", pal().muted, _$p));
1137
+ return _el$48;
1138
+ }
1139
+ }), _$createComponent(Show, {
1140
+ get when() {
1141
+ return data().dist.toolCall > 0;
1142
+ },
1143
+ get children() {
1144
+ var _el$49 = _$createElement("text");
1145
+ _$insert(_el$49, () => justify(t().distTool, fmt(data().dist.toolCall), t().tok));
1146
+ _$effect((_$p) => _$setProp(_el$49, "fg", pal().muted, _$p));
1147
+ return _el$49;
1148
+ }
1149
+ }), _$createComponent(Show, {
1150
+ get when() {
1151
+ return data().dist.toolResult > 0;
1152
+ },
1153
+ get children() {
1154
+ var _el$50 = _$createElement("text");
1155
+ _$insert(_el$50, () => justify(t().distRes, fmt(data().dist.toolResult), t().tok));
1156
+ _$effect((_$p) => _$setProp(_el$50, "fg", pal().muted, _$p));
1157
+ return _el$50;
1158
+ }
1159
+ }), (() => {
1160
+ var _el$51 = _$createElement("text");
1161
+ _$insert(_el$51, () => justify(t().distTotal, fmt(data().dist.apiInput), t().tok));
1162
+ _$effect((_$p) => _$setProp(_el$51, "fg", pal().text, _$p));
1163
+ return _el$51;
1164
+ })()];
1165
+ }
1166
+ })];
1167
+ }
1168
+ });
1169
+ }
1170
+ }), _$createComponent(Show, {
1171
+ get when() {
1172
+ return sectionSkills();
1173
+ },
1174
+ get children() {
1175
+ return _$createComponent(Show, {
1176
+ get when() {
1177
+ return data().hasSkills;
1178
+ },
1179
+ get children() {
1180
+ return [_$memo(() => (() => {
1181
+ var _el$67 = _$createElement("text"), _el$68 = _$createElement("span"), _el$69 = _$createElement("span"), _el$70 = _$createElement("b"), _el$71 = _$createElement("span"), _el$72 = _$createTextNode(` (`), _el$73 = _$createTextNode(`)`), _el$74 = _$createElement("span");
1182
+ _$insertNode(_el$67, _el$68);
1183
+ _$insertNode(_el$67, _el$69);
1184
+ _$insertNode(_el$67, _el$71);
1185
+ _$insertNode(_el$67, _el$74);
1186
+ _$setProp(_el$67, "onMouseUp", () => setSkillsOpen((o) => {
1187
+ const n = !o;
1188
+ persistFold("skills", n);
1189
+ return n;
1190
+ }));
1191
+ _$insert(_el$68, () => skillsOpen() ? "\u25BC " : "\u25B6 ");
1192
+ _$insertNode(_el$69, _el$70);
1193
+ _$insert(_el$70, () => t().secSkills);
1194
+ _$insertNode(_el$71, _el$72);
1195
+ _$insertNode(_el$71, _el$73);
1196
+ _$insert(_el$71, () => data().skills.length, _el$73);
1197
+ _$insert(_el$74, () => sep().slice(visualWidth((skillsOpen() ? "\u25BC " : "\u25B6 ") + t().secSkills + ` (${data().skills.length})`)));
1198
+ _$effect((_p$) => {
1199
+ var _v$17 = {
1200
+ fg: pal().muted
1201
+ }, _v$18 = {
1202
+ fg: pal().primary
1203
+ }, _v$19 = {
1204
+ fg: pal().muted
1205
+ }, _v$20 = {
1206
+ fg: pal().muted
1207
+ };
1208
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$68, "style", _v$17, _p$.e));
1209
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$69, "style", _v$18, _p$.t));
1210
+ _v$19 !== _p$.a && (_p$.a = _$setProp(_el$71, "style", _v$19, _p$.a));
1211
+ _v$20 !== _p$.o && (_p$.o = _$setProp(_el$74, "style", _v$20, _p$.o));
1212
+ return _p$;
1213
+ }, {
1214
+ e: void 0,
1215
+ t: void 0,
1216
+ a: void 0,
1217
+ o: void 0
1218
+ });
1219
+ return _el$67;
1220
+ })()), _$createComponent(Show, {
1221
+ get when() {
1222
+ return skillsOpen();
1223
+ },
1224
+ get children() {
1225
+ return data().skills.map((sk) => {
1226
+ const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t().tok);
1227
+ const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
1228
+ const label = truncateVisual(sk.name, maxLabel);
1229
+ return (() => {
1230
+ var _el$75 = _$createElement("text");
1231
+ _$insert(_el$75, () => justify(label, fmt(sk.tokens), t().tok));
1232
+ _$effect((_$p) => _$setProp(_el$75, "fg", pal().muted, _$p));
1233
+ return _el$75;
1234
+ })();
1235
+ });
1236
+ }
1237
+ })];
1238
+ }
1239
+ });
1240
+ }
1241
+ })];
1242
+ }
1243
+ });
1244
+ }
1245
+ }), null);
1246
+ _$effect((_p$) => {
1247
+ var _v$9 = {
1248
+ fg: pal().muted
1249
+ }, _v$0 = {
1250
+ fg: pal().primary
1251
+ };
1252
+ _v$9 !== _p$.e && (_p$.e = _$setProp(_el$3, "style", _v$9, _p$.e));
1253
+ _v$0 !== _p$.t && (_p$.t = _$setProp(_el$4, "style", _v$0, _p$.t));
1254
+ return _p$;
1255
+ }, {
1256
+ e: void 0,
1257
+ t: void 0
1258
+ });
1259
+ return _el$;
1260
+ })();
1261
+ }
1262
+ function createSidebarSlot(api, signals) {
1263
+ return {
1264
+ order: 55,
1265
+ slots: {
1266
+ sidebar_content(ctx, input) {
1267
+ return _$createComponent(TokenCachePanel, {
1268
+ get theme() {
1269
+ return ctx.theme.current;
1270
+ },
1271
+ api,
1272
+ get sessionId() {
1273
+ return input.session_id;
1274
+ },
1275
+ signals
1276
+ });
1277
+ }
1278
+ }
1279
+ };
1280
+ }
1281
+ var tui = async (api) => {
1282
+ const [currencySymbol, setCurrencySymbol] = createSignal("$");
1283
+ const [exchangeRate, setExchangeRate] = createSignal(1);
1284
+ const [sectionDetail, setSectionDetail] = createSignal(true);
1285
+ const [sectionModel, setSectionModel] = createSignal(true);
1286
+ const [sectionDist, setSectionDist] = createSignal(true);
1287
+ const [sectionSkills, setSectionSkills] = createSignal(true);
1288
+ const [borderVisible, setBorderVisible] = createSignal(true);
1289
+ const [langZH, setLangZH] = createSignal(LANG_ZH);
1290
+ const signals = {
1291
+ currencySymbol,
1292
+ setCurrencySymbol,
1293
+ exchangeRate,
1294
+ setExchangeRate,
1295
+ langZH,
1296
+ setLangZH,
1297
+ sectionDetail,
1298
+ setSectionDetail,
1299
+ sectionModel,
1300
+ setSectionModel,
1301
+ sectionDist,
1302
+ setSectionDist,
1303
+ sectionSkills,
1304
+ setSectionSkills,
1305
+ borderVisible,
1306
+ setBorderVisible
1307
+ };
1308
+ api.slots.register(createSidebarSlot(api, signals));
1309
+ const KV_PREFIX = "cache_panel";
1310
+ api.command?.register(() => [{
1311
+ title: "Cache: Set Currency",
1312
+ value: "cache.currency",
1313
+ description: "Change the currency unit for cost display",
1314
+ slash: {
1315
+ name: "cache-currency"
1316
+ },
1317
+ onSelect: (dialog) => {
1318
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1319
+ title: "Select Currency",
1320
+ get options() {
1321
+ return Object.entries(CURRENCIES).map(([code, sym]) => ({
1322
+ title: `${code} (${sym})`,
1323
+ value: code
1324
+ }));
1325
+ },
1326
+ onSelect: (opt) => {
1327
+ const sym = CURRENCIES[opt.value] ?? "$";
1328
+ const defRate = DEFAULT_RATES[opt.value] ?? 1;
1329
+ api.kv.set(`${KV_PREFIX}.currency`, sym);
1330
+ api.kv.set(`${KV_PREFIX}.rate`, defRate);
1331
+ signals.setCurrencySymbol(sym);
1332
+ signals.setExchangeRate(defRate);
1333
+ api.ui.toast({
1334
+ message: `Currency: ${opt.value} (${sym}), rate: ${defRate}`
1335
+ });
1336
+ dialog?.clear();
1337
+ }
1338
+ }));
1339
+ }
1340
+ }, {
1341
+ title: "Cache: Set Exchange Rate",
1342
+ value: "cache.rate",
1343
+ description: "Set the exchange rate multiplier for the selected currency",
1344
+ slash: {
1345
+ name: "cache-rate"
1346
+ },
1347
+ onSelect: (dialog) => {
1348
+ dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
1349
+ title: "Exchange Rate",
1350
+ description: () => (() => {
1351
+ var _el$76 = _$createElement("text");
1352
+ _$insertNode(_el$76, _$createTextNode(`Enter the exchange rate from USD to your currency (e.g. 7.2 for CNY)`));
1353
+ return _el$76;
1354
+ })(),
1355
+ placeholder: "1.0",
1356
+ get value() {
1357
+ return String(api.kv.get(`${KV_PREFIX}.rate`, 1));
1358
+ },
1359
+ onConfirm: (val) => {
1360
+ const n = parseFloat(val);
1361
+ if (n > 0) {
1362
+ api.kv.set(`${KV_PREFIX}.rate`, n);
1363
+ signals.setExchangeRate(n);
1364
+ api.ui.toast({
1365
+ message: `Exchange rate set to ${n}`
1366
+ });
1367
+ }
1368
+ dialog?.clear();
1369
+ }
1370
+ }));
1371
+ }
1372
+ }, {
1373
+ title: "Cache: Toggle Section",
1374
+ value: "cache.section",
1375
+ description: "Show or hide a sidebar section",
1376
+ slash: {
1377
+ name: "cache-section"
1378
+ },
1379
+ onSelect: (dialog) => {
1380
+ const detailOn = Boolean(api.kv.get(`${KV_PREFIX}.section.detail`, true));
1381
+ const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
1382
+ const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
1383
+ const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
1384
+ const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
1385
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1386
+ title: "Toggle Section",
1387
+ options: [{
1388
+ title: `Token Detail [${detailOn ? "ON" : "OFF"}]`,
1389
+ value: "detail"
1390
+ }, {
1391
+ title: `Model & Pricing [${modelOn ? "ON" : "OFF"}]`,
1392
+ value: "model"
1393
+ }, {
1394
+ title: `Token Dist. [${distOn ? "ON" : "OFF"}]`,
1395
+ value: "dist"
1396
+ }, {
1397
+ title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`,
1398
+ value: "skills"
1399
+ }, {
1400
+ title: `Panel Border [${borderOn ? "ON" : "OFF"}]`,
1401
+ value: "border"
1402
+ }],
1403
+ onSelect: (opt) => {
1404
+ if (opt.value === "border") {
1405
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
1406
+ api.kv.set(`${KV_PREFIX}.border`, !cur);
1407
+ signals.setBorderVisible(!cur);
1408
+ api.ui.toast({
1409
+ message: `Panel border ${!cur ? "shown" : "hidden"}`
1410
+ });
1411
+ } else {
1412
+ const key = `${KV_PREFIX}.section.${opt.value}`;
1413
+ const cur = Boolean(api.kv.get(key, true));
1414
+ api.kv.set(key, !cur);
1415
+ if (opt.value === "detail") signals.setSectionDetail(!cur);
1416
+ if (opt.value === "model") signals.setSectionModel(!cur);
1417
+ if (opt.value === "dist") signals.setSectionDist(!cur);
1418
+ if (opt.value === "skills") signals.setSectionSkills(!cur);
1419
+ api.ui.toast({
1420
+ message: `${opt.value} section ${!cur ? "shown" : "hidden"}`
1421
+ });
1422
+ }
1423
+ dialog?.clear();
1424
+ }
1425
+ }));
1426
+ }
1427
+ }, {
1428
+ title: "Cache: Show Config",
1429
+ value: "cache.config",
1430
+ description: "Display the current plugin configuration",
1431
+ slash: {
1432
+ name: "cache-config"
1433
+ },
1434
+ onSelect: (dialog) => {
1435
+ const sym = api.kv.get(`${KV_PREFIX}.currency`) ?? "$";
1436
+ const rate = api.kv.get(`${KV_PREFIX}.rate`) ?? 1;
1437
+ const detail = Boolean(api.kv.get(`${KV_PREFIX}.section.detail`, true));
1438
+ const model = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
1439
+ const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
1440
+ const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
1441
+ api.ui.toast({
1442
+ title: "Cache Panel Config",
1443
+ message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"}`,
1444
+ duration: 8e3
1445
+ });
1446
+ dialog?.clear();
1447
+ }
1448
+ }, {
1449
+ title: "Cache: Switch Language",
1450
+ value: "cache.lang",
1451
+ description: "Switch between Chinese and English display",
1452
+ slash: {
1453
+ name: "cache-lang"
1454
+ },
1455
+ onSelect: (dialog) => {
1456
+ const cur = langZH();
1457
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1458
+ title: "Display Language",
1459
+ options: [{
1460
+ title: `\u4E2D\u6587 ${cur ? "\u2713" : ""}`,
1461
+ value: "zh"
1462
+ }, {
1463
+ title: `English ${cur ? "" : "\u2713"}`,
1464
+ value: "en"
1465
+ }],
1466
+ onSelect: (opt) => {
1467
+ const zh = opt.value === "zh";
1468
+ api.kv.set(`${KV_PREFIX}.lang`, opt.value);
1469
+ setLangZH(zh);
1470
+ api.ui.toast({
1471
+ message: zh ? "\u8BED\u8A00\u5DF2\u5207\u6362\u4E3A\u4E2D\u6587" : "Switched to English"
1472
+ });
1473
+ dialog?.clear();
1474
+ }
1475
+ }));
1476
+ }
1477
+ }, {
1478
+ title: "Cache: Debug Skills Detection",
1479
+ value: "cache.debug-skills",
1480
+ description: "Dump all tool parts found in the current session for skill detection debugging",
1481
+ slash: {
1482
+ name: "cache-debug-skills"
1483
+ },
1484
+ onSelect: () => {
1485
+ const rt = api.route.current;
1486
+ if (rt.name !== "session" || !rt.params) {
1487
+ api.ui.toast({
1488
+ message: "Please run this command inside a session",
1489
+ variant: "warning"
1490
+ });
1491
+ return;
1492
+ }
1493
+ const sid = String(rt.params.sessionID);
1494
+ const msgs = api.state.session.messages(sid);
1495
+ const byTool = {};
1496
+ const skillParts = [];
1497
+ for (const msg of msgs) {
1498
+ if (msg.role !== "assistant") continue;
1499
+ let parts = [];
1500
+ try {
1501
+ parts = api.state.part(msg.id);
1502
+ } catch {
1503
+ }
1504
+ for (const p of parts) {
1505
+ if (p.type === "tool") {
1506
+ const t = String(p.tool ?? "?");
1507
+ byTool[t] = (byTool[t] ?? 0) + 1;
1508
+ if (t === "skill") {
1509
+ const meta = p.state?.metadata;
1510
+ const rootMeta = p.metadata;
1511
+ skillParts.push(`state.metadata=${JSON.stringify(meta)} | root.metadata=${JSON.stringify(rootMeta)} | state.title="${p.state?.title}" | state.output[:80]="${String(p.state?.output ?? "").slice(0, 80)}"`);
1512
+ }
1513
+ }
1514
+ }
1515
+ }
1516
+ const summary = Object.entries(byTool).map(([k, v]) => `${k}: ${v}`).join(" | ");
1517
+ const extra = skillParts.length > 0 ? "\n\nSkill parts:\n" + skillParts.join("\n") : "\n\n\u26A0 No skill tool parts found \u2014 AI may be reading SKILL.md instead. Try: 'Use the skill tool to load karpathy-guidelines'";
1518
+ api.ui.toast({
1519
+ title: `Tool Summary (${Object.keys(byTool).length} types)`,
1520
+ message: summary + extra,
1521
+ duration: 15e3
1522
+ });
1523
+ }
1524
+ }]);
1525
+ };
1526
+ var mod = {
1527
+ id: "opencode-visual-cache",
1528
+ tui
1529
+ };
1530
+ var index_default = mod;
1531
+ export {
1532
+ index_default as default
1533
+ };