@townco/ui 0.1.83 → 0.1.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/hooks/use-chat-input.js +13 -6
- package/dist/core/hooks/use-chat-messages.d.ts +17 -0
- package/dist/core/hooks/use-chat-messages.js +294 -10
- package/dist/core/schemas/chat.d.ts +20 -0
- package/dist/core/schemas/chat.js +4 -0
- package/dist/core/schemas/index.d.ts +1 -0
- package/dist/core/schemas/index.js +1 -0
- package/dist/core/schemas/source.d.ts +22 -0
- package/dist/core/schemas/source.js +45 -0
- package/dist/core/store/chat-store.d.ts +4 -0
- package/dist/core/store/chat-store.js +54 -0
- package/dist/gui/components/Actions.d.ts +15 -0
- package/dist/gui/components/Actions.js +22 -0
- package/dist/gui/components/ChatInput.d.ts +9 -1
- package/dist/gui/components/ChatInput.js +24 -6
- package/dist/gui/components/ChatInputCommandMenu.d.ts +1 -0
- package/dist/gui/components/ChatInputCommandMenu.js +22 -5
- package/dist/gui/components/ChatInputParameters.d.ts +13 -0
- package/dist/gui/components/ChatInputParameters.js +67 -0
- package/dist/gui/components/ChatLayout.d.ts +2 -0
- package/dist/gui/components/ChatLayout.js +183 -61
- package/dist/gui/components/ChatPanelTabContent.d.ts +7 -0
- package/dist/gui/components/ChatPanelTabContent.js +17 -7
- package/dist/gui/components/ChatView.js +105 -15
- package/dist/gui/components/CitationChip.d.ts +15 -0
- package/dist/gui/components/CitationChip.js +72 -0
- package/dist/gui/components/EditableUserMessage.d.ts +18 -0
- package/dist/gui/components/EditableUserMessage.js +109 -0
- package/dist/gui/components/MessageActions.d.ts +16 -0
- package/dist/gui/components/MessageActions.js +97 -0
- package/dist/gui/components/MessageContent.js +22 -7
- package/dist/gui/components/Response.d.ts +3 -0
- package/dist/gui/components/Response.js +30 -3
- package/dist/gui/components/Sidebar.js +1 -1
- package/dist/gui/components/TodoSubline.js +1 -1
- package/dist/gui/components/WorkProgress.js +7 -0
- package/dist/gui/components/index.d.ts +6 -1
- package/dist/gui/components/index.js +6 -1
- package/dist/gui/hooks/index.d.ts +1 -0
- package/dist/gui/hooks/index.js +1 -0
- package/dist/gui/hooks/use-favicon.d.ts +6 -0
- package/dist/gui/hooks/use-favicon.js +47 -0
- package/dist/gui/hooks/use-scroll-to-bottom.d.ts +14 -0
- package/dist/gui/hooks/use-scroll-to-bottom.js +317 -1
- package/dist/gui/index.d.ts +1 -1
- package/dist/gui/index.js +1 -1
- package/dist/gui/lib/motion.js +6 -6
- package/dist/gui/lib/remark-citations.d.ts +28 -0
- package/dist/gui/lib/remark-citations.js +70 -0
- package/dist/sdk/client/acp-client.d.ts +38 -1
- package/dist/sdk/client/acp-client.js +67 -3
- package/dist/sdk/schemas/message.d.ts +40 -0
- package/dist/sdk/schemas/message.js +20 -0
- package/dist/sdk/transports/http.d.ts +24 -1
- package/dist/sdk/transports/http.js +189 -1
- package/dist/sdk/transports/stdio.d.ts +1 -0
- package/dist/sdk/transports/stdio.js +39 -0
- package/dist/sdk/transports/types.d.ts +46 -1
- package/dist/sdk/transports/websocket.d.ts +1 -0
- package/dist/sdk/transports/websocket.js +4 -0
- package/dist/tui/components/ChatView.js +3 -4
- package/package.json +5 -3
- package/src/styles/global.css +71 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AnimatePresence, motion
|
|
3
|
-
import { ArrowDown, X } from "lucide-react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
3
|
+
import { ArrowDown, ArrowUp, X } from "lucide-react";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { ASIDE_WIDTH_DEFAULT, ASIDE_WIDTH_MAX, ASIDE_WIDTH_MIN, } from "../constants.js";
|
|
6
6
|
import { useLockBodyScroll } from "../hooks/use-lock-body-scroll.js";
|
|
7
7
|
import { useIsMobile } from "../hooks/use-mobile.js";
|
|
8
8
|
import { useScrollToBottom } from "../hooks/use-scroll-to-bottom.js";
|
|
9
|
-
import { asideContentTransition, asideContentVariants, asideMobileTransition, asideMobileVariants,
|
|
9
|
+
import { asideContentTransition, asideContentVariants, asideMobileTransition, asideMobileVariants, } from "../lib/motion.js";
|
|
10
10
|
import { cn } from "../lib/utils.js";
|
|
11
11
|
import { IconButton } from "./IconButton.js";
|
|
12
12
|
import { Toaster } from "./Sonner.js";
|
|
@@ -28,6 +28,8 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
|
|
|
28
28
|
const isTogglingRef = React.useRef(false);
|
|
29
29
|
// Track if aside panel is being dragged (for Main reflow coordination)
|
|
30
30
|
const [isDraggingAside, setIsDraggingAside] = React.useState(false);
|
|
31
|
+
// Track aside panel width (for main content padding)
|
|
32
|
+
const [asideWidth, setAsideWidth] = React.useState(ASIDE_WIDTH_DEFAULT);
|
|
31
33
|
// Helper to toggle the right panel
|
|
32
34
|
const togglePanel = React.useCallback(() => {
|
|
33
35
|
// Prevent rapid toggling during animation
|
|
@@ -39,12 +41,25 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
|
|
|
39
41
|
// Reset after animation duration + buffer
|
|
40
42
|
setTimeout(() => {
|
|
41
43
|
isTogglingRef.current = false;
|
|
42
|
-
},
|
|
44
|
+
}, 350); // 250ms animation + 100ms buffer
|
|
43
45
|
}, []);
|
|
44
46
|
// Helper to set panel open/closed (for backwards compatibility)
|
|
45
47
|
const setPanelOpen = React.useCallback((open) => {
|
|
46
48
|
setPanelSize(open ? "large" : "hidden");
|
|
47
49
|
}, []);
|
|
50
|
+
// Keyboard shortcut to toggle right panel (Cmd/Ctrl+B)
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
const handleKeyDown = (event) => {
|
|
53
|
+
if (event.key.toLowerCase() === "b" &&
|
|
54
|
+
(event.metaKey || event.ctrlKey) &&
|
|
55
|
+
!event.shiftKey) {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
togglePanel();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
61
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
62
|
+
}, [togglePanel]);
|
|
48
63
|
return (_jsx(ChatLayoutContext.Provider, { value: {
|
|
49
64
|
sidebarOpen,
|
|
50
65
|
setSidebarOpen,
|
|
@@ -60,6 +75,8 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
|
|
|
60
75
|
togglePanel,
|
|
61
76
|
isDraggingAside,
|
|
62
77
|
setIsDraggingAside,
|
|
78
|
+
asideWidth,
|
|
79
|
+
setAsideWidth,
|
|
63
80
|
}, children: _jsx("div", { ref: ref, "data-panel-state": panelOpen ? "expanded" : "collapsed", className: cn("flex h-screen flex-row bg-background text-foreground overflow-hidden", className), ...props, children: children }) }));
|
|
64
81
|
});
|
|
65
82
|
ChatLayoutRoot.displayName = "ChatLayout.Root";
|
|
@@ -68,17 +85,13 @@ const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, re
|
|
|
68
85
|
});
|
|
69
86
|
ChatLayoutHeader.displayName = "ChatLayout.Header";
|
|
70
87
|
const ChatLayoutMain = React.forwardRef(({ className, children }, ref) => {
|
|
71
|
-
const { isDraggingAside } = useChatLayoutContext();
|
|
72
|
-
return (_jsx(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Smooth animation for panel open/close
|
|
79
|
-
duration: 0.5,
|
|
80
|
-
ease: motionEasing.smooth,
|
|
81
|
-
}, className: cn("flex flex-1 flex-col overflow-hidden h-full min-w-0", className), children: children }));
|
|
88
|
+
const { panelOpen, isDraggingAside, asideWidth } = useChatLayoutContext();
|
|
89
|
+
return (_jsx("div", { ref: ref, className: cn("flex flex-1 flex-col overflow-hidden h-full min-w-0",
|
|
90
|
+
// Use CSS transition for smooth reflow (like left sidebar does)
|
|
91
|
+
!isDraggingAside && "transition-[padding] duration-250", className), style: {
|
|
92
|
+
// Add padding when panel is open to make room for the fixed aside
|
|
93
|
+
paddingRight: panelOpen ? asideWidth : 0,
|
|
94
|
+
}, children: children }));
|
|
82
95
|
});
|
|
83
96
|
ChatLayoutMain.displayName = "ChatLayout.Main";
|
|
84
97
|
const ChatLayoutBody = React.forwardRef(({ showToaster = true, className, children, ...props }, ref) => {
|
|
@@ -86,7 +99,86 @@ const ChatLayoutBody = React.forwardRef(({ showToaster = true, className, childr
|
|
|
86
99
|
});
|
|
87
100
|
ChatLayoutBody.displayName = "ChatLayout.Body";
|
|
88
101
|
const ChatLayoutMessages = React.forwardRef(({ className, children, onScrollChange, showScrollToBottom = true, initialScrollToBottom = true, ...props }, ref) => {
|
|
89
|
-
const { containerRef, endRef, isAtBottom, scrollToBottom } = useScrollToBottom();
|
|
102
|
+
const { containerRef, endRef, isAtBottom, scrollToBottom, isUserMessageAboveFold, hasMoreUserMessagesBelow, userMessagesAboveCount, userMessagesBelowCount, scrollToPreviousUserMessage, scrollToNextUserMessage, scrollToUserMessageByIndex, getUserMessagePreviews, } = useScrollToBottom();
|
|
103
|
+
// State for hover menu
|
|
104
|
+
const [showMessageMenu, setShowMessageMenu] = React.useState(false);
|
|
105
|
+
const [messagePreviews, setMessagePreviews] = React.useState([]);
|
|
106
|
+
const hoverTimeoutRef = React.useRef(null);
|
|
107
|
+
// State for navigator pill visibility (only show when scrolling or cursor in top region)
|
|
108
|
+
const [isScrolling, setIsScrolling] = React.useState(false);
|
|
109
|
+
const [isCursorInTopRegion, setIsCursorInTopRegion] = React.useState(false);
|
|
110
|
+
const [isHoveringNav, setIsHoveringNav] = React.useState(false);
|
|
111
|
+
const scrollTimeoutRef = React.useRef(null);
|
|
112
|
+
// Track scrolling activity
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
const container = containerRef.current;
|
|
115
|
+
if (!container)
|
|
116
|
+
return;
|
|
117
|
+
const handleScroll = () => {
|
|
118
|
+
setIsScrolling(true);
|
|
119
|
+
// Clear existing timeout
|
|
120
|
+
if (scrollTimeoutRef.current) {
|
|
121
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
122
|
+
}
|
|
123
|
+
// Hide after 1.5 seconds of no scrolling
|
|
124
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
125
|
+
setIsScrolling(false);
|
|
126
|
+
}, 1500);
|
|
127
|
+
};
|
|
128
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
129
|
+
return () => {
|
|
130
|
+
container.removeEventListener("scroll", handleScroll);
|
|
131
|
+
if (scrollTimeoutRef.current) {
|
|
132
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}, [containerRef]);
|
|
136
|
+
// Track cursor position (show pill when cursor is in top 100px of the container)
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
const container = containerRef.current;
|
|
139
|
+
if (!container)
|
|
140
|
+
return;
|
|
141
|
+
const handleMouseMove = (e) => {
|
|
142
|
+
const containerRect = container.getBoundingClientRect();
|
|
143
|
+
const relativeY = e.clientY - containerRect.top;
|
|
144
|
+
const isInTopRegion = relativeY >= 0 && relativeY <= 100;
|
|
145
|
+
setIsCursorInTopRegion(isInTopRegion);
|
|
146
|
+
};
|
|
147
|
+
const handleMouseLeave = () => {
|
|
148
|
+
setIsCursorInTopRegion(false);
|
|
149
|
+
};
|
|
150
|
+
container.addEventListener("mousemove", handleMouseMove);
|
|
151
|
+
container.addEventListener("mouseleave", handleMouseLeave);
|
|
152
|
+
return () => {
|
|
153
|
+
container.removeEventListener("mousemove", handleMouseMove);
|
|
154
|
+
container.removeEventListener("mouseleave", handleMouseLeave);
|
|
155
|
+
};
|
|
156
|
+
}, [containerRef]);
|
|
157
|
+
// Show navigator pill only when scrolling, cursor in top region, or hovering over the nav
|
|
158
|
+
const showNavigatorPill = isUserMessageAboveFold &&
|
|
159
|
+
(isScrolling || isCursorInTopRegion || isHoveringNav);
|
|
160
|
+
// Handle mouse enter on navigation component
|
|
161
|
+
const handleNavMouseEnter = React.useCallback(() => {
|
|
162
|
+
if (hoverTimeoutRef.current) {
|
|
163
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
164
|
+
}
|
|
165
|
+
setIsHoveringNav(true);
|
|
166
|
+
const previews = getUserMessagePreviews();
|
|
167
|
+
setMessagePreviews(previews);
|
|
168
|
+
setShowMessageMenu(true);
|
|
169
|
+
}, [getUserMessagePreviews]);
|
|
170
|
+
// Handle mouse leave with delay
|
|
171
|
+
const handleNavMouseLeave = React.useCallback(() => {
|
|
172
|
+
hoverTimeoutRef.current = setTimeout(() => {
|
|
173
|
+
setShowMessageMenu(false);
|
|
174
|
+
setIsHoveringNav(false);
|
|
175
|
+
}, 150);
|
|
176
|
+
}, []);
|
|
177
|
+
// Handle click on a message in the menu
|
|
178
|
+
const handleMessageClick = React.useCallback((index) => {
|
|
179
|
+
scrollToUserMessageByIndex(index, "smooth");
|
|
180
|
+
setShowMessageMenu(false);
|
|
181
|
+
}, [scrollToUserMessageByIndex]);
|
|
90
182
|
const hasInitialScrolledRef = React.useRef(false);
|
|
91
183
|
// Merge refs
|
|
92
184
|
React.useImperativeHandle(ref, () => containerRef.current);
|
|
@@ -117,7 +209,17 @@ const ChatLayoutMessages = React.forwardRef(({ className, children, onScrollChan
|
|
|
117
209
|
return (_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [
|
|
118
210
|
_jsxs("div", { ref: containerRef, className: cn("h-full overflow-y-auto flex flex-col", className), ...props, children: [
|
|
119
211
|
_jsx("div", { className: "mx-auto max-w-chat flex-1 w-full flex flex-col", children: children }), _jsx("div", { ref: endRef, className: "shrink-0" })
|
|
120
|
-
] }),
|
|
212
|
+
] }), showNavigatorPill && (_jsxs("nav", { className: "absolute top-4 left-1/2 -translate-x-1/2 z-10", onMouseEnter: handleNavMouseEnter, onMouseLeave: handleNavMouseLeave, "aria-label": "User message navigation", children: [
|
|
213
|
+
_jsxs("div", { className: cn("flex items-center gap-0.5 rounded-full", "bg-card border border-border shadow-lg", "animate-in fade-in slide-in-from-top-2"), children: [userMessagesAboveCount > 0 && (_jsxs("button", { type: "button", onClick: () => scrollToPreviousUserMessage("smooth"), className: cn("flex items-center gap-1 py-2 pl-2.5 pr-2", hasMoreUserMessagesBelow
|
|
214
|
+
? "rounded-l-full"
|
|
215
|
+
: "rounded-full pr-2.5", "text-foreground", "hover:bg-accent hover:text-accent-foreground", "transition-colors duration-150"), "aria-label": `${userMessagesAboveCount} previous user message${userMessagesAboveCount !== 1 ? "s" : ""}`, children: [
|
|
216
|
+
_jsx(ArrowUp, { className: "size-4" }), _jsx("span", { className: "text-xs font-medium min-w-[1ch]", children: userMessagesAboveCount })
|
|
217
|
+
] })), userMessagesAboveCount > 0 && hasMoreUserMessagesBelow && (_jsx("div", { className: "w-px h-4 bg-border" })), hasMoreUserMessagesBelow && (_jsxs("button", { type: "button", onClick: () => scrollToNextUserMessage("smooth"), className: cn("flex items-center gap-1 py-2 pl-2 pr-2.5", userMessagesAboveCount > 0
|
|
218
|
+
? "rounded-r-full"
|
|
219
|
+
: "rounded-full pl-2.5", "text-foreground", "hover:bg-accent hover:text-accent-foreground", "transition-colors duration-150"), "aria-label": `${userMessagesBelowCount} next user message${userMessagesBelowCount !== 1 ? "s" : ""}`, children: [
|
|
220
|
+
_jsx("span", { className: "text-xs font-medium min-w-[1ch]", children: userMessagesBelowCount }), _jsx(ArrowDown, { className: "size-4" })
|
|
221
|
+
] }))] }), showMessageMenu && messagePreviews.length > 0 && (_jsx("div", { className: cn("absolute top-full left-1/2 -translate-x-1/2 mt-2", "w-72 max-h-64 overflow-y-auto", "bg-card border border-border rounded-lg shadow-xl", "animate-in fade-in slide-in-from-top-1 duration-150"), children: _jsx("div", { className: "p-1", children: messagePreviews.map(({ index, preview }) => (_jsxs("button", { type: "button", onClick: () => handleMessageClick(index), className: cn("w-full text-left px-3 py-2 rounded-md", "text-sm text-foreground", "hover:bg-accent hover:text-accent-foreground", "transition-colors duration-100", "truncate"), children: [
|
|
222
|
+
_jsxs("span", { className: "text-muted-foreground mr-2 text-xs", children: [index + 1, "."] }), preview] }, index))) }) }))] })), showScrollButton && (_jsx("button", { type: "button", onClick: () => scrollToBottom("smooth"), className: cn("absolute bottom-4 left-1/2 -translate-x-1/2 z-10", "flex items-center justify-center p-2 rounded-full", "bg-card border border-border shadow-lg", "text-foreground", "hover:bg-accent hover:text-accent-foreground", "transition-all duration-200 ease-in-out", "animate-in fade-in slide-in-from-bottom-2"), "aria-label": "Scroll to bottom", children: _jsx(ArrowDown, { className: "size-4" }) }))] }));
|
|
121
223
|
});
|
|
122
224
|
ChatLayoutMessages.displayName = "ChatLayout.Messages";
|
|
123
225
|
const ChatLayoutFooter = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
@@ -132,11 +234,11 @@ const ChatLayoutSidebar = React.forwardRef(({ className, children, ...props }, r
|
|
|
132
234
|
});
|
|
133
235
|
ChatLayoutSidebar.displayName = "ChatLayout.Sidebar";
|
|
134
236
|
const ChatLayoutAside = React.forwardRef(({ breakpoint = "md", onClose, className, children }, ref) => {
|
|
135
|
-
const { panelSize, togglePanel, setIsDraggingAside } = useChatLayoutContext();
|
|
237
|
+
const { panelSize, togglePanel, setIsDraggingAside, setAsideWidth } = useChatLayoutContext();
|
|
136
238
|
// State for committed width (persisted between renders)
|
|
137
239
|
const [committedWidth, setCommittedWidth] = React.useState(ASIDE_WIDTH_DEFAULT);
|
|
138
|
-
//
|
|
139
|
-
const
|
|
240
|
+
// State for current drag width (real-time updates during drag)
|
|
241
|
+
const [dragWidth, setDragWidth] = React.useState(committedWidth);
|
|
140
242
|
// Track if currently dragging for visual feedback
|
|
141
243
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
142
244
|
// Track drag start position and width
|
|
@@ -144,14 +246,34 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "md", onClose, classNam
|
|
|
144
246
|
const dragStartWidth = React.useRef(committedWidth);
|
|
145
247
|
// Hidden state - don't render
|
|
146
248
|
const isVisible = panelSize !== "hidden";
|
|
249
|
+
// Track if panel just mounted to animate from 0 width
|
|
250
|
+
const [hasAnimatedIn, setHasAnimatedIn] = React.useState(false);
|
|
251
|
+
// Reset animation state when panel becomes hidden
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
if (!isVisible) {
|
|
254
|
+
setHasAnimatedIn(false);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Small delay to ensure the element is in the DOM before transitioning
|
|
258
|
+
const timer = requestAnimationFrame(() => {
|
|
259
|
+
setHasAnimatedIn(true);
|
|
260
|
+
});
|
|
261
|
+
return () => cancelAnimationFrame(timer);
|
|
262
|
+
}, [isVisible]);
|
|
147
263
|
// Detect mobile viewport (< 768px)
|
|
148
264
|
const isMobile = useIsMobile();
|
|
149
265
|
// Lock body scroll when panel is open on mobile
|
|
150
266
|
useLockBodyScroll(isMobile && isVisible);
|
|
151
|
-
// Sync
|
|
267
|
+
// Sync drag width when committed width changes
|
|
152
268
|
React.useEffect(() => {
|
|
153
|
-
|
|
154
|
-
}, [committedWidth
|
|
269
|
+
setDragWidth(committedWidth);
|
|
270
|
+
}, [committedWidth]);
|
|
271
|
+
// Sync aside width to context (for main content padding)
|
|
272
|
+
React.useEffect(() => {
|
|
273
|
+
if (isVisible) {
|
|
274
|
+
setAsideWidth(isDragging ? dragWidth : committedWidth);
|
|
275
|
+
}
|
|
276
|
+
}, [isVisible, isDragging, dragWidth, committedWidth, setAsideWidth]);
|
|
155
277
|
// Handle pointer down to start drag
|
|
156
278
|
const handlePointerDown = React.useCallback((e) => {
|
|
157
279
|
e.preventDefault();
|
|
@@ -168,8 +290,8 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "md", onClose, classNam
|
|
|
168
290
|
return;
|
|
169
291
|
const deltaX = e.clientX - dragStartX.current;
|
|
170
292
|
const newWidth = Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, dragStartWidth.current - deltaX));
|
|
171
|
-
|
|
172
|
-
}, [isDragging
|
|
293
|
+
setDragWidth(newWidth);
|
|
294
|
+
}, [isDragging]);
|
|
173
295
|
// Handle pointer up to end drag
|
|
174
296
|
const handlePointerUp = React.useCallback((e) => {
|
|
175
297
|
if (!isDragging)
|
|
@@ -177,45 +299,45 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "md", onClose, classNam
|
|
|
177
299
|
const deltaX = e.clientX - dragStartX.current;
|
|
178
300
|
const newWidth = Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, dragStartWidth.current - deltaX));
|
|
179
301
|
setCommittedWidth(newWidth);
|
|
180
|
-
|
|
302
|
+
setDragWidth(newWidth);
|
|
181
303
|
setIsDragging(false);
|
|
182
304
|
setIsDraggingAside(false); // Notify context drag ended
|
|
183
305
|
// Release pointer capture
|
|
184
306
|
e.target.releasePointerCapture(e.pointerId);
|
|
185
|
-
}, [isDragging,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
307
|
+
}, [isDragging, setIsDraggingAside]);
|
|
308
|
+
// Determine which component to show based on visibility and device
|
|
309
|
+
const showMobile = isVisible && isMobile;
|
|
310
|
+
const showDesktop = isVisible && !isMobile;
|
|
311
|
+
return (_jsxs(AnimatePresence, { initial: false, children: [showMobile && (_jsx(motion.aside, { variants: asideMobileVariants, initial: "initial", animate: "animate", exit: "exit", transition: asideMobileTransition, className: cn("fixed inset-0 z-50 bg-card", "flex flex-col",
|
|
312
|
+
// Only visible on mobile (< 768px)
|
|
313
|
+
"block md:hidden", className), "data-aside": "aside", "data-mobile": "true", children: _jsxs(motion.div, { className: "flex flex-col h-full", variants: asideContentVariants, initial: "initial", animate: "animate", transition: asideContentTransition, children: [
|
|
314
|
+
_jsx("div", { className: "flex justify-end px-4 pt-3 shrink-0", children: _jsx(IconButton, { onClick: () => {
|
|
315
|
+
onClose?.();
|
|
316
|
+
togglePanel();
|
|
317
|
+
}, "aria-label": "Close panel", children: _jsx(X, { className: "size-4" }) }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: children })
|
|
318
|
+
] }) }, "aside-mobile")), showDesktop && (_jsx("aside", { ref: ref, style: {
|
|
319
|
+
// Animate from 0 on mount, then to committedWidth
|
|
320
|
+
width: isDragging
|
|
321
|
+
? dragWidth
|
|
322
|
+
: hasAnimatedIn
|
|
323
|
+
? committedWidth
|
|
324
|
+
: 0,
|
|
325
|
+
transition: isDragging
|
|
326
|
+
? "none"
|
|
327
|
+
: "width 0.25s cubic-bezier(0.25, 0.1, 0.25, 1)",
|
|
328
|
+
}, className: cn(
|
|
329
|
+
// Fixed position on the right (like left sidebar)
|
|
330
|
+
"fixed inset-y-0 right-0 z-40",
|
|
331
|
+
// Hidden by default, visible at breakpoint
|
|
332
|
+
"hidden h-full border-l border-border bg-card overflow-hidden",
|
|
333
|
+
// Breakpoint visibility
|
|
334
|
+
breakpoint === "sm" && "sm:block", breakpoint === "md" && "md:block", breakpoint === "lg" && "lg:block", breakpoint === "xl" && "xl:block", breakpoint === "2xl" && "2xl:block", className), "data-aside": "aside", children: _jsxs("div", { className: "flex flex-col h-full", children: [
|
|
335
|
+
_jsx("div", { onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, className: cn("absolute left-0 inset-y-0 w-2 -ml-1 cursor-col-resize z-10", "group flex items-center justify-center", "hover:bg-primary/5 transition-colors delay-300", isDragging && "bg-primary/10") }), _jsx("div", { className: cn("h-full overflow-y-auto", isDragging ? "w-full" : undefined), style: !isDragging
|
|
336
|
+
? {
|
|
337
|
+
width: `${committedWidth}px`,
|
|
338
|
+
}
|
|
339
|
+
: undefined, children: children })
|
|
340
|
+
] }) }, "aside-desktop"))] }));
|
|
219
341
|
});
|
|
220
342
|
ChatLayoutAside.displayName = "ChatLayout.Aside";
|
|
221
343
|
/* -------------------------------------------------------------------------------------------------
|
|
@@ -18,6 +18,8 @@ export interface FilesTabContentProps extends React.HTMLAttributes<HTMLDivElemen
|
|
|
18
18
|
export declare const FilesTabContent: React.ForwardRefExoticComponent<FilesTabContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
19
19
|
export interface SourcesTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
20
|
sources?: SourceItem[];
|
|
21
|
+
/** ID of the source to highlight (for scroll-to behavior) */
|
|
22
|
+
highlightedSourceId?: string;
|
|
21
23
|
}
|
|
22
24
|
export declare const SourcesTabContent: React.ForwardRefExoticComponent<SourcesTabContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
23
25
|
export interface DatabaseTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -30,6 +32,11 @@ export interface SettingsTabContentProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
30
32
|
description?: string;
|
|
31
33
|
prettyName?: string;
|
|
32
34
|
icon?: string;
|
|
35
|
+
children?: Array<{
|
|
36
|
+
name: string;
|
|
37
|
+
prettyName: string;
|
|
38
|
+
icon?: string;
|
|
39
|
+
}>;
|
|
33
40
|
}>;
|
|
34
41
|
mcps?: Array<{
|
|
35
42
|
name: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Globe } from "lucide-react";
|
|
2
3
|
import * as React from "react";
|
|
3
|
-
import { mockSourceData } from "../data/mockSourceData.js";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { FileSystemView } from "./FileSystemView.js";
|
|
6
6
|
import { SourceListItem } from "./SourceListItem.js";
|
|
@@ -31,10 +31,14 @@ export const FilesTabContent = React.forwardRef(({ files = [], provider, onFileS
|
|
|
31
31
|
}, onDownload: handleDownload, onRename: handleRename, onDelete: handleDelete, className: "h-full" }) }));
|
|
32
32
|
});
|
|
33
33
|
FilesTabContent.displayName = "FilesTabContent";
|
|
34
|
-
export const SourcesTabContent = React.forwardRef(({ sources, className, ...props }, ref) => {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
export const SourcesTabContent = React.forwardRef(({ sources = [], highlightedSourceId, className, ...props }, ref) => {
|
|
35
|
+
// Show empty state if no sources
|
|
36
|
+
if (sources.length === 0) {
|
|
37
|
+
return (_jsxs("div", { ref: ref, className: cn("flex flex-col items-center justify-center h-full text-center py-8", className), ...props, children: [
|
|
38
|
+
_jsx(Globe, { className: "size-8 text-muted-foreground/50 mb-3" }), _jsx("p", { className: "text-paragraph-sm text-muted-foreground", children: "No sources yet" }), _jsx("p", { className: "text-caption text-muted-foreground/70 mt-1", children: "Sources from web searches and fetches will appear here" })
|
|
39
|
+
] }));
|
|
40
|
+
}
|
|
41
|
+
return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: sources.map((source) => (_jsx(SourceListItem, { source: source, isSelected: source.id === highlightedSourceId }, source.id))) }));
|
|
38
42
|
});
|
|
39
43
|
SourcesTabContent.displayName = "SourcesTabContent";
|
|
40
44
|
export const DatabaseTabContent = React.forwardRef(({ data, className, ...props }, ref) => {
|
|
@@ -46,11 +50,17 @@ export const DatabaseTabContent = React.forwardRef(({ data, className, ...props
|
|
|
46
50
|
] }));
|
|
47
51
|
});
|
|
48
52
|
DatabaseTabContent.displayName = "DatabaseTabContent";
|
|
53
|
+
const ToolItem = ({ tool }) => {
|
|
54
|
+
const hasChildren = tool.children && tool.children.length > 0;
|
|
55
|
+
return (_jsxs("div", { className: "p-3 border border-border rounded-lg bg-muted/30", children: [
|
|
56
|
+
_jsx("div", { className: "font-medium text-paragraph-sm", children: tool.prettyName || tool.name }), tool.description && !hasChildren && (_jsx("div", { className: "text-caption text-muted-foreground mt-1 line-clamp-1", children: tool.description })), hasChildren && (_jsxs("div", { className: "text-caption text-muted-foreground mt-1", children: ["Built-in tools:", " ", tool.children
|
|
57
|
+
?.map((child) => child.prettyName || child.name)
|
|
58
|
+
.join(", ")] }))] }));
|
|
59
|
+
};
|
|
49
60
|
export const SettingsTabContent = React.forwardRef(({ tools = [], mcps = [], subagents = [], className, ...props }, ref) => {
|
|
50
61
|
return (_jsxs("div", { ref: ref, className: cn("space-y-6", className), ...props, children: [
|
|
51
62
|
_jsxs("div", { className: "space-y-3 p-2", children: [
|
|
52
|
-
_jsx("h3", { className: "font-semibold text-subheading", children: "Tools" }), tools.length > 0 ? (_jsx("div", { className: "space-y-2", children: tools.map((tool) => (
|
|
53
|
-
_jsx("div", { className: "font-medium text-paragraph-sm", children: tool.prettyName || tool.name }), tool.description && (_jsx("div", { className: "text-caption text-muted-foreground mt-1 line-clamp-1", children: tool.description }))] }, tool.name))) })) : (_jsx("p", { className: "text-paragraph-sm text-muted-foreground", children: "No tools available" }))] }), _jsxs("div", { className: "space-y-3 p-2", children: [
|
|
63
|
+
_jsx("h3", { className: "font-semibold text-subheading", children: "Tools" }), tools.length > 0 ? (_jsx("div", { className: "space-y-2", children: tools.map((tool) => (_jsx(ToolItem, { tool: tool }, tool.name))) })) : (_jsx("p", { className: "text-paragraph-sm text-muted-foreground", children: "No tools available" }))] }), _jsxs("div", { className: "space-y-3 p-2", children: [
|
|
54
64
|
_jsx("h3", { className: "font-semibold text-subheading", children: "MCP Servers" }), mcps.length > 0 ? (_jsx("div", { className: "space-y-2", children: mcps.map((mcp) => (_jsxs("div", { className: "p-3 border border-border rounded-lg bg-muted/30", children: [
|
|
55
65
|
_jsx("div", { className: "font-medium text-paragraph-sm", children: mcp.name }), _jsxs("div", { className: "text-caption text-muted-foreground mt-1", children: ["Transport: ", mcp.transport] })
|
|
56
66
|
] }, mcp.name))) })) : (_jsx("p", { className: "text-paragraph-sm text-muted-foreground", children: "No MCP servers connected" }))] }), _jsxs("div", { className: "space-y-3 p-2", children: [
|