@kenz1117/dsh-ui-usage-billing 0.2.5 → 0.3.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/README.md +35 -6
- package/lib/client.js +779 -274
- package/lib/index.js +194 -63
- package/lib/types/aggregate.d.ts +73 -6
- package/lib/types/client/TrendChart.d.ts +5 -4
- package/lib/types/client/UsageBilling.d.ts +30 -4
- package/lib/types/client/apply.d.ts +31 -0
- package/lib/types/client/billing-service.d.ts +40 -0
- package/lib/types/client/budget-store.d.ts +32 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/pricing.d.ts +0 -10
- package/lib/types/index.d.ts +5 -0
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -7,6 +7,7 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
11
|
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
11
12
|
function r(e) {
|
|
12
13
|
var t, f, n = "";
|
|
@@ -23,7 +24,7 @@ window.__ModuleLoader__.load({
|
|
|
23
24
|
}
|
|
24
25
|
//#endregion
|
|
25
26
|
//#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-l1);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:12px;padding:10px 14px;transition:border-color .14s,background-color .14s;display:flex}.VWh0dG_trigger:hover,.VWh0dG_trigger:focus-visible{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover);outline:none}.VWh0dG_triggerIcon{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.VWh0dG_triggerIcon svg{stroke-width:1.8px;width:15px;height:15px}.VWh0dG_triggerToday,.VWh0dG_triggerMonth{align-items:baseline;gap:4px;min-width:0;display:flex}.VWh0dG_triggerDivider{background:var(--dsw-alias-border-l1);align-self:stretch;width:1px}.VWh0dG_triggerAmount{color:var(--dsw-alias-label-primary);letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap;font-size:17px;font-weight:600;line-height:22px}.VWh0dG_triggerAmountSub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:13px;font-weight:600;line-height:18px}.VWh0dG_triggerMeta{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:10.5px;line-height:14px}.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{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:16px;justify-content:space-between;align-items:center;gap:24px;padding:20px 24px;display:flex}.VWh0dG_heroMain{flex-direction:column;gap:4px;min-width:0;display:flex}.VWh0dG_heroLabel{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroValue{letter-spacing:-.02em;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:40px;font-weight:600;line-height:48px}.VWh0dG_heroMeta{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSide{flex-direction:column;flex:none;gap:10px;display:flex}.VWh0dG_heroSideItem{align-items:baseline;gap:8px;display:flex}.VWh0dG_heroSideLabel{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSideValue{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:16px;font-weight:600;line-height:22px}.VWh0dG_delta{align-items:center;gap:2px;margin-left:4px;font-size:11px;font-weight:600;line-height:17px;display:inline-flex}.VWh0dG_deltaUp{color:var(--dsw-static-green-500)}.VWh0dG_deltaDown{color:var(--dsw-static-red-500)}.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_chartStack{stroke:var(--dsw-alias-bg-layer-1);stroke-width:.75px}.VWh0dG_chartStack:hover{opacity:.85}.VWh0dG_chartLine{stroke:var(--dsw-static-blue-500);stroke-width:2px;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_chartTooltipSwatch{border-radius:3px;flex:none;width:8px;height:8px;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);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);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_rateBadge{vertical-align:1px;white-space:nowrap;border-radius:999px;align-items:center;margin-left:6px;padding:1px 6px;font-size:10px;font-weight:500;line-height:14px;display:inline-flex}.VWh0dG_rateBadgeLive{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 10%, transparent)}.VWh0dG_rateBadgeBuiltin{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform)}.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:14px}.VWh0dG_heroSide{flex-direction:row;gap:20px}}";
|
|
27
|
+
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-l1);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:12px;padding:10px 14px;transition:border-color .14s,background-color .14s;display:flex}.VWh0dG_trigger:hover,.VWh0dG_trigger:focus-visible{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover);outline:none}.VWh0dG_triggerIcon{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.VWh0dG_triggerIcon svg{stroke-width:1.8px;width:15px;height:15px}.VWh0dG_triggerToday,.VWh0dG_triggerMonth{align-items:baseline;gap:4px;min-width:0;display:flex}.VWh0dG_triggerDivider{background:var(--dsw-alias-border-l1);align-self:stretch;width:1px}.VWh0dG_triggerAmount{color:var(--dsw-alias-label-primary);letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap;font-size:17px;font-weight:600;line-height:22px}.VWh0dG_triggerAmountSub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:13px;font-weight:600;line-height:18px}.VWh0dG_triggerMeta{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:10.5px;line-height:14px}.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{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:16px;justify-content:space-between;align-items:center;gap:24px;padding:20px 24px;display:flex}.VWh0dG_heroMain{flex-direction:column;gap:4px;min-width:0;display:flex}.VWh0dG_heroLabel{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroValue{letter-spacing:-.02em;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:40px;font-weight:600;line-height:48px}.VWh0dG_heroMeta{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSide{flex-direction:column;flex:none;gap:10px;display:flex}.VWh0dG_heroSideItem{align-items:baseline;gap:8px;display:flex}.VWh0dG_heroSideLabel{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSideValue{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:16px;font-weight:600;line-height:22px}.VWh0dG_delta{align-items:center;gap:2px;margin-left:4px;font-size:11px;font-weight:600;line-height:17px;display:inline-flex}.VWh0dG_deltaUp{color:var(--dsw-static-green-500)}.VWh0dG_deltaDown{color:var(--dsw-static-red-500)}.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_rangeToggle{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:8px;gap:2px;padding:2px;display:inline-flex}.VWh0dG_rangeButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:2px 8px;font-size:11px;line-height:16px;transition:background-color .14s,color .14s}.VWh0dG_rangeButtonActive{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font-weight:600}.VWh0dG_budget{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:14px;flex-direction:column;gap:8px;padding:12px 16px;display:flex}.VWh0dG_budgetHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.VWh0dG_budgetLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}.VWh0dG_budgetValue{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_budgetControls{align-items:center;gap:8px;min-width:0;display:inline-flex}.VWh0dG_budgetInputWrap{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;align-items:center;gap:2px;padding:2px 6px;display:inline-flex}.VWh0dG_budgetInputWrap:focus-within{border-color:var(--dsw-alias-border-l3)}.VWh0dG_budgetUnit{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.VWh0dG_budgetInput{width:64px;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;text-align:right;background:0 0;border:none;padding:0;font-size:12px;line-height:16px}.VWh0dG_budgetInput:focus-visible{outline:none}.VWh0dG_switch{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;border-radius:999px;flex:none;width:30px;height:17px;padding:0;transition:background-color .16s,border-color .16s;position:relative}.VWh0dG_switchOn{border-color:var(--dsw-static-blue-500);background:var(--dsw-static-blue-500)}.VWh0dG_switchKnob{background:var(--dsw-static-neutral-bluish-00);border-radius:50%;width:11px;height:11px;transition:transform .16s;position:absolute;top:2px;left:2px}.VWh0dG_switchOn .VWh0dG_switchKnob{transform:translate(13px)}.VWh0dG_budgetTrack{background:var(--dsw-alias-bg-module-platform);border-radius:999px;height:6px;overflow:hidden}.VWh0dG_budgetFill{background:var(--dsw-static-blue-500);border-radius:999px;height:100%;transition:width .2s}.VWh0dG_budgetFillOver{background:var(--dsw-static-red-500);animation:1.6s ease-in-out infinite VWh0dG_budgetOverPulse}@keyframes VWh0dG_budgetOverPulse{50%{opacity:.55}}.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_chartStack{stroke:var(--dsw-alias-bg-layer-1);stroke-width:.75px}.VWh0dG_chartStack:hover{opacity:.85}.VWh0dG_chartLine{stroke:var(--dsw-static-blue-500);stroke-width:2px;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_chartTooltipSwatch{border-radius:3px;flex:none;width:8px;height:8px;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);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);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_rateBadge{vertical-align:1px;white-space:nowrap;border-radius:999px;align-items:center;margin-left:6px;padding:1px 6px;font-size:10px;font-weight:500;line-height:14px;display:inline-flex}.VWh0dG_rateBadgeLive{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 10%, transparent)}.VWh0dG_rateBadgeBuiltin{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform)}.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}.VWh0dG_uncataloguedTag{vertical-align:1px;color:var(--dsw-static-amber-500);background:color-mix(in srgb, var(--dsw-static-amber-500) 12%, transparent);border-radius:999px;margin-left:6px;padding:0 6px;font-size:10px;font-weight:600;line-height:16px;display:inline-block}.VWh0dG_balanceCell{flex-direction:column;align-items:flex-end;gap:2px;display:inline-flex}.VWh0dG_balanceDays{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:10.5px;line-height:14px}.VWh0dG_balanceDaysLow{color:var(--dsw-static-red-500);font-weight:600}body[data-zine-mode] .VWh0dG_trigger,body[data-zine-mode] .VWh0dG_railButton{display:none}body[data-zine-mode] .VWh0dG_dashboardHead{background:#000;border-bottom:2px solid #e8ff00}body[data-zine-mode] .VWh0dG_headTitleRow{align-items:center;gap:8px;margin-top:4px;display:flex}body[data-zine-mode] .VWh0dG_dashboardTitle{color:#e8ff00;letter-spacing:.04em;text-transform:uppercase;text-shadow:0 0 8px #e8ff0066;font-weight:900}body[data-zine-mode] .VWh0dG_dashboardSubtitle{color:#c9d98a;letter-spacing:.08em;text-transform:uppercase;font-size:10px}body[data-zine-mode] .VWh0dG_closeButton{color:#0a0a05;background:#ff2d95;border:1.5px solid #ff2d95;border-radius:0;box-shadow:0 0 10px #ff2d9573}body[data-zine-mode] .VWh0dG_hero{background:#000;border:2.5px solid #e8ff00;border-radius:0;position:relative;box-shadow:0 0 0 1px #0009,0 0 24px #e8ff002e}body[data-zine-mode] .VWh0dG_heroLabel{color:#c9d98a;letter-spacing:.12em;text-transform:uppercase;font-size:10px;font-weight:900}body[data-zine-mode] .VWh0dG_heroValue{color:#e8ff00;letter-spacing:-.02em;text-shadow:0 0 12px #e8ff0073;font-size:38px;font-weight:900}body[data-zine-mode] .VWh0dG_heroMeta{color:#c9d98a;letter-spacing:.06em;text-transform:uppercase;font-size:10.5px}body[data-zine-mode] .VWh0dG_heroSideLabel{color:#c9d98a;letter-spacing:.1em;text-transform:uppercase;font-size:10px;font-weight:900}body[data-zine-mode] .VWh0dG_heroSideValue{color:#e8ff00;text-shadow:0 0 6px #e8ff0059}body[data-zine-mode] .VWh0dG_delta{font-weight:900}body[data-zine-mode] .VWh0dG_deltaUp{color:#ff2d95}body[data-zine-mode] .VWh0dG_deltaDown{color:#c9d98a}body[data-zine-mode] .VWh0dG_panel{background:#000;border:2px solid #e8ff00;border-radius:0}body[data-zine-mode] .VWh0dG_trendPanel{position:relative}body[data-zine-mode] .VWh0dG_panelTitle{color:#e8ff00;letter-spacing:.06em;text-transform:uppercase;text-shadow:0 0 6px #e8ff0066;font-weight:900}body[data-zine-mode] .VWh0dG_panelHint{color:#c9d98a;letter-spacing:.08em;text-transform:uppercase;font-size:10px;font-weight:900}body[data-zine-mode] .VWh0dG_modelTableScroll{background:#000;border:1.5px solid #e8ff0080;border-radius:0}@media (width<=640px){.VWh0dG_kpiGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.VWh0dG_hero{flex-direction:column;align-items:flex-start;gap:14px}.VWh0dG_heroSide{flex-direction:row;gap:20px}}";
|
|
27
28
|
const tagId = "@kenz1117/dsh-ui-usage-billing/UsageBilling.module.css";
|
|
28
29
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
29
30
|
const tag = document.createElement("style");
|
|
@@ -33,93 +34,118 @@ window.__ModuleLoader__.load({
|
|
|
33
34
|
document.head.appendChild(tag);
|
|
34
35
|
}
|
|
35
36
|
var UsageBilling_module_css_default = {
|
|
36
|
-
"
|
|
37
|
-
"bandPriceOff": "VWh0dG_bandPriceOff",
|
|
38
|
-
"chartBar": "VWh0dG_chartBar",
|
|
39
|
-
"bandTag": "VWh0dG_bandTag",
|
|
40
|
-
"triggerMonth": "VWh0dG_triggerMonth",
|
|
41
|
-
"chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
|
|
42
|
-
"deltaDown": "VWh0dG_deltaDown",
|
|
43
|
-
"modelTable": "VWh0dG_modelTable",
|
|
37
|
+
"chartGrid": "VWh0dG_chartGrid",
|
|
44
38
|
"pricingTable": "VWh0dG_pricingTable",
|
|
45
|
-
"
|
|
39
|
+
"rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
|
|
40
|
+
"chartLegendBar": "VWh0dG_chartLegendBar",
|
|
41
|
+
"numCol": "VWh0dG_numCol",
|
|
42
|
+
"budgetFill": "VWh0dG_budgetFill",
|
|
46
43
|
"modelDot": "VWh0dG_modelDot",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"flatTag": "VWh0dG_flatTag",
|
|
50
|
-
"panel": "VWh0dG_panel",
|
|
51
|
-
"triggerAmountSub": "VWh0dG_triggerAmountSub",
|
|
52
|
-
"dashboard": "VWh0dG_dashboard",
|
|
44
|
+
"uncataloguedTag": "VWh0dG_uncataloguedTag",
|
|
45
|
+
"trendPanel": "VWh0dG_trendPanel",
|
|
53
46
|
"kpiTile": "VWh0dG_kpiTile",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
47
|
+
"healthDot": "VWh0dG_healthDot",
|
|
48
|
+
"triggerMeta": "VWh0dG_triggerMeta",
|
|
49
|
+
"heroSide": "VWh0dG_heroSide",
|
|
50
|
+
"tableScroll": "VWh0dG_tableScroll",
|
|
51
|
+
"dashboardRight": "VWh0dG_dashboardRight",
|
|
52
|
+
"modelProvider": "VWh0dG_modelProvider",
|
|
53
|
+
"costCol": "VWh0dG_costCol",
|
|
54
|
+
"chartStack": "VWh0dG_chartStack",
|
|
55
|
+
"chartLine": "VWh0dG_chartLine",
|
|
56
|
+
"kpiGreen": "VWh0dG_kpiGreen",
|
|
57
|
+
"dashboardModal": "VWh0dG_dashboardModal",
|
|
58
|
+
"trigger": "VWh0dG_trigger",
|
|
59
|
+
"rangeToggle": "VWh0dG_rangeToggle",
|
|
60
|
+
"budgetInputWrap": "VWh0dG_budgetInputWrap",
|
|
61
|
+
"modelName": "VWh0dG_modelName",
|
|
62
|
+
"planTag": "VWh0dG_planTag",
|
|
63
|
+
"heroSideItem": "VWh0dG_heroSideItem",
|
|
64
|
+
"rangeButtonActive": "VWh0dG_rangeButtonActive",
|
|
65
|
+
"hero": "VWh0dG_hero",
|
|
66
|
+
"switchKnob": "VWh0dG_switchKnob",
|
|
67
|
+
"chartTooltipRow": "VWh0dG_chartTooltipRow",
|
|
68
|
+
"chartLegendLine": "VWh0dG_chartLegendLine",
|
|
69
|
+
"modelTableScroll": "VWh0dG_modelTableScroll",
|
|
70
|
+
"chartWrap": "VWh0dG_chartWrap",
|
|
71
|
+
"chartTooltip": "VWh0dG_chartTooltip",
|
|
57
72
|
"heroMeta": "VWh0dG_heroMeta",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
73
|
+
"healthBad": "VWh0dG_healthBad",
|
|
74
|
+
"budgetFillOver": "VWh0dG_budgetFillOver",
|
|
75
|
+
"closeButton": "VWh0dG_closeButton",
|
|
76
|
+
"budgetLabel": "VWh0dG_budgetLabel",
|
|
77
|
+
"chartTooltipDate": "VWh0dG_chartTooltipDate",
|
|
78
|
+
"balanceDays": "VWh0dG_balanceDays",
|
|
61
79
|
"heroSideLabel": "VWh0dG_heroSideLabel",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
80
|
+
"heroMain": "VWh0dG_heroMain",
|
|
81
|
+
"panelHead": "VWh0dG_panelHead",
|
|
82
|
+
"pricingChevron": "VWh0dG_pricingChevron",
|
|
83
|
+
"dashboard": "VWh0dG_dashboard",
|
|
84
|
+
"chartSvg": "VWh0dG_chartSvg",
|
|
85
|
+
"kpiGrid": "VWh0dG_kpiGrid",
|
|
64
86
|
"heroLabel": "VWh0dG_heroLabel",
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
87
|
+
"chartBar": "VWh0dG_chartBar",
|
|
88
|
+
"switch": "VWh0dG_switch",
|
|
89
|
+
"chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
|
|
90
|
+
"pricingToggle": "VWh0dG_pricingToggle",
|
|
91
|
+
"pricingToggleText": "VWh0dG_pricingToggleText",
|
|
92
|
+
"bandPriceOff": "VWh0dG_bandPriceOff",
|
|
70
93
|
"railButton": "VWh0dG_railButton",
|
|
71
|
-
"
|
|
94
|
+
"budgetHead": "VWh0dG_budgetHead",
|
|
95
|
+
"pricingChevronOpen": "VWh0dG_pricingChevronOpen",
|
|
96
|
+
"triggerAmountSub": "VWh0dG_triggerAmountSub",
|
|
97
|
+
"emptyRow": "VWh0dG_emptyRow",
|
|
98
|
+
"deltaDown": "VWh0dG_deltaDown",
|
|
99
|
+
"dashboardSubtitle": "VWh0dG_dashboardSubtitle",
|
|
100
|
+
"budgetOverPulse": "VWh0dG_budgetOverPulse",
|
|
101
|
+
"chartDot": "VWh0dG_chartDot",
|
|
102
|
+
"dashboardBody": "VWh0dG_dashboardBody",
|
|
103
|
+
"kpiDetail": "VWh0dG_kpiDetail",
|
|
104
|
+
"rangeButton": "VWh0dG_rangeButton",
|
|
105
|
+
"heroValue": "VWh0dG_heroValue",
|
|
106
|
+
"bandTagOff": "VWh0dG_bandTagOff",
|
|
107
|
+
"bandPrice": "VWh0dG_bandPrice",
|
|
108
|
+
"kpiLabel": "VWh0dG_kpiLabel",
|
|
72
109
|
"panelTitle": "VWh0dG_panelTitle",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
110
|
+
"chartEmpty": "VWh0dG_chartEmpty",
|
|
111
|
+
"chartCrosshair": "VWh0dG_chartCrosshair",
|
|
112
|
+
"triggerDivider": "VWh0dG_triggerDivider",
|
|
113
|
+
"budgetTrack": "VWh0dG_budgetTrack",
|
|
114
|
+
"rateBadge": "VWh0dG_rateBadge",
|
|
76
115
|
"healthOk": "VWh0dG_healthOk",
|
|
77
|
-
"heroSideValue": "VWh0dG_heroSideValue",
|
|
78
|
-
"kpiDetail": "VWh0dG_kpiDetail",
|
|
79
|
-
"pricingToggleText": "VWh0dG_pricingToggleText",
|
|
80
|
-
"closeButton": "VWh0dG_closeButton",
|
|
81
116
|
"dashboardTitle": "VWh0dG_dashboardTitle",
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"chartLegend": "VWh0dG_chartLegend",
|
|
85
|
-
"numCol": "VWh0dG_numCol",
|
|
86
|
-
"healthBadgeBad": "VWh0dG_healthBadgeBad",
|
|
87
|
-
"kpiGrid": "VWh0dG_kpiGrid",
|
|
88
|
-
"chartDot": "VWh0dG_chartDot",
|
|
89
|
-
"healthDot": "VWh0dG_healthDot",
|
|
90
|
-
"pricingChevron": "VWh0dG_pricingChevron",
|
|
91
|
-
"emptyRow": "VWh0dG_emptyRow",
|
|
92
|
-
"rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
|
|
93
|
-
"healthIdle": "VWh0dG_healthIdle",
|
|
94
|
-
"trigger": "VWh0dG_trigger",
|
|
95
|
-
"dashboardHead": "VWh0dG_dashboardHead",
|
|
96
|
-
"tableScroll": "VWh0dG_tableScroll",
|
|
97
|
-
"kpiGreen": "VWh0dG_kpiGreen",
|
|
98
|
-
"na": "VWh0dG_na",
|
|
117
|
+
"chartAxisLabel": "VWh0dG_chartAxisLabel",
|
|
118
|
+
"switchOn": "VWh0dG_switchOn",
|
|
99
119
|
"healthBadgeOk": "VWh0dG_healthBadgeOk",
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"panelHead": "VWh0dG_panelHead",
|
|
106
|
-
"kpiLabel": "VWh0dG_kpiLabel",
|
|
107
|
-
"chartWrap": "VWh0dG_chartWrap",
|
|
108
|
-
"heroValue": "VWh0dG_heroValue",
|
|
109
|
-
"triggerMeta": "VWh0dG_triggerMeta",
|
|
110
|
-
"chartGrid": "VWh0dG_chartGrid",
|
|
111
|
-
"chartLine": "VWh0dG_chartLine",
|
|
112
|
-
"chartTooltip": "VWh0dG_chartTooltip",
|
|
120
|
+
"healthBadgeBad": "VWh0dG_healthBadgeBad",
|
|
121
|
+
"balanceCell": "VWh0dG_balanceCell",
|
|
122
|
+
"budget": "VWh0dG_budget",
|
|
123
|
+
"bandTag": "VWh0dG_bandTag",
|
|
124
|
+
"delta": "VWh0dG_delta",
|
|
113
125
|
"rateBadgeLive": "VWh0dG_rateBadgeLive",
|
|
126
|
+
"triggerAmount": "VWh0dG_triggerAmount",
|
|
127
|
+
"triggerToday": "VWh0dG_triggerToday",
|
|
128
|
+
"panel": "VWh0dG_panel",
|
|
129
|
+
"budgetUnit": "VWh0dG_budgetUnit",
|
|
130
|
+
"heroSideValue": "VWh0dG_heroSideValue",
|
|
131
|
+
"budgetInput": "VWh0dG_budgetInput",
|
|
132
|
+
"chartLegend": "VWh0dG_chartLegend",
|
|
133
|
+
"modelTable": "VWh0dG_modelTable",
|
|
134
|
+
"budgetControls": "VWh0dG_budgetControls",
|
|
135
|
+
"modelCell": "VWh0dG_modelCell",
|
|
136
|
+
"panelHint": "VWh0dG_panelHint",
|
|
137
|
+
"budgetValue": "VWh0dG_budgetValue",
|
|
138
|
+
"na": "VWh0dG_na",
|
|
139
|
+
"flatTag": "VWh0dG_flatTag",
|
|
114
140
|
"healthBadge": "VWh0dG_healthBadge",
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
141
|
+
"balanceDaysLow": "VWh0dG_balanceDaysLow",
|
|
142
|
+
"triggerMonth": "VWh0dG_triggerMonth",
|
|
143
|
+
"headTitleRow": "VWh0dG_headTitleRow",
|
|
144
|
+
"healthIdle": "VWh0dG_healthIdle",
|
|
145
|
+
"deltaUp": "VWh0dG_deltaUp",
|
|
146
|
+
"dashboardHead": "VWh0dG_dashboardHead",
|
|
147
|
+
"triggerIcon": "VWh0dG_triggerIcon",
|
|
148
|
+
"kpiValue": "VWh0dG_kpiValue"
|
|
123
149
|
};
|
|
124
150
|
/** 运行时实时覆盖:undefined = 用内置目录与内置汇率(默认值降级)。 */
|
|
125
151
|
let liveRate;
|
|
@@ -150,19 +176,6 @@ window.__ModuleLoader__.load({
|
|
|
150
176
|
/** Default share of traffic assumed to fall in the peak band (0..1). */
|
|
151
177
|
const DEFAULT_PEAK_SHARE = .5;
|
|
152
178
|
/**
|
|
153
|
-
* Model keys served through a subscription plan (e.g. a coding plan or topic
|
|
154
|
-
* plan) instead of metered per-token API billing. Usage through these routes
|
|
155
|
-
* costs no tokens: the estimator treats them as ¥0 and the billing table
|
|
156
|
-
* labels them 订阅包含. Add any model key your deployment serves through a
|
|
157
|
-
* plan here; leave empty when every route is pay-as-you-go.
|
|
158
|
-
* kimi-k3: 月之暗面 coding plan 通道的调用按套餐计费,费用记 0。
|
|
159
|
-
*/
|
|
160
|
-
const SUBSCRIPTION_PLAN_KEYS = ["kimi-k3"];
|
|
161
|
-
/** Whether one stats model key is billed through a subscription plan. */
|
|
162
|
-
function isSubscriptionPlan(key) {
|
|
163
|
-
return SUBSCRIPTION_PLAN_KEYS.includes(key);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
179
|
* Built-in catalog of current mainstream models as of 2026-08-16, priced from
|
|
167
180
|
* each provider's official price page. Domestic providers are OpenAI-API
|
|
168
181
|
* compatible and publish RMB prices directly; overseas providers publish USD
|
|
@@ -382,6 +395,18 @@ window.__ModuleLoader__.load({
|
|
|
382
395
|
output: 100
|
|
383
396
|
}
|
|
384
397
|
},
|
|
398
|
+
{
|
|
399
|
+
key: "mimo-v2.5",
|
|
400
|
+
name: "MiMo V2.5",
|
|
401
|
+
provider: "小米",
|
|
402
|
+
colorVar: "dsw-static-green-400",
|
|
403
|
+
price: {
|
|
404
|
+
currency: "CNY",
|
|
405
|
+
input: 4,
|
|
406
|
+
cacheHit: .4,
|
|
407
|
+
output: 12
|
|
408
|
+
}
|
|
409
|
+
},
|
|
385
410
|
{
|
|
386
411
|
key: "minimax",
|
|
387
412
|
name: "MiniMax-M3",
|
|
@@ -678,7 +703,6 @@ window.__ModuleLoader__.load({
|
|
|
678
703
|
* @returns the estimated cost in CNY.
|
|
679
704
|
*/
|
|
680
705
|
function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE) {
|
|
681
|
-
if (isSubscriptionPlan(entry.key)) return 0;
|
|
682
706
|
const peak = priceBandCost(entry.price, buckets, entry.price.currency);
|
|
683
707
|
const off = entry.price.offPeak === void 0 ? peak : priceBandCost(entry.price.offPeak, buckets, entry.price.currency);
|
|
684
708
|
return peak * peakShare + off * (1 - peakShare);
|
|
@@ -723,9 +747,10 @@ window.__ModuleLoader__.load({
|
|
|
723
747
|
/**
|
|
724
748
|
* TrendChart: dependency-free SVG chart of daily cost + calls.
|
|
725
749
|
*
|
|
726
|
-
* The columns are
|
|
727
|
-
*
|
|
728
|
-
*
|
|
750
|
+
* The columns are STACKED per day — one bar per day, with each model's cost
|
|
751
|
+
* as a colored segment inside the bar, so the daily total reads at a glance
|
|
752
|
+
* and the model mix stays visible. The blue line is the total call volume
|
|
753
|
+
* across all models, plotted on its own right-hand axis.
|
|
729
754
|
* A hover crosshair shows the day's model breakdown. No chart library — the
|
|
730
755
|
* surface stays self-contained and offline.
|
|
731
756
|
*/
|
|
@@ -763,7 +788,7 @@ window.__ModuleLoader__.load({
|
|
|
763
788
|
color: ""
|
|
764
789
|
};
|
|
765
790
|
/**
|
|
766
|
-
* Render the daily
|
|
791
|
+
* Render the daily stacked cost bars plus the total-calls line.
|
|
767
792
|
* @param props.data - sorted daily rows (ascending date).
|
|
768
793
|
* @param props.models - the model legend, in bar order.
|
|
769
794
|
*/
|
|
@@ -778,13 +803,12 @@ window.__ModuleLoader__.load({
|
|
|
778
803
|
if (n === 1) return PAD.left + plotW / 2;
|
|
779
804
|
return PAD.left + plotW * i / (n - 1);
|
|
780
805
|
};
|
|
781
|
-
const maxCost = Math.max(...data.
|
|
806
|
+
const maxCost = Math.max(...data.map((d) => Math.max(d.cost, Object.values(d.byModel ?? {}).reduce((sum, v) => sum + v, 0))), 1e-4);
|
|
782
807
|
const yCost = (value) => PAD.top + plotH - value / maxCost * plotH;
|
|
783
808
|
const maxCalls = Math.max(...data.map((d) => d.calls), 1);
|
|
784
809
|
const yCalls = (value) => PAD.top + plotH - value / maxCalls * plotH;
|
|
785
810
|
const groupW = plotW / n;
|
|
786
|
-
const
|
|
787
|
-
const barW = Math.min(14, groupW / barCount * .7);
|
|
811
|
+
const barW = Math.min(18, groupW * .6);
|
|
788
812
|
return {
|
|
789
813
|
n,
|
|
790
814
|
plotW,
|
|
@@ -794,18 +818,31 @@ window.__ModuleLoader__.load({
|
|
|
794
818
|
yCalls,
|
|
795
819
|
barW,
|
|
796
820
|
bars: data.flatMap((d, i) => {
|
|
821
|
+
const x = inner(i) - barW / 2;
|
|
797
822
|
if (models.length === 0) return [{
|
|
798
823
|
date: d.date,
|
|
799
824
|
model: TOTAL_MODEL,
|
|
800
|
-
x
|
|
801
|
-
|
|
825
|
+
x,
|
|
826
|
+
base: 0,
|
|
827
|
+
value: d.cost,
|
|
828
|
+
topRounded: true
|
|
802
829
|
}];
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
value
|
|
808
|
-
|
|
830
|
+
let topKey = null;
|
|
831
|
+
for (const model of models) if ((d.byModel?.[model.key] ?? 0) > 0) topKey = model.key;
|
|
832
|
+
let acc = 0;
|
|
833
|
+
return models.map((model) => {
|
|
834
|
+
const value = d.byModel?.[model.key] ?? 0;
|
|
835
|
+
const bar = {
|
|
836
|
+
date: d.date,
|
|
837
|
+
model,
|
|
838
|
+
x,
|
|
839
|
+
base: acc,
|
|
840
|
+
value,
|
|
841
|
+
topRounded: model.key === topKey
|
|
842
|
+
};
|
|
843
|
+
acc += value;
|
|
844
|
+
return bar;
|
|
845
|
+
});
|
|
809
846
|
}),
|
|
810
847
|
costTicks: [
|
|
811
848
|
0,
|
|
@@ -870,10 +907,10 @@ window.__ModuleLoader__.load({
|
|
|
870
907
|
}),
|
|
871
908
|
bars.map((bar) => bar.value > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
872
909
|
x: bar.x,
|
|
873
|
-
y: yCost(bar.value),
|
|
910
|
+
y: yCost(bar.base + bar.value),
|
|
874
911
|
width: barW,
|
|
875
|
-
height: yCost(
|
|
876
|
-
rx: 2,
|
|
912
|
+
height: yCost(bar.base) - yCost(bar.base + bar.value),
|
|
913
|
+
rx: bar.topRounded ? 2 : 0,
|
|
877
914
|
className: bar.model.color === "" ? UsageBilling_module_css_default.chartBar : UsageBilling_module_css_default.chartStack,
|
|
878
915
|
style: bar.model.color === "" ? void 0 : { fill: bar.model.color }
|
|
879
916
|
}, `${bar.date}-${bar.model.key}`) : null),
|
|
@@ -976,6 +1013,13 @@ window.__ModuleLoader__.load({
|
|
|
976
1013
|
* before real data arrives the dashboard shows an empty (zero) snapshot,
|
|
977
1014
|
* never fabricated samples.
|
|
978
1015
|
*/
|
|
1016
|
+
/** 会话明细面板最多展示的行数(完整长尾在服务端另有一层封顶)。 */
|
|
1017
|
+
const SESSION_DISPLAY_LIMIT = 20;
|
|
1018
|
+
/** 项目名取 cwd 的末级目录;无 cwd 时由调用方回退为 em dash。 */
|
|
1019
|
+
function projectName(cwd) {
|
|
1020
|
+
if (cwd === void 0) return void 0;
|
|
1021
|
+
return cwd.split(/[\\/]/).filter(Boolean).pop() ?? cwd;
|
|
1022
|
+
}
|
|
979
1023
|
/** Idle health state before the probe settles. */
|
|
980
1024
|
const IDLE_HEALTH = {
|
|
981
1025
|
checked: false,
|
|
@@ -989,6 +1033,8 @@ window.__ModuleLoader__.load({
|
|
|
989
1033
|
* The dashboard's display names (中文厂商名) never equal the provider names a
|
|
990
1034
|
* user actually configures (deepseek, zhipu, qwen…), so the dot match also
|
|
991
1035
|
* accepts a bidirectional substring hit and a display-name alias list.
|
|
1036
|
+
* 导出供一致性守卫测试:catalog 每个厂商都必须在此登记(Custom 除外),
|
|
1037
|
+
* 防止新增厂商漏配导致健康绿灯不亮。
|
|
992
1038
|
*/
|
|
993
1039
|
const PROVIDER_ALIASES = {
|
|
994
1040
|
"DeepSeek": ["deepseek"],
|
|
@@ -1009,6 +1055,11 @@ window.__ModuleLoader__.load({
|
|
|
1009
1055
|
"ark"
|
|
1010
1056
|
],
|
|
1011
1057
|
"月之暗面": ["moonshot", "kimi"],
|
|
1058
|
+
"小米": [
|
|
1059
|
+
"xiaomi",
|
|
1060
|
+
"mi",
|
|
1061
|
+
"mimo"
|
|
1062
|
+
],
|
|
1012
1063
|
"MiniMax": ["minimax"],
|
|
1013
1064
|
"百度文心": [
|
|
1014
1065
|
"ernie",
|
|
@@ -1058,6 +1109,41 @@ window.__ModuleLoader__.load({
|
|
|
1058
1109
|
const aliases = PROVIDER_ALIASES[display];
|
|
1059
1110
|
return aliases !== void 0 && aliases.some((alias) => providerNameHits(normalizeProvider(alias), liveKey));
|
|
1060
1111
|
}
|
|
1112
|
+
/**
|
|
1113
|
+
* 从真实 model id 反推提供方显示名:目录未收录的模型(key 落回「其他」)
|
|
1114
|
+
* 只靠 entry.provider(Custom)永远点不亮健康灯,这里用厂商别名对 model id
|
|
1115
|
+
* 做强匹配(别名作为完整 id / 前缀 / 独立段)与弱匹配(长别名子串),
|
|
1116
|
+
* 命中即显示厂商名并点亮健康点;无命中保持 Custom。
|
|
1117
|
+
* 导出供守卫测试:短别名(mi/yi)仅允许前缀形式,防止 minimax 等误吞。
|
|
1118
|
+
*/
|
|
1119
|
+
function providerFromModelKey(modelKey) {
|
|
1120
|
+
const key = modelKey.trim().toLowerCase();
|
|
1121
|
+
const compact = key.replace(/[\s_/-]+/g, "");
|
|
1122
|
+
if (compact.length === 0) return void 0;
|
|
1123
|
+
for (const [display, aliases] of Object.entries(PROVIDER_ALIASES)) for (const alias of aliases) {
|
|
1124
|
+
const a = normalizeProvider(alias);
|
|
1125
|
+
if (a.length === 0) continue;
|
|
1126
|
+
if (key === a) return display;
|
|
1127
|
+
if (key.startsWith(`${a}-`) || key.startsWith(`${a}/`) || key.startsWith(`${a}_`)) return display;
|
|
1128
|
+
if (key.includes(`${a}-`) || key.includes(`${a}/`) || key.includes(`${a}_`)) return display;
|
|
1129
|
+
}
|
|
1130
|
+
for (const [display, aliases] of Object.entries(PROVIDER_ALIASES)) for (const alias of aliases) {
|
|
1131
|
+
const a = normalizeProvider(alias);
|
|
1132
|
+
if (a.length < 4) continue;
|
|
1133
|
+
if (compact.includes(a)) return display;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
/** 余额不足告警的默认阈值(人民币元):宿主 Config 未配置时客户端兜底。 */
|
|
1137
|
+
const DEFAULT_LOW_BALANCE_THRESHOLD = 50;
|
|
1138
|
+
/**
|
|
1139
|
+
* 日均消耗(元/天):取最近 7 天(含今天)总花费 ÷ 有记录天数;无记录返回 0
|
|
1140
|
+
* (此时可用天数无法估算,调用方不显示天数提示)。日期戳字典序即时间序。
|
|
1141
|
+
*/
|
|
1142
|
+
function dailyBurnRate(byDay, today) {
|
|
1143
|
+
const dates = Object.keys(byDay).filter((d) => d <= today).sort().slice(-7);
|
|
1144
|
+
if (dates.length === 0) return 0;
|
|
1145
|
+
return dates.reduce((sum, d) => sum + (byDay[d]?.cost ?? 0), 0) / dates.length;
|
|
1146
|
+
}
|
|
1061
1147
|
/** Resolve one provider's dot state: green when live, red when failed, gray when unknown. */
|
|
1062
1148
|
function providerDot(health, provider) {
|
|
1063
1149
|
if (!health.checked) return UsageBilling_module_css_default.healthIdle;
|
|
@@ -1132,7 +1218,10 @@ window.__ModuleLoader__.load({
|
|
|
1132
1218
|
byModel: candidate.byModel ?? {},
|
|
1133
1219
|
byDay: candidate.byDay ?? {},
|
|
1134
1220
|
...candidate.byDayModels !== void 0 ? { byDayModels: candidate.byDayModels } : {},
|
|
1135
|
-
...candidate.updatedAt !== void 0 ? { updatedAt: candidate.updatedAt } : {}
|
|
1221
|
+
...candidate.updatedAt !== void 0 ? { updatedAt: candidate.updatedAt } : {},
|
|
1222
|
+
...typeof candidate.budget === "number" ? { budget: candidate.budget } : {},
|
|
1223
|
+
...typeof candidate.lowBalanceThreshold === "number" ? { lowBalanceThreshold: candidate.lowBalanceThreshold } : {},
|
|
1224
|
+
...Array.isArray(candidate.bySession) ? { bySession: candidate.bySession } : {}
|
|
1136
1225
|
};
|
|
1137
1226
|
} catch {
|
|
1138
1227
|
return null;
|
|
@@ -1182,6 +1271,8 @@ window.__ModuleLoader__.load({
|
|
|
1182
1271
|
}
|
|
1183
1272
|
/**
|
|
1184
1273
|
* Sidebar footer trigger: compact pill in wide mode, icon in rail mode.
|
|
1274
|
+
* ZINE 模式下入口由主题插件的贴纸层承担,本触发器由 CSS
|
|
1275
|
+
* (body[data-zine-mode] 选择器)隐藏,组件本身无 zine 分支。
|
|
1185
1276
|
* @param props - framework props plus `wide` column state.
|
|
1186
1277
|
*/
|
|
1187
1278
|
function UsageBillingTrigger(props) {
|
|
@@ -1212,6 +1303,7 @@ window.__ModuleLoader__.load({
|
|
|
1212
1303
|
if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1213
1304
|
type: "button",
|
|
1214
1305
|
className: UsageBilling_module_css_default.railButton,
|
|
1306
|
+
"data-testid": "billing-rail-button",
|
|
1215
1307
|
onClick: onOpen,
|
|
1216
1308
|
title: `${t("billing.title")} · ${formatMoney(monthCost)}`,
|
|
1217
1309
|
children: cardIcon
|
|
@@ -1219,15 +1311,18 @@ window.__ModuleLoader__.load({
|
|
|
1219
1311
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1220
1312
|
type: "button",
|
|
1221
1313
|
className: UsageBilling_module_css_default.trigger,
|
|
1314
|
+
"data-testid": "billing-trigger",
|
|
1222
1315
|
onClick: onOpen,
|
|
1223
1316
|
title: `${t("billing.title")} · 本月 ${formatMoney(monthCost)}`,
|
|
1224
1317
|
children: [
|
|
1225
1318
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1226
1319
|
className: UsageBilling_module_css_default.triggerIcon,
|
|
1320
|
+
"data-testid": "billing-trigger-icon",
|
|
1227
1321
|
children: cardIcon
|
|
1228
1322
|
}),
|
|
1229
1323
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1230
1324
|
className: UsageBilling_module_css_default.triggerToday,
|
|
1325
|
+
"data-testid": "billing-trigger-today",
|
|
1231
1326
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1232
1327
|
className: UsageBilling_module_css_default.triggerMeta,
|
|
1233
1328
|
children: "今日"
|
|
@@ -1239,6 +1334,7 @@ window.__ModuleLoader__.load({
|
|
|
1239
1334
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.triggerDivider }),
|
|
1240
1335
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1241
1336
|
className: UsageBilling_module_css_default.triggerMonth,
|
|
1337
|
+
"data-testid": "billing-trigger-month",
|
|
1242
1338
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1243
1339
|
className: UsageBilling_module_css_default.triggerMeta,
|
|
1244
1340
|
children: "当月"
|
|
@@ -1252,12 +1348,15 @@ window.__ModuleLoader__.load({
|
|
|
1252
1348
|
}
|
|
1253
1349
|
/**
|
|
1254
1350
|
* The centered billing dashboard modal.
|
|
1255
|
-
* @param props - stats, locale function, close handler, model health, balances.
|
|
1351
|
+
* @param props - stats, locale function, close handler, model health, balances, renderSlot.
|
|
1256
1352
|
*/
|
|
1257
|
-
function BillingDashboard({ stats, t, onClose, health, balances }) {
|
|
1353
|
+
function BillingDashboard({ stats, t, onClose, health, balances, renderSlot, budgetEnabled, budgetAmount, onToggleBudget, onBudgetAmount }) {
|
|
1258
1354
|
const { total, byModel, byDay } = stats;
|
|
1259
1355
|
const [pricingOpen, setPricingOpen] = (0, react.useState)(false);
|
|
1356
|
+
const [sessionsOpen, setSessionsOpen] = (0, react.useState)(false);
|
|
1357
|
+
const [trendDays, setTrendDays] = (0, react.useState)(7);
|
|
1260
1358
|
const rateInfo = getRateInfo();
|
|
1359
|
+
const dailyBurn = dailyBurnRate(byDay, localDayStamp());
|
|
1261
1360
|
const balanceFor = (provider) => balances.find((balance) => normalizeProvider(balance.provider) === normalizeProvider(provider));
|
|
1262
1361
|
const renderBalance = (balance) => {
|
|
1263
1362
|
if (balance === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1271,7 +1370,18 @@ window.__ModuleLoader__.load({
|
|
|
1271
1370
|
className: UsageBilling_module_css_default.na,
|
|
1272
1371
|
children: "—"
|
|
1273
1372
|
});
|
|
1274
|
-
|
|
1373
|
+
const amount = balance.currency === "USD" ? `$${balance.totalBalance.toFixed(2)}` : formatMoney(balance.totalBalance);
|
|
1374
|
+
const balanceCny = balance.currency === "USD" ? balance.totalBalance * rateInfo.rate : balance.totalBalance;
|
|
1375
|
+
if (dailyBurn <= 0) return amount;
|
|
1376
|
+
const days = Math.floor(balanceCny / dailyBurn);
|
|
1377
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1378
|
+
className: UsageBilling_module_css_default.balanceCell,
|
|
1379
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: amount }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1380
|
+
className: clsx(UsageBilling_module_css_default.balanceDays, days <= 3 && UsageBilling_module_css_default.balanceDaysLow),
|
|
1381
|
+
"data-testid": "billing-balance-days",
|
|
1382
|
+
children: t("billing.balanceDays").replace("{days}", String(days))
|
|
1383
|
+
})]
|
|
1384
|
+
});
|
|
1275
1385
|
};
|
|
1276
1386
|
const cacheHitRate = total.cacheHit + total.cacheMiss > 0 ? total.cacheHit / (total.cacheHit + total.cacheMiss) * 100 : 0;
|
|
1277
1387
|
const dates = Object.keys(byDay).sort();
|
|
@@ -1284,13 +1394,13 @@ window.__ModuleLoader__.load({
|
|
|
1284
1394
|
const yearCost = dates.reduce((sum, d) => sum + (d.startsWith(yearPrefix) ? byDay[d]?.cost ?? 0 : 0), 0);
|
|
1285
1395
|
const trendDates = (0, react.useMemo)(() => {
|
|
1286
1396
|
const out = [];
|
|
1287
|
-
for (let offset =
|
|
1397
|
+
for (let offset = trendDays - 1; offset >= 0; offset -= 1) {
|
|
1288
1398
|
const day = /* @__PURE__ */ new Date();
|
|
1289
1399
|
day.setDate(day.getDate() - offset);
|
|
1290
1400
|
out.push(localDayStamp(day.getTime()));
|
|
1291
1401
|
}
|
|
1292
1402
|
return out;
|
|
1293
|
-
}, []);
|
|
1403
|
+
}, [trendDays]);
|
|
1294
1404
|
const latestDate = trendDates.at(-1) ?? today;
|
|
1295
1405
|
const trend = (0, react.useMemo)(() => trendDates.map((date) => {
|
|
1296
1406
|
const byModel = {};
|
|
@@ -1312,6 +1422,8 @@ window.__ModuleLoader__.load({
|
|
|
1312
1422
|
]);
|
|
1313
1423
|
const modelRows = (0, react.useMemo)(() => Object.entries(byModel).filter(([, data]) => data.calls > 0).map(([key, data]) => {
|
|
1314
1424
|
const entry = modelOf(key);
|
|
1425
|
+
const uncatalogued = entry.key === "other";
|
|
1426
|
+
const inferredProvider = uncatalogued ? providerFromModelKey(key) : void 0;
|
|
1315
1427
|
const buckets = {
|
|
1316
1428
|
input: data.input,
|
|
1317
1429
|
cacheHit: data.cacheHit,
|
|
@@ -1320,15 +1432,16 @@ window.__ModuleLoader__.load({
|
|
|
1320
1432
|
};
|
|
1321
1433
|
return {
|
|
1322
1434
|
key,
|
|
1323
|
-
name: entry.name,
|
|
1324
|
-
provider: entry.provider,
|
|
1435
|
+
name: uncatalogued ? key : entry.name,
|
|
1436
|
+
provider: inferredProvider ?? entry.provider,
|
|
1325
1437
|
calls: data.calls,
|
|
1326
1438
|
input: data.input,
|
|
1327
1439
|
output: data.output,
|
|
1328
1440
|
cacheHitRate: data.cacheHit + data.cacheMiss > 0 ? data.cacheHit / (data.cacheHit + data.cacheMiss) * 100 : 0,
|
|
1329
1441
|
estimated: computeCost(entry, buckets),
|
|
1330
|
-
plan:
|
|
1331
|
-
...data.cost > 0 ? { actual: data.cost } : {}
|
|
1442
|
+
plan: data.plan === true,
|
|
1443
|
+
...data.cost > 0 ? { actual: data.cost } : {},
|
|
1444
|
+
uncatalogued
|
|
1332
1445
|
};
|
|
1333
1446
|
}).sort((a, b) => (b.actual ?? b.estimated) - (a.actual ?? a.estimated)).map((row, index) => ({
|
|
1334
1447
|
...row,
|
|
@@ -1352,19 +1465,28 @@ window.__ModuleLoader__.load({
|
|
|
1352
1465
|
className: UsageBilling_module_css_default.dashboardModal ?? "",
|
|
1353
1466
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1354
1467
|
className: UsageBilling_module_css_default.dashboard,
|
|
1468
|
+
"data-testid": "billing-dashboard",
|
|
1355
1469
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1356
1470
|
className: UsageBilling_module_css_default.dashboardHead,
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1471
|
+
"data-testid": "billing-dashboard-head",
|
|
1472
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1473
|
+
renderSlot("billing.dashboard.decor", { position: "head" }),
|
|
1474
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1475
|
+
className: UsageBilling_module_css_default.headTitleRow,
|
|
1476
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1477
|
+
className: UsageBilling_module_css_default.dashboardTitle,
|
|
1478
|
+
children: t("billing.title")
|
|
1479
|
+
}), renderSlot("billing.dashboard.decor", { position: "headTitle" })]
|
|
1480
|
+
}),
|
|
1481
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1482
|
+
className: UsageBilling_module_css_default.dashboardSubtitle,
|
|
1483
|
+
children: [
|
|
1484
|
+
t("billing.lastUpdated"),
|
|
1485
|
+
" ",
|
|
1486
|
+
latestDate
|
|
1487
|
+
]
|
|
1488
|
+
})
|
|
1489
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1368
1490
|
className: UsageBilling_module_css_default.dashboardRight,
|
|
1369
1491
|
children: [health.checked && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1370
1492
|
className: clsx(UsageBilling_module_css_default.healthBadge, health.available ? UsageBilling_module_css_default.healthBadgeOk : UsageBilling_module_css_default.healthBadgeBad),
|
|
@@ -1376,6 +1498,7 @@ window.__ModuleLoader__.load({
|
|
|
1376
1498
|
type: "button",
|
|
1377
1499
|
className: UsageBilling_module_css_default.closeButton,
|
|
1378
1500
|
"aria-label": t("billing.close"),
|
|
1501
|
+
"data-testid": "billing-close",
|
|
1379
1502
|
onClick: onClose,
|
|
1380
1503
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1381
1504
|
viewBox: "0 0 24 24",
|
|
@@ -1391,62 +1514,141 @@ window.__ModuleLoader__.load({
|
|
|
1391
1514
|
children: [
|
|
1392
1515
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1393
1516
|
className: UsageBilling_module_css_default.hero,
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
t("billing.calls")
|
|
1411
|
-
]
|
|
1412
|
-
})
|
|
1413
|
-
]
|
|
1414
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1415
|
-
className: UsageBilling_module_css_default.heroSide,
|
|
1416
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1417
|
-
className: UsageBilling_module_css_default.heroSideItem,
|
|
1418
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1419
|
-
className: UsageBilling_module_css_default.heroSideLabel,
|
|
1420
|
-
children: t("billing.yearCost")
|
|
1421
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1422
|
-
className: UsageBilling_module_css_default.heroSideValue,
|
|
1423
|
-
children: formatMoney(yearCost)
|
|
1424
|
-
})]
|
|
1425
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1426
|
-
className: UsageBilling_module_css_default.heroSideItem,
|
|
1427
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1428
|
-
className: UsageBilling_module_css_default.heroSideLabel,
|
|
1429
|
-
children: t("billing.todayCost")
|
|
1430
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1431
|
-
className: UsageBilling_module_css_default.heroSideValue,
|
|
1432
|
-
children: [formatMoney(todayCost), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1433
|
-
className: clsx(UsageBilling_module_css_default.delta, deltaPct >= 0 ? UsageBilling_module_css_default.deltaUp : UsageBilling_module_css_default.deltaDown),
|
|
1517
|
+
"data-testid": "billing-hero",
|
|
1518
|
+
children: [
|
|
1519
|
+
renderSlot("billing.dashboard.decor", { position: "hero" }),
|
|
1520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1521
|
+
className: UsageBilling_module_css_default.heroMain,
|
|
1522
|
+
children: [
|
|
1523
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1524
|
+
className: UsageBilling_module_css_default.heroLabel,
|
|
1525
|
+
children: t("billing.monthCost")
|
|
1526
|
+
}),
|
|
1527
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1528
|
+
className: UsageBilling_module_css_default.heroValue,
|
|
1529
|
+
children: formatMoney(monthCost)
|
|
1530
|
+
}),
|
|
1531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1532
|
+
className: UsageBilling_module_css_default.heroMeta,
|
|
1434
1533
|
children: [
|
|
1435
|
-
|
|
1534
|
+
monthCalls.toLocaleString(),
|
|
1436
1535
|
" ",
|
|
1437
|
-
|
|
1438
|
-
"%"
|
|
1536
|
+
t("billing.calls")
|
|
1439
1537
|
]
|
|
1538
|
+
})
|
|
1539
|
+
]
|
|
1540
|
+
}),
|
|
1541
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1542
|
+
className: UsageBilling_module_css_default.heroSide,
|
|
1543
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1544
|
+
className: UsageBilling_module_css_default.heroSideItem,
|
|
1545
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1546
|
+
className: UsageBilling_module_css_default.heroSideLabel,
|
|
1547
|
+
children: t("billing.yearCost")
|
|
1548
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1549
|
+
className: UsageBilling_module_css_default.heroSideValue,
|
|
1550
|
+
children: formatMoney(yearCost)
|
|
1551
|
+
})]
|
|
1552
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1553
|
+
className: UsageBilling_module_css_default.heroSideItem,
|
|
1554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1555
|
+
className: UsageBilling_module_css_default.heroSideLabel,
|
|
1556
|
+
children: t("billing.todayCost")
|
|
1557
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1558
|
+
className: UsageBilling_module_css_default.heroSideValue,
|
|
1559
|
+
children: [formatMoney(todayCost), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1560
|
+
className: clsx(UsageBilling_module_css_default.delta, deltaPct >= 0 ? UsageBilling_module_css_default.deltaUp : UsageBilling_module_css_default.deltaDown),
|
|
1561
|
+
children: [
|
|
1562
|
+
deltaPct >= 0 ? "▲" : "▼",
|
|
1563
|
+
" ",
|
|
1564
|
+
Math.abs(deltaPct).toFixed(1),
|
|
1565
|
+
"%"
|
|
1566
|
+
]
|
|
1567
|
+
})]
|
|
1440
1568
|
})]
|
|
1441
1569
|
})]
|
|
1570
|
+
})
|
|
1571
|
+
]
|
|
1572
|
+
}),
|
|
1573
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1574
|
+
className: UsageBilling_module_css_default.budget,
|
|
1575
|
+
"data-testid": "billing-budget",
|
|
1576
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1577
|
+
className: UsageBilling_module_css_default.budgetHead,
|
|
1578
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1579
|
+
className: UsageBilling_module_css_default.budgetLabel,
|
|
1580
|
+
children: t("billing.budget")
|
|
1581
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1582
|
+
className: UsageBilling_module_css_default.budgetControls,
|
|
1583
|
+
children: [
|
|
1584
|
+
budgetEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1585
|
+
className: UsageBilling_module_css_default.budgetInputWrap,
|
|
1586
|
+
"data-testid": "billing-budget-input-wrap",
|
|
1587
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1588
|
+
className: UsageBilling_module_css_default.budgetUnit,
|
|
1589
|
+
"aria-hidden": "true",
|
|
1590
|
+
children: "¥"
|
|
1591
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1592
|
+
className: UsageBilling_module_css_default.budgetInput,
|
|
1593
|
+
"data-testid": "billing-budget-input",
|
|
1594
|
+
type: "number",
|
|
1595
|
+
min: 0,
|
|
1596
|
+
step: 1,
|
|
1597
|
+
value: budgetAmount === 0 ? "" : budgetAmount,
|
|
1598
|
+
placeholder: stats.budget !== void 0 ? String(stats.budget) : "0",
|
|
1599
|
+
"aria-label": `${t("billing.budget")}(元)`,
|
|
1600
|
+
title: `${t("billing.budget")}(元)`,
|
|
1601
|
+
onChange: (e) => {
|
|
1602
|
+
onBudgetAmount(e.target.valueAsNumber);
|
|
1603
|
+
}
|
|
1604
|
+
})]
|
|
1605
|
+
}),
|
|
1606
|
+
budgetEnabled && budgetAmount > 0 && (() => {
|
|
1607
|
+
const pct = monthCost / budgetAmount * 100;
|
|
1608
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1609
|
+
className: UsageBilling_module_css_default.budgetValue,
|
|
1610
|
+
"data-testid": "billing-budget-value",
|
|
1611
|
+
children: [
|
|
1612
|
+
formatMoney(monthCost),
|
|
1613
|
+
" / ",
|
|
1614
|
+
formatMoney(budgetAmount),
|
|
1615
|
+
" · ",
|
|
1616
|
+
pct.toFixed(1),
|
|
1617
|
+
"%"
|
|
1618
|
+
]
|
|
1619
|
+
});
|
|
1620
|
+
})(),
|
|
1621
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1622
|
+
type: "button",
|
|
1623
|
+
role: "switch",
|
|
1624
|
+
"aria-checked": budgetEnabled,
|
|
1625
|
+
"aria-label": t("billing.budget"),
|
|
1626
|
+
"data-testid": "billing-budget-toggle",
|
|
1627
|
+
className: clsx(UsageBilling_module_css_default.switch, budgetEnabled && UsageBilling_module_css_default.switchOn),
|
|
1628
|
+
onClick: onToggleBudget,
|
|
1629
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.switchKnob })
|
|
1630
|
+
})
|
|
1631
|
+
]
|
|
1442
1632
|
})]
|
|
1443
|
-
})
|
|
1633
|
+
}), budgetEnabled && budgetAmount > 0 && (() => {
|
|
1634
|
+
const pct = monthCost / budgetAmount * 100;
|
|
1635
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1636
|
+
className: UsageBilling_module_css_default.budgetTrack,
|
|
1637
|
+
"data-testid": "billing-budget-track",
|
|
1638
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1639
|
+
className: clsx(UsageBilling_module_css_default.budgetFill, pct >= 100 && UsageBilling_module_css_default.budgetFillOver),
|
|
1640
|
+
style: { width: `${Math.min(pct, 100)}%` }
|
|
1641
|
+
})
|
|
1642
|
+
});
|
|
1643
|
+
})()]
|
|
1444
1644
|
}),
|
|
1445
1645
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1446
1646
|
className: UsageBilling_module_css_default.kpiGrid,
|
|
1647
|
+
"data-testid": "billing-kpi-grid",
|
|
1447
1648
|
children: [
|
|
1448
1649
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1449
1650
|
className: UsageBilling_module_css_default.kpiTile,
|
|
1651
|
+
"data-testid": "billing-kpi-tile",
|
|
1450
1652
|
children: [
|
|
1451
1653
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1452
1654
|
className: UsageBilling_module_css_default.kpiLabel,
|
|
@@ -1536,16 +1738,36 @@ window.__ModuleLoader__.load({
|
|
|
1536
1738
|
]
|
|
1537
1739
|
}),
|
|
1538
1740
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1539
|
-
className: UsageBilling_module_css_default.panel,
|
|
1741
|
+
className: clsx(UsageBilling_module_css_default.panel, UsageBilling_module_css_default.trendPanel),
|
|
1742
|
+
"data-testid": "billing-panel-trend",
|
|
1540
1743
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1541
1744
|
className: UsageBilling_module_css_default.panelHead,
|
|
1542
|
-
children: [
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1745
|
+
children: [
|
|
1746
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1747
|
+
className: UsageBilling_module_css_default.panelTitle,
|
|
1748
|
+
children: t("billing.trend")
|
|
1749
|
+
}),
|
|
1750
|
+
renderSlot("billing.dashboard.decor", { position: "trend" }),
|
|
1751
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1752
|
+
className: UsageBilling_module_css_default.rangeToggle,
|
|
1753
|
+
role: "group",
|
|
1754
|
+
"aria-label": t("billing.trend"),
|
|
1755
|
+
children: [7, 30].map((days) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1756
|
+
type: "button",
|
|
1757
|
+
className: clsx(UsageBilling_module_css_default.rangeButton, trendDays === days && UsageBilling_module_css_default.rangeButtonActive),
|
|
1758
|
+
"aria-pressed": trendDays === days,
|
|
1759
|
+
"data-testid": `billing-trend-${days}d`,
|
|
1760
|
+
onClick: () => {
|
|
1761
|
+
setTrendDays(days);
|
|
1762
|
+
},
|
|
1763
|
+
children: days === 7 ? t("billing.trend7d") : t("billing.trend30d")
|
|
1764
|
+
}, days))
|
|
1765
|
+
}),
|
|
1766
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1767
|
+
className: UsageBilling_module_css_default.panelHint,
|
|
1768
|
+
children: latestDate
|
|
1769
|
+
})
|
|
1770
|
+
]
|
|
1549
1771
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
|
|
1550
1772
|
data: trend,
|
|
1551
1773
|
models: chartModels
|
|
@@ -1553,62 +1775,171 @@ window.__ModuleLoader__.load({
|
|
|
1553
1775
|
}),
|
|
1554
1776
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1555
1777
|
className: UsageBilling_module_css_default.panel,
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
className: UsageBilling_module_css_default.
|
|
1560
|
-
children:
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1778
|
+
"data-testid": "billing-panel-models",
|
|
1779
|
+
children: [
|
|
1780
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1781
|
+
className: UsageBilling_module_css_default.panelHead,
|
|
1782
|
+
children: [
|
|
1783
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1784
|
+
className: UsageBilling_module_css_default.panelTitle,
|
|
1785
|
+
children: t("billing.models")
|
|
1786
|
+
}),
|
|
1787
|
+
renderSlot("billing.dashboard.decor", { position: "models" }),
|
|
1788
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1789
|
+
className: UsageBilling_module_css_default.panelHint,
|
|
1790
|
+
children: stats.updatedAt !== void 0 ? `${t("billing.lastUpdated")} ${formatClock(stats.updatedAt)}` : ""
|
|
1791
|
+
})
|
|
1792
|
+
]
|
|
1793
|
+
}),
|
|
1794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1795
|
+
className: clsx(UsageBilling_module_css_default.tableScroll, UsageBilling_module_css_default.modelTableScroll),
|
|
1796
|
+
"data-testid": "billing-table-scroll",
|
|
1797
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
1798
|
+
className: UsageBilling_module_css_default.modelTable,
|
|
1799
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1800
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.models") }),
|
|
1801
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1802
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1803
|
+
children: t("billing.calls")
|
|
1804
|
+
}),
|
|
1805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1806
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1807
|
+
children: t("billing.inputTokens")
|
|
1808
|
+
}),
|
|
1809
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1810
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1811
|
+
children: t("billing.outputTokens")
|
|
1812
|
+
}),
|
|
1813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1814
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1815
|
+
children: t("billing.cacheHitRate")
|
|
1816
|
+
}),
|
|
1817
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1818
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1819
|
+
children: t("billing.actual")
|
|
1820
|
+
}),
|
|
1821
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1822
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1823
|
+
children: t("billing.balance")
|
|
1824
|
+
})
|
|
1825
|
+
] }) }), /* @__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", {
|
|
1826
|
+
colSpan: 7,
|
|
1827
|
+
className: UsageBilling_module_css_default.emptyRow,
|
|
1828
|
+
children: t("billing.noData")
|
|
1829
|
+
}) }), modelRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1831
|
+
className: UsageBilling_module_css_default.modelCell,
|
|
1832
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1833
|
+
className: clsx(UsageBilling_module_css_default.modelDot, providerDot(health, row.provider)),
|
|
1834
|
+
"aria-hidden": "true"
|
|
1835
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1836
|
+
className: UsageBilling_module_css_default.modelName,
|
|
1837
|
+
children: [row.name, row.uncatalogued && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1838
|
+
className: UsageBilling_module_css_default.uncataloguedTag,
|
|
1839
|
+
"data-testid": "billing-uncatalogued-tag",
|
|
1840
|
+
children: t("billing.uncatalogued")
|
|
1841
|
+
})]
|
|
1842
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1843
|
+
className: UsageBilling_module_css_default.modelProvider,
|
|
1844
|
+
children: row.provider
|
|
1845
|
+
})] })]
|
|
1846
|
+
}) }),
|
|
1847
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1848
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1849
|
+
children: row.calls.toLocaleString()
|
|
1850
|
+
}),
|
|
1851
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1852
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1853
|
+
children: formatTokens(row.input)
|
|
1854
|
+
}),
|
|
1855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1856
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1857
|
+
children: formatTokens(row.output)
|
|
1858
|
+
}),
|
|
1859
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1860
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1861
|
+
children: formatPercent(row.cacheHitRate)
|
|
1862
|
+
}),
|
|
1863
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1864
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1865
|
+
children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1866
|
+
className: UsageBilling_module_css_default.planTag,
|
|
1867
|
+
children: "订阅包含"
|
|
1868
|
+
}) : row.actual !== void 0 ? formatMoney(row.actual) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1869
|
+
className: UsageBilling_module_css_default.na,
|
|
1870
|
+
children: "—"
|
|
1871
|
+
})
|
|
1872
|
+
}),
|
|
1873
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1874
|
+
className: UsageBilling_module_css_default.numCol,
|
|
1875
|
+
children: renderBalance(balanceFor(row.provider))
|
|
1876
|
+
})
|
|
1877
|
+
] }, row.key))] })]
|
|
1878
|
+
})
|
|
1879
|
+
}),
|
|
1880
|
+
renderSlot("billing.dashboard.decor", { position: "footer" })
|
|
1881
|
+
]
|
|
1882
|
+
}),
|
|
1883
|
+
stats.bySession !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1884
|
+
className: UsageBilling_module_css_default.panel,
|
|
1885
|
+
"data-testid": "billing-panel-sessions",
|
|
1886
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1887
|
+
type: "button",
|
|
1888
|
+
className: UsageBilling_module_css_default.pricingToggle,
|
|
1889
|
+
"data-testid": "billing-sessions-toggle",
|
|
1890
|
+
onClick: () => {
|
|
1891
|
+
setSessionsOpen((prev) => !prev);
|
|
1892
|
+
},
|
|
1893
|
+
"aria-expanded": sessionsOpen,
|
|
1894
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1895
|
+
className: UsageBilling_module_css_default.pricingToggleText,
|
|
1896
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1897
|
+
className: UsageBilling_module_css_default.panelTitle,
|
|
1898
|
+
children: t("billing.sessions")
|
|
1899
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1900
|
+
className: UsageBilling_module_css_default.panelHint,
|
|
1901
|
+
children: stats.bySession.length > SESSION_DISPLAY_LIMIT ? t("billing.sessionOverflow").replace("{limit}", String(SESSION_DISPLAY_LIMIT)).replace("{total}", String(stats.bySession.length)) : `${stats.bySession.length}`
|
|
1902
|
+
})]
|
|
1903
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1904
|
+
className: clsx(UsageBilling_module_css_default.pricingChevron, sessionsOpen && UsageBilling_module_css_default.pricingChevronOpen),
|
|
1905
|
+
viewBox: "0 0 24 24",
|
|
1906
|
+
fill: "none",
|
|
1907
|
+
stroke: "currentColor",
|
|
1908
|
+
"aria-hidden": "true",
|
|
1909
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
|
|
1564
1910
|
})]
|
|
1565
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1911
|
+
}), sessionsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1566
1912
|
className: UsageBilling_module_css_default.tableScroll,
|
|
1913
|
+
"data-testid": "billing-sessions-table",
|
|
1567
1914
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
1568
1915
|
className: UsageBilling_module_css_default.modelTable,
|
|
1569
1916
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1570
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.
|
|
1917
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.sessions") }),
|
|
1918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.project") }),
|
|
1571
1919
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1572
1920
|
className: UsageBilling_module_css_default.numCol,
|
|
1573
1921
|
children: t("billing.calls")
|
|
1574
1922
|
}),
|
|
1575
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1576
|
-
className: UsageBilling_module_css_default.numCol,
|
|
1577
|
-
children: t("billing.inputTokens")
|
|
1578
|
-
}),
|
|
1579
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1580
|
-
className: UsageBilling_module_css_default.numCol,
|
|
1581
|
-
children: t("billing.outputTokens")
|
|
1582
|
-
}),
|
|
1583
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1584
|
-
className: UsageBilling_module_css_default.numCol,
|
|
1585
|
-
children: t("billing.cacheHitRate")
|
|
1586
|
-
}),
|
|
1587
1923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1588
1924
|
className: UsageBilling_module_css_default.numCol,
|
|
1589
1925
|
children: t("billing.actual")
|
|
1590
1926
|
}),
|
|
1591
1927
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
1592
1928
|
className: UsageBilling_module_css_default.numCol,
|
|
1593
|
-
children: t("billing.
|
|
1929
|
+
children: t("billing.lastActive")
|
|
1594
1930
|
})
|
|
1595
|
-
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [
|
|
1596
|
-
colSpan:
|
|
1931
|
+
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [stats.bySession.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1932
|
+
colSpan: 5,
|
|
1597
1933
|
className: UsageBilling_module_css_default.emptyRow,
|
|
1598
1934
|
children: t("billing.noData")
|
|
1599
|
-
}) }),
|
|
1600
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1601
|
-
className: UsageBilling_module_css_default.
|
|
1602
|
-
children:
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
children: row.name
|
|
1608
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1609
|
-
className: UsageBilling_module_css_default.modelProvider,
|
|
1610
|
-
children: row.provider
|
|
1611
|
-
})] })]
|
|
1935
|
+
}) }), stats.bySession.slice(0, SESSION_DISPLAY_LIMIT).map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1936
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1937
|
+
className: UsageBilling_module_css_default.modelName,
|
|
1938
|
+
children: row.title ?? row.id.slice(0, 8)
|
|
1939
|
+
}) }),
|
|
1940
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1941
|
+
className: UsageBilling_module_css_default.modelProvider,
|
|
1942
|
+
children: projectName(row.cwd) ?? "—"
|
|
1612
1943
|
}) }),
|
|
1613
1944
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1614
1945
|
className: UsageBilling_module_css_default.numCol,
|
|
@@ -1616,39 +1947,23 @@ window.__ModuleLoader__.load({
|
|
|
1616
1947
|
}),
|
|
1617
1948
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1618
1949
|
className: UsageBilling_module_css_default.numCol,
|
|
1619
|
-
children:
|
|
1950
|
+
children: formatMoney(row.cost)
|
|
1620
1951
|
}),
|
|
1621
1952
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1622
1953
|
className: UsageBilling_module_css_default.numCol,
|
|
1623
|
-
children:
|
|
1624
|
-
}),
|
|
1625
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1626
|
-
className: UsageBilling_module_css_default.numCol,
|
|
1627
|
-
children: formatPercent(row.cacheHitRate)
|
|
1628
|
-
}),
|
|
1629
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1630
|
-
className: UsageBilling_module_css_default.numCol,
|
|
1631
|
-
children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1632
|
-
className: UsageBilling_module_css_default.planTag,
|
|
1633
|
-
children: "订阅包含"
|
|
1634
|
-
}) : row.actual !== void 0 ? formatMoney(row.actual) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1635
|
-
className: UsageBilling_module_css_default.na,
|
|
1636
|
-
children: "—"
|
|
1637
|
-
})
|
|
1638
|
-
}),
|
|
1639
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1640
|
-
className: UsageBilling_module_css_default.numCol,
|
|
1641
|
-
children: renderBalance(balanceFor(row.provider))
|
|
1954
|
+
children: row.lastActive > 0 ? `${localDayStamp(row.lastActive)} ${formatClock(row.lastActive)}` : "—"
|
|
1642
1955
|
})
|
|
1643
|
-
] }, row.
|
|
1956
|
+
] }, row.id))] })]
|
|
1644
1957
|
})
|
|
1645
1958
|
})]
|
|
1646
1959
|
}),
|
|
1647
1960
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1648
1961
|
className: UsageBilling_module_css_default.panel,
|
|
1962
|
+
"data-testid": "billing-panel-pricing",
|
|
1649
1963
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1650
1964
|
type: "button",
|
|
1651
1965
|
className: UsageBilling_module_css_default.pricingToggle,
|
|
1966
|
+
"data-testid": "billing-pricing-toggle",
|
|
1652
1967
|
onClick: () => {
|
|
1653
1968
|
setPricingOpen((prev) => !prev);
|
|
1654
1969
|
},
|
|
@@ -1769,7 +2084,7 @@ window.__ModuleLoader__.load({
|
|
|
1769
2084
|
* @param props - framework-provided sidebar and locale props.
|
|
1770
2085
|
*/
|
|
1771
2086
|
function UsageBilling(props) {
|
|
1772
|
-
const { t, checkModels } = props;
|
|
2087
|
+
const { t, checkModels, publishCosts, registerOpen, renderSlot, useStore, actions } = props;
|
|
1773
2088
|
const [stats, setStats] = (0, react.useState)(EMPTY_STATS);
|
|
1774
2089
|
const [health, setHealth] = (0, react.useState)(IDLE_HEALTH);
|
|
1775
2090
|
const [balances, setBalances] = (0, react.useState)([]);
|
|
@@ -1814,17 +2129,102 @@ window.__ModuleLoader__.load({
|
|
|
1814
2129
|
}, [checkModels]);
|
|
1815
2130
|
const today = localDayStamp();
|
|
1816
2131
|
const monthCost = Object.entries(stats.byDay).filter(([date]) => date.startsWith(today.slice(0, 7))).reduce((sum, [, day]) => sum + day.cost, 0);
|
|
2132
|
+
const todayCost = stats.byDay[today]?.cost ?? 0;
|
|
2133
|
+
const budgetEnabled = useStore((s) => s.enabled);
|
|
2134
|
+
const budgetAmount = useStore((s) => s.amount);
|
|
2135
|
+
const budgetAlertedDay = useStore((s) => s.lastAlertDay);
|
|
2136
|
+
const effectiveBudget = budgetAmount > 0 ? budgetAmount : stats.budget ?? 0;
|
|
2137
|
+
const toggleBudget = (0, react.useCallback)(() => {
|
|
2138
|
+
const next = !budgetEnabled;
|
|
2139
|
+
actions.setEnabled(next);
|
|
2140
|
+
if (next && typeof Notification !== "undefined" && Notification.permission === "default") Notification.requestPermission();
|
|
2141
|
+
}, [actions, budgetEnabled]);
|
|
2142
|
+
(0, react.useEffect)(() => {
|
|
2143
|
+
if (!budgetEnabled || effectiveBudget <= 0) return;
|
|
2144
|
+
const pct = monthCost / effectiveBudget * 100;
|
|
2145
|
+
if (pct < 100) return;
|
|
2146
|
+
const day = localDayStamp();
|
|
2147
|
+
if (budgetAlertedDay === day) return;
|
|
2148
|
+
actions.markAlerted(day);
|
|
2149
|
+
if (typeof Notification === "undefined" || Notification.permission !== "granted") return;
|
|
2150
|
+
const body = t("billing.budgetOverBody").replace("{cost}", formatMoney(monthCost)).replace("{budget}", formatMoney(effectiveBudget)).replace("{pct}", pct.toFixed(0));
|
|
2151
|
+
try {
|
|
2152
|
+
new Notification(t("billing.budget"), { body });
|
|
2153
|
+
} catch {}
|
|
2154
|
+
}, [
|
|
2155
|
+
budgetEnabled,
|
|
2156
|
+
effectiveBudget,
|
|
2157
|
+
monthCost,
|
|
2158
|
+
budgetAlertedDay,
|
|
2159
|
+
actions,
|
|
2160
|
+
t
|
|
2161
|
+
]);
|
|
2162
|
+
const lastBalanceAlertDay = useStore((s) => s.lastBalanceAlertDay);
|
|
2163
|
+
const lowBalanceRow = (0, react.useMemo)(() => {
|
|
2164
|
+
if (balances.length === 0) return void 0;
|
|
2165
|
+
const threshold = stats.lowBalanceThreshold ?? DEFAULT_LOW_BALANCE_THRESHOLD;
|
|
2166
|
+
const burn = dailyBurnRate(stats.byDay, today);
|
|
2167
|
+
const rate = getRateInfo().rate;
|
|
2168
|
+
for (const balance of balances) {
|
|
2169
|
+
if (balance.totalBalance === void 0 || balance.error !== void 0) continue;
|
|
2170
|
+
const cny = balance.currency === "USD" ? balance.totalBalance * rate : balance.totalBalance;
|
|
2171
|
+
if (cny >= threshold) continue;
|
|
2172
|
+
const days = burn > 0 ? Math.floor(cny / burn) : void 0;
|
|
2173
|
+
return {
|
|
2174
|
+
name: balance.displayName,
|
|
2175
|
+
cny,
|
|
2176
|
+
days
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
}, [
|
|
2180
|
+
balances,
|
|
2181
|
+
stats.lowBalanceThreshold,
|
|
2182
|
+
stats.byDay,
|
|
2183
|
+
today
|
|
2184
|
+
]);
|
|
2185
|
+
(0, react.useEffect)(() => {
|
|
2186
|
+
if (lowBalanceRow === void 0) return;
|
|
2187
|
+
const day = localDayStamp();
|
|
2188
|
+
if (lastBalanceAlertDay === day) return;
|
|
2189
|
+
actions.markBalanceAlerted(day);
|
|
2190
|
+
if (typeof Notification === "undefined" || Notification.permission !== "granted") return;
|
|
2191
|
+
const body = t("billing.balanceLowBody").replace("{name}", lowBalanceRow.name).replace("{balance}", formatMoney(lowBalanceRow.cny)).replace("{days}", lowBalanceRow.days === void 0 ? "—" : String(lowBalanceRow.days));
|
|
2192
|
+
try {
|
|
2193
|
+
new Notification(t("billing.balance"), { body });
|
|
2194
|
+
} catch {}
|
|
2195
|
+
}, [
|
|
2196
|
+
lowBalanceRow,
|
|
2197
|
+
lastBalanceAlertDay,
|
|
2198
|
+
actions,
|
|
2199
|
+
t
|
|
2200
|
+
]);
|
|
2201
|
+
(0, react.useEffect)(() => {
|
|
2202
|
+
publishCosts({
|
|
2203
|
+
todayCost,
|
|
2204
|
+
monthCost
|
|
2205
|
+
});
|
|
2206
|
+
}, [
|
|
2207
|
+
todayCost,
|
|
2208
|
+
monthCost,
|
|
2209
|
+
publishCosts
|
|
2210
|
+
]);
|
|
2211
|
+
(0, react.useEffect)(() => registerOpen(openDashboard), [registerOpen, openDashboard]);
|
|
1817
2212
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBillingTrigger, {
|
|
1818
2213
|
...props,
|
|
1819
2214
|
onOpen: openDashboard,
|
|
1820
2215
|
monthCost,
|
|
1821
|
-
todayCost
|
|
2216
|
+
todayCost
|
|
1822
2217
|
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BillingDashboard, {
|
|
1823
2218
|
stats,
|
|
1824
2219
|
t,
|
|
1825
2220
|
onClose: close,
|
|
1826
2221
|
health,
|
|
1827
|
-
balances
|
|
2222
|
+
balances,
|
|
2223
|
+
renderSlot,
|
|
2224
|
+
budgetEnabled,
|
|
2225
|
+
budgetAmount: effectiveBudget,
|
|
2226
|
+
onToggleBudget: toggleBudget,
|
|
2227
|
+
onBudgetAmount: actions.setAmount
|
|
1828
2228
|
})] });
|
|
1829
2229
|
}
|
|
1830
2230
|
//#endregion
|
|
@@ -1845,7 +2245,15 @@ window.__ModuleLoader__.load({
|
|
|
1845
2245
|
"billing.outputTokens": "输出",
|
|
1846
2246
|
"billing.avgCost": "平均成本",
|
|
1847
2247
|
"billing.trend": "每日费用与调用趋势",
|
|
2248
|
+
"billing.trend7d": "7 天",
|
|
2249
|
+
"billing.trend30d": "30 天",
|
|
1848
2250
|
"billing.trendEmpty": "暂无趋势数据",
|
|
2251
|
+
"billing.budget": "本月预算",
|
|
2252
|
+
"billing.sessions": "会话明细",
|
|
2253
|
+
"billing.project": "项目",
|
|
2254
|
+
"billing.lastActive": "最后活跃",
|
|
2255
|
+
"billing.sessionOverflow": "仅显示花费前 {limit} 个,共 {total} 个会话",
|
|
2256
|
+
"billing.budgetOverBody": "本月花费 {cost} 已超过预算 {budget}({pct}%)",
|
|
1849
2257
|
"billing.models": "模型计费明细",
|
|
1850
2258
|
"billing.estimated": "估算",
|
|
1851
2259
|
"billing.actual": "实际",
|
|
@@ -1871,7 +2279,10 @@ window.__ModuleLoader__.load({
|
|
|
1871
2279
|
"billing.balance": "余额",
|
|
1872
2280
|
"billing.balanceUnconfigured": "未配置",
|
|
1873
2281
|
"billing.balanceUnauthorized": "密钥无效",
|
|
1874
|
-
"billing.balanceUnreachable": "查询失败"
|
|
2282
|
+
"billing.balanceUnreachable": "查询失败",
|
|
2283
|
+
"billing.uncatalogued": "未收录",
|
|
2284
|
+
"billing.balanceDays": "约可撑 {days} 天",
|
|
2285
|
+
"billing.balanceLowBody": "{name} 余额 {balance},约可撑 {days} 天,请及时充值"
|
|
1875
2286
|
};
|
|
1876
2287
|
const en = {
|
|
1877
2288
|
"billing.title": "Usage",
|
|
@@ -1888,7 +2299,15 @@ window.__ModuleLoader__.load({
|
|
|
1888
2299
|
"billing.outputTokens": "Output",
|
|
1889
2300
|
"billing.avgCost": "Avg cost",
|
|
1890
2301
|
"billing.trend": "Daily cost & calls",
|
|
2302
|
+
"billing.trend7d": "7D",
|
|
2303
|
+
"billing.trend30d": "30D",
|
|
1891
2304
|
"billing.trendEmpty": "No trend data yet",
|
|
2305
|
+
"billing.budget": "Monthly budget",
|
|
2306
|
+
"billing.sessions": "Sessions",
|
|
2307
|
+
"billing.project": "Project",
|
|
2308
|
+
"billing.lastActive": "Last active",
|
|
2309
|
+
"billing.sessionOverflow": "Top {limit} of {total} sessions by cost",
|
|
2310
|
+
"billing.budgetOverBody": "This month {cost} exceeded the budget {budget} ({pct}%)",
|
|
1892
2311
|
"billing.models": "Model billing",
|
|
1893
2312
|
"billing.estimated": "Est.",
|
|
1894
2313
|
"billing.actual": "Actual",
|
|
@@ -1914,9 +2333,81 @@ window.__ModuleLoader__.load({
|
|
|
1914
2333
|
"billing.balance": "Balance",
|
|
1915
2334
|
"billing.balanceUnconfigured": "Not set",
|
|
1916
2335
|
"billing.balanceUnauthorized": "Bad key",
|
|
1917
|
-
"billing.balanceUnreachable": "Unavailable"
|
|
2336
|
+
"billing.balanceUnreachable": "Unavailable",
|
|
2337
|
+
"billing.uncatalogued": "Not catalogued",
|
|
2338
|
+
"billing.balanceDays": "~{days} days left",
|
|
2339
|
+
"billing.balanceLowBody": "{name} balance {balance}, ~{days} days left, please top up"
|
|
1918
2340
|
};
|
|
1919
2341
|
//#endregion
|
|
2342
|
+
//#region src/client/billing-service.ts
|
|
2343
|
+
/** 创建计费指标运行时(apply 内调用,随插件纤维存活)。 */
|
|
2344
|
+
function createBillingMetrics() {
|
|
2345
|
+
let costs;
|
|
2346
|
+
let open;
|
|
2347
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2348
|
+
return {
|
|
2349
|
+
readCosts: () => costs,
|
|
2350
|
+
subscribeCosts: (listener) => {
|
|
2351
|
+
listener(costs);
|
|
2352
|
+
listeners.add(listener);
|
|
2353
|
+
return () => {
|
|
2354
|
+
listeners.delete(listener);
|
|
2355
|
+
};
|
|
2356
|
+
},
|
|
2357
|
+
openDashboard: () => {
|
|
2358
|
+
open?.();
|
|
2359
|
+
},
|
|
2360
|
+
publishCosts: (next) => {
|
|
2361
|
+
costs = next;
|
|
2362
|
+
for (const listener of listeners) listener(next);
|
|
2363
|
+
},
|
|
2364
|
+
registerOpen: (handler) => {
|
|
2365
|
+
open = handler;
|
|
2366
|
+
return () => {
|
|
2367
|
+
if (open === handler) open = void 0;
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
//#endregion
|
|
2373
|
+
//#region src/client/budget-store.ts
|
|
2374
|
+
/**
|
|
2375
|
+
* 预算偏好 store:本月预算的开关与金额。
|
|
2376
|
+
*
|
|
2377
|
+
* 用户在仪表盘里用开关控制预算条显隐、用数字输入框设置金额;状态经框架
|
|
2378
|
+
* store 引擎持久化到 localStorage(persist key 即存储身份),重启后保留。
|
|
2379
|
+
* 宿主 Config 的 monthlyBudget 仅作为金额未设置时的默认值,用户输入优先。
|
|
2380
|
+
*/
|
|
2381
|
+
/**
|
|
2382
|
+
* Declare the budget-preferences store handle.
|
|
2383
|
+
* @returns the store handle for the register call's store seat.
|
|
2384
|
+
*/
|
|
2385
|
+
function createBillingBudgetStore() {
|
|
2386
|
+
return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
|
|
2387
|
+
init: () => ({
|
|
2388
|
+
enabled: false,
|
|
2389
|
+
amount: 0,
|
|
2390
|
+
lastAlertDay: "",
|
|
2391
|
+
lastBalanceAlertDay: ""
|
|
2392
|
+
}),
|
|
2393
|
+
persist: "dsh.ui-usage-billing.budget",
|
|
2394
|
+
actions: {
|
|
2395
|
+
setEnabled: (d, on) => {
|
|
2396
|
+
d.enabled = on;
|
|
2397
|
+
},
|
|
2398
|
+
setAmount: (d, value) => {
|
|
2399
|
+
d.amount = Number.isFinite(value) && value > 0 ? value : 0;
|
|
2400
|
+
},
|
|
2401
|
+
markAlerted: (d, day) => {
|
|
2402
|
+
d.lastAlertDay = day;
|
|
2403
|
+
},
|
|
2404
|
+
markBalanceAlerted: (d, day) => {
|
|
2405
|
+
d.lastBalanceAlertDay = day;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
//#endregion
|
|
1920
2411
|
//#region src/client/apply.ts
|
|
1921
2412
|
/** Required services for the usage billing surface. */
|
|
1922
2413
|
const inject = [
|
|
@@ -1929,6 +2420,9 @@ window.__ModuleLoader__.load({
|
|
|
1929
2420
|
* @param ctx - client root context.
|
|
1930
2421
|
*/
|
|
1931
2422
|
function apply(ctx) {
|
|
2423
|
+
const metrics = createBillingMetrics();
|
|
2424
|
+
ctx.provide("billingMetrics", metrics);
|
|
2425
|
+
const budgetStore = createBillingBudgetStore();
|
|
1932
2426
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
1933
2427
|
zh,
|
|
1934
2428
|
en
|
|
@@ -1938,36 +2432,47 @@ window.__ModuleLoader__.load({
|
|
|
1938
2432
|
id: "usage-billing",
|
|
1939
2433
|
order: -10,
|
|
1940
2434
|
locale: NS,
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2435
|
+
children: { "billing.dashboard.decor": {
|
|
2436
|
+
kind: "list",
|
|
2437
|
+
scope: "root"
|
|
2438
|
+
} },
|
|
2439
|
+
store: budgetStore,
|
|
2440
|
+
inject: () => ({
|
|
2441
|
+
checkModels: async () => {
|
|
2442
|
+
try {
|
|
2443
|
+
const { result } = await ctx.connection.api.llm.models({});
|
|
2444
|
+
if (!result.ok) return {
|
|
2445
|
+
checked: true,
|
|
2446
|
+
available: false,
|
|
2447
|
+
providers: 0,
|
|
2448
|
+
failures: 0,
|
|
2449
|
+
okProviders: [],
|
|
2450
|
+
badProviders: []
|
|
2451
|
+
};
|
|
2452
|
+
return {
|
|
2453
|
+
checked: true,
|
|
2454
|
+
available: result.value.groups.length > 0,
|
|
2455
|
+
providers: result.value.groups.length,
|
|
2456
|
+
failures: result.value.failures.length,
|
|
2457
|
+
okProviders: result.value.groups.map((group) => group.name),
|
|
2458
|
+
badProviders: result.value.failures.map((failure) => failure.name)
|
|
2459
|
+
};
|
|
2460
|
+
} catch {
|
|
2461
|
+
return {
|
|
2462
|
+
checked: true,
|
|
2463
|
+
available: false,
|
|
2464
|
+
providers: 0,
|
|
2465
|
+
failures: 0,
|
|
2466
|
+
okProviders: [],
|
|
2467
|
+
badProviders: []
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
},
|
|
2471
|
+
publishCosts: (costs) => {
|
|
2472
|
+
metrics.publishCosts(costs);
|
|
2473
|
+
},
|
|
2474
|
+
registerOpen: (handler) => metrics.registerOpen(handler)
|
|
2475
|
+
})
|
|
1971
2476
|
}, UsageBilling));
|
|
1972
2477
|
}
|
|
1973
2478
|
//#endregion
|