@kenz1117/dsh-ui-usage-billing 0.2.6 → 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
- "chartAxisLabel": "VWh0dG_chartAxisLabel",
37
- "delta": "VWh0dG_delta",
38
- "heroValue": "VWh0dG_heroValue",
39
- "emptyRow": "VWh0dG_emptyRow",
40
- "chartLegendBar": "VWh0dG_chartLegendBar",
41
- "na": "VWh0dG_na",
42
- "triggerIcon": "VWh0dG_triggerIcon",
43
- "chartBar": "VWh0dG_chartBar",
44
37
  "chartGrid": "VWh0dG_chartGrid",
38
+ "pricingTable": "VWh0dG_pricingTable",
39
+ "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
40
+ "chartLegendBar": "VWh0dG_chartLegendBar",
41
+ "numCol": "VWh0dG_numCol",
42
+ "budgetFill": "VWh0dG_budgetFill",
43
+ "modelDot": "VWh0dG_modelDot",
44
+ "uncataloguedTag": "VWh0dG_uncataloguedTag",
45
+ "trendPanel": "VWh0dG_trendPanel",
45
46
  "kpiTile": "VWh0dG_kpiTile",
46
- "dashboard": "VWh0dG_dashboard",
47
- "heroSideLabel": "VWh0dG_heroSideLabel",
48
- "triggerDivider": "VWh0dG_triggerDivider",
49
- "dashboardModal": "VWh0dG_dashboardModal",
47
+ "healthDot": "VWh0dG_healthDot",
48
+ "triggerMeta": "VWh0dG_triggerMeta",
50
49
  "heroSide": "VWh0dG_heroSide",
51
- "chartDot": "VWh0dG_chartDot",
52
- "healthOk": "VWh0dG_healthOk",
53
- "deltaUp": "VWh0dG_deltaUp",
54
- "chartSvg": "VWh0dG_chartSvg",
55
- "chartLegend": "VWh0dG_chartLegend",
56
- "kpiGrid": "VWh0dG_kpiGrid",
57
- "pricingChevron": "VWh0dG_pricingChevron",
58
- "chartLegendLine": "VWh0dG_chartLegendLine",
59
- "chartStack": "VWh0dG_chartStack",
60
- "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
50
+ "tableScroll": "VWh0dG_tableScroll",
51
+ "dashboardRight": "VWh0dG_dashboardRight",
52
+ "modelProvider": "VWh0dG_modelProvider",
61
53
  "costCol": "VWh0dG_costCol",
62
- "heroLabel": "VWh0dG_heroLabel",
63
- "kpiValue": "VWh0dG_kpiValue",
64
- "heroMain": "VWh0dG_heroMain",
65
- "triggerMonth": "VWh0dG_triggerMonth",
66
- "rateBadge": "VWh0dG_rateBadge",
67
- "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
68
- "heroSideValue": "VWh0dG_heroSideValue",
69
- "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
70
- "dashboardHead": "VWh0dG_dashboardHead",
71
- "panelHead": "VWh0dG_panelHead",
72
- "kpiDetail": "VWh0dG_kpiDetail",
73
- "trigger": "VWh0dG_trigger",
74
- "chartWrap": "VWh0dG_chartWrap",
75
- "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
76
- "healthDot": "VWh0dG_healthDot",
77
- "triggerAmount": "VWh0dG_triggerAmount",
78
- "healthBad": "VWh0dG_healthBad",
79
- "healthBadgeBad": "VWh0dG_healthBadgeBad",
80
- "heroSideItem": "VWh0dG_heroSideItem",
54
+ "chartStack": "VWh0dG_chartStack",
55
+ "chartLine": "VWh0dG_chartLine",
81
56
  "kpiGreen": "VWh0dG_kpiGreen",
82
- "panelTitle": "VWh0dG_panelTitle",
57
+ "dashboardModal": "VWh0dG_dashboardModal",
58
+ "trigger": "VWh0dG_trigger",
59
+ "rangeToggle": "VWh0dG_rangeToggle",
60
+ "budgetInputWrap": "VWh0dG_budgetInputWrap",
83
61
  "modelName": "VWh0dG_modelName",
84
- "rateBadgeLive": "VWh0dG_rateBadgeLive",
85
- "chartLine": "VWh0dG_chartLine",
86
62
  "planTag": "VWh0dG_planTag",
87
- "heroMeta": "VWh0dG_heroMeta",
88
- "panelHint": "VWh0dG_panelHint",
89
- "bandPrice": "VWh0dG_bandPrice",
90
- "closeButton": "VWh0dG_closeButton",
91
- "modelTable": "VWh0dG_modelTable",
92
- "modelDot": "VWh0dG_modelDot",
93
- "chartCrosshair": "VWh0dG_chartCrosshair",
94
- "numCol": "VWh0dG_numCol",
95
- "bandTagOff": "VWh0dG_bandTagOff",
96
- "triggerMeta": "VWh0dG_triggerMeta",
63
+ "heroSideItem": "VWh0dG_heroSideItem",
64
+ "rangeButtonActive": "VWh0dG_rangeButtonActive",
97
65
  "hero": "VWh0dG_hero",
98
- "healthIdle": "VWh0dG_healthIdle",
99
- "dashboardTitle": "VWh0dG_dashboardTitle",
66
+ "switchKnob": "VWh0dG_switchKnob",
100
67
  "chartTooltipRow": "VWh0dG_chartTooltipRow",
101
- "healthBadge": "VWh0dG_healthBadge",
102
- "chartTooltipDate": "VWh0dG_chartTooltipDate",
103
- "bandTag": "VWh0dG_bandTag",
68
+ "chartLegendLine": "VWh0dG_chartLegendLine",
69
+ "modelTableScroll": "VWh0dG_modelTableScroll",
70
+ "chartWrap": "VWh0dG_chartWrap",
104
71
  "chartTooltip": "VWh0dG_chartTooltip",
105
- "dashboardBody": "VWh0dG_dashboardBody",
106
- "flatTag": "VWh0dG_flatTag",
107
- "healthBadgeOk": "VWh0dG_healthBadgeOk",
108
- "kpiLabel": "VWh0dG_kpiLabel",
109
- "tableScroll": "VWh0dG_tableScroll",
72
+ "heroMeta": "VWh0dG_heroMeta",
73
+ "healthBad": "VWh0dG_healthBad",
74
+ "budgetFillOver": "VWh0dG_budgetFillOver",
75
+ "closeButton": "VWh0dG_closeButton",
76
+ "budgetLabel": "VWh0dG_budgetLabel",
77
+ "chartTooltipDate": "VWh0dG_chartTooltipDate",
78
+ "balanceDays": "VWh0dG_balanceDays",
79
+ "heroSideLabel": "VWh0dG_heroSideLabel",
80
+ "heroMain": "VWh0dG_heroMain",
81
+ "panelHead": "VWh0dG_panelHead",
82
+ "pricingChevron": "VWh0dG_pricingChevron",
83
+ "dashboard": "VWh0dG_dashboard",
84
+ "chartSvg": "VWh0dG_chartSvg",
85
+ "kpiGrid": "VWh0dG_kpiGrid",
86
+ "heroLabel": "VWh0dG_heroLabel",
87
+ "chartBar": "VWh0dG_chartBar",
88
+ "switch": "VWh0dG_switch",
89
+ "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
110
90
  "pricingToggle": "VWh0dG_pricingToggle",
111
91
  "pricingToggleText": "VWh0dG_pricingToggleText",
92
+ "bandPriceOff": "VWh0dG_bandPriceOff",
93
+ "railButton": "VWh0dG_railButton",
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",
109
+ "panelTitle": "VWh0dG_panelTitle",
112
110
  "chartEmpty": "VWh0dG_chartEmpty",
111
+ "chartCrosshair": "VWh0dG_chartCrosshair",
112
+ "triggerDivider": "VWh0dG_triggerDivider",
113
+ "budgetTrack": "VWh0dG_budgetTrack",
114
+ "rateBadge": "VWh0dG_rateBadge",
115
+ "healthOk": "VWh0dG_healthOk",
116
+ "dashboardTitle": "VWh0dG_dashboardTitle",
117
+ "chartAxisLabel": "VWh0dG_chartAxisLabel",
118
+ "switchOn": "VWh0dG_switchOn",
119
+ "healthBadgeOk": "VWh0dG_healthBadgeOk",
120
+ "healthBadgeBad": "VWh0dG_healthBadgeBad",
121
+ "balanceCell": "VWh0dG_balanceCell",
122
+ "budget": "VWh0dG_budget",
123
+ "bandTag": "VWh0dG_bandTag",
124
+ "delta": "VWh0dG_delta",
125
+ "rateBadgeLive": "VWh0dG_rateBadgeLive",
126
+ "triggerAmount": "VWh0dG_triggerAmount",
127
+ "triggerToday": "VWh0dG_triggerToday",
113
128
  "panel": "VWh0dG_panel",
114
- "pricingTable": "VWh0dG_pricingTable",
129
+ "budgetUnit": "VWh0dG_budgetUnit",
130
+ "heroSideValue": "VWh0dG_heroSideValue",
131
+ "budgetInput": "VWh0dG_budgetInput",
132
+ "chartLegend": "VWh0dG_chartLegend",
133
+ "modelTable": "VWh0dG_modelTable",
134
+ "budgetControls": "VWh0dG_budgetControls",
115
135
  "modelCell": "VWh0dG_modelCell",
116
- "dashboardRight": "VWh0dG_dashboardRight",
117
- "railButton": "VWh0dG_railButton",
118
- "triggerToday": "VWh0dG_triggerToday",
119
- "triggerAmountSub": "VWh0dG_triggerAmountSub",
120
- "modelProvider": "VWh0dG_modelProvider",
121
- "bandPriceOff": "VWh0dG_bandPriceOff",
122
- "deltaDown": "VWh0dG_deltaDown"
136
+ "panelHint": "VWh0dG_panelHint",
137
+ "budgetValue": "VWh0dG_budgetValue",
138
+ "na": "VWh0dG_na",
139
+ "flatTag": "VWh0dG_flatTag",
140
+ "healthBadge": "VWh0dG_healthBadge",
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;
@@ -721,9 +747,10 @@ window.__ModuleLoader__.load({
721
747
  /**
722
748
  * TrendChart: dependency-free SVG chart of daily cost + calls.
723
749
  *
724
- * The columns are GROUPED per model — one bar per model per day, each in its
725
- * brand color, so per-model cost is directly comparable. The blue line is the
726
- * 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.
727
754
  * A hover crosshair shows the day's model breakdown. No chart library — the
728
755
  * surface stays self-contained and offline.
729
756
  */
@@ -761,7 +788,7 @@ window.__ModuleLoader__.load({
761
788
  color: ""
762
789
  };
763
790
  /**
764
- * Render the daily grouped cost bars plus the total-calls line.
791
+ * Render the daily stacked cost bars plus the total-calls line.
765
792
  * @param props.data - sorted daily rows (ascending date).
766
793
  * @param props.models - the model legend, in bar order.
767
794
  */
@@ -776,13 +803,12 @@ window.__ModuleLoader__.load({
776
803
  if (n === 1) return PAD.left + plotW / 2;
777
804
  return PAD.left + plotW * i / (n - 1);
778
805
  };
779
- 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);
780
807
  const yCost = (value) => PAD.top + plotH - value / maxCost * plotH;
781
808
  const maxCalls = Math.max(...data.map((d) => d.calls), 1);
782
809
  const yCalls = (value) => PAD.top + plotH - value / maxCalls * plotH;
783
810
  const groupW = plotW / n;
784
- const barCount = Math.max(models.length, 1);
785
- const barW = Math.min(14, groupW / barCount * .7);
811
+ const barW = Math.min(18, groupW * .6);
786
812
  return {
787
813
  n,
788
814
  plotW,
@@ -792,18 +818,31 @@ window.__ModuleLoader__.load({
792
818
  yCalls,
793
819
  barW,
794
820
  bars: data.flatMap((d, i) => {
821
+ const x = inner(i) - barW / 2;
795
822
  if (models.length === 0) return [{
796
823
  date: d.date,
797
824
  model: TOTAL_MODEL,
798
- x: inner(i) - barW / 2,
799
- value: d.cost
825
+ x,
826
+ base: 0,
827
+ value: d.cost,
828
+ topRounded: true
800
829
  }];
801
- return models.map((model, k) => ({
802
- date: d.date,
803
- model,
804
- x: inner(i) - groupW / 2 + groupW * (k + .5) / models.length - barW / 2,
805
- value: d.byModel?.[model.key] ?? 0
806
- }));
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
+ });
807
846
  }),
808
847
  costTicks: [
809
848
  0,
@@ -868,10 +907,10 @@ window.__ModuleLoader__.load({
868
907
  }),
869
908
  bars.map((bar) => bar.value > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
870
909
  x: bar.x,
871
- y: yCost(bar.value),
910
+ y: yCost(bar.base + bar.value),
872
911
  width: barW,
873
- height: yCost(0) - yCost(bar.value),
874
- rx: 2,
912
+ height: yCost(bar.base) - yCost(bar.base + bar.value),
913
+ rx: bar.topRounded ? 2 : 0,
875
914
  className: bar.model.color === "" ? UsageBilling_module_css_default.chartBar : UsageBilling_module_css_default.chartStack,
876
915
  style: bar.model.color === "" ? void 0 : { fill: bar.model.color }
877
916
  }, `${bar.date}-${bar.model.key}`) : null),
@@ -974,6 +1013,13 @@ window.__ModuleLoader__.load({
974
1013
  * before real data arrives the dashboard shows an empty (zero) snapshot,
975
1014
  * never fabricated samples.
976
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
+ }
977
1023
  /** Idle health state before the probe settles. */
978
1024
  const IDLE_HEALTH = {
979
1025
  checked: false,
@@ -987,6 +1033,8 @@ window.__ModuleLoader__.load({
987
1033
  * The dashboard's display names (中文厂商名) never equal the provider names a
988
1034
  * user actually configures (deepseek, zhipu, qwen…), so the dot match also
989
1035
  * accepts a bidirectional substring hit and a display-name alias list.
1036
+ * 导出供一致性守卫测试:catalog 每个厂商都必须在此登记(Custom 除外),
1037
+ * 防止新增厂商漏配导致健康绿灯不亮。
990
1038
  */
991
1039
  const PROVIDER_ALIASES = {
992
1040
  "DeepSeek": ["deepseek"],
@@ -1007,6 +1055,11 @@ window.__ModuleLoader__.load({
1007
1055
  "ark"
1008
1056
  ],
1009
1057
  "月之暗面": ["moonshot", "kimi"],
1058
+ "小米": [
1059
+ "xiaomi",
1060
+ "mi",
1061
+ "mimo"
1062
+ ],
1010
1063
  "MiniMax": ["minimax"],
1011
1064
  "百度文心": [
1012
1065
  "ernie",
@@ -1056,6 +1109,41 @@ window.__ModuleLoader__.load({
1056
1109
  const aliases = PROVIDER_ALIASES[display];
1057
1110
  return aliases !== void 0 && aliases.some((alias) => providerNameHits(normalizeProvider(alias), liveKey));
1058
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
+ }
1059
1147
  /** Resolve one provider's dot state: green when live, red when failed, gray when unknown. */
1060
1148
  function providerDot(health, provider) {
1061
1149
  if (!health.checked) return UsageBilling_module_css_default.healthIdle;
@@ -1130,7 +1218,10 @@ window.__ModuleLoader__.load({
1130
1218
  byModel: candidate.byModel ?? {},
1131
1219
  byDay: candidate.byDay ?? {},
1132
1220
  ...candidate.byDayModels !== void 0 ? { byDayModels: candidate.byDayModels } : {},
1133
- ...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 } : {}
1134
1225
  };
1135
1226
  } catch {
1136
1227
  return null;
@@ -1180,6 +1271,8 @@ window.__ModuleLoader__.load({
1180
1271
  }
1181
1272
  /**
1182
1273
  * Sidebar footer trigger: compact pill in wide mode, icon in rail mode.
1274
+ * ZINE 模式下入口由主题插件的贴纸层承担,本触发器由 CSS
1275
+ * (body[data-zine-mode] 选择器)隐藏,组件本身无 zine 分支。
1183
1276
  * @param props - framework props plus `wide` column state.
1184
1277
  */
1185
1278
  function UsageBillingTrigger(props) {
@@ -1210,6 +1303,7 @@ window.__ModuleLoader__.load({
1210
1303
  if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1211
1304
  type: "button",
1212
1305
  className: UsageBilling_module_css_default.railButton,
1306
+ "data-testid": "billing-rail-button",
1213
1307
  onClick: onOpen,
1214
1308
  title: `${t("billing.title")} · ${formatMoney(monthCost)}`,
1215
1309
  children: cardIcon
@@ -1217,15 +1311,18 @@ window.__ModuleLoader__.load({
1217
1311
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1218
1312
  type: "button",
1219
1313
  className: UsageBilling_module_css_default.trigger,
1314
+ "data-testid": "billing-trigger",
1220
1315
  onClick: onOpen,
1221
1316
  title: `${t("billing.title")} · 本月 ${formatMoney(monthCost)}`,
1222
1317
  children: [
1223
1318
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1224
1319
  className: UsageBilling_module_css_default.triggerIcon,
1320
+ "data-testid": "billing-trigger-icon",
1225
1321
  children: cardIcon
1226
1322
  }),
1227
1323
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1228
1324
  className: UsageBilling_module_css_default.triggerToday,
1325
+ "data-testid": "billing-trigger-today",
1229
1326
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1230
1327
  className: UsageBilling_module_css_default.triggerMeta,
1231
1328
  children: "今日"
@@ -1237,6 +1334,7 @@ window.__ModuleLoader__.load({
1237
1334
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.triggerDivider }),
1238
1335
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1239
1336
  className: UsageBilling_module_css_default.triggerMonth,
1337
+ "data-testid": "billing-trigger-month",
1240
1338
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1241
1339
  className: UsageBilling_module_css_default.triggerMeta,
1242
1340
  children: "当月"
@@ -1250,12 +1348,15 @@ window.__ModuleLoader__.load({
1250
1348
  }
1251
1349
  /**
1252
1350
  * The centered billing dashboard modal.
1253
- * @param props - stats, locale function, close handler, model health, balances.
1351
+ * @param props - stats, locale function, close handler, model health, balances, renderSlot.
1254
1352
  */
1255
- function BillingDashboard({ stats, t, onClose, health, balances }) {
1353
+ function BillingDashboard({ stats, t, onClose, health, balances, renderSlot, budgetEnabled, budgetAmount, onToggleBudget, onBudgetAmount }) {
1256
1354
  const { total, byModel, byDay } = stats;
1257
1355
  const [pricingOpen, setPricingOpen] = (0, react.useState)(false);
1356
+ const [sessionsOpen, setSessionsOpen] = (0, react.useState)(false);
1357
+ const [trendDays, setTrendDays] = (0, react.useState)(7);
1258
1358
  const rateInfo = getRateInfo();
1359
+ const dailyBurn = dailyBurnRate(byDay, localDayStamp());
1259
1360
  const balanceFor = (provider) => balances.find((balance) => normalizeProvider(balance.provider) === normalizeProvider(provider));
1260
1361
  const renderBalance = (balance) => {
1261
1362
  if (balance === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1269,7 +1370,18 @@ window.__ModuleLoader__.load({
1269
1370
  className: UsageBilling_module_css_default.na,
1270
1371
  children: "—"
1271
1372
  });
1272
- 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
+ });
1273
1385
  };
1274
1386
  const cacheHitRate = total.cacheHit + total.cacheMiss > 0 ? total.cacheHit / (total.cacheHit + total.cacheMiss) * 100 : 0;
1275
1387
  const dates = Object.keys(byDay).sort();
@@ -1282,13 +1394,13 @@ window.__ModuleLoader__.load({
1282
1394
  const yearCost = dates.reduce((sum, d) => sum + (d.startsWith(yearPrefix) ? byDay[d]?.cost ?? 0 : 0), 0);
1283
1395
  const trendDates = (0, react.useMemo)(() => {
1284
1396
  const out = [];
1285
- for (let offset = 6; offset >= 0; offset -= 1) {
1397
+ for (let offset = trendDays - 1; offset >= 0; offset -= 1) {
1286
1398
  const day = /* @__PURE__ */ new Date();
1287
1399
  day.setDate(day.getDate() - offset);
1288
1400
  out.push(localDayStamp(day.getTime()));
1289
1401
  }
1290
1402
  return out;
1291
- }, []);
1403
+ }, [trendDays]);
1292
1404
  const latestDate = trendDates.at(-1) ?? today;
1293
1405
  const trend = (0, react.useMemo)(() => trendDates.map((date) => {
1294
1406
  const byModel = {};
@@ -1310,6 +1422,8 @@ window.__ModuleLoader__.load({
1310
1422
  ]);
1311
1423
  const modelRows = (0, react.useMemo)(() => Object.entries(byModel).filter(([, data]) => data.calls > 0).map(([key, data]) => {
1312
1424
  const entry = modelOf(key);
1425
+ const uncatalogued = entry.key === "other";
1426
+ const inferredProvider = uncatalogued ? providerFromModelKey(key) : void 0;
1313
1427
  const buckets = {
1314
1428
  input: data.input,
1315
1429
  cacheHit: data.cacheHit,
@@ -1318,15 +1432,16 @@ window.__ModuleLoader__.load({
1318
1432
  };
1319
1433
  return {
1320
1434
  key,
1321
- name: entry.name,
1322
- provider: entry.provider,
1435
+ name: uncatalogued ? key : entry.name,
1436
+ provider: inferredProvider ?? entry.provider,
1323
1437
  calls: data.calls,
1324
1438
  input: data.input,
1325
1439
  output: data.output,
1326
1440
  cacheHitRate: data.cacheHit + data.cacheMiss > 0 ? data.cacheHit / (data.cacheHit + data.cacheMiss) * 100 : 0,
1327
1441
  estimated: computeCost(entry, buckets),
1328
1442
  plan: data.plan === true,
1329
- ...data.cost > 0 ? { actual: data.cost } : {}
1443
+ ...data.cost > 0 ? { actual: data.cost } : {},
1444
+ uncatalogued
1330
1445
  };
1331
1446
  }).sort((a, b) => (b.actual ?? b.estimated) - (a.actual ?? a.estimated)).map((row, index) => ({
1332
1447
  ...row,
@@ -1350,19 +1465,28 @@ window.__ModuleLoader__.load({
1350
1465
  className: UsageBilling_module_css_default.dashboardModal ?? "",
1351
1466
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1352
1467
  className: UsageBilling_module_css_default.dashboard,
1468
+ "data-testid": "billing-dashboard",
1353
1469
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1354
1470
  className: UsageBilling_module_css_default.dashboardHead,
1355
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1356
- className: UsageBilling_module_css_default.dashboardTitle,
1357
- children: t("billing.title")
1358
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1359
- className: UsageBilling_module_css_default.dashboardSubtitle,
1360
- children: [
1361
- t("billing.lastUpdated"),
1362
- " ",
1363
- latestDate
1364
- ]
1365
- })] }), /* @__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", {
1366
1490
  className: UsageBilling_module_css_default.dashboardRight,
1367
1491
  children: [health.checked && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1368
1492
  className: clsx(UsageBilling_module_css_default.healthBadge, health.available ? UsageBilling_module_css_default.healthBadgeOk : UsageBilling_module_css_default.healthBadgeBad),
@@ -1374,6 +1498,7 @@ window.__ModuleLoader__.load({
1374
1498
  type: "button",
1375
1499
  className: UsageBilling_module_css_default.closeButton,
1376
1500
  "aria-label": t("billing.close"),
1501
+ "data-testid": "billing-close",
1377
1502
  onClick: onClose,
1378
1503
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1379
1504
  viewBox: "0 0 24 24",
@@ -1389,62 +1514,141 @@ window.__ModuleLoader__.load({
1389
1514
  children: [
1390
1515
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1391
1516
  className: UsageBilling_module_css_default.hero,
1392
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1393
- className: UsageBilling_module_css_default.heroMain,
1394
- children: [
1395
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1396
- className: UsageBilling_module_css_default.heroLabel,
1397
- children: t("billing.monthCost")
1398
- }),
1399
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1400
- className: UsageBilling_module_css_default.heroValue,
1401
- children: formatMoney(monthCost)
1402
- }),
1403
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1404
- className: UsageBilling_module_css_default.heroMeta,
1405
- children: [
1406
- monthCalls.toLocaleString(),
1407
- " ",
1408
- t("billing.calls")
1409
- ]
1410
- })
1411
- ]
1412
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1413
- className: UsageBilling_module_css_default.heroSide,
1414
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1415
- className: UsageBilling_module_css_default.heroSideItem,
1416
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1417
- className: UsageBilling_module_css_default.heroSideLabel,
1418
- children: t("billing.yearCost")
1419
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1420
- className: UsageBilling_module_css_default.heroSideValue,
1421
- children: formatMoney(yearCost)
1422
- })]
1423
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1424
- className: UsageBilling_module_css_default.heroSideItem,
1425
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1426
- className: UsageBilling_module_css_default.heroSideLabel,
1427
- children: t("billing.todayCost")
1428
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1429
- className: UsageBilling_module_css_default.heroSideValue,
1430
- children: [formatMoney(todayCost), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1431
- 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,
1432
1533
  children: [
1433
- deltaPct >= 0 ? "▲" : "▼",
1534
+ monthCalls.toLocaleString(),
1434
1535
  " ",
1435
- Math.abs(deltaPct).toFixed(1),
1436
- "%"
1536
+ t("billing.calls")
1437
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
+ })]
1438
1568
  })]
1439
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
+ ]
1440
1632
  })]
1441
- })]
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
+ })()]
1442
1644
  }),
1443
1645
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1444
1646
  className: UsageBilling_module_css_default.kpiGrid,
1647
+ "data-testid": "billing-kpi-grid",
1445
1648
  children: [
1446
1649
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1447
1650
  className: UsageBilling_module_css_default.kpiTile,
1651
+ "data-testid": "billing-kpi-tile",
1448
1652
  children: [
1449
1653
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1450
1654
  className: UsageBilling_module_css_default.kpiLabel,
@@ -1534,16 +1738,36 @@ window.__ModuleLoader__.load({
1534
1738
  ]
1535
1739
  }),
1536
1740
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1537
- 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",
1538
1743
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1539
1744
  className: UsageBilling_module_css_default.panelHead,
1540
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1541
- className: UsageBilling_module_css_default.panelTitle,
1542
- children: t("billing.trend")
1543
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1544
- className: UsageBilling_module_css_default.panelHint,
1545
- children: latestDate
1546
- })]
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
+ ]
1547
1771
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
1548
1772
  data: trend,
1549
1773
  models: chartModels
@@ -1551,62 +1775,171 @@ window.__ModuleLoader__.load({
1551
1775
  }),
1552
1776
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1553
1777
  className: UsageBilling_module_css_default.panel,
1554
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1555
- className: UsageBilling_module_css_default.panelHead,
1556
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1557
- className: UsageBilling_module_css_default.panelTitle,
1558
- children: t("billing.models")
1559
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1560
- className: UsageBilling_module_css_default.panelHint,
1561
- 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" })
1562
1910
  })]
1563
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1911
+ }), sessionsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1564
1912
  className: UsageBilling_module_css_default.tableScroll,
1913
+ "data-testid": "billing-sessions-table",
1565
1914
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1566
1915
  className: UsageBilling_module_css_default.modelTable,
1567
1916
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1568
- /* @__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") }),
1569
1919
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1570
1920
  className: UsageBilling_module_css_default.numCol,
1571
1921
  children: t("billing.calls")
1572
1922
  }),
1573
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1574
- className: UsageBilling_module_css_default.numCol,
1575
- children: t("billing.inputTokens")
1576
- }),
1577
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1578
- className: UsageBilling_module_css_default.numCol,
1579
- children: t("billing.outputTokens")
1580
- }),
1581
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1582
- className: UsageBilling_module_css_default.numCol,
1583
- children: t("billing.cacheHitRate")
1584
- }),
1585
1923
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1586
1924
  className: UsageBilling_module_css_default.numCol,
1587
1925
  children: t("billing.actual")
1588
1926
  }),
1589
1927
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1590
1928
  className: UsageBilling_module_css_default.numCol,
1591
- children: t("billing.balance")
1929
+ children: t("billing.lastActive")
1592
1930
  })
1593
- ] }) }), /* @__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", {
1594
- 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,
1595
1933
  className: UsageBilling_module_css_default.emptyRow,
1596
1934
  children: t("billing.noData")
1597
- }) }), modelRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1598
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1599
- className: UsageBilling_module_css_default.modelCell,
1600
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1601
- className: clsx(UsageBilling_module_css_default.modelDot, providerDot(health, row.provider)),
1602
- "aria-hidden": "true"
1603
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1604
- className: UsageBilling_module_css_default.modelName,
1605
- children: row.name
1606
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1607
- className: UsageBilling_module_css_default.modelProvider,
1608
- children: row.provider
1609
- })] })]
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) ?? "—"
1610
1943
  }) }),
1611
1944
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1612
1945
  className: UsageBilling_module_css_default.numCol,
@@ -1614,39 +1947,23 @@ window.__ModuleLoader__.load({
1614
1947
  }),
1615
1948
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1616
1949
  className: UsageBilling_module_css_default.numCol,
1617
- children: formatTokens(row.input)
1618
- }),
1619
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1620
- className: UsageBilling_module_css_default.numCol,
1621
- children: formatTokens(row.output)
1950
+ children: formatMoney(row.cost)
1622
1951
  }),
1623
1952
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1624
1953
  className: UsageBilling_module_css_default.numCol,
1625
- children: formatPercent(row.cacheHitRate)
1626
- }),
1627
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1628
- className: UsageBilling_module_css_default.numCol,
1629
- children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1630
- className: UsageBilling_module_css_default.planTag,
1631
- children: "订阅包含"
1632
- }) : row.actual !== void 0 ? formatMoney(row.actual) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1633
- className: UsageBilling_module_css_default.na,
1634
- children: "—"
1635
- })
1636
- }),
1637
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1638
- className: UsageBilling_module_css_default.numCol,
1639
- children: renderBalance(balanceFor(row.provider))
1954
+ children: row.lastActive > 0 ? `${localDayStamp(row.lastActive)} ${formatClock(row.lastActive)}` : "—"
1640
1955
  })
1641
- ] }, row.key))] })]
1956
+ ] }, row.id))] })]
1642
1957
  })
1643
1958
  })]
1644
1959
  }),
1645
1960
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1646
1961
  className: UsageBilling_module_css_default.panel,
1962
+ "data-testid": "billing-panel-pricing",
1647
1963
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1648
1964
  type: "button",
1649
1965
  className: UsageBilling_module_css_default.pricingToggle,
1966
+ "data-testid": "billing-pricing-toggle",
1650
1967
  onClick: () => {
1651
1968
  setPricingOpen((prev) => !prev);
1652
1969
  },
@@ -1767,7 +2084,7 @@ window.__ModuleLoader__.load({
1767
2084
  * @param props - framework-provided sidebar and locale props.
1768
2085
  */
1769
2086
  function UsageBilling(props) {
1770
- const { t, checkModels } = props;
2087
+ const { t, checkModels, publishCosts, registerOpen, renderSlot, useStore, actions } = props;
1771
2088
  const [stats, setStats] = (0, react.useState)(EMPTY_STATS);
1772
2089
  const [health, setHealth] = (0, react.useState)(IDLE_HEALTH);
1773
2090
  const [balances, setBalances] = (0, react.useState)([]);
@@ -1812,17 +2129,102 @@ window.__ModuleLoader__.load({
1812
2129
  }, [checkModels]);
1813
2130
  const today = localDayStamp();
1814
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]);
1815
2212
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBillingTrigger, {
1816
2213
  ...props,
1817
2214
  onOpen: openDashboard,
1818
2215
  monthCost,
1819
- todayCost: stats.byDay[today]?.cost ?? 0
2216
+ todayCost
1820
2217
  }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BillingDashboard, {
1821
2218
  stats,
1822
2219
  t,
1823
2220
  onClose: close,
1824
2221
  health,
1825
- balances
2222
+ balances,
2223
+ renderSlot,
2224
+ budgetEnabled,
2225
+ budgetAmount: effectiveBudget,
2226
+ onToggleBudget: toggleBudget,
2227
+ onBudgetAmount: actions.setAmount
1826
2228
  })] });
1827
2229
  }
1828
2230
  //#endregion
@@ -1843,7 +2245,15 @@ window.__ModuleLoader__.load({
1843
2245
  "billing.outputTokens": "输出",
1844
2246
  "billing.avgCost": "平均成本",
1845
2247
  "billing.trend": "每日费用与调用趋势",
2248
+ "billing.trend7d": "7 天",
2249
+ "billing.trend30d": "30 天",
1846
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}%)",
1847
2257
  "billing.models": "模型计费明细",
1848
2258
  "billing.estimated": "估算",
1849
2259
  "billing.actual": "实际",
@@ -1869,7 +2279,10 @@ window.__ModuleLoader__.load({
1869
2279
  "billing.balance": "余额",
1870
2280
  "billing.balanceUnconfigured": "未配置",
1871
2281
  "billing.balanceUnauthorized": "密钥无效",
1872
- "billing.balanceUnreachable": "查询失败"
2282
+ "billing.balanceUnreachable": "查询失败",
2283
+ "billing.uncatalogued": "未收录",
2284
+ "billing.balanceDays": "约可撑 {days} 天",
2285
+ "billing.balanceLowBody": "{name} 余额 {balance},约可撑 {days} 天,请及时充值"
1873
2286
  };
1874
2287
  const en = {
1875
2288
  "billing.title": "Usage",
@@ -1886,7 +2299,15 @@ window.__ModuleLoader__.load({
1886
2299
  "billing.outputTokens": "Output",
1887
2300
  "billing.avgCost": "Avg cost",
1888
2301
  "billing.trend": "Daily cost & calls",
2302
+ "billing.trend7d": "7D",
2303
+ "billing.trend30d": "30D",
1889
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}%)",
1890
2311
  "billing.models": "Model billing",
1891
2312
  "billing.estimated": "Est.",
1892
2313
  "billing.actual": "Actual",
@@ -1912,9 +2333,81 @@ window.__ModuleLoader__.load({
1912
2333
  "billing.balance": "Balance",
1913
2334
  "billing.balanceUnconfigured": "Not set",
1914
2335
  "billing.balanceUnauthorized": "Bad key",
1915
- "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"
1916
2340
  };
1917
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
1918
2411
  //#region src/client/apply.ts
1919
2412
  /** Required services for the usage billing surface. */
1920
2413
  const inject = [
@@ -1927,6 +2420,9 @@ window.__ModuleLoader__.load({
1927
2420
  * @param ctx - client root context.
1928
2421
  */
1929
2422
  function apply(ctx) {
2423
+ const metrics = createBillingMetrics();
2424
+ ctx.provide("billingMetrics", metrics);
2425
+ const budgetStore = createBillingBudgetStore();
1930
2426
  ctx.effect(() => ctx.locale.register(NS, {
1931
2427
  zh,
1932
2428
  en
@@ -1936,36 +2432,47 @@ window.__ModuleLoader__.load({
1936
2432
  id: "usage-billing",
1937
2433
  order: -10,
1938
2434
  locale: NS,
1939
- inject: () => ({ checkModels: async () => {
1940
- try {
1941
- const { result } = await ctx.connection.api.llm.models({});
1942
- if (!result.ok) return {
1943
- checked: true,
1944
- available: false,
1945
- providers: 0,
1946
- failures: 0,
1947
- okProviders: [],
1948
- badProviders: []
1949
- };
1950
- return {
1951
- checked: true,
1952
- available: result.value.groups.length > 0,
1953
- providers: result.value.groups.length,
1954
- failures: result.value.failures.length,
1955
- okProviders: result.value.groups.map((group) => group.name),
1956
- badProviders: result.value.failures.map((failure) => failure.name)
1957
- };
1958
- } catch {
1959
- return {
1960
- checked: true,
1961
- available: false,
1962
- providers: 0,
1963
- failures: 0,
1964
- okProviders: [],
1965
- badProviders: []
1966
- };
1967
- }
1968
- } })
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
+ })
1969
2476
  }, UsageBilling));
1970
2477
  }
1971
2478
  //#endregion