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 */} -
+
{children}
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" - /> -