From 8f96cd10150dd151da8a26c51cc4831596f16303 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Tue, 14 Oct 2025 13:51:27 -0300 Subject: [PATCH] Refactor server code and add auth API fixes --- add-req.sh | 67 +- docs/DEV.md | 7 + prompts/dev/shared.md | 4 +- src/basic/keywords/hear_talk.rs | 2 +- src/basic/mod.rs | 2 - src/bot/mod.rs | 600 ++---- src/config/mod.rs | 2 +- src/main.rs | 30 +- .../annoucements.gbdialog/start-ctx.bas | 8 - .../annoucements.gbdialog/start.bas | 10 +- web/index.html | 1679 +++++++---------- 11 files changed, 891 insertions(+), 1520 deletions(-) mode change 100644 => 100755 add-req.sh delete mode 100644 templates/annoucements.gbai/annoucements.gbdialog/start-ctx.bas diff --git a/add-req.sh b/add-req.sh old mode 100644 new mode 100755 index c7d6320e..d71fb4bf --- a/add-req.sh +++ b/add-req.sh @@ -3,7 +3,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$SCRIPT_DIR" OUTPUT_FILE="$SCRIPT_DIR/prompt.out" -rm $OUTPUT_FILE + +rm -f "$OUTPUT_FILE" echo "Consolidated LLM Context" > "$OUTPUT_FILE" prompts=( @@ -13,8 +14,10 @@ prompts=( ) for file in "${prompts[@]}"; do - cat "$file" >> "$OUTPUT_FILE" - echo "" >> "$OUTPUT_FILE" + if [ -f "$file" ]; then + cat "$file" >> "$OUTPUT_FILE" + echo "" >> "$OUTPUT_FILE" + fi done dirs=( @@ -22,8 +25,8 @@ dirs=( #"automation" #"basic" "bot" - #"channels" - "config" + "channels" + #"config" #"context" #"email" #"file" @@ -37,25 +40,55 @@ dirs=( #"web_automation" #"whatsapp" ) + +filter_rust_file() { + sed -E '/^\s*\/\//d' "$1" | \ + sed -E '/info!\s*\(/d' | \ + sed -E '/debug!\s*\(/d' | \ + sed -E '/trace!\s*\(/d' +} + for dir in "${dirs[@]}"; do - find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read file; do - echo $file >> "$OUTPUT_FILE" - cat "$file" >> "$OUTPUT_FILE" + find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read -r file; do + echo "$file" >> "$OUTPUT_FILE" + filter_rust_file "$file" >> "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE" done done -# Also append the specific files you mentioned -echo "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE" -cat "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE" +# Additional specific files +files=( + "$PROJECT_ROOT/src/main.rs" + "$PROJECT_ROOT/src/basic/keywords/hear_talk.rs" + "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/start.bas" + "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/auth.bas" + "$PROJECT_ROOT/web/index.html" +) -cat "$PROJECT_ROOT/src/basic/keywords/hear_talk.rs" >> "$OUTPUT_FILE" -echo "$PROJECT_ROOT/src/basic/mod.rs">> "$OUTPUT_FILE" -cat "$PROJECT_ROOT/src/basic/mod.rs" >> "$OUTPUT_FILE" +for file in "${files[@]}"; do + if [[ "$file" == *.rs ]]; then + echo "$file" >> "$OUTPUT_FILE" + filter_rust_file "$file" >> "$OUTPUT_FILE" + else + echo "$file" >> "$OUTPUT_FILE" + cat "$file" >> "$OUTPUT_FILE" + fi +done -echo "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/start.bas" >> "$OUTPUT_FILE" -cat "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/start.bas" >> "$OUTPUT_FILE" +# Remove all blank lines and reduce whitespace greater than 1 space +sed -i 's/[[:space:]]*$//' "$OUTPUT_FILE" +sed -i '/^$/d' "$OUTPUT_FILE" +sed -i 's/ \+/ /g' "$OUTPUT_FILE" + +# Calculate and display token count (approximation: words * 1.3) +WORD_COUNT=$(wc -w < "$OUTPUT_FILE") +TOKEN_COUNT=$(echo "$WORD_COUNT * 1.3 / 1" | bc) +FILE_SIZE=$(wc -c < "$OUTPUT_FILE") echo "" >> "$OUTPUT_FILE" -# cargo build --message-format=short 2>&1 | grep -E 'error' >> "$OUTPUT_FILE" +echo "Approximate token count: $TOKEN_COUNT" +echo "Context size: $FILE_SIZE bytes" + +cat "$OUTPUT_FILE" | xclip -selection clipboard +echo "Content copied to clipboard (xclip)" diff --git a/docs/DEV.md b/docs/DEV.md index 50127659..68807c30 100644 --- a/docs/DEV.md +++ b/docs/DEV.md @@ -18,3 +18,10 @@ cargo install cargo-edit cargo upgrade cargo audit + +apt install xclip + +# Prompt add-ons + +- Prompt add-ons: Fill the file with info!, trace! and debug! macros. +- diff --git a/prompts/dev/shared.md b/prompts/dev/shared.md index c05a90ce..2984d187 100644 --- a/prompts/dev/shared.md +++ b/prompts/dev/shared.md @@ -2,9 +2,11 @@ MOST IMPORTANT CODE GENERATION RULES: - No placeholders, never comment/uncomment code, no explanations, no filler text. - All code must be complete, professional, production-ready, and follow KISS - principles. - NEVER return placeholders of any kind, neither commented code, only REAL PRODUCTION GRADE code. -- Always increment logging with info! to give birth to the console. +- Always increment logging with (all-in-one-line) info!, debug!, trace! to give birth to the console. - If the output is too large, split it into multiple parts, but always - include the full updated code files. - Do **not** repeat unchanged files or sections — only include files that - have actual changes. - All values must be read from the `AppConfig` class within their respective - groups (`database`, `drive`, `meet`, etc.); never use hardcoded or magic - values. - Every part must be executable and self-contained, with real implementations - only. - Only generated production ready enterprise grade VERY condensed no commented code. +- DO NOT WRITE ANY ERROR HANDLING CODE LET IT CRASH. +- Never generate two ore more trace mensages that are equal! diff --git a/src/basic/keywords/hear_talk.rs b/src/basic/keywords/hear_talk.rs index 731e02b8..8e739e81 100644 --- a/src/basic/keywords/hear_talk.rs +++ b/src/basic/keywords/hear_talk.rs @@ -141,7 +141,7 @@ pub fn set_user_keyword(state: Arc, user: UserSession, engine: &mut En if let Err(e) = session_manager.update_user_id(user_clone_spawn.id, user_id) { - debug!("Failed to update user ID in session: {}", e); + error!("Failed to update user ID in session: {}", e); } else { info!( "Updated session {} to user ID: {}", diff --git a/src/basic/mod.rs b/src/basic/mod.rs index d0843f7e..c9ac133b 100644 --- a/src/basic/mod.rs +++ b/src/basic/mod.rs @@ -82,8 +82,6 @@ impl ScriptService { let trimmed = line.trim(); if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("REM") { - result.push_str(line); - result.push('\n'); continue; } diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 0ed7728d..882e2cf2 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,7 +1,10 @@ +use crate::channels::ChannelAdapter; +use crate::shared::models::{BotResponse, UserMessage, UserSession}; +use crate::shared::state::AppState; use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_ws::Message as WsMessage; use chrono::Utc; -use log::{debug, error, info, trace, warn}; +use log::{debug, error, info, warn}; use serde_json; use std::collections::HashMap; use std::fs; @@ -9,17 +12,12 @@ use std::sync::Arc; use tokio::sync::mpsc; use uuid::Uuid; -use crate::channels::ChannelAdapter; -use crate::shared::models::{BotResponse, UserMessage, UserSession}; -use crate::shared::state::AppState; - pub struct BotOrchestrator { pub state: Arc, } impl BotOrchestrator { pub fn new(state: Arc) -> Self { - info!("Creating new BotOrchestrator instance"); Self { state } } @@ -28,32 +26,26 @@ impl BotOrchestrator { session_id: Uuid, user_input: &str, ) -> Result, Box> { - debug!( + info!( "Handling user input for session {}: '{}'", session_id, user_input ); let mut session_manager = self.state.session_manager.lock().await; session_manager.provide_input(session_id, user_input.to_string())?; - debug!("User input handled for session {}", session_id); Ok(None) } pub async fn is_waiting_for_input(&self, session_id: Uuid) -> bool { - trace!("Checking if session {} is waiting for input", session_id); let session_manager = self.state.session_manager.lock().await; - let result = session_manager.is_waiting_for_input(&session_id); - trace!("Session {} waiting for input: {}", session_id, result); - result + session_manager.is_waiting_for_input(&session_id) } pub fn add_channel(&self, channel_type: &str, adapter: Arc) { - info!("Adding channel adapter for type: {}", channel_type); self.state .channels .lock() .unwrap() .insert(channel_type.to_string(), adapter); - debug!("Channel adapter for {} added successfully", channel_type); } pub async fn register_response_channel( @@ -61,13 +53,15 @@ impl BotOrchestrator { session_id: String, sender: mpsc::Sender, ) { - debug!("Registering response channel for session: {}", session_id); self.state .response_channels .lock() .await .insert(session_id.clone(), sender); - trace!("Response channel registered for session: {}", session_id); + } + + pub async fn unregister_response_channel(&self, session_id: &str) { + self.state.response_channels.lock().await.remove(session_id); } pub async fn set_user_answer_mode( @@ -82,7 +76,6 @@ impl BotOrchestrator { ); let mut session_manager = self.state.session_manager.lock().await; session_manager.update_answer_mode(user_id, bot_id, mode)?; - debug!("Answer mode updated successfully"); Ok(()) } @@ -95,11 +88,10 @@ impl BotOrchestrator { event_type: &str, data: serde_json::Value, ) -> Result<(), Box> { - debug!( + 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(), @@ -114,13 +106,10 @@ impl BotOrchestrator { is_complete: true, }; - trace!("Event response created: {:?}", event_response); - if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) { adapter.send_message(event_response).await?; - debug!("Event sent successfully via channel adapter"); } else { - warn!("No channel adapter found for channel: {}", channel); + warn!("No channel adapter found for channel 1: {}", channel); } Ok(()) } @@ -131,11 +120,10 @@ impl BotOrchestrator { channel: &str, content: &str, ) -> Result<(), Box> { - debug!( + info!( "Sending direct message to session {}: '{}'", session_id, content ); - let bot_response = BotResponse { bot_id: "default_bot".to_string(), user_id: "default_user".to_string(), @@ -149,9 +137,8 @@ impl BotOrchestrator { if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) { adapter.send_message(bot_response).await?; - debug!("Direct message sent successfully"); } else { - warn!("No channel adapter found for channel: {}", channel); + warn!("No channel adapter found for channel 2: {}", channel); } Ok(()) } @@ -169,31 +156,29 @@ impl BotOrchestrator { message.content, message.message_type ); - let user_id = Uuid::parse_str(&message.user_id).unwrap_or_else(|_| { - let new_id = Uuid::new_v4(); - warn!("Invalid user ID provided, generated new UUID: {}", new_id); - new_id - }); + let user_id = Uuid::parse_str(&message.user_id).map_err(|e| { + error!("Invalid user ID provided: {}", e); + e + })?; let bot_id = if let Ok(bot_guid) = std::env::var("BOT_GUID") { - Uuid::parse_str(&bot_guid).unwrap_or_else(|_| { - warn!("Invalid BOT_GUID from env, using nil UUID"); - Uuid::nil() - }) + Uuid::parse_str(&bot_guid).map_err(|e| { + warn!("Invalid BOT_GUID from env: {}", e); + e + })? } else { warn!("BOT_GUID not set in environment, using nil UUID"); Uuid::nil() }; - debug!("Parsed user_id: {}, bot_id: {}", user_id, bot_id); - let session = { - let mut session_manager = self.state.session_manager.lock().await; - match session_manager.get_or_create_user_session(user_id, bot_id, "New Conversation")? { - Some(session) => { - debug!("Found existing session: {}", session.id); - session - } + let mut sm = self.state.session_manager.lock().await; + let session_id = Uuid::parse_str(&message.session_id).map_err(|e| { + error!("Invalid session ID: {}", e); + e + })?; + match sm.get_session_by_id(session_id)? { + Some(session) => session, None => { error!( "Failed to create session for user {} with bot {}", @@ -204,8 +189,6 @@ impl BotOrchestrator { } }; - trace!("Current session state: {:?}", session); - if self.is_waiting_for_input(session.id).await { debug!( "Session {} is waiting for input, processing as variable input", @@ -214,11 +197,10 @@ impl BotOrchestrator { if let Some(variable_name) = self.handle_user_input(session.id, &message.content).await? { - info!( + debug!( "Stored user input in variable '{}' for session {}", variable_name, session.id ); - if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) { let ack_response = BotResponse { bot_id: message.bot_id.clone(), @@ -231,20 +213,17 @@ impl BotOrchestrator { is_complete: true, }; adapter.send_message(ack_response).await?; - debug!("Acknowledgment sent for variable storage"); } - return Ok(()); } + return Ok(()); } if session.answer_mode == 1 && session.current_tool.is_some() { - debug!("Session in answer mode with active tool, providing user response"); self.state.tool_manager.provide_user_response( &message.user_id, &message.bot_id, message.content.clone(), )?; - trace!("User response provided to tool manager"); return Ok(()); } @@ -257,16 +236,13 @@ impl BotOrchestrator { &message.content, message.message_type, )?; - debug!("User message saved to session history"); } let response_content = self.direct_mode_handler(&message, &session).await?; - debug!("Generated response content: '{}'", response_content); { let mut session_manager = self.state.session_manager.lock().await; session_manager.save_message(session.id, user_id, 2, &response_content, 1)?; - debug!("Bot response saved to session history"); } let bot_response = BotResponse { @@ -280,13 +256,13 @@ impl BotOrchestrator { is_complete: true, }; - trace!("Final bot response: {:?}", bot_response); - if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) { adapter.send_message(bot_response).await?; - info!("Response sent successfully via channel adapter"); } else { - warn!("No channel adapter found for channel: {}", message.channel); + warn!( + "No channel adapter found for channel 3: {}", + message.channel + ); } Ok(()) @@ -297,23 +273,17 @@ impl BotOrchestrator { message: &UserMessage, session: &UserSession, ) -> Result> { - debug!("Using direct mode handler for session {}", session.id); - let history = { let mut session_manager = self.state.session_manager.lock().await; session_manager.get_conversation_history(session.id, session.user_id)? }; - debug!("Retrieved {} history entries", history.len()); - let mut prompt = String::new(); for (role, content) in history { prompt.push_str(&format!("{}: {}\n", role, content)); } prompt.push_str(&format!("User: {}\nAssistant:", message.content)); - trace!("Constructed prompt for LLM: {}", prompt); - self.state .llm_provider .generate(&prompt, &serde_json::Value::Null) @@ -329,33 +299,30 @@ impl BotOrchestrator { "Streaming response for user: {}, session: {}", message.user_id, message.session_id ); - debug!("Message content: '{}'", message.content); - let user_id = Uuid::parse_str(&message.user_id).unwrap_or_else(|_| { - let new_id = Uuid::new_v4(); - warn!("Invalid user ID, generated new: {}", new_id); - new_id - }); + let user_id = Uuid::parse_str(&message.user_id).map_err(|e| { + error!("Invalid user ID: {}", e); + e + })?; let bot_id = if let Ok(bot_guid) = std::env::var("BOT_GUID") { - Uuid::parse_str(&bot_guid).unwrap_or_else(|_| { - warn!("Invalid BOT_GUID from env, using nil UUID"); - Uuid::nil() - }) + Uuid::parse_str(&bot_guid).map_err(|e| { + warn!("Invalid BOT_GUID from env: {}", e); + e + })? } else { warn!("BOT_GUID not set in environment, using nil UUID"); Uuid::nil() }; - debug!("User ID: {}, Bot ID: {}", user_id, bot_id); - let session = { let mut sm = self.state.session_manager.lock().await; - match sm.get_or_create_user_session(user_id, bot_id, "New Conversation")? { - Some(sess) => { - debug!("Using existing session: {}", sess.id); - sess - } + let session_id = Uuid::parse_str(&message.session_id).map_err(|e| { + error!("Invalid session ID: {}", e); + e + })?; + match sm.get_session_by_id(session_id)? { + Some(sess) => sess, None => { error!("Failed to create session for streaming"); return Err("Failed to create session".into()); @@ -363,10 +330,7 @@ impl BotOrchestrator { } }; - trace!("Session state: {:?}", session); - if session.answer_mode == 1 && session.current_tool.is_some() { - debug!("Session in answer mode, forwarding to tool manager"); self.state.tool_manager.provide_user_response( &message.user_id, &message.bot_id, @@ -384,7 +348,6 @@ impl BotOrchestrator { &message.content, message.message_type, )?; - debug!("User message saved for streaming session"); } let prompt = { @@ -399,7 +362,6 @@ impl BotOrchestrator { "Stream prompt constructed with {} history entries", history.len() ); - trace!("Full prompt: {}", p); p }; @@ -407,7 +369,6 @@ impl BotOrchestrator { let llm = self.state.llm_provider.clone(); if message.channel == "web" { - debug!("Sending thinking start event for web channel"); self.send_event( &message.user_id, &message.bot_id, @@ -418,7 +379,6 @@ impl BotOrchestrator { ) .await?; } else { - debug!("Sending thinking message for non-web channel"); let thinking_response = BotResponse { bot_id: message.bot_id.clone(), user_id: message.user_id.clone(), @@ -432,15 +392,12 @@ impl BotOrchestrator { response_tx.send(thinking_response).await?; } - info!("Starting LLM stream generation"); tokio::spawn(async move { if let Err(e) = llm .generate_stream(&prompt, &serde_json::Value::Null, stream_tx) .await { error!("LLM streaming error: {}", e); - } else { - debug!("LLM stream generation completed"); } }); @@ -449,15 +406,11 @@ impl BotOrchestrator { let mut in_analysis = false; let mut chunk_count = 0; - debug!("Starting to process stream chunks"); while let Some(chunk) = stream_rx.recv().await { chunk_count += 1; - trace!("Received chunk {}: '{}'", chunk_count, chunk); - analysis_buffer.push_str(&chunk); - if analysis_buffer.contains("<|channel|>") { - debug!("Analysis section started"); + if analysis_buffer.contains("<|channel|>") && !in_analysis { in_analysis = true; } @@ -469,7 +422,6 @@ impl BotOrchestrator { ); in_analysis = false; analysis_buffer.clear(); - if message.channel == "web" { let orchestrator = BotOrchestrator::new(Arc::clone(&self.state)); orchestrator @@ -489,13 +441,10 @@ impl BotOrchestrator { analysis_buffer.clear(); continue; } - - trace!("Skipping analysis chunk"); continue; } full_response.push_str(&chunk); - let partial = BotResponse { bot_id: message.bot_id.clone(), user_id: message.user_id.clone(), @@ -517,12 +466,10 @@ impl BotOrchestrator { "Stream processing completed, {} chunks processed", chunk_count ); - info!("Full response length: {} characters", full_response.len()); { let mut sm = self.state.session_manager.lock().await; sm.save_message(session.id, user_id, 2, &full_response, 1)?; - debug!("Stream response saved to session history"); } let final_msg = BotResponse { @@ -535,9 +482,7 @@ impl BotOrchestrator { stream_token: None, is_complete: true, }; - response_tx.send(final_msg).await?; - debug!("Final stream message sent"); Ok(()) } @@ -546,10 +491,8 @@ impl BotOrchestrator { &self, user_id: Uuid, ) -> Result, Box> { - debug!("Getting sessions for user: {}", user_id); let mut session_manager = self.state.session_manager.lock().await; let sessions = session_manager.get_user_sessions(user_id)?; - debug!("Found {} sessions for user {}", sessions.len(), user_id); Ok(sessions) } @@ -558,205 +501,29 @@ impl BotOrchestrator { session_id: Uuid, user_id: Uuid, ) -> Result, Box> { - debug!( + info!( "Getting conversation history for session {} user {}", session_id, user_id ); let mut session_manager = self.state.session_manager.lock().await; let history = session_manager.get_conversation_history(session_id, user_id)?; - debug!("Retrieved {} history entries", history.len()); Ok(history) } - pub async fn process_message_with_tools( - &self, - message: UserMessage, - ) -> Result<(), Box> { - info!( - "Processing message with tools from user: {}, session: {}", - message.user_id, message.session_id - ); - debug!("Message content: '{}'", message.content); - - let user_id = Uuid::parse_str(&message.user_id).unwrap_or_else(|_| { - let new_id = Uuid::new_v4(); - warn!("Invalid user ID, generated new: {}", new_id); - new_id - }); - - let bot_id = if let Ok(bot_guid) = std::env::var("BOT_GUID") { - Uuid::parse_str(&bot_guid).unwrap_or_else(|_| { - warn!("Invalid BOT_GUID from env, using nil UUID"); - Uuid::nil() - }) - } else { - warn!("BOT_GUID not set in environment, using nil UUID"); - Uuid::nil() - }; - - let session = { - let mut session_manager = self.state.session_manager.lock().await; - match session_manager.get_or_create_user_session(user_id, bot_id, "New Conversation")? { - Some(session) => { - debug!("Found existing session: {}", session.id); - session - } - None => { - error!("Failed to create session for tools processing"); - return Err("Failed to create session".into()); - } - } - }; - - { - let mut session_manager = self.state.session_manager.lock().await; - session_manager.save_message( - session.id, - user_id, - 1, - &message.content, - message.message_type, - )?; - debug!("User message saved for tools processing"); - } - - let is_tool_waiting = self - .state - .tool_manager - .is_tool_waiting(&message.session_id) - .await - .unwrap_or(false); - - if is_tool_waiting { - debug!( - "Tool is waiting for input, providing: '{}'", - message.content - ); - self.state - .tool_manager - .provide_input(&message.session_id, &message.content) - .await?; - - if let Ok(tool_output) = self - .state - .tool_manager - .get_tool_output(&message.session_id) - .await - { - debug!("Retrieved {} tool output entries", tool_output.len()); - for output in tool_output { - let bot_response = BotResponse { - bot_id: message.bot_id.clone(), - user_id: message.user_id.clone(), - session_id: message.session_id.clone(), - channel: message.channel.clone(), - content: output.clone(), - message_type: 1, - stream_token: None, - is_complete: true, - }; - - if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) - { - adapter.send_message(bot_response).await?; - debug!("Tool output sent: '{}'", output); - } - } - } - return Ok(()); - } - - let response = if message.content.to_lowercase().contains("calculator") - || message.content.to_lowercase().contains("calculate") - || message.content.to_lowercase().contains("math") - { - debug!("Message requires calculator tool"); - match self - .state - .tool_manager - .execute_tool("calculator", &message.session_id, &message.user_id) - .await - { - Ok(tool_result) => { - debug!("Calculator tool executed successfully"); - let mut session_manager = self.state.session_manager.lock().await; - session_manager.save_message(session.id, user_id, 2, &tool_result.output, 2)?; - tool_result.output - } - Err(e) => { - error!("Calculator tool error: {}", e); - format!("I encountered an error starting the calculator: {}", e) - } - } - } else { - debug!("Using LLM for response generation"); - let available_tools = self.state.tool_manager.list_tools(); - let tools_context = if !available_tools.is_empty() { - format!("\n\nAvailable tools: {}. If the user needs calculations, suggest using the calculator tool.", available_tools.join(", ")) - } else { - String::new() - }; - - let full_prompt = format!("{}{}", message.content, tools_context); - trace!("Full prompt with tools context: {}", full_prompt); - - self.state - .llm_provider - .generate(&full_prompt, &serde_json::Value::Null) - .await? - }; - - debug!("Generated response: '{}'", response); - - { - let mut session_manager = self.state.session_manager.lock().await; - session_manager.save_message(session.id, user_id, 2, &response, 1)?; - debug!("Response saved to session history"); - } - - let bot_response = BotResponse { - bot_id: message.bot_id, - user_id: message.user_id, - session_id: message.session_id.clone(), - channel: message.channel.clone(), - content: response, - message_type: 1, - stream_token: None, - is_complete: true, - }; - - if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) { - adapter.send_message(bot_response).await?; - info!("Tools response sent successfully"); - } else { - warn!("No channel adapter found for channel: {}", message.channel); - } - - Ok(()) - } - pub async fn run_start_script( session: &UserSession, state: Arc, - token_id: Option, + token: Option, ) -> Result> { info!( - "Running start script for session: {} with token_id: {:?}", - session.id, token_id + "Running start script for session: {} with token: {:?}", + session.id, token ); - let start_script_path = "./templates/annoucements.gbai/annoucements.gbdialog/start.bas"; let start_script = match std::fs::read_to_string(start_script_path) { - Ok(content) => { - debug!("Loaded start script from {}", start_script_path); - content - } - Err(_) => { - debug!("No start.bas found, using default welcome script"); - r#"TALK "Welcome to General Bots!""#.to_string() - } + Ok(content) => content, + Err(_) => r#"TALK "Welcome to General Bots!""#.to_string(), }; - debug!( "Start script content for session {}: {}", session.id, start_script @@ -764,12 +531,9 @@ impl BotOrchestrator { let session_clone = session.clone(); let state_clone = state.clone(); - let script_service = crate::basic::ScriptService::new(state_clone, session_clone.clone()); - if let Some(token_id_value) = token_id { - debug!("Token ID available for script: {}", token_id_value); - } + if let Some(_token_id_value) = token {} match script_service .compile(&start_script) @@ -804,7 +568,6 @@ impl BotOrchestrator { ); if channel == "web" { - debug!("Sending warning as web event"); self.send_event( "system", "system", @@ -818,7 +581,6 @@ impl BotOrchestrator { ) .await } else { - debug!("Sending warning as regular message"); if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) { let warn_response = BotResponse { bot_id: "system".to_string(), @@ -846,40 +608,22 @@ impl BotOrchestrator { session_id: &str, user_id: &str, _bot_id: &str, - token_id: Option, + token: Option, ) -> Result> { info!( - "Triggering auto welcome for session: {} with token_id: {:?}", - session_id, token_id + "Triggering auto welcome for user: {}, session: {}, token: {:?}", + user_id, session_id, token ); - let user_uuid = Uuid::parse_str(user_id).unwrap_or_else(|_| { - let new_id = Uuid::new_v4(); - warn!("Invalid user ID, generated new: {}", new_id); - new_id - }); - - let bot_uuid = if let Ok(bot_guid) = std::env::var("BOT_GUID") { - Uuid::parse_str(&bot_guid).unwrap_or_else(|_| { - warn!("Invalid BOT_GUID from env, using nil UUID"); - Uuid::nil() - }) - } else { - warn!("BOT_GUID not set in environment, using nil UUID"); - Uuid::nil() - }; + let session_uuid = Uuid::parse_str(session_id).map_err(|e| { + error!("Invalid session ID: {}", e); + e + })?; let session = { let mut session_manager = self.state.session_manager.lock().await; - match session_manager.get_or_create_user_session( - user_uuid, - bot_uuid, - "New Conversation", - )? { - Some(session) => { - debug!("Found existing session: {}", session.id); - session - } + match session_manager.get_session_by_id(session_uuid)? { + Some(session) => session, None => { error!("Failed to create session for auto welcome"); return Ok(false); @@ -887,8 +631,7 @@ impl BotOrchestrator { } }; - let result = Self::run_start_script(&session, Arc::clone(&self.state), token_id).await?; - + let result = Self::run_start_script(&session, Arc::clone(&self.state), token).await?; info!( "Auto welcome completed for session: {} with result: {}", session_id, result @@ -911,7 +654,6 @@ impl BotOrchestrator { impl Default for BotOrchestrator { fn default() -> Self { - info!("Creating default BotOrchestrator"); Self { state: Arc::new(AppState::default()), } @@ -924,18 +666,21 @@ async fn websocket_handler( stream: web::Payload, data: web::Data, ) -> Result { - info!("WebSocket connection attempt"); + let query = web::Query::>::from_query(req.query_string()).unwrap(); + let session_id = query.get("session_id").cloned().unwrap(); + let user_id = query + .get("user_id") + .cloned() + .unwrap_or_else(|| "default_user".to_string()); let (res, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; - let session_id = Uuid::new_v4().to_string(); let (tx, mut rx) = mpsc::channel::(100); - info!("WebSocket session established: {}", session_id); - let orchestrator = BotOrchestrator::new(Arc::clone(&data)); orchestrator .register_response_channel(session_id.clone(), tx.clone()) .await; + data.web_adapter .add_connection(session_id.clone(), tx.clone()) .await; @@ -944,28 +689,35 @@ async fn websocket_handler( .await; let bot_id = std::env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string()); - orchestrator .send_event( - "default_user", + &user_id, &bot_id, &session_id, "web", "session_start", serde_json::json!({ "session_id": session_id, + "user_id": user_id, "timestamp": Utc::now().to_rfc3339() }), ) .await .ok(); + info!( + "WebSocket connection established for session: {}, user: {}", + session_id, user_id + ); + let orchestrator_clone = BotOrchestrator::new(Arc::clone(&data)); let session_id_clone = session_id.clone(); + let user_id_clone = user_id.clone(); + tokio::spawn(async move { tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; if let Err(e) = orchestrator_clone - .trigger_auto_welcome(&session_id_clone, "default_user", &bot_id, None) + .trigger_auto_welcome(&session_id_clone, &user_id_clone, &bot_id, None) .await { error!("Failed to trigger auto welcome: {}", e); @@ -975,6 +727,7 @@ async fn websocket_handler( let web_adapter = data.web_adapter.clone(); let session_id_clone1 = session_id.clone(); let session_id_clone2 = session_id.clone(); + let user_id_clone = user_id.clone(); actix_web::rt::spawn(async move { info!( @@ -985,7 +738,6 @@ async fn websocket_handler( while let Some(msg) = rx.recv().await { message_count += 1; if let Ok(json) = serde_json::to_string(&msg) { - trace!("Sending WebSocket message {}: {}", message_count, json); if let Err(e) = session.text(json).await { warn!("Failed to send WebSocket message {}: {}", message_count, e); break; @@ -1008,14 +760,11 @@ async fn websocket_handler( match msg { WsMessage::Text(text) => { message_count += 1; - debug!("Received WebSocket message {}: {}", message_count, text); - let bot_id = std::env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string()); - let user_message = UserMessage { bot_id: bot_id, - user_id: "default_user".to_string(), + user_id: user_id_clone.clone(), session_id: session_id_clone2.clone(), channel: "web".to_string(), content: text.to_string(), @@ -1032,14 +781,11 @@ async fn websocket_handler( } } WsMessage::Close(_) => { - info!("WebSocket close received for session {}", session_id_clone2); - let bot_id = std::env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string()); - orchestrator .send_event( - "default_user", + &user_id_clone, &bot_id, &session_id_clone2, "web", @@ -1048,13 +794,13 @@ async fn websocket_handler( ) .await .ok(); - web_adapter.remove_connection(&session_id_clone2).await; + orchestrator + .unregister_response_channel(&session_id_clone2) + .await; break; } - _ => { - trace!("Received non-text WebSocket message"); - } + _ => {} } } info!( @@ -1070,28 +816,87 @@ async fn websocket_handler( Ok(res) } +#[actix_web::get("/api/auth")] +async fn auth_handler( + data: web::Data, + web::Query(params): web::Query>, +) -> Result { + let token = params.get("token").cloned().unwrap_or_default(); + info!("Auth handler called with token: {}", token); + let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000000").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 session = { + let mut sm = data.session_manager.lock().await; + + match sm.get_or_create_user_session(user_id, bot_id, "Auth Session") { + Ok(Some(s)) => s, + Ok(None) => { + error!("Failed to create session"); + return Ok(HttpResponse::InternalServerError() + .json(serde_json::json!({"error": "Failed to create session"}))); + } + Err(e) => { + error!("Failed to create session: {}", e); + return Ok(HttpResponse::InternalServerError() + .json(serde_json::json!({"error": e.to_string()}))); + } + } + }; + + let auth_script_path = "./templates/annoucements.gbai/annoucements.gbdialog/auth.bas"; + let auth_script = match std::fs::read_to_string(auth_script_path) { + Ok(content) => content, + Err(_) => r#"SET_USER "00000000-0000-0000-0000-000000000001""#.to_string(), + }; + + let script_service = crate::basic::ScriptService::new(Arc::clone(&data), session.clone()); + if let Err(e) = script_service + .compile(&auth_script) + .and_then(|ast| script_service.run(&ast)) + { + error!("Failed to run auth script: {}", e); + return Ok( + HttpResponse::InternalServerError().json(serde_json::json!({"error": "Auth failed"})) + ); + } + + Ok(HttpResponse::Ok().json(serde_json::json!({ + "user_id": session.user_id, + "session_id": session.id, + "status": "authenticated" + }))) +} + #[actix_web::get("/api/whatsapp/webhook")] async fn whatsapp_webhook_verify( data: web::Data, web::Query(params): web::Query>, ) -> Result { - info!("WhatsApp webhook verification request"); - let empty = String::new(); let mode = params.get("hub.mode").unwrap_or(&empty); let token = params.get("hub.verify_token").unwrap_or(&empty); let challenge = params.get("hub.challenge").unwrap_or(&empty); - debug!( + info!( "Verification params - mode: {}, token: {}, challenge: {}", mode, token, challenge ); match data.whatsapp_adapter.verify_webhook(mode, token, challenge) { - Ok(challenge_response) => { - info!("WhatsApp webhook verification successful"); - Ok(HttpResponse::Ok().body(challenge_response)) - } + Ok(challenge_response) => Ok(HttpResponse::Ok().body(challenge_response)), Err(_) => { warn!("WhatsApp webhook verification failed"); Ok(HttpResponse::Forbidden().body("Verification failed")) @@ -1099,35 +904,6 @@ async fn whatsapp_webhook_verify( } } -#[actix_web::post("/api/whatsapp/webhook")] -async fn whatsapp_webhook( - data: web::Data, - payload: web::Json, -) -> Result { - info!("WhatsApp webhook message received"); - - match data - .whatsapp_adapter - .process_incoming_message(payload.into_inner()) - .await - { - Ok(user_messages) => { - info!("Processed {} WhatsApp messages", user_messages.len()); - for user_message in user_messages { - let orchestrator = BotOrchestrator::new(Arc::clone(&data)); - if let Err(e) = orchestrator.process_message(user_message).await { - error!("Error processing WhatsApp message: {}", e); - } - } - Ok(HttpResponse::Ok().body("")) - } - Err(e) => { - error!("Error processing WhatsApp webhook: {}", e); - Ok(HttpResponse::BadRequest().body("Invalid message")) - } - } -} - #[actix_web::post("/api/voice/start")] async fn voice_start( data: web::Data, @@ -1180,8 +956,6 @@ async fn voice_stop( .and_then(|s| s.as_str()) .unwrap_or(""); - info!("Voice session stop request - session: {}", session_id); - match data.voice_adapter.stop_voice_session(session_id).await { Ok(()) => { info!( @@ -1210,15 +984,11 @@ async fn start_session( .get("session_id") .and_then(|s| s.as_str()) .unwrap_or(""); - let token_id = info - .get("token_id") + let token = info + .get("token") .and_then(|t| t.as_str()) .map(|s| s.to_string()); - info!( - "Starting session: {} with token_id: {:?}", - session_id, token_id - ); let session_uuid = match Uuid::parse_str(session_id) { Ok(uuid) => uuid, Err(_) => { @@ -1232,10 +1002,7 @@ async fn start_session( let session = { let mut session_manager = data.session_manager.lock().await; match session_manager.get_session_by_id(session_uuid) { - Ok(Some(s)) => { - debug!("Found existing session: {}", session_uuid); - s - } + Ok(Some(s)) => s, Ok(None) => { warn!("Session not found: {}", session_uuid); return Ok(HttpResponse::NotFound() @@ -1249,8 +1016,7 @@ async fn start_session( } }; - let result = BotOrchestrator::run_start_script(&session, Arc::clone(&data), token_id).await; - + let result = BotOrchestrator::run_start_script(&session, Arc::clone(&data), token).await; match result { Ok(true) => { info!( @@ -1284,15 +1050,16 @@ async fn start_session( #[actix_web::post("/api/sessions")] async fn create_session(data: web::Data) -> Result { - info!("Creating new session"); - 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") { - Uuid::parse_str(&bot_guid).unwrap_or_else(|_| { - warn!("Invalid BOT_GUID from env, using nil UUID"); - Uuid::nil() - }) + 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() @@ -1324,14 +1091,10 @@ async fn create_session(data: web::Data) -> Result { #[actix_web::get("/api/sessions")] async fn get_sessions(data: web::Data) -> Result { - info!("Getting sessions list"); let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(); let orchestrator = BotOrchestrator::new(Arc::clone(&data)); match orchestrator.get_user_sessions(user_id).await { - Ok(sessions) => { - info!("Retrieved {} sessions", sessions.len()); - Ok(HttpResponse::Ok().json(sessions)) - } + Ok(sessions) => Ok(HttpResponse::Ok().json(sessions)), Err(e) => { error!("Failed to get sessions: {}", e); Ok(HttpResponse::InternalServerError() @@ -1346,8 +1109,6 @@ async fn get_session_history( path: web::Path, ) -> Result { let session_id = path.into_inner(); - info!("Getting session history for: {}", session_id); - let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(); match Uuid::parse_str(&session_id) { @@ -1384,19 +1145,15 @@ async fn set_mode_handler( data: web::Data, info: web::Json>, ) -> Result { - info!("Setting user answer mode"); - let default_user = "default_user".to_string(); let default_bot = "default_bot".to_string(); let default_mode = "0".to_string(); - let user_id = info.get("user_id").unwrap_or(&default_user); let bot_id = info.get("bot_id").unwrap_or(&default_bot); let mode_str = info.get("mode").unwrap_or(&default_mode); - let mode = mode_str.parse::().unwrap_or(0); - debug!( + info!( "Setting mode - user: {}, bot: {}, mode: {}", user_id, bot_id, mode ); @@ -1412,7 +1169,6 @@ async fn set_mode_handler( ); } - info!("Answer mode updated successfully"); Ok(HttpResponse::Ok().json(serde_json::json!({"status": "mode_updated"}))) } @@ -1424,7 +1180,6 @@ async fn send_warning_handler( let default_session = "default".to_string(); let default_channel = "web".to_string(); let default_message = "Warning!".to_string(); - let session_id = info.get("session_id").unwrap_or(&default_session); let channel = info.get("channel").unwrap_or(&default_channel); let message = info.get("message").unwrap_or(&default_message); @@ -1445,18 +1200,13 @@ async fn send_warning_handler( ); } - info!("Warning sent successfully"); Ok(HttpResponse::Ok().json(serde_json::json!({"status": "warning_sent"}))) } #[actix_web::get("/")] async fn index() -> Result { - info!("Serving index page"); match fs::read_to_string("web/index.html") { - Ok(html) => { - debug!("Index page loaded successfully"); - Ok(HttpResponse::Ok().content_type("text/html").body(html)) - } + Ok(html) => Ok(HttpResponse::Ok().content_type("text/html").body(html)), Err(e) => { error!("Failed to load index page: {}", e); Ok(HttpResponse::InternalServerError().body("Failed to load index page")) @@ -1467,10 +1217,7 @@ async fn index() -> Result { #[actix_web::get("/static/{filename:.*}")] async fn static_files(req: HttpRequest) -> Result { let filename = req.match_info().query("filename"); - let path = format!("static/{}", filename); - - info!("Serving static file: {}", filename); - + let path = format!("web/static/{}", filename); match fs::read(&path) { Ok(content) => { debug!( @@ -1485,7 +1232,6 @@ async fn static_files(req: HttpRequest) -> Result { f if f.ends_with(".jpg") | f.ends_with(".jpeg") => "image/jpeg", _ => "text/plain", }; - Ok(HttpResponse::Ok().content_type(content_type).body(content)) } Err(e) => { diff --git a/src/config/mod.rs b/src/config/mod.rs index 24baa3aa..705c4973 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -78,7 +78,7 @@ impl AppConfig { pub fn from_env() -> Self { let database = DatabaseConfig { - username: env::var("TABLES_USERNAME").unwrap_or_else(|_| "user".to_string()), + username: env::var("TABLES_USERNAME").unwrap(), password: env::var("TABLES_PASSWORD").unwrap_or_else(|_| "pass".to_string()), server: env::var("TABLES_SERVER").unwrap_or_else(|_| "localhost".to_string()), port: env::var("TABLES_PORT") diff --git a/src/main.rs b/src/main.rs index 2a3fac37..7e537f05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ #![allow(dead_code)] - use actix_cors::Cors; use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; @@ -7,7 +6,6 @@ use dotenvy::dotenv; use log::info; use std::collections::HashMap; use std::sync::{Arc, Mutex}; - mod auth; mod automation; mod basic; @@ -25,10 +23,9 @@ mod session; mod shared; mod tools; mod whatsapp; - use crate::bot::{ - create_session, get_session_history, get_sessions, index, set_mode_handler, start_session, - static_files, voice_start, voice_stop, websocket_handler, whatsapp_webhook, + auth_handler, create_session, get_session_history, get_sessions, index, set_mode_handler, + start_session, static_files, voice_start, voice_stop, websocket_handler, whatsapp_webhook_verify, }; use crate::channels::{VoiceAdapter, WebChannelAdapter}; @@ -49,14 +46,12 @@ async fn main() -> std::io::Result<()> { dotenv().ok(); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - info!("Starting General Bots 6.0..."); - let cfg = AppConfig::from_env(); let config = std::sync::Arc::new(cfg.clone()); let db_pool = match diesel::Connection::establish(&cfg.database_url()) { Ok(conn) => { - info!("Connected to main database"); + info!("Connected to main database successfully"); Arc::new(Mutex::new(conn)) } Err(e) => { @@ -79,7 +74,7 @@ async fn main() -> std::io::Result<()> { let db_custom_pool = match diesel::Connection::establish(&custom_db_url) { Ok(conn) => { - info!("Connected to custom database using constructed URL"); + info!("Connected to custom database successfully"); Arc::new(Mutex::new(conn)) } Err(e2) => { @@ -97,7 +92,7 @@ async fn main() -> std::io::Result<()> { let redis_client = match redis::Client::open("redis://127.0.0.1/") { Ok(client) => { - info!("Connected to Redis"); + info!("Connected to Redis successfully"); Some(Arc::new(client)) } Err(e) => { @@ -109,7 +104,6 @@ async fn main() -> std::io::Result<()> { let tool_manager = Arc::new(tools::ToolManager::new()); let llama_url = std::env::var("LLM_URL").unwrap_or_else(|_| "http://localhost:8081".to_string()); - let llm_provider = Arc::new(crate::llm::OpenAIClient::new( "empty".to_string(), Some(llama_url.clone()), @@ -121,13 +115,11 @@ async fn main() -> std::io::Result<()> { "api_key".to_string(), "api_secret".to_string(), )); - let whatsapp_adapter = Arc::new(WhatsAppAdapter::new( "whatsapp_token".to_string(), "phone_number_id".to_string(), "verify_token".to_string(), )); - let tool_api = Arc::new(tools::ToolApi::new()); let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new( @@ -150,7 +142,14 @@ async fn main() -> std::io::Result<()> { tool_manager: tool_manager.clone(), llm_provider: llm_provider.clone(), auth_service: auth_service.clone(), - channels: Arc::new(Mutex::new(HashMap::new())), + channels: Arc::new(Mutex::new({ + let mut map = HashMap::new(); + map.insert( + "web".to_string(), + web_adapter.clone() as Arc, + ); + map + })), response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())), web_adapter: web_adapter.clone(), voice_adapter: voice_adapter.clone(), @@ -171,7 +170,6 @@ async fn main() -> std::io::Result<()> { .max_age(3600); let app_state_clone = app_state.clone(); - let mut app = App::new() .wrap(cors) .wrap(Logger::default()) @@ -183,8 +181,8 @@ async fn main() -> std::io::Result<()> { .service(index) .service(static_files) .service(websocket_handler) + .service(auth_handler) .service(whatsapp_webhook_verify) - .service(whatsapp_webhook) .service(voice_start) .service(voice_stop) .service(create_session) diff --git a/templates/annoucements.gbai/annoucements.gbdialog/start-ctx.bas b/templates/annoucements.gbai/annoucements.gbdialog/start-ctx.bas deleted file mode 100644 index 58f97a65..00000000 --- a/templates/annoucements.gbai/annoucements.gbdialog/start-ctx.bas +++ /dev/null @@ -1,8 +0,0 @@ -TALK "Welcome to General Bots! What is your name?" -HEAR name -TALK "Hello, " + name - -text = GET "default.pdf" -SET_CONTEXT text - -resume = LLM "Build a resume from " + text diff --git a/templates/annoucements.gbai/annoucements.gbdialog/start.bas b/templates/annoucements.gbai/annoucements.gbdialog/start.bas index 08e611d8..19b86467 100644 --- a/templates/annoucements.gbai/annoucements.gbdialog/start.bas +++ b/templates/annoucements.gbai/annoucements.gbdialog/start.bas @@ -1,5 +1,7 @@ TALK "Welcome to General Bots!" -TALK "What is your name?" -HEAR name -TALK "Hello " + name + ", nice to meet you!" -SET_USER "92fcffaa-bf0a-41a9-8d99-5541709d695b" + +REM text = GET "default.pdf" +REM resume = LLM "Build a resume from " + text +REM TALK resume + +REM SET_CONTEXT text diff --git a/web/index.html b/web/index.html index 6588a8fe..5116bda4 100644 --- a/web/index.html +++ b/web/index.html @@ -1,1073 +1,666 @@ - - - General Bots 2400 - - - - - - - - - -
- - -