From 752d45531245fe6de5e18f0311cf14a26160f99a Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Fri, 28 Nov 2025 18:15:09 -0300 Subject: [PATCH] Replace magic numbers with MessageType constants This commit replaces all hardcoded message type integers (0, 1, 2, 3, 4, 5) with named constants from a new MessageType module, improving code readability and maintainability across the codebase. --- src/basic/keywords/hear_talk.rs | 3 +- src/basic/keywords/universal_messaging.rs | 9 +- src/console/chat_panel.rs | 211 +++++++++++----------- src/core/bot/mod.rs | 9 +- src/core/bot/multimedia.rs | 13 +- src/core/shared/mod.rs | 1 + src/core/shared/models.rs | 7 +- src/drive/drive_monitor/mod.rs | 3 +- ui/minimal/index.html | 203 +++++++++++++++++---- 9 files changed, 302 insertions(+), 157 deletions(-) diff --git a/src/basic/keywords/hear_talk.rs b/src/basic/keywords/hear_talk.rs index c8103ca8..90c0a3ed 100644 --- a/src/basic/keywords/hear_talk.rs +++ b/src/basic/keywords/hear_talk.rs @@ -1,3 +1,4 @@ +use crate::shared::message_types::MessageType; use crate::shared::models::{BotResponse, UserSession}; use crate::shared::state::AppState; use log::{error, trace}; @@ -84,7 +85,7 @@ pub async fn execute_talk( session_id: user_session.id.to_string(), channel: "web".to_string(), content: message, - message_type: 1, + message_type: MessageType::USER, stream_token: None, is_complete: true, suggestions, diff --git a/src/basic/keywords/universal_messaging.rs b/src/basic/keywords/universal_messaging.rs index 6327edc0..f4c63713 100644 --- a/src/basic/keywords/universal_messaging.rs +++ b/src/basic/keywords/universal_messaging.rs @@ -1,6 +1,7 @@ use crate::core::bot::channels::{ instagram::InstagramAdapter, teams::TeamsAdapter, whatsapp::WhatsAppAdapter, ChannelAdapter, }; +use crate::shared::message_types::MessageType; use crate::shared::models::UserSession; use crate::shared::state::AppState; use log::{error, trace}; @@ -200,7 +201,7 @@ async fn send_message_to_recipient( user_id: recipient_id.clone(), channel: "whatsapp".to_string(), content: message.to_string(), - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: vec![], @@ -218,7 +219,7 @@ async fn send_message_to_recipient( user_id: recipient_id.clone(), channel: "instagram".to_string(), content: message.to_string(), - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: vec![], @@ -236,7 +237,7 @@ async fn send_message_to_recipient( user_id: recipient_id.clone(), channel: "teams".to_string(), content: message.to_string(), - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: vec![], @@ -588,7 +589,7 @@ async fn send_web_message( session_id: session_id.to_string(), channel: "web".to_string(), content: message.to_string(), - message_type: 1, + message_type: MessageType::USER, stream_token: None, is_complete: true, suggestions: Vec::new(), diff --git a/src/console/chat_panel.rs b/src/console/chat_panel.rs index 67c5bbaa..cec48d8d 100644 --- a/src/console/chat_panel.rs +++ b/src/console/chat_panel.rs @@ -1,113 +1,114 @@ +use crate::shared::message_types::MessageType; +use crate::shared::models::BotResponse; +use crate::shared::state::AppState; use color_eyre::Result; use std::sync::Arc; -use crate::shared::state::AppState; -use crate::shared::models::BotResponse; use tokio::sync::mpsc; use uuid::Uuid; pub struct ChatPanel { - pub messages: Vec, - pub input_buffer: String, - pub session_id: Uuid, - pub user_id: Uuid, - pub response_rx: Option>, + pub messages: Vec, + pub input_buffer: String, + pub session_id: Uuid, + pub user_id: Uuid, + pub response_rx: Option>, } impl ChatPanel { - pub fn new(_app_state: Arc) -> Self { - Self { - messages: vec!["Welcome to General Bots Console Chat!".to_string()], - input_buffer: String::new(), - session_id: Uuid::new_v4(), - user_id: Uuid::new_v4(), - response_rx: None, - } - } - pub fn add_char(&mut self, c: char) { - self.input_buffer.push(c); - } - pub fn backspace(&mut self) { - self.input_buffer.pop(); - } - pub async fn send_message(&mut self, bot_name: &str, app_state: &Arc) -> Result<()> { - if self.input_buffer.trim().is_empty() { - return Ok(()); - } - let message = self.input_buffer.clone(); - self.messages.push(format!("You: {}", message)); - self.input_buffer.clear(); - let bot_id = self.get_bot_id(bot_name, app_state).await?; - let user_message = crate::shared::models::UserMessage { - bot_id: bot_id.to_string(), - user_id: self.user_id.to_string(), - session_id: self.session_id.to_string(), - channel: "console".to_string(), - content: message, - message_type: 1, - media_url: None, - timestamp: chrono::Utc::now(), - context_name: None, - }; - let (tx, rx) = mpsc::channel::(100); - self.response_rx = Some(rx); -let orchestrator = crate::bot::BotOrchestrator::new(app_state.clone()); -let _ = orchestrator.stream_response(user_message, tx).await; - Ok(()) - } - pub async fn poll_response(&mut self, _bot_name: &str) -> Result<()> { - if let Some(rx) = &mut self.response_rx { - while let Ok(response) = rx.try_recv() { - if !response.content.is_empty() && !response.is_complete { - if let Some(last_msg) = self.messages.last_mut() { - if last_msg.starts_with("Bot: ") { - last_msg.push_str(&response.content); - } else { - self.messages.push(format!("Bot: {}", response.content)); - } - } else { - self.messages.push(format!("Bot: {}", response.content)); - } - } - if response.is_complete && response.content.is_empty() { - break; - } - } - } - Ok(()) - } - async fn get_bot_id(&self, bot_name: &str, app_state: &Arc) -> Result { - use crate::shared::models::schema::bots::dsl::*; - use diesel::prelude::*; - let mut conn = app_state.conn.get().unwrap(); - let bot_id = bots - .filter(name.eq(bot_name)) - .select(id) - .first::(&mut *conn)?; - Ok(bot_id) - } - pub fn render(&self) -> String { - let mut lines = Vec::new(); - lines.push("╔═══════════════════════════════════════╗".to_string()); - lines.push("║ CONVERSATION ║".to_string()); - lines.push("╚═══════════════════════════════════════╝".to_string()); - lines.push("".to_string()); - let visible_start = if self.messages.len() > 15 { - self.messages.len() - 15 - } else { - 0 - }; - for msg in &self.messages[visible_start..] { - if msg.starts_with("You: ") { - lines.push(format!(" {}", msg)); - } else if msg.starts_with("Bot: ") { - lines.push(format!(" {}", msg)); - } else { - lines.push(format!(" {}", msg)); - } - } - lines.push("".to_string()); - lines.push("─────────────────────────────────────────".to_string()); - lines.push(format!(" > {}_", self.input_buffer)); - lines.push("".to_string()); - lines.push(" Enter: Send | Tab: Switch Panel".to_string()); - lines.join("\n") - } + pub fn new(_app_state: Arc) -> Self { + Self { + messages: vec!["Welcome to General Bots Console Chat!".to_string()], + input_buffer: String::new(), + session_id: Uuid::new_v4(), + user_id: Uuid::new_v4(), + response_rx: None, + } + } + pub fn add_char(&mut self, c: char) { + self.input_buffer.push(c); + } + pub fn backspace(&mut self) { + self.input_buffer.pop(); + } + pub async fn send_message(&mut self, bot_name: &str, app_state: &Arc) -> Result<()> { + if self.input_buffer.trim().is_empty() { + return Ok(()); + } + let message = self.input_buffer.clone(); + self.messages.push(format!("You: {}", message)); + self.input_buffer.clear(); + let bot_id = self.get_bot_id(bot_name, app_state).await?; + let user_message = crate::shared::models::UserMessage { + bot_id: bot_id.to_string(), + user_id: self.user_id.to_string(), + session_id: self.session_id.to_string(), + channel: "console".to_string(), + content: message, + message_type: MessageType::USER, + media_url: None, + timestamp: chrono::Utc::now(), + context_name: None, + }; + let (tx, rx) = mpsc::channel::(100); + self.response_rx = Some(rx); + let orchestrator = crate::bot::BotOrchestrator::new(app_state.clone()); + let _ = orchestrator.stream_response(user_message, tx).await; + Ok(()) + } + pub async fn poll_response(&mut self, _bot_name: &str) -> Result<()> { + if let Some(rx) = &mut self.response_rx { + while let Ok(response) = rx.try_recv() { + if !response.content.is_empty() && !response.is_complete { + if let Some(last_msg) = self.messages.last_mut() { + if last_msg.starts_with("Bot: ") { + last_msg.push_str(&response.content); + } else { + self.messages.push(format!("Bot: {}", response.content)); + } + } else { + self.messages.push(format!("Bot: {}", response.content)); + } + } + if response.is_complete && response.content.is_empty() { + break; + } + } + } + Ok(()) + } + async fn get_bot_id(&self, bot_name: &str, app_state: &Arc) -> Result { + use crate::shared::models::schema::bots::dsl::*; + use diesel::prelude::*; + let mut conn = app_state.conn.get().unwrap(); + let bot_id = bots + .filter(name.eq(bot_name)) + .select(id) + .first::(&mut *conn)?; + Ok(bot_id) + } + pub fn render(&self) -> String { + let mut lines = Vec::new(); + lines.push("╔═══════════════════════════════════════╗".to_string()); + lines.push("║ CONVERSATION ║".to_string()); + lines.push("╚═══════════════════════════════════════╝".to_string()); + lines.push("".to_string()); + let visible_start = if self.messages.len() > 15 { + self.messages.len() - 15 + } else { + 0 + }; + for msg in &self.messages[visible_start..] { + if msg.starts_with("You: ") { + lines.push(format!(" {}", msg)); + } else if msg.starts_with("Bot: ") { + lines.push(format!(" {}", msg)); + } else { + lines.push(format!(" {}", msg)); + } + } + lines.push("".to_string()); + lines.push("─────────────────────────────────────────".to_string()); + lines.push(format!(" > {}_", self.input_buffer)); + lines.push("".to_string()); + lines.push(" Enter: Send | Tab: Switch Panel".to_string()); + lines.join("\n") + } } diff --git a/src/core/bot/mod.rs b/src/core/bot/mod.rs index 14bc9d76..d4338b63 100644 --- a/src/core/bot/mod.rs +++ b/src/core/bot/mod.rs @@ -7,6 +7,7 @@ use crate::llm::llm_models; use crate::llm::OpenAIClient; #[cfg(feature = "nvidia")] use crate::nvidia::get_system_metrics; +use crate::shared::message_types::MessageType; use crate::shared::models::{BotResponse, UserMessage, UserSession}; use crate::shared::state::AppState; use axum::extract::ws::{Message, WebSocket}; @@ -209,7 +210,7 @@ impl BotOrchestrator { session_id: message.session_id.clone(), channel: message.channel.clone(), content: processed, - message_type: 2, + message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: false, suggestions: Vec::new(), @@ -246,7 +247,7 @@ impl BotOrchestrator { session_id: message.session_id.clone(), channel: message.channel.clone(), content: processed, - message_type: 2, + message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: false, suggestions: Vec::new(), @@ -281,7 +282,7 @@ impl BotOrchestrator { session_id: message.session_id.clone(), channel: message.channel.clone(), content: chunk, - message_type: 2, + message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: false, suggestions: Vec::new(), @@ -314,7 +315,7 @@ impl BotOrchestrator { session_id: message.session_id, channel: message.channel, content: full_response, - message_type: 2, + message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: true, suggestions: Vec::new(), diff --git a/src/core/bot/multimedia.rs b/src/core/bot/multimedia.rs index d8339e80..c1593030 100644 --- a/src/core/bot/multimedia.rs +++ b/src/core/bot/multimedia.rs @@ -11,6 +11,7 @@ //! - Storage abstraction for S3-compatible backends //! - URL processing and validation +use crate::shared::message_types::MessageType; use crate::shared::models::{BotResponse, UserMessage}; use anyhow::Result; use async_trait::async_trait; @@ -154,7 +155,7 @@ impl MultimediaHandler for DefaultMultimediaHandler { session_id: session_id.to_string(), channel: "multimedia".to_string(), content, - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: Vec::new(), @@ -182,7 +183,7 @@ impl MultimediaHandler for DefaultMultimediaHandler { session_id: session_id.to_string(), channel: "multimedia".to_string(), content: response_content, - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: Vec::new(), @@ -215,7 +216,7 @@ impl MultimediaHandler for DefaultMultimediaHandler { session_id: session_id.to_string(), channel: "multimedia".to_string(), content: response_content, - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: Vec::new(), @@ -248,7 +249,7 @@ impl MultimediaHandler for DefaultMultimediaHandler { session_id: session_id.to_string(), channel: "multimedia".to_string(), content: response_content, - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: Vec::new(), @@ -277,7 +278,7 @@ impl MultimediaHandler for DefaultMultimediaHandler { session_id: session_id.to_string(), channel: "multimedia".to_string(), content: response_content, - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: Vec::new(), @@ -294,7 +295,7 @@ impl MultimediaHandler for DefaultMultimediaHandler { session_id: session_id.to_string(), channel: "multimedia".to_string(), content: "Message received and processing...".to_string(), - message_type: 0, + message_type: MessageType::EXTERNAL, stream_token: None, is_complete: true, suggestions: Vec::new(), diff --git a/src/core/shared/mod.rs b/src/core/shared/mod.rs index cb05772f..3b1bb98b 100644 --- a/src/core/shared/mod.rs +++ b/src/core/shared/mod.rs @@ -1,5 +1,6 @@ pub mod admin; pub mod analytics; +pub mod message_types; pub mod models; pub mod state; pub mod utils; diff --git a/src/core/shared/models.rs b/src/core/shared/models.rs index fc41db1d..7280508b 100644 --- a/src/core/shared/models.rs +++ b/src/core/shared/models.rs @@ -1,3 +1,4 @@ +use crate::shared::message_types::MessageType; use chrono::{DateTime, Utc}; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -51,7 +52,7 @@ pub struct UserMessage { pub session_id: String, pub channel: String, pub content: String, - pub message_type: i32, + pub message_type: MessageType, pub media_url: Option, pub timestamp: DateTime, pub context_name: Option, @@ -68,7 +69,7 @@ pub struct BotResponse { pub session_id: String, pub channel: String, pub content: String, - pub message_type: i32, + pub message_type: MessageType, pub stream_token: Option, pub is_complete: bool, pub suggestions: Vec, @@ -90,7 +91,7 @@ impl BotResponse { session_id: session_id.to_string(), channel, content, - message_type: 2, + message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: true, suggestions: Vec::new(), diff --git a/src/drive/drive_monitor/mod.rs b/src/drive/drive_monitor/mod.rs index cae681a2..7e236d20 100644 --- a/src/drive/drive_monitor/mod.rs +++ b/src/drive/drive_monitor/mod.rs @@ -3,6 +3,7 @@ use crate::basic::compiler::BasicCompiler; use crate::config::ConfigManager; use crate::core::kb::KnowledgeBaseManager; +use crate::shared::message_types::MessageType; use crate::shared::state::AppState; use aws_sdk_s3::Client; use log::{debug, error, info}; @@ -345,7 +346,7 @@ impl DriveMonitor { session_id: session_id.clone(), channel: "web".to_string(), content: serde_json::to_string(&theme_data)?, - message_type: 2, + message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: true, suggestions: Vec::new(), diff --git a/ui/minimal/index.html b/ui/minimal/index.html index 58e05c71..0b7c1a3e 100644 --- a/ui/minimal/index.html +++ b/ui/minimal/index.html @@ -4,6 +4,17 @@ General Bots + @@ -733,12 +744,21 @@ isUserScrolling = false, autoScrollEnabled = true, isContextChange = false; - const maxReconnectAttempts = 5, - messagesDiv = document.getElementById("messages"), - input = document.getElementById("messageInput"), - sendBtn = document.getElementById("sendBtn"), - voiceBtn = document.getElementById("voiceBtn"), - connectionStatus = document.getElementById("connectionStatus"), + const maxReconnectAttempts = 5; + let messagesDiv = document.getElementById("messages"); + let input = document.getElementById("messageInput"); + let sendBtn = document.getElementById("sendBtn"); + let voiceBtn = document.getElementById("voiceBtn"); + + // Debug element initialization + console.log("Element initialization:"); + console.log("messagesDiv:", messagesDiv); + console.log("input:", input); + console.log("sendBtn:", sendBtn); + console.log("voiceBtn:", voiceBtn); + + const connectionStatus = + document.getElementById("connectionStatus"), flashOverlay = document.getElementById("flashOverlay"), suggestionsContainer = document.getElementById("suggestions"), floatLogo = document.getElementById("floatLogo"), @@ -878,17 +898,29 @@ async function initializeAuth() { try { + console.log("Starting auth initialization..."); updateConnectionStatus("connecting"); const p = window.location.pathname .split("/") .filter((s) => s), - b = p.length > 0 ? p[0] : "default", - r = await fetch( - `http://localhost:8080/api/auth?bot_name=${encodeURIComponent(b)}`, + b = p.length > 0 ? p[0] : "default"; + console.log("Bot name:", b); + const r = await fetch( + `/api/auth?bot_name=${encodeURIComponent(b)}`, ), a = await r.json(); + console.log("Auth response:", a); currentUserId = a.user_id; currentSessionId = a.session_id; + currentBotId = a.bot_id || "default_bot"; + console.log( + "Auth initialized - User:", + currentUserId, + "Session:", + currentSessionId, + "Bot:", + currentBotId, + ); connectWebSocket(); loadSessions(); } catch (e) { @@ -984,6 +1016,7 @@ const u = getWebSocketUrl(); ws = new WebSocket(u); ws.onmessage = function (e) { + console.log("WebSocket message received:", e.data); try { if (!e.data || e.data.trim() === "") { console.warn("Empty WebSocket message received"); @@ -997,19 +1030,35 @@ if (r.bot_id) { currentBotId = r.bot_id; } - if (r.message_type === 2) { - try { - const d = JSON.parse(r.content); - handleEvent(d.event, d.data); - } catch (parseErr) { - console.error( - "Failed to parse event content:", - parseErr, - ); + // BOT_RESPONSE type is used for both regular streaming content and special event messages + // Event messages have JSON-encoded content with 'event' and 'data' properties + // Regular messages have plain text content that should be displayed directly + if (r.message_type === MessageType.BOT_RESPONSE) { + // Check if content looks like JSON (starts with { or [) + const contentTrimmed = r.content.trim(); + if ( + contentTrimmed.startsWith("{") || + contentTrimmed.startsWith("[") + ) { + try { + const d = JSON.parse(r.content); + if (d.event && d.data) { + // This is an event message + handleEvent(d.event, d.data); + return; + } + } catch (parseErr) { + // Not a valid event message, treat as regular content + console.debug( + "Content is not an event message, processing as regular message", + ); + } } + // Process as regular message content + processMessageContent(r); return; } - if (r.message_type === 5) { + if (r.message_type === MessageType.CONTEXT_CHANGE) { isContextChange = true; return; } @@ -1024,7 +1073,10 @@ } }; ws.onopen = function () { - console.log("Connected to WebSocket"); + console.log( + "Connected to WebSocket, readyState:", + ws.readyState, + ); updateConnectionStatus("connected"); reconnectAttempts = 0; hasReceivedInitialMessage = false; @@ -1218,7 +1270,7 @@ session_id: currentSessionId, channel: "web", content: "continue", - message_type: 3, + message_type: MessageType.CONTINUE, media_url: null, timestamp: new Date().toISOString(), }; @@ -1327,7 +1379,8 @@ const h = (e) => { const d = JSON.parse(e.data); if ( - d.message_type === 5 && + d.message_type === + MessageType.CONTEXT_CHANGE && d.context_name === c ) { ws.removeEventListener("message", h); @@ -1341,7 +1394,7 @@ session_id: currentSessionId, channel: "web", content: t, - message_type: 4, + message_type: MessageType.SUGGESTION, is_suggestion: true, context_name: c, timestamp: new Date().toISOString(), @@ -1367,12 +1420,42 @@ } async function sendMessage() { + console.log("=== sendMessage called ==="); + console.log("input element:", input); + console.log( + "input.value:", + input ? input.value : "input is null", + ); + if (pendingContextChange) { await pendingContextChange; pendingContextChange = null; } + + if (!input) { + console.error("Input element is null!"); + return; + } + const m = input.value.trim(); - if (!m || !ws || ws.readyState !== WebSocket.OPEN) { + console.log( + "Attempting to send message:", + m, + "WS state:", + ws ? ws.readyState : "no ws", + "WebSocket.OPEN value:", + WebSocket.OPEN, + ); + + if (!m) { + console.log("Message is empty, not sending"); + return; + } + + if (!ws || ws.readyState !== WebSocket.OPEN) { + console.log( + "WebSocket not connected, attempting reconnect", + ); if (!ws || ws.readyState !== WebSocket.OPEN) { showWarning( "Conexão não disponível. Tentando reconectar...", @@ -1384,27 +1467,44 @@ if (isThinking) { hideThinkingIndicator(); } + + console.log("Adding message to UI"); addMessage("user", m); + + console.log("Building message data object"); + console.log("currentBotId:", currentBotId); + console.log("currentUserId:", currentUserId); + console.log("currentSessionId:", currentSessionId); + const d = { - bot_id: currentBotId, + bot_id: currentBotId || "default_bot", user_id: currentUserId, session_id: currentSessionId, channel: "web", content: m, - message_type: 1, + message_type: MessageType.USER, media_url: null, timestamp: new Date().toISOString(), }; - ws.send(JSON.stringify(d)); + console.log("Message data object:", JSON.stringify(d, null, 2)); + + try { + const messageString = JSON.stringify(d); + console.log("Stringified message:", messageString); + console.log("About to call ws.send()"); + ws.send(messageString); + console.log("ws.send() completed successfully"); + } catch (error) { + console.error("Error sending message:", error); + console.error("Error stack:", error.stack); + } + + console.log("Clearing input field"); input.value = ""; input.focus(); + console.log("=== sendMessage completed ==="); } - sendBtn.onclick = sendMessage; - input.addEventListener("keypress", (e) => { - if (e.key === "Enter") sendMessage(); - }); - async function toggleVoiceMode() { isVoiceMode = !isVoiceMode; const v = document.getElementById("voiceToggle"); @@ -1569,7 +1669,44 @@ scrollToBottomBtn.classList.remove("visible"); } - window.addEventListener("load", initializeAuth); + window.addEventListener("load", () => { + console.log("Page loaded, initializing..."); + + // Re-get elements after DOM is ready + messagesDiv = document.getElementById("messages"); + input = document.getElementById("messageInput"); + sendBtn = document.getElementById("sendBtn"); + voiceBtn = document.getElementById("voiceBtn"); + + console.log("After load - input:", !!input); + console.log("After load - sendBtn:", !!sendBtn); + + // Attach event listeners after DOM is ready + if (sendBtn) { + sendBtn.onclick = () => { + console.log("Send button clicked!"); + sendMessage(); + }; + console.log("sendBtn.onclick attached"); + } else { + console.error("sendBtn element not found!"); + } + + if (input) { + input.addEventListener("keypress", (e) => { + console.log("Key pressed:", e.key); + if (e.key === "Enter") { + console.log("Enter key detected, sending message"); + sendMessage(); + } + }); + console.log("input keypress listener attached"); + } else { + console.error("input element not found!"); + } + + initializeAuth(); + }); window.addEventListener("focus", function () { if (!ws || ws.readyState !== WebSocket.OPEN) { connectWebSocket();