diff --git a/add-req.sh b/add-req.sh index 7b50bef5..0fce6738 100755 --- a/add-req.sh +++ b/add-req.sh @@ -20,25 +20,24 @@ done dirs=( # "auth" - "automation" - "basic" + # "automation" + # "basic" # "bot" "bootstrap" - # "package_manager" + "package_manager" # "channels" # "config" # "context" # "email" - # "file" + "file" # "llm" "drive_monitor" # "llm_legacy" # "org" # "session" - "file" - "kb" + #"kb" "shared" - "tests" + #"tests" # "tools" # "web_automation" # "whatsapp" diff --git a/src/basic/keywords/get.rs b/src/basic/keywords/get.rs index 089c75f7..aa2ab44b 100644 --- a/src/basic/keywords/get.rs +++ b/src/basic/keywords/get.rs @@ -175,7 +175,7 @@ pub async fn get_from_bucket( "App configuration missing".into() })?; - let org_prefix = &cfg.minio.org_prefix; + let org_prefix = &cfg.drive.org_prefix; if org_prefix.contains("..") || org_prefix.contains('/') || org_prefix.contains('\\') { error!("Invalid org_prefix: {}", org_prefix); diff --git a/src/bootstrap/mod.rs b/src/bootstrap/mod.rs index 66d9b243..6988f638 100644 --- a/src/bootstrap/mod.rs +++ b/src/bootstrap/mod.rs @@ -2,11 +2,10 @@ use crate::config::AppConfig; use crate::package_manager::{InstallMode, PackageManager}; use anyhow::Result; use diesel::connection::SimpleConnection; -use diesel::{Connection, QueryableByName}; use diesel::RunQueryDsl; +use diesel::{Connection, QueryableByName}; use dotenvy::dotenv; -use log::{error, info}; -use opendal::services::S3; +use log::{error, info, trace}; use opendal::Operator; use rand::distr::Alphanumeric; use rand::Rng; @@ -29,6 +28,7 @@ pub struct ComponentInfo { pub struct BootstrapManager { pub install_mode: InstallMode, pub tenant: Option, + pub s3_operator: Operator, } impl BootstrapManager { @@ -37,9 +37,12 @@ impl BootstrapManager { "Initializing BootstrapManager with mode {:?} and tenant {:?}", install_mode, tenant ); + let config = AppConfig::from_env(); + let s3_operator = Self::create_s3_operator(&config); Self { install_mode, tenant, + s3_operator, } } @@ -289,9 +292,21 @@ impl BootstrapManager { } } + self.s3_operator = Self::create_s3_operator(&config); Ok(config) } + fn create_s3_operator(config: &AppConfig) -> Operator { + use opendal::Scheme; + use std::collections::HashMap; + let mut map = HashMap::new(); + map.insert("endpoint".to_string(), config.drive.server.clone()); + map.insert("access_key_id".to_string(), config.drive.access_key.clone()); + map.insert("secret_access_key".to_string(), config.drive.secret_key.clone()); + trace!("Creating S3 operator with endpoint {}", config.drive.server); + Operator::via_iter(Scheme::S3, map).expect("Failed to initialize S3 operator") + } + fn generate_secure_password(&self, length: usize) -> String { let mut rng = rand::rng(); std::iter::repeat_with(|| rng.sample(Alphanumeric) as char) @@ -307,7 +322,7 @@ impl BootstrapManager { } fn update_bot_config(&self, bot_id: &uuid::Uuid, component: &str) -> Result<()> { - use diesel::sql_types::{Uuid as SqlUuid, Text}; + use diesel::sql_types::{Text, Uuid as SqlUuid}; let database_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string()); let mut conn = diesel::pg::PgConnection::establish(&database_url)?; @@ -321,7 +336,7 @@ impl BootstrapManager { "INSERT INTO bot_configuration (id, bot_id, config_key, config_value, config_type) VALUES ($1, $2, $3, $4, 'string') ON CONFLICT (config_key) - DO UPDATE SET config_value = EXCLUDED.config_value, updated_at = NOW()" + DO UPDATE SET config_value = EXCLUDED.config_value, updated_at = NOW()", ) .bind::(new_id) .bind::(bot_id) @@ -332,39 +347,53 @@ impl BootstrapManager { Ok(()) } - pub async fn upload_templates_to_minio(&self, config: &AppConfig) -> Result<()> { + pub async fn upload_templates_to_drive(&self, config: &AppConfig) -> Result<()> { let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| config.database_url()); let mut conn = diesel::PgConnection::establish(&database_url)?; self.create_bots_from_templates(&mut conn)?; - - let client = Operator::new( - S3::default() - .root("/") - .endpoint(&config.minio.server) - .access_key_id(&config.minio.access_key) - .secret_access_key(&config.minio.secret_key), - )? - .finish(); - let templates_dir = Path::new("templates"); if !templates_dir.exists() { return Ok(()); } - + let operator = &self.s3_operator; for entry in std::fs::read_dir(templates_dir)? { + let bot_name = templates_dir + .read_dir()? + .filter_map(|e| e.ok()) + .find(|e| { + e.path().is_dir() + && e.path() + .file_name() + .unwrap() + .to_string_lossy() + .ends_with(".gbai") + }) + .map(|e| { + let name = e.path().file_name().unwrap().to_string_lossy().to_string(); + name + }) + .unwrap_or_else(|| "default".to_string()); let entry = entry?; let path = entry.path(); - if path.is_dir() && path.extension().map(|e| e == "gbai").unwrap_or(false) { + if path.is_dir() + && path + .file_name() + .unwrap() + .to_string_lossy() + .ends_with(".gbai") + { let bot_name = path.file_name().unwrap().to_string_lossy().to_string(); - - self.upload_directory_recursive(&client, &path, &bot_name, "") + let bucket = bot_name.clone(); + info!("Uploading template {} to Drive bucket {}", bot_name, bucket); + self.upload_directory_recursive(&operator, &path, &bucket, &bot_name) .await?; + info!("Uploaded template {} to Drive bucket {}", bot_name, bucket); } } - Ok(()) } + fn create_bots_from_templates(&self, conn: &mut diesel::PgConnection) -> Result<()> { use crate::shared::models::schema::bots; use diesel::prelude::*; @@ -425,6 +454,16 @@ impl BootstrapManager { prefix: &'a str, ) -> std::pin::Pin> + 'a>> { Box::pin(async move { + trace!("Checking bucket existence: {}", bucket); + if client.stat(bucket).await.is_err() { + info!("Bucket {} not found, creating it", bucket); + trace!("Creating bucket: {}", bucket); + client.create_dir(bucket).await?; + trace!("Bucket {} created successfully", bucket); + } else { + trace!("Bucket {} already exists", bucket); + } + trace!("Starting upload from local path: {}", local_path.display()); for entry in std::fs::read_dir(local_path)? { let entry = entry?; let path = entry.path(); @@ -443,7 +482,18 @@ impl BootstrapManager { key ); let content = std::fs::read(&path)?; + trace!( + "Writing file {} to bucket {} with key {}", + path.display(), + bucket, + key + ); client.write(&key, content).await?; + trace!( + "Successfully wrote file {} to bucket {}", + path.display(), + bucket + ); } else if path.is_dir() { self.upload_directory_recursive(client, &path, bucket, &key) .await?; diff --git a/src/config/mod.rs b/src/config/mod.rs index ebcf621a..f0f75a58 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -8,14 +8,13 @@ use std::sync::{Arc, Mutex}; #[derive(Clone)] pub struct AppConfig { - pub minio: DriveConfig, + pub drive: DriveConfig, pub server: ServerConfig, pub database: DatabaseConfig, pub database_custom: DatabaseConfig, pub email: EmailConfig, pub ai: AIConfig, pub site_path: String, - pub s3_bucket: String, pub stack_path: PathBuf, pub db_conn: Option>>, } @@ -218,7 +217,7 @@ impl AppConfig { }; AppConfig { - minio, + drive: minio, server: ServerConfig { host: get_str("SERVER_HOST", "127.0.0.1"), port: get_u16("SERVER_PORT", 8080), @@ -227,7 +226,6 @@ impl AppConfig { database_custom, email, ai, - s3_bucket: get_str("DRIVE_BUCKET", "default"), site_path: get_str("SITES_ROOT", "./botserver-stack/sites"), stack_path, db_conn: None, @@ -300,7 +298,7 @@ impl AppConfig { }; AppConfig { - minio, + drive: minio, server: ServerConfig { host: std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()), port: std::env::var("SERVER_PORT") @@ -312,7 +310,6 @@ impl AppConfig { database_custom, email, ai, - s3_bucket: std::env::var("DRIVE_BUCKET").unwrap_or_else(|_| "default".to_string()), site_path: std::env::var("SITES_ROOT") .unwrap_or_else(|_| "./botserver-stack/sites".to_string()), stack_path: PathBuf::from(stack_path), diff --git a/src/main.rs b/src/main.rs index 1849f04f..fddf2d36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,7 +140,7 @@ async fn main() -> std::io::Result<()> { let _ = bootstrap.start_all(); - if let Err(e) = bootstrap.upload_templates_to_minio(&cfg).await { + if let Err(e) = bootstrap.upload_templates_to_drive(&cfg).await { log::warn!("Failed to upload templates to MinIO: {}", e); } @@ -193,32 +193,8 @@ async fn main() -> std::io::Result<()> { )); let tool_api = Arc::new(tools::ToolApi::new()); - use opendal::services::S3; - use opendal::Operator; - use opendal::ErrorKind; - async fn ensure_bucket_exists(cfg: &AppConfig) { - let builder = S3::default() - .endpoint(&cfg.minio.server) - .access_key_id(&cfg.minio.access_key) - .secret_access_key(&cfg.minio.secret_key) - .bucket(&cfg.s3_bucket) - .root("/"); - let op = Operator::new(builder).unwrap().finish(); - match op.stat("/").await { - Ok(_) => info!("Bucket {} exists", cfg.s3_bucket), - Err(e) if e.kind() == ErrorKind::NotFound => { - if let Err(err) = op.create_dir("/").await { - info!("Created bucket {}: {:?}", cfg.s3_bucket, err); - } - } - Err(e) => info!("Bucket check failed: {:?}", e), - } - } - - ensure_bucket_exists(&config).await; - - let drive = init_drive(&config.minio) + let drive = init_drive(&config.drive) .await .expect("Failed to initialize Drive"); @@ -279,7 +255,7 @@ async fn main() -> std::io::Result<()> { let drive_state = app_state.clone(); let bot_guid = std::env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string()); - let bucket_name = format!("{}{}.gbai", cfg.minio.org_prefix, bot_guid); + let bucket_name = format!("{}{}.gbai", cfg.drive.org_prefix, bot_guid); let drive_monitor = Arc::new(DriveMonitor::new(drive_state, bucket_name)); let _drive_handle = drive_monitor.spawn(); diff --git a/templates/announcements.gbai/annoucements.gbot/config.csv b/templates/announcements.gbai/annoucements.gbot/config.csv new file mode 100644 index 00000000..d639a67e --- /dev/null +++ b/templates/announcements.gbai/annoucements.gbot/config.csv @@ -0,0 +1,4 @@ +name,value +prompt-compact, 10 +prompt-cache,true +prompt-fixed-kb,geral diff --git a/templates/announcements.gbai/announcements.gbdialog/change-subject.bas b/templates/announcements.gbai/announcements.gbdialog/change-subject.bas new file mode 100644 index 00000000..e2ffefa5 --- /dev/null +++ b/templates/announcements.gbai/announcements.gbdialog/change-subject.bas @@ -0,0 +1,9 @@ +PARAM subject as string +DESCRIPTION "Chamado quando alguém quer mudar o assunto da conversa." + +kbname = LLM "Devolva uma única palavra circular, comunicado ou geral de acordo com a seguinte frase:" + subject + +ADD_KB kbname + + +TALK "You have chosen to change the subject to " + subject + "." \ No newline at end of file diff --git a/templates/announcements.gbai/announcements.gbdialog/start.bas b/templates/announcements.gbai/announcements.gbdialog/start.bas index 7a5f9df5..16be13d8 100644 --- a/templates/announcements.gbai/announcements.gbdialog/start.bas +++ b/templates/announcements.gbai/announcements.gbdialog/start.bas @@ -1,9 +1,17 @@ -LET resume = GET_BOT_MEMORY("resume") +LET resume1 = GET_BOT_MEMORY("general") +LET resume2 = GET_BOT_MEMORY("auxiliom") +LET resume3 = GET_BOT_MEMORY("toolbix") -IF resume <> "" THEN - TALK resume -END IF +SET_CONTEXT "general", resume1 +SET_CONTEXT "auxiliom", resume2 +SET_CONTEXT "toolbix", resume3 + + +ADD_SUGGESTION "general", "Show me the weekly announcements" +ADD_SUGGESTION "auxiliom", "Will Auxiliom help me with what?" +ADD_SUGGESTION "auxiliom", "What does Auxiliom do?" +ADD_SUGGESTION "toolbix", "Show me Toolbix features" +ADD_SUGGESTION "toolbix", "How can Toolbix help my business?" -ADD_KB "weekly" TALK "You can ask me about any of the announcements or circulars." diff --git a/templates/announcements.gbai/announcements.gbdialog/update-summary.bas b/templates/announcements.gbai/announcements.gbdialog/update-summary.bas index 8a736cf7..d164e53a 100644 --- a/templates/announcements.gbai/announcements.gbdialog/update-summary.bas +++ b/templates/announcements.gbai/announcements.gbdialog/update-summary.bas @@ -1,5 +1,11 @@ -let text = GET "default.gbdrive/default.pdf" +let text = GET "announcements.gbkb/news/news.pdf" let resume = LLM "Resume this document, in a table (DO NOT THINK) no_think: " + text SET_BOT_MEMORY "resume", resume + +let text1 = GET "announcements.gbkb/auxiliom/auxiliom.pdf" +SET_BOT_MEMORY "auxiliom", text1 + +let text2 = GET "announcements.gbkb/toolbix/toolbix.pdf" +SET_BOT_MEMORY "toolbix", text2 \ No newline at end of file diff --git a/templates/announcements.gbai/announcements.gbkb/auxiliom/auxiliom.pdf b/templates/announcements.gbai/announcements.gbkb/auxiliom/auxiliom.pdf new file mode 100644 index 00000000..0097331f Binary files /dev/null and b/templates/announcements.gbai/announcements.gbkb/auxiliom/auxiliom.pdf differ diff --git a/templates/announcements.gbai/announcements.gbkb/news/news.pdf b/templates/announcements.gbai/announcements.gbkb/news/news.pdf new file mode 100644 index 00000000..fce456da Binary files /dev/null and b/templates/announcements.gbai/announcements.gbkb/news/news.pdf differ diff --git a/templates/announcements.gbai/announcements.gbkb/toolbix/toolbix.pdf b/templates/announcements.gbai/announcements.gbkb/toolbix/toolbix.pdf new file mode 100644 index 00000000..eb2e7c34 Binary files /dev/null and b/templates/announcements.gbai/announcements.gbkb/toolbix/toolbix.pdf differ diff --git a/templates/announcements.gbai/announcements.gbkb/weekly/default.pdf b/templates/announcements.gbai/announcements.gbkb/weekly/default.pdf deleted file mode 100644 index f38959e6..00000000 Binary files a/templates/announcements.gbai/announcements.gbkb/weekly/default.pdf and /dev/null differ diff --git a/templates/default.gbai/default.gbot/config.csv b/templates/default.gbai/default.gbot/config.csv index 8a4f03d9..3d3fcf93 100644 --- a/templates/default.gbai/default.gbot/config.csv +++ b/templates/default.gbai/default.gbot/config.csv @@ -1,8 +1,8 @@ name,value -server_host=0.0.0.0 -server_port=8080 -sites_root=/tmp +server_host,0.0.0.0 +server_port,8080 +sites_root,/tmp llm-key,gsk_ llm-model,openai/gpt-oss-20b