@kenz1117/dsh-ui-usage-billing 0.3.0 → 0.4.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
@@ -24,7 +24,7 @@ window.__ModuleLoader__.load({
24
24
  }
25
25
  //#endregion
26
26
  //#region \0dsh-css:/Users/ken/deepseek-harness/packages/client/ui-usage-billing/src/client/UsageBilling.module.css.mjs
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
+ 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}.VWh0dG_currencyToggle{border:1px solid var(--dsw-alias-border-muted,#3a4250);background:var(--dsw-alias-bg-layer-2,#232a33);border-radius:6px;align-items:center;gap:2px;margin-right:8px;padding:2px;display:inline-flex}.VWh0dG_currencyButton{color:var(--dsw-alias-text-secondary,#9aa5b1);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:4px 7px;font-size:11px;font-weight:700;line-height:1}.VWh0dG_currencyButtonActive{background:var(--dsw-alias-accent-muted,#2f6fed);color:#fff}.VWh0dG_subscriptionGrid{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px;margin-top:10px;display:grid}.VWh0dG_subscriptionCard{border:1px solid var(--dsw-alias-border-muted,#3a4250);background:var(--dsw-alias-bg-layer-2,#232a33);border-radius:8px;padding:10px 12px}.VWh0dG_subscriptionHead{align-items:baseline;gap:8px;margin-bottom:6px;display:flex}.VWh0dG_subscriptionName{font-size:13px;font-weight:700}.VWh0dG_subscriptionPlan{color:var(--dsw-alias-text-secondary,#9aa5b1);font-size:11px}.VWh0dG_subscriptionStatus{color:var(--dsw-alias-warning,#e0a63c);margin-bottom:6px;font-size:11px}.VWh0dG_subscriptionWindow{align-items:center;gap:8px;margin-top:6px;font-size:11px;display:flex}.VWh0dG_subscriptionWindowLabel{color:var(--dsw-alias-text-secondary,#9aa5b1);flex:0 0 44px}.VWh0dG_subscriptionTrack{background:var(--dsw-alias-bg-layer-3,#1a2028);border-radius:3px;flex:1;height:5px;overflow:hidden}.VWh0dG_subscriptionFill{background:var(--dsw-alias-accent,#3b82f6);border-radius:3px;height:100%;display:block}.VWh0dG_subscriptionPct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:52px}.VWh0dG_subscriptionReset{color:var(--dsw-alias-text-secondary,#9aa5b1);flex:none}.VWh0dG_providerGroupList{flex-direction:column;gap:10px;display:flex}.VWh0dG_providerGroup{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px;padding:12px 14px}.VWh0dG_providerGroupHead{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.VWh0dG_providerGroupTitle{align-items:center;gap:7px;min-width:0;display:inline-flex}.VWh0dG_providerGroupName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px}.VWh0dG_providerGroupMeta{flex:none;align-items:center;gap:10px;display:inline-flex}.VWh0dG_providerGroupBadge{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_providerGroupBalance{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;align-items:baseline;gap:6px;font-size:12px;line-height:17px;display:inline-flex}.VWh0dG_providerGroupBalanceLabel{color:var(--dsw-alias-label-caption);font-size:11px;line-height:17px}.VWh0dG_roundsFlagBadge{color:#fff;background:#ef4444;border-radius:999px;margin-left:8px;padding:1px 7px;font-size:10px;font-weight:800}.VWh0dG_rounds{margin-top:10px}.VWh0dG_roundsBars{align-items:flex-end;gap:3px;height:110px;padding:4px 2px 0;display:flex;overflow-x:auto}.VWh0dG_roundsBarCol{flex-direction:column;flex:1 0 28px;justify-content:flex-end;align-items:stretch;height:100%;display:flex}.VWh0dG_roundsBarLabel{text-align:center;color:var(--dsw-alias-text-secondary,#9aa5b1);white-space:nowrap;text-overflow:ellipsis;max-width:100%;margin-bottom:2px;font-size:8px;line-height:1;overflow:hidden}.VWh0dG_roundsBarWrap{flex:1;align-items:flex-end;min-height:0;display:flex}.VWh0dG_roundsBar{background:var(--dsw-alias-accent,#3b82f6);border-radius:2px 2px 0 0;width:100%;min-height:2px;position:relative}.VWh0dG_roundsBarFlagged{outline-offset:1px;background:#ef4444;border-radius:2px 2px 0 0;outline:1.5px solid #ef4444;width:100%;min-height:2px;position:relative}.VWh0dG_roundsFlagMark{background:#ef4444;border-radius:50%;width:7px;height:7px;position:absolute;top:-3px;right:-3px;box-shadow:0 0 4px #ef4444}.VWh0dG_roundsAxis{color:var(--dsw-alias-text-secondary,#9aa5b1);justify-content:space-between;margin-top:6px;font-size:10px;display:flex}.VWh0dG_roundsEmpty{color:var(--dsw-alias-text-secondary,#9aa5b1);margin-top:10px;font-size:12px}.VWh0dG_heatmap{margin-top:10px}.VWh0dG_heatmapGrid{grid-template-columns:repeat(7,minmax(0,1fr));gap:8px;display:grid}.VWh0dG_heatmapCellEmpty{background:var(--dsw-alias-bg-layer-1);height:30px;color:var(--dsw-alias-label-dimmed,#6b7480);font-variant-numeric:tabular-nums;border-radius:8px;justify-content:center;align-items:center;font-size:11px;display:flex}.VWh0dG_heatmapCell{font-variant-numeric:tabular-nums;cursor:pointer;border:0;border-radius:8px;justify-content:center;align-items:center;height:30px;padding:0;font-size:11px;display:flex}.VWh0dG_heatmapCell[data-level=\"0\"],.VWh0dG_heatmapCell[data-level=\"1\"],.VWh0dG_heatmapCell[data-level=\"2\"]{color:var(--dsw-alias-label-primary)}.VWh0dG_heatmapCell[data-level=\"3\"],.VWh0dG_heatmapCell[data-level=\"4\"]{color:var(--dsw-static-neutral-bluish-00,#fff)}.VWh0dG_heatmapFooter{color:var(--dsw-alias-text-secondary,#9aa5b1);align-items:center;gap:8px;margin-top:10px;font-size:10px;display:flex}.VWh0dG_heatmapLegend{gap:3px;display:inline-flex}.VWh0dG_heatmapLegend i{border-radius:4px;width:14px;height:14px;display:block}.VWh0dG_heatmapHover{font-variant-numeric:tabular-nums;margin-left:auto}@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}}";
28
28
  const tagId = "@kenz1117/dsh-ui-usage-billing/UsageBilling.module.css";
29
29
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
30
30
  const tag = document.createElement("style");
@@ -34,119 +34,171 @@ window.__ModuleLoader__.load({
34
34
  document.head.appendChild(tag);
35
35
  }
36
36
  var UsageBilling_module_css_default = {
37
- "chartGrid": "VWh0dG_chartGrid",
38
- "pricingTable": "VWh0dG_pricingTable",
39
- "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
40
- "chartLegendBar": "VWh0dG_chartLegendBar",
37
+ "providerGroupBalance": "VWh0dG_providerGroupBalance",
38
+ "chartCrosshair": "VWh0dG_chartCrosshair",
39
+ "panelHead": "VWh0dG_panelHead",
40
+ "triggerAmountSub": "VWh0dG_triggerAmountSub",
41
+ "heatmapCellEmpty": "VWh0dG_heatmapCellEmpty",
42
+ "subscriptionTrack": "VWh0dG_subscriptionTrack",
41
43
  "numCol": "VWh0dG_numCol",
42
- "budgetFill": "VWh0dG_budgetFill",
43
- "modelDot": "VWh0dG_modelDot",
44
- "uncataloguedTag": "VWh0dG_uncataloguedTag",
45
- "trendPanel": "VWh0dG_trendPanel",
46
- "kpiTile": "VWh0dG_kpiTile",
47
- "healthDot": "VWh0dG_healthDot",
48
- "triggerMeta": "VWh0dG_triggerMeta",
49
- "heroSide": "VWh0dG_heroSide",
50
- "tableScroll": "VWh0dG_tableScroll",
51
- "dashboardRight": "VWh0dG_dashboardRight",
52
- "modelProvider": "VWh0dG_modelProvider",
53
- "costCol": "VWh0dG_costCol",
54
- "chartStack": "VWh0dG_chartStack",
55
- "chartLine": "VWh0dG_chartLine",
56
- "kpiGreen": "VWh0dG_kpiGreen",
57
- "dashboardModal": "VWh0dG_dashboardModal",
58
- "trigger": "VWh0dG_trigger",
59
- "rangeToggle": "VWh0dG_rangeToggle",
60
- "budgetInputWrap": "VWh0dG_budgetInputWrap",
61
- "modelName": "VWh0dG_modelName",
62
- "planTag": "VWh0dG_planTag",
63
- "heroSideItem": "VWh0dG_heroSideItem",
64
- "rangeButtonActive": "VWh0dG_rangeButtonActive",
65
- "hero": "VWh0dG_hero",
66
- "switchKnob": "VWh0dG_switchKnob",
67
- "chartTooltipRow": "VWh0dG_chartTooltipRow",
44
+ "rangeButton": "VWh0dG_rangeButton",
45
+ "healthOk": "VWh0dG_healthOk",
68
46
  "chartLegendLine": "VWh0dG_chartLegendLine",
69
- "modelTableScroll": "VWh0dG_modelTableScroll",
70
- "chartWrap": "VWh0dG_chartWrap",
71
- "chartTooltip": "VWh0dG_chartTooltip",
72
- "heroMeta": "VWh0dG_heroMeta",
73
- "healthBad": "VWh0dG_healthBad",
74
- "budgetFillOver": "VWh0dG_budgetFillOver",
75
- "closeButton": "VWh0dG_closeButton",
76
- "budgetLabel": "VWh0dG_budgetLabel",
77
- "chartTooltipDate": "VWh0dG_chartTooltipDate",
47
+ "switchOn": "VWh0dG_switchOn",
48
+ "rangeButtonActive": "VWh0dG_rangeButtonActive",
49
+ "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
50
+ "flatTag": "VWh0dG_flatTag",
78
51
  "balanceDays": "VWh0dG_balanceDays",
79
- "heroSideLabel": "VWh0dG_heroSideLabel",
80
- "heroMain": "VWh0dG_heroMain",
81
- "panelHead": "VWh0dG_panelHead",
82
- "pricingChevron": "VWh0dG_pricingChevron",
83
- "dashboard": "VWh0dG_dashboard",
52
+ "triggerMeta": "VWh0dG_triggerMeta",
53
+ "kpiLabel": "VWh0dG_kpiLabel",
54
+ "bandPriceOff": "VWh0dG_bandPriceOff",
55
+ "triggerAmount": "VWh0dG_triggerAmount",
56
+ "budgetFillOver": "VWh0dG_budgetFillOver",
57
+ "bandTagOff": "VWh0dG_bandTagOff",
58
+ "subscriptionPlan": "VWh0dG_subscriptionPlan",
59
+ "rounds": "VWh0dG_rounds",
60
+ "dashboardHead": "VWh0dG_dashboardHead",
84
61
  "chartSvg": "VWh0dG_chartSvg",
85
- "kpiGrid": "VWh0dG_kpiGrid",
86
- "heroLabel": "VWh0dG_heroLabel",
87
- "chartBar": "VWh0dG_chartBar",
88
- "switch": "VWh0dG_switch",
89
- "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
62
+ "pricingTable": "VWh0dG_pricingTable",
90
63
  "pricingToggle": "VWh0dG_pricingToggle",
91
- "pricingToggleText": "VWh0dG_pricingToggleText",
92
- "bandPriceOff": "VWh0dG_bandPriceOff",
93
- "railButton": "VWh0dG_railButton",
94
- "budgetHead": "VWh0dG_budgetHead",
95
- "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
96
- "triggerAmountSub": "VWh0dG_triggerAmountSub",
64
+ "modelTable": "VWh0dG_modelTable",
65
+ "kpiTile": "VWh0dG_kpiTile",
66
+ "budget": "VWh0dG_budget",
67
+ "hero": "VWh0dG_hero",
68
+ "subscriptionStatus": "VWh0dG_subscriptionStatus",
69
+ "dashboardRight": "VWh0dG_dashboardRight",
70
+ "kpiGreen": "VWh0dG_kpiGreen",
97
71
  "emptyRow": "VWh0dG_emptyRow",
72
+ "heatmapLegend": "VWh0dG_heatmapLegend",
73
+ "chartAxisLabel": "VWh0dG_chartAxisLabel",
74
+ "providerGroupBalanceLabel": "VWh0dG_providerGroupBalanceLabel",
75
+ "budgetValue": "VWh0dG_budgetValue",
98
76
  "deltaDown": "VWh0dG_deltaDown",
99
- "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
77
+ "currencyButton": "VWh0dG_currencyButton",
78
+ "pricingChevron": "VWh0dG_pricingChevron",
79
+ "rangeToggle": "VWh0dG_rangeToggle",
80
+ "budgetLabel": "VWh0dG_budgetLabel",
100
81
  "budgetOverPulse": "VWh0dG_budgetOverPulse",
101
- "chartDot": "VWh0dG_chartDot",
102
- "dashboardBody": "VWh0dG_dashboardBody",
82
+ "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
83
+ "heatmapFooter": "VWh0dG_heatmapFooter",
84
+ "triggerMonth": "VWh0dG_triggerMonth",
85
+ "heroMain": "VWh0dG_heroMain",
86
+ "budgetInputWrap": "VWh0dG_budgetInputWrap",
87
+ "modelProvider": "VWh0dG_modelProvider",
88
+ "dashboardTitle": "VWh0dG_dashboardTitle",
103
89
  "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",
90
+ "chartTooltipRow": "VWh0dG_chartTooltipRow",
91
+ "currencyButtonActive": "VWh0dG_currencyButtonActive",
92
+ "closeButton": "VWh0dG_closeButton",
93
+ "deltaUp": "VWh0dG_deltaUp",
94
+ "chartTooltip": "VWh0dG_chartTooltip",
95
+ "subscriptionHead": "VWh0dG_subscriptionHead",
96
+ "roundsBarWrap": "VWh0dG_roundsBarWrap",
97
+ "costCol": "VWh0dG_costCol",
98
+ "healthDot": "VWh0dG_healthDot",
99
+ "headTitleRow": "VWh0dG_headTitleRow",
100
+ "roundsBarCol": "VWh0dG_roundsBarCol",
101
+ "healthBadgeBad": "VWh0dG_healthBadgeBad",
110
102
  "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",
103
+ "heatmapGrid": "VWh0dG_heatmapGrid",
104
+ "subscriptionGrid": "VWh0dG_subscriptionGrid",
105
+ "subscriptionWindow": "VWh0dG_subscriptionWindow",
106
+ "chartLegend": "VWh0dG_chartLegend",
107
+ "subscriptionFill": "VWh0dG_subscriptionFill",
108
+ "panelTitle": "VWh0dG_panelTitle",
109
+ "roundsBarLabel": "VWh0dG_roundsBarLabel",
110
+ "heatmapCell": "VWh0dG_heatmapCell",
111
+ "heroLabel": "VWh0dG_heroLabel",
112
+ "heroValue": "VWh0dG_heroValue",
113
+ "heroSideItem": "VWh0dG_heroSideItem",
114
+ "modelDot": "VWh0dG_modelDot",
115
+ "providerGroupMeta": "VWh0dG_providerGroupMeta",
119
116
  "healthBadgeOk": "VWh0dG_healthBadgeOk",
120
- "healthBadgeBad": "VWh0dG_healthBadgeBad",
117
+ "triggerIcon": "VWh0dG_triggerIcon",
118
+ "healthBadge": "VWh0dG_healthBadge",
119
+ "budgetHead": "VWh0dG_budgetHead",
121
120
  "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",
121
+ "heroSide": "VWh0dG_heroSide",
122
+ "currencyToggle": "VWh0dG_currencyToggle",
123
+ "railButton": "VWh0dG_railButton",
124
+ "roundsEmpty": "VWh0dG_roundsEmpty",
125
+ "heatmap": "VWh0dG_heatmap",
126
+ "subscriptionWindowLabel": "VWh0dG_subscriptionWindowLabel",
127
+ "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
128
+ "chartBar": "VWh0dG_chartBar",
129
+ "subscriptionCard": "VWh0dG_subscriptionCard",
130
+ "heroSideLabel": "VWh0dG_heroSideLabel",
131
+ "modelTableScroll": "VWh0dG_modelTableScroll",
132
+ "trigger": "VWh0dG_trigger",
133
+ "chartTooltipDate": "VWh0dG_chartTooltipDate",
134
+ "bandPrice": "VWh0dG_bandPrice",
135
+ "trendPanel": "VWh0dG_trendPanel",
128
136
  "panel": "VWh0dG_panel",
129
- "budgetUnit": "VWh0dG_budgetUnit",
130
- "heroSideValue": "VWh0dG_heroSideValue",
131
- "budgetInput": "VWh0dG_budgetInput",
132
- "chartLegend": "VWh0dG_chartLegend",
133
- "modelTable": "VWh0dG_modelTable",
137
+ "providerGroupList": "VWh0dG_providerGroupList",
138
+ "roundsBars": "VWh0dG_roundsBars",
139
+ "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
140
+ "healthIdle": "VWh0dG_healthIdle",
141
+ "roundsAxis": "VWh0dG_roundsAxis",
134
142
  "budgetControls": "VWh0dG_budgetControls",
135
- "modelCell": "VWh0dG_modelCell",
143
+ "chartLine": "VWh0dG_chartLine",
144
+ "balanceDaysLow": "VWh0dG_balanceDaysLow",
145
+ "providerGroupBadge": "VWh0dG_providerGroupBadge",
146
+ "heroSideValue": "VWh0dG_heroSideValue",
147
+ "pricingToggleText": "VWh0dG_pricingToggleText",
136
148
  "panelHint": "VWh0dG_panelHint",
137
- "budgetValue": "VWh0dG_budgetValue",
149
+ "chartWrap": "VWh0dG_chartWrap",
150
+ "bandTag": "VWh0dG_bandTag",
151
+ "triggerDivider": "VWh0dG_triggerDivider",
152
+ "heatmapHover": "VWh0dG_heatmapHover",
153
+ "kpiValue": "VWh0dG_kpiValue",
154
+ "budgetUnit": "VWh0dG_budgetUnit",
155
+ "chartLegendBar": "VWh0dG_chartLegendBar",
156
+ "switch": "VWh0dG_switch",
157
+ "modelName": "VWh0dG_modelName",
158
+ "chartStack": "VWh0dG_chartStack",
159
+ "roundsBar": "VWh0dG_roundsBar",
160
+ "budgetFill": "VWh0dG_budgetFill",
161
+ "roundsFlagMark": "VWh0dG_roundsFlagMark",
162
+ "dashboard": "VWh0dG_dashboard",
163
+ "budgetInput": "VWh0dG_budgetInput",
138
164
  "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"
165
+ "uncataloguedTag": "VWh0dG_uncataloguedTag",
166
+ "subscriptionPct": "VWh0dG_subscriptionPct",
167
+ "providerGroupTitle": "VWh0dG_providerGroupTitle",
168
+ "dashboardModal": "VWh0dG_dashboardModal",
169
+ "dashboardBody": "VWh0dG_dashboardBody",
170
+ "heroMeta": "VWh0dG_heroMeta",
171
+ "triggerToday": "VWh0dG_triggerToday",
172
+ "delta": "VWh0dG_delta",
173
+ "planTag": "VWh0dG_planTag",
174
+ "providerGroup": "VWh0dG_providerGroup",
175
+ "modelCell": "VWh0dG_modelCell",
176
+ "tableScroll": "VWh0dG_tableScroll",
177
+ "subscriptionReset": "VWh0dG_subscriptionReset",
178
+ "providerGroupHead": "VWh0dG_providerGroupHead",
179
+ "chartDot": "VWh0dG_chartDot",
180
+ "budgetTrack": "VWh0dG_budgetTrack",
181
+ "kpiGrid": "VWh0dG_kpiGrid",
182
+ "rateBadge": "VWh0dG_rateBadge",
183
+ "chartGrid": "VWh0dG_chartGrid",
184
+ "providerGroupName": "VWh0dG_providerGroupName",
185
+ "roundsFlagBadge": "VWh0dG_roundsFlagBadge",
186
+ "healthBad": "VWh0dG_healthBad",
187
+ "roundsBarFlagged": "VWh0dG_roundsBarFlagged",
188
+ "switchKnob": "VWh0dG_switchKnob",
189
+ "rateBadgeLive": "VWh0dG_rateBadgeLive",
190
+ "subscriptionName": "VWh0dG_subscriptionName"
149
191
  };
192
+ //#endregion
193
+ //#region src/client/pricing.ts
194
+ /**
195
+ * USD → CNY rate for display. Source: China Foreign Exchange Trade System
196
+ * mid-rate 6.7878 on 2026-08-14; rounded to 6.79. Only applies to overseas
197
+ * USD-priced models — domestic models never pass through this rate.
198
+ * The node half may refresh this at boot via `/api/billing/pricing`; until a
199
+ * live rate arrives the built-in value stays in force.
200
+ */
201
+ const USD_TO_CNY = 6.79;
150
202
  /** 运行时实时覆盖:undefined = 用内置目录与内置汇率(默认值降级)。 */
151
203
  let liveRate;
152
204
  let livePrices;
@@ -209,6 +261,24 @@ window.__ModuleLoader__.load({
209
261
  },
210
262
  peakHours: "09:00-12:00 / 14:00-18:00"
211
263
  },
264
+ {
265
+ key: "flash-vision-exp",
266
+ name: "DeepSeek V4 Flash Vision (Exp)",
267
+ provider: "DeepSeek",
268
+ colorVar: "dsw-static-blue-500",
269
+ price: {
270
+ currency: "CNY",
271
+ input: 3,
272
+ cacheHit: .1,
273
+ output: 9,
274
+ offPeak: {
275
+ input: 1.5,
276
+ cacheHit: .05,
277
+ output: 4.5
278
+ }
279
+ },
280
+ peakHours: "09:00-12:00 / 14:00-18:00"
281
+ },
212
282
  {
213
283
  key: "pro",
214
284
  name: "DeepSeek V4 Pro",
@@ -649,14 +719,35 @@ window.__ModuleLoader__.load({
649
719
  }
650
720
  }
651
721
  ];
722
+ /**
723
+ * 真实 provider model id → 计费目录键(`MODEL_CATALOG[].key`)的映射。未知 id
724
+ * 原样保留并落回 `other`(未知模型不估算费用)。聚合层(aggregate.ts)在折叠时
725
+ * 用同一张表把日志里的 model id 归并为目录键,客户端渲染(`modelOf`)也按它
726
+ * 解析,两侧共用一份映射,避免同一模型两侧不一致导致「未收录」。
727
+ */
728
+ const MODEL_KEY_ALIASES = {
729
+ "deepseek-v4-flash": "flash",
730
+ "deepseek-v4-flash-vision-exp": "flash-vision-exp",
731
+ "deepseek-v4-pro": "pro",
732
+ "glm-5.2": "glm",
733
+ "qwen3.8-max": "qwen-3.8-max",
734
+ "qwen3.7-max": "qwen-max",
735
+ "qwen-max": "qwen-max",
736
+ "hunyuan-t1": "hunyuan-t1",
737
+ "step-3.7-flash": "step",
738
+ "seed-2.0-mini": "doubao-mini",
739
+ "k3": "kimi-k3",
740
+ "kimi-k3": "kimi-k3"
741
+ };
652
742
  /** Lookup a model by its stats key; falls back to the generic `other` entry. */
653
743
  function modelOf(key) {
654
- const base = MODEL_CATALOG.find((entry) => entry.key === key) ?? (() => {
744
+ const resolved = MODEL_KEY_ALIASES[key] ?? key;
745
+ const base = MODEL_CATALOG.find((entry) => entry.key === resolved) ?? (() => {
655
746
  const fallback = MODEL_CATALOG.at(-1);
656
747
  if (fallback !== void 0) return fallback;
657
748
  throw new Error("MODEL_CATALOG must not be empty");
658
749
  })();
659
- const live = livePrices?.[key];
750
+ const live = livePrices?.[resolved];
660
751
  if (live === void 0) return base;
661
752
  return {
662
753
  ...base,
@@ -707,14 +798,24 @@ window.__ModuleLoader__.load({
707
798
  const off = entry.price.offPeak === void 0 ? peak : priceBandCost(entry.price.offPeak, buckets, entry.price.currency);
708
799
  return peak * peakShare + off * (1 - peakShare);
709
800
  }
710
- /** Format a CNY amount with adaptive precision. */
711
- function formatMoney(cny) {
712
- const value = Number(cny);
713
- if (!Number.isFinite(value)) return "¥0";
714
- if (value >= 1e3) return `¥${value.toFixed(0)}`;
715
- if (value >= 10) return `¥${value.toFixed(1)}`;
716
- if (value >= .1) return `¥${value.toFixed(2)}`;
717
- return `¥${value.toFixed(3)}`;
801
+ /** 人民币 美元(显示换算用):1 USD = {@link USD_TO_CNY} CNY。 */
802
+ function cnyToUsd(cny) {
803
+ return cny / USD_TO_CNY;
804
+ }
805
+ /**
806
+ * Format an amount with adaptive precision and the given currency symbol.
807
+ * @param amount - the amount (CNY by default; pass `usd` for dollar display).
808
+ * @param currency - display currency; default `cny`.
809
+ */
810
+ function formatMoney(amount, currency = "cny") {
811
+ const value = Number(amount);
812
+ if (!Number.isFinite(value)) return currency === "cny" ? "¥0" : "$0";
813
+ const symbol = currency === "cny" ? "¥" : "$";
814
+ if (value <= 0) return `${symbol}0`;
815
+ if (value >= 1e3) return `${symbol}${value.toFixed(0)}`;
816
+ if (value >= 10) return `${symbol}${value.toFixed(1)}`;
817
+ if (value >= .1) return `${symbol}${value.toFixed(2)}`;
818
+ return `${symbol}${value.toFixed(3)}`;
718
819
  }
719
820
  /**
720
821
  * Format a per-1M-token price in its native currency (free when the rate is
@@ -791,9 +892,11 @@ window.__ModuleLoader__.load({
791
892
  * Render the daily stacked cost bars plus the total-calls line.
792
893
  * @param props.data - sorted daily rows (ascending date).
793
894
  * @param props.models - the model legend, in bar order.
895
+ * @param props.currency - display currency for the cost labels.
794
896
  */
795
- function TrendChart({ data, models = [] }) {
897
+ function TrendChart({ data, models = [], currency = "cny" }) {
796
898
  const [hover, setHover] = (0, react.useState)(null);
899
+ const money = (cny) => formatMoney(currency === "usd" ? cnyToUsd(cny) : cny, currency);
797
900
  const layout = (0, react.useMemo)(() => {
798
901
  const n = data.length;
799
902
  if (n === 0) return null;
@@ -902,7 +1005,7 @@ window.__ModuleLoader__.load({
902
1005
  y: y + 3,
903
1006
  textAnchor: "end",
904
1007
  className: UsageBilling_module_css_default.chartAxisLabel,
905
- children: formatMoney(value)
1008
+ children: money(value)
906
1009
  })] }, `cost-${idx}`);
907
1010
  }),
908
1011
  bars.map((bar) => bar.value > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
@@ -969,7 +1072,7 @@ window.__ModuleLoader__.load({
969
1072
  }),
970
1073
  model.name,
971
1074
  " ",
972
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(activePoint.byModel?.[model.key] ?? 0) })
1075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: money(activePoint.byModel?.[model.key] ?? 0) })
973
1076
  ]
974
1077
  }, model.key)),
975
1078
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -977,7 +1080,7 @@ window.__ModuleLoader__.load({
977
1080
  children: [
978
1081
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendBar }),
979
1082
  "总计 ",
980
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(activePoint.cost) })
1083
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: money(activePoint.cost) })
981
1084
  ]
982
1085
  }),
983
1086
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1001,6 +1104,318 @@ window.__ModuleLoader__.load({
1001
1104
  });
1002
1105
  }
1003
1106
  //#endregion
1107
+ //#region src/client/round-chart.tsx
1108
+ /**
1109
+ * RoundCostChart: dependency-free per-turn cost bars with spike markers.
1110
+ *
1111
+ * One bar per turn (most recent N, newest last), height scaled to the window
1112
+ * maximum. Turns flagged by {@link flagAnomalies} get a warning outline and a
1113
+ * corner marker; hover shows the turn's model, cost, and window time. Styling
1114
+ * lives in the billing CSS module (`.rounds*`).
1115
+ */
1116
+ /** Local time `HH:MM`. */
1117
+ function clock(time) {
1118
+ const date = new Date(time);
1119
+ const pad = (n) => String(n).padStart(2, "0");
1120
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
1121
+ }
1122
+ /** Local date + time for the hover line. */
1123
+ function dateTime(time) {
1124
+ const date = new Date(time);
1125
+ const pad = (n) => String(n).padStart(2, "0");
1126
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
1127
+ }
1128
+ /**
1129
+ * Render the per-turn cost bars.
1130
+ * @param props.rounds - turns, most recent last (ascending startedAt); oldest beyond the limit are dropped.
1131
+ * @param props.flags - spike flags matched by sessionId+turn.
1132
+ * @param props.currency - display currency for the amount labels.
1133
+ * @param props.t - locale function for the model label.
1134
+ */
1135
+ function RoundCostChart({ rounds, flags, currency, t }) {
1136
+ const visible = (0, react.useMemo)(() => rounds.slice(-40), [rounds]);
1137
+ const flagKey = (0, react.useMemo)(() => new Set(flags.map((flag) => `${flag.sessionId}:${flag.turn}`)), [flags]);
1138
+ const maxCost = (0, react.useMemo)(() => Math.max(1e-4, ...visible.map((round) => round.cost)), [visible]);
1139
+ if (visible.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1140
+ className: UsageBilling_module_css_default.roundsEmpty,
1141
+ children: [t("billing.model"), " —"]
1142
+ });
1143
+ const money = (cny) => formatMoney(currency === "usd" ? cnyToUsd(cny) : cny, currency);
1144
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1145
+ className: UsageBilling_module_css_default.rounds,
1146
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1147
+ className: UsageBilling_module_css_default.roundsBars,
1148
+ role: "img",
1149
+ "aria-label": "cost per turn",
1150
+ children: visible.map((round) => {
1151
+ const flagged = flagKey.has(`${round.sessionId}:${round.turn}`);
1152
+ const height = Math.max(1, round.cost / maxCost * 100);
1153
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1154
+ className: UsageBilling_module_css_default.roundsBarCol,
1155
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1156
+ className: UsageBilling_module_css_default.roundsBarLabel,
1157
+ children: money(round.cost)
1158
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1159
+ className: UsageBilling_module_css_default.roundsBarWrap,
1160
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1161
+ className: flagged ? UsageBilling_module_css_default.roundsBarFlagged : UsageBilling_module_css_default.roundsBar,
1162
+ style: { height: `${height}%` },
1163
+ "data-testid": "round-bar",
1164
+ title: `${t("billing.model")} ${round.model} · ${money(round.cost)} · ${dateTime(round.startedAt)}${round.endedAt !== void 0 ? ` → ${clock(round.endedAt)}` : ""}`,
1165
+ children: flagged && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1166
+ className: UsageBilling_module_css_default.roundsFlagMark,
1167
+ "aria-hidden": "true"
1168
+ })
1169
+ })
1170
+ })]
1171
+ }, `${round.sessionId}:${round.turn}`);
1172
+ })
1173
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1174
+ className: UsageBilling_module_css_default.roundsAxis,
1175
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1176
+ t("billing.costAbbr"),
1177
+ " ",
1178
+ money(maxCost)
1179
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [visible.length, " 轮"] })]
1180
+ })]
1181
+ });
1182
+ }
1183
+ //#endregion
1184
+ //#region src/client/heatmap.tsx
1185
+ /**
1186
+ * UsageHeatmap: dependency-free month calendar heatmap of daily cost.
1187
+ *
1188
+ * Styled after an "activity map": one large rounded cell per day with the
1189
+ * date number printed inside, laid out in a 7-column grid (Sunday-first).
1190
+ * Week-first data layout: each week is one array element of 7 cells, so the
1191
+ * grid auto-rows place them correctly without per-cell gridColumnStart hacks.
1192
+ * Cell intensity is the day's cost quantized to five levels against the month
1193
+ * maximum (mint-green gradient, like the reference activity map). Leading
1194
+ * slots before the 1st and trailing slots after the last day carry the
1195
+ * cross-month dates as gray placeholders; future days of this month render as
1196
+ * gray placeholders too. Hover shows the exact date and amount.
1197
+ */
1198
+ const LEVEL_COLORS = [
1199
+ "var(--dsw-alias-bg-layer-2)",
1200
+ "color-mix(in srgb, #10b981 22%, var(--dsw-alias-bg-layer-2))",
1201
+ "color-mix(in srgb, #10b981 45%, var(--dsw-alias-bg-layer-2))",
1202
+ "color-mix(in srgb, #10b981 70%, var(--dsw-alias-bg-layer-2))",
1203
+ "#10b981"
1204
+ ];
1205
+ /** Local-time `YYYY-MM-DD` stamp (matches the dashboard's day keys). */
1206
+ function dayStamp(date) {
1207
+ const pad = (n) => String(n).padStart(2, "0");
1208
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
1209
+ }
1210
+ /**
1211
+ * Build the current-month cells arranged in week rows (Sunday-first).
1212
+ * The grid is a complete calendar month view: leading slots before the 1st
1213
+ * carry the previous month's date number, trailing slots after the last day
1214
+ * carry the next month's date number, and every future day of this month is a
1215
+ * gray placeholder — so the rectangle always fills whole weeks, like the
1216
+ * reference activity map.
1217
+ */
1218
+ function buildMonthWeeks(days, now) {
1219
+ const byDate = /* @__PURE__ */ new Map();
1220
+ for (const day of days) byDate.set(day.date, day.value);
1221
+ let max = 0;
1222
+ for (const value of byDate.values()) if (value > max) max = value;
1223
+ const year = now.getFullYear();
1224
+ const month = now.getMonth();
1225
+ const today = now.getDate();
1226
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
1227
+ const firstDow = new Date(year, month, 1).getDay();
1228
+ const totalCells = firstDow + daysInMonth;
1229
+ const totalWeeks = Math.ceil(totalCells / 7);
1230
+ const weeks = [];
1231
+ for (let week = 0; week < totalWeeks; week += 1) {
1232
+ const row = [];
1233
+ for (let col = 0; col < 7; col += 1) {
1234
+ const dayNum = week * 7 + col - firstDow + 1;
1235
+ if (dayNum < 1) {
1236
+ const d = new Date(year, month, dayNum);
1237
+ row.push({
1238
+ date: dayStamp(d),
1239
+ dayNum: d.getDate(),
1240
+ value: 0,
1241
+ level: 0,
1242
+ placeholder: true
1243
+ });
1244
+ continue;
1245
+ }
1246
+ if (dayNum > daysInMonth) {
1247
+ const d = new Date(year, month + 1, dayNum - daysInMonth);
1248
+ row.push({
1249
+ date: dayStamp(d),
1250
+ dayNum: d.getDate(),
1251
+ value: 0,
1252
+ level: 0,
1253
+ placeholder: true
1254
+ });
1255
+ continue;
1256
+ }
1257
+ const iso = dayStamp(new Date(year, month, dayNum));
1258
+ const value = byDate.get(iso) ?? 0;
1259
+ let level = 0;
1260
+ if (value > 0 && max > 0) {
1261
+ const scaled = Math.ceil(value / max * 4);
1262
+ level = Math.min(4, Math.max(1, scaled));
1263
+ }
1264
+ const isFuture = dayNum > today;
1265
+ row.push({
1266
+ date: iso,
1267
+ dayNum,
1268
+ value: isFuture ? 0 : value,
1269
+ level: isFuture ? 0 : level,
1270
+ placeholder: isFuture
1271
+ });
1272
+ }
1273
+ weeks.push(row);
1274
+ }
1275
+ return weeks;
1276
+ }
1277
+ /**
1278
+ * Render the month heatmap.
1279
+ * @param props.days - daily cost rows (keys are `YYYY-MM-DD`).
1280
+ * @param props.currency - display currency for the hover amount.
1281
+ * @param props.now - anchor date (defaults to today); injectable for tests.
1282
+ * @param props.t - locale function (used for the legend labels).
1283
+ */
1284
+ function UsageHeatmap({ days, currency, now, t }) {
1285
+ const [hover, setHover] = (0, react.useState)(null);
1286
+ const weeks = (0, react.useMemo)(() => buildMonthWeeks(days, now ?? /* @__PURE__ */ new Date()), [days, now]);
1287
+ const money = (cny) => formatMoney(currency === "usd" ? cnyToUsd(cny) : cny, currency);
1288
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1289
+ className: UsageBilling_module_css_default.heatmap,
1290
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1291
+ className: UsageBilling_module_css_default.heatmapGrid,
1292
+ role: "img",
1293
+ "aria-label": "daily cost heatmap",
1294
+ children: weeks.map((week) => week.map((cell) => {
1295
+ if (cell.placeholder) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1296
+ className: UsageBilling_module_css_default.heatmapCellEmpty,
1297
+ children: cell.dayNum
1298
+ }, cell.date);
1299
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1300
+ type: "button",
1301
+ className: UsageBilling_module_css_default.heatmapCell,
1302
+ "data-testid": "heatmap-cell",
1303
+ "data-level": cell.level,
1304
+ style: { background: LEVEL_COLORS[cell.level] },
1305
+ title: `${cell.date} · ${money(cell.value)}`,
1306
+ "aria-label": `${cell.date}: ${money(cell.value)}`,
1307
+ onMouseEnter: () => {
1308
+ setHover(cell);
1309
+ },
1310
+ onMouseLeave: () => {
1311
+ setHover(null);
1312
+ },
1313
+ onFocus: () => {
1314
+ setHover(cell);
1315
+ },
1316
+ onBlur: () => {
1317
+ setHover(null);
1318
+ },
1319
+ children: cell.dayNum
1320
+ }, cell.date);
1321
+ }))
1322
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1323
+ className: UsageBilling_module_css_default.heatmapFooter,
1324
+ children: [
1325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1326
+ className: UsageBilling_module_css_default.heatmapLegendText,
1327
+ children: t("billing.heatmapLess")
1328
+ }),
1329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1330
+ className: UsageBilling_module_css_default.heatmapLegend,
1331
+ children: LEVEL_COLORS.map((color, level) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: color } }, level))
1332
+ }),
1333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1334
+ className: UsageBilling_module_css_default.heatmapLegendText,
1335
+ children: t("billing.heatmapMore")
1336
+ }),
1337
+ hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1338
+ className: UsageBilling_module_css_default.heatmapHover,
1339
+ "data-testid": "heatmap-hover",
1340
+ children: [
1341
+ hover.date,
1342
+ " · ",
1343
+ money(hover.value)
1344
+ ]
1345
+ })
1346
+ ]
1347
+ })]
1348
+ });
1349
+ }
1350
+ //#endregion
1351
+ //#region src/client/anomaly.ts
1352
+ const DEFAULTS = {
1353
+ window: 6,
1354
+ threshold: 2,
1355
+ reasonFactor: 1.8,
1356
+ reasonHitDropPp: 15
1357
+ };
1358
+ function mean(values) {
1359
+ if (values.length === 0) return null;
1360
+ let sum = 0;
1361
+ for (const value of values) sum += value;
1362
+ return sum / values.length;
1363
+ }
1364
+ /** 输入侧缓存命中率(百分比);无输入时 null。 */
1365
+ function cacheHitRate(round) {
1366
+ const denominator = round.cacheHit + round.cacheMiss;
1367
+ return denominator <= 0 ? null : round.cacheHit / denominator * 100;
1368
+ }
1369
+ /**
1370
+ * 标记成本异常轮次(按时间顺序传入;最近的轮次排在末尾)。
1371
+ * @param rounds - 按起始时间升序的轮次序列(最早在前)。
1372
+ * @param options - 窗口/阈值/归因灵敏度。
1373
+ * @returns 异常标记数组(保持输入顺序)。
1374
+ */
1375
+ function flagAnomalies(rounds, options) {
1376
+ const opts = {
1377
+ ...DEFAULTS,
1378
+ ...options
1379
+ };
1380
+ const flags = [];
1381
+ if (opts.window <= 0 || opts.threshold <= 0) return flags;
1382
+ const baselineCosts = [];
1383
+ const baselineOutputs = [];
1384
+ const baselineInputs = [];
1385
+ const baselineHits = [];
1386
+ for (const round of rounds) {
1387
+ if (round.cost <= 0) continue;
1388
+ const output = round.output;
1389
+ const input = round.input;
1390
+ const hit = cacheHitRate(round);
1391
+ const baselineCost = mean(baselineCosts);
1392
+ const reasons = [];
1393
+ if (baselineCost !== null && baselineCost > 0 && round.cost > baselineCost * opts.threshold) {
1394
+ const baselineOutput = mean(baselineOutputs);
1395
+ const baselineInput = mean(baselineInputs);
1396
+ const baselineHit = mean(baselineHits);
1397
+ if (baselineOutput !== null && baselineOutput > 0 && output > baselineOutput * opts.reasonFactor) reasons.push("output-growth");
1398
+ if (baselineInput !== null && baselineInput > 0 && input > baselineInput * opts.reasonFactor) reasons.push("context-bloat");
1399
+ if (baselineHit !== null && hit !== null && hit < baselineHit - opts.reasonHitDropPp) reasons.push("cache-hit-drop");
1400
+ flags.push({
1401
+ sessionId: round.sessionId,
1402
+ turn: round.turn,
1403
+ cost: round.cost,
1404
+ reasons
1405
+ });
1406
+ }
1407
+ baselineCosts.push(round.cost);
1408
+ baselineOutputs.push(output);
1409
+ baselineInputs.push(input);
1410
+ if (hit !== null) baselineHits.push(hit);
1411
+ while (baselineCosts.length > opts.window) baselineCosts.shift();
1412
+ while (baselineOutputs.length > opts.window) baselineOutputs.shift();
1413
+ while (baselineInputs.length > opts.window) baselineInputs.shift();
1414
+ while (baselineHits.length > opts.window) baselineHits.shift();
1415
+ }
1416
+ return flags;
1417
+ }
1418
+ //#endregion
1004
1419
  //#region src/client/UsageBilling.tsx
1005
1420
  /**
1006
1421
  * UsageBilling: sidebar footer trigger + full billing dashboard modal.
@@ -1024,7 +1439,7 @@ window.__ModuleLoader__.load({
1024
1439
  const IDLE_HEALTH = {
1025
1440
  checked: false,
1026
1441
  available: false,
1027
- providers: 0,
1442
+ models: 0,
1028
1443
  failures: 0,
1029
1444
  okProviders: [],
1030
1445
  badProviders: []
@@ -1133,6 +1548,36 @@ window.__ModuleLoader__.load({
1133
1548
  if (compact.includes(a)) return display;
1134
1549
  }
1135
1550
  }
1551
+ /**
1552
+ * 订阅套餐 provider id → 所属模型厂商(用于把订阅额度归并到对应厂商组)。
1553
+ * 厂商名与 PROVIDER_ALIASES 保持一致,使订阅卡片与模型用量落在同一组下。
1554
+ * opencode 是跨厂商订阅通道、无单一模型厂商,按自身显示名独立成组。
1555
+ */
1556
+ const SUBSCRIPTION_VENDORS = {
1557
+ "kimi-coding": "月之暗面",
1558
+ "zai-coding-cn": "智谱 AI",
1559
+ "zai-coding": "智谱 AI",
1560
+ "qwen-token-plan": "阿里通义",
1561
+ "qwen-token-plan-cn": "阿里通义",
1562
+ "xiaomi-token-plan-ams": "小米",
1563
+ "xiaomi-token-plan-cn": "小米",
1564
+ "xiaomi-token-plan-sgp": "小米",
1565
+ "volcengine-token-plan": "字节豆包",
1566
+ "ark-token-plan": "字节豆包",
1567
+ "doubao-token-plan": "字节豆包",
1568
+ "ernie": "百度文心",
1569
+ "baidu": "百度文心",
1570
+ "wenxin": "百度文心",
1571
+ "minimax": "MiniMax",
1572
+ "opencode": "OpenCode",
1573
+ "opencode-go": "OpenCode"
1574
+ };
1575
+ /** 订阅套餐归并到的厂商显示名;未知 id 回退为从 model id 反推或 id 本身。 */
1576
+ function subscriptionVendorOf(provider) {
1577
+ const mapped = SUBSCRIPTION_VENDORS[provider];
1578
+ if (mapped !== void 0) return mapped;
1579
+ return providerFromModelKey(provider) ?? provider;
1580
+ }
1136
1581
  /** 余额不足告警的默认阈值(人民币元):宿主 Config 未配置时客户端兜底。 */
1137
1582
  const DEFAULT_LOW_BALANCE_THRESHOLD = 50;
1138
1583
  /**
@@ -1151,12 +1596,34 @@ window.__ModuleLoader__.load({
1151
1596
  if (health.badProviders.some((live) => providerMatches(provider, live))) return UsageBilling_module_css_default.healthBad;
1152
1597
  return UsageBilling_module_css_default.healthIdle;
1153
1598
  }
1599
+ /** 订阅额度查询状态的文案(ok 时无需额外标注,返回空串)。 */
1600
+ function subscriptionStatusText(status, t) {
1601
+ switch (status) {
1602
+ case "ok": return "";
1603
+ case "not-configured": return t("billing.subscriptionNotConfigured");
1604
+ case "unauthorized": return t("billing.subscriptionUnauthorized");
1605
+ case "rate-limited": return t("billing.subscriptionRateLimited");
1606
+ case "invalid-response": return t("billing.subscriptionInvalid");
1607
+ default: return t("billing.subscriptionUnavailable");
1608
+ }
1609
+ }
1610
+ /** 订阅额度窗口的类型标签(本次 / 本周 / 本月 / 计费周期)。 */
1611
+ function subscriptionWindowLabel(kind, t) {
1612
+ switch (kind) {
1613
+ case "session": return t("billing.subscriptionSession");
1614
+ case "weekly": return t("billing.subscriptionWeekly");
1615
+ case "monthly": return t("billing.subscriptionMonthly");
1616
+ case "billing": return t("billing.subscriptionBilling");
1617
+ }
1618
+ }
1154
1619
  /** Path to the usage-stats endpoint served by this plugin's node half. */
1155
1620
  const USAGE_STATS_PATH = "/api/billing/usage-stats";
1156
1621
  /** Path to the live-pricing endpoint served by this plugin's node half. */
1157
1622
  const PRICING_PATH = "/api/billing/pricing";
1158
1623
  /** Path to the account-balance endpoint served by this plugin's node half. */
1159
1624
  const BALANCE_PATH = "/api/billing/balance";
1625
+ /** Path to the subscription-plan quota endpoint served by this plugin's node half. */
1626
+ const SUBSCRIPTIONS_PATH = "/api/billing/subscriptions";
1160
1627
  /** 弹窗打开期间统计与定价的自动刷新间隔(毫秒)。 */
1161
1628
  const STATS_REFRESH_INTERVAL_MS = 3e4;
1162
1629
  /**
@@ -1221,7 +1688,9 @@ window.__ModuleLoader__.load({
1221
1688
  ...candidate.updatedAt !== void 0 ? { updatedAt: candidate.updatedAt } : {},
1222
1689
  ...typeof candidate.budget === "number" ? { budget: candidate.budget } : {},
1223
1690
  ...typeof candidate.lowBalanceThreshold === "number" ? { lowBalanceThreshold: candidate.lowBalanceThreshold } : {},
1224
- ...Array.isArray(candidate.bySession) ? { bySession: candidate.bySession } : {}
1691
+ ...Array.isArray(candidate.bySession) ? { bySession: candidate.bySession } : {},
1692
+ ...Array.isArray(candidate.byTurn) ? { byTurn: candidate.byTurn } : {},
1693
+ ...Array.isArray(candidate.byWorkspace) ? { byWorkspace: candidate.byWorkspace } : {}
1225
1694
  };
1226
1695
  } catch {
1227
1696
  return null;
@@ -1270,6 +1739,22 @@ window.__ModuleLoader__.load({
1270
1739
  }
1271
1740
  }
1272
1741
  /**
1742
+ * 拉取订阅套餐剩余额度(供订阅面板);失败返回空列表。
1743
+ * @returns the quota rows, or an empty list on any failure.
1744
+ */
1745
+ async function fetchSubscriptions() {
1746
+ try {
1747
+ const response = await fetch(SUBSCRIPTIONS_PATH);
1748
+ if (!response.ok) return [];
1749
+ const text = await response.text();
1750
+ const parsed = JSON.parse(text);
1751
+ if (parsed !== null && typeof parsed === "object" && "quotas" in parsed) return parsed.quotas;
1752
+ return [];
1753
+ } catch {
1754
+ return [];
1755
+ }
1756
+ }
1757
+ /**
1273
1758
  * Sidebar footer trigger: compact pill in wide mode, icon in rail mode.
1274
1759
  * ZINE 模式下入口由主题插件的贴纸层承担,本触发器由 CSS
1275
1760
  * (body[data-zine-mode] 选择器)隐藏,组件本身无 zine 分支。
@@ -1350,12 +1835,17 @@ window.__ModuleLoader__.load({
1350
1835
  * The centered billing dashboard modal.
1351
1836
  * @param props - stats, locale function, close handler, model health, balances, renderSlot.
1352
1837
  */
1353
- function BillingDashboard({ stats, t, onClose, health, balances, renderSlot, budgetEnabled, budgetAmount, onToggleBudget, onBudgetAmount }) {
1838
+ function BillingDashboard({ stats, t, onClose, health, balances, quotas, currency, onCurrency, turns, renderSlot, budgetEnabled, budgetAmount, onToggleBudget, onBudgetAmount }) {
1354
1839
  const { total, byModel, byDay } = stats;
1355
1840
  const [pricingOpen, setPricingOpen] = (0, react.useState)(false);
1356
1841
  const [sessionsOpen, setSessionsOpen] = (0, react.useState)(false);
1842
+ const [roundsOpen, setRoundsOpen] = (0, react.useState)(false);
1843
+ const [heatmapOpen, setHeatmapOpen] = (0, react.useState)(true);
1844
+ const [workspacesOpen, setWorkspacesOpen] = (0, react.useState)(false);
1357
1845
  const [trendDays, setTrendDays] = (0, react.useState)(7);
1358
1846
  const rateInfo = getRateInfo();
1847
+ const money = (cny) => formatMoney(currency === "usd" ? cnyToUsd(cny) : cny, currency);
1848
+ const roundFlags = (0, react.useMemo)(() => flagAnomalies([...turns].reverse()), [turns]);
1359
1849
  const dailyBurn = dailyBurnRate(byDay, localDayStamp());
1360
1850
  const balanceFor = (provider) => balances.find((balance) => normalizeProvider(balance.provider) === normalizeProvider(provider));
1361
1851
  const renderBalance = (balance) => {
@@ -1370,7 +1860,7 @@ window.__ModuleLoader__.load({
1370
1860
  className: UsageBilling_module_css_default.na,
1371
1861
  children: "—"
1372
1862
  });
1373
- const amount = balance.currency === "USD" ? `$${balance.totalBalance.toFixed(2)}` : formatMoney(balance.totalBalance);
1863
+ const amount = balance.currency === "USD" ? `$${balance.totalBalance.toFixed(2)}` : money(balance.totalBalance);
1374
1864
  const balanceCny = balance.currency === "USD" ? balance.totalBalance * rateInfo.rate : balance.totalBalance;
1375
1865
  if (dailyBurn <= 0) return amount;
1376
1866
  const days = Math.floor(balanceCny / dailyBurn);
@@ -1402,6 +1892,10 @@ window.__ModuleLoader__.load({
1402
1892
  return out;
1403
1893
  }, [trendDays]);
1404
1894
  const latestDate = trendDates.at(-1) ?? today;
1895
+ const heatmapDays = (0, react.useMemo)(() => Object.entries(byDay).map(([date, day]) => ({
1896
+ date,
1897
+ value: day.cost
1898
+ })), [byDay]);
1405
1899
  const trend = (0, react.useMemo)(() => trendDates.map((date) => {
1406
1900
  const byModel = {};
1407
1901
  const dayModels = stats.byDayModels?.[date];
@@ -1447,6 +1941,42 @@ window.__ModuleLoader__.load({
1447
1941
  ...row,
1448
1942
  color: CHART_PALETTE[index % CHART_PALETTE.length] ?? "#8b95a3"
1449
1943
  })), [byModel]);
1944
+ const providerGroups = (0, react.useMemo)(() => {
1945
+ const subscriptionsByVendor = /* @__PURE__ */ new Map();
1946
+ for (const quota of quotas) {
1947
+ if (quota.status === "not-configured") continue;
1948
+ const vendor = subscriptionVendorOf(quota.provider);
1949
+ const list = subscriptionsByVendor.get(vendor);
1950
+ if (list === void 0) subscriptionsByVendor.set(vendor, [quota]);
1951
+ else list.push(quota);
1952
+ }
1953
+ const modelsByVendor = /* @__PURE__ */ new Map();
1954
+ for (const row of modelRows) {
1955
+ const list = modelsByVendor.get(row.provider);
1956
+ if (list === void 0) modelsByVendor.set(row.provider, [row]);
1957
+ else list.push(row);
1958
+ }
1959
+ return [...new Set([...modelsByVendor.keys(), ...subscriptionsByVendor.keys()])].map((name) => ({
1960
+ name,
1961
+ models: modelsByVendor.get(name) ?? [],
1962
+ subscriptions: subscriptionsByVendor.get(name) ?? [],
1963
+ balance: balanceFor(name),
1964
+ dot: providerDot(health, name)
1965
+ })).sort((a, b) => {
1966
+ const costOf = (group) => group.models.reduce((sum, m) => sum + (m.actual ?? m.estimated), 0);
1967
+ const diff = costOf(b) - costOf(a);
1968
+ if (diff !== 0) return diff;
1969
+ if (a.models.length === 0 && b.models.length === 0) return 0;
1970
+ if (b.models.length === 0) return -1;
1971
+ if (a.models.length === 0) return 1;
1972
+ return a.name.localeCompare(b.name, "zh");
1973
+ });
1974
+ }, [
1975
+ modelRows,
1976
+ quotas,
1977
+ balances,
1978
+ health
1979
+ ]);
1450
1980
  const estimatedTotal = modelRows.reduce((sum, row) => sum + row.estimated, 0);
1451
1981
  const displayTotal = total.cost > 0 ? total.cost : estimatedTotal;
1452
1982
  const avgPerCall = total.calls > 0 ? displayTotal / total.calls : 0;
@@ -1488,26 +2018,45 @@ window.__ModuleLoader__.load({
1488
2018
  })
1489
2019
  ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1490
2020
  className: UsageBilling_module_css_default.dashboardRight,
1491
- children: [health.checked && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1492
- className: clsx(UsageBilling_module_css_default.healthBadge, health.available ? UsageBilling_module_css_default.healthBadgeOk : UsageBilling_module_css_default.healthBadgeBad),
1493
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1494
- className: clsx(UsageBilling_module_css_default.healthDot, health.available ? UsageBilling_module_css_default.healthOk : UsageBilling_module_css_default.healthBad),
1495
- "aria-hidden": "true"
1496
- }), health.available ? `${health.providers} 模型可用${health.failures > 0 ? ` · ${health.failures} 失效` : ""}` : `${health.failures} 模型不可用`]
1497
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1498
- type: "button",
1499
- className: UsageBilling_module_css_default.closeButton,
1500
- "aria-label": t("billing.close"),
1501
- "data-testid": "billing-close",
1502
- onClick: onClose,
1503
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1504
- viewBox: "0 0 24 24",
1505
- fill: "none",
1506
- stroke: "currentColor",
1507
- "aria-hidden": "true",
1508
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })]
2021
+ children: [
2022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2023
+ className: UsageBilling_module_css_default.currencyToggle,
2024
+ role: "group",
2025
+ "aria-label": t("billing.currency"),
2026
+ children: ["cny", "usd"].map((unit) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2027
+ type: "button",
2028
+ className: clsx(UsageBilling_module_css_default.currencyButton, currency === unit && UsageBilling_module_css_default.currencyButtonActive),
2029
+ "aria-pressed": currency === unit,
2030
+ "data-testid": `billing-currency-${unit}`,
2031
+ title: unit === "cny" ? t("billing.currencyCny") : t("billing.currencyUsd"),
2032
+ onClick: () => {
2033
+ onCurrency(unit);
2034
+ },
2035
+ children: unit === "cny" ? "¥" : "$"
2036
+ }, unit))
2037
+ }),
2038
+ health.checked && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2039
+ className: clsx(UsageBilling_module_css_default.healthBadge, health.available ? UsageBilling_module_css_default.healthBadgeOk : UsageBilling_module_css_default.healthBadgeBad),
2040
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2041
+ className: clsx(UsageBilling_module_css_default.healthDot, health.available ? UsageBilling_module_css_default.healthOk : UsageBilling_module_css_default.healthBad),
2042
+ "aria-hidden": "true"
2043
+ }), health.available ? `${health.models} 模型可用${health.failures > 0 ? ` · ${health.failures} 厂商失效` : ""}` : `${health.failures} 厂商不可用`]
2044
+ }),
2045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2046
+ type: "button",
2047
+ className: UsageBilling_module_css_default.closeButton,
2048
+ "aria-label": t("billing.close"),
2049
+ "data-testid": "billing-close",
2050
+ onClick: onClose,
2051
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2052
+ viewBox: "0 0 24 24",
2053
+ fill: "none",
2054
+ stroke: "currentColor",
2055
+ "aria-hidden": "true",
2056
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })]
2057
+ })
1509
2058
  })
1510
- })]
2059
+ ]
1511
2060
  })]
1512
2061
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1513
2062
  className: UsageBilling_module_css_default.dashboardBody,
@@ -1526,7 +2075,7 @@ window.__ModuleLoader__.load({
1526
2075
  }),
1527
2076
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1528
2077
  className: UsageBilling_module_css_default.heroValue,
1529
- children: formatMoney(monthCost)
2078
+ children: money(monthCost)
1530
2079
  }),
1531
2080
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1532
2081
  className: UsageBilling_module_css_default.heroMeta,
@@ -1547,7 +2096,7 @@ window.__ModuleLoader__.load({
1547
2096
  children: t("billing.yearCost")
1548
2097
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1549
2098
  className: UsageBilling_module_css_default.heroSideValue,
1550
- children: formatMoney(yearCost)
2099
+ children: money(yearCost)
1551
2100
  })]
1552
2101
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1553
2102
  className: UsageBilling_module_css_default.heroSideItem,
@@ -1556,7 +2105,7 @@ window.__ModuleLoader__.load({
1556
2105
  children: t("billing.todayCost")
1557
2106
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1558
2107
  className: UsageBilling_module_css_default.heroSideValue,
1559
- children: [formatMoney(todayCost), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2108
+ children: [money(todayCost), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1560
2109
  className: clsx(UsageBilling_module_css_default.delta, deltaPct >= 0 ? UsageBilling_module_css_default.deltaUp : UsageBilling_module_css_default.deltaDown),
1561
2110
  children: [
1562
2111
  deltaPct >= 0 ? "▲" : "▼",
@@ -1609,9 +2158,9 @@ window.__ModuleLoader__.load({
1609
2158
  className: UsageBilling_module_css_default.budgetValue,
1610
2159
  "data-testid": "billing-budget-value",
1611
2160
  children: [
1612
- formatMoney(monthCost),
2161
+ money(monthCost),
1613
2162
  " / ",
1614
- formatMoney(budgetAmount),
2163
+ money(budgetAmount),
1615
2164
  " · ",
1616
2165
  pct.toFixed(1),
1617
2166
  "%"
@@ -1702,7 +2251,7 @@ window.__ModuleLoader__.load({
1702
2251
  }),
1703
2252
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1704
2253
  className: UsageBilling_module_css_default.kpiValue,
1705
- children: formatMoney(avgPerCall)
2254
+ children: money(avgPerCall)
1706
2255
  }),
1707
2256
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1708
2257
  className: UsageBilling_module_css_default.kpiDetail,
@@ -1770,19 +2319,20 @@ window.__ModuleLoader__.load({
1770
2319
  ]
1771
2320
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
1772
2321
  data: trend,
1773
- models: chartModels
2322
+ models: chartModels,
2323
+ currency
1774
2324
  })]
1775
2325
  }),
1776
2326
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1777
2327
  className: UsageBilling_module_css_default.panel,
1778
- "data-testid": "billing-panel-models",
2328
+ "data-testid": "billing-panel-providers",
1779
2329
  children: [
1780
2330
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1781
2331
  className: UsageBilling_module_css_default.panelHead,
1782
2332
  children: [
1783
2333
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1784
2334
  className: UsageBilling_module_css_default.panelTitle,
1785
- children: t("billing.models")
2335
+ children: t("billing.providerBilling")
1786
2336
  }),
1787
2337
  renderSlot("billing.dashboard.decor", { position: "models" }),
1788
2338
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1791,95 +2341,328 @@ window.__ModuleLoader__.load({
1791
2341
  })
1792
2342
  ]
1793
2343
  }),
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")
2344
+ providerGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2345
+ className: UsageBilling_module_css_default.emptyRow,
2346
+ "data-testid": "billing-provider-empty",
2347
+ children: t("billing.noData")
2348
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2349
+ className: UsageBilling_module_css_default.providerGroupList,
2350
+ "data-testid": "billing-provider-groups",
2351
+ children: providerGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2352
+ className: UsageBilling_module_css_default.providerGroup,
2353
+ "data-testid": "billing-provider-group",
2354
+ children: [
2355
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2356
+ className: UsageBilling_module_css_default.providerGroupHead,
2357
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2358
+ className: UsageBilling_module_css_default.providerGroupTitle,
2359
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2360
+ className: clsx(UsageBilling_module_css_default.healthDot, group.dot),
2361
+ "aria-hidden": "true"
2362
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2363
+ className: UsageBilling_module_css_default.providerGroupName,
2364
+ children: group.name
1841
2365
  })]
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)
2366
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2367
+ className: UsageBilling_module_css_default.providerGroupMeta,
2368
+ children: [group.subscriptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2369
+ className: UsageBilling_module_css_default.providerGroupBadge,
2370
+ "data-testid": "billing-provider-sub-count",
2371
+ children: [group.subscriptions.length, " 套餐"]
2372
+ }), group.balance !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2373
+ className: UsageBilling_module_css_default.providerGroupBalance,
2374
+ "data-testid": "billing-provider-balance",
2375
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2376
+ className: UsageBilling_module_css_default.providerGroupBalanceLabel,
2377
+ children: t("billing.balance")
2378
+ }), renderBalance(group.balance)]
2379
+ })]
2380
+ })]
1862
2381
  }),
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: ""
2382
+ group.models.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2383
+ className: clsx(UsageBilling_module_css_default.tableScroll, UsageBilling_module_css_default.modelTableScroll),
2384
+ "data-testid": "billing-table-scroll",
2385
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2386
+ className: UsageBilling_module_css_default.modelTable,
2387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.model") }),
2389
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2390
+ className: UsageBilling_module_css_default.numCol,
2391
+ children: t("billing.calls")
2392
+ }),
2393
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2394
+ className: UsageBilling_module_css_default.numCol,
2395
+ children: t("billing.inputTokens")
2396
+ }),
2397
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2398
+ className: UsageBilling_module_css_default.numCol,
2399
+ children: t("billing.outputTokens")
2400
+ }),
2401
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2402
+ className: UsageBilling_module_css_default.numCol,
2403
+ children: t("billing.cacheHitRate")
2404
+ }),
2405
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2406
+ className: UsageBilling_module_css_default.numCol,
2407
+ children: t("billing.actual")
2408
+ })
2409
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: group.models.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2411
+ className: UsageBilling_module_css_default.modelCell,
2412
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2413
+ className: UsageBilling_module_css_default.modelName,
2414
+ children: [row.name, row.uncatalogued && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2415
+ className: UsageBilling_module_css_default.uncataloguedTag,
2416
+ "data-testid": "billing-uncatalogued-tag",
2417
+ children: t("billing.uncatalogued")
2418
+ })]
2419
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2420
+ className: UsageBilling_module_css_default.modelProvider,
2421
+ children: row.provider
2422
+ })] })
2423
+ }) }),
2424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2425
+ className: UsageBilling_module_css_default.numCol,
2426
+ children: row.calls.toLocaleString()
2427
+ }),
2428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2429
+ className: UsageBilling_module_css_default.numCol,
2430
+ children: formatTokens(row.input)
2431
+ }),
2432
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2433
+ className: UsageBilling_module_css_default.numCol,
2434
+ children: formatTokens(row.output)
2435
+ }),
2436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2437
+ className: UsageBilling_module_css_default.numCol,
2438
+ children: formatPercent(row.cacheHitRate)
2439
+ }),
2440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2441
+ className: UsageBilling_module_css_default.numCol,
2442
+ children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2443
+ className: UsageBilling_module_css_default.planTag,
2444
+ children: "订阅包含"
2445
+ }) : row.actual !== void 0 ? money(row.actual) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2446
+ className: UsageBilling_module_css_default.na,
2447
+ children: "—"
2448
+ })
2449
+ })
2450
+ ] }, row.key)) })]
1871
2451
  })
1872
2452
  }),
1873
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1874
- className: UsageBilling_module_css_default.numCol,
1875
- children: renderBalance(balanceFor(row.provider))
2453
+ group.subscriptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2454
+ className: UsageBilling_module_css_default.subscriptionGrid,
2455
+ "data-testid": "billing-subscriptions-grid",
2456
+ children: group.subscriptions.map((quota) => {
2457
+ const statusText = subscriptionStatusText(quota.status, t);
2458
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2459
+ className: UsageBilling_module_css_default.subscriptionCard,
2460
+ "data-testid": "billing-subscription-card",
2461
+ children: [
2462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2463
+ className: UsageBilling_module_css_default.subscriptionHead,
2464
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2465
+ className: UsageBilling_module_css_default.subscriptionName,
2466
+ children: quota.displayName
2467
+ }), quota.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2468
+ className: UsageBilling_module_css_default.subscriptionPlan,
2469
+ children: quota.plan
2470
+ })]
2471
+ }),
2472
+ statusText !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2473
+ className: UsageBilling_module_css_default.subscriptionStatus,
2474
+ children: statusText
2475
+ }),
2476
+ quota.windows.length === 0 && statusText === "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2477
+ className: UsageBilling_module_css_default.subscriptionStatus,
2478
+ children: t("billing.subscriptionNoApi")
2479
+ }),
2480
+ quota.windows.map((window) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2481
+ className: UsageBilling_module_css_default.subscriptionWindow,
2482
+ children: [
2483
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2484
+ className: UsageBilling_module_css_default.subscriptionWindowLabel,
2485
+ children: subscriptionWindowLabel(window.kind, t)
2486
+ }),
2487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2488
+ className: UsageBilling_module_css_default.subscriptionTrack,
2489
+ "aria-hidden": "true",
2490
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2491
+ className: UsageBilling_module_css_default.subscriptionFill,
2492
+ style: { width: `${Math.min(100, Math.max(0, window.remainingPercent))}%` }
2493
+ })
2494
+ }),
2495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2496
+ className: UsageBilling_module_css_default.subscriptionPct,
2497
+ children: t("billing.subscriptionRemaining").replace("{pct}", String(window.remainingPercent))
2498
+ }),
2499
+ window.resetsAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2500
+ className: UsageBilling_module_css_default.subscriptionReset,
2501
+ children: t("billing.subscriptionReset").replace("{date}", window.resetsAt.slice(0, 10))
2502
+ })
2503
+ ]
2504
+ }, window.kind))
2505
+ ]
2506
+ }, quota.provider);
2507
+ })
1876
2508
  })
1877
- ] }, row.key))] })]
1878
- })
2509
+ ]
2510
+ }, group.name))
1879
2511
  }),
1880
2512
  renderSlot("billing.dashboard.decor", { position: "footer" })
1881
2513
  ]
1882
2514
  }),
2515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2516
+ className: UsageBilling_module_css_default.panel,
2517
+ "data-testid": "billing-panel-heatmap",
2518
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2519
+ type: "button",
2520
+ className: UsageBilling_module_css_default.pricingToggle,
2521
+ "data-testid": "billing-heatmap-toggle",
2522
+ onClick: () => {
2523
+ setHeatmapOpen((prev) => !prev);
2524
+ },
2525
+ "aria-expanded": heatmapOpen,
2526
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2527
+ className: UsageBilling_module_css_default.pricingToggleText,
2528
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2529
+ className: UsageBilling_module_css_default.panelTitle,
2530
+ children: t("billing.heatmap")
2531
+ })
2532
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2533
+ className: clsx(UsageBilling_module_css_default.pricingChevron, heatmapOpen && UsageBilling_module_css_default.pricingChevronOpen),
2534
+ viewBox: "0 0 24 24",
2535
+ fill: "none",
2536
+ stroke: "currentColor",
2537
+ "aria-hidden": "true",
2538
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
2539
+ })]
2540
+ }), heatmapOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageHeatmap, {
2541
+ days: heatmapDays,
2542
+ currency,
2543
+ t
2544
+ })]
2545
+ }),
2546
+ turns.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2547
+ className: UsageBilling_module_css_default.panel,
2548
+ "data-testid": "billing-panel-rounds",
2549
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2550
+ type: "button",
2551
+ className: UsageBilling_module_css_default.pricingToggle,
2552
+ "data-testid": "billing-rounds-toggle",
2553
+ onClick: () => {
2554
+ setRoundsOpen((prev) => !prev);
2555
+ },
2556
+ "aria-expanded": roundsOpen,
2557
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2558
+ className: UsageBilling_module_css_default.pricingToggleText,
2559
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2560
+ className: UsageBilling_module_css_default.panelTitle,
2561
+ children: t("billing.rounds")
2562
+ }), roundFlags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2563
+ className: UsageBilling_module_css_default.roundsFlagBadge,
2564
+ "data-testid": "billing-rounds-flag-count",
2565
+ children: [
2566
+ roundFlags.length,
2567
+ " ",
2568
+ t("billing.anomaly")
2569
+ ]
2570
+ })]
2571
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2572
+ className: clsx(UsageBilling_module_css_default.pricingChevron, roundsOpen && UsageBilling_module_css_default.pricingChevronOpen),
2573
+ viewBox: "0 0 24 24",
2574
+ fill: "none",
2575
+ stroke: "currentColor",
2576
+ "aria-hidden": "true",
2577
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
2578
+ })]
2579
+ }), roundsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoundCostChart, {
2580
+ rounds: turns,
2581
+ flags: roundFlags,
2582
+ currency,
2583
+ t
2584
+ })]
2585
+ }),
2586
+ stats.byWorkspace !== void 0 && stats.byWorkspace.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2587
+ className: UsageBilling_module_css_default.panel,
2588
+ "data-testid": "billing-panel-workspaces",
2589
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2590
+ type: "button",
2591
+ className: UsageBilling_module_css_default.pricingToggle,
2592
+ "data-testid": "billing-workspaces-toggle",
2593
+ onClick: () => {
2594
+ setWorkspacesOpen((prev) => !prev);
2595
+ },
2596
+ "aria-expanded": workspacesOpen,
2597
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2598
+ className: UsageBilling_module_css_default.pricingToggleText,
2599
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2600
+ className: UsageBilling_module_css_default.panelTitle,
2601
+ children: t("billing.workspaces")
2602
+ })
2603
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2604
+ className: clsx(UsageBilling_module_css_default.pricingChevron, workspacesOpen && UsageBilling_module_css_default.pricingChevronOpen),
2605
+ viewBox: "0 0 24 24",
2606
+ fill: "none",
2607
+ stroke: "currentColor",
2608
+ "aria-hidden": "true",
2609
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
2610
+ })]
2611
+ }), workspacesOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2612
+ className: UsageBilling_module_css_default.tableScroll,
2613
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2614
+ className: UsageBilling_module_css_default.modelTable,
2615
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2616
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("billing.project") }),
2617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2618
+ className: UsageBilling_module_css_default.numCol,
2619
+ children: t("billing.calls")
2620
+ }),
2621
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2622
+ className: UsageBilling_module_css_default.numCol,
2623
+ children: t("billing.inputTokens")
2624
+ }),
2625
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2626
+ className: UsageBilling_module_css_default.numCol,
2627
+ children: t("billing.outputTokens")
2628
+ }),
2629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2630
+ className: UsageBilling_module_css_default.numCol,
2631
+ children: t("billing.actual")
2632
+ }),
2633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2634
+ className: UsageBilling_module_css_default.numCol,
2635
+ children: t("billing.lastActive")
2636
+ })
2637
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: stats.byWorkspace.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2638
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2639
+ className: UsageBilling_module_css_default.modelName,
2640
+ children: row.name
2641
+ }) }),
2642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2643
+ className: UsageBilling_module_css_default.numCol,
2644
+ children: row.calls.toLocaleString()
2645
+ }),
2646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2647
+ className: UsageBilling_module_css_default.numCol,
2648
+ children: formatTokens(row.input)
2649
+ }),
2650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2651
+ className: UsageBilling_module_css_default.numCol,
2652
+ children: formatTokens(row.output)
2653
+ }),
2654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2655
+ className: UsageBilling_module_css_default.numCol,
2656
+ children: money(row.cost)
2657
+ }),
2658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2659
+ className: UsageBilling_module_css_default.numCol,
2660
+ children: row.lastActive > 0 ? `${localDayStamp(row.lastActive)}` : "—"
2661
+ })
2662
+ ] }, row.name)) })]
2663
+ })
2664
+ })]
2665
+ }),
1883
2666
  stats.bySession !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1884
2667
  className: UsageBilling_module_css_default.panel,
1885
2668
  "data-testid": "billing-panel-sessions",
@@ -1947,7 +2730,7 @@ window.__ModuleLoader__.load({
1947
2730
  }),
1948
2731
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1949
2732
  className: UsageBilling_module_css_default.numCol,
1950
- children: formatMoney(row.cost)
2733
+ children: money(row.cost)
1951
2734
  }),
1952
2735
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1953
2736
  className: UsageBilling_module_css_default.numCol,
@@ -2088,6 +2871,8 @@ window.__ModuleLoader__.load({
2088
2871
  const [stats, setStats] = (0, react.useState)(EMPTY_STATS);
2089
2872
  const [health, setHealth] = (0, react.useState)(IDLE_HEALTH);
2090
2873
  const [balances, setBalances] = (0, react.useState)([]);
2874
+ const [quotas, setQuotas] = (0, react.useState)([]);
2875
+ const [currency, setCurrency] = (0, react.useState)("cny");
2091
2876
  const [open, setOpen] = (0, react.useState)(false);
2092
2877
  const close = (0, react.useCallback)(() => {
2093
2878
  setOpen(false);
@@ -2099,6 +2884,9 @@ window.__ModuleLoader__.load({
2099
2884
  fetchBalances().then((list) => {
2100
2885
  if (list.length > 0) setBalances(list);
2101
2886
  });
2887
+ fetchSubscriptions().then((list) => {
2888
+ if (list.length > 0) setQuotas(list);
2889
+ });
2102
2890
  }, []);
2103
2891
  const openDashboard = (0, react.useCallback)(() => {
2104
2892
  reloadStats();
@@ -2209,6 +2997,7 @@ window.__ModuleLoader__.load({
2209
2997
  publishCosts
2210
2998
  ]);
2211
2999
  (0, react.useEffect)(() => registerOpen(openDashboard), [registerOpen, openDashboard]);
3000
+ const turns = (0, react.useMemo)(() => stats.byTurn ?? [], [stats.byTurn]);
2212
3001
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBillingTrigger, {
2213
3002
  ...props,
2214
3003
  onOpen: openDashboard,
@@ -2220,6 +3009,10 @@ window.__ModuleLoader__.load({
2220
3009
  onClose: close,
2221
3010
  health,
2222
3011
  balances,
3012
+ quotas,
3013
+ currency,
3014
+ onCurrency: setCurrency,
3015
+ turns,
2223
3016
  renderSlot,
2224
3017
  budgetEnabled,
2225
3018
  budgetAmount: effectiveBudget,
@@ -2255,6 +3048,7 @@ window.__ModuleLoader__.load({
2255
3048
  "billing.sessionOverflow": "仅显示花费前 {limit} 个,共 {total} 个会话",
2256
3049
  "billing.budgetOverBody": "本月花费 {cost} 已超过预算 {budget}({pct}%)",
2257
3050
  "billing.models": "模型计费明细",
3051
+ "billing.providerBilling": "厂商计费与订阅",
2258
3052
  "billing.estimated": "估算",
2259
3053
  "billing.actual": "实际",
2260
3054
  "billing.pricing": "模型单价表",
@@ -2282,7 +3076,35 @@ window.__ModuleLoader__.load({
2282
3076
  "billing.balanceUnreachable": "查询失败",
2283
3077
  "billing.uncatalogued": "未收录",
2284
3078
  "billing.balanceDays": "约可撑 {days} 天",
2285
- "billing.balanceLowBody": "{name} 余额 {balance},约可撑 {days} 天,请及时充值"
3079
+ "billing.balanceLowBody": "{name} 余额 {balance},约可撑 {days} 天,请及时充值",
3080
+ "billing.subscriptions": "订阅套餐",
3081
+ "billing.subscriptionNotConfigured": "未配置密钥",
3082
+ "billing.subscriptionUnauthorized": "密钥无效",
3083
+ "billing.subscriptionUnavailable": "查询失败",
3084
+ "billing.subscriptionInvalid": "响应异常",
3085
+ "billing.subscriptionRateLimited": "触发限流",
3086
+ "billing.subscriptionSession": "本次",
3087
+ "billing.subscriptionWeekly": "本周",
3088
+ "billing.subscriptionMonthly": "本月",
3089
+ "billing.subscriptionBilling": "计费周期",
3090
+ "billing.subscriptionRemaining": "剩余 {pct}%",
3091
+ "billing.subscriptionReset": "{date} 重置",
3092
+ "billing.subscriptionNoApi": "该厂商暂未提供用量查询接口",
3093
+ "billing.heatmapLess": "少",
3094
+ "billing.heatmapMore": "多",
3095
+ "billing.currency": "币种",
3096
+ "billing.currencyCny": "人民币",
3097
+ "billing.currencyUsd": "美元",
3098
+ "billing.heatmap": "用量热力图",
3099
+ "billing.rounds": "每轮费用",
3100
+ "billing.anomaly": "成本突增",
3101
+ "billing.workspaces": "工作区统计",
3102
+ "billing.plan": "套餐",
3103
+ "billing.remaining": "剩余",
3104
+ "billing.unknownModel": "未定价",
3105
+ "billing.model": "模型",
3106
+ "billing.currentRound": "当前",
3107
+ "billing.costAbbr": "费用"
2286
3108
  };
2287
3109
  const en = {
2288
3110
  "billing.title": "Usage",
@@ -2309,6 +3131,7 @@ window.__ModuleLoader__.load({
2309
3131
  "billing.sessionOverflow": "Top {limit} of {total} sessions by cost",
2310
3132
  "billing.budgetOverBody": "This month {cost} exceeded the budget {budget} ({pct}%)",
2311
3133
  "billing.models": "Model billing",
3134
+ "billing.providerBilling": "Provider billing & subscriptions",
2312
3135
  "billing.estimated": "Est.",
2313
3136
  "billing.actual": "Actual",
2314
3137
  "billing.pricing": "Model pricing",
@@ -2336,7 +3159,35 @@ window.__ModuleLoader__.load({
2336
3159
  "billing.balanceUnreachable": "Unavailable",
2337
3160
  "billing.uncatalogued": "Not catalogued",
2338
3161
  "billing.balanceDays": "~{days} days left",
2339
- "billing.balanceLowBody": "{name} balance {balance}, ~{days} days left, please top up"
3162
+ "billing.balanceLowBody": "{name} balance {balance}, ~{days} days left, please top up",
3163
+ "billing.subscriptions": "Subscriptions",
3164
+ "billing.subscriptionNotConfigured": "Key not set",
3165
+ "billing.subscriptionUnauthorized": "Bad key",
3166
+ "billing.subscriptionUnavailable": "Unavailable",
3167
+ "billing.subscriptionInvalid": "Bad response",
3168
+ "billing.subscriptionRateLimited": "Rate limited",
3169
+ "billing.subscriptionSession": "Current",
3170
+ "billing.subscriptionWeekly": "Weekly",
3171
+ "billing.subscriptionMonthly": "Monthly",
3172
+ "billing.subscriptionBilling": "Billing",
3173
+ "billing.subscriptionRemaining": "{pct}% left",
3174
+ "billing.subscriptionReset": "Resets {date}",
3175
+ "billing.subscriptionNoApi": "Provider does not offer a usage API",
3176
+ "billing.heatmapLess": "Less",
3177
+ "billing.heatmapMore": "More",
3178
+ "billing.currency": "Currency",
3179
+ "billing.currencyCny": "CNY",
3180
+ "billing.currencyUsd": "USD",
3181
+ "billing.heatmap": "Usage heatmap",
3182
+ "billing.rounds": "Cost per turn",
3183
+ "billing.anomaly": "Cost spike",
3184
+ "billing.workspaces": "Workspaces",
3185
+ "billing.plan": "Plan",
3186
+ "billing.remaining": "Left",
3187
+ "billing.unknownModel": "Unpriced",
3188
+ "billing.model": "Model",
3189
+ "billing.currentRound": "current",
3190
+ "billing.costAbbr": "cost"
2340
3191
  };
2341
3192
  //#endregion
2342
3193
  //#region src/client/billing-service.ts
@@ -2444,7 +3295,7 @@ window.__ModuleLoader__.load({
2444
3295
  if (!result.ok) return {
2445
3296
  checked: true,
2446
3297
  available: false,
2447
- providers: 0,
3298
+ models: 0,
2448
3299
  failures: 0,
2449
3300
  okProviders: [],
2450
3301
  badProviders: []
@@ -2452,7 +3303,7 @@ window.__ModuleLoader__.load({
2452
3303
  return {
2453
3304
  checked: true,
2454
3305
  available: result.value.groups.length > 0,
2455
- providers: result.value.groups.length,
3306
+ models: result.value.groups.reduce((sum, group) => sum + group.models.length, 0),
2456
3307
  failures: result.value.failures.length,
2457
3308
  okProviders: result.value.groups.map((group) => group.name),
2458
3309
  badProviders: result.value.failures.map((failure) => failure.name)
@@ -2461,7 +3312,7 @@ window.__ModuleLoader__.load({
2461
3312
  return {
2462
3313
  checked: true,
2463
3314
  available: false,
2464
- providers: 0,
3315
+ models: 0,
2465
3316
  failures: 0,
2466
3317
  okProviders: [],
2467
3318
  badProviders: []