From 48c1ae0b51414035d8771e19fc5d6a1f1d5ec7bc Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sun, 30 Nov 2025 15:07:29 -0300 Subject: [PATCH] , dt.month, dt.hour, dt.is_weekend, etc.) - Add startup wizard module for first-run configuration - Add white-label branding system with .product file support - Add bot manager for lifecycle, MinIO buckets, and templates - Add version tracking registry for component updates - Create comparison doc: BASIC vs n8n/Zapier/Make/Copilot - Add WhatsApp-style sample dialogs to template documentation - Add data traceability SVG diagram ``` --- Cargo.toml | 5 +- docs/HEAR_VALIDATION_REFERENCE.md | 788 ++++ docs/MULTI_AGENT_OFFICE_SUITE.md | 3550 +++++++++++++++++ docs/src/SUMMARY.md | 15 +- docs/src/chapter-06-gbdialog/README.md | 7 + .../keyword-delete-file.md | 2 +- .../keyword-delete-http.md | 2 +- .../keyword-generate-pdf.md | 2 +- .../chapter-06-gbdialog/keyword-group-by.md | 2 +- .../chapter-06-gbdialog/keyword-merge-pdf.md | 2 +- .../chapter-06-gbdialog/keyword-set-header.md | 2 +- .../keyword-synchronize.md | 123 + docs/src/chapter-06-gbdialog/keyword-table.md | 223 ++ docs/src/chapter-06-gbdialog/keywords.md | 126 +- docs/src/chapter-06-gbdialog/prompt-blocks.md | 476 +++ .../script-execution-flow.md | 508 +++ docs/src/chapter-09-api/llm-rest-server.md | 243 ++ docs/src/chapter-11-features/README.md | 245 +- docs/src/chapter-11-features/editions.md | 375 ++ docs/src/executive-vision.md | 353 +- .../down.sql | 15 + .../2025-01-20-000001_multi_agent_bots/up.sql | 226 ++ migrations/6.1.0_table_keyword/down.sql | 22 + migrations/6.1.0_table_keyword/up.sql | 120 + src/basic/compiler/mod.rs | 9 + src/basic/keywords/add_bot.rs | 889 +++++ src/basic/keywords/file_operations.rs | 66 +- src/basic/keywords/for_next.rs | 97 +- src/basic/keywords/hear_talk.rs | 1539 ++++++- src/basic/keywords/http_operations.rs | 102 +- src/basic/keywords/mod.rs | 3 + src/basic/keywords/play.rs | 757 ++++ src/basic/keywords/table_definition.rs | 763 ++++ src/basic/mod.rs | 394 +- src/core/automation/mod.rs | 6 +- src/core/package_manager/installer.rs | 43 + src/directory/mod.rs | 6 +- src/timeseries/mod.rs | 670 ++++ templates/README.md | 461 +-- templates/bank.gbai/start.bas | 685 ++++ templates/base.html | 89 - templates/bling.gbai/bling.gbdialog/README.md | 118 + .../bling.gbai/bling.gbdialog/add-stock.bas | 32 + .../bling.gbdialog/data-analysis.bas | 38 + .../bling.gbai/bling.gbdialog/refresh-llm.bas | 2 + templates/bling.gbai/bling.gbdialog/start.bas | 16 + .../bling.gbdialog/sync-accounts.bas | 92 + .../bling.gbai/bling.gbdialog/sync-erp.bas | 333 ++ .../bling.gbdialog/sync-inventory.bas | 66 + .../bling.gbdialog/sync-suppliers.bas | 73 + .../bling.gbai/bling.gbdialog/tables.bas | 284 ++ templates/bling.gbai/bling.gbot/config.csv | 25 + templates/chat.html | 159 - .../compliance/hipaa-medical.gbai/README.md | 174 + .../hipaa.gbdialog/start.bas | 88 + .../hipaa-medical.gbai/hipaa.gbot/config.csv | 63 + templates/compliance/privacy.gbai/README.md | 200 + .../privacy.gbdialog/delete-data.bas | 213 + .../privacy.gbdialog/export-data.bas | 372 ++ .../privacy.gbdialog/manage-consents.bas | 333 ++ .../privacy.gbdialog/request-data.bas | 152 + .../privacy.gbai/privacy.gbdialog/start.bas | 39 + .../privacy.gbai/privacy.gbot/config.csv | 44 + .../privacy.gbai/privacy.gbui/index.html | 913 +++++ templates/drive.html | 423 -- .../erp.gbai/erp.gbdialog/purchasing.bas | 28 +- templates/home.html | 89 - .../api-client.gbdialog/climate.bas | 0 .../msft-partner-center.bas | 0 .../public-apis.gbai/KEYWORDS_CHECKLIST.md | 0 .../public-apis.gbai/QUICKSTART.md | 0 .../public-apis.gbai/README.md | 0 .../public-apis.gbdialog/animals-apis.bas | 0 .../data-utility-apis.bas | 0 .../entertainment-apis.bas | 0 .../public-apis.gbdialog/food-apis.bas | 0 .../science-space-apis.bas | 0 .../public-apis.gbdialog/weather-apis.bas | 0 templates/mail.html | 591 --- templates/meet.html | 949 ----- .../analytics.gbdialog/custom-report.bas | 257 ++ .../analytics.gbdialog/platform-overview.bas | 115 + .../analytics.gbdialog/start.bas | 58 + .../analytics.gbai/analytics.gbot/config.csv | 39 + .../office.gbdialog/api-integration.bas | 0 .../office.gbai/office.gbdialog/data-sync.bas | 0 .../office.gbdialog/document-processor.bas | 0 .../office.gbai/office.gbdialog/start.bas | 0 .../office.gbai/office.gbot/config.csv | 0 .../reminder.gbdata/reminders.csv | 0 .../reminder.gbdialog/add-reminder.bas | 0 .../reminder.gbdialog/reminder.bas | 0 .../reminder.gbai/reminder.gbdialog/start.bas | 0 .../crm.gbdialog/account-management.bas | 480 +++ .../crm.gbdialog/activity-tracking.bas | 148 + .../analyze-customer-sentiment.bas | 0 .../crm.gbai/crm.gbdialog/basic-check.bas | 0 .../crm.gbai/crm.gbdialog/case-management.bas | 0 .../crm.gbdialog/create-lead-from-draft.bas | 0 .../crm.gbai/crm.gbdialog/crm-jobs.bas | 0 .../crm.gbai/crm.gbdialog/data-enrichment.bas | 0 .../crm.gbai/crm.gbdialog/geral.bas | 0 .../crm.gbai/crm.gbdialog/lead-management.bas | 0 .../crm.gbai/crm.gbdialog/myitems.bas | 0 .../crm.gbai/crm.gbdialog/new_email.bas | 0 .../crm.gbai/crm.gbdialog/new_session.bas | 0 .../crm.gbdialog/on-emulator-sent.bas | 0 .../crm.gbdialog/on-receive-email.bas | 0 .../crm.gbai/crm.gbdialog/on_transfer.bas | 0 .../crm.gbdialog/opportunity-management.bas | 0 .../crm.gbdialog/send-proposal-v0.bas | 0 .../crm.gbai/crm.gbdialog/send-proposal.bas | 0 .../crm.gbai/crm.gbdialog/tables.bas | 0 .../crm.gbdialog/update-opportunity.bas | 0 .../marketing.gbdialog/add-new-idea.bas | 0 .../marketing.gbdialog/broadcast.bas | 0 .../campaigns/lead-nurture-campaign.bas | 0 .../campaigns/product-launch-campaign.bas | 0 .../campaigns/welcome-campaign.bas | 0 .../marketing.gbdialog/get-image.bas | 0 .../marketing.gbdialog/post-to-instagram.bas | 0 .../marketing.gbdialog/poster.bas | 0 templates/tasks.html | 860 ---- templates/tools.html | 1019 ----- ui/suite/analytics/analytics.html | 1215 ++++++ ui/suite/calendar/calendar.html | 1762 ++++++++ ui/suite/chat/projector.html | 1399 +++++++ ui/suite/css/apps-extended.css | 318 ++ ui/suite/css/components.css | 1046 +++++ ui/suite/index.html | 323 +- ui/suite/paper/paper.html | 1716 ++++++++ ui/suite/research/research.html | 1457 +++++++ 132 files changed, 27274 insertions(+), 4858 deletions(-) create mode 100644 docs/HEAR_VALIDATION_REFERENCE.md create mode 100644 docs/MULTI_AGENT_OFFICE_SUITE.md create mode 100644 docs/src/chapter-06-gbdialog/keyword-synchronize.md create mode 100644 docs/src/chapter-06-gbdialog/keyword-table.md create mode 100644 docs/src/chapter-06-gbdialog/prompt-blocks.md create mode 100644 docs/src/chapter-06-gbdialog/script-execution-flow.md create mode 100644 docs/src/chapter-09-api/llm-rest-server.md create mode 100644 docs/src/chapter-11-features/editions.md create mode 100644 migrations/2025-01-20-000001_multi_agent_bots/down.sql create mode 100644 migrations/2025-01-20-000001_multi_agent_bots/up.sql create mode 100644 migrations/6.1.0_table_keyword/down.sql create mode 100644 migrations/6.1.0_table_keyword/up.sql create mode 100644 src/basic/keywords/add_bot.rs create mode 100644 src/basic/keywords/play.rs create mode 100644 src/basic/keywords/table_definition.rs create mode 100644 src/timeseries/mod.rs create mode 100644 templates/bank.gbai/start.bas delete mode 100644 templates/base.html create mode 100644 templates/bling.gbai/bling.gbdialog/README.md create mode 100644 templates/bling.gbai/bling.gbdialog/add-stock.bas create mode 100644 templates/bling.gbai/bling.gbdialog/data-analysis.bas create mode 100644 templates/bling.gbai/bling.gbdialog/refresh-llm.bas create mode 100644 templates/bling.gbai/bling.gbdialog/start.bas create mode 100644 templates/bling.gbai/bling.gbdialog/sync-accounts.bas create mode 100644 templates/bling.gbai/bling.gbdialog/sync-erp.bas create mode 100644 templates/bling.gbai/bling.gbdialog/sync-inventory.bas create mode 100644 templates/bling.gbai/bling.gbdialog/sync-suppliers.bas create mode 100644 templates/bling.gbai/bling.gbdialog/tables.bas create mode 100644 templates/bling.gbai/bling.gbot/config.csv delete mode 100644 templates/chat.html create mode 100644 templates/compliance/hipaa-medical.gbai/README.md create mode 100644 templates/compliance/hipaa-medical.gbai/hipaa.gbdialog/start.bas create mode 100644 templates/compliance/hipaa-medical.gbai/hipaa.gbot/config.csv create mode 100644 templates/compliance/privacy.gbai/README.md create mode 100644 templates/compliance/privacy.gbai/privacy.gbdialog/delete-data.bas create mode 100644 templates/compliance/privacy.gbai/privacy.gbdialog/export-data.bas create mode 100644 templates/compliance/privacy.gbai/privacy.gbdialog/manage-consents.bas create mode 100644 templates/compliance/privacy.gbai/privacy.gbdialog/request-data.bas create mode 100644 templates/compliance/privacy.gbai/privacy.gbdialog/start.bas create mode 100644 templates/compliance/privacy.gbai/privacy.gbot/config.csv create mode 100644 templates/compliance/privacy.gbai/privacy.gbui/index.html delete mode 100644 templates/drive.html delete mode 100644 templates/home.html rename templates/{ => integration}/api-client.gbai/api-client.gbdialog/climate.bas (100%) rename templates/{ => integration}/api-client.gbai/api-client.gbdialog/msft-partner-center.bas (100%) rename templates/{ => integration}/public-apis.gbai/KEYWORDS_CHECKLIST.md (100%) rename templates/{ => integration}/public-apis.gbai/QUICKSTART.md (100%) rename templates/{ => integration}/public-apis.gbai/README.md (100%) rename templates/{ => integration}/public-apis.gbai/public-apis.gbdialog/animals-apis.bas (100%) rename templates/{ => integration}/public-apis.gbai/public-apis.gbdialog/data-utility-apis.bas (100%) rename templates/{ => integration}/public-apis.gbai/public-apis.gbdialog/entertainment-apis.bas (100%) rename templates/{ => integration}/public-apis.gbai/public-apis.gbdialog/food-apis.bas (100%) rename templates/{ => integration}/public-apis.gbai/public-apis.gbdialog/science-space-apis.bas (100%) rename templates/{ => integration}/public-apis.gbai/public-apis.gbdialog/weather-apis.bas (100%) delete mode 100644 templates/mail.html delete mode 100644 templates/meet.html create mode 100644 templates/platform/analytics.gbai/analytics.gbdialog/custom-report.bas create mode 100644 templates/platform/analytics.gbai/analytics.gbdialog/platform-overview.bas create mode 100644 templates/platform/analytics.gbai/analytics.gbdialog/start.bas create mode 100644 templates/platform/analytics.gbai/analytics.gbot/config.csv rename templates/{ => productivity}/office.gbai/office.gbdialog/api-integration.bas (100%) rename templates/{ => productivity}/office.gbai/office.gbdialog/data-sync.bas (100%) rename templates/{ => productivity}/office.gbai/office.gbdialog/document-processor.bas (100%) rename templates/{ => productivity}/office.gbai/office.gbdialog/start.bas (100%) rename templates/{ => productivity}/office.gbai/office.gbot/config.csv (100%) rename templates/{ => productivity}/reminder.gbai/reminder.gbdata/reminders.csv (100%) rename templates/{ => productivity}/reminder.gbai/reminder.gbdialog/add-reminder.bas (100%) rename templates/{ => productivity}/reminder.gbai/reminder.gbdialog/reminder.bas (100%) rename templates/{ => productivity}/reminder.gbai/reminder.gbdialog/start.bas (100%) create mode 100644 templates/sales/crm.gbai/crm.gbdialog/account-management.bas create mode 100644 templates/sales/crm.gbai/crm.gbdialog/activity-tracking.bas rename templates/{ => sales}/crm.gbai/crm.gbdialog/analyze-customer-sentiment.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/basic-check.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/case-management.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/create-lead-from-draft.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/crm-jobs.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/data-enrichment.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/geral.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/lead-management.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/myitems.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/new_email.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/new_session.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/on-emulator-sent.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/on-receive-email.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/on_transfer.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/opportunity-management.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/send-proposal-v0.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/send-proposal.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/tables.bas (100%) rename templates/{ => sales}/crm.gbai/crm.gbdialog/update-opportunity.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/add-new-idea.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/broadcast.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/campaigns/lead-nurture-campaign.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/campaigns/product-launch-campaign.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/campaigns/welcome-campaign.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/get-image.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/post-to-instagram.bas (100%) rename templates/{ => sales}/marketing.gbai/marketing.gbdialog/poster.bas (100%) delete mode 100644 templates/tasks.html delete mode 100644 templates/tools.html create mode 100644 ui/suite/analytics/analytics.html create mode 100644 ui/suite/calendar/calendar.html create mode 100644 ui/suite/chat/projector.html create mode 100644 ui/suite/css/apps-extended.css create mode 100644 ui/suite/css/components.css create mode 100644 ui/suite/paper/paper.html create mode 100644 ui/suite/research/research.html diff --git a/Cargo.toml b/Cargo.toml index 1f6fd8577..bd742ee49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ compliance = ["dep:csv"] attendance = [] directory = [] weba = [] +timeseries = [] # ===== OPTIONAL INFRASTRUCTURE ===== redis-cache = ["dep:redis"] @@ -82,7 +83,7 @@ progress-bars = ["dep:indicatif"] # ===== META FEATURES (BUNDLES) ===== full = [ "ui-server", "desktop", "console", - "vectordb", "llm", "nvidia", + "vectordb", "llm", "nvidia", "timeseries", "email", "whatsapp", "instagram", "msteams", "chat", "drive", "tasks", "calendar", "meet", "mail", "compliance", "attendance", "directory", "weba", @@ -91,7 +92,7 @@ full = [ communications = ["email", "whatsapp", "instagram", "msteams", "chat", "redis-cache"] productivity = ["chat", "drive", "tasks", "calendar", "meet", "mail", "redis-cache"] -enterprise = ["compliance", "attendance", "directory", "llm", "vectordb", "monitoring"] +enterprise = ["compliance", "attendance", "directory", "llm", "vectordb", "monitoring", "timeseries"] minimal = ["ui-server", "chat"] lightweight = ["ui-server", "chat", "drive", "tasks"] diff --git a/docs/HEAR_VALIDATION_REFERENCE.md b/docs/HEAR_VALIDATION_REFERENCE.md new file mode 100644 index 000000000..951d23fbf --- /dev/null +++ b/docs/HEAR_VALIDATION_REFERENCE.md @@ -0,0 +1,788 @@ +# HEAR Keyword - Input Validation Reference + +> Complete reference for HEAR keyword with automatic input validation in General Bots BASIC + +## Overview + +The `HEAR` keyword waits for user input with optional automatic validation. When using `HEAR AS `, the system will: + +1. Wait for user input +2. Validate against the specified type +3. **Automatically retry** with a helpful error message if invalid +4. Return the normalized/parsed value once valid + +This eliminates the need for manual validation loops and provides a consistent, user-friendly experience. + +--- + +## Table of Contents + +1. [Basic HEAR](#basic-hear) +2. [Text Validation Types](#text-validation-types) +3. [Numeric Types](#numeric-types) +4. [Date/Time Types](#datetime-types) +5. [Brazilian Document Types](#brazilian-document-types) +6. [Contact Types](#contact-types) +7. [Menu Selection](#menu-selection) +8. [Media Types](#media-types) +9. [Authentication Types](#authentication-types) +10. [Examples](#examples) +11. [Best Practices](#best-practices) + +--- + +## Basic HEAR + +```basic +' Simple HEAR without validation - accepts any input +HEAR response +TALK "You said: " + response +``` + +--- + +## Text Validation Types + +### HEAR AS EMAIL + +Validates email address format and normalizes to lowercase. + +```basic +TALK "What's your email address?" +HEAR email AS EMAIL +TALK "We'll send confirmation to: " + email +``` + +**Validation:** +- Must contain `@` symbol +- Must have valid domain format +- Normalized to lowercase + +**Error message:** "Please enter a valid email address (e.g., user@example.com)" + +--- + +### HEAR AS NAME + +Validates name format (letters, spaces, hyphens, apostrophes). + +```basic +TALK "What's your full name?" +HEAR name AS NAME +TALK "Nice to meet you, " + name + "!" +``` + +**Validation:** +- Minimum 2 characters +- Maximum 100 characters +- Only letters, spaces, hyphens, apostrophes +- Auto-capitalizes first letter of each word + +**Error message:** "Please enter a valid name (letters and spaces only)" + +--- + +### HEAR AS URL + +Validates and normalizes URL format. + +```basic +TALK "Enter your website URL:" +HEAR website AS URL +TALK "I'll check " + website +``` + +**Validation:** +- Valid URL format +- Auto-adds `https://` if protocol missing + +**Error message:** "Please enter a valid URL" + +--- + +### HEAR AS PASSWORD + +Validates password strength (minimum requirements). + +```basic +TALK "Create a password (minimum 8 characters):" +HEAR password AS PASSWORD +' Returns "[PASSWORD SET]" - actual password stored securely +``` + +**Validation:** +- Minimum 8 characters +- Returns strength indicator (weak/medium/strong) +- Never echoes the actual password + +**Error message:** "Password must be at least 8 characters" + +--- + +### HEAR AS COLOR + +Validates and normalizes color values. + +```basic +TALK "Pick a color:" +HEAR color AS COLOR +TALK "You selected: " + color ' Returns hex format like #FF0000 +``` + +**Accepts:** +- Named colors: "red", "blue", "green", etc. +- Hex format: "#FF0000" or "FF0000" +- RGB format: "rgb(255, 0, 0)" + +**Returns:** Normalized hex format (#RRGGBB) + +--- + +### HEAR AS UUID + +Validates UUID/GUID format. + +```basic +TALK "Enter the transaction ID:" +HEAR transaction_id AS UUID +``` + +--- + +## Numeric Types + +### HEAR AS INTEGER + +Validates and parses integer numbers. + +```basic +TALK "How old are you?" +HEAR age AS INTEGER +TALK "In 10 years you'll be " + STR(age + 10) +``` + +**Validation:** +- Accepts whole numbers only +- Removes formatting (commas, spaces) +- Returns numeric value + +**Error message:** "Please enter a valid whole number" + +--- + +### HEAR AS FLOAT / DECIMAL + +Validates and parses decimal numbers. + +```basic +TALK "Enter the temperature:" +HEAR temperature AS FLOAT +TALK "Temperature is " + STR(temperature) + "°C" +``` + +**Validation:** +- Accepts decimal numbers +- Handles both `.` and `,` as decimal separator +- Returns numeric value rounded to 2 decimal places + +--- + +### HEAR AS MONEY / CURRENCY / AMOUNT + +Validates and normalizes monetary amounts. + +```basic +TALK "How much would you like to transfer?" +HEAR amount AS MONEY +TALK "Transferring R$ " + FORMAT(amount, "#,##0.00") +``` + +**Accepts:** +- "100" +- "100.00" +- "1,234.56" (US format) +- "1.234,56" (Brazilian/European format) +- "R$ 100,00" +- "$100.00" + +**Returns:** Normalized decimal value (e.g., "1234.56") + +**Error message:** "Please enter a valid amount (e.g., 100.00 or R$ 100,00)" + +--- + +### HEAR AS CREDITCARD / CARD + +Validates credit card number using Luhn algorithm. + +```basic +TALK "Enter your card number:" +HEAR card AS CREDITCARD +' Returns masked format: "4111 **** **** 1111" +``` + +**Validation:** +- 13-19 digits +- Passes Luhn checksum +- Detects card type (Visa, Mastercard, Amex, etc.) + +**Returns:** Masked card number with metadata about card type + +--- + +## Date/Time Types + +### HEAR AS DATE + +Validates and parses date input. + +```basic +TALK "When is your birthday?" +HEAR birthday AS DATE +TALK "Your birthday is " + FORMAT(birthday, "MMMM d") +``` + +**Accepts multiple formats:** +- "25/12/2024" (DD/MM/YYYY) +- "12/25/2024" (MM/DD/YYYY) +- "2024-12-25" (ISO format) +- "25 Dec 2024" +- "December 25, 2024" +- "today", "tomorrow", "yesterday" +- "hoje", "amanhã", "ontem" (Portuguese) + +**Returns:** Normalized ISO date (YYYY-MM-DD) + +**Error message:** "Please enter a valid date (e.g., 25/12/2024 or 2024-12-25)" + +--- + +### HEAR AS HOUR / TIME + +Validates and parses time input. + +```basic +TALK "What time should we schedule the meeting?" +HEAR meeting_time AS HOUR +TALK "Meeting scheduled for " + meeting_time +``` + +**Accepts:** +- "14:30" (24-hour format) +- "2:30 PM" (12-hour format) +- "14:30:00" (with seconds) + +**Returns:** Normalized 24-hour format (HH:MM) + +**Error message:** "Please enter a valid time (e.g., 14:30 or 2:30 PM)" + +--- + +## Brazilian Document Types + +### HEAR AS CPF + +Validates Brazilian CPF (individual taxpayer ID). + +```basic +TALK "Enter your CPF:" +HEAR cpf AS CPF +TALK "CPF validated: " + cpf ' Returns formatted: 123.456.789-09 +``` + +**Validation:** +- 11 digits +- Valid check digits (mod 11 algorithm) +- Rejects known invalid patterns (all same digit) + +**Returns:** Formatted CPF (XXX.XXX.XXX-XX) + +**Error message:** "Please enter a valid CPF (11 digits)" + +--- + +### HEAR AS CNPJ + +Validates Brazilian CNPJ (company taxpayer ID). + +```basic +TALK "Enter your company's CNPJ:" +HEAR cnpj AS CNPJ +TALK "CNPJ validated: " + cnpj ' Returns formatted: 12.345.678/0001-95 +``` + +**Validation:** +- 14 digits +- Valid check digits + +**Returns:** Formatted CNPJ (XX.XXX.XXX/XXXX-XX) + +**Error message:** "Please enter a valid CNPJ (14 digits)" + +--- + +## Contact Types + +### HEAR AS MOBILE / PHONE / TELEPHONE + +Validates phone number format. + +```basic +TALK "What's your phone number?" +HEAR phone AS MOBILE +TALK "We'll send SMS to: " + phone +``` + +**Validation:** +- 10-15 digits +- Auto-formats based on detected country + +**Returns:** Formatted phone number + +**Error message:** "Please enter a valid mobile number" + +--- + +### HEAR AS ZIPCODE / CEP / POSTALCODE + +Validates postal code format. + +```basic +TALK "What's your ZIP code?" +HEAR cep AS ZIPCODE +TALK "Your ZIP code is: " + cep +``` + +**Supports:** +- Brazilian CEP: 8 digits → "12345-678" +- US ZIP: 5 or 9 digits → "12345" or "12345-6789" +- UK postcode: alphanumeric → "SW1A 1AA" + +**Returns:** Formatted postal code with country detection + +--- + +## Menu Selection + +### HEAR AS "Option1", "Option2", "Option3" + +Presents a menu and validates selection. + +```basic +TALK "Choose your preferred fruit:" +HEAR fruit AS "Apple", "Banana", "Orange", "Mango" +TALK "You selected: " + fruit +``` + +**Accepts:** +- Exact match: "Apple" +- Case-insensitive: "apple" +- Numeric selection: "1", "2", "3" +- Partial match: "app" → "Apple" (if unique) + +**Automatically adds suggestions** for the menu options. + +**Error message:** "Please select one of: Apple, Banana, Orange, Mango" + +--- + +### HEAR AS BOOLEAN + +Validates yes/no response. + +```basic +TALK "Do you agree to the terms?" +HEAR agreed AS BOOLEAN +IF agreed THEN + TALK "Thank you for agreeing!" +ELSE + TALK "You must agree to continue." +END IF +``` + +**Accepts (true):** "yes", "y", "true", "1", "sim", "ok", "sure", "confirm" + +**Accepts (false):** "no", "n", "false", "0", "não", "cancel", "deny" + +**Returns:** "true" or "false" (with boolean metadata) + +**Error message:** "Please answer yes or no" + +--- + +### HEAR AS LANGUAGE + +Validates language code or name. + +```basic +TALK "What language do you prefer?" +HEAR language AS LANGUAGE +SET CONTEXT LANGUAGE language +TALK "Language set to: " + language +``` + +**Accepts:** +- ISO codes: "en", "pt", "es", "fr", "de" +- Full names: "English", "Portuguese", "Spanish" +- Native names: "Português", "Español", "Français" + +**Returns:** ISO 639-1 language code + +--- + +## Media Types + +### HEAR AS IMAGE / PHOTO / PICTURE + +Waits for image upload. + +```basic +TALK "Please send a photo of your document:" +HEAR document_photo AS IMAGE +TALK "Image received: " + document_photo +' Returns URL to the uploaded image +``` + +**Validation:** +- Must receive image attachment +- Accepts: JPG, PNG, GIF, WebP + +**Error message:** "Please send an image" + +--- + +### HEAR AS QRCODE + +Waits for image with QR code and reads it. + +```basic +TALK "Send me a photo of the QR code:" +HEAR qr_data AS QRCODE +TALK "QR code contains: " + qr_data +``` + +**Process:** +1. Waits for image upload +2. Calls BotModels vision API to decode QR +3. Returns the decoded data + +**Error message:** "Please send an image containing a QR code" + +--- + +### HEAR AS AUDIO / VOICE / SOUND + +Waits for audio input and transcribes to text. + +```basic +TALK "Send me a voice message:" +HEAR transcription AS AUDIO +TALK "You said: " + transcription +``` + +**Process:** +1. Waits for audio attachment +2. Calls BotModels speech-to-text API +3. Returns transcribed text + +**Error message:** "Please send an audio file or voice message" + +--- + +### HEAR AS VIDEO + +Waits for video upload and describes content. + +```basic +TALK "Send a video of the problem:" +HEAR video_description AS VIDEO +TALK "I can see: " + video_description +``` + +**Process:** +1. Waits for video attachment +2. Calls BotModels vision API to describe +3. Returns AI-generated description + +**Error message:** "Please send a video" + +--- + +### HEAR AS FILE / DOCUMENT / DOC / PDF + +Waits for document upload. + +```basic +TALK "Please upload your contract:" +HEAR contract AS DOCUMENT +TALK "Document received: " + contract +``` + +**Accepts:** PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT, CSV + +**Returns:** URL to the uploaded file + +--- + +## Authentication Types + +### HEAR AS LOGIN + +Waits for Active Directory/OAuth login completion. + +```basic +TALK "Please click the link to authenticate:" +HEAR user AS LOGIN +TALK "Welcome, " + user.name + "!" +``` + +**Process:** +1. Generates authentication URL +2. Waits for OAuth callback +3. Returns user object with tokens + +--- + +## Examples + +### Complete Registration Flow + +```basic +TALK "Let's create your account!" + +TALK "What's your full name?" +HEAR name AS NAME + +TALK "Enter your email address:" +HEAR email AS EMAIL + +TALK "Enter your CPF:" +HEAR cpf AS CPF + +TALK "What's your phone number?" +HEAR phone AS MOBILE + +TALK "Choose a password:" +HEAR password AS PASSWORD + +TALK "What's your birth date?" +HEAR birthdate AS DATE + +TALK "Select your gender:" +HEAR gender AS "Male", "Female", "Other", "Prefer not to say" + +' All inputs are now validated and normalized +TALK "Creating account for " + name + "..." + +TABLE new_user + ROW name, email, cpf, phone, birthdate, gender, NOW() +END TABLE +SAVE "users.csv", new_user + +TALK "✅ Account created successfully!" +``` + +### Payment Flow + +```basic +TALK "💳 Let's process your payment" + +TALK "Enter the amount:" +HEAR amount AS MONEY + +IF amount < 1 THEN + TALK "Minimum payment is R$ 1.00" + RETURN +END IF + +TALK "How would you like to pay?" +HEAR method AS "Credit Card", "Debit Card", "PIX", "Boleto" + +IF method = "PIX" THEN + TALK "Enter the PIX key (phone, email, or CPF):" + ' Note: We could create HEAR AS PIX_KEY if needed + HEAR pix_key +ELSEIF method = "Boleto" THEN + TALK "Enter the barcode (47-48 digits):" + HEAR barcode AS INTEGER +END IF + +TALK "Confirm payment of R$ " + FORMAT(amount, "#,##0.00") + "?" +HEAR confirm AS BOOLEAN + +IF confirm THEN + TALK "✅ Processing payment..." +ELSE + TALK "Payment cancelled." +END IF +``` + +### Customer Support with Media + +```basic +TALK "How can I help you today?" +HEAR issue AS "Report a bug", "Request feature", "Billing question", "Other" + +IF issue = "Report a bug" THEN + TALK "Please describe the problem:" + HEAR description + + TALK "Can you send a screenshot of the issue?" + HEAR screenshot AS IMAGE + + TALK "Thank you! We've logged your bug report." + TALK "Reference: BUG-" + FORMAT(NOW(), "yyyyMMddHHmmss") + +ELSEIF issue = "Billing question" THEN + TALK "Please upload your invoice or send the transaction ID:" + HEAR reference +END IF +``` + +--- + +## Best Practices + +### 1. Always Use Appropriate Types + +```basic +' ❌ Bad - no validation +HEAR email +IF NOT email CONTAINS "@" THEN + TALK "Invalid email" + ' Need to implement retry logic... +END IF + +' ✅ Good - automatic validation and retry +HEAR email AS EMAIL +' Guaranteed to be valid when we get here +``` + +### 2. Combine with Context + +```basic +SET CONTEXT "You are a helpful banking assistant. +When asking for monetary values, always confirm before processing." + +TALK "How much would you like to withdraw?" +HEAR amount AS MONEY +' LLM and validation work together +``` + +### 3. Use Menu for Limited Options + +```basic +' ❌ Bad - open-ended when options are known +HEAR payment_method +IF payment_method <> "credit" AND payment_method <> "debit" THEN + ' Handle unknown input... +END IF + +' ✅ Good - constrained to valid options +HEAR payment_method AS "Credit Card", "Debit Card", "PIX" +``` + +### 4. Provide Context Before HEAR + +```basic +' ❌ Bad - no context +HEAR value AS MONEY + +' ✅ Good - user knows what to enter +TALK "Enter the transfer amount (minimum R$ 1.00):" +HEAR amount AS MONEY +``` + +### 5. Use HEAR AS for Security-Sensitive Data + +```basic +' CPF is automatically validated +HEAR cpf AS CPF + +' Credit card passes Luhn check and is masked +HEAR card AS CREDITCARD + +' Password never echoed back +HEAR password AS PASSWORD +``` + +--- + +## Error Handling + +Validation errors are handled automatically, but you can customize: + +```basic +' The system automatically retries up to 3 times +' After 3 failures, execution continues with empty value + +' You can check if validation succeeded: +HEAR email AS EMAIL +IF email = "" THEN + TALK "Unable to validate email after multiple attempts." + TALK "Please contact support for assistance." + RETURN +END IF +``` + +--- + +## Metadata Access + +Some validation types provide additional metadata: + +```basic +HEAR card AS CREDITCARD +' card = "**** **** **** 1234" +' Metadata available: card_type, last_four + +HEAR date AS DATE +' date = "2024-12-25" +' Metadata available: original input, parsed format + +HEAR audio AS AUDIO +' audio = "transcribed text here" +' Metadata available: language, confidence +``` + +--- + +## Integration with BotModels + +Media types (QRCODE, AUDIO, VIDEO) automatically call BotModels services: + +| Type | BotModels Endpoint | Service | +|------|-------------------|---------| +| QRCODE | `/api/v1/vision/qrcode` | QR Code detection | +| AUDIO | `/api/v1/speech/to-text` | Whisper transcription | +| VIDEO | `/api/v1/vision/describe-video` | BLIP2 video description | +| IMAGE (with question) | `/api/v1/vision/vqa` | Visual Q&A | + +Configure BotModels URL in `config.csv`: +``` +botmodels-url,http://localhost:8001 +botmodels-enabled,true +``` + +--- + +## Summary Table + +| Type | Example Input | Normalized Output | +|------|---------------|-------------------| +| EMAIL | "User@Example.COM" | "user@example.com" | +| NAME | "john DOE" | "John Doe" | +| INTEGER | "1,234" | 1234 | +| MONEY | "R$ 1.234,56" | "1234.56" | +| DATE | "25/12/2024" | "2024-12-25" | +| HOUR | "2:30 PM" | "14:30" | +| BOOLEAN | "yes" / "sim" | "true" | +| CPF | "12345678909" | "123.456.789-09" | +| MOBILE | "11999998888" | "(11) 99999-8888" | +| CREDITCARD | "4111111111111111" | "4111 **** **** 1111" | +| QRCODE | [image] | "decoded QR data" | +| AUDIO | [audio file] | "transcribed text" | + +--- + +*HEAR AS validation - Making input handling simple, secure, and user-friendly.* \ No newline at end of file diff --git a/docs/MULTI_AGENT_OFFICE_SUITE.md b/docs/MULTI_AGENT_OFFICE_SUITE.md new file mode 100644 index 000000000..0aacb0972 --- /dev/null +++ b/docs/MULTI_AGENT_OFFICE_SUITE.md @@ -0,0 +1,3550 @@ +# Multi-Agent Office Suite - Complete Design Document + +## 🎯 Vision: Beat Microsoft 365, Google Workspace & All AI Competitors + +**General Bots = Multi-Agent AI + Complete Office Suite + Research Engine + Banking + Everything** + +This document outlines the complete implementation plan to make General Bots the world's most powerful FREE enterprise platform. + +--- + +## 📋 Table of Contents + +1. [BOT Keyword - Multi-Agent System](#1-bot-keyword---multi-agent-system) +2. [Chat UI Enhancements](#2-chat-ui-enhancements) +3. [Conversational Banking (bank.gbai)](#3-conversational-banking-bankgbai) +4. [Excel Clone (HTMX/Rust)](#4-excel-clone-htmxrust) +5. [Word Editor for .docx](#5-word-editor-for-docx) +6. [M365/Office Competitive Analysis](#6-m365office-competitive-analysis) +7. [Google/MS Graph API Compatibility](#7-googlems-graph-api-compatibility) +8. [Copilot/Gemini Feature Parity](#8-copilotgemini-feature-parity) +9. [Attachment System (Plus Button)](#9-attachment-system-plus-button) +10. [Conversation Branching](#10-conversation-branching) +11. [PLAY Keyword - Content Projector](#11-play-keyword---content-projector) +12. [Implementation Priority](#12-implementation-priority) + +--- + +## 1. BOT Keyword - Multi-Agent System + +### Concept + +Every conversation becomes a **group conversation** where multiple specialized bots can participate. Bots join based on triggers (tools, schedules, keywords) and collaborate to answer complex queries. + +### Keywords + +```basic +' Add a bot to the conversation +ADD BOT "finance-expert" WITH TRIGGER "money, budget, invoice, payment" +ADD BOT "legal-advisor" WITH TRIGGER "contract, agreement, compliance" +ADD BOT "hr-assistant" WITH TRIGGER "employee, vacation, hiring" + +' Add bot with tool-based trigger +ADD BOT "data-analyst" WITH TOOLS "AGGREGATE, CHART, REPORT" + +' Add bot with schedule-based participation +ADD BOT "daily-reporter" WITH SCHEDULE "0 9 * * *" + +' Remove bot from conversation +REMOVE BOT "finance-expert" + +' List active bots +bots = LIST BOTS + +' Set bot priority (who answers first) +SET BOT PRIORITY "legal-advisor", 1 + +' Bot-to-bot delegation +DELEGATE TO "specialist-bot" WITH CONTEXT current_conversation + +' Create bot swarm for complex tasks +CREATE SWARM "research-team" WITH BOTS "researcher, analyst, writer" +``` + +### Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CONVERSATION ORCHESTRATOR │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ User Message ──▶ Trigger Analyzer ──▶ Bot Selector │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌──────────────┐ │ +│ │ Keyword Triggers │ │ Tool Triggers │ │ +│ │ - finance terms │ │ - AGGREGATE │ │ +│ │ - legal terms │ │ - CHART │ │ +│ │ - hr terms │ │ - specific │ │ +│ └─────────────────┘ └──────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────┐ │ +│ │ BOT RESPONSE AGGREGATOR │ │ +│ │ - Merge responses │ │ +│ │ - Resolve conflicts │ │ +│ │ - Format for user │ │ +│ └─────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Database Schema + +```sql +-- Bot definitions +CREATE TABLE bots ( + id UUID PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + system_prompt TEXT, + model_config JSONB, + tools JSONB, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Bot triggers +CREATE TABLE bot_triggers ( + id UUID PRIMARY KEY, + bot_id UUID REFERENCES bots(id), + trigger_type VARCHAR(50), -- 'keyword', 'tool', 'schedule', 'event' + trigger_config JSONB, + priority INT DEFAULT 0, + is_active BOOLEAN DEFAULT true +); + +-- Session bot associations +CREATE TABLE session_bots ( + id UUID PRIMARY KEY, + session_id UUID, + bot_id UUID REFERENCES bots(id), + joined_at TIMESTAMPTZ DEFAULT NOW(), + priority INT DEFAULT 0, + is_active BOOLEAN DEFAULT true +); + +-- Bot message history +CREATE TABLE bot_messages ( + id UUID PRIMARY KEY, + session_id UUID, + bot_id UUID REFERENCES bots(id), + content TEXT, + role VARCHAR(50), + created_at TIMESTAMPTZ DEFAULT NOW() +); +``` + +### Rust Implementation + +```rust +// src/basic/keywords/add_bot.rs + +use crate::shared::models::UserSession; +use crate::shared::state::AppState; +use rhai::{Dynamic, Engine}; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BotTrigger { + pub trigger_type: TriggerType, + pub keywords: Option>, + pub tools: Option>, + pub schedule: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum TriggerType { + Keyword, + Tool, + Schedule, + Event, +} + +pub fn add_bot_keyword(state: Arc, user: UserSession, engine: &mut Engine) { + let state_clone = Arc::clone(&state); + let user_clone = user.clone(); + + // ADD BOT "name" WITH TRIGGER "keywords" + engine.register_custom_syntax( + &["ADD", "BOT", "$expr$", "WITH", "TRIGGER", "$expr$"], + false, + move |context, inputs| { + let bot_name = context.eval_expression_tree(&inputs[0])?.to_string(); + let trigger = context.eval_expression_tree(&inputs[1])?.to_string(); + + let state_for_thread = Arc::clone(&state_clone); + let session_id = user_clone.id; + + let (tx, rx) = std::sync::mpsc::channel(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + let result = rt.block_on(async { + add_bot_to_session( + &state_for_thread, + session_id, + &bot_name, + BotTrigger { + trigger_type: TriggerType::Keyword, + keywords: Some(trigger.split(',').map(|s| s.trim().to_string()).collect()), + tools: None, + schedule: None, + } + ).await + }); + let _ = tx.send(result); + }); + + match rx.recv_timeout(std::time::Duration::from_secs(30)) { + Ok(Ok(msg)) => Ok(Dynamic::from(msg)), + Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + e.into(), + rhai::Position::NONE, + ))), + Err(_) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + "ADD BOT timed out".into(), + rhai::Position::NONE, + ))), + } + }, + ); + + // ADD BOT "name" WITH TOOLS "tool1, tool2" + engine.register_custom_syntax( + &["ADD", "BOT", "$expr$", "WITH", "TOOLS", "$expr$"], + false, + move |context, inputs| { + // Similar implementation for tool-based triggers + }, + ); + + // ADD BOT "name" WITH SCHEDULE "cron" + engine.register_custom_syntax( + &["ADD", "BOT", "$expr$", "WITH", "SCHEDULE", "$expr$"], + false, + move |context, inputs| { + // Similar implementation for schedule-based triggers + }, + ); +} + +async fn add_bot_to_session( + state: &AppState, + session_id: Uuid, + bot_name: &str, + trigger: BotTrigger, +) -> Result { + // Implementation to add bot to session +} +``` + +### Multi-Agent Orchestrator + +```rust +// src/core/multi_agent.rs + +use std::collections::HashMap; +use uuid::Uuid; + +pub struct MultiAgentOrchestrator { + state: Arc, + active_bots: HashMap, +} + +impl MultiAgentOrchestrator { + pub async fn process_message( + &self, + session_id: Uuid, + message: &str, + ) -> Result, Error> { + // 1. Get all active bots for this session + let bots = self.get_session_bots(session_id).await?; + + // 2. Analyze message and match triggers + let matching_bots = self.match_triggers(&bots, message).await?; + + // 3. If no specific bot matches, use default + if matching_bots.is_empty() { + return self.default_bot_response(session_id, message).await; + } + + // 4. Get responses from all matching bots + let mut responses = Vec::new(); + for bot in matching_bots { + let response = self.get_bot_response(&bot, session_id, message).await?; + responses.push(response); + } + + // 5. Aggregate responses + let final_response = self.aggregate_responses(responses).await?; + + Ok(final_response) + } + + async fn match_triggers( + &self, + bots: &[BotInstance], + message: &str, + ) -> Vec { + let mut matching = Vec::new(); + let message_lower = message.to_lowercase(); + + for bot in bots { + if let Some(trigger) = &bot.trigger { + match trigger.trigger_type { + TriggerType::Keyword => { + if let Some(keywords) = &trigger.keywords { + for keyword in keywords { + if message_lower.contains(&keyword.to_lowercase()) { + matching.push(bot.clone()); + break; + } + } + } + } + TriggerType::Tool => { + // Check if message implies using specific tools + } + _ => {} + } + } + } + + // Sort by priority + matching.sort_by(|a, b| b.priority.cmp(&a.priority)); + matching + } + + async fn aggregate_responses( + &self, + responses: Vec, + ) -> Result, Error> { + // Use LLM to merge multiple bot responses into coherent answer + // Or return all responses with bot attribution + Ok(responses) + } +} +``` + +--- + +## 2. Chat UI Enhancements + +### 2.1 Poe/Perplexity-Style Features + +#### Chat Interface Components + +```html + + +
+ +
+
+ +
+ +
+ + +
+ + Connected +
+ + +
+ +
+ + + + + +
+ +
+
+ + + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+``` + +### 2.2 Simple Chat/Talk UIs + +#### Intercom-Style Widget + +```html + + +
+ + + +
+ + +``` + +#### PTT (Push-to-Talk) Interface + +```html + + +
+
+ 🔇 + Press and hold to talk +
+ +
+ + +
+ + + +
+ +
+ +
+ +
+
+ + + + +``` + +#### Totem/Kiosk Interface + +```html + + + + + + + + Bot Totem + + + +
+ +

How can I help you today?

+
+ +
+
+ 🤖 +
+ +
+ Touch any option below or tap the microphone to speak +
+ +
+
+
🗺️
+
Directions
+
+
+
📅
+
Schedule
+
+
+
🏢
+
Services
+
+
+
📞
+
Contact
+
+
+
🎤
+
Speak
+
+
+
+
Help
+
+
+
+ +
+
+ 👆 + Touch to interact +
+
+ + + + +``` + +--- + +## 3. Conversational Banking (bank.gbai) + +### Complete Banking Template + +``` +templates/bank.gbai/ +├── bank.gbdialog/ +│ └── start.json +├── bank.gbot/ +│ └── config.csv +├── bank.gbkb/ +│ └── banking-faq.md +├── dialogs/ +│ ├── account.bas +│ ├── transfer.bas +│ ├── payment.bas +│ ├── loan.bas +│ ├── investment.bas +│ ├── cards.bas +│ └── support.bas +├── tables/ +│ ├── accounts.csv +│ ├── transactions.csv +│ ├── cards.csv +│ ├── loans.csv +│ ├── beneficiaries.csv +│ └── scheduled_payments.csv +└── README.md +``` + +### Bank Configuration + +```csv +# bank.gbot/config.csv +key,value +bank-name,General Bank +bank-code,001 +swift-code,GENBBRSP +support-phone,0800-123-4567 +support-email,support@generalbank.com +pix-enabled,true +ted-enabled,true +doc-enabled,true +boleto-enabled,true +credit-card-enabled,true +debit-card-enabled,true +investment-enabled,true +loan-enabled,true +insurance-enabled,true +two-factor-auth,true +transaction-limit-default,5000.00 +daily-limit-default,20000.00 +``` + +### Account Management + +```basic +' dialogs/account.bas + +' Show account balance +SUB ShowBalance() + user_id = GET USER ID + + accounts = FIND "accounts.csv" WHERE user_id = user_id + + IF LEN(accounts) = 0 THEN + TALK "You don't have any accounts registered. Would you like to open one?" + RETURN + END IF + + TALK "Here are your account balances:" + TALK "" + + total = 0 + FOR EACH account IN accounts + TALK "📊 **" + account.account_type + " Account**" + TALK " Account: " + account.account_number + TALK " Balance: R$ " + FORMAT(account.balance, "0.00") + TALK " Available: R$ " + FORMAT(account.available_balance, "0.00") + TALK "" + total = total + account.balance + NEXT + + TALK "💰 **Total Balance: R$ " + FORMAT(total, "0.00") + "**" +END SUB + +' Show recent transactions +SUB ShowTransactions(account_number, days) + IF days = "" THEN days = 30 END IF + + start_date = DATEADD(NOW(), -days, "day") + + transactions = FIND "transactions.csv" WHERE account_number = account_number AND date >= start_date ORDER BY date DESC LIMIT 20 + + IF LEN(transactions) = 0 THEN + TALK "No transactions found in the last " + days + " days." + RETURN + END IF + + TALK "📋 **Recent Transactions**" + TALK "" + + FOR EACH tx IN transactions + IF tx.type = "credit" THEN + icon = "💵" + sign = "+" + ELSE + icon = "💸" + sign = "-" + END IF + + TALK icon + " " + FORMAT(tx.date, "dd/MM") + " | " + tx.description + TALK " " + sign + "R$ " + FORMAT(tx.amount, "0.00") + " | Balance: R$ " + FORMAT(tx.balance_after, "0.00") + TALK "" + NEXT +END SUB + +' Generate account statement +SUB GenerateStatement(account_number, start_date, end_date) + transactions = FIND "transactions.csv" WHERE account_number = account_number AND date >= start_date AND date <= end_date ORDER BY date + + TABLE statement + COLUMN "Date" FORMAT "dd/MM/yyyy" + COLUMN "Description" + COLUMN "Type" + COLUMN "Amount" FORMAT "R$ #,##0.00" + COLUMN "Balance" FORMAT "R$ #,##0.00" + + FOR EACH tx IN transactions + ROW tx.date, tx.description, tx.type, tx.amount, tx.balance_after + NEXT + END TABLE + + ' Export to PDF + pdf_file = EXPORT TABLE statement TO "pdf" WITH TITLE "Account Statement - " + account_number + + TALK "Your statement is ready!" + TALK "📄 [Download Statement](" + pdf_file + ")" + + ' Send by email + email = GET USER email + IF email <> "" THEN + SEND MAIL email, "Your Account Statement", "Please find attached your account statement.", pdf_file + TALK "I've also sent a copy to your email." + END IF +END SUB + +' Open new account +SUB OpenAccount(account_type) + user_id = GET USER ID + user = GET USER + + ' Verify KYC + IF NOT user.kyc_verified THEN + TALK "To open a new account, we need to verify your identity first." + CALL VerifyKYC() + RETURN + END IF + + ' Generate account number + account_number = GenerateAccountNumber() + + ' Create account + TABLE new_account + ROW account_number, user_id, account_type, 0.00, 0.00, NOW(), "active" + END TABLE + + SAVE "accounts.csv", new_account + + TALK "🎉 Congratulations! Your " + account_type + " account has been created!" + TALK "" + TALK "📋 **Account Details**" + TALK "Account Number: " + account_number + TALK "Type: " + account_type + TALK "Status: Active" + TALK "" + TALK "Your virtual debit card is being generated..." + + ' Create virtual card + CALL CreateVirtualCard(account_number) +END SUB + +FUNCTION GenerateAccountNumber() + ' Generate unique account number + branch = "0001" + sequence = GET BOT MEMORY "account_sequence" + IF sequence = "" THEN sequence = 10000 END IF + sequence = sequence + 1 + SET BOT MEMORY "account_sequence", sequence + + account = branch + "-" + FORMAT(sequence, "000000") + digit = CalculateCheckDigit(account) + + RETURN account + "-" + digit +END FUNCTION +``` + +### Money Transfers + +```basic +' dialogs/transfer.bas + +' PIX Transfer +SUB PIXTransfer() + TALK "Let's make a PIX transfer. What type of key will you use?" + + ADD SUGGESTION "CPF/CNPJ" + ADD SUGGESTION "Phone" + ADD SUGGESTION "Email" + ADD SUGGESTION "Random Key" + + key_type = HEAR + + TALK "Enter the PIX key:" + pix_key = HEAR + + ' Validate and get recipient info + recipient = ValidatePIXKey(key_type, pix_key) + + IF recipient.error THEN + TALK "❌ Invalid PIX key. Please check and try again." + RETURN + END IF + + TALK "Recipient: **" + recipient.name + "**" + TALK "Bank: " + recipient.bank_name + TALK "" + TALK "Enter the amount to transfer:" + + amount = HEAR + amount = ParseMoney(amount) + + ' Check balance and limits + account = GET USER primary_account + + IF amount > account.available_balance THEN + TALK "❌ Insufficient balance. Available: R$ " + FORMAT(account.available_balance, "0.00") + RETURN + END IF + + daily_used = GetDailyTransferTotal(account.account_number) + daily_limit = GET USER daily_transfer_limit + + IF daily_used + amount > daily_limit THEN + TALK "❌ This transfer would exceed your daily limit." + TALK "Daily limit: R$ " + FORMAT(daily_limit, "0.00") + TALK "Already used: R$ " + FORMAT(daily_used, "0.00") + TALK "Available: R$ " + FORMAT(daily_limit - daily_used, "0.00") + RETURN + END IF + + ' Confirm transaction + TALK "📤 **Transfer Summary**" + TALK "To: " + recipient.name + TALK "PIX Key: " + MaskPIXKey(pix_key) + TALK "Amount: R$ " + FORMAT(amount, "0.00") + TALK "" + TALK "Confirm this transfer?" + + ADD SUGGESTION "Yes, confirm" + ADD SUGGESTION "No, cancel" + + confirmation = HEAR + + IF confirmation CONTAINS "yes" OR confirmation CONTAINS "confirm" THEN + ' Request 2FA + TALK "For your security, enter the code sent to your phone:" + code = HEAR + + IF NOT Verify2FA(code) THEN + TALK "❌ Invalid code. Transfer cancelled for security." + RETURN + END IF + + ' Execute transfer + result = ExecutePIXTransfer(account.account_number, recipient, amount) + + IF result.success THEN + TALK "✅ **Transfer completed!**" + TALK "Transaction ID: " + result.transaction_id + TALK "New balance: R$ " + FORMAT(result.new_balance, "0.00") + + ' Save transaction + TABLE transaction + ROW result.transaction_id, account.account_number, "pix_out", amount, result.new_balance, NOW(), recipient.pix_key, recipient.name, "completed" + END TABLE + SAVE "transactions.csv", transaction + ELSE + TALK "❌ Transfer failed: " + result.error + END IF + ELSE + TALK "Transfer cancelled." + END IF +END SUB + +' TED Transfer +SUB TEDTransfer() + TALK "Let's make a TED transfer." + + ' Get recipient bank info + TALK "Enter the bank code (e.g., 001 for Banco do Brasil):" + bank_code = HEAR + + TALK "Enter the branch number:" + branch = HEAR + + TALK "Enter the account number (with digit):" + account_number = HEAR + + TALK "Enter the recipient's full name:" + recipient_name = HEAR + + TALK "Enter the recipient's CPF/CNPJ:" + document = HEAR + + TALK "Enter the amount to transfer:" + amount = HEAR + amount = ParseMoney(amount) + + ' Validate and process similar to PIX + ' ... (similar flow with bank validation) +END SUB + +' Schedule recurring transfer +SUB ScheduleTransfer() + TALK "Let's schedule a recurring transfer." + + TALK "How often should the transfer occur?" + ADD SUGGESTION "Weekly" + ADD SUGGESTION "Monthly" + ADD SUGGESTION "Custom" + + frequency = HEAR + + ' Get transfer details + TALK "Enter the PIX key of the recipient:" + pix_key = HEAR + + TALK "Enter the amount:" + amount = HEAR + + TALK "When should the first transfer occur?" + start_date = HEAR + + ' Create scheduled payment + TABLE scheduled + ROW GenerateID(), GET USER ID, "pix", pix_key, amount, frequency, start_date, "active" + END TABLE + + SAVE "scheduled_payments.csv", scheduled + + ' Set up the schedule + SET SCHEDULE frequency WITH START start_date + CALL ExecuteScheduledTransfer(scheduled.id) + END SCHEDULE + + TALK "✅ Recurring transfer scheduled!" + TALK "First transfer: " + FORMAT(start_date, "dd/MM/yyyy") + TALK "Frequency: " + frequency + TALK "Amount: R$ " + FORMAT(amount, "0.00") +END SUB +``` + +### Bill Payment + +```basic +' dialogs/payment.bas + +' Pay bill/boleto +SUB PayBoleto() + TALK "Enter the barcode or paste the boleto line:" + barcode = HEAR + + ' Parse boleto + boleto = ParseBoleto(barcode) + + IF boleto.error THEN + TALK "❌ Invalid barcode. Please check and try again." + RETURN + END IF + + TALK "📄 **Bill Details**" + TALK "Beneficiary: " + boleto.beneficiary + TALK "Amount: R$ " + FORMAT(boleto.amount, "0.00") + TALK "Due date: " + FORMAT(boleto.due_date, "dd/MM/yyyy") + + IF boleto.is_overdue THEN + TALK "⚠️ This bill is overdue. Late fees may apply." + TALK "Original amount: R$ " + FORMAT(boleto.original_amount, "0.00") + TALK "Late fee: R$ " + FORMAT(boleto.late_fee, "0.00") + TALK "Interest: R$ " + FORMAT(boleto.interest, "0.00") + END IF + + TALK "" + TALK "Pay this bill?" + + ADD SUGGESTION "Yes, pay now" + ADD SUGGESTION "Schedule for due date" + ADD SUGGESTION "Cancel" + + choice = HEAR + + IF choice CONTAINS "now" THEN + ' Process payment + result = ProcessBoletoPayment(boleto) + + IF result.success THEN + TALK "✅ **Payment completed!**" + TALK "Transaction ID: " + result.transaction_id + TALK "Authentication: " + result.authentication + ELSE + TALK "❌ Payment failed: " + result.error + END IF + + ELSEIF choice CONTAINS "schedule" THEN + ' Schedule for due date + TABLE scheduled + ROW GenerateID(), GET USER ID, "boleto", barcode, boleto.amount, boleto.due_date, "pending" + END TABLE + + SAVE "scheduled_payments.csv", scheduled + + TALK "✅ Payment scheduled for " + FORMAT(boleto.due_date, "dd/MM/yyyy") + ELSE + TALK "Payment cancelled." + END IF +END SUB + +' Pay utilities +SUB PayUtility(utility_type) + TALK "Enter your " + utility_type + " account number or scan the bill:" + account = HEAR + + ' Fetch bill info + bill = FetchUtilityBill(utility_type, account) + + IF bill.found THEN + TALK "📄 **" + utility_type + " Bill**" + TALK "Account: " + account + TALK "Reference: " + bill.reference + TALK "Amount: R$ " + FORMAT(bill.amount, "0.00") + TALK "Due date: " + FORMAT(bill.due_date, "dd/MM/yyyy") + + TALK "Pay this bill?" + ' ... continue payment flow + ELSE + TALK "No pending bill found for this account." + END IF +END SUB +``` + +### Loans + +```basic +' dialogs/loan.bas + +' Loan simulation +SUB SimulateLoan() + TALK "Let's simulate a loan. What type of loan are you interested in?" + + ADD SUGGESTION "Personal Loan" + ADD SUGGESTION "Payroll Loan" + ADD SUGGESTION "Home Equity" + ADD SUGGESTION "Vehicle Loan" + + loan_type = HEAR + + TALK "What amount do you need?" + amount = HEAR + amount = ParseMoney(amount) + + TALK "In how many months would you like to pay?" + ADD SUGGESTION "12 months" + ADD SUGGESTION "24 months" + ADD SUGGESTION "36 months" + ADD SUGGESTION "48 months" + ADD SUGGESTION "60 months" + + months = HEAR + months = ParseNumber(months) + + ' Get user's rate based on credit score + user = GET USER + rate = GetPersonalizedRate(user.id, loan_type) + + ' Calculate loan + monthly_payment = CalculatePMT(amount, rate, months) + total_amount = monthly_payment * months + total_interest = total_amount - amount + + TALK "💰 **Loan Simulation**" + TALK "" + TALK "📊 **Summary**" + TALK "Loan type: " + loan_type + TALK "Amount: R$ " + FORMAT(amount, "0.00") + TALK "Term: " + months + " months" + TALK "Interest rate: " + FORMAT(rate * 100, "0.00") + "% per month" + TALK "" + TALK "📅 **Monthly Payment: R$ " + FORMAT(monthly_payment, "0.00") + "**" + TALK "" + TALK "Total to pay: R$ " + FORMAT(total_amount, "0.00") + TALK "Total interest: R$ " + FORMAT(total_interest, "0.00") + TALK "" + TALK "Would you like to proceed with this loan?" + + ADD SUGGESTION "Yes, apply now" + ADD SUGGESTION "Try different values" + ADD SUGGESTION "Not now" + + choice = HEAR + + IF choice CONTAINS "apply" THEN + CALL ApplyForLoan(loan_type, amount, months, rate) + ELSEIF choice CONTAINS "different" THEN + CALL SimulateLoan() + ELSE + TALK "No problem! I'm here whenever you need." + END IF +END SUB + +' Apply for loan +SUB ApplyForLoan(loan_type, amount, months, rate) + user = GET USER + + ' Check eligibility + eligibility = CheckLoanEligibility(user.id, loan_type, amount) + + IF NOT eligibility.eligible THEN + TALK "❌ Unfortunately, we couldn't approve this loan at this time." + TALK "Reason: " + eligibility.reason + + IF eligibility.alternative_amount > 0 THEN + TALK "However, you're pre-approved for up to R$ " + FORMAT(eligibility.alternative_amount, "0.00") + TALK "Would you like to apply for this amount instead?" + END IF + RETURN + END IF + + TALK "✅ **Great news! You're pre-approved!**" + TALK "" + TALK "To complete your application, I need some additional information." + + ' Collect additional info + TALK "What is your monthly income?" + income = HEAR + + TALK "What is your profession?" + profession = HEAR + + TALK "Do you have any other loans? (yes/no)" + has_other_loans = HEAR + + IF has_other_loans CONTAINS "yes" THEN + TALK "What is the total monthly payment of your other loans?" + other_loans_payment = HEAR + END IF + + ' Create loan application + application_id = GenerateID() + + TABLE loan_application + ROW application_id, user.id, loan_type, amount, months, rate, income, profession, NOW(), "pending_analysis" + END TABLE + + SAVE "loan_applications.csv", loan_application + + TALK "🎉 **Application Submitted!**" + TALK "" + TALK "Application ID: " + application_id + TALK "Status: Under Analysis" + TALK "" + TALK "We'll analyze your application within 24 hours." + TALK "You'll receive updates via email and app notifications." + + ' Send notification + SEND MAIL user.email, "Loan Application Received", "Your loan application " + application_id + " has been received and is under analysis." +END SUB +``` + +### Cards Management + +```basic +' dialogs/cards.bas + +' View cards +SUB ViewCards() + user_id = GET USER ID + + cards = FIND "cards.csv" WHERE user_id = user_id AND status = "active" + + IF LEN(cards) = 0 THEN + TALK "You don't have any active cards." + TALK "Would you like to request one?" + RETURN + END IF + + TALK "💳 **Your Cards**" + TALK "" + + FOR EACH card IN cards + IF card.card_type = "credit" THEN + icon = "💳" + ELSE + icon = "💵" + END IF + + masked_number = "**** **** **** " + RIGHT(card.card_number, 4) + + TALK icon + " **" + card.card_type + " Card**" + TALK " Number: " + masked_number + TALK " Expiry: " + card.expiry_date + + IF card.card_type = "credit" THEN + TALK " Limit: R$ " + FORMAT(card.credit_limit, "0.00") + TALK " Available: R$ " + FORMAT(card.available_limit, "0.00") + TALK " Current bill: R$ " + FORMAT(card.current_bill, "0.00") + END IF + + TALK " Status: " + card.status + TALK "" + NEXT + + TALK "What would you like to do?" + ADD SUGGESTION "View transactions" + ADD SUGGESTION "Block card" + ADD SUGGESTION "Request new card" + ADD SUGGESTION "Increase limit" +END SUB + +' Block card +SUB BlockCard(card_id) + TALK "⚠️ **Block Card**" + TALK "Are you sure you want to block this card?" + TALK "This action will prevent all transactions." + + ADD SUGGESTION "Yes, block it" + ADD SUGGESTION "Cancel" + + choice = HEAR + + IF choice CONTAINS "yes" THEN + ' Request reason + TALK "Please tell me why you're blocking the card:" + ADD SUGGESTION "Lost" + ADD SUGGESTION "Stolen" + ADD SUGGESTION "Suspicious activity" + ADD SUGGESTION "Temporary block" + + reason = HEAR + + ' Update card status + UPDATE "cards.csv" SET status = "blocked", blocked_reason = reason WHERE id = card_id + + ' Log the action + TABLE card_log + ROW GenerateID(), card_id, "blocked", reason, NOW() + END TABLE + SAVE "card_logs.csv", card_log + + TALK "✅ **Card blocked successfully**" + + IF reason CONTAINS "stolen" OR reason CONTAINS "lost" THEN + TALK "For your security, we recommend requesting a new card." + TALK "Would you like to request a replacement?" + + IF HEAR CONTAINS "yes" THEN + CALL RequestNewCard("replacement") + END IF + ELSE + TALK "You can unblock your card anytime through this chat or the app." + END IF + ELSE + TALK "Card block cancelled." + END IF +END SUB + +' Request credit limit increase +SUB RequestLimitIncrease() + user_id = GET USER ID + + cards = FIND "cards.csv" WHERE user_id = user_id AND card_type = "credit" AND status = "active" + + IF LEN(cards) = 0 THEN + TALK "You don't have an active credit card." + RETURN + END IF + + card = cards[0] + current_limit = card.credit_limit + + ' Check eligibility for increase + eligibility = CheckLimitIncreaseEligibility(card.id) + + IF eligibility.eligible THEN + TALK "📈 **Good news! You're eligible for a limit increase!**" + TALK "" + TALK "Current limit: R$ " + FORMAT(current_limit, "0.00") + TALK "Maximum available: R$ " + FORMAT(eligibility.max_limit, "0.00") + TALK "" + TALK "What limit would you like?" + + new_limit = HEAR + new_limit = ParseMoney(new_limit) + + IF new_limit > eligibility.max_limit THEN + TALK "The maximum limit available is R$ " + FORMAT(eligibility.max_limit, "0.00") + new_limit = eligibility.max_limit + END IF + + ' Approve instantly + UPDATE "cards.csv" SET credit_limit = new_limit WHERE id = card.id + + TALK "✅ **Limit increased!**" + TALK "New limit: R$ " + FORMAT(new_limit, "0.00") + TALK "Effective immediately." + ELSE + TALK "At this time, we cannot increase your limit." + TALK "Reason: " + eligibility.reason + TALK "Please try again in " + eligibility.wait_days + " days." + END IF +END SUB +``` + +### Investment Module + +```basic +' dialogs/investment.bas + +' View investments +SUB ViewInvestments() + user_id = GET USER ID + + investments = FIND "investments.csv" WHERE user_id = user_id + + IF LEN(investments) = 0 THEN + TALK "You don't have any investments yet." + TALK "Would you like to explore our investment options?" + + IF HEAR CONTAINS "yes" THEN + CALL ShowInvestmentOptions() + END IF + RETURN + END IF + + total_invested = 0 + total_earnings = 0 + + TALK "📊 **Your Investment Portfolio**" + TALK "" + + FOR EACH inv IN investments + earnings = inv.current_value - inv.invested_amount + earnings_pct = (earnings / inv.invested_amount) * 100 + + IF earnings >= 0 THEN + icon = "📈" + color = "green" + ELSE + icon = "📉" + color = "red" + END IF + + TALK icon + " **" + inv.product_name + "**" + TALK " Type: " + inv.product_type + TALK " Invested: R$ " + FORMAT(inv.invested_amount, "0.00") + TALK " Current: R$ " + FORMAT(inv.current_value, "0.00") + TALK " Return: " + FORMAT(earnings_pct, "0.00") + "%" + TALK "" + + total_invested = total_invested + inv.invested_amount + total_earnings = total_earnings + earnings + NEXT + + total_pct = (total_earnings / total_invested) * 100 + + TALK "💰 **Portfolio Summary**" + TALK "Total invested: R$ " + FORMAT(total_invested, "0.00") + TALK "Total value: R$ " + FORMAT(total_invested + total_earnings, "0.00") + TALK "Total return: " + FORMAT(total_pct, "0.00") + "%" +END SUB + +' Show investment options +SUB ShowInvestmentOptions() + TALK "💎 **Investment Options**" + TALK "" + TALK "**Fixed Income:**" + TALK "📌 CDB - from 100% CDI" + TALK "📌 LCI/LCA - Tax-free, from 95% CDI" + TALK "📌 Treasury Bonds - Government backed" + TALK "" + TALK "**Variable Income:**" + TALK "📊 Stocks - Direct investment" + TALK "📊 ETFs - Diversified funds" + TALK "📊 REITs - Real estate funds" + TALK "" + TALK "**Crypto:**" + TALK "🪙 Bitcoin, Ethereum, and more" + TALK "" + TALK "What interests you?" + + ADD SUGGESTION "Fixed Income" + ADD SUGGESTION "Stocks" + ADD SUGGESTION "Crypto" + ADD SUGGESTION "I need advice" +END SUB +``` + +--- + +## 4. Excel Clone (HTMX/Rust) + +### Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ GENERAL BOTS SHEETS │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ +│ │ Browser │◄──►│ HTMX/WS │◄──►│ Rust Backend │ │ +│ │ (Canvas) │ │ Updates │ │ (Calamine) │ │ +│ └─────────────┘ └──────────────┘ └─────────────────┘ │ +│ │ │ │ +│ │ ▼ │ +│ │ ┌─────────────────┐ │ +│ │ │ File Storage │ │ +│ │ │ (.gbdrive) │ │ +│ │ └─────────────────┘ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ FORMULA ENGINE │ │ +│ │ - 400+ Excel functions │ │ +│ │ - Array formulas │ │ +│ │ - Cross-sheet references │ │ +│ │ - Custom functions (BASIC integration) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Rust Backend + +```rust +// src/sheets/mod.rs + +use calamine::{Reader, Xlsx, DataType, Range}; +use rust_xlsxwriter::Workbook; +use std::collections::HashMap; + +pub mod engine; +pub mod formulas; +pub mod api; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SpreadsheetState { + pub id: Uuid, + pub name: String, + pub sheets: Vec, + pub active_sheet: usize, + pub modified: bool, + pub last_saved: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SheetState { + pub name: String, + pub cells: HashMap, + pub col_widths: HashMap, + pub row_heights: HashMap, + pub frozen_rows: usize, + pub frozen_cols: usize, + pub selection: Selection, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CellRef { + pub row: usize, + pub col: usize, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CellData { + pub value: CellValue, + pub formula: Option, + pub format: CellFormat, + pub style: CellStyle, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CellValue { + Empty, + String(String), + Number(f64), + Boolean(bool), + Error(String), + DateTime(DateTime), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CellFormat { + pub number_format: String, + pub alignment: Alignment, + pub wrap_text: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CellStyle { + pub font: FontStyle, + pub fill: FillStyle, + pub border: BorderStyle, +} + +// Spreadsheet Engine +pub struct SpreadsheetEngine { + state: SpreadsheetState, + formula_engine: FormulaEngine, + dependency_graph: DependencyGraph, +} + +impl SpreadsheetEngine { + pub fn new() -> Self { + Self { + state: SpreadsheetState::default(), + formula_engine: FormulaEngine::new(), + dependency_graph: DependencyGraph::new(), + } + } + + pub fn load_xlsx(&mut self, path: &str) -> Result<(), Error> { + let mut workbook: Xlsx<_> = calamine::open_workbook(path)?; + + for sheet_name in workbook.sheet_names().to_owned() { + if let Some(Ok(range)) = workbook.worksheet_range(&sheet_name) { + let mut sheet = SheetState::new(&sheet_name); + + for (row_idx, row) in range.rows().enumerate() { + for (col_idx, cell) in row.iter().enumerate() { + let cell_ref = CellRef { row: row_idx, col: col_idx }; + let cell_data = self.convert_calamine_cell(cell); + sheet.cells.insert(cell_ref, cell_data); + } + } + + self.state.sheets.push(sheet); + } + } + + Ok(()) + } + + pub fn save_xlsx(&self, path: &str) -> Result<(), Error> { + let mut workbook = Workbook::new(); + + for sheet in &self.state.sheets { + let worksheet = workbook.add_worksheet(); + worksheet.set_name(&sheet.name)?; + + for (cell_ref, cell_data) in &sheet.cells { + match &cell_data.value { + CellValue::String(s) => { + worksheet.write_string(cell_ref.row as u32, cell_ref.col as u16, s)?; + } + CellValue::Number(n) => { + worksheet.write_number(cell_ref.row as u32, cell_ref.col as u16, *n)?; + } + CellValue::Boolean(b) => { + worksheet.write_boolean(cell_ref.row as u32, cell_ref.col as u16, *b)?; + } + _ => {} + } + + // Write formula if exists + if let Some(formula) = &cell_data.formula { + worksheet.write_formula( + cell_ref.row as u32, + cell_ref.col as u16, + formula + )?; + } + } + } + + workbook.save(path)?; + Ok(()) + } + + pub fn set_cell(&mut self, sheet: usize, row: usize, col: usize, value: &str) -> Vec { + let cell_ref = CellRef { row, col }; + + // Check if it's a formula + if value.starts_with('=') { + let formula = value[1..].to_string(); + let calculated = self.formula_engine.evaluate(&formula, &self.state.sheets[sheet]); + + self.state.sheets[sheet].cells.insert(cell_ref.clone(), CellData { + value: calculated, + formula: Some(formula), + format: CellFormat::default(), + style: CellStyle::default(), + }); + + // Update dependency graph + self.dependency_graph.update(&cell_ref, &formula); + } else { + // Parse as value + let cell_value = self.parse_value(value); + + self.state.sheets[sheet].cells.insert(cell_ref.clone(), CellData { + value: cell_value, + formula: None, + format: CellFormat::default(), + style: CellStyle::default(), + }); + } + + // Recalculate dependents + let updates = self.recalculate_dependents(&cell_ref); + + self.state.modified = true; + updates + } + + fn recalculate_dependents(&mut self, cell_ref: &CellRef) -> Vec { + let mut updates = Vec::new(); + let dependents = self.dependency_graph.get_dependents(cell_ref); + + for dep in dependents { + if let Some(cell) = self.state.sheets[self.state.active_sheet].cells.get_mut(&dep) { + if let Some(formula) = &cell.formula { + let new_value = self.formula_engine.evaluate( + formula, + &self.state.sheets[self.state.active_sheet] + ); + cell.value = new_value.clone(); + updates.push(CellUpdate { + row: dep.row, + col: dep.col, + value: new_value, + }); + } + } + } + + updates + } +} +``` + +### Formula Engine + +```rust +// src/sheets/formulas.rs + +use std::collections::HashMap; + +pub struct FormulaEngine { + functions: HashMap) -> CellValue>>, +} + +impl FormulaEngine { + pub fn new() -> Self { + let mut engine = Self { + functions: HashMap::new(), + }; + engine.register_builtin_functions(); + engine + } + + fn register_builtin_functions(&mut self) { + // Math functions + self.register("SUM", |args| { + let sum: f64 = args.iter() + .filter_map(|v| v.as_number()) + .sum(); + CellValue::Number(sum) + }); + + self.register("AVERAGE", |args| { + let numbers: Vec = args.iter() + .filter_map(|v| v.as_number()) + .collect(); + if numbers.is_empty() { + CellValue::Error("#DIV/0!".to_string()) + } else { + CellValue::Number(numbers.iter().sum::() / numbers.len() as f64) + } + }); + + self.register("MIN", |args| { + args.iter() + .filter_map(|v| v.as_number()) + .min_by(|a, b| a.partial_cmp(b).unwrap()) + .map(CellValue::Number) + .unwrap_or(CellValue::Error("#VALUE!".to_string())) + }); + + self.register("MAX", |args| { + args.iter() + .filter_map(|v| v.as_number()) + .max_by(|a, b| a.partial_cmp(b).unwrap()) + .map(CellValue::Number) + .unwrap_or(CellValue::Error("#VALUE!".to_string())) + }); + + self.register("COUNT", |args| { + CellValue::Number(args.iter() + .filter(|v| v.as_number().is_some()) + .count() as f64) + }); + + self.register("COUNTA", |args| { + CellValue::Number(args.iter() + .filter(|v| !matches!(v, CellValue::Empty)) + .count() as f64) + }); + + // Text functions + self.register("CONCATENATE", |args| { + let result: String = args.iter() + .map(|v| v.to_string()) + .collect(); + CellValue::String(result) + }); + + self.register("LEFT", |args| { + if args.len() >= 2 { + let text = args[0].to_string(); + let n = args[1].as_number().unwrap_or(1.0) as usize; + CellValue::String(text.chars().take(n).collect()) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + self.register("RIGHT", |args| { + if args.len() >= 2 { + let text = args[0].to_string(); + let n = args[1].as_number().unwrap_or(1.0) as usize; + let start = text.len().saturating_sub(n); + CellValue::String(text.chars().skip(start).collect()) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + self.register("MID", |args| { + if args.len() >= 3 { + let text = args[0].to_string(); + let start = (args[1].as_number().unwrap_or(1.0) as usize).saturating_sub(1); + let n = args[2].as_number().unwrap_or(1.0) as usize; + CellValue::String(text.chars().skip(start).take(n).collect()) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + self.register("LEN", |args| { + if let Some(text) = args.get(0) { + CellValue::Number(text.to_string().len() as f64) + } else { + CellValue::Number(0.0) + } + }); + + self.register("TRIM", |args| { + if let Some(text) = args.get(0) { + CellValue::String(text.to_string().trim().to_string()) + } else { + CellValue::String(String::new()) + } + }); + + self.register("UPPER", |args| { + if let Some(text) = args.get(0) { + CellValue::String(text.to_string().to_uppercase()) + } else { + CellValue::String(String::new()) + } + }); + + self.register("LOWER", |args| { + if let Some(text) = args.get(0) { + CellValue::String(text.to_string().to_lowercase()) + } else { + CellValue::String(String::new()) + } + }); + + // Logical functions + self.register("IF", |args| { + if args.len() >= 3 { + let condition = args[0].as_bool().unwrap_or(false); + if condition { + args[1].clone() + } else { + args[2].clone() + } + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + self.register("AND", |args| { + CellValue::Boolean(args.iter().all(|v| v.as_bool().unwrap_or(false))) + }); + + self.register("OR", |args| { + CellValue::Boolean(args.iter().any(|v| v.as_bool().unwrap_or(false))) + }); + + self.register("NOT", |args| { + if let Some(val) = args.get(0) { + CellValue::Boolean(!val.as_bool().unwrap_or(false)) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + // Lookup functions + self.register("VLOOKUP", |args| { + // Implementation for VLOOKUP + CellValue::Error("#N/A".to_string()) // Placeholder + }); + + self.register("HLOOKUP", |args| { + // Implementation for HLOOKUP + CellValue::Error("#N/A".to_string()) // Placeholder + }); + + self.register("INDEX", |args| { + // Implementation for INDEX + CellValue::Error("#REF!".to_string()) // Placeholder + }); + + self.register("MATCH", |args| { + // Implementation for MATCH + CellValue::Error("#N/A".to_string()) // Placeholder + }); + + // Date functions + self.register("TODAY", |_args| { + CellValue::DateTime(Utc::now()) + }); + + self.register("NOW", |_args| { + CellValue::DateTime(Utc::now()) + }); + + self.register("YEAR", |args| { + if let Some(CellValue::DateTime(dt)) = args.get(0) { + CellValue::Number(dt.year() as f64) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + self.register("MONTH", |args| { + if let Some(CellValue::DateTime(dt)) = args.get(0) { + CellValue::Number(dt.month() as f64) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + self.register("DAY", |args| { + if let Some(CellValue::DateTime(dt)) = args.get(0) { + CellValue::Number(dt.day() as f64) + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + // Financial functions + self.register("PMT", |args| { + if args.len() >= 3 { + let rate = args[0].as_number().unwrap_or(0.0); + let nper = args[1].as_number().unwrap_or(0.0); + let pv = args[2].as_number().unwrap_or(0.0); + + if rate == 0.0 { + CellValue::Number(-pv / nper) + } else { + let pmt = pv * rate * (1.0 + rate).powf(nper) / + ((1.0 + rate).powf(nper) - 1.0); + CellValue::Number(-pmt) + } + } else { + CellValue::Error("#VALUE!".to_string()) + } + }); + + // Add 400+ more functions... + } + + fn register(&mut self, name: &str, f: F) + where + F: Fn(Vec) -> CellValue + 'static, + { + self.functions.insert(name.to_uppercase(), Box::new(f)); + } + + pub fn evaluate(&self, formula: &str, sheet: &SheetState) -> CellValue { + // Parse and evaluate formula + let tokens = self.tokenize(formula); + let ast = self.parse(tokens); + self.eval_ast(&ast, sheet) + } +} +``` + +### HTMX UI Component + +```html + +{% extends "base.html" %} + +{% block title %}Sheets - General Bots{% endblock %} + +{% block content %} +
+ +
+
+ + + + +
+ +
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ +
+ +
+ +
+ +
+
+ + +
+
A1
+
fx
+ +
+ + +
+ + + + + +
+ + +
+
+ +
+ +
+ + +
+ Ready + + + +
+ + + + + + +
+ + + + +{% endblock %} +``` + +--- + +## 5. Word Editor for .docx + +### Architecture + +```rust +// src/docs/mod.rs + +use docx_rs::{Docx, Paragraph, Run, Table, TableCell, TableRow}; + +pub struct DocumentEditor { + document: Docx, + file_path: Option, + modified: bool, +} + +impl DocumentEditor { + pub fn new() -> Self { + Self { + document: Docx::new(), + file_path: None, + modified: false, + } + } + + pub fn open(path: &str) -> Result { + let file = std::fs::File::open(path)?; + let document = docx_rs::read_docx(&file)?; + + Ok(Self { + document, + file_path: Some(path.to_string()), + modified: false, + }) + } + + pub fn save(&self, path: &str) -> Result<(), Error> { + let file = std::fs::File::create(path)?; + self.document.build().pack(file)?; + Ok(()) + } + + pub fn add_paragraph(&mut self, text: &str, style: &ParagraphStyle) -> &mut Self { + let mut paragraph = Paragraph::new(); + let mut run = Run::new().add_text(text); + + if style.bold { + run = run.bold(); + } + if style.italic { + run = run.italic(); + } + if let Some(size) = style.font_size { + run = run.size(size * 2); // half-points + } + + paragraph = paragraph.add_run(run); + self.document = std::mem::take(&mut self.document).add_paragraph(paragraph); + self.modified = true; + self + } + + pub fn to_html(&self) -> String { + // Convert document to HTML for editing + let mut html = String::new(); + // Implementation... + html + } + + pub fn from_html(&mut self, html: &str) -> Result<(), Error> { + // Parse HTML and update document + Ok(()) + } +} +``` + +### HTMX Word Editor UI + +```html + +{% extends "base.html" %} + +{% block title %}Documents - General Bots{% endblock %} + +{% block content %} +
+ +
+
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + +
+ +
+ +
+
+ + +
+
+
+ + +
+
+ +
+
+ + +
+ Page 1 of 1 + 0 words + 0 characters + Saved +
+ + + +
+ + + + +{% endblock %} +``` + +--- + +## 6. M365/Office Competitive Analysis + +### Feature Comparison Matrix + +| Feature | Microsoft 365 | Google Workspace | General Bots | Status | +|---------|---------------|------------------|--------------|--------| +| **Email** | Outlook | Gmail | ✅ Mail | Complete | +| **Calendar** | Outlook Calendar | Google Calendar | ✅ Calendar | Complete | +| **File Storage** | OneDrive | Google Drive | ✅ .gbdrive | Complete | +| **Word Processing** | Word | Docs | 🔄 Docs Editor | In Progress | +| **Spreadsheets** | Excel | Sheets | 🔄 Sheets Editor | In Progress | +| **Presentations** | PowerPoint | Slides | 📋 Planned | Planned | +| **Video Calls** | Teams | Meet | 🔄 Meet | In Progress | +| **Chat** | Teams Chat | Google Chat | ✅ Chat | Complete | +| **AI Assistant** | Copilot | Gemini | ✅ Multi-LLM | Complete | +| **Tasks** | To Do/Planner | Tasks | ✅ Tasks | Complete | +| **Forms** | Forms | Forms | ✅ Forms | Complete | +| **Notes** | OneNote | Keep | 📋 Planned | Planned | +| **Whiteboard** | Whiteboard | Jamboard | 📋 Planned | Planned | + +### Missing Features to Implement + +```rust +// Priority 1: Core Office Features +// - Presentations engine (PowerPoint/Slides equivalent) +// - Real-time collaboration (multiple users editing) +// - Version history and restore +// - Comments and suggestions mode + +// Priority 2: Copilot/Gemini Parity +// - AI in documents (rewrite, summarize, expand) +// - AI in spreadsheets (formula generation, data analysis) +// - AI in email (compose, reply, summarize threads) +// - AI in meetings (transcription, summary, action items) + +// Priority 3: Enterprise Features +// - Admin console +// - Compliance center (eDiscovery, legal hold) +// - Data loss prevention +// - Retention policies +// - Audit logs (already have basic) +``` + +--- + +## 7. Google/MS Graph API Compatibility + +### API Endpoints to Implement + +```rust +// src/api/compat/google.rs + +// Google Drive API compatible endpoints +// GET /drive/v3/files +// POST /drive/v3/files +// GET /drive/v3/files/{fileId} +// DELETE /drive/v3/files/{fileId} +// PATCH /drive/v3/files/{fileId} + +// Google Calendar API compatible endpoints +// GET /calendar/v3/calendars/{calendarId}/events +// POST /calendar/v3/calendars/{calendarId}/events +// GET /calendar/v3/calendars/{calendarId}/events/{eventId} + +// Google Gmail API compatible endpoints +// GET /gmail/v1/users/{userId}/messages +// POST /gmail/v1/users/{userId}/messages/send +// GET /gmail/v1/users/{userId}/threads + +// src/api/compat/msgraph.rs + +// Microsoft Graph API compatible endpoints +// GET /v1.0/me/drive/root/children +// GET /v1.0/me/messages +// POST /v1.0/me/sendMail +// GET /v1.0/me/calendar/events +// POST /v1.0/me/calendar/events +// GET /v1.0/me/contacts + +pub fn configure_compat_routes(cfg: &mut web::ServiceConfig) { + // Google API compatibility + cfg.service( + web::scope("/drive/v3") + .route("/files", web::get().to(google_list_files)) + .route("/files", web::post().to(google_create_file)) + .route("/files/{fileId}", web::get().to(google_get_file)) + ); + + // MS Graph API compatibility + cfg.service( + web::scope("/v1.0") + .route("/me/drive/root/children", web::get().to(graph_list_files)) + .route("/me/messages", web::get().to(graph_list_messages)) + .route("/me/sendMail", web::post().to(graph_send_mail)) + ); +} +``` + +--- + +## 8. Copilot/Gemini Feature Parity + +### AI Features Checklist + +| Feature | Copilot | Gemini | General Bots | BASIC Keyword | +|---------|---------|--------|--------------|---------------| +| Chat with AI | ✅ | ✅ | ✅ | `LLM` | +| Web search | ✅ | ✅ | 📋 | `SEARCH WEB` | +| Image generation | ✅ | ✅ | ✅ | `IMAGE` | +| Code generation | ✅ | ✅ | ✅ | `LLM` | +| Document summary | ✅ | ✅ | ✅ | `LLM` with file | +| Email compose | ✅ | ✅ | ✅ | `SEND MAIL` | +| Meeting summary | ✅ | ✅ | 📋 | `SUMMARIZE MEETING` | +| Data analysis | ✅ | ✅ | ✅ | `AGGREGATE` | +| Create presentations | ✅ | ✅ | 📋 | `CREATE PPT` | +| Voice input | ✅ | ✅ | ✅ | Voice API | +| Multi-modal | ✅ | ✅ | ✅ | `SEE`, `IMAGE` | +| Tool use | ✅ | ✅ | ✅ | `USE TOOL` | +| Memory/context | ✅ | ✅ | ✅ | `SET CONTEXT` | +| Multi-turn | ✅ | ✅ | ✅ | Built-in | + +--- + +## 9. Attachment System (Plus Button) + +### Implementation + +```rust +// src/api/attachments.rs + +#[derive(Debug, Serialize, Deserialize)] +pub struct Attachment { + pub id: Uuid, + pub message_id: Option, + pub file_type: AttachmentType, + pub file_name: String, + pub file_size: i64, + pub mime_type: String, + pub storage_path: String, + pub thumbnail_path: Option, + pub created_at: DateTime, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AttachmentType { + Image, + Document, + Audio, + Video, + Code, + Archive, + Other, +} + +pub async fn upload_attachment( + State(state): State>, + Extension(user): Extension, + mut multipart: Multipart, +) -> Result, ApiError> { + while let Some(field) = multipart.next_field().await? { + let name = field.name().unwrap_or("file").to_string(); + let file_name = field.file_name().unwrap_or("unnamed").to_string(); + let content_type = field.content_type().unwrap_or("application/octet-stream").to_string(); + let data = field.bytes().await?; + + // Determine attachment type + let file_type = detect_attachment_type(&content_type, &file_name); + + // Store file + let storage_path = store_attachment(&state, &user, &data, &file_name).await?; + + // Generate thumbnail for images/videos + let thumbnail_path = if matches!(file_type, AttachmentType::Image | AttachmentType::Video) { + Some(generate_thumbnail(&storage_path).await?) + } else { + None + }; + + // Create attachment record + let attachment = Attachment { + id: Uuid::new_v4(), + message_id: None, + file_type, + file_name, + file_size: data.len() as i64, + mime_type: content_type, + storage_path, + thumbnail_path, + created_at: Utc::now(), + }; + + // Save to database + save_attachment(&state, &attachment).await?; + + return Ok(Json(attachment)); + } + + Err(ApiError::BadRequest("No file provided".to_string())) +} +``` + +--- + +## 10. Conversation Branching + +### Database Schema + +```sql +-- Conversation branches +CREATE TABLE conversation_branches ( + id UUID PRIMARY KEY, + parent_session_id UUID NOT NULL, + branch_session_id UUID NOT NULL, + branch_from_message_id UUID NOT NULL, + branch_name VARCHAR(255), + created_at TIMESTAMPTZ DEFAULT NOW(), + FOREIGN KEY (parent_session_id) REFERENCES sessions(id), + FOREIGN KEY (branch_session_id) REFERENCES sessions(id), + FOREIGN KEY (branch_from_message_id) REFERENCES messages(id) +); +``` + +### Implementation + +```rust +// src/api/branches.rs + +pub async fn create_branch( + State(state): State>, + Extension(user): Extension, + Json(req): Json, +) -> Result, ApiError> { + // Create new session for branch + let branch_session = create_session(&state, user.user_id, user.bot_id).await?; + + // Copy messages up to branch point + copy_messages_to_branch( + &state, + user.id, + branch_session.id, + req.branch_from_message_id, + ).await?; + + // Create branch record + let branch = ConversationBranch { + id: Uuid::new_v4(), + parent_session_id: user.id, + branch_session_id: branch_session.id, + branch_from_message_id: req.branch_from_message_id, + branch_name: req.name, + created_at: Utc::now(), + }; + + save_branch(&state, &branch).await?; + + Ok(Json(BranchResponse { + branch_id: branch.id, + session_id: branch_session.id, + })) +} +``` + +### UI Component + +```html + +
+
{{ message.content }}
+
+ + +
+
+ + - - - - - - {% block styles %}{% endblock %} - - - -
- - -
- - - - - -
- - - -
-
-
- - -
- {% block content %}{% endblock %} -
- - -
- - - - - - - {% block scripts %}{% endblock %} - - diff --git a/templates/bling.gbai/bling.gbdialog/README.md b/templates/bling.gbai/bling.gbdialog/README.md new file mode 100644 index 000000000..a45a377e2 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/README.md @@ -0,0 +1,118 @@ +# Bling ERP Integration (.gbdialog) + +This package provides complete integration with [Bling ERP](https://www.bling.com.br/) for data synchronization and conversational commerce. + +## Scripts + +| File | Description | +|------|-------------| +| `start.bas` | Welcome message and system prompt configuration | +| `tables.bas` | Database schema definitions for all synced entities | +| `sync-erp.bas` | Main ERP synchronization (products, orders, contacts, vendors) | +| `sync-accounts.bas` | Accounts payable and receivable synchronization | +| `sync-inventory.bas` | Stock/inventory levels synchronization | +| `sync-suppliers.bas` | Supplier/vendor data synchronization | +| `add-stock.bas` | Manual stock adjustment tool | +| `data-analysis.bas` | LLM-powered data analysis and reporting | +| `refresh-llm.bas` | Scheduled LLM context refresh | + +## Configuration + +Configure the integration in `bling.gbot/config.csv`: + +| Parameter | Description | +|-----------|-------------| +| `param-blingClientID` | Bling API Client ID | +| `param-blingClientSecret` | Bling API Client Secret | +| `param-blingHost` | Bling API base URL | +| `param-host` | API endpoint (default: `https://api.bling.com.br/Api/v3`) | +| `param-limit` | Records per page for API calls | +| `param-pages` | Maximum pages to sync | +| `param-admin1` | Primary admin email for notifications | +| `param-admin2` | Secondary admin email for notifications | + +## Synchronized Entities + +### Products (`maria.Produtos`) +- Product details, SKU, pricing +- Product variations and hierarchy +- Product images (`maria.ProdutoImagem`) + +### Orders (`maria.Pedidos`) +- Sales orders with line items (`maria.PedidosItem`) +- Payment parcels (`maria.Parcela`) + +### Contacts (`maria.Contatos`) +- Customers and suppliers +- Address and billing information + +### Vendors (`maria.Vendedores`) +- Sales representatives +- Commission and discount limits + +### Financial +- Accounts Receivable (`maria.ContasAReceber`) +- Accounts Payable (`maria.ContasAPagar`) +- Payment Methods (`maria.FormaDePagamento`) +- Revenue Categories (`maria.CategoriaReceita`) + +### Inventory +- Stock by Warehouse (`maria.Depositos`) +- Product Suppliers (`maria.ProdutoFornecedor`) +- Price History (`maria.HistoricoPreco`) + +## Scheduled Jobs + +The following schedules are configured: + +| Job | Schedule | Description | +|-----|----------|-------------| +| `sync-erp.bas` | Daily at 22:30 | Full ERP synchronization | +| `sync-accounts.bas` | Every 2 days at midnight | Financial accounts sync | +| `sync-inventory.bas` | Daily at 23:30 | Stock levels update | +| `refresh-llm.bas` | Daily at 21:00 | Refresh LLM context | + +## Data Analysis + +The `data-analysis.bas` script enables natural language queries against synced data: + +**Example queries:** +- "Which products have excess stock that can be transferred?" +- "What are the top 10 best-selling products?" +- "What is the average ticket for each store?" +- "Which products need restocking?" + +## Usage + +### Manual Stock Adjustment + +Vendors can adjust stock via conversation: + +```basic +REM User provides SKU and quantity +REM Stock is updated in Bling and local database +``` + +### Running Sync Manually + +```basic +RUN "sync-erp.bas" +RUN "sync-accounts.bas" +RUN "sync-inventory.bas" +``` + +## API Integration + +All API calls use the Bling v3 REST API with pagination support: + +- Products: `GET /produtos` +- Orders: `GET /pedidos/vendas` +- Contacts: `GET /contatos` +- Inventory: `GET /estoques/saldos` +- Accounts: `GET /contas/receber`, `GET /contas/pagar` + +## Related Documentation + +- [Bling API Documentation](https://developer.bling.com.br/) +- [General Bots BASIC Reference](../../docs/src/chapter-06-gbdialog/README.md) +- [Template Guide](../../README.md) \ No newline at end of file diff --git a/templates/bling.gbai/bling.gbdialog/add-stock.bas b/templates/bling.gbai/bling.gbdialog/add-stock.bas new file mode 100644 index 000000000..8e4dfc514 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/add-stock.bas @@ -0,0 +1,32 @@ +person = FIND "People.xlsx", "id=" + mobile +vendor = FIND "maria.Vendedores", "id=" + person.erpId + +TALK "Olá " + vendor.Contato_Nome + "!" + +REM Estoque pelo nome em caso de não presente na planilha +TALK "Qual o SKU do Produto?" +HEAR sku + +produto = FIND "maria.Produtos", "sku=" + sku + +TALK "Qual a quantidade que se deseja acrescentar?" +HEAR qtd + +estoque = { + produto: { + id: produto.Id + }, + deposito: { + id: person.deposito_Id + }, + preco: produto.Preco, + operacao: "B", + quantidade: qtd, + observacoes: "Acréscimo de estoque." +} + +rec = POST host + "/estoques", estoque + +TALK "Estoque atualizado, obrigado." +TALK TO admin1, "Estoque do ${sku} foi atualizado com ${qtd}." +TALK TO admin2, "Estoque do ${sku} foi atualizado com ${qtd}." diff --git a/templates/bling.gbai/bling.gbdialog/data-analysis.bas b/templates/bling.gbai/bling.gbdialog/data-analysis.bas new file mode 100644 index 000000000..04c5c6ece --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/data-analysis.bas @@ -0,0 +1,38 @@ +ALLOW ROLE "analiseDados" + +BEGIN TALK +Exemplos de perguntas para o *BlingBot*: + +1. Quais são os produtos que têm estoque excessivo em uma loja e podem ser transferidos para outra loja com menor estoque? + +2. Quais são os 10 produtos mais vendidos na loja {nome_loja} no período {periodo}? + +3. Qual é o ticket médio da loja {nome_loja}? + +4. Qual a quantidade disponível do produto {nome_produto} na loja {nome_loja}? + +5. Quais produtos precisam ser transferidos da loja {origem} para a loja {destino}? + +6. Quais produtos estão com estoque crítico na loja {nome_loja}? + +7. Qual a sugestão de compra para o fornecedor {nome_fornecedor}? + +8. Quantos pedidos são realizados por dia na loja {nome_loja}? + +9. Quantos produtos ativos existem no sistema? + +10. Qual o estoque disponível na loja {nome_loja}? +END TALK + +REM SET SCHEDULE + +SET CONTEXT "As lojas B, L e R estão identificadas no final dos nomes das colunas da tabela de Análise de Compras. Dicionário de dados AnaliseCompras.qtEstoqueL: Descrição quantidade do Leblon. AnaliseCompras.qtEstoqueB: Descrição quantidade da Barra AnaliseCompras.qtEstoqueR: Descrição quantidade do Rio Sul. Com base no comportamento de compra registrado, analise os dados fornecidos para identificar oportunidades de otimização de estoque. Aplique regras básicas de transferência de produtos entre as lojas, considerando a necessidade de balanceamento de inventário. Retorne um relatório das 10 ações mais críticas, detalhe a movimentação sugerida para cada produto. Deve indicar a loja de origem, a loja de destino e o motivo da transferência. A análise deve ser objetiva e pragmática, focando na melhoria da disponibilidade de produtos nas lojas. Sempre use LIKE %% para comparar nomes. IMPORTANTE: Compare sempre com a função LOWER ao filtrar valores, em ambos os operandos de texto em SQL, para ignorar case, exemplo WHERE LOWER(loja.nome) LIKE LOWER(%Leblon%)." + +SET ANSWER MODE "sql" + +TALK "Pergunte-me qualquer coisa sobre os seus dados." + +REM IF mobile = "5521992223002" THEN +REM ELSE +REM TALK "Não autorizado." +REM END IF diff --git a/templates/bling.gbai/bling.gbdialog/refresh-llm.bas b/templates/bling.gbai/bling.gbdialog/refresh-llm.bas new file mode 100644 index 000000000..eeaa2314e --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/refresh-llm.bas @@ -0,0 +1,2 @@ +SET SCHEDULE "0 0 21 * * *" +REFRESH "data-analysis" diff --git a/templates/bling.gbai/bling.gbdialog/start.bas b/templates/bling.gbai/bling.gbdialog/start.bas new file mode 100644 index 000000000..73c937da7 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/start.bas @@ -0,0 +1,16 @@ +TALK O BlingBot deseja boas-vindas! +TALK Qual o seu pedido? + +BEGIN SYSTEM PROMPT +Você deve atuar como um chatbot funcionário da loja integrada ao Bling ERP, respeitando as seguintes regras: + +Sempre que o atendente fizer um pedido, ofereça as condições de cor e tamanho presentes no JSON de produtos. + +A cada pedido realizado, retorne JSON similar ao JSONPedidosExemplo adicionados e o nome do cliente. + +Mantenha itensPedido com apenas um item. + +É importante usar o mesmo id do JSON de produtos fornecido, para haver a correlação dos objetos. + +ItensAcompanhamento deve conter a coleção de itens de acompanhamento do pedido, que é solicitado quando o pedido é feito, por exemplo: Quadro, com Caixa de Giz. +END SYSTEM PROMPT diff --git a/templates/bling.gbai/bling.gbdialog/sync-accounts.bas b/templates/bling.gbai/bling.gbdialog/sync-accounts.bas new file mode 100644 index 000000000..f0c7555b8 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/sync-accounts.bas @@ -0,0 +1,92 @@ +REM Executa a cada dois dias, 23h. +SET SCHEDULE "0 0 0 */2 * *" + +REM Variables from config.csv: admin1, admin2, host, limit, pages +REM Using admin1 for notifications +admin = admin1 + +REM Pagination settings for Bling API +pageVariable = "pagina" +limitVariable = "limite" +syncLimit = 100 + +REM ============================================ +REM Sync Contas a Receber (Accounts Receivable) +REM ============================================ +SEND EMAIL admin, "Sincronizando Contas a Receber..." + +page = 1 +totalReceber = 0 + +DO WHILE page > 0 AND page <= pages + url = host + "/contas/receber?" + pageVariable + "=" + page + "&" + limitVariable + "=" + syncLimit + res = GET url + WAIT 0.33 + + IF res.data THEN + items = res.data + itemCount = UBOUND(items) + + IF itemCount > 0 THEN + MERGE "maria.ContasAReceber" WITH items BY "Id" + totalReceber = totalReceber + itemCount + page = page + 1 + + IF itemCount < syncLimit THEN + page = 0 + END IF + ELSE + page = 0 + END IF + ELSE + page = 0 + END IF + + res = null + items = null +LOOP + +SEND EMAIL admin, "Contas a Receber sincronizadas: " + totalReceber + " registros." + +REM ============================================ +REM Sync Contas a Pagar (Accounts Payable) +REM ============================================ +SEND EMAIL admin, "Sincronizando Contas a Pagar..." + +page = 1 +totalPagar = 0 + +DO WHILE page > 0 AND page <= pages + url = host + "/contas/pagar?" + pageVariable + "=" + page + "&" + limitVariable + "=" + syncLimit + res = GET url + WAIT 0.33 + + IF res.data THEN + items = res.data + itemCount = UBOUND(items) + + IF itemCount > 0 THEN + MERGE "maria.ContasAPagar" WITH items BY "Id" + totalPagar = totalPagar + itemCount + page = page + 1 + + IF itemCount < syncLimit THEN + page = 0 + END IF + ELSE + page = 0 + END IF + ELSE + page = 0 + END IF + + res = null + items = null +LOOP + +SEND EMAIL admin, "Contas a Pagar sincronizadas: " + totalPagar + " registros." + +REM ============================================ +REM Summary +REM ============================================ +SEND EMAIL admin, "Transferência do ERP (Contas) para BlingBot concluído. Total: " + (totalReceber + totalPagar) + " registros." diff --git a/templates/bling.gbai/bling.gbdialog/sync-erp.bas b/templates/bling.gbai/bling.gbdialog/sync-erp.bas new file mode 100644 index 000000000..37613302a --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/sync-erp.bas @@ -0,0 +1,333 @@ +REM Geral +REM SET SCHEDULE "0 30 22 * * *" + +daysToSync = -7 +ontem = DATEADD today, "days", daysToSync +ontem = FORMAT ontem, "yyyy-MM-dd" +tomorrow = DATEADD today, "days", 1 +tomorrow = FORMAT tomorrow, "yyyy-MM-dd" +dateFilter = "&dataAlteracaoInicial=${ontem}&dataAlteracaoFinal=${tomorrow}" +admin = admin1 + +SEND EMAIL admin, "Sincronismo: ${ontem} e ${tomorrow} (${daysToSync * -1} dia(s)) iniciado..." + +REM Produtos +i = 1 +SEND EMAIL admin, "Sincronizando Produtos..." + +DO WHILE i > 0 AND i < pages + REM ${dateFilter} + res = GET host + "/produtos?pagina=${i}&criterio=5&tipo=P&limite=${limit}${dateFilter}" + WAIT 0.33 + list = res.data + res = null + + REM Sincroniza itens de Produto + prd1 = "" + j = 0 + k = 0 + items = NEW ARRAY + + DO WHILE j < ubound(list) + produto_id = list[j].id + res = GET host + "/produtos/${produto_id}" + WAIT 0.33 + produto = res.data + res = null + + IF produto.codigo && produto.codigo.trim().length THEN + prd1 = prd1 + "&idsProdutos%5B%5D=" + list[j].id + items[k] = produto + produto.sku = items[k].codigo + + IF produto.variacoes.length > 0 THEN + produto.hierarquia = "p" + ELSE + produto.hierarquia = "s" + END IF + + produtoDB = FIND "maria.Produtos", "sku=" + produto.codigo + IF produtoDB THEN + IF produtoDB.preco <> produto.preco THEN + hist = NEW OBJECT + hist.sku = produto.sku + hist.precoAntigo = produtoDB.preco + hist.precoAtual = produto.preco + hist.produto_id = produto.id + hist.dataModificado = FORMAT today, "yyyy-MM-dd" + SAVE "maria.HistoricoPreco", hist + hist = null + END IF + END IF + k = k + 1 + END IF + j = j + 1 + LOOP + + list = null + list = items + + MERGE "maria.Produtos" WITH list BY "Id" + list = items + + REM Calcula ids de produtos + j = 0 + DO WHILE j < ubound(list) + REM Varre todas as variações. + listV = list[j].variacoes + IF listV THEN + k = 0 + prd2 = "" + DO WHILE k < ubound(listV) + IF listV[k].codigo && listV[k].codigo.trim().length THEN + listV[k].skuPai = list[j].sku + listV[k].sku = listV[k].codigo + listV[k].hierarquia = "f" + k = k + 1 + ELSE + listV.splice(k, 1) + END IF + LOOP + + k = 0 + DO WHILE k < ubound(listV) + listV[k].hierarquia = 'f' + DELETE "maria.ProdutoImagem", "sku=" + listV[k].sku + + REM Sincroniza images da variação. + images = listV[k]?.midia?.imagens?.externas + l = 0 + DO WHILE l < ubound(images) + images[l].ordinal = k + images[l].sku = listV[k].sku + images[l].id= random() + l = l + 1 + LOOP + SAVE "maria.ProdutoImagem", images + images=null + k = k + 1 + LOOP + + MERGE "maria.Produtos" WITH listV BY "Id" + END IF + listV=null + + REM Sincroniza images do produto raiz. + DELETE "maria.ProdutoImagem", "sku=" + list[j].sku + k = 0 + images = list[j].midia?.imagens?.externas + DO WHILE k < ubound(images) + images[k].ordinal = k + images[k].sku = list[j].sku + images[k].id= random() + k = k + 1 + LOOP + SAVE "maria.ProdutoImagem", images + j = j + 1 + LOOP + + i = i + 1 + IF list?.length < limit THEN + i = 0 + END IF + list=null + res=null + items=null +LOOP + +SEND EMAIL admin, "Produtos concluído." +RESET REPORT + +REM Pedidos +SEND EMAIL admin, "Sincronizando Pedidos..." +i = 1 + +DO WHILE i > 0 AND i < pages + res = GET host + "/pedidos/vendas?pagina=${i}&limite=${limit}${dateFilter}" + list = res.data + res = null + + REM Sincroniza itens + j = 0 + fullList = [] + + DO WHILE j < ubound(list) + pedido_id = list[j].id + res = GET host + "/pedidos/vendas/${pedido_id}" + items = res.data.itens + + REM Insere ref. de pedido no item. + k = 0 + DO WHILE k < ubound(items) + items[k].pedido_id = pedido_id + items[k].sku = items[k].codigo + items[k].numero = list[j].numero + REM Obter custo do produto fornecedor do fornecedor marcado como default. + items[k].custo = items[k].valor / 2 + k = k + 1 + LOOP + MERGE "maria.PedidosItem" WITH items BY "Id" + + items = res.data.parcelas + k = 0 + DO WHILE k < ubound(items) + items[k].pedido_id = pedido_id + k = k + 1 + LOOP + MERGE "maria.Parcela" WITH items BY "Id" + + fullList[j] = res.data + res = null + j = j + 1 + LOOP + + MERGE "maria.Pedidos" WITH fullList BY "Id" + i = i + 1 + IF list?.length < limit THEN + i = 0 + END IF + list=null + res=null +LOOP + +SEND EMAIL admin, "Pedidos concluído." + +REM Comuns +pageVariable="pagina" +limitVariable="limite" +syncLimit = 100 + +REM Sincroniza CategoriaReceita +SEND EMAIL admin, "Sincronizando CategoriaReceita..." +syncPage = 1 +totalCategoria = 0 + +DO WHILE syncPage > 0 AND syncPage <= pages + syncUrl = host + "/categorias/receitas-despesas?" + pageVariable + "=" + syncPage + "&" + limitVariable + "=" + syncLimit + syncRes = GET syncUrl + WAIT 0.33 + + IF syncRes.data THEN + syncItems = syncRes.data + syncCount = UBOUND(syncItems) + + IF syncCount > 0 THEN + MERGE "maria.CategoriaReceita" WITH syncItems BY "Id" + totalCategoria = totalCategoria + syncCount + syncPage = syncPage + 1 + + IF syncCount < syncLimit THEN + syncPage = 0 + END IF + ELSE + syncPage = 0 + END IF + ELSE + syncPage = 0 + END IF + + syncRes = null + syncItems = null +LOOP + +SEND EMAIL admin, "CategoriaReceita sincronizada: " + totalCategoria + " registros." + +REM Sincroniza Formas de Pagamento +SEND EMAIL admin, "Sincronizando Formas de Pagamento..." +syncPage = 1 +totalForma = 0 + +DO WHILE syncPage > 0 AND syncPage <= pages + syncUrl = host + "/formas-pagamentos?" + pageVariable + "=" + syncPage + "&" + limitVariable + "=" + syncLimit + syncRes = GET syncUrl + WAIT 0.33 + + IF syncRes.data THEN + syncItems = syncRes.data + syncCount = UBOUND(syncItems) + + IF syncCount > 0 THEN + MERGE "maria.FormaDePagamento" WITH syncItems BY "Id" + totalForma = totalForma + syncCount + syncPage = syncPage + 1 + + IF syncCount < syncLimit THEN + syncPage = 0 + END IF + ELSE + syncPage = 0 + END IF + ELSE + syncPage = 0 + END IF + + syncRes = null + syncItems = null +LOOP + +SEND EMAIL admin, "Formas de Pagamento sincronizadas: " + totalForma + " registros." + +REM Contatos +SEND EMAIL admin, "Sincronizando Contatos..." +i = 1 + +DO WHILE i > 0 AND i < pages + res = GET host + "/contatos?pagina=${i}&limite=${limit}${dateFilter} " + list = res.data + + REM Sincroniza itens + j = 0 + items = NEW ARRAY + + DO WHILE j < ubound(list) + contato_id = list[j].id + res = GET host + "/contatos/${contato_id}" + items[j] = res.data + WAIT 0.33 + j = j + 1 + LOOP + + MERGE "maria.Contatos" WITH items BY "Id" + i = i + 1 + IF list?.length < limit THEN + i = 0 + END IF + list=null + res=null +LOOP + +SEND EMAIL admin, "Contatos concluído." + +REM Vendedores +REM Sincroniza Vendedores. +SEND EMAIL admin, "Sincronizando Vendedores..." +i = 1 + +DO WHILE i > 0 AND i < pages + res = GET host + "/vendedores?pagina=${i}&situacaoContato=T&limite=${limit}${dateFilter}" + list = res.data + + REM Sincroniza itens + j = 0 + items = NEW ARRAY + + DO WHILE j < ubound(list) + vendedor_id = list[j].id + res = GET host + "/vendedores/${vendedor_id}" + items[j] = res.data + WAIT 0.33 + j = j + 1 + LOOP + + MERGE "maria.Vendedores" WITH items BY "Id" + i = i + 1 + IF list?.length < limit THEN + i = 0 + END IF + list=null + res=null +LOOP + +SEND EMAIL admin, "Vendedores concluído." + +SEND EMAIL admin, "Transferência do ERP para BlingBot concluído." diff --git a/templates/bling.gbai/bling.gbdialog/sync-inventory.bas b/templates/bling.gbai/bling.gbdialog/sync-inventory.bas new file mode 100644 index 000000000..2b4752251 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/sync-inventory.bas @@ -0,0 +1,66 @@ +REM SET SCHEDULE "0 30 23 * * *" + +i = 1 + +SEND EMAIL admin, "Sincronismo Estoque iniciado..." + +fullList = FIND "maria.Produtos" + +REM Initialize chunk parameters +chunkSize = 100 +startIndex = 0 + +REM ubound(fullList) +DO WHILE startIndex < ubound(fullList) + list = mid( fullList, startIndex, chunkSize) + prd1 = "" + j = 0 + items = NEW ARRAY + + DO WHILE j < ubound(list) + produto_id = list[j].id + prd1 = prd1 + "&idsProdutos%5B%5D=" + produto_id + j = j +1 + LOOP + + list = null + + REM Sincroniza Estoque + IF j > 0 THEN + res = GET host + "/estoques/saldos?${prd1}" + WAIT 0.33 + items = res.data + res = null + + k = 0 + DO WHILE k < ubound(items) + depositos = items[k].depositos + pSku = FIND "maria.Produtos", "id=${items[k].produto.id}" + + IF pSku THEN + prdSku = pSku.sku + DELETE "maria.Depositos", "Sku=" + prdSku + + l = 0 + DO WHILE l < ubound(depositos) + depositos[l].sku = prdSku + l = l + 1 + LOOP + + SAVE "maria.Depositos", depositos + depositos = null + END IF + + pSku = null + k = k +1 + LOOP + items = null + END IF + + REM Update startIndex for the next chunk + startIndex = startIndex + chunkSize + items = null +LOOP + +fullList = null +SEND EMAIL admin, "Estoque concluído." diff --git a/templates/bling.gbai/bling.gbdialog/sync-suppliers.bas b/templates/bling.gbai/bling.gbdialog/sync-suppliers.bas new file mode 100644 index 000000000..d179e1fd0 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/sync-suppliers.bas @@ -0,0 +1,73 @@ +REM Geral +REM Produto Fornecedor + +FUNCTION SyncProdutoFornecedor(idProduto) + REM Sincroniza ProdutoFornecedor. + DELETE "maria.ProdutoFornecedor", "Produto_id=" + idProduto + + i1 = 1 + DO WHILE i1 > 0 AND i1 < pages + res = GET host + "/produtos/fornecedores?pagina=${i1}&limite=${limit}&idProduto=${idProduto}" + list1 = res.data + res = null + WAIT 0.33 + + REM Sincroniza itens + let j1 = 0 + items1 = NEW ARRAY + + DO WHILE j1 < ubound(list1) + produtoFornecedor_id = list1[j1].id + res = GET host + "/produtos/fornecedores/${produtoFornecedor_id}" + items1[j1] = res.data + res = null + WAIT 0.33 + j1 = j1 + 1 + LOOP + + SAVE "maria.ProdutoFornecedor", items1 + items1= null + i1 = i1 + 1 + + IF list1?.length < limit THEN + i1 = 0 + END IF + res=null + list1=null + LOOP +END FUNCTION + +i = 1 +SEND EMAIL admin, "Sincronismo Fornecedores iniciado..." + +fullList = FIND "maria.Produtos" + +REM Initialize chunk parameters +chunkSize = 100 +startIndex = 0 + +REM ubound(fullList) +DO WHILE startIndex < ubound(fullList) + list = mid( fullList, startIndex, chunkSize) + + REM Sincroniza itens de Produto + prd1 = "" + j = 0 + items = NEW ARRAY + + DO WHILE j < ubound(list) + produto_id = list[j].id + prd1 = prd1 + "&idsProdutos%5B%5D=" + produto_id + CALL SyncProdutoFornecedor(produto_id) + j = j +1 + LOOP + + list = null + + REM Update startIndex for the next chunk + startIndex = startIndex + chunkSize + items = null +LOOP + +fullList = null +SEND EMAIL admin, "Fornecedores concluído." diff --git a/templates/bling.gbai/bling.gbdialog/tables.bas b/templates/bling.gbai/bling.gbdialog/tables.bas new file mode 100644 index 000000000..6d102e3b7 --- /dev/null +++ b/templates/bling.gbai/bling.gbdialog/tables.bas @@ -0,0 +1,284 @@ +TABLE Contatos ON maria + Id number key + Nome string(150) + Codigo string(50) + Situacao string(5) + NumeroDocumento string(25) + Telefone string(20) + Celular string(20) + Fantasia string(150) + Tipo string(5) + IndicadorIe string(5) + Ie string(22) + Rg string(22) + OrgaoEmissor string(22) + Email string(50) + Endereco_geral_endereco string(100) + Endereco_geral_cep string(10) + Endereco_geral_bairro string(50) + Endereco_geral_municipio string(50) + Endereco_geral_uf string(5) + Endereco_geral_numero string(15) + Endereco_geral_complemento string(50) + Cobranca_endereco string(100) + Cobranca_cep string(10) + Cobranca_bairro string(50) + Cobranca_municipio string(50) + Cobranca_uf string(5) + Cobranca_numero string(15) + Cobranca_complemento string(50) + Vendedor_id number + DadosAdicionais_dataNascimento date + DadosAdicionais_sexo string(5) + DadosAdicionais_naturalidade string(25) + Financeiro_limiteCredito double + Financeiro_condicaoPagamento string(20) + Financeiro_categoria_id number + Pais_nome string(100) +END TABLE + +TABLE Pedidos ON maria + Id number key + Numero integer + NumeroLoja string(15) + Data date + DataSaida date + DataPrevista date + TotalProdutos double + Desconto_valor double + Desconto_unidade string(15) + Contato_id number + Total double + Contato_nome string(150) + Contato_tipoPessoa string(1) + Contato_numeroDocumento string(20) + Situacao_id integer + Situacao_valor double + Loja_id integer + Vendedor_id number + NotaFiscal_id number +END TABLE + +TABLE PedidosItem ON maria + Id number key + Numero integer + Sku string(20) + Unidade string(8) + Quantidade integer + Desconto double + Valor double + Custo double + AliquotaIPI double + Descricao string(255) + DescricaoDetalhada string(250) + Produto_id number + Pedido_id number +END TABLE + +TABLE ProdutoImagem ON maria + Id number key + Ordinal number + Sku string(20) + Link string(250) +END TABLE + +TABLE Produtos ON maria + Id number key + Nome string(150) + Sku string(20) + SkuPai string(20) + Preco double + Tipo string(1) + Situacao string(1) + Formato string(1) + Hierarquia string(1) + DescricaoCurta string(4000) + DataValidade date + Unidade string(5) + PesoLiquido double + PesoBruto double + Volumes integer + ItensPorCaixa integer + Gtin string(50) + GtinEmbalagem string(50) + TipoProducao string(5) + Condicao integer + FreteGratis boolean + Marca string(100) + DescricaoComplementar string(4000) + LinkExterno string(255) + Observacoes string(255) + Categoria_id integer + Estoque_minimo integer + Estoque_maximo integer + Estoque_crossdocking integer + Estoque_localizacao string(50) + ActionEstoque string(50) + Dimensoes_largura double + Dimensoes_altura double + Dimensoes_profundidade double + Dimensoes_unidadeMedida double + Tributacao_origem integer + Tributacao_nFCI string(50) + Tributacao_ncm string(50) + Tributacao_cest string(50) + Tributacao_codigoListaServicos string(50) + Tributacao_spedTipoItem string(50) + Tributacao_codigoItem string(50) + Tributacao_percentualTributos double + Tributacao_valorBaseStRetencao double + Tributacao_valorStRetencao double + Tributacao_valorICMSSubstituto double + Tributacao_codigoExcecaoTipi string(50) + Tributacao_classeEnquadramentoIpi string(50) + Tributacao_valorIpiFixo double + Tributacao_codigoSeloIpi string(50) + Tributacao_valorPisFixo double + Tributacao_valorCofinsFixo double + Tributacao_dadosAdicionais string(50) + GrupoProduto_id number + Midia_video_url string(255) + Midia_imagens_externas_0_link string(255) + LinhaProduto_id number + Estrutura_tipoEstoque string(5) + Estrutura_lancamentoEstoque string(5) + Estrutura_componentes_0_produto_id number + Estrutura_componentes_0_produto_Quantidade double +END TABLE + +TABLE Depositos ON maria + Internal_Id number key + Id number + Sku string(20) + SaldoFisico double + SaldoVirtual double +END TABLE + +TABLE Vendedores ON maria + Id number key + DescontoLimite double + Loja_Id number + Contato_Id number + Contato_Nome string(100) + Contato_Situacao string(1) +END TABLE + +TABLE ProdutoFornecedor ON maria + Id number key + Descricao string(255) + Codigo string(50) + PrecoCusto double + PrecoCompra double + Padrao boolean + Produto_id number + Fornecedor_id number + Garantia integer +END TABLE + +TABLE ContasAPagar ON maria + Id number key + Situacao integer + Vencimento date + Valor double + Contato_id number + FormaPagamento_id number + Saldo double + DataEmissao date + VencimentoOriginal date + NumeroDocumento string(50) + Competencia date + Historico string(255) + NumeroBanco string(10) + Portador_id number + Categoria_id number + Borderos string(255) + Ocorrencia_tipo integer +END TABLE + +TABLE ContasAReceber ON maria + Id number key + Situacao integer + Vencimento date + Valor double + IdTransacao string(50) + LinkQRCodePix string(255) + LinkBoleto string(255) + DataEmissao date + Contato_id number + Contato_nome string(150) + Contato_numeroDocumento string(20) + Contato_tipo string(1) + FormaPagamento_id number + FormaPagamento_codigoFiscal integer + ContaContabil_id number + ContaContabil_descricao string(255) + Origem_id number + Origem_tipoOrigem string(20) + Origem_numero string(20) + Origem_dataEmissao date + Origem_valor double + Origem_situacao integer + Origem_url string(255) + Saldo double + VencimentoOriginal date + NumeroDocumento string(50) + Competencia date + Historico string(255) + NumeroBanco string(10) + Portador_id number + Categoria_id number + Vendedor_id number + Borderos string(255) + Ocorrencia_tipo integer +END TABLE + +TABLE CategoriaReceita ON maria + Id number key + IdCategoriaPai number + Descricao string(255) + Tipo integer + Situacao integer +END TABLE + +TABLE FormaDePagamento ON maria + Id number key + Descricao string(255) + TipoPagamento integer + Situacao integer + Fixa boolean + Padrao integer + Finalidade integer + Condicao string(10) + Destino integer + Taxas_aliquota double + Taxas_valor double + Taxas_prazo integer + DadosCartao_bandeira integer + DadosCartao_tipo integer + DadosCartao_cnpjCredenciadora string(16) +END TABLE + +TABLE NaturezaDeOperacao ON maria + Id number key + Situacao integer + Padrao integer + Descricao string(255) +END TABLE + +TABLE Parcela ON maria + Id number key + Pedido_id number + DataVencimento date + Valor double + Observacoes string(255) + FormaPagamento_id number +END TABLE + +TABLE HistoricoPreco ON maria + Id number key + Sku string(50) + PrecoAntigo double + PrecoAtual double + Produto_id number + DataModificado date +END TABLE diff --git a/templates/bling.gbai/bling.gbot/config.csv b/templates/bling.gbai/bling.gbot/config.csv new file mode 100644 index 000000000..229b0bfac --- /dev/null +++ b/templates/bling.gbai/bling.gbot/config.csv @@ -0,0 +1,25 @@ +name,value +, +bot-name,Bling ERP Integration +bot-description,Synchronizes data with Bling ERP system +, +param-blingClientID, +param-blingClientSecret, +param-blingHost,https://api.bling.com.br/Api/v3 +param-blingTenant, +, +param-host,https://api.bling.com.br/Api/v3 +param-limit,100 +param-pages,50 +, +param-admin1,admin@yourdomain.com +param-admin2,admin2@yourdomain.com +, +llm-model,gpt-4o-mini +llm-temperature,0.3 +, +sync-interval,21600 +sync-products,true +sync-orders,true +sync-contacts,true +sync-inventory,true diff --git a/templates/chat.html b/templates/chat.html deleted file mode 100644 index 31a4889e4..000000000 --- a/templates/chat.html +++ /dev/null @@ -1,159 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Chat - General Bots{% endblock %} - -{% block content %} -
- - - - -
- -
- - Connecting... -
- - -
- -
- - - - - -
- -
- - -
- - - -
- - - - - - - -
- - -
- -
-
-
- - - -
- -{% endblock %} - -{% block scripts %} - -{% endblock %} diff --git a/templates/compliance/hipaa-medical.gbai/README.md b/templates/compliance/hipaa-medical.gbai/README.md new file mode 100644 index 000000000..8817a1316 --- /dev/null +++ b/templates/compliance/hipaa-medical.gbai/README.md @@ -0,0 +1,174 @@ +# HIPAA Medical Privacy Template + +A HIPAA-compliant healthcare privacy portal template for General Bots. + +## Overview + +This template provides healthcare organizations with a ready-to-deploy patient privacy rights management system that complies with: + +- **HIPAA** (Health Insurance Portability and Accountability Act) +- **HITECH Act** (Health Information Technology for Economic and Clinical Health) +- State-specific healthcare privacy regulations + +## Features + +### Patient Rights Management + +1. **Access Medical Records** - Patients can request copies of their Protected Health Information (PHI) +2. **Request Amendments** - Patients can request corrections to their medical records +3. **Accounting of Disclosures** - Track and report who has accessed patient PHI +4. **Request Restrictions** - Allow patients to limit how their PHI is used or shared +5. **Confidential Communications** - Patients can specify preferred contact methods +6. **File Privacy Complaints** - Streamlined complaint submission process +7. **Revoke Authorizations** - Withdraw previous consent for PHI disclosure + +### HIPAA Compliance Features + +- **Audit Trail** - Complete logging of all PHI access and requests +- **Encryption** - AES-256 at rest, TLS 1.3 in transit +- **Access Controls** - Role-based access control (RBAC) +- **Break Glass** - Emergency access procedures with audit +- **Minimum Necessary** - Automatic enforcement of minimum necessary standard +- **PHI Detection** - Automatic detection and redaction of PHI in communications +- **Breach Notification** - Built-in breach response workflow + +## Installation + +1. Copy this template to your General Bots instance: + +```bash +cp -r templates/hipaa-medical.gbai /path/to/your/bot/ +``` + +2. Configure the bot settings in `hipaa.gbot/config.csv`: + +```csv +Covered Entity Name,Your Healthcare Organization +Privacy Officer Email,privacy@yourhealthcare.org +HIPAA Security Officer,security@yourhealthcare.org +``` + +3. Deploy the template: + +```bash +botserver --deploy hipaa-medical.gbai +``` + +## Configuration + +### Required Settings + +| Setting | Description | Example | +|---------|-------------|---------| +| `Covered Entity Name` | Your organization's legal name | Memorial Hospital | +| `Privacy Officer Email` | HIPAA Privacy Officer contact | privacy@hospital.org | +| `HIPAA Security Officer` | Security Officer contact | security@hospital.org | +| `Covered Entity NPI` | National Provider Identifier | 1234567890 | + +### Security Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `Require 2FA` | true | Two-factor authentication required | +| `Session Timeout` | 300 | Session timeout in seconds (5 minutes) | +| `Encryption At Rest` | AES-256 | Data encryption standard | +| `PHI Auto Redaction` | true | Automatically redact PHI in logs | + +### Compliance Timelines + +| Requirement | Deadline | Setting | +|-------------|----------|---------| +| Access Requests | 30 days | `Response SLA Hours` | +| Urgent Requests | 48 hours | `Urgent Response Hours` | +| Breach Notification | 60 hours | `Breach Notification Hours` | + +## Dialogs + +### Main Entry Point + +- `start.bas` - Main menu for patient privacy rights + +### Patient Rights Dialogs + +- `access-phi.bas` - Request medical records +- `request-amendment.bas` - Request record corrections +- `accounting-disclosures.bas` - View access history +- `request-restrictions.bas` - Limit PHI use/sharing +- `confidential-communications.bas` - Set contact preferences +- `file-complaint.bas` - Submit privacy complaints +- `revoke-authorization.bas` - Withdraw consent + +## Integration + +### Patient Portal Integration + +Connect to your existing patient portal: + +```basic +' In your custom dialog +patient = GET PATIENT FROM "portal" WHERE mrn = patient_mrn +IF patient.verified THEN + CALL "access-phi.bas" +END IF +``` + +### EHR Integration + +The template can integrate with common EHR systems: + +- Epic +- Cerner +- Meditech +- Allscripts + +Configure your EHR connection in the bot settings or use the FHIR API for standard integration. + +## Audit Requirements + +All interactions are logged to the `hipaa_audit_log` table with: + +- Timestamp +- Session ID +- Action performed +- User/patient identifier +- IP address +- User agent +- Outcome + +Retain audit logs for a minimum of 6 years (2,190 days) per HIPAA requirements. + +## Customization + +### Adding Custom Dialogs + +Create new `.bas` files in the `hipaa.gbdialog` folder: + +```basic +' custom-workflow.bas +TALK "Starting custom HIPAA workflow..." +' Your custom logic here +``` + +### Branding + +Customize the welcome message and organization details in `config.csv`. + +## Support + +For questions about this template: + +- **Documentation**: See General Bots docs +- **Issues**: GitHub Issues +- **HIPAA Guidance**: consult your compliance officer + +## Disclaimer + +This template is provided as a compliance aid and does not constitute legal advice. Healthcare organizations are responsible for ensuring their HIPAA compliance program meets all regulatory requirements. Consult with healthcare compliance professionals and legal counsel. + +## License + +AGPL-3.0 - See LICENSE file in the main repository. + +--- + +Built with ❤️ by Pragmatismo \ No newline at end of file diff --git a/templates/compliance/hipaa-medical.gbai/hipaa.gbdialog/start.bas b/templates/compliance/hipaa-medical.gbai/hipaa.gbdialog/start.bas new file mode 100644 index 000000000..66a36ae4d --- /dev/null +++ b/templates/compliance/hipaa-medical.gbai/hipaa.gbdialog/start.bas @@ -0,0 +1,88 @@ +' ============================================================================= +' HIPAA Medical Privacy Portal - Main Dialog +' General Bots Template for Healthcare Data Protection +' ============================================================================= +' This template helps healthcare organizations comply with: +' - HIPAA (Health Insurance Portability and Accountability Act) +' - HITECH Act (Health Information Technology for Economic and Clinical Health) +' - State-specific healthcare privacy regulations +' ============================================================================= + +TALK "🏥 Welcome to the HIPAA Privacy Portal" +TALK "I can help you manage your Protected Health Information (PHI) rights." +TALK "" + +TALK "Under HIPAA, you have the following rights:" +TALK "" +TALK "1️⃣ **Access Your Medical Records** - Request copies of your health information" +TALK "2️⃣ **Request Amendments** - Correct errors in your medical records" +TALK "3️⃣ **Accounting of Disclosures** - See who has accessed your PHI" +TALK "4️⃣ **Request Restrictions** - Limit how we use or share your information" +TALK "5️⃣ **Confidential Communications** - Choose how we contact you" +TALK "6️⃣ **File a Privacy Complaint** - Report a privacy concern" +TALK "7️⃣ **Revoke Authorization** - Withdraw previous consent for PHI disclosure" + +HEAR choice AS "What would you like to do? (1-7 or describe your request)" + +SELECT CASE choice + CASE "1", "access", "records", "medical records", "view", "copy" + CALL "access-phi.bas" + + CASE "2", "amend", "amendment", "correct", "correction", "fix", "error" + CALL "request-amendment.bas" + + CASE "3", "accounting", "disclosures", "who accessed", "access log" + CALL "accounting-disclosures.bas" + + CASE "4", "restrict", "restriction", "limit", "limitations" + CALL "request-restrictions.bas" + + CASE "5", "communications", "contact", "how to contact", "confidential" + CALL "confidential-communications.bas" + + CASE "6", "complaint", "report", "privacy concern", "violation" + CALL "file-complaint.bas" + + CASE "7", "revoke", "withdraw", "cancel authorization" + CALL "revoke-authorization.bas" + + CASE ELSE + ' Use LLM to understand medical privacy requests + SET CONTEXT "You are a HIPAA compliance assistant. Classify the user's request into one of these categories: access_records, amendment, disclosures, restrictions, communications, complaint, revoke. Only respond with the category name." + + intent = LLM "Classify this healthcare privacy request: " + choice + + SELECT CASE intent + CASE "access_records" + CALL "access-phi.bas" + CASE "amendment" + CALL "request-amendment.bas" + CASE "disclosures" + CALL "accounting-disclosures.bas" + CASE "restrictions" + CALL "request-restrictions.bas" + CASE "communications" + CALL "confidential-communications.bas" + CASE "complaint" + CALL "file-complaint.bas" + CASE "revoke" + CALL "revoke-authorization.bas" + CASE ELSE + TALK "I'm not sure I understood your request." + TALK "Please select a number from 1-7 or contact our Privacy Officer directly." + TALK "" + TALK "📞 Privacy Officer: privacy@healthcare.org" + TALK "📧 Email: hipaa-requests@healthcare.org" + CALL "start.bas" + END SELECT +END SELECT + +' Log all interactions for HIPAA audit trail +INSERT INTO "hipaa_audit_log" VALUES { + "timestamp": NOW(), + "session_id": GET SESSION "id", + "action": "privacy_portal_access", + "choice": choice, + "ip_address": GET SESSION "client_ip", + "user_agent": GET SESSION "user_agent" +} diff --git a/templates/compliance/hipaa-medical.gbai/hipaa.gbot/config.csv b/templates/compliance/hipaa-medical.gbai/hipaa.gbot/config.csv new file mode 100644 index 000000000..73dfcba31 --- /dev/null +++ b/templates/compliance/hipaa-medical.gbai/hipaa.gbot/config.csv @@ -0,0 +1,63 @@ +name,value +Bot Name,HIPAA Medical Privacy Center +Bot Description,Healthcare Data Protection and Patient Rights Management Bot +Bot Version,1.0.0 +Bot Author,Pragmatismo +Bot License,AGPL-3.0 +Bot Category,Healthcare Compliance +Bot Tags,hipaa;healthcare;phi;medical;compliance;privacy;patient-rights +Default Language,en +Supported Languages,en;es;pt +Welcome Message,Welcome to the Healthcare Privacy Center. I can help you with your patient rights under HIPAA, including accessing your medical records, requesting amendments, and managing your health information privacy. +Error Message,I apologize, but I encountered an issue processing your request. For urgent matters, please contact our Privacy Officer directly at privacy@healthcare.org +Timeout Message,Your session has timed out for security. Please start a new conversation. This is required to protect your health information. +Session Timeout,300 +Max Retries,3 +Log Level,info +Enable Audit Log,true +Audit Log Retention Days,2190 +Require Authentication,true +Require Email Verification,true +Require 2FA,true +Data Retention Days,2190 +Auto Delete Completed Requests,false +Send Confirmation Emails,true +Privacy Officer Email,privacy@healthcare.org +HIPAA Privacy Officer,hipaa-officer@healthcare.org +HIPAA Security Officer,security-officer@healthcare.org +Covered Entity Name,Your Healthcare Organization +Covered Entity Address,123 Medical Center Drive +Covered Entity NPI,1234567890 +Compliance Frameworks,HIPAA;HITECH;State-Privacy-Laws +Response SLA Hours,30 +Urgent Response Hours,48 +Escalation Email,compliance@healthcare.org +Enable HIPAA Mode,true +PHI Processing Enabled,true +PHI Detection Enabled,true +PHI Auto Redaction,true +Minimum Necessary Standard,true +Encryption At Rest,AES-256 +Encryption In Transit,TLS-1.3 +Access Control Model,RBAC +Break Glass Enabled,true +Break Glass Audit Required,true +Consent Required,true +Authorization Form Version,2.0 +NPP Version,3.0 +NPP Last Updated,2025-01-01 +Designated Record Set,true +Accounting of Disclosures,true +Restriction Requests Enabled,true +Confidential Communications,true +Patient Portal URL,https://portal.healthcare.org +HIE Participation,true +E-Prescribing Enabled,true +Telehealth Enabled,true +BAA Required,true +Workforce Training Required,true +Training Frequency Days,365 +Incident Response Plan,true +Breach Notification Hours,60 +OCR Complaint URL,https://www.hhs.gov/hipaa/filing-a-complaint +State AG Contact,state-ag@state.gov diff --git a/templates/compliance/privacy.gbai/README.md b/templates/compliance/privacy.gbai/README.md new file mode 100644 index 000000000..ed1a11943 --- /dev/null +++ b/templates/compliance/privacy.gbai/README.md @@ -0,0 +1,200 @@ +# Privacy Rights Center Template (privacy.gbai) + +A comprehensive LGPD/GDPR compliance template for General Bots that enables organizations to handle data subject rights requests automatically. + +## Overview + +This template provides a complete privacy portal that helps organizations comply with: + +- **LGPD** (Lei Geral de Proteção de Dados - Brazil) +- **GDPR** (General Data Protection Regulation - EU) +- **CCPA** (California Consumer Privacy Act - US) +- **Other privacy regulations** with similar data subject rights + +## Features + +### Data Subject Rights Implemented + +| Right | LGPD Article | GDPR Article | Dialog File | +|-------|--------------|--------------|-------------| +| Access | Art. 18 | Art. 15 | `request-data.bas` | +| Rectification | Art. 18 III | Art. 16 | `rectify-data.bas` | +| Erasure (Deletion) | Art. 18 VI | Art. 17 | `delete-data.bas` | +| Data Portability | Art. 18 V | Art. 20 | `export-data.bas` | +| Consent Management | Art. 8 | Art. 7 | `manage-consents.bas` | +| Object to Processing | Art. 18 IV | Art. 21 | `object-processing.bas` | + +### Key Capabilities + +- **Identity Verification** - Email-based verification codes before processing requests +- **Audit Trail** - Complete logging of all privacy requests for compliance +- **Multi-format Export** - JSON, CSV, XML export options for data portability +- **Consent Tracking** - Granular consent management with history +- **Email Notifications** - Automated confirmations and reports +- **SLA Tracking** - Response time monitoring (default: 72 hours) + +## Installation + +1. Copy the template to your bot's packages directory: + +```bash +cp -r templates/privacy.gbai /path/to/your/bot/packages/ +``` + +2. Configure the bot settings in `privacy.gbot/config.csv`: + +```csv +name,value +Company Name,Your Company Name +Privacy Officer Email,privacy@yourcompany.com +DPO Contact,dpo@yourcompany.com +``` + +3. Restart General Bots to load the template. + +## Configuration Options + +### Required Settings + +| Setting | Description | Example | +|---------|-------------|---------| +| `Company Name` | Your organization name | Acme Corp | +| `Privacy Officer Email` | Contact for privacy matters | privacy@acme.com | +| `DPO Contact` | Data Protection Officer | dpo@acme.com | + +### Optional Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `Session Timeout` | 900 | Session timeout in seconds | +| `Response SLA Hours` | 72 | Max hours to respond to requests | +| `Data Retention Days` | 30 | Days to retain completed request data | +| `Enable HIPAA Mode` | false | Enable PHI handling features | +| `Require 2FA` | false | Require two-factor authentication | + +## File Structure + +``` +privacy.gbai/ +├── README.md # This file +├── privacy.gbdialog/ +│ ├── start.bas # Main entry point +│ ├── request-data.bas # Data access requests +│ ├── delete-data.bas # Data erasure requests +│ ├── export-data.bas # Data portability +│ └── manage-consents.bas # Consent management +├── privacy.gbot/ +│ └── config.csv # Bot configuration +└── privacy.gbui/ + └── index.html # Web portal UI +``` + +## Usage Examples + +### Starting the Privacy Portal + +Users can access the privacy portal by saying: + +- "I want to access my data" +- "Delete my information" +- "Export my data" +- "Manage my consents" +- Or selecting options 1-6 from the menu + +### API Integration + +The template exposes REST endpoints for integration: + +``` +POST /api/privacy/request - Submit a new request +GET /api/privacy/requests - List user's requests +GET /api/privacy/request/:id - Get request status +POST /api/privacy/consent - Update consents +``` + +### Webhook Events + +The template emits webhook events for integration: + +- `privacy.request.created` - New request submitted +- `privacy.request.completed` - Request fulfilled +- `privacy.consent.updated` - Consent preferences changed +- `privacy.data.deleted` - User data erased + +## Customization + +### Adding Custom Consent Categories + +Edit `manage-consents.bas` to add new consent categories: + +```basic +consent_categories = [ + { + "id": "custom_category", + "name": "Custom Category Name", + "description": "Description for users", + "required": FALSE, + "legal_basis": "Consent" + } +] +``` + +### Branding the UI + +Modify `privacy.gbui/index.html` to match your branding: + +- Update CSS variables for colors +- Replace logo and company name +- Add custom legal text + +### Email Templates + +Customize email notifications by editing the `SEND MAIL` blocks in each dialog file. + +## Compliance Notes + +### Response Deadlines + +| Regulation | Standard Deadline | Extended Deadline | +|------------|-------------------|-------------------| +| LGPD | 15 days | - | +| GDPR | 30 days | 90 days (complex) | +| CCPA | 45 days | 90 days | + +### Data Retention + +Some data may need to be retained for legal compliance: + +- Financial records (tax requirements) +- Legal dispute documentation +- Fraud prevention records +- Regulatory compliance data + +The template handles this by anonymizing retained records while deleting identifiable information. + +### Audit Requirements + +All requests are logged to `privacy_requests` and `consent_history` tables with: + +- Timestamp +- User identifier +- Request type +- IP address +- Completion status +- Legal basis + +## Support + +For questions about this template: + +- **Documentation**: https://docs.pragmatismo.com.br/privacy-template +- **Issues**: https://github.com/GeneralBots/BotServer/issues +- **Email**: support@pragmatismo.com.br + +## License + +This template is part of General Bots and is licensed under AGPL-3.0. + +--- + +**Note**: This template provides technical implementation for privacy compliance. Organizations should consult with legal counsel to ensure full compliance with applicable regulations. \ No newline at end of file diff --git a/templates/compliance/privacy.gbai/privacy.gbdialog/delete-data.bas b/templates/compliance/privacy.gbai/privacy.gbdialog/delete-data.bas new file mode 100644 index 000000000..7135b37e0 --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbdialog/delete-data.bas @@ -0,0 +1,213 @@ +' Privacy Template - Data Deletion Request (Right to be Forgotten) +' LGPD Art. 18, GDPR Art. 17, HIPAA (where applicable) +' This dialog handles user requests to delete their personal data + +TALK "🔒 **Data Deletion Request**" +TALK "I can help you exercise your right to have your personal data deleted." +TALK "This is also known as the 'Right to be Forgotten' under LGPD and GDPR." + +' Authenticate the user first +TALK "For security purposes, I need to verify your identity before proceeding." +HEAR email AS EMAIL WITH "Please enter your registered email address:" + +' Verify email exists in system +user = FIND "users.csv" WHERE email = email +IF user IS NULL THEN + TALK "⚠️ I couldn't find an account with that email address." + TALK "Please check the email and try again, or contact support@company.com" + EXIT +END IF + +' Send verification code +verification_code = RANDOM(100000, 999999) +SET BOT MEMORY "verification_" + email, verification_code +SET BOT MEMORY "verification_expiry_" + email, NOW() + 15 * 60 + +SEND MAIL email, "Data Deletion Verification Code", " +Your verification code is: " + verification_code + " + +This code expires in 15 minutes. + +If you did not request data deletion, please ignore this email and contact support immediately. + +Pragmatismo Privacy Team +" + +HEAR entered_code AS INTEGER WITH "I've sent a verification code to your email. Please enter it here:" + +stored_code = GET BOT MEMORY "verification_" + email +IF entered_code <> stored_code THEN + TALK "❌ Invalid verification code. Please try again." + EXIT +END IF + +TALK "✅ Identity verified." +TALK "" +TALK "**What data would you like to delete?**" +TALK "" +TALK "1️⃣ All my personal data (complete account deletion)" +TALK "2️⃣ Conversation history only" +TALK "3️⃣ Files and documents only" +TALK "4️⃣ Activity logs and analytics" +TALK "5️⃣ Specific data categories (I'll choose)" +TALK "6️⃣ Cancel this request" + +HEAR deletion_choice AS INTEGER WITH "Please enter your choice (1-6):" + +SELECT CASE deletion_choice + CASE 1 + deletion_type = "complete" + TALK "⚠️ **Complete Account Deletion**" + TALK "This will permanently delete:" + TALK "• Your user profile and account" + TALK "• All conversation history" + TALK "• All uploaded files and documents" + TALK "• All activity logs" + TALK "• All preferences and settings" + TALK "" + TALK "**This action cannot be undone.**" + + CASE 2 + deletion_type = "conversations" + TALK "This will delete all your conversation history with our bots." + + CASE 3 + deletion_type = "files" + TALK "This will delete all files and documents you've uploaded." + + CASE 4 + deletion_type = "logs" + TALK "This will delete all activity logs and analytics data associated with you." + + CASE 5 + deletion_type = "selective" + TALK "Please specify which data categories you want deleted:" + HEAR categories WITH "Enter categories separated by commas (e.g., 'email history, phone number, address'):" + + CASE 6 + TALK "Request cancelled. No data has been deleted." + EXIT + + CASE ELSE + TALK "Invalid choice. Please start over." + EXIT +END SELECT + +' Explain data retention exceptions +TALK "" +TALK "📋 **Legal Notice:**" +TALK "Some data may be retained for legal compliance purposes:" +TALK "• Financial records (tax requirements)" +TALK "• Legal dispute documentation" +TALK "• Fraud prevention records" +TALK "• Regulatory compliance data" +TALK "" +TALK "Retained data will be minimized and protected according to law." + +HEAR reason WITH "Please briefly explain why you're requesting deletion (optional, press Enter to skip):" + +HEAR confirmation WITH "Type 'DELETE MY DATA' to confirm this irreversible action:" + +IF confirmation <> "DELETE MY DATA" THEN + TALK "Confirmation not received. Request cancelled for your protection." + EXIT +END IF + +' Log the deletion request +request_id = "DEL-" + FORMAT(NOW(), "YYYYMMDD") + "-" + RANDOM(10000, 99999) +request_date = NOW() + +' Create deletion request record +INSERT INTO "deletion_requests.csv", request_id, email, deletion_type, categories, reason, request_date, "pending" + +' Process the deletion based on type +SELECT CASE deletion_type + CASE "complete" + ' Delete from all tables + DELETE FROM "messages" WHERE user_email = email + DELETE FROM "files" WHERE owner_email = email + DELETE FROM "activity_logs" WHERE user_email = email + DELETE FROM "user_preferences" WHERE email = email + DELETE FROM "sessions" WHERE user_email = email + + ' Anonymize required retention records + UPDATE "audit_logs" SET user_email = "DELETED_USER_" + request_id WHERE user_email = email + + ' Mark user for deletion (actual deletion after retention period) + UPDATE "users" SET status = "pending_deletion", deletion_request_id = request_id WHERE email = email + + CASE "conversations" + DELETE FROM "messages" WHERE user_email = email + DELETE FROM "sessions" WHERE user_email = email + + CASE "files" + ' Get file list for physical deletion + files = FIND "files" WHERE owner_email = email + FOR EACH file IN files + DELETE FILE file.path + NEXT + DELETE FROM "files" WHERE owner_email = email + + CASE "logs" + DELETE FROM "activity_logs" WHERE user_email = email + ' Anonymize audit logs (keep for compliance but remove PII) + UPDATE "audit_logs" SET user_email = "ANONYMIZED" WHERE user_email = email + + CASE "selective" + ' Process specific categories + TALK "Processing selective deletion for: " + categories + ' Custom handling based on categories specified + INSERT INTO "manual_deletion_queue", request_id, email, categories, request_date +END SELECT + +' Update request status +UPDATE "deletion_requests" SET status = "completed", completion_date = NOW() WHERE request_id = request_id + +' Send confirmation email +SEND MAIL email, "Data Deletion Request Confirmed - " + request_id, " +Dear User, + +Your data deletion request has been received and processed. + +**Request Details:** +- Request ID: " + request_id + " +- Request Date: " + FORMAT(request_date, "YYYY-MM-DD HH:mm") + " +- Deletion Type: " + deletion_type + " +- Status: Completed + +**What happens next:** +" + IF(deletion_type = "complete", " +- Your account will be fully deleted within 30 days +- You will receive a final confirmation email +- Some data may be retained for legal compliance (anonymized) +", " +- The specified data has been deleted from our systems +- Some backups may take up to 30 days to purge +") + " + +**Your Rights:** +- You can request a copy of any retained data +- You can file a complaint with your data protection authority +- Contact us at privacy@company.com for questions + +Under LGPD (Brazil) and GDPR (EU), you have the right to: +- Request confirmation of this deletion +- Lodge a complaint with supervisory authorities +- Seek judicial remedy if unsatisfied + +Thank you for trusting us with your data. + +Pragmatismo Privacy Team +Request Reference: " + request_id + " +" + +TALK "" +TALK "✅ **Request Completed**" +TALK "" +TALK "Your deletion request has been processed." +TALK "Request ID: **" + request_id + "**" +TALK "" +TALK "A confirmation email has been sent to " + email +TALK "" +TALK "If you have questions, contact privacy@company.com" +TALK "Reference your Request ID in any communications." diff --git a/templates/compliance/privacy.gbai/privacy.gbdialog/export-data.bas b/templates/compliance/privacy.gbai/privacy.gbdialog/export-data.bas new file mode 100644 index 000000000..cdac2ce6f --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbdialog/export-data.bas @@ -0,0 +1,372 @@ +' ============================================================================ +' Privacy Template: Data Portability/Export Request +' LGPD Art. 18 V / GDPR Art. 20 - Right to Data Portability +' ============================================================================ +' This dialog enables users to export their data in portable formats +' Supports JSON, CSV, and XML export for interoperability + +TALK "📦 **Data Portability Request**" +TALK "You have the right to receive your personal data in a structured, commonly used, and machine-readable format." +TALK "" + +' Verify user identity +TALK "First, I need to verify your identity." +HEAR email AS EMAIL WITH "Please enter your registered email address:" + +user = FIND "users" WHERE email = email +IF user IS NULL THEN + TALK "❌ No account found with that email address." + TALK "Please check and try again, or contact support." + EXIT +END IF + +' Send verification code +code = GENERATE CODE 6 +SET SESSION "export_verification_code", code +SET SESSION "export_email", email + +SEND MAIL email, "Data Export Request - Verification Code", " +Your verification code is: " + code + " + +This code expires in 15 minutes. + +If you did not request this data export, please ignore this email. + +Pragmatismo Privacy Team +" + +HEAR entered_code AS TEXT WITH "📧 Enter the verification code sent to your email:" + +IF entered_code <> code THEN + TALK "❌ Invalid verification code. Please start over." + EXIT +END IF + +TALK "✅ Identity verified!" +TALK "" + +' Ask for export format +TALK "**Choose your export format:**" +TALK "" +TALK "1️⃣ **JSON** - Best for importing into other systems" +TALK "2️⃣ **CSV** - Best for spreadsheets (Excel, Google Sheets)" +TALK "3️⃣ **XML** - Universal interchange format" +TALK "4️⃣ **All formats** - Get all three formats in a ZIP file" + +HEAR format_choice WITH "Enter your choice (1-4):" + +SELECT CASE format_choice + CASE "1", "json", "JSON" + export_format = "json" + format_name = "JSON" + CASE "2", "csv", "CSV" + export_format = "csv" + format_name = "CSV" + CASE "3", "xml", "XML" + export_format = "xml" + format_name = "XML" + CASE "4", "all", "ALL" + export_format = "all" + format_name = "All Formats (ZIP)" + CASE ELSE + export_format = "json" + format_name = "JSON" + TALK "Defaulting to JSON format." +END SELECT + +TALK "" +TALK "**Select data categories to export:**" +TALK "" +TALK "1️⃣ Everything (complete data export)" +TALK "2️⃣ Profile information only" +TALK "3️⃣ Conversations and messages" +TALK "4️⃣ Files and documents" +TALK "5️⃣ Activity history" +TALK "6️⃣ Custom selection" + +HEAR data_choice WITH "Enter your choice (1-6):" + +' Define what data to export based on choice +SELECT CASE data_choice + CASE "1" + include_profile = TRUE + include_conversations = TRUE + include_files = TRUE + include_activity = TRUE + include_consents = TRUE + data_scope = "complete" + + CASE "2" + include_profile = TRUE + include_conversations = FALSE + include_files = FALSE + include_activity = FALSE + include_consents = TRUE + data_scope = "profile" + + CASE "3" + include_profile = FALSE + include_conversations = TRUE + include_files = FALSE + include_activity = FALSE + include_consents = FALSE + data_scope = "conversations" + + CASE "4" + include_profile = FALSE + include_conversations = FALSE + include_files = TRUE + include_activity = FALSE + include_consents = FALSE + data_scope = "files" + + CASE "5" + include_profile = FALSE + include_conversations = FALSE + include_files = FALSE + include_activity = TRUE + include_consents = FALSE + data_scope = "activity" + + CASE "6" + TALK "Select categories (yes/no for each):" + HEAR include_profile AS BOOLEAN WITH "Include profile information?" + HEAR include_conversations AS BOOLEAN WITH "Include conversations?" + HEAR include_files AS BOOLEAN WITH "Include files metadata?" + HEAR include_activity AS BOOLEAN WITH "Include activity logs?" + HEAR include_consents AS BOOLEAN WITH "Include consent records?" + data_scope = "custom" + + CASE ELSE + include_profile = TRUE + include_conversations = TRUE + include_files = TRUE + include_activity = TRUE + include_consents = TRUE + data_scope = "complete" +END SELECT + +TALK "" +TALK "🔄 Preparing your data export... This may take a few minutes." +TALK "" + +' Gather the data +export_data = {} +request_id = "EXP-" + FORMAT(NOW(), "YYYYMMDD-HHmmss") + "-" + user.id + +' Export metadata +export_data.metadata = { + "export_id": request_id, + "export_date": NOW(), + "format": format_name, + "data_scope": data_scope, + "legal_basis": "LGPD Art. 18 V / GDPR Art. 20", + "data_controller": "Your Organization Name", + "contact": "privacy@company.com" +} + +' Gather profile data +IF include_profile THEN + profile = FIND "users" WHERE id = user.id + export_data.profile = { + "name": profile.name, + "email": profile.email, + "phone": profile.phone, + "address": profile.address, + "created_at": profile.created_at, + "last_login": profile.last_login, + "timezone": profile.timezone, + "language": profile.language, + "preferences": profile.preferences + } + TALK "✓ Profile data collected" +END IF + +' Gather conversations +IF include_conversations THEN + messages = FIND "messages" WHERE user_id = user.id ORDER BY created_at + sessions = FIND "sessions" WHERE user_id = user.id + + export_data.conversations = { + "total_sessions": COUNT(sessions), + "total_messages": COUNT(messages), + "sessions": sessions, + "messages": messages + } + TALK "✓ Conversation data collected (" + COUNT(messages) + " messages)" +END IF + +' Gather files metadata +IF include_files THEN + files = FIND "user_files" WHERE user_id = user.id + + file_list = [] + FOR EACH file IN files + file_info = { + "filename": file.name, + "size": file.size, + "type": file.mime_type, + "uploaded_at": file.created_at, + "last_accessed": file.last_accessed, + "path": file.path + } + APPEND file_list, file_info + NEXT + + export_data.files = { + "total_files": COUNT(files), + "total_size": SUM(files, "size"), + "file_list": file_list + } + TALK "✓ Files metadata collected (" + COUNT(files) + " files)" +END IF + +' Gather activity logs +IF include_activity THEN + activity = FIND "activity_logs" WHERE user_id = user.id ORDER BY timestamp DESC LIMIT 10000 + + export_data.activity = { + "total_events": COUNT(activity), + "events": activity + } + TALK "✓ Activity logs collected (" + COUNT(activity) + " events)" +END IF + +' Gather consent records +IF include_consents THEN + consents = FIND "user_consents" WHERE user_id = user.id + + export_data.consents = { + "consent_records": consents, + "current_preferences": { + "marketing_emails": user.marketing_consent, + "analytics": user.analytics_consent, + "third_party_sharing": user.sharing_consent + } + } + TALK "✓ Consent records collected" +END IF + +TALK "" +TALK "📁 Generating export files..." + +' Generate export files based on format +timestamp = FORMAT(NOW(), "YYYYMMDD_HHmmss") +base_filename = "data_export_" + timestamp + +SELECT CASE export_format + CASE "json" + filename = base_filename + ".json" + WRITE filename, JSON(export_data) + + CASE "csv" + ' Generate multiple CSV files for different data types + IF include_profile THEN + WRITE base_filename + "_profile.csv", CSV(export_data.profile) + END IF + IF include_conversations THEN + WRITE base_filename + "_messages.csv", CSV(export_data.conversations.messages) + END IF + IF include_files THEN + WRITE base_filename + "_files.csv", CSV(export_data.files.file_list) + END IF + IF include_activity THEN + WRITE base_filename + "_activity.csv", CSV(export_data.activity.events) + END IF + ' Create ZIP of all CSVs + filename = base_filename + "_csv.zip" + COMPRESS filename, base_filename + "_*.csv" + + CASE "xml" + filename = base_filename + ".xml" + WRITE filename, XML(export_data) + + CASE "all" + ' Generate all formats + WRITE base_filename + ".json", JSON(export_data) + WRITE base_filename + ".xml", XML(export_data) + + IF include_profile THEN + WRITE base_filename + "_profile.csv", CSV(export_data.profile) + END IF + IF include_conversations THEN + WRITE base_filename + "_messages.csv", CSV(export_data.conversations.messages) + END IF + IF include_files THEN + WRITE base_filename + "_files.csv", CSV(export_data.files.file_list) + END IF + + filename = base_filename + "_complete.zip" + COMPRESS filename, base_filename + ".*" +END SELECT + +' Upload to secure storage +secure_path = "/secure/exports/" + user.id + "/" +UPLOAD filename TO secure_path + +' Generate download link (expires in 7 days) +download_link = GENERATE SECURE LINK secure_path + filename EXPIRES 7 DAYS + +' Log the export request for compliance +INSERT INTO "privacy_requests" VALUES { + "id": request_id, + "user_id": user.id, + "request_type": "data_portability", + "data_scope": data_scope, + "format": format_name, + "requested_at": NOW(), + "completed_at": NOW(), + "status": "completed", + "legal_basis": "LGPD Art. 18 V / GDPR Art. 20" +} + +' Send email with download link +SEND MAIL email, "Your Data Export is Ready - " + request_id, " +Dear " + user.name + ", + +Your data export request has been completed. + +**Export Details:** +- Request ID: " + request_id + " +- Format: " + format_name + " +- Data Included: " + data_scope + " +- Generated: " + FORMAT(NOW(), "DD/MM/YYYY HH:mm") + " + +**Download Your Data:** +" + download_link + " + +⚠️ This link expires in 7 days for security purposes. + +**What's Included:** +" + IF(include_profile, "✓ Profile information\n", "") + IF(include_conversations, "✓ Conversation history\n", "") + IF(include_files, "✓ Files metadata\n", "") + IF(include_activity, "✓ Activity logs\n", "") + IF(include_consents, "✓ Consent records\n", "") + " + +**Your Rights Under LGPD/GDPR:** +- Import this data to another service provider +- Request data deletion after export +- Request additional data categories +- File a complaint with data protection authorities + +If you have questions, contact privacy@company.com + +Pragmatismo Privacy Team +" + +TALK "" +TALK "✅ **Export Complete!**" +TALK "" +TALK "📧 A download link has been sent to: " + email +TALK "" +TALK "**Export Details:**" +TALK "• Request ID: " + request_id +TALK "• Format: " + format_name +TALK "• Link expires in: 7 days" +TALK "" +TALK "You can use this data to:" +TALK "• Import into another service" +TALK "• Keep a personal backup" +TALK "• Review what data we hold" +TALK "" +TALK "🔒 Need anything else?" +TALK "• Say **'delete my data'** to request deletion" +TALK "• Say **'privacy settings'** to manage consents" +TALK "• Say **'help'** for other options" diff --git a/templates/compliance/privacy.gbai/privacy.gbdialog/manage-consents.bas b/templates/compliance/privacy.gbai/privacy.gbdialog/manage-consents.bas new file mode 100644 index 000000000..59a318db3 --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbdialog/manage-consents.bas @@ -0,0 +1,333 @@ +' ============================================================================ +' Privacy Template: Consent Management +' LGPD Art. 8 / GDPR Art. 7 - Consent Management +' ============================================================================ +' This dialog allows users to view, grant, and revoke their consents +' Essential for LGPD/GDPR compliance with granular consent tracking + +TALK "🔐 **Consent Management Center**" +TALK "Here you can view and manage all your data processing consents." +TALK "" + +' Verify user identity first +HEAR email AS EMAIL WITH "Please enter your registered email address:" + +user = FIND "users" WHERE email = email +IF user IS NULL THEN + TALK "⚠️ We couldn't find an account with that email." + TALK "Please check the email address and try again." + EXIT +END IF + +' Send quick verification +code = GENERATE CODE 6 +SET SESSION "consent_verify_code", code +SET SESSION "consent_verify_email", email + +SEND MAIL email, "Consent Management - Verification", " +Your verification code is: " + code + " + +This code expires in 10 minutes. + +Pragmatismo Privacy Team +" + +HEAR entered_code AS TEXT WITH "📧 Enter the verification code sent to your email:" + +IF entered_code <> code THEN + TALK "❌ Invalid code. Please try again." + EXIT +END IF + +TALK "✅ Identity verified!" +TALK "" + +' Load current consents +consents = FIND "user_consents" WHERE user_id = user.id + +' Define consent categories +consent_categories = [ + { + "id": "essential", + "name": "Essential Services", + "description": "Required for basic service functionality", + "required": TRUE, + "legal_basis": "Contract performance" + }, + { + "id": "analytics", + "name": "Analytics & Improvement", + "description": "Help us improve our services through usage analysis", + "required": FALSE, + "legal_basis": "Legitimate interest / Consent" + }, + { + "id": "marketing", + "name": "Marketing Communications", + "description": "Receive news, updates, and promotional content", + "required": FALSE, + "legal_basis": "Consent" + }, + { + "id": "personalization", + "name": "Personalization", + "description": "Customize your experience based on preferences", + "required": FALSE, + "legal_basis": "Consent" + }, + { + "id": "third_party", + "name": "Third-Party Sharing", + "description": "Share data with trusted partners for enhanced services", + "required": FALSE, + "legal_basis": "Consent" + }, + { + "id": "ai_training", + "name": "AI Model Training", + "description": "Use anonymized data to improve AI capabilities", + "required": FALSE, + "legal_basis": "Consent" + } +] + +TALK "📋 **Your Current Consents:**" +TALK "" + +FOR EACH category IN consent_categories + current_consent = FILTER(consents, "category = '" + category.id + "'") + IF current_consent IS NOT NULL THEN + status = current_consent.granted ? "✅ Granted" : "❌ Denied" + granted_date = FORMAT(current_consent.updated_at, "DD/MM/YYYY") + ELSE + status = "⚪ Not Set" + granted_date = "N/A" + END IF + + required_tag = category.required ? " (Required)" : "" + TALK category.name + required_tag + ": " + status + TALK " └─ " + category.description + TALK " └─ Legal basis: " + category.legal_basis + TALK " └─ Last updated: " + granted_date + TALK "" +NEXT + +TALK "**What would you like to do?**" +TALK "" +TALK "1️⃣ Grant a consent" +TALK "2️⃣ Revoke a consent" +TALK "3️⃣ Revoke ALL optional consents" +TALK "4️⃣ Grant ALL consents" +TALK "5️⃣ View consent history" +TALK "6️⃣ Download consent record" +TALK "7️⃣ Exit" + +HEAR action AS INTEGER WITH "Enter your choice (1-7):" + +SELECT CASE action + CASE 1 + ' Grant consent + TALK "Which consent would you like to grant?" + TALK "Available options: analytics, marketing, personalization, third_party, ai_training" + HEAR grant_category WITH "Enter consent category:" + + ' Validate category + valid_categories = ["analytics", "marketing", "personalization", "third_party", "ai_training"] + IF NOT CONTAINS(valid_categories, grant_category) THEN + TALK "❌ Invalid category. Please try again." + EXIT + END IF + + ' Record consent with full audit trail + consent_record = { + "user_id": user.id, + "category": grant_category, + "granted": TRUE, + "granted_at": NOW(), + "updated_at": NOW(), + "ip_address": GET SESSION "client_ip", + "user_agent": GET SESSION "user_agent", + "consent_version": "2.0", + "method": "explicit_dialog" + } + + ' Check if exists and update, otherwise insert + existing = FIND "user_consents" WHERE user_id = user.id AND category = grant_category + IF existing IS NOT NULL THEN + UPDATE "user_consents" SET granted = TRUE, updated_at = NOW(), method = "explicit_dialog" WHERE id = existing.id + ELSE + INSERT INTO "user_consents" VALUES consent_record + END IF + + ' Log to consent history + INSERT INTO "consent_history" VALUES { + "user_id": user.id, + "category": grant_category, + "action": "granted", + "timestamp": NOW(), + "ip_address": GET SESSION "client_ip" + } + + TALK "✅ Consent for **" + grant_category + "** has been granted." + TALK "You can revoke this consent at any time." + + CASE 2 + ' Revoke consent + TALK "Which consent would you like to revoke?" + TALK "Note: Essential services consent cannot be revoked while using the service." + HEAR revoke_category WITH "Enter consent category:" + + IF revoke_category = "essential" THEN + TALK "⚠️ Essential consent is required for service operation." + TALK "To revoke it, you must delete your account." + EXIT + END IF + + UPDATE "user_consents" SET granted = FALSE, updated_at = NOW(), method = "explicit_revoke" WHERE user_id = user.id AND category = revoke_category + + INSERT INTO "consent_history" VALUES { + "user_id": user.id, + "category": revoke_category, + "action": "revoked", + "timestamp": NOW(), + "ip_address": GET SESSION "client_ip" + } + + TALK "✅ Consent for **" + revoke_category + "** has been revoked." + TALK "This change takes effect immediately." + + ' Notify relevant systems + WEBHOOK POST "/internal/consent-changed" WITH { + "user_id": user.id, + "category": revoke_category, + "action": "revoked" + } + + CASE 3 + ' Revoke all optional + TALK "⚠️ This will revoke ALL optional consents:" + TALK "• Analytics & Improvement" + TALK "• Marketing Communications" + TALK "• Personalization" + TALK "• Third-Party Sharing" + TALK "• AI Model Training" + + HEAR confirm WITH "Type 'REVOKE ALL' to confirm:" + + IF confirm <> "REVOKE ALL" THEN + TALK "Operation cancelled." + EXIT + END IF + + UPDATE "user_consents" SET granted = FALSE, updated_at = NOW() WHERE user_id = user.id AND category <> "essential" + + INSERT INTO "consent_history" VALUES { + "user_id": user.id, + "category": "ALL_OPTIONAL", + "action": "bulk_revoked", + "timestamp": NOW(), + "ip_address": GET SESSION "client_ip" + } + + TALK "✅ All optional consents have been revoked." + + CASE 4 + ' Grant all + TALK "This will grant consent for all categories." + TALK "You can revoke individual consents at any time." + + HEAR confirm WITH "Type 'GRANT ALL' to confirm:" + + IF confirm <> "GRANT ALL" THEN + TALK "Operation cancelled." + EXIT + END IF + + FOR EACH category IN consent_categories + existing = FIND "user_consents" WHERE user_id = user.id AND category = category.id + IF existing IS NOT NULL THEN + UPDATE "user_consents" SET granted = TRUE, updated_at = NOW() WHERE id = existing.id + ELSE + INSERT INTO "user_consents" VALUES { + "user_id": user.id, + "category": category.id, + "granted": TRUE, + "granted_at": NOW(), + "updated_at": NOW(), + "method": "bulk_grant" + } + END IF + NEXT + + INSERT INTO "consent_history" VALUES { + "user_id": user.id, + "category": "ALL", + "action": "bulk_granted", + "timestamp": NOW() + } + + TALK "✅ All consents have been granted." + + CASE 5 + ' View history + TALK "📜 **Your Consent History:**" + TALK "" + + history = FIND "consent_history" WHERE user_id = user.id ORDER BY timestamp DESC LIMIT 20 + + IF COUNT(history) = 0 THEN + TALK "No consent history found." + ELSE + FOR EACH record IN history + action_icon = record.action CONTAINS "grant" ? "✅" : "❌" + TALK action_icon + " " + FORMAT(record.timestamp, "DD/MM/YYYY HH:mm") + " - " + record.category + " " + record.action + NEXT + END IF + + CASE 6 + ' Download consent record + TALK "📥 Generating your consent record..." + + consent_report = { + "generated_at": NOW(), + "user_email": email, + "current_consents": consents, + "consent_history": FIND "consent_history" WHERE user_id = user.id, + "legal_notice": "This document serves as proof of consent status under LGPD/GDPR" + } + + filename = "consent_record_" + FORMAT(NOW(), "YYYYMMDD") + ".pdf" + GENERATE PDF filename WITH TEMPLATE "consent_report" DATA consent_report + + SEND MAIL email, "Your Consent Record", " +Dear User, + +Please find attached your complete consent record as requested. + +This document includes: +- Current consent status for all categories +- Complete consent history with timestamps +- Legal basis for each processing activity + +Keep this document for your records. + +Pragmatismo Privacy Team + ", ATTACHMENT filename + + TALK "✅ Consent record has been sent to " + email + + CASE 7 + TALK "Thank you for managing your privacy preferences." + TALK "You can return here anytime to update your consents." + EXIT + + CASE ELSE + TALK "Invalid choice. Please try again." +END SELECT + +TALK "" +TALK "🔒 **Privacy Reminder:**" +TALK "• Your consents are stored securely" +TALK "• Changes take effect immediately" +TALK "• You can modify consents anytime" +TALK "• Contact privacy@company.com for questions" diff --git a/templates/compliance/privacy.gbai/privacy.gbdialog/request-data.bas b/templates/compliance/privacy.gbai/privacy.gbdialog/request-data.bas new file mode 100644 index 000000000..306b906fc --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbdialog/request-data.bas @@ -0,0 +1,152 @@ +' ============================================================================ +' Privacy Template: Data Access Request (Subject Access Request - SAR) +' LGPD Art. 18 / GDPR Art. 15 - Right of Access +' ============================================================================ +' This dialog handles user requests to access their personal data +' Companies can install this template for LGPD/GDPR compliance + +TALK "📋 **Data Access Request**" +TALK "You have the right to access all personal data we hold about you." +TALK "" + +' Verify user identity +TALK "First, I need to verify your identity for security purposes." +HEAR email AS EMAIL WITH "Please provide your registered email address:" + +' Check if email exists in system +user = FIND "users" WHERE email = email +IF user IS NULL THEN + TALK "❌ We couldn't find an account with that email address." + TALK "Please check the email and try again, or contact support." + EXIT +END IF + +' Send verification code +code = GENERATE CODE 6 +SET SESSION "verification_code", code +SET SESSION "verified_email", email + +SEND MAIL email, "Data Access Request - Verification Code", " +Your verification code is: " + code + " + +This code expires in 15 minutes. + +If you did not request this, please ignore this email. +" + +HEAR entered_code AS TEXT WITH "📧 We sent a verification code to your email. Please enter it:" + +IF entered_code <> code THEN + TALK "❌ Invalid verification code. Please start over." + EXIT +END IF + +TALK "✅ Identity verified successfully!" +TALK "" + +' Gather all user data +TALK "🔍 Gathering your personal data... This may take a moment." +TALK "" + +' Get user profile data +profile = FIND "users" WHERE email = email +sessions = FIND "sessions" WHERE user_id = profile.id +messages = FIND "messages" WHERE user_id = profile.id +files = FIND "user_files" WHERE user_id = profile.id +consents = FIND "user_consents" WHERE user_id = profile.id +audit_logs = FIND "audit_logs" WHERE user_id = profile.id + +' Build comprehensive report +report_data = { + "request_date": NOW(), + "request_type": "Subject Access Request (SAR)", + "legal_basis": "LGPD Art. 18 / GDPR Art. 15", + "profile": { + "name": profile.name, + "email": profile.email, + "phone": profile.phone, + "created_at": profile.created_at, + "last_login": profile.last_login, + "preferences": profile.preferences + }, + "sessions": { + "total_count": COUNT(sessions), + "active_sessions": FILTER(sessions, "status = 'active'"), + "session_history": sessions + }, + "communications": { + "total_messages": COUNT(messages), + "messages": messages + }, + "files": { + "total_files": COUNT(files), + "file_list": MAP(files, "name, size, created_at") + }, + "consents": consents, + "activity_log": audit_logs +} + +' Generate PDF report +report_filename = "data_access_report_" + FORMAT(NOW(), "YYYYMMDD_HHmmss") + ".pdf" +GENERATE PDF report_filename WITH TEMPLATE "data_access_report" DATA report_data + +' Upload to user's secure area +UPLOAD report_filename TO "/secure/reports/" + profile.id + "/" + +' Send report via email +SEND MAIL email, "Your Data Access Request - Complete Report", " +Dear " + profile.name + ", + +As requested, please find attached a complete report of all personal data we hold about you. + +This report includes: +- Your profile information +- Session history +- Communication records +- Files you have uploaded +- Consent records +- Activity logs + +Report generated: " + FORMAT(NOW(), "DD/MM/YYYY HH:mm") + " + +Your rights under LGPD/GDPR: +- Right to rectification (Art. 18 III LGPD / Art. 16 GDPR) +- Right to erasure (Art. 18 VI LGPD / Art. 17 GDPR) +- Right to data portability (Art. 18 V LGPD / Art. 20 GDPR) +- Right to object to processing (Art. 18 IV LGPD / Art. 21 GDPR) + +To exercise any of these rights, please contact us or use our privacy portal. + +Best regards, +Privacy & Compliance Team +", ATTACHMENT report_filename + +' Log the request for compliance audit +INSERT INTO "privacy_requests" VALUES { + "user_id": profile.id, + "request_type": "data_access", + "requested_at": NOW(), + "completed_at": NOW(), + "status": "completed", + "legal_basis": "LGPD Art. 18 / GDPR Art. 15" +} + +TALK "✅ **Request Complete!**" +TALK "" +TALK "📧 We have sent a comprehensive report to: " + email +TALK "" +TALK "The report includes:" +TALK "• Your profile information" +TALK "• " + COUNT(sessions) + " session records" +TALK "• " + COUNT(messages) + " message records" +TALK "• " + COUNT(files) + " files" +TALK "• Consent history" +TALK "• Activity logs" +TALK "" +TALK "You can also download the report from your account settings." +TALK "" +TALK "🔒 **Your other privacy rights:**" +TALK "• Say **'correct my data'** to update your information" +TALK "• Say **'delete my data'** to request data erasure" +TALK "• Say **'export my data'** for portable format" +TALK "• Say **'privacy settings'** to manage consents" diff --git a/templates/compliance/privacy.gbai/privacy.gbdialog/start.bas b/templates/compliance/privacy.gbai/privacy.gbdialog/start.bas new file mode 100644 index 000000000..44c005f7c --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbdialog/start.bas @@ -0,0 +1,39 @@ +' ============================================================================= +' Privacy Rights Center - LGPD/GDPR Compliance Dialog +' General Bots Template for Data Subject Rights Management +' ============================================================================= +' This template helps organizations comply with: +' - LGPD (Lei Geral de Proteção de Dados - Brazil) +' - GDPR (General Data Protection Regulation - EU) +' - CCPA (California Consumer Privacy Act) +' ============================================================================= + +TALK "Welcome to the Privacy Rights Center. I can help you exercise your data protection rights." +TALK "As a data subject, you have the following rights under LGPD/GDPR:" + +TALK "1. Right of Access - View all data we hold about you" +TALK "2. Right to Rectification - Correct inaccurate data" +TALK "3. Right to Erasure - Request deletion of your data" +TALK "4. Right to Portability - Export your data" +TALK "5. Right to Object - Opt-out of certain processing" +TALK "6. Consent Management - Review and update your consents" + +HEAR choice AS TEXT WITH "What would you like to do? (1-6 or type your request)" + +SELECT CASE choice + CASE "1", "access", "view", "see my data" + CALL "access-data.bas" + + CASE "2", "rectification", "correct", "update", "fix" + CALL "rectify-data.bas" + + CASE "3", "erasure", "delete", "remove", "forget me" + CALL "erase-data.bas" + + CASE "4", "portability", "export", "download" + CALL "export-data.bas" + + CASE "5", "object", "opt-out", "stop processing" + CALL "object-processing.bas" + + CASE "6", "consent", "cons \ No newline at end of file diff --git a/templates/compliance/privacy.gbai/privacy.gbot/config.csv b/templates/compliance/privacy.gbai/privacy.gbot/config.csv new file mode 100644 index 000000000..276375526 --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbot/config.csv @@ -0,0 +1,44 @@ +name,value +Bot Name,Privacy Rights Center +Bot Description,LGPD/GDPR Data Subject Rights Management Bot +Bot Version,1.0.0 +Bot Author,Pragmatismo +Bot License,AGPL-3.0 +Bot Category,Compliance +Bot Tags,privacy;lgpd;gdpr;hipaa;ccpa;compliance;data-protection +Default Language,en +Supported Languages,en;pt;es;de;fr;it +Welcome Message,Welcome to the Privacy Rights Center. I can help you exercise your data protection rights under LGPD, GDPR, and other privacy regulations. +Error Message,I apologize, but I encountered an issue processing your request. Please try again or contact our privacy team at privacy@company.com +Timeout Message,Your session has timed out for security. Please start a new conversation. +Session Timeout,900 +Max Retries,3 +Log Level,info +Enable Audit Log,true +Audit Log Retention Days,2555 +Require Authentication,true +Require Email Verification,true +Require 2FA,false +Data Retention Days,30 +Auto Delete Completed Requests,false +Send Confirmation Emails,true +Privacy Officer Email,privacy@company.com +DPO Contact,dpo@company.com +Supervisory Authority,ANPD +Company Name,Your Company Name +Company Address,Your Company Address +Company Country,BR +Compliance Frameworks,LGPD;GDPR;CCPA +Response SLA Hours,72 +Escalation Email,legal@company.com +Enable HIPAA Mode,false +PHI Processing Enabled,false +Encryption At Rest,true +Encryption In Transit,true +PII Detection Enabled,true +Auto Anonymization,true +Consent Required,true +Consent Version,1.0 +Terms URL,https://company.com/terms +Privacy Policy URL,https://company.com/privacy +Cookie Policy URL,https://company.com/cookies diff --git a/templates/compliance/privacy.gbai/privacy.gbui/index.html b/templates/compliance/privacy.gbai/privacy.gbui/index.html new file mode 100644 index 000000000..6a853ef96 --- /dev/null +++ b/templates/compliance/privacy.gbai/privacy.gbui/index.html @@ -0,0 +1,913 @@ + + + + + + Privacy Rights Center + + + +
+
+ +

Privacy Rights Center

+

Exercise your data protection rights under LGPD, GDPR, and other privacy regulations

+
+ 🇧🇷 LGPD Compliant + 🇪🇺 GDPR Compliant + 🏥 HIPAA Ready + 🇺🇸 CCPA Compliant +
+
+ +
+
+
📋
+

Access My Data

+

Request a complete copy of all personal data we hold about you in a portable format.

+ LGPD Art. 18 / GDPR Art. 15 +
+ +
+
✏️
+

Correct My Data

+

Request correction of inaccurate or incomplete personal data we hold about you.

+ LGPD Art. 18 III / GDPR Art. 16 +
+ +
+
🗑️
+

Delete My Data

+

Request deletion of your personal data (Right to be Forgotten).

+ LGPD Art. 18 VI / GDPR Art. 17 +
+ +
+
📦
+

Export My Data

+

Download your data in a machine-readable format to transfer to another service.

+ LGPD Art. 18 V / GDPR Art. 20 +
+ +
+
⚙️
+

Manage Consents

+

Review and update your data processing consents and preferences.

+ LGPD Art. 8 / GDPR Art. 7 +
+ +
+
🚫
+

Object to Processing

+

Object to certain types of data processing or opt-out of specific activities.

+ LGPD Art. 18 IV / GDPR Art. 21 +
+
+ +
+
+

📊 Your Request History

+ +
+
+
+ Completed + Data Access Request + 2025-01-15 + Download +
+
+ Processing + Consent Update + 2025-01-20 + In Progress +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/drive.html b/templates/drive.html deleted file mode 100644 index cdf84b52a..000000000 --- a/templates/drive.html +++ /dev/null @@ -1,423 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Drive - BotServer{% endblock %} - -{% block content %} -
- -
-

Drive

-
- - -
-
- - -
-
-
-
-
12.3 GB of 50 GB used
-
- - -
- -
-
- - - - - -
- - -
-
Loading folders...
-
-
- - -
- - - - -
- -
- - -
-
Loading files...
-
-
- - - -
-
- - - - - - - - - - -{% endblock %} diff --git a/templates/erp.gbai/erp.gbdialog/purchasing.bas b/templates/erp.gbai/erp.gbdialog/purchasing.bas index 246a3e4a6..fc16c7657 100644 --- a/templates/erp.gbai/erp.gbdialog/purchasing.bas +++ b/templates/erp.gbai/erp.gbdialog/purchasing.bas @@ -45,7 +45,7 @@ IF action = "create_po" THEN WHILE adding_items = true DO TALK "Enter item code (or 'done' to finish):" - item_code = HEAR + HEAR item_code AS "done", * IF item_code = "done" THEN adding_items = false @@ -56,16 +56,10 @@ IF action = "create_po" THEN TALK "Item not found. Try again." ELSE TALK "Quantity to order:" - quantity = HEAR + HEAR quantity AS INTEGER TALK "Unit price (or press Enter for last cost: " + item.last_cost + "):" - price_input = HEAR - - IF price_input = "" THEN - unit_price = item.last_cost - ELSE - unit_price = price_input - END IF + HEAR unit_price AS MONEY DEFAULT item.last_cost line = CREATE OBJECT SET line.id = FORMAT GUID() @@ -101,7 +95,7 @@ IF action = "approve_po" THEN IF po_number = "" THEN TALK "Enter PO number to approve:" - po_number = HEAR + HEAR po_number END IF po = FIND "purchase_orders", "po_number = '" + po_number + "'" @@ -128,9 +122,9 @@ IF action = "approve_po" THEN END FOR TALK "Approve this PO? (yes/no)" - approval = HEAR + HEAR approval AS "yes", "no" - IF approval = "yes" OR approval = "YES" OR approval = "Yes" THEN + IF approval = "yes" THEN po.status = "approved" po.approved_by = user_id po.approved_date = current_time @@ -158,7 +152,7 @@ IF action = "vendor_performance" THEN IF vendor_code = "" THEN TALK "Enter vendor code:" - vendor_code = HEAR + HEAR vendor_code END IF vendor = FIND "vendors", "vendor_code = '" + vendor_code + "'" @@ -266,16 +260,16 @@ IF action = "requisition" THEN WHILE adding = true DO TALK "Enter item description (or 'done'):" - item_desc = HEAR + HEAR item_desc AS "done", * IF item_desc = "done" THEN adding = false ELSE TALK "Quantity needed:" - quantity = HEAR + HEAR quantity AS INTEGER TALK "Reason/Project:" - reason = HEAR + HEAR reason req_item = CREATE OBJECT SET req_item.description = item_desc @@ -304,7 +298,7 @@ IF action = "price_comparison" THEN IF item_code = "" THEN TALK "Enter item code:" - item_code = HEAR + HEAR item_code END IF item = FIND "items", "item_code = '" + item_code + "'" diff --git a/templates/home.html b/templates/home.html deleted file mode 100644 index 3ae839dce..000000000 --- a/templates/home.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Home - General Bots{% endblock %} - -{% block content %} -
-

Welcome to General Bots

-

Your AI-powered workspace

- -
- {% for app in apps %} - -
{{ app.icon }}
-
{{ app.name }}
-
{{ app.description }}
-
- {% endfor %} -
-
- - -{% endblock %} diff --git a/templates/api-client.gbai/api-client.gbdialog/climate.bas b/templates/integration/api-client.gbai/api-client.gbdialog/climate.bas similarity index 100% rename from templates/api-client.gbai/api-client.gbdialog/climate.bas rename to templates/integration/api-client.gbai/api-client.gbdialog/climate.bas diff --git a/templates/api-client.gbai/api-client.gbdialog/msft-partner-center.bas b/templates/integration/api-client.gbai/api-client.gbdialog/msft-partner-center.bas similarity index 100% rename from templates/api-client.gbai/api-client.gbdialog/msft-partner-center.bas rename to templates/integration/api-client.gbai/api-client.gbdialog/msft-partner-center.bas diff --git a/templates/public-apis.gbai/KEYWORDS_CHECKLIST.md b/templates/integration/public-apis.gbai/KEYWORDS_CHECKLIST.md similarity index 100% rename from templates/public-apis.gbai/KEYWORDS_CHECKLIST.md rename to templates/integration/public-apis.gbai/KEYWORDS_CHECKLIST.md diff --git a/templates/public-apis.gbai/QUICKSTART.md b/templates/integration/public-apis.gbai/QUICKSTART.md similarity index 100% rename from templates/public-apis.gbai/QUICKSTART.md rename to templates/integration/public-apis.gbai/QUICKSTART.md diff --git a/templates/public-apis.gbai/README.md b/templates/integration/public-apis.gbai/README.md similarity index 100% rename from templates/public-apis.gbai/README.md rename to templates/integration/public-apis.gbai/README.md diff --git a/templates/public-apis.gbai/public-apis.gbdialog/animals-apis.bas b/templates/integration/public-apis.gbai/public-apis.gbdialog/animals-apis.bas similarity index 100% rename from templates/public-apis.gbai/public-apis.gbdialog/animals-apis.bas rename to templates/integration/public-apis.gbai/public-apis.gbdialog/animals-apis.bas diff --git a/templates/public-apis.gbai/public-apis.gbdialog/data-utility-apis.bas b/templates/integration/public-apis.gbai/public-apis.gbdialog/data-utility-apis.bas similarity index 100% rename from templates/public-apis.gbai/public-apis.gbdialog/data-utility-apis.bas rename to templates/integration/public-apis.gbai/public-apis.gbdialog/data-utility-apis.bas diff --git a/templates/public-apis.gbai/public-apis.gbdialog/entertainment-apis.bas b/templates/integration/public-apis.gbai/public-apis.gbdialog/entertainment-apis.bas similarity index 100% rename from templates/public-apis.gbai/public-apis.gbdialog/entertainment-apis.bas rename to templates/integration/public-apis.gbai/public-apis.gbdialog/entertainment-apis.bas diff --git a/templates/public-apis.gbai/public-apis.gbdialog/food-apis.bas b/templates/integration/public-apis.gbai/public-apis.gbdialog/food-apis.bas similarity index 100% rename from templates/public-apis.gbai/public-apis.gbdialog/food-apis.bas rename to templates/integration/public-apis.gbai/public-apis.gbdialog/food-apis.bas diff --git a/templates/public-apis.gbai/public-apis.gbdialog/science-space-apis.bas b/templates/integration/public-apis.gbai/public-apis.gbdialog/science-space-apis.bas similarity index 100% rename from templates/public-apis.gbai/public-apis.gbdialog/science-space-apis.bas rename to templates/integration/public-apis.gbai/public-apis.gbdialog/science-space-apis.bas diff --git a/templates/public-apis.gbai/public-apis.gbdialog/weather-apis.bas b/templates/integration/public-apis.gbai/public-apis.gbdialog/weather-apis.bas similarity index 100% rename from templates/public-apis.gbai/public-apis.gbdialog/weather-apis.bas rename to templates/integration/public-apis.gbai/public-apis.gbdialog/weather-apis.bas diff --git a/templates/mail.html b/templates/mail.html deleted file mode 100644 index 8c6fbfe9d..000000000 --- a/templates/mail.html +++ /dev/null @@ -1,591 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Mail - BotServer{% endblock %} - -{% block content %} -
- -
-

Mail

-
- - -
-
- - -
- -
- - - - -
-
- 📥 - Inbox - -
-
- 📤 - Sent -
-
- 📝 - Drafts - -
-
- - Starred - -
-
- 🗑️ - Trash -
-
- - -
-
Labels
-
-
Loading...
-
-
-
- - -
- - - - -
-
Loading emails...
-
-
- - -
-
-
📧
-

Select an email to read

-
-
-
-
- - - - - - - -{% endblock %} diff --git a/templates/meet.html b/templates/meet.html deleted file mode 100644 index b1f07a81f..000000000 --- a/templates/meet.html +++ /dev/null @@ -1,949 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Meet - BotServer{% endblock %} - -{% block content %} -
- -
-

Video Meetings

-
- - -
-
- - -
- -
- -
- - - -
- - -
-
Loading meetings...
-
-
- - -
- -
-
-
- -
- - - -
-
-
-

Ready to join?

-

Check your audio and video before joining

-
-
- - -
-
- - -
-
- - -
-
-
-
-
- - - -
- - - -
-
- - - - - -
- - - - -{% endblock %} diff --git a/templates/platform/analytics.gbai/analytics.gbdialog/custom-report.bas b/templates/platform/analytics.gbai/analytics.gbdialog/custom-report.bas new file mode 100644 index 000000000..4f51104c0 --- /dev/null +++ b/templates/platform/analytics.gbai/analytics.gbdialog/custom-report.bas @@ -0,0 +1,257 @@ +' ============================================================================= +' Custom Report Generator Dialog +' General Bots Analytics Template +' ============================================================================= +' This dialog allows users to create custom reports from platform metrics +' ============================================================================= + +TALK "Custom Report Generator" +TALK "I will help you create a custom analytics report." + +HEAR report_name AS TEXT WITH "What would you like to name this report?" + +TALK "Select the time range for your report:" +TALK "1. Last Hour" +TALK "2. Last 24 Hours" +TALK "3. Last 7 Days" +TALK "4. Last 30 Days" +TALK "5. Last 90 Days" +TALK "6. Custom Range" + +HEAR time_choice AS INTEGER + +SELECT CASE time_choice + CASE 1 + time_range = "1h" + time_label = "Last Hour" + CASE 2 + time_range = "24h" + time_label = "Last 24 Hours" + CASE 3 + time_range = "7d" + time_label = "Last 7 Days" + CASE 4 + time_range = "30d" + time_label = "Last 30 Days" + CASE 5 + time_range = "90d" + time_label = "Last 90 Days" + CASE 6 + HEAR start_date AS DATE WITH "Enter start date (YYYY-MM-DD):" + HEAR end_date AS DATE WITH "Enter end date (YYYY-MM-DD):" + time_range = "custom" + time_label = FORMAT(start_date, "YYYY-MM-DD") + " to " + FORMAT(end_date, "YYYY-MM-DD") + CASE ELSE + time_range = "7d" + time_label = "Last 7 Days" +END SELECT + +TALK "Select metrics to include (enter numbers separated by commas):" +TALK "1. Message Volume" +TALK "2. Active Sessions" +TALK "3. Response Time" +TALK "4. LLM Token Usage" +TALK "5. Error Rate" +TALK "6. Storage Usage" +TALK "7. API Calls" +TALK "8. User Activity" +TALK "9. Bot Performance" +TALK "10. All Metrics" + +HEAR metrics_choice AS TEXT + +metrics_list = SPLIT(metrics_choice, ",") + +include_messages = CONTAINS(metrics_list, "1") OR CONTAINS(metrics_list, "10") +include_sessions = CONTAINS(metrics_list, "2") OR CONTAINS(metrics_list, "10") +include_response = CONTAINS(metrics_list, "3") OR CONTAINS(metrics_list, "10") +include_tokens = CONTAINS(metrics_list, "4") OR CONTAINS(metrics_list, "10") +include_errors = CONTAINS(metrics_list, "5") OR CONTAINS(metrics_list, "10") +include_storage = CONTAINS(metrics_list, "6") OR CONTAINS(metrics_list, "10") +include_api = CONTAINS(metrics_list, "7") OR CONTAINS(metrics_list, "10") +include_users = CONTAINS(metrics_list, "8") OR CONTAINS(metrics_list, "10") +include_bots = CONTAINS(metrics_list, "9") OR CONTAINS(metrics_list, "10") + +TALK "Select grouping interval:" +TALK "1. Hourly" +TALK "2. Daily" +TALK "3. Weekly" +TALK "4. Monthly" + +HEAR group_choice AS INTEGER + +SELECT CASE group_choice + CASE 1 + group_interval = "1h" + CASE 2 + group_interval = "1d" + CASE 3 + group_interval = "1w" + CASE 4 + group_interval = "1mo" + CASE ELSE + group_interval = "1d" +END SELECT + +TALK "Generating your custom report..." + +report_data = {} +report_data.name = report_name +report_data.time_range = time_label +report_data.generated_at = NOW() +report_data.generated_by = GET SESSION "user_email" + +IF include_messages THEN + messages = QUERY METRICS "messages" FOR time_range BY group_interval + report_data.messages = messages + report_data.total_messages = SUM(messages, "count") +END IF + +IF include_sessions THEN + sessions = QUERY METRICS "active_sessions" FOR time_range BY group_interval + report_data.sessions = sessions + report_data.peak_sessions = MAX(sessions, "count") +END IF + +IF include_response THEN + response_times = QUERY METRICS "response_time" FOR time_range BY group_interval + report_data.response_times = response_times + report_data.avg_response_ms = AVG(response_times, "duration_ms") +END IF + +IF include_tokens THEN + tokens = QUERY METRICS "llm_tokens" FOR time_range BY group_interval + report_data.tokens = tokens + report_data.total_tokens = SUM(tokens, "total_tokens") +END IF + +IF include_errors THEN + errors = QUERY METRICS "errors" FOR time_range BY group_interval + report_data.errors = errors + report_data.total_errors = SUM(errors, "count") +END IF + +IF include_storage THEN + storage = QUERY METRICS "storage_usage" FOR time_range BY group_interval + report_data.storage = storage + report_data.current_storage_gb = LAST(storage, "bytes_used") / 1073741824 +END IF + +IF include_api THEN + api_calls = QUERY METRICS "api_requests" FOR time_range BY group_interval + report_data.api_calls = api_calls + report_data.total_api_calls = SUM(api_calls, "count") +END IF + +IF include_users THEN + users = FIND "users" WHERE last_login >= DATEADD(NOW(), -30, "day") + report_data.active_users_30d = COUNT(users) +END IF + +IF include_bots THEN + bots = FIND "bots" WHERE status = "active" + report_data.active_bots = COUNT(bots) +END IF + +SET CONTEXT "You are an analytics expert. Generate executive insights from this report data." + +insights = LLM "Analyze this platform data and provide 3-5 key insights for executives: " + JSON(report_data) +report_data.ai_insights = insights + +TALK "Select export format:" +TALK "1. PDF Report" +TALK "2. Excel Spreadsheet" +TALK "3. CSV Data" +TALK "4. JSON Data" +TALK "5. All Formats" + +HEAR format_choice AS INTEGER + +timestamp = FORMAT(NOW(), "YYYYMMDD_HHmmss") +base_filename = "report_" + REPLACE(report_name, " ", "_") + "_" + timestamp + +SELECT CASE format_choice + CASE 1 + filename = base_filename + ".pdf" + GENERATE PDF filename WITH TEMPLATE "analytics_report" DATA report_data + CASE 2 + filename = base_filename + ".xlsx" + WRITE filename, report_data + CASE 3 + filename = base_filename + ".csv" + WRITE filename, CSV(report_data) + CASE 4 + filename = base_filename + ".json" + WRITE filename, JSON(report_data) + CASE 5 + GENERATE PDF base_filename + ".pdf" WITH TEMPLATE "analytics_report" DATA report_data + WRITE base_filename + ".xlsx", report_data + WRITE base_filename + ".csv", CSV(report_data) + WRITE base_filename + ".json", JSON(report_data) + filename = base_filename + ".zip" + COMPRESS filename, base_filename + ".*" + CASE ELSE + filename = base_filename + ".pdf" + GENERATE PDF filename WITH TEMPLATE "analytics_report" DATA report_data +END SELECT + +UPLOAD filename TO "/reports/custom/" + +download_link = GENERATE SECURE LINK "/reports/custom/" + filename EXPIRES 7 DAYS + +TALK "Report generated successfully." +TALK "Report Name: " + report_name +TALK "Time Range: " + time_label +TALK "Download: " + download_link + +HEAR send_email AS BOOLEAN WITH "Would you like to receive this report via email?" + +IF send_email THEN + user_email = GET SESSION "user_email" + SEND MAIL user_email, "Custom Analytics Report: " + report_name, "Your custom analytics report is ready. Download link: " + download_link + " (expires in 7 days)", ATTACHMENT filename + TALK "Report sent to " + user_email +END IF + +INSERT INTO "report_history" VALUES { + "id": "RPT-" + timestamp, + "name": report_name, + "generated_by": GET SESSION "user_email", + "generated_at": NOW(), + "time_range": time_label, + "metrics_included": metrics_choice, + "filename": filename +} + +TALK "Would you like to schedule this report to run automatically?" +HEAR schedule_report AS BOOLEAN + +IF schedule_report THEN + TALK "Select schedule frequency:" + TALK "1. Daily" + TALK "2. Weekly" + TALK "3. Monthly" + + HEAR freq_choice AS INTEGER + + SELECT CASE freq_choice + CASE 1 + schedule = "0 8 * * *" + freq_label = "Daily at 8:00 AM" + CASE 2 + schedule = "0 8 * * 1" + freq_label = "Weekly on Monday at 8:00 AM" + CASE 3 + schedule = "0 8 1 * *" + freq_label = "Monthly on 1st at 8:00 AM" + CASE ELSE + schedule = "0 8 * * 1" + freq_label = "Weekly on Monday at 8:00 AM" + END SELECT + + SET BOT MEMORY "scheduled_report_" + report_name, JSON(report_data) + SET SCHEDULE schedule, "generate-scheduled-report.bas" + + TALK "Report scheduled: " + freq_label +END IF + +TALK "Thank you for using the Custom Report Generator." diff --git a/templates/platform/analytics.gbai/analytics.gbdialog/platform-overview.bas b/templates/platform/analytics.gbai/analytics.gbdialog/platform-overview.bas new file mode 100644 index 000000000..9a7bdd611 --- /dev/null +++ b/templates/platform/analytics.gbai/analytics.gbdialog/platform-overview.bas @@ -0,0 +1,115 @@ +' ============================================================================= +' Platform Overview - Key Metrics Summary +' Analytics Bot Dialog for General Bots +' ============================================================================= + +TALK "Generating platform overview..." + +HEAR timeRange AS TEXT WITH "Select time range (1h, 6h, 24h, 7d, 30d):" DEFAULT "24h" + +' Query platform metrics from time-series database +messages = QUERY METRICS "messages" FOR timeRange +sessions = QUERY METRICS "active_sessions" FOR timeRange +responseTime = QUERY METRICS "response_time" FOR timeRange +errors = QUERY METRICS "errors" FOR timeRange +tokens = QUERY METRICS "llm_tokens" FOR timeRange + +' Calculate totals +totalMessages = SUM(messages, "count") +avgSessions = AVG(sessions, "count") +avgResponseTime = AVG(responseTime, "duration_ms") +totalErrors = SUM(errors, "count") +totalTokens = SUM(tokens, "total_tokens") + +' Calculate trends compared to previous period +prevMessages = QUERY METRICS "messages" FOR timeRange OFFSET 1 +prevSessions = QUERY METRICS "active_sessions" FOR timeRange OFFSET 1 +messagesTrend = ((totalMessages - SUM(prevMessages, "count")) / SUM(prevMessages, "count")) * 100 +sessionsTrend = ((avgSessions - AVG(prevSessions, "count")) / AVG(prevSessions, "count")) * 100 + +TALK "Platform Overview for " + timeRange +TALK "" +TALK "Messages" +TALK " Total: " + FORMAT(totalMessages, "#,###") +TALK " Trend: " + FORMAT(messagesTrend, "+#.#") + "%" +TALK "" +TALK "Sessions" +TALK " Average Active: " + FORMAT(avgSessions, "#,###") +TALK " Trend: " + FORMAT(sessionsTrend, "+#.#") + "%" +TALK "" +TALK "Performance" +TALK " Avg Response Time: " + FORMAT(avgResponseTime, "#.##") + " ms" +TALK "" +TALK "Errors" +TALK " Total: " + FORMAT(totalErrors, "#,###") +TALK " Error Rate: " + FORMAT((totalErrors / totalMessages) * 100, "#.##") + "%" +TALK "" +TALK "LLM Usage" +TALK " Total Tokens: " + FORMAT(totalTokens, "#,###") +TALK "" + +HEAR action AS TEXT WITH "Options: (D)etail, (E)xport report, (A)lerts, (B)ack" + +SELECT CASE UCASE(action) + CASE "D", "DETAIL" + TALK "Select metric for detailed view:" + TALK "1. Messages breakdown by channel" + TALK "2. Sessions by bot" + TALK "3. Response time distribution" + TALK "4. Error breakdown by type" + + HEAR detailChoice AS INTEGER + + SELECT CASE detailChoice + CASE 1 + CALL "message-analytics.bas" + CASE 2 + CALL "user-analytics.bas" + CASE 3 + CALL "performance-metrics.bas" + CASE 4 + CALL "error-analysis.bas" + END SELECT + + CASE "E", "EXPORT" + HEAR exportFormat AS TEXT WITH "Export format (PDF, CSV, XLSX):" DEFAULT "PDF" + + report = { + "title": "Platform Overview Report", + "generated_at": NOW(), + "time_range": timeRange, + "metrics": { + "total_messages": totalMessages, + "messages_trend": messagesTrend, + "avg_sessions": avgSessions, + "sessions_trend": sessionsTrend, + "avg_response_time": avgResponseTime, + "total_errors": totalErrors, + "error_rate": (totalErrors / totalMessages) * 100, + "total_tokens": totalTokens + } + } + + filename = "platform_overview_" + FORMAT(NOW(), "YYYYMMDD_HHmmss") + + SELECT CASE UCASE(exportFormat) + CASE "PDF" + GENERATE PDF filename + ".pdf" WITH TEMPLATE "analytics_report" DATA report + CASE "CSV" + WRITE filename + ".csv", CSV(report.metrics) + CASE "XLSX" + WRITE filename + ".xlsx", EXCEL(report) + END SELECT + + TALK "Report exported: " + filename + "." + LCASE(exportFormat) + TALK "The file is available in your Drive." + + CASE "A", "ALERTS" + CALL "configure-alerts.bas" + + CASE "B", "BACK" + CALL "start.bas" + + CASE ELSE + CALL "start.bas" +END SELECT diff --git a/templates/platform/analytics.gbai/analytics.gbdialog/start.bas b/templates/platform/analytics.gbai/analytics.gbdialog/start.bas new file mode 100644 index 000000000..585b49d34 --- /dev/null +++ b/templates/platform/analytics.gbai/analytics.gbdialog/start.bas @@ -0,0 +1,58 @@ +' ============================================================================= +' Analytics Bot - Platform Metrics and Reporting Dialog +' General Bots Template for Platform Analytics +' ============================================================================= +' This template provides analytics capabilities for: +' - Platform usage metrics +' - Performance monitoring +' - Custom report generation +' - Multi-agent analytics queries +' ============================================================================= + +TALK "Welcome to the Analytics Center. I can help you understand your platform metrics and generate reports." + +TALK "What would you like to analyze?" +TALK "1. Platform Overview - Key metrics summary" +TALK "2. Message Analytics - Conversation statistics" +TALK "3. User Analytics - Active users and sessions" +TALK "4. Performance Metrics - Response times and throughput" +TALK "5. LLM Usage - Token consumption and costs" +TALK "6. Storage Analytics - Disk usage and file statistics" +TALK "7. Error Analysis - Error patterns and trends" +TALK "8. Generate Custom Report" + +HEAR choice AS INTEGER + +SELECT CASE choice + CASE 1 + CALL "platform-overview.bas" + + CASE 2 + CALL "message-analytics.bas" + + CASE 3 + CALL "user-analytics.bas" + + CASE 4 + CALL "performance-metrics.bas" + + CASE 5 + CALL "llm-usage.bas" + + CASE 6 + CALL "storage-analytics.bas" + + CASE 7 + CALL "error-analysis.bas" + + CASE 8 + CALL "custom-report.bas" + + CASE ELSE + SET CONTEXT "You are an analytics assistant. Help the user understand platform metrics. Available data: messages, sessions, response_time, llm_tokens, storage, errors. Answer questions about trends, patterns, and performance." + + HEAR query AS TEXT + + response = LLM "Analyze this analytics query and provide insights: " + query + TALK response +END SELECT diff --git a/templates/platform/analytics.gbai/analytics.gbot/config.csv b/templates/platform/analytics.gbai/analytics.gbot/config.csv new file mode 100644 index 000000000..a86909aff --- /dev/null +++ b/templates/platform/analytics.gbai/analytics.gbot/config.csv @@ -0,0 +1,39 @@ +name,value +Bot Name,Analytics Manager +Bot Description,Platform analytics and reporting bot for managers and administrators +Bot Version,1.0.0 +Bot Author,Pragmatismo +Bot License,AGPL-3.0 +Bot Category,Platform +Bot Tags,analytics;reporting;metrics;dashboard;monitoring;insights +Default Language,en +Supported Languages,en;pt;es +Welcome Message,Welcome to the Analytics Manager. I can help you understand your platform metrics and generate reports. +Error Message,I encountered an issue processing your analytics request. Please try again or contact support. +Timeout Message,Your session has timed out. Please start a new conversation. +Session Timeout,1800 +Max Retries,3 +Log Level,info +Enable Audit Log,true +Require Authentication,true +Required Role,manager +Data Retention Days,365 +Default Time Range,7d +Supported Time Ranges,1h;6h;24h;7d;30d;90d;1y +Default Chart Type,line +Enable Real Time,true +Refresh Interval Seconds,30 +Export Formats,pdf;csv;xlsx;json +Max Export Rows,100000 +Enable AI Insights,true +Enable Anomaly Detection,true +Enable Forecasting,true +Enable Alerts,true +Alert Threshold Critical,90 +Alert Threshold Warning,70 +Metrics Bucket,metrics +Metrics Org,pragmatismo +Dashboard Cache Seconds,60 +Report Cache Seconds,300 +Enable Multi Agent,true +Delegate Bots,default;crm;support diff --git a/templates/office.gbai/office.gbdialog/api-integration.bas b/templates/productivity/office.gbai/office.gbdialog/api-integration.bas similarity index 100% rename from templates/office.gbai/office.gbdialog/api-integration.bas rename to templates/productivity/office.gbai/office.gbdialog/api-integration.bas diff --git a/templates/office.gbai/office.gbdialog/data-sync.bas b/templates/productivity/office.gbai/office.gbdialog/data-sync.bas similarity index 100% rename from templates/office.gbai/office.gbdialog/data-sync.bas rename to templates/productivity/office.gbai/office.gbdialog/data-sync.bas diff --git a/templates/office.gbai/office.gbdialog/document-processor.bas b/templates/productivity/office.gbai/office.gbdialog/document-processor.bas similarity index 100% rename from templates/office.gbai/office.gbdialog/document-processor.bas rename to templates/productivity/office.gbai/office.gbdialog/document-processor.bas diff --git a/templates/office.gbai/office.gbdialog/start.bas b/templates/productivity/office.gbai/office.gbdialog/start.bas similarity index 100% rename from templates/office.gbai/office.gbdialog/start.bas rename to templates/productivity/office.gbai/office.gbdialog/start.bas diff --git a/templates/office.gbai/office.gbot/config.csv b/templates/productivity/office.gbai/office.gbot/config.csv similarity index 100% rename from templates/office.gbai/office.gbot/config.csv rename to templates/productivity/office.gbai/office.gbot/config.csv diff --git a/templates/reminder.gbai/reminder.gbdata/reminders.csv b/templates/productivity/reminder.gbai/reminder.gbdata/reminders.csv similarity index 100% rename from templates/reminder.gbai/reminder.gbdata/reminders.csv rename to templates/productivity/reminder.gbai/reminder.gbdata/reminders.csv diff --git a/templates/reminder.gbai/reminder.gbdialog/add-reminder.bas b/templates/productivity/reminder.gbai/reminder.gbdialog/add-reminder.bas similarity index 100% rename from templates/reminder.gbai/reminder.gbdialog/add-reminder.bas rename to templates/productivity/reminder.gbai/reminder.gbdialog/add-reminder.bas diff --git a/templates/reminder.gbai/reminder.gbdialog/reminder.bas b/templates/productivity/reminder.gbai/reminder.gbdialog/reminder.bas similarity index 100% rename from templates/reminder.gbai/reminder.gbdialog/reminder.bas rename to templates/productivity/reminder.gbai/reminder.gbdialog/reminder.bas diff --git a/templates/reminder.gbai/reminder.gbdialog/start.bas b/templates/productivity/reminder.gbai/reminder.gbdialog/start.bas similarity index 100% rename from templates/reminder.gbai/reminder.gbdialog/start.bas rename to templates/productivity/reminder.gbai/reminder.gbdialog/start.bas diff --git a/templates/sales/crm.gbai/crm.gbdialog/account-management.bas b/templates/sales/crm.gbai/crm.gbdialog/account-management.bas new file mode 100644 index 000000000..408a65aa8 --- /dev/null +++ b/templates/sales/crm.gbai/crm.gbdialog/account-management.bas @@ -0,0 +1,480 @@ +' ============================================================================= +' Account Management Dialog +' Dynamics CRM-style Account Entity Management +' General Bots CRM Template +' ============================================================================= +' This dialog provides comprehensive account (company) management similar to +' Microsoft Dynamics CRM including: +' - Account creation and updates +' - Account hierarchy (parent/child relationships) +' - Contact associations +' - Activity timeline +' - Account scoring and health +' ============================================================================= + +PARAM action AS TEXT + +SELECT CASE UCASE(action) + CASE "CREATE" + CALL create_account + CASE "UPDATE" + CALL update_account + CASE "VIEW" + CALL view_account + CASE "LIST" + CALL list_accounts + CASE "SEARCH" + CALL search_accounts + CASE "HIERARCHY" + CALL account_hierarchy + CASE "CONTACTS" + CALL account_contacts + CASE "ACTIVITIES" + CALL account_activities + CASE "HEALTH" + CALL account_health + CASE ELSE + TALK "Account Management" + TALK "Available actions: Create, Update, View, List, Search, Hierarchy, Contacts, Activities, Health" + HEAR selected_action AS TEXT WITH "What would you like to do?" + CALL "account-management.bas", selected_action +END SELECT + +' ----------------------------------------------------------------------------- +' CREATE ACCOUNT +' ----------------------------------------------------------------------------- +SUB create_account + TALK "Create New Account" + + HEAR account_name AS TEXT WITH "Company name:" + HEAR account_type AS TEXT WITH "Account type (Customer, Partner, Vendor, Competitor, Other):" DEFAULT "Customer" + HEAR industry AS TEXT WITH "Industry:" + HEAR phone AS TEXT WITH "Main phone:" DEFAULT "" + HEAR website AS TEXT WITH "Website:" DEFAULT "" + HEAR email AS TEXT WITH "Primary email:" DEFAULT "" + + TALK "Address Information" + HEAR street AS TEXT WITH "Street address:" DEFAULT "" + HEAR city AS TEXT WITH "City:" DEFAULT "" + HEAR state AS TEXT WITH "State/Province:" DEFAULT "" + HEAR postal_code AS TEXT WITH "Postal code:" DEFAULT "" + HEAR country AS TEXT WITH "Country:" DEFAULT "" + + TALK "Business Information" + HEAR employees AS INTEGER WITH "Number of employees:" DEFAULT 0 + HEAR revenue AS MONEY WITH "Annual revenue:" DEFAULT 0 + HEAR description AS TEXT WITH "Description:" DEFAULT "" + + HEAR parent_account AS TEXT WITH "Parent account (leave empty if none):" DEFAULT "" + + account_id = "ACC-" + FORMAT(NOW(), "YYYYMMDDHHmmss") + "-" + RANDOM(1000, 9999) + created_by = GET SESSION "user_email" + + account = { + "id": account_id, + "name": account_name, + "type": account_type, + "industry": industry, + "phone": phone, + "website": website, + "email": email, + "street": street, + "city": city, + "state": state, + "postal_code": postal_code, + "country": country, + "employees": employees, + "annual_revenue": revenue, + "description": description, + "parent_account_id": parent_account, + "owner_id": created_by, + "created_by": created_by, + "created_at": NOW(), + "modified_at": NOW(), + "status": "Active", + "health_score": 100 + } + + SAVE "accounts.csv", account + + LOG ACTIVITY account_id, "Account", "Created", "Account created: " + account_name, created_by + + RECORD METRIC "crm_accounts" WITH action="created", type=account_type + + TALK "Account created successfully" + TALK "Account ID: " + account_id + TALK "Name: " + account_name + TALK "Type: " + account_type + + HEAR add_contact AS BOOLEAN WITH "Would you like to add a primary contact?" + IF add_contact THEN + SET BOT MEMORY "current_account_id", account_id + CALL "contact-management.bas", "CREATE" + END IF +END SUB + +' ----------------------------------------------------------------------------- +' UPDATE ACCOUNT +' ----------------------------------------------------------------------------- +SUB update_account + HEAR account_id AS TEXT WITH "Enter Account ID or search by name:" + + IF LEFT(account_id, 4) <> "ACC-" THEN + accounts = FIND "accounts" WHERE name LIKE "%" + account_id + "%" + IF COUNT(accounts) = 0 THEN + TALK "No accounts found matching: " + account_id + EXIT SUB + ELSEIF COUNT(accounts) = 1 THEN + account = FIRST(accounts) + account_id = account.id + ELSE + TALK "Multiple accounts found:" + FOR EACH acc IN accounts + TALK acc.id + " - " + acc.name + NEXT + HEAR account_id AS TEXT WITH "Enter the Account ID:" + END IF + END IF + + account = FIND "accounts" WHERE id = account_id + IF account IS NULL THEN + TALK "Account not found: " + account_id + EXIT SUB + END IF + + TALK "Updating: " + account.name + TALK "Press Enter to keep current value" + + HEAR new_name AS TEXT WITH "Company name [" + account.name + "]:" DEFAULT account.name + HEAR new_type AS TEXT WITH "Account type [" + account.type + "]:" DEFAULT account.type + HEAR new_industry AS TEXT WITH "Industry [" + account.industry + "]:" DEFAULT account.industry + HEAR new_phone AS TEXT WITH "Phone [" + account.phone + "]:" DEFAULT account.phone + HEAR new_website AS TEXT WITH "Website [" + account.website + "]:" DEFAULT account.website + HEAR new_email AS TEXT WITH "Email [" + account.email + "]:" DEFAULT account.email + HEAR new_employees AS INTEGER WITH "Employees [" + account.employees + "]:" DEFAULT account.employees + HEAR new_revenue AS MONEY WITH "Annual revenue [" + account.annual_revenue + "]:" DEFAULT account.annual_revenue + HEAR new_status AS TEXT WITH "Status [" + account.status + "] (Active, Inactive, On Hold):" DEFAULT account.status + + UPDATE "accounts" SET + name = new_name, + type = new_type, + industry = new_industry, + phone = new_phone, + website = new_website, + email = new_email, + employees = new_employees, + annual_revenue = new_revenue, + status = new_status, + modified_at = NOW(), + modified_by = GET SESSION "user_email" + WHERE id = account_id + + LOG ACTIVITY account_id, "Account", "Updated", "Account updated", GET SESSION "user_email" + + TALK "Account updated successfully" +END SUB + +' ----------------------------------------------------------------------------- +' VIEW ACCOUNT (360-degree view) +' ----------------------------------------------------------------------------- +SUB view_account + HEAR account_id AS TEXT WITH "Enter Account ID:" + + account = FIND "accounts" WHERE id = account_id + IF account IS NULL THEN + TALK "Account not found" + EXIT SUB + END IF + + TALK "Account Details" + TALK "Name: " + account.name + TALK "ID: " + account.id + TALK "Type: " + account.type + TALK "Industry: " + account.industry + TALK "Status: " + account.status + TALK "" + TALK "Contact Information" + TALK "Phone: " + account.phone + TALK "Email: " + account.email + TALK "Website: " + account.website + TALK "" + TALK "Address" + TALK account.street + TALK account.city + ", " + account.state + " " + account.postal_code + TALK account.country + TALK "" + TALK "Business Information" + TALK "Employees: " + FORMAT(account.employees, "#,###") + TALK "Annual Revenue: " + FORMAT(account.annual_revenue, "$#,###") + TALK "Health Score: " + account.health_score + "/100" + TALK "" + TALK "System Information" + TALK "Owner: " + account.owner_id + TALK "Created: " + FORMAT(account.created_at, "YYYY-MM-DD") + TALK "Modified: " + FORMAT(account.modified_at, "YYYY-MM-DD") + + contacts = FIND "contacts" WHERE account_id = account_id + opportunities = FIND "opportunities" WHERE account_id = account_id + cases = FIND "cases" WHERE account_id = account_id + + TALK "" + TALK "Related Records" + TALK "Contacts: " + COUNT(contacts) + TALK "Opportunities: " + COUNT(opportunities) + TALK "Cases: " + COUNT(cases) + + open_opportunities = FILTER(opportunities, "status <> 'Closed Won' AND status <> 'Closed Lost'") + total_pipeline = SUM(open_opportunities, "estimated_value") + TALK "Pipeline Value: " + FORMAT(total_pipeline, "$#,###") + + won_opportunities = FILTER(opportunities, "status = 'Closed Won'") + total_revenue = SUM(won_opportunities, "actual_value") + TALK "Lifetime Revenue: " + FORMAT(total_revenue, "$#,###") +END SUB + +' ----------------------------------------------------------------------------- +' LIST ACCOUNTS +' ----------------------------------------------------------------------------- +SUB list_accounts + TALK "List Accounts" + TALK "Filter by:" + TALK "1. All Active" + TALK "2. By Type" + TALK "3. By Industry" + TALK "4. By Owner" + TALK "5. Recently Modified" + + HEAR filter_choice AS INTEGER + + SELECT CASE filter_choice + CASE 1 + accounts = FIND "accounts" WHERE status = "Active" ORDER BY name + CASE 2 + HEAR filter_type AS TEXT WITH "Account type (Customer, Partner, Vendor, Competitor):" + accounts = FIND "accounts" WHERE type = filter_type AND status = "Active" ORDER BY name + CASE 3 + HEAR filter_industry AS TEXT WITH "Industry:" + accounts = FIND "accounts" WHERE industry = filter_industry AND status = "Active" ORDER BY name + CASE 4 + HEAR filter_owner AS TEXT WITH "Owner email:" + accounts = FIND "accounts" WHERE owner_id = filter_owner AND status = "Active" ORDER BY name + CASE 5 + accounts = FIND "accounts" WHERE modified_at >= DATEADD(NOW(), -7, "day") ORDER BY modified_at DESC + CASE ELSE + accounts = FIND "accounts" WHERE status = "Active" ORDER BY name LIMIT 20 + END SELECT + + IF COUNT(accounts) = 0 THEN + TALK "No accounts found" + EXIT SUB + END IF + + TALK "Found " + COUNT(accounts) + " accounts:" + TALK "" + + FOR EACH acc IN accounts + TALK acc.id + " | " + acc.name + " | " + acc.type + " | " + acc.industry + NEXT +END SUB + +' ----------------------------------------------------------------------------- +' SEARCH ACCOUNTS +' ----------------------------------------------------------------------------- +SUB search_accounts + HEAR search_term AS TEXT WITH "Search accounts (name, email, phone, or website):" + + accounts = FIND "accounts" WHERE + name LIKE "%" + search_term + "%" OR + email LIKE "%" + search_term + "%" OR + phone LIKE "%" + search_term + "%" OR + website LIKE "%" + search_term + "%" + + IF COUNT(accounts) = 0 THEN + TALK "No accounts found for: " + search_term + EXIT SUB + END IF + + TALK "Found " + COUNT(accounts) + " matching accounts:" + FOR EACH acc IN accounts + TALK acc.id + " - " + acc.name + " (" + acc.type + ")" + TALK " Phone: " + acc.phone + " | Email: " + acc.email + NEXT +END SUB + +' ----------------------------------------------------------------------------- +' ACCOUNT HIERARCHY +' ----------------------------------------------------------------------------- +SUB account_hierarchy + HEAR account_id AS TEXT WITH "Enter Account ID to view hierarchy:" + + account = FIND "accounts" WHERE id = account_id + IF account IS NULL THEN + TALK "Account not found" + EXIT SUB + END IF + + TALK "Account Hierarchy for: " + account.name + TALK "" + + IF account.parent_account_id <> "" THEN + parent = FIND "accounts" WHERE id = account.parent_account_id + IF parent IS NOT NULL THEN + TALK "Parent Account:" + TALK " " + parent.name + " (" + parent.id + ")" + END IF + END IF + + TALK "" + TALK "Current Account:" + TALK " " + account.name + " (" + account.id + ")" + + children = FIND "accounts" WHERE parent_account_id = account_id + IF COUNT(children) > 0 THEN + TALK "" + TALK "Child Accounts:" + FOR EACH child IN children + TALK " - " + child.name + " (" + child.id + ")" + NEXT + END IF +END SUB + +' ----------------------------------------------------------------------------- +' ACCOUNT CONTACTS +' ----------------------------------------------------------------------------- +SUB account_contacts + HEAR account_id AS TEXT WITH "Enter Account ID:" + + account = FIND "accounts" WHERE id = account_id + IF account IS NULL THEN + TALK "Account not found" + EXIT SUB + END IF + + contacts = FIND "contacts" WHERE account_id = account_id ORDER BY is_primary DESC, last_name + + TALK "Contacts for: " + account.name + TALK "Total: " + COUNT(contacts) + TALK "" + + IF COUNT(contacts) = 0 THEN + TALK "No contacts associated with this account" + HEAR add_new AS BOOLEAN WITH "Would you like to add a contact?" + IF add_new THEN + SET BOT MEMORY "current_account_id", account_id + CALL "contact-management.bas", "CREATE" + END IF + EXIT SUB + END IF + + FOR EACH contact IN contacts + primary_marker = "" + IF contact.is_primary THEN + primary_marker = " [PRIMARY]" + END IF + TALK contact.first_name + " " + contact.last_name + primary_marker + TALK " Title: " + contact.job_title + TALK " Email: " + contact.email + TALK " Phone: " + contact.phone + TALK "" + NEXT +END SUB + +' ----------------------------------------------------------------------------- +' ACCOUNT ACTIVITIES +' ----------------------------------------------------------------------------- +SUB account_activities + HEAR account_id AS TEXT WITH "Enter Account ID:" + + account = FIND "accounts" WHERE id = account_id + IF account IS NULL THEN + TALK "Account not found" + EXIT SUB + END IF + + activities = FIND "activities" WHERE related_to = account_id ORDER BY activity_date DESC LIMIT 20 + + TALK "Recent Activities for: " + account.name + TALK "" + + IF COUNT(activities) = 0 THEN + TALK "No activities recorded" + EXIT SUB + END IF + + FOR EACH activity IN activities + TALK FORMAT(activity.activity_date, "YYYY-MM-DD HH:mm") + " | " + activity.activity_type + TALK " " + activity.subject + TALK " By: " + activity.created_by + TALK "" + NEXT +END SUB + +' ----------------------------------------------------------------------------- +' ACCOUNT HEALTH SCORE +' ----------------------------------------------------------------------------- +SUB account_health + HEAR account_id AS TEXT WITH "Enter Account ID:" + + account = FIND "accounts" WHERE id = account_id + IF account IS NULL THEN + TALK "Account not found" + EXIT SUB + END IF + + contacts = FIND "contacts" WHERE account_id = account_id + opportunities = FIND "opportunities" WHERE account_id = account_id + activities = FIND "activities" WHERE related_to = account_id AND activity_date >= DATEADD(NOW(), -90, "day") + cases = FIND "cases" WHERE account_id = account_id AND status = "Open" + + health_score = 100 + health_factors = [] + + IF COUNT(contacts) = 0 THEN + health_score = health_score - 20 + PUSH health_factors, "No contacts (-20)" + END IF + + recent_activities = FILTER(activities, "activity_date >= " + DATEADD(NOW(), -30, "day")) + IF COUNT(recent_activities) = 0 THEN + health_score = health_score - 15 + PUSH health_factors, "No recent activity (-15)" + END IF + + IF COUNT(cases) > 3 THEN + health_score = health_score - 10 + PUSH health_factors, "Multiple open cases (-10)" + END IF + + open_opps = FILTER(opportunities, "status <> 'Closed Won' AND status <> 'Closed Lost'") + IF COUNT(open_opps) > 0 THEN + health_score = health_score + 10 + PUSH health_factors, "Active opportunities (+10)" + END IF + + won_opps = FILTER(opportunities, "status = 'Closed Won' AND close_date >= " + DATEADD(NOW(), -365, "day")) + IF COUNT(won_opps) > 0 THEN + health_score = health_score + 15 + PUSH health_factors, "Recent closed deals (+15)" + END IF + + IF health_score > 100 THEN health_score = 100 + IF health_score < 0 THEN health_score = 0 + + UPDATE "accounts" SET health_score = health_score, modified_at = NOW() WHERE id = account_id + + TALK "Account Health Assessment" + TALK "Account: " + account.name + TALK "" + TALK "Health Score: " + health_score + "/100" + TALK "" + TALK "Factors:" + FOR EACH factor IN health_factors + TALK " - " + factor + NEXT + TALK "" + TALK "Statistics:" + TALK " Contacts: " + COUNT(contacts) + TALK " Activities (90 days): " + COUNT(activities) + TALK " Open Cases: " + COUNT(cases) + TALK " Open Opportunities: " + COUNT(open_opps) +END SUB diff --git a/templates/sales/crm.gbai/crm.gbdialog/activity-tracking.bas b/templates/sales/crm.gbai/crm.gbdialog/activity-tracking.bas new file mode 100644 index 000000000..9aa7dafc4 --- /dev/null +++ b/templates/sales/crm.gbai/crm.gbdialog/activity-tracking.bas @@ -0,0 +1,148 @@ +' ============================================================================= +' Activity Tracking Dialog - CRM Template +' Microsoft Dynamics CRM-style Activity Management +' ============================================================================= +' This dialog handles logging and tracking of customer activities: +' - Phone Calls +' - Emails +' - Meetings/Appointments +' - Tasks +' - Notes +' ============================================================================= + +TALK "Activity Tracking - Log and manage customer interactions" + +HEAR activity_type AS TEXT WITH "Select activity type: (call, email, meeting, task, note)" + +SELECT CASE LCASE(activity_type) + CASE "call", "phone", "1" + CALL "log-call.bas" + + CASE "email", "2" + CALL "log-email-activity.bas" + + CASE "meeting", "appointment", "3" + CALL "log-meeting.bas" + + CASE "task", "4" + CALL "log-task.bas" + + CASE "note", "5" + CALL "log-note.bas" + + CASE ELSE + TALK "I will help you log a general activity." + + HEAR regarding_type AS TEXT WITH "What does this activity relate to? (lead, contact, account, opportunity)" + HEAR regarding_id AS TEXT WITH "Enter the record ID or name:" + + SELECT CASE LCASE(regarding_type) + CASE "lead" + record = FIND "leads" WHERE id = regarding_id OR name LIKE regarding_id FIRST + CASE "contact" + record = FIND "contacts" WHERE id = regarding_id OR full_name LIKE regarding_id FIRST + CASE "account" + record = FIND "accounts" WHERE id = regarding_id OR name LIKE regarding_id FIRST + CASE "opportunity" + record = FIND "opportunities" WHERE id = regarding_id OR name LIKE regarding_id FIRST + END SELECT + + IF record IS NULL THEN + TALK "Record not found. Please verify the ID or name." + EXIT + END IF + + TALK "Record found: " + record.name + + HEAR subject AS TEXT WITH "Activity subject:" + HEAR description AS TEXT WITH "Activity description (details of the interaction):" + HEAR duration AS INTEGER WITH "Duration in minutes:" DEFAULT 30 + HEAR outcome AS TEXT WITH "Outcome (completed, pending, cancelled):" DEFAULT "completed" + + activity_id = "ACT-" + FORMAT(NOW(), "YYYYMMDDHHmmss") + "-" + RANDOM(1000, 9999) + + INSERT INTO "activities" VALUES { + "id": activity_id, + "activity_type": "general", + "subject": subject, + "description": description, + "regarding_type": regarding_type, + "regarding_id": record.id, + "regarding_name": record.name, + "duration_minutes": duration, + "outcome": outcome, + "status": "completed", + "owner_id": GET SESSION "user_id", + "owner_name": GET SESSION "user_name", + "created_at": NOW(), + "activity_date": NOW() + } + + RECORD METRIC "crm_activities" WITH activity_type = "general", outcome = outcome + + TALK "Activity logged successfully." + TALK "Activity ID: " + activity_id +END SELECT + +' Show recent activities for context +HEAR show_recent AS BOOLEAN WITH "Would you like to see recent activities?" + +IF show_recent THEN + HEAR filter_type AS TEXT WITH "Filter by: (all, lead, contact, account, opportunity)" DEFAULT "all" + + IF filter_type = "all" THEN + recent = FIND "activities" ORDER BY activity_date DESC LIMIT 10 + ELSE + recent = FIND "activities" WHERE regarding_type = filter_type ORDER BY activity_date DESC LIMIT 10 + END IF + + IF COUNT(recent) = 0 THEN + TALK "No recent activities found." + ELSE + TALK "Recent Activities:" + TALK "" + + FOR EACH act IN recent + date_str = FORMAT(act.activity_date, "MMM DD, YYYY HH:mm") + TALK act.activity_type + " - " + act.subject + TALK " Regarding: " + act.regarding_name + " (" + act.regarding_type + ")" + TALK " Date: " + date_str + " | Duration: " + act.duration_minutes + " min" + TALK " Status: " + act.status + TALK "" + NEXT + END IF +END IF + +' Activity analytics summary +HEAR show_summary AS BOOLEAN WITH "Would you like to see activity summary?" + +IF show_summary THEN + HEAR summary_period AS TEXT WITH "Period: (today, week, month)" DEFAULT "week" + + SELECT CASE summary_period + CASE "today" + start_date = TODAY() + CASE "week" + start_date = DATEADD(TODAY(), -7, "day") + CASE "month" + start_date = DATEADD(TODAY(), -30, "day") + END SELECT + + activities = FIND "activities" WHERE activity_date >= start_date + + call_count = COUNT(FILTER(activities, "activity_type = 'call'")) + email_count = COUNT(FILTER(activities, "activity_type = 'email'")) + meeting_count = COUNT(FILTER(activities, "activity_type = 'meeting'")) + task_count = COUNT(FILTER(activities, "activity_type = 'task'")) + total_duration = SUM(activities, "duration_minutes") + + TALK "Activity Summary for " + summary_period + TALK "" + TALK "Calls: " + call_count + TALK "Emails: " + email_count + TALK "Meetings: " + meeting_count + TALK "Tasks: " + task_count + TALK "" + TALK "Total Activities: " + COUNT(activities) + TALK "Total Time: " + FORMAT(total_duration / 60, "#.#") + " hours" +END IF diff --git a/templates/crm.gbai/crm.gbdialog/analyze-customer-sentiment.bas b/templates/sales/crm.gbai/crm.gbdialog/analyze-customer-sentiment.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/analyze-customer-sentiment.bas rename to templates/sales/crm.gbai/crm.gbdialog/analyze-customer-sentiment.bas diff --git a/templates/crm.gbai/crm.gbdialog/basic-check.bas b/templates/sales/crm.gbai/crm.gbdialog/basic-check.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/basic-check.bas rename to templates/sales/crm.gbai/crm.gbdialog/basic-check.bas diff --git a/templates/crm.gbai/crm.gbdialog/case-management.bas b/templates/sales/crm.gbai/crm.gbdialog/case-management.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/case-management.bas rename to templates/sales/crm.gbai/crm.gbdialog/case-management.bas diff --git a/templates/crm.gbai/crm.gbdialog/create-lead-from-draft.bas b/templates/sales/crm.gbai/crm.gbdialog/create-lead-from-draft.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/create-lead-from-draft.bas rename to templates/sales/crm.gbai/crm.gbdialog/create-lead-from-draft.bas diff --git a/templates/crm.gbai/crm.gbdialog/crm-jobs.bas b/templates/sales/crm.gbai/crm.gbdialog/crm-jobs.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/crm-jobs.bas rename to templates/sales/crm.gbai/crm.gbdialog/crm-jobs.bas diff --git a/templates/crm.gbai/crm.gbdialog/data-enrichment.bas b/templates/sales/crm.gbai/crm.gbdialog/data-enrichment.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/data-enrichment.bas rename to templates/sales/crm.gbai/crm.gbdialog/data-enrichment.bas diff --git a/templates/crm.gbai/crm.gbdialog/geral.bas b/templates/sales/crm.gbai/crm.gbdialog/geral.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/geral.bas rename to templates/sales/crm.gbai/crm.gbdialog/geral.bas diff --git a/templates/crm.gbai/crm.gbdialog/lead-management.bas b/templates/sales/crm.gbai/crm.gbdialog/lead-management.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/lead-management.bas rename to templates/sales/crm.gbai/crm.gbdialog/lead-management.bas diff --git a/templates/crm.gbai/crm.gbdialog/myitems.bas b/templates/sales/crm.gbai/crm.gbdialog/myitems.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/myitems.bas rename to templates/sales/crm.gbai/crm.gbdialog/myitems.bas diff --git a/templates/crm.gbai/crm.gbdialog/new_email.bas b/templates/sales/crm.gbai/crm.gbdialog/new_email.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/new_email.bas rename to templates/sales/crm.gbai/crm.gbdialog/new_email.bas diff --git a/templates/crm.gbai/crm.gbdialog/new_session.bas b/templates/sales/crm.gbai/crm.gbdialog/new_session.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/new_session.bas rename to templates/sales/crm.gbai/crm.gbdialog/new_session.bas diff --git a/templates/crm.gbai/crm.gbdialog/on-emulator-sent.bas b/templates/sales/crm.gbai/crm.gbdialog/on-emulator-sent.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/on-emulator-sent.bas rename to templates/sales/crm.gbai/crm.gbdialog/on-emulator-sent.bas diff --git a/templates/crm.gbai/crm.gbdialog/on-receive-email.bas b/templates/sales/crm.gbai/crm.gbdialog/on-receive-email.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/on-receive-email.bas rename to templates/sales/crm.gbai/crm.gbdialog/on-receive-email.bas diff --git a/templates/crm.gbai/crm.gbdialog/on_transfer.bas b/templates/sales/crm.gbai/crm.gbdialog/on_transfer.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/on_transfer.bas rename to templates/sales/crm.gbai/crm.gbdialog/on_transfer.bas diff --git a/templates/crm.gbai/crm.gbdialog/opportunity-management.bas b/templates/sales/crm.gbai/crm.gbdialog/opportunity-management.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/opportunity-management.bas rename to templates/sales/crm.gbai/crm.gbdialog/opportunity-management.bas diff --git a/templates/crm.gbai/crm.gbdialog/send-proposal-v0.bas b/templates/sales/crm.gbai/crm.gbdialog/send-proposal-v0.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/send-proposal-v0.bas rename to templates/sales/crm.gbai/crm.gbdialog/send-proposal-v0.bas diff --git a/templates/crm.gbai/crm.gbdialog/send-proposal.bas b/templates/sales/crm.gbai/crm.gbdialog/send-proposal.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/send-proposal.bas rename to templates/sales/crm.gbai/crm.gbdialog/send-proposal.bas diff --git a/templates/crm.gbai/crm.gbdialog/tables.bas b/templates/sales/crm.gbai/crm.gbdialog/tables.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/tables.bas rename to templates/sales/crm.gbai/crm.gbdialog/tables.bas diff --git a/templates/crm.gbai/crm.gbdialog/update-opportunity.bas b/templates/sales/crm.gbai/crm.gbdialog/update-opportunity.bas similarity index 100% rename from templates/crm.gbai/crm.gbdialog/update-opportunity.bas rename to templates/sales/crm.gbai/crm.gbdialog/update-opportunity.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/add-new-idea.bas b/templates/sales/marketing.gbai/marketing.gbdialog/add-new-idea.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/add-new-idea.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/add-new-idea.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/broadcast.bas b/templates/sales/marketing.gbai/marketing.gbdialog/broadcast.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/broadcast.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/broadcast.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/campaigns/lead-nurture-campaign.bas b/templates/sales/marketing.gbai/marketing.gbdialog/campaigns/lead-nurture-campaign.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/campaigns/lead-nurture-campaign.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/campaigns/lead-nurture-campaign.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/campaigns/product-launch-campaign.bas b/templates/sales/marketing.gbai/marketing.gbdialog/campaigns/product-launch-campaign.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/campaigns/product-launch-campaign.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/campaigns/product-launch-campaign.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/campaigns/welcome-campaign.bas b/templates/sales/marketing.gbai/marketing.gbdialog/campaigns/welcome-campaign.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/campaigns/welcome-campaign.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/campaigns/welcome-campaign.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/get-image.bas b/templates/sales/marketing.gbai/marketing.gbdialog/get-image.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/get-image.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/get-image.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/post-to-instagram.bas b/templates/sales/marketing.gbai/marketing.gbdialog/post-to-instagram.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/post-to-instagram.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/post-to-instagram.bas diff --git a/templates/marketing.gbai/marketing.gbdialog/poster.bas b/templates/sales/marketing.gbai/marketing.gbdialog/poster.bas similarity index 100% rename from templates/marketing.gbai/marketing.gbdialog/poster.bas rename to templates/sales/marketing.gbai/marketing.gbdialog/poster.bas diff --git a/templates/tasks.html b/templates/tasks.html deleted file mode 100644 index 09987e809..000000000 --- a/templates/tasks.html +++ /dev/null @@ -1,860 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Tasks - BotServer{% endblock %} - -{% block content %} -
- -
-

Tasks

-
- - -
-
- - -
- -
- -
- - - - - -
- - -
-
- Projects - -
-
-
Loading projects...
-
-
- - -
-
Tags
-
-
- - Important - 5 -
-
- - Personal - 3 -
-
- - Work - 12 -
-
-
-
- - -
- -
- - - -
- - -
- -
- - -
-
- - -
- -
-
-
-

To Do

- 0 -
-
-
No tasks
-
- -
- -
-
-

In Progress

- 0 -
-
-
No tasks
-
- -
- -
-
-

Review

- 0 -
-
-
No tasks
-
- -
- -
-
-

Done

- 0 -
-
-
No tasks
-
- -
-
-
-
- - - -
-
- - - - - - - - - - -{% endblock %} diff --git a/templates/tools.html b/templates/tools.html deleted file mode 100644 index 8c8683e31..000000000 --- a/templates/tools.html +++ /dev/null @@ -1,1019 +0,0 @@ - - - -
- - - - -
-

- 🧹 - Drive Cleaner -

-

- Remove temporary files, browser cache, and other junk to free up disk space. -

- - -
-
-

Disk Space

- -
-
-

Click "Analyze" to scan your disks

-
-
- - -
-

Select items to clean:

-
- - - - - - -
-
- - - - - -
- - -
- - -
-
- - -
-

- 🛡️ - Antivirus -

-

- Scan your system for threats using ClamAV open-source antivirus engine. -

- - -
-
Loading protection status...
-
- - -
-
-
🪟 Use General Bots Instead of Windows Defender
-
- Disable Windows Defender and use General Bots protection (requires admin) -
-
-
- -
-
- - - - - -
-
- - -
- - - -
-
- - -
-

- - Optimize Windows -

-

- Optimize your Windows system for better performance using - OptimizationWindowsV1. -

- - -
-
-
- 💾 -
- Defragment Disk - Optimize disk for faster access -
- -
-
- 🧠 -
- Clear Memory - Free up RAM for better performance -
- -
-
- 🚀 -
- Optimize Services - Disable unnecessary services -
- -
-
- 🎯 -
- Startup Programs - Manage startup applications -
- -
-
-
- - -
- - -
-
- - -
-

- 📦 - Install Software -

-

- Quickly install recommended software on your system. -

- - -
-
-
🦁
-
- Brave Browser - Privacy-focused browser with built-in ad blocker -
- -
-
- - - -
- - -
-
- - - - diff --git a/ui/suite/analytics/analytics.html b/ui/suite/analytics/analytics.html new file mode 100644 index 000000000..d755708ed --- /dev/null +++ b/ui/suite/analytics/analytics.html @@ -0,0 +1,1215 @@ +
+
+
+ + + + +

Analytics Dashboard

+
+
+ + +
+
+ +
+ + + + +
+ +
+ +
+
+

Messages Over Time

+
+ + +
+
+
+
+
+ Loading chart data... +
+
+
+ + +
+
+

Response Time Distribution

+
+ + +
+
+
+
+
+ Loading chart data... +
+
+
+ + +
+
+

Channel Distribution

+
+
+
+
+ Loading chart data... +
+
+
+ + +
+
+

Bot Performance

+
+
+
+
+ Loading chart data... +
+
+
+
+ + +
+
+ + + + +

AI Analytics Assistant

+ Ask questions about your metrics and data +
+ +
+
+
+ + + + + + +
+
+

+ Hello! I can help you analyze your time-series + data. Try asking: +

+
    +
  • + What was the peak message volume today? +
  • +
  • + Why did response times increase last hour? +
  • +
  • + Compare this week vs last week traffic +
  • +
  • + Show me error patterns +
  • +
+
+
+
+ +
+ + +
+
+
+ + + +
+ + + + +
diff --git a/ui/suite/calendar/calendar.html b/ui/suite/calendar/calendar.html new file mode 100644 index 000000000..6d8a96393 --- /dev/null +++ b/ui/suite/calendar/calendar.html @@ -0,0 +1,1762 @@ + +
+ + + + +
+ +
+
+ + +

January 2025

+
+
+
+ + + +
+ +
+
+ + +
+ + + + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ + + +
+ + +
+
+ + + + + + +
+ + + + diff --git a/ui/suite/chat/projector.html b/ui/suite/chat/projector.html new file mode 100644 index 000000000..e1228014b --- /dev/null +++ b/ui/suite/chat/projector.html @@ -0,0 +1,1399 @@ + + + + + + + + diff --git a/ui/suite/css/apps-extended.css b/ui/suite/css/apps-extended.css new file mode 100644 index 000000000..c5d051327 --- /dev/null +++ b/ui/suite/css/apps-extended.css @@ -0,0 +1,318 @@ +/* Extended App Menu Styles - Office 365 Style Grid */ + +/* Override app grid for more columns */ +.app-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + padding: 8px 0; +} + +/* Make dropdown wider to accommodate more apps */ +.apps-dropdown { + width: 360px; + max-height: 80vh; + overflow-y: auto; +} + +/* App item refined styling */ +.app-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + padding: 12px 8px; + border-radius: 8px; + text-decoration: none; + color: hsl(var(--foreground)); + transition: all 0.15s ease; + cursor: pointer; + border: 1px solid transparent; + background: transparent; + min-height: 70px; +} + +.app-item:hover { + background: hsl(var(--accent)); + border-color: hsl(var(--border)); + transform: translateY(-2px); + box-shadow: 0 4px 12px hsla(var(--foreground) / 0.08); +} + +.app-item.active { + background: hsla(var(--primary) / 0.1); + border-color: hsl(var(--primary)); +} + +.app-item.active .app-icon { + transform: scale(1.05); +} + +/* App icon styling */ +.app-icon { + font-size: 26px; + line-height: 1; + transition: transform 0.15s ease; + filter: drop-shadow(0 2px 4px hsla(var(--foreground) / 0.1)); +} + +.app-item:hover .app-icon { + transform: scale(1.1); +} + +/* App name styling */ +.app-item span { + font-size: 11px; + font-weight: 500; + color: hsl(var(--foreground)); + text-align: center; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +/* Dropdown title */ +.apps-dropdown-title { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: hsl(var(--muted-foreground)); + margin-bottom: 12px; + padding: 0 4px; +} + +/* Section divider within app menu */ +.app-grid-section { + grid-column: 1 / -1; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: hsl(var(--muted-foreground)); + padding: 12px 4px 6px; + margin-top: 8px; + border-top: 1px solid hsl(var(--border)); +} + +.app-grid-section:first-child { + margin-top: 0; + border-top: none; + padding-top: 0; +} + +/* Custom scrollbar for dropdown */ +.apps-dropdown::-webkit-scrollbar { + width: 6px; +} + +.apps-dropdown::-webkit-scrollbar-track { + background: transparent; +} + +.apps-dropdown::-webkit-scrollbar-thumb { + background: hsl(var(--muted-foreground) / 0.3); + border-radius: 3px; +} + +.apps-dropdown::-webkit-scrollbar-thumb:hover { + background: hsl(var(--muted-foreground) / 0.5); +} + +/* App badges (for notifications, etc.) */ +.app-item-badge { + position: absolute; + top: 4px; + right: 4px; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 8px; + background: hsl(var(--destructive)); + color: hsl(var(--destructive-foreground)); + font-size: 10px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; +} + +.app-item { + position: relative; +} + +/* Keyboard shortcut hints */ +.app-item::after { + content: attr(data-shortcut); + position: absolute; + bottom: 2px; + right: 4px; + font-size: 9px; + color: hsl(var(--muted-foreground)); + opacity: 0; + transition: opacity 0.15s; +} + +.app-item:hover::after { + opacity: 0.7; +} + +/* Responsive: 3 columns on smaller screens */ +@media (max-width: 480px) { + .apps-dropdown { + width: calc(100vw - 32px); + max-width: 320px; + right: 16px; + } + + .app-grid { + grid-template-columns: repeat(3, 1fr); + } + + .app-icon { + font-size: 24px; + } + + .app-item span { + font-size: 10px; + } +} + +/* App categories for organized menu */ +.app-category { + display: contents; +} + +/* Pinned/Favorite apps section */ +.app-grid-pinned { + display: flex; + gap: 8px; + padding-bottom: 12px; + margin-bottom: 12px; + border-bottom: 1px solid hsl(var(--border)); + overflow-x: auto; +} + +.app-grid-pinned .app-item { + flex-shrink: 0; + width: 72px; +} + +/* Search within app menu */ +.app-search { + padding: 0 4px 12px; +} + +.app-search input { + width: 100%; + padding: 8px 12px; + border: 1px solid hsl(var(--border)); + border-radius: 6px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-size: 13px; +} + +.app-search input:focus { + outline: none; + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 3px hsla(var(--primary) / 0.1); +} + +.app-search input::placeholder { + color: hsl(var(--muted-foreground)); +} + +/* Footer with settings link */ +.apps-dropdown-footer { + border-top: 1px solid hsl(var(--border)); + padding-top: 12px; + margin-top: 12px; + display: flex; + justify-content: center; +} + +.apps-dropdown-footer a { + font-size: 12px; + color: hsl(var(--primary)); + text-decoration: none; + display: flex; + align-items: center; + gap: 4px; +} + +.apps-dropdown-footer a:hover { + text-decoration: underline; +} + +/* Animation for menu items */ +.app-item { + animation: fadeInUp 0.2s ease backwards; +} + +.app-item:nth-child(1) { animation-delay: 0.02s; } +.app-item:nth-child(2) { animation-delay: 0.04s; } +.app-item:nth-child(3) { animation-delay: 0.06s; } +.app-item:nth-child(4) { animation-delay: 0.08s; } +.app-item:nth-child(5) { animation-delay: 0.10s; } +.app-item:nth-child(6) { animation-delay: 0.12s; } +.app-item:nth-child(7) { animation-delay: 0.14s; } +.app-item:nth-child(8) { animation-delay: 0.16s; } + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Dark mode adjustments */ +[data-theme="dark"] .app-item:hover { + background: hsla(var(--foreground) / 0.1); +} + +[data-theme="dark"] .app-icon { + filter: drop-shadow(0 2px 4px hsla(0 0% 0% / 0.3)); +} + +/* Focus styles for accessibility */ +.app-item:focus-visible { + outline: 2px solid hsl(var(--primary)); + outline-offset: 2px; +} + +/* App item tooltip */ +.app-item[title] { + position: relative; +} + +/* Loading state for apps */ +.app-item.loading .app-icon { + opacity: 0.5; + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 0.5; } + 50% { opacity: 1; } +} + +/* New app indicator */ +.app-item.new::before { + content: ''; + position: absolute; + top: 6px; + right: 6px; + width: 8px; + height: 8px; + border-radius: 50%; + background: hsl(var(--chart-2)); + box-shadow: 0 0 0 2px hsl(var(--card)); +} diff --git a/ui/suite/css/components.css b/ui/suite/css/components.css new file mode 100644 index 000000000..39c374188 --- /dev/null +++ b/ui/suite/css/components.css @@ -0,0 +1,1046 @@ +/* Shared Component Styles for General Bots Suite */ + +/* ============================================ */ +/* BUTTONS */ +/* ============================================ */ + +/* Base button reset */ +button { + font-family: inherit; + cursor: pointer; +} + +/* Primary button */ +.btn-primary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 20px; + border: none; + border-radius: 6px; + background: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; +} + +.btn-primary:hover { + opacity: 0.9; + transform: translateY(-1px); + box-shadow: 0 4px 12px hsla(var(--primary) / 0.3); +} + +.btn-primary:active { + transform: translateY(0); +} + +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +/* Secondary button */ +.btn-secondary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 20px; + border: 1px solid hsl(var(--border)); + border-radius: 6px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; +} + +.btn-secondary:hover { + background: hsl(var(--accent)); + border-color: hsl(var(--primary)); +} + +/* Ghost button */ +.btn-ghost { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 20px; + border: none; + border-radius: 6px; + background: transparent; + color: hsl(var(--foreground)); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; +} + +.btn-ghost:hover { + background: hsl(var(--accent)); +} + +/* Icon button */ +.btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + border: none; + border-radius: 6px; + background: transparent; + color: hsl(var(--foreground)); + cursor: pointer; + transition: all 0.15s ease; +} + +.btn-icon:hover { + background: hsl(var(--accent)); +} + +.btn-icon.active { + background: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); +} + +/* Small icon button */ +.btn-icon-sm { + width: 28px; + height: 28px; + border: none; + border-radius: 4px; + background: transparent; + color: hsl(var(--muted-foreground)); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; +} + +.btn-icon-sm:hover { + background: hsl(var(--accent)); + color: hsl(var(--foreground)); +} + +/* Danger button */ +.btn-danger { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 20px; + border: none; + border-radius: 6px; + background: hsl(var(--destructive)); + color: hsl(var(--destructive-foreground)); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; +} + +.btn-danger:hover { + opacity: 0.9; +} + +/* Button sizes */ +.btn-sm { + padding: 6px 12px; + font-size: 12px; +} + +.btn-lg { + padding: 14px 28px; + font-size: 16px; +} + +.btn-full { + width: 100%; +} + +/* ============================================ */ +/* FORMS */ +/* ============================================ */ + +/* Form group */ +.form-group { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 16px; +} + +.form-group:last-child { + margin-bottom: 0; +} + +/* Form row (horizontal layout) */ +.form-row { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} + +/* Labels */ +.form-label { + font-size: 13px; + font-weight: 500; + color: hsl(var(--foreground)); +} + +.form-label-sm { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: hsl(var(--muted-foreground)); +} + +/* Text input */ +.input, +input[type="text"], +input[type="email"], +input[type="password"], +input[type="search"], +input[type="url"], +input[type="number"], +input[type="tel"], +input[type="date"], +input[type="datetime-local"] { + width: 100%; + padding: 10px 12px; + border: 1px solid hsl(var(--border)); + border-radius: 6px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-size: 14px; + font-family: inherit; + transition: all 0.15s ease; +} + +.input:focus, +input:focus { + outline: none; + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 3px hsla(var(--primary) / 0.1); +} + +.input::placeholder, +input::placeholder { + color: hsl(var(--muted-foreground)); +} + +.input:disabled, +input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Input with icon */ +.input-with-icon { + position: relative; + display: flex; + align-items: center; +} + +.input-with-icon .input-icon { + position: absolute; + left: 12px; + color: hsl(var(--muted-foreground)); + pointer-events: none; +} + +.input-with-icon input { + padding-left: 40px; +} + +/* Textarea */ +textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid hsl(var(--border)); + border-radius: 6px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-size: 14px; + font-family: inherit; + resize: vertical; + min-height: 80px; + transition: all 0.15s ease; +} + +textarea:focus { + outline: none; + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 3px hsla(var(--primary) / 0.1); +} + +textarea::placeholder { + color: hsl(var(--muted-foreground)); +} + +/* Select */ +select { + width: 100%; + padding: 10px 12px; + border: 1px solid hsl(var(--border)); + border-radius: 6px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-size: 14px; + font-family: inherit; + cursor: pointer; + transition: all 0.15s ease; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 36px; +} + +select:focus { + outline: none; + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 3px hsla(var(--primary) / 0.1); +} + +/* Checkbox */ +.checkbox { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.checkbox input[type="checkbox"] { + width: 18px; + height: 18px; + border: 2px solid hsl(var(--border)); + border-radius: 4px; + cursor: pointer; + accent-color: hsl(var(--primary)); +} + +.checkbox span { + font-size: 14px; + color: hsl(var(--foreground)); +} + +/* Radio */ +.radio { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.radio input[type="radio"] { + width: 18px; + height: 18px; + cursor: pointer; + accent-color: hsl(var(--primary)); +} + +.radio span { + font-size: 14px; + color: hsl(var(--foreground)); +} + +/* Toggle/Switch */ +.toggle { + position: relative; + display: inline-flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.toggle input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.toggle-track { + width: 44px; + height: 24px; + border-radius: 12px; + background: hsl(var(--muted)); + transition: background 0.2s ease; +} + +.toggle input:checked + .toggle-track { + background: hsl(var(--primary)); +} + +.toggle-thumb { + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + border-radius: 50%; + background: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease; +} + +.toggle input:checked ~ .toggle-thumb { + transform: translateX(20px); +} + +/* Form actions */ +.form-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + padding-top: 16px; + margin-top: 16px; + border-top: 1px solid hsl(var(--border)); +} + +/* ============================================ */ +/* MODALS */ +/* ============================================ */ + +.modal { + position: fixed; + inset: 0; + background: hsla(var(--foreground) / 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 200; + padding: 16px; + animation: modalFadeIn 0.2s ease; +} + +.modal.hidden { + display: none; +} + +@keyframes modalFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.modal-content { + width: 100%; + max-width: 480px; + max-height: calc(100vh - 32px); + background: hsl(var(--card)); + border-radius: 12px; + overflow: hidden; + display: flex; + flex-direction: column; + animation: modalSlideIn 0.2s ease; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-10px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +.modal-sm .modal-content { + max-width: 360px; +} + +.modal-lg .modal-content { + max-width: 640px; +} + +.modal-xl .modal-content { + max-width: 800px; +} + +.modal-full .modal-content { + max-width: calc(100vw - 32px); + max-height: calc(100vh - 32px); +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid hsl(var(--border)); +} + +.modal-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.modal-body { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 12px; + padding: 16px 20px; + border-top: 1px solid hsl(var(--border)); +} + +/* ============================================ */ +/* CARDS */ +/* ============================================ */ + +.card { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 12px; + overflow: hidden; +} + +.card-header { + padding: 16px 20px; + border-bottom: 1px solid hsl(var(--border)); +} + +.card-header h3, +.card-header h4 { + margin: 0; +} + +.card-body { + padding: 20px; +} + +.card-footer { + padding: 16px 20px; + border-top: 1px solid hsl(var(--border)); + background: hsl(var(--muted)); +} + +/* Clickable card */ +.card-clickable { + cursor: pointer; + transition: all 0.15s ease; +} + +.card-clickable:hover { + border-color: hsl(var(--primary)); + transform: translateY(-2px); + box-shadow: 0 4px 12px hsla(var(--foreground) / 0.08); +} + +/* ============================================ */ +/* BADGES & TAGS */ +/* ============================================ */ + +.badge { + display: inline-flex; + align-items: center; + padding: 4px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + background: hsl(var(--muted)); + color: hsl(var(--foreground)); +} + +.badge-primary { + background: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); +} + +.badge-success { + background: hsl(var(--chart-2)); + color: white; +} + +.badge-warning { + background: hsl(var(--chart-3)); + color: hsl(var(--foreground)); +} + +.badge-danger { + background: hsl(var(--destructive)); + color: hsl(var(--destructive-foreground)); +} + +.badge-sm { + padding: 2px 6px; + font-size: 10px; +} + +/* Tag (removable badge) */ +.tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + background: hsl(var(--muted)); + color: hsl(var(--foreground)); +} + +.tag-remove { + display: flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + border: none; + border-radius: 50%; + background: hsla(var(--foreground) / 0.1); + color: inherit; + cursor: pointer; + padding: 0; + font-size: 10px; +} + +.tag-remove:hover { + background: hsla(var(--foreground) / 0.2); +} + +/* ============================================ */ +/* TOOLTIPS */ +/* ============================================ */ + +[data-tooltip] { + position: relative; +} + +[data-tooltip]::after { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + padding: 6px 10px; + border-radius: 4px; + background: hsl(var(--foreground)); + color: hsl(var(--background)); + font-size: 12px; + white-space: nowrap; + opacity: 0; + visibility: hidden; + transition: all 0.15s ease; + pointer-events: none; + z-index: 100; +} + +[data-tooltip]:hover::after { + opacity: 1; + visibility: visible; +} + +/* ============================================ */ +/* DROPDOWNS */ +/* ============================================ */ + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + min-width: 180px; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 8px; + box-shadow: 0 8px 24px hsla(var(--foreground) / 0.1); + padding: 4px; + opacity: 0; + visibility: hidden; + transform: translateY(-8px); + transition: all 0.15s ease; + z-index: 100; +} + +.dropdown.open .dropdown-menu { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.dropdown-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 10px 12px; + border: none; + border-radius: 4px; + background: transparent; + color: hsl(var(--foreground)); + font-size: 14px; + text-align: left; + cursor: pointer; + transition: background 0.15s ease; +} + +.dropdown-item:hover { + background: hsl(var(--accent)); +} + +.dropdown-item.danger { + color: hsl(var(--destructive)); +} + +.dropdown-divider { + height: 1px; + background: hsl(var(--border)); + margin: 4px 0; +} + +/* ============================================ */ +/* TABS */ +/* ============================================ */ + +.tabs { + display: flex; + border-bottom: 1px solid hsl(var(--border)); +} + +.tab { + padding: 12px 20px; + border: none; + border-bottom: 2px solid transparent; + background: transparent; + color: hsl(var(--muted-foreground)); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; +} + +.tab:hover { + color: hsl(var(--foreground)); +} + +.tab.active { + color: hsl(var(--primary)); + border-bottom-color: hsl(var(--primary)); +} + +.tab-content { + padding: 20px 0; +} + +.tab-panel { + display: none; +} + +.tab-panel.active { + display: block; +} + +/* ============================================ */ +/* ALERTS */ +/* ============================================ */ + +.alert { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + border-radius: 8px; + border: 1px solid; +} + +.alert-info { + background: hsla(var(--primary) / 0.1); + border-color: hsl(var(--primary)); + color: hsl(var(--primary)); +} + +.alert-success { + background: hsla(var(--chart-2) / 0.1); + border-color: hsl(var(--chart-2)); + color: hsl(var(--chart-2)); +} + +.alert-warning { + background: hsla(var(--chart-3) / 0.1); + border-color: hsl(var(--chart-3)); + color: hsl(var(--chart-3)); +} + +.alert-danger { + background: hsla(var(--destructive) / 0.1); + border-color: hsl(var(--destructive)); + color: hsl(var(--destructive)); +} + +.alert-icon { + flex-shrink: 0; +} + +.alert-content { + flex: 1; +} + +.alert-title { + font-weight: 600; + margin-bottom: 4px; +} + +.alert-message { + font-size: 14px; + opacity: 0.9; +} + +/* ============================================ */ +/* LOADING STATES */ +/* ============================================ */ + +.spinner { + width: 20px; + height: 20px; + border: 2px solid hsl(var(--muted)); + border-top-color: hsl(var(--primary)); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.spinner-sm { + width: 14px; + height: 14px; + border-width: 1.5px; +} + +.spinner-lg { + width: 32px; + height: 32px; + border-width: 3px; +} + +/* Skeleton loading */ +.skeleton { + background: linear-gradient( + 90deg, + hsl(var(--muted)) 25%, + hsl(var(--accent)) 50%, + hsl(var(--muted)) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: 4px; +} + +@keyframes shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +.skeleton-text { + height: 14px; + margin-bottom: 8px; +} + +.skeleton-title { + height: 20px; + width: 60%; + margin-bottom: 12px; +} + +.skeleton-avatar { + width: 40px; + height: 40px; + border-radius: 50%; +} + +/* ============================================ */ +/* EMPTY STATES */ +/* ============================================ */ + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + text-align: center; +} + +.empty-state-icon { + font-size: 48px; + margin-bottom: 16px; + opacity: 0.5; +} + +.empty-state-title { + font-size: 18px; + font-weight: 600; + margin-bottom: 8px; + color: hsl(var(--foreground)); +} + +.empty-state-message { + font-size: 14px; + color: hsl(var(--muted-foreground)); + max-width: 300px; + margin-bottom: 20px; +} + +/* ============================================ */ +/* DIVIDERS */ +/* ============================================ */ + +.divider { + height: 1px; + background: hsl(var(--border)); + margin: 20px 0; +} + +.divider-vertical { + width: 1px; + height: 24px; + background: hsl(var(--border)); + margin: 0 12px; +} + +.divider-text { + display: flex; + align-items: center; + gap: 16px; + margin: 20px 0; +} + +.divider-text::before, +.divider-text::after { + content: ''; + flex: 1; + height: 1px; + background: hsl(var(--border)); +} + +.divider-text span { + font-size: 12px; + color: hsl(var(--muted-foreground)); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ============================================ */ +/* UTILITIES */ +/* ============================================ */ + +.hidden { + display: none !important; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.text-muted { + color: hsl(var(--muted-foreground)); +} + +.text-primary { + color: hsl(var(--primary)); +} + +.text-success { + color: hsl(var(--chart-2)); +} + +.text-warning { + color: hsl(var(--chart-3)); +} + +.text-danger { + color: hsl(var(--destructive)); +} + +.truncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-1 { gap: 4px; } +.gap-2 { gap: 8px; } +.gap-3 { gap: 12px; } +.gap-4 { gap: 16px; } +.gap-5 { gap: 20px; } + +.p-1 { padding: 4px; } +.p-2 { padding: 8px; } +.p-3 { padding: 12px; } +.p-4 { padding: 16px; } +.p-5 { padding: 20px; } + +.m-1 { margin: 4px; } +.m-2 { margin: 8px; } +.m-3 { margin: 12px; } +.m-4 { margin: 16px; } +.m-5 { margin: 20px; } + +.rounded { border-radius: 6px; } +.rounded-lg { border-radius: 12px; } +.rounded-full { border-radius: 9999px; } + +.shadow-sm { box-shadow: 0 1px 2px hsla(var(--foreground) / 0.05); } +.shadow { box-shadow: 0 2px 8px hsla(var(--foreground) / 0.08); } +.shadow-lg { box-shadow: 0 8px 24px hsla(var(--foreground) / 0.12); } diff --git a/ui/suite/index.html b/ui/suite/index.html index 2e42bb5e6..52ac2b1ed 100644 --- a/ui/suite/index.html +++ b/ui/suite/index.html @@ -12,6 +12,8 @@ + + @@ -68,9 +70,6 @@ viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" - hx-get="/api/apps" - hx-target="#appsDropdown" - hx-trigger="click" > @@ -93,6 +92,7 @@ >
Applications
@@ -210,6 +455,70 @@ } }); } + + // Handle app item clicks - update active state + document.querySelectorAll(".app-item").forEach((item) => { + item.addEventListener("click", function () { + document + .querySelectorAll(".app-item") + .forEach((i) => i.classList.remove("active")); + this.classList.add("active"); + appsDropdown.classList.remove("show"); + appsBtn.setAttribute("aria-expanded", "false"); + }); + }); + + // Handle hash navigation + function handleHashChange() { + const hash = window.location.hash.slice(1) || "chat"; + const appItem = document.querySelector( + `[data-section="${hash}"]`, + ); + if (appItem) { + document + .querySelectorAll(".app-item") + .forEach((i) => i.classList.remove("active")); + appItem.classList.add("active"); + + // Trigger HTMX load if not already loaded + const hxGet = appItem.getAttribute("hx-get"); + if (hxGet) { + htmx.ajax("GET", hxGet, { + target: "#main-content", + }); + } + } + } + + // Load initial content based on hash or default to chat + window.addEventListener("hashchange", handleHashChange); + + // Initial load + setTimeout(() => { + handleHashChange(); + }, 100); + + // Keyboard shortcuts + document.addEventListener("keydown", (e) => { + // Alt + number for quick app switching + if (e.altKey && !e.ctrlKey && !e.shiftKey) { + const num = parseInt(e.key); + if (num >= 1 && num <= 9) { + const items = + document.querySelectorAll(".app-item"); + if (items[num - 1]) { + items[num - 1].click(); + e.preventDefault(); + } + } + } + + // Alt + A to open apps menu + if (e.altKey && e.key.toLowerCase() === "a") { + appsBtn.click(); + e.preventDefault(); + } + }); }); diff --git a/ui/suite/paper/paper.html b/ui/suite/paper/paper.html new file mode 100644 index 000000000..aacffb372 --- /dev/null +++ b/ui/suite/paper/paper.html @@ -0,0 +1,1716 @@ + +
+ + + + +
+ +
+ +
+ + + + +
+ + +
+
+ +
+ + + +
+ + + + +
+ + + +
+ + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + + + +
+
+ + +
+ + + +
+
+ + +
+
+ +
+ + +
+
+
+ + + + + + + + +
+
+ 0 words + 0 characters +
+
+ Saved +
+
+ Last edited: Just now +
+
+
+ + + +
+ + + + diff --git a/ui/suite/research/research.html b/ui/suite/research/research.html new file mode 100644 index 000000000..06949b73b --- /dev/null +++ b/ui/suite/research/research.html @@ -0,0 +1,1457 @@ + +
+ + + + +
+ +
+
+
+ + + + + + + +
+ +
+ + + +
+
+ + +
+
+ +
+ Searching sources... +
+
+ + +
+

Try asking about

+
+ + + + +
+ + + +
+ + +
+ +
+
+ + + +
+ + + + +-right: 1px solid var(--border); + background: var(--card); + display: flex; + flex-direction: column; + overflow-y: auto; + transition: width 0.2s ease; +} + +.research-sidebar.collapsed { + width: 60px; +} + +.research-sidebar.collapsed .sidebar-header h2, +.research-sidebar.collapsed .section-header h3, +.research-sidebar.collapsed .sidebar-section h3, +.research-sidebar.collapsed .focus-btn span, +.research-sidebar.collapsed .prompts-grid, +.research-sidebar.collapsed .collections-list, +.research-sidebar.collapsed .recent-list, +.research-sidebar.collapsed .category-name, +.research-sidebar.collapsed .category-count { + display: none; +} + +.sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid var(--border); +} + +.sidebar-header h2 { + font-size: 18px; + font-weight: 600; + margin: 0; +} + +/* Focus Modes */ +.focus-modes { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 12px; + border-bottom: 1px solid var(--border); +} + +.focus-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--background); + color: var(--foreground); + font-size: 12px; + cursor: pointer; + transition: all 0.15s; +} + +.focus-btn:hover { + background: var(--accent); +} + +.focus-btn.active { + background: var(--primary); + color: var(--primary-foreground); + border-color: var(--primary); +} + +.research-sidebar.collapsed .focus-btn { + padding: 8px; + justify-content: center; +} + +/* Sidebar Sections */ +.sidebar-section { + padding: 16px; + border-bottom: 1px solid var(--border); +} + +.sidebar-section h3 { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + color: var(--muted-foreground); + margin: 0 0 12px 0; + letter-spacing: 0.5px; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.section-header h3 { + margin: 0; +} + +.btn-icon-sm { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + border-radius: 4px; + background: transparent; + color: var(--muted-foreground); + cursor: pointer; +} + +.btn-icon-sm:hover { + background: var(--accent); + color: var(--foreground); +} + +/* Collections */ +.collections-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.collection-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: 6px; + background: transparent; + border: none; + color: var(--foreground); + font-size: 13px; + text-align: left; + cursor: pointer; + transition: background 0.15s; +} + +.collection-item:hover { + background: var(--accent); +} + +.collection-icon { + font-size: 14px; +} + +/* Recent Searches */ +.recent-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.recent-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + border-radius: 4px; + background: transparent; + border: none; + color: var(--muted-foreground); + font-size: 12px; + text-align: left; + cursor: pointer; + transition: all 0.15s; +} + +.recent-item:hover { + background: var(--accent); + color: var(--foreground); +} + +/* Prompts */ +.prompts-grid { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.prompt-chip { + padding: 6px 10px; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--background); + color: var(--foreground); + font-size: 11px; + cursor: pointer; + transition: all 0.15s; +} + +.prompt-chip:hover { + background: var(--primary); + color: var(--primary-foreground); + border-color: var(--primary); +} + +/* Source Categories */ +.sources-categories { + display: flex; + flex-direction: column; + gap: 4px; +} + +.source-category { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--foreground); + font-size: 13px; + text-align: left; + cursor: pointer; + transition: background 0.15s; +} + +.source-category:hover { + background: var(--accent); +} + +.category-icon { + font-size: 16px; +} + +.category-name { + flex: 1; +} + +.category-count { + font-size: 11px; + padding: 2px 6px; + border-radius: 10px; + background: var(--muted); + color: var(--muted-foreground); +} + +/* Main Content */ +.research-main { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Search Header */ +.search-header { + padding: 24px 32px; + border-bottom: 1px solid var(--border); + background: var(--card); +} + +.search-form { + max-width: 800px; + margin: 0 auto; +} + +.search-input-wrapper { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px 20px; + border: 2px solid var(--border); + border-radius: 12px; + background: var(--background); + transition: border-color 0.2s; +} + +.search-input-wrapper:focus-within { + border-color: var(--primary); +} + +.search-icon { + color: var(--muted-foreground); + margin-top: 2px; + flex-shrink: 0; +} + +.search-input-wrapper textarea { + flex: 1; + border: none; + background: transparent; + color: var(--foreground); + font-size: 16px; + line-height: 1.5; + resize: none; + outline: none; + min-height: 24px; + max-height: 120px; +} + +.search-input-wrapper textarea::placeholder { + color: var(--muted-foreground); +} + +.search-options { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 12px; + padding: 0 4px; +} + +.option-toggle { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.option-toggle input { + display: none; +} + +.toggle-label { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + border-radius: 16px; + background: var(--muted); + color: var(--muted-foreground); + font-size: 12px; + transition: all 0.15s; +} + +.option-toggle input:checked + .toggle-label { + background: var(--primary); + color: var(--primary-foreground); +} + +.search-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border: none; + border-radius: 8px; + background: var(--primary); + color: var(--primary-foreground); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.search-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +/* Search Indicator */ +.search-indicator { + display: none; + align-items: center; + justify-content: center; + gap: 12px; + padding: 16px; + margin-top: 16px; +} + +.search-indicator.htmx-request { + display: flex; +} + +.indicator-dots { + display: flex; + gap: 4px; +} + +.indicator-dots span { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--primary); + animation: bounce 1.4s infinite ease-in-out both; +} + +.indicator-dots span:nth-child(1) { animation-delay: -0.32s; } +.indicator-dots span:nth-child(2) { animation-delay: -0.16s; } + +@keyframes bounce { + 0%, 80%, 100% { transform: scale(0); } + 40% { transform: scale(1); } +} + +.indicator-text { + color: var(--muted-foreground); + font-size: 14px; +} + +/* Suggestions Panel */ +.suggestions-panel { + flex: 1; + padding: 40px 32px; + overflow-y: auto; + max-width: 900px; + margin: 0 auto; + width: 100%; +} + +.suggestions-panel.hidden { + display: none; +} + +.suggestions-panel h3 { + font-size: 14px; + font-weight: 600; + color: var(--muted-foreground); + margin: 0 0 16px 0; +} + +.suggestion-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + margin-bottom: 32px; +} + +.suggestion-card { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--card); + color: var(--foreground); + text-align: left; + cursor: pointer; + transition: all 0.2s; +} + +.suggestion-card:hover { + border-color: var(--primary); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.suggestion-icon { + font-size: 24px; +} + +.suggestion-text { + font-size: 14px; + font-weight: 500; +} + +/* Trending */ +.trending-section { + margin-top: 24px; +} + +.trending-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.trending-tag { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--background); + color: var(--foreground); + font-size: 13px; + cursor: pointer; + transition: all 0.15s; +} + +.trending-tag:hover { + background: var(--accent); + border-color: var(--primary); +} + +.trending-tag .trend-icon { + color: var(--chart-1); +} + +/* Results Container */ +.results-container { + flex: 1; + overflow-y: auto; + padding: 24px 32px; +} + +.results-container:empty + .suggestions-panel { + display: block; +} + +.results-container:not(:empty) + .suggestions-panel { + display: none; +} + +/* Result Card */ +.result-card { + max-width: 900px; + margin: 0 auto; +} + +.result-answer { + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} + +.answer-content { + font-size: 15px; + line-height: 1.8; +} + +.answer-content h1, +.answer-content h2, +.answer-content h3 { + margin-top: 24px; + margin-bottom: 12px; +} + +.answer-content p { + margin: 12px 0; +} + +.answer-content ul, +.answer-content ol { + padding-left: 24px; + margin: 12px 0; +} + +.answer-content code { + background: var(--muted); + padding: 2px 6px; + border-radius: 4px; + font-family: monospace; + font-size: 13px; +} + +.answer-content pre { + background: var(--muted); + padding: 16px; + border-radius: 8px; + overflow-x: auto; + margin: 16px 0; +} + +/* Citations */ +.citation { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin: 0 2px; + border-radius: 4px; + background: var(--primary); + color: var(--primary-foreground); + font-size: 10px; + font-weight: 600; + cursor: pointer; + vertical-align: super; + transition: transform 0.15s; +} + +.citation:hover { + transform: scale(1.1); +} + +/* Answer Actions */ +.answer-actions { + display: flex; + gap: 8px; + margin-top: 20px; + padding-top: 16px; + border-top: 1px solid var(--border); +} + +.action-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--background); + color: var(--muted-foreground); + cursor: pointer; + transition: all 0.15s; +} + +.action-btn:hover { + background: var(--accent); + color: var(--foreground); + border-color: var(--primary); +} + +/* Related Questions */ +.related-questions { + margin-bottom: 24px; +} + +.related-questions h4 { + font-size: 14px; + font-weight: 600; + color: var(--muted-foreground); + margin: 0 0 12px 0; +} + +.related-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.related-item { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--card); + color: var(--foreground); + font-size: 14px; + text-align: left; + cursor: pointer; + transition: all 0.15s; +} + +.related-item:hover { + background: var(--accent); + border-color: var(--primary); +} + +.related-item svg { + color: var(--primary); + flex-shrink: 0; +} + +/* Sources Preview */ +.sources-preview { + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 20px; +} + +.sources-preview-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.sources-preview-header h4 { + font-size: 14px; + font-weight: 600; + margin: 0; +} + +.btn-text { + background: none; + border: none; + color: var(--primary); + font-size: 13px; + cursor: pointer; +} + +.btn-text:hover { + text-decoration: underline; +} + +.sources-preview-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 12px; +} + +.source-card { + display: flex; + gap: 12px; + padding: 12px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--background); + cursor: pointer; + transition: all 0.15s; +} + +.source-card:hover { + border-color: var(--primary); + background: var(--accent); +} + +.source-number { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 4px; + background: var(--muted); + color: var(--foreground); + font-size: 12px; + font-weight: 600; + flex-shrink: 0; +} + +.source-info { + flex: 1; + min-width: 0; +} + +.source-title { + font-size: 13px; + font-weight: 500; + margin-bottom: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.source-domain { + font-size: 11px; + color: var(--muted-foreground); + display: flex; + align-items: center; + gap: 4px; +} + +.source-favicon { + width: 12px; + height: 12px; + border-radius: 2px; +} + +/* Sources Panel (Right) */ +.sources-panel { + width: 360px; + border-left: 1px solid var(--border); + background: var(--card); + display: flex; + flex-direction: column; + transition: transform 0.2s ease; +} + +.sources-panel.hidden { + display: none; +} + +.sources-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid var(--border); +} + +.sources-header h3 { + font-size: 16px; + font-weight: 600; + margin: 0; +} + +.sources-actions { + display: flex; + gap: 4px; +} + +.sources-list { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.source-detail-card { + padding: 16px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--background); + margin-bottom: 12px; +} + +.source-detail-header { + display: flex; + align-items: flex-start; + gap: 12px; + margin-bottom: 12px; +} + +.source-detail-number { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 6px; + background: var(--primary); + color: var(--primary-foreground); + font-size: 13px; + font-weight: 600; + flex-shrink: 0; +} + +.source-detail-title { + font-size: 14px; + font-weight: 600; + line-height: 1.4; + margin-bottom: 4px; +} + +.source-detail-url { + font-size: 12px; + color: var(--primary); + text-decoration: none; +} + +.source-detail-url:hover { + text-decoration: underline; +} + +.source-detail-snippet { + font-size: 13px; + color: var(--muted-foreground); + line-height: 1.6; + margin-bottom: 12px; +} + +.source-detail-actions { + display: flex; + gap: 8px; +} + +.source-action-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--background); + color: var(--foreground); + font-size: 11px; + cursor: pointer; + transition: all 0.15s; +} + +.source-action-btn:hover { + background: var(--accent); + border-color: var(--primary); +} + +/* Responsive */ +@media (max-width: 1024px) { + .sources-panel { + position: absolute; + right: 0; + top: 60px; + bottom: 0; + z-index: 50; + } +} + +@media (max-width: 768px) { + .research-sidebar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + z-index: 60; + transform: translateX(-100%); + } + + .research-sidebar.open { + transform: translateX(0); + } + + .search-header { + padding: 16px; + } + + .search-input-wrapper { + padding: 12px 16px; + } + + .suggestions-panel { + padding: 20px 16px; + } + + .suggestion-cards { + grid-template-columns: 1fr; + } + + .sources-preview-list { + grid-template-columns: 1fr; + } + + .sources-panel { + width: 100%; + } +} + + +