@townco/ui 0.1.47 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/use-chat-input.d.ts +2 -0
- package/dist/core/hooks/use-chat-input.js +11 -1
- package/dist/core/hooks/use-chat-messages.d.ts +22 -1
- package/dist/core/hooks/use-chat-messages.js +19 -4
- package/dist/core/hooks/use-chat-session.js +22 -0
- package/dist/core/hooks/use-message-history.d.ts +12 -0
- package/dist/core/hooks/use-message-history.js +113 -0
- package/dist/core/hooks/use-tool-calls.d.ts +11 -0
- package/dist/core/schemas/chat.d.ts +40 -0
- package/dist/core/schemas/chat.js +9 -0
- package/dist/core/schemas/tool-call.d.ts +34 -0
- package/dist/core/schemas/tool-call.js +27 -0
- package/dist/core/store/chat-store.d.ts +7 -0
- package/dist/core/store/chat-store.js +46 -0
- package/dist/gui/components/ChatEmptyState.d.ts +4 -0
- package/dist/gui/components/ChatEmptyState.js +2 -2
- package/dist/gui/components/ChatInput.d.ts +21 -1
- package/dist/gui/components/ChatInput.js +184 -7
- package/dist/gui/components/ChatLayout.d.ts +3 -2
- package/dist/gui/components/ChatLayout.js +7 -7
- package/dist/gui/components/ChatPanelTabContent.d.ts +17 -0
- package/dist/gui/components/ChatPanelTabContent.js +6 -5
- package/dist/gui/components/ChatView.d.ts +3 -1
- package/dist/gui/components/ChatView.js +81 -49
- package/dist/gui/components/ContextUsageButton.d.ts +2 -0
- package/dist/gui/components/ContextUsageButton.js +3 -1
- package/dist/gui/components/InvokingGroup.d.ts +9 -0
- package/dist/gui/components/InvokingGroup.js +16 -0
- package/dist/gui/components/MessageContent.js +122 -6
- package/dist/gui/components/PanelTabsHeader.d.ts +1 -1
- package/dist/gui/components/PanelTabsHeader.js +6 -1
- package/dist/gui/components/Response.js +2 -0
- package/dist/gui/components/Task.js +3 -3
- package/dist/gui/components/TodoListItem.js +1 -1
- package/dist/gui/components/ToolCall.js +57 -5
- package/dist/gui/components/ToolCallGroup.d.ts +8 -0
- package/dist/gui/components/ToolCallGroup.js +29 -0
- package/dist/gui/components/index.d.ts +1 -2
- package/dist/gui/components/index.js +1 -2
- package/dist/sdk/client/acp-client.d.ts +24 -1
- package/dist/sdk/client/acp-client.js +28 -7
- package/dist/sdk/schemas/message.d.ts +41 -9
- package/dist/sdk/schemas/message.js +15 -3
- package/dist/sdk/schemas/session.d.ts +75 -10
- package/dist/sdk/transports/http.d.ts +14 -0
- package/dist/sdk/transports/http.js +130 -36
- package/dist/sdk/transports/stdio.d.ts +14 -0
- package/dist/sdk/transports/stdio.js +27 -10
- package/dist/sdk/transports/types.d.ts +29 -0
- package/package.json +3 -3
- package/dist/core/hooks/index.d.ts.map +0 -1
- package/dist/core/hooks/index.js.map +0 -1
- package/dist/core/hooks/use-chat-input.d.ts.map +0 -1
- package/dist/core/hooks/use-chat-input.js.map +0 -1
- package/dist/core/hooks/use-chat-messages.d.ts.map +0 -1
- package/dist/core/hooks/use-chat-messages.js.map +0 -1
- package/dist/core/hooks/use-chat-session.d.ts.map +0 -1
- package/dist/core/hooks/use-chat-session.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/core/lib/logger.d.ts +0 -24
- package/dist/core/lib/logger.js +0 -108
- package/dist/core/schemas/chat.d.ts.map +0 -1
- package/dist/core/schemas/chat.js.map +0 -1
- package/dist/core/schemas/index.d.ts.map +0 -1
- package/dist/core/schemas/index.js.map +0 -1
- package/dist/core/store/chat-store.d.ts.map +0 -1
- package/dist/core/store/chat-store.js.map +0 -1
- package/dist/gui/components/Button.d.ts.map +0 -1
- package/dist/gui/components/Button.js.map +0 -1
- package/dist/gui/components/Card.d.ts.map +0 -1
- package/dist/gui/components/Card.js.map +0 -1
- package/dist/gui/components/ChatInput.d.ts.map +0 -1
- package/dist/gui/components/ChatInput.js.map +0 -1
- package/dist/gui/components/ChatSecondaryPanel.d.ts.map +0 -1
- package/dist/gui/components/ChatSecondaryPanel.js.map +0 -1
- package/dist/gui/components/ChatStatus.d.ts.map +0 -1
- package/dist/gui/components/ChatStatus.js.map +0 -1
- package/dist/gui/components/Conversation.d.ts.map +0 -1
- package/dist/gui/components/Conversation.js.map +0 -1
- package/dist/gui/components/Dialog.d.ts.map +0 -1
- package/dist/gui/components/Dialog.js.map +0 -1
- package/dist/gui/components/HeightTransition.d.ts.map +0 -1
- package/dist/gui/components/HeightTransition.js.map +0 -1
- package/dist/gui/components/Input.d.ts.map +0 -1
- package/dist/gui/components/Input.js.map +0 -1
- package/dist/gui/components/Label.d.ts.map +0 -1
- package/dist/gui/components/Label.js.map +0 -1
- package/dist/gui/components/MarkdownRenderer.d.ts.map +0 -1
- package/dist/gui/components/MarkdownRenderer.js.map +0 -1
- package/dist/gui/components/Message.d.ts.map +0 -1
- package/dist/gui/components/Message.js.map +0 -1
- package/dist/gui/components/MessageContent.d.ts.map +0 -1
- package/dist/gui/components/MessageContent.js.map +0 -1
- package/dist/gui/components/MessageList.d.ts.map +0 -1
- package/dist/gui/components/MessageList.js.map +0 -1
- package/dist/gui/components/Reasoning.d.ts.map +0 -1
- package/dist/gui/components/Reasoning.js.map +0 -1
- package/dist/gui/components/Response.d.ts.map +0 -1
- package/dist/gui/components/Response.js.map +0 -1
- package/dist/gui/components/Select.d.ts.map +0 -1
- package/dist/gui/components/Select.js.map +0 -1
- package/dist/gui/components/Tabs.d.ts.map +0 -1
- package/dist/gui/components/Tabs.js.map +0 -1
- package/dist/gui/components/Task.d.ts.map +0 -1
- package/dist/gui/components/Task.js.map +0 -1
- package/dist/gui/components/Textarea.d.ts.map +0 -1
- package/dist/gui/components/Textarea.js.map +0 -1
- package/dist/gui/components/ThinkingBlock.d.ts.map +0 -1
- package/dist/gui/components/ThinkingBlock.js.map +0 -1
- package/dist/gui/components/TodoList.d.ts.map +0 -1
- package/dist/gui/components/TodoList.js.map +0 -1
- package/dist/gui/components/TodoListItem.d.ts.map +0 -1
- package/dist/gui/components/TodoListItem.js.map +0 -1
- package/dist/gui/components/index.d.ts.map +0 -1
- package/dist/gui/components/index.js.map +0 -1
- package/dist/gui/index.d.ts.map +0 -1
- package/dist/gui/index.js.map +0 -1
- package/dist/gui/lib/utils.d.ts.map +0 -1
- package/dist/gui/lib/utils.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/sdk/client/acp-client.d.ts.map +0 -1
- package/dist/sdk/client/acp-client.js.map +0 -1
- package/dist/sdk/client/index.d.ts.map +0 -1
- package/dist/sdk/client/index.js.map +0 -1
- package/dist/sdk/index.d.ts.map +0 -1
- package/dist/sdk/index.js.map +0 -1
- package/dist/sdk/schemas/agent.d.ts.map +0 -1
- package/dist/sdk/schemas/agent.js.map +0 -1
- package/dist/sdk/schemas/index.d.ts.map +0 -1
- package/dist/sdk/schemas/index.js.map +0 -1
- package/dist/sdk/schemas/message.d.ts.map +0 -1
- package/dist/sdk/schemas/message.js.map +0 -1
- package/dist/sdk/schemas/session.d.ts.map +0 -1
- package/dist/sdk/schemas/session.js.map +0 -1
- package/dist/sdk/transports/http.d.ts.map +0 -1
- package/dist/sdk/transports/http.js.map +0 -1
- package/dist/sdk/transports/index.d.ts.map +0 -1
- package/dist/sdk/transports/index.js.map +0 -1
- package/dist/sdk/transports/stdio.d.ts.map +0 -1
- package/dist/sdk/transports/stdio.js.map +0 -1
- package/dist/sdk/transports/types.d.ts.map +0 -1
- package/dist/sdk/transports/types.js.map +0 -1
- package/dist/sdk/transports/websocket.d.ts.map +0 -1
- package/dist/sdk/transports/websocket.js.map +0 -1
- package/dist/test-http-client.d.ts +0 -11
- package/dist/test-http-client.d.ts.map +0 -1
- package/dist/test-http-client.js +0 -147
- package/dist/test-http-client.js.map +0 -1
- package/dist/test-http-transport.d.ts +0 -11
- package/dist/test-http-transport.d.ts.map +0 -1
- package/dist/test-http-transport.js +0 -127
- package/dist/test-http-transport.js.map +0 -1
- package/dist/tui/components/ChatView.d.ts.map +0 -1
- package/dist/tui/components/ChatView.js.map +0 -1
- package/dist/tui/components/GameOfLife.d.ts.map +0 -1
- package/dist/tui/components/GameOfLife.js.map +0 -1
- package/dist/tui/components/InputBox.d.ts.map +0 -1
- package/dist/tui/components/InputBox.js.map +0 -1
- package/dist/tui/components/MessageList.d.ts.map +0 -1
- package/dist/tui/components/MessageList.js.map +0 -1
- package/dist/tui/components/ReadlineInput.d.ts.map +0 -1
- package/dist/tui/components/ReadlineInput.js.map +0 -1
- package/dist/tui/components/StatusBar.d.ts.map +0 -1
- package/dist/tui/components/StatusBar.js.map +0 -1
- package/dist/tui/components/index.d.ts.map +0 -1
- package/dist/tui/components/index.js.map +0 -1
- package/dist/tui/index.d.ts.map +0 -1
- package/dist/tui/index.js.map +0 -1
|
@@ -1,26 +1,68 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createLogger } from "@townco/core";
|
|
3
|
-
import { ArrowUp, ChevronUp, Code, PanelRight, Settings, Sparkles, } from "lucide-react";
|
|
3
|
+
import { ArrowUp, Bug, ChevronUp, Code, PanelRight, Settings, Sparkles, X, } from "lucide-react";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { useChatMessages, useChatSession, useToolCalls, } from "../../core/hooks/index.js";
|
|
6
|
-
import { useChatStore } from "../../core/store/chat-store.js";
|
|
6
|
+
import { selectTodosForCurrentSession, useChatStore, } from "../../core/store/chat-store.js";
|
|
7
7
|
import { calculateTokenPercentage, formatTokenPercentage, } from "../../core/utils/model-context.js";
|
|
8
8
|
import { cn } from "../lib/utils.js";
|
|
9
|
-
import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SourcesTabContent, Tabs, TabsContent, ThemeToggle, TodoTabContent, } from "./index.js";
|
|
9
|
+
import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SettingsTabContent, SourcesTabContent, Tabs, TabsContent, ThemeToggle, TodoTabContent, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./index.js";
|
|
10
10
|
const logger = createLogger("gui");
|
|
11
11
|
// Helper component to provide openFiles callback
|
|
12
12
|
function OpenFilesButton({ children, }) {
|
|
13
|
-
const { setPanelSize, setActiveTab } = ChatLayout.useChatLayoutContext();
|
|
13
|
+
const { setPanelSize, setActiveTab, panelSize, activeTab } = ChatLayout.useChatLayoutContext();
|
|
14
14
|
const openFiles = () => {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
if (panelSize !== "hidden" && activeTab === "files") {
|
|
16
|
+
setPanelSize("hidden");
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
setPanelSize("small");
|
|
20
|
+
setActiveTab("files");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const openSettings = () => {
|
|
24
|
+
if (panelSize !== "hidden" && activeTab === "settings") {
|
|
25
|
+
setPanelSize("hidden");
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
setPanelSize("small");
|
|
29
|
+
setActiveTab("settings");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return _jsx(_Fragment, { children: children({ openFiles, openSettings }) });
|
|
33
|
+
}
|
|
34
|
+
// Hook to handle sidebar keyboard shortcut (Cmd+B / Ctrl+B)
|
|
35
|
+
function SidebarHotkey() {
|
|
36
|
+
const { panelSize, setPanelSize } = ChatLayout.useChatLayoutContext();
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const handleKeyDown = (e) => {
|
|
39
|
+
// Cmd+B (Mac) or Ctrl+B (Windows/Linux)
|
|
40
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "b") {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
setPanelSize(panelSize === "hidden" ? "small" : "hidden");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
46
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
47
|
+
}, [panelSize, setPanelSize]);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Chat input with attachment handling
|
|
51
|
+
function ChatInputWithAttachments({ client, startSession, placeholder, latestContextSize, currentModel, commandMenuItems, }) {
|
|
52
|
+
const attachedFiles = useChatStore((state) => state.input.attachedFiles);
|
|
53
|
+
const addFileAttachment = useChatStore((state) => state.addFileAttachment);
|
|
54
|
+
const removeFileAttachment = useChatStore((state) => state.removeFileAttachment);
|
|
55
|
+
const handleFilesSelected = (files) => {
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
addFileAttachment(file);
|
|
58
|
+
}
|
|
17
59
|
};
|
|
18
|
-
return _jsx(
|
|
60
|
+
return (_jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), attachedFiles.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 p-3 border-b border-border", children: attachedFiles.map((file, index) => (_jsxs("div", { className: "relative group rounded-md overflow-hidden border border-border", children: [_jsx("img", { src: `data:${file.mimeType};base64,${file.data}`, alt: file.name, className: "h-20 w-20 object-cover" }), _jsx("button", { type: "button", onClick: () => removeFileAttachment(index), className: "absolute top-1 right-1 p-1 rounded-full bg-background/80 hover:bg-background opacity-0 group-hover:opacity-100 transition-opacity", children: _jsx(X, { className: "size-3" }) })] }, index))) })), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true, onFilesDropped: handleFilesSelected }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, { onFilesSelected: handleFilesSelected }), latestContextSize != null && (_jsx(ContextUsageButton, { contextSize: latestContextSize, modelContextWindow: currentModel?.includes("claude") ? 200000 : 128000 }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputVoiceInput, {}), _jsx(ChatInputSubmit, { children: _jsx(ArrowUp, { className: "size-4" }) })] })] })] }));
|
|
19
61
|
}
|
|
20
62
|
// Controlled Tabs component for the aside panel
|
|
21
|
-
function AsideTabs() {
|
|
63
|
+
function AsideTabs({ todos, tools, mcps, subagents, }) {
|
|
22
64
|
const { activeTab, setActiveTab } = ChatLayout.useChatLayoutContext();
|
|
23
|
-
return (_jsxs(Tabs, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "flex flex-col h-full", children: [_jsx("div", { className: cn("border-b border-border bg-card", "px-6 py-2 h-16", "flex items-center", "[border-bottom-width:0.5px]"), children: _jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources"], variant: "compact" }) }), _jsx(TabsContent, { value: "todo", className: "flex-1 p-4 mt-0", children: _jsx(TodoTabContent, {}) }), _jsx(TabsContent, { value: "files", className: "flex-1 p-4 mt-0", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "flex-1 p-4 mt-0", children: _jsx(SourcesTabContent, {}) })] }));
|
|
65
|
+
return (_jsxs(Tabs, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "flex flex-col h-full", children: [_jsx("div", { className: cn("border-b border-border bg-card", "px-6 py-2 h-16", "flex items-center", "[border-bottom-width:0.5px]"), children: _jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources", "settings"], variant: "compact" }) }), _jsx(TabsContent, { value: "todo", className: "flex-1 p-4 mt-0", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "flex-1 p-4 mt-0", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "flex-1 p-4 mt-0", children: _jsx(SourcesTabContent, {}) }), _jsx(TabsContent, { value: "settings", className: "flex-1 p-4 mt-0 overflow-y-auto", children: _jsx(SettingsTabContent, { tools: tools ?? [], mcps: mcps ?? [], subagents: subagents ?? [] }) })] }));
|
|
24
66
|
}
|
|
25
67
|
// Mobile header component that uses ChatHeader context
|
|
26
68
|
function MobileHeader({ agentName, showHeader, }) {
|
|
@@ -28,13 +70,14 @@ function MobileHeader({ agentName, showHeader, }) {
|
|
|
28
70
|
return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx("h1", { className: "text-heading-4 text-foreground", children: agentName }) })), !showHeader && _jsx("div", { className: "flex-1" }), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle menu", onClick: () => setIsExpanded(!isExpanded), children: _jsx(ChevronUp, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isExpanded ? "" : "rotate-180") }) })] }));
|
|
29
71
|
}
|
|
30
72
|
// Header component that uses ChatLayout context (must be inside ChatLayout.Root)
|
|
31
|
-
function AppChatHeader({ agentName, todos, sources, showHeader, }) {
|
|
73
|
+
function AppChatHeader({ agentName, todos, sources, showHeader, sessionId, debuggerUrl, tools, mcps, subagents, }) {
|
|
32
74
|
const { panelSize, setPanelSize } = ChatLayout.useChatLayoutContext();
|
|
33
|
-
|
|
75
|
+
const debuggerLink = sessionId && debuggerUrl ? `${debuggerUrl}/sessions/${sessionId}` : null;
|
|
76
|
+
return (_jsxs(ChatHeader.Root, { className: cn("border-b border-border bg-card relative lg:p-0", "[border-bottom-width:0.5px]"), children: [_jsxs("div", { className: "hidden lg:flex items-center w-full h-16 py-5 pl-6 pr-4", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx("h1", { className: "text-heading-4 text-foreground", children: agentName }) })), !showHeader && _jsx("div", { className: "flex-1" }), debuggerUrl && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(IconButton, { "aria-label": "View session in debugger", disabled: !debuggerLink, asChild: !!debuggerLink, children: debuggerLink ? (_jsx("a", { href: debuggerLink, target: "_blank", rel: "noopener noreferrer", children: _jsx(Bug, { className: "size-4 text-muted-foreground" }) })) : (_jsx(Bug, { className: "size-4 text-muted-foreground" })) }) }), _jsx(TooltipContent, { children: _jsx("p", { children: "View session in debugger" }) })] }) })), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle sidebar", onClick: () => {
|
|
34
77
|
setPanelSize(panelSize === "hidden" ? "small" : "hidden");
|
|
35
|
-
}, children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName, showHeader: showHeader }), _jsx(ChatHeader.ExpandablePanel, { className: cn("pt-6 pb-8 px-6", "border-b border-border bg-card", "shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]", "[border-bottom-width:0.5px]"), children: _jsxs(Tabs, { defaultValue: "todo", className: "w-full", children: [_jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources"], variant: "default" }), _jsx(TabsContent, { value: "todo", className: "mt-4", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "mt-4", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "mt-4", children: _jsx(SourcesTabContent, { sources: sources }) })] }) })] }));
|
|
78
|
+
}, children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName, showHeader: showHeader }), _jsx(ChatHeader.ExpandablePanel, { className: cn("pt-6 pb-8 px-6", "border-b border-border bg-card", "shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]", "[border-bottom-width:0.5px]"), children: _jsxs(Tabs, { defaultValue: "todo", className: "w-full", children: [_jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources", "settings"], variant: "default" }), _jsx(TabsContent, { value: "todo", className: "mt-4", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "mt-4", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "mt-4", children: _jsx(SourcesTabContent, { sources: sources }) }), _jsx(TabsContent, { value: "settings", className: "mt-4", children: _jsx(SettingsTabContent, { tools: tools ?? [], mcps: mcps ?? [], subagents: subagents ?? [] }) })] }) })] }));
|
|
36
79
|
}
|
|
37
|
-
export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
80
|
+
export function ChatView({ client, initialSessionId, error: initError, debuggerUrl, }) {
|
|
38
81
|
// Use shared hooks from @townco/ui/core - MUST be called before any early returns
|
|
39
82
|
const { connectionStatus, connect, sessionId, startSession } = useChatSession(client, initialSessionId);
|
|
40
83
|
const { messages, sendMessage } = useChatMessages(client, startSession);
|
|
@@ -42,11 +85,14 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
42
85
|
const error = useChatStore((state) => state.error);
|
|
43
86
|
const currentModel = useChatStore((state) => state.currentModel);
|
|
44
87
|
const [agentName, setAgentName] = useState("Agent");
|
|
45
|
-
const [agentDescription, setAgentDescription] = useState("This agent can help you
|
|
88
|
+
const [agentDescription, setAgentDescription] = useState("This research agent can help you find and summarize information, analyze sources, track tasks, and answer questions about your research. Start by typing a message below to begin your investigation.");
|
|
46
89
|
const [suggestedPrompts, setSuggestedPrompts] = useState([
|
|
47
90
|
"Search the web for the latest news on top tech company earnings, produce a summary for each company, and then a macro trend analysis of the tech industry. Use your todo list",
|
|
48
91
|
"What can you help me with?",
|
|
49
92
|
]);
|
|
93
|
+
const [agentTools, setAgentTools] = useState([]);
|
|
94
|
+
const [agentMcps, setAgentMcps] = useState([]);
|
|
95
|
+
const [agentSubagents, setAgentSubagents] = useState([]);
|
|
50
96
|
const [isLargeScreen, setIsLargeScreen] = useState(typeof window !== "undefined" ? window.innerWidth >= 1024 : true);
|
|
51
97
|
const [placeholder, setPlaceholder] = useState("Type a message or / for commands...");
|
|
52
98
|
// Log connection status changes
|
|
@@ -80,6 +126,21 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
80
126
|
if (agentInfo.suggestedPrompts && agentInfo.suggestedPrompts.length > 0) {
|
|
81
127
|
setSuggestedPrompts(agentInfo.suggestedPrompts);
|
|
82
128
|
}
|
|
129
|
+
// Get tools, MCPs, and subagents
|
|
130
|
+
if (agentInfo.tools && agentInfo.tools.length > 0) {
|
|
131
|
+
setAgentTools(agentInfo.tools);
|
|
132
|
+
logger.info("Agent tools loaded", { tools: agentInfo.tools });
|
|
133
|
+
}
|
|
134
|
+
if (agentInfo.mcps && agentInfo.mcps.length > 0) {
|
|
135
|
+
setAgentMcps(agentInfo.mcps);
|
|
136
|
+
logger.info("Agent MCPs loaded", { mcps: agentInfo.mcps });
|
|
137
|
+
}
|
|
138
|
+
if (agentInfo.subagents && agentInfo.subagents.length > 0) {
|
|
139
|
+
setAgentSubagents(agentInfo.subagents);
|
|
140
|
+
logger.info("Agent subagents loaded", {
|
|
141
|
+
subagents: agentInfo.subagents,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
83
144
|
}
|
|
84
145
|
}, [client, sessionId, connectionStatus]);
|
|
85
146
|
// Monitor screen size changes and update isLargeScreen state
|
|
@@ -108,8 +169,7 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
108
169
|
if (initError) {
|
|
109
170
|
return (_jsx("div", { className: "flex items-center justify-center h-screen bg-background", children: _jsxs("div", { className: "text-center p-8 max-w-md", children: [_jsx("h1", { className: "text-2xl font-bold text-destructive mb-4", children: "Initialization Error" }), _jsx("p", { className: "text-foreground mb-4", children: initError }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Failed to initialize the ACP client. Check the console for details." })] }) }));
|
|
110
171
|
}
|
|
111
|
-
|
|
112
|
-
const todos = [];
|
|
172
|
+
const todos = useChatStore(selectTodosForCurrentSession);
|
|
113
173
|
// Dummy sources data based on Figma design
|
|
114
174
|
const sources = [
|
|
115
175
|
{
|
|
@@ -149,26 +209,6 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
149
209
|
const latestContextSize = useChatStore((state) => state.latestContextSize);
|
|
150
210
|
// Command menu items for chat input
|
|
151
211
|
const commandMenuItems = [
|
|
152
|
-
{
|
|
153
|
-
id: "model-sonnet",
|
|
154
|
-
label: "Use Sonnet 4.5",
|
|
155
|
-
description: "Switch to Claude Sonnet 4.5 model",
|
|
156
|
-
icon: _jsx(Sparkles, { className: "h-4 w-4" }),
|
|
157
|
-
category: "model",
|
|
158
|
-
onSelect: () => {
|
|
159
|
-
logger.info("User selected Sonnet 4.5 model");
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "model-opus",
|
|
164
|
-
label: "Use Opus",
|
|
165
|
-
description: "Switch to Claude Opus model",
|
|
166
|
-
icon: _jsx(Sparkles, { className: "h-4 w-4" }),
|
|
167
|
-
category: "model",
|
|
168
|
-
onSelect: () => {
|
|
169
|
-
logger.info("User selected Opus model");
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
212
|
{
|
|
173
213
|
id: "settings",
|
|
174
214
|
label: "Open Settings",
|
|
@@ -179,22 +219,14 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
179
219
|
logger.info("User opened settings");
|
|
180
220
|
},
|
|
181
221
|
},
|
|
182
|
-
{
|
|
183
|
-
id: "code-mode",
|
|
184
|
-
label: "Code Mode",
|
|
185
|
-
description: "Enable code-focused responses",
|
|
186
|
-
icon: _jsx(Code, { className: "h-4 w-4" }),
|
|
187
|
-
category: "mode",
|
|
188
|
-
onSelect: () => {
|
|
189
|
-
logger.info("User enabled code mode");
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
222
|
];
|
|
193
|
-
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0 }), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx(OpenFilesButton, { children: ({ openFiles }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: agentDescription, suggestedPrompts: suggestedPrompts, onPromptClick: (prompt) => {
|
|
223
|
+
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsx(SidebarHotkey, {}), _jsxs(ChatLayout.Main, { children: [_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0, sessionId: sessionId, tools: agentTools, mcps: agentMcps, subagents: agentSubagents, ...(debuggerUrl && { debuggerUrl }) }), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx(OpenFilesButton, { children: ({ openFiles, openSettings }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: agentDescription, suggestedPrompts: suggestedPrompts, onPromptClick: (prompt) => {
|
|
194
224
|
sendMessage(prompt);
|
|
195
225
|
setPlaceholder("Type a message or / for commands...");
|
|
196
226
|
logger.info("Prompt clicked", { prompt });
|
|
197
|
-
}, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles
|
|
227
|
+
}, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles, onOpenSettings: openSettings, toolsAndMcpsCount: agentTools.length +
|
|
228
|
+
agentMcps.length +
|
|
229
|
+
agentSubagents.length }) })) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
|
|
198
230
|
// Calculate dynamic spacing based on message sequence
|
|
199
231
|
const isFirst = index === 0;
|
|
200
232
|
const previousMessage = isFirst ? null : messages[index - 1];
|
|
@@ -213,5 +245,5 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
213
245
|
previousMessage?.role === "assistant" ? "mt-2" : "mt-6";
|
|
214
246
|
}
|
|
215
247
|
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
216
|
-
}) })) }), _jsx(ChatLayout.Footer, { children:
|
|
248
|
+
}) })) }), _jsx(ChatLayout.Footer, { children: _jsx(ChatInputWithAttachments, { client: client, startSession: startSession, placeholder: placeholder, latestContextSize: latestContextSize, currentModel: currentModel, commandMenuItems: commandMenuItems }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsx(AsideTabs, { todos: todos, tools: agentTools, mcps: agentMcps, subagents: agentSubagents }) }))] }));
|
|
217
249
|
}
|
|
@@ -32,6 +32,8 @@ export const ContextUsageButton = React.forwardRef(({ contextSize, modelContextW
|
|
|
32
32
|
const center = size / 2;
|
|
33
33
|
const circumference = 2 * Math.PI * radius;
|
|
34
34
|
const offset = circumference - (clampedPercentage / 100) * circumference;
|
|
35
|
-
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full cursor-default", colorClass, className), ...props, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "transform -rotate-90", children: [_jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", className: "opacity-20" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", className: "transition-all duration-300 ease-in-out" })] }) }) }), _jsx(TooltipContent, { side: "top", align: "center", className: "p-3", children: _jsxs("div", { className: "space-y-2 font-mono text-xs", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "System Prompt:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.systemPromptTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.systemPromptTokens), ")"] })] })] }),
|
|
35
|
+
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full cursor-default", colorClass, className), ...props, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "transform -rotate-90", children: [_jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", className: "opacity-20" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", className: "transition-all duration-300 ease-in-out" })] }) }) }), _jsx(TooltipContent, { side: "top", align: "center", className: "p-3", children: _jsxs("div", { className: "space-y-2 font-mono text-xs", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "System Prompt:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.systemPromptTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.systemPromptTokens), ")"] })] })] }), contextSize.toolOverheadTokens !== undefined &&
|
|
36
|
+
contextSize.toolOverheadTokens > 0 && (_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tools Definition:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolOverheadTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolOverheadTokens), ")"] })] })] })), contextSize.mcpOverheadTokens !== undefined &&
|
|
37
|
+
contextSize.mcpOverheadTokens > 0 && (_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "MCPs Definition:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.mcpOverheadTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.mcpOverheadTokens), ")"] })] })] })), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "User Messages:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.userMessagesTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.userMessagesTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Assistant Messages:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.assistantMessagesTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.assistantMessagesTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tool Inputs:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolInputTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolInputTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tool Results:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolResultsTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolResultsTokens), ")"] })] })] })] }), _jsx("div", { className: "border-t border-border pt-2", children: _jsxs("div", { className: cn("flex justify-end gap-2 font-semibold", colorClass), children: [_jsx("span", { children: actualTokens.toLocaleString() }), _jsxs("span", { children: ["(", formattedPercentage, ")"] })] }) })] }) })] }) }));
|
|
36
38
|
});
|
|
37
39
|
ContextUsageButton.displayName = "ContextUsageButton";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
|
|
2
|
+
export interface InvokingGroupProps {
|
|
3
|
+
toolCalls: ToolCallType[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* InvokingGroup component - displays a group of preliminary (invoking) tool calls
|
|
7
|
+
* Shows as "Invoking parallel operation (N)" with a summary of unique tool names
|
|
8
|
+
*/
|
|
9
|
+
export declare function InvokingGroup({ toolCalls }: InvokingGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ListVideo } from "lucide-react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
/**
|
|
5
|
+
* InvokingGroup component - displays a group of preliminary (invoking) tool calls
|
|
6
|
+
* Shows as "Invoking parallel operation (N)" with a summary of unique tool names
|
|
7
|
+
*/
|
|
8
|
+
export function InvokingGroup({ toolCalls }) {
|
|
9
|
+
// Get unique display names for the summary
|
|
10
|
+
const displayNames = toolCalls.map((tc) => tc.prettyName || tc.title);
|
|
11
|
+
const uniqueNames = [...new Set(displayNames)];
|
|
12
|
+
const summary = uniqueNames.length <= 2
|
|
13
|
+
? uniqueNames.join(", ")
|
|
14
|
+
: `${uniqueNames.slice(0, 2).join(", ")} +${uniqueNames.length - 2} more`;
|
|
15
|
+
return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-paragraph-sm text-muted-foreground/50", children: [_jsx(ListVideo, { className: "h-3 w-3" }), _jsx("span", { children: "Invoking parallel operation" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-muted-foreground/50", children: toolCalls.length })] }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground/50 pl-4.5", children: summary })] }));
|
|
16
|
+
}
|
|
@@ -3,9 +3,11 @@ import { cva } from "class-variance-authority";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useChatStore } from "../../core/store/chat-store.js";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { InvokingGroup } from "./InvokingGroup.js";
|
|
6
7
|
import { Reasoning } from "./Reasoning.js";
|
|
7
8
|
import { Response } from "./Response.js";
|
|
8
9
|
import { ToolCall } from "./ToolCall.js";
|
|
10
|
+
import { ToolCallGroup } from "./ToolCallGroup.js";
|
|
9
11
|
/**
|
|
10
12
|
* MessageContent component inspired by shadcn.io/ai
|
|
11
13
|
* Provides the content container with role-based styling
|
|
@@ -53,36 +55,150 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
53
55
|
.slice()
|
|
54
56
|
.sort((a, b) => (a.contentPosition ?? Infinity) -
|
|
55
57
|
(b.contentPosition ?? Infinity));
|
|
58
|
+
// Helper to check if a tool call is preliminary (invoking)
|
|
59
|
+
const isPreliminary = (tc) => tc.status === "pending" &&
|
|
60
|
+
(!tc.rawInput || Object.keys(tc.rawInput).length === 0);
|
|
61
|
+
// Helper to group tool calls by batchId and consecutive preliminary calls
|
|
62
|
+
const groupToolCalls = (toolCalls) => {
|
|
63
|
+
const result = [];
|
|
64
|
+
const batchGroups = new Map();
|
|
65
|
+
let currentInvokingGroup = [];
|
|
66
|
+
const flushInvokingGroup = () => {
|
|
67
|
+
if (currentInvokingGroup.length > 1) {
|
|
68
|
+
result.push({
|
|
69
|
+
type: "invoking",
|
|
70
|
+
toolCalls: currentInvokingGroup,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (currentInvokingGroup.length === 1) {
|
|
74
|
+
result.push(currentInvokingGroup[0]);
|
|
75
|
+
}
|
|
76
|
+
currentInvokingGroup = [];
|
|
77
|
+
};
|
|
78
|
+
for (const tc of toolCalls) {
|
|
79
|
+
// Handle batch groups
|
|
80
|
+
if (tc.batchId) {
|
|
81
|
+
flushInvokingGroup();
|
|
82
|
+
const existing = batchGroups.get(tc.batchId);
|
|
83
|
+
if (existing) {
|
|
84
|
+
existing.push(tc);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const group = [tc];
|
|
88
|
+
batchGroups.set(tc.batchId, group);
|
|
89
|
+
result.push({ type: "batch", toolCalls: group });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Handle consecutive preliminary (invoking) tool calls
|
|
93
|
+
else if (isPreliminary(tc)) {
|
|
94
|
+
currentInvokingGroup.push(tc);
|
|
95
|
+
}
|
|
96
|
+
// Regular tool call
|
|
97
|
+
else {
|
|
98
|
+
flushInvokingGroup();
|
|
99
|
+
result.push(tc);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Flush any remaining invoking group
|
|
103
|
+
flushInvokingGroup();
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
// Helper to render a tool call or group
|
|
107
|
+
const renderToolCallOrGroup = (item, index) => {
|
|
108
|
+
// Batch group (parallel operations)
|
|
109
|
+
if (typeof item === "object" &&
|
|
110
|
+
"type" in item &&
|
|
111
|
+
item.type === "batch") {
|
|
112
|
+
return (_jsx(ToolCallGroup, { toolCalls: item.toolCalls }, `batch-${item.toolCalls[0]?.batchId || index}`));
|
|
113
|
+
}
|
|
114
|
+
// Invoking group (consecutive preliminary tool calls)
|
|
115
|
+
if (typeof item === "object" &&
|
|
116
|
+
"type" in item &&
|
|
117
|
+
item.type === "invoking") {
|
|
118
|
+
return (_jsx(InvokingGroup, { toolCalls: item.toolCalls }, `invoking-${item.toolCalls[0]?.id || index}`));
|
|
119
|
+
}
|
|
120
|
+
// Single tool call
|
|
121
|
+
return (_jsx(ToolCall, { toolCall: item }, item.id));
|
|
122
|
+
};
|
|
56
123
|
// If no tool calls or they don't have positions, render old way
|
|
57
124
|
if (sortedToolCalls.length === 0 ||
|
|
58
125
|
!sortedToolCalls.some((tc) => tc.contentPosition !== undefined)) {
|
|
59
|
-
|
|
126
|
+
const groupedToolCalls = groupToolCalls(sortedToolCalls);
|
|
127
|
+
return (_jsxs(_Fragment, { children: [groupedToolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) })), _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false })] }));
|
|
60
128
|
}
|
|
61
129
|
// Render content interleaved with tool calls
|
|
130
|
+
// Group consecutive tool calls with the same batchId
|
|
62
131
|
const elements = [];
|
|
63
132
|
let currentPosition = 0;
|
|
64
|
-
|
|
133
|
+
let currentBatch = [];
|
|
134
|
+
let currentBatchId;
|
|
135
|
+
const flushBatch = () => {
|
|
136
|
+
if (currentBatch.length > 1 && currentBatchId) {
|
|
137
|
+
elements.push(_jsx(ToolCallGroup, { toolCalls: currentBatch }, `group-${currentBatchId}`));
|
|
138
|
+
}
|
|
139
|
+
else if (currentBatch.length === 1) {
|
|
140
|
+
elements.push(_jsx("div", { children: _jsx(ToolCall, { toolCall: currentBatch[0] }) }, `tool-${currentBatch[0].id}`));
|
|
141
|
+
}
|
|
142
|
+
currentBatch = [];
|
|
143
|
+
currentBatchId = undefined;
|
|
144
|
+
};
|
|
145
|
+
// Separate preliminary tool calls - they should render at the end, not break text
|
|
146
|
+
const preliminaryToolCalls = sortedToolCalls.filter(isPreliminary);
|
|
147
|
+
const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !isPreliminary(tc));
|
|
148
|
+
// Process non-preliminary tool calls inline with text
|
|
149
|
+
nonPreliminaryToolCalls.forEach((toolCall, index) => {
|
|
65
150
|
const position = toolCall.contentPosition ?? message.content.length;
|
|
66
151
|
// Add text before this tool call
|
|
67
152
|
if (position > currentPosition) {
|
|
153
|
+
// Flush any pending batch before adding text
|
|
154
|
+
flushBatch();
|
|
68
155
|
const textChunk = message.content.slice(currentPosition, position);
|
|
69
156
|
if (textChunk) {
|
|
70
157
|
elements.push(_jsx(Response, { content: textChunk, isStreaming: false, showEmpty: false }, `text-before-${toolCall.id}`));
|
|
71
158
|
}
|
|
72
159
|
}
|
|
73
|
-
//
|
|
74
|
-
|
|
160
|
+
// Check if this tool call should be batched
|
|
161
|
+
if (toolCall.batchId) {
|
|
162
|
+
if (currentBatchId === toolCall.batchId) {
|
|
163
|
+
// Same batch, add to current batch
|
|
164
|
+
currentBatch.push(toolCall);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Different batch, flush previous and start new
|
|
168
|
+
flushBatch();
|
|
169
|
+
currentBatchId = toolCall.batchId;
|
|
170
|
+
currentBatch = [toolCall];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// No batch ID, flush previous and render individually
|
|
175
|
+
flushBatch();
|
|
176
|
+
elements.push(_jsx("div", { children: _jsx(ToolCall, { toolCall: toolCall }) }, `tool-${toolCall.id}`));
|
|
177
|
+
}
|
|
75
178
|
currentPosition = position;
|
|
179
|
+
// Flush batch at the end
|
|
180
|
+
if (index === nonPreliminaryToolCalls.length - 1) {
|
|
181
|
+
flushBatch();
|
|
182
|
+
}
|
|
76
183
|
});
|
|
77
|
-
// Add remaining text after the last tool call
|
|
184
|
+
// Add remaining text after the last non-preliminary tool call
|
|
78
185
|
if (currentPosition < message.content.length) {
|
|
79
186
|
const remainingText = message.content.slice(currentPosition);
|
|
80
187
|
if (remainingText) {
|
|
81
188
|
elements.push(_jsx(Response, { content: remainingText, isStreaming: message.isStreaming, showEmpty: false }, "text-end"));
|
|
82
189
|
}
|
|
83
190
|
}
|
|
191
|
+
// Render preliminary (invoking) tool calls at the end, grouped
|
|
192
|
+
if (preliminaryToolCalls.length > 0) {
|
|
193
|
+
if (preliminaryToolCalls.length > 1) {
|
|
194
|
+
elements.push(_jsx(InvokingGroup, { toolCalls: preliminaryToolCalls }, `invoking-group-${preliminaryToolCalls[0].id}`));
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
elements.push(_jsx("div", { children: _jsx(ToolCall, { toolCall: preliminaryToolCalls[0] }) }, `tool-${preliminaryToolCalls[0].id}`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
84
200
|
return _jsx(_Fragment, { children: elements });
|
|
85
|
-
})()) : (_jsx("div", { className: "whitespace-pre-wrap", children: message.content }))] }));
|
|
201
|
+
})()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: message.images.map((image, index) => (_jsx("img", { src: `data:${image.mimeType};base64,${image.data}`, alt: `Attachment ${index + 1}`, className: "max-w-[200px] max-h-[200px] rounded-lg object-cover" }, index))) })), message.content && (_jsx("div", { className: "whitespace-pre-wrap", children: message.content }))] }))] }));
|
|
86
202
|
}
|
|
87
203
|
return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
|
|
88
204
|
});
|
|
@@ -8,7 +8,7 @@ export interface PanelTabsHeaderProps extends Omit<React.ComponentPropsWithoutRe
|
|
|
8
8
|
/**
|
|
9
9
|
* Which tabs to show
|
|
10
10
|
*/
|
|
11
|
-
visibleTabs?: ("todo" | "files" | "database" | "sources")[];
|
|
11
|
+
visibleTabs?: ("todo" | "files" | "database" | "sources" | "settings")[];
|
|
12
12
|
/**
|
|
13
13
|
* Styling variant
|
|
14
14
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CheckSquare, Database, FileText, Link2 } from "lucide-react";
|
|
2
|
+
import { CheckSquare, Database, FileText, Link2, Settings } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { TabsList, TabsTrigger } from "./Tabs.js";
|
|
@@ -25,6 +25,11 @@ export const PanelTabsHeader = React.forwardRef(({ showIcons = true, visibleTabs
|
|
|
25
25
|
label: "Sources",
|
|
26
26
|
icon: Link2,
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
id: "settings",
|
|
30
|
+
label: "Settings",
|
|
31
|
+
icon: Settings,
|
|
32
|
+
},
|
|
28
33
|
];
|
|
29
34
|
const tabs = allTabs.filter((tab) => visibleTabs.includes(tab.id));
|
|
30
35
|
const gap = variant === "compact" ? "gap-[4px]" : "gap-3";
|
|
@@ -91,6 +91,8 @@ export const Response = React.forwardRef(({ content, isStreaming = false, showEm
|
|
|
91
91
|
blockquote: ({ node, ...props }) => (_jsx("blockquote", { className: "border-l-4 border-[primary] pl-4 italic my-4 text-foreground bg-card py-2 rounded-r-md shadow-sm", ...props })),
|
|
92
92
|
// Horizontal rule
|
|
93
93
|
hr: ({ node, ...props }) => (_jsx("hr", { className: "my-6 border-t border-border opacity-50", ...props })),
|
|
94
|
+
// Image styling
|
|
95
|
+
img: ({ node, ...props }) => (_jsx("img", { className: "max-w-full h-auto rounded-md border border-border my-4", loading: "lazy", ...props })),
|
|
94
96
|
};
|
|
95
97
|
return (_jsx("div", { ref: ref, className: cn("markdown-content prose prose-sm max-w-none dark:prose-invert", className), ...props, children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: components, children: content }) }));
|
|
96
98
|
});
|
|
@@ -30,8 +30,8 @@ export const Task = React.forwardRef(({ task, collapsible = true, defaultExpande
|
|
|
30
30
|
};
|
|
31
31
|
return (_jsxs("div", { ref: ref, className: cn("rounded-lg border border-border bg-card transition-all", "hover:shadow-sm hover:border-border/80", className), ...props, children: [_jsxs("button", { type: "button", onClick: handleClick, className: cn("w-full flex items-center gap-3 px-3 py-2.5 text-left", hasDetails && collapsible && "cursor-pointer"), disabled: !hasDetails && !collapsible, children: [_jsx(StatusIcon, { className: cn("w-4 h-4 shrink-0", getStatusColor(), task.status === "in_progress" && "animate-spin") }), _jsx("span", { className: cn("flex-1 text-paragraph-sm font-[var(--font-family)]", task.status === "completed" && "line-through opacity-60", task.status === "in_progress" && "font-medium"), children: task.text }), hasDetails && collapsible && (_jsx(ChevronDown, { className: cn("w-4 h-4 text-foreground opacity-50 transition-transform duration-200 shrink-0", isExpanded && "rotate-180"), "aria-hidden": "true" }))] }), hasDetails && isExpanded && (_jsxs("div", { className: "px-3 pb-3 pt-1 border-t border-border/50 animate-fadeIn", children: [task.details && (_jsx("p", { className: "text-paragraph-sm text-foreground opacity-80 leading-relaxed mb-2", children: task.details })), task.files && task.files.length > 0 && (_jsxs("div", { className: "space-y-1", children: [_jsx("span", { className: "text-caption font-medium text-foreground opacity-60 uppercase tracking-wide", children: "Files:" }), _jsx("div", { className: "space-y-1", children: task.files.map((file) => (_jsx("div", { className: "text-caption font-mono text-foreground opacity-70 bg-background px-2 py-1 rounded border border-border/50", children: file }, file))) })] }))] }))] }));
|
|
32
32
|
});
|
|
33
|
-
Task.displayName = "
|
|
34
|
-
export const TaskList = React.forwardRef(({ tasks, collapsible = true, onTaskClick, emptyMessage = "No
|
|
33
|
+
Task.displayName = "Subagent";
|
|
34
|
+
export const TaskList = React.forwardRef(({ tasks, collapsible = true, onTaskClick, emptyMessage = "No subagents yet.", className, ...props }, ref) => {
|
|
35
35
|
return (_jsx("div", { ref: ref, className: cn("space-y-2 max-h-96 overflow-y-auto", className), ...props, children: tasks.length === 0 ? (_jsx("p", { className: "text-paragraph-sm text-foreground opacity-60 italic py-4 text-center", children: emptyMessage })) : (tasks.map((task) => (_jsx(Task, { task: task, collapsible: collapsible, ...(onTaskClick ? { onTaskClick } : {}) }, task.id)))) }));
|
|
36
36
|
});
|
|
37
|
-
TaskList.displayName = "
|
|
37
|
+
TaskList.displayName = "SubagentList";
|
|
@@ -15,7 +15,7 @@ export const TodoListItem = React.forwardRef(({ todo, className, ...props }, ref
|
|
|
15
15
|
// Focus state - matching FileSystemItem
|
|
16
16
|
"focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-border-dark",
|
|
17
17
|
// Selected state (if needed later)
|
|
18
|
-
isSelected && "bg-accent", className), role: "button", tabIndex: 0, ...props, children: [_jsx("div", { className: "shrink-0 size-4 flex items-center justify-center text-foreground", children:
|
|
18
|
+
isSelected && "bg-accent", className), role: "button", tabIndex: 0, ...props, children: [_jsx("div", { className: "shrink-0 size-4 flex items-center justify-center text-foreground", children: todo.status === "completed" ? (_jsx(CircleCheck, { className: "size-4 text-muted-foreground" })) : todo.status === "in_progress" ? (_jsx("div", { className: "size-2.5 rounded-full bg-foreground animate-pulse-scale" })) : (_jsx(Circle, { className: "size-4 text-foreground" })) }), _jsx("p", { className: cn("flex-1 text-foreground",
|
|
19
19
|
// Completed state: strikethrough + muted color
|
|
20
20
|
isCompleted && "line-through text-muted-foreground",
|
|
21
21
|
// In-progress state: medium weight for emphasis
|