opencode-visual-cache 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/README_EN.md +4 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +196 -4
- package/dist/tui.js +407 -96
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +253 -2
package/README.md
CHANGED
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
- **折叠记忆**:折叠状态持久化,重启后保持
|
|
51
51
|
- **语言适配**:自动检测系统语言,支持 `/cache-lang` 运行时切换中/英文,偏好持久化
|
|
52
52
|
- **多币种**:通过 `/cache-currency` 切换货币,费用和节省同步换算
|
|
53
|
-
- **斜杠命令**:`/cache-rate` `/cache-section` `/cache-config` `/cache-lang` 动态配置面板
|
|
53
|
+
- **斜杠命令**:`/cache-session` `/cache-session-back` `/cache-rate` `/cache-section` `/cache-config` `/cache-lang` 动态配置面板
|
|
54
|
+
- **子代理缓存查看**:`/cache-session` 自动扫描并列出子代理,选择一个即可切换面板显示其缓存统计,支持 `/cache-session-back` 返回主会话
|
|
54
55
|
- **已加载技能**:检测 session 中 LLM 调用 `skill` tool 的记录,展示已加载技能名及估算 Token 占用
|
|
55
56
|
|
|
56
57
|
---
|
|
@@ -100,6 +101,8 @@ npm install -g opencode-visual-cache@latest
|
|
|
100
101
|
|
|
101
102
|
| 命令 | 功能 | 使用方式 |
|
|
102
103
|
|------|------|---------|
|
|
104
|
+
| `/cache-session` | 查看子代理缓存统计 | 自动列出子代理供选择,或手动粘贴 Session ID 切换面板数据源 |
|
|
105
|
+
| `/cache-session-back` | 返回主会话统计 | 从子代理缓存视图切回主会话 |
|
|
103
106
|
| `/cache-currency` | 切换货币单位 | 从列表选择货币(USD / CNY / EUR / JPY / GBP / KRW),自动填入默认汇率 |
|
|
104
107
|
| `/cache-rate` | 调整汇率乘数 | 输入自定义汇率(如 `7.2`),用于费用换算 |
|
|
105
108
|
| `/cache-section` | 开关区块与边框 | 独立控制 Token 明细 / 模型与定价 / 估算 Token 分布 / 已加载技能 / 面板边框的显隐 |
|
package/README_EN.md
CHANGED
|
@@ -50,7 +50,8 @@ Interested in sub-agent monitoring? Check out [opencode-subagent-magazine](https
|
|
|
50
50
|
- **Persistent State**: Fold preferences and config remembered across restarts via api.kv
|
|
51
51
|
- **Language**: Auto-detects system locale, with `/cache-lang` for runtime switching between Chinese and English — preference persisted across restarts
|
|
52
52
|
- **Multi-currency**: Switch via `/cache-currency` — costs, savings, and per-million rates convert in real time
|
|
53
|
-
- **Slash Commands**: `/cache-rate` `/cache-section` `/cache-config` `/cache-lang` for live panel configuration
|
|
53
|
+
- **Slash Commands**: `/cache-session` `/cache-session-back` `/cache-rate` `/cache-section` `/cache-config` `/cache-lang` for live panel configuration
|
|
54
|
+
- **Sub-Agent Cache View**: `/cache-session` auto-scans and lists sub-agents; select one to switch the panel stats. Use `/cache-session-back` to return to the main session
|
|
54
55
|
- **Loaded Skills**: Detects `skill` tool calls in the session and displays loaded skill names with estimated token footprint
|
|
55
56
|
|
|
56
57
|
---
|
|
@@ -100,6 +101,8 @@ The plugin supports slash commands and command palette (`Ctrl + P`) for runtime
|
|
|
100
101
|
|
|
101
102
|
| Command | Function | How to use |
|
|
102
103
|
|---------|----------|------------|
|
|
104
|
+
| `/cache-session` | View sub-agent cache stats | Lists sub-agents automatically, or paste a Session ID to switch the panel data source |
|
|
105
|
+
| `/cache-session-back` | Return to main session | Switch back to main session from sub-agent cache view |
|
|
103
106
|
| `/cache-currency` | Switch currency | Pick from a list (USD / CNY / EUR / JPY / GBP / KRW); default exchange rate auto-filled |
|
|
104
107
|
| `/cache-rate` | Adjust exchange rate | Enter a custom rate (e.g. `7.2` for CNY) |
|
|
105
108
|
| `/cache-section` | Toggle sections & border | Independently show/hide Detail, Model & Pricing, Token Distribution, Loaded Skills, or the panel border |
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.4.0";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION = "1.
|
|
2
|
+
export const PLUGIN_VERSION = "1.4.0";
|
package/dist/index.js
CHANGED
|
@@ -94,6 +94,14 @@ const ZH_T = {
|
|
|
94
94
|
secDetail: "明细",
|
|
95
95
|
secModel: "模型",
|
|
96
96
|
secSkills: "已加载技能",
|
|
97
|
+
balTotal: "总余额:",
|
|
98
|
+
balNoKey: "未配置 API Key",
|
|
99
|
+
balLoading: "查询中...",
|
|
100
|
+
balError: "查询失败",
|
|
101
|
+
balErr401: "API Key 无效",
|
|
102
|
+
balErr403: "余额查询被拒绝",
|
|
103
|
+
balErrEmpty: "未获取到余额数据",
|
|
104
|
+
balErrTimeout: "查询超时",
|
|
97
105
|
};
|
|
98
106
|
const EN_T = {
|
|
99
107
|
title: "Token Cache",
|
|
@@ -125,6 +133,14 @@ const EN_T = {
|
|
|
125
133
|
secDetail: "Detail",
|
|
126
134
|
secModel: "Model",
|
|
127
135
|
secSkills: "Loaded Skills",
|
|
136
|
+
balTotal: "Total:",
|
|
137
|
+
balNoKey: "No API Key set",
|
|
138
|
+
balLoading: "Fetching...",
|
|
139
|
+
balError: "Fetch failed",
|
|
140
|
+
balErr401: "Invalid API Key",
|
|
141
|
+
balErr403: "Balance request rejected",
|
|
142
|
+
balErrEmpty: "No balance data",
|
|
143
|
+
balErrTimeout: "Request timed out",
|
|
128
144
|
};
|
|
129
145
|
// ── color helpers ────────────────────────────────────────────────
|
|
130
146
|
/** Extract { r, g, b } (0–255) from a hex string or RGBA-like object. */
|
|
@@ -299,6 +315,44 @@ function estimateTokens(text) {
|
|
|
299
315
|
const asciiPerToken = jsonLike ? 3.5 : codeLike ? 3.5 : 4;
|
|
300
316
|
return Math.max(1, Math.ceil(ascii / asciiPerToken + cjk / 1.0));
|
|
301
317
|
}
|
|
318
|
+
const BALANCE_POLL_MS = 5 * 60 * 1000; // 5 minutes
|
|
319
|
+
async function fetchDeepSeekBalance(apiKey, signal) {
|
|
320
|
+
const res = await fetch("https://api.deepseek.com/user/balance", {
|
|
321
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
322
|
+
signal,
|
|
323
|
+
});
|
|
324
|
+
if (!res.ok) {
|
|
325
|
+
if (res.status === 401)
|
|
326
|
+
throw new Error("401");
|
|
327
|
+
if (res.status === 402 || res.status === 403)
|
|
328
|
+
throw new Error("403");
|
|
329
|
+
throw new Error(String(res.status));
|
|
330
|
+
}
|
|
331
|
+
const json = await res.json();
|
|
332
|
+
const infos = json.balance_infos ?? [];
|
|
333
|
+
if (infos.length === 0)
|
|
334
|
+
throw new Error("EMPTY");
|
|
335
|
+
return infos.map((info) => ({
|
|
336
|
+
currency: info.currency ?? "CNY",
|
|
337
|
+
total: info.total_balance ?? "0",
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 将余额从来源币种换算为目标币种。
|
|
342
|
+
* DEFAULT_RATES 以 USD=1 为基准:先折算为 USD,再换算到目标币种。
|
|
343
|
+
*/
|
|
344
|
+
function convertBalance(target, targetRate, amount, from) {
|
|
345
|
+
if (from === target)
|
|
346
|
+
return amount;
|
|
347
|
+
const fromRate = DEFAULT_RATES[from] ?? 1;
|
|
348
|
+
const usd = from === "USD" ? amount : amount / fromRate;
|
|
349
|
+
return target === "USD" ? usd : usd * targetRate;
|
|
350
|
+
}
|
|
351
|
+
/** 货币符号:优先取 /cache-currency 内置映射,未知币种回退为代码。 */
|
|
352
|
+
function balanceSymbol(currency) {
|
|
353
|
+
const sym = CURRENCIES[currency];
|
|
354
|
+
return sym ?? currency + " ";
|
|
355
|
+
}
|
|
302
356
|
const CURRENCIES = {
|
|
303
357
|
USD: "$", CNY: "¥", EUR: "€", JPY: "JP¥", GBP: "£", KRW: "₩",
|
|
304
358
|
};
|
|
@@ -325,7 +379,7 @@ function TokenCachePanel(props) {
|
|
|
325
379
|
const [skillsOpen, setSkillsOpen] = createSignal(true);
|
|
326
380
|
let boxEl;
|
|
327
381
|
// ── shared signals (de-structured so internal code is unchanged) ──
|
|
328
|
-
const { currencySymbol, setCurrencySymbol, exchangeRate, setExchangeRate, langZH, setLangZH, sectionDetail, setSectionDetail, sectionModel, setSectionModel, sectionDist, setSectionDist, sectionSkills, setSectionSkills, borderVisible, setBorderVisible, } = props.signals;
|
|
382
|
+
const { currencySymbol, setCurrencySymbol, exchangeRate, setExchangeRate, langZH, setLangZH, sectionDetail, setSectionDetail, sectionModel, setSectionModel, sectionDist, setSectionDist, sectionSkills, setSectionSkills, sectionBalance, setSectionBalance, balanceRefresh, balanceCurrency, setBalanceCurrency, borderVisible, setBorderVisible, } = props.signals;
|
|
329
383
|
// ── reactive translation (follows langZH signal) ──
|
|
330
384
|
const t = createMemo(() => langZH() ? ZH_T : EN_T);
|
|
331
385
|
// ── scan session messages reactively ──
|
|
@@ -352,6 +406,52 @@ function TokenCachePanel(props) {
|
|
|
352
406
|
hasSkills: false,
|
|
353
407
|
});
|
|
354
408
|
const [refreshTick, setRefreshTick] = createSignal(0);
|
|
409
|
+
// ── balance state + polling ──────────────────────────────────
|
|
410
|
+
const [balanceState, setBalanceState] = createSignal({
|
|
411
|
+
status: "idle", data: null, lastFetch: 0,
|
|
412
|
+
});
|
|
413
|
+
// 请求序号:防止定时轮询与手动刷新并发时,慢的旧请求覆盖新结果
|
|
414
|
+
let balanceSeq = 0;
|
|
415
|
+
const pollBalance = async () => {
|
|
416
|
+
const key = props.api.kv.get(`${KV_PREFIX}.ds_key`, "");
|
|
417
|
+
if (!key) {
|
|
418
|
+
setBalanceState({ status: "idle", data: null, lastFetch: 0, error: undefined, key: undefined });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const now = Date.now();
|
|
422
|
+
const prev = balanceState();
|
|
423
|
+
// key 已更换(重新输入)→ 强制重新查询,绕过缓存
|
|
424
|
+
if (prev.status === "ok" && prev.key === key && now - prev.lastFetch < BALANCE_POLL_MS)
|
|
425
|
+
return; // cache still fresh
|
|
426
|
+
const seq = ++balanceSeq;
|
|
427
|
+
setBalanceState({ ...prev, status: "loading", error: undefined, key });
|
|
428
|
+
const controller = new AbortController();
|
|
429
|
+
let timedOut = false;
|
|
430
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, 10_000);
|
|
431
|
+
try {
|
|
432
|
+
const data = await fetchDeepSeekBalance(key, controller.signal);
|
|
433
|
+
clearTimeout(timer);
|
|
434
|
+
if (seq !== balanceSeq)
|
|
435
|
+
return; // 已被更新的请求取代,丢弃过期结果
|
|
436
|
+
setBalanceState({ status: "ok", data, lastFetch: Date.now(), error: undefined, key });
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
clearTimeout(timer);
|
|
440
|
+
if (seq !== balanceSeq)
|
|
441
|
+
return;
|
|
442
|
+
const code = timedOut ? "TIMEOUT" : (err instanceof Error ? err.message : "");
|
|
443
|
+
// 失败时清空旧数据,避免显示过期余额
|
|
444
|
+
setBalanceState({ status: "error", data: null, lastFetch: 0, error: code, key });
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
// Re-fetch when the API key is (re)configured via /cache-balance-key.
|
|
448
|
+
// 注意:pollBalance 内部读写 balanceState 信号,若不做 untrack 包裹,
|
|
449
|
+
// effect 会追踪 balanceState 的变化并与 pollBalance 的 setBalanceState
|
|
450
|
+
// 形成无限循环(每次重跑都发起新的 fetch 请求)。
|
|
451
|
+
createEffect(() => {
|
|
452
|
+
void balanceRefresh();
|
|
453
|
+
untrack(() => { void pollBalance(); });
|
|
454
|
+
});
|
|
355
455
|
// ── auto-clear override when the user navigates to a different main session ──
|
|
356
456
|
let lastMainSid = props.sessionId;
|
|
357
457
|
createEffect(() => {
|
|
@@ -602,10 +702,14 @@ function TokenCachePanel(props) {
|
|
|
602
702
|
setCurrencySymbol(sym);
|
|
603
703
|
if (typeof rate === "number" && rate > 0)
|
|
604
704
|
setExchangeRate(rate);
|
|
705
|
+
const balCur = props.api.kv.get(`${KV_PREFIX}.balance_currency`);
|
|
706
|
+
if (typeof balCur === "string")
|
|
707
|
+
setBalanceCurrency(balCur);
|
|
605
708
|
setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)));
|
|
606
709
|
setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)));
|
|
607
710
|
setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)));
|
|
608
711
|
setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX}.section.skills`, true)));
|
|
712
|
+
setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX}.section.balance`, true)));
|
|
609
713
|
const bv = props.api.kv.get(`${KV_PREFIX}.border`, true);
|
|
610
714
|
setBorderVisible(bv !== false);
|
|
611
715
|
// Restore language preference
|
|
@@ -663,7 +767,8 @@ function TokenCachePanel(props) {
|
|
|
663
767
|
const unsubMsg = props.api.event.on("message.updated", () => { bumpPartVersion(); setRefreshTick(v => v + 1); });
|
|
664
768
|
const unsubSession = props.api.event.on("session.updated", () => { setRefreshTick(v => v + 1); });
|
|
665
769
|
setRefreshTick(v => v + 1);
|
|
666
|
-
|
|
770
|
+
const balanceTimer = setInterval(pollBalance, BALANCE_POLL_MS);
|
|
771
|
+
onCleanup(() => { clearTimeout(partTimer); clearInterval(balanceTimer); unsubPart(); unsubMsg(); unsubSession(); });
|
|
667
772
|
});
|
|
668
773
|
// ── colours ──
|
|
669
774
|
// Pull from the current theme, auto-desaturate if too punchy,
|
|
@@ -732,7 +837,36 @@ function TokenCachePanel(props) {
|
|
|
732
837
|
const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
|
|
733
838
|
const label = truncateVisual(sk.name, maxLabel);
|
|
734
839
|
return (_jsx("text", { fg: pal().muted, children: justify(label, fmt(sk.tokens), t().tok) }));
|
|
735
|
-
}) })] }) })
|
|
840
|
+
}) })] }) }), _jsxs(Show, { when: sectionBalance(), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsx(Show, { when: balanceState().status === "idle", children: _jsxs("text", { fg: pal().muted, children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { children: t().balNoKey })] }) }), _jsx(Show, { when: balanceState().status === "loading", children: _jsxs("text", { fg: pal().muted, children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { children: t().balLoading })] }) }), _jsx(Show, { when: balanceState().status === "error", children: _jsxs("text", { fg: pal().error, children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { children: (() => {
|
|
841
|
+
const code = balanceState().error;
|
|
842
|
+
if (code === "401")
|
|
843
|
+
return t().balErr401;
|
|
844
|
+
if (code === "403")
|
|
845
|
+
return t().balErr403;
|
|
846
|
+
if (code === "EMPTY")
|
|
847
|
+
return t().balErrEmpty;
|
|
848
|
+
if (code === "TIMEOUT")
|
|
849
|
+
return t().balErrTimeout;
|
|
850
|
+
return t().balError + (code ? ` (${code})` : "");
|
|
851
|
+
})() })] }) }), _jsx(Show, { when: balanceState().status === "ok" && balanceState().data, children: (() => {
|
|
852
|
+
const list = balanceState().data;
|
|
853
|
+
const pref = balanceCurrency();
|
|
854
|
+
// 偏好币种是 DeepSeek 原生返回的(CNY/USD)→ 直接显示
|
|
855
|
+
const native = pref ? list.find(x => x.currency === pref) : undefined;
|
|
856
|
+
if (native) {
|
|
857
|
+
return (_jsx("text", { fg: pal().text, children: justify(t().balTotal, balanceSymbol(native.currency) + native.total) }));
|
|
858
|
+
}
|
|
859
|
+
// 非原生币种(EUR/JPY/GBP/KRW…)→ 取第一条余额按汇率换算
|
|
860
|
+
const base = list[0];
|
|
861
|
+
const baseAmt = parseFloat(base.total);
|
|
862
|
+
const converted = Number.isFinite(baseAmt)
|
|
863
|
+
? convertBalance(pref || base.currency, exchangeRate(), baseAmt, base.currency)
|
|
864
|
+
: baseAmt;
|
|
865
|
+
const shown = pref && base.currency !== pref
|
|
866
|
+
? converted.toLocaleString("en-US", { maximumFractionDigits: 2 })
|
|
867
|
+
: base.total;
|
|
868
|
+
return (_jsx("text", { fg: pal().text, children: justify(t().balTotal, balanceSymbol(pref || base.currency) + shown) }));
|
|
869
|
+
})() })] })] })] })] }));
|
|
736
870
|
}
|
|
737
871
|
// ---------------------------------------------------------------------------
|
|
738
872
|
// Plugin entry
|
|
@@ -764,6 +898,9 @@ const tui = async (api) => {
|
|
|
764
898
|
const [sectionModel, setSectionModel] = createSignal(true);
|
|
765
899
|
const [sectionDist, setSectionDist] = createSignal(true);
|
|
766
900
|
const [sectionSkills, setSectionSkills] = createSignal(true);
|
|
901
|
+
const [sectionBalance, setSectionBalance] = createSignal(true);
|
|
902
|
+
const [balanceRefresh, setBalanceRefresh] = createSignal(0);
|
|
903
|
+
const [balanceCurrency, setBalanceCurrency] = createSignal("");
|
|
767
904
|
const [borderVisible, setBorderVisible] = createSignal(true);
|
|
768
905
|
const [langZH, setLangZH] = createSignal(LANG_ZH);
|
|
769
906
|
const [overrideSessionId, setOverrideSessionId] = createSignal(undefined);
|
|
@@ -775,6 +912,9 @@ const tui = async (api) => {
|
|
|
775
912
|
sectionModel, setSectionModel,
|
|
776
913
|
sectionDist, setSectionDist,
|
|
777
914
|
sectionSkills, setSectionSkills,
|
|
915
|
+
sectionBalance, setSectionBalance,
|
|
916
|
+
balanceRefresh, setBalanceRefresh,
|
|
917
|
+
balanceCurrency, setBalanceCurrency,
|
|
778
918
|
borderVisible, setBorderVisible,
|
|
779
919
|
overrideSessionId, setOverrideSessionId,
|
|
780
920
|
};
|
|
@@ -796,6 +936,9 @@ const tui = async (api) => {
|
|
|
796
936
|
const defRate = DEFAULT_RATES[opt.value] ?? 1;
|
|
797
937
|
api.kv.set(`${KV_PREFIX}.currency`, sym);
|
|
798
938
|
api.kv.set(`${KV_PREFIX}.rate`, defRate);
|
|
939
|
+
// 同步余额显示币种偏好:CNY/USD 原生直显,其余币种按汇率换算
|
|
940
|
+
api.kv.set(`${KV_PREFIX}.balance_currency`, opt.value);
|
|
941
|
+
signals.setBalanceCurrency(opt.value);
|
|
799
942
|
signals.setCurrencySymbol(sym);
|
|
800
943
|
signals.setExchangeRate(defRate);
|
|
801
944
|
api.ui.toast({ message: `Currency: ${opt.value} (${sym}), rate: ${defRate}` });
|
|
@@ -830,12 +973,14 @@ const tui = async (api) => {
|
|
|
830
973
|
const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
|
|
831
974
|
const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
|
|
832
975
|
const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
|
|
976
|
+
const balanceOn = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
|
|
833
977
|
const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
|
|
834
978
|
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Toggle Section", options: [
|
|
835
979
|
{ title: `Token Detail [${detailOn ? "ON" : "OFF"}]`, value: "detail" },
|
|
836
980
|
{ title: `Model & Pricing [${modelOn ? "ON" : "OFF"}]`, value: "model" },
|
|
837
981
|
{ title: `Token Dist. [${distOn ? "ON" : "OFF"}]`, value: "dist" },
|
|
838
982
|
{ title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`, value: "skills" },
|
|
983
|
+
{ title: `DS Balance [${balanceOn ? "ON" : "OFF"}]`, value: "balance" },
|
|
839
984
|
{ title: `Panel Border [${borderOn ? "ON" : "OFF"}]`, value: "border" },
|
|
840
985
|
], onSelect: (opt) => {
|
|
841
986
|
if (opt.value === "border") {
|
|
@@ -856,6 +1001,8 @@ const tui = async (api) => {
|
|
|
856
1001
|
signals.setSectionDist(!cur);
|
|
857
1002
|
if (opt.value === "skills")
|
|
858
1003
|
signals.setSectionSkills(!cur);
|
|
1004
|
+
if (opt.value === "balance")
|
|
1005
|
+
signals.setSectionBalance(!cur);
|
|
859
1006
|
api.ui.toast({ message: `${opt.value} section ${!cur ? "shown" : "hidden"}` });
|
|
860
1007
|
}
|
|
861
1008
|
dialog?.clear();
|
|
@@ -874,9 +1021,10 @@ const tui = async (api) => {
|
|
|
874
1021
|
const model = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
|
|
875
1022
|
const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
|
|
876
1023
|
const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
|
|
1024
|
+
const balance = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
|
|
877
1025
|
api.ui.toast({
|
|
878
1026
|
title: "Cache Panel Config",
|
|
879
|
-
message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"}`,
|
|
1027
|
+
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"}`,
|
|
880
1028
|
duration: 8000,
|
|
881
1029
|
});
|
|
882
1030
|
dialog?.clear();
|
|
@@ -901,6 +1049,50 @@ const tui = async (api) => {
|
|
|
901
1049
|
} })));
|
|
902
1050
|
},
|
|
903
1051
|
},
|
|
1052
|
+
{
|
|
1053
|
+
title: "Cache: Set DeepSeek API Key",
|
|
1054
|
+
value: "cache.balance.key",
|
|
1055
|
+
description: "Set or update the DeepSeek API key for balance display",
|
|
1056
|
+
slash: { name: "cache-balance-key" },
|
|
1057
|
+
onSelect: (dialog) => {
|
|
1058
|
+
const zh = langZH();
|
|
1059
|
+
const current = api.kv.get(`${KV_PREFIX}.ds_key`, "");
|
|
1060
|
+
// 已保存的 key 以脱敏形式预填:保留 "sk-" 前缀 + 头 5 尾 5 字符,中间用 * 填充
|
|
1061
|
+
const maskKey = (k) => {
|
|
1062
|
+
if (!k)
|
|
1063
|
+
return "";
|
|
1064
|
+
const prefix = k.startsWith("sk-") ? "sk-" : "";
|
|
1065
|
+
const body = prefix ? k.slice(3) : k;
|
|
1066
|
+
if (body.length <= 10)
|
|
1067
|
+
return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 5));
|
|
1068
|
+
return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 10)) + body.slice(-5);
|
|
1069
|
+
};
|
|
1070
|
+
const masked = maskKey(current);
|
|
1071
|
+
dialog?.replace(() => (_jsx(api.ui.DialogPrompt, { title: zh ? "DeepSeek API Key" : "DeepSeek API Key", description: () => _jsx("text", { children: zh ? "输入 DeepSeek API Key 以显示账户余额(留空清除)" : "Enter your DeepSeek API key to show account balance (leave empty to clear)" }), placeholder: "sk-...", value: masked, onConfirm: (val) => {
|
|
1072
|
+
const input = val.trim();
|
|
1073
|
+
// 空 → 清除;含 * (脱敏占位符残留)→ 视为未修改,保留原 key;否则为新 key
|
|
1074
|
+
let key;
|
|
1075
|
+
if (input === "") {
|
|
1076
|
+
key = "";
|
|
1077
|
+
}
|
|
1078
|
+
else if (input.includes("*")) {
|
|
1079
|
+
key = current;
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
key = input;
|
|
1083
|
+
}
|
|
1084
|
+
api.kv.set(`${KV_PREFIX}.ds_key`, key);
|
|
1085
|
+
setBalanceRefresh(v => v + 1);
|
|
1086
|
+
if (key) {
|
|
1087
|
+
api.ui.toast({ message: zh ? "API Key 已保存,正在查询余额..." : "API Key saved, fetching balance..." });
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
api.ui.toast({ message: zh ? "API Key 已清除" : "API Key cleared" });
|
|
1091
|
+
}
|
|
1092
|
+
dialog?.clear();
|
|
1093
|
+
}, onCancel: () => dialog?.clear() })));
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
904
1096
|
{
|
|
905
1097
|
title: "Cache: Debug Skills Detection",
|
|
906
1098
|
value: "cache.debug-skills",
|