Spaces:
Paused
Paused
Prathamesh Sarjerao Vaidya commited on
Commit ·
d35734f
1
Parent(s): 0e9dd68
fixed some errors
Browse files- backend/app/api/routes.py +58 -22
- backend/app/api/websocket.py +11 -5
- backend/app/utils/helpers.py +21 -1
- frontend/js/main.js +84 -16
- frontend/js/services/api-service.js +7 -7
- frontend/js/ui/progress.js +44 -5
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 |
-
#
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
|
| 142 |
-
#
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
"progress": progress,
|
| 146 |
-
"message": message
|
| 147 |
-
})
|
| 148 |
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 164 |
)
|
| 165 |
|
|
|
|
|
|
|
|
|
|
| 166 |
# Save JSON
|
| 167 |
with open(results_path, 'w') as f:
|
| 168 |
-
json.dump(
|
| 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":
|
| 177 |
})
|
| 178 |
|
| 179 |
# Send completion notification
|
| 180 |
await manager.send_message(session_id, {
|
| 181 |
"type": "complete",
|
| 182 |
"session_id": session_id,
|
| 183 |
-
"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": "
|
| 197 |
-
"
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 32 |
except Exception as e:
|
| 33 |
-
logger.error(f"Error sending
|
| 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 |
-
|
|
|
|
| 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 |
-
//
|
| 151 |
-
|
| 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 |
-
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
|
| 49 |
-
|
| 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')
|
|
|
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
|
|
|
|
| 58 |
const estimatedTotal = elapsed / progress;
|
| 59 |
const remaining = Math.max(0, estimatedTotal - elapsed);
|
| 60 |
-
const etaSeconds = Math.ceil(remaining / 1000);
|
| 61 |
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|