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:
Claude
2026-04-30 13:06:55 +00:00
parent 749403697a
commit 89c63891fa
186 changed files with 1297 additions and 1297 deletions

View File

@@ -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"

View File

@@ -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."

View File

@@ -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}")))?
}

View File

@@ -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))
}

View File

@@ -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");

View File

@@ -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.
///

View File

@@ -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(),
)
})?

View File

@@ -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;
}
};