fix(kon): harden core crate — shared System instance, accurate CPU field, serialisable errors
- Share single System::new_all() in probe_system() instead of calling it twice - Rename CpuInfo::core_count to logical_processors (sys.cpus().len() returns logical, not physical) - Add fallback arm to probe_os() for unsupported cfg targets - Add serde::Serialize to KonError for structured frontend error reporting - Annotate dead code (ProviderRegistry, TranscriptMetadata) with #[allow(dead_code)] + TODO comments - Update downstream references in recommendation tests and tauri hardware command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::types::ModelId;
|
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 {
|
pub enum KonError {
|
||||||
#[error("model not found: {0}")]
|
#[error("model not found: {0}")]
|
||||||
ModelNotFound(ModelId),
|
ModelNotFound(ModelId),
|
||||||
@@ -32,10 +38,23 @@ pub enum KonError {
|
|||||||
StorageError(String),
|
StorageError(String),
|
||||||
|
|
||||||
#[error("io error: {0}")]
|
#[error("io error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(
|
||||||
|
#[from]
|
||||||
|
#[serde(serialize_with = "serialize_io_error")]
|
||||||
|
std::io::Error,
|
||||||
|
),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialises `std::io::Error` as its display string, since it does
|
||||||
|
/// not implement `Serialize` natively.
|
||||||
|
fn serialize_io_error<S: serde::Serializer>(
|
||||||
|
err: &std::io::Error,
|
||||||
|
s: S,
|
||||||
|
) -> std::result::Result<S::Ok, S::Error> {
|
||||||
|
s.serialize_str(&err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, KonError>;
|
pub type Result<T> = std::result::Result<T, KonError>;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub struct SystemProfile {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CpuInfo {
|
pub struct CpuInfo {
|
||||||
pub core_count: usize,
|
pub logical_processors: usize,
|
||||||
pub brand: String,
|
pub brand: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,16 +49,16 @@ pub enum Os {
|
|||||||
Android,
|
Android,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn probe_ram() -> Megabytes {
|
/// Probes RAM from a shared `System` instance.
|
||||||
let sys = System::new_all();
|
fn probe_ram_from(sys: &System) -> Megabytes {
|
||||||
let total_bytes = sys.total_memory();
|
let total_bytes = sys.total_memory();
|
||||||
Megabytes(total_bytes / (1024 * 1024))
|
Megabytes(total_bytes / (1024 * 1024))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn probe_cpu() -> CpuInfo {
|
/// Probes CPU info from a shared `System` instance.
|
||||||
let sys = System::new_all();
|
fn probe_cpu_from(sys: &System) -> CpuInfo {
|
||||||
CpuInfo {
|
CpuInfo {
|
||||||
core_count: sys.cpus().len(),
|
logical_processors: sys.cpus().len(),
|
||||||
brand: sys
|
brand: sys
|
||||||
.cpus()
|
.cpus()
|
||||||
.first()
|
.first()
|
||||||
@@ -75,32 +75,30 @@ pub fn probe_gpu() -> Option<GpuInfo> {
|
|||||||
|
|
||||||
pub fn probe_os() -> Os {
|
pub fn probe_os() -> Os {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
return Os::Windows;
|
||||||
Os::Windows
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
return Os::Linux;
|
||||||
Os::Linux
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
return Os::MacOs;
|
||||||
Os::MacOs
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
{
|
return Os::Ios;
|
||||||
Os::Ios
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
return Os::Android;
|
||||||
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 {
|
pub fn probe_system() -> SystemProfile {
|
||||||
|
let sys = System::new_all();
|
||||||
SystemProfile {
|
SystemProfile {
|
||||||
ram: probe_ram(),
|
ram: probe_ram_from(&sys),
|
||||||
cpu: probe_cpu(),
|
cpu: probe_cpu_from(&sys),
|
||||||
gpu: probe_gpu(),
|
gpu: probe_gpu(),
|
||||||
os: probe_os(),
|
os: probe_os(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ pub trait TextProcessor: Send + Sync {
|
|||||||
|
|
||||||
/// Holds the active provider instances. Constructed at startup,
|
/// Holds the active provider instances. Constructed at startup,
|
||||||
/// rebuilt when user changes provider in settings.
|
/// 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 struct ProviderRegistry {
|
||||||
pub stt: Arc<dyn SpeechToText>,
|
pub stt: Arc<dyn SpeechToText>,
|
||||||
pub text: Option<Arc<dyn TextProcessor>>,
|
pub text: Option<Arc<dyn TextProcessor>>,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use crate::hardware::SystemProfile;
|
use crate::hardware::SystemProfile;
|
||||||
use crate::model_registry::{
|
use crate::model_registry::{all_models, AccuracyTier, Engine, ModelEntry, SpeedTier};
|
||||||
all_models, AccuracyTier, Engine, ModelEntry, SpeedTier,
|
|
||||||
};
|
|
||||||
use crate::types::Megabytes;
|
use crate::types::Megabytes;
|
||||||
|
|
||||||
/// A model's suitability score for a given system. Higher is better.
|
/// 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.
|
/// Scores a single model against a system profile.
|
||||||
/// Pure function, no side effects.
|
/// Pure function, no side effects.
|
||||||
pub fn score_model(
|
pub fn score_model(model: &'static ModelEntry, profile: &SystemProfile) -> Option<ScoredModel> {
|
||||||
model: &'static ModelEntry,
|
|
||||||
profile: &SystemProfile,
|
|
||||||
) -> Option<ScoredModel> {
|
|
||||||
if model.ram_required > profile.ram {
|
if model.ram_required > profile.ram {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -41,9 +36,7 @@ pub fn score_model(
|
|||||||
if let Some(gpu) = &profile.gpu {
|
if let Some(gpu) = &profile.gpu {
|
||||||
let has_accel = match model.engine {
|
let has_accel = match model.engine {
|
||||||
Engine::Whisper => {
|
Engine::Whisper => {
|
||||||
gpu.acceleration.metal
|
gpu.acceleration.metal || gpu.acceleration.vulkan || gpu.acceleration.cuda
|
||||||
|| gpu.acceleration.vulkan
|
|
||||||
|| gpu.acceleration.cuda
|
|
||||||
}
|
}
|
||||||
Engine::Parakeet | Engine::Moonshine => {
|
Engine::Parakeet | Engine::Moonshine => {
|
||||||
gpu.acceleration.cuda || gpu.acceleration.vulkan
|
gpu.acceleration.cuda || gpu.acceleration.vulkan
|
||||||
@@ -55,8 +48,7 @@ pub fn score_model(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let headroom =
|
let headroom = Megabytes(profile.ram.0.saturating_sub(model.ram_required.0));
|
||||||
Megabytes(profile.ram.0.saturating_sub(model.ram_required.0));
|
|
||||||
if headroom > Megabytes::from_gb(4.0) {
|
if headroom > Megabytes::from_gb(4.0) {
|
||||||
score += 10.0;
|
score += 10.0;
|
||||||
}
|
}
|
||||||
@@ -99,7 +91,7 @@ mod tests {
|
|||||||
SystemProfile {
|
SystemProfile {
|
||||||
ram,
|
ram,
|
||||||
cpu: CpuInfo {
|
cpu: CpuInfo {
|
||||||
core_count: 8,
|
logical_processors: 8,
|
||||||
brand: "Test CPU".into(),
|
brand: "Test CPU".into(),
|
||||||
},
|
},
|
||||||
gpu: None,
|
gpu: None,
|
||||||
@@ -111,7 +103,7 @@ mod tests {
|
|||||||
SystemProfile {
|
SystemProfile {
|
||||||
ram,
|
ram,
|
||||||
cpu: CpuInfo {
|
cpu: CpuInfo {
|
||||||
core_count: 8,
|
logical_processors: 8,
|
||||||
brand: "Test CPU".into(),
|
brand: "Test CPU".into(),
|
||||||
},
|
},
|
||||||
gpu: Some(GpuInfo {
|
gpu: Some(GpuInfo {
|
||||||
@@ -157,14 +149,12 @@ mod tests {
|
|||||||
.find(|m| m.engine == Engine::Parakeet)
|
.find(|m| m.engine == Engine::Parakeet)
|
||||||
.expect("need a Parakeet model");
|
.expect("need a Parakeet model");
|
||||||
|
|
||||||
let gpu_score =
|
let gpu_score = score_model(model, &profile_with_gpu(Megabytes(16384)))
|
||||||
score_model(model, &profile_with_gpu(Megabytes(16384)))
|
.unwrap()
|
||||||
.unwrap()
|
.score;
|
||||||
.score;
|
let cpu_score = score_model(model, &profile_with_ram(Megabytes(16384)))
|
||||||
let cpu_score =
|
.unwrap()
|
||||||
score_model(model, &profile_with_ram(Megabytes(16384)))
|
.score;
|
||||||
.unwrap()
|
|
||||||
.score;
|
|
||||||
|
|
||||||
assert!(gpu_score > cpu_score);
|
assert!(gpu_score > cpu_score);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ pub struct TranscriptionOptions {
|
|||||||
|
|
||||||
/// Full provenance metadata for a transcript.
|
/// Full provenance metadata for a transcript.
|
||||||
/// Captures everything needed to reproduce the transcription.
|
/// 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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TranscriptMetadata {
|
pub struct TranscriptMetadata {
|
||||||
pub engine: String,
|
pub engine: String,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub fn probe_system() -> Result<SystemInfo, String> {
|
|||||||
Ok(SystemInfo {
|
Ok(SystemInfo {
|
||||||
ram_mb: profile.ram.0,
|
ram_mb: profile.ram.0,
|
||||||
cpu_brand: profile.cpu.brand,
|
cpu_brand: profile.cpu.brand,
|
||||||
cpu_cores: profile.cpu.core_count,
|
cpu_cores: profile.cpu.logical_processors,
|
||||||
os: match profile.os {
|
os: match profile.os {
|
||||||
Os::Windows => "Windows",
|
Os::Windows => "Windows",
|
||||||
Os::Linux => "Linux",
|
Os::Linux => "Linux",
|
||||||
|
|||||||
Reference in New Issue
Block a user