fix: auto-discover localllm binary in target/release and PATH

This commit is contained in:
Evandro Fonseca Junior committed 2026-03-26 21:48:51 -03:00
1 parent 2cac674cba
commit f5a4a9ee97
1 file changed
+71 -4
+71 -4
View File
@@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
use std::sync::Mutex;
use tauri::{AppHandle, Manager, State};
@@ -47,6 +47,56 @@ fn query_gpu() -> Result<GpuInfo, String> {
// ─── Server lifecycle ─────────────────────────────────────────────────────────
/// Locate the `localllm` binary by searching several well-known locations:
/// 1. Workspace `target/release/` relative to the Tauri app's source tree
/// 2. Next to the running executable (for installed/bundled builds)
/// 3. System PATH
fn find_localllm_binary() -> Option<PathBuf> {
let exe_name = if cfg!(target_os = "windows") { "localllm.exe" } else { "localllm" };
// 1. Workspace target/release — works when running `npm run tauri dev` from tauri-app/
// The Tauri dev binary lives at tauri-app/src-tauri/target/debug/forgelm-chat.exe
// so we walk up to find the workspace root and look for target/release/localllm.exe
if let Ok(exe) = std::env::current_exe() {
// Walk up from the current exe looking for target/release/localllm.exe
let mut dir = exe.parent();
while let Some(d) = dir {
let candidate = d.join("target").join("release").join(exe_name);
if candidate.exists() {
return Some(candidate);
}
// Also check sibling: if we're inside target/debug, go up two levels
let candidate2 = d.join(exe_name);
if candidate2.exists() {
return Some(candidate2);
}
dir = d.parent();
}
}
// 2. Relative to CWD (useful when launched from repo root)
let cwd_candidate = PathBuf::from("target").join("release").join(exe_name);
if cwd_candidate.exists() {
return Some(cwd_candidate);
}
// 3. System PATH
if let Ok(output) = Command::new(if cfg!(target_os = "windows") { "where" } else { "which" })
.arg("localllm")
.output()
{
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout);
let first_line = path_str.lines().next().unwrap_or("").trim();
if !first_line.is_empty() {
return Some(PathBuf::from(first_line));
}
}
}
None
}
#[tauri::command]
fn start_server(
state: State<ServerState>,
@@ -60,15 +110,20 @@ fn start_server(
return Err("Server is already running".into());
}
let binary = if cfg!(target_os = "windows") { "localllm.exe" } else { "localllm" };
let binary_path = find_localllm_binary()
.ok_or_else(|| {
"localllm binary not found. Build it with: cargo build --release -p inference-server\n\
Then ensure the binary is in one of: target/release/, PATH, or next to this app."
.to_string()
})?;
let child = Command::new(binary)
let child = Command::new(&binary_path)
.env("LOCALLLM_MODEL", &model_path)
.env("LOCALLLM_ADDR", &addr)
.env("LOCALLLM_GPU_LAYERS", gpu_layers.to_string())
.env("LOCALLLM_CTX", ctx_size.to_string())
.spawn()
.map_err(|e| format!("Failed to start server: {e}"))?;
.map_err(|e| format!("Failed to start server ({}): {e}", binary_path.display()))?;
let pid = child.id();
*guard = Some(child);
@@ -275,6 +330,17 @@ fn load_config(app: AppHandle) -> Result<AppConfig, String> {
serde_json::from_str(&json).map_err(|e| format!("Parse error: {e}"))
}
/// Returns the resolved path to the localllm binary (useful for debugging).
#[tauri::command]
fn find_server_binary() -> Result<String, String> {
find_localllm_binary()
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
"localllm binary not found. Build with: cargo build --release -p inference-server"
.to_string()
})
}
/// Returns the app data directory path (useful for debugging).
#[tauri::command]
fn get_app_data_dir(app: AppHandle) -> Result<String, String> {
@@ -304,6 +370,7 @@ pub fn run() {
save_config,
load_config,
get_app_data_dir,
find_server_binary,
])
.setup(|app| {
#[cfg(debug_assertions)]