docs: Add JWT secret rotation and security considerations
- Add JWT to available rotation components - Document security limitations and manual steps - Add component table with service restart requirements - Include verification and best practices documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6d48dbba1b
commit
cb84ad2b56
64 changed files with 688 additions and 139 deletions
|
|
@ -55,7 +55,7 @@ On first run, botserver automatically:
|
|||
- Installs required components (PostgreSQL, S3 storage, Cache, LLM)
|
||||
- Sets up database with migrations
|
||||
- Downloads AI models
|
||||
- Starts HTTP server at `http://127.0.0.1:8080`
|
||||
- Starts HTTP server at `http://127.0.0.1:9000`
|
||||
|
||||
### Run the Desktop App
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ By the end of this chapter, you will:
|
|||
./botserver
|
||||
```
|
||||
|
||||
Open `http://localhost:8080`. Start chatting. That's it.
|
||||
Open `http://localhost:9000`. Start chatting. That's it.
|
||||
|
||||
Everything installs automatically on first run—PostgreSQL, storage, cache, and your first bot.
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ Everything installs automatically on first run—PostgreSQL, storage, cache, and
|
|||
<p>Just three steps:</p>
|
||||
<p>1️⃣ Download <code>botserver</code></p>
|
||||
<p>2️⃣ Run <code>./botserver</code></p>
|
||||
<p>3️⃣ Open your browser to localhost:8080</p>
|
||||
<p>3️⃣ Open your browser to localhost:9000</p>
|
||||
<p>The bootstrap process handles everything else automatically!</p>
|
||||
<div class="wa-time">09:00</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ After botserver starts, you can immediately start chatting with your bot. No pro
|
|||
|
||||
## Just Start Talking
|
||||
|
||||
Open your browser to `http://localhost:8080` and start chatting:
|
||||
Open your browser to `http://localhost:9000` and start chatting:
|
||||
|
||||
```
|
||||
You: Hi!
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ psql $DATABASE_URL -c "SELECT version();"
|
|||
curl http://localhost:8081/v1/models
|
||||
|
||||
# Open UI
|
||||
open http://localhost:8080
|
||||
open http://localhost:9000
|
||||
```
|
||||
|
||||
## Bot Deployment
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ botserver certificates include `127.0.0.1` as a Subject Alternative Name (SAN),
|
|||
|
||||
| Component | Description | IP:Port |
|
||||
|-----------|-------------|---------|
|
||||
| api | Main botserver API | `127.0.0.1:8443` (HTTPS) / `127.0.0.1:8080` (HTTP) |
|
||||
| api | Main botserver API | `127.0.0.1:8443` (HTTPS) / `127.0.0.1:9000` (HTTP) |
|
||||
| tables | PostgreSQL database | `127.0.0.1:5432` |
|
||||
| drive | Object storage (S3-compatible) | `127.0.0.1:9000` |
|
||||
| cache | Redis cache | `127.0.0.1:6379` |
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ Installation begins by downloading and running the botserver binary. The bootstr
|
|||
|
||||
Bot deployment uses object storage buckets. Each bot receives its own bucket for file storage. Bots are deployed to the drive rather than the work folder, which is reserved for internal operations as documented in the gbapp chapter.
|
||||
|
||||
After startup, access the UI interface at `http://localhost:8080` to interact with your bots and monitor their operation.
|
||||
After startup, access the UI interface at `http://localhost:9000` to interact with your bots and monitor their operation.
|
||||
|
||||
|
||||
## Use Cases
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ Installing Cache...
|
|||
Creating bots from templates...
|
||||
default.gbai deployed
|
||||
announcements.gbai deployed
|
||||
botserver ready at http://localhost:8080
|
||||
botserver ready at http://localhost:9000
|
||||
```
|
||||
|
||||
### 3. Open Browser
|
||||
|
||||
```
|
||||
http://localhost:8080
|
||||
http://localhost:9000
|
||||
```
|
||||
|
||||
Start chatting with your bot!
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ A session is a persistent conversation container that tracks everything about an
|
|||
|
||||
### UI Interface
|
||||
|
||||
When a user opens `http://localhost:8080`, the browser receives a session token in the form of a UUID. This token is stored in localStorage for persistence across page loads. The session itself is created in PostgreSQL for durability and cached for fast access during active conversations.
|
||||
When a user opens `http://localhost:9000`, the browser receives a session token in the form of a UUID. This token is stored in localStorage for persistence across page loads. The session itself is created in PostgreSQL for durability and cached for fast access during active conversations.
|
||||
|
||||
### API Access
|
||||
|
||||
|
|
@ -22,12 +22,12 @@ Programmatic access to sessions uses the REST API. A POST request to `/api/sessi
|
|||
|
||||
```bash
|
||||
# Get new session
|
||||
curl -X POST http://localhost:8080/api/session
|
||||
curl -X POST http://localhost:9000/api/session
|
||||
# Returns: {"session_id": "uuid-here", "token": "secret-token"}
|
||||
|
||||
# Use session
|
||||
curl -H "Authorization: Bearer secret-token" \
|
||||
http://localhost:8080/api/chat
|
||||
http://localhost:9000/api/chat
|
||||
```
|
||||
|
||||
### Anonymous vs Authenticated
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ greeting,Welcome to support!
|
|||
```bash
|
||||
cp -r my-bot.gbai/ templates/
|
||||
./botserver restart
|
||||
# Visit http://localhost:8080/my-bot
|
||||
# Visit http://localhost:9000/my-bot
|
||||
```
|
||||
|
||||
### Production Server
|
||||
|
|
|
|||
|
|
@ -674,7 +674,7 @@ idleTimeout = 60 ' Return to welcome screen after inactivity
|
|||
lastActivity = NOW
|
||||
|
||||
' Initialize display
|
||||
' (Runs in browser kiosk mode at http://localhost:8088/embedded/)
|
||||
' (Runs in browser kiosk mode at http://localhost:9000/embedded/)
|
||||
|
||||
TALK welcomeMessage
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ Select based on your needs:
|
|||
|
||||
```bash
|
||||
# Templates are auto-deployed during bootstrap
|
||||
# Access at: http://localhost:8080/template-name
|
||||
# Access at: http://localhost:9000/template-name
|
||||
```
|
||||
|
||||
### 3. Customize Configuration
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ General Bots UI system built with HTMX and server-side rendering.
|
|||
## Quick Access
|
||||
|
||||
```
|
||||
http://localhost:8080 → Main interface
|
||||
http://localhost:8080/chat → Chat app
|
||||
http://localhost:8080/drive → File manager
|
||||
http://localhost:8080/console → Terminal mode
|
||||
http://localhost:9000 → Main interface
|
||||
http://localhost:9000/chat → Chat app
|
||||
http://localhost:9000/drive → File manager
|
||||
http://localhost:9000/console → Terminal mode
|
||||
```
|
||||
|
||||
## Suite Applications
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ Bot responses support full Markdown rendering:
|
|||
### WebSocket Connection
|
||||
|
||||
```
|
||||
ws://your-server:8080/ws
|
||||
ws://your-server:9000/ws
|
||||
```
|
||||
|
||||
**Message Types:**
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Default template for browser access:
|
|||
|
||||
```bash
|
||||
./botserver
|
||||
# Browse to http://localhost:8080
|
||||
# Browse to http://localhost:9000
|
||||
# Loads Suite interface
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ Console mode supports any terminal with basic text output capabilities. UTF-8 su
|
|||
|
||||
## Tips
|
||||
|
||||
Console mode operates in read-only fashion and does not accept bot commands. For interactive bot testing, use the web interface available at http://localhost:8080. The display refreshes automatically every few seconds to show current status. Output is buffered for performance to avoid slowing down the server during high activity periods.
|
||||
Console mode operates in read-only fashion and does not accept bot commands. For interactive bot testing, use the web interface available at http://localhost:9000. The display refreshes automatically every few seconds to show current status. Output is buffered for performance to avoid slowing down the server during high activity periods.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ This means:
|
|||
The widget connects via WebSocket for real-time updates:
|
||||
|
||||
```
|
||||
ws://localhost:8080/ws/dev
|
||||
ws://localhost:9000/ws/dev
|
||||
```
|
||||
|
||||
When connected:
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ Launch your preferred web browser by clicking its icon.
|
|||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 🌐 Browser [─][□][×]│
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ ← → ↻ │ https://your-company.bot:8080 │ ☆ │ │
|
||||
│ ← → ↻ │ https://your-company.bot:9000 │ ☆ │ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Loading... │
|
||||
|
|
@ -76,7 +76,7 @@ Launch your preferred web browser by clicking its icon.
|
|||
Type your General Bots address in the address bar and press **Enter**.
|
||||
|
||||
💡 **Tip**: Your URL will look something like:
|
||||
- `http://localhost:8080` (development)
|
||||
- `http://localhost:9000` (development)
|
||||
- `https://bots.yourcompany.com` (production)
|
||||
- `https://app.pragmatismo.cloud` (cloud hosted)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ The dashboard displays botserver at the center orchestrating all interactions, w
|
|||
The monitoring dashboard is the **default homepage** when accessing Suite:
|
||||
|
||||
```/dev/null/monitoring-url.txt#L1
|
||||
http://localhost:8080/monitoring
|
||||
http://localhost:9000/monitoring
|
||||
```
|
||||
|
||||
Or from within Suite:
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ Automatic dark mode activates based on system preference:
|
|||
Connection handling is simplified for reliability:
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:8080/ws');
|
||||
const ws = new WebSocket('ws://localhost:9000/ws');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
|
@ -98,7 +98,7 @@ function sendMessage() {
|
|||
The single.gbui template is perfect for embedding in existing websites:
|
||||
|
||||
```html
|
||||
<iframe src="http://localhost:8080/ui/suite/single.gbui"
|
||||
<iframe src="http://localhost:9000/ui/suite/single.gbui"
|
||||
width="400"
|
||||
height="600">
|
||||
</iframe>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ General Bots Suite is your all-in-one workspace that combines communication, pro
|
|||
### Opening the Suite
|
||||
|
||||
1. **Open your web browser** (Chrome, Firefox, Safari, or Edge)
|
||||
2. **Go to your General Bots address** (example: `http://your-company.bot:8080`)
|
||||
2. **Go to your General Bots address** (example: `http://your-company.bot:9000`)
|
||||
3. **The Suite loads automatically** - you'll see the workspace in seconds
|
||||
|
||||
### Your First Look
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ The Suite interface provides multi-application integration with seamless navigat
|
|||
|
||||
The Suite interface is best suited for enterprise deployments requiring full functionality, power users working with multiple services simultaneously, desktop application distribution via Tauri builds, and multi-service integrations where context switching between modules matters.
|
||||
|
||||
You can access the Suite interface via web at `http://localhost:8080/suite` or as a native desktop application using the `botui` Tauri app (see [BotUI Desktop](../botui/README.md)).
|
||||
You can access the Suite interface via web at `http://localhost:9000/suite` or as a native desktop application using the `botui` Tauri app (see [BotUI Desktop](../botui/README.md)).
|
||||
|
||||
## Minimal Interface
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ This lightweight interface provides core chat and basic interactions only, fast
|
|||
|
||||
The Minimal interface excels for mobile web access, embedded chatbots in external websites, low-bandwidth environments, quick access terminals and kiosks, and scenarios where simplicity matters more than features.
|
||||
|
||||
Access the Minimal interface at the root URL `http://localhost:8080` where it is served by default, explicitly at `http://localhost:8080/minimal`, or embedded via iframe or WebView in your own applications.
|
||||
Access the Minimal interface at the root URL `http://localhost:9000` where it is served by default, explicitly at `http://localhost:9000/minimal`, or embedded via iframe or WebView in your own applications.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
@ -211,11 +211,11 @@ ls -la ui/suite/
|
|||
ls -la ui/minimal/
|
||||
|
||||
# Test interfaces
|
||||
curl http://localhost:8080/
|
||||
curl http://localhost:8080/suite/
|
||||
curl http://localhost:9000/
|
||||
curl http://localhost:9000/suite/
|
||||
|
||||
# Check static file serving
|
||||
curl http://localhost:8080/js/htmx-app.js
|
||||
curl http://localhost:9000/js/htmx-app.js
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
|
|
|||
|
|
@ -4,18 +4,38 @@ Saves data to a database table using upsert (insert or update) semantics.
|
|||
|
||||
## Syntax
|
||||
|
||||
### Form 1: Save with object (classic)
|
||||
|
||||
```basic
|
||||
SAVE "table", id, data
|
||||
```
|
||||
|
||||
### Form 2: Save with variables (direct)
|
||||
|
||||
```basic
|
||||
SAVE "table", id, field1, field2, field3, ...
|
||||
```
|
||||
|
||||
The variable names are used as column names automatically.
|
||||
|
||||
## Parameters
|
||||
|
||||
### Form 1 (with object)
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| table | String | The name of the database table |
|
||||
| id | String/Number | The unique identifier for the record |
|
||||
| data | Object | A map/object containing field names and values |
|
||||
|
||||
### Form 2 (with variables)
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| table | String | The name of the database table |
|
||||
| id | String/Number | The unique identifier for the record |
|
||||
| field1, field2, ... | Any | Variable references (names become column names) |
|
||||
|
||||
## Description
|
||||
|
||||
`SAVE` performs an upsert operation:
|
||||
|
|
@ -24,9 +44,55 @@ SAVE "table", id, data
|
|||
|
||||
The `id` parameter maps to the `id` column in the table.
|
||||
|
||||
### Form 1 vs Form 2
|
||||
|
||||
**Form 1** (with object) is useful when you need custom column names or complex data structures:
|
||||
|
||||
```basic
|
||||
data = #{
|
||||
"customer_name": "João Silva",
|
||||
"email": "joao@example.com"
|
||||
}
|
||||
SAVE "customers", "CUST-001", data
|
||||
```
|
||||
|
||||
**Form 2** (with variables) is simpler - variable names become column names:
|
||||
|
||||
```basic
|
||||
customerName = "João Silva"
|
||||
email = "joao@example.com"
|
||||
phone = "+5511999887766"
|
||||
SAVE "customers", "CUST-001", customerName, email, phone
|
||||
' Creates columns: customerName, email, phone
|
||||
```
|
||||
|
||||
This eliminates the need for WITH blocks when variable names match your desired column names.
|
||||
|
||||
### Perfect for TOOL Functions
|
||||
|
||||
**This is especially useful for TOOL functions** where variables are automatically filled by user input and can be saved directly without needing WITH blocks:
|
||||
|
||||
```basic
|
||||
' TOOL function parameters - automatically filled by LLM
|
||||
PARAM nome AS STRING LIKE "João Silva" DESCRIPTION "Nome completo"
|
||||
PARAM email AS EMAIL LIKE "joao@example.com" DESCRIPTION "Email"
|
||||
PARAM telefone AS STRING LIKE "(21) 98888-8888" DESCRIPTION "Telefone"
|
||||
|
||||
' Generate unique ID
|
||||
customerId = "CUST-" + FORMAT(NOW(), "yyyyMMddHHmmss")
|
||||
|
||||
' Save directly - variable names become column names automatically!
|
||||
' No need for WITH block - just pass the variables directly
|
||||
SAVE "customers", customerId, nome, email, telefone
|
||||
|
||||
RETURN customerId
|
||||
```
|
||||
|
||||
In TOOL functions, the parameters (variables like `nome`, `email`, `telefone`) are automatically extracted from user input by the LLM. The direct SAVE syntax allows you to persist these variables immediately without manual object construction.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Save with Object
|
||||
### Basic Save with Object (Form 1)
|
||||
|
||||
```basic
|
||||
' Create data object using Rhai map syntax
|
||||
|
|
@ -40,32 +106,73 @@ data = #{
|
|||
SAVE "customers", "CUST-001", data
|
||||
```
|
||||
|
||||
### Save Order Data
|
||||
### Save with Variables - No WITH Block Needed (Form 2)
|
||||
|
||||
```basic
|
||||
' Variable names become column names automatically
|
||||
casamentoId = "CAS-20250117-1234"
|
||||
protocolo = "CAS123456"
|
||||
nomeNoivo = "Carlos Eduardo"
|
||||
nomeNoiva = "Juliana Cristina"
|
||||
telefoneNoivo = "(21) 98888-8888"
|
||||
telefoneNoiva = "(21) 97777-7777"
|
||||
emailNoivo = "carlos@example.com"
|
||||
emailNoiva = "juliana@example.com"
|
||||
tipoCasamento = "RELIGIOSO_COM_EFEITO_CIVIL"
|
||||
dataPreferencial = "2026-12-15"
|
||||
horarioPreferencial = "16:00"
|
||||
|
||||
' Save directly without WITH block
|
||||
SAVE "casamentos", casamentoId, protocolo, nomeNoivo, nomeNoiva, telefoneNoivo, telefoneNoiva, emailNoivo, emailNoiva, tipoCasamento, dataPreferencial, horarioPreferencial
|
||||
```
|
||||
|
||||
### Save Order Data (Direct Syntax - No Object)
|
||||
|
||||
```basic
|
||||
order_id = "ORD-" + FORMAT(NOW(), "YYYYMMDDHHmmss")
|
||||
customer_id = "CUST-001"
|
||||
customer_name = "João Silva"
|
||||
total = 150.50
|
||||
status = "pending"
|
||||
|
||||
order_data = #{
|
||||
"customer_id": customer_id,
|
||||
"customer_name": customer_name,
|
||||
"total": total,
|
||||
"status": "pending",
|
||||
"created_at": NOW()
|
||||
}
|
||||
|
||||
SAVE "orders", order_id, order_data
|
||||
' Save directly - variable names become columns
|
||||
SAVE "orders", order_id, customer_id, customer_name, total, status
|
||||
|
||||
TALK "Order " + order_id + " saved successfully!"
|
||||
```
|
||||
|
||||
### Save Event Registration
|
||||
|
||||
```basic
|
||||
' Event registration form data
|
||||
eventId = "EVT-" + FORMAT(NOW(), "YYYYMMDDHHmmss")
|
||||
nome = "Maria Santos"
|
||||
email = "maria@example.com"
|
||||
telefone = "(11) 91234-5678"
|
||||
dataEvento = "2025-03-15"
|
||||
quantidadePessoas = 3
|
||||
observacoes = "Precisa de cadeira de rodas"
|
||||
|
||||
' Direct save - no WITH block needed
|
||||
SAVE "eventos", eventId, nome, email, telefone, dataEvento, quantidadePessoas, observacoes
|
||||
|
||||
TALK "Inscrição confirmada! ID: " + eventId
|
||||
```
|
||||
|
||||
### Update Existing Record
|
||||
|
||||
```basic
|
||||
' If order exists, this updates it; otherwise creates it
|
||||
order_id = "ORD-20250117-0001"
|
||||
status = "shipped"
|
||||
shipped_at = NOW()
|
||||
tracking_number = "TRACK123456"
|
||||
|
||||
' Use object for updates to specific columns
|
||||
update_data = #{
|
||||
"status": "shipped",
|
||||
"shipped_at": NOW(),
|
||||
"tracking_number": tracking
|
||||
"status": status,
|
||||
"shipped_at": shipped_at,
|
||||
"tracking_number": tracking_number
|
||||
}
|
||||
|
||||
SAVE "orders", order_id, update_data
|
||||
|
|
@ -79,15 +186,10 @@ WEBHOOK "new-customer"
|
|||
customer_id = "CUST-" + FORMAT(NOW(), "YYYYMMDDHHmmss")
|
||||
phone = body.phone
|
||||
name = body.name
|
||||
source = "webhook"
|
||||
|
||||
customer_data = #{
|
||||
"name": name,
|
||||
"phone": phone,
|
||||
"source": "webhook",
|
||||
"created_at": NOW()
|
||||
}
|
||||
|
||||
SAVE "customers", customer_id, customer_data
|
||||
' Direct save with variables
|
||||
SAVE "customers", customer_id, phone, name, source
|
||||
|
||||
' Notify via WhatsApp
|
||||
TALK TO "whatsapp:" + phone, "Welcome " + name + "! Your account has been created."
|
||||
|
|
@ -121,12 +223,11 @@ WEBHOOK "create-order"
|
|||
|
||||
' Save order
|
||||
order_id = body.order_id
|
||||
order_data = #{
|
||||
"customer_id": body.customer_id,
|
||||
"total": body.total,
|
||||
"status": "pending"
|
||||
}
|
||||
SAVE "orders", order_id, order_data
|
||||
customer_id = body.customer_id
|
||||
total = body.total
|
||||
status = "pending"
|
||||
|
||||
SAVE "orders", order_id, customer_id, total, status
|
||||
|
||||
' Save each line item
|
||||
FOR EACH item IN body.items
|
||||
|
|
@ -146,6 +247,27 @@ TALK TO "whatsapp:" + body.customer_phone, "Order #" + order_id + " confirmed!"
|
|||
result_status = "ok"
|
||||
```
|
||||
|
||||
### Comparison: WITH Block vs Direct Syntax
|
||||
|
||||
**Old way (WITH block):**
|
||||
```basic
|
||||
WITH casamento
|
||||
id = casamentoId
|
||||
protocolo = protocolo
|
||||
noivo = nomeNoivo
|
||||
noiva = nomeNoiva
|
||||
END WITH
|
||||
SAVE "casamentos", casamento
|
||||
```
|
||||
|
||||
**New way (direct):**
|
||||
```basic
|
||||
' Variable names become column names automatically
|
||||
SAVE "casamentos", casamentoId, protocolo, nomeNoivo, nomeNoiva
|
||||
```
|
||||
|
||||
The direct syntax is cleaner and avoids the intermediate object creation. Use it when your variable names match your desired column names.
|
||||
|
||||
## Return Value
|
||||
|
||||
Returns an object with:
|
||||
|
|
|
|||
|
|
@ -4,11 +4,27 @@ Send email messages.
|
|||
|
||||
## Syntax
|
||||
|
||||
### Single Line
|
||||
|
||||
```basic
|
||||
SEND MAIL to, subject, body
|
||||
SEND MAIL to, subject, body USING "account@example.com"
|
||||
```
|
||||
|
||||
### Multi-Line Block with Variable Substitution
|
||||
|
||||
```basic
|
||||
BEGIN MAIL recipient
|
||||
Subject: Email subject here
|
||||
|
||||
Dear ${customerName},
|
||||
|
||||
Your order ${orderId} is ready.
|
||||
|
||||
Thank you!
|
||||
END MAIL
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|
|
@ -17,6 +33,7 @@ SEND MAIL to, subject, body USING "account@example.com"
|
|||
| `subject` | String | Email subject line |
|
||||
| `body` | String | Email body (plain text or HTML) |
|
||||
| `account` | String | (Optional) Connected account to send through |
|
||||
| `${variable}` | Expression | Variable substitution within MAIL blocks |
|
||||
|
||||
## Description
|
||||
|
||||
|
|
@ -25,6 +42,190 @@ The `SEND MAIL` keyword sends emails using either:
|
|||
1. **Default SMTP** - Configuration from `config.csv`
|
||||
2. **Connected Account** - Send through Gmail, Outlook, etc. configured in Sources app
|
||||
|
||||
## BEGIN MAIL / END MAIL Blocks
|
||||
|
||||
The `BEGIN MAIL / END MAIL` block syntax allows you to write elegant multi-line emails with automatic variable substitution using `${variable}` syntax.
|
||||
|
||||
### Syntax
|
||||
|
||||
```basic
|
||||
BEGIN MAIL recipient
|
||||
Subject: Email subject ${variable}
|
||||
|
||||
Dear ${customerName},
|
||||
|
||||
Your order ${orderId} has been shipped.
|
||||
|
||||
Tracking: ${trackingNumber}
|
||||
|
||||
Best regards,
|
||||
The Team
|
||||
END MAIL
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **First line after `BEGIN MAIL`**: Should contain the email recipient
|
||||
2. **Line starting with `Subject:`**: Email subject line (supports `${variable}`)
|
||||
3. **Blank line after subject**: Separates subject from body
|
||||
4. **Body lines**: Email content with automatic `${variable}` substitution
|
||||
5. **Each line** is converted to string concatenation with proper newline handling
|
||||
|
||||
**Input:**
|
||||
```basic
|
||||
nome = "João"
|
||||
pedido = "12345"
|
||||
|
||||
BEGIN MAIL "cliente@example.com"
|
||||
Subject: Confirmação do Pedido ${pedido}
|
||||
|
||||
Olá ${nome},
|
||||
|
||||
Seu pedido foi confirmado!
|
||||
|
||||
Atenciosamente,
|
||||
Equipe de Vendas
|
||||
END MAIL
|
||||
```
|
||||
|
||||
**Converted to:**
|
||||
```basic
|
||||
SEND MAIL "cliente@example.com", "Confirmação do Pedido 12345", "Olá " + nome + ",\n\nSeu pedido foi confirmado!\n\nAtenciosamente,\nEquipe de Vendas"
|
||||
```
|
||||
|
||||
### Variable Substitution Rules
|
||||
|
||||
- `${variableName}` - Replaced with the variable value
|
||||
- `${FUNCTION(args)}` - Function calls are evaluated and substituted
|
||||
- Plain text without `${}` is treated as a string literal
|
||||
- Special characters like `$` (not followed by `{`) are preserved
|
||||
- Newlines are preserved as `\n` in the final email body
|
||||
|
||||
### Examples
|
||||
|
||||
#### Simple Email
|
||||
|
||||
```basic
|
||||
email = "customer@example.com"
|
||||
nome = "Maria"
|
||||
|
||||
BEGIN MAIL email
|
||||
Subject: Bem-vindo ao nosso serviço!
|
||||
|
||||
Olá ${nome},
|
||||
|
||||
Obrigado por se cadastrar!
|
||||
|
||||
Atenciosamente,
|
||||
Equipe
|
||||
END MAIL
|
||||
```
|
||||
|
||||
#### With Function Calls
|
||||
|
||||
```basic
|
||||
BEGIN MAIL "cliente@empresa.com"
|
||||
Subject: Pedido ${pedidoId} - Confirmação
|
||||
|
||||
Prezado ${nomeCliente},
|
||||
|
||||
Confirmamos seu pedido #${pedidoId} no valor de ${FORMAT(total, "currency")}.
|
||||
|
||||
Entrega prevista para: ${FORMAT(dataEntrega, "dd/MM/yyyy")}
|
||||
|
||||
Atenciosamente,
|
||||
Departamento de Vendas
|
||||
END MAIL
|
||||
```
|
||||
|
||||
#### HTML Email
|
||||
|
||||
```basic
|
||||
BEGIN MAIL "cliente@exemplo.com"
|
||||
Subject: Seu pedido foi enviado!
|
||||
|
||||
<h1>Confirmação de Pedido</h1>
|
||||
|
||||
<p>Olá ${nome},</p>
|
||||
<p>Seu pedido <strong>${pedidoId}</strong> foi enviado com sucesso!</p>
|
||||
|
||||
<p>Valor: <em>${FORMAT(valor, "currency")}</em></p>
|
||||
|
||||
<p>Atenciosamente,<br>Loja Virtual</p>
|
||||
END MAIL
|
||||
```
|
||||
|
||||
### Real-World Example: Wedding Confirmation
|
||||
|
||||
```basic
|
||||
PARAM nomeNoivo AS STRING LIKE "Carlos" DESCRIPTION "Nome do noivo"
|
||||
PARAM nomeNoiva AS STRING LIKE "Ana" DESCRIPTION "Nome da noiva"
|
||||
PARAM emailNoivo AS EMAIL LIKE "noivo@example.com" DESCRIPTION "Email do noivo"
|
||||
PARAM emailNoiva AS EMAIL LIKE "noiva@example.com" DESCRIPTION "Email da noiva"
|
||||
PARAM protocolo AS STRING LIKE "CAS123456" DESCRIPTION "Protocolo"
|
||||
|
||||
casamentoId = "CAS-" + FORMAT(NOW(), "yyyyMMddHHmmss")
|
||||
tipoTexto = "Religioso Simples"
|
||||
|
||||
BEGIN MAIL emailNoivo
|
||||
Subject: Confirmação de Casamento - Protocolo ${protocolo}
|
||||
|
||||
Queridos ${nomeNoivo} e ${nomeNoiva},
|
||||
|
||||
Parabéns pelo compromisso de amor que estão assumindo! Recebemos a solicitação de casamento no Santuário Cristo Redentor.
|
||||
|
||||
DADOS DA SOLICITAÇÃO:
|
||||
Protocolo: ${protocolo}
|
||||
ID: ${casamentoId}
|
||||
Noivo: ${nomeNoivo}
|
||||
Noiva: ${nomeNoiva}
|
||||
Tipo: ${tipoTexto}
|
||||
|
||||
Nossa equipe verificará a disponibilidade e enviará todas as instruções necessárias em breve.
|
||||
|
||||
Que Deus abençoe a união de vocês!
|
||||
|
||||
Atenciosamente,
|
||||
Secretaria do Santuário Cristo Redentor
|
||||
Tel: (21) 4101-0770 | WhatsApp: (21) 99566-5883
|
||||
END MAIL
|
||||
```
|
||||
|
||||
### Multiple Recipients
|
||||
|
||||
Send the same email to multiple people:
|
||||
|
||||
```basic
|
||||
BEGIN MAIL "team1@company.com"
|
||||
Subject: Meeting Reminder
|
||||
|
||||
Team meeting tomorrow at 3 PM.
|
||||
END MAIL
|
||||
|
||||
BEGIN MAIL "team2@company.com"
|
||||
Subject: Meeting Reminder
|
||||
|
||||
Team meeting tomorrow at 3 PM.
|
||||
END MAIL
|
||||
```
|
||||
|
||||
Or use comma-separated recipients:
|
||||
|
||||
```basic
|
||||
recipients = "john@company.com, jane@company.com, bob@company.com"
|
||||
SEND MAIL recipients, "Meeting Update", "Meeting rescheduled to 4 PM"
|
||||
```
|
||||
|
||||
### Advantages
|
||||
|
||||
1. **Cleaner Syntax** - No more repetitive string concatenation for email body
|
||||
2. **Easier to Read** - Multi-line emails are natural to write and maintain
|
||||
3. **Template-Like** - Write emails like templates with `${variable}` placeholders
|
||||
4. **Automatic Newlines** - Blank lines in the block become `\n` in the email
|
||||
5. **Perfect for TOOL Functions** - Variables are automatically filled by user input
|
||||
|
||||
## Examples
|
||||
|
||||
## Configuration
|
||||
|
||||
Default SMTP in `config.csv`:
|
||||
|
|
|
|||
|
|
@ -4,18 +4,31 @@ Sends a message to the current conversation or to a specific recipient on any su
|
|||
|
||||
## Syntax
|
||||
|
||||
### Single Message
|
||||
|
||||
```basic
|
||||
TALK message
|
||||
|
||||
TALK TO recipient, message
|
||||
```
|
||||
|
||||
### Multi-Line Block with Variable Substitution
|
||||
|
||||
```basic
|
||||
BEGIN TALK
|
||||
Line 1 with ${variable}
|
||||
Line 2 with ${anotherVariable}
|
||||
Plain text line
|
||||
END TALK
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| message | String | The message to send |
|
||||
| recipient | String | Channel and address in format `channel:address` |
|
||||
| ${variable} | Expression | Variable substitution within TALK blocks |
|
||||
|
||||
## Description
|
||||
|
||||
|
|
@ -23,6 +36,151 @@ TALK TO recipient, message
|
|||
|
||||
- **TALK message** - Sends to the current conversation (web chat, WhatsApp, etc.)
|
||||
- **TALK TO recipient, message** - Sends to a specific recipient on any channel
|
||||
- **BEGIN TALK / END TALK** - Multi-line block with automatic variable substitution
|
||||
|
||||
## BEGIN TALK / END TALK Blocks
|
||||
|
||||
The `BEGIN TALK / END TALK` block syntax allows you to write multiple messages with automatic variable substitution using `${variable}` syntax.
|
||||
|
||||
### Syntax
|
||||
|
||||
```basic
|
||||
BEGIN TALK
|
||||
Hello ${name}!
|
||||
Your order ${orderId} is confirmed.
|
||||
Total: ${FORMAT(total, "currency")}
|
||||
Thank you for your purchase!
|
||||
END TALK
|
||||
```
|
||||
|
||||
Each line within the block becomes a separate `TALK` statement. The `${variable}` syntax is automatically converted to string concatenation.
|
||||
|
||||
### How It Works
|
||||
|
||||
**Input:**
|
||||
```basic
|
||||
nomeNoivo = "Carlos"
|
||||
protocolo = "CAS123456"
|
||||
|
||||
BEGIN TALK
|
||||
Solicitacao de Casamento enviada com sucesso!
|
||||
PROTOCOLO: ${protocolo}
|
||||
Noivo: ${nomeNoivo}
|
||||
END TALK
|
||||
```
|
||||
|
||||
**Converted to:**
|
||||
```basic
|
||||
TALK "Solicitacao de Casamento enviada com sucesso!"
|
||||
TALK "PROTOCOLO: " + protocolo
|
||||
TALK "Noivo: " + nomeNoivo
|
||||
```
|
||||
|
||||
### Variable Substitution Rules
|
||||
|
||||
- `${variableName}` - Replaced with the variable value using string concatenation
|
||||
- `${FUNCTION(args)}` - Function calls are evaluated and substituted
|
||||
- Plain text without `${}` is treated as a string literal
|
||||
- Special characters like `$` (not followed by `{`) are preserved as-is
|
||||
|
||||
### Examples
|
||||
|
||||
#### Simple Substitution
|
||||
|
||||
```basic
|
||||
nome = "João"
|
||||
idade = 30
|
||||
|
||||
BEGIN TALK
|
||||
Olá ${nome}!
|
||||
Você tem ${idade} anos.
|
||||
END TALK
|
||||
```
|
||||
|
||||
**Equivalent to:**
|
||||
```basic
|
||||
TALK "Olá " + nome + "!"
|
||||
TALK "Você tem " + idade + " anos."
|
||||
```
|
||||
|
||||
#### With Function Calls
|
||||
|
||||
```basic
|
||||
total = 299.90
|
||||
numero = 42
|
||||
|
||||
BEGIN TALK
|
||||
Seu pedido: ${numero}
|
||||
Total: ${FORMAT(total, "currency")}
|
||||
Obrigado pela preferência!
|
||||
END TALK
|
||||
```
|
||||
|
||||
#### Mixed Content
|
||||
|
||||
```basic
|
||||
nome = "Maria"
|
||||
codigo = "PROMO2024"
|
||||
desconto = 20
|
||||
|
||||
BEGIN TALK
|
||||
🎉 Oferta Especial para ${nome}!
|
||||
|
||||
Use o código: ${codigo}
|
||||
Desconto de ${desconto}%
|
||||
|
||||
Aproveite!
|
||||
END TALK
|
||||
```
|
||||
|
||||
### Real-World Example: Wedding Confirmation
|
||||
|
||||
```basic
|
||||
PARAM nomeNoivo AS STRING LIKE "Carlos" DESCRIPTION "Nome do noivo"
|
||||
PARAM nomeNoiva AS STRING LIKE "Ana" DESCRIPTION "Nome da noiva"
|
||||
PARAM protocolo AS STRING LIKE "CAS123456" DESCRIPTION "Protocolo"
|
||||
PARAM dataCasamento AS DATE LIKE "2026-12-15" DESCRIPTION "Data do casamento"
|
||||
|
||||
casamentoId = "CAS-" + FORMAT(NOW(), "yyyyMMddHHmmss")
|
||||
dataDisplay = FORMAT(dataCasamento, "dd/MM/yyyy")
|
||||
|
||||
BEGIN TALK
|
||||
✅ Solicitação de Casamento enviada com sucesso!
|
||||
|
||||
Protocolo: ${protocolo}
|
||||
ID: ${casamentoId}
|
||||
Noivo: ${nomeNoivo}
|
||||
Noiva: ${nomeNoiva}
|
||||
Data: ${dataDisplay}
|
||||
|
||||
Status: Aguardando verificação de disponibilidade
|
||||
Contato: (21) 4101-0770
|
||||
END TALK
|
||||
```
|
||||
|
||||
This is much cleaner than writing individual TALK statements with manual concatenation:
|
||||
|
||||
**Old way:**
|
||||
```basic
|
||||
TALK "Solicitacao de Casamento enviada com sucesso!"
|
||||
TALK "Protocolo: " + protocolo
|
||||
TALK "ID: " + casamentoId
|
||||
TALK "Noivo: " + nomeNoivo
|
||||
TALK "Noiva: " + nomeNoiva
|
||||
TALK "Data: " + dataDisplay
|
||||
TALK "Status: Aguardando verificacao de disponibilidade"
|
||||
TALK "Contato: (21) 4101-0770"
|
||||
```
|
||||
|
||||
### Advantages
|
||||
|
||||
1. **Cleaner Syntax** - No more repetitive `TALK` statements and `+` concatenations
|
||||
2. **Easier to Read** - Multi-line messages are more natural to write
|
||||
3. **Less Error-Prone** - Automatic substitution reduces typos in variable names
|
||||
4. **Template-Like** - Write messages like templates with `${variable}` placeholders
|
||||
5. **Perfect for TOOL Functions** - Variables are automatically filled by user input
|
||||
|
||||
## TALK - Current Conversation
|
||||
|
||||
## TALK - Current Conversation
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ This script works identically whether the user is:
|
|||
## Supported Channels
|
||||
|
||||
### Web (Default)
|
||||
The primary channel. Users access via browser at `http://localhost:8080`.
|
||||
The primary channel. Users access via browser at `http://localhost:9000`.
|
||||
|
||||
### WhatsApp Business
|
||||
Requires WhatsApp Business API configuration. Messages are automatically formatted for WhatsApp's constraints.
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ bot.example.com {
|
|||
|
||||
# WebSocket (sticky sessions)
|
||||
handle /ws* {
|
||||
reverse_proxy botserver-1:8080 botserver-2:8080 {
|
||||
reverse_proxy botserver-1:9000 botserver-2:9000 {
|
||||
lb_policy cookie
|
||||
health_uri /api/health
|
||||
health_interval 10s
|
||||
|
|
@ -301,7 +301,7 @@ bot.example.com {
|
|||
|
||||
# API (round robin)
|
||||
handle /api/* {
|
||||
reverse_proxy botserver-1:8080 botserver-2:8080 {
|
||||
reverse_proxy botserver-1:9000 botserver-2:9000 {
|
||||
lb_policy round_robin
|
||||
fail_duration 30s
|
||||
}
|
||||
|
|
@ -400,7 +400,7 @@ VAULT_ADDR=https://localhost:8200
|
|||
VAULT_TOKEN=hvs.your-token-here
|
||||
|
||||
# Directory for user auth (Zitadel)
|
||||
DIRECTORY_URL=https://localhost:8080
|
||||
DIRECTORY_URL=https://localhost:9000
|
||||
DIRECTORY_CLIENT_ID=your-client-id
|
||||
DIRECTORY_CLIENT_SECRET=your-client-secret
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ scale-rule-queue-action,up
|
|||
for i in {2..5}; do
|
||||
lxc launch images:debian/12 botserver-$i
|
||||
lxc config device add botserver-$i port-$((8080+i)) proxy \
|
||||
listen=tcp:0.0.0.0:$((8080+i)) connect=tcp:127.0.0.1:8080
|
||||
listen=tcp:0.0.0.0:$((8080+i)) connect=tcp:127.0.0.1:9000
|
||||
done
|
||||
```
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ update_load_balancer() {
|
|||
upstreams=""
|
||||
for container in $(lxc list -c n --format csv | grep "^botserver-"); do
|
||||
ip=$(lxc list $container -c 4 --format csv | cut -d' ' -f1)
|
||||
upstreams="$upstreams\n to $ip:8080"
|
||||
upstreams="$upstreams\n to $ip:9000"
|
||||
done
|
||||
|
||||
# Update Caddy config
|
||||
|
|
@ -268,12 +268,12 @@ bot.example.com {
|
|||
|
||||
# Health check endpoint (no load balancing)
|
||||
handle /api/health {
|
||||
reverse_proxy localhost:8080
|
||||
reverse_proxy localhost:9000
|
||||
}
|
||||
|
||||
# WebSocket connections (sticky sessions)
|
||||
handle /ws* {
|
||||
reverse_proxy botserver-1:8080 botserver-2:8080 botserver-3:8080 {
|
||||
reverse_proxy botserver-1:9000 botserver-2:9000 botserver-3:9000 {
|
||||
lb_policy cookie
|
||||
lb_try_duration 5s
|
||||
health_uri /api/health
|
||||
|
|
@ -284,7 +284,7 @@ bot.example.com {
|
|||
|
||||
# API requests (round robin)
|
||||
handle /api/* {
|
||||
reverse_proxy botserver-1:8080 botserver-2:8080 botserver-3:8080 {
|
||||
reverse_proxy botserver-1:9000 botserver-2:9000 botserver-3:9000 {
|
||||
lb_policy round_robin
|
||||
lb_try_duration 5s
|
||||
health_uri /api/health
|
||||
|
|
@ -295,7 +295,7 @@ bot.example.com {
|
|||
|
||||
# Static files (any instance)
|
||||
handle {
|
||||
reverse_proxy botserver-1:8080 botserver-2:8080 botserver-3:8080 {
|
||||
reverse_proxy botserver-1:9000 botserver-2:9000 botserver-3:9000 {
|
||||
lb_policy first
|
||||
}
|
||||
}
|
||||
|
|
@ -353,7 +353,7 @@ bot.example.com {
|
|||
window 1m
|
||||
}
|
||||
}
|
||||
reverse_proxy botserver:8080
|
||||
reverse_proxy botserver:9000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ Use the Vault CLI or API:
|
|||
```bash
|
||||
# Directory (Zitadel) - includes URL, no longer in .env
|
||||
vault kv put gbo/directory \
|
||||
url=https://localhost:8080 \
|
||||
url=https://localhost:9000 \
|
||||
project_id=your-project-id \
|
||||
client_id=your-client-id \
|
||||
client_secret=your-client-secret
|
||||
|
|
@ -506,7 +506,7 @@ If you're currently using environment variables:
|
|||
```bash
|
||||
# .env - TOO MANY SECRETS!
|
||||
DATABASE_URL=postgres://user:password@localhost/db
|
||||
DIRECTORY_URL=https://localhost:8080
|
||||
DIRECTORY_URL=https://localhost:9000
|
||||
DIRECTORY_CLIENT_ID=your-client-id
|
||||
DIRECTORY_CLIENT_SECRET=your-client-secret
|
||||
REDIS_PASSWORD=redis-secret
|
||||
|
|
@ -528,7 +528,7 @@ VAULT_TOKEN=hvs.xxxxx
|
|||
```bash
|
||||
# EVERYTHING in Vault
|
||||
vault kv put gbo/directory \
|
||||
url=https://localhost:8080 \
|
||||
url=https://localhost:9000 \
|
||||
project_id=12345 \
|
||||
client_id=xxx \
|
||||
client_secret=xxx
|
||||
|
|
@ -579,7 +579,7 @@ fi
|
|||
|
||||
# Store everything in Vault
|
||||
vault kv put gbo/directory \
|
||||
url="${DIRECTORY_URL:-https://localhost:8080}" \
|
||||
url="${DIRECTORY_URL:-https://localhost:9000}" \
|
||||
project_id="${DIRECTORY_PROJECT_ID:-}" \
|
||||
client_id="${ZITADEL_CLIENT_ID:-}" \
|
||||
client_secret="${ZITADEL_CLIENT_SECRET:-}"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/admin
|
||||
http://localhost:9000/api/v1/admin
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/ai
|
||||
http://localhost:9000/api/v1/ai
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/analytics
|
||||
http://localhost:9000/api/v1/analytics
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/backup
|
||||
http://localhost:9000/api/v1/backup
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/compliance
|
||||
http://localhost:9000/api/v1/compliance
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ The Document Processing API enables:
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/documents
|
||||
http://localhost:9000/api/v1/documents
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
@ -47,7 +47,7 @@ curl -X POST \
|
|||
-H "Authorization: Bearer token123" \
|
||||
-F "file=@document.pdf" \
|
||||
-F 'process_options={"extract_text":true,"extract_metadata":true}' \
|
||||
http://localhost:8080/api/v1/documents/upload
|
||||
http://localhost:9000/api/v1/documents/upload
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
|
@ -532,7 +532,7 @@ import requests
|
|||
# Upload and process document
|
||||
with open('document.pdf', 'rb') as f:
|
||||
response = requests.post(
|
||||
'http://localhost:8080/api/v1/documents/upload',
|
||||
'http://localhost:9000/api/v1/documents/upload',
|
||||
headers={'Authorization': 'Bearer token123'},
|
||||
files={'file': f},
|
||||
data={'process_options': '{"extract_text": true}'}
|
||||
|
|
@ -542,7 +542,7 @@ document_id = response.json()['document_id']
|
|||
|
||||
# Get extracted text
|
||||
text_response = requests.get(
|
||||
f'http://localhost:8080/api/v1/documents/{document_id}/text',
|
||||
f'http://localhost:9000/api/v1/documents/{document_id}/text',
|
||||
headers={'Authorization': 'Bearer token123'}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ async function authenticate() {
|
|||
**cURL:**
|
||||
```bash
|
||||
# Session validation
|
||||
curl -X GET http://localhost:8080/auth/validate \
|
||||
curl -X GET http://localhost:9000/auth/validate \
|
||||
-H "Authorization: Bearer YOUR_SESSION_TOKEN"
|
||||
```
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ async function createGroup() {
|
|||
import requests
|
||||
|
||||
def create_group():
|
||||
url = "http://localhost:8080/api/groups/create"
|
||||
url = "http://localhost:9000/api/groups/create"
|
||||
headers = {
|
||||
"Authorization": "Bearer YOUR_TOKEN",
|
||||
"Content-Type": "application/json"
|
||||
|
|
@ -96,7 +96,7 @@ async function addMember(groupId, userId) {
|
|||
|
||||
**cURL:**
|
||||
```bash
|
||||
curl -X GET http://localhost:8080/api/admin/system/status \
|
||||
curl -X GET http://localhost:9000/api/admin/system/status \
|
||||
-H "Authorization: Bearer ADMIN_TOKEN"
|
||||
```
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ import (
|
|||
func getSystemStatus(token string) {
|
||||
client := &http.Client{}
|
||||
req, _ := http.NewRequest("GET",
|
||||
"http://localhost:8080/api/admin/system/status", nil)
|
||||
"http://localhost:9000/api/admin/system/status", nil)
|
||||
req.Header.Add("Authorization", "Bearer " + token)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
|
@ -164,7 +164,7 @@ class BotChat {
|
|||
}
|
||||
|
||||
connect() {
|
||||
this.ws = new WebSocket('ws://localhost:8080/ws');
|
||||
this.ws = new WebSocket('ws://localhost:9000/ws');
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('Connected to bot');
|
||||
|
|
@ -370,7 +370,7 @@ function handleConversationCompleted(conversationData) {
|
|||
|
||||
1. Import the API collection (when available)
|
||||
2. Set environment variables for:
|
||||
- `base_url`: http://localhost:8080
|
||||
- `base_url`: http://localhost:9000
|
||||
- `token`: Your session token
|
||||
3. Run requests individually or as collection
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ The Group Membership API enables:
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/groups
|
||||
http://localhost:9000/api/v1/groups
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/ml
|
||||
http://localhost:9000/api/v1/ml
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/monitoring
|
||||
http://localhost:9000/api/v1/monitoring
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ The Notifications API enables:
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/notifications
|
||||
http://localhost:9000/api/v1/notifications
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
@ -446,7 +446,7 @@ curl -X POST \
|
|||
"title": "Hello",
|
||||
"message": "This is a test notification"
|
||||
}' \
|
||||
http://localhost:8080/api/v1/notifications/send
|
||||
http://localhost:9000/api/v1/notifications/send
|
||||
```
|
||||
|
||||
### Schedule Broadcast
|
||||
|
|
@ -463,7 +463,7 @@ curl -X POST \
|
|||
},
|
||||
"schedule": "2024-01-20T02:00:00Z"
|
||||
}' \
|
||||
http://localhost:8080/api/v1/notifications/broadcast
|
||||
http://localhost:9000/api/v1/notifications/broadcast
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/reports
|
||||
http://localhost:9000/api/v1/reports
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ This API is on the development roadmap. The endpoints documented below represent
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/security
|
||||
http://localhost:9000/api/v1/security
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ The Storage API allows you to:
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/storage
|
||||
http://localhost:9000/api/v1/storage
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
@ -268,7 +268,7 @@ curl -X PUT \
|
|||
-H "Authorization: Bearer token123" \
|
||||
-H "Content-Type: application/pdf" \
|
||||
--data-binary @document.pdf \
|
||||
http://localhost:8080/api/v1/storage/buckets/mybot.gbai/objects/docs/manual.pdf
|
||||
http://localhost:9000/api/v1/storage/buckets/mybot.gbai/objects/docs/manual.pdf
|
||||
```
|
||||
|
||||
### Download File
|
||||
|
|
@ -276,7 +276,7 @@ curl -X PUT \
|
|||
```bash
|
||||
curl -X GET \
|
||||
-H "Authorization: Bearer token123" \
|
||||
http://localhost:8080/api/v1/storage/buckets/mybot.gbai/objects/docs/manual.pdf \
|
||||
http://localhost:9000/api/v1/storage/buckets/mybot.gbai/objects/docs/manual.pdf \
|
||||
-o downloaded.pdf
|
||||
```
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ curl -X GET \
|
|||
```bash
|
||||
curl -X GET \
|
||||
-H "Authorization: Bearer token123" \
|
||||
"http://localhost:8080/api/v1/storage/buckets/mybot.gbai/objects?prefix=docs/"
|
||||
"http://localhost:9000/api/v1/storage/buckets/mybot.gbai/objects?prefix=docs/"
|
||||
```
|
||||
|
||||
## Storage Organization
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ The Tasks API enables:
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/tasks
|
||||
http://localhost:9000/api/v1/tasks
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ The User Security API enables:
|
|||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8080/api/v1/security
|
||||
http://localhost:9000/api/v1/security
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ Register new user (if self-registration enabled).
|
|||
|
||||
For full user management, access Zitadel admin console:
|
||||
|
||||
1. **Access Console**: `http://localhost:8080` (or your Zitadel URL)
|
||||
1. **Access Console**: `http://localhost:9000` (or your Zitadel URL)
|
||||
2. **Create Users**: Organization → Users → Add
|
||||
3. **Manage Roles**: Users → Select User → Authorizations
|
||||
4. **Reset Passwords**: Users → Select User → Actions → Reset Password
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ Run General Bots on your own infrastructure with single binary deployment, conta
|
|||
./botserver
|
||||
```
|
||||
|
||||
Access at `http://localhost:8080` and start building.
|
||||
Access at `http://localhost:9000` and start building.
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ app.example.com {
|
|||
| ⚠️ | Password Rotation | Directory | HIPAA | Configure 90-day rotation policy |
|
||||
| 📝 | Access Reviews | Directory | All | Quarterly manual review of user permissions |
|
||||
|
||||
**Configuration**: Directory Admin Console (`http://localhost:8080`)
|
||||
**Configuration**: Directory Admin Console (`http://localhost:9000`)
|
||||
|
||||
**Key Settings**:
|
||||
- Password min length: 12 characters
|
||||
|
|
@ -206,7 +206,7 @@ directory = "oidc"
|
|||
|
||||
[directory."oidc"]
|
||||
type = "oidc"
|
||||
issuer = "http://localhost:8080"
|
||||
issuer = "http://localhost:9000"
|
||||
```
|
||||
|
||||
**DNS Records**:
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ cargo test --all
|
|||
RUSTFLAGS="-Z sanitizer=address" cargo +nightly test
|
||||
|
||||
# Verify rate limiting
|
||||
curl -X POST http://localhost:8080/api/test \
|
||||
curl -X POST http://localhost:9000/api/test \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{}' \
|
||||
--parallel --parallel-max 1000
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ Document WebSocket protocols with connection details, message formats for both d
|
|||
|
||||
### Connection
|
||||
```
|
||||
ws://localhost:8080/ws
|
||||
ws://localhost:9000/ws
|
||||
```
|
||||
|
||||
### Message Format
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ return {
|
|||
'botserver/nvim-botserver',
|
||||
config = function()
|
||||
require('botserver').setup({
|
||||
server_url = 'http://localhost:8080',
|
||||
server_url = 'http://localhost:9000',
|
||||
default_bot = 'edu'
|
||||
})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ cargo test --all-features
|
|||
|
||||
3. **Port Already in Use**
|
||||
- Change SERVER_PORT in .env
|
||||
- Kill existing process: `lsof -i :8080`
|
||||
- Kill existing process: `lsof -i :9000`
|
||||
|
||||
4. **Compilation Errors**
|
||||
- Update Rust: `rustup update`
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ cd botserver
|
|||
|
||||
Open in your browser:
|
||||
```
|
||||
http://mybot.local:8088
|
||||
http://mybot.local:9000
|
||||
```
|
||||
|
||||
## Common Beginner Mistakes
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ wget https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/
|
|||
--threads 4
|
||||
|
||||
# Verify
|
||||
curl http://localhost:8080/v1/models
|
||||
curl http://localhost:9000/v1/models
|
||||
```
|
||||
|
||||
### Systemd Service
|
||||
|
|
@ -127,7 +127,7 @@ sudo systemctl start llama-server
|
|||
```env
|
||||
# Use local llama.cpp
|
||||
LLM_PROVIDER=llamacpp
|
||||
LLM_API_URL=http://127.0.0.1:8080
|
||||
LLM_API_URL=http://127.0.0.1:9000
|
||||
LLM_MODEL=tinyllama
|
||||
|
||||
# Memory limits
|
||||
|
|
@ -263,7 +263,7 @@ llama.cpp exposes an OpenAI-compatible API:
|
|||
### Chat Completion
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/v1/chat/completions \
|
||||
curl http://localhost:9000/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "tinyllama",
|
||||
|
|
@ -277,7 +277,7 @@ curl http://localhost:8080/v1/chat/completions \
|
|||
### Streaming
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/v1/chat/completions \
|
||||
curl http://localhost:9000/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "tinyllama",
|
||||
|
|
@ -289,8 +289,8 @@ curl http://localhost:8080/v1/chat/completions \
|
|||
### Health Check
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
curl http://localhost:8080/v1/models
|
||||
curl http://localhost:9000/health
|
||||
curl http://localhost:9000/v1/models
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ BotDevice can work offline with local LLM:
|
|||
1. Install botserver on the device (see [Local LLM](./local-llm.md))
|
||||
2. Configure to use localhost:
|
||||
```javascript
|
||||
window.BOTSERVER_URL = "http://127.0.0.1:8088";
|
||||
window.BOTSERVER_URL = "http://127.0.0.1:9000";
|
||||
```
|
||||
3. Run llama.cpp with small model (TinyLlama on 4GB+ devices)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ cd botserver
|
|||
```
|
||||
|
||||
That's it! After ~10-15 minutes:
|
||||
- botserver runs on port 8088
|
||||
- llama.cpp runs on port 8080 with TinyLlama
|
||||
- Embedded UI available at `http://your-device:8088/embedded/`
|
||||
- botserver runs on port 9000
|
||||
- llama.cpp runs on port 8081 with TinyLlama
|
||||
- Embedded UI available at `http://your-device:9000/embedded/`
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
|
|
@ -87,17 +87,17 @@ ssh pi@raspberrypi.local 'sudo systemctl status botserver'
|
|||
ssh pi@raspberrypi.local 'sudo systemctl status llama-server'
|
||||
|
||||
# Test botserver
|
||||
curl http://raspberrypi.local:8088/health
|
||||
curl http://raspberrypi.local:9000/health
|
||||
|
||||
# Test llama.cpp
|
||||
curl http://raspberrypi.local:8080/v1/models
|
||||
curl http://raspberrypi.local:9000/v1/models
|
||||
```
|
||||
|
||||
### Step 5: Access the Interface
|
||||
|
||||
Open in your browser:
|
||||
```
|
||||
http://raspberrypi.local:8088/embedded/
|
||||
http://raspberrypi.local:9000/embedded/
|
||||
```
|
||||
|
||||
Or set up kiosk mode (auto-starts on boot):
|
||||
|
|
@ -142,11 +142,11 @@ Key settings:
|
|||
```env
|
||||
# Server
|
||||
HOST=0.0.0.0
|
||||
PORT=8088
|
||||
PORT=9000
|
||||
|
||||
# Local LLM
|
||||
LLM_PROVIDER=llamacpp
|
||||
LLM_API_URL=http://127.0.0.1:8080
|
||||
LLM_API_URL=http://127.0.0.1:8081
|
||||
LLM_MODEL=tinyllama
|
||||
|
||||
# Memory limits for small devices
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ lxc config set botserver limits.memory 4GB
|
|||
lxc config set botserver limits.cpu 2
|
||||
|
||||
# Forward ports
|
||||
lxc config device add botserver http proxy listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:8080
|
||||
lxc config device add botserver http proxy listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:9000
|
||||
lxc config device add botserver https proxy listen=tcp:0.0.0.0:443 connect=tcp:127.0.0.1:8443
|
||||
|
||||
# Set environment for Vault
|
||||
|
|
@ -118,7 +118,7 @@ services:
|
|||
botserver:
|
||||
image: generalbots/botserver:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "8080:9000"
|
||||
environment:
|
||||
- VAULT_ADDR=http://vault:8200
|
||||
volumes:
|
||||
|
|
|
|||
|
|
@ -404,6 +404,7 @@ botserver rotate-secret <component>
|
|||
| `email` | SMTP password |
|
||||
| `directory` | Zitadel client secret |
|
||||
| `encryption` | Master encryption key (⚠️ dangerous) |
|
||||
| `jwt` | JWT signing secret (⚠️ invalidates refresh tokens) |
|
||||
|
||||
**Examples:**
|
||||
|
||||
|
|
@ -496,6 +497,73 @@ botserver version --all
|
|||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Current Limitations
|
||||
|
||||
⚠️ **Manual Service Updates Required**
|
||||
After rotating credentials, you MUST manually update each service:
|
||||
|
||||
- **Database (tables):** Run the provided SQL command to update PostgreSQL user password
|
||||
- **Drive (MinIO):** Run the provided `mc admin` commands to update S3 credentials
|
||||
- **Cache (Redis):** Run the provided `redis-cli` command to update password
|
||||
- **Directory (Zitadel):** Update client secret via admin console
|
||||
|
||||
⚠️ **Service Restart Required**
|
||||
After rotating **JWT secret**, you MUST restart botserver:
|
||||
```bash
|
||||
botserver restart
|
||||
```
|
||||
|
||||
All users will need to re-login (refresh tokens invalidated). Access tokens (15-minute expiry) will expire naturally.
|
||||
|
||||
⚠️ **No Automatic Rollback**
|
||||
If verification fails, you must manually restore from backups:
|
||||
```bash
|
||||
# Database: Re-run SQL with old password
|
||||
# JWT: Restore .env.backup.<timestamp>
|
||||
# Other: Use backup values shown in rotation output
|
||||
```
|
||||
|
||||
### Available Components for Rotation
|
||||
|
||||
| Component | Credential Type | Manual Update Required | Service Restart |
|
||||
|-----------|----------------|------------------------|-----------------|
|
||||
| `tables` | PostgreSQL password | ✅ Run SQL command | ❌ No |
|
||||
| `drive` | MinIO S3 credentials | ✅ Run mc commands | ❌ No |
|
||||
| `cache` | Redis/Valkey password | ✅ Run redis-cli | ❌ No |
|
||||
| `email` | SMTP password | ✅ Update mail server | ❌ No |
|
||||
| `directory` | Zitadel client secret | ✅ Update via console | ❌ No |
|
||||
| `encryption` | Master encryption key | ⚠️ Re-encrypt all data | ❌ No |
|
||||
| `jwt` | JWT signing secret | ❌ No | ✅ **Yes** |
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Test in staging first** - Never rotate in production without testing
|
||||
2. **Schedule during low traffic** - Rotate JWT outside peak hours
|
||||
3. **Have rollback plan ready** - Save backup paths shown during rotation
|
||||
4. **Monitor logs** - Check for authentication failures after rotation:
|
||||
```bash
|
||||
tail -f /var/log/botserver/app.log | grep -i "authentication\\|jwt\\|token"
|
||||
```
|
||||
5. **Rotate regularly** - Every 90 days for production, per security compliance
|
||||
6. **After JWT rotation** - Verify all services are healthy before declaring success
|
||||
|
||||
### Verification
|
||||
|
||||
The `rotate-secret` command includes automatic verification where possible:
|
||||
|
||||
- **Database:** Tests PostgreSQL connection with new credentials
|
||||
- **JWT:** Checks health endpoint (requires service to be running)
|
||||
- **Other:** Displays manual verification instructions
|
||||
|
||||
If verification fails:
|
||||
1. Check the error message for specific failure details
|
||||
2. Restore from backup if needed
|
||||
3. Re-run rotation after fixing the issue
|
||||
|
||||
---
|
||||
|
||||
## Complete Setup Example
|
||||
|
||||
Here's a complete workflow to set up Vault and migrate secrets.
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ ss -tlnp | grep LISTEN
|
|||
1. **Port already in use**
|
||||
```bash
|
||||
# Find what's using the port
|
||||
lsof -i :8080
|
||||
lsof -i :9000
|
||||
lsof -i :5432
|
||||
|
||||
# Kill conflicting process
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ The Security Protection module provides comprehensive host-level security throug
|
|||
│
|
||||
▼ HTMX/API calls
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ botserver (port 8088) │
|
||||
│ botserver (port 9000) │
|
||||
│ /api/security/protection/* │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
<text x="80" y="20" text-anchor="middle" font-size="11" font-weight="600" class="white-text">botserver</text>
|
||||
|
||||
<text x="80" y="55" text-anchor="middle" font-size="10" class="main-text">(Rust)</text>
|
||||
<text x="80" y="72" text-anchor="middle" font-size="10" font-weight="500" class="main-text">Port 8088</text>
|
||||
<text x="80" y="72" text-anchor="middle" font-size="10" font-weight="500" class="main-text">Port 9000</text>
|
||||
<text x="80" y="95" text-anchor="middle" font-size="9" class="mono-text">ARM64 Binary</text>
|
||||
</g>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
|
@ -90,7 +90,7 @@
|
|||
|
||||
<text x="85" y="52" text-anchor="middle" font-size="10" class="main-text">Rust Runtime</text>
|
||||
<text x="85" y="68" text-anchor="middle" font-size="9" class="mono-text">Session Manager</text>
|
||||
<text x="85" y="84" text-anchor="middle" font-size="9" class="mono-text">Port 8088</text>
|
||||
<text x="85" y="84" text-anchor="middle" font-size="9" class="mono-text">Port 9000</text>
|
||||
</g>
|
||||
|
||||
<!-- Arrow 2 -->
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
|
@ -46,7 +46,7 @@
|
|||
<g transform="translate(320, 80)">
|
||||
<rect width="240" height="280" rx="10" fill="#E8F5E9" stroke="#388E3C" stroke-width="2"/>
|
||||
<text x="120" y="30" text-anchor="middle" class="box-label">botserver (Rust)</text>
|
||||
<text x="120" y="50" text-anchor="middle" class="small-text">Port 8088</text>
|
||||
<text x="120" y="50" text-anchor="middle" class="small-text">Port 9000</text>
|
||||
<rect x="20" y="70" width="200" height="40" rx="4" fill="#C8E6C9" stroke="#388E3C"/>
|
||||
<text x="120" y="95" text-anchor="middle" class="small-text">BASIC Interpreter</text>
|
||||
<rect x="20" y="120" width="200" height="40" rx="4" fill="#C8E6C9" stroke="#388E3C"/>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
|
@ -299,7 +299,7 @@ Or continue reading for the full journey:
|
|||
<div class="wa-bubble">
|
||||
<p>Great! Just run:</p>
|
||||
<p><code>./botserver</code></p>
|
||||
<p>Then open http://localhost:8080</p>
|
||||
<p>Then open http://localhost:9000</p>
|
||||
<p>That's it! 🚀</p>
|
||||
<div class="wa-time">09:00</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ chmod +x botserver
|
|||
|
||||
### 2. Open Browser
|
||||
```
|
||||
http://localhost:8080
|
||||
http://localhost:9000
|
||||
```
|
||||
|
||||
### 3. Start Chatting
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue