mirror of https://github.com/kortix-ai/suna.git
Merge pull request #1083 from kubet/feat/visual-improvements-and-fixes
Feat/visual improvements and fixes
This commit is contained in:
commit
8f2ea13fa2
|
@ -52,6 +52,7 @@ export default function ThreadPage({
|
||||||
const [debugMode, setDebugMode] = useState(false);
|
const [debugMode, setDebugMode] = useState(false);
|
||||||
const [initialPanelOpenAttempted, setInitialPanelOpenAttempted] = useState(false);
|
const [initialPanelOpenAttempted, setInitialPanelOpenAttempted] = useState(false);
|
||||||
const [selectedAgentId, setSelectedAgentId] = useState<string | undefined>(undefined);
|
const [selectedAgentId, setSelectedAgentId] = useState<string | undefined>(undefined);
|
||||||
|
const [isSidePanelAnimating, setIsSidePanelAnimating] = useState(false);
|
||||||
|
|
||||||
// Refs
|
// Refs
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -533,6 +534,12 @@ export default function ThreadPage({
|
||||||
}
|
}
|
||||||
}, [streamingToolCall, handleStreamingToolCall]);
|
}, [streamingToolCall, handleStreamingToolCall]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsSidePanelAnimating(true);
|
||||||
|
const timer = setTimeout(() => setIsSidePanelAnimating(false), 200); // Match transition duration
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [isSidePanelOpen]);
|
||||||
|
|
||||||
if (!initialLoadCompleted || isLoading) {
|
if (!initialLoadCompleted || isLoading) {
|
||||||
return <ThreadSkeleton isSidePanelOpen={isSidePanelOpen} />;
|
return <ThreadSkeleton isSidePanelOpen={isSidePanelOpen} />;
|
||||||
}
|
}
|
||||||
|
@ -616,6 +623,7 @@ export default function ThreadPage({
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
initialLoadCompleted={initialLoadCompleted}
|
initialLoadCompleted={initialLoadCompleted}
|
||||||
agentName={agent && agent.name}
|
agentName={agent && agent.name}
|
||||||
|
disableInitialAnimation={!initialLoadCompleted && toolCalls.length > 0}
|
||||||
>
|
>
|
||||||
{/* {workflowId && (
|
{/* {workflowId && (
|
||||||
<div className="px-4 pt-4">
|
<div className="px-4 pt-4">
|
||||||
|
@ -641,7 +649,8 @@ export default function ThreadPage({
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed bottom-0 z-10 bg-gradient-to-t from-background via-background/90 to-transparent px-4 pt-8 transition-all duration-200 ease-in-out",
|
"fixed bottom-0 z-10 bg-gradient-to-t from-background via-background/90 to-transparent px-4 pt-8",
|
||||||
|
isSidePanelAnimating ? "" : "transition-all duration-200 ease-in-out",
|
||||||
leftSidebarState === 'expanded' ? 'left-[72px] md:left-[256px]' : 'left-[72px]',
|
leftSidebarState === 'expanded' ? 'left-[72px] md:left-[256px]' : 'left-[72px]',
|
||||||
isSidePanelOpen ? 'right-[90%] sm:right-[450px] md:right-[500px] lg:right-[550px] xl:right-[650px]' : 'right-0',
|
isSidePanelOpen ? 'right-[90%] sm:right-[450px] md:right-[500px] lg:right-[550px] xl:right-[650px]' : 'right-0',
|
||||||
isMobile ? 'left-0 right-0' : ''
|
isMobile ? 'left-0 right-0' : ''
|
||||||
|
|
|
@ -39,6 +39,7 @@ interface ThreadLayoutProps {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
initialLoadCompleted: boolean;
|
initialLoadCompleted: boolean;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
|
disableInitialAnimation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ThreadLayout({
|
export function ThreadLayout({
|
||||||
|
@ -72,7 +73,8 @@ export function ThreadLayout({
|
||||||
debugMode,
|
debugMode,
|
||||||
isMobile,
|
isMobile,
|
||||||
initialLoadCompleted,
|
initialLoadCompleted,
|
||||||
agentName
|
agentName,
|
||||||
|
disableInitialAnimation = false
|
||||||
}: ThreadLayoutProps) {
|
}: ThreadLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
|
@ -117,6 +119,7 @@ export function ThreadLayout({
|
||||||
isLoading={!initialLoadCompleted || isLoading}
|
isLoading={!initialLoadCompleted || isLoading}
|
||||||
onFileClick={onViewFiles}
|
onFileClick={onViewFiles}
|
||||||
agentName={agentName}
|
agentName={agentName}
|
||||||
|
disableInitialAnimation={disableInitialAnimation}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{sandboxId && (
|
{sandboxId && (
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { ChatInput } from '@/components/thread/chat-input/chat-input';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface ThreadSkeletonProps {
|
interface ThreadSkeletonProps {
|
||||||
isSidePanelOpen?: boolean;
|
isSidePanelOpen?: boolean;
|
||||||
|
@ -12,6 +14,17 @@ export function ThreadSkeleton({
|
||||||
showHeader = true,
|
showHeader = true,
|
||||||
messageCount = 3,
|
messageCount = 3,
|
||||||
}: ThreadSkeletonProps) {
|
}: ThreadSkeletonProps) {
|
||||||
|
// Mock handlers for the ChatInput component
|
||||||
|
const handleSubmit = (message: string) => {
|
||||||
|
// No-op for skeleton
|
||||||
|
console.log('Skeleton submit:', message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
// No-op for skeleton
|
||||||
|
console.log('Skeleton change:', value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<div
|
<div
|
||||||
|
@ -36,7 +49,7 @@ export function ThreadSkeleton({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Skeleton Chat Messages */}
|
{/* Skeleton Chat Messages */}
|
||||||
<div className="flex-1 overflow-y-auto px-6 py-4 pb-[5.5rem]">
|
<div className="flex-1 overflow-y-auto px-6 py-4 pb-72">
|
||||||
<div className="mx-auto max-w-3xl space-y-6">
|
<div className="mx-auto max-w-3xl space-y-6">
|
||||||
{/* Generate multiple message skeletons based on messageCount */}
|
{/* Generate multiple message skeletons based on messageCount */}
|
||||||
{Array.from({ length: messageCount }).map((_, index) => (
|
{Array.from({ length: messageCount }).map((_, index) => (
|
||||||
|
@ -102,20 +115,44 @@ export function ThreadSkeleton({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Skeleton Side Panel (closed state) */}
|
{/* ChatInput - Inside the left div, positioned at bottom with exact same styling */}
|
||||||
{isSidePanelOpen && (
|
<div
|
||||||
<div className="hidden sm:block">
|
className={cn(
|
||||||
<div className="h-screen w-[450px] border-l">
|
"bg-gradient-to-t from-background via-background/90 to-transparent px-0 pt-8 transition-all duration-200 ease-in-out"
|
||||||
<div className="p-4">
|
)}
|
||||||
<Skeleton className="h-8 w-32 mb-4" />
|
>
|
||||||
<Skeleton className="h-20 w-full rounded-md mb-4" />
|
<div className={cn(
|
||||||
<Skeleton className="h-40 w-full rounded-md" />
|
"mx-auto",
|
||||||
</div>
|
"max-w-3xl"
|
||||||
|
)}>
|
||||||
|
<ChatInput
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Describe what you need help with..."
|
||||||
|
loading={false}
|
||||||
|
disabled={true}
|
||||||
|
isAgentRunning={false}
|
||||||
|
value=""
|
||||||
|
hideAttachments={false}
|
||||||
|
isLoggedIn={true}
|
||||||
|
hideAgentSelection={true}
|
||||||
|
defaultShowSnackbar={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
|
{/* Side Panel - Always visible in skeleton with exact responsive widths */}
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<div className="h-screen w-[90%] sm:w-[450px] md:w-[500px] lg:w-[550px] xl:w-[650px] border-l">
|
||||||
|
<div className="p-4">
|
||||||
|
<Skeleton className="h-8 w-32 mb-4" />
|
||||||
|
<Skeleton className="h-20 w-full rounded-md mb-4" />
|
||||||
|
<Skeleton className="h-40 w-full rounded-md" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -48,6 +48,7 @@ interface ToolCallSidePanelProps {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
onFileClick?: (filePath: string) => void;
|
onFileClick?: (filePath: string) => void;
|
||||||
|
disableInitialAnimation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToolCallSnapshot {
|
interface ToolCallSnapshot {
|
||||||
|
@ -73,6 +74,7 @@ export function ToolCallSidePanel({
|
||||||
externalNavigateToIndex,
|
externalNavigateToIndex,
|
||||||
agentName,
|
agentName,
|
||||||
onFileClick,
|
onFileClick,
|
||||||
|
disableInitialAnimation,
|
||||||
}: ToolCallSidePanelProps) {
|
}: ToolCallSidePanelProps) {
|
||||||
const [dots, setDots] = React.useState('');
|
const [dots, setDots] = React.useState('');
|
||||||
const [internalIndex, setInternalIndex] = React.useState(0);
|
const [internalIndex, setInternalIndex] = React.useState(0);
|
||||||
|
@ -678,11 +680,11 @@ export function ToolCallSidePanel({
|
||||||
<motion.div
|
<motion.div
|
||||||
key="sidepanel"
|
key="sidepanel"
|
||||||
layoutId={FLOATING_LAYOUT_ID}
|
layoutId={FLOATING_LAYOUT_ID}
|
||||||
initial={{ opacity: 0 }}
|
initial={disableInitialAnimation ? { opacity: 1 } : { opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{
|
transition={{
|
||||||
opacity: { duration: 0.15 },
|
opacity: { duration: disableInitialAnimation ? 0 : 0.15 },
|
||||||
layout: {
|
layout: {
|
||||||
type: "spring",
|
type: "spring",
|
||||||
stiffness: 400,
|
stiffness: 400,
|
||||||
|
|
|
@ -285,7 +285,7 @@ export function FileOperationToolView({
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
{isHtml && htmlPreviewUrl && !isStreaming && (
|
{isHtml && htmlPreviewUrl && !isStreaming && (
|
||||||
<Button variant="outline" size="sm" className="h-8 text-xs bg-white dark:bg-zinc-900 hover:bg-zinc-100 dark:hover:bg-zinc-800" asChild>
|
<Button variant="outline" size="sm" className="h-8 text-xs bg-white dark:bg-muted/50 hover:bg-zinc-100 dark:hover:bg-zinc-800 shadow-none" asChild>
|
||||||
<a href={htmlPreviewUrl} target="_blank" rel="noopener noreferrer">
|
<a href={htmlPreviewUrl} target="_blank" rel="noopener noreferrer">
|
||||||
<ExternalLink className="h-3.5 w-3.5 mr-1.5" />
|
<ExternalLink className="h-3.5 w-3.5 mr-1.5" />
|
||||||
Open in Browser
|
Open in Browser
|
||||||
|
@ -295,14 +295,14 @@ export function FileOperationToolView({
|
||||||
<TabsList className="h-8 bg-muted/50 border border-border/50 p-0.5 gap-1">
|
<TabsList className="h-8 bg-muted/50 border border-border/50 p-0.5 gap-1">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="code"
|
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"
|
className="flex items-center gap-1.5 px-4 py-2 text-xs font-medium transition-all [&[data-state=active]]:bg-white [&[data-state=active]]:dark:bg-primary/10 [&[data-state=active]]:text-foreground hover:bg-background/50 text-muted-foreground shadow-none"
|
||||||
>
|
>
|
||||||
<Code className="h-3.5 w-3.5" />
|
<Code className="h-3.5 w-3.5" />
|
||||||
Source
|
Source
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="preview"
|
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"
|
className="flex items-center gap-1.5 px-4 py-2 text-xs font-medium transition-all [&[data-state=active]]:bg-white [&[data-state=active]]:dark:bg-primary/10 [&[data-state=active]]:text-foreground hover:bg-background/50 text-muted-foreground shadow-none"
|
||||||
>
|
>
|
||||||
<Eye className="h-3.5 w-3.5" />
|
<Eye className="h-3.5 w-3.5" />
|
||||||
Preview
|
Preview
|
||||||
|
|
Loading…
Reference in New Issue