@feiyang666/dsh-usage-plugin 1.11.1 → 1.13.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/CHANGELOG.md +268 -205
- package/README.md +311 -308
- package/README.zh.md +4 -1
- package/lib/balance.js +117 -17
- package/lib/client.js +518 -266
- package/lib/index.js +176 -3
- package/package.json +80 -77
- package/scripts/release.mjs +6 -4
package/lib/client.js
CHANGED
|
@@ -7,6 +7,114 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
var React = require("react");
|
|
8
8
|
var el = React.createElement;
|
|
9
9
|
|
|
10
|
+
// ── i18n: zh keys → en dictionary (community addition) ──
|
|
11
|
+
var __T_EN = {"未知模型":"Unknown model","未知服务商":"Unknown provider","完成":"Done","工具调用":"Tool call","超长":"Overlong","错误":"Error","自动(生效前基础价 · 生效后峰谷价)":"Auto (base price before effective date · peak/valley after)","基础价格":"Base price","峰谷价格":"Peak/valley price","自动":"Auto","canvas 2d 不可用":"canvas 2D unavailable","用量与消耗报告":"Usage & Cost Report","生成时间 ":"Generated ","(北京) · 共 ":" (Beijing) · total "," 条调用 · 缓存命中率 ":" calls · cache-hit rate "," · 高峰价消耗 ":" · peak-rate cost "," · 空闲价消耗 ":" · off-peak cost "," · 合计 ":" · total "," · 报告仅含最近 ":" · report limited to the latest "," 条":" records","调用次数":"Calls","输入 · 未命中":"Input · miss","输入 · 缓存命中":"Input · cache hit","输出":"Output","高峰消耗":"Peak cost","空闲消耗":"Off-peak cost","总消耗 (峰谷价)":"Total cost (peak/valley)","缓存命中列表(共 ":"Cache hit list ("," 条)":" records)","时间(北京)":"Time (Beijing)","模型":"Model","输入·未命中":"Input·miss","缓存命中":"Cache hit","缓存写入":"Cache write","推理":"Reasoning","命中率":"Hit rate","时段":"Period","结束":"End","消耗(峰谷)":"Cost (peak/valley)","峰":"Peak","谷":"Valley","消耗表(按模型 · 峰谷价 · 高峰/空闲分列)":"Cost table (by model · peak/valley prices)","调用":"Calls","总消耗":"Total cost","合计":"Total","消耗表(按 API 服务商 × 模型 · 峰谷价)":"Cost table (by API provider × model · peak/valley)","API 服务商 / 模型":"API provider / model"," 个模型)":" models)","总费用合计":"Total cost overall","计价按请求的 API 服务商分别应用已核验价格;DigitalOcean 美元价按 USD/CNY 汇率折算;无法可靠映射的第三方价格按 ¥0。":"Billing applies verified prices per requesting API provider; DigitalOcean USD prices are converted at the USD/CNY rate; third-party prices that cannot be mapped reliably are counted as ¥0."," 个服务商":" providers","今天":"Today","近7天":"Last 7 days","近30天":"Last 30 days","全部":"All","自定义区间:":"Custom range:","至":"to","清除筛选":"Clear filters","当前范围:共 ":"Current range: ","(全部记录)":"(all records)"," · 总消耗 ":" · total cost ","高峰 ":"Peak "," · 空闲 ":" · off-peak ","命中率 ":"hit rate ","高峰时段 9:00–12:00、14:00–18:00":"Peak hours 9:00–12:00 & 14:00–18:00","其余空闲时段":"other hours are off-peak","高峰 + 空闲 · ":"Peak + off-peak · ","消耗表(按模型)":"Cost table (by model)","API 服务商":"API provider","高峰/空闲":"Peak/Off-peak","消耗明细(按 API 服务商 × 模型)":"Cost breakdown (by API provider × model)","DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。":"DeepSeek official requests use official peak/valley prices; SiliconFlow uses its own public CNY prices; DigitalOcean uses public USD prices converted to CNY at the USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0. Model names and API providers follow the actual request parameters."," 次(高峰 ":" calls (peak "," / 空闲 ":" / off-peak "," · 缓存命中 ":" · cache hit "," · 输出 ":" · output "," · 推理 ":" · reasoning "," · 空闲消耗 ":" · off-peak cost ","‹ 上月":"‹ Prev month","下月 ›":"Next month ›","按消耗":"By cost","按调用数":"By calls","热力:":"Heatmap: ","低 → 高(":"low → high (","当日消耗":"Day cost","当日调用数":"Day calls","· 悬停查看详情,点击某天查看当日调用":"· hover for details, click a day for that day's calls","本月调用":"Calls this month","本月消耗":"Cost this month","本月暂无记录。":"No records this month.","日期":"Date","总消耗(自动)":"Total cost (auto)"," 调用明细":" call details"," 条 · ":" records · "," | 空闲 ":" | off-peak "," | 合计 ":" | total ","该日无记录。":"No records for this day.","消耗":"Cost","清除区间":"Clear range"," 条 · 输入·未命中 ":" records · input·miss "," · 缓存写入 ":" · cache write "," · 高峰消耗 ":" · peak cost ","该时间范围内没有记录。":"No records in this range.","‹ 上一页":"‹ Prev page","第 ":"Page "," 页 · 每页 ":" page · per page "," 条 · 共 ":" records · total ","下一页 ›":"Next page ›","新价格表生效时间未知":"New price schedule effective date unknown","新价格表(峰谷价)将于 ":"The new peak/valley price schedule takes effect ","(北京时间)生效,当前按旧价格表(基础价)计费":" (Beijing time); currently billing on the old base-price schedule","新价格表(峰谷价)已生效(自 ":"New peak/valley schedule in effect (since "," 起)":")","今日 $ / ¥ 汇率":"Today's $/¥ rate","暂不可用":"Temporarily unavailable","汇率日期 ":"Rate date ","等待获取":"Pending fetch","汇率状态":"Rate status","缓存汇率":"Cached rate","最新可用":"Latest available","获取失败":"Fetch failed","刷新汇率":"Refresh rate","美元计价的 DigitalOcean 消耗按该 USD/CNY 汇率换算成人民币;人民币定价不受汇率影响。":"USD-denominated DigitalOcean costs are converted to CNY at this rate; CNY pricing is unaffected by it.","DeepSeek 官方 API 价格表":"DeepSeek Official API Price Table","本表仅用于 DeepSeek 官方 Provider;第三方 Provider 按下方已核验的自身价格计费,无法可靠匹配价格的模型按 0 统计。价格按官方公布固定,不可编辑。":"This table applies only to the official DeepSeek provider; third-party providers bill at their own verified prices below, and models without a reliable price match are counted as 0. Prices are fixed as officially published and are not editable.","峰谷价":"Peak/valley","基础价":"Base","自动模式:按调用时间自动选择计费档位——新价格表生效前的调用按基础价(旧价格表),生效后的调用按峰谷价(高峰时段 9:00–12:00、14:00–18:00 用高峰价,其余空闲时段用空闲价)。当前显示:":"Auto mode: the billing tier is chosen automatically by call time — calls made before the new schedule took effect use base (old) prices, calls after it use peak/valley prices (peak hours 9:00–12:00 & 14:00–18:00 at peak price, other hours at off-peak). Currently shown:","基础价表(旧价格表,新价格生效前)":"Base price table (old schedule, before new prices take effect)","峰谷价表(新价格表)":"Peak/valley table (new schedule)","。价格按官方公布固定,不可编辑。":". Prices are fixed as officially published and are not editable.","缓存命中(输入)":"Cache hit (input)","空闲 · 缓存命中":"Off-peak · cache hit","空闲 · 输入":"Off-peak · input","空闲 · 输出":"Off-peak · output","高峰 · 缓存命中":"Peak · cache hit","高峰 · 输入":"Peak · input","高峰 · 输出":"Peak · output","单位:元 / 百万 tokens。高峰时段(北京时间 9:00–12:00、14:00–18:00)用高峰价,其余空闲时段用空闲价(空闲价 = 高峰价的一半)。概览、用量日历、缓存命中列表中的消耗均已按高峰 / 空闲分列统计。":"Unit: CNY per million tokens. Peak hours (Beijing 9:00–12:00 & 14:00–18:00) use peak prices; other hours use off-peak (= half of peak). Costs in Overview, Usage Calendar and Cache Hit List are split into peak / off-peak accordingly.","第三方平台价格与覆盖状态(2026-08-19 核验)":"Third-party platform prices & coverage (verified 2026-08-19)","只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。":"Only prices that reliably match a record's provider/model are used for automatic billing; USD prices are computed in USD first, then converted to CNY at the USD/CNY rate.","模型 / 状态":"Model / status","输入":"Input","说明":"Notes","已用于自动计费":"Used for auto billing","缓存命中价按 2026-08-03 起生效公告":"Cache-hit price per the notice effective 2026-08-03","按输入价":"Billed at input price","公告未单列缓存命中价":"The notice lists no separate cache-hit price","自动按 USD/CNY 汇率换算人民币并计费":"Auto-converted to CNY at the USD/CNY rate and billed","Alibaba / 千问":"Alibaba / Qwen","暂不计费":"Not billed yet","调用与 token 正常统计;费用暂按 ¥0":"Calls and tokens tracked normally; cost counted as ¥0 for now","免费;费用固定按 ¥0":"Free; cost fixed at ¥0","DigitalOcean 使用美元官方价并按最新可用 USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud 的 DeepSeek V4 Flash 免费按 ¥0。":"DigitalOcean bills at official USD prices converted via the latest available USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0.","概览":"Overview","用量日历":"Usage Calendar","缓存命中列表":"Cache Hit List","价格表":"Price Table","已导出:":"Exported: ","导出失败:":"Export failed: ","未知错误":"unknown error","导出中…":"Exporting…","生成图片中…":"Rendering image…","画布不可用":"Canvas unavailable","生成图片失败:":"Image rendering failed: ","打开目录选择…":"Open directory picker…","导出目标:":"Export destination:","选择目录失败:":"Failed to pick directory: ","打开文件夹失败:":"Failed to open folder: ","读取文件…":"Reading file…","导入成功:新增 ":"Import OK: added "," 条,跳过重复 ":", skipped duplicates "," 条,忽略无效 ":", ignored invalid "," 条,现有共 ":", total now ","导入失败:":"Import failed: ","读取文件失败":"Failed to read file","用量与消耗":"Usage & Cost","记录插件激活后的每一次模型调用":"Logs every model call since the plugin was activated","当前 · 高峰时段":"Current · peak hours","当前 · 空闲时段":"Current · off-peak hours","新价格已生效":"New prices in effect","新价格未生效":"New prices not yet in effect","刷新":"Refresh","清空":"Clear","计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。":"Billing notes: DeepSeek official and SiliconFlow bill in CNY; DigitalOcean bills at official USD prices × USD/CNY rate; Qwen is not billed yet; AMD GPU Cloud DeepSeek V4 Flash is free at ¥0.","导出 CSV":"Export CSV","导出 JSON":"Export JSON","导出图片 (PNG)":"Export image (PNG)","打开目录":"Open folder","导出目标目录(留空 = 默认数据目录)":"Export target directory (empty = default data dir)","选择目录…":"Choose directory…","已恢复默认数据目录":"Restored default data directory","重置":"Reset","选择文件导入":"Choose a file to import","数据持久化:":"Data persistence:","(每次调用实时落盘,插件重启后自动恢复,最多保留 100000 条)":"(every call is written to disk live, restored automatically after restart, up to 100000 records)","持久化未启用:":"Persistence disabled:","未知原因":"unknown reason","DEEPSEEK_API_KEY(推理 Key)":"DEEPSEEK_API_KEY (inference key)","模型设置中 Provider ID 或显示名为 siliconflow 的提供商 API Key":"API key of the provider whose Provider ID or display name is siliconflow in model settings","打开 SiliconFlow API 密钥":"Open SiliconFlow API keys","账户级 dop_v1_ Personal Access Token;Read Only(api:read)或 billing:read":"Account-level dop_v1_ Personal Access Token; Read Only (api:read) or billing:read","创建 DigitalOcean Account API Token":"Create a DigitalOcean account API token","暂无公开余额 API,仅支持控制台查看":"No public balance API yet — console only","打开 AMD Developer Cloud":"Open AMD Developer Cloud","查询失败":"Query failed","无法读取凭据状态":"Cannot read credential status","请输入以 dop_v1_ 开头的 DigitalOcean 账户 PAT。":"Enter a DigitalOcean account PAT starting with dop_v1_.","保存失败":"Save failed","使用凭据 ":"Using credential ","来源 ":"source ","模型提供商 ":"Model provider ","端点 ":"Endpoint "," · 账户余额":" · account balance","USD · 预付款/信用余额":"USD · prepaid/credit balance","USD · 待结算金额":"USD · pending amount","USD · 当前无余额":"USD · no balance"," · 账户可用":" · account available"," · 账户不可用":" · account unavailable"," · 查询成功":" · query succeeded","剩余余额查询":"Remaining balance query","查询 DeepSeek、SiliconFlow 与 DigitalOcean 账户余额":"Query DeepSeek, SiliconFlow and DigitalOcean account balances","打开控制台":"Open console","查询中…":"Querying…","查询账单":"Query billing","查询余额":"Query balance","所需凭据:":"Required credentials:","仅支持控制台查看":"Console only","插件不会尝试未经公开文档确认的端点,也不会把 AMD 推理 Key 当作账单凭据。":"The plugin never probes endpoints unconfirmed by public docs, and never uses AMD inference keys as billing credentials.","请确认凭据类型正确、权限包含余额/账单读取,且网络可访问服务商官方 API。":"Please verify the credential type is correct, its permissions include balance/billing read, and the provider's official API is reachable from this network.","打开服务商凭据页面":"Open provider credentials page","正在查询余额…":"Querying balance…","可用":"Available","不可用":"Unavailable","账单更新时间":"Billing updated","查询时间":"Queried at","北京时间":"Beijing time","请创建账户级 Personal Access Token。可选 Read Only(api:read,包含 billing:read),或自定义 billing:read;不要使用 Gradient AI 推理 Key。":"Please create an account-level Personal Access Token. Choose Read Only (api:read incl. billing:read) or a custom scope with billing:read; do not use a Gradient AI inference key.","正在检查已保存的 Token…":"Checking saved token…","已保存:":"Saved: ","(来源:":" (source: ","无法读取 Token 状态:":"Cannot read token status: ","尚未保存 DIGITALOCEAN_TOKEN。":"DIGITALOCEAN_TOKEN is not saved yet.","输入新的 dop_v1_ Token 可替换(当前值已隐藏)":"Enter a new dop_v1_ token to replace it (current value hidden)","保存中…":"Saving…","保存并查询":"Save & query","当前 Token 来自只读环境变量;请在原来源中修改,页面不会覆盖它。":"The current token comes from a read-only environment variable; edit it at its source — this panel will not overwrite it.","Token 已安全保存;页面和 API 响应不会回传明文。":"Token stored securely; neither the page nor API responses echo it back.","SiliconFlow 查询说明":"SiliconFlow query notes","公开 API 已成功返回 ¥0.00。SiliconFlow 的 /v1/user/info 当前不返回代金券或历史用量;控制台可用总额可能非零。这里忠实展示 API 原始余额字段,不把它等同于控制台完整额度。":"The public API returned ¥0.00 successfully. SiliconFlow's /v1/user/info currently returns no vouchers or historical usage; the console total may still be non-zero. The raw balance field from the API is shown faithfully here and should not be read as the full console credit.","余额卡片展示 SiliconFlow /v1/user/info 公开 API 返回的原始余额字段。":"The balance card shows the raw balance field returned by SiliconFlow's public /v1/user/info API.","返回字段":"Returned fields","凭据读取规则":"Credential lookup rules","插件只检查“设置 → 模型”中 Provider ID 或显示名为 siliconflow 的提供商,并读取其 apiKeyEnv 对应的已保存 API Key。":"The plugin only checks providers in Settings → Models whose Provider ID or display name is siliconflow, and reads the saved API key referenced by their apiKeyEnv.","如果未找到提供商、未填写 API Key 或凭据引用失效,插件会停止查询并说明需要修复的配置;不会回退到其他服务商的 Key。":"If no such provider exists, the key is empty, or the credential reference is broken, the plugin stops querying and explains what needs fixing; it never falls back to another provider's key.","DigitalOcean 查询的是主账户 Billing API;DigitalOcean AI 推理 Key 与 AMD GPU Cloud 推理 Key 均不能直接查询账单。":"DigitalOcean queries the main account Billing API; DigitalOcean AI inference keys and AMD GPU Cloud inference keys cannot query billing directly.","\n调用 ":"\ncalls ",")\n输入·未命中 ":")\ninput·miss ","\n高峰消耗 ":"\npeak cost ","\n无记录":"\nno records","查询 DeepSeek、SiliconFlow、DigitalOcean 与百炼 Token Plan":"Query DeepSeek, SiliconFlow, DigitalOcean & Bailian Token Plan balances","百炼 Token Plan · 1 周配额":"Bailian Token Plan · 1-week quota","百炼 Token Plan 配额用量":"Bailian Token Plan quota usage","百炼 Token Plan":"Bailian Token Plan","本周配额":"This week's quota","开始":"Start","结束":"End","已用 ":"Used ","百炼 Token Plan 查询说明":"Bailian Token Plan query notes","本查询复用百炼 CLI(bl)的“控制台登录”OAuth token,不需要阿里云 AccessKey,权限面很小。":"This query reuses the console-login OAuth token from the Bailian CLI (bl); no Alibaba Cloud AccessKey is needed and the permission scope is minimal.","如尚未登录,请先在终端执行:bl auth login --console,然后回到本页点“查询余额”。":"If you haven't logged in yet, run: bl auth login --console, then come back and click 'Query balance'.","打开百炼 CLI 登录说明":"Open Bailian CLI login docs","百炼控制台 OAuth 登录(~/.bailian/config.json 的 access_token,用 bl auth login --console 生成)":"Bailian console OAuth login (access_token in ~/.bailian/config.json, created by bl auth login --console)","数据来自阿里云百炼控制台内部门户网关(zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage),展示的是 1 周配额已用进度与周期。":"Data from Alibaba Cloud Bailian console portal gateway (zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage), showing 1-week quota usage progress and period."};
|
|
12
|
+
|
|
13
|
+
function __detectLang() {
|
|
14
|
+
try { var v = window.localStorage.getItem("dsh-usage-lang"); if (v === "zh" || v === "en") return v; } catch (e) {}
|
|
15
|
+
try { var nav = (typeof navigator !== "undefined" && navigator.language) || "en"; return nav.toLowerCase().indexOf("zh") === 0 ? "zh" : "en"; } catch (e2) { return "en"; }
|
|
16
|
+
}
|
|
17
|
+
var __LANG = __detectLang();
|
|
18
|
+
var t = function (s) { if (__LANG !== "en") return s; return __T_EN[s] || s; };
|
|
19
|
+
// host sends semantic keys for balance presentation text; both locales live client-side
|
|
20
|
+
var __BAL_ZH = {
|
|
21
|
+
"hint.deepseek": "DEEPSEEK_API_KEY(推理 Key)",
|
|
22
|
+
"hint.siliconflow": "模型设置中名为 siliconflow 的提供商所引用的 API Key",
|
|
23
|
+
"hint.digitalocean": "DIGITALOCEAN_TOKEN(账户级 Personal Access Token,不是 DO AI 推理 Key)",
|
|
24
|
+
"hint.amd": "AMD GPU Cloud 当前未公开余额查询端点",
|
|
25
|
+
"toppedUp": "充值余额",
|
|
26
|
+
"granted": "赠送余额",
|
|
27
|
+
"grantedLegacy": "赠送/旧免费余额",
|
|
28
|
+
"actualTopUp": "实际充值",
|
|
29
|
+
"platformGift": "平台赠送",
|
|
30
|
+
"field.chargeBalance": "chargeBalance · 用户充值余额",
|
|
31
|
+
"field.balance": "balance · 旧版赠送余额字段",
|
|
32
|
+
"field.monthToDateUsage": "month_to_date_usage · 当前账期使用金额",
|
|
33
|
+
"meaning.totalBalance": "公开 API 返回的总余额,通常为 balance 与 chargeBalance 的合计",
|
|
34
|
+
"meaning.chargeBalance": "用户充值形成的余额",
|
|
35
|
+
"meaning.balance": "赠送或旧版免费余额字段",
|
|
36
|
+
"bal.credit": "可用信用余额",
|
|
37
|
+
"bal.due": "待结算账户余额",
|
|
38
|
+
"bal.settled": "账户余额",
|
|
39
|
+
"monthToDateUsage": "本月至今使用",
|
|
40
|
+
"note.deepseek": "数据来自 DeepSeek 官方 /user/balance 接口。",
|
|
41
|
+
"note.siliconflow": "数据来自 SiliconFlow 官方 /v1/user/info 接口;该接口未公开代金券或历史用量字段,数值可能与控制台可用总额不同。",
|
|
42
|
+
"note.digitalocean": "数据来自 DigitalOcean 账户级 Billing API;负数账户余额按可用信用额取绝对值展示,DO AI 推理 Key 不能用于此查询。"
|
|
43
|
+
};
|
|
44
|
+
var __BAL_EN = {
|
|
45
|
+
"hint.deepseek": "DEEPSEEK_API_KEY (inference key)",
|
|
46
|
+
"hint.siliconflow": "API key referenced by the provider named siliconflow in model settings",
|
|
47
|
+
"hint.digitalocean": "DIGITALOCEAN_TOKEN (account-level Personal Access Token, not a DO AI inference key)",
|
|
48
|
+
"hint.amd": "AMD GPU Cloud has no public balance endpoint yet",
|
|
49
|
+
"toppedUp": "Topped-up balance",
|
|
50
|
+
"granted": "Granted balance",
|
|
51
|
+
"grantedLegacy": "Granted (legacy free) balance",
|
|
52
|
+
"actualTopUp": "Actual top-up amount",
|
|
53
|
+
"platformGift": "Granted by the platform",
|
|
54
|
+
"field.chargeBalance": "chargeBalance · user topped-up balance",
|
|
55
|
+
"field.balance": "balance · legacy granted/free-balance field",
|
|
56
|
+
"field.monthToDateUsage": "month_to_date_usage · usage this billing period",
|
|
57
|
+
"meaning.totalBalance": "Total balance returned by the public API; usually balance plus chargeBalance",
|
|
58
|
+
"meaning.chargeBalance": "Balance formed by user top-ups",
|
|
59
|
+
"meaning.balance": "Granted or legacy free-balance field",
|
|
60
|
+
"bal.credit": "Available credit balance",
|
|
61
|
+
"bal.due": "Outstanding account balance",
|
|
62
|
+
"bal.settled": "Account balance",
|
|
63
|
+
"monthToDateUsage": "Month-to-date usage",
|
|
64
|
+
"note.deepseek": "Data from the official DeepSeek /user/balance endpoint.",
|
|
65
|
+
"note.siliconflow": "Data from the official SiliconFlow /v1/user/info endpoint; it exposes no voucher or historical-usage fields, so values may differ from the console total.",
|
|
66
|
+
"note.digitalocean": "Data from the DigitalOcean account-level Billing API; a negative account balance is shown as available credit, and DO AI inference keys cannot be used for this query."
|
|
67
|
+
};
|
|
68
|
+
var tb = function (k) { var pack = __LANG === "en" ? __BAL_EN : __BAL_ZH; return pack[k] || k; };
|
|
69
|
+
var __msgText = function (m) {
|
|
70
|
+
if (!m) return "";
|
|
71
|
+
if (typeof m === "string") return m;
|
|
72
|
+
if (m.seq) {
|
|
73
|
+
var acc = "";
|
|
74
|
+
for (var si = 0; si < m.seq.length; si++) {
|
|
75
|
+
var part = m.seq[si];
|
|
76
|
+
acc += (part && typeof part === "object" && part.k !== undefined) ? t(part.k) : String(part);
|
|
77
|
+
}
|
|
78
|
+
return acc;
|
|
79
|
+
}
|
|
80
|
+
var base = m.k ? t(m.k) : (m.raw || "");
|
|
81
|
+
if (m.tail !== undefined && m.tail !== null) base += String(m.tail);
|
|
82
|
+
return base;
|
|
83
|
+
};
|
|
84
|
+
var __msgText = function (m) {
|
|
85
|
+
if (!m) return "";
|
|
86
|
+
if (typeof m === "string") return m;
|
|
87
|
+
if (m.seq) {
|
|
88
|
+
var acc = "";
|
|
89
|
+
for (var si = 0; si < m.seq.length; si++) {
|
|
90
|
+
var part = m.seq[si];
|
|
91
|
+
acc += (part && typeof part === "object" && part.k !== undefined) ? t(part.k) : String(part);
|
|
92
|
+
}
|
|
93
|
+
return acc;
|
|
94
|
+
}
|
|
95
|
+
var base = m.k ? t(m.k) : (m.raw || "");
|
|
96
|
+
if (m.tail !== undefined && m.tail !== null) base += String(m.tail);
|
|
97
|
+
return base;
|
|
98
|
+
};
|
|
99
|
+
var __langListeners = [];
|
|
100
|
+
var setLang = function (lang) {
|
|
101
|
+
var next = lang === "zh" ? "zh" : "en";
|
|
102
|
+
if (next === __LANG) return __LANG;
|
|
103
|
+
__LANG = next;
|
|
104
|
+
try { window.localStorage.setItem("dsh-usage-lang", __LANG); } catch (e) {}
|
|
105
|
+
for (var i = 0; i < __langListeners.length; i++) { try { __langListeners[i](__LANG); } catch (e2) {} }
|
|
106
|
+
return __LANG;
|
|
107
|
+
};
|
|
108
|
+
function useLang() {
|
|
109
|
+
var st = React.useState(__LANG);
|
|
110
|
+
React.useEffect(function () {
|
|
111
|
+
var fn = function () { st[1](__LANG); };
|
|
112
|
+
__langListeners.push(fn);
|
|
113
|
+
return function () { var idx = __langListeners.indexOf(fn); if (idx >= 0) __langListeners.splice(idx, 1); };
|
|
114
|
+
}, []);
|
|
115
|
+
return st[0];
|
|
116
|
+
}
|
|
117
|
+
|
|
10
118
|
// ── inline styles (replaces a stylesheet; portable across client plugins) ──
|
|
11
119
|
// 字号用 em 表示,随宿主“显示大小”设置的字体基准自动缩放(继承应用字号);
|
|
12
120
|
// 间距保持 px,仅微调以适配更大的字号。标注的 px 是默认 13px 基准下的设计值。
|
|
@@ -40,7 +148,9 @@ window.__ModuleLoader__.load({
|
|
|
40
148
|
scroll: { overflowX: "auto", overflowY: "hidden", width: "100%", maxWidth: "100%", minWidth: 0 },
|
|
41
149
|
// 表格宽度自适应容器(width:100% + 列内容允许换行),不再用 max-content 撑出横向滚动条;
|
|
42
150
|
// 极窄窗口下容器仍保留 overflow-x 兜底。
|
|
43
|
-
|
|
151
|
+
// minWidth:720 直接写在内联样式里(内联样式必然生效),不依赖下方注入的全局样式表:
|
|
152
|
+
// 窄屏时表格保持 720px、外层 st.scroll 容器横向滚动,避免 9–11 列被压到逐字竖排。
|
|
153
|
+
tbl: { width: "100%", minWidth: 720, borderCollapse: "collapse", fontSize: fs(12) },
|
|
44
154
|
th: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
|
|
45
155
|
thFirst: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
|
|
46
156
|
td: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
|
|
@@ -69,6 +179,27 @@ window.__ModuleLoader__.load({
|
|
|
69
179
|
heroLabel: { fontSize: fs(12), opacity: 0.85 },
|
|
70
180
|
heroValue: { fontSize: fs(32), fontWeight: 700, letterSpacing: 0.5 },
|
|
71
181
|
heroCurrency: { fontSize: fs(12), opacity: 0.9 },
|
|
182
|
+
// ── 百炼 Token Plan 纯进度条样式(白色填充,蓝色 hero 高对比)──
|
|
183
|
+
qwenHero: { display: "flex", flexDirection: "column", alignItems: "stretch", gap: 10, width: "100%" },
|
|
184
|
+
qwenBarWrap: { width: "100%" },
|
|
185
|
+
qwenBar: { width: "100%", height: 16, borderRadius: 999, background: "rgba(0,0,0,.22)", overflow: "hidden", position: "relative", boxShadow: "inset 0 1px 2px rgba(0,0,0,.25)" },
|
|
186
|
+
qwenBarFill: { height: "100%", borderRadius: 999, background: "linear-gradient(90deg,#ffffff,#d7ecff)", transition: "width .4s ease", boxShadow: "0 0 6px rgba(255,255,255,.35)" },
|
|
187
|
+
qwenHeroTitle: { fontSize: fs(15), fontWeight: 600, opacity: 0.95 },
|
|
188
|
+
qwenHeroUsed: { fontSize: fs(15), fontWeight: 700, textAlign: "center", letterSpacing: 0.5 },
|
|
189
|
+
// ── 本周配额卡片:连续线段 + 今天节点 ──
|
|
190
|
+
qwenWeekCard: { border: "1px solid rgba(90,140,255,.25)", background: "rgba(90,140,255,.06)", borderRadius: 10, padding: "14px 16px", marginTop: 14 },
|
|
191
|
+
qwenWeekLabel: { fontSize: fs(11), opacity: 0.6, marginBottom: 8, fontWeight: 600 },
|
|
192
|
+
qwenWeekRow: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "nowrap" },
|
|
193
|
+
qwenWeekItem: { display: "flex", flexDirection: "column", gap: 2, flexShrink: 0 },
|
|
194
|
+
qwenWeekItemR: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 2, flexShrink: 0 },
|
|
195
|
+
qwenWeekHint: { fontSize: fs(10), opacity: 0.55 },
|
|
196
|
+
qwenWeekVal: { fontSize: fs(14), fontWeight: 600 },
|
|
197
|
+
qwenWeekLine: { position: "relative", flex: "1 1 auto", minWidth: 80, height: 24, display: "flex", alignItems: "center", margin: "0 4px" },
|
|
198
|
+
qwenWeekTrack: { position: "relative", width: "100%", height: 5, borderRadius: 999, background: "rgba(90,140,255,.18)" },
|
|
199
|
+
qwenWeekProgress: { position: "absolute", left: 0, top: 0, bottom: 0, width: "0%", borderRadius: 999, background: "linear-gradient(90deg,#3d6bd6,#5a8cff)", boxShadow: "0 0 6px rgba(90,140,255,.35)", transition: "width .4s ease" },
|
|
200
|
+
qwenWeekToday: { position: "absolute", top: "50%", transform: "translate(-50%,-50%)", display: "flex", flexDirection: "column", alignItems: "center", gap: 3, background: "#15171c", padding: "3px 8px", borderRadius: 999, border: "1px solid rgba(90,140,255,.45)", whiteSpace: "nowrap", zIndex: 2, transition: "left .4s ease" },
|
|
201
|
+
qwenWeekTodayDot: { width: 7, height: 7, borderRadius: "50%", background: "#5a8cff", boxShadow: "0 0 6px rgba(90,140,255,.6)" },
|
|
202
|
+
qwenWeekTodayDate: { fontSize: fs(11), fontWeight: 600, color: "#c7d8ff", lineHeight: 1 },
|
|
72
203
|
calBar: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 10 },
|
|
73
204
|
calGrid: { display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 4, marginTop: 6 },
|
|
74
205
|
calWk: { textAlign: "center", fontSize: fs(11), opacity: 0.5, padding: "2px 0" },
|
|
@@ -92,7 +223,14 @@ window.__ModuleLoader__.load({
|
|
|
92
223
|
if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-plugin-style")) {
|
|
93
224
|
var styleSheetEl = document.createElement("style");
|
|
94
225
|
styleSheetEl.id = "dsh-usage-plugin-style";
|
|
95
|
-
styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}"
|
|
226
|
+
styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}" +
|
|
227
|
+
// 所有用量表格:保证最小宽度,避免在窄屏被压缩导致标题/单元格逐字竖排;
|
|
228
|
+
// 外层 st.scroll 容器已设 overflow-x:auto,无法完整展示时可横向滚动。
|
|
229
|
+
".dsh-usage-table{min-width:720px}" +
|
|
230
|
+
// 极窄屏 (≤ 540px):对带 collapse-mobile 的宽表隐藏第 4 列至倒数第 2 列,
|
|
231
|
+
// 只保留前 3 个主标识列 + 最后合计列(如 消耗表/明细表 保留 模型/服务商/调用/总消耗),
|
|
232
|
+
// 避免每列被压到极限宽度而逐字竖排。价格表等窄表不折叠,保留横向滚动。
|
|
233
|
+
"@media (max-width:540px){.dsh-usage-table.collapse-mobile tr>*:not(:nth-child(-n+3)):not(:last-child){display:none}.dsh-usage-table.collapse-mobile{min-width:320px}}";
|
|
96
234
|
document.head.appendChild(styleSheetEl);
|
|
97
235
|
}
|
|
98
236
|
|
|
@@ -102,7 +240,7 @@ window.__ModuleLoader__.load({
|
|
|
102
240
|
var fmtTime = function (ts) { var d = new Date(ts + 8 * 3600 * 1000); return pad2(d.getUTCMonth() + 1) + "-" + pad2(d.getUTCDate()) + " " + pad2(d.getUTCHours()) + ":" + pad2(d.getUTCMinutes()) + ":" + pad2(d.getUTCSeconds()); };
|
|
103
241
|
var fmtMoney = function (n) { if (!n) return "¥0.0000"; if (n < 0.0001) return "¥" + n.toExponential(2); if (n < 1) return "¥" + n.toFixed(4); return "¥" + n.toFixed(2); };
|
|
104
242
|
var fmtPrice = function (n) { if (n == null || isNaN(n)) return "—"; return String(parseFloat(Number(n).toFixed(4))); };
|
|
105
|
-
var modelLabel = function (mk) { if (mk === "deepseek-v4-flash") return "deepseek-v4-flash"; if (mk === "deepseek-v4-flash-vision-exp") return "deepseek-v4-flash-vision-exp"; if (mk === "deepseek-v4-pro") return "deepseek-v4-pro"; return "未知模型"; };
|
|
243
|
+
var modelLabel = function (mk) { if (mk === "deepseek-v4-flash") return "deepseek-v4-flash"; if (mk === "deepseek-v4-flash-vision-exp") return "deepseek-v4-flash-vision-exp"; if (mk === "deepseek-v4-pro") return "deepseek-v4-pro"; return t("未知模型"); };
|
|
106
244
|
// 模型显示名:以请求参数里的真实模型名为准(非 DeepSeek 模型也如实显示),
|
|
107
245
|
// 空模型名时退回已知档位名 / provider / 未知。
|
|
108
246
|
var modelName = function (r) {
|
|
@@ -111,7 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
111
249
|
var mv = r && r.modelKey;
|
|
112
250
|
if (mv && mv !== "unknown") return modelLabel(mv);
|
|
113
251
|
var p = String((r && r.provider) || "").trim();
|
|
114
|
-
return p ? p : "未知模型";
|
|
252
|
+
return p ? p : t("未知模型");
|
|
115
253
|
};
|
|
116
254
|
// 模型分组键:真实模型名小写(用于把同名的记录归为一组展示)
|
|
117
255
|
var modelGroupKey = function (r) {
|
|
@@ -125,13 +263,13 @@ window.__ModuleLoader__.load({
|
|
|
125
263
|
// API 服务商显示名 / 分组键
|
|
126
264
|
var providerName = function (p) {
|
|
127
265
|
var s = String(p == null ? "" : p).trim();
|
|
128
|
-
return s || "未知服务商";
|
|
266
|
+
return s || t("未知服务商");
|
|
129
267
|
};
|
|
130
268
|
var providerGroupKey = function (p) {
|
|
131
269
|
var s = String(p == null ? "" : p).trim();
|
|
132
270
|
return s ? s.toLowerCase() : "unknown";
|
|
133
271
|
};
|
|
134
|
-
var finishLabel = function (f) { if (f === "stop") return "完成"; if (f === "tool-calls") return "工具调用"; if (f === "max-tokens") return "超长"; if (f === "error") return "错误"; return f || "—"; };
|
|
272
|
+
var finishLabel = function (f) { if (f === "stop") return t("完成"); if (f === "tool-calls") return t("工具调用"); if (f === "max-tokens") return t("超长"); if (f === "error") return t("错误"); return f || "—"; };
|
|
135
273
|
var pct = function (a, b) { return b > 0 ? (a / b * 100).toFixed(1) + "%" : "—"; };
|
|
136
274
|
// 周末统一按空闲价规则生效时间:北京时间 2026-08-23 00:00(周日)。此前(含周末)仍按原峰谷分段。
|
|
137
275
|
var WEEKEND_FLAT_AT = Date.parse("2026-08-23T00:00:00+08:00");
|
|
@@ -163,14 +301,14 @@ window.__ModuleLoader__.load({
|
|
|
163
301
|
// 计费档位选择:auto 用 autoCost(按生效日期自动切换),base 用 baseCost,其余用 peakValleyCost
|
|
164
302
|
// 计费档位显示文案
|
|
165
303
|
var regimeLabel = function (regime) {
|
|
166
|
-
if (regime === "auto") return "自动(生效前基础价 · 生效后峰谷价)";
|
|
167
|
-
if (regime === "base") return "基础价格";
|
|
168
|
-
return "峰谷价格";
|
|
304
|
+
if (regime === "auto") return t("自动(生效前基础价 · 生效后峰谷价)");
|
|
305
|
+
if (regime === "base") return t("基础价格");
|
|
306
|
+
return t("峰谷价格");
|
|
169
307
|
};
|
|
170
308
|
var costHint = function (regime) {
|
|
171
|
-
if (regime === "auto") return "自动";
|
|
172
|
-
if (regime === "base") return "基础价格";
|
|
173
|
-
return "峰谷价格";
|
|
309
|
+
if (regime === "auto") return t("自动");
|
|
310
|
+
if (regime === "base") return t("基础价格");
|
|
311
|
+
return t("峰谷价格");
|
|
174
312
|
};
|
|
175
313
|
var costOf = function (r, regime) {
|
|
176
314
|
if (!r) return 0;
|
|
@@ -201,7 +339,18 @@ window.__ModuleLoader__.load({
|
|
|
201
339
|
};
|
|
202
340
|
var bjKey = function (ts) { var d = new Date(ts + 8 * 3600 * 1000); return d.getUTCFullYear() + "-" + pad2(d.getUTCMonth() + 1) + "-" + pad2(d.getUTCDate()); };
|
|
203
341
|
var bjStartMs = function (key) { var p = String(key).split("-"); return Date.UTC(+p[0], +p[1] - 1, +p[2]) - 8 * 3600 * 1000; };
|
|
204
|
-
var
|
|
342
|
+
var __MONTHS_EN = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
343
|
+
var __MONTHS_FULL_EN = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
344
|
+
var __WD_ZH = ["一", "二", "三", "四", "五", "六", "日"];
|
|
345
|
+
var __WD_EN = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
|
346
|
+
var weekdayLabels = function () { return __LANG === "en" ? __WD_EN : __WD_ZH; };
|
|
347
|
+
var fmtMonthLabel = function (y, m1) { return __LANG === "en" ? __MONTHS_FULL_EN[m1 - 1] + " " + y : y + "年" + m1 + "月"; };
|
|
348
|
+
var dayLabel = function (key) {
|
|
349
|
+
var p = String(key).split("-");
|
|
350
|
+
if (__LANG === "en") return __MONTHS_EN[(+p[1]) - 1] + " " + (+p[2]) + ", " + p[0];
|
|
351
|
+
return p[0] + "年" + (+p[1]) + "月" + (+p[2]) + "日";
|
|
352
|
+
};
|
|
353
|
+
var dailyStatsTitle = function (y, m1) { return __LANG === "en" ? "Daily stats (" + __MONTHS_FULL_EN[m1 - 1] + " " + y + ")" : "每日统计(" + y + "年" + m1 + "月)"; };
|
|
205
354
|
var monthDays = function (y, m) { return new Date(Date.UTC(y, m + 1, 0)).getUTCDate(); };
|
|
206
355
|
var monthOffset = function (y, m) { var ms = bjStartMs(y + "-" + pad2(m + 1) + "-01"); var wd = new Date(ms).getUTCDay(); return (wd + 6) % 7; };
|
|
207
356
|
var fmtBalance = function (s) {
|
|
@@ -213,7 +362,12 @@ window.__ModuleLoader__.load({
|
|
|
213
362
|
return fmtInt(parseInt(parts[0], 10)) + "." + (dec.length === 1 ? dec + "0" : dec);
|
|
214
363
|
};
|
|
215
364
|
var currencySymbol = function (currency) { return currency === "CNY" ? "¥" : currency === "USD" ? "$" : (currency || ""); };
|
|
216
|
-
|
|
365
|
+
var fmtShortDate = function (iso) {
|
|
366
|
+
if (!iso) return "—";
|
|
367
|
+
var p = String(iso).split("-");
|
|
368
|
+
return (p[1] || "") + "." + (p[2] || "");
|
|
369
|
+
};
|
|
370
|
+
var api = function (payload) {
|
|
217
371
|
return fetch("/usage/api", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then(function (r) { return r.json(); });
|
|
218
372
|
};
|
|
219
373
|
var PRICE_MODELS = ["deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-v4-pro"];
|
|
@@ -319,7 +473,7 @@ window.__ModuleLoader__.load({
|
|
|
319
473
|
canvas.width = W * scale;
|
|
320
474
|
canvas.height = H * scale;
|
|
321
475
|
var ctx = canvas.getContext("2d");
|
|
322
|
-
if (!ctx) throw new Error("canvas 2d 不可用");
|
|
476
|
+
if (!ctx) throw new Error(t("canvas 2d 不可用"));
|
|
323
477
|
ctx.scale(scale, scale);
|
|
324
478
|
ctx.textBaseline = "alphabetic";
|
|
325
479
|
var font = function (size, weight) { ctx.font = (weight || "400") + " " + size + 'px "Segoe UI","Microsoft YaHei",sans-serif'; };
|
|
@@ -327,18 +481,18 @@ window.__ModuleLoader__.load({
|
|
|
327
481
|
ctx.fillStyle = "#ffffff"; ctx.fillRect(0, 0, W, H);
|
|
328
482
|
ctx.fillStyle = "#3a7bd5"; ctx.fillRect(0, 0, W, 64);
|
|
329
483
|
ctx.fillStyle = "#ffffff"; ctx.textAlign = "left"; font(20, "700");
|
|
330
|
-
ctx.fillText("用量与消耗报告", P, 40);
|
|
484
|
+
ctx.fillText(t("用量与消耗报告"), P, 40);
|
|
331
485
|
ctx.fillStyle = "#7a8699"; font(13, "400");
|
|
332
|
-
ctx.fillText("生成时间 " + fmtTime(Date.now()) + "(北京) · 共 " + count + " 条调用 · 缓存命中率 " + hitRate +
|
|
333
|
-
" · 高峰价消耗 " + fmtMoney(totalPeakCost) + " · 空闲价消耗 " + fmtMoney(totalOffCost) + " · 合计 " + fmtMoney(totalCost) +
|
|
334
|
-
(totalIn > count ? " · 报告仅含最近 " + count + " 条" : ""), P, 92);
|
|
486
|
+
ctx.fillText(t("生成时间 ") + fmtTime(Date.now()) + t("(北京) · 共 ") + count + t(" 条调用 · 缓存命中率 ") + hitRate +
|
|
487
|
+
t(" · 高峰价消耗 ") + fmtMoney(totalPeakCost) + t(" · 空闲价消耗 ") + fmtMoney(totalOffCost) + t(" · 合计 ") + fmtMoney(totalCost) +
|
|
488
|
+
(totalIn > count ? t(" · 报告仅含最近 ") + count + t(" 条") : ""), P, 92);
|
|
335
489
|
|
|
336
490
|
var cardW = (W - 2 * P - 6 * 10) / 7;
|
|
337
491
|
var cards = [
|
|
338
|
-
{ l: "调用次数", v: fmtInt(count) }, { l: "输入 · 未命中", v: fmtInt(totalMiss) },
|
|
339
|
-
{ l: "输入 · 缓存命中", v: fmtInt(totalHit) }, { l: "输出", v: fmtInt(totalOut) },
|
|
340
|
-
{ l: "高峰消耗", v: fmtMoney(totalPeakCost) }, { l: "空闲消耗", v: fmtMoney(totalOffCost) },
|
|
341
|
-
{ l: "总消耗 (峰谷价)", v: fmtMoney(totalCost) }
|
|
492
|
+
{ l: t("调用次数"), v: fmtInt(count) }, { l: t("输入 · 未命中"), v: fmtInt(totalMiss) },
|
|
493
|
+
{ l: t("输入 · 缓存命中"), v: fmtInt(totalHit) }, { l: t("输出"), v: fmtInt(totalOut) },
|
|
494
|
+
{ l: t("高峰消耗"), v: fmtMoney(totalPeakCost) }, { l: t("空闲消耗"), v: fmtMoney(totalOffCost) },
|
|
495
|
+
{ l: t("总消耗 (峰谷价)"), v: fmtMoney(totalCost) }
|
|
342
496
|
];
|
|
343
497
|
for (var c = 0; c < cards.length; c++) {
|
|
344
498
|
var x = P + c * (cardW + 10);
|
|
@@ -350,16 +504,16 @@ window.__ModuleLoader__.load({
|
|
|
350
504
|
|
|
351
505
|
var c1 = { time: P, model: P + 150, missR: P + 440, hitR: P + 540, writeR: P + 630, outR: P + 720, reasonR: P + 810, rateR: P + 900, periodR: P + 990, endR: P + 1080, costR: P + 1440 };
|
|
352
506
|
var y = 226;
|
|
353
|
-
ctx.fillStyle = "#1c2733"; font(15, "600"); ctx.fillText("缓存命中列表(共 " + count + " 条)", P, y);
|
|
507
|
+
ctx.fillStyle = "#1c2733"; font(15, "600"); ctx.fillText(t("缓存命中列表(共 ") + count + t(" 条)"), P, y);
|
|
354
508
|
y = 244;
|
|
355
509
|
ctx.fillStyle = "#eef1f5"; ctx.fillRect(P, y, W - 2 * P, headH);
|
|
356
510
|
ctx.fillStyle = "#55617a"; font(fBase + 1, "600");
|
|
357
511
|
var hty = y + Math.round(headH * 0.65);
|
|
358
|
-
ctx.textAlign = "left"; ctx.fillText("时间(北京)", c1.time, hty); ctx.fillText("模型", c1.model, hty);
|
|
512
|
+
ctx.textAlign = "left"; ctx.fillText(t("时间(北京)"), c1.time, hty); ctx.fillText(t("模型"), c1.model, hty);
|
|
359
513
|
ctx.textAlign = "right";
|
|
360
|
-
ctx.fillText("输入·未命中", c1.missR, hty); ctx.fillText("缓存命中", c1.hitR, hty); ctx.fillText("缓存写入", c1.writeR, hty);
|
|
361
|
-
ctx.fillText("输出", c1.outR, hty); ctx.fillText("推理", c1.reasonR, hty); ctx.fillText("命中率", c1.rateR, hty);
|
|
362
|
-
ctx.fillText("时段", c1.periodR, hty); ctx.fillText("结束", c1.endR, hty); ctx.fillText("消耗(峰谷)", c1.costR, hty);
|
|
514
|
+
ctx.fillText(t("输入·未命中"), c1.missR, hty); ctx.fillText(t("缓存命中"), c1.hitR, hty); ctx.fillText(t("缓存写入"), c1.writeR, hty);
|
|
515
|
+
ctx.fillText(t("输出"), c1.outR, hty); ctx.fillText(t("推理"), c1.reasonR, hty); ctx.fillText(t("命中率"), c1.rateR, hty);
|
|
516
|
+
ctx.fillText(t("时段"), c1.periodR, hty); ctx.fillText(t("结束"), c1.endR, hty); ctx.fillText(t("消耗(峰谷)"), c1.costR, hty);
|
|
363
517
|
y += headH;
|
|
364
518
|
for (var r3 = 0; r3 < count; r3++) {
|
|
365
519
|
var rec = records[r3];
|
|
@@ -372,11 +526,13 @@ window.__ModuleLoader__.load({
|
|
|
372
526
|
ctx.fillText(fmtInt(miss), c1.missR, ty);
|
|
373
527
|
ctx.fillStyle = "#22a45d"; ctx.fillText(fmtInt(hit), c1.hitR, ty);
|
|
374
528
|
ctx.fillStyle = "#1c2733";
|
|
375
|
-
|
|
529
|
+
// 兼容性:DeepSeek 系 provider 不单独上报 cacheWriteTokens,历史记录该字段
|
|
530
|
+
// 可能为 0。此时用未命中 token 数(inputTokens)兜底展示,避免"缓存写入"列为空。
|
|
531
|
+
ctx.fillText(rec.cacheWriteTokens ? fmtInt(rec.cacheWriteTokens) : (rec.inputTokens ? fmtInt(rec.inputTokens) : "—"), c1.writeR, ty);
|
|
376
532
|
ctx.fillText(fmtInt(rec.outputTokens || 0), c1.outR, ty);
|
|
377
533
|
ctx.fillText(rec.reasoningTokens ? fmtInt(rec.reasoningTokens) : "—", c1.reasonR, ty);
|
|
378
534
|
ctx.fillStyle = "#55617a"; ctx.fillText(pct(hit, hit + miss), c1.rateR, ty);
|
|
379
|
-
ctx.fillStyle = rec.peak ? "#e08700" : "#3d6bd6"; ctx.fillText(rec.peak ? "峰" : "谷", c1.periodR, ty);
|
|
535
|
+
ctx.fillStyle = rec.peak ? "#e08700" : "#3d6bd6"; ctx.fillText(rec.peak ? t("峰") : t("谷"), c1.periodR, ty);
|
|
380
536
|
ctx.fillStyle = "#55617a"; ctx.fillText(finishLabel(rec.finishReason), c1.endR, ty);
|
|
381
537
|
ctx.fillStyle = rec.peak ? "#e08700" : "#3d6bd6"; ctx.fillText(fmtMoney(cost), c1.costR, ty);
|
|
382
538
|
y += rowH;
|
|
@@ -384,19 +540,19 @@ window.__ModuleLoader__.load({
|
|
|
384
540
|
|
|
385
541
|
y += 24;
|
|
386
542
|
ctx.textAlign = "left"; ctx.fillStyle = "#1c2733"; font(15, "600");
|
|
387
|
-
ctx.fillText("消耗表(按模型 · 峰谷价 · 高峰/空闲分列)", P, y);
|
|
543
|
+
ctx.fillText(t("消耗表(按模型 · 峰谷价 · 高峰/空闲分列)"), P, y);
|
|
388
544
|
y += 18;
|
|
389
545
|
var c2 = { model: P, callsR: P + 330, missR: P + 470, hitR: P + 590, outR: P + 710, reasonR: P + 840, peakR: P + 1010, offR: P + 1140, costR: P + 1440 };
|
|
390
546
|
ctx.fillStyle = "#eef1f5"; ctx.fillRect(P, y, W - 2 * P, headH);
|
|
391
547
|
ctx.fillStyle = "#55617a"; font(fBase + 1, "600");
|
|
392
548
|
var hty2 = y + Math.round(headH * 0.65);
|
|
393
|
-
ctx.textAlign = "left"; ctx.fillText("模型", c2.model, hty2);
|
|
549
|
+
ctx.textAlign = "left"; ctx.fillText(t("模型"), c2.model, hty2);
|
|
394
550
|
ctx.textAlign = "right";
|
|
395
|
-
ctx.fillText("调用", c2.callsR, hty2); ctx.fillText("输入·未命中", c2.missR, hty2); ctx.fillText("缓存命中", c2.hitR, hty2);
|
|
396
|
-
ctx.fillText("输出", c2.outR, hty2); ctx.fillText("推理", c2.reasonR, hty2);
|
|
397
|
-
ctx.fillText("高峰消耗", c2.peakR, hty2); ctx.fillText("空闲消耗", c2.offR, hty2); ctx.fillText("总消耗", c2.costR, hty2);
|
|
551
|
+
ctx.fillText(t("调用"), c2.callsR, hty2); ctx.fillText(t("输入·未命中"), c2.missR, hty2); ctx.fillText(t("缓存命中"), c2.hitR, hty2);
|
|
552
|
+
ctx.fillText(t("输出"), c2.outR, hty2); ctx.fillText(t("推理"), c2.reasonR, hty2);
|
|
553
|
+
ctx.fillText(t("高峰消耗"), c2.peakR, hty2); ctx.fillText(t("空闲消耗"), c2.offR, hty2); ctx.fillText(t("总消耗"), c2.costR, hty2);
|
|
398
554
|
y += headH;
|
|
399
|
-
var t2rows = modelRows.concat([{ key: "合计", name: "合计", calls: count, miss: totalMiss, hit: totalHit, out: totalOut, reason: totalReason, cost: totalCost, peakCost: totalPeakCost, offCost: totalOffCost }]);
|
|
555
|
+
var t2rows = modelRows.concat([{ key: "合计", name: t("合计"), calls: count, miss: totalMiss, hit: totalHit, out: totalOut, reason: totalReason, cost: totalCost, peakCost: totalPeakCost, offCost: totalOffCost }]);
|
|
400
556
|
for (var m = 0; m < t2rows.length; m++) {
|
|
401
557
|
var row = t2rows[m];
|
|
402
558
|
var isTotal = row.key === "合计";
|
|
@@ -416,15 +572,15 @@ window.__ModuleLoader__.load({
|
|
|
416
572
|
}
|
|
417
573
|
y += 24;
|
|
418
574
|
ctx.textAlign = "left"; ctx.fillStyle = "#1c2733"; font(15, "600");
|
|
419
|
-
ctx.fillText("消耗表(按 API 服务商 × 模型 · 峰谷价)", P, y);
|
|
575
|
+
ctx.fillText(t("消耗表(按 API 服务商 × 模型 · 峰谷价)"), P, y);
|
|
420
576
|
y += 18;
|
|
421
577
|
ctx.fillStyle = "#eef1f5"; ctx.fillRect(P, y, W - 2 * P, headH);
|
|
422
578
|
ctx.fillStyle = "#55617a"; font(fBase + 1, "600");
|
|
423
579
|
var hty3 = y + Math.round(headH * 0.65);
|
|
424
|
-
ctx.textAlign = "left"; ctx.fillText("API 服务商 / 模型", c2.model, hty3);
|
|
580
|
+
ctx.textAlign = "left"; ctx.fillText(t("API 服务商 / 模型"), c2.model, hty3);
|
|
425
581
|
ctx.textAlign = "right";
|
|
426
|
-
ctx.fillText("调用", c2.callsR, hty3);
|
|
427
|
-
ctx.fillText("高峰消耗", c2.peakR, hty3); ctx.fillText("空闲消耗", c2.offR, hty3); ctx.fillText("总消耗", c2.costR, hty3);
|
|
582
|
+
ctx.fillText(t("调用"), c2.callsR, hty3);
|
|
583
|
+
ctx.fillText(t("高峰消耗"), c2.peakR, hty3); ctx.fillText(t("空闲消耗"), c2.offR, hty3); ctx.fillText(t("总消耗"), c2.costR, hty3);
|
|
428
584
|
y += headH;
|
|
429
585
|
var p3n = 0;
|
|
430
586
|
for (var p3g = 0; p3g < providerGroups.length; p3g++) {
|
|
@@ -433,7 +589,7 @@ window.__ModuleLoader__.load({
|
|
|
433
589
|
ctx.fillStyle = "#e8edf5"; ctx.fillRect(P, y, W - 2 * P, rowH);
|
|
434
590
|
var ty3 = y + Math.round(rowH * 0.68);
|
|
435
591
|
ctx.textAlign = "left"; ctx.fillStyle = "#1c2733"; font(fBase, "600");
|
|
436
|
-
ctx.fillText(gpr.name + "(" + pgrp.models.length + " 个模型)", c2.model, ty3);
|
|
592
|
+
ctx.fillText(gpr.name + "(" + pgrp.models.length + t(" 个模型)"), c2.model, ty3);
|
|
437
593
|
ctx.textAlign = "right"; ctx.fillStyle = "#1c2733"; font(fBase, "600");
|
|
438
594
|
ctx.fillText(fmtInt(gpr.calls), c2.callsR, ty3);
|
|
439
595
|
ctx.fillStyle = "#e08700"; ctx.fillText(fmtMoney(gpr.peakCost), c2.peakR, ty3);
|
|
@@ -457,7 +613,7 @@ window.__ModuleLoader__.load({
|
|
|
457
613
|
ctx.fillStyle = "#eef1f5"; ctx.fillRect(P, y, W - 2 * P, rowH);
|
|
458
614
|
var ty5 = y + Math.round(rowH * 0.68);
|
|
459
615
|
ctx.textAlign = "left"; ctx.fillStyle = "#1c2733"; font(fBase, "600");
|
|
460
|
-
ctx.fillText("总费用合计", c2.model, ty5);
|
|
616
|
+
ctx.fillText(t("总费用合计"), c2.model, ty5);
|
|
461
617
|
ctx.textAlign = "right"; ctx.fillStyle = "#1c2733"; font(fBase, "600");
|
|
462
618
|
ctx.fillText(fmtInt(count), c2.callsR, ty5);
|
|
463
619
|
ctx.fillStyle = "#e08700"; ctx.fillText(fmtMoney(totalPeakCost), c2.peakR, ty5);
|
|
@@ -466,7 +622,7 @@ window.__ModuleLoader__.load({
|
|
|
466
622
|
y += rowH;
|
|
467
623
|
y += 26;
|
|
468
624
|
ctx.textAlign = "left"; ctx.fillStyle = "#98a2b3"; font(11, "400");
|
|
469
|
-
ctx.fillText("计价按请求的 API 服务商分别应用已核验价格;DigitalOcean 美元价按 USD/CNY 汇率折算;无法可靠映射的第三方价格按 ¥0。", P, y);
|
|
625
|
+
ctx.fillText(t("计价按请求的 API 服务商分别应用已核验价格;DigitalOcean 美元价按 USD/CNY 汇率折算;无法可靠映射的第三方价格按 ¥0。"), P, y);
|
|
470
626
|
}
|
|
471
627
|
|
|
472
628
|
// ── Overview view ──
|
|
@@ -539,7 +695,7 @@ window.__ModuleLoader__.load({
|
|
|
539
695
|
for (var rr = 0; rr < records.length; rr++) {
|
|
540
696
|
if (modelGroupKey(records[rr]) === modelRows[mr].key) pset[providerName(records[rr].provider)] = true;
|
|
541
697
|
}
|
|
542
|
-
modelRows[mr].providerText = Object.keys(pset).sort().join(" / ") || "未知服务商";
|
|
698
|
+
modelRows[mr].providerText = Object.keys(pset).sort().join(" / ") || t("未知服务商");
|
|
543
699
|
}
|
|
544
700
|
var providerRows = [];
|
|
545
701
|
for (var kp in byProvider) providerRows.push(byProvider[kp]);
|
|
@@ -562,7 +718,7 @@ window.__ModuleLoader__.load({
|
|
|
562
718
|
var gprov2 = g.provider;
|
|
563
719
|
pmRows.push(el("tr", { key: "g-" + gprov2.key },
|
|
564
720
|
el("td", { style: st.tdGroup }, gprov2.name),
|
|
565
|
-
el("td", { style: st.tdGroup }, "(" + g.models.length + " 个模型)"),
|
|
721
|
+
el("td", { style: st.tdGroup }, "(" + g.models.length + t(" 个模型)")),
|
|
566
722
|
el("td", { style: st.tdGroupR }, fmtInt(gprov2.calls)),
|
|
567
723
|
el("td", { style: st.tdGroupR }, el("span", { style: st.badgePeak }, fmtInt(gprov2.peakCalls)), " / ", el("span", { style: st.badgeValley }, fmtInt(gprov2.offCalls))),
|
|
568
724
|
el("td", { style: st.tdGroupR }, fmtInt(gprov2.miss)),
|
|
@@ -591,8 +747,8 @@ window.__ModuleLoader__.load({
|
|
|
591
747
|
}
|
|
592
748
|
}
|
|
593
749
|
pmRows.push(el("tr", { key: "g-total" },
|
|
594
|
-
el("td", { style: st.tdTotalFirst }, "总费用合计"),
|
|
595
|
-
el("td", { style: st.tdTotal }, fmtInt(providerRows.length) + " 个服务商"),
|
|
750
|
+
el("td", { style: st.tdTotalFirst }, t("总费用合计")),
|
|
751
|
+
el("td", { style: st.tdTotal }, fmtInt(providerRows.length) + t(" 个服务商")),
|
|
596
752
|
el("td", { style: st.tdTotal }, fmtInt(totalCalls)),
|
|
597
753
|
el("td", { style: st.tdTotal }, el("span", { style: st.badgePeak }, fmtInt(peakCalls)), " / ", el("span", { style: st.badgeValley }, fmtInt(offCalls))),
|
|
598
754
|
el("td", { style: st.tdTotal }, fmtInt(totalMiss)),
|
|
@@ -613,33 +769,33 @@ window.__ModuleLoader__.load({
|
|
|
613
769
|
return el("div", null,
|
|
614
770
|
el("div", { style: st.calBar },
|
|
615
771
|
el("div", { style: st.seg },
|
|
616
|
-
presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
|
|
772
|
+
presetBtn("today", t("今天")), presetBtn("7d", t("近7天")), presetBtn("30d", t("近30天")), presetBtn("all", t("全部"))
|
|
617
773
|
),
|
|
618
|
-
el("span", { style: st.note }, "自定义区间:"),
|
|
774
|
+
el("span", { style: st.note }, t("自定义区间:")),
|
|
619
775
|
el("input", { type: "date", style: st.dateInput, value: fromDate, onChange: function (e) { setCustomFrom(e.target.value); } }),
|
|
620
|
-
el("span", { style: st.note }, "至"),
|
|
776
|
+
el("span", { style: st.note }, t("至")),
|
|
621
777
|
el("input", { type: "date", style: st.dateInput, value: toDate, onChange: function (e) { setCustomTo(e.target.value); } }),
|
|
622
|
-
(fromDate || toDate || preset !== "all") ? el("button", { style: st.btn, onClick: function () { setFromDate(""); setToDate(""); setPreset("all"); } }, "清除筛选") : null
|
|
778
|
+
(fromDate || toDate || preset !== "all") ? el("button", { style: st.btn, onClick: function () { setFromDate(""); setToDate(""); setPreset("all"); } }, t("清除筛选")) : null
|
|
623
779
|
),
|
|
624
780
|
el("div", { style: st.note, marginTop: 8 },
|
|
625
|
-
"当前范围:共 " + fmtInt(records.length) + " 条" + (preset === "all" && !fromDate && !toDate ? "(全部记录)" : "") + " · 总消耗 " + fmtMoney(totalCost) + "(" + costHint(regime) + ")"
|
|
781
|
+
t("当前范围:共 ") + fmtInt(records.length) + t(" 条") + (preset === "all" && !fromDate && !toDate ? t("(全部记录)") : "") + t(" · 总消耗 ") + fmtMoney(totalCost) + "(" + costHint(regime) + ")"
|
|
626
782
|
),
|
|
627
783
|
el("div", { style: st.cards },
|
|
628
|
-
el(Card, { label: "调用次数", value: fmtInt(totalCalls), hint: "高峰 " + fmtInt(peakCalls) + " · 空闲 " + fmtInt(offCalls) }),
|
|
629
|
-
el(Card, { label: "输入 · 未命中", value: fmtInt(totalMiss), hint: "token" }),
|
|
630
|
-
el(Card, { label: "输入 · 缓存命中", value: fmtInt(totalHit), hint: "命中率 " + hitRate }),
|
|
631
|
-
el(Card, { label: "输出", value: fmtInt(totalOut), hint: "token" }),
|
|
632
|
-
el(Card, { label: "高峰消耗", value: fmtMoney(peakTotal), hint: "工作日高峰 9:00–12:00、14:00–18:00 · 周末全天空闲价" }),
|
|
633
|
-
el(Card, { label: "空闲消耗", value: fmtMoney(offTotal), hint: "其余空闲时段" }),
|
|
634
|
-
el(Card, { label: "总消耗", value: fmtMoney(totalCost), hint: "高峰 + 空闲 · " + regimeLabel(regime) })
|
|
784
|
+
el(Card, { label: t("调用次数"), value: fmtInt(totalCalls), hint: t("高峰 ") + fmtInt(peakCalls) + t(" · 空闲 ") + fmtInt(offCalls) }),
|
|
785
|
+
el(Card, { label: t("输入 · 未命中"), value: fmtInt(totalMiss), hint: "token" }),
|
|
786
|
+
el(Card, { label: t("输入 · 缓存命中"), value: fmtInt(totalHit), hint: t("命中率 ") + hitRate }),
|
|
787
|
+
el(Card, { label: t("输出"), value: fmtInt(totalOut), hint: "token" }),
|
|
788
|
+
el(Card, { label: t("高峰消耗"), value: fmtMoney(peakTotal), hint: t("工作日高峰 9:00–12:00、14:00–18:00 · 周末全天空闲价") }),
|
|
789
|
+
el(Card, { label: t("空闲消耗"), value: fmtMoney(offTotal), hint: t("其余空闲时段") }),
|
|
790
|
+
el(Card, { label: t("总消耗"), value: fmtMoney(totalCost), hint: t("高峰 + 空闲 · ") + regimeLabel(regime) })
|
|
635
791
|
),
|
|
636
|
-
el("div", { style: st.sec }, "消耗表(按模型)"),
|
|
792
|
+
el("div", { style: st.sec }, t("消耗表(按模型)")),
|
|
637
793
|
el("div", { style: st.scroll },
|
|
638
|
-
el("table", { style: st.tbl },
|
|
794
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
639
795
|
el("thead", null, el("tr", null,
|
|
640
|
-
el("th", { style: st.thFirst }, "模型"), el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
|
|
641
|
-
el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
|
|
642
|
-
el("th", { style: st.th }, "高峰消耗"), el("th", { style: st.th }, "空闲消耗"), el("th", { style: st.th }, "总消耗")
|
|
796
|
+
el("th", { style: st.thFirst }, t("模型")), el("th", { style: st.thFirst }, t("API 服务商")), el("th", { style: st.th }, t("调用")), el("th", { style: st.th }, t("高峰/空闲")),
|
|
797
|
+
el("th", { style: st.th }, t("输入·未命中")), el("th", { style: st.th }, t("缓存命中")), el("th", { style: st.th }, t("输出")), el("th", { style: st.th }, t("推理")),
|
|
798
|
+
el("th", { style: st.th }, t("高峰消耗")), el("th", { style: st.th }, t("空闲消耗")), el("th", { style: st.th }, t("总消耗"))
|
|
643
799
|
)),
|
|
644
800
|
el("tbody", null,
|
|
645
801
|
modelRows.map(function (m) {
|
|
@@ -658,8 +814,8 @@ window.__ModuleLoader__.load({
|
|
|
658
814
|
);
|
|
659
815
|
}),
|
|
660
816
|
el("tr", null,
|
|
661
|
-
el("td", { style: st.tdTotalFirst }, "合计"),
|
|
662
|
-
el("td", { style: st.tdTotalFirst }, fmtInt(providerRows.length) + " 个服务商"),
|
|
817
|
+
el("td", { style: st.tdTotalFirst }, t("合计")),
|
|
818
|
+
el("td", { style: st.tdTotalFirst }, fmtInt(providerRows.length) + t(" 个服务商")),
|
|
663
819
|
el("td", { style: st.tdTotal }, fmtInt(totalCalls)),
|
|
664
820
|
el("td", { style: st.tdTotal }, el("span", { style: st.badgePeak }, fmtInt(peakCalls)), " / ", el("span", { style: st.badgeValley }, fmtInt(offCalls))),
|
|
665
821
|
el("td", { style: st.tdTotal }, fmtInt(totalMiss)),
|
|
@@ -673,19 +829,19 @@ window.__ModuleLoader__.load({
|
|
|
673
829
|
)
|
|
674
830
|
)
|
|
675
831
|
),
|
|
676
|
-
el("div", { style: st.sec, marginTop: 14 }, "消耗明细(按 API 服务商 × 模型)"),
|
|
832
|
+
el("div", { style: st.sec, marginTop: 14 }, t("消耗明细(按 API 服务商 × 模型)")),
|
|
677
833
|
el("div", { style: st.scroll },
|
|
678
|
-
el("table", { style: st.tbl },
|
|
834
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
679
835
|
el("thead", null, el("tr", null,
|
|
680
|
-
el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
|
|
681
|
-
el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
|
|
682
|
-
el("th", { style: st.th }, "高峰消耗"), el("th", { style: st.th }, "空闲消耗"), el("th", { style: st.th }, "总消耗")
|
|
836
|
+
el("th", { style: st.thFirst }, t("API 服务商")), el("th", { style: st.thFirst }, t("模型")), el("th", { style: st.th }, t("调用")), el("th", { style: st.th }, t("高峰/空闲")),
|
|
837
|
+
el("th", { style: st.th }, t("输入·未命中")), el("th", { style: st.th }, t("缓存命中")), el("th", { style: st.th }, t("输出")), el("th", { style: st.th }, t("推理")),
|
|
838
|
+
el("th", { style: st.th }, t("高峰消耗")), el("th", { style: st.th }, t("空闲消耗")), el("th", { style: st.th }, t("总消耗"))
|
|
683
839
|
)),
|
|
684
840
|
el("tbody", null, pmRows)
|
|
685
841
|
)
|
|
686
842
|
),
|
|
687
843
|
el("div", { style: st.note, marginTop: 8 },
|
|
688
|
-
"DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。")
|
|
844
|
+
t("DeepSeek 官方请求按官方峰谷价;SiliconFlow 按自身人民币公开价;DigitalOcean 按美元公开价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。模型名与 API 服务商均以请求参数为准。"))
|
|
689
845
|
);
|
|
690
846
|
}
|
|
691
847
|
|
|
@@ -735,7 +891,7 @@ window.__ModuleLoader__.load({
|
|
|
735
891
|
}
|
|
736
892
|
monthCost = monthPeak + monthOff;
|
|
737
893
|
|
|
738
|
-
var weeks =
|
|
894
|
+
var weeks = weekdayLabels();
|
|
739
895
|
var cells = [];
|
|
740
896
|
for (var b = 0; b < offset; b++) cells.push(el("div", { key: "b" + b, style: st.calCellBlank }, ""));
|
|
741
897
|
for (var d3 = 1; d3 <= nDays; d3++) {
|
|
@@ -748,9 +904,9 @@ window.__ModuleLoader__.load({
|
|
|
748
904
|
var tip = r3
|
|
749
905
|
? (function () {
|
|
750
906
|
var ds3 = daySplit(r3, regime, splitMap[key3]);
|
|
751
|
-
return dayLabel(key3) + "\n调用 " + r3.calls + " 次(高峰 " + r3.peakCalls + " / 空闲 " + r3.offPeakCalls + ")\n输入·未命中 " + fmtInt(r3.miss) + " · 缓存命中 " + fmtInt(r3.hit) + " · 输出 " + fmtInt(r3.out) + " · 推理 " + fmtInt(r3.reason) + "\n高峰消耗 " + fmtMoney(ds3.peak) + " · 空闲消耗 " + fmtMoney(ds3.off) + " · 合计 " + fmtMoney(ds3.peak + ds3.off) + "(" + costHint(regime) + ")";
|
|
907
|
+
return dayLabel(key3) + t("\n调用 ") + r3.calls + t(" 次(高峰 ") + r3.peakCalls + t(" / 空闲 ") + r3.offPeakCalls + t(")\n输入·未命中 ") + fmtInt(r3.miss) + t(" · 缓存命中 ") + fmtInt(r3.hit) + t(" · 输出 ") + fmtInt(r3.out) + t(" · 推理 ") + fmtInt(r3.reason) + t("\n高峰消耗 ") + fmtMoney(ds3.peak) + t(" · 空闲消耗 ") + fmtMoney(ds3.off) + t(" · 合计 ") + fmtMoney(ds3.peak + ds3.off) + "(" + costHint(regime) + ")";
|
|
752
908
|
})()
|
|
753
|
-
: dayLabel(key3) + "\n无记录";
|
|
909
|
+
: dayLabel(key3) + t("\n无记录");
|
|
754
910
|
var isSel = selectedDay === key3;
|
|
755
911
|
cells.push(el("div", {
|
|
756
912
|
key: key3,
|
|
@@ -793,13 +949,13 @@ window.__ModuleLoader__.load({
|
|
|
793
949
|
|
|
794
950
|
return el("div", null,
|
|
795
951
|
el("div", { style: st.calBar },
|
|
796
|
-
el("button", { style: st.btn, onClick: prevMonth }, "‹ 上月"),
|
|
797
|
-
el("span", { style: { fontSize: fs(14), fontWeight: 600, minWidth: 120, textAlign: "center" } }, y
|
|
798
|
-
el("button", { style: st.btn, onClick: nextMonth }, "下月 ›"),
|
|
799
|
-
el("button", { style: st.btn, onClick: goToday }, "今天"),
|
|
952
|
+
el("button", { style: st.btn, onClick: prevMonth }, t("‹ 上月")),
|
|
953
|
+
el("span", { style: { fontSize: fs(14), fontWeight: 600, minWidth: 120, textAlign: "center" } }, fmtMonthLabel(y, m + 1)),
|
|
954
|
+
el("button", { style: st.btn, onClick: nextMonth }, t("下月 ›")),
|
|
955
|
+
el("button", { style: st.btn, onClick: goToday }, t("今天")),
|
|
800
956
|
el("div", { style: st.seg, marginLeft: 8 },
|
|
801
|
-
el("button", { style: dimMode === "cost" ? st.segBtnOn : st.segBtn, onClick: function () { setDim("cost"); } }, "按消耗"),
|
|
802
|
-
el("button", { style: dimMode === "calls" ? st.segBtnOn : st.segBtn, onClick: function () { setDim("calls"); } }, "按调用数")
|
|
957
|
+
el("button", { style: dimMode === "cost" ? st.segBtnOn : st.segBtn, onClick: function () { setDim("cost"); } }, t("按消耗")),
|
|
958
|
+
el("button", { style: dimMode === "calls" ? st.segBtnOn : st.segBtn, onClick: function () { setDim("calls"); } }, t("按调用数"))
|
|
803
959
|
)
|
|
804
960
|
),
|
|
805
961
|
el("div", { style: st.calGrid },
|
|
@@ -807,27 +963,27 @@ window.__ModuleLoader__.load({
|
|
|
807
963
|
cells
|
|
808
964
|
),
|
|
809
965
|
el("div", { style: st.legend },
|
|
810
|
-
el("span", null, "热力:"),
|
|
966
|
+
el("span", null, t("热力:")),
|
|
811
967
|
el("span", { style: st.legendBar }),
|
|
812
|
-
el("span", null, "低 → 高(" + (dimMode === "cost" ? "当日消耗" : "当日调用数") + ")"),
|
|
813
|
-
el("span", null, "· 悬停查看详情,点击某天查看当日调用")
|
|
968
|
+
el("span", null, t("低 → 高(") + (dimMode === "cost" ? t("当日消耗") : t("当日调用数")) + ")"),
|
|
969
|
+
el("span", null, t("· 悬停查看详情,点击某天查看当日调用"))
|
|
814
970
|
),
|
|
815
971
|
el("div", { style: st.cards, marginTop: 12 },
|
|
816
|
-
el(Card, { label: "本月调用", value: fmtInt(monthCalls), hint: "高峰 " + fmtInt(monthPeakCalls) + " · 空闲 " + fmtInt(monthOffCalls) + " · " + y
|
|
817
|
-
el(Card, { label: "输入 · 未命中", value: fmtInt(monthMiss), hint: "token" }),
|
|
818
|
-
el(Card, { label: "输入 · 缓存命中", value: fmtInt(monthHit), hint: "token" }),
|
|
819
|
-
el(Card, { label: "输出", value: fmtInt(monthOut), hint: "token" }),
|
|
820
|
-
el(Card, { label: "高峰消耗", value: fmtMoney(monthPeak), hint: "工作日高峰 9:00–12:00、14:00–18:00 · 周末全天空闲价" }),
|
|
821
|
-
el(Card, { label: "空闲消耗", value: fmtMoney(monthOff), hint: "其余空闲时段" }),
|
|
822
|
-
el(Card, { label: "本月消耗", value: fmtMoney(monthCost), hint: "高峰 + 空闲 · " + costHint(regime) })
|
|
972
|
+
el(Card, { label: t("本月调用"), value: fmtInt(monthCalls), hint: t("高峰 ") + fmtInt(monthPeakCalls) + t(" · 空闲 ") + fmtInt(monthOffCalls) + " · " + fmtMonthLabel(y, m + 1) }),
|
|
973
|
+
el(Card, { label: t("输入 · 未命中"), value: fmtInt(monthMiss), hint: "token" }),
|
|
974
|
+
el(Card, { label: t("输入 · 缓存命中"), value: fmtInt(monthHit), hint: "token" }),
|
|
975
|
+
el(Card, { label: t("输出"), value: fmtInt(monthOut), hint: "token" }),
|
|
976
|
+
el(Card, { label: t("高峰消耗"), value: fmtMoney(monthPeak), hint: t("工作日高峰 9:00–12:00、14:00–18:00 · 周末全天空闲价") }),
|
|
977
|
+
el(Card, { label: t("空闲消耗"), value: fmtMoney(monthOff), hint: t("其余空闲时段") }),
|
|
978
|
+
el(Card, { label: t("本月消耗"), value: fmtMoney(monthCost), hint: t("高峰 + 空闲 · ") + costHint(regime) })
|
|
823
979
|
),
|
|
824
|
-
el("div", { style: st.sec },
|
|
980
|
+
el("div", { style: st.sec }, dailyStatsTitle(y, m + 1)),
|
|
825
981
|
monthRows.length === 0
|
|
826
|
-
? el("div", { style: st.empty }, "本月暂无记录。")
|
|
982
|
+
? el("div", { style: st.empty }, t("本月暂无记录。"))
|
|
827
983
|
: el("div", { style: st.scroll },
|
|
828
|
-
el("table", { style: st.tbl },
|
|
984
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
829
985
|
el("thead", null, el("tr", null,
|
|
830
|
-
el("th", { style: st.thFirst }, "日期"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "高峰消耗"), el("th", { style: st.th }, "空闲消耗"), el("th", { style: st.th }, "总消耗(自动)")
|
|
986
|
+
el("th", { style: st.thFirst }, t("日期")), el("th", { style: st.th }, t("调用")), el("th", { style: st.th }, t("高峰/空闲")), el("th", { style: st.th }, t("输入·未命中")), el("th", { style: st.th }, t("缓存命中")), el("th", { style: st.th }, t("输出")), el("th", { style: st.th }, t("高峰消耗")), el("th", { style: st.th }, t("空闲消耗")), el("th", { style: st.th }, t("总消耗(自动)"))
|
|
831
987
|
)),
|
|
832
988
|
el("tbody", null, monthRows.map(function (drow) {
|
|
833
989
|
var sel = selectedDay === drow.day;
|
|
@@ -848,17 +1004,17 @@ window.__ModuleLoader__.load({
|
|
|
848
1004
|
),
|
|
849
1005
|
selectedDay
|
|
850
1006
|
? el("div", null,
|
|
851
|
-
el("div", { style: st.selDayTitle }, dayLabel(selectedDay) + " 调用明细"),
|
|
1007
|
+
el("div", { style: st.selDayTitle }, dayLabel(selectedDay) + t(" 调用明细")),
|
|
852
1008
|
el("div", { style: st.note, marginTop: 6 },
|
|
853
|
-
"高峰 " + selPeakCalls + " 条 · " + fmtMoney(selPeakCost) +
|
|
854
|
-
" | 空闲 " + selOffCalls + " 条 · " + fmtMoney(selOffCost) +
|
|
855
|
-
" | 合计 " + fmtMoney(selPeakCost + selOffCost) + "(" + costHint(regime) + ")"),
|
|
1009
|
+
t("高峰 ") + selPeakCalls + t(" 条 · ") + fmtMoney(selPeakCost) +
|
|
1010
|
+
t(" | 空闲 ") + selOffCalls + t(" 条 · ") + fmtMoney(selOffCost) +
|
|
1011
|
+
t(" | 合计 ") + fmtMoney(selPeakCost + selOffCost) + "(" + costHint(regime) + ")"),
|
|
856
1012
|
selRecords.length === 0
|
|
857
|
-
? el("div", { style: st.empty }, "该日无记录。")
|
|
1013
|
+
? el("div", { style: st.empty }, t("该日无记录。"))
|
|
858
1014
|
: el("div", { style: st.scroll },
|
|
859
|
-
el("table", { style: st.tbl },
|
|
1015
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
860
1016
|
el("thead", null, el("tr", null,
|
|
861
|
-
el("th", { style: st.thFirst }, "时间(北京)"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"), el("th", { style: st.th }, "时段"), el("th", { style: st.th }, "结束"), el("th", { style: st.th }, "消耗")
|
|
1017
|
+
el("th", { style: st.thFirst }, t("时间(北京)")), el("th", { style: st.thFirst }, t("模型")), el("th", { style: st.th }, t("输入·未命中")), el("th", { style: st.th }, t("缓存命中")), el("th", { style: st.th }, t("输出")), el("th", { style: st.th }, t("推理")), el("th", { style: st.th }, t("时段")), el("th", { style: st.th }, t("结束")), el("th", { style: st.th }, t("消耗"))
|
|
862
1018
|
)),
|
|
863
1019
|
el("tbody", null, selRecords.map(function (rr) {
|
|
864
1020
|
var hit2 = rr.cacheReadTokens || 0, miss2 = rr.inputTokens || 0;
|
|
@@ -873,7 +1029,7 @@ window.__ModuleLoader__.load({
|
|
|
873
1029
|
el("td", { style: st.td }, el("span", { style: st.badgeHit }, fmtInt(hit2))),
|
|
874
1030
|
el("td", { style: st.td }, fmtInt(rr.outputTokens || 0)),
|
|
875
1031
|
el("td", { style: st.td }, rr.reasoningTokens ? fmtInt(rr.reasoningTokens) : "—"),
|
|
876
|
-
el("td", { style: st.td }, rr.peak ? el("span", { style: st.badgePeak }, "峰") : el("span", { style: st.badgeValley }, "谷")),
|
|
1032
|
+
el("td", { style: st.td }, rr.peak ? el("span", { style: st.badgePeak }, t("峰")) : el("span", { style: st.badgeValley }, t("谷"))),
|
|
877
1033
|
el("td", { style: st.td }, finishLabel(rr.finishReason)),
|
|
878
1034
|
el("td", { style: st.td }, el("span", { style: rr.peak ? st.costPeak : st.costOff }, fmtMoney(cost2)))
|
|
879
1035
|
);
|
|
@@ -938,35 +1094,39 @@ window.__ModuleLoader__.load({
|
|
|
938
1094
|
function pageBtn(kind, label, disabled, onClick) {
|
|
939
1095
|
return el("button", { style: disabled ? st.btnDisabled : st.btn, onClick: onClick }, label);
|
|
940
1096
|
}
|
|
1097
|
+
// 分页切换条:顶部与底部各渲染一份,便于长列表翻页。
|
|
1098
|
+
function pagerBar() {
|
|
1099
|
+
return el("div", { style: st.calBar },
|
|
1100
|
+
pageBtn("prev", t("‹ 上一页"), curPage <= 1, function () { setPage(curPage - 1); }),
|
|
1101
|
+
el("span", { style: st.note }, t("第 ") + curPage + " / " + totalPages + t(" 页 · 每页 ") + PAGE_SIZE + t(" 条 · 共 ") + fmtInt(filtered.length) + t(" 条")),
|
|
1102
|
+
pageBtn("next", t("下一页 ›"), curPage >= totalPages, function () { setPage(curPage + 1); })
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
941
1105
|
var sums = splitTotals(filtered, regime);
|
|
942
1106
|
|
|
943
1107
|
return el("div", null,
|
|
944
1108
|
el("div", { style: st.calBar },
|
|
945
1109
|
el("div", { style: st.seg },
|
|
946
|
-
presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
|
|
1110
|
+
presetBtn("today", t("今天")), presetBtn("7d", t("近7天")), presetBtn("30d", t("近30天")), presetBtn("all", t("全部"))
|
|
947
1111
|
),
|
|
948
|
-
el("span", { style: st.note }, "自定义区间:"),
|
|
1112
|
+
el("span", { style: st.note }, t("自定义区间:")),
|
|
949
1113
|
el("input", { type: "date", style: st.dateInput, value: fromDate, onChange: function (e) { setCustomFrom(e.target.value); } }),
|
|
950
|
-
el("span", { style: st.note }, "至"),
|
|
1114
|
+
el("span", { style: st.note }, t("至")),
|
|
951
1115
|
el("input", { type: "date", style: st.dateInput, value: toDate, onChange: function (e) { setCustomTo(e.target.value); } }),
|
|
952
|
-
(fromDate || toDate) ? el("button", { style: st.btn, onClick: function () { setFromDate(""); setToDate(""); setPreset("all"); setPage(1); } }, "清除区间") : null
|
|
1116
|
+
(fromDate || toDate) ? el("button", { style: st.btn, onClick: function () { setFromDate(""); setToDate(""); setPreset("all"); setPage(1); } }, t("清除区间")) : null
|
|
953
1117
|
),
|
|
954
1118
|
el("div", { style: st.note, marginTop: 8 },
|
|
955
|
-
"当前范围:共 " + fmtInt(filtered.length) + " 条 · 输入·未命中 " + fmtInt(sumMiss) + " · 缓存命中 " + fmtInt(sumHit) + " · 缓存写入 " + fmtInt(sumWrite) + " · 输出 " + fmtInt(sumOut) +
|
|
956
|
-
" · 高峰消耗 " + fmtMoney(sums.peak) + " · 空闲消耗 " + fmtMoney(sums.off) + " · 总消耗 " + fmtMoney(sumCost) + "(" + costHint(regime) + ")"
|
|
1119
|
+
t("当前范围:共 ") + fmtInt(filtered.length) + t(" 条 · 输入·未命中 ") + fmtInt(sumMiss) + t(" · 缓存命中 ") + fmtInt(sumHit) + t(" · 缓存写入 ") + fmtInt(sumWrite) + t(" · 输出 ") + fmtInt(sumOut) +
|
|
1120
|
+
t(" · 高峰消耗 ") + fmtMoney(sums.peak) + t(" · 空闲消耗 ") + fmtMoney(sums.off) + t(" · 总消耗 ") + fmtMoney(sumCost) + "(" + costHint(regime) + ")"
|
|
957
1121
|
),
|
|
958
1122
|
filtered.length === 0
|
|
959
|
-
? el("div", { style: st.empty }, "该时间范围内没有记录。")
|
|
1123
|
+
? el("div", { style: st.empty }, t("该时间范围内没有记录。"))
|
|
960
1124
|
: el("div", null,
|
|
961
|
-
|
|
962
|
-
pageBtn("prev", "‹ 上一页", curPage <= 1, function () { setPage(curPage - 1); }),
|
|
963
|
-
el("span", { style: st.note }, "第 " + curPage + " / " + totalPages + " 页 · 每页 " + PAGE_SIZE + " 条 · 共 " + fmtInt(filtered.length) + " 条"),
|
|
964
|
-
pageBtn("next", "下一页 ›", curPage >= totalPages, function () { setPage(curPage + 1); })
|
|
965
|
-
),
|
|
1125
|
+
pagerBar(),
|
|
966
1126
|
el("div", { style: st.scroll },
|
|
967
|
-
el("table", { style: st.tbl },
|
|
1127
|
+
el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
|
|
968
1128
|
el("thead", null, el("tr", null,
|
|
969
|
-
el("th", { style: st.thFirst }, "时间(北京)"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "缓存写入"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"), el("th", { style: st.th }, "命中率"), el("th", { style: st.th }, "时段"), el("th", { style: st.th }, "结束"), el("th", { style: st.th }, "消耗")
|
|
1129
|
+
el("th", { style: st.thFirst }, t("时间(北京)")), el("th", { style: st.thFirst }, t("模型")), el("th", { style: st.th }, t("输入·未命中")), el("th", { style: st.th }, t("缓存命中")), el("th", { style: st.th }, t("缓存写入")), el("th", { style: st.th }, t("输出")), el("th", { style: st.th }, t("推理")), el("th", { style: st.th }, t("命中率")), el("th", { style: st.th }, t("时段")), el("th", { style: st.th }, t("结束")), el("th", { style: st.th }, t("消耗"))
|
|
970
1130
|
)),
|
|
971
1131
|
el("tbody", null,
|
|
972
1132
|
pageRows.map(function (rr3, idx) {
|
|
@@ -984,14 +1144,14 @@ window.__ModuleLoader__.load({
|
|
|
984
1144
|
el("td", { style: st.td }, fmtInt(rr3.outputTokens || 0)),
|
|
985
1145
|
el("td", { style: st.td }, rr3.reasoningTokens ? fmtInt(rr3.reasoningTokens) : "—"),
|
|
986
1146
|
el("td", { style: st.td }, pct(hit3, hit3 + miss3)),
|
|
987
|
-
el("td", { style: st.td }, rr3.peak ? el("span", { style: st.badgePeak }, "峰") : el("span", { style: st.badgeValley }, "谷")),
|
|
1147
|
+
el("td", { style: st.td }, rr3.peak ? el("span", { style: st.badgePeak }, t("峰")) : el("span", { style: st.badgeValley }, t("谷"))),
|
|
988
1148
|
el("td", { style: st.td }, finishLabel(rr3.finishReason)),
|
|
989
1149
|
el("td", { style: st.td }, el("span", { style: rr3.peak ? st.costPeak : st.costOff }, fmtMoney(cost3)))
|
|
990
1150
|
);
|
|
991
1151
|
}),
|
|
992
1152
|
el("tr", null,
|
|
993
|
-
el("td", { style: st.tdTotalFirst }, "总费用合计"),
|
|
994
|
-
el("td", { style: st.tdTotal }, fmtInt(filtered.length) + " 条"),
|
|
1153
|
+
el("td", { style: st.tdTotalFirst }, t("总费用合计")),
|
|
1154
|
+
el("td", { style: st.tdTotal }, fmtInt(filtered.length) + t(" 条")),
|
|
995
1155
|
el("td", { style: st.tdTotal }, fmtInt(sumMiss)),
|
|
996
1156
|
el("td", { style: st.tdTotal }, fmtInt(sumHit)),
|
|
997
1157
|
el("td", { style: st.tdTotal }, fmtInt(sumWrite)),
|
|
@@ -1004,7 +1164,8 @@ window.__ModuleLoader__.load({
|
|
|
1004
1164
|
)
|
|
1005
1165
|
)
|
|
1006
1166
|
)
|
|
1007
|
-
)
|
|
1167
|
+
),
|
|
1168
|
+
pagerBar()
|
|
1008
1169
|
)
|
|
1009
1170
|
);
|
|
1010
1171
|
}
|
|
@@ -1019,9 +1180,9 @@ window.__ModuleLoader__.load({
|
|
|
1019
1180
|
var regimeState = React.useState("auto");
|
|
1020
1181
|
var regime = regimeState[0], setRegime = regimeState[1];
|
|
1021
1182
|
var effectiveIn = function () {
|
|
1022
|
-
if (!effectiveAt) return "新价格表生效时间未知";
|
|
1183
|
+
if (!effectiveAt) return t("新价格表生效时间未知");
|
|
1023
1184
|
var now = Date.now();
|
|
1024
|
-
return now < effectiveAt ? "新价格表(峰谷价)将于 " + fmtTime(effectiveAt) + "(北京时间)生效,当前按旧价格表(基础价)计费" : "新价格表(峰谷价)已生效(自 " + fmtTime(effectiveAt) + " 起)";
|
|
1185
|
+
return now < effectiveAt ? t("新价格表(峰谷价)将于 ") + fmtTime(effectiveAt) + t("(北京时间)生效,当前按旧价格表(基础价)计费") : t("新价格表(峰谷价)已生效(自 ") + fmtTime(effectiveAt) + t(" 起)");
|
|
1025
1186
|
};
|
|
1026
1187
|
var showRegime = function (r) {
|
|
1027
1188
|
if (r === "auto") return Date.now() < effectiveAt ? "base" : "peakValley";
|
|
@@ -1058,77 +1219,77 @@ window.__ModuleLoader__.load({
|
|
|
1058
1219
|
|
|
1059
1220
|
return el("div", null,
|
|
1060
1221
|
el("div", { style: st.nowPeriod },
|
|
1061
|
-
el("span", { style: periodNow().peak ? st.badgePeak : st.badgeValley }, "当前 · " + periodNow().label),
|
|
1062
|
-
el("span", { style: st.sub }, periodNow().desc)
|
|
1222
|
+
el("span", { style: periodNow().peak ? st.badgePeak : st.badgeValley }, t("当前 · ") + t(periodNow().label)),
|
|
1223
|
+
el("span", { style: st.sub }, t(periodNow().desc))
|
|
1063
1224
|
),
|
|
1064
|
-
el("div", { style: st.sec }, "今日 $ / ¥ 汇率"),
|
|
1225
|
+
el("div", { style: st.sec }, t("今日 $ / ¥ 汇率")),
|
|
1065
1226
|
el("div", { style: st.cards, marginTop: 8 },
|
|
1066
|
-
el(Card, { label: "$ → ¥", value: fx.rate ? ("$1 = ¥" + Number(fx.rate).toFixed(4)) : "暂不可用", hint: fx.date ? ("汇率日期 " + fx.date) : "等待获取" }),
|
|
1067
|
-
el(Card, { label: "¥ → $", value: fx.inverse ? ("¥1 = $" + Number(fx.inverse).toFixed(6)) : "暂不可用", hint: fx.source || "Frankfurter" }),
|
|
1068
|
-
el(Card, { label: "汇率状态", value: fx.stale ? "缓存汇率" : (fx.rate ? "最新可用" : "获取失败"), hint: fx.error || "Frankfurter · central-bank reference rates" })
|
|
1227
|
+
el(Card, { label: "$ → ¥", value: fx.rate ? ("$1 = ¥" + Number(fx.rate).toFixed(4)) : t("暂不可用"), hint: fx.date ? (t("汇率日期 ") + fx.date) : t("等待获取") }),
|
|
1228
|
+
el(Card, { label: "¥ → $", value: fx.inverse ? ("¥1 = $" + Number(fx.inverse).toFixed(6)) : t("暂不可用"), hint: fx.source || "Frankfurter" }),
|
|
1229
|
+
el(Card, { label: t("汇率状态"), value: fx.stale ? t("缓存汇率") : (fx.rate ? t("最新可用") : t("获取失败")), hint: fx.error || "Frankfurter · central-bank reference rates" })
|
|
1069
1230
|
),
|
|
1070
1231
|
el("div", { style: st.actions, marginTop: 8 },
|
|
1071
|
-
el("button", { style: st.btn, onClick: props.onFxRefresh }, "刷新汇率"),
|
|
1072
|
-
el("span", { style: st.note }, "美元计价的 DigitalOcean 消耗按该 USD/CNY 汇率换算成人民币;人民币定价不受汇率影响。")
|
|
1232
|
+
el("button", { style: st.btn, onClick: props.onFxRefresh }, t("刷新汇率")),
|
|
1233
|
+
el("span", { style: st.note }, t("美元计价的 DigitalOcean 消耗按该 USD/CNY 汇率换算成人民币;人民币定价不受汇率影响。"))
|
|
1073
1234
|
),
|
|
1074
|
-
el("div", { style: st.sec, marginTop: 18 }, "DeepSeek 官方 API 价格表"),
|
|
1235
|
+
el("div", { style: st.sec, marginTop: 18 }, t("DeepSeek 官方 API 价格表")),
|
|
1075
1236
|
el("div", { style: st.note, marginTop: 6 },
|
|
1076
|
-
"本表仅用于 DeepSeek 官方 Provider;第三方 Provider 按下方已核验的自身价格计费,无法可靠匹配价格的模型按 0 统计。价格按官方公布固定,不可编辑。"),
|
|
1237
|
+
t("本表仅用于 DeepSeek 官方 Provider;第三方 Provider 按下方已核验的自身价格计费,无法可靠匹配价格的模型按 0 统计。价格按官方公布固定,不可编辑。")),
|
|
1077
1238
|
el("div", { style: st.calBar },
|
|
1078
1239
|
el("div", { style: st.seg },
|
|
1079
|
-
el("button", { style: regime === "auto" ? st.segBtnOn : st.segBtn, onClick: function () { setRegime("auto"); } }, "自动"),
|
|
1080
|
-
el("button", { style: regime === "peakValley" ? st.segBtnOn : st.segBtn, onClick: function () { setRegime("peakValley"); } }, "峰谷价"),
|
|
1081
|
-
el("button", { style: regime === "base" ? st.segBtnOn : st.segBtn, onClick: function () { setRegime("base"); } }, "基础价")
|
|
1240
|
+
el("button", { style: regime === "auto" ? st.segBtnOn : st.segBtn, onClick: function () { setRegime("auto"); } }, t("自动")),
|
|
1241
|
+
el("button", { style: regime === "peakValley" ? st.segBtnOn : st.segBtn, onClick: function () { setRegime("peakValley"); } }, t("峰谷价")),
|
|
1242
|
+
el("button", { style: regime === "base" ? st.segBtnOn : st.segBtn, onClick: function () { setRegime("base"); } }, t("基础价"))
|
|
1082
1243
|
)
|
|
1083
1244
|
),
|
|
1084
1245
|
el("div", { style: st.note, marginTop: 6 }, effectiveIn()),
|
|
1085
1246
|
regime === "auto"
|
|
1086
1247
|
? el("div", { style: st.note, marginTop: 6 },
|
|
1087
|
-
"自动模式:按调用时间自动选择计费档位——新价格表生效前的调用按基础价(旧价格表),生效后的调用按峰谷价(工作日高峰时段 9:00–12:00、14:00–18:00 用高峰价,其余时间——含周末全天——用空闲价)。当前显示:",
|
|
1088
|
-
el("strong", null, showRegime(regime) === "base" ? "基础价表(旧价格表,新价格生效前)" : "峰谷价表(新价格表)"),
|
|
1089
|
-
"。价格按官方公布固定,不可编辑。"
|
|
1248
|
+
t("自动模式:按调用时间自动选择计费档位——新价格表生效前的调用按基础价(旧价格表),生效后的调用按峰谷价(工作日高峰时段 9:00–12:00、14:00–18:00 用高峰价,其余时间——含周末全天——用空闲价)。当前显示:"),
|
|
1249
|
+
el("strong", null, showRegime(regime) === "base" ? t("基础价表(旧价格表,新价格生效前)") : t("峰谷价表(新价格表)")),
|
|
1250
|
+
t("。价格按官方公布固定,不可编辑。")
|
|
1090
1251
|
)
|
|
1091
1252
|
: null,
|
|
1092
1253
|
showRegime(regime) === "base"
|
|
1093
1254
|
? el("div", { style: st.scroll },
|
|
1094
|
-
el("table", { style: st.tbl },
|
|
1255
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1095
1256
|
el("thead", null, el("tr", null,
|
|
1096
|
-
el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "缓存命中(输入)"), el("th", { style: st.th }, "输入 · 未命中"), el("th", { style: st.th }, "输出")
|
|
1257
|
+
el("th", { style: st.thFirst }, t("模型")), el("th", { style: st.th }, t("缓存命中(输入)")), el("th", { style: st.th }, t("输入 · 未命中")), el("th", { style: st.th }, t("输出"))
|
|
1097
1258
|
)),
|
|
1098
1259
|
el("tbody", null, rows)
|
|
1099
1260
|
)
|
|
1100
1261
|
)
|
|
1101
1262
|
: el("div", { style: st.scroll },
|
|
1102
|
-
el("table", { style: st.tbl },
|
|
1263
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1103
1264
|
el("thead", null, el("tr", null,
|
|
1104
|
-
el("th", { style: st.thFirst }, "模型"),
|
|
1105
|
-
el("th", { style: st.th }, "空闲 · 缓存命中"), el("th", { style: st.th }, "空闲 · 输入"), el("th", { style: st.th }, "空闲 · 输出"),
|
|
1106
|
-
el("th", { style: st.th }, "高峰 · 缓存命中"), el("th", { style: st.th }, "高峰 · 输入"), el("th", { style: st.th }, "高峰 · 输出")
|
|
1265
|
+
el("th", { style: st.thFirst }, t("模型")),
|
|
1266
|
+
el("th", { style: st.th }, t("空闲 · 缓存命中")), el("th", { style: st.th }, t("空闲 · 输入")), el("th", { style: st.th }, t("空闲 · 输出")),
|
|
1267
|
+
el("th", { style: st.th }, t("高峰 · 缓存命中")), el("th", { style: st.th }, t("高峰 · 输入")), el("th", { style: st.th }, t("高峰 · 输出"))
|
|
1107
1268
|
)),
|
|
1108
1269
|
el("tbody", null, rows)
|
|
1109
1270
|
)
|
|
1110
1271
|
),
|
|
1111
1272
|
el("div", { style: st.note, marginTop: 8 },
|
|
1112
|
-
"单位:元 / 百万 tokens。工作日高峰时段(北京时间 9:00–12:00、14:00–18:00)用高峰价,其余时间用空闲价(空闲价 = 高峰价的一半);自 2026-08-23 起周末(周六、周日)全天按空闲价计费,新规则生效前仍按原规则。概览、用量日历、缓存命中列表中的消耗均已按高峰 / 空闲分列统计。"
|
|
1273
|
+
t("单位:元 / 百万 tokens。工作日高峰时段(北京时间 9:00–12:00、14:00–18:00)用高峰价,其余时间用空闲价(空闲价 = 高峰价的一半);自 2026-08-23 起周末(周六、周日)全天按空闲价计费,新规则生效前仍按原规则。概览、用量日历、缓存命中列表中的消耗均已按高峰 / 空闲分列统计。")
|
|
1113
1274
|
),
|
|
1114
|
-
el("div", { style: st.sec, marginTop: 18 }, "第三方平台价格与覆盖状态(2026-08-19 核验)"),
|
|
1115
|
-
el("div", { style: st.note, marginTop: 6 }, "只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。"),
|
|
1275
|
+
el("div", { style: st.sec, marginTop: 18 }, t("第三方平台价格与覆盖状态(2026-08-19 核验)")),
|
|
1276
|
+
el("div", { style: st.note, marginTop: 6 }, t("只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。")),
|
|
1116
1277
|
el("div", { style: st.scroll, marginTop: 8 },
|
|
1117
|
-
el("table", { style: st.tbl },
|
|
1278
|
+
el("table", { className: "dsh-usage-table", style: st.tbl },
|
|
1118
1279
|
el("thead", null, el("tr", null,
|
|
1119
|
-
el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型 / 状态"),
|
|
1120
|
-
el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输入"), el("th", { style: st.th }, "输出"), el("th", { style: st.thFirst }, "说明")
|
|
1280
|
+
el("th", { style: st.thFirst }, t("API 服务商")), el("th", { style: st.thFirst }, t("模型 / 状态")),
|
|
1281
|
+
el("th", { style: st.th }, t("缓存命中")), el("th", { style: st.th }, t("输入")), el("th", { style: st.th }, t("输出")), el("th", { style: st.thFirst }, t("说明"))
|
|
1121
1282
|
)),
|
|
1122
1283
|
el("tbody", null, [
|
|
1123
|
-
["SiliconFlow", "DeepSeek-V4-Flash", "¥0.02", "¥1.00", "¥2.00", "已用于自动计费"],
|
|
1124
|
-
["SiliconFlow", "DeepSeek-V4-Pro", "¥1.00", "¥12.00", "¥24.00", "缓存命中价按 2026-08-03 起生效公告"],
|
|
1125
|
-
["SiliconFlow", "DeepSeek-V3.2", "¥0.40", "¥4.00", "¥6.00", "已用于自动计费"],
|
|
1126
|
-
["SiliconFlow", "Qwen3.6-27B", "按输入价", "¥3.00", "¥18.00", "公告未单列缓存命中价"],
|
|
1127
|
-
["DigitalOcean", "DeepSeek V4 Flash", "$0.028", "$0.112", "$0.224", "自动按 USD/CNY 汇率换算人民币并计费"],
|
|
1128
|
-
["DigitalOcean", "DeepSeek V4 Pro", "$0.348", "$1.392", "$2.784", "自动按 USD/CNY 汇率换算人民币并计费"],
|
|
1129
|
-
["DigitalOcean", "DeepSeek V3.2", "$0.15", "$0.425", "$1.36", "自动按 USD/CNY 汇率换算人民币并计费"],
|
|
1130
|
-
["Alibaba / 千问", "暂不计费", "—", "—", "—", "调用与 token 正常统计;费用暂按 ¥0"],
|
|
1131
|
-
["AMD GPU Cloud", "DeepSeek V4 Flash", "¥0", "¥0", "¥0", "免费;费用固定按 ¥0"]
|
|
1284
|
+
["SiliconFlow", "DeepSeek-V4-Flash", "¥0.02", "¥1.00", "¥2.00", t("已用于自动计费")],
|
|
1285
|
+
["SiliconFlow", "DeepSeek-V4-Pro", "¥1.00", "¥12.00", "¥24.00", t("缓存命中价按 2026-08-03 起生效公告")],
|
|
1286
|
+
["SiliconFlow", "DeepSeek-V3.2", "¥0.40", "¥4.00", "¥6.00", t("已用于自动计费")],
|
|
1287
|
+
["SiliconFlow", "Qwen3.6-27B", t("按输入价"), "¥3.00", "¥18.00", t("公告未单列缓存命中价")],
|
|
1288
|
+
["DigitalOcean", "DeepSeek V4 Flash", "$0.028", "$0.112", "$0.224", t("自动按 USD/CNY 汇率换算人民币并计费")],
|
|
1289
|
+
["DigitalOcean", "DeepSeek V4 Pro", "$0.348", "$1.392", "$2.784", t("自动按 USD/CNY 汇率换算人民币并计费")],
|
|
1290
|
+
["DigitalOcean", "DeepSeek V3.2", "$0.15", "$0.425", "$1.36", t("自动按 USD/CNY 汇率换算人民币并计费")],
|
|
1291
|
+
[t("Alibaba / 千问"), t("暂不计费"), "—", "—", "—", t("调用与 token 正常统计;费用暂按 ¥0")],
|
|
1292
|
+
["AMD GPU Cloud", "DeepSeek V4 Flash", "¥0", "¥0", "¥0", t("免费;费用固定按 ¥0")]
|
|
1132
1293
|
].map(function (row, idx) {
|
|
1133
1294
|
return el("tr", { key: "provider-price-" + idx },
|
|
1134
1295
|
el("td", { style: st.tdFirst }, row[0]), el("td", { style: st.tdWrap }, row[1]),
|
|
@@ -1137,18 +1298,19 @@ window.__ModuleLoader__.load({
|
|
|
1137
1298
|
}))
|
|
1138
1299
|
)
|
|
1139
1300
|
),
|
|
1140
|
-
el("div", { style: st.note, marginTop: 8 }, "DigitalOcean 使用美元官方价并按最新可用 USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud 的 DeepSeek V4 Flash 免费按 ¥0。")
|
|
1301
|
+
el("div", { style: st.note, marginTop: 8 }, t("DigitalOcean 使用美元官方价并按最新可用 USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud 的 DeepSeek V4 Flash 免费按 ¥0。"))
|
|
1141
1302
|
);
|
|
1142
1303
|
}
|
|
1143
1304
|
// ── Usage panel ──
|
|
1144
1305
|
var SUBTABS = [
|
|
1145
|
-
{ k: "overview",
|
|
1146
|
-
{ k: "calendar",
|
|
1147
|
-
{ k: "cache",
|
|
1148
|
-
{ k: "prices",
|
|
1306
|
+
{ k: "overview", z: "概览" },
|
|
1307
|
+
{ k: "calendar", z: "用量日历" },
|
|
1308
|
+
{ k: "cache", z: "缓存命中列表" },
|
|
1309
|
+
{ k: "prices", z: "价格表" }
|
|
1149
1310
|
];
|
|
1150
1311
|
|
|
1151
1312
|
function UsagePanel(props) {
|
|
1313
|
+
var lang = useLang();
|
|
1152
1314
|
var state = React.useState({ records: [], count: 0, dataPath: "", persistOk: false, persistError: "", pricing: null, days: [] });
|
|
1153
1315
|
var data = state[0], setData = state[1];
|
|
1154
1316
|
var tabState = React.useState("overview");
|
|
@@ -1184,43 +1346,43 @@ window.__ModuleLoader__.load({
|
|
|
1184
1346
|
|
|
1185
1347
|
function showExport(res) {
|
|
1186
1348
|
if (res && res.ok) {
|
|
1187
|
-
setExportMsg("已导出:"
|
|
1349
|
+
setExportMsg({ k: "已导出:", tail: res.path });
|
|
1188
1350
|
if (res.dir) api({ action: "reveal", dir: res.dir }).catch(function () {});
|
|
1189
1351
|
} else {
|
|
1190
|
-
setExportMsg("导出失败:"
|
|
1352
|
+
setExportMsg({ k: "导出失败:", tail: (res && res.error) || t("未知错误") });
|
|
1191
1353
|
}
|
|
1192
1354
|
}
|
|
1193
1355
|
|
|
1194
1356
|
function doExport(kind) {
|
|
1195
|
-
setExportMsg("导出中…");
|
|
1196
|
-
api({ action: "export", kind: kind, dir: destDir || undefined }).then(showExport).catch(function (e) { setExportMsg("导出失败:"
|
|
1357
|
+
setExportMsg({ k: "导出中…" });
|
|
1358
|
+
api({ action: "export", kind: kind, dir: destDir || undefined }).then(showExport).catch(function (e) { setExportMsg({ k: "导出失败:", tail: String((e && e.message) || e) }); });
|
|
1197
1359
|
}
|
|
1198
1360
|
|
|
1199
1361
|
function doExportImage() {
|
|
1200
|
-
setExportMsg("生成图片中…");
|
|
1201
|
-
if (!canvasNode) { setExportMsg("画布不可用"); return; }
|
|
1362
|
+
setExportMsg({ k: "生成图片中…" });
|
|
1363
|
+
if (!canvasNode) { setExportMsg({ k: "画布不可用" }); return; }
|
|
1202
1364
|
try {
|
|
1203
1365
|
drawReport(canvasNode, (data.records || []).slice());
|
|
1204
1366
|
var dataUrl = canvasNode.toDataURL("image/png");
|
|
1205
|
-
api({ action: "exportPng", dataUrl: dataUrl, dir: destDir || undefined }).then(showExport).catch(function (e) { setExportMsg("导出失败:"
|
|
1367
|
+
api({ action: "exportPng", dataUrl: dataUrl, dir: destDir || undefined }).then(showExport).catch(function (e) { setExportMsg({ k: "导出失败:", tail: String((e && e.message) || e) }); });
|
|
1206
1368
|
} catch (e) {
|
|
1207
|
-
setExportMsg("生成图片失败:"
|
|
1369
|
+
setExportMsg({ k: "生成图片失败:", tail: String((e && e.message) || e) });
|
|
1208
1370
|
}
|
|
1209
1371
|
}
|
|
1210
1372
|
|
|
1211
1373
|
function pickDestDir() {
|
|
1212
|
-
setExportMsg("打开目录选择…");
|
|
1374
|
+
setExportMsg({ k: "打开目录选择…" });
|
|
1213
1375
|
api({ action: "pickDir" }).then(function (res) {
|
|
1214
|
-
if (res && res.ok) { setDestDir(res.path); setExportMsg("导出目标:"
|
|
1376
|
+
if (res && res.ok) { setDestDir(res.path); setExportMsg({ k: "导出目标:", tail: res.path }); }
|
|
1215
1377
|
else if (res && res.cancelled) { setExportMsg(""); }
|
|
1216
|
-
else { setExportMsg("选择目录失败:"
|
|
1217
|
-
}).catch(function (e) { setExportMsg("选择目录失败:"
|
|
1378
|
+
else { setExportMsg({ k: "选择目录失败:", tail: (res && res.error) || "" }); }
|
|
1379
|
+
}).catch(function (e) { setExportMsg({ k: "选择目录失败:", tail: String((e && e.message) || e) }); });
|
|
1218
1380
|
}
|
|
1219
1381
|
|
|
1220
1382
|
function doReveal() {
|
|
1221
1383
|
api({ action: "reveal", dir: destDir || "data" }).then(function (res) {
|
|
1222
|
-
if (!res || !res.ok) setExportMsg("打开文件夹失败:"
|
|
1223
|
-
}).catch(function (e) { setExportMsg("打开文件夹失败:"
|
|
1384
|
+
if (!res || !res.ok) setExportMsg({ k: "打开文件夹失败:", tail: (res && res.error) || "" });
|
|
1385
|
+
}).catch(function (e) { setExportMsg({ k: "打开文件夹失败:", tail: String((e && e.message) || e) }); });
|
|
1224
1386
|
}
|
|
1225
1387
|
|
|
1226
1388
|
function doClear() {
|
|
@@ -1232,7 +1394,7 @@ window.__ModuleLoader__.load({
|
|
|
1232
1394
|
function onFileChange(e) {
|
|
1233
1395
|
var f = e.target.files && e.target.files[0];
|
|
1234
1396
|
if (!f) return;
|
|
1235
|
-
setImportMsg("读取文件…");
|
|
1397
|
+
setImportMsg({ k: "读取文件…" });
|
|
1236
1398
|
var readPromise = typeof f.text === "function"
|
|
1237
1399
|
? f.text()
|
|
1238
1400
|
: new Promise(function (resolve, reject) { var r = new FileReader(); r.onload = function () { resolve(r.result); }; r.onerror = reject; r.readAsText(f); });
|
|
@@ -1240,13 +1402,13 @@ window.__ModuleLoader__.load({
|
|
|
1240
1402
|
try { e.target.value = ""; } catch (err) {}
|
|
1241
1403
|
api({ action: "import", content: String(content), filename: f.name }).then(function (res) {
|
|
1242
1404
|
if (res && res.ok) {
|
|
1243
|
-
setImportMsg("导入成功:新增 "
|
|
1405
|
+
setImportMsg({ seq: [{ k: "导入成功:新增 " }, res.imported, { k: " 条,跳过重复 " }, res.skipped, { k: " 条,忽略无效 " }, res.invalid, { k: " 条,现有共 " }, res.total, { k: " 条" }] });
|
|
1244
1406
|
refresh();
|
|
1245
1407
|
} else {
|
|
1246
|
-
setImportMsg("导入失败:"
|
|
1408
|
+
setImportMsg({ k: "导入失败:", tail: (res && res.error) || t("未知错误") });
|
|
1247
1409
|
}
|
|
1248
|
-
}).catch(function (err) { setImportMsg("导入失败:"
|
|
1249
|
-
}).catch(function () { setImportMsg("读取文件失败"); });
|
|
1410
|
+
}).catch(function (err) { setImportMsg({ k: "导入失败:", tail: String((err && err.message) || err) }); });
|
|
1411
|
+
}).catch(function () { setImportMsg({ k: "读取文件失败" }); });
|
|
1250
1412
|
}
|
|
1251
1413
|
|
|
1252
1414
|
var records = data.records || [];
|
|
@@ -1259,29 +1421,30 @@ window.__ModuleLoader__.load({
|
|
|
1259
1421
|
el("div", { style: st.head },
|
|
1260
1422
|
el("div", { style: st.headleft },
|
|
1261
1423
|
el("div", null,
|
|
1262
|
-
el("div", { style: st.title }, "用量与消耗"),
|
|
1263
|
-
el("div", { style: st.sub }, "记录插件激活后的每一次模型调用")
|
|
1424
|
+
el("div", { style: st.title }, t("用量与消耗")),
|
|
1425
|
+
el("div", { style: st.sub }, t("记录插件激活后的每一次模型调用"))
|
|
1264
1426
|
),
|
|
1265
|
-
el("span", { style: nowPeriod.peak ? st.badgePeak : st.badgeValley }, "当前 · " + nowPeriod.label),
|
|
1266
|
-
el("span", { style: nowEffective ? st.badgeHit : st.badgeValley }, nowEffective ? "新价格已生效" : "新价格未生效")
|
|
1427
|
+
el("span", { style: nowPeriod.peak ? st.badgePeak : st.badgeValley }, t("当前 · ") + t(nowPeriod.label)),
|
|
1428
|
+
el("span", { style: nowEffective ? st.badgeHit : st.badgeValley }, nowEffective ? t("新价格已生效") : t("新价格未生效"))
|
|
1267
1429
|
),
|
|
1268
1430
|
el("div", { style: st.actions },
|
|
1269
|
-
el("button", { style: st.btn, onClick: refresh }, "刷新"),
|
|
1270
|
-
el("button", { style: st.btn, onClick:
|
|
1431
|
+
el("button", { style: st.btn, onClick: refresh }, t("刷新")),
|
|
1432
|
+
el("button", { style: st.btn, title: "Language / 语言(外部标签页名在刷新页面后更新)", onClick: function () { setLang(lang === "en" ? "zh" : "en"); } }, lang === "en" ? "中文" : "EN"),
|
|
1433
|
+
el("button", { style: st.btn, onClick: doClear }, t("清空"))
|
|
1271
1434
|
)
|
|
1272
1435
|
),
|
|
1273
1436
|
el("div", { style: st.subtabBar },
|
|
1274
|
-
SUBTABS.map(function (
|
|
1437
|
+
SUBTABS.map(function (sub) {
|
|
1275
1438
|
return el("button", {
|
|
1276
|
-
key:
|
|
1277
|
-
style: tab ===
|
|
1278
|
-
onClick: function () { setTab(
|
|
1279
|
-
}, t.
|
|
1439
|
+
key: sub.k,
|
|
1440
|
+
style: tab === sub.k ? st.subtabOn : st.subtab,
|
|
1441
|
+
onClick: function () { setTab(sub.k); }
|
|
1442
|
+
}, t(sub.z));
|
|
1280
1443
|
})
|
|
1281
1444
|
),
|
|
1282
1445
|
el(BillingHint, null),
|
|
1283
1446
|
el("div", { style: st.note, marginTop: 6 },
|
|
1284
|
-
"计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。"
|
|
1447
|
+
t("计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。")
|
|
1285
1448
|
),
|
|
1286
1449
|
loading
|
|
1287
1450
|
? el(LoadingView, null)
|
|
@@ -1292,31 +1455,32 @@ window.__ModuleLoader__.load({
|
|
|
1292
1455
|
tab === "prices" ? el(PriceView, { pricing: data.pricing, effectiveAt: data.effectiveAt, fx: data.fx || {}, onFxRefresh: function () { api({ action: "fxRefresh" }).then(refresh).catch(function () {}); }, onChanged: refresh }) : null
|
|
1293
1456
|
),
|
|
1294
1457
|
el("div", { style: st.actions, marginTop: 4 },
|
|
1295
|
-
el("button", { style: st.btn, onClick: function () { doExport("csv"); } }, "导出 CSV"),
|
|
1296
|
-
el("button", { style: st.btn, onClick: function () { doExport("json"); } }, "导出 JSON"),
|
|
1297
|
-
el("button", { style: st.btn, onClick: doExportImage }, "导出图片 (PNG)"),
|
|
1298
|
-
el("button", { style: st.btn, onClick: doReveal }, "打开目录"),
|
|
1299
|
-
exportMsg ? el("span", { style: st.sub }, exportMsg) : null
|
|
1458
|
+
el("button", { style: st.btn, onClick: function () { doExport("csv"); } }, t("导出 CSV")),
|
|
1459
|
+
el("button", { style: st.btn, onClick: function () { doExport("json"); } }, t("导出 JSON")),
|
|
1460
|
+
el("button", { style: st.btn, onClick: doExportImage }, t("导出图片 (PNG)")),
|
|
1461
|
+
el("button", { style: st.btn, onClick: doReveal }, t("打开目录")),
|
|
1462
|
+
exportMsg ? el("span", { style: st.sub }, __msgText(exportMsg)) : null
|
|
1300
1463
|
),
|
|
1301
1464
|
el("div", { style: st.actions },
|
|
1302
|
-
el("input", { style: st.input, placeholder: "导出目标目录(留空 = 默认数据目录)", value: destDir, onChange: function (e) { setDestDir(e.target.value); } }),
|
|
1303
|
-
el("button", { style: st.btn, onClick: pickDestDir }, "选择目录…"),
|
|
1304
|
-
destDir ? el("button", { style: st.btn, onClick: function () { setDestDir(""); setExportMsg("已恢复默认数据目录"); } }, "重置") : null
|
|
1465
|
+
el("input", { style: st.input, placeholder: t("导出目标目录(留空 = 默认数据目录)"), value: destDir, onChange: function (e) { setDestDir(e.target.value); } }),
|
|
1466
|
+
el("button", { style: st.btn, onClick: pickDestDir }, t("选择目录…")),
|
|
1467
|
+
destDir ? el("button", { style: st.btn, onClick: function () { setDestDir(""); setExportMsg({ k: "已恢复默认数据目录" }); } }, t("重置")) : null
|
|
1305
1468
|
),
|
|
1306
1469
|
el("div", { style: st.actions },
|
|
1307
1470
|
el("input", { type: "file", accept: ".json,.csv", style: { display: "none" }, ref: function (n) { fileInputNode = n; }, onChange: onFileChange }),
|
|
1308
|
-
el("button", { style: st.btn, onClick: pickFile }, "选择文件导入"),
|
|
1309
|
-
importMsg ? el("span", { style: st.sub }, importMsg) : null
|
|
1471
|
+
el("button", { style: st.btn, onClick: pickFile }, t("选择文件导入")),
|
|
1472
|
+
importMsg ? el("span", { style: st.sub }, __msgText(importMsg)) : null
|
|
1310
1473
|
),
|
|
1311
1474
|
error ? el("div", { style: st.err }, error) : null,
|
|
1312
1475
|
dataPath
|
|
1313
|
-
? el("div", { style: st.note }, "数据持久化:" + dataPath + "(每次调用实时落盘,插件重启后自动恢复,最多保留 100000 条)")
|
|
1314
|
-
: el("div", { style: st.note, color: "#ff6b6b", opacity: 1 }, "持久化未启用:" + (data.persistError || "未知原因"))
|
|
1476
|
+
? el("div", { style: st.note }, t("数据持久化:") + dataPath + t("(每次调用实时落盘,插件重启后自动恢复,最多保留 100000 条)"))
|
|
1477
|
+
: el("div", { style: st.note, color: "#ff6b6b", opacity: 1 }, t("持久化未启用:") + (data.persistError || t("未知原因")))
|
|
1315
1478
|
);
|
|
1316
1479
|
}
|
|
1317
1480
|
|
|
1318
1481
|
// ── Balance panel ──
|
|
1319
1482
|
function BalancePanel() {
|
|
1483
|
+
var lang = useLang();
|
|
1320
1484
|
var state = React.useState({ status: "idle", data: null, error: "" });
|
|
1321
1485
|
var s = state[0], setS = state[1];
|
|
1322
1486
|
var providerState = React.useState("deepseek");
|
|
@@ -1328,10 +1492,11 @@ window.__ModuleLoader__.load({
|
|
|
1328
1492
|
var saveState = React.useState({ status: "idle", error: "" });
|
|
1329
1493
|
var saving = saveState[0], setSaving = saveState[1];
|
|
1330
1494
|
var providers = [
|
|
1331
|
-
{ id: "deepseek", name: "DeepSeek", hint: "DEEPSEEK_API_KEY(推理 Key)" },
|
|
1332
|
-
{ id: "siliconflow", name: "SiliconFlow", hint: "模型设置中 Provider ID 或显示名为 siliconflow 的提供商 API Key", actionUrl: "https://cloud.siliconflow.cn/account/ak", actionLabel: "打开 SiliconFlow API 密钥" },
|
|
1333
|
-
{ id: "digitalocean", name: "DigitalOcean", hint: "账户级 dop_v1_ Personal Access Token;Read Only(api:read)或 billing:read", actionUrl: "https://cloud.digitalocean.com/account/api/tokens", actionLabel: "创建 DigitalOcean Account API Token" },
|
|
1334
|
-
{ id: "amd-gpu-cloud", name: "AMD GPU Cloud", hint: "暂无公开余额 API,仅支持控制台查看", unsupported: true, actionUrl: "https://www.amd.com/en/developer/resources/cloud-access/amd-developer-cloud.html", actionLabel: "打开 AMD Developer Cloud" }
|
|
1495
|
+
{ id: "deepseek", name: "DeepSeek", hint: t("DEEPSEEK_API_KEY(推理 Key)") },
|
|
1496
|
+
{ id: "siliconflow", name: "SiliconFlow", hint: t("模型设置中 Provider ID 或显示名为 siliconflow 的提供商 API Key"), actionUrl: "https://cloud.siliconflow.cn/account/ak", actionLabel: t("打开 SiliconFlow API 密钥") },
|
|
1497
|
+
{ id: "digitalocean", name: "DigitalOcean", hint: t("账户级 dop_v1_ Personal Access Token;Read Only(api:read)或 billing:read"), actionUrl: "https://cloud.digitalocean.com/account/api/tokens", actionLabel: t("创建 DigitalOcean Account API Token") },
|
|
1498
|
+
{ id: "amd-gpu-cloud", name: "AMD GPU Cloud", hint: t("暂无公开余额 API,仅支持控制台查看"), unsupported: true, actionUrl: "https://www.amd.com/en/developer/resources/cloud-access/amd-developer-cloud.html", actionLabel: t("打开 AMD Developer Cloud") },
|
|
1499
|
+
{ id: "qwen-token-plan", name: "百炼 Token Plan", hint: t("百炼控制台 OAuth 登录(~/.bailian/config.json 的 access_token,用 bl auth login --console 生成)"), actionUrl: "https://bailian.console.aliyun.com/cli", actionLabel: t("打开百炼 CLI 登录说明") }
|
|
1335
1500
|
];
|
|
1336
1501
|
var selected = providers[0];
|
|
1337
1502
|
for (var pi = 0; pi < providers.length; pi++) if (providers[pi].id === provider) selected = providers[pi];
|
|
@@ -1341,7 +1506,7 @@ window.__ModuleLoader__.load({
|
|
|
1341
1506
|
setS({ status: "loading", data: null, error: "" });
|
|
1342
1507
|
api({ action: "balance", provider: target }).then(function (res) {
|
|
1343
1508
|
if (res && res.ok) setS({ status: "done", data: res, error: "" });
|
|
1344
|
-
else setS({ status: "error", data: res, error: (res && res.error) || "查询失败" });
|
|
1509
|
+
else setS({ status: "error", data: res, error: (res && res.error) || t("查询失败") });
|
|
1345
1510
|
}).catch(function (e) {
|
|
1346
1511
|
setS({ status: "error", data: null, error: String((e && e.message) || e) });
|
|
1347
1512
|
});
|
|
@@ -1351,7 +1516,7 @@ window.__ModuleLoader__.load({
|
|
|
1351
1516
|
setCredential({ status: "loading", configured: false, source: "", writable: true, masked: "" });
|
|
1352
1517
|
api({ action: "balanceCredentialStatus", provider: "digitalocean" }).then(function (res) {
|
|
1353
1518
|
if (!res || !res.ok) {
|
|
1354
|
-
setCredential({ status: "error", configured: false, source: "", writable: false, masked: "", error: (res && res.error) || "无法读取凭据状态" });
|
|
1519
|
+
setCredential({ status: "error", configured: false, source: "", writable: false, masked: "", error: (res && res.error) || t("无法读取凭据状态") });
|
|
1355
1520
|
return;
|
|
1356
1521
|
}
|
|
1357
1522
|
setCredential({ status: "done", configured: !!res.configured, source: res.source || "", writable: res.writable !== false, masked: res.masked || "" });
|
|
@@ -1364,13 +1529,13 @@ window.__ModuleLoader__.load({
|
|
|
1364
1529
|
function saveDigitalOceanCredential() {
|
|
1365
1530
|
var value = String(digitalOceanToken || "").trim();
|
|
1366
1531
|
if (!value) {
|
|
1367
|
-
setSaving({ status: "error",
|
|
1532
|
+
setSaving({ status: "error", errorKey: "请输入以 dop_v1_ 开头的 DigitalOcean 账户 PAT。" });
|
|
1368
1533
|
return;
|
|
1369
1534
|
}
|
|
1370
1535
|
setSaving({ status: "saving", error: "" });
|
|
1371
1536
|
api({ action: "saveBalanceCredential", provider: "digitalocean", value: value }).then(function (res) {
|
|
1372
1537
|
if (!res || !res.ok) {
|
|
1373
|
-
setSaving({ status: "error", error: (res && res.error) || "保存失败" });
|
|
1538
|
+
setSaving({ status: "error", error: (res && res.error) || t("保存失败") });
|
|
1374
1539
|
return;
|
|
1375
1540
|
}
|
|
1376
1541
|
setDigitalOceanToken("");
|
|
@@ -1396,27 +1561,48 @@ window.__ModuleLoader__.load({
|
|
|
1396
1561
|
var symbol = currencySymbol(d && d.currency);
|
|
1397
1562
|
var actionUrl = d && d.credentialHelpUrl || selected.actionUrl || "";
|
|
1398
1563
|
var credentialMeta = [];
|
|
1399
|
-
if (d && d.credentialName) credentialMeta.push("使用凭据 " + d.credentialName);
|
|
1400
|
-
if (d && d.credentialSource) credentialMeta.push("来源 " + d.credentialSource);
|
|
1401
|
-
if (d && d.modelProviderRoute) credentialMeta.push("模型提供商 " + d.modelProviderRoute);
|
|
1402
|
-
if (d && d.endpoint) credentialMeta.push("端点 " + d.endpoint);
|
|
1403
|
-
var
|
|
1564
|
+
if (d && d.credentialName) credentialMeta.push(t("使用凭据 ") + d.credentialName);
|
|
1565
|
+
if (d && d.credentialSource) credentialMeta.push(t("来源 ") + d.credentialSource);
|
|
1566
|
+
if (d && d.modelProviderRoute) credentialMeta.push(t("模型提供商 ") + d.modelProviderRoute);
|
|
1567
|
+
if (d && d.endpoint) credentialMeta.push(t("端点 ") + d.endpoint);
|
|
1568
|
+
var isQwen = d && d.provider === "qwen-token-plan";
|
|
1569
|
+
var heroLabel = d && d.balanceLabelKey ? tb(d.balanceLabelKey) : (d && d.balanceLabel ? t(d.balanceLabel) : ((d && d.providerName) || selected.name) + t(" · 账户余额"));
|
|
1404
1570
|
var heroStatus = d && d.provider === "digitalocean"
|
|
1405
|
-
? (d.balanceKind === "credit" ? "USD · 预付款/信用余额" : d.balanceKind === "due" ? "USD · 待结算金额" : "USD · 当前无余额")
|
|
1406
|
-
:
|
|
1571
|
+
? (d.balanceKind === "credit" ? t("USD · 预付款/信用余额") : d.balanceKind === "due" ? t("USD · 待结算金额") : t("USD · 当前无余额"))
|
|
1572
|
+
: isQwen
|
|
1573
|
+
? t("百炼 Token Plan · 1 周配额")
|
|
1574
|
+
: (d && d.currency) + (d && d.isAvailable === true ? t(" · 账户可用") : d && d.isAvailable === false ? t(" · 账户不可用") : t(" · 查询成功"));
|
|
1575
|
+
var qwenQuota = isQwen ? (d && d.qwenQuota) : null;
|
|
1576
|
+
var qwenRatio = qwenQuota && qwenQuota.ratio != null ? Math.round(qwenQuota.ratio * 1000) / 10 : 0;
|
|
1577
|
+
var qwenPctText = qwenQuota && qwenQuota.usedPercent ? qwenQuota.usedPercent : "0%";
|
|
1578
|
+
// 本周配额:计算今天在开始~结束周期中的位置(北京时间)
|
|
1579
|
+
var weekPos = 0;
|
|
1580
|
+
var todayText = "—";
|
|
1581
|
+
if (isQwen && qwenQuota && qwenQuota.weekStart && qwenQuota.weekEnd) {
|
|
1582
|
+
var startMs = Date.parse(qwenQuota.weekStart + "T00:00:00+08:00");
|
|
1583
|
+
var endMs = Date.parse(qwenQuota.weekEnd + "T00:00:00+08:00");
|
|
1584
|
+
var bjNow = new Date(Date.now() + 8 * 3600 * 1000);
|
|
1585
|
+
var todayMs = Date.UTC(bjNow.getUTCFullYear(), bjNow.getUTCMonth(), bjNow.getUTCDate()) - 8 * 3600 * 1000;
|
|
1586
|
+
if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs) {
|
|
1587
|
+
weekPos = Math.max(2, Math.min(98, ((todayMs - startMs) / (endMs - startMs)) * 100));
|
|
1588
|
+
var mm = String(bjNow.getUTCMonth() + 1).padStart(2, "0");
|
|
1589
|
+
var dd = String(bjNow.getUTCDate()).padStart(2, "0");
|
|
1590
|
+
todayText = mm + "." + dd;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1407
1593
|
|
|
1408
1594
|
return el("div", { style: st.root },
|
|
1409
1595
|
el("div", { style: st.head },
|
|
1410
1596
|
el("div", null,
|
|
1411
|
-
el("div", { style: st.title }, "剩余余额查询"),
|
|
1412
|
-
el("div", { style: st.sub }, "查询 DeepSeek、SiliconFlow
|
|
1597
|
+
el("div", { style: st.title }, t("剩余余额查询")),
|
|
1598
|
+
el("div", { style: st.sub }, t("查询 DeepSeek、SiliconFlow、DigitalOcean 与百炼 Token Plan"))
|
|
1413
1599
|
),
|
|
1414
1600
|
el("div", { style: st.actions },
|
|
1415
1601
|
selected.unsupported
|
|
1416
|
-
? el("a", { style: Object.assign({}, st.btn, { textDecoration: "none" }), href: selected.actionUrl, target: "_blank", rel: "noreferrer" }, "打开控制台")
|
|
1602
|
+
? el("a", { style: Object.assign({}, st.btn, { textDecoration: "none" }), href: selected.actionUrl, target: "_blank", rel: "noreferrer" }, t("打开控制台"))
|
|
1417
1603
|
: provider === "digitalocean" && !credential.configured
|
|
1418
1604
|
? null
|
|
1419
|
-
: el("button", { style: s.status === "loading" ? st.btnDisabled : st.btn, disabled: s.status === "loading", onClick: query }, s.status === "loading" ? "查询中…" : provider === "digitalocean" ? "查询账单" : "查询余额")
|
|
1605
|
+
: el("button", { style: s.status === "loading" ? st.btnDisabled : st.btn, disabled: s.status === "loading", onClick: query }, s.status === "loading" ? t("查询中…") : provider === "digitalocean" ? t("查询账单") : t("查询余额"))
|
|
1420
1606
|
)
|
|
1421
1607
|
),
|
|
1422
1608
|
el("div", { style: st.subtabBar },
|
|
@@ -1428,54 +1614,90 @@ window.__ModuleLoader__.load({
|
|
|
1428
1614
|
}, p.name);
|
|
1429
1615
|
})
|
|
1430
1616
|
),
|
|
1431
|
-
el("div", { style: st.note }, "所需凭据:" + selected.hint),
|
|
1617
|
+
el("div", { style: st.note }, t("所需凭据:") + selected.hint),
|
|
1432
1618
|
s.status === "error"
|
|
1433
1619
|
? el("div", { style: d && d.unsupported ? st.infobox : st.errbox },
|
|
1434
|
-
el("div", { style: d && d.unsupported ? st.infoboxTitle : st.errboxTitle }, d && d.unsupported ? "仅支持控制台查看" : "查询失败"),
|
|
1620
|
+
el("div", { style: d && d.unsupported ? st.infoboxTitle : st.errboxTitle }, d && d.unsupported ? t("仅支持控制台查看") : t("查询失败")),
|
|
1435
1621
|
el("div", null, s.error),
|
|
1436
1622
|
el("div", { style: st.note, marginTop: 6 }, selected.unsupported
|
|
1437
|
-
? "插件不会尝试未经公开文档确认的端点,也不会把 AMD 推理 Key 当作账单凭据。"
|
|
1438
|
-
: "请确认凭据类型正确、权限包含余额/账单读取,且网络可访问服务商官方 API。"),
|
|
1439
|
-
actionUrl ? el("a", { style: st.actionLink, href: actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel || "打开服务商凭据页面") : null
|
|
1623
|
+
? t("插件不会尝试未经公开文档确认的端点,也不会把 AMD 推理 Key 当作账单凭据。")
|
|
1624
|
+
: t("请确认凭据类型正确、权限包含余额/账单读取,且网络可访问服务商官方 API。")),
|
|
1625
|
+
actionUrl ? el("a", { style: st.actionLink, href: actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel || t("打开服务商凭据页面")) : null
|
|
1440
1626
|
)
|
|
1441
1627
|
: null,
|
|
1442
|
-
s.status === "loading" && !d ? el(LoadingView, { text: "正在查询余额…" }) : null,
|
|
1443
|
-
d && d.ok
|
|
1628
|
+
s.status === "loading" && !d ? el(LoadingView, { text: t("正在查询余额…") }) : null,
|
|
1629
|
+
d && d.ok && isQwen
|
|
1444
1630
|
? el("div", { style: st.hero },
|
|
1445
|
-
el("div", { style: st.
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1631
|
+
el("div", { style: st.qwenHero },
|
|
1632
|
+
el("div", { style: st.qwenHeroTitle }, t("百炼 Token Plan")),
|
|
1633
|
+
el("div", { style: st.qwenBarWrap },
|
|
1634
|
+
el("div", { style: st.qwenBar },
|
|
1635
|
+
el("div", { style: Object.assign({}, st.qwenBarFill, { width: qwenRatio + "%" }) })
|
|
1636
|
+
)
|
|
1637
|
+
),
|
|
1638
|
+
el("div", { style: st.qwenHeroUsed }, t("已用 ") + qwenPctText)
|
|
1639
|
+
)
|
|
1640
|
+
)
|
|
1641
|
+
: d && d.ok
|
|
1642
|
+
? el("div", { style: st.hero },
|
|
1643
|
+
el("div", { style: st.heroLabel }, heroLabel),
|
|
1644
|
+
el("div", { style: st.heroValue }, symbol + " " + fmtBalance(d.totalBalance)),
|
|
1645
|
+
el("div", { style: st.heroCurrency }, heroStatus),
|
|
1646
|
+
d.isAvailable == null ? null : el("span", { style: d.isAvailable ? st.badgeHit : st.badgePeak }, d.isAvailable ? t("可用") : t("不可用"))
|
|
1647
|
+
)
|
|
1648
|
+
: null,
|
|
1649
|
+
d && d.ok && isQwen
|
|
1650
|
+
? el("div", { style: st.qwenWeekCard },
|
|
1651
|
+
el("div", { style: st.qwenWeekLabel }, t("本周配额")),
|
|
1652
|
+
el("div", { style: st.qwenWeekRow },
|
|
1653
|
+
el("div", { style: st.qwenWeekItem },
|
|
1654
|
+
el("span", { style: st.qwenWeekHint }, t("开始")),
|
|
1655
|
+
el("span", { style: st.qwenWeekVal }, qwenQuota && qwenQuota.weekStart ? fmtShortDate(qwenQuota.weekStart) : "—")
|
|
1656
|
+
),
|
|
1657
|
+
el("div", { style: st.qwenWeekLine },
|
|
1658
|
+
el("div", { style: st.qwenWeekTrack },
|
|
1659
|
+
el("div", { style: Object.assign({}, st.qwenWeekProgress, { width: weekPos + "%" }) })
|
|
1660
|
+
),
|
|
1661
|
+
el("div", { style: Object.assign({}, st.qwenWeekToday, { left: weekPos + "%" }) },
|
|
1662
|
+
el("span", { style: st.qwenWeekTodayDot }),
|
|
1663
|
+
el("span", { style: st.qwenWeekTodayDate }, todayText)
|
|
1664
|
+
)
|
|
1665
|
+
),
|
|
1666
|
+
el("div", { style: st.qwenWeekItemR },
|
|
1667
|
+
el("span", { style: st.qwenWeekHint }, t("结束")),
|
|
1668
|
+
el("span", { style: st.qwenWeekVal }, qwenQuota && qwenQuota.weekEnd ? fmtShortDate(qwenQuota.weekEnd) : "—")
|
|
1669
|
+
)
|
|
1670
|
+
)
|
|
1449
1671
|
)
|
|
1450
1672
|
: null,
|
|
1451
|
-
d && d.ok
|
|
1673
|
+
d && d.ok && !isQwen
|
|
1452
1674
|
? el("div", { style: st.cards },
|
|
1453
1675
|
(d.details || []).map(function (item, idx) {
|
|
1454
|
-
return el(Card, { key: String(idx), label: item.
|
|
1676
|
+
return el(Card, { key: String(idx), label: tb(item.labelKey), value: symbol + " " + fmtBalance(item.value), hint: tb(item.hintKey) });
|
|
1455
1677
|
}),
|
|
1456
|
-
d.generatedAt ? el(Card, { label: "账单更新时间", value: fmtTime(Date.parse(d.generatedAt)), hint: d.generatedAt }) : null,
|
|
1457
|
-
el(Card, { label: "查询时间", value: fmtTime(d.queriedAt), hint: "北京时间" })
|
|
1678
|
+
d.generatedAt ? el(Card, { label: t("账单更新时间"), value: fmtTime(Date.parse(d.generatedAt)), hint: d.generatedAt }) : null,
|
|
1679
|
+
el(Card, { label: t("查询时间"), value: fmtTime(d.queriedAt), hint: t("北京时间") })
|
|
1458
1680
|
)
|
|
1459
1681
|
: null,
|
|
1460
1682
|
provider === "digitalocean"
|
|
1461
1683
|
? el("div", { style: Object.assign({}, st.infobox, { marginTop: 18 }) },
|
|
1462
1684
|
el("div", { style: st.infoboxTitle }, "DigitalOcean Account API"),
|
|
1463
|
-
el("div", null, "请创建账户级 Personal Access Token。可选 Read Only(api:read,包含 billing:read),或自定义 billing:read;不要使用 Gradient AI 推理 Key。"),
|
|
1685
|
+
el("div", null, t("请创建账户级 Personal Access Token。可选 Read Only(api:read,包含 billing:read),或自定义 billing:read;不要使用 Gradient AI 推理 Key。")),
|
|
1464
1686
|
el("a", { style: st.actionLink, href: selected.actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel),
|
|
1465
1687
|
el("div", { style: st.note, marginTop: 8 }, credential.status === "loading"
|
|
1466
|
-
? "正在检查已保存的 Token…"
|
|
1688
|
+
? t("正在检查已保存的 Token…")
|
|
1467
1689
|
: credential.configured
|
|
1468
|
-
? "已保存:" + (credential.masked || "••••••••••••") + (credential.source ? "(来源:" + credential.source + ")" : "")
|
|
1690
|
+
? t("已保存:") + (credential.masked || "••••••••••••") + (credential.source ? t("(来源:") + credential.source + ")" : "")
|
|
1469
1691
|
: credential.status === "error"
|
|
1470
|
-
? "无法读取 Token 状态:" + (credential.error || "未知错误")
|
|
1471
|
-
: "尚未保存 DIGITALOCEAN_TOKEN。"),
|
|
1692
|
+
? t("无法读取 Token 状态:") + (credential.error || t("未知错误"))
|
|
1693
|
+
: t("尚未保存 DIGITALOCEAN_TOKEN。")),
|
|
1472
1694
|
el("div", { style: st.actions, marginTop: 8 },
|
|
1473
1695
|
el("input", {
|
|
1474
1696
|
type: "password",
|
|
1475
1697
|
autoComplete: "new-password",
|
|
1476
1698
|
spellCheck: false,
|
|
1477
1699
|
style: st.input,
|
|
1478
|
-
placeholder: credential.configured ? "输入新的 dop_v1_ Token 可替换(当前值已隐藏)" : "dop_v1_…",
|
|
1700
|
+
placeholder: credential.configured ? t("输入新的 dop_v1_ Token 可替换(当前值已隐藏)") : "dop_v1_…",
|
|
1479
1701
|
value: digitalOceanToken,
|
|
1480
1702
|
disabled: saving.status === "saving" || (credential.configured && !credential.writable),
|
|
1481
1703
|
onChange: function (e) { setDigitalOceanToken(e.target.value); }
|
|
@@ -1484,39 +1706,47 @@ window.__ModuleLoader__.load({
|
|
|
1484
1706
|
style: saving.status === "saving" || (credential.configured && !credential.writable) ? st.btnDisabled : st.btnPrimary,
|
|
1485
1707
|
disabled: saving.status === "saving" || (credential.configured && !credential.writable),
|
|
1486
1708
|
onClick: saveDigitalOceanCredential
|
|
1487
|
-
}, saving.status === "saving" ? "保存中…" : "保存并查询")
|
|
1709
|
+
}, saving.status === "saving" ? t("保存中…") : t("保存并查询"))
|
|
1488
1710
|
),
|
|
1489
1711
|
credential.configured && !credential.writable
|
|
1490
|
-
? el("div", { style: st.note, marginTop: 6 }, "当前 Token 来自只读环境变量;请在原来源中修改,页面不会覆盖它。")
|
|
1712
|
+
? el("div", { style: st.note, marginTop: 6 }, t("当前 Token 来自只读环境变量;请在原来源中修改,页面不会覆盖它。"))
|
|
1491
1713
|
: null,
|
|
1492
|
-
saving.status === "done" ? el("div", { style: st.note, marginTop: 6, color: "#2ecc71", opacity: 1 }, "Token 已安全保存;页面和 API 响应不会回传明文。") : null,
|
|
1493
|
-
saving.status === "error" ? el("div", { style: st.err, marginTop: 6 }, saving.error) : null
|
|
1714
|
+
saving.status === "done" ? el("div", { style: st.note, marginTop: 6, color: "#2ecc71", opacity: 1 }, t("Token 已安全保存;页面和 API 响应不会回传明文。")) : null,
|
|
1715
|
+
saving.status === "error" ? el("div", { style: st.err, marginTop: 6 }, (saving.errorKey ? t(saving.errorKey) : saving.error)) : null
|
|
1494
1716
|
)
|
|
1495
1717
|
: null,
|
|
1496
1718
|
d && d.ok && d.provider === "siliconflow"
|
|
1497
1719
|
? el("div", { style: Object.assign({}, st.infobox, { marginTop: 22 }) },
|
|
1498
|
-
el("div", { style: st.infoboxTitle }, "SiliconFlow 查询说明"),
|
|
1720
|
+
el("div", { style: st.infoboxTitle }, t("SiliconFlow 查询说明")),
|
|
1499
1721
|
d.zeroBalance
|
|
1500
1722
|
? el("div", null,
|
|
1501
|
-
el("div", null, "公开 API 已成功返回 ¥0.00。SiliconFlow 的 /v1/user/info 当前不返回代金券或历史用量;控制台可用总额可能非零。这里忠实展示 API 原始余额字段,不把它等同于控制台完整额度。"),
|
|
1723
|
+
el("div", null, t("公开 API 已成功返回 ¥0.00。SiliconFlow 的 /v1/user/info 当前不返回代金券或历史用量;控制台可用总额可能非零。这里忠实展示 API 原始余额字段,不把它等同于控制台完整额度。")),
|
|
1502
1724
|
el("a", { style: st.actionLink, href: selected.actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel)
|
|
1503
1725
|
)
|
|
1504
|
-
: el("div", null, "余额卡片展示 SiliconFlow /v1/user/info 公开 API 返回的原始余额字段。"),
|
|
1505
|
-
el("div", { style: { marginTop: 12, fontWeight: 600 } }, "返回字段"),
|
|
1726
|
+
: el("div", null, t("余额卡片展示 SiliconFlow /v1/user/info 公开 API 返回的原始余额字段。")),
|
|
1727
|
+
el("div", { style: { marginTop: 12, fontWeight: 600 } }, t("返回字段")),
|
|
1506
1728
|
(d.fieldDefinitions || []).map(function (item) {
|
|
1507
|
-
return el("div", { key: item.name, style: { marginTop: 4 } }, item.name + ":" + item.
|
|
1729
|
+
return el("div", { key: item.name, style: { marginTop: 4 } }, item.name + ":" + tb(item.meaningKey));
|
|
1508
1730
|
})
|
|
1509
1731
|
)
|
|
1510
1732
|
: null,
|
|
1511
1733
|
provider === "siliconflow"
|
|
1512
1734
|
? el("div", { style: Object.assign({}, st.infobox, { marginTop: 14 }) },
|
|
1513
|
-
el("div", { style: st.infoboxTitle }, "凭据读取规则"),
|
|
1514
|
-
el("div", null, "插件只检查“设置 → 模型”中 Provider ID 或显示名为 siliconflow 的提供商,并读取其 apiKeyEnv 对应的已保存 API Key。"),
|
|
1515
|
-
el("div", { style: st.note, marginTop: 6 }, "如果未找到提供商、未填写 API Key 或凭据引用失效,插件会停止查询并说明需要修复的配置;不会回退到其他服务商的 Key。")
|
|
1735
|
+
el("div", { style: st.infoboxTitle }, t("凭据读取规则")),
|
|
1736
|
+
el("div", null, t("插件只检查“设置 → 模型”中 Provider ID 或显示名为 siliconflow 的提供商,并读取其 apiKeyEnv 对应的已保存 API Key。")),
|
|
1737
|
+
el("div", { style: st.note, marginTop: 6 }, t("如果未找到提供商、未填写 API Key 或凭据引用失效,插件会停止查询并说明需要修复的配置;不会回退到其他服务商的 Key。"))
|
|
1516
1738
|
)
|
|
1517
1739
|
: null,
|
|
1518
|
-
|
|
1519
|
-
|
|
1740
|
+
provider === "qwen-token-plan"
|
|
1741
|
+
? el("div", { style: Object.assign({}, st.infobox, { marginTop: 14 }) },
|
|
1742
|
+
el("div", { style: st.infoboxTitle }, t("百炼 Token Plan 查询说明")),
|
|
1743
|
+
el("div", null, t("本查询复用百炼 CLI(bl)的“控制台登录”OAuth token,不需要阿里云 AccessKey,权限面很小。")),
|
|
1744
|
+
el("div", { style: st.note, marginTop: 6 }, t("如尚未登录,请先在终端执行:bl auth login --console,然后回到本页点“查询余额”。")),
|
|
1745
|
+
el("a", { style: st.actionLink, href: selected.actionUrl, target: "_blank", rel: "noreferrer" }, selected.actionLabel || t("打开百炼 CLI 登录说明"))
|
|
1746
|
+
)
|
|
1747
|
+
: null,
|
|
1748
|
+
d && d.ok ? el("div", { style: st.note }, (d.sourceNoteKey ? tb(d.sourceNoteKey) : (d.sourceNote ? t(d.sourceNote) : "")) + (credentialMeta.length ? " " + credentialMeta.join(__LANG === "en" ? "; " : ";") + (__LANG === "en" ? "." : "。") : "")) : null,
|
|
1749
|
+
el("div", { style: st.note }, t("DigitalOcean 查询的是主账户 Billing API;DigitalOcean AI 推理 Key 与 AMD GPU Cloud 推理 Key 均不能直接查询账单。"))
|
|
1520
1750
|
);
|
|
1521
1751
|
}
|
|
1522
1752
|
|
|
@@ -1530,33 +1760,55 @@ window.__ModuleLoader__.load({
|
|
|
1530
1760
|
|
|
1531
1761
|
slots.inject("conversation.view", function () {
|
|
1532
1762
|
return slots.register(
|
|
1533
|
-
{ name: "conversation.view", id: "usage-cost-view", order: 20, label: "用量与消耗" },
|
|
1763
|
+
{ name: "conversation.view", id: "usage-cost-view", order: 20, label: t("用量与消耗") },
|
|
1534
1764
|
function () { return el("div", { style: st.tab }, el(UsagePanel, { timer: timer })); }
|
|
1535
1765
|
);
|
|
1536
1766
|
});
|
|
1537
1767
|
|
|
1538
1768
|
slots.inject("conversation.view", function () {
|
|
1539
1769
|
return slots.register(
|
|
1540
|
-
{ name: "conversation.view", id: "balance-view", order: 30, label: "剩余余额查询" },
|
|
1770
|
+
{ name: "conversation.view", id: "balance-view", order: 30, label: t("剩余余额查询") },
|
|
1541
1771
|
function () { return el("div", { style: st.tab }, el(BalancePanel, null)); }
|
|
1542
1772
|
);
|
|
1543
1773
|
});
|
|
1544
1774
|
|
|
1545
1775
|
slots.inject("settings.section", function () {
|
|
1546
1776
|
return slots.register(
|
|
1547
|
-
{ name: "settings.section", id: "usage-cost", order: 30, label: "用量与消耗" },
|
|
1777
|
+
{ name: "settings.section", id: "usage-cost", order: 30, label: t("用量与消耗") },
|
|
1548
1778
|
function () { return el(UsagePanel, { timer: timer }); }
|
|
1549
1779
|
);
|
|
1550
1780
|
});
|
|
1551
1781
|
|
|
1552
1782
|
slots.inject("settings.section", function () {
|
|
1553
1783
|
return slots.register(
|
|
1554
|
-
{ name: "settings.section", id: "balance", order: 31, label: "剩余余额查询" },
|
|
1784
|
+
{ name: "settings.section", id: "balance", order: 31, label: t("剩余余额查询") },
|
|
1555
1785
|
function () { return el(BalancePanel, null); }
|
|
1556
1786
|
);
|
|
1557
1787
|
});
|
|
1558
1788
|
}
|
|
1559
1789
|
|
|
1790
|
+
// test/debug surface: instant-switch guarantees and locale formatters
|
|
1791
|
+
exports.__i18n = {
|
|
1792
|
+
t: t,
|
|
1793
|
+
tb: tb,
|
|
1794
|
+
msgText: __msgText,
|
|
1795
|
+
setLang: setLang,
|
|
1796
|
+
dayLabel: dayLabel,
|
|
1797
|
+
fmtMonthLabel: fmtMonthLabel,
|
|
1798
|
+
dailyStatsTitle: dailyStatsTitle,
|
|
1799
|
+
weekdayLabels: weekdayLabels
|
|
1800
|
+
};
|
|
1801
|
+
// test/debug surface: instant-switch guarantees and locale formatters
|
|
1802
|
+
exports.__i18n = {
|
|
1803
|
+
t: t,
|
|
1804
|
+
tb: tb,
|
|
1805
|
+
msgText: __msgText,
|
|
1806
|
+
setLang: setLang,
|
|
1807
|
+
dayLabel: dayLabel,
|
|
1808
|
+
fmtMonthLabel: fmtMonthLabel,
|
|
1809
|
+
dailyStatsTitle: dailyStatsTitle,
|
|
1810
|
+
weekdayLabels: weekdayLabels
|
|
1811
|
+
};
|
|
1560
1812
|
exports.inject = inject;
|
|
1561
1813
|
exports.apply = apply;
|
|
1562
1814
|
return module.exports;
|