diff --git a/packages/ui/src/core/components/SidePanel.tsx b/packages/ui/src/core/components/SidePanel.tsx
index 920cc1910..68e5cf861 100644
--- a/packages/ui/src/core/components/SidePanel.tsx
+++ b/packages/ui/src/core/components/SidePanel.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Minus, X } from 'lucide-react';
import { Button } from './shadcn/button';
@@ -7,14 +7,15 @@ interface SidePanelProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
- title?: string;
+ title?: React.ReactNode;
panelWidth?: number;
backdrop?: boolean;
side?: 'left' | 'right';
resizable?: boolean;
className?: string;
+ contentClassName?: string;
}
-export function SidePanel({ isOpen, title, onClose, children, panelWidth = 768, backdrop = false, side = 'right', resizable = true, className }: SidePanelProps) {
+export function SidePanel({ isOpen, title, onClose, children, panelWidth = 768, backdrop = false, side = 'right', resizable = true, className, contentClassName }: SidePanelProps) {
const [_panelWidth, setPanelWidth] = useState(panelWidth);
const handleDragStart = (e: React.MouseEvent) => {
@@ -101,7 +102,7 @@ export function SidePanel({ isOpen, title, onClose, children, panelWidth = 768,
)}
{/* Scrollable content */}
-
diff --git a/packages/ui/src/features/agent/chat/AgentRightPanel.tsx b/packages/ui/src/features/agent/chat/AgentRightPanel.tsx
index ec36c6b99..dab5edc65 100644
--- a/packages/ui/src/features/agent/chat/AgentRightPanel.tsx
+++ b/packages/ui/src/features/agent/chat/AgentRightPanel.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
import { Badge, Button, useToast, Tabs, TabsBar, TabsPanel, type Tab as TabDefinition } from '@vertesia/ui/core';
import {
CheckCircleIcon,
@@ -216,11 +216,13 @@ function WorkstreamsTab({ workstreams }: WorkstreamsTabProps) {
// Right panel tabs
// ---------------------------------------------------------------------------
-type RightPanelTab = 'plan' | 'workstreams' | 'documents' | 'uploads' | 'artifacts' | 'payload';
+type RightPanelTab = 'plan' | 'workstreams' | 'documents' | 'uploads' | 'artifacts' | 'payload' | 'conversation';
export interface AgentRightPanelProps {
/** Optional payload content to show as a "Payload" tab */
payloadContent?: React.ReactNode;
+ /** Optional conversation content to show as a "Conversation" tab */
+ conversationContent?: React.ReactNode;
// Plan
plan?: Plan;
workstreamStatus?: Map;
@@ -288,20 +290,16 @@ function AgentRightPanelComponent({
// Payload
payloadContent,
+ // Conversation
+ conversationContent,
+
// Panel
onClose,
defaultTab,
}: AgentRightPanelProps) {
const [activeTab, setActiveTab] = useState(defaultTab || 'plan');
- // Auto-switch to relevant tab when content appears
- useEffect(() => {
- if (defaultTab) {
- setActiveTab(defaultTab);
- }
- }, [defaultTab]);
-
- // Determine which tabs have content (for badges/indicators)
+// Determine which tabs have content (for badges/indicators)
const hasWorkstreams = !hideWorkstreams && activeWorkstreams.length > 0;
const hasDocuments = openDocuments.length > 0;
const hasUploads = processingFiles ? processingFiles.size > 0 : false;
@@ -311,7 +309,15 @@ function AgentRightPanelComponent({
setActiveTab('plan');
}, []);
+ const conversationTab: TabDefinition = {
+ name: 'conversation',
+ label: 'Conversation',
+ content: conversationContent ? {conversationContent}
: null,
+ is_allowed: !!conversationContent,
+ };
+
const tabs: TabDefinition[] = [
+ ...(conversationContent ? [conversationTab] : []),
{
name: 'plan',
label: hasPlan
@@ -398,11 +404,13 @@ function AgentRightPanelComponent({
>
-
+
-
+ {!conversationContent && (
+
+ )}
diff --git a/packages/ui/src/features/agent/chat/ModernAgentConversation.tsx b/packages/ui/src/features/agent/chat/ModernAgentConversation.tsx
index bf0ecf30e..e970c8e26 100644
--- a/packages/ui/src/features/agent/chat/ModernAgentConversation.tsx
+++ b/packages/ui/src/features/agent/chat/ModernAgentConversation.tsx
@@ -219,6 +219,10 @@ interface ModernAgentConversationProps {
/** Optional payload content to show as a "Payload" tab in the right panel */
payloadContent?: React.ReactNode;
+ /** Optional conversation content to show as a "Conversation" tab in the right panel */
+ conversationContent?: React.ReactNode;
+ /** When true, renders the conversation inside the right panel as a "Conversation" tab */
+ conversationTab?: boolean;
}
export function ModernAgentConversation(
@@ -737,6 +741,8 @@ function ModernAgentConversationInner({
CollectionLinkComponent,
prependFriendlyMessage,
payloadContent,
+ conversationContent,
+ conversationTab = false,
}: ModernAgentConversationProps & { run: AsyncExecutionResult }) {
const { client } = useUserSession();
const toast = useToast();
@@ -867,14 +873,16 @@ function ModernAgentConversationInner({
// ────────────────────────────────────────────
// Unified right panel state
// ────────────────────────────────────────────
- type RightPanelTab = 'plan' | 'workstreams' | 'documents' | 'uploads' | 'artifacts';
- const [rightPanelTab, setRightPanelTab] = useState('plan');
+ type RightPanelTab = 'plan' | 'workstreams' | 'documents' | 'uploads' | 'artifacts' | 'conversation';
+ const [rightPanelTab, _setRightPanelTab] = useState((conversationContent || conversationTab) ? 'conversation' : 'plan');
const [rightPanelWidth, setRightPanelWidth] = useState(400);
const [isRightPanelResizing, setIsRightPanelResizing] = useState(false);
const isRightPanelVisible = showRightPanelProp && (showSlidingPanel
|| isDocPanelOpen
- || (!hideWorkstreamTabs && panelWorkstreams.length > 0));
+ || (!hideWorkstreamTabs && panelWorkstreams.length > 0)
+ || !!conversationContent
+ || conversationTab);
useEffect(() => {
if (!isRightPanelVisible && isRightPanelResizing) {
@@ -916,28 +924,7 @@ function ModernAgentConversationInner({
};
}, [isRightPanelResizing]);
- // Auto-switch tab when plan panel opens
- useEffect(() => {
- if (showSlidingPanel) {
- setRightPanelTab('plan');
- }
- }, [showSlidingPanel]);
-
- // Auto-switch tab when document panel opens
- useEffect(() => {
- if (isDocPanelOpen) {
- setRightPanelTab('documents');
- }
- }, [isDocPanelOpen]);
-
- // Auto-switch tab when active workstreams appear and no other panel is focused.
- useEffect(() => {
- if (!hideWorkstreamTabs && panelWorkstreams.length > 0 && !showSlidingPanel && !isDocPanelOpen) {
- setRightPanelTab('workstreams');
- }
- }, [hideWorkstreamTabs, panelWorkstreams.length, showSlidingPanel, isDocPanelOpen]);
-
- const handleCloseRightPanel = useCallback(() => {
+const handleCloseRightPanel = useCallback(() => {
setShowSlidingPanel(false);
handleCloseDocPanel();
}, [setShowSlidingPanel, handleCloseDocPanel]);
@@ -1337,13 +1324,142 @@ function ModernAgentConversationInner({
}, new Map()),
[getActivePlan.plan]);
+ // Conversation area inner content — shared between main layout and conversationTab mode
+ const conversationAreaJsx = (
+
+ {!hideHeader && (
+
+ 0}
+ showPlanButton={showRightPanelProp && !conversationTab}
+ onTogglePlanPanel={handleTogglePlanPanel}
+ onDownload={downloadConversation}
+ onCopyRunId={copyRunId}
+ resetWorkflow={resetWorkflow}
+ onRestart={onRestart}
+ onFork={onFork}
+ onExportPdf={exportConversationPdf}
+ isReceivingChunks={debugChunkFlash}
+ />
+
+ )}
+
+ {messages.length === 0 && !isCompleted ? (
+
+
+
+
+
+ {ThinkingMessages[thinkingMessageIndex]}
+
+
+
+
+
+ ) : (
+
}
+ isCompleted={isCompleted}
+ plan={getActivePlan.plan}
+ workstreamStatus={getActivePlan.workstreamStatus}
+ showPlanPanel={showRightPanelProp && showSlidingPanel}
+ onTogglePlanPanel={handleTogglePlanPanel}
+ plans={plans}
+ activePlanIndex={activePlanIndex}
+ onChangePlan={handleChangePlan}
+ taskLabels={taskLabels}
+ streamingMessages={streamingMessages}
+ onSendMessage={handleSendMessage}
+ thinkingMessageIndex={thinkingMessageIndex}
+ messageItemClassNames={messageItemClassNames}
+ messageStyleOverrides={messageStyleOverrides}
+ toolCallGroupClassNames={toolCallGroupClassNames}
+ hideToolCallsInViewMode={hideToolCallsInViewMode}
+ streamingMessageClassNames={streamingMessageClassNames}
+ batchProgressPanelClassNames={batchProgressPanelClassNames}
+ artifactRunId={run.runId}
+ viewMode={viewMode}
+ hideWorkstreamTabs={hideWorkstreamTabs}
+ workingIndicatorClassName={workingIndicatorClassName}
+ messageListClassName={messageListClassName}
+ StoreLinkComponent={effectiveStoreLinkComponent}
+ CollectionLinkComponent={CollectionLinkComponent}
+ prependFriendlyMessage={prependFriendlyMessage}
+ />
+ )}
+
+ {!hideMessageInput && (
+
+ {effectiveWorkflowStatus && effectiveWorkflowStatus !== "RUNNING" ? (
+
+ This Workflow is {effectiveWorkflowStatus}
+
+ ) : showInput && (
+
+ )}
+
+ )}
+
+ );
+
// Main content - wrapped with FusionFragmentProvider when fusionData is provided
const mainContent = (
)}
- {/* Conversation Area - responsive width based on panel visibility */}
-
- {/* Streaming activity indicator moved to Header */}
-
- {/* Header - flex-shrink-0 to prevent shrinking */}
- {!hideHeader && (
-
- 0}
- showPlanButton={showRightPanelProp}
- onTogglePlanPanel={handleTogglePlanPanel}
- onDownload={downloadConversation}
- onCopyRunId={copyRunId}
- resetWorkflow={resetWorkflow}
- onRestart={onRestart}
- onFork={onFork}
- onExportPdf={exportConversationPdf}
- isReceivingChunks={debugChunkFlash}
- />
-
- )}
-
- {messages.length === 0 && !isCompleted ? (
-
-
-
-
-
- {ThinkingMessages[thinkingMessageIndex]}
-
-
-
-
-
- ) : (
-
}
- isCompleted={isCompleted}
- plan={getActivePlan.plan}
- workstreamStatus={getActivePlan.workstreamStatus}
- showPlanPanel={showRightPanelProp && showSlidingPanel}
- onTogglePlanPanel={handleTogglePlanPanel}
- plans={plans}
- activePlanIndex={activePlanIndex}
- onChangePlan={handleChangePlan}
- taskLabels={taskLabels}
- streamingMessages={streamingMessages}
- onSendMessage={handleSendMessage}
- thinkingMessageIndex={thinkingMessageIndex}
- messageItemClassNames={messageItemClassNames}
- messageStyleOverrides={messageStyleOverrides}
- toolCallGroupClassNames={toolCallGroupClassNames}
- hideToolCallsInViewMode={hideToolCallsInViewMode}
- streamingMessageClassNames={streamingMessageClassNames}
- batchProgressPanelClassNames={batchProgressPanelClassNames}
- artifactRunId={run.runId}
- viewMode={viewMode}
- hideWorkstreamTabs={hideWorkstreamTabs}
- workingIndicatorClassName={workingIndicatorClassName}
- messageListClassName={messageListClassName}
- StoreLinkComponent={effectiveStoreLinkComponent}
- CollectionLinkComponent={CollectionLinkComponent}
- prependFriendlyMessage={prependFriendlyMessage}
- />
- )}
-
- {/* Show workflow status message when not running, or show input when running/unknown */}
- {/* Input area - flex-shrink-0 to stay pinned at bottom, with iOS safe area support */}
- {!hideMessageInput && (
-
- {effectiveWorkflowStatus && effectiveWorkflowStatus !== "RUNNING" ? (
-
- This Workflow is {effectiveWorkflowStatus}
-
- ) : showInput && (
-
- )}
-
- )}
-
+ {/* Conversation Area — hidden when conversationTab moves it into the right panel */}
+ {!conversationTab && conversationAreaJsx}
{/* Unified Right Panel — Plan | Workstreams | Documents | Uploads */}
{isRightPanelVisible && (
<>
+ {!conversationTab && (
+ setIsRightPanelResizing(true)}
+ role="separator"
+ aria-orientation="vertical"
+ aria-label="Resize right panel"
+ />
+ )}
setIsRightPanelResizing(true)}
- role="separator"
- aria-orientation="vertical"
- aria-label="Resize right panel"
- />
-