sync active training with UI if running

This commit is contained in:
Dobromir Popov
2025-11-12 22:37:20 +02:00
parent fcbc475686
commit 1af3124be7
7 changed files with 384 additions and 10 deletions

View File

@@ -2054,6 +2054,52 @@ class RealTrainingAdapter:
'duration_seconds': session.duration_seconds,
'error': session.error
}
def get_active_training_session(self) -> Optional[Dict]:
"""
Get currently active training session (if any)
This allows the UI to resume tracking training progress after page reload
Returns:
Dict with training info if active session exists, None otherwise
"""
# Find any session with 'running' status
for training_id, session in self.training_sessions.items():
if session.status == 'running':
return {
'training_id': training_id,
'status': session.status,
'model_name': session.model_name,
'test_cases_count': session.test_cases_count,
'current_epoch': session.current_epoch,
'total_epochs': session.total_epochs,
'current_loss': session.current_loss,
'start_time': session.start_time
}
return None
def get_all_training_sessions(self) -> List[Dict]:
"""
Get all training sessions (for debugging/monitoring)
Returns:
List of all training session summaries
"""
sessions = []
for training_id, session in self.training_sessions.items():
sessions.append({
'training_id': training_id,
'status': session.status,
'model_name': session.model_name,
'current_epoch': session.current_epoch,
'total_epochs': session.total_epochs,
'start_time': session.start_time,
'duration_seconds': session.duration_seconds
})
return sessions
# Real-time inference support

View File

@@ -1241,6 +1241,48 @@ class AnnotationDashboard:
}
})
@self.server.route('/api/active-training', methods=['GET'])
def get_active_training():
"""
Get currently active training session (if any)
Allows UI to resume tracking after page reload or across multiple clients
"""
try:
if not self.training_adapter:
return jsonify({
'success': False,
'active': False,
'error': {
'code': 'TRAINING_UNAVAILABLE',
'message': 'Real training adapter not available'
}
})
active_session = self.training_adapter.get_active_training_session()
if active_session:
return jsonify({
'success': True,
'active': True,
'session': active_session
})
else:
return jsonify({
'success': True,
'active': False
})
except Exception as e:
logger.error(f"Error getting active training: {e}")
return jsonify({
'success': False,
'active': False,
'error': {
'code': 'ACTIVE_TRAINING_ERROR',
'message': str(e)
}
})
# Live Training API Endpoints
@self.server.route('/api/live-training/start', methods=['POST'])
def start_live_training():

View File

@@ -92,6 +92,16 @@
// Load initial data (may call renderAnnotationsList which needs deleteAnnotation)
loadInitialData();
// Load available models for training panel
if (typeof loadAvailableModels === 'function') {
loadAvailableModels();
}
// Check for active training session (resume tracking after page reload)
if (typeof checkActiveTraining === 'function') {
checkActiveTraining();
}
// Setup keyboard shortcuts
setupKeyboardShortcuts();
});

View File

@@ -109,6 +109,30 @@
// Track model states
let modelStates = [];
let selectedModel = null;
let activeTrainingId = null; // Track active training session
function checkActiveTraining() {
/**
* Check if there's an active training session on page load
* This allows resuming progress tracking after page reload
*/
fetch('/api/active-training')
.then(response => response.json())
.then(data => {
if (data.success && data.active && data.session) {
console.log('Active training session found:', data.session);
// Resume tracking
activeTrainingId = data.session.training_id;
showTrainingStatus();
pollTrainingProgress(activeTrainingId);
} else {
console.log('No active training session');
}
})
.catch(error => {
console.error('Error checking active training:', error);
});
}
function loadAvailableModels() {
fetch('/api/available-models')
@@ -290,11 +314,16 @@
startTraining(modelName, annotationIds);
});
function startTraining(modelName, annotationIds) {
// Show training status
function showTrainingStatus() {
// Show training status UI
document.getElementById('training-status').style.display = 'block';
document.getElementById('training-results').style.display = 'none';
document.getElementById('train-model-btn').disabled = true;
}
function startTraining(modelName, annotationIds) {
// Show training status
showTrainingStatus();
// Reset progress
document.getElementById('training-progress-bar').style.width = '0%';
@@ -313,18 +342,22 @@
.then(response => response.json())
.then(data => {
if (data.success) {
// Store active training ID for persistence across reloads
activeTrainingId = data.training_id;
// Start polling for training progress
pollTrainingProgress(data.training_id);
} else {
showError('Failed to start training: ' + data.error.message);
document.getElementById('training-status').style.display = 'none';
document.getElementById('train-model-btn').disabled = false;
activeTrainingId = null;
}
})
.catch(error => {
showError('Network error: ' + error.message);
document.getElementById('training-status').style.display = 'none';
document.getElementById('train-model-btn').disabled = false;
activeTrainingId = null;
});
}
@@ -350,9 +383,11 @@
// Check if complete
if (progress.status === 'completed') {
clearInterval(pollInterval);
activeTrainingId = null; // Clear active training
showTrainingResults(progress);
} else if (progress.status === 'failed') {
clearInterval(pollInterval);
activeTrainingId = null; // Clear active training
showError('Training failed: ' + progress.error);
document.getElementById('training-status').style.display = 'none';
document.getElementById('train-model-btn').disabled = false;
@@ -361,6 +396,7 @@
})
.catch(error => {
clearInterval(pollInterval);
// Don't clear activeTrainingId on network error - training might still be running
showError('Failed to get training progress: ' + error.message);
document.getElementById('training-status').style.display = 'none';
document.getElementById('train-model-btn').disabled = false;