@kenz1117/dsh-ui-usage-billing 0.2.1 → 0.2.3

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
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
23
23
  }
24
24
  //#endregion
25
25
  //#region \0dsh-css:/Users/ken/deepseek-harness/packages/client/ui-usage-billing/src/client/UsageBilling.module.css.mjs
26
- const css = ".VWh0dG_railButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:36px;height:36px;color:var(--dsw-static-blue-500);cursor:pointer;border-radius:10px;justify-content:center;align-items:center;padding:0;transition:background-color .14s,border-color .14s,color .14s;display:flex}.VWh0dG_railButton:hover,.VWh0dG_railButton:focus-visible{background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l3);color:var(--dsw-static-blue-400);outline:none}.VWh0dG_railButton svg{stroke-width:2px;width:18px;height:18px}.VWh0dG_trigger{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;border-radius:12px;align-items:center;gap:10px;padding:9px 12px;transition:border-color .14s,background-color .14s,transform .12s;display:flex}.VWh0dG_trigger:hover,.VWh0dG_trigger:focus-visible{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover);outline:none}.VWh0dG_trigger:active{transform:scale(.985)}.VWh0dG_triggerIcon{background:linear-gradient(135deg, var(--dsw-static-blue-500), var(--dsw-static-deepseek-500));width:30px;height:30px;color:var(--dsw-static-neutral-bluish-00);border-radius:9px;flex:none;justify-content:center;align-items:center;display:inline-flex}.VWh0dG_triggerIcon svg{stroke-width:2.2px;width:15px;height:15px}.VWh0dG_triggerText{flex-direction:column;gap:1px;min-width:0;display:flex}.VWh0dG_triggerLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:16px}.VWh0dG_triggerMeta{color:var(--dsw-alias-label-tertiary);white-space:nowrap;align-items:baseline;gap:6px;font-size:11px;line-height:15px;display:flex}.VWh0dG_triggerMeta strong{color:var(--dsw-static-blue-500);font-variant-numeric:tabular-nums;font-size:13px;font-weight:600}.VWh0dG_triggerMeta em{color:var(--dsw-alias-label-caption);font-style:normal}.VWh0dG_triggerChevron{width:14px;height:14px;color:var(--dsw-alias-label-caption);flex:none;margin-left:auto}.VWh0dG_dashboardModal{width:min(760px,100vw - 48px);max-height:min(760px,88vh)}.VWh0dG_dashboard{flex-direction:column;width:100%;max-height:min(760px,88vh);display:flex}.VWh0dG_dashboardHead{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:flex-start;gap:12px;padding:20px 24px 14px;display:flex}.VWh0dG_dashboardTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:17px;font-weight:600;line-height:24px}.VWh0dG_dashboardSubtitle{color:var(--dsw-alias-label-caption);margin:3px 0 0;font-size:12px;line-height:17px}.VWh0dG_closeButton{width:30px;height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;transition:background-color .14s;display:inline-flex}.VWh0dG_closeButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_closeButton svg{stroke-width:2px;width:16px;height:16px}.VWh0dG_dashboardBody{flex-direction:column;gap:14px;padding:16px 24px 24px;display:flex;overflow-y:auto}.VWh0dG_hero{background:linear-gradient(135deg, var(--dsw-static-blue-800) 0%, var(--dsw-static-deepseek-600) 55%, var(--dsw-static-deepseek-500) 100%);min-height:118px;color:var(--dsw-static-neutral-bluish-00);border-radius:16px;align-items:center;gap:24px;padding:18px 22px;display:flex;position:relative;overflow:hidden}.VWh0dG_hero:after{content:\"\";pointer-events:none;background:radial-gradient(circle,#ffffff29 0%,#fff0 70%);border-radius:50%;width:200px;height:200px;position:absolute;top:-60px;right:-40px}.VWh0dG_heroMain{flex-direction:column;flex:auto;justify-content:center;gap:3px;min-width:0;display:flex}.VWh0dG_heroLabel{opacity:.82;white-space:nowrap;font-size:12px;font-weight:500;line-height:17px}.VWh0dG_heroValue{letter-spacing:-.02em;font-variant-numeric:tabular-nums;white-space:nowrap;font-size:38px;font-weight:700;line-height:46px}.VWh0dG_heroMeta{opacity:.82;white-space:nowrap;align-items:center;gap:6px;font-size:12px;line-height:17px;display:flex}.VWh0dG_heroMeta em{background:#ffffff29;border-radius:6px;margin-left:2px;padding:1px 6px;font-style:normal}.VWh0dG_heroDivider{background:#ffffff38;flex:none;align-self:stretch;width:1px}.VWh0dG_heroSide{flex:none;align-items:center;gap:28px;min-width:140px;display:flex}.VWh0dG_heroSideItem{flex-direction:column;justify-content:center;gap:3px;display:flex}.VWh0dG_heroSideLabel{opacity:.82;white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSideValue{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:22px;font-weight:600;line-height:28px}.VWh0dG_delta{align-items:center;gap:2px;font-size:12px;font-weight:600;line-height:17px;display:inline-flex}.VWh0dG_deltaUp{color:#9ff0b8}.VWh0dG_deltaDown{color:#ffb4b4}.VWh0dG_kpiGrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;display:grid}.VWh0dG_kpiTile{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:14px;flex-direction:column;gap:3px;padding:14px 15px;transition:border-color .14s,background-color .14s;display:flex}.VWh0dG_kpiTile:hover{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_kpiLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:15px}.VWh0dG_kpiValue{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:21px;font-weight:600;line-height:27px}.VWh0dG_kpiGreen{color:var(--dsw-static-green-500)}.VWh0dG_kpiDetail{color:var(--dsw-alias-label-caption);white-space:nowrap;text-overflow:ellipsis;font-size:11px;line-height:15px;overflow:hidden}.VWh0dG_panel{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:10px;padding:16px 18px;display:flex}.VWh0dG_panelHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.VWh0dG_panelTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;font-weight:600;line-height:20px}.VWh0dG_panelHint{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:11px;line-height:16px}.VWh0dG_chartWrap{width:100%;position:relative}.VWh0dG_chartSvg{width:100%;height:auto;display:block}.VWh0dG_chartEmpty{height:140px;color:var(--dsw-alias-label-caption);justify-content:center;align-items:center;font-size:13px;display:flex}.VWh0dG_emptyRow{text-align:center;color:var(--dsw-alias-label-caption);padding:28px 0;font-size:13px}.VWh0dG_chartGrid{stroke:var(--dsw-alias-border-l1);stroke-width:1px}.VWh0dG_chartAxisLabel{fill:var(--dsw-alias-label-tertiary);font-size:10px}.VWh0dG_chartBar{fill:var(--dsw-static-blue-500);opacity:.35}.VWh0dG_chartBar:hover{opacity:.6}.VWh0dG_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-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_pricingToggleText{align-items:baseline;gap:10px;min-width:0;display:flex}.VWh0dG_pricingChevron{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.VWh0dG_pricingChevronOpen{transform:rotate(180deg)}.VWh0dG_bandPrice{align-items:baseline;gap:6px;display:inline-flex}.VWh0dG_bandPriceOff{color:var(--dsw-alias-label-caption)}.VWh0dG_bandPriceOff:before{content:\"/\";color:var(--dsw-alias-label-dimmed);margin-right:4px}.VWh0dG_bandTag{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:1px;font-size:10.5px;line-height:14px;display:inline-flex}.VWh0dG_bandTagOff{color:var(--dsw-static-green-500);font-weight:600}.VWh0dG_flatTag{color:var(--dsw-alias-label-caption);font-size:10.5px;line-height:14px}.VWh0dG_healthDot{border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.VWh0dG_healthOk{background:var(--dsw-static-green-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-static-green-500) 18%, transparent)}.VWh0dG_healthBad{background:var(--dsw-static-red-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-static-red-500) 18%, transparent)}.VWh0dG_healthIdle{background:var(--dsw-static-neutral-bluish-400)}.VWh0dG_dashboardRight{align-items:center;gap:8px;min-width:0;display:flex}.VWh0dG_healthBadge{white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:3px 9px;font-size:11px;line-height:16px;display:inline-flex}.VWh0dG_healthBadgeOk{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 10%, transparent)}.VWh0dG_healthBadgeBad{color:var(--dsw-static-red-500);background:color-mix(in srgb, var(--dsw-static-red-500) 10%, transparent)}.VWh0dG_planTag{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 12%, transparent);border-radius:999px;align-items:center;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}@media (width<=640px){.VWh0dG_kpiGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.VWh0dG_hero{flex-direction:column;align-items:flex-start;gap:12px}.VWh0dG_heroMain{width:100%;min-width:0}.VWh0dG_heroDivider{align-self:stretch;width:100%;height:1px}.VWh0dG_heroSide{min-width:0}}";
26
+ const css = ".VWh0dG_railButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:36px;height:36px;color:var(--dsw-static-blue-500);cursor:pointer;border-radius:10px;justify-content:center;align-items:center;padding:0;transition:background-color .14s,border-color .14s,color .14s;display:flex}.VWh0dG_railButton:hover,.VWh0dG_railButton:focus-visible{background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l3);color:var(--dsw-static-blue-400);outline:none}.VWh0dG_railButton svg{stroke-width:2px;width:18px;height:18px}.VWh0dG_trigger{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;border-radius:12px;align-items:center;gap:12px;padding:10px 14px;transition:border-color .14s,background-color .14s;display:flex}.VWh0dG_trigger:hover,.VWh0dG_trigger:focus-visible{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover);outline:none}.VWh0dG_triggerIcon{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.VWh0dG_triggerIcon svg{stroke-width:1.8px;width:15px;height:15px}.VWh0dG_triggerToday,.VWh0dG_triggerMonth{align-items:baseline;gap:4px;min-width:0;display:flex}.VWh0dG_triggerDivider{background:var(--dsw-alias-border-l1);align-self:stretch;width:1px}.VWh0dG_triggerAmount{color:var(--dsw-alias-label-primary);letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap;font-size:17px;font-weight:600;line-height:22px}.VWh0dG_triggerAmountSub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:13px;font-weight:600;line-height:18px}.VWh0dG_triggerMeta{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:10.5px;line-height:14px}.VWh0dG_dashboardModal{width:min(760px,100vw - 48px);max-height:min(760px,88vh)}.VWh0dG_dashboard{flex-direction:column;width:100%;max-height:min(760px,88vh);display:flex}.VWh0dG_dashboardHead{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:flex-start;gap:12px;padding:20px 24px 14px;display:flex}.VWh0dG_dashboardTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:17px;font-weight:600;line-height:24px}.VWh0dG_dashboardSubtitle{color:var(--dsw-alias-label-caption);margin:3px 0 0;font-size:12px;line-height:17px}.VWh0dG_closeButton{width:30px;height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;transition:background-color .14s;display:inline-flex}.VWh0dG_closeButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_closeButton svg{stroke-width:2px;width:16px;height:16px}.VWh0dG_dashboardBody{flex-direction:column;gap:14px;padding:16px 24px 24px;display:flex;overflow-y:auto}.VWh0dG_hero{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:16px;justify-content:space-between;align-items:center;gap:24px;padding:20px 24px;display:flex}.VWh0dG_heroMain{flex-direction:column;gap:4px;min-width:0;display:flex}.VWh0dG_heroLabel{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroValue{letter-spacing:-.02em;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:40px;font-weight:600;line-height:48px}.VWh0dG_heroMeta{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSide{flex-direction:column;flex:none;gap:10px;display:flex}.VWh0dG_heroSideItem{align-items:baseline;gap:8px;display:flex}.VWh0dG_heroSideLabel{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;line-height:17px}.VWh0dG_heroSideValue{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:16px;font-weight:600;line-height:22px}.VWh0dG_delta{align-items:center;gap:2px;margin-left:4px;font-size:11px;font-weight:600;line-height:17px;display:inline-flex}.VWh0dG_deltaUp{color:var(--dsw-static-green-500)}.VWh0dG_deltaDown{color:var(--dsw-static-red-500)}.VWh0dG_kpiGrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;display:grid}.VWh0dG_kpiTile{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:14px;flex-direction:column;gap:3px;padding:14px 15px;transition:border-color .14s,background-color .14s;display:flex}.VWh0dG_kpiTile:hover{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_kpiLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:15px}.VWh0dG_kpiValue{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:21px;font-weight:600;line-height:27px}.VWh0dG_kpiGreen{color:var(--dsw-static-green-500)}.VWh0dG_kpiDetail{color:var(--dsw-alias-label-caption);white-space:nowrap;text-overflow:ellipsis;font-size:11px;line-height:15px;overflow:hidden}.VWh0dG_panel{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:10px;padding:16px 18px;display:flex}.VWh0dG_panelHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.VWh0dG_panelTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;font-weight:600;line-height:20px}.VWh0dG_panelHint{color:var(--dsw-alias-label-caption);white-space:nowrap;font-size:11px;line-height:16px}.VWh0dG_chartWrap{width:100%;position:relative}.VWh0dG_chartSvg{width:100%;height:auto;display:block}.VWh0dG_chartEmpty{height:140px;color:var(--dsw-alias-label-caption);justify-content:center;align-items:center;font-size:13px;display:flex}.VWh0dG_emptyRow{text-align:center;color:var(--dsw-alias-label-caption);padding:28px 0;font-size:13px}.VWh0dG_chartGrid{stroke:var(--dsw-alias-border-l1);stroke-width:1px}.VWh0dG_chartAxisLabel{fill:var(--dsw-alias-label-tertiary);font-size:10px}.VWh0dG_chartBar{fill:var(--dsw-static-blue-500);opacity:.35}.VWh0dG_chartBar:hover{opacity:.6}.VWh0dG_chartStack{stroke:var(--dsw-alias-bg-layer-1);stroke-width:.75px}.VWh0dG_chartStack:hover{opacity:.85}.VWh0dG_chartLine{stroke:var(--dsw-static-blue-500);stroke-width:2px;stroke-linecap:round;stroke-linejoin:round}.VWh0dG_chartCrosshair{stroke:var(--dsw-alias-label-dimmed);stroke-width:1px;stroke-dasharray:3 3}.VWh0dG_chartDot{fill:var(--dsw-static-neutral-bluish-00);stroke:var(--dsw-static-blue-500);stroke-width:2.5px}.VWh0dG_chartTooltip{pointer-events:none;z-index:2;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2);white-space:nowrap;border-radius:10px;padding:8px 10px;position:absolute;transform:translate(-50%,calc(-100% - 10px))}.VWh0dG_chartTooltipDate{color:var(--dsw-alias-label-primary);margin-bottom:3px;font-size:11px;font-weight:600}.VWh0dG_chartTooltipRow{color:var(--dsw-alias-label-secondary);align-items:center;gap:5px;font-size:11px;line-height:17px;display:flex}.VWh0dG_chartTooltipRow strong{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-weight:600}.VWh0dG_chartLegendLine{background:var(--dsw-static-blue-500);border-radius:2px;width:10px;height:3px;display:inline-block}.VWh0dG_chartLegendBar{background:var(--dsw-static-blue-500);opacity:.5;border-radius:2px;width:7px;height:7px;display:inline-block}.VWh0dG_chartTooltipSwatch{border-radius:3px;flex:none;width:8px;height:8px;display:inline-block}.VWh0dG_chartLegend{color:var(--dsw-alias-label-tertiary);justify-content:flex-end;gap:14px;margin-top:2px;font-size:11px;display:flex}.VWh0dG_chartLegend span{align-items:center;gap:5px;display:inline-flex}.VWh0dG_tableScroll{border:1px solid var(--dsw-alias-border-l1);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;overflow:auto}.VWh0dG_modelTable,.VWh0dG_pricingTable{border-collapse:collapse;width:100%;font-size:12.5px}.VWh0dG_modelTable th,.VWh0dG_modelTable td,.VWh0dG_pricingTable th,.VWh0dG_pricingTable td{border-bottom:1px solid var(--dsw-alias-border-l1);text-align:left;white-space:nowrap;padding:9px 12px}.VWh0dG_modelTable tbody tr:last-child td,.VWh0dG_pricingTable tbody tr:last-child td{border-bottom:0}.VWh0dG_modelTable thead th,.VWh0dG_pricingTable thead th{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform);font-size:11px;font-weight:500}.VWh0dG_modelTable tbody tr,.VWh0dG_pricingTable tbody tr{transition:background-color .12s}.VWh0dG_modelTable tbody tr:hover,.VWh0dG_pricingTable tbody tr:hover{background:var(--dsw-alias-interactive-bg-hover)}.VWh0dG_numCol{text-align:right;font-variant-numeric:tabular-nums}.VWh0dG_costCol{color:var(--dsw-static-blue-500);font-weight:600}.VWh0dG_na{color:var(--dsw-alias-label-dimmed)}.VWh0dG_modelCell{align-items:center;gap:8px;display:inline-flex}.VWh0dG_modelDot{width:9px;height:9px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-bg-layer-1) 60%, transparent);border-radius:50%;flex:none}.VWh0dG_modelName{color:var(--dsw-alias-label-primary);font-weight:500;line-height:16px;display:block}.VWh0dG_modelProvider{color:var(--dsw-alias-label-caption);font-size:10.5px;line-height:14px;display:block}.VWh0dG_pricingToggle{cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;gap:10px;width:100%;padding:0;display:flex}.VWh0dG_pricingToggle:hover .VWh0dG_panelTitle,.VWh0dG_pricingToggle:focus-visible .VWh0dG_panelTitle{color:var(--dsw-static-blue-500)}.VWh0dG_rateBadge{vertical-align:1px;white-space:nowrap;border-radius:999px;align-items:center;margin-left:6px;padding:1px 6px;font-size:10px;font-weight:500;line-height:14px;display:inline-flex}.VWh0dG_rateBadgeLive{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 10%, transparent)}.VWh0dG_rateBadgeBuiltin{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform)}.VWh0dG_pricingToggleText{align-items:baseline;gap:10px;min-width:0;display:flex}.VWh0dG_pricingChevron{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.VWh0dG_pricingChevronOpen{transform:rotate(180deg)}.VWh0dG_bandPrice{align-items:baseline;gap:6px;display:inline-flex}.VWh0dG_bandPriceOff{color:var(--dsw-alias-label-caption)}.VWh0dG_bandPriceOff:before{content:\"/\";color:var(--dsw-alias-label-dimmed);margin-right:4px}.VWh0dG_bandTag{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:1px;font-size:10.5px;line-height:14px;display:inline-flex}.VWh0dG_bandTagOff{color:var(--dsw-static-green-500);font-weight:600}.VWh0dG_flatTag{color:var(--dsw-alias-label-caption);font-size:10.5px;line-height:14px}.VWh0dG_healthDot{border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.VWh0dG_healthOk{background:var(--dsw-static-green-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-static-green-500) 18%, transparent)}.VWh0dG_healthBad{background:var(--dsw-static-red-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-static-red-500) 18%, transparent)}.VWh0dG_healthIdle{background:var(--dsw-static-neutral-bluish-400)}.VWh0dG_dashboardRight{align-items:center;gap:8px;min-width:0;display:flex}.VWh0dG_healthBadge{white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:3px 9px;font-size:11px;line-height:16px;display:inline-flex}.VWh0dG_healthBadgeOk{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 10%, transparent)}.VWh0dG_healthBadgeBad{color:var(--dsw-static-red-500);background:color-mix(in srgb, var(--dsw-static-red-500) 10%, transparent)}.VWh0dG_planTag{color:var(--dsw-static-green-500);background:color-mix(in srgb, var(--dsw-static-green-500) 12%, transparent);border-radius:999px;align-items:center;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}@media (width<=640px){.VWh0dG_kpiGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.VWh0dG_hero{flex-direction:column;align-items:flex-start;gap:14px}.VWh0dG_heroSide{flex-direction:row;gap:20px}}";
27
27
  const tagId = "@kenz1117/dsh-ui-usage-billing/UsageBilling.module.css";
28
28
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
29
  const tag = document.createElement("style");
@@ -33,112 +33,120 @@ window.__ModuleLoader__.load({
33
33
  document.head.appendChild(tag);
34
34
  }
35
35
  var UsageBilling_module_css_default = {
36
- "bandPrice": "VWh0dG_bandPrice",
37
- "heroSideItem": "VWh0dG_heroSideItem",
38
- "chartWrap": "VWh0dG_chartWrap",
39
- "bandPriceOff": "VWh0dG_bandPriceOff",
40
- "pricingToggle": "VWh0dG_pricingToggle",
41
- "chartCrosshair": "VWh0dG_chartCrosshair",
42
- "healthBadgeBad": "VWh0dG_healthBadgeBad",
43
- "hero": "VWh0dG_hero",
44
- "kpiTile": "VWh0dG_kpiTile",
45
- "kpiDetail": "VWh0dG_kpiDetail",
46
- "triggerIcon": "VWh0dG_triggerIcon",
36
+ "panelTitle": "VWh0dG_panelTitle",
37
+ "chartLegendLine": "VWh0dG_chartLegendLine",
38
+ "dashboardModal": "VWh0dG_dashboardModal",
39
+ "emptyRow": "VWh0dG_emptyRow",
40
+ "heroValue": "VWh0dG_heroValue",
41
+ "modelDot": "VWh0dG_modelDot",
42
+ "chartLegendBar": "VWh0dG_chartLegendBar",
43
+ "rateBadge": "VWh0dG_rateBadge",
44
+ "bandTagOff": "VWh0dG_bandTagOff",
45
+ "heroSideLabel": "VWh0dG_heroSideLabel",
46
+ "triggerAmountSub": "VWh0dG_triggerAmountSub",
47
+ "healthIdle": "VWh0dG_healthIdle",
48
+ "chartGrid": "VWh0dG_chartGrid",
49
+ "deltaUp": "VWh0dG_deltaUp",
50
+ "healthBadgeOk": "VWh0dG_healthBadgeOk",
51
+ "kpiValue": "VWh0dG_kpiValue",
52
+ "kpiGrid": "VWh0dG_kpiGrid",
53
+ "heroSide": "VWh0dG_heroSide",
47
54
  "chartDot": "VWh0dG_chartDot",
48
- "numCol": "VWh0dG_numCol",
49
- "railButton": "VWh0dG_railButton",
55
+ "heroSideItem": "VWh0dG_heroSideItem",
56
+ "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
50
57
  "deltaDown": "VWh0dG_deltaDown",
51
- "kpiValue": "VWh0dG_kpiValue",
52
- "kpiLabel": "VWh0dG_kpiLabel",
53
- "chartLine": "VWh0dG_chartLine",
54
- "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
58
+ "rateBadgeLive": "VWh0dG_rateBadgeLive",
59
+ "pricingChevron": "VWh0dG_pricingChevron",
55
60
  "heroLabel": "VWh0dG_heroLabel",
56
- "delta": "VWh0dG_delta",
57
- "heroValue": "VWh0dG_heroValue",
61
+ "trigger": "VWh0dG_trigger",
58
62
  "chartAxisLabel": "VWh0dG_chartAxisLabel",
59
- "heroMain": "VWh0dG_heroMain",
60
63
  "modelTable": "VWh0dG_modelTable",
61
- "panelTitle": "VWh0dG_panelTitle",
62
- "na": "VWh0dG_na",
64
+ "chartBar": "VWh0dG_chartBar",
63
65
  "modelProvider": "VWh0dG_modelProvider",
64
- "triggerText": "VWh0dG_triggerText",
65
- "modelCell": "VWh0dG_modelCell",
66
- "chartLegendBar": "VWh0dG_chartLegendBar",
67
- "modelDot": "VWh0dG_modelDot",
68
- "healthBadgeOk": "VWh0dG_healthBadgeOk",
69
- "triggerLabel": "VWh0dG_triggerLabel",
66
+ "dashboardBody": "VWh0dG_dashboardBody",
67
+ "chartCrosshair": "VWh0dG_chartCrosshair",
68
+ "triggerAmount": "VWh0dG_triggerAmount",
69
+ "chartTooltip": "VWh0dG_chartTooltip",
70
+ "pricingToggleText": "VWh0dG_pricingToggleText",
71
+ "closeButton": "VWh0dG_closeButton",
72
+ "costCol": "VWh0dG_costCol",
73
+ "bandPriceOff": "VWh0dG_bandPriceOff",
74
+ "hero": "VWh0dG_hero",
75
+ "heroMain": "VWh0dG_heroMain",
70
76
  "chartEmpty": "VWh0dG_chartEmpty",
71
- "chartBar": "VWh0dG_chartBar",
72
- "heroDivider": "VWh0dG_heroDivider",
73
- "triggerChevron": "VWh0dG_triggerChevron",
74
- "dashboardRight": "VWh0dG_dashboardRight",
77
+ "triggerMeta": "VWh0dG_triggerMeta",
78
+ "kpiDetail": "VWh0dG_kpiDetail",
79
+ "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
75
80
  "dashboardTitle": "VWh0dG_dashboardTitle",
81
+ "healthDot": "VWh0dG_healthDot",
82
+ "planTag": "VWh0dG_planTag",
83
+ "heroSideValue": "VWh0dG_heroSideValue",
84
+ "pricingToggle": "VWh0dG_pricingToggle",
85
+ "triggerDivider": "VWh0dG_triggerDivider",
76
86
  "panelHint": "VWh0dG_panelHint",
77
- "panelHead": "VWh0dG_panelHead",
78
- "tableScroll": "VWh0dG_tableScroll",
79
- "heroSideLabel": "VWh0dG_heroSideLabel",
80
- "panel": "VWh0dG_panel",
81
- "closeButton": "VWh0dG_closeButton",
82
- "triggerMeta": "VWh0dG_triggerMeta",
83
- "modelName": "VWh0dG_modelName",
84
- "pricingChevron": "VWh0dG_pricingChevron",
85
- "deltaUp": "VWh0dG_deltaUp",
86
- "emptyRow": "VWh0dG_emptyRow",
87
+ "modelCell": "VWh0dG_modelCell",
87
88
  "healthOk": "VWh0dG_healthOk",
88
- "chartStack": "VWh0dG_chartStack",
89
- "dashboardBody": "VWh0dG_dashboardBody",
90
- "dashboardHead": "VWh0dG_dashboardHead",
91
- "dashboardModal": "VWh0dG_dashboardModal",
92
- "kpiGrid": "VWh0dG_kpiGrid",
89
+ "chartWrap": "VWh0dG_chartWrap",
90
+ "bandTag": "VWh0dG_bandTag",
93
91
  "kpiGreen": "VWh0dG_kpiGreen",
94
- "heroSide": "VWh0dG_heroSide",
95
- "bandTagOff": "VWh0dG_bandTagOff",
96
- "planTag": "VWh0dG_planTag",
97
- "pricingToggleText": "VWh0dG_pricingToggleText",
98
- "heroMeta": "VWh0dG_heroMeta",
99
- "chartGrid": "VWh0dG_chartGrid",
100
- "healthDot": "VWh0dG_healthDot",
101
- "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
102
- "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
92
+ "flatTag": "VWh0dG_flatTag",
93
+ "dashboardHead": "VWh0dG_dashboardHead",
103
94
  "pricingTable": "VWh0dG_pricingTable",
104
- "healthIdle": "VWh0dG_healthIdle",
105
- "costCol": "VWh0dG_costCol",
95
+ "kpiLabel": "VWh0dG_kpiLabel",
96
+ "numCol": "VWh0dG_numCol",
97
+ "tableScroll": "VWh0dG_tableScroll",
98
+ "chartStack": "VWh0dG_chartStack",
99
+ "modelName": "VWh0dG_modelName",
100
+ "dashboard": "VWh0dG_dashboard",
101
+ "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
102
+ "kpiTile": "VWh0dG_kpiTile",
103
+ "panel": "VWh0dG_panel",
104
+ "bandPrice": "VWh0dG_bandPrice",
106
105
  "healthBad": "VWh0dG_healthBad",
107
- "bandTag": "VWh0dG_bandTag",
108
- "chartTooltipRow": "VWh0dG_chartTooltipRow",
109
106
  "chartLegend": "VWh0dG_chartLegend",
107
+ "chartLine": "VWh0dG_chartLine",
108
+ "chartTooltipRow": "VWh0dG_chartTooltipRow",
109
+ "triggerMonth": "VWh0dG_triggerMonth",
110
+ "healthBadgeBad": "VWh0dG_healthBadgeBad",
111
+ "railButton": "VWh0dG_railButton",
112
+ "triggerIcon": "VWh0dG_triggerIcon",
113
+ "healthBadge": "VWh0dG_healthBadge",
110
114
  "chartSvg": "VWh0dG_chartSvg",
111
- "dashboard": "VWh0dG_dashboard",
115
+ "panelHead": "VWh0dG_panelHead",
116
+ "heroMeta": "VWh0dG_heroMeta",
117
+ "delta": "VWh0dG_delta",
112
118
  "chartTooltipDate": "VWh0dG_chartTooltipDate",
113
- "chartTooltip": "VWh0dG_chartTooltip",
114
- "chartLegendLine": "VWh0dG_chartLegendLine",
115
- "healthBadge": "VWh0dG_healthBadge",
116
- "heroSideValue": "VWh0dG_heroSideValue",
117
- "trigger": "VWh0dG_trigger",
118
- "flatTag": "VWh0dG_flatTag"
119
+ "triggerToday": "VWh0dG_triggerToday",
120
+ "dashboardRight": "VWh0dG_dashboardRight",
121
+ "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
122
+ "na": "VWh0dG_na"
119
123
  };
120
- //#endregion
121
- //#region src/client/pricing.ts
124
+ /** 运行时实时覆盖:undefined = 用内置目录与内置汇率(默认值降级)。 */
125
+ let liveRate;
126
+ let livePrices;
122
127
  /**
123
- * Billing engine: per-model price tables and token-usage cost estimation.
124
- *
125
- * Each model's price table uses its NATIVE currency: domestic providers
126
- * (DeepSeek, 智谱, 通义…) publish RMB prices and store them directly;
127
- * overseas providers (OpenAI, Google, xAI, Meta) publish USD.
128
- * Cost is always computed and displayed in CNY — only USD-priced models go
129
- * through the exchange rate, never domestic ones.
130
- *
131
- * Google-style two-band billing is modeled per model: Gemini's Flex tier
132
- * prices spare-capacity traffic at -50%; DeepSeek V4 splits peak
133
- * (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate. The estimator
134
- * mixes both bands by a configured peak share ({@link DEFAULT_PEAK_SHARE}).
128
+ * Apply the node half's live pricing snapshot. Absent fields keep the
129
+ * built-in catalog and rate; callers never fabricate values.
130
+ * @param pricing - the `/api/billing/pricing` response.
135
131
  */
132
+ function applyLivePricing(pricing) {
133
+ liveRate = pricing.rate;
134
+ livePrices = pricing.prices;
135
+ }
136
+ /** 当前汇率:实时覆盖优先,缺省回退内置固定值。 */
137
+ function currentRate() {
138
+ return liveRate ?? 6.79;
139
+ }
136
140
  /**
137
- * USD → CNY rate for display. Source: China Foreign Exchange Trade System
138
- * mid-rate 6.7878 on 2026-08-14; rounded to 6.79. Only applies to overseas
139
- * USD-priced models — domestic models never pass through this rate.
141
+ * 当前生效的 USD → CNY 汇率及其来源:live = 启动时实时拉取成功,
142
+ * builtin = 实时拉取失败、正在用内置默认值。
140
143
  */
141
- const USD_TO_CNY = 6.79;
144
+ function getRateInfo() {
145
+ return {
146
+ rate: currentRate(),
147
+ live: liveRate !== void 0
148
+ };
149
+ }
142
150
  /** Default share of traffic assumed to fall in the peak band (0..1). */
143
151
  const DEFAULT_PEAK_SHARE = .5;
144
152
  /**
@@ -617,11 +625,22 @@ window.__ModuleLoader__.load({
617
625
  ];
618
626
  /** Lookup a model by its stats key; falls back to the generic `other` entry. */
619
627
  function modelOf(key) {
620
- const found = MODEL_CATALOG.find((entry) => entry.key === key);
621
- if (found !== void 0) return found;
622
- const fallback = MODEL_CATALOG.at(-1);
623
- if (fallback !== void 0) return fallback;
624
- throw new Error("MODEL_CATALOG must not be empty");
628
+ const base = MODEL_CATALOG.find((entry) => entry.key === key) ?? (() => {
629
+ const fallback = MODEL_CATALOG.at(-1);
630
+ if (fallback !== void 0) return fallback;
631
+ throw new Error("MODEL_CATALOG must not be empty");
632
+ })();
633
+ const live = livePrices?.[key];
634
+ if (live === void 0) return base;
635
+ return {
636
+ ...base,
637
+ price: {
638
+ currency: "USD",
639
+ input: live.input,
640
+ cacheHit: live.cacheHit,
641
+ output: live.output
642
+ }
643
+ };
625
644
  }
626
645
  /** Resolve a price-table row by its CSS variable name (theme token or fallback color). */
627
646
  function resolveToken(name) {
@@ -640,12 +659,18 @@ window.__ModuleLoader__.load({
640
659
  const miss = buckets.cacheMiss > 0 ? buckets.cacheMiss : Math.max(0, buckets.input - buckets.cacheHit);
641
660
  const hit = Math.min(buckets.cacheHit, buckets.input);
642
661
  const raw = (miss * (band.cacheMiss ?? band.input) + hit * band.cacheHit + buckets.output * band.output) / 1e6;
643
- return currency === "USD" ? raw * USD_TO_CNY : raw;
662
+ return currency === "USD" ? raw * currentRate() : raw;
644
663
  }
645
664
  /**
646
665
  * Estimate the CNY cost of one model's token usage, mixing the peak and
647
666
  * off-peak bands by the given peak share (flat-priced models cost the same in
648
667
  * both bands).
668
+ *
669
+ * 计费维度是「缓存命中价 × 时段价」的交叉:每个时段档内部分别按缓存命中
670
+ * 价(cacheHit)与未命中价(input/cacheMiss)计价,两个时段档再按
671
+ * peakShare 混合。时段定义以北京时间为准(如 DeepSeek V4 高峰
672
+ * 09:00-12:00 / 14:00-18:00)。因聚合只有按日 token 量、没有请求级时间戳,
673
+ * 时段只能按比例估算,而非逐请求判定。
649
674
  * @param entry - the catalog entry whose prices apply.
650
675
  * @param buckets - token usage counts.
651
676
  * @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
@@ -659,10 +684,12 @@ window.__ModuleLoader__.load({
659
684
  }
660
685
  /** Format a CNY amount with adaptive precision. */
661
686
  function formatMoney(cny) {
662
- if (cny >= 1e3) return `¥${cny.toFixed(0)}`;
663
- if (cny >= 10) return `¥${cny.toFixed(1)}`;
664
- if (cny >= .1) return `¥${cny.toFixed(2)}`;
665
- return `¥${cny.toFixed(3)}`;
687
+ const value = Number(cny);
688
+ if (!Number.isFinite(value)) return "¥0";
689
+ if (value >= 1e3) return `¥${value.toFixed(0)}`;
690
+ if (value >= 10) return `¥${value.toFixed(1)}`;
691
+ if (value >= .1) return `¥${value.toFixed(2)}`;
692
+ return `¥${value.toFixed(3)}`;
666
693
  }
667
694
  /**
668
695
  * Format a per-1M-token price in its native currency (free when the rate is
@@ -686,23 +713,27 @@ window.__ModuleLoader__.load({
686
713
  }
687
714
  /** Format a percentage. */
688
715
  function formatPercent(value) {
689
- return `${value.toFixed(1)}%`;
716
+ const normalized = Number(value);
717
+ if (!Number.isFinite(normalized)) return "0.0%";
718
+ return `${normalized.toFixed(1)}%`;
690
719
  }
691
720
  //#endregion
692
721
  //#region src/client/TrendChart.tsx
693
722
  /**
694
- * TrendChart: dependency-free SVG stacked bar chart of daily cost per model.
695
- * Each day's column stacks every model's share in its brand color, so the
696
- * total trend and the per-model composition are visible at once. A hover
697
- * crosshair shows the day's model breakdown. No chart library the surface
698
- * stays self-contained and offline.
723
+ * TrendChart: dependency-free SVG chart of daily cost + calls.
724
+ *
725
+ * The columns are GROUPED per model one bar per model per day, each in its
726
+ * brand color, so per-model cost is directly comparable. The blue line is the
727
+ * total call volume across all models, plotted on its own right-hand axis.
728
+ * A hover crosshair shows the day's model breakdown. No chart library — the
729
+ * surface stays self-contained and offline.
699
730
  */
700
731
  /** Fixed viewBox; the SVG scales to its container. */
701
732
  const W = 680;
702
733
  const H = 220;
703
734
  const PAD = {
704
735
  top: 18,
705
- right: 16,
736
+ right: 40,
706
737
  bottom: 26,
707
738
  left: 46
708
739
  };
@@ -711,6 +742,12 @@ window.__ModuleLoader__.load({
711
742
  const [, month, day] = iso.split("-");
712
743
  return `${Number(month)}/${Number(day)}`;
713
744
  }
745
+ /** Compact tick label for the calls axis: `1.2K` / `3.4M`. */
746
+ function shortNumber(value) {
747
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
748
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
749
+ return String(Math.round(value));
750
+ }
714
751
  /** Ticks every `step` items for sparse axis labels. */
715
752
  function tickIndexes(length, step) {
716
753
  const out = [];
@@ -718,52 +755,56 @@ window.__ModuleLoader__.load({
718
755
  if (length > 0 && out[out.length - 1] !== length - 1) out.push(length - 1);
719
756
  return out;
720
757
  }
758
+ /** Single-color fallback identity used when the stats carry no per-model detail. */
759
+ const TOTAL_MODEL = {
760
+ key: "__total__",
761
+ name: "总计",
762
+ color: ""
763
+ };
721
764
  /**
722
- * Render the daily per-model stacked cost chart.
765
+ * Render the daily grouped cost bars plus the total-calls line.
723
766
  * @param props.data - sorted daily rows (ascending date).
724
- * @param props.models - the model legend, in stack order (bottom first).
767
+ * @param props.models - the model legend, in bar order.
725
768
  */
726
769
  function TrendChart({ data, models = [] }) {
727
770
  const [hover, setHover] = (0, react.useState)(null);
728
771
  const layout = (0, react.useMemo)(() => {
729
772
  const n = data.length;
730
773
  if (n === 0) return null;
731
- const maxCost = Math.max(...data.map((d) => d.cost), 1e-4);
732
774
  const plotW = W - PAD.left - PAD.right;
733
775
  const plotH = H - PAD.top - PAD.bottom;
734
776
  const inner = (i) => {
735
777
  if (n === 1) return PAD.left + plotW / 2;
736
778
  return PAD.left + plotW * i / (n - 1);
737
779
  };
780
+ const maxCost = Math.max(...data.flatMap((d) => Object.values(d.byModel ?? {})), ...data.map((d) => d.cost), 1e-4);
738
781
  const yCost = (value) => PAD.top + plotH - value / maxCost * plotH;
782
+ const maxCalls = Math.max(...data.map((d) => d.calls), 1);
783
+ const yCalls = (value) => PAD.top + plotH - value / maxCalls * plotH;
784
+ const groupW = plotW / n;
785
+ const barCount = Math.max(models.length, 1);
786
+ const barW = Math.min(14, groupW / barCount * .7);
739
787
  return {
740
788
  n,
741
789
  plotW,
742
790
  plotH,
743
791
  inner,
744
792
  yCost,
745
- barW: Math.min(18, plotW / n * .5),
746
- columns: data.map((d, i) => {
747
- let acc = 0;
748
- const segments = models.map((model) => {
749
- const value = d.byModel?.[model.key] ?? 0;
750
- const y0 = acc;
751
- acc += value;
752
- return {
753
- model,
754
- y0,
755
- y1: acc,
756
- rounded: false
757
- };
758
- });
759
- const top = segments.at(-1);
760
- if (top !== void 0) top.rounded = true;
761
- return {
793
+ yCalls,
794
+ barW,
795
+ bars: data.flatMap((d, i) => {
796
+ if (models.length === 0) return [{
762
797
  date: d.date,
763
- x: inner(i),
764
- segments,
765
- total: acc
766
- };
798
+ model: TOTAL_MODEL,
799
+ x: inner(i) - barW / 2,
800
+ value: d.cost
801
+ }];
802
+ return models.map((model, k) => ({
803
+ date: d.date,
804
+ model,
805
+ x: inner(i) - groupW / 2 + groupW * (k + .5) / models.length - barW / 2,
806
+ value: d.byModel?.[model.key] ?? 0
807
+ }));
767
808
  }),
768
809
  costTicks: [
769
810
  0,
@@ -772,15 +813,24 @@ window.__ModuleLoader__.load({
772
813
  .75,
773
814
  1
774
815
  ].map((f) => maxCost * f).reverse(),
775
- maxCost
816
+ callsTicks: [
817
+ 0,
818
+ .25,
819
+ .5,
820
+ .75,
821
+ 1
822
+ ].map((f) => maxCalls * f).reverse(),
823
+ linePath: data.map((d, i) => {
824
+ const y = yCalls(d.calls);
825
+ return `${i === 0 ? "M" : "L"}${inner(i)} ${y}`;
826
+ }).join(" ")
776
827
  };
777
828
  }, [data, models]);
778
829
  if (layout === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
779
830
  className: UsageBilling_module_css_default.chartEmpty,
780
831
  children: "暂无趋势数据"
781
832
  });
782
- const { n, plotW, inner, yCost, barW, columns, costTicks } = layout;
783
- const activeColumn = hover === null ? void 0 : columns[hover];
833
+ const { n, plotW, plotH, inner, yCost, yCalls, barW, bars, costTicks, callsTicks, linePath } = layout;
784
834
  const activePoint = hover === null ? void 0 : data[hover];
785
835
  const indices = tickIndexes(n, Math.max(1, Math.ceil(n / 8)));
786
836
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -790,7 +840,7 @@ window.__ModuleLoader__.load({
790
840
  viewBox: `0 0 ${W} ${H}`,
791
841
  className: UsageBilling_module_css_default.chartSvg,
792
842
  role: "img",
793
- "aria-label": "Daily cost by model",
843
+ "aria-label": "Daily cost by model and total calls",
794
844
  onMouseLeave: () => {
795
845
  setHover(null);
796
846
  },
@@ -817,15 +867,30 @@ window.__ModuleLoader__.load({
817
867
  children: formatMoney(value)
818
868
  })] }, `cost-${idx}`);
819
869
  }),
820
- columns.map((column) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { children: column.segments.map((segment) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
821
- x: column.x - barW / 2,
822
- y: yCost(segment.y1),
870
+ bars.map((bar) => bar.value > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
871
+ x: bar.x,
872
+ y: yCost(bar.value),
823
873
  width: barW,
824
- height: Math.max(segment.y1 - segment.y0 > 0 ? 1 : 0, yCost(segment.y0) - yCost(segment.y1)),
825
- rx: segment.rounded ? 3 : 0,
826
- className: UsageBilling_module_css_default.chartStack,
827
- style: { fill: segment.model.color }
828
- }, segment.model.key)) }, column.date)),
874
+ height: yCost(0) - yCost(bar.value),
875
+ rx: 2,
876
+ className: bar.model.color === "" ? UsageBilling_module_css_default.chartBar : UsageBilling_module_css_default.chartStack,
877
+ style: bar.model.color === "" ? void 0 : { fill: bar.model.color }
878
+ }, `${bar.date}-${bar.model.key}`) : null),
879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
880
+ d: linePath,
881
+ fill: "none",
882
+ className: UsageBilling_module_css_default.chartLine
883
+ }),
884
+ callsTicks.map((value, idx) => {
885
+ const y = yCalls(value);
886
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
887
+ x: W - PAD.right + 8,
888
+ y: y + 3,
889
+ textAnchor: "start",
890
+ className: UsageBilling_module_css_default.chartAxisLabel,
891
+ children: shortNumber(value)
892
+ }, `calls-${idx}`);
893
+ }),
829
894
  indices.map((i) => {
830
895
  const point = data[i];
831
896
  if (point === void 0) return null;
@@ -837,44 +902,44 @@ window.__ModuleLoader__.load({
837
902
  children: shortDate(point.date)
838
903
  }, point.date);
839
904
  }),
840
- activeColumn !== void 0 && hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
905
+ hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
841
906
  x1: inner(hover),
842
907
  x2: inner(hover),
843
908
  y1: PAD.top,
844
- y2: PAD.top + layout.plotH,
909
+ y2: PAD.top + plotH,
845
910
  className: UsageBilling_module_css_default.chartCrosshair
846
911
  })
847
912
  ]
848
913
  }),
849
- activeColumn !== void 0 && activePoint !== void 0 && hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
914
+ activePoint !== void 0 && hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
850
915
  className: UsageBilling_module_css_default.chartTooltip,
851
916
  style: {
852
917
  left: `${inner(hover) / W * 100}%`,
853
- top: `${yCost(activeColumn.total) / H * 100}%`
918
+ top: `${yCost(activePoint.cost) / H * 100}%`
854
919
  },
855
920
  children: [
856
921
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
857
922
  className: UsageBilling_module_css_default.chartTooltipDate,
858
923
  children: activePoint.date
859
924
  }),
860
- activeColumn.segments.filter((segment) => segment.y1 - segment.y0 > 0).map((segment) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
925
+ models.filter((model) => (activePoint.byModel?.[model.key] ?? 0) > 0).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
861
926
  className: UsageBilling_module_css_default.chartTooltipRow,
862
927
  children: [
863
928
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
864
929
  className: UsageBilling_module_css_default.chartTooltipSwatch,
865
- style: { background: segment.model.color }
930
+ style: { background: model.color }
866
931
  }),
867
- segment.model.name,
932
+ model.name,
868
933
  " ",
869
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(segment.y1 - segment.y0) })
934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(activePoint.byModel?.[model.key] ?? 0) })
870
935
  ]
871
- }, segment.model.key)),
936
+ }, model.key)),
872
937
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
873
938
  className: UsageBilling_module_css_default.chartTooltipRow,
874
939
  children: [
875
940
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendBar }),
876
941
  "总计 ",
877
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(activeColumn.total) })
942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(activePoint.cost) })
878
943
  ]
879
944
  }),
880
945
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -887,12 +952,12 @@ window.__ModuleLoader__.load({
887
952
  })
888
953
  ]
889
954
  }),
890
- models.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
955
+ models.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
891
956
  className: UsageBilling_module_css_default.chartLegend,
892
- children: models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
957
+ children: [models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
893
958
  className: UsageBilling_module_css_default.chartTooltipSwatch,
894
959
  style: { background: model.color }
895
- }), model.name] }, model.key))
960
+ }), model.name] }, model.key)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.chartLegendLine }), "调用"] })]
896
961
  })
897
962
  ]
898
963
  });
@@ -1001,6 +1066,43 @@ window.__ModuleLoader__.load({
1001
1066
  }
1002
1067
  /** Path to the usage-stats endpoint served by this plugin's node half. */
1003
1068
  const USAGE_STATS_PATH = "/api/billing/usage-stats";
1069
+ /** Path to the live-pricing endpoint served by this plugin's node half. */
1070
+ const PRICING_PATH = "/api/billing/pricing";
1071
+ /** Path to the account-balance endpoint served by this plugin's node half. */
1072
+ const BALANCE_PATH = "/api/billing/balance";
1073
+ /** 弹窗打开期间统计与定价的自动刷新间隔(毫秒)。 */
1074
+ const STATS_REFRESH_INTERVAL_MS = 3e4;
1075
+ /**
1076
+ * 本地时区(北京时间)日期戳:与服务端聚合的 dayStamp 一致。不要用
1077
+ * `toISOString()`——那是 UTC,北京时间的凌晨 0-8 点会取到前一天。
1078
+ */
1079
+ function localDayStamp(time = Date.now()) {
1080
+ const date = new Date(time);
1081
+ const pad = (n) => String(n).padStart(2, "0");
1082
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
1083
+ }
1084
+ /** 本地时区时钟:`HH:MM:SS`。 */
1085
+ function formatClock(time) {
1086
+ const date = new Date(time);
1087
+ const pad = (n) => String(n).padStart(2, "0");
1088
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
1089
+ }
1090
+ /**
1091
+ * 高区分度图表色板:趋势图柱、图例与计费表圆点按模型分配。不用模型品牌色
1092
+ * (目录里多为蓝色系,视觉上几乎分不开),保证每个模型一眼可辨。
1093
+ */
1094
+ const CHART_PALETTE = [
1095
+ "#3b82f6",
1096
+ "#06b6d4",
1097
+ "#8b5cf6",
1098
+ "#f59e0b",
1099
+ "#10b981",
1100
+ "#ef4444",
1101
+ "#ec4899",
1102
+ "#6366f1",
1103
+ "#f97316",
1104
+ "#14b8a6"
1105
+ ];
1004
1106
  /** Empty snapshot: shown before (or without) real host data — zeros, never fabricated samples. */
1005
1107
  const EMPTY_STATS = {
1006
1108
  total: {
@@ -1022,89 +1124,174 @@ window.__ModuleLoader__.load({
1022
1124
  if (!response.ok) return null;
1023
1125
  const text = await response.text();
1024
1126
  const parsed = JSON.parse(text);
1025
- if (parsed !== null && typeof parsed === "object" && "total" in parsed) return parsed;
1026
- return null;
1127
+ if (parsed === null || typeof parsed !== "object" || !("total" in parsed)) return null;
1128
+ const candidate = parsed;
1129
+ return {
1130
+ total: candidate.total ?? EMPTY_STATS.total,
1131
+ byModel: candidate.byModel ?? {},
1132
+ byDay: candidate.byDay ?? {},
1133
+ ...candidate.byDayModels !== void 0 ? { byDayModels: candidate.byDayModels } : {},
1134
+ ...candidate.updatedAt !== void 0 ? { updatedAt: candidate.updatedAt } : {}
1135
+ };
1027
1136
  } catch {
1028
1137
  return null;
1029
1138
  }
1030
1139
  }
1031
1140
  /**
1141
+ * Apply the node half's live pricing snapshot. The node half refreshes once
1142
+ * at boot, so an early `builtin` answer may just mean the refresh is still
1143
+ * in flight — retry briefly before settling for the built-in values.
1144
+ * Any final failure keeps the built-in catalog and rate — degrade, never
1145
+ * fabricate.
1146
+ * @param attempt - current retry index (0-based).
1147
+ */
1148
+ async function loadLivePricing(attempt = 0) {
1149
+ const MAX_ATTEMPTS = 4;
1150
+ try {
1151
+ const response = await fetch(PRICING_PATH);
1152
+ if (!response.ok) return;
1153
+ const text = await response.text();
1154
+ const parsed = JSON.parse(text);
1155
+ if (parsed === null || typeof parsed !== "object" || !("source" in parsed)) return;
1156
+ const pricing = parsed;
1157
+ if (pricing.source === "builtin" && attempt < MAX_ATTEMPTS - 1) {
1158
+ setTimeout(() => {
1159
+ loadLivePricing(attempt + 1);
1160
+ }, 2e3);
1161
+ return;
1162
+ }
1163
+ applyLivePricing(pricing);
1164
+ } catch {}
1165
+ }
1166
+ /**
1167
+ * 拉取各提供方账户余额(供模型计费明细表的余额列);失败返回空列表。
1168
+ * @returns the balance rows, or an empty list on any failure.
1169
+ */
1170
+ async function fetchBalances() {
1171
+ try {
1172
+ const response = await fetch(BALANCE_PATH);
1173
+ if (!response.ok) return [];
1174
+ const text = await response.text();
1175
+ const parsed = JSON.parse(text);
1176
+ if (parsed !== null && typeof parsed === "object" && "balances" in parsed) return parsed.balances;
1177
+ return [];
1178
+ } catch {
1179
+ return [];
1180
+ }
1181
+ }
1182
+ /**
1032
1183
  * Sidebar footer trigger: compact pill in wide mode, icon in rail mode.
1033
1184
  * @param props - framework props plus `wide` column state.
1034
1185
  */
1035
1186
  function UsageBillingTrigger(props) {
1036
- const { wide, onOpen, monthCost, todayCost } = props;
1187
+ const { wide, t, onOpen, monthCost, todayCost } = props;
1188
+ const cardIcon = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1189
+ viewBox: "0 0 24 24",
1190
+ fill: "none",
1191
+ stroke: "currentColor",
1192
+ "aria-hidden": "true",
1193
+ children: [
1194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1195
+ x: "2.5",
1196
+ y: "5",
1197
+ width: "19",
1198
+ height: "14",
1199
+ rx: "2.5"
1200
+ }),
1201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 9.5h19" }),
1202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1203
+ x: "6",
1204
+ y: "12",
1205
+ width: "4",
1206
+ height: "3.5",
1207
+ rx: "0.75"
1208
+ })
1209
+ ]
1210
+ });
1037
1211
  if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1038
1212
  type: "button",
1039
1213
  className: UsageBilling_module_css_default.railButton,
1040
1214
  onClick: onOpen,
1041
- title: formatMoney(monthCost),
1042
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1043
- viewBox: "0 0 24 24",
1044
- fill: "none",
1045
- stroke: "currentColor",
1046
- "aria-hidden": "true",
1047
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" })
1048
- })
1215
+ title: `${t("billing.title")} · ${formatMoney(monthCost)}`,
1216
+ children: cardIcon
1049
1217
  });
1050
1218
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1051
1219
  type: "button",
1052
1220
  className: UsageBilling_module_css_default.trigger,
1053
1221
  onClick: onOpen,
1222
+ title: `${t("billing.title")} · 本月 ${formatMoney(monthCost)}`,
1054
1223
  children: [
1055
1224
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1056
1225
  className: UsageBilling_module_css_default.triggerIcon,
1057
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1058
- viewBox: "0 0 24 24",
1059
- fill: "none",
1060
- stroke: "currentColor",
1061
- "aria-hidden": "true",
1062
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" })
1063
- })
1226
+ children: cardIcon
1064
1227
  }),
1065
1228
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1066
- className: UsageBilling_module_css_default.triggerText,
1229
+ className: UsageBilling_module_css_default.triggerToday,
1067
1230
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1068
- className: UsageBilling_module_css_default.triggerLabel,
1069
- children: "计费仪表盘"
1070
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1071
1231
  className: UsageBilling_module_css_default.triggerMeta,
1072
- children: [
1073
- "当月 ",
1074
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(monthCost) }),
1075
- todayCost > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("em", { children: ["今日 ", formatMoney(todayCost)] })
1076
- ]
1232
+ children: "今日"
1233
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1234
+ className: UsageBilling_module_css_default.triggerAmount,
1235
+ children: formatMoney(todayCost)
1077
1236
  })]
1078
1237
  }),
1079
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1080
- className: UsageBilling_module_css_default.triggerChevron,
1081
- viewBox: "0 0 24 24",
1082
- fill: "none",
1083
- stroke: "currentColor",
1084
- "aria-hidden": "true",
1085
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m9 6 6 6-6 6" })
1238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: UsageBilling_module_css_default.triggerDivider }),
1239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1240
+ className: UsageBilling_module_css_default.triggerMonth,
1241
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1242
+ className: UsageBilling_module_css_default.triggerMeta,
1243
+ children: "当月"
1244
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1245
+ className: UsageBilling_module_css_default.triggerAmountSub,
1246
+ children: formatMoney(monthCost)
1247
+ })]
1086
1248
  })
1087
1249
  ]
1088
1250
  });
1089
1251
  }
1090
1252
  /**
1091
1253
  * The centered billing dashboard modal.
1092
- * @param props - stats, locale function, close handler, and model health.
1254
+ * @param props - stats, locale function, close handler, model health, balances.
1093
1255
  */
1094
- function BillingDashboard({ stats, t, onClose, health }) {
1256
+ function BillingDashboard({ stats, t, onClose, health, balances }) {
1095
1257
  const { total, byModel, byDay } = stats;
1096
1258
  const [pricingOpen, setPricingOpen] = (0, react.useState)(false);
1259
+ const rateInfo = getRateInfo();
1260
+ const balanceFor = (provider) => balances.find((balance) => normalizeProvider(balance.provider) === normalizeProvider(provider));
1261
+ const renderBalance = (balance) => {
1262
+ if (balance === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1263
+ className: UsageBilling_module_css_default.na,
1264
+ children: "—"
1265
+ });
1266
+ if (balance.error === "unconfigured") return t("billing.balanceUnconfigured");
1267
+ if (balance.error === "unauthorized") return t("billing.balanceUnauthorized");
1268
+ if (balance.error === "unreachable") return t("billing.balanceUnreachable");
1269
+ if (balance.totalBalance === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1270
+ className: UsageBilling_module_css_default.na,
1271
+ children: "—"
1272
+ });
1273
+ return balance.currency === "USD" ? `$${balance.totalBalance.toFixed(2)}` : formatMoney(balance.totalBalance);
1274
+ };
1097
1275
  const cacheHitRate = total.cacheHit + total.cacheMiss > 0 ? total.cacheHit / (total.cacheHit + total.cacheMiss) * 100 : 0;
1098
1276
  const dates = Object.keys(byDay).sort();
1099
- const latestDate = dates.at(-1) ?? "";
1100
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1277
+ const today = localDayStamp();
1101
1278
  const todayCost = byDay[today]?.cost ?? 0;
1102
1279
  const monthPrefix = today.slice(0, 7);
1103
1280
  const yearPrefix = today.slice(0, 4);
1104
1281
  const monthCost = dates.reduce((sum, d) => sum + (d.startsWith(monthPrefix) ? byDay[d]?.cost ?? 0 : 0), 0);
1105
1282
  const monthCalls = dates.reduce((sum, d) => sum + (d.startsWith(monthPrefix) ? byDay[d]?.calls ?? 0 : 0), 0);
1106
1283
  const yearCost = dates.reduce((sum, d) => sum + (d.startsWith(yearPrefix) ? byDay[d]?.cost ?? 0 : 0), 0);
1107
- const trend = (0, react.useMemo)(() => dates.map((date) => {
1284
+ const trendDates = (0, react.useMemo)(() => {
1285
+ const out = [];
1286
+ for (let offset = 6; offset >= 0; offset -= 1) {
1287
+ const day = /* @__PURE__ */ new Date();
1288
+ day.setDate(day.getDate() - offset);
1289
+ out.push(localDayStamp(day.getTime()));
1290
+ }
1291
+ return out;
1292
+ }, []);
1293
+ const latestDate = trendDates.at(-1) ?? today;
1294
+ const trend = (0, react.useMemo)(() => trendDates.map((date) => {
1108
1295
  const byModel = {};
1109
1296
  const dayModels = stats.byDayModels?.[date];
1110
1297
  if (dayModels !== void 0) {
@@ -1118,7 +1305,7 @@ window.__ModuleLoader__.load({
1118
1305
  byModel
1119
1306
  };
1120
1307
  }), [
1121
- dates,
1308
+ trendDates,
1122
1309
  byDay,
1123
1310
  stats.byDayModels
1124
1311
  ]);
@@ -1134,7 +1321,6 @@ window.__ModuleLoader__.load({
1134
1321
  key,
1135
1322
  name: entry.name,
1136
1323
  provider: entry.provider,
1137
- color: resolveToken(entry.colorVar),
1138
1324
  calls: data.calls,
1139
1325
  input: data.input,
1140
1326
  output: data.output,
@@ -1143,7 +1329,10 @@ window.__ModuleLoader__.load({
1143
1329
  plan: isSubscriptionPlan(key),
1144
1330
  ...data.cost > 0 ? { actual: data.cost } : {}
1145
1331
  };
1146
- }).sort((a, b) => (b.actual ?? b.estimated) - (a.actual ?? a.estimated)), [byModel]);
1332
+ }).sort((a, b) => (b.actual ?? b.estimated) - (a.actual ?? a.estimated)).map((row, index) => ({
1333
+ ...row,
1334
+ color: CHART_PALETTE[index % CHART_PALETTE.length] ?? "#8b95a3"
1335
+ })), [byModel]);
1147
1336
  const estimatedTotal = modelRows.reduce((sum, row) => sum + row.estimated, 0);
1148
1337
  const displayTotal = total.cost > 0 ? total.cost : estimatedTotal;
1149
1338
  const avgPerCall = total.calls > 0 ? displayTotal / total.calls : 0;
@@ -1201,64 +1390,56 @@ window.__ModuleLoader__.load({
1201
1390
  children: [
1202
1391
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1203
1392
  className: UsageBilling_module_css_default.hero,
1204
- children: [
1205
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1206
- className: UsageBilling_module_css_default.heroMain,
1207
- children: [
1208
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1209
- className: UsageBilling_module_css_default.heroLabel,
1210
- children: t("billing.monthCost")
1211
- }),
1212
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1213
- className: UsageBilling_module_css_default.heroValue,
1214
- children: formatMoney(monthCost)
1215
- }),
1216
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1217
- className: UsageBilling_module_css_default.heroMeta,
1393
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1394
+ className: UsageBilling_module_css_default.heroMain,
1395
+ children: [
1396
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1397
+ className: UsageBilling_module_css_default.heroLabel,
1398
+ children: t("billing.monthCost")
1399
+ }),
1400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1401
+ className: UsageBilling_module_css_default.heroValue,
1402
+ children: formatMoney(monthCost)
1403
+ }),
1404
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1405
+ className: UsageBilling_module_css_default.heroMeta,
1406
+ children: [
1407
+ monthCalls.toLocaleString(),
1408
+ " ",
1409
+ t("billing.calls")
1410
+ ]
1411
+ })
1412
+ ]
1413
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1414
+ className: UsageBilling_module_css_default.heroSide,
1415
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1416
+ className: UsageBilling_module_css_default.heroSideItem,
1417
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1418
+ className: UsageBilling_module_css_default.heroSideLabel,
1419
+ children: t("billing.yearCost")
1420
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1421
+ className: UsageBilling_module_css_default.heroSideValue,
1422
+ children: formatMoney(yearCost)
1423
+ })]
1424
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1425
+ className: UsageBilling_module_css_default.heroSideItem,
1426
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1427
+ className: UsageBilling_module_css_default.heroSideLabel,
1428
+ children: t("billing.todayCost")
1429
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1430
+ className: UsageBilling_module_css_default.heroSideValue,
1431
+ children: [formatMoney(todayCost), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1432
+ className: clsx(UsageBilling_module_css_default.delta, deltaPct >= 0 ? UsageBilling_module_css_default.deltaUp : UsageBilling_module_css_default.deltaDown),
1218
1433
  children: [
1219
- monthCalls.toLocaleString(),
1434
+ deltaPct >= 0 ? "▲" : "▼",
1220
1435
  " ",
1221
- t("billing.calls")
1436
+ Math.abs(deltaPct).toFixed(1),
1437
+ "%"
1222
1438
  ]
1223
- })
1224
- ]
1225
- }),
1226
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: UsageBilling_module_css_default.heroDivider }),
1227
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1228
- className: UsageBilling_module_css_default.heroSide,
1229
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1230
- className: UsageBilling_module_css_default.heroSideItem,
1231
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1232
- className: UsageBilling_module_css_default.heroSideLabel,
1233
- children: t("billing.yearCost")
1234
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1235
- className: UsageBilling_module_css_default.heroSideValue,
1236
- children: formatMoney(yearCost)
1237
1439
  })]
1238
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1239
- className: UsageBilling_module_css_default.heroSideItem,
1240
- children: [
1241
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1242
- className: UsageBilling_module_css_default.heroSideLabel,
1243
- children: t("billing.todayCost")
1244
- }),
1245
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1246
- className: UsageBilling_module_css_default.heroSideValue,
1247
- children: formatMoney(todayCost)
1248
- }),
1249
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1250
- className: clsx(UsageBilling_module_css_default.delta, deltaPct >= 0 ? UsageBilling_module_css_default.deltaUp : UsageBilling_module_css_default.deltaDown),
1251
- children: [
1252
- deltaPct >= 0 ? "▲" : "▼",
1253
- " ",
1254
- Math.abs(deltaPct).toFixed(1),
1255
- "%"
1256
- ]
1257
- })
1258
- ]
1259
1440
  })]
1260
- })
1261
- ]
1441
+ })]
1442
+ })]
1262
1443
  }),
1263
1444
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1264
1445
  className: UsageBilling_module_css_default.kpiGrid,
@@ -1376,13 +1557,9 @@ window.__ModuleLoader__.load({
1376
1557
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1377
1558
  className: UsageBilling_module_css_default.panelTitle,
1378
1559
  children: t("billing.models")
1379
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1560
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1380
1561
  className: UsageBilling_module_css_default.panelHint,
1381
- children: [
1382
- t("billing.estimated"),
1383
- " · ",
1384
- t("billing.pricePerM")
1385
- ]
1562
+ children: stats.updatedAt !== void 0 ? `${t("billing.lastUpdated")} ${formatClock(stats.updatedAt)}` : ""
1386
1563
  })]
1387
1564
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1388
1565
  className: UsageBilling_module_css_default.tableScroll,
@@ -1408,11 +1585,11 @@ window.__ModuleLoader__.load({
1408
1585
  }),
1409
1586
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1410
1587
  className: UsageBilling_module_css_default.numCol,
1411
- children: t("billing.estimated")
1588
+ children: t("billing.actual")
1412
1589
  }),
1413
1590
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1414
1591
  className: UsageBilling_module_css_default.numCol,
1415
- children: t("billing.actual")
1592
+ children: t("billing.balance")
1416
1593
  })
1417
1594
  ] }) }), /* @__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", {
1418
1595
  colSpan: 7,
@@ -1448,13 +1625,6 @@ window.__ModuleLoader__.load({
1448
1625
  className: UsageBilling_module_css_default.numCol,
1449
1626
  children: formatPercent(row.cacheHitRate)
1450
1627
  }),
1451
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1452
- className: clsx(UsageBilling_module_css_default.numCol, UsageBilling_module_css_default.costCol),
1453
- children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1454
- className: UsageBilling_module_css_default.planTag,
1455
- children: "订阅包含"
1456
- }) : formatMoney(row.estimated)
1457
- }),
1458
1628
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1459
1629
  className: UsageBilling_module_css_default.numCol,
1460
1630
  children: row.plan ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1464,6 +1634,10 @@ window.__ModuleLoader__.load({
1464
1634
  className: UsageBilling_module_css_default.na,
1465
1635
  children: "—"
1466
1636
  })
1637
+ }),
1638
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1639
+ className: UsageBilling_module_css_default.numCol,
1640
+ children: renderBalance(balanceFor(row.provider))
1467
1641
  })
1468
1642
  ] }, row.key))] })]
1469
1643
  })
@@ -1483,9 +1657,17 @@ window.__ModuleLoader__.load({
1483
1657
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1484
1658
  className: UsageBilling_module_css_default.panelTitle,
1485
1659
  children: t("billing.pricing")
1486
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1660
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1487
1661
  className: UsageBilling_module_css_default.panelHint,
1488
- children: t("billing.pricePerM")
1662
+ children: [
1663
+ t("billing.todayRate"),
1664
+ " 1 USD = ",
1665
+ formatMoney(rateInfo.rate),
1666
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1667
+ className: clsx(UsageBilling_module_css_default.rateBadge, rateInfo.live ? UsageBilling_module_css_default.rateBadgeLive : UsageBilling_module_css_default.rateBadgeBuiltin),
1668
+ children: rateInfo.live ? t("billing.rateLive") : t("billing.rateBuiltin")
1669
+ })
1670
+ ]
1489
1671
  })]
1490
1672
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1491
1673
  className: clsx(UsageBilling_module_css_default.pricingChevron, pricingOpen && UsageBilling_module_css_default.pricingChevronOpen),
@@ -1589,22 +1771,37 @@ window.__ModuleLoader__.load({
1589
1771
  const { t, checkModels } = props;
1590
1772
  const [stats, setStats] = (0, react.useState)(EMPTY_STATS);
1591
1773
  const [health, setHealth] = (0, react.useState)(IDLE_HEALTH);
1774
+ const [balances, setBalances] = (0, react.useState)([]);
1592
1775
  const [open, setOpen] = (0, react.useState)(false);
1593
1776
  const close = (0, react.useCallback)(() => {
1594
1777
  setOpen(false);
1595
1778
  }, []);
1779
+ const reloadStats = (0, react.useCallback)(() => {
1780
+ loadUsageStats().then((data) => {
1781
+ if (data !== null) setStats(data);
1782
+ });
1783
+ fetchBalances().then((list) => {
1784
+ if (list.length > 0) setBalances(list);
1785
+ });
1786
+ }, []);
1596
1787
  const openDashboard = (0, react.useCallback)(() => {
1788
+ reloadStats();
1789
+ loadLivePricing();
1597
1790
  setOpen(true);
1598
- }, []);
1791
+ }, [reloadStats]);
1599
1792
  (0, react.useEffect)(() => {
1600
- let mounted = true;
1601
- loadUsageStats().then((data) => {
1602
- if (mounted && data !== null) setStats(data);
1603
- });
1793
+ reloadStats();
1794
+ loadLivePricing();
1795
+ }, [reloadStats]);
1796
+ (0, react.useEffect)(() => {
1797
+ const timer = setInterval(() => {
1798
+ reloadStats();
1799
+ loadLivePricing();
1800
+ }, STATS_REFRESH_INTERVAL_MS);
1604
1801
  return () => {
1605
- mounted = false;
1802
+ clearInterval(timer);
1606
1803
  };
1607
- }, []);
1804
+ }, [reloadStats]);
1608
1805
  (0, react.useEffect)(() => {
1609
1806
  let mounted = true;
1610
1807
  checkModels().then((result) => {
@@ -1614,7 +1811,7 @@ window.__ModuleLoader__.load({
1614
1811
  mounted = false;
1615
1812
  };
1616
1813
  }, [checkModels]);
1617
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1814
+ const today = localDayStamp();
1618
1815
  const monthCost = Object.entries(stats.byDay).filter(([date]) => date.startsWith(today.slice(0, 7))).reduce((sum, [, day]) => sum + day.cost, 0);
1619
1816
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBillingTrigger, {
1620
1817
  ...props,
@@ -1625,7 +1822,8 @@ window.__ModuleLoader__.load({
1625
1822
  stats,
1626
1823
  t,
1627
1824
  onClose: close,
1628
- health
1825
+ health,
1826
+ balances
1629
1827
  })] });
1630
1828
  }
1631
1829
  //#endregion
@@ -1665,7 +1863,14 @@ window.__ModuleLoader__.load({
1665
1863
  "billing.openDashboard": "打开计费仪表盘",
1666
1864
  "billing.close": "关闭",
1667
1865
  "billing.lastUpdated": "数据更新于",
1668
- "billing.noData": "暂无计费数据"
1866
+ "billing.noData": "暂无计费数据",
1867
+ "billing.todayRate": "今日汇率",
1868
+ "billing.rateLive": "实时",
1869
+ "billing.rateBuiltin": "内置",
1870
+ "billing.balance": "余额",
1871
+ "billing.balanceUnconfigured": "未配置",
1872
+ "billing.balanceUnauthorized": "密钥无效",
1873
+ "billing.balanceUnreachable": "查询失败"
1669
1874
  };
1670
1875
  const en = {
1671
1876
  "billing.title": "Usage",
@@ -1701,7 +1906,14 @@ window.__ModuleLoader__.load({
1701
1906
  "billing.openDashboard": "Open billing dashboard",
1702
1907
  "billing.close": "Close",
1703
1908
  "billing.lastUpdated": "Updated",
1704
- "billing.noData": "No billing data yet"
1909
+ "billing.noData": "No billing data yet",
1910
+ "billing.todayRate": "Today rate",
1911
+ "billing.rateLive": "Live",
1912
+ "billing.rateBuiltin": "Built-in",
1913
+ "billing.balance": "Balance",
1914
+ "billing.balanceUnconfigured": "Not set",
1915
+ "billing.balanceUnauthorized": "Bad key",
1916
+ "billing.balanceUnreachable": "Unavailable"
1705
1917
  };
1706
1918
  //#endregion
1707
1919
  //#region src/client/apply.ts