From b5e15014545e6332e6c13bd9dd0eb7648baaf551 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Mon, 3 Nov 2025 10:13:39 -0300 Subject: [PATCH] feat: add trace logging, refactor bot streaming, add config fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `trace!` logging in `bot_memory.rs` to record retrieved memory values for easier debugging. - Refactored `BotOrchestrator` in `bot/mod.rs`: - Removed duplicate session save block and consolidated message persistence. - Replaced low‑level LLM streaming with a structured `UserMessage` and `stream_response` workflow, improving error handling and readability. - Updated configuration loading in `config/mod.rs`: - Imported `get_default_bot` and enhanced `get_config` to fall back to the default bot configuration when the primary query fails. - Established a fresh DB connection for the fallback path to avoid borrowing issues. --- src/basic/keywords/bot_memory.rs | 3 +- src/bot/mod.rs | 46 +- src/config/mod.rs | 46 +- src/context/prompt_processor.rs | 472 ------------------ src/drive_monitor/mod.rs | 6 - src/llm/local.rs | 2 +- src/main.rs | 2 +- src/session/mod.rs | 39 +- .../default.gbai/default.gbot/config.csv | 2 +- web/html/index.html | 50 +- 10 files changed, 138 insertions(+), 530 deletions(-) delete mode 100644 src/context/prompt_processor.rs diff --git a/src/basic/keywords/bot_memory.rs b/src/basic/keywords/bot_memory.rs index 08373503..6de02204 100644 --- a/src/basic/keywords/bot_memory.rs +++ b/src/basic/keywords/bot_memory.rs @@ -1,7 +1,7 @@ use crate::shared::models::UserSession; use crate::shared::state::AppState; use diesel::prelude::*; -use log::{error, info}; +use log::{error, info, trace}; use rhai::{Dynamic, Engine}; use std::sync::Arc; use uuid::Uuid; @@ -133,6 +133,7 @@ pub fn get_bot_memory_keyword(state: Arc, user: UserSession, engine: & .optional() .unwrap_or(None); + trace!("GET_MEMORY for key '{}' returned value: {:?}", key_param, memory_value); memory_value.unwrap_or_default() } else { String::new() diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 8760ee72..e40998fe 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -455,16 +455,6 @@ impl BotOrchestrator { return Ok(()); } - { - let mut session_manager = self.state.session_manager.lock().await; - session_manager.save_message( - session.id, - user_id, - 1, - &message.content, - message.message_type, - )?; - } // Handle context change messages (type 4) immediately @@ -489,6 +479,13 @@ impl BotOrchestrator { { let mut session_manager = self.state.session_manager.lock().await; + session_manager.save_message( + session.id, + user_id, + 1, + &message.content, + message.message_type, + )?; session_manager.save_message(session.id, user_id, 2, &response_content, 1)?; } @@ -570,19 +567,26 @@ impl BotOrchestrator { prompt.push_str(&format!("User: {}\nAssistant:", message.content)); - let (tx, mut rx) = mpsc::channel::(100); let llm = self.state.llm_provider.clone(); - tokio::spawn(async move { - if let Err(e) = llm - .generate_stream(&prompt, &serde_json::Value::Null, tx) - .await - { - error!("LLM streaming error in direct_mode_handler: {}", e); - } - }); + let user_message = UserMessage { + bot_id: "default".to_string(), + user_id: session.user_id.to_string(), + session_id: session.id.to_string(), + channel: "web".to_string(), + content: message.content.clone(), + message_type: 1, + media_url: None, + timestamp: Utc::now(), + context_name: None, + }; + + let (response_tx, mut response_rx) = mpsc::channel::(100); + if let Err(e) = self.stream_response(user_message, response_tx).await { + error!("Failed to stream response in direct_mode_handler: {}", e); + } let mut full_response = String::new(); - while let Some(chunk) = rx.recv().await { - full_response.push_str(&chunk); + while let Some(response) = response_rx.recv().await { + full_response.push_str(&response.content); } Ok(full_response) diff --git a/src/config/mod.rs b/src/config/mod.rs index 6eefa013..49df5560 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -121,6 +121,7 @@ impl AppConfig { info!("Loading configuration from database"); use crate::shared::models::schema::bot_configuration::dsl::*; +use crate::bot::get_default_bot; use diesel::prelude::*; let config_map: HashMap = bot_configuration @@ -351,12 +352,28 @@ impl AppConfig { value: String, } + // First attempt: use the current context (existing query) let result = diesel::sql_query("SELECT get_config($1, $2) as value") .bind::(key) .bind::(fallback_str) .get_result::(conn) - .map(|row| row.value)?; - Ok(result) + .map(|row| row.value); + + match result { + Ok(v) => Ok(v), + Err(_) => { + // Fallback to default bot + let (default_bot_id, _default_bot_name) = crate::bot::get_default_bot(conn); + // Use a fresh connection for ConfigManager to avoid borrowing issues + let fresh_conn = establish_pg_connection() + .map_err(|e| diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::UnableToSendCommand, + Box::new(e.to_string()) + ))?; + let manager = ConfigManager::new(Arc::new(Mutex::new(fresh_conn))); + manager.get_config(&default_bot_id, key, fallback) + } + } } } @@ -424,18 +441,33 @@ impl ConfigManager { fallback: Option<&str>, ) -> Result { use crate::shared::models::schema::bot_configuration::dsl::*; - + use crate::bot::get_default_bot; + let mut conn = self.conn.lock().unwrap(); let fallback_str = fallback.unwrap_or(""); + // Try config for provided bot_id let result = bot_configuration .filter(bot_id.eq(code_bot_id)) .filter(config_key.eq(key)) .select(config_value) - .first::(&mut *conn) - .unwrap_or(fallback_str.to_string()); - - Ok(result) + .first::(&mut *conn); + + let value = match result { + Ok(v) => v, + Err(_) => { + // Fallback to default bot + let (default_bot_id, _default_bot_name) = crate::bot::get_default_bot(&mut *conn); + bot_configuration + .filter(bot_id.eq(default_bot_id)) + .filter(config_key.eq(key)) + .select(config_value) + .first::(&mut *conn) + .unwrap_or(fallback_str.to_string()) + } + }; + + Ok(value) } pub fn sync_gbot_config( diff --git a/src/context/prompt_processor.rs b/src/context/prompt_processor.rs deleted file mode 100644 index 80df6a86..00000000 --- a/src/context/prompt_processor.rs +++ /dev/null @@ -1,472 +0,0 @@ -use crate::basic::keywords::add_tool::get_session_tools; -use crate::kb::embeddings::search_similar; -use crate::shared::models::UserSession; -use crate::shared::state::AppState; -use log::{debug, error, info}; -use serde::{Deserialize, Serialize}; -use std::error::Error; -use std::sync::Arc; - -/// Answer modes for the bot -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum AnswerMode { - Direct = 0, // Direct LLM response - WithTools = 1, // LLM with tool calling - DocumentsOnly = 2, // Search KB documents only, no LLM - WebSearch = 3, // Include web search results - Mixed = 4, // Use tools stack from ADD_TOOL and KB from session -} - -impl AnswerMode { - pub fn from_i32(value: i32) -> Self { - match value { - 0 => Self::Direct, - 1 => Self::WithTools, - 2 => Self::DocumentsOnly, - 3 => Self::WebSearch, - 4 => Self::Mixed, - _ => Self::Direct, - } - } -} - -/// Context from KB documents -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DocumentContext { - pub source: String, - pub content: String, - pub score: f32, - pub collection_name: String, -} - -/// Context from tools -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolContext { - pub tool_name: String, - pub description: String, - pub endpoint: String, -} - -/// Enhanced prompt with context -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EnhancedPrompt { - pub original_query: String, - pub system_prompt: String, - pub user_prompt: String, - pub document_contexts: Vec, - pub available_tools: Vec, - pub answer_mode: AnswerMode, -} - -/// Prompt processor that enhances queries with KB and tool context -pub struct PromptProcessor { - state: Arc, -} - -impl PromptProcessor { - pub fn new(state: Arc) -> Self { - Self { state } - } - - /// Process a user query and enhance it with context - pub async fn process_query( - &self, - session: &UserSession, - query: &str, - ) -> Result> { - let answer_mode = AnswerMode::from_i32(session.answer_mode); - - info!( - "Processing query in {:?} mode: {}", - answer_mode, - query.chars().take(50).collect::() - ); - - match answer_mode { - AnswerMode::Direct => self.process_direct(query).await, - AnswerMode::WithTools => self.process_with_tools(session, query).await, - AnswerMode::DocumentsOnly => self.process_documents_only(session, query).await, - AnswerMode::WebSearch => self.process_web_search(session, query).await, - AnswerMode::Mixed => self.process_mixed(session, query).await, - } - } - - /// Direct mode: no additional context - async fn process_direct( - &self, - query: &str, - ) -> Result> { - Ok(EnhancedPrompt { - original_query: query.to_string(), - system_prompt: "You are a helpful AI assistant.".to_string(), - user_prompt: query.to_string(), - document_contexts: Vec::new(), - available_tools: Vec::new(), - answer_mode: AnswerMode::Direct, - }) - } - - /// With tools mode: include available tools - async fn process_with_tools( - &self, - session: &UserSession, - query: &str, - ) -> Result> { - let tools = self.get_available_tools(session).await?; - - let system_prompt = if tools.is_empty() { - "You are a helpful AI assistant.".to_string() - } else { - format!( - "You are a helpful AI assistant with access to the following tools:\n{}", - self.format_tools_for_prompt(&tools) - ) - }; - - Ok(EnhancedPrompt { - original_query: query.to_string(), - system_prompt, - user_prompt: query.to_string(), - document_contexts: Vec::new(), - available_tools: tools, - answer_mode: AnswerMode::WithTools, - }) - } - - /// Documents only mode: search KB and use documents to answer - async fn process_documents_only( - &self, - session: &UserSession, - query: &str, - ) -> Result> { - let documents = self.search_kb_documents(session, query, 5).await?; - - let system_prompt = "You are a helpful AI assistant. Answer the user's question based ONLY on the provided documents. If the documents don't contain relevant information, say so.".to_string(); - - let user_prompt = if documents.is_empty() { - format!("Question: {}\n\nNo relevant documents found.", query) - } else { - format!( - "Question: {}\n\nRelevant documents:\n{}", - query, - self.format_documents_for_prompt(&documents) - ) - }; - - Ok(EnhancedPrompt { - original_query: query.to_string(), - system_prompt, - user_prompt, - document_contexts: documents, - available_tools: Vec::new(), - answer_mode: AnswerMode::DocumentsOnly, - }) - } - - /// Web search mode: include web search results - async fn process_web_search( - &self, - _session: &UserSession, - query: &str, - ) -> Result> { - // TODO: Implement web search integration - debug!("Web search mode not fully implemented yet"); - self.process_direct(query).await - } - - /// Mixed mode: combine KB documents and tools - async fn process_mixed( - &self, - session: &UserSession, - query: &str, - ) -> Result> { - // Get both documents and tools - let documents = self.search_kb_documents(session, query, 3).await?; - let tools = self.get_available_tools(session).await?; - - let mut system_parts = vec!["You are a helpful AI assistant.".to_string()]; - - if !documents.is_empty() { - system_parts.push( - "Use the provided documents as knowledge base to answer questions.".to_string(), - ); - } - - if !tools.is_empty() { - system_parts.push(format!( - "You have access to the following tools:\n{}", - self.format_tools_for_prompt(&tools) - )); - } - - let system_prompt = system_parts.join("\n\n"); - - let user_prompt = if documents.is_empty() { - query.to_string() - } else { - format!( - "Context from knowledge base:\n{}\n\nQuestion: {}", - self.format_documents_for_prompt(&documents), - query - ) - }; - - Ok(EnhancedPrompt { - original_query: query.to_string(), - system_prompt, - user_prompt, - document_contexts: documents, - available_tools: tools, - answer_mode: AnswerMode::Mixed, - }) - } - - /// Search KB documents for a query - async fn search_kb_documents( - &self, - session: &UserSession, - query: &str, - limit: usize, - ) -> Result, Box> { - // Get active KB collections from session context - let collections = self.get_active_collections(session).await?; - - if collections.is_empty() { - debug!("No active KB collections for session"); - return Ok(Vec::new()); - } - - let mut all_results = Vec::new(); - - // Search in each collection - for collection_name in collections { - debug!("Searching in collection: {}", collection_name); - - match search_similar(&self.state, &collection_name, query, limit).await { - Ok(results) => { - for result in results { - all_results.push(DocumentContext { - source: result.file_path, - content: result.chunk_text, - score: result.score, - collection_name: collection_name.clone(), - }); - } - } - Err(e) => { - error!("Failed to search collection {}: {}", collection_name, e); - } - } - } - - // Sort by score and limit - all_results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap()); - all_results.truncate(limit); - - info!("Found {} relevant documents", all_results.len()); - - Ok(all_results) - } - - /// Get active KB collections from session context - async fn get_active_collections( - &self, - session: &UserSession, - ) -> Result, Box> { - let mut collections = Vec::new(); - - // Check for active_kb_collection in context_data - if let Some(active_kb) = session.context_data.get("active_kb_collection") { - if let Some(name) = active_kb.as_str() { - let collection_name = format!("kb_{}_{}", session.bot_id, name); - collections.push(collection_name); - } - } - - // Check for temporary website collections - if let Some(temp_website) = session.context_data.get("temporary_website_collection") { - if let Some(name) = temp_website.as_str() { - collections.push(name.to_string()); - } - } - - // Check for additional collections from ADD_KB - if let Some(additional) = session.context_data.get("additional_kb_collections") { - if let Some(arr) = additional.as_array() { - for item in arr { - if let Some(name) = item.as_str() { - let collection_name = format!("kb_{}_{}", session.bot_id, name); - collections.push(collection_name); - } - } - } - } - - Ok(collections) - } - - /// Get available tools from session context - async fn get_available_tools( - &self, - session: &UserSession, - ) -> Result, Box> { - let mut tools = Vec::new(); - - // Check for tools in session context - if let Some(tools_data) = session.context_data.get("available_tools") { - if let Some(arr) = tools_data.as_array() { - for item in arr { - if let (Some(name), Some(desc), Some(endpoint)) = ( - item.get("name").and_then(|v| v.as_str()), - item.get("description").and_then(|v| v.as_str()), - item.get("endpoint").and_then(|v| v.as_str()), - ) { - tools.push(ToolContext { - tool_name: name.to_string(), - description: desc.to_string(), - endpoint: endpoint.to_string(), - }); - } - } - } - } - - // Load all tools associated with this session from session_tool_associations - if let Ok(mut conn) = self.state.conn.lock() { - match get_session_tools(&mut *conn, &session.id) { - Ok(session_tools) => { - info!( - "Loaded {} tools from session_tool_associations for session {}", - session_tools.len(), - session.id - ); - - for tool_name in session_tools { - // Add the tool if not already in list - if !tools.iter().any(|t| t.tool_name == tool_name) { - tools.push(ToolContext { - tool_name: tool_name.clone(), - description: format!("Tool: {}", tool_name), - endpoint: format!("/default/{}", tool_name), - }); - } - } - } - Err(e) => { - error!("Failed to load session tools: {}", e); - } - } - } else { - error!("Failed to acquire database lock for loading session tools"); - } - - // Also check for legacy current_tool (backward compatibility) - if let Some(current_tool) = &session.current_tool { - // Add the current tool if not already in list - if !tools.iter().any(|t| &t.tool_name == current_tool) { - tools.push(ToolContext { - tool_name: current_tool.clone(), - description: format!("Legacy tool: {}", current_tool), - endpoint: format!("/default/{}", current_tool), - }); - } - } - - debug!("Found {} available tools", tools.len()); - - Ok(tools) - } - - /// Format documents for inclusion in prompt - fn format_documents_for_prompt(&self, documents: &[DocumentContext]) -> String { - documents - .iter() - .enumerate() - .map(|(idx, doc)| { - format!( - "[Document {}] (Source: {}, Relevance: {:.2})\n{}", - idx + 1, - doc.source, - doc.score, - doc.content.chars().take(500).collect::() - ) - }) - .collect::>() - .join("\n\n") - } - - /// Format tools for inclusion in prompt - fn format_tools_for_prompt(&self, tools: &[ToolContext]) -> String { - tools - .iter() - .map(|tool| format!("- {}: {}", tool.tool_name, tool.description)) - .collect::>() - .join("\n") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_answer_mode_from_i32() { - assert_eq!(AnswerMode::from_i32(0), AnswerMode::Direct); - assert_eq!(AnswerMode::from_i32(1), AnswerMode::WithTools); - assert_eq!(AnswerMode::from_i32(2), AnswerMode::DocumentsOnly); - assert_eq!(AnswerMode::from_i32(3), AnswerMode::WebSearch); - assert_eq!(AnswerMode::from_i32(4), AnswerMode::Mixed); - assert_eq!(AnswerMode::from_i32(99), AnswerMode::Direct); // Default - } - - #[test] - fn test_format_documents() { - let processor = PromptProcessor::new(Arc::new(AppState::default())); - - let docs = vec![ - DocumentContext { - source: "test.pdf".to_string(), - content: "This is test content".to_string(), - score: 0.95, - collection_name: "test_collection".to_string(), - }, - DocumentContext { - source: "another.pdf".to_string(), - content: "More content here".to_string(), - score: 0.85, - collection_name: "test_collection".to_string(), - }, - ]; - - let formatted = processor.format_documents_for_prompt(&docs); - - assert!(formatted.contains("[Document 1]")); - assert!(formatted.contains("[Document 2]")); - assert!(formatted.contains("test.pdf")); - assert!(formatted.contains("This is test content")); - } - - #[test] - fn test_format_tools() { - let processor = PromptProcessor::new(Arc::new(AppState::default())); - - let tools = vec![ - ToolContext { - tool_name: "enrollment".to_string(), - description: "Enroll a user".to_string(), - endpoint: "/default/enrollment".to_string(), - }, - ToolContext { - tool_name: "pricing".to_string(), - description: "Get product pricing".to_string(), - endpoint: "/default/pricing".to_string(), - }, - ]; - - let formatted = processor.format_tools_for_prompt(&tools); - - assert!(formatted.contains("enrollment")); - assert!(formatted.contains("Enroll a user")); - assert!(formatted.contains("pricing")); - } -} diff --git a/src/drive_monitor/mod.rs b/src/drive_monitor/mod.rs index 58d17c9a..b83f513b 100644 --- a/src/drive_monitor/mod.rs +++ b/src/drive_monitor/mod.rs @@ -71,12 +71,6 @@ impl DriveMonitor { } }; - if !crate::llm::local::is_server_running(&llm_url).await || - !crate::llm::local::is_server_running(&embedding_url).await { - trace!("LLM servers not ready - llm: {}, embedding: {}", llm_url, embedding_url); - return; - } - let mut tick = interval(Duration::from_secs(30)); loop { tick.tick().await; diff --git a/src/llm/local.rs b/src/llm/local.rs index 4fc331d5..28c108fd 100644 --- a/src/llm/local.rs +++ b/src/llm/local.rs @@ -252,7 +252,7 @@ pub async fn start_embedding_server( if cfg!(windows) { let mut cmd = tokio::process::Command::new("cmd"); cmd.arg("/c").arg(format!( - "cd {} && .\\llama-server.exe -m {} --log-disable --host 0.0.0.0 --port {} --embedding --n-gpu-layers 99 >../../../../logs/llm/stdout.log", + "cd {} && .\\llama-server.exe -m {} --host 0.0.0.0 --port {} --embedding --n-gpu-layers 99 >../../../../logs/llm/stdout.log", llama_cpp_path, model_path, port )); cmd.spawn()?; diff --git a/src/main.rs b/src/main.rs index 82a09e6e..a7ef3712 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![allow(dead_code)] +#![allow(warnings)] #![cfg_attr(feature = "desktop", windows_subsystem = "windows")] use actix_cors::Cors; diff --git a/src/session/mod.rs b/src/session/mod.rs index 1246d6de..f3c3b863 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -239,12 +239,10 @@ impl SessionManager { session_id: &Uuid, user_id: &Uuid, ) -> Result> { - // Bring the Redis command trait into scope so we can call `get`. use redis::Commands; - let redis_key = format!("context:{}:{}", user_id, session_id); + let base_key = format!("context:{}:{}", user_id, session_id); if let Some(redis_client) = &self.redis { - // Attempt to obtain a Redis connection; log and ignore errors let conn_option = redis_client .get_connection() .map_err(|e| { @@ -254,26 +252,39 @@ impl SessionManager { .ok(); if let Some(mut connection) = conn_option { - match connection.get::<_, Option>(&redis_key) { - Ok(Some(context)) => { - debug!( - "Retrieved context from Cache for key {}: {} chars", - redis_key, - context.len() - ); - return Ok(context); + // First cache trip: get context name + match connection.get::<_, Option>(&base_key) { + Ok(Some(context_name)) => { + debug!("Found context name '{}' for key {}", context_name, base_key); + // Second cache trip: get actual context value + let full_key = format!("context:{}:{}:{}", user_id, session_id, context_name); + match connection.get::<_, Option>(&full_key) { + Ok(Some(context_value)) => { + debug!( + "Retrieved context value from Cache for key {}: {} chars", + full_key, + context_value.len() + ); + return Ok(context_value); + } + Ok(None) => { + debug!("No context value found for key {}", full_key); + } + Err(e) => { + warn!("Failed to retrieve context value from Cache: {}", e); + } + } } Ok(None) => { - debug!("No context found in Cache for key {}", redis_key); + debug!("No context name found for key {}", base_key); } Err(e) => { - warn!("Failed to retrieve context from Cache: {}", e); + warn!("Failed to retrieve context name from Cache: {}", e); } } } } - // If no context found, return empty string Ok(String::new()) } diff --git a/templates/default.gbai/default.gbot/config.csv b/templates/default.gbai/default.gbot/config.csv index ecc06a96..63024780 100644 --- a/templates/default.gbai/default.gbot/config.csv +++ b/templates/default.gbai/default.gbot/config.csv @@ -17,7 +17,7 @@ llm-server-host,0.0.0.0 llm-server-port,8081 llm-server-gpu-layers,35 llm-server-n-moe,23 -llm-server-ctx-size,16000 +llm-server-ctx-size,512 llm-server-parallel,8 llm-server-cont-batching,true llm-server-mlock,true diff --git a/web/html/index.html b/web/html/index.html index efd35648..7e044fa0 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -915,7 +915,7 @@ } .theme-toggle { - top: 16px; + top: 24px; right: 60px; width: 44px; height: 44px; @@ -959,7 +959,7 @@ } .theme-toggle { - top: 16px; + top: 24px; right: 60px; width: 40px; height: 40px; @@ -1075,6 +1075,7 @@ let isUserScrolling = false; let autoScrollEnabled = true; let currentTheme = 'dark'; + let isContextChange = false; const messagesDiv = document.getElementById("messages"); const input = document.getElementById("messageInput"); @@ -1207,7 +1208,7 @@ } if (percentage >= 50) { - contextIndicator.style.display = "block"; + //contextIndicator.style.display = "block"; } else { contextIndicator.style.display = "none"; } @@ -1251,7 +1252,9 @@ hasReceivedInitialMessage = false; connectWebSocket(); loadSessions(); - document.getElementById("messages").innerHTML = ` + + // Clear messages and show empty state + messagesDiv.innerHTML = `
General Bots Logo @@ -1260,9 +1263,22 @@

Seu assistente de IA avançado

`; + + // Clear suggestions + clearSuggestions(); + + // Reset context usage updateContextUsage(0); + + // Stop voice if active if (isVoiceMode) { - await startVoiceSession(); + await stopVoiceSession(); + isVoiceMode = false; + const voiceToggle = document.getElementById("voiceToggle"); + voiceToggle.textContent = "🎤 Modo Voz"; + voiceToggle.classList.remove("recording"); + const voiceStatus = document.getElementById("voiceStatus"); + voiceStatus.style.display = "none"; } if (window.innerWidth <= 768) { @@ -1338,6 +1354,12 @@ return; } + // Check if this is a context change message and skip processing + if (response.message_type === 5) { + isContextChange = true; + return; + } + processMessageContent(response); }; @@ -1377,6 +1399,12 @@ } function processMessageContent(response) { + // Skip processing if this is a context change + if (isContextChange) { + isContextChange = false; + return; + } + const emptyState = document.getElementById("emptyState"); if (emptyState) { emptyState.remove(); @@ -1685,6 +1713,14 @@ return div.innerHTML; } + function clearSuggestions() { + const footer = document.querySelector('footer'); + const container = footer.querySelector('.suggestions-container'); + if (container) { + container.innerHTML = ''; + } + } + function handleSuggestions(suggestions) { const footer = document.querySelector('footer'); let container = footer.querySelector('.suggestions-container'); @@ -1695,7 +1731,9 @@ footer.insertBefore(container, footer.firstChild); } + // Clear existing suggestions before adding new ones container.innerHTML = ''; + suggestions.forEach(s => { const btn = document.createElement('button'); btn.textContent = s.text; @@ -1750,7 +1788,7 @@ await pendingContextChange; const contextIndicator = document.getElementById('contextIndicator'); if (contextIndicator) { - contextIndicator.style.display = 'block'; + //contextIndicator.style.display = 'block'; document.getElementById('contextPercentage').textContent = context; } } else {