(onStatusChange)
| 20 | |
| 21 | // ─── Binary Status Bar ──────────────────────────────────────────────────────── |
| 22 | function BinaryStatusBar(onStatusChange) { |
| 23 | const bar = document.createElement('div'); |
| 24 | bar.className = 'flex items-center justify-between gap-3 p-3 rounded-xl bg-white/3 border border-white/5'; |
| 25 | |
| 26 | const label = document.createElement('div'); |
| 27 | label.className = 'flex flex-col gap-0.5'; |
| 28 | label.innerHTML = ` |
| 29 | <span class="text-xs font-bold text-white">sd.cpp inference engine</span> |
| 30 | <span id="binary-status-text" class="text-[11px] text-muted">${t('localModels.checking')}</span> |
| 31 | `; |
| 32 | |
| 33 | const btn = document.createElement('button'); |
| 34 | btn.id = 'binary-action-btn'; |
| 35 | btn.className = 'px-3 py-1.5 rounded-lg text-xs font-bold transition-all hidden'; |
| 36 | btn.textContent = t('localModels.installEngine'); |
| 37 | |
| 38 | bar.appendChild(label); |
| 39 | bar.appendChild(btn); |
| 40 | |
| 41 | const progressBar = document.createElement('div'); |
| 42 | progressBar.className = 'h-1 rounded-full bg-white/5 mt-2 hidden overflow-hidden'; |
| 43 | progressBar.id = 'binary-progress-bar'; |
| 44 | progressBar.innerHTML = `<div id="binary-progress-fill" class="h-full bg-primary transition-all" style="width:0%"></div>`; |
| 45 | bar.appendChild(progressBar); |
| 46 | |
| 47 | const refresh = async () => { |
| 48 | const status = await localAI.getBinaryStatus(); |
| 49 | const text = bar.querySelector('#binary-status-text'); |
| 50 | if (status.exists) { |
| 51 | text.textContent = t('localModels.installed'); |
| 52 | text.className = 'text-[11px] text-green-400'; |
| 53 | btn.classList.add('hidden'); |
| 54 | } else { |
| 55 | text.textContent = t('localModels.notInstalled'); |
| 56 | text.className = 'text-[11px] text-yellow-400'; |
| 57 | btn.textContent = t('localModels.installEngine'); |
| 58 | btn.className = 'px-3 py-1.5 rounded-lg text-xs font-bold bg-primary text-black transition-all'; |
| 59 | btn.classList.remove('hidden'); |
| 60 | } |
| 61 | if (onStatusChange) onStatusChange(status.exists); |
| 62 | }; |
| 63 | |
| 64 | btn.onclick = async () => { |
| 65 | btn.disabled = true; |
| 66 | btn.textContent = t('localModels.downloading'); |
| 67 | progressBar.classList.remove('hidden'); |
| 68 | |
| 69 | const unsub = localAI.onDownloadProgress(({ id, phase, progress }) => { |
| 70 | if (id !== '__binary__') return; |
| 71 | const fill = document.getElementById('binary-progress-fill'); |
| 72 | const text = bar.querySelector('#binary-status-text'); |
| 73 | if (fill) fill.style.width = `${Math.round(progress * 100)}%`; |
| 74 | if (text) text.textContent = phase === 'extracting' ? t('localModels.extracting') : `${t('localModels.downloading')} ${Math.round(progress * 100)}%`; |
| 75 | }); |
| 76 | |
| 77 | try { |
| 78 | await localAI.downloadBinary(); |
| 79 | unsub(); |
no test coverage detected