@@ -16,6 +16,7 @@ import {
1616 type ProviderKind ,
1717 type ServerProvider ,
1818 type ServerProviderModel ,
19+ type ServerProviderUsageBucket ,
1920 ThreadId ,
2021} from "@t3tools/contracts" ;
2122import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings" ;
@@ -46,6 +47,11 @@ import {
4647 getCustomModelOptionsByProvider ,
4748 resolveAppModelSelectionState ,
4849} from "../../modelSelection" ;
50+ import {
51+ formatUsageRemainingPercent ,
52+ formatUsageResetAt ,
53+ getProviderUsageBuckets ,
54+ } from "../../lib/accountQuota" ;
4955import { ensureNativeApi , readNativeApi } from "../../nativeApi" ;
5056import { useStore } from "../../store" ;
5157import { formatRelativeTime , formatRelativeTimeLabel } from "../../timestampFormat" ;
@@ -60,6 +66,51 @@ import { toastManager } from "../ui/toast";
6066import { Tooltip , TooltipPopup , TooltipTrigger } from "../ui/tooltip" ;
6167import { ProjectFavicon } from "../ProjectFavicon" ;
6268
69+ function ProviderUsageRows ( {
70+ buckets,
71+ timestampFormat,
72+ } : {
73+ buckets : ReadonlyArray < ServerProviderUsageBucket > ;
74+ timestampFormat : "locale" | "12-hour" | "24-hour" ;
75+ } ) {
76+ if ( buckets . length === 0 ) {
77+ return null ;
78+ }
79+
80+ return (
81+ < div className = "px-4 pb-4 sm:px-5" >
82+ < div className = "space-y-3" >
83+ { buckets . map ( ( bucket ) => (
84+ < div key = { bucket . id } className = "space-y-1.5" >
85+ < div className = "flex items-center justify-between gap-3" >
86+ < span className = "text-xs font-medium text-foreground" > { bucket . label } </ span >
87+ < span className = "text-[11px] text-muted-foreground" >
88+ { formatUsageRemainingPercent ( bucket ) }
89+ </ span >
90+ </ div >
91+ < div
92+ className = "h-1.5 overflow-hidden rounded-full bg-muted"
93+ role = "progressbar"
94+ aria-label = { bucket . label }
95+ aria-valuemin = { 0 }
96+ aria-valuemax = { 100 }
97+ aria-valuenow = { bucket . usedPercent }
98+ >
99+ < div
100+ className = "h-full rounded-full bg-foreground/80 transition-[width]"
101+ style = { { width : `${ bucket . usedPercent } %` } }
102+ />
103+ </ div >
104+ < div className = "text-[11px] text-muted-foreground" >
105+ { formatUsageResetAt ( bucket . resetsAt , timestampFormat ) }
106+ </ div >
107+ </ div >
108+ ) ) }
109+ </ div >
110+ </ div >
111+ ) ;
112+ }
113+
63114const THEME_OPTIONS = [
64115 {
65116 value : "system" ,
@@ -1064,6 +1115,7 @@ export function GeneralSettingsPanel() {
10641115 const customModelError = customModelErrorByProvider [ providerCard . provider ] ?? null ;
10651116 const providerDisplayName =
10661117 PROVIDER_DISPLAY_NAMES [ providerCard . provider ] ?? providerCard . title ;
1118+ const usageBuckets = getProviderUsageBuckets ( providerCard . liveProvider ) ;
10671119
10681120 return (
10691121 < div key = { providerCard . provider } className = "border-t border-border first:border-t-0" >
@@ -1154,6 +1206,11 @@ export function GeneralSettingsPanel() {
11541206 </ div >
11551207 </ div >
11561208
1209+ < ProviderUsageRows
1210+ buckets = { usageBuckets }
1211+ timestampFormat = { settings . timestampFormat }
1212+ />
1213+
11571214 < Collapsible
11581215 open = { openProviderDetails [ providerCard . provider ] }
11591216 onOpenChange = { ( open ) =>
0 commit comments