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.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-28 18:15:09 -03:00
parent 2dca1664dd
commit 752d455312
9 changed files with 302 additions and 157 deletions

View file

@ -1,3 +1,4 @@
use crate::shared::message_types::MessageType;
use crate::shared::models::{BotResponse, UserSession}; use crate::shared::models::{BotResponse, UserSession};
use crate::shared::state::AppState; use crate::shared::state::AppState;
use log::{error, trace}; use log::{error, trace};
@ -84,7 +85,7 @@ pub async fn execute_talk(
session_id: user_session.id.to_string(), session_id: user_session.id.to_string(),
channel: "web".to_string(), channel: "web".to_string(),
content: message, content: message,
message_type: 1, message_type: MessageType::USER,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions, suggestions,

View file

@ -1,6 +1,7 @@
use crate::core::bot::channels::{ use crate::core::bot::channels::{
instagram::InstagramAdapter, teams::TeamsAdapter, whatsapp::WhatsAppAdapter, ChannelAdapter, instagram::InstagramAdapter, teams::TeamsAdapter, whatsapp::WhatsAppAdapter, ChannelAdapter,
}; };
use crate::shared::message_types::MessageType;
use crate::shared::models::UserSession; use crate::shared::models::UserSession;
use crate::shared::state::AppState; use crate::shared::state::AppState;
use log::{error, trace}; use log::{error, trace};
@ -200,7 +201,7 @@ async fn send_message_to_recipient(
user_id: recipient_id.clone(), user_id: recipient_id.clone(),
channel: "whatsapp".to_string(), channel: "whatsapp".to_string(),
content: message.to_string(), content: message.to_string(),
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: vec![], suggestions: vec![],
@ -218,7 +219,7 @@ async fn send_message_to_recipient(
user_id: recipient_id.clone(), user_id: recipient_id.clone(),
channel: "instagram".to_string(), channel: "instagram".to_string(),
content: message.to_string(), content: message.to_string(),
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: vec![], suggestions: vec![],
@ -236,7 +237,7 @@ async fn send_message_to_recipient(
user_id: recipient_id.clone(), user_id: recipient_id.clone(),
channel: "teams".to_string(), channel: "teams".to_string(),
content: message.to_string(), content: message.to_string(),
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: vec![], suggestions: vec![],
@ -588,7 +589,7 @@ async fn send_web_message(
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "web".to_string(), channel: "web".to_string(),
content: message.to_string(), content: message.to_string(),
message_type: 1, message_type: MessageType::USER,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),

View file

@ -1,7 +1,8 @@
use crate::shared::message_types::MessageType;
use crate::shared::models::BotResponse;
use crate::shared::state::AppState;
use color_eyre::Result; use color_eyre::Result;
use std::sync::Arc; use std::sync::Arc;
use crate::shared::state::AppState;
use crate::shared::models::BotResponse;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use uuid::Uuid; use uuid::Uuid;
pub struct ChatPanel { pub struct ChatPanel {
@ -41,15 +42,15 @@ impl ChatPanel {
session_id: self.session_id.to_string(), session_id: self.session_id.to_string(),
channel: "console".to_string(), channel: "console".to_string(),
content: message, content: message,
message_type: 1, message_type: MessageType::USER,
media_url: None, media_url: None,
timestamp: chrono::Utc::now(), timestamp: chrono::Utc::now(),
context_name: None, context_name: None,
}; };
let (tx, rx) = mpsc::channel::<BotResponse>(100); let (tx, rx) = mpsc::channel::<BotResponse>(100);
self.response_rx = Some(rx); self.response_rx = Some(rx);
let orchestrator = crate::bot::BotOrchestrator::new(app_state.clone()); let orchestrator = crate::bot::BotOrchestrator::new(app_state.clone());
let _ = orchestrator.stream_response(user_message, tx).await; let _ = orchestrator.stream_response(user_message, tx).await;
Ok(()) Ok(())
} }
pub async fn poll_response(&mut self, _bot_name: &str) -> Result<()> { pub async fn poll_response(&mut self, _bot_name: &str) -> Result<()> {

View file

@ -7,6 +7,7 @@ use crate::llm::llm_models;
use crate::llm::OpenAIClient; use crate::llm::OpenAIClient;
#[cfg(feature = "nvidia")] #[cfg(feature = "nvidia")]
use crate::nvidia::get_system_metrics; use crate::nvidia::get_system_metrics;
use crate::shared::message_types::MessageType;
use crate::shared::models::{BotResponse, UserMessage, UserSession}; use crate::shared::models::{BotResponse, UserMessage, UserSession};
use crate::shared::state::AppState; use crate::shared::state::AppState;
use axum::extract::ws::{Message, WebSocket}; use axum::extract::ws::{Message, WebSocket};
@ -209,7 +210,7 @@ impl BotOrchestrator {
session_id: message.session_id.clone(), session_id: message.session_id.clone(),
channel: message.channel.clone(), channel: message.channel.clone(),
content: processed, content: processed,
message_type: 2, message_type: MessageType::BOT_RESPONSE,
stream_token: None, stream_token: None,
is_complete: false, is_complete: false,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -246,7 +247,7 @@ impl BotOrchestrator {
session_id: message.session_id.clone(), session_id: message.session_id.clone(),
channel: message.channel.clone(), channel: message.channel.clone(),
content: processed, content: processed,
message_type: 2, message_type: MessageType::BOT_RESPONSE,
stream_token: None, stream_token: None,
is_complete: false, is_complete: false,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -281,7 +282,7 @@ impl BotOrchestrator {
session_id: message.session_id.clone(), session_id: message.session_id.clone(),
channel: message.channel.clone(), channel: message.channel.clone(),
content: chunk, content: chunk,
message_type: 2, message_type: MessageType::BOT_RESPONSE,
stream_token: None, stream_token: None,
is_complete: false, is_complete: false,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -314,7 +315,7 @@ impl BotOrchestrator {
session_id: message.session_id, session_id: message.session_id,
channel: message.channel, channel: message.channel,
content: full_response, content: full_response,
message_type: 2, message_type: MessageType::BOT_RESPONSE,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),

View file

@ -11,6 +11,7 @@
//! - Storage abstraction for S3-compatible backends //! - Storage abstraction for S3-compatible backends
//! - URL processing and validation //! - URL processing and validation
use crate::shared::message_types::MessageType;
use crate::shared::models::{BotResponse, UserMessage}; use crate::shared::models::{BotResponse, UserMessage};
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
@ -154,7 +155,7 @@ impl MultimediaHandler for DefaultMultimediaHandler {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "multimedia".to_string(), channel: "multimedia".to_string(),
content, content,
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -182,7 +183,7 @@ impl MultimediaHandler for DefaultMultimediaHandler {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "multimedia".to_string(), channel: "multimedia".to_string(),
content: response_content, content: response_content,
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -215,7 +216,7 @@ impl MultimediaHandler for DefaultMultimediaHandler {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "multimedia".to_string(), channel: "multimedia".to_string(),
content: response_content, content: response_content,
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -248,7 +249,7 @@ impl MultimediaHandler for DefaultMultimediaHandler {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "multimedia".to_string(), channel: "multimedia".to_string(),
content: response_content, content: response_content,
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -277,7 +278,7 @@ impl MultimediaHandler for DefaultMultimediaHandler {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "multimedia".to_string(), channel: "multimedia".to_string(),
content: response_content, content: response_content,
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),
@ -294,7 +295,7 @@ impl MultimediaHandler for DefaultMultimediaHandler {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel: "multimedia".to_string(), channel: "multimedia".to_string(),
content: "Message received and processing...".to_string(), content: "Message received and processing...".to_string(),
message_type: 0, message_type: MessageType::EXTERNAL,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),

View file

@ -1,5 +1,6 @@
pub mod admin; pub mod admin;
pub mod analytics; pub mod analytics;
pub mod message_types;
pub mod models; pub mod models;
pub mod state; pub mod state;
pub mod utils; pub mod utils;

View file

@ -1,3 +1,4 @@
use crate::shared::message_types::MessageType;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::prelude::*; use diesel::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -51,7 +52,7 @@ pub struct UserMessage {
pub session_id: String, pub session_id: String,
pub channel: String, pub channel: String,
pub content: String, pub content: String,
pub message_type: i32, pub message_type: MessageType,
pub media_url: Option<String>, pub media_url: Option<String>,
pub timestamp: DateTime<Utc>, pub timestamp: DateTime<Utc>,
pub context_name: Option<String>, pub context_name: Option<String>,
@ -68,7 +69,7 @@ pub struct BotResponse {
pub session_id: String, pub session_id: String,
pub channel: String, pub channel: String,
pub content: String, pub content: String,
pub message_type: i32, pub message_type: MessageType,
pub stream_token: Option<String>, pub stream_token: Option<String>,
pub is_complete: bool, pub is_complete: bool,
pub suggestions: Vec<Suggestion>, pub suggestions: Vec<Suggestion>,
@ -90,7 +91,7 @@ impl BotResponse {
session_id: session_id.to_string(), session_id: session_id.to_string(),
channel, channel,
content, content,
message_type: 2, message_type: MessageType::BOT_RESPONSE,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),

View file

@ -3,6 +3,7 @@
use crate::basic::compiler::BasicCompiler; use crate::basic::compiler::BasicCompiler;
use crate::config::ConfigManager; use crate::config::ConfigManager;
use crate::core::kb::KnowledgeBaseManager; use crate::core::kb::KnowledgeBaseManager;
use crate::shared::message_types::MessageType;
use crate::shared::state::AppState; use crate::shared::state::AppState;
use aws_sdk_s3::Client; use aws_sdk_s3::Client;
use log::{debug, error, info}; use log::{debug, error, info};
@ -345,7 +346,7 @@ impl DriveMonitor {
session_id: session_id.clone(), session_id: session_id.clone(),
channel: "web".to_string(), channel: "web".to_string(),
content: serde_json::to_string(&theme_data)?, content: serde_json::to_string(&theme_data)?,
message_type: 2, message_type: MessageType::BOT_RESPONSE,
stream_token: None, stream_token: None,
is_complete: true, is_complete: true,
suggestions: Vec::new(), suggestions: Vec::new(),

View file

@ -4,6 +4,17 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>General Bots</title> <title>General Bots</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<script>
// Message Type Constants inline since we don't have a /shared route
const MessageType = {
EXTERNAL: 0,
USER: 1,
BOT_RESPONSE: 2,
CONTINUE: 3,
SUGGESTION: 4,
CONTEXT_CHANGE: 5,
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
@ -733,12 +744,21 @@
isUserScrolling = false, isUserScrolling = false,
autoScrollEnabled = true, autoScrollEnabled = true,
isContextChange = false; isContextChange = false;
const maxReconnectAttempts = 5, const maxReconnectAttempts = 5;
messagesDiv = document.getElementById("messages"), let messagesDiv = document.getElementById("messages");
input = document.getElementById("messageInput"), let input = document.getElementById("messageInput");
sendBtn = document.getElementById("sendBtn"), let sendBtn = document.getElementById("sendBtn");
voiceBtn = document.getElementById("voiceBtn"), let voiceBtn = document.getElementById("voiceBtn");
connectionStatus = document.getElementById("connectionStatus"),
// 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"), flashOverlay = document.getElementById("flashOverlay"),
suggestionsContainer = document.getElementById("suggestions"), suggestionsContainer = document.getElementById("suggestions"),
floatLogo = document.getElementById("floatLogo"), floatLogo = document.getElementById("floatLogo"),
@ -878,17 +898,29 @@
async function initializeAuth() { async function initializeAuth() {
try { try {
console.log("Starting auth initialization...");
updateConnectionStatus("connecting"); updateConnectionStatus("connecting");
const p = window.location.pathname const p = window.location.pathname
.split("/") .split("/")
.filter((s) => s), .filter((s) => s),
b = p.length > 0 ? p[0] : "default", b = p.length > 0 ? p[0] : "default";
r = await fetch( console.log("Bot name:", b);
`http://localhost:8080/api/auth?bot_name=${encodeURIComponent(b)}`, const r = await fetch(
`/api/auth?bot_name=${encodeURIComponent(b)}`,
), ),
a = await r.json(); a = await r.json();
console.log("Auth response:", a);
currentUserId = a.user_id; currentUserId = a.user_id;
currentSessionId = a.session_id; currentSessionId = a.session_id;
currentBotId = a.bot_id || "default_bot";
console.log(
"Auth initialized - User:",
currentUserId,
"Session:",
currentSessionId,
"Bot:",
currentBotId,
);
connectWebSocket(); connectWebSocket();
loadSessions(); loadSessions();
} catch (e) { } catch (e) {
@ -984,6 +1016,7 @@
const u = getWebSocketUrl(); const u = getWebSocketUrl();
ws = new WebSocket(u); ws = new WebSocket(u);
ws.onmessage = function (e) { ws.onmessage = function (e) {
console.log("WebSocket message received:", e.data);
try { try {
if (!e.data || e.data.trim() === "") { if (!e.data || e.data.trim() === "") {
console.warn("Empty WebSocket message received"); console.warn("Empty WebSocket message received");
@ -997,19 +1030,35 @@
if (r.bot_id) { if (r.bot_id) {
currentBotId = r.bot_id; currentBotId = r.bot_id;
} }
if (r.message_type === 2) { // 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 { try {
const d = JSON.parse(r.content); const d = JSON.parse(r.content);
if (d.event && d.data) {
// This is an event message
handleEvent(d.event, d.data); handleEvent(d.event, d.data);
} catch (parseErr) {
console.error(
"Failed to parse event content:",
parseErr,
);
}
return; return;
} }
if (r.message_type === 5) { } 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 === MessageType.CONTEXT_CHANGE) {
isContextChange = true; isContextChange = true;
return; return;
} }
@ -1024,7 +1073,10 @@
} }
}; };
ws.onopen = function () { ws.onopen = function () {
console.log("Connected to WebSocket"); console.log(
"Connected to WebSocket, readyState:",
ws.readyState,
);
updateConnectionStatus("connected"); updateConnectionStatus("connected");
reconnectAttempts = 0; reconnectAttempts = 0;
hasReceivedInitialMessage = false; hasReceivedInitialMessage = false;
@ -1218,7 +1270,7 @@
session_id: currentSessionId, session_id: currentSessionId,
channel: "web", channel: "web",
content: "continue", content: "continue",
message_type: 3, message_type: MessageType.CONTINUE,
media_url: null, media_url: null,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
@ -1327,7 +1379,8 @@
const h = (e) => { const h = (e) => {
const d = JSON.parse(e.data); const d = JSON.parse(e.data);
if ( if (
d.message_type === 5 && d.message_type ===
MessageType.CONTEXT_CHANGE &&
d.context_name === c d.context_name === c
) { ) {
ws.removeEventListener("message", h); ws.removeEventListener("message", h);
@ -1341,7 +1394,7 @@
session_id: currentSessionId, session_id: currentSessionId,
channel: "web", channel: "web",
content: t, content: t,
message_type: 4, message_type: MessageType.SUGGESTION,
is_suggestion: true, is_suggestion: true,
context_name: c, context_name: c,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@ -1367,12 +1420,42 @@
} }
async function sendMessage() { async function sendMessage() {
console.log("=== sendMessage called ===");
console.log("input element:", input);
console.log(
"input.value:",
input ? input.value : "input is null",
);
if (pendingContextChange) { if (pendingContextChange) {
await pendingContextChange; await pendingContextChange;
pendingContextChange = null; pendingContextChange = null;
} }
if (!input) {
console.error("Input element is null!");
return;
}
const m = input.value.trim(); 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) { if (!ws || ws.readyState !== WebSocket.OPEN) {
showWarning( showWarning(
"Conexão não disponível. Tentando reconectar...", "Conexão não disponível. Tentando reconectar...",
@ -1384,26 +1467,43 @@
if (isThinking) { if (isThinking) {
hideThinkingIndicator(); hideThinkingIndicator();
} }
console.log("Adding message to UI");
addMessage("user", m); addMessage("user", m);
console.log("Building message data object");
console.log("currentBotId:", currentBotId);
console.log("currentUserId:", currentUserId);
console.log("currentSessionId:", currentSessionId);
const d = { const d = {
bot_id: currentBotId, bot_id: currentBotId || "default_bot",
user_id: currentUserId, user_id: currentUserId,
session_id: currentSessionId, session_id: currentSessionId,
channel: "web", channel: "web",
content: m, content: m,
message_type: 1, message_type: MessageType.USER,
media_url: null, media_url: null,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
ws.send(JSON.stringify(d)); console.log("Message data object:", JSON.stringify(d, null, 2));
input.value = "";
input.focus(); 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);
} }
sendBtn.onclick = sendMessage; console.log("Clearing input field");
input.addEventListener("keypress", (e) => { input.value = "";
if (e.key === "Enter") sendMessage(); input.focus();
}); console.log("=== sendMessage completed ===");
}
async function toggleVoiceMode() { async function toggleVoiceMode() {
isVoiceMode = !isVoiceMode; isVoiceMode = !isVoiceMode;
@ -1569,7 +1669,44 @@
scrollToBottomBtn.classList.remove("visible"); 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 () { window.addEventListener("focus", function () {
if (!ws || ws.readyState !== WebSocket.OPEN) { if (!ws || ws.readyState !== WebSocket.OPEN) {
connectWebSocket(); connectWebSocket();