mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
* chore: add CI secrets management and local testing script - Added ci-secrets.example.json to provide a template for CI secrets configuration. - Introduced test-ci-local.sh script to facilitate local testing of the package-and-publish workflow using act. - Updated .gitignore to exclude the actual ci-secrets.json file containing sensitive tokens. * chore: enhance local testing and environment loading scripts - Added scripts to load environment variables from .env and JSON files, improving local development setup. - Introduced ci-event.json to simulate GitHub event payloads for local CI testing. - Updated test-ci-local.sh to utilize the new event JSON for better integration with local testing workflows. - Modified .gitignore to include ci-secrets.local.json for local secret management. * chore: enhance environment loading and improve V8 memory management - Updated load-env.sh to conditionally load ci-secrets.local.json and set APPLE_PASSWORD for notarization. - Introduced a delay in auto-starting skills to prevent memory spikes during initialization. - Adjusted V8 runtime memory settings to minimize initial heap allocation, reducing the risk of OOM errors. * chore: update version bump workflow to modify tauri.conf.json - Changed the version update process to reflect changes in tauri.conf.json instead of Cargo.toml. - Adjusted the commit message to indicate the inclusion of tauri.conf.json in the version bump process. * chore: migrate from V8 to QuickJS runtime and update related configurations - Replaced V8 runtime references with QuickJS throughout the codebase, including updates to initialization and error handling. - Modified package.json to change the build command for macOS to source environment variables correctly. - Updated Cargo.toml to remove deno_core dependency and include rquickjs with appropriate features. - Cleaned up unused V8-related code and comments in the runtime modules. - Adjusted skill manifest checks to support QuickJS compatibility. * refactor: implement QuickJS runtime and remove V8 references - Replaced V8 engine with QuickJS in the runtime module, updating related imports and initialization logic. - Introduced new `qjs_engine.rs` and `qjs_skill_instance.rs` files to handle QuickJS-specific functionality. - Removed the deprecated `v8_skill_instance.rs` and associated V8-related code. - Updated JavaScript bootstrap code to align with QuickJS operations and APIs. - Adjusted documentation and comments to reflect the transition to QuickJS. * refactor: update IdbStorage to use parking_lot::Mutex for improved concurrency - Changed the connection management in IdbStorage from RwLock to parking_lot::Mutex for better performance. - Updated related methods to reflect the new locking mechanism, enhancing the efficiency of database operations. - Adjusted the IdbOpenResult struct to include serde::Serialize for potential serialization needs. * refactor: update QuickJS skill instance and timer management - Modified memory limit and stack size settings in QjsSkillInstance to be asynchronous, improving performance. - Cleaned up imports in qjs_ops.rs by removing unused dependencies and enhancing function definitions for clarity. - Refactored timer management functions for better readability and efficiency, including renaming and restructuring comments. * chore: update package.json scripts and clean up build.rs - Added a new script for running the Tauri app with environment variable loading. - Simplified the macOS development command to use a dedicated build script. - Removed unnecessary environment variable logging from build.rs to streamline the build process. - Cleaned up commented-out sections in the GitHub Actions workflow for Android packaging.
598 lines
22 KiB
Rust
598 lines
22 KiB
Rust
//! AlphaHuman Desktop Application
|
|
//!
|
|
//! This is the Rust backend for the cross-platform crypto community platform.
|
|
//! It provides:
|
|
//! - System tray with background execution
|
|
//! - Deep link authentication
|
|
//! - Persistent Socket.io connection
|
|
//! - Secure session storage
|
|
//! - Native notifications
|
|
|
|
mod ai;
|
|
mod commands;
|
|
mod models;
|
|
mod runtime;
|
|
mod services;
|
|
mod utils;
|
|
|
|
use ai::*;
|
|
use commands::*;
|
|
use services::socket_service::SOCKET_SERVICE;
|
|
use tauri::{AppHandle, Manager, RunEvent};
|
|
|
|
#[cfg(desktop)]
|
|
use tauri::{
|
|
menu::{Menu, MenuItem},
|
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
|
};
|
|
|
|
#[cfg(any(windows, target_os = "linux"))]
|
|
use tauri_plugin_deep_link::DeepLinkExt;
|
|
|
|
/// Demo command - can be removed in production
|
|
#[tauri::command]
|
|
fn greet(name: &str) -> String {
|
|
format!("Hello, {}! You've been greeted from Rust!", name)
|
|
}
|
|
|
|
// Macro to define common handlers shared across all platforms
|
|
macro_rules! common_handlers {
|
|
() => {
|
|
// Demo
|
|
greet,
|
|
// Auth commands
|
|
get_auth_state,
|
|
get_session_token,
|
|
get_current_user,
|
|
is_authenticated,
|
|
logout,
|
|
store_session,
|
|
// Socket commands
|
|
socket_connect,
|
|
socket_disconnect,
|
|
get_socket_state,
|
|
is_socket_connected,
|
|
report_socket_connected,
|
|
report_socket_disconnected,
|
|
report_socket_error,
|
|
update_socket_status,
|
|
// AI encryption commands
|
|
ai_init_encryption,
|
|
ai_encrypt,
|
|
ai_decrypt,
|
|
// AI memory filesystem commands
|
|
ai_memory_init,
|
|
ai_memory_upsert_file,
|
|
ai_memory_get_file,
|
|
ai_memory_upsert_chunk,
|
|
ai_memory_delete_chunks_by_path,
|
|
ai_memory_fts_search,
|
|
ai_memory_get_chunks,
|
|
ai_memory_get_all_embeddings,
|
|
ai_memory_cache_embedding,
|
|
ai_memory_get_cached_embedding,
|
|
ai_memory_set_meta,
|
|
ai_memory_get_meta,
|
|
// AI session commands
|
|
ai_sessions_init,
|
|
ai_sessions_load_index,
|
|
ai_sessions_update_index,
|
|
ai_sessions_append_transcript,
|
|
ai_sessions_read_transcript,
|
|
ai_sessions_delete,
|
|
ai_sessions_list,
|
|
ai_read_memory_file,
|
|
ai_write_memory_file,
|
|
ai_list_memory_files,
|
|
// Runtime commands
|
|
runtime_discover_skills,
|
|
runtime_list_skills,
|
|
runtime_start_skill,
|
|
runtime_stop_skill,
|
|
runtime_get_skill_state,
|
|
runtime_call_tool,
|
|
runtime_all_tools,
|
|
runtime_broadcast_event,
|
|
// Runtime enable/disable + KV commands
|
|
runtime_enable_skill,
|
|
runtime_disable_skill,
|
|
runtime_is_skill_enabled,
|
|
runtime_get_skill_preferences,
|
|
runtime_skill_kv_get,
|
|
runtime_skill_kv_set,
|
|
// Runtime JSON-RPC + data commands
|
|
runtime_rpc,
|
|
runtime_skill_data_read,
|
|
runtime_skill_data_write,
|
|
runtime_skill_data_dir,
|
|
// Socket.io commands (Rust-native persistent connection)
|
|
runtime_socket_connect,
|
|
runtime_socket_disconnect,
|
|
runtime_socket_state,
|
|
runtime_socket_emit,
|
|
};
|
|
}
|
|
|
|
// Macro to define desktop-only window handlers
|
|
macro_rules! desktop_window_handlers {
|
|
() => {
|
|
show_window,
|
|
hide_window,
|
|
toggle_window,
|
|
is_window_visible,
|
|
minimize_window,
|
|
maximize_window,
|
|
close_window,
|
|
set_window_title,
|
|
};
|
|
}
|
|
|
|
// Helper function to show the window (used by tray and macOS reopen)
|
|
#[cfg(desktop)]
|
|
fn show_main_window(app: &AppHandle) {
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let _ = window.show();
|
|
let _ = window.unminimize();
|
|
let _ = window.set_focus();
|
|
}
|
|
}
|
|
|
|
// Helper function to toggle window visibility
|
|
#[cfg(desktop)]
|
|
fn toggle_main_window_visibility(app: &AppHandle) {
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
match window.is_visible() {
|
|
Ok(true) => {
|
|
let _ = window.hide();
|
|
}
|
|
Ok(false) => {
|
|
show_main_window(app);
|
|
}
|
|
Err(_) => {
|
|
// If we can't determine visibility, try to show it
|
|
show_main_window(app);
|
|
}
|
|
}
|
|
} else {
|
|
eprintln!("Could not find window 'main'");
|
|
}
|
|
}
|
|
|
|
// Setup system tray with menu
|
|
#[cfg(desktop)]
|
|
fn setup_tray(app: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
|
let show_hide_item =
|
|
MenuItem::with_id(app, "show_hide", "Show/Hide Window", true, None::<&str>)?;
|
|
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
|
|
|
let menu = Menu::with_items(app, &[&show_hide_item, &quit_item])?;
|
|
|
|
let _tray = TrayIconBuilder::with_id("main-tray")
|
|
.icon(app.default_window_icon().unwrap().clone())
|
|
.menu(&menu)
|
|
.tooltip("AlphaHuman")
|
|
.on_menu_event(move |app, event| match event.id().as_ref() {
|
|
"show_hide" => {
|
|
toggle_main_window_visibility(app);
|
|
}
|
|
"quit" => {
|
|
// Cleanup before exit - request frontend to disconnect
|
|
let _ = SOCKET_SERVICE.request_disconnect();
|
|
app.exit(0);
|
|
}
|
|
_ => {}
|
|
})
|
|
.on_tray_icon_event(|tray, event| match event {
|
|
TrayIconEvent::Click {
|
|
button: MouseButton::Left,
|
|
button_state: MouseButtonState::Up,
|
|
..
|
|
} => {
|
|
let app = tray.app_handle();
|
|
toggle_main_window_visibility(app);
|
|
}
|
|
TrayIconEvent::DoubleClick {
|
|
button: MouseButton::Left,
|
|
..
|
|
} => {
|
|
let app = tray.app_handle();
|
|
show_main_window(app);
|
|
}
|
|
_ => {}
|
|
})
|
|
.build(app)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
|
|
// Initialize platform-appropriate logger
|
|
#[cfg(target_os = "android")]
|
|
{
|
|
android_logger::init_once(
|
|
android_logger::Config::default()
|
|
.with_max_level(log::LevelFilter::Debug)
|
|
.with_tag("AlphaHuman"),
|
|
);
|
|
}
|
|
#[cfg(not(target_os = "android"))]
|
|
{
|
|
let _ = env_logger::try_init();
|
|
}
|
|
|
|
let mut builder = tauri::Builder::default()
|
|
// Plugins
|
|
.plugin(tauri_plugin_opener::init())
|
|
.plugin(tauri_plugin_deep_link::init())
|
|
.plugin(tauri_plugin_os::init());
|
|
|
|
// Add desktop-only plugins (autostart, notification)
|
|
#[cfg(desktop)]
|
|
{
|
|
builder = builder
|
|
.plugin(tauri_plugin_autostart::init(
|
|
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
|
Some(vec!["--minimized"]),
|
|
))
|
|
.plugin(tauri_plugin_notification::init());
|
|
}
|
|
|
|
builder
|
|
// Setup
|
|
.setup(|app| {
|
|
// Initialize socket service with app handle
|
|
SOCKET_SERVICE.set_app_handle(app.handle().clone());
|
|
|
|
// Register deep link handlers (Windows/Linux)
|
|
#[cfg(any(windows, target_os = "linux"))]
|
|
{
|
|
app.deep_link().register_all()?;
|
|
}
|
|
|
|
// Setup system tray (desktop only)
|
|
#[cfg(desktop)]
|
|
{
|
|
setup_tray(app.handle())?;
|
|
}
|
|
|
|
// macOS-specific: Handle window close event to minimize to tray
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let app_handle = app.handle().clone();
|
|
window.on_window_event(move |event| {
|
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
|
// Prevent the window from closing, hide it instead
|
|
api.prevent_close();
|
|
if let Some(win) = app_handle.get_webview_window("main") {
|
|
let _ = win.hide();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create the SocketManager (persistent Rust-native Socket.io)
|
|
let socket_mgr = std::sync::Arc::new(
|
|
runtime::socket_manager::SocketManager::new(),
|
|
);
|
|
socket_mgr.set_app_handle(app.handle().clone());
|
|
|
|
// Initialize QuickJS Runtime Engine (desktop only - not available on Android/iOS)
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
{
|
|
let data_dir = app
|
|
.path()
|
|
.app_data_dir()
|
|
.unwrap_or_else(|_| {
|
|
// Fallback for platforms where app_data_dir isn't available
|
|
dirs::home_dir()
|
|
.unwrap_or_else(|| std::path::PathBuf::from("."))
|
|
.join(".alphahuman")
|
|
});
|
|
let skills_data_dir = data_dir.join("skills");
|
|
|
|
// Initialize local model service (for skills to use)
|
|
let model_dir = data_dir.join("models");
|
|
services::llama::LLAMA_MANAGER.set_data_dir(model_dir);
|
|
log::info!("[runtime] Local model service initialized");
|
|
|
|
match runtime::qjs_engine::RuntimeEngine::new(skills_data_dir) {
|
|
Ok(engine) => {
|
|
engine.set_app_handle(app.handle().clone());
|
|
|
|
// Set resource directory for bundled skills (production builds)
|
|
if let Ok(resource_dir) = app.path().resource_dir() {
|
|
engine.set_resource_dir(resource_dir);
|
|
}
|
|
|
|
let engine = std::sync::Arc::new(engine);
|
|
|
|
// Wire the SkillRegistry into the SocketManager for MCP
|
|
socket_mgr.set_registry(engine.registry());
|
|
|
|
app.manage(engine.clone());
|
|
|
|
// Start the cron scheduler
|
|
let cron = engine.cron_scheduler();
|
|
tauri::async_runtime::spawn(async move {
|
|
cron.start();
|
|
});
|
|
|
|
// Auto-start skills in background (no delay needed for QuickJS -
|
|
// lightweight contexts don't have V8's memory reservation issue)
|
|
let engine_clone = engine.clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
engine_clone.auto_start_skills().await;
|
|
});
|
|
|
|
log::info!("[runtime] QuickJS runtime engine initialized");
|
|
}
|
|
Err(e) => {
|
|
log::error!("[runtime] Failed to initialize QuickJS runtime: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "android")]
|
|
{
|
|
log::info!("[runtime] QuickJS runtime and local model disabled on Android");
|
|
}
|
|
|
|
#[cfg(target_os = "ios")]
|
|
{
|
|
log::info!("[runtime] QuickJS runtime and local model disabled on iOS");
|
|
}
|
|
|
|
// Store SocketManager as Tauri state
|
|
app.manage(socket_mgr.clone());
|
|
|
|
// Auto-connect socket if there's an existing session
|
|
let socket_mgr_clone = socket_mgr.clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
use commands::auth::SESSION_SERVICE;
|
|
if let Some(token) = SESSION_SERVICE.get_token() {
|
|
let url = utils::config::get_backend_url();
|
|
log::info!("[socket-mgr] Auto-connecting with stored session");
|
|
if let Err(e) = socket_mgr_clone.connect(&url, &token).await {
|
|
log::error!("[socket-mgr] Auto-connect failed: {e}");
|
|
}
|
|
} else {
|
|
log::info!("[socket-mgr] No stored session — waiting for login");
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
})
|
|
// Register all commands
|
|
// Common handlers are defined via macros above, conditionally include desktop window handlers
|
|
.invoke_handler({
|
|
#[cfg(desktop)]
|
|
{
|
|
tauri::generate_handler![
|
|
// Common handlers (expanded from common_handlers! macro)
|
|
greet,
|
|
get_auth_state,
|
|
get_session_token,
|
|
get_current_user,
|
|
is_authenticated,
|
|
logout,
|
|
store_session,
|
|
socket_connect,
|
|
socket_disconnect,
|
|
get_socket_state,
|
|
is_socket_connected,
|
|
report_socket_connected,
|
|
report_socket_disconnected,
|
|
report_socket_error,
|
|
update_socket_status,
|
|
// Desktop-only window handlers (expanded from desktop_window_handlers! macro)
|
|
show_window,
|
|
hide_window,
|
|
toggle_window,
|
|
is_window_visible,
|
|
minimize_window,
|
|
maximize_window,
|
|
close_window,
|
|
set_window_title,
|
|
// AI encryption commands
|
|
ai_init_encryption,
|
|
ai_encrypt,
|
|
ai_decrypt,
|
|
// AI memory filesystem commands
|
|
ai_memory_init,
|
|
ai_memory_upsert_file,
|
|
ai_memory_get_file,
|
|
ai_memory_upsert_chunk,
|
|
ai_memory_delete_chunks_by_path,
|
|
ai_memory_fts_search,
|
|
ai_memory_get_chunks,
|
|
ai_memory_get_all_embeddings,
|
|
ai_memory_cache_embedding,
|
|
ai_memory_get_cached_embedding,
|
|
ai_memory_set_meta,
|
|
ai_memory_get_meta,
|
|
// AI session commands
|
|
ai_sessions_init,
|
|
ai_sessions_load_index,
|
|
ai_sessions_update_index,
|
|
ai_sessions_append_transcript,
|
|
ai_sessions_read_transcript,
|
|
ai_sessions_delete,
|
|
ai_sessions_list,
|
|
ai_read_memory_file,
|
|
ai_write_memory_file,
|
|
ai_list_memory_files,
|
|
// Runtime commands
|
|
runtime_discover_skills,
|
|
runtime_list_skills,
|
|
runtime_start_skill,
|
|
runtime_stop_skill,
|
|
runtime_get_skill_state,
|
|
runtime_call_tool,
|
|
runtime_all_tools,
|
|
runtime_broadcast_event,
|
|
// Runtime enable/disable + KV commands
|
|
runtime_enable_skill,
|
|
runtime_disable_skill,
|
|
runtime_is_skill_enabled,
|
|
runtime_get_skill_preferences,
|
|
runtime_skill_kv_get,
|
|
runtime_skill_kv_set,
|
|
// Runtime JSON-RPC + data commands
|
|
runtime_rpc,
|
|
runtime_skill_data_read,
|
|
runtime_skill_data_write,
|
|
runtime_skill_data_dir,
|
|
// Socket.io commands (Rust-native persistent connection)
|
|
runtime_socket_connect,
|
|
runtime_socket_disconnect,
|
|
runtime_socket_state,
|
|
runtime_socket_emit,
|
|
// TDLib commands (native Telegram library)
|
|
tdlib_create_client,
|
|
tdlib_send,
|
|
tdlib_receive,
|
|
tdlib_destroy,
|
|
tdlib_is_available,
|
|
// Model commands (local LLM)
|
|
model_is_available,
|
|
model_get_status,
|
|
model_ensure_loaded,
|
|
model_generate,
|
|
model_summarize,
|
|
model_unload,
|
|
// Android MediaPipe LLM commands
|
|
model_get_recommended,
|
|
model_list_downloaded,
|
|
model_load_path,
|
|
]
|
|
}
|
|
#[cfg(not(desktop))]
|
|
{
|
|
tauri::generate_handler![
|
|
// Common handlers (expanded from common_handlers! macro)
|
|
greet,
|
|
get_auth_state,
|
|
get_session_token,
|
|
get_current_user,
|
|
is_authenticated,
|
|
logout,
|
|
store_session,
|
|
socket_connect,
|
|
socket_disconnect,
|
|
get_socket_state,
|
|
is_socket_connected,
|
|
report_socket_connected,
|
|
report_socket_disconnected,
|
|
report_socket_error,
|
|
update_socket_status,
|
|
// AI encryption commands
|
|
ai_init_encryption,
|
|
ai_encrypt,
|
|
ai_decrypt,
|
|
// AI memory filesystem commands
|
|
ai_memory_init,
|
|
ai_memory_upsert_file,
|
|
ai_memory_get_file,
|
|
ai_memory_upsert_chunk,
|
|
ai_memory_delete_chunks_by_path,
|
|
ai_memory_fts_search,
|
|
ai_memory_get_chunks,
|
|
ai_memory_get_all_embeddings,
|
|
ai_memory_cache_embedding,
|
|
ai_memory_get_cached_embedding,
|
|
ai_memory_set_meta,
|
|
ai_memory_get_meta,
|
|
// AI session commands
|
|
ai_sessions_init,
|
|
ai_sessions_load_index,
|
|
ai_sessions_update_index,
|
|
ai_sessions_append_transcript,
|
|
ai_sessions_read_transcript,
|
|
ai_sessions_delete,
|
|
ai_sessions_list,
|
|
ai_read_memory_file,
|
|
ai_write_memory_file,
|
|
ai_list_memory_files,
|
|
// Runtime commands
|
|
runtime_discover_skills,
|
|
runtime_list_skills,
|
|
runtime_start_skill,
|
|
runtime_stop_skill,
|
|
runtime_get_skill_state,
|
|
runtime_call_tool,
|
|
runtime_all_tools,
|
|
runtime_broadcast_event,
|
|
// Runtime enable/disable + KV commands
|
|
runtime_enable_skill,
|
|
runtime_disable_skill,
|
|
runtime_is_skill_enabled,
|
|
runtime_get_skill_preferences,
|
|
runtime_skill_kv_get,
|
|
runtime_skill_kv_set,
|
|
// Runtime JSON-RPC + data commands
|
|
runtime_rpc,
|
|
runtime_skill_data_read,
|
|
runtime_skill_data_write,
|
|
runtime_skill_data_dir,
|
|
// Socket.io commands (Rust-native persistent connection)
|
|
runtime_socket_connect,
|
|
runtime_socket_disconnect,
|
|
runtime_socket_state,
|
|
runtime_socket_emit,
|
|
// TDLib commands (native Telegram library)
|
|
tdlib_create_client,
|
|
tdlib_send,
|
|
tdlib_receive,
|
|
tdlib_destroy,
|
|
tdlib_is_available,
|
|
// Model commands (local LLM / MediaPipe)
|
|
model_is_available,
|
|
model_get_status,
|
|
model_ensure_loaded,
|
|
model_generate,
|
|
model_summarize,
|
|
model_unload,
|
|
// Android MediaPipe LLM commands
|
|
model_get_recommended,
|
|
model_list_downloaded,
|
|
model_load_path,
|
|
]
|
|
}
|
|
})
|
|
.build(tauri::generate_context!())
|
|
.expect("error while building tauri application")
|
|
.run(|app_handle, event| {
|
|
match event {
|
|
// Handle macOS Dock icon click (reopen event)
|
|
#[cfg(target_os = "macos")]
|
|
RunEvent::Reopen { .. } => {
|
|
show_main_window(app_handle);
|
|
}
|
|
|
|
// Gracefully shut down TDLib before process exit to prevent
|
|
// use-after-free crash in the blocking receive loop.
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
RunEvent::Exit => {
|
|
log::info!("[app] Exit event received, shutting down TDLib");
|
|
use crate::services::tdlib::TDLIB_MANAGER;
|
|
// Signal the TDLib worker to stop. The blocking receive() call
|
|
// has a 2-second internal timeout, so we must wait long enough
|
|
// for any in-flight call to finish before process teardown runs
|
|
// C++ destructors on TDLib's internal state.
|
|
TDLIB_MANAGER.signal_shutdown();
|
|
std::thread::sleep(std::time::Duration::from_millis(2500));
|
|
log::info!("[app] TDLib shutdown wait complete");
|
|
let _ = app_handle;
|
|
}
|
|
|
|
_ => {
|
|
let _ = app_handle;
|
|
}
|
|
}
|
|
});
|
|
}
|