mod commands; use std::sync::Arc; use tokio::sync::RwLock; use tauri::{Emitter, Manager}; use xianren_core::CoreApp; use xianren_core::models as models_db; use xianren_core::settings as settings_db; use xianren_engine::EngineManager; use std::path::{Path, PathBuf}; pub struct App { pub core: Arc, pub engine: EngineManager, pub engine_base: Arc>>, pub server: tokio::sync::Mutex>, } pub fn run() { let data_dir = dirs::data_dir() .unwrap_or_else(|| PathBuf::from(".")) .join("XianrenStudio"); let logs_dir = data_dir.join("logs"); let _ = std::fs::create_dir_all(&logs_dir); let file_appender = tracing_appender::rolling::daily(&logs_dir, "app.log"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "info".into()), ) .with_writer(non_blocking) .with_ansi(false) .init(); tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .setup(|app| { let core = CoreApp::init(None) .map_err(|e| -> Box { e.to_string().into() })?; tauri::Manager::manage( app, App { core: Arc::new(core), engine: EngineManager::new(), engine_base: Arc::new(RwLock::new(None)), server: tokio::sync::Mutex::new(None), }, ); // 启动时自动扫描模型目录 let app_state = app.state::(); let db = app_state.core.db.lock().unwrap(); // 校验引擎路径:旧版本可能残留指向无效 exe 的配置(缺 DLL),自动修复 let configured = settings_db::get(&db, "engine_bin").ok().flatten(); let configured_ok = configured .as_ref() .map(|p| engine_binary_usable(PathBuf::from(p).as_path())) .unwrap_or(false); if !configured_ok { let mut found: Option = None; for dir in ["cpu", "vulkan", "cuda"] { let candidate = app_state.core.engines_dir.join(dir).join("llama-server.exe"); if engine_binary_usable(&candidate) { found = Some(candidate); break; } } if found.is_none() { let candidate = app_state.core.engines_dir.join("llama-server-cpu.exe"); if engine_binary_usable(&candidate) { found = Some(candidate); } } if let Some(path) = found { let _ = settings_db::set(&db, "engine_bin", &path.to_string_lossy()); tracing::info!(path = %path.display(), "engine binary auto-fixed"); } else { tracing::warn!("no usable llama-server binary found"); } } let model_dir = settings_db::get(&db, "model_dir") .ok() .flatten() .map(PathBuf::from) .unwrap_or_else(|| app_state.core.models_dir.clone()); match models_db::scan_directory(&db, &model_dir) { Ok(result) => { tracing::info!( added = result.added, updated = result.updated, missing = result.missing, "auto scan complete" ); } Err(e) => { tracing::warn!(error = %e, "auto scan failed"); } } let _ = app.emit("models://updated", ()); Ok(()) }) .invoke_handler(tauri::generate_handler![ commands::app_info, commands::list_models, commands::import_model, commands::remove_model, commands::scan_models, commands::add_remote_model, commands::search_models, commands::list_model_files, commands::settings_get, commands::settings_set, commands::list_conversations, commands::create_conversation, commands::delete_conversation, commands::get_messages, commands::engine_start, commands::engine_stop, commands::engine_status, commands::deploy_model, commands::chat_send, commands::regenerate_message, commands::edit_message, commands::list_message_versions, commands::apply_message_version, commands::report_error, commands::download_enqueue, commands::server_start, commands::server_stop, commands::server_status, commands::open_path, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } /// 判断引擎二进制是否可用:文件存在,且同目录包含 DLL(llama.cpp 发布版依赖同目录 DLL)。 fn engine_binary_usable(path: &Path) -> bool { if !path.is_file() { return false; } let Some(parent) = path.parent() else { return false; }; std::fs::read_dir(parent) .map(|entries| { entries.flatten().any(|entry| { entry .path() .extension() .map(|ext| ext.eq_ignore_ascii_case("dll")) .unwrap_or(false) }) }) .unwrap_or(false) }