opencode-visual-cache 1.4.0 → 1.5.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 +28 -1
- package/README_EN.md +28 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/balance-providers.d.ts +27 -0
- package/dist/balance-providers.js +131 -0
- package/dist/index.js +200 -64
- package/dist/tui.js +296 -74
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/balance-providers.ts +153 -0
- package/src/index.tsx +222 -73
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/solid/jsx-runtime";
|
|
2
2
|
import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
|
|
3
3
|
import { PLUGIN_VERSION } from "./_version";
|
|
4
|
+
import { balanceProviders, getBalanceProvider, maskKey, matchBalanceProvider } from "./balance-providers";
|
|
4
5
|
// ── terminal-width helpers ────────────────────────────────────────
|
|
5
6
|
// CJK characters occupy 2 terminal columns; padEnd/padStart count
|
|
6
7
|
// string length (=1 per char), which breaks alignment with mixed text.
|
|
@@ -95,7 +96,7 @@ const ZH_T = {
|
|
|
95
96
|
secModel: "模型",
|
|
96
97
|
secSkills: "已加载技能",
|
|
97
98
|
balTotal: "总余额:",
|
|
98
|
-
balNoKey: "未配置 API Key",
|
|
99
|
+
balNoKey: "未配置 {p} API Key",
|
|
99
100
|
balLoading: "查询中...",
|
|
100
101
|
balError: "查询失败",
|
|
101
102
|
balErr401: "API Key 无效",
|
|
@@ -134,7 +135,7 @@ const EN_T = {
|
|
|
134
135
|
secModel: "Model",
|
|
135
136
|
secSkills: "Loaded Skills",
|
|
136
137
|
balTotal: "Total:",
|
|
137
|
-
balNoKey: "
|
|
138
|
+
balNoKey: "{p} API Key not set",
|
|
138
139
|
balLoading: "Fetching...",
|
|
139
140
|
balError: "Fetch failed",
|
|
140
141
|
balErr401: "Invalid API Key",
|
|
@@ -316,27 +317,6 @@ function estimateTokens(text) {
|
|
|
316
317
|
return Math.max(1, Math.ceil(ascii / asciiPerToken + cjk / 1.0));
|
|
317
318
|
}
|
|
318
319
|
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
320
|
/**
|
|
341
321
|
* 将余额从来源币种换算为目标币种。
|
|
342
322
|
* DEFAULT_RATES 以 USD=1 为基准:先折算为 USD,再换算到目标币种。
|
|
@@ -348,6 +328,29 @@ function convertBalance(target, targetRate, amount, from) {
|
|
|
348
328
|
const usd = from === "USD" ? amount : amount / fromRate;
|
|
349
329
|
return target === "USD" ? usd : usd * targetRate;
|
|
350
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* 从 OpenCode 已认证的 provider 读取 API key 作为余额查询的自动兜底。
|
|
333
|
+
* 匹配复用前缀逻辑:先精确匹配 id,再前缀匹配(如 moonshotai-cn → moonshot)。
|
|
334
|
+
* key 来源:auth.json(provider.key)或配置(provider.options.apiKey)。
|
|
335
|
+
* 仅当手动配置的 key 缺失时使用;读取失败或未匹配返回空串。
|
|
336
|
+
*/
|
|
337
|
+
function findOpencodeKey(api, provider) {
|
|
338
|
+
try {
|
|
339
|
+
const provs = api.state.provider;
|
|
340
|
+
// 大小写不敏感:精确匹配 id,否则前缀匹配(如 moonshotai-cn → moonshot)
|
|
341
|
+
const id = provider.id.toLowerCase();
|
|
342
|
+
const hit = provs.find((p) => p.id.toLowerCase() === id) ?? provs.find((p) => p.id.toLowerCase().startsWith(id));
|
|
343
|
+
if (!hit)
|
|
344
|
+
return "";
|
|
345
|
+
const k = typeof hit.key === "string" ? hit.key : "";
|
|
346
|
+
if (k)
|
|
347
|
+
return k;
|
|
348
|
+
return typeof hit.options?.apiKey === "string" ? hit.options.apiKey : "";
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return "";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
351
354
|
/** 货币符号:优先取 /cache-currency 内置映射,未知币种回退为代码。 */
|
|
352
355
|
function balanceSymbol(currency) {
|
|
353
356
|
const sym = CURRENCIES[currency];
|
|
@@ -379,7 +382,7 @@ function TokenCachePanel(props) {
|
|
|
379
382
|
const [skillsOpen, setSkillsOpen] = createSignal(true);
|
|
380
383
|
let boxEl;
|
|
381
384
|
// ── shared signals (de-structured so internal code is unchanged) ──
|
|
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;
|
|
385
|
+
const { currencySymbol, setCurrencySymbol, exchangeRate, setExchangeRate, langZH, setLangZH, sectionDetail, setSectionDetail, sectionModel, setSectionModel, sectionDist, setSectionDist, sectionSkills, setSectionSkills, sectionBalance, setSectionBalance, balanceRefresh, balanceProviderId, setBalanceProviderId, autoBalance, setAutoBalance, balanceCurrency, setBalanceCurrency, borderVisible, setBorderVisible, } = props.signals;
|
|
383
386
|
// ── reactive translation (follows langZH signal) ──
|
|
384
387
|
const t = createMemo(() => langZH() ? ZH_T : EN_T);
|
|
385
388
|
// ── scan session messages reactively ──
|
|
@@ -412,8 +415,13 @@ function TokenCachePanel(props) {
|
|
|
412
415
|
});
|
|
413
416
|
// 请求序号:防止定时轮询与手动刷新并发时,慢的旧请求覆盖新结果
|
|
414
417
|
let balanceSeq = 0;
|
|
418
|
+
// 当前 provider 显示名
|
|
419
|
+
const providerName = createMemo(() => getBalanceProvider(balanceProviderId()).name);
|
|
415
420
|
const pollBalance = async () => {
|
|
416
|
-
const
|
|
421
|
+
const provider = getBalanceProvider(balanceProviderId());
|
|
422
|
+
// 手动配置的 key 优先;缺失时自动复用 OpenCode 已认证的 key(auth.json / config)
|
|
423
|
+
const key = props.api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "")
|
|
424
|
+
|| findOpencodeKey(props.api, provider);
|
|
417
425
|
if (!key) {
|
|
418
426
|
setBalanceState({ status: "idle", data: null, lastFetch: 0, error: undefined, key: undefined });
|
|
419
427
|
return;
|
|
@@ -429,7 +437,7 @@ function TokenCachePanel(props) {
|
|
|
429
437
|
let timedOut = false;
|
|
430
438
|
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, 10_000);
|
|
431
439
|
try {
|
|
432
|
-
const data = await
|
|
440
|
+
const data = await provider.fetchBalance(key, controller.signal);
|
|
433
441
|
clearTimeout(timer);
|
|
434
442
|
if (seq !== balanceSeq)
|
|
435
443
|
return; // 已被更新的请求取代,丢弃过期结果
|
|
@@ -452,6 +460,39 @@ function TokenCachePanel(props) {
|
|
|
452
460
|
void balanceRefresh();
|
|
453
461
|
untrack(() => { void pollBalance(); });
|
|
454
462
|
});
|
|
463
|
+
// 自动切换当前会话的 provider(前缀匹配)。手动切换会关闭此行为。
|
|
464
|
+
// 直接追踪 messages 取最后一条 assistant 消息的 providerID——
|
|
465
|
+
// 不依赖 session.model 的响应式更新(模型切换时该链路可能不触发重算)。
|
|
466
|
+
createEffect(() => {
|
|
467
|
+
if (!autoBalance())
|
|
468
|
+
return;
|
|
469
|
+
const sid = props.signals.overrideSessionId() ?? props.sessionId;
|
|
470
|
+
const msgs = props.api.state.session.messages(sid);
|
|
471
|
+
let pid = "";
|
|
472
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
473
|
+
const m = msgs[i];
|
|
474
|
+
if (m.role === "assistant" && m.providerID) {
|
|
475
|
+
pid = m.providerID;
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// 会话尚无 assistant 消息(新会话 / 刚切换模型未对话 / 消息未加载)
|
|
480
|
+
// → 回退到会话级模型元数据,反映当前正在使用的 provider
|
|
481
|
+
if (!pid) {
|
|
482
|
+
try {
|
|
483
|
+
const session = props.api.state.session.get(sid);
|
|
484
|
+
pid = session?.model?.providerID ?? "";
|
|
485
|
+
}
|
|
486
|
+
catch { /* ignore */ }
|
|
487
|
+
}
|
|
488
|
+
if (!pid)
|
|
489
|
+
return;
|
|
490
|
+
const hit = matchBalanceProvider(pid);
|
|
491
|
+
if (hit && hit.id !== balanceProviderId()) {
|
|
492
|
+
setBalanceProviderId(hit.id);
|
|
493
|
+
props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
455
496
|
// ── auto-clear override when the user navigates to a different main session ──
|
|
456
497
|
let lastMainSid = props.sessionId;
|
|
457
498
|
createEffect(() => {
|
|
@@ -705,6 +746,25 @@ function TokenCachePanel(props) {
|
|
|
705
746
|
const balCur = props.api.kv.get(`${KV_PREFIX}.balance_currency`);
|
|
706
747
|
if (typeof balCur === "string")
|
|
707
748
|
setBalanceCurrency(balCur);
|
|
749
|
+
// Restore balance provider (fall back to default when unknown)
|
|
750
|
+
const savedProvider = props.api.kv.get(`${KV_PREFIX}.balance.provider`);
|
|
751
|
+
if (typeof savedProvider === "string" && balanceProviders.some((p) => p.id === savedProvider)) {
|
|
752
|
+
setBalanceProviderId(savedProvider);
|
|
753
|
+
}
|
|
754
|
+
// Restore auto-switch (default on)
|
|
755
|
+
const savedAuto = props.api.kv.get(`${KV_PREFIX}.balance.auto`);
|
|
756
|
+
if (typeof savedAuto === "boolean")
|
|
757
|
+
setAutoBalance(savedAuto);
|
|
758
|
+
// Migrate legacy DeepSeek key (cache_panel.ds_key → cache_panel.balance.deepseek.key)
|
|
759
|
+
const legacyKey = props.api.kv.get(`${KV_PREFIX}.ds_key`, "");
|
|
760
|
+
if (legacyKey) {
|
|
761
|
+
const dsKey = props.api.kv.get(`${KV_PREFIX}.balance.deepseek.key`, "");
|
|
762
|
+
if (!dsKey)
|
|
763
|
+
props.api.kv.set(`${KV_PREFIX}.balance.deepseek.key`, legacyKey);
|
|
764
|
+
props.api.kv.set(`${KV_PREFIX}.ds_key`, "");
|
|
765
|
+
}
|
|
766
|
+
// 恢复的 provider 可能与默认值不同,强制重新查询
|
|
767
|
+
props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
|
|
708
768
|
setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)));
|
|
709
769
|
setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)));
|
|
710
770
|
setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)));
|
|
@@ -837,7 +897,7 @@ function TokenCachePanel(props) {
|
|
|
837
897
|
const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
|
|
838
898
|
const label = truncateVisual(sk.name, maxLabel);
|
|
839
899
|
return (_jsx("text", { fg: pal().muted, children: justify(label, fmt(sk.tokens), t().tok) }));
|
|
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: (() => {
|
|
900
|
+
}) })] }) }), _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.replace("{p}", providerName()) })] }) }), _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
901
|
const code = balanceState().error;
|
|
842
902
|
if (code === "401")
|
|
843
903
|
return t().balErr401;
|
|
@@ -900,6 +960,8 @@ const tui = async (api) => {
|
|
|
900
960
|
const [sectionSkills, setSectionSkills] = createSignal(true);
|
|
901
961
|
const [sectionBalance, setSectionBalance] = createSignal(true);
|
|
902
962
|
const [balanceRefresh, setBalanceRefresh] = createSignal(0);
|
|
963
|
+
const [balanceProviderId, setBalanceProviderId] = createSignal("deepseek");
|
|
964
|
+
const [autoBalance, setAutoBalance] = createSignal(true);
|
|
903
965
|
const [balanceCurrency, setBalanceCurrency] = createSignal("");
|
|
904
966
|
const [borderVisible, setBorderVisible] = createSignal(true);
|
|
905
967
|
const [langZH, setLangZH] = createSignal(LANG_ZH);
|
|
@@ -914,6 +976,8 @@ const tui = async (api) => {
|
|
|
914
976
|
sectionSkills, setSectionSkills,
|
|
915
977
|
sectionBalance, setSectionBalance,
|
|
916
978
|
balanceRefresh, setBalanceRefresh,
|
|
979
|
+
balanceProviderId, setBalanceProviderId,
|
|
980
|
+
autoBalance, setAutoBalance,
|
|
917
981
|
balanceCurrency, setBalanceCurrency,
|
|
918
982
|
borderVisible, setBorderVisible,
|
|
919
983
|
overrideSessionId, setOverrideSessionId,
|
|
@@ -921,6 +985,46 @@ const tui = async (api) => {
|
|
|
921
985
|
api.slots.register(createSidebarSlot(api, signals));
|
|
922
986
|
// ── slash commands for runtime config ──
|
|
923
987
|
const KV_PREFIX = "cache_panel";
|
|
988
|
+
/** 菜单中 provider 选项标题:标注 key 来源(手动配置 / OpenCode 自动复用 / 未配置)。 */
|
|
989
|
+
const providerOptionTitle = (p, current) => {
|
|
990
|
+
const zh = langZH();
|
|
991
|
+
const hasManual = !!api.kv.get(`${KV_PREFIX}.balance.${p.id}.key`, "");
|
|
992
|
+
const hasAuto = !hasManual && !!findOpencodeKey(api, p);
|
|
993
|
+
const mark = hasManual
|
|
994
|
+
? (zh ? "(用户 key)" : " (user key)")
|
|
995
|
+
: hasAuto
|
|
996
|
+
? (zh ? "(OpenCode)" : " (OpenCode)")
|
|
997
|
+
: (zh ? "(未配置)" : " (not set)");
|
|
998
|
+
return p.name + mark + (current && p.id === current ? " *" : "");
|
|
999
|
+
};
|
|
1000
|
+
/** 弹出指定 provider 的 API Key 输入框(脱敏预填;空清除 / 含 * 保留原 key / 新 key 实时刷新)。 */
|
|
1001
|
+
const promptBalanceKey = (dialog, provider) => {
|
|
1002
|
+
const zh = langZH();
|
|
1003
|
+
const current = api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
|
|
1004
|
+
const masked = maskKey(current);
|
|
1005
|
+
dialog?.replace(() => (_jsx(api.ui.DialogPrompt, { title: provider.name, description: () => _jsx("text", { children: zh ? `输入 ${provider.name} API Key 以显示账户余额(留空清除)` : `Enter your ${provider.name} API key to show account balance (leave empty to clear)` }), placeholder: provider.keyPlaceholder ?? "sk-...", value: masked, onConfirm: (val) => {
|
|
1006
|
+
const input = val.trim();
|
|
1007
|
+
let key;
|
|
1008
|
+
if (input === "") {
|
|
1009
|
+
key = "";
|
|
1010
|
+
}
|
|
1011
|
+
else if (input.includes("*")) {
|
|
1012
|
+
key = current;
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
key = input;
|
|
1016
|
+
}
|
|
1017
|
+
api.kv.set(`${KV_PREFIX}.balance.${provider.id}.key`, key);
|
|
1018
|
+
setBalanceRefresh(v => v + 1);
|
|
1019
|
+
if (key) {
|
|
1020
|
+
api.ui.toast({ message: zh ? "API Key 已保存,正在查询余额..." : "API Key saved, fetching balance..." });
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
api.ui.toast({ message: zh ? "API Key 已清除" : "API Key cleared" });
|
|
1024
|
+
}
|
|
1025
|
+
dialog?.clear();
|
|
1026
|
+
}, onCancel: () => dialog?.clear() })));
|
|
1027
|
+
};
|
|
924
1028
|
api.command?.register(() => [
|
|
925
1029
|
{
|
|
926
1030
|
title: "Cache: Set Currency",
|
|
@@ -980,7 +1084,7 @@ const tui = async (api) => {
|
|
|
980
1084
|
{ title: `Model & Pricing [${modelOn ? "ON" : "OFF"}]`, value: "model" },
|
|
981
1085
|
{ title: `Token Dist. [${distOn ? "ON" : "OFF"}]`, value: "dist" },
|
|
982
1086
|
{ title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`, value: "skills" },
|
|
983
|
-
{ title: `
|
|
1087
|
+
{ title: `Balance [${balanceOn ? "ON" : "OFF"}]`, value: "balance" },
|
|
984
1088
|
{ title: `Panel Border [${borderOn ? "ON" : "OFF"}]`, value: "border" },
|
|
985
1089
|
], onSelect: (opt) => {
|
|
986
1090
|
if (opt.value === "border") {
|
|
@@ -1050,47 +1154,79 @@ const tui = async (api) => {
|
|
|
1050
1154
|
},
|
|
1051
1155
|
},
|
|
1052
1156
|
{
|
|
1053
|
-
title: "Cache:
|
|
1054
|
-
value: "cache.balance
|
|
1055
|
-
description: "
|
|
1056
|
-
slash: { name: "cache-balance
|
|
1157
|
+
title: "Cache: Switch Balance Provider",
|
|
1158
|
+
value: "cache.balance",
|
|
1159
|
+
description: "切换余额提供商 / 自动切换当前会话提供商 | Switch balance provider / auto-switch session provider",
|
|
1160
|
+
slash: { name: "cache-balance" },
|
|
1057
1161
|
onSelect: (dialog) => {
|
|
1058
1162
|
const zh = langZH();
|
|
1059
|
-
const current =
|
|
1060
|
-
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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..." });
|
|
1163
|
+
const current = signals.balanceProviderId();
|
|
1164
|
+
const auto = signals.autoBalance();
|
|
1165
|
+
const autoLabel = auto
|
|
1166
|
+
? (zh ? "自动切换提供商 [开]" : "Auto-switch provider [ON]")
|
|
1167
|
+
: (zh ? "自动切换提供商 [关]" : "Auto-switch provider [OFF]");
|
|
1168
|
+
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: zh ? "余额提供商 / 自动切换" : "Balance Provider / Auto-switch", options: [
|
|
1169
|
+
{
|
|
1170
|
+
title: autoLabel,
|
|
1171
|
+
value: "__auto__",
|
|
1172
|
+
},
|
|
1173
|
+
...balanceProviders.map((p) => ({
|
|
1174
|
+
title: providerOptionTitle(p, current),
|
|
1175
|
+
value: p.id,
|
|
1176
|
+
})),
|
|
1177
|
+
], onSelect: (opt) => {
|
|
1178
|
+
if (opt.value === "__auto__") {
|
|
1179
|
+
const next = !auto;
|
|
1180
|
+
api.kv.set(`${KV_PREFIX}.balance.auto`, next);
|
|
1181
|
+
signals.setAutoBalance(next);
|
|
1182
|
+
api.ui.toast({ message: zh ? `自动切换余额提供商: ${next ? "开" : "关"}` : `Auto-switch balance provider: ${next ? "ON" : "OFF"}` });
|
|
1183
|
+
dialog?.clear();
|
|
1088
1184
|
}
|
|
1089
1185
|
else {
|
|
1090
|
-
|
|
1186
|
+
const provider = getBalanceProvider(opt.value);
|
|
1187
|
+
// 手动切换会关闭自动切换
|
|
1188
|
+
api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
|
|
1189
|
+
api.kv.set(`${KV_PREFIX}.balance.auto`, false);
|
|
1190
|
+
signals.setBalanceProviderId(provider.id);
|
|
1191
|
+
signals.setAutoBalance(false);
|
|
1192
|
+
// 切换后立即按新 provider 刷新显示(无 key 时显示 idle,避免残留上一 provider 余额)
|
|
1193
|
+
signals.setBalanceRefresh(signals.balanceRefresh() + 1);
|
|
1194
|
+
const hasKey = !!api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
|
|
1195
|
+
if (!hasKey) {
|
|
1196
|
+
// 未配置 key → 进入设置流程(对话框保持打开等待输入)
|
|
1197
|
+
promptBalanceKey(dialog, provider);
|
|
1198
|
+
}
|
|
1199
|
+
else {
|
|
1200
|
+
api.ui.toast({ message: zh ? `余额提供商: ${provider.name}(自动切换已关闭)` : `Balance provider: ${provider.name} (auto-switch off)` });
|
|
1201
|
+
dialog?.clear();
|
|
1202
|
+
}
|
|
1091
1203
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1204
|
+
} })));
|
|
1205
|
+
},
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
title: "Cache: Set Balance API Key",
|
|
1209
|
+
value: "cache.balance.key",
|
|
1210
|
+
description: "Select a provider and set its API key for balance display",
|
|
1211
|
+
slash: { name: "cache-balance-key" },
|
|
1212
|
+
onSelect: (dialog) => {
|
|
1213
|
+
const zh = langZH();
|
|
1214
|
+
// 步骤 1:选择 provider
|
|
1215
|
+
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: zh ? "选择余额提供商" : "Select Balance Provider", options: balanceProviders.map((p) => ({
|
|
1216
|
+
title: providerOptionTitle(p),
|
|
1217
|
+
value: p.id,
|
|
1218
|
+
})), onSelect: (opt) => {
|
|
1219
|
+
const provider = getBalanceProvider(opt.value);
|
|
1220
|
+
// 手动指定 provider 会关闭自动切换
|
|
1221
|
+
api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
|
|
1222
|
+
api.kv.set(`${KV_PREFIX}.balance.auto`, false);
|
|
1223
|
+
signals.setBalanceProviderId(provider.id);
|
|
1224
|
+
signals.setAutoBalance(false);
|
|
1225
|
+
// 切换后立即刷新显示(防止取消输入时残留上一 provider 的余额)
|
|
1226
|
+
signals.setBalanceRefresh(signals.balanceRefresh() + 1);
|
|
1227
|
+
// 步骤 2:输入 key
|
|
1228
|
+
promptBalanceKey(dialog, provider);
|
|
1229
|
+
} })));
|
|
1094
1230
|
},
|
|
1095
1231
|
},
|
|
1096
1232
|
{
|