@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/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
- "triggerAmount": "VWh0dG_triggerAmount",
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
- "heroSideItem": "VWh0dG_heroSideItem",
39
+ "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
40
+ "chartLegendBar": "VWh0dG_chartLegendBar",
41
+ "numCol": "VWh0dG_numCol",
42
+ "budgetFill": "VWh0dG_budgetFill",
46
43
  "modelDot": "VWh0dG_modelDot",
47
- "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
48
- "kpiValue": "VWh0dG_kpiValue",
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
- "heroMain": "VWh0dG_heroMain",
55
- "healthBad": "VWh0dG_healthBad",
56
- "rateBadge": "VWh0dG_rateBadge",
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
- "triggerDivider": "VWh0dG_triggerDivider",
59
- "bandTagOff": "VWh0dG_bandTagOff",
60
- "triggerToday": "VWh0dG_triggerToday",
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
- "modelProvider": "VWh0dG_modelProvider",
63
- "chartEmpty": "VWh0dG_chartEmpty",
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
- "deltaUp": "VWh0dG_deltaUp",
66
- "chartCrosshair": "VWh0dG_chartCrosshair",
67
- "chartTooltipRow": "VWh0dG_chartTooltipRow",
68
- "chartLegendBar": "VWh0dG_chartLegendBar",
69
- "triggerIcon": "VWh0dG_triggerIcon",
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
- "chartStack": "VWh0dG_chartStack",
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
- "modelName": "VWh0dG_modelName",
74
- "chartAxisLabel": "VWh0dG_chartAxisLabel",
75
- "heroSide": "VWh0dG_heroSide",
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
- "dashboardBody": "VWh0dG_dashboardBody",
83
- "chartLegendLine": "VWh0dG_chartLegendLine",
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
- "hero": "VWh0dG_hero",
101
- "chartTooltipDate": "VWh0dG_chartTooltipDate",
102
- "modelCell": "VWh0dG_modelCell",
103
- "planTag": "VWh0dG_planTag",
104
- "costCol": "VWh0dG_costCol",
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
- "dashboardRight": "VWh0dG_dashboardRight",
116
- "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
117
- "pricingToggle": "VWh0dG_pricingToggle",
118
- "delta": "VWh0dG_delta",
119
- "dashboardModal": "VWh0dG_dashboardModal",
120
- "bandPrice": "VWh0dG_bandPrice",
121
- "chartSvg": "VWh0dG_chartSvg",
122
- "panelHint": "VWh0dG_panelHint"
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 GROUPED per model — one bar per model per day, each in its
727
- * brand color, so per-model cost is directly comparable. The blue line is the
728
- * total call volume across all models, plotted on its own right-hand axis.
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 grouped cost bars plus the total-calls line.
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.flatMap((d) => Object.values(d.byModel ?? {})), ...data.map((d) => d.cost), 1e-4);
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 barCount = Math.max(models.length, 1);
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: inner(i) - barW / 2,
801
- value: d.cost
825
+ x,
826
+ base: 0,
827
+ value: d.cost,
828
+ topRounded: true
802
829
  }];
803
- return models.map((model, k) => ({
804
- date: d.date,
805
- model,
806
- x: inner(i) - groupW / 2 + groupW * (k + .5) / models.length - barW / 2,
807
- value: d.byModel?.[model.key] ?? 0
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(0) - yCost(bar.value),
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
- return balance.currency === "USD" ? `$${balance.totalBalance.toFixed(2)}` : formatMoney(balance.totalBalance);
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 = 6; offset >= 0; offset -= 1) {
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: isSubscriptionPlan(key),
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
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1358
- className: UsageBilling_module_css_default.dashboardTitle,
1359
- children: t("billing.title")
1360
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1361
- className: UsageBilling_module_css_default.dashboardSubtitle,
1362
- children: [
1363
- t("billing.lastUpdated"),
1364
- " ",
1365
- latestDate
1366
- ]
1367
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
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
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1395
- className: UsageBilling_module_css_default.heroMain,
1396
- children: [
1397
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1398
- className: UsageBilling_module_css_default.heroLabel,
1399
- children: t("billing.monthCost")
1400
- }),
1401
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1402
- className: UsageBilling_module_css_default.heroValue,
1403
- children: formatMoney(monthCost)
1404
- }),
1405
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1406
- className: UsageBilling_module_css_default.heroMeta,
1407
- children: [
1408
- monthCalls.toLocaleString(),
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
- deltaPct >= 0 ? "▲" : "▼",
1534
+ monthCalls.toLocaleString(),
1436
1535
  " ",
1437
- Math.abs(deltaPct).toFixed(1),
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: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1543
- className: UsageBilling_module_css_default.panelTitle,
1544
- children: t("billing.trend")
1545
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1546
- className: UsageBilling_module_css_default.panelHint,
1547
- children: latestDate
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
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1557
- className: UsageBilling_module_css_default.panelHead,
1558
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1559
- className: UsageBilling_module_css_default.panelTitle,
1560
- children: t("billing.models")
1561
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1562
- className: UsageBilling_module_css_default.panelHint,
1563
- children: stats.updatedAt !== void 0 ? `${t("billing.lastUpdated")} ${formatClock(stats.updatedAt)}` : ""
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.models") }),
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.balance")
1929
+ children: t("billing.lastActive")
1594
1930
  })
1595
- ] }) }), /* @__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", {
1596
- colSpan: 7,
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
- }) }), modelRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1601
- className: UsageBilling_module_css_default.modelCell,
1602
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1603
- className: clsx(UsageBilling_module_css_default.modelDot, providerDot(health, row.provider)),
1604
- "aria-hidden": "true"
1605
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1606
- className: UsageBilling_module_css_default.modelName,
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: formatTokens(row.input)
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: formatTokens(row.output)
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.key))] })]
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: stats.byDay[today]?.cost ?? 0
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
- inject: () => ({ checkModels: async () => {
1942
- try {
1943
- const { result } = await ctx.connection.api.llm.models({});
1944
- if (!result.ok) return {
1945
- checked: true,
1946
- available: false,
1947
- providers: 0,
1948
- failures: 0,
1949
- okProviders: [],
1950
- badProviders: []
1951
- };
1952
- return {
1953
- checked: true,
1954
- available: result.value.groups.length > 0,
1955
- providers: result.value.groups.length,
1956
- failures: result.value.failures.length,
1957
- okProviders: result.value.groups.map((group) => group.name),
1958
- badProviders: result.value.failures.map((failure) => failure.name)
1959
- };
1960
- } catch {
1961
- return {
1962
- checked: true,
1963
- available: false,
1964
- providers: 0,
1965
- failures: 0,
1966
- okProviders: [],
1967
- badProviders: []
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