diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/CopilotShell.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/CopilotShell.tsx index ef95fe1980..5d947e1b92 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/CopilotShell.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/CopilotShell.tsx @@ -73,9 +73,7 @@ export function CopilotShell({ children }: CopilotShellProps) { onClick={() => handleSelectSession(session.id)} className={cn( "w-full rounded-lg px-3 py-2.5 text-left transition-colors", - isActive - ? "bg-zinc-100" - : "hover:bg-zinc-50", + isActive ? "bg-zinc-100" : "hover:bg-zinc-50", )} > ([]); + const [accumulatedSessions, setAccumulatedSessions] = useState< + SessionSummaryResponse[] + >([]); const [totalCount, setTotalCount] = useState(null); const [hasAutoSelectedSession, setHasAutoSelectedSession] = useState(false); const hasCreatedSessionRef = useRef(false); @@ -51,7 +57,7 @@ export function useCopilotShell() { const newSessions = responseData.sessions; const total = responseData.total; setTotalCount(total); - + if (offset === 0) { setAccumulatedSessions(newSessions); } else { @@ -67,7 +73,9 @@ export function useCopilotShell() { const areAllSessionsLoaded = useMemo(() => { if (totalCount === null) return false; - return accumulatedSessions.length >= totalCount && !isFetching && !isLoading; + return ( + accumulatedSessions.length >= totalCount && !isFetching && !isLoading + ); }, [accumulatedSessions.length, totalCount, isFetching, isLoading]); useEffect(() => { @@ -105,33 +113,35 @@ export function useCopilotShell() { [searchParams], ); - const { data: currentSessionData, isLoading: isCurrentSessionLoading } = useGetV2GetSession( - currentSessionId || "", - { + const { data: currentSessionData, isLoading: isCurrentSessionLoading } = + useGetV2GetSession(currentSessionId || "", { query: { enabled: !!currentSessionId && (!isMobile || isDrawerOpen), select: okData, }, - }, - ); + }); const sessions = useMemo( function getSessions() { const filteredSessions: SessionSummaryResponse[] = []; - + if (accumulatedSessions.length > 0) { const visibleSessions = filterVisibleSessions(accumulatedSessions); - + if (currentSessionId) { - const currentInAll = accumulatedSessions.find((s) => s.id === currentSessionId); + const currentInAll = accumulatedSessions.find( + (s) => s.id === currentSessionId, + ); if (currentInAll) { - const isInVisible = visibleSessions.some((s) => s.id === currentSessionId); + const isInVisible = visibleSessions.some( + (s) => s.id === currentSessionId, + ); if (!isInVisible) { filteredSessions.push(currentInAll); } } } - + filteredSessions.push(...visibleSessions); } @@ -140,7 +150,8 @@ export function useCopilotShell() { (s) => s.id === currentSessionId, ); if (!isCurrentInList) { - const summarySession = convertSessionDetailToSummary(currentSessionData); + const summarySession = + convertSessionDetailToSummary(currentSessionData); // Add new session at the beginning to match API order (most recent first) filteredSessions.unshift(summarySession); } @@ -189,9 +200,9 @@ export function useCopilotShell() { useEffect(() => { if (!areAllSessionsLoaded || hasAutoSelectedSession) return; - + const visibleSessions = filterVisibleSessions(accumulatedSessions); - + if (paramSessionId) { setHasAutoSelectedSession(true); return; @@ -201,7 +212,12 @@ export function useCopilotShell() { const lastSession = visibleSessions[0]; setHasAutoSelectedSession(true); router.push(`/copilot/chat?sessionId=${lastSession.id}`); - } else if (accumulatedSessions.length === 0 && !isLoading && totalCount === 0 && !hasCreatedSessionRef.current) { + } else if ( + accumulatedSessions.length === 0 && + !isLoading && + totalCount === 0 && + !hasCreatedSessionRef.current + ) { hasCreatedSessionRef.current = true; postV2CreateSession({ body: JSON.stringify({}) }) .then((response) => { @@ -216,7 +232,15 @@ export function useCopilotShell() { } else if (totalCount === 0) { setHasAutoSelectedSession(true); } - }, [areAllSessionsLoaded, accumulatedSessions, paramSessionId, hasAutoSelectedSession, router, isLoading, totalCount]); + }, [ + areAllSessionsLoaded, + accumulatedSessions, + paramSessionId, + hasAutoSelectedSession, + router, + isLoading, + totalCount, + ]); useEffect(() => { if (paramSessionId) { @@ -228,13 +252,24 @@ export function useCopilotShell() { if (!areAllSessionsLoaded) return false; if (paramSessionId) { - const sessionFound = accumulatedSessions.some((s) => s.id === paramSessionId); + const sessionFound = accumulatedSessions.some( + (s) => s.id === paramSessionId, + ); const sessionLoading = isCurrentSessionLoading; - return sessionFound || (!sessionLoading && currentSessionData !== undefined); + return ( + sessionFound || (!sessionLoading && currentSessionData !== undefined) + ); } return hasAutoSelectedSession; - }, [areAllSessionsLoaded, accumulatedSessions, paramSessionId, isCurrentSessionLoading, currentSessionData, hasAutoSelectedSession]); + }, [ + areAllSessionsLoaded, + accumulatedSessions, + paramSessionId, + isCurrentSessionLoading, + currentSessionData, + hasAutoSelectedSession, + ]); return { isMobile, diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/Chat.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/Chat.tsx index 8ead9a4235..3d28b2c466 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/Chat.tsx +++ b/autogpt_platform/frontend/src/components/contextual/Chat/Chat.tsx @@ -50,7 +50,7 @@ export function Chat({
{/* Header */} {showHeader && ( -
+
{showSessionInfo && sessionId && ( diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/AIChatBubble/AIChatBubble.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/AIChatBubble/AIChatBubble.tsx index 64c2aadbae..076de22480 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/AIChatBubble/AIChatBubble.tsx +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/AIChatBubble/AIChatBubble.tsx @@ -8,12 +8,7 @@ export interface AIChatBubbleProps { export function AIChatBubble({ children, className }: AIChatBubbleProps) { return ( -
+
{children}
); diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/ChatContainer.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/ChatContainer.tsx index a838bf3d78..2f6c5c71db 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/ChatContainer.tsx +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/ChatContainer.tsx @@ -50,24 +50,27 @@ export function ChatContainer({ return (
{/* Messages or Welcome Screen - Scrollable */} -
+
- +
{/* Input - Fixed at bottom */}
-
+
- {agentOutput && - agentOutput.type === "tool_response" && ( -
- -
- )} + {agentOutput && agentOutput.type === "tool_response" && ( +
+ +
+ )} )}
{/* Gradient flare background */} -
+
-
+
{/* Render all persisted messages */} {(() => { let lastAssistantMessageIndex = -1; @@ -61,165 +61,177 @@ export function MessageList({ } } - return messages - .map((message, index) => { - // Log message for debugging - if (message.type === "message" && message.role === "assistant") { - const prevMessage = messages[index - 1]; - const prevMessageToolName = prevMessage?.type === "tool_call" ? prevMessage.toolName : undefined; - console.log("[MessageList] Assistant message:", { - index, - content: message.content.substring(0, 200), - fullContent: message.content, - prevMessageType: prevMessage?.type, - prevMessageToolName, - }); - } - - // Check if current message is an agent_output tool_response - // and if previous message is an assistant message - let agentOutput: ChatMessageData | undefined; - let messageToRender: ChatMessageData = message; - - if (message.type === "tool_response" && message.result) { - let parsedResult: Record | null = null; - try { - parsedResult = - typeof message.result === "string" - ? JSON.parse(message.result) - : (message.result as Record); - } catch { - parsedResult = null; - } - if (parsedResult?.type === "agent_output") { + return messages.map((message, index) => { + // Log message for debugging + if (message.type === "message" && message.role === "assistant") { const prevMessage = messages[index - 1]; - if ( - prevMessage && - prevMessage.type === "message" && - prevMessage.role === "assistant" - ) { - // This agent output will be rendered inside the previous assistant message - // Skip rendering this message separately - return null; - } + const prevMessageToolName = + prevMessage?.type === "tool_call" + ? prevMessage.toolName + : undefined; + console.log("[MessageList] Assistant message:", { + index, + content: message.content.substring(0, 200), + fullContent: message.content, + prevMessageType: prevMessage?.type, + prevMessageToolName, + }); } - } - // Check if assistant message follows a tool_call and looks like a tool output - if (message.type === "message" && message.role === "assistant") { - const prevMessage = messages[index - 1]; - - // Check if next message is an agent_output tool_response to include in current assistant message - const nextMessage = messages[index + 1]; - if ( - nextMessage && - nextMessage.type === "tool_response" && - nextMessage.result - ) { + // Check if current message is an agent_output tool_response + // and if previous message is an assistant message + let agentOutput: ChatMessageData | undefined; + let messageToRender: ChatMessageData = message; + + if (message.type === "tool_response" && message.result) { let parsedResult: Record | null = null; try { parsedResult = - typeof nextMessage.result === "string" - ? JSON.parse(nextMessage.result) - : (nextMessage.result as Record); + typeof message.result === "string" + ? JSON.parse(message.result) + : (message.result as Record); } catch { parsedResult = null; } if (parsedResult?.type === "agent_output") { - agentOutput = nextMessage; + const prevMessage = messages[index - 1]; + if ( + prevMessage && + prevMessage.type === "message" && + prevMessage.role === "assistant" + ) { + // This agent output will be rendered inside the previous assistant message + // Skip rendering this message separately + return null; + } } } - // Only convert to tool_response if it follows a tool_call AND looks like a tool output - if (prevMessage && prevMessage.type === "tool_call") { - const content = message.content.toLowerCase().trim(); - // Patterns that indicate this is a tool output result, not an agent response - const isToolOutputPattern = - content.startsWith("no agents found") || - content.startsWith("no results found") || - content.includes("no agents found matching") || - content.match(/^no \w+ found/i) || - (content.length < 150 && content.includes("try different")) || - (content.length < 200 && !content.includes("i'll") && !content.includes("let me") && !content.includes("i can") && !content.includes("i will")); + // Check if assistant message follows a tool_call and looks like a tool output + if (message.type === "message" && message.role === "assistant") { + const prevMessage = messages[index - 1]; - console.log("[MessageList] Checking if assistant message is tool output:", { - content: message.content.substring(0, 100), - isToolOutputPattern, - prevToolName: prevMessage.toolName, - }); - - if (isToolOutputPattern) { - // Convert this message to a tool_response format for rendering - messageToRender = { - type: "tool_response", - toolId: prevMessage.toolId, - toolName: prevMessage.toolName, - result: message.content, - success: true, - timestamp: message.timestamp, - } as ChatMessageData; - } - } - } - - const isFinalMessage = - messageToRender.type !== "message" || - messageToRender.role !== "assistant" || - index === lastAssistantMessageIndex; - - // Render last tool_response as AIChatBubble (but skip agent_output that's rendered inside assistant message) + // Check if next message is an agent_output tool_response to include in current assistant message + const nextMessage = messages[index + 1]; if ( - messageToRender.type === "tool_response" && - message.type === "tool_response" && - index === lastToolResponseIndex + nextMessage && + nextMessage.type === "tool_response" && + nextMessage.result ) { - // Check if this is an agent_output that should be rendered inside assistant message - let parsedResult: Record | null = null; - try { - parsedResult = - typeof messageToRender.result === "string" - ? JSON.parse(messageToRender.result) - : (messageToRender.result as Record); - } catch { - parsedResult = null; - } - - const isAgentOutput = parsedResult?.type === "agent_output"; - const prevMessage = messages[index - 1]; - const shouldSkip = - isAgentOutput && - prevMessage && - prevMessage.type === "message" && - prevMessage.role === "assistant"; - - if (shouldSkip) return null; - - const resultValue = - typeof messageToRender.result === "string" - ? messageToRender.result - : messageToRender.result - ? JSON.stringify(messageToRender.result, null, 2) - : ""; - - return ( -
- - - -
- ); + let parsedResult: Record | null = null; + try { + parsedResult = + typeof nextMessage.result === "string" + ? JSON.parse(nextMessage.result) + : (nextMessage.result as Record); + } catch { + parsedResult = null; + } + if (parsedResult?.type === "agent_output") { + agentOutput = nextMessage; + } } + // Only convert to tool_response if it follows a tool_call AND looks like a tool output + if (prevMessage && prevMessage.type === "tool_call") { + const content = message.content.toLowerCase().trim(); + // Patterns that indicate this is a tool output result, not an agent response + const isToolOutputPattern = + content.startsWith("no agents found") || + content.startsWith("no results found") || + content.includes("no agents found matching") || + content.match(/^no \w+ found/i) || + (content.length < 150 && content.includes("try different")) || + (content.length < 200 && + !content.includes("i'll") && + !content.includes("let me") && + !content.includes("i can") && + !content.includes("i will")); + + console.log( + "[MessageList] Checking if assistant message is tool output:", + { + content: message.content.substring(0, 100), + isToolOutputPattern, + prevToolName: prevMessage.toolName, + }, + ); + + if (isToolOutputPattern) { + // Convert this message to a tool_response format for rendering + messageToRender = { + type: "tool_response", + toolId: prevMessage.toolId, + toolName: prevMessage.toolName, + result: message.content, + success: true, + timestamp: message.timestamp, + } as ChatMessageData; + } + } + } + + const isFinalMessage = + messageToRender.type !== "message" || + messageToRender.role !== "assistant" || + index === lastAssistantMessageIndex; + + // Render last tool_response as AIChatBubble (but skip agent_output that's rendered inside assistant message) + if ( + messageToRender.type === "tool_response" && + message.type === "tool_response" && + index === lastToolResponseIndex + ) { + // Check if this is an agent_output that should be rendered inside assistant message + let parsedResult: Record | null = null; + try { + parsedResult = + typeof messageToRender.result === "string" + ? JSON.parse(messageToRender.result) + : (messageToRender.result as Record); + } catch { + parsedResult = null; + } + + const isAgentOutput = parsedResult?.type === "agent_output"; + const prevMessage = messages[index - 1]; + const shouldSkip = + isAgentOutput && + prevMessage && + prevMessage.type === "message" && + prevMessage.role === "assistant"; + + if (shouldSkip) return null; + + const resultValue = + typeof messageToRender.result === "string" + ? messageToRender.result + : messageToRender.result + ? JSON.stringify(messageToRender.result, null, 2) + : ""; + return ( - + className="min-w-0 overflow-x-hidden hyphens-auto break-words px-4 py-2" + > + + + +
); - }); + } + + return ( + + ); + }); })()} {/* Render thinking message when streaming but no chunks yet */} diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/ToolCallMessage/ToolCallMessage.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/ToolCallMessage/ToolCallMessage.tsx index fb9574660c..60b7bdbae9 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/ToolCallMessage/ToolCallMessage.tsx +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/ToolCallMessage/ToolCallMessage.tsx @@ -20,8 +20,8 @@ export function ToolCallMessage({ const displayData = toolArguments ? JSON.stringify(toolArguments) : "No arguments"; - - const displayText = `${displayKey}: ${displayData}`; + + const displayText = `${displayKey}: ${displayData}`; return ( diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/UserChatBubble/UserChatBubble.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/UserChatBubble/UserChatBubble.tsx index 34d888f746..cd8d5d350e 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/UserChatBubble/UserChatBubble.tsx +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/UserChatBubble/UserChatBubble.tsx @@ -6,10 +6,7 @@ export interface UserChatBubbleProps { className?: string; } -export function UserChatBubble({ - children, - className, -}: UserChatBubbleProps) { +export function UserChatBubble({ children, className }: UserChatBubbleProps) { return (
{ if (isLoading || isCreating) { const timer = setTimeout(() => { diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx index 58f53f6fa4..7428d78758 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx @@ -75,17 +75,17 @@ export function NavbarLink({ name, href }: Props) { {href === "/library" && (isChatEnabled ? ( + className={cn( + iconWidthClass, + isActive && "text-white dark:text-black", + )} + /> ) : ( ))}