From 0d56e06724674ec209593454ce84abbb76b6350e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=93=D0=BB=D0=B0=D0=B7=D1=83?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Mon, 16 Feb 2026 19:55:39 +0300 Subject: [PATCH] feat: add whisper progress tracking and UI updates for download status --- src/components/app/CheatingDaddyApp.js | 8 +++ src/components/views/MainView.js | 96 ++++++++++++++++++++++++++ src/utils/localai.js | 9 +++ src/utils/whisperWorker.js | 34 +++++++++ 4 files changed, 147 insertions(+) diff --git a/src/components/app/CheatingDaddyApp.js b/src/components/app/CheatingDaddyApp.js index 24df6db..6fecf2d 100644 --- a/src/components/app/CheatingDaddyApp.js +++ b/src/components/app/CheatingDaddyApp.js @@ -382,6 +382,7 @@ export class CheatingDaddyApp extends LitElement { _storageLoaded: { state: true }, _updateAvailable: { state: true }, _whisperDownloading: { state: true }, + _whisperProgress: { state: true }, }; constructor() { @@ -407,6 +408,7 @@ export class CheatingDaddyApp extends LitElement { this._timerInterval = null; this._updateAvailable = false; this._whisperDownloading = false; + this._whisperProgress = null; this._localVersion = ""; this._loadFromStorage(); @@ -485,6 +487,10 @@ export class CheatingDaddyApp extends LitElement { ); ipcRenderer.on("whisper-downloading", (_, downloading) => { this._whisperDownloading = downloading; + if (!downloading) this._whisperProgress = null; + }); + ipcRenderer.on("whisper-progress", (_, progress) => { + this._whisperProgress = progress; }); } } @@ -500,6 +506,7 @@ export class CheatingDaddyApp extends LitElement { ipcRenderer.removeAllListeners("click-through-toggled"); ipcRenderer.removeAllListeners("reconnect-failed"); ipcRenderer.removeAllListeners("whisper-downloading"); + ipcRenderer.removeAllListeners("whisper-progress"); } } @@ -778,6 +785,7 @@ export class CheatingDaddyApp extends LitElement { .onStart=${() => this.handleStart()} .onExternalLink=${(url) => this.handleExternalLinkClick(url)} .whisperDownloading=${this._whisperDownloading} + .whisperProgress=${this._whisperProgress} > `; diff --git a/src/components/views/MainView.js b/src/components/views/MainView.js index 16bd44a..ac82a35 100644 --- a/src/components/views/MainView.js +++ b/src/components/views/MainView.js @@ -151,6 +151,63 @@ export class MainView extends LitElement { } } + /* ── Whisper download progress ── */ + + .whisper-progress-container { + margin-top: 8px; + padding: 8px 10px; + background: var(--bg-elevated, rgba(255, 255, 255, 0.05)); + border-radius: var(--radius-sm, 6px); + border: 1px solid var(--border, rgba(255, 255, 255, 0.1)); + } + + .whisper-progress-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + font-size: 11px; + color: var(--text-secondary, #999); + } + + .whisper-progress-file { + font-family: var(--font-mono, monospace); + font-size: 10px; + color: var(--text-secondary, #999); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 200px; + } + + .whisper-progress-pct { + font-variant-numeric: tabular-nums; + font-weight: 600; + color: var(--accent, #6cb4ee); + } + + .whisper-progress-track { + height: 4px; + background: var(--border, rgba(255, 255, 255, 0.1)); + border-radius: 2px; + overflow: hidden; + } + + .whisper-progress-bar { + height: 100%; + background: var(--accent, #6cb4ee); + border-radius: 2px; + transition: width 0.3s ease; + min-width: 0; + } + + .whisper-progress-size { + margin-top: 4px; + font-size: 10px; + color: var(--text-tertiary, #666); + text-align: right; + } + /* ── Start button ── */ .start-button { @@ -422,6 +479,7 @@ export class MainView extends LitElement { onProfileChange: { type: Function }, isInitializing: { type: Boolean }, whisperDownloading: { type: Boolean }, + whisperProgress: { type: Object }, // Internal state _mode: { state: true }, _token: { state: true }, @@ -453,6 +511,7 @@ export class MainView extends LitElement { this.onProfileChange = () => {}; this.isInitializing = false; this.whisperDownloading = false; + this.whisperProgress = null; this._mode = "byok"; this._token = ""; @@ -893,6 +952,40 @@ export class MainView extends LitElement { this.requestUpdate(); } + _formatBytes(bytes) { + if (!bytes || bytes === 0) return "0 B"; + const units = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, i)).toFixed(i > 1 ? 1 : 0) + " " + units[i]; + } + + _renderWhisperProgress() { + const p = this.whisperProgress; + if (!p) return ""; + + const pct = Math.round(p.progress || 0); + const fileName = p.file ? p.file.split("/").pop() : ""; + + return html` +
+
+ ${fileName || "Preparing..."} + ${pct}% +
+
+
+
+ ${p.total + ? html`
+ ${this._formatBytes(p.loaded)} / ${this._formatBytes(p.total)} +
` + : ""} +
+ `; + } + _handleProfileChange(e) { this.onProfileChange(e.target.value); } @@ -1261,6 +1354,9 @@ export class MainView extends LitElement { : "Downloaded automatically on first use"} `} + ${this.whisperDownloading && this.whisperProgress + ? this._renderWhisperProgress() + : ""} ${this._renderStartButton()} diff --git a/src/utils/localai.js b/src/utils/localai.js index 5167ceb..7f99244 100644 --- a/src/utils/localai.js +++ b/src/utils/localai.js @@ -263,6 +263,15 @@ function spawnWhisperWorker() { case "status": sendToRenderer("update-status", msg.message); break; + case "progress": + sendToRenderer("whisper-progress", { + file: msg.file, + progress: msg.progress, + loaded: msg.loaded, + total: msg.total, + status: msg.status, + }); + break; } }); diff --git a/src/utils/whisperWorker.js b/src/utils/whisperWorker.js index e5444eb..98aafbe 100644 --- a/src/utils/whisperWorker.js +++ b/src/utils/whisperWorker.js @@ -155,6 +155,40 @@ async function loadModel(modelName, cacheDir, device = "cpu") { { dtype: "q8", device: dev, + progress_callback: (progress) => { + // progress: { status, name?, file?, progress?, loaded?, total? } + if ( + progress.status === "download" || + progress.status === "progress" + ) { + send({ + type: "progress", + file: progress.file || progress.name || "", + progress: progress.progress ?? 0, + loaded: progress.loaded ?? 0, + total: progress.total ?? 0, + status: progress.status, + }); + } else if (progress.status === "done") { + send({ + type: "progress", + file: progress.file || progress.name || "", + progress: 100, + loaded: progress.total ?? 0, + total: progress.total ?? 0, + status: "done", + }); + } else if (progress.status === "initiate") { + send({ + type: "progress", + file: progress.file || progress.name || "", + progress: 0, + loaded: 0, + total: 0, + status: "initiate", + }); + } + }, }, );