Refactor server code and add auth API fixes
This commit is contained in:
parent
a8f52451c5
commit
8f96cd1015
11 changed files with 891 additions and 1520 deletions
63
add-req.sh
Normal file → Executable file
63
add-req.sh
Normal file → Executable file
|
|
@ -3,7 +3,8 @@
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$SCRIPT_DIR"
|
PROJECT_ROOT="$SCRIPT_DIR"
|
||||||
OUTPUT_FILE="$SCRIPT_DIR/prompt.out"
|
OUTPUT_FILE="$SCRIPT_DIR/prompt.out"
|
||||||
rm $OUTPUT_FILE
|
|
||||||
|
rm -f "$OUTPUT_FILE"
|
||||||
echo "Consolidated LLM Context" > "$OUTPUT_FILE"
|
echo "Consolidated LLM Context" > "$OUTPUT_FILE"
|
||||||
|
|
||||||
prompts=(
|
prompts=(
|
||||||
|
|
@ -13,8 +14,10 @@ prompts=(
|
||||||
)
|
)
|
||||||
|
|
||||||
for file in "${prompts[@]}"; do
|
for file in "${prompts[@]}"; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
cat "$file" >> "$OUTPUT_FILE"
|
cat "$file" >> "$OUTPUT_FILE"
|
||||||
echo "" >> "$OUTPUT_FILE"
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
dirs=(
|
dirs=(
|
||||||
|
|
@ -22,8 +25,8 @@ dirs=(
|
||||||
#"automation"
|
#"automation"
|
||||||
#"basic"
|
#"basic"
|
||||||
"bot"
|
"bot"
|
||||||
#"channels"
|
"channels"
|
||||||
"config"
|
#"config"
|
||||||
#"context"
|
#"context"
|
||||||
#"email"
|
#"email"
|
||||||
#"file"
|
#"file"
|
||||||
|
|
@ -37,25 +40,55 @@ dirs=(
|
||||||
#"web_automation"
|
#"web_automation"
|
||||||
#"whatsapp"
|
#"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
|
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 -r file; do
|
||||||
echo $file >> "$OUTPUT_FILE"
|
echo "$file" >> "$OUTPUT_FILE"
|
||||||
cat "$file" >> "$OUTPUT_FILE"
|
filter_rust_file "$file" >> "$OUTPUT_FILE"
|
||||||
echo "" >> "$OUTPUT_FILE"
|
echo "" >> "$OUTPUT_FILE"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
# Also append the specific files you mentioned
|
# Additional specific files
|
||||||
echo "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
|
files=(
|
||||||
cat "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
|
"$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"
|
for file in "${files[@]}"; do
|
||||||
echo "$PROJECT_ROOT/src/basic/mod.rs">> "$OUTPUT_FILE"
|
if [[ "$file" == *.rs ]]; then
|
||||||
cat "$PROJECT_ROOT/src/basic/mod.rs" >> "$OUTPUT_FILE"
|
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"
|
# Remove all blank lines and reduce whitespace greater than 1 space
|
||||||
cat "$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/start.bas" >> "$OUTPUT_FILE"
|
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"
|
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)"
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,10 @@ cargo install cargo-edit
|
||||||
|
|
||||||
cargo upgrade
|
cargo upgrade
|
||||||
cargo audit
|
cargo audit
|
||||||
|
|
||||||
|
apt install xclip
|
||||||
|
|
||||||
|
# Prompt add-ons
|
||||||
|
|
||||||
|
- Prompt add-ons: Fill the file with info!, trace! and debug! macros.
|
||||||
|
-
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ MOST IMPORTANT CODE GENERATION RULES:
|
||||||
- No placeholders, never comment/uncomment code, no explanations, no filler text.
|
- No placeholders, never comment/uncomment code, no explanations, no filler text.
|
||||||
- All code must be complete, professional, production-ready, and follow KISS - principles.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Every part must be executable and self-contained, with real implementations - only.
|
||||||
- Only generated production ready enterprise grade VERY condensed no commented code.
|
- 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!
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ pub fn set_user_keyword(state: Arc<AppState>, user: UserSession, engine: &mut En
|
||||||
|
|
||||||
if let Err(e) = session_manager.update_user_id(user_clone_spawn.id, user_id)
|
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 {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
"Updated session {} to user ID: {}",
|
"Updated session {} to user ID: {}",
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,6 @@ impl ScriptService {
|
||||||
let trimmed = line.trim();
|
let trimmed = line.trim();
|
||||||
|
|
||||||
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("REM") {
|
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("REM") {
|
||||||
result.push_str(line);
|
|
||||||
result.push('\n');
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
600
src/bot/mod.rs
600
src/bot/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -78,7 +78,7 @@ impl AppConfig {
|
||||||
|
|
||||||
pub fn from_env() -> Self {
|
pub fn from_env() -> Self {
|
||||||
let database = DatabaseConfig {
|
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()),
|
password: env::var("TABLES_PASSWORD").unwrap_or_else(|_| "pass".to_string()),
|
||||||
server: env::var("TABLES_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
server: env::var("TABLES_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
||||||
port: env::var("TABLES_PORT")
|
port: env::var("TABLES_PORT")
|
||||||
|
|
|
||||||
30
src/main.rs
30
src/main.rs
|
|
@ -1,5 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::middleware::Logger;
|
use actix_web::middleware::Logger;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
|
|
@ -7,7 +6,6 @@ use dotenvy::dotenv;
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
mod automation;
|
mod automation;
|
||||||
mod basic;
|
mod basic;
|
||||||
|
|
@ -25,10 +23,9 @@ mod session;
|
||||||
mod shared;
|
mod shared;
|
||||||
mod tools;
|
mod tools;
|
||||||
mod whatsapp;
|
mod whatsapp;
|
||||||
|
|
||||||
use crate::bot::{
|
use crate::bot::{
|
||||||
create_session, get_session_history, get_sessions, index, set_mode_handler, start_session,
|
auth_handler, create_session, get_session_history, get_sessions, index, set_mode_handler,
|
||||||
static_files, voice_start, voice_stop, websocket_handler, whatsapp_webhook,
|
start_session, static_files, voice_start, voice_stop, websocket_handler,
|
||||||
whatsapp_webhook_verify,
|
whatsapp_webhook_verify,
|
||||||
};
|
};
|
||||||
use crate::channels::{VoiceAdapter, WebChannelAdapter};
|
use crate::channels::{VoiceAdapter, WebChannelAdapter};
|
||||||
|
|
@ -49,14 +46,12 @@ async fn main() -> std::io::Result<()> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
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 cfg = AppConfig::from_env();
|
||||||
let config = std::sync::Arc::new(cfg.clone());
|
let config = std::sync::Arc::new(cfg.clone());
|
||||||
|
|
||||||
let db_pool = match diesel::Connection::establish(&cfg.database_url()) {
|
let db_pool = match diesel::Connection::establish(&cfg.database_url()) {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
info!("Connected to main database");
|
info!("Connected to main database successfully");
|
||||||
Arc::new(Mutex::new(conn))
|
Arc::new(Mutex::new(conn))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -79,7 +74,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
let db_custom_pool = match diesel::Connection::establish(&custom_db_url) {
|
let db_custom_pool = match diesel::Connection::establish(&custom_db_url) {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
info!("Connected to custom database using constructed URL");
|
info!("Connected to custom database successfully");
|
||||||
Arc::new(Mutex::new(conn))
|
Arc::new(Mutex::new(conn))
|
||||||
}
|
}
|
||||||
Err(e2) => {
|
Err(e2) => {
|
||||||
|
|
@ -97,7 +92,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
let redis_client = match redis::Client::open("redis://127.0.0.1/") {
|
let redis_client = match redis::Client::open("redis://127.0.0.1/") {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
info!("Connected to Redis");
|
info!("Connected to Redis successfully");
|
||||||
Some(Arc::new(client))
|
Some(Arc::new(client))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -109,7 +104,6 @@ async fn main() -> std::io::Result<()> {
|
||||||
let tool_manager = Arc::new(tools::ToolManager::new());
|
let tool_manager = Arc::new(tools::ToolManager::new());
|
||||||
let llama_url =
|
let llama_url =
|
||||||
std::env::var("LLM_URL").unwrap_or_else(|_| "http://localhost:8081".to_string());
|
std::env::var("LLM_URL").unwrap_or_else(|_| "http://localhost:8081".to_string());
|
||||||
|
|
||||||
let llm_provider = Arc::new(crate::llm::OpenAIClient::new(
|
let llm_provider = Arc::new(crate::llm::OpenAIClient::new(
|
||||||
"empty".to_string(),
|
"empty".to_string(),
|
||||||
Some(llama_url.clone()),
|
Some(llama_url.clone()),
|
||||||
|
|
@ -121,13 +115,11 @@ async fn main() -> std::io::Result<()> {
|
||||||
"api_key".to_string(),
|
"api_key".to_string(),
|
||||||
"api_secret".to_string(),
|
"api_secret".to_string(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new(
|
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new(
|
||||||
"whatsapp_token".to_string(),
|
"whatsapp_token".to_string(),
|
||||||
"phone_number_id".to_string(),
|
"phone_number_id".to_string(),
|
||||||
"verify_token".to_string(),
|
"verify_token".to_string(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let tool_api = Arc::new(tools::ToolApi::new());
|
let tool_api = Arc::new(tools::ToolApi::new());
|
||||||
|
|
||||||
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::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(),
|
tool_manager: tool_manager.clone(),
|
||||||
llm_provider: llm_provider.clone(),
|
llm_provider: llm_provider.clone(),
|
||||||
auth_service: auth_service.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<dyn crate::channels::ChannelAdapter>,
|
||||||
|
);
|
||||||
|
map
|
||||||
|
})),
|
||||||
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
||||||
web_adapter: web_adapter.clone(),
|
web_adapter: web_adapter.clone(),
|
||||||
voice_adapter: voice_adapter.clone(),
|
voice_adapter: voice_adapter.clone(),
|
||||||
|
|
@ -171,7 +170,6 @@ async fn main() -> std::io::Result<()> {
|
||||||
.max_age(3600);
|
.max_age(3600);
|
||||||
|
|
||||||
let app_state_clone = app_state.clone();
|
let app_state_clone = app_state.clone();
|
||||||
|
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
.wrap(cors)
|
.wrap(cors)
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
|
|
@ -183,8 +181,8 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(index)
|
.service(index)
|
||||||
.service(static_files)
|
.service(static_files)
|
||||||
.service(websocket_handler)
|
.service(websocket_handler)
|
||||||
|
.service(auth_handler)
|
||||||
.service(whatsapp_webhook_verify)
|
.service(whatsapp_webhook_verify)
|
||||||
.service(whatsapp_webhook)
|
|
||||||
.service(voice_start)
|
.service(voice_start)
|
||||||
.service(voice_stop)
|
.service(voice_stop)
|
||||||
.service(create_session)
|
.service(create_session)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
TALK "Welcome to General Bots!"
|
TALK "Welcome to General Bots!"
|
||||||
TALK "What is your name?"
|
|
||||||
HEAR name
|
REM text = GET "default.pdf"
|
||||||
TALK "Hello " + name + ", nice to meet you!"
|
REM resume = LLM "Build a resume from " + text
|
||||||
SET_USER "92fcffaa-bf0a-41a9-8d99-5541709d695b"
|
REM TALK resume
|
||||||
|
|
||||||
|
REM SET_CONTEXT text
|
||||||
|
|
|
||||||
713
web/index.html
713
web/index.html
|
|
@ -11,468 +11,11 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.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");
|
||||||
|
|
||||||
:root {
|
|
||||||
--dante-blue: #000a1f;
|
|
||||||
--dante-blue2: #001a3d;
|
|
||||||
--dante-gold: #ffd700;
|
|
||||||
--dante-gold2: #ffed4e;
|
|
||||||
--dante-glow: rgba(255, 215, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "Inter", sans-serif;
|
|
||||||
background: radial-gradient(
|
|
||||||
circle at 20% 30%,
|
|
||||||
rgba(0, 48, 135, 0.4),
|
|
||||||
rgba(0, 26, 77, 0.7)
|
|
||||||
);
|
|
||||||
color: #fff;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.neon-text {
|
|
||||||
color: var(--dante-gold);
|
|
||||||
text-shadow:
|
|
||||||
0 0 15px var(--dante-glow),
|
|
||||||
0 0 30px var(--dante-glow),
|
|
||||||
0 0 60px var(--dante-glow),
|
|
||||||
0 0 90px rgba(255, 215, 0, 0.5);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.neon-border {
|
|
||||||
border: 3px solid var(--dante-gold);
|
|
||||||
box-shadow:
|
|
||||||
0 0 40px var(--dante-glow),
|
|
||||||
0 0 60px rgba(255, 215, 0, 0.6),
|
|
||||||
inset 0 0 30px rgba(255, 215, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass {
|
|
||||||
background: rgba(0, 20, 60, 0.4);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border: 1px solid rgba(253, 185, 19, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.background-animated {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: conic-gradient(
|
|
||||||
from 90deg,
|
|
||||||
#001a4d,
|
|
||||||
#003087,
|
|
||||||
#00509e,
|
|
||||||
#003087,
|
|
||||||
#001a4d
|
|
||||||
);
|
|
||||||
animation: rotate-bg 20s linear infinite;
|
|
||||||
opacity: 0.5;
|
|
||||||
z-index: 0;
|
|
||||||
filter: blur(120px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotate-bg {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shine::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
transparent,
|
|
||||||
rgba(253, 185, 19, 0.4),
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
animation: shine 3s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shine {
|
|
||||||
0% {
|
|
||||||
left: -100%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
left: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing-dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--dante-gold);
|
|
||||||
animation: typing-bounce 1.2s infinite;
|
|
||||||
box-shadow: 0 0 10px var(--dante-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing-dot:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing-dot:nth-child(3) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes typing-bounce {
|
|
||||||
0%,
|
|
||||||
60%,
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
30% {
|
|
||||||
transform: translateY(-8px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-status {
|
|
||||||
text-align: center;
|
|
||||||
margin: 10px 0;
|
|
||||||
color: #19c37d;
|
|
||||||
font-family: "Orbitron", monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pulse {
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-message {
|
|
||||||
color: #19c37d;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-item {
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #d1d5db;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-item:hover {
|
|
||||||
background: rgba(255, 215, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 260px;
|
|
||||||
background: rgba(0, 10, 31, 0.8);
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-right: 1px solid rgba(255, 215, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-toggle {
|
|
||||||
background: rgba(25, 195, 125, 0.2);
|
|
||||||
border: 1px solid rgba(25, 195, 125, 0.5);
|
|
||||||
color: #19c37d;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
font-family: "Orbitron", monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-toggle.recording {
|
|
||||||
background: rgba(239, 68, 68, 0.2);
|
|
||||||
border: 1px solid rgba(239, 68, 68, 0.5);
|
|
||||||
color: #ef4444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-chat {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid rgba(255, 215, 0, 0.3);
|
|
||||||
color: var(--dante-gold);
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
font-family: "Orbitron", monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-chat:hover,
|
|
||||||
.voice-toggle:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.history {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-message {
|
|
||||||
color: #d1d5db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assistant-message {
|
|
||||||
color: #ececf1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-area {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-area input {
|
|
||||||
width: 100%;
|
|
||||||
background: rgba(64, 65, 79, 0.5);
|
|
||||||
border: none;
|
|
||||||
padding: 12px 45px 12px 15px;
|
|
||||||
border-radius: 12px;
|
|
||||||
color: white;
|
|
||||||
font-size: 16px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-area button {
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
top: 5px;
|
|
||||||
background: rgba(25, 195, 125, 0.3);
|
|
||||||
border: 1px solid rgba(25, 195, 125, 0.5);
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #19c37d;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: "Orbitron", monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-indicator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: rgba(255, 215, 0, 0.1);
|
|
||||||
border: 1px solid rgba(255, 215, 0, 0.3);
|
|
||||||
border-radius: 12px;
|
|
||||||
margin: 10px auto;
|
|
||||||
max-width: 800px;
|
|
||||||
animation: neonPulse 2s infinite;
|
|
||||||
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes neonPulse {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
box-shadow:
|
|
||||||
0 0 5px rgba(255, 215, 0, 0.3),
|
|
||||||
0 0 10px rgba(255, 215, 0, 0.2);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow:
|
|
||||||
0 0 10px rgba(255, 215, 0, 0.5),
|
|
||||||
0 0 20px rgba(255, 215, 0, 0.3),
|
|
||||||
0 0 30px rgba(255, 215, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-message {
|
|
||||||
background: rgba(255, 69, 0, 0.2);
|
|
||||||
border: 1px solid rgba(255, 69, 0, 0.5);
|
|
||||||
color: #ff4500;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: 12px;
|
|
||||||
margin: 10px auto;
|
|
||||||
max-width: 800px;
|
|
||||||
text-align: center;
|
|
||||||
animation: flash 0.5s ease 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes flash {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
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>
|
||||||
|
<link rel="stylesheet" href="static/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="relative overflow-hidden flex">
|
<body class="relative overflow-hidden flex">
|
||||||
<div class="background-animated"></div>
|
<div class="background-animated"></div>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<button class="new-chat" onclick="createNewSession()">
|
<button class="new-chat" onclick="createNewSession()">
|
||||||
|
|
@ -487,7 +30,6 @@
|
||||||
</button>
|
</button>
|
||||||
<div class="history" id="history"></div>
|
<div class="history" id="history"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<header
|
<header
|
||||||
|
|
@ -503,7 +45,6 @@
|
||||||
General Bots
|
General Bots
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="newChatBtn"
|
id="newChatBtn"
|
||||||
class="px-8 py-3 rounded-xl glass neon-border text-yellow-300 font-semibold hover:scale-105 transition-all"
|
class="px-8 py-3 rounded-xl glass neon-border text-yellow-300 font-semibold hover:scale-105 transition-all"
|
||||||
|
|
@ -511,11 +52,9 @@
|
||||||
Novo Chat
|
Novo Chat
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="voice-status" id="voiceStatus" style="display: none">
|
<div class="voice-status" id="voiceStatus" style="display: none">
|
||||||
<div class="pulse">🎤 Ouvindo... Fale agora</div>
|
<div class="pulse">🎤 Ouvindo... Fale agora</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
id="messages"
|
id="messages"
|
||||||
class="relative z-10 overflow-y-auto h-[calc(100vh-170px)] px-10 py-8"
|
class="relative z-10 overflow-y-auto h-[calc(100vh-170px)] px-10 py-8"
|
||||||
|
|
@ -537,7 +76,6 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
class="glass border-t border-yellow-400/30 px-10 py-6 relative z-20 backdrop-blur-lg"
|
class="glass border-t border-yellow-400/30 px-10 py-6 relative z-20 backdrop-blur-lg"
|
||||||
>
|
>
|
||||||
|
|
@ -557,10 +95,10 @@
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let ws = null;
|
let ws = null;
|
||||||
let currentSessionId = null;
|
let currentSessionId = null;
|
||||||
|
let currentUserId = null;
|
||||||
let isStreaming = false;
|
let isStreaming = false;
|
||||||
let voiceRoom = null;
|
let voiceRoom = null;
|
||||||
let isVoiceMode = false;
|
let isVoiceMode = false;
|
||||||
|
|
@ -575,46 +113,66 @@
|
||||||
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({
|
marked.setOptions({
|
||||||
highlight: function (code, lang) {
|
highlight: function (code, lang) {
|
||||||
// Simple syntax highlighting - you could integrate highlight.js here
|
|
||||||
return `<pre><code class="language-${lang}">${code}</code></pre>`;
|
return `<pre><code class="language-${lang}">${code}</code></pre>`;
|
||||||
},
|
},
|
||||||
breaks: true,
|
breaks: true,
|
||||||
gfm: true,
|
gfm: true,
|
||||||
|
tables: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize
|
const renderer = new marked.Renderer();
|
||||||
createNewSession();
|
renderer.table = function (header, body) {
|
||||||
|
return `<table class="markdown-table"><thead>${header}</thead><tbody>${body}</tbody></table>`;
|
||||||
|
};
|
||||||
|
renderer.tablerow = function (content) {
|
||||||
|
const cleanedContent = content.replace(/<td><\/td>\s*$/, "");
|
||||||
|
return `<tr>${cleanedContent}</tr>`;
|
||||||
|
};
|
||||||
|
marked.setOptions({ renderer });
|
||||||
|
|
||||||
async function loadSessions() {
|
function cleanMarkdownTable(markdown) {
|
||||||
const response = await fetch("/api/sessions");
|
return markdown
|
||||||
const sessions = await response.json();
|
.replace(/\|\|+/g, "|")
|
||||||
const history = document.getElementById("history");
|
.replace(/\n\s*\|/g, "\n|")
|
||||||
history.innerHTML = "";
|
.replace(/(^|\n)\|(\s*\|)+\s*($|\n)/g, "$1$3")
|
||||||
|
.replace(
|
||||||
sessions.forEach((session) => {
|
/(\n\|[^\n]+\|)\n(\|[^\n]+\|)/g,
|
||||||
const div = document.createElement("div");
|
"$1\n| --- | --- | --- |\n$2",
|
||||||
div.className = "history-item";
|
);
|
||||||
div.textContent = session.title;
|
|
||||||
div.onclick = () => switchSession(session.id);
|
|
||||||
history.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNewSession() {
|
function parseMarkdown(content) {
|
||||||
const response = await fetch("/api/sessions", {
|
try {
|
||||||
method: "POST",
|
const cleanedContent = cleanMarkdownTable(content);
|
||||||
});
|
return marked.parse(cleanedContent);
|
||||||
const session = await response.json();
|
} catch (error) {
|
||||||
currentSessionId = session.session_id;
|
console.error("Markdown parsing error:", error);
|
||||||
|
return content
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
||||||
|
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
||||||
|
.replace(/\n/g, "<br>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeAuth() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth");
|
||||||
|
const authData = await response.json();
|
||||||
|
currentUserId = authData.user_id;
|
||||||
|
currentSessionId = authData.session_id;
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
loadSessions();
|
loadSessions();
|
||||||
document.getElementById("messages").innerHTML = "";
|
await triggerStartScript();
|
||||||
if (isVoiceMode) {
|
} catch (error) {
|
||||||
await startVoiceSession();
|
console.error("Failed to initialize auth:", error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerStartScript() {
|
||||||
|
if (!currentSessionId) return;
|
||||||
|
try {
|
||||||
await fetch("/api/start", {
|
await fetch("/api/start", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -622,6 +180,54 @@
|
||||||
session_id: currentSessionId,
|
session_id: currentSessionId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to trigger start script:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSessions() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/sessions");
|
||||||
|
const sessions = await response.json();
|
||||||
|
const history = document.getElementById("history");
|
||||||
|
history.innerHTML = "";
|
||||||
|
sessions.forEach((session) => {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "history-item";
|
||||||
|
div.textContent = session.title;
|
||||||
|
div.onclick = () => switchSession(session.id);
|
||||||
|
history.appendChild(div);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load sessions:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createNewSession() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/sessions", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
const session = await response.json();
|
||||||
|
currentSessionId = session.session_id;
|
||||||
|
connectWebSocket();
|
||||||
|
loadSessions();
|
||||||
|
document.getElementById("messages").innerHTML = `
|
||||||
|
<div id="emptyState" class="text-center pt-40 flex flex-col items-center justify-center">
|
||||||
|
<div class="w-36 h-36 rounded-3xl neon-border flex items-center justify-center shine mb-6">
|
||||||
|
<span class="text-7xl neon-text font-extrabold">D</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-4xl neon-text font-bold">Bem-vindo ao General Bots</h2>
|
||||||
|
<p class="text-blue-200 mt-3 opacity-80 text-lg">Seu assistente de IA avançado</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
if (isVoiceMode) {
|
||||||
|
await startVoiceSession();
|
||||||
|
}
|
||||||
|
await triggerStartScript();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create session:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchSession(sessionId) {
|
function switchSession(sessionId) {
|
||||||
|
|
@ -634,11 +240,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSessionHistory(sessionId) {
|
async function loadSessionHistory(sessionId) {
|
||||||
|
try {
|
||||||
const response = await fetch("/api/sessions/" + sessionId);
|
const response = await fetch("/api/sessions/" + sessionId);
|
||||||
const history = await response.json();
|
const history = await response.json();
|
||||||
const messages = document.getElementById("messages");
|
const messages = document.getElementById("messages");
|
||||||
messages.innerHTML = "";
|
messages.innerHTML = "";
|
||||||
|
|
||||||
history.forEach(([role, content]) => {
|
history.forEach(([role, content]) => {
|
||||||
const className =
|
const className =
|
||||||
role === "user"
|
role === "user"
|
||||||
|
|
@ -652,23 +258,29 @@
|
||||||
className,
|
className,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load session history:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
if (ws) ws.close();
|
if (ws) {
|
||||||
ws = new WebSocket("ws://" + window.location.host + "/ws");
|
ws.close();
|
||||||
|
}
|
||||||
|
const wsUrl =
|
||||||
|
"ws://" +
|
||||||
|
window.location.host +
|
||||||
|
"/ws?session_id=" + currentSessionId + "&user_id=" + currentUserId;
|
||||||
|
ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
ws.onmessage = function (event) {
|
ws.onmessage = function (event) {
|
||||||
const response = JSON.parse(event.data);
|
const response = JSON.parse(event.data);
|
||||||
|
|
||||||
// Handle event messages (thinking_start, thinking_end, warn)
|
|
||||||
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);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle regular messages
|
|
||||||
if (!response.is_complete) {
|
if (!response.is_complete) {
|
||||||
if (!isStreaming) {
|
if (!isStreaming) {
|
||||||
isStreaming = true;
|
isStreaming = true;
|
||||||
|
|
@ -689,28 +301,37 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onopen = function () {
|
ws.onopen = function () {
|
||||||
console.log("Connected to WebSocket");
|
console.log(
|
||||||
|
"Connected to WebSocket with session:",
|
||||||
|
currentSessionId,
|
||||||
|
"user:",
|
||||||
|
currentUserId
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function () {
|
||||||
|
console.log("WebSocket disconnected");
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function (error) {
|
||||||
|
console.error("WebSocket error:", error);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEvent(eventType, eventData) {
|
function handleEvent(eventType, eventData) {
|
||||||
console.log("Event received:", eventType, eventData);
|
console.log("Event received:", eventType, eventData);
|
||||||
|
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case "thinking_start":
|
case "thinking_start":
|
||||||
showThinkingIndicator();
|
showThinkingIndicator();
|
||||||
isThinking = true;
|
isThinking = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "thinking_end":
|
case "thinking_end":
|
||||||
hideThinkingIndicator();
|
hideThinkingIndicator();
|
||||||
isThinking = false;
|
isThinking = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "warn":
|
case "warn":
|
||||||
showWarning(eventData.message);
|
showWarning(eventData.message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log("Unknown event type:", eventType);
|
console.log("Unknown event type:", eventType);
|
||||||
}
|
}
|
||||||
|
|
@ -718,10 +339,8 @@
|
||||||
|
|
||||||
function showThinkingIndicator() {
|
function showThinkingIndicator() {
|
||||||
if (isThinking) return;
|
if (isThinking) return;
|
||||||
|
|
||||||
const emptyState = document.getElementById("emptyState");
|
const emptyState = document.getElementById("emptyState");
|
||||||
if (emptyState) emptyState.remove();
|
if (emptyState) emptyState.remove();
|
||||||
|
|
||||||
const thinkingDiv = document.createElement("div");
|
const thinkingDiv = document.createElement("div");
|
||||||
thinkingDiv.id = "thinking-indicator";
|
thinkingDiv.id = "thinking-indicator";
|
||||||
thinkingDiv.className = "thinking-indicator";
|
thinkingDiv.className = "thinking-indicator";
|
||||||
|
|
@ -733,7 +352,6 @@
|
||||||
</div>
|
</div>
|
||||||
<span class="text-yellow-300 font-semibold">Pensando...</span>
|
<span class="text-yellow-300 font-semibold">Pensando...</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
messagesDiv.appendChild(thinkingDiv);
|
messagesDiv.appendChild(thinkingDiv);
|
||||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||||
isThinking = true;
|
isThinking = true;
|
||||||
|
|
@ -742,9 +360,7 @@
|
||||||
|
|
||||||
function hideThinkingIndicator() {
|
function hideThinkingIndicator() {
|
||||||
if (!isThinking) return;
|
if (!isThinking) return;
|
||||||
|
const thinkingDiv = document.getElementById("thinking-indicator");
|
||||||
const thinkingDiv =
|
|
||||||
document.getElementById("thinking-indicator");
|
|
||||||
if (thinkingDiv) {
|
if (thinkingDiv) {
|
||||||
thinkingDiv.remove();
|
thinkingDiv.remove();
|
||||||
}
|
}
|
||||||
|
|
@ -756,11 +372,8 @@
|
||||||
const warningDiv = document.createElement("div");
|
const warningDiv = document.createElement("div");
|
||||||
warningDiv.className = "warning-message";
|
warningDiv.className = "warning-message";
|
||||||
warningDiv.innerHTML = `⚠️ ${message}`;
|
warningDiv.innerHTML = `⚠️ ${message}`;
|
||||||
|
|
||||||
messagesDiv.appendChild(warningDiv);
|
messagesDiv.appendChild(warningDiv);
|
||||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||||
|
|
||||||
// Remove warning after 5 seconds
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (warningDiv.parentNode) {
|
if (warningDiv.parentNode) {
|
||||||
warningDiv.remove();
|
warningDiv.remove();
|
||||||
|
|
@ -776,14 +389,11 @@
|
||||||
) {
|
) {
|
||||||
const emptyState = document.getElementById("emptyState");
|
const emptyState = document.getElementById("emptyState");
|
||||||
if (emptyState) emptyState.remove();
|
if (emptyState) emptyState.remove();
|
||||||
|
|
||||||
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 =
|
const processedContent =
|
||||||
role === "assistant" || role === "voice"
|
role === "assistant" || role === "voice"
|
||||||
? marked.parse(content)
|
? parseMarkdown(content)
|
||||||
: content;
|
: content;
|
||||||
|
|
||||||
if (role === "user") {
|
if (role === "user") {
|
||||||
|
|
@ -791,10 +401,8 @@
|
||||||
} 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 markdown-content" id="${streaming ? msgId : ""}">${streaming ? "" : processedContent}</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
|
|
||||||
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>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
messagesDiv.appendChild(msg);
|
messagesDiv.appendChild(msg);
|
||||||
gsap.from(msg, {
|
gsap.from(msg, {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
|
@ -808,10 +416,9 @@
|
||||||
function updateLastMessage(content) {
|
function updateLastMessage(content) {
|
||||||
const m = document.getElementById(streamingMessageId);
|
const m = document.getElementById(streamingMessageId);
|
||||||
if (m) {
|
if (m) {
|
||||||
// Parse markdown incrementally during streaming
|
|
||||||
const currentContent = m.textContent || m.innerText;
|
const currentContent = m.textContent || m.innerText;
|
||||||
const newContent = currentContent + content;
|
const newContent = currentContent + content;
|
||||||
m.innerHTML = marked.parse(newContent);
|
m.innerHTML = parseMarkdown(newContent);
|
||||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -820,14 +427,24 @@
|
||||||
const message = input.value.trim();
|
const message = input.value.trim();
|
||||||
if (!message || !ws || ws.readyState !== WebSocket.OPEN) return;
|
if (!message || !ws || ws.readyState !== WebSocket.OPEN) return;
|
||||||
|
|
||||||
// Hide thinking indicator if it's showing
|
|
||||||
if (isThinking) {
|
if (isThinking) {
|
||||||
hideThinkingIndicator();
|
hideThinkingIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage("user", message);
|
addMessage("user", message);
|
||||||
ws.send(message);
|
const messageData = {
|
||||||
|
bot_id: "default_bot",
|
||||||
|
user_id: currentUserId,
|
||||||
|
session_id: currentSessionId,
|
||||||
|
channel: "web",
|
||||||
|
content: message,
|
||||||
|
message_type: 1,
|
||||||
|
media_url: null,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
ws.send(JSON.stringify(messageData));
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
|
||||||
anime({
|
anime({
|
||||||
targets: sendBtn,
|
targets: sendBtn,
|
||||||
scale: [1, 0.85, 1],
|
scale: [1, 0.85, 1],
|
||||||
|
|
@ -847,7 +464,6 @@
|
||||||
isVoiceMode = !isVoiceMode;
|
isVoiceMode = !isVoiceMode;
|
||||||
const voiceToggle = document.getElementById("voiceToggle");
|
const voiceToggle = document.getElementById("voiceToggle");
|
||||||
const voiceStatus = document.getElementById("voiceStatus");
|
const voiceStatus = document.getElementById("voiceStatus");
|
||||||
|
|
||||||
if (isVoiceMode) {
|
if (isVoiceMode) {
|
||||||
voiceToggle.textContent = "🔴 Parar Voz";
|
voiceToggle.textContent = "🔴 Parar Voz";
|
||||||
voiceToggle.classList.add("recording");
|
voiceToggle.classList.add("recording");
|
||||||
|
|
@ -863,17 +479,15 @@
|
||||||
|
|
||||||
async function startVoiceSession() {
|
async function startVoiceSession() {
|
||||||
if (!currentSessionId) return;
|
if (!currentSessionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/voice/start", {
|
const response = await fetch("/api/voice/start", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
session_id: currentSessionId,
|
session_id: currentSessionId,
|
||||||
user_id: "user_" + currentSessionId,
|
user_id: currentUserId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
await connectToVoiceRoom(data.token);
|
await connectToVoiceRoom(data.token);
|
||||||
|
|
@ -886,7 +500,6 @@
|
||||||
|
|
||||||
async function stopVoiceSession() {
|
async function stopVoiceSession() {
|
||||||
if (!currentSessionId) return;
|
if (!currentSessionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch("/api/voice/stop", {
|
await fetch("/api/voice/stop", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -895,12 +508,10 @@
|
||||||
session_id: currentSessionId,
|
session_id: currentSessionId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (voiceRoom) {
|
if (voiceRoom) {
|
||||||
voiceRoom.disconnect();
|
voiceRoom.disconnect();
|
||||||
voiceRoom = null;
|
voiceRoom = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaRecorder && mediaRecorder.state === "recording") {
|
if (mediaRecorder && mediaRecorder.state === "recording") {
|
||||||
mediaRecorder.stop();
|
mediaRecorder.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -932,7 +543,6 @@
|
||||||
audio: true,
|
audio: true,
|
||||||
video: false,
|
video: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const track of localTracks) {
|
for (const track of localTracks) {
|
||||||
await room.localParticipant.publishTrack(track);
|
await room.localParticipant.publishTrack(track);
|
||||||
}
|
}
|
||||||
|
|
@ -965,7 +575,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
mediaRecorder.start();
|
mediaRecorder.start();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (
|
if (
|
||||||
mediaRecorder &&
|
mediaRecorder &&
|
||||||
|
|
@ -996,9 +605,7 @@
|
||||||
"Isso parece ótimo",
|
"Isso parece ótimo",
|
||||||
"Estou ouvindo sua voz",
|
"Estou ouvindo sua voz",
|
||||||
];
|
];
|
||||||
|
const randomPhrase = phrases[Math.floor(Math.random() * phrases.length)];
|
||||||
const randomPhrase =
|
|
||||||
phrases[Math.floor(Math.random() * phrases.length)];
|
|
||||||
|
|
||||||
if (voiceRoom) {
|
if (voiceRoom) {
|
||||||
const message = {
|
const message = {
|
||||||
|
|
@ -1006,17 +613,14 @@
|
||||||
content: randomPhrase,
|
content: randomPhrase,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
voiceRoom.localParticipant.publishData(
|
voiceRoom.localParticipant.publishData(
|
||||||
new TextEncoder().encode(JSON.stringify(message)),
|
new TextEncoder().encode(JSON.stringify(message)),
|
||||||
LiveKitClient.DataPacketKind.RELIABLE,
|
LiveKitClient.DataPacketKind.RELIABLE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage("voice", `🎤 ${randomPhrase}`);
|
addMessage("voice", `🎤 ${randomPhrase}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neon text animation
|
|
||||||
gsap.to(".neon-text", {
|
gsap.to(".neon-text", {
|
||||||
textShadow:
|
textShadow:
|
||||||
"0 0 25px var(--dante-glow),0 0 50px var(--dante-glow),0 0 100px rgba(255,215,0,0.8)",
|
"0 0 25px var(--dante-glow),0 0 50px var(--dante-glow),0 0 100px rgba(255,215,0,0.8)",
|
||||||
|
|
@ -1026,7 +630,6 @@
|
||||||
ease: "power1.inOut",
|
ease: "power1.inOut",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test warning functionality
|
|
||||||
window.testWarning = function () {
|
window.testWarning = function () {
|
||||||
fetch("/api/warn", {
|
fetch("/api/warn", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -1041,33 +644,23 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test markdown functionality
|
|
||||||
window.testMarkdown = function () {
|
window.testMarkdown = function () {
|
||||||
const markdownContent = `# Título Principal
|
const markdownContent = `Tabela de Frutas
|
||||||
|
| Nome da fruta | Cor predominante | Estação em que costuma estar em flor/colheita |
|
||||||
## Subtítulo
|
|---------------|------------------|-----------------------------------------------|
|
||||||
|
| Maçã | Verde, vermelho | Outono (início do verão em países de clima temperado) |
|
||||||
Este é um **texto em negrito** e este é um *texto em itálico*.
|
| Banana | Amarelo | Todo ano (principalmente nas regiões tropicais) |
|
||||||
|
| Laranja | Laranja | Inverno (pico de colheita em países de clima temperado) |
|
||||||
### Lista de Itens:
|
**Nota**: As informações sobre a estação de colheita são gerais e podem variar de acordo com a região e variedade da fruta.`;
|
||||||
- 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);
|
addMessage("assistant", markdownContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.testProblematicTable = function () {
|
||||||
|
const problematicContent = `Tabela de Frutas**| Nome da fruta | Cor predominante | Estação em que costuma estar em flor/colheita |||||| Maçã | Verde, vermelho | Outono (início do verão em países de clima temperado) || Banana | Amarelo | Todo ano (principalmente nas regiões tropicais) || Laranja | Laranja | Inverno (pico de colheita em países de clima temperado) | Nota: As informações sobre a estação de colheita são gerais e podem variar de acordo com a região e variedade da fruta`;
|
||||||
|
addMessage("assistant", problematicContent);
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeAuth();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue