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` +