@kenz1117/dsh-ui-usage-billing 1.4.4 → 1.4.5

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/index.js CHANGED
@@ -216,6 +216,7 @@ function applyUserModelAliases(aliases) {
216
216
  */
217
217
  function applyLivePricing(pricing) {
218
218
  liveRate = typeof pricing.rate === "number" && Number.isFinite(pricing.rate) && pricing.rate > 0 ? pricing.rate : void 0;
219
+ typeof pricing.rateEur === "number" && Number.isFinite(pricing.rateEur) && pricing.rateEur > 0 && pricing.rateEur;
219
220
  livePrices = pricing.prices;
220
221
  liveExtraModels = pricing.extraModels;
221
222
  }
@@ -643,8 +644,8 @@ function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
643
644
  */
644
645
  function formatMoney(amount, currency = "cny") {
645
646
  const value = Number(amount);
646
- if (!Number.isFinite(value)) return currency === "cny" ? "¥0" : "$0";
647
- const symbol = currency === "cny" ? "¥" : "$";
647
+ const symbol = currency === "usd" ? "$" : currency === "eur" ? "€" : "¥";
648
+ if (!Number.isFinite(value)) return `${symbol}0`;
648
649
  if (value <= 0) return `${symbol}0`;
649
650
  if (value >= 1e3) return `${symbol}${value.toFixed(0)}`;
650
651
  if (value >= 10) return `${symbol}${value.toFixed(1)}`;
@@ -4620,6 +4621,40 @@ async function fetchJson(url) {
4620
4621
  }
4621
4622
  }
4622
4623
  /** Latest USD → CNY rate from the first working source, or undefined when none respond. */
4624
+ /**
4625
+ * EUR → CNY 汇率源。与 USD 同一套降级约定:腾讯行情优先(免 key、国内可达),
4626
+ * open.er-api.com 兑底;全部失败由调用方降级内置汇率,不阻塞其他价格拉取。
4627
+ */
4628
+ const EUR_RATE_SOURCES = [{
4629
+ url: "https://qt.gtimg.cn/q=whEURCNY",
4630
+ parse: (text) => {
4631
+ const price = /"([^"]*)"/.exec(text)?.[1]?.split("~")[3];
4632
+ return price !== void 0 && price !== "" ? Number(price) : void 0;
4633
+ }
4634
+ }, {
4635
+ url: "https://open.er-api.com/v6/latest/EUR",
4636
+ parse: (text) => {
4637
+ try {
4638
+ const cny = JSON.parse(text).rates?.CNY;
4639
+ return typeof cny === "number" && Number.isFinite(cny) && cny > 0 ? cny : void 0;
4640
+ } catch {
4641
+ return;
4642
+ }
4643
+ }
4644
+ }];
4645
+ /**
4646
+ * 按顺序尝试汇率源,返回第一个合法正数。
4647
+ * @param sources - 待尝试的汇率源。
4648
+ * @returns 汇率;全部失败时 undefined。
4649
+ */
4650
+ async function fetchRateFrom(sources) {
4651
+ for (const source of sources) {
4652
+ const text = await fetchText(source.url);
4653
+ if (text === null) continue;
4654
+ const value = source.parse(text);
4655
+ if (value !== void 0 && Number.isFinite(value) && value > 0) return value;
4656
+ }
4657
+ }
4623
4658
  async function fetchRate() {
4624
4659
  for (const source of RATE_SOURCES) {
4625
4660
  const text = await fetchText(source.url);
@@ -4820,17 +4855,19 @@ function buildExtraModels(data) {
4820
4855
  * @returns the live pricing snapshot (builtin when everything failed).
4821
4856
  */
4822
4857
  async function fetchLivePricing() {
4823
- const [rate, models, modelsDev] = await Promise.all([
4858
+ const [rate, rateEur, models, modelsDev] = await Promise.all([
4824
4859
  fetchRate(),
4860
+ fetchRateFrom(EUR_RATE_SOURCES),
4825
4861
  fetchRouterModels(),
4826
4862
  fetchJson(MODELS_DEV_URL)
4827
4863
  ]);
4828
4864
  const prices = models === void 0 ? void 0 : buildPrices(models);
4829
4865
  const extraModels = modelsDev === null ? void 0 : buildExtraModels(modelsDev);
4830
- if (rate === void 0 && prices === void 0 && (extraModels === void 0 || extraModels.length === 0)) return { source: "builtin" };
4866
+ if (rate === void 0 && rateEur === void 0 && prices === void 0 && (extraModels === void 0 || extraModels.length === 0)) return { source: "builtin" };
4831
4867
  return {
4832
4868
  source: "live",
4833
4869
  ...rate !== void 0 ? { rate } : {},
4870
+ ...rateEur !== void 0 ? { rateEur } : {},
4834
4871
  ...prices !== void 0 ? { prices } : {},
4835
4872
  ...extraModels !== void 0 && extraModels.length > 0 ? { extraModels } : {}
4836
4873
  };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 峰谷指示点状态(issue #65 / dots):把 `channelCountdown` 的结果映射成三种
3
+ * 展示状态。纯函数,便于无 DOM 单测;无峰谷的模型返回 null —— 不渲染任何东西,
4
+ * 绝不为「没有档位」的模型臆造一个状态。
5
+ */
6
+ import type { PriceTierId } from './pricing.ts';
7
+ /** 指示点状态:低谷 / 即将切换 / 高峰。 */
8
+ export type BandState = 'offPeak' | 'soon' | 'peak';
9
+ /**
10
+ * 由倒计时推导指示点状态。
11
+ * @param band - `channelCountdown` 的返回值;null/undefined = 该模型无峰谷。
12
+ * @param leadMs - 「即将切换」的提前量(复用峰谷提醒的 `leadMin`)。
13
+ * @returns 展示状态;无峰谷时为 null。
14
+ */
15
+ export declare function bandStateOf(band: {
16
+ tier: PriceTierId;
17
+ nextSwitchInMs: number;
18
+ } | null | undefined, leadMs: number): BandState | null;
19
+ //# sourceMappingURL=band-dot.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /** Locale dictionaries for the usage billing surface. */
2
- export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'trendEmpty' | 'trendTotal' | 'roundsUnit' | 'healthModelsLive' | 'healthVendorsDown' | 'healthVendorsUnavailable' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'directTag' | 'relayTag' | 'unknownTag' | 'planCountUnit' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'pricingSyncedAt' | 'pricingSyncNow' | 'pricingSyncing' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'peakAlertDescPeakZhipu' | 'peakAlertDescOffZhipu' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'tierAlertEnterPeakZhipu' | 'tierAlertEnterOffZhipu' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapHalf' | 'heatmapMonth' | 'metricCost' | 'metricTokens' | 'heatmapTotal' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown';
2
+ export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'pricingSearch' | 'bandPinHint' | 'trendEmpty' | 'trendTotal' | 'roundsUnit' | 'healthModelsLive' | 'healthVendorsDown' | 'healthVendorsUnavailable' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'directTag' | 'relayTag' | 'unknownTag' | 'planCountUnit' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'pricingSyncedAt' | 'pricingSyncNow' | 'pricingSyncing' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'currencyEur' | 'language' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'peakAlertDescPeakZhipu' | 'peakAlertDescOffZhipu' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'tierAlertEnterPeakZhipu' | 'tierAlertEnterOffZhipu' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapHalf' | 'heatmapMonth' | 'metricCost' | 'metricTokens' | 'heatmapTotal' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown';
3
3
  export declare const NS = "usageBilling";
4
4
  export declare const zh: Record<UsageBillingKey, string>;
5
5
  export declare const en: Record<UsageBillingKey, string>;
@@ -26,6 +26,8 @@ import type { LivePricing } from '../pricing-shared.ts';
26
26
  * live rate arrives the built-in value stays in force.
27
27
  */
28
28
  export declare const USD_TO_CNY = 6.79;
29
+ /** 内置 EUR→CNY 汇率(实时汇率不可用时的兑底,与 USD_TO_CNY 同口径)。 */
30
+ export declare const EUR_TO_CNY = 7.71;
29
31
  /**
30
32
  * 注入用户自定义模型别名(node 半区在插件启动时调用一次)。纯内存状态:
31
33
  * 聚合折叠与客户端渲染共用同一份(两侧一致性由同一注入点保证)。
@@ -137,6 +139,11 @@ export declare function getRateInfo(): {
137
139
  rate: number;
138
140
  live: boolean;
139
141
  };
142
+ /** 欧元汇率与其是否为实时值(与 {@link getRateInfo} 同形)。 */
143
+ export declare function getEurRateInfo(): {
144
+ rate: number;
145
+ live: boolean;
146
+ };
140
147
  /** Default share of traffic assumed to fall in the peak band (0..1). */
141
148
  export declare const DEFAULT_PEAK_SHARE = 0.5;
142
149
  /**
@@ -165,7 +172,7 @@ export declare const FLASH_REPRICE_MS: number;
165
172
  /** 计费时段档位:高峰 / 空闲(官方 DeepSeek 刊例价:高峰 = 空闲 × 2)。 */
166
173
  export type PriceTierId = 'peak' | 'offPeak';
167
174
  /** 成本显示币种:人民币(国内模型直价)/ 美元(国外模型直价或换算显示)。 */
168
- export type CostCurrency = 'cny' | 'usd';
175
+ export type CostCurrency = 'cny' | 'usd' | 'eur';
169
176
  /**
170
177
  * 工作日高峰时段判定(北京时间,UTC+8,无夏令时):09:00–12:00、14:00–18:00。
171
178
  * 周末(周六/周日)北京全天为低谷,不调用本函数判定峰/平。
@@ -436,6 +443,14 @@ export declare function computeCostAt(entry: ModelEntry, buckets: TokenUsageBuck
436
443
  /** 人民币 → 美元(显示换算用):用当前生效汇率(实时优先,缺失回退内置),
437
444
  * 与计价链路的 `currentRate()` 同口径,避免实时汇率生效时 USD 显示与计价不一致。 */
438
445
  export declare function cnyToUsd(cny: number): number;
446
+ /**
447
+ * 人民币金额换算到展示币种。目录以 CNY/USD 为原生币种,费用统一以
448
+ * 人民币累计,展示层再按所选币种换算——EUR 与 USD 同一口径,不改变存储与聚合。
449
+ * @param cny - 人民币金额。
450
+ * @param currency - 目标展示币种。
451
+ * @returns 换算后的金额(汇率不可用时原值返回)。
452
+ */
453
+ export declare function convertFromCny(cny: number, currency: CostCurrency): number;
439
454
  /**
440
455
  * Format an amount with adaptive precision and the given currency symbol.
441
456
  * @param amount - the amount (CNY by default; pass `usd` for dollar display).
@@ -446,7 +461,7 @@ export declare function formatMoney(amount: number, currency?: CostCurrency): st
446
461
  * Format a per-1M-token price in its native currency (free when the rate is
447
462
  * zero): CNY for domestic models, USD for overseas ones.
448
463
  */
449
- export declare function formatUnitPrice(price: number, currency?: 'CNY' | 'USD'): string;
464
+ export declare function formatUnitPrice(price: number, currency?: 'CNY' | 'USD' | 'EUR'): string;
450
465
  /**
451
466
  * 把一条「每百万 token」单价从原生币种换算到目标展示币种(按 USD→CNY 汇率)。
452
467
  * 汇率缺失/非法时回退原值,避免 0 汇率把价格算没。
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 费率表搜索:目录 + models.dev 补充条目约 3000 行,无过滤时只能靠滚动定位
3
+ * 单个模型(issue #66)。判定抽成纯函数,便于无 DOM 单测;匹配目录键、显示名
4
+ * 与厂商三个字段,大小写不敏感,空查询原样返回(默认视图与现状一致)。
5
+ */
6
+ /** 搜索可匹配的最小条目形状(ModelEntry 的子集,便于测试构造)。 */
7
+ export interface SearchableEntry {
8
+ key: string;
9
+ name?: string;
10
+ provider?: string;
11
+ }
12
+ /**
13
+ * 按查询过滤费率表行。
14
+ * @param entries - 待过滤的目录条目。
15
+ * @param query - 用户输入(首尾空白忽略)。
16
+ * @returns 命中的条目;查询为空/全空白时原样返回入参引用。
17
+ */
18
+ export declare function filterRateRows<T extends SearchableEntry>(entries: readonly T[], query: string): readonly T[];
19
+ //# sourceMappingURL=rate-search.d.ts.map
@@ -121,6 +121,38 @@ export declare const DEFAULT_CURRENCY: CostCurrency;
121
121
  export declare function loadCurrency(): CostCurrency;
122
122
  /** 写入显示币种。失败静默(展示偏好非关键)。 */
123
123
  export declare function saveCurrency(currency: CostCurrency): void;
124
+ /**
125
+ * 被固定的模型(峰谷指示点):费率表里点击圆点固定,固定后在输入框旁常驻显示。
126
+ * 与币种偏好同一套做法:localStorage + CustomEvent,因为胶囊与弹窗分属两棵 React 树。
127
+ */
128
+ export declare const PINNED_MODELS_STORAGE_KEY = "dsh.ui-usage-billing.pinned";
129
+ /** 固定列表变更后派发的 CustomEvent 名。 */
130
+ export declare const PINNED_MODELS_EVENT = "dsh.ui-usage-billing.pinned-pref";
131
+ /** 读取固定模型列表(损坏/非数组/非字符串项一律丢弃)。 */
132
+ export declare function loadPinnedModels(): string[];
133
+ /** 写入固定模型列表。失败静默(展示偏好非关键)。 */
134
+ export declare function savePinnedModels(keys: readonly string[]): void;
135
+ /** 界面语言(与币种解耦后独立持久化)。 */
136
+ export type BillingLanguage = 'zh' | 'en';
137
+ /** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
138
+ export declare const LANGUAGE_STORAGE_KEY = "dsh.ui-usage-billing.language";
139
+ /** 语言切换后派发的 CustomEvent 名(另一棵树监听它即时重读)。 */
140
+ export declare const LANGUAGE_PREF_EVENT = "dsh.ui-usage-billing.language-pref";
141
+ /**
142
+ * 读取界面语言。**迁移关键**:历史版本的语言是从币种推导的(选 $ 就是英文),
143
+ * 因此当尚无语言偏好时,**从已存的币种播种一次**并写回;升级后没有任何人
144
+ * 的界面语言会静默改变,且播种只发生一次。
145
+ * @returns 界面语言。
146
+ */
147
+ export declare function loadLanguage(): BillingLanguage;
148
+ /** 写入界面语言。失败静默(展示偏好非关键)。 */
149
+ export declare function saveLanguage(language: BillingLanguage): void;
150
+ /**
151
+ * 切换某个模型的固定状态。
152
+ * @param key - 目录键。
153
+ * @returns 切换后的列表(已写入)。
154
+ */
155
+ export declare function togglePinnedModel(key: string): string[];
124
156
  /** 用户自定义单价(与 client/pricing.ts 的 `UserPriceEntry` 同形;type import,无运行时依赖)。 */
125
157
  export type StoredUserPrice = UserPriceEntry;
126
158
  /** 自定义价表:模型(+可选来源)→ 单价,条目列表(支持同名模型绑定不同中转站 origin)。 */
@@ -19,6 +19,8 @@ export interface LivePricing {
19
19
  source: 'live' | 'builtin';
20
20
  /** USD → CNY mid rate (present when the rate fetch succeeded). */
21
21
  rate?: number;
22
+ /** EUR → CNY mid rate (present when the euro rate fetch succeeded). */
23
+ rateEur?: number;
22
24
  /** Overrides keyed by built-in catalog key (present when router matches succeeded). */
23
25
  prices?: Record<string, LivePrice>;
24
26
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kenz1117/dsh-ui-usage-billing",
3
3
  "description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
4
- "version": "1.4.4",
4
+ "version": "1.4.5",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",