@townco/ui 0.1.68 → 0.1.70

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.
Files changed (51) hide show
  1. package/dist/core/hooks/use-chat-messages.d.ts +6 -1
  2. package/dist/core/hooks/use-chat-session.d.ts +1 -1
  3. package/dist/core/hooks/use-tool-calls.d.ts +6 -1
  4. package/dist/core/schemas/chat.d.ts +10 -0
  5. package/dist/core/schemas/tool-call.d.ts +13 -8
  6. package/dist/core/schemas/tool-call.js +8 -0
  7. package/dist/core/utils/tool-call-state.d.ts +30 -0
  8. package/dist/core/utils/tool-call-state.js +73 -0
  9. package/dist/core/utils/tool-summary.d.ts +13 -0
  10. package/dist/core/utils/tool-summary.js +172 -0
  11. package/dist/core/utils/tool-verbiage.d.ts +28 -0
  12. package/dist/core/utils/tool-verbiage.js +185 -0
  13. package/dist/gui/components/AppSidebar.d.ts +22 -0
  14. package/dist/gui/components/AppSidebar.js +22 -0
  15. package/dist/gui/components/ChatLayout.d.ts +5 -0
  16. package/dist/gui/components/ChatLayout.js +130 -138
  17. package/dist/gui/components/ChatView.js +42 -118
  18. package/dist/gui/components/HookNotification.d.ts +9 -0
  19. package/dist/gui/components/HookNotification.js +50 -0
  20. package/dist/gui/components/MessageContent.js +151 -39
  21. package/dist/gui/components/SessionHistory.d.ts +10 -0
  22. package/dist/gui/components/SessionHistory.js +101 -0
  23. package/dist/gui/components/SessionHistoryItem.d.ts +11 -0
  24. package/dist/gui/components/SessionHistoryItem.js +24 -0
  25. package/dist/gui/components/Sheet.d.ts +25 -0
  26. package/dist/gui/components/Sheet.js +36 -0
  27. package/dist/gui/components/Sidebar.d.ts +65 -0
  28. package/dist/gui/components/Sidebar.js +231 -0
  29. package/dist/gui/components/SidebarToggle.d.ts +3 -0
  30. package/dist/gui/components/SidebarToggle.js +9 -0
  31. package/dist/gui/components/SubAgentDetails.js +13 -2
  32. package/dist/gui/components/ToolCallList.js +3 -3
  33. package/dist/gui/components/ToolOperation.d.ts +11 -0
  34. package/dist/gui/components/ToolOperation.js +329 -0
  35. package/dist/gui/components/WorkProgress.d.ts +20 -0
  36. package/dist/gui/components/WorkProgress.js +79 -0
  37. package/dist/gui/components/index.d.ts +8 -1
  38. package/dist/gui/components/index.js +9 -1
  39. package/dist/gui/hooks/index.d.ts +1 -0
  40. package/dist/gui/hooks/index.js +1 -0
  41. package/dist/gui/hooks/use-mobile.d.ts +1 -0
  42. package/dist/gui/hooks/use-mobile.js +15 -0
  43. package/dist/gui/index.d.ts +1 -0
  44. package/dist/gui/index.js +2 -0
  45. package/dist/gui/lib/motion.d.ts +55 -0
  46. package/dist/gui/lib/motion.js +217 -0
  47. package/dist/sdk/schemas/message.d.ts +2 -2
  48. package/dist/sdk/schemas/session.d.ts +5 -0
  49. package/dist/sdk/transports/types.d.ts +5 -0
  50. package/package.json +8 -7
  51. package/src/styles/global.css +128 -1
@@ -0,0 +1,329 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import JsonView from "@uiw/react-json-view";
3
+ import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
4
+ import { AlertCircle, BrainCircuit, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, ListVideo, Search, Wrench, } from "lucide-react";
5
+ import React, { useEffect, useState } from "react";
6
+ import { areAllToolCallsCompleted, getGroupDisplayState, getToolCallDisplayState, getToolCallStateVerbiage, hasAnyToolCallFailed, isPreliminaryToolCall, } from "../../core/utils/tool-call-state.js";
7
+ import { generateSmartSummary } from "../../core/utils/tool-summary.js";
8
+ import { expandCollapseVariants, fadeInVariants, getDuration, getTransition, motionDuration, motionEasing, rotateVariants, shimmerTransition, standardTransition, } from "../lib/motion.js";
9
+ import { cn } from "../lib/utils.js";
10
+ import * as ChatLayout from "./ChatLayout.js";
11
+ import { SubAgentDetails } from "./SubAgentDetails.js";
12
+ import { useTheme } from "./ThemeProvider.js";
13
+ /**
14
+ * Map of icon names to Lucide components
15
+ */
16
+ const ICON_MAP = {
17
+ Globe: Globe,
18
+ Image: Image,
19
+ Link: Link,
20
+ Cloud: Cloud,
21
+ CheckSquare: CheckSquare,
22
+ Search: Search,
23
+ FileText: FileText,
24
+ Edit: Edit,
25
+ Wrench: Wrench,
26
+ BrainCircuit: BrainCircuit,
27
+ CircleDot: CircleDot,
28
+ };
29
+ /**
30
+ * ToolOperation component - unified display for tool calls
31
+ * Handles both individual and grouped tool calls with smooth transitions
32
+ */
33
+ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = true, }) {
34
+ const [isExpanded, setIsExpanded] = useState(false);
35
+ const [isMinimized, setIsMinimized] = useState(false);
36
+ const [userInteracted, setUserInteracted] = useState(false);
37
+ const { resolvedTheme } = useTheme();
38
+ const shouldReduceMotion = useReducedMotion();
39
+ // For single tool call, extract it
40
+ const singleToolCall = toolCalls.length === 1 ? toolCalls[0] : null;
41
+ const isTodoWrite = singleToolCall?.title === "todo_write";
42
+ // Detect subagent calls
43
+ const hasLiveSubagent = !!(singleToolCall?.subagentPort && singleToolCall?.subagentSessionId);
44
+ const hasStoredSubagent = !!(singleToolCall?.subagentMessages &&
45
+ singleToolCall.subagentMessages.length > 0);
46
+ const isSubagentCall = hasLiveSubagent || hasStoredSubagent;
47
+ const isReplaySubagent = hasStoredSubagent;
48
+ // State for subagent expansion
49
+ const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
50
+ // Safely access ChatLayout context
51
+ const layoutContext = React.useContext(ChatLayout.Context);
52
+ // Determine display state
53
+ const displayState = isGrouped
54
+ ? getGroupDisplayState(toolCalls)
55
+ : singleToolCall
56
+ ? getToolCallDisplayState(singleToolCall)
57
+ : "executing";
58
+ const isCompleted = displayState === "completed";
59
+ const isFailed = displayState === "failed";
60
+ const isSelecting = displayState === "selecting";
61
+ const isActive = displayState === "executing";
62
+ // Auto-minimize when completed (only if user hasn't manually interacted)
63
+ useEffect(() => {
64
+ if (autoMinimize && isCompleted && !isMinimized && !userInteracted) {
65
+ // Small delay to show the completed state briefly
66
+ const timer = setTimeout(() => {
67
+ setIsMinimized(true);
68
+ setIsExpanded(false);
69
+ }, 500);
70
+ return () => clearTimeout(timer);
71
+ }
72
+ return undefined;
73
+ }, [autoMinimize, isCompleted, isMinimized, userInteracted]);
74
+ // Click handler for header
75
+ const handleHeaderClick = React.useCallback(() => {
76
+ if (isTodoWrite && layoutContext && singleToolCall) {
77
+ // Toggle sidepanel for TodoWrite
78
+ if (layoutContext.panelSize !== "hidden" &&
79
+ layoutContext.activeTab === "todo") {
80
+ layoutContext.setPanelSize("hidden");
81
+ }
82
+ else {
83
+ layoutContext.setPanelSize("small");
84
+ layoutContext.setActiveTab("todo");
85
+ }
86
+ }
87
+ else if (isSubagentCall) {
88
+ // Toggle subagent expansion
89
+ setUserInteracted(true);
90
+ setIsSubagentExpanded(!isSubagentExpanded);
91
+ }
92
+ else {
93
+ // Normal expand/collapse
94
+ setUserInteracted(true); // Mark as user-interacted to prevent auto-minimize
95
+ setIsExpanded(!isExpanded);
96
+ if (isMinimized) {
97
+ setIsMinimized(false);
98
+ }
99
+ }
100
+ }, [
101
+ isTodoWrite,
102
+ layoutContext,
103
+ isExpanded,
104
+ isMinimized,
105
+ singleToolCall,
106
+ isSubagentCall,
107
+ isSubagentExpanded,
108
+ ]);
109
+ // Get icon for display
110
+ const getIcon = () => {
111
+ if (isGrouped) {
112
+ return ListVideo;
113
+ }
114
+ if (singleToolCall?.icon && ICON_MAP[singleToolCall.icon]) {
115
+ return ICON_MAP[singleToolCall.icon];
116
+ }
117
+ return CircleDot;
118
+ };
119
+ const IconComponent = getIcon();
120
+ // Get verbiage/summary
121
+ const getDisplayText = () => {
122
+ if (isGrouped) {
123
+ const tense = isCompleted ? "past" : "active";
124
+ return generateSmartSummary(toolCalls, tense);
125
+ }
126
+ if (singleToolCall) {
127
+ return getToolCallStateVerbiage(singleToolCall);
128
+ }
129
+ return "Tool operation";
130
+ };
131
+ const displayText = getDisplayText();
132
+ // For preliminary/selecting states, show simple non-expandable text
133
+ if (isSelecting && !isGrouped) {
134
+ return (_jsx(motion.div, { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", animate: {
135
+ backgroundPosition: ["200% 0", "-200% 0"],
136
+ }, transition: {
137
+ ...shimmerTransition,
138
+ duration: getDuration(shouldReduceMotion ?? false, 1.5),
139
+ }, style: {
140
+ backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
141
+ backgroundSize: "200% 100%",
142
+ }, children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/50", children: displayText }) }));
143
+ }
144
+ // If it's a grouped preliminary (selecting) state
145
+ if (isSelecting && isGrouped) {
146
+ return (_jsxs(motion.div, { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", animate: {
147
+ backgroundPosition: ["200% 0", "-200% 0"],
148
+ }, transition: {
149
+ ...shimmerTransition,
150
+ duration: getDuration(shouldReduceMotion ?? false, 1.5),
151
+ }, style: {
152
+ backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
153
+ backgroundSize: "200% 100%",
154
+ }, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Selecting tools" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })] }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText })] }));
155
+ }
156
+ // Full display (for single tool call or expanded group, includes minimized state)
157
+ return (_jsxs(motion.div, { className: "flex flex-col my-4", initial: {
158
+ filter: "blur(12px)",
159
+ opacity: 0,
160
+ y: 12,
161
+ }, animate: {
162
+ filter: "blur(0px)",
163
+ opacity: 1,
164
+ y: 0,
165
+ }, exit: {
166
+ filter: "blur(12px)",
167
+ opacity: 0,
168
+ y: -12,
169
+ }, transition: getTransition(shouldReduceMotion ?? false, {
170
+ duration: 0.5,
171
+ ease: motionEasing.smooth,
172
+ }), children: [_jsxs(motion.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 rounded-md px-1 -mx-1", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, animate: isActive || isSelecting
173
+ ? {
174
+ backgroundPosition: ["200% 0", "-200% 0"],
175
+ }
176
+ : {}, transition: isActive || isSelecting
177
+ ? {
178
+ ...shimmerTransition,
179
+ duration: getDuration(shouldReduceMotion ?? false, 1.5),
180
+ }
181
+ : {}, style: isActive || isSelecting
182
+ ? {
183
+ backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
184
+ backgroundSize: "200% 100%",
185
+ }
186
+ : {}, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: [isGrouped && _jsx("span", { className: "mr-1", children: "Parallel operation" }), !isGrouped && displayText] }), isGrouped && (_jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })), isFailed && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors" })) : (_jsx(motion.div, { animate: {
187
+ rotate: isExpanded ? 180 : 0,
188
+ }, transition: getTransition(shouldReduceMotion ?? false, {
189
+ duration: motionDuration.normal,
190
+ ease: motionEasing.smooth,
191
+ }), className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped && singleToolCall?.subline && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: singleToolCall.subline })), isGrouped && !isExpanded && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText }))] }), !isTodoWrite && isSubagentCall && singleToolCall && (_jsx("div", { className: "pl-4.5 mt-2", children: _jsx(SubAgentDetails, { port: singleToolCall.subagentPort, sessionId: singleToolCall.subagentSessionId, parentStatus: singleToolCall.status, agentName: singleToolCall.rawInput?.agentName, query: singleToolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: singleToolCall.subagentMessages, isReplay: isReplaySubagent }) })), _jsx(AnimatePresence, { initial: false, children: !isTodoWrite && isExpanded && (_jsx(motion.div, { initial: "collapsed", animate: "expanded", exit: "collapsed", variants: expandCollapseVariants, transition: getTransition(shouldReduceMotion ?? false, {
192
+ duration: motionDuration.normal,
193
+ ease: motionEasing.smooth,
194
+ }), className: "mt-1", children: isGrouped ? (
195
+ // Render individual tool calls in group
196
+ _jsx("div", { className: "flex flex-col gap-4 mt-2", children: toolCalls.map((toolCall) => (_jsx(GroupedToolCallItem, { toolCall: toolCall }, toolCall.id))) })) : (
197
+ // Render single tool call details
198
+ singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall }))) }, "expanded-content")) })] }));
199
+ }
200
+ /**
201
+ * Component to render a single tool call within a grouped parallel operation
202
+ * Handles subagent calls with their own expansion state
203
+ */
204
+ function GroupedToolCallItem({ toolCall }) {
205
+ const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
206
+ // Detect subagent calls
207
+ const hasLiveSubagent = !!(toolCall.subagentPort && toolCall.subagentSessionId);
208
+ const hasStoredSubagent = !!(toolCall.subagentMessages && toolCall.subagentMessages.length > 0);
209
+ const isSubagentCall = hasLiveSubagent || hasStoredSubagent;
210
+ const isReplaySubagent = hasStoredSubagent;
211
+ if (isSubagentCall) {
212
+ // Render subagent with clickable header and SubAgentDetails component
213
+ return (_jsxs("div", { className: "flex flex-col ml-5", children: [_jsxs("button", { type: "button", className: "flex items-center gap-1.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsSubagentExpanded(!isSubagentExpanded), "aria-expanded": isSubagentExpanded, children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(CircleDot, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: toolCall.rawInput?.agentName || "Subagent" }), _jsx(ChevronDown, { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors transition-transform duration-200 ${isSubagentExpanded ? "rotate-180" : ""}` })] }), _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, storedMessages: toolCall.subagentMessages, isReplay: isReplaySubagent }) })] }));
214
+ }
215
+ // Regular tool call - show details
216
+ return (_jsx("div", { className: "flex items-start gap-1.5", children: _jsx("div", { className: "flex-1 ml-5", children: _jsx(ToolOperationDetails, { toolCall: toolCall }) }) }));
217
+ }
218
+ /**
219
+ * Component to display detailed tool call information
220
+ */
221
+ function ToolOperationDetails({ toolCall }) {
222
+ const { resolvedTheme } = useTheme();
223
+ // Don't show details for preliminary tool calls
224
+ if (isPreliminaryToolCall(toolCall)) {
225
+ return (_jsx("div", { className: "text-paragraph-sm text-text-secondary/70 ml-2", children: getToolCallStateVerbiage(toolCall) }));
226
+ }
227
+ // JSON View style based on theme
228
+ const jsonStyle = {
229
+ fontSize: "11px",
230
+ backgroundColor: "transparent",
231
+ fontFamily: "inherit",
232
+ "--w-rjv-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
233
+ "--w-rjv-key-string": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
234
+ "--w-rjv-background-color": "transparent",
235
+ "--w-rjv-line-color": resolvedTheme === "dark" ? "#27272a" : "#e4e4e7",
236
+ "--w-rjv-arrow-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
237
+ "--w-rjv-edit-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
238
+ "--w-rjv-info-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
239
+ "--w-rjv-update-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
240
+ "--w-rjv-copied-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
241
+ "--w-rjv-copied-success-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
242
+ "--w-rjv-curlybraces-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
243
+ "--w-rjv-colon-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
244
+ "--w-rjv-brackets-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
245
+ "--w-rjv-quotes-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
246
+ "--w-rjv-quotes-string-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
247
+ "--w-rjv-type-string-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
248
+ "--w-rjv-type-int-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
249
+ "--w-rjv-type-float-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
250
+ "--w-rjv-type-bigint-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
251
+ "--w-rjv-type-boolean-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
252
+ "--w-rjv-type-date-color": resolvedTheme === "dark" ? "#ec4899" : "#db2777",
253
+ "--w-rjv-type-url-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
254
+ "--w-rjv-type-null-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
255
+ "--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
256
+ "--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
257
+ };
258
+ return (_jsxs("div", { className: "text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary 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 && loc.line !== undefined && `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary 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.content && toolCall.content.length > 0) ||
259
+ toolCall.error) && (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary 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) => {
260
+ // Generate a stable key based on content
261
+ const getBlockKey = () => {
262
+ if (block.type === "diff" && "path" in block) {
263
+ return `diff-${block.path}-${idx}`;
264
+ }
265
+ if (block.type === "terminal" && "terminalId" in block) {
266
+ return `terminal-${block.terminalId}`;
267
+ }
268
+ if (block.type === "text" && "text" in block) {
269
+ return `text-${block.text.substring(0, 20)}-${idx}`;
270
+ }
271
+ if (block.type === "content" && "content" in block) {
272
+ const innerContent = block.content;
273
+ return `content-${innerContent.text?.substring(0, 20)}-${idx}`;
274
+ }
275
+ return `block-${idx}`;
276
+ };
277
+ // Helper to render text content
278
+ const renderTextContent = (text, key) => {
279
+ try {
280
+ const parsed = JSON.parse(text);
281
+ if (typeof parsed === "object" && parsed !== null) {
282
+ return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) }, key));
283
+ }
284
+ }
285
+ catch {
286
+ // Not valid JSON
287
+ }
288
+ return (_jsx("pre", { className: "whitespace-pre-wrap font-mono text-[11px] text-foreground overflow-x-auto", children: text }, key));
289
+ };
290
+ // Handle nested content blocks
291
+ if (block.type === "content" && "content" in block) {
292
+ const innerContent = block.content;
293
+ if (innerContent.type === "text" && innerContent.text) {
294
+ return renderTextContent(innerContent.text, getBlockKey());
295
+ }
296
+ }
297
+ // Handle direct text blocks
298
+ if (block.type === "text" && "text" in block) {
299
+ return renderTextContent(block.text, getBlockKey());
300
+ }
301
+ // Handle image blocks
302
+ if (block.type === "image") {
303
+ const alt = block.alt || "Generated image";
304
+ let imageSrc;
305
+ if ("data" in block) {
306
+ const mimeType = block.mimeType || "image/png";
307
+ imageSrc = `data:${mimeType};base64,${block.data}`;
308
+ }
309
+ else if ("url" in block) {
310
+ imageSrc = block.url;
311
+ }
312
+ else {
313
+ return null;
314
+ }
315
+ 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()));
316
+ }
317
+ // Handle diff blocks
318
+ if (block.type === "diff" &&
319
+ "path" in block &&
320
+ "oldText" in block &&
321
+ "newText" in block) {
322
+ 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-text-secondary border-b border-border", children: [block.path, "line" in block &&
323
+ block.line !== null &&
324
+ block.line !== undefined &&
325
+ `:${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()));
326
+ }
327
+ return null;
328
+ }), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })), 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-b border-border last:border-0 flex flex-wrap gap-4 text-[10px] text-text-secondary 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)"] }))] }))] }))] }));
329
+ }
@@ -0,0 +1,20 @@
1
+ import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
2
+ export interface WorkProgressProps {
3
+ /** Optional thinking/reasoning content */
4
+ thinking?: string;
5
+ /** Whether thinking is currently streaming */
6
+ isThinkingStreaming?: boolean;
7
+ /** Tool calls to display */
8
+ toolCalls?: ToolCallType[];
9
+ /** Display style for thinking */
10
+ thinkingDisplayStyle?: "collapsible" | "inline";
11
+ /** Whether to auto-collapse thinking when done */
12
+ autoCollapseThinking?: boolean;
13
+ /** Additional CSS class */
14
+ className?: string;
15
+ }
16
+ /**
17
+ * WorkProgress component - coordinates display of thinking and tool execution
18
+ * Provides a unified view of the agent's work from thinking through execution
19
+ */
20
+ export declare function WorkProgress({ thinking, isThinkingStreaming, toolCalls, thinkingDisplayStyle, autoCollapseThinking, className, }: WorkProgressProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { getGroupDisplayState, isPreliminaryToolCall, } from "../../core/utils/tool-call-state.js";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Reasoning } from "./Reasoning.js";
6
+ import { ToolOperation } from "./ToolOperation.js";
7
+ /**
8
+ * WorkProgress component - coordinates display of thinking and tool execution
9
+ * Provides a unified view of the agent's work from thinking through execution
10
+ */
11
+ export function WorkProgress({ thinking, isThinkingStreaming = false, toolCalls = [], thinkingDisplayStyle = "collapsible", autoCollapseThinking = true, className, }) {
12
+ // Group tool calls by batchId and type
13
+ const groupedToolCalls = React.useMemo(() => {
14
+ if (toolCalls.length === 0)
15
+ return [];
16
+ const result = [];
17
+ const batchMap = new Map();
18
+ let selectingGroup = [];
19
+ const flushSelectingGroup = () => {
20
+ if (selectingGroup.length > 0) {
21
+ if (selectingGroup.length > 1) {
22
+ result.push({ type: "selecting", toolCalls: selectingGroup });
23
+ }
24
+ else {
25
+ result.push({ type: "single", toolCall: selectingGroup[0] });
26
+ }
27
+ selectingGroup = [];
28
+ }
29
+ };
30
+ for (const toolCall of toolCalls) {
31
+ // Handle batch groups (parallel operations)
32
+ if (toolCall.batchId) {
33
+ flushSelectingGroup();
34
+ const existing = batchMap.get(toolCall.batchId);
35
+ if (existing) {
36
+ existing.push(toolCall);
37
+ }
38
+ else {
39
+ const group = [toolCall];
40
+ batchMap.set(toolCall.batchId, group);
41
+ result.push({
42
+ type: "batch",
43
+ batchId: toolCall.batchId,
44
+ toolCalls: group,
45
+ });
46
+ }
47
+ }
48
+ // Handle consecutive preliminary (selecting) tool calls
49
+ else if (isPreliminaryToolCall(toolCall)) {
50
+ selectingGroup.push(toolCall);
51
+ }
52
+ // Regular tool call
53
+ else {
54
+ flushSelectingGroup();
55
+ result.push({ type: "single", toolCall });
56
+ }
57
+ }
58
+ // Flush any remaining selecting group
59
+ flushSelectingGroup();
60
+ return result;
61
+ }, [toolCalls]);
62
+ const hasThinking = !!thinking;
63
+ const hasToolCalls = toolCalls.length > 0;
64
+ if (!hasThinking && !hasToolCalls) {
65
+ return null;
66
+ }
67
+ return (_jsxs("div", { className: cn("work-progress", className), children: [hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: isThinkingStreaming, mode: thinkingDisplayStyle, autoCollapse: autoCollapseThinking })), groupedToolCalls.map((group, index) => {
68
+ if (group.type === "batch") {
69
+ // Parallel operations group
70
+ return (_jsx(ToolOperation, { toolCalls: group.toolCalls, isGrouped: true, autoMinimize: true }, `batch-${group.batchId}`));
71
+ }
72
+ if (group.type === "selecting") {
73
+ // Multiple selecting operations
74
+ return (_jsx(ToolOperation, { toolCalls: group.toolCalls, isGrouped: true, autoMinimize: false }, `selecting-${index}`));
75
+ }
76
+ // Single tool call
77
+ return (_jsx(ToolOperation, { toolCalls: [group.toolCall], isGrouped: false, autoMinimize: true }, group.toolCall.id));
78
+ })] }));
79
+ }
@@ -2,6 +2,7 @@ export { toast } from "sonner";
2
2
  export { MockFileSystemProvider, mockFileSystemData, } from "../data/mockFileSystemData.js";
3
3
  export { mockSourceData } from "../data/mockSourceData.js";
4
4
  export type { FileSystemData, FileSystemItem as FileSystemItemData, FileSystemItemType, FileSystemProvider, } from "../types/filesystem.js";
5
+ export { AppSidebar, type AppSidebarProps, } from "./AppSidebar.js";
5
6
  export { Button, type ButtonProps, buttonVariants } from "./Button.js";
6
7
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
7
8
  export { ChatEmptyState, type ChatEmptyStateProps } from "./ChatEmptyState.js";
@@ -32,6 +33,11 @@ export { PanelTabsHeader, type PanelTabsHeaderProps, } from "./PanelTabsHeader.j
32
33
  export { Reasoning, type ReasoningProps } from "./Reasoning.js";
33
34
  export { Response, type ResponseProps } from "./Response.js";
34
35
  export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
36
+ export { SessionHistory, type SessionHistoryProps, } from "./SessionHistory.js";
37
+ export { SessionHistoryItem, type SessionHistoryItemProps, } from "./SessionHistoryItem.js";
38
+ export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, } from "./Sheet.js";
39
+ export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, } from "./Sidebar.js";
40
+ export { SidebarToggle } from "./SidebarToggle.js";
35
41
  export { Toaster } from "./Sonner.js";
36
42
  export { type SourceItem, SourceListItem, type SourceListItemProps, } from "./SourceListItem.js";
37
43
  export { SubAgentDetails, type SubAgentDetailsProps, } from "./SubAgentDetails.js";
@@ -43,6 +49,7 @@ export { ThemeToggle } from "./ThemeToggle.js";
43
49
  export { ThinkingBlock, type ThinkingBlockProps, } from "./ThinkingBlock.js";
44
50
  export { TodoList, type TodoListProps } from "./TodoList.js";
45
51
  export { type TodoItem, TodoListItem, type TodoListItemProps, } from "./TodoListItem.js";
46
- export { ToolCall, type ToolCallProps } from "./ToolCall.js";
47
52
  export { ToolCallList, type ToolCallListProps } from "./ToolCallList.js";
53
+ export { ToolOperation, type ToolOperationProps } from "./ToolOperation.js";
48
54
  export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
55
+ export { WorkProgress, type WorkProgressProps } from "./WorkProgress.js";
@@ -2,6 +2,8 @@
2
2
  export { toast } from "sonner";
3
3
  export { MockFileSystemProvider, mockFileSystemData, } from "../data/mockFileSystemData.js";
4
4
  export { mockSourceData } from "../data/mockSourceData.js";
5
+ // Sidebar components
6
+ export { AppSidebar, } from "./AppSidebar.js";
5
7
  export { Button, buttonVariants } from "./Button.js";
6
8
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
7
9
  export { ChatEmptyState } from "./ChatEmptyState.js";
@@ -37,6 +39,11 @@ export { PanelTabsHeader, } from "./PanelTabsHeader.js";
37
39
  export { Reasoning } from "./Reasoning.js";
38
40
  export { Response } from "./Response.js";
39
41
  export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
42
+ export { SessionHistory, } from "./SessionHistory.js";
43
+ export { SessionHistoryItem, } from "./SessionHistoryItem.js";
44
+ export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, } from "./Sheet.js";
45
+ export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, } from "./Sidebar.js";
46
+ export { SidebarToggle } from "./SidebarToggle.js";
40
47
  export { Toaster } from "./Sonner.js";
41
48
  // Toast components
42
49
  export { SourceListItem, } from "./SourceListItem.js";
@@ -50,7 +57,8 @@ export { ThemeToggle } from "./ThemeToggle.js";
50
57
  export { ThinkingBlock, } from "./ThinkingBlock.js";
51
58
  export { TodoList } from "./TodoList.js";
52
59
  export { TodoListItem, } from "./TodoListItem.js";
53
- export { ToolCall } from "./ToolCall.js";
54
60
  export { ToolCallList } from "./ToolCallList.js";
61
+ export { ToolOperation } from "./ToolOperation.js";
55
62
  // Tooltip components
56
63
  export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
64
+ export { WorkProgress } from "./WorkProgress.js";
@@ -0,0 +1 @@
1
+ export { useIsMobile } from "./use-mobile.js";
@@ -0,0 +1 @@
1
+ export { useIsMobile } from "./use-mobile.js";
@@ -0,0 +1 @@
1
+ export declare function useIsMobile(): boolean;
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ const MOBILE_BREAKPOINT = 768;
3
+ export function useIsMobile() {
4
+ const [isMobile, setIsMobile] = React.useState(undefined);
5
+ React.useEffect(() => {
6
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
7
+ const onChange = () => {
8
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9
+ };
10
+ mql.addEventListener("change", onChange);
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
+ return () => mql.removeEventListener("change", onChange);
13
+ }, []);
14
+ return !!isMobile;
15
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./components/index.js";
2
+ export { useIsMobile } from "./hooks/index.js";
2
3
  export { cn } from "./lib/utils.js";
package/dist/gui/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // Re-export all components
2
2
  export * from "./components/index.js";
3
+ // Re-export hooks
4
+ export { useIsMobile } from "./hooks/index.js";
3
5
  // Re-export utilities
4
6
  export { cn } from "./lib/utils.js";
@@ -0,0 +1,55 @@
1
+ import type { Transition, Variants } from "framer-motion";
2
+ /**
3
+ * Motion utilities and reusable animation variants for the UI package
4
+ * Provides consistent animation behavior across components
5
+ */
6
+ export declare const motionDuration: {
7
+ readonly fast: 0.15;
8
+ readonly normal: 0.25;
9
+ readonly slow: 0.4;
10
+ };
11
+ export declare const motionEasing: {
12
+ readonly smooth: readonly [0.25, 0.1, 0.25, 1];
13
+ readonly bounce: readonly [0.68, -0.55, 0.265, 1.55];
14
+ readonly sharp: readonly [0.4, 0, 0.2, 1];
15
+ readonly gentle: readonly [0.25, 0.46, 0.45, 0.94];
16
+ };
17
+ export declare const fadeInVariants: Variants;
18
+ export declare const fadeInUpVariants: Variants;
19
+ export declare const expandCollapseVariants: Variants;
20
+ export declare const slideDownVariants: Variants;
21
+ export declare const shimmerTransition: Transition;
22
+ export declare const shimmerVariants: Variants;
23
+ export declare const scaleInVariants: Variants;
24
+ export declare const pulseVariants: Variants;
25
+ export declare const rotateVariants: Variants;
26
+ export declare const standardTransition: Transition;
27
+ export declare const fastTransition: Transition;
28
+ export declare const slowTransition: Transition;
29
+ export declare const springTransition: Transition;
30
+ export declare const gentleSpringTransition: Transition;
31
+ export declare const layoutTransition: Transition;
32
+ export declare const staggerChildren: {
33
+ visible: {
34
+ transition: {
35
+ staggerChildren: number;
36
+ };
37
+ };
38
+ };
39
+ export declare const staggerChildrenFast: {
40
+ visible: {
41
+ transition: {
42
+ staggerChildren: number;
43
+ };
44
+ };
45
+ };
46
+ export declare const slideInFromRightVariants: Variants;
47
+ export declare const slideOutToRightVariants: Variants;
48
+ /**
49
+ * Get transition with reduced motion consideration
50
+ */
51
+ export declare function getTransition(shouldReduceMotion: boolean, transition?: Transition): Transition;
52
+ /**
53
+ * Get duration with reduced motion consideration
54
+ */
55
+ export declare function getDuration(shouldReduceMotion: boolean, duration?: number): number;