Merge pull request #1083 from kubet/feat/visual-improvements-and-fixes

Feat/visual improvements and fixes
This commit is contained in:
kubet 2025-07-25 22:15:30 +02:00 committed by GitHub
commit 8f2ea13fa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 70 additions and 19 deletions

View File

@ -52,6 +52,7 @@ export default function ThreadPage({
const [debugMode, setDebugMode] = useState(false);
const [initialPanelOpenAttempted, setInitialPanelOpenAttempted] = useState(false);
const [selectedAgentId, setSelectedAgentId] = useState<string | undefined>(undefined);
const [isSidePanelAnimating, setIsSidePanelAnimating] = useState(false);
// Refs
const messagesEndRef = useRef<HTMLDivElement>(null);
@ -533,6 +534,12 @@ export default function ThreadPage({
}
}, [streamingToolCall, handleStreamingToolCall]);
useEffect(() => {
setIsSidePanelAnimating(true);
const timer = setTimeout(() => setIsSidePanelAnimating(false), 200); // Match transition duration
return () => clearTimeout(timer);
}, [isSidePanelOpen]);
if (!initialLoadCompleted || isLoading) {
return <ThreadSkeleton isSidePanelOpen={isSidePanelOpen} />;
}
@ -616,6 +623,7 @@ export default function ThreadPage({
isMobile={isMobile}
initialLoadCompleted={initialLoadCompleted}
agentName={agent && agent.name}
disableInitialAnimation={!initialLoadCompleted && toolCalls.length > 0}
>
{/* {workflowId && (
<div className="px-4 pt-4">
@ -641,7 +649,8 @@ export default function ThreadPage({
<div
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]',
isSidePanelOpen ? 'right-[90%] sm:right-[450px] md:right-[500px] lg:right-[550px] xl:right-[650px]' : 'right-0',
isMobile ? 'left-0 right-0' : ''

View File

@ -39,6 +39,7 @@ interface ThreadLayoutProps {
isMobile: boolean;
initialLoadCompleted: boolean;
agentName?: string;
disableInitialAnimation?: boolean;
}
export function ThreadLayout({
@ -72,7 +73,8 @@ export function ThreadLayout({
debugMode,
isMobile,
initialLoadCompleted,
agentName
agentName,
disableInitialAnimation = false
}: ThreadLayoutProps) {
return (
<div className="flex h-screen">
@ -117,6 +119,7 @@ export function ThreadLayout({
isLoading={!initialLoadCompleted || isLoading}
onFileClick={onViewFiles}
agentName={agentName}
disableInitialAnimation={disableInitialAnimation}
/>
{sandboxId && (

View File

@ -1,5 +1,7 @@
import React from 'react';
import { Skeleton } from '@/components/ui/skeleton';
import { ChatInput } from '@/components/thread/chat-input/chat-input';
import { cn } from '@/lib/utils';
interface ThreadSkeletonProps {
isSidePanelOpen?: boolean;
@ -12,6 +14,17 @@ export function ThreadSkeleton({
showHeader = true,
messageCount = 3,
}: 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 (
<div className="flex h-screen">
<div
@ -36,7 +49,7 @@ export function ThreadSkeleton({
)}
{/* 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">
{/* Generate multiple message skeletons based on messageCount */}
{Array.from({ length: messageCount }).map((_, index) => (
@ -102,20 +115,44 @@ export function ThreadSkeleton({
</div>
</div>
</div>
</div>
{/* Skeleton Side Panel (closed state) */}
{isSidePanelOpen && (
<div className="hidden sm:block">
<div className="h-screen w-[450px] 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>
{/* ChatInput - Inside the left div, positioned at bottom with exact same styling */}
<div
className={cn(
"bg-gradient-to-t from-background via-background/90 to-transparent px-0 pt-8 transition-all duration-200 ease-in-out"
)}
>
<div className={cn(
"mx-auto",
"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>
{/* 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>
);
}

View File

@ -48,6 +48,7 @@ interface ToolCallSidePanelProps {
isLoading?: boolean;
agentName?: string;
onFileClick?: (filePath: string) => void;
disableInitialAnimation?: boolean;
}
interface ToolCallSnapshot {
@ -73,6 +74,7 @@ export function ToolCallSidePanel({
externalNavigateToIndex,
agentName,
onFileClick,
disableInitialAnimation,
}: ToolCallSidePanelProps) {
const [dots, setDots] = React.useState('');
const [internalIndex, setInternalIndex] = React.useState(0);
@ -678,11 +680,11 @@ export function ToolCallSidePanel({
<motion.div
key="sidepanel"
layoutId={FLOATING_LAYOUT_ID}
initial={{ opacity: 0 }}
initial={disableInitialAnimation ? { opacity: 1 } : { opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
opacity: { duration: 0.15 },
opacity: { duration: disableInitialAnimation ? 0 : 0.15 },
layout: {
type: "spring",
stiffness: 400,

View File

@ -285,7 +285,7 @@ export function FileOperationToolView({
</div>
<div className='flex items-center gap-2'>
{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">
<ExternalLink className="h-3.5 w-3.5 mr-1.5" />
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">
<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"
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" />
Source
</TabsTrigger>
<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"
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" />
Preview