From 1e13f79899622dd8a5c268b5b8e854b14f82d87f Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Thu, 21 May 2026 20:59:24 +0900 Subject: feat: system prompt editor, tool permissions save-on-send, responsive sidebar, UI polish - System Prompt sidebar view: editable textarea, save-on-send, reset button - Tool permissions: save-on-send pattern (not immediate), reset button - Dynamic system prompt: buildSystemPrompt reads from DB, tool list auto-generated - Responsive sidebar: overlay on small screens with backdrop - Chat bubbles: user=fit-width, assistant=full-width - Fix infinite loops (use onMount for data fetching) - Fix sendMessage race condition (await settings saves before chat POST) - Model selector: auto-open model modal after key selection - Rename views: Permissions->Tools, Tab Settings->Model Choice - Shared appSettings store for cross-component reactive state - Delete old chat.svelte.ts --- packages/frontend/src/App.svelte | 17 ++++-- .../frontend/src/lib/components/ChatMessage.svelte | 4 +- .../src/lib/components/ModelSelector.svelte | 17 +++--- .../src/lib/components/PermissionLog.svelte | 43 ++++++++------- .../src/lib/components/SidebarPanel.svelte | 9 ++-- .../src/lib/components/SystemPromptPanel.svelte | 61 ++++++++++++++++++++++ packages/frontend/src/lib/settings.svelte.ts | 15 ++++++ packages/frontend/src/lib/tabs.svelte.ts | 33 ++++++++++++ 8 files changed, 165 insertions(+), 34 deletions(-) create mode 100644 packages/frontend/src/lib/components/SystemPromptPanel.svelte (limited to 'packages/frontend') diff --git a/packages/frontend/src/App.svelte b/packages/frontend/src/App.svelte index 4d4200a..8288dfd 100644 --- a/packages/frontend/src/App.svelte +++ b/packages/frontend/src/App.svelte @@ -66,7 +66,7 @@ onMount(() => {
sidebarOpen = !sidebarOpen} /> -
+
@@ -76,9 +76,11 @@ onMount(() => {
- +
@@ -109,6 +111,15 @@ onMount(() => {
+ +{#if sidebarOpen} + +
sidebarOpen = false} + >
+{/if} +
{:else} -
-
+
+
{#if message.thinking}
diff --git a/packages/frontend/src/lib/components/ModelSelector.svelte b/packages/frontend/src/lib/components/ModelSelector.svelte index b88b2e3..0af5ae6 100644 --- a/packages/frontend/src/lib/components/ModelSelector.svelte +++ b/packages/frontend/src/lib/components/ModelSelector.svelte @@ -44,16 +44,19 @@ function selectKey(keyId: string) { showKeyModal = false; onKeyChange(keyId); + // Immediately open model selection for the new key + openModelModal(keyId); } - async function openModelModal() { - if (!activeKeyId) return; + async function openModelModal(keyIdOverride?: string) { + const keyId = keyIdOverride ?? activeKeyId; + if (!keyId) return; showModelModal = true; modelError = null; // Check session cache - if (modelCache.has(activeKeyId)) { - availableModels = modelCache.get(activeKeyId)!; + if (modelCache.has(keyId)) { + availableModels = modelCache.get(keyId)!; loadingModels = false; return; } @@ -63,7 +66,7 @@ try { const res = await fetch( - `${config.apiBase}/models/available?keyId=${encodeURIComponent(activeKeyId)}`, + `${config.apiBase}/models/available?keyId=${encodeURIComponent(keyId)}`, ); if (!res.ok) { const data = await res.json().catch(() => ({})); @@ -73,7 +76,7 @@ const data = await res.json(); availableModels = data.models ?? []; // Cache for session - modelCache.set(activeKeyId, availableModels); + modelCache.set(keyId, availableModels); } catch (err) { modelError = err instanceof Error ? err.message : "Failed to fetch models"; } finally { @@ -99,7 +102,7 @@
Model -
diff --git a/packages/frontend/src/lib/components/PermissionLog.svelte b/packages/frontend/src/lib/components/PermissionLog.svelte index 06607de..7733dcf 100644 --- a/packages/frontend/src/lib/components/PermissionLog.svelte +++ b/packages/frontend/src/lib/components/PermissionLog.svelte @@ -1,5 +1,7 @@
Tool Permissions
+

Changes are applied when you send your next message.

{#each toolPermissions as perm (perm.id)} @@ -63,7 +60,7 @@ $effect(() => { togglePermission(perm.id)} />
@@ -74,6 +71,14 @@ $effect(() => { {/each}
+ +

Warning: changing tool access will reset the AI's prompt cache for active conversations, which may increase usage costs.

diff --git a/packages/frontend/src/lib/components/SidebarPanel.svelte b/packages/frontend/src/lib/components/SidebarPanel.svelte index 9aca408..93d528e 100644 --- a/packages/frontend/src/lib/components/SidebarPanel.svelte +++ b/packages/frontend/src/lib/components/SidebarPanel.svelte @@ -8,6 +8,7 @@ import KeyUsage from "./KeyUsage.svelte"; import ClaudeReset from "./ClaudeReset.svelte"; import SettingsPanel from "./SettingsPanel.svelte"; + import SystemPromptPanel from "./SystemPromptPanel.svelte"; import type { TaskItem, LogEntry, KeyInfo } from "../types.js"; const { @@ -42,7 +43,7 @@ let nextId = 0; let panels = $state([{ id: nextId++, selected: "Model Choice" }]); - const viewOptions = ["Select a view", "Model Choice", "Key Usage", "Claude Reset", "Model Status", "Tasks", "Config", "Skills", "Permissions", "Settings"]; + const viewOptions = ["Select a view", "Model Choice", "Key Usage", "Claude Reset", "Model Status", "Tasks", "Config", "Skills", "Tools", "System Prompt", "Settings"]; function addPanel() { panels = [...panels, { id: nextId++, selected: "Select a view" }]; @@ -113,9 +114,11 @@ {:else if panel.selected === "Skills"} - {:else if panel.selected === "Permissions"} + {:else if panel.selected === "Tools"} - {:else if panel.selected === "Settings"} + {:else if panel.selected === "System Prompt"} + + {:else if panel.selected === "Settings"} {/if}
diff --git a/packages/frontend/src/lib/components/SystemPromptPanel.svelte b/packages/frontend/src/lib/components/SystemPromptPanel.svelte new file mode 100644 index 0000000..6b91ebe --- /dev/null +++ b/packages/frontend/src/lib/components/SystemPromptPanel.svelte @@ -0,0 +1,61 @@ + + +
+
System Prompt
+

The base instructions sent to the AI at the start of every conversation. Tool descriptions are appended automatically. Changes are applied when you send your next message.

+ + + + + +

Warning: changing the system prompt will reset the AI's prompt cache for active conversations, which may increase usage costs.

+
diff --git a/packages/frontend/src/lib/settings.svelte.ts b/packages/frontend/src/lib/settings.svelte.ts index 7acc55a..6c35efd 100644 --- a/packages/frontend/src/lib/settings.svelte.ts +++ b/packages/frontend/src/lib/settings.svelte.ts @@ -1,8 +1,23 @@ /** Shared reactive app settings. */ let autoExpandThinking = $state(false); +let systemPrompt = $state(""); +let savedSystemPrompt = $state(""); +let toolPerms = $state>({ read: true, edit: false, bash: false, external_directory: false }); +let savedToolPerms = $state>({ read: true, edit: false, bash: false, external_directory: false }); export const appSettings = { get autoExpandThinking() { return autoExpandThinking; }, set autoExpandThinking(v: boolean) { autoExpandThinking = v; }, + get systemPrompt() { return systemPrompt; }, + set systemPrompt(v: string) { systemPrompt = v; }, + get savedSystemPrompt() { return savedSystemPrompt; }, + set savedSystemPrompt(v: string) { savedSystemPrompt = v; }, + get toolPerms() { return toolPerms; }, + set toolPerms(v: Record) { toolPerms = v; }, + get savedToolPerms() { return savedToolPerms; }, + set savedToolPerms(v: Record) { savedToolPerms = v; }, + get toolPermsDirty() { + return Object.keys(toolPerms).some((k) => toolPerms[k] !== savedToolPerms[k]); + }, }; diff --git a/packages/frontend/src/lib/tabs.svelte.ts b/packages/frontend/src/lib/tabs.svelte.ts index 9ebfaed..e38a6e3 100644 --- a/packages/frontend/src/lib/tabs.svelte.ts +++ b/packages/frontend/src/lib/tabs.svelte.ts @@ -1,4 +1,5 @@ import { config } from "./config.js"; +import { appSettings } from "./settings.svelte.js"; import type { AgentEvent, ChatMessage, ContentSegment, DebugInfo, LogEntry, PermissionPrompt, TaskItem } from "./types.js"; import { wsClient } from "./ws.svelte.js"; @@ -335,6 +336,38 @@ function createTabStore() { }).catch(() => {}); } + // Save settings to DB before sending (bakes in on send) + const settingsSaves: Promise[] = []; + + if (appSettings.systemPrompt !== appSettings.savedSystemPrompt) { + appSettings.savedSystemPrompt = appSettings.systemPrompt; + settingsSaves.push( + fetch(`${config.apiBase}/tabs/settings/system_prompt`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ value: appSettings.systemPrompt }), + }).catch(() => {}), + ); + } + + if (appSettings.toolPermsDirty) { + const perms = appSettings.toolPerms; + appSettings.savedToolPerms = { ...perms }; + for (const [id, enabled] of Object.entries(perms)) { + settingsSaves.push( + fetch(`${config.apiBase}/tabs/settings/perm_${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ value: enabled ? "allow" : "ask" }), + }).catch(() => {}), + ); + } + } + + if (settingsSaves.length > 0) { + await Promise.all(settingsSaves); + } + try { const res = await fetch(`${config.apiBase}/chat`, { method: "POST", -- cgit v1.2.3