From be971fb082b8447f29a1471a8c37cc9c64506d69 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 891101a6a..0114adb39 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 5de056a353b5cf5c871748fd7a609d0fbcb6056c 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 f4e102ab5..b4637b028 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 414575a1ecd22ecf4dfa3a7900c3428e3b9181a7 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 000000000..7a06ce52e --- /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 000000000..99eccd4f5 --- /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 7474d67fa..f37c80ada 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 d3b626ee7..bfae8d91a 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -878,6 +878,48 @@ /> +
+ From 866fe9049fc92904e4f7a8b76e6a15b6a4344f3c 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 ffe61b58a..b844793ad 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 dcfce44b9180205700eeacf280d6297435b4669d 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 99eccd4f5..bc2c177c0 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 bfae8d91a..69a13478f 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 45eb6c99b5006a2735a2e90ac81b31eaf3ca22f3 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 bc2c177c0..6791be985 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