@@ -15,8 +15,63 @@ import {
1515 Spinner ,
1616 Text ,
1717} from "@radix-ui/themes" ;
18+ import { trpcClient } from "@renderer/trpc/client" ;
19+ import { logger } from "@utils/logger" ;
1820import { getPostHogUrl } from "@utils/urls" ;
19- import { useState } from "react" ;
21+ import { useEffect , useState } from "react" ;
22+
23+ const log = logger . scope ( "plan-usage" ) ;
24+
25+ interface UsageBucket {
26+ used_usd : number ;
27+ limit_usd : number ;
28+ remaining_usd : number ;
29+ resets_in_seconds : number ;
30+ exceeded : boolean ;
31+ }
32+
33+ interface UsageData {
34+ sustained : UsageBucket ;
35+ burst : UsageBucket ;
36+ is_rate_limited : boolean ;
37+ }
38+
39+ function formatUsd ( amount : number ) : string {
40+ return `$${ amount . toFixed ( 2 ) } ` ;
41+ }
42+
43+ function formatResetTime ( seconds : number ) : string {
44+ const days = Math . ceil ( seconds / 86400 ) ;
45+ if ( days === 1 ) return "1 day" ;
46+ return `${ days } days` ;
47+ }
48+
49+ function useUsage ( ) {
50+ const [ usage , setUsage ] = useState < UsageData | null > ( null ) ;
51+ const [ isLoading , setIsLoading ] = useState ( true ) ;
52+
53+ useEffect ( ( ) => {
54+ let cancelled = false ;
55+
56+ trpcClient . llmGateway . usage
57+ . query ( )
58+ . then ( ( data ) => {
59+ if ( ! cancelled ) setUsage ( data ) ;
60+ } )
61+ . catch ( ( error ) => {
62+ log . warn ( "Failed to fetch usage" , error ) ;
63+ } )
64+ . finally ( ( ) => {
65+ if ( ! cancelled ) setIsLoading ( false ) ;
66+ } ) ;
67+
68+ return ( ) => {
69+ cancelled = true ;
70+ } ;
71+ } , [ ] ) ;
72+
73+ return { usage, isLoading } ;
74+ }
2075
2176export function PlanUsageSettings ( ) {
2277 const {
@@ -31,6 +86,7 @@ export function PlanUsageSettings() {
3186 const { upgradeToPro, cancelSeat, reactivateSeat, clearError } =
3287 useSeatStore ( ) ;
3388 const [ showUpgradeDialog , setShowUpgradeDialog ] = useState ( false ) ;
89+ const { usage, isLoading : usageLoading } = useUsage ( ) ;
3490
3591 const formattedActiveUntil = activeUntil
3692 ? activeUntil . toLocaleDateString ( undefined , {
@@ -181,59 +237,30 @@ export function PlanUsageSettings() {
181237 < Text size = "2" weight = "medium" style = { { color : "var(--gray-9)" } } >
182238 Usage
183239 </ Text >
184- { isPro ? (
240+ { usageLoading ? (
185241 < Flex
186- direction = "column "
187- gap = "3 "
242+ align = "center "
243+ justify = "center "
188244 p = "4"
189245 style = { {
190- border : "1px solid var(--accent-7 )" ,
246+ border : "1px solid var(--gray-5 )" ,
191247 borderRadius : "var(--radius-3)" ,
192248 } }
193249 >
194- < Flex align = "center" justify = "between" >
195- < Text size = "2" weight = "medium" >
196- Token usage
197- </ Text >
198- < Text
199- size = "2"
200- weight = "medium"
201- style = { { color : "var(--accent-9)" } }
202- >
203- Unlimited
204- </ Text >
205- </ Flex >
206- < div
207- style = { {
208- position : "relative" ,
209- height : 8 ,
210- borderRadius : 4 ,
211- overflow : "hidden" ,
212- background : "var(--gray-4)" ,
213- } }
214- >
215- < div
216- style = { {
217- position : "absolute" ,
218- inset : 0 ,
219- borderRadius : 4 ,
220- background :
221- "linear-gradient(90deg, var(--amber-9), var(--orange-9), var(--amber-8), var(--orange-10), var(--amber-9))" ,
222- backgroundSize : "300% 100%" ,
223- animation : "lava-flow 4s linear infinite" ,
224- boxShadow : "0 0 10px var(--orange-8)" ,
225- } }
226- />
227- < style > { `
228- @keyframes lava-flow {
229- 0% { background-position: 300% 50%; }
230- 100% { background-position: 0% 50%; }
231- }
232- ` } </ style >
233- </ div >
234- < Text size = "1" style = { { color : "var(--gray-9)" } } >
235- Unlimited tokens included with Pro (go crazy)
236- </ Text >
250+ < Spinner size = "2" />
251+ </ Flex >
252+ ) : usage ? (
253+ < Flex direction = "column" gap = "3" >
254+ < UsageMeter
255+ label = "Sustained"
256+ bucket = { usage . sustained }
257+ color = { usage . sustained . exceeded ? "red" : undefined }
258+ />
259+ < UsageMeter
260+ label = "Burst"
261+ bucket = { usage . burst }
262+ color = { usage . burst . exceeded ? "red" : undefined }
263+ />
237264 </ Flex >
238265 ) : (
239266 < Flex
@@ -245,17 +272,8 @@ export function PlanUsageSettings() {
245272 borderRadius : "var(--radius-3)" ,
246273 } }
247274 >
248- < Flex align = "center" justify = "between" >
249- < Text size = "2" weight = "medium" >
250- Token usage
251- </ Text >
252- < Text size = "2" weight = "medium" >
253- 0%
254- </ Text >
255- </ Flex >
256- < Progress value = { 0 } size = "2" />
257- < Text size = "1" style = { { color : "var(--gray-9)" } } >
258- 0 tokens used this period
275+ < Text size = "2" color = "gray" >
276+ Unable to load usage data
259277 </ Text >
260278 </ Flex >
261279 ) }
@@ -349,6 +367,52 @@ export function PlanUsageSettings() {
349367 ) ;
350368}
351369
370+ interface UsageMeterProps {
371+ label : string ;
372+ bucket : UsageBucket ;
373+ color ?: "red" ;
374+ }
375+
376+ function UsageMeter ( { label, bucket, color } : UsageMeterProps ) {
377+ const percentage =
378+ bucket . limit_usd > 0
379+ ? Math . min ( 100 , ( bucket . used_usd / bucket . limit_usd ) * 100 )
380+ : 0 ;
381+
382+ const borderColor = color === "red" ? "var(--red-7)" : "var(--gray-5)" ;
383+
384+ return (
385+ < Flex
386+ direction = "column"
387+ gap = "3"
388+ p = "4"
389+ style = { {
390+ border : `1px solid ${ borderColor } ` ,
391+ borderRadius : "var(--radius-3)" ,
392+ } }
393+ >
394+ < Flex align = "center" justify = "between" >
395+ < Text size = "2" weight = "medium" >
396+ { label }
397+ </ Text >
398+ < Text size = "2" weight = "medium" >
399+ { formatUsd ( bucket . used_usd ) } / { formatUsd ( bucket . limit_usd ) }
400+ </ Text >
401+ </ Flex >
402+ < Progress
403+ value = { percentage }
404+ size = "2"
405+ color = { color === "red" ? "red" : undefined }
406+ />
407+ < Text size = "1" style = { { color : "var(--gray-9)" } } >
408+ { bucket . exceeded
409+ ? "Limit exceeded"
410+ : `${ formatUsd ( bucket . remaining_usd ) } remaining \u00b7 resets in ${ formatResetTime ( bucket . resets_in_seconds ) } ` }
411+ </ Text >
412+ </ Flex >
413+ ) ;
414+ }
415+
352416interface PlanCardProps {
353417 name : string ;
354418 price : string ;
0 commit comments