From 288b21cec98421fda57028a0c8c9d835cfbb14b0 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Fri, 22 May 2026 17:07:31 +0900 Subject: feat: add/remove keys from UI, backend URL setting, user service, Docker fix - Add POST /models/add-key and POST /models/remove-key API endpoints - Add 'Add New Key' modal (page-level) with provider selection - Add remove button per key in Model Status view - Add configurable backend URL setting in Settings panel with localStorage persistence - Convert systemd service from system to user service (systemctl --user) - Fix Docker entrypoint to chown all nested node_modules dirs - Update dispatch.toml credential paths to use -pro/-max naming - Make API port configurable via PORT env var (default 3000, prod 18390) --- .../frontend/src/lib/components/ModelStatus.svelte | 49 +++++++++++++++++++++- .../src/lib/components/SettingsPanel.svelte | 46 ++++++++++++++++++++ .../src/lib/components/SidebarPanel.svelte | 4 +- packages/frontend/src/lib/config.ts | 36 ++++++++++++++-- 4 files changed, 129 insertions(+), 6 deletions(-) (limited to 'packages/frontend/src/lib') diff --git a/packages/frontend/src/lib/components/ModelStatus.svelte b/packages/frontend/src/lib/components/ModelStatus.svelte index 1270fcc..d6ff0f5 100644 --- a/packages/frontend/src/lib/components/ModelStatus.svelte +++ b/packages/frontend/src/lib/components/ModelStatus.svelte @@ -20,10 +20,12 @@ const { keys = [], currentModel, apiBase = "", + onAddKey = () => {}, }: { keys?: KeyInfo[]; currentModel?: string; apiBase?: string; + onAddKey?: () => void; } = $props(); const activeKeys = $derived(keys.filter((k) => k.status === "active").length); @@ -44,6 +46,9 @@ let showKeyModal = $state(null); // keyId or null let keyModalValue = $state(""); let keyModalError = $state(null); let keyModalSaving = $state(false); +let removingKey = $state(null); + + async function loadCredentialStatus(): Promise { try { @@ -129,6 +134,27 @@ async function saveApiKey(): Promise { } } +async function removeKey(keyId: string): Promise { + if (!confirm(`Remove key "${keyId}" from config?`)) return; + removingKey = keyId; + try { + const res = await fetch(`${apiBase}/models/remove-key`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: keyId }), + }); + const data = (await res.json()) as { success?: boolean; error?: string }; + if (res.ok && data.success) { + await new Promise((r) => setTimeout(r, 500)); + window.location.reload(); + } + } catch { + // ignore + } finally { + removingKey = null; + } +} + $effect(() => { void loadCredentialStatus(); void loadApiKeyStatus(); @@ -209,8 +235,21 @@ function truncate(str: string | null, max: number): string { > {key.status} - {key.id} + {key.id} {key.provider} + {#if key.status === "exhausted"}
@@ -270,6 +309,13 @@ function truncate(str: string | null, max: number): string {
{/if} {/if} + {#if showKeyModal} {/if} + diff --git a/packages/frontend/src/lib/components/SettingsPanel.svelte b/packages/frontend/src/lib/components/SettingsPanel.svelte index 79574d2..c19fe45 100644 --- a/packages/frontend/src/lib/components/SettingsPanel.svelte +++ b/packages/frontend/src/lib/components/SettingsPanel.svelte @@ -1,4 +1,5 @@