From ce96a0e5272d6bcdf732dd0fc1ff8a9d9c834a0c Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Wed, 03 Jun 2026 15:00:58 +0800
Subject: [PATCH] Fix Round 4 model download QA issues
---
02-P-NBL/round-4/code-review.md | 6 +
privatevoice.src/internal/model/downloader.go | 8 +
02-P-NBL/round-4/dev-self-check.md | 32 ++++++
02-P-NBL/round-4/logs/local-build-install-20260603-round4.log | 154 ++++++++++++++++++++++++++++++
privatevoice.src/frontend/src/components/settings/GeneralPage.svelte | 3
02-P-NBL/round-4/logs/npm-run-build-20260603-round4-after-qa-fix.log | 38 +++++++
02-P-NBL/round-4/logs/go-test-all-20260603-round4-after-qa-fix.log | 23 ++++
privatevoice.src/internal/model/model_test.go | 14 ++
02-P-NBL/round-4/logs/git-diff-check-20260603-round4-after-qa-fix.log | 0
9 files changed, 277 insertions(+), 1 deletions(-)
diff --git a/02-P-NBL/round-4/code-review.md b/02-P-NBL/round-4/code-review.md
index 9153635..37a6923 100644
--- a/02-P-NBL/round-4/code-review.md
+++ b/02-P-NBL/round-4/code-review.md
@@ -36,3 +36,9 @@
- `privatevoice.src/frontend/src/lib/i18n/en.json`
- `privatevoice.src/frontend/src/lib/i18n/zh.json`
+## 5. QA 预检发现与处理
+
+- 发现:Parakeet 下载过程中如果 App 中途退出,`.downloads/parakeet-en` 会保留大体积 partial。
+- 处理:`DownloadProfile` 现在会在同模型新下载开始前清理旧 partial。
+- 发现:Parakeet 下载时设置页 Engine 显示 Loading,但实际 Moonshine 仍可用。
+- 处理:前端只在已安装模型切换或下载完成后进入 Loading,下载过程中保留当前引擎状态。
diff --git a/02-P-NBL/round-4/dev-self-check.md b/02-P-NBL/round-4/dev-self-check.md
index e801e3f..efc8e9c 100644
--- a/02-P-NBL/round-4/dev-self-check.md
+++ b/02-P-NBL/round-4/dev-self-check.md
@@ -80,3 +80,35 @@
- 切回中文后 SenseVoice 正常。
- 下载失败和中断下载不破坏 Moonshine/SenseVoice。
+## 5. QA 预检补丁
+
+QA 预检中已补充两处修复:
+
+- 下载过程中不再把 Engine UI 误显示为 Loading;旧 Moonshine/SenseVoice 引擎保持当前状态显示。
+- App 退出或重启导致的旧 `.downloads/<model>` partial,会在下一次同模型下载前清理,避免长期占用磁盘。
+
+补丁后再次通过:
+
+```bash
+cd privatevoice.src
+go test ./... -count=1
+```
+
+日志:`logs/go-test-all-20260603-round4-after-qa-fix.log`
+
+补丁后再次通过:
+
+```bash
+cd privatevoice.src/frontend
+npm run build
+```
+
+日志:`logs/npm-run-build-20260603-round4-after-qa-fix.log`
+
+补丁后再次通过:
+
+```bash
+git diff --check
+```
+
+日志:`logs/git-diff-check-20260603-round4-after-qa-fix.log`
diff --git a/02-P-NBL/round-4/logs/git-diff-check-20260603-round4-after-qa-fix.log b/02-P-NBL/round-4/logs/git-diff-check-20260603-round4-after-qa-fix.log
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/02-P-NBL/round-4/logs/git-diff-check-20260603-round4-after-qa-fix.log
diff --git a/02-P-NBL/round-4/logs/go-test-all-20260603-round4-after-qa-fix.log b/02-P-NBL/round-4/logs/go-test-all-20260603-round4-after-qa-fix.log
new file mode 100644
index 0000000..15cfa00
--- /dev/null
+++ b/02-P-NBL/round-4/logs/go-test-all-20260603-round4-after-qa-fix.log
@@ -0,0 +1,23 @@
+? voicesnap [no test files]
+? voicesnap/internal/audio [no test files]
+ok voicesnap/internal/config 0.654s
+ok voicesnap/internal/correctioncsv 1.184s
+? voicesnap/internal/dock [no test files]
+ok voicesnap/internal/engine 1.378s
+ok voicesnap/internal/history 1.711s
+? voicesnap/internal/hotkey [no test files]
+? voicesnap/internal/input [no test files]
+ok voicesnap/internal/language 3.217s
+? voicesnap/internal/logger [no test files]
+ok voicesnap/internal/model 2.491s
+ok voicesnap/internal/modelselection 2.033s
+? voicesnap/internal/overlay [no test files]
+? voicesnap/internal/paths [no test files]
+? voicesnap/internal/permissions [no test files]
+? voicesnap/internal/singleinstance [no test files]
+? voicesnap/internal/sound [no test files]
+? voicesnap/internal/startup [no test files]
+? voicesnap/internal/textproc [no test files]
+? voicesnap/internal/updater [no test files]
+ok voicesnap/internal/userdict 2.798s
+? voicesnap/services [no test files]
diff --git a/02-P-NBL/round-4/logs/local-build-install-20260603-round4.log b/02-P-NBL/round-4/logs/local-build-install-20260603-round4.log
new file mode 100644
index 0000000..f422c68
--- /dev/null
+++ b/02-P-NBL/round-4/logs/local-build-install-20260603-round4.log
@@ -0,0 +1,154 @@
+
+==> Preflight checks
+Using existing local signing identity: PrivateVoice Local Code Signing
+
+==> Cleaning old local build
+
+==> Building frontend
+
+> privatevoice-input-frontend@2.1.21 build
+> vite build
+
+vite v6.4.1 building for production...
+transforming...
+2:46:23 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:28:0 Elements with the 'dialog' interactive role must have a tabindex value
+https://svelte.dev/e/a11y_interactive_supports_focus
+26:
+27: <!-- svelte-ignore a11y_click_events_have_key_events -->
+28: <div class="overlay" onclick={onclose} role="dialog">
+ ^
+29: <!-- svelte-ignore a11y_click_events_have_key_events -->
+30: <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
+2:46:23 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:30:2 Non-interactive element `<div>` should not be assigned mouse or keyboard event listeners
+https://svelte.dev/e/a11y_no_noninteractive_element_interactions
+28: <div class="overlay" onclick={onclose} role="dialog">
+29: <!-- svelte-ignore a11y_click_events_have_key_events -->
+30: <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
+ ^
+31: <h2 class="title">{t('update.title')}</h2>
+32: <p class="version">{t('update.version', { version: info.version })}</p>
+2:46:23 PM [vite-plugin-svelte] src/components/shared/ToggleSwitch.svelte:17:0 Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute
+https://svelte.dev/e/a11y_consider_explicit_label
+15: </script>
+16:
+17: <button
+ ^
+18: class="toggle"
+19: class:active={checked}
+✓ 163 modules transformed.
+rendering chunks...
+computing gzip size...
+dist/index.html 0.41 kB │ gzip: 0.27 kB
+dist/assets/app-icon-Bs-pS5hx.png 400.98 kB
+dist/assets/index-Bm3gykhy.css 35.00 kB │ gzip: 5.92 kB
+dist/assets/index-DUZkgsrv.js 142.07 kB │ gzip: 47.42 kB
+✓ built in 853ms
+
+==> Building Go binary (arm64)
+# voicesnap
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/go.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000000.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000001.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000002.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000003.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000004.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000005.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000006.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000007.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000028.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000029.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000030.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000031.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000032.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000033.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000034.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000035.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000036.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000037.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000038.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000039.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000040.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000041.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000042.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000043.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000044.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000045.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000046.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000047.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000048.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000049.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000050.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000051.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000052.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000053.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000054.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: object file (/private/var/folders/tz/gdr6h5t96cj4r08kchkjx2b00000gn/T/go-link-3018710619/000055.o) was built for newer 'macOS' version (26.0) than being linked (11.0)
+ld: warning: building for macOS-11.0, but linking with dylib '@rpath/libonnxruntime.1.23.2.dylib' which was built for newer version 13.4
+
+==> Assembling PrivateVoice Input.app
+
+==> Fixing rpaths
+/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: warning: changes being made to the file will invalidate the code signature in: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
+
+==> Code signing with stable local identity
+/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib: replacing existing signature
+/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib: replacing existing signature
+/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/MacOS/PrivateVoice Input: replacing existing signature
+/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app: replacing existing signature
+
+==> Verifying app signature
+--prepared:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
+--validated:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
+--prepared:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
+--validated:/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
+/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app: valid on disk
+/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app: satisfies its Designated Requirement
+Executable=/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app/Contents/MacOS/PrivateVoice Input
+Identifier=com.privatevoice.input
+Format=app bundle with Mach-O thin (arm64)
+CodeDirectory v=20500 size=28343 flags=0x10000(runtime) hashes=875+7 location=embedded
+Signature size=1830
+Authority=PrivateVoice Local Code Signing
+Signed Time=Jun 3, 2026 at 14:46:31
+Info.plist entries=12
+TeamIdentifier=not set
+Runtime Version=26.2.0
+Sealed Resources version=2 rules=13 files=3
+Internal requirements count=1 size=100
+
+==> Creating DMG
+created: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-build20260603.1327-arm64-local.dmg
+
+==> Signing DMG
+正在检查“Protective Master Boot Record(MBR:0)”的校验和…
+Protective Master Boot Record(MBR:0):已验证CRC32 $A8E7551D
+正在检查“GPT Header(Primary GPT Header:1)”的校验和…
+ GPT Header(Primary GPT Header:1):已验证CRC32 $27DFC2C8
+正在检查“GPT Partition Data(Primary GPT Table:2)”的校验和…
+GPT Partition Data(Primary GPT Table:已验证CRC32 $86F223D2
+正在检查“(Apple_Free:3)”的校验和…
+ (Apple_Free:3):已验证CRC32 $00000000
+正在检查“disk image(Apple_APFS:4)”的校验和…
+ disk image(Apple_APFS:4):已验证CRC32 $4FDADCFF
+正在检查“(Apple_Free:5)”的校验和…
+ (Apple_Free:5):已验证CRC32 $00000000
+正在检查“GPT Partition Data(Backup GPT Table:6)”的校验和…
+GPT Partition Data(Backup GPT Table::已验证CRC32 $86F223D2
+正在检查“GPT Header(Backup GPT Header:7)”的校验和…
+ GPT Header(Backup GPT Header:7):已验证CRC32 $09CBE188
+已验证CRC32 $757AF404
+hdiutil: verify: checksum of "/Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-build20260603.1327-arm64-local.dmg" is VALID
+
+==> Installing to /Applications
+--prepared:/Applications/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
+--validated:/Applications/PrivateVoice Input.app/Contents/Frameworks/libsherpa-onnx-c-api.dylib
+--prepared:/Applications/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
+--validated:/Applications/PrivateVoice Input.app/Contents/Frameworks/libonnxruntime.1.23.2.dylib
+/Applications/PrivateVoice Input.app: valid on disk
+/Applications/PrivateVoice Input.app: satisfies its Designated Requirement
+
+==> Local build complete
+ App: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input.app
+ DMG: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-build20260603.1327-arm64-local.dmg
+ Compatibility DMG: /Users/ar/Projects/VoiceSnap-2.1.1-Round4/privatevoice.src/build/local/arm64/PrivateVoice Input-2.1.21-arm64-local.dmg
+ Signing identity: PrivateVoice Local Code Signing
diff --git a/02-P-NBL/round-4/logs/npm-run-build-20260603-round4-after-qa-fix.log b/02-P-NBL/round-4/logs/npm-run-build-20260603-round4-after-qa-fix.log
new file mode 100644
index 0000000..768d355
--- /dev/null
+++ b/02-P-NBL/round-4/logs/npm-run-build-20260603-round4-after-qa-fix.log
@@ -0,0 +1,38 @@
+
+> privatevoice-input-frontend@2.1.21 build
+> vite build
+
+vite v6.4.1 building for production...
+transforming...
+2:57:48 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:28:0 Elements with the 'dialog' interactive role must have a tabindex value
+https://svelte.dev/e/a11y_interactive_supports_focus
+26:
+27: <!-- svelte-ignore a11y_click_events_have_key_events -->
+28: <div class="overlay" onclick={onclose} role="dialog">
+ ^
+29: <!-- svelte-ignore a11y_click_events_have_key_events -->
+30: <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
+2:57:48 PM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:30:2 Non-interactive element `<div>` should not be assigned mouse or keyboard event listeners
+https://svelte.dev/e/a11y_no_noninteractive_element_interactions
+28: <div class="overlay" onclick={onclose} role="dialog">
+29: <!-- svelte-ignore a11y_click_events_have_key_events -->
+30: <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
+ ^
+31: <h2 class="title">{t('update.title')}</h2>
+32: <p class="version">{t('update.version', { version: info.version })}</p>
+2:57:49 PM [vite-plugin-svelte] src/components/shared/ToggleSwitch.svelte:17:0 Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute
+https://svelte.dev/e/a11y_consider_explicit_label
+15: </script>
+16:
+17: <button
+ ^
+18: class="toggle"
+19: class:active={checked}
+✓ 163 modules transformed.
+rendering chunks...
+computing gzip size...
+dist/index.html 0.41 kB │ gzip: 0.28 kB
+dist/assets/app-icon-Bs-pS5hx.png 400.98 kB
+dist/assets/index-Bm3gykhy.css 35.00 kB │ gzip: 5.92 kB
+dist/assets/index-QkfSvZCU.js 142.09 kB │ gzip: 47.42 kB
+✓ built in 2.17s
diff --git a/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte b/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
index 3d78976..132378a 100755
--- a/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
+++ b/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
@@ -185,12 +185,13 @@
modelBusyID = option.modelID
modelProgress = 0
modelError = ''
- engineStatus.set('loading')
try {
if (option.installed) {
+ engineStatus.set('loading')
await Call.ByName('voicesnap/services.EngineService.SelectModel', option.modelID)
} else {
await Call.ByName('voicesnap/services.EngineService.DownloadModelByID', option.modelID)
+ engineStatus.set('loading')
}
await loadModelOptions()
} catch (err: any) {
diff --git a/privatevoice.src/internal/model/downloader.go b/privatevoice.src/internal/model/downloader.go
index 7aa9c25..e56e2cb 100755
--- a/privatevoice.src/internal/model/downloader.go
+++ b/privatevoice.src/internal/model/downloader.go
@@ -27,6 +27,7 @@
if err := os.MkdirAll(modelsDir, 0755); err != nil {
return fmt.Errorf("failed to create models dir: %w", err)
}
+ cleanupStaleDownloadDirs(profile, modelsDir)
var lastErr error
for i, url := range urls {
@@ -52,6 +53,13 @@
return fmt.Errorf("download failed: %w", lastErr)
}
+func cleanupStaleDownloadDirs(profile ModelProfile, modelsDir string) {
+ downloadRoot := filepath.Join(modelsDir, ".downloads", profile.ID)
+ if err := os.RemoveAll(downloadRoot); err != nil {
+ logger.Info("Failed to remove stale download dir %s: %v", downloadRoot, err)
+ }
+}
+
func downloadAndInstallFromURL(profile ModelProfile, url, modelsDir string, attempt int, progress ProgressCallback) error {
runID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), attempt)
downloadDir := filepath.Join(modelsDir, ".downloads", profile.ID, runID)
diff --git a/privatevoice.src/internal/model/model_test.go b/privatevoice.src/internal/model/model_test.go
index b85c5ad..f0e822c 100644
--- a/privatevoice.src/internal/model/model_test.go
+++ b/privatevoice.src/internal/model/model_test.go
@@ -377,6 +377,20 @@
}
}
+func TestDownloadProfileRemovesStalePartialDownload(t *testing.T) {
+ root := t.TempDir()
+ stalePath := filepath.Join(root, ".downloads", DefaultModelID, "old-run", "model_package")
+ writeTestFile(t, stalePath, "partial")
+
+ err := DownloadProfile(DefaultModelProfile(), []string{"://bad-url"}, root, nil)
+ if err == nil {
+ t.Fatal("expected download failure")
+ }
+ if _, statErr := os.Stat(filepath.Dir(stalePath)); !os.IsNotExist(statErr) {
+ t.Fatalf("stale download run still exists or stat failed: %v", statErr)
+ }
+}
+
func TestDownloadProfileFallsBackWhenPrimaryArchiveInvalid(t *testing.T) {
root := t.TempDir()
validArchive := tarArchive(t, map[string]string{
--
Gitblit v1.9.3