File size: 5,996 Bytes
accf76b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/**
 * 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();
}