Merge pull request #612 from escapade-mckv/tool-views-fix

fix(bug): fix double json dumps in response processor
This commit is contained in:
Bobbie 2025-06-03 12:54:37 +05:30 committed by GitHub
commit 35ef852ec4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 29 deletions

View File

@ -1747,7 +1747,7 @@ class ResponseProcessor:
return summary return summary
else: else:
return json.dumps(structured_result_v1) return structured_result_v1
def _format_xml_tool_result(self, tool_call: Dict[str, Any], result: ToolResult) -> str: def _format_xml_tool_result(self, tool_call: Dict[str, Any], result: ToolResult) -> str:
"""Format a tool result wrapped in a <tool_result> tag. """Format a tool result wrapped in a <tool_result> tag.

View File

@ -637,4 +637,20 @@
animation: var(--animate-shimmer); animation: var(--animate-shimmer);
width: 100%; width: 100%;
transform: translateX(-100%); transform: translateX(-100%);
}
@theme inline {
--animate-shiny-text: shiny-text 5s infinite;
@keyframes shiny-text {
0%,
90%,
100% {
background-position: calc(-100% - var(--shiny-width)) 0;
}
30%,
60% {
background-position: calc(100% + var(--shiny-width)) 0;
}
}
} }

View File

@ -421,7 +421,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
groupedMessages.push(currentGroup); groupedMessages.push(currentGroup);
} }
// Handle streaming content // Handle streaming content - only add to existing group or create new one if needed
if (streamingTextContent) { if (streamingTextContent) {
const lastGroup = groupedMessages.at(-1); const lastGroup = groupedMessages.at(-1);
if (!lastGroup || lastGroup.type === 'user') { if (!lastGroup || lastGroup.type === 'user') {
@ -443,7 +443,6 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
key: `assistant-group-${assistantGroupCounter}-streaming` key: `assistant-group-${assistantGroupCounter}-streaming`
}); });
} else if (lastGroup.type === 'assistant_group') { } else if (lastGroup.type === 'assistant_group') {
// Add to existing assistant group
lastGroup.messages.push({ lastGroup.messages.push({
content: streamingTextContent, content: streamingTextContent,
type: 'assistant', type: 'assistant',
@ -513,7 +512,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
return ( return (
<div key={group.key} ref={groupIndex === groupedMessages.length - 1 ? latestMessageRef : null}> <div key={group.key} ref={groupIndex === groupedMessages.length - 1 ? latestMessageRef : null}>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{/* Logo positioned above the message content */} {/* Logo positioned above the message content - ONLY ONCE PER GROUP */}
<div className="flex items-center"> <div className="flex items-center">
<div className="rounded-md flex items-center justify-center"> <div className="rounded-md flex items-center justify-center">
{agentAvatar} {agentAvatar}
@ -521,8 +520,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
<p className='ml-2 text-sm text-muted-foreground'>{agentName ? agentName : 'Suna'}</p> <p className='ml-2 text-sm text-muted-foreground'>{agentName ? agentName : 'Suna'}</p>
</div> </div>
{/* Message content */} {/* Message content - ALL messages in the group */}
<div className="flex max-w-[90%] rounded-lg text-sm break-words overflow-hidden"> <div className="flex max-w-[90%] rounded-lg text-sm break-words overflow-hidden">
<div className="space-y-2 min-w-0 flex-1"> <div className="space-y-2 min-w-0 flex-1">
{(() => { {(() => {
// In debug mode, just show raw messages content // In debug mode, just show raw messages content
@ -566,13 +565,13 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
const renderedToolResultIds = new Set<string>(); const renderedToolResultIds = new Set<string>();
const elements: React.ReactNode[] = []; const elements: React.ReactNode[] = [];
let assistantMessageCount = 0; // Track assistant messages for spacing
group.messages.forEach((message, msgIndex) => { group.messages.forEach((message, msgIndex) => {
if (message.type === 'assistant') { if (message.type === 'assistant') {
const parsedContent = safeJsonParse<ParsedContent>(message.content, {}); const parsedContent = safeJsonParse<ParsedContent>(message.content, {});
const msgKey = message.message_id || `submsg-assistant-${msgIndex}`; const msgKey = message.message_id || `submsg-assistant-${msgIndex}`;
let assistantMessageCount = 0;
if (!parsedContent.content) return; if (!parsedContent.content) return;
const renderedContent = renderMarkdownContent( const renderedContent = renderMarkdownContent(
@ -586,13 +585,12 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
); );
elements.push( elements.push(
<div key={msgKey} className={assistantMessageCount > 0 ? "mt-2" : ""}> <div key={msgKey} className={assistantMessageCount > 0 ? "mt-4" : ""}>
<div className="prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3 break-words overflow-hidden"> <div className="prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3 break-words overflow-hidden">
{renderedContent} {renderedContent}
</div> </div>
</div> </div>
); );
assistantMessageCount++;
} }
}); });

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { AnimatedShinyText } from '@/components/ui/animated-shiny-text';
const items = [ const items = [
{ id: 1, content: "Initializing neural pathways..." }, { id: 1, content: "Initializing neural pathways..." },
@ -33,10 +34,8 @@ export const AgentLoader = () => {
}, []); }, []);
return ( return (
<div className="flex-1 space-y-2 w-full h-16 bg-background"> <div className="flex py-2 items-center w-full">
<div className="max-w-[90%] animate-shimmer bg-transparent h-full p-0.5 text-sm border rounded-xl shadow-sm relative overflow-hidden"> <div></div>
<div className="rounded-md bg-background flex px-5 items-start justify-start h-full relative z-10">
<div className="flex flex-col py-5 items-start w-full space-y-3">
<AnimatePresence> <AnimatePresence>
<motion.div <motion.div
key={items[index].id} key={items[index].id}
@ -45,13 +44,11 @@ export const AgentLoader = () => {
exit={{ y: -20, opacity: 0, filter: "blur(8px)" }} exit={{ y: -20, opacity: 0, filter: "blur(8px)" }}
transition={{ ease: "easeInOut" }} transition={{ ease: "easeInOut" }}
style={{ position: "absolute" }} style={{ position: "absolute" }}
className='ml-7'
> >
{items[index].content} <AnimatedShinyText>{items[index].content}</AnimatedShinyText>
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
</div> </div>
</div>
</div>
</div>
); );
}; };

View File

@ -0,0 +1,34 @@
import { ComponentPropsWithoutRef, CSSProperties, FC } from "react";
import { cn } from "@/lib/utils";
export interface AnimatedShinyTextProps
extends ComponentPropsWithoutRef<"span"> {
shimmerWidth?: number;
}
export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
children,
className,
shimmerWidth = 100,
...props
}) => {
return (
<span
style={
{
"--shiny-width": `${shimmerWidth}px`,
} as CSSProperties
}
className={cn(
"mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70",
"animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
className,
)}
{...props}
>
{children}
</span>
);
};

View File

@ -1,11 +0,0 @@
{
"mcpServers": {
"exa": {
"command": "mkdir",
"args": [
"-p",
"C:\\Users\\91877\\pwned"
]
}
}
}