From 4acb9bb8f5fc47e88d2ac0cdff28c0de9c4dcb6a Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Thu, 16 Oct 2025 11:43:02 -0300 Subject: [PATCH] Migrate automations to param and sqlite - Rename script_name to param in automation flow and DB schema - Add BotMemory model and bot_memories table - Remove script_name field from automation - Enable sqlite support via rusqlite and related crates (optional) - Update prompts and queries to use param instead of script_name - Remove deprecated annoucements GBai templates and align add-req.sh - Refactor main to initialize automation service and simplify startup --- Cargo.lock | 66 +++- Cargo.toml | 38 +- add-req.sh | 5 +- docs/guide/automation.md | 90 +++++ docs/guide/conversation.md | 0 docs/guide/debugging.md | 0 docs/guide/last.md | 348 +++++++++++++++++ docs/guide/quickstart.md | 0 prompts/dev/add-keyword.md | 4 +- prompts/dev/doc-keyword.md | 201 ++++++++++ prompts/dev/doc-topic.md | 1 + scripts/database/{6.0.sql => 6.0.0.sql} | 5 - scripts/database/6.0.1.sql | 13 + src/basic/keywords/bot_memory.rs | 141 +++++++ src/basic/keywords/mod.rs | 1 + src/basic/keywords/on.rs | 14 +- src/basic/keywords/set_schedule.rs | 14 +- src/main.rs | 369 +++++++++--------- src/shared/models.rs | 25 +- .../announcements.gbdialog}/auth.bas | 0 .../announcements.gbdialog}/start.bas | 0 .../announcements.gbdialog/update-summary.bas | 6 + 22 files changed, 1132 insertions(+), 209 deletions(-) create mode 100644 docs/guide/automation.md create mode 100644 docs/guide/conversation.md create mode 100644 docs/guide/debugging.md create mode 100644 docs/guide/last.md create mode 100644 docs/guide/quickstart.md create mode 100644 prompts/dev/doc-keyword.md create mode 100644 prompts/dev/doc-topic.md rename scripts/database/{6.0.sql => 6.0.0.sql} (99%) create mode 100644 scripts/database/6.0.1.sql create mode 100644 src/basic/keywords/bot_memory.rs rename templates/{annoucements.gbai/annoucements.gbdialog => announcements.gbai/announcements.gbdialog}/auth.bas (100%) rename templates/{annoucements.gbai/annoucements.gbdialog => announcements.gbai/announcements.gbdialog}/start.bas (100%) create mode 100644 templates/announcements.gbai/announcements.gbdialog/update-summary.bas diff --git a/Cargo.lock b/Cargo.lock index 3dfd3ef0..9f05d59a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,7 +1009,7 @@ dependencies = [ [[package]] name = "botserver" -version = "0.1.0" +version = "6.0.1" dependencies = [ "actix-cors", "actix-multipart", @@ -1045,6 +1045,7 @@ dependencies = [ "regex", "reqwest 0.12.23", "rhai", + "rusqlite", "serde", "serde_json", "smartstring", @@ -1766,8 +1767,11 @@ dependencies = [ "diesel_derives", "downcast-rs", "itoa", + "libsqlite3-sys", "pq-sys", "serde_json", + "sqlite-wasm-rs", + "time", "uuid", ] @@ -1986,6 +1990,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.3.0" @@ -2312,6 +2328,15 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "headless_chrome" version = "1.0.18" @@ -2986,6 +3011,16 @@ dependencies = [ "windows-targets 0.53.5", ] +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "libwebrtc" version = "0.3.16" @@ -4258,6 +4293,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags 2.9.4", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -4698,6 +4747,21 @@ dependencies = [ "der", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aead1c279716985b981b7940ef9b652d3f93d70a7296853c633b7ce8fa8088a" +dependencies = [ + "js-sys", + "once_cell", + "thiserror 2.0.17", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 16e2c4e3..d5ba3216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,46 @@ [package] name = "botserver" -version = "0.1.0" +version = "6.0.1" edition = "2021" -authors = ["Rodrigo Rodriguez "] +authors = [ + "@AlanPerdomo", + "@AnaPaulaGil", + "@arenasio", + "@AtyllaL", + "@christopherdecastilho", + "@danielolima96", + "@Dariojunior3", + "@davidlerner26", + "@ExperimentationGarage", + "@flavioandrade91", + "@HeraldoAlmeida", + "@joao-parana", + "@jonathasc", + "@jramos-br", + "@lpicanco", + "@marcosvelasco", + "@matheus39x", + "@oerlabshenrique", + "@othonlima", + "@PH-Nascimento", + "@phpussente", + "@RobsonDantasE", + "@rodrigorodriguez", + "@SarahLourenco", + "@thipatriota", + "@webgus", + "@ZuilhoSe", +] description = "General Bots Server" license = "AGPL-3.0" -repository = "https://github.pragmatismo.com.br/generalbots/botserver" +repository = "https://alm.pragmatismo.com.br/generalbots/botserver" [features] default = ["vectordb"] vectordb = ["qdrant-client"] email = ["imap"] web_automation = ["headless_chrome"] +sqlite = ["rusqlite"] [dependencies] actix-cors = "0.7" @@ -28,6 +57,9 @@ base64 = "0.22" bytes = "1.8" chrono = { version = "0.4", features = ["serde"] } diesel = { version = "2.1", features = ["postgres", "uuid", "chrono", "serde_json"] } +rusqlite = { version = "0.37.0", optional = true } +[target.'cfg(not(release))'.dependencies] +diesel = { version = "2.1", features = ["sqlite"] } dotenvy = "0.15" downloader = "0.2" env_logger = "0.11" diff --git a/add-req.sh b/add-req.sh index 00d4d38f..31f43178 100755 --- a/add-req.sh +++ b/add-req.sh @@ -24,7 +24,7 @@ dirs=( #"auth" #"automation" #"basic" - "bot" + #"bot" #"channels" #"config" #"context" @@ -60,8 +60,7 @@ done files=( "$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/templates/annoucements.gbai/annoucements.gbdialog/update-summary.bas" ) for file in "${files[@]}"; do diff --git a/docs/guide/automation.md b/docs/guide/automation.md new file mode 100644 index 00000000..86db7c04 --- /dev/null +++ b/docs/guide/automation.md @@ -0,0 +1,90 @@ +# Automation System Documentation + +## Overview + +The automation system allows you to execute scripts automatically based on triggers like database changes or scheduled times. + +## Database Configuration + +### system_automations Table Structure + +To create an automation, insert a record into the `system_automations` table: + +| Column | Type | Description | +|--------|------|-------------| +| id | UUID | Unique identifier (auto-generated) | +| name | TEXT | Human-readable name | +| kind | INTEGER | Trigger type (see below) | +| target | TEXT | Target table name (for table triggers) | +| param | TEXT | Script filename or path | +| schedule | TEXT | Cron pattern (for scheduled triggers) | +| is_active | BOOLEAN | Whether automation is enabled | +| last_triggered | TIMESTAMP | Last execution time | + +### Trigger Types (kind field) + +- `0` - TableInsert (triggers on new rows) +- `1` - TableUpdate (triggers on row updates) +- `2` - TableDelete (triggers on row deletions) +- `3` - Scheduled (triggers on cron schedule) + +## Configuration Examples + +### 1. Scheduled Automation (Daily at 2:30 AM) +```sql +INSERT INTO system_automations (name, kind, target, param, schedule, is_active) +VALUES ('Daily Resume Update', 3, NULL, 'daily_resume.js', '30 2 * * *', true); +``` + +### 2. Table Change Automation +```sql +-- Trigger when new documents are added to documents table +INSERT INTO system_automations (name, kind, target, param, schedule, is_active) +VALUES ('Process New Documents', 0, 'documents', 'process_document.js', NULL, true); +``` + +## Cron Pattern Format + +Use standard cron syntax: `minute hour day month weekday` + +Examples: +- `0 9 * * *` - Daily at 9:00 AM +- `30 14 * * 1-5` - Weekdays at 2:30 PM +- `0 0 1 * *` - First day of every month at midnight + +## Sample Script + +```BASIC + let text = GET "default.gbdrive/default.pdf" + + let resume = LLM "Build table resume with deadlines, dates and actions: " + text + + SET BOT MEMORY "resume", resume +``` + +## Script Capabilities + +### Available Commands +- `GET "path"` - Read files from storage +- `LLM "prompt"` - Query language model with prompts +- `SET BOT MEMORY "key", value` - Store data in bot memory +- Database operations (query, insert, update) +- HTTP requests to external APIs + +## Best Practices + +1. **Keep scripts focused** - Each script should do one thing well +2. **Handle errors gracefully** - Use try/catch blocks +3. **Log important actions** - Use console.log for debugging +4. **Test thoroughly** - Verify scripts work before automating +5. **Monitor execution** - Check logs for any automation errors + +## Monitoring + +Check application logs to monitor automation execution: +```bash +# Look for automation-related messages +grep "Automation\|Script executed" application.log +``` + +The system will automatically update `last_triggered` timestamps and log any errors encountered during execution. diff --git a/docs/guide/conversation.md b/docs/guide/conversation.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/guide/debugging.md b/docs/guide/debugging.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/guide/last.md b/docs/guide/last.md new file mode 100644 index 00000000..9e40ef37 --- /dev/null +++ b/docs/guide/last.md @@ -0,0 +1,348 @@ +# 📚 **BASIC LEARNING EXAMPLES - LAST Function** + +## 🎯 **EXAMPLE 1: BASIC CONCEPT OF LAST FUNCTION** + +``` +**BASIC CONCEPT:** +LAST FUNCTION - Extract last word + +**LEVEL:** +☒ Beginner ☐ Intermediate ☐ Advanced + +**LEARNING OBJECTIVE:** +Understand how the LAST function extracts the last word from text + +**CODE EXAMPLE:** +```basic +10 PALAVRA$ = "The mouse chewed the clothes" +20 ULTIMA$ = LAST(PALAVRA$) +30 PRINT "Last word: "; ULTIMA$ +``` + +**SPECIFIC QUESTIONS:** +- How does the function know where the last word ends? +- What happens if there are extra spaces? +- Can I use it with numeric variables? + +**PROJECT CONTEXT:** +I'm creating a program that analyzes sentences + +**EXPECTED RESULT:** +Should display: "Last word: clothes" + +**PARTS I DON'T UNDERSTAND:** +- Why are parentheses needed? +- How does the function work internally? +``` + +--- + +## 🛠️ **EXAMPLE 2: SOLVING ERROR WITH LAST** + +``` +**BASIC ERROR:** +"Syntax error" when using LAST + +**MY CODE:** +```basic +10 TEXTO$ = "Good day world" +20 RESULTADO$ = LAST TEXTO$ +30 PRINT RESULTADO$ +``` + +**PROBLEM LINE:** +Line 20 + +**EXPECTED BEHAVIOR:** +Show "world" on screen + +**CURRENT BEHAVIOR:** +Syntax error + +**WHAT I'VE TRIED:** +- Tried without parentheses +- Tried with different quotes +- Tried changing variable name + +**BASIC VERSION:** +QBASIC with Rhai extension + +**CORRECTED SOLUTION:** +```basic +10 TEXTO$ = "Good day world" +20 RESULTADO$ = LAST(TEXTO$) +30 PRINT RESULTADO$ +``` +``` + +--- + +## 📖 **EXAMPLE 3: EXPLAINING LAST COMMAND** + +``` +**COMMAND:** +LAST - Extracts last word + +**SYNTAX:** +```basic +ULTIMA$ = LAST(TEXTO$) +``` + +**PARAMETERS:** +- TEXTO$: String from which to extract the last word + +**SIMPLE EXAMPLE:** +```basic +10 FRASE$ = "The sun is bright" +20 ULTIMA$ = LAST(FRASE$) +30 PRINT ULTIMA$ ' Shows: bright +``` + +**PRACTICAL EXAMPLE:** +```basic +10 INPUT "Enter your full name: "; NOME$ +20 SOBRENOME$ = LAST(NOME$) +30 PRINT "Hello Mr./Mrs. "; SOBRENOME$ +``` + +**COMMON ERRORS:** +- Forgetting parentheses: `LAST TEXTO$` ❌ +- Using with numbers: `LAST(123)` ❌ +- Forgetting to assign to a variable + +**BEGINNER TIP:** +Always use parentheses and ensure content is text + +**SUGGESTED EXERCISE:** +Create a program that asks for a sentence and shows the first and last word +``` + +--- + +## 🎨 **EXAMPLE 4: COMPLETE PROJECT WITH LAST** + +``` +# BASIC PROJECT: SENTENCE ANALYZER + +## 📝 DESCRIPTION +Program that analyzes sentences and extracts useful information + +## 🎨 FEATURES +- [x] Extract last word +- [x] Count words +- [x] Show statistics + +## 🧩 CODE STRUCTURE +```basic +10 PRINT "=== SENTENCE ANALYZER ===" +20 INPUT "Enter a sentence: "; FRASE$ +30 +40 ' Extract last word +50 ULTIMA$ = LAST(FRASE$) +60 +70 ' Count words (simplified) +80 PALAVRAS = 1 +90 FOR I = 1 TO LEN(FRASE$) +100 IF MID$(FRASE$, I, 1) = " " THEN PALAVRAS = PALAVRAS + 1 +110 NEXT I +120 +130 PRINT +140 PRINT "Last word: "; ULTIMA$ +150 PRINT "Total words: "; PALAVRAS +160 PRINT "Original sentence: "; FRASE$ +``` + +## 🎯 LEARNINGS +- How to use LAST function +- How to count words manually +- String manipulation in BASIC + +## ❓ QUESTIONS TO EVOLVE +- How to extract the first word? +- How to handle punctuation? +- How to work with multiple sentences? +``` + +--- + +## 🏆 **EXAMPLE 5: SPECIAL CASES AND TESTS** + +``` +**BASIC CONCEPT:** +SPECIAL CASES OF LAST FUNCTION + +**LEVEL:** +☐ Beginner ☒ Intermediate ☐ Advanced + +**LEARNING OBJECTIVE:** +Understand how LAST behaves in special situations + +**CODE EXAMPLES:** +```basic +' Case 1: Empty string +10 TEXTO$ = "" +20 PRINT LAST(TEXTO$) ' Result: "" + +' Case 2: Single word only +30 TEXTO$ = "Sun" +40 PRINT LAST(TEXTO$) ' Result: "Sun" + +' Case 3: Multiple spaces +50 TEXTO$ = "Hello World " +60 PRINT LAST(TEXTO$) ' Result: "World" + +' Case 4: With tabs and newlines +70 TEXTO$ = "Line1" + CHR$(9) + "Line2" + CHR$(13) +80 PRINT LAST(TEXTO$) ' Result: "Line2" +``` + +**SPECIFIC QUESTIONS:** +- What happens with empty strings? +- How does it work with special characters? +- Is it case-sensitive? + +**PROJECT CONTEXT:** +I need to robustly validate user inputs + +**EXPECTED RESULT:** +Consistent behavior in all cases + +**PARTS I DON'T UNDERSTAND:** +- How the function handles whitespace? +- What are CHR$(9) and CHR$(13)? +``` + +--- + +## 🛠️ **EXAMPLE 6: INTEGRATION WITH OTHER FUNCTIONS** + +``` +**BASIC CONCEPT:** +COMBINING LAST WITH OTHER FUNCTIONS + +**LEVEL:** +☐ Beginner ☒ Intermediate ☐ Advanced + +**LEARNING OBJECTIVE:** +Learn to use LAST in more complex expressions + +**CODE EXAMPLE:** +```basic +10 ' Example 1: With concatenation +20 PARTE1$ = "Programming" +30 PARTE2$ = " in BASIC" +40 FRASE_COMPLETA$ = PARTE1$ + PARTE2$ +50 PRINT LAST(FRASE_COMPLETA$) ' Result: "BASIC" + +60 ' Example 2: With string functions +70 NOME_COMPLETO$ = "Maria Silva Santos" +80 SOBRENOME$ = LAST(NOME_COMPLETO$) +90 PRINT "Mr./Mrs. "; SOBRENOME$ + +100 ' Example 3: In conditional expressions +110 FRASE$ = "The sky is blue" +120 IF LAST(FRASE$) = "blue" THEN PRINT "The last word is blue!" +``` + +**SPECIFIC QUESTIONS:** +- Can I use LAST directly in IF? +- How to combine with LEFT$, RIGHT$, MID$? +- Is there a size limit for the string? + +**PROJECT CONTEXT:** +Creating validations and text processing + +**EXPECTED RESULT:** +Use LAST flexibly in different contexts + +**PARTS I DON'T UNDERSTAND:** +- Expression evaluation order +- Performance with very large strings +``` + +--- + +## 📚 **EXAMPLE 7: PRACTICAL EXERCISES** + +``` +# EXERCISES: PRACTICING WITH LAST + +## 🎯 EXERCISE 1 - BASIC +Create a program that asks for the user's full name and greets using only the last name. + +**SOLUTION:** +```basic +10 INPUT "Enter your full name: "; NOME$ +20 SOBRENOME$ = LAST(NOME$) +30 PRINT "Hello, Mr./Mrs. "; SOBRENOME$; "!" +``` + +## 🎯 EXERCISE 2 - INTERMEDIATE +Make a program that analyzes if the last word of a sentence is "end". + +**SOLUTION:** +```basic +10 INPUT "Enter a sentence: "; FRASE$ +20 IF LAST(FRASE$) = "end" THEN PRINT "Sentence ends with 'end'" ELSE PRINT "Sentence doesn't end with 'end'" +``` + +## 🎯 EXERCISE 3 - ADVANCED +Create a program that processes multiple sentences and shows statistics. + +**SOLUTION:** +```basic +10 DIM FRASES$(3) +20 FRASES$(1) = "The sun shines" +30 FRASES$(2) = "The rain falls" +40 FRASES$(3) = "The wind blows" +50 +60 FOR I = 1 TO 3 +70 PRINT "Sentence "; I; ": "; FRASES$(I) +80 PRINT "Last word: "; LAST(FRASES$(I)) +90 PRINT +100 NEXT I +``` + +## 💡 TIPS +- Always test with different inputs +- Use PRINT for debugging +- Start with simple examples +``` + +--- + +## 🎨 **EXAMPLE 8: MARKDOWN DOCUMENTATION** + +```markdown +# LAST FUNCTION - COMPLETE GUIDE + +## 🎯 OBJECTIVE +Extract the last word from a string + +## 📋 SYNTAX +```basic +RESULTADO$ = LAST(TEXTO$) +``` + +## 🧩 PARAMETERS +- `TEXTO$`: Input string + +## 🔍 BEHAVIOR +- Splits string by spaces +- Returns the last part +- Ignores extra spaces at beginning/end + +## 🚀 EXAMPLES +```basic +10 PRINT LAST("hello world") ' Output: world +20 PRINT LAST("one word") ' Output: word +30 PRINT LAST(" spaces ") ' Output: spaces +``` + +## ⚠️ LIMITATIONS +- Doesn't work with numbers +- Requires parentheses +- Considers only spaces as separators +``` + +These examples cover from the basic concept to practical applications of the LAST function, always focusing on BASIC beginners! 🚀 \ No newline at end of file diff --git a/docs/guide/quickstart.md b/docs/guide/quickstart.md new file mode 100644 index 00000000..e69de29b diff --git a/prompts/dev/add-keyword.md b/prompts/dev/add-keyword.md index 5aa90571..84085b5e 100644 --- a/prompts/dev/add-keyword.md +++ b/prompts/dev/add-keyword.md @@ -118,12 +118,12 @@ pub async fn execute_set_schedule( let result = sqlx::query( "INSERT INTO system_automations - (kind, schedule, script_name) + (kind, schedule, param) VALUES ($1, $2, $3)" ) .bind(TriggerKind::Scheduled as i32) .bind(cron) - .bind(script_name) + .bind(param) .execute(pool) .await?; diff --git a/prompts/dev/doc-keyword.md b/prompts/dev/doc-keyword.md new file mode 100644 index 00000000..49851ec0 --- /dev/null +++ b/prompts/dev/doc-keyword.md @@ -0,0 +1,201 @@ +# Modelo de Prompt para Aprendizado de BASIC em Markdown + +## 🎯 **ESTRUTURA PARA APRENDIZ DE BASIC** + +``` +**CONCEITO BASIC:** +[Nome do conceito ou comando] + +**NÍVEL:** +☐ Iniciante ☐ Intermediário ☐ Avançado + +**OBJETIVO DE APRENDIZADO:** +[O que você quer entender ou criar] + +**CÓDIGO EXEMPLO:** +```basic +[Seu código ou exemplo aqui] +``` + +**DÚVIDAS ESPECÍFICAS:** +- [Dúvida 1 sobre o conceito] +- [Dúvida 2 sobre sintaxe] +- [Dúvida 3 sobre aplicação] + +**CONTEXTO DO PROJETO:** +[Descrição do que está tentando fazer] + +**RESULTADO ESPERADO:** +[O que o código deve fazer] + +**PARTES QUE NÃO ENTENDE:** +- [Trecho específico do código] +- [Mensagem de erro] +- [Lógica confusa] +``` + +--- + +## 📚 **EXEMPLO PRÁTICO: LOOP FOR** + +``` +**CONCEITO BASIC:** +LOOP FOR + +**NÍVEL:** +☒ Iniciante ☐ Intermediário ☐ Avançado + +**OBJETIVO DE APRENDIZADO:** +Entender como criar um contador de 1 a 10 + +**CÓDIGO EXEMPLO:** +```basic +10 FOR I = 1 TO 10 +20 PRINT "Número: "; I +30 NEXT I +``` + +**DÚVIDAS ESPECÍFICAS:** +- O que significa "NEXT I"? +- Posso usar outras letras além de "I"? +- Como fazer contagem regressiva? + +**CONTEXTO DO PROJETO:** +Estou criando um programa que lista números + +**RESULTADO ESPERADO:** +Que apareça: Número: 1, Número: 2, etc. + +**PARTES QUE NÃO ENTENDE:** +- Por que precisa do número 10 na linha 10? +- O que acontece se esquecer o NEXT? +``` + +--- + +## 🛠️ **MODELO PARA RESOLVER ERROS** + +``` +**ERRO NO BASIC:** +[Mensagem de erro ou comportamento estranho] + +**MEU CÓDIGO:** +```basic +[Coloque seu código completo] +``` + +**LINHA COM PROBLEMA:** +[Linha específica onde ocorre o erro] + +**COMPORTAMENTO ESPERADO:** +[O que deveria acontecer] + +**COMPORTAMENTO ATUAL:** +[O que está acontecendo de errado] + +**O QUE JÁ TENTEI:** +- [Tentativa 1 de correção] +- [Tentativa 2] +- [Tentativa 3] + +**VERSÃO DO BASIC:** +[QBASIC, GW-BASIC, FreeBASIC, etc.] +``` + +--- + +## 📖 **MODELO PARA EXPLICAR COMANDOS** + +``` +**COMANDO:** +[Nome do comando - ex: PRINT, INPUT, GOTO] + +**SYNTAX:** +[Como escrever corretamente] + +**PARÂMETROS:** +- Parâmetro 1: [Função] +- Parâmetro 2: [Função] + +**EXEMPLO SIMPLES:** +```basic +[Exemplo mínimo e funcional] +``` + +**EXEMPLO PRÁTICO:** +```basic +[Exemplo em contexto real] +``` + +**ERROS COMUNS:** +- [Erro frequente 1] +- [Erro frequente 2] + +**DICA PARA INICIANTES:** +[Dica simples para não errar] + +**EXERCÍCIO SUGERIDO:** +[Pequeno exercício para praticar] +``` + +--- + +## 🎨 **FORMATAÇÃO MARKDOWN PARA BASIC** + +### **Como documentar seu código em .md:** +```markdown +# [NOME DO PROGRAMA] + +## 🎯 OBJETIVO +[O que o programa faz] + +## 📋 COMO USAR +1. [Passo 1] +2. [Passo 2] + +## 🧩 CÓDIGO FONTE +```basic +[Seu código aqui] +``` + +## 🔍 EXPLICAÇÃO +- **Linha X**: [Explicação] +- **Linha Y**: [Explicação] + +## 🚀 EXEMPLO DE EXECUÇÃO +``` +[Saída do programa] +``` +``` + +--- + +## 🏆 **MODELO DE PROJETO COMPLETO** + +``` +# PROJETO BASIC: [NOME] + +## 📝 DESCRIÇÃO +[Descrição do que o programa faz] + +## 🎨 FUNCIONALIDADES +- [ ] Funcionalidade 1 +- [ ] Funcionalidade 2 +- [ ] Funcionalidade 3 + +## 🧩 ESTRUTURA DO CÓDIGO +```basic +[Seu código organizado] +``` + +## 🎯 APRENDIZADOS +- [Conceito 1 aprendido] +- [Conceito 2 aprendido] + +## ❓ DÚVIDAS PARA EVOLUIR +- [Dúvida para melhorar] +- [O que gostaria de fazer depois] +``` + +gerenerate several examples +for this keyword written in rhai do this only for basic audience: diff --git a/prompts/dev/doc-topic.md b/prompts/dev/doc-topic.md new file mode 100644 index 00000000..d8ffd3b4 --- /dev/null +++ b/prompts/dev/doc-topic.md @@ -0,0 +1 @@ +- Be pragmatic and concise with examples. diff --git a/scripts/database/6.0.sql b/scripts/database/6.0.0.sql similarity index 99% rename from scripts/database/6.0.sql rename to scripts/database/6.0.0.sql index bb05719a..cd3f4839 100644 --- a/scripts/database/6.0.sql +++ b/scripts/database/6.0.0.sql @@ -1,8 +1,3 @@ --- public.bots definition - --- Drop table - --- DROP TABLE public.bots; CREATE TABLE public.bots ( id uuid DEFAULT gen_random_uuid() NOT NULL, diff --git a/scripts/database/6.0.1.sql b/scripts/database/6.0.1.sql new file mode 100644 index 00000000..20d9d1e1 --- /dev/null +++ b/scripts/database/6.0.1.sql @@ -0,0 +1,13 @@ + +CREATE TABLE bot_memories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, + key TEXT NOT NULL, + value TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(bot_id, key) +); + +CREATE INDEX idx_bot_memories_bot_id ON bot_memories(bot_id); +CREATE INDEX idx_bot_memories_key ON bot_memories(key); diff --git a/src/basic/keywords/bot_memory.rs b/src/basic/keywords/bot_memory.rs new file mode 100644 index 00000000..b61cb938 --- /dev/null +++ b/src/basic/keywords/bot_memory.rs @@ -0,0 +1,141 @@ +use crate::shared::models::UserSession; +use crate::shared::state::AppState; +use diesel::prelude::*; +use log::{error, info}; +use rhai::{Dynamic, Engine}; +use std::sync::Arc; +use uuid::Uuid; + +pub fn set_bot_memory_keyword(state: Arc, user: UserSession, engine: &mut Engine) { + let state_clone = Arc::clone(&state); + let user_clone = user.clone(); + + engine + .register_custom_syntax( + &["SET_BOT_MEMORY", "$expr$", "$expr$"], + true, + move |context, inputs| { + let key = context.eval_expression_tree(&inputs[0])?.to_string(); + let value = context.eval_expression_tree(&inputs[1])?.to_string(); + + let state_for_spawn = Arc::clone(&state_clone); + let user_clone_spawn = user_clone.clone(); + let key_clone = key.clone(); + let value_clone = value.clone(); + + tokio::spawn(async move { + use crate::shared::models::bot_memories; + + let mut conn = match state_for_spawn.conn.lock() { + Ok(conn) => conn, + Err(e) => { + error!( + "Failed to acquire database connection for SET BOT MEMORY: {}", + e + ); + return; + } + }; + + let bot_uuid = match Uuid::parse_str(&user_clone_spawn.bot_id.to_string()) { + Ok(uuid) => uuid, + Err(e) => { + error!("Invalid bot ID format: {}", e); + return; + } + }; + + let now = chrono::Utc::now(); + + let existing_memory: Option = bot_memories::table + .filter(bot_memories::bot_id.eq(bot_uuid)) + .filter(bot_memories::key.eq(&key_clone)) + .select(bot_memories::id) + .first(&mut *conn) + .optional() + .unwrap_or(None); + + if let Some(memory_id) = existing_memory { + let update_result = diesel::update( + bot_memories::table.filter(bot_memories::id.eq(memory_id)), + ) + .set(( + bot_memories::value.eq(&value_clone), + bot_memories::updated_at.eq(now), + )) + .execute(&mut *conn); + + match update_result { + Ok(_) => { + info!( + "Updated bot memory for key: {} with value length: {}", + key_clone, + value_clone.len() + ); + } + Err(e) => { + error!("Failed to update bot memory: {}", e); + } + } + } else { + let new_memory = crate::shared::models::BotMemory { + id: Uuid::new_v4(), + bot_id: bot_uuid, + key: key_clone.clone(), + value: value_clone.clone(), + created_at: now, + updated_at: now, + }; + + let insert_result = diesel::insert_into(bot_memories::table) + .values(&new_memory) + .execute(&mut *conn); + + match insert_result { + Ok(_) => { + info!( + "Created new bot memory for key: {} with value length: {}", + key_clone, + value_clone.len() + ); + } + Err(e) => { + error!("Failed to insert bot memory: {}", e); + } + } + } + }); + + Ok(Dynamic::UNIT) + }, + ) + .unwrap(); +} + +pub fn get_bot_memory_keyword(state: Arc, user: UserSession, engine: &mut Engine) { + let state_clone = Arc::clone(&state); + let user_clone = user.clone(); + + engine.register_fn("GET_BOT_MEMORY", move |key_param: String| -> String { + use crate::shared::models::bot_memories; + + let state = Arc::clone(&state_clone); + + let conn_result = state.conn.lock(); + if let Ok(mut conn) = conn_result { + let bot_uuid = user_clone.bot_id; + + let memory_value: Option = bot_memories::table + .filter(bot_memories::bot_id.eq(bot_uuid)) + .filter(bot_memories::key.eq(&key_param)) + .select(bot_memories::value) + .first(&mut *conn) + .optional() + .unwrap_or(None); + + memory_value.unwrap_or_default() + } else { + String::new() + } + }); +} diff --git a/src/basic/keywords/mod.rs b/src/basic/keywords/mod.rs index 220dd655..34dd3fcf 100644 --- a/src/basic/keywords/mod.rs +++ b/src/basic/keywords/mod.rs @@ -1,3 +1,4 @@ +pub mod bot_memory; pub mod create_site; pub mod find; pub mod first; diff --git a/src/basic/keywords/on.rs b/src/basic/keywords/on.rs index 2f9bf717..ddcb6c89 100644 --- a/src/basic/keywords/on.rs +++ b/src/basic/keywords/on.rs @@ -18,7 +18,7 @@ pub fn on_keyword(state: &AppState, _user: UserSession, engine: &mut Engine) { move |context, inputs| { let trigger_type = context.eval_expression_tree(&inputs[0])?.to_string(); let table = context.eval_expression_tree(&inputs[1])?.to_string(); - let script_name = format!("{}_{}.rhai", table, trigger_type.to_lowercase()); + let name = format!("{}_{}.rhai", table, trigger_type.to_lowercase()); let kind = match trigger_type.to_uppercase().as_str() { "UPDATE" => TriggerKind::TableUpdate, @@ -28,7 +28,7 @@ pub fn on_keyword(state: &AppState, _user: UserSession, engine: &mut Engine) { }; let mut conn = state_clone.conn.lock().unwrap(); - let result = execute_on_trigger(&mut *conn, kind, &table, &script_name) + let result = execute_on_trigger(&mut *conn, kind, &table, &name) .map_err(|e| format!("DB error: {}", e))?; if let Some(rows_affected) = result.get("rows_affected") { @@ -45,11 +45,11 @@ pub fn execute_on_trigger( conn: &mut diesel::PgConnection, kind: TriggerKind, table: &str, - script_name: &str, + param: &str, ) -> Result { info!( - "Starting execute_on_trigger with kind: {:?}, table: {}, script_name: {}", - kind, table, script_name + "Starting execute_on_trigger with kind: {:?}, table: {}, param: {}", + kind, table, param ); use crate::shared::models::system_automations; @@ -57,7 +57,7 @@ pub fn execute_on_trigger( let new_automation = ( system_automations::kind.eq(kind as i32), system_automations::target.eq(table), - system_automations::script_name.eq(script_name), + system_automations::param.eq(param), ); let result = diesel::insert_into(system_automations::table) @@ -72,7 +72,7 @@ pub fn execute_on_trigger( "command": "on_trigger", "trigger_type": format!("{:?}", kind), "table": table, - "script_name": script_name, + "param": param, "rows_affected": result })) } diff --git a/src/basic/keywords/set_schedule.rs b/src/basic/keywords/set_schedule.rs index c4029e33..f0bba130 100644 --- a/src/basic/keywords/set_schedule.rs +++ b/src/basic/keywords/set_schedule.rs @@ -15,10 +15,10 @@ pub fn set_schedule_keyword(state: &AppState, _user: UserSession, engine: &mut E .register_custom_syntax(&["SET_SCHEDULE", "$string$"], true, { move |context, inputs| { let cron = context.eval_expression_tree(&inputs[0])?.to_string(); - let script_name = format!("cron_{}.rhai", cron.replace(' ', "_")); + let param = format!("cron_{}.rhai", cron.replace(' ', "_")); let mut conn = state_clone.conn.lock().unwrap(); - let result = execute_set_schedule(&mut *conn, &cron, &script_name) + let result = execute_set_schedule(&mut *conn, &cron, ¶m) .map_err(|e| format!("DB error: {}", e))?; if let Some(rows_affected) = result.get("rows_affected") { @@ -34,11 +34,11 @@ pub fn set_schedule_keyword(state: &AppState, _user: UserSession, engine: &mut E pub fn execute_set_schedule( conn: &mut diesel::PgConnection, cron: &str, - script_name: &str, + param: &str, ) -> Result> { info!( - "Starting execute_set_schedule with cron: {}, script_name: {}", - cron, script_name + "Starting execute_set_schedule with cron: {}, param: {}", + cron, param ); use crate::shared::models::system_automations; @@ -46,7 +46,7 @@ pub fn execute_set_schedule( let new_automation = ( system_automations::kind.eq(TriggerKind::Scheduled as i32), system_automations::schedule.eq(cron), - system_automations::script_name.eq(script_name), + system_automations::param.eq(param), ); let result = diesel::insert_into(system_automations::table) @@ -56,7 +56,7 @@ pub fn execute_set_schedule( Ok(json!({ "command": "set_schedule", "schedule": cron, - "script_name": script_name, + "param": param, "rows_affected": result })) } diff --git a/src/main.rs b/src/main.rs index fab9652d..2068cb2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod session; mod shared; mod tools; mod whatsapp; +use crate::automation::AutomationService; use crate::bot::{ auth_handler, create_session, get_session_history, get_sessions, index, set_mode_handler, start_session, static_files, voice_start, voice_stop, websocket_handler, @@ -42,203 +43,213 @@ use crate::shared::state::AppState; use crate::whatsapp::WhatsAppAdapter; #[actix_web::main] -async fn main() -> std::io::Result<()> {// Load environment variables from a .env file, if present. -dotenv().ok(); -let llama_url = - std::env::var("LLM_URL").unwrap_or_else(|_| "http://localhost:8081".to_string()); +async fn main() -> std::io::Result<()> { + // Load environment variables from a .env file, if present. + dotenv().ok(); + let llama_url = + std::env::var("LLM_URL").unwrap_or_else(|_| "http://localhost:8081".to_string()); -// Initialise logger with environment‑based log level (default to "info"). -env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + // Initialise logger with environment‑based log level (default to "info"). + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); -// Load application configuration. -let cfg = AppConfig::from_env(); -let config = std::sync::Arc::new(cfg.clone()); + // Load application configuration. + let cfg = AppConfig::from_env(); + let config = std::sync::Arc::new(cfg.clone()); -// ---------------------------------------------------------------------- -// Database connections -// ---------------------------------------------------------------------- -let db_pool = match diesel::Connection::establish(&cfg.database_url()) { - Ok(conn) => { - info!("Connected to main database successfully"); - Arc::new(Mutex::new(conn)) - } - Err(e) => { - log::error!("Failed to connect to main database: {}", e); - return Err(std::io::Error::new( - std::io::ErrorKind::ConnectionRefused, - format!("Database connection failed: {}", e), - )); - } -}; + // ---------------------------------------------------------------------- + // Database connections + // ---------------------------------------------------------------------- + let db_pool = match diesel::Connection::establish(&cfg.database_url()) { + Ok(conn) => { + info!("Connected to main database successfully"); + Arc::new(Mutex::new(conn)) + } + Err(e) => { + log::error!("Failed to connect to main database: {}", e); + return Err(std::io::Error::new( + std::io::ErrorKind::ConnectionRefused, + format!("Database connection failed: {}", e), + )); + } + }; -// Placeholder for a second/custom database – currently just re‑using the main pool. -let _custom_db_url = format!( - "postgres://{}:{}@{}:{}/{}", - cfg.database_custom.username, - cfg.database_custom.password, - cfg.database_custom.server, - cfg.database_custom.port, - cfg.database_custom.database -); -let db_custom_pool = db_pool.clone(); + // Placeholder for a second/custom database – currently just re‑using the main pool. + let _custom_db_url = format!( + "postgres://{}:{}@{}:{}/{}", + cfg.database_custom.username, + cfg.database_custom.password, + cfg.database_custom.server, + cfg.database_custom.port, + cfg.database_custom.database + ); + let db_custom_pool = db_pool.clone(); -// ---------------------------------------------------------------------- -// LLM local server initialisation -// ---------------------------------------------------------------------- -ensure_llama_servers_running() - .await - .expect("Failed to initialize LLM local server."); + // ---------------------------------------------------------------------- + // LLM local server initialisation + // ---------------------------------------------------------------------- + ensure_llama_servers_running() + .await + .expect("Failed to initialize LLM local server."); -// ---------------------------------------------------------------------- -// Redis client (optional) -// ---------------------------------------------------------------------- -let redis_client = match redis::Client::open("redis://127.0.0.1/") { - Ok(client) => { - info!("Connected to Redis successfully"); - Some(Arc::new(client)) - } - Err(e) => { - log::warn!("Failed to connect to Redis: {}", e); - None - } -}; + // ---------------------------------------------------------------------- + // Redis client (optional) + // ---------------------------------------------------------------------- + let redis_client = match redis::Client::open("redis://127.0.0.1/") { + Ok(client) => { + info!("Connected to Redis successfully"); + Some(Arc::new(client)) + } + Err(e) => { + log::warn!("Failed to connect to Redis: {}", e); + None + } + }; -// ---------------------------------------------------------------------- -// Tooling and LLM provider -// ---------------------------------------------------------------------- -let tool_manager = Arc::new(tools::ToolManager::new()); -let llm_provider = Arc::new(crate::llm::OpenAIClient::new( - "empty".to_string(), - Some(llama_url.clone()), -)); + // ---------------------------------------------------------------------- + // Tooling and LLM provider + // ---------------------------------------------------------------------- + let tool_manager = Arc::new(tools::ToolManager::new()); + let llm_provider = Arc::new(crate::llm::OpenAIClient::new( + "empty".to_string(), + Some(llama_url.clone()), + )); -// ---------------------------------------------------------------------- -// Channel adapters -// ---------------------------------------------------------------------- -let web_adapter = Arc::new(WebChannelAdapter::new()); -let voice_adapter = Arc::new(VoiceAdapter::new( - "https://livekit.example.com".to_string(), - "api_key".to_string(), - "api_secret".to_string(), -)); -let whatsapp_adapter = Arc::new(WhatsAppAdapter::new( - "whatsapp_token".to_string(), - "phone_number_id".to_string(), - "verify_token".to_string(), -)); -let tool_api = Arc::new(tools::ToolApi::new()); + // ---------------------------------------------------------------------- + // Channel adapters + // ---------------------------------------------------------------------- + let web_adapter = Arc::new(WebChannelAdapter::new()); + let voice_adapter = Arc::new(VoiceAdapter::new( + "https://livekit.example.com".to_string(), + "api_key".to_string(), + "api_secret".to_string(), + )); + let whatsapp_adapter = Arc::new(WhatsAppAdapter::new( + "whatsapp_token".to_string(), + "phone_number_id".to_string(), + "verify_token".to_string(), + )); + let tool_api = Arc::new(tools::ToolApi::new()); -// ---------------------------------------------------------------------- -// S3 / MinIO client -// ---------------------------------------------------------------------- -let drive = init_drive(&config.minio) - .await - .expect("Failed to initialize Drive"); + // ---------------------------------------------------------------------- + // S3 / MinIO client + // ---------------------------------------------------------------------- + let drive = init_drive(&config.minio) + .await + .expect("Failed to initialize Drive"); -// ---------------------------------------------------------------------- -// Session and authentication services -// ---------------------------------------------------------------------- -let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new( - diesel::Connection::establish(&cfg.database_url()).unwrap(), - redis_client.clone(), -))); + // ---------------------------------------------------------------------- + // Session and authentication services + // ---------------------------------------------------------------------- + let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new( + diesel::Connection::establish(&cfg.database_url()).unwrap(), + redis_client.clone(), + ))); -let auth_service = Arc::new(tokio::sync::Mutex::new(auth::AuthService::new( - diesel::Connection::establish(&cfg.database_url()).unwrap(), - redis_client.clone(), -))); + let auth_service = Arc::new(tokio::sync::Mutex::new(auth::AuthService::new( + diesel::Connection::establish(&cfg.database_url()).unwrap(), + redis_client.clone(), + ))); -// ---------------------------------------------------------------------- -// Global application state -// ---------------------------------------------------------------------- -let app_state = Arc::new(AppState { - // `s3_client` expects an `Option`. - s3_client: Some(drive.clone()), - config: Some(cfg.clone()), - conn: db_pool.clone(), - custom_conn: db_custom_pool.clone(), - redis_client: redis_client.clone(), - session_manager: session_manager.clone(), - tool_manager: tool_manager.clone(), - llm_provider: llm_provider.clone(), - auth_service: auth_service.clone(), - channels: Arc::new(Mutex::new({ - let mut map = HashMap::new(); - map.insert( - "web".to_string(), - web_adapter.clone() as Arc, - ); - map - })), - response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())), - web_adapter: web_adapter.clone(), - voice_adapter: voice_adapter.clone(), - whatsapp_adapter: whatsapp_adapter.clone(), - tool_api: tool_api.clone(), -}); + // ---------------------------------------------------------------------- + // Global application state + // ---------------------------------------------------------------------- + let app_state = Arc::new(AppState { + // `s3_client` expects an `Option`. + s3_client: Some(drive.clone()), + config: Some(cfg.clone()), + conn: db_pool.clone(), + custom_conn: db_custom_pool.clone(), + redis_client: redis_client.clone(), + session_manager: session_manager.clone(), + tool_manager: tool_manager.clone(), + llm_provider: llm_provider.clone(), + auth_service: auth_service.clone(), + channels: Arc::new(Mutex::new({ + let mut map = HashMap::new(); + map.insert( + "web".to_string(), + web_adapter.clone() as Arc, + ); + map + })), + response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())), + web_adapter: web_adapter.clone(), + voice_adapter: voice_adapter.clone(), + whatsapp_adapter: whatsapp_adapter.clone(), + tool_api: tool_api.clone(), + }); -// ---------------------------------------------------------------------- -// Start HTTP server (multithreaded) -// ---------------------------------------------------------------------- -info!( - "Starting server on {}:{}", - config.server.host, config.server.port -); + // ---------------------------------------------------------------------- + // Start HTTP server (multithreaded) + // ---------------------------------------------------------------------- + info!( + "Starting server on {}:{}", + config.server.host, config.server.port + ); -// Determine the number of worker threads – default to the number of logical CPUs, -// fallback to 4 if the information cannot be retrieved. -let worker_count = std::thread::available_parallelism() - .map(|n| n.get()) - .unwrap_or(4); + // Determine the number of worker threads – default to the number of logical CPUs, + // fallback to 4 if the information cannot be retrieved. + let worker_count = std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(4); -HttpServer::new(move || { - // CORS configuration – allow any origin/method/header (adjust for production). - let cors = Cors::default() - .allow_any_origin() - .allow_any_method() - .allow_any_header() - .max_age(3600); + // Start automation service in background + let automation_state = app_state.clone(); - let app_state_clone = app_state.clone(); - let mut app = App::new() - .wrap(cors) - .wrap(Logger::default()) - .wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i")) - .app_data(web::Data::from(app_state_clone)); + let automation = AutomationService::new( + automation_state, + "templates/announcements.gbai/announcements.gbdialog", + ); + let _automation_handle = automation.spawn(); - // Register all route handlers / services. - app = app - .service(upload_file) - .service(index) - .service(static_files) - .service(websocket_handler) - .service(auth_handler) - .service(whatsapp_webhook_verify) - .service(voice_start) - .service(voice_stop) - .service(create_session) - .service(get_sessions) - .service(start_session) - .service(get_session_history) - .service(set_mode_handler) - .service(chat_completions_local) - .service(embeddings_local); + HttpServer::new(move || { + // CORS configuration – allow any origin/method/header (adjust for production). + let cors = Cors::default() + .allow_any_origin() + .allow_any_method() + .allow_any_header() + .max_age(3600); - #[cfg(feature = "email")] - { + let app_state_clone = app_state.clone(); + let mut app = App::new() + .wrap(cors) + .wrap(Logger::default()) + .wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i")) + .app_data(web::Data::from(app_state_clone)); + + // Register all route handlers / services. app = app - .service(get_latest_email_from) - .service(get_emails) - .service(list_emails) - .service(send_email) - .service(save_draft) - .service(save_click); - } + .service(upload_file) + .service(index) + .service(static_files) + .service(websocket_handler) + .service(auth_handler) + .service(whatsapp_webhook_verify) + .service(voice_start) + .service(voice_stop) + .service(create_session) + .service(get_sessions) + .service(start_session) + .service(get_session_history) + .service(set_mode_handler) + .service(chat_completions_local) + .service(embeddings_local); - app -}) -.workers(worker_count) // Enable multithreaded handling -.bind((config.server.host.clone(), config.server.port))? -.run() -.await + #[cfg(feature = "email")] + { + app = app + .service(get_latest_email_from) + .service(get_emails) + .service(list_emails) + .service(send_email) + .service(save_draft) + .service(save_click); + } + + app + }) + .workers(worker_count) // Enable multithreaded handling + .bind((config.server.host.clone(), config.server.port))? + .run() + .await } diff --git a/src/shared/models.rs b/src/shared/models.rs index a39b2a0d..550f64b0 100644 --- a/src/shared/models.rs +++ b/src/shared/models.rs @@ -11,6 +11,7 @@ pub struct Organization { pub slug: String, pub created_at: chrono::DateTime, } + #[derive(Debug, Clone, Queryable, Serialize, Deserialize)] #[diesel(table_name = users)] pub struct User { @@ -67,7 +68,6 @@ pub struct Automation { pub kind: i32, pub target: Option, pub schedule: Option, - pub script_name: String, pub param: String, pub is_active: bool, pub last_triggered: Option>, @@ -136,6 +136,17 @@ pub struct PaginationQuery { pub page_size: Option, } +#[derive(Debug, Clone, Serialize, Deserialize, Queryable, Identifiable, Insertable)] +#[diesel(table_name = bot_memories)] +pub struct BotMemory { + pub id: Uuid, + pub bot_id: Uuid, + pub key: String, + pub value: String, + pub created_at: chrono::DateTime, + pub updated_at: chrono::DateTime, +} + diesel::table! { organizations (org_id) { org_id -> Uuid, @@ -162,7 +173,6 @@ diesel::table! { kind -> Int4, target -> Nullable, schedule -> Nullable, - script_name -> Text, param -> Text, is_active -> Bool, last_triggered -> Nullable, @@ -216,3 +226,14 @@ diesel::table! { updated_at -> Timestamptz, } } + +diesel::table! { + bot_memories (id) { + id -> Uuid, + bot_id -> Uuid, + key -> Text, + value -> Text, + created_at -> Timestamptz, + updated_at -> Timestamptz, + } +} diff --git a/templates/annoucements.gbai/annoucements.gbdialog/auth.bas b/templates/announcements.gbai/announcements.gbdialog/auth.bas similarity index 100% rename from templates/annoucements.gbai/annoucements.gbdialog/auth.bas rename to templates/announcements.gbai/announcements.gbdialog/auth.bas diff --git a/templates/annoucements.gbai/annoucements.gbdialog/start.bas b/templates/announcements.gbai/announcements.gbdialog/start.bas similarity index 100% rename from templates/annoucements.gbai/annoucements.gbdialog/start.bas rename to templates/announcements.gbai/announcements.gbdialog/start.bas diff --git a/templates/announcements.gbai/announcements.gbdialog/update-summary.bas b/templates/announcements.gbai/announcements.gbdialog/update-summary.bas new file mode 100644 index 00000000..2dcea9fc --- /dev/null +++ b/templates/announcements.gbai/announcements.gbdialog/update-summary.bas @@ -0,0 +1,6 @@ + + +let text = GET "default.gbdrive/default.pdf" +let resume = LLM "Build table resume with deadlines, dates and actions: " + text + +SET_BOT_MEMORY "resume" resume