import React from 'react'; import { useApiRequest } from '../../../hooks/useApi'; import MessageItem from './DashboardChatAreaMessageItem'; import { WorkflowMessage, Document, WorkflowState } from './dashboardChatAreaTypes'; import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css'; interface MessageListProps { workflowState: WorkflowState; onFilePreview?: (file: any) => void; } // Helper function to transform WorkflowMessage to display Message const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, request: any): Promise => { let documents: Document[] = []; // Check if we have documents directly from the API or need to fetch via fileIds if (workflowMessage.documents && workflowMessage.documents.length > 0) { // Use documents directly from the API documents = workflowMessage.documents.map(doc => ({ id: doc.id || doc.fileId, fileId: typeof doc.fileId === 'string' ? parseInt(doc.fileId) : doc.fileId, name: doc.filename, ext: doc.filename.split('.').pop() || 'unknown', type: doc.mimeType, size: doc.fileSize, downloadUrl: `/api/workflows/files/${doc.fileId}/download` })); } else if (workflowMessage.fileIds && workflowMessage.fileIds.length > 0) { // Fallback to legacy fileIds approach console.log(`📎 Processing ${workflowMessage.fileIds.length} files for message ${workflowMessage.id}`); const documentPromises = workflowMessage.fileIds.map(async (fileId, fileIndex) => { try { console.log(`📁 Fetching metadata for file ${fileIndex + 1}/${workflowMessage.fileIds!.length}: ${fileId}`); const response = await request({ url: `/api/workflows/files/${fileId}/preview`, method: 'get' }); const document: Document = { id: fileId.toString(), fileId: fileId, name: response.name || response.fileName || `File_${fileId}`, ext: response.extension || response.ext || (response.name ? response.name.split('.').pop() : 'txt'), type: response.mimeType || response.type || 'application/octet-stream', size: response.size || 0, downloadUrl: response.downloadUrl || response.url }; console.log(`✅ File ${fileId} metadata processed:`, document.name); return document; } catch (error) { console.error(`❌ Failed to fetch metadata for file ${fileId}:`, error); // Return a fallback object for failed requests return { id: fileId.toString(), fileId: fileId, name: `File_${fileId}`, ext: 'unknown', type: 'application/octet-stream', size: 0 }; } }); documents = await Promise.all(documentPromises); } // Try different possible field names for content (API uses 'message', some legacy might use 'content') const possibleContent = workflowMessage.message || workflowMessage.content || (workflowMessage as any).text || (workflowMessage as any).body || ''; const transformedMessage = { id: workflowMessage.id, role: workflowMessage.role, agentName: workflowMessage.role === 'user' ? 'You' : 'Assistant', content: possibleContent, timestamp: workflowMessage.publishedAt || workflowMessage.timestamp, documents: documents }; console.log(`🔄 Transformation result for ${workflowMessage.id}:`, { role: workflowMessage.role, originalMessage: workflowMessage, originalContent: workflowMessage.content, originalContentType: typeof workflowMessage.content, possibleContent: possibleContent, possibleContentType: typeof possibleContent, transformedContent: transformedMessage.content, transformedContentType: typeof transformedMessage.content, documentsCount: documents.length, hasDocuments: documents.length > 0, // Timestamp debugging publishedAt: workflowMessage.publishedAt, timestamp: workflowMessage.timestamp, finalTimestamp: transformedMessage.timestamp, hasTimestamp: !!transformedMessage.timestamp, allOriginalKeys: Object.keys(workflowMessage), // Check for alternative field names alternativeFields: { message: (workflowMessage as any).message, text: (workflowMessage as any).text, body: (workflowMessage as any).body, data: (workflowMessage as any).data } }); // Special logging for user messages with documents if (workflowMessage.role === 'user' && documents.length > 0) { console.log(`👤📎 USER message with ${documents.length} documents:`, { messageId: workflowMessage.id, documents: documents.map(doc => ({ name: doc.name, fileId: doc.fileId, type: doc.type })) }); } return transformedMessage; }; const MessageList: React.FC = ({ workflowState, onFilePreview }) => { const { request } = useApiRequest(); const [transformedMessages, setTransformedMessages] = React.useState([]); const [isTransforming, setIsTransforming] = React.useState(false); const scrollContainerRef = React.useRef(null); const [isUserScrolledUp, setIsUserScrolledUp] = React.useState(false); const lastMessageCountRef = React.useRef(0); // Transform messages when workflow messages change React.useEffect(() => { const transformMessages = async () => { if (!workflowState.messages || workflowState.messages.length === 0) { setTransformedMessages([]); return; } setIsTransforming(true); console.log(`🔄 Transforming ${workflowState.messages.length} workflow messages...`); try { const transformed = await Promise.all( workflowState.messages.map(async (msg: WorkflowMessage, index: number) => { console.log(`🔄 Transforming message ${index + 1}/${workflowState.messages.length}: ${msg.id}`); const content = msg.message || msg.content || ''; console.log(`📝 RAW API Message (${msg.role}):`, { id: msg.id, rawMessage: msg, contentType: typeof content, contentValue: content, contentLength: content.length, hasContent: !!content, contentPreview: content.substring(0, 100) + (content.length > 100 ? '...' : ''), fileCount: msg.fileIds?.length || 0, allKeys: Object.keys(msg) }); return await transformWorkflowMessage(msg, request); }) ); console.log(`✅ Successfully transformed ${transformed.length} messages`); setTransformedMessages(transformed); } catch (error) { console.error('❌ Error transforming messages:', error); setTransformedMessages([]); } finally { setIsTransforming(false); } }; transformMessages(); }, [workflowState.messages, request]); // Check if user is scrolled near the bottom const checkScrollPosition = React.useCallback(() => { const container = scrollContainerRef.current; if (!container) return; const { scrollTop, scrollHeight, clientHeight } = container; const distanceFromBottom = scrollHeight - scrollTop - clientHeight; // Consider "near bottom" if within 100px of the bottom const isNearBottom = distanceFromBottom < 100; setIsUserScrolledUp(!isNearBottom); console.log('📏 Scroll position:', { scrollTop, scrollHeight, clientHeight, distanceFromBottom, isNearBottom, isUserScrolledUp: !isNearBottom }); }, []); // Scroll to bottom function const scrollToBottom = React.useCallback(() => { const container = scrollContainerRef.current; if (container) { console.log('⬇️ Auto-scrolling to bottom'); container.scrollTop = container.scrollHeight; } }, []); // Auto-scroll when new messages arrive (only if user is near bottom) React.useEffect(() => { const currentMessageCount = transformedMessages.length; const hadMessages = lastMessageCountRef.current > 0; const hasNewMessages = currentMessageCount > lastMessageCountRef.current; if (hasNewMessages && hadMessages && !isUserScrolledUp) { console.log('🆕 New messages detected, auto-scrolling to bottom'); // Small delay to ensure DOM is updated setTimeout(scrollToBottom, 100); } lastMessageCountRef.current = currentMessageCount; }, [transformedMessages.length, isUserScrolledUp, scrollToBottom]); // Scroll to bottom on initial load React.useEffect(() => { if (transformedMessages.length > 0 && lastMessageCountRef.current === 0) { console.log('📜 Initial load, scrolling to bottom'); setTimeout(scrollToBottom, 100); } }, [transformedMessages.length, scrollToBottom]); const { currentWorkflowId, isLoading, error } = workflowState; return (
{error && (
Error: {error}
)}
{transformedMessages.map((message, index) => { console.log(`🎨 Rendering transformed message ${message.id}:`, { role: message.role, contentLength: message.content?.length || 0, hasContent: !!message.content, documentsCount: message.documents?.length || 0 }); return ( ); })}
{(isLoading || isTransforming) && (
{isTransforming ? 'Processing messages...' : 'Loading messages...'}
)} {transformedMessages.length === 0 && !isLoading && !isTransforming && !currentWorkflowId && (
No workflow selected. Start a conversation to create a new workflow.
)} {transformedMessages.length === 0 && !isLoading && !isTransforming && currentWorkflowId && (
No messages in this workflow yet.
)}
{/* Scroll to bottom button - positioned relative to message list container */}
); }; export default MessageList;