chore: rebrand from Kon/Corbie to Magnotia
Replace all instances of the legacy product names "Kon" and "Corbie" with "Magnotia" across user-facing copy, code identifiers, package names, bundle ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE terminal) reference and the parent CORBEL company name. - Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary - Updates package.json, tauri.conf.json (productName + identifier) - Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations - Renames brand and roadmap docs - Regenerates Cargo.lock and package-lock.json Verified: svelte-check passes; pure-rust crates compile under new names.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "kon-transcription"
|
||||
name = "magnotia-transcription"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Speech-to-text engine wrappers, model management, and inference concurrency for Kon"
|
||||
description = "Speech-to-text engine wrappers, model management, and inference concurrency for Magnotia"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
@@ -15,13 +15,13 @@ build = "build.rs"
|
||||
# `whisper-vulkan` is a separate feature so a non-Vulkan target (Android
|
||||
# without GPU drivers, a CPU-only Windows build) can pull in whisper-rs
|
||||
# but skip the Vulkan backend. Build CPU-only with:
|
||||
# cargo build -p kon-transcription --no-default-features --features whisper
|
||||
# cargo build -p magnotia-transcription --no-default-features --features whisper
|
||||
default = ["whisper", "whisper-vulkan"]
|
||||
whisper = ["dep:whisper-rs", "dep:num_cpus"]
|
||||
whisper-vulkan = ["whisper-rs?/vulkan"]
|
||||
|
||||
[dependencies]
|
||||
kon-core = { path = "../core" }
|
||||
magnotia-core = { path = "../core" }
|
||||
|
||||
# Parakeet via ONNX. Whisper is handled directly via whisper-rs below.
|
||||
transcribe-rs = { version = "0.3", default-features = false, features = ["onnx"] }
|
||||
@@ -53,6 +53,6 @@ thiserror = "2"
|
||||
tracing = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
# TcpListener fixture for the download resume tests (mirrors kon-llm).
|
||||
# TcpListener fixture for the download resume tests (mirrors magnotia-llm).
|
||||
tokio = { version = "1", features = ["rt", "sync", "net", "io-util", "macros"] }
|
||||
tempfile = "3"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! workspace ever pulls `tokenizers` into the dependency graph on a
|
||||
//! Windows target. If we ever legitimately need it we can reintroduce
|
||||
//! it via a sidecar (isolated process, separate CRT) rather than
|
||||
//! linking it into `kon_lib`.
|
||||
//! linking it into `magnotia_lib`.
|
||||
//!
|
||||
//! The check is advisory on non-Windows targets — it still prints a
|
||||
//! cargo:warning if `tokenizers` appears, so the Windows failure isn't
|
||||
@@ -56,7 +56,7 @@ fn main() {
|
||||
|
||||
if target_os == "windows" {
|
||||
panic!(
|
||||
"kon-transcription: the `tokenizers` crate appears in Cargo.lock and this is a \
|
||||
"magnotia-transcription: the `tokenizers` crate appears in Cargo.lock and this is a \
|
||||
Windows build. Linking `whisper-rs-sys` + `tokenizers` in the same binary has \
|
||||
been a persistent MSVC C-runtime conflict (see Whispering v7.11.0). Route any \
|
||||
tokenizer usage through an out-of-process sidecar instead, or gate it off for \
|
||||
@@ -65,7 +65,7 @@ fn main() {
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:warning=kon-transcription: `tokenizers` crate is in the dependency graph. \
|
||||
"cargo:warning=magnotia-transcription: `tokenizers` crate is in the dependency graph. \
|
||||
This build is non-Windows so the link will succeed, but Windows builds will panic \
|
||||
at build time per docs/whisper-ecosystem/brief.md item #6. Isolate tokenizer usage \
|
||||
in a sidecar before a Windows ship."
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::types::{AudioSamples, TranscriptionOptions};
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::types::{AudioSamples, TranscriptionOptions};
|
||||
|
||||
use crate::local_engine::{LocalEngine, TimedTranscript};
|
||||
|
||||
@@ -14,5 +14,5 @@ pub async fn run_inference(
|
||||
) -> Result<TimedTranscript> {
|
||||
tokio::task::spawn_blocking(move || engine.transcribe_sync(&audio, &options))
|
||||
.await
|
||||
.map_err(|e| KonError::TranscriptionFailed(format!("Task join error: {e}")))?
|
||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Task join error: {e}")))?
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::time::Instant;
|
||||
|
||||
use transcribe_rs::{SpeechModel, TranscribeOptions, TranscriptionResult};
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::types::{
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::types::{
|
||||
AudioSamples, EngineName, ModelId, Segment, Transcript, TranscriptionOptions,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct SpeechModelAdapter(pub Box<dyn SpeechModel + Send>);
|
||||
impl Transcriber for SpeechModelAdapter {
|
||||
fn capabilities(&self) -> TranscriberCapabilities {
|
||||
TranscriberCapabilities {
|
||||
sample_rate: kon_core::constants::WHISPER_SAMPLE_RATE,
|
||||
sample_rate: magnotia_core::constants::WHISPER_SAMPLE_RATE,
|
||||
channels: 1,
|
||||
supports_initial_prompt: false,
|
||||
}
|
||||
@@ -48,7 +48,7 @@ impl Transcriber for SpeechModelAdapter {
|
||||
let result: TranscriptionResult = self
|
||||
.0
|
||||
.transcribe(samples, &opts)
|
||||
.map_err(|e| KonError::TranscriptionFailed(e.to_string()))?;
|
||||
.map_err(|e| MagnotiaError::TranscriptionFailed(e.to_string()))?;
|
||||
Ok(result
|
||||
.segments
|
||||
.unwrap_or_default()
|
||||
@@ -140,7 +140,7 @@ impl LocalEngine {
|
||||
options: &TranscriptionOptions,
|
||||
) -> Result<TimedTranscript> {
|
||||
let mut guard = self.engine.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let backend = guard.as_mut().ok_or(KonError::EngineNotLoaded)?;
|
||||
let backend = guard.as_mut().ok_or(MagnotiaError::EngineNotLoaded)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let segments = backend.transcribe_sync(audio.samples(), options)?;
|
||||
@@ -160,7 +160,7 @@ impl LocalEngine {
|
||||
/// Thin wrapper over `ParakeetModel` that overrides `transcribe_raw` to
|
||||
/// request word-granularity segments. `transcribe-rs` 0.3's trait impl for
|
||||
/// `ParakeetModel::transcribe_raw` ignores `TranscribeOptions` and uses
|
||||
/// `TimestampGranularity::Token` (per-subword) — which surfaces in Kon as
|
||||
/// `TimestampGranularity::Token` (per-subword) — which surfaces in Magnotia as
|
||||
/// "T Est Ing . One , Two , Three" output. The concrete-type method
|
||||
/// `ParakeetModel::transcribe_with` accepts `ParakeetParams` with an
|
||||
/// explicit granularity; this wrapper exposes that to the trait object.
|
||||
@@ -197,7 +197,7 @@ impl transcribe_rs::SpeechModel for ParakeetWordGranularity {
|
||||
pub fn load_parakeet(model_dir: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
||||
use transcribe_rs::onnx::Quantization;
|
||||
let model = transcribe_rs::onnx::parakeet::ParakeetModel::load(model_dir, &Quantization::Int8)
|
||||
.map_err(|e| KonError::TranscriptionFailed(format!("Failed to load Parakeet: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Failed to load Parakeet: {e}")))?;
|
||||
Ok(Box::new(SpeechModelAdapter(Box::new(
|
||||
ParakeetWordGranularity(model),
|
||||
))))
|
||||
@@ -207,7 +207,7 @@ pub fn load_parakeet(model_dir: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
||||
#[cfg(feature = "whisper")]
|
||||
pub fn load_whisper(model_path: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
||||
let backend = WhisperRsBackend::load(model_path)
|
||||
.map_err(|e| KonError::TranscriptionFailed(format!("Failed to load Whisper: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Failed to load Whisper: {e}")))?;
|
||||
Ok(Box::new(backend))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::model_registry::{find_model, ModelFile};
|
||||
use kon_core::types::{DownloadProgress, ModelId};
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::model_registry::{find_model, ModelFile};
|
||||
use magnotia_core::types::{DownloadProgress, ModelId};
|
||||
|
||||
static ACTIVE_DOWNLOADS: LazyLock<Mutex<HashSet<String>>> =
|
||||
LazyLock::new(|| Mutex::new(HashSet::new()));
|
||||
@@ -18,9 +18,9 @@ impl DownloadReservation {
|
||||
let id = id.as_str().to_string();
|
||||
let mut active = ACTIVE_DOWNLOADS
|
||||
.lock()
|
||||
.map_err(|_| KonError::DownloadFailed("download lock poisoned".into()))?;
|
||||
.map_err(|_| MagnotiaError::DownloadFailed("download lock poisoned".into()))?;
|
||||
if !active.insert(id.clone()) {
|
||||
return Err(KonError::DownloadFailed(format!(
|
||||
return Err(MagnotiaError::DownloadFailed(format!(
|
||||
"download already in progress for {id}"
|
||||
)));
|
||||
}
|
||||
@@ -37,15 +37,15 @@ impl Drop for DownloadReservation {
|
||||
}
|
||||
|
||||
/// Resolve the models storage directory.
|
||||
/// Windows: %LOCALAPPDATA%/kon/models
|
||||
/// Unix: ~/.kon/models
|
||||
/// Windows: %LOCALAPPDATA%/magnotia/models
|
||||
/// Unix: ~/.magnotia/models
|
||||
pub fn models_dir() -> PathBuf {
|
||||
kon_core::paths::app_paths().models_dir()
|
||||
magnotia_core::paths::app_paths().models_dir()
|
||||
}
|
||||
|
||||
/// Get the directory path where a specific model's files are stored.
|
||||
pub fn model_dir(id: &ModelId) -> PathBuf {
|
||||
kon_core::paths::app_paths().speech_model_dir(id)
|
||||
magnotia_core::paths::app_paths().speech_model_dir(id)
|
||||
}
|
||||
|
||||
/// Check whether all files for a model have been downloaded.
|
||||
@@ -61,7 +61,7 @@ pub fn is_downloaded(id: &ModelId) -> bool {
|
||||
|
||||
/// List all downloaded model IDs.
|
||||
pub fn list_downloaded() -> Vec<ModelId> {
|
||||
kon_core::model_registry::all_models()
|
||||
magnotia_core::model_registry::all_models()
|
||||
.iter()
|
||||
.filter(|m| is_downloaded(&m.id))
|
||||
.map(|m| m.id.clone())
|
||||
@@ -74,13 +74,13 @@ pub fn list_downloaded() -> Vec<ModelId> {
|
||||
/// For files that declare a `sha256` checksum we validate an existing
|
||||
/// complete file before skipping the download — a truncated or
|
||||
/// tampered file gets redownloaded automatically (pattern ported from
|
||||
/// `kon-llm`'s model_manager, item #8 in the Whisper ecosystem brief).
|
||||
/// `magnotia-llm`'s model_manager, item #8 in the Whisper ecosystem brief).
|
||||
pub async fn download(
|
||||
id: &ModelId,
|
||||
progress: impl Fn(DownloadProgress) + Send + 'static,
|
||||
) -> Result<()> {
|
||||
let _reservation = DownloadReservation::acquire(id)?;
|
||||
let entry = find_model(id).ok_or_else(|| KonError::ModelNotFound(id.clone()))?;
|
||||
let entry = find_model(id).ok_or_else(|| MagnotiaError::ModelNotFound(id.clone()))?;
|
||||
|
||||
let dir = model_dir(id);
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
@@ -98,7 +98,7 @@ pub async fn download(
|
||||
let _ = std::fs::remove_file(&dest);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(KonError::DownloadFailed(format!(
|
||||
return Err(MagnotiaError::DownloadFailed(format!(
|
||||
"failed to verify existing {}: {e}",
|
||||
file.filename
|
||||
)));
|
||||
@@ -113,10 +113,10 @@ pub async fn download(
|
||||
}
|
||||
|
||||
fn verified_manifest_path(dir: &Path) -> PathBuf {
|
||||
dir.join(".kon-verified")
|
||||
dir.join(".magnotia-verified")
|
||||
}
|
||||
|
||||
fn verified_manifest_matches(entry: &kon_core::model_registry::ModelEntry, dir: &Path) -> bool {
|
||||
fn verified_manifest_matches(entry: &magnotia_core::model_registry::ModelEntry, dir: &Path) -> bool {
|
||||
let manifest = match std::fs::read_to_string(verified_manifest_path(dir)) {
|
||||
Ok(contents) => contents,
|
||||
Err(_) => return false,
|
||||
@@ -137,7 +137,7 @@ fn verified_manifest_matches(entry: &kon_core::model_registry::ModelEntry, dir:
|
||||
}
|
||||
|
||||
fn write_verified_manifest(
|
||||
entry: &kon_core::model_registry::ModelEntry,
|
||||
entry: &magnotia_core::model_registry::ModelEntry,
|
||||
dir: &Path,
|
||||
) -> std::io::Result<()> {
|
||||
let mut lines = Vec::with_capacity(entry.files.len() + 1);
|
||||
@@ -193,7 +193,7 @@ async fn download_file(
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.map_err(|e| KonError::DownloadFailed(e.to_string()))?;
|
||||
.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?;
|
||||
|
||||
// Check for existing partial download (resume support)
|
||||
let existing_bytes = if part_path.exists() {
|
||||
@@ -212,12 +212,12 @@ async fn download_file(
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| KonError::DownloadFailed(e.to_string()))?;
|
||||
.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?;
|
||||
|
||||
// If we requested Range but the server returned 200 (full file), the
|
||||
// server does not support resume. Rather than blindly appending a
|
||||
// full file on top of our partial bytes (which would produce a
|
||||
// corrupt result), restart cleanly. This mirrors the kon-llm
|
||||
// corrupt result), restart cleanly. This mirrors the magnotia-llm
|
||||
// ResumeUnsupported branch — item #8 of the brief.
|
||||
//
|
||||
// For the non-resume path, we still have to validate the status:
|
||||
@@ -234,14 +234,14 @@ async fn download_file(
|
||||
false
|
||||
}
|
||||
other => {
|
||||
return Err(KonError::DownloadFailed(format!(
|
||||
return Err(MagnotiaError::DownloadFailed(format!(
|
||||
"resume request returned unexpected status {other}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !response.status().is_success() {
|
||||
return Err(KonError::DownloadFailed(format!(
|
||||
return Err(MagnotiaError::DownloadFailed(format!(
|
||||
"download returned HTTP {} for {}",
|
||||
response.status(),
|
||||
file.filename
|
||||
@@ -288,7 +288,7 @@ async fn download_file(
|
||||
}
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk.map_err(|e| KonError::DownloadFailed(e.to_string()))?;
|
||||
let chunk = chunk.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?;
|
||||
std::io::Write::write_all(&mut out, &chunk)?;
|
||||
hasher.update(&chunk);
|
||||
downloaded += chunk.len() as u64;
|
||||
@@ -316,7 +316,7 @@ async fn download_file(
|
||||
let actual = format!("{:x}", hasher.finalize());
|
||||
if actual != file.sha256 {
|
||||
let _ = std::fs::remove_file(&part_path);
|
||||
return Err(KonError::DownloadFailed(format!(
|
||||
return Err(MagnotiaError::DownloadFailed(format!(
|
||||
"SHA256 mismatch for {}: expected {}, got {}",
|
||||
file.filename, file.sha256, actual
|
||||
)));
|
||||
@@ -354,7 +354,7 @@ mod tests {
|
||||
let list = list_downloaded();
|
||||
// In test environment, no models are downloaded
|
||||
// This just verifies the function doesn't panic
|
||||
assert!(list.len() <= kon_core::model_registry::all_models().len());
|
||||
assert!(list.len() <= magnotia_core::model_registry::all_models().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -478,7 +478,7 @@ mod tests {
|
||||
let file = ModelFile {
|
||||
filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()),
|
||||
url: leak(format!("http://{addr}/fixture.bin")),
|
||||
size: kon_core::types::Megabytes(0),
|
||||
size: magnotia_core::types::Megabytes(0),
|
||||
sha256: leak(expected_sha.clone()),
|
||||
};
|
||||
let id = ModelId::new("test-fixture");
|
||||
@@ -513,7 +513,7 @@ mod tests {
|
||||
let file = ModelFile {
|
||||
filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()),
|
||||
url: leak(format!("http://{addr}/fixture.bin")),
|
||||
size: kon_core::types::Megabytes(0),
|
||||
size: magnotia_core::types::Megabytes(0),
|
||||
sha256: leak(expected_sha),
|
||||
};
|
||||
let id = ModelId::new("test-fixture");
|
||||
@@ -568,7 +568,7 @@ mod tests {
|
||||
let file = ModelFile {
|
||||
filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()),
|
||||
url: leak(format!("http://{addr}/fixture.bin")),
|
||||
size: kon_core::types::Megabytes(0),
|
||||
size: magnotia_core::types::Megabytes(0),
|
||||
sha256: leak("0".repeat(64)),
|
||||
};
|
||||
let id = ModelId::new("test-fixture");
|
||||
@@ -596,7 +596,7 @@ mod tests {
|
||||
let file = ModelFile {
|
||||
filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()),
|
||||
url: leak(format!("http://{addr}/fixture.bin")),
|
||||
size: kon_core::types::Megabytes(0),
|
||||
size: magnotia_core::types::Megabytes(0),
|
||||
sha256: leak("deadbeef".repeat(8)),
|
||||
};
|
||||
let id = ModelId::new("test-fixture");
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
//! `whisper` feature — `WhisperRsBackend` (direct whisper-rs, the only
|
||||
//! path that pipes `initial_prompt`).
|
||||
|
||||
use kon_core::error::Result;
|
||||
use kon_core::types::{Segment, TranscriptionOptions};
|
||||
use magnotia_core::error::Result;
|
||||
use magnotia_core::types::{Segment, TranscriptionOptions};
|
||||
|
||||
/// Static capabilities a `Transcriber` advertises to callers.
|
||||
///
|
||||
|
||||
@@ -10,8 +10,8 @@ use std::path::Path;
|
||||
|
||||
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::types::{Segment, TranscriptionOptions};
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::types::{Segment, TranscriptionOptions};
|
||||
|
||||
use crate::transcriber::{Transcriber, TranscriberCapabilities};
|
||||
|
||||
@@ -40,7 +40,7 @@ impl WhisperRsBackend {
|
||||
impl Transcriber for WhisperRsBackend {
|
||||
fn capabilities(&self) -> TranscriberCapabilities {
|
||||
TranscriberCapabilities {
|
||||
sample_rate: kon_core::constants::WHISPER_SAMPLE_RATE,
|
||||
sample_rate: magnotia_core::constants::WHISPER_SAMPLE_RATE,
|
||||
channels: 1,
|
||||
supports_initial_prompt: true,
|
||||
}
|
||||
@@ -63,7 +63,7 @@ impl Transcriber for WhisperRsBackend {
|
||||
);
|
||||
|
||||
let mut state = self.ctx.create_state().map_err(|e| {
|
||||
KonError::TranscriptionFailed(WhisperBackendError::State(e.to_string()).to_string())
|
||||
MagnotiaError::TranscriptionFailed(WhisperBackendError::State(e.to_string()).to_string())
|
||||
})?;
|
||||
|
||||
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
|
||||
@@ -83,7 +83,7 @@ impl Transcriber for WhisperRsBackend {
|
||||
params.set_print_realtime(false);
|
||||
|
||||
state.full(params, samples).map_err(|e| {
|
||||
KonError::TranscriptionFailed(
|
||||
MagnotiaError::TranscriptionFailed(
|
||||
WhisperBackendError::Transcribe(e.to_string()).to_string(),
|
||||
)
|
||||
})?;
|
||||
@@ -98,7 +98,7 @@ impl Transcriber for WhisperRsBackend {
|
||||
let text = seg
|
||||
.to_str()
|
||||
.map_err(|e| {
|
||||
KonError::TranscriptionFailed(
|
||||
MagnotiaError::TranscriptionFailed(
|
||||
WhisperBackendError::Transcribe(e.to_string()).to_string(),
|
||||
)
|
||||
})?
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
//! Smoke test: whisper-rs 0.16 loads a GGUF model, transcribes silence, and
|
||||
//! accepts set_initial_prompt without panicking.
|
||||
//!
|
||||
//! Runs only when `KON_WHISPER_TEST_MODEL` is set to the path of a
|
||||
//! Runs only when `MAGNOTIA_WHISPER_TEST_MODEL` is set to the path of a
|
||||
//! ggml/gguf whisper model on disk. Otherwise the test exits quiet.
|
||||
|
||||
use std::env;
|
||||
|
||||
#[test]
|
||||
fn whisper_rs_smoke_loads_and_transcribes() {
|
||||
let model_path = match env::var("KON_WHISPER_TEST_MODEL") {
|
||||
let model_path = match env::var("MAGNOTIA_WHISPER_TEST_MODEL") {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
eprintln!("KON_WHISPER_TEST_MODEL not set — skipping");
|
||||
eprintln!("MAGNOTIA_WHISPER_TEST_MODEL not set — skipping");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user