Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src-tauri/src/asr/moonshine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ impl MoonshineBackend {
let arr = if use_cache {
kv.values[idx]
.as_ref()
.ok_or_else(|| anyhow!("KV cache slot {} unpopulated under use_cache", idx))?
.ok_or_else(|| anyhow!("KV cache slot {idx} unpopulated under use_cache"))?
.clone()
} else {
let resolved_shape: Vec<usize> = past
Expand Down Expand Up @@ -736,7 +736,7 @@ impl MoonshineBackend {
.map_err(|e| anyhow!("ort extract logits: {e}"))?;
let shape = logits_view.shape().to_vec();
if shape.len() != 3 || shape[0] != 1 {
return Err(anyhow!("unexpected decoder logits shape {:?}", shape));
return Err(anyhow!("unexpected decoder logits shape {shape:?}"));
}
let last = shape[1] - 1;
let vocab = shape[2];
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/asr/parakeet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ impl AsrBackend for ParakeetBackend {
.map_err(|e| anyhow!("ort threads: {e}"))?
.commit_from_file(&model_path_owned)
.map_err(|e| anyhow!("loading {}: {e}", model_path_owned.display()))
.with_context(|| format!("warm_up parakeet {}", model_name_owned))
.with_context(|| format!("warm_up parakeet {model_name_owned}"))
})?;

// Sniff I/O names. NeMo's istupakov export uses
Expand Down
7 changes: 2 additions & 5 deletions src-tauri/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1052,13 +1052,10 @@ async fn cmd_fetch_onnxruntime() -> Result<()> {
let pct = (bytes as f64 / total as f64 * 100.0) as u64;
let _ = write!(
std::io::stderr(),
"\r {:>3}% {:>10} / {:>10} bytes",
pct,
bytes,
total
"\r {pct:>3}% {bytes:>10} / {total:>10} bytes"
);
} else {
let _ = write!(std::io::stderr(), "\r {:>10} bytes", bytes);
let _ = write!(std::io::stderr(), "\r {bytes:>10} bytes");
}
let _ = std::io::stderr().flush();
});
Expand Down
5 changes: 1 addition & 4 deletions src-tauri/src/diarize/embedder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,7 @@ impl Embedder {
// is correct for both shapes since the leading axes are 1.
let shape = view.shape().to_vec();
if shape.last().copied().unwrap_or(0) == 0 {
return Err(anyhow!(
"embedder produced zero-length output ({:?})",
shape
));
return Err(anyhow!("embedder produced zero-length output ({shape:?})"));
}
let mut out: Vec<f32> = view.iter().copied().collect();
l2_normalize(&mut out);
Expand Down
3 changes: 1 addition & 2 deletions src-tauri/src/diarize/segmenter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,7 @@ impl Segmenter {
let shape = logits.shape().to_vec();
if shape.len() != 3 || shape[0] != 1 || shape[2] != 7 {
return Err(anyhow!(
"unexpected segmenter output shape {:?} (want [1, T, 7])",
shape
"unexpected segmenter output shape {shape:?} (want [1, T, 7])"
));
}
let t_frames = shape[1];
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ fn read_proc_meminfo_total_gb() -> Option<f64> {
}

/// Pulled out for testability. Reads the `MemTotal: NNN kB` line.
#[cfg_attr(not(target_os = "linux"), allow(dead_code))] // Linux detect() + tests only
fn parse_meminfo_total_kb(content: &str) -> Option<u64> {
for line in content.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
Expand Down Expand Up @@ -262,6 +263,7 @@ fn df_k_gb(path: &str) -> Option<f64> {
/// rows; field 4 is `Available` in 1K blocks. Some `df` flavours wrap long
/// device names onto a second line, so the available column may be on the
/// row after the device name. Find the first row with a numeric column 4.
#[cfg_attr(not(target_os = "linux"), allow(dead_code))] // Linux detect() + tests only
fn parse_df_avail_kb(out: &str) -> Option<u64> {
for line in out.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
Expand Down Expand Up @@ -303,6 +305,7 @@ fn detect_soc_label() -> Option<String> {
None
}

#[cfg_attr(not(target_os = "linux"), allow(dead_code))] // Linux detect() + tests only
fn parse_device_tree_model(raw: &[u8]) -> Option<String> {
// Trim trailing NUL bytes the kernel attaches to the device-tree string.
let end = raw.iter().position(|b| *b == 0).unwrap_or(raw.len());
Expand All @@ -313,6 +316,7 @@ fn parse_device_tree_model(raw: &[u8]) -> Option<String> {
Some(s.to_string())
}

#[cfg_attr(not(target_os = "linux"), allow(dead_code))] // Linux detect() + tests only
fn parse_cpuinfo_model(content: &str) -> Option<String> {
// ARM kernels emit `Model : Raspberry Pi 5 Model B Rev 1.0` and/or
// `Hardware : BCM2712`. Prefer the human-friendly Model line.
Expand Down
23 changes: 22 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,25 @@ async fn ollama_list_models() -> Result<Vec<ollama::ModelInfo>, String> {
ollama::list_models().await.map_err(|e| e.to_string())
}

/// True when `model` is already resident in Ollama's memory, so the
/// next chat won't cold-load. The chat UI uses this to decide whether
/// to paint the load dialog *before* firing the request (a cold load
/// can thrash the machine hard enough that a delayed dialog never
/// renders). Best-effort: returns false if Ollama can't be reached.
#[tauri::command]
async fn ollama_model_loaded(model: String) -> bool {
ollama::is_model_loaded(&model).await
}

/// Proactively load `model` into memory at the throttled server's low
/// priority, so the one-time cold load happens at a predictable moment
/// (e.g. just after launch) instead of freezing the user mid-chat.
#[tauri::command]
async fn ollama_warm(model: String) -> Result<(), String> {
ollama::ensure_running().await.map_err(|e| e.to_string())?;
ollama::warm(&model).await.map_err(|e| e.to_string())
}

#[tauri::command]
async fn ollama_delete_model(name: String) -> Result<(), String> {
ollama::delete_model(&name).await.map_err(|e| e.to_string())
Expand Down Expand Up @@ -632,7 +651,7 @@ fn mesh_file_save_at(path: String, bytes_b64: String) -> Result<(), String> {
}
let target = std::path::PathBuf::from(&path);
if target.is_dir() {
return Err(format!("target {} is a directory", path));
return Err(format!("target {path} is a directory"));
}
// Best-effort: make sure the parent directory exists. The
// save-dialog typically lands the user inside an existing folder,
Expand Down Expand Up @@ -1005,6 +1024,8 @@ fn main() {
ollama_install,
ollama_stop,
ollama_list_models,
ollama_model_loaded,
ollama_warm,
ollama_delete_model,
preload_modes,
ensure_tracked_models,
Expand Down
99 changes: 69 additions & 30 deletions src-tauri/src/mesh/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,25 +532,28 @@ impl Drop for DaemonChild {
/// dev cycle rewrites with fresh content instead of perpetually
/// staging corrupt bits. Files at paths we DON'T own (PATH
/// lookup, env-var override) just get skipped without deletion.
fn looks_like_executable(path: &Path) -> bool {
/// Validate a candidate daemon binary. `Ok(())` when usable, else
/// `Err(reason)` describing why it was rejected. Performs the self-heal
/// removal of a stale *owned* slot, but does NOT log — callers collect
/// the reasons and surface them only when the whole search fails, so a
/// successful daemon launch stays quiet (see `ensure_daemon_running`).
fn check_executable(path: &Path) -> Result<(), String> {
match validate_path_is_executable(path) {
Ok(()) => true,
Ok(()) => Ok(()),
Err(reason) => {
// Self-heal: if this is a known-owned slot and the
// content is invalid, delete it. Tauri's externalBin
// staging will regenerate from the source on the
// next build; the source's own validator catches
// problems before propagating them.
// Self-heal: if this is a known-owned slot and the content is
// invalid, delete it. Tauri's externalBin staging regenerates
// it from the source on the next build; the source's own
// validator catches problems before propagating them.
if is_owned_slot(path) {
eprintln!(
"daemon: {} failed executable check ({reason}); removing stale file",
path.display()
);
let _ = std::fs::remove_file(path);
Err(format!(
"{} failed executable check ({reason}); removed stale file",
path.display()
))
} else {
eprintln!("daemon: skipping {} ({reason})", path.display());
Err(format!("skipping {} ({reason})", path.display()))
}
false
}
}
}
Expand All @@ -576,7 +579,7 @@ fn validate_path_is_executable(path: &Path) -> Result<(), String> {
0xFEED_FACE | 0xFEED_FACF | 0xCAFE_BABE | 0xBEBA_FECA
);
if !(pe || elf || macho) {
return Err(format!("bad magic {:02x?}", head));
return Err(format!("bad magic {head:02x?}"));
}
if pe {
f.seek(SeekFrom::Start(0x3C))
Expand Down Expand Up @@ -623,6 +626,14 @@ fn is_owned_slot(path: &Path) -> bool {
}

pub fn daemon_binary_candidates() -> Vec<PathBuf> {
daemon_binary_candidates_diag().0
}

/// Like [`daemon_binary_candidates`] but also returns the human-readable
/// reason each rejected path was skipped. `ensure_daemon_running` holds
/// these and only prints them if the whole search fails, so a successful
/// launch doesn't spam the log with every probed-and-skipped location.
fn daemon_binary_candidates_diag() -> (Vec<PathBuf>, Vec<String>) {
let exe = if cfg!(windows) {
"myownmesh.exe"
} else {
Expand All @@ -633,23 +644,25 @@ pub fn daemon_binary_candidates() -> Vec<PathBuf> {
// sidecars next to the dev exe; `tauri build` strips it.
// Checking both covers dev + production from one runtime path.
let exe_with_triple = if cfg!(windows) {
format!("myownmesh-{}.exe", DAEMON_SIDECAR_TRIPLE)
format!("myownmesh-{DAEMON_SIDECAR_TRIPLE}.exe")
} else {
format!("myownmesh-{}", DAEMON_SIDECAR_TRIPLE)
format!("myownmesh-{DAEMON_SIDECAR_TRIPLE}")
};
let mut out: Vec<PathBuf> = Vec::new();
let mut diags: Vec<String> = Vec::new();

// Helper: push a candidate iff it exists AND looks like a
// real executable (filters out the zero-byte stub
// `build.rs` writes when the daemon fetch was skipped, AND
// filters out corrupt / truncated downloads that would
// otherwise produce a confusing "%1 is not a valid Win32
// application" error when we try to spawn them).
fn push_if_usable(out: &mut Vec<PathBuf>, p: PathBuf) {
if !looks_like_executable(&p) {
return;
// application" error when we try to spawn them). Rejection
// reasons are collected into `diags` rather than logged here.
fn push_if_usable(out: &mut Vec<PathBuf>, diags: &mut Vec<String>, p: PathBuf) {
match check_executable(&p) {
Ok(()) => out.push(p),
Err(reason) => diags.push(reason),
}
out.push(p);
}

// 1. Bundled sidecar next to the running LLM executable —
Expand All @@ -659,8 +672,8 @@ pub fn daemon_binary_candidates() -> Vec<PathBuf> {
// via the same code path.
if let Ok(exe_path) = std::env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
push_if_usable(&mut out, exe_dir.join(exe));
push_if_usable(&mut out, exe_dir.join(&exe_with_triple));
push_if_usable(&mut out, &mut diags, exe_dir.join(exe));
push_if_usable(&mut out, &mut diags, exe_dir.join(&exe_with_triple));
}
}

Expand All @@ -670,7 +683,11 @@ pub fn daemon_binary_candidates() -> Vec<PathBuf> {
// the *only* place the binary lives. Relative to the
// crate, so it works from any working directory.
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
push_if_usable(&mut out, manifest.join("binaries").join(&exe_with_triple));
push_if_usable(
&mut out,
&mut diags,
manifest.join("binaries").join(&exe_with_triple),
);

// 2 + 3. Explicit env-var overrides.
for var in ["MYOWNLLM_MESH_BIN", "MYOWNMESH_BIN"] {
Expand All @@ -696,12 +713,21 @@ pub fn daemon_binary_candidates() -> Vec<PathBuf> {
// `manifest` already declared above for the `binaries/`
// lookup; reuse it here.
for profile in ["debug", "release"] {
push_if_usable(&mut out, manifest.join("target").join(profile).join(exe));
push_if_usable(
&mut out,
&mut diags,
manifest.join("target").join(profile).join(exe),
);
if let Some(parent) = manifest.parent() {
push_if_usable(&mut out, parent.join("target").join(profile).join(exe));
push_if_usable(
&mut out,
&mut diags,
parent.join("target").join(profile).join(exe),
);
if let Some(grandparent) = parent.parent() {
push_if_usable(
&mut out,
&mut diags,
grandparent
.join("MyOwnMesh")
.join("target")
Expand All @@ -719,7 +745,7 @@ pub fn daemon_binary_candidates() -> Vec<PathBuf> {
let canonical = std::fs::canonicalize(p).unwrap_or_else(|_| p.clone());
seen.insert(canonical)
});
out
(out, diags)
}

/// Legacy single-binary lookup. Returns the highest-priority
Expand Down Expand Up @@ -786,8 +812,15 @@ pub async fn ensure_daemon_running() -> Result<(ControlClient, Option<DaemonChil
// skipped in favour of a working binary at the next. The
// diag log surfaces each attempt so a user with multiple
// stale candidates can see which one we picked.
let candidates = daemon_binary_candidates();
// `skip_diags` records every probed-but-rejected location. We stay
// quiet about them on the happy path and only surface them if the
// search ultimately fails, so a normal launch doesn't log a wall of
// "skipping ..." lines for paths that simply don't apply here.
let (candidates, skip_diags) = daemon_binary_candidates_diag();
if candidates.is_empty() {
for d in &skip_diags {
eprintln!("daemon: {d}");
}
return Err(anyhow!(
"couldn't find a `myownmesh` binary. Re-run the LLM build with network \
access so `build.rs` can fetch the daemon, set MYOWNLLM_MESH_BIN to a \
Expand Down Expand Up @@ -870,8 +903,14 @@ pub async fn ensure_daemon_running() -> Result<(ControlClient, Option<DaemonChil
drop(handle);
}

// Every candidate failed. Surface a diag listing all of them
// so the user can see what got tried — beats "couldn't find".
// Every candidate failed. Now that the search has definitively
// failed, surface the locations we skipped during enumeration too —
// they're part of the picture the user needs to debug it.
for d in &skip_diags {
eprintln!("daemon: {d}");
}
// Surface a diag listing everything tried so the user can see what
// got attempted — beats a bare "couldn't find".
let tried: Vec<String> = candidates.iter().map(|p| p.display().to_string()).collect();
Err(anyhow!(
"no working `myownmesh` binary on this machine. Tried:\n {}\nLast error: {}",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/mesh/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
//! `myownmesh-core` and aren't duplicated here.

pub use myownmesh_core::identity::{
generate_network_id, load_or_create, normalize_network_id, set_label, Identity,
generate_network_id, load_or_create, normalize_network_id, set_label,
};
3 changes: 1 addition & 2 deletions src-tauri/src/mesh/roster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
//! over.

pub use myownmesh_core::roster::{
add_peer, add_peer_in, delete, empty_for, is_authorized, load, remove_peer, remove_peer_in,
save, AuthorizedPeer, Roster, ROSTER_VERSION,
add_peer, delete, load, remove_peer, save, AuthorizedPeer, Roster, ROSTER_VERSION,
};

/// One-shot migration from the pre-multi-network single roster file
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/mesh/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
//! which reads `MYOWNMESH_HOME` — set to `~/.myownllm` in `main.rs`
//! — so the local Device ID is unchanged.

pub use myownmesh_core::signing::{pubkey_part, sign, verify};
pub use myownmesh_core::signing::{sign, verify};
Loading
Loading