Fix bot_id routing: Extract bot name from URL path
- Add bot_name field to WsQuery struct - Extract bot_name from URL path (e.g., /edu, /chat/edu) - Pass bot_name to backend WebSocket URL - Use URL path for bot identification instead of relying on client message
This commit is contained in:
parent
db0f0c1178
commit
27e839f22a
7 changed files with 2867 additions and 1720 deletions
|
|
@ -13,7 +13,7 @@ workspace = true
|
||||||
features = ["http-client"]
|
features = ["http-client"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ui-server", "chat", "drive", "tasks"]
|
default = ["ui-server", "chat", "drive", "tasks", "admin"]
|
||||||
ui-server = []
|
ui-server = []
|
||||||
embed-ui = ["rust-embed"]
|
embed-ui = ["rust-embed"]
|
||||||
|
|
||||||
|
|
|
||||||
278
PROMPT.md
278
PROMPT.md
|
|
@ -1,278 +0,0 @@
|
||||||
# BotUI Development Guide
|
|
||||||
|
|
||||||
**Version:** 6.2.0
|
|
||||||
**Purpose:** Web UI server for General Bots (Axum + HTMX + CSS)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ZERO TOLERANCE POLICY
|
|
||||||
|
|
||||||
**EVERY SINGLE WARNING MUST BE FIXED. NO EXCEPTIONS.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❌ ABSOLUTE PROHIBITIONS
|
|
||||||
|
|
||||||
```
|
|
||||||
❌ NEVER use #![allow()] or #[allow()] in source code
|
|
||||||
❌ NEVER use _ prefix for unused variables - DELETE or USE them
|
|
||||||
❌ NEVER use .unwrap() - use ? or proper error handling
|
|
||||||
❌ NEVER use .expect() - use ? or proper error handling
|
|
||||||
❌ NEVER use panic!() or unreachable!()
|
|
||||||
❌ NEVER use todo!() or unimplemented!()
|
|
||||||
❌ NEVER leave unused imports or dead code
|
|
||||||
❌ NEVER add comments - code must be self-documenting
|
|
||||||
❌ NEVER use CDN links - all assets must be local
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ ARCHITECTURE
|
|
||||||
|
|
||||||
### Dual Modes
|
|
||||||
|
|
||||||
| Mode | Command | Description |
|
|
||||||
|------|---------|-------------|
|
|
||||||
| Web | `cargo run` | Axum server on port 3000 |
|
|
||||||
| Desktop | `cargo tauri dev` | Tauri native window |
|
|
||||||
|
|
||||||
### Code Organization
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── main.rs # Entry point - mode detection
|
|
||||||
├── lib.rs # Feature-gated module exports
|
|
||||||
├── http_client.rs # HTTP wrapper for botserver
|
|
||||||
├── ui_server/
|
|
||||||
│ └── mod.rs # Axum router + UI serving
|
|
||||||
├── desktop/
|
|
||||||
│ ├── mod.rs # Desktop module organization
|
|
||||||
│ ├── drive.rs # File operations via Tauri
|
|
||||||
│ └── tray.rs # System tray
|
|
||||||
└── shared/
|
|
||||||
└── state.rs # Shared application state
|
|
||||||
|
|
||||||
ui/
|
|
||||||
├── suite/ # Main UI (HTML/CSS/JS)
|
|
||||||
│ ├── js/vendor/ # Local JS libraries
|
|
||||||
│ └── css/ # Stylesheets
|
|
||||||
└── minimal/ # Minimal chat UI
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 HTMX-FIRST FRONTEND
|
|
||||||
|
|
||||||
### Core Principle
|
|
||||||
- **Use HTMX** to minimize JavaScript
|
|
||||||
- **Server returns HTML fragments**, not JSON
|
|
||||||
- **Delegate ALL logic** to Rust server
|
|
||||||
|
|
||||||
### HTMX Usage
|
|
||||||
|
|
||||||
| Use Case | Solution |
|
|
||||||
|----------|----------|
|
|
||||||
| Data fetching | `hx-get`, `hx-post` |
|
|
||||||
| Form submission | `hx-post`, `hx-put` |
|
|
||||||
| Real-time updates | `hx-ext="ws"` |
|
|
||||||
| Content swapping | `hx-target`, `hx-swap` |
|
|
||||||
| Polling | `hx-trigger="every 5s"` |
|
|
||||||
| Loading states | `hx-indicator` |
|
|
||||||
|
|
||||||
### When JS is Required
|
|
||||||
|
|
||||||
| Use Case | Why JS Required |
|
|
||||||
|----------|-----------------|
|
|
||||||
| Modal show/hide | DOM manipulation |
|
|
||||||
| Toast notifications | Dynamic element creation |
|
|
||||||
| Clipboard operations | `navigator.clipboard` API |
|
|
||||||
| Keyboard shortcuts | `keydown` event handling |
|
|
||||||
| Complex animations | GSAP or custom |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 LOCAL ASSETS ONLY - NO CDN
|
|
||||||
|
|
||||||
```
|
|
||||||
ui/suite/js/vendor/
|
|
||||||
├── htmx.min.js
|
|
||||||
├── htmx-ws.js
|
|
||||||
├── marked.min.js
|
|
||||||
├── gsap.min.js
|
|
||||||
└── livekit-client.umd.min.js
|
|
||||||
```
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- ✅ CORRECT -->
|
|
||||||
<script src="js/vendor/htmx.min.js"></script>
|
|
||||||
|
|
||||||
<!-- ❌ WRONG -->
|
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 OFFICIAL ICONS - MANDATORY
|
|
||||||
|
|
||||||
**NEVER generate icons with LLM. Use official SVG icons:**
|
|
||||||
|
|
||||||
```
|
|
||||||
ui/suite/assets/icons/
|
|
||||||
├── gb-logo.svg # Main GB logo
|
|
||||||
├── gb-bot.svg # Bot/assistant
|
|
||||||
├── gb-analytics.svg # Analytics
|
|
||||||
├── gb-calendar.svg # Calendar
|
|
||||||
├── gb-chat.svg # Chat
|
|
||||||
├── gb-drive.svg # File storage
|
|
||||||
├── gb-mail.svg # Email
|
|
||||||
├── gb-meet.svg # Video meetings
|
|
||||||
├── gb-tasks.svg # Task management
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
All icons use `stroke="currentColor"` for CSS theming.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 SECURITY ARCHITECTURE
|
|
||||||
|
|
||||||
### Centralized Auth Engine
|
|
||||||
|
|
||||||
All authentication is handled by `security-bootstrap.js` which MUST be loaded immediately after HTMX:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<head>
|
|
||||||
<!-- 1. HTMX first -->
|
|
||||||
<script src="js/vendor/htmx.min.js"></script>
|
|
||||||
<script src="js/vendor/htmx-ws.js"></script>
|
|
||||||
|
|
||||||
<!-- 2. Security bootstrap immediately after -->
|
|
||||||
<script src="js/security-bootstrap.js"></script>
|
|
||||||
|
|
||||||
<!-- 3. Other scripts -->
|
|
||||||
<script src="js/api-client.js"></script>
|
|
||||||
</head>
|
|
||||||
```
|
|
||||||
|
|
||||||
### DO NOT Duplicate Auth Logic
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ❌ WRONG - Don't add auth headers manually
|
|
||||||
fetch("/api/data", {
|
|
||||||
headers: { "Authorization": "Bearer " + token }
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ CORRECT - Let security-bootstrap.js handle it
|
|
||||||
fetch("/api/data");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 DESIGN SYSTEM
|
|
||||||
|
|
||||||
### Layout Standards
|
|
||||||
|
|
||||||
```css
|
|
||||||
.app-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 320px 1fr;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-panel {
|
|
||||||
overflow-y: scroll;
|
|
||||||
scrollbar-width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Theme Variables Required
|
|
||||||
|
|
||||||
```css
|
|
||||||
[data-theme="your-theme"] {
|
|
||||||
--bg: #0a0a0a;
|
|
||||||
--surface: #161616;
|
|
||||||
--surface-hover: #1e1e1e;
|
|
||||||
--border: #2a2a2a;
|
|
||||||
--text: #ffffff;
|
|
||||||
--text-secondary: #888888;
|
|
||||||
--primary: #c5f82a;
|
|
||||||
--success: #22c55e;
|
|
||||||
--warning: #f59e0b;
|
|
||||||
--error: #ef4444;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ CODE PATTERNS
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ❌ WRONG
|
|
||||||
let value = something.unwrap();
|
|
||||||
|
|
||||||
// ✅ CORRECT
|
|
||||||
let value = something?;
|
|
||||||
let value = something.ok_or_else(|| Error::NotFound)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Self Usage
|
|
||||||
|
|
||||||
```rust
|
|
||||||
impl MyStruct {
|
|
||||||
fn new() -> Self { Self { } } // ✅ Not MyStruct
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format Strings
|
|
||||||
|
|
||||||
```rust
|
|
||||||
format!("Hello {name}") // ✅ Not format!("{}", name)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Derive Eq with PartialEq
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(PartialEq, Eq)] // ✅ Always both
|
|
||||||
struct MyStruct { }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 KEY DEPENDENCIES
|
|
||||||
|
|
||||||
| Library | Version | Purpose |
|
|
||||||
|---------|---------|---------|
|
|
||||||
| axum | 0.7.5 | Web framework |
|
|
||||||
| reqwest | 0.12 | HTTP client |
|
|
||||||
| tokio | 1.41 | Async runtime |
|
|
||||||
| askama | 0.12 | HTML Templates |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔑 REMEMBER
|
|
||||||
|
|
||||||
- **ZERO WARNINGS** - Every clippy warning must be fixed
|
|
||||||
- **NO ALLOW IN CODE** - Never use #[allow()] in source files
|
|
||||||
- **NO DEAD CODE** - Delete unused code
|
|
||||||
- **NO UNWRAP/EXPECT** - Use ? operator
|
|
||||||
- **HTMX first** - Minimize JS, delegate to server
|
|
||||||
- **Local assets** - No CDN, all vendor files local
|
|
||||||
- **No business logic** - All logic in botserver
|
|
||||||
- **HTML responses** - Server returns fragments, not JSON
|
|
||||||
- **Version 6.2.0** - do not change without approval
|
|
||||||
317
README.md
317
README.md
|
|
@ -1,39 +1,314 @@
|
||||||
|
# BotUI - General Bots Web Interface
|
||||||
|
|
||||||
# General Bots Desktop
|
**Version:** 6.2.0
|
||||||
|
**Purpose:** Web UI server for General Bots (Axum + HTMX + CSS)
|
||||||
|
|
||||||
An AI-powered desktop automation tool that records and plays back user interactions useful for legacy systems and common desktop tasks. The BotDesktop automation tool fills a critical gap in the enterprise automation landscape by addressing legacy systems and desktop applications that lack modern APIs or integration capabilities. While botserver excels at creating conversational bots for modern channels like web, mobile and messaging platforms, many organizations still rely heavily on traditional desktop applications, mainframe systems, and custom internal tools that can only be accessed through their user interface. BotDesktop's ability to record and replay user interactions provides a practical bridge between these legacy systems and modern automation needs.
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||

|
BotUI is a modern web interface for General Bots, built with Rust, Axum, and HTMX. It provides a clean, responsive interface for interacting with the General Bots platform, featuring real-time updates via WebSocket connections and a minimalist JavaScript approach powered by HTMX.
|
||||||
|
|
||||||
|
The interface supports multiple features including chat, file management, tasks, calendar, analytics, and more - all served through a fast, efficient Rust backend with a focus on server-rendered HTML and minimal client-side JavaScript.
|
||||||
|
|
||||||
The tool's AI-powered approach to desktop automation represents a significant advancement over traditional robotic process automation (RPA) tools. By leveraging machine learning to understand screen elements and user interactions, BotDesktop can adapt to minor UI changes and variations that would break conventional scripted automation. This resilience is particularly valuable in enterprise environments where applications receive regular updates or where slight variations exist between different versions or installations of the same software. The AI component also simplifies the creation of automation scripts - instead of requiring complex programming, users can simply demonstrate the desired actions which BotDesktop observes and learns to replicate.
|
For comprehensive documentation, see **[docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)** or the **[BotBook](./botbook)** for detailed guides and API references.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
From an integration perspective, BotDesktop complements botserver by enabling end-to-end automation scenarios that span both modern and legacy systems. For example, a bot created in botserver could collect information from users through a modern chat interface, then use BotDesktop to input that data into a legacy desktop application that lacks API access. This hybrid approach allows organizations to modernize their user interactions while still leveraging their existing IT investments. Additionally, BotDesktop can automate routine desktop tasks like file management, data entry, and application monitoring that fall outside the scope of conversational bot interactions.
|
## Quick Start
|
||||||
|
|
||||||
|
|
||||||
The combined toolset of botserver and BotDesktop provides organizations with comprehensive automation capabilities across their entire technology stack. While botserver handles the modern, API-driven interactions with users across multiple channels, BotDesktop extends automation capabilities to the desktop environment where many critical business processes still reside. This dual approach allows organizations to progressively modernize their systems while maintaining operational efficiency through automation of both new and legacy components. The result is a more flexible and complete automation solution that can adapt to various technical environments and business needs.
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. Install dependencies:
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
# Development mode - starts Axum server on port 3000
|
||||||
|
cargo run
|
||||||
|
|
||||||
|
# Desktop mode (Tauri) - starts native window
|
||||||
|
cargo tauri dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Create a .env file with your Azure OpenAI credentials
|
### Environment Variables
|
||||||
|
|
||||||
3. Development:
|
- `BOTUI_PORT` - Server port (default: 3000)
|
||||||
```bash
|
|
||||||
npm run dev
|
---
|
||||||
|
|
||||||
|
## ZERO TOLERANCE POLICY
|
||||||
|
|
||||||
|
**EVERY SINGLE WARNING MUST BE FIXED. NO EXCEPTIONS.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ ABSOLUTE PROHIBITIONS
|
||||||
|
|
||||||
|
```
|
||||||
|
❌ NEVER use #![allow()] or #[allow()] in source code
|
||||||
|
❌ NEVER use _ prefix for unused variables - DELETE or USE them
|
||||||
|
❌ NEVER use .unwrap() - use ? or proper error handling
|
||||||
|
❌ NEVER use .expect() - use ? or proper error handling
|
||||||
|
❌ NEVER use panic!() or unreachable!()
|
||||||
|
❌ NEVER use todo!() or unimplemented!()
|
||||||
|
❌ NEVER leave unused imports or dead code
|
||||||
|
❌ NEVER add comments - code must be self-documenting
|
||||||
|
❌ NEVER use CDN links - all assets must be local
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Build:
|
---
|
||||||
```bash
|
|
||||||
npm run build
|
## 🏗️ ARCHITECTURE
|
||||||
|
|
||||||
|
### Dual Modes
|
||||||
|
|
||||||
|
| Mode | Command | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| Web | `cargo run` | Axum server on port 3000 |
|
||||||
|
| Desktop | `cargo tauri dev` | Tauri native window |
|
||||||
|
|
||||||
|
### Code Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.rs # Entry point - mode detection
|
||||||
|
├── lib.rs # Feature-gated module exports
|
||||||
|
├── http_client.rs # HTTP wrapper for botserver
|
||||||
|
├── ui_server/
|
||||||
|
│ └── mod.rs # Axum router + UI serving
|
||||||
|
├── desktop/
|
||||||
|
│ ├── mod.rs # Desktop module organization
|
||||||
|
│ ├── drive.rs # File operations via Tauri
|
||||||
|
│ └── tray.rs # System tray
|
||||||
|
└── shared/
|
||||||
|
└── state.rs # Shared application state
|
||||||
|
|
||||||
|
ui/
|
||||||
|
├── suite/ # Main UI (HTML/CSS/JS)
|
||||||
|
│ ├── js/vendor/ # Local JS libraries
|
||||||
|
│ └── css/ # Stylesheets
|
||||||
|
└── minimal/ # Minimal chat UI
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
---
|
||||||
```bash
|
|
||||||
npm test
|
## 🎨 HTMX-FIRST FRONTEND
|
||||||
|
|
||||||
|
### Core Principle
|
||||||
|
- **Use HTMX** to minimize JavaScript
|
||||||
|
- **Server returns HTML fragments**, not JSON
|
||||||
|
- **Delegate ALL logic** to Rust server
|
||||||
|
|
||||||
|
### HTMX Usage
|
||||||
|
|
||||||
|
| Use Case | Solution |
|
||||||
|
|----------|----------|
|
||||||
|
| Data fetching | `hx-get`, `hx-post` |
|
||||||
|
| Form submission | `hx-post`, `hx-put` |
|
||||||
|
| Real-time updates | `hx-ext="ws"` |
|
||||||
|
| Content swapping | `hx-target`, `hx-swap` |
|
||||||
|
| Polling | `hx-trigger="every 5s"` |
|
||||||
|
| Loading states | `hx-indicator` |
|
||||||
|
|
||||||
|
### When JS is Required
|
||||||
|
|
||||||
|
| Use Case | Why JS Required |
|
||||||
|
|----------|-----------------|
|
||||||
|
| Modal show/hide | DOM manipulation |
|
||||||
|
| Toast notifications | Dynamic element creation |
|
||||||
|
| Clipboard operations | `navigator.clipboard` API |
|
||||||
|
| Keyboard shortcuts | `keydown` event handling |
|
||||||
|
| Complex animations | GSAP or custom |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 LOCAL ASSETS ONLY - NO CDN
|
||||||
|
|
||||||
```
|
```
|
||||||
|
ui/suite/js/vendor/
|
||||||
|
├── htmx.min.js
|
||||||
|
├── htmx-ws.js
|
||||||
|
├── marked.min.js
|
||||||
|
├── gsap.min.js
|
||||||
|
└── livekit-client.umd.min.js
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- ✅ CORRECT -->
|
||||||
|
<script src="js/vendor/htmx.min.js"></script>
|
||||||
|
|
||||||
|
<!-- ❌ WRONG -->
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 OFFICIAL ICONS - MANDATORY
|
||||||
|
|
||||||
|
**NEVER generate icons with LLM. Use official SVG icons:**
|
||||||
|
|
||||||
|
```
|
||||||
|
ui/suite/assets/icons/
|
||||||
|
├── gb-logo.svg # Main GB logo
|
||||||
|
├── gb-bot.svg # Bot/assistant
|
||||||
|
├── gb-analytics.svg # Analytics
|
||||||
|
├── gb-calendar.svg # Calendar
|
||||||
|
├── gb-chat.svg # Chat
|
||||||
|
├── gb-drive.svg # File storage
|
||||||
|
├── gb-mail.svg # Email
|
||||||
|
├── gb-meet.svg # Video meetings
|
||||||
|
├── gb-tasks.svg # Task management
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
All icons use `stroke="currentColor"` for CSS theming.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 SECURITY ARCHITECTURE
|
||||||
|
|
||||||
|
### Centralized Auth Engine
|
||||||
|
|
||||||
|
All authentication is handled by `security-bootstrap.js` which MUST be loaded immediately after HTMX:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<head>
|
||||||
|
<!-- 1. HTMX first -->
|
||||||
|
<script src="js/vendor/htmx.min.js"></script>
|
||||||
|
<script src="js/vendor/htmx-ws.js"></script>
|
||||||
|
|
||||||
|
<!-- 2. Security bootstrap immediately after -->
|
||||||
|
<script src="js/security-bootstrap.js"></script>
|
||||||
|
|
||||||
|
<!-- 3. Other scripts -->
|
||||||
|
<script src="js/api-client.js"></script>
|
||||||
|
</head>
|
||||||
|
```
|
||||||
|
|
||||||
|
### DO NOT Duplicate Auth Logic
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ❌ WRONG - Don't add auth headers manually
|
||||||
|
fetch("/api/data", {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ CORRECT - Let security-bootstrap.js handle it
|
||||||
|
fetch("/api/data");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 DESIGN SYSTEM
|
||||||
|
|
||||||
|
### Layout Standards
|
||||||
|
|
||||||
|
```css
|
||||||
|
.app-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px 1fr;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-panel {
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Variables Required
|
||||||
|
|
||||||
|
```css
|
||||||
|
[data-theme="your-theme"] {
|
||||||
|
--bg: #0a0a0a;
|
||||||
|
--surface: #161616;
|
||||||
|
--surface-hover: #1e1e1e;
|
||||||
|
--border: #2a2a2a;
|
||||||
|
--text: #ffffff;
|
||||||
|
--text-secondary: #888888;
|
||||||
|
--primary: #c5f82a;
|
||||||
|
--success: #22c55e;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--error: #ef4444;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ CODE PATTERNS
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// ❌ WRONG
|
||||||
|
let value = something.unwrap();
|
||||||
|
|
||||||
|
// ✅ CORRECT
|
||||||
|
let value = something?;
|
||||||
|
let value = something.ok_or_else(|| Error::NotFound)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Self Usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl MyStruct {
|
||||||
|
fn new() -> Self { Self { } } // ✅ Not MyStruct
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format Strings
|
||||||
|
|
||||||
|
```rust
|
||||||
|
format!("Hello {name}") // ✅ Not format!("{}", name)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Derive Eq with PartialEq
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(PartialEq, Eq)] // ✅ Always both
|
||||||
|
struct MyStruct { }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 KEY DEPENDENCIES
|
||||||
|
|
||||||
|
| Library | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| axum | 0.7.5 | Web framework |
|
||||||
|
| reqwest | 0.12 | HTTP client |
|
||||||
|
| tokio | 1.41 | Async runtime |
|
||||||
|
| askama | 0.12 | HTML Templates |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
For complete documentation, guides, and API references:
|
||||||
|
|
||||||
|
- **[docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)** - Full online documentation
|
||||||
|
- **[BotBook](./botbook)** - Local comprehensive guide
|
||||||
|
- **[General Bots Repository](https://github.com/GeneralBots/BotServer)** - Main project repository
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 REMEMBER
|
||||||
|
|
||||||
|
- **ZERO WARNINGS** - Every clippy warning must be fixed
|
||||||
|
- **NO ALLOW IN CODE** - Never use #[allow()] in source files
|
||||||
|
- **NO DEAD CODE** - Delete unused code
|
||||||
|
- **NO UNWRAP/EXPECT** - Use ? operator
|
||||||
|
- **HTMX first** - Minimize JS, delegate to server
|
||||||
|
- **Local assets** - No CDN, all vendor files local
|
||||||
|
- **No business logic** - All logic in botserver
|
||||||
|
- **HTML responses** - Server returns fragments, not JSON
|
||||||
|
- **Version 6.2.0** - do not change without approval
|
||||||
|
|
@ -595,6 +595,7 @@ fn create_api_router() -> Router<AppState> {
|
||||||
struct WsQuery {
|
struct WsQuery {
|
||||||
session_id: String,
|
session_id: String,
|
||||||
user_id: String,
|
user_id: String,
|
||||||
|
bot_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
|
|
@ -605,9 +606,28 @@ struct OptionalWsQuery {
|
||||||
async fn ws_proxy(
|
async fn ws_proxy(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
OriginalUri(uri): OriginalUri,
|
||||||
Query(params): Query<WsQuery>,
|
Query(params): Query<WsQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
ws.on_upgrade(move |socket| handle_ws_proxy(socket, state, params))
|
// Extract bot_name from URL path (e.g., /edu, /chat/edu)
|
||||||
|
let path_parts: Vec<&str> = uri.path().split('/').collect();
|
||||||
|
let bot_name = params
|
||||||
|
.bot_name
|
||||||
|
.or_else(|| {
|
||||||
|
// Try to extract from path like /edu or /app/edu
|
||||||
|
path_parts
|
||||||
|
.iter()
|
||||||
|
.find(|part| !part.is_empty() && *part != "chat" && *part != "app")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "default".to_string());
|
||||||
|
|
||||||
|
let params_with_bot = WsQuery {
|
||||||
|
bot_name: Some(bot_name),
|
||||||
|
..params
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.on_upgrade(move |socket| handle_ws_proxy(socket, state, params_with_bot))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ws_task_progress_proxy(
|
async fn ws_task_progress_proxy(
|
||||||
|
|
@ -761,14 +781,15 @@ async fn handle_task_progress_ws_proxy(
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQuery) {
|
async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQuery) {
|
||||||
let backend_url = format!(
|
let backend_url = format!(
|
||||||
"{}/ws?session_id={}&user_id={}",
|
"{}/ws?session_id={}&user_id={}&bot_name={}",
|
||||||
state
|
state
|
||||||
.client
|
.client
|
||||||
.base_url()
|
.base_url()
|
||||||
.replace("https://", "wss://")
|
.replace("https://", "wss://")
|
||||||
.replace("http://", "ws://"),
|
.replace("http://", "ws://"),
|
||||||
params.session_id,
|
params.session_id,
|
||||||
params.user_id
|
params.user_id,
|
||||||
|
params.bot_name.unwrap_or_else(|| "default".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
info!("Proxying WebSocket to: {backend_url}");
|
info!("Proxying WebSocket to: {backend_url}");
|
||||||
|
|
@ -975,11 +996,12 @@ fn add_static_routes(router: Router<AppState>, _suite_path: &Path) -> Router<App
|
||||||
#[cfg(not(feature = "embed-ui"))]
|
#[cfg(not(feature = "embed-ui"))]
|
||||||
{
|
{
|
||||||
let mut r = router;
|
let mut r = router;
|
||||||
|
// Serve suite directories ONLY through /suite/{dir} path
|
||||||
|
// This prevents duplicate routes that cause ServeDir to return index.html for file requests
|
||||||
for dir in SUITE_DIRS {
|
for dir in SUITE_DIRS {
|
||||||
let path = _suite_path.join(dir);
|
let path = _suite_path.join(dir);
|
||||||
r = r
|
info!("Adding route for /suite/{} -> {:?}", dir, path);
|
||||||
.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone()))
|
r = r.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone()));
|
||||||
.nest_service(&format!("/{dir}"), ServeDir::new(path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for file in ROOT_FILES {
|
for file in ROOT_FILES {
|
||||||
|
|
@ -1005,7 +1027,8 @@ pub fn configure_router() -> Router {
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.route("/minimal", get(serve_minimal))
|
.route("/minimal", get(serve_minimal))
|
||||||
.route("/suite", get(serve_suite))
|
.route("/suite", get(serve_suite))
|
||||||
.route("/favicon.ico", get(serve_favicon));
|
.route("/favicon.ico", get(serve_favicon))
|
||||||
|
.nest_service("/auth", ServeDir::new(suite_path.join("auth")));
|
||||||
|
|
||||||
router = add_static_routes(router, &suite_path);
|
router = add_static_routes(router, &suite_path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Login - General Bots</title>
|
<title>Login - General Bots</title>
|
||||||
<script src="/js/vendor/htmx.min.js"></script>
|
<script src="/suite/js/vendor/htmx.min.js"></script>
|
||||||
<script src="/js/vendor/htmx-json-enc.js"></script>
|
<script src="/suite/js/vendor/htmx-json-enc.js"></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--primary: #3b82f6;
|
--primary: #3b82f6;
|
||||||
|
|
|
||||||
|
|
@ -110,97 +110,97 @@
|
||||||
|
|
||||||
/* Messages Area */
|
/* Messages Area */
|
||||||
#messages {
|
#messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--accent, #3b82f6) var(--surface, #1a1a24);
|
scrollbar-color: var(--accent, #3b82f6) var(--surface, #1a1a24);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom scrollbar for markers */
|
/* Custom scrollbar for markers */
|
||||||
#messages::-webkit-scrollbar {
|
#messages::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages::-webkit-scrollbar-track {
|
#messages::-webkit-scrollbar-track {
|
||||||
background: var(--surface, #1a1a24);
|
background: var(--surface, #1a1a24);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages::-webkit-scrollbar-thumb {
|
#messages::-webkit-scrollbar-thumb {
|
||||||
background: var(--accent, #3b82f6);
|
background: var(--accent, #3b82f6);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid var(--surface, #1a1a24);
|
border: 1px solid var(--surface, #1a1a24);
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages::-webkit-scrollbar-thumb:hover {
|
#messages::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--accent-hover, #2563eb);
|
background: var(--accent-hover, #2563eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar markers container */
|
/* Scrollbar markers container */
|
||||||
.scrollbar-markers {
|
.scrollbar-markers {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-marker {
|
.scrollbar-marker {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: var(--accent, #3b82f6);
|
background: var(--accent, #3b82f6);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-marker:hover {
|
.scrollbar-marker:hover {
|
||||||
transform: scale(1.5);
|
transform: scale(1.5);
|
||||||
background: var(--accent-hover, #2563eb);
|
background: var(--accent-hover, #2563eb);
|
||||||
box-shadow: 0 0 8px var(--accent, #3b82f6);
|
box-shadow: 0 0 8px var(--accent, #3b82f6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-marker.user-marker {
|
.scrollbar-marker.user-marker {
|
||||||
background: var(--accent, #3b82f6);
|
background: var(--accent, #3b82f6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-marker.bot-marker {
|
.scrollbar-marker.bot-marker {
|
||||||
background: var(--success, #22c55e);
|
background: var(--success, #22c55e);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-marker-tooltip {
|
.scrollbar-marker-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
background: var(--surface, #1a1a24);
|
background: var(--surface, #1a1a24);
|
||||||
border: 1px solid var(--border, #2a2a2a);
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text, #ffffff);
|
color: var(--text, #ffffff);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 12;
|
z-index: 12;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-marker:hover .scrollbar-marker-tooltip {
|
.scrollbar-marker:hover .scrollbar-marker-tooltip {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Message Styles */
|
/* Message Styles */
|
||||||
|
|
@ -399,28 +399,28 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention-results {
|
.mention-results {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 250px;
|
max-height: 250px;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--accent, #3b82f6) transparent;
|
scrollbar-color: var(--accent, #3b82f6) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention-results::-webkit-scrollbar {
|
.mention-results::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention-results::-webkit-scrollbar-track {
|
.mention-results::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention-results::-webkit-scrollbar-thumb {
|
.mention-results::-webkit-scrollbar-thumb {
|
||||||
background: var(--accent, #3b82f6);
|
background: var(--accent, #3b82f6);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention-results::-webkit-scrollbar-thumb:hover {
|
.mention-results::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--accent-hover, #2563eb);
|
background: var(--accent-hover, #2563eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention-item {
|
.mention-item {
|
||||||
|
|
@ -574,6 +574,13 @@ background: var(--accent-hover, #2563eb);
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-card-btm {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.entity-card-btn {
|
.entity-card-btn {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
||||||
1894
ui/suite/index.html
1894
ui/suite/index.html
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue