2025-10-07 09:08:53 -03:00
|
|
|
#![allow(dead_code)]
|
2025-10-26 00:02:19 -03:00
|
|
|
#![cfg_attr(feature = "desktop", windows_subsystem = "windows")]
|
2025-10-28 14:00:52 -03:00
|
|
|
|
2025-10-06 19:12:13 -03:00
|
|
|
use actix_cors::Cors;
|
2025-10-07 07:16:03 -03:00
|
|
|
use actix_web::middleware::Logger;
|
2025-10-06 20:06:43 -03:00
|
|
|
use actix_web::{web, App, HttpServer};
|
2025-10-11 12:29:03 -03:00
|
|
|
use dotenvy::dotenv;
|
2025-10-06 19:12:13 -03:00
|
|
|
use log::info;
|
2025-10-12 20:12:49 -03:00
|
|
|
use std::collections::HashMap;
|
2025-10-11 20:02:14 -03:00
|
|
|
use std::sync::{Arc, Mutex};
|
2025-10-18 19:08:00 -03:00
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
mod auth;
|
|
|
|
|
mod automation;
|
|
|
|
|
mod basic;
|
2025-10-18 19:08:00 -03:00
|
|
|
mod bootstrap;
|
2025-10-06 10:30:17 -03:00
|
|
|
mod bot;
|
|
|
|
|
mod channels;
|
|
|
|
|
mod config;
|
|
|
|
|
mod context;
|
2025-10-18 18:19:08 -03:00
|
|
|
mod drive_monitor;
|
2025-10-07 10:53:09 -03:00
|
|
|
#[cfg(feature = "email")]
|
2025-10-06 10:30:17 -03:00
|
|
|
mod email;
|
2025-10-26 00:02:19 -03:00
|
|
|
#[cfg(feature = "desktop")]
|
|
|
|
|
mod ui;
|
2025-10-06 10:30:17 -03:00
|
|
|
mod file;
|
2025-10-18 18:19:08 -03:00
|
|
|
mod kb;
|
2025-10-06 10:30:17 -03:00
|
|
|
mod llm;
|
2025-10-06 20:06:43 -03:00
|
|
|
mod llm_legacy;
|
2025-10-18 12:01:39 -03:00
|
|
|
mod meet;
|
2025-10-06 10:30:17 -03:00
|
|
|
mod org;
|
2025-10-18 09:26:48 -03:00
|
|
|
mod package_manager;
|
2025-10-06 10:30:17 -03:00
|
|
|
mod session;
|
|
|
|
|
mod shared;
|
|
|
|
|
mod tools;
|
2025-10-18 18:19:08 -03:00
|
|
|
#[cfg(feature = "web_automation")]
|
|
|
|
|
mod web_automation;
|
2025-10-18 12:01:39 -03:00
|
|
|
mod web_server;
|
2025-10-06 10:30:17 -03:00
|
|
|
mod whatsapp;
|
2025-10-30 12:35:25 -03:00
|
|
|
mod create_bucket;
|
2025-10-18 19:08:00 -03:00
|
|
|
|
2025-10-18 12:01:39 -03:00
|
|
|
use crate::auth::auth_handler;
|
2025-10-16 11:43:02 -03:00
|
|
|
use crate::automation::AutomationService;
|
2025-10-18 19:08:00 -03:00
|
|
|
use crate::bootstrap::BootstrapManager;
|
2025-10-19 11:08:23 -03:00
|
|
|
use crate::bot::{start_session, websocket_handler};
|
2025-10-06 20:06:43 -03:00
|
|
|
use crate::channels::{VoiceAdapter, WebChannelAdapter};
|
|
|
|
|
use crate::config::AppConfig;
|
2025-10-07 10:53:09 -03:00
|
|
|
#[cfg(feature = "email")]
|
2025-10-19 11:08:23 -03:00
|
|
|
use crate::email::{
|
|
|
|
|
get_emails, get_latest_email_from, list_emails, save_click, save_draft, send_email,
|
|
|
|
|
};
|
2025-10-15 12:45:15 -03:00
|
|
|
use crate::file::{init_drive, upload_file};
|
2025-10-19 11:08:23 -03:00
|
|
|
use crate::llm_legacy::llm_local::{
|
|
|
|
|
chat_completions_local, embeddings_local, ensure_llama_servers_running,
|
|
|
|
|
};
|
2025-10-18 12:01:39 -03:00
|
|
|
use crate::meet::{voice_start, voice_stop};
|
2025-10-18 19:08:00 -03:00
|
|
|
use crate::package_manager::InstallMode;
|
2025-10-18 12:01:39 -03:00
|
|
|
use crate::session::{create_session, get_session_history, get_sessions};
|
2025-10-11 20:02:14 -03:00
|
|
|
use crate::shared::state::AppState;
|
2025-10-20 23:32:49 -03:00
|
|
|
use crate::web_server::{bot_index, index, static_files};
|
2025-10-18 12:01:39 -03:00
|
|
|
use crate::whatsapp::whatsapp_webhook_verify;
|
2025-10-06 20:06:43 -03:00
|
|
|
use crate::whatsapp::WhatsAppAdapter;
|
2025-10-30 18:48:16 -03:00
|
|
|
use crate::bot::BotOrchestrator;
|
2025-10-06 10:30:17 -03:00
|
|
|
|
2025-10-26 00:02:19 -03:00
|
|
|
#[cfg(not(feature = "desktop"))]
|
2025-10-19 14:02:47 -03:00
|
|
|
#[tokio::main]
|
2025-10-16 11:43:02 -03:00
|
|
|
async fn main() -> std::io::Result<()> {
|
2025-10-30 12:35:25 -03:00
|
|
|
// Test bucket creation
|
|
|
|
|
match create_bucket::create_bucket("test-bucket") {
|
|
|
|
|
Ok(_) => println!("Bucket created successfully"),
|
|
|
|
|
Err(e) => eprintln!("Failed to create bucket: {}", e),
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 09:26:48 -03:00
|
|
|
let args: Vec<String> = std::env::args().collect();
|
|
|
|
|
if args.len() > 1 {
|
|
|
|
|
let command = &args[1];
|
|
|
|
|
match command.as_str() {
|
2025-10-20 23:32:49 -03:00
|
|
|
"install" | "remove" | "list" | "status" | "start" | "stop" | "restart" | "--help"
|
|
|
|
|
| "-h" => match package_manager::cli::run().await {
|
|
|
|
|
Ok(_) => return Ok(()),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!("CLI error: {}", e);
|
|
|
|
|
return Err(std::io::Error::new(
|
|
|
|
|
std::io::ErrorKind::Other,
|
|
|
|
|
format!("CLI command failed: {}", e),
|
|
|
|
|
));
|
2025-10-18 09:26:48 -03:00
|
|
|
}
|
2025-10-20 23:32:49 -03:00
|
|
|
},
|
2025-10-18 09:26:48 -03:00
|
|
|
_ => {
|
|
|
|
|
eprintln!("Unknown command: {}", command);
|
|
|
|
|
eprintln!("Run 'botserver --help' for usage information");
|
2025-10-20 19:49:54 -03:00
|
|
|
return Err(std::io::Error::new(
|
|
|
|
|
std::io::ErrorKind::InvalidInput,
|
|
|
|
|
format!("Unknown command: {}", command),
|
|
|
|
|
));
|
2025-10-18 09:26:48 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 12:35:25 -03:00
|
|
|
// Rest of the original main function remains unchanged...
|
2025-10-24 11:17:22 -03:00
|
|
|
dotenv().ok();
|
2025-10-26 17:13:58 -03:00
|
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
|
2025-10-28 14:00:52 -03:00
|
|
|
.write_style(env_logger::WriteStyle::Always)
|
|
|
|
|
.init();
|
2025-10-19 11:08:23 -03:00
|
|
|
|
2025-10-24 11:17:22 -03:00
|
|
|
let install_mode = if args.contains(&"--container".to_string()) {
|
2025-10-18 19:08:00 -03:00
|
|
|
InstallMode::Container
|
|
|
|
|
} else {
|
|
|
|
|
InstallMode::Local
|
|
|
|
|
};
|
2025-10-19 11:08:23 -03:00
|
|
|
|
2025-10-18 19:08:00 -03:00
|
|
|
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") {
|
|
|
|
|
args.get(idx + 1).cloned()
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2025-10-16 11:43:02 -03:00
|
|
|
|
2025-10-30 12:35:25 -03:00
|
|
|
let mut bootstrap = BootstrapManager::new(install_mode.clone(), tenant.clone()).await;
|
2025-10-28 21:06:31 -03:00
|
|
|
|
|
|
|
|
// Prevent double bootstrap: skip if environment already initialized
|
|
|
|
|
let env_path = std::env::current_dir()?.join("botserver-stack").join(".env");
|
|
|
|
|
let cfg = if env_path.exists() {
|
|
|
|
|
info!("Environment already initialized, skipping bootstrap");
|
|
|
|
|
match diesel::Connection::establish(
|
|
|
|
|
&std::env::var("DATABASE_URL")
|
|
|
|
|
.unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string()),
|
|
|
|
|
) {
|
|
|
|
|
Ok(mut conn) => AppConfig::from_database(&mut conn),
|
|
|
|
|
Err(_) => AppConfig::from_env(),
|
2025-10-16 11:43:02 -03:00
|
|
|
}
|
2025-10-28 21:06:31 -03:00
|
|
|
} else {
|
2025-10-30 12:35:25 -03:00
|
|
|
match bootstrap.bootstrap().await {
|
2025-10-28 21:06:31 -03:00
|
|
|
Ok(config) => {
|
|
|
|
|
info!("Bootstrap completed successfully");
|
|
|
|
|
config
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
log::error!("Bootstrap failed: {}", e);
|
|
|
|
|
match diesel::Connection::establish(
|
|
|
|
|
&std::env::var("DATABASE_URL")
|
|
|
|
|
.unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string()),
|
|
|
|
|
) {
|
|
|
|
|
Ok(mut conn) => AppConfig::from_database(&mut conn),
|
|
|
|
|
Err(_) => AppConfig::from_env(),
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-30 12:35:25 -03:00
|
|
|
// Start all services (synchronous)
|
|
|
|
|
if let Err(e) = bootstrap.start_all() {
|
|
|
|
|
log::warn!("Failed to start all services: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Upload templates (asynchronous)
|
|
|
|
|
if let Err(e) = futures::executor::block_on(bootstrap.upload_templates_to_drive(&cfg)) {
|
2025-10-20 23:32:49 -03:00
|
|
|
log::warn!("Failed to upload templates to MinIO: {}", e);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 20:48:28 -03:00
|
|
|
// Refresh configuration from environment to ensure latest DATABASE_URL and credentials
|
|
|
|
|
dotenv().ok();
|
|
|
|
|
let refreshed_cfg = AppConfig::from_env();
|
|
|
|
|
let config = std::sync::Arc::new(refreshed_cfg.clone());
|
|
|
|
|
let db_pool = match diesel::Connection::establish(&refreshed_cfg.database_url()) {
|
2025-10-18 19:08:00 -03:00
|
|
|
Ok(conn) => Arc::new(Mutex::new(conn)),
|
2025-10-16 11:43:02 -03:00
|
|
|
Err(e) => {
|
|
|
|
|
log::error!("Failed to connect to main database: {}", e);
|
2025-10-20 19:49:54 -03:00
|
|
|
return Err(std::io::Error::new(
|
|
|
|
|
std::io::ErrorKind::ConnectionRefused,
|
|
|
|
|
format!("Database connection failed: {}", e),
|
|
|
|
|
));
|
2025-10-16 11:43:02 -03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let db_custom_pool = db_pool.clone();
|
2025-10-20 19:49:54 -03:00
|
|
|
ensure_llama_servers_running()
|
|
|
|
|
.await
|
|
|
|
|
.expect("Failed to initialize LLM local server");
|
2025-10-20 16:52:08 -03:00
|
|
|
|
2025-10-21 22:43:28 -03:00
|
|
|
let cache_url = std::env::var("CACHE_URL")
|
|
|
|
|
.or_else(|_| std::env::var("REDIS_URL"))
|
|
|
|
|
.unwrap_or_else(|_| "redis://localhost:6379".to_string());
|
2025-10-16 14:22:28 -03:00
|
|
|
let redis_client = match redis::Client::open(cache_url.as_str()) {
|
2025-10-18 19:08:00 -03:00
|
|
|
Ok(client) => Some(Arc::new(client)),
|
2025-10-16 11:43:02 -03:00
|
|
|
Err(e) => {
|
2025-10-21 22:43:28 -03:00
|
|
|
log::warn!("Failed to connect to Redis: Redis URL did not parse- {}", e);
|
2025-10-16 11:43:02 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let tool_manager = Arc::new(tools::ToolManager::new());
|
2025-10-20 19:49:54 -03:00
|
|
|
let llm_provider = Arc::new(crate::llm::OpenAIClient::new(
|
|
|
|
|
"empty".to_string(),
|
2025-11-01 08:43:14 -03:00
|
|
|
Some(cfg.llm.url.clone()),
|
2025-10-20 19:49:54 -03:00
|
|
|
));
|
2025-10-16 11:43:02 -03:00
|
|
|
let web_adapter = Arc::new(WebChannelAdapter::new());
|
2025-10-20 19:49:54 -03:00
|
|
|
let voice_adapter = Arc::new(VoiceAdapter::new(
|
|
|
|
|
"https://livekit.example.com".to_string(),
|
|
|
|
|
"api_key".to_string(),
|
|
|
|
|
"api_secret".to_string(),
|
|
|
|
|
));
|
|
|
|
|
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new(
|
|
|
|
|
"whatsapp_token".to_string(),
|
|
|
|
|
"phone_number_id".to_string(),
|
|
|
|
|
"verify_token".to_string(),
|
|
|
|
|
));
|
2025-10-16 11:43:02 -03:00
|
|
|
let tool_api = Arc::new(tools::ToolApi::new());
|
|
|
|
|
|
2025-10-29 09:54:39 -03:00
|
|
|
let drive = init_drive(&config.drive)
|
2025-10-20 19:49:54 -03:00
|
|
|
.await
|
|
|
|
|
.expect("Failed to initialize Drive");
|
2025-10-19 11:08:23 -03:00
|
|
|
|
|
|
|
|
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
|
|
|
|
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
|
|
|
|
redis_client.clone(),
|
|
|
|
|
)));
|
2025-10-28 14:00:52 -03:00
|
|
|
|
2025-10-19 11:08:23 -03:00
|
|
|
let auth_service = Arc::new(tokio::sync::Mutex::new(auth::AuthService::new(
|
|
|
|
|
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
|
|
|
|
redis_client.clone(),
|
|
|
|
|
)));
|
2025-10-16 11:43:02 -03:00
|
|
|
|
|
|
|
|
let app_state = Arc::new(AppState {
|
2025-10-30 12:35:25 -03:00
|
|
|
s3_client: Some(drive),
|
2025-10-16 11:43:02 -03:00
|
|
|
config: Some(cfg.clone()),
|
|
|
|
|
conn: db_pool.clone(),
|
2025-10-30 18:48:16 -03:00
|
|
|
bucket_name: "default.gbai".to_string(), // Default bucket name
|
2025-10-16 11:43:02 -03:00
|
|
|
custom_conn: db_custom_pool.clone(),
|
|
|
|
|
redis_client: redis_client.clone(),
|
|
|
|
|
session_manager: session_manager.clone(),
|
|
|
|
|
tool_manager: tool_manager.clone(),
|
|
|
|
|
llm_provider: llm_provider.clone(),
|
|
|
|
|
auth_service: auth_service.clone(),
|
|
|
|
|
channels: Arc::new(Mutex::new({
|
|
|
|
|
let mut map = HashMap::new();
|
2025-10-20 19:49:54 -03:00
|
|
|
map.insert(
|
|
|
|
|
"web".to_string(),
|
|
|
|
|
web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>,
|
|
|
|
|
);
|
2025-10-16 11:43:02 -03:00
|
|
|
map
|
|
|
|
|
})),
|
|
|
|
|
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
|
|
|
|
web_adapter: web_adapter.clone(),
|
|
|
|
|
voice_adapter: voice_adapter.clone(),
|
|
|
|
|
whatsapp_adapter: whatsapp_adapter.clone(),
|
|
|
|
|
tool_api: tool_api.clone(),
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-28 14:00:52 -03:00
|
|
|
info!("Starting HTTP server on {}:{}", config.server.host, config.server.port);
|
2025-10-24 11:17:22 -03:00
|
|
|
let worker_count = std::thread::available_parallelism()
|
2025-10-20 19:49:54 -03:00
|
|
|
.map(|n| n.get())
|
|
|
|
|
.unwrap_or(4);
|
2025-10-16 11:43:02 -03:00
|
|
|
|
2025-10-24 11:17:22 -03:00
|
|
|
let automation_state = app_state.clone();
|
2025-10-20 19:49:54 -03:00
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
let rt = tokio::runtime::Builder::new_current_thread()
|
|
|
|
|
.enable_all()
|
|
|
|
|
.build()
|
|
|
|
|
.expect("Failed to create runtime for automation");
|
|
|
|
|
let local = tokio::task::LocalSet::new();
|
|
|
|
|
local.block_on(&rt, async move {
|
2025-10-30 18:48:16 -03:00
|
|
|
let scripts_dir = "work/default.gbai/.gbdialog".to_string();
|
2025-10-20 23:32:49 -03:00
|
|
|
let automation = AutomationService::new(automation_state, &scripts_dir);
|
2025-10-20 19:49:54 -03:00
|
|
|
automation.spawn().await.ok();
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-10-16 11:43:02 -03:00
|
|
|
|
2025-10-30 18:48:16 -03:00
|
|
|
// Initialize bot orchestrator and mount all bots
|
|
|
|
|
let bot_orchestrator = BotOrchestrator::new(app_state.clone());
|
|
|
|
|
|
|
|
|
|
// Mount all active bots from database
|
|
|
|
|
if let Err(e) = bot_orchestrator.mount_all_bots().await {
|
|
|
|
|
log::error!("Failed to mount bots: {}", e);
|
|
|
|
|
}
|
2025-10-18 18:19:08 -03:00
|
|
|
|
2025-10-30 18:48:16 -03:00
|
|
|
|
2025-10-16 11:43:02 -03:00
|
|
|
HttpServer::new(move || {
|
2025-10-20 19:49:54 -03:00
|
|
|
let cors = Cors::default()
|
|
|
|
|
.allow_any_origin()
|
|
|
|
|
.allow_any_method()
|
|
|
|
|
.allow_any_header()
|
|
|
|
|
.max_age(3600);
|
2025-10-19 11:08:23 -03:00
|
|
|
|
2025-10-28 14:00:52 -03:00
|
|
|
let app_state_clone = app_state.clone();
|
2025-10-16 11:43:02 -03:00
|
|
|
let mut app = App::new()
|
|
|
|
|
.wrap(cors)
|
|
|
|
|
.wrap(Logger::default())
|
|
|
|
|
.wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i"))
|
2025-10-31 15:40:52 -03:00
|
|
|
.app_data(web::Data::from(app_state_clone))
|
2025-10-16 11:43:02 -03:00
|
|
|
.service(auth_handler)
|
2025-10-31 15:40:52 -03:00
|
|
|
.service(chat_completions_local)
|
2025-10-16 11:43:02 -03:00
|
|
|
.service(create_session)
|
2025-10-31 15:40:52 -03:00
|
|
|
.service(embeddings_local)
|
|
|
|
|
.service(get_session_history)
|
2025-10-16 11:43:02 -03:00
|
|
|
.service(get_sessions)
|
2025-10-31 15:40:52 -03:00
|
|
|
.service(index)
|
2025-10-16 11:43:02 -03:00
|
|
|
.service(start_session)
|
2025-10-31 15:40:52 -03:00
|
|
|
.service(upload_file)
|
|
|
|
|
.service(voice_start)
|
|
|
|
|
.service(voice_stop)
|
|
|
|
|
.service(whatsapp_webhook_verify)
|
|
|
|
|
.service(websocket_handler);
|
|
|
|
|
|
2025-10-16 11:43:02 -03:00
|
|
|
#[cfg(feature = "email")]
|
|
|
|
|
{
|
|
|
|
|
app = app
|
|
|
|
|
.service(get_latest_email_from)
|
|
|
|
|
.service(get_emails)
|
|
|
|
|
.service(list_emails)
|
|
|
|
|
.service(send_email)
|
|
|
|
|
.service(save_draft)
|
|
|
|
|
.service(save_click);
|
|
|
|
|
|
2025-10-31 15:40:52 -03:00
|
|
|
}
|
|
|
|
|
app = app.service(static_files);
|
|
|
|
|
app = app.service(bot_index);
|
2025-10-16 11:43:02 -03:00
|
|
|
app
|
2025-10-31 15:40:52 -03:00
|
|
|
|
2025-10-16 11:43:02 -03:00
|
|
|
})
|
2025-10-18 19:08:00 -03:00
|
|
|
.workers(worker_count)
|
2025-10-16 11:43:02 -03:00
|
|
|
.bind((config.server.host.clone(), config.server.port))?
|
|
|
|
|
.run()
|
|
|
|
|
.await
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|