@townco/ui 0.1.77 → 0.1.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/hooks/use-chat-messages.d.ts +6 -4
- package/dist/core/hooks/use-chat-messages.js +4 -1
- package/dist/core/hooks/use-chat-session.d.ts +1 -1
- package/dist/core/hooks/use-chat-session.js +5 -1
- package/dist/core/hooks/use-subagent-stream.js +6 -6
- package/dist/core/hooks/use-tool-calls.d.ts +5 -3
- package/dist/core/hooks/use-tool-calls.js +1 -1
- package/dist/core/schemas/chat.d.ts +14 -10
- package/dist/core/schemas/tool-call.d.ts +21 -8
- package/dist/core/schemas/tool-call.js +15 -0
- package/dist/core/store/chat-store.js +1 -0
- package/dist/core/utils/tool-summary.js +8 -3
- package/dist/core/utils/tool-verbiage.js +1 -1
- package/dist/gui/components/AppSidebar.d.ts +1 -1
- package/dist/gui/components/AppSidebar.js +4 -3
- package/dist/gui/components/ChatEmptyState.js +1 -1
- package/dist/gui/components/ChatHeader.d.ts +1 -28
- package/dist/gui/components/ChatHeader.js +4 -71
- package/dist/gui/components/ChatLayout.d.ts +6 -2
- package/dist/gui/components/ChatLayout.js +82 -33
- package/dist/gui/components/ChatView.js +28 -45
- package/dist/gui/components/ContextUsageButton.d.ts +0 -1
- package/dist/gui/components/ContextUsageButton.js +10 -3
- package/dist/gui/components/HookNotification.js +2 -1
- package/dist/gui/components/MessageContent.js +24 -160
- package/dist/gui/components/SessionHistory.js +1 -2
- package/dist/gui/components/SessionHistoryItem.js +1 -1
- package/dist/gui/components/Sidebar.js +27 -42
- package/dist/gui/components/SubAgentDetails.js +10 -14
- package/dist/gui/components/TodoSubline.js +1 -0
- package/dist/gui/components/ToolOperation.js +117 -81
- package/dist/gui/components/WorkProgress.js +5 -3
- package/dist/gui/components/index.d.ts +0 -1
- package/dist/gui/components/resizable.d.ts +1 -1
- package/dist/gui/constants.d.ts +6 -0
- package/dist/gui/constants.js +8 -0
- package/dist/gui/hooks/index.d.ts +1 -0
- package/dist/gui/hooks/index.js +1 -0
- package/dist/gui/hooks/use-lock-body-scroll.d.ts +7 -0
- package/dist/gui/hooks/use-lock-body-scroll.js +29 -0
- package/dist/gui/lib/motion.d.ts +12 -0
- package/dist/gui/lib/motion.js +69 -0
- package/dist/sdk/schemas/session.d.ts +37 -24
- package/dist/sdk/transports/http.d.ts +1 -1
- package/dist/sdk/transports/http.js +99 -1
- package/dist/sdk/transports/stdio.js +2 -2
- package/dist/sdk/transports/types.d.ts +11 -0
- package/dist/sdk/transports/types.js +28 -1
- package/package.json +3 -5
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import JsonView from "@uiw/react-json-view";
|
|
3
|
-
import {
|
|
4
|
-
import { AlertCircle, BrainCircuit, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, ListVideo, Search, Wrench, } from "lucide-react";
|
|
3
|
+
import { AlertCircle, BrainCircuit, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, FoldVertical, Globe, Image, Link, ListVideo, ScissorsLineDashed, Search, Wrench, } from "lucide-react";
|
|
5
4
|
import React, { useEffect, useState } from "react";
|
|
6
|
-
import {
|
|
5
|
+
import { getGroupDisplayState, getToolCallDisplayState, getToolCallStateVerbiage, isPreliminaryToolCall, } from "../../core/utils/tool-call-state.js";
|
|
7
6
|
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
7
|
import * as ChatLayout from "./ChatLayout.js";
|
|
11
8
|
import { SubAgentDetails } from "./SubAgentDetails.js";
|
|
12
9
|
import { useTheme } from "./ThemeProvider.js";
|
|
13
10
|
import { TodoSubline } from "./TodoSubline.js";
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
|
|
14
12
|
/**
|
|
15
13
|
* Map of icon names to Lucide components
|
|
16
14
|
*/
|
|
@@ -27,6 +25,17 @@ const ICON_MAP = {
|
|
|
27
25
|
BrainCircuit: BrainCircuit,
|
|
28
26
|
CircleDot: CircleDot,
|
|
29
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* CompactionDetails component - shows detailed stats when tool response was compacted
|
|
30
|
+
*/
|
|
31
|
+
function CompactionDetails({ compactionAction, originalTokens, finalTokens, originalContentPath, }) {
|
|
32
|
+
// Calculate stats
|
|
33
|
+
const tokensSaved = originalTokens && finalTokens ? originalTokens - finalTokens : undefined;
|
|
34
|
+
const reductionPercent = originalTokens && finalTokens
|
|
35
|
+
? Math.round((1 - finalTokens / originalTokens) * 100)
|
|
36
|
+
: undefined;
|
|
37
|
+
return (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [compactionAction === "compacted" ? (_jsx(FoldVertical, { className: "w-3.5 h-3.5 text-text-secondary" })) : (_jsx(ScissorsLineDashed, { className: "w-3.5 h-3.5 text-destructive" })), _jsxs("span", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider font-sans", children: ["Response", " ", compactionAction === "compacted" ? "Compacted" : "Truncated"] })] }), _jsxs("div", { className: "grid grid-cols-4 gap-3 mb-3", children: [originalTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Original" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [originalTokens.toLocaleString(), " tokens"] })] })), finalTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Compacted" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [finalTokens.toLocaleString(), " tokens"] })] })), tokensSaved !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Saved" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [tokensSaved.toLocaleString(), " tokens"] })] })), reductionPercent !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Reduction" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [reductionPercent, "%"] })] }))] }), originalContentPath && (_jsxs("div", { className: "text-[10px] text-text-secondary font-sans", children: [_jsx("span", { className: "font-medium", children: "Original saved to:" }), " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded text-[9px]", children: originalContentPath })] }))] }));
|
|
38
|
+
}
|
|
30
39
|
/**
|
|
31
40
|
* ToolOperation component - unified display for tool calls
|
|
32
41
|
* Handles both individual and grouped tool calls with smooth transitions
|
|
@@ -35,8 +44,6 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
35
44
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
36
45
|
const [isMinimized, setIsMinimized] = useState(false);
|
|
37
46
|
const [userInteracted, setUserInteracted] = useState(false);
|
|
38
|
-
const { resolvedTheme } = useTheme();
|
|
39
|
-
const shouldReduceMotion = useReducedMotion();
|
|
40
47
|
// For single tool call, extract it
|
|
41
48
|
const singleToolCall = toolCalls.length === 1 ? toolCalls[0] : null;
|
|
42
49
|
const isTodoWrite = singleToolCall?.title === "todo_write";
|
|
@@ -44,6 +51,31 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
44
51
|
const toolHookNotification = singleToolCall
|
|
45
52
|
? hookNotifications.find((n) => n.toolCallId === singleToolCall.id)
|
|
46
53
|
: undefined;
|
|
54
|
+
// Detect if compaction was applied (from hook notification or persisted _meta)
|
|
55
|
+
const hasCompaction = !!((toolHookNotification?.status === "completed" &&
|
|
56
|
+
toolHookNotification.metadata?.action &&
|
|
57
|
+
toolHookNotification.metadata.action !== "no_action_needed" &&
|
|
58
|
+
toolHookNotification.metadata.action !== "none") ||
|
|
59
|
+
singleToolCall?._meta?.compactionAction);
|
|
60
|
+
// Detect if truncation was used (as opposed to intelligent compaction)
|
|
61
|
+
const isTruncation = !!(toolHookNotification?.metadata?.action === "truncated" ||
|
|
62
|
+
toolHookNotification?.metadata?.action === "compacted_then_truncated" ||
|
|
63
|
+
singleToolCall?._meta?.compactionAction === "truncated");
|
|
64
|
+
// For grouped tool calls, check if any have compaction
|
|
65
|
+
const groupHasCompaction = isGrouped
|
|
66
|
+
? toolCalls.some((tc) => tc._meta?.compactionAction ||
|
|
67
|
+
hookNotifications.some((n) => n.toolCallId === tc.id &&
|
|
68
|
+
n.status === "completed" &&
|
|
69
|
+
n.metadata?.action &&
|
|
70
|
+
n.metadata.action !== "no_action_needed" &&
|
|
71
|
+
n.metadata.action !== "none"))
|
|
72
|
+
: false;
|
|
73
|
+
const groupHasTruncation = isGrouped
|
|
74
|
+
? toolCalls.some((tc) => tc._meta?.compactionAction === "truncated" ||
|
|
75
|
+
hookNotifications.some((n) => n.toolCallId === tc.id &&
|
|
76
|
+
(n.metadata?.action === "truncated" ||
|
|
77
|
+
n.metadata?.action === "compacted_then_truncated")))
|
|
78
|
+
: false;
|
|
47
79
|
// Detect subagent calls
|
|
48
80
|
const hasLiveSubagent = !!(singleToolCall?.subagentPort && singleToolCall?.subagentSessionId);
|
|
49
81
|
const hasStoredSubagent = !!(singleToolCall?.subagentMessages &&
|
|
@@ -63,7 +95,6 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
63
95
|
const isCompleted = displayState === "completed";
|
|
64
96
|
const isFailed = displayState === "failed";
|
|
65
97
|
const isSelecting = displayState === "selecting";
|
|
66
|
-
const isActive = displayState === "executing";
|
|
67
98
|
// Auto-minimize when completed (only if user hasn't manually interacted)
|
|
68
99
|
useEffect(() => {
|
|
69
100
|
if (autoMinimize && isCompleted && !isMinimized && !userInteracted) {
|
|
@@ -136,64 +167,31 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
136
167
|
const displayText = getDisplayText();
|
|
137
168
|
// For preliminary/selecting states, show simple non-expandable text
|
|
138
169
|
if (isSelecting && !isGrouped) {
|
|
139
|
-
return (_jsx(
|
|
140
|
-
backgroundPosition: ["200% 0", "-200% 0"],
|
|
141
|
-
}, transition: {
|
|
142
|
-
...shimmerTransition,
|
|
143
|
-
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
144
|
-
}, style: {
|
|
145
|
-
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
146
|
-
backgroundSize: "200% 100%",
|
|
147
|
-
}, children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/50", children: displayText }) }));
|
|
170
|
+
return (_jsx("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/50", children: displayText }) }));
|
|
148
171
|
}
|
|
149
172
|
// If it's a grouped preliminary (selecting) state
|
|
150
173
|
if (isSelecting && isGrouped) {
|
|
151
|
-
return (_jsxs(
|
|
152
|
-
backgroundPosition: ["200% 0", "-200% 0"],
|
|
153
|
-
}, transition: {
|
|
154
|
-
...shimmerTransition,
|
|
155
|
-
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
156
|
-
}, style: {
|
|
157
|
-
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
158
|
-
backgroundSize: "200% 100%",
|
|
159
|
-
}, 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 })] }));
|
|
174
|
+
return (_jsxs("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", 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 })] }));
|
|
160
175
|
}
|
|
161
176
|
// Full display (for single tool call or expanded group, includes minimized state)
|
|
162
|
-
return (_jsxs(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
: {}, transition: isActive || isSelecting
|
|
182
|
-
? {
|
|
183
|
-
...shimmerTransition,
|
|
184
|
-
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
185
|
-
}
|
|
186
|
-
: {}, style: isActive || isSelecting
|
|
187
|
-
? {
|
|
188
|
-
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
189
|
-
backgroundSize: "200% 100%",
|
|
190
|
-
}
|
|
191
|
-
: {}, 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: {
|
|
192
|
-
rotate: isExpanded ? 180 : 0,
|
|
193
|
-
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
194
|
-
duration: motionDuration.normal,
|
|
195
|
-
ease: motionEasing.smooth,
|
|
196
|
-
}), 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 &&
|
|
177
|
+
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 rounded-md px-1 -mx-1", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, 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("span", { title: isGrouped
|
|
178
|
+
? `${toolCalls.filter((tc) => tc.status === "failed").length} of ${toolCalls.length} operations failed`
|
|
179
|
+
: singleToolCall?.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), isGrouped && groupHasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: groupHasTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: groupHasTruncation
|
|
180
|
+
? "Some responses were truncated"
|
|
181
|
+
: "Some responses were compacted" })] }) })), !isGrouped && hasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: isTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: (() => {
|
|
182
|
+
const meta = singleToolCall?._meta;
|
|
183
|
+
const percentage = meta?.originalTokens && meta?.finalTokens
|
|
184
|
+
? Math.round((1 - meta.finalTokens / meta.originalTokens) * 100)
|
|
185
|
+
: null;
|
|
186
|
+
if (isTruncation) {
|
|
187
|
+
return percentage
|
|
188
|
+
? `Response truncated (${percentage}% reduction)`
|
|
189
|
+
: "Response was truncated";
|
|
190
|
+
}
|
|
191
|
+
return percentage
|
|
192
|
+
? `Response compacted (${percentage}% reduction)`
|
|
193
|
+
: "Response was compacted";
|
|
194
|
+
})() })] }) })), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors" })) : (_jsx("div", { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-all duration-200 ${isExpanded ? "rotate-180" : ""}`, children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped &&
|
|
197
195
|
singleToolCall &&
|
|
198
196
|
(isTodoWrite && singleToolCall.rawInput?.todos ? (_jsx(TodoSubline, { todos: singleToolCall.rawInput.todos, className: "text-paragraph-sm text-text-secondary/70 pl-4.5" })) : singleToolCall.subline ? (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: singleToolCall.subline })) : null), !isGrouped && toolHookNotification && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: toolHookNotification.status === "triggered"
|
|
199
197
|
? "Compacting response..."
|
|
@@ -218,37 +216,70 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
|
|
|
218
216
|
return (_jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: [compactedCount, " response", compactedCount > 1 ? "s" : "", " ", "compacted"] }));
|
|
219
217
|
}
|
|
220
218
|
return null;
|
|
221
|
-
})()] }), !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 }) })),
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall, ...(toolHookNotification
|
|
232
|
-
? { hookNotification: toolHookNotification }
|
|
233
|
-
: {}) }))) }, "expanded-content")) })] }));
|
|
219
|
+
})()] }), !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 }) })), !isTodoWrite && isExpanded && (_jsx("div", { className: "mt-1", children: isGrouped ? (
|
|
220
|
+
// Render individual tool calls in group
|
|
221
|
+
_jsx("div", { className: "flex flex-col gap-4 mt-2", children: toolCalls.map((toolCall) => {
|
|
222
|
+
const hookNotification = hookNotifications.find((n) => n.toolCallId === toolCall.id);
|
|
223
|
+
return (_jsx(GroupedToolCallItem, { toolCall: toolCall, ...(hookNotification ? { hookNotification } : {}) }, toolCall.id));
|
|
224
|
+
}) })) : (
|
|
225
|
+
// Render single tool call details
|
|
226
|
+
singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall, ...(toolHookNotification
|
|
227
|
+
? { hookNotification: toolHookNotification }
|
|
228
|
+
: {}) }))) }))] }));
|
|
234
229
|
}
|
|
235
230
|
/**
|
|
236
231
|
* Component to render a single tool call within a grouped parallel operation
|
|
237
|
-
*
|
|
232
|
+
* Each tool call is individually expandable and collapsed by default
|
|
238
233
|
*/
|
|
239
234
|
function GroupedToolCallItem({ toolCall, hookNotification, }) {
|
|
240
|
-
const [
|
|
235
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
241
236
|
// Detect subagent calls
|
|
242
237
|
const hasLiveSubagent = !!(toolCall.subagentPort && toolCall.subagentSessionId);
|
|
243
238
|
const hasStoredSubagent = !!(toolCall.subagentMessages && toolCall.subagentMessages.length > 0);
|
|
244
239
|
const isSubagentCall = hasLiveSubagent || hasStoredSubagent;
|
|
245
240
|
const isReplaySubagent = hasStoredSubagent;
|
|
241
|
+
// Detect compaction for this individual tool call
|
|
242
|
+
const hasCompaction = !!((hookNotification?.status === "completed" &&
|
|
243
|
+
hookNotification.metadata?.action &&
|
|
244
|
+
hookNotification.metadata.action !== "no_action_needed" &&
|
|
245
|
+
hookNotification.metadata.action !== "none") ||
|
|
246
|
+
toolCall._meta?.compactionAction);
|
|
247
|
+
const isTruncation = !!(hookNotification?.metadata?.action === "truncated" ||
|
|
248
|
+
hookNotification?.metadata?.action === "compacted_then_truncated" ||
|
|
249
|
+
toolCall._meta?.compactionAction === "truncated");
|
|
250
|
+
const isFailed = toolCall.status === "failed";
|
|
246
251
|
if (isSubagentCall) {
|
|
247
252
|
// Render subagent with clickable header and SubAgentDetails component
|
|
248
|
-
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: () =>
|
|
253
|
+
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: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, 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" }), isFailed && (_jsx("span", { title: toolCall.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), hasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: isTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: (() => {
|
|
254
|
+
const meta = toolCall._meta;
|
|
255
|
+
const percentage = meta?.originalTokens && meta?.finalTokens
|
|
256
|
+
? Math.round((1 - meta.finalTokens / meta.originalTokens) * 100)
|
|
257
|
+
: null;
|
|
258
|
+
if (isTruncation) {
|
|
259
|
+
return percentage
|
|
260
|
+
? `Response truncated (${percentage}% reduction)`
|
|
261
|
+
: "Response was truncated";
|
|
262
|
+
}
|
|
263
|
+
return percentage
|
|
264
|
+
? `Response compacted (${percentage}% reduction)`
|
|
265
|
+
: "Response was compacted";
|
|
266
|
+
})() })] }) })), _jsx(ChevronDown, { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors transition-transform duration-200 ${isExpanded ? "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: isExpanded, onExpandChange: setIsExpanded, storedMessages: toolCall.subagentMessages, isReplay: isReplaySubagent }) })] }));
|
|
249
267
|
}
|
|
250
|
-
// Regular tool call -
|
|
251
|
-
return (
|
|
268
|
+
// Regular tool call - collapsible with clickable header
|
|
269
|
+
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: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: [_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: toolCall.prettyName || toolCall.title }), isFailed && (_jsx("span", { title: toolCall.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), hasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: isTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: (() => {
|
|
270
|
+
const meta = toolCall._meta;
|
|
271
|
+
const percentage = meta?.originalTokens && meta?.finalTokens
|
|
272
|
+
? Math.round((1 - meta.finalTokens / meta.originalTokens) * 100)
|
|
273
|
+
: null;
|
|
274
|
+
if (isTruncation) {
|
|
275
|
+
return percentage
|
|
276
|
+
? `Response truncated (${percentage}% reduction)`
|
|
277
|
+
: "Response was truncated";
|
|
278
|
+
}
|
|
279
|
+
return percentage
|
|
280
|
+
? `Response compacted (${percentage}% reduction)`
|
|
281
|
+
: "Response was compacted";
|
|
282
|
+
})() })] }) })), _jsx(ChevronDown, { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }), isExpanded && (_jsx("div", { className: "mt-1", children: _jsx(ToolOperationDetails, { toolCall: toolCall, ...(hookNotification ? { hookNotification } : {}) }) }))] }));
|
|
252
283
|
}
|
|
253
284
|
/**
|
|
254
285
|
* Component to display detailed tool call information
|
|
@@ -290,7 +321,7 @@ function ToolOperationDetails({ toolCall, hookNotification, }) {
|
|
|
290
321
|
"--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
291
322
|
"--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
|
|
292
323
|
};
|
|
293
|
-
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) ||
|
|
324
|
+
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, shortenTextAfterLength: 80, style: jsonStyle }) })] })), toolCall._meta?.compactionAction && (_jsx(CompactionDetails, { compactionAction: toolCall._meta.compactionAction, originalTokens: toolCall._meta.originalTokens, finalTokens: toolCall._meta.finalTokens, originalContentPath: toolCall._meta.originalContentPath })), ((toolCall.content && toolCall.content.length > 0) ||
|
|
294
325
|
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) => {
|
|
295
326
|
// Generate a stable key based on content
|
|
296
327
|
const getBlockKey = () => {
|
|
@@ -314,7 +345,12 @@ function ToolOperationDetails({ toolCall, hookNotification, }) {
|
|
|
314
345
|
try {
|
|
315
346
|
const parsed = JSON.parse(text);
|
|
316
347
|
if (typeof parsed === "object" && parsed !== null) {
|
|
317
|
-
|
|
348
|
+
// Filter out internal metadata fields from display
|
|
349
|
+
const displayValue = { ...parsed };
|
|
350
|
+
if ("_compactionMeta" in displayValue) {
|
|
351
|
+
delete displayValue._compactionMeta;
|
|
352
|
+
}
|
|
353
|
+
return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: displayValue, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) }, key));
|
|
318
354
|
}
|
|
319
355
|
}
|
|
320
356
|
catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { isPreliminaryToolCall } from "../../core/utils/tool-call-state.js";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Reasoning } from "./Reasoning.js";
|
|
6
6
|
import { ToolOperation } from "./ToolOperation.js";
|
|
@@ -22,6 +22,7 @@ export function WorkProgress({ thinking, isThinkingStreaming = false, toolCalls
|
|
|
22
22
|
result.push({ type: "selecting", toolCalls: selectingGroup });
|
|
23
23
|
}
|
|
24
24
|
else {
|
|
25
|
+
// biome-ignore lint/style/noNonNullAssertion: else branch ensures array has exactly one element
|
|
25
26
|
result.push({ type: "single", toolCall: selectingGroup[0] });
|
|
26
27
|
}
|
|
27
28
|
selectingGroup = [];
|
|
@@ -64,14 +65,15 @@ export function WorkProgress({ thinking, isThinkingStreaming = false, toolCalls
|
|
|
64
65
|
if (!hasThinking && !hasToolCalls) {
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
67
|
-
return (_jsxs("div", { className: cn("work-progress", className), children: [hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: isThinkingStreaming, mode: thinkingDisplayStyle, autoCollapse: autoCollapseThinking })), groupedToolCalls.map((group,
|
|
68
|
+
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
69
|
if (group.type === "batch") {
|
|
69
70
|
// Parallel operations group
|
|
70
71
|
return (_jsx(ToolOperation, { toolCalls: group.toolCalls, isGrouped: true, autoMinimize: true }, `batch-${group.batchId}`));
|
|
71
72
|
}
|
|
72
73
|
if (group.type === "selecting") {
|
|
73
74
|
// Multiple selecting operations
|
|
74
|
-
|
|
75
|
+
const selectingKey = group.toolCalls.map((tc) => tc.id).join("-");
|
|
76
|
+
return (_jsx(ToolOperation, { toolCalls: group.toolCalls, isGrouped: true, autoMinimize: false }, `selecting-${selectingKey}`));
|
|
75
77
|
}
|
|
76
78
|
// Single tool call
|
|
77
79
|
return (_jsx(ToolOperation, { toolCalls: [group.toolCall], isGrouped: false, autoMinimize: true }, group.toolCall.id));
|
|
@@ -6,7 +6,6 @@ export { AppSidebar, type AppSidebarProps, } from "./AppSidebar.js";
|
|
|
6
6
|
export { Button, type ButtonProps, buttonVariants } from "./Button.js";
|
|
7
7
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
|
|
8
8
|
export { ChatEmptyState, type ChatEmptyStateProps } from "./ChatEmptyState.js";
|
|
9
|
-
export type { ConnectionStatus } from "./ChatHeader.js";
|
|
10
9
|
export * as ChatHeader from "./ChatHeader.js";
|
|
11
10
|
export { Actions as ChatInputActions, Attachment as ChatInputAttachment, type ChatInputActionsProps, type ChatInputAttachmentProps, type ChatInputCommandMenuProps, type ChatInputFieldProps, type ChatInputRootProps, type ChatInputSubmitProps, type ChatInputToolbarProps, type ChatInputVoiceInputProps, CommandMenu as ChatInputCommandMenu, type CommandMenuItem, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, VoiceInput as ChatInputVoiceInput, } from "./ChatInput.js";
|
|
12
11
|
export * as ChatLayout from "./ChatLayout.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ResizablePrimitive from "react-resizable-panels";
|
|
2
|
-
declare const ResizablePanelGroup:
|
|
2
|
+
declare const ResizablePanelGroup: React.FC<React.ComponentProps<typeof ResizablePrimitive.PanelGroup>>;
|
|
3
3
|
declare const ResizablePanel: ({ className, ...props }: React.ComponentProps<typeof ResizablePrimitive.Panel>) => import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
declare const ResizableHandle: ({ withHandle, className, ...props }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
|
5
5
|
withHandle?: boolean;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const SIDEBAR_WIDTH_DESKTOP = 256;
|
|
2
|
+
export declare const SIDEBAR_WIDTH_MOBILE = "calc(100vw - 104px)";
|
|
3
|
+
export declare const SIDEBAR_TAP_ZONE = 104;
|
|
4
|
+
export declare const ASIDE_WIDTH_DEFAULT = 450;
|
|
5
|
+
export declare const ASIDE_WIDTH_MIN = 250;
|
|
6
|
+
export declare const ASIDE_WIDTH_MAX = 800;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Sidebar constants
|
|
2
|
+
export const SIDEBAR_WIDTH_DESKTOP = 256;
|
|
3
|
+
export const SIDEBAR_WIDTH_MOBILE = "calc(100vw - 104px)";
|
|
4
|
+
export const SIDEBAR_TAP_ZONE = 104;
|
|
5
|
+
// Aside panel constants
|
|
6
|
+
export const ASIDE_WIDTH_DEFAULT = 450;
|
|
7
|
+
export const ASIDE_WIDTH_MIN = 250;
|
|
8
|
+
export const ASIDE_WIDTH_MAX = 800;
|
package/dist/gui/hooks/index.js
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to lock body scroll when a modal/drawer is open
|
|
4
|
+
* Primarily used for mobile sidebar overlay to prevent background scrolling
|
|
5
|
+
*
|
|
6
|
+
* @param lock - Whether to lock the body scroll
|
|
7
|
+
*/
|
|
8
|
+
export function useLockBodyScroll(lock) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!lock)
|
|
11
|
+
return;
|
|
12
|
+
// Store current scroll position
|
|
13
|
+
const scrollY = window.scrollY;
|
|
14
|
+
// Lock body scroll
|
|
15
|
+
document.body.style.position = "fixed";
|
|
16
|
+
document.body.style.top = `-${scrollY}px`;
|
|
17
|
+
document.body.style.width = "100%";
|
|
18
|
+
document.body.style.overflow = "hidden";
|
|
19
|
+
return () => {
|
|
20
|
+
// Unlock body scroll
|
|
21
|
+
document.body.style.position = "";
|
|
22
|
+
document.body.style.top = "";
|
|
23
|
+
document.body.style.width = "";
|
|
24
|
+
document.body.style.overflow = "";
|
|
25
|
+
// Restore scroll position
|
|
26
|
+
window.scrollTo(0, scrollY);
|
|
27
|
+
};
|
|
28
|
+
}, [lock]);
|
|
29
|
+
}
|
package/dist/gui/lib/motion.d.ts
CHANGED
|
@@ -53,3 +53,15 @@ export declare function getTransition(shouldReduceMotion: boolean, transition?:
|
|
|
53
53
|
* Get duration with reduced motion consideration
|
|
54
54
|
*/
|
|
55
55
|
export declare function getDuration(shouldReduceMotion: boolean, duration?: number): number;
|
|
56
|
+
export declare const sidebarTransition: Transition;
|
|
57
|
+
export declare const sidebarMobileTransition: Transition;
|
|
58
|
+
export declare const sidebarDesktopVariants: Variants;
|
|
59
|
+
export declare const sidebarMobileVariants: Variants;
|
|
60
|
+
export declare const sidebarContentVariants: Variants;
|
|
61
|
+
export declare const sidebarContentTransition: Transition;
|
|
62
|
+
export declare const backdropVariants: Variants;
|
|
63
|
+
export declare const asideDesktopVariants: Variants;
|
|
64
|
+
export declare const asideMobileVariants: Variants;
|
|
65
|
+
export declare const asideContentVariants: Variants;
|
|
66
|
+
export declare const asideContentTransition: Transition;
|
|
67
|
+
export declare const asideMobileTransition: Transition;
|
package/dist/gui/lib/motion.js
CHANGED
|
@@ -215,3 +215,72 @@ export function getTransition(shouldReduceMotion, transition = standardTransitio
|
|
|
215
215
|
export function getDuration(shouldReduceMotion, duration = motionDuration.normal) {
|
|
216
216
|
return shouldReduceMotion ? 0.01 : duration;
|
|
217
217
|
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Sidebar Animations (AppSidebar)
|
|
220
|
+
// ============================================================================
|
|
221
|
+
export const sidebarTransition = {
|
|
222
|
+
duration: 0.5,
|
|
223
|
+
ease: motionEasing.smooth,
|
|
224
|
+
};
|
|
225
|
+
export const sidebarMobileTransition = {
|
|
226
|
+
duration: 0.3,
|
|
227
|
+
ease: motionEasing.smooth,
|
|
228
|
+
};
|
|
229
|
+
// Desktop: Slide animation (fixed width, no reflow)
|
|
230
|
+
export const sidebarDesktopVariants = {
|
|
231
|
+
initial: { x: "-100%" },
|
|
232
|
+
animate: { x: 0 },
|
|
233
|
+
exit: { x: "-100%" },
|
|
234
|
+
};
|
|
235
|
+
// Mobile: Slide animation (overlay from left)
|
|
236
|
+
export const sidebarMobileVariants = {
|
|
237
|
+
initial: { x: "-100%" },
|
|
238
|
+
animate: { x: 0 },
|
|
239
|
+
exit: { x: "-100%" },
|
|
240
|
+
};
|
|
241
|
+
// Sidebar content fade-in (snappy, only on open)
|
|
242
|
+
export const sidebarContentVariants = {
|
|
243
|
+
initial: { opacity: 0, x: -20 },
|
|
244
|
+
animate: { opacity: 1, x: 0 },
|
|
245
|
+
};
|
|
246
|
+
export const sidebarContentTransition = {
|
|
247
|
+
duration: 0.5,
|
|
248
|
+
ease: motionEasing.smooth,
|
|
249
|
+
delay: 0.25,
|
|
250
|
+
};
|
|
251
|
+
// Backdrop animation for mobile overlay
|
|
252
|
+
export const backdropVariants = {
|
|
253
|
+
initial: { opacity: 0 },
|
|
254
|
+
animate: { opacity: 1 },
|
|
255
|
+
exit: { opacity: 0 },
|
|
256
|
+
};
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// Aside Panel Animations (ChatLayout.Aside)
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Desktop: Width-based slide animation (inline panel, main content reflows)
|
|
261
|
+
export const asideDesktopVariants = {
|
|
262
|
+
initial: { width: 0 },
|
|
263
|
+
animate: { width: "auto" },
|
|
264
|
+
exit: { width: 0 },
|
|
265
|
+
};
|
|
266
|
+
// Mobile: Slide animation from right (full-screen overlay)
|
|
267
|
+
export const asideMobileVariants = {
|
|
268
|
+
initial: { x: "100%" },
|
|
269
|
+
animate: { x: 0 },
|
|
270
|
+
exit: { x: "100%" },
|
|
271
|
+
};
|
|
272
|
+
// Aside content fade-in (delayed, snappy)
|
|
273
|
+
export const asideContentVariants = {
|
|
274
|
+
initial: { opacity: 0, x: 20 },
|
|
275
|
+
animate: { opacity: 1, x: 0 },
|
|
276
|
+
};
|
|
277
|
+
export const asideContentTransition = {
|
|
278
|
+
duration: 0.35,
|
|
279
|
+
ease: motionEasing.smooth,
|
|
280
|
+
delay: 0.1,
|
|
281
|
+
};
|
|
282
|
+
// Mobile transition (faster, matches sidebar)
|
|
283
|
+
export const asideMobileTransition = {
|
|
284
|
+
duration: 0.3,
|
|
285
|
+
ease: motionEasing.smooth,
|
|
286
|
+
};
|