@townco/ui 0.1.36 → 0.1.38
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-input.d.ts +1 -1
- package/dist/core/hooks/use-chat-input.js +2 -2
- package/dist/core/hooks/use-chat-messages.d.ts +3 -1
- package/dist/core/hooks/use-chat-messages.js +36 -7
- package/dist/core/hooks/use-chat-session.d.ts +1 -1
- package/dist/core/hooks/use-chat-session.js +4 -7
- package/dist/core/hooks/use-tool-calls.d.ts +2 -0
- package/dist/core/lib/logger.d.ts +24 -0
- package/dist/core/lib/logger.js +108 -0
- package/dist/core/schemas/chat.d.ts +4 -0
- package/dist/core/schemas/tool-call.d.ts +2 -0
- package/dist/core/schemas/tool-call.js +4 -0
- package/dist/gui/components/ChatEmptyState.d.ts +6 -0
- package/dist/gui/components/ChatEmptyState.js +2 -2
- package/dist/gui/components/ChatInput.d.ts +4 -0
- package/dist/gui/components/ChatInput.js +12 -7
- package/dist/gui/components/ChatLayout.js +102 -8
- package/dist/gui/components/ChatPanelTabContent.d.ts +1 -1
- package/dist/gui/components/ChatPanelTabContent.js +10 -3
- package/dist/gui/components/ChatView.js +45 -14
- package/dist/gui/components/ContextUsageButton.d.ts +7 -0
- package/dist/gui/components/ContextUsageButton.js +18 -0
- package/dist/gui/components/FileSystemItem.js +6 -7
- package/dist/gui/components/FileSystemView.js +3 -3
- package/dist/gui/components/InlineToolCallSummary.d.ts +14 -0
- package/dist/gui/components/InlineToolCallSummary.js +110 -0
- package/dist/gui/components/InlineToolCallSummaryACP.d.ts +15 -0
- package/dist/gui/components/InlineToolCallSummaryACP.js +90 -0
- package/dist/gui/components/MessageContent.js +1 -1
- package/dist/gui/components/Response.js +2 -2
- package/dist/gui/components/SourceListItem.js +9 -1
- package/dist/gui/components/TodoListItem.js +19 -2
- package/dist/gui/components/ToolCall.js +21 -2
- package/dist/gui/components/Tooltip.d.ts +7 -0
- package/dist/gui/components/Tooltip.js +10 -0
- package/dist/gui/components/index.d.ts +4 -0
- package/dist/gui/components/index.js +5 -0
- package/dist/gui/components/tool-call-summary.d.ts +44 -0
- package/dist/gui/components/tool-call-summary.js +67 -0
- package/dist/gui/data/mockSourceData.d.ts +10 -0
- package/dist/gui/data/mockSourceData.js +40 -0
- package/dist/gui/data/mockTodoData.d.ts +10 -0
- package/dist/gui/data/mockTodoData.js +35 -0
- package/dist/gui/examples/FileSystemDemo.d.ts +5 -0
- package/dist/gui/examples/FileSystemDemo.js +24 -0
- package/dist/gui/examples/FileSystemExample.d.ts +17 -0
- package/dist/gui/examples/FileSystemExample.js +94 -0
- package/dist/sdk/schemas/session.d.ts +2 -0
- package/dist/sdk/transports/http.d.ts +1 -0
- package/dist/sdk/transports/http.js +40 -8
- package/dist/sdk/transports/stdio.js +13 -0
- package/dist/tui/components/ChatView.js +5 -5
- package/package.json +3 -3
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { mockSourceData } from "../data/mockSourceData.js";
|
|
4
|
+
import { mockTodoData } from "../data/mockTodoData.js";
|
|
3
5
|
import { cn } from "../lib/utils.js";
|
|
4
6
|
import { FileSystemView } from "./FileSystemView.js";
|
|
5
7
|
import { SourceListItem } from "./SourceListItem.js";
|
|
8
|
+
import { TodoList } from "./TodoList.js";
|
|
6
9
|
export const TodoTabContent = React.forwardRef(({ todos, className, ...props }, ref) => {
|
|
7
|
-
|
|
10
|
+
// Use mock data if no todos provided or if empty array
|
|
11
|
+
const displayTodos = todos && todos.length > 0 ? todos : mockTodoData;
|
|
12
|
+
return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: _jsx(TodoList, { todos: displayTodos }) }));
|
|
8
13
|
});
|
|
9
14
|
TodoTabContent.displayName = "TodoTabContent";
|
|
10
15
|
export const FilesTabContent = React.forwardRef(({ files = [], provider, onFileSelect, className, ...props }, ref) => {
|
|
@@ -29,8 +34,10 @@ export const FilesTabContent = React.forwardRef(({ files = [], provider, onFileS
|
|
|
29
34
|
}, onDownload: handleDownload, onRename: handleRename, onDelete: handleDelete, className: "h-full" }) }));
|
|
30
35
|
});
|
|
31
36
|
FilesTabContent.displayName = "FilesTabContent";
|
|
32
|
-
export const SourcesTabContent = React.forwardRef(({ sources
|
|
33
|
-
|
|
37
|
+
export const SourcesTabContent = React.forwardRef(({ sources, className, ...props }, ref) => {
|
|
38
|
+
// Use mock data if no sources provided or if empty array
|
|
39
|
+
const displaySources = sources && sources.length > 0 ? sources : mockSourceData;
|
|
40
|
+
return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: displaySources.map((source) => (_jsx(SourceListItem, { source: source }, source.id))) }));
|
|
34
41
|
});
|
|
35
42
|
SourcesTabContent.displayName = "SourcesTabContent";
|
|
36
43
|
export const DatabaseTabContent = React.forwardRef(({ data, className, ...props }, ref) => {
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createLogger } from "@townco/core";
|
|
3
3
|
import { ArrowUp, ChevronUp, Code, PanelRight, Settings, Sparkles, } from "lucide-react";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { useChatMessages, useChatSession, useToolCalls, } from "../../core/hooks/index.js";
|
|
6
6
|
import { useChatStore } from "../../core/store/chat-store.js";
|
|
7
|
+
import { calculateTokenPercentage, formatTokenPercentage, } from "../../core/utils/model-context.js";
|
|
7
8
|
import { cn } from "../lib/utils.js";
|
|
8
|
-
import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, FilesTabContent, Message, MessageContent, PanelTabsHeader, SourcesTabContent, Tabs, TabsContent, TodoTabContent, } from "./index.js";
|
|
9
|
+
import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, Message, MessageContent, PanelTabsHeader, SourcesTabContent, Tabs, TabsContent, TodoTabContent, } from "./index.js";
|
|
9
10
|
const logger = createLogger("gui");
|
|
11
|
+
// Helper component to provide openFiles callback
|
|
12
|
+
function OpenFilesButton({ children, }) {
|
|
13
|
+
const { setPanelSize, setActiveTab } = ChatLayout.useChatLayoutContext();
|
|
14
|
+
const openFiles = () => {
|
|
15
|
+
setPanelSize("small");
|
|
16
|
+
setActiveTab("files");
|
|
17
|
+
};
|
|
18
|
+
return _jsx(_Fragment, { children: children({ openFiles }) });
|
|
19
|
+
}
|
|
20
|
+
// Controlled Tabs component for the aside panel
|
|
21
|
+
function AsideTabs() {
|
|
22
|
+
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, {}) })] }));
|
|
24
|
+
}
|
|
10
25
|
// Mobile header component that uses ChatHeader context
|
|
11
26
|
function MobileHeader({ agentName, showHeader, }) {
|
|
12
27
|
const { isExpanded, setIsExpanded } = ChatHeader.useChatHeaderContext();
|
|
@@ -21,12 +36,14 @@ function AppChatHeader({ agentName, todos, sources, showHeader, }) {
|
|
|
21
36
|
}
|
|
22
37
|
export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
23
38
|
// Use shared hooks from @townco/ui/core - MUST be called before any early returns
|
|
24
|
-
const { connectionStatus, connect, sessionId } = useChatSession(client, initialSessionId);
|
|
25
|
-
const { messages, sendMessage } = useChatMessages(client);
|
|
39
|
+
const { connectionStatus, connect, sessionId, startSession } = useChatSession(client, initialSessionId);
|
|
40
|
+
const { messages, sendMessage } = useChatMessages(client, startSession);
|
|
26
41
|
useToolCalls(client); // Still need to subscribe to tool call events
|
|
27
42
|
const error = useChatStore((state) => state.error);
|
|
43
|
+
const currentModel = useChatStore((state) => state.currentModel);
|
|
28
44
|
const [agentName, setAgentName] = useState("Agent");
|
|
29
45
|
const [isLargeScreen, setIsLargeScreen] = useState(typeof window !== "undefined" ? window.innerWidth >= 1024 : true);
|
|
46
|
+
const [placeholder, setPlaceholder] = useState("Type a message or / for commands...");
|
|
30
47
|
// Log connection status changes
|
|
31
48
|
useEffect(() => {
|
|
32
49
|
logger.debug("Connection status changed", { status: connectionStatus });
|
|
@@ -57,6 +74,14 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
57
74
|
mediaQuery.removeEventListener("change", handleChange);
|
|
58
75
|
};
|
|
59
76
|
}, []);
|
|
77
|
+
// Handle prompt hover - temporarily show the full prompt as placeholder
|
|
78
|
+
const handlePromptHover = (prompt) => {
|
|
79
|
+
setPlaceholder(prompt);
|
|
80
|
+
};
|
|
81
|
+
// Handle prompt leave - restore the default placeholder
|
|
82
|
+
const handlePromptLeave = () => {
|
|
83
|
+
setPlaceholder("Type a message or / for commands...");
|
|
84
|
+
};
|
|
60
85
|
// If there's an initialization error, show error UI (after all hooks have been called)
|
|
61
86
|
if (initError) {
|
|
62
87
|
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." })] }) }));
|
|
@@ -98,6 +123,11 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
98
123
|
favicon: "https://www.google.com/s2/favicons?domain=theverge.com&sz=32",
|
|
99
124
|
},
|
|
100
125
|
];
|
|
126
|
+
// Get the latest token usage from the most recent assistant message
|
|
127
|
+
const latestTokenUsage = messages
|
|
128
|
+
.slice()
|
|
129
|
+
.reverse()
|
|
130
|
+
.find((msg) => msg.role === "assistant" && msg.tokenUsage)?.tokenUsage;
|
|
101
131
|
// Command menu items for chat input
|
|
102
132
|
const commandMenuItems = [
|
|
103
133
|
{
|
|
@@ -141,15 +171,16 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
141
171
|
},
|
|
142
172
|
},
|
|
143
173
|
];
|
|
144
|
-
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("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: "This agent can help you with your tasks. Start a conversation by typing a message below.", suggestedPrompts: [
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
174
|
+
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: "This agent can help you with your tasks. Start a conversation by typing a message below.", suggestedPrompts: [
|
|
175
|
+
"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",
|
|
176
|
+
"Explain how this works",
|
|
177
|
+
"Create a new feature",
|
|
178
|
+
"Review my changes",
|
|
179
|
+
], onPromptClick: (prompt) => {
|
|
180
|
+
sendMessage(prompt);
|
|
181
|
+
setPlaceholder("Type a message or / for commands...");
|
|
182
|
+
logger.info("Prompt clicked", { prompt });
|
|
183
|
+
}, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles }) })) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
|
|
153
184
|
// Calculate dynamic spacing based on message sequence
|
|
154
185
|
const isFirst = index === 0;
|
|
155
186
|
const previousMessage = isFirst ? null : messages[index - 1];
|
|
@@ -168,5 +199,5 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
168
199
|
previousMessage?.role === "assistant" ? "mt-2" : "mt-6";
|
|
169
200
|
}
|
|
170
201
|
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
171
|
-
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder:
|
|
202
|
+
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, {}), latestTokenUsage && (_jsx(ContextUsageButton, { percentage: calculateTokenPercentage(latestTokenUsage.totalTokens ?? 0, currentModel ?? undefined), tokens: latestTokenUsage.totalTokens ?? 0, formattedPercentage: formatTokenPercentage(latestTokenUsage.totalTokens ?? 0, currentModel ?? undefined) }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputVoiceInput, {}), _jsx(ChatInputSubmit, { children: _jsx(ArrowUp, { className: "size-4" }) })] })] })] }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsx(AsideTabs, {}) }))] }));
|
|
172
203
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ContextUsageButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
3
|
+
percentage: number;
|
|
4
|
+
tokens: number;
|
|
5
|
+
formattedPercentage: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const ContextUsageButton: React.ForwardRefExoticComponent<ContextUsageButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { Button } from "./Button.js";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
|
|
6
|
+
export const ContextUsageButton = React.forwardRef(({ percentage, tokens, formattedPercentage, className, ...props }, ref) => {
|
|
7
|
+
// Clamp percentage between 0 and 100
|
|
8
|
+
const clampedPercentage = Math.min(100, Math.max(0, percentage));
|
|
9
|
+
// SVG parameters
|
|
10
|
+
const size = 16;
|
|
11
|
+
const strokeWidth = 2;
|
|
12
|
+
const radius = (size - strokeWidth) / 2;
|
|
13
|
+
const center = size / 2;
|
|
14
|
+
const circumference = 2 * Math.PI * radius;
|
|
15
|
+
const offset = circumference - (clampedPercentage / 100) * circumference;
|
|
16
|
+
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", 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", children: _jsxs("p", { children: ["Context: ", formattedPercentage, " (", tokens.toLocaleString(), " tokens)"] }) })] }) }));
|
|
17
|
+
});
|
|
18
|
+
ContextUsageButton.displayName = "ContextUsageButton";
|
|
@@ -10,7 +10,6 @@ import { cn } from "../lib/utils.js";
|
|
|
10
10
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./DropdownMenu.js";
|
|
11
11
|
export function FileSystemItem({ item, level = 0, onSelect, selectedId, isDropTarget = false, onDownload, onRename, onDelete, }) {
|
|
12
12
|
const [isExpanded, setIsExpanded] = React.useState(true);
|
|
13
|
-
const [isFocused, setIsFocused] = React.useState(false);
|
|
14
13
|
const isSelected = selectedId === item.id;
|
|
15
14
|
const handleToggle = () => {
|
|
16
15
|
if (item.type === "folder") {
|
|
@@ -49,7 +48,7 @@ export function FileSystemItem({ item, level = 0, onSelect, selectedId, isDropTa
|
|
|
49
48
|
};
|
|
50
49
|
return (_jsxs("div", { className: "flex flex-col w-full", children: [_jsxs("div", { role: "button", tabIndex: 0, "aria-expanded": item.type === "folder" ? isExpanded : undefined, className: cn(
|
|
51
50
|
// Base styles with semantic typography
|
|
52
|
-
"group flex items-center gap-2 rounded-md cursor-pointer transition-colors text-paragraph-sm", getPaddingClass(),
|
|
51
|
+
"group/item flex items-center gap-2 rounded-md cursor-pointer transition-colors text-paragraph-sm", getPaddingClass(),
|
|
53
52
|
// State styles using semantic colors
|
|
54
53
|
// Default: transparent background
|
|
55
54
|
// Hover: accent-hover background
|
|
@@ -62,11 +61,11 @@ export function FileSystemItem({ item, level = 0, onSelect, selectedId, isDropTa
|
|
|
62
61
|
isDropTarget && [
|
|
63
62
|
"bg-accent",
|
|
64
63
|
"border border-dashed border-border-dark",
|
|
65
|
-
]), onClick: handleClick, onKeyDown: handleKeyDown,
|
|
66
|
-
// Hidden by default, shown on group hover
|
|
67
|
-
"opacity-0 group-hover:opacity-100",
|
|
68
|
-
//
|
|
69
|
-
|
|
64
|
+
]), onClick: handleClick, onKeyDown: handleKeyDown, children: [_jsx("div", { className: "shrink-0 size-4 flex items-center justify-center text-foreground", children: item.type === "folder" ? (isExpanded ? (_jsx(FolderOpen, { className: "size-4" })) : (_jsx(Folder, { className: "size-4" }))) : (_jsx(FileCode, { className: "size-4" })) }), _jsx("p", { className: "flex-1 text-foreground whitespace-nowrap overflow-hidden text-ellipsis", children: item.name }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("shrink-0 size-4 transition-opacity text-muted-foreground hover:text-foreground",
|
|
65
|
+
// Hidden by default, shown on group hover only
|
|
66
|
+
"opacity-0 group-hover/item:opacity-100",
|
|
67
|
+
// Force visible when menu is open
|
|
68
|
+
"data-[state=open]:opacity-100"), onClick: (e) => {
|
|
70
69
|
e.stopPropagation();
|
|
71
70
|
}, "aria-label": "More options", type: "button", tabIndex: -1, children: _jsx(MoreVertical, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", side: "bottom", sideOffset: 5, alignOffset: 0, collisionPadding: 8, className: "w-40 z-[100]", onClick: (e) => e.stopPropagation(), children: [onDownload && (_jsx(DropdownMenuItem, { onClick: (e) => {
|
|
72
71
|
e.stopPropagation();
|
|
@@ -37,10 +37,10 @@ export function FileSystemView({ className, provider = defaultProvider, onItemSe
|
|
|
37
37
|
onItemSelect?.(item);
|
|
38
38
|
};
|
|
39
39
|
if (isLoading) {
|
|
40
|
-
return (_jsx("div", { className: cn("
|
|
40
|
+
return (_jsx("div", { className: cn("", className), children: _jsx("p", { className: "text-sm text-muted-foreground", children: "Loading..." }) }));
|
|
41
41
|
}
|
|
42
42
|
if (error) {
|
|
43
|
-
return (_jsx("div", { className: cn("
|
|
43
|
+
return (_jsx("div", { className: cn("", className), children: _jsxs("p", { className: "text-sm text-destructive", children: ["Error: ", error] }) }));
|
|
44
44
|
}
|
|
45
|
-
return (_jsx("div", { className: cn("flex flex-col
|
|
45
|
+
return (_jsx("div", { className: cn("flex flex-col", className), children: items.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No items found" })) : (items.map((item) => (_jsx(FileSystemItem, { item: item, onSelect: handleItemSelect, ...(selectedId && { selectedId }), ...(onDownload && { onDownload }), ...(onRename && { onRename }), ...(onDelete && { onDelete }) }, item.id)))) }));
|
|
46
46
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AcpClient } from "../../sdk/client/acp-client.js";
|
|
2
|
+
import type { ToolCall } from "../../core/schemas/tool-call.js";
|
|
3
|
+
export interface InlineToolCallSummaryProps {
|
|
4
|
+
/** The tool call to summarize */
|
|
5
|
+
toolCall: ToolCall;
|
|
6
|
+
/** ACP client for making requests */
|
|
7
|
+
client: AcpClient | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Inline tool call summary that appears beneath completed tool calls
|
|
11
|
+
* Shows a brief AI-generated summary of what the tool accomplished
|
|
12
|
+
* Uses the ACP client to make the request
|
|
13
|
+
*/
|
|
14
|
+
export declare function InlineToolCallSummary({ toolCall, client, }: InlineToolCallSummaryProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CheckCircle, Sparkles } from "lucide-react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Inline tool call summary that appears beneath completed tool calls
|
|
6
|
+
* Shows a brief AI-generated summary of what the tool accomplished
|
|
7
|
+
* Uses the ACP client to make the request
|
|
8
|
+
*/
|
|
9
|
+
export function InlineToolCallSummary({ toolCall, client, }) {
|
|
10
|
+
const [summary, setSummary] = useState(null);
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Only generate summary for completed tool calls
|
|
14
|
+
if (toolCall.status !== "completed" || !client) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Don't fetch if we already have a summary
|
|
18
|
+
if (summary) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const fetchSummary = async () => {
|
|
22
|
+
setLoading(true);
|
|
23
|
+
try {
|
|
24
|
+
// Create a minimal Anthropic-format request with just this tool call
|
|
25
|
+
const request = {
|
|
26
|
+
messages: [
|
|
27
|
+
{
|
|
28
|
+
role: "assistant",
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "tool_use",
|
|
32
|
+
id: toolCall.id,
|
|
33
|
+
name: toolCall.title.toLowerCase().replace(/\s+/g, "_"),
|
|
34
|
+
input: toolCall.rawInput || {},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
role: "user",
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "tool_result",
|
|
43
|
+
tool_use_id: toolCall.id,
|
|
44
|
+
content: extractToolOutput(toolCall),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
// Make ACP RPC request to agent/summarize method
|
|
51
|
+
const response = await client.request("agent/summarize", request);
|
|
52
|
+
// Extract the description from the first tool call
|
|
53
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
54
|
+
const firstToolCall = response.toolCalls[0];
|
|
55
|
+
if (firstToolCall) {
|
|
56
|
+
setSummary(firstToolCall.description);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// Silently fail - summaries are optional enhancements
|
|
62
|
+
console.debug("Failed to fetch tool call summary via ACP:", error);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
fetchSummary();
|
|
69
|
+
}, [toolCall, client, summary]);
|
|
70
|
+
// Don't show anything if not completed or still loading
|
|
71
|
+
if (toolCall.status !== "completed" || loading) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Don't show if no summary
|
|
75
|
+
if (!summary) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return (_jsxs("div", { className: "flex items-start gap-2 mt-2 px-3 py-2 rounded-lg bg-emerald-50/50 border border-emerald-100", children: [_jsx(CheckCircle, { className: "h-3.5 w-3.5 text-emerald-600 shrink-0 mt-0.5" }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("p", { className: "text-[11px] text-emerald-900 font-medium", children: summary }) }), _jsx(Sparkles, { className: "h-3 w-3 text-emerald-600/50 shrink-0 mt-0.5" })] }));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Extract output text from a tool call for summarization
|
|
82
|
+
*/
|
|
83
|
+
function extractToolOutput(toolCall) {
|
|
84
|
+
// Try to extract text from content blocks
|
|
85
|
+
if (toolCall.content && toolCall.content.length > 0) {
|
|
86
|
+
const textBlocks = [];
|
|
87
|
+
for (const block of toolCall.content) {
|
|
88
|
+
if (block.type === "text" && "text" in block) {
|
|
89
|
+
textBlocks.push(block.text);
|
|
90
|
+
}
|
|
91
|
+
else if (block.type === "content" && "content" in block) {
|
|
92
|
+
const innerContent = block.content;
|
|
93
|
+
if (innerContent.type === "text" && innerContent.text) {
|
|
94
|
+
textBlocks.push(innerContent.text);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (textBlocks.length > 0) {
|
|
99
|
+
// Truncate very long outputs for summary purposes
|
|
100
|
+
const output = textBlocks.join("\n");
|
|
101
|
+
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Fallback to raw output
|
|
105
|
+
if (toolCall.rawOutput) {
|
|
106
|
+
const output = JSON.stringify(toolCall.rawOutput);
|
|
107
|
+
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
108
|
+
}
|
|
109
|
+
return "No output";
|
|
110
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AcpClient } from "../../sdk/client/acp-client.js";
|
|
2
|
+
import type { ToolCall } from "../../core/schemas/tool-call.js";
|
|
3
|
+
export interface InlineToolCallSummaryACPProps {
|
|
4
|
+
/** The tool call to summarize */
|
|
5
|
+
toolCall: ToolCall;
|
|
6
|
+
/** ACP client for making requests */
|
|
7
|
+
client: AcpClient | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Inline tool call summary using ACP protocol
|
|
11
|
+
*
|
|
12
|
+
* This version uses the ACP client to make a custom method call
|
|
13
|
+
* instead of direct HTTP fetch
|
|
14
|
+
*/
|
|
15
|
+
export declare function InlineToolCallSummaryACP({ toolCall, client, }: InlineToolCallSummaryACPProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CheckCircle, Sparkles } from "lucide-react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Inline tool call summary using ACP protocol
|
|
6
|
+
*
|
|
7
|
+
* This version uses the ACP client to make a custom method call
|
|
8
|
+
* instead of direct HTTP fetch
|
|
9
|
+
*/
|
|
10
|
+
export function InlineToolCallSummaryACP({ toolCall, client, }) {
|
|
11
|
+
const [summary, setSummary] = useState(null);
|
|
12
|
+
const [loading, setLoading] = useState(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (toolCall.status !== "completed" || !client || summary) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const fetchSummary = async () => {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
try {
|
|
20
|
+
// This would require adding a custom ACP method to the server
|
|
21
|
+
// For now, this shows the concept - you'd need to implement
|
|
22
|
+
// the corresponding handler in the ACP adapter
|
|
23
|
+
// Example of what it might look like:
|
|
24
|
+
// const response = await client.request({
|
|
25
|
+
// method: "agent/summarize",
|
|
26
|
+
// params: {
|
|
27
|
+
// messages: [
|
|
28
|
+
// {
|
|
29
|
+
// role: "assistant",
|
|
30
|
+
// content: [{
|
|
31
|
+
// type: "tool_use",
|
|
32
|
+
// id: toolCall.id,
|
|
33
|
+
// name: toolCall.title,
|
|
34
|
+
// input: toolCall.rawInput || {}
|
|
35
|
+
// }]
|
|
36
|
+
// },
|
|
37
|
+
// {
|
|
38
|
+
// role: "user",
|
|
39
|
+
// content: [{
|
|
40
|
+
// type: "tool_result",
|
|
41
|
+
// tool_use_id: toolCall.id,
|
|
42
|
+
// content: extractToolOutput(toolCall)
|
|
43
|
+
// }]
|
|
44
|
+
// }
|
|
45
|
+
// ]
|
|
46
|
+
// }
|
|
47
|
+
// });
|
|
48
|
+
// For now, fall back to direct fetch since the ACP method
|
|
49
|
+
// isn't implemented
|
|
50
|
+
console.log("ACP-based summary not yet implemented");
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.debug("Failed to fetch tool call summary via ACP:", error);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
fetchSummary();
|
|
60
|
+
}, [toolCall, client, summary]);
|
|
61
|
+
if (toolCall.status !== "completed" || loading || !summary) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return (_jsxs("div", { className: "flex items-start gap-2 mt-2 px-3 py-2 rounded-lg bg-emerald-50/50 border border-emerald-100", children: [_jsx(CheckCircle, { className: "h-3.5 w-3.5 text-emerald-600 shrink-0 mt-0.5" }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("p", { className: "text-[11px] text-emerald-900 font-medium", children: summary }) }), _jsx(Sparkles, { className: "h-3 w-3 text-emerald-600/50 shrink-0 mt-0.5" })] }));
|
|
65
|
+
}
|
|
66
|
+
function extractToolOutput(toolCall) {
|
|
67
|
+
if (toolCall.content && toolCall.content.length > 0) {
|
|
68
|
+
const textBlocks = [];
|
|
69
|
+
for (const block of toolCall.content) {
|
|
70
|
+
if (block.type === "text" && "text" in block) {
|
|
71
|
+
textBlocks.push(block.text);
|
|
72
|
+
}
|
|
73
|
+
else if (block.type === "content" && "content" in block) {
|
|
74
|
+
const innerContent = block.content;
|
|
75
|
+
if (innerContent.type === "text" && innerContent.text) {
|
|
76
|
+
textBlocks.push(innerContent.text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (textBlocks.length > 0) {
|
|
81
|
+
const output = textBlocks.join("\n");
|
|
82
|
+
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (toolCall.rawOutput) {
|
|
86
|
+
const output = JSON.stringify(toolCall.rawOutput);
|
|
87
|
+
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
88
|
+
}
|
|
89
|
+
return "No output";
|
|
90
|
+
}
|
|
@@ -135,7 +135,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
return _jsx(_Fragment, { children: elements });
|
|
138
|
-
})()) : (_jsx("div", { className: "whitespace-pre-wrap", children: message.content }))
|
|
138
|
+
})()) : (_jsx("div", { className: "whitespace-pre-wrap", children: message.content }))] }));
|
|
139
139
|
}
|
|
140
140
|
return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
|
|
141
141
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import ReactMarkdown from "react-markdown";
|
|
4
4
|
import remarkGfm from "remark-gfm";
|
|
@@ -92,6 +92,6 @@ export const Response = React.forwardRef(({ content, isStreaming = false, showEm
|
|
|
92
92
|
// Horizontal rule
|
|
93
93
|
hr: ({ node, ...props }) => (_jsx("hr", { className: "my-6 border-t border-border opacity-50", ...props })),
|
|
94
94
|
};
|
|
95
|
-
return (
|
|
95
|
+
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
96
|
});
|
|
97
97
|
Response.displayName = "Response";
|
|
@@ -2,6 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
export const SourceListItem = React.forwardRef(({ source, isSelected, className, ...props }, ref) => {
|
|
5
|
-
return (_jsxs("button", { ref: ref, type: "button", className: cn(
|
|
5
|
+
return (_jsxs("button", { ref: ref, type: "button", className: cn(
|
|
6
|
+
// Base styles matching FileSystemItem
|
|
7
|
+
"group flex w-full text-left gap-2 items-start p-2 rounded-md cursor-pointer transition-colors text-paragraph-sm",
|
|
8
|
+
// Hover state - matching FileSystemItem
|
|
9
|
+
"hover:bg-accent-hover",
|
|
10
|
+
// Focus state - matching FileSystemItem
|
|
11
|
+
"focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-border-dark",
|
|
12
|
+
// Selected state - matching FileSystemItem
|
|
13
|
+
isSelected && "bg-accent", className), onClick: () => window.open(source.url, "_blank"), ...props, children: [_jsx("div", { className: "shrink-0 flex items-center h-5", children: _jsx("div", { className: "relative rounded-[3px] size-4 overflow-hidden bg-muted", children: source.favicon ? (_jsx("img", { alt: source.sourceName, className: "size-full object-cover", src: source.favicon })) : (_jsx("div", { className: "size-full bg-muted" })) }) }), _jsxs("div", { className: "flex flex-1 flex-col gap-1 min-w-0", children: [_jsxs("div", { className: "text-paragraph-sm text-foreground", children: [_jsx("span", { className: "font-medium", children: source.sourceName }), _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", source.title] })] }), _jsx("p", { className: "text-paragraph-sm text-muted-foreground line-clamp-3", children: source.snippet })] })] }));
|
|
6
14
|
});
|
|
7
15
|
SourceListItem.displayName = "SourceListItem";
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Circle, CircleCheck } from "lucide-react";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
import { cn } from "../lib/utils.js";
|
|
4
5
|
export const TodoListItem = React.forwardRef(({ todo, className, ...props }, ref) => {
|
|
5
|
-
|
|
6
|
+
const isCompleted = todo.status === "completed";
|
|
7
|
+
const isSelected = false; // TODO: Add selection support later if needed
|
|
8
|
+
return (
|
|
9
|
+
/* biome-ignore lint/a11y/useSemanticElements: Keeping div for consistency with FileSystemItem pattern */
|
|
10
|
+
_jsxs("div", { ref: ref, className: cn(
|
|
11
|
+
// Base styles matching FileSystemItem
|
|
12
|
+
"group flex items-center gap-2 p-2 rounded-md cursor-pointer transition-colors text-paragraph-sm",
|
|
13
|
+
// Hover state - matching FileSystemItem
|
|
14
|
+
"hover:bg-accent-hover",
|
|
15
|
+
// Focus state - matching FileSystemItem
|
|
16
|
+
"focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-border-dark",
|
|
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: isCompleted ? (_jsx(CircleCheck, { className: "size-4 text-muted-foreground" })) : (_jsx(Circle, { className: "size-4 text-foreground" })) }), _jsx("p", { className: cn("flex-1 text-foreground",
|
|
19
|
+
// Completed state: strikethrough + muted color
|
|
20
|
+
isCompleted && "line-through text-muted-foreground",
|
|
21
|
+
// In-progress state: medium weight for emphasis
|
|
22
|
+
todo.status === "in_progress" && "font-medium"), children: todo.text })] }));
|
|
6
23
|
});
|
|
7
24
|
TodoListItem.displayName = "TodoListItem";
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import JsonView from "@uiw/react-json-view";
|
|
3
|
-
import { ChevronDown, Wrench } from "lucide-react";
|
|
3
|
+
import { CheckSquare, ChevronDown, Cloud, Edit, FileText, Globe, Link, Search, Wrench, } from "lucide-react";
|
|
4
4
|
import { useState } from "react";
|
|
5
|
+
/**
|
|
6
|
+
* Map of icon names to Lucide components
|
|
7
|
+
*/
|
|
8
|
+
const ICON_MAP = {
|
|
9
|
+
Globe: Globe,
|
|
10
|
+
Link: Link,
|
|
11
|
+
Cloud: Cloud,
|
|
12
|
+
CheckSquare: CheckSquare,
|
|
13
|
+
Search: Search,
|
|
14
|
+
FileText: FileText,
|
|
15
|
+
Edit: Edit,
|
|
16
|
+
Wrench: Wrench,
|
|
17
|
+
};
|
|
5
18
|
/**
|
|
6
19
|
* Tool call kind icons (using emoji for simplicity)
|
|
7
20
|
*/
|
|
@@ -22,7 +35,13 @@ const _kindIcons = {
|
|
|
22
35
|
*/
|
|
23
36
|
export function ToolCall({ toolCall }) {
|
|
24
37
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
25
|
-
|
|
38
|
+
// Determine which icon to show
|
|
39
|
+
const IconComponent = toolCall.icon && ICON_MAP[toolCall.icon]
|
|
40
|
+
? ICON_MAP[toolCall.icon]
|
|
41
|
+
: Wrench;
|
|
42
|
+
// Determine display name
|
|
43
|
+
const displayName = toolCall.prettyName || toolCall.title;
|
|
44
|
+
return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsx("button", { type: "button", className: "flex items-center gap-2 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: _jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-zinc-500", children: [_jsx("div", { className: "text-zinc-500", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-zinc-500", children: displayName }), _jsx(ChevronDown, { className: `h-3 w-3 text-zinc-400 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }) }), isExpanded && (_jsxs("div", { className: "mt-2 text-sm border border-zinc-200 rounded-lg bg-zinc-50 overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-zinc-200", children: [_jsx("div", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-zinc-700 bg-zinc-200/50 px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null &&
|
|
26
45
|
loc.line !== undefined &&
|
|
27
46
|
`:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-zinc-200", children: [_jsx("div", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-zinc-700", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: {
|
|
28
47
|
fontSize: "11px",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
declare const TooltipProvider: React.FC<TooltipPrimitive.TooltipProviderProps>;
|
|
4
|
+
declare const Tooltip: React.FC<TooltipPrimitive.TooltipProps>;
|
|
5
|
+
declare const TooltipTrigger: React.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
6
|
+
declare const TooltipContent: React.ForwardRefExoticComponent<Omit<TooltipPrimitive.TooltipContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
6
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
7
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
8
|
+
const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className), ...props })));
|
|
9
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
10
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|