basic progress bar

This commit is contained in:
Ida Dittrich 2025-08-21 08:41:05 +02:00
parent 28f0293ada
commit 18d03e4e78
4 changed files with 331 additions and 272 deletions

View file

@ -12,14 +12,10 @@ const LogItem: React.FC<LogItemProps> = ({ log }) => {
// Format timestamp with robust parsing (same logic as MessageList)
const formatTimestamp = (timestamp: any) => {
console.log(`⏰ LogItem formatTimestamp called with:`, {
timestamp,
type: typeof timestamp,
hasValue: !!timestamp
});
if (!timestamp) {
console.log(`⏰ LogItem: No timestamp provided`);
return 'No timestamp';
}
@ -62,13 +58,7 @@ const LogItem: React.FC<LogItemProps> = ({ log }) => {
return `Invalid: ${timestamp}`;
}
console.log(`✅ LogItem: Successfully parsed timestamp:`, {
original: timestamp,
originalType: typeof timestamp,
processed: dateToTry,
wasConverted: dateToTry !== timestamp,
parsed: date.toISOString()
});
const now = new Date();
const isToday = date.toDateString() === now.toDateString();
@ -81,7 +71,6 @@ const LogItem: React.FC<LogItemProps> = ({ log }) => {
date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
console.log(`⏰ LogItem: Formatted timestamp:`, { formatted });
return formatted;
};
@ -89,17 +78,7 @@ const LogItem: React.FC<LogItemProps> = ({ log }) => {
const logLevel = log.level || (log.type?.toLowerCase() as 'info' | 'warning' | 'error' | 'debug') || 'info';
// Debug: Log what the LogItem is receiving
console.log(`📋 LogItem rendering:`, {
logId: log.id,
logType: log.type,
logLevel: logLevel,
message: log.message?.substring(0, 50) + (log.message?.length > 50 ? '...' : ''),
timestamp: log.timestamp,
timestampType: typeof log.timestamp,
fullLogObject: log,
allLogKeys: Object.keys(log),
hasDetails: !!(log.details || log.performance)
});
return (
<div className={`${messageStyles.log_container} ${messageStyles[logLevel]}`}>

View file

@ -63,26 +63,8 @@ const getFileIcon = (type?: string, ext?: string): string => {
const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) => {
const { downloadFile, isDownloading } = useFileDownload();
// Debug: Log what the MessageItem is receiving
console.log(`🎭 MessageItem rendering:`, {
messageId: message.id,
messageRole: message.role,
content: message.content?.substring(0, 50) + (message.content?.length > 50 ? '...' : ''),
contentLength: message.content?.length || 0,
hasContent: !!message.content,
hasDocuments: !!(message.documents),
documentsArray: message.documents,
documentsLength: message.documents?.length || 0,
documentsCheck: message.documents && message.documents.length > 0,
// Timestamp debugging
timestamp: message.timestamp,
hasTimestamp: !!message.timestamp,
timestampType: typeof message.timestamp,
});
const handleDocumentClick = (document: Document) => {
console.log(`🖱️ Document clicked:`, document);
// If there's a downloadUrl, use it; otherwise try the url
const downloadLink = document.downloadUrl || document.url;
@ -95,7 +77,6 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
const handlePreview = (document: Document, e: React.MouseEvent) => {
e.stopPropagation();
console.log(`👁️ Preview requested for:`, document);
// Use fileId if available, otherwise try to use id as fallback
const fileId = document.fileId || parseInt(document.id || '0');
@ -105,7 +86,6 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
return;
}
console.log('✅ MessageItem - Previewing file:', { fileId, document });
// Call the parent callback to show preview in the file preview quadrant
if (onFilePreview) {
@ -135,37 +115,22 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
// Construct filename with extension if available
const fileName = document.ext ? `${document.name}.${document.ext}` : document.name;
console.log(`💾 Downloading file ${fileId} as "${fileName}"`);
await downloadFile(fileId, fileName);
};
// Debug: Log document check before rendering
const hasDocuments = message.documents && message.documents.length > 0;
console.log(`🔍 About to check documents:`, {
hasDocuments: !!(message.documents),
documentsLength: message.documents?.length || 0,
willRenderFiles: hasDocuments
});
// Log if no documents
if (!hasDocuments) {
console.log(`📭 No documents to render for message ${message.id}`);
}
// Format timestamp
const formatTimestamp = (timestamp?: string) => {
console.log(`⏰ formatTimestamp called with:`, { timestamp, type: typeof timestamp, hasValue: !!timestamp });
if (!timestamp) {
console.log(`⏰ No timestamp provided, returning empty string`);
return '';
}
const date = new Date(timestamp);
console.log(`⏰ Parsed date:`, { date, isValid: !isNaN(date.getTime()) });
if (isNaN(date.getTime())) {
console.log(`⏰ Invalid date, returning empty string`);
return '';
}
@ -180,7 +145,6 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
console.log(`⏰ Formatted timestamp:`, { formatted });
return formatted;
};
@ -221,7 +185,6 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
</div>
<div>
{message.documents!.map((document, docIndex) => {
console.log(`📄 Rendering document ${docIndex + 1}:`, document);
return (
<div
key={document.id || docIndex}

View file

@ -5,68 +5,89 @@ import LogItem from './DashboardChatAreaLogItem';
import { WorkflowMessage, Document, WorkflowState, WorkflowLog } from './dashboardChatAreaTypes';
import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css';
// Helper function to parse task progress from message content first line
const parseTaskProgress = (content: string): { current: number; total: number; percentage: number } | null => {
// Get the first line of the message
const firstLine = content.split('\n')[0].trim();
// Helper function to parse task and action progress from log messages
const parseLogProgress = (logMessage: string): {
taskNumber?: number;
totalTasks?: number;
actionNumber?: number;
totalActions?: number;
isCompleted?: boolean;
type: 'task_start' | 'action_start' | 'action_complete' | 'task_complete' | 'unknown';
} => {
const message = logMessage.trim();
// Look for patterns like "Starting Task 1/5", "Task 2/10 Completed Successfully!", etc.
const taskPattern = /(?:Starting\s+)?Task\s+(\d+)\/(\d+)/i;
const match = firstLine.match(taskPattern);
if (match) {
const current = parseInt(match[1]);
const total = parseInt(match[2]);
const percentage = Math.round((current / total) * 100);
console.log(`📊 Parsed task progress from first line:`, {
firstLine,
current,
total,
percentage,
originalText: match[0]
});
return { current, total, percentage };
// Pattern: "Executing task X/Y"
const taskStartPattern = /^Executing task (\d+)\/(\d+)$/i;
const taskStartMatch = message.match(taskStartPattern);
if (taskStartMatch) {
return {
taskNumber: parseInt(taskStartMatch[1]),
totalTasks: parseInt(taskStartMatch[2]),
type: 'task_start'
};
}
return null;
// Pattern: "Task X - Starting action Y/Z"
const actionStartPattern = /^Task (\d+) - Starting action (\d+)\/(\d+)$/i;
const actionStartMatch = message.match(actionStartPattern);
if (actionStartMatch) {
return {
taskNumber: parseInt(actionStartMatch[1]),
actionNumber: parseInt(actionStartMatch[2]),
totalActions: parseInt(actionStartMatch[3]),
type: 'action_start'
};
}
// Pattern: "Task X - Action Y/Z completed" or "✅ Task X - Action Y/Z completed"
const actionCompletePattern = /^(?:✅\s+)?Task (\d+) - Action (\d+)\/(\d+) completed$/i;
const actionCompleteMatch = message.match(actionCompletePattern);
if (actionCompleteMatch) {
return {
taskNumber: parseInt(actionCompleteMatch[1]),
actionNumber: parseInt(actionCompleteMatch[2]),
totalActions: parseInt(actionCompleteMatch[3]),
isCompleted: true,
type: 'action_complete'
};
}
// Pattern: "Task X/Y completed" or "🎯 Task X/Y completed"
const taskCompletePattern = /^(?:🎯\s+)?Task (\d+)\/(\d+) completed$/i;
const taskCompleteMatch = message.match(taskCompletePattern);
if (taskCompleteMatch) {
return {
taskNumber: parseInt(taskCompleteMatch[1]),
totalTasks: parseInt(taskCompleteMatch[2]),
isCompleted: true,
type: 'task_complete'
};
}
return { type: 'unknown' };
};
// Helper function to analyze workflow progress from all messages
const analyzeWorkflowProgress = (messages: any[]): {
// Helper function to analyze workflow progress from messages and logs
const analyzeWorkflowProgressFromMessages = (messages: any[], logs: any[] = []): {
current: number;
total: number;
percentage: number;
isLoading: boolean;
taskBreakdown?: string;
} | null => {
if (messages.length === 0) return null;
console.log('🚨 analyzeWorkflowProgressFromMessages called:', {
messagesLength: messages.length,
logsLength: logs.length,
logMessages: logs.map(l => l.message)
});
if (messages.length === 0 && logs.length === 0) return null;
// Check if the last message is from user (indicating we're waiting for assistant response)
const lastMessage = messages[messages.length - 1];
const isWaitingForAssistant = lastMessage?.role === 'user';
let latestProgress: { current: number; total: number; percentage: number } | null = null;
// Go through messages in reverse order (latest first) to find the most recent progress
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
if (message.role === 'assistant' && message.content) {
const progress = parseTaskProgress(message.content);
if (progress) {
latestProgress = progress;
break; // Use the most recent progress found
}
}
}
console.log(`🔄 Analyzed workflow progress:`, {
totalMessages: messages.length,
lastMessageRole: lastMessage?.role,
isWaitingForAssistant,
latestProgress
});
// If we're waiting for assistant response after a user message, show loading state
// If we're waiting for assistant response, show loading state
if (isWaitingForAssistant) {
return {
current: 0,
@ -76,11 +97,168 @@ const analyzeWorkflowProgress = (messages: any[]): {
};
}
// If we have progress and we're not waiting, show the progress
if (latestProgress) {
// Parse logs first (they contain the structure info)
let totalTasks = 0;
const taskActionCounts: { [taskNumber: number]: { total: number; completedActions: Set<number> } } = {};
// Parse logs for structure information
logs.forEach((log) => {
if (!log.message) return;
const content = log.message.trim();
// Debug: log message content for analysis
if (content.includes('✅') || content.includes('🚀') || content.includes('⚡') || content.includes('🎯') || content.includes('Executing task') || content.includes('Starting action')) {
console.log('📊 Progress: Analyzing log content:', content);
}
// Look for task execution patterns: "Executing task X/Y"
const taskExecMatch = content.match(/Executing task (\d+)\/(\d+)/i);
if (taskExecMatch) {
const totalTasksFound = parseInt(taskExecMatch[2]);
if (totalTasksFound > totalTasks) {
totalTasks = totalTasksFound;
}
console.log('📊 Found total tasks:', totalTasksFound);
}
// Look for action start patterns: "Task X - Starting action Y/Z"
const actionStartMatch = content.match(/Task (\d+) - Starting action (\d+)\/(\d+)/i);
if (actionStartMatch) {
const taskNumber = parseInt(actionStartMatch[1]);
const totalActions = parseInt(actionStartMatch[3]);
if (!taskActionCounts[taskNumber]) {
taskActionCounts[taskNumber] = { total: 0, completedActions: new Set() };
}
if (totalActions > taskActionCounts[taskNumber].total) {
taskActionCounts[taskNumber].total = totalActions;
}
console.log(`📊 Task ${taskNumber} has ${totalActions} actions`);
}
// Look for action completion patterns: "✅ Task X - Action Y/Z completed"
const actionCompleteMatch = content.match(/✅\s+Task (\d+) - Action (\d+)\/(\d+) completed/i);
if (actionCompleteMatch) {
const taskNumber = parseInt(actionCompleteMatch[1]);
const actionNumber = parseInt(actionCompleteMatch[2]);
const totalActions = parseInt(actionCompleteMatch[3]);
if (!taskActionCounts[taskNumber]) {
taskActionCounts[taskNumber] = { total: totalActions, completedActions: new Set() };
}
// Add this specific action to the completed set
taskActionCounts[taskNumber].completedActions.add(actionNumber);
// Update total if needed
if (totalActions > taskActionCounts[taskNumber].total) {
taskActionCounts[taskNumber].total = totalActions;
}
console.log(`📊 Task ${taskNumber} action ${actionNumber} completed`);
}
// Look for task completion patterns: "🎯 Task X/Y completed"
const taskCompleteMatch = content.match(/🎯\s+Task (\d+)\/(\d+) completed/i);
if (taskCompleteMatch) {
const taskNumber = parseInt(taskCompleteMatch[1]);
const totalTasksFound = parseInt(taskCompleteMatch[2]);
if (totalTasksFound > totalTasks) {
totalTasks = totalTasksFound;
}
// Mark all actions for this task as completed
if (taskActionCounts[taskNumber]) {
const task = taskActionCounts[taskNumber];
for (let i = 1; i <= task.total; i++) {
task.completedActions.add(i);
}
}
console.log(`📊 Task ${taskNumber} completed (all actions)`);
}
});
// Also parse assistant messages for any additional patterns
messages.forEach((message) => {
if (message.role !== 'assistant' || !message.content) return;
const content = message.content.trim();
// Look for action completion in messages: "✅ Task X - Action document.generateReport completed"
const actionCompleteMatch = content.match(/✅\s+Task (\d+) - Action\s+(?:(\d+)\/(\d+)|.*completed)/i);
if (actionCompleteMatch) {
const taskNumber = parseInt(actionCompleteMatch[1]);
// If we have the action number format (e.g., "1/1")
if (actionCompleteMatch[2] && actionCompleteMatch[3]) {
const actionNumber = parseInt(actionCompleteMatch[2]);
const totalActions = parseInt(actionCompleteMatch[3]);
if (!taskActionCounts[taskNumber]) {
taskActionCounts[taskNumber] = { total: totalActions, completedActions: new Set() };
}
taskActionCounts[taskNumber].completedActions.add(actionNumber);
if (totalActions > taskActionCounts[taskNumber].total) {
taskActionCounts[taskNumber].total = totalActions;
}
} else {
// If it's a named action without numbers, treat it as action 1/1 for this task
if (!taskActionCounts[taskNumber]) {
taskActionCounts[taskNumber] = { total: 1, completedActions: new Set() };
}
taskActionCounts[taskNumber].completedActions.add(1);
if (taskActionCounts[taskNumber].total === 0) {
taskActionCounts[taskNumber].total = 1;
}
}
console.log(`📊 Message: Task ${taskNumber} action completed`);
}
});
// Calculate completed tasks (a task is complete when all its actions are done)
let completedTasks = 0;
const taskStatuses = [];
// Check each task's completion status
for (let taskNum = 1; taskNum <= totalTasks; taskNum++) {
const task = taskActionCounts[taskNum];
if (task) {
const isTaskComplete = task.total > 0 && task.completedActions.size === task.total;
if (isTaskComplete) {
completedTasks++;
}
taskStatuses.push(`Task ${taskNum}: ${task.completedActions.size}/${task.total} actions`);
} else {
taskStatuses.push(`Task ${taskNum}: Not started`);
}
}
console.log('📊 Final calculation (task-based):', {
totalTasks,
completedTasks,
taskActionCounts: Object.fromEntries(
Object.entries(taskActionCounts).map(([taskNum, task]) => [
taskNum,
{
total: task.total,
completed: task.completedActions.size,
completedActions: Array.from(task.completedActions),
isComplete: task.total > 0 && task.completedActions.size === task.total
}
])
)
});
// Create task breakdown string
const taskBreakdown = taskStatuses.length > 0 ? taskStatuses.join(', ') : undefined;
// If we have total tasks, always use task-based progress
if (totalTasks > 0) {
const percentage = Math.round((completedTasks / totalTasks) * 100);
console.log(`📊 Task-based progress: ${completedTasks}/${totalTasks} tasks (${percentage}%)`);
return {
...latestProgress,
isLoading: false
current: completedTasks,
total: totalTasks,
percentage: percentage,
isLoading: false,
taskBreakdown: taskBreakdown || `${completedTasks}/${totalTasks} tasks completed`
};
}
@ -135,13 +313,6 @@ const safeParseDate = (timestamp: any, fallback: number = Date.now()): Date => {
return new Date(fallback);
}
console.log(`✅ Successfully parsed timestamp:`, {
original: timestamp,
originalType: typeof timestamp,
processed: dateToTry,
wasConverted: dateToTry !== timestamp,
parsed: date.toISOString()
});
return date;
};
@ -150,23 +321,12 @@ const safeParseDate = (timestamp: any, fallback: number = Date.now()): Date => {
const mergeMessagesAndLogs = (messages: any[], logs: WorkflowLog[]): Array<{type: 'message' | 'log', item: any, timestamp: Date}> => {
const combined: Array<{type: 'message' | 'log', item: any, timestamp: Date}> = [];
console.log(`🔄 Starting merge process:`, {
messagesCount: messages.length,
logsCount: logs.length,
firstMessage: messages[0],
firstLog: logs[0]
});
// Add messages
messages.forEach((message, index) => {
const rawTimestamp = message.timestamp || message.publishedAt;
console.log(`📨 Processing message ${index + 1}/${messages.length}:`, {
messageId: message.id,
rawTimestamp,
timestampType: typeof rawTimestamp
});
// Use current time minus index for fallback to maintain order
const fallbackTime = Date.now() - ((messages.length + logs.length) - index) * 1000;
const timestamp = safeParseDate(rawTimestamp, fallbackTime);
@ -180,12 +340,6 @@ const mergeMessagesAndLogs = (messages: any[], logs: WorkflowLog[]): Array<{type
// Add logs
logs.forEach((log, index) => {
console.log(`📋 Processing log ${index + 1}/${logs.length}:`, {
logId: log.id,
rawTimestamp: log.timestamp,
timestampType: typeof log.timestamp,
logObject: log
});
// Use current time minus index for fallback to maintain order
const fallbackTime = Date.now() - ((logs.length) - index) * 1000;
@ -201,19 +355,6 @@ const mergeMessagesAndLogs = (messages: any[], logs: WorkflowLog[]): Array<{type
// Sort by timestamp (chronological order)
combined.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
console.log(`🔄 Final merged timeline:`, {
totalMessages: messages.length,
totalLogs: logs.length,
totalCombined: combined.length,
sortedTimeline: combined.map((item, index) => ({
index,
type: item.type,
timestamp: item.timestamp.toISOString(),
content: item.type === 'message' ?
item.item.content?.substring(0, 30) + '...' :
item.item.message?.substring(0, 30) + '...'
}))
});
return combined;
};
@ -241,7 +382,6 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
}));
} 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 {
@ -296,40 +436,6 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
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;
};
@ -337,17 +443,36 @@ const MessageList: React.FC<MessageListProps> = ({
workflowState,
onFilePreview
}) => {
const { request } = useApiRequest();
const [transformedMessages, setTransformedMessages] = React.useState<any[]>([]);
const [isTransforming, setIsTransforming] = React.useState(false);
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
const [isUserScrolledUp, setIsUserScrolledUp] = React.useState(false);
const lastMessageCountRef = React.useRef(0);
// Analyze workflow progress from all messages
const workflowProgress = React.useMemo(() => {
return analyzeWorkflowProgress(transformedMessages);
}, [transformedMessages]);
const [workflowProgress, setWorkflowProgress] = React.useState<{
current: number;
total: number;
percentage: number;
isLoading: boolean;
taskBreakdown?: string;
} | null>(null);
// Calculate workflow progress when messages or logs change
React.useEffect(() => {
console.log('🔄 Progress calculation triggered:', {
messagesCount: transformedMessages.length,
logsCount: workflowState.logs?.length || 0,
logs: workflowState.logs?.map(l => l.message) || []
});
const progress = analyzeWorkflowProgressFromMessages(transformedMessages, workflowState.logs || []);
console.log('📊 Progress result:', progress);
setWorkflowProgress(progress);
}, [transformedMessages.length, JSON.stringify(transformedMessages.map(m => m.content)), workflowState.logs?.length, JSON.stringify(workflowState.logs?.map(l => l.message))]);
// Create merged timeline of messages and logs
const timeline = React.useMemo(() => {
@ -363,30 +488,14 @@ const MessageList: React.FC<MessageListProps> = ({
}
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)
});
workflowState.messages.map(async (msg: WorkflowMessage) => {
return await transformWorkflowMessage(msg, request);
})
);
console.log(`✅ Successfully transformed ${transformed.length} messages`);
setTransformedMessages(transformed);
} catch (error) {
console.error('❌ Error transforming messages:', error);
@ -411,21 +520,12 @@ const MessageList: React.FC<MessageListProps> = ({
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;
}
}, []);
@ -437,7 +537,6 @@ const MessageList: React.FC<MessageListProps> = ({
const hasNewItems = currentTimelineCount > lastMessageCountRef.current;
if (hasNewItems && hadItems && !isUserScrolledUp) {
console.log('🆕 New timeline items detected, auto-scrolling to bottom');
// Small delay to ensure DOM is updated
setTimeout(scrollToBottom, 100);
}
@ -448,7 +547,6 @@ const MessageList: React.FC<MessageListProps> = ({
// Scroll to bottom on initial load
React.useEffect(() => {
if (timeline.length > 0 && lastMessageCountRef.current === 0) {
console.log('📜 Initial load, scrolling to bottom');
setTimeout(scrollToBottom, 100);
}
}, [timeline.length, scrollToBottom]);
@ -457,44 +555,33 @@ const MessageList: React.FC<MessageListProps> = ({
return (
<div className={messageStyles.message_list_container}>
<div
ref={scrollContainerRef}
<div
ref={scrollContainerRef}
className={messageStyles.chat_messages}
onScroll={checkScrollPosition}
>
onScroll={checkScrollPosition}
>
{error && (
<div className={messageStyles.message_error}>
Error: {error}
</div>
)}
<div className={messageStyles.messages_container}>
{timeline.map((timelineItem, index) => {
if (timelineItem.type === 'message') {
const message = timelineItem.item;
console.log(`🎨 Rendering timeline message ${message.id}:`, {
role: message.role,
contentLength: message.content?.length || 0,
hasContent: !!message.content,
documentsCount: message.documents?.length || 0
});
return (
<MessageItem
return (
<MessageItem
key={`message-${message.id}`}
message={message}
index={index}
onFilePreview={onFilePreview}
/>
);
message={message}
index={index}
onFilePreview={onFilePreview}
/>
);
} else if (timelineItem.type === 'log') {
const log = timelineItem.item;
console.log(`📋 Rendering timeline log ${log.id}:`, {
logLevel: log.level || log.type,
message: log.message?.substring(0, 50),
timestamp: log.timestamp
});
return (
<LogItem
@ -510,7 +597,7 @@ const MessageList: React.FC<MessageListProps> = ({
{(isLoading || isTransforming) && (
<div className={messageStyles.message_loading}>
{isTransforming ? 'Processing messages...' : 'Loading messages...'}
{isTransforming ? 'Processing messages...' : 'Loading messages...'}
</div>
)}
@ -526,43 +613,65 @@ const MessageList: React.FC<MessageListProps> = ({
</div>
)}
</div>
</div>
{/* Workflow Progress Bar - positioned outside scrollable area */}
{workflowProgress && (
{(workflowProgress || workflowState.logs?.length > 0) && (
<div className={messageStyles.workflow_progress_container}>
<div className={messageStyles.workflow_progress_label}>
<span>Workflow Progress</span>
<span>
{workflowProgress.isLoading
? 'Loading tasks...'
: `${workflowProgress.current}/${workflowProgress.total} Tasks (${workflowProgress.percentage}%)`
}
</span>
</div>
<div className={messageStyles.workflow_progress_bar}>
<div
className={`${messageStyles.workflow_progress_fill} ${
workflowProgress.isLoading ? messageStyles.loading : ''
}`}
style={{ width: workflowProgress.isLoading ? '0%' : `${workflowProgress.percentage}%` }}
/>
</div>
{workflowProgress ? (
<>
<div className={messageStyles.workflow_progress_label}>
<span>Workflow Progress</span>
<span>
{workflowProgress.isLoading
? 'Loading tasks...'
: `${workflowProgress.current}/${workflowProgress.total} Actions (${workflowProgress.percentage}%)`
}
</span>
</div>
{workflowProgress.taskBreakdown && !workflowProgress.isLoading && (
<div className={messageStyles.workflow_progress_breakdown}>
{workflowProgress.taskBreakdown}
</div>
)}
<div className={messageStyles.workflow_progress_bar}>
<div
className={`${messageStyles.workflow_progress_fill} ${
workflowProgress.isLoading ? messageStyles.loading : ''
}`}
style={{ width: workflowProgress.isLoading ? '0%' : `${workflowProgress.percentage}%` }}
/>
</div>
</>
) : (
<>
<div className={messageStyles.workflow_progress_label}>
<span>Workflow Progress</span>
<span>Analyzing workflow... ({workflowState.logs?.length || 0} logs)</span>
</div>
<div className={messageStyles.workflow_progress_bar}>
<div
className={`${messageStyles.workflow_progress_fill} ${messageStyles.loading}`}
style={{ width: '0%' }}
/>
</div>
</>
)}
</div>
)}
{/* Scroll to bottom button - positioned relative to message list container */}
<button
<button
className={`${messageStyles.scroll_to_bottom_btn} ${
(isUserScrolledUp && timeline.length > 0)
? messageStyles.visible
: messageStyles.hidden
}`}
onClick={scrollToBottom}
title="Scroll to bottom"
>
</button>
onClick={scrollToBottom}
title="Scroll to bottom"
>
</button>
</div>
);
};

View file

@ -372,6 +372,14 @@
font-weight: 600;
}
.workflow_progress_breakdown {
font-size: 10px;
color: var(--color-gray);
margin-bottom: 6px;
text-align: center;
opacity: 0.8;
}
.workflow_progress_bar {
width: 100%;
height: 8px;