mirror of https://github.com/kortix-ai/suna.git
fix: visual improvements stream
This commit is contained in:
parent
d4b031d00e
commit
f33f5f6c6e
|
@ -0,0 +1,50 @@
|
|||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { CircleDashed } from 'lucide-react';
|
||||
import { extractToolNameFromStream } from '@/components/thread/tool-views/xml-parser';
|
||||
import { getToolIcon, getUserFriendlyToolName } from '@/components/thread/utils';
|
||||
|
||||
interface ShowToolStreamProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const ShowToolStream: React.FC<ShowToolStreamProps> = ({ content }) => {
|
||||
const toolName = extractToolNameFromStream(content);
|
||||
|
||||
if (!toolName) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="mt-2 mb-1"
|
||||
>
|
||||
<div className="animate-shimmer inline-flex items-center gap-1.5 py-1 px-1 pr-1.5 text-xs font-medium text-primary bg-primary/10 rounded-lg border border-primary/20">
|
||||
<div className='border-2 bg-gradient-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800 flex items-center justify-center p-0.5 rounded-sm border-neutral-400/20 dark:border-neutral-600'>
|
||||
<CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-primary">Processing...</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
const IconComponent = getToolIcon(toolName);
|
||||
const displayName = getUserFriendlyToolName(toolName);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layoutId={`tool-stream-${toolName}`}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="mt-2 mb-1"
|
||||
>
|
||||
<div className="animate-shimmer inline-flex items-center gap-1.5 py-1 px-1 pr-1.5 text-xs font-medium text-primary bg-primary/10 rounded-lg border border-primary/20">
|
||||
<div className='border-2 bg-gradient-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800 flex items-center justify-center p-0.5 rounded-sm border-neutral-400/20 dark:border-neutral-600'>
|
||||
<CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-primary">{displayName}</span>
|
||||
<span className="ml-1 text-primary/70 text-xs">running...</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
|
@ -18,6 +18,8 @@ import { KortixLogo } from '@/components/sidebar/kortix-logo';
|
|||
import { AgentLoader } from './loader';
|
||||
import { parseXmlToolCalls, isNewXmlFormat, extractToolNameFromStream } from '@/components/thread/tool-views/xml-parser';
|
||||
import { parseToolResult } from '@/components/thread/tool-views/tool-result-parser';
|
||||
import { ShowToolStream } from './ShowToolStream';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
// Define the set of tags whose raw XML should be hidden during streaming
|
||||
const HIDE_STREAMING_XML_TAGS = new Set([
|
||||
|
@ -166,7 +168,14 @@ export function renderMarkdownContent(
|
|||
}
|
||||
|
||||
contentParts.push(
|
||||
<div key={`tool-${match.index}-${index}`} className="my-1">
|
||||
<motion.div
|
||||
key={`tool-${match.index}-${index}`}
|
||||
className="my-1"
|
||||
layoutId={`tool-stream-${toolName}`}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<button
|
||||
onClick={() => handleToolClick(messageId, toolName)}
|
||||
className="inline-flex items-center gap-1.5 py-1 px-1 pr-1.5 text-xs text-muted-foreground bg-muted hover:bg-muted/80 rounded-lg transition-colors cursor-pointer border border-neutral-200 dark:border-neutral-700/50"
|
||||
|
@ -177,7 +186,7 @@ export function renderMarkdownContent(
|
|||
<span className="font-mono text-xs text-foreground">{getUserFriendlyToolName(toolName)}</span>
|
||||
{paramDisplay && <span className="ml-1 text-muted-foreground truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -248,7 +257,14 @@ export function renderMarkdownContent(
|
|||
|
||||
// Render tool button as a clickable element
|
||||
contentParts.push(
|
||||
<div key={toolCallKey} className="my-1">
|
||||
<motion.div
|
||||
key={toolCallKey}
|
||||
className="my-1"
|
||||
layoutId={`tool-stream-${toolName}`}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<button
|
||||
onClick={() => handleToolClick(messageId, toolName)}
|
||||
className="inline-flex items-center gap-1.5 py-1 px-1 pr-1.5 text-xs text-muted-foreground bg-muted hover:bg-muted/80 rounded-lg transition-colors cursor-pointer border border-neutral-200 dark:border-neutral-700/50"
|
||||
|
@ -259,7 +275,7 @@ export function renderMarkdownContent(
|
|||
<span className="font-mono text-xs text-foreground">{getUserFriendlyToolName(toolName)}</span>
|
||||
{paramDisplay && <span className="ml-1 text-muted-foreground truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
lastIndex = xmlRegex.lastIndex;
|
||||
|
@ -764,35 +780,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
|||
<span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />
|
||||
)}
|
||||
|
||||
{detectedTag && detectedTag !== 'function_calls' && (
|
||||
<div className="mt-2 mb-1">
|
||||
<button
|
||||
className="animate-shimmer inline-flex items-center gap-1.5 py-1 px-1 pr-1.5 text-xs font-medium text-primary bg-muted hover:bg-muted/80 rounded-md transition-colors cursor-pointer border border-primary/20"
|
||||
>
|
||||
<div className='border-2 bg-gradient-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800 flex items-center justify-center p-0.5 rounded-sm border-neutral-400/20 dark:border-neutral-600'>
|
||||
<CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-primary">{getUserFriendlyToolName(detectedTag)}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{detectedTag === 'function_calls' && (
|
||||
<div className="mt-2 mb-1">
|
||||
<button
|
||||
className="animate-shimmer inline-flex items-center gap-1.5 py-1 px-1 pr-1.5 text-xs font-medium text-primary bg-muted hover:bg-muted/80 rounded-md transition-colors cursor-pointer border border-primary/20"
|
||||
>
|
||||
<div className='border-2 bg-gradient-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800 flex items-center justify-center p-0.5 rounded-sm border-neutral-400/20 dark:border-neutral-600'>
|
||||
<CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-primary">
|
||||
{(() => {
|
||||
const extractedToolName = extractToolNameFromStream(streamingTextContent);
|
||||
return extractedToolName ? getUserFriendlyToolName(extractedToolName) : 'Using Tool...';
|
||||
})()}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{detectedTag && (
|
||||
<ShowToolStream content={textToRender.substring(tagStartIndex)} />
|
||||
)}
|
||||
|
||||
{streamingToolCall && !detectedTag && (
|
||||
|
@ -867,22 +856,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
|||
)}
|
||||
|
||||
{detectedTag && (
|
||||
<div className="mt-2 mb-1">
|
||||
<button
|
||||
className="animate-shimmer inline-flex items-center gap-1.5 py-1 px-2.5 text-xs font-medium text-primary bg-primary/10 hover:bg-primary/20 rounded-md transition-colors cursor-pointer border border-primary/20"
|
||||
>
|
||||
<CircleDashed className="h-3.5 w-3.5 text-primary flex-shrink-0 animate-spin animation-duration-2000" />
|
||||
<span className="font-mono text-xs text-primary">
|
||||
{detectedTag === 'function_calls' ?
|
||||
(() => {
|
||||
const extractedToolName = extractToolNameFromStream(streamingText);
|
||||
return extractedToolName ? getUserFriendlyToolName(extractedToolName) : 'Using Tool...';
|
||||
})() :
|
||||
getUserFriendlyToolName(detectedTag)
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<ShowToolStream content={textToRender.substring(tagStartIndex)} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -163,15 +163,15 @@ export function AskToolView({
|
|||
sandboxId={project?.sandbox?.id}
|
||||
showPreview={true}
|
||||
className={cn(
|
||||
"w-full",
|
||||
isImage ? "h-auto min-h-[54px]" :
|
||||
isPreviewable ? "min-h-[240px] max-h-[400px] overflow-auto" : "h-[54px]"
|
||||
isImage ? "aspect-square w-full" : "w-full",
|
||||
isImage ? "" :
|
||||
isPreviewable ? "min-h-full max-h-[400px] overflow-auto" : "h-[54px]"
|
||||
)}
|
||||
customStyle={
|
||||
isImage ? {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
'--attachment-height': shouldSpanFull ? '240px' : '180px'
|
||||
height: '100%',
|
||||
'--attachment-height': '100%'
|
||||
} as React.CSSProperties :
|
||||
isPreviewable ? {
|
||||
gridColumn: '1 / -1'
|
||||
|
|
|
@ -292,13 +292,19 @@ export function FileOperationToolView({
|
|||
</a>
|
||||
</Button>
|
||||
)}
|
||||
<TabsList className="-mr-2 h-7 bg-zinc-100/70 dark:bg-zinc-800/70 rounded-lg">
|
||||
<TabsTrigger value="code" className="rounded-md data-[state=active]:bg-white dark:data-[state=active]:bg-zinc-900 data-[state=active]:text-primary">
|
||||
<Code className="h-4 w-4" />
|
||||
<TabsList className="h-8 bg-muted/50 border border-border/50 p-0.5 gap-1">
|
||||
<TabsTrigger
|
||||
value="code"
|
||||
className="flex items-center gap-1.5 px-4 py-2 text-xs font-medium transition-all [&[data-state=active]]:bg-primary/10 [&[data-state=active]]:text-foreground hover:bg-background/50 text-muted-foreground"
|
||||
>
|
||||
<Code className="h-3.5 w-3.5" />
|
||||
Source
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="preview" className="rounded-md data-[state=active]:bg-white dark:data-[state=active]:bg-zinc-900 data-[state=active]:text-primary">
|
||||
<Eye className="h-4 w-4" />
|
||||
<TabsTrigger
|
||||
value="preview"
|
||||
className="flex items-center gap-1.5 px-4 py-2 text-xs font-medium transition-all [&[data-state=active]]:bg-primary/10 [&[data-state=active]]:text-foreground hover:bg-background/50 text-muted-foreground"
|
||||
>
|
||||
<Eye className="h-3.5 w-3.5" />
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
|
Loading…
Reference in New Issue