11import { Dialog } from "@opencode-ai/ui/dialog"
22import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
33import type { IconName } from "@opencode-ai/ui/icons/provider"
4+ import { Icon } from "@opencode-ai/ui/icon"
45import { Spinner } from "@opencode-ai/ui/spinner"
56import { createResource , For , Show , createMemo , createSignal } from "solid-js"
67import { useGlobalSDK } from "@/context/global-sdk"
8+ import { usePlatform } from "@/context/platform"
79
810interface AccountUsage {
911 id : string
@@ -73,7 +75,12 @@ function UsageBarPercent(props: { label: string; utilization: number; resetsAt?:
7375
7476export function DialogAuthUsage ( ) {
7577 const globalSDK = useGlobalSDK ( )
78+ const platform = usePlatform ( )
7679 const [ switching , setSwitching ] = createSignal < string | null > ( null )
80+ const [ deleting , setDeleting ] = createSignal < string | null > ( null )
81+ const [ confirmDelete , setConfirmDelete ] = createSignal < string | null > ( null )
82+
83+ const doFetch = platform . fetch ?? fetch
7784
7885 const [ usage , { refetch, mutate } ] = createResource ( async ( ) => {
7986 const result = await globalSDK . client . auth . usage ( { } )
@@ -89,13 +96,13 @@ export function DialogAuthUsage() {
8996 const switchAccount = async ( providerID : string , recordID : string ) => {
9097 setSwitching ( recordID )
9198 try {
92- const result = await fetch ( `${ globalSDK . url } /auth/active` , {
99+ const result = await doFetch ( `${ globalSDK . url } /provider /auth/active` , {
93100 method : "POST" ,
94101 headers : { "Content-Type" : "application/json" } ,
95102 body : JSON . stringify ( { providerID, recordID } ) ,
96103 } ) . then ( ( r ) => r . json ( ) )
97104
98- if ( result . success ) {
105+ if ( result ) {
99106 const current = usage ( )
100107 if ( current && current [ providerID ] ) {
101108 mutate ( {
@@ -106,7 +113,6 @@ export function DialogAuthUsage() {
106113 ...acc ,
107114 isActive : acc . id === recordID ,
108115 } ) ) ,
109- anthropicUsage : result . anthropicUsage ?? current [ providerID ] . anthropicUsage ,
110116 } ,
111117 } )
112118 }
@@ -116,6 +122,45 @@ export function DialogAuthUsage() {
116122 }
117123 }
118124
125+ const deleteAccount = async ( providerID : string , recordID : string ) => {
126+ setDeleting ( recordID )
127+ try {
128+ const result = await doFetch ( `${ globalSDK . url } /provider/auth/account` , {
129+ method : "DELETE" ,
130+ headers : { "Content-Type" : "application/json" } ,
131+ body : JSON . stringify ( { providerID, recordID } ) ,
132+ } ) . then ( ( r ) => r . json ( ) )
133+
134+ if ( result . success ) {
135+ if ( result . remaining === 0 ) {
136+ // Provider was disconnected, refresh to remove from list
137+ await refetch ( )
138+ } else {
139+ // Update local state
140+ const current = usage ( )
141+ if ( current && current [ providerID ] ) {
142+ const remaining = current [ providerID ] . accounts . filter ( ( acc ) => acc . id !== recordID )
143+ // If deleted account was active, mark first remaining as active
144+ const hadActive = current [ providerID ] . accounts . find ( ( acc ) => acc . id === recordID ) ?. isActive
145+ if ( hadActive && remaining . length > 0 ) {
146+ remaining [ 0 ] . isActive = true
147+ }
148+ mutate ( {
149+ ...current ,
150+ [ providerID ] : {
151+ ...current [ providerID ] ,
152+ accounts : remaining ,
153+ } ,
154+ } )
155+ }
156+ }
157+ }
158+ } finally {
159+ setDeleting ( null )
160+ setConfirmDelete ( null )
161+ }
162+ }
163+
119164 return (
120165 < Dialog title = "Rate Limits & Usage" >
121166 < div class = "flex flex-col gap-6 px-4 pb-4 min-w-[420px]" >
@@ -198,51 +243,96 @@ export function DialogAuthUsage() {
198243 return secs > 60 ? `${ Math . ceil ( secs / 60 ) } m` : `${ secs } s`
199244 }
200245 const isSwitching = ( ) => switching ( ) === account . id
246+ const isDeleting = ( ) => deleting ( ) === account . id
247+ const isConfirming = ( ) => confirmDelete ( ) === account . id
201248 const canSwitch = ( ) => info . accounts . length > 1 && ! account . isActive && ! isSwitching ( )
202249
203250 return (
204- < button
205- type = "button"
206- disabled = { ! canSwitch ( ) && ! account . isActive }
207- onClick = { ( ) => canSwitch ( ) && switchAccount ( providerID , account . id ) }
208- class = "flex flex-col gap-2 p-3 rounded-lg text-left transition-all"
251+ < div
252+ class = "flex items-center gap-2 p-3 rounded-lg transition-all"
209253 classList = { {
210- "bg-fill-ghost-base hover:bg-fill-ghost-strong cursor-pointer" : canSwitch ( ) ,
211- "bg-fill-ghost-base opacity-60" : ! canSwitch ( ) && ! account . isActive ,
254+ "bg-fill-ghost-base" : ! account . isActive ,
212255 "bg-fill-success-ghost border border-fill-success-base" : account . isActive ,
213256 } }
214257 >
215- < div class = "flex justify-between items-center w-full" >
216- < div class = "flex items-center gap-2" >
217- < Show when = { isSwitching ( ) } >
218- < Spinner class = "size-3" />
219- </ Show >
220- < span class = "text-13-medium text-text-base" >
221- Account { index ( ) + 1 }
222- < Show when = { account . label && account . label !== "default" } >
223- < span class = "text-text-muted" > ({ account . label } )</ span >
258+ < button
259+ type = "button"
260+ disabled = { ! canSwitch ( ) && ! account . isActive }
261+ onClick = { ( ) => canSwitch ( ) && switchAccount ( providerID , account . id ) }
262+ class = "flex-1 flex flex-col gap-2 text-left transition-all"
263+ classList = { {
264+ "hover:opacity-80 cursor-pointer" : canSwitch ( ) ,
265+ "opacity-60" : ! canSwitch ( ) && ! account . isActive ,
266+ } }
267+ >
268+ < div class = "flex justify-between items-center w-full" >
269+ < div class = "flex items-center gap-2" >
270+ < Show when = { isSwitching ( ) } >
271+ < Spinner class = "size-3" />
224272 </ Show >
225- </ span >
226- < Show when = { account . isActive } >
227- < span class = "text-10-medium text-fill-success-base bg-fill-success-ghost px-1.5 py-0.5 rounded" >
228- Active
229- </ span >
230- </ Show >
231- < Show when = { isInCooldown ( ) } >
232- < span class = "text-10-medium text-fill-danger-base bg-fill-danger-ghost px-1.5 py-0.5 rounded" >
233- Cooldown { cooldownRemaining ( ) }
273+ < span class = "text-13-medium text-text-base" >
274+ Account { index ( ) + 1 }
275+ < Show when = { account . label && account . label !== "default" } >
276+ < span class = "text-text-muted" > ({ account . label } )</ span >
277+ </ Show >
234278 </ span >
235- </ Show >
279+ < Show when = { account . isActive } >
280+ < span class = "text-10-medium text-fill-success-base bg-fill-success-ghost px-1.5 py-0.5 rounded" >
281+ Active
282+ </ span >
283+ </ Show >
284+ < Show when = { isInCooldown ( ) } >
285+ < span class = "text-10-medium text-fill-danger-base bg-fill-danger-ghost px-1.5 py-0.5 rounded" >
286+ Cooldown { cooldownRemaining ( ) }
287+ </ span >
288+ </ Show >
289+ </ div >
290+ < span class = "text-11-regular text-text-muted" > { account . health . successCount } requests</ span >
236291 </ div >
237- < span class = "text-11-regular text-text-muted" > { account . health . successCount } requests</ span >
238- </ div >
239292
240- < Show when = { account . health . failureCount > 0 } >
241- < div class = "text-11-regular text-fill-danger-base" >
242- { account . health . failureCount } failed requests
293+ < Show when = { account . health . failureCount > 0 } >
294+ < div class = "text-11-regular text-fill-danger-base" >
295+ { account . health . failureCount } failed requests
296+ </ div >
297+ </ Show >
298+ </ button >
299+
300+ { /* Delete button */ }
301+ < Show when = { isConfirming ( ) } >
302+ < div class = "flex items-center gap-1" >
303+ < button
304+ type = "button"
305+ onClick = { ( ) => deleteAccount ( providerID , account . id ) }
306+ disabled = { isDeleting ( ) }
307+ class = "px-2 py-1 rounded text-10-medium bg-fill-danger-base text-white hover:bg-fill-danger-strong transition-colors disabled:opacity-50"
308+ >
309+ < Show when = { isDeleting ( ) } fallback = "Confirm" >
310+ < Spinner class = "size-3" />
311+ </ Show >
312+ </ button >
313+ < button
314+ type = "button"
315+ onClick = { ( ) => setConfirmDelete ( null ) }
316+ class = "px-2 py-1 rounded text-10-medium bg-fill-ghost-strong text-text-base hover:bg-fill-ghost-base transition-colors"
317+ >
318+ Cancel
319+ </ button >
243320 </ div >
244321 </ Show >
245- </ button >
322+ < Show when = { ! isConfirming ( ) } >
323+ < button
324+ type = "button"
325+ onClick = { ( e ) => {
326+ e . stopPropagation ( )
327+ setConfirmDelete ( account . id )
328+ } }
329+ class = "p-1 rounded hover:bg-fill-danger-ghost text-icon-muted hover:text-fill-danger-base transition-colors"
330+ title = "Remove account"
331+ >
332+ < Icon name = "close" class = "size-4" />
333+ </ button >
334+ </ Show >
335+ </ div >
246336 )
247337 } }
248338 </ For >
0 commit comments