diff options
| author | Adam Malczewski <[email protected]> | 2026-05-20 23:15:43 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-20 23:15:43 +0900 |
| commit | 29e4c2db4555bb764f458971f0c591c092e30ed6 (patch) | |
| tree | 15639bbe68a1bd152bed9afacf31c06445e2d6df | |
| parent | ba68da660c9a02b90ff2134152abe650f004867e (diff) | |
| download | dispatch-29e4c2db4555bb764f458971f0c591c092e30ed6.tar.gz dispatch-29e4c2db4555bb764f458971f0c591c092e30ed6.zip | |
fix: per-key independent loading, better spacing between Claude accounts
- Each key now loads independently with its own spinner
- Claude account sub-cards separated by border + spacing
- Fixed Svelte 5 if/else chain and class: directive issues
| -rw-r--r-- | packages/frontend/src/lib/components/KeyUsage.svelte | 99 |
1 files changed, 55 insertions, 44 deletions
diff --git a/packages/frontend/src/lib/components/KeyUsage.svelte b/packages/frontend/src/lib/components/KeyUsage.svelte index 7f0453e..3587ff8 100644 --- a/packages/frontend/src/lib/components/KeyUsage.svelte +++ b/packages/frontend/src/lib/components/KeyUsage.svelte @@ -14,52 +14,61 @@ provider: string; data: KeyUsageData | null; error: string | null; + loading: boolean; } let entries = $state<KeyUsageEntry[]>([]); - let loading = $state(true); - async function fetchAll() { - loading = true; - const results: KeyUsageEntry[] = []; - - for (const key of keys) { - try { - const res = await fetch( - `${apiBase}/models/key-usage?keyId=${encodeURIComponent(key.id)}`, - ); - if (!res.ok) { - const data = await res.json().catch(() => ({})); - results.push({ - keyId: key.id, - provider: key.provider, - data: null, - error: data.error ?? `HTTP ${res.status}`, - }); - } else { - results.push({ - keyId: key.id, - provider: key.provider, - data: await res.json(), - error: null, - }); - } - } catch (e) { - results.push({ - keyId: key.id, - provider: key.provider, + async function fetchOne(key: KeyInfo) { + try { + const res = await fetch( + `${apiBase}/models/key-usage?keyId=${encodeURIComponent(key.id)}`, + ); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + updateEntry(key.id, { data: null, - error: e instanceof Error ? e.message : "Failed to fetch", + error: data.error ?? `HTTP ${res.status}`, + loading: false, + }); + } else { + updateEntry(key.id, { + data: await res.json(), + error: null, + loading: false, }); } + } catch (e) { + updateEntry(key.id, { + data: null, + error: e instanceof Error ? e.message : "Failed to fetch", + loading: false, + }); } + } - entries = results; - loading = false; + function updateEntry( + keyId: string, + patch: { data?: KeyUsageData | null; error?: string | null; loading?: boolean }, + ) { + entries = entries.map((e) => + e.keyId === keyId ? { ...e, ...patch } : e, + ); } $effect(() => { - fetchAll(); + // Initialize all entries as loading + entries = keys.map((k) => ({ + keyId: k.id, + provider: k.provider, + data: null, + error: null, + loading: true, + })); + // Fire all fetches in parallel + for (const key of keys) { + fetchOne(key); + } }); function progressClass(utilization: number): string { @@ -80,23 +89,22 @@ <div class="flex flex-col gap-3"> {#if keys.length === 0} <p class="text-xs text-base-content/50">No keys available.</p> - {:else if loading} - <div class="flex items-center gap-2 py-2"> - <span class="loading loading-spinner loading-sm"></span> - <span class="text-xs text-base-content/50">Loading usage data...</span> - </div> {:else} <div class="flex flex-col gap-3 max-h-96 overflow-y-auto"> {#each entries as entry (entry.keyId)} - <div - class="bg-base-200 rounded-lg p-2" - > + <div class="bg-base-200 rounded-lg p-2"> <div class="flex items-center gap-1.5 mb-1.5"> <span class="text-xs font-semibold">{entry.keyId}</span> <span class="badge badge-xs badge-ghost">{entry.provider}</span> </div> - {#if entry.error} + {#if entry.loading} + <div class="flex items-center gap-1.5 py-1"> + <span class="loading loading-spinner loading-xs"></span> + <span class="text-xs text-base-content/50">Loading...</span> + </div> + + {:else if entry.error} <div role="alert" class="text-xs text-error/80">{entry.error}</div> {:else if !entry.data} @@ -105,7 +113,10 @@ {:else if entry.data.provider === "anthropic"} <!-- Render each Claude account --> {#if entry.data.accounts} - {#each entry.data.accounts as acct (acct.source)} + {#each entry.data.accounts as acct, idx (acct.source)} + {#if idx > 0} + <div class="border-t border-base-300 my-1.5"></div> + {/if} <div class="flex flex-col gap-1 pl-1"> <div class="flex items-center gap-1"> <span class="text-xs font-medium">{acct.label}</span> |
