- Database schema added.

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-10-14 16:34:34 -03:00
parent a6df35dae8
commit 573437cb6f
6 changed files with 296 additions and 167 deletions

View file

@ -1,158 +1,246 @@
-- Optimized database schema -- public.bots definition
-- Add organizations table
CREATE TABLE IF NOT EXISTS organizations ( -- Drop table
org_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL, -- DROP TABLE public.bots;
slug VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CREATE TABLE public.bots (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 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 -- public.clicks definition
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Drop table
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL, -- DROP TABLE public.clicks;
password_hash VARCHAR(255) NOT NULL,
phone_number VARCHAR(50), CREATE TABLE public.clicks (
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), campaign_id text NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), email text NOT NULL,
is_active BOOLEAN DEFAULT true 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(), -- public.organizations definition
name VARCHAR(255) NOT NULL,
description TEXT, -- Drop table
llm_provider VARCHAR(100) NOT NULL,
llm_config JSONB NOT NULL DEFAULT '{}', -- DROP TABLE public.organizations;
context_provider VARCHAR(100) NOT NULL,
context_config JSONB NOT NULL DEFAULT '{}', CREATE TABLE public.organizations (
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), org_id uuid DEFAULT gen_random_uuid() NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), "name" varchar(255) NOT NULL,
is_active BOOLEAN DEFAULT true 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(), -- public.users definition
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, -- Drop table
title VARCHAR(500) NOT NULL DEFAULT 'New Conversation',
answer_mode INTEGER NOT NULL DEFAULT 0, -- 0=direct, 1=tool -- DROP TABLE public.users;
context_data JSONB NOT NULL DEFAULT '{}',
current_tool VARCHAR(255), CREATE TABLE public.users (
message_count INTEGER NOT NULL DEFAULT 0, id uuid DEFAULT gen_random_uuid() NOT NULL,
total_tokens INTEGER NOT NULL DEFAULT 0, username varchar(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), email varchar(255) NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), password_hash varchar(255) NOT NULL,
last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(), phone_number varchar(50) NULL,
UNIQUE(user_id, bot_id, title) 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(), -- public.bot_channels definition
session_id UUID NOT NULL REFERENCES user_sessions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- Drop table
role INTEGER NOT NULL, -- 0=user, 1=assistant, 2=system
content_encrypted TEXT NOT NULL, -- DROP TABLE public.bot_channels;
message_type INTEGER NOT NULL DEFAULT 0, -- 0=text, 1=image, 2=audio, 3=file
media_url TEXT, CREATE TABLE public.bot_channels (
token_count INTEGER NOT NULL DEFAULT 0, id uuid DEFAULT gen_random_uuid() NOT NULL,
processing_time_ms INTEGER, bot_id uuid NOT NULL,
llm_model VARCHAR(100), channel_type int4 NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), config jsonb DEFAULT '{}'::jsonb NOT NULL,
message_index INTEGER 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 ( -- public.context_injections definition
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, -- Drop table
channel_type INTEGER NOT NULL, -- 0=web, 1=whatsapp, 2=voice, 3=api
config JSONB NOT NULL DEFAULT '{}', -- DROP TABLE public.context_injections;
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CREATE TABLE public.context_injections (
UNIQUE(bot_id, channel_type) 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(), -- public.message_history definition
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
phone_number VARCHAR(50) NOT NULL, -- Drop table
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- DROP TABLE public.message_history;
UNIQUE(phone_number, bot_id)
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 ( -- public.usage_analytics definition
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
kind INTEGER NOT NULL, -- 0=scheduled, 1=table_update, 2=table_insert, 3=table_delete -- Drop table
target VARCHAR(32),
schedule CHAR(12), -- DROP TABLE public.usage_analytics;
param VARCHAR(32) NOT NULL,
is_active BOOLEAN DEFAULT true NOT NULL, CREATE TABLE public.usage_analytics (
last_triggered TIMESTAMPTZ, id uuid DEFAULT gen_random_uuid() NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 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 INDEX idx_usage_analytics_date ON public.usage_analytics USING btree (date);
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;

View file

@ -170,23 +170,24 @@ pub fn set_context_keyword(state: &AppState, user: UserSession, engine: &mut Eng
let cache_clone = cache.clone(); let cache_clone = cache.clone();
tokio::spawn(async move { if let Some(cache_client) = &cache_clone {
if let Some(cache_client) = &cache_clone { let mut conn = match futures::executor::block_on(
let mut conn = match cache_client.get_multiplexed_async_connection().await { cache_client.get_multiplexed_async_connection(),
Ok(conn) => conn, ) {
Err(e) => { Ok(conn) => conn,
error!("Failed to connect to cache: {}", e); Err(e) => {
return; 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(&redis_key)
.arg(&context_value) .arg(&context_value)
.query_async(&mut conn) .query_async(&mut conn),
.await; );
} }
});
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
}) })

View file

@ -5,6 +5,7 @@ use actix_web::{web, HttpRequest, HttpResponse, Result};
use actix_ws::Message as WsMessage; use actix_ws::Message as WsMessage;
use chrono::Utc; use chrono::Utc;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use redis::Commands;
use serde_json; use serde_json;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
@ -501,13 +502,57 @@ impl BotOrchestrator {
session_id: Uuid, session_id: Uuid,
user_id: Uuid, user_id: Uuid,
) -> Result<Vec<(String, String)>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<(String, String)>, Box<dyn std::error::Error + Send + Sync>> {
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<String>>(&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!( info!(
"Getting conversation history for session {} user {}", "Getting conversation history for session {} user {}",
session_id, user_id session_id, user_id
); );
let mut session_manager = self.state.session_manager.lock().await; let mut session_manager = self.state.session_manager.lock().await;
let history = session_manager.get_conversation_history(session_id, user_id)?; 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( pub async fn run_start_script(
@ -710,10 +755,6 @@ async fn websocket_handler(
session_id, user_id 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 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();

View file

@ -1,9 +1,6 @@
REM print!(token); REM result = GET "http://0.0.0.0/api/isvalid?token=" + token;
REM result = GET "http://0.0.0.0/danteapi/isvalid?token=" + token;
REM user = FIND "users", "external_id=" + result.user_id REM user = FIND "users", "external_id=" + result.user_id
REM SET_USER user.id
SET_USER "92fcffaa-bf0a-41a9-8d99-5541709d695b" SET_USER "92fcffaa-bf0a-41a9-8d99-5541709d695b"
return true; return true;

View file

@ -1,4 +1,6 @@
TALK "Oi from BASIC" TALK "Olá, estou preparando um resumo para você."
REM text = GET "default.pdf" REM text = GET "default.pdf"
REM resume = LLM "Say Hello and present a a resume from " + text REM resume = LLM "Say Hello and present a a resume from " + text
REM TALK resume REM TALK resume

View file

@ -2,7 +2,7 @@
<html lang="pt-br"> <html lang="pt-br">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>General Bots 2400</title> <title>General Bots</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></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/gsap/3.12.2/gsap.min.js"></script>