diff --git a/extensions/inference-cortex-extension/src/index.ts b/extensions/inference-cortex-extension/src/index.ts index d44e1e063..09de67076 100644 --- a/extensions/inference-cortex-extension/src/index.ts +++ b/extensions/inference-cortex-extension/src/index.ts @@ -129,9 +129,6 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { ) if (!Number.isNaN(threads_number)) this.cpu_threads = threads_number - // Run the process watchdog - // const systemInfo = await systemInformation() - await executeOnMain(NODE, 'run') this.subscribeToEvents() window.addEventListener('beforeunload', () => { @@ -143,7 +140,6 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { console.log('Clean up cortex.cpp services') this.shouldReconnect = false this.clean() - // await executeOnMain(NODE, 'dispose') super.onUnload() } @@ -222,24 +218,6 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { ) } - /** - * Do health check on cortex.cpp - * @returns - */ - private async healthz(): Promise { - return this.apiInstance().then((api) => - api - .get('healthz', { - retry: { - limit: 20, - delay: () => 500, - methods: ['get'], - }, - }) - .then(() => {}) - ) - } - /** * Clean cortex processes * @returns diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore index 502406b4e..043572272 100644 --- a/src-tauri/.gitignore +++ b/src-tauri/.gitignore @@ -2,3 +2,5 @@ # will have compiled files and executables /target/ /gen/schemas +binaries +!binaries/download.sh \ No newline at end of file diff --git a/src-tauri/src/handlers/cmd.rs b/src-tauri/src/core/cmd.rs similarity index 56% rename from src-tauri/src/handlers/cmd.rs rename to src-tauri/src/core/cmd.rs index c8bd7aaf0..ff5a319bd 100644 --- a/src-tauri/src/handlers/cmd.rs +++ b/src-tauri/src/core/cmd.rs @@ -1,9 +1,4 @@ -use flate2::read::GzDecoder; -use serde_json::Value; use std::fs; -use std::fs::File; -use std::io::Read; -use tar::Archive; use tauri::AppHandle; use serde::{Deserialize, Serialize}; @@ -231,129 +226,4 @@ pub fn get_user_home_path(app: AppHandle) -> String { return get_app_configurations(app.clone()).data_folder; } -fn extract_extension_manifest(archive: &mut Archive) -> Result, String> { - let entry = archive - .entries() - .map_err(|e| e.to_string())? - .filter_map(|e| e.ok()) // Ignore errors in individual entries - .find(|entry| { - if let Ok(file_path) = entry.path() { - let path_str = file_path.to_string_lossy(); - path_str == "package/package.json" || path_str == "package.json" - } else { - false - } - }); - if let Some(mut entry) = entry { - let mut content = String::new(); - entry - .read_to_string(&mut content) - .map_err(|e| e.to_string())?; - - let package_json: Value = serde_json::from_str(&content).map_err(|e| e.to_string())?; - return Ok(Some(package_json)); - } - - Ok(None) -} - -pub fn install_extensions(app: tauri::AppHandle) -> Result<(), String> { - let extensions_path = get_jan_extensions_path(app.clone()); - let pre_install_path = PathBuf::from("./../pre-install"); - - if !extensions_path.exists() { - fs::create_dir_all(&extensions_path).map_err(|e| e.to_string())?; - } - - let extensions_json_path = extensions_path.join("extensions.json"); - let mut extensions_list = if extensions_json_path.exists() { - let existing_data = - fs::read_to_string(&extensions_json_path).unwrap_or_else(|_| "[]".to_string()); - serde_json::from_str::>(&existing_data).unwrap_or_else(|_| vec![]) - } else { - vec![] - }; - - for entry in fs::read_dir(&pre_install_path).map_err(|e| e.to_string())? { - let entry = entry.map_err(|e| e.to_string())?; - let path = entry.path(); - - if path.extension().map_or(false, |ext| ext == "tgz") { - println!("Installing extension from {:?}", path); - let tar_gz = File::open(&path).map_err(|e| e.to_string())?; - let gz_decoder = GzDecoder::new(tar_gz); - let mut archive = Archive::new(gz_decoder); - - let mut extension_name = None; - let mut extension_manifest = None; - extract_extension_manifest(&mut archive) - .map_err(|e| e.to_string()) - .and_then(|manifest| match manifest { - Some(manifest) => { - extension_name = manifest["name"].as_str().map(|s| s.to_string()); - extension_manifest = Some(manifest); - Ok(()) - } - None => Err("Manifest is None".to_string()), - })?; - - let extension_name = extension_name.ok_or("package.json not found in archive")?; - let extension_dir = extensions_path.join(extension_name.clone()); - fs::create_dir_all(&extension_dir).map_err(|e| e.to_string())?; - - let tar_gz = File::open(&path).map_err(|e| e.to_string())?; - let gz_decoder = GzDecoder::new(tar_gz); - let mut archive = Archive::new(gz_decoder); - for entry in archive.entries().map_err(|e| e.to_string())? { - let mut entry = entry.map_err(|e| e.to_string())?; - let file_path = entry.path().map_err(|e| e.to_string())?; - let components: Vec<_> = file_path.components().collect(); - if components.len() > 1 { - let relative_path: PathBuf = components[1..].iter().collect(); - let target_path = extension_dir.join(relative_path); - if let Some(parent) = target_path.parent() { - fs::create_dir_all(parent).map_err(|e| e.to_string())?; - } - let _result = entry.unpack(&target_path).map_err(|e| e.to_string())?; - } - } - - let main_entry = extension_manifest - .as_ref() - .and_then(|manifest| manifest["main"].as_str()) - .unwrap_or("index.js"); - let url = extension_dir.join(main_entry).to_string_lossy().to_string(); - - let new_extension = serde_json::json!({ - "url": url, - "name": extension_name.clone(), - "origin": extension_dir.to_string_lossy(), - "active": true, - "description": extension_manifest - .as_ref() - .and_then(|manifest| manifest["description"].as_str()) - .unwrap_or(""), - "version": extension_manifest - .as_ref() - .and_then(|manifest| manifest["version"].as_str()) - .unwrap_or(""), - "productName": extension_manifest - .as_ref() - .and_then(|manifest| manifest["productName"].as_str()) - .unwrap_or(""), - }); - - extensions_list.push(new_extension); - - println!("Installed extension to {:?}", extension_dir); - } - } - fs::write( - &extensions_json_path, - serde_json::to_string_pretty(&extensions_list).map_err(|e| e.to_string())?, - ) - .map_err(|e| e.to_string())?; - - Ok(()) -} diff --git a/src-tauri/src/handlers/fs.rs b/src-tauri/src/core/fs.rs similarity index 98% rename from src-tauri/src/handlers/fs.rs rename to src-tauri/src/core/fs.rs index 9b841474c..c8aed9846 100644 --- a/src-tauri/src/handlers/fs.rs +++ b/src-tauri/src/core/fs.rs @@ -1,4 +1,4 @@ -use crate::handlers::cmd::get_jan_data_folder_path; +use crate::core::cmd::get_jan_data_folder_path; use std::fs; use std::path::PathBuf; diff --git a/src-tauri/src/handlers/mod.rs b/src-tauri/src/core/mod.rs similarity index 64% rename from src-tauri/src/handlers/mod.rs rename to src-tauri/src/core/mod.rs index 132afdf66..f9510420d 100644 --- a/src-tauri/src/handlers/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -1,2 +1,3 @@ pub mod cmd; pub mod fs; +pub mod setup; \ No newline at end of file diff --git a/src-tauri/src/core/setup.rs b/src-tauri/src/core/setup.rs new file mode 100644 index 000000000..e8fbb8d73 --- /dev/null +++ b/src-tauri/src/core/setup.rs @@ -0,0 +1,244 @@ +use flate2::read::GzDecoder; +use tauri::{App, Manager}; +use tauri_plugin_shell::process::CommandEvent; +use tauri_plugin_shell::ShellExt; +use std::fs::{self, File}; +use std::io::Read; +use std::path::PathBuf; +use tar::Archive; + +use crate::AppState; + +use super::cmd::get_jan_extensions_path; + +pub fn install_extensions(app: tauri::AppHandle) -> Result<(), String> { + let extensions_path = get_jan_extensions_path(app.clone()); + let pre_install_path = PathBuf::from("./../pre-install"); + + if !extensions_path.exists() { + fs::create_dir_all(&extensions_path).map_err(|e| e.to_string())?; + } + + let extensions_json_path = extensions_path.join("extensions.json"); + let mut extensions_list = if extensions_json_path.exists() { + let existing_data = + fs::read_to_string(&extensions_json_path).unwrap_or_else(|_| "[]".to_string()); + serde_json::from_str::>(&existing_data).unwrap_or_else(|_| vec![]) + } else { + vec![] + }; + + for entry in fs::read_dir(&pre_install_path).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + + if path.extension().map_or(false, |ext| ext == "tgz") { + println!("Installing extension from {:?}", path); + let tar_gz = File::open(&path).map_err(|e| e.to_string())?; + let gz_decoder = GzDecoder::new(tar_gz); + let mut archive = Archive::new(gz_decoder); + + let mut extension_name = None; + let mut extension_manifest = None; + extract_extension_manifest(&mut archive) + .map_err(|e| e.to_string()) + .and_then(|manifest| match manifest { + Some(manifest) => { + extension_name = manifest["name"].as_str().map(|s| s.to_string()); + extension_manifest = Some(manifest); + Ok(()) + } + None => Err("Manifest is None".to_string()), + })?; + + let extension_name = extension_name.ok_or("package.json not found in archive")?; + let extension_dir = extensions_path.join(extension_name.clone()); + fs::create_dir_all(&extension_dir).map_err(|e| e.to_string())?; + + let tar_gz = File::open(&path).map_err(|e| e.to_string())?; + let gz_decoder = GzDecoder::new(tar_gz); + let mut archive = Archive::new(gz_decoder); + for entry in archive.entries().map_err(|e| e.to_string())? { + let mut entry = entry.map_err(|e| e.to_string())?; + let file_path = entry.path().map_err(|e| e.to_string())?; + let components: Vec<_> = file_path.components().collect(); + if components.len() > 1 { + let relative_path: PathBuf = components[1..].iter().collect(); + let target_path = extension_dir.join(relative_path); + if let Some(parent) = target_path.parent() { + fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + let _result = entry.unpack(&target_path).map_err(|e| e.to_string())?; + } + } + + let main_entry = extension_manifest + .as_ref() + .and_then(|manifest| manifest["main"].as_str()) + .unwrap_or("index.js"); + let url = extension_dir.join(main_entry).to_string_lossy().to_string(); + + let new_extension = serde_json::json!({ + "url": url, + "name": extension_name.clone(), + "origin": extension_dir.to_string_lossy(), + "active": true, + "description": extension_manifest + .as_ref() + .and_then(|manifest| manifest["description"].as_str()) + .unwrap_or(""), + "version": extension_manifest + .as_ref() + .and_then(|manifest| manifest["version"].as_str()) + .unwrap_or(""), + "productName": extension_manifest + .as_ref() + .and_then(|manifest| manifest["productName"].as_str()) + .unwrap_or(""), + }); + + extensions_list.push(new_extension); + + println!("Installed extension to {:?}", extension_dir); + } + } + fs::write( + &extensions_json_path, + serde_json::to_string_pretty(&extensions_list).map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + + Ok(()) +} + +fn extract_extension_manifest( + archive: &mut Archive, +) -> Result, String> { + let entry = archive + .entries() + .map_err(|e| e.to_string())? + .filter_map(|e| e.ok()) // Ignore errors in individual entries + .find(|entry| { + if let Ok(file_path) = entry.path() { + let path_str = file_path.to_string_lossy(); + path_str == "package/package.json" || path_str == "package.json" + } else { + false + } + }); + + if let Some(mut entry) = entry { + let mut content = String::new(); + entry + .read_to_string(&mut content) + .map_err(|e| e.to_string())?; + + let package_json: serde_json::Value = + serde_json::from_str(&content).map_err(|e| e.to_string())?; + return Ok(Some(package_json)); + } + + Ok(None) +} + +pub fn setup_sidecar(app: &App) -> Result<(), String> { + // Setup sidecar + + let app_state = app.state::(); + let mut sidecar_command = app.shell().sidecar("cortex-server").unwrap().args([ + "--start-server", + "--port", + "39291", + "--config_file_path", + app.app_handle() + .path() + .app_data_dir() + .unwrap() + .join(".janrc") + .to_str() + .unwrap(), + "--data_folder_path", + app.app_handle() + .path() + .app_data_dir() + .unwrap() + .to_str() + .unwrap(), + "config", + "--api_keys", + app_state.inner().app_token.as_deref().unwrap_or(""), + ]); + + #[cfg(target_os = "windows")] + { + sidecar_command = sidecar_command.env("PATH", { + let app_data_dir = app.app_handle().path().app_data_dir().unwrap(); + let dest = app_data_dir.to_str().unwrap(); + let path = std::env::var("PATH").unwrap_or_default(); + format!("{}{}{}", path, std::path::MAIN_SEPARATOR, dest) + }); + } + + #[cfg(not(target_os = "windows"))] + { + sidecar_command = sidecar_command.env("LD_LIBRARY_PATH", { + let app_data_dir = app.app_handle().path().app_data_dir().unwrap(); + let dest = app_data_dir.to_str().unwrap(); + let ld_library_path = std::env::var("LD_LIBRARY_PATH").unwrap_or_default(); + format!("{}{}{}", ld_library_path, std::path::MAIN_SEPARATOR, dest) + }); + } + + let (mut rx, mut _child) = sidecar_command.spawn().expect("Failed to spawn sidecar"); + tauri::async_runtime::spawn(async move { + // read events such as stdout + while let Some(event) = rx.recv().await { + if let CommandEvent::Stdout(line_bytes) = event { + let line = String::from_utf8_lossy(&line_bytes); + println!("Outputs: {:?}", line) + } + } + }); + Ok(()) +} + + +fn copy_dir_all(src: PathBuf, dst: PathBuf) -> Result<(), String> { + fs::create_dir_all(&dst).map_err(|e| e.to_string())?; + println!("Copying from {:?} to {:?}", src, dst); + for entry in fs::read_dir(src).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let ty = entry.file_type().map_err(|e| e.to_string())?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.join(entry.file_name())).map_err(|e| e.to_string())?; + } else { + fs::copy(entry.path(), dst.join(entry.file_name())).map_err(|e| e.to_string())?; + } + } + Ok(()) +} + +pub fn setup_engine_binaries(app: &App) -> Result<(), String> { + // Copy engine binaries to app_data + let app_data_dir = app.handle().path().app_data_dir().unwrap(); + let binaries_dir = app + .handle() + .path() + .resource_dir() + .unwrap() + .join("binaries"); + let themes_dir = app + .handle() + .path() + .resource_dir() + .unwrap() + .join("resources"); + + if let Err(e) = copy_dir_all(binaries_dir, app_data_dir.clone()) { + eprintln!("Failed to copy binaries: {}", e); + } + if let Err(e) = copy_dir_all(themes_dir, app_data_dir.clone()) { + eprintln!("Failed to copy themes: {}", e); + } + Ok(()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5be78d97c..2afe3397f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,12 +1,8 @@ -use std::fs; -use std::path::PathBuf; +mod core; +use core::setup::{self, setup_engine_binaries, setup_sidecar}; -mod handlers; - -use crate::handlers::cmd; use rand::{distributions::Alphanumeric, Rng}; -use tauri::{command, Manager, State}; -use tauri_plugin_shell::{process::CommandEvent, ShellExt}; +use tauri::{command, State}; struct AppState { app_token: Option, @@ -25,44 +21,29 @@ fn generate_app_token() -> String { .collect() } -fn copy_dir_all(src: PathBuf, dst: PathBuf) -> Result<(), String> { - fs::create_dir_all(&dst).map_err(|e| e.to_string())?; - println!("Copying from {:?} to {:?}", src, dst); - for entry in fs::read_dir(src).map_err(|e| e.to_string())? { - let entry = entry.map_err(|e| e.to_string())?; - let ty = entry.file_type().map_err(|e| e.to_string())?; - if ty.is_dir() { - copy_dir_all(entry.path(), dst.join(entry.file_name())).map_err(|e| e.to_string())?; - } else { - fs::copy(entry.path(), dst.join(entry.file_name())).map_err(|e| e.to_string())?; - } - } - Ok(()) -} - #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_shell::init()) .invoke_handler(tauri::generate_handler![ - handlers::fs::join_path, - handlers::fs::mkdir, - handlers::fs::exists_sync, - handlers::fs::readdir_sync, - handlers::fs::read_file_sync, - handlers::fs::rm, + core::fs::join_path, + core::fs::mkdir, + core::fs::exists_sync, + core::fs::readdir_sync, + core::fs::read_file_sync, + core::fs::rm, // App commands - handlers::cmd::get_themes, - handlers::cmd::get_app_configurations, - handlers::cmd::get_active_extensions, - handlers::cmd::get_user_home_path, - handlers::cmd::update_app_configuration, - handlers::cmd::get_jan_data_folder_path, - handlers::cmd::get_jan_extensions_path, - handlers::cmd::relaunch, - handlers::cmd::open_app_directory, - handlers::cmd::open_file_explorer, + core::cmd::get_themes, + core::cmd::get_app_configurations, + core::cmd::get_active_extensions, + core::cmd::get_user_home_path, + core::cmd::update_app_configuration, + core::cmd::get_jan_data_folder_path, + core::cmd::get_jan_extensions_path, + core::cmd::relaunch, + core::cmd::open_app_directory, + core::cmd::open_file_explorer, app_token, ]) .manage(AppState { @@ -77,59 +58,14 @@ pub fn run() { )?; } - // Setup sidecar - let app_state = app.state::(); - let sidecar_command = app.shell().sidecar("cortex-server").unwrap().args([ - "--start-server", - "--port", - "39291", - "--config_file_path", - app.app_handle() - .path() - .app_data_dir() - .unwrap() - .join(".janrc") - .to_str() - .unwrap(), - "--data_folder_path", - app.app_handle() - .path() - .app_data_dir() - .unwrap() - .to_str() - .unwrap(), - "config", - "--api_keys", - app_state.inner().app_token.as_deref().unwrap_or("") - - ]); - let (mut rx, mut _child) = sidecar_command.spawn().expect("Failed to spawn sidecar"); - tauri::async_runtime::spawn(async move { - // read events such as stdout - while let Some(event) = rx.recv().await { - if let CommandEvent::Stdout(line_bytes) = event { - let line = String::from_utf8_lossy(&line_bytes); - println!("Outputs: {:?}", line) - } - } - }); - // Install extensions - if let Err(e) = cmd::install_extensions(app.handle().clone()) { + if let Err(e) = setup::install_extensions(app.handle().clone()) { eprintln!("Failed to install extensions: {}", e); } - // Copy engine binaries to app_data - let app_data_dir = app.app_handle().path().app_data_dir().unwrap(); - let binaries_dir = app.app_handle().path().resource_dir().unwrap().join("binaries"); - let themes_dir = app.app_handle().path().resource_dir().unwrap().join("resources"); + setup_sidecar(app).expect("Failed to setup sidecar"); - if let Err(e) = copy_dir_all(binaries_dir, app_data_dir.clone()) { - eprintln!("Failed to copy binaries: {}", e); - } - if let Err(e) = copy_dir_all(themes_dir, app_data_dir.clone()) { - eprintln!("Failed to copy themes: {}", e); - } + setup_engine_binaries(app).expect("Failed to setup engine binaries"); Ok(()) })