|
96 | 96 | function computeScreenOrder() { |
97 | 97 | const out = ['welcome', 'apikey', 'speech']; |
98 | 98 | if (state.speechProvider === 'whisper') out.push('whisper'); |
| 99 | + if (state.speechProvider === 'whisper') out.push('model-download'); |
99 | 100 | out.push('finish'); |
100 | 101 | return out; |
101 | 102 | } |
|
121 | 122 | case 'whisper': |
122 | 123 | // Allow advancing whether whisper is detected OR user skipped |
123 | 124 | return state.whisperDetected || state.skippingWhisper; |
| 125 | + case 'model-download': |
| 126 | + return !!state.modelDownloadChoice; |
124 | 127 | case 'finish': |
125 | 128 | return true; |
126 | 129 | default: |
|
371 | 374 | runWhisperDetect(); |
372 | 375 | } |
373 | 376 |
|
| 377 | + // ── Wire up: Model Download screen ─────────────────────────────── |
| 378 | + const modelDownloadLog = $('#modelDownloadLog'); |
| 379 | + const modelDownloadChoices = $('#modelDownloadChoices'); |
| 380 | + |
| 381 | + function appendModelLog(line) { |
| 382 | + modelDownloadLog.textContent += (modelDownloadLog.textContent ? '\n' : '') + line; |
| 383 | + modelDownloadLog.scrollTop = modelDownloadLog.scrollHeight; |
| 384 | + } |
| 385 | + |
| 386 | + let modelDownloadInitialized = false; |
| 387 | + function enterModelDownloadScreen() { |
| 388 | + if (modelDownloadInitialized) return; |
| 389 | + modelDownloadInitialized = true; |
| 390 | + |
| 391 | + // Set up choice card click handlers |
| 392 | + $$('#modelDownloadChoices .choice-card').forEach((card) => { |
| 393 | + card.addEventListener('click', () => { |
| 394 | + const value = card.dataset.value; |
| 395 | + state.modelDownloadChoice = value; |
| 396 | + $$('#modelDownloadChoices .choice-card').forEach((c) => c.classList.remove('selected')); |
| 397 | + card.classList.add('selected'); |
| 398 | + |
| 399 | + if (value === 'now') { |
| 400 | + // Start downloading the model immediately |
| 401 | + startModelDownload(); |
| 402 | + } |
| 403 | + }); |
| 404 | + }); |
| 405 | + } |
| 406 | + |
| 407 | + async function startModelDownload() { |
| 408 | + const nextBtnEl = document.getElementById('nextBtn'); |
| 409 | + if (nextBtnEl) { |
| 410 | + nextBtnEl.disabled = true; |
| 411 | + nextBtnEl.innerHTML = '<span class="spinner"></span> Downloading…'; |
| 412 | + } |
| 413 | + |
| 414 | + appendModelLog('Starting model download…'); |
| 415 | + |
| 416 | + let progressHandler = null; |
| 417 | + if (window.electronAPI && window.electronAPI.onInstallProgress) { |
| 418 | + progressHandler = (line) => appendModelLog(line); |
| 419 | + window.electronAPI.onInstallProgress(progressHandler); |
| 420 | + } |
| 421 | + |
| 422 | + try { |
| 423 | + const r = await window.electronAPI.downloadWhisperModel('turbo'); |
| 424 | + if (r.ok) { |
| 425 | + appendModelLog(`\n✓ Model downloaded successfully: ${r.path}`); |
| 426 | + if (nextBtnEl) { |
| 427 | + nextBtnEl.innerHTML = '<i class="fas fa-check-circle"></i> Downloaded'; |
| 428 | + nextBtnEl.classList.remove('primary'); |
| 429 | + nextBtnEl.classList.add('success'); |
| 430 | + } |
| 431 | + } else { |
| 432 | + appendModelLog(`\n✗ Download failed: ${r.message}`); |
| 433 | + if (nextBtnEl) { |
| 434 | + nextBtnEl.disabled = false; |
| 435 | + nextBtnEl.innerHTML = 'Continue <i class="fas fa-arrow-right"></i>'; |
| 436 | + } |
| 437 | + } |
| 438 | + } catch (e) { |
| 439 | + appendModelLog(`\n! Error: ${e.message || e}`); |
| 440 | + if (nextBtnEl) { |
| 441 | + nextBtnEl.disabled = false; |
| 442 | + nextBtnEl.innerHTML = 'Continue <i class="fas fa-arrow-right"></i>'; |
| 443 | + } |
| 444 | + } finally { |
| 445 | + if (progressHandler && window.electronAPI.removeAllListeners) { |
| 446 | + try { window.electronAPI.removeAllListeners('install-progress'); } catch (_) { /* ignore */ } |
| 447 | + } |
| 448 | + } |
| 449 | + } |
| 450 | + |
374 | 451 | // ── Wire up: Finish screen ──────────────────────────────────────── |
375 | 452 | function populateSummary() { |
376 | 453 | const rows = []; |
|
482 | 559 | } |
483 | 560 | } |
484 | 561 |
|
| 562 | + // Model download screen: persist choice |
| 563 | + if (name === 'model-download') { |
| 564 | + if (window.electronAPI && state.modelDownloadChoice) { |
| 565 | + try { |
| 566 | + await window.electronAPI.saveSettings({ whisperModelDownload: state.modelDownloadChoice }); |
| 567 | + } catch (_) { /* ignore */ } |
| 568 | + } |
| 569 | + } |
| 570 | + |
485 | 571 | // Finish: close onboarding |
486 | 572 | if (name === 'finish') { |
487 | 573 | try { |
|
504 | 590 | state.step = orderScreenToStep(nextName); |
505 | 591 | showScreen(nextName); |
506 | 592 | if (nextName === 'whisper') enterWhisperScreen(); |
| 593 | + if (nextName === 'model-download') enterModelDownloadScreen(); |
507 | 594 | if (nextName === 'finish') populateSummary(); |
508 | 595 |
|
509 | 596 | // Re-render stepper with new total |
|
0 commit comments