dsh-bottom-info-bar 1.5.0 → 1.7.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/lib/client.js +142 -19
- package/lib/index.js +1021 -74
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -6,7 +6,7 @@ var module = { exports: {} }; var exports = module.exports;
|
|
|
6
6
|
// - styles.insert(css) → document 注入 <style>(installStyles)
|
|
7
7
|
// - React 由 bundle 的 require('react') 提供(seed 模块)
|
|
8
8
|
// 样式策略:① 整个数据令牌加粗(.bi-num 700)② 服务商名加粗 ③ 高峰价与低余额用警示红、空闲价用绿色
|
|
9
|
-
// 显示行为:①
|
|
9
|
+
// 显示行为:① 本会话花费始终显示——新会话/对话刚开始(尚无记账)时显示"本会话 ¥0.000",
|
|
10
10
|
// hover 仍可查看持久化的 今天/近一月/全部;
|
|
11
11
|
// ② 完整模式下原生统计行无 steps 门槛,对话刚开始即显示"0 轮 · 0 步"。
|
|
12
12
|
// 失败策略(AUDIT-CODE-REVIEW 缺陷 #1):逐接口容错——
|
|
@@ -70,10 +70,10 @@ function rpc(method, args, externalSignal) {
|
|
|
70
70
|
|
|
71
71
|
// load() 的逐接口容错状态合并(模块级纯函数,供单测提取):
|
|
72
72
|
// 成功端点 → 写新值 + 清除错误;失败端点 → 保留旧值(无旧数据则为 null)+ 记录错误信息。
|
|
73
|
-
// results 与端点顺序一一对应:balance / pricing / usage / billingMode / sub。
|
|
73
|
+
// results 与端点顺序一一对应:balance / pricing / usage / billingMode / sub / billing。
|
|
74
74
|
function mergeLoadResults(prev, results) {
|
|
75
|
-
const keys = ['balance', 'pricing', 'usage', 'billingMode', 'sub'];
|
|
76
|
-
const next = { loading: false, errors: { balance: null, pricing: null, usage: null, billingMode: null, sub: null } };
|
|
75
|
+
const keys = ['balance', 'pricing', 'usage', 'billingMode', 'sub', 'billing'];
|
|
76
|
+
const next = { loading: false, errors: { balance: null, pricing: null, usage: null, billingMode: null, sub: null, billing: null } };
|
|
77
77
|
for (let i = 0; i < keys.length; i++) {
|
|
78
78
|
const key = keys[i];
|
|
79
79
|
const r = results[i];
|
|
@@ -117,7 +117,7 @@ function installStyles() {
|
|
|
117
117
|
.bi-sep { color: var(--bi-separator); margin: 0 6px; }
|
|
118
118
|
/* 服务商名等一般强调:加粗 600 */
|
|
119
119
|
.bi-root b { color: var(--bi-label-primary); font-weight: 600; }
|
|
120
|
-
/* 数字:加粗 700
|
|
120
|
+
/* 数字:加粗 700(余额/倒计时/本会话花费/原生统计数字) */
|
|
121
121
|
.bi-root b.bi-num { font-weight: 700; }
|
|
122
122
|
/* 标签与数据不用字符空格拼接:统一由 4px 布局间距控制,避免中英文/数字字宽造成忽松忽紧。 */
|
|
123
123
|
.bi-metric { display: inline-flex; align-items: baseline; white-space: nowrap; }
|
|
@@ -242,8 +242,8 @@ module.exports = {
|
|
|
242
242
|
const usageProj = props.useProjection ? props.useProjection('tokenUsage') : undefined;
|
|
243
243
|
|
|
244
244
|
const [state, setState] = React.useState({
|
|
245
|
-
loading: true, balance: null, pricing: null, usage: null, billingMode: null, sub: null,
|
|
246
|
-
errors: { balance: null, pricing: null, usage: null, billingMode: null, sub: null },
|
|
245
|
+
loading: true, balance: null, pricing: null, usage: null, billingMode: null, sub: null, billing: null,
|
|
246
|
+
errors: { balance: null, pricing: null, usage: null, billingMode: null, sub: null, billing: null },
|
|
247
247
|
});
|
|
248
248
|
// 版本信息由 host 在启动时从 package.json 读取;无论是否有新版,都用于服务商/模型 hover 展示。
|
|
249
249
|
const [updateInfo, setUpdateInfo] = React.useState(null);
|
|
@@ -352,6 +352,7 @@ module.exports = {
|
|
|
352
352
|
rpc('getUsageSummary', Object.assign({ sessionId: sessionId }, selectionArgs), signal),
|
|
353
353
|
rpc('getBillingMode', selectionArgs, signal),
|
|
354
354
|
rpc('getSubscriptionSnapshot', selectionArgs, signal),
|
|
355
|
+
rpc('getBillingStatus', selectionArgs, signal),
|
|
355
356
|
]).then(function (results) {
|
|
356
357
|
// Do not allow a late A response to overwrite newly active B.
|
|
357
358
|
if ((signal && signal.aborted) || requestVersion !== loadVersionRef.current) return;
|
|
@@ -406,9 +407,13 @@ module.exports = {
|
|
|
406
407
|
|| state.pricing.provider !== activeSessionModel.provider || state.pricing.model !== activeSessionModel.model)
|
|
407
408
|
? { provider: activeSessionModel.provider, model: activeSessionModel.model, providerDisplay: activeSessionModel.providerDisplay, modelDisplay: activeSessionModel.modelDisplay, mode: 'unknown', acceptsImageInput: activeSessionModel.acceptsImageInput }
|
|
408
409
|
: (waitForSessionModel ? null : state.pricing);
|
|
410
|
+
// v1.6 T7:订阅 provider 集合提取为共享常量,消除两端硬编码漂移
|
|
411
|
+
var SUBSCRIPTION_PROVIDERS = ['codex', 'chatgpt', 'opencode-go', 'opencode', 'openai-codex', 'zai', 'zai-coding-cn', 'xiaomi-token-plan-cn', 'xiaomi-token-plan-sgp', 'xiaomi-token-plan-ams'];
|
|
412
|
+
// v1.7 FR-14:云账单 provider 集合(账单型显示,与余额/额度互斥)
|
|
413
|
+
var BILLING_PROVIDERS = ['together', 'fireworks', 'amazon-bedrock', 'cloudflare-ai-gateway', 'cloudflare-workers-ai'];
|
|
409
414
|
const visibleBillingMode = activeSessionModel && (!state.billingMode
|
|
410
415
|
|| state.billingMode.provider !== activeSessionModel.provider || state.billingMode.model !== activeSessionModel.model)
|
|
411
|
-
? { provider: activeSessionModel.provider, model: activeSessionModel.model, mode:
|
|
416
|
+
? { provider: activeSessionModel.provider, model: activeSessionModel.model, mode: SUBSCRIPTION_PROVIDERS.indexOf(activeSessionModel.provider) >= 0 ? 'subscription' : (BILLING_PROVIDERS.indexOf(activeSessionModel.provider) >= 0 ? 'billing' : 'balance') }
|
|
412
417
|
: (waitForSessionModel ? null : state.billingMode);
|
|
413
418
|
// ---- 与原生一致格式工具 ----
|
|
414
419
|
function formatTokens(n) {
|
|
@@ -527,7 +532,7 @@ module.exports = {
|
|
|
527
532
|
const versionLine = updateInfo && typeof updateInfo.current === 'string'
|
|
528
533
|
? '\n插件版本:' + updateInfo.current : '';
|
|
529
534
|
const provTitle = '服务商:' + provLabel + ' ' + modelLabel + '\n'
|
|
530
|
-
+ (pr && pr.mode === 'peak-valley' ? '
|
|
535
|
+
+ (pr && pr.mode === 'peak-valley' ? '定价:峰谷价(工作日高峰 9-12、14-18 点;周末全天空闲)'
|
|
531
536
|
: (pr && pr.mode === 'flat' ? '定价:固定价' : '定价:未收录,按默认计'))
|
|
532
537
|
+ versionLine;
|
|
533
538
|
return React.createElement('span', { key: 'prov', className: 'bi-model-group', title: provTitle },
|
|
@@ -537,40 +542,79 @@ module.exports = {
|
|
|
537
542
|
|
|
538
543
|
// 订阅服务名(订阅制模式下"服务商"指订阅服务本身,不是模型厂商)
|
|
539
544
|
// Codex 与 ChatGPT 已合并:实际 provider openai-codex / chatgpt 均显示 ChatGPT;codex 保持 Codex
|
|
545
|
+
// v1.6 T7:新增 zai/zai-coding-cn → '智谱';v1.7:小米 Token Plan → '小米 MiMo'
|
|
540
546
|
function subscriptionServiceName(provider) {
|
|
541
547
|
if (provider === 'chatgpt' || provider === 'openai-codex') return 'ChatGPT';
|
|
542
548
|
if (provider === 'codex') return 'Codex';
|
|
543
549
|
if (provider === 'opencode-go' || provider === 'opencode') return 'OpenCode Go';
|
|
550
|
+
if (provider === 'zai' || provider === 'zai-coding-cn') return '智谱';
|
|
551
|
+
if (provider === 'xiaomi-token-plan-cn' || provider === 'xiaomi-token-plan-sgp' || provider === 'xiaomi-token-plan-ams') return '小米 MiMo';
|
|
544
552
|
return '订阅';
|
|
545
553
|
}
|
|
546
554
|
|
|
555
|
+
// 账单型服务名(v1.7:云账单 provider 显示品牌名,未知保持兜底)
|
|
556
|
+
function billingServiceName(provider) {
|
|
557
|
+
if (provider === 'together') return 'Together';
|
|
558
|
+
if (provider === 'fireworks') return 'Fireworks';
|
|
559
|
+
if (provider === 'amazon-bedrock') return 'AWS Bedrock';
|
|
560
|
+
if (provider === 'cloudflare-ai-gateway' || provider === 'cloudflare-workers-ai') return 'Cloudflare';
|
|
561
|
+
return '云账单';
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 套餐档位短名(JWT 订阅卡:plus/pro/team/enterprise → Plus/Pro/Team/Enterprise;未知返回 null 显示模型名)
|
|
565
|
+
function subscriptionPlanShort(planType) {
|
|
566
|
+
if (typeof planType !== 'string' || planType.length === 0) return null;
|
|
567
|
+
const map = { plus: 'Plus', pro: 'Pro', team: 'Team', enterprise: 'Enterprise' };
|
|
568
|
+
return map[planType.toLowerCase()] || null;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 本地时区 YYYY-MM-DD(订阅到期日)
|
|
572
|
+
function formatDate(ms) {
|
|
573
|
+
if (ms == null || isNaN(ms)) return '—';
|
|
574
|
+
const d = new Date(ms);
|
|
575
|
+
const p = function (x) { return String(x).padStart(2, '0'); };
|
|
576
|
+
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
|
|
577
|
+
}
|
|
578
|
+
|
|
547
579
|
// 订阅制模型组:订阅服务名 · 具体模型(如 `OpenCode Go · V4 Flash`、`Codex · GPT 5 Codex`)
|
|
580
|
+
// v1.7 FR-8:codex 源有 JWT 套餐档位时,模型位显示套餐档位(如 `ChatGPT · Plus`),真实信息来自 id_token claims
|
|
548
581
|
function subscriptionProviderGroup() {
|
|
549
582
|
const pr = visiblePricing;
|
|
550
583
|
const serviceName = subscriptionServiceName(visibleBillingMode && visibleBillingMode.provider);
|
|
551
|
-
const
|
|
584
|
+
const subSnapshot = state.sub;
|
|
585
|
+
const planShort = subSnapshot && subSnapshot.planType ? subscriptionPlanShort(subSnapshot.planType) : null;
|
|
586
|
+
const rawModelLabel = (pr && pr.modelDisplay) ? pr.modelDisplay
|
|
552
587
|
: (pr && pr.model ? pr.model : '未知模型');
|
|
588
|
+
const modelLabel = planShort ? planShort : rawModelLabel;
|
|
553
589
|
const modelName = modelLabelWithoutProvider(modelLabel, serviceName);
|
|
554
590
|
const versionLine = updateInfo && typeof updateInfo.current === 'string'
|
|
555
591
|
? '\n插件版本:' + updateInfo.current : '';
|
|
556
|
-
const
|
|
592
|
+
const planLine = subSnapshot && subSnapshot.plan ? '\n套餐:' + subSnapshot.plan : '';
|
|
593
|
+
const expiryLine = subSnapshot && subSnapshot.expiryAt ? '\n到期:' + formatDate(subSnapshot.expiryAt) + '(本地时区)' : '';
|
|
594
|
+
const title = '订阅服务:' + serviceName + '\n模型:' + rawModelLabel + planLine + expiryLine + versionLine;
|
|
557
595
|
return React.createElement('span', { key: 'subprov', className: 'bi-model-group', title: title },
|
|
558
596
|
React.createElement('b', { className: 'bi-model-provider' }, serviceName),
|
|
559
597
|
modelDetail(pr, modelName),
|
|
560
598
|
);
|
|
561
599
|
}
|
|
562
600
|
|
|
563
|
-
// ---- 余额制模式(v1.0.0 现状,完全不动):服务商+模型 → 余额 → 时段 → 倒计时 →
|
|
601
|
+
// ---- 余额制模式(v1.0.0 现状,完全不动):服务商+模型 → 余额 → 时段 → 倒计时 → 本会话花费 ----
|
|
564
602
|
function pushBalanceGroups(groups, trailingErrorGroups) {
|
|
565
603
|
const bal = state.balance;
|
|
566
604
|
const errors = state.errors || {};
|
|
567
605
|
const alertActive = !!(bal && bal.alert && bal.alert.active);
|
|
568
606
|
groups.push(providerGroup());
|
|
569
607
|
|
|
608
|
+
// v1.6 T7:未适配账户渲染"未适配"弱提示
|
|
609
|
+
if (bal && bal.unmapped) {
|
|
610
|
+
trailingErrorGroups.push(React.createElement('span', { className: 'bi-muted', key: 'unmapped', title: '该服务商暂未适配余额查询,后续版本将支持。' }, '未适配'));
|
|
611
|
+
}
|
|
570
612
|
// 余额(纯金额;hover 仅展示余额,不显示充值/赠金)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
613
|
+
// v1.6 T7:未配置提示改为按账户显示凭据名(去掉写死的 DeepSeek 文案)
|
|
614
|
+
else if (bal && bal.error && bal.error.kind === 'no-key') {
|
|
615
|
+
const credName = bal.error.message ? String(bal.error.message).replace('未配置 ', '') : 'API_KEY';
|
|
616
|
+
trailingErrorGroups.push(React.createElement('span', { className: 'bi-err', key: 'nokey', title: '未配置 ' + credName + '。请在"设置 → 模型"中填写。' },
|
|
617
|
+
'未配置 ' + credName + ' → 设置→模型 填写'));
|
|
574
618
|
} else if (bal && bal.data) {
|
|
575
619
|
const symbol = bal.currency === 'USD' ? '$' : '¥';
|
|
576
620
|
const balTitle = bal.estimate
|
|
@@ -612,7 +656,7 @@ module.exports = {
|
|
|
612
656
|
metric('距' + (peakNow ? '空闲' : '高峰'), fmtCountdown(pr.nextSwitch.at - now))));
|
|
613
657
|
}
|
|
614
658
|
|
|
615
|
-
//
|
|
659
|
+
// 本会话花费(只显示钱;hover 浮窗显示 本会话(含子代理)+ 今天 / 近一月 / 全部;金额数字加粗)
|
|
616
660
|
// 始终显示:新会话/对话刚开始尚无记账时显示 ¥0.000,hover 仍可查看持久化的 今天/近一月/全部
|
|
617
661
|
const usg = state.usage;
|
|
618
662
|
if (usg) {
|
|
@@ -627,8 +671,10 @@ module.exports = {
|
|
|
627
671
|
const month = usg.monthSpend != null ? '近一月 ' + symbol + fmt(usg.monthSpend, 3) : '';
|
|
628
672
|
const total = usg.totalSpend != null ? '全部 ' + symbol + fmt(usg.totalSpend, 3) : '';
|
|
629
673
|
const detail = [today, month, total].filter(function (s) { return s.length > 0; }).join(' · ');
|
|
630
|
-
|
|
631
|
-
|
|
674
|
+
// v1.7 发布前微调:hover 明确标注"含子代理"——本会话聚合同账户所有记录(同起点后)
|
|
675
|
+
groups.push(React.createElement('span', { key: 'convo',
|
|
676
|
+
title: '本会话 ' + costTxt + '(含子代理)' + (detail ? '\n' + detail : '') },
|
|
677
|
+
metric('本会话', costTxt)));
|
|
632
678
|
} else if (errors.usage) {
|
|
633
679
|
// 本次 RPC 失败且无旧数据:只降级花费块,其余端点数据照常渲染
|
|
634
680
|
trailingErrorGroups.push(React.createElement('span', { className: 'bi-err', key: 'usageerr', title: '花费暂不可用;不会影响对话。' }, '花费获取失败'));
|
|
@@ -656,6 +702,11 @@ module.exports = {
|
|
|
656
702
|
groups.push(subscriptionProviderGroup());
|
|
657
703
|
const sub = state.sub;
|
|
658
704
|
const errors = state.errors || {};
|
|
705
|
+
// v1.7 FR-8:JWT 订阅卡——真实套餐到期日(纯本地解码;无登录态/解析失败不显示此处)
|
|
706
|
+
if (sub && sub.planType && sub.expiryAt) {
|
|
707
|
+
groups.push(React.createElement('span', { key: 'subexp', title: '订阅到期:' + formatDate(sub.expiryAt) + '(本地时区)' },
|
|
708
|
+
metric('到期', formatDate(sub.expiryAt))));
|
|
709
|
+
}
|
|
659
710
|
if (!sub) {
|
|
660
711
|
if (errors.sub) {
|
|
661
712
|
// 本次 RPC 失败且无旧数据:显示失败信息而非永久"加载中…"
|
|
@@ -723,18 +774,89 @@ module.exports = {
|
|
|
723
774
|
}
|
|
724
775
|
}
|
|
725
776
|
|
|
777
|
+
// ---- v1.7 账单型模式(FR-10~13/FR-14,互斥第三态):
|
|
778
|
+
// 账单服务+模型 → 本月真实花费 →(预算%)→(免费额度+重置倒计时);余额/额度/本会话花费均不显示 ----
|
|
779
|
+
function billingFailureHint(error, provider) {
|
|
780
|
+
const serviceName = billingServiceName(provider);
|
|
781
|
+
const message = error && typeof error.message === 'string' ? error.message : '';
|
|
782
|
+
const statusMatch = message.match(/HTTP (\d{3})/);
|
|
783
|
+
const status = statusMatch ? statusMatch[1] : '';
|
|
784
|
+
if (error && error.kind === 'no-key') return '未配置 ' + message.replace('未配置 ', '') + '。请在"设置 → 模型"中填写。';
|
|
785
|
+
if (status === '403' || /缺少 .*权限/.test(message)) return serviceName + ' 拒绝访问:Token 可能缺少账单读取权限。';
|
|
786
|
+
if (error && error.kind === 'parse') return serviceName + ' 返回的数据暂时无法识别。请稍后再试。';
|
|
787
|
+
if (/timeout|timed out|abort/i.test(message)) return serviceName + ' 响应超时。请检查网络后再试。';
|
|
788
|
+
return serviceName + ' 账单暂不可用。请检查网络与权限后再试。';
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function pushBillingGroups(groups, trailingErrorGroups) {
|
|
792
|
+
groups.push(billingProviderGroup());
|
|
793
|
+
const bill = state.billing;
|
|
794
|
+
const errors = state.errors || {};
|
|
795
|
+
if (!bill) {
|
|
796
|
+
if (errors.billing) {
|
|
797
|
+
trailingErrorGroups.push(React.createElement('span', { className: 'bi-stale', key: 'billerr', title: billingFailureHint({ kind: 'exception', message: String(errors.billing) }, visibleBillingMode && visibleBillingMode.provider) }, '刷新失败'));
|
|
798
|
+
}
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const d = bill.data;
|
|
802
|
+
const hasSpend = !!(d && (d.currentPeriodSpend != null || d.usage != null));
|
|
803
|
+
if (bill.error && !hasSpend) {
|
|
804
|
+
trailingErrorGroups.push(React.createElement('span', { className: 'bi-stale', key: 'billstale', title: billingFailureHint(bill.error, bill.type || (visibleBillingMode && visibleBillingMode.provider)) }, '刷新失败'));
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (hasSpend) {
|
|
808
|
+
const symbol = d.currency === 'CNY' ? '¥' : '$';
|
|
809
|
+
const titleLines = ['账单源:' + billingServiceName(visibleBillingMode && visibleBillingMode.provider)]
|
|
810
|
+
.concat(d.note ? [d.note] : [])
|
|
811
|
+
.concat(d.currentPeriodSpend != null ? ['本月花费:' + symbol + fmt(d.currentPeriodSpend, 2)] : [])
|
|
812
|
+
.concat(d.budgetPercent != null ? ['预算使用:' + fmt(d.budgetPercent, 0) + '%'] : [])
|
|
813
|
+
.concat(d.freeRemaining != null ? ['每日免费额度剩余:' + fmt(d.freeRemaining, 0)] : []);
|
|
814
|
+
const nodes = [];
|
|
815
|
+
if (d.currentPeriodSpend != null) nodes.push(metric('本月', symbol + fmt(d.currentPeriodSpend, 2)));
|
|
816
|
+
else if (d.usage != null) nodes.push(metric('本月用量', fmt(d.usage, 2) + (d.usageUnit ? ' ' + d.usageUnit : '')));
|
|
817
|
+
if (d.budgetPercent != null) nodes.push(' · ', metric('预算', fmt(d.budgetPercent, 0) + '%'));
|
|
818
|
+
// 免费额度仅当接口显式给出(freeRemaining/resetsAt 同时存在)才显示,绝不编造
|
|
819
|
+
if (d.freeRemaining != null && d.resetsAt) {
|
|
820
|
+
nodes.push(' · ', metric('免费', fmt(d.freeRemaining, 0) + ' · 距重置 ' + fmtResetCountdown(d.resetsAt - now)));
|
|
821
|
+
}
|
|
822
|
+
groups.push(React.createElement('span', { key: 'bill', title: titleLines.join('\n') }, ...nodes));
|
|
823
|
+
if (bill.error || errors.billing) {
|
|
824
|
+
trailingErrorGroups.push(React.createElement('span', { className: 'bi-stale', key: 'billstale', title: billingFailureHint(bill.error || { kind: 'exception', message: String(errors.billing || '') }, bill.type || (visibleBillingMode && visibleBillingMode.provider)) }, '刷新失败'));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// 账单型模型组:账单服务名 · 具体模型(如 `AWS Bedrock · Claude`)
|
|
830
|
+
function billingProviderGroup() {
|
|
831
|
+
const pr = visiblePricing;
|
|
832
|
+
const serviceName = billingServiceName(visibleBillingMode && visibleBillingMode.provider);
|
|
833
|
+
const modelLabel = (pr && pr.modelDisplay) ? pr.modelDisplay
|
|
834
|
+
: (pr && pr.model ? pr.model : '未知模型');
|
|
835
|
+
const modelName = modelLabelWithoutProvider(modelLabel, serviceName);
|
|
836
|
+
const versionLine = updateInfo && typeof updateInfo.current === 'string'
|
|
837
|
+
? '\n插件版本:' + updateInfo.current : '';
|
|
838
|
+
const title = '账单服务:' + serviceName + '\n模型:' + modelLabel + versionLine;
|
|
839
|
+
return React.createElement('span', { key: 'billprov', className: 'bi-model-group', title: title },
|
|
840
|
+
React.createElement('b', { className: 'bi-model-provider' }, serviceName),
|
|
841
|
+
modelDetail(pr, modelName),
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
726
845
|
const groups = [];
|
|
727
846
|
// 报错不打断主要信息的阅读顺序:统一延后到整行最右侧。
|
|
728
847
|
const trailingErrorGroups = [];
|
|
729
848
|
// 两态严格判定:density 只能是 'full' 或 'compact'(host 校验 + 本地防抖保证)
|
|
730
849
|
const full = displayDensity === 'full';
|
|
731
|
-
// 模式互斥:订阅制渲染订阅版 row2,余额制渲染 v1.0.0
|
|
850
|
+
// 模式互斥:订阅制渲染订阅版 row2,账单制渲染账单版 row2,余额制渲染 v1.0.0 现状——三态绝不叠加(FR-14)
|
|
732
851
|
const isSub = !!(visibleBillingMode && visibleBillingMode.mode === 'subscription');
|
|
852
|
+
const isBilling = !!(visibleBillingMode && visibleBillingMode.mode === 'billing');
|
|
733
853
|
// Never paint a loading placeholder. Before the active session's model
|
|
734
854
|
// is available, leave this compact row empty rather than briefly showing
|
|
735
855
|
// either a generic loading label or data from the previous session.
|
|
736
856
|
if (waitForSessionModel) {
|
|
737
857
|
// Intentionally empty: session model publish fills the row immediately.
|
|
858
|
+
} else if (isBilling) {
|
|
859
|
+
pushBillingGroups(groups, trailingErrorGroups);
|
|
738
860
|
} else if (isSub) {
|
|
739
861
|
pushSubscriptionGroups(groups, trailingErrorGroups);
|
|
740
862
|
} else {
|
|
@@ -749,6 +871,7 @@ module.exports = {
|
|
|
749
871
|
if (errors.usage) failedLabels.push('花费');
|
|
750
872
|
if (errors.billingMode) failedLabels.push('模式');
|
|
751
873
|
if (errors.sub) failedLabels.push('订阅额度');
|
|
874
|
+
if (errors.billing) failedLabels.push('账单');
|
|
752
875
|
if (failedLabels.length > 0) {
|
|
753
876
|
trailingErrorGroups.push(React.createElement('span', { className: 'bi-stale', key: 'degraded',
|
|
754
877
|
title: failedLabels.join('、') + '暂不可用;正在保留上次数据并自动重试。' }, '刷新失败'));
|