summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/ChatPanel.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/ChatPanel.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/ChatPanel.svelte')
-rw-r--r--packages/frontend/src/lib/components/ChatPanel.svelte74
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}
>
- ↓
+ &#x25BC;
</button>
</div>
</div>