@townco/ui 0.1.102 → 0.1.104
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-subagent-stream.js +5 -4
- package/dist/gui/components/InvokingGroup.d.ts +9 -0
- package/dist/gui/components/InvokingGroup.js +16 -0
- package/dist/gui/components/ToolCall.d.ts +8 -0
- package/dist/gui/components/ToolCall.js +226 -0
- package/dist/gui/components/ToolCallGroup.d.ts +8 -0
- package/dist/gui/components/ToolCallGroup.js +29 -0
- package/package.json +3 -3
|
@@ -121,8 +121,8 @@ export function useSubagentStream(options) {
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
}, [scheduleUpdate]);
|
|
124
|
-
const connectToSubagent = useCallback(async (port, sessionId, host) => {
|
|
125
|
-
const baseUrl =
|
|
124
|
+
const connectToSubagent = useCallback(async (port, sessionId, host, protocol) => {
|
|
125
|
+
const baseUrl = `${protocol}//${host}:${port}`;
|
|
126
126
|
logger.info("Connecting to sub-agent SSE", { baseUrl, sessionId });
|
|
127
127
|
setIsStreaming(true);
|
|
128
128
|
setError(null);
|
|
@@ -226,6 +226,7 @@ export function useSubagentStream(options) {
|
|
|
226
226
|
const sessionId = options?.sessionId;
|
|
227
227
|
const host = options?.host ??
|
|
228
228
|
(typeof window !== "undefined" ? window.location.hostname : "localhost");
|
|
229
|
+
const protocol = typeof window !== "undefined" ? window.location.protocol : "http:";
|
|
229
230
|
// Connect when options change
|
|
230
231
|
useEffect(() => {
|
|
231
232
|
if (!port || !sessionId) {
|
|
@@ -236,7 +237,7 @@ export function useSubagentStream(options) {
|
|
|
236
237
|
setError(null);
|
|
237
238
|
setHasCompleted(false);
|
|
238
239
|
setIsStreaming(true);
|
|
239
|
-
connectToSubagent(port, sessionId, host);
|
|
240
|
+
connectToSubagent(port, sessionId, host, protocol);
|
|
240
241
|
// Cleanup on unmount or options change
|
|
241
242
|
return () => {
|
|
242
243
|
if (abortControllerRef.current) {
|
|
@@ -248,7 +249,7 @@ export function useSubagentStream(options) {
|
|
|
248
249
|
updateTimeoutRef.current = null;
|
|
249
250
|
}
|
|
250
251
|
};
|
|
251
|
-
}, [port, sessionId, host, connectToSubagent]);
|
|
252
|
+
}, [port, sessionId, host, protocol, connectToSubagent]);
|
|
252
253
|
// Derive streaming status: streaming if we haven't completed yet
|
|
253
254
|
const effectiveIsStreaming = !hasCompleted;
|
|
254
255
|
return { messages, isStreaming: effectiveIsStreaming, error };
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
|
|
2
|
+
export interface ToolCallProps {
|
|
3
|
+
toolCall: ToolCallType;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* ToolCall component - displays a single tool call with collapsible details
|
|
7
|
+
*/
|
|
8
|
+
export declare function ToolCall({ toolCall }: ToolCallProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import JsonView from "@uiw/react-json-view";
|
|
3
|
+
import { AlertCircle, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, Search, Wrench, } from "lucide-react";
|
|
4
|
+
import React, { useState } from "react";
|
|
5
|
+
import { ChatLayout } from "./index.js";
|
|
6
|
+
import { SubAgentDetails } from "./SubAgentDetails.js";
|
|
7
|
+
import { useTheme } from "./ThemeProvider.js";
|
|
8
|
+
/**
|
|
9
|
+
* Map of icon names to Lucide components
|
|
10
|
+
*/
|
|
11
|
+
const ICON_MAP = {
|
|
12
|
+
Globe: Globe,
|
|
13
|
+
Image: Image,
|
|
14
|
+
Link: Link,
|
|
15
|
+
Cloud: Cloud,
|
|
16
|
+
CheckSquare: CheckSquare,
|
|
17
|
+
Search: Search,
|
|
18
|
+
FileText: FileText,
|
|
19
|
+
Edit: Edit,
|
|
20
|
+
Wrench: Wrench,
|
|
21
|
+
CircleDot: CircleDot,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Tool call kind icons (using emoji for simplicity)
|
|
25
|
+
*/
|
|
26
|
+
const _kindIcons = {
|
|
27
|
+
read: "\u{1F4C4}",
|
|
28
|
+
edit: "\u{270F}\u{FE0F}",
|
|
29
|
+
delete: "\u{1F5D1}\u{FE0F}",
|
|
30
|
+
move: "\u{1F4E6}",
|
|
31
|
+
search: "\u{1F50D}",
|
|
32
|
+
execute: "\u{2699}\u{FE0F}",
|
|
33
|
+
think: "\u{1F4AD}",
|
|
34
|
+
fetch: "\u{1F310}",
|
|
35
|
+
switch_mode: "\u{1F501}",
|
|
36
|
+
other: "\u{1F527}",
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* ToolCall component - displays a single tool call with collapsible details
|
|
40
|
+
*/
|
|
41
|
+
export function ToolCall({ toolCall }) {
|
|
42
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
43
|
+
const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
|
|
44
|
+
const { resolvedTheme } = useTheme();
|
|
45
|
+
// Detect TodoWrite tool and subagent
|
|
46
|
+
const isTodoWrite = toolCall.title === "todo_write";
|
|
47
|
+
const isSubagentCall = !!(toolCall.subagentPort && toolCall.subagentSessionId);
|
|
48
|
+
// Safely access ChatLayout context - will be undefined if not within ChatLayout
|
|
49
|
+
const layoutContext = React.useContext(ChatLayout.Context);
|
|
50
|
+
// Click handler: toggle sidepanel for TodoWrite, subagent details for subagents, expand for others
|
|
51
|
+
const handleHeaderClick = React.useCallback(() => {
|
|
52
|
+
if (isTodoWrite && layoutContext) {
|
|
53
|
+
// Toggle sidepanel - close if already open on todo tab, otherwise open
|
|
54
|
+
if (layoutContext.panelSize !== "hidden" &&
|
|
55
|
+
layoutContext.activeTab === "todo") {
|
|
56
|
+
layoutContext.setPanelSize("hidden");
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
layoutContext.setPanelSize("small");
|
|
60
|
+
layoutContext.setActiveTab("todo");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (isSubagentCall) {
|
|
64
|
+
// Toggle subagent details
|
|
65
|
+
setIsSubagentExpanded(!isSubagentExpanded);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Normal expand/collapse
|
|
69
|
+
setIsExpanded(!isExpanded);
|
|
70
|
+
}
|
|
71
|
+
}, [
|
|
72
|
+
isTodoWrite,
|
|
73
|
+
layoutContext,
|
|
74
|
+
isExpanded,
|
|
75
|
+
isSubagentCall,
|
|
76
|
+
isSubagentExpanded,
|
|
77
|
+
]);
|
|
78
|
+
// Determine which icon to show
|
|
79
|
+
const IconComponent = toolCall.icon && ICON_MAP[toolCall.icon]
|
|
80
|
+
? ICON_MAP[toolCall.icon]
|
|
81
|
+
: CircleDot;
|
|
82
|
+
// Determine display name
|
|
83
|
+
const displayName = toolCall.prettyName || toolCall.title;
|
|
84
|
+
// Determine icon color based on status (especially for subagents)
|
|
85
|
+
const isSubagentRunning = isSubagentCall &&
|
|
86
|
+
(toolCall.status === "in_progress" || toolCall.status === "pending");
|
|
87
|
+
const isSubagentFailed = isSubagentCall && toolCall.status === "failed";
|
|
88
|
+
const iconColorClass = isSubagentCall
|
|
89
|
+
? isSubagentFailed
|
|
90
|
+
? "text-destructive"
|
|
91
|
+
: isSubagentRunning
|
|
92
|
+
? "text-foreground animate-pulse"
|
|
93
|
+
: "text-green-500"
|
|
94
|
+
: "text-muted-foreground";
|
|
95
|
+
const statusTooltip = isSubagentCall
|
|
96
|
+
? isSubagentFailed
|
|
97
|
+
? "Sub-agent failed"
|
|
98
|
+
: isSubagentRunning
|
|
99
|
+
? "Sub-agent running"
|
|
100
|
+
: "Sub-agent completed"
|
|
101
|
+
: undefined;
|
|
102
|
+
// Check if there's an error
|
|
103
|
+
const hasError = toolCall.status === "failed" || !!toolCall.error;
|
|
104
|
+
// Check if this is a preliminary (pending) tool call without full details yet
|
|
105
|
+
const isPreliminary = toolCall.status === "pending" &&
|
|
106
|
+
(!toolCall.rawInput || Object.keys(toolCall.rawInput).length === 0);
|
|
107
|
+
// JSON View style based on theme
|
|
108
|
+
const jsonStyle = {
|
|
109
|
+
fontSize: "11px",
|
|
110
|
+
backgroundColor: "transparent",
|
|
111
|
+
fontFamily: "inherit",
|
|
112
|
+
"--w-rjv-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
|
|
113
|
+
"--w-rjv-key-string": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
|
|
114
|
+
"--w-rjv-background-color": "transparent",
|
|
115
|
+
"--w-rjv-line-color": resolvedTheme === "dark" ? "#27272a" : "#e4e4e7",
|
|
116
|
+
"--w-rjv-arrow-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
117
|
+
"--w-rjv-edit-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
|
|
118
|
+
"--w-rjv-info-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
119
|
+
"--w-rjv-update-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
|
|
120
|
+
"--w-rjv-copied-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
|
|
121
|
+
"--w-rjv-copied-success-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
|
|
122
|
+
"--w-rjv-curlybraces-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
123
|
+
"--w-rjv-colon-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
124
|
+
"--w-rjv-brackets-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
125
|
+
"--w-rjv-quotes-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
126
|
+
"--w-rjv-quotes-string-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
|
|
127
|
+
"--w-rjv-type-string-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
|
|
128
|
+
"--w-rjv-type-int-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
|
|
129
|
+
"--w-rjv-type-float-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
|
|
130
|
+
"--w-rjv-type-bigint-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
|
|
131
|
+
"--w-rjv-type-boolean-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
|
|
132
|
+
"--w-rjv-type-date-color": resolvedTheme === "dark" ? "#ec4899" : "#db2777",
|
|
133
|
+
"--w-rjv-type-url-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
|
|
134
|
+
"--w-rjv-type-null-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
135
|
+
"--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
136
|
+
"--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
137
|
+
};
|
|
138
|
+
// Preliminary tool calls show as simple light gray text without expansion
|
|
139
|
+
if (isPreliminary) {
|
|
140
|
+
return (_jsx("div", { className: "flex flex-col my-4", children: _jsxs("span", { className: "text-paragraph-sm text-muted-foreground/50", children: ["Invoking ", displayName] }) }));
|
|
141
|
+
}
|
|
142
|
+
return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [_jsx("div", { className: iconColorClass, title: statusTooltip, children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: displayName }), hasError && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-muted-foreground/70" })) : (_jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` }))] }), toolCall.subline && (_jsx("span", { className: "text-paragraph-sm text-muted-foreground/70 pl-4.5", children: toolCall.subline }))] }), !isTodoWrite && isSubagentCall && (_jsx("div", { className: "pl-4.5", children: _jsx(SubAgentDetails, { port: toolCall.subagentPort, sessionId: toolCall.subagentSessionId, parentStatus: toolCall.status, agentName: toolCall.rawInput?.agentName, query: toolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded }) })), !isTodoWrite && !isSubagentCall && isExpanded && (_jsxs("div", { className: "mt-2 text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.rawInput &&
|
|
143
|
+
Object.keys(toolCall.rawInput).length > 0 &&
|
|
144
|
+
!toolCall.subagentPort && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) })] })), toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground 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-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null &&
|
|
145
|
+
loc.line !== undefined &&
|
|
146
|
+
`:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), (toolCall.content && toolCall.content.length > 0) ||
|
|
147
|
+
toolCall.error ? (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsxs("div", { className: "space-y-2 text-[11px] text-foreground", children: [toolCall.content?.map((block, idx) => {
|
|
148
|
+
// Generate a stable key based on content
|
|
149
|
+
const getBlockKey = () => {
|
|
150
|
+
if (block.type === "diff" && "path" in block) {
|
|
151
|
+
return `diff-${block.path}-${idx}`;
|
|
152
|
+
}
|
|
153
|
+
if (block.type === "terminal" && "terminalId" in block) {
|
|
154
|
+
return `terminal-${block.terminalId}`;
|
|
155
|
+
}
|
|
156
|
+
if (block.type === "text" && "text" in block) {
|
|
157
|
+
return `text-${block.text.substring(0, 20)}-${idx}`;
|
|
158
|
+
}
|
|
159
|
+
if (block.type === "content" && "content" in block) {
|
|
160
|
+
const innerContent = block.content;
|
|
161
|
+
return `content-${innerContent.text?.substring(0, 20)}-${idx}`;
|
|
162
|
+
}
|
|
163
|
+
return `block-${idx}`;
|
|
164
|
+
};
|
|
165
|
+
// Helper to render text content (with JSON parsing if applicable)
|
|
166
|
+
const renderTextContent = (text, key) => {
|
|
167
|
+
// Try to parse as JSON
|
|
168
|
+
try {
|
|
169
|
+
const parsed = JSON.parse(text);
|
|
170
|
+
// If it's an object or array, render with JsonView
|
|
171
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
172
|
+
return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) }, key));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Not valid JSON, render as plain text
|
|
177
|
+
}
|
|
178
|
+
// Render as plain text
|
|
179
|
+
return (_jsx("pre", { className: "whitespace-pre-wrap font-mono text-[11px] text-foreground overflow-x-auto", children: text }, key));
|
|
180
|
+
};
|
|
181
|
+
// Handle nested content blocks (ACP format)
|
|
182
|
+
if (block.type === "content" && "content" in block) {
|
|
183
|
+
const innerContent = block.content;
|
|
184
|
+
if (innerContent.type === "text" && innerContent.text) {
|
|
185
|
+
return renderTextContent(innerContent.text, getBlockKey());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Handle direct text blocks
|
|
189
|
+
if (block.type === "text" && "text" in block) {
|
|
190
|
+
return renderTextContent(block.text, getBlockKey());
|
|
191
|
+
}
|
|
192
|
+
// Handle image blocks
|
|
193
|
+
if (block.type === "image") {
|
|
194
|
+
const alt = block.alt || "Generated image";
|
|
195
|
+
let imageSrc;
|
|
196
|
+
if ("data" in block) {
|
|
197
|
+
// Base64 encoded image
|
|
198
|
+
const mimeType = block.mimeType || "image/png";
|
|
199
|
+
imageSrc = `data:${mimeType};base64,${block.data}`;
|
|
200
|
+
}
|
|
201
|
+
else if ("url" in block) {
|
|
202
|
+
// URL or file path
|
|
203
|
+
imageSrc = block.url;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return (_jsx("div", { className: "my-2", children: _jsx("img", { src: imageSrc, alt: alt, className: "max-w-full h-auto rounded-md border border-border" }) }, getBlockKey()));
|
|
209
|
+
}
|
|
210
|
+
// Handle diff blocks
|
|
211
|
+
if (block.type === "diff" &&
|
|
212
|
+
"path" in block &&
|
|
213
|
+
"oldText" in block &&
|
|
214
|
+
"newText" in block) {
|
|
215
|
+
return (_jsxs("div", { className: "border border-border rounded bg-card", children: [_jsxs("div", { className: "bg-muted px-2 py-1 text-[10px] font-mono text-muted-foreground border-b border-border", children: [block.path, "line" in block &&
|
|
216
|
+
block.line !== null &&
|
|
217
|
+
block.line !== undefined &&
|
|
218
|
+
`:${block.line}`] }), _jsxs("div", { className: "p-2 font-mono text-[11px]", children: [_jsxs("div", { className: "text-red-500 dark:text-red-400", children: ["- ", block.oldText] }), _jsxs("div", { className: "text-green-500 dark:text-green-400", children: ["+ ", block.newText] })] })] }, getBlockKey()));
|
|
219
|
+
}
|
|
220
|
+
// Handle terminal blocks
|
|
221
|
+
if (block.type === "terminal" && "terminalId" in block) {
|
|
222
|
+
return (_jsxs("div", { className: "bg-neutral-900 text-neutral-100 p-2 rounded text-[11px] font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })) : null, toolCall._meta?.truncationWarning && (_jsxs("div", { className: "mx-3 mt-3 mb-0 flex items-center gap-2 rounded-md bg-yellow-50 dark:bg-yellow-950/20 px-3 py-2 text-[11px] text-yellow-800 dark:text-yellow-200 border border-yellow-200 dark:border-yellow-900", children: [_jsx("span", { className: "text-yellow-600 dark:text-yellow-500", children: "\u26A0\uFE0F" }), _jsx("span", { children: toolCall._meta.truncationWarning })] })), (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-muted/50 border-t border-border flex flex-wrap gap-4 text-[10px] text-muted-foreground font-sans", children: [toolCall.tokenUsage && (_jsxs("div", { className: "flex gap-3", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Input:" }), toolCall.tokenUsage.inputTokens.toLocaleString()] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Output:" }), toolCall.tokenUsage.outputTokens.toLocaleString()] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Total:" }), toolCall.tokenUsage.totalTokens.toLocaleString()] }))] })), toolCall.startedAt && (_jsxs("div", { className: "flex gap-3 ml-auto", children: [_jsxs("span", { children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString()] }), toolCall.completedAt && (_jsxs("span", { children: ["Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }))] }));
|
|
226
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
|
|
2
|
+
export interface ToolCallGroupProps {
|
|
3
|
+
toolCalls: ToolCallType[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* ToolCallGroup component - displays a group of parallel tool calls with collapsible details
|
|
7
|
+
*/
|
|
8
|
+
export declare function ToolCallGroup({ toolCalls }: ToolCallGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDown, ListVideo } from "lucide-react";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { ToolCall } from "./ToolCall.js";
|
|
5
|
+
/**
|
|
6
|
+
* ToolCallGroup component - displays a group of parallel tool calls with collapsible details
|
|
7
|
+
*/
|
|
8
|
+
export function ToolCallGroup({ toolCalls }) {
|
|
9
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
10
|
+
// Calculate group status based on individual tool call statuses
|
|
11
|
+
const getGroupStatus = () => {
|
|
12
|
+
const statuses = toolCalls.map((tc) => tc.status);
|
|
13
|
+
if (statuses.some((s) => s === "failed"))
|
|
14
|
+
return "failed";
|
|
15
|
+
if (statuses.some((s) => s === "in_progress"))
|
|
16
|
+
return "in_progress";
|
|
17
|
+
if (statuses.every((s) => s === "completed"))
|
|
18
|
+
return "completed";
|
|
19
|
+
return "pending";
|
|
20
|
+
};
|
|
21
|
+
const groupStatus = getGroupStatus();
|
|
22
|
+
// Generate summary of tool names
|
|
23
|
+
const toolNames = toolCalls.map((tc) => tc.prettyName || tc.title);
|
|
24
|
+
const uniqueNames = [...new Set(toolNames)];
|
|
25
|
+
const summary = uniqueNames.length <= 2
|
|
26
|
+
? uniqueNames.join(", ")
|
|
27
|
+
: `${uniqueNames.slice(0, 2).join(", ")} +${uniqueNames.length - 2} more`;
|
|
28
|
+
return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 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-muted-foreground", children: [_jsx("div", { className: "text-muted-foreground", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: "Parallel operation" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-muted-foreground/70", children: toolCalls.length }), _jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }), !isExpanded && (_jsx("span", { className: "text-paragraph-sm text-muted-foreground/70 pl-4.5", children: summary }))] }), isExpanded && (_jsx("div", { className: "mt-1", children: toolCalls.map((toolCall) => (_jsxs("div", { className: "flex items-start", children: [_jsx("div", { className: "w-2.5 h-4 border-l-2 border-b-2 border-border rounded-bl-[6px] mt-1 mr-0.5 shrink-0" }), _jsx("div", { className: "flex-1 -mt-2", children: _jsx(ToolCall, { toolCall: toolCall }) })] }, toolCall.id))) }))] }));
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.104",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@radix-ui/react-slot": "^1.2.4",
|
|
50
50
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
51
51
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
52
|
-
"@townco/core": "0.0.
|
|
52
|
+
"@townco/core": "0.0.82",
|
|
53
53
|
"@types/mdast": "^4.0.4",
|
|
54
54
|
"@uiw/react-json-view": "^2.0.0-alpha.39",
|
|
55
55
|
"class-variance-authority": "^0.7.1",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"zustand": "^5.0.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@townco/tsconfig": "0.1.
|
|
70
|
+
"@townco/tsconfig": "0.1.101",
|
|
71
71
|
"@types/node": "^24.10.0",
|
|
72
72
|
"@types/react": "^19.2.2",
|
|
73
73
|
"@types/unist": "^3.0.3",
|