@townco/ui 0.1.76 → 0.1.78
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 +4 -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 +3 -3
- package/dist/core/hooks/use-tool-calls.js +1 -1
- package/dist/core/schemas/chat.d.ts +10 -10
- package/dist/core/schemas/tool-call.d.ts +8 -8
- 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/Button.d.ts +1 -1
- 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 +16 -75
- 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/message.d.ts +2 -2
- package/dist/sdk/schemas/session.d.ts +18 -18
- package/dist/sdk/transports/http.d.ts +1 -1
- package/dist/sdk/transports/http.js +9 -0
- 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
- package/dist/gui/components/InvokingGroup.d.ts +0 -9
- package/dist/gui/components/InvokingGroup.js +0 -16
- package/dist/gui/components/SubagentStream.d.ts +0 -23
- package/dist/gui/components/SubagentStream.js +0 -98
- package/dist/gui/components/ToolCall.d.ts +0 -8
- package/dist/gui/components/ToolCall.js +0 -234
- package/dist/gui/components/ToolCallGroup.d.ts +0 -8
- package/dist/gui/components/ToolCallGroup.js +0 -29
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { AnimatePresence, motion, useMotionValue } from "framer-motion";
|
|
3
|
-
import { ArrowDown } from "lucide-react";
|
|
3
|
+
import { ArrowDown, X } from "lucide-react";
|
|
4
4
|
import * as React from "react";
|
|
5
|
+
import { ASIDE_WIDTH_DEFAULT, ASIDE_WIDTH_MAX, ASIDE_WIDTH_MIN, } from "../constants.js";
|
|
6
|
+
import { useLockBodyScroll } from "../hooks/use-lock-body-scroll.js";
|
|
7
|
+
import { useIsMobile } from "../hooks/use-mobile.js";
|
|
5
8
|
import { useScrollToBottom } from "../hooks/use-scroll-to-bottom.js";
|
|
6
|
-
import { motionEasing } from "../lib/motion.js";
|
|
9
|
+
import { asideContentTransition, asideContentVariants, asideMobileTransition, asideMobileVariants, motionEasing, } from "../lib/motion.js";
|
|
7
10
|
import { cn } from "../lib/utils.js";
|
|
11
|
+
import { IconButton } from "./IconButton.js";
|
|
8
12
|
import { Toaster } from "./Sonner.js";
|
|
9
13
|
const ChatLayoutContext = React.createContext(undefined);
|
|
10
14
|
const useChatLayoutContext = () => {
|
|
@@ -18,17 +22,29 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
|
|
|
18
22
|
const [sidebarOpen, setSidebarOpen] = React.useState(defaultSidebarOpen);
|
|
19
23
|
const [panelSize, setPanelSize] = React.useState(defaultPanelSize);
|
|
20
24
|
const [activeTab, setActiveTab] = React.useState(defaultActiveTab);
|
|
21
|
-
//
|
|
22
|
-
const
|
|
25
|
+
// Derive panelOpen from panelSize (single source of truth)
|
|
26
|
+
const panelOpen = React.useMemo(() => panelSize !== "hidden", [panelSize]);
|
|
27
|
+
// Track if toggle is in progress to prevent rapid toggling
|
|
28
|
+
const isTogglingRef = React.useRef(false);
|
|
29
|
+
// Track if aside panel is being dragged (for Main reflow coordination)
|
|
30
|
+
const [isDraggingAside, setIsDraggingAside] = React.useState(false);
|
|
23
31
|
// Helper to toggle the right panel
|
|
24
32
|
const togglePanel = React.useCallback(() => {
|
|
33
|
+
// Prevent rapid toggling during animation
|
|
34
|
+
if (isTogglingRef.current) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
isTogglingRef.current = true;
|
|
25
38
|
setPanelSize((size) => (size === "hidden" ? "large" : "hidden"));
|
|
26
|
-
|
|
39
|
+
// Reset after animation duration + buffer
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
isTogglingRef.current = false;
|
|
42
|
+
}, 600); // 500ms animation + 100ms buffer
|
|
43
|
+
}, []);
|
|
44
|
+
// Helper to set panel open/closed (for backwards compatibility)
|
|
45
|
+
const setPanelOpen = React.useCallback((open) => {
|
|
46
|
+
setPanelSize(open ? "large" : "hidden");
|
|
27
47
|
}, []);
|
|
28
|
-
// Sync panelOpen with panelSize
|
|
29
|
-
React.useEffect(() => {
|
|
30
|
-
setPanelOpen(panelSize !== "hidden");
|
|
31
|
-
}, [panelSize]);
|
|
32
48
|
return (_jsx(ChatLayoutContext.Provider, { value: {
|
|
33
49
|
sidebarOpen,
|
|
34
50
|
setSidebarOpen,
|
|
@@ -42,6 +58,8 @@ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPa
|
|
|
42
58
|
panelOpen,
|
|
43
59
|
setPanelOpen,
|
|
44
60
|
togglePanel,
|
|
61
|
+
isDraggingAside,
|
|
62
|
+
setIsDraggingAside,
|
|
45
63
|
}, 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 }) }));
|
|
46
64
|
});
|
|
47
65
|
ChatLayoutRoot.displayName = "ChatLayout.Root";
|
|
@@ -50,10 +68,17 @@ const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, re
|
|
|
50
68
|
});
|
|
51
69
|
ChatLayoutHeader.displayName = "ChatLayout.Header";
|
|
52
70
|
const ChatLayoutMain = React.forwardRef(({ className, children }, ref) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
const { isDraggingAside } = useChatLayoutContext();
|
|
72
|
+
return (_jsx(motion.div, { ref: ref, layout: true, transition: isDraggingAside
|
|
73
|
+
? {
|
|
74
|
+
// Instant reflow during aside drag
|
|
75
|
+
duration: 0,
|
|
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 }));
|
|
57
82
|
});
|
|
58
83
|
ChatLayoutMain.displayName = "ChatLayout.Main";
|
|
59
84
|
const ChatLayoutBody = React.forwardRef(({ showToaster = true, className, children, ...props }, ref) => {
|
|
@@ -89,7 +114,7 @@ const ChatLayoutMessages = React.forwardRef(({ className, children, onScrollChan
|
|
|
89
114
|
}, [initialScrollToBottom, containerRef]);
|
|
90
115
|
// Show scroll button when not at bottom
|
|
91
116
|
const showScrollButton = !isAtBottom && showScrollToBottom;
|
|
92
|
-
return (_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [_jsxs("div", { ref: containerRef, className: cn("h-full overflow-y-auto flex flex-col", className), ...props, children: [_jsx("div", { className: "mx-auto max-w-chat flex-1 w-full flex flex-col", children: children }), _jsx("div", { ref: endRef, className: "
|
|
117
|
+
return (_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [_jsxs("div", { ref: containerRef, className: cn("h-full overflow-y-auto flex flex-col", className), ...props, children: [_jsx("div", { className: "mx-auto max-w-chat flex-1 w-full flex flex-col", children: children }), _jsx("div", { ref: endRef, className: "shrink-0" })] }), 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" }) }))] }));
|
|
93
118
|
});
|
|
94
119
|
ChatLayoutMessages.displayName = "ChatLayout.Messages";
|
|
95
120
|
const ChatLayoutFooter = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
@@ -103,10 +128,10 @@ const ChatLayoutSidebar = React.forwardRef(({ className, children, ...props }, r
|
|
|
103
128
|
return (_jsx("div", { ref: ref, className: cn("border-r border-border bg-card w-64 overflow-y-auto", className), ...props, children: children }));
|
|
104
129
|
});
|
|
105
130
|
ChatLayoutSidebar.displayName = "ChatLayout.Sidebar";
|
|
106
|
-
const ChatLayoutAside = React.forwardRef(({ breakpoint = "
|
|
107
|
-
const { panelSize } = useChatLayoutContext();
|
|
131
|
+
const ChatLayoutAside = React.forwardRef(({ breakpoint = "md", onClose, className, children }, ref) => {
|
|
132
|
+
const { panelSize, togglePanel, setIsDraggingAside } = useChatLayoutContext();
|
|
108
133
|
// State for committed width (persisted between renders)
|
|
109
|
-
const [committedWidth, setCommittedWidth] = React.useState(
|
|
134
|
+
const [committedWidth, setCommittedWidth] = React.useState(ASIDE_WIDTH_DEFAULT);
|
|
110
135
|
// Motion value for real-time drag updates
|
|
111
136
|
const widthMotionValue = useMotionValue(committedWidth);
|
|
112
137
|
// Track if currently dragging for visual feedback
|
|
@@ -116,6 +141,10 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "lg", className, childr
|
|
|
116
141
|
const dragStartWidth = React.useRef(committedWidth);
|
|
117
142
|
// Hidden state - don't render
|
|
118
143
|
const isVisible = panelSize !== "hidden";
|
|
144
|
+
// Detect mobile viewport (< 768px)
|
|
145
|
+
const isMobile = useIsMobile();
|
|
146
|
+
// Lock body scroll when panel is open on mobile
|
|
147
|
+
useLockBodyScroll(isMobile && isVisible);
|
|
119
148
|
// Sync motion value when committed width changes
|
|
120
149
|
React.useEffect(() => {
|
|
121
150
|
widthMotionValue.set(committedWidth);
|
|
@@ -124,17 +153,18 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "lg", className, childr
|
|
|
124
153
|
const handlePointerDown = React.useCallback((e) => {
|
|
125
154
|
e.preventDefault();
|
|
126
155
|
setIsDragging(true);
|
|
156
|
+
setIsDraggingAside(true); // Notify context for Main reflow
|
|
127
157
|
dragStartX.current = e.clientX;
|
|
128
158
|
dragStartWidth.current = committedWidth;
|
|
129
159
|
// Capture pointer for smooth dragging
|
|
130
160
|
e.target.setPointerCapture(e.pointerId);
|
|
131
|
-
}, [committedWidth]);
|
|
161
|
+
}, [committedWidth, setIsDraggingAside]);
|
|
132
162
|
// Handle pointer move during drag
|
|
133
163
|
const handlePointerMove = React.useCallback((e) => {
|
|
134
164
|
if (!isDragging)
|
|
135
165
|
return;
|
|
136
166
|
const deltaX = e.clientX - dragStartX.current;
|
|
137
|
-
const newWidth = Math.max(
|
|
167
|
+
const newWidth = Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, dragStartWidth.current - deltaX));
|
|
138
168
|
widthMotionValue.set(newWidth);
|
|
139
169
|
}, [isDragging, widthMotionValue]);
|
|
140
170
|
// Handle pointer up to end drag
|
|
@@ -142,24 +172,43 @@ const ChatLayoutAside = React.forwardRef(({ breakpoint = "lg", className, childr
|
|
|
142
172
|
if (!isDragging)
|
|
143
173
|
return;
|
|
144
174
|
const deltaX = e.clientX - dragStartX.current;
|
|
145
|
-
const newWidth = Math.max(
|
|
175
|
+
const newWidth = Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, dragStartWidth.current - deltaX));
|
|
146
176
|
setCommittedWidth(newWidth);
|
|
147
177
|
widthMotionValue.set(newWidth);
|
|
148
178
|
setIsDragging(false);
|
|
179
|
+
setIsDraggingAside(false); // Notify context drag ended
|
|
149
180
|
// Release pointer capture
|
|
150
181
|
e.target.releasePointerCapture(e.pointerId);
|
|
151
|
-
}, [isDragging, widthMotionValue]);
|
|
152
|
-
return (_jsx(AnimatePresence, { initial: false, mode: "wait", children: isVisible && (_jsxs(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
182
|
+
}, [isDragging, widthMotionValue, setIsDraggingAside]);
|
|
183
|
+
return (_jsx(AnimatePresence, { initial: false, mode: "wait", children: isVisible && (_jsxs(_Fragment, { children: [isMobile && (_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",
|
|
184
|
+
// Only visible on mobile (< 768px)
|
|
185
|
+
"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: [_jsx("div", { className: "flex justify-end px-4 pt-3 shrink-0", children: _jsx(IconButton, { onClick: () => {
|
|
186
|
+
onClose?.();
|
|
187
|
+
togglePanel();
|
|
188
|
+
}, "aria-label": "Close panel", children: _jsx(X, { className: "size-4" }) }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: children })] }) }, "aside-mobile")), !isMobile && (_jsx(motion.aside, { ref: ref, initial: { width: 0, opacity: 0 }, animate: {
|
|
189
|
+
width: committedWidth,
|
|
190
|
+
opacity: 1,
|
|
191
|
+
}, exit: { width: 0, opacity: 0 }, style: {
|
|
192
|
+
// Use motion value during drag for instant updates
|
|
193
|
+
width: isDragging ? widthMotionValue : undefined,
|
|
194
|
+
}, transition: isDragging
|
|
195
|
+
? {
|
|
196
|
+
// Instant updates during drag (no animation)
|
|
197
|
+
duration: 0,
|
|
198
|
+
}
|
|
199
|
+
: {
|
|
200
|
+
// Smooth animation for open/close
|
|
201
|
+
duration: 0.5,
|
|
202
|
+
ease: motionEasing.smooth,
|
|
203
|
+
}, className: cn(
|
|
204
|
+
// Hidden by default, visible at breakpoint
|
|
205
|
+
"hidden h-full border-l border-border bg-card overflow-hidden relative",
|
|
206
|
+
// Breakpoint visibility
|
|
207
|
+
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(motion.div, { className: "flex flex-col h-full", variants: asideContentVariants, initial: "initial", animate: "animate", transition: asideContentTransition, children: [_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
|
|
208
|
+
? {
|
|
209
|
+
width: `${committedWidth}px`,
|
|
210
|
+
}
|
|
211
|
+
: undefined, children: children })] }) }, "aside-desktop"))] })) }));
|
|
163
212
|
});
|
|
164
213
|
ChatLayoutAside.displayName = "ChatLayout.Aside";
|
|
165
214
|
/* -------------------------------------------------------------------------------------------------
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createLogger } from "@townco/core";
|
|
3
|
-
import { ArrowUp, Bug,
|
|
3
|
+
import { ArrowUp, Bug, PanelRight, Settings, X } from "lucide-react";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { useChatMessages, useChatSession, useToolCalls, } from "../../core/hooks/index.js";
|
|
6
6
|
import { selectTodosForCurrentSession, useChatStore, } from "../../core/store/chat-store.js";
|
|
7
7
|
import { cn } from "../lib/utils.js";
|
|
8
|
-
import { AppSidebar, ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SettingsTabContent, SidebarInset, SidebarProvider, SidebarToggle, SourcesTabContent, Tabs, TabsContent,
|
|
8
|
+
import { AppSidebar, ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SettingsTabContent, SidebarInset, SidebarProvider, SidebarToggle, SourcesTabContent, Tabs, TabsContent, TodoTabContent, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./index.js";
|
|
9
9
|
const logger = createLogger("gui");
|
|
10
10
|
// Helper component to provide openFiles callback
|
|
11
11
|
function OpenFilesButton({ children, }) {
|
|
@@ -33,7 +33,7 @@ function OpenFilesButton({ children, }) {
|
|
|
33
33
|
// Note: Keyboard shortcut (Cmd+B / Ctrl+B) for toggling the right panel
|
|
34
34
|
// is now handled internally by ChatLayout.Root
|
|
35
35
|
// Chat input with attachment handling
|
|
36
|
-
function ChatInputWithAttachments({ client, startSession, placeholder,
|
|
36
|
+
function ChatInputWithAttachments({ client, startSession, placeholder, commandMenuItems, }) {
|
|
37
37
|
const attachedFiles = useChatStore((state) => state.input.attachedFiles);
|
|
38
38
|
const addFileAttachment = useChatStore((state) => state.addFileAttachment);
|
|
39
39
|
const removeFileAttachment = useChatStore((state) => state.removeFileAttachment);
|
|
@@ -42,27 +42,22 @@ function ChatInputWithAttachments({ client, startSession, placeholder, latestCon
|
|
|
42
42
|
addFileAttachment(file);
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
-
return (_jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), attachedFiles.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 p-3 border-b border-border", children: attachedFiles.map((file, index) => (_jsxs("div", { className: "relative group rounded-md overflow-hidden border border-border", children: [_jsx("img", { src: `data:${file.mimeType};base64,${file.data}`, alt: file.name, className: "h-20 w-20 object-cover" }), _jsx("button", { type: "button", onClick: () => removeFileAttachment(index), className: "absolute top-1 right-1 p-1 rounded-full bg-background/80 hover:bg-background opacity-0 group-hover:opacity-100 transition-opacity", children: _jsx(X, { className: "size-3" }) })] },
|
|
45
|
+
return (_jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), attachedFiles.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 p-3 border-b border-border", children: attachedFiles.map((file, index) => (_jsxs("div", { className: "relative group rounded-md overflow-hidden border border-border", children: [_jsx("img", { src: `data:${file.mimeType};base64,${file.data}`, alt: file.name, className: "h-20 w-20 object-cover" }), _jsx("button", { type: "button", onClick: () => removeFileAttachment(index), className: "absolute top-1 right-1 p-1 rounded-full bg-background/80 hover:bg-background opacity-0 group-hover:opacity-100 transition-opacity", children: _jsx(X, { className: "size-3" }) })] }, `attached-${file.name}-${file.data.slice(0, 20)}`))) })), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true, onFilesDropped: handleFilesSelected }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, { onFilesSelected: handleFilesSelected })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputVoiceInput, {}), _jsx(ChatInputSubmit, { children: _jsx(ArrowUp, { className: "size-4" }) })] })] })] }));
|
|
46
46
|
}
|
|
47
47
|
// Controlled Tabs component for the aside panel
|
|
48
48
|
function AsideTabs({ todos, tools, mcps, subagents, }) {
|
|
49
49
|
const { activeTab, setActiveTab } = ChatLayout.useChatLayoutContext();
|
|
50
|
-
return (_jsxs(Tabs, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "flex flex-col h-full", children: [_jsx("div", { className: cn("border-b border-border bg-card", "px-4 py-2 h-16", "flex items-center"
|
|
51
|
-
}
|
|
52
|
-
// Mobile header component that uses ChatHeader context
|
|
53
|
-
function MobileHeader({ agentName, showHeader, }) {
|
|
54
|
-
const { isExpanded, setIsExpanded } = ChatHeader.useChatHeaderContext();
|
|
55
|
-
return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(SidebarToggle, {}), showHeader && (_jsx("span", { className: "text-heading-4 text-foreground", children: agentName }))] }), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle menu", onClick: () => setIsExpanded(!isExpanded), children: _jsx(ChevronUp, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isExpanded ? "" : "rotate-180") }) })] }));
|
|
50
|
+
return (_jsxs(Tabs, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "flex flex-col h-full", children: [_jsx("div", { className: cn("border-b border-border bg-card", "px-4 py-2 h-16", "flex items-center"), children: _jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources", "settings"], variant: "compact" }) }), _jsx(TabsContent, { value: "todo", className: "flex-1 p-4 mt-0", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "flex-1 p-4 mt-0", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "flex-1 p-4 mt-0", children: _jsx(SourcesTabContent, {}) }), _jsx(TabsContent, { value: "settings", className: "flex-1 p-4 mt-0 overflow-y-auto", children: _jsx(SettingsTabContent, { tools: tools ?? [], mcps: mcps ?? [], subagents: subagents ?? [] }) })] }));
|
|
56
51
|
}
|
|
57
52
|
// Header component that uses ChatLayout context (must be inside ChatLayout.Root)
|
|
58
|
-
function AppChatHeader({ agentName,
|
|
53
|
+
function AppChatHeader({ agentName, showHeader, sessionId, debuggerUrl, }) {
|
|
59
54
|
const { togglePanel, panelOpen } = ChatLayout.useChatLayoutContext();
|
|
60
55
|
const debuggerLink = debuggerUrl
|
|
61
56
|
? sessionId
|
|
62
57
|
? `${debuggerUrl}/sessions/${sessionId}`
|
|
63
58
|
: debuggerUrl
|
|
64
59
|
: null;
|
|
65
|
-
return (
|
|
60
|
+
return (_jsx(ChatHeader.Root, { className: cn("border-b border-border bg-card h-16"), children: _jsxs("div", { className: "flex items-center w-full py-5 px-4 lg:px-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(SidebarToggle, {}), showHeader && agentName && (_jsx("span", { className: "text-heading-4 text-foreground", children: agentName }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ContextUsageButton, {}), debuggerUrl && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(IconButton, { "aria-label": "View session in debugger", disabled: !debuggerLink, asChild: !!debuggerLink, children: debuggerLink ? (_jsx("a", { href: debuggerLink, target: "_blank", rel: "noopener noreferrer", children: _jsx(Bug, { className: "size-4 text-muted-foreground" }) })) : (_jsx(Bug, { className: "size-4 text-muted-foreground" })) }) }), _jsx(TooltipContent, { children: _jsx("p", { children: sessionId ? "View session in debugger" : "Open debugger" }) })] }) })), _jsx(IconButton, { "aria-label": "Toggle panel", onClick: togglePanel, "data-state": panelOpen ? "open" : "closed", children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] })] }) }));
|
|
66
61
|
}
|
|
67
62
|
export function ChatView({ client, initialSessionId, error: initError, debuggerUrl, }) {
|
|
68
63
|
// Use shared hooks from @townco/ui/core - MUST be called before any early returns
|
|
@@ -70,18 +65,16 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
70
65
|
const { messages, sendMessage } = useChatMessages(client, startSession);
|
|
71
66
|
useToolCalls(client); // Still need to subscribe to tool call events
|
|
72
67
|
const error = useChatStore((state) => state.error);
|
|
73
|
-
const [agentName, setAgentName] = useState(
|
|
74
|
-
const [agentDescription, setAgentDescription] = useState(
|
|
75
|
-
const [suggestedPrompts, setSuggestedPrompts] = useState(
|
|
76
|
-
"Search the web for the latest news on top tech company earnings, produce a summary for each company, and then a macro trend analysis of the tech industry. Use your todo list",
|
|
77
|
-
"What can you help me with?",
|
|
78
|
-
]);
|
|
68
|
+
const [agentName, setAgentName] = useState(undefined);
|
|
69
|
+
const [agentDescription, setAgentDescription] = useState(undefined);
|
|
70
|
+
const [suggestedPrompts, setSuggestedPrompts] = useState(undefined);
|
|
79
71
|
const [agentTools, setAgentTools] = useState([]);
|
|
80
72
|
const [agentMcps, setAgentMcps] = useState([]);
|
|
81
73
|
const [agentSubagents, setAgentSubagents] = useState([]);
|
|
82
|
-
const [isLargeScreen, setIsLargeScreen] = useState(typeof window !== "undefined" ? window.innerWidth >= 1024 : true);
|
|
83
74
|
const [placeholder, setPlaceholder] = useState("Type a message or / for commands...");
|
|
84
75
|
const [hideTopBar, setHideTopBar] = useState(false);
|
|
76
|
+
const todos = useChatStore(selectTodosForCurrentSession);
|
|
77
|
+
const _latestContextSize = useChatStore((state) => state.latestContextSize);
|
|
85
78
|
// Log connection status changes
|
|
86
79
|
useEffect(() => {
|
|
87
80
|
logger.debug("Connection status changed", { status: connectionStatus });
|
|
@@ -90,8 +83,9 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
90
83
|
}
|
|
91
84
|
}, [connectionStatus, error]);
|
|
92
85
|
// Get agent name from session metadata or client info
|
|
86
|
+
// Re-run when connectionStatus changes to pick up agentInfo after connect()
|
|
93
87
|
useEffect(() => {
|
|
94
|
-
if (client) {
|
|
88
|
+
if (client && connectionStatus === "connected") {
|
|
95
89
|
// Try to get from current session first
|
|
96
90
|
const session = client.getCurrentSession();
|
|
97
91
|
if (session?.metadata?.agentName) {
|
|
@@ -107,12 +101,16 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
107
101
|
else if (agentInfo.name) {
|
|
108
102
|
setAgentName(agentInfo.name);
|
|
109
103
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
// Set description - use agent's description or a default
|
|
105
|
+
setAgentDescription(agentInfo.description ||
|
|
106
|
+
"Start a conversation below to begin working with this agent.");
|
|
113
107
|
if (agentInfo.suggestedPrompts && agentInfo.suggestedPrompts.length > 0) {
|
|
114
108
|
setSuggestedPrompts(agentInfo.suggestedPrompts);
|
|
115
109
|
}
|
|
110
|
+
else {
|
|
111
|
+
// Set default prompts if none provided
|
|
112
|
+
setSuggestedPrompts([]);
|
|
113
|
+
}
|
|
116
114
|
// Get tools, MCPs, and subagents
|
|
117
115
|
if (agentInfo.tools && agentInfo.tools.length > 0) {
|
|
118
116
|
setAgentTools(agentInfo.tools);
|
|
@@ -132,21 +130,7 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
132
130
|
setHideTopBar(true);
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
|
-
}, [client,
|
|
136
|
-
// Monitor screen size changes and update isLargeScreen state
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
const mediaQuery = window.matchMedia("(min-width: 1024px)");
|
|
139
|
-
const handleChange = (e) => {
|
|
140
|
-
setIsLargeScreen(e.matches);
|
|
141
|
-
};
|
|
142
|
-
// Set initial value
|
|
143
|
-
setIsLargeScreen(mediaQuery.matches);
|
|
144
|
-
// Listen for changes
|
|
145
|
-
mediaQuery.addEventListener("change", handleChange);
|
|
146
|
-
return () => {
|
|
147
|
-
mediaQuery.removeEventListener("change", handleChange);
|
|
148
|
-
};
|
|
149
|
-
}, []);
|
|
133
|
+
}, [client, connectionStatus]);
|
|
150
134
|
// Handle prompt hover - temporarily show the full prompt as placeholder
|
|
151
135
|
const handlePromptHover = (prompt) => {
|
|
152
136
|
setPlaceholder(prompt);
|
|
@@ -159,9 +143,8 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
159
143
|
if (initError) {
|
|
160
144
|
return (_jsx("div", { className: "flex items-center justify-center h-screen bg-background", children: _jsxs("div", { className: "text-center p-8 max-w-md", children: [_jsx("h1", { className: "text-2xl font-bold text-destructive mb-4", children: "Initialization Error" }), _jsx("p", { className: "text-foreground mb-4", children: initError }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Failed to initialize the ACP client. Check the console for details." })] }) }));
|
|
161
145
|
}
|
|
162
|
-
const todos = useChatStore(selectTodosForCurrentSession);
|
|
163
146
|
// Dummy sources data based on Figma design
|
|
164
|
-
const
|
|
147
|
+
const _sources = [
|
|
165
148
|
{
|
|
166
149
|
id: "1",
|
|
167
150
|
title: "Boeing Scores Early Wins at Dubai Airshow",
|
|
@@ -195,8 +178,6 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
195
178
|
favicon: "https://www.google.com/s2/favicons?domain=theverge.com&sz=32",
|
|
196
179
|
},
|
|
197
180
|
];
|
|
198
|
-
// Get the latest context size from the session context
|
|
199
|
-
const latestContextSize = useChatStore((state) => state.latestContextSize);
|
|
200
181
|
// Command menu items for chat input
|
|
201
182
|
const commandMenuItems = [
|
|
202
183
|
{
|
|
@@ -210,13 +191,15 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
210
191
|
},
|
|
211
192
|
},
|
|
212
193
|
];
|
|
213
|
-
return (_jsxs(SidebarProvider, { defaultOpen: false, children: [_jsx(AppSidebar, { client: client, currentSessionId: sessionId }), _jsx(SidebarInset, { children: _jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [!hideTopBar && (_jsx(AppChatHeader, { agentName: agentName,
|
|
194
|
+
return (_jsxs(SidebarProvider, { defaultOpen: false, children: [_jsx(AppSidebar, { client: client, currentSessionId: sessionId }), _jsx(SidebarInset, { children: _jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [!hideTopBar && (_jsx(AppChatHeader, { agentName: agentName, showHeader: messages.length > 0, sessionId: sessionId, ...(debuggerUrl && { debuggerUrl }) })), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (
|
|
195
|
+
// Only render empty state once agent info is loaded
|
|
196
|
+
agentName !== undefined ? (_jsx(OpenFilesButton, { children: ({ openFiles, openSettings }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: agentDescription ?? "", suggestedPrompts: suggestedPrompts ?? [], onPromptClick: (prompt) => {
|
|
214
197
|
sendMessage(prompt);
|
|
215
198
|
setPlaceholder("Type a message or / for commands...");
|
|
216
199
|
logger.info("Prompt clicked", { prompt });
|
|
217
200
|
}, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles, onOpenSettings: openSettings, toolsAndMcpsCount: agentTools.length +
|
|
218
201
|
agentMcps.length +
|
|
219
|
-
agentSubagents.length }) })) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
|
|
202
|
+
agentSubagents.length }) })) })) : null) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
|
|
220
203
|
// Calculate dynamic spacing based on message sequence
|
|
221
204
|
const isFirst = index === 0;
|
|
222
205
|
const previousMessage = isFirst
|
|
@@ -241,5 +224,5 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
|
|
|
241
224
|
: "mt-6";
|
|
242
225
|
}
|
|
243
226
|
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
244
|
-
}) })) }), _jsx(ChatLayout.Footer, { children: _jsx(ChatInputWithAttachments, { client: client, startSession: startSession, placeholder: placeholder,
|
|
227
|
+
}) })) }), _jsx(ChatLayout.Footer, { children: _jsx(ChatInputWithAttachments, { client: client, startSession: startSession, placeholder: placeholder, commandMenuItems: commandMenuItems }) })] })] }), _jsx(ChatLayout.Aside, { breakpoint: "md", children: _jsx(AsideTabs, { todos: todos, tools: agentTools, mcps: agentMcps, subagents: agentSubagents }) })] }) })] }));
|
|
245
228
|
}
|
|
@@ -12,6 +12,5 @@ export interface ContextSize {
|
|
|
12
12
|
modelContextWindow?: number;
|
|
13
13
|
}
|
|
14
14
|
export interface ContextUsageButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
15
|
-
contextSize: ContextSize;
|
|
16
15
|
}
|
|
17
16
|
export declare const ContextUsageButton: React.ForwardRefExoticComponent<ContextUsageButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { useChatStore } from "../../core/store/chat-store.js";
|
|
3
4
|
import { cn } from "../lib/utils.js";
|
|
4
5
|
import { Button } from "./Button.js";
|
|
5
6
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
|
|
6
7
|
// Default context window for backward compatibility (should not be used in practice)
|
|
7
8
|
const DEFAULT_MODEL_CONTEXT_WINDOW = 200000;
|
|
8
|
-
export const ContextUsageButton = React.forwardRef(({
|
|
9
|
-
|
|
9
|
+
export const ContextUsageButton = React.forwardRef(({ className, ...props }, ref) => {
|
|
10
|
+
const latestContextSize = useChatStore((state) => state.latestContextSize);
|
|
11
|
+
// Don't render if no context size available
|
|
12
|
+
if (latestContextSize == null) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const contextSize = latestContextSize;
|
|
16
|
+
// Use max of estimated and LLM-reported tokens (LLM reported as fallback if higher)
|
|
10
17
|
const actualTokens = Math.max(contextSize.totalEstimated, contextSize.llmReportedInputTokens ?? 0);
|
|
11
18
|
// Use model context window from backend, or default for backward compatibility
|
|
12
19
|
const modelContextWindow = contextSize.modelContextWindow ?? DEFAULT_MODEL_CONTEXT_WINDOW;
|
|
@@ -36,7 +43,7 @@ export const ContextUsageButton = React.forwardRef(({ contextSize, className, ..
|
|
|
36
43
|
const center = size / 2;
|
|
37
44
|
const circumference = 2 * Math.PI * radius;
|
|
38
45
|
const offset = circumference - (clampedPercentage / 100) * circumference;
|
|
39
|
-
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full cursor-default", colorClass, className), ...props, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "transform -rotate-90", children: [_jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", className: "opacity-20" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", className: "transition-all duration-300 ease-in-out" })] }) }) }), _jsx(TooltipContent, { side: "top", align: "center", className: "p-3", children: _jsxs("div", { className: "space-y-2 font-mono text-xs", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "System Prompt:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.systemPromptTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.systemPromptTokens), ")"] })] })] }), contextSize.toolOverheadTokens !== undefined &&
|
|
46
|
+
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full cursor-default", colorClass, className), ...props, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "transform -rotate-90", children: [_jsx("title", { children: "Context usage indicator" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", className: "opacity-20" }), _jsx("circle", { cx: center, cy: center, r: radius, stroke: "currentColor", strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", className: "transition-all duration-300 ease-in-out" })] }) }) }), _jsx(TooltipContent, { side: "top", align: "center", className: "p-3", children: _jsxs("div", { className: "space-y-2 font-mono text-xs", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "System Prompt:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.systemPromptTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.systemPromptTokens), ")"] })] })] }), contextSize.toolOverheadTokens !== undefined &&
|
|
40
47
|
contextSize.toolOverheadTokens > 0 && (_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tools Definition:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolOverheadTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolOverheadTokens), ")"] })] })] })), contextSize.mcpOverheadTokens !== undefined &&
|
|
41
48
|
contextSize.mcpOverheadTokens > 0 && (_jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "MCPs Definition:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.mcpOverheadTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.mcpOverheadTokens), ")"] })] })] })), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "User Messages:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.userMessagesTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.userMessagesTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Assistant Messages:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.assistantMessagesTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.assistantMessagesTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tool Inputs:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolInputTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolInputTokens), ")"] })] })] }), _jsxs("div", { className: "flex justify-between gap-6", children: [_jsx("span", { className: "text-muted-foreground", children: "Tool Results:" }), _jsxs("div", { className: "flex gap-1 items-baseline", children: [_jsx("span", { children: contextSize.toolResultsTokens.toLocaleString() }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", calculatePercentage(contextSize.toolResultsTokens), ")"] })] })] })] }), _jsx("div", { className: "border-t border-border pt-2", children: _jsxs("div", { className: cn("flex justify-end gap-2 font-semibold", colorClass), children: [_jsx("span", { children: actualTokens.toLocaleString() }), _jsxs("span", { children: ["(", formattedPercentage, ")"] })] }) })] }) })] }) }));
|
|
42
49
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { AlertCircle, AlertTriangle, Archive, CheckCircle2, ChevronDown, Loader2, Scissors, } from "lucide-react";
|
|
3
|
-
import
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
4
|
/**
|
|
5
5
|
* Get display information for a hook type
|
|
6
6
|
*/
|
|
@@ -55,6 +55,7 @@ export function HookNotification({ notification }) {
|
|
|
55
55
|
}
|
|
56
56
|
// Calculate initial elapsed time
|
|
57
57
|
const updateElapsed = () => {
|
|
58
|
+
// biome-ignore lint/style/noNonNullAssertion: triggeredAt is guaranteed to exist for hook_triggered notifications
|
|
58
59
|
const elapsed = (Date.now() - notification.triggeredAt) / 1000;
|
|
59
60
|
setElapsedTime(elapsed);
|
|
60
61
|
};
|