From fda0b0c9e89117aa9b67a7daf573c5a02fc92d18 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sat, 1 Nov 2025 17:56:53 -0300 Subject: [PATCH] feat(auth): add bot name lookup and suggestion support in bot responses Implemented a new `bot_from_name` method in `AuthService` to retrieve a bot's UUID by its name, enabling explicit bot selection via a `bot_name` query parameter in the authentication endpoint. Updated `auth_handler` to prioritize this parameter, fall back to the first active bot when necessary, and handle cases where no active bots exist. Extended `BotOrchestrator` to import the `Suggestion` model and fetch suggestion data from Redis for each user session. Integrated these suggestions into the `BotResponse` payload, ensuring clients receive relevant suggestions alongside bot messages. These changes improve bot selection flexibility and enrich the response data with contextual suggestions. --- src/auth/mod.rs | 55 ++++++++++++++++++++++++++++++++++++++++++--- src/bot/mod.rs | 44 +++++++++++++++++++++++++++++++----- web/html/index.html | 6 ++++- 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 186f611b..a3fd4207 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -144,6 +144,22 @@ impl AuthService { Ok(user) } + + pub fn bot_from_name( + &mut self, + bot_name: &str, + ) -> Result, Box> { + use crate::shared::models::bots; + + let bot = bots::table + .filter(bots::name.eq(bot_name)) + .filter(bots::is_active.eq(true)) + .select(bots::id) + .first::(&mut self.conn) + .optional()?; + + Ok(bot) + } } #[actix_web::get("/api/auth")] @@ -152,6 +168,7 @@ async fn auth_handler( data: web::Data, web::Query(params): web::Query>, ) -> Result { + let bot_name = params.get("bot_name").cloned().unwrap_or_default(); let _token = params.get("token").cloned().unwrap_or_default(); // Create or get anonymous user with proper UUID @@ -168,9 +185,41 @@ async fn auth_handler( }; let mut db_conn = data.conn.lock().unwrap(); - let (bot_id, bot_name) = match crate::bot::bot_from_url(&mut *db_conn, req.path()) { - Ok((id, name)) => (id, name), - Err(res) => return Ok(res), + // Use bot_name query parameter if provided, otherwise fallback to path-based lookup + let bot_name_param = bot_name.clone(); + let (bot_id, bot_name) = { + use crate::shared::models::schema::bots::dsl::*; + use diesel::prelude::*; + use actix_web::error::ErrorInternalServerError; + + // Try to find bot by the provided name + match bots + .filter(name.eq(&bot_name_param)) + .filter(is_active.eq(true)) + .select((id, name)) + .first::<(Uuid, String)>(&mut *db_conn) + .optional() + .map_err(|e| ErrorInternalServerError(e))? + { + Some((id_val, name_val)) => (id_val, name_val), + None => { + // Fallback to first active bot if not found + match bots + .filter(is_active.eq(true)) + .select((id, name)) + .first::<(Uuid, String)>(&mut *db_conn) + .optional() + .map_err(|e| ErrorInternalServerError(e))? + { + Some((id_val, name_val)) => (id_val, name_val), + None => { + error!("No active bots found"); + return Ok(HttpResponse::ServiceUnavailable() + .json(serde_json::json!({"error": "No bots available"}))); + } + } + } + } }; let session = { diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 43962fb8..25b53053 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -3,7 +3,7 @@ use crate::context::langcache::get_langcache_client; use crate::drive_monitor::DriveMonitor; use crate::kb::embeddings::generate_embeddings; use crate::kb::qdrant_client::{ensure_collection_exists, get_qdrant_client, QdrantPoint}; -use crate::shared::models::{BotResponse, UserMessage, UserSession}; +use crate::shared::models::{BotResponse, Suggestion, UserMessage, UserSession}; use crate::shared::state::AppState; use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_ws::Message as WsMessage; @@ -550,6 +550,25 @@ impl BotOrchestrator { message.user_id, message.session_id ); + // Get suggestions from Redis + let suggestions = if let Some(redis) = &self.state.redis_client { + let mut conn = redis.get_multiplexed_async_connection().await?; + let redis_key = format!("suggestions:{}:{}", message.user_id, message.session_id); + let suggestions: Vec = redis::cmd("LRANGE") + .arg(&redis_key) + .arg(0) + .arg(-1) + .query_async(&mut conn) + .await?; + + suggestions + .into_iter() + .filter_map(|s| serde_json::from_str::(&s).ok()) + .collect() + } else { + Vec::new() + }; + let user_id = Uuid::parse_str(&message.user_id).map_err(|e| { error!("Invalid user ID: {}", e); e @@ -733,7 +752,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: false, - suggestions: Vec::new(), + suggestions: suggestions.clone(), }; if response_tx.send(partial).await.is_err() { @@ -761,7 +780,7 @@ impl BotOrchestrator { message_type: 1, stream_token: None, is_complete: true, - suggestions: Vec::new(), + suggestions, }; response_tx.send(final_msg).await?; @@ -801,8 +820,23 @@ impl BotOrchestrator { session.id, token ); - let bot_guid = std::env::var("BOT_GUID").unwrap_or_else(|_| String::from("default_bot")); - let start_script_path = format!("./{}.gbai/.gbdialog/start.bas", bot_guid); + use crate::shared::models::schema::bots::dsl::*; + use diesel::prelude::*; + + let bot_id = session.bot_id; + let bot_name: String = { + let mut db_conn = state.conn.lock().unwrap(); + bots.filter(id.eq(Uuid::parse_str(&bot_id.to_string())?)) + .select(name) + .first(&mut *db_conn) + .map_err(|e| { + error!("Failed to query bot name for {}: {}", bot_id, e); + e + })? + }; + + + let start_script_path = format!("./work/{}.gbai/{}.gbdialog/start.ast", bot_name, bot_name); let start_script = match std::fs::read_to_string(&start_script_path) { Ok(content) => content, diff --git a/web/html/index.html b/web/html/index.html index 42c58b59..2291ca79 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -1093,7 +1093,11 @@ async function initializeAuth() { try { updateConnectionStatus("connecting"); - const response = await fetch("/api/auth"); + // Extract bot name from URL path (first segment after /) + const pathSegments = window.location.pathname.split('/').filter(s => s); + const botName = pathSegments.length > 0 ? pathSegments[0] : 'default'; + + const response = await fetch(`/api/auth?bot_name=${encodeURIComponent(botName)}`); const authData = await response.json(); currentUserId = authData.user_id; currentSessionId = authData.session_id;