shubeydoo's picture
Liquid AI LFM2-VL-450M-WebGPU Demo
accf76b
/**
* 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();
}