diff options
| author | Adam Malczewski <[email protected]> | 2026-05-23 19:07:21 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-23 19:07:21 +0900 |
| commit | 997b00034435440d412f955e05e53f09bae83f9e (patch) | |
| tree | 22d1f530e9e1a97bd19286456d2f06793bd030f7 /packages/frontend/src/lib | |
| parent | ff00dec6ae2971bee38c74cb00fe034de9a839ee (diff) | |
| download | dispatch-997b00034435440d412f955e05e53f09bae83f9e.tar.gz dispatch-997b00034435440d412f955e05e53f09bae83f9e.zip | |
feat: google gemini provider, adaptive thinking for opus 4.7, model search filter
- Added Google (Gemini) as a provider: add-key UI, env var resolution via resolveApiKey, usage tracking via native models endpoint + gemini.google.com cookie scraping
- @ai-sdk/anthropic upgraded to v3 (adaptive thinking support) with LanguageModelV1 cast for ai v4 compat
- Claude Opus 4.7 uses adaptive thinking (type: adaptive); all other models keep explicit budget tokens
- Model selector modal: search filter with space matching dash/underscore
- Copy button: all tool results truncated at 300 chars
- Sidebar layout fix: Claude Reset panel removed from flex-1 fill to prevent overlap
Diffstat (limited to 'packages/frontend/src/lib')
| -rw-r--r-- | packages/frontend/src/lib/components/KeyUsage.svelte | 53 | ||||
| -rw-r--r-- | packages/frontend/src/lib/components/ModelSelector.svelte | 46 | ||||
| -rw-r--r-- | packages/frontend/src/lib/types.ts | 21 |
3 files changed, 108 insertions, 12 deletions
diff --git a/packages/frontend/src/lib/components/KeyUsage.svelte b/packages/frontend/src/lib/components/KeyUsage.svelte index 49c3cf6..96a4b08 100644 --- a/packages/frontend/src/lib/components/KeyUsage.svelte +++ b/packages/frontend/src/lib/components/KeyUsage.svelte @@ -404,6 +404,59 @@ function hasBucketData(bucket: UsageBucket | undefined): boolean { <span class="text-xs text-base-content/40">Resets: {formatDate(entry.data.resetAt)}</span> {/if} </div> + {:else if entry.data.provider === "google"} + <div class="flex flex-col gap-0.5 pl-1"> + <!-- Cookie-scraped usage from gemini.google.com --> + {#if entry.data.currentUsage} + {@const u = entry.data.currentUsage} + <div class="flex flex-col gap-0.5"> + <div class="flex items-center justify-between"> + <span class="text-xs text-base-content/50">Current</span> + <span class="text-xs font-mono">{u.percentUsed}%</span> + </div> + <progress class="progress w-full h-2 {progressClass(u.percentUsed / 100)}" value={u.percentUsed} max="100"></progress> + {#if u.resetsAt} + <span class="text-xs text-base-content/40">Resets: {u.resetsAt}</span> + {/if} + </div> + {/if} + {#if entry.data.weeklyUsage} + {@const w = entry.data.weeklyUsage} + <div class="flex flex-col gap-0.5"> + <div class="flex items-center justify-between"> + <span class="text-xs text-base-content/50">Weekly</span> + <span class="text-xs font-mono">{w.percentUsed}%</span> + </div> + <progress class="progress w-full h-2 {progressClass(w.percentUsed / 100)}" value={w.percentUsed} max="100"></progress> + {#if w.resetsAt} + <span class="text-xs text-base-content/40">Resets: {w.resetsAt}</span> + {/if} + </div> + {/if} + <!-- API key rate limits --> + {#if !entry.data.currentUsage && entry.data.models && entry.data.models.length > 0} + {@const m = entry.data.models[0]} + <div class="flex items-center justify-between"> + <span class="text-xs text-base-content/50">Models</span> + <span class="text-xs font-mono">{entry.data.models.length} available</span> + </div> + {#if m.rpm > 0} + <div class="flex items-center justify-between"> + <span class="text-xs text-base-content/50">RPM</span> + <span class="text-xs font-mono">{m.rpm}</span> + </div> + {/if} + {#if m.requestsPerDay > 0} + <div class="flex items-center justify-between"> + <span class="text-xs text-base-content/50">RPD</span> + <span class="text-xs font-mono">{m.requestsPerDay.toLocaleString()}</span> + </div> + {/if} + {#if !entry.data.currentUsage} + <p class="text-xs text-base-content/40 mt-0.5">Set GEMINI_COOKIE (__Secure-1PSID) for usage %</p> + {/if} + {/if} + </div> {/if} {/if} </div> diff --git a/packages/frontend/src/lib/components/ModelSelector.svelte b/packages/frontend/src/lib/components/ModelSelector.svelte index bbbf036..235cf3b 100644 --- a/packages/frontend/src/lib/components/ModelSelector.svelte +++ b/packages/frontend/src/lib/components/ModelSelector.svelte @@ -62,6 +62,7 @@ const modelCache = new Map<string, string[]>(); let loadingModels = $state(false); let modelError = $state<string | null>(null); let sliderDragging = $state<number | null>(null); + let modelSearch = $state(""); let cwdExists = $state<boolean | null>(null); let cwdCheckTimer: ReturnType<typeof setTimeout> | null = null; @@ -126,6 +127,7 @@ const modelCache = new Map<string, string[]>(); if (!keyId) return; showModelModal = true; modelError = null; + modelSearch = ""; // Check session cache if (modelCache.has(keyId)) { @@ -393,17 +395,39 @@ const modelCache = new Map<string, string[]>(); <span>{modelError}</span> </div> {:else} - <div class="mt-4 flex flex-col gap-1 max-h-96 overflow-y-auto"> - {#each availableModels as model} - <button - class="btn {model === activeModelId - ? 'btn-primary' - : 'btn-ghost'} justify-start font-mono text-base" - onclick={() => selectModel(model)} - > - {model} - </button> - {/each} + {@const search = modelSearch.toLowerCase().trim()} + {@const searchRegex = search + ? new RegExp( + search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/ /g, "[ _-]"), + ) + : null} + {@const filteredModels = searchRegex + ? availableModels.filter((m) => searchRegex.test(m.toLowerCase())) + : availableModels} + <div class="mt-4 flex flex-col gap-1"> + <input + type="text" + class="input input-bordered input-sm w-full" + placeholder="Filter models..." + bind:value={modelSearch} + /> + <div class="mt-2 max-h-96 overflow-y-auto flex flex-col gap-1"> + {#each filteredModels as model} + <button + class="btn {model === activeModelId + ? 'btn-primary' + : 'btn-ghost'} justify-start font-mono text-base" + onclick={() => selectModel(model)} + > + {model} + </button> + {/each} + {#if filteredModels.length === 0} + <p class="text-xs text-base-content/50 py-2 text-center"> + {search ? 'No models match your search.' : 'No models available.'} + </p> + {/if} + </div> </div> {/if} <div class="modal-action"> diff --git a/packages/frontend/src/lib/types.ts b/packages/frontend/src/lib/types.ts index 532c948..2e6b219 100644 --- a/packages/frontend/src/lib/types.ts +++ b/packages/frontend/src/lib/types.ts @@ -163,4 +163,23 @@ export interface CopilotUsageData { plan?: string; } -export type KeyUsageData = ClaudeUsageData | OpencodeUsageData | CopilotUsageData; +export interface GoogleUsageData { + provider: "google"; + models?: Array<{ + name: string; + inputTokenLimit: number; + outputTokenLimit: number; + rpm: number; + requestsPerDay: number; + }>; + currentUsage?: { + percentUsed: number; + resetsAt?: string; + }; + weeklyUsage?: { + percentUsed: number; + resetsAt?: string; + }; +} + +export type KeyUsageData = ClaudeUsageData | OpencodeUsageData | CopilotUsageData | GoogleUsageData; |
