// Multi-Modal Knowledge Distillation - JavaScript class KnowledgeDistillationApp { constructor() { this.selectedModels = []; this.selectedTeachers = []; this.selectedStudent = null; this.configuredModels = {}; this.currentStep = 1; this.trainingSession = null; this.websocket = null; // Add global error handler window.addEventListener('error', (event) => { console.error('Global error:', event.error); this.handleGlobalError(event.error); }); // Add unhandled promise rejection handler window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); this.handleGlobalError(event.reason); }); this.init(); } handleGlobalError(error) { const errorMsg = error?.message || 'An unexpected error occurred'; console.error('Handling global error:', errorMsg); // Try to show error in UI, fallback to console try { if (this.showError) { this.showError(`Error: ${errorMsg}`); } } catch (e) { console.error('Could not show error in UI:', e); } } init() { this.setupEventListeners(); this.updateModelCount(); // Initialize models manager this.modelsManager = new ModelsManager(this); } setupEventListeners() { // File upload const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('dragover', this.handleDragOver.bind(this)); uploadArea.addEventListener('dragleave', this.handleDragLeave.bind(this)); uploadArea.addEventListener('drop', this.handleDrop.bind(this)); fileInput.addEventListener('change', this.handleFileSelect.bind(this)); // Hugging Face models document.getElementById('add-hf-model').addEventListener('click', this.addHuggingFaceModel.bind(this)); document.getElementById('hf-repo').addEventListener('keypress', (e) => { if (e.key === 'Enter') this.addHuggingFaceModel(); }); // URL models document.getElementById('add-url-model').addEventListener('click', this.addUrlModel.bind(this)); document.getElementById('model-url').addEventListener('keypress', (e) => { if (e.key === 'Enter') this.addUrlModel(); }); // Navigation document.getElementById('next-step-1').addEventListener('click', () => this.goToStep(2)); document.getElementById('back-step-2').addEventListener('click', () => this.goToStep(1)); document.getElementById('back-step-3').addEventListener('click', () => this.goToStep(2)); document.getElementById('start-training').addEventListener('click', this.showConfirmModal.bind(this)); document.getElementById('start-new-training').addEventListener('click', () => this.resetAndGoToStep(1)); // Training controls document.getElementById('cancel-training').addEventListener('click', this.cancelTraining.bind(this)); document.getElementById('download-model').addEventListener('click', this.downloadModel.bind(this)); // Modals document.getElementById('confirm-start').addEventListener('click', this.startTraining.bind(this)); document.getElementById('confirm-cancel').addEventListener('click', this.hideConfirmModal.bind(this)); document.getElementById('error-ok').addEventListener('click', this.hideErrorModal.bind(this)); // Suggested models document.querySelectorAll('.suggestion-btn').forEach(btn => { btn.addEventListener('click', (e) => { const modelName = e.target.getAttribute('data-model'); const trustRequired = e.target.classList.contains('trust-required'); const gatedModel = e.target.classList.contains('gated-model'); document.getElementById('hf-repo').value = modelName; // Auto-enable trust remote code if required if (trustRequired) { document.getElementById('trust-remote-code').checked = true; this.showTokenStatus('⚠️ Trust Remote Code enabled for this model', 'warning'); } // Show warning for gated models if (gatedModel) { const tokenInput = document.getElementById('hf-token'); if (!tokenInput.value.trim()) { this.showTokenStatus('🔒 This model requires a Hugging Face token and access permission!', 'error'); tokenInput.focus(); return; } else { this.showTokenStatus('✅ Token detected for gated model', 'success'); } } this.addHuggingFaceModel(); }); }); // Test token button document.getElementById('test-token').addEventListener('click', this.testToken.bind(this)); // Test model button document.getElementById('test-model').addEventListener('click', this.testModel.bind(this)); // Download and upload buttons document.getElementById('download-model').addEventListener('click', this.downloadModel.bind(this)); document.getElementById('upload-to-hf').addEventListener('click', this.showHFUploadModal.bind(this)); document.getElementById('confirm-hf-upload').addEventListener('click', this.uploadToHuggingFace.bind(this)); document.getElementById('cancel-hf-upload').addEventListener('click', this.hideHFUploadModal.bind(this)); // Incremental training document.getElementById('enable-incremental').addEventListener('change', this.toggleIncrementalTraining.bind(this)); document.getElementById('existing-student').addEventListener('change', this.onStudentModelChange.bind(this)); document.getElementById('refresh-students').addEventListener('click', this.loadTrainedStudents.bind(this)); // Student source options document.querySelectorAll('input[name="student-source"]').forEach(radio => { radio.addEventListener('change', this.onStudentSourceChange.bind(this)); }); // HF student model document.getElementById('test-student-model').addEventListener('click', this.testStudentModel.bind(this)); document.getElementById('add-hf-student').addEventListener('click', this.addHFStudentModel.bind(this)); // HF Space student model document.getElementById('test-space-model').addEventListener('click', this.testSpaceModel.bind(this)); document.getElementById('add-space-student').addEventListener('click', this.addSpaceStudentModel.bind(this)); // File upload document.getElementById('student-file-upload').addEventListener('change', this.onStudentFilesUpload.bind(this)); // Load trained students on page load this.loadTrainedStudents(); } // File handling handleDragOver(e) { e.preventDefault(); e.currentTarget.classList.add('dragover'); } handleDragLeave(e) { e.preventDefault(); e.currentTarget.classList.remove('dragover'); } handleDrop(e) { e.preventDefault(); e.currentTarget.classList.remove('dragover'); const files = Array.from(e.dataTransfer.files); this.processFiles(files); } handleFileSelect(e) { const files = Array.from(e.target.files); this.processFiles(files); } async processFiles(files) { const validFiles = files.filter(file => this.validateFile(file)); if (validFiles.length === 0) { this.showError('No valid model files selected. Please select .pt, .pth, .bin, or .safetensors files.'); return; } this.showLoading(`Processing ${validFiles.length} file(s)...`); try { for (const file of validFiles) { await this.uploadFile(file); } } catch (error) { this.showError(`Error processing files: ${error.message}`); } finally { this.hideLoading(); } } validateFile(file) { const validExtensions = ['.pt', '.pth', '.bin', '.safetensors']; const extension = '.' + file.name.split('.').pop().toLowerCase(); const maxSize = 5 * 1024 * 1024 * 1024; // 5GB if (!validExtensions.includes(extension)) { this.showError(`Invalid file type: ${file.name}. Allowed types: ${validExtensions.join(', ')}`); return false; } if (file.size > maxSize) { this.showError(`File too large: ${file.name}. Maximum size: 5GB`); return false; } return true; } async uploadFile(file) { const formData = new FormData(); formData.append('files', file); formData.append('model_names', file.name.split('.')[0]); try { const response = await fetch('/upload', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (result.success) { result.models.forEach(model => this.addModel(model)); this.addConsoleMessage(`Successfully uploaded: ${file.name}`, 'success'); } else { throw new Error(result.message || 'Upload failed'); } } catch (error) { this.showError(`Upload failed for ${file.name}: ${error.message}`); throw error; } } async addHuggingFaceModel() { const repoInput = document.getElementById('hf-repo'); const tokenInput = document.getElementById('hf-token'); const accessTypeSelect = document.getElementById('model-access-type'); const repo = repoInput.value.trim(); const manualToken = tokenInput.value.trim(); const accessType = accessTypeSelect ? accessTypeSelect.value : 'read'; if (!repo) { this.showError('Please enter a Hugging Face repository name'); return; } if (!this.isValidHuggingFaceRepo(repo)) { this.showError('Invalid repository format. Use format: organization/model-name (e.g., google/bert_uncased_L-2_H-128_A-2)'); return; } let tokenToUse = manualToken; // If no manual token provided, get appropriate token for access type if (!manualToken) { try { const response = await fetch(`/api/tokens/for-task/${accessType}`); if (response.ok) { const data = await response.json(); if (data.success) { // We don't store the actual token, just indicate it will be used this.showSuccess(`سيتم استخدام ${data.token_info.type_name} للوصول للنموذج`); tokenToUse = 'auto'; // Indicate automatic token selection } } else { this.showWarning('لم يتم العثور على رمز مناسب، قد تحتاج لإضافة رمز يدوياً'); } } catch (error) { console.error('Error getting token for task:', error); this.showWarning('خطأ في الحصول على الرمز المناسب'); } } const model = { id: `hf_${Date.now()}`, name: repo, source: 'huggingface', path: repo, token: tokenToUse, accessType: accessType, info: { modality: 'unknown', format: 'huggingface' } }; this.addModel(model); repoInput.value = ''; // Don't clear token as user might want to use it for multiple models } async addUrlModel() { const urlInput = document.getElementById('model-url'); const url = urlInput.value.trim(); if (!url) { this.showError('Please enter a model URL'); return; } if (!this.isValidUrl(url)) { this.showError('Invalid URL format'); return; } // Validate that URL points to a model file const filename = this.extractFilenameFromUrl(url); const validExtensions = ['.pt', '.pth', '.bin', '.safetensors']; const hasValidExtension = validExtensions.some(ext => filename.toLowerCase().endsWith(ext)); if (!hasValidExtension) { this.showError(`URL must point to a model file with extension: ${validExtensions.join(', ')}`); return; } this.showLoading('Validating URL...'); try { // Test if URL is accessible const response = await fetch(url, { method: 'HEAD' }); if (!response.ok) { throw new Error(`URL not accessible: ${response.status}`); } const model = { id: `url_${Date.now()}`, name: filename, source: 'url', path: url, info: { modality: 'unknown', format: filename.split('.').pop(), size: response.headers.get('content-length') ? parseInt(response.headers.get('content-length')) : null } }; this.addModel(model); urlInput.value = ''; this.hideLoading(); } catch (error) { this.hideLoading(); this.showError(`URL validation failed: ${error.message}`); } } addModel(model) { if (this.selectedModels.length >= 10) { this.showError('Maximum 10 models allowed'); return; } // Check for duplicates if (this.selectedModels.some(m => m.path === model.path)) { this.showError('Model already added'); return; } this.selectedModels.push(model); this.updateModelsDisplay(); this.updateModelCount(); this.updateNextButton(); } removeModel(modelId) { this.selectedModels = this.selectedModels.filter(m => m.id !== modelId); this.updateModelsDisplay(); this.updateModelCount(); this.updateNextButton(); } updateModelsDisplay() { const grid = document.getElementById('models-grid'); grid.innerHTML = ''; this.selectedModels.forEach(model => { const card = this.createModelCard(model); grid.appendChild(card); }); } createModelCard(model) { const card = document.createElement('div'); card.className = 'model-card'; const modalityIcon = this.getModalityIcon(model.info.modality); const sizeText = model.size ? this.formatBytes(model.size) : 'Unknown size'; card.innerHTML = `

${modalityIcon} ${model.name}

Source: ${model.source}
Format: ${model.info.format}
Modality: ${model.info.modality}
Size: ${sizeText}
`; return card; } getModalityIcon(modality) { const icons = { text: '', vision: '', multimodal: '', audio: '', unknown: '' }; return icons[modality] || icons.unknown; } updateModelCount() { document.getElementById('model-count').textContent = this.selectedModels.length; } updateNextButton() { const button = document.getElementById('next-step-1'); button.disabled = this.selectedModels.length === 0; } // Navigation goToStep(step) { // Hide all steps document.querySelectorAll('.step-section').forEach(section => { section.classList.add('hidden'); }); // Show target step document.getElementById(`step-${step}`).classList.remove('hidden'); this.currentStep = step; } resetAndGoToStep(step) { // Reset training session this.trainingSession = null; if (this.websocket) { this.websocket.close(); this.websocket = null; } // Reset UI elements document.getElementById('download-model').classList.add('hidden'); document.getElementById('start-new-training').classList.add('hidden'); document.getElementById('cancel-training').classList.remove('hidden'); // Clear console document.getElementById('training-console').innerHTML = ''; // Reset progress document.getElementById('overall-progress').style.width = '0%'; document.getElementById('progress-percentage').textContent = '0%'; // Go to step this.goToStep(step); } // Training showConfirmModal() { document.getElementById('confirm-modal').classList.remove('hidden'); } hideConfirmModal() { document.getElementById('confirm-modal').classList.add('hidden'); } async startTraining() { this.hideConfirmModal(); // Get configuration const config = this.getTrainingConfig(); // Check if any models require token and warn user const hasGatedModels = this.selectedModels.some(model => model.path.includes('gemma') || model.path.includes('llama') || model.path.includes('claude') ); if (hasGatedModels && !config.hf_token) { const proceed = confirm( 'Some selected models may require a Hugging Face token for access. ' + 'Do you want to continue without a token? (Training may fail for gated models)' ); if (!proceed) return; } try { const response = await fetch('/start-training', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const result = await response.json(); if (result.success) { this.trainingSession = result.session_id; this.goToStep(3); this.connectWebSocket(); this.startProgressPolling(); } else { throw new Error(result.message || 'Failed to start training'); } } catch (error) { this.showError(`Failed to start training: ${error.message}`); } } getTrainingConfig() { // Get HF token from interface const hfToken = document.getElementById('hf-token').value.trim(); const trustRemoteCode = document.getElementById('trust-remote-code').checked; const incrementalTraining = document.getElementById('enable-incremental').checked; const existingStudent = document.getElementById('existing-student').value; // Get student model info based on source let studentModelPath = null; let studentSource = 'local'; if (incrementalTraining && existingStudent) { const selectedOption = document.querySelector('#existing-student option:checked'); if (selectedOption && selectedOption.dataset.source === 'huggingface') { studentSource = 'huggingface'; studentModelPath = existingStudent; // Already the repo name } else if (selectedOption && selectedOption.dataset.source === 'space') { studentSource = 'space'; studentModelPath = existingStudent.startsWith('space:') ? existingStudent.substring(6) : existingStudent; } else { studentSource = 'local'; studentModelPath = existingStudent; } } const config = { session_id: `session_${Date.now()}`, teacher_models: this.selectedModels.map(m => ({ path: m.path, token: m.token || hfToken || null, trust_remote_code: trustRemoteCode })), student_config: { hidden_size: parseInt(document.getElementById('hidden-size').value), num_layers: parseInt(document.getElementById('num-layers').value), output_size: parseInt(document.getElementById('hidden-size').value) }, training_params: { max_steps: parseInt(document.getElementById('max-steps').value), learning_rate: parseFloat(document.getElementById('learning-rate').value), temperature: parseFloat(document.getElementById('temperature').value), alpha: parseFloat(document.getElementById('alpha').value), batch_size: 8 }, distillation_strategy: document.getElementById('strategy').value, hf_token: hfToken || null, trust_remote_code: trustRemoteCode, incremental_training: incrementalTraining, existing_student_model: studentModelPath, student_source: studentSource }; return config; } connectWebSocket() { if (!this.trainingSession) return; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/${this.trainingSession}`; this.websocket = new WebSocket(wsUrl); this.websocket.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'training_update') { this.updateTrainingProgress(data.data); } }; this.websocket.onerror = (error) => { console.error('WebSocket error:', error); this.addConsoleMessage('WebSocket connection error', 'error'); }; this.websocket.onclose = () => { console.log('WebSocket connection closed'); }; } async startProgressPolling() { if (!this.trainingSession) return; this.trainingStartTime = Date.now(); // Track start time const poll = async () => { try { const response = await fetch(`/progress/${this.trainingSession}`); const progress = await response.json(); this.updateTrainingProgress(progress); // If stuck on loading for too long, show helpful message if (progress.status === 'loading_models' && progress.progress < 0.2) { const elapsed = Date.now() - this.trainingStartTime; if (elapsed > 60000) { // 1 minute const messageEl = document.getElementById('training-message'); if (messageEl && !messageEl.innerHTML.includes('Large models')) { messageEl.innerHTML = `${progress.message}
Large models may take several minutes to load. Please be patient...`; } } } if (progress.status === 'completed' || progress.status === 'failed') { return; // Stop polling } setTimeout(poll, 2000); // Poll every 2 seconds } catch (error) { console.error('Error polling progress:', error); setTimeout(poll, 5000); // Retry after 5 seconds } }; poll(); } updateTrainingProgress(progress) { // Update progress bar const progressFill = document.getElementById('overall-progress'); const progressText = document.getElementById('progress-percentage'); const percentage = Math.round(progress.progress * 100); progressFill.style.width = `${percentage}%`; progressText.textContent = `${percentage}%`; // Update status info document.getElementById('training-status').textContent = this.formatStatus(progress.status); document.getElementById('current-step').textContent = `${progress.current_step} / ${progress.total_steps}`; document.getElementById('eta').textContent = progress.eta || 'Calculating...'; // Update metrics if (progress.loss !== null && progress.loss !== undefined) { document.getElementById('current-loss').textContent = progress.loss.toFixed(4); } // Add console message if (progress.message) { this.addConsoleMessage(progress.message, this.getMessageType(progress.status)); } // Handle completion if (progress.status === 'completed') { document.getElementById('download-model').classList.remove('hidden'); document.getElementById('upload-to-hf').classList.remove('hidden'); document.getElementById('start-new-training').classList.remove('hidden'); document.getElementById('cancel-training').classList.add('hidden'); this.addConsoleMessage('Training completed successfully!', 'success'); } else if (progress.status === 'failed') { document.getElementById('start-new-training').classList.remove('hidden'); document.getElementById('cancel-training').classList.add('hidden'); this.addConsoleMessage(`Training failed: ${progress.message}`, 'error'); } } formatStatus(status) { const statusMap = { 'initializing': 'Initializing...', 'loading_models': 'Loading Models...', 'initializing_student': 'Initializing Student...', 'training': 'Training...', 'saving': 'Saving Model...', 'completed': 'Completed', 'failed': 'Failed' }; return statusMap[status] || status; } getMessageType(status) { if (status === 'completed') return 'success'; if (status === 'failed') return 'error'; if (status === 'loading_models' || status === 'initializing') return 'warning'; return 'info'; } addConsoleMessage(message, type = 'info') { const console = document.getElementById('training-console'); if (!console) { // Fallback to browser console if training console not found console.log(`[${type.toUpperCase()}] ${message}`); return; } try { const line = document.createElement('div'); line.className = `console-line ${type}`; line.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; console.appendChild(line); console.scrollTop = console.scrollHeight; } catch (error) { console.error('Error adding console message:', error); console.log(`[${type.toUpperCase()}] ${message}`); } } async cancelTraining() { if (this.websocket) { this.websocket.close(); } this.addConsoleMessage('Training cancelled by user', 'warning'); } async downloadModel() { if (!this.trainingSession) return; try { const response = await fetch(`/download/${this.trainingSession}`); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `distilled_model_${this.trainingSession}.safetensors`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } else { throw new Error('Download failed'); } } catch (error) { this.showError(`Download failed: ${error.message}`); } } // Utility functions isValidHuggingFaceRepo(repo) { return /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo); } isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } extractFilenameFromUrl(url) { try { const pathname = new URL(url).pathname; return pathname.split('/').pop() || 'model'; } catch { return 'model'; } } formatBytes(bytes) { const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) return '0 B'; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`; } showError(message) { try { const errorMessage = document.getElementById('error-message'); const errorModal = document.getElementById('error-modal'); if (errorMessage && errorModal) { errorMessage.textContent = message; errorModal.classList.remove('hidden'); } else { // Fallback: use alert if modal elements not found console.error('Error modal elements not found, using alert'); alert(`Error: ${message}`); } } catch (error) { console.error('Error showing error message:', error); alert(`Error: ${message}`); } } hideErrorModal() { document.getElementById('error-modal').classList.add('hidden'); } showLoading(message) { // Create loading overlay if it doesn't exist let loadingOverlay = document.getElementById('loading-overlay'); if (!loadingOverlay) { loadingOverlay = document.createElement('div'); loadingOverlay.id = 'loading-overlay'; loadingOverlay.className = 'loading-overlay'; loadingOverlay.innerHTML = `
${message}
`; document.body.appendChild(loadingOverlay); } else { loadingOverlay.querySelector('.loading-message').textContent = message; loadingOverlay.classList.remove('hidden'); } } hideLoading() { const loadingOverlay = document.getElementById('loading-overlay'); if (loadingOverlay) { loadingOverlay.classList.add('hidden'); } } async testToken() { const tokenInput = document.getElementById('hf-token'); const statusDiv = document.getElementById('token-status'); const token = tokenInput.value.trim(); if (!token) { this.showTokenStatus('Please enter a token first', 'warning'); return; } this.showLoading('Testing token...'); try { const response = await fetch('/test-token'); const result = await response.json(); this.hideLoading(); if (result.token_valid) { this.showTokenStatus('✅ Token is valid and working!', 'success'); } else if (result.token_available) { this.showTokenStatus(`❌ Token validation failed: ${result.message}`, 'error'); } else { this.showTokenStatus('⚠️ No token found in environment. Using interface token.', 'warning'); } } catch (error) { this.hideLoading(); this.showTokenStatus(`❌ Error testing token: ${error.message}`, 'error'); } } showTokenStatus(message, type) { const statusDiv = document.getElementById('token-status'); if (!statusDiv) { console.warn('Token status div not found, using console message instead'); console.log(`${type.toUpperCase()}: ${message}`); return; } statusDiv.textContent = message; statusDiv.className = `token-status ${type}`; statusDiv.classList.remove('hidden'); // Hide after 5 seconds setTimeout(() => { if (statusDiv) { statusDiv.classList.add('hidden'); } }, 5000); } async testModel() { const repoInput = document.getElementById('hf-repo'); const trustRemoteCode = document.getElementById('trust-remote-code').checked; const repo = repoInput.value.trim(); if (!repo) { this.showTokenStatus('Please enter a model repository name first', 'warning'); return; } if (!this.isValidHuggingFaceRepo(repo)) { this.showTokenStatus('Invalid repository format. Use: organization/model-name', 'error'); return; } this.showLoading(`Testing model: ${repo}...`); try { const response = await fetch('/test-model', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_path: repo, trust_remote_code: trustRemoteCode }) }); const result = await response.json(); this.hideLoading(); if (result.success) { const info = result.model_info; let message = `✅ Model ${repo} is accessible!`; if (info.architecture) { message += ` Architecture: ${info.architecture}`; } if (info.modality) { message += `, Modality: ${info.modality}`; } this.showTokenStatus(message, 'success'); } else { let message = `❌ Model test failed: ${result.error}`; if (result.suggestions && result.suggestions.length > 0) { message += `. Suggestions: ${result.suggestions.join(', ')}`; } this.showTokenStatus(message, 'error'); } } catch (error) { this.hideLoading(); this.showTokenStatus(`❌ Error testing model: ${error.message}`, 'error'); } } downloadModel() { if (!this.trainingSession) { this.showError('No training session found'); return; } // Create download link const downloadUrl = `/download/${this.trainingSession}`; const link = document.createElement('a'); link.href = downloadUrl; link.download = `distilled_model_${this.trainingSession}`; document.body.appendChild(link); link.click(); document.body.removeChild(link); this.addConsoleMessage('Download started...', 'info'); } showHFUploadModal() { const modal = document.getElementById('hf-upload-modal'); modal.classList.remove('hidden'); // Pre-fill token if available const hfToken = document.getElementById('hf-token').value.trim(); if (hfToken) { document.getElementById('hf-upload-token').value = hfToken; // Auto-validate token and suggest username this.validateTokenAndSuggestName(hfToken); } } hideHFUploadModal() { const modal = document.getElementById('hf-upload-modal'); modal.classList.add('hidden'); } async uploadToHuggingFace() { if (!this.trainingSession) { this.showError('No training session found'); return; } const repoName = document.getElementById('hf-repo-name').value.trim(); const description = document.getElementById('hf-description').value.trim(); const token = document.getElementById('hf-upload-token').value.trim(); const isPrivate = document.getElementById('hf-private').checked; if (!repoName || !token) { this.showError('Repository name and token are required'); return; } if (!repoName.includes('/')) { this.showError('Repository name must be in format: username/model-name'); return; } this.showLoading('Uploading model to Hugging Face...'); this.hideHFUploadModal(); try { const formData = new FormData(); formData.append('repo_name', repoName); formData.append('description', description); formData.append('private', isPrivate); formData.append('hf_token', token); const response = await fetch(`/upload-to-hf/${this.trainingSession}`, { method: 'POST', body: formData }); const result = await response.json(); this.hideLoading(); if (result.success) { this.addConsoleMessage(`✅ Model uploaded successfully to ${result.repo_url}`, 'success'); this.addConsoleMessage(`📁 Uploaded files: ${result.uploaded_files.join(', ')}`, 'info'); // Show success message with link const successMsg = document.createElement('div'); successMsg.className = 'alert alert-success'; successMsg.innerHTML = ` 🎉 Upload Successful!
Your model is now available at: ${result.repo_url} `; // Find a safe container to insert the message let container = document.querySelector('.step-3 .step-content'); if (!container) { container = document.querySelector('.step-3'); } if (!container) { container = document.querySelector('#training-progress'); } if (!container) { container = document.body; } if (container && container.firstChild) { container.insertBefore(successMsg, container.firstChild); } else if (container) { container.appendChild(successMsg); } // Remove after 10 seconds setTimeout(() => { if (successMsg && successMsg.parentNode) { successMsg.parentNode.removeChild(successMsg); } }, 10000); } else { const errorMsg = result.detail || result.message || 'Unknown error'; this.showError(`Upload failed: ${errorMsg}`); this.addConsoleMessage(`❌ Upload failed: ${errorMsg}`, 'error'); } } catch (error) { this.hideLoading(); const errorMsg = error.message || 'Network error occurred'; this.showError(`Upload failed: ${errorMsg}`); this.addConsoleMessage(`❌ Upload error: ${errorMsg}`, 'error'); console.error('Upload error details:', error); } } async loadTrainedStudents() { try { const response = await fetch('/trained-students'); const data = await response.json(); const select = document.getElementById('existing-student'); select.innerHTML = ''; if (data.trained_students && data.trained_students.length > 0) { data.trained_students.forEach(model => { const option = document.createElement('option'); option.value = model.path; option.textContent = `${model.name} (${model.architecture}, ${model.training_sessions} sessions)`; option.dataset.modelInfo = JSON.stringify(model); select.appendChild(option); }); } else { const option = document.createElement('option'); option.value = ''; option.textContent = 'No trained models found'; option.disabled = true; select.appendChild(option); } } catch (error) { console.error('Error loading trained students:', error); const select = document.getElementById('existing-student'); select.innerHTML = ''; } } toggleIncrementalTraining() { const enabled = document.getElementById('enable-incremental').checked; const options = document.getElementById('incremental-options'); if (enabled) { options.classList.remove('hidden'); this.loadTrainedStudents(); } else { options.classList.add('hidden'); document.getElementById('student-info').classList.add('hidden'); } } onStudentModelChange() { const select = document.getElementById('existing-student'); const selectedOption = select.options[select.selectedIndex]; const studentInfo = document.getElementById('student-info'); if (selectedOption && selectedOption.dataset.modelInfo) { const modelData = JSON.parse(selectedOption.dataset.modelInfo); // Update info display document.getElementById('student-arch').textContent = modelData.architecture || 'Unknown'; document.getElementById('student-teachers').textContent = modelData.original_teachers.length > 0 ? modelData.original_teachers.join(', ') : 'None'; document.getElementById('student-sessions').textContent = modelData.training_sessions || '0'; document.getElementById('student-last').textContent = modelData.last_training !== 'unknown' ? new Date(modelData.last_training).toLocaleString() : 'Unknown'; studentInfo.classList.remove('hidden'); } else { studentInfo.classList.add('hidden'); } } onStudentSourceChange() { try { const selectedRadio = document.querySelector('input[name="student-source"]:checked'); if (!selectedRadio) { console.warn('No student source radio button selected'); return; } const selectedSource = selectedRadio.value; // Hide all options safely const optionIds = ['local-student-options', 'hf-student-options', 'space-student-options', 'upload-student-options']; optionIds.forEach(id => { const element = document.getElementById(id); if (element) { element.classList.add('hidden'); } }); // Show selected option const targetElement = document.getElementById(`${selectedSource}-student-options`); if (targetElement) { targetElement.classList.remove('hidden'); } else { console.warn(`Element ${selectedSource}-student-options not found`); } // Reset student info const studentInfo = document.getElementById('student-info'); if (studentInfo) { studentInfo.classList.add('hidden'); } } catch (error) { console.error('Error in onStudentSourceChange:', error); } } async testStudentModel() { const repoInput = document.getElementById('hf-student-repo'); const repo = repoInput.value.trim(); if (!repo) { this.showTokenStatus('Please enter a student model repository name', 'warning'); return; } if (!this.isValidHuggingFaceRepo(repo)) { this.showTokenStatus('Invalid repository format. Use: organization/model-name', 'error'); return; } this.showLoading(`Testing student model: ${repo}...`); try { const response = await fetch('/test-model', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_path: repo, trust_remote_code: document.getElementById('trust-remote-code').checked }) }); const result = await response.json(); this.hideLoading(); if (result.success) { this.showTokenStatus(`✅ Student model ${repo} is accessible!`, 'success'); } else { this.showTokenStatus(`❌ Student model test failed: ${result.error}`, 'error'); } } catch (error) { this.hideLoading(); this.showTokenStatus(`❌ Error testing student model: ${error.message}`, 'error'); } } addHFStudentModel() { const repo = document.getElementById('hf-student-repo').value.trim(); if (!repo) { this.showTokenStatus('Please enter a repository name first', 'warning'); return; } if (!this.isValidHuggingFaceRepo(repo)) { this.showTokenStatus('Invalid repository format. Use: organization/model-name', 'error'); return; } // Set the HF repo as the selected student model const existingStudentSelect = document.getElementById('existing-student'); // Remove any existing HF options to avoid duplicates Array.from(existingStudentSelect.options).forEach(option => { if (option.value.startsWith('hf:')) { option.remove(); } }); // Add HF repo as an option const option = document.createElement('option'); option.value = repo; // Store the repo directly, not with hf: prefix option.textContent = `${repo} (Hugging Face)`; option.selected = true; option.dataset.source = 'huggingface'; existingStudentSelect.appendChild(option); // Update student info display this.displayHFStudentInfo(repo); // Show success message this.showTokenStatus(`✅ Added Hugging Face student model: ${repo}`, 'success'); // Clear input document.getElementById('hf-student-repo').value = ''; } displayHFStudentInfo(repo) { // Show student info for HF model const studentInfo = document.getElementById('student-info'); document.getElementById('student-arch').textContent = 'Hugging Face Model'; document.getElementById('student-teachers').textContent = 'Unknown (External Model)'; document.getElementById('student-sessions').textContent = 'N/A'; document.getElementById('student-last').textContent = 'External Model'; studentInfo.classList.remove('hidden'); // Add note about HF model const noteDiv = document.createElement('div'); noteDiv.className = 'alert alert-info'; noteDiv.innerHTML = ` Hugging Face Model: ${repo}
This model will be loaded from Hugging Face Hub. Make sure you have access to it. `; // Remove any existing notes const existingNotes = studentInfo.querySelectorAll('.alert-info'); existingNotes.forEach(note => note.remove()); studentInfo.appendChild(noteDiv); } async testSpaceModel() { const spaceInput = document.getElementById('hf-space-repo'); const space = spaceInput.value.trim(); if (!space) { this.showTokenStatus('Please enter a Space name first', 'warning'); return; } if (!this.isValidHuggingFaceRepo(space)) { this.showTokenStatus('Invalid Space format. Use: username/space-name', 'error'); return; } this.showLoading(`Testing Space: ${space}...`); try { // Test if the Space exists and has models const response = await fetch('/test-space', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ space_name: space, hf_token: document.getElementById('hf-token').value.trim() }) }); const result = await response.json(); this.hideLoading(); if (result.success) { const modelsCount = result.models ? result.models.length : 0; this.showTokenStatus(`✅ Space ${space} is accessible! Found ${modelsCount} trained models.`, 'success'); } else { this.showTokenStatus(`❌ Space test failed: ${result.error}`, 'error'); } } catch (error) { this.hideLoading(); this.showTokenStatus(`❌ Error testing Space: ${error.message}`, 'error'); } } addSpaceStudentModel() { const space = document.getElementById('hf-space-repo').value.trim(); if (!space) { this.showTokenStatus('Please enter a Space name first', 'warning'); return; } if (!this.isValidHuggingFaceRepo(space)) { this.showTokenStatus('Invalid Space format. Use: username/space-name', 'error'); return; } // Set the Space as the selected student model const existingStudentSelect = document.getElementById('existing-student'); // Remove any existing Space options to avoid duplicates Array.from(existingStudentSelect.options).forEach(option => { if (option.value.startsWith('space:')) { option.remove(); } }); // Add Space as an option const option = document.createElement('option'); option.value = `space:${space}`; option.textContent = `${space} (Hugging Face Space)`; option.selected = true; option.dataset.source = 'space'; existingStudentSelect.appendChild(option); // Update student info display this.displaySpaceStudentInfo(space); // Show success message this.showTokenStatus(`✅ Added Hugging Face Space: ${space}`, 'success'); // Clear input document.getElementById('hf-space-repo').value = ''; } displaySpaceStudentInfo(space) { // Show student info for Space const studentInfo = document.getElementById('student-info'); document.getElementById('student-arch').textContent = 'Hugging Face Space'; document.getElementById('student-teachers').textContent = 'Multiple Models Available'; document.getElementById('student-sessions').textContent = 'External Space'; document.getElementById('student-last').textContent = 'External Space'; studentInfo.classList.remove('hidden'); // Add note about Space const noteDiv = document.createElement('div'); noteDiv.className = 'alert alert-info'; noteDiv.innerHTML = ` Hugging Face Space: ${space}
This will load trained models from another Space. The Space should have completed training and saved models. `; // Remove any existing notes const existingNotes = studentInfo.querySelectorAll('.alert-info'); existingNotes.forEach(note => note.remove()); studentInfo.appendChild(noteDiv); } onStudentFilesUpload(event) { const files = event.target.files; if (files.length === 0) return; const fileNames = Array.from(files).map(f => f.name); this.showTokenStatus(`📁 Selected files: ${fileNames.join(', ')}`, 'success'); // TODO: Implement file upload functionality // For now, just show that files were selected } async validateTokenAndSuggestName(token) { if (!token) return; try { const response = await fetch('/validate-repo-name', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ repo_name: 'test/test', // Dummy name to get username hf_token: token }) }); const result = await response.json(); if (result.username) { // Auto-suggest repository name const repoInput = document.getElementById('hf-repo-name'); if (!repoInput.value.trim()) { const modelName = `distilled-model-${Date.now()}`; repoInput.value = `${result.username}/${modelName}`; repoInput.placeholder = `${result.username}/your-model-name`; } } } catch (error) { console.error('Error validating token:', error); } } async validateRepoName() { const repoName = document.getElementById('hf-repo-name').value.trim(); const token = document.getElementById('hf-upload-token').value.trim(); if (!repoName || !token) return; try { const response = await fetch('/validate-repo-name', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ repo_name: repoName, hf_token: token }) }); const result = await response.json(); const statusDiv = document.getElementById('repo-validation-status'); if (!statusDiv) { // Create status div if it doesn't exist const div = document.createElement('div'); div.id = 'repo-validation-status'; div.className = 'validation-status'; document.getElementById('hf-repo-name').parentNode.appendChild(div); } const status = document.getElementById('repo-validation-status'); if (result.valid) { status.innerHTML = `✅ Repository name is valid`; status.className = 'validation-status success'; } else { status.innerHTML = `❌ ${result.error}`; if (result.suggested_name) { status.innerHTML += `
💡 Suggested: ${result.suggested_name}`; // Auto-fill suggested name document.getElementById('hf-repo-name').value = result.suggested_name; } status.className = 'validation-status error'; } status.classList.remove('hidden'); } catch (error) { console.error('Error validating repo name:', error); } } } // Initialize app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.app = new KnowledgeDistillationApp(); }); // Advanced Features Functions async function showGoogleModels() { try { const response = await fetch('/api/models/google'); const data = await response.json(); if (response.ok) { const modelsHtml = data.models.map(model => `

${model.name}

${model.description}

${model.medical_specialized ? 'Medical Specialized' : 'General Purpose'} ${model.size_gb} GB ${model.modality}
`).join(''); showModal('Google Models', modelsHtml); } } catch (error) { console.error('Error loading Google models:', error); showError('Failed to load Google models'); } } async function showSystemInfo() { try { const response = await fetch('/api/system/performance'); const data = await response.json(); if (response.ok) { const systemInfoHtml = `
Memory Information
Process Memory: ${data.memory.process_memory_mb.toFixed(1)} MB
Memory Usage: ${data.memory.process_memory_percent.toFixed(1)}%
Available Memory: ${data.memory.system_memory_available_gb.toFixed(1)} GB
CPU Cores: ${data.cpu_cores}
Optimizations Applied
${data.recommendations.length > 0 ? `
Recommendations
` : ''}
`; showModal('System Information', systemInfoHtml); } } catch (error) { console.error('Error loading system info:', error); showError('Failed to load system information'); } } async function forceMemoryCleanup() { try { const response = await fetch('/api/system/cleanup', { method: 'POST' }); const data = await response.json(); if (response.ok) { showSuccess(data.message); // Refresh system info setTimeout(() => showSystemInfo(), 1000); } else { showError('Failed to cleanup memory'); } } catch (error) { console.error('Error during memory cleanup:', error); showError('Error during memory cleanup'); } } function addGoogleModel(modelName) { // Add the Google model to the HF repo input const hfRepoInput = document.getElementById('hf-repo'); if (hfRepoInput) { hfRepoInput.value = modelName; // Trigger the add model function if (window.app && window.app.addHuggingFaceModel) { window.app.addHuggingFaceModel(); } } closeModal(); } function showModal(title, content) { // Create modal if it doesn't exist let modal = document.getElementById('advanced-modal'); if (!modal) { modal = document.createElement('div'); modal.id = 'advanced-modal'; modal.className = 'modal-overlay'; modal.innerHTML = ` `; document.body.appendChild(modal); } else { document.getElementById('modal-title').textContent = title; document.getElementById('modal-body').innerHTML = content; } modal.style.display = 'flex'; } function closeModal() { const modal = document.getElementById('advanced-modal'); if (modal) { modal.style.display = 'none'; } } function showSuccess(message) { showNotification(message, 'success'); } function showError(message) { showNotification(message, 'error'); } function showNotification(message, type) { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; document.body.appendChild(notification); // Auto remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 5000); } // Models Management Functions class ModelsManager { constructor(app) { this.app = app; this.setupEventListeners(); this.loadConfiguredModels(); } setupEventListeners() { // Refresh models const refreshButton = document.getElementById('refresh-models'); if (refreshButton) { refreshButton.addEventListener('click', () => { this.loadConfiguredModels(); }); } // Search models const searchButton = document.getElementById('search-models-btn'); if (searchButton) { searchButton.addEventListener('click', () => { this.searchModels(); }); } // Search on Enter key const searchQuery = document.getElementById('model-search-query'); if (searchQuery) { searchQuery.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.searchModels(); } }); } // Add custom model const addCustomButton = document.getElementById('add-custom-model'); if (addCustomButton) { addCustomButton.addEventListener('click', () => { this.showAddCustomModelModal(); }); } } async loadConfiguredModels() { try { const response = await fetch('/api/models/teachers'); const data = await response.json(); if (data.success) { this.app.configuredModels = data.teachers; this.app.selectedTeachers = data.selected; this.displayConfiguredModels(data.teachers, data.selected); } } catch (error) { console.error('Error loading configured models:', error); this.app.showError('خطأ في تحميل النماذج المُعدة'); } } displayConfiguredModels(models, selected) { const container = document.getElementById('configured-models-list'); if (!container) return; if (Object.keys(models).length === 0) { container.innerHTML = '

لا توجد نماذج مُعدة

'; return; } container.innerHTML = Object.entries(models).map(([id, model]) => `

${model.description || 'لا يوجد وصف'}

${model.category} ${model.modality} ${model.parameters || 'Unknown'}
`).join(''); } async searchModels() { const queryElement = document.getElementById('model-search-query'); const typeElement = document.getElementById('model-type-filter'); if (!queryElement) return; const query = queryElement.value.trim(); const modelType = typeElement ? typeElement.value : ''; if (!query) { this.app.showError('يرجى إدخال كلمة البحث'); return; } const searchButton = document.getElementById('search-models-btn'); const originalText = searchButton.innerHTML; searchButton.innerHTML = ' جاري البحث...'; searchButton.disabled = true; try { const response = await fetch('/api/models/search', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: query, limit: 20, model_type: modelType || null }) }); const data = await response.json(); if (data.success) { this.displaySearchResults(data.results); } else { this.app.showError('فشل في البحث عن النماذج'); } } catch (error) { console.error('Error searching models:', error); this.app.showError('خطأ في البحث عن النماذج'); } finally { searchButton.innerHTML = originalText; searchButton.disabled = false; } } displaySearchResults(results) { const resultsContainer = document.getElementById('model-search-results-list'); const searchResults = document.getElementById('model-search-results'); if (!resultsContainer || !searchResults) return; if (results.length === 0) { resultsContainer.innerHTML = '

لم يتم العثور على نتائج

'; } else { resultsContainer.innerHTML = results.map(result => `
${result.name}

${result.description || 'لا يوجد وصف'}

${result.author} ${result.downloads || 0} تحميل ${result.likes || 0} إعجاب ${result.pipeline_tag || 'unknown'}
`).join(''); } searchResults.style.display = 'block'; } async addModelFromSearch(modelId, name, description, pipelineTag) { try { // Determine category and modality from pipeline tag let category = 'text'; let modality = 'text'; if (pipelineTag.includes('image') || pipelineTag.includes('vision')) { category = 'vision'; modality = 'vision'; } else if (pipelineTag.includes('audio') || pipelineTag.includes('speech')) { category = 'audio'; modality = 'audio'; } const modelInfo = { name: name, model_id: modelId, category: category, type: 'teacher', description: description, modality: modality, architecture: 'transformer' }; const success = await this.submitModel(modelInfo); if (success) { this.app.showSuccess(`تم إضافة النموذج: ${name}`); this.loadConfiguredModels(); } } catch (error) { console.error('Error adding model from search:', error); this.app.showError('فشل في إضافة النموذج'); } } async submitModel(modelInfo) { try { const response = await fetch('/api/models/add', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(modelInfo) }); const data = await response.json(); return data.success; } catch (error) { console.error('Error submitting model:', error); this.app.showError('فشل في إضافة النموذج'); return false; } } async toggleModelSelection(modelId, selected) { try { if (selected) { // Add to selected teachers if (!this.app.selectedTeachers.includes(modelId)) { this.app.selectedTeachers.push(modelId); } } else { // Remove from selected teachers const index = this.app.selectedTeachers.indexOf(modelId); if (index > -1) { this.app.selectedTeachers.splice(index, 1); } } // Update server const response = await fetch('/api/models/select', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ teacher_models: this.app.selectedTeachers }) }); if (response.ok) { this.app.showSuccess(selected ? 'تم تحديد النموذج' : 'تم إلغاء تحديد النموذج'); this.updateSelectedModelsDisplay(); } } catch (error) { console.error('Error toggling model selection:', error); this.app.showError('فشل في تحديث اختيار النموذج'); } } updateSelectedModelsDisplay() { // Update the selected models count and display const countElement = document.getElementById('model-count'); if (countElement) { countElement.textContent = this.app.selectedTeachers.length; } // Update next step button const nextButton = document.getElementById('next-step-1'); if (nextButton) { nextButton.disabled = this.app.selectedTeachers.length === 0; } // Update models grid display this.displaySelectedModels(); } displaySelectedModels() { const modelsGrid = document.getElementById('models-grid'); if (!modelsGrid) return; if (this.app.selectedTeachers.length === 0) { modelsGrid.innerHTML = '

لم يتم اختيار أي نماذج بعد

'; return; } modelsGrid.innerHTML = this.app.selectedTeachers.map(modelId => { const model = this.app.configuredModels[modelId]; if (!model) return ''; return `
${model.name}

${model.description || 'لا يوجد وصف'}

${model.category} ${model.modality}
`; }).join(''); } async removeModel(modelId) { if (!confirm('هل أنت متأكد من حذف النموذج؟')) { return; } try { const response = await fetch(`/api/models/${encodeURIComponent(modelId)}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { this.app.showSuccess('تم حذف النموذج'); this.loadConfiguredModels(); } else { this.app.showError('فشل في حذف النموذج'); } } catch (error) { console.error('Error removing model:', error); this.app.showError('خطأ في حذف النموذج'); } } showModelInfo(modelId) { const model = this.app.configuredModels[modelId]; if (model) { this.app.showInfo(`معلومات النموذج: ${model.name}\nالوصف: ${model.description}\nالفئة: ${model.category}\nالحجم: ${model.size}`); } } showAddCustomModelModal() { // Show modal for adding custom model this.app.showInfo('سيتم إضافة نافذة إضافة نموذج مخصص قريباً'); } }