From 48290c6e3c24eeb139c2aa1107a1106bacf4833e Mon Sep 17 00:00:00 2001 From: christopher Date: Tue, 21 Oct 2025 21:25:49 -0300 Subject: [PATCH 1/6] Add Windows post-install commands for MinIO Add curl command to download MinIO client for Windows and configure MinIO client with admin user and policy. Also add powershell commands to install Valkey on Windows. --- src/package_manager/installer.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/package_manager/installer.rs b/src/package_manager/installer.rs index 891101a6..0114adb3 100644 --- a/src/package_manager/installer.rs +++ b/src/package_manager/installer.rs @@ -91,7 +91,13 @@ impl PackageManager { "chmod +x {{BIN_PATH}}/mc".to_string() ], pre_install_cmds_windows: vec![], - post_install_cmds_windows: vec![], + post_install_cmds_windows: vec![ + "curl https://dl.min.io/client/mc/release/windows-amd64/mc.exe -O {{BIN_PATH}}\\mc.exe".to_string(), + "cmd /c {{BIN_PATH}}\\mc.exe alias set mc http://localhost:9000 gbdriveuser {}".to_string(), + "cmd /c {{BIN_PATH}}\\mc.exe mb mc\\default.gbai".to_string(), + "cmd /c {{BIN_PATH}}\\mc.exe admin user add mc gbdriveuser {}".to_string(), + "cmd /c {{BIN_PATH}}\\mc.exe admin policy attach mc readwrite --user=gbdriveuser".to_string() + ], env_vars: HashMap::from([ ("MINIO_ROOT_USER".to_string(), "gbdriveuser".to_string()), ("MINIO_ROOT_PASSWORD".to_string(), drive_password) @@ -199,7 +205,12 @@ impl PackageManager { post_install_cmds_linux: vec![], pre_install_cmds_macos: vec![], post_install_cmds_macos: vec![], - pre_install_cmds_windows: vec![], + pre_install_cmds_windows: vec![ + + "powershell -Command \"if (!(Test-Path -Path 'C:\\ProgramData\\valkey\\keyrings\\valkey.gpg')) { Invoke-WebRequest -Uri 'https://packages.redis.io/gpg' -OutFile C:\\ProgramData\\valkey\\keyrings\\valkey.gpg }\"".to_string(), + "powershell -Command \"if (!(Test-Path -Path 'C:\\ProgramData\\valkey\\sources.list')) { Add-Content -Path 'C:\\ProgramData\\valkey\\sources.list' -Value 'deb [signed-by=C:\\ProgramData\\valkey\\keyrings\\valkey.gpg] https://packages.redis.io/windows valkey main' }\"".to_string(), + "powershell -Command \"winget install -e --id Valkey valkey-server\"".to_string() + ], post_install_cmds_windows: vec![], env_vars: HashMap::new(), exec_cmd: "valkey-server --port 6379 --dir {{DATA_PATH}}".to_string(), From 0330b8fdb8e541a0c14e858d770019e2714c4452 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Wed, 29 Oct 2025 14:49:25 -0300 Subject: [PATCH 2/6] feat(bootstrap): improve bucket creation and error handling logic Enhance the bucket creation process in `BootstrapManager` to handle existing buckets gracefully. Adds logic to reuse the default template when a bucket already exists and ensures proper path formatting before creation. This improves reliability and prevents redundant bucket creation errors. --- src/bootstrap/mod.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/bootstrap/mod.rs b/src/bootstrap/mod.rs index f4e102ab..b4637b02 100644 --- a/src/bootstrap/mod.rs +++ b/src/bootstrap/mod.rs @@ -395,14 +395,27 @@ impl BootstrapManager { let bot_name = path.file_name().unwrap().to_string_lossy().to_string(); let bucket = bot_name.trim_start_matches('/').to_string(); info!("Uploading template {} to Drive bucket {}", bot_name, bucket); - if operator.stat(&bucket).await.is_err() { - info!("Bucket {} not found, creating it", bucket); - operator.create_dir("/").await?; - debug!("Bucket {} created successfully", bucket); - } - self.upload_directory_recursive(&operator, &path, &bucket) - .await?; - info!("Uploaded template {} to Drive bucket {}", bot_name, bucket); +if operator.stat(&bucket).await.is_err() { + info!("Bucket {} not found, creating it", bucket); + let bucket_path = if bucket.ends_with('/') { bucket.clone() } else { format!("{}/", bucket) }; +match operator.create_dir(&bucket_path).await { + Ok(_) => { + debug!("Bucket {} created successfully", bucket); + } + Err(e) => { + let err_msg = format!("{}", e); + if err_msg.contains("BucketAlreadyOwnedByYou") { + log::warn!("Bucket {} already exists, reusing default.gbai", bucket); + self.upload_directory_recursive(&operator, &Path::new("templates/default.gbai"), "default.gbai").await?; + continue; + } else { + return Err(e.into()); + } + } + } +} +self.upload_directory_recursive(&operator, &path, &bucket).await?; +info!("Uploaded template {} to Drive bucket {}", bot_name, bucket); } } Ok(()) From d278b95fac18cc14a86a391d76a9a4ae9ffcac29 Mon Sep 17 00:00:00 2001 From: christopher Date: Wed, 29 Oct 2025 18:54:33 -0300 Subject: [PATCH 3/6] feat(keywords, ui): add suggestion module and UI for context switching Added new `add_suggestion` module to support suggestion handling logic. Updated `index.html` to include a dynamic suggestions container that fetches and displays context suggestions. This improves user experience by enabling quick context changes through interactive suggestion buttons. --- .cline/config.json | 3 ++ src/basic/keywords/add_suggestion.rs | 51 ++++++++++++++++++++++++++++ src/basic/keywords/mod.rs | 1 + web/html/index.html | 42 +++++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 .cline/config.json create mode 100644 src/basic/keywords/add_suggestion.rs diff --git a/.cline/config.json b/.cline/config.json new file mode 100644 index 00000000..7a06ce52 --- /dev/null +++ b/.cline/config.json @@ -0,0 +1,3 @@ +{ + "checkpoints": false +} diff --git a/src/basic/keywords/add_suggestion.rs b/src/basic/keywords/add_suggestion.rs new file mode 100644 index 00000000..99eccd4f --- /dev/null +++ b/src/basic/keywords/add_suggestion.rs @@ -0,0 +1,51 @@ +use crate::shared::state::AppState; +use crate::shared::models::UserSession; +use log::{debug, error, info}; +use rhai::{Dynamic, Engine}; +use serde_json::json; +use std::sync::Arc; + +pub fn add_suggestion_keyword(state: Arc, user: UserSession, engine: &mut Engine) { + let cache = state.redis_client.clone(); + + engine + .register_custom_syntax(&["ADD_SUGGESTION", "$expr$", "$expr$"], true, move |context, inputs| { + let context_name = context.eval_expression_tree(&inputs[0])?.to_string(); + let button_text = context.eval_expression_tree(&inputs[1])?.to_string(); + + info!("ADD_SUGGESTION command executed: context='{}', text='{}'", context_name, button_text); + + if let Some(cache_client) = &cache { + let cache_client = cache_client.clone(); + let redis_key = format!("suggestions:{}:{}", user.user_id, user.id); + let suggestion = json!({ "context": context_name, "text": button_text }); + + tokio::spawn(async move { + let mut conn = match cache_client.get_multiplexed_async_connection().await { + Ok(conn) => conn, + Err(e) => { + error!("Failed to connect to cache: {}", e); + return; + } + }; + + // Append suggestion to Redis list + let result: Result<(), redis::RedisError> = redis::cmd("RPUSH") + .arg(&redis_key) + .arg(suggestion.to_string()) + .query_async(&mut conn) + .await; + + match result { + Ok(_) => debug!("Suggestion added successfully to Redis key {}", redis_key), + Err(e) => error!("Failed to add suggestion to Redis: {}", e), + } + }); + } else { + debug!("No Redis client configured; suggestion will not persist"); + } + + Ok(Dynamic::UNIT) + }) + .unwrap(); +} diff --git a/src/basic/keywords/mod.rs b/src/basic/keywords/mod.rs index 7474d67f..f37c80ad 100644 --- a/src/basic/keywords/mod.rs +++ b/src/basic/keywords/mod.rs @@ -19,6 +19,7 @@ pub mod set; pub mod set_kb; pub mod set_schedule; pub mod wait; +pub mod add_suggestion; #[cfg(feature = "email")] pub mod create_draft_keyword; diff --git a/web/html/index.html b/web/html/index.html index d3b626ee..bfae8d91 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -878,6 +878,48 @@ /> +
+ From 254de04d28f1c7dee35cd25954e37351776e46fa Mon Sep 17 00:00:00 2001 From: christopher Date: Thu, 30 Oct 2025 09:09:15 -0300 Subject: [PATCH 4/6] feat(keywords): add suggestion keyword to ScriptService initialization --- src/basic/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/mod.rs b/src/basic/mod.rs index ffe61b58..b844793a 100644 --- a/src/basic/mod.rs +++ b/src/basic/mod.rs @@ -30,6 +30,7 @@ use self::keywords::set::set_keyword; use self::keywords::set_kb::{add_kb_keyword, set_kb_keyword}; use self::keywords::set_schedule::set_schedule_keyword; use self::keywords::wait::wait_keyword; +use self::keywords::add_suggestion::add_suggestion_keyword; #[cfg(feature = "email")] use self::keywords::create_draft_keyword; @@ -81,6 +82,7 @@ impl ScriptService { clear_tools_keyword(state.clone(), user.clone(), &mut engine); list_tools_keyword(state.clone(), user.clone(), &mut engine); add_website_keyword(state.clone(), user.clone(), &mut engine); + add_suggestion_keyword(state.clone(), user.clone(), &mut engine); #[cfg(feature = "web_automation")] get_website_keyword(&state, user.clone(), &mut engine); From 4337bd42299df1315b6a37652bf1f7e21f521a3d Mon Sep 17 00:00:00 2001 From: christopher Date: Thu, 30 Oct 2025 14:53:06 -0300 Subject: [PATCH 5/6] feat(suggestions): enhance suggestion handling and context management in WebSocket --- src/basic/keywords/add_suggestion.rs | 17 ++++++++++++++++- web/html/index.html | 24 ++++++++++++++++++------ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/basic/keywords/add_suggestion.rs b/src/basic/keywords/add_suggestion.rs index 99eccd4f..bc2c177c 100644 --- a/src/basic/keywords/add_suggestion.rs +++ b/src/basic/keywords/add_suggestion.rs @@ -37,7 +37,22 @@ pub fn add_suggestion_keyword(state: Arc, user: UserSession, engine: & .await; match result { - Ok(_) => debug!("Suggestion added successfully to Redis key {}", redis_key), + Ok(_) => { + debug!("Suggestion added successfully to Redis key {}", redis_key); + + // Also register context as inactive initially + let active_key = format!("active_context:{}:{}", user.user_id, user.id); + let _: Result<(), redis::RedisError> = redis::cmd("HSET") + .arg(&active_key) + .arg(&context_name) + .arg("inactive") + .query_async(&mut conn) + .await + .unwrap_or_else(|e| { + error!("Failed to set context state: {}", e); + () + }); + } Err(e) => error!("Failed to add suggestion to Redis: {}", e), } }); diff --git a/web/html/index.html b/web/html/index.html index bfae8d91..69a13478 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -907,12 +907,24 @@ async function setContext(context) { try { - await fetch('/api/set_context', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ context }) - }); - alert(`Contexto alterado para: ${context}`); + if (ws && ws.readyState === WebSocket.OPEN) { + const suggestionEvent = { + bot_id: "default_bot", + user_id: currentUserId, + session_id: currentSessionId, + channel: "web", + content: context, + message_type: 4, // custom type for suggestion click + is_suggestion: true, + context_name: context, + timestamp: new Date().toISOString() + }; + ws.send(JSON.stringify(suggestionEvent)); + alert(`Contexto alterado para: ${context}`); + } else { + console.warn("WebSocket não está conectado. Tentando reconectar..."); + connectWebSocket(); + } } catch (err) { console.error('Failed to set context:', err); } From 8af46bbb060f0a97cbe80565125e30d8741abf37 Mon Sep 17 00:00:00 2001 From: christopher Date: Fri, 31 Oct 2025 15:58:54 -0300 Subject: [PATCH 6/6] feat: capture Redis RPUSH length and log HSET result for suggestions Changed the Redis RPUSH command to return the new list length (`Result`) and added a debug log that includes this length. Updated the HSET command to capture its result (`Result`), logging the number of fields added on success and handling errors explicitly. Removed unnecessary `unwrap_or_else` and added comments to clarify behavior, improving observability and error handling for suggestion storage. --- src/basic/keywords/add_suggestion.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/basic/keywords/add_suggestion.rs b/src/basic/keywords/add_suggestion.rs index bc2c177c..6791be98 100644 --- a/src/basic/keywords/add_suggestion.rs +++ b/src/basic/keywords/add_suggestion.rs @@ -29,29 +29,32 @@ pub fn add_suggestion_keyword(state: Arc, user: UserSession, engine: & } }; - // Append suggestion to Redis list - let result: Result<(), redis::RedisError> = redis::cmd("RPUSH") + // Append suggestion to Redis list - RPUSH returns the new length as i64 + let result: Result = redis::cmd("RPUSH") .arg(&redis_key) .arg(suggestion.to_string()) .query_async(&mut conn) .await; match result { - Ok(_) => { - debug!("Suggestion added successfully to Redis key {}", redis_key); + Ok(length) => { + debug!("Suggestion added successfully to Redis key {}, new length: {}", redis_key, length); // Also register context as inactive initially let active_key = format!("active_context:{}:{}", user.user_id, user.id); - let _: Result<(), redis::RedisError> = redis::cmd("HSET") + let hset_result: Result = redis::cmd("HSET") .arg(&active_key) .arg(&context_name) .arg("inactive") .query_async(&mut conn) - .await - .unwrap_or_else(|e| { - error!("Failed to set context state: {}", e); - () - }); + .await; + + match hset_result { + Ok(fields_added) => { + debug!("Context state set to inactive for {}, fields added: {}", context_name, fields_added) + }, + Err(e) => error!("Failed to set context state: {}", e), + } } Err(e) => error!("Failed to add suggestion to Redis: {}", e), } @@ -63,4 +66,4 @@ pub fn add_suggestion_keyword(state: Arc, user: UserSession, engine: & Ok(Dynamic::UNIT) }) .unwrap(); -} +} \ No newline at end of file