opencode-visual-cache 1.4.0 → 1.6.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/tui.js CHANGED
@@ -13,53 +13,111 @@ import { createElement as _$createElement } from "@opentui/solid";
13
13
  import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
14
14
 
15
15
  // src/_version.ts
16
- var PLUGIN_VERSION = "1.4.0";
16
+ var PLUGIN_VERSION = "1.6.0";
17
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;
18
+ // src/balance-providers.ts
19
+ var BalanceError = class extends Error {
20
+ };
21
+ var siliconflowProvider = {
22
+ id: "siliconflow",
23
+ name: "SiliconFlow",
24
+ keyPlaceholder: "sk-...",
25
+ async fetchBalance(apiKey, signal) {
26
+ const res = await fetch("https://api.siliconflow.cn/v1/user/info", {
27
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
28
+ signal
29
+ });
30
+ if (!res.ok) {
31
+ if (res.status === 401) throw new BalanceError("401");
32
+ if (res.status === 403) throw new BalanceError("403");
33
+ throw new BalanceError(String(res.status));
49
34
  }
50
- result += c;
51
- w += cw;
35
+ const json = await res.json();
36
+ const total = json.data?.totalBalance ?? json.data?.balance;
37
+ if (typeof total === "undefined" || total === null) throw new BalanceError("EMPTY");
38
+ return [{ currency: "CNY", total: String(total) }];
52
39
  }
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;
40
+ };
41
+ var deepseekProvider = {
42
+ id: "deepseek",
43
+ name: "DeepSeek",
44
+ keyPlaceholder: "sk-...",
45
+ async fetchBalance(apiKey, signal) {
46
+ const res = await fetch("https://api.deepseek.com/user/balance", {
47
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
48
+ signal
49
+ });
50
+ if (!res.ok) {
51
+ if (res.status === 401) throw new BalanceError("401");
52
+ if (res.status === 402 || res.status === 403) throw new BalanceError("403");
53
+ throw new BalanceError(String(res.status));
54
+ }
55
+ const json = await res.json();
56
+ const infos = json.balance_infos ?? [];
57
+ if (infos.length === 0) throw new BalanceError("EMPTY");
58
+ return infos.map((info) => ({
59
+ currency: info.currency ?? "CNY",
60
+ total: info.total_balance ?? "0"
61
+ }));
62
+ }
63
+ };
64
+ var openrouterProvider = {
65
+ id: "openrouter",
66
+ name: "OpenRouter",
67
+ keyPlaceholder: "sk-or-...",
68
+ async fetchBalance(apiKey, signal) {
69
+ const res = await fetch("https://openrouter.ai/api/v1/credits", {
70
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
71
+ signal
72
+ });
73
+ if (!res.ok) {
74
+ if (res.status === 401 || res.status === 403) throw new BalanceError("403");
75
+ throw new BalanceError(String(res.status));
76
+ }
77
+ const json = await res.json();
78
+ const credits = json.data?.total_credits;
79
+ const usage = json.data?.total_usage;
80
+ if (typeof credits !== "number" || typeof usage !== "number") throw new BalanceError("EMPTY");
81
+ return [{ currency: "USD", total: (credits - usage).toFixed(2) }];
82
+ }
83
+ };
84
+ var moonshotProvider = {
85
+ id: "moonshot",
86
+ name: "Moonshot",
87
+ keyPlaceholder: "sk-...",
88
+ async fetchBalance(apiKey, signal) {
89
+ const res = await fetch("https://api.moonshot.cn/v1/users/me/balance", {
90
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
91
+ signal
92
+ });
93
+ if (!res.ok) {
94
+ if (res.status === 401) throw new BalanceError("401");
95
+ if (res.status === 403) throw new BalanceError("403");
96
+ throw new BalanceError(String(res.status));
97
+ }
98
+ const json = await res.json();
99
+ const balance = json.data?.available_balance;
100
+ if (typeof balance === "undefined" || balance === null) throw new BalanceError("EMPTY");
101
+ return [{ currency: "CNY", total: String(balance) }];
61
102
  }
62
- })();
103
+ };
104
+ var balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider];
105
+ function getBalanceProvider(id) {
106
+ return balanceProviders.find((p) => p.id === id) ?? balanceProviders[0] ?? deepseekProvider;
107
+ }
108
+ function matchBalanceProvider(providerId) {
109
+ const id = providerId.toLowerCase();
110
+ const exact = balanceProviders.find((p) => p.id.toLowerCase() === id);
111
+ if (exact) return exact;
112
+ return balanceProviders.find((p) => id.startsWith(p.id.toLowerCase()));
113
+ }
114
+ function maskKey(k) {
115
+ if (!k) return "";
116
+ if (k.length <= 10) return k.slice(0, 5) + "*".repeat(Math.max(3, k.length - 5));
117
+ return k.slice(0, 5) + "*".repeat(Math.max(3, k.length - 10)) + k.slice(-5);
118
+ }
119
+
120
+ // src/i18n.ts
63
121
  var ZH_T = {
64
122
  title: "\u7F13\u5B58\u7EDF\u8BA1",
65
123
  hit: "\u547D\u4E2D\u7387",
@@ -91,13 +149,55 @@ var ZH_T = {
91
149
  secModel: "\u6A21\u578B",
92
150
  secSkills: "\u5DF2\u52A0\u8F7D\u6280\u80FD",
93
151
  balTotal: "\u603B\u4F59\u989D:",
94
- balNoKey: "\u672A\u914D\u7F6E API Key",
152
+ balNoKey: "\u672A\u914D\u7F6E {p} API Key",
95
153
  balLoading: "\u67E5\u8BE2\u4E2D...",
96
154
  balError: "\u67E5\u8BE2\u5931\u8D25",
97
155
  balErr401: "API Key \u65E0\u6548",
98
156
  balErr403: "\u4F59\u989D\u67E5\u8BE2\u88AB\u62D2\u7EDD",
99
157
  balErrEmpty: "\u672A\u83B7\u53D6\u5230\u4F59\u989D\u6570\u636E",
100
- balErrTimeout: "\u67E5\u8BE2\u8D85\u65F6"
158
+ balErrTimeout: "\u67E5\u8BE2\u8D85\u65F6",
159
+ balUnsupported: "\u5F53\u524D\u63D0\u4F9B\u5546\u4E0D\u652F\u6301\u4F59\u989D\u67E5\u8BE2",
160
+ barHit: "\u547D\u4E2D\u7387",
161
+ barBal: "\u4F59\u989D",
162
+ barTok: "Tokens",
163
+ // ── /cache-section 面板 ──
164
+ secToggle: "\u5207\u6362\u533A\u5757",
165
+ secBalance: "\u4F59\u989D",
166
+ secBottom: "\u5E95\u90E8\u72B6\u6001\u680F",
167
+ secBorder: "\u9762\u677F\u8FB9\u6846",
168
+ // ── toast ──
169
+ keySaved: "API Key \u5DF2\u4FDD\u5B58\uFF0C\u6B63\u5728\u67E5\u8BE2\u4F59\u989D...",
170
+ keyCleared: "API Key \u5DF2\u6E05\u9664",
171
+ currencySet: "\u5E01\u79CD: {v} ({s}), \u6C47\u7387: {r}",
172
+ rateSet: "\u6C47\u7387\u5DF2\u8BBE\u4E3A {r}",
173
+ panelConfigTitle: "\u7F13\u5B58\u9762\u677F\u914D\u7F6E",
174
+ panelConfigMsg: "\u5E01\u79CD: {c} | \u6C47\u7387: {r} | \u660E\u7EC6: {d} | \u6A21\u578B: {m} | \u5206\u5E03: {t} | \u6280\u80FD: {k} | \u4F59\u989D: {b} | \u5E95\u90E8: {f}",
175
+ borderShown: "\u9762\u677F\u8FB9\u6846 \u5DF2\u663E\u793A",
176
+ borderHidden: "\u9762\u677F\u8FB9\u6846 \u5DF2\u9690\u85CF",
177
+ sectionShown: "{s} \u5DF2\u663E\u793A",
178
+ sectionHidden: "{s} \u5DF2\u9690\u85CF",
179
+ langSwitched: "\u8BED\u8A00\u5DF2\u5207\u6362\u4E3A\u4E2D\u6587",
180
+ autoSwitchOn: "\u81EA\u52A8\u5207\u6362\u4F59\u989D\u63D0\u4F9B\u5546: \u5F00",
181
+ autoSwitchOff: "\u81EA\u52A8\u5207\u6362\u4F59\u989D\u63D0\u4F9B\u5546: \u5173",
182
+ providerManual: "\u4F59\u989D\u63D0\u4F9B\u5546: {p}\uFF08\u81EA\u52A8\u5207\u6362\u5DF2\u5173\u95ED\uFF09",
183
+ runInSession: "\u8BF7\u5728\u4F1A\u8BDD\u5185\u8FD0\u884C\u6B64\u547D\u4EE4",
184
+ backToMain: "\u5DF2\u5207\u56DE\u4E3B\u4F1A\u8BDD",
185
+ subAgentSwitched: "\u5DF2\u5207\u6362\u81F3\u5B50\u4EE3\u7406: {s}",
186
+ // ── 对话框 / 菜单 ──
187
+ langTitle: "\u663E\u793A\u8BED\u8A00",
188
+ subPrefix: "\u5B50\u4EE3\u7406: ",
189
+ keyUser: "\uFF08\u7528\u6237 key\uFF09",
190
+ keyOpenCode: "\uFF08OpenCode\uFF09",
191
+ keyNotSet: "\uFF08\u672A\u914D\u7F6E\uFF09",
192
+ balKeyPrompt: "\u8F93\u5165 {p} API Key \u4EE5\u663E\u793A\u8D26\u6237\u4F59\u989D\uFF08\u7559\u7A7A\u6E05\u9664\uFF09",
193
+ balProvTitle: "\u4F59\u989D\u63D0\u4F9B\u5546 / \u81EA\u52A8\u5207\u6362",
194
+ autoSwitchOpt: "\u81EA\u52A8\u5207\u6362\u63D0\u4F9B\u5546",
195
+ balSelectTitle: "\u9009\u62E9\u4F59\u989D\u63D0\u4F9B\u5546",
196
+ backToMainTitle: "\u56DE\u5230\u4E3B\u4F1A\u8BDD",
197
+ subSelectTitle: "\u9009\u62E9\u5B50\u4EE3\u7406",
198
+ subSwitchTitle: "\u5207\u6362\u5B50\u4EE3\u7406",
199
+ subViewTitle: "\u67E5\u770B\u5B50\u4EE3\u7406\u7F13\u5B58",
200
+ subNoFound: "\u672A\u627E\u5230\u5B50\u4EE3\u7406\uFF0C\u8BF7\u624B\u52A8\u7C98\u8D34 Session ID"
101
201
  };
102
202
  var EN_T = {
103
203
  title: "Token Cache",
@@ -130,14 +230,290 @@ var EN_T = {
130
230
  secModel: "Model",
131
231
  secSkills: "Loaded Skills",
132
232
  balTotal: "Total:",
133
- balNoKey: "No API Key set",
233
+ balNoKey: "{p} API Key not set",
134
234
  balLoading: "Fetching...",
135
235
  balError: "Fetch failed",
136
236
  balErr401: "Invalid API Key",
137
237
  balErr403: "Balance request rejected",
138
238
  balErrEmpty: "No balance data",
139
- balErrTimeout: "Request timed out"
239
+ balErrTimeout: "Request timed out",
240
+ balUnsupported: "Balance query unsupported",
241
+ barHit: "Hit",
242
+ barBal: "Balance",
243
+ barTok: "Tokens",
244
+ // ── /cache-section panel ──
245
+ secToggle: "Toggle Section",
246
+ secBalance: "Balance",
247
+ secBottom: "Bottom Bar",
248
+ secBorder: "Panel Border",
249
+ // ── toasts ──
250
+ keySaved: "API Key saved, fetching balance...",
251
+ keyCleared: "API Key cleared",
252
+ currencySet: "Currency: {v} ({s}), rate: {r}",
253
+ rateSet: "Exchange rate set to {r}",
254
+ panelConfigTitle: "Cache Panel Config",
255
+ panelConfigMsg: "Currency: {c} | Rate: {r} | Detail: {d} | Model: {m} | Dist: {t} | Skills: {k} | Balance: {b} | Bottom: {f}",
256
+ borderShown: "Panel border shown",
257
+ borderHidden: "Panel border hidden",
258
+ sectionShown: "{s} section shown",
259
+ sectionHidden: "{s} section hidden",
260
+ langSwitched: "Switched to English",
261
+ autoSwitchOn: "Auto-switch balance provider: ON",
262
+ autoSwitchOff: "Auto-switch balance provider: OFF",
263
+ providerManual: "Balance provider: {p} (auto-switch off)",
264
+ runInSession: "Please run this command inside a session",
265
+ backToMain: "Switched to main session",
266
+ subAgentSwitched: "Showing sub-agent: {s}",
267
+ // ── dialogs / menus ──
268
+ langTitle: "Display Language",
269
+ subPrefix: "Sub: ",
270
+ keyUser: " (user key)",
271
+ keyOpenCode: " (OpenCode)",
272
+ keyNotSet: " (not set)",
273
+ balKeyPrompt: "Enter your {p} API key to show account balance (leave empty to clear)",
274
+ balProvTitle: "Balance Provider / Auto-switch",
275
+ autoSwitchOpt: "Auto-switch provider",
276
+ balSelectTitle: "Select Balance Provider",
277
+ backToMainTitle: "Back to Main",
278
+ subSelectTitle: "Select Sub-Agent",
279
+ subSwitchTitle: "Switch Sub",
280
+ subViewTitle: "View Sub Cache",
281
+ subNoFound: "No sub-agents found. Paste a Session ID manually"
282
+ };
283
+ var JA_T = {
284
+ title: "\u30AD\u30E3\u30C3\u30B7\u30E5\u7D71\u8A08",
285
+ hit: "\u30D2\u30C3\u30C8\u7387",
286
+ totalHit: "\u7DCF\u30D2\u30C3\u30C8:",
287
+ read: "\u30AD\u30E3\u30C3\u30B7\u30E5\u8AAD\u8FBC:",
288
+ write: "\u30AD\u30E3\u30C3\u30B7\u30E5\u66F8\u8FBC:",
289
+ miss: "\u672A\u30D2\u30C3\u30C8:",
290
+ out: "\u51FA\u529B:",
291
+ cost: "\u8CBB\u7528:",
292
+ saved: "\u7D2F\u8A08\u7BC0\u7D04:",
293
+ model: "\u30E2\u30C7\u30EB:",
294
+ provider: "\u30D7\u30ED\u30D0\u30A4\u30C0:",
295
+ rate: "\u5358\u4FA1:",
296
+ hitFolded: "\u30D2\u30C3\u30C8",
297
+ inputRate: "\u5165\u529B",
298
+ cacheRate: "\u30AD\u30E3\u30C3\u30B7\u30E5",
299
+ writeRate: "\u66F8\u8FBC",
300
+ noData: "\u30AD\u30E3\u30C3\u30B7\u30E5\u30C7\u30FC\u30BF\u5F85\u6A5F\u4E2D...",
301
+ tok: "tok",
302
+ distTitle: "Token \u5206\u5E03\uFF08\u63A8\u5B9A\uFF09",
303
+ distSys: "\u30B7\u30B9\u30C6\u30E0:",
304
+ distUser: "\u30E6\u30FC\u30B6\u30FC:",
305
+ distAgent: "Agent \u6307\u793A:",
306
+ distTool: "Tool \u547C\u3073\u51FA\u3057:",
307
+ distRes: "Tool \u7D50\u679C:",
308
+ distTotal: "\u5408\u8A08:",
309
+ distOut: "\u51FA\u529B:",
310
+ secDetail: "\u660E\u7D30",
311
+ secModel: "\u30E2\u30C7\u30EB",
312
+ secSkills: "\u8AAD\u307F\u8FBC\u307F\u6E08\u307F\u30B9\u30AD\u30EB",
313
+ balTotal: "\u7DCF\u6B8B\u9AD8:",
314
+ balNoKey: "{p} \u306E API Key \u672A\u8A2D\u5B9A",
315
+ balLoading: "\u53D6\u5F97\u4E2D...",
316
+ balError: "\u53D6\u5F97\u5931\u6557",
317
+ balErr401: "API Key \u7121\u52B9",
318
+ balErr403: "\u6B8B\u9AD8\u7167\u4F1A\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",
319
+ balErrEmpty: "\u6B8B\u9AD8\u30C7\u30FC\u30BF\u306A\u3057",
320
+ balErrTimeout: "\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8",
321
+ balUnsupported: "\u3053\u306E\u30D7\u30ED\u30D0\u30A4\u30C0\u306F\u6B8B\u9AD8\u7167\u4F1A\u975E\u5BFE\u5FDC",
322
+ barHit: "\u30D2\u30C3\u30C8\u7387",
323
+ barBal: "\u6B8B\u9AD8",
324
+ barTok: "Tokens",
325
+ // ── /cache-section パネル ──
326
+ secToggle: "\u30BB\u30AF\u30B7\u30E7\u30F3\u5207\u66FF",
327
+ secBalance: "\u6B8B\u9AD8",
328
+ secBottom: "\u4E0B\u90E8\u30B9\u30C6\u30FC\u30BF\u30B9\u30D0\u30FC",
329
+ secBorder: "\u30D1\u30CD\u30EB\u67A0\u7DDA",
330
+ // ── toast ──
331
+ keySaved: "API Key \u4FDD\u5B58\u6E08\u307F\u3001\u6B8B\u9AD8\u3092\u53D6\u5F97\u4E2D...",
332
+ keyCleared: "API Key \u3092\u30AF\u30EA\u30A2\u3057\u307E\u3057\u305F",
333
+ currencySet: "\u901A\u8CA8: {v} ({s}), \u30EC\u30FC\u30C8: {r}",
334
+ rateSet: "\u30EC\u30FC\u30C8\u3092 {r} \u306B\u8A2D\u5B9A\u3057\u307E\u3057\u305F",
335
+ panelConfigTitle: "\u30D1\u30CD\u30EB\u8A2D\u5B9A",
336
+ panelConfigMsg: "\u901A\u8CA8: {c} | \u30EC\u30FC\u30C8: {r} | \u660E\u7D30: {d} | \u30E2\u30C7\u30EB: {m} | \u5206\u5E03: {t} | \u30B9\u30AD\u30EB: {k} | \u6B8B\u9AD8: {b} | \u4E0B\u90E8: {f}",
337
+ borderShown: "\u30D1\u30CD\u30EB\u67A0\u7DDA \u8868\u793A",
338
+ borderHidden: "\u30D1\u30CD\u30EB\u67A0\u7DDA \u975E\u8868\u793A",
339
+ sectionShown: "{s} \u8868\u793A",
340
+ sectionHidden: "{s} \u975E\u8868\u793A",
341
+ langSwitched: "\u65E5\u672C\u8A9E\u306B\u5207\u308A\u66FF\u3048\u307E\u3057\u305F",
342
+ autoSwitchOn: "\u6B8B\u9AD8\u30D7\u30ED\u30D0\u30A4\u30C0\u81EA\u52D5\u5207\u66FF: \u30AA\u30F3",
343
+ autoSwitchOff: "\u6B8B\u9AD8\u30D7\u30ED\u30D0\u30A4\u30C0\u81EA\u52D5\u5207\u66FF: \u30AA\u30D5",
344
+ providerManual: "\u6B8B\u9AD8\u30D7\u30ED\u30D0\u30A4\u30C0: {p}\uFF08\u81EA\u52D5\u5207\u66FF\u30AA\u30D5\uFF09",
345
+ runInSession: "\u30BB\u30C3\u30B7\u30E7\u30F3\u5185\u3067\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
346
+ backToMain: "\u30E1\u30A4\u30F3\u30BB\u30C3\u30B7\u30E7\u30F3\u306B\u623B\u308A\u307E\u3057\u305F",
347
+ subAgentSwitched: "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u5207\u66FF: {s}",
348
+ // ── ダイアログ / メニュー ──
349
+ langTitle: "\u8868\u793A\u8A00\u8A9E",
350
+ subPrefix: "\u30B5\u30D6: ",
351
+ keyUser: "\uFF08\u30E6\u30FC\u30B6\u30FC key\uFF09",
352
+ keyOpenCode: "\uFF08OpenCode\uFF09",
353
+ keyNotSet: "\uFF08\u672A\u8A2D\u5B9A\uFF09",
354
+ balKeyPrompt: "{p} \u306E API Key \u3092\u5165\u529B\u3057\u3066\u6B8B\u9AD8\u3092\u8868\u793A\uFF08\u7A7A\u6B04\u3067\u30AF\u30EA\u30A2\uFF09",
355
+ balProvTitle: "\u6B8B\u9AD8\u30D7\u30ED\u30D0\u30A4\u30C0 / \u81EA\u52D5\u5207\u66FF",
356
+ autoSwitchOpt: "\u81EA\u52D5\u5207\u66FF\u30D7\u30ED\u30D0\u30A4\u30C0",
357
+ balSelectTitle: "\u6B8B\u9AD8\u30D7\u30ED\u30D0\u30A4\u30C0\u9078\u629E",
358
+ backToMainTitle: "\u30E1\u30A4\u30F3\u306B\u623B\u308B",
359
+ subSelectTitle: "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u9078\u629E",
360
+ subSwitchTitle: "\u30B5\u30D6\u5207\u66FF",
361
+ subViewTitle: "\u30B5\u30D6\u30AD\u30E3\u30C3\u30B7\u30E5\u8868\u793A",
362
+ subNoFound: "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Session ID \u3092\u624B\u52D5\u3067\u8CBC\u308A\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"
363
+ };
364
+ var KO_T = {
365
+ title: "\uCE90\uC2DC \uD1B5\uACC4",
366
+ hit: "\uD788\uD2B8\uC728",
367
+ totalHit: "\uCD1D \uD788\uD2B8:",
368
+ read: "\uCE90\uC2DC \uC77D\uAE30:",
369
+ write: "\uCE90\uC2DC \uC4F0\uAE30:",
370
+ miss: "\uBBF8\uD788\uD2B8:",
371
+ out: "\uCD9C\uB825:",
372
+ cost: "\uBE44\uC6A9:",
373
+ saved: "\uB204\uC801 \uC808\uC57D:",
374
+ model: "\uBAA8\uB378:",
375
+ provider: "\uD504\uB85C\uBC14\uC774\uB354:",
376
+ rate: "\uB2E8\uAC00:",
377
+ hitFolded: "\uD788\uD2B8",
378
+ inputRate: "\uC785\uB825",
379
+ cacheRate: "\uCE90\uC2DC",
380
+ writeRate: "\uC4F0\uAE30",
381
+ noData: "\uCE90\uC2DC \uB370\uC774\uD130 \uB300\uAE30 \uC911...",
382
+ tok: "tok",
383
+ distTitle: "Token \uBD84\uD3EC(\uCD94\uC815)",
384
+ distSys: "\uC2DC\uC2A4\uD15C:",
385
+ distUser: "\uC0AC\uC6A9\uC790:",
386
+ distAgent: "Agent \uC9C0\uC2DC:",
387
+ distTool: "Tool \uD638\uCD9C:",
388
+ distRes: "Tool \uACB0\uACFC:",
389
+ distTotal: "\uD569\uACC4:",
390
+ distOut: "\uCD9C\uB825:",
391
+ secDetail: "\uC0C1\uC138",
392
+ secModel: "\uBAA8\uB378",
393
+ secSkills: "\uB85C\uB4DC\uB41C \uC2A4\uD0AC",
394
+ balTotal: "\uCD1D \uC794\uC561:",
395
+ balNoKey: "{p} API Key \uBBF8\uC124\uC815",
396
+ balLoading: "\uC870\uD68C \uC911...",
397
+ balError: "\uC870\uD68C \uC2E4\uD328",
398
+ balErr401: "API Key \uBB34\uD6A8",
399
+ balErr403: "\uC794\uC561 \uC870\uD68C\uAC00 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
400
+ balErrEmpty: "\uC794\uC561 \uB370\uC774\uD130 \uC5C6\uC74C",
401
+ balErrTimeout: "\uC2DC\uAC04 \uCD08\uACFC",
402
+ balUnsupported: "\uC774 \uD504\uB85C\uBC14\uC774\uB354\uB294 \uC794\uC561 \uC870\uD68C \uBBF8\uC9C0\uC6D0",
403
+ barHit: "\uD788\uD2B8\uC728",
404
+ barBal: "\uC794\uC561",
405
+ barTok: "Tokens",
406
+ // ── /cache-section 패널 ──
407
+ secToggle: "\uC139\uC158 \uC804\uD658",
408
+ secBalance: "\uC794\uC561",
409
+ secBottom: "\uD558\uB2E8 \uC0C1\uD0DC\uBC14",
410
+ secBorder: "\uD328\uB110 \uD14C\uB450\uB9AC",
411
+ // ── toast ──
412
+ keySaved: "API Key \uC800\uC7A5\uB428, \uC794\uC561 \uC870\uD68C \uC911...",
413
+ keyCleared: "API Key \uC0AD\uC81C\uB428",
414
+ currencySet: "\uD1B5\uD654: {v} ({s}), \uD658\uC728: {r}",
415
+ rateSet: "\uD658\uC728\uC744 {r}(\uC73C)\uB85C \uC124\uC815\uD588\uC2B5\uB2C8\uB2E4",
416
+ panelConfigTitle: "\uD328\uB110 \uC124\uC815",
417
+ panelConfigMsg: "\uD1B5\uD654: {c} | \uD658\uC728: {r} | \uC0C1\uC138: {d} | \uBAA8\uB378: {m} | \uBD84\uD3EC: {t} | \uC2A4\uD0AC: {k} | \uC794\uC561: {b} | \uD558\uB2E8: {f}",
418
+ borderShown: "\uD328\uB110 \uD14C\uB450\uB9AC \uD45C\uC2DC",
419
+ borderHidden: "\uD328\uB110 \uD14C\uB450\uB9AC \uC228\uAE40",
420
+ sectionShown: "{s} \uD45C\uC2DC",
421
+ sectionHidden: "{s} \uC228\uAE40",
422
+ langSwitched: "\uD55C\uAD6D\uC5B4\uB85C \uC804\uD658\uD588\uC2B5\uB2C8\uB2E4",
423
+ autoSwitchOn: "\uC794\uC561 \uD504\uB85C\uBC14\uC774\uB354 \uC790\uB3D9 \uC804\uD658: \uCF1C\uC9D0",
424
+ autoSwitchOff: "\uC794\uC561 \uD504\uB85C\uBC14\uC774\uB354 \uC790\uB3D9 \uC804\uD658: \uAEBC\uC9D0",
425
+ providerManual: "\uC794\uC561 \uD504\uB85C\uBC14\uC774\uB354: {p}(\uC790\uB3D9 \uC804\uD658 \uAEBC\uC9D0)",
426
+ runInSession: "\uC138\uC158 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD574 \uC8FC\uC138\uC694",
427
+ backToMain: "\uBA54\uC778 \uC138\uC158\uC73C\uB85C \uB3CC\uC544\uAC14\uC2B5\uB2C8\uB2E4",
428
+ subAgentSwitched: "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8\uB85C \uC804\uD658: {s}",
429
+ // ── 다이얼로그 / 메뉴 ──
430
+ langTitle: "\uD45C\uC2DC \uC5B8\uC5B4",
431
+ subPrefix: "\uC11C\uBE0C: ",
432
+ keyUser: "(\uC0AC\uC6A9\uC790 key)",
433
+ keyOpenCode: "(OpenCode)",
434
+ keyNotSet: "(\uBBF8\uC124\uC815)",
435
+ balKeyPrompt: "{p} API Key\uB97C \uC785\uB825\uD558\uC5EC \uC794\uC561 \uD45C\uC2DC(\uBE44\uC6B0\uBA74 \uC0AD\uC81C)",
436
+ balProvTitle: "\uC794\uC561 \uD504\uB85C\uBC14\uC774\uB354 / \uC790\uB3D9 \uC804\uD658",
437
+ autoSwitchOpt: "\uC790\uB3D9 \uC804\uD658 \uD504\uB85C\uBC14\uC774\uB354",
438
+ balSelectTitle: "\uC794\uC561 \uD504\uB85C\uBC14\uC774\uB354 \uC120\uD0DD",
439
+ backToMainTitle: "\uBA54\uC778\uC73C\uB85C \uB3CC\uC544\uAC00\uAE30",
440
+ subSelectTitle: "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8 \uC120\uD0DD",
441
+ subSwitchTitle: "\uC11C\uBE0C \uC804\uD658",
442
+ subViewTitle: "\uC11C\uBE0C \uCE90\uC2DC \uBCF4\uAE30",
443
+ subNoFound: "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. Session ID\uB97C \uC9C1\uC811 \uBD99\uC5EC\uB123\uC73C\uC138\uC694"
140
444
  };
445
+ var LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
446
+ var LANG_META = [
447
+ { code: "zh", label: "\u4E2D\u6587" },
448
+ { code: "en", label: "English" },
449
+ { code: "ja", label: "\u65E5\u672C\u8A9E" },
450
+ { code: "ko", label: "\uD55C\uAD6D\uC5B4" }
451
+ ];
452
+ function applyParams(tpl, params) {
453
+ if (!params) return tpl;
454
+ return tpl.replace(
455
+ /\{(\w+)\}/g,
456
+ (m, k) => k in params ? String(params[k]) : m
457
+ );
458
+ }
459
+ function createT(getCode) {
460
+ return (key, params) => applyParams(LANGS[getCode()][key], params);
461
+ }
462
+ function detectLang() {
463
+ try {
464
+ const loc = Intl.DateTimeFormat().resolvedOptions().locale.toLowerCase();
465
+ if (loc.startsWith("zh")) return "zh";
466
+ if (loc.startsWith("ja")) return "ja";
467
+ if (loc.startsWith("ko")) return "ko";
468
+ return "en";
469
+ } catch {
470
+ return "en";
471
+ }
472
+ }
473
+
474
+ // src/index.tsx
475
+ function charColumns(c) {
476
+ const code = c.codePointAt(0) ?? 0;
477
+ if (code < 32) return 0;
478
+ if (code < 127) return 1;
479
+ if (code < 160) return 0;
480
+ if (code >= 4352 && code <= 4447 || // Hangul Jamo
481
+ code >= 11904 && code <= 42191 || // CJK Radicals … Yi
482
+ code >= 44032 && code <= 55203 || // Hangul
483
+ code >= 63744 && code <= 64255 || // CJK Compat
484
+ code >= 65040 && code <= 65135 || // Vertical / Compat
485
+ code >= 65281 && code <= 65376 || // Fullwidth
486
+ code >= 65504 && code <= 65510 || // Fullwidth signs
487
+ code >= 127744 && code <= 128591 || // Misc Symbols (emoji)
488
+ code >= 131072 && code <= 262141)
489
+ return 2;
490
+ return 1;
491
+ }
492
+ function visualWidth(s) {
493
+ let w = 0;
494
+ for (const c of s) w += charColumns(c);
495
+ return w;
496
+ }
497
+ function visualPadEnd(s, cols) {
498
+ const pad = cols - visualWidth(s);
499
+ return pad > 0 ? s + " ".repeat(pad) : s;
500
+ }
501
+ function truncateVisual(s, maxCols) {
502
+ if (visualWidth(s) <= maxCols) return s;
503
+ let result = "", w = 0;
504
+ for (const c of s) {
505
+ const cw = charColumns(c);
506
+ if (w + cw > maxCols - 1) {
507
+ result += "\u2026";
508
+ break;
509
+ }
510
+ result += c;
511
+ w += cw;
512
+ }
513
+ return result;
514
+ }
515
+ var DEBUG_LANG = typeof process !== "undefined" ? process.env?.CACHE_TUI_LANG : void 0;
516
+ var INIT_LANG = DEBUG_LANG !== void 0 && LANG_META.some((m) => m.code === DEBUG_LANG) ? DEBUG_LANG : detectLang();
141
517
  function rgb(raw) {
142
518
  if (typeof raw === "string" && raw.startsWith("#")) {
143
519
  const h = raw.slice(1);
@@ -249,37 +625,56 @@ function estimateTokens(text) {
249
625
  return Math.max(1, Math.ceil(ascii / asciiPerToken + cjk / 1));
250
626
  }
251
627
  var BALANCE_POLL_MS = 5 * 60 * 1e3;
252
- async function fetchDeepSeekBalance(apiKey, signal) {
253
- const res = await fetch("https://api.deepseek.com/user/balance", {
254
- headers: {
255
- Authorization: `Bearer ${apiKey}`,
256
- Accept: "application/json"
257
- },
258
- signal
259
- });
260
- if (!res.ok) {
261
- if (res.status === 401) throw new Error("401");
262
- if (res.status === 402 || res.status === 403) throw new Error("403");
263
- throw new Error(String(res.status));
264
- }
265
- const json = await res.json();
266
- const infos = json.balance_infos ?? [];
267
- if (infos.length === 0) throw new Error("EMPTY");
268
- return infos.map((info) => ({
269
- currency: info.currency ?? "CNY",
270
- total: info.total_balance ?? "0"
271
- }));
272
- }
273
628
  function convertBalance(target, targetRate, amount, from) {
274
629
  if (from === target) return amount;
275
630
  const fromRate = DEFAULT_RATES[from] ?? 1;
276
631
  const usd = from === "USD" ? amount : amount / fromRate;
277
632
  return target === "USD" ? usd : usd * targetRate;
278
633
  }
634
+ function findOpencodeKey(api, provider) {
635
+ try {
636
+ const provs = api.state.provider;
637
+ const id = provider.id.toLowerCase();
638
+ const hit = provs.find((p) => p.id.toLowerCase() === id) ?? provs.find((p) => p.id.toLowerCase().startsWith(id));
639
+ if (!hit) return "";
640
+ const k = typeof hit.key === "string" ? hit.key : "";
641
+ if (k) return k;
642
+ return typeof hit.options?.apiKey === "string" ? hit.options.apiKey : "";
643
+ } catch {
644
+ return "";
645
+ }
646
+ }
279
647
  function balanceSymbol(currency) {
280
648
  const sym = CURRENCIES[currency];
281
649
  return sym ?? currency + " ";
282
650
  }
651
+ function fmtCompact(n) {
652
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
653
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
654
+ return String(Math.round(n));
655
+ }
656
+ function formatBalanceAmount(total) {
657
+ const n = parseFloat(total);
658
+ if (!Number.isFinite(n)) return total;
659
+ if (n === 0 || n >= 1) return n.toLocaleString("en-US", {
660
+ minimumFractionDigits: 2,
661
+ maximumFractionDigits: 2
662
+ });
663
+ return n.toLocaleString("en-US", {
664
+ maximumFractionDigits: 6
665
+ });
666
+ }
667
+ function formatBalanceText(list, pref, rate) {
668
+ const native = pref ? list.find((x) => x.currency === pref) : void 0;
669
+ if (native) return balanceSymbol(native.currency) + formatBalanceAmount(native.total);
670
+ const base = list[0];
671
+ const baseAmt = parseFloat(base.total);
672
+ const converted = Number.isFinite(baseAmt) ? convertBalance(pref || base.currency, rate, baseAmt, base.currency) : baseAmt;
673
+ const shown = pref && base.currency !== pref ? converted.toLocaleString("en-US", {
674
+ maximumFractionDigits: 2
675
+ }) : formatBalanceAmount(base.total);
676
+ return balanceSymbol(pref || base.currency) + shown;
677
+ }
283
678
  var CURRENCIES = {
284
679
  USD: "$",
285
680
  CNY: "\xA5",
@@ -317,8 +712,7 @@ function TokenCachePanel(props) {
317
712
  setCurrencySymbol,
318
713
  exchangeRate,
319
714
  setExchangeRate,
320
- langZH,
321
- setLangZH,
715
+ langCode,
322
716
  sectionDetail,
323
717
  setSectionDetail,
324
718
  sectionModel,
@@ -330,12 +724,19 @@ function TokenCachePanel(props) {
330
724
  sectionBalance,
331
725
  setSectionBalance,
332
726
  balanceRefresh,
727
+ balanceProviderId,
728
+ setBalanceProviderId,
729
+ autoBalance,
730
+ setAutoBalance,
731
+ balanceUnsupported,
732
+ setBalanceUnsupported,
733
+ balanceState,
333
734
  balanceCurrency,
334
735
  setBalanceCurrency,
335
736
  borderVisible,
336
737
  setBorderVisible
337
738
  } = props.signals;
338
- const t = createMemo(() => langZH() ? ZH_T : EN_T);
739
+ const t = createT(() => langCode());
339
740
  const [lastDist, setLastDist] = createSignal({
340
741
  system: 0,
341
742
  user: 0,
@@ -382,69 +783,37 @@ function TokenCachePanel(props) {
382
783
  hasSkills: false
383
784
  });
384
785
  const [refreshTick, setRefreshTick] = createSignal(0);
385
- const [balanceState, setBalanceState] = createSignal({
386
- status: "idle",
387
- data: null,
388
- lastFetch: 0
389
- });
390
- let balanceSeq = 0;
391
- const pollBalance = async () => {
392
- const key = props.api.kv.get(`${KV_PREFIX}.ds_key`, "");
393
- if (!key) {
394
- setBalanceState({
395
- status: "idle",
396
- data: null,
397
- lastFetch: 0,
398
- error: void 0,
399
- key: void 0
400
- });
401
- return;
786
+ const providerName = createMemo(() => getBalanceProvider(balanceProviderId()).name);
787
+ createEffect(() => {
788
+ if (!autoBalance()) return;
789
+ const sid = props.signals.overrideSessionId() ?? props.sessionId;
790
+ const msgs = props.api.state.session.messages(sid);
791
+ let pid = "";
792
+ for (let i = msgs.length - 1; i >= 0; i--) {
793
+ const m = msgs[i];
794
+ if (m.role === "assistant" && m.providerID) {
795
+ pid = m.providerID;
796
+ break;
797
+ }
402
798
  }
403
- const now = Date.now();
404
- const prev = balanceState();
405
- if (prev.status === "ok" && prev.key === key && now - prev.lastFetch < BALANCE_POLL_MS) return;
406
- const seq = ++balanceSeq;
407
- setBalanceState({
408
- ...prev,
409
- status: "loading",
410
- error: void 0,
411
- key
412
- });
413
- const controller = new AbortController();
414
- let timedOut = false;
415
- const timer = setTimeout(() => {
416
- timedOut = true;
417
- controller.abort();
418
- }, 1e4);
419
- try {
420
- const data2 = await fetchDeepSeekBalance(key, controller.signal);
421
- clearTimeout(timer);
422
- if (seq !== balanceSeq) return;
423
- setBalanceState({
424
- status: "ok",
425
- data: data2,
426
- lastFetch: Date.now(),
427
- error: void 0,
428
- key
429
- });
430
- } catch (err) {
431
- clearTimeout(timer);
432
- if (seq !== balanceSeq) return;
433
- const code = timedOut ? "TIMEOUT" : err instanceof Error ? err.message : "";
434
- setBalanceState({
435
- status: "error",
436
- data: null,
437
- lastFetch: 0,
438
- error: code,
439
- key
440
- });
799
+ if (!pid) {
800
+ try {
801
+ const session = props.api.state.session.get(sid);
802
+ pid = session?.model?.providerID ?? "";
803
+ } catch {
804
+ }
805
+ }
806
+ if (!pid) return;
807
+ const hit = matchBalanceProvider(pid);
808
+ if (hit) {
809
+ setBalanceUnsupported(false);
810
+ if (hit.id !== balanceProviderId()) {
811
+ setBalanceProviderId(hit.id);
812
+ props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
813
+ }
814
+ } else {
815
+ setBalanceUnsupported(true);
441
816
  }
442
- };
443
- createEffect(() => {
444
- void balanceRefresh();
445
- untrack(() => {
446
- void pollBalance();
447
- });
448
817
  });
449
818
  let lastMainSid = props.sessionId;
450
819
  createEffect(() => {
@@ -476,18 +845,18 @@ function TokenCachePanel(props) {
476
845
  let prevMsgHitRate = -1, lastMsgHitRate = -1;
477
846
  for (const msg of msgs) {
478
847
  if (msg.role !== "assistant") continue;
479
- const t2 = msg.tokens;
480
- if (!t2) continue;
481
- const mit = num(t2.input) + num(t2.cache?.read), mrt = num(t2.cache?.read);
848
+ const tok = msg.tokens;
849
+ if (!tok) continue;
850
+ const mit = num(tok.input) + num(tok.cache?.read) + num(tok.cache?.write), mrt = num(tok.cache?.read);
482
851
  if (mit > 0) {
483
852
  prevMsgHitRate = lastMsgHitRate;
484
853
  lastMsgHitRate = mrt / mit * 100;
485
854
  }
486
855
  if (fallbackTokens) {
487
- input += num(t2.input);
488
- read += num(t2.cache?.read);
489
- write += num(t2.cache?.write);
490
- output += num(t2.output);
856
+ input += num(tok.input);
857
+ read += num(tok.cache?.read);
858
+ write += num(tok.cache?.write);
859
+ output += num(tok.output);
491
860
  }
492
861
  if (fallbackCost) {
493
862
  cost += num(msg.cost);
@@ -509,10 +878,10 @@ function TokenCachePanel(props) {
509
878
  break;
510
879
  }
511
880
  const hitRate = lastMsgHitRate >= 0 ? lastMsgHitRate : 0;
512
- const freshTotal = input + read, sessionHitRate = freshTotal > 0 ? read / freshTotal * 100 : 0;
881
+ const freshTotal = input + read + write, sessionHitRate = freshTotal > 0 ? read / freshTotal * 100 : 0;
513
882
  const model = mid.split("/").pop() ?? mid, hasPricing = inputRate > 0 || cacheReadRate > 0 || cacheWriteRate > 0;
514
883
  const hasTrendData = prevMsgHitRate >= 0 && lastMsgHitRate >= 0;
515
- const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName = pid || "";
884
+ const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName2 = pid || "";
516
885
  const distData = untrack(() => {
517
886
  let dist = {
518
887
  system: 0,
@@ -602,13 +971,13 @@ function TokenCachePanel(props) {
602
971
  }
603
972
  for (let i = msgs.length - 1; i >= 0; i--) {
604
973
  if (msgs[i].role !== "assistant") continue;
605
- const t2 = msgs[i].tokens;
606
- if (t2 && (t2.input > 0 || (t2.cache?.read ?? 0) > 0)) {
974
+ const tok = msgs[i].tokens;
975
+ if (tok && ((tok.input ?? 0) > 0 || (tok.cache?.read ?? 0) > 0 || (tok.cache?.write ?? 0) > 0)) {
607
976
  lastAssMsg = msgs[i];
608
977
  break;
609
978
  }
610
979
  }
611
- dist.apiInput = num(lastAssMsg?.tokens?.input) + num(lastAssMsg?.tokens?.cache?.read);
980
+ dist.apiInput = num(lastAssMsg?.tokens?.input) + num(lastAssMsg?.tokens?.cache?.read) + num(lastAssMsg?.tokens?.cache?.write);
612
981
  dist.apiOutput = num(lastAssMsg?.tokens?.output);
613
982
  hasDistData = dist.system + dist.user + dist.agent + dist.toolCall + dist.toolResult > 0 || dist.apiOutput > 0 || dist.apiInput > 0;
614
983
  } catch {
@@ -637,7 +1006,7 @@ function TokenCachePanel(props) {
637
1006
  hasData: read > 0 || write > 0 || input > 0 || output > 0 || cost > 0,
638
1007
  trend,
639
1008
  hasTrendData,
640
- providerName,
1009
+ providerName: providerName2,
641
1010
  sessionHitRate,
642
1011
  dist: distData.finalDist,
643
1012
  hasDistData: distData.finalHasDist,
@@ -689,6 +1058,20 @@ function TokenCachePanel(props) {
689
1058
  if (typeof rate === "number" && rate > 0) setExchangeRate(rate);
690
1059
  const balCur = props.api.kv.get(`${KV_PREFIX}.balance_currency`);
691
1060
  if (typeof balCur === "string") setBalanceCurrency(balCur);
1061
+ const savedProvider = props.api.kv.get(`${KV_PREFIX}.balance.provider`);
1062
+ if (typeof savedProvider === "string" && balanceProviders.some((p) => p.id === savedProvider)) {
1063
+ setBalanceProviderId(savedProvider);
1064
+ setBalanceUnsupported(false);
1065
+ }
1066
+ const savedAuto = props.api.kv.get(`${KV_PREFIX}.balance.auto`);
1067
+ if (typeof savedAuto === "boolean") setAutoBalance(savedAuto);
1068
+ const legacyKey = props.api.kv.get(`${KV_PREFIX}.ds_key`, "");
1069
+ if (legacyKey) {
1070
+ const dsKey = props.api.kv.get(`${KV_PREFIX}.balance.deepseek.key`, "");
1071
+ if (!dsKey) props.api.kv.set(`${KV_PREFIX}.balance.deepseek.key`, legacyKey);
1072
+ props.api.kv.set(`${KV_PREFIX}.ds_key`, "");
1073
+ }
1074
+ props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
692
1075
  setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)));
693
1076
  setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)));
694
1077
  setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)));
@@ -696,10 +1079,6 @@ function TokenCachePanel(props) {
696
1079
  setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX}.section.balance`, true)));
697
1080
  const bv = props.api.kv.get(`${KV_PREFIX}.border`, true);
698
1081
  setBorderVisible(bv !== false);
699
- const savedLang = props.api.kv.get(`${KV_PREFIX}.lang`);
700
- if (savedLang === "zh" || savedLang === "en") {
701
- setLangZH(savedLang === "zh");
702
- }
703
1082
  const cachedDist = props.api.kv.get(`${KV_PREFIX}.dist_snapshot`);
704
1083
  if (cachedDist) {
705
1084
  setLastDist(cachedDist);
@@ -746,10 +1125,8 @@ function TokenCachePanel(props) {
746
1125
  setRefreshTick((v) => v + 1);
747
1126
  });
748
1127
  setRefreshTick((v) => v + 1);
749
- const balanceTimer = setInterval(pollBalance, BALANCE_POLL_MS);
750
1128
  onCleanup(() => {
751
1129
  clearTimeout(partTimer);
752
- clearInterval(balanceTimer);
753
1130
  unsubPart();
754
1131
  unsubMsg();
755
1132
  unsubSession();
@@ -777,11 +1154,12 @@ function TokenCachePanel(props) {
777
1154
  const gutter = createMemo(() => borderVisible() ? 6 : 0);
778
1155
  const sep = createMemo(() => "\u2500".repeat(Math.max(1, panelWidth() - gutter())));
779
1156
  function trendLabel(t2) {
780
- return (t2 > 0 ? "\u2191" : t2 < 0 ? "\u2193" : "-") + (t2 !== 0 ? Math.abs(t2).toFixed(1) + "%" : "");
1157
+ if (Math.abs(t2) < 0.05) return "-";
1158
+ return (t2 > 0 ? "\u2191" : "\u2193") + Math.abs(t2).toFixed(1) + "%";
781
1159
  }
782
1160
  const barW = createMemo(() => {
783
1161
  const trendSpace = data().hasTrendData ? LABEL_GAP + visualWidth(trendLabel(data().trend)) : 0;
784
- const overhead = visualWidth(t().hit) + LABEL_GAP + BAR_BRACKETS + BAR_GAP + PCT_FIXED_WIDTH + trendSpace + gutter();
1162
+ const overhead = visualWidth(t("hit")) + LABEL_GAP + BAR_BRACKETS + BAR_GAP + PCT_FIXED_WIDTH + trendSpace + gutter();
785
1163
  return Math.max(3, panelWidth() - overhead);
786
1164
  });
787
1165
  const bar = createMemo(() => progressBar(data().hitRate, barW()));
@@ -835,7 +1213,7 @@ function TokenCachePanel(props) {
835
1213
  }));
836
1214
  _$insert(_el$3, () => open() ? "\u25BC " : "\u25B6 ");
837
1215
  _$insertNode(_el$4, _el$5);
838
- _$insert(_el$5, () => t().title);
1216
+ _$insert(_el$5, () => t("title"));
839
1217
  _$insert(_el$4, _$createComponent(Show, {
840
1218
  get when() {
841
1219
  return open();
@@ -862,13 +1240,13 @@ function TokenCachePanel(props) {
862
1240
  get children() {
863
1241
  return [(() => {
864
1242
  var _el$8 = _$createElement("span");
865
- _$insert(_el$8, () => " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t().title) - visualWidth(pct() + " " + t().hitFolded + " " + trendLabel(data().trend)))));
1243
+ _$insert(_el$8, () => " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t("title")) - visualWidth(pct() + " " + t("hitFolded") + " " + trendLabel(data().trend)))));
866
1244
  return _el$8;
867
1245
  })(), (() => {
868
1246
  var _el$9 = _$createElement("span"), _el$0 = _$createTextNode(` `);
869
1247
  _$insertNode(_el$9, _el$0);
870
1248
  _$insert(_el$9, pct, _el$0);
871
- _$insert(_el$9, () => t().hitFolded, null);
1249
+ _$insert(_el$9, () => t("hitFolded"), null);
872
1250
  _$effect((_$p) => _$setProp(_el$9, "style", {
873
1251
  fg: hitColor()
874
1252
  }, _$p));
@@ -878,7 +1256,7 @@ function TokenCachePanel(props) {
878
1256
  _$insertNode(_el$1, _el$10);
879
1257
  _$insert(_el$1, () => trendLabel(data().trend), null);
880
1258
  _$effect((_$p) => _$setProp(_el$1, "style", {
881
- fg: data().trend !== 0 ? data().trend > 0 ? pal().success : pal().error : pal().text
1259
+ fg: Math.abs(data().trend) >= 0.05 ? data().trend > 0 ? pal().success : pal().error : pal().text
882
1260
  }, _$p));
883
1261
  return _el$1;
884
1262
  })()];
@@ -890,13 +1268,13 @@ function TokenCachePanel(props) {
890
1268
  get children() {
891
1269
  return [(() => {
892
1270
  var _el$11 = _$createElement("span");
893
- _$insert(_el$11, () => " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t().title) - visualWidth(pct() + " " + t().hitFolded))));
1271
+ _$insert(_el$11, () => " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t("title")) - visualWidth(pct() + " " + t("hitFolded")))));
894
1272
  return _el$11;
895
1273
  })(), (() => {
896
1274
  var _el$12 = _$createElement("span"), _el$13 = _$createTextNode(` `);
897
1275
  _$insertNode(_el$12, _el$13);
898
1276
  _$insert(_el$12, pct, _el$13);
899
- _$insert(_el$12, () => t().hitFolded, null);
1277
+ _$insert(_el$12, () => t("hitFolded"), null);
900
1278
  _$effect((_$p) => _$setProp(_el$12, "style", {
901
1279
  fg: hitColor()
902
1280
  }, _$p));
@@ -917,28 +1295,28 @@ function TokenCachePanel(props) {
917
1295
  },
918
1296
  get children() {
919
1297
  return (() => {
920
- const prefix = " \u21B3 " + (langZH() ? "\u5B50\u4EE3\u7406: " : "Sub: ");
1298
+ const prefix = " \u21B3 " + t("subPrefix");
921
1299
  const maxSidW = Math.max(6, panelWidth() - visualWidth(prefix));
922
1300
  return (() => {
923
- var _el$64 = _$createElement("text"), _el$65 = _$createElement("span"), _el$66 = _$createElement("span");
924
- _$insertNode(_el$64, _el$65);
925
- _$insertNode(_el$64, _el$66);
926
- _$insert(_el$65, prefix);
927
- _$insert(_el$66, () => truncateVisual(props.signals.overrideSessionId(), maxSidW));
1301
+ var _el$69 = _$createElement("text"), _el$70 = _$createElement("span"), _el$71 = _$createElement("span");
1302
+ _$insertNode(_el$69, _el$70);
1303
+ _$insertNode(_el$69, _el$71);
1304
+ _$insert(_el$70, prefix);
1305
+ _$insert(_el$71, () => truncateVisual(props.signals.overrideSessionId(), maxSidW));
928
1306
  _$effect((_p$) => {
929
- var _v$15 = {
1307
+ var _v$17 = {
930
1308
  fg: pal().muted
931
- }, _v$16 = {
1309
+ }, _v$18 = {
932
1310
  fg: pal().text
933
1311
  };
934
- _v$15 !== _p$.e && (_p$.e = _$setProp(_el$65, "style", _v$15, _p$.e));
935
- _v$16 !== _p$.t && (_p$.t = _$setProp(_el$66, "style", _v$16, _p$.t));
1312
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$70, "style", _v$17, _p$.e));
1313
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$71, "style", _v$18, _p$.t));
936
1314
  return _p$;
937
1315
  }, {
938
1316
  e: void 0,
939
1317
  t: void 0
940
1318
  });
941
- return _el$64;
1319
+ return _el$69;
942
1320
  })();
943
1321
  })();
944
1322
  }
@@ -948,30 +1326,30 @@ function TokenCachePanel(props) {
948
1326
  },
949
1327
  get fallback() {
950
1328
  return [(() => {
951
- var _el$67 = _$createElement("text");
952
- _$insert(_el$67, sep);
953
- _$effect((_$p) => _$setProp(_el$67, "fg", pal().muted, _$p));
954
- return _el$67;
1329
+ var _el$72 = _$createElement("text");
1330
+ _$insert(_el$72, sep);
1331
+ _$effect((_$p) => _$setProp(_el$72, "fg", pal().muted, _$p));
1332
+ return _el$72;
955
1333
  })(), (() => {
956
- var _el$68 = _$createElement("text"), _el$69 = _$createElement("span"), _el$71 = _$createElement("span");
957
- _$insertNode(_el$68, _el$69);
958
- _$insertNode(_el$68, _el$71);
959
- _$insertNode(_el$69, _$createTextNode(`> `));
960
- _$insert(_el$71, () => t().noData);
1334
+ var _el$73 = _$createElement("text"), _el$74 = _$createElement("span"), _el$76 = _$createElement("span");
1335
+ _$insertNode(_el$73, _el$74);
1336
+ _$insertNode(_el$73, _el$76);
1337
+ _$insertNode(_el$74, _$createTextNode(`> `));
1338
+ _$insert(_el$76, () => t("noData"));
961
1339
  _$effect((_p$) => {
962
- var _v$17 = {
1340
+ var _v$19 = {
963
1341
  fg: pal().muted
964
- }, _v$18 = {
1342
+ }, _v$20 = {
965
1343
  fg: pal().muted
966
1344
  };
967
- _v$17 !== _p$.e && (_p$.e = _$setProp(_el$69, "style", _v$17, _p$.e));
968
- _v$18 !== _p$.t && (_p$.t = _$setProp(_el$71, "style", _v$18, _p$.t));
1345
+ _v$19 !== _p$.e && (_p$.e = _$setProp(_el$74, "style", _v$19, _p$.e));
1346
+ _v$20 !== _p$.t && (_p$.t = _$setProp(_el$76, "style", _v$20, _p$.t));
969
1347
  return _p$;
970
1348
  }, {
971
1349
  e: void 0,
972
1350
  t: void 0
973
1351
  });
974
- return _el$68;
1352
+ return _el$73;
975
1353
  })()];
976
1354
  },
977
1355
  get children() {
@@ -986,7 +1364,7 @@ function TokenCachePanel(props) {
986
1364
  _$insertNode(_el$15, _el$18);
987
1365
  _$insertNode(_el$15, _el$21);
988
1366
  _$insertNode(_el$16, _el$17);
989
- _$insert(_el$16, () => t().hit, _el$17);
1367
+ _$insert(_el$16, () => t("hit"), _el$17);
990
1368
  _$insertNode(_el$18, _el$19);
991
1369
  _$insertNode(_el$18, _el$20);
992
1370
  _$insert(_el$18, bar, _el$20);
@@ -1000,7 +1378,7 @@ function TokenCachePanel(props) {
1000
1378
  _$insertNode(_el$22, _el$23);
1001
1379
  _$insert(_el$22, () => trendLabel(data().trend), null);
1002
1380
  _$effect((_$p) => _$setProp(_el$22, "style", {
1003
- fg: data().trend !== 0 ? data().trend > 0 ? pal().success : pal().error : pal().text
1381
+ fg: Math.abs(data().trend) >= 0.05 ? data().trend > 0 ? pal().success : pal().error : pal().text
1004
1382
  }, _$p));
1005
1383
  return _el$22;
1006
1384
  }
@@ -1025,7 +1403,7 @@ function TokenCachePanel(props) {
1025
1403
  return _el$15;
1026
1404
  })(), (() => {
1027
1405
  var _el$24 = _$createElement("text");
1028
- _$insert(_el$24, () => justify(t().totalHit, (Math.floor(data().sessionHitRate * 10) / 10).toFixed(1) + "%"));
1406
+ _$insert(_el$24, () => justify(t("totalHit"), (Math.floor(data().sessionHitRate * 10) / 10).toFixed(1) + "%"));
1029
1407
  _$effect((_$p) => _$setProp(_el$24, "fg", pal().muted, _$p));
1030
1408
  return _el$24;
1031
1409
  })(), _$createComponent(Show, {
@@ -1045,8 +1423,8 @@ function TokenCachePanel(props) {
1045
1423
  }));
1046
1424
  _$insert(_el$26, () => detailOpen() ? "\u25BC " : "\u25B6 ");
1047
1425
  _$insertNode(_el$27, _el$28);
1048
- _$insert(_el$28, () => t().secDetail);
1049
- _$insert(_el$29, () => sep().slice(visualWidth((detailOpen() ? "\u25BC " : "\u25B6 ") + t().secDetail)));
1426
+ _$insert(_el$28, () => t("secDetail"));
1427
+ _$insert(_el$29, () => sep().slice(visualWidth((detailOpen() ? "\u25BC " : "\u25B6 ") + t("secDetail"))));
1050
1428
  _$effect((_p$) => {
1051
1429
  var _v$4 = {
1052
1430
  fg: pal().muted
@@ -1076,7 +1454,7 @@ function TokenCachePanel(props) {
1076
1454
  },
1077
1455
  get children() {
1078
1456
  var _el$30 = _$createElement("text");
1079
- _$insert(_el$30, () => justify(t().read, fmt(data().read), t().tok));
1457
+ _$insert(_el$30, () => justify(t("read"), fmt(data().read), t("tok")));
1080
1458
  _$effect((_$p) => _$setProp(_el$30, "fg", pal().muted, _$p));
1081
1459
  return _el$30;
1082
1460
  }
@@ -1086,18 +1464,18 @@ function TokenCachePanel(props) {
1086
1464
  },
1087
1465
  get children() {
1088
1466
  var _el$31 = _$createElement("text");
1089
- _$insert(_el$31, () => justify(t().write, fmt(data().write), t().tok));
1467
+ _$insert(_el$31, () => justify(t("write"), fmt(data().write), t("tok")));
1090
1468
  _$effect((_$p) => _$setProp(_el$31, "fg", pal().muted, _$p));
1091
1469
  return _el$31;
1092
1470
  }
1093
1471
  }), (() => {
1094
1472
  var _el$32 = _$createElement("text");
1095
- _$insert(_el$32, () => justify(t().miss, fmt(data().freshInput), t().tok));
1473
+ _$insert(_el$32, () => justify(t("miss"), fmt(data().freshInput + data().write), t("tok")));
1096
1474
  _$effect((_$p) => _$setProp(_el$32, "fg", pal().muted, _$p));
1097
1475
  return _el$32;
1098
1476
  })(), (() => {
1099
1477
  var _el$33 = _$createElement("text");
1100
- _$insert(_el$33, () => justify(t().out, fmt(data().output), t().tok));
1478
+ _$insert(_el$33, () => justify(t("out"), fmt(data().output), t("tok")));
1101
1479
  _$effect((_$p) => _$setProp(_el$33, "fg", pal().muted, _$p));
1102
1480
  return _el$33;
1103
1481
  })(), _$createComponent(Show, {
@@ -1109,8 +1487,8 @@ function TokenCachePanel(props) {
1109
1487
  _$insertNode(_el$34, _el$35);
1110
1488
  _$insertNode(_el$34, _el$36);
1111
1489
  _$insertNode(_el$34, _el$37);
1112
- _$insert(_el$35, () => t().saved);
1113
- _$insert(_el$36, () => " ".repeat(Math.max(1, panelWidth() - gutter() - visualWidth(t().saved) - visualWidth("~" + fmtCost(data().saved, currencySymbol(), exchangeRate())))));
1490
+ _$insert(_el$35, () => t("saved"));
1491
+ _$insert(_el$36, () => " ".repeat(Math.max(1, panelWidth() - gutter() - visualWidth(t("saved")) - visualWidth("~" + fmtCost(data().saved, currencySymbol(), exchangeRate())))));
1114
1492
  _$insertNode(_el$37, _el$38);
1115
1493
  _$insert(_el$37, () => fmtCost(data().saved, currencySymbol(), exchangeRate()), null);
1116
1494
  _$effect((_p$) => {
@@ -1138,37 +1516,37 @@ function TokenCachePanel(props) {
1138
1516
  },
1139
1517
  get children() {
1140
1518
  return [_$memo(() => (() => {
1141
- var _el$72 = _$createElement("text"), _el$73 = _$createElement("span"), _el$74 = _$createElement("span"), _el$75 = _$createElement("b"), _el$76 = _$createElement("span");
1142
- _$insertNode(_el$72, _el$73);
1143
- _$insertNode(_el$72, _el$74);
1144
- _$insertNode(_el$72, _el$76);
1145
- _$setProp(_el$72, "onMouseUp", () => setModelOpen((o) => {
1519
+ var _el$77 = _$createElement("text"), _el$78 = _$createElement("span"), _el$79 = _$createElement("span"), _el$80 = _$createElement("b"), _el$81 = _$createElement("span");
1520
+ _$insertNode(_el$77, _el$78);
1521
+ _$insertNode(_el$77, _el$79);
1522
+ _$insertNode(_el$77, _el$81);
1523
+ _$setProp(_el$77, "onMouseUp", () => setModelOpen((o) => {
1146
1524
  const n = !o;
1147
1525
  persistFold("model", n);
1148
1526
  return n;
1149
1527
  }));
1150
- _$insert(_el$73, () => modelOpen() ? "\u25BC " : "\u25B6 ");
1151
- _$insertNode(_el$74, _el$75);
1152
- _$insert(_el$75, () => t().secModel);
1153
- _$insert(_el$76, () => sep().slice(visualWidth((modelOpen() ? "\u25BC " : "\u25B6 ") + t().secModel)));
1528
+ _$insert(_el$78, () => modelOpen() ? "\u25BC " : "\u25B6 ");
1529
+ _$insertNode(_el$79, _el$80);
1530
+ _$insert(_el$80, () => t("secModel"));
1531
+ _$insert(_el$81, () => sep().slice(visualWidth((modelOpen() ? "\u25BC " : "\u25B6 ") + t("secModel"))));
1154
1532
  _$effect((_p$) => {
1155
- var _v$19 = {
1533
+ var _v$21 = {
1156
1534
  fg: pal().muted
1157
- }, _v$20 = {
1535
+ }, _v$22 = {
1158
1536
  fg: pal().primary
1159
- }, _v$21 = {
1537
+ }, _v$23 = {
1160
1538
  fg: pal().muted
1161
1539
  };
1162
- _v$19 !== _p$.e && (_p$.e = _$setProp(_el$73, "style", _v$19, _p$.e));
1163
- _v$20 !== _p$.t && (_p$.t = _$setProp(_el$74, "style", _v$20, _p$.t));
1164
- _v$21 !== _p$.a && (_p$.a = _$setProp(_el$76, "style", _v$21, _p$.a));
1540
+ _v$21 !== _p$.e && (_p$.e = _$setProp(_el$78, "style", _v$21, _p$.e));
1541
+ _v$22 !== _p$.t && (_p$.t = _$setProp(_el$79, "style", _v$22, _p$.t));
1542
+ _v$23 !== _p$.a && (_p$.a = _$setProp(_el$81, "style", _v$23, _p$.a));
1165
1543
  return _p$;
1166
1544
  }, {
1167
1545
  e: void 0,
1168
1546
  t: void 0,
1169
1547
  a: void 0
1170
1548
  });
1171
- return _el$72;
1549
+ return _el$77;
1172
1550
  })()), _$createComponent(Show, {
1173
1551
  get when() {
1174
1552
  return modelOpen();
@@ -1176,7 +1554,7 @@ function TokenCachePanel(props) {
1176
1554
  get children() {
1177
1555
  return [(() => {
1178
1556
  var _el$39 = _$createElement("text");
1179
- _$insert(_el$39, () => justify(t().cost, fmtCost(data().cost, currencySymbol(), exchangeRate())));
1557
+ _$insert(_el$39, () => justify(t("cost"), fmtCost(data().cost, currencySymbol(), exchangeRate())));
1180
1558
  _$effect((_$p) => _$setProp(_el$39, "fg", pal().text, _$p));
1181
1559
  return _el$39;
1182
1560
  })(), _$createComponent(Show, {
@@ -1185,13 +1563,13 @@ function TokenCachePanel(props) {
1185
1563
  },
1186
1564
  get children() {
1187
1565
  var _el$40 = _$createElement("text");
1188
- _$insert(_el$40, () => justify(t().provider, data().providerName));
1566
+ _$insert(_el$40, () => justify(t("provider"), data().providerName));
1189
1567
  _$effect((_$p) => _$setProp(_el$40, "fg", pal().muted, _$p));
1190
1568
  return _el$40;
1191
1569
  }
1192
1570
  }), (() => {
1193
1571
  var _el$41 = _$createElement("text");
1194
- _$insert(_el$41, () => justify(t().model, data().model));
1572
+ _$insert(_el$41, () => justify(t("model"), data().model));
1195
1573
  _$effect((_$p) => _$setProp(_el$41, "fg", pal().muted, _$p));
1196
1574
  return _el$41;
1197
1575
  })(), _$createComponent(Show, {
@@ -1201,7 +1579,7 @@ function TokenCachePanel(props) {
1201
1579
  get children() {
1202
1580
  return [(() => {
1203
1581
  var _el$42 = _$createElement("text");
1204
- _$insert(_el$42, () => justify(t().rate, currencySymbol() + (data().inputRate * exchangeRate()).toFixed(2) + "/M " + t().inputRate));
1582
+ _$insert(_el$42, () => justify(t("rate"), currencySymbol() + (data().inputRate * exchangeRate()).toFixed(2) + "/M " + t("inputRate")));
1205
1583
  _$effect((_$p) => _$setProp(_el$42, "fg", pal().muted, _$p));
1206
1584
  return _el$42;
1207
1585
  })(), _$createComponent(Show, {
@@ -1210,7 +1588,7 @@ function TokenCachePanel(props) {
1210
1588
  },
1211
1589
  get children() {
1212
1590
  var _el$43 = _$createElement("text");
1213
- _$insert(_el$43, () => justify("", currencySymbol() + (data().cacheReadRate * exchangeRate()).toFixed(2) + "/M " + t().cacheRate));
1591
+ _$insert(_el$43, () => justify("", currencySymbol() + (data().cacheReadRate * exchangeRate()).toFixed(2) + "/M " + t("cacheRate")));
1214
1592
  _$effect((_$p) => _$setProp(_el$43, "fg", pal().muted, _$p));
1215
1593
  return _el$43;
1216
1594
  }
@@ -1220,7 +1598,7 @@ function TokenCachePanel(props) {
1220
1598
  },
1221
1599
  get children() {
1222
1600
  var _el$44 = _$createElement("text");
1223
- _$insert(_el$44, () => justify("", currencySymbol() + (data().cacheWriteRate * exchangeRate()).toFixed(2) + "/M " + t().writeRate));
1601
+ _$insert(_el$44, () => justify("", currencySymbol() + (data().cacheWriteRate * exchangeRate()).toFixed(2) + "/M " + t("writeRate")));
1224
1602
  _$effect((_$p) => _$setProp(_el$44, "fg", pal().muted, _$p));
1225
1603
  return _el$44;
1226
1604
  }
@@ -1241,37 +1619,37 @@ function TokenCachePanel(props) {
1241
1619
  },
1242
1620
  get children() {
1243
1621
  return [_$memo(() => (() => {
1244
- var _el$77 = _$createElement("text"), _el$78 = _$createElement("span"), _el$79 = _$createElement("span"), _el$80 = _$createElement("b"), _el$81 = _$createElement("span");
1245
- _$insertNode(_el$77, _el$78);
1246
- _$insertNode(_el$77, _el$79);
1247
- _$insertNode(_el$77, _el$81);
1248
- _$setProp(_el$77, "onMouseUp", () => setDistOpen((o) => {
1622
+ var _el$82 = _$createElement("text"), _el$83 = _$createElement("span"), _el$84 = _$createElement("span"), _el$85 = _$createElement("b"), _el$86 = _$createElement("span");
1623
+ _$insertNode(_el$82, _el$83);
1624
+ _$insertNode(_el$82, _el$84);
1625
+ _$insertNode(_el$82, _el$86);
1626
+ _$setProp(_el$82, "onMouseUp", () => setDistOpen((o) => {
1249
1627
  const n = !o;
1250
1628
  persistFold("dist", n);
1251
1629
  return n;
1252
1630
  }));
1253
- _$insert(_el$78, () => distOpen() ? "\u25BC " : "\u25B6 ");
1254
- _$insertNode(_el$79, _el$80);
1255
- _$insert(_el$80, () => t().distTitle);
1256
- _$insert(_el$81, () => sep().slice(visualWidth((distOpen() ? "\u25BC " : "\u25B6 ") + t().distTitle)));
1631
+ _$insert(_el$83, () => distOpen() ? "\u25BC " : "\u25B6 ");
1632
+ _$insertNode(_el$84, _el$85);
1633
+ _$insert(_el$85, () => t("distTitle"));
1634
+ _$insert(_el$86, () => sep().slice(visualWidth((distOpen() ? "\u25BC " : "\u25B6 ") + t("distTitle"))));
1257
1635
  _$effect((_p$) => {
1258
- var _v$22 = {
1636
+ var _v$24 = {
1259
1637
  fg: pal().muted
1260
- }, _v$23 = {
1638
+ }, _v$25 = {
1261
1639
  fg: pal().primary
1262
- }, _v$24 = {
1640
+ }, _v$26 = {
1263
1641
  fg: pal().muted
1264
1642
  };
1265
- _v$22 !== _p$.e && (_p$.e = _$setProp(_el$78, "style", _v$22, _p$.e));
1266
- _v$23 !== _p$.t && (_p$.t = _$setProp(_el$79, "style", _v$23, _p$.t));
1267
- _v$24 !== _p$.a && (_p$.a = _$setProp(_el$81, "style", _v$24, _p$.a));
1643
+ _v$24 !== _p$.e && (_p$.e = _$setProp(_el$83, "style", _v$24, _p$.e));
1644
+ _v$25 !== _p$.t && (_p$.t = _$setProp(_el$84, "style", _v$25, _p$.t));
1645
+ _v$26 !== _p$.a && (_p$.a = _$setProp(_el$86, "style", _v$26, _p$.a));
1268
1646
  return _p$;
1269
1647
  }, {
1270
1648
  e: void 0,
1271
1649
  t: void 0,
1272
1650
  a: void 0
1273
1651
  });
1274
- return _el$77;
1652
+ return _el$82;
1275
1653
  })()), _$createComponent(Show, {
1276
1654
  get when() {
1277
1655
  return distOpen();
@@ -1283,7 +1661,7 @@ function TokenCachePanel(props) {
1283
1661
  },
1284
1662
  get children() {
1285
1663
  var _el$45 = _$createElement("text");
1286
- _$insert(_el$45, () => justify(t().distSys, fmt(data().dist.system), t().tok));
1664
+ _$insert(_el$45, () => justify(t("distSys"), fmt(data().dist.system), t("tok")));
1287
1665
  _$effect((_$p) => _$setProp(_el$45, "fg", pal().muted, _$p));
1288
1666
  return _el$45;
1289
1667
  }
@@ -1293,7 +1671,7 @@ function TokenCachePanel(props) {
1293
1671
  },
1294
1672
  get children() {
1295
1673
  var _el$46 = _$createElement("text");
1296
- _$insert(_el$46, () => justify(t().distUser, fmt(data().dist.user), t().tok));
1674
+ _$insert(_el$46, () => justify(t("distUser"), fmt(data().dist.user), t("tok")));
1297
1675
  _$effect((_$p) => _$setProp(_el$46, "fg", pal().muted, _$p));
1298
1676
  return _el$46;
1299
1677
  }
@@ -1303,7 +1681,7 @@ function TokenCachePanel(props) {
1303
1681
  },
1304
1682
  get children() {
1305
1683
  var _el$47 = _$createElement("text");
1306
- _$insert(_el$47, () => justify(t().distAgent, fmt(data().dist.agent), t().tok));
1684
+ _$insert(_el$47, () => justify(t("distAgent"), fmt(data().dist.agent), t("tok")));
1307
1685
  _$effect((_$p) => _$setProp(_el$47, "fg", pal().muted, _$p));
1308
1686
  return _el$47;
1309
1687
  }
@@ -1313,7 +1691,7 @@ function TokenCachePanel(props) {
1313
1691
  },
1314
1692
  get children() {
1315
1693
  var _el$48 = _$createElement("text");
1316
- _$insert(_el$48, () => justify(t().distTool, fmt(data().dist.toolCall), t().tok));
1694
+ _$insert(_el$48, () => justify(t("distTool"), fmt(data().dist.toolCall), t("tok")));
1317
1695
  _$effect((_$p) => _$setProp(_el$48, "fg", pal().muted, _$p));
1318
1696
  return _el$48;
1319
1697
  }
@@ -1323,13 +1701,13 @@ function TokenCachePanel(props) {
1323
1701
  },
1324
1702
  get children() {
1325
1703
  var _el$49 = _$createElement("text");
1326
- _$insert(_el$49, () => justify(t().distRes, fmt(data().dist.toolResult), t().tok));
1704
+ _$insert(_el$49, () => justify(t("distRes"), fmt(data().dist.toolResult), t("tok")));
1327
1705
  _$effect((_$p) => _$setProp(_el$49, "fg", pal().muted, _$p));
1328
1706
  return _el$49;
1329
1707
  }
1330
1708
  }), (() => {
1331
1709
  var _el$50 = _$createElement("text");
1332
- _$insert(_el$50, () => justify(t().distTotal, fmt(data().dist.apiInput), t().tok));
1710
+ _$insert(_el$50, () => justify(t("distTotal"), fmt(data().dist.apiInput), t("tok")));
1333
1711
  _$effect((_$p) => _$setProp(_el$50, "fg", pal().text, _$p));
1334
1712
  return _el$50;
1335
1713
  })()];
@@ -1349,37 +1727,37 @@ function TokenCachePanel(props) {
1349
1727
  },
1350
1728
  get children() {
1351
1729
  return [_$memo(() => (() => {
1352
- var _el$82 = _$createElement("text"), _el$83 = _$createElement("span"), _el$84 = _$createElement("span"), _el$85 = _$createElement("b"), _el$86 = _$createElement("span"), _el$87 = _$createTextNode(` (`), _el$88 = _$createTextNode(`)`), _el$89 = _$createElement("span");
1353
- _$insertNode(_el$82, _el$83);
1354
- _$insertNode(_el$82, _el$84);
1355
- _$insertNode(_el$82, _el$86);
1356
- _$insertNode(_el$82, _el$89);
1357
- _$setProp(_el$82, "onMouseUp", () => setSkillsOpen((o) => {
1730
+ var _el$87 = _$createElement("text"), _el$88 = _$createElement("span"), _el$89 = _$createElement("span"), _el$90 = _$createElement("b"), _el$91 = _$createElement("span"), _el$92 = _$createTextNode(` (`), _el$93 = _$createTextNode(`)`), _el$94 = _$createElement("span");
1731
+ _$insertNode(_el$87, _el$88);
1732
+ _$insertNode(_el$87, _el$89);
1733
+ _$insertNode(_el$87, _el$91);
1734
+ _$insertNode(_el$87, _el$94);
1735
+ _$setProp(_el$87, "onMouseUp", () => setSkillsOpen((o) => {
1358
1736
  const n = !o;
1359
1737
  persistFold("skills", n);
1360
1738
  return n;
1361
1739
  }));
1362
- _$insert(_el$83, () => skillsOpen() ? "\u25BC " : "\u25B6 ");
1363
- _$insertNode(_el$84, _el$85);
1364
- _$insert(_el$85, () => t().secSkills);
1365
- _$insertNode(_el$86, _el$87);
1366
- _$insertNode(_el$86, _el$88);
1367
- _$insert(_el$86, () => data().skills.length, _el$88);
1368
- _$insert(_el$89, () => sep().slice(visualWidth((skillsOpen() ? "\u25BC " : "\u25B6 ") + t().secSkills + ` (${data().skills.length})`)));
1740
+ _$insert(_el$88, () => skillsOpen() ? "\u25BC " : "\u25B6 ");
1741
+ _$insertNode(_el$89, _el$90);
1742
+ _$insert(_el$90, () => t("secSkills"));
1743
+ _$insertNode(_el$91, _el$92);
1744
+ _$insertNode(_el$91, _el$93);
1745
+ _$insert(_el$91, () => data().skills.length, _el$93);
1746
+ _$insert(_el$94, () => sep().slice(visualWidth((skillsOpen() ? "\u25BC " : "\u25B6 ") + t("secSkills") + ` (${data().skills.length})`)));
1369
1747
  _$effect((_p$) => {
1370
- var _v$25 = {
1748
+ var _v$27 = {
1371
1749
  fg: pal().muted
1372
- }, _v$26 = {
1750
+ }, _v$28 = {
1373
1751
  fg: pal().primary
1374
- }, _v$27 = {
1752
+ }, _v$29 = {
1375
1753
  fg: pal().muted
1376
- }, _v$28 = {
1754
+ }, _v$30 = {
1377
1755
  fg: pal().muted
1378
1756
  };
1379
- _v$25 !== _p$.e && (_p$.e = _$setProp(_el$83, "style", _v$25, _p$.e));
1380
- _v$26 !== _p$.t && (_p$.t = _$setProp(_el$84, "style", _v$26, _p$.t));
1381
- _v$27 !== _p$.a && (_p$.a = _$setProp(_el$86, "style", _v$27, _p$.a));
1382
- _v$28 !== _p$.o && (_p$.o = _$setProp(_el$89, "style", _v$28, _p$.o));
1757
+ _v$27 !== _p$.e && (_p$.e = _$setProp(_el$88, "style", _v$27, _p$.e));
1758
+ _v$28 !== _p$.t && (_p$.t = _$setProp(_el$89, "style", _v$28, _p$.t));
1759
+ _v$29 !== _p$.a && (_p$.a = _$setProp(_el$91, "style", _v$29, _p$.a));
1760
+ _v$30 !== _p$.o && (_p$.o = _$setProp(_el$94, "style", _v$30, _p$.o));
1383
1761
  return _p$;
1384
1762
  }, {
1385
1763
  e: void 0,
@@ -1387,21 +1765,21 @@ function TokenCachePanel(props) {
1387
1765
  a: void 0,
1388
1766
  o: void 0
1389
1767
  });
1390
- return _el$82;
1768
+ return _el$87;
1391
1769
  })()), _$createComponent(Show, {
1392
1770
  get when() {
1393
1771
  return skillsOpen();
1394
1772
  },
1395
1773
  get children() {
1396
1774
  return data().skills.map((sk) => {
1397
- const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t().tok);
1775
+ const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t("tok"));
1398
1776
  const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
1399
1777
  const label = truncateVisual(sk.name, maxLabel);
1400
1778
  return (() => {
1401
- var _el$90 = _$createElement("text");
1402
- _$insert(_el$90, () => justify(label, fmt(sk.tokens), t().tok));
1403
- _$effect((_$p) => _$setProp(_el$90, "fg", pal().muted, _$p));
1404
- return _el$90;
1779
+ var _el$95 = _$createElement("text");
1780
+ _$insert(_el$95, () => justify(label, fmt(sk.tokens), t("tok")));
1781
+ _$effect((_$p) => _$setProp(_el$95, "fg", pal().muted, _$p));
1782
+ return _el$95;
1405
1783
  })();
1406
1784
  });
1407
1785
  }
@@ -1421,14 +1799,14 @@ function TokenCachePanel(props) {
1421
1799
  return _el$51;
1422
1800
  })(), _$createComponent(Show, {
1423
1801
  get when() {
1424
- return balanceState().status === "idle";
1802
+ return balanceUnsupported();
1425
1803
  },
1426
1804
  get children() {
1427
1805
  var _el$52 = _$createElement("text"), _el$53 = _$createElement("span"), _el$55 = _$createElement("span");
1428
1806
  _$insertNode(_el$52, _el$53);
1429
1807
  _$insertNode(_el$52, _el$55);
1430
1808
  _$insertNode(_el$53, _$createTextNode(`> `));
1431
- _$insert(_el$55, () => t().balNoKey);
1809
+ _$insert(_el$55, () => t("balUnsupported"));
1432
1810
  _$effect((_p$) => {
1433
1811
  var _v$9 = pal().muted, _v$0 = {
1434
1812
  fg: pal().muted
@@ -1444,87 +1822,98 @@ function TokenCachePanel(props) {
1444
1822
  }
1445
1823
  }), _$createComponent(Show, {
1446
1824
  get when() {
1447
- return balanceState().status === "loading";
1825
+ return !balanceUnsupported();
1448
1826
  },
1449
1827
  get children() {
1450
- var _el$56 = _$createElement("text"), _el$57 = _$createElement("span"), _el$59 = _$createElement("span");
1451
- _$insertNode(_el$56, _el$57);
1452
- _$insertNode(_el$56, _el$59);
1453
- _$insertNode(_el$57, _$createTextNode(`> `));
1454
- _$insert(_el$59, () => t().balLoading);
1455
- _$effect((_p$) => {
1456
- var _v$1 = pal().muted, _v$10 = {
1457
- fg: pal().muted
1458
- };
1459
- _v$1 !== _p$.e && (_p$.e = _$setProp(_el$56, "fg", _v$1, _p$.e));
1460
- _v$10 !== _p$.t && (_p$.t = _$setProp(_el$57, "style", _v$10, _p$.t));
1461
- return _p$;
1462
- }, {
1463
- e: void 0,
1464
- t: void 0
1465
- });
1466
- return _el$56;
1467
- }
1468
- }), _$createComponent(Show, {
1469
- get when() {
1470
- return balanceState().status === "error";
1471
- },
1472
- get children() {
1473
- var _el$60 = _$createElement("text"), _el$61 = _$createElement("span"), _el$63 = _$createElement("span");
1474
- _$insertNode(_el$60, _el$61);
1475
- _$insertNode(_el$60, _el$63);
1476
- _$insertNode(_el$61, _$createTextNode(`> `));
1477
- _$insert(_el$63, () => {
1478
- const code = balanceState().error;
1479
- if (code === "401") return t().balErr401;
1480
- if (code === "403") return t().balErr403;
1481
- if (code === "EMPTY") return t().balErrEmpty;
1482
- if (code === "TIMEOUT") return t().balErrTimeout;
1483
- return t().balError + (code ? ` (${code})` : "");
1484
- });
1485
- _$effect((_p$) => {
1486
- var _v$11 = pal().error, _v$12 = {
1487
- fg: pal().muted
1488
- };
1489
- _v$11 !== _p$.e && (_p$.e = _$setProp(_el$60, "fg", _v$11, _p$.e));
1490
- _v$12 !== _p$.t && (_p$.t = _$setProp(_el$61, "style", _v$12, _p$.t));
1491
- return _p$;
1492
- }, {
1493
- e: void 0,
1494
- t: void 0
1495
- });
1496
- return _el$60;
1497
- }
1498
- }), _$createComponent(Show, {
1499
- get when() {
1500
- return _$memo(() => balanceState().status === "ok")() && balanceState().data;
1501
- },
1502
- get children() {
1503
- return (() => {
1504
- const list = balanceState().data;
1505
- const pref = balanceCurrency();
1506
- const native = pref ? list.find((x) => x.currency === pref) : void 0;
1507
- if (native) {
1508
- return (() => {
1509
- var _el$91 = _$createElement("text");
1510
- _$insert(_el$91, () => justify(t().balTotal, balanceSymbol(native.currency) + native.total));
1511
- _$effect((_$p) => _$setProp(_el$91, "fg", pal().text, _$p));
1512
- return _el$91;
1513
- })();
1828
+ return [_$createComponent(Show, {
1829
+ get when() {
1830
+ return balanceState().status === "idle";
1831
+ },
1832
+ get children() {
1833
+ var _el$56 = _$createElement("text"), _el$57 = _$createElement("span"), _el$59 = _$createElement("span");
1834
+ _$insertNode(_el$56, _el$57);
1835
+ _$insertNode(_el$56, _el$59);
1836
+ _$insertNode(_el$57, _$createTextNode(`> `));
1837
+ _$insert(_el$59, () => t("balNoKey", {
1838
+ p: providerName()
1839
+ }));
1840
+ _$effect((_p$) => {
1841
+ var _v$1 = pal().muted, _v$10 = {
1842
+ fg: pal().muted
1843
+ };
1844
+ _v$1 !== _p$.e && (_p$.e = _$setProp(_el$56, "fg", _v$1, _p$.e));
1845
+ _v$10 !== _p$.t && (_p$.t = _$setProp(_el$57, "style", _v$10, _p$.t));
1846
+ return _p$;
1847
+ }, {
1848
+ e: void 0,
1849
+ t: void 0
1850
+ });
1851
+ return _el$56;
1852
+ }
1853
+ }), _$createComponent(Show, {
1854
+ get when() {
1855
+ return balanceState().status === "loading";
1856
+ },
1857
+ get children() {
1858
+ var _el$60 = _$createElement("text"), _el$61 = _$createElement("span"), _el$63 = _$createElement("span");
1859
+ _$insertNode(_el$60, _el$61);
1860
+ _$insertNode(_el$60, _el$63);
1861
+ _$insertNode(_el$61, _$createTextNode(`> `));
1862
+ _$insert(_el$63, () => t("balLoading"));
1863
+ _$effect((_p$) => {
1864
+ var _v$11 = pal().muted, _v$12 = {
1865
+ fg: pal().muted
1866
+ };
1867
+ _v$11 !== _p$.e && (_p$.e = _$setProp(_el$60, "fg", _v$11, _p$.e));
1868
+ _v$12 !== _p$.t && (_p$.t = _$setProp(_el$61, "style", _v$12, _p$.t));
1869
+ return _p$;
1870
+ }, {
1871
+ e: void 0,
1872
+ t: void 0
1873
+ });
1874
+ return _el$60;
1514
1875
  }
1515
- const base = list[0];
1516
- const baseAmt = parseFloat(base.total);
1517
- const converted = Number.isFinite(baseAmt) ? convertBalance(pref || base.currency, exchangeRate(), baseAmt, base.currency) : baseAmt;
1518
- const shown = pref && base.currency !== pref ? converted.toLocaleString("en-US", {
1519
- maximumFractionDigits: 2
1520
- }) : base.total;
1521
- return (() => {
1522
- var _el$92 = _$createElement("text");
1523
- _$insert(_el$92, () => justify(t().balTotal, balanceSymbol(pref || base.currency) + shown));
1524
- _$effect((_$p) => _$setProp(_el$92, "fg", pal().text, _$p));
1525
- return _el$92;
1526
- })();
1527
- })();
1876
+ }), _$createComponent(Show, {
1877
+ get when() {
1878
+ return balanceState().status === "error";
1879
+ },
1880
+ get children() {
1881
+ var _el$64 = _$createElement("text"), _el$65 = _$createElement("span"), _el$67 = _$createElement("span");
1882
+ _$insertNode(_el$64, _el$65);
1883
+ _$insertNode(_el$64, _el$67);
1884
+ _$insertNode(_el$65, _$createTextNode(`> `));
1885
+ _$insert(_el$67, () => {
1886
+ const code = balanceState().error;
1887
+ if (code === "401") return t("balErr401");
1888
+ if (code === "403") return t("balErr403");
1889
+ if (code === "EMPTY") return t("balErrEmpty");
1890
+ if (code === "TIMEOUT") return t("balErrTimeout");
1891
+ return t("balError") + (code ? ` (${code})` : "");
1892
+ });
1893
+ _$effect((_p$) => {
1894
+ var _v$13 = pal().error, _v$14 = {
1895
+ fg: pal().muted
1896
+ };
1897
+ _v$13 !== _p$.e && (_p$.e = _$setProp(_el$64, "fg", _v$13, _p$.e));
1898
+ _v$14 !== _p$.t && (_p$.t = _$setProp(_el$65, "style", _v$14, _p$.t));
1899
+ return _p$;
1900
+ }, {
1901
+ e: void 0,
1902
+ t: void 0
1903
+ });
1904
+ return _el$64;
1905
+ }
1906
+ }), _$createComponent(Show, {
1907
+ get when() {
1908
+ return _$memo(() => balanceState().status === "ok")() && balanceState().data;
1909
+ },
1910
+ get children() {
1911
+ var _el$68 = _$createElement("text");
1912
+ _$insert(_el$68, () => justify(t("balTotal"), formatBalanceText(balanceState().data, balanceCurrency(), exchangeRate())));
1913
+ _$effect((_$p) => _$setProp(_el$68, "fg", pal().text, _$p));
1914
+ return _el$68;
1915
+ }
1916
+ })];
1528
1917
  }
1529
1918
  })];
1530
1919
  }
@@ -1534,13 +1923,13 @@ function TokenCachePanel(props) {
1534
1923
  }
1535
1924
  }), null);
1536
1925
  _$effect((_p$) => {
1537
- var _v$13 = {
1926
+ var _v$15 = {
1538
1927
  fg: pal().muted
1539
- }, _v$14 = {
1928
+ }, _v$16 = {
1540
1929
  fg: pal().primary
1541
1930
  };
1542
- _v$13 !== _p$.e && (_p$.e = _$setProp(_el$3, "style", _v$13, _p$.e));
1543
- _v$14 !== _p$.t && (_p$.t = _$setProp(_el$4, "style", _v$14, _p$.t));
1931
+ _v$15 !== _p$.e && (_p$.e = _$setProp(_el$3, "style", _v$15, _p$.e));
1932
+ _v$16 !== _p$.t && (_p$.t = _$setProp(_el$4, "style", _v$16, _p$.t));
1544
1933
  return _p$;
1545
1934
  }, {
1546
1935
  e: void 0,
@@ -1549,6 +1938,233 @@ function TokenCachePanel(props) {
1549
1938
  return _el$;
1550
1939
  })();
1551
1940
  }
1941
+ function BottomStatusBar(props) {
1942
+ const KV_PREFIX = "cache_panel";
1943
+ const t = createT(() => props.signals.langCode());
1944
+ const sid = props.sessionId;
1945
+ const stats = createMemo(() => {
1946
+ const id = sid;
1947
+ if (!id) return null;
1948
+ const msgs = props.api.state.session.messages(id);
1949
+ const session = typeof props.api.state.session.get === "function" ? props.api.state.session.get(id) : void 0;
1950
+ let input = session?.tokens?.input ?? 0;
1951
+ let read = session?.tokens?.cache?.read ?? 0;
1952
+ let write = session?.tokens?.cache?.write ?? 0;
1953
+ if (session?.tokens == null) {
1954
+ for (const m of msgs) {
1955
+ if (m.role !== "assistant") continue;
1956
+ const tk = m.tokens;
1957
+ if (!tk) continue;
1958
+ input += num(tk.input);
1959
+ read += num(tk.cache?.read);
1960
+ write += num(tk.cache?.write);
1961
+ }
1962
+ }
1963
+ let hitRate = -1, prevHitRate = -1;
1964
+ for (let i = msgs.length - 1; i >= 0; i--) {
1965
+ const m = msgs[i];
1966
+ if (m.role !== "assistant") continue;
1967
+ const tk = m.tokens;
1968
+ if (!tk) continue;
1969
+ const mit = num(tk.input) + num(tk.cache?.read) + num(tk.cache?.write);
1970
+ const mrt = num(tk.cache?.read);
1971
+ if (mit <= 0) continue;
1972
+ const rate = mrt / mit * 100;
1973
+ if (hitRate < 0) {
1974
+ hitRate = rate;
1975
+ continue;
1976
+ }
1977
+ prevHitRate = rate;
1978
+ break;
1979
+ }
1980
+ return {
1981
+ hitRate,
1982
+ prevHitRate,
1983
+ input,
1984
+ read,
1985
+ write
1986
+ };
1987
+ });
1988
+ createEffect(() => {
1989
+ if (!props.signals.autoBalance()) return;
1990
+ const id = sid;
1991
+ if (!id) return;
1992
+ const msgs = props.api.state.session.messages(id);
1993
+ let pid = "";
1994
+ for (let i = msgs.length - 1; i >= 0; i--) {
1995
+ const m = msgs[i];
1996
+ if (m.role === "assistant" && m.providerID) {
1997
+ pid = m.providerID;
1998
+ break;
1999
+ }
2000
+ }
2001
+ if (!pid) {
2002
+ try {
2003
+ pid = props.api.state.session.get(id)?.model?.providerID ?? "";
2004
+ } catch {
2005
+ }
2006
+ }
2007
+ if (!pid) return;
2008
+ const hit = matchBalanceProvider(pid);
2009
+ if (hit) {
2010
+ props.signals.setBalanceUnsupported(false);
2011
+ if (hit.id !== props.signals.balanceProviderId()) {
2012
+ props.signals.setBalanceProviderId(hit.id);
2013
+ props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
2014
+ }
2015
+ } else {
2016
+ props.signals.setBalanceUnsupported(true);
2017
+ }
2018
+ });
2019
+ const pal = createMemo(() => {
2020
+ const th = props.api.theme.current;
2021
+ const sat = (k, fb) => desaturateTo(th[k], MAX_SAT, fb);
2022
+ return {
2023
+ text: sat("text", FALLBACK.text),
2024
+ muted: sat("textMuted", FALLBACK.muted),
2025
+ success: sat("success", FALLBACK.success),
2026
+ warning: sat("warning", FALLBACK.warning),
2027
+ error: sat("error", FALLBACK.error)
2028
+ };
2029
+ });
2030
+ const hitColor = createMemo(() => {
2031
+ const r = stats()?.hitRate ?? -1;
2032
+ if (r >= 85) return pal().success;
2033
+ if (r >= 70) return pal().warning;
2034
+ return pal().error;
2035
+ });
2036
+ const trend = createMemo(() => {
2037
+ const s = stats();
2038
+ if (!s || s.prevHitRate < 0 || s.hitRate < 0) return null;
2039
+ const d = s.hitRate - s.prevHitRate;
2040
+ return Math.abs(d) < 0.05 ? null : d;
2041
+ });
2042
+ const balanceText = createMemo(() => {
2043
+ const s = props.signals.balanceState();
2044
+ if (s.status === "ok" && s.data) return formatBalanceText(s.data, props.signals.balanceCurrency(), props.signals.exchangeRate());
2045
+ if (s.status === "loading") return "\u2026";
2046
+ if (s.status === "error") return "\u26A0";
2047
+ return "-";
2048
+ });
2049
+ const directory = createMemo(() => {
2050
+ try {
2051
+ return props.api.state.path.directory;
2052
+ } catch {
2053
+ return "";
2054
+ }
2055
+ });
2056
+ onMount(() => {
2057
+ try {
2058
+ const v = props.api.kv.get(`${KV_PREFIX}.section.bottom`, true);
2059
+ props.signals.setSectionBottom(v !== false);
2060
+ } catch {
2061
+ }
2062
+ });
2063
+ return _$createComponent(Show, {
2064
+ get when() {
2065
+ return props.signals.sectionBottom();
2066
+ },
2067
+ get fallback() {
2068
+ return (() => {
2069
+ var _el$110 = _$createElement("text");
2070
+ _$insert(_el$110, directory);
2071
+ _$effect((_$p) => _$setProp(_el$110, "fg", pal().muted, _$p));
2072
+ return _el$110;
2073
+ })();
2074
+ },
2075
+ get children() {
2076
+ var _el$96 = _$createElement("box"), _el$97 = _$createElement("text"), _el$98 = _$createElement("box"), _el$99 = _$createElement("text"), _el$100 = _$createElement("span"), _el$101 = _$createTextNode(` `), _el$102 = _$createElement("span"), _el$104 = _$createElement("span"), _el$105 = _$createElement("span"), _el$108 = _$createElement("text");
2077
+ _$insertNode(_el$96, _el$97);
2078
+ _$insertNode(_el$96, _el$98);
2079
+ _$setProp(_el$96, "marginLeft", 1);
2080
+ _$setProp(_el$96, "flexGrow", 1);
2081
+ _$setProp(_el$96, "flexShrink", 0);
2082
+ _$setProp(_el$96, "flexDirection", "row");
2083
+ _$setProp(_el$96, "justifyContent", "space-between");
2084
+ _$insert(_el$97, directory);
2085
+ _$insertNode(_el$98, _el$99);
2086
+ _$insertNode(_el$98, _el$108);
2087
+ _$setProp(_el$98, "flexDirection", "row");
2088
+ _$insertNode(_el$99, _el$100);
2089
+ _$insertNode(_el$99, _el$102);
2090
+ _$insertNode(_el$99, _el$104);
2091
+ _$insertNode(_el$99, _el$105);
2092
+ _$insertNode(_el$100, _el$101);
2093
+ _$insert(_el$100, () => t("barHit"), _el$101);
2094
+ _$insert(_el$102, (() => {
2095
+ var _c$ = _$memo(() => (stats()?.hitRate ?? -1) >= 0);
2096
+ return () => _c$() ? (Math.floor(stats().hitRate * 10) / 10).toFixed(1) + "%" : "--";
2097
+ })());
2098
+ _$insert(_el$99, _$createComponent(Show, {
2099
+ get when() {
2100
+ return trend() !== null;
2101
+ },
2102
+ get children() {
2103
+ var _el$103 = _$createElement("span");
2104
+ _$insert(_el$103, () => " " + (trend() > 0 ? "\u2191" : "\u2193") + Math.abs(trend()).toFixed(1) + "%");
2105
+ _$effect((_$p) => _$setProp(_el$103, "style", {
2106
+ fg: trend() > 0 ? pal().success : pal().error
2107
+ }, _$p));
2108
+ return _el$103;
2109
+ }
2110
+ }), _el$104);
2111
+ _$insert(_el$104, () => " \xB7 " + t("barTok") + " ");
2112
+ _$insert(_el$105, (() => {
2113
+ var _c$2 = _$memo(() => !!stats());
2114
+ return () => _c$2() ? fmtCompact(stats().input + stats().read + stats().write) : "--";
2115
+ })());
2116
+ _$insert(_el$99, _$createComponent(Show, {
2117
+ get when() {
2118
+ return !props.signals.balanceUnsupported();
2119
+ },
2120
+ get children() {
2121
+ return [(() => {
2122
+ var _el$106 = _$createElement("span");
2123
+ _$insert(_el$106, () => " \xB7 " + t("barBal") + " ");
2124
+ _$effect((_$p) => _$setProp(_el$106, "style", {
2125
+ fg: pal().muted
2126
+ }, _$p));
2127
+ return _el$106;
2128
+ })(), (() => {
2129
+ var _el$107 = _$createElement("span");
2130
+ _$insert(_el$107, balanceText);
2131
+ _$effect((_$p) => _$setProp(_el$107, "style", {
2132
+ fg: pal().text
2133
+ }, _$p));
2134
+ return _el$107;
2135
+ })()];
2136
+ }
2137
+ }), null);
2138
+ _$insertNode(_el$108, _$createTextNode(` \xB7 `));
2139
+ _$effect((_p$) => {
2140
+ var _v$31 = pal().muted, _v$32 = {
2141
+ fg: pal().muted
2142
+ }, _v$33 = {
2143
+ fg: hitColor()
2144
+ }, _v$34 = {
2145
+ fg: pal().muted
2146
+ }, _v$35 = {
2147
+ fg: pal().text
2148
+ }, _v$36 = pal().muted;
2149
+ _v$31 !== _p$.e && (_p$.e = _$setProp(_el$97, "fg", _v$31, _p$.e));
2150
+ _v$32 !== _p$.t && (_p$.t = _$setProp(_el$100, "style", _v$32, _p$.t));
2151
+ _v$33 !== _p$.a && (_p$.a = _$setProp(_el$102, "style", _v$33, _p$.a));
2152
+ _v$34 !== _p$.o && (_p$.o = _$setProp(_el$104, "style", _v$34, _p$.o));
2153
+ _v$35 !== _p$.i && (_p$.i = _$setProp(_el$105, "style", _v$35, _p$.i));
2154
+ _v$36 !== _p$.n && (_p$.n = _$setProp(_el$108, "fg", _v$36, _p$.n));
2155
+ return _p$;
2156
+ }, {
2157
+ e: void 0,
2158
+ t: void 0,
2159
+ a: void 0,
2160
+ o: void 0,
2161
+ i: void 0,
2162
+ n: void 0
2163
+ });
2164
+ return _el$96;
2165
+ }
2166
+ });
2167
+ }
1552
2168
  function createSidebarSlot(api, signals) {
1553
2169
  let lastSlotSid = "";
1554
2170
  return {
@@ -1584,18 +2200,28 @@ var tui = async (api) => {
1584
2200
  const [sectionDist, setSectionDist] = createSignal(true);
1585
2201
  const [sectionSkills, setSectionSkills] = createSignal(true);
1586
2202
  const [sectionBalance, setSectionBalance] = createSignal(true);
2203
+ const [sectionBottom, setSectionBottom] = createSignal(true);
1587
2204
  const [balanceRefresh, setBalanceRefresh] = createSignal(0);
2205
+ const [balanceProviderId, setBalanceProviderId] = createSignal("deepseek");
2206
+ const [autoBalance, setAutoBalance] = createSignal(true);
2207
+ const [balanceUnsupported, setBalanceUnsupported] = createSignal(false);
1588
2208
  const [balanceCurrency, setBalanceCurrency] = createSignal("");
1589
2209
  const [borderVisible, setBorderVisible] = createSignal(true);
1590
- const [langZH, setLangZH] = createSignal(LANG_ZH);
2210
+ const [langCode, setLangCode] = createSignal(INIT_LANG);
1591
2211
  const [overrideSessionId, setOverrideSessionId] = createSignal(void 0);
2212
+ const [balanceState, setBalanceState] = createSignal({
2213
+ status: "idle",
2214
+ data: null,
2215
+ lastFetch: 0
2216
+ });
2217
+ let balanceSeq = 0;
1592
2218
  const signals = {
1593
2219
  currencySymbol,
1594
2220
  setCurrencySymbol,
1595
2221
  exchangeRate,
1596
2222
  setExchangeRate,
1597
- langZH,
1598
- setLangZH,
2223
+ langCode,
2224
+ setLangCode,
1599
2225
  sectionDetail,
1600
2226
  setSectionDetail,
1601
2227
  sectionModel,
@@ -1606,8 +2232,17 @@ var tui = async (api) => {
1606
2232
  setSectionSkills,
1607
2233
  sectionBalance,
1608
2234
  setSectionBalance,
2235
+ sectionBottom,
2236
+ setSectionBottom,
1609
2237
  balanceRefresh,
1610
2238
  setBalanceRefresh,
2239
+ balanceProviderId,
2240
+ setBalanceProviderId,
2241
+ autoBalance,
2242
+ setAutoBalance,
2243
+ balanceUnsupported,
2244
+ setBalanceUnsupported,
2245
+ balanceState,
1611
2246
  balanceCurrency,
1612
2247
  setBalanceCurrency,
1613
2248
  borderVisible,
@@ -1616,7 +2251,182 @@ var tui = async (api) => {
1616
2251
  setOverrideSessionId
1617
2252
  };
1618
2253
  api.slots.register(createSidebarSlot(api, signals));
2254
+ api.slots.register({
2255
+ order: 55,
2256
+ slots: {
2257
+ session_prompt(_ctx, input) {
2258
+ return _$createComponent(api.ui.Prompt, {
2259
+ get sessionID() {
2260
+ return input.session_id;
2261
+ },
2262
+ get visible() {
2263
+ return input.visible;
2264
+ },
2265
+ get disabled() {
2266
+ return input.disabled;
2267
+ },
2268
+ get onSubmit() {
2269
+ return input.on_submit;
2270
+ },
2271
+ ref(r$) {
2272
+ var _ref$2 = input.ref;
2273
+ typeof _ref$2 === "function" ? _ref$2(r$) : input.ref = r$;
2274
+ },
2275
+ get hint() {
2276
+ return _$createComponent(BottomStatusBar, {
2277
+ api,
2278
+ signals,
2279
+ get sessionId() {
2280
+ return input.session_id;
2281
+ }
2282
+ });
2283
+ }
2284
+ });
2285
+ }
2286
+ }
2287
+ });
1619
2288
  const KV_PREFIX = "cache_panel";
2289
+ const restoreLang = () => {
2290
+ try {
2291
+ const saved = api.kv.get(`${KV_PREFIX}.lang`);
2292
+ if (saved && LANG_META.some((m) => m.code === saved)) setLangCode(saved);
2293
+ } catch {
2294
+ }
2295
+ };
2296
+ if (api.kv.ready) {
2297
+ restoreLang();
2298
+ } else {
2299
+ const langTimer = setInterval(() => {
2300
+ if (api.kv.ready) {
2301
+ clearInterval(langTimer);
2302
+ restoreLang();
2303
+ }
2304
+ }, 10);
2305
+ api.lifecycle.onDispose(() => clearInterval(langTimer));
2306
+ }
2307
+ const pollBalance = async () => {
2308
+ const provider = getBalanceProvider(balanceProviderId());
2309
+ const key = api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "") || findOpencodeKey(api, provider);
2310
+ if (balanceUnsupported()) {
2311
+ setBalanceState({
2312
+ status: "idle",
2313
+ data: null,
2314
+ lastFetch: 0,
2315
+ error: void 0,
2316
+ key: void 0
2317
+ });
2318
+ return;
2319
+ }
2320
+ if (!key) {
2321
+ setBalanceState({
2322
+ status: "idle",
2323
+ data: null,
2324
+ lastFetch: 0,
2325
+ error: void 0,
2326
+ key: void 0
2327
+ });
2328
+ return;
2329
+ }
2330
+ const now = Date.now();
2331
+ const prev = balanceState();
2332
+ if (prev.status === "ok" && prev.key === key && now - prev.lastFetch < BALANCE_POLL_MS) return;
2333
+ const seq = ++balanceSeq;
2334
+ setBalanceState({
2335
+ ...prev,
2336
+ status: "loading",
2337
+ error: void 0,
2338
+ key
2339
+ });
2340
+ const controller = new AbortController();
2341
+ let timedOut = false;
2342
+ const timer = setTimeout(() => {
2343
+ timedOut = true;
2344
+ controller.abort();
2345
+ }, 1e4);
2346
+ try {
2347
+ const data = await provider.fetchBalance(key, controller.signal);
2348
+ clearTimeout(timer);
2349
+ if (seq !== balanceSeq) return;
2350
+ setBalanceState({
2351
+ status: "ok",
2352
+ data,
2353
+ lastFetch: Date.now(),
2354
+ error: void 0,
2355
+ key
2356
+ });
2357
+ } catch (err) {
2358
+ clearTimeout(timer);
2359
+ if (seq !== balanceSeq) return;
2360
+ const code = timedOut ? "TIMEOUT" : err instanceof Error ? err.message : "";
2361
+ setBalanceState({
2362
+ status: "error",
2363
+ data: null,
2364
+ lastFetch: 0,
2365
+ error: code,
2366
+ key
2367
+ });
2368
+ }
2369
+ };
2370
+ createEffect(() => {
2371
+ void balanceRefresh();
2372
+ untrack(() => {
2373
+ void pollBalance();
2374
+ });
2375
+ });
2376
+ const balanceTimer = setInterval(pollBalance, BALANCE_POLL_MS);
2377
+ api.lifecycle.onDispose(() => clearInterval(balanceTimer));
2378
+ const providerOptionTitle = (p, current) => {
2379
+ const t = createT(() => langCode());
2380
+ const hasManual = !!api.kv.get(`${KV_PREFIX}.balance.${p.id}.key`, "");
2381
+ const hasAuto = !hasManual && !!findOpencodeKey(api, p);
2382
+ const mark = hasManual ? t("keyUser") : hasAuto ? t("keyOpenCode") : t("keyNotSet");
2383
+ return p.name + mark + (current && p.id === current ? " *" : "");
2384
+ };
2385
+ const promptBalanceKey = (dialog, provider) => {
2386
+ const t = createT(() => langCode());
2387
+ const current = api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
2388
+ const masked = maskKey(current);
2389
+ dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
2390
+ get title() {
2391
+ return provider.name;
2392
+ },
2393
+ description: () => (() => {
2394
+ var _el$111 = _$createElement("text");
2395
+ _$insert(_el$111, () => t("balKeyPrompt", {
2396
+ p: provider.name
2397
+ }));
2398
+ return _el$111;
2399
+ })(),
2400
+ get placeholder() {
2401
+ return provider.keyPlaceholder ?? "sk-...";
2402
+ },
2403
+ value: masked,
2404
+ onConfirm: (val) => {
2405
+ const input = val.trim();
2406
+ let key;
2407
+ if (input === "") {
2408
+ key = "";
2409
+ } else if (input.includes("*")) {
2410
+ key = current;
2411
+ } else {
2412
+ key = input;
2413
+ }
2414
+ api.kv.set(`${KV_PREFIX}.balance.${provider.id}.key`, key);
2415
+ setBalanceRefresh((v) => v + 1);
2416
+ if (key) {
2417
+ api.ui.toast({
2418
+ message: t("keySaved")
2419
+ });
2420
+ } else {
2421
+ api.ui.toast({
2422
+ message: t("keyCleared")
2423
+ });
2424
+ }
2425
+ dialog?.clear();
2426
+ },
2427
+ onCancel: () => dialog?.clear()
2428
+ }));
2429
+ };
1620
2430
  api.command?.register(() => [{
1621
2431
  title: "Cache: Set Currency",
1622
2432
  value: "cache.currency",
@@ -1634,6 +2444,7 @@ var tui = async (api) => {
1634
2444
  }));
1635
2445
  },
1636
2446
  onSelect: (opt) => {
2447
+ const t = createT(() => langCode());
1637
2448
  const sym = CURRENCIES[opt.value] ?? "$";
1638
2449
  const defRate = DEFAULT_RATES[opt.value] ?? 1;
1639
2450
  api.kv.set(`${KV_PREFIX}.currency`, sym);
@@ -1643,7 +2454,11 @@ var tui = async (api) => {
1643
2454
  signals.setCurrencySymbol(sym);
1644
2455
  signals.setExchangeRate(defRate);
1645
2456
  api.ui.toast({
1646
- message: `Currency: ${opt.value} (${sym}), rate: ${defRate}`
2457
+ message: t("currencySet", {
2458
+ v: opt.value,
2459
+ s: sym,
2460
+ r: defRate
2461
+ })
1647
2462
  });
1648
2463
  dialog?.clear();
1649
2464
  }
@@ -1660,21 +2475,24 @@ var tui = async (api) => {
1660
2475
  dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
1661
2476
  title: "Exchange Rate",
1662
2477
  description: () => (() => {
1663
- var _el$93 = _$createElement("text");
1664
- _$insertNode(_el$93, _$createTextNode(`Enter the exchange rate from USD to your currency (e.g. 7.2 for CNY)`));
1665
- return _el$93;
2478
+ var _el$112 = _$createElement("text");
2479
+ _$insertNode(_el$112, _$createTextNode(`Enter the exchange rate from USD to your currency (e.g. 7.2 for CNY)`));
2480
+ return _el$112;
1666
2481
  })(),
1667
2482
  placeholder: "1.0",
1668
2483
  get value() {
1669
2484
  return String(api.kv.get(`${KV_PREFIX}.rate`, 1));
1670
2485
  },
1671
2486
  onConfirm: (val) => {
2487
+ const t = createT(() => langCode());
1672
2488
  const n = parseFloat(val);
1673
2489
  if (n > 0) {
1674
2490
  api.kv.set(`${KV_PREFIX}.rate`, n);
1675
2491
  signals.setExchangeRate(n);
1676
2492
  api.ui.toast({
1677
- message: `Exchange rate set to ${n}`
2493
+ message: t("rateSet", {
2494
+ r: n
2495
+ })
1678
2496
  });
1679
2497
  }
1680
2498
  dialog?.clear();
@@ -1689,40 +2507,59 @@ var tui = async (api) => {
1689
2507
  name: "cache-section"
1690
2508
  },
1691
2509
  onSelect: (dialog) => {
2510
+ const t = createT(() => langCode());
1692
2511
  const detailOn = Boolean(api.kv.get(`${KV_PREFIX}.section.detail`, true));
1693
2512
  const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
1694
2513
  const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
1695
2514
  const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
1696
2515
  const balanceOn = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
2516
+ const bottomOn = Boolean(api.kv.get(`${KV_PREFIX}.section.bottom`, true));
1697
2517
  const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
2518
+ const labels = {
2519
+ detail: t("secDetail"),
2520
+ model: t("secModel"),
2521
+ dist: t("distTitle"),
2522
+ skills: t("secSkills"),
2523
+ balance: t("secBalance"),
2524
+ bottom: t("secBottom"),
2525
+ border: t("secBorder")
2526
+ };
2527
+ const optTitle = (label, on) => `${visualPadEnd(label, 15)}[${on ? "ON" : "OFF"}]`;
1698
2528
  dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1699
- title: "Toggle Section",
1700
- options: [{
1701
- title: `Token Detail [${detailOn ? "ON" : "OFF"}]`,
1702
- value: "detail"
1703
- }, {
1704
- title: `Model & Pricing [${modelOn ? "ON" : "OFF"}]`,
1705
- value: "model"
1706
- }, {
1707
- title: `Token Dist. [${distOn ? "ON" : "OFF"}]`,
1708
- value: "dist"
1709
- }, {
1710
- title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`,
1711
- value: "skills"
1712
- }, {
1713
- title: `DS Balance [${balanceOn ? "ON" : "OFF"}]`,
1714
- value: "balance"
1715
- }, {
1716
- title: `Panel Border [${borderOn ? "ON" : "OFF"}]`,
1717
- value: "border"
1718
- }],
2529
+ get title() {
2530
+ return t("secToggle");
2531
+ },
2532
+ get options() {
2533
+ return [{
2534
+ title: optTitle(labels.detail, detailOn),
2535
+ value: "detail"
2536
+ }, {
2537
+ title: optTitle(labels.model, modelOn),
2538
+ value: "model"
2539
+ }, {
2540
+ title: optTitle(labels.dist, distOn),
2541
+ value: "dist"
2542
+ }, {
2543
+ title: optTitle(labels.skills, skillsOn),
2544
+ value: "skills"
2545
+ }, {
2546
+ title: optTitle(labels.balance, balanceOn),
2547
+ value: "balance"
2548
+ }, {
2549
+ title: optTitle(labels.bottom, bottomOn),
2550
+ value: "bottom"
2551
+ }, {
2552
+ title: optTitle(labels.border, borderOn),
2553
+ value: "border"
2554
+ }];
2555
+ },
1719
2556
  onSelect: (opt) => {
1720
2557
  if (opt.value === "border") {
1721
2558
  const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
1722
2559
  api.kv.set(`${KV_PREFIX}.border`, !cur);
1723
2560
  signals.setBorderVisible(!cur);
1724
2561
  api.ui.toast({
1725
- message: `Panel border ${!cur ? "shown" : "hidden"}`
2562
+ message: !cur ? t("borderShown") : t("borderHidden")
1726
2563
  });
1727
2564
  } else {
1728
2565
  const key = `${KV_PREFIX}.section.${opt.value}`;
@@ -1733,8 +2570,12 @@ var tui = async (api) => {
1733
2570
  if (opt.value === "dist") signals.setSectionDist(!cur);
1734
2571
  if (opt.value === "skills") signals.setSectionSkills(!cur);
1735
2572
  if (opt.value === "balance") signals.setSectionBalance(!cur);
2573
+ if (opt.value === "bottom") signals.setSectionBottom(!cur);
2574
+ const name = labels[opt.value] ?? opt.value;
1736
2575
  api.ui.toast({
1737
- message: `${opt.value} section ${!cur ? "shown" : "hidden"}`
2576
+ message: t(!cur ? "sectionShown" : "sectionHidden", {
2577
+ s: name
2578
+ })
1738
2579
  });
1739
2580
  }
1740
2581
  dialog?.clear();
@@ -1749,6 +2590,7 @@ var tui = async (api) => {
1749
2590
  name: "cache-config"
1750
2591
  },
1751
2592
  onSelect: (dialog) => {
2593
+ const t = createT(() => langCode());
1752
2594
  const sym = api.kv.get(`${KV_PREFIX}.currency`) ?? "$";
1753
2595
  const rate = api.kv.get(`${KV_PREFIX}.rate`) ?? 1;
1754
2596
  const detail = Boolean(api.kv.get(`${KV_PREFIX}.section.detail`, true));
@@ -1756,9 +2598,20 @@ var tui = async (api) => {
1756
2598
  const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
1757
2599
  const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
1758
2600
  const balance = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
2601
+ const bottom = Boolean(api.kv.get(`${KV_PREFIX}.section.bottom`, true));
2602
+ const on = (v) => v ? "ON" : "OFF";
1759
2603
  api.ui.toast({
1760
- title: "Cache Panel Config",
1761
- message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"} | Balance: ${balance ? "ON" : "OFF"}`,
2604
+ title: t("panelConfigTitle"),
2605
+ message: t("panelConfigMsg", {
2606
+ c: sym,
2607
+ r: rate,
2608
+ d: on(detail),
2609
+ m: on(model),
2610
+ t: on(dist),
2611
+ k: on(skills),
2612
+ b: on(balance),
2613
+ f: on(bottom)
2614
+ }),
1762
2615
  duration: 8e3
1763
2616
  });
1764
2617
  dialog?.clear();
@@ -1771,78 +2624,114 @@ var tui = async (api) => {
1771
2624
  name: "cache-lang"
1772
2625
  },
1773
2626
  onSelect: (dialog) => {
1774
- const cur = langZH();
2627
+ const t = createT(() => langCode());
2628
+ const cur = langCode();
1775
2629
  dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1776
- title: "Display Language",
1777
- options: [{
1778
- title: `\u4E2D\u6587 ${cur ? "\u2713" : ""}`,
1779
- value: "zh"
1780
- }, {
1781
- title: `English ${cur ? "" : "\u2713"}`,
1782
- value: "en"
1783
- }],
2630
+ get title() {
2631
+ return t("langTitle");
2632
+ },
2633
+ get options() {
2634
+ return LANG_META.map((m) => ({
2635
+ title: `${visualPadEnd(m.label, 9)}${cur === m.code ? "\u2713" : ""}`,
2636
+ value: m.code
2637
+ }));
2638
+ },
1784
2639
  onSelect: (opt) => {
1785
- const zh = opt.value === "zh";
1786
- api.kv.set(`${KV_PREFIX}.lang`, opt.value);
1787
- setLangZH(zh);
2640
+ const code = opt.value;
2641
+ api.kv.set(`${KV_PREFIX}.lang`, code);
2642
+ setLangCode(code);
1788
2643
  api.ui.toast({
1789
- message: zh ? "\u8BED\u8A00\u5DF2\u5207\u6362\u4E3A\u4E2D\u6587" : "Switched to English"
2644
+ message: t("langSwitched")
1790
2645
  });
1791
2646
  dialog?.clear();
1792
2647
  }
1793
2648
  }));
1794
2649
  }
1795
2650
  }, {
1796
- title: "Cache: Set DeepSeek API Key",
1797
- value: "cache.balance.key",
1798
- description: "Set or update the DeepSeek API key for balance display",
2651
+ title: "Cache: Switch Balance Provider",
2652
+ value: "cache.balance",
2653
+ description: "\u5207\u6362\u4F59\u989D\u63D0\u4F9B\u5546 / \u81EA\u52A8\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u63D0\u4F9B\u5546 | Switch balance provider / auto-switch session provider",
1799
2654
  slash: {
1800
- name: "cache-balance-key"
2655
+ name: "cache-balance"
1801
2656
  },
1802
2657
  onSelect: (dialog) => {
1803
- const zh = langZH();
1804
- const current = api.kv.get(`${KV_PREFIX}.ds_key`, "");
1805
- const maskKey = (k) => {
1806
- if (!k) return "";
1807
- const prefix = k.startsWith("sk-") ? "sk-" : "";
1808
- const body = prefix ? k.slice(3) : k;
1809
- if (body.length <= 10) return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 5));
1810
- return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 10)) + body.slice(-5);
1811
- };
1812
- const masked = maskKey(current);
1813
- dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
1814
- title: zh ? "DeepSeek API Key" : "DeepSeek API Key",
1815
- description: () => (() => {
1816
- var _el$95 = _$createElement("text");
1817
- _$insert(_el$95, zh ? "\u8F93\u5165 DeepSeek API Key \u4EE5\u663E\u793A\u8D26\u6237\u4F59\u989D\uFF08\u7559\u7A7A\u6E05\u9664\uFF09" : "Enter your DeepSeek API key to show account balance (leave empty to clear)");
1818
- return _el$95;
1819
- })(),
1820
- placeholder: "sk-...",
1821
- value: masked,
1822
- onConfirm: (val) => {
1823
- const input = val.trim();
1824
- let key;
1825
- if (input === "") {
1826
- key = "";
1827
- } else if (input.includes("*")) {
1828
- key = current;
1829
- } else {
1830
- key = input;
1831
- }
1832
- api.kv.set(`${KV_PREFIX}.ds_key`, key);
1833
- setBalanceRefresh((v) => v + 1);
1834
- if (key) {
2658
+ const t = createT(() => langCode());
2659
+ const current = signals.balanceProviderId();
2660
+ const auto = signals.autoBalance();
2661
+ const autoLabel = `${t("autoSwitchOpt")} [${auto ? "ON" : "OFF"}]`;
2662
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
2663
+ get title() {
2664
+ return t("balProvTitle");
2665
+ },
2666
+ get options() {
2667
+ return [{
2668
+ title: autoLabel,
2669
+ value: "__auto__"
2670
+ }, ...balanceProviders.map((p) => ({
2671
+ title: providerOptionTitle(p, current),
2672
+ value: p.id
2673
+ }))];
2674
+ },
2675
+ onSelect: (opt) => {
2676
+ if (opt.value === "__auto__") {
2677
+ const next = !auto;
2678
+ api.kv.set(`${KV_PREFIX}.balance.auto`, next);
2679
+ signals.setAutoBalance(next);
1835
2680
  api.ui.toast({
1836
- message: zh ? "API Key \u5DF2\u4FDD\u5B58\uFF0C\u6B63\u5728\u67E5\u8BE2\u4F59\u989D..." : "API Key saved, fetching balance..."
2681
+ message: next ? t("autoSwitchOn") : t("autoSwitchOff")
1837
2682
  });
2683
+ dialog?.clear();
1838
2684
  } else {
1839
- api.ui.toast({
1840
- message: zh ? "API Key \u5DF2\u6E05\u9664" : "API Key cleared"
1841
- });
2685
+ const provider = getBalanceProvider(opt.value);
2686
+ api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
2687
+ api.kv.set(`${KV_PREFIX}.balance.auto`, false);
2688
+ signals.setBalanceProviderId(provider.id);
2689
+ signals.setAutoBalance(false);
2690
+ signals.setBalanceUnsupported(false);
2691
+ signals.setBalanceRefresh(signals.balanceRefresh() + 1);
2692
+ const hasKey = !!api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
2693
+ if (!hasKey) {
2694
+ promptBalanceKey(dialog, provider);
2695
+ } else {
2696
+ api.ui.toast({
2697
+ message: t("providerManual", {
2698
+ p: provider.name
2699
+ })
2700
+ });
2701
+ dialog?.clear();
2702
+ }
1842
2703
  }
1843
- dialog?.clear();
2704
+ }
2705
+ }));
2706
+ }
2707
+ }, {
2708
+ title: "Cache: Set Balance API Key",
2709
+ value: "cache.balance.key",
2710
+ description: "Select a provider and set its API key for balance display",
2711
+ slash: {
2712
+ name: "cache-balance-key"
2713
+ },
2714
+ onSelect: (dialog) => {
2715
+ const t = createT(() => langCode());
2716
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
2717
+ get title() {
2718
+ return t("balSelectTitle");
1844
2719
  },
1845
- onCancel: () => dialog?.clear()
2720
+ get options() {
2721
+ return balanceProviders.map((p) => ({
2722
+ title: providerOptionTitle(p),
2723
+ value: p.id
2724
+ }));
2725
+ },
2726
+ onSelect: (opt) => {
2727
+ const provider = getBalanceProvider(opt.value);
2728
+ api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
2729
+ api.kv.set(`${KV_PREFIX}.balance.auto`, false);
2730
+ signals.setBalanceProviderId(provider.id);
2731
+ signals.setAutoBalance(false);
2732
+ signals.setBalanceRefresh(signals.balanceRefresh() + 1);
2733
+ promptBalanceKey(dialog, provider);
2734
+ }
1846
2735
  }));
1847
2736
  }
1848
2737
  }, {
@@ -1853,10 +2742,11 @@ var tui = async (api) => {
1853
2742
  name: "cache-debug-skills"
1854
2743
  },
1855
2744
  onSelect: () => {
2745
+ const t = createT(() => langCode());
1856
2746
  const rt = api.route.current;
1857
2747
  if (rt.name !== "session" || !rt.params) {
1858
2748
  api.ui.toast({
1859
- message: "Please run this command inside a session",
2749
+ message: t("runInSession"),
1860
2750
  variant: "warning"
1861
2751
  });
1862
2752
  return;
@@ -1874,9 +2764,9 @@ var tui = async (api) => {
1874
2764
  }
1875
2765
  for (const p of parts) {
1876
2766
  if (p.type === "tool") {
1877
- const t = String(p.tool ?? "?");
1878
- byTool[t] = (byTool[t] ?? 0) + 1;
1879
- if (t === "skill") {
2767
+ const t2 = String(p.tool ?? "?");
2768
+ byTool[t2] = (byTool[t2] ?? 0) + 1;
2769
+ if (t2 === "skill") {
1880
2770
  const meta = p.state?.metadata;
1881
2771
  const rootMeta = p.metadata;
1882
2772
  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)}"`);
@@ -1945,7 +2835,7 @@ var tui = async (api) => {
1945
2835
  return true;
1946
2836
  });
1947
2837
  if (unique.length > 0) {
1948
- const zh = langZH();
2838
+ const t = createT(() => langCode());
1949
2839
  const currentSid = signals.overrideSessionId() ?? api.kv.get(`${KV_PREFIX}.session`, "");
1950
2840
  const options = unique.map((c, i) => ({
1951
2841
  title: `${i + 1}. ${c.title}`,
@@ -1953,7 +2843,7 @@ var tui = async (api) => {
1953
2843
  description: c.description
1954
2844
  }));
1955
2845
  const backValue = "__main__";
1956
- const backTitle = `\u2500 ${zh ? "\u56DE\u5230\u4E3B\u4F1A\u8BDD" : "Back to Main"}`;
2846
+ const backTitle = `\u2500 ${t("backToMainTitle")}`;
1957
2847
  options.unshift({
1958
2848
  title: backTitle,
1959
2849
  value: backValue,
@@ -1966,7 +2856,9 @@ var tui = async (api) => {
1966
2856
  });
1967
2857
  const currentIdx = currentSid ? options.findIndex((o) => o.value === currentSid) : -1;
1968
2858
  dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1969
- title: zh ? "\u9009\u62E9\u5B50\u4EE3\u7406" : "Select Sub-Agent",
2859
+ get title() {
2860
+ return t("subSelectTitle");
2861
+ },
1970
2862
  options,
1971
2863
  get current() {
1972
2864
  return currentIdx >= 0 ? options[currentIdx].value : void 0;
@@ -1976,28 +2868,30 @@ var tui = async (api) => {
1976
2868
  signals.setOverrideSessionId(void 0);
1977
2869
  api.kv.set(`${KV_PREFIX}.session`, "");
1978
2870
  api.ui.toast({
1979
- message: zh ? "\u5DF2\u5207\u56DE\u4E3B\u4F1A\u8BDD" : "Switched to main session"
2871
+ message: t("backToMain")
1980
2872
  });
1981
2873
  } else {
1982
2874
  signals.setOverrideSessionId(opt.value);
1983
2875
  api.kv.set(`${KV_PREFIX}.session`, opt.value);
1984
2876
  api.ui.toast({
1985
- message: (zh ? "\u5DF2\u5207\u6362\u81F3\u5B50\u4EE3\u7406: " : "Showing sub-agent: ") + opt.value.slice(0, 24) + "\u2026"
2877
+ message: t("subAgentSwitched", {
2878
+ s: opt.value.slice(0, 24) + "\u2026"
2879
+ })
1986
2880
  });
1987
2881
  }
1988
2882
  dialog?.clear();
1989
2883
  }
1990
2884
  }));
1991
2885
  } else {
1992
- const zh = langZH();
2886
+ const t = createT(() => langCode());
1993
2887
  dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
1994
2888
  get title() {
1995
- return signals.overrideSessionId() ? zh ? "\u5207\u6362\u5B50\u4EE3\u7406" : "Switch Sub" : zh ? "\u67E5\u770B\u5B50\u4EE3\u7406\u7F13\u5B58" : "View Sub Cache";
2889
+ return _$memo(() => !!signals.overrideSessionId())() ? t("subSwitchTitle") : t("subViewTitle");
1996
2890
  },
1997
2891
  description: () => (() => {
1998
- var _el$96 = _$createElement("text");
1999
- _$insert(_el$96, zh ? "\u672A\u627E\u5230\u5B50\u4EE3\u7406\uFF0C\u8BF7\u624B\u52A8\u7C98\u8D34 Session ID" : "No sub-agents found. Paste a Session ID manually");
2000
- return _el$96;
2892
+ var _el$114 = _$createElement("text");
2893
+ _$insert(_el$114, () => t("subNoFound"));
2894
+ return _el$114;
2001
2895
  })(),
2002
2896
  placeholder: "ses_...",
2003
2897
  get value() {
@@ -2009,7 +2903,9 @@ var tui = async (api) => {
2009
2903
  signals.setOverrideSessionId(sid);
2010
2904
  api.kv.set(`${KV_PREFIX}.session`, sid);
2011
2905
  api.ui.toast({
2012
- message: (langZH() ? "\u5DF2\u5207\u6362\u81F3\u5B50\u4EE3\u7406: " : "Showing sub-agent: ") + sid.slice(0, 24) + "\u2026"
2906
+ message: t("subAgentSwitched", {
2907
+ s: sid.slice(0, 24) + "\u2026"
2908
+ })
2013
2909
  });
2014
2910
  }
2015
2911
  dialog?.clear();
@@ -2026,10 +2922,11 @@ var tui = async (api) => {
2026
2922
  name: "cache-session-back"
2027
2923
  },
2028
2924
  onSelect: (dialog) => {
2925
+ const t = createT(() => langCode());
2029
2926
  signals.setOverrideSessionId(void 0);
2030
2927
  api.kv.set(`${KV_PREFIX}.session`, "");
2031
2928
  api.ui.toast({
2032
- message: langZH() ? "\u5DF2\u5207\u56DE\u4E3B\u4F1A\u8BDD" : "Switched to main session"
2929
+ message: t("backToMain")
2033
2930
  });
2034
2931
  dialog?.clear();
2035
2932
  }