@kenz1117/dsh-ui-usage-billing 0.1.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 ADDED
@@ -0,0 +1,1746 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@kenz1117/dsh-ui-usage-billing",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
11
+ function r(e) {
12
+ var t, f, n = "";
13
+ if ("string" == typeof e || "number" == typeof e) n += e;
14
+ else if ("object" == typeof e) if (Array.isArray(e)) {
15
+ var o = e.length;
16
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
17
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
18
+ return n;
19
+ }
20
+ function clsx() {
21
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
22
+ return n;
23
+ }
24
+ //#endregion
25
+ //#region \0dsh-css:/Users/ken/deepseek-harness/packages/client/ui-usage-billing/src/client/UsageBilling.module.css.mjs
26
+ const css = ".VWh0dG_railButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:36px;height:36px;color:var(--dsw-static-blue-500);cursor:pointer;border-radius:10px;justify-content:center;align-items:center;padding:0;transition:background-color .14s,border-color .14s,color .14s;display:flex}.VWh0dG_railButton:hover,.VWh0dG_railButton:focus-visible{background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l3);color:var(--dsw-static-blue-400);outline:none}.VWh0dG_railButton svg{stroke-width:2px;width:18px;height:18px}.VWh0dG_trigger{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;border-radius:12px;align-items:center;gap:10px;padding:9px 12px;transition:border-color .14s,background-color .14s,transform .12s;display:flex}.VWh0dG_trigger:hover,.VWh0dG_trigger:focus-visible{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover);outline:none}.VWh0dG_trigger:active{transform:scale(.985)}.VWh0dG_triggerIcon{background:linear-gradient(135deg, var(--dsw-static-blue-500), var(--dsw-static-deepseek-500));width:30px;height:30px;color:var(--dsw-static-neutral-bluish-00);border-radius:9px;flex:none;justify-content:center;align-items:center;display:inline-flex}.VWh0dG_triggerIcon svg{stroke-width:2.2px;width:15px;height:15px}.VWh0dG_triggerText{flex-direction:column;gap:1px;min-width:0;display:flex}.VWh0dG_triggerLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:16px}.VWh0dG_triggerMeta{color:var(--dsw-alias-label-tertiary);white-space:nowrap;align-items:baseline;gap:6px;font-size:11px;line-height:15px;display:flex}.VWh0dG_triggerMeta strong{color:var(--dsw-static-blue-500);font-variant-numeric:tabular-nums;font-size:13px;font-weight:600}.VWh0dG_triggerMeta em{color:var(--dsw-alias-label-caption);font-style:normal}.VWh0dG_triggerChevron{width:14px;height:14px;color:var(--dsw-alias-label-caption);flex:none;margin-left:auto}.VWh0dG_dashboardModal{width:min(760px,100vw - 48px);max-height:min(760px,88vh)}.VWh0dG_dashboard{flex-direction:column;width:100%;max-height:min(760px,88vh);display:flex}.VWh0dG_dashboardHead{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:flex-start;gap:12px;padding:20px 24px 14px;display:flex}.VWh0dG_dashboardTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:17px;font-weight:600;line-height:24px}.VWh0dG_dashboardSubtitle{color:var(--dsw-alias-label-caption);margin:3px 0 0;font-size:12px;line-height:17px}.VWh0dG_closeButton{width:30px;height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;transition:background-color .14s;display:inline-flex}.VWh0dG_closeButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_closeButton svg{stroke-width:2px;width:16px;height:16px}.VWh0dG_dashboardBody{flex-direction:column;gap:14px;padding:16px 24px 24px;display:flex;overflow-y:auto}.VWh0dG_hero{background:linear-gradient(135deg, var(--dsw-static-blue-800) 0%, var(--dsw-static-deepseek-600) 55%, var(--dsw-static-deepseek-500) 100%);min-height:118px;color:var(--dsw-static-neutral-bluish-00);border-radius:16px;align-items:center;gap:24px;padding:18px 22px;display:flex;position:relative;overflow:hidden}.VWh0dG_hero:after{content:\"\";pointer-events:none;background:radial-gradient(circle,#ffffff29 0%,#fff0 70%);border-radius:50%;width:200px;height:200px;position:absolute;top:-60px;right:-40px}.VWh0dG_heroMain{flex-direction:column;flex:auto;justify-content:center;gap:3px;min-width:0;display:flex}.VWh0dG_heroLabel{opacity:.82;white-space:nowrap;font-size:12px;font-weight:500;line-height:17px}.VWh0dG_heroValue{letter-spacing:-.02em;font-variant-numeric:tabular-nums;white-space:nowrap;font-size:38px;font-weight:700;line-height:46px}.VWh0dG_heroMeta{opacity:.82;white-space:nowrap;align-items:center;gap:6px;font-size:12px;line-height:17px;display:flex}.VWh0dG_heroMeta em{background:#ffffff29;border-radius:6px;margin-left:2px;padding:1px 6px;font-style:normal}.VWh0dG_heroDivider{background:#ffffff38;flex:none;align-self:stretch;width:1px}.VWh0dG_heroSide{flex:none;align-items:center;min-width:140px;display:flex}.VWh0dG_heroSideItem{flex-direction:column;justify-content:center;gap:3px;display:flex}.VWh0dG_heroSideLabel{opacity:.82;white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSideValue{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:22px;font-weight:600;line-height:28px}.VWh0dG_delta{align-items:center;gap:2px;font-size:12px;font-weight:600;line-height:17px;display:inline-flex}.VWh0dG_deltaUp{color:#9ff0b8}.VWh0dG_deltaDown{color:#ffb4b4}.VWh0dG_kpiGrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;display:grid}.VWh0dG_kpiTile{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:14px;flex-direction:column;gap:3px;padding:14px 15px;transition:border-color .14s,background-color .14s;display:flex}.VWh0dG_kpiTile:hover{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_kpiLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:15px}.VWh0dG_kpiValue{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:21px;font-weight:600;line-height:27px}.VWh0dG_kpiGreen{color:var(--dsw-static-green-500)}.VWh0dG_kpiDetail{color:var(--dsw-alias-label-caption);white-space:nowrap;text-overflow:ellipsis;font-size:11px;line-height:15px;overflow:hidden}.VWh0dG_panel{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:10px;padding:16px 18px;display:flex}.VWh0dG_panelHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.VWh0dG_panelTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;font-weight:600;line-height:20px}.VWh0dG_panelHint{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:11px;line-height:16px}.VWh0dG_chartWrap{width:100%;position:relative}.VWh0dG_chartSvg{width:100%;height:auto;display:block}.VWh0dG_chartEmpty{height:140px;color:var(--dsw-alias-label-caption);justify-content:center;align-items:center;font-size:13px;display:flex}.VWh0dG_emptyRow{text-align:center;color:var(--dsw-alias-label-caption);padding:28px 0;font-size:13px}.VWh0dG_chartGrid{stroke:var(--dsw-alias-border-l1);stroke-width:1px}.VWh0dG_chartAxisLabel{fill:var(--dsw-alias-label-tertiary);font-size:10px}.VWh0dG_chartBar{fill:var(--dsw-static-blue-500);opacity:.35}.VWh0dG_chartBar:hover{opacity:.6}.VWh0dG_chartLine{stroke:var(--dsw-static-blue-500);stroke-linecap:round;stroke-linejoin:round}.VWh0dG_chartCrosshair{stroke:var(--dsw-alias-label-dimmed);stroke-width:1px;stroke-dasharray:3 3}.VWh0dG_chartDot{fill:var(--dsw-static-neutral-bluish-00);stroke:var(--dsw-static-blue-500);stroke-width:2.5px}.VWh0dG_chartTooltip{pointer-events:none;z-index:2;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2);white-space:nowrap;border-radius:10px;padding:8px 10px;position:absolute;transform:translate(-50%,calc(-100% - 10px))}.VWh0dG_chartTooltipDate{color:var(--dsw-alias-label-primary);margin-bottom:3px;font-size:11px;font-weight:600}.VWh0dG_chartTooltipRow{color:var(--dsw-alias-label-secondary);align-items:center;gap:5px;font-size:11px;line-height:17px;display:flex}.VWh0dG_chartTooltipRow strong{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-weight:600}.VWh0dG_chartLegendLine{background:var(--dsw-static-blue-500);border-radius:2px;width:10px;height:3px;display:inline-block}.VWh0dG_chartLegendBar{background:var(--dsw-static-blue-500);opacity:.5;border-radius:2px;width:7px;height:7px;display:inline-block}.VWh0dG_chartLegend{color:var(--dsw-alias-label-tertiary);justify-content:flex-end;gap:14px;margin-top:2px;font-size:11px;display:flex}.VWh0dG_chartLegend span{align-items:center;gap:5px;display:inline-flex}.VWh0dG_tableScroll{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;overflow:auto}.VWh0dG_modelTable,.VWh0dG_pricingTable{border-collapse:collapse;width:100%;font-size:12.5px}.VWh0dG_modelTable th,.VWh0dG_modelTable td,.VWh0dG_pricingTable th,.VWh0dG_pricingTable td{border-bottom:1px solid var(--dsw-alias-border-l1);text-align:left;white-space:nowrap;padding:9px 12px}.VWh0dG_modelTable tbody tr:last-child td,.VWh0dG_pricingTable tbody tr:last-child td{border-bottom:0}.VWh0dG_modelTable thead th,.VWh0dG_pricingTable thead th{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform);font-size:11px;font-weight:500}.VWh0dG_modelTable tbody tr,.VWh0dG_pricingTable tbody tr{transition:background-color .12s}.VWh0dG_modelTable tbody tr:hover,.VWh0dG_pricingTable tbody tr:hover{background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_numCol{text-align:right;font-variant-numeric:tabular-nums}.VWh0dG_costCol{color:var(--dsw-static-blue-500);font-weight:600}.VWh0dG_na{color:var(--dsw-alias-label-dimmed)}.VWh0dG_modelCell{align-items:center;gap:8px;display:inline-flex}.VWh0dG_modelDot{width:9px;height:9px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-bg-layer-1) 60%, transparent);border-radius:50%;flex:none}.VWh0dG_modelName{color:var(--dsw-alias-label-primary);font-weight:500;line-height:16px;display:block}.VWh0dG_modelProvider{color:var(--dsw-alias-label-caption);font-size:10.5px;line-height:14px;display:block}.VWh0dG_pricingToggle{cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;gap:10px;width:100%;padding:0;display:flex}.VWh0dG_pricingToggle:hover .VWh0dG_panelTitle,.VWh0dG_pricingToggle:focus-visible .VWh0dG_panelTitle{color:var(--dsw-static-blue-500)}.VWh0dG_pricingToggleText{align-items:baseline;gap:10px;min-width:0;display:flex}.VWh0dG_pricingChevron{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.VWh0dG_pricingChevronOpen{transform:rotate(180deg)}.VWh0dG_bandPrice{align-items:baseline;gap:6px;display:inline-flex}.VWh0dG_bandPriceOff{color:var(--dsw-alias-label-caption)}.VWh0dG_bandPriceOff:before{content:\"/\";color:var(--dsw-alias-label-dimmed);margin-right:4px}.VWh0dG_bandTag{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:1px;font-size:10.5px;line-height:14px;display:inline-flex}.VWh0dG_bandTagOff{color:var(--dsw-static-green-500);font-weight:600}.VWh0dG_flatTag{color:var(--dsw-alias-label-caption);font-size:10.5px;line-height:14px}.VWh0dG_healthDot{border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.VWh0dG_healthOk{background:var(--dsw-static-green-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-static-green-500) 18%, transparent)}.VWh0dG_healthBad{background:var(--dsw-static-red-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-static-red-500) 18%, transparent)}.VWh0dG_healthIdle{background:var(--dsw-static-neutral-bluish-400)}.VWh0dG_dashboardRight{align-items:center;gap:8px;min-width:0;display:flex}.VWh0dG_healthBadge{white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:3px 9px;font-size:11px;line-height:16px;display:inline-flex}.VWh0dG_healthBadgeOk{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 10%, transparent)}.VWh0dG_healthBadgeBad{color:var(--dsw-static-red-500);background:color-mix(in srgb, var(--dsw-static-red-500) 10%, transparent)}.VWh0dG_planTag{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 12%, transparent);border-radius:999px;align-items:center;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}@media (width<=640px){.VWh0dG_kpiGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.VWh0dG_hero{flex-direction:column;align-items:flex-start;gap:12px}.VWh0dG_heroMain{width:100%;min-width:0}.VWh0dG_heroDivider{align-self:stretch;width:100%;height:1px}.VWh0dG_heroSide{min-width:0}}";
27
+ const tagId = "@kenz1117/dsh-ui-usage-billing/UsageBilling.module.css";
28
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
+ const tag = document.createElement("style");
30
+ tag.dataset.plugin = "@kenz1117/dsh-ui-usage-billing";
31
+ tag.dataset.pluginCss = tagId;
32
+ tag.textContent = css;
33
+ document.head.appendChild(tag);
34
+ }
35
+ var UsageBilling_module_css_default = {
36
+ "delta": "VWh0dG_delta",
37
+ "modelCell": "VWh0dG_modelCell",
38
+ "healthBad": "VWh0dG_healthBad",
39
+ "dashboardModal": "VWh0dG_dashboardModal",
40
+ "numCol": "VWh0dG_numCol",
41
+ "healthOk": "VWh0dG_healthOk",
42
+ "chartAxisLabel": "VWh0dG_chartAxisLabel",
43
+ "triggerText": "VWh0dG_triggerText",
44
+ "panelTitle": "VWh0dG_panelTitle",
45
+ "triggerChevron": "VWh0dG_triggerChevron",
46
+ "triggerIcon": "VWh0dG_triggerIcon",
47
+ "chartCrosshair": "VWh0dG_chartCrosshair",
48
+ "chartLegendLine": "VWh0dG_chartLegendLine",
49
+ "kpiTile": "VWh0dG_kpiTile",
50
+ "heroSideValue": "VWh0dG_heroSideValue",
51
+ "pricingChevron": "VWh0dG_pricingChevron",
52
+ "deltaDown": "VWh0dG_deltaDown",
53
+ "healthDot": "VWh0dG_healthDot",
54
+ "kpiLabel": "VWh0dG_kpiLabel",
55
+ "kpiGreen": "VWh0dG_kpiGreen",
56
+ "tableScroll": "VWh0dG_tableScroll",
57
+ "panel": "VWh0dG_panel",
58
+ "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
59
+ "heroSideItem": "VWh0dG_heroSideItem",
60
+ "heroLabel": "VWh0dG_heroLabel",
61
+ "chartBar": "VWh0dG_chartBar",
62
+ "chartTooltipRow": "VWh0dG_chartTooltipRow",
63
+ "chartWrap": "VWh0dG_chartWrap",
64
+ "healthBadgeBad": "VWh0dG_healthBadgeBad",
65
+ "heroMain": "VWh0dG_heroMain",
66
+ "chartEmpty": "VWh0dG_chartEmpty",
67
+ "kpiGrid": "VWh0dG_kpiGrid",
68
+ "chartTooltip": "VWh0dG_chartTooltip",
69
+ "heroMeta": "VWh0dG_heroMeta",
70
+ "chartTooltipDate": "VWh0dG_chartTooltipDate",
71
+ "modelDot": "VWh0dG_modelDot",
72
+ "healthBadgeOk": "VWh0dG_healthBadgeOk",
73
+ "planTag": "VWh0dG_planTag",
74
+ "kpiValue": "VWh0dG_kpiValue",
75
+ "healthBadge": "VWh0dG_healthBadge",
76
+ "modelName": "VWh0dG_modelName",
77
+ "emptyRow": "VWh0dG_emptyRow",
78
+ "heroValue": "VWh0dG_heroValue",
79
+ "na": "VWh0dG_na",
80
+ "dashboardRight": "VWh0dG_dashboardRight",
81
+ "dashboardHead": "VWh0dG_dashboardHead",
82
+ "chartLine": "VWh0dG_chartLine",
83
+ "healthIdle": "VWh0dG_healthIdle",
84
+ "costCol": "VWh0dG_costCol",
85
+ "bandTag": "VWh0dG_bandTag",
86
+ "panelHead": "VWh0dG_panelHead",
87
+ "dashboardBody": "VWh0dG_dashboardBody",
88
+ "chartSvg": "VWh0dG_chartSvg",
89
+ "chartDot": "VWh0dG_chartDot",
90
+ "bandTagOff": "VWh0dG_bandTagOff",
91
+ "dashboardTitle": "VWh0dG_dashboardTitle",
92
+ "heroDivider": "VWh0dG_heroDivider",
93
+ "chartGrid": "VWh0dG_chartGrid",
94
+ "pricingToggleText": "VWh0dG_pricingToggleText",
95
+ "heroSide": "VWh0dG_heroSide",
96
+ "kpiDetail": "VWh0dG_kpiDetail",
97
+ "chartLegend": "VWh0dG_chartLegend",
98
+ "modelTable": "VWh0dG_modelTable",
99
+ "pricingTable": "VWh0dG_pricingTable",
100
+ "modelProvider": "VWh0dG_modelProvider",
101
+ "hero": "VWh0dG_hero",
102
+ "heroSideLabel": "VWh0dG_heroSideLabel",
103
+ "triggerMeta": "VWh0dG_triggerMeta",
104
+ "chartLegendBar": "VWh0dG_chartLegendBar",
105
+ "deltaUp": "VWh0dG_deltaUp",
106
+ "pricingToggle": "VWh0dG_pricingToggle",
107
+ "railButton": "VWh0dG_railButton",
108
+ "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
109
+ "bandPrice": "VWh0dG_bandPrice",
110
+ "triggerLabel": "VWh0dG_triggerLabel",
111
+ "panelHint": "VWh0dG_panelHint",
112
+ "closeButton": "VWh0dG_closeButton",
113
+ "bandPriceOff": "VWh0dG_bandPriceOff",
114
+ "flatTag": "VWh0dG_flatTag",
115
+ "dashboard": "VWh0dG_dashboard",
116
+ "trigger": "VWh0dG_trigger"
117
+ };
118
+ //#endregion
119
+ //#region src/client/pricing.ts
120
+ /**
121
+ * Billing engine: per-model price tables and token-usage cost estimation.
122
+ *
123
+ * Each model's price table uses its NATIVE currency: domestic providers
124
+ * (DeepSeek, 智谱, 通义…) publish RMB prices and store them directly;
125
+ * overseas providers (OpenAI, Google, xAI, Meta) publish USD.
126
+ * Cost is always computed and displayed in CNY — only USD-priced models go
127
+ * through the exchange rate, never domestic ones.
128
+ *
129
+ * Google-style two-band billing is modeled per model: Gemini's Flex tier
130
+ * prices spare-capacity traffic at -50%; DeepSeek V4 splits peak
131
+ * (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate. The estimator
132
+ * mixes both bands by a configured peak share ({@link DEFAULT_PEAK_SHARE}).
133
+ */
134
+ /**
135
+ * USD → CNY rate for display. Source: China Foreign Exchange Trade System
136
+ * mid-rate 6.7878 on 2026-08-14; rounded to 6.79. Only applies to overseas
137
+ * USD-priced models — domestic models never pass through this rate.
138
+ */
139
+ const USD_TO_CNY = 6.79;
140
+ /** Default share of traffic assumed to fall in the peak band (0..1). */
141
+ const DEFAULT_PEAK_SHARE = .5;
142
+ /**
143
+ * Model keys served through a subscription plan (e.g. a coding plan or topic
144
+ * plan) instead of metered per-token API billing. Usage through these routes
145
+ * costs no tokens: the estimator treats them as ¥0 and the billing table
146
+ * labels them 订阅包含. Add any model key your deployment serves through a
147
+ * plan here; leave empty when every route is pay-as-you-go.
148
+ */
149
+ const SUBSCRIPTION_PLAN_KEYS = [];
150
+ /** Whether one stats model key is billed through a subscription plan. */
151
+ function isSubscriptionPlan(key) {
152
+ return SUBSCRIPTION_PLAN_KEYS.includes(key);
153
+ }
154
+ /**
155
+ * Built-in catalog of current mainstream models as of 2026-08-16, priced from
156
+ * each provider's official price page. Domestic providers are OpenAI-API
157
+ * compatible and publish RMB prices directly; overseas providers publish USD
158
+ * and convert through the exchange rate at estimate time. Retired models
159
+ * (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
160
+ * absent, as are Anthropic Claude models (their native API is not
161
+ * OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
162
+ * keys match the harness stats file so real usage prices from the catalog;
163
+ * unknown keys fall back to `other`.
164
+ *
165
+ * Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
166
+ * splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
167
+ * from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
168
+ */
169
+ const MODEL_CATALOG = [
170
+ {
171
+ key: "flash",
172
+ name: "DeepSeek V4 Flash",
173
+ provider: "DeepSeek",
174
+ colorVar: "dsw-static-blue-500",
175
+ price: {
176
+ currency: "CNY",
177
+ input: 3,
178
+ cacheHit: .1,
179
+ output: 9,
180
+ offPeak: {
181
+ input: 1.5,
182
+ cacheHit: .05,
183
+ output: 4.5
184
+ }
185
+ },
186
+ peakHours: "09:00-12:00 / 14:00-18:00"
187
+ },
188
+ {
189
+ key: "pro",
190
+ name: "DeepSeek V4 Pro",
191
+ provider: "DeepSeek",
192
+ colorVar: "dsw-static-deepseek-500",
193
+ price: {
194
+ currency: "CNY",
195
+ input: 9,
196
+ cacheHit: .3,
197
+ output: 27,
198
+ offPeak: {
199
+ input: 4.5,
200
+ cacheHit: .15,
201
+ output: 13.5
202
+ }
203
+ },
204
+ peakHours: "09:00-12:00 / 14:00-18:00"
205
+ },
206
+ {
207
+ key: "glm",
208
+ name: "GLM-5.2",
209
+ provider: "智谱 AI",
210
+ colorVar: "dsw-static-blue-600",
211
+ price: {
212
+ currency: "CNY",
213
+ input: 8,
214
+ cacheHit: 2,
215
+ output: 28
216
+ }
217
+ },
218
+ {
219
+ key: "glm-5.3",
220
+ name: "GLM-5.3",
221
+ provider: "智谱 AI",
222
+ colorVar: "dsw-static-blue-500",
223
+ price: {
224
+ currency: "CNY",
225
+ input: 8,
226
+ cacheHit: 2,
227
+ output: 28
228
+ }
229
+ },
230
+ {
231
+ key: "glm-4.6",
232
+ name: "GLM-4.6",
233
+ provider: "智谱 AI",
234
+ colorVar: "dsw-static-blue-400",
235
+ price: {
236
+ currency: "CNY",
237
+ input: 4,
238
+ cacheHit: .8,
239
+ output: 16
240
+ }
241
+ },
242
+ {
243
+ key: "qwen-3.8-max",
244
+ name: "Qwen3.8 Max",
245
+ provider: "阿里通义",
246
+ colorVar: "dsw-static-blue-600",
247
+ price: {
248
+ currency: "CNY",
249
+ input: 13.58,
250
+ cacheHit: 1.36,
251
+ output: 40.74
252
+ }
253
+ },
254
+ {
255
+ key: "qwen-max",
256
+ name: "Qwen3.7-Max",
257
+ provider: "阿里通义",
258
+ colorVar: "dsw-static-blue-300",
259
+ price: {
260
+ currency: "CNY",
261
+ input: 6,
262
+ cacheHit: .6,
263
+ output: 18
264
+ }
265
+ },
266
+ {
267
+ key: "qwen-plus",
268
+ name: "Qwen3.5-Plus",
269
+ provider: "阿里通义",
270
+ colorVar: "dsw-static-blue-500",
271
+ price: {
272
+ currency: "CNY",
273
+ input: .8,
274
+ cacheHit: .08,
275
+ output: 4.8
276
+ }
277
+ },
278
+ {
279
+ key: "qwen-flash",
280
+ name: "Qwen3.5-Flash",
281
+ provider: "阿里通义",
282
+ colorVar: "dsw-static-blue-400",
283
+ price: {
284
+ currency: "CNY",
285
+ input: .2,
286
+ cacheHit: .02,
287
+ output: 2
288
+ }
289
+ },
290
+ {
291
+ key: "doubao",
292
+ name: "Doubao Seed-2.0 Pro",
293
+ provider: "字节豆包",
294
+ colorVar: "dsw-static-red-500",
295
+ price: {
296
+ currency: "CNY",
297
+ input: 3.2,
298
+ cacheHit: .64,
299
+ output: 16
300
+ }
301
+ },
302
+ {
303
+ key: "doubao-mini",
304
+ name: "Doubao Seed-2.0 Mini",
305
+ provider: "字节豆包",
306
+ colorVar: "dsw-static-red-300",
307
+ price: {
308
+ currency: "CNY",
309
+ input: .2,
310
+ cacheHit: .02,
311
+ output: 2
312
+ }
313
+ },
314
+ {
315
+ key: "doubao-1.6",
316
+ name: "Doubao Seed-1.6",
317
+ provider: "字节豆包",
318
+ colorVar: "dsw-static-red-400",
319
+ price: {
320
+ currency: "CNY",
321
+ input: .8,
322
+ cacheHit: 0,
323
+ output: 8
324
+ }
325
+ },
326
+ {
327
+ key: "kimi",
328
+ name: "Kimi K2.7 Code",
329
+ provider: "月之暗面",
330
+ colorVar: "dsw-static-neutral-bluish-700",
331
+ price: {
332
+ currency: "CNY",
333
+ input: 6.5,
334
+ cacheHit: 1.3,
335
+ output: 27
336
+ }
337
+ },
338
+ {
339
+ key: "kimi-k2.7-hs",
340
+ name: "Kimi K2.7 Code HighSpeed",
341
+ provider: "月之暗面",
342
+ colorVar: "dsw-static-neutral-bluish-600",
343
+ price: {
344
+ currency: "CNY",
345
+ input: 13,
346
+ cacheHit: 2.6,
347
+ output: 54
348
+ }
349
+ },
350
+ {
351
+ key: "kimi-k2.6",
352
+ name: "Kimi K2.6",
353
+ provider: "月之暗面",
354
+ colorVar: "dsw-static-neutral-bluish-500",
355
+ price: {
356
+ currency: "CNY",
357
+ input: 6.5,
358
+ cacheHit: 1.1,
359
+ output: 27
360
+ }
361
+ },
362
+ {
363
+ key: "kimi-k3",
364
+ name: "Kimi K3",
365
+ provider: "月之暗面",
366
+ colorVar: "dsw-static-neutral-bluish-500",
367
+ price: {
368
+ currency: "CNY",
369
+ input: 20,
370
+ cacheHit: 2,
371
+ output: 100
372
+ }
373
+ },
374
+ {
375
+ key: "minimax",
376
+ name: "MiniMax-M3",
377
+ provider: "MiniMax",
378
+ colorVar: "dsw-static-amber-500",
379
+ price: {
380
+ currency: "CNY",
381
+ input: 2.1,
382
+ cacheHit: .42,
383
+ output: 8.4
384
+ }
385
+ },
386
+ {
387
+ key: "ernie",
388
+ name: "ERNIE-5.1",
389
+ provider: "百度文心",
390
+ colorVar: "dsw-static-blue-300",
391
+ price: {
392
+ currency: "CNY",
393
+ input: 4,
394
+ cacheHit: .4,
395
+ output: 18
396
+ }
397
+ },
398
+ {
399
+ key: "hunyuan",
400
+ name: "混元 Hy3",
401
+ provider: "腾讯混元",
402
+ colorVar: "dsw-static-amber-400",
403
+ price: {
404
+ currency: "CNY",
405
+ input: 1,
406
+ cacheHit: .25,
407
+ output: 4
408
+ }
409
+ },
410
+ {
411
+ key: "hunyuan-t1",
412
+ name: "混元 T1",
413
+ provider: "腾讯混元",
414
+ colorVar: "dsw-static-amber-300",
415
+ price: {
416
+ currency: "CNY",
417
+ input: 1,
418
+ cacheHit: .1,
419
+ output: 4
420
+ }
421
+ },
422
+ {
423
+ key: "yi",
424
+ name: "Yi-Lightning",
425
+ provider: "零一万物",
426
+ colorVar: "dsw-static-green-500",
427
+ price: {
428
+ currency: "CNY",
429
+ input: .99,
430
+ cacheHit: .1,
431
+ output: .99
432
+ }
433
+ },
434
+ {
435
+ key: "step",
436
+ name: "Step 3.7 Flash",
437
+ provider: "阶跃星辰",
438
+ colorVar: "dsw-static-neutral-bluish-400",
439
+ price: {
440
+ currency: "CNY",
441
+ input: 1.35,
442
+ cacheHit: .27,
443
+ output: 8.1
444
+ }
445
+ },
446
+ {
447
+ key: "spark",
448
+ name: "Spark 4.0 Ultra",
449
+ provider: "科大讯飞",
450
+ colorVar: "dsw-static-green-400",
451
+ price: {
452
+ currency: "CNY",
453
+ input: 5,
454
+ cacheHit: .5,
455
+ output: 10
456
+ }
457
+ },
458
+ {
459
+ key: "sensenova",
460
+ name: "SenseNova 6.5",
461
+ provider: "商汤",
462
+ colorVar: "dsw-static-red-400",
463
+ price: {
464
+ currency: "CNY",
465
+ input: 4.5,
466
+ cacheHit: .45,
467
+ output: 9
468
+ }
469
+ },
470
+ {
471
+ key: "baichuan",
472
+ name: "Baichuan M3-Plus",
473
+ provider: "百川智能",
474
+ colorVar: "dsw-static-neutral-bluish-500",
475
+ price: {
476
+ currency: "CNY",
477
+ input: 5,
478
+ cacheHit: .5,
479
+ output: 9
480
+ }
481
+ },
482
+ {
483
+ key: "gpt-5.6-sol",
484
+ name: "GPT-5.6 Sol",
485
+ provider: "OpenAI",
486
+ colorVar: "dsw-static-green-500",
487
+ price: {
488
+ currency: "USD",
489
+ input: 5,
490
+ cacheHit: .5,
491
+ output: 30
492
+ }
493
+ },
494
+ {
495
+ key: "gpt-5.6-terra",
496
+ name: "GPT-5.6 Terra",
497
+ provider: "OpenAI",
498
+ colorVar: "dsw-static-green-400",
499
+ price: {
500
+ currency: "USD",
501
+ input: 2,
502
+ cacheHit: .2,
503
+ output: 12
504
+ }
505
+ },
506
+ {
507
+ key: "gpt-5.6-luna",
508
+ name: "GPT-5.6 Luna",
509
+ provider: "OpenAI",
510
+ colorVar: "dsw-static-green-500",
511
+ price: {
512
+ currency: "USD",
513
+ input: .2,
514
+ cacheHit: .02,
515
+ output: 1.2
516
+ }
517
+ },
518
+ {
519
+ key: "gemini-pro",
520
+ name: "Gemini 3.1 Pro",
521
+ provider: "Google",
522
+ colorVar: "dsw-static-blue-600",
523
+ price: {
524
+ currency: "USD",
525
+ input: 2,
526
+ cacheHit: .2,
527
+ output: 12,
528
+ offPeak: {
529
+ input: 1,
530
+ cacheHit: .1,
531
+ output: 6
532
+ }
533
+ },
534
+ peakHours: "Standard / Flex"
535
+ },
536
+ {
537
+ key: "gemini-flash",
538
+ name: "Gemini 3.6 Flash",
539
+ provider: "Google",
540
+ colorVar: "dsw-static-blue-400",
541
+ price: {
542
+ currency: "USD",
543
+ input: 1.5,
544
+ cacheHit: .15,
545
+ output: 7.5,
546
+ offPeak: {
547
+ input: .75,
548
+ cacheHit: .075,
549
+ output: 3.75
550
+ }
551
+ },
552
+ peakHours: "Standard / Flex"
553
+ },
554
+ {
555
+ key: "grok",
556
+ name: "Grok 4.6",
557
+ provider: "xAI",
558
+ colorVar: "dsw-static-neutral-bluish-700",
559
+ price: {
560
+ currency: "USD",
561
+ input: 2,
562
+ cacheHit: .5,
563
+ output: 6
564
+ }
565
+ },
566
+ {
567
+ key: "grok-4.3",
568
+ name: "Grok 4.3",
569
+ provider: "xAI",
570
+ colorVar: "dsw-static-neutral-bluish-500",
571
+ price: {
572
+ currency: "USD",
573
+ input: 1.25,
574
+ cacheHit: .2,
575
+ output: 2.5
576
+ }
577
+ },
578
+ {
579
+ key: "llama",
580
+ name: "Llama 4 Maverick",
581
+ provider: "Meta",
582
+ colorVar: "dsw-static-red-500",
583
+ price: {
584
+ currency: "USD",
585
+ input: .2,
586
+ cacheHit: .05,
587
+ output: .6
588
+ }
589
+ },
590
+ {
591
+ key: "llama-scout",
592
+ name: "Llama 4 Scout",
593
+ provider: "Meta",
594
+ colorVar: "dsw-static-red-400",
595
+ price: {
596
+ currency: "USD",
597
+ input: .1,
598
+ cacheHit: .025,
599
+ output: .3
600
+ }
601
+ },
602
+ {
603
+ key: "other",
604
+ name: "其他模型",
605
+ provider: "Custom",
606
+ colorVar: "dsw-static-neutral-bluish-500",
607
+ price: {
608
+ currency: "CNY",
609
+ input: .5,
610
+ cacheHit: .25,
611
+ cacheMiss: .5,
612
+ output: 1.5
613
+ }
614
+ }
615
+ ];
616
+ /** Lookup a model by its stats key; falls back to the generic `other` entry. */
617
+ function modelOf(key) {
618
+ return MODEL_CATALOG.find((entry) => entry.key === key) ?? MODEL_CATALOG[MODEL_CATALOG.length - 1];
619
+ }
620
+ /** Resolve a price-table row by its CSS variable name (theme token or fallback color). */
621
+ function resolveToken(name) {
622
+ const value = getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim();
623
+ return value !== "" ? value : "#8b95a3";
624
+ }
625
+ /**
626
+ * Price one band's token usage in CNY. The stats `input` field is the TOTAL
627
+ * prompt tokens (cacheHit + cacheMiss), so billing splits it: the cache-hit
628
+ * share prices at the hit rate and the remaining share at the miss rate.
629
+ * Providers that report only disjoint buckets carry `cacheMiss` explicitly;
630
+ * otherwise the miss share is derived as `input - cacheHit`. Only USD-priced
631
+ * bands go through the exchange rate.
632
+ */
633
+ function priceBandCost(band, buckets, currency) {
634
+ const miss = buckets.cacheMiss > 0 ? buckets.cacheMiss : Math.max(0, buckets.input - buckets.cacheHit);
635
+ const hit = Math.min(buckets.cacheHit, buckets.input);
636
+ const raw = (miss * (band.cacheMiss ?? band.input) + hit * band.cacheHit + buckets.output * band.output) / 1e6;
637
+ return currency === "USD" ? raw * USD_TO_CNY : raw;
638
+ }
639
+ /**
640
+ * Estimate the CNY cost of one model's token usage, mixing the peak and
641
+ * off-peak bands by the given peak share (flat-priced models cost the same in
642
+ * both bands).
643
+ * @param entry - the catalog entry whose prices apply.
644
+ * @param buckets - token usage counts.
645
+ * @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
646
+ * @returns the estimated cost in CNY.
647
+ */
648
+ function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE) {
649
+ if (isSubscriptionPlan(entry.key)) return 0;
650
+ const peak = priceBandCost(entry.price, buckets, entry.price.currency);
651
+ const off = entry.price.offPeak === void 0 ? peak : priceBandCost(entry.price.offPeak, buckets, entry.price.currency);
652
+ return peak * peakShare + off * (1 - peakShare);
653
+ }
654
+ /** Format a CNY amount with adaptive precision. */
655
+ function formatMoney(cny) {
656
+ if (cny >= 1e3) return `¥${cny.toFixed(0)}`;
657
+ if (cny >= 10) return `¥${cny.toFixed(1)}`;
658
+ if (cny >= .1) return `¥${cny.toFixed(2)}`;
659
+ return `¥${cny.toFixed(3)}`;
660
+ }
661
+ /**
662
+ * Format a per-1M-token price in its native currency (free when the rate is
663
+ * zero): CNY for domestic models, USD for overseas ones.
664
+ */
665
+ function formatUnitPrice(price, currency = "CNY") {
666
+ if (price === 0) return "免费";
667
+ if (currency === "USD") {
668
+ if (price >= 10) return `$${price.toFixed(1)}`;
669
+ return `$${price.toFixed(2)}`;
670
+ }
671
+ if (price >= 10) return `¥${price.toFixed(1)}`;
672
+ return `¥${price.toFixed(2)}`;
673
+ }
674
+ /** Format a large token count with B/M/K suffix. */
675
+ function formatTokens(value) {
676
+ if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
677
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
678
+ if (value >= 1e3) return `${(value / 1e3).toFixed(0)}K`;
679
+ return String(value);
680
+ }
681
+ /** Format a percentage. */
682
+ function formatPercent(value) {
683
+ return `${value.toFixed(1)}%`;
684
+ }
685
+ //#endregion
686
+ //#region src/client/TrendChart.tsx
687
+ /**
688
+ * TrendChart: dependency-free SVG combo chart of daily cost (area line) and
689
+ * daily call volume (bars) with a hover crosshair. No chart library — the
690
+ * surface stays self-contained and offline.
691
+ */
692
+ /** Fixed viewBox; the SVG scales to its container. */
693
+ const W = 680;
694
+ const H = 220;
695
+ const PAD = {
696
+ top: 18,
697
+ right: 16,
698
+ bottom: 26,
699
+ left: 46
700
+ };
701
+ /** Split a date into `M/D` for axis labels. */
702
+ function shortDate(iso) {
703
+ const [, month, day] = iso.split("-");
704
+ return `${Number(month)}/${Number(day)}`;
705
+ }
706
+ /** Build an SVG path string through points with a smooth monotone-ish curve. */
707
+ function linePath(points) {
708
+ if (points.length === 0) return "";
709
+ if (points.length === 1) return `M ${points[0].x} ${points[0].y}`;
710
+ let d = `M ${points[0].x} ${points[0].y}`;
711
+ for (let i = 1; i < points.length; i += 1) {
712
+ const prev = points[i - 1];
713
+ const curr = points[i];
714
+ const mx = (prev.x + curr.x) / 2;
715
+ d += ` C ${mx} ${prev.y}, ${mx} ${curr.y}, ${curr.x} ${curr.y}`;
716
+ }
717
+ return d;
718
+ }
719
+ /** Ticks every `step` items for sparse axis labels. */
720
+ function tickIndexes(length, step) {
721
+ const out = [];
722
+ for (let i = 0; i < length; i += step) out.push(i);
723
+ if (length > 0 && out[out.length - 1] !== length - 1) out.push(length - 1);
724
+ return out;
725
+ }
726
+ /**
727
+ * Render the daily trend chart.
728
+ * @param props.data - sorted daily rows (ascending date).
729
+ */
730
+ function TrendChart({ data }) {
731
+ const [hover, setHover] = (0, react.useState)(null);
732
+ const layout = (0, react.useMemo)(() => {
733
+ const n = data.length;
734
+ if (n === 0) return null;
735
+ const maxCost = Math.max(...data.map((d) => d.cost), 1e-4);
736
+ const maxCalls = Math.max(...data.map((d) => d.calls), 1);
737
+ const plotW = W - PAD.left - PAD.right;
738
+ const plotH = H - PAD.top - PAD.bottom;
739
+ const inner = (i) => {
740
+ if (n === 1) return PAD.left + plotW / 2;
741
+ return PAD.left + plotW * i / (n - 1);
742
+ };
743
+ const yCost = (value) => PAD.top + plotH - value / maxCost * plotH;
744
+ const yCalls = (value) => PAD.top + plotH - value / maxCalls * plotH;
745
+ const barW = Math.min(16, plotW / n * .5);
746
+ const line = linePath(data.map((d, i) => ({
747
+ x: inner(i),
748
+ y: yCost(d.cost)
749
+ })));
750
+ return {
751
+ n,
752
+ plotW,
753
+ plotH,
754
+ inner,
755
+ yCost,
756
+ yCalls,
757
+ barW,
758
+ line,
759
+ area: line.length === 0 ? "" : `${line} L ${inner(n - 1)} ${PAD.top + plotH} L ${inner(0)} ${PAD.top + plotH} Z`,
760
+ costTicks: [
761
+ 0,
762
+ .25,
763
+ .5,
764
+ .75,
765
+ 1
766
+ ].map((f) => maxCost * f).reverse(),
767
+ callTicks: [
768
+ 0,
769
+ .25,
770
+ .5,
771
+ .75,
772
+ 1
773
+ ].map((f) => maxCalls * f).reverse(),
774
+ maxCost,
775
+ maxCalls
776
+ };
777
+ }, [data]);
778
+ if (layout === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
779
+ className: UsageBilling_module_css_default.chartEmpty,
780
+ children: "暂无趋势数据"
781
+ });
782
+ const { n, plotW, inner, yCost, yCalls, barW, line, area, costTicks, callTicks } = layout;
783
+ const active = hover === null ? null : data[hover];
784
+ const indices = tickIndexes(n, Math.max(1, Math.ceil(n / 8)));
785
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
+ className: UsageBilling_module_css_default.chartWrap,
787
+ children: [
788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
789
+ viewBox: `0 0 ${W} ${H}`,
790
+ className: UsageBilling_module_css_default.chartSvg,
791
+ role: "img",
792
+ "aria-label": "Daily cost and call volume trend",
793
+ onMouseLeave: () => {
794
+ setHover(null);
795
+ },
796
+ onMouseMove: (e) => {
797
+ const rect = e.currentTarget.getBoundingClientRect();
798
+ const ratio = ((e.clientX - rect.left) / rect.width * W - PAD.left) / plotW;
799
+ const index = Math.round(ratio * (n - 1));
800
+ setHover(Math.min(Math.max(index, 0), n - 1));
801
+ },
802
+ children: [
803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
804
+ id: "billing-cost-fill",
805
+ x1: "0",
806
+ y1: "0",
807
+ x2: "0",
808
+ y2: "1",
809
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
810
+ offset: "0%",
811
+ stopColor: "var(--dsw-static-blue-500)",
812
+ stopOpacity: "0.28"
813
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
814
+ offset: "100%",
815
+ stopColor: "var(--dsw-static-blue-500)",
816
+ stopOpacity: "0.02"
817
+ })]
818
+ }) }),
819
+ costTicks.map((value, idx) => {
820
+ const y = yCost(value);
821
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
822
+ x1: PAD.left,
823
+ x2: W - PAD.right,
824
+ y1: y,
825
+ y2: y,
826
+ className: UsageBilling_module_css_default.chartGrid
827
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
828
+ x: PAD.left - 8,
829
+ y: y + 3,
830
+ textAnchor: "end",
831
+ className: UsageBilling_module_css_default.chartAxisLabel,
832
+ children: formatMoney(value)
833
+ })] }, `cost-${idx}`);
834
+ }),
835
+ callTicks.map((value, idx) => {
836
+ const y = yCalls(value);
837
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
838
+ x: W - PAD.right + 8,
839
+ y: y + 3,
840
+ className: UsageBilling_module_css_default.chartAxisLabel,
841
+ children: value >= 1e3 ? `${Math.round(value / 1e3)}k` : String(Math.round(value))
842
+ }, `calls-${idx}`);
843
+ }),
844
+ data.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
845
+ x: inner(i) - barW / 2,
846
+ y: yCalls(d.calls),
847
+ width: barW,
848
+ height: Math.max(1, PAD.top + layout.plotH - yCalls(d.calls)),
849
+ rx: 2,
850
+ className: UsageBilling_module_css_default.chartBar
851
+ }, d.date)),
852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
853
+ d: area,
854
+ fill: "url(#billing-cost-fill)"
855
+ }),
856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
857
+ d: line,
858
+ fill: "none",
859
+ className: UsageBilling_module_css_default.chartLine,
860
+ strokeWidth: 2
861
+ }),
862
+ indices.map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
863
+ x: inner(i),
864
+ y: H - 6,
865
+ textAnchor: "middle",
866
+ className: UsageBilling_module_css_default.chartAxisLabel,
867
+ children: shortDate(data[i].date)
868
+ }, data[i].date)),
869
+ active !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
870
+ x1: inner(hover),
871
+ x2: inner(hover),
872
+ y1: PAD.top,
873
+ y2: PAD.top + layout.plotH,
874
+ className: UsageBilling_module_css_default.chartCrosshair
875
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
876
+ cx: inner(hover),
877
+ cy: yCost(active.cost),
878
+ r: 4.5,
879
+ className: UsageBilling_module_css_default.chartDot
880
+ })] })
881
+ ]
882
+ }),
883
+ active !== null && hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
884
+ className: UsageBilling_module_css_default.chartTooltip,
885
+ style: {
886
+ left: `${inner(hover) / W * 100}%`,
887
+ top: `${yCost(active.cost) / H * 100}%`
888
+ },
889
+ children: [
890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
891
+ className: UsageBilling_module_css_default.chartTooltipDate,
892
+ children: active.date
893
+ }),
894
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
895
+ className: UsageBilling_module_css_default.chartTooltipRow,
896
+ children: [
897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendLine }),
898
+ "费用 ",
899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(active.cost) })
900
+ ]
901
+ }),
902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
903
+ className: UsageBilling_module_css_default.chartTooltipRow,
904
+ children: [
905
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendBar }),
906
+ "调用 ",
907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: active.calls.toLocaleString() })
908
+ ]
909
+ })
910
+ ]
911
+ }),
912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
913
+ className: UsageBilling_module_css_default.chartLegend,
914
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendLine }), "每日费用"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendBar }), "调用次数"] })]
915
+ })
916
+ ]
917
+ });
918
+ }
919
+ //#endregion
920
+ //#region src/client/UsageBilling.tsx
921
+ /**
922
+ * UsageBilling: sidebar footer trigger + full billing dashboard modal.
923
+ *
924
+ * The trigger sits above Settings in the sidebar footer (rail shows an icon,
925
+ * wide shows a pill with the running total). Clicking opens a centered modal
926
+ * dashboard: hero total, KPI tiles, a dependency-free SVG daily trend chart,
927
+ * a per-model billing table priced from the built-in catalog, and a pricing
928
+ * table. Data comes from the host's `/api/billing/usage-stats` endpoint;
929
+ * before real data arrives the dashboard shows an empty (zero) snapshot,
930
+ * never fabricated samples.
931
+ */
932
+ /** Idle health state before the probe settles. */
933
+ const IDLE_HEALTH = {
934
+ checked: false,
935
+ available: false,
936
+ providers: 0,
937
+ failures: 0,
938
+ okProviders: [],
939
+ badProviders: []
940
+ };
941
+ /**
942
+ * The dashboard's display names (中文厂商名) never equal the provider names a
943
+ * user actually configures (deepseek, zhipu, qwen…), so the dot match also
944
+ * accepts a bidirectional substring hit and a display-name alias list.
945
+ */
946
+ const PROVIDER_ALIASES = {
947
+ "DeepSeek": ["deepseek"],
948
+ "智谱 AI": [
949
+ "zhipu",
950
+ "glm",
951
+ "z.ai"
952
+ ],
953
+ "阿里通义": [
954
+ "qwen",
955
+ "tongyi",
956
+ "dashscope",
957
+ "aliyun"
958
+ ],
959
+ "字节豆包": [
960
+ "doubao",
961
+ "volcengine",
962
+ "ark"
963
+ ],
964
+ "月之暗面": ["moonshot", "kimi"],
965
+ "MiniMax": ["minimax"],
966
+ "百度文心": [
967
+ "ernie",
968
+ "wenxin",
969
+ "qianfan",
970
+ "baidu"
971
+ ],
972
+ "腾讯混元": ["hunyuan", "tencent"],
973
+ "零一万物": [
974
+ "01.ai",
975
+ "lingyi",
976
+ "yi"
977
+ ],
978
+ "阶跃星辰": [
979
+ "step",
980
+ "stepfun",
981
+ "step-3.7"
982
+ ],
983
+ "科大讯飞": [
984
+ "spark",
985
+ "xfyun",
986
+ "iflytek"
987
+ ],
988
+ "商汤": ["sensenova", "sensetime"],
989
+ "百川智能": ["baichuan"],
990
+ "OpenAI": ["openai"],
991
+ "Google": ["google", "gemini"],
992
+ "xAI": ["xai", "grok"],
993
+ "Meta": ["meta", "llama"]
994
+ };
995
+ /** Normalize a provider name for dot matching: lower case, no spaces. */
996
+ function normalizeProvider(name) {
997
+ return name.trim().toLowerCase().replace(/[\s_/-]+/g, "");
998
+ }
999
+ /** Whether one normalized name is a substring of the other (length-guarded). */
1000
+ function providerNameHits(display, live) {
1001
+ if (display.length === 0 || live.length === 0) return false;
1002
+ if (display === live) return true;
1003
+ const [short, long] = display.length <= live.length ? [display, live] : [live, display];
1004
+ return short.length >= 3 && long.includes(short);
1005
+ }
1006
+ /** Whether a catalog display name matches one live provider name. */
1007
+ function providerMatches(display, live) {
1008
+ const displayKey = normalizeProvider(display);
1009
+ const liveKey = normalizeProvider(live);
1010
+ if (providerNameHits(displayKey, liveKey)) return true;
1011
+ const aliases = PROVIDER_ALIASES[display];
1012
+ return aliases !== void 0 && aliases.some((alias) => providerNameHits(normalizeProvider(alias), liveKey));
1013
+ }
1014
+ /** Resolve one provider's dot state: green when live, red when failed, gray when unknown. */
1015
+ function providerDot(health, provider) {
1016
+ if (!health.checked) return UsageBilling_module_css_default.healthIdle;
1017
+ if (health.okProviders.some((live) => providerMatches(provider, live))) return UsageBilling_module_css_default.healthOk;
1018
+ if (health.badProviders.some((live) => providerMatches(provider, live))) return UsageBilling_module_css_default.healthBad;
1019
+ return UsageBilling_module_css_default.healthIdle;
1020
+ }
1021
+ /** Path to the usage-stats endpoint served by this plugin's node half. */
1022
+ const USAGE_STATS_PATH = "/api/billing/usage-stats";
1023
+ /** Empty snapshot: shown before (or without) real host data — zeros, never fabricated samples. */
1024
+ const EMPTY_STATS = {
1025
+ total: {
1026
+ calls: 0,
1027
+ input: 0,
1028
+ output: 0,
1029
+ cacheHit: 0,
1030
+ cacheMiss: 0,
1031
+ cost: 0
1032
+ },
1033
+ byModel: {},
1034
+ byDay: {}
1035
+ };
1036
+ /** Try to load stats from the server; returns null when no valid JSON stats are served. */
1037
+ async function loadUsageStats() {
1038
+ try {
1039
+ const response = await fetch(USAGE_STATS_PATH);
1040
+ if (!response.ok) return null;
1041
+ const text = await response.text();
1042
+ const parsed = JSON.parse(text);
1043
+ if (parsed !== null && typeof parsed === "object" && "total" in parsed) return parsed;
1044
+ return null;
1045
+ } catch {
1046
+ return null;
1047
+ }
1048
+ }
1049
+ /**
1050
+ * Sidebar footer trigger: compact pill in wide mode, icon in rail mode.
1051
+ * @param props - framework props plus `wide` column state.
1052
+ */
1053
+ function UsageBillingTrigger(props) {
1054
+ const { wide, onOpen, totalCost, todayCost } = props;
1055
+ if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1056
+ type: "button",
1057
+ className: UsageBilling_module_css_default.railButton,
1058
+ onClick: onOpen,
1059
+ title: formatMoney(totalCost),
1060
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1061
+ viewBox: "0 0 24 24",
1062
+ fill: "none",
1063
+ stroke: "currentColor",
1064
+ "aria-hidden": "true",
1065
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" })
1066
+ })
1067
+ });
1068
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1069
+ type: "button",
1070
+ className: UsageBilling_module_css_default.trigger,
1071
+ onClick: onOpen,
1072
+ children: [
1073
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1074
+ className: UsageBilling_module_css_default.triggerIcon,
1075
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1076
+ viewBox: "0 0 24 24",
1077
+ fill: "none",
1078
+ stroke: "currentColor",
1079
+ "aria-hidden": "true",
1080
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" })
1081
+ })
1082
+ }),
1083
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1084
+ className: UsageBilling_module_css_default.triggerText,
1085
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1086
+ className: UsageBilling_module_css_default.triggerLabel,
1087
+ children: "计费仪表盘"
1088
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1089
+ className: UsageBilling_module_css_default.triggerMeta,
1090
+ children: [
1091
+ "总计 ",
1092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(totalCost) }),
1093
+ todayCost > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("em", { children: ["今日 ", formatMoney(todayCost)] })
1094
+ ]
1095
+ })]
1096
+ }),
1097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1098
+ className: UsageBilling_module_css_default.triggerChevron,
1099
+ viewBox: "0 0 24 24",
1100
+ fill: "none",
1101
+ stroke: "currentColor",
1102
+ "aria-hidden": "true",
1103
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m9 6 6 6-6 6" })
1104
+ })
1105
+ ]
1106
+ });
1107
+ }
1108
+ /**
1109
+ * The centered billing dashboard modal.
1110
+ * @param props - stats, locale function, close handler, and model health.
1111
+ */
1112
+ function BillingDashboard({ stats, t, onClose, health }) {
1113
+ const { total, byModel, byDay } = stats;
1114
+ const [pricingOpen, setPricingOpen] = (0, react.useState)(false);
1115
+ const cacheHitRate = total.cacheHit + total.cacheMiss > 0 ? total.cacheHit / (total.cacheHit + total.cacheMiss) * 100 : 0;
1116
+ const dates = Object.keys(byDay).sort();
1117
+ const latestDate = dates.at(-1) ?? "";
1118
+ const todayCost = byDay[(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)]?.cost ?? 0;
1119
+ const trend = (0, react.useMemo)(() => dates.map((date) => ({
1120
+ date,
1121
+ cost: byDay[date].cost,
1122
+ calls: byDay[date].calls
1123
+ })), [dates, byDay]);
1124
+ const modelRows = (0, react.useMemo)(() => Object.entries(byModel).filter(([, data]) => data.calls > 0).map(([key, data]) => {
1125
+ const entry = modelOf(key);
1126
+ const buckets = {
1127
+ input: data.input,
1128
+ cacheHit: data.cacheHit,
1129
+ cacheMiss: data.cacheMiss,
1130
+ output: data.output
1131
+ };
1132
+ return {
1133
+ key,
1134
+ name: entry.name,
1135
+ provider: entry.provider,
1136
+ color: resolveToken(entry.colorVar),
1137
+ calls: data.calls,
1138
+ input: data.input,
1139
+ output: data.output,
1140
+ cacheHitRate: data.cacheHit + data.cacheMiss > 0 ? data.cacheHit / (data.cacheHit + data.cacheMiss) * 100 : 0,
1141
+ estimated: computeCost(entry, buckets),
1142
+ plan: isSubscriptionPlan(key),
1143
+ ...data.cost > 0 ? { actual: data.cost } : {}
1144
+ };
1145
+ }).sort((a, b) => (b.actual ?? b.estimated) - (a.actual ?? a.estimated)), [byModel]);
1146
+ const estimatedTotal = modelRows.reduce((sum, row) => sum + row.estimated, 0);
1147
+ const displayTotal = total.cost > 0 ? total.cost : estimatedTotal;
1148
+ const avgPerCall = total.calls > 0 ? displayTotal / total.calls : 0;
1149
+ const prevDayCost = trend.length >= 2 ? trend[trend.length - 2].cost : 0;
1150
+ const deltaPct = prevDayCost > 0 ? (todayCost - prevDayCost) / prevDayCost * 100 : 0;
1151
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1152
+ open: true,
1153
+ onClose,
1154
+ title: t("billing.title"),
1155
+ headless: true,
1156
+ className: UsageBilling_module_css_default.dashboardModal ?? "",
1157
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1158
+ className: UsageBilling_module_css_default.dashboard,
1159
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1160
+ className: UsageBilling_module_css_default.dashboardHead,
1161
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1162
+ className: UsageBilling_module_css_default.dashboardTitle,
1163
+ children: t("billing.title")
1164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1165
+ className: UsageBilling_module_css_default.dashboardSubtitle,
1166
+ children: [
1167
+ t("billing.lastUpdated"),
1168
+ " ",
1169
+ latestDate
1170
+ ]
1171
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1172
+ className: UsageBilling_module_css_default.dashboardRight,
1173
+ children: [health.checked && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1174
+ className: clsx(UsageBilling_module_css_default.healthBadge, health.available ? UsageBilling_module_css_default.healthBadgeOk : UsageBilling_module_css_default.healthBadgeBad),
1175
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1176
+ className: clsx(UsageBilling_module_css_default.healthDot, health.available ? UsageBilling_module_css_default.healthOk : UsageBilling_module_css_default.healthBad),
1177
+ "aria-hidden": "true"
1178
+ }), health.available ? `${health.providers} 模型可用${health.failures > 0 ? ` · ${health.failures} 失效` : ""}` : `${health.failures} 模型不可用`]
1179
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1180
+ type: "button",
1181
+ className: UsageBilling_module_css_default.closeButton,
1182
+ "aria-label": t("billing.close"),
1183
+ onClick: onClose,
1184
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1185
+ viewBox: "0 0 24 24",
1186
+ fill: "none",
1187
+ stroke: "currentColor",
1188
+ "aria-hidden": "true",
1189
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })]
1190
+ })
1191
+ })]
1192
+ })]
1193
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1194
+ className: UsageBilling_module_css_default.dashboardBody,
1195
+ children: [
1196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1197
+ className: UsageBilling_module_css_default.hero,
1198
+ children: [
1199
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1200
+ className: UsageBilling_module_css_default.heroMain,
1201
+ children: [
1202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1203
+ className: UsageBilling_module_css_default.heroLabel,
1204
+ children: t("billing.totalCost")
1205
+ }),
1206
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1207
+ className: UsageBilling_module_css_default.heroValue,
1208
+ children: formatMoney(displayTotal)
1209
+ }),
1210
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1211
+ className: UsageBilling_module_css_default.heroMeta,
1212
+ children: [
1213
+ total.calls.toLocaleString(),
1214
+ " ",
1215
+ t("billing.calls"),
1216
+ total.cost > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", { children: "· 实际" })
1217
+ ]
1218
+ })
1219
+ ]
1220
+ }),
1221
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: UsageBilling_module_css_default.heroDivider }),
1222
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1223
+ className: UsageBilling_module_css_default.heroSide,
1224
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1225
+ className: UsageBilling_module_css_default.heroSideItem,
1226
+ children: [
1227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1228
+ className: UsageBilling_module_css_default.heroSideLabel,
1229
+ children: t("billing.todayCost")
1230
+ }),
1231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1232
+ className: UsageBilling_module_css_default.heroSideValue,
1233
+ children: formatMoney(todayCost)
1234
+ }),
1235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1236
+ className: clsx(UsageBilling_module_css_default.delta, deltaPct >= 0 ? UsageBilling_module_css_default.deltaUp : UsageBilling_module_css_default.deltaDown),
1237
+ children: [
1238
+ deltaPct >= 0 ? "▲" : "▼",
1239
+ " ",
1240
+ Math.abs(deltaPct).toFixed(1),
1241
+ "%"
1242
+ ]
1243
+ })
1244
+ ]
1245
+ })
1246
+ })
1247
+ ]
1248
+ }),
1249
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1250
+ className: UsageBilling_module_css_default.kpiGrid,
1251
+ children: [
1252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1253
+ className: UsageBilling_module_css_default.kpiTile,
1254
+ children: [
1255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1256
+ className: UsageBilling_module_css_default.kpiLabel,
1257
+ children: t("billing.cacheHitRate")
1258
+ }),
1259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1260
+ className: clsx(UsageBilling_module_css_default.kpiValue, UsageBilling_module_css_default.kpiGreen),
1261
+ children: formatPercent(cacheHitRate)
1262
+ }),
1263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1264
+ className: UsageBilling_module_css_default.kpiDetail,
1265
+ children: [
1266
+ formatTokens(total.cacheHit),
1267
+ " / ",
1268
+ formatTokens(total.cacheHit + total.cacheMiss)
1269
+ ]
1270
+ })
1271
+ ]
1272
+ }),
1273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1274
+ className: UsageBilling_module_css_default.kpiTile,
1275
+ children: [
1276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1277
+ className: UsageBilling_module_css_default.kpiLabel,
1278
+ children: t("billing.tokens")
1279
+ }),
1280
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1281
+ className: UsageBilling_module_css_default.kpiValue,
1282
+ children: formatTokens(total.input + total.output)
1283
+ }),
1284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1285
+ className: UsageBilling_module_css_default.kpiDetail,
1286
+ children: [
1287
+ t("billing.inputTokens"),
1288
+ " ",
1289
+ formatTokens(total.input),
1290
+ " · ",
1291
+ t("billing.outputTokens"),
1292
+ " ",
1293
+ formatTokens(total.output)
1294
+ ]
1295
+ })
1296
+ ]
1297
+ }),
1298
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1299
+ className: UsageBilling_module_css_default.kpiTile,
1300
+ children: [
1301
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1302
+ className: UsageBilling_module_css_default.kpiLabel,
1303
+ children: t("billing.avgCost")
1304
+ }),
1305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1306
+ className: UsageBilling_module_css_default.kpiValue,
1307
+ children: formatMoney(avgPerCall)
1308
+ }),
1309
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1310
+ className: UsageBilling_module_css_default.kpiDetail,
1311
+ children: [
1312
+ t("billing.calls"),
1313
+ " ",
1314
+ total.calls.toLocaleString()
1315
+ ]
1316
+ })
1317
+ ]
1318
+ }),
1319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1320
+ className: UsageBilling_module_css_default.kpiTile,
1321
+ children: [
1322
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1323
+ className: UsageBilling_module_css_default.kpiLabel,
1324
+ children: t("billing.calls")
1325
+ }),
1326
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1327
+ className: UsageBilling_module_css_default.kpiValue,
1328
+ children: total.calls.toLocaleString()
1329
+ }),
1330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1331
+ className: UsageBilling_module_css_default.kpiDetail,
1332
+ children: [
1333
+ modelRows.length,
1334
+ " ",
1335
+ t("billing.models")
1336
+ ]
1337
+ })
1338
+ ]
1339
+ })
1340
+ ]
1341
+ }),
1342
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1343
+ className: UsageBilling_module_css_default.panel,
1344
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1345
+ className: UsageBilling_module_css_default.panelHead,
1346
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1347
+ className: UsageBilling_module_css_default.panelTitle,
1348
+ children: t("billing.trend")
1349
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1350
+ className: UsageBilling_module_css_default.panelHint,
1351
+ children: latestDate
1352
+ })]
1353
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, { data: trend })]
1354
+ }),
1355
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1356
+ className: UsageBilling_module_css_default.panel,
1357
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1358
+ className: UsageBilling_module_css_default.panelHead,
1359
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1360
+ className: UsageBilling_module_css_default.panelTitle,
1361
+ children: t("billing.models")
1362
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1363
+ className: UsageBilling_module_css_default.panelHint,
1364
+ children: [
1365
+ t("billing.estimated"),
1366
+ " · ",
1367
+ t("billing.pricePerM")
1368
+ ]
1369
+ })]
1370
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1371
+ className: UsageBilling_module_css_default.tableScroll,
1372
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1373
+ className: UsageBilling_module_css_default.modelTable,
1374
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.models") }),
1376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1377
+ className: UsageBilling_module_css_default.numCol,
1378
+ children: t("billing.calls")
1379
+ }),
1380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1381
+ className: UsageBilling_module_css_default.numCol,
1382
+ children: t("billing.inputTokens")
1383
+ }),
1384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1385
+ className: UsageBilling_module_css_default.numCol,
1386
+ children: t("billing.outputTokens")
1387
+ }),
1388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1389
+ className: UsageBilling_module_css_default.numCol,
1390
+ children: t("billing.cacheHitRate")
1391
+ }),
1392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1393
+ className: UsageBilling_module_css_default.numCol,
1394
+ children: t("billing.estimated")
1395
+ }),
1396
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1397
+ className: UsageBilling_module_css_default.numCol,
1398
+ children: t("billing.actual")
1399
+ })
1400
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [modelRows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1401
+ colSpan: 7,
1402
+ className: UsageBilling_module_css_default.emptyRow,
1403
+ children: t("billing.noData")
1404
+ }) }), modelRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1405
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1406
+ className: UsageBilling_module_css_default.modelCell,
1407
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1408
+ className: clsx(UsageBilling_module_css_default.modelDot, providerDot(health, row.provider)),
1409
+ "aria-hidden": "true"
1410
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1411
+ className: UsageBilling_module_css_default.modelName,
1412
+ children: row.name
1413
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1414
+ className: UsageBilling_module_css_default.modelProvider,
1415
+ children: row.provider
1416
+ })] })]
1417
+ }) }),
1418
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1419
+ className: UsageBilling_module_css_default.numCol,
1420
+ children: row.calls.toLocaleString()
1421
+ }),
1422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1423
+ className: UsageBilling_module_css_default.numCol,
1424
+ children: formatTokens(row.input)
1425
+ }),
1426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1427
+ className: UsageBilling_module_css_default.numCol,
1428
+ children: formatTokens(row.output)
1429
+ }),
1430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1431
+ className: UsageBilling_module_css_default.numCol,
1432
+ children: formatPercent(row.cacheHitRate)
1433
+ }),
1434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1435
+ className: clsx(UsageBilling_module_css_default.numCol, UsageBilling_module_css_default.costCol),
1436
+ children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1437
+ className: UsageBilling_module_css_default.planTag,
1438
+ children: "订阅包含"
1439
+ }) : formatMoney(row.estimated)
1440
+ }),
1441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1442
+ className: UsageBilling_module_css_default.numCol,
1443
+ children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1444
+ className: UsageBilling_module_css_default.planTag,
1445
+ children: "订阅包含"
1446
+ }) : row.actual !== void 0 ? formatMoney(row.actual) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1447
+ className: UsageBilling_module_css_default.na,
1448
+ children: "—"
1449
+ })
1450
+ })
1451
+ ] }, row.key))] })]
1452
+ })
1453
+ })]
1454
+ }),
1455
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1456
+ className: UsageBilling_module_css_default.panel,
1457
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1458
+ type: "button",
1459
+ className: UsageBilling_module_css_default.pricingToggle,
1460
+ onClick: () => {
1461
+ setPricingOpen((prev) => !prev);
1462
+ },
1463
+ "aria-expanded": pricingOpen,
1464
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1465
+ className: UsageBilling_module_css_default.pricingToggleText,
1466
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1467
+ className: UsageBilling_module_css_default.panelTitle,
1468
+ children: t("billing.pricing")
1469
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1470
+ className: UsageBilling_module_css_default.panelHint,
1471
+ children: t("billing.pricePerM")
1472
+ })]
1473
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1474
+ className: clsx(UsageBilling_module_css_default.pricingChevron, pricingOpen && UsageBilling_module_css_default.pricingChevronOpen),
1475
+ viewBox: "0 0 24 24",
1476
+ fill: "none",
1477
+ stroke: "currentColor",
1478
+ "aria-hidden": "true",
1479
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
1480
+ })]
1481
+ }), pricingOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1482
+ className: UsageBilling_module_css_default.tableScroll,
1483
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1484
+ className: UsageBilling_module_css_default.pricingTable,
1485
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1486
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Model" }),
1487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1488
+ className: UsageBilling_module_css_default.numCol,
1489
+ children: t("billing.input")
1490
+ }),
1491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1492
+ className: UsageBilling_module_css_default.numCol,
1493
+ children: t("billing.cacheHit")
1494
+ }),
1495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1496
+ className: UsageBilling_module_css_default.numCol,
1497
+ children: t("billing.output")
1498
+ }),
1499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.band") })
1500
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: MODEL_CATALOG.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1502
+ className: UsageBilling_module_css_default.modelCell,
1503
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1504
+ className: UsageBilling_module_css_default.modelDot,
1505
+ style: { background: resolveToken(entry.colorVar) }
1506
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1507
+ className: UsageBilling_module_css_default.modelName,
1508
+ children: entry.name
1509
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1510
+ className: UsageBilling_module_css_default.modelProvider,
1511
+ children: entry.provider
1512
+ })] })]
1513
+ }) }),
1514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1515
+ className: UsageBilling_module_css_default.numCol,
1516
+ children: entry.price.offPeak !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1517
+ className: UsageBilling_module_css_default.bandPrice,
1518
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatUnitPrice(entry.price.input, entry.price.currency) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1519
+ className: UsageBilling_module_css_default.bandPriceOff,
1520
+ children: formatUnitPrice(entry.price.offPeak.input, entry.price.currency)
1521
+ })]
1522
+ }) : formatUnitPrice(entry.price.input, entry.price.currency)
1523
+ }),
1524
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1525
+ className: UsageBilling_module_css_default.numCol,
1526
+ children: entry.price.offPeak !== void 0 && entry.price.offPeak.cacheHit !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1527
+ className: UsageBilling_module_css_default.bandPrice,
1528
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatUnitPrice(entry.price.cacheHit, entry.price.currency) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1529
+ className: UsageBilling_module_css_default.bandPriceOff,
1530
+ children: formatUnitPrice(entry.price.offPeak.cacheHit, entry.price.currency)
1531
+ })]
1532
+ }) : formatUnitPrice(entry.price.cacheHit, entry.price.currency)
1533
+ }),
1534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1535
+ className: UsageBilling_module_css_default.numCol,
1536
+ children: entry.price.offPeak !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1537
+ className: UsageBilling_module_css_default.bandPrice,
1538
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatUnitPrice(entry.price.output, entry.price.currency) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1539
+ className: UsageBilling_module_css_default.bandPriceOff,
1540
+ children: formatUnitPrice(entry.price.offPeak.output, entry.price.currency)
1541
+ })]
1542
+ }) : formatUnitPrice(entry.price.output, entry.price.currency)
1543
+ }),
1544
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: entry.price.offPeak !== void 0 && entry.peakHours !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1545
+ className: UsageBilling_module_css_default.bandTag,
1546
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1547
+ t("billing.peak"),
1548
+ " ",
1549
+ entry.peakHours
1550
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1551
+ className: UsageBilling_module_css_default.bandTagOff,
1552
+ children: [t("billing.offPeak"), " 50%"]
1553
+ })]
1554
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1555
+ className: UsageBilling_module_css_default.flatTag,
1556
+ children: t("billing.flat")
1557
+ }) })
1558
+ ] }, entry.key)) })]
1559
+ })
1560
+ })]
1561
+ })
1562
+ ]
1563
+ })]
1564
+ })
1565
+ });
1566
+ }
1567
+ /**
1568
+ * UsageBilling: sidebar trigger plus the billing dashboard modal.
1569
+ * @param props - framework-provided sidebar and locale props.
1570
+ */
1571
+ function UsageBilling(props) {
1572
+ const { t, checkModels } = props;
1573
+ const [stats, setStats] = (0, react.useState)(EMPTY_STATS);
1574
+ const [health, setHealth] = (0, react.useState)(IDLE_HEALTH);
1575
+ const [open, setOpen] = (0, react.useState)(false);
1576
+ const close = (0, react.useCallback)(() => {
1577
+ setOpen(false);
1578
+ }, []);
1579
+ const openDashboard = (0, react.useCallback)(() => {
1580
+ setOpen(true);
1581
+ }, []);
1582
+ (0, react.useEffect)(() => {
1583
+ let mounted = true;
1584
+ loadUsageStats().then((data) => {
1585
+ if (mounted && data !== null) setStats(data);
1586
+ });
1587
+ return () => {
1588
+ mounted = false;
1589
+ };
1590
+ }, []);
1591
+ (0, react.useEffect)(() => {
1592
+ let mounted = true;
1593
+ checkModels().then((result) => {
1594
+ if (mounted) setHealth(result);
1595
+ });
1596
+ return () => {
1597
+ mounted = false;
1598
+ };
1599
+ }, [checkModels]);
1600
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1601
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBillingTrigger, {
1602
+ ...props,
1603
+ onOpen: openDashboard,
1604
+ totalCost: stats.total.cost > 0 ? stats.total.cost : 0,
1605
+ todayCost: stats.byDay[today]?.cost ?? 0
1606
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BillingDashboard, {
1607
+ stats,
1608
+ t,
1609
+ onClose: close,
1610
+ health
1611
+ })] });
1612
+ }
1613
+ //#endregion
1614
+ //#region src/client/locales.ts
1615
+ const NS = "usageBilling";
1616
+ const zh = {
1617
+ "billing.title": "使用统计",
1618
+ "billing.subtitle": "计费仪表盘",
1619
+ "billing.cost": "费用",
1620
+ "billing.todayCost": "今日费用",
1621
+ "billing.totalCost": "总费用",
1622
+ "billing.calls": "调用",
1623
+ "billing.cacheHitRate": "缓存命中率",
1624
+ "billing.tokens": "Token",
1625
+ "billing.inputTokens": "输入",
1626
+ "billing.outputTokens": "输出",
1627
+ "billing.avgCost": "平均成本",
1628
+ "billing.trend": "每日费用与调用趋势",
1629
+ "billing.trendEmpty": "暂无趋势数据",
1630
+ "billing.models": "模型计费明细",
1631
+ "billing.estimated": "估算",
1632
+ "billing.actual": "实际",
1633
+ "billing.pricing": "模型单价表",
1634
+ "billing.showPricing": "查看模型单价",
1635
+ "billing.hidePricing": "收起单价表",
1636
+ "billing.pricePerM": "¥ / 1M tokens",
1637
+ "billing.input": "输入",
1638
+ "billing.output": "输出",
1639
+ "billing.cacheHit": "缓存命中",
1640
+ "billing.peak": "高峰",
1641
+ "billing.offPeak": "低谷",
1642
+ "billing.flat": "全天统一",
1643
+ "billing.peakHours": "高峰时段",
1644
+ "billing.band": "时段",
1645
+ "billing.openDashboard": "打开计费仪表盘",
1646
+ "billing.close": "关闭",
1647
+ "billing.lastUpdated": "数据更新于",
1648
+ "billing.noData": "暂无计费数据"
1649
+ };
1650
+ const en = {
1651
+ "billing.title": "Usage",
1652
+ "billing.subtitle": "Billing dashboard",
1653
+ "billing.cost": "Cost",
1654
+ "billing.todayCost": "Today",
1655
+ "billing.totalCost": "Total",
1656
+ "billing.calls": "Calls",
1657
+ "billing.cacheHitRate": "Cache Hit",
1658
+ "billing.tokens": "Tokens",
1659
+ "billing.inputTokens": "Input",
1660
+ "billing.outputTokens": "Output",
1661
+ "billing.avgCost": "Avg cost",
1662
+ "billing.trend": "Daily cost & calls",
1663
+ "billing.trendEmpty": "No trend data yet",
1664
+ "billing.models": "Model billing",
1665
+ "billing.estimated": "Est.",
1666
+ "billing.actual": "Actual",
1667
+ "billing.pricing": "Model pricing",
1668
+ "billing.showPricing": "View pricing",
1669
+ "billing.hidePricing": "Hide pricing",
1670
+ "billing.pricePerM": "¥ / 1M tokens",
1671
+ "billing.input": "Input",
1672
+ "billing.output": "Output",
1673
+ "billing.cacheHit": "Cache hit",
1674
+ "billing.peak": "Peak",
1675
+ "billing.offPeak": "Off-peak",
1676
+ "billing.flat": "Flat",
1677
+ "billing.peakHours": "Peak hours",
1678
+ "billing.band": "Band",
1679
+ "billing.openDashboard": "Open billing dashboard",
1680
+ "billing.close": "Close",
1681
+ "billing.lastUpdated": "Updated",
1682
+ "billing.noData": "No billing data yet"
1683
+ };
1684
+ //#endregion
1685
+ //#region src/client/apply.ts
1686
+ /** Required services for the usage billing surface. */
1687
+ const inject = [
1688
+ "slots",
1689
+ "locale",
1690
+ "connection"
1691
+ ];
1692
+ /**
1693
+ * Client plugin body: the UsageBilling entry in the sidebar footer.
1694
+ * @param ctx - client root context.
1695
+ */
1696
+ function apply(ctx) {
1697
+ ctx.effect(() => ctx.locale.register(NS, {
1698
+ zh,
1699
+ en
1700
+ }), "ui-usage-billing: dictionaries");
1701
+ ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
1702
+ name: "sidebar.footer.action",
1703
+ id: "usage-billing",
1704
+ order: -10,
1705
+ locale: NS,
1706
+ inject: () => ({ checkModels: async () => {
1707
+ try {
1708
+ const { result } = await ctx.connection.api.llm.models({});
1709
+ if (!result.ok) return {
1710
+ checked: true,
1711
+ available: false,
1712
+ providers: 0,
1713
+ failures: 0,
1714
+ okProviders: [],
1715
+ badProviders: []
1716
+ };
1717
+ return {
1718
+ checked: true,
1719
+ available: result.value.groups.length > 0,
1720
+ providers: result.value.groups.length,
1721
+ failures: result.value.failures.length,
1722
+ okProviders: result.value.groups.map((group) => group.name),
1723
+ badProviders: result.value.failures.map((failure) => failure.name)
1724
+ };
1725
+ } catch {
1726
+ return {
1727
+ checked: true,
1728
+ available: false,
1729
+ providers: 0,
1730
+ failures: 0,
1731
+ okProviders: [],
1732
+ badProviders: []
1733
+ };
1734
+ }
1735
+ } })
1736
+ }, UsageBilling));
1737
+ }
1738
+ //#endregion
1739
+ exports.UsageBilling = UsageBilling;
1740
+ exports.apply = apply;
1741
+ exports.inject = inject;
1742
+ return module.exports;
1743
+ }
1744
+ });
1745
+
1746
+ //# sourceMappingURL=client.js.map