- Pre-generate sessionId on client side before API call to avoid race conditions - Add small delays in progress tracker to ensure EventSource connection is established - Improve logging and error handling in progress streaming - Add connection confirmation messages in EventSource stream - Fix TypeScript interface to include sessionId in AnalysisProgress This should resolve the lag between actual analysis progress and progress bar display.
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import { EventEmitter } from 'events'
|
|
|
|
export type ProgressStatus = 'pending' | 'active' | 'completed' | 'error'
|
|
|
|
export interface ProgressStep {
|
|
id: string
|
|
title: string
|
|
description: string
|
|
status: ProgressStatus
|
|
startTime?: number
|
|
endTime?: number
|
|
details?: string
|
|
}
|
|
|
|
export interface AnalysisProgress {
|
|
sessionId: string
|
|
currentStep: number
|
|
totalSteps: number
|
|
steps: ProgressStep[]
|
|
timeframeProgress?: {
|
|
current: number
|
|
total: number
|
|
currentTimeframe?: string
|
|
}
|
|
}
|
|
|
|
class ProgressTracker extends EventEmitter {
|
|
private sessions: Map<string, AnalysisProgress> = new Map()
|
|
|
|
createSession(sessionId: string, steps: ProgressStep[]): AnalysisProgress {
|
|
const progress: AnalysisProgress = {
|
|
sessionId,
|
|
currentStep: 0,
|
|
totalSteps: steps.length,
|
|
steps: steps.map(step => ({ ...step, status: 'pending' }))
|
|
}
|
|
|
|
this.sessions.set(sessionId, progress)
|
|
|
|
// Small delay to ensure EventSource connection is established before emitting
|
|
setTimeout(() => {
|
|
this.emit(`progress:${sessionId}`, progress)
|
|
}, 100)
|
|
|
|
return progress
|
|
}
|
|
|
|
updateStep(sessionId: string, stepId: string, status: ProgressStatus, details?: string): void {
|
|
console.log(`🔍 Progress Update: ${sessionId} -> ${stepId} -> ${status}${details ? ` (${details})` : ''}`)
|
|
const progress = this.sessions.get(sessionId)
|
|
if (!progress) {
|
|
console.log(`🔍 Warning: No session found for ${sessionId}`)
|
|
return
|
|
}
|
|
|
|
const updatedSteps = progress.steps.map(step => {
|
|
if (step.id === stepId) {
|
|
const updatedStep = {
|
|
...step,
|
|
status,
|
|
details: details || step.details
|
|
}
|
|
|
|
if (status === 'active' && !step.startTime) {
|
|
updatedStep.startTime = Date.now()
|
|
} else if ((status === 'completed' || status === 'error') && !step.endTime) {
|
|
updatedStep.endTime = Date.now()
|
|
}
|
|
|
|
return updatedStep
|
|
}
|
|
return step
|
|
})
|
|
|
|
const currentStepIndex = updatedSteps.findIndex(step => step.status === 'active')
|
|
|
|
const updatedProgress: AnalysisProgress = {
|
|
...progress,
|
|
steps: updatedSteps,
|
|
currentStep: currentStepIndex >= 0 ? currentStepIndex + 1 : progress.currentStep
|
|
}
|
|
|
|
this.sessions.set(sessionId, updatedProgress)
|
|
console.log(`🔍 Emitting progress event for ${sessionId}, currentStep: ${updatedProgress.currentStep}`)
|
|
|
|
// Small delay to ensure proper event ordering and prevent race conditions
|
|
setTimeout(() => {
|
|
this.emit(`progress:${sessionId}`, updatedProgress)
|
|
}, 50)
|
|
}
|
|
|
|
updateTimeframeProgress(sessionId: string, current: number, total: number, currentTimeframe?: string): void {
|
|
const progress = this.sessions.get(sessionId)
|
|
if (!progress) return
|
|
|
|
const updatedProgress: AnalysisProgress = {
|
|
...progress,
|
|
timeframeProgress: {
|
|
current,
|
|
total,
|
|
currentTimeframe
|
|
}
|
|
}
|
|
|
|
this.sessions.set(sessionId, updatedProgress)
|
|
this.emit(`progress:${sessionId}`, updatedProgress)
|
|
}
|
|
|
|
getProgress(sessionId: string): AnalysisProgress | undefined {
|
|
return this.sessions.get(sessionId)
|
|
}
|
|
|
|
deleteSession(sessionId: string): void {
|
|
this.sessions.delete(sessionId)
|
|
this.emit(`progress:${sessionId}:complete`)
|
|
}
|
|
|
|
// Get all active sessions (for debugging)
|
|
getActiveSessions(): string[] {
|
|
return Array.from(this.sessions.keys())
|
|
}
|
|
}
|
|
|
|
export const progressTracker = new ProgressTracker()
|