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]]
|
[[package]]
|
||||||
name = "botserver"
|
name = "botserver"
|
||||||
version = "0.1.0"
|
version = "6.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
|
|
@ -1045,6 +1045,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest 0.12.23",
|
"reqwest 0.12.23",
|
||||||
"rhai",
|
"rhai",
|
||||||
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smartstring",
|
"smartstring",
|
||||||
|
|
@ -1766,8 +1767,11 @@ dependencies = [
|
||||||
"diesel_derives",
|
"diesel_derives",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libsqlite3-sys",
|
||||||
"pq-sys",
|
"pq-sys",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sqlite-wasm-rs",
|
||||||
|
"time",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1986,6 +1990,18 @@ dependencies = [
|
||||||
"num-traits",
|
"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]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|
@ -2312,6 +2328,15 @@ version = "0.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
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]]
|
[[package]]
|
||||||
name = "headless_chrome"
|
name = "headless_chrome"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -2986,6 +3011,16 @@ dependencies = [
|
||||||
"windows-targets 0.53.5",
|
"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]]
|
[[package]]
|
||||||
name = "libwebrtc"
|
name = "libwebrtc"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
|
|
@ -4258,6 +4293,20 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.26"
|
version = "0.1.26"
|
||||||
|
|
@ -4698,6 +4747,21 @@ dependencies = [
|
||||||
"der",
|
"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]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|
|
||||||
38
Cargo.toml
38
Cargo.toml
|
|
@ -1,17 +1,46 @@
|
||||||
[package]
|
[package]
|
||||||
name = "botserver"
|
name = "botserver"
|
||||||
version = "0.1.0"
|
version = "6.0.1"
|
||||||
edition = "2021"
|
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"
|
description = "General Bots Server"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
repository = "https://github.pragmatismo.com.br/generalbots/botserver"
|
repository = "https://alm.pragmatismo.com.br/generalbots/botserver"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vectordb"]
|
default = ["vectordb"]
|
||||||
vectordb = ["qdrant-client"]
|
vectordb = ["qdrant-client"]
|
||||||
email = ["imap"]
|
email = ["imap"]
|
||||||
web_automation = ["headless_chrome"]
|
web_automation = ["headless_chrome"]
|
||||||
|
sqlite = ["rusqlite"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-cors = "0.7"
|
actix-cors = "0.7"
|
||||||
|
|
@ -28,6 +57,9 @@ base64 = "0.22"
|
||||||
bytes = "1.8"
|
bytes = "1.8"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
diesel = { version = "2.1", features = ["postgres", "uuid", "chrono", "serde_json"] }
|
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"
|
dotenvy = "0.15"
|
||||||
downloader = "0.2"
|
downloader = "0.2"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ dirs=(
|
||||||
#"auth"
|
#"auth"
|
||||||
#"automation"
|
#"automation"
|
||||||
#"basic"
|
#"basic"
|
||||||
"bot"
|
#"bot"
|
||||||
#"channels"
|
#"channels"
|
||||||
#"config"
|
#"config"
|
||||||
#"context"
|
#"context"
|
||||||
|
|
@ -60,8 +60,7 @@ done
|
||||||
files=(
|
files=(
|
||||||
"$PROJECT_ROOT/src/main.rs"
|
"$PROJECT_ROOT/src/main.rs"
|
||||||
"$PROJECT_ROOT/src/basic/keywords/hear_talk.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/update-summary.bas"
|
||||||
"$PROJECT_ROOT/templates/annoucements.gbai/annoucements.gbdialog/auth.bas"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for file in "${files[@]}"; do
|
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(
|
let result = sqlx::query(
|
||||||
"INSERT INTO system_automations
|
"INSERT INTO system_automations
|
||||||
(kind, schedule, script_name)
|
(kind, schedule, param)
|
||||||
VALUES ($1, $2, $3)"
|
VALUES ($1, $2, $3)"
|
||||||
)
|
)
|
||||||
.bind(TriggerKind::Scheduled as i32)
|
.bind(TriggerKind::Scheduled as i32)
|
||||||
.bind(cron)
|
.bind(cron)
|
||||||
.bind(script_name)
|
.bind(param)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.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 (
|
CREATE TABLE public.bots (
|
||||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
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 create_site;
|
||||||
pub mod find;
|
pub mod find;
|
||||||
pub mod first;
|
pub mod first;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub fn on_keyword(state: &AppState, _user: UserSession, engine: &mut Engine) {
|
||||||
move |context, inputs| {
|
move |context, inputs| {
|
||||||
let trigger_type = context.eval_expression_tree(&inputs[0])?.to_string();
|
let trigger_type = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
let table = context.eval_expression_tree(&inputs[1])?.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() {
|
let kind = match trigger_type.to_uppercase().as_str() {
|
||||||
"UPDATE" => TriggerKind::TableUpdate,
|
"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 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))?;
|
.map_err(|e| format!("DB error: {}", e))?;
|
||||||
|
|
||||||
if let Some(rows_affected) = result.get("rows_affected") {
|
if let Some(rows_affected) = result.get("rows_affected") {
|
||||||
|
|
@ -45,11 +45,11 @@ pub fn execute_on_trigger(
|
||||||
conn: &mut diesel::PgConnection,
|
conn: &mut diesel::PgConnection,
|
||||||
kind: TriggerKind,
|
kind: TriggerKind,
|
||||||
table: &str,
|
table: &str,
|
||||||
script_name: &str,
|
param: &str,
|
||||||
) -> Result<Value, String> {
|
) -> Result<Value, String> {
|
||||||
info!(
|
info!(
|
||||||
"Starting execute_on_trigger with kind: {:?}, table: {}, script_name: {}",
|
"Starting execute_on_trigger with kind: {:?}, table: {}, param: {}",
|
||||||
kind, table, script_name
|
kind, table, param
|
||||||
);
|
);
|
||||||
|
|
||||||
use crate::shared::models::system_automations;
|
use crate::shared::models::system_automations;
|
||||||
|
|
@ -57,7 +57,7 @@ pub fn execute_on_trigger(
|
||||||
let new_automation = (
|
let new_automation = (
|
||||||
system_automations::kind.eq(kind as i32),
|
system_automations::kind.eq(kind as i32),
|
||||||
system_automations::target.eq(table),
|
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)
|
let result = diesel::insert_into(system_automations::table)
|
||||||
|
|
@ -72,7 +72,7 @@ pub fn execute_on_trigger(
|
||||||
"command": "on_trigger",
|
"command": "on_trigger",
|
||||||
"trigger_type": format!("{:?}", kind),
|
"trigger_type": format!("{:?}", kind),
|
||||||
"table": table,
|
"table": table,
|
||||||
"script_name": script_name,
|
"param": param,
|
||||||
"rows_affected": result
|
"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, {
|
.register_custom_syntax(&["SET_SCHEDULE", "$string$"], true, {
|
||||||
move |context, inputs| {
|
move |context, inputs| {
|
||||||
let cron = context.eval_expression_tree(&inputs[0])?.to_string();
|
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 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))?;
|
.map_err(|e| format!("DB error: {}", e))?;
|
||||||
|
|
||||||
if let Some(rows_affected) = result.get("rows_affected") {
|
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(
|
pub fn execute_set_schedule(
|
||||||
conn: &mut diesel::PgConnection,
|
conn: &mut diesel::PgConnection,
|
||||||
cron: &str,
|
cron: &str,
|
||||||
script_name: &str,
|
param: &str,
|
||||||
) -> Result<Value, Box<dyn std::error::Error>> {
|
) -> Result<Value, Box<dyn std::error::Error>> {
|
||||||
info!(
|
info!(
|
||||||
"Starting execute_set_schedule with cron: {}, script_name: {}",
|
"Starting execute_set_schedule with cron: {}, param: {}",
|
||||||
cron, script_name
|
cron, param
|
||||||
);
|
);
|
||||||
|
|
||||||
use crate::shared::models::system_automations;
|
use crate::shared::models::system_automations;
|
||||||
|
|
@ -46,7 +46,7 @@ pub fn execute_set_schedule(
|
||||||
let new_automation = (
|
let new_automation = (
|
||||||
system_automations::kind.eq(TriggerKind::Scheduled as i32),
|
system_automations::kind.eq(TriggerKind::Scheduled as i32),
|
||||||
system_automations::schedule.eq(cron),
|
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)
|
let result = diesel::insert_into(system_automations::table)
|
||||||
|
|
@ -56,7 +56,7 @@ pub fn execute_set_schedule(
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"command": "set_schedule",
|
"command": "set_schedule",
|
||||||
"schedule": cron,
|
"schedule": cron,
|
||||||
"script_name": script_name,
|
"param": param,
|
||||||
"rows_affected": result
|
"rows_affected": result
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
369
src/main.rs
369
src/main.rs
|
|
@ -23,6 +23,7 @@ mod session;
|
||||||
mod shared;
|
mod shared;
|
||||||
mod tools;
|
mod tools;
|
||||||
mod whatsapp;
|
mod whatsapp;
|
||||||
|
use crate::automation::AutomationService;
|
||||||
use crate::bot::{
|
use crate::bot::{
|
||||||
auth_handler, create_session, get_session_history, get_sessions, index, set_mode_handler,
|
auth_handler, create_session, get_session_history, get_sessions, index, set_mode_handler,
|
||||||
start_session, static_files, voice_start, voice_stop, websocket_handler,
|
start_session, static_files, voice_start, voice_stop, websocket_handler,
|
||||||
|
|
@ -42,203 +43,213 @@ use crate::shared::state::AppState;
|
||||||
use crate::whatsapp::WhatsAppAdapter;
|
use crate::whatsapp::WhatsAppAdapter;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {// Load environment variables from a .env file, if present.
|
async fn main() -> std::io::Result<()> {
|
||||||
dotenv().ok();
|
// Load environment variables from a .env file, if present.
|
||||||
let llama_url =
|
dotenv().ok();
|
||||||
std::env::var("LLM_URL").unwrap_or_else(|_| "http://localhost:8081".to_string());
|
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").
|
// Initialise logger with environment‑based log level (default to "info").
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||||
|
|
||||||
// Load application configuration.
|
// Load application configuration.
|
||||||
let cfg = AppConfig::from_env();
|
let cfg = AppConfig::from_env();
|
||||||
let config = std::sync::Arc::new(cfg.clone());
|
let config = std::sync::Arc::new(cfg.clone());
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Database connections
|
// Database connections
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let db_pool = match diesel::Connection::establish(&cfg.database_url()) {
|
let db_pool = match diesel::Connection::establish(&cfg.database_url()) {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
info!("Connected to main database successfully");
|
info!("Connected to main database successfully");
|
||||||
Arc::new(Mutex::new(conn))
|
Arc::new(Mutex::new(conn))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to connect to main database: {}", e);
|
log::error!("Failed to connect to main database: {}", e);
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::ConnectionRefused,
|
std::io::ErrorKind::ConnectionRefused,
|
||||||
format!("Database connection failed: {}", e),
|
format!("Database connection failed: {}", e),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Placeholder for a second/custom database – currently just re‑using the main pool.
|
// Placeholder for a second/custom database – currently just re‑using the main pool.
|
||||||
let _custom_db_url = format!(
|
let _custom_db_url = format!(
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
cfg.database_custom.username,
|
cfg.database_custom.username,
|
||||||
cfg.database_custom.password,
|
cfg.database_custom.password,
|
||||||
cfg.database_custom.server,
|
cfg.database_custom.server,
|
||||||
cfg.database_custom.port,
|
cfg.database_custom.port,
|
||||||
cfg.database_custom.database
|
cfg.database_custom.database
|
||||||
);
|
);
|
||||||
let db_custom_pool = db_pool.clone();
|
let db_custom_pool = db_pool.clone();
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// LLM local server initialisation
|
// LLM local server initialisation
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
ensure_llama_servers_running()
|
ensure_llama_servers_running()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to initialize LLM local server.");
|
.expect("Failed to initialize LLM local server.");
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Redis client (optional)
|
// Redis client (optional)
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let redis_client = match redis::Client::open("redis://127.0.0.1/") {
|
let redis_client = match redis::Client::open("redis://127.0.0.1/") {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
info!("Connected to Redis successfully");
|
info!("Connected to Redis successfully");
|
||||||
Some(Arc::new(client))
|
Some(Arc::new(client))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Failed to connect to Redis: {}", e);
|
log::warn!("Failed to connect to Redis: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Tooling and LLM provider
|
// Tooling and LLM provider
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let tool_manager = Arc::new(tools::ToolManager::new());
|
let tool_manager = Arc::new(tools::ToolManager::new());
|
||||||
let llm_provider = Arc::new(crate::llm::OpenAIClient::new(
|
let llm_provider = Arc::new(crate::llm::OpenAIClient::new(
|
||||||
"empty".to_string(),
|
"empty".to_string(),
|
||||||
Some(llama_url.clone()),
|
Some(llama_url.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Channel adapters
|
// Channel adapters
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let web_adapter = Arc::new(WebChannelAdapter::new());
|
let web_adapter = Arc::new(WebChannelAdapter::new());
|
||||||
let voice_adapter = Arc::new(VoiceAdapter::new(
|
let voice_adapter = Arc::new(VoiceAdapter::new(
|
||||||
"https://livekit.example.com".to_string(),
|
"https://livekit.example.com".to_string(),
|
||||||
"api_key".to_string(),
|
"api_key".to_string(),
|
||||||
"api_secret".to_string(),
|
"api_secret".to_string(),
|
||||||
));
|
));
|
||||||
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new(
|
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new(
|
||||||
"whatsapp_token".to_string(),
|
"whatsapp_token".to_string(),
|
||||||
"phone_number_id".to_string(),
|
"phone_number_id".to_string(),
|
||||||
"verify_token".to_string(),
|
"verify_token".to_string(),
|
||||||
));
|
));
|
||||||
let tool_api = Arc::new(tools::ToolApi::new());
|
let tool_api = Arc::new(tools::ToolApi::new());
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// S3 / MinIO client
|
// S3 / MinIO client
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let drive = init_drive(&config.minio)
|
let drive = init_drive(&config.minio)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to initialize Drive");
|
.expect("Failed to initialize Drive");
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Session and authentication services
|
// Session and authentication services
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
||||||
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
||||||
redis_client.clone(),
|
redis_client.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
let auth_service = Arc::new(tokio::sync::Mutex::new(auth::AuthService::new(
|
let auth_service = Arc::new(tokio::sync::Mutex::new(auth::AuthService::new(
|
||||||
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
||||||
redis_client.clone(),
|
redis_client.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Global application state
|
// Global application state
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
// `s3_client` expects an `Option<aws_sdk_s3::Client>`.
|
// `s3_client` expects an `Option<aws_sdk_s3::Client>`.
|
||||||
s3_client: Some(drive.clone()),
|
s3_client: Some(drive.clone()),
|
||||||
config: Some(cfg.clone()),
|
config: Some(cfg.clone()),
|
||||||
conn: db_pool.clone(),
|
conn: db_pool.clone(),
|
||||||
custom_conn: db_custom_pool.clone(),
|
custom_conn: db_custom_pool.clone(),
|
||||||
redis_client: redis_client.clone(),
|
redis_client: redis_client.clone(),
|
||||||
session_manager: session_manager.clone(),
|
session_manager: session_manager.clone(),
|
||||||
tool_manager: tool_manager.clone(),
|
tool_manager: tool_manager.clone(),
|
||||||
llm_provider: llm_provider.clone(),
|
llm_provider: llm_provider.clone(),
|
||||||
auth_service: auth_service.clone(),
|
auth_service: auth_service.clone(),
|
||||||
channels: Arc::new(Mutex::new({
|
channels: Arc::new(Mutex::new({
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(
|
map.insert(
|
||||||
"web".to_string(),
|
"web".to_string(),
|
||||||
web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>,
|
web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>,
|
||||||
);
|
);
|
||||||
map
|
map
|
||||||
})),
|
})),
|
||||||
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
||||||
web_adapter: web_adapter.clone(),
|
web_adapter: web_adapter.clone(),
|
||||||
voice_adapter: voice_adapter.clone(),
|
voice_adapter: voice_adapter.clone(),
|
||||||
whatsapp_adapter: whatsapp_adapter.clone(),
|
whatsapp_adapter: whatsapp_adapter.clone(),
|
||||||
tool_api: tool_api.clone(),
|
tool_api: tool_api.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Start HTTP server (multithreaded)
|
// Start HTTP server (multithreaded)
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
info!(
|
info!(
|
||||||
"Starting server on {}:{}",
|
"Starting server on {}:{}",
|
||||||
config.server.host, config.server.port
|
config.server.host, config.server.port
|
||||||
);
|
);
|
||||||
|
|
||||||
// Determine the number of worker threads – default to the number of logical CPUs,
|
// Determine the number of worker threads – default to the number of logical CPUs,
|
||||||
// fallback to 4 if the information cannot be retrieved.
|
// fallback to 4 if the information cannot be retrieved.
|
||||||
let worker_count = std::thread::available_parallelism()
|
let worker_count = std::thread::available_parallelism()
|
||||||
.map(|n| n.get())
|
.map(|n| n.get())
|
||||||
.unwrap_or(4);
|
.unwrap_or(4);
|
||||||
|
|
||||||
HttpServer::new(move || {
|
// Start automation service in background
|
||||||
// CORS configuration – allow any origin/method/header (adjust for production).
|
let automation_state = app_state.clone();
|
||||||
let cors = Cors::default()
|
|
||||||
.allow_any_origin()
|
|
||||||
.allow_any_method()
|
|
||||||
.allow_any_header()
|
|
||||||
.max_age(3600);
|
|
||||||
|
|
||||||
let app_state_clone = app_state.clone();
|
let automation = AutomationService::new(
|
||||||
let mut app = App::new()
|
automation_state,
|
||||||
.wrap(cors)
|
"templates/announcements.gbai/announcements.gbdialog",
|
||||||
.wrap(Logger::default())
|
);
|
||||||
.wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i"))
|
let _automation_handle = automation.spawn();
|
||||||
.app_data(web::Data::from(app_state_clone));
|
|
||||||
|
|
||||||
// Register all route handlers / services.
|
HttpServer::new(move || {
|
||||||
app = app
|
// CORS configuration – allow any origin/method/header (adjust for production).
|
||||||
.service(upload_file)
|
let cors = Cors::default()
|
||||||
.service(index)
|
.allow_any_origin()
|
||||||
.service(static_files)
|
.allow_any_method()
|
||||||
.service(websocket_handler)
|
.allow_any_header()
|
||||||
.service(auth_handler)
|
.max_age(3600);
|
||||||
.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);
|
|
||||||
|
|
||||||
#[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
|
app = app
|
||||||
.service(get_latest_email_from)
|
.service(upload_file)
|
||||||
.service(get_emails)
|
.service(index)
|
||||||
.service(list_emails)
|
.service(static_files)
|
||||||
.service(send_email)
|
.service(websocket_handler)
|
||||||
.service(save_draft)
|
.service(auth_handler)
|
||||||
.service(save_click);
|
.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
|
#[cfg(feature = "email")]
|
||||||
})
|
{
|
||||||
.workers(worker_count) // Enable multithreaded handling
|
app = app
|
||||||
.bind((config.server.host.clone(), config.server.port))?
|
.service(get_latest_email_from)
|
||||||
.run()
|
.service(get_emails)
|
||||||
.await
|
.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 slug: String,
|
||||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Queryable, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Queryable, Serialize, Deserialize)]
|
||||||
#[diesel(table_name = users)]
|
#[diesel(table_name = users)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
|
@ -67,7 +68,6 @@ pub struct Automation {
|
||||||
pub kind: i32,
|
pub kind: i32,
|
||||||
pub target: Option<String>,
|
pub target: Option<String>,
|
||||||
pub schedule: Option<String>,
|
pub schedule: Option<String>,
|
||||||
pub script_name: String,
|
|
||||||
pub param: String,
|
pub param: String,
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
pub last_triggered: Option<chrono::DateTime<chrono::Utc>>,
|
pub last_triggered: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
|
@ -136,6 +136,17 @@ pub struct PaginationQuery {
|
||||||
pub page_size: Option<i64>,
|
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! {
|
diesel::table! {
|
||||||
organizations (org_id) {
|
organizations (org_id) {
|
||||||
org_id -> Uuid,
|
org_id -> Uuid,
|
||||||
|
|
@ -162,7 +173,6 @@ diesel::table! {
|
||||||
kind -> Int4,
|
kind -> Int4,
|
||||||
target -> Nullable<Text>,
|
target -> Nullable<Text>,
|
||||||
schedule -> Nullable<Text>,
|
schedule -> Nullable<Text>,
|
||||||
script_name -> Text,
|
|
||||||
param -> Text,
|
param -> Text,
|
||||||
is_active -> Bool,
|
is_active -> Bool,
|
||||||
last_triggered -> Nullable<Timestamptz>,
|
last_triggered -> Nullable<Timestamptz>,
|
||||||
|
|
@ -216,3 +226,14 @@ diesel::table! {
|
||||||
updated_at -> Timestamptz,
|
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