botserver/src/main.rs

244 lines
8.6 KiB
Rust
Raw Normal View History

#![allow(dead_code)]
2025-10-06 19:12:13 -03:00
use actix_cors::Cors;
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;
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;
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-18 19:08:00 -03:00
2025-10-18 12:01:39 -03:00
use crate::auth::auth_handler;
use crate::automation::AutomationService;
2025-10-18 19:08:00 -03:00
use crate::bootstrap::BootstrapManager;
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-18 18:19:08 -03:00
use crate::drive_monitor::DriveMonitor;
2025-10-07 10:53:09 -03:00
#[cfg(feature = "email")]
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};
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-18 12:01:39 -03:00
use crate::web_server::{index, static_files};
use crate::whatsapp::whatsapp_webhook_verify;
2025-10-06 20:06:43 -03:00
use crate::whatsapp::WhatsAppAdapter;
2025-10-06 10:30:17 -03:00
2025-10-19 14:02:47 -03:00
#[tokio::main]
async fn main() -> std::io::Result<()> {
2025-10-18 09:26:48 -03:00
let args: Vec<String> = std::env::args().collect();
2025-10-18 09:26:48 -03:00
if args.len() > 1 {
let command = &args[1];
match command.as_str() {
"install" | "remove" | "list" | "status" | "--help" | "-h" => {
match package_manager::cli::run().await {
2025-10-18 09:26:48 -03:00
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
}
}
}
_ => {
eprintln!("Unknown command: {}", command);
eprintln!("Run 'botserver --help' for usage information");
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("Unknown command: {}", command)));
2025-10-18 09:26:48 -03:00
}
}
}
dotenv().ok();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
2025-10-18 19:08:00 -03:00
info!("Starting BotServer bootstrap process");
2025-10-18 19:08:00 -03:00
let install_mode = if args.contains(&"--container".to_string()) {
InstallMode::Container
} else {
InstallMode::Local
};
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-18 19:08:00 -03:00
let mut bootstrap = BootstrapManager::new(install_mode.clone(), tenant.clone());
let cfg = match bootstrap.bootstrap() {
Ok(config) => {
info!("Bootstrap completed successfully, configuration loaded from database");
config
}
2025-10-18 19:08:00 -03:00
Err(e) => {
log::error!("Bootstrap failed: {}", e);
info!("Attempting to load configuration from database");
match diesel::Connection::establish(&std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string())) {
2025-10-18 19:08:00 -03:00
Ok(mut conn) => AppConfig::from_database(&mut conn),
Err(_) => {
info!("Database not available, using environment variables as fallback");
AppConfig::from_env()
}
}
}
};
let _ = bootstrap.start_all();
2025-10-18 19:08:00 -03:00
let config = std::sync::Arc::new(cfg.clone());
2025-10-18 19:08:00 -03:00
info!("Establishing database connection to {}", cfg.database_url());
let db_pool = match diesel::Connection::establish(&cfg.database_url()) {
Ok(conn) => Arc::new(Mutex::new(conn)),
Err(e) => {
log::error!("Failed to connect to main database: {}", e);
return Err(std::io::Error::new(std::io::ErrorKind::ConnectionRefused, format!("Database connection failed: {}", e)));
}
};
let db_custom_pool = db_pool.clone();
2025-10-18 19:08:00 -03:00
info!("Initializing LLM server at {}", cfg.ai.endpoint);
ensure_llama_servers_running().await.expect("Failed to initialize LLM local server");
let cache_url = cfg.config_path("cache").join("redis.conf").display().to_string();
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)),
Err(e) => {
log::warn!("Failed to connect to Redis: {}", e);
None
}
};
let tool_manager = Arc::new(tools::ToolManager::new());
let llm_provider = Arc::new(crate::llm::OpenAIClient::new("empty".to_string(), Some(cfg.ai.endpoint.clone())));
let web_adapter = Arc::new(WebChannelAdapter::new());
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()));
let tool_api = Arc::new(tools::ToolApi::new());
2025-10-18 19:08:00 -03:00
info!("Initializing MinIO drive at {}", cfg.minio.server);
let drive = init_drive(&config.minio).await.expect("Failed to initialize Drive");
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
diesel::Connection::establish(&cfg.database_url()).unwrap(),
redis_client.clone(),
)));
let auth_service = Arc::new(tokio::sync::Mutex::new(auth::AuthService::new(
diesel::Connection::establish(&cfg.database_url()).unwrap(),
redis_client.clone(),
)));
let app_state = Arc::new(AppState {
s3_client: Some(drive.clone()),
config: Some(cfg.clone()),
conn: db_pool.clone(),
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();
map.insert("web".to_string(), web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>);
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(),
});
info!("Starting HTTP server on {}:{}", config.server.host, config.server.port);
let worker_count = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4);
let automation_state = app_state.clone();
let automation = AutomationService::new(automation_state, "templates/announcements.gbai/announcements.gbdialog");
let _automation_handle = automation.spawn();
2025-10-18 18:19:08 -03:00
let drive_state = app_state.clone();
let bucket_name = format!("{}default.gbai", cfg.minio.org_prefix);
let drive_monitor = Arc::new(DriveMonitor::new(drive_state, bucket_name));
let _drive_handle = drive_monitor.spawn();
HttpServer::new(move || {
let cors = Cors::default().allow_any_origin().allow_any_method().allow_any_header().max_age(3600);
let app_state_clone = app_state.clone();
let mut app = App::new()
.wrap(cors)
.wrap(Logger::default())
.wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i"))
.app_data(web::Data::from(app_state_clone));
2025-10-15 12:45:15 -03:00
app = app
.service(upload_file)
.service(index)
.service(static_files)
.service(websocket_handler)
.service(auth_handler)
.service(whatsapp_webhook_verify)
.service(voice_start)
.service(voice_stop)
.service(create_session)
.service(get_sessions)
.service(start_session)
.service(get_session_history)
.service(chat_completions_local)
.service(embeddings_local);
#[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);
}
app
})
2025-10-18 19:08:00 -03:00
.workers(worker_count)
.bind((config.server.host.clone(), config.server.port))?
.run()
.await
2025-10-06 10:30:17 -03:00
}