feat: drag-and-drop model import and context size configuration
This commit is contained in:
1 parent
18d8b03074
commit
d1d77b83ef
4 files changed
+306
-33
No files matched your search
@@ -6,8 +6,6 @@ use tauri::{Manager, State};
|
||||
pub struct ServerState(pub Mutex<Option<Child>>);
|
||||
|
||||
/// Query GPU info via nvidia-smi.
|
||||
/// Returns (gpu_name, vram_mib, recommended_layers).
|
||||
/// recommended_layers is estimated as vram_mib / 80 (rough heuristic for 4-bit quant).
|
||||
#[tauri::command]
|
||||
fn query_gpu() -> Result<GpuInfo, String> {
|
||||
let output = Command::new("nvidia-smi")
|
||||
@@ -28,7 +26,6 @@ fn query_gpu() -> Result<GpuInfo, String> {
|
||||
return Err("No GPU found".into());
|
||||
}
|
||||
|
||||
// Format: "GPU Name, 8192"
|
||||
let parts: Vec<&str> = line.splitn(2, ',').collect();
|
||||
if parts.len() < 2 {
|
||||
return Err(format!("Unexpected nvidia-smi output: {line}"));
|
||||
@@ -36,9 +33,6 @@ fn query_gpu() -> Result<GpuInfo, String> {
|
||||
|
||||
let name = parts[0].trim().to_string();
|
||||
let vram_mib: u32 = parts[1].trim().parse().unwrap_or(0);
|
||||
|
||||
// Heuristic: ~80 MiB per layer for Q4/Q6 models (conservative)
|
||||
// Reserve 1 GiB for OS/other usage
|
||||
let usable = vram_mib.saturating_sub(1024);
|
||||
let recommended_layers = (usable / 80).min(200) as i32;
|
||||
|
||||
@@ -56,12 +50,14 @@ pub struct GpuInfo {
|
||||
pub recommended_layers: i32,
|
||||
}
|
||||
|
||||
/// Start the inference server with full configuration.
|
||||
#[tauri::command]
|
||||
fn start_server(
|
||||
state: State<ServerState>,
|
||||
model_path: String,
|
||||
addr: String,
|
||||
gpu_layers: i32,
|
||||
ctx_size: u32,
|
||||
) -> Result<String, String> {
|
||||
let mut guard = state.0.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -79,6 +75,7 @@ fn start_server(
|
||||
.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}"))?;
|
||||
|
||||
@@ -116,7 +113,6 @@ fn server_status(state: State<ServerState>) -> String {
|
||||
}
|
||||
|
||||
/// Scan a directory for .gguf model files.
|
||||
/// Returns a list of { name, path } objects.
|
||||
#[tauri::command]
|
||||
fn scan_models(dir: String) -> Result<Vec<ModelFile>, String> {
|
||||
let path = Path::new(&dir);
|
||||
@@ -154,6 +150,51 @@ fn scan_models(dir: String) -> Result<Vec<ModelFile>, String> {
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
/// Copy a .gguf file into the models directory.
|
||||
/// Returns the new ModelFile on success.
|
||||
#[tauri::command]
|
||||
fn import_model(src_path: String, dest_dir: String) -> Result<ModelFile, String> {
|
||||
let src = Path::new(&src_path);
|
||||
|
||||
// Validate source
|
||||
if !src.exists() {
|
||||
return Err(format!("Source file not found: {src_path}"));
|
||||
}
|
||||
if !src.is_file() {
|
||||
return Err("Source is not a file".into());
|
||||
}
|
||||
let ext = src.extension().unwrap_or_default();
|
||||
if !ext.eq_ignore_ascii_case("gguf") {
|
||||
return Err("Only .gguf files are supported".into());
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
let dest_dir_path = Path::new(&dest_dir);
|
||||
if !dest_dir_path.exists() {
|
||||
std::fs::create_dir_all(dest_dir_path)
|
||||
.map_err(|e| format!("Failed to create models directory: {e}"))?;
|
||||
}
|
||||
|
||||
let file_name = src.file_name().unwrap_or_default();
|
||||
let dest_path = dest_dir_path.join(file_name);
|
||||
|
||||
// If already in the same directory, just return it
|
||||
if src == dest_path {
|
||||
return Ok(ModelFile {
|
||||
name: file_name.to_string_lossy().to_string(),
|
||||
path: dest_path.to_string_lossy().to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Copy the file
|
||||
std::fs::copy(src, &dest_path).map_err(|e| format!("Failed to copy model: {e}"))?;
|
||||
|
||||
Ok(ModelFile {
|
||||
name: file_name.to_string_lossy().to_string(),
|
||||
path: dest_path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
pub struct ModelFile {
|
||||
pub name: String,
|
||||
@@ -168,6 +209,7 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
query_gpu,
|
||||
scan_models,
|
||||
import_model,
|
||||
start_server,
|
||||
stop_server,
|
||||
server_status
|
||||
|
||||
Reference in new issue
Block a user