dsh-bottom-info-bar 1.9.2 → 1.10.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 +236 -197
- package/lib/constants.js +30 -30
- package/lib/host-locale.js +56 -0
- package/lib/index.js +93 -86
- package/lib/locales.js +541 -0
- package/package.json +5 -2
package/lib/client.js
CHANGED
|
@@ -16,6 +16,37 @@ var module = { exports: {} }; var exports = module.exports;
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const React = require('react');
|
|
19
|
+
const LOCALE_NAMESPACE = 'dsh-bottom-info-bar';
|
|
20
|
+
const LOCALES = {"zh":{"ui.requestTimedOut":"请求超时","ui.requestCanceled":"请求已取消","ui.couldNotParseResponse":"响应解析失败","ui.rpcFailed":"RPC 失败","ui.refreshFailed":"刷新失败","color.red":"红","color.green":"绿","color.blue":"蓝","color.purple":"紫","color.orange":"橙","color.neutral":"中性","mode.balance":"余额制","mode.subscription":"订阅制","mode.billing":"账单制","mode.native":"原生统计行","mode.common":"通用","ui.nativeStatsField":"原生统计行字段","ui.shownIn":"出现于","ui.pleaseTryAgainLater":"请稍后再试","ui.restoreDefaultColor":"恢复默认颜色","ui.infoBarSettings":"信息底栏设置","ui.settingsAreTemporarilyUnavailable":"配置暂时无法读取","ui.loadingInfoBarSettings":"正在载入信息底栏设置…","ui.couldNotLoadInfoBar":"信息底栏设置暂时无法读取:","ui.changesAppliedButCouldNot":"改动已生效,但写入本地配置文件失败:","ui.unknownReason":"未知原因","ui.couldNotSave":"{errorPrefix}保存失败:{value}","ui.color":"「{value}」颜色","ui.hasAnInvalidColorEnter":"「{value}」颜色格式无效:请输入 #RRGGBB(例如 #0044CC)","ui.defaultColorsRestored":"已恢复默认颜色","ui.defaultLabelsRestored":"已恢复默认标签","ui.couldNotReset":"重置失败:{value}","ui.identifiesTheProviderAndModel":"身份锚点:当前对话的服务商与模型标识。","ui.keepEnabledToSeeThese":"关闭后相应提示不再出现,建议保留。","ui.recommended":"建议保留","ui.show":"显示{label}","ui.clickToHide":"点击隐藏","ui.clickToShow":"点击显示","ui.default":"默认","ui.current":"当前:{valueText}","ui.providerAndModel":"(身份锚点)","ui.presetColor":"{label}的预设颜色","ui.customColor":"{label}的自定义颜色","ui.customColorOpenColorPicker":"自定义颜色(打开系统取色器)","ui.hexColor":"{label}的十六进制颜色","ui.saving":"正在保存…","ui.chooseWhichFieldsToShow":"选择信息底栏显示哪些信息、为每个信息挑一个颜色。改动即时生效并自动保存,重启后仍然保留;隐藏信息不会删除任何记账数据。","ui.visibleFields":"显示字段","ui.hiddenFieldsTakeNoSpace":"关闭的字段立刻从信息栏隐藏且不占位;「刷新失败」「账单未保存」等提示建议保留,方便发现数据异常。","ui.fieldColors":"字段颜色","ui.chooseAPresetUseThe":"挑选预设色或用取色器、十六进制自定义;颜色会自动适配浅色与深色主题,保持可读。未改动前与现在的外观完全一致。","ui.resetLabels":"重置标签","ui.resetColors":"重置颜色","ui.couldNotDisplayInfoBar":"信息底栏设置渲染出错:{value}","ui.infoBar":"信息底栏","ui.weekly":"周","ui.monthly":"月","ui.window":"窗口","ui.supportsImageInput":"支持图像输入。","ui.vision":"视觉","ui.unknown":"未知","ui.unknownModel":"未知模型","ui.pluginVersion":"\n插件版本:{current}","ui.provider":"服务商:{provLabel} {modelLabel}\n","ui.pricingPeakOffPeakBeijing":"定价:峰谷价(工作日高峰 9-12、14-18 点;周末全天空闲)","ui.pricingFixed":"定价:固定价","ui.pricingNotListedUsingDefaults":"定价:未收录,按默认计","ui.zhipu":"智谱","ui.xiaomiMiMo":"小米 MiMo","ui.subscription":"订阅","ui.cloudBilling":"云账单","ui.plan":"\n套餐:{plan}","ui.expiresLocalTime":"\n到期:{value}(本地时区)","ui.subscriptionServiceModel":"订阅服务:{serviceName}\n模型:{rawModelLabel}{planLine}{expiryLine}{versionLine}","ui.balanceLookupIsNotYet":"该服务商暂未适配余额查询,后续版本将支持。","ui.notSupported":"未适配","ui.notConfigured":"未配置 ","ui.notConfiguredConfigureItIn":"未配置 {credName}。请在\"设置 → 模型\"中填写。","ui.notConfiguredSettingsModels":"未配置 {credName} → 设置→模型 填写","ui.estimatedBalance":"估算余额:{symbol}{value}","ui.balance":"余额:{symbol}{value}","ui.balance.pushBalanceGroups":"余额","ui.low":"低","ui.estimated":"(估算)","ui.balanceIsTemporarilyUnavailableShowing":"余额暂不可用;正在显示上次数据并自动重试。","ui.couldNotLoadBalanceCheck":"余额获取失败。请检查网络和 API Key。","ui.balanceUnavailable":"余额获取失败","ui.beijingTime":"北京时间 ","ui.peakPrice":"高峰价","ui.offPeakPrice":"空闲价","ui.input":":输入 ¥","ui.mCachedInput":"/M · 缓存 ¥","ui.mOutput":"/M · 输出 ¥","ui.beijingTimeSwitchesTo":"北京时间 {atLabel} 切换为","ui.until":"距","ui.offPeak":"空闲","ui.peak":"高峰","ui.today":"今天 {symbol}{value}","ui.lastDays":"近一月 {symbol}{value}","ui.allTime":"全部 {symbol}{value}","ui.sessionIncludingSubagents":"本会话 {costTxt}(含子代理){value}","ui.session":"本会话","ui.spendIsTemporarilyUnavailableChat":"花费暂不可用;不会影响对话。","ui.spendUnavailable":"花费获取失败","ui.noSignInCredentialsFound":"未找到 {serviceName} 登录凭证。请重新授权。","ui.credentialsHaveExpiredPleaseReauthorize":"{serviceName} 登录凭证已失效。请重新授权。","ui.deniedAccessReauthorizeOrTry":"{serviceName} 拒绝访问。请重新授权或稍后再试。","ui.rateLimitReachedPleaseTry":"{serviceName} 请求过于频繁。请稍后再试。","ui.timedOutCheckYourConnection":"{serviceName} 响应超时。请检查网络后再试。","ui.returnedAnUnrecognizedResponsePlease":"{serviceName} 返回的数据暂时无法识别。请稍后再试。","ui.isTemporarilyUnavailableCheckYour":"{serviceName} 暂不可用。请检查网络后再试。","ui.subscriptionExpiresLocalTime":"订阅到期:{value}(本地时区)","ui.expires":"到期","ui.subscriptionSource":"订阅源:{value}(","ui.prepaidBalance":"充值余额","ui.availableBalance":"可用余额:{balTxt}","ui.subscriptionSource.titleLines":"订阅源:{value}","ui.windowRemainingUsed":"{label}窗口:剩余 {value}%(已用 {usedPercent}%)","ui.resetsResetsIn":" · 重置 {value} · 距重置 {value2}","ui.windowRemainingUsedResets":"{label}窗口 剩余 {value}%(已用 {usedPercent}%) · 重置 {value4}","ui.resetsIn":"距重置","ui.configureItInSettingsModels":"。请在\"设置 → 模型\"中填写。","ui.deniedAccessTheTokenMay":"{serviceName} 拒绝访问:Token 可能缺少账单读取权限。","ui.billingIsTemporarilyUnavailableCheck":"{serviceName} 账单暂不可用。请检查网络与权限后再试。","ui.billingSource":"账单源:{value}","ui.thisMonthSSpend":"本月花费:{symbol}{value}","ui.budgetUsed":"预算使用:{value}%","ui.dailyFreeQuotaRemaining":"每日免费额度剩余:{value}","ui.thisMonth":"本月","ui.thisMonthSUsage":"本月用量","ui.budget":"预算","ui.free":"免费","ui.resetsIn.pushBillingGroups":"{value} · 距重置 {value2}","ui.billingServiceModel":"账单服务:{serviceName}\n模型:{modelLabel}{versionLine}","ui.pricing":"定价","ui.spend":"花费","ui.mode":"模式","ui.subscriptionQuota":"订阅额度","ui.billing":"账单","ui.temporarilyUnavailableKeepingTheLast":"暂不可用;正在保留上次数据并自动重试。","ui.spendJournalSavedButThe":"账单流水已保存,但可直接查看的账单文件暂未更新:","ui.thisSpendRecordWasNot":"本次账单未保存,不会计入金额:","ui.ledgerUpdatePending":"账单待整理","ui.spendNotSaved":"账单未保存","ui.askYourAgentToUpdate":"请告知你的 Agent 将本插件更新到“{latest}”版本。","ui.updateAvailable":"新版本提醒","ui.tools":"工具调用","ui.avgTTFT":"首 token 平均","ui.cacheHit":"缓存命中","ui.input.BottomInfoBar":"输入","ui.output":"输出","ui.savingView":"正在保存切换…","ui.clickToSwitchFullCompact":"单击切换 完整/简洁","ui.pressEnterOrSpaceFor":"按 Enter 或空格切换为简洁模式。","ui.pressEnterOrSpaceFor.BottomInfoBar":"按 Enter 或空格切换为完整模式。","host.hour":"5 小时","host.unknownProvider":"未知服务商","host.unexpectedSubscriptionQuotaRequestFailure":"订阅额度请求未知异常","host.settingsFileCouldNotBe":"设置文件写入未完成","host.everydayQuestions":"日常问答","host.mediumCodingTask":"中等编码任务","host.longDocumentAnalysisCodeReview":"长文档分析/代码审查","host.largeProjectRefactorAcrossMultiple":"大工程重构(多轮)","host.subagentWorkflow":"子代理工作流","host.couldNotSaveSettingsJson":"settings.json 落盘失败:{value}","host.couldNotReadCredentials":"凭据读取失败","host.notConfigured":"未配置 {credential}","host.requestFailedHTTP":"请求失败(HTTP {status})","host.unexpectedResponseFormat":"响应格式异常","host.chatgptSubscriptionIsNotConnected":"未找到 ChatGPT 订阅登录凭证(~/.codex/auth.json),请安装 dsh-chatgpt-subscription 插件绑定","host.chatgptSubscriptionCredentialsAreMissing":"ChatGPT 订阅登录凭证缺少 id_token,请安装 dsh-chatgpt-subscription 插件重新绑定","host.opencodeGoIsNotConfigured":"未配置 OpenCode Go(OPENCODE_GO_API_KEY 或 opencode auth.json)","host.zhipu":"智谱 {mapped}","host.zhipu.parseZaiQuota":"智谱 {value}{value2}","host.zhipuAPIKeyIsNot":"未配置智谱 API Key(ZAI_API_KEY 或 ZAI_CODING_CN_API_KEY)","host.zhipuAPIAuthenticationFailedThe":"智谱 API 认证失败(密钥过期或不正确)","host.requestFailed":"请求失败({value} {msg})","host.xiaomiMiMoTokenPlanCredentials":"未配置小米 MiMo Token Plan 凭据({credName} 或 XIAOMI_API_KEY)","host.requestFailedHTTP.fetchXiaomiTokenPlanUsage":"请求失败(HTTP {value})","host.notConfiguredTOGETHERAPIKEY":"未配置 TOGETHER_API_KEY","host.actualMonthlyBillFromThe":"本月真实账单(Together 官方 Usage API)","host.notConfiguredFIREWORKSAPIKEY":"未配置 FIREWORKS_API_KEY","host.couldNotReadAccountAccount":"账户解析失败(未取得 account_id)","host.actualBillForThisPeriod":"本周期真实账单(Fireworks Billing Summary)","host.actualUsageForThisPeriod":"本周期真实用量(billingUsage 回退端点,无金额)","host.notConfiguredAWSCredentialsAWS":"未配置 AWS 凭据(AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY)","host.requestFailedHTTPTheToken":"请求失败(HTTP {status})——可能缺少 ce:GetCostAndUsage 权限","host.actualMonthlyBillFromAWS":"AWS Cost Explorer 本月真实账单(账单延迟约 24 小时)","host.notConfiguredCLOUDFLAREAPIKEY":"未配置 CLOUDFLARE_API_KEY(需账号级 Token 并授予 Billing 读权限)","host.notConfiguredCLOUDFLAREACCOUNTID":"未配置 CLOUDFLARE_ACCOUNT_ID","host.requestFailedHTTPTheToken.fetchCloudflareBilling":"请求失败(HTTP {status})——Token 需 Billing 读权限","host.actualMonthlyUsageFromThe":"Cloudflare Billable Usage API(Alpha),本月真实用量","host.unexpectedBillingRequestFailure":"账单请求未知异常","host.spendSummaryFileMissingArchived":"账单汇总文件缺失:折叠期间的历史金额暂未计入显示(明细仍在冷归档 usage-archive/,可人工恢复)","host.spendLedgerCouldNotBe":"账单文件写入未完成","host.couldNotSaveArchivedSpend":"折叠汇总落盘失败:{value}","host.basedOnYourLastSessions":"基于你最近 {count} 次会话","host.estimatedFromSpendingOverThe":"基于过去 {SPEND_DAYS} 天消耗速度的估算","host.yourTypicalSession":"你的典型会话","host.patchMustIncludeFieldsOr":"patch 需包含 fields 或 colors 之一","host.fieldsMustBeAnObject":"fields 必须是对象","host.unknownFieldId":"未知字段 id: {key}","host.fieldVisibilityMustBeA":"字段开关必须是布尔值: {key}","host.colorsMustBeAnObject":"colors 必须是对象","host.colorMustBeAPreset":"颜色只接受预设色名或 #RRGGBB: {key}","field.anchorGroup.label":"服务商与模型","field.anchorGroup.note":"当前对话使用的服务商与模型名,信息栏的身份锚点","field.subServiceGroup.label":"订阅服务与模型","field.subServiceGroup.note":"订阅服务名(ChatGPT/Codex/OpenCode Go/智谱/小米 MiMo)与模型或套餐档位","field.billingServiceGroup.label":"账单服务与模型","field.billingServiceGroup.note":"云账单服务名(Together/Fireworks/AWS Bedrock/Cloudflare)与模型名","field.sessionCost.label":"本会话花费","field.sessionCost.note":"当前会话(含子代理)的真实花费;新会话尚无记账时显示 ¥0.000,悬停可看今天/近一月/全部","field.balance.note":"服务商账户的真实余额;低余额时数字变红并带「低」字","field.period.label":"定价时段","field.period.note":"仅峰谷价模型显示(当前为 DeepSeek 系列):高峰价红色 / 空闲价绿色","field.countdown.label":"时段切换倒计时","field.countdown.note":"仅峰谷价模型显示:距空闲/距高峰的小时计倒计时","field.expiry.label":"订阅到期日","field.expiry.note":"订阅登录凭据中带有到期信息时显示(如 Codex 套餐)","field.subWindow5h.label":"5 小时额度窗口","field.subWindow5h.note":"5 小时滚动额度剩余百分比;简洁模式下仅显示时间最短的可用窗口","field.subWindowWeek.label":"周额度窗口","field.subWindowWeek.note":"每周额度剩余百分比;完整模式显示,简洁模式让位给更短窗口","field.subWindowMonth.label":"月额度窗口","field.subWindowMonth.note":"每月额度剩余百分比;完整模式显示,简洁模式让位给更短窗口","field.resetCountdown.label":"额度重置倒计时","field.resetCountdown.note":"当前显示的额度窗口距重置的倒计时","field.subBalance.note":"订阅源为充值余额形态时显示(如智谱按量账户),与额度窗口互斥","field.billingSpend.label":"本月花费/用量","field.billingSpend.note":"云账单当前计费周期的真实花费或用量","field.budget.label":"预算使用","field.budget.note":"仅提供预算查询的服务商显示(当前为 AWS Bedrock)","field.freeQuota.label":"免费额度与重置","field.freeQuota.note":"接口显式给出每日免费额度与重置时刻时才显示,绝不编造","field.turnsSteps.label":"轮次与步数","field.turnsSteps.note":"「N 轮 · M 步」对话进度统计","field.llmTime.label":"LLM 耗时","field.llmTime.note":"模型推理累计耗时","field.toolTime.label":"工具调用耗时","field.toolTime.note":"工具调用累计耗时","field.cacheHit.note":"提示词缓存命中率百分比","field.tokensIO.label":"输入/输出 tokens","field.tokensIO.note":"「输入 X tok · 输出 Y tok」本次会话累计用量","field.unmapped.label":"未适配提示","field.unmapped.note":"当前服务商暂无余额查询适配时出现的弱提示","field.noKeyHint.label":"未配置凭据提示","field.noKeyHint.note":"缺少 API Key 时的设置引导文案","field.balanceError.label":"余额获取/刷新失败提示","field.balanceError.note":"余额查询失败或正在显示上次数据时的提示","field.usageError.label":"花费获取失败提示","field.usageError.note":"花费数据暂时不可用时的提示","field.refreshFailure.label":"刷新失败提示","field.refreshFailure.note":"任一数据源降级时在行尾合并显示的一个提醒(多个来源自动去重)","field.persistWarning.label":"账单未保存提醒","field.persistWarning.note":"账本落盘异常时出现;隐藏后金额可能悄悄不准,建议保留","field.updateNotice.note":"npm 上有新版本时提醒更新","group.native":"原生字段","group.plugin":"插件字段","ui.sentenceSeparator":"","ui.listSeparator":"、","ui.sentenceEnd":"。","ui.fieldErrorPrefix":"「{label}」:","ui.turnCount":"{count} 轮","ui.turnCountPlural":"{count} 轮","ui.stepCount":"{count} 步","ui.stepCountPlural":"{count} 步"},"en":{"ui.requestTimedOut":"Request timed out","ui.requestCanceled":"Request canceled","ui.couldNotParseResponse":"Could not parse response","ui.rpcFailed":"RPC failed","ui.refreshFailed":"Refresh failed","color.red":"Red","color.green":"Green","color.blue":"Blue","color.purple":"Purple","color.orange":"Orange","color.neutral":"Neutral","mode.balance":"Balance","mode.subscription":"Subscription","mode.billing":"Billing","mode.native":"Native stats","mode.common":"General","ui.nativeStatsField":"Native stats field","ui.shownIn":"Shown in: ","ui.pleaseTryAgainLater":"Please try again later","ui.restoreDefaultColor":"Restore default color","ui.infoBarSettings":"Info Bar settings","ui.settingsAreTemporarilyUnavailable":"Settings are temporarily unavailable","ui.loadingInfoBarSettings":"Loading Info Bar settings…","ui.couldNotLoadInfoBar":"Could not load Info Bar settings: ","ui.changesAppliedButCouldNot":"Changes applied, but could not be saved locally: ","ui.unknownReason":"Unknown reason","ui.couldNotSave":"{errorPrefix}Could not save: {value}","ui.color":"\"{value}\" color: ","ui.hasAnInvalidColorEnter":"\"{value}\" has an invalid color. Enter #RRGGBB (for example, #0044CC).","ui.defaultColorsRestored":"Default colors restored","ui.defaultLabelsRestored":"Default labels restored","ui.couldNotReset":"Could not reset: {value}","ui.identifiesTheProviderAndModel":"Identifies the provider and model for this conversation.","ui.keepEnabledToSeeThese":"Keep enabled to see these alerts.","ui.recommended":"Recommended","ui.show":"Show {label}","ui.clickToHide":"Click to hide","ui.clickToShow":"Click to show","ui.default":"Default","ui.current":"Current: {valueText}","ui.providerAndModel":" (provider and model)","ui.presetColor":"{label} preset color","ui.customColor":"{label} custom color","ui.customColorOpenColorPicker":"Custom color (open color picker)","ui.hexColor":"{label} hex color","ui.saving":"Saving…","ui.chooseWhichFieldsToShow":"Choose which fields to show and customize their colors. Changes apply immediately and are saved across restarts. Hiding a field does not delete spend records.","ui.visibleFields":"Visible fields","ui.hiddenFieldsTakeNoSpace":"Hidden fields take no space. Keep alerts such as Refresh failed and Spend not saved enabled to spot data issues.","ui.fieldColors":"Field colors","ui.chooseAPresetUseThe":"Choose a preset, use the color picker, or enter a hex color. Colors adjust for readability in light and dark themes. Unchanged fields keep their default appearance.","ui.resetLabels":"Reset labels","ui.resetColors":"Reset colors","ui.couldNotDisplayInfoBar":"Could not display Info Bar settings: {value}","ui.infoBar":"Info Bar","ui.weekly":"Weekly","ui.monthly":"Monthly","ui.window":"Window","ui.supportsImageInput":"Supports image input.","ui.vision":"Vision","ui.unknown":"Unknown","ui.unknownModel":"Unknown model","ui.pluginVersion":"\nPlugin version: {current}","ui.provider":"Provider: {provLabel} {modelLabel}\n","ui.pricingPeakOffPeakBeijing":"Pricing: peak/off-peak (Beijing time; weekday peaks 09:00–12:00 and 14:00–18:00; weekends off-peak)","ui.pricingFixed":"Pricing: fixed","ui.pricingNotListedUsingDefaults":"Pricing: not listed; using defaults","ui.zhipu":"Zhipu","ui.xiaomiMiMo":"Xiaomi MiMo","ui.subscription":"Subscription","ui.cloudBilling":"Cloud billing","ui.plan":"\nPlan: {plan}","ui.expiresLocalTime":"\nExpires: {value} (local time)","ui.subscriptionServiceModel":"Subscription service: {serviceName}\nModel: {rawModelLabel}{planLine}{expiryLine}{versionLine}","ui.balanceLookupIsNotYet":"Balance lookup is not yet supported for this provider.","ui.notSupported":"Not supported","ui.notConfigured":"Not configured: ","ui.notConfiguredConfigureItIn":"Not configured: {credName}. Configure it in Settings → Models.","ui.notConfiguredSettingsModels":"Not configured: {credName} → Settings → Models","ui.estimatedBalance":"Estimated balance: {symbol}{value}","ui.balance":"Balance: {symbol}{value}","ui.balance.pushBalanceGroups":"Balance","ui.low":"Low","ui.estimated":"(estimated)","ui.balanceIsTemporarilyUnavailableShowing":"Balance is temporarily unavailable. Showing the last data and retrying automatically.","ui.couldNotLoadBalanceCheck":"Could not load balance. Check your connection and API key.","ui.balanceUnavailable":"Balance unavailable","ui.beijingTime":"Beijing time: ","ui.peakPrice":"Peak price","ui.offPeakPrice":"Off-peak price","ui.input":": input ¥","ui.mCachedInput":"/M · cached input ¥","ui.mOutput":"/M · output ¥","ui.beijingTimeSwitchesTo":"Beijing time: {atLabel} switches to ","ui.until":"Until ","ui.offPeak":"off-peak","ui.peak":"peak","ui.today":"Today {symbol}{value}","ui.lastDays":"Last 30 days {symbol}{value}","ui.allTime":"All time {symbol}{value}","ui.sessionIncludingSubagents":"Session {costTxt} (including subagents){value}","ui.session":"Session","ui.spendIsTemporarilyUnavailableChat":"Spend is temporarily unavailable. Chat is unaffected.","ui.spendUnavailable":"Spend unavailable","ui.noSignInCredentialsFound":"No sign-in credentials found for {serviceName}. Please reauthorize.","ui.credentialsHaveExpiredPleaseReauthorize":"{serviceName} credentials have expired. Please reauthorize.","ui.deniedAccessReauthorizeOrTry":"{serviceName} denied access. Reauthorize or try again later.","ui.rateLimitReachedPleaseTry":"{serviceName} rate limit reached. Please try again later.","ui.timedOutCheckYourConnection":"{serviceName} timed out. Check your connection and try again.","ui.returnedAnUnrecognizedResponsePlease":"{serviceName} returned an unrecognized response. Please try again later.","ui.isTemporarilyUnavailableCheckYour":"{serviceName} is temporarily unavailable. Check your connection and try again.","ui.subscriptionExpiresLocalTime":"Subscription expires: {value} (local time)","ui.expires":"Expires","ui.subscriptionSource":"Subscription source: {value} (","ui.prepaidBalance":"Prepaid balance","ui.availableBalance":"Available balance: {balTxt}","ui.subscriptionSource.titleLines":"Subscription source: {value}","ui.windowRemainingUsed":"{label} window: remaining {value}% (used {usedPercent}%)","ui.resetsResetsIn":" · Resets {value} · Resets in {value2}","ui.windowRemainingUsedResets":"{label} window: remaining {value}% (used {usedPercent}%) · Resets {value4}","ui.resetsIn":"Resets in","ui.configureItInSettingsModels":". Configure it in Settings → Models.","ui.deniedAccessTheTokenMay":"{serviceName} denied access: the token may lack billing read permissions.","ui.billingIsTemporarilyUnavailableCheck":"{serviceName} billing is temporarily unavailable. Check your connection and permissions, then try again.","ui.billingSource":"Billing source: {value}","ui.thisMonthSSpend":"This month’s spend: {symbol}{value}","ui.budgetUsed":"Budget used: {value}%","ui.dailyFreeQuotaRemaining":"Daily free quota remaining: {value}","ui.thisMonth":"This month","ui.thisMonthSUsage":"This month’s usage","ui.budget":"Budget","ui.free":"Free","ui.resetsIn.pushBillingGroups":"{value} · Resets in {value2}","ui.billingServiceModel":"Billing service: {serviceName}\nModel: {modelLabel}{versionLine}","ui.pricing":"Pricing","ui.spend":"Spend","ui.mode":"Mode","ui.subscriptionQuota":"Subscription quota","ui.billing":"Billing","ui.temporarilyUnavailableKeepingTheLast":": temporarily unavailable. Keeping the last data and retrying automatically.","ui.spendJournalSavedButThe":"Spend journal saved, but the readable ledger has not been updated: ","ui.thisSpendRecordWasNot":"This spend record was not saved and will not be added to totals: ","ui.ledgerUpdatePending":"Ledger update pending","ui.spendNotSaved":"Spend not saved","ui.askYourAgentToUpdate":"Ask your agent to update this plugin to version {latest}.","ui.updateAvailable":"Update available","ui.tools":"Tools","ui.avgTTFT":"Avg. TTFT","ui.cacheHit":"Cache hit","ui.input.BottomInfoBar":"Input","ui.output":"Output","ui.savingView":"Saving view…","ui.clickToSwitchFullCompact":"Click to switch full/compact view","ui.pressEnterOrSpaceFor":"Press Enter or Space for compact view.","ui.pressEnterOrSpaceFor.BottomInfoBar":"Press Enter or Space for full view.","host.hour":"5-hour","host.unknownProvider":"Unknown provider","host.unexpectedSubscriptionQuotaRequestFailure":"Unexpected subscription quota request failure","host.settingsFileCouldNotBe":"Settings file could not be saved","host.everydayQuestions":"Everyday questions","host.mediumCodingTask":"Medium coding task","host.longDocumentAnalysisCodeReview":"Long document analysis / code review","host.largeProjectRefactorAcrossMultiple":"Large project refactor across multiple turns","host.subagentWorkflow":"Subagent workflow","host.couldNotSaveSettingsJson":"Could not save settings.json: {value}","host.couldNotReadCredentials":"Could not read credentials","host.notConfigured":"Not configured: {credential}","host.requestFailedHTTP":"Request failed: HTTP {status}.","host.unexpectedResponseFormat":"Unexpected response format","host.chatgptSubscriptionIsNotConnected":"ChatGPT subscription is not connected: credentials not found in ~/.codex/auth.json. Install dsh-chatgpt-subscription and sign in.","host.chatgptSubscriptionCredentialsAreMissing":"ChatGPT subscription credentials are missing id_token. Install dsh-chatgpt-subscription and reauthorize.","host.opencodeGoIsNotConfigured":"OpenCode Go is not configured. Set OPENCODE_GO_API_KEY or use opencode auth.json.","host.zhipu":"Zhipu {mapped}","host.zhipu.parseZaiQuota":"Zhipu {value}{value2}","host.zhipuAPIKeyIsNot":"Zhipu API key is not configured. Set ZAI_API_KEY or ZAI_CODING_CN_API_KEY.","host.zhipuAPIAuthenticationFailedThe":"Zhipu API authentication failed: the key is expired or invalid.","host.requestFailed":"Request failed: {value} {msg}.","host.xiaomiMiMoTokenPlanCredentials":"Xiaomi MiMo Token Plan credentials are not configured: {credName} or XIAOMI_API_KEY.","host.requestFailedHTTP.fetchXiaomiTokenPlanUsage":"Request failed: HTTP {value}.","host.notConfiguredTOGETHERAPIKEY":"Not configured: TOGETHER_API_KEY","host.actualMonthlyBillFromThe":"Actual monthly bill from the Together Usage API","host.notConfiguredFIREWORKSAPIKEY":"Not configured: FIREWORKS_API_KEY","host.couldNotReadAccountAccount":"Could not read account (account_id missing)","host.actualBillForThisPeriod":"Actual bill for this period (Fireworks Billing Summary)","host.actualUsageForThisPeriod":"Actual usage for this period (billingUsage fallback; no spend amount)","host.notConfiguredAWSCredentialsAWS":"Not configured: AWS credentials — AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY","host.requestFailedHTTPTheToken":"Request failed: HTTP {status}. The token may lack ce:GetCostAndUsage permission","host.actualMonthlyBillFromAWS":"Actual monthly bill from AWS Cost Explorer — about 24 hours behind","host.notConfiguredCLOUDFLAREAPIKEY":"Not configured: CLOUDFLARE_API_KEY — requires an account-level token with Billing read permission","host.notConfiguredCLOUDFLAREACCOUNTID":"Not configured: CLOUDFLARE_ACCOUNT_ID","host.requestFailedHTTPTheToken.fetchCloudflareBilling":"Request failed: HTTP {status}. The token requires Billing read permission","host.actualMonthlyUsageFromThe":"Actual monthly usage from the Cloudflare Billable Usage API — Alpha","host.unexpectedBillingRequestFailure":"Unexpected billing request failure","host.spendSummaryFileMissingArchived":"Spend summary file missing: archived amounts are not included in displayed totals. Details remain in usage-archive/ for manual recovery.","host.spendLedgerCouldNotBe":"Spend ledger could not be saved","host.couldNotSaveArchivedSpend":"Could not save archived spend summaries: {value}","host.basedOnYourLastSessions":"Based on your last {count} sessions","host.estimatedFromSpendingOverThe":"Estimated from spending over the last {SPEND_DAYS} days","host.yourTypicalSession":"Your typical session","host.patchMustIncludeFieldsOr":"patch must include fields or colors","host.fieldsMustBeAnObject":"fields must be an object","host.unknownFieldId":"Unknown field id: {key}","host.fieldVisibilityMustBeA":"Field visibility must be a boolean: {key}","host.colorsMustBeAnObject":"colors must be an object","host.colorMustBeAPreset":"Color must be a preset name or #RRGGBB: {key}","field.anchorGroup.label":"Provider and model","field.anchorGroup.note":"Identifies the provider and model used in this conversation.","field.subServiceGroup.label":"Subscription service and model","field.subServiceGroup.note":"Subscription service (ChatGPT, Codex, OpenCode Go, Zhipu or Xiaomi MiMo) and model or plan tier.","field.billingServiceGroup.label":"Billing service and model","field.billingServiceGroup.note":"Cloud billing service (Together, Fireworks, AWS Bedrock or Cloudflare) and model.","field.sessionCost.label":"Session spend","field.sessionCost.note":"Actual session spend, including subagents; shows ¥0.000 before any records exist. Hover for today, the last 30 days and all time.","field.balance.note":"Actual account balance. A low balance appears in red with a Low label.","field.period.label":"Pricing period","field.period.note":"For models with peak/off-peak pricing (currently DeepSeek): red for peak, green for off-peak.","field.countdown.label":"Price switch countdown","field.countdown.note":"Time until peak/off-peak pricing changes, for models with time-based pricing.","field.expiry.label":"Subscription expiry","field.expiry.note":"Shown when subscription credentials include an expiry date, such as a Codex plan.","field.subWindow5h.label":"5-hour quota","field.subWindow5h.note":"Remaining quota in the rolling 5-hour window. Compact view shows only the shortest available window.","field.subWindowWeek.label":"Weekly quota","field.subWindowWeek.note":"Weekly quota remaining. Shown in full view; compact view prioritizes shorter windows.","field.subWindowMonth.label":"Monthly quota","field.subWindowMonth.note":"Monthly quota remaining. Shown in full view; compact view prioritizes shorter windows.","field.resetCountdown.label":"Quota reset countdown","field.resetCountdown.note":"Time until the displayed quota window resets.","field.subBalance.note":"Shown for prepaid accounts, such as Zhipu pay-as-you-go, in place of quota windows.","field.billingSpend.label":"Monthly spend / usage","field.billingSpend.note":"Actual cloud spend or usage for the current billing period.","field.budget.label":"Budget used","field.budget.note":"Shown for providers that support budget queries (currently AWS Bedrock).","field.freeQuota.label":"Free quota and reset","field.freeQuota.note":"Shown only when the API reports a daily free allowance and reset time.","field.turnsSteps.label":"Turns and steps","field.turnsSteps.note":"Conversation progress: N turns · M steps.","field.llmTime.label":"LLM time","field.llmTime.note":"Total model inference time.","field.toolTime.label":"Tool time","field.toolTime.note":"Total tool execution time.","field.cacheHit.note":"Prompt cache hit rate as a percentage.","field.tokensIO.label":"Input / output tokens","field.tokensIO.note":"Total session usage: Input X tok · Output Y tok.","field.unmapped.label":"Unsupported provider hint","field.unmapped.note":"Shown when balance lookup is not supported for the current provider.","field.noKeyHint.label":"Missing credentials hint","field.noKeyHint.note":"Setup instructions when an API key is missing.","field.balanceError.label":"Balance error","field.balanceError.note":"Shown when balance lookup fails or the last data is being displayed.","field.usageError.label":"Spend error","field.usageError.note":"Shown when spend data is temporarily unavailable.","field.refreshFailure.label":"Refresh error","field.refreshFailure.note":"One alert at the end of the bar when any data source fails; duplicate alerts are combined.","field.persistWarning.label":"Unsaved spend alert","field.persistWarning.note":"Shown when the ledger cannot be saved. Keep enabled to spot missing amounts.","field.updateNotice.note":"Shown when a new version is available on npm.","group.native":"Native fields","group.plugin":"Plugin fields","ui.sentenceSeparator":" ","ui.listSeparator":", ","ui.sentenceEnd":".","ui.fieldErrorPrefix":"\"{label}\": ","ui.turnCount":"{count} turn","ui.turnCountPlural":"{count} turns","ui.stepCount":"{count} step","ui.stepCountPlural":"{count} steps"}};
|
|
21
|
+
let t;
|
|
22
|
+
// Compatibility with existing host snapshots, whose display fields are text.
|
|
23
|
+
// Known labels and messages follow the browser locale even while snapshots are cached.
|
|
24
|
+
function localizeHostText(message, translate, dictionaries) {
|
|
25
|
+
if (typeof message !== 'string' || message.length === 0) return message
|
|
26
|
+
for (const key of Object.keys(dictionaries.zh)) {
|
|
27
|
+
if (dictionaries.zh[key] === message || dictionaries.en[key] === message) return translate(key)
|
|
28
|
+
}
|
|
29
|
+
for (const key of Object.keys(dictionaries.zh)) {
|
|
30
|
+
if (!key.startsWith('host.')) continue
|
|
31
|
+
for (const language of ['zh', 'en']) {
|
|
32
|
+
const template = dictionaries[language][key]
|
|
33
|
+
if (!/\{\w+\}/.test(template)) continue
|
|
34
|
+
const names = []
|
|
35
|
+
const pattern = template.split(/(\{\w+\})/).map(function (part) {
|
|
36
|
+
if (/^\{\w+\}$/.test(part)) { names.push(part.slice(1, -1)); return '([\\s\\S]*?)' }
|
|
37
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
38
|
+
}).join('')
|
|
39
|
+
const match = new RegExp('^' + pattern + '$').exec(message)
|
|
40
|
+
if (match) {
|
|
41
|
+
const params = {}
|
|
42
|
+
names.forEach(function (name, index) { params[name] = match[index + 1] })
|
|
43
|
+
return translate(key, params)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return message
|
|
48
|
+
}
|
|
49
|
+
function hostText(message) { return localizeHostText(message, t, LOCALES); }
|
|
19
50
|
|
|
20
51
|
const RPC_BASE = '/_dsh/dsh-bottom-info-bar';
|
|
21
52
|
|
|
@@ -37,8 +68,8 @@ const FORCE_REFRESH_WINDOW_MS = 6000;
|
|
|
37
68
|
function rpc(method, args, externalSignal) {
|
|
38
69
|
let abortReason = null;
|
|
39
70
|
const controller = new AbortController();
|
|
40
|
-
const timer = window.setTimeout(function () { abortReason = '
|
|
41
|
-
function onExternalAbort() { abortReason = '
|
|
71
|
+
const timer = window.setTimeout(function () { abortReason = t('ui.requestTimedOut'); controller.abort(); }, RPC_TIMEOUT_MS);
|
|
72
|
+
function onExternalAbort() { abortReason = t('ui.requestCanceled'); controller.abort(); }
|
|
42
73
|
function cleanup() {
|
|
43
74
|
window.clearTimeout(timer);
|
|
44
75
|
if (externalSignal) externalSignal.removeEventListener('abort', onExternalAbort);
|
|
@@ -46,7 +77,7 @@ function rpc(method, args, externalSignal) {
|
|
|
46
77
|
if (externalSignal) {
|
|
47
78
|
if (externalSignal.aborted) {
|
|
48
79
|
cleanup();
|
|
49
|
-
return Promise.reject(new Error('
|
|
80
|
+
return Promise.reject(new Error(t('ui.requestCanceled')));
|
|
50
81
|
}
|
|
51
82
|
externalSignal.addEventListener('abort', onExternalAbort);
|
|
52
83
|
}
|
|
@@ -64,12 +95,12 @@ function rpc(method, args, externalSignal) {
|
|
|
64
95
|
});
|
|
65
96
|
}
|
|
66
97
|
return res.text().then(function (raw) {
|
|
67
|
-
try { return JSON.parse(raw); } catch (e) { throw new Error('
|
|
98
|
+
try { return JSON.parse(raw); } catch (e) { throw new Error(t('ui.couldNotParseResponse')); }
|
|
68
99
|
});
|
|
69
100
|
}).catch(function (err) {
|
|
70
101
|
// 本函数主动 abort(超时/外部取消)→ 统一为可读错误;其余错误原样抛出
|
|
71
102
|
if (abortReason !== null) throw new Error(abortReason);
|
|
72
|
-
if (err && err.name === 'AbortError') throw new Error('
|
|
103
|
+
if (err && err.name === 'AbortError') throw new Error(t('ui.requestCanceled'));
|
|
73
104
|
throw err;
|
|
74
105
|
}).finally(cleanup);
|
|
75
106
|
}
|
|
@@ -88,7 +119,7 @@ function mergeLoadResults(prev, results) {
|
|
|
88
119
|
} else {
|
|
89
120
|
next[key] = prev[key];
|
|
90
121
|
const reason = r && r.reason;
|
|
91
|
-
next.errors[key] = reason && reason.message ? String(reason.message) : String(reason || '
|
|
122
|
+
next.errors[key] = reason && reason.message ? String(reason.message) : String(reason || t('ui.rpcFailed'));
|
|
92
123
|
}
|
|
93
124
|
}
|
|
94
125
|
return next;
|
|
@@ -98,40 +129,40 @@ function mergeLoadResults(prev, results) {
|
|
|
98
129
|
// 字段注册表/预设色板由构建从 constants.js 注入(单一来源,宿主白名单同源)
|
|
99
130
|
const FIELD_REGISTRY = [
|
|
100
131
|
// 插件字段 · 服务锚点(D6:与其他字段同等可隐藏)
|
|
101
|
-
{ id: 'anchorGroup', label:
|
|
102
|
-
{ id: 'subServiceGroup', label:
|
|
103
|
-
{ id: 'billingServiceGroup', label:
|
|
132
|
+
{ id: 'anchorGroup', label: "field.anchorGroup.label", group: 'plugin', modes: ['balance'], anchor: true, colorKind: 'provider', note: "field.anchorGroup.note" },
|
|
133
|
+
{ id: 'subServiceGroup', label: "field.subServiceGroup.label", group: 'plugin', modes: ['subscription'], anchor: true, colorKind: 'provider', note: "field.subServiceGroup.note" },
|
|
134
|
+
{ id: 'billingServiceGroup', label: "field.billingServiceGroup.label", group: 'plugin', modes: ['billing'], anchor: true, colorKind: 'provider', note: "field.billingServiceGroup.note" },
|
|
104
135
|
// 插件字段 · 通用(多模式共用)
|
|
105
|
-
{ id: 'sessionCost', label:
|
|
136
|
+
{ id: 'sessionCost', label: "field.sessionCost.label", group: 'plugin', modes: ['balance', 'subscription'], colorKind: 'inherit', note: "field.sessionCost.note" },
|
|
106
137
|
// 插件字段 · 余额制
|
|
107
|
-
{ id: 'balance', label:
|
|
108
|
-
{ id: 'period', label:
|
|
109
|
-
{ id: 'countdown', label:
|
|
138
|
+
{ id: 'balance', label: "ui.balance.pushBalanceGroups", group: 'plugin', modes: ['balance'], colorKind: 'inherit', note: "field.balance.note" },
|
|
139
|
+
{ id: 'period', label: "field.period.label", group: 'plugin', modes: ['balance'], colorKind: 'period', note: "field.period.note" },
|
|
140
|
+
{ id: 'countdown', label: "field.countdown.label", group: 'plugin', modes: ['balance'], colorKind: 'inherit', note: "field.countdown.note" },
|
|
110
141
|
// 插件字段 · 订阅制
|
|
111
|
-
{ id: 'expiry', label:
|
|
112
|
-
{ id: 'subWindow5h', label:
|
|
113
|
-
{ id: 'subWindowWeek', label:
|
|
114
|
-
{ id: 'subWindowMonth', label:
|
|
115
|
-
{ id: 'resetCountdown', label:
|
|
116
|
-
{ id: 'subBalance', label:
|
|
142
|
+
{ id: 'expiry', label: "field.expiry.label", group: 'plugin', modes: ['subscription'], colorKind: 'inherit', note: "field.expiry.note" },
|
|
143
|
+
{ id: 'subWindow5h', label: "field.subWindow5h.label", group: 'plugin', modes: ['subscription'], colorKind: 'inherit', note: "field.subWindow5h.note" },
|
|
144
|
+
{ id: 'subWindowWeek', label: "field.subWindowWeek.label", group: 'plugin', modes: ['subscription'], colorKind: 'inherit', note: "field.subWindowWeek.note" },
|
|
145
|
+
{ id: 'subWindowMonth', label: "field.subWindowMonth.label", group: 'plugin', modes: ['subscription'], colorKind: 'inherit', note: "field.subWindowMonth.note" },
|
|
146
|
+
{ id: 'resetCountdown', label: "field.resetCountdown.label", group: 'plugin', modes: ['subscription'], colorKind: 'inherit', note: "field.resetCountdown.note" },
|
|
147
|
+
{ id: 'subBalance', label: "ui.prepaidBalance", group: 'plugin', modes: ['subscription'], colorKind: 'inherit', note: "field.subBalance.note" },
|
|
117
148
|
// 插件字段 · 账单制
|
|
118
|
-
{ id: 'billingSpend', label:
|
|
119
|
-
{ id: 'budget', label:
|
|
120
|
-
{ id: 'freeQuota', label:
|
|
149
|
+
{ id: 'billingSpend', label: "field.billingSpend.label", group: 'plugin', modes: ['billing'], colorKind: 'inherit', note: "field.billingSpend.note" },
|
|
150
|
+
{ id: 'budget', label: "field.budget.label", group: 'plugin', modes: ['billing'], colorKind: 'inherit', note: "field.budget.note" },
|
|
151
|
+
{ id: 'freeQuota', label: "field.freeQuota.label", group: 'plugin', modes: ['billing'], colorKind: 'inherit', note: "field.freeQuota.note" },
|
|
121
152
|
// 原生字段(DeepSeek 原生底部栏原有;完整模式独占可见)
|
|
122
|
-
{ id: 'turnsSteps', label:
|
|
123
|
-
{ id: 'llmTime', label:
|
|
124
|
-
{ id: 'toolTime', label:
|
|
125
|
-
{ id: 'cacheHit', label:
|
|
126
|
-
{ id: 'tokensIO', label:
|
|
153
|
+
{ id: 'turnsSteps', label: "field.turnsSteps.label", group: 'native', modes: ['native'], colorKind: 'inherit', note: "field.turnsSteps.note" },
|
|
154
|
+
{ id: 'llmTime', label: "field.llmTime.label", group: 'native', modes: ['native'], colorKind: 'inherit', note: "field.llmTime.note" },
|
|
155
|
+
{ id: 'toolTime', label: "field.toolTime.label", group: 'native', modes: ['native'], colorKind: 'inherit', note: "field.toolTime.note" },
|
|
156
|
+
{ id: 'cacheHit', label: "ui.cacheHit", group: 'native', modes: ['native'], colorKind: 'inherit', note: "field.cacheHit.note" },
|
|
157
|
+
{ id: 'tokensIO', label: "field.tokensIO.label", group: 'native', modes: ['native'], colorKind: 'inherit', note: "field.tokensIO.note" },
|
|
127
158
|
// 插件字段 · 状态与提醒(建议保留)
|
|
128
|
-
{ id: 'unmapped', label:
|
|
129
|
-
{ id: 'noKeyHint', label:
|
|
130
|
-
{ id: 'balanceError', label:
|
|
131
|
-
{ id: 'usageError', label:
|
|
132
|
-
{ id: 'refreshFailure', label:
|
|
133
|
-
{ id: 'persistWarning', label:
|
|
134
|
-
{ id: 'updateNotice', label:
|
|
159
|
+
{ id: 'unmapped', label: "field.unmapped.label", group: 'plugin', modes: ['balance'], colorKind: 'muted', note: "field.unmapped.note" },
|
|
160
|
+
{ id: 'noKeyHint', label: "field.noKeyHint.label", group: 'plugin', modes: ['balance'], suggestKeep: true, colorKind: 'alert', note: "field.noKeyHint.note" },
|
|
161
|
+
{ id: 'balanceError', label: "field.balanceError.label", group: 'plugin', modes: ['balance'], suggestKeep: true, colorKind: 'alert', note: "field.balanceError.note" },
|
|
162
|
+
{ id: 'usageError', label: "field.usageError.label", group: 'plugin', modes: ['balance', 'subscription'], suggestKeep: true, colorKind: 'alert', note: "field.usageError.note" },
|
|
163
|
+
{ id: 'refreshFailure', label: "field.refreshFailure.label", group: 'plugin', modes: ['balance', 'subscription', 'billing'], suggestKeep: true, colorKind: 'alert', note: "field.refreshFailure.note" },
|
|
164
|
+
{ id: 'persistWarning', label: "field.persistWarning.label", group: 'plugin', modes: ['balance', 'subscription', 'billing'], suggestKeep: true, colorKind: 'alert', note: "field.persistWarning.note" },
|
|
165
|
+
{ id: 'updateNotice', label: "ui.updateAvailable", group: 'plugin', modes: ['balance', 'subscription', 'billing'], suggestKeep: true, colorKind: 'alert', note: "field.updateNotice.note" },
|
|
135
166
|
];
|
|
136
167
|
const PRESET_COLORS = ['red', 'green', 'blue', 'purple', 'orange', 'neutral'];
|
|
137
168
|
const PRESET_COLOR_SET = new Set(PRESET_COLORS);
|
|
@@ -322,7 +353,7 @@ function assembleInfoBarRow(groups, trailingErrorGroups, createElement) {
|
|
|
322
353
|
const seenRefreshFailure = { value: false };
|
|
323
354
|
const visibleErrors = trailingErrorGroups.filter(function (node) {
|
|
324
355
|
const text = trailingErrorText(node);
|
|
325
|
-
if (text !== '
|
|
356
|
+
if (text !== t('ui.refreshFailed')) return true;
|
|
326
357
|
if (seenRefreshFailure.value) return false;
|
|
327
358
|
seenRefreshFailure.value = true;
|
|
328
359
|
return true;
|
|
@@ -420,27 +451,28 @@ function installStyles() {
|
|
|
420
451
|
// (官方 dsh-client-ui-settings-general 同型写法),不再有 async 串接。
|
|
421
452
|
// 约定:本段顶层标识符一律 bibSet* / InfoBarSettings* 前缀,绝不与主包重名。
|
|
422
453
|
const BIB_SET_EVENT = 'dsh-bib-config-changed';
|
|
423
|
-
const BIB_SET_PRESET_LABELS = { red:
|
|
454
|
+
const BIB_SET_PRESET_LABELS = { red: "color.red", green: "color.green", blue: "color.blue", purple: "color.purple", orange: "color.orange", neutral: "color.neutral" };
|
|
424
455
|
// 原生取色器(input[type=color])在未自定义时显示的代表色(浅色主题值;实际信息栏渲染仍按主题变量)
|
|
425
456
|
const BIB_SET_PRESET_WELL_HEX = { red: '#D92D20', green: '#087F5B', blue: '#0044CC', purple: '#6941C6', orange: '#B54708', neutral: '#333333' };
|
|
426
457
|
const BIB_SET_HEX_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
427
458
|
// 「显示字段」两级分组(D6 用户拍板:原生在前、插件在后)——构建从 constants.js 注入(单一来源)
|
|
428
459
|
const FIELD_GROUP_ORDER = ['native', 'plugin'];
|
|
429
460
|
const FIELD_GROUP_LABELS = {
|
|
430
|
-
native:
|
|
431
|
-
plugin:
|
|
461
|
+
native: "group.native",
|
|
462
|
+
plugin: "group.plugin",
|
|
432
463
|
};
|
|
433
464
|
// 每行出现条件文案:modes → 中文(余额制/订阅制/账单制/原生统计行/通用)
|
|
434
|
-
const BIB_SET_MODE_LABELS = { balance:
|
|
465
|
+
const BIB_SET_MODE_LABELS = { balance: "mode.balance", subscription: "mode.subscription", billing: "mode.billing", native: "mode.native", common: "mode.common" };
|
|
435
466
|
function bibSetModeText(field) {
|
|
436
467
|
if (!field || !Array.isArray(field.modes) || field.modes.length === 0) return '';
|
|
437
|
-
if (field.modes.length === 1 && field.modes[0] === 'native') return '
|
|
468
|
+
if (field.modes.length === 1 && field.modes[0] === 'native') return t('ui.nativeStatsField');
|
|
438
469
|
const labels = [];
|
|
439
470
|
for (let i = 0; i < field.modes.length; i++) {
|
|
440
|
-
const
|
|
471
|
+
const key = BIB_SET_MODE_LABELS[field.modes[i]];
|
|
472
|
+
const label = key && t(key);
|
|
441
473
|
if (label && labels.indexOf(label) === -1) labels.push(label);
|
|
442
474
|
}
|
|
443
|
-
return labels.length > 0 ? '
|
|
475
|
+
return labels.length > 0 ? t('ui.shownIn') + labels.join(t('ui.listSeparator')) : '';
|
|
444
476
|
}
|
|
445
477
|
|
|
446
478
|
function bibSetDispatchChanged() {
|
|
@@ -449,7 +481,7 @@ function bibSetDispatchChanged() {
|
|
|
449
481
|
}
|
|
450
482
|
|
|
451
483
|
function bibSetOperationMessage(err) {
|
|
452
|
-
return String((err && err.message) || err || '
|
|
484
|
+
return String((err && err.message) || err || t('ui.pleaseTryAgainLater'));
|
|
453
485
|
}
|
|
454
486
|
|
|
455
487
|
// ---------- 设置页样式(融入 DSH 设置面板:卡片/行布局/控件全部走 --dsw-alias-* 令牌) ----------
|
|
@@ -563,7 +595,7 @@ function bibSetPalette(props) {
|
|
|
563
595
|
const isDefault = option === 'default';
|
|
564
596
|
const coreStyle = {};
|
|
565
597
|
if (!isDefault) coreStyle.background = 'var(--bi-palette-' + option + ')';
|
|
566
|
-
const ariaLabel = isDefault ? '
|
|
598
|
+
const ariaLabel = isDefault ? t('ui.restoreDefaultColor') : (t(BIB_SET_PRESET_LABELS[option]) || option);
|
|
567
599
|
return React.createElement('button', {
|
|
568
600
|
key: option,
|
|
569
601
|
type: 'button',
|
|
@@ -584,7 +616,7 @@ function bibSetPalette(props) {
|
|
|
584
616
|
// ---------- 页面组件 ----------
|
|
585
617
|
// M2 首渲骨架:页面标题行——任何状态下都先输出可见标题(数据未到、加载失败也一样有东西看)
|
|
586
618
|
function bibSetPageTitle() {
|
|
587
|
-
return React.createElement('h1', { className: 'bib-set-page-title' }, '
|
|
619
|
+
return React.createElement('h1', { className: 'bib-set-page-title' }, t('ui.infoBarSettings'));
|
|
588
620
|
}
|
|
589
621
|
|
|
590
622
|
function InfoBarSettingsSection(props) {
|
|
@@ -607,7 +639,7 @@ function InfoBarSettingsSection(props) {
|
|
|
607
639
|
setSnapshot({ fields: cfg.fields, colors: cfg.colors || {}, configVersion: cfg.configVersion || 0 });
|
|
608
640
|
setStatus('ready');
|
|
609
641
|
} else {
|
|
610
|
-
setLoadError('
|
|
642
|
+
setLoadError(t('ui.settingsAreTemporarilyUnavailable')); setStatus('error');
|
|
611
643
|
}
|
|
612
644
|
}).catch(function (err) {
|
|
613
645
|
if (!active) return;
|
|
@@ -632,13 +664,13 @@ function InfoBarSettingsSection(props) {
|
|
|
632
664
|
if (status === 'loading') {
|
|
633
665
|
return React.createElement('div', { className: 'bib-set-root bib-settings' },
|
|
634
666
|
bibSetPageTitle(),
|
|
635
|
-
React.createElement('p', { className: 'bib-set-status' }, '
|
|
667
|
+
React.createElement('p', { className: 'bib-set-status' }, t('ui.loadingInfoBarSettings')));
|
|
636
668
|
}
|
|
637
669
|
// RPC 加载失败:照旧显示可见错误文字(角色 alert),页面不空白
|
|
638
670
|
if (status === 'error') {
|
|
639
671
|
return React.createElement('div', { className: 'bib-set-root bib-settings' },
|
|
640
672
|
bibSetPageTitle(),
|
|
641
|
-
React.createElement('p', { className: 'bib-set-alert', role: 'alert' }, '
|
|
673
|
+
React.createElement('p', { className: 'bib-set-alert', role: 'alert' }, t('ui.couldNotLoadInfoBar') + (hostText(loadError) || t('ui.pleaseTryAgainLater'))));
|
|
642
674
|
}
|
|
643
675
|
|
|
644
676
|
const fieldsById = {};
|
|
@@ -648,7 +680,7 @@ function InfoBarSettingsSection(props) {
|
|
|
648
680
|
const value = snapshot.colors[id];
|
|
649
681
|
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
650
682
|
}
|
|
651
|
-
function labelOf(id) { const f = fieldsById[id]; return f ? f.label : id; }
|
|
683
|
+
function labelOf(id) { const f = fieldsById[id]; return f ? t(f.label) : id; }
|
|
652
684
|
function makePair(key, value) { const pair = {}; pair[key] = value; return pair; }
|
|
653
685
|
|
|
654
686
|
// 服务端快照回写(configVersion 一并更新;persisted=false 时如实提示落盘失败)
|
|
@@ -661,7 +693,7 @@ function InfoBarSettingsSection(props) {
|
|
|
661
693
|
configVersion: typeof res.configVersion === 'number' ? res.configVersion : ((prev && prev.configVersion) || 0),
|
|
662
694
|
};
|
|
663
695
|
});
|
|
664
|
-
if (res.persisted === false) setNotice('
|
|
696
|
+
if (res.persisted === false) setNotice({ text: function () { return t('ui.changesAppliedButCouldNot') + (hostText(res.warning) || t('ui.unknownReason')); } });
|
|
665
697
|
}
|
|
666
698
|
|
|
667
699
|
// 乐观更新 + 失败回退(参照 density toggle)+ 版本号守卫
|
|
@@ -680,7 +712,9 @@ function InfoBarSettingsSection(props) {
|
|
|
680
712
|
if (seq !== opSeqRef.current) { endOp(); return; }
|
|
681
713
|
endOp();
|
|
682
714
|
revertOptimistic();
|
|
683
|
-
|
|
715
|
+
// Translate feedback during rendering, including its field label. The
|
|
716
|
+
// object wrapper prevents React from treating the thunk as a state updater.
|
|
717
|
+
setOpError({ text: function () { return t('ui.couldNotSave', { errorPrefix: errorPrefix(), value: hostText(bibSetOperationMessage(err)) }); } });
|
|
684
718
|
});
|
|
685
719
|
}
|
|
686
720
|
|
|
@@ -690,7 +724,7 @@ function InfoBarSettingsSection(props) {
|
|
|
690
724
|
commit({ fields: makePair(id, next) },
|
|
691
725
|
function () { setSnapshot(function (s) { return Object.assign({}, s, { fields: Object.assign({}, s.fields, makePair(id, next)) }); }); },
|
|
692
726
|
function () { setSnapshot(function (s) { return Object.assign({}, s, { fields: Object.assign({}, s.fields, makePair(id, previous)) }); }); },
|
|
693
|
-
'
|
|
727
|
+
function () { return t('ui.fieldErrorPrefix', { label: labelOf(id) }); });
|
|
694
728
|
}
|
|
695
729
|
|
|
696
730
|
function setColor(id, next) {
|
|
@@ -702,7 +736,7 @@ function InfoBarSettingsSection(props) {
|
|
|
702
736
|
commit({ colors: makePair(id, next) },
|
|
703
737
|
function () { setSnapshot(function (s) { return Object.assign({}, s, { colors: Object.assign({}, s.colors, makePair(id, next)) }); }); },
|
|
704
738
|
function () { setSnapshot(function (s) { return Object.assign({}, s, { colors: Object.assign({}, s.colors, makePair(id, previous)) }); }); },
|
|
705
|
-
'
|
|
739
|
+
function () { return t('ui.color', { value: labelOf(id) }); });
|
|
706
740
|
}
|
|
707
741
|
|
|
708
742
|
// hex 输入:输入中仅标记非法(描红 + aria-invalid);Enter/失焦时合法才提交,非法回退当前值
|
|
@@ -725,7 +759,7 @@ function InfoBarSettingsSection(props) {
|
|
|
725
759
|
setHexDrafts(function (drafts) { return Object.assign({}, drafts, makePair(id, undefined)); });
|
|
726
760
|
if (value.length === 0) return;
|
|
727
761
|
if (!BIB_SET_HEX_PATTERN.test(value)) {
|
|
728
|
-
setOpError('
|
|
762
|
+
setOpError({ text: function () { return t('ui.hasAnInvalidColorEnter', { value: labelOf(id) }); } });
|
|
729
763
|
return;
|
|
730
764
|
}
|
|
731
765
|
setColor(id, value.toUpperCase());
|
|
@@ -741,11 +775,11 @@ function InfoBarSettingsSection(props) {
|
|
|
741
775
|
endOp();
|
|
742
776
|
applyServerResult(res);
|
|
743
777
|
bibSetDispatchChanged();
|
|
744
|
-
setNotice(kind === 'colors' ? '
|
|
778
|
+
setNotice({ text: function () { return kind === 'colors' ? t('ui.defaultColorsRestored') : t('ui.defaultLabelsRestored'); } });
|
|
745
779
|
}).catch(function (err) {
|
|
746
780
|
if (seq !== opSeqRef.current) { endOp(); return; }
|
|
747
781
|
endOp();
|
|
748
|
-
setOpError('
|
|
782
|
+
setOpError({ text: function () { return t('ui.couldNotReset', { value: hostText(bibSetOperationMessage(err)) }); } });
|
|
749
783
|
});
|
|
750
784
|
}
|
|
751
785
|
|
|
@@ -761,26 +795,26 @@ function InfoBarSettingsSection(props) {
|
|
|
761
795
|
const isAnchor = field.anchor === true;
|
|
762
796
|
const descParts = [];
|
|
763
797
|
// D6:锚点与其他字段同等可隐藏——「身份锚点」仅作为说明保留,不再恒开/禁用
|
|
764
|
-
if (isAnchor) descParts.push('
|
|
798
|
+
if (isAnchor) descParts.push(t('ui.identifiesTheProviderAndModel'));
|
|
765
799
|
const modeText = bibSetModeText(field);
|
|
766
|
-
if (modeText) descParts.push(modeText + '
|
|
767
|
-
if (field.note) descParts.push(field.note);
|
|
768
|
-
if (field.suggestKeep) descParts.push('
|
|
800
|
+
if (modeText) descParts.push(modeText + t('ui.sentenceEnd'));
|
|
801
|
+
if (field.note) descParts.push(t(field.note));
|
|
802
|
+
if (field.suggestKeep) descParts.push(t('ui.keepEnabledToSeeThese'));
|
|
769
803
|
rows.push(React.createElement('div', { key: field.id, className: 'bib-set-row' },
|
|
770
804
|
React.createElement('div', { className: 'bib-set-rowText' },
|
|
771
805
|
React.createElement('div', { className: 'bib-set-rowTitle' },
|
|
772
|
-
field.label,
|
|
773
|
-
field.suggestKeep ? React.createElement('span', { className: 'bib-set-keep' }, '
|
|
774
|
-
React.createElement('div', { className: 'bib-set-rowDesc' }, descParts.join(''))),
|
|
806
|
+
t(field.label),
|
|
807
|
+
field.suggestKeep ? React.createElement('span', { className: 'bib-set-keep' }, t('ui.recommended')) : null),
|
|
808
|
+
React.createElement('div', { className: 'bib-set-rowDesc' }, descParts.join(t('ui.sentenceSeparator')))),
|
|
775
809
|
bibSetSwitch({
|
|
776
|
-
label: '
|
|
810
|
+
label: t('ui.show', { label: t(field.label) }),
|
|
777
811
|
checked: fieldOn(field.id),
|
|
778
|
-
title: fieldOn(field.id) ? '
|
|
812
|
+
title: fieldOn(field.id) ? t('ui.clickToHide') : t('ui.clickToShow'),
|
|
779
813
|
onToggle: function (next) { setFieldFlag(field.id, next); },
|
|
780
814
|
})));
|
|
781
815
|
}
|
|
782
816
|
groupsChildren.push(React.createElement('div', { key: 'g-' + group },
|
|
783
|
-
React.createElement('div', { className: 'bib-set-group-title' }, FIELD_GROUP_LABELS[group]
|
|
817
|
+
React.createElement('div', { className: 'bib-set-group-title' }, FIELD_GROUP_LABELS[group] ? t(FIELD_GROUP_LABELS[group]) : group),
|
|
784
818
|
rows));
|
|
785
819
|
}
|
|
786
820
|
|
|
@@ -799,23 +833,23 @@ function InfoBarSettingsSection(props) {
|
|
|
799
833
|
const hexValue = draft !== null ? draft : committedHexText(field.id);
|
|
800
834
|
const hexInvalid = draft !== null && draft.trim().length > 0 && !BIB_SET_HEX_PATTERN.test(draft.trim());
|
|
801
835
|
const wellValue = isHex ? value : (isPreset ? (BIB_SET_PRESET_WELL_HEX[value] || '#333333') : '#333333');
|
|
802
|
-
const valueText = value === null ? '
|
|
803
|
-
: (isPreset ? (BIB_SET_PRESET_LABELS[value] || value) : value.toUpperCase());
|
|
836
|
+
const valueText = value === null ? t('ui.default')
|
|
837
|
+
: (isPreset ? (t(BIB_SET_PRESET_LABELS[value]) || value) : value.toUpperCase());
|
|
804
838
|
rows.push(React.createElement('div', { key: field.id, className: 'bib-set-row' },
|
|
805
839
|
React.createElement('div', { className: 'bib-set-rowText' },
|
|
806
|
-
React.createElement('div', { className: 'bib-set-rowTitle' }, field.label),
|
|
807
|
-
React.createElement('div', { className: 'bib-set-rowDesc' }, '
|
|
840
|
+
React.createElement('div', { className: 'bib-set-rowTitle' }, t(field.label)),
|
|
841
|
+
React.createElement('div', { className: 'bib-set-rowDesc' }, t('ui.current', { valueText: valueText }) + (field.anchor === true ? t('ui.providerAndModel') : ''))),
|
|
808
842
|
React.createElement('div', { className: 'bib-set-controls' },
|
|
809
843
|
React.createElement(bibSetPalette, {
|
|
810
|
-
label: field.label
|
|
844
|
+
label: t('ui.presetColor', { label: t(field.label) }),
|
|
811
845
|
value: value,
|
|
812
846
|
onSelect: function (next) { setColor(field.id, next); },
|
|
813
847
|
}),
|
|
814
848
|
React.createElement('label', { className: 'bib-set-well' },
|
|
815
849
|
React.createElement('input', {
|
|
816
850
|
type: 'color',
|
|
817
|
-
'aria-label': field.label
|
|
818
|
-
title: '
|
|
851
|
+
'aria-label': t('ui.customColor', { label: t(field.label) }),
|
|
852
|
+
title: t('ui.customColorOpenColorPicker'),
|
|
819
853
|
value: wellValue,
|
|
820
854
|
onChange: function (event) {
|
|
821
855
|
const picked = event && event.target ? event.target.value : null;
|
|
@@ -825,7 +859,7 @@ function InfoBarSettingsSection(props) {
|
|
|
825
859
|
React.createElement('input', {
|
|
826
860
|
type: 'text',
|
|
827
861
|
className: 'bib-set-hex',
|
|
828
|
-
'aria-label': field.label
|
|
862
|
+
'aria-label': t('ui.hexColor', { label: t(field.label) }),
|
|
829
863
|
'aria-invalid': hexInvalid ? 'true' : 'false',
|
|
830
864
|
'data-invalid': hexInvalid ? 'true' : 'false',
|
|
831
865
|
placeholder: '#RRGGBB',
|
|
@@ -838,52 +872,54 @@ function InfoBarSettingsSection(props) {
|
|
|
838
872
|
}))));
|
|
839
873
|
}
|
|
840
874
|
colorChildren.push(React.createElement('div', { key: 'c-' + group },
|
|
841
|
-
React.createElement('div', { className: 'bib-set-group-title' }, FIELD_GROUP_LABELS[group]
|
|
875
|
+
React.createElement('div', { className: 'bib-set-group-title' }, FIELD_GROUP_LABELS[group] ? t(FIELD_GROUP_LABELS[group]) : group),
|
|
842
876
|
rows));
|
|
843
877
|
}
|
|
844
878
|
|
|
845
879
|
const feedback = [];
|
|
846
|
-
if (opError) feedback.push(React.createElement('p', { key: 'err', className: 'bib-set-alert', role: 'alert' }, opError));
|
|
847
|
-
else if (notice) feedback.push(React.createElement('p', { key: 'notice', className: 'bib-set-notice', role: 'status' }, notice));
|
|
848
|
-
else if (saving) feedback.push(React.createElement('p', { key: 'saving', className: 'bib-set-notice', 'aria-live': 'polite' }, '
|
|
880
|
+
if (opError) feedback.push(React.createElement('p', { key: 'err', className: 'bib-set-alert', role: 'alert' }, opError.text()));
|
|
881
|
+
else if (notice) feedback.push(React.createElement('p', { key: 'notice', className: 'bib-set-notice', role: 'status' }, notice.text()));
|
|
882
|
+
else if (saving) feedback.push(React.createElement('p', { key: 'saving', className: 'bib-set-notice', 'aria-live': 'polite' }, t('ui.saving')));
|
|
849
883
|
|
|
850
884
|
return React.createElement('div', { className: 'bib-set-root bib-settings' },
|
|
851
885
|
bibSetPageTitle(),
|
|
852
886
|
React.createElement('p', { className: 'bib-set-intro' },
|
|
853
|
-
'
|
|
887
|
+
t('ui.chooseWhichFieldsToShow')),
|
|
854
888
|
React.createElement('section', { className: 'bib-set-card', 'aria-labelledby': 'bib-set-fields-title' },
|
|
855
889
|
React.createElement('div', { className: 'bib-set-card-header' },
|
|
856
|
-
React.createElement('h2', { id: 'bib-set-fields-title', className: 'bib-set-card-title' }, '
|
|
890
|
+
React.createElement('h2', { id: 'bib-set-fields-title', className: 'bib-set-card-title' }, t('ui.visibleFields')),
|
|
857
891
|
React.createElement('div', { className: 'bib-set-card-desc' },
|
|
858
|
-
'
|
|
892
|
+
t('ui.hiddenFieldsTakeNoSpace'))),
|
|
859
893
|
React.createElement('div', { className: 'bib-set-body' }, groupsChildren)),
|
|
860
894
|
React.createElement('section', { className: 'bib-set-card', 'aria-labelledby': 'bib-set-colors-title' },
|
|
861
895
|
React.createElement('div', { className: 'bib-set-card-header' },
|
|
862
|
-
React.createElement('h2', { id: 'bib-set-colors-title', className: 'bib-set-card-title' }, '
|
|
896
|
+
React.createElement('h2', { id: 'bib-set-colors-title', className: 'bib-set-card-title' }, t('ui.fieldColors')),
|
|
863
897
|
React.createElement('div', { className: 'bib-set-card-desc' },
|
|
864
|
-
'
|
|
898
|
+
t('ui.chooseAPresetUseThe'))),
|
|
865
899
|
React.createElement('div', { className: 'bib-set-body' }, colorChildren)),
|
|
866
900
|
React.createElement('div', { className: 'bib-set-footer' },
|
|
867
901
|
feedback,
|
|
868
902
|
React.createElement('button', {
|
|
869
903
|
type: 'button', className: 'bib-set-btn', disabled: saving,
|
|
870
904
|
onClick: function () { runReset('fields'); },
|
|
871
|
-
}, '
|
|
905
|
+
}, t('ui.resetLabels')),
|
|
872
906
|
React.createElement('button', {
|
|
873
907
|
type: 'button', className: 'bib-set-btn', disabled: saving,
|
|
874
908
|
onClick: function () { runReset('colors'); },
|
|
875
|
-
}, '
|
|
909
|
+
}, t('ui.resetColors'))));
|
|
876
910
|
} catch (err) {
|
|
877
911
|
// M2:渲染期异常绝不以白屏收场——改为页面内可见的红色错误框(role=alert,含错误信息)
|
|
878
912
|
return React.createElement('div', { className: 'bib-set-root bib-settings' },
|
|
879
913
|
bibSetPageTitle(),
|
|
880
|
-
React.createElement('p', { className: 'bib-set-alert', role: 'alert' }, '
|
|
914
|
+
React.createElement('p', { className: 'bib-set-alert', role: 'alert' }, t('ui.couldNotDisplayInfoBar', { value: bibSetOperationMessage(err) })));
|
|
881
915
|
}
|
|
882
916
|
}
|
|
883
917
|
|
|
884
918
|
module.exports = {
|
|
885
|
-
inject: ['slots'],
|
|
919
|
+
inject: ['slots', 'locale'],
|
|
886
920
|
async apply(ctx) {
|
|
921
|
+
ctx.effect(function () { return ctx.locale.register(LOCALE_NAMESPACE, LOCALES); }, 'info bar: dictionaries');
|
|
922
|
+
t = ctx.locale.bind(LOCALE_NAMESPACE);
|
|
887
923
|
// slots 服务可能晚于 apply 就绪:优先 ctx.slots(inject 注入属性),回退 ctx.get('slots');
|
|
888
924
|
// 仍不可用则轮询等待(最多 60×300ms ≈ 18s),绝不提前退出导致注册丢失
|
|
889
925
|
let slots = ctx.slots || ctx.get('slots');
|
|
@@ -915,7 +951,7 @@ module.exports = {
|
|
|
915
951
|
if (occupantDispose) { occupantDispose(); occupantDispose = null; }
|
|
916
952
|
occupantDispose = slots.register(
|
|
917
953
|
// 静态注册无动态沙箱的优先级自动分配:显式给低 priority(最低者渲染)以遮蔽原生 stats 栏(priority 0)
|
|
918
|
-
{ name: 'conversation.composer.dock', id: 'stats', priority: -1000 },
|
|
954
|
+
{ name: 'conversation.composer.dock', id: 'stats', priority: -1000, locale: LOCALE_NAMESPACE },
|
|
919
955
|
function (slotProps) {
|
|
920
956
|
return React.createElement(BottomInfoBar, Object.assign({}, slotProps, { density: density, onToggleDensity: onToggleDensity }));
|
|
921
957
|
}
|
|
@@ -964,7 +1000,7 @@ module.exports = {
|
|
|
964
1000
|
}, 'dsh-bottom-info-bar: settings styles');
|
|
965
1001
|
slots.inject('settings.section', function () {
|
|
966
1002
|
return slots.register(
|
|
967
|
-
{ name: 'settings.section', id: 'bottom-info-bar', order: 100, label: '
|
|
1003
|
+
{ name: 'settings.section', id: 'bottom-info-bar', order: 100, locale: LOCALE_NAMESPACE, label: function () { return t('ui.infoBar'); } },
|
|
968
1004
|
InfoBarSettingsSection);
|
|
969
1005
|
});
|
|
970
1006
|
|
|
@@ -1240,11 +1276,15 @@ module.exports = {
|
|
|
1240
1276
|
return Math.max(0, 100 - w.usedPercent);
|
|
1241
1277
|
}
|
|
1242
1278
|
// 订阅窗口紧凑行标签(5小时 → '5h',周 → '周',月 → '月');hover 明细仍用完整标签
|
|
1279
|
+
function quotaWindowLabel(window) {
|
|
1280
|
+
const keys = { five_hour: 'host.hour', seven_day: 'ui.weekly', monthly: 'ui.monthly' };
|
|
1281
|
+
return keys[window.key] ? t(keys[window.key]) : window.label;
|
|
1282
|
+
}
|
|
1243
1283
|
function compactWindowLabel(key) {
|
|
1244
1284
|
if (key === 'five_hour') return '5h';
|
|
1245
|
-
if (key === 'seven_day') return '
|
|
1246
|
-
if (key === 'monthly') return '
|
|
1247
|
-
return '
|
|
1285
|
+
if (key === 'seven_day') return t('ui.weekly');
|
|
1286
|
+
if (key === 'monthly') return t('ui.monthly');
|
|
1287
|
+
return t('ui.window');
|
|
1248
1288
|
}
|
|
1249
1289
|
|
|
1250
1290
|
// 数字统一加粗(仅数字本身)
|
|
@@ -1265,9 +1305,9 @@ module.exports = {
|
|
|
1265
1305
|
return React.createElement('span', { className: 'bi-model-capability-pending', 'aria-hidden': 'true' }, modelLabel);
|
|
1266
1306
|
}
|
|
1267
1307
|
if (!pr || pr.acceptsImageInput !== true) return React.createElement('span', { className: 'bi-model-name' }, modelLabel);
|
|
1268
|
-
return React.createElement('span', { className: 'bi-vision', title: '
|
|
1308
|
+
return React.createElement('span', { className: 'bi-vision', title: t('ui.supportsImageInput') },
|
|
1269
1309
|
React.createElement('span', { className: 'bi-vision-model' }, modelLabel),
|
|
1270
|
-
React.createElement('span', { className: 'bi-vision-kind' }, '
|
|
1310
|
+
React.createElement('span', { className: 'bi-vision-kind' }, t('ui.vision')));
|
|
1271
1311
|
}
|
|
1272
1312
|
|
|
1273
1313
|
function modelSeparator() {
|
|
@@ -1293,15 +1333,15 @@ module.exports = {
|
|
|
1293
1333
|
// 既保留服务商信息,也不让模型名重复前缀。
|
|
1294
1334
|
function providerGroup() {
|
|
1295
1335
|
const pr = visiblePricing;
|
|
1296
|
-
const provLabel = (pr && pr.providerDisplay) ? pr.providerDisplay : '
|
|
1336
|
+
const provLabel = (pr && pr.providerDisplay) ? pr.providerDisplay : t('ui.unknown');
|
|
1297
1337
|
const modelLabel = (pr && pr.modelDisplay) ? pr.modelDisplay
|
|
1298
|
-
: (pr && pr.model ? pr.model : '
|
|
1338
|
+
: (pr && pr.model ? pr.model : t('ui.unknownModel'));
|
|
1299
1339
|
const modelName = modelLabelWithoutProvider(modelLabel, provLabel);
|
|
1300
1340
|
const versionLine = updateInfo && typeof updateInfo.current === 'string'
|
|
1301
|
-
? '
|
|
1302
|
-
const provTitle = '
|
|
1303
|
-
+ (pr && pr.mode === 'peak-valley' ? '
|
|
1304
|
-
: (pr && pr.mode === 'flat' ? '
|
|
1341
|
+
? t('ui.pluginVersion', { current: updateInfo.current }) : '';
|
|
1342
|
+
const provTitle = t('ui.provider', { provLabel: provLabel, modelLabel: modelLabel })
|
|
1343
|
+
+ (pr && pr.mode === 'peak-valley' ? t('ui.pricingPeakOffPeakBeijing')
|
|
1344
|
+
: (pr && pr.mode === 'flat' ? t('ui.pricingFixed') : t('ui.pricingNotListedUsingDefaults')))
|
|
1305
1345
|
+ versionLine;
|
|
1306
1346
|
return React.createElement('span', { key: 'prov', className: 'bi-model-group', title: provTitle },
|
|
1307
1347
|
React.createElement('b', { className: 'bi-model-provider' }, provLabel),
|
|
@@ -1315,9 +1355,9 @@ module.exports = {
|
|
|
1315
1355
|
if (provider === 'chatgpt' || provider === 'openai-codex') return 'ChatGPT';
|
|
1316
1356
|
if (provider === 'codex') return 'Codex';
|
|
1317
1357
|
if (provider === 'opencode-go' || provider === 'opencode') return 'OpenCode Go';
|
|
1318
|
-
if (provider === 'zai' || provider === 'zai-coding-cn') return '
|
|
1319
|
-
if (provider === 'xiaomi-token-plan-cn' || provider === 'xiaomi-token-plan-sgp' || provider === 'xiaomi-token-plan-ams') return '
|
|
1320
|
-
return '
|
|
1358
|
+
if (provider === 'zai' || provider === 'zai-coding-cn') return t('ui.zhipu');
|
|
1359
|
+
if (provider === 'xiaomi-token-plan-cn' || provider === 'xiaomi-token-plan-sgp' || provider === 'xiaomi-token-plan-ams') return t('ui.xiaomiMiMo');
|
|
1360
|
+
return t('ui.subscription');
|
|
1321
1361
|
}
|
|
1322
1362
|
|
|
1323
1363
|
// 账单型服务名(v1.7:云账单 provider 显示品牌名,未知保持兜底)
|
|
@@ -1326,7 +1366,7 @@ module.exports = {
|
|
|
1326
1366
|
if (provider === 'fireworks') return 'Fireworks';
|
|
1327
1367
|
if (provider === 'amazon-bedrock') return 'AWS Bedrock';
|
|
1328
1368
|
if (provider === 'cloudflare-ai-gateway' || provider === 'cloudflare-workers-ai') return 'Cloudflare';
|
|
1329
|
-
return '
|
|
1369
|
+
return t('ui.cloudBilling');
|
|
1330
1370
|
}
|
|
1331
1371
|
|
|
1332
1372
|
// 套餐档位短名(JWT 订阅卡:plus/pro/team/enterprise → Plus/Pro/Team/Enterprise;未知返回 null 显示模型名)
|
|
@@ -1352,14 +1392,14 @@ module.exports = {
|
|
|
1352
1392
|
const subSnapshot = state.sub;
|
|
1353
1393
|
const planShort = subSnapshot && subSnapshot.planType ? subscriptionPlanShort(subSnapshot.planType) : null;
|
|
1354
1394
|
const rawModelLabel = (pr && pr.modelDisplay) ? pr.modelDisplay
|
|
1355
|
-
: (pr && pr.model ? pr.model : '
|
|
1395
|
+
: (pr && pr.model ? pr.model : t('ui.unknownModel'));
|
|
1356
1396
|
const modelLabel = planShort ? planShort : rawModelLabel;
|
|
1357
1397
|
const modelName = modelLabelWithoutProvider(modelLabel, serviceName);
|
|
1358
1398
|
const versionLine = updateInfo && typeof updateInfo.current === 'string'
|
|
1359
|
-
? '
|
|
1360
|
-
const planLine = subSnapshot && subSnapshot.plan ? '
|
|
1361
|
-
const expiryLine = subSnapshot && subSnapshot.expiryAt ? '
|
|
1362
|
-
const title = '
|
|
1399
|
+
? t('ui.pluginVersion', { current: updateInfo.current }) : '';
|
|
1400
|
+
const planLine = subSnapshot && subSnapshot.plan ? t('ui.plan', { plan: hostText(subSnapshot.plan) }) : '';
|
|
1401
|
+
const expiryLine = subSnapshot && subSnapshot.expiryAt ? t('ui.expiresLocalTime', { value: formatDate(subSnapshot.expiryAt) }) : '';
|
|
1402
|
+
const title = t('ui.subscriptionServiceModel', { serviceName: serviceName, rawModelLabel: rawModelLabel, planLine: planLine, expiryLine: expiryLine, versionLine: versionLine });
|
|
1363
1403
|
return React.createElement('span', { key: 'subprov', className: 'bi-model-group', title: title },
|
|
1364
1404
|
React.createElement('b', { className: 'bi-model-provider' }, serviceName),
|
|
1365
1405
|
modelDetail(pr, modelName),
|
|
@@ -1386,45 +1426,45 @@ module.exports = {
|
|
|
1386
1426
|
if (bal && bal.unmapped) {
|
|
1387
1427
|
if (fieldVisible('unmapped')) {
|
|
1388
1428
|
trailingErrorGroups.push(fieldSpan('unmapped', 'unmapped',
|
|
1389
|
-
React.createElement('span', { className: 'bi-muted', title: '
|
|
1429
|
+
React.createElement('span', { className: 'bi-muted', title: t('ui.balanceLookupIsNotYet') }, t('ui.notSupported'))));
|
|
1390
1430
|
}
|
|
1391
1431
|
}
|
|
1392
1432
|
// 余额(纯金额;hover 仅展示余额,不显示充值/赠金)
|
|
1393
1433
|
// v1.6 T7:未配置提示改为按账户显示凭据名(去掉写死的 DeepSeek 文案)
|
|
1394
1434
|
else if (bal && bal.error && bal.error.kind === 'no-key') {
|
|
1395
1435
|
if (fieldVisible('noKeyHint')) {
|
|
1396
|
-
const credName = bal.error.message ? String(bal.error.message).replace(
|
|
1436
|
+
const credName = bal.error.message ? String(hostText(bal.error.message)).replace(/(?:未配置 |Not configured: )/, '') : 'API_KEY';
|
|
1397
1437
|
trailingErrorGroups.push(fieldSpan('noKeyHint', 'nokey',
|
|
1398
|
-
React.createElement('span', { className: 'bi-err', title: '
|
|
1399
|
-
'
|
|
1438
|
+
React.createElement('span', { className: 'bi-err', title: t('ui.notConfiguredConfigureItIn', { credName: credName }) },
|
|
1439
|
+
t('ui.notConfiguredSettingsModels', { credName: credName }))));
|
|
1400
1440
|
}
|
|
1401
1441
|
} else if (bal && bal.data) {
|
|
1402
1442
|
const symbol = bal.currency === 'USD' ? '$' : '¥';
|
|
1403
1443
|
const balTitle = bal.estimate
|
|
1404
|
-
? '
|
|
1405
|
-
: '
|
|
1444
|
+
? t('ui.estimatedBalance', { symbol: symbol, value: fmt(bal.data.total) })
|
|
1445
|
+
: t('ui.balance', { symbol: symbol, value: fmt(bal.data.total) });
|
|
1406
1446
|
if (fieldVisible('balance')) {
|
|
1407
1447
|
groups.push(fieldSpan('balance', 'bal', React.createElement('span', { title: balTitle },
|
|
1408
|
-
metric('
|
|
1409
|
-
alertActive ? React.createElement('span', { className: 'bi-low-status' }, '
|
|
1410
|
-
bal.estimate ? React.createElement('span', { className: 'bi-muted' }, '
|
|
1448
|
+
metric(t('ui.balance.pushBalanceGroups'), symbol + fmt(bal.data.total), alertActive ? 'bi-alert-num' : ''),
|
|
1449
|
+
alertActive ? React.createElement('span', { className: 'bi-low-status' }, t('ui.low')) : null,
|
|
1450
|
+
bal.estimate ? React.createElement('span', { className: 'bi-muted' }, t('ui.estimated')) : null,
|
|
1411
1451
|
)));
|
|
1412
1452
|
}
|
|
1413
1453
|
// host 快照失败(bal.error)或本次 RPC 失败(errors.balance)→ 均保留旧数据 + 降级标记
|
|
1414
1454
|
if (fieldVisible('balanceError') && (bal.error || errors.balance)) {
|
|
1415
1455
|
trailingErrorGroups.push(fieldSpan('balanceError', 'balerr',
|
|
1416
|
-
React.createElement('span', { className: 'bi-stale', title: '
|
|
1456
|
+
React.createElement('span', { className: 'bi-stale', title: t('ui.balanceIsTemporarilyUnavailableShowing') }, t('ui.refreshFailed'))));
|
|
1417
1457
|
}
|
|
1418
1458
|
} else if (bal && bal.error) {
|
|
1419
1459
|
if (fieldVisible('balanceError')) {
|
|
1420
1460
|
trailingErrorGroups.push(fieldSpan('balanceError', 'berr',
|
|
1421
|
-
React.createElement('span', { className: 'bi-err', title: '
|
|
1461
|
+
React.createElement('span', { className: 'bi-err', title: t('ui.couldNotLoadBalanceCheck') }, t('ui.balanceUnavailable'))));
|
|
1422
1462
|
}
|
|
1423
1463
|
} else if (errors.balance) {
|
|
1424
1464
|
// 本次 RPC 失败且无旧数据:只降级余额块,其余端点数据照常渲染
|
|
1425
1465
|
if (fieldVisible('balanceError')) {
|
|
1426
1466
|
trailingErrorGroups.push(fieldSpan('balanceError', 'berr',
|
|
1427
|
-
React.createElement('span', { className: 'bi-err', title: '
|
|
1467
|
+
React.createElement('span', { className: 'bi-err', title: t('ui.couldNotLoadBalanceCheck') }, t('ui.balanceUnavailable'))));
|
|
1428
1468
|
}
|
|
1429
1469
|
}
|
|
1430
1470
|
|
|
@@ -1433,19 +1473,19 @@ module.exports = {
|
|
|
1433
1473
|
if (pr && pr.mode === 'peak-valley' && fieldVisible('period')) {
|
|
1434
1474
|
const peakNow = pr.period === 'peak';
|
|
1435
1475
|
const p = pr.prices || {};
|
|
1436
|
-
const periodTitle = '
|
|
1437
|
-
+ '
|
|
1438
|
-
+ '
|
|
1476
|
+
const periodTitle = t('ui.beijingTime') + (peakNow ? t('ui.peakPrice') : t('ui.offPeakPrice')) + t('ui.input') + (p.inputCacheMiss != null ? p.inputCacheMiss : '?')
|
|
1477
|
+
+ t('ui.mCachedInput') + (p.inputCacheHit != null ? p.inputCacheHit : '?')
|
|
1478
|
+
+ t('ui.mOutput') + (p.output != null ? p.output : '?') + '/M';
|
|
1439
1479
|
groups.push(fieldSpan('period', 'period', React.createElement('span', { className: peakNow ? 'bi-peak' : 'bi-offpeak', title: periodTitle },
|
|
1440
|
-
peakNow ? '
|
|
1480
|
+
peakNow ? t('ui.peakPrice') : t('ui.offPeakPrice'))));
|
|
1441
1481
|
}
|
|
1442
1482
|
|
|
1443
1483
|
// 倒计时:仅峰谷价服务商显示"距高峰/距空闲"(hover 展示下次切换时刻;数字加粗)
|
|
1444
1484
|
if (pr && pr.mode === 'peak-valley' && pr.nextSwitch && fieldVisible('countdown')) {
|
|
1445
1485
|
const peakNow = pr.period === 'peak';
|
|
1446
|
-
const countdownTitle = '
|
|
1486
|
+
const countdownTitle = t('ui.beijingTimeSwitchesTo', { atLabel: pr.nextSwitch.atLabel }) + (peakNow ? t('ui.offPeakPrice') : t('ui.peakPrice')) + t('ui.sentenceEnd');
|
|
1447
1487
|
groups.push(fieldSpan('countdown', 'countdown', React.createElement('span', { title: countdownTitle },
|
|
1448
|
-
metric('
|
|
1488
|
+
metric(t('ui.until') + (peakNow ? t('ui.offPeak') : t('ui.peak')), fmtCountdown(pr.nextSwitch.at - now)))));
|
|
1449
1489
|
}
|
|
1450
1490
|
|
|
1451
1491
|
// 本会话花费(公共小部件 pushSessionCost:只显示钱;hover 显示 今天/近一月/全部)
|
|
@@ -1468,17 +1508,17 @@ module.exports = {
|
|
|
1468
1508
|
const symbol = usdSymbol ? '$' : '¥';
|
|
1469
1509
|
const costTxt = costCNY != null ? '¥' + costCNY.toFixed(3)
|
|
1470
1510
|
: (costUSD != null ? '$' + costUSD.toFixed(3) : symbol + (0).toFixed(3));
|
|
1471
|
-
const today = usg.todaySpend != null ? '
|
|
1472
|
-
const month = usg.monthSpend != null ? '
|
|
1473
|
-
const total = usg.totalSpend != null ? '
|
|
1511
|
+
const today = usg.todaySpend != null ? t('ui.today', { symbol: symbol, value: fmt(usg.todaySpend, 3) }) : '';
|
|
1512
|
+
const month = usg.monthSpend != null ? t('ui.lastDays', { symbol: symbol, value: fmt(usg.monthSpend, 3) }) : '';
|
|
1513
|
+
const total = usg.totalSpend != null ? t('ui.allTime', { symbol: symbol, value: fmt(usg.totalSpend, 3) }) : '';
|
|
1474
1514
|
const detail = [today, month, total].filter(function (s) { return s.length > 0; }).join(' · ');
|
|
1475
1515
|
groups.push(fieldSpan('sessionCost', 'convo', React.createElement('span', {
|
|
1476
|
-
title: '
|
|
1477
|
-
metric('
|
|
1516
|
+
title: t('ui.sessionIncludingSubagents', { costTxt: costTxt, value: detail ? '\n' + detail : '' }) },
|
|
1517
|
+
metric(t('ui.session'), costTxt))));
|
|
1478
1518
|
}
|
|
1479
1519
|
} else if (errs.usage && fieldVisible('usageError')) {
|
|
1480
1520
|
trailingErrorGroups.push(fieldSpan('usageError', 'usageerr',
|
|
1481
|
-
React.createElement('span', { className: 'bi-err', title: '
|
|
1521
|
+
React.createElement('span', { className: 'bi-err', title: t('ui.spendIsTemporarilyUnavailableChat') }, t('ui.spendUnavailable'))));
|
|
1482
1522
|
}
|
|
1483
1523
|
}
|
|
1484
1524
|
|
|
@@ -1488,16 +1528,16 @@ module.exports = {
|
|
|
1488
1528
|
function subscriptionFailureHint(error, source) {
|
|
1489
1529
|
const kind = error && error.kind;
|
|
1490
1530
|
const serviceName = subscriptionServiceName(source);
|
|
1491
|
-
const message = error && typeof error.message === 'string' ? error.message : '';
|
|
1531
|
+
const message = error && typeof error.message === 'string' ? hostText(error.message) : '';
|
|
1492
1532
|
const statusMatch = message.match(/HTTP (\d{3})/);
|
|
1493
1533
|
const status = statusMatch ? statusMatch[1] : '';
|
|
1494
|
-
if (kind === 'no-key') return '
|
|
1495
|
-
if (kind === 'auth' || status === '401') return serviceName
|
|
1496
|
-
if (status === '403') return serviceName
|
|
1497
|
-
if (status === '429') return serviceName
|
|
1498
|
-
if (kind === 'timeout' || /timeout|timed out|abort/i.test(message)) return serviceName
|
|
1499
|
-
if (kind === 'parse') return serviceName
|
|
1500
|
-
return serviceName
|
|
1534
|
+
if (kind === 'no-key') return t('ui.noSignInCredentialsFound', { serviceName: serviceName });
|
|
1535
|
+
if (kind === 'auth' || status === '401') return t('ui.credentialsHaveExpiredPleaseReauthorize', { serviceName: serviceName });
|
|
1536
|
+
if (status === '403') return t('ui.deniedAccessReauthorizeOrTry', { serviceName: serviceName });
|
|
1537
|
+
if (status === '429') return t('ui.rateLimitReachedPleaseTry', { serviceName: serviceName });
|
|
1538
|
+
if (kind === 'timeout' || /timeout|timed out|abort/i.test(message)) return t('ui.timedOutCheckYourConnection', { serviceName: serviceName });
|
|
1539
|
+
if (kind === 'parse') return t('ui.returnedAnUnrecognizedResponsePlease', { serviceName: serviceName });
|
|
1540
|
+
return t('ui.isTemporarilyUnavailableCheckYour', { serviceName: serviceName });
|
|
1501
1541
|
}
|
|
1502
1542
|
|
|
1503
1543
|
function pushSubscriptionGroups(groups, trailingErrorGroups) {
|
|
@@ -1509,14 +1549,14 @@ module.exports = {
|
|
|
1509
1549
|
const errors = state.errors || {};
|
|
1510
1550
|
// v1.7 FR-8:JWT 订阅卡——真实套餐到期日(纯本地解码;无登录态/解析失败不显示此处)
|
|
1511
1551
|
if (sub && sub.planType && sub.expiryAt && fieldVisible('expiry')) {
|
|
1512
|
-
groups.push(fieldSpan('expiry', 'subexp', React.createElement('span', { title: '
|
|
1513
|
-
metric('
|
|
1552
|
+
groups.push(fieldSpan('expiry', 'subexp', React.createElement('span', { title: t('ui.subscriptionExpiresLocalTime', { value: formatDate(sub.expiryAt) }) },
|
|
1553
|
+
metric(t('ui.expires'), formatDate(sub.expiryAt)))));
|
|
1514
1554
|
}
|
|
1515
1555
|
if (!sub) {
|
|
1516
1556
|
if (errors.sub && fieldVisible('refreshFailure')) {
|
|
1517
1557
|
// 本次 RPC 失败且无旧数据:显示失败信息而非永久"加载中…"
|
|
1518
1558
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'suberr',
|
|
1519
|
-
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint({ kind: 'exception', message: String(errors.sub) }, visibleBillingMode && visibleBillingMode.provider) }, '
|
|
1559
|
+
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint({ kind: 'exception', message: String(errors.sub) }, visibleBillingMode && visibleBillingMode.provider) }, t('ui.refreshFailed'))));
|
|
1520
1560
|
}
|
|
1521
1561
|
return;
|
|
1522
1562
|
}
|
|
@@ -1532,7 +1572,7 @@ module.exports = {
|
|
|
1532
1572
|
if (sub.error && !hasData) {
|
|
1533
1573
|
if (fieldVisible('refreshFailure')) {
|
|
1534
1574
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'substale',
|
|
1535
|
-
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint(sub.error, sub.source || (visibleBillingMode && visibleBillingMode.provider)) }, '
|
|
1575
|
+
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint(sub.error, sub.source || (visibleBillingMode && visibleBillingMode.provider)) }, t('ui.refreshFailed'))));
|
|
1536
1576
|
}
|
|
1537
1577
|
return;
|
|
1538
1578
|
}
|
|
@@ -1541,17 +1581,17 @@ module.exports = {
|
|
|
1541
1581
|
if (!hasData && typeof sub.balance === 'number' && isFinite(sub.balance)) {
|
|
1542
1582
|
if (fieldVisible('subBalance')) {
|
|
1543
1583
|
const balTxt = '¥' + fmt(sub.balance, 2);
|
|
1544
|
-
const titleLines = ['
|
|
1545
|
-
'
|
|
1584
|
+
const titleLines = [t('ui.subscriptionSource', { value: subscriptionServiceName(visibleBillingMode && visibleBillingMode.provider) }) + (hostText(sub.plan) || t('ui.prepaidBalance')) + ')',
|
|
1585
|
+
t('ui.availableBalance', { balTxt: balTxt })];
|
|
1546
1586
|
groups.push(fieldSpan('subBalance', 'subbal', React.createElement('span', { title: titleLines.join('\n') },
|
|
1547
|
-
metric('
|
|
1587
|
+
metric(t('ui.balance.pushBalanceGroups'), balTxt))));
|
|
1548
1588
|
}
|
|
1549
1589
|
// 充值余额用户按量付费,花销与余额同等重要 → 追加公共花费块(含子代理聚合)
|
|
1550
1590
|
pushSessionCost(groups, trailingErrorGroups, false);
|
|
1551
1591
|
// host 快照失败(sub.error)或本次 RPC 失败(errors.sub)→ 保留旧数据 + 降级标记
|
|
1552
1592
|
if (fieldVisible('refreshFailure') && (sub.error || errors.sub)) {
|
|
1553
1593
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'substale',
|
|
1554
|
-
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint(sub.error || { kind: 'exception', message: String(errors.sub || '') }, sub.source || (visibleBillingMode && visibleBillingMode.provider)) }, '
|
|
1594
|
+
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint(sub.error || { kind: 'exception', message: String(errors.sub || '') }, sub.source || (visibleBillingMode && visibleBillingMode.provider)) }, t('ui.refreshFailed'))));
|
|
1555
1595
|
}
|
|
1556
1596
|
return;
|
|
1557
1597
|
}
|
|
@@ -1574,10 +1614,10 @@ module.exports = {
|
|
|
1574
1614
|
|
|
1575
1615
|
// 预警触发条件:已用 ≥80%(= 剩余 ≤20%)→ 鲜红色文字;正常额度使用中性文字。
|
|
1576
1616
|
const LOW_QUOTA_PERCENT = 20;
|
|
1577
|
-
const titleLines = ['
|
|
1617
|
+
const titleLines = [t('ui.subscriptionSource.titleLines', { value: subscriptionServiceName(visibleBillingMode && visibleBillingMode.provider) }) + (sub.plan ? ' (' + hostText(sub.plan) + ')' : '')]
|
|
1578
1618
|
.concat(windows.map(function (w) {
|
|
1579
|
-
return
|
|
1580
|
-
+ (w.resetsAt ? '
|
|
1619
|
+
return t('ui.windowRemainingUsed', { label: quotaWindowLabel(w), value: remainingPercent(w), usedPercent: w.usedPercent })
|
|
1620
|
+
+ (w.resetsAt ? t('ui.resetsResetsIn', { value: formatDateTime(w.resetsAt), value2: fmtResetCountdown(w.resetsAt - now) }) : '');
|
|
1581
1621
|
}));
|
|
1582
1622
|
const winNodes = [];
|
|
1583
1623
|
for (let i = 0; i < visible.length; i++) {
|
|
@@ -1588,7 +1628,7 @@ module.exports = {
|
|
|
1588
1628
|
// 每个窗口独立 data-field(subWindow5h/Week/Month),色变量按字段注入;「低」字标签保留
|
|
1589
1629
|
winNodes.push(fieldSpan(WINDOW_FIELD_IDS[w.key] || 'subWindow5h', 'w' + i,
|
|
1590
1630
|
metric(compactWindowLabel(w.key), remaining + '%', numberClass)));
|
|
1591
|
-
if (remaining <= LOW_QUOTA_PERCENT) winNodes.push(React.createElement('span', { key: 'low' + i, className: 'bi-low-status' }, '
|
|
1631
|
+
if (remaining <= LOW_QUOTA_PERCENT) winNodes.push(React.createElement('span', { key: 'low' + i, className: 'bi-low-status' }, t('ui.low')));
|
|
1592
1632
|
}
|
|
1593
1633
|
// 全部窗口被隐藏(或紧凑模式无候选)→ 整组不推送,分隔符由组装层正确收合
|
|
1594
1634
|
if (winNodes.length > 0) {
|
|
@@ -1597,14 +1637,13 @@ module.exports = {
|
|
|
1597
1637
|
// host 快照失败(sub.error)或本次 RPC 失败(errors.sub)→ 均保留旧数据 + 降级标记
|
|
1598
1638
|
if (fieldVisible('refreshFailure') && (sub.error || errors.sub)) {
|
|
1599
1639
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'substale',
|
|
1600
|
-
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint(sub.error || { kind: 'exception', message: String(errors.sub || '') }, sub.source || (visibleBillingMode && visibleBillingMode.provider)) }, '
|
|
1640
|
+
React.createElement('span', { className: 'bi-stale', title: subscriptionFailureHint(sub.error || { kind: 'exception', message: String(errors.sub || '') }, sub.source || (visibleBillingMode && visibleBillingMode.provider)) }, t('ui.refreshFailed'))));
|
|
1601
1641
|
}
|
|
1602
1642
|
// 距重置倒计时(与显示的窗口一致,确保额度与倒计时匹配)
|
|
1603
1643
|
if (displayWindow && displayWindow.resetsAt && fieldVisible('resetCountdown')) {
|
|
1604
|
-
const cdTitle =
|
|
1605
|
-
+ '%(已用 ' + displayWindow.usedPercent + '%) · 重置 ' + formatDateTime(displayWindow.resetsAt);
|
|
1644
|
+
const cdTitle = t('ui.windowRemainingUsedResets', { label: quotaWindowLabel(displayWindow), value: remainingPercent(displayWindow), usedPercent: displayWindow.usedPercent, value4: formatDateTime(displayWindow.resetsAt) });
|
|
1606
1645
|
groups.push(fieldSpan('resetCountdown', 'subcd', React.createElement('span', { title: cdTitle },
|
|
1607
|
-
metric('
|
|
1646
|
+
metric(t('ui.resetsIn'), fmtResetCountdown(displayWindow.resetsAt - now)))));
|
|
1608
1647
|
}
|
|
1609
1648
|
}
|
|
1610
1649
|
}
|
|
@@ -1613,14 +1652,14 @@ module.exports = {
|
|
|
1613
1652
|
// 账单服务+模型 → 本月真实花费 →(预算%)→(免费额度+重置倒计时);余额/额度/本会话花费均不显示 ----
|
|
1614
1653
|
function billingFailureHint(error, provider) {
|
|
1615
1654
|
const serviceName = billingServiceName(provider);
|
|
1616
|
-
const message = error && typeof error.message === 'string' ? error.message : '';
|
|
1655
|
+
const message = error && typeof error.message === 'string' ? hostText(error.message) : '';
|
|
1617
1656
|
const statusMatch = message.match(/HTTP (\d{3})/);
|
|
1618
1657
|
const status = statusMatch ? statusMatch[1] : '';
|
|
1619
|
-
if (error && error.kind === 'no-key') return '
|
|
1620
|
-
if (status === '403' || /缺少
|
|
1621
|
-
if (error && error.kind === 'parse') return serviceName
|
|
1622
|
-
if (/timeout|timed out|abort/i.test(message)) return serviceName
|
|
1623
|
-
return serviceName
|
|
1658
|
+
if (error && error.kind === 'no-key') return t('ui.notConfigured') + message.replace(/(?:未配置 |Not configured: )/, '') + t('ui.configureItInSettingsModels');
|
|
1659
|
+
if (status === '403' || /缺少 .*权限|lack .*permission/.test(message)) return t('ui.deniedAccessTheTokenMay', { serviceName: serviceName });
|
|
1660
|
+
if (error && error.kind === 'parse') return t('ui.returnedAnUnrecognizedResponsePlease', { serviceName: serviceName });
|
|
1661
|
+
if (/timeout|timed out|abort/i.test(message)) return t('ui.timedOutCheckYourConnection', { serviceName: serviceName });
|
|
1662
|
+
return t('ui.billingIsTemporarilyUnavailableCheck', { serviceName: serviceName });
|
|
1624
1663
|
}
|
|
1625
1664
|
|
|
1626
1665
|
function pushBillingGroups(groups, trailingErrorGroups) {
|
|
@@ -1633,7 +1672,7 @@ module.exports = {
|
|
|
1633
1672
|
if (!bill) {
|
|
1634
1673
|
if (errors.billing && fieldVisible('refreshFailure')) {
|
|
1635
1674
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'billerr',
|
|
1636
|
-
React.createElement('span', { className: 'bi-stale', title: billingFailureHint({ kind: 'exception', message: String(errors.billing) }, visibleBillingMode && visibleBillingMode.provider) }, '
|
|
1675
|
+
React.createElement('span', { className: 'bi-stale', title: billingFailureHint({ kind: 'exception', message: String(errors.billing) }, visibleBillingMode && visibleBillingMode.provider) }, t('ui.refreshFailed'))));
|
|
1637
1676
|
}
|
|
1638
1677
|
return;
|
|
1639
1678
|
}
|
|
@@ -1642,39 +1681,39 @@ module.exports = {
|
|
|
1642
1681
|
if (bill.error && !hasSpend) {
|
|
1643
1682
|
if (fieldVisible('refreshFailure')) {
|
|
1644
1683
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'billstale',
|
|
1645
|
-
React.createElement('span', { className: 'bi-stale', title: billingFailureHint(bill.error, bill.type || (visibleBillingMode && visibleBillingMode.provider)) }, '
|
|
1684
|
+
React.createElement('span', { className: 'bi-stale', title: billingFailureHint(bill.error, bill.type || (visibleBillingMode && visibleBillingMode.provider)) }, t('ui.refreshFailed'))));
|
|
1646
1685
|
}
|
|
1647
1686
|
return;
|
|
1648
1687
|
}
|
|
1649
1688
|
if (hasSpend) {
|
|
1650
1689
|
const symbol = d.currency === 'CNY' ? '¥' : '$';
|
|
1651
|
-
const titleLines = ['
|
|
1652
|
-
.concat(d.note ? [d.note] : [])
|
|
1653
|
-
.concat(d.currentPeriodSpend != null ? ['
|
|
1654
|
-
.concat(d.budgetPercent != null ? ['
|
|
1655
|
-
.concat(d.freeRemaining != null ? ['
|
|
1690
|
+
const titleLines = [t('ui.billingSource', { value: billingServiceName(visibleBillingMode && visibleBillingMode.provider) })]
|
|
1691
|
+
.concat(d.note ? [hostText(d.note)] : [])
|
|
1692
|
+
.concat(d.currentPeriodSpend != null ? [t('ui.thisMonthSSpend', { symbol: symbol, value: fmt(d.currentPeriodSpend, 2) })] : [])
|
|
1693
|
+
.concat(d.budgetPercent != null ? [t('ui.budgetUsed', { value: fmt(d.budgetPercent, 0) })] : [])
|
|
1694
|
+
.concat(d.freeRemaining != null ? [t('ui.dailyFreeQuotaRemaining', { value: fmt(d.freeRemaining, 0) })] : []);
|
|
1656
1695
|
// v1.9.0 PR2:本月/预算/免费额度三片段各自显隐,分隔符只在可见片段之间
|
|
1657
1696
|
const nodes = [];
|
|
1658
1697
|
if (d.currentPeriodSpend != null && fieldVisible('billingSpend')) {
|
|
1659
|
-
nodes.push(fieldSpan('billingSpend', 'billspend', metric('
|
|
1698
|
+
nodes.push(fieldSpan('billingSpend', 'billspend', metric(t('ui.thisMonth'), symbol + fmt(d.currentPeriodSpend, 2))));
|
|
1660
1699
|
} else if (d.usage != null && fieldVisible('billingSpend')) {
|
|
1661
|
-
nodes.push(fieldSpan('billingSpend', 'billspend', metric('
|
|
1700
|
+
nodes.push(fieldSpan('billingSpend', 'billspend', metric(t('ui.thisMonthSUsage'), fmt(d.usage, 2) + (d.usageUnit ? ' ' + d.usageUnit : ''))));
|
|
1662
1701
|
}
|
|
1663
1702
|
if (d.budgetPercent != null && fieldVisible('budget')) {
|
|
1664
1703
|
if (nodes.length > 0) nodes.push(' · ');
|
|
1665
|
-
nodes.push(fieldSpan('budget', 'billbudget', metric('
|
|
1704
|
+
nodes.push(fieldSpan('budget', 'billbudget', metric(t('ui.budget'), fmt(d.budgetPercent, 0) + '%')));
|
|
1666
1705
|
}
|
|
1667
1706
|
// 免费额度仅当接口显式给出(freeRemaining/resetsAt 同时存在)才显示,绝不编造
|
|
1668
1707
|
if (d.freeRemaining != null && d.resetsAt && fieldVisible('freeQuota')) {
|
|
1669
1708
|
if (nodes.length > 0) nodes.push(' · ');
|
|
1670
|
-
nodes.push(fieldSpan('freeQuota', 'billfree', metric('
|
|
1709
|
+
nodes.push(fieldSpan('freeQuota', 'billfree', metric(t('ui.free'), t('ui.resetsIn.pushBillingGroups', { value: fmt(d.freeRemaining, 0), value2: fmtResetCountdown(d.resetsAt - now) }))));
|
|
1671
1710
|
}
|
|
1672
1711
|
if (nodes.length > 0) {
|
|
1673
1712
|
groups.push(React.createElement('span', { key: 'bill', title: titleLines.join('\n') }, ...nodes));
|
|
1674
1713
|
}
|
|
1675
1714
|
if (fieldVisible('refreshFailure') && (bill.error || errors.billing)) {
|
|
1676
1715
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'billstale',
|
|
1677
|
-
React.createElement('span', { className: 'bi-stale', title: billingFailureHint(bill.error || { kind: 'exception', message: String(errors.billing || '') }, bill.type || (visibleBillingMode && visibleBillingMode.provider)) }, '
|
|
1716
|
+
React.createElement('span', { className: 'bi-stale', title: billingFailureHint(bill.error || { kind: 'exception', message: String(errors.billing || '') }, bill.type || (visibleBillingMode && visibleBillingMode.provider)) }, t('ui.refreshFailed'))));
|
|
1678
1717
|
}
|
|
1679
1718
|
}
|
|
1680
1719
|
}
|
|
@@ -1684,11 +1723,11 @@ module.exports = {
|
|
|
1684
1723
|
const pr = visiblePricing;
|
|
1685
1724
|
const serviceName = billingServiceName(visibleBillingMode && visibleBillingMode.provider);
|
|
1686
1725
|
const modelLabel = (pr && pr.modelDisplay) ? pr.modelDisplay
|
|
1687
|
-
: (pr && pr.model ? pr.model : '
|
|
1726
|
+
: (pr && pr.model ? pr.model : t('ui.unknownModel'));
|
|
1688
1727
|
const modelName = modelLabelWithoutProvider(modelLabel, serviceName);
|
|
1689
1728
|
const versionLine = updateInfo && typeof updateInfo.current === 'string'
|
|
1690
|
-
? '
|
|
1691
|
-
const title = '
|
|
1729
|
+
? t('ui.pluginVersion', { current: updateInfo.current }) : '';
|
|
1730
|
+
const title = t('ui.billingServiceModel', { serviceName: serviceName, modelLabel: modelLabel, versionLine: versionLine });
|
|
1692
1731
|
return React.createElement('span', { key: 'billprov', className: 'bi-model-group', title: title },
|
|
1693
1732
|
React.createElement('b', { className: 'bi-model-provider' }, serviceName),
|
|
1694
1733
|
modelDetail(pr, modelName),
|
|
@@ -1719,16 +1758,16 @@ module.exports = {
|
|
|
1719
1758
|
// 全局降级提示:任一端点失败 → 旧数据照常渲染 + 角落提示(title 列出失败项),仅失败项降级
|
|
1720
1759
|
const errors = state.errors || {};
|
|
1721
1760
|
const failedLabels = [];
|
|
1722
|
-
if (errors.balance) failedLabels.push('
|
|
1723
|
-
if (errors.pricing) failedLabels.push('
|
|
1724
|
-
if (errors.usage) failedLabels.push('
|
|
1725
|
-
if (errors.billingMode) failedLabels.push('
|
|
1726
|
-
if (errors.sub) failedLabels.push('
|
|
1727
|
-
if (errors.billing) failedLabels.push('
|
|
1761
|
+
if (errors.balance) failedLabels.push(t('ui.balance.pushBalanceGroups'));
|
|
1762
|
+
if (errors.pricing) failedLabels.push(t('ui.pricing'));
|
|
1763
|
+
if (errors.usage) failedLabels.push(t('ui.spend'));
|
|
1764
|
+
if (errors.billingMode) failedLabels.push(t('ui.mode'));
|
|
1765
|
+
if (errors.sub) failedLabels.push(t('ui.subscriptionQuota'));
|
|
1766
|
+
if (errors.billing) failedLabels.push(t('ui.billing'));
|
|
1728
1767
|
if (failedLabels.length > 0 && fieldVisible('refreshFailure')) {
|
|
1729
1768
|
trailingErrorGroups.push(fieldSpan('refreshFailure', 'degraded',
|
|
1730
1769
|
React.createElement('span', { className: 'bi-stale', key: 'degraded',
|
|
1731
|
-
title: failedLabels.join('
|
|
1770
|
+
title: failedLabels.join(t('ui.listSeparator')) + t('ui.temporarilyUnavailableKeepingTheLast') }, t('ui.refreshFailed'))));
|
|
1732
1771
|
}
|
|
1733
1772
|
const persistence = state.usage && state.usage.persistence;
|
|
1734
1773
|
if (persistence && persistence.state && persistence.state !== 'ok' && fieldVisible('persistWarning')) {
|
|
@@ -1736,15 +1775,15 @@ module.exports = {
|
|
|
1736
1775
|
trailingErrorGroups.push(fieldSpan('persistWarning', 'ledger-save', React.createElement('span', {
|
|
1737
1776
|
className: snapshotOnly ? 'bi-stale' : 'bi-err',
|
|
1738
1777
|
title: snapshotOnly
|
|
1739
|
-
? '
|
|
1740
|
-
: '
|
|
1741
|
-
}, snapshotOnly ? '
|
|
1778
|
+
? t('ui.spendJournalSavedButThe') + (hostText(persistence.message) || t('ui.unknownReason'))
|
|
1779
|
+
: t('ui.thisSpendRecordWasNot') + (hostText(persistence.message) || t('ui.unknownReason')),
|
|
1780
|
+
}, snapshotOnly ? t('ui.ledgerUpdatePending') : t('ui.spendNotSaved'))));
|
|
1742
1781
|
}
|
|
1743
1782
|
|
|
1744
1783
|
if (updateInfo && updateInfo.available === true && fieldVisible('updateNotice')) {
|
|
1745
1784
|
groups.push(fieldSpan('updateNotice', 'update', React.createElement('span', {
|
|
1746
|
-
className: 'bi-update', title: '
|
|
1747
|
-
}, '
|
|
1785
|
+
className: 'bi-update', title: t('ui.askYourAgentToUpdate', { latest: updateInfo.latest }),
|
|
1786
|
+
}, t('ui.updateAvailable'))));
|
|
1748
1787
|
}
|
|
1749
1788
|
|
|
1750
1789
|
// ---- 组装(分隔符收合与「刷新失败」去重见模块级 assembleInfoBarRow) ----
|
|
@@ -1777,28 +1816,28 @@ module.exports = {
|
|
|
1777
1816
|
// v1.9.0 PR2:原生统计行字段按设置过滤(隐藏组完全不进 ng,不占版式也不进 title)
|
|
1778
1817
|
if (fieldVisible('turnsSteps')) {
|
|
1779
1818
|
// 中文界面遵循数字与汉字混排留白:数值与量词视觉上分开,便于快速扫读。
|
|
1780
|
-
group([num(statsProj.turns
|
|
1819
|
+
group([num(t(statsProj.turns === 1 ? 'ui.turnCount' : 'ui.turnCountPlural', { count: statsProj.turns })), ' · ', num(t(statsProj.steps === 1 ? 'ui.stepCount' : 'ui.stepCountPlural', { count: statsProj.steps }))], false, 'turnsSteps');
|
|
1781
1820
|
}
|
|
1782
1821
|
|
|
1783
1822
|
const durations = [];
|
|
1784
1823
|
if (statsProj.llmMs > 0 && fieldVisible('llmTime')) durations.push(fieldSpan('llmTime', 'durl', metric('LLM', formatDuration(statsProj.llmMs))));
|
|
1785
1824
|
if (statsProj.toolMs > 0 && fieldVisible('toolTime')) {
|
|
1786
1825
|
if (durations.length > 0) durations.push(' · ');
|
|
1787
|
-
durations.push(fieldSpan('toolTime', 'durt', metric('
|
|
1826
|
+
durations.push(fieldSpan('toolTime', 'durt', metric(t('ui.tools'), formatDuration(statsProj.toolMs))));
|
|
1788
1827
|
}
|
|
1789
1828
|
if (durations.length > 0) group(durations);
|
|
1790
1829
|
|
|
1791
1830
|
const speeds = [];
|
|
1792
|
-
if (statsProj.ttftSteps > 0) speeds.push(metric('
|
|
1831
|
+
if (statsProj.ttftSteps > 0) speeds.push(metric(t('ui.avgTTFT'), formatDuration(statsProj.ttftMs / statsProj.ttftSteps)));
|
|
1793
1832
|
if (statsProj.decodeMs > 0) speeds.push(' · ', num(formatTps(statsProj.decodeTokens / (statsProj.decodeMs / 1e3)) + ' tok/s'));
|
|
1794
1833
|
if (speeds.length > 0) group(speeds, HIDE_SPEED_FIELDS); // 不占可见版式,title 浮窗保留(官方隐藏字段,非用户可配)
|
|
1795
1834
|
|
|
1796
1835
|
if (usageProj && (billedInput(usageProj) > 0 || (usageProj.outputTokens || 0) > 0)) {
|
|
1797
1836
|
const denom = billedInput(usageProj);
|
|
1798
1837
|
const hit = denom > 0 ? Math.round(((usageProj.cacheReadTokens || 0) / denom) * 100) : null;
|
|
1799
|
-
if (hit != null && fieldVisible('cacheHit')) group([metric('
|
|
1838
|
+
if (hit != null && fieldVisible('cacheHit')) group([metric(t('ui.cacheHit'), hit + '%')], false, 'cacheHit');
|
|
1800
1839
|
if (fieldVisible('tokensIO')) {
|
|
1801
|
-
group([metric('
|
|
1840
|
+
group([metric(t('ui.input.BottomInfoBar'), formatTokens(billedInput(usageProj)) + ' tok'), ' · ', metric(t('ui.output'), formatTokens(usageProj.outputTokens || 0) + ' tok')], false, 'tokensIO');
|
|
1802
1841
|
}
|
|
1803
1842
|
}
|
|
1804
1843
|
|
|
@@ -1848,10 +1887,10 @@ module.exports = {
|
|
|
1848
1887
|
'aria-disabled': isDensitySaving,
|
|
1849
1888
|
'data-density': displayDensity,
|
|
1850
1889
|
'data-density-saving': isDensitySaving,
|
|
1851
|
-
title: isDensitySaving ? '
|
|
1890
|
+
title: isDensitySaving ? t('ui.savingView') : t('ui.clickToSwitchFullCompact'),
|
|
1852
1891
|
}, animatedRow1, row2,
|
|
1853
1892
|
React.createElement('span', { id: 'dsh-bottom-info-bar-action', className: 'bi-sr-only' },
|
|
1854
|
-
full ? '
|
|
1893
|
+
full ? t('ui.pressEnterOrSpaceFor') : t('ui.pressEnterOrSpaceFor.BottomInfoBar')));
|
|
1855
1894
|
}
|
|
1856
1895
|
},
|
|
1857
1896
|
};
|