summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/ModelStatus.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-22 17:07:31 +0900
committerAdam Malczewski <[email protected]>2026-05-22 17:07:31 +0900
commit288b21cec98421fda57028a0c8c9d835cfbb14b0 (patch)
tree9ce3ec38acdcf7be96e28f1e0d5deccf6b46c917 /packages/frontend/src/lib/components/ModelStatus.svelte
parent45a4890031192f4e7409443f98e824dad17ba175 (diff)
downloaddispatch-288b21cec98421fda57028a0c8c9d835cfbb14b0.tar.gz
dispatch-288b21cec98421fda57028a0c8c9d835cfbb14b0.zip
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)
Diffstat (limited to 'packages/frontend/src/lib/components/ModelStatus.svelte')
-rw-r--r--packages/frontend/src/lib/components/ModelStatus.svelte49
1 files changed, 48 insertions, 1 deletions
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<string | null>(null); // keyId or null
let keyModalValue = $state("");
let keyModalError = $state<string | null>(null);
let keyModalSaving = $state(false);
+let removingKey = $state<string | null>(null);
+
+
async function loadCredentialStatus(): Promise<void> {
try {
@@ -129,6 +134,27 @@ async function saveApiKey(): Promise<void> {
}
}
+async function removeKey(keyId: string): Promise<void> {
+ 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}
</span>
- <span class="text-xs font-mono">{key.id}</span>
+ <span class="text-xs font-mono flex-1">{key.id}</span>
<span class="text-xs text-base-content/40">{key.provider}</span>
+ <button
+ type="button"
+ class="btn btn-xs btn-ghost btn-square text-base-content/30 hover:text-error"
+ disabled={removingKey === key.id}
+ onclick={() => removeKey(key.id)}
+ title="Remove key"
+ >
+ {#if removingKey === key.id}
+ <span class="loading loading-spinner loading-xs"></span>
+ {:else}
+ ✕
+ {/if}
+ </button>
</div>
{#if key.status === "exhausted"}
<div class="pl-2 flex flex-col gap-0.5">
@@ -270,6 +309,13 @@ function truncate(str: string | null, max: number): string {
</div>
{/if}
{/if}
+ <button
+ type="button"
+ class="btn btn-sm btn-primary btn-outline w-full mt-2"
+ onclick={onAddKey}
+ >
+ + Add New Key
+ </button>
<!-- Import Key Modal -->
{#if showKeyModal}
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" role="dialog">
@@ -308,4 +354,5 @@ function truncate(str: string | null, max: number): string {
</div>
</div>
{/if}
+
</div>