@townco/ui 0.1.67 → 0.1.69
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/use-chat-messages.d.ts +5 -0
- package/dist/core/hooks/use-tool-calls.d.ts +5 -0
- package/dist/core/hooks/use-tool-calls.js +2 -1
- package/dist/core/schemas/chat.d.ts +10 -0
- package/dist/core/schemas/tool-call.d.ts +96 -0
- package/dist/core/schemas/tool-call.js +12 -0
- package/dist/core/utils/tool-call-state.d.ts +30 -0
- package/dist/core/utils/tool-call-state.js +73 -0
- package/dist/core/utils/tool-summary.d.ts +13 -0
- package/dist/core/utils/tool-summary.js +172 -0
- package/dist/core/utils/tool-verbiage.d.ts +28 -0
- package/dist/core/utils/tool-verbiage.js +185 -0
- package/dist/gui/components/AppSidebar.d.ts +22 -0
- package/dist/gui/components/AppSidebar.js +22 -0
- package/dist/gui/components/ChatLayout.d.ts +5 -0
- package/dist/gui/components/ChatLayout.js +239 -132
- package/dist/gui/components/ChatView.js +42 -118
- package/dist/gui/components/MessageContent.js +199 -49
- package/dist/gui/components/SessionHistory.d.ts +10 -0
- package/dist/gui/components/SessionHistory.js +101 -0
- package/dist/gui/components/SessionHistoryItem.d.ts +11 -0
- package/dist/gui/components/SessionHistoryItem.js +24 -0
- package/dist/gui/components/Sheet.d.ts +25 -0
- package/dist/gui/components/Sheet.js +36 -0
- package/dist/gui/components/Sidebar.d.ts +65 -0
- package/dist/gui/components/Sidebar.js +231 -0
- package/dist/gui/components/SidebarToggle.d.ts +3 -0
- package/dist/gui/components/SidebarToggle.js +9 -0
- package/dist/gui/components/SubAgentDetails.d.ts +13 -6
- package/dist/gui/components/SubAgentDetails.js +29 -14
- package/dist/gui/components/ToolCallList.js +3 -3
- package/dist/gui/components/ToolOperation.d.ts +11 -0
- package/dist/gui/components/ToolOperation.js +289 -0
- package/dist/gui/components/WorkProgress.d.ts +20 -0
- package/dist/gui/components/WorkProgress.js +79 -0
- package/dist/gui/components/index.d.ts +8 -1
- package/dist/gui/components/index.js +9 -1
- package/dist/gui/hooks/index.d.ts +1 -0
- package/dist/gui/hooks/index.js +1 -0
- package/dist/gui/hooks/use-mobile.d.ts +1 -0
- package/dist/gui/hooks/use-mobile.js +15 -0
- package/dist/gui/index.d.ts +1 -0
- package/dist/gui/index.js +2 -0
- package/dist/gui/lib/motion.d.ts +55 -0
- package/dist/gui/lib/motion.js +217 -0
- package/dist/sdk/schemas/session.d.ts +102 -6
- package/dist/sdk/transports/http.js +105 -37
- package/dist/sdk/transports/types.d.ts +5 -0
- package/package.json +8 -7
- package/src/styles/global.css +128 -1
- package/dist/gui/components/InvokingGroup.d.ts +0 -9
- package/dist/gui/components/InvokingGroup.js +0 -16
- package/dist/gui/components/ToolCall.d.ts +0 -8
- package/dist/gui/components/ToolCall.js +0 -226
- package/dist/gui/components/ToolCallGroup.d.ts +0 -8
- package/dist/gui/components/ToolCallGroup.js +0 -29
|
@@ -1,12 +1,11 @@
|
|
|
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, Bug,
|
|
4
|
-
import {
|
|
3
|
+
import { ArrowUp, Bug, ChevronUp, PanelRight, Settings, X } from "lucide-react";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
5
|
import { useChatMessages, useChatSession, useToolCalls, } from "../../core/hooks/index.js";
|
|
6
6
|
import { selectTodosForCurrentSession, useChatStore, } from "../../core/store/chat-store.js";
|
|
7
|
-
import { calculateTokenPercentage, formatTokenPercentage, } from "../../core/utils/model-context.js";
|
|
8
7
|
import { cn } from "../lib/utils.js";
|
|
9
|
-
import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton,
|
|
8
|
+
import { AppSidebar, ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SettingsTabContent, SidebarInset, SidebarProvider, SidebarToggle, SourcesTabContent, Tabs, TabsContent, ThemeToggle, TodoTabContent, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./index.js";
|
|
10
9
|
const logger = createLogger("gui");
|
|
11
10
|
// Helper component to provide openFiles callback
|
|
12
11
|
function OpenFilesButton({ children, }) {
|
|
@@ -31,85 +30,8 @@ function OpenFilesButton({ children, }) {
|
|
|
31
30
|
};
|
|
32
31
|
return _jsx(_Fragment, { children: children({ openFiles, openSettings }) });
|
|
33
32
|
}
|
|
34
|
-
//
|
|
35
|
-
|
|
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
|
-
// Format relative time from date string
|
|
51
|
-
function formatRelativeTime(dateString) {
|
|
52
|
-
const date = new Date(dateString);
|
|
53
|
-
const now = new Date();
|
|
54
|
-
const diffMs = now.getTime() - date.getTime();
|
|
55
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
56
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
57
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
58
|
-
if (diffMins < 1)
|
|
59
|
-
return "Just now";
|
|
60
|
-
if (diffMins < 60)
|
|
61
|
-
return `${diffMins}m ago`;
|
|
62
|
-
if (diffHours < 24)
|
|
63
|
-
return `${diffHours}h ago`;
|
|
64
|
-
if (diffDays < 7)
|
|
65
|
-
return `${diffDays}d ago`;
|
|
66
|
-
return date.toLocaleDateString();
|
|
67
|
-
}
|
|
68
|
-
// Session switcher dropdown component
|
|
69
|
-
function SessionSwitcher({ agentName, client, currentSessionId, }) {
|
|
70
|
-
const [sessions, setSessions] = useState([]);
|
|
71
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
72
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
73
|
-
const fetchSessions = useCallback(async () => {
|
|
74
|
-
if (!client)
|
|
75
|
-
return;
|
|
76
|
-
setIsLoading(true);
|
|
77
|
-
try {
|
|
78
|
-
const sessionList = await client.listSessions();
|
|
79
|
-
setSessions(sessionList);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
logger.error("Failed to fetch sessions", { error });
|
|
83
|
-
}
|
|
84
|
-
finally {
|
|
85
|
-
setIsLoading(false);
|
|
86
|
-
}
|
|
87
|
-
}, [client]);
|
|
88
|
-
// Fetch sessions when dropdown opens
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (isOpen) {
|
|
91
|
-
fetchSessions();
|
|
92
|
-
}
|
|
93
|
-
}, [isOpen, fetchSessions]);
|
|
94
|
-
const handleNewSession = () => {
|
|
95
|
-
// Clear session from URL and reload to start fresh
|
|
96
|
-
const url = new URL(window.location.href);
|
|
97
|
-
url.searchParams.delete("session");
|
|
98
|
-
window.location.href = url.toString();
|
|
99
|
-
};
|
|
100
|
-
const handleSessionSelect = (sessionId) => {
|
|
101
|
-
if (sessionId === currentSessionId) {
|
|
102
|
-
setIsOpen(false);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
// Update URL with session ID and reload
|
|
106
|
-
const url = new URL(window.location.href);
|
|
107
|
-
url.searchParams.set("session", sessionId);
|
|
108
|
-
window.location.href = url.toString();
|
|
109
|
-
};
|
|
110
|
-
return (_jsxs(DropdownMenu, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex items-center gap-1 text-heading-4 text-foreground hover:text-foreground/80 transition-colors cursor-pointer", children: [agentName, _jsx(ChevronDown, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isOpen && "rotate-180") })] }) }), _jsxs(DropdownMenuContent, { align: "start", className: "w-72", children: [_jsxs(DropdownMenuLabel, { className: "flex items-center justify-between", children: [_jsx("span", { children: "Sessions" }), isLoading && (_jsx("span", { className: "text-caption text-muted-foreground", children: "Loading..." }))] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: handleNewSession, className: "gap-2", children: [_jsx(Plus, { className: "size-4" }), _jsx("span", { children: "New Session" })] }), sessions.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsx("div", { className: "max-h-64 overflow-y-auto", children: sessions.map((session) => (_jsxs(DropdownMenuItem, { onClick: () => handleSessionSelect(session.sessionId), className: cn("flex flex-col items-start gap-0.5 py-2", session.sessionId === currentSessionId &&
|
|
111
|
-
"bg-muted/50 font-medium"), children: [_jsxs("div", { className: "flex w-full items-center justify-between gap-2", children: [_jsx("span", { className: "truncate text-paragraph-sm", children: session.firstUserMessage || "Empty session" }), session.sessionId === currentSessionId && (_jsx("span", { className: "shrink-0 text-caption text-primary", children: "Current" }))] }), _jsxs("span", { className: "text-caption text-muted-foreground", children: [formatRelativeTime(session.updatedAt), " \u2022 ", session.messageCount, " ", "messages"] })] }, session.sessionId))) }), sessions.length === 0 && !isLoading && (_jsx("div", { className: "px-2 py-4 text-center text-paragraph-sm text-muted-foreground", children: "No previous sessions" }))] })] }));
|
|
112
|
-
}
|
|
33
|
+
// Note: Keyboard shortcut (Cmd+B / Ctrl+B) for toggling the right panel
|
|
34
|
+
// is now handled internally by ChatLayout.Root
|
|
113
35
|
// Chat input with attachment handling
|
|
114
36
|
function ChatInputWithAttachments({ client, startSession, placeholder, latestContextSize, currentModel, commandMenuItems, }) {
|
|
115
37
|
const attachedFiles = useChatStore((state) => state.input.attachedFiles);
|
|
@@ -128,21 +50,19 @@ function AsideTabs({ todos, tools, mcps, subagents, }) {
|
|
|
128
50
|
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-4 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 ?? [] }) })] }));
|
|
129
51
|
}
|
|
130
52
|
// Mobile header component that uses ChatHeader context
|
|
131
|
-
function MobileHeader({ agentName, showHeader,
|
|
53
|
+
function MobileHeader({ agentName, showHeader, }) {
|
|
132
54
|
const { isExpanded, setIsExpanded } = ChatHeader.useChatHeaderContext();
|
|
133
|
-
return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [
|
|
55
|
+
return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(SidebarToggle, {}), showHeader && (_jsx("span", { className: "text-heading-4 text-foreground", children: agentName }))] }), _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") }) })] }));
|
|
134
56
|
}
|
|
135
57
|
// Header component that uses ChatLayout context (must be inside ChatLayout.Root)
|
|
136
|
-
function AppChatHeader({ agentName, todos, sources, showHeader, sessionId, debuggerUrl, tools, mcps, subagents,
|
|
137
|
-
const {
|
|
58
|
+
function AppChatHeader({ agentName, todos, sources, showHeader, sessionId, debuggerUrl, tools, mcps, subagents, }) {
|
|
59
|
+
const { togglePanel, panelOpen } = ChatLayout.useChatLayoutContext();
|
|
138
60
|
const debuggerLink = debuggerUrl
|
|
139
61
|
? sessionId
|
|
140
62
|
? `${debuggerUrl}/sessions/${sessionId}`
|
|
141
63
|
: debuggerUrl
|
|
142
64
|
: null;
|
|
143
|
-
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: [
|
|
144
|
-
setPanelSize(panelSize === "hidden" ? "small" : "hidden");
|
|
145
|
-
}, children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName, showHeader: showHeader, client: client, currentSessionId: sessionId }), _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 ?? [] }) })] }) })] }));
|
|
65
|
+
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: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(SidebarToggle, {}), showHeader && (_jsx("span", { className: "text-heading-4 text-foreground", children: agentName }))] }), 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: sessionId ? "View session in debugger" : "Open debugger" }) })] }) })), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle panel", onClick: togglePanel, "data-state": panelOpen ? "open" : "closed", 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 ?? [] }) })] }) })] }));
|
|
146
66
|
}
|
|
147
67
|
export function ChatView({ client, initialSessionId, error: initError, debuggerUrl, }) {
|
|
148
68
|
// Use shared hooks from @townco/ui/core - MUST be called before any early returns
|
|
@@ -291,32 +211,36 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
291
211
|
},
|
|
292
212
|
},
|
|
293
213
|
];
|
|
294
|
-
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
214
|
+
return (_jsxs(SidebarProvider, { defaultOpen: false, children: [_jsx(AppSidebar, { client: client, currentSessionId: sessionId }), _jsx(SidebarInset, { children: _jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [!hideTopBar && (_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) => {
|
|
215
|
+
sendMessage(prompt);
|
|
216
|
+
setPlaceholder("Type a message or / for commands...");
|
|
217
|
+
logger.info("Prompt clicked", { prompt });
|
|
218
|
+
}, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles, onOpenSettings: openSettings, toolsAndMcpsCount: agentTools.length +
|
|
219
|
+
agentMcps.length +
|
|
220
|
+
agentSubagents.length }) })) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
|
|
221
|
+
// Calculate dynamic spacing based on message sequence
|
|
222
|
+
const isFirst = index === 0;
|
|
223
|
+
const previousMessage = isFirst
|
|
224
|
+
? null
|
|
225
|
+
: messages[index - 1];
|
|
226
|
+
let spacingClass = "mt-2";
|
|
227
|
+
if (isFirst) {
|
|
228
|
+
// First message needs more top margin if it's an assistant initial message
|
|
229
|
+
spacingClass =
|
|
230
|
+
message.role === "assistant" ? "mt-8" : "mt-2";
|
|
231
|
+
}
|
|
232
|
+
else if (message.role === "user") {
|
|
233
|
+
// User message usually starts a new turn
|
|
234
|
+
spacingClass =
|
|
235
|
+
previousMessage?.role === "user" ? "mt-4" : "mt-4";
|
|
236
|
+
}
|
|
237
|
+
else if (message.role === "assistant") {
|
|
238
|
+
// Assistant message is usually a response
|
|
239
|
+
spacingClass =
|
|
240
|
+
previousMessage?.role === "assistant"
|
|
241
|
+
? "mt-2"
|
|
242
|
+
: "mt-6";
|
|
243
|
+
}
|
|
244
|
+
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
245
|
+
}) })) }), _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 }) }))] }) })] }));
|
|
322
246
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
|
+
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { useChatStore } from "../../core/store/chat-store.js";
|
|
6
|
+
import { isPreliminaryToolCall } from "../../core/utils/tool-call-state.js";
|
|
7
|
+
import { getDuration, getTransition, motionEasing, shimmerTransition, } from "../lib/motion.js";
|
|
5
8
|
import { cn } from "../lib/utils.js";
|
|
6
|
-
import { InvokingGroup } from "./InvokingGroup.js";
|
|
7
9
|
import { Reasoning } from "./Reasoning.js";
|
|
8
10
|
import { Response } from "./Response.js";
|
|
9
|
-
import {
|
|
10
|
-
import { ToolCallGroup } from "./ToolCallGroup.js";
|
|
11
|
+
import { ToolOperation } from "./ToolOperation.js";
|
|
11
12
|
/**
|
|
12
13
|
* MessageContent component inspired by shadcn.io/ai
|
|
13
14
|
* Provides the content container with role-based styling
|
|
@@ -35,6 +36,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
35
36
|
// Get streaming start time and current model from store
|
|
36
37
|
const streamingStartTime = useChatStore((state) => state.streamingStartTime);
|
|
37
38
|
const currentModel = useChatStore((state) => state.currentModel);
|
|
39
|
+
const shouldReduceMotion = useReducedMotion();
|
|
38
40
|
// Use smart rendering if message is provided and no custom children
|
|
39
41
|
const useSmartRendering = message && !children;
|
|
40
42
|
// Derive props from message if using smart rendering
|
|
@@ -49,36 +51,88 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
49
51
|
const hasThinking = !!thinking;
|
|
50
52
|
// Check if waiting (streaming but no content yet)
|
|
51
53
|
const isWaiting = message.isStreaming && !message.content && message.role === "assistant";
|
|
52
|
-
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(
|
|
54
|
+
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(motion.div, { initial: {
|
|
55
|
+
filter: "blur(12px)",
|
|
56
|
+
opacity: 0,
|
|
57
|
+
y: 12,
|
|
58
|
+
}, animate: {
|
|
59
|
+
filter: "blur(0px)",
|
|
60
|
+
opacity: 1,
|
|
61
|
+
y: 0,
|
|
62
|
+
}, exit: {
|
|
63
|
+
filter: "blur(12px)",
|
|
64
|
+
opacity: 0,
|
|
65
|
+
y: -12,
|
|
66
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
67
|
+
duration: 0.5,
|
|
68
|
+
ease: motionEasing.smooth,
|
|
69
|
+
}), children: _jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true }) })), isWaiting && streamingStartTime && (_jsx(motion.div, { initial: {
|
|
70
|
+
filter: "blur(12px)",
|
|
71
|
+
opacity: 0,
|
|
72
|
+
y: 12,
|
|
73
|
+
}, animate: {
|
|
74
|
+
filter: "blur(0px)",
|
|
75
|
+
opacity: 1,
|
|
76
|
+
y: 0,
|
|
77
|
+
}, exit: {
|
|
78
|
+
filter: "blur(12px)",
|
|
79
|
+
opacity: 0,
|
|
80
|
+
y: -12,
|
|
81
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
82
|
+
duration: 0.4,
|
|
83
|
+
ease: motionEasing.smooth,
|
|
84
|
+
}), children: _jsx(motion.div, { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", animate: {
|
|
85
|
+
backgroundPosition: ["-200% 0", "200% 0"],
|
|
86
|
+
}, transition: {
|
|
87
|
+
...shimmerTransition,
|
|
88
|
+
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
89
|
+
}, style: {
|
|
90
|
+
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
91
|
+
backgroundSize: "200% 100%",
|
|
92
|
+
}, children: _jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Thinking..." }) }) }) })), message.role === "assistant" ? ((() => {
|
|
53
93
|
// Sort tool calls by content position
|
|
54
94
|
const sortedToolCalls = (message.toolCalls || [])
|
|
55
95
|
.slice()
|
|
56
96
|
.sort((a, b) => (a.contentPosition ?? Infinity) -
|
|
57
97
|
(b.contentPosition ?? Infinity));
|
|
58
|
-
// Helper to
|
|
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
|
|
98
|
+
// Helper to group tool calls by batchId, consecutive same-title calls, or consecutive preliminary calls
|
|
62
99
|
const groupToolCalls = (toolCalls) => {
|
|
63
100
|
const result = [];
|
|
64
101
|
const batchGroups = new Map();
|
|
65
|
-
let
|
|
66
|
-
|
|
67
|
-
|
|
102
|
+
let currentSelectingGroup = [];
|
|
103
|
+
let currentConsecutiveGroup = [];
|
|
104
|
+
let currentConsecutiveTitle = null;
|
|
105
|
+
const flushSelectingGroup = () => {
|
|
106
|
+
if (currentSelectingGroup.length > 1) {
|
|
68
107
|
result.push({
|
|
69
|
-
type: "
|
|
70
|
-
toolCalls:
|
|
108
|
+
type: "selecting",
|
|
109
|
+
toolCalls: currentSelectingGroup,
|
|
71
110
|
});
|
|
72
111
|
}
|
|
73
|
-
else if (
|
|
74
|
-
result.push(
|
|
112
|
+
else if (currentSelectingGroup.length === 1) {
|
|
113
|
+
result.push(currentSelectingGroup[0]);
|
|
75
114
|
}
|
|
76
|
-
|
|
115
|
+
currentSelectingGroup = [];
|
|
116
|
+
};
|
|
117
|
+
const flushConsecutiveGroup = () => {
|
|
118
|
+
if (currentConsecutiveGroup.length > 1) {
|
|
119
|
+
// Multiple consecutive same-title calls - group them
|
|
120
|
+
result.push({
|
|
121
|
+
type: "batch",
|
|
122
|
+
toolCalls: currentConsecutiveGroup,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else if (currentConsecutiveGroup.length === 1) {
|
|
126
|
+
result.push(currentConsecutiveGroup[0]);
|
|
127
|
+
}
|
|
128
|
+
currentConsecutiveGroup = [];
|
|
129
|
+
currentConsecutiveTitle = null;
|
|
77
130
|
};
|
|
78
131
|
for (const tc of toolCalls) {
|
|
79
|
-
// Handle batch groups
|
|
132
|
+
// Handle batch groups (explicit batchId)
|
|
80
133
|
if (tc.batchId) {
|
|
81
|
-
|
|
134
|
+
flushSelectingGroup();
|
|
135
|
+
flushConsecutiveGroup();
|
|
82
136
|
const existing = batchGroups.get(tc.batchId);
|
|
83
137
|
if (existing) {
|
|
84
138
|
existing.push(tc);
|
|
@@ -89,18 +143,29 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
89
143
|
result.push({ type: "batch", toolCalls: group });
|
|
90
144
|
}
|
|
91
145
|
}
|
|
92
|
-
// Handle consecutive preliminary (
|
|
93
|
-
else if (
|
|
94
|
-
|
|
146
|
+
// Handle consecutive preliminary (selecting) tool calls
|
|
147
|
+
else if (isPreliminaryToolCall(tc)) {
|
|
148
|
+
flushConsecutiveGroup();
|
|
149
|
+
currentSelectingGroup.push(tc);
|
|
95
150
|
}
|
|
96
|
-
// Regular tool call
|
|
151
|
+
// Regular tool call - group consecutive same-title calls (e.g., subagent)
|
|
97
152
|
else {
|
|
98
|
-
|
|
99
|
-
|
|
153
|
+
flushSelectingGroup();
|
|
154
|
+
// Check if this continues a consecutive group
|
|
155
|
+
if (currentConsecutiveTitle === tc.title) {
|
|
156
|
+
currentConsecutiveGroup.push(tc);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Different title - flush previous group and start new one
|
|
160
|
+
flushConsecutiveGroup();
|
|
161
|
+
currentConsecutiveGroup = [tc];
|
|
162
|
+
currentConsecutiveTitle = tc.title;
|
|
163
|
+
}
|
|
100
164
|
}
|
|
101
165
|
}
|
|
102
|
-
// Flush any remaining
|
|
103
|
-
|
|
166
|
+
// Flush any remaining groups
|
|
167
|
+
flushSelectingGroup();
|
|
168
|
+
flushConsecutiveGroup();
|
|
104
169
|
return result;
|
|
105
170
|
};
|
|
106
171
|
// Helper to render a tool call or group
|
|
@@ -109,42 +174,60 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
109
174
|
if (typeof item === "object" &&
|
|
110
175
|
"type" in item &&
|
|
111
176
|
item.type === "batch") {
|
|
112
|
-
return (_jsx(
|
|
177
|
+
return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `batch-${item.toolCalls[0]?.batchId || index}`));
|
|
113
178
|
}
|
|
114
|
-
//
|
|
179
|
+
// Selecting group (consecutive preliminary tool calls)
|
|
115
180
|
if (typeof item === "object" &&
|
|
116
181
|
"type" in item &&
|
|
117
|
-
item.type === "
|
|
118
|
-
return (_jsx(
|
|
182
|
+
item.type === "selecting") {
|
|
183
|
+
return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `selecting-${item.toolCalls[0]?.id || index}`));
|
|
119
184
|
}
|
|
120
185
|
// Single tool call
|
|
121
|
-
return (_jsx(
|
|
186
|
+
return (_jsx(ToolOperation, { toolCalls: [item], isGrouped: false }, item.id));
|
|
122
187
|
};
|
|
123
|
-
// If no tool calls or they don't have positions, render
|
|
188
|
+
// If no tool calls or they don't have positions, render simplified way
|
|
124
189
|
if (sortedToolCalls.length === 0 ||
|
|
125
190
|
!sortedToolCalls.some((tc) => tc.contentPosition !== undefined)) {
|
|
126
191
|
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(
|
|
192
|
+
return (_jsxs(_Fragment, { children: [groupedToolCalls.length > 0 && (_jsx(AnimatePresence, { mode: "popLayout", children: _jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) }) })), _jsx(motion.div, { initial: {
|
|
193
|
+
filter: "blur(12px)",
|
|
194
|
+
opacity: 0,
|
|
195
|
+
y: 12,
|
|
196
|
+
}, animate: {
|
|
197
|
+
filter: "blur(0px)",
|
|
198
|
+
opacity: 1,
|
|
199
|
+
y: 0,
|
|
200
|
+
}, exit: {
|
|
201
|
+
filter: "blur(12px)",
|
|
202
|
+
opacity: 0,
|
|
203
|
+
y: -12,
|
|
204
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
205
|
+
duration: 0.4,
|
|
206
|
+
ease: motionEasing.smooth,
|
|
207
|
+
}), children: _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false }) })] }));
|
|
128
208
|
}
|
|
129
209
|
// Render content interleaved with tool calls
|
|
130
|
-
// Group consecutive tool calls with the same batchId
|
|
210
|
+
// Group consecutive tool calls with the same batchId or same title
|
|
131
211
|
const elements = [];
|
|
132
212
|
let currentPosition = 0;
|
|
133
213
|
let currentBatch = [];
|
|
134
214
|
let currentBatchId;
|
|
215
|
+
let currentBatchTitle;
|
|
135
216
|
const flushBatch = () => {
|
|
136
|
-
if (currentBatch.length > 1
|
|
137
|
-
|
|
217
|
+
if (currentBatch.length > 1) {
|
|
218
|
+
// Group multiple consecutive calls (by batchId or same title)
|
|
219
|
+
elements.push(_jsx(ToolOperation, { toolCalls: currentBatch, isGrouped: true }, `group-${currentBatchId || currentBatchTitle}-${currentBatch[0].id}`));
|
|
138
220
|
}
|
|
139
221
|
else if (currentBatch.length === 1) {
|
|
140
|
-
elements.push(_jsx("div", { children: _jsx(
|
|
222
|
+
elements.push(_jsx("div", { children: _jsx(ToolOperation, { toolCalls: [currentBatch[0]], isGrouped: false }) }, `tool-${currentBatch[0].id}`));
|
|
141
223
|
}
|
|
142
224
|
currentBatch = [];
|
|
143
225
|
currentBatchId = undefined;
|
|
226
|
+
currentBatchTitle = undefined;
|
|
144
227
|
};
|
|
145
228
|
// Separate preliminary tool calls - they should render at the end, not break text
|
|
146
|
-
const preliminaryToolCalls = sortedToolCalls.filter(
|
|
147
|
-
const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !
|
|
229
|
+
const preliminaryToolCalls = sortedToolCalls.filter(isPreliminaryToolCall);
|
|
230
|
+
const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !isPreliminaryToolCall(tc));
|
|
148
231
|
// Process non-preliminary tool calls inline with text
|
|
149
232
|
nonPreliminaryToolCalls.forEach((toolCall, index) => {
|
|
150
233
|
const position = toolCall.contentPosition ?? message.content.length;
|
|
@@ -154,10 +237,25 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
154
237
|
flushBatch();
|
|
155
238
|
const textChunk = message.content.slice(currentPosition, position);
|
|
156
239
|
if (textChunk) {
|
|
157
|
-
elements.push(_jsx(
|
|
240
|
+
elements.push(_jsx(motion.div, { initial: {
|
|
241
|
+
filter: "blur(12px)",
|
|
242
|
+
opacity: 0,
|
|
243
|
+
y: 12,
|
|
244
|
+
}, animate: {
|
|
245
|
+
filter: "blur(0px)",
|
|
246
|
+
opacity: 1,
|
|
247
|
+
y: 0,
|
|
248
|
+
}, exit: {
|
|
249
|
+
filter: "blur(12px)",
|
|
250
|
+
opacity: 0,
|
|
251
|
+
y: -12,
|
|
252
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
253
|
+
duration: 0.4,
|
|
254
|
+
ease: motionEasing.smooth,
|
|
255
|
+
}), children: _jsx(Response, { content: textChunk, isStreaming: false, showEmpty: false }) }, `text-before-${toolCall.id}`));
|
|
158
256
|
}
|
|
159
257
|
}
|
|
160
|
-
// Check if this tool call should be batched
|
|
258
|
+
// Check if this tool call should be batched (by batchId or consecutive same title)
|
|
161
259
|
if (toolCall.batchId) {
|
|
162
260
|
if (currentBatchId === toolCall.batchId) {
|
|
163
261
|
// Same batch, add to current batch
|
|
@@ -167,13 +265,20 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
167
265
|
// Different batch, flush previous and start new
|
|
168
266
|
flushBatch();
|
|
169
267
|
currentBatchId = toolCall.batchId;
|
|
268
|
+
currentBatchTitle = toolCall.title;
|
|
170
269
|
currentBatch = [toolCall];
|
|
171
270
|
}
|
|
172
271
|
}
|
|
272
|
+
else if (currentBatchTitle === toolCall.title &&
|
|
273
|
+
!currentBatchId) {
|
|
274
|
+
// Same title as previous (no batchId), continue grouping
|
|
275
|
+
currentBatch.push(toolCall);
|
|
276
|
+
}
|
|
173
277
|
else {
|
|
174
|
-
//
|
|
278
|
+
// Different title or switching from batchId to title grouping
|
|
175
279
|
flushBatch();
|
|
176
|
-
|
|
280
|
+
currentBatchTitle = toolCall.title;
|
|
281
|
+
currentBatch = [toolCall];
|
|
177
282
|
}
|
|
178
283
|
currentPosition = position;
|
|
179
284
|
// Flush batch at the end
|
|
@@ -185,20 +290,65 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
185
290
|
if (currentPosition < message.content.length) {
|
|
186
291
|
const remainingText = message.content.slice(currentPosition);
|
|
187
292
|
if (remainingText) {
|
|
188
|
-
elements.push(_jsx(
|
|
293
|
+
elements.push(_jsx(motion.div, { initial: {
|
|
294
|
+
filter: "blur(12px)",
|
|
295
|
+
opacity: 0,
|
|
296
|
+
y: 12,
|
|
297
|
+
}, animate: {
|
|
298
|
+
filter: "blur(0px)",
|
|
299
|
+
opacity: 1,
|
|
300
|
+
y: 0,
|
|
301
|
+
}, exit: {
|
|
302
|
+
filter: "blur(12px)",
|
|
303
|
+
opacity: 0,
|
|
304
|
+
y: -12,
|
|
305
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
306
|
+
duration: 0.4,
|
|
307
|
+
ease: motionEasing.smooth,
|
|
308
|
+
}), children: _jsx(Response, { content: remainingText, isStreaming: message.isStreaming, showEmpty: false }) }, "text-end"));
|
|
189
309
|
}
|
|
190
310
|
}
|
|
191
|
-
// Render preliminary (
|
|
311
|
+
// Render preliminary (selecting) tool calls at the end, grouped
|
|
192
312
|
if (preliminaryToolCalls.length > 0) {
|
|
193
313
|
if (preliminaryToolCalls.length > 1) {
|
|
194
|
-
elements.push(_jsx(
|
|
314
|
+
elements.push(_jsx(ToolOperation, { toolCalls: preliminaryToolCalls, isGrouped: true }, `selecting-group-${preliminaryToolCalls[0].id}`));
|
|
195
315
|
}
|
|
196
316
|
else {
|
|
197
|
-
elements.push(_jsx("div", { children: _jsx(
|
|
317
|
+
elements.push(_jsx("div", { children: _jsx(ToolOperation, { toolCalls: [preliminaryToolCalls[0]], isGrouped: false }) }, `tool-${preliminaryToolCalls[0].id}`));
|
|
198
318
|
}
|
|
199
319
|
}
|
|
200
|
-
return _jsx(
|
|
201
|
-
})()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx(
|
|
320
|
+
return (_jsx(AnimatePresence, { mode: "popLayout", children: elements }));
|
|
321
|
+
})()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx(motion.div, { className: "flex flex-wrap gap-2", initial: {
|
|
322
|
+
filter: "blur(12px)",
|
|
323
|
+
opacity: 0,
|
|
324
|
+
y: 12,
|
|
325
|
+
}, animate: {
|
|
326
|
+
filter: "blur(0px)",
|
|
327
|
+
opacity: 1,
|
|
328
|
+
y: 0,
|
|
329
|
+
}, exit: {
|
|
330
|
+
filter: "blur(12px)",
|
|
331
|
+
opacity: 0,
|
|
332
|
+
y: -12,
|
|
333
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
334
|
+
duration: 0.5,
|
|
335
|
+
ease: motionEasing.smooth,
|
|
336
|
+
}), 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(motion.div, { className: "whitespace-pre-wrap", initial: {
|
|
337
|
+
filter: "blur(12px)",
|
|
338
|
+
opacity: 0,
|
|
339
|
+
y: 12,
|
|
340
|
+
}, animate: {
|
|
341
|
+
filter: "blur(0px)",
|
|
342
|
+
opacity: 1,
|
|
343
|
+
y: 0,
|
|
344
|
+
}, exit: {
|
|
345
|
+
filter: "blur(12px)",
|
|
346
|
+
opacity: 0,
|
|
347
|
+
y: -12,
|
|
348
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
349
|
+
duration: 0.4,
|
|
350
|
+
ease: motionEasing.smooth,
|
|
351
|
+
}), children: message.content }))] }))] }));
|
|
202
352
|
}
|
|
203
353
|
return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
|
|
204
354
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AcpClient } from "../../sdk/client/index.js";
|
|
2
|
+
export interface SessionHistoryProps {
|
|
3
|
+
client: AcpClient | null;
|
|
4
|
+
currentSessionId: string | null;
|
|
5
|
+
onSessionSelect?: ((sessionId: string) => void) | undefined;
|
|
6
|
+
onRenameSession?: ((sessionId: string) => void) | undefined;
|
|
7
|
+
onArchiveSession?: ((sessionId: string) => void) | undefined;
|
|
8
|
+
onDeleteSession?: ((sessionId: string) => void) | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare function SessionHistory({ client, currentSessionId, onSessionSelect, onRenameSession, onArchiveSession, onDeleteSession, }: SessionHistoryProps): import("react/jsx-runtime").JSX.Element;
|