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/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.
|
|
2
|
+
export const PLUGIN_VERSION="1.4.0";
|
package/src/index.tsx
CHANGED
|
@@ -113,6 +113,14 @@ const ZH_T = {
|
|
|
113
113
|
secDetail: "明细",
|
|
114
114
|
secModel: "模型",
|
|
115
115
|
secSkills: "已加载技能",
|
|
116
|
+
balTotal: "总余额:",
|
|
117
|
+
balNoKey: "未配置 API Key",
|
|
118
|
+
balLoading: "查询中...",
|
|
119
|
+
balError: "查询失败",
|
|
120
|
+
balErr401: "API Key 无效",
|
|
121
|
+
balErr403: "余额查询被拒绝",
|
|
122
|
+
balErrEmpty:"未获取到余额数据",
|
|
123
|
+
balErrTimeout: "查询超时",
|
|
116
124
|
} as const
|
|
117
125
|
|
|
118
126
|
const EN_T = {
|
|
@@ -145,6 +153,14 @@ const EN_T = {
|
|
|
145
153
|
secDetail: "Detail",
|
|
146
154
|
secModel: "Model",
|
|
147
155
|
secSkills: "Loaded Skills",
|
|
156
|
+
balTotal: "Total:",
|
|
157
|
+
balNoKey: "No API Key set",
|
|
158
|
+
balLoading: "Fetching...",
|
|
159
|
+
balError: "Fetch failed",
|
|
160
|
+
balErr401: "Invalid API Key",
|
|
161
|
+
balErr403: "Balance request rejected",
|
|
162
|
+
balErrEmpty:"No balance data",
|
|
163
|
+
balErrTimeout: "Request timed out",
|
|
148
164
|
} as const
|
|
149
165
|
|
|
150
166
|
// ── color helpers ────────────────────────────────────────────────
|
|
@@ -331,6 +347,64 @@ interface TokenDist {
|
|
|
331
347
|
stepCost: number
|
|
332
348
|
}
|
|
333
349
|
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// DeepSeek Balance
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
interface DeepSeekBalance {
|
|
355
|
+
currency: string
|
|
356
|
+
total: string
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
interface BalanceState {
|
|
360
|
+
status: "idle" | "loading" | "ok" | "error"
|
|
361
|
+
data: DeepSeekBalance[] | null
|
|
362
|
+
lastFetch: number
|
|
363
|
+
error?: string
|
|
364
|
+
key?: string // 上次成功/尝试查询所用的 key,用于检测 key 是否更换
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const BALANCE_POLL_MS = 5 * 60 * 1000 // 5 minutes
|
|
368
|
+
|
|
369
|
+
async function fetchDeepSeekBalance(apiKey: string, signal?: AbortSignal): Promise<DeepSeekBalance[]> {
|
|
370
|
+
const res = await fetch("https://api.deepseek.com/user/balance", {
|
|
371
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
372
|
+
signal,
|
|
373
|
+
})
|
|
374
|
+
if (!res.ok) {
|
|
375
|
+
if (res.status === 401) throw new Error("401")
|
|
376
|
+
if (res.status === 402 || res.status === 403) throw new Error("403")
|
|
377
|
+
throw new Error(String(res.status))
|
|
378
|
+
}
|
|
379
|
+
const json = await res.json() as {
|
|
380
|
+
is_available?: boolean
|
|
381
|
+
balance_infos?: { currency: string; total_balance: string; granted_balance: string; topped_up_balance: string }[]
|
|
382
|
+
}
|
|
383
|
+
const infos = json.balance_infos ?? []
|
|
384
|
+
if (infos.length === 0) throw new Error("EMPTY")
|
|
385
|
+
return infos.map((info) => ({
|
|
386
|
+
currency: info.currency ?? "CNY",
|
|
387
|
+
total: info.total_balance ?? "0",
|
|
388
|
+
}))
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 将余额从来源币种换算为目标币种。
|
|
393
|
+
* DEFAULT_RATES 以 USD=1 为基准:先折算为 USD,再换算到目标币种。
|
|
394
|
+
*/
|
|
395
|
+
function convertBalance(target: string, targetRate: number, amount: number, from: string): number {
|
|
396
|
+
if (from === target) return amount
|
|
397
|
+
const fromRate = DEFAULT_RATES[from] ?? 1
|
|
398
|
+
const usd = from === "USD" ? amount : amount / fromRate
|
|
399
|
+
return target === "USD" ? usd : usd * targetRate
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** 货币符号:优先取 /cache-currency 内置映射,未知币种回退为代码。 */
|
|
403
|
+
function balanceSymbol(currency: string): string {
|
|
404
|
+
const sym = CURRENCIES[currency]
|
|
405
|
+
return sym ?? currency + " "
|
|
406
|
+
}
|
|
407
|
+
|
|
334
408
|
// ---------------------------------------------------------------------------
|
|
335
409
|
// Sidebar component
|
|
336
410
|
// ---------------------------------------------------------------------------
|
|
@@ -353,6 +427,14 @@ interface PanelSignals {
|
|
|
353
427
|
setSectionDist: (v: boolean) => void
|
|
354
428
|
sectionSkills: () => boolean
|
|
355
429
|
setSectionSkills: (v: boolean) => void
|
|
430
|
+
sectionBalance: () => boolean
|
|
431
|
+
setSectionBalance: (v: boolean) => void
|
|
432
|
+
/** Increment to force a DeepSeek balance re-fetch. */
|
|
433
|
+
balanceRefresh: () => number
|
|
434
|
+
setBalanceRefresh: (v: number) => void
|
|
435
|
+
/** Preferred currency code for balance display (CNY / USD / …). Empty = first entry. */
|
|
436
|
+
balanceCurrency: () => string
|
|
437
|
+
setBalanceCurrency: (v: string) => void
|
|
356
438
|
borderVisible: () => boolean
|
|
357
439
|
setBorderVisible: (v: boolean) => void
|
|
358
440
|
/** When set, the panel renders stats for this session instead of the main one. */
|
|
@@ -404,6 +486,9 @@ function TokenCachePanel(props: {
|
|
|
404
486
|
sectionModel, setSectionModel,
|
|
405
487
|
sectionDist, setSectionDist,
|
|
406
488
|
sectionSkills, setSectionSkills,
|
|
489
|
+
sectionBalance, setSectionBalance,
|
|
490
|
+
balanceRefresh,
|
|
491
|
+
balanceCurrency, setBalanceCurrency,
|
|
407
492
|
borderVisible, setBorderVisible,
|
|
408
493
|
} = props.signals
|
|
409
494
|
|
|
@@ -437,6 +522,48 @@ function TokenCachePanel(props: {
|
|
|
437
522
|
})
|
|
438
523
|
const [refreshTick, setRefreshTick] = createSignal(0)
|
|
439
524
|
|
|
525
|
+
// ── balance state + polling ──────────────────────────────────
|
|
526
|
+
const [balanceState, setBalanceState] = createSignal<BalanceState>({
|
|
527
|
+
status: "idle", data: null, lastFetch: 0,
|
|
528
|
+
})
|
|
529
|
+
// 请求序号:防止定时轮询与手动刷新并发时,慢的旧请求覆盖新结果
|
|
530
|
+
let balanceSeq = 0
|
|
531
|
+
|
|
532
|
+
const pollBalance = async () => {
|
|
533
|
+
const key = props.api.kv.get<string>(`${KV_PREFIX}.ds_key`, "")
|
|
534
|
+
if (!key) { setBalanceState({ status: "idle", data: null, lastFetch: 0, error: undefined, key: undefined }); return }
|
|
535
|
+
const now = Date.now()
|
|
536
|
+
const prev = balanceState()
|
|
537
|
+
// key 已更换(重新输入)→ 强制重新查询,绕过缓存
|
|
538
|
+
if (prev.status === "ok" && prev.key === key && now - prev.lastFetch < BALANCE_POLL_MS) return // cache still fresh
|
|
539
|
+
const seq = ++balanceSeq
|
|
540
|
+
setBalanceState({ ...prev, status: "loading", error: undefined, key })
|
|
541
|
+
const controller = new AbortController()
|
|
542
|
+
let timedOut = false
|
|
543
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort() }, 10_000)
|
|
544
|
+
try {
|
|
545
|
+
const data = await fetchDeepSeekBalance(key, controller.signal)
|
|
546
|
+
clearTimeout(timer)
|
|
547
|
+
if (seq !== balanceSeq) return // 已被更新的请求取代,丢弃过期结果
|
|
548
|
+
setBalanceState({ status: "ok", data, lastFetch: Date.now(), error: undefined, key })
|
|
549
|
+
} catch (err) {
|
|
550
|
+
clearTimeout(timer)
|
|
551
|
+
if (seq !== balanceSeq) return
|
|
552
|
+
const code = timedOut ? "TIMEOUT" : (err instanceof Error ? err.message : "")
|
|
553
|
+
// 失败时清空旧数据,避免显示过期余额
|
|
554
|
+
setBalanceState({ status: "error", data: null, lastFetch: 0, error: code, key })
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Re-fetch when the API key is (re)configured via /cache-balance-key.
|
|
559
|
+
// 注意:pollBalance 内部读写 balanceState 信号,若不做 untrack 包裹,
|
|
560
|
+
// effect 会追踪 balanceState 的变化并与 pollBalance 的 setBalanceState
|
|
561
|
+
// 形成无限循环(每次重跑都发起新的 fetch 请求)。
|
|
562
|
+
createEffect(() => {
|
|
563
|
+
void balanceRefresh()
|
|
564
|
+
untrack(() => { void pollBalance() })
|
|
565
|
+
})
|
|
566
|
+
|
|
440
567
|
// ── auto-clear override when the user navigates to a different main session ──
|
|
441
568
|
let lastMainSid = props.sessionId
|
|
442
569
|
createEffect(() => {
|
|
@@ -634,10 +761,13 @@ function TokenCachePanel(props: {
|
|
|
634
761
|
const rate = props.api.kv.get<number>(`${KV_PREFIX}.rate`)
|
|
635
762
|
if (typeof sym === "string") setCurrencySymbol(sym)
|
|
636
763
|
if (typeof rate === "number" && rate > 0) setExchangeRate(rate)
|
|
764
|
+
const balCur = props.api.kv.get<string>(`${KV_PREFIX}.balance_currency`)
|
|
765
|
+
if (typeof balCur === "string") setBalanceCurrency(balCur)
|
|
637
766
|
setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)))
|
|
638
767
|
setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)))
|
|
639
768
|
setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)))
|
|
640
769
|
setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX}.section.skills`, true)))
|
|
770
|
+
setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX}.section.balance`, true)))
|
|
641
771
|
const bv = props.api.kv.get<boolean>(`${KV_PREFIX}.border`, true)
|
|
642
772
|
setBorderVisible(bv !== false)
|
|
643
773
|
// Restore language preference
|
|
@@ -692,7 +822,8 @@ function TokenCachePanel(props: {
|
|
|
692
822
|
const unsubMsg = props.api.event.on("message.updated", () => { bumpPartVersion(); setRefreshTick(v => v + 1) })
|
|
693
823
|
const unsubSession = props.api.event.on("session.updated", () => { setRefreshTick(v => v + 1) })
|
|
694
824
|
setRefreshTick(v => v + 1)
|
|
695
|
-
|
|
825
|
+
const balanceTimer = setInterval(pollBalance, BALANCE_POLL_MS)
|
|
826
|
+
onCleanup(() => { clearTimeout(partTimer); clearInterval(balanceTimer); unsubPart(); unsubMsg(); unsubSession() })
|
|
696
827
|
})
|
|
697
828
|
|
|
698
829
|
// ── colours ──
|
|
@@ -977,6 +1108,65 @@ function TokenCachePanel(props: {
|
|
|
977
1108
|
</Show>
|
|
978
1109
|
</Show>
|
|
979
1110
|
</Show>
|
|
1111
|
+
|
|
1112
|
+
{/* ── DeepSeek balance (single line) ── */}
|
|
1113
|
+
<Show when={sectionBalance()}>
|
|
1114
|
+
<text fg={pal().muted}>{sep()}</text>
|
|
1115
|
+
<Show when={balanceState().status === "idle"}>
|
|
1116
|
+
<text fg={pal().muted}>
|
|
1117
|
+
<span style={{ fg: pal().muted }}>{"> "}</span>
|
|
1118
|
+
<span>{t().balNoKey}</span>
|
|
1119
|
+
</text>
|
|
1120
|
+
</Show>
|
|
1121
|
+
<Show when={balanceState().status === "loading"}>
|
|
1122
|
+
<text fg={pal().muted}>
|
|
1123
|
+
<span style={{ fg: pal().muted }}>{"> "}</span>
|
|
1124
|
+
<span>{t().balLoading}</span>
|
|
1125
|
+
</text>
|
|
1126
|
+
</Show>
|
|
1127
|
+
<Show when={balanceState().status === "error"}>
|
|
1128
|
+
<text fg={pal().error}>
|
|
1129
|
+
<span style={{ fg: pal().muted }}>{"> "}</span>
|
|
1130
|
+
<span>{(() => {
|
|
1131
|
+
const code = balanceState().error
|
|
1132
|
+
if (code === "401") return t().balErr401
|
|
1133
|
+
if (code === "403") return t().balErr403
|
|
1134
|
+
if (code === "EMPTY") return t().balErrEmpty
|
|
1135
|
+
if (code === "TIMEOUT") return t().balErrTimeout
|
|
1136
|
+
return t().balError + (code ? ` (${code})` : "")
|
|
1137
|
+
})()}</span>
|
|
1138
|
+
</text>
|
|
1139
|
+
</Show>
|
|
1140
|
+
<Show when={balanceState().status === "ok" && balanceState().data}>
|
|
1141
|
+
{(() => {
|
|
1142
|
+
const list = balanceState().data!
|
|
1143
|
+
const pref = balanceCurrency()
|
|
1144
|
+
// 偏好币种是 DeepSeek 原生返回的(CNY/USD)→ 直接显示
|
|
1145
|
+
const native = pref ? list.find(x => x.currency === pref) : undefined
|
|
1146
|
+
if (native) {
|
|
1147
|
+
return (
|
|
1148
|
+
<text fg={pal().text}>
|
|
1149
|
+
{justify(t().balTotal, balanceSymbol(native.currency) + native.total)}
|
|
1150
|
+
</text>
|
|
1151
|
+
)
|
|
1152
|
+
}
|
|
1153
|
+
// 非原生币种(EUR/JPY/GBP/KRW…)→ 取第一条余额按汇率换算
|
|
1154
|
+
const base = list[0]
|
|
1155
|
+
const baseAmt = parseFloat(base.total)
|
|
1156
|
+
const converted = Number.isFinite(baseAmt)
|
|
1157
|
+
? convertBalance(pref || base.currency, exchangeRate(), baseAmt, base.currency)
|
|
1158
|
+
: baseAmt
|
|
1159
|
+
const shown = pref && base.currency !== pref
|
|
1160
|
+
? converted.toLocaleString("en-US", { maximumFractionDigits: 2 })
|
|
1161
|
+
: base.total
|
|
1162
|
+
return (
|
|
1163
|
+
<text fg={pal().text}>
|
|
1164
|
+
{justify(t().balTotal, balanceSymbol(pref || base.currency) + shown)}
|
|
1165
|
+
</text>
|
|
1166
|
+
)
|
|
1167
|
+
})()}
|
|
1168
|
+
</Show>
|
|
1169
|
+
</Show>
|
|
980
1170
|
</Show>
|
|
981
1171
|
</Show>
|
|
982
1172
|
</box>
|
|
@@ -1022,6 +1212,9 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1022
1212
|
const [sectionModel, setSectionModel] = createSignal(true)
|
|
1023
1213
|
const [sectionDist, setSectionDist] = createSignal(true)
|
|
1024
1214
|
const [sectionSkills, setSectionSkills] = createSignal(true)
|
|
1215
|
+
const [sectionBalance, setSectionBalance] = createSignal(true)
|
|
1216
|
+
const [balanceRefresh, setBalanceRefresh] = createSignal(0)
|
|
1217
|
+
const [balanceCurrency, setBalanceCurrency] = createSignal("")
|
|
1025
1218
|
const [borderVisible, setBorderVisible] = createSignal(true)
|
|
1026
1219
|
const [langZH, setLangZH] = createSignal(LANG_ZH)
|
|
1027
1220
|
const [overrideSessionId, setOverrideSessionId] = createSignal<string | undefined>(undefined)
|
|
@@ -1034,6 +1227,9 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1034
1227
|
sectionModel, setSectionModel,
|
|
1035
1228
|
sectionDist, setSectionDist,
|
|
1036
1229
|
sectionSkills, setSectionSkills,
|
|
1230
|
+
sectionBalance, setSectionBalance,
|
|
1231
|
+
balanceRefresh, setBalanceRefresh,
|
|
1232
|
+
balanceCurrency, setBalanceCurrency,
|
|
1037
1233
|
borderVisible, setBorderVisible,
|
|
1038
1234
|
overrideSessionId, setOverrideSessionId,
|
|
1039
1235
|
}
|
|
@@ -1061,6 +1257,9 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1061
1257
|
const defRate = DEFAULT_RATES[opt.value] ?? 1
|
|
1062
1258
|
api.kv.set(`${KV_PREFIX}.currency`, sym)
|
|
1063
1259
|
api.kv.set(`${KV_PREFIX}.rate`, defRate)
|
|
1260
|
+
// 同步余额显示币种偏好:CNY/USD 原生直显,其余币种按汇率换算
|
|
1261
|
+
api.kv.set(`${KV_PREFIX}.balance_currency`, opt.value)
|
|
1262
|
+
signals.setBalanceCurrency(opt.value)
|
|
1064
1263
|
signals.setCurrencySymbol(sym)
|
|
1065
1264
|
signals.setExchangeRate(defRate)
|
|
1066
1265
|
api.ui.toast({ message: `Currency: ${opt.value} (${sym}), rate: ${defRate}` })
|
|
@@ -1105,6 +1304,7 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1105
1304
|
const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true))
|
|
1106
1305
|
const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true))
|
|
1107
1306
|
const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true))
|
|
1307
|
+
const balanceOn = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true))
|
|
1108
1308
|
const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true))
|
|
1109
1309
|
dialog?.replace(() => (
|
|
1110
1310
|
<api.ui.DialogSelect
|
|
@@ -1114,6 +1314,7 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1114
1314
|
{ title: `Model & Pricing [${modelOn ? "ON" : "OFF"}]`, value: "model" },
|
|
1115
1315
|
{ title: `Token Dist. [${distOn ? "ON" : "OFF"}]`, value: "dist" },
|
|
1116
1316
|
{ title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`, value: "skills" },
|
|
1317
|
+
{ title: `DS Balance [${balanceOn ? "ON" : "OFF"}]`, value: "balance" },
|
|
1117
1318
|
{ title: `Panel Border [${borderOn ? "ON" : "OFF"}]`, value: "border" },
|
|
1118
1319
|
]}
|
|
1119
1320
|
onSelect={(opt) => {
|
|
@@ -1130,6 +1331,7 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1130
1331
|
if (opt.value === "model") signals.setSectionModel(!cur)
|
|
1131
1332
|
if (opt.value === "dist") signals.setSectionDist(!cur)
|
|
1132
1333
|
if (opt.value === "skills") signals.setSectionSkills(!cur)
|
|
1334
|
+
if (opt.value === "balance") signals.setSectionBalance(!cur)
|
|
1133
1335
|
api.ui.toast({ message: `${opt.value} section ${!cur ? "shown" : "hidden"}` })
|
|
1134
1336
|
}
|
|
1135
1337
|
dialog?.clear()
|
|
@@ -1150,9 +1352,10 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1150
1352
|
const model = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true))
|
|
1151
1353
|
const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true))
|
|
1152
1354
|
const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true))
|
|
1355
|
+
const balance = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true))
|
|
1153
1356
|
api.ui.toast({
|
|
1154
1357
|
title: "Cache Panel Config",
|
|
1155
|
-
message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"}`,
|
|
1358
|
+
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"}`,
|
|
1156
1359
|
duration: 8000,
|
|
1157
1360
|
})
|
|
1158
1361
|
dialog?.clear()
|
|
@@ -1183,6 +1386,54 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1183
1386
|
))
|
|
1184
1387
|
},
|
|
1185
1388
|
},
|
|
1389
|
+
{
|
|
1390
|
+
title: "Cache: Set DeepSeek API Key",
|
|
1391
|
+
value: "cache.balance.key",
|
|
1392
|
+
description: "Set or update the DeepSeek API key for balance display",
|
|
1393
|
+
slash: { name: "cache-balance-key" },
|
|
1394
|
+
onSelect: (dialog) => {
|
|
1395
|
+
const zh = langZH()
|
|
1396
|
+
const current = api.kv.get<string>(`${KV_PREFIX}.ds_key`, "")
|
|
1397
|
+
// 已保存的 key 以脱敏形式预填:保留 "sk-" 前缀 + 头 5 尾 5 字符,中间用 * 填充
|
|
1398
|
+
const maskKey = (k: string): string => {
|
|
1399
|
+
if (!k) return ""
|
|
1400
|
+
const prefix = k.startsWith("sk-") ? "sk-" : ""
|
|
1401
|
+
const body = prefix ? k.slice(3) : k
|
|
1402
|
+
if (body.length <= 10) return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 5))
|
|
1403
|
+
return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 10)) + body.slice(-5)
|
|
1404
|
+
}
|
|
1405
|
+
const masked = maskKey(current)
|
|
1406
|
+
dialog?.replace(() => (
|
|
1407
|
+
<api.ui.DialogPrompt
|
|
1408
|
+
title={zh ? "DeepSeek API Key" : "DeepSeek API Key"}
|
|
1409
|
+
description={() => <text>{zh ? "输入 DeepSeek API Key 以显示账户余额(留空清除)" : "Enter your DeepSeek API key to show account balance (leave empty to clear)"}</text>}
|
|
1410
|
+
placeholder="sk-..."
|
|
1411
|
+
value={masked}
|
|
1412
|
+
onConfirm={(val) => {
|
|
1413
|
+
const input = val.trim()
|
|
1414
|
+
// 空 → 清除;含 * (脱敏占位符残留)→ 视为未修改,保留原 key;否则为新 key
|
|
1415
|
+
let key: string
|
|
1416
|
+
if (input === "") {
|
|
1417
|
+
key = ""
|
|
1418
|
+
} else if (input.includes("*")) {
|
|
1419
|
+
key = current
|
|
1420
|
+
} else {
|
|
1421
|
+
key = input
|
|
1422
|
+
}
|
|
1423
|
+
api.kv.set(`${KV_PREFIX}.ds_key`, key)
|
|
1424
|
+
setBalanceRefresh(v => v + 1)
|
|
1425
|
+
if (key) {
|
|
1426
|
+
api.ui.toast({ message: zh ? "API Key 已保存,正在查询余额..." : "API Key saved, fetching balance..." })
|
|
1427
|
+
} else {
|
|
1428
|
+
api.ui.toast({ message: zh ? "API Key 已清除" : "API Key cleared" })
|
|
1429
|
+
}
|
|
1430
|
+
dialog?.clear()
|
|
1431
|
+
}}
|
|
1432
|
+
onCancel={() => dialog?.clear()}
|
|
1433
|
+
/>
|
|
1434
|
+
))
|
|
1435
|
+
},
|
|
1436
|
+
},
|
|
1186
1437
|
{
|
|
1187
1438
|
title: "Cache: Debug Skills Detection",
|
|
1188
1439
|
value: "cache.debug-skills",
|