diff options
| author | Adam Malczewski <[email protected]> | 2026-05-22 15:24:13 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-22 15:24:13 +0900 |
| commit | 9ecaabd87c0e51b8a7408dabb0133a9344586859 (patch) | |
| tree | 4b5809ab23948a5e3f558f3aa34c52d5038f4e19 /packages/frontend/src/lib/components/ChatPanel.svelte | |
| parent | 8f1dd855f0c4c877bff8d3a4ba193432b268b1c2 (diff) | |
| download | dispatch-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/ChatPanel.svelte')
| -rw-r--r-- | packages/frontend/src/lib/components/ChatPanel.svelte | 74 |
1 files changed, 37 insertions, 37 deletions
diff --git a/packages/frontend/src/lib/components/ChatPanel.svelte b/packages/frontend/src/lib/components/ChatPanel.svelte index 24d8c7f..8c92e4f 100644 --- a/packages/frontend/src/lib/components/ChatPanel.svelte +++ b/packages/frontend/src/lib/components/ChatPanel.svelte @@ -1,46 +1,46 @@ <script lang="ts"> - import { untrack } from "svelte"; - import { tabStore } from "../tabs.svelte.js"; - import ChatMessageComponent from "./ChatMessage.svelte"; +import { untrack } from "svelte"; +import { tabStore } from "../tabs.svelte.js"; +import ChatMessageComponent from "./ChatMessage.svelte"; - let messagesEl: HTMLDivElement | undefined; - let userScrolledUp = $state(false); - let isAutoScrolling = false; +let messagesEl: HTMLDivElement | undefined; +let userScrolledUp = $state(false); +let isAutoScrolling = false; - const messages = $derived(tabStore.activeTab?.messages ?? []); +const messages = $derived(tabStore.activeTab?.messages ?? []); - function isNearBottom(el: HTMLElement): boolean { - return el.scrollHeight - el.scrollTop - el.clientHeight < 64; - } +function isNearBottom(el: HTMLElement): boolean { + return el.scrollHeight - el.scrollTop - el.clientHeight < 64; +} - function scrollToBottom(animate = false) { - if (!messagesEl) return; - messagesEl.scrollTo({ - top: messagesEl.scrollHeight, - behavior: animate ? "smooth" : "instant", - }); - } +function scrollToBottom(animate = false) { + if (!messagesEl) return; + messagesEl.scrollTo({ + top: messagesEl.scrollHeight, + behavior: animate ? "smooth" : "instant", + }); +} - function handleScroll() { - if (!messagesEl || isAutoScrolling) return; - userScrolledUp = !isNearBottom(messagesEl); - } +function handleScroll() { + if (!messagesEl || isAutoScrolling) return; + userScrolledUp = !isNearBottom(messagesEl); +} - function resumeAutoScroll() { - userScrolledUp = false; - isAutoScrolling = true; - scrollToBottom(true); - } +function resumeAutoScroll() { + userScrolledUp = false; + isAutoScrolling = true; + scrollToBottom(true); +} - $effect(() => { - const count = messages.length; - void count; - if (messagesEl) { - untrack(() => { - if (!userScrolledUp) scrollToBottom(false); - }); - } - }); +$effect(() => { + const count = messages.length; + void count; + if (messagesEl) { + untrack(() => { + if (!userScrolledUp) scrollToBottom(false); + }); + } +}); </script> <div class="flex flex-col h-full"> @@ -67,12 +67,12 @@ <!-- Scroll-to-bottom button --> <button type="button" - class="absolute bottom-0 left-1/2 -translate-x-1/2 mb-4 btn btn-circle btn-sm shadow-lg transition-opacity duration-200 {userScrolledUp ? 'opacity-100' : 'opacity-0 pointer-events-none'}" + class="absolute bottom-0 left-1/2 -translate-x-1/2 mb-4 btn btn-sm px-8 rounded-lg shadow-lg transition-opacity duration-200 {userScrolledUp ? 'opacity-100' : 'opacity-0 pointer-events-none'}" onclick={resumeAutoScroll} aria-label="Scroll to bottom" tabindex={userScrolledUp ? 0 : -1} > - ↓ + ▼ </button> </div> </div> |
