diff --git a/scripts/database/6.0.sql b/scripts/database/6.0.sql index bc48f79b..bb05719a 100644 --- a/scripts/database/6.0.sql +++ b/scripts/database/6.0.sql @@ -1,158 +1,246 @@ --- Optimized database schema --- Add organizations table -CREATE TABLE IF NOT EXISTS organizations ( - org_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) NOT NULL, - slug VARCHAR(255) UNIQUE NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +-- public.bots definition + +-- Drop table + +-- DROP TABLE public.bots; + +CREATE TABLE public.bots ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + "name" varchar(255) NOT NULL, + description text NULL, + llm_provider varchar(100) NOT NULL, + llm_config jsonb DEFAULT '{}'::jsonb NOT NULL, + context_provider varchar(100) NOT NULL, + context_config jsonb DEFAULT '{}'::jsonb NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL, + updated_at timestamptz DEFAULT now() NOT NULL, + is_active bool DEFAULT true NULL, + CONSTRAINT bots_pkey PRIMARY KEY (id) ); --- Create indexes for better performance -CREATE INDEX IF NOT EXISTS idx_organizations_slug ON organizations(slug); -CREATE INDEX IF NOT EXISTS idx_organizations_created_at ON organizations(created_at); --- Core tables -CREATE TABLE IF NOT EXISTS users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - username VARCHAR(255) UNIQUE NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - phone_number VARCHAR(50), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - is_active BOOLEAN DEFAULT true +-- public.clicks definition + +-- Drop table + +-- DROP TABLE public.clicks; + +CREATE TABLE public.clicks ( + campaign_id text NOT NULL, + email text NOT NULL, + updated_at timestamptz DEFAULT now() NULL, + CONSTRAINT clicks_campaign_id_email_key UNIQUE (campaign_id, email) ); -CREATE TABLE IF NOT EXISTS bots ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) NOT NULL, - description TEXT, - llm_provider VARCHAR(100) NOT NULL, - llm_config JSONB NOT NULL DEFAULT '{}', - context_provider VARCHAR(100) NOT NULL, - context_config JSONB NOT NULL DEFAULT '{}', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - is_active BOOLEAN DEFAULT true + +-- public.organizations definition + +-- Drop table + +-- DROP TABLE public.organizations; + +CREATE TABLE public.organizations ( + org_id uuid DEFAULT gen_random_uuid() NOT NULL, + "name" varchar(255) NOT NULL, + slug varchar(255) NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL, + updated_at timestamptz DEFAULT now() NOT NULL, + CONSTRAINT organizations_pkey PRIMARY KEY (org_id), + CONSTRAINT organizations_slug_key UNIQUE (slug) +); +CREATE INDEX idx_organizations_created_at ON public.organizations USING btree (created_at); +CREATE INDEX idx_organizations_slug ON public.organizations USING btree (slug); + + +-- public.system_automations definition + +-- Drop table + +-- DROP TABLE public.system_automations; + +CREATE TABLE public.system_automations ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + kind int4 NOT NULL, + "target" varchar(32) NULL, + schedule bpchar(12) NULL, + param varchar(32) NOT NULL, + is_active bool DEFAULT true NOT NULL, + last_triggered timestamptz NULL, + created_at timestamptz DEFAULT now() NOT NULL, + CONSTRAINT system_automations_pkey PRIMARY KEY (id) +); +CREATE INDEX idx_system_automations_active ON public.system_automations USING btree (kind) WHERE is_active; + + +-- public.tools definition + +-- Drop table + +-- DROP TABLE public.tools; + +CREATE TABLE public.tools ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + "name" varchar(255) NOT NULL, + description text NOT NULL, + parameters jsonb DEFAULT '{}'::jsonb NOT NULL, + script text NOT NULL, + is_active bool DEFAULT true NULL, + created_at timestamptz DEFAULT now() NOT NULL, + CONSTRAINT tools_name_key UNIQUE (name), + CONSTRAINT tools_pkey PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS user_sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, - title VARCHAR(500) NOT NULL DEFAULT 'New Conversation', - answer_mode INTEGER NOT NULL DEFAULT 0, -- 0=direct, 1=tool - context_data JSONB NOT NULL DEFAULT '{}', - current_tool VARCHAR(255), - message_count INTEGER NOT NULL DEFAULT 0, - total_tokens INTEGER NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(user_id, bot_id, title) + +-- public.users definition + +-- Drop table + +-- DROP TABLE public.users; + +CREATE TABLE public.users ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + username varchar(255) NOT NULL, + email varchar(255) NOT NULL, + password_hash varchar(255) NOT NULL, + phone_number varchar(50) NULL, + created_at timestamptz DEFAULT now() NOT NULL, + updated_at timestamptz DEFAULT now() NOT NULL, + is_active bool DEFAULT true NULL, + CONSTRAINT users_email_key UNIQUE (email), + CONSTRAINT users_pkey PRIMARY KEY (id), + CONSTRAINT users_username_key UNIQUE (username) ); -CREATE TABLE IF NOT EXISTS message_history ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID NOT NULL REFERENCES user_sessions(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - role INTEGER NOT NULL, -- 0=user, 1=assistant, 2=system - content_encrypted TEXT NOT NULL, - message_type INTEGER NOT NULL DEFAULT 0, -- 0=text, 1=image, 2=audio, 3=file - media_url TEXT, - token_count INTEGER NOT NULL DEFAULT 0, - processing_time_ms INTEGER, - llm_model VARCHAR(100), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - message_index INTEGER NOT NULL + +-- public.bot_channels definition + +-- Drop table + +-- DROP TABLE public.bot_channels; + +CREATE TABLE public.bot_channels ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + bot_id uuid NOT NULL, + channel_type int4 NOT NULL, + config jsonb DEFAULT '{}'::jsonb NOT NULL, + is_active bool DEFAULT true NULL, + created_at timestamptz DEFAULT now() NOT NULL, + CONSTRAINT bot_channels_bot_id_channel_type_key UNIQUE (bot_id, channel_type), + CONSTRAINT bot_channels_pkey PRIMARY KEY (id), + CONSTRAINT bot_channels_bot_id_fkey FOREIGN KEY (bot_id) REFERENCES public.bots(id) ON DELETE CASCADE +); +CREATE INDEX idx_bot_channels_type ON public.bot_channels USING btree (channel_type) WHERE is_active; + + +-- public.user_sessions definition + +-- Drop table + +-- DROP TABLE public.user_sessions; + +CREATE TABLE public.user_sessions ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + bot_id uuid NOT NULL, + title varchar(500) DEFAULT 'New Conversation'::character varying NOT NULL, + answer_mode int4 DEFAULT 0 NOT NULL, + context_data jsonb DEFAULT '{}'::jsonb NOT NULL, + current_tool varchar(255) NULL, + message_count int4 DEFAULT 0 NOT NULL, + total_tokens int4 DEFAULT 0 NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL, + updated_at timestamptz DEFAULT now() NOT NULL, + last_activity timestamptz DEFAULT now() NOT NULL, + CONSTRAINT user_sessions_pkey PRIMARY KEY (id), + CONSTRAINT user_sessions_bot_id_fkey FOREIGN KEY (bot_id) REFERENCES public.bots(id) ON DELETE CASCADE, + CONSTRAINT user_sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE +); +CREATE INDEX idx_user_sessions_updated_at ON public.user_sessions USING btree (updated_at); +CREATE INDEX idx_user_sessions_user_bot ON public.user_sessions USING btree (user_id, bot_id); + + +-- public.whatsapp_numbers definition + +-- Drop table + +-- DROP TABLE public.whatsapp_numbers; + +CREATE TABLE public.whatsapp_numbers ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + bot_id uuid NOT NULL, + phone_number varchar(50) NOT NULL, + is_active bool DEFAULT true NULL, + created_at timestamptz DEFAULT now() NOT NULL, + CONSTRAINT whatsapp_numbers_phone_number_bot_id_key UNIQUE (phone_number, bot_id), + CONSTRAINT whatsapp_numbers_pkey PRIMARY KEY (id), + CONSTRAINT whatsapp_numbers_bot_id_fkey FOREIGN KEY (bot_id) REFERENCES public.bots(id) ON DELETE CASCADE ); --- Channel and integration tables -CREATE TABLE IF NOT EXISTS bot_channels ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, - channel_type INTEGER NOT NULL, -- 0=web, 1=whatsapp, 2=voice, 3=api - config JSONB NOT NULL DEFAULT '{}', - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(bot_id, channel_type) + +-- public.context_injections definition + +-- Drop table + +-- DROP TABLE public.context_injections; + +CREATE TABLE public.context_injections ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + session_id uuid NOT NULL, + injected_by uuid NOT NULL, + context_data jsonb NOT NULL, + reason text NULL, + created_at timestamptz DEFAULT now() NOT NULL, + CONSTRAINT context_injections_pkey PRIMARY KEY (id), + CONSTRAINT context_injections_injected_by_fkey FOREIGN KEY (injected_by) REFERENCES public.users(id) ON DELETE CASCADE, + CONSTRAINT context_injections_session_id_fkey FOREIGN KEY (session_id) REFERENCES public.user_sessions(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS whatsapp_numbers ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, - phone_number VARCHAR(50) NOT NULL, - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(phone_number, bot_id) + +-- public.message_history definition + +-- Drop table + +-- DROP TABLE public.message_history; + +CREATE TABLE public.message_history ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + session_id uuid NOT NULL, + user_id uuid NOT NULL, + "role" int4 NOT NULL, + content_encrypted text NOT NULL, + message_type int4 DEFAULT 0 NOT NULL, + media_url text NULL, + token_count int4 DEFAULT 0 NOT NULL, + processing_time_ms int4 NULL, + llm_model varchar(100) NULL, + created_at timestamptz DEFAULT now() NOT NULL, + message_index int4 NOT NULL, + CONSTRAINT message_history_pkey PRIMARY KEY (id), + CONSTRAINT message_history_session_id_fkey FOREIGN KEY (session_id) REFERENCES public.user_sessions(id) ON DELETE CASCADE, + CONSTRAINT message_history_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE ); +CREATE INDEX idx_message_history_created_at ON public.message_history USING btree (created_at); +CREATE INDEX idx_message_history_session_id ON public.message_history USING btree (session_id); --- Automation tables -CREATE TABLE IF NOT EXISTS system_automations ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - kind INTEGER NOT NULL, -- 0=scheduled, 1=table_update, 2=table_insert, 3=table_delete - target VARCHAR(32), - schedule CHAR(12), - param VARCHAR(32) NOT NULL, - is_active BOOLEAN DEFAULT true NOT NULL, - last_triggered TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + +-- public.usage_analytics definition + +-- Drop table + +-- DROP TABLE public.usage_analytics; + +CREATE TABLE public.usage_analytics ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + bot_id uuid NOT NULL, + session_id uuid NOT NULL, + "date" date DEFAULT CURRENT_DATE NOT NULL, + message_count int4 DEFAULT 0 NOT NULL, + total_tokens int4 DEFAULT 0 NOT NULL, + total_processing_time_ms int4 DEFAULT 0 NOT NULL, + CONSTRAINT usage_analytics_pkey PRIMARY KEY (id), + CONSTRAINT usage_analytics_bot_id_fkey FOREIGN KEY (bot_id) REFERENCES public.bots(id) ON DELETE CASCADE, + CONSTRAINT usage_analytics_session_id_fkey FOREIGN KEY (session_id) REFERENCES public.user_sessions(id) ON DELETE CASCADE, + CONSTRAINT usage_analytics_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE ); - -CREATE TABLE IF NOT EXISTS clicks ( - campaign_id TEXT NOT NULL, - email TEXT NOT NULL, - updated_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(campaign_id, email) -); - --- Tools and context tables -CREATE TABLE IF NOT EXISTS tools ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) UNIQUE NOT NULL, - description TEXT NOT NULL, - parameters JSONB NOT NULL DEFAULT '{}', - script TEXT NOT NULL, - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE IF NOT EXISTS context_injections ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID NOT NULL REFERENCES user_sessions(id) ON DELETE CASCADE, - injected_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - context_data JSONB NOT NULL, - reason TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Analytics tables -CREATE TABLE IF NOT EXISTS usage_analytics ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, - session_id UUID NOT NULL REFERENCES user_sessions(id) ON DELETE CASCADE, - date DATE NOT NULL DEFAULT CURRENT_DATE, - message_count INTEGER NOT NULL DEFAULT 0, - total_tokens INTEGER NOT NULL DEFAULT 0, - total_processing_time_ms INTEGER NOT NULL DEFAULT 0 -); - --- Performance indexes -CREATE INDEX IF NOT EXISTS idx_user_sessions_user_bot ON user_sessions(user_id, bot_id); -CREATE INDEX IF NOT EXISTS idx_user_sessions_updated_at ON user_sessions(updated_at); -CREATE INDEX IF NOT EXISTS idx_message_history_session_id ON message_history(session_id); -CREATE INDEX IF NOT EXISTS idx_message_history_created_at ON message_history(created_at); -CREATE INDEX IF NOT EXISTS idx_usage_analytics_date ON usage_analytics(date); -CREATE INDEX IF NOT EXISTS idx_system_automations_active ON system_automations(kind) WHERE is_active; -CREATE INDEX IF NOT EXISTS idx_bot_channels_type ON bot_channels(channel_type) WHERE is_active; - --- Default data -INSERT INTO bots (id, name, llm_provider, context_provider) -VALUES ('00000000-0000-0000-0000-000000000000', 'Default Bot', 'mock', 'mock') -ON CONFLICT (id) DO NOTHING; - -INSERT INTO users (id, username, email, password_hash) -VALUES ('00000000-0000-0000-0000-000000000001', 'demo', 'demo@example.com', '$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG') -ON CONFLICT (id) DO NOTHING; +CREATE INDEX idx_usage_analytics_date ON public.usage_analytics USING btree (date); diff --git a/src/basic/keywords/hear_talk.rs b/src/basic/keywords/hear_talk.rs index 10518c90..a27fc46a 100644 --- a/src/basic/keywords/hear_talk.rs +++ b/src/basic/keywords/hear_talk.rs @@ -170,23 +170,24 @@ pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Eng let cache_clone = cache.clone(); - tokio::spawn(async move { - if let Some(cache_client) = &cache_clone { - let mut conn = match cache_client.get_multiplexed_async_connection().await { - Ok(conn) => conn, - Err(e) => { - error!("Failed to connect to cache: {}", e); - return; - } - }; + if let Some(cache_client) = &cache_clone { + let mut conn = match futures::executor::block_on( + cache_client.get_multiplexed_async_connection(), + ) { + Ok(conn) => conn, + Err(e) => { + error!("Failed to connect to cache: {}", e); + return Ok(Dynamic::UNIT); + } + }; - let _: Result<(), _> = redis::cmd("SET") + let _: Result<(), _> = futures::executor::block_on( + redis::cmd("SET") .arg(&redis_key) .arg(&context_value) - .query_async(&mut conn) - .await; - } - }); + .query_async(&mut conn), + ); + } Ok(Dynamic::UNIT) }) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 3afdaed3..be191322 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -5,6 +5,7 @@ use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_ws::Message as WsMessage; use chrono::Utc; use log::{debug, error, info, warn}; +use redis::Commands; use serde_json; use std::collections::HashMap; use std::fs; @@ -501,13 +502,57 @@ impl BotOrchestrator { session_id: Uuid, user_id: Uuid, ) -> Result, Box> { + let redis_key = format!("context:{}:{}", user_id, session_id); + + // Fetch context from Redis and append to history on first retrieval + let redis_context = if let Some(redis_client) = &self.state.redis_client { + let conn = redis_client + .get_connection() + .map_err(|e| { + warn!("Failed to get Redis connection: {}", e); + e + }) + .ok(); + + if let Some(mut connection) = conn { + match connection.get::<_, Option>(&redis_key) { + Ok(Some(context)) => { + info!( + "Retrieved context from Redis for key {}: {} chars", + redis_key, + context.len() + ); + Some(context) + } + Ok(None) => { + debug!("No context found in Redis for key {}", redis_key); + None + } + Err(e) => { + warn!("Failed to retrieve context from Redis: {}", e); + None + } + } + } else { + None + } + } else { + None + }; + info!( "Getting conversation history for session {} user {}", session_id, user_id ); let mut session_manager = self.state.session_manager.lock().await; let history = session_manager.get_conversation_history(session_id, user_id)?; - Ok(history) + if let Some(context) = redis_context { + let mut result = vec![("system".to_string(), context)]; + result.extend(history); + Ok(result) + } else { + Ok(history) + } } pub async fn run_start_script( @@ -710,10 +755,6 @@ async fn websocket_handler( session_id, user_id ); - let orchestrator_clone = BotOrchestrator::new(Arc::clone(&data)); - let session_id_clone = session_id.clone(); - let user_id_clone = user_id.clone(); - let web_adapter = data.web_adapter.clone(); let session_id_clone1 = session_id.clone(); let session_id_clone2 = session_id.clone(); diff --git a/templates/annoucements.gbai/annoucements.gbdialog/auth.bas b/templates/annoucements.gbai/annoucements.gbdialog/auth.bas index ba36a0ca..291be8d2 100644 --- a/templates/annoucements.gbai/annoucements.gbdialog/auth.bas +++ b/templates/annoucements.gbai/annoucements.gbdialog/auth.bas @@ -1,9 +1,6 @@ -REM print!(token); - -REM result = GET "http://0.0.0.0/danteapi/isvalid?token=" + token; +REM result = GET "http://0.0.0.0/api/isvalid?token=" + token; REM user = FIND "users", "external_id=" + result.user_id -REM SET_USER user.id SET_USER "92fcffaa-bf0a-41a9-8d99-5541709d695b" return true; diff --git a/templates/annoucements.gbai/annoucements.gbdialog/start.bas b/templates/annoucements.gbai/annoucements.gbdialog/start.bas index 02dac8b2..406c6be5 100644 --- a/templates/annoucements.gbai/annoucements.gbdialog/start.bas +++ b/templates/annoucements.gbai/annoucements.gbdialog/start.bas @@ -1,4 +1,6 @@ -TALK "Oi from BASIC" +TALK "Olá, estou preparando um resumo para você." + + REM text = GET "default.pdf" REM resume = LLM "Say Hello and present a a resume from " + text REM TALK resume diff --git a/web/index.html b/web/index.html index c393e4c9..2c884c29 100644 --- a/web/index.html +++ b/web/index.html @@ -2,7 +2,7 @@ - General Bots 2400 + General Bots