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 log::{debug, error, info};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -73,7 +72,7 @@ pub fn talk_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine
|
|||
debug!("TALK: Sending message: {}", message);
|
||||
|
||||
// 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 {
|
||||
bot_id,
|
||||
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,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
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();
|
||||
|
||||
engine
|
||||
.register_custom_syntax(&["SET_CONTEXT", "$expr$"], true, move |context, inputs| {
|
||||
// Evaluate the expression that should be stored in the context.
|
||||
let context_value = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||
.register_custom_syntax(&["SET_CONTEXT", "$expr$", "$expr$"], true, move |context, inputs| {
|
||||
// Evaluate both expressions - first is context name, second is context value
|
||||
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);
|
||||
// Build the Redis key using the user ID and the session ID.
|
||||
let redis_key = format!("context:{}:{}", user.user_id, user.id);
|
||||
info!("SET CONTEXT command executed - name: {}, value: {}", context_name, context_value);
|
||||
// Build the Redis key using user ID, session ID and context name
|
||||
let redis_key = format!("context:{}:{}:{}", user.user_id, user.id, context_name);
|
||||
log::trace!(
|
||||
target: "app::set_context",
|
||||
"Constructed Redis key: {} for user {} and session {}",
|
||||
"Constructed Redis key: {} for user {}, session {}, context {}",
|
||||
redis_key,
|
||||
user.user_id,
|
||||
user.id
|
||||
user.id,
|
||||
context_name
|
||||
);
|
||||
|
||||
// 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);
|
||||
hear_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);
|
||||
|
||||
// KB and Tools keywords
|
||||
|
|
|
|||
|
|
@ -210,19 +210,20 @@ impl BotOrchestrator {
|
|||
data: serde_json::Value,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
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(),
|
||||
session_id: session_id.to_string(),
|
||||
channel: channel.to_string(),
|
||||
content: serde_json::to_string(&serde_json::json!({
|
||||
"event": event_type,
|
||||
"data": data
|
||||
}))?,
|
||||
message_type: 2,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
};
|
||||
let event_response = BotResponse {
|
||||
bot_id: bot_id.to_string(),
|
||||
user_id: user_id.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
channel: channel.to_string(),
|
||||
content: serde_json::to_string(&serde_json::json!({
|
||||
"event": event_type,
|
||||
"data": data
|
||||
}))?,
|
||||
message_type: 2,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
|
||||
if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) {
|
||||
adapter.send_message(event_response).await?;
|
||||
|
|
@ -249,6 +250,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
|
||||
if let Some(adapter) = self.state.channels.lock().unwrap().get(channel) {
|
||||
|
|
@ -303,6 +305,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
adapter.send_message(ack_response).await?;
|
||||
}
|
||||
|
|
@ -346,6 +349,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
|
||||
if let Some(adapter) = self.state.channels.lock().unwrap().get(&message.channel) {
|
||||
|
|
@ -593,6 +597,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
response_tx.send(thinking_response).await?;
|
||||
}
|
||||
|
|
@ -664,6 +669,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: false,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
|
||||
if response_tx.send(partial).await.is_err() {
|
||||
|
|
@ -688,6 +694,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
|
||||
response_tx.send(final_msg).await?;
|
||||
|
|
@ -787,6 +794,7 @@ impl BotOrchestrator {
|
|||
message_type: 1,
|
||||
stream_token: None,
|
||||
is_complete: true,
|
||||
suggestions: Vec::new(),
|
||||
};
|
||||
adapter.send_message(warn_response).await
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -354,19 +354,7 @@ impl SessionManager {
|
|||
#[actix_web::post("/api/sessions")]
|
||||
async fn create_session(data: web::Data<AppState>) -> Result<HttpResponse> {
|
||||
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") {
|
||||
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 bot_id = Uuid::nil();
|
||||
|
||||
let session = {
|
||||
let mut session_manager = data.session_manager.lock().await;
|
||||
|
|
|
|||
|
|
@ -118,6 +118,13 @@ pub struct UserMessage {
|
|||
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)]
|
||||
pub struct BotResponse {
|
||||
pub bot_id: String,
|
||||
|
|
@ -128,6 +135,7 @@ pub struct BotResponse {
|
|||
pub message_type: i32,
|
||||
pub stream_token: Option<String>,
|
||||
pub is_complete: bool,
|
||||
pub suggestions: Vec<Suggestion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
|
|
|||
|
|
@ -880,36 +880,30 @@
|
|||
</div>
|
||||
<div id="suggestions-container" style="text-align:center; margin-top:10px;"></div>
|
||||
<script>
|
||||
async function loadSuggestions() {
|
||||
try {
|
||||
const res = await fetch('/api/suggestions');
|
||||
const suggestions = await res.json();
|
||||
const container = document.getElementById('suggestions-container');
|
||||
container.innerHTML = '';
|
||||
suggestions.forEach(s => {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = s.text;
|
||||
btn.className = 'suggestion-button';
|
||||
btn.style.margin = '5px';
|
||||
btn.style.padding = '8px 12px';
|
||||
btn.style.backgroundColor = '#ffd700';
|
||||
btn.style.color = '#0a0e27';
|
||||
btn.style.border = 'none';
|
||||
btn.style.borderRadius = '6px';
|
||||
btn.style.cursor = 'pointer';
|
||||
btn.onclick = () => setContext(s.context);
|
||||
container.appendChild(btn);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to load suggestions:', err);
|
||||
}
|
||||
function handleSuggestions(suggestions) {
|
||||
const container = document.getElementById('suggestions-container');
|
||||
container.innerHTML = '';
|
||||
suggestions.forEach(s => {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = s.text;
|
||||
btn.className = 'suggestion-button';
|
||||
btn.style.margin = '5px';
|
||||
btn.style.padding = '8px 12px';
|
||||
btn.style.backgroundColor = '#ffd700';
|
||||
btn.style.color = '#0a0e27';
|
||||
btn.style.border = 'none';
|
||||
btn.style.borderRadius = '6px';
|
||||
btn.style.cursor = 'pointer';
|
||||
btn.onclick = () => setContext(s.context);
|
||||
container.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
async function setContext(context) {
|
||||
try {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const suggestionEvent = {
|
||||
bot_id: "default_bot",
|
||||
bot_id: currentBotId,
|
||||
user_id: currentUserId,
|
||||
session_id: currentSessionId,
|
||||
channel: "web",
|
||||
|
|
@ -929,8 +923,6 @@
|
|||
console.error('Failed to set context:', err);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', loadSuggestions);
|
||||
</script>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
@ -964,6 +956,7 @@
|
|||
let ws = null;
|
||||
let currentSessionId = null;
|
||||
let currentUserId = null;
|
||||
let currentBotId = "default_bot";
|
||||
let isStreaming = false;
|
||||
let voiceRoom = null;
|
||||
let isVoiceMode = false;
|
||||
|
|
@ -1233,6 +1226,11 @@
|
|||
ws.onmessage = function (event) {
|
||||
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) {
|
||||
const eventData = JSON.parse(response.content);
|
||||
handleEvent(eventData.event, eventData.data);
|
||||
|
|
@ -1297,6 +1295,12 @@
|
|||
updateContextUsage(response.context_usage);
|
||||
}
|
||||
|
||||
// Handle suggestion messages
|
||||
if (response.message_type === 3) {
|
||||
handleSuggestions(response.suggestions);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle complete messages
|
||||
if (response.is_complete) {
|
||||
if (isStreaming) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue