basic progress bar
This commit is contained in:
parent
28f0293ada
commit
18d03e4e78
4 changed files with 331 additions and 272 deletions
|
|
@ -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]}`}>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue