summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/ConfigPanel.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-22 15:24:13 +0900
committerAdam Malczewski <[email protected]>2026-05-22 15:24:13 +0900
commit9ecaabd87c0e51b8a7408dabb0133a9344586859 (patch)
tree4b5809ab23948a5e3f558f3aa34c52d5038f4e19 /packages/frontend/src/lib/components/ConfigPanel.svelte
parent8f1dd855f0c4c877bff8d3a4ba193432b268b1c2 (diff)
downloaddispatch-9ecaabd87c0e51b8a7408dabb0133a9344586859.tar.gz
dispatch-9ecaabd87c0e51b8a7408dabb0133a9344586859.zip
feat: agent builder, CWD support, auto-save, UI polish, unavailable tool handling
- Agent Builder: full CRUD with card grid, drag-and-drop model reorder, edit/delete - Auto-save on edit with 600ms debounce, AbortController for concurrency, fieldset disabled until name entered - Agent definitions stored as TOML with cwd field, loaded from global/project dirs - Working directory: per-tab CWD override in Chat Settings, agent default CWD, auto-create on first message - CWD validation: check-dir endpoint with ~ expansion, real-time validity indicator - Subagent CWD validated against parent's effective CWD using path.relative - Unavailable tool calls: caught gracefully, shown as tool call with error badge, model retries - UI: tab bar border radius, sidebar border removed, chat input ghost style, scroll-to-bottom rectangle - Skills dir collapse uses CSS rotation, Model Choice renamed to Chat Settings, System Prompt view removed - Reusable SkillsBrowser/ToolPermissions with external mode for Agent Builder - ModelSelector: Agent/Manual toggle, agent list, Agent Settings link - Page router, skills recursive scanning, bin/up gopass removed, docker volume mounts
Diffstat (limited to 'packages/frontend/src/lib/components/ConfigPanel.svelte')
-rw-r--r--packages/frontend/src/lib/components/ConfigPanel.svelte177
1 files changed, 92 insertions, 85 deletions
diff --git a/packages/frontend/src/lib/components/ConfigPanel.svelte b/packages/frontend/src/lib/components/ConfigPanel.svelte
index 25cbe98..f517d28 100644
--- a/packages/frontend/src/lib/components/ConfigPanel.svelte
+++ b/packages/frontend/src/lib/components/ConfigPanel.svelte
@@ -1,94 +1,101 @@
<script lang="ts">
- interface AgentTemplate {
- name: string;
- description?: string;
- model_tag?: string;
- tools?: string[];
+interface AgentTemplate {
+ name: string;
+ description?: string;
+ model_tag?: string;
+ tools?: string[];
+}
+
+interface ModelEntry {
+ id: string;
+ provider?: string;
+ tags?: string[];
+}
+
+interface KeyEntry {
+ id: string;
+ provider?: string;
+ status?: string;
+ lastError?: string;
+ exhaustedAt?: string;
+}
+
+interface ConfigData {
+ agents?: Record<string, AgentTemplate>;
+ models?: Record<string, { provider?: string; tags?: string[] }>;
+ keys?: Record<string, { env?: string }>;
+ fallback?: string[];
+ permissions?: Record<string, unknown>;
+}
+
+interface ModelsData {
+ models?: ModelEntry[];
+ tags?: Record<string, string[]>;
+ keys?: KeyEntry[];
+}
+
+const { apiBase }: { apiBase: string } = $props();
+
+let configData = $state<ConfigData | null>(null);
+let modelsData = $state<ModelsData | null>(null);
+let error = $state<string | null>(null);
+let loading = $state(false);
+
+async function fetchData() {
+ loading = true;
+ error = null;
+ try {
+ const [configRes, modelsRes] = await Promise.all([
+ fetch(`${apiBase}/config`),
+ fetch(`${apiBase}/models`),
+ ]);
+ if (!configRes.ok) throw new Error(`/config returned ${configRes.status}`);
+ if (!modelsRes.ok) throw new Error(`/models returned ${modelsRes.status}`);
+ const configJson = await configRes.json();
+ const modelsJson = await modelsRes.json();
+ configData = configJson.config ?? configJson;
+ modelsData = modelsJson;
+ } catch (e) {
+ error = e instanceof Error ? e.message : String(e);
+ } finally {
+ loading = false;
}
+}
- interface ModelEntry {
- id: string;
- provider?: string;
- tags?: string[];
- }
-
- interface KeyEntry {
- id: string;
- provider?: string;
- status?: string;
- lastError?: string;
- exhaustedAt?: string;
- }
-
- interface ConfigData {
- agents?: Record<string, AgentTemplate>;
- models?: Record<string, { provider?: string; tags?: string[] }>;
- keys?: Record<string, { env?: string }>;
- fallback?: string[];
- permissions?: Record<string, unknown>;
- }
-
- interface ModelsData {
- models?: ModelEntry[];
- tags?: Record<string, string[]>;
- keys?: KeyEntry[];
- }
-
- const { apiBase }: { apiBase: string } = $props();
-
- let configData = $state<ConfigData | null>(null);
- let modelsData = $state<ModelsData | null>(null);
- let error = $state<string | null>(null);
- let loading = $state(false);
-
- async function fetchData() {
- loading = true;
- error = null;
- try {
- const [configRes, modelsRes] = await Promise.all([
- fetch(`${apiBase}/config`),
- fetch(`${apiBase}/models`),
- ]);
- if (!configRes.ok) throw new Error(`/config returned ${configRes.status}`);
- if (!modelsRes.ok) throw new Error(`/models returned ${modelsRes.status}`);
- const configJson = await configRes.json();
- const modelsJson = await modelsRes.json();
- configData = configJson.config ?? configJson;
- modelsData = modelsJson;
- } catch (e) {
- error = e instanceof Error ? e.message : String(e);
- } finally {
- loading = false;
- }
- }
-
- $effect(() => {
- fetchData();
- });
-
- const modelCount = $derived(modelsData?.models?.length ?? 0);
- const keyCount = $derived(modelsData?.keys?.length ?? 0);
-
- function formatDate(iso: string | undefined): string {
- if (!iso) return "";
- try {
- return new Date(iso).toLocaleString();
- } catch {
- return iso;
- }
- }
-
- function permissionEntries(permissions: Record<string, unknown>): Array<{ name: string; value: unknown }> {
- return Object.entries(permissions).map(([name, value]) => ({ name, value }));
- }
+$effect(() => {
+ fetchData();
+});
- function isSimpleRule(value: unknown): value is { action: string } {
- return typeof value === "object" && value !== null && "action" in value && Object.keys(value).length === 1;
- }
+const modelCount = $derived(modelsData?.models?.length ?? 0);
+const keyCount = $derived(modelsData?.keys?.length ?? 0);
- function isPatternRule(value: unknown): value is Record<string, { action: string }> {
- return typeof value === "object" && value !== null && !("action" in value);
+function formatDate(iso: string | undefined): string {
+ if (!iso) return "";
+ try {
+ return new Date(iso).toLocaleString();
+ } catch {
+ return iso;
}
+}
+
+function permissionEntries(
+ permissions: Record<string, unknown>,
+): Array<{ name: string; value: unknown }> {
+ return Object.entries(permissions).map(([name, value]) => ({ name, value }));
+}
+
+function isSimpleRule(value: unknown): value is { action: string } {
+ return (
+ typeof value === "object" &&
+ value !== null &&
+ "action" in value &&
+ Object.keys(value).length === 1
+ );
+}
+
+function isPatternRule(value: unknown): value is Record<string, { action: string }> {
+ return typeof value === "object" && value !== null && !("action" in value);
+}
</script>
<details class="collapse collapse-arrow bg-base-200 mt-2">