diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 1a0f2e63..186f611b 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -189,7 +189,7 @@ async fn auth_handler( } } }; - + let auth_script_path = format!("./work/{}.gbai/{}.gbdialog/auth.ast", bot_name, bot_name); if std::path::Path::new(&auth_script_path).exists() { let auth_script = match std::fs::read_to_string(&auth_script_path) { diff --git a/src/basic/keywords/hear_talk.rs b/src/basic/keywords/hear_talk.rs index d0916a23..4248d544 100644 --- a/src/basic/keywords/hear_talk.rs +++ b/src/basic/keywords/hear_talk.rs @@ -1,8 +1,7 @@ -use crate::shared::models::{BotResponse, UserSession}; +use crate::shared::models::{BotResponse, Suggestion, UserSession}; use crate::shared::state::AppState; use log::{debug, error, info}; use rhai::{Dynamic, Engine, EvalAltResult}; -use std::env; use std::sync::Arc; use uuid::Uuid; @@ -73,7 +72,7 @@ pub fn talk_keyword(state: Arc, user: UserSession, engine: &mut Engine debug!("TALK: Sending message: {}", message); // Build the bot response that will be sent back to the client. - let bot_id = env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string()); + let bot_id = "default_bot".to_string(); let response = BotResponse { bot_id, user_id: user_clone.user_id.to_string(), @@ -83,6 +82,7 @@ pub fn talk_keyword(state: Arc, user: UserSession, engine: &mut Engine message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; let user_id = user_clone.id.to_string(); @@ -168,23 +168,73 @@ pub fn set_user_keyword(state: Arc, user: UserSession, engine: &mut En }) .unwrap(); } -pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Engine) { +pub fn add_suggestion_keyword(state: Arc, user: UserSession, engine: &mut Engine) { + let user_clone = user.clone(); + + engine + .register_custom_syntax(&["ADD_SUGGESTION", "$expr$", "$expr$", "$expr$"], true, move |context, inputs| { + // Evaluate expressions: text, context_name + let text = context.eval_expression_tree(&inputs[0])?.to_string(); + let context_name = context.eval_expression_tree(&inputs[1])?.to_string(); + + info!("ADD_SUGGESTION command executed - text: {}, context: {}", text, context_name); + + // Get current response channels + let state_clone = Arc::clone(&state); + let user_id = user_clone.id.to_string(); + + tokio::spawn(async move { + let mut response_channels = state_clone.response_channels.lock().await; + if let Some(tx) = response_channels.get_mut(&user_id) { + let suggestion = Suggestion { + text, + context_name, + is_suggestion: true + }; + + // Create a response with just this suggestion + let response = BotResponse { + bot_id: "system".to_string(), + user_id: user_clone.user_id.to_string(), + session_id: user_clone.id.to_string(), + channel: "web".to_string(), + content: String::new(), + message_type: 3, // Special type for suggestions + stream_token: None, + is_complete: true, + suggestions: vec![suggestion], + }; + + if let Err(e) = tx.try_send(response) { + error!("Failed to send suggestion: {}", e); + } + } + }); + + Ok(Dynamic::UNIT) + }) + .unwrap(); +} + +pub fn set_context_keyword(state: Arc, user: UserSession, engine: &mut Engine) { let cache = state.redis_client.clone(); engine - .register_custom_syntax(&["SET_CONTEXT", "$expr$"], true, move |context, inputs| { - // Evaluate the expression that should be stored in the context. - let context_value = context.eval_expression_tree(&inputs[0])?.to_string(); + .register_custom_syntax(&["SET_CONTEXT", "$expr$", "$expr$"], true, move |context, inputs| { + // Evaluate both expressions - first is context name, second is context value + let context_name = context.eval_expression_tree(&inputs[0])?.to_string(); + let context_value = context.eval_expression_tree(&inputs[1])?.to_string(); - info!("SET CONTEXT command executed: {}", context_value); - // Build the Redis key using the user ID and the session ID. - let redis_key = format!("context:{}:{}", user.user_id, user.id); + info!("SET CONTEXT command executed - name: {}, value: {}", context_name, context_value); + // Build the Redis key using user ID, session ID and context name + let redis_key = format!("context:{}:{}:{}", user.user_id, user.id, context_name); log::trace!( target: "app::set_context", - "Constructed Redis key: {} for user {} and session {}", + "Constructed Redis key: {} for user {}, session {}, context {}", redis_key, user.user_id, - user.id + user.id, + context_name ); // If a Redis client is configured, perform the SET operation in a background task. diff --git a/src/basic/mod.rs b/src/basic/mod.rs index b844793a..64ef0c32 100644 --- a/src/basic/mod.rs +++ b/src/basic/mod.rs @@ -71,7 +71,7 @@ impl ScriptService { set_schedule_keyword(&state, user.clone(), &mut engine); hear_keyword(state.clone(), user.clone(), &mut engine); talk_keyword(state.clone(), user.clone(), &mut engine); - set_context_keyword(&state, user.clone(), &mut engine); + set_context_keyword(state.clone(), user.clone(), &mut engine); set_user_keyword(state.clone(), user.clone(), &mut engine); // KB and Tools keywords diff --git a/src/bot/mod.rs b/src/bot/mod.rs index db54784e..0aeb2f0e 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -210,19 +210,20 @@ impl BotOrchestrator { data: serde_json::Value, ) -> Result<(), Box> { info!("Sending event '{}' to session {} on channel {}", event_type, session_id, channel); - let event_response = BotResponse { - bot_id: bot_id.to_string(), - user_id: user_id.to_string(), - session_id: session_id.to_string(), - channel: channel.to_string(), - content: serde_json::to_string(&serde_json::json!({ - "event": event_type, - "data": data - }))?, - message_type: 2, - stream_token: None, - is_complete: true, - }; + let event_response = BotResponse { + bot_id: bot_id.to_string(), + user_id: user_id.to_string(), + session_id: session_id.to_string(), + channel: channel.to_string(), + content: serde_json::to_string(&serde_json::json!({ + "event": event_type, + "data": data + }))?, + message_type: 2, + stream_token: None, + is_complete: true, + suggestions: Vec::new(), + }; if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) { adapter.send_message(event_response).await?; @@ -249,6 +250,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) { @@ -303,6 +305,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; adapter.send_message(ack_response).await?; } @@ -346,6 +349,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) { @@ -593,6 +597,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; response_tx.send(thinking_response).await?; } @@ -664,6 +669,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: false, + suggestions: Vec::new(), }; if response_tx.send(partial).await.is_err() { @@ -688,6 +694,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; response_tx.send(final_msg).await?; @@ -787,6 +794,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, + suggestions: Vec::new(), }; adapter.send_message(warn_response).await } else { diff --git a/src/session/mod.rs b/src/session/mod.rs index 61c99e53..abe2c7ff 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -354,19 +354,7 @@ impl SessionManager { #[actix_web::post("/api/sessions")] async fn create_session(data: web::Data) -> Result { let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(); - let bot_id = if let Ok(bot_guid) = std::env::var("BOT_GUID") { - match Uuid::parse_str(&bot_guid) { - Ok(uuid) => uuid, - Err(e) => { - warn!("Invalid BOT_GUID from env: {}", e); - return Ok(HttpResponse::BadRequest() - .json(serde_json::json!({"error": "Invalid BOT_GUID"}))); - } - } - } else { - warn!("BOT_GUID not set in environment, using nil UUID"); - Uuid::nil() - }; + let bot_id = Uuid::nil(); let session = { let mut session_manager = data.session_manager.lock().await; diff --git a/src/shared/models.rs b/src/shared/models.rs index 9adcf8e4..375db639 100644 --- a/src/shared/models.rs +++ b/src/shared/models.rs @@ -118,6 +118,13 @@ pub struct UserMessage { pub timestamp: chrono::DateTime, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Suggestion { + pub text: String, // The button text that will be sent as message + pub context_name: String, // The context name to set when clicked + pub is_suggestion: bool, // Flag to identify suggestion clicks +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BotResponse { pub bot_id: String, @@ -128,6 +135,7 @@ pub struct BotResponse { pub message_type: i32, pub stream_token: Option, pub is_complete: bool, + pub suggestions: Vec, } #[derive(Debug, Deserialize)] diff --git a/web/app/index.html b/web/app/index.html index f0d69817..4a670a75 100644 --- a/web/app/index.html +++ b/web/app/index.html @@ -1,4 +1,4 @@ - + diff --git a/web/html/index.html b/web/html/index.html index 69a13478..42c58b59 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -880,36 +880,30 @@
@@ -964,6 +956,7 @@ let ws = null; let currentSessionId = null; let currentUserId = null; + let currentBotId = "default_bot"; let isStreaming = false; let voiceRoom = null; let isVoiceMode = false; @@ -1232,6 +1225,11 @@ ws.onmessage = function (event) { const response = JSON.parse(event.data); + + // Update current bot_id if provided in the message + if (response.bot_id) { + currentBotId = response.bot_id; + } if (response.message_type === 2) { const eventData = JSON.parse(response.content); @@ -1297,6 +1295,12 @@ updateContextUsage(response.context_usage); } + // Handle suggestion messages + if (response.message_type === 3) { + handleSuggestions(response.suggestions); + return; + } + // Handle complete messages if (response.is_complete) { if (isStreaming) {