/** * Universal Model Trainer - Dashboard JavaScript */ // API Base URL const API_BASE = '/api'; // State Management const state = { jobs: [], models: [], datasets: [], systemJob: null, searchResults: { models: [], datasets: [] } }; // Utility Functions const utils = { async fetchAPI(endpoint, options = {}) { try { const response = await fetch(`${API_BASE}${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: response.statusText })); throw new Error(error.detail || 'Request failed'); } return await response.json(); } catch (error) { console.error('API Error:', error); throw error; } }, formatDate(dateStr) { if (!dateStr) return 'N/A'; return new Date(dateStr).toLocaleString(); }, formatDuration(seconds) { if (!seconds) return 'N/A'; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); return h > 0 ? `${h}h ${m}m ${s}s` : m > 0 ? `${m}m ${s}s` : `${s}s`; }, formatNumber(num) { if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; return num?.toString() || '0'; }, getStatusBadgeClass(status) { const classes = { 'queued': 'badge-queued', 'running': 'badge-running', 'completed': 'badge-completed', 'failed': 'badge-failed', 'cancelled': 'badge-cancelled', 'paused': 'badge-paused' }; return classes[status?.toLowerCase()] || 'badge-queued'; }, showToast(message, type = 'info') { const toastContainer = document.getElementById('toastContainer') || createToastContainer(); const toast = document.createElement('div'); toast.className = `alert alert-${type} alert-dismissible fade show`; toast.innerHTML = ` ${message} `; toastContainer.appendChild(toast); setTimeout(() => toast.remove(), 5000); }, debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } }; function createToastContainer() { const container = document.createElement('div'); container.id = 'toastContainer'; container.style.cssText = 'position: fixed; top: 80px; right: 20px; z-index: 9999; max-width: 400px;'; document.body.appendChild(container); return container; } // API Functions const api = { // Jobs async getJobs(filters = {}) { const params = new URLSearchParams(filters).toString(); return utils.fetchAPI(`/jobs?${params}`); }, async getJob(jobId) { return utils.fetchAPI(`/jobs/${jobId}`); }, async getActiveJobs() { return utils.fetchAPI('/jobs/active'); }, async getJobStatistics() { return utils.fetchAPI('/jobs/statistics'); }, async cancelJob(jobId) { return utils.fetchAPI(`/jobs/${jobId}/cancel`, { method: 'POST' }); }, async pauseJob(jobId) { return utils.fetchAPI(`/jobs/${jobId}/pause`, { method: 'POST' }); }, async resumeJob(jobId) { return utils.fetchAPI(`/jobs/${jobId}/resume`, { method: 'POST' }); }, async retryJob(jobId) { return utils.fetchAPI(`/jobs/${jobId}/retry`, { method: 'POST' }); }, async deleteJob(jobId) { return utils.fetchAPI(`/jobs/${jobId}`, { method: 'DELETE' }); }, async getJobLogs(jobId, limit = 100) { return utils.fetchAPI(`/jobs/${jobId}/logs?limit=${limit}`); }, async getJobCheckpoints(jobId) { return utils.fetchAPI(`/jobs/${jobId}/checkpoints`); }, // Training async startTraining(config) { return utils.fetchAPI('/training/start', { method: 'POST', body: JSON.stringify(config) }); }, async getTrainingTemplates() { return utils.fetchAPI('/training/templates'); }, // Models async searchModels(query, task = null, limit = 20) { const params = new URLSearchParams({ query, limit }); if (task) params.append('task', task); return utils.fetchAPI(`/models/search?${params}`); }, async getModelInfo(modelId) { return utils.fetchAPI(`/models/${encodeURIComponent(modelId)}`); }, async getModelRecommendations(task) { return utils.fetchAPI(`/models/recommendations/${task}`); }, async checkModelCompatibility(modelId, task) { return utils.fetchAPI(`/models/${encodeURIComponent(modelId)}/compatibility?task=${task}`); }, // Datasets async searchDatasets(query, limit = 20) { return utils.fetchAPI(`/datasets/search?query=${encodeURIComponent(query)}&limit=${limit}`); }, async getDatasetInfo(datasetId) { return utils.fetchAPI(`/datasets/${encodeURIComponent(datasetId)}`); }, async getDatasetRecommendations(task) { return utils.fetchAPI(`/datasets/recommendations/${task}`); }, async getDatasetPreview(datasetId, config = null, split = null) { let url = `/datasets/${encodeURIComponent(datasetId)}/preview`; const params = new URLSearchParams(); if (config) params.append('config', config); if (split) params.append('split', split); if (params.toString()) url += `?${params}`; return utils.fetchAPI(url); }, // System async getSystemInfo() { return utils.fetchAPI('/system/info'); }, async getResourceUsage() { return utils.fetchAPI('/system/resources'); }, async clearCache() { return utils.fetchAPI('/system/cache/clear', { method: 'POST' }); } }; // UI Components const ui = { // Dashboard Stats async loadDashboardStats() { try { const [stats, system] = await Promise.all([ api.getJobStatistics(), api.getResourceUsage() ]); document.getElementById('totalJobs').textContent = stats.total_jobs || 0; document.getElementById('runningJobs').textContent = stats.running_jobs || 0; document.getElementById('completedJobs').textContent = stats.completed_jobs || 0; document.getElementById('failedJobs').textContent = stats.failed_jobs || 0; // Update resource bars if (system.cpu_percent !== undefined) { document.getElementById('cpuUsage').style.width = `${system.cpu_percent}%`; document.getElementById('cpuUsageText').textContent = `${system.cpu_percent.toFixed(1)}%`; } if (system.memory_percent !== undefined) { document.getElementById('memoryUsage').style.width = `${system.memory_percent}%`; document.getElementById('memoryUsageText').textContent = `${system.memory_percent.toFixed(1)}%`; } if (system.disk_percent !== undefined) { document.getElementById('diskUsage').style.width = `${system.disk_percent}%`; document.getElementById('diskUsageText').textContent = `${system.disk_percent.toFixed(1)}%`; } } catch (error) { console.error('Error loading stats:', error); } }, // Jobs Table async loadJobsTable() { try { const response = await api.getJobs(); state.jobs = response.jobs || []; this.renderJobsTable(); } catch (error) { utils.showToast('Failed to load jobs', 'danger'); } }, renderJobsTable() { const tbody = document.getElementById('jobsTableBody'); if (!tbody) return; if (state.jobs.length === 0) { tbody.innerHTML = ` No training jobs yet. Start a new training to see it here. `; return; } tbody.innerHTML = state.jobs.map(job => ` ${job.id.substring(0, 8)}... ${job.task_type}
${job.model_name}
${job.dataset_name}
${job.status} ${job.progress !== undefined ? `
${job.progress.toFixed(1)}% ` : '-'} ${utils.formatDate(job.created_at)}
${job.status === 'running' ? ` ` : ''} ${job.status === 'paused' ? ` ` : ''} ${job.status === 'queued' || job.status === 'running' ? ` ` : ''} ${job.status === 'failed' ? ` ` : ''}
`).join(''); }, async showJobDetail(jobId) { try { const job = await api.getJob(jobId); state.currentJob = job; const modal = new bootstrap.Modal(document.getElementById('jobDetailModal')); // Populate modal content document.getElementById('jobDetailTitle').textContent = `Job ${job.id.substring(0, 8)}`; document.getElementById('jobDetailStatus').innerHTML = ` ${job.status} `; document.getElementById('jobDetailModel').textContent = job.model_name; document.getElementById('jobDetailDataset').textContent = job.dataset_name; document.getElementById('jobDetailTask').textContent = job.task_type; document.getElementById('jobDetailProgress').style.width = `${job.progress || 0}%`; document.getElementById('jobDetailProgressText').textContent = `${(job.progress || 0).toFixed(1)}%`; document.getElementById('jobDetailEpochs').textContent = `${job.current_epoch || 0}/${job.total_epochs || 0}`; document.getElementById('jobDetailLoss').textContent = job.current_loss?.toFixed(4) || 'N/A'; document.getElementById('jobDetailLR').textContent = job.learning_rate || 'N/A'; document.getElementById('jobDetailCreated').textContent = utils.formatDate(job.created_at); document.getElementById('jobDetailStarted').textContent = utils.formatDate(job.started_at); document.getElementById('jobDetailFinished').textContent = utils.formatDate(job.finished_at); document.getElementById('jobDetailDuration').textContent = utils.formatDuration(job.duration); // Load logs const logs = await api.getJobLogs(jobId, 50); const logsContainer = document.getElementById('jobDetailLogs'); logsContainer.innerHTML = logs.logs?.map(log => `
[${new Date(log.timestamp).toLocaleTimeString()}] ${log.message}
`).join('') || '
No logs available
'; modal.show(); } catch (error) { utils.showToast('Failed to load job details', 'danger'); } }, async cancelJob(jobId) { if (confirm('Are you sure you want to cancel this job?')) { try { await api.cancelJob(jobId); utils.showToast('Job cancelled', 'warning'); await this.loadJobsTable(); } catch (error) { utils.showToast('Failed to cancel job', 'danger'); } } }, async pauseJob(jobId) { try { await api.pauseJob(jobId); utils.showToast('Job paused', 'info'); await this.loadJobsTable(); } catch (error) { utils.showToast('Failed to pause job', 'danger'); } }, async resumeJob(jobId) { try { await api.resumeJob(jobId); utils.showToast('Job resumed', 'success'); await this.loadJobsTable(); } catch (error) { utils.showToast('Failed to resume job', 'danger'); } }, async retryJob(jobId) { if (confirm('Retry this job?')) { try { await api.retryJob(jobId); utils.showToast('Job retry started', 'info'); await this.loadJobsTable(); } catch (error) { utils.showToast('Failed to retry job', 'danger'); } } }, // Model Search async searchModels(query) { if (!query || query.length < 2) return; const task = document.getElementById('taskType')?.value; try { const results = await api.searchModels(query, task); state.searchResults.models = results.models || []; this.renderModelResults(); } catch (error) { utils.showToast('Model search failed', 'danger'); } }, renderModelResults() { const container = document.getElementById('modelResults'); if (!container) return; container.innerHTML = state.searchResults.models.map(model => `
${model.id}
${utils.formatNumber(model.downloads)} ${utils.formatNumber(model.likes)} ${model.pipeline_tag || model.task}
${model.description?.substring(0, 100) || ''}...
`).join('') || '
No models found
'; }, selectModel(modelId) { document.getElementById('modelName').value = modelId; document.getElementById('modelResults').style.display = 'none'; utils.showToast(`Selected model: ${modelId}`, 'success'); }, // Dataset Search async searchDatasets(query) { if (!query || query.length < 2) return; try { const results = await api.searchDatasets(query); state.searchResults.datasets = results.datasets || []; this.renderDatasetResults(); } catch (error) { utils.showToast('Dataset search failed', 'danger'); } }, renderDatasetResults() { const container = document.getElementById('datasetResults'); if (!container) return; container.innerHTML = state.searchResults.datasets.map(dataset => `
${dataset.id}
${utils.formatNumber(dataset.downloads)} ${utils.formatNumber(dataset.likes)}
${dataset.description?.substring(0, 100) || ''}...
`).join('') || '
No datasets found
'; }, selectDataset(datasetId) { document.getElementById('datasetName').value = datasetId; document.getElementById('datasetResults').style.display = 'none'; utils.showToast(`Selected dataset: ${datasetId}`, 'success'); }, // Training Form async submitTrainingForm() { const form = document.getElementById('trainingForm'); const formData = new FormData(form); const config = { model_name: formData.get('modelName'), dataset_config: { dataset_name: formData.get('datasetName'), split: formData.get('split') || 'train', text_column: formData.get('textColumn'), label_column: formData.get('labelColumn'), }, training_args: { task_type: formData.get('taskType'), epochs: parseInt(formData.get('epochs')) || 3, batch_size: parseInt(formData.get('batchSize')) || 8, learning_rate: parseFloat(formData.get('learningRate')) || 5e-5, max_length: parseInt(formData.get('maxLength')) || 512, warmup_steps: parseInt(formData.get('warmupSteps')) || 0, weight_decay: parseFloat(formData.get('weightDecay')) || 0.01, }, peft_config: { use_peft: formData.get('usePeft') === 'on', lora_r: parseInt(formData.get('loraR')) || 8, lora_alpha: parseInt(formData.get('loraAlpha')) || 16, lora_dropout: parseFloat(formData.get('loraDropout')) || 0.05, }, output_config: { output_dir: formData.get('outputDir') || 'output', push_to_hub: formData.get('pushToHub') === 'on', hub_repo_name: formData.get('hubRepoName'), } }; try { utils.showToast('Starting training...', 'info'); const result = await api.startTraining(config); utils.showToast(`Training started: ${result.job_id}`, 'success'); form.reset(); await this.loadJobsTable(); await this.loadDashboardStats(); } catch (error) { utils.showToast(`Failed to start training: ${error.message}`, 'danger'); } }, // System Status async loadSystemStatus() { try { const info = await api.getSystemInfo(); const container = document.getElementById('systemInfo'); if (container) { container.innerHTML = `

Platform: ${info.platform || 'N/A'}

Python: ${info.python_version || 'N/A'}

CUDA Available: ${info.cuda_available ? 'Yes' : 'No'}

Transformers: ${info.transformers_version || 'N/A'}

Torch: ${info.torch_version || 'N/A'}

GPU: ${info.gpu_info || 'None'}

`; } } catch (error) { console.error('Failed to load system info:', error); } }, async clearCache() { if (confirm('Clear all cached models and datasets?')) { try { await api.clearCache(); utils.showToast('Cache cleared', 'success'); await this.loadSystemStatus(); } catch (error) { utils.showToast('Failed to clear cache', 'danger'); } } } }; // Event Listeners document.addEventListener('DOMContentLoaded', () => { // Load initial data ui.loadDashboardStats(); ui.loadJobsTable(); ui.loadSystemStatus(); // Auto-refresh setInterval(() => { ui.loadDashboardStats(); ui.loadJobsTable(); }, 30000); // Model search with debounce const modelInput = document.getElementById('modelName'); if (modelInput) { modelInput.addEventListener('input', utils.debounce((e) => { ui.searchModels(e.target.value); }, 300)); modelInput.addEventListener('focus', () => { document.getElementById('modelResults').style.display = 'block'; }); } // Dataset search with debounce const datasetInput = document.getElementById('datasetName'); if (datasetInput) { datasetInput.addEventListener('input', utils.debounce((e) => { ui.searchDatasets(e.target.value); }, 300)); datasetInput.addEventListener('focus', () => { document.getElementById('datasetResults').style.display = 'block'; }); } // Training form submission const trainingForm = document.getElementById('trainingForm'); if (trainingForm) { trainingForm.addEventListener('submit', (e) => { e.preventDefault(); ui.submitTrainingForm(); }); } // Close search results on outside click document.addEventListener('click', (e) => { if (!e.target.closest('#modelName') && !e.target.closest('#modelResults')) { document.getElementById('modelResults')?.style.display = 'none'; } if (!e.target.closest('#datasetName') && !e.target.closest('#datasetResults')) { document.getElementById('datasetResults')?.style.display = 'none'; } }); }); // Export for global access window.ui = ui; window.api = api; window.utils = utils;