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
This commit is contained in:
parent
83d4a61fcd
commit
4acb9bb8f5
22 changed files with 1132 additions and 209 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
38
Cargo.toml
38
Cargo.toml
|
|
@ -1,17 +1,46 @@
|
|||
[package]
|
||||
name = "botserver"
|
||||
version = "0.1.0"
|
||||
version = "6.0.1"
|
||||
edition = "2021"
|
||||
authors = ["Rodrigo Rodriguez <me@rodrigorodriguez.com>"]
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
90
docs/guide/automation.md
Normal file
90
docs/guide/automation.md
Normal file
|
|
@ -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.
|
||||
0
docs/guide/conversation.md
Normal file
0
docs/guide/conversation.md
Normal file
0
docs/guide/debugging.md
Normal file
0
docs/guide/debugging.md
Normal file
348
docs/guide/last.md
Normal file
348
docs/guide/last.md
Normal file
|
|
@ -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! 🚀
|
||||
0
docs/guide/quickstart.md
Normal file
0
docs/guide/quickstart.md
Normal file
|
|
@ -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?;
|
||||
|
||||
|
|
|
|||
201
prompts/dev/doc-keyword.md
Normal file
201
prompts/dev/doc-keyword.md
Normal file
|
|
@ -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:
|
||||
1
prompts/dev/doc-topic.md
Normal file
1
prompts/dev/doc-topic.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Be pragmatic and concise with examples.
|
||||
|
|
@ -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,
|
||||
13
scripts/database/6.0.1.sql
Normal file
13
scripts/database/6.0.1.sql
Normal file
|
|
@ -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);
|
||||
141
src/basic/keywords/bot_memory.rs
Normal file
141
src/basic/keywords/bot_memory.rs
Normal file
|
|
@ -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<AppState>, 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<Uuid> = 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<AppState>, 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<String> = 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()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod bot_memory;
|
||||
pub mod create_site;
|
||||
pub mod find;
|
||||
pub mod first;
|
||||
|
|
|
|||
|
|
@ -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<Value, String> {
|
||||
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
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Value, Box<dyn std::error::Error>> {
|
||||
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
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
369
src/main.rs
369
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<aws_sdk_s3::Client>`.
|
||||
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<dyn crate::channels::ChannelAdapter>,
|
||||
);
|
||||
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<aws_sdk_s3::Client>`.
|
||||
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<dyn crate::channels::ChannelAdapter>,
|
||||
);
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub struct Organization {
|
|||
pub slug: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
pub schedule: Option<String>,
|
||||
pub script_name: String,
|
||||
pub param: String,
|
||||
pub is_active: bool,
|
||||
pub last_triggered: Option<chrono::DateTime<chrono::Utc>>,
|
||||
|
|
@ -136,6 +136,17 @@ pub struct PaginationQuery {
|
|||
pub page_size: Option<i64>,
|
||||
}
|
||||
|
||||
#[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<Utc>,
|
||||
pub updated_at: chrono::DateTime<Utc>,
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
organizations (org_id) {
|
||||
org_id -> Uuid,
|
||||
|
|
@ -162,7 +173,6 @@ diesel::table! {
|
|||
kind -> Int4,
|
||||
target -> Nullable<Text>,
|
||||
schedule -> Nullable<Text>,
|
||||
script_name -> Text,
|
||||
param -> Text,
|
||||
is_active -> Bool,
|
||||
last_triggered -> Nullable<Timestamptz>,
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue