- New features for start.bas
This commit is contained in:
parent
733f7cba10
commit
3aeb3ebc70
16 changed files with 836 additions and 166 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1021,6 +1021,7 @@ dependencies = [
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"num-format",
|
"num-format",
|
||||||
"qdrant-client",
|
"qdrant-client",
|
||||||
|
"rand 0.9.2",
|
||||||
"redis",
|
"redis",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest 0.12.23",
|
"reqwest 0.12.23",
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,4 @@ zip = "2.2"
|
||||||
time = "0.3.44"
|
time = "0.3.44"
|
||||||
aws-sdk-s3 = "1.108.0"
|
aws-sdk-s3 = "1.108.0"
|
||||||
headless_chrome = { version = "1.0.18", optional = true }
|
headless_chrome = { version = "1.0.18", optional = true }
|
||||||
|
rand = "0.9.2"
|
||||||
|
|
|
||||||
19
scripts/dev/build_prompt.sh → add-req.sh
Executable file → Normal file
19
scripts/dev/build_prompt.sh → add-req.sh
Executable file → Normal file
|
|
@ -1,16 +1,15 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
PROJECT_ROOT="$SCRIPT_DIR"
|
||||||
OUTPUT_FILE="$SCRIPT_DIR/prompt.out"
|
OUTPUT_FILE="$SCRIPT_DIR/prompt.out"
|
||||||
rm $OUTPUT_FILE
|
rm $OUTPUT_FILE
|
||||||
echo "Consolidated LLM Context" > "$OUTPUT_FILE"
|
echo "Consolidated LLM Context" > "$OUTPUT_FILE"
|
||||||
|
|
||||||
prompts=(
|
prompts=(
|
||||||
"../../prompts/dev/shared.md"
|
"./prompts/dev/shared.md"
|
||||||
"../../Cargo.toml"
|
"./Cargo.toml"
|
||||||
#"../../prompts/dev/fix.md"
|
"./prompts/dev/generation.md"
|
||||||
"../../prompts/dev/generation.md"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for file in "${prompts[@]}"; do
|
for file in "${prompts[@]}"; do
|
||||||
|
|
@ -23,12 +22,12 @@ dirs=(
|
||||||
#"automation"
|
#"automation"
|
||||||
#"basic"
|
#"basic"
|
||||||
"bot"
|
"bot"
|
||||||
"channels"
|
#"channels"
|
||||||
"config"
|
"config"
|
||||||
"context"
|
#"context"
|
||||||
#"email"
|
#"email"
|
||||||
#"file"
|
#"file"
|
||||||
"llm"
|
#"llm"
|
||||||
#"llm_legacy"
|
#"llm_legacy"
|
||||||
#"org"
|
#"org"
|
||||||
"session"
|
"session"
|
||||||
|
|
@ -36,7 +35,7 @@ dirs=(
|
||||||
#"tests"
|
#"tests"
|
||||||
#"tools"
|
#"tools"
|
||||||
#"web_automation"
|
#"web_automation"
|
||||||
"whatsapp"
|
#"whatsapp"
|
||||||
)
|
)
|
||||||
for dir in "${dirs[@]}"; do
|
for dir in "${dirs[@]}"; do
|
||||||
find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read file; do
|
find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read file; do
|
||||||
|
|
@ -54,6 +53,8 @@ cat "$PROJECT_ROOT/src/basic/keywords/hear_talk.rs" >> "$OUTPUT_FILE"
|
||||||
echo "$PROJECT_ROOT/src/basic/mod.rs">> "$OUTPUT_FILE"
|
echo "$PROJECT_ROOT/src/basic/mod.rs">> "$OUTPUT_FILE"
|
||||||
cat "$PROJECT_ROOT/src/basic/mod.rs" >> "$OUTPUT_FILE"
|
cat "$PROJECT_ROOT/src/basic/mod.rs" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
echo "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/start.bas" >> "$OUTPUT_FILE"
|
||||||
|
cat "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/start.bas" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
echo "" >> "$OUTPUT_FILE"
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
# LLM
|
||||||
|
|
||||||
|
Zed Assistant: Groq + GPT OSS 120B |
|
||||||
|
FIX Manual: DeepSeek | ChatGPT 120B | Claude 4.5 Thinking | Mistral
|
||||||
|
ADD Manual: Claude/DeepSeek -> DeepSeek
|
||||||
|
|
||||||
# DEV
|
# DEV
|
||||||
|
|
||||||
curl -sSL https://get.livekit.io | bash
|
curl -sSL https://get.livekit.io | bash
|
||||||
|
|
|
||||||
6
docs/GLOSSARY.md
Normal file
6
docs/GLOSSARY.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
RPM: Requests per minute
|
||||||
|
RPD: Requests per day
|
||||||
|
TPM: Tokens per minute
|
||||||
|
TPD: Tokens per day
|
||||||
|
ASH: Audio seconds per hour
|
||||||
|
ASD: Audio seconds per day
|
||||||
59
fix-errors.sh
Executable file
59
fix-errors.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$SCRIPT_DIR"
|
||||||
|
OUTPUT_FILE="$SCRIPT_DIR/prompt.out"
|
||||||
|
rm $OUTPUT_FILE
|
||||||
|
echo "Please, fix this consolidated LLM Context" > "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
prompts=(
|
||||||
|
"./prompts/dev/shared.md"
|
||||||
|
"./Cargo.toml"
|
||||||
|
"./prompts/dev/fix.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
for file in "${prompts[@]}"; do
|
||||||
|
cat "$file" >> "$OUTPUT_FILE"
|
||||||
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
dirs=(
|
||||||
|
#"auth"
|
||||||
|
#"automation"
|
||||||
|
#"basic"
|
||||||
|
"bot"
|
||||||
|
#"channels"
|
||||||
|
#"config"
|
||||||
|
#"context"
|
||||||
|
#"email"
|
||||||
|
#"file"
|
||||||
|
#"llm"
|
||||||
|
#"llm_legacy"
|
||||||
|
#"org"
|
||||||
|
"session"
|
||||||
|
"shared"
|
||||||
|
#"tests"
|
||||||
|
#"tools"
|
||||||
|
#"web_automation"
|
||||||
|
#"whatsapp"
|
||||||
|
)
|
||||||
|
for dir in "${dirs[@]}"; do
|
||||||
|
find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read file; do
|
||||||
|
echo $file >> "$OUTPUT_FILE"
|
||||||
|
cat "$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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
cargo build --message-format=short 2>&1 | grep -E 'error' >> "$OUTPUT_FILE"
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
|
use crate::shared::models::{BotResponse, UserSession};
|
||||||
use crate::shared::state::AppState;
|
use crate::shared::state::AppState;
|
||||||
use crate::{channels::ChannelAdapter, shared::models::UserSession};
|
use log::{debug, error, info};
|
||||||
use log::info;
|
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult};
|
use rhai::{Dynamic, Engine, EvalAltResult};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn hear_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
pub fn hear_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine) {
|
||||||
let session_id = user.id;
|
let session_id = user.id;
|
||||||
let cache = state.redis_client.clone();
|
let state_clone = Arc::clone(&state);
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(&["HEAR", "$ident$"], true, move |_context, inputs| {
|
.register_custom_syntax(&["HEAR", "$ident$"], true, move |_context, inputs| {
|
||||||
|
|
@ -19,22 +21,24 @@ pub fn hear_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
||||||
variable_name
|
variable_name
|
||||||
);
|
);
|
||||||
|
|
||||||
let cache_clone = cache.clone();
|
let state_for_spawn = Arc::clone(&state_clone);
|
||||||
let session_id_clone = session_id;
|
let session_id_clone = session_id;
|
||||||
let var_name_clone = variable_name.clone();
|
let var_name_clone = variable_name.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
log::debug!(
|
debug!(
|
||||||
"HEAR: Starting async task for session {} and variable '{}'",
|
"HEAR: Setting session {} to wait for input for variable '{}'",
|
||||||
session_id_clone,
|
session_id_clone, var_name_clone
|
||||||
var_name_clone
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(cache_client) = &cache_clone {
|
let mut session_manager = state_for_spawn.session_manager.lock().await;
|
||||||
let mut conn = match cache_client.get_multiplexed_async_connection().await {
|
session_manager.mark_waiting(session_id_clone);
|
||||||
|
|
||||||
|
if let Some(redis_client) = &state_for_spawn.redis_client {
|
||||||
|
let mut conn = match redis_client.get_multiplexed_async_connection().await {
|
||||||
Ok(conn) => conn,
|
Ok(conn) => conn,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to connect to cache: {}", e);
|
error!("Failed to connect to cache: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -56,10 +60,8 @@ pub fn hear_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn talk_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
pub fn talk_keyword(state: Arc<AppState>, user: UserSession, engine: &mut Engine) {
|
||||||
use crate::shared::models::BotResponse;
|
let state_clone = Arc::clone(&state);
|
||||||
|
|
||||||
let state_clone = state.clone();
|
|
||||||
let user_clone = user.clone();
|
let user_clone = user.clone();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
|
|
@ -68,37 +70,97 @@ pub fn talk_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
||||||
|
|
||||||
info!("TALK command executed: {}", message);
|
info!("TALK command executed: {}", message);
|
||||||
|
|
||||||
let response = BotResponse {
|
let state_for_spawn = Arc::clone(&state_clone);
|
||||||
bot_id: "default_bot".to_string(),
|
let user_clone_spawn = user_clone.clone();
|
||||||
user_id: user_clone.user_id.to_string(),
|
let message_clone = message.clone();
|
||||||
session_id: user_clone.id.to_string(),
|
|
||||||
channel: "basic".to_string(),
|
|
||||||
content: message,
|
|
||||||
message_type: 1,
|
|
||||||
stream_token: None,
|
|
||||||
is_complete: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let state_for_spawn = state_clone.clone();
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = state_for_spawn.web_adapter.send_message(response).await {
|
debug!("TALK: Sending message via WebSocket: {}", message_clone);
|
||||||
log::error!("Failed to send TALK message: {}", e);
|
|
||||||
|
let bot_id =
|
||||||
|
std::env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string());
|
||||||
|
|
||||||
|
let response = BotResponse {
|
||||||
|
bot_id: bot_id,
|
||||||
|
user_id: user_clone_spawn.user_id.to_string(),
|
||||||
|
session_id: user_clone_spawn.id.to_string(),
|
||||||
|
channel: "web".to_string(),
|
||||||
|
content: message_clone,
|
||||||
|
message_type: 1,
|
||||||
|
stream_token: None,
|
||||||
|
is_complete: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response_channels = state_for_spawn.response_channels.lock().await;
|
||||||
|
if let Some(tx) = response_channels.get(&user_clone_spawn.id.to_string()) {
|
||||||
|
if let Err(e) = tx.send(response).await {
|
||||||
|
error!("Failed to send TALK message via WebSocket: {}", e);
|
||||||
|
} else {
|
||||||
|
debug!("TALK message sent successfully via WebSocket");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"No WebSocket connection found for session {}, sending via web adapter",
|
||||||
|
user_clone_spawn.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = state_for_spawn
|
||||||
|
.web_adapter
|
||||||
|
.send_message_to_session(&user_clone_spawn.id.to_string(), response)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to send TALK message via web adapter: {}", e);
|
||||||
|
} else {
|
||||||
|
debug!("TALK message sent successfully via web adapter");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
|
||||||
let cache = state.redis_client.clone();
|
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(
|
.register_custom_syntax(&["SET_USER", "$expr$"], true, move |context, inputs| {
|
||||||
&["SET", "CONTEXT", "$expr$"],
|
let user_id_str = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
true,
|
|
||||||
move |context, inputs| {
|
info!("SET USER command executed with ID: {}", user_id_str);
|
||||||
|
|
||||||
|
match Uuid::parse_str(&user_id_str) {
|
||||||
|
Ok(user_id) => {
|
||||||
|
debug!("Successfully parsed user UUID: {}", user_id);
|
||||||
|
|
||||||
|
let state_for_spawn = Arc::clone(&state_clone);
|
||||||
|
let user_clone_spawn = user_clone.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut session_manager = state_for_spawn.session_manager.lock().await;
|
||||||
|
|
||||||
|
if let Err(e) = session_manager.update_user_id(user_clone_spawn.id, user_id)
|
||||||
|
{
|
||||||
|
debug!("Failed to update user ID in session: {}", e);
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"Updated session {} to user ID: {}",
|
||||||
|
user_clone_spawn.id, user_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("Invalid UUID format for SET USER: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
|
||||||
|
let cache = state.redis_client.clone();
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_custom_syntax(&["SET_CONTEXT", "$expr$"], true, move |context, inputs| {
|
||||||
let context_value = context.eval_expression_tree(&inputs[0])?.to_string();
|
let context_value = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
|
|
||||||
info!("SET CONTEXT command executed: {}", context_value);
|
info!("SET CONTEXT command executed: {}", context_value);
|
||||||
|
|
@ -112,7 +174,7 @@ pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Eng
|
||||||
let mut conn = match cache_client.get_multiplexed_async_connection().await {
|
let mut conn = match cache_client.get_multiplexed_async_connection().await {
|
||||||
Ok(conn) => conn,
|
Ok(conn) => conn,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to connect to cache: {}", e);
|
error!("Failed to connect to cache: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -126,7 +188,7 @@ pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Eng
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
},
|
})
|
||||||
)
|
.unwrap();
|
||||||
.unwrap();
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub mod set_schedule;
|
||||||
pub mod wait;
|
pub mod wait;
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
pub mod create_draft;
|
pub mod create_draft_keyword;
|
||||||
|
|
||||||
#[cfg(feature = "web_automation")]
|
#[cfg(feature = "web_automation")]
|
||||||
pub mod get_website;
|
pub mod get_website;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use self::keywords::create_draft_keyword;
|
||||||
use self::keywords::get_website::get_website_keyword;
|
use self::keywords::get_website::get_website_keyword;
|
||||||
|
|
||||||
pub struct ScriptService {
|
pub struct ScriptService {
|
||||||
engine: Engine,
|
pub engine: Engine,
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
user: UserSession,
|
user: UserSession,
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +56,8 @@ impl ScriptService {
|
||||||
print_keyword(&state, user.clone(), &mut engine);
|
print_keyword(&state, user.clone(), &mut engine);
|
||||||
on_keyword(&state, user.clone(), &mut engine);
|
on_keyword(&state, user.clone(), &mut engine);
|
||||||
set_schedule_keyword(&state, user.clone(), &mut engine);
|
set_schedule_keyword(&state, user.clone(), &mut engine);
|
||||||
hear_keyword(&state, user.clone(), &mut engine);
|
hear_keyword(state.clone(), user.clone(), &mut engine);
|
||||||
talk_keyword(&state, user.clone(), &mut engine);
|
talk_keyword(state.clone(), user.clone(), &mut engine);
|
||||||
set_context_keyword(&state, user.clone(), &mut engine);
|
set_context_keyword(&state, user.clone(), &mut engine);
|
||||||
|
|
||||||
#[cfg(feature = "web_automation")]
|
#[cfg(feature = "web_automation")]
|
||||||
|
|
@ -141,6 +141,7 @@ impl ScriptService {
|
||||||
"HEAR",
|
"HEAR",
|
||||||
"TALK",
|
"TALK",
|
||||||
"SET CONTEXT",
|
"SET CONTEXT",
|
||||||
|
"SET USER",
|
||||||
];
|
];
|
||||||
|
|
||||||
let is_basic_command = basic_commands.iter().any(|&cmd| trimmed.starts_with(cmd));
|
let is_basic_command = basic_commands.iter().any(|&cmd| trimmed.starts_with(cmd));
|
||||||
|
|
|
||||||
373
src/bot/mod.rs
373
src/bot/mod.rs
|
|
@ -125,6 +125,37 @@ impl BotOrchestrator {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_direct_message(
|
||||||
|
&self,
|
||||||
|
session_id: &str,
|
||||||
|
channel: &str,
|
||||||
|
content: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
debug!(
|
||||||
|
"Sending direct message to session {}: '{}'",
|
||||||
|
session_id, content
|
||||||
|
);
|
||||||
|
|
||||||
|
let bot_response = BotResponse {
|
||||||
|
bot_id: "default_bot".to_string(),
|
||||||
|
user_id: "default_user".to_string(),
|
||||||
|
session_id: session_id.to_string(),
|
||||||
|
channel: channel.to_string(),
|
||||||
|
content: content.to_string(),
|
||||||
|
message_type: 1,
|
||||||
|
stream_token: None,
|
||||||
|
is_complete: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn process_message(
|
pub async fn process_message(
|
||||||
&self,
|
&self,
|
||||||
message: UserMessage,
|
message: UserMessage,
|
||||||
|
|
@ -143,28 +174,32 @@ impl BotOrchestrator {
|
||||||
warn!("Invalid user ID provided, generated new UUID: {}", new_id);
|
warn!("Invalid user ID provided, generated new UUID: {}", new_id);
|
||||||
new_id
|
new_id
|
||||||
});
|
});
|
||||||
let bot_id = Uuid::parse_str(&message.bot_id)
|
|
||||||
.unwrap_or_else(|_| Uuid::parse_str("00000000-0000-0000-0000-000000000000").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()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
warn!("BOT_GUID not set in environment, using nil UUID");
|
||||||
|
Uuid::nil()
|
||||||
|
};
|
||||||
|
|
||||||
debug!("Parsed user_id: {}, bot_id: {}", user_id, bot_id);
|
debug!("Parsed user_id: {}, bot_id: {}", user_id, bot_id);
|
||||||
|
|
||||||
let session = {
|
let session = {
|
||||||
let mut session_manager = self.state.session_manager.lock().await;
|
let mut session_manager = self.state.session_manager.lock().await;
|
||||||
match session_manager.get_user_session(user_id, bot_id)? {
|
match session_manager.get_or_create_user_session(user_id, bot_id, "New Conversation")? {
|
||||||
Some(session) => {
|
Some(session) => {
|
||||||
debug!("Found existing session: {}", session.id);
|
debug!("Found existing session: {}", session.id);
|
||||||
session
|
session
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!(
|
error!(
|
||||||
"Creating new session for user {} with bot {}",
|
"Failed to create session for user {} with bot {}",
|
||||||
user_id, bot_id
|
user_id, bot_id
|
||||||
);
|
);
|
||||||
let new_session =
|
return Err("Failed to create session".into());
|
||||||
session_manager.create_session(user_id, bot_id, "New Conversation")?;
|
|
||||||
debug!("New session created: {}", new_session.id);
|
|
||||||
Self::run_start_script(&new_session, Arc::clone(&self.state)).await;
|
|
||||||
new_session
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -296,43 +331,34 @@ impl BotOrchestrator {
|
||||||
);
|
);
|
||||||
debug!("Message content: '{}'", message.content);
|
debug!("Message content: '{}'", message.content);
|
||||||
|
|
||||||
let mut user_id = Uuid::parse_str(&message.user_id).unwrap_or_else(|_| {
|
let user_id = Uuid::parse_str(&message.user_id).unwrap_or_else(|_| {
|
||||||
let new_id = Uuid::new_v4();
|
let new_id = Uuid::new_v4();
|
||||||
warn!("Invalid user ID, generated new: {}", new_id);
|
warn!("Invalid user ID, generated new: {}", new_id);
|
||||||
new_id
|
new_id
|
||||||
});
|
});
|
||||||
let bot_id = Uuid::parse_str(&message.bot_id).unwrap_or_else(|_| {
|
|
||||||
warn!("Invalid bot ID, using nil UUID");
|
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()
|
Uuid::nil()
|
||||||
});
|
};
|
||||||
|
|
||||||
debug!("User ID: {}, Bot ID: {}", user_id, bot_id);
|
debug!("User ID: {}, Bot ID: {}", user_id, bot_id);
|
||||||
|
|
||||||
let mut auth = self.state.auth_service.lock().await;
|
|
||||||
let user_exists = auth.get_user_by_id(user_id)?;
|
|
||||||
|
|
||||||
if user_exists.is_none() {
|
|
||||||
debug!("User {} not found, creating anonymous user", user_id);
|
|
||||||
user_id = auth.create_user("anonymous1", "anonymous@local", "password")?;
|
|
||||||
info!("Created new anonymous user: {}", user_id);
|
|
||||||
} else {
|
|
||||||
user_id = user_exists.unwrap().id;
|
|
||||||
debug!("Found existing user: {}", user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let session = {
|
let session = {
|
||||||
let mut sm = self.state.session_manager.lock().await;
|
let mut sm = self.state.session_manager.lock().await;
|
||||||
match sm.get_user_session(user_id, bot_id)? {
|
match sm.get_or_create_user_session(user_id, bot_id, "New Conversation")? {
|
||||||
Some(sess) => {
|
Some(sess) => {
|
||||||
debug!("Using existing session: {}", sess.id);
|
debug!("Using existing session: {}", sess.id);
|
||||||
sess
|
sess
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!("Creating new session for streaming");
|
error!("Failed to create session for streaming");
|
||||||
let new_session = sm.create_session(user_id, bot_id, "New Conversation")?;
|
return Err("Failed to create session".into());
|
||||||
debug!("New session created: {}", new_session.id);
|
|
||||||
Self::run_start_script(&new_session, Arc::clone(&self.state)).await;
|
|
||||||
new_session
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -557,23 +583,27 @@ impl BotOrchestrator {
|
||||||
warn!("Invalid user ID, generated new: {}", new_id);
|
warn!("Invalid user ID, generated new: {}", new_id);
|
||||||
new_id
|
new_id
|
||||||
});
|
});
|
||||||
let bot_id = Uuid::parse_str(&message.bot_id)
|
|
||||||
.unwrap_or_else(|_| Uuid::parse_str("00000000-0000-0000-0000-000000000000").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()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
warn!("BOT_GUID not set in environment, using nil UUID");
|
||||||
|
Uuid::nil()
|
||||||
|
};
|
||||||
|
|
||||||
let session = {
|
let session = {
|
||||||
let mut session_manager = self.state.session_manager.lock().await;
|
let mut session_manager = self.state.session_manager.lock().await;
|
||||||
match session_manager.get_user_session(user_id, bot_id)? {
|
match session_manager.get_or_create_user_session(user_id, bot_id, "New Conversation")? {
|
||||||
Some(session) => {
|
Some(session) => {
|
||||||
debug!("Found existing session: {}", session.id);
|
debug!("Found existing session: {}", session.id);
|
||||||
session
|
session
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!("Creating new session for tools processing");
|
error!("Failed to create session for tools processing");
|
||||||
let new_session =
|
return Err("Failed to create session".into());
|
||||||
session_manager.create_session(user_id, bot_id, "New Conversation")?;
|
|
||||||
debug!("New session created: {}", new_session.id);
|
|
||||||
Self::run_start_script(&new_session, Arc::clone(&self.state)).await;
|
|
||||||
new_session
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -705,10 +735,17 @@ impl BotOrchestrator {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_start_script(session: &UserSession, state: Arc<AppState>) {
|
pub async fn run_start_script(
|
||||||
info!("Running start script for session: {}", session.id);
|
session: &UserSession,
|
||||||
|
state: Arc<AppState>,
|
||||||
let start_script_path = "start.bas";
|
token_id: Option<String>,
|
||||||
|
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
info!(
|
||||||
|
"Running start script for session: {} with token_id: {:?}",
|
||||||
|
session.id, token_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let start_script_path = "./templates/annoucements.gbai/annoucements.gbdialog/start.bas";
|
||||||
let start_script = match std::fs::read_to_string(start_script_path) {
|
let start_script = match std::fs::read_to_string(start_script_path) {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
debug!("Loaded start script from {}", start_script_path);
|
debug!("Loaded start script from {}", start_script_path);
|
||||||
|
|
@ -720,31 +757,39 @@ impl BotOrchestrator {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Start script content for session {}: {}", session.id, start_script);
|
debug!(
|
||||||
|
"Start script content for session {}: {}",
|
||||||
|
session.id, start_script
|
||||||
|
);
|
||||||
|
|
||||||
let session_clone = session.clone();
|
let session_clone = session.clone();
|
||||||
let state_clone = state.clone();
|
let state_clone = state.clone();
|
||||||
tokio::spawn(async move {
|
|
||||||
let state_for_run = state_clone.clone();
|
let script_service = crate::basic::ScriptService::new(state_clone, session_clone.clone());
|
||||||
match crate::basic::ScriptService::new(state_clone, session_clone.clone())
|
|
||||||
.compile(&start_script)
|
if let Some(token_id_value) = token_id {
|
||||||
.and_then(|ast| {
|
debug!("Token ID available for script: {}", token_id_value);
|
||||||
crate::basic::ScriptService::new(state_for_run, session_clone.clone()).run(&ast)
|
}
|
||||||
}) {
|
|
||||||
Ok(_) => {
|
match script_service
|
||||||
info!(
|
.compile(&start_script)
|
||||||
"Start script executed successfully for session {}",
|
.and_then(|ast| script_service.run(&ast))
|
||||||
session_clone.id
|
{
|
||||||
);
|
Ok(result) => {
|
||||||
}
|
info!(
|
||||||
Err(e) => {
|
"Start script executed successfully for session {}, result: {}",
|
||||||
error!(
|
session_clone.id, result
|
||||||
"Failed to run start script for session {}: {}",
|
);
|
||||||
session_clone.id, e
|
Ok(true)
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Failed to run start script for session {}: {}",
|
||||||
|
session_clone.id, e
|
||||||
|
);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_warning(
|
pub async fn send_warning(
|
||||||
|
|
@ -795,6 +840,73 @@ impl BotOrchestrator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn trigger_auto_welcome(
|
||||||
|
&self,
|
||||||
|
session_id: &str,
|
||||||
|
user_id: &str,
|
||||||
|
_bot_id: &str,
|
||||||
|
token_id: Option<String>,
|
||||||
|
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
info!(
|
||||||
|
"Triggering auto welcome for session: {} with token_id: {:?}",
|
||||||
|
session_id, token_id
|
||||||
|
);
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Failed to create session for auto welcome");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Self::run_start_script(&session, Arc::clone(&self.state), token_id).await?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Auto welcome completed for session: {} with result: {}",
|
||||||
|
session_id, result
|
||||||
|
);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_web_response_channel(
|
||||||
|
&self,
|
||||||
|
session_id: &str,
|
||||||
|
) -> Result<mpsc::Sender<BotResponse>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let response_channels = self.state.response_channels.lock().await;
|
||||||
|
if let Some(tx) = response_channels.get(session_id) {
|
||||||
|
Ok(tx.clone())
|
||||||
|
} else {
|
||||||
|
Err("No response channel found for session".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BotOrchestrator {
|
impl Default for BotOrchestrator {
|
||||||
|
|
@ -831,10 +943,12 @@ async fn websocket_handler(
|
||||||
.add_connection(session_id.clone(), tx.clone())
|
.add_connection(session_id.clone(), tx.clone())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let bot_id = std::env::var("BOT_GUID").unwrap_or_else(|_| "default_bot".to_string());
|
||||||
|
|
||||||
orchestrator
|
orchestrator
|
||||||
.send_event(
|
.send_event(
|
||||||
"default_user",
|
"default_user",
|
||||||
"default_bot",
|
&bot_id,
|
||||||
&session_id,
|
&session_id,
|
||||||
"web",
|
"web",
|
||||||
"session_start",
|
"session_start",
|
||||||
|
|
@ -845,6 +959,19 @@ async fn websocket_handler(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
let orchestrator_clone = BotOrchestrator::new(Arc::clone(&data));
|
||||||
|
let session_id_clone = session_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)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to trigger auto welcome: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let web_adapter = data.web_adapter.clone();
|
let web_adapter = data.web_adapter.clone();
|
||||||
let session_id_clone1 = session_id.clone();
|
let session_id_clone1 = session_id.clone();
|
||||||
let session_id_clone2 = session_id.clone();
|
let session_id_clone2 = session_id.clone();
|
||||||
|
|
@ -883,8 +1010,11 @@ async fn websocket_handler(
|
||||||
message_count += 1;
|
message_count += 1;
|
||||||
debug!("Received WebSocket message {}: {}", message_count, text);
|
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 {
|
let user_message = UserMessage {
|
||||||
bot_id: "default_bot".to_string(),
|
bot_id: bot_id,
|
||||||
user_id: "default_user".to_string(),
|
user_id: "default_user".to_string(),
|
||||||
session_id: session_id_clone2.clone(),
|
session_id: session_id_clone2.clone(),
|
||||||
channel: "web".to_string(),
|
channel: "web".to_string(),
|
||||||
|
|
@ -903,10 +1033,14 @@ async fn websocket_handler(
|
||||||
}
|
}
|
||||||
WsMessage::Close(_) => {
|
WsMessage::Close(_) => {
|
||||||
info!("WebSocket close received for session {}", session_id_clone2);
|
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
|
orchestrator
|
||||||
.send_event(
|
.send_event(
|
||||||
"default_user",
|
"default_user",
|
||||||
"default_bot",
|
&bot_id,
|
||||||
&session_id_clone2,
|
&session_id_clone2,
|
||||||
"web",
|
"web",
|
||||||
"session_end",
|
"session_end",
|
||||||
|
|
@ -1067,17 +1201,112 @@ async fn voice_stop(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_web::post("/api/start")]
|
||||||
|
async fn start_session(
|
||||||
|
data: web::Data<AppState>,
|
||||||
|
info: web::Json<serde_json::Value>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let session_id = info
|
||||||
|
.get("session_id")
|
||||||
|
.and_then(|s| s.as_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
let token_id = info
|
||||||
|
.get("token_id")
|
||||||
|
.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(_) => {
|
||||||
|
warn!("Invalid session ID format: {}", session_id);
|
||||||
|
return Ok(
|
||||||
|
HttpResponse::BadRequest().json(serde_json::json!({"error": "Invalid session ID"}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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(None) => {
|
||||||
|
warn!("Session not found: {}", session_uuid);
|
||||||
|
return Ok(HttpResponse::NotFound()
|
||||||
|
.json(serde_json::json!({"error": "Session not found"})));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error retrieving session {}: {}", session_uuid, e);
|
||||||
|
return Ok(HttpResponse::InternalServerError()
|
||||||
|
.json(serde_json::json!({"error": "Failed to retrieve session"})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = BotOrchestrator::run_start_script(&session, Arc::clone(&data), token_id).await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(true) => {
|
||||||
|
info!(
|
||||||
|
"Start script completed successfully for session: {}",
|
||||||
|
session_id
|
||||||
|
);
|
||||||
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "started",
|
||||||
|
"session_id": session.id,
|
||||||
|
"result": "success"
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
warn!("Start script returned false for session: {}", session_id);
|
||||||
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "started",
|
||||||
|
"session_id": session.id,
|
||||||
|
"result": "failed"
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Error running start script for session {}: {}",
|
||||||
|
session_id, e
|
||||||
|
);
|
||||||
|
Ok(HttpResponse::InternalServerError()
|
||||||
|
.json(serde_json::json!({"error": e.to_string()})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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> {
|
||||||
info!("Creating new session");
|
info!("Creating new session");
|
||||||
|
|
||||||
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 = Uuid::parse_str("00000000-0000-0000-0000-000000000000").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()
|
||||||
|
})
|
||||||
|
} 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;
|
||||||
match session_manager.create_session(user_id, bot_id, "New Conversation") {
|
match session_manager.get_or_create_user_session(user_id, bot_id, "New Conversation") {
|
||||||
Ok(s) => s,
|
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) => {
|
Err(e) => {
|
||||||
error!("Failed to create session: {}", e);
|
error!("Failed to create session: {}", e);
|
||||||
return Ok(HttpResponse::InternalServerError()
|
return Ok(HttpResponse::InternalServerError()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::info;
|
use log::{debug, info};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
|
|
@ -32,6 +32,28 @@ impl WebChannelAdapter {
|
||||||
pub async fn remove_connection(&self, session_id: &str) {
|
pub async fn remove_connection(&self, session_id: &str) {
|
||||||
self.connections.lock().await.remove(session_id);
|
self.connections.lock().await.remove(session_id);
|
||||||
}
|
}
|
||||||
|
pub async fn send_message_to_session(
|
||||||
|
&self,
|
||||||
|
session_id: &str,
|
||||||
|
message: BotResponse,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let connections = self.connections.lock().await;
|
||||||
|
if let Some(tx) = connections.get(session_id) {
|
||||||
|
if let Err(e) = tx.send(message).await {
|
||||||
|
log::error!(
|
||||||
|
"Failed to send message to WebSocket session {}: {}",
|
||||||
|
session_id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
debug!("Message sent to WebSocket session: {}", session_id);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
debug!("No WebSocket connection found for session: {}", session_id);
|
||||||
|
Err("No WebSocket connection found".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@ mod tools;
|
||||||
mod whatsapp;
|
mod whatsapp;
|
||||||
|
|
||||||
use crate::bot::{
|
use crate::bot::{
|
||||||
create_session, get_session_history, get_sessions, index, set_mode_handler, static_files,
|
create_session, get_session_history, get_sessions, index, set_mode_handler, start_session,
|
||||||
voice_start, voice_stop, websocket_handler, whatsapp_webhook, whatsapp_webhook_verify,
|
static_files, voice_start, voice_stop, websocket_handler, whatsapp_webhook,
|
||||||
|
whatsapp_webhook_verify,
|
||||||
};
|
};
|
||||||
use crate::channels::{VoiceAdapter, WebChannelAdapter};
|
use crate::channels::{VoiceAdapter, WebChannelAdapter};
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
|
|
@ -188,6 +189,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(voice_stop)
|
.service(voice_stop)
|
||||||
.service(create_session)
|
.service(create_session)
|
||||||
.service(get_sessions)
|
.service(get_sessions)
|
||||||
|
.service(start_session)
|
||||||
.service(get_session_history)
|
.service(get_session_history)
|
||||||
.service(set_mode_handler)
|
.service(set_mode_handler)
|
||||||
.service(chat_completions_local)
|
.service(chat_completions_local)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use log::info;
|
use log::{debug, error, info, warn};
|
||||||
use redis::Client;
|
use redis::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -41,13 +41,16 @@ impl SessionManager {
|
||||||
&mut self,
|
&mut self,
|
||||||
session_id: Uuid,
|
session_id: Uuid,
|
||||||
input: String,
|
input: String,
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
|
||||||
info!(
|
info!(
|
||||||
"SessionManager.provide_input called for session {}",
|
"SessionManager.provide_input called for session {}",
|
||||||
session_id
|
session_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(sess) = self.sessions.get_mut(&session_id) {
|
if let Some(sess) = self.sessions.get_mut(&session_id) {
|
||||||
sess.data = input;
|
sess.data = input;
|
||||||
|
self.waiting_for_input.remove(&session_id);
|
||||||
|
Ok(Some("user_input".to_string()))
|
||||||
} else {
|
} else {
|
||||||
let sess = SessionData {
|
let sess = SessionData {
|
||||||
id: session_id,
|
id: session_id,
|
||||||
|
|
@ -55,9 +58,9 @@ impl SessionManager {
|
||||||
data: input,
|
data: input,
|
||||||
};
|
};
|
||||||
self.sessions.insert(session_id, sess);
|
self.sessions.insert(session_id, sess);
|
||||||
|
self.waiting_for_input.remove(&session_id);
|
||||||
|
Ok(Some("user_input".to_string()))
|
||||||
}
|
}
|
||||||
self.waiting_for_input.remove(&session_id);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_waiting_for_input(&self, session_id: &Uuid) -> bool {
|
pub fn is_waiting_for_input(&self, session_id: &Uuid) -> bool {
|
||||||
|
|
@ -69,6 +72,20 @@ impl SessionManager {
|
||||||
info!("Session {} marked as waiting for input", session_id);
|
info!("Session {} marked as waiting for input", session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_session_by_id(
|
||||||
|
&mut self,
|
||||||
|
session_id: Uuid,
|
||||||
|
) -> Result<Option<UserSession>, Box<dyn Error + Send + Sync>> {
|
||||||
|
use crate::shared::models::user_sessions::dsl::*;
|
||||||
|
|
||||||
|
let result = user_sessions
|
||||||
|
.filter(id.eq(session_id))
|
||||||
|
.first::<UserSession>(&mut self.conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_user_session(
|
pub fn get_user_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
uid: Uuid,
|
uid: Uuid,
|
||||||
|
|
@ -86,6 +103,21 @@ impl SessionManager {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_or_create_user_session(
|
||||||
|
&mut self,
|
||||||
|
uid: Uuid,
|
||||||
|
bid: Uuid,
|
||||||
|
session_title: &str,
|
||||||
|
) -> Result<Option<UserSession>, Box<dyn Error + Send + Sync>> {
|
||||||
|
if let Some(existing) = self.get_user_session(uid, bid)? {
|
||||||
|
debug!("Found existing session: {}", existing.id);
|
||||||
|
return Ok(Some(existing));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Creating new session for user {} with bot {}", uid, bid);
|
||||||
|
self.create_session(uid, bid, session_title).map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_session(
|
pub fn create_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
uid: Uuid,
|
uid: Uuid,
|
||||||
|
|
@ -93,21 +125,35 @@ impl SessionManager {
|
||||||
session_title: &str,
|
session_title: &str,
|
||||||
) -> Result<UserSession, Box<dyn Error + Send + Sync>> {
|
) -> Result<UserSession, Box<dyn Error + Send + Sync>> {
|
||||||
use crate::shared::models::user_sessions::dsl::*;
|
use crate::shared::models::user_sessions::dsl::*;
|
||||||
|
use crate::shared::models::users::dsl as users_dsl;
|
||||||
// Return an existing session if one already matches the user, bot, and title.
|
|
||||||
if let Some(existing) = user_sessions
|
|
||||||
.filter(user_id.eq(uid))
|
|
||||||
.filter(bot_id.eq(bid))
|
|
||||||
.filter(title.eq(session_title))
|
|
||||||
.first::<UserSession>(&mut self.conn)
|
|
||||||
.optional()?
|
|
||||||
{
|
|
||||||
return Ok(existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
||||||
// Insert the new session and retrieve the full record in one step.
|
let user_exists: Option<Uuid> = users_dsl::users
|
||||||
|
.filter(users_dsl::id.eq(uid))
|
||||||
|
.select(users_dsl::id)
|
||||||
|
.first(&mut self.conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
if user_exists.is_none() {
|
||||||
|
warn!(
|
||||||
|
"User {} does not exist in database, creating placeholder user",
|
||||||
|
uid
|
||||||
|
);
|
||||||
|
diesel::insert_into(users_dsl::users)
|
||||||
|
.values((
|
||||||
|
users_dsl::id.eq(uid),
|
||||||
|
users_dsl::username.eq(format!("anonymous_{}", rand::random::<u32>())),
|
||||||
|
users_dsl::email.eq(format!("anonymous_{}@local", rand::random::<u32>())),
|
||||||
|
users_dsl::password_hash.eq("placeholder"),
|
||||||
|
users_dsl::is_active.eq(true),
|
||||||
|
users_dsl::created_at.eq(now),
|
||||||
|
users_dsl::updated_at.eq(now),
|
||||||
|
))
|
||||||
|
.execute(&mut self.conn)?;
|
||||||
|
info!("Created placeholder user: {}", uid);
|
||||||
|
}
|
||||||
|
|
||||||
let inserted: UserSession = diesel::insert_into(user_sessions)
|
let inserted: UserSession = diesel::insert_into(user_sessions)
|
||||||
.values((
|
.values((
|
||||||
id.eq(Uuid::new_v4()),
|
id.eq(Uuid::new_v4()),
|
||||||
|
|
@ -121,8 +167,13 @@ impl SessionManager {
|
||||||
updated_at.eq(now),
|
updated_at.eq(now),
|
||||||
))
|
))
|
||||||
.returning(UserSession::as_returning())
|
.returning(UserSession::as_returning())
|
||||||
.get_result(&mut self.conn)?;
|
.get_result(&mut self.conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to create session in database: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
info!("New session created: {}", inserted.id);
|
||||||
Ok(inserted)
|
Ok(inserted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +190,8 @@ impl SessionManager {
|
||||||
let next_index = message_history
|
let next_index = message_history
|
||||||
.filter(session_id.eq(sess_id))
|
.filter(session_id.eq(sess_id))
|
||||||
.count()
|
.count()
|
||||||
.get_result::<i64>(&mut self.conn)?;
|
.get_result::<i64>(&mut self.conn)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
diesel::insert_into(message_history)
|
diesel::insert_into(message_history)
|
||||||
.values((
|
.values((
|
||||||
|
|
@ -154,23 +206,39 @@ impl SessionManager {
|
||||||
))
|
))
|
||||||
.execute(&mut self.conn)?;
|
.execute(&mut self.conn)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Message saved for session {} with index {}",
|
||||||
|
sess_id, next_index
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_conversation_history(
|
pub fn get_conversation_history(
|
||||||
&mut self,
|
&mut self,
|
||||||
_sess_id: Uuid,
|
sess_id: Uuid,
|
||||||
_uid: Uuid,
|
_uid: Uuid,
|
||||||
) -> Result<Vec<(String, String)>, Box<dyn Error + Send + Sync>> {
|
) -> Result<Vec<(String, String)>, Box<dyn Error + Send + Sync>> {
|
||||||
// use crate::shared::models::message_history::dsl::*;
|
use crate::shared::models::message_history::dsl::*;
|
||||||
|
|
||||||
// let messages = message_history
|
let messages = message_history
|
||||||
// .filter(session_id.eq(sess_id))
|
.filter(session_id.eq(sess_id))
|
||||||
// .order(message_index.asc())
|
.order(message_index.asc())
|
||||||
// .select((role, content_encrypted))
|
.select((role, content_encrypted))
|
||||||
// .load::<(String, String)>(&mut self.conn)?;
|
.load::<(i32, String)>(&mut self.conn)?;
|
||||||
|
|
||||||
Ok(vec![])
|
let history = messages
|
||||||
|
.into_iter()
|
||||||
|
.map(|(other_role, content)| {
|
||||||
|
let role_str = match other_role {
|
||||||
|
0 => "user".to_string(),
|
||||||
|
1 => "assistant".to_string(),
|
||||||
|
_ => "unknown".to_string(),
|
||||||
|
};
|
||||||
|
(role_str, content)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user_sessions(
|
pub fn get_user_sessions(
|
||||||
|
|
@ -195,10 +263,16 @@ impl SessionManager {
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
use crate::shared::models::user_sessions::dsl::*;
|
use crate::shared::models::user_sessions::dsl::*;
|
||||||
|
|
||||||
let user_uuid = Uuid::parse_str(uid)?;
|
let user_uuid = Uuid::parse_str(uid).map_err(|e| {
|
||||||
let bot_uuid = Uuid::parse_str(bid)?;
|
warn!("Invalid user ID format: {}", uid);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
let bot_uuid = Uuid::parse_str(bid).map_err(|e| {
|
||||||
|
warn!("Invalid bot ID format: {}", bid);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
diesel::update(
|
let updated_count = diesel::update(
|
||||||
user_sessions
|
user_sessions
|
||||||
.filter(user_id.eq(user_uuid))
|
.filter(user_id.eq(user_uuid))
|
||||||
.filter(bot_id.eq(bot_uuid)),
|
.filter(bot_id.eq(bot_uuid)),
|
||||||
|
|
@ -206,6 +280,35 @@ impl SessionManager {
|
||||||
.set((answer_mode.eq(mode), updated_at.eq(chrono::Utc::now())))
|
.set((answer_mode.eq(mode), updated_at.eq(chrono::Utc::now())))
|
||||||
.execute(&mut self.conn)?;
|
.execute(&mut self.conn)?;
|
||||||
|
|
||||||
|
if updated_count == 0 {
|
||||||
|
warn!("No session found for user {} and bot {}", uid, bid);
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"Answer mode updated to {} for user {} and bot {}",
|
||||||
|
mode, uid, bid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_user_id(
|
||||||
|
&mut self,
|
||||||
|
session_id: Uuid,
|
||||||
|
new_user_id: Uuid,
|
||||||
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
use crate::shared::models::user_sessions::dsl::*;
|
||||||
|
|
||||||
|
let updated_count = diesel::update(user_sessions.filter(id.eq(session_id)))
|
||||||
|
.set((user_id.eq(new_user_id), updated_at.eq(chrono::Utc::now())))
|
||||||
|
.execute(&mut self.conn)?;
|
||||||
|
|
||||||
|
if updated_count == 0 {
|
||||||
|
warn!("No session found with ID: {}", session_id);
|
||||||
|
} else {
|
||||||
|
info!("Updated session {} to user ID: {}", session_id, new_user_id);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
TALK "Welcome to General Bots!"
|
TALK "Welcome to General Bots!"
|
||||||
|
TALK "What is your name?"
|
||||||
HEAR name
|
HEAR name
|
||||||
TALK "Hello, " + name
|
TALK "Hello " + name + ", nice to meet you!"
|
||||||
|
SET_USER "92fcffaa-bf0a-41a9-8d99-5541709d695b"
|
||||||
text = GET "default.pdf"
|
|
||||||
SET CONTEXT text
|
|
||||||
|
|
||||||
resume = LLM "Build a resume from " + text
|
|
||||||
|
|
|
||||||
176
web/index.html
176
web/index.html
|
|
@ -8,6 +8,7 @@
|
||||||
<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://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.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>
|
||||||
<style>
|
<style>
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800&family=Inter:wght@400;600&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800&family=Inter:wght@400;600&display=swap");
|
||||||
|
|
||||||
|
|
@ -350,6 +351,123 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Markdown Styles */
|
||||||
|
.markdown-content {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content h1,
|
||||||
|
.markdown-content h2,
|
||||||
|
.markdown-content h3,
|
||||||
|
.markdown-content h4,
|
||||||
|
.markdown-content h5,
|
||||||
|
.markdown-content h6 {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
color: var(--dante-gold);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content h1 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
border-bottom: 2px solid var(--dante-gold);
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content h3 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content p {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content ul,
|
||||||
|
.markdown-content ol {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content li {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content code {
|
||||||
|
background: rgba(255, 215, 0, 0.1);
|
||||||
|
color: var(--dante-gold2);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content pre {
|
||||||
|
background: rgba(0, 10, 31, 0.8);
|
||||||
|
border: 1px solid rgba(255, 215, 0, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content blockquote {
|
||||||
|
border-left: 4px solid var(--dante-gold);
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
color: #ccc;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content th,
|
||||||
|
.markdown-content td {
|
||||||
|
border: 1px solid rgba(255, 215, 0, 0.3);
|
||||||
|
padding: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content th {
|
||||||
|
background: rgba(255, 215, 0, 0.1);
|
||||||
|
color: var(--dante-gold);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content a {
|
||||||
|
color: var(--dante-gold2);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted var(--dante-gold2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content a:hover {
|
||||||
|
border-bottom: 1px solid var(--dante-gold2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content strong {
|
||||||
|
color: var(--dante-gold2);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content em {
|
||||||
|
color: #ffed4e;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="relative overflow-hidden flex">
|
<body class="relative overflow-hidden flex">
|
||||||
|
|
@ -457,6 +575,16 @@
|
||||||
const sendBtn = document.getElementById("sendBtn");
|
const sendBtn = document.getElementById("sendBtn");
|
||||||
const newChatBtn = document.getElementById("newChatBtn");
|
const newChatBtn = document.getElementById("newChatBtn");
|
||||||
|
|
||||||
|
// Configure marked for markdown parsing
|
||||||
|
marked.setOptions({
|
||||||
|
highlight: function (code, lang) {
|
||||||
|
// Simple syntax highlighting - you could integrate highlight.js here
|
||||||
|
return `<pre><code class="language-${lang}">${code}</code></pre>`;
|
||||||
|
},
|
||||||
|
breaks: true,
|
||||||
|
gfm: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
createNewSession();
|
createNewSession();
|
||||||
|
|
||||||
|
|
@ -487,6 +615,13 @@
|
||||||
if (isVoiceMode) {
|
if (isVoiceMode) {
|
||||||
await startVoiceSession();
|
await startVoiceSession();
|
||||||
}
|
}
|
||||||
|
await fetch("/api/start", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
session_id: currentSessionId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchSession(sessionId) {
|
function switchSession(sessionId) {
|
||||||
|
|
@ -645,10 +780,16 @@
|
||||||
const msg = document.createElement("div");
|
const msg = document.createElement("div");
|
||||||
msg.className = "mb-8";
|
msg.className = "mb-8";
|
||||||
|
|
||||||
|
// Parse markdown for assistant messages
|
||||||
|
const processedContent =
|
||||||
|
role === "assistant" || role === "voice"
|
||||||
|
? marked.parse(content)
|
||||||
|
: content;
|
||||||
|
|
||||||
if (role === "user") {
|
if (role === "user") {
|
||||||
msg.innerHTML = `<div class="flex justify-end"><div class="glass neon-border rounded-2xl px-6 py-4 max-w-3xl text-lg text-yellow-100 font-semibold shadow-2xl">${content}</div></div>`;
|
msg.innerHTML = `<div class="flex justify-end"><div class="glass neon-border rounded-2xl px-6 py-4 max-w-3xl text-lg text-yellow-100 font-semibold shadow-2xl">${content}</div></div>`;
|
||||||
} else if (role === "assistant") {
|
} else if (role === "assistant") {
|
||||||
msg.innerHTML = `<div class="flex justify-start"><div class="flex gap-4 max-w-3xl"><div class="w-12 h-12 rounded-xl neon-border flex items-center justify-center flex-shrink-0 shine shadow-2xl"><span class="text-2xl neon-text font-extrabold">D</span></div><div class="glass border-2 border-yellow-400/30 rounded-2xl px-6 py-4 flex-1 text-blue-50 font-medium text-lg shadow-2xl" id="${streaming ? msgId : ""}">${streaming ? "" : content}</div></div></div>`;
|
msg.innerHTML = `<div class="flex justify-start"><div class="flex gap-4 max-w-3xl"><div class="w-12 h-12 rounded-xl neon-border flex items-center justify-center flex-shrink-0 shine shadow-2xl"><span class="text-2xl neon-text font-extrabold">D</span></div><div class="glass border-2 border-yellow-400/30 rounded-2xl px-6 py-4 flex-1 text-blue-50 font-medium text-lg shadow-2xl markdown-content" id="${streaming ? msgId : ""}">${streaming ? "" : processedContent}</div></div></div>`;
|
||||||
} else {
|
} else {
|
||||||
// Voice message
|
// Voice message
|
||||||
msg.innerHTML = `<div class="flex justify-start"><div class="flex gap-4 max-w-3xl"><div class="w-12 h-12 rounded-xl neon-border flex items-center justify-center flex-shrink-0 shine shadow-2xl"><span class="text-2xl neon-text font-extrabold">D</span></div><div class="glass border-2 border-green-400/30 rounded-2xl px-6 py-4 flex-1 text-green-100 font-medium text-lg shadow-2xl">${content}</div></div></div>`;
|
msg.innerHTML = `<div class="flex justify-start"><div class="flex gap-4 max-w-3xl"><div class="w-12 h-12 rounded-xl neon-border flex items-center justify-center flex-shrink-0 shine shadow-2xl"><span class="text-2xl neon-text font-extrabold">D</span></div><div class="glass border-2 border-green-400/30 rounded-2xl px-6 py-4 flex-1 text-green-100 font-medium text-lg shadow-2xl">${content}</div></div></div>`;
|
||||||
|
|
@ -667,7 +808,10 @@
|
||||||
function updateLastMessage(content) {
|
function updateLastMessage(content) {
|
||||||
const m = document.getElementById(streamingMessageId);
|
const m = document.getElementById(streamingMessageId);
|
||||||
if (m) {
|
if (m) {
|
||||||
m.textContent += content;
|
// Parse markdown incrementally during streaming
|
||||||
|
const currentContent = m.textContent || m.innerText;
|
||||||
|
const newContent = currentContent + content;
|
||||||
|
m.innerHTML = marked.parse(newContent);
|
||||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -896,6 +1040,34 @@
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Test markdown functionality
|
||||||
|
window.testMarkdown = function () {
|
||||||
|
const markdownContent = `# Título Principal
|
||||||
|
|
||||||
|
## Subtítulo
|
||||||
|
|
||||||
|
Este é um **texto em negrito** e este é um *texto em itálico*.
|
||||||
|
|
||||||
|
### Lista de Itens:
|
||||||
|
- Primeiro item
|
||||||
|
- Segundo item
|
||||||
|
- Terceiro item
|
||||||
|
|
||||||
|
### Código:
|
||||||
|
\`\`\`javascript
|
||||||
|
function exemplo() {
|
||||||
|
console.log("Olá, mundo!");
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
> Esta é uma citação importante sobre o assunto.
|
||||||
|
|
||||||
|
[Link para documentação](https://exemplo.com)`;
|
||||||
|
|
||||||
|
addMessage("assistant", markdownContent);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue