Prathamesh Sarjerao Vaidya commited on
Commit
d35734f
·
1 Parent(s): 0e9dd68

fixed some errors

Browse files
backend/app/api/routes.py CHANGED
@@ -20,6 +20,7 @@ from app.models.responses import (
20
  )
21
  from app.utils.file_utils import generate_session_id
22
  from app.services.cleanup_service import cleanup_service
 
23
 
24
  logger = logging.getLogger(__name__)
25
 
@@ -117,6 +118,7 @@ async def analyze_video(session_id: str):
117
  async def process_video_direct(session_id: str):
118
  """Direct video processing (async background task)"""
119
  from app.core.video_processor import VideoProcessor
 
120
  import json
121
 
122
  try:
@@ -129,29 +131,45 @@ async def process_video_direct(session_id: str):
129
  output_path = Config.OUTPUT_FOLDER / f"analyzed_{session_id}.mp4"
130
  results_path = Config.OUTPUT_FOLDER / f"results_{session_id}.json"
131
 
132
- # Create processor
133
  processor = VideoProcessor()
134
 
135
- # Progress callback
136
- async def progress_callback(progress: float, message: str):
137
- session_manager.update_session(session_id, {
138
- "progress": progress,
139
- "message": message
140
- })
141
 
142
- # Send WebSocket update if connected
143
- await manager.send_message(session_id, {
144
- "type": "progress",
145
- "progress": progress,
146
- "message": message
147
- })
148
 
149
- logger.info(f"Progress {session_id}: {progress*100:.1f}% - {message}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # Process video (blocking call in background task)
152
  logger.info(f"Processing video: {input_path}")
153
 
154
- # Run blocking code in executor to not block event loop
155
  import asyncio
156
  loop = asyncio.get_event_loop()
157
 
@@ -160,12 +178,15 @@ async def process_video_direct(session_id: str):
160
  processor.process_video,
161
  input_path,
162
  output_path,
163
- lambda p, m: asyncio.create_task(progress_callback(p, m))
164
  )
165
 
 
 
 
166
  # Save JSON
167
  with open(results_path, 'w') as f:
168
- json.dump(results, f, indent=2, default=str)
169
 
170
  # Update session
171
  session_manager.update_session(session_id, {
@@ -173,14 +194,14 @@ async def process_video_direct(session_id: str):
173
  "output_path": str(output_path),
174
  "results_path": str(results_path),
175
  "end_time": datetime.now().isoformat(),
176
- "results": results
177
  })
178
 
179
  # Send completion notification
180
  await manager.send_message(session_id, {
181
  "type": "complete",
182
  "session_id": session_id,
183
- "results": results
184
  })
185
 
186
  logger.info(f"✅ Processing completed: {session_id}")
@@ -192,10 +213,25 @@ async def process_video_direct(session_id: str):
192
  "error": str(e)
193
  })
194
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  await manager.send_message(session_id, {
196
- "type": "error",
197
- "error": str(e)
 
198
  })
 
 
199
 
200
 
201
  @router.get("/api/results/{session_id}", response_model=ResultsResponse)
 
20
  )
21
  from app.utils.file_utils import generate_session_id
22
  from app.services.cleanup_service import cleanup_service
23
+ from app.utils.helpers import convert_numpy_types
24
 
25
  logger = logging.getLogger(__name__)
26
 
 
118
  async def process_video_direct(session_id: str):
119
  """Direct video processing (async background task)"""
120
  from app.core.video_processor import VideoProcessor
121
+ from app.utils.helpers import convert_numpy_types
122
  import json
123
 
124
  try:
 
131
  output_path = Config.OUTPUT_FOLDER / f"analyzed_{session_id}.mp4"
132
  results_path = Config.OUTPUT_FOLDER / f"results_{session_id}.json"
133
 
 
134
  processor = VideoProcessor()
135
 
136
+ # Track last sent progress to throttle updates
137
+ last_sent_progress = -1
138
+
139
+ def progress_callback(progress: float, message: str):
140
+ """Throttled progress callback - only send every 10%"""
141
+ nonlocal last_sent_progress
142
 
143
+ # Round to nearest 10%
144
+ progress_percent = int(progress * 100)
145
+ progress_milestone = (progress_percent // 10) * 10
 
 
 
146
 
147
+ # Only send if we've crossed a 10% milestone
148
+ if progress_milestone > last_sent_progress or progress >= 1.0:
149
+ last_sent_progress = progress_milestone
150
+
151
+ # Update session
152
+ session_manager.update_session(session_id, {
153
+ "progress": progress,
154
+ "message": message
155
+ })
156
+
157
+ logger.info(f"📊 Progress {session_id}: {progress*100:.0f}% - {message}")
158
+
159
+ # Send WebSocket update
160
+ try:
161
+ import asyncio
162
+ loop = asyncio.get_event_loop()
163
+ if loop.is_running():
164
+ # Create task to send WebSocket message
165
+ asyncio.create_task(
166
+ send_progress_update(session_id, progress, message)
167
+ )
168
+ except Exception as e:
169
+ logger.debug(f"WebSocket send failed: {e}")
170
 
 
171
  logger.info(f"Processing video: {input_path}")
172
 
 
173
  import asyncio
174
  loop = asyncio.get_event_loop()
175
 
 
178
  processor.process_video,
179
  input_path,
180
  output_path,
181
+ progress_callback
182
  )
183
 
184
+ # Sanitize results
185
+ safe_results = convert_numpy_types(results)
186
+
187
  # Save JSON
188
  with open(results_path, 'w') as f:
189
+ json.dump(safe_results, f, indent=2, default=str)
190
 
191
  # Update session
192
  session_manager.update_session(session_id, {
 
194
  "output_path": str(output_path),
195
  "results_path": str(results_path),
196
  "end_time": datetime.now().isoformat(),
197
+ "results": safe_results
198
  })
199
 
200
  # Send completion notification
201
  await manager.send_message(session_id, {
202
  "type": "complete",
203
  "session_id": session_id,
204
+ "results": safe_results
205
  })
206
 
207
  logger.info(f"✅ Processing completed: {session_id}")
 
213
  "error": str(e)
214
  })
215
 
216
+ try:
217
+ await manager.send_message(session_id, {
218
+ "type": "error",
219
+ "error": str(e)
220
+ })
221
+ except:
222
+ pass
223
+
224
+
225
+ async def send_progress_update(session_id: str, progress: float, message: str):
226
+ """Helper to send progress via WebSocket"""
227
+ try:
228
  await manager.send_message(session_id, {
229
+ "type": "progress",
230
+ "progress": progress,
231
+ "message": message
232
  })
233
+ except Exception as e:
234
+ logger.debug(f"Failed to send progress update: {e}")
235
 
236
 
237
  @router.get("/api/results/{session_id}", response_model=ResultsResponse)
backend/app/api/websocket.py CHANGED
@@ -5,6 +5,7 @@ WebSocket connection management
5
  from typing import Dict
6
  from fastapi import WebSocket
7
  import logging
 
8
 
9
  logger = logging.getLogger(__name__)
10
 
@@ -18,19 +19,23 @@ class ConnectionManager:
18
  async def connect(self, session_id: str, websocket: WebSocket):
19
  await websocket.accept()
20
  self.active_connections[session_id] = websocket
21
- logger.info(f"WebSocket connected: {session_id}")
22
 
23
  def disconnect(self, session_id: str):
24
  if session_id in self.active_connections:
25
  del self.active_connections[session_id]
26
- logger.info(f"WebSocket disconnected: {session_id}")
27
 
28
  async def send_message(self, session_id: str, message: dict):
 
29
  if session_id in self.active_connections:
30
  try:
31
- await self.active_connections[session_id].send_json(message)
 
 
 
32
  except Exception as e:
33
- logger.error(f"Error sending message to {session_id}: {e}")
34
  self.disconnect(session_id)
35
 
36
  async def broadcast(self, message: dict):
@@ -38,7 +43,8 @@ class ConnectionManager:
38
  disconnected = []
39
  for session_id, connection in self.active_connections.items():
40
  try:
41
- await connection.send_json(message)
 
42
  except Exception:
43
  disconnected.append(session_id)
44
 
 
5
  from typing import Dict
6
  from fastapi import WebSocket
7
  import logging
8
+ import json
9
 
10
  logger = logging.getLogger(__name__)
11
 
 
19
  async def connect(self, session_id: str, websocket: WebSocket):
20
  await websocket.accept()
21
  self.active_connections[session_id] = websocket
22
+ logger.info(f"WebSocket connected: {session_id}")
23
 
24
  def disconnect(self, session_id: str):
25
  if session_id in self.active_connections:
26
  del self.active_connections[session_id]
27
+ logger.info(f"🔌 WebSocket disconnected: {session_id}")
28
 
29
  async def send_message(self, session_id: str, message: dict):
30
+ """Send JSON message to client"""
31
  if session_id in self.active_connections:
32
  try:
33
+ # ✅ Ensure clean JSON serialization
34
+ json_str = json.dumps(message, default=str)
35
+ await self.active_connections[session_id].send_text(json_str)
36
+ logger.debug(f"📤 Sent to {session_id}: {message.get('type')}")
37
  except Exception as e:
38
+ logger.error(f"Error sending to {session_id}: {e}")
39
  self.disconnect(session_id)
40
 
41
  async def broadcast(self, message: dict):
 
43
  disconnected = []
44
  for session_id, connection in self.active_connections.items():
45
  try:
46
+ json_str = json.dumps(message, default=str)
47
+ await connection.send_text(json_str)
48
  except Exception:
49
  disconnected.append(session_id)
50
 
backend/app/utils/helpers.py CHANGED
@@ -6,6 +6,7 @@ import time
6
  import logging
7
  from functools import wraps
8
  from typing import Any, Dict, Optional
 
9
 
10
  logger = logging.getLogger(__name__)
11
 
@@ -46,4 +47,23 @@ def create_error_response(error: str, details: Optional[str] = None) -> Dict[str
46
  }
47
  if details:
48
  response["details"] = details
49
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import logging
7
  from functools import wraps
8
  from typing import Any, Dict, Optional
9
+ import numpy as np
10
 
11
  logger = logging.getLogger(__name__)
12
 
 
47
  }
48
  if details:
49
  response["details"] = details
50
+ return response
51
+
52
+ def convert_numpy_types(obj):
53
+ """
54
+ Recursively convert numpy types to native Python types for JSON serialization
55
+ """
56
+ if isinstance(obj, np.bool_):
57
+ return bool(obj)
58
+ elif isinstance(obj, (np.int_, np.intc, np.intp, np.int8, np.int16, np.int32, np.int64)):
59
+ return int(obj)
60
+ elif isinstance(obj, (np.float_, np.float16, np.float32, np.float64)):
61
+ return float(obj)
62
+ elif isinstance(obj, np.ndarray):
63
+ return obj.tolist()
64
+ elif isinstance(obj, dict):
65
+ return {key: convert_numpy_types(value) for key, value in obj.items()}
66
+ elif isinstance(obj, (list, tuple)):
67
+ return [convert_numpy_types(item) for item in obj]
68
+ else:
69
+ return obj
frontend/js/main.js CHANGED
@@ -5,7 +5,7 @@
5
  import { APP_CONFIG } from './core/config.js';
6
  import { appState } from './core/state.js';
7
  import { apiService } from './services/api-service.js';
8
- import { pollingService } from './services/polling-service.js';
9
  import { toast } from './ui/toast.js';
10
  import { progressManager } from './ui/progress.js';
11
  import { videoHandler } from './handlers/video-handler.js';
@@ -142,23 +142,11 @@ async function startAnalysis() {
142
  // Start analysis
143
  const data = await apiService.startAnalysis(appState.sessionId);
144
 
145
- appState.setTask(data.task_id);
146
  progressManager.start();
147
-
148
  toast.info('Analysis started!');
149
 
150
- // Start polling
151
- pollingService.startPolling(data.task_id, {
152
- onProgress: (progress, message) => {
153
- progressManager.update(progress, message);
154
- },
155
- onComplete: async (result) => {
156
- await handleAnalysisComplete(result);
157
- },
158
- onError: (error) => {
159
- handleAnalysisError(error);
160
- }
161
- });
162
 
163
  } catch (error) {
164
  console.error('Analysis error:', error);
@@ -168,6 +156,86 @@ async function startAnalysis() {
168
  }
169
  }
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  async function handleAnalysisComplete(result) {
172
  progressManager.complete();
173
 
@@ -266,7 +334,7 @@ function displayBodyParts(bodyParts) {
266
  function resetApp() {
267
  appState.reset();
268
  progressManager.reset();
269
- pollingService.stopPolling();
270
 
271
  elements.fileInfo.style.display = 'none';
272
  elements.uploadSection.style.display = 'block';
 
5
  import { APP_CONFIG } from './core/config.js';
6
  import { appState } from './core/state.js';
7
  import { apiService } from './services/api-service.js';
8
+ // import { pollingService } from './services/polling-service.js';
9
  import { toast } from './ui/toast.js';
10
  import { progressManager } from './ui/progress.js';
11
  import { videoHandler } from './handlers/video-handler.js';
 
142
  // Start analysis
143
  const data = await apiService.startAnalysis(appState.sessionId);
144
 
 
145
  progressManager.start();
 
146
  toast.info('Analysis started!');
147
 
148
+ // Connect WebSocket for real-time updates
149
+ connectWebSocket(appState.sessionId);
 
 
 
 
 
 
 
 
 
 
150
 
151
  } catch (error) {
152
  console.error('Analysis error:', error);
 
156
  }
157
  }
158
 
159
+ function connectWebSocket(sessionId) {
160
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
161
+ const wsUrl = `${protocol}//${window.location.host}/ws/${sessionId}`;
162
+
163
+ console.log('🔌 Connecting WebSocket:', wsUrl);
164
+
165
+ const ws = new WebSocket(wsUrl);
166
+ let heartbeatInterval;
167
+
168
+ ws.onopen = () => {
169
+ console.log('✅ WebSocket connected');
170
+ toast.info('Connected - receiving updates');
171
+
172
+ // Send heartbeat every 20 seconds
173
+ heartbeatInterval = setInterval(() => {
174
+ if (ws.readyState === WebSocket.OPEN) {
175
+ ws.send('ping');
176
+ }
177
+ }, 20000);
178
+ };
179
+
180
+ ws.onmessage = (event) => {
181
+ try {
182
+ const data = JSON.parse(event.data);
183
+ console.log('📨 WebSocket message:', data);
184
+
185
+ switch (data.type) {
186
+ case 'connected':
187
+ console.log('✅ Connected to session:', data.session_id);
188
+ break;
189
+
190
+ case 'progress':
191
+ // ✅ Update progress bar and ETA
192
+ const progress = data.progress || 0;
193
+ const message = data.message || 'Processing...';
194
+
195
+ console.log(`📊 Progress: ${(progress * 100).toFixed(0)}%`);
196
+ progressManager.update(progress, message);
197
+ break;
198
+
199
+ case 'complete':
200
+ console.log('🎉 Analysis complete!');
201
+ clearInterval(heartbeatInterval);
202
+ handleAnalysisComplete(data);
203
+ ws.close();
204
+ break;
205
+
206
+ case 'error':
207
+ console.error('❌ Error:', data.error);
208
+ clearInterval(heartbeatInterval);
209
+ handleAnalysisError(new Error(data.error));
210
+ ws.close();
211
+ break;
212
+
213
+ case 'pong':
214
+ // Heartbeat response
215
+ break;
216
+
217
+ default:
218
+ console.log('Unknown message type:', data.type);
219
+ }
220
+ } catch (error) {
221
+ console.error('Failed to parse WebSocket message:', error, event.data);
222
+ }
223
+ };
224
+
225
+ ws.onerror = (error) => {
226
+ console.error('❌ WebSocket error:', error);
227
+ toast.error('Connection error - progress may not update');
228
+ };
229
+
230
+ ws.onclose = (event) => {
231
+ console.log('🔌 WebSocket closed:', event.code, event.reason);
232
+ clearInterval(heartbeatInterval);
233
+ };
234
+
235
+ // Store reference for cleanup
236
+ appState.ws = ws;
237
+ }
238
+
239
  async function handleAnalysisComplete(result) {
240
  progressManager.complete();
241
 
 
334
  function resetApp() {
335
  appState.reset();
336
  progressManager.reset();
337
+ // pollingService.stopPolling();
338
 
339
  elements.fileInfo.style.display = 'none';
340
  elements.uploadSection.style.display = 'block';
frontend/js/services/api-service.js CHANGED
@@ -39,15 +39,15 @@ export class APIService {
39
  return await response.json();
40
  }
41
 
42
- async getTaskStatus(taskId) {
43
- const response = await fetch(`${this.baseURL}/api/task/${taskId}`);
44
 
45
- if (!response.ok) {
46
- throw new Error('Failed to get task status');
47
- }
48
 
49
- return await response.json();
50
- }
51
 
52
  async getResults(sessionId) {
53
  const response = await fetch(`${this.baseURL}/api/results/${sessionId}`);
 
39
  return await response.json();
40
  }
41
 
42
+ // async getTaskStatus(taskId) {
43
+ // const response = await fetch(`${this.baseURL}/api/task/${taskId}`);
44
 
45
+ // if (!response.ok) {
46
+ // throw new Error('Failed to get task status');
47
+ // }
48
 
49
+ // return await response.json();
50
+ // }
51
 
52
  async getResults(sessionId) {
53
  const response = await fetch(`${this.baseURL}/api/results/${sessionId}`);
frontend/js/ui/progress.js CHANGED
@@ -9,25 +9,32 @@ export class ProgressManager {
9
  text: document.getElementById('progressText'),
10
  message: document.getElementById('processingMessage'),
11
  elapsed: document.getElementById('elapsedTime'),
12
- eta: document.getElementById('etaTime') // New element for ETA
 
13
  };
14
  this.startTime = null;
15
  this.interval = null;
 
16
  }
17
 
18
  start() {
19
  this.startTime = Date.now();
 
20
  this.updateElapsedTime();
21
 
22
  // Update elapsed time every second
23
  this.interval = setInterval(() => {
24
  this.updateElapsedTime();
25
  }, 1000);
 
 
26
  }
27
 
28
  update(progress, message = '') {
29
  const percentage = Math.round(progress * 100);
30
 
 
 
31
  if (this.elements.fill) {
32
  this.elements.fill.style.width = `${percentage}%`;
33
  }
@@ -40,7 +47,12 @@ export class ProgressManager {
40
  this.elements.message.textContent = message;
41
  }
42
 
 
 
 
 
43
  // Update ETA
 
44
  this.updateETA(progress);
45
  }
46
 
@@ -52,14 +64,27 @@ export class ProgressManager {
52
  }
53
 
54
  updateETA(progress) {
55
- if (!this.startTime || !this.elements.eta || progress <= 0) return;
 
 
 
 
 
56
 
57
- const elapsed = Date.now() - this.startTime;
 
58
  const estimatedTotal = elapsed / progress;
59
  const remaining = Math.max(0, estimatedTotal - elapsed);
60
- const etaSeconds = Math.ceil(remaining / 1000);
61
 
62
- this.elements.eta.textContent = this.formatTime(etaSeconds);
 
 
 
 
 
 
 
 
63
  }
64
 
65
  formatTime(seconds) {
@@ -77,7 +102,17 @@ export class ProgressManager {
77
  }
78
 
79
  complete() {
 
80
  this.update(1.0, 'Analysis complete!');
 
 
 
 
 
 
 
 
 
81
  this.stop();
82
  }
83
 
@@ -91,12 +126,16 @@ export class ProgressManager {
91
  reset() {
92
  this.stop();
93
  this.startTime = null;
 
94
 
95
  if (this.elements.fill) this.elements.fill.style.width = '0%';
96
  if (this.elements.text) this.elements.text.textContent = '0%';
97
  if (this.elements.message) this.elements.message.textContent = '';
98
  if (this.elements.elapsed) this.elements.elapsed.textContent = '0s';
99
  if (this.elements.eta) this.elements.eta.textContent = '--';
 
 
 
100
  }
101
  }
102
 
 
9
  text: document.getElementById('progressText'),
10
  message: document.getElementById('processingMessage'),
11
  elapsed: document.getElementById('elapsedTime'),
12
+ eta: document.getElementById('etaTime'),
13
+ status: document.getElementById('statusValue')
14
  };
15
  this.startTime = null;
16
  this.interval = null;
17
+ this.lastProgress = 0;
18
  }
19
 
20
  start() {
21
  this.startTime = Date.now();
22
+ this.lastProgress = 0;
23
  this.updateElapsedTime();
24
 
25
  // Update elapsed time every second
26
  this.interval = setInterval(() => {
27
  this.updateElapsedTime();
28
  }, 1000);
29
+
30
+ console.log('⏱️ Progress tracking started');
31
  }
32
 
33
  update(progress, message = '') {
34
  const percentage = Math.round(progress * 100);
35
 
36
+ console.log(`📊 Updating progress: ${percentage}% - ${message}`);
37
+
38
  if (this.elements.fill) {
39
  this.elements.fill.style.width = `${percentage}%`;
40
  }
 
47
  this.elements.message.textContent = message;
48
  }
49
 
50
+ if (this.elements.status) {
51
+ this.elements.status.textContent = 'Processing';
52
+ }
53
+
54
  // Update ETA
55
+ this.lastProgress = progress;
56
  this.updateETA(progress);
57
  }
58
 
 
64
  }
65
 
66
  updateETA(progress) {
67
+ if (!this.startTime || !this.elements.eta || progress <= 0) {
68
+ if (this.elements.eta) {
69
+ this.elements.eta.textContent = 'Calculating...';
70
+ }
71
+ return;
72
+ }
73
 
74
+ // Calculate ETA based on current progress
75
+ const elapsed = (Date.now() - this.startTime) / 1000; // seconds
76
  const estimatedTotal = elapsed / progress;
77
  const remaining = Math.max(0, estimatedTotal - elapsed);
 
78
 
79
+ console.log(`⏱️ ETA: ${remaining.toFixed(0)}s remaining (${(progress * 100).toFixed(0)}% complete)`);
80
+
81
+ if (this.elements.eta) {
82
+ if (remaining > 0 && progress < 1.0) {
83
+ this.elements.eta.textContent = this.formatTime(Math.ceil(remaining));
84
+ } else {
85
+ this.elements.eta.textContent = 'Almost done!';
86
+ }
87
+ }
88
  }
89
 
90
  formatTime(seconds) {
 
102
  }
103
 
104
  complete() {
105
+ console.log('✅ Progress complete!');
106
  this.update(1.0, 'Analysis complete!');
107
+
108
+ if (this.elements.status) {
109
+ this.elements.status.textContent = 'Complete';
110
+ }
111
+
112
+ if (this.elements.eta) {
113
+ this.elements.eta.textContent = 'Done!';
114
+ }
115
+
116
  this.stop();
117
  }
118
 
 
126
  reset() {
127
  this.stop();
128
  this.startTime = null;
129
+ this.lastProgress = 0;
130
 
131
  if (this.elements.fill) this.elements.fill.style.width = '0%';
132
  if (this.elements.text) this.elements.text.textContent = '0%';
133
  if (this.elements.message) this.elements.message.textContent = '';
134
  if (this.elements.elapsed) this.elements.elapsed.textContent = '0s';
135
  if (this.elements.eta) this.elements.eta.textContent = '--';
136
+ if (this.elements.status) this.elements.status.textContent = 'Ready';
137
+
138
+ console.log('🔄 Progress reset');
139
  }
140
  }
141