/** * Main application bootstrap * Initializes the WebGPU model loading and wires up event handlers */ import { setupEventListeners, populateModelSelector, toggleModelSection, updateLoadingProgress, showLoadingProgress, showModelInputWrapper, updateModelStatus, updateWebGPUStatus, setLoadModelButtonEnabled, getSelectedModelId, updateButtonStates, updateCacheInfo, setupClearCacheHandler, setClearCacheButtonText } from './ui.js'; import { generate, loadModel, isModelLoaded, getCurrentModelId, clearImageCache, clearModelCache, getCacheInfo } from './infer.js'; import { getAvailableModels, getModelConfig } from './config.js'; /** * Handle loading a model */ async function handleLoadModel() { const modelId = getSelectedModelId(); if (!modelId) { updateModelStatus('Please select a model', 'error'); return; } // Stop capturing if active (prevents crash) if (window.stopLiveCaption) { window.stopLiveCaption(); } // Clear image cache when loading a new model clearImageCache(); setLoadModelButtonEnabled(false); showModelInputWrapper(false); showLoadingProgress(true); updateButtonStates(false); // Disable Start button while loading updateModelStatus('Loading model, will take a few minutes if not cached...', 'loading'); try { await loadModel(modelId, { progressCallback: (progress) => { if (progress.status === 'loading') { const percent = Math.round(progress.progress || 0); updateLoadingProgress(percent); // Show file download progress (includes MB downloaded / total) const statusText = progress.file ? `Downloading: ${progress.file}` : 'Loading model...'; updateModelStatus(statusText, 'loading'); } else if (progress.status === 'done') { updateLoadingProgress(100); } } }); showLoadingProgress(false); showModelInputWrapper(true); const modelConfig = getModelConfig(modelId); const modelLabel = modelConfig ? `LFM2-VL-450M ${modelConfig.label}` : modelId; updateModelStatus(`Loaded ${modelLabel}`, 'success'); updateButtonStates(true); await refreshCacheInfo(); } catch (error) { console.error('Model loading error:', error); if (error.message && error.message.includes('already loading')) { updateModelStatus('Model loading in progress...', 'loading'); return; } showLoadingProgress(false); showModelInputWrapper(true); updateModelStatus(`Error: ${error.message}`, 'error'); updateButtonStates(false); } finally { setLoadModelButtonEnabled(true); } } /** * Handle reloading the current model */ async function handleReloadModel() { const currentModelId = getCurrentModelId(); if (!currentModelId) { updateModelStatus('No model loaded', 'error'); return; } await handleLoadModel(); } /** * Update cache storage info display */ async function refreshCacheInfo() { const info = await getCacheInfo(); updateCacheInfo(info ? info.used : 0); } /** * Handle clearing the model cache */ async function handleClearCache() { const info = await getCacheInfo(); const usedMB = info ? (info.used / 1024 / 1024).toFixed(0) : 0; const confirmed = confirm( `Delete downloaded model files?\n\n` + `This will free up ~${usedMB} MB of storage.\n` + `Models will be re-downloaded next time you load them.` ); if (!confirmed) return; setClearCacheButtonText('Deleting...'); await clearModelCache(); setClearCacheButtonText('Clear'); await refreshCacheInfo(); updateModelStatus('Downloaded models deleted', 'success'); } /** * Check WebGPU availability */ async function checkWebGPU() { if (!navigator.gpu) { updateWebGPUStatus('WebGPU not available. Enable at chrome://flags/#enable-unsafe-webgpu', false); return false; } try { const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { updateWebGPUStatus('WebGPU adapter not found', false); return false; } const info = adapter.info || {}; const desc = info.description || info.vendor || info.architecture || 'Available'; updateWebGPUStatus(`WebGPU: ${desc}`, true); return true; } catch (error) { updateWebGPUStatus(`WebGPU error: ${error.message}`, false); return false; } } /** * Initialize the application */ async function init() { // Populate model selector populateModelSelector(getAvailableModels()); // Check WebGPU availability await checkWebGPU(); // Set up event listeners setupEventListeners(null, handleLoadModel, handleReloadModel); // Set up cache handler setupClearCacheHandler(handleClearCache); // Show model section (WebGPU only) toggleModelSection(true); // Initialize button states (disabled until model loads) updateButtonStates(false); // Initialize cache info display await refreshCacheInfo(); } // Export functions for use by inline script window.webgpuInit = { init, handleLoadModel, handleReloadModel, checkWebGPU, populateModelSelector: () => populateModelSelector(getAvailableModels()), toggleModelSection, updateModelStatus, getCurrentModelId, isModelLoaded, getAvailableModels, generate, updateButtonStates }; // Signal that WebGPU module is ready window.dispatchEvent(new Event('webgpu-ready')); // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }