Fix progress tracking synchronization issues

- 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.
This commit is contained in:
mindesbunister
2025-07-17 12:10:47 +02:00
parent 7e8f033bb2
commit 0399103f8a
4 changed files with 45 additions and 13 deletions

View File

@@ -6,13 +6,13 @@ import { progressTracker } from '../../../lib/progress-tracker'
export async function POST(request) { export async function POST(request) {
try { try {
const body = await request.json() const body = await request.json()
const { symbol, layouts, timeframe, timeframes, selectedLayouts, analyze = true } = body const { symbol, layouts, timeframe, timeframes, selectedLayouts, analyze = true, sessionId: providedSessionId } = body
console.log('📊 Enhanced screenshot request:', { symbol, layouts, timeframe, timeframes, selectedLayouts }) console.log('📊 Enhanced screenshot request:', { symbol, layouts, timeframe, timeframes, selectedLayouts, providedSessionId })
// Generate unique session ID for progress tracking // Use provided sessionId or generate one
const sessionId = `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` const sessionId = providedSessionId || `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
console.log('🔍 Created session ID:', sessionId) console.log('🔍 Using session ID:', sessionId)
// Create progress tracking session with initial steps // Create progress tracking session with initial steps
const initialSteps = [ const initialSteps = [

View File

@@ -12,21 +12,30 @@ export async function GET(
const stream = new ReadableStream({ const stream = new ReadableStream({
start(controller) { start(controller) {
console.log(`🔍 Starting EventSource stream for session: ${sessionId}`)
// Send initial progress if session exists // Send initial progress if session exists
const initialProgress = progressTracker.getProgress(sessionId) const initialProgress = progressTracker.getProgress(sessionId)
if (initialProgress) { if (initialProgress) {
console.log(`🔍 Sending initial progress for ${sessionId}:`, initialProgress.currentStep)
const data = `data: ${JSON.stringify(initialProgress)}\n\n` const data = `data: ${JSON.stringify(initialProgress)}\n\n`
controller.enqueue(encoder.encode(data)) controller.enqueue(encoder.encode(data))
} else {
// Send a connection established message even if no progress yet
const connectMsg = `data: ${JSON.stringify({ type: 'connected', sessionId })}\n\n`
controller.enqueue(encoder.encode(connectMsg))
} }
// Listen for progress updates // Listen for progress updates
const progressHandler = (progress: any) => { const progressHandler = (progress: any) => {
console.log(`🔍 Streaming progress update for ${sessionId}:`, progress.currentStep)
const data = `data: ${JSON.stringify(progress)}\n\n` const data = `data: ${JSON.stringify(progress)}\n\n`
controller.enqueue(encoder.encode(data)) controller.enqueue(encoder.encode(data))
} }
// Listen for completion // Listen for completion
const completeHandler = () => { const completeHandler = () => {
console.log(`🔍 Streaming completion for ${sessionId}`)
const data = `data: ${JSON.stringify({ type: 'complete' })}\n\n` const data = `data: ${JSON.stringify({ type: 'complete' })}\n\n`
controller.enqueue(encoder.encode(data)) controller.enqueue(encoder.encode(data))
controller.close() controller.close()

View File

@@ -40,6 +40,7 @@ interface ProgressStep {
} }
interface AnalysisProgress { interface AnalysisProgress {
sessionId: string
currentStep: number currentStep: number
totalSteps: number totalSteps: number
steps: ProgressStep[] steps: ProgressStep[]
@@ -82,6 +83,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
// Real-time progress tracking // Real-time progress tracking
const startProgressTracking = (sessionId: string) => { const startProgressTracking = (sessionId: string) => {
console.log(`🔍 Starting progress tracking for session: ${sessionId}`)
// Close existing connection // Close existing connection
if (eventSource) { if (eventSource) {
eventSource.close() eventSource.close()
@@ -89,13 +92,23 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
const es = new EventSource(`/api/progress/${sessionId}/stream`) const es = new EventSource(`/api/progress/${sessionId}/stream`)
es.onopen = () => {
console.log(`🔍 EventSource connection opened for ${sessionId}`)
}
es.onmessage = (event) => { es.onmessage = (event) => {
try { try {
const progressData = JSON.parse(event.data) const progressData = JSON.parse(event.data)
console.log(`🔍 Received progress update for ${sessionId}:`, progressData)
if (progressData.type === 'complete') { if (progressData.type === 'complete') {
console.log(`🔍 Analysis complete for ${sessionId}`)
es.close() es.close()
setEventSource(null) setEventSource(null)
} else if (progressData.type === 'connected') {
console.log(`🔍 EventSource connected for ${sessionId}`)
} else { } else {
// Update progress state immediately
setProgress(progressData) setProgress(progressData)
} }
} catch (error) { } catch (error) {
@@ -203,6 +216,11 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
try { try {
if (analysisTimeframes.length === 1) { if (analysisTimeframes.length === 1) {
// Single timeframe analysis with real-time progress // Single timeframe analysis with real-time progress
// Pre-generate sessionId and start progress tracking BEFORE making the API call
const sessionId = `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
startProgressTracking(sessionId)
const response = await fetch('/api/enhanced-screenshot', { const response = await fetch('/api/enhanced-screenshot', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -210,7 +228,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
symbol: analysisSymbol, symbol: analysisSymbol,
timeframe: analysisTimeframes[0], timeframe: analysisTimeframes[0],
layouts: selectedLayouts, layouts: selectedLayouts,
analyze: true analyze: true,
sessionId: sessionId // Pass pre-generated sessionId
}) })
}) })
@@ -220,11 +239,6 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
throw new Error(data.error || 'Analysis failed') throw new Error(data.error || 'Analysis failed')
} }
// Start real-time progress tracking if sessionId is provided
if (data.sessionId) {
startProgressTracking(data.sessionId)
}
setResult(data) setResult(data)
// Call the callback with analysis result if provided // Call the callback with analysis result if provided

View File

@@ -36,7 +36,12 @@ class ProgressTracker extends EventEmitter {
} }
this.sessions.set(sessionId, progress) this.sessions.set(sessionId, progress)
// Small delay to ensure EventSource connection is established before emitting
setTimeout(() => {
this.emit(`progress:${sessionId}`, progress) this.emit(`progress:${sessionId}`, progress)
}, 100)
return progress return progress
} }
@@ -77,7 +82,11 @@ class ProgressTracker extends EventEmitter {
this.sessions.set(sessionId, updatedProgress) this.sessions.set(sessionId, updatedProgress)
console.log(`🔍 Emitting progress event for ${sessionId}, currentStep: ${updatedProgress.currentStep}`) 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) this.emit(`progress:${sessionId}`, updatedProgress)
}, 50)
} }
updateTimeframeProgress(sessionId: string, current: number, total: number, currentTimeframe?: string): void { updateTimeframeProgress(sessionId: string, current: number, total: number, currentTimeframe?: string): void {