Update .gitignore, remove old prompt docs, update botserver submodule
This commit is contained in:
parent
468a4fdbb7
commit
ce4c676501
8 changed files with 33 additions and 1908 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -84,3 +84,11 @@ errors_utf8.txt
|
||||||
vault-unseal-keysdefault-vault.tar
|
vault-unseal-keysdefault-vault.tar
|
||||||
prompts/sec-bots.md
|
prompts/sec-bots.md
|
||||||
AGENTS-PROD.md
|
AGENTS-PROD.md
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.head
|
||||||
|
*.bad2
|
||||||
|
*.bad
|
||||||
|
*.check
|
||||||
|
*.fix
|
||||||
|
*.bak
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7673eccc5963ec73b9f83b61888cb36670d60802
|
Subproject commit e8e1b7d65b8d1c37e2ea73cdcfec9a0e1f040027
|
||||||
File diff suppressed because it is too large
Load diff
146
prompts/crmex.md
146
prompts/crmex.md
|
|
@ -1,146 +0,0 @@
|
||||||
# Email Campaigns — Feature Plan
|
|
||||||
|
|
||||||
## Existing Foundation (botserver/src/marketing/)
|
|
||||||
- `campaigns.rs` — CrmCampaign model, CRUD handlers
|
|
||||||
- `metrics.rs` — CampaignMetrics, ChannelBreakdown, open/click/conversion rates
|
|
||||||
- `lists.rs` — recipient lists
|
|
||||||
- `templates.rs` — content templates
|
|
||||||
- `triggers.rs` — event-based sending
|
|
||||||
- `email/tracking.rs` — open/click tracking pixels
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Features to Build
|
|
||||||
|
|
||||||
### 1. Insights Dashboard
|
|
||||||
**What:** Time series views of delivery + engagement metrics per campaign.
|
|
||||||
|
|
||||||
**Data points per time bucket (hourly/daily):**
|
|
||||||
- Sent, delivered, bounced, failed
|
|
||||||
- Opens (unique + total), clicks, replies, unsubscribes
|
|
||||||
- Delivery rate, open rate, click-to-open rate (CTOR)
|
|
||||||
|
|
||||||
**Filters/pivots:**
|
|
||||||
- By mailbox provider (Gmail, Outlook, Yahoo, etc. — parsed from MX/SMTP response)
|
|
||||||
- By sender identity (from address / domain)
|
|
||||||
- By campaign or list
|
|
||||||
- Message search → show exact SMTP response from provider
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- Add `email_delivery_events` table: `(id, campaign_id, recipient_id, event_type, provider, smtp_response, ts)`
|
|
||||||
- API: `GET /api/campaigns/:id/insights?from=&to=&group_by=provider|identity|day`
|
|
||||||
- UI: HTMX + chart.js time series (local vendor)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Advisor Recommendations
|
|
||||||
**What:** Analyze sending config + results and surface actionable fixes.
|
|
||||||
|
|
||||||
**Checks to run:**
|
|
||||||
| Check | Signal | Recommendation |
|
|
||||||
|---|---|---|
|
|
||||||
| SPF/DKIM/DMARC | DNS lookup | "Add missing record" |
|
|
||||||
| Bounce rate > 5% | delivery_events | "Clean list — remove hard bounces" |
|
|
||||||
| Open rate < 15% | metrics | "Improve subject line / send time" |
|
|
||||||
| Spam complaints > 0.1% | FBL data | "Remove complainers immediately" |
|
|
||||||
| Sending from new IP | warmup_schedule | "Follow warmup plan" |
|
|
||||||
| List age > 6 months | list.last_sent | "Re-engagement campaign before bulk send" |
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- `marketing/advisor.rs` — `AdvisorEngine::analyze(campaign_id) -> Vec<Recommendation>`
|
|
||||||
- API: `GET /api/campaigns/:id/advisor`
|
|
||||||
- Runs automatically after each campaign completes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. IP Warmup (like OneSignal / Mailchimp)
|
|
||||||
**What:** Gradually increase daily send volume over 4–6 weeks to build sender reputation.
|
|
||||||
|
|
||||||
**Warmup schedule (standard):**
|
|
||||||
| Day | Max emails/day |
|
|
||||||
|---|---|
|
|
||||||
| 1–2 | 50 |
|
|
||||||
| 3–4 | 100 |
|
|
||||||
| 5–7 | 500 |
|
|
||||||
| 8–10 | 1,000 |
|
|
||||||
| 11–14 | 5,000 |
|
|
||||||
| 15–21 | 10,000 |
|
|
||||||
| 22–28 | 50,000 |
|
|
||||||
| 29+ | unlimited |
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- Only send to most engaged subscribers first (opened in last 90 days)
|
|
||||||
- Stop warmup if bounce rate > 3% or complaint rate > 0.1%
|
|
||||||
- Resume next day at same volume if paused
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- `marketing/warmup.rs` — `WarmupSchedule`, `WarmupEngine::get_daily_limit(ip, day) -> u32`
|
|
||||||
- `warmup_schedules` table: `(id, ip, started_at, current_day, status, paused_reason)`
|
|
||||||
- Scheduler checks warmup limit before each send batch
|
|
||||||
- API: `GET /api/warmup/status`, `POST /api/warmup/start`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Optimized Shared Delivery
|
|
||||||
**What:** Auto-select best sending IP based on real-time reputation signals.
|
|
||||||
|
|
||||||
**Logic:**
|
|
||||||
- Track per-IP: bounce rate, complaint rate, delivery rate (last 24h)
|
|
||||||
- Score each IP: `score = delivery_rate - (bounce_rate * 10) - (complaint_rate * 100)`
|
|
||||||
- Route each send to highest-scored IP for that destination provider
|
|
||||||
- Rotate IPs to spread load and preserve reputation
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- `marketing/ip_router.rs` — `IpRouter::select(destination_domain) -> IpAddr`
|
|
||||||
- `ip_reputation` table: `(ip, provider, bounces, complaints, delivered, window_start)`
|
|
||||||
- Plugs into Stalwart send path via botserver API
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Modern Email Marketing Features
|
|
||||||
|
|
||||||
| Feature | Description |
|
|
||||||
|---|---|
|
|
||||||
| **Send time optimization** | ML-based per-contact best send time (based on past open history) |
|
|
||||||
| **A/B testing** | Split subject/content, auto-pick winner after N hours |
|
|
||||||
| **Suppression list** | Global unsubscribe/bounce/complaint list, auto-applied to all sends |
|
|
||||||
| **Re-engagement flows** | Auto-trigger "we miss you" to contacts inactive > 90 days |
|
|
||||||
| **Transactional + marketing separation** | Separate IPs/domains for transactional vs bulk |
|
|
||||||
| **One-click unsubscribe** | RFC 8058 `List-Unsubscribe-Post` header on all bulk sends |
|
|
||||||
| **Preview & spam score** | Pre-send SpamAssassin score check |
|
|
||||||
| **Link tracking** | Redirect all links through tracker, record clicks per contact |
|
|
||||||
| **Webhook events** | Push delivery events to external URLs (Stalwart webhook → botserver) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## DB Tables to Add
|
|
||||||
|
|
||||||
```sql
|
|
||||||
email_delivery_events (id, campaign_id, recipient_id, event_type, provider, smtp_code, smtp_response, ts)
|
|
||||||
warmup_schedules (id, ip, started_at, current_day, daily_limit, status, paused_reason)
|
|
||||||
ip_reputation (id, ip, provider, delivered, bounced, complained, window_start)
|
|
||||||
advisor_recommendations (id, campaign_id, check_name, severity, message, created_at, dismissed)
|
|
||||||
ab_tests (id, campaign_id, variant_a, variant_b, split_pct, winner, decided_at)
|
|
||||||
suppression_list (id, org_id, email, reason, added_at)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files to Create
|
|
||||||
```
|
|
||||||
botserver/src/marketing/
|
|
||||||
├── warmup.rs — IP warmup engine + schedule
|
|
||||||
├── advisor.rs — recommendation engine
|
|
||||||
├── ip_router.rs — optimized IP selection
|
|
||||||
├── ab_test.rs — A/B test logic
|
|
||||||
├── suppression.rs — global suppression list
|
|
||||||
└── send_time.rs — send time optimization
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Existing Code to Extend
|
|
||||||
- `marketing/metrics.rs` → add time-series queries + provider breakdown
|
|
||||||
- `marketing/campaigns.rs` → add warmup_enabled, ab_test_id fields
|
|
||||||
- `email/tracking.rs` → already has open/click tracking, extend with provider parsing
|
|
||||||
- `core/shared/schema/` → add new tables above
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
# detector - Detecção de Desvios na Folha
|
|
||||||
|
|
||||||
## Objetivo
|
|
||||||
- Bot detector deve usar start.bas para inserir dados via init_folha.bas
|
|
||||||
- detecta.bas deve detectar anomalias nos dados inseridos
|
|
||||||
|
|
||||||
## ✅ Status Atual
|
|
||||||
|
|
||||||
### Correção REM em mod.rs (FEITA)
|
|
||||||
**Arquivo:** `botserver/src/basic/mod.rs` linha ~588-594
|
|
||||||
|
|
||||||
Filtro adicionado para `REM ` e `REM\t` no `compile_tool_script`:
|
|
||||||
```rust
|
|
||||||
!(trimmed.starts_with("PARAM ") ||
|
|
||||||
trimmed.starts_with("PARAM\t") ||
|
|
||||||
trimmed.starts_with("DESCRIPTION ") ||
|
|
||||||
trimmed.starts_with("DESCRIPTION\t") ||
|
|
||||||
trimmed.starts_with("REM ") || // <-- ADICIONADO
|
|
||||||
trimmed.starts_with("REM\t") || // <-- ADICIONADO
|
|
||||||
trimmed.starts_with('\'') ||
|
|
||||||
trimmed.starts_with('#') ||
|
|
||||||
trimmed.is_empty())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arquivos Envolvidos (VERIFICADOS)
|
|
||||||
- `/opt/gbo/data/detector.gbai/detector.gbdialog/start.bas` ✅ OK
|
|
||||||
- Contém botões de sugestão: detecta e init_folha
|
|
||||||
- `/opt/gbo/data/detector.gbai/detector.gbdialog/init_folha.bas` ✅ OK
|
|
||||||
- 4 INSERT statements para dados de exemplo
|
|
||||||
- `/opt/gbo/data/detector.gbai/detector.gbdialog/detecta.bas` ✅ OK
|
|
||||||
- Usa DETECT keyword
|
|
||||||
- `/opt/gbo/data/detector.gbai/detector.gbdialog/tables.bas` ✅ OK
|
|
||||||
- TABLE folha_salarios definida
|
|
||||||
|
|
||||||
### Botserver (RODANDO)
|
|
||||||
- ✅ Botserver compilado com sucesso
|
|
||||||
- ✅ Botserver rodando em http://localhost:8080
|
|
||||||
- ✅ Health check OK
|
|
||||||
|
|
||||||
## Próximos Passos (Pendentes)
|
|
||||||
|
|
||||||
1. **Testar via navegador** - Necessário instalar Playwright browsers
|
|
||||||
- Navegar para http://localhost:3000/detector
|
|
||||||
- Clicar em "⚙️ Inicializar Dados de Teste"
|
|
||||||
- Verificar se INSERT funciona
|
|
||||||
- Clicar em "🔍 Detectar Desvios na Folha"
|
|
||||||
- Verificar se DETECT funciona
|
|
||||||
|
|
||||||
2. **Verificar se há warnings relevantes**
|
|
||||||
- Alguns warnings de código podem precisar ser corrigidos
|
|
||||||
|
|
||||||
## Cache
|
|
||||||
- AST limpo: `rm ./botserver-stack/data/system/work/detector.gbai/detector.gbdialog/*.ast`
|
|
||||||
- Reiniciado: `./restart.sh`
|
|
||||||
- Botserver: ✅ Rodando
|
|
||||||
|
|
||||||
## Arquivos de Trabalho
|
|
||||||
- Work directory: `./botserver-stack/data/system/work/detector.gbai/detector.gbdialog/`
|
|
||||||
- Todos os arquivos BASIC estão presentes e parecem válidos
|
|
||||||
|
|
@ -270,3 +270,27 @@ botui/ui/suite/
|
||||||
```
|
```
|
||||||
|
|
||||||
File uploads go to `POST /api/suite/upload` → stored in Drive → media_id passed to tool.
|
File uploads go to `POST /api/suite/upload` → stored in Drive → media_id passed to tool.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compilation & Validation Log
|
||||||
|
|
||||||
|
**Last Validated:** 2026-04-17
|
||||||
|
|
||||||
|
**Status:** ✅ Design Document - No Code to Compile
|
||||||
|
|
||||||
|
**Validation Checklist:**
|
||||||
|
- [x] Architecture diagram is clear
|
||||||
|
- [x] App pattern is defined
|
||||||
|
- [x] WhatsApp campaign flow is documented
|
||||||
|
- [x] Tool definitions are complete
|
||||||
|
- [x] HTML structure is valid
|
||||||
|
- [x] Rust code examples are syntactically correct
|
||||||
|
- [x] Implementation phases are planned
|
||||||
|
- [x] File structure is organized
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- This is a design document for integrated suite conversational interface
|
||||||
|
- No compilation required - it's planning documentation
|
||||||
|
- Ready for implementation when developer is available
|
||||||
|
- References existing code in botserver/src/marketing/ and botserver/src/core/bot/
|
||||||
|
|
|
||||||
|
|
@ -1,435 +0,0 @@
|
||||||
# SWITCHER Feature - Response Format Modifiers
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Add a switcher interface that allows users to toggle response modifiers that influence how the AI generates responses. Unlike suggestions (which are one-time actions), switchers are persistent toggles that remain active until deactivated.
|
|
||||||
|
|
||||||
## Location
|
|
||||||
`botui/ui/suite/chat/` - alongside existing suggestion buttons
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
### Standard Switcher (predefined prompt)
|
|
||||||
```
|
|
||||||
ADD SWITCHER "tables" AS "Tabelas"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Switcher (with custom prompt)
|
|
||||||
```
|
|
||||||
ADD SWITCHER "sempre mostrar 10 perguntas" AS "Mostrar Perguntas"
|
|
||||||
```
|
|
||||||
|
|
||||||
## What Switcher Does
|
|
||||||
|
|
||||||
The switcher:
|
|
||||||
1. **Injects the prompt** into every LLM request
|
|
||||||
2. **The prompt** can be:
|
|
||||||
- **Standard**: References a predefined prompt by ID (`"tables"`, `"cards"`, etc.)
|
|
||||||
- **Custom**: Any custom instruction string (`"sempre mostrar 10 perguntas"`)
|
|
||||||
3. **Influences** the AI response format
|
|
||||||
4. **Persists** until toggled OFF
|
|
||||||
|
|
||||||
## Available Standard Switchers
|
|
||||||
|
|
||||||
| ID | Label | Color | Description |
|
|
||||||
|----|--------|--------|-------------|
|
|
||||||
| tables | Tabelas | #4CAF50 | Format responses as tables |
|
|
||||||
| infographic | Infográfico | #2196F3 | Visual, graphical representations |
|
|
||||||
| cards | Cards | #FF9800 | Card-based layout |
|
|
||||||
| list | Lista | #9C27B0 | Bulleted lists |
|
|
||||||
| comparison | Comparação | #E91E63 | Side-by-side comparisons |
|
|
||||||
| timeline | Timeline | #00BCD4 | Chronological ordering |
|
|
||||||
| markdown | Markdown | #607D8B | Standard markdown |
|
|
||||||
| chart | Gráfico | #F44336 | Charts and diagrams |
|
|
||||||
|
|
||||||
## Predefined Prompts (Backend)
|
|
||||||
|
|
||||||
Each standard ID maps to a predefined prompt in the backend:
|
|
||||||
|
|
||||||
```
|
|
||||||
ID: tables
|
|
||||||
Prompt: "REGRAS DE FORMATO: SEMPRE retorne suas respostas em formato de tabela HTML usando <table>, <thead>, <tbody>, <tr>, <th>, <td>. Cada dado deve ser uma célula. Use cabeçalhos claros na primeira linha. Se houver dados numéricos, alinhe à direita. Se houver texto, alinhe à esquerda. Use cores sutis em linhas alternadas (nth-child). NÃO use markdown tables, use HTML puro."
|
|
||||||
|
|
||||||
ID: infographic
|
|
||||||
Prompt: "REGRAS DE FORMATO: Crie representações visuais HTML usando SVG, progress bars, stat cards, e elementos gráficos. Use elementos como: <svg> para gráficos, <div style="width:X%;background:color"> para barras de progresso, ícones emoji, badges coloridos. Organize informações visualmente com grids, flexbox, e espaçamento. Inclua legendas e rótulos visuais claros."
|
|
||||||
|
|
||||||
ID: cards
|
|
||||||
Prompt: "REGRAS DE FORMATO: Retorne informações em formato de cards HTML. Cada card deve ter: <div class="card" style="border:1px solid #ddd;border-radius:8px;padding:16px;margin:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1)">. Dentro do card use: título em <h3> ou <strong>, subtítulo em <p> style="color:#666", ícone emoji ou ícone SVG no topo, badges de status. Organize cards em grid usando display:grid ou flex-wrap."
|
|
||||||
|
|
||||||
ID: list
|
|
||||||
Prompt: "REGRAS DE FORMATO: Use apenas listas HTML: <ul> para bullets e <ol> para números numerados. Cada item em <li>. Use sublistas aninhadas quando apropriado. NÃO use parágrafos de texto, converta tudo em itens de lista. Adicione ícones emoji no início de cada <li> quando possível. Use classes CSS para estilização: .list-item, .sub-list."
|
|
||||||
|
|
||||||
ID: comparison
|
|
||||||
Prompt: "REGRAS DE FORMATO: Crie comparações lado a lado em HTML. Use grid de 2 colunas: <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px">. Cada lado em uma <div class="comparison-side"> com borda colorida distinta. Use headers claros para cada lado. Adicione seção de "Diferenças Chave" com bullet points. Use cores contrastantes para cada lado (ex: azul vs laranja). Inclua tabela de comparação resumida no final."
|
|
||||||
|
|
||||||
ID: timeline
|
|
||||||
Prompt: "REGRAS DE FORMATO: Organize eventos cronologicamente em formato de timeline HTML. Use <div class="timeline"> com border-left vertical. Cada evento em <div class="timeline-item"> com: data em <span class="timeline-date" style="font-weight:bold;color:#666">, título em <h3>, descrição em <p>. Adicione círculo indicador na timeline line. Ordene do mais antigo para o mais recente. Use espaçamento claro entre eventos."
|
|
||||||
|
|
||||||
ID: markdown
|
|
||||||
Prompt: "REGRAS DE FORMATO: Use exclusivamente formato Markdown padrão. Sintaxe permitida: **negrito**, *itálico*, `inline code`, ```bloco de código```, # cabeçalhos, - bullets, 1. números, [links](url), , | tabela | markdown |. NÃO use HTML tags exceto para blocos de código. Siga estritamente a sintaxe CommonMark."
|
|
||||||
|
|
||||||
ID: chart
|
|
||||||
Prompt: "REGRAS DE FORMATO: Crie gráficos e diagramas em HTML SVG. Use elementos SVG: <svg width="X" height="Y">, <line> para gráficos de linha, <rect> para gráficos de barra, <circle> para gráficos de pizza, <path> para gráficos de área. Inclua eixos com labels, grid lines, legendas. Use cores distintas para cada série de dados (ex: vermelho, azul, verde). Adicione tooltips com valores ao hover. Se o usuário pedir gráfico de pizza com "pizza vermelha", use fill="#FF0000" no SVG."
|
|
||||||
```
|
|
||||||
|
|
||||||
## UI Design
|
|
||||||
|
|
||||||
### HTML Structure
|
|
||||||
```html
|
|
||||||
<div class="switchers-container" id="switchers">
|
|
||||||
<div class="switchers-label">Formato:</div>
|
|
||||||
<div class="switchers-chips" id="switchersChips">
|
|
||||||
<!-- Switcher chips will be rendered here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Placement
|
|
||||||
Position the switchers container **above** the suggestions container:
|
|
||||||
```html
|
|
||||||
<footer>
|
|
||||||
<div class="switchers-container" id="switchers"></div>
|
|
||||||
<div class="suggestions-container" id="suggestions"></div>
|
|
||||||
<!-- ... existing form ... -->
|
|
||||||
</footer>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS Styling
|
|
||||||
|
|
||||||
#### Container
|
|
||||||
```css
|
|
||||||
.switchers-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
background: rgba(0, 0, 0, 0.02);
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switchers-label {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #666;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Switcher Chips (Toggle Buttons)
|
|
||||||
```css
|
|
||||||
.switchers-chips {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switcher-chip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
color: #666;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switcher-chip:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.08);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switcher-chip.active {
|
|
||||||
border-color: currentColor;
|
|
||||||
background: currentColor;
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switcher-chip-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## JavaScript Implementation
|
|
||||||
|
|
||||||
### State Management
|
|
||||||
```javascript
|
|
||||||
// Track active switchers
|
|
||||||
var activeSwitchers = new Set();
|
|
||||||
|
|
||||||
// Switcher definitions (from ADD SWITCHER commands in start.bas)
|
|
||||||
var switcherDefinitions = [
|
|
||||||
{
|
|
||||||
id: 'tables',
|
|
||||||
label: 'Tabelas',
|
|
||||||
icon: '📊',
|
|
||||||
color: '#4CAF50'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'infographic',
|
|
||||||
label: 'Infográfico',
|
|
||||||
icon: '📈',
|
|
||||||
color: '#2196F3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'cards',
|
|
||||||
label: 'Cards',
|
|
||||||
icon: '🃏',
|
|
||||||
color: '#FF9800'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'list',
|
|
||||||
label: 'Lista',
|
|
||||||
icon: '📋',
|
|
||||||
color: '#9C27B0'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'comparison',
|
|
||||||
label: 'Comparação',
|
|
||||||
icon: '⚖️',
|
|
||||||
color: '#E91E63'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'timeline',
|
|
||||||
label: 'Timeline',
|
|
||||||
icon: '📅',
|
|
||||||
color: '#00BCD4'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'markdown',
|
|
||||||
label: 'Markdown',
|
|
||||||
icon: '📝',
|
|
||||||
color: '#607D8B'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chart',
|
|
||||||
label: 'Gráfico',
|
|
||||||
icon: '📉',
|
|
||||||
color: '#F44336'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Render Switchers
|
|
||||||
```javascript
|
|
||||||
function renderSwitchers() {
|
|
||||||
var container = document.getElementById("switcherChips");
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
container.innerHTML = switcherDefinitions.map(function(sw) {
|
|
||||||
var isActive = activeSwitchers.has(sw.id);
|
|
||||||
return (
|
|
||||||
'<div class="switcher-chip' + (isActive ? ' active' : '') + '" ' +
|
|
||||||
'data-switch-id="' + sw.id + '" ' +
|
|
||||||
'style="--switcher-color: ' + sw.color + '; ' +
|
|
||||||
(isActive ? 'color: ' + sw.color + ' background: ' + sw.color + '; ' : '') +
|
|
||||||
'">' +
|
|
||||||
'<span class="switcher-chip-icon">' + sw.icon + '</span>' +
|
|
||||||
'<span>' + sw.label + '</span>' +
|
|
||||||
'</div>'
|
|
||||||
);
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
// Add click handlers
|
|
||||||
container.querySelectorAll('.switcher-chip').forEach(function(chip) {
|
|
||||||
chip.addEventListener('click', function() {
|
|
||||||
toggleSwitcher(this.getAttribute('data-switch-id'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Toggle Handler
|
|
||||||
```javascript
|
|
||||||
function toggleSwitcher(switcherId) {
|
|
||||||
if (activeSwitchers.has(switcherId)) {
|
|
||||||
activeSwitchers.delete(switcherId);
|
|
||||||
} else {
|
|
||||||
activeSwitchers.add(switcherId);
|
|
||||||
}
|
|
||||||
renderSwitchers();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Message Enhancement
|
|
||||||
When sending a user message, prepend active switcher prompts:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function sendMessage(messageContent) {
|
|
||||||
// ... existing code ...
|
|
||||||
|
|
||||||
var content = messageContent || input.value.trim();
|
|
||||||
if (!content) return;
|
|
||||||
|
|
||||||
// Prepend active switcher prompts
|
|
||||||
var enhancedContent = content;
|
|
||||||
if (activeSwitchers.size > 0) {
|
|
||||||
// Get prompts for active switchers from backend
|
|
||||||
var activePrompts = [];
|
|
||||||
activeSwitchers.forEach(function(id) {
|
|
||||||
// Backend has predefined prompts for each ID
|
|
||||||
activePrompts.push(getSwitcherPrompt(id));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inject prompts before user message
|
|
||||||
if (activePrompts.length > 0) {
|
|
||||||
enhancedContent = activePrompts.join('\n\n') + '\n\n---\n\n' + content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send enhanced content
|
|
||||||
addMessage("user", content);
|
|
||||||
|
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
||||||
ws.send(JSON.stringify({
|
|
||||||
bot_id: currentBotId,
|
|
||||||
user_id: currentUserId,
|
|
||||||
session_id: currentSessionId,
|
|
||||||
channel: "web",
|
|
||||||
content: enhancedContent,
|
|
||||||
message_type: MessageType.USER,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSwitcherPrompt(switcherId) {
|
|
||||||
// Get predefined prompt from backend or API
|
|
||||||
// For example, tables ID maps to:
|
|
||||||
// "REGRAS DE FORMATO: SEMPRE retorne suas respostas em formato de tabela HTML..."
|
|
||||||
var switcher = switcherDefinitions.find(function(s) { return s.id === switcherId; });
|
|
||||||
if (!switcher) return "";
|
|
||||||
|
|
||||||
// This could be fetched from backend or stored locally
|
|
||||||
return SWITCHER_PROMPTS[switcherId] || "";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bot Integration (start.bas)
|
|
||||||
|
|
||||||
The bot receives the switcher prompt injected into the user message and simply passes it to the LLM.
|
|
||||||
|
|
||||||
### Example in start.bas
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Switcher prompts are automatically injected by frontend
|
|
||||||
REM Just pass user_input to LLM - no parsing needed!
|
|
||||||
|
|
||||||
REM If user types: "mostra os cursos"
|
|
||||||
REM And "Tabelas" switcher is active
|
|
||||||
REM Frontend sends: "REGRAS DE FORMATO: SEMPRE retorne suas respostas em formato de tabela HTML... --- mostra os cursos"
|
|
||||||
|
|
||||||
REM Bot passes directly to LLM:
|
|
||||||
response$ = CALL_LLM(user_input)
|
|
||||||
|
|
||||||
REM The LLM will follow the REGRAS DE FORMATO instructions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multiple Active Switchers
|
|
||||||
|
|
||||||
When multiple switchers are active, all prompts are injected:
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Frontend injects multiple REGRAS DE FORMATO blocks
|
|
||||||
REM Example with "Tabelas" and "Gráfico" active:
|
|
||||||
REM
|
|
||||||
REM "REGRAS DE FORMATO: SEMPRE retorne suas respostas em formato de tabela HTML...
|
|
||||||
REM REGRAS DE FORMATO: Crie gráficos e diagramas em HTML SVG...
|
|
||||||
REM ---
|
|
||||||
REM mostra os dados de vendas"
|
|
||||||
|
|
||||||
REM Bot passes to LLM:
|
|
||||||
response$ = CALL_LLM(user_input)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
1. ✅ Create prompts/switcher.md (this file)
|
|
||||||
2. ⬜ Define predefined prompts in backend (map IDs to prompt strings)
|
|
||||||
3. ⬜ Add HTML structure to chat.html (switchers container)
|
|
||||||
4. ⬜ Add CSS styles to chat.css (switcher chip styles)
|
|
||||||
5. ⬜ Add switcher definitions to chat.js
|
|
||||||
6. ⬜ Implement renderSwitchers() function
|
|
||||||
7. ⬜ Implement toggleSwitcher() function
|
|
||||||
8. ⬜ Modify sendMessage() to prepend switcher prompts
|
|
||||||
9. ⬜ Update salesianos bot start.bas to use ADD SWITCHER commands
|
|
||||||
10. ⬜ Test locally with all switcher options
|
|
||||||
11. ⬜ Verify multiple switchers can be active simultaneously
|
|
||||||
12. ⬜ Test persistence across page refreshes (optional - localStorage)
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
- [ ] Switchers appear above suggestions
|
|
||||||
- [ ] Switchers are colorful and match their defined colors
|
|
||||||
- [ ] Clicking a switcher toggles it on/off
|
|
||||||
- [ ] Multiple switchers can be active simultaneously
|
|
||||||
- [ ] Active switchers have distinct visual state (border, background, shadow)
|
|
||||||
- [ ] Formatted responses match the selected format
|
|
||||||
- [ ] Toggling off removes the format modifier
|
|
||||||
- [ ] Works with empty active switchers (normal response)
|
|
||||||
- [ ] Works in combination with suggestions
|
|
||||||
- [ ] Responsive design on mobile devices
|
|
||||||
|
|
||||||
## Files to Modify
|
|
||||||
|
|
||||||
1. `botui/ui/suite/chat/chat.html` - Add switcher container HTML
|
|
||||||
2. `botui/ui/suite/chat/chat.css` - Add switcher styles
|
|
||||||
3. `botui/ui/suite/chat/chat.js` - Add switcher logic
|
|
||||||
4. `botserver/bots/salesianos/start.bas` - Add ADD SWITCHER commands
|
|
||||||
|
|
||||||
## Example start.bas
|
|
||||||
|
|
||||||
```basic
|
|
||||||
USE_WEBSITE("https://salesianos.br", "30d")
|
|
||||||
|
|
||||||
USE KB "carta"
|
|
||||||
USE KB "proc"
|
|
||||||
|
|
||||||
USE TOOL "inscricao"
|
|
||||||
USE TOOL "consultar_inscricao"
|
|
||||||
USE TOOL "agendamento_visita"
|
|
||||||
USE TOOL "informacoes_curso"
|
|
||||||
USE TOOL "documentos_necessarios"
|
|
||||||
USE TOOL "contato_secretaria"
|
|
||||||
USE TOOL "calendario_letivo"
|
|
||||||
|
|
||||||
ADD_SUGGESTION_TOOL "inscricao" AS "Fazer Inscrição"
|
|
||||||
ADD_SUGGESTION_TOOL "consultar_inscricao" AS "Consultar Inscrição"
|
|
||||||
ADD_SUGGESTION_TOOL "agendamento_visita" AS "Agendar Visita"
|
|
||||||
ADD_SUGGESTION_TOOL "informacoes_curso" AS "Informações de Cursos"
|
|
||||||
ADD_SUGGESTION_TOOL "documentos_necessarios" AS "Documentos Necessários"
|
|
||||||
ADD_SUGGESTION_TOOL "contato_secretaria" AS "Falar com Secretaria"
|
|
||||||
ADD_SUGGESTION_TOOL "segunda_via" AS "Segunda Via de Boleto"
|
|
||||||
ADD_SUGGESTION_TOOL "calendario_letivo" AS "Calendário Letivo"
|
|
||||||
ADD_SUGGESTION_TOOL "outros" AS "Outros"
|
|
||||||
|
|
||||||
ADD SWITCHER "tables" AS "Tabelas"
|
|
||||||
ADD SWITCHER "infographic" AS "Infográfico"
|
|
||||||
ADD SWITCHER "cards" AS "Cards"
|
|
||||||
ADD SWITCHER "list" AS "Lista"
|
|
||||||
ADD SWITCHER "comparison" AS "Comparação"
|
|
||||||
ADD SWITCHER "timeline" AS "Timeline"
|
|
||||||
ADD SWITCHER "markdown" AS "Markdown"
|
|
||||||
ADD SWITCHER "chart" AS "Gráfico"
|
|
||||||
|
|
||||||
TALK "Olá! Sou o assistente virtual da Escola Salesiana. Como posso ajudá-lo hoje com inscrições, visitas, informações sobre cursos, documentos ou calendário letivo? Você pode também escolher formatos de resposta acima da caixa de mensagem."
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Switchers are **persistent** until deactivated
|
|
||||||
- Multiple switchers can be active at once
|
|
||||||
- Switcher prompts are prepended to user messages with "---" separator
|
|
||||||
- The backend (LLM) should follow these format instructions
|
|
||||||
- UI should provide clear visual feedback for active switchers
|
|
||||||
- Color coding helps users quickly identify active formats
|
|
||||||
- Standard switchers use predefined prompts in backend
|
|
||||||
- Custom switchers allow any prompt string to be injected
|
|
||||||
- render each switch with a random glow color at html
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
# USE KB 2.0: Group-Based Knowledge Base Access
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Modify the USE KB keyword to respect user group permissions, ensuring that THINK KB queries only return answers from knowledge base folders that belong to groups the logged-in user is a member of.
|
|
||||||
|
|
||||||
## Current Architecture
|
|
||||||
|
|
||||||
### USE KB Flow
|
|
||||||
1. User executes `USE KB "kb_name"` in BASIC script
|
|
||||||
2. `use_kb.rs:add_kb_to_session()` checks if KB exists in `kb_collections`
|
|
||||||
3. Creates default KB entry if not found
|
|
||||||
4. Adds association to `session_kb_associations` table
|
|
||||||
5. KB becomes active for the session
|
|
||||||
|
|
||||||
### THINK KB Flow
|
|
||||||
1. User executes `THINK KB "query"`
|
|
||||||
2. `think_kb.rs:think_kb_search()` gets all active KBs from `session_kb_associations`
|
|
||||||
3. For each active KB, calls `KnowledgeBaseManager.search()` on its Qdrant collection
|
|
||||||
4. Returns combined results from all active KBs
|
|
||||||
|
|
||||||
### Group System
|
|
||||||
- Groups stored in `rbac_groups` table
|
|
||||||
- User membership in `rbac_user_groups` table
|
|
||||||
- Group permissions via `rbac_group_roles` table
|
|
||||||
|
|
||||||
## Proposed Changes
|
|
||||||
|
|
||||||
### 1. Database Schema Changes
|
|
||||||
|
|
||||||
Add new table `kb_group_associations`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE kb_group_associations (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
kb_id UUID NOT NULL REFERENCES kb_collections(id) ON DELETE CASCADE,
|
|
||||||
group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE,
|
|
||||||
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
||||||
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
UNIQUE(kb_id, group_id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Migration file: `botserver/migrations/6.2.0-01-kb-groups/up.sql`
|
|
||||||
|
|
||||||
### 2. Backend Logic Changes
|
|
||||||
|
|
||||||
#### Modify `think_kb_search()` in `think_kb.rs`
|
|
||||||
- Add user group lookup before searching
|
|
||||||
- Filter active KBs to only those accessible by user's groups
|
|
||||||
- Allow access if KB has no group associations (public KBs) OR user is in associated groups
|
|
||||||
|
|
||||||
```rust
|
|
||||||
async fn think_kb_search(
|
|
||||||
kb_manager: Arc<KnowledgeBaseManager>,
|
|
||||||
db_pool: DbPool,
|
|
||||||
session_id: Uuid,
|
|
||||||
bot_id: Uuid,
|
|
||||||
user_id: Uuid, // Add user_id parameter
|
|
||||||
query: &str,
|
|
||||||
) -> Result<serde_json::Value, String> {
|
|
||||||
// Get user's groups
|
|
||||||
let user_groups = get_user_groups(&db_pool, user_id)?;
|
|
||||||
|
|
||||||
// Get active KBs filtered by groups
|
|
||||||
let accessible_kbs = get_accessible_kbs_for_session(&db_pool, session_id, &user_groups)?;
|
|
||||||
|
|
||||||
// Search only accessible KBs
|
|
||||||
// ... rest of search logic
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Add `get_accessible_kbs_for_session()` function
|
|
||||||
```rust
|
|
||||||
fn get_accessible_kbs_for_session(
|
|
||||||
conn_pool: &DbPool,
|
|
||||||
session_id: Uuid,
|
|
||||||
user_groups: &[String],
|
|
||||||
) -> Result<Vec<(String, String, String)>, String> {
|
|
||||||
// Query that joins session_kb_associations with kb_group_associations
|
|
||||||
// Returns KBs where group_id IS NULL (public) OR group_id IN user_groups
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Modify `add_kb_to_session()` in `use_kb.rs`
|
|
||||||
- Add optional group access check
|
|
||||||
- Allow USE KB if user has access to the KB's groups
|
|
||||||
|
|
||||||
### 3. API Changes
|
|
||||||
|
|
||||||
Add new endpoints in `rbac.rs` for KB-group management:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Assign KB to group
|
|
||||||
POST /api/rbac/kbs/{kb_id}/groups/{group_id}
|
|
||||||
|
|
||||||
// Remove KB from group
|
|
||||||
DELETE /api/rbac/kbs/{kb_id}/groups/{group_id}
|
|
||||||
|
|
||||||
// Get groups for KB
|
|
||||||
GET /api/rbac/kbs/{kb_id}/groups
|
|
||||||
|
|
||||||
// Get KBs accessible by user
|
|
||||||
GET /api/rbac/users/{user_id}/accessible-kbs
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Frontend Changes
|
|
||||||
|
|
||||||
#### Update `botui/ui/suite/admin/groups.html`
|
|
||||||
- Add "Knowledge Bases" tab to group detail panel
|
|
||||||
- Show list of KBs assigned to the group
|
|
||||||
- Allow adding/removing KB assignments
|
|
||||||
|
|
||||||
#### Update `botui/ui/suite/drive/drive.html`
|
|
||||||
- Add group visibility indicators for KB folders
|
|
||||||
- Show which groups have access to each KB
|
|
||||||
|
|
||||||
### 5. Migration Strategy
|
|
||||||
|
|
||||||
1. Create new migration for `kb_group_associations` table
|
|
||||||
2. Run migration to create table
|
|
||||||
3. Assign existing KBs to default groups (e.g., "all_users" group)
|
|
||||||
4. Update application code
|
|
||||||
5. Deploy and test
|
|
||||||
|
|
||||||
### 6. Backward Compatibility
|
|
||||||
|
|
||||||
- Existing KBs without group associations remain public
|
|
||||||
- Existing USE KB calls continue to work
|
|
||||||
- THINK KB will filter results based on new permissions
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
1. ✅ Database migration for kb_group_associations
|
|
||||||
2. ✅ Modify think_kb_search to accept user_id and filter by groups
|
|
||||||
3. ✅ Update THINK KB keyword registration to pass user_id
|
|
||||||
4. ✅ Add group access check to USE KB
|
|
||||||
5. ✅ Add API endpoints for KB-group management
|
|
||||||
6. ✅ Update admin UI for group-KB assignment
|
|
||||||
7. ✅ Update drive UI to show group access
|
|
||||||
8. ✅ Add tests for group-based access control
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- All KB access checks must happen at the database level
|
|
||||||
- No client-side filtering of search results
|
|
||||||
- Group membership verified on each request
|
|
||||||
- Audit logging for KB access attempts
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- Unit tests for group access functions
|
|
||||||
- Integration tests for THINK KB with group filtering
|
|
||||||
- UI tests for admin group-KB management
|
|
||||||
- End-to-end tests with different user group scenarios
|
|
||||||
Loading…
Add table
Reference in a new issue