diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index dbcc873..9f556e3 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -948,15 +948,25 @@ pub async fn download_npu_model(app: AppHandle) -> WaResult<()> { .map_err(|e| WaError::new("npu", e.to_string())) } -/// Hosted OpenVINO runtime bundle (ORT 1.24.1 + OpenVINO 2025.4.1 DLLs, zipped). -/// Overridable at runtime via `WA_NPU_RUNTIME_URL`. Keep the SHA-256 in step with -/// the uploaded bundle (see `dist/whispassist-npu-runtime-win-x64.zip`). +/// Hosted OpenVINO runtime bundle (ORT 1.24.1 + OpenVINO 2025.4.1 DLLs, 7z). +/// Served from the repo's `runtime/` dir via gitea's raw path; overridable at +/// runtime via `WA_NPU_RUNTIME_URL`. Keep the SHA-256 in step with the file. #[cfg(feature = "npu")] const NPU_RUNTIME_URL: &str = - "https://git.dou.bet/api/packages/iamdoubz/generic/npu-runtime/2025.4.1/whispassist-npu-runtime-win-x64.zip"; + "https://git.dou.bet/iamdoubz/WhispAssist/raw/branch/main/runtime/openvino.7z"; /// SHA-256 of the runtime bundle; empty string disables the integrity check. #[cfg(feature = "npu")] -const NPU_RUNTIME_SHA256: &str = "c60de07b5b1ddc2fd1e966d8275d9f55ec261efc81355ea814d6dac897adbdc5"; +const NPU_RUNTIME_SHA256: &str = "ca0be9fc52c78ee623b152f790450b3d4020c5a7ebe99d27736455b308782191"; + +/// Hosted DirectML runtime bundle (ORT 1.24.1 DirectML-EP DLLs, 7z), same repo +/// raw path as the OpenVINO one; overridable via `WA_DIRECTML_RUNTIME_URL`. +#[cfg(feature = "npu")] +const DIRECTML_RUNTIME_URL: &str = + "https://git.dou.bet/iamdoubz/WhispAssist/raw/branch/main/runtime/directml.7z"; +/// SHA-256 of the DirectML runtime bundle; empty string disables the check. +#[cfg(feature = "npu")] +const DIRECTML_RUNTIME_SHA256: &str = + "34369222fcc1be2e72a957b868b1976a90150ba704a06c9e8992c34ee368926b"; /// Stages the ONNX Runtime + OpenVINO DLLs into the app's NPU runtime dir by /// downloading the hosted bundle and unzipping it (T3.4). `WA_NPU_RUNTIME_SRC` @@ -986,10 +996,9 @@ async fn stage_npu_runtime(app: &AppHandle) -> WaResult<()> { .await } -/// Stages the DirectML ONNX Runtime into the app's DirectML runtime dir (P2). -/// Unlike the NPU bundle there's no hosted default yet, so the URL must come -/// from `WA_DIRECTML_RUNTIME_URL` — or drop `onnxruntime.dll` into -/// `runtime\directml\` manually (dev testing can also just set `ORT_DYLIB_PATH`). +/// Stages the DirectML ONNX Runtime into the app's DirectML runtime dir (P2) by +/// downloading the hosted 7z bundle and unpacking it. Overridable via +/// `WA_DIRECTML_RUNTIME_URL`; dev testing can also just set `ORT_DYLIB_PATH`. #[cfg(feature = "npu")] async fn stage_directml_runtime(app: &AppHandle) -> WaResult<()> { if crate::paths::directml_runtime_ready() { @@ -997,14 +1006,16 @@ async fn stage_directml_runtime(app: &AppHandle) -> WaResult<()> { } let dir = crate::paths::directml_runtime_dir(); std::fs::create_dir_all(&dir).map_err(|e| WaError::new("directml", e.to_string()))?; - let url = std::env::var("WA_DIRECTML_RUNTIME_URL").map_err(|_| { - WaError::new( - "directml", - "no DirectML runtime: set WA_DIRECTML_RUNTIME_URL or place onnxruntime.dll in runtime\\directml\\", - ) - })?; - // Empty SHA: no published bundle to pin yet (ponytail — fill in once hosted). - download_and_extract_runtime(app, &dir, &url, "", crate::paths::directml_runtime_ready).await + let url = std::env::var("WA_DIRECTML_RUNTIME_URL") + .unwrap_or_else(|_| DIRECTML_RUNTIME_URL.to_string()); + download_and_extract_runtime( + app, + &dir, + &url, + DIRECTML_RUNTIME_SHA256, + crate::paths::directml_runtime_ready, + ) + .await } #[cfg(feature = "npu")] @@ -1070,7 +1081,7 @@ async fn download_and_extract_runtime( )); } let total = resp.content_length(); - let tmp = dir.join("runtime.zip.part"); + let tmp = dir.join("runtime.7z.part"); let mut file = std::fs::File::create(&tmp).map_err(|e| WaError::new("npu", e.to_string()))?; let mut hasher = Sha256::new(); let mut received = 0u64; @@ -1094,10 +1105,10 @@ async fn download_and_extract_runtime( return Err(WaError::new("npu", "runtime bundle checksum mismatch")); } - // Unzip off the async runtime (CPU/IO-bound). + // Un-7z off the async runtime (CPU/IO-bound). let tmp_for_unzip = tmp.clone(); let dir_for_unzip = dir.to_path_buf(); - tokio::task::spawn_blocking(move || extract_zip_flat(&tmp_for_unzip, &dir_for_unzip)) + tokio::task::spawn_blocking(move || extract_7z_flat(&tmp_for_unzip, &dir_for_unzip)) .await .map_err(|e| WaError::new("npu", e.to_string()))? .map_err(|e| WaError::new("npu", e))?; @@ -1113,27 +1124,23 @@ async fn download_and_extract_runtime( } /// Extract every file entry of a zip into `dir`, flattening paths to just the -/// file name (which also prevents zip-slip path traversal). +/// file name (which also strips the archive's top folder — the bundles nest DLLs +/// under `directml/` or `openvino/` — and prevents path traversal). #[cfg(feature = "npu")] -fn extract_zip_flat(zip_path: &Path, dir: &Path) -> Result<(), String> { - let file = std::fs::File::open(zip_path).map_err(|e| e.to_string())?; - let mut archive = zip::ZipArchive::new(file).map_err(|e| e.to_string())?; - for i in 0..archive.len() { - let mut entry = archive.by_index(i).map_err(|e| e.to_string())?; - if entry.is_dir() { - continue; +fn extract_7z_flat(archive_path: &Path, dir: &Path) -> Result<(), String> { + sevenz_rust2::decompress_file_with_extract_fn(archive_path, dir, |entry, reader, _dest| { + if entry.is_directory() { + return Ok(true); } - let Some(name) = Path::new(entry.name()) - .file_name() - .and_then(|n| n.to_str()) - .map(str::to_string) - else { - continue; + // Flatten to just the file name, dropping any folder prefix. + let Some(name) = Path::new(entry.name()).file_name().and_then(|n| n.to_str()) else { + return Ok(true); }; - let mut out = std::fs::File::create(dir.join(name)).map_err(|e| e.to_string())?; - std::io::copy(&mut entry, &mut out).map_err(|e| e.to_string())?; - } - Ok(()) + let mut out = std::fs::File::create(dir.join(name)).map_err(sevenz_rust2::Error::io)?; + std::io::copy(reader, &mut out).map_err(sevenz_rust2::Error::io)?; + Ok(true) + }) + .map_err(|e| e.to_string()) } /// Downloads everything the NPU engine needs (ONNX model + OpenVINO runtime) for @@ -2859,6 +2866,35 @@ pub async fn privacy_self_check(state: State<'_, AppState>) -> WaResult TranscriptSegment { TranscriptSegment { id: 0,