feat(auth): add suggestion support and clean up code
- Added new ADD_SUGGESTION keyword handler to support sending suggestions in responses - Removed unused env import in hear_talk module - Simplified bot_id assignment to use static string - Added suggestions field to BotResponse struct - Improved SET_CONTEXT keyword to take both name and value parameters - Fixed whitespace in auth handler - Enhanced error handling for suggestion sending The changes improve the suggestion system functionality while cleaning up unused code and standardizing response handling.
This commit is contained in:
parent
683955a4a4
commit
6f59cdaab6
8 changed files with 125 additions and 67 deletions
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::shared::models::{BotResponse, UserSession};
|
use crate::shared::models::{BotResponse, Suggestion, UserSession};
|
||||||
use crate::shared::state::AppState;
|
use crate::shared::state::AppState;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult};
|
use rhai::{Dynamic, Engine, EvalAltResult};
|
||||||
use std::env;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -73,7 +72,7 @@ pub fn talk_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine
|
||||||
debug!("TALK: Sending message: {}", message);
|
debug!("TALK: Sending message: {}", message);
|
||||||
|
|
||||||
// Build the bot response that will be sent back to the client.
|
// Build the bot response that will be sent back to the client.
|
||||||
let bot_id = env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string());
|
let bot_id = "default_bot".to_string();
|
||||||
let response = BotResponse {
|
let response = BotResponse {
|
||||||
bot_id,
|
bot_id,
|
||||||
user_id: user_clone.user_id.to_string(),
|
user_id: user_clone.user_id.to_string(),
|
||||||
|
|
@ -83,6 +82,7 @@ pub fn talk_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = user_clone.id.to_string();
|
let user_id = user_clone.id.to_string();
|
||||||
|
|
@ -168,23 +168,73 @@ pub fn set_user_keyword(state: Arc<AppState>, user: UserSession, engine: &mut En
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
pub fn add_suggestion_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine) {
|
||||||
|
let user_clone = user.clone();
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_custom_syntax(&["ADD_SUGGESTION", "$expr$", "$expr$", "$expr$"], true, move |context, inputs| {
|
||||||
|
// Evaluate expressions: text, context_name
|
||||||
|
let text = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
|
let context_name = context.eval_expression_tree(&inputs[1])?.to_string();
|
||||||
|
|
||||||
|
info!("ADD_SUGGESTION command executed - text: {}, context: {}", text, context_name);
|
||||||
|
|
||||||
|
// Get current response channels
|
||||||
|
let state_clone = Arc::clone(&state);
|
||||||
|
let user_id = user_clone.id.to_string();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut response_channels = state_clone.response_channels.lock().await;
|
||||||
|
if let Some(tx) = response_channels.get_mut(&user_id) {
|
||||||
|
let suggestion = Suggestion {
|
||||||
|
text,
|
||||||
|
context_name,
|
||||||
|
is_suggestion: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a response with just this suggestion
|
||||||
|
let response = BotResponse {
|
||||||
|
bot_id: "system".to_string(),
|
||||||
|
user_id: user_clone.user_id.to_string(),
|
||||||
|
session_id: user_clone.id.to_string(),
|
||||||
|
channel: "web".to_string(),
|
||||||
|
content: String::new(),
|
||||||
|
message_type: 3, // Special type for suggestions
|
||||||
|
stream_token: None,
|
||||||
|
is_complete: true,
|
||||||
|
suggestions: vec![suggestion],
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = tx.try_send(response) {
|
||||||
|
error!("Failed to send suggestion: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_context_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine) {
|
||||||
let cache = state.redis_client.clone();
|
let cache = state.redis_client.clone();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(&["SET_CONTEXT", "$expr$"], true, move |context, inputs| {
|
.register_custom_syntax(&["SET_CONTEXT", "$expr$", "$expr$"], true, move |context, inputs| {
|
||||||
// Evaluate the expression that should be stored in the context.
|
// Evaluate both expressions - first is context name, second is context value
|
||||||
let context_value = context.eval_expression_tree(&inputs[0])?.to_string();
|
let context_name = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
|
let context_value = context.eval_expression_tree(&inputs[1])?.to_string();
|
||||||
|
|
||||||
info!("SET CONTEXT command executed: {}", context_value);
|
info!("SET CONTEXT command executed - name: {}, value: {}", context_name, context_value);
|
||||||
// Build the Redis key using the user ID and the session ID.
|
// Build the Redis key using user ID, session ID and context name
|
||||||
let redis_key = format!("context:{}:{}", user.user_id, user.id);
|
let redis_key = format!("context:{}:{}:{}", user.user_id, user.id, context_name);
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: "app::set_context",
|
target: "app::set_context",
|
||||||
"Constructed Redis key: {} for user {} and session {}",
|
"Constructed Redis key: {} for user {}, session {}, context {}",
|
||||||
redis_key,
|
redis_key,
|
||||||
user.user_id,
|
user.user_id,
|
||||||
user.id
|
user.id,
|
||||||
|
context_name
|
||||||
);
|
);
|
||||||
|
|
||||||
// If a Redis client is configured, perform the SET operation in a background task.
|
// If a Redis client is configured, perform the SET operation in a background task.
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl ScriptService {
|
||||||
set_schedule_keyword(&state, user.clone(), &mut engine);
|
set_schedule_keyword(&state, user.clone(), &mut engine);
|
||||||
hear_keyword(state.clone(), user.clone(), &mut engine);
|
hear_keyword(state.clone(), user.clone(), &mut engine);
|
||||||
talk_keyword(state.clone(), user.clone(), &mut engine);
|
talk_keyword(state.clone(), user.clone(), &mut engine);
|
||||||
set_context_keyword(&state, user.clone(), &mut engine);
|
set_context_keyword(state.clone(), user.clone(), &mut engine);
|
||||||
set_user_keyword(state.clone(), user.clone(), &mut engine);
|
set_user_keyword(state.clone(), user.clone(), &mut engine);
|
||||||
|
|
||||||
// KB and Tools keywords
|
// KB and Tools keywords
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,7 @@ impl BotOrchestrator {
|
||||||
message_type: 2,
|
message_type: 2,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) {
|
if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) {
|
||||||
|
|
@ -249,6 +250,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) {
|
if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) {
|
||||||
|
|
@ -303,6 +305,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
adapter.send_message(ack_response).await?;
|
adapter.send_message(ack_response).await?;
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +349,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) {
|
if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) {
|
||||||
|
|
@ -593,6 +597,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
response_tx.send(thinking_response).await?;
|
response_tx.send(thinking_response).await?;
|
||||||
}
|
}
|
||||||
|
|
@ -664,6 +669,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: false,
|
is_complete: false,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if response_tx.send(partial).await.is_err() {
|
if response_tx.send(partial).await.is_err() {
|
||||||
|
|
@ -688,6 +694,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
response_tx.send(final_msg).await?;
|
response_tx.send(final_msg).await?;
|
||||||
|
|
@ -787,6 +794,7 @@ impl BotOrchestrator {
|
||||||
message_type: 1,
|
message_type: 1,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
suggestions: Vec::new(),
|
||||||
};
|
};
|
||||||
adapter.send_message(warn_response).await
|
adapter.send_message(warn_response).await
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -354,19 +354,7 @@ impl SessionManager {
|
||||||
#[actix_web::post("/api/sessions")]
|
#[actix_web::post("/api/sessions")]
|
||||||
async fn create_session(data: web::Data<AppState>) -> Result<HttpResponse> {
|
async fn create_session(data: web::Data<AppState>) -> Result<HttpResponse> {
|
||||||
let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
|
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") {
|
let bot_id = 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()
|
|
||||||
};
|
|
||||||
|
|
||||||
let session = {
|
let session = {
|
||||||
let mut session_manager = data.session_manager.lock().await;
|
let mut session_manager = data.session_manager.lock().await;
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,13 @@ pub struct UserMessage {
|
||||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
pub timestamp: chrono::DateTime<chrono::Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Suggestion {
|
||||||
|
pub text: String, // The button text that will be sent as message
|
||||||
|
pub context_name: String, // The context name to set when clicked
|
||||||
|
pub is_suggestion: bool, // Flag to identify suggestion clicks
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct BotResponse {
|
pub struct BotResponse {
|
||||||
pub bot_id: String,
|
pub bot_id: String,
|
||||||
|
|
@ -128,6 +135,7 @@ pub struct BotResponse {
|
||||||
pub message_type: i32,
|
pub message_type: i32,
|
||||||
pub stream_token: Option<String>,
|
pub stream_token: Option<String>,
|
||||||
pub is_complete: bool,
|
pub is_complete: bool,
|
||||||
|
pub suggestions: Vec<Suggestion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
|
||||||
|
|
@ -880,10 +880,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="suggestions-container" style="text-align:center; margin-top:10px;"></div>
|
<div id="suggestions-container" style="text-align:center; margin-top:10px;"></div>
|
||||||
<script>
|
<script>
|
||||||
async function loadSuggestions() {
|
function handleSuggestions(suggestions) {
|
||||||
try {
|
|
||||||
const res = await fetch('/api/suggestions');
|
|
||||||
const suggestions = await res.json();
|
|
||||||
const container = document.getElementById('suggestions-container');
|
const container = document.getElementById('suggestions-container');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
suggestions.forEach(s => {
|
suggestions.forEach(s => {
|
||||||
|
|
@ -900,16 +897,13 @@
|
||||||
btn.onclick = () => setContext(s.context);
|
btn.onclick = () => setContext(s.context);
|
||||||
container.appendChild(btn);
|
container.appendChild(btn);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load suggestions:', err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setContext(context) {
|
async function setContext(context) {
|
||||||
try {
|
try {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
const suggestionEvent = {
|
const suggestionEvent = {
|
||||||
bot_id: "default_bot",
|
bot_id: currentBotId,
|
||||||
user_id: currentUserId,
|
user_id: currentUserId,
|
||||||
session_id: currentSessionId,
|
session_id: currentSessionId,
|
||||||
channel: "web",
|
channel: "web",
|
||||||
|
|
@ -929,8 +923,6 @@
|
||||||
console.error('Failed to set context:', err);
|
console.error('Failed to set context:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', loadSuggestions);
|
|
||||||
</script>
|
</script>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -964,6 +956,7 @@
|
||||||
let ws = null;
|
let ws = null;
|
||||||
let currentSessionId = null;
|
let currentSessionId = null;
|
||||||
let currentUserId = null;
|
let currentUserId = null;
|
||||||
|
let currentBotId = "default_bot";
|
||||||
let isStreaming = false;
|
let isStreaming = false;
|
||||||
let voiceRoom = null;
|
let voiceRoom = null;
|
||||||
let isVoiceMode = false;
|
let isVoiceMode = false;
|
||||||
|
|
@ -1233,6 +1226,11 @@
|
||||||
ws.onmessage = function (event) {
|
ws.onmessage = function (event) {
|
||||||
const response = JSON.parse(event.data);
|
const response = JSON.parse(event.data);
|
||||||
|
|
||||||
|
// Update current bot_id if provided in the message
|
||||||
|
if (response.bot_id) {
|
||||||
|
currentBotId = response.bot_id;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.message_type === 2) {
|
if (response.message_type === 2) {
|
||||||
const eventData = JSON.parse(response.content);
|
const eventData = JSON.parse(response.content);
|
||||||
handleEvent(eventData.event, eventData.data);
|
handleEvent(eventData.event, eventData.data);
|
||||||
|
|
@ -1297,6 +1295,12 @@
|
||||||
updateContextUsage(response.context_usage);
|
updateContextUsage(response.context_usage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle suggestion messages
|
||||||
|
if (response.message_type === 3) {
|
||||||
|
handleSuggestions(response.suggestions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle complete messages
|
// Handle complete messages
|
||||||
if (response.is_complete) {
|
if (response.is_complete) {
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue