summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-21 14:31:40 +0900
committerAdam Malczewski <[email protected]>2026-05-21 14:31:40 +0900
commitc957e89e3ec46f1db64dcb1416f5ade7fb6e617e (patch)
treec6962a749ff906c6ac9b6c8b2ec89e47dd96d15a
parent42a2e1de886a3fa88e28848d5e2c0c632c015dc4 (diff)
downloaddispatch-c957e89e3ec46f1db64dcb1416f5ade7fb6e617e.tar.gz
dispatch-c957e89e3ec46f1db64dcb1416f5ade7fb6e617e.zip
feat: usage cache with spinner refresh — shows cached data immediately, refreshes in background
-rw-r--r--packages/frontend/src/lib/components/KeyUsage.svelte56
1 files changed, 32 insertions, 24 deletions
diff --git a/packages/frontend/src/lib/components/KeyUsage.svelte b/packages/frontend/src/lib/components/KeyUsage.svelte
index 4b06309..5e86f36 100644
--- a/packages/frontend/src/lib/components/KeyUsage.svelte
+++ b/packages/frontend/src/lib/components/KeyUsage.svelte
@@ -17,6 +17,9 @@
loading: boolean;
}
+ // Module-level cache survives remounts during the same page session
+ const usageCache = new Map<string, KeyUsageData>();
+
let entries = $state<KeyUsageEntry[]>([]);
async function fetchOne(key: KeyInfo) {
@@ -27,20 +30,20 @@
if (!res.ok) {
const data = await res.json().catch(() => ({}));
updateEntry(key.id, {
- data: null,
error: data.error ?? `HTTP ${res.status}`,
loading: false,
});
} else {
+ const fresh = await res.json() as KeyUsageData;
+ usageCache.set(key.id, fresh);
updateEntry(key.id, {
- data: await res.json(),
+ data: fresh,
error: null,
loading: false,
});
}
} catch (e) {
updateEntry(key.id, {
- data: null,
error: e instanceof Error ? e.message : "Failed to fetch",
loading: false,
});
@@ -57,14 +60,17 @@
}
$effect(() => {
- // Initialize all entries as loading
- entries = keys.map((k) => ({
- keyId: k.id,
- provider: k.provider,
- data: null,
- error: null,
- loading: true,
- }));
+ // Show cached data immediately if available, then refresh in background
+ entries = keys.map((k) => {
+ const cached = usageCache.get(k.id);
+ return {
+ keyId: k.id,
+ provider: k.provider,
+ data: cached ?? null,
+ error: null,
+ loading: true,
+ };
+ });
// Fire all fetches in parallel
for (const key of keys) {
fetchOne(key);
@@ -165,23 +171,21 @@
{:else}
<div class="flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto">
<!-- Claude (all accounts merged under one card) -->
- {#if claudeLoading}
- <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">Claude</span>
- <span class="badge badge-xs badge-ghost">anthropic</span>
- </div>
- <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>
- </div>
- {:else if claudeAccounts.length > 0}
+ {#if claudeAccounts.length > 0 || claudeLoading}
<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">Claude</span>
<span class="badge badge-xs badge-ghost">anthropic</span>
+ {#if claudeLoading}
+ <span class="loading loading-spinner loading-xs"></span>
+ {/if}
</div>
+
+ {#if claudeAccounts.length === 0 && claudeLoading}
+ <div class="flex items-center gap-1.5 py-1">
+ <span class="text-xs text-base-content/50">Loading...</span>
+ </div>
+ {:else}
{#each claudeAccounts as acct, idx (acct.source)}
{#if idx > 0}
<div class="border-t border-base-300 my-1.5"></div>
@@ -228,8 +232,9 @@
{/if}
</div>
{/each}
- </div>
{/if}
+ </div>
+ {/if}
<!-- Non-Claude keys -->
{#each nonClaudeEntries as entry (entry.keyId)}
@@ -237,6 +242,9 @@
<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>
+ {#if entry.loading}
+ <span class="loading loading-spinner loading-xs"></span>
+ {/if}
</div>
{#if entry.loading}