@ottocode/web-sdk 0.1.278 → 0.1.279

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/dist/index.js CHANGED
@@ -326,6 +326,15 @@ function hasPlatformOpenUrl() {
326
326
  function hasPlatformSystemFonts() {
327
327
  return !!getPlatformWindow()?.OTTO_LIST_SYSTEM_FONTS;
328
328
  }
329
+ function isPlatformDesktop() {
330
+ if (typeof window === "undefined")
331
+ return false;
332
+ try {
333
+ return "__TAURI__" in window || "__TAURI_INTERNALS__" in window || "__TAURI_METADATA__" in window;
334
+ } catch {
335
+ return false;
336
+ }
337
+ }
329
338
 
330
339
  // src/lib/open-url.ts
331
340
  function openUrl(url) {
@@ -1941,6 +1950,30 @@ var skillsMixin = {
1941
1950
  }
1942
1951
  };
1943
1952
 
1953
+ // src/lib/api-client/usage.ts
1954
+ import {
1955
+ getUsageStats as apiGetUsageStats,
1956
+ getGlobalUsageStats as apiGetGlobalUsageStats
1957
+ } from "@ottocode/api";
1958
+ var usageMixin = {
1959
+ async getUsageStats() {
1960
+ const { data, error } = await apiGetUsageStats();
1961
+ if (error)
1962
+ throw new Error(extractErrorMessage(error));
1963
+ if (!data)
1964
+ throw new Error("Empty response from /v1/usage/stats");
1965
+ return data;
1966
+ },
1967
+ async getGlobalUsageStats() {
1968
+ const { data, error } = await apiGetGlobalUsageStats();
1969
+ if (error)
1970
+ throw new Error(extractErrorMessage(error));
1971
+ if (!data)
1972
+ throw new Error("Empty response from /v1/usage/stats/global");
1973
+ return data;
1974
+ }
1975
+ };
1976
+
1944
1977
  // src/lib/api-client/index.ts
1945
1978
  class ApiClient {
1946
1979
  getSessions = sessionsMixin.getSessions;
@@ -2031,6 +2064,8 @@ class ApiClient {
2031
2064
  getSkillFileContent = skillsMixin.getSkillFileContent;
2032
2065
  getSkillsConfig = skillsMixin.getSkillsConfig;
2033
2066
  updateSkillsConfig = skillsMixin.updateSkillsConfig;
2067
+ getUsageStats = usageMixin.getUsageStats;
2068
+ getGlobalUsageStats = usageMixin.getGlobalUsageStats;
2034
2069
  }
2035
2070
  var apiClient = new ApiClient;
2036
2071
 
@@ -2092,7 +2127,8 @@ var DEFAULT_FONT_FAMILY = "IBM Plex Mono";
2092
2127
  var DEFAULT_STORED_PREFERENCES = {
2093
2128
  vimMode: false,
2094
2129
  compactThread: true,
2095
- fontFamily: DEFAULT_FONT_FAMILY
2130
+ fontFamily: DEFAULT_FONT_FAMILY,
2131
+ smartEdges: true
2096
2132
  };
2097
2133
  function cssFontFamily(fontFamily) {
2098
2134
  const trimmed = fontFamily.trim();
@@ -2125,7 +2161,8 @@ function resolveInitialPreferences() {
2125
2161
  return {
2126
2162
  vimMode: typeof parsed.vimMode === "boolean" ? parsed.vimMode : DEFAULT_STORED_PREFERENCES.vimMode,
2127
2163
  compactThread: typeof parsed.compactThread === "boolean" ? parsed.compactThread : DEFAULT_STORED_PREFERENCES.compactThread,
2128
- fontFamily: typeof parsed.fontFamily === "string" && parsed.fontFamily.trim() ? parsed.fontFamily.trim() : DEFAULT_STORED_PREFERENCES.fontFamily
2164
+ fontFamily: typeof parsed.fontFamily === "string" && parsed.fontFamily.trim() ? parsed.fontFamily.trim() : DEFAULT_STORED_PREFERENCES.fontFamily,
2165
+ smartEdges: typeof parsed.smartEdges === "boolean" ? parsed.smartEdges : DEFAULT_STORED_PREFERENCES.smartEdges
2129
2166
  };
2130
2167
  }
2131
2168
  } catch (error) {
@@ -2180,6 +2217,9 @@ function usePreferences() {
2180
2217
  if (updates.fontFamily !== undefined) {
2181
2218
  localUpdates.fontFamily = updates.fontFamily.trim() || DEFAULT_FONT_FAMILY;
2182
2219
  }
2220
+ if (updates.smartEdges !== undefined) {
2221
+ localUpdates.smartEdges = updates.smartEdges;
2222
+ }
2183
2223
  if (Object.keys(localUpdates).length > 0) {
2184
2224
  updateStore(localUpdates);
2185
2225
  }
@@ -23748,7 +23788,8 @@ import {
23748
23788
  Check as Check11,
23749
23789
  Key,
23750
23790
  Type,
23751
- Sparkles as Sparkles6
23791
+ Sparkles as Sparkles6,
23792
+ BarChart3
23752
23793
  } from "lucide-react";
23753
23794
  import { QRCodeSVG } from "qrcode.react";
23754
23795
 
@@ -24580,6 +24621,7 @@ function PreferencesModal({ isOpen, onClose }) {
24580
24621
  const { data: config2 } = useConfig();
24581
24622
  const { preferences: preferences2, updatePreferences } = usePreferences();
24582
24623
  const updateDefaults = useUpdateDefaults();
24624
+ const isDesktop = isPlatformDesktop();
24583
24625
  return /* @__PURE__ */ jsx96(Modal, {
24584
24626
  isOpen,
24585
24627
  onClose,
@@ -24609,6 +24651,11 @@ function PreferencesModal({ isOpen, onClose }) {
24609
24651
  checked: preferences2.fullWidthContent,
24610
24652
  onChange: (checked) => updatePreferences({ fullWidthContent: checked })
24611
24653
  }),
24654
+ isDesktop ? /* @__PURE__ */ jsx96(ToggleRow, {
24655
+ label: "Smart Sidebar Edges",
24656
+ checked: preferences2.smartEdges,
24657
+ onChange: (checked) => updatePreferences({ smartEdges: checked })
24658
+ }) : null,
24612
24659
  /* @__PURE__ */ jsx96(FontPickerRow, {
24613
24660
  value: preferences2.fontFamily,
24614
24661
  onChange: (fontFamily) => updatePreferences({ fontFamily })
@@ -24693,7 +24740,9 @@ function PreferencesModal({ isOpen, onClose }) {
24693
24740
  })
24694
24741
  });
24695
24742
  }
24696
- var SettingsSidebar = memo34(function SettingsSidebar2() {
24743
+ var SettingsSidebar = memo34(function SettingsSidebar2({
24744
+ onOpenDashboard
24745
+ } = {}) {
24697
24746
  const isExpanded = useSettingsStore((state) => state.isExpanded);
24698
24747
  const collapseSidebar = useSettingsStore((state) => state.collapseSidebar);
24699
24748
  const [isPreferencesOpen, setIsPreferencesOpen] = useState43(false);
@@ -24850,6 +24899,35 @@ var SettingsSidebar = memo34(function SettingsSidebar2() {
24850
24899
  /* @__PURE__ */ jsx96(OttoRouterTopupModal, {})
24851
24900
  ]
24852
24901
  }),
24902
+ /* @__PURE__ */ jsxs84("button", {
24903
+ type: "button",
24904
+ onClick: () => {
24905
+ if (onOpenDashboard) {
24906
+ onOpenDashboard();
24907
+ return;
24908
+ }
24909
+ if (typeof window === "undefined")
24910
+ return;
24911
+ const basePathRaw = globalThis.OTTO_ROUTER_BASEPATH ?? "/";
24912
+ const basePath = basePathRaw.replace(/\/+$/, "");
24913
+ const target = `${basePath}/dashboard`.replace(/\/+/g, "/");
24914
+ window.location.assign(target || "/dashboard");
24915
+ },
24916
+ title: "Open usage dashboard",
24917
+ className: "group shrink-0 w-full h-12 px-3 flex items-center gap-2 bg-muted/20 hover:bg-muted/60 border-t border-border transition-colors text-left cursor-pointer",
24918
+ children: [
24919
+ /* @__PURE__ */ jsx96(BarChart3, {
24920
+ className: "w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors"
24921
+ }),
24922
+ /* @__PURE__ */ jsx96("span", {
24923
+ className: "text-sm flex-1 text-muted-foreground group-hover:text-foreground transition-colors",
24924
+ children: "Usage Dashboard"
24925
+ }),
24926
+ /* @__PURE__ */ jsx96(ChevronRight13, {
24927
+ className: "w-4 h-4 text-muted-foreground group-hover:text-foreground group-hover:translate-x-0.5 transition-all"
24928
+ })
24929
+ ]
24930
+ }),
24853
24931
  /* @__PURE__ */ jsxs84("button", {
24854
24932
  type: "button",
24855
24933
  onClick: () => setIsPreferencesOpen(true),
@@ -31497,8 +31575,966 @@ var OnboardingModal = memo52(function OnboardingModal2({
31497
31575
  ]
31498
31576
  });
31499
31577
  });
31578
+ // src/components/dashboard/UsageDashboard.tsx
31579
+ import { useCallback as useCallback35, useEffect as useEffect58, useMemo as useMemo31, useState as useState51 } from "react";
31580
+ import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft2, Globe2 as Globe22, RefreshCw as RefreshCw12 } from "lucide-react";
31581
+ import { jsx as jsx116, jsxs as jsxs103, Fragment as Fragment44 } from "react/jsx-runtime";
31582
+ function formatNumber(n) {
31583
+ if (!Number.isFinite(n) || n === 0)
31584
+ return "0";
31585
+ if (Math.abs(n) >= 1e9)
31586
+ return `${(n / 1e9).toFixed(1)}B`;
31587
+ if (Math.abs(n) >= 1e6)
31588
+ return `${(n / 1e6).toFixed(1)}M`;
31589
+ if (Math.abs(n) >= 1000)
31590
+ return `${(n / 1000).toFixed(1)}k`;
31591
+ return n.toLocaleString();
31592
+ }
31593
+ function formatUsd(n) {
31594
+ if (!Number.isFinite(n) || n === 0)
31595
+ return "$0";
31596
+ if (n < 0.01)
31597
+ return `$${n.toFixed(4)}`;
31598
+ if (n < 1)
31599
+ return `$${n.toFixed(3)}`;
31600
+ if (n < 100)
31601
+ return `$${n.toFixed(2)}`;
31602
+ return `$${n.toFixed(0)}`;
31603
+ }
31604
+ function authLabel(t) {
31605
+ if (t === "oauth")
31606
+ return "oauth";
31607
+ if (t === "subscription")
31608
+ return "sub";
31609
+ if (t === "wallet")
31610
+ return "wallet";
31611
+ if (t === "api")
31612
+ return "api";
31613
+ return "—";
31614
+ }
31615
+ function authColor(t) {
31616
+ if (t === "oauth")
31617
+ return "text-emerald-400";
31618
+ if (t === "subscription")
31619
+ return "text-violet-400";
31620
+ if (t === "wallet")
31621
+ return "text-fuchsia-400";
31622
+ if (t === "api")
31623
+ return "text-sky-400";
31624
+ return "text-muted-foreground";
31625
+ }
31626
+ function DailyChart({ data }) {
31627
+ const [tab, setTab] = useState51("cost");
31628
+ const [hover, setHover] = useState51(null);
31629
+ const max = useMemo31(() => {
31630
+ if (tab === "tokens") {
31631
+ return data.reduce((m, d) => Math.max(m, d.inputTokens + d.outputTokens), 0);
31632
+ }
31633
+ return data.reduce((m, d) => Math.max(m, d.notionalCostUsd), 0);
31634
+ }, [data, tab]);
31635
+ if (data.length === 0) {
31636
+ return /* @__PURE__ */ jsx116("div", {
31637
+ className: "h-44 flex items-center justify-center text-xs text-muted-foreground",
31638
+ children: "No activity yet"
31639
+ });
31640
+ }
31641
+ const focus = hover != null ? data[hover] : data[data.length - 1];
31642
+ const focusValue = tab === "tokens" ? formatNumber(focus.inputTokens + focus.outputTokens) : formatUsd(focus.notionalCostUsd);
31643
+ const focusSub = tab === "tokens" ? `${formatNumber(focus.inputTokens)} in · ${formatNumber(focus.outputTokens)} out` : focus.costUsd > 0 ? `${formatUsd(focus.costUsd)} pay-as-you-go · ${formatUsd(focus.notionalCostUsd - focus.costUsd)} via plans` : `${formatUsd(focus.notionalCostUsd)} via plans`;
31644
+ return /* @__PURE__ */ jsxs103("div", {
31645
+ children: [
31646
+ /* @__PURE__ */ jsxs103("div", {
31647
+ className: "flex items-baseline justify-between gap-4 mb-4",
31648
+ children: [
31649
+ /* @__PURE__ */ jsxs103("div", {
31650
+ className: "min-w-0",
31651
+ children: [
31652
+ /* @__PURE__ */ jsx116("div", {
31653
+ className: "text-[10px] uppercase tracking-[0.12em] text-muted-foreground",
31654
+ children: focus.date
31655
+ }),
31656
+ /* @__PURE__ */ jsx116("div", {
31657
+ className: "text-xl font-semibold tabular-nums mt-0.5 truncate",
31658
+ children: focusValue
31659
+ }),
31660
+ /* @__PURE__ */ jsx116("div", {
31661
+ className: "text-[11px] text-muted-foreground tabular-nums mt-0.5 truncate",
31662
+ children: focusSub
31663
+ })
31664
+ ]
31665
+ }),
31666
+ /* @__PURE__ */ jsxs103("div", {
31667
+ className: "shrink-0 inline-flex p-0.5 rounded-md border border-border bg-muted/30 text-[11px]",
31668
+ children: [
31669
+ /* @__PURE__ */ jsx116("button", {
31670
+ type: "button",
31671
+ onClick: () => setTab("cost"),
31672
+ className: `px-2.5 py-1 rounded transition-colors ${tab === "cost" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
31673
+ children: "Cost"
31674
+ }),
31675
+ /* @__PURE__ */ jsx116("button", {
31676
+ type: "button",
31677
+ onClick: () => setTab("tokens"),
31678
+ className: `px-2.5 py-1 rounded transition-colors ${tab === "tokens" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
31679
+ children: "Tokens"
31680
+ })
31681
+ ]
31682
+ })
31683
+ ]
31684
+ }),
31685
+ tab === "cost" ? /* @__PURE__ */ jsx116(CostChart, {
31686
+ data,
31687
+ max,
31688
+ hover,
31689
+ onHover: setHover
31690
+ }) : /* @__PURE__ */ jsx116(TokenChart, {
31691
+ data,
31692
+ max,
31693
+ hover,
31694
+ onHover: setHover
31695
+ }),
31696
+ /* @__PURE__ */ jsxs103("div", {
31697
+ className: "mt-3 flex items-center justify-between text-[10px] text-muted-foreground font-mono",
31698
+ children: [
31699
+ /* @__PURE__ */ jsx116("span", {
31700
+ children: data[0]?.date
31701
+ }),
31702
+ tab === "cost" ? /* @__PURE__ */ jsxs103("span", {
31703
+ className: "flex items-center gap-3",
31704
+ children: [
31705
+ /* @__PURE__ */ jsx116(Dot, {
31706
+ color: "bg-sky-400",
31707
+ label: "api"
31708
+ }),
31709
+ /* @__PURE__ */ jsx116(Dot, {
31710
+ color: "bg-emerald-400/70",
31711
+ label: "oauth"
31712
+ }),
31713
+ /* @__PURE__ */ jsx116(Dot, {
31714
+ color: "bg-violet-400/70",
31715
+ label: "sub"
31716
+ })
31717
+ ]
31718
+ }) : /* @__PURE__ */ jsxs103("span", {
31719
+ className: "flex items-center gap-3",
31720
+ children: [
31721
+ /* @__PURE__ */ jsx116(Dot, {
31722
+ color: "bg-muted-foreground/60",
31723
+ label: "input"
31724
+ }),
31725
+ /* @__PURE__ */ jsx116(Dot, {
31726
+ color: "bg-foreground/80",
31727
+ label: "output"
31728
+ })
31729
+ ]
31730
+ }),
31731
+ /* @__PURE__ */ jsx116("span", {
31732
+ children: data[data.length - 1]?.date
31733
+ })
31734
+ ]
31735
+ })
31736
+ ]
31737
+ });
31738
+ }
31739
+ function CostChart({ data, max, hover, onHover }) {
31740
+ return /* @__PURE__ */ jsx116("div", {
31741
+ role: "img",
31742
+ "aria-label": "Daily cost chart",
31743
+ className: "flex items-end gap-[3px] h-44 select-none",
31744
+ onMouseLeave: () => onHover(null),
31745
+ children: data.map((d, i) => {
31746
+ const notional = d.notionalCostUsd;
31747
+ const heightPct = max > 0 ? Math.max(2, notional / max * 100) : 2;
31748
+ const apiPct = notional > 0 ? d.notionalByAuth.api / notional * 100 : 0;
31749
+ const oauthPct = notional > 0 ? d.notionalByAuth.oauth / notional * 100 : 0;
31750
+ const subPct = notional > 0 ? d.notionalByAuth.subscription / notional * 100 : 0;
31751
+ const active = hover === i;
31752
+ return /* @__PURE__ */ jsx116("button", {
31753
+ type: "button",
31754
+ onMouseEnter: () => onHover(i),
31755
+ onFocus: () => onHover(i),
31756
+ className: "flex-1 h-full flex flex-col justify-end min-w-0 group cursor-default",
31757
+ children: /* @__PURE__ */ jsxs103("div", {
31758
+ className: `w-full rounded-sm overflow-hidden flex flex-col-reverse transition-all ${active ? "opacity-100" : "opacity-90 group-hover:opacity-100"}`,
31759
+ style: { height: `${heightPct}%` },
31760
+ children: [
31761
+ apiPct > 0 && /* @__PURE__ */ jsx116("div", {
31762
+ className: "bg-sky-400",
31763
+ style: { height: `${apiPct}%` }
31764
+ }),
31765
+ oauthPct > 0 && /* @__PURE__ */ jsx116("div", {
31766
+ className: "bg-emerald-400/70",
31767
+ style: {
31768
+ height: `${oauthPct}%`,
31769
+ backgroundImage: "repeating-linear-gradient(45deg, rgba(255,255,255,0.08) 0 2px, transparent 2px 4px)"
31770
+ }
31771
+ }),
31772
+ subPct > 0 && /* @__PURE__ */ jsx116("div", {
31773
+ className: "bg-violet-400/70",
31774
+ style: {
31775
+ height: `${subPct}%`,
31776
+ backgroundImage: "repeating-linear-gradient(45deg, rgba(255,255,255,0.08) 0 2px, transparent 2px 4px)"
31777
+ }
31778
+ }),
31779
+ notional === 0 && d.messages > 0 && /* @__PURE__ */ jsx116("div", {
31780
+ className: "bg-muted-foreground/30 h-full"
31781
+ })
31782
+ ]
31783
+ })
31784
+ }, d.date);
31785
+ })
31786
+ });
31787
+ }
31788
+ function TokenChart({ data, max, hover, onHover }) {
31789
+ return /* @__PURE__ */ jsx116("div", {
31790
+ role: "img",
31791
+ "aria-label": "Daily tokens chart",
31792
+ className: "flex items-end gap-[3px] h-44 select-none",
31793
+ onMouseLeave: () => onHover(null),
31794
+ children: data.map((d, i) => {
31795
+ const total = d.inputTokens + d.outputTokens;
31796
+ const heightPct = max > 0 ? Math.max(2, total / max * 100) : 2;
31797
+ const inputPct = total > 0 ? d.inputTokens / total * 100 : 0;
31798
+ const outputPct = total > 0 ? d.outputTokens / total * 100 : 0;
31799
+ const active = hover === i;
31800
+ return /* @__PURE__ */ jsx116("button", {
31801
+ type: "button",
31802
+ onMouseEnter: () => onHover(i),
31803
+ onFocus: () => onHover(i),
31804
+ className: "flex-1 h-full flex flex-col justify-end min-w-0 group cursor-default",
31805
+ children: /* @__PURE__ */ jsxs103("div", {
31806
+ className: `w-full rounded-sm overflow-hidden flex flex-col-reverse transition-all ${active ? "opacity-100" : "opacity-90 group-hover:opacity-100"}`,
31807
+ style: { height: `${heightPct}%` },
31808
+ children: [
31809
+ inputPct > 0 && /* @__PURE__ */ jsx116("div", {
31810
+ className: "bg-muted-foreground/60",
31811
+ style: { height: `${inputPct}%` }
31812
+ }),
31813
+ outputPct > 0 && /* @__PURE__ */ jsx116("div", {
31814
+ className: "bg-foreground/80",
31815
+ style: { height: `${outputPct}%` }
31816
+ })
31817
+ ]
31818
+ })
31819
+ }, d.date);
31820
+ })
31821
+ });
31822
+ }
31823
+ function Dot({ color, label }) {
31824
+ return /* @__PURE__ */ jsxs103("span", {
31825
+ className: "flex items-center gap-1",
31826
+ children: [
31827
+ /* @__PURE__ */ jsx116("span", {
31828
+ className: `size-1.5 rounded-full ${color}`
31829
+ }),
31830
+ /* @__PURE__ */ jsx116("span", {
31831
+ children: label
31832
+ })
31833
+ ]
31834
+ });
31835
+ }
31836
+ function SplitRow({
31837
+ color,
31838
+ label,
31839
+ msgs,
31840
+ value,
31841
+ total
31842
+ }) {
31843
+ const pct = total > 0 ? msgs / total * 100 : 0;
31844
+ return /* @__PURE__ */ jsxs103("div", {
31845
+ className: "flex items-center gap-3 py-2",
31846
+ children: [
31847
+ /* @__PURE__ */ jsx116("span", {
31848
+ className: "text-xs text-muted-foreground w-20 shrink-0",
31849
+ children: label
31850
+ }),
31851
+ /* @__PURE__ */ jsx116("div", {
31852
+ className: "flex-1 h-1.5 rounded-full bg-muted/40 overflow-hidden",
31853
+ children: /* @__PURE__ */ jsx116("div", {
31854
+ className: `h-full ${color} transition-all duration-500`,
31855
+ style: { width: `${pct}%` }
31856
+ })
31857
+ }),
31858
+ /* @__PURE__ */ jsxs103("span", {
31859
+ className: "text-[11px] text-muted-foreground tabular-nums w-20 text-right shrink-0",
31860
+ children: [
31861
+ formatNumber(msgs),
31862
+ " msgs"
31863
+ ]
31864
+ }),
31865
+ /* @__PURE__ */ jsx116("span", {
31866
+ className: "text-sm font-medium tabular-nums w-20 text-right shrink-0 text-foreground",
31867
+ children: formatUsd(value)
31868
+ })
31869
+ ]
31870
+ });
31871
+ }
31872
+ function AuthSplit({ stats }) {
31873
+ const total = stats.totals.messagesByAuth.api + stats.totals.messagesByAuth.oauth + stats.totals.messagesByAuth.subscription;
31874
+ if (total === 0)
31875
+ return /* @__PURE__ */ jsx116("div", {
31876
+ className: "text-xs text-muted-foreground",
31877
+ children: "No activity yet."
31878
+ });
31879
+ const notionalByAuth = { oauth: 0, api: 0, subscription: 0 };
31880
+ for (const p of stats.providers) {
31881
+ if (p.authType === "oauth")
31882
+ notionalByAuth.oauth += p.notionalCostUsd;
31883
+ else if (p.authType === "subscription" || p.authType === "wallet")
31884
+ notionalByAuth.subscription += p.notionalCostUsd;
31885
+ else if (p.authType === "api")
31886
+ notionalByAuth.api += p.notionalCostUsd;
31887
+ }
31888
+ const rows = [
31889
+ {
31890
+ key: "api",
31891
+ color: "bg-sky-400",
31892
+ label: "API key",
31893
+ msgs: stats.totals.messagesByAuth.api,
31894
+ value: notionalByAuth.api
31895
+ },
31896
+ {
31897
+ key: "oauth",
31898
+ color: "bg-emerald-400",
31899
+ label: "OAuth",
31900
+ msgs: stats.totals.messagesByAuth.oauth,
31901
+ value: notionalByAuth.oauth
31902
+ },
31903
+ {
31904
+ key: "subscription",
31905
+ color: "bg-violet-400",
31906
+ label: "Subscription",
31907
+ msgs: stats.totals.messagesByAuth.subscription,
31908
+ value: notionalByAuth.subscription
31909
+ }
31910
+ ].sort((a, b) => b.value - a.value || b.msgs - a.msgs);
31911
+ return /* @__PURE__ */ jsx116("div", {
31912
+ className: "space-y-0.5",
31913
+ children: rows.map((r) => /* @__PURE__ */ jsx116(SplitRow, {
31914
+ total,
31915
+ color: r.color,
31916
+ label: r.label,
31917
+ msgs: r.msgs,
31918
+ value: r.value
31919
+ }, r.key))
31920
+ });
31921
+ }
31922
+ function ProviderList({ providers }) {
31923
+ if (providers.length === 0) {
31924
+ return /* @__PURE__ */ jsx116("div", {
31925
+ className: "py-10 text-center text-xs text-muted-foreground",
31926
+ children: "No usage yet"
31927
+ });
31928
+ }
31929
+ return /* @__PURE__ */ jsx116("div", {
31930
+ className: "divide-y divide-border/60",
31931
+ children: providers.map((p) => /* @__PURE__ */ jsxs103("div", {
31932
+ className: "flex items-center gap-3 py-2.5 px-1 hover:bg-muted/20 transition-colors",
31933
+ children: [
31934
+ /* @__PURE__ */ jsx116("div", {
31935
+ className: "size-7 shrink-0 rounded-md bg-muted/30 flex items-center justify-center text-muted-foreground",
31936
+ children: /* @__PURE__ */ jsx116(ProviderLogo, {
31937
+ provider: p.provider,
31938
+ size: 16
31939
+ })
31940
+ }),
31941
+ /* @__PURE__ */ jsxs103("div", {
31942
+ className: "flex-1 min-w-0",
31943
+ children: [
31944
+ /* @__PURE__ */ jsx116("div", {
31945
+ className: "text-sm text-foreground truncate capitalize",
31946
+ children: p.provider
31947
+ }),
31948
+ /* @__PURE__ */ jsxs103("div", {
31949
+ className: "text-[10px] text-muted-foreground tabular-nums",
31950
+ children: [
31951
+ formatNumber(p.messages),
31952
+ " msgs ·",
31953
+ " ",
31954
+ /* @__PURE__ */ jsx116("span", {
31955
+ className: authColor(p.authType),
31956
+ children: authLabel(p.authType)
31957
+ })
31958
+ ]
31959
+ })
31960
+ ]
31961
+ }),
31962
+ /* @__PURE__ */ jsxs103("div", {
31963
+ className: "text-right tabular-nums shrink-0",
31964
+ children: [
31965
+ /* @__PURE__ */ jsx116("div", {
31966
+ className: "text-sm font-medium text-foreground",
31967
+ children: formatUsd(p.notionalCostUsd)
31968
+ }),
31969
+ /* @__PURE__ */ jsxs103("div", {
31970
+ className: "text-[10px] text-muted-foreground",
31971
+ children: [
31972
+ formatNumber(p.inputTokens + p.outputTokens),
31973
+ " tok"
31974
+ ]
31975
+ })
31976
+ ]
31977
+ })
31978
+ ]
31979
+ }, p.provider))
31980
+ });
31981
+ }
31982
+ function ModelList({ models }) {
31983
+ if (models.length === 0) {
31984
+ return /* @__PURE__ */ jsx116("div", {
31985
+ className: "py-10 text-center text-xs text-muted-foreground",
31986
+ children: "No model usage yet"
31987
+ });
31988
+ }
31989
+ return /* @__PURE__ */ jsx116("div", {
31990
+ className: "divide-y divide-border/60",
31991
+ children: models.slice(0, 12).map((m) => /* @__PURE__ */ jsxs103("div", {
31992
+ className: "flex items-center gap-3 py-2.5 px-1 hover:bg-muted/20 transition-colors",
31993
+ children: [
31994
+ /* @__PURE__ */ jsx116("div", {
31995
+ className: "size-7 shrink-0 rounded-md bg-muted/30 flex items-center justify-center text-muted-foreground",
31996
+ children: /* @__PURE__ */ jsx116(ProviderLogo, {
31997
+ provider: m.provider,
31998
+ size: 14
31999
+ })
32000
+ }),
32001
+ /* @__PURE__ */ jsxs103("div", {
32002
+ className: "flex-1 min-w-0",
32003
+ children: [
32004
+ /* @__PURE__ */ jsx116("div", {
32005
+ className: "text-sm text-foreground truncate font-mono",
32006
+ children: m.model
32007
+ }),
32008
+ /* @__PURE__ */ jsxs103("div", {
32009
+ className: "text-[10px] text-muted-foreground tabular-nums",
32010
+ children: [
32011
+ formatNumber(m.messages),
32012
+ " msgs ·",
32013
+ " ",
32014
+ formatNumber(m.inputTokens + m.outputTokens),
32015
+ " tok"
32016
+ ]
32017
+ })
32018
+ ]
32019
+ }),
32020
+ /* @__PURE__ */ jsxs103("div", {
32021
+ className: "text-right tabular-nums shrink-0",
32022
+ children: [
32023
+ /* @__PURE__ */ jsx116("div", {
32024
+ className: "text-sm font-medium text-foreground",
32025
+ children: formatUsd(m.notionalCostUsd)
32026
+ }),
32027
+ /* @__PURE__ */ jsx116("div", {
32028
+ className: `text-[10px] ${authColor(m.authType)}`,
32029
+ children: authLabel(m.authType)
32030
+ })
32031
+ ]
32032
+ })
32033
+ ]
32034
+ }, `${m.provider}-${m.model}`))
32035
+ });
32036
+ }
32037
+ function ProjectList({
32038
+ projects
32039
+ }) {
32040
+ if (projects.included.length === 0 && projects.unavailable.length === 0) {
32041
+ return /* @__PURE__ */ jsx116("div", {
32042
+ className: "py-10 text-center text-xs text-muted-foreground",
32043
+ children: "No projects registered yet"
32044
+ });
32045
+ }
32046
+ return /* @__PURE__ */ jsxs103("div", {
32047
+ className: "divide-y divide-border/60",
32048
+ children: [
32049
+ projects.included.map((p) => /* @__PURE__ */ jsxs103("div", {
32050
+ className: "flex items-center gap-3 py-2.5 px-1 hover:bg-muted/20 transition-colors",
32051
+ children: [
32052
+ /* @__PURE__ */ jsxs103("div", {
32053
+ className: "flex-1 min-w-0",
32054
+ children: [
32055
+ /* @__PURE__ */ jsx116("div", {
32056
+ className: "text-sm text-foreground truncate",
32057
+ children: p.name
32058
+ }),
32059
+ /* @__PURE__ */ jsx116("div", {
32060
+ className: "text-[10px] text-muted-foreground tabular-nums truncate font-mono",
32061
+ children: p.path
32062
+ })
32063
+ ]
32064
+ }),
32065
+ /* @__PURE__ */ jsxs103("div", {
32066
+ className: "text-right tabular-nums shrink-0",
32067
+ children: [
32068
+ /* @__PURE__ */ jsx116("div", {
32069
+ className: "text-sm font-medium text-foreground",
32070
+ children: formatUsd(p.notionalCostUsd)
32071
+ }),
32072
+ /* @__PURE__ */ jsxs103("div", {
32073
+ className: "text-[10px] text-muted-foreground",
32074
+ children: [
32075
+ formatNumber(p.messages),
32076
+ " msgs"
32077
+ ]
32078
+ })
32079
+ ]
32080
+ })
32081
+ ]
32082
+ }, p.id)),
32083
+ projects.unavailable.map((p) => /* @__PURE__ */ jsxs103("div", {
32084
+ className: "flex items-center gap-3 py-2.5 px-1 opacity-60",
32085
+ title: p.reason,
32086
+ children: [
32087
+ /* @__PURE__ */ jsx116(AlertTriangle3, {
32088
+ className: "size-3.5 text-amber-400 shrink-0"
32089
+ }),
32090
+ /* @__PURE__ */ jsxs103("div", {
32091
+ className: "flex-1 min-w-0",
32092
+ children: [
32093
+ /* @__PURE__ */ jsx116("div", {
32094
+ className: "text-sm text-foreground/80 truncate",
32095
+ children: p.name
32096
+ }),
32097
+ /* @__PURE__ */ jsxs103("div", {
32098
+ className: "text-[10px] text-muted-foreground truncate font-mono",
32099
+ children: [
32100
+ p.path,
32101
+ " · ",
32102
+ p.reason
32103
+ ]
32104
+ })
32105
+ ]
32106
+ }),
32107
+ /* @__PURE__ */ jsx116("div", {
32108
+ className: "text-[10px] text-amber-400 shrink-0",
32109
+ children: "unavailable"
32110
+ })
32111
+ ]
32112
+ }, p.id))
32113
+ ]
32114
+ });
32115
+ }
32116
+ function Section({
32117
+ title,
32118
+ subtitle,
32119
+ right,
32120
+ children,
32121
+ className = ""
32122
+ }) {
32123
+ return /* @__PURE__ */ jsxs103("section", {
32124
+ className: `rounded-2xl border border-border bg-card/60 backdrop-blur-sm ${className}`,
32125
+ children: [
32126
+ (title || right) && /* @__PURE__ */ jsxs103("header", {
32127
+ className: "px-5 pt-4 pb-3 flex items-center justify-between gap-3",
32128
+ children: [
32129
+ /* @__PURE__ */ jsxs103("div", {
32130
+ className: "min-w-0",
32131
+ children: [
32132
+ title && /* @__PURE__ */ jsx116("h2", {
32133
+ className: "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground",
32134
+ children: title
32135
+ }),
32136
+ subtitle && /* @__PURE__ */ jsx116("p", {
32137
+ className: "text-xs text-muted-foreground/70 mt-0.5",
32138
+ children: subtitle
32139
+ })
32140
+ ]
32141
+ }),
32142
+ right
32143
+ ]
32144
+ }),
32145
+ /* @__PURE__ */ jsx116("div", {
32146
+ className: "px-5 pb-5",
32147
+ children
32148
+ })
32149
+ ]
32150
+ });
32151
+ }
32152
+ function UsageDashboard({ onBack }) {
32153
+ const [stats, setStats] = useState51(null);
32154
+ const [loading, setLoading] = useState51(false);
32155
+ const [error, setError] = useState51(null);
32156
+ const [scope, setScope] = useState51("project");
32157
+ const fetchStats = useCallback35(async () => {
32158
+ setLoading(true);
32159
+ setError(null);
32160
+ try {
32161
+ const data = scope === "global" ? await apiClient.getGlobalUsageStats() : await apiClient.getUsageStats();
32162
+ setStats(data);
32163
+ } catch (e) {
32164
+ setError(e instanceof Error ? e.message : "Failed to load usage stats");
32165
+ } finally {
32166
+ setLoading(false);
32167
+ }
32168
+ }, [scope]);
32169
+ useEffect58(() => {
32170
+ fetchStats();
32171
+ }, [fetchStats]);
32172
+ const handleBack = useCallback35(() => {
32173
+ if (onBack)
32174
+ return onBack();
32175
+ if (typeof window === "undefined")
32176
+ return;
32177
+ if (window.history.length > 1)
32178
+ window.history.back();
32179
+ else
32180
+ window.location.assign("/");
32181
+ }, [onBack]);
32182
+ const projectName = useMemo31(() => {
32183
+ if (scope === "global") {
32184
+ const included = stats?.projects?.included.length ?? 0;
32185
+ const unavailable = stats?.projects?.unavailable.length ?? 0;
32186
+ const total = included + unavailable;
32187
+ if (total === 0)
32188
+ return "all projects";
32189
+ return unavailable > 0 ? `${included} of ${total} projects` : `${total} project${total === 1 ? "" : "s"}`;
32190
+ }
32191
+ if (!stats?.project)
32192
+ return "";
32193
+ const parts = stats.project.split("/").filter(Boolean);
32194
+ return parts[parts.length - 1] ?? stats.project;
32195
+ }, [scope, stats?.project, stats?.projects]);
32196
+ const totalSpend = stats?.totals.costUsd ?? 0;
32197
+ const totalMessages = stats?.totals.messages ?? 0;
32198
+ const totalTokens = (stats?.totals.inputTokens ?? 0) + (stats?.totals.outputTokens ?? 0);
32199
+ return /* @__PURE__ */ jsxs103("div", {
32200
+ className: "fixed inset-0 flex flex-col bg-background text-foreground",
32201
+ children: [
32202
+ /* @__PURE__ */ jsx116("header", {
32203
+ className: "shrink-0 h-10 border-b border-border/60 bg-background/80 backdrop-blur",
32204
+ children: /* @__PURE__ */ jsxs103("div", {
32205
+ className: "h-full max-w-5xl mx-auto px-6 flex items-center justify-between gap-3",
32206
+ children: [
32207
+ /* @__PURE__ */ jsxs103("div", {
32208
+ className: "flex items-center gap-2 min-w-0",
32209
+ children: [
32210
+ /* @__PURE__ */ jsx116("button", {
32211
+ type: "button",
32212
+ onClick: handleBack,
32213
+ className: "inline-flex items-center justify-center size-6 rounded text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors",
32214
+ title: "Back",
32215
+ "aria-label": "Back",
32216
+ children: /* @__PURE__ */ jsx116(ArrowLeft2, {
32217
+ className: "size-3.5"
32218
+ })
32219
+ }),
32220
+ /* @__PURE__ */ jsxs103("div", {
32221
+ className: "min-w-0 flex items-baseline gap-1.5 text-xs",
32222
+ children: [
32223
+ /* @__PURE__ */ jsx116("span", {
32224
+ className: "text-muted-foreground/70 uppercase tracking-[0.1em] text-[10px]",
32225
+ children: "Usage"
32226
+ }),
32227
+ /* @__PURE__ */ jsx116("span", {
32228
+ className: "text-muted-foreground/40",
32229
+ children: "/"
32230
+ }),
32231
+ /* @__PURE__ */ jsx116("span", {
32232
+ className: "font-medium truncate text-foreground",
32233
+ children: projectName || "—"
32234
+ })
32235
+ ]
32236
+ })
32237
+ ]
32238
+ }),
32239
+ /* @__PURE__ */ jsxs103("div", {
32240
+ className: "flex items-center gap-1.5",
32241
+ children: [
32242
+ /* @__PURE__ */ jsxs103("div", {
32243
+ className: "inline-flex p-0.5 rounded-md border border-border bg-muted/30 text-[11px]",
32244
+ children: [
32245
+ /* @__PURE__ */ jsx116("button", {
32246
+ type: "button",
32247
+ onClick: () => setScope("project"),
32248
+ className: `px-2 py-0.5 rounded transition-colors ${scope === "project" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
32249
+ children: "Project"
32250
+ }),
32251
+ /* @__PURE__ */ jsxs103("button", {
32252
+ type: "button",
32253
+ onClick: () => setScope("global"),
32254
+ className: `px-2 py-0.5 rounded transition-colors inline-flex items-center gap-1 ${scope === "global" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
32255
+ children: [
32256
+ /* @__PURE__ */ jsx116(Globe22, {
32257
+ className: "size-3"
32258
+ }),
32259
+ "Global"
32260
+ ]
32261
+ })
32262
+ ]
32263
+ }),
32264
+ /* @__PURE__ */ jsx116("button", {
32265
+ type: "button",
32266
+ onClick: () => void fetchStats(),
32267
+ disabled: loading,
32268
+ className: "inline-flex items-center justify-center size-6 rounded text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors disabled:opacity-50",
32269
+ title: "Refresh",
32270
+ "aria-label": "Refresh",
32271
+ children: /* @__PURE__ */ jsx116(RefreshCw12, {
32272
+ className: `size-3.5 ${loading ? "animate-spin" : ""}`
32273
+ })
32274
+ })
32275
+ ]
32276
+ })
32277
+ ]
32278
+ })
32279
+ }),
32280
+ /* @__PURE__ */ jsx116("main", {
32281
+ className: "flex-1 min-h-0 overflow-y-auto overscroll-contain",
32282
+ children: /* @__PURE__ */ jsxs103("div", {
32283
+ className: "max-w-5xl mx-auto px-6 py-8 space-y-5",
32284
+ children: [
32285
+ error && /* @__PURE__ */ jsx116("div", {
32286
+ className: "rounded-xl border border-destructive/40 bg-destructive/10 p-3 text-xs text-destructive",
32287
+ children: error
32288
+ }),
32289
+ loading && !stats && /* @__PURE__ */ jsx116("div", {
32290
+ className: "py-24 text-center text-xs text-muted-foreground",
32291
+ children: "loading…"
32292
+ }),
32293
+ stats && /* @__PURE__ */ jsxs103(Fragment44, {
32294
+ children: [
32295
+ /* @__PURE__ */ jsxs103("div", {
32296
+ className: "rounded-2xl border border-border bg-gradient-to-br from-card to-card/40 px-6 py-7",
32297
+ children: [
32298
+ /* @__PURE__ */ jsxs103("div", {
32299
+ className: "grid grid-cols-1 md:grid-cols-[1.4fr_1fr_1fr] gap-6 md:gap-10 items-center",
32300
+ children: [
32301
+ /* @__PURE__ */ jsxs103("div", {
32302
+ children: [
32303
+ /* @__PURE__ */ jsx116("div", {
32304
+ className: "text-[10px] uppercase tracking-[0.18em] text-muted-foreground",
32305
+ children: "Token value · API rates"
32306
+ }),
32307
+ /* @__PURE__ */ jsx116("div", {
32308
+ className: "mt-2 text-5xl font-semibold tabular-nums tracking-tight",
32309
+ children: formatUsd(stats.totals.notionalCostUsd)
32310
+ }),
32311
+ /* @__PURE__ */ jsxs103("div", {
32312
+ className: "mt-2 text-[11px] text-muted-foreground tabular-nums",
32313
+ children: [
32314
+ "all ",
32315
+ formatNumber(stats.totals.messages),
32316
+ " msgs valued at catalog API pricing"
32317
+ ]
32318
+ })
32319
+ ]
32320
+ }),
32321
+ /* @__PURE__ */ jsxs103("div", {
32322
+ className: "md:border-l md:border-border/60 md:pl-6",
32323
+ children: [
32324
+ /* @__PURE__ */ jsx116("div", {
32325
+ className: "text-[10px] uppercase tracking-[0.18em] text-muted-foreground",
32326
+ children: "API spend"
32327
+ }),
32328
+ /* @__PURE__ */ jsx116("div", {
32329
+ className: "mt-2 text-3xl font-semibold tabular-nums tracking-tight",
32330
+ children: formatUsd(totalSpend)
32331
+ }),
32332
+ /* @__PURE__ */ jsxs103("div", {
32333
+ className: "mt-2 text-[11px] text-muted-foreground tabular-nums",
32334
+ children: [
32335
+ formatNumber(stats.totals.messagesByAuth.api),
32336
+ " ",
32337
+ "pay-as-you-go msgs"
32338
+ ]
32339
+ })
32340
+ ]
32341
+ }),
32342
+ /* @__PURE__ */ jsxs103("div", {
32343
+ className: "md:border-l md:border-border/60 md:pl-6",
32344
+ children: [
32345
+ /* @__PURE__ */ jsx116("div", {
32346
+ className: "text-[10px] uppercase tracking-[0.18em] text-emerald-400/80",
32347
+ children: "OAuth · plan value"
32348
+ }),
32349
+ /* @__PURE__ */ jsx116("div", {
32350
+ className: "mt-2 text-3xl font-semibold tabular-nums tracking-tight text-emerald-400",
32351
+ children: formatUsd(stats.providers.filter((p) => p.authType === "oauth").reduce((s, p) => s + p.notionalCostUsd, 0))
32352
+ }),
32353
+ /* @__PURE__ */ jsxs103("div", {
32354
+ className: "mt-2 text-[11px] text-muted-foreground tabular-nums",
32355
+ children: [
32356
+ formatNumber(stats.totals.messagesByAuth.oauth),
32357
+ " msgs via plan subscriptions"
32358
+ ]
32359
+ })
32360
+ ]
32361
+ })
32362
+ ]
32363
+ }),
32364
+ /* @__PURE__ */ jsxs103("div", {
32365
+ className: "mt-6 pt-5 border-t border-border/40 grid grid-cols-3 gap-6 text-[11px] text-muted-foreground",
32366
+ children: [
32367
+ /* @__PURE__ */ jsxs103("div", {
32368
+ children: [
32369
+ /* @__PURE__ */ jsx116("div", {
32370
+ className: "uppercase tracking-[0.14em] text-muted-foreground/70",
32371
+ children: "Messages"
32372
+ }),
32373
+ /* @__PURE__ */ jsxs103("div", {
32374
+ className: "mt-1 text-sm text-foreground tabular-nums",
32375
+ children: [
32376
+ formatNumber(totalMessages),
32377
+ " ",
32378
+ /* @__PURE__ */ jsxs103("span", {
32379
+ className: "text-muted-foreground/70",
32380
+ children: [
32381
+ "· ",
32382
+ formatNumber(stats.totals.sessions),
32383
+ " sessions"
32384
+ ]
32385
+ })
32386
+ ]
32387
+ })
32388
+ ]
32389
+ }),
32390
+ /* @__PURE__ */ jsxs103("div", {
32391
+ children: [
32392
+ /* @__PURE__ */ jsx116("div", {
32393
+ className: "uppercase tracking-[0.14em] text-muted-foreground/70",
32394
+ children: "Tokens"
32395
+ }),
32396
+ /* @__PURE__ */ jsxs103("div", {
32397
+ className: "mt-1 text-sm text-foreground tabular-nums",
32398
+ children: [
32399
+ formatNumber(totalTokens),
32400
+ " ",
32401
+ /* @__PURE__ */ jsxs103("span", {
32402
+ className: "text-muted-foreground/70",
32403
+ children: [
32404
+ "· ",
32405
+ formatNumber(stats.totals.inputTokens),
32406
+ " in /",
32407
+ " ",
32408
+ formatNumber(stats.totals.outputTokens),
32409
+ " out"
32410
+ ]
32411
+ })
32412
+ ]
32413
+ })
32414
+ ]
32415
+ }),
32416
+ /* @__PURE__ */ jsxs103("div", {
32417
+ children: [
32418
+ /* @__PURE__ */ jsx116("div", {
32419
+ className: "uppercase tracking-[0.14em] text-muted-foreground/70",
32420
+ children: "Mix"
32421
+ }),
32422
+ /* @__PURE__ */ jsxs103("div", {
32423
+ className: "mt-1 text-sm tabular-nums",
32424
+ children: [
32425
+ /* @__PURE__ */ jsxs103("span", {
32426
+ className: authColor("api"),
32427
+ children: [
32428
+ formatNumber(stats.totals.messagesByAuth.api),
32429
+ " api"
32430
+ ]
32431
+ }),
32432
+ stats.totals.messagesByAuth.oauth > 0 && /* @__PURE__ */ jsxs103(Fragment44, {
32433
+ children: [
32434
+ /* @__PURE__ */ jsx116("span", {
32435
+ className: "text-muted-foreground/40",
32436
+ children: " · "
32437
+ }),
32438
+ /* @__PURE__ */ jsxs103("span", {
32439
+ className: authColor("oauth"),
32440
+ children: [
32441
+ formatNumber(stats.totals.messagesByAuth.oauth),
32442
+ " ",
32443
+ "oauth"
32444
+ ]
32445
+ })
32446
+ ]
32447
+ }),
32448
+ stats.totals.messagesByAuth.subscription > 0 && /* @__PURE__ */ jsxs103(Fragment44, {
32449
+ children: [
32450
+ /* @__PURE__ */ jsx116("span", {
32451
+ className: "text-muted-foreground/40",
32452
+ children: " · "
32453
+ }),
32454
+ /* @__PURE__ */ jsxs103("span", {
32455
+ className: authColor("subscription"),
32456
+ children: [
32457
+ formatNumber(stats.totals.messagesByAuth.subscription),
32458
+ " ",
32459
+ "sub"
32460
+ ]
32461
+ })
32462
+ ]
32463
+ })
32464
+ ]
32465
+ })
32466
+ ]
32467
+ })
32468
+ ]
32469
+ })
32470
+ ]
32471
+ }),
32472
+ /* @__PURE__ */ jsx116(Section, {
32473
+ title: "How you're paying",
32474
+ subtitle: "OAuth & Subscription are flat-rate · not per-token",
32475
+ children: /* @__PURE__ */ jsx116(AuthSplit, {
32476
+ stats
32477
+ })
32478
+ }),
32479
+ /* @__PURE__ */ jsx116(Section, {
32480
+ title: "Daily activity",
32481
+ children: /* @__PURE__ */ jsx116(DailyChart, {
32482
+ data: stats.daily
32483
+ })
32484
+ }),
32485
+ /* @__PURE__ */ jsxs103("div", {
32486
+ className: "grid grid-cols-1 md:grid-cols-2 gap-5",
32487
+ children: [
32488
+ /* @__PURE__ */ jsx116(Section, {
32489
+ title: "By provider",
32490
+ children: /* @__PURE__ */ jsx116(ProviderList, {
32491
+ providers: stats.providers
32492
+ })
32493
+ }),
32494
+ /* @__PURE__ */ jsx116(Section, {
32495
+ title: "Top models",
32496
+ subtitle: stats.models.length > 12 ? `Showing 12 of ${stats.models.length}` : undefined,
32497
+ children: /* @__PURE__ */ jsx116(ModelList, {
32498
+ models: stats.models
32499
+ })
32500
+ })
32501
+ ]
32502
+ }),
32503
+ scope === "global" && stats.projects && /* @__PURE__ */ jsx116(Section, {
32504
+ title: "Projects",
32505
+ subtitle: stats.projects.unavailable.length > 0 ? `${stats.projects.included.length} included · ${stats.projects.unavailable.length} unavailable` : `${stats.projects.included.length} included`,
32506
+ children: /* @__PURE__ */ jsx116(ProjectList, {
32507
+ projects: stats.projects
32508
+ })
32509
+ }),
32510
+ stats.notes.missingPricing.length > 0 && /* @__PURE__ */ jsxs103("div", {
32511
+ className: "text-[10px] text-muted-foreground/70 text-center font-mono py-2",
32512
+ children: [
32513
+ "pricing missing for ",
32514
+ stats.notes.missingPricing.length,
32515
+ " model",
32516
+ stats.notes.missingPricing.length === 1 ? "" : "s",
32517
+ " · cost shown as $0"
32518
+ ]
32519
+ }),
32520
+ /* @__PURE__ */ jsxs103("div", {
32521
+ className: "text-[10px] text-muted-foreground/60 text-center pt-2 pb-4",
32522
+ children: [
32523
+ "estimated from catalog pricing · generated",
32524
+ " ",
32525
+ new Date(stats.generatedAt).toLocaleString()
32526
+ ]
32527
+ })
32528
+ ]
32529
+ })
32530
+ ]
32531
+ })
32532
+ })
32533
+ ]
32534
+ });
32535
+ }
31500
32536
  // src/hooks/useClientEvents.ts
31501
- import { useEffect as useEffect58, useRef as useRef39 } from "react";
32537
+ import { useEffect as useEffect59, useRef as useRef39 } from "react";
31502
32538
  import { useQueryClient as useQueryClient22 } from "@tanstack/react-query";
31503
32539
  import {
31504
32540
  buildClientEventsStreamUrl,
@@ -31686,10 +32722,10 @@ async function maybeShowLocalAccessToast(baseUrl) {
31686
32722
  function useClientEvents(activeSessionId) {
31687
32723
  const queryClient = useQueryClient22();
31688
32724
  const activeSessionIdRef = useRef39(activeSessionId);
31689
- useEffect58(() => {
32725
+ useEffect59(() => {
31690
32726
  activeSessionIdRef.current = activeSessionId;
31691
32727
  }, [activeSessionId]);
31692
- useEffect58(() => {
32728
+ useEffect59(() => {
31693
32729
  if (typeof window === "undefined" || window.parent !== window)
31694
32730
  return;
31695
32731
  if (!("Notification" in window))
@@ -31718,7 +32754,7 @@ function useClientEvents(activeSessionId) {
31718
32754
  }
31719
32755
  });
31720
32756
  }, []);
31721
- useEffect58(() => {
32757
+ useEffect59(() => {
31722
32758
  const controller = new AbortController;
31723
32759
  const baseUrl = getBaseUrl();
31724
32760
  createClientEventsStream({
@@ -31768,7 +32804,7 @@ function useClientEvents(activeSessionId) {
31768
32804
  return buildClientEventsStreamUrl({ baseUrl: getBaseUrl() });
31769
32805
  }
31770
32806
  // src/hooks/useTheme.ts
31771
- import { useEffect as useEffect59, useState as useState51, useCallback as useCallback35, useMemo as useMemo31 } from "react";
32807
+ import { useEffect as useEffect60, useState as useState52, useCallback as useCallback36, useMemo as useMemo32 } from "react";
31772
32808
  var STORAGE_KEY3 = "otto-theme";
31773
32809
  function resolveInitialTheme() {
31774
32810
  if (typeof window === "undefined") {
@@ -31784,8 +32820,8 @@ function resolveInitialTheme() {
31784
32820
  return "dark";
31785
32821
  }
31786
32822
  function useTheme() {
31787
- const [theme, setTheme] = useState51(() => resolveInitialTheme());
31788
- useEffect59(() => {
32823
+ const [theme, setTheme] = useState52(() => resolveInitialTheme());
32824
+ useEffect60(() => {
31789
32825
  if (typeof document === "undefined")
31790
32826
  return;
31791
32827
  const root = document.documentElement;
@@ -31803,7 +32839,7 @@ function useTheme() {
31803
32839
  window.parent.postMessage({ type: "otto-set-theme", theme }, "*");
31804
32840
  }
31805
32841
  }, [theme]);
31806
- useEffect59(() => {
32842
+ useEffect60(() => {
31807
32843
  if (typeof window === "undefined")
31808
32844
  return;
31809
32845
  const handler = (e) => {
@@ -31814,17 +32850,17 @@ function useTheme() {
31814
32850
  window.addEventListener("message", handler);
31815
32851
  return () => window.removeEventListener("message", handler);
31816
32852
  }, []);
31817
- const toggleTheme = useCallback35(() => {
32853
+ const toggleTheme = useCallback36(() => {
31818
32854
  setTheme((prev) => prev === "dark" ? "light" : "dark");
31819
32855
  }, []);
31820
- return useMemo31(() => ({ theme, setTheme, toggleTheme }), [theme, toggleTheme]);
32856
+ return useMemo32(() => ({ theme, setTheme, toggleTheme }), [theme, toggleTheme]);
31821
32857
  }
31822
32858
  // src/hooks/useWorkingDirectory.ts
31823
- import { useEffect as useEffect60, useState as useState52 } from "react";
32859
+ import { useEffect as useEffect61, useState as useState53 } from "react";
31824
32860
  import { getCwd } from "@ottocode/api";
31825
32861
  function useWorkingDirectory() {
31826
- const [dirName, setDirName] = useState52(null);
31827
- useEffect60(() => {
32862
+ const [dirName, setDirName] = useState53(null);
32863
+ useEffect61(() => {
31828
32864
  const fetchWorkingDirectory = async () => {
31829
32865
  try {
31830
32866
  const response = await getCwd({ baseURL: getBaseUrl() });
@@ -31848,7 +32884,7 @@ function useWorkingDirectory() {
31848
32884
  return dirName;
31849
32885
  }
31850
32886
  // src/hooks/useKeyboardShortcuts.ts
31851
- import { useEffect as useEffect61, useCallback as useCallback36 } from "react";
32887
+ import { useEffect as useEffect62, useCallback as useCallback37 } from "react";
31852
32888
 
31853
32889
  // src/stores/sidebarStore.ts
31854
32890
  import { create as create24 } from "zustand";
@@ -31902,7 +32938,7 @@ function useKeyboardShortcuts({
31902
32938
  const toggleResearch = useResearchStore((state) => state.toggleSidebar);
31903
32939
  const toggleTerminalPanel = useTerminalStore((state) => state.togglePanel);
31904
32940
  const currentSessionIndex = sessionIds.indexOf(activeSessionId || "");
31905
- const handleKeyDown = useCallback36((e) => {
32941
+ const handleKeyDown = useCallback37((e) => {
31906
32942
  const target = e.target;
31907
32943
  const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
31908
32944
  const isInTerminal = !!target.closest("[data-terminal-viewer]");
@@ -32152,7 +33188,7 @@ function useKeyboardShortcuts({
32152
33188
  onReturnToInput,
32153
33189
  closeDiff
32154
33190
  ]);
32155
- useEffect61(() => {
33191
+ useEffect62(() => {
32156
33192
  window.addEventListener("keydown", handleKeyDown, true);
32157
33193
  return () => window.removeEventListener("keydown", handleKeyDown, true);
32158
33194
  }, [handleKeyDown]);
@@ -32164,9 +33200,9 @@ function useKeyboardShortcuts({
32164
33200
  }
32165
33201
  // src/hooks/useImageUpload.ts
32166
33202
  import {
32167
- useState as useState53,
32168
- useCallback as useCallback37,
32169
- useEffect as useEffect62
33203
+ useState as useState54,
33204
+ useCallback as useCallback38,
33205
+ useEffect as useEffect63
32170
33206
  } from "react";
32171
33207
  var SUPPORTED_TYPES2 = ["image/png", "image/jpeg", "image/gif", "image/webp"];
32172
33208
  function generateId2() {
@@ -32194,11 +33230,11 @@ async function fileToPreview2(file) {
32194
33230
  }
32195
33231
  function useImageUpload(options = {}) {
32196
33232
  const { maxImages = 5, maxSizeMB = 5, pageWide = true } = options;
32197
- const [images, setImages] = useState53([]);
32198
- const [isDragging, setIsDragging] = useState53(false);
32199
- const [error, setError] = useState53(null);
33233
+ const [images, setImages] = useState54([]);
33234
+ const [isDragging, setIsDragging] = useState54(false);
33235
+ const [error, setError] = useState54(null);
32200
33236
  const maxSizeBytes = maxSizeMB * 1024 * 1024;
32201
- const validateFile = useCallback37((file) => {
33237
+ const validateFile = useCallback38((file) => {
32202
33238
  if (!SUPPORTED_TYPES2.includes(file.type)) {
32203
33239
  return `Unsupported file type: ${file.type}. Supported: PNG, JPEG, GIF, WebP`;
32204
33240
  }
@@ -32207,7 +33243,7 @@ function useImageUpload(options = {}) {
32207
33243
  }
32208
33244
  return null;
32209
33245
  }, [maxSizeBytes, maxSizeMB]);
32210
- const addImages = useCallback37(async (files) => {
33246
+ const addImages = useCallback38(async (files) => {
32211
33247
  setError(null);
32212
33248
  const fileArray = Array.from(files);
32213
33249
  const remaining = maxImages - images.length;
@@ -32243,22 +33279,22 @@ function useImageUpload(options = {}) {
32243
33279
  setImages((prev) => [...prev, ...newImages]);
32244
33280
  }
32245
33281
  }, [images.length, maxImages, validateFile]);
32246
- const removeImage = useCallback37((id) => {
33282
+ const removeImage = useCallback38((id) => {
32247
33283
  setImages((prev) => prev.filter((img) => img.id !== id));
32248
33284
  setError(null);
32249
33285
  }, []);
32250
- const clearImages = useCallback37(() => {
33286
+ const clearImages = useCallback38(() => {
32251
33287
  setImages([]);
32252
33288
  setError(null);
32253
33289
  }, []);
32254
- const handleDragEnter = useCallback37((e) => {
33290
+ const handleDragEnter = useCallback38((e) => {
32255
33291
  e.preventDefault();
32256
33292
  e.stopPropagation();
32257
33293
  if (e.dataTransfer.types.includes("Files")) {
32258
33294
  setIsDragging(true);
32259
33295
  }
32260
33296
  }, []);
32261
- const handleDragLeave = useCallback37((e) => {
33297
+ const handleDragLeave = useCallback38((e) => {
32262
33298
  e.preventDefault();
32263
33299
  e.stopPropagation();
32264
33300
  const rect = e.currentTarget.getBoundingClientRect();
@@ -32268,11 +33304,11 @@ function useImageUpload(options = {}) {
32268
33304
  setIsDragging(false);
32269
33305
  }
32270
33306
  }, []);
32271
- const handleDragOver = useCallback37((e) => {
33307
+ const handleDragOver = useCallback38((e) => {
32272
33308
  e.preventDefault();
32273
33309
  e.stopPropagation();
32274
33310
  }, []);
32275
- const handleDrop = useCallback37((e) => {
33311
+ const handleDrop = useCallback38((e) => {
32276
33312
  e.preventDefault();
32277
33313
  e.stopPropagation();
32278
33314
  setIsDragging(false);
@@ -32284,7 +33320,7 @@ function useImageUpload(options = {}) {
32284
33320
  }
32285
33321
  }
32286
33322
  }, [addImages]);
32287
- const handlePaste = useCallback37((e) => {
33323
+ const handlePaste = useCallback38((e) => {
32288
33324
  const items = e.clipboardData?.items;
32289
33325
  if (!items)
32290
33326
  return;
@@ -32302,7 +33338,7 @@ function useImageUpload(options = {}) {
32302
33338
  addImages(imageFiles);
32303
33339
  }
32304
33340
  }, [addImages]);
32305
- useEffect62(() => {
33341
+ useEffect63(() => {
32306
33342
  if (!pageWide)
32307
33343
  return;
32308
33344
  let dragCounter = 0;
@@ -32363,7 +33399,7 @@ function useImageUpload(options = {}) {
32363
33399
  };
32364
33400
  }
32365
33401
  // src/hooks/useSetuPayments.ts
32366
- import { useEffect as useEffect63, useRef as useRef40 } from "react";
33402
+ import { useEffect as useEffect64, useRef as useRef40 } from "react";
32367
33403
  function useSetuPayments(sessionId) {
32368
33404
  const clientRef = useRef40(null);
32369
33405
  const loadingToastIdRef = useRef40(null);
@@ -32373,7 +33409,7 @@ function useSetuPayments(sessionId) {
32373
33409
  const updateToast = useToastStore((s) => s.updateToast);
32374
33410
  const setPendingTopup = useTopupApprovalStore((s) => s.setPendingTopup);
32375
33411
  const clearPendingTopup = useTopupApprovalStore((s) => s.clearPendingTopup);
32376
- useEffect63(() => {
33412
+ useEffect64(() => {
32377
33413
  if (!sessionId)
32378
33414
  return;
32379
33415
  const client5 = new SSEClient;
@@ -32502,6 +33538,16 @@ function useSetuPayments(sessionId) {
32502
33538
  clearPendingTopup
32503
33539
  ]);
32504
33540
  }
33541
+ // src/stores/rightRailStore.ts
33542
+ import { create as create25 } from "zustand";
33543
+ import { persist as persist3 } from "zustand/middleware";
33544
+ var useRightRailStore = create25()(persist3((set) => ({
33545
+ isPinned: false,
33546
+ togglePinned: () => set((state) => ({ isPinned: !state.isPinned })),
33547
+ setPinned: (pinned) => set({ isPinned: pinned })
33548
+ }), {
33549
+ name: "right-rail-storage"
33550
+ }));
32505
33551
  export {
32506
33552
  useWorkingDirectory,
32507
33553
  useViewerTabsStore,
@@ -32542,6 +33588,7 @@ export {
32542
33588
  useSessionFiles,
32543
33589
  useSession,
32544
33590
  useSendMessage,
33591
+ useRightRailStore,
32545
33592
  useRevokeMCPAuth,
32546
33593
  useRestoreFiles,
32547
33594
  useResearchStore,
@@ -32614,6 +33661,7 @@ export {
32614
33661
  notifyPlatformFontFamilyChanged,
32615
33662
  normalizeQueueState,
32616
33663
  listPlatformSystemFonts,
33664
+ isPlatformDesktop,
32617
33665
  hasPlatformSystemFonts,
32618
33666
  hasPlatformOpenUrl,
32619
33667
  getRuntimeApiBaseUrl,
@@ -32625,6 +33673,7 @@ export {
32625
33673
  UserMessageGroup,
32626
33674
  UsageRing,
32627
33675
  UsageModal,
33676
+ UsageDashboard,
32628
33677
  TunnelSidebarToggle,
32629
33678
  TunnelSidebar,
32630
33679
  ToolResultRenderer,
@@ -32698,4 +33747,4 @@ export {
32698
33747
  API_BASE_URL
32699
33748
  };
32700
33749
 
32701
- //# debugId=EA8B7C332CFA367264756E2164756E21
33750
+ //# debugId=C84305701420A48C64756E2164756E21