diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index d88ca08..d0e4db3 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1,8 +1,14 @@ use std::path::PathBuf; +use serde::Serialize; + use crate::types::ModelId; -#[derive(Debug, thiserror::Error)] +/// Structured error type for Kon. +/// +/// Implements `Serialize` so errors can be sent to the frontend as +/// structured JSON rather than opaque strings. +#[derive(Debug, thiserror::Error, Serialize)] pub enum KonError { #[error("model not found: {0}")] ModelNotFound(ModelId), @@ -32,10 +38,23 @@ pub enum KonError { StorageError(String), #[error("io error: {0}")] - Io(#[from] std::io::Error), + Io( + #[from] + #[serde(serialize_with = "serialize_io_error")] + std::io::Error, + ), #[error("{0}")] Other(String), } +/// Serialises `std::io::Error` as its display string, since it does +/// not implement `Serialize` natively. +fn serialize_io_error( + err: &std::io::Error, + s: S, +) -> std::result::Result { + s.serialize_str(&err.to_string()) +} + pub type Result = std::result::Result; diff --git a/crates/core/src/hardware.rs b/crates/core/src/hardware.rs index c7bb866..23df6a2 100644 --- a/crates/core/src/hardware.rs +++ b/crates/core/src/hardware.rs @@ -13,7 +13,7 @@ pub struct SystemProfile { #[derive(Debug, Clone)] pub struct CpuInfo { - pub core_count: usize, + pub logical_processors: usize, pub brand: String, } @@ -49,16 +49,16 @@ pub enum Os { Android, } -pub fn probe_ram() -> Megabytes { - let sys = System::new_all(); +/// Probes RAM from a shared `System` instance. +fn probe_ram_from(sys: &System) -> Megabytes { let total_bytes = sys.total_memory(); Megabytes(total_bytes / (1024 * 1024)) } -pub fn probe_cpu() -> CpuInfo { - let sys = System::new_all(); +/// Probes CPU info from a shared `System` instance. +fn probe_cpu_from(sys: &System) -> CpuInfo { CpuInfo { - core_count: sys.cpus().len(), + logical_processors: sys.cpus().len(), brand: sys .cpus() .first() @@ -75,32 +75,30 @@ pub fn probe_gpu() -> Option { pub fn probe_os() -> Os { #[cfg(target_os = "windows")] - { - Os::Windows - } + return Os::Windows; #[cfg(target_os = "linux")] - { - Os::Linux - } + return Os::Linux; #[cfg(target_os = "macos")] - { - Os::MacOs - } + return Os::MacOs; #[cfg(target_os = "ios")] - { - Os::Ios - } + return Os::Ios; #[cfg(target_os = "android")] - { - Os::Android - } + return Os::Android; + + // Fallback for unsupported targets — treat as Linux since + // most exotic/embedded targets are Unix-like. + #[allow(unreachable_code)] + Os::Linux } -/// Composes the individual probes. No logic here — just assembly. +/// Composes the individual probes using a single `System` snapshot. +/// `System::new_all()` is expensive — calling it once rather than +/// per-probe avoids redundant OS queries. pub fn probe_system() -> SystemProfile { + let sys = System::new_all(); SystemProfile { - ram: probe_ram(), - cpu: probe_cpu(), + ram: probe_ram_from(&sys), + cpu: probe_cpu_from(&sys), gpu: probe_gpu(), os: probe_os(), } diff --git a/crates/core/src/providers.rs b/crates/core/src/providers.rs index 658475b..0307c58 100644 --- a/crates/core/src/providers.rs +++ b/crates/core/src/providers.rs @@ -32,6 +32,8 @@ pub trait TextProcessor: Send + Sync { /// Holds the active provider instances. Constructed at startup, /// rebuilt when user changes provider in settings. +// TODO: Wire into Tauri app state once multi-engine switching is implemented. +#[allow(dead_code)] pub struct ProviderRegistry { pub stt: Arc, pub text: Option>, diff --git a/crates/core/src/recommendation.rs b/crates/core/src/recommendation.rs index 185400e..cce6dff 100644 --- a/crates/core/src/recommendation.rs +++ b/crates/core/src/recommendation.rs @@ -1,7 +1,5 @@ use crate::hardware::SystemProfile; -use crate::model_registry::{ - all_models, AccuracyTier, Engine, ModelEntry, SpeedTier, -}; +use crate::model_registry::{all_models, AccuracyTier, Engine, ModelEntry, SpeedTier}; use crate::types::Megabytes; /// A model's suitability score for a given system. Higher is better. @@ -14,10 +12,7 @@ pub struct ScoredModel { /// Scores a single model against a system profile. /// Pure function, no side effects. -pub fn score_model( - model: &'static ModelEntry, - profile: &SystemProfile, -) -> Option { +pub fn score_model(model: &'static ModelEntry, profile: &SystemProfile) -> Option { if model.ram_required > profile.ram { return None; } @@ -41,9 +36,7 @@ pub fn score_model( if let Some(gpu) = &profile.gpu { let has_accel = match model.engine { Engine::Whisper => { - gpu.acceleration.metal - || gpu.acceleration.vulkan - || gpu.acceleration.cuda + gpu.acceleration.metal || gpu.acceleration.vulkan || gpu.acceleration.cuda } Engine::Parakeet | Engine::Moonshine => { gpu.acceleration.cuda || gpu.acceleration.vulkan @@ -55,8 +48,7 @@ pub fn score_model( } } - let headroom = - Megabytes(profile.ram.0.saturating_sub(model.ram_required.0)); + let headroom = Megabytes(profile.ram.0.saturating_sub(model.ram_required.0)); if headroom > Megabytes::from_gb(4.0) { score += 10.0; } @@ -99,7 +91,7 @@ mod tests { SystemProfile { ram, cpu: CpuInfo { - core_count: 8, + logical_processors: 8, brand: "Test CPU".into(), }, gpu: None, @@ -111,7 +103,7 @@ mod tests { SystemProfile { ram, cpu: CpuInfo { - core_count: 8, + logical_processors: 8, brand: "Test CPU".into(), }, gpu: Some(GpuInfo { @@ -157,14 +149,12 @@ mod tests { .find(|m| m.engine == Engine::Parakeet) .expect("need a Parakeet model"); - let gpu_score = - score_model(model, &profile_with_gpu(Megabytes(16384))) - .unwrap() - .score; - let cpu_score = - score_model(model, &profile_with_ram(Megabytes(16384))) - .unwrap() - .score; + let gpu_score = score_model(model, &profile_with_gpu(Megabytes(16384))) + .unwrap() + .score; + let cpu_score = score_model(model, &profile_with_ram(Megabytes(16384))) + .unwrap() + .score; assert!(gpu_score > cpu_score); } diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 8555a64..c72f981 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -168,6 +168,8 @@ pub struct TranscriptionOptions { /// Full provenance metadata for a transcript. /// Captures everything needed to reproduce the transcription. +// TODO: Attach to Transcript once the store layer persists transcription provenance. +#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TranscriptMetadata { pub engine: String, diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index f91d98a..7c0d7a7 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -31,7 +31,7 @@ pub fn probe_system() -> Result { Ok(SystemInfo { ram_mb: profile.ram.0, cpu_brand: profile.cpu.brand, - cpu_cores: profile.cpu.core_count, + cpu_cores: profile.cpu.logical_processors, os: match profile.os { Os::Windows => "Windows", Os::Linux => "Linux",