From e68a12176de58e24d1f054f8fbe9efefd6a9c24f Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sun, 30 Nov 2025 21:00:48 -0300 Subject: [PATCH] Add Suite app documentation, templates, and Askama config - Add askama.toml for template configuration (ui/ directory) - Add Suite app documentation with flow diagrams (SVG) - App launcher, chat flow, drive flow, tasks flow - Individual app docs: chat, drive, tasks, mail, etc. - Add HTML templates for Suite apps - Base template with header and app launcher - Auth login page - Chat, Drive, Mail, Meet, Tasks templates - Partial templates for messages, sessions, notifications - Add Extensions type to AppState for type-erased storage - Add mTLS module for service-to-service authentication - Update web handlers to use new template paths (suite/) - Fix auth module to avoid axum-extra TypedHeader dependency --- askama.toml | 9 + docs/src/SUMMARY.md | 13 + docs/src/assets/suite/app-launcher.svg | 214 ++++ docs/src/assets/suite/chat-flow.svg | 239 +++++ docs/src/assets/suite/drive-flow.svg | 269 +++++ docs/src/assets/suite/tasks-flow.svg | 280 +++++ docs/src/chapter-04-gbui/apps/README.md | 165 +++ docs/src/chapter-04-gbui/apps/analytics.md | 1 + docs/src/chapter-04-gbui/apps/calendar.md | 1 + docs/src/chapter-04-gbui/apps/chat.md | 384 +++++++ docs/src/chapter-04-gbui/apps/compliance.md | 1 + docs/src/chapter-04-gbui/apps/designer.md | 1 + docs/src/chapter-04-gbui/apps/drive.md | 636 +++++++++++ docs/src/chapter-04-gbui/apps/mail.md | 1 + docs/src/chapter-04-gbui/apps/meet.md | 1 + docs/src/chapter-04-gbui/apps/paper.md | 1 + docs/src/chapter-04-gbui/apps/research.md | 1 + docs/src/chapter-04-gbui/apps/sources.md | 1 + docs/src/chapter-04-gbui/apps/tasks.md | 610 +++++++++++ src/core/shared/state.rs | 67 ++ src/main.rs | 1 + src/security/mutual_tls.rs | 401 +++++++ src/web/auth.rs | 54 +- src/web/auth_handlers.rs | 2 +- src/web/chat_handlers.rs | 50 +- src/web/mod.rs | 18 +- ui/suite/auth/login.html | 351 ++++++ ui/suite/base.html | 502 +++++++++ ui/suite/chat.html | 607 +++++++++++ ui/suite/drive.html | 600 +++++++++++ ui/suite/home.html | 372 +++++++ ui/suite/mail.html | 512 +++++++++ ui/suite/meet.html | 1071 +++++++++++++++++++ ui/suite/partials/apps_menu.html | 101 ++ ui/suite/partials/contexts.html | 117 ++ ui/suite/partials/message.html | 16 + ui/suite/partials/messages.html | 25 + ui/suite/partials/notification.html | 47 + ui/suite/partials/sessions.html | 25 + ui/suite/partials/suggestions.html | 17 + ui/suite/partials/user_menu.html | 166 +++ ui/suite/tasks.html | 608 +++++++++++ 42 files changed, 8507 insertions(+), 51 deletions(-) create mode 100644 askama.toml create mode 100644 docs/src/assets/suite/app-launcher.svg create mode 100644 docs/src/assets/suite/chat-flow.svg create mode 100644 docs/src/assets/suite/drive-flow.svg create mode 100644 docs/src/assets/suite/tasks-flow.svg create mode 100644 docs/src/chapter-04-gbui/apps/README.md create mode 100644 docs/src/chapter-04-gbui/apps/analytics.md create mode 100644 docs/src/chapter-04-gbui/apps/calendar.md create mode 100644 docs/src/chapter-04-gbui/apps/chat.md create mode 100644 docs/src/chapter-04-gbui/apps/compliance.md create mode 100644 docs/src/chapter-04-gbui/apps/designer.md create mode 100644 docs/src/chapter-04-gbui/apps/drive.md create mode 100644 docs/src/chapter-04-gbui/apps/mail.md create mode 100644 docs/src/chapter-04-gbui/apps/meet.md create mode 100644 docs/src/chapter-04-gbui/apps/paper.md create mode 100644 docs/src/chapter-04-gbui/apps/research.md create mode 100644 docs/src/chapter-04-gbui/apps/sources.md create mode 100644 docs/src/chapter-04-gbui/apps/tasks.md create mode 100644 src/security/mutual_tls.rs create mode 100644 ui/suite/auth/login.html create mode 100644 ui/suite/base.html create mode 100644 ui/suite/chat.html create mode 100644 ui/suite/drive.html create mode 100644 ui/suite/home.html create mode 100644 ui/suite/mail.html create mode 100644 ui/suite/meet.html create mode 100644 ui/suite/partials/apps_menu.html create mode 100644 ui/suite/partials/contexts.html create mode 100644 ui/suite/partials/message.html create mode 100644 ui/suite/partials/messages.html create mode 100644 ui/suite/partials/notification.html create mode 100644 ui/suite/partials/sessions.html create mode 100644 ui/suite/partials/suggestions.html create mode 100644 ui/suite/partials/user_menu.html create mode 100644 ui/suite/tasks.html diff --git a/askama.toml b/askama.toml new file mode 100644 index 000000000..3852f1568 --- /dev/null +++ b/askama.toml @@ -0,0 +1,9 @@ +[general] +# Configure Askama to look for templates in ui/ directory +dirs = ["ui"] + +# Enable syntax highlighting hints for editors +syntax = [{ name = "html", ext = ["html"] }] + +# Escape HTML by default for security +escape = "html" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 3423ac5e5..02517c4a2 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -45,6 +45,19 @@ - [Player - Media Viewer](./chapter-04-gbui/player.md) - [Monitoring Dashboard](./chapter-04-gbui/monitoring.md) - [HTMX Architecture](./chapter-04-gbui/htmx-architecture.md) + - [Suite Applications](./chapter-04-gbui/apps/README.md) + - [Chat - AI Assistant](./chapter-04-gbui/apps/chat.md) + - [Drive - File Management](./chapter-04-gbui/apps/drive.md) + - [Tasks - To-Do Lists](./chapter-04-gbui/apps/tasks.md) + - [Mail - Email Client](./chapter-04-gbui/apps/mail.md) + - [Calendar - Scheduling](./chapter-04-gbui/apps/calendar.md) + - [Meet - Video Calls](./chapter-04-gbui/apps/meet.md) + - [Paper - AI Writing](./chapter-04-gbui/apps/paper.md) + - [Research - AI Search](./chapter-04-gbui/apps/research.md) + - [Analytics - Dashboards](./chapter-04-gbui/apps/analytics.md) + - [Designer - Visual Builder](./chapter-04-gbui/apps/designer.md) + - [Sources - Prompts & Templates](./chapter-04-gbui/apps/sources.md) + - [Compliance - Security Scanner](./chapter-04-gbui/apps/compliance.md) # Part V - Themes and Styling diff --git a/docs/src/assets/suite/app-launcher.svg b/docs/src/assets/suite/app-launcher.svg new file mode 100644 index 000000000..7bdad04a6 --- /dev/null +++ b/docs/src/assets/suite/app-launcher.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + General Bots Suite + Your AI-Powered Productivity Workspace + + + + + + + + + + + + Chat + AI Assistant + + + + + + + + + + Drive + File Storage + + + + + + + + + + + Tasks + To-Do Lists + + + + + + + + + + + Mail + Email Client + + + + + + + + + + + + Calendar + Scheduling + + + + + + + + + + Meet + Video Calls + + + + + + + + + + + + + Paper + AI Writing + + + + + + + + + + Research + AI Search + + + + + + + + + + + Analytics + Reports + + + + + + + Additional Tools + + + + Designer + + + + + Sources + + + + + Compliance + + + + + Settings + + + + diff --git a/docs/src/assets/suite/chat-flow.svg b/docs/src/assets/suite/chat-flow.svg new file mode 100644 index 000000000..745ef3ea5 --- /dev/null +++ b/docs/src/assets/suite/chat-flow.svg @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + Chat - AI Assistant Flow + WebSocket-powered real-time conversation with your AI assistant + + + Input + Transport + Processing + Response + + + + + + + + User Message + Text or Voice Input + + + + + + Voice Input + Speech-to-Text + + + + + + Quick Suggestions + One-click actions + + + + + + WebSocket + Real-time Connection + + + + + + HTMX POST + Fallback Transport + + + + + + AI Processing + LLM + Context + + + + + + Context + History + + + + + + Tools + KB + APIs + + + + + + Bot Response + Streamed to UI + + + + + + Message Display + Markdown Rendered + + + + + + Chat History + Scroll + Load More + + + + + + + + + + + + + + + + + + + + + + + + + + Conversation continues... + + + + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + Type or Speak + Enter your message + + Send + WebSocket transmit + + Process + AI generates response + + Display + See bot reply + + + + + Key Features: + + + Voice input with speech-to-text + + + Real-time WebSocket connection + + + Context-aware AI responses + + + Markdown rendering + + + + + Shortcuts: + Enter = Send | Shift+Enter = New line | ↑ = Edit last | / = Commands + + + + + Endpoints: + /ws (WebSocket) | POST /api/sessions/current/message | GET /api/sessions/current/history | POST /api/voice/start + + + diff --git a/docs/src/assets/suite/drive-flow.svg b/docs/src/assets/suite/drive-flow.svg new file mode 100644 index 000000000..9b6c75898 --- /dev/null +++ b/docs/src/assets/suite/drive-flow.svg @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + Drive - File Management Flow + Cloud storage with drag-and-drop, sharing, and organization + + + Navigation + Actions + Storage + Display + + + + + + + + Sidebar + + + + + My Drive + + + Starred + + + Recent + + + Trash + + + + + + + New + Upload Files | New Folder + + + + + + + Upload + Drag & Drop + + + + Download + Single/Batch + + + + Rename + Edit name + + + + Move / Copy + Organize + + + + Share + Get link + + + + Delete + Move to Trash + + + + + + SeaweedFS + Object Storage + + + + + + REST API + /api/v1/drive/* + + + + + + PostgreSQL + File Metadata + + + + + + File View + + + + + Grid + + + List + + + My Drive / Projects / 2024 + + + + 📁 Docs + + + 📄 Report + + + 🖼 Image + + + Storage Used + + + 4.2 GB of 10 GB + + + + + + + + + + + + + + + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + Navigate + Browse folders + + Action + Upload, move, share + + Store + Save to cloud + + View + Grid or list display + + + + + Key Features: + + + Drag-and-drop upload + + + Context menu actions + + + SeaweedFS object storage + + + Grid/List view toggle + + + Star & label files + + + + + Shortcuts: + Ctrl+U = Upload | Ctrl+N = New Folder | Delete = Trash | Ctrl+C/V = Copy/Paste | Enter = Open + + + + + Endpoints: + GET /api/v1/drive/list | POST /api/v1/drive/upload | DELETE /api/v1/drive/file | PUT /api/v1/drive/move + + + + + HTMX: + hx-get="/api/v1/drive/list" hx-target="#file-list" hx-trigger="load, click" hx-swap="innerHTML" + + + diff --git a/docs/src/assets/suite/tasks-flow.svg b/docs/src/assets/suite/tasks-flow.svg new file mode 100644 index 000000000..12f5a2eca --- /dev/null +++ b/docs/src/assets/suite/tasks-flow.svg @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + Tasks - To-Do Management Flow + Create, organize, and track your tasks with categories and priorities + + + Create + Organize + Track + Complete + + + + + + + + Add Task + + + + + What needs to be done? + + + + Category + + + Due Date + + + + + + Filter Tabs + + + + All + + + Active + + + Done + + + Priority + + + + + + Categories + + + Work + + + Personal + + + Shopping + + + Health + + + Custom + + + + + + Task List + + + + + Review report + + + + Call client + + + + Update docs + + + + + Send email + + + + + + Stats + + + + + 12 + Total + + + 5 + Active + + + 7 + Completed + + + 2 + Overdue + + + + + + + + + + + + + + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + Add Task + Enter description + + Categorize + Set priority & date + + Track + View & filter tasks + + Complete + Check off done + + + + + Priority Levels: + + + High - Must do today + + + Medium - Important + + + Low - Can wait + + + None - No deadline + + + + + Key Features: + + + Click checkbox to complete + + + Filter by status or category + + + Set due dates with calendar + + + Real-time stats dashboard + + + + + Shortcuts: + Enter = Add task | Space = Toggle complete | Delete = Remove task | Tab = Next field + + + + + Endpoints: + GET /api/tasks | POST /api/tasks | PATCH /api/tasks/:id | DELETE /api/tasks/:id + + + + + HTMX: + hx-post="/api/tasks" hx-target="#task-list" hx-swap="afterbegin" hx-on::after-request="this.reset()" + + + diff --git a/docs/src/chapter-04-gbui/apps/README.md b/docs/src/chapter-04-gbui/apps/README.md new file mode 100644 index 000000000..84541d1c8 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/README.md @@ -0,0 +1,165 @@ +# Suite Applications + +> **Individual app documentation for General Bots Suite** + +Each application in the Suite has its own dedicated documentation with: +- Flow diagrams (SVG with light/dark theme support) +- Interface layouts +- HTMX integration patterns +- API endpoints +- CSS classes +- JavaScript handlers +- Keyboard shortcuts + +--- + +## Core Applications + +| App | Description | Documentation | +|-----|-------------|---------------| +| 💬 **Chat** | AI-powered conversation assistant | [chat.md](./chat.md) | +| 📁 **Drive** | Cloud file storage and management | [drive.md](./drive.md) | +| ✓ **Tasks** | To-do lists with priorities | [tasks.md](./tasks.md) | +| ✉ **Mail** | Email client | [mail.md](./mail.md) | +| 📅 **Calendar** | Scheduling and events | [calendar.md](./calendar.md) | +| 🎥 **Meet** | Video conferencing | [meet.md](./meet.md) | + +## Productivity Applications + +| App | Description | Documentation | +|-----|-------------|---------------| +| 📝 **Paper** | AI-assisted document writing | [paper.md](./paper.md) | +| 🔍 **Research** | AI-powered search and discovery | [research.md](./research.md) | +| 📊 **Analytics** | Reports and dashboards | [analytics.md](./analytics.md) | + +## Developer Tools + +| App | Description | Documentation | +|-----|-------------|---------------| +| 🎨 **Designer** | Visual dialog builder (VB6-style) | [designer.md](./designer.md) | +| 📚 **Sources** | Prompts, templates, and models | [sources.md](./sources.md) | +| 🛡️ **Compliance** | Security scanner | [compliance.md](./compliance.md) | + +--- + +## App Launcher + +The Suite features a Google-style app launcher accessible from the header: + +App Launcher + +### Accessing Apps + +1. **Click the grid icon** (⋮⋮⋮) in the top-right corner +2. **Select an app** from the dropdown menu +3. App loads in the main content area + +### Keyboard Shortcuts + +| Shortcut | App | +|----------|-----| +| `Alt+1` | Chat | +| `Alt+2` | Drive | +| `Alt+3` | Tasks | +| `Alt+4` | Mail | +| `Alt+5` | Calendar | +| `Alt+6` | Meet | + +--- + +## Architecture Overview + +All Suite apps follow the same patterns: + +### HTMX Loading + +Apps are loaded lazily when selected: + +```html + + Chat + +``` + +### Component Structure + +Each app is a self-contained HTML fragment: + +``` +app-name/ +├── app-name.html # Main component +├── app-name.css # Styles (optional) +└── app-name.js # JavaScript (optional) +``` + +### API Integration + +Apps communicate with the backend via REST APIs: + +```html +
+ Loading... +
+``` + +### Real-Time Updates + +WebSocket support for live data: + +```html +
+ +
+``` + +--- + +## Creating Custom Apps + +To add a new app to the Suite: + +1. **Create the component** in `ui/suite/your-app/` +2. **Add navigation entry** in `index.html` +3. **Define API endpoints** in your Rust backend +4. **Document the app** in this folder + +### Template + +```html + +
+
+

Your App

+
+ +
+
Loading...
+
+
+ + +``` + +--- + +## See Also + +- [Suite Manual](../suite-manual.md) - Complete user guide +- [HTMX Architecture](../htmx-architecture.md) - Technical details +- [UI Structure](../ui-structure.md) - File organization +- [Chapter 10: REST API](../../chapter-10-api/README.md) - API reference \ No newline at end of file diff --git a/docs/src/chapter-04-gbui/apps/analytics.md b/docs/src/chapter-04-gbui/apps/analytics.md new file mode 100644 index 000000000..2327c7c9a --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/analytics.md @@ -0,0 +1 @@ +# Analytics - Dashboards diff --git a/docs/src/chapter-04-gbui/apps/calendar.md b/docs/src/chapter-04-gbui/apps/calendar.md new file mode 100644 index 000000000..7bf06f5ba --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/calendar.md @@ -0,0 +1 @@ +# Calendar - Scheduling diff --git a/docs/src/chapter-04-gbui/apps/chat.md b/docs/src/chapter-04-gbui/apps/chat.md new file mode 100644 index 000000000..8eeefaed5 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/chat.md @@ -0,0 +1,384 @@ +# Chat - AI Assistant + +> **Your intelligent conversation partner** + +Chat Flow Diagram + +--- + +## Overview + +Chat is the heart of General Bots Suite - your AI-powered assistant that understands context, remembers conversations, and helps you get things done. Built with WebSocket for real-time communication and HTMX for seamless updates. + +## Interface Layout + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Connection Status [●] │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ 🤖 Bot 10:30 AM │ │ +│ │ Hello! How can I help you today? │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ You 10:31 AM│ │ +│ │ What meetings do I have today? │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ 🤖 Bot 10:31 AM │ │ +│ │ You have 2 meetings scheduled: │ │ +│ │ • 2:00 PM - Team Standup (30 min) │ │ +│ │ • 4:00 PM - Project Review (1 hour) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +├─────────────────────────────────────────────────────────────────┤ +│ [📊 Tasks] [📧 Check mail] [📅 Schedule] [❓ Help] │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌────────────────────────────────────────────┐ [🎤] [↑] │ +│ │ Type your message... │ │ +│ └────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Features + +### Real-Time Messaging + +Messages are sent and received instantly via WebSocket connection: + +```html +
+
+
+ +
+ + +
+
+``` + +### Voice Input + +Click the microphone button to speak your message: + +1. Click **🎤** to start recording +2. Speak your message clearly +3. Click again to stop +4. Message converts to text automatically + +```html + +``` + +### Quick Suggestions + +Pre-built action chips for common requests: + +| Chip | Action | +|------|--------| +| 📊 Tasks | Show your task list | +| 📧 Check mail | Display unread emails | +| 📅 Schedule | Today's calendar | +| ❓ Help | Available commands | + +```html +
+
+``` + +### Message History + +- Auto-loads previous messages on page open +- Scroll up to load older messages +- Click "Scroll to bottom" button to return to latest + +### Markdown Support + +Bot responses support full Markdown rendering: + +- **Bold** and *italic* text +- `code snippets` and code blocks +- Bullet and numbered lists +- Links and images +- Tables + +## Keyboard Shortcuts + +| Shortcut | Action | +|----------|--------| +| `Enter` | Send message | +| `Shift+Enter` | New line (without sending) | +| `↑` (Up arrow) | Edit last message | +| `/` | Open command menu | +| `Escape` | Cancel current action | + +## API Endpoints + +### WebSocket Connection + +``` +ws://your-server:8080/ws +``` + +**Message Types:** +- `TEXT (1)` - Regular text messages +- `VOICE (2)` - Voice messages +- `CONTINUE (3)` - Continue interrupted responses +- `CONTEXT (4)` - Context changes +- `SYSTEM (5)` - System messages + +### REST Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/sessions/current/message` | POST | Send a message | +| `/api/sessions/current/history` | GET | Get chat history | +| `/api/voice/start` | POST | Start voice recording | +| `/api/voice/stop` | POST | Stop voice recording | +| `/api/suggestions` | GET | Get suggestion chips | + +## HTMX Integration + +### Message Submission + +```html +
+ + +
+``` + +### History Loading + +```html +
+ +
+``` + +### Connection Status + +```html +
+ +
+``` + +## Example Conversations + +### Getting Information + +``` +You: What's the weather like today? +Bot: Currently in your area: + 🌤 Partly cloudy, 72°F (22°C) + Wind: 8 mph from the west + Humidity: 45% +``` + +### Creating Tasks + +``` +You: Remind me to call John tomorrow at 3pm +Bot: ✅ Task created: + 📋 Call John + 📅 Tomorrow at 3:00 PM + + Would you like me to set a notification? +``` + +### Drafting Emails + +``` +You: Write an email declining the meeting tomorrow +Bot: Here's a draft: + + Subject: Unable to Attend Tomorrow's Meeting + + Hi [Name], + + Thank you for the invitation. Unfortunately, I have + a scheduling conflict and won't be able to attend + tomorrow's meeting. + + Would it be possible to reschedule, or could someone + share the meeting notes with me afterward? + + Best regards, + [Your name] + + [📧 Send] [✏️ Edit] [🗑 Discard] +``` + +## CSS Classes + +```css +.chat-layout { + display: flex; + flex-direction: column; + height: 100%; +} + +#messages { + flex: 1; + overflow-y: auto; + padding: 1rem; +} + +.message { + margin-bottom: 1rem; + padding: 0.75rem 1rem; + border-radius: 12px; + max-width: 80%; +} + +.message.bot { + background: var(--surface); + margin-right: auto; +} + +.message.user { + background: var(--primary); + color: white; + margin-left: auto; +} + +.suggestions-container { + display: flex; + gap: 0.5rem; + padding: 0.5rem 1rem; + overflow-x: auto; +} + +.suggestion-chip { + padding: 0.5rem 1rem; + border-radius: 20px; + background: var(--surface); + cursor: pointer; + white-space: nowrap; +} + +.input-container { + display: flex; + gap: 0.5rem; + padding: 1rem; + border-top: 1px solid var(--border); +} + +.connection-status { + height: 4px; + transition: background 0.3s; +} + +.connection-status.connected { + background: var(--success); +} + +.connection-status.disconnected { + background: var(--error); +} +``` + +## JavaScript Events + +```javascript +// Connection status handling +document.body.addEventListener('htmx:wsOpen', () => { + document.getElementById('connectionStatus') + .classList.replace('disconnected', 'connected'); +}); + +document.body.addEventListener('htmx:wsClose', () => { + document.getElementById('connectionStatus') + .classList.replace('connected', 'disconnected'); +}); + +// Auto-scroll to new messages +document.body.addEventListener('htmx:afterSwap', (e) => { + if (e.detail.target.id === 'messages') { + e.detail.target.scrollTop = e.detail.target.scrollHeight; + } +}); + +// Voice input handling +document.getElementById('voiceBtn').addEventListener('click', async () => { + const recognition = new webkitSpeechRecognition(); + recognition.onresult = (event) => { + document.getElementById('messageInput').value = + event.results[0][0].transcript; + }; + recognition.start(); +}); +``` + +## Accessibility + +- Full keyboard navigation +- Screen reader announcements for new messages +- High contrast mode support +- Adjustable font sizes +- ARIA labels on all interactive elements + +```html +
+
+ + +``` + +## Troubleshooting + +### Messages Not Sending + +1. Check connection status indicator +2. Verify WebSocket is connected +3. Try refreshing the page +4. Check browser console for errors + +### Voice Not Working + +1. Allow microphone permissions in browser +2. Check device microphone settings +3. Try a different browser +4. Ensure HTTPS connection (required for voice) + +### History Not Loading + +1. Check network connection +2. Verify API endpoint is accessible +3. Clear browser cache +4. Check for JavaScript errors + +## See Also + +- [HTMX Architecture](../htmx-architecture.md) - How Chat uses HTMX +- [Suite Manual](../suite-manual.md) - Complete user guide +- [Tasks App](./tasks.md) - Create tasks from chat +- [Mail App](./mail.md) - Email integration \ No newline at end of file diff --git a/docs/src/chapter-04-gbui/apps/compliance.md b/docs/src/chapter-04-gbui/apps/compliance.md new file mode 100644 index 000000000..0e5d9fa8a --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/compliance.md @@ -0,0 +1 @@ +# Compliance - Security Scanner diff --git a/docs/src/chapter-04-gbui/apps/designer.md b/docs/src/chapter-04-gbui/apps/designer.md new file mode 100644 index 000000000..9152c0b66 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/designer.md @@ -0,0 +1 @@ +# Designer - Visual Builder diff --git a/docs/src/chapter-04-gbui/apps/drive.md b/docs/src/chapter-04-gbui/apps/drive.md new file mode 100644 index 000000000..1e3f138f8 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/drive.md @@ -0,0 +1,636 @@ +# Drive - File Management + +> **Your cloud storage workspace** + +Drive Flow Diagram + +--- + +## Overview + +Drive is your personal cloud storage within General Bots Suite. Upload, organize, and share files with a familiar interface inspired by Google Drive. Built with HTMX for smooth interactions and SeaweedFS for reliable object storage. + +## Interface Layout + +``` +┌────────────────┬──────────────────────────────────────────────────┐ +│ │ 🔍 Search files... [⊞] [≡] │ +│ [+ New ▼] ├──────────────────────────────────────────────────┤ +│ │ 📁 My Drive > Projects > 2024 │ +│ ─────────────│ ─────────────────────────────────────────────────│ +│ 🏠 My Drive │ [☐] Name Size Modified │ +│ ⭐ Starred │ ─────────────────────────────────────────────── │ +│ 🕐 Recent │ 📁 Reports - Today │ +│ 🗑 Trash │ 📁 Presentations - Yesterday │ +│ │ 📄 Budget.xlsx 245 KB Mar 15 │ +│ ─────────────│ 📄 Notes.docx 12 KB Mar 14 │ +│ Labels │ 🖼 Logo.png 89 KB Mar 10 │ +│ 🔵 Work │ 📊 Sales.csv 156 KB Mar 8 │ +│ 🟢 Personal │ │ +│ 🟡 Projects │ │ +│ ├──────────────────────────────────────────────────┤ +│ ─────────────│ Storage: ████████░░ 4.2 GB of 10 GB │ +│ 4.2 GB used │ │ +└────────────────┴──────────────────────────────────────────────────┘ +``` + +## Features + +### Upload Files + +**Drag and Drop:** +1. Drag files from your computer +2. Drop anywhere in the file area +3. Upload progress shows automatically + +**Click to Upload:** +1. Click **+ New** button +2. Select **Upload Files** or **Upload Folder** +3. Choose files from file picker + +```html + + +
+ +
+``` + +### File Operations + +| Action | How to Access | HTMX Attribute | +|--------|---------------|----------------| +| **Open** | Double-click | `hx-get="/api/v1/drive/open"` | +| **Download** | Right-click > Download | `hx-get="/api/v1/drive/download"` | +| **Rename** | Right-click > Rename | `hx-patch="/api/v1/drive/rename"` | +| **Copy** | Right-click > Copy | `hx-post="/api/v1/drive/copy"` | +| **Move** | Right-click > Move to | `hx-post="/api/v1/drive/move"` | +| **Star** | Right-click > Star | `hx-post="/api/v1/drive/star"` | +| **Share** | Right-click > Share | `hx-get="/api/v1/drive/share-modal"` | +| **Delete** | Right-click > Delete | `hx-delete="/api/v1/drive/file"` | + +### Context Menu + +```html +
+
+ 📂 Open +
+
+ ⬇️ Download +
+
+
+ ✏️ Rename +
+
+ 📋 Copy +
+
+ 📁 Move to... +
+
+
+ ⭐ Add to Starred +
+
+ 🔗 Share +
+
+
+ 🗑 Delete +
+
+``` + +### View Modes + +**Grid View (⊞):** +- Large thumbnails for images +- Folder icons with previews +- Best for visual browsing + +**List View (≡):** +- Detailed file information +- Sortable columns +- Best for managing many files + +```html +
+ + +
+``` + +### Navigation + +**Sidebar:** +- My Drive - All your files +- Starred - Favorite files +- Recent - Recently accessed +- Trash - Deleted files (30-day retention) + +**Breadcrumb:** +```html + +``` + +### Labels & Organization + +Create colored labels to organize files: + +| Label | Color | Use Case | +|-------|-------|----------| +| 🔵 Work | Blue | Business files | +| 🟢 Personal | Green | Personal documents | +| 🟡 Projects | Yellow | Active projects | +| 🔴 Urgent | Red | Priority items | + +## Keyboard Shortcuts + +| Shortcut | Action | +|----------|--------| +| `Ctrl+U` | Upload files | +| `Ctrl+N` | New folder | +| `Delete` | Move to trash | +| `Ctrl+C` | Copy selected | +| `Ctrl+X` | Cut selected | +| `Ctrl+V` | Paste | +| `Enter` | Open selected | +| `F2` | Rename selected | +| `Ctrl+A` | Select all | +| `Escape` | Deselect all | + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/v1/drive/list` | GET | List files in directory | +| `/api/v1/drive/upload` | POST | Upload files | +| `/api/v1/drive/download` | GET | Download file | +| `/api/v1/drive/file` | DELETE | Delete file | +| `/api/v1/drive/rename` | PATCH | Rename file | +| `/api/v1/drive/move` | POST | Move file | +| `/api/v1/drive/copy` | POST | Copy file | +| `/api/v1/drive/star` | POST | Toggle star | +| `/api/v1/drive/share` | POST | Create share link | +| `/api/v1/drive/folder` | POST | Create folder | + +### Query Parameters + +``` +GET /api/v1/drive/list?path=/Projects&sort=name&order=asc&view=grid +``` + +| Parameter | Values | Default | +|-----------|--------|---------| +| `path` | Directory path | `/` | +| `sort` | `name`, `size`, `modified`, `type` | `name` | +| `order` | `asc`, `desc` | `asc` | +| `view` | `grid`, `list` | `grid` | + +## HTMX Integration + +### File Listing + +```html +
+
+ Loading files... +
+
+``` + +### File Upload with Progress + +```html +
+ + + +
+ +
+
+
+
+ Uploading... +
+``` + +### Folder Navigation + +```html +
+
+ 📁 +
+
Projects
+
+``` + +## CSS Classes + +```css +.drive-container { + display: grid; + grid-template-columns: 250px 1fr; + height: calc(100vh - 64px); +} + +.drive-sidebar { + background: var(--surface); + border-right: 1px solid var(--border); + padding: 1rem; +} + +.drive-main { + display: flex; + flex-direction: column; + overflow: hidden; +} + +.file-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 1rem; + padding: 1rem; +} + +.file-card { + border: 2px solid transparent; + border-radius: 8px; + padding: 1rem; + cursor: pointer; + transition: all 0.2s; +} + +.file-card:hover { + background: var(--surface-hover); + border-color: var(--border); +} + +.file-card.selected { + background: var(--primary-light); + border-color: var(--primary); +} + +.file-card-preview { + height: 100px; + display: flex; + align-items: center; + justify-content: center; + font-size: 3rem; + border-radius: 4px; + background: var(--surface); +} + +.file-card-preview img { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.file-card-name { + margin-top: 0.5rem; + font-size: 0.875rem; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-list { + display: flex; + flex-direction: column; +} + +.file-row { + display: grid; + grid-template-columns: auto 1fr 100px 100px 150px auto; + gap: 1rem; + padding: 0.75rem 1rem; + align-items: center; + border-bottom: 1px solid var(--border); +} + +.file-row:hover { + background: var(--surface-hover); +} + +.upload-zone { + border: 2px dashed var(--border); + border-radius: 8px; + padding: 2rem; + text-align: center; + transition: all 0.2s; +} + +.upload-zone.dragover { + border-color: var(--primary); + background: var(--primary-light); +} + +.breadcrumb { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; +} + +.breadcrumb-item { + cursor: pointer; + color: var(--text-secondary); +} + +.breadcrumb-item:hover { + color: var(--primary); +} + +.breadcrumb-item.current { + color: var(--text-primary); + font-weight: 500; +} + +.storage-bar { + height: 8px; + background: var(--surface); + border-radius: 4px; + overflow: hidden; +} + +.storage-bar-fill { + height: 100%; + background: var(--primary); + border-radius: 4px; + transition: width 0.3s; +} +``` + +## JavaScript Handlers + +```javascript +// Drag and drop handling +function initDragAndDrop() { + const uploadZone = document.querySelector('.upload-zone'); + + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => { + uploadZone.addEventListener(event, preventDefaults); + }); + + ['dragenter', 'dragover'].forEach(event => { + uploadZone.addEventListener(event, () => { + uploadZone.classList.add('dragover'); + }); + }); + + ['dragleave', 'drop'].forEach(event => { + uploadZone.addEventListener(event, () => { + uploadZone.classList.remove('dragover'); + }); + }); + + uploadZone.addEventListener('drop', handleDrop); +} + +function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); +} + +function handleDrop(e) { + const files = e.dataTransfer.files; + uploadFiles(files); +} + +function uploadFiles(files) { + const formData = new FormData(); + const currentPath = document.getElementById('current-path').value; + + formData.append('path', currentPath); + [...files].forEach(file => { + formData.append('files', file); + }); + + htmx.ajax('POST', '/api/v1/drive/upload', { + target: '#file-list', + swap: 'innerHTML', + values: formData + }); +} + +// Context menu +function initContextMenu() { + const contextMenu = document.getElementById('context-menu'); + + document.addEventListener('contextmenu', (e) => { + const fileCard = e.target.closest('.file-card, .file-row'); + if (fileCard) { + e.preventDefault(); + selectFile(fileCard); + contextMenu.style.left = e.clientX + 'px'; + contextMenu.style.top = e.clientY + 'px'; + contextMenu.classList.add('visible'); + } + }); + + document.addEventListener('click', () => { + contextMenu.classList.remove('visible'); + }); +} + +// File selection +let selectedFiles = new Set(); + +function selectFile(element) { + if (!event.ctrlKey && !event.metaKey) { + document.querySelectorAll('.file-card.selected, .file-row.selected') + .forEach(el => el.classList.remove('selected')); + selectedFiles.clear(); + } + + element.classList.toggle('selected'); + const path = element.dataset.path; + + if (element.classList.contains('selected')) { + selectedFiles.add(path); + } else { + selectedFiles.delete(path); + } + + document.querySelector('[name="selected-path"]').value = + [...selectedFiles].join(','); +} + +// View toggle +function setView(view) { + const fileList = document.getElementById('file-list'); + fileList.classList.toggle('file-grid', view === 'grid'); + fileList.classList.toggle('file-list-view', view === 'list'); + + document.querySelectorAll('.view-toggle-btn').forEach(btn => { + btn.classList.toggle('active', btn.dataset.view === view); + }); + + localStorage.setItem('drive-view', view); +} + +// Keyboard shortcuts +document.addEventListener('keydown', (e) => { + if (e.ctrlKey || e.metaKey) { + switch (e.key) { + case 'u': + e.preventDefault(); + document.getElementById('upload-input').click(); + break; + case 'n': + e.preventDefault(); + showModal('new-folder-modal'); + break; + case 'a': + e.preventDefault(); + document.querySelectorAll('.file-card, .file-row') + .forEach(el => el.classList.add('selected')); + break; + } + } + + if (e.key === 'Delete' && selectedFiles.size > 0) { + htmx.ajax('DELETE', '/api/v1/drive/file', { + target: '#file-list', + values: { paths: [...selectedFiles].join(',') } + }); + } + + if (e.key === 'Enter' && selectedFiles.size === 1) { + const path = [...selectedFiles][0]; + htmx.ajax('GET', '/api/v1/drive/open', { + values: { path } + }); + } +}); +``` + +## File Type Icons + +| Extension | Icon | Category | +|-----------|------|----------| +| `.pdf` | 📕 | Document | +| `.doc`, `.docx` | 📄 | Document | +| `.xls`, `.xlsx` | 📊 | Spreadsheet | +| `.ppt`, `.pptx` | 📽 | Presentation | +| `.jpg`, `.png`, `.gif` | 🖼 | Image | +| `.mp4`, `.mov` | 🎬 | Video | +| `.mp3`, `.wav` | 🎵 | Audio | +| `.zip`, `.rar` | 📦 | Archive | +| `.txt`, `.md` | 📝 | Text | +| Folder | 📁 | Directory | + +## Storage Backend + +Drive uses **SeaweedFS** for object storage: + +- Distributed file system +- Automatic replication +- High availability +- Efficient for large files +- S3-compatible API + +File metadata is stored in **PostgreSQL**: +- File names and paths +- Permissions and sharing +- Labels and stars +- Version history + +## Troubleshooting + +### Upload Fails + +1. Check file size limit (default: 100MB) +2. Verify storage quota +3. Check network connection +4. Look for file type restrictions + +### Files Not Displaying + +1. Refresh the page +2. Check current path is valid +3. Verify permissions +4. Clear browser cache + +### Context Menu Not Working + +1. Enable JavaScript +2. Check for console errors +3. Try right-clicking on file directly +4. Refresh the page + +## See Also + +- [HTMX Architecture](../htmx-architecture.md) - How Drive uses HTMX +- [Suite Manual](../suite-manual.md) - Complete user guide +- [Chat App](./chat.md) - Share files in chat +- [Storage API](../../chapter-10-api/storage-api.md) - API reference \ No newline at end of file diff --git a/docs/src/chapter-04-gbui/apps/mail.md b/docs/src/chapter-04-gbui/apps/mail.md new file mode 100644 index 000000000..3ed8353ee --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/mail.md @@ -0,0 +1 @@ +# Mail - Email Client diff --git a/docs/src/chapter-04-gbui/apps/meet.md b/docs/src/chapter-04-gbui/apps/meet.md new file mode 100644 index 000000000..4fe5641f2 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/meet.md @@ -0,0 +1 @@ +# Meet - Video Calls diff --git a/docs/src/chapter-04-gbui/apps/paper.md b/docs/src/chapter-04-gbui/apps/paper.md new file mode 100644 index 000000000..55defd34d --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/paper.md @@ -0,0 +1 @@ +# Paper - AI Writing diff --git a/docs/src/chapter-04-gbui/apps/research.md b/docs/src/chapter-04-gbui/apps/research.md new file mode 100644 index 000000000..e1393c9c9 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/research.md @@ -0,0 +1 @@ +# Research - AI Search diff --git a/docs/src/chapter-04-gbui/apps/sources.md b/docs/src/chapter-04-gbui/apps/sources.md new file mode 100644 index 000000000..a61479aa3 --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/sources.md @@ -0,0 +1 @@ +# Sources - Prompts & Templates diff --git a/docs/src/chapter-04-gbui/apps/tasks.md b/docs/src/chapter-04-gbui/apps/tasks.md new file mode 100644 index 000000000..dc23d8afc --- /dev/null +++ b/docs/src/chapter-04-gbui/apps/tasks.md @@ -0,0 +1,610 @@ +# Tasks - To-Do Management + +> **Track what needs to be done** + +Tasks Flow Diagram + +--- + +## Overview + +Tasks is your to-do list manager within General Bots Suite. Create tasks, set priorities, organize by category, and track your progress. Built with HTMX for instant updates without page reloads. + +## Interface Layout + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ✓ Tasks Total: 12 Active: 5 Done: 7│ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ What needs to be done? [Category ▼] [📅] [+ Add]│ │ +│ └─────────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ [📋 All (12)] [⏳ Active (5)] [✓ Completed (7)] [⚡ Priority] │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ☐ Review quarterly report 📅 Today 🔴 │ +│ ☐ Call client about proposal 📅 Today 🟡 │ +│ ☐ Update project documentation 📅 Tomorrow 🟢 │ +│ ☐ Schedule team meeting 📅 Mar 20 ⚪ │ +│ ────────────────────────────────────────────────────────────── │ +│ ☑ Send meeting notes ✓ Done │ +│ ☑ Complete expense report ✓ Done │ +│ ☑ Review pull request ✓ Done │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Features + +### Adding Tasks + +**Quick Add:** +1. Type task description in the input box +2. Press **Enter** or click **+ Add** + +**With Details:** +1. Type task description +2. Select a category (optional) +3. Pick a due date (optional) +4. Click **+ Add** + +```html +
+ + + + +
+``` + +### Completing Tasks + +Click the checkbox to mark a task complete: + +```html +
+ + Review quarterly report + 📅 Today + 🔴 +
+``` + +### Priority Levels + +| Priority | Color | Icon | When to Use | +|----------|-------|------|-------------| +| **High** | Red | 🔴 | Must do today | +| **Medium** | Yellow | 🟡 | Important but not urgent | +| **Low** | Green | 🟢 | Can wait | +| **None** | Gray | ⚪ | No deadline | + +### Categories + +Organize tasks by category: + +| Category | Color | Icon | +|----------|-------|------| +| Work | Blue | 💼 | +| Personal | Green | 🏠 | +| Shopping | Orange | 🛒 | +| Health | Red | ❤️ | +| Custom | Purple | 🏷️ | + +### Filter Tabs + +| Tab | Shows | HTMX Trigger | +|-----|-------|--------------| +| **All** | All tasks | `hx-get="/api/tasks?filter=all"` | +| **Active** | Uncompleted tasks | `hx-get="/api/tasks?filter=active"` | +| **Completed** | Done tasks | `hx-get="/api/tasks?filter=completed"` | +| **Priority** | High priority only | `hx-get="/api/tasks?filter=priority"` | + +```html +
+ + + + +
+``` + +### Stats Dashboard + +Real-time statistics shown in the header: + +```html +
+ + 12 + Total + + + 5 + Active + + + 7 + Completed + +
+``` + +## Keyboard Shortcuts + +| Shortcut | Action | +|----------|--------| +| `Enter` | Add task (when in input) | +| `Space` | Toggle task completion | +| `Delete` | Remove selected task | +| `Tab` | Move to next field | +| `Escape` | Cancel editing | +| `↑` / `↓` | Navigate tasks | + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/tasks` | GET | List all tasks | +| `/api/tasks` | POST | Create new task | +| `/api/tasks/:id` | GET | Get single task | +| `/api/tasks/:id` | PATCH | Update task | +| `/api/tasks/:id` | DELETE | Delete task | +| `/api/tasks/stats` | GET | Get task statistics | + +### Query Parameters + +``` +GET /api/tasks?filter=active&category=work&sort=dueDate&order=asc +``` + +| Parameter | Values | Default | +|-----------|--------|---------| +| `filter` | `all`, `active`, `completed`, `priority` | `all` | +| `category` | `work`, `personal`, `shopping`, `health` | none | +| `sort` | `created`, `dueDate`, `priority`, `text` | `created` | +| `order` | `asc`, `desc` | `desc` | + +### Request Body (Create/Update) + +```json +{ + "text": "Review quarterly report", + "category": "work", + "dueDate": "2024-03-20", + "priority": "high", + "completed": false +} +``` + +### Response Format + +```json +{ + "id": 123, + "text": "Review quarterly report", + "category": "work", + "dueDate": "2024-03-20", + "priority": "high", + "completed": false, + "createdAt": "2024-03-18T10:30:00Z", + "updatedAt": "2024-03-18T10:30:00Z" +} +``` + +## HTMX Integration + +### Task Creation + +```html +
+ + +
+``` + +### Task Toggle + +```html + +``` + +### Task Deletion + +```html + +``` + +### Inline Editing + +```html + + Review quarterly report + +``` + +## CSS Classes + +```css +.tasks-container { + display: flex; + flex-direction: column; + height: 100%; + padding: 1rem; +} + +.tasks-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.header-stats { + display: flex; + gap: 1.5rem; +} + +.stat-item { + text-align: center; +} + +.stat-value { + display: block; + font-size: 1.5rem; + font-weight: 600; + color: var(--primary); +} + +.stat-label { + font-size: 0.75rem; + color: var(--text-secondary); +} + +.add-task-form { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.task-input { + flex: 1; + padding: 0.75rem 1rem; + border: 2px solid var(--border); + border-radius: 8px; + font-size: 1rem; +} + +.task-input:focus { + border-color: var(--primary); + outline: none; +} + +.filter-tabs { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; + border-bottom: 1px solid var(--border); + padding-bottom: 0.5rem; +} + +.filter-tab { + padding: 0.5rem 1rem; + border: none; + background: transparent; + cursor: pointer; + border-radius: 6px; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.filter-tab:hover { + background: var(--surface-hover); +} + +.filter-tab.active { + background: var(--primary); + color: white; +} + +.tab-count { + background: rgba(255,255,255,0.2); + padding: 0.125rem 0.5rem; + border-radius: 10px; + font-size: 0.75rem; +} + +.task-list { + flex: 1; + overflow-y: auto; +} + +.task-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + border-bottom: 1px solid var(--border); + transition: all 0.2s; +} + +.task-item:hover { + background: var(--surface-hover); +} + +.task-item.completed { + opacity: 0.6; +} + +.task-item.completed .task-text { + text-decoration: line-through; + color: var(--text-secondary); +} + +.task-checkbox { + width: 20px; + height: 20px; + border: 2px solid var(--border); + border-radius: 4px; + cursor: pointer; + appearance: none; +} + +.task-checkbox:checked { + background: var(--success); + border-color: var(--success); +} + +.task-checkbox:checked::after { + content: '✓'; + display: block; + text-align: center; + color: white; + font-size: 14px; + line-height: 16px; +} + +.task-text { + flex: 1; +} + +.task-due { + font-size: 0.875rem; + color: var(--text-secondary); +} + +.task-due.overdue { + color: var(--error); +} + +.task-priority { + font-size: 0.75rem; +} + +.task-priority.high { + color: var(--error); +} + +.task-priority.medium { + color: var(--warning); +} + +.task-priority.low { + color: var(--success); +} + +.task-delete { + opacity: 0; + padding: 0.25rem; + border: none; + background: transparent; + cursor: pointer; + color: var(--error); +} + +.task-item:hover .task-delete { + opacity: 1; +} + +/* Animation for task completion */ +.task-item.completing { + animation: complete 0.3s ease-out; +} + +@keyframes complete { + 0% { transform: scale(1); } + 50% { transform: scale(1.02); background: var(--success-light); } + 100% { transform: scale(1); } +} +``` + +## JavaScript Handlers + +```javascript +// Tab switching +function setActiveTab(button) { + document.querySelectorAll('.filter-tab').forEach(tab => { + tab.classList.remove('active'); + }); + button.classList.add('active'); +} + +// Task completion animation +document.body.addEventListener('htmx:beforeSwap', (e) => { + if (e.detail.target.classList.contains('task-item')) { + e.detail.target.classList.add('completing'); + } +}); + +// Update counts after any task change +document.body.addEventListener('taskUpdated', () => { + htmx.ajax('GET', '/api/tasks/stats', { + target: '#task-stats', + swap: 'innerHTML' + }); +}); + +// Keyboard navigation +document.addEventListener('keydown', (e) => { + const taskItems = document.querySelectorAll('.task-item'); + const focused = document.activeElement.closest('.task-item'); + + if (e.key === 'ArrowDown' && focused) { + const index = [...taskItems].indexOf(focused); + if (index < taskItems.length - 1) { + taskItems[index + 1].querySelector('input').focus(); + } + } + + if (e.key === 'ArrowUp' && focused) { + const index = [...taskItems].indexOf(focused); + if (index > 0) { + taskItems[index - 1].querySelector('input').focus(); + } + } + + if (e.key === ' ' && focused) { + e.preventDefault(); + focused.querySelector('.task-checkbox').click(); + } +}); + +// Due date formatting +function formatDueDate(dateString) { + const date = new Date(dateString); + const today = new Date(); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + + if (date.toDateString() === today.toDateString()) { + return 'Today'; + } else if (date.toDateString() === tomorrow.toDateString()) { + return 'Tomorrow'; + } else { + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric' + }); + } +} +``` + +## Creating Tasks from Chat + +In the Chat app, you can create tasks naturally: + +``` +You: Create a task to review the budget by Friday +Bot: ✅ Task created: + 📋 Review the budget + 📅 Due: Friday, March 22 + 🏷️ Category: Work + + [View Tasks] +``` + +## Integration with Calendar + +Tasks with due dates appear in your Calendar view: + +```html +
+ 📋 Review quarterly report +
+``` + +## Troubleshooting + +### Tasks Not Saving + +1. Check network connection +2. Verify API endpoint is accessible +3. Check browser console for errors +4. Try refreshing the page + +### Filters Not Working + +1. Click the filter tab again +2. Check if tasks exist for that filter +3. Clear browser cache +4. Verify HTMX is loaded + +### Stats Not Updating + +1. Check for JavaScript errors +2. Verify `taskUpdated` event is firing +3. Inspect network requests +4. Reload the page + +## See Also + +- [HTMX Architecture](../htmx-architecture.md) - How Tasks uses HTMX +- [Suite Manual](../suite-manual.md) - Complete user guide +- [Chat App](./chat.md) - Create tasks from chat +- [Calendar App](./calendar.md) - View tasks in calendar +- [Tasks API](../../chapter-10-api/tasks-api.md) - API reference \ No newline at end of file diff --git a/src/core/shared/state.rs b/src/core/shared/state.rs index af0b68d70..f45a70d42 100644 --- a/src/core/shared/state.rs +++ b/src/core/shared/state.rs @@ -14,10 +14,73 @@ use crate::tasks::{TaskEngine, TaskScheduler}; use aws_sdk_s3::Client as S3Client; #[cfg(feature = "redis-cache")] use redis::Client as RedisClient; +use std::any::{Any, TypeId}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::mpsc; +/// Type-erased extension storage for AppState +#[derive(Default)] +pub struct Extensions { + map: HashMap>, +} + +impl Extensions { + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + /// Insert a value into the extensions + pub fn insert(&mut self, value: T) { + self.map.insert(TypeId::of::(), Box::new(value)); + } + + /// Get a reference to a value from the extensions + pub fn get(&self) -> Option<&T> { + self.map + .get(&TypeId::of::()) + .and_then(|boxed| boxed.downcast_ref::()) + } + + /// Get a mutable reference to a value from the extensions + pub fn get_mut(&mut self) -> Option<&mut T> { + self.map + .get_mut(&TypeId::of::()) + .and_then(|boxed| boxed.downcast_mut::()) + } + + /// Check if a value of type T exists + pub fn contains(&self) -> bool { + self.map.contains_key(&TypeId::of::()) + } + + /// Remove a value from the extensions + pub fn remove(&mut self) -> Option { + self.map + .remove(&TypeId::of::()) + .and_then(|boxed| boxed.downcast::().ok()) + .map(|boxed| *boxed) + } +} + +impl Clone for Extensions { + fn clone(&self) -> Self { + // Extensions cannot be cloned deeply, so we create an empty one + // This is a limitation - extensions should be Arc-wrapped if sharing is needed + Self::new() + } +} + +impl std::fmt::Debug for Extensions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Extensions") + .field("count", &self.map.len()) + .finish() + } +} + pub struct AppState { #[cfg(feature = "drive")] pub drive: Option, @@ -41,6 +104,8 @@ pub struct AppState { pub voice_adapter: Arc, pub kb_manager: Option>, pub task_engine: Arc, + /// Type-erased extension storage for web handlers and other components + pub extensions: Extensions, } impl Clone for AppState { fn clone(&self) -> Self { @@ -67,6 +132,7 @@ impl Clone for AppState { web_adapter: Arc::clone(&self.web_adapter), voice_adapter: Arc::clone(&self.voice_adapter), task_engine: Arc::clone(&self.task_engine), + extensions: self.extensions.clone(), } } } @@ -103,6 +169,7 @@ impl std::fmt::Debug for AppState { .field("response_channels", &"Arc>") .field("web_adapter", &self.web_adapter) .field("voice_adapter", &self.voice_adapter) + .field("extensions", &self.extensions) .finish() } } diff --git a/src/main.rs b/src/main.rs index d34c881d1..5440915f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -649,6 +649,7 @@ async fn main() -> std::io::Result<()> { voice_adapter: voice_adapter.clone(), kb_manager: Some(kb_manager.clone()), task_engine: task_engine, + extensions: botserver::core::shared::state::Extensions::new(), }); // Initialize TaskScheduler with the AppState diff --git a/src/security/mutual_tls.rs b/src/security/mutual_tls.rs new file mode 100644 index 000000000..a8959d9b6 --- /dev/null +++ b/src/security/mutual_tls.rs @@ -0,0 +1,401 @@ +//! Mutual TLS (mTLS) Module +//! +//! This module provides mutual TLS authentication for service-to-service communication. +//! It enables secure connections between BotServer and its dependent services like +//! PostgreSQL, Qdrant, LiveKit, Forgejo, and Directory services. + +use std::path::Path; +use std::sync::Arc; +use tracing::{debug, error, info, warn}; + +/// Services module containing mTLS configuration functions for each service +pub mod services { + use super::*; + + /// Configure mTLS for PostgreSQL connections + /// + /// # Arguments + /// * `ca_cert_path` - Path to the CA certificate + /// * `client_cert_path` - Path to the client certificate + /// * `client_key_path` - Path to the client private key + /// + /// # Returns + /// Result containing the SSL mode string or an error + pub fn configure_postgres_mtls( + ca_cert_path: Option<&Path>, + client_cert_path: Option<&Path>, + client_key_path: Option<&Path>, + ) -> Result { + match (ca_cert_path, client_cert_path, client_key_path) { + (Some(ca), Some(cert), Some(key)) => { + if !ca.exists() { + return Err(MtlsError::CertificateNotFound( + ca.to_string_lossy().to_string(), + )); + } + if !cert.exists() { + return Err(MtlsError::CertificateNotFound( + cert.to_string_lossy().to_string(), + )); + } + if !key.exists() { + return Err(MtlsError::KeyNotFound(key.to_string_lossy().to_string())); + } + + info!("PostgreSQL mTLS configured with client certificates"); + Ok("verify-full".to_string()) + } + (Some(ca), None, None) => { + if !ca.exists() { + return Err(MtlsError::CertificateNotFound( + ca.to_string_lossy().to_string(), + )); + } + info!("PostgreSQL TLS configured with CA verification only"); + Ok("verify-ca".to_string()) + } + _ => { + debug!("PostgreSQL mTLS not configured, using default connection"); + Ok("prefer".to_string()) + } + } + } + + /// Configure mTLS for Qdrant vector database connections + /// + /// # Arguments + /// * `ca_cert_path` - Path to the CA certificate + /// * `client_cert_path` - Path to the client certificate + /// * `client_key_path` - Path to the client private key + /// + /// # Returns + /// Result containing the mTLS configuration or an error + pub fn configure_qdrant_mtls( + ca_cert_path: Option<&Path>, + client_cert_path: Option<&Path>, + client_key_path: Option<&Path>, + ) -> Result { + match (ca_cert_path, client_cert_path, client_key_path) { + (Some(ca), Some(cert), Some(key)) => { + let ca_pem = std::fs::read_to_string(ca).map_err(|e| { + MtlsError::IoError(format!("Failed to read CA cert: {}", e)) + })?; + let cert_pem = std::fs::read_to_string(cert).map_err(|e| { + MtlsError::IoError(format!("Failed to read client cert: {}", e)) + })?; + let key_pem = std::fs::read_to_string(key).map_err(|e| { + MtlsError::IoError(format!("Failed to read client key: {}", e)) + })?; + + info!("Qdrant mTLS configured successfully"); + Ok(MtlsConfig { + enabled: true, + ca_cert: Some(ca_pem), + client_cert: Some(cert_pem), + client_key: Some(key_pem), + }) + } + _ => { + debug!("Qdrant mTLS not configured"); + Ok(MtlsConfig::default()) + } + } + } + + /// Configure mTLS for LiveKit media server connections + /// + /// # Arguments + /// * `ca_cert_path` - Path to the CA certificate + /// * `client_cert_path` - Path to the client certificate + /// * `client_key_path` - Path to the client private key + /// + /// # Returns + /// Result containing the mTLS configuration or an error + pub fn configure_livekit_mtls( + ca_cert_path: Option<&Path>, + client_cert_path: Option<&Path>, + client_key_path: Option<&Path>, + ) -> Result { + match (ca_cert_path, client_cert_path, client_key_path) { + (Some(ca), Some(cert), Some(key)) => { + let ca_pem = std::fs::read_to_string(ca).map_err(|e| { + MtlsError::IoError(format!("Failed to read CA cert: {}", e)) + })?; + let cert_pem = std::fs::read_to_string(cert).map_err(|e| { + MtlsError::IoError(format!("Failed to read client cert: {}", e)) + })?; + let key_pem = std::fs::read_to_string(key).map_err(|e| { + MtlsError::IoError(format!("Failed to read client key: {}", e)) + })?; + + info!("LiveKit mTLS configured successfully"); + Ok(MtlsConfig { + enabled: true, + ca_cert: Some(ca_pem), + client_cert: Some(cert_pem), + client_key: Some(key_pem), + }) + } + _ => { + debug!("LiveKit mTLS not configured"); + Ok(MtlsConfig::default()) + } + } + } + + /// Configure mTLS for Forgejo git server connections + /// + /// # Arguments + /// * `ca_cert_path` - Path to the CA certificate + /// * `client_cert_path` - Path to the client certificate + /// * `client_key_path` - Path to the client private key + /// + /// # Returns + /// Result containing the mTLS configuration or an error + pub fn configure_forgejo_mtls( + ca_cert_path: Option<&Path>, + client_cert_path: Option<&Path>, + client_key_path: Option<&Path>, + ) -> Result { + match (ca_cert_path, client_cert_path, client_key_path) { + (Some(ca), Some(cert), Some(key)) => { + let ca_pem = std::fs::read_to_string(ca).map_err(|e| { + MtlsError::IoError(format!("Failed to read CA cert: {}", e)) + })?; + let cert_pem = std::fs::read_to_string(cert).map_err(|e| { + MtlsError::IoError(format!("Failed to read client cert: {}", e)) + })?; + let key_pem = std::fs::read_to_string(key).map_err(|e| { + MtlsError::IoError(format!("Failed to read client key: {}", e)) + })?; + + info!("Forgejo mTLS configured successfully"); + Ok(MtlsConfig { + enabled: true, + ca_cert: Some(ca_pem), + client_cert: Some(cert_pem), + client_key: Some(key_pem), + }) + } + _ => { + debug!("Forgejo mTLS not configured"); + Ok(MtlsConfig::default()) + } + } + } + + /// Configure mTLS for Directory service (LDAP/AD) connections + /// + /// # Arguments + /// * `ca_cert_path` - Path to the CA certificate + /// * `client_cert_path` - Path to the client certificate + /// * `client_key_path` - Path to the client private key + /// + /// # Returns + /// Result containing the mTLS configuration or an error + pub fn configure_directory_mtls( + ca_cert_path: Option<&Path>, + client_cert_path: Option<&Path>, + client_key_path: Option<&Path>, + ) -> Result { + match (ca_cert_path, client_cert_path, client_key_path) { + (Some(ca), Some(cert), Some(key)) => { + let ca_pem = std::fs::read_to_string(ca).map_err(|e| { + MtlsError::IoError(format!("Failed to read CA cert: {}", e)) + })?; + let cert_pem = std::fs::read_to_string(cert).map_err(|e| { + MtlsError::IoError(format!("Failed to read client cert: {}", e)) + })?; + let key_pem = std::fs::read_to_string(key).map_err(|e| { + MtlsError::IoError(format!("Failed to read client key: {}", e)) + })?; + + info!("Directory service mTLS configured successfully"); + Ok(MtlsConfig { + enabled: true, + ca_cert: Some(ca_pem), + client_cert: Some(cert_pem), + client_key: Some(key_pem), + }) + } + _ => { + debug!("Directory service mTLS not configured"); + Ok(MtlsConfig::default()) + } + } + } +} + +/// mTLS configuration structure +#[derive(Debug, Clone, Default)] +pub struct MtlsConfig { + /// Whether mTLS is enabled + pub enabled: bool, + /// CA certificate PEM content + pub ca_cert: Option, + /// Client certificate PEM content + pub client_cert: Option, + /// Client private key PEM content + pub client_key: Option, +} + +impl MtlsConfig { + /// Create a new mTLS configuration + pub fn new( + ca_cert: Option, + client_cert: Option, + client_key: Option, + ) -> Self { + let enabled = ca_cert.is_some() && client_cert.is_some() && client_key.is_some(); + Self { + enabled, + ca_cert, + client_cert, + client_key, + } + } + + /// Check if mTLS is properly configured + pub fn is_configured(&self) -> bool { + self.enabled + && self.ca_cert.is_some() + && self.client_cert.is_some() + && self.client_key.is_some() + } +} + +/// mTLS error types +#[derive(Debug, thiserror::Error)] +pub enum MtlsError { + #[error("Certificate not found: {0}")] + CertificateNotFound(String), + + #[error("Private key not found: {0}")] + KeyNotFound(String), + + #[error("Invalid certificate format: {0}")] + InvalidCertificate(String), + + #[error("Invalid key format: {0}")] + InvalidKey(String), + + #[error("IO error: {0}")] + IoError(String), + + #[error("TLS configuration error: {0}")] + TlsConfigError(String), +} + +/// mTLS Manager for handling mutual TLS connections +pub struct MtlsManager { + config: MtlsConfig, +} + +impl MtlsManager { + /// Create a new mTLS manager + pub fn new(config: MtlsConfig) -> Self { + Self { config } + } + + /// Get the current configuration + pub fn config(&self) -> &MtlsConfig { + &self.config + } + + /// Check if mTLS is enabled + pub fn is_enabled(&self) -> bool { + self.config.enabled + } + + /// Validate the mTLS configuration + pub fn validate(&self) -> Result<(), MtlsError> { + if !self.config.enabled { + return Ok(()); + } + + // Validate CA certificate + if let Some(ref ca) = self.config.ca_cert { + if !ca.contains("-----BEGIN CERTIFICATE-----") { + return Err(MtlsError::InvalidCertificate( + "CA certificate is not in PEM format".to_string(), + )); + } + } + + // Validate client certificate + if let Some(ref cert) = self.config.client_cert { + if !cert.contains("-----BEGIN CERTIFICATE-----") { + return Err(MtlsError::InvalidCertificate( + "Client certificate is not in PEM format".to_string(), + )); + } + } + + // Validate client key + if let Some(ref key) = self.config.client_key { + if !key.contains("-----BEGIN") || !key.contains("PRIVATE KEY-----") { + return Err(MtlsError::InvalidKey( + "Client key is not in PEM format".to_string(), + )); + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mtls_config_default() { + let config = MtlsConfig::default(); + assert!(!config.enabled); + assert!(config.ca_cert.is_none()); + assert!(config.client_cert.is_none()); + assert!(config.client_key.is_none()); + } + + #[test] + fn test_mtls_config_new() { + let config = MtlsConfig::new( + Some("ca_cert".to_string()), + Some("client_cert".to_string()), + Some("client_key".to_string()), + ); + assert!(config.enabled); + assert!(config.is_configured()); + } + + #[test] + fn test_mtls_config_partial() { + let config = MtlsConfig::new(Some("ca_cert".to_string()), None, None); + assert!(!config.enabled); + assert!(!config.is_configured()); + } + + #[test] + fn test_mtls_manager_validation() { + let config = MtlsConfig { + enabled: true, + ca_cert: Some("-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----".to_string()), + client_cert: Some("-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----".to_string()), + client_key: Some("-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----".to_string()), + }; + let manager = MtlsManager::new(config); + assert!(manager.validate().is_ok()); + } + + #[test] + fn test_mtls_manager_invalid_cert() { + let config = MtlsConfig { + enabled: true, + ca_cert: Some("invalid".to_string()), + client_cert: Some("-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----".to_string()), + client_key: Some("-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----".to_string()), + }; + let manager = MtlsManager::new(config); + assert!(manager.validate().is_err()); + } +} diff --git a/src/web/auth.rs b/src/web/auth.rs index df3868d4d..c0c5bb310 100644 --- a/src/web/auth.rs +++ b/src/web/auth.rs @@ -3,21 +3,34 @@ use axum::{ async_trait, extract::{FromRef, FromRequestParts, Query, State}, - headers::{authorization::Bearer, Authorization, Cookie}, - http::{header, request::Parts, Request, StatusCode}, + http::{header, request::Parts, HeaderMap, Request, StatusCode}, middleware::Next, response::{IntoResponse, Redirect, Response}, - Json, RequestPartsExt, TypedHeader, + Json, }; use chrono::{Duration, Utc}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use tower_cookies::{Cookies, Key}; +use tower_cookies::Cookies; use uuid::Uuid; use crate::shared::state::AppState; +/// Extract bearer token from Authorization header +fn extract_bearer_token(headers: &HeaderMap) -> Option { + headers + .get(header::AUTHORIZATION) + .and_then(|value| value.to_str().ok()) + .and_then(|auth| { + if auth.to_lowercase().starts_with("bearer ") { + Some(auth[7..].to_string()) + } else { + None + } + }) +} + /// JWT Claims structure #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { @@ -45,6 +58,16 @@ pub struct UserSession { pub created_at: i64, } +/// Cookie key for signing (simple wrapper) +#[derive(Clone)] +pub struct CookieKey(Vec); + +impl CookieKey { + pub fn from(bytes: &[u8]) -> Self { + Self(bytes.to_vec()) + } +} + /// Authentication configuration #[derive(Clone)] pub struct AuthConfig { @@ -54,16 +77,18 @@ pub struct AuthConfig { pub zitadel_url: String, pub zitadel_client_id: String, pub zitadel_client_secret: String, - pub cookie_key: Key, + pub cookie_key: CookieKey, } impl AuthConfig { pub fn from_env() -> Self { // Use Zitadel directory service for all configuration // No environment variables should be read directly + use base64::Engine; let jwt_secret = { // Generate a secure random secret - should come from directory service - let secret = base64::encode(uuid::Uuid::new_v4().as_bytes()); + let secret = + base64::engine::general_purpose::STANDARD.encode(uuid::Uuid::new_v4().as_bytes()); tracing::info!("Using generated JWT secret"); secret }; @@ -81,7 +106,7 @@ impl AuthConfig { zitadel_url: crate::core::urls::InternalUrls::DIRECTORY_BASE.to_string(), zitadel_client_id: "botserver-web".to_string(), zitadel_client_secret: String::new(), // Retrieved from directory service - cookie_key: Key::from(cookie_secret.as_bytes()), + cookie_key: CookieKey::from(cookie_secret.as_bytes()), } } @@ -108,18 +133,13 @@ where { type Rejection = (StatusCode, &'static str); - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let app_state = AppState::from_ref(state); - let auth_config = app_state - .extensions - .get::() - .ok_or((StatusCode::INTERNAL_SERVER_ERROR, "Auth not configured"))?; + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + // Get auth config from environment for now (simplified) + let auth_config = AuthConfig::from_env(); // Try to get token from Authorization header first - let token = if let Ok(TypedHeader(Authorization(bearer))) = - parts.extract::>>().await - { - bearer.token().to_string() + let token = if let Some(bearer_token) = extract_bearer_token(&parts.headers) { + bearer_token } else if let Ok(cookies) = parts.extract::().await { // Fall back to cookie cookies diff --git a/src/web/auth_handlers.rs b/src/web/auth_handlers.rs index 2260a1620..afcf31228 100644 --- a/src/web/auth_handlers.rs +++ b/src/web/auth_handlers.rs @@ -20,7 +20,7 @@ use super::auth::{ /// Login page template #[derive(Template)] -#[template(path = "auth/login.html")] +#[template(path = "suite/auth/login.html")] pub struct LoginTemplate { pub error_message: Option, pub redirect_url: Option, diff --git a/src/web/chat_handlers.rs b/src/web/chat_handlers.rs index d993c4dbc..377628acb 100644 --- a/src/web/chat_handlers.rs +++ b/src/web/chat_handlers.rs @@ -18,35 +18,35 @@ use crate::shared::state::AppState; /// Chat page template #[derive(Template)] -#[template(path = "chat.html")] +#[template(path = "suite/chat.html")] pub struct ChatTemplate { pub session_id: String, } /// Session list template #[derive(Template)] -#[template(path = "partials/sessions.html")] +#[template(path = "suite/partials/sessions.html")] struct SessionsTemplate { sessions: Vec, } /// Message list template #[derive(Template)] -#[template(path = "partials/messages.html")] +#[template(path = "suite/partials/messages.html")] struct MessagesTemplate { messages: Vec, } /// Suggestions template #[derive(Template)] -#[template(path = "partials/suggestions.html")] +#[template(path = "suite/partials/suggestions.html")] struct SuggestionsTemplate { suggestions: Vec, } /// Context selector template #[derive(Template)] -#[template(path = "partials/contexts.html")] +#[template(path = "suite/partials/contexts.html")] struct ContextsTemplate { contexts: Vec, current_context: Option, @@ -94,15 +94,13 @@ impl ChatState { pub fn new() -> Self { let (tx, _) = broadcast::channel(1000); Self { - sessions: Arc::new(RwLock::new(vec![ - SessionItem { - id: Uuid::new_v4().to_string(), - name: "Default Session".to_string(), - last_message: "Welcome to General Bots".to_string(), - timestamp: chrono::Utc::now().to_rfc3339(), - active: true, - }, - ])), + sessions: Arc::new(RwLock::new(vec![SessionItem { + id: Uuid::new_v4().to_string(), + name: "Default Session".to_string(), + last_message: "Welcome to General Bots".to_string(), + timestamp: chrono::Utc::now().to_rfc3339(), + active: true, + }])), messages: Arc::new(RwLock::new(vec![])), contexts: Arc::new(RwLock::new(vec![ Context { @@ -212,7 +210,9 @@ async fn send_message( } // Broadcast via WebSocket - let _ = chat_state.broadcast.send(WsMessage::Message(user_message.clone())); + let _ = chat_state + .broadcast + .send(WsMessage::Message(user_message.clone())); // Simulate bot response (this would call actual LLM service) let bot_message = Message { @@ -231,7 +231,9 @@ async fn send_message( } // Broadcast bot message - let _ = chat_state.broadcast.send(WsMessage::Message(bot_message.clone())); + let _ = chat_state + .broadcast + .send(WsMessage::Message(bot_message.clone())); // Return rendered messages MessagesTemplate { @@ -279,13 +281,13 @@ async fn create_session( // Return single session HTML format!( - r#"
{}
{}
-
"#, + "##, new_session.id, new_session.name, new_session.timestamp ) } @@ -312,11 +314,7 @@ async fn switch_session( }); // Return messages for this session - get_messages( - Query(GetMessagesParams { session_id: id }), - State(state), - ) - .await + get_messages(Query(GetMessagesParams { session_id: id }), State(state)).await } /// Get suggestions @@ -388,7 +386,11 @@ pub async fn websocket_handler( ws.on_upgrade(move |socket| handle_chat_socket(socket, state, claims)) } -async fn handle_chat_socket(socket: axum::extract::ws::WebSocket, state: AppState, claims: crate::web::auth::Claims) { +async fn handle_chat_socket( + socket: axum::extract::ws::WebSocket, + state: AppState, + claims: crate::web::auth::Claims, +) { let (mut sender, mut receiver) = socket.split(); let chat_state = state.extensions.get::().unwrap(); let mut rx = chat_state.broadcast.subscribe(); diff --git a/src/web/mod.rs b/src/web/mod.rs index 57898d885..db06caa55 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -49,7 +49,7 @@ pub mod drive { } #[derive(Template)] - #[template(path = "drive.html")] + #[template(path = "suite/drive.html")] struct DriveTemplate { user_name: String, user_email: String, @@ -158,7 +158,7 @@ pub mod mail { } #[derive(Template)] - #[template(path = "mail.html")] + #[template(path = "suite/mail.html")] struct MailTemplate { user_name: String, user_email: String, @@ -277,7 +277,7 @@ pub mod meet { } #[derive(Template)] - #[template(path = "meet.html")] + #[template(path = "suite/meet.html")] struct MeetTemplate { user_name: String, user_email: String, @@ -370,7 +370,7 @@ pub mod tasks { } #[derive(Template)] - #[template(path = "tasks.html")] + #[template(path = "suite/tasks.html")] struct TasksTemplate { user_name: String, user_email: String, @@ -387,7 +387,7 @@ pub struct BaseContext { /// Home page template #[derive(Template)] -#[template(path = "home.html")] +#[template(path = "suite/home.html")] struct HomeTemplate { base: BaseContext, apps: Vec, @@ -404,7 +404,7 @@ struct AppCard { /// Apps menu template #[derive(Template)] -#[template(path = "partials/apps_menu.html")] +#[template(path = "suite/partials/apps_menu.html")] struct AppsMenuTemplate { apps: Vec, } @@ -420,7 +420,7 @@ struct AppMenuItem { /// User menu template #[derive(Template)] -#[template(path = "partials/user_menu.html")] +#[template(path = "suite/partials/user_menu.html")] struct UserMenuTemplate { user_name: String, user_email: String, @@ -704,7 +704,7 @@ pub struct HtmxResponse { /// Notification for HTMX #[derive(Serialize, Template)] -#[template(path = "partials/notification.html")] +#[template(path = "suite/partials/notification.html")] pub struct NotificationTemplate { pub message: String, pub severity: String, // info, success, warning, error @@ -712,7 +712,7 @@ pub struct NotificationTemplate { /// Message template for chat/notifications #[derive(Serialize, Template)] -#[template(path = "partials/message.html")] +#[template(path = "suite/partials/message.html")] pub struct MessageTemplate { pub id: String, pub sender: String, diff --git a/ui/suite/auth/login.html b/ui/suite/auth/login.html new file mode 100644 index 000000000..8e8872eb5 --- /dev/null +++ b/ui/suite/auth/login.html @@ -0,0 +1,351 @@ + + + + + + Login - General Bots + + + + + + + + + diff --git a/ui/suite/base.html b/ui/suite/base.html new file mode 100644 index 000000000..b44fadb59 --- /dev/null +++ b/ui/suite/base.html @@ -0,0 +1,502 @@ + + + + + + {% block title %}General Bots Suite{% endblock %} + + + + + + + + + + + {% block head %}{% endblock %} + + + +
+ + +
+ + + + + + + + +
+
+ + +
+
+ {% block content %}{% endblock %} +
+
+ + +
+ + + + {% block scripts %}{% endblock %} + + diff --git a/ui/suite/chat.html b/ui/suite/chat.html new file mode 100644 index 000000000..d1209b766 --- /dev/null +++ b/ui/suite/chat.html @@ -0,0 +1,607 @@ +{% extends "suite/base.html" %} {% block title %}Chat - General Bots Suite{% +endblock %} {% block content %} +
+ + + + +
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+ + +
+ + + + + +
+
+
+
+ + + +
+
+ + + + +{% endblock %} diff --git a/ui/suite/drive.html b/ui/suite/drive.html new file mode 100644 index 000000000..e6e0097f8 --- /dev/null +++ b/ui/suite/drive.html @@ -0,0 +1,600 @@ +{% extends "suite/base.html" %} + +{% block title %}Drive - General Bots Suite{% endblock %} + +{% block content %} +
+ + + + +
+ +
+ + +
+
+ + +
+ + +
+
+ + +
+
+ +
+
+
+
+ + + + + +
+
+ + + + + +

Drop files here to upload

+
+
+ + + + +{% endblock %} diff --git a/ui/suite/home.html b/ui/suite/home.html new file mode 100644 index 000000000..3cd352d0f --- /dev/null +++ b/ui/suite/home.html @@ -0,0 +1,372 @@ + + + + + + General Bots Suite + + + + + + + + + + diff --git a/ui/suite/mail.html b/ui/suite/mail.html new file mode 100644 index 000000000..e6657f2f3 --- /dev/null +++ b/ui/suite/mail.html @@ -0,0 +1,512 @@ +{% extends "base.html" %} + +{% block title %}Mail - General Bots{% endblock %} + +{% block content %} +
+ + + + +
+
+
+ + +
+ +
+ +
+
+
+ Loading emails... +
+
+
+ + +
+
+
+ + + + +

Select an email to read

+

Choose from your inbox on the left

+
+
+
+
+ + + + +{% endblock %} diff --git a/ui/suite/meet.html b/ui/suite/meet.html new file mode 100644 index 000000000..ba0765b7c --- /dev/null +++ b/ui/suite/meet.html @@ -0,0 +1,1071 @@ +{% extends "base.html" %} + +{% block title %}Meet - General Bots{% endblock %} + +{% block content %} +
+ +
+
+
+

Join Meeting

+

Configure your audio and video before joining

+
+ +
+ +
+ + + + + Camera is off +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+
+ + + + + + +
+ + + + +{% endblock %} diff --git a/ui/suite/partials/apps_menu.html b/ui/suite/partials/apps_menu.html new file mode 100644 index 000000000..6684090c1 --- /dev/null +++ b/ui/suite/partials/apps_menu.html @@ -0,0 +1,101 @@ +
+
+

Apps

+
+
+ {% for app in apps %} + +
+ {{ app.icon|safe }} +
+ {{ app.name }} +
+ {% endfor %} +
+
+ + diff --git a/ui/suite/partials/contexts.html b/ui/suite/partials/contexts.html new file mode 100644 index 000000000..1d5f47341 --- /dev/null +++ b/ui/suite/partials/contexts.html @@ -0,0 +1,117 @@ +
+ + +
+ +{% if contexts.len() > 0 %} +
+ {% for context in contexts %} +
+
{{ context.name }}
+
{{ context.description }}
+
+ {% endfor %} +
+{% else %} +
+

No contexts configured

+
+{% endif %} + + diff --git a/ui/suite/partials/message.html b/ui/suite/partials/message.html new file mode 100644 index 000000000..47b791ade --- /dev/null +++ b/ui/suite/partials/message.html @@ -0,0 +1,16 @@ +
+
+ {% if is_user %} + U + {% else %} + 🤖 + {% endif %} +
+
+
+ {{ sender }} + {{ timestamp }} +
+
{{ content }}
+
+
diff --git a/ui/suite/partials/messages.html b/ui/suite/partials/messages.html new file mode 100644 index 000000000..55b59cb80 --- /dev/null +++ b/ui/suite/partials/messages.html @@ -0,0 +1,25 @@ +{% for message in messages %} +
+
+ {% if message.is_user %} + U + {% else %} + 🤖 + {% endif %} +
+
+
+ {{ message.sender }} + {{ message.timestamp }} +
+
{{ message.content }}
+
+
+{% endfor %} +{% if messages.is_empty() %} +
+
💬
+

Start a conversation

+

Type a message below or use voice input

+
+{% endif %} diff --git a/ui/suite/partials/notification.html b/ui/suite/partials/notification.html new file mode 100644 index 000000000..d2760453b --- /dev/null +++ b/ui/suite/partials/notification.html @@ -0,0 +1,47 @@ + diff --git a/ui/suite/partials/sessions.html b/ui/suite/partials/sessions.html new file mode 100644 index 000000000..1bd3559a2 --- /dev/null +++ b/ui/suite/partials/sessions.html @@ -0,0 +1,25 @@ +{% for session in sessions %} +
+
+
{{ session.name }}
+
{{ session.last_message }}
+
+
+ {{ session.timestamp }} +
+
+{% endfor %} +{% if sessions.is_empty() %} +
+

No conversations yet

+ +
+{% endif %} diff --git a/ui/suite/partials/suggestions.html b/ui/suite/partials/suggestions.html new file mode 100644 index 000000000..c583a72c6 --- /dev/null +++ b/ui/suite/partials/suggestions.html @@ -0,0 +1,17 @@ +
+{% for suggestion in suggestions %} + +{% endfor %} +
+{% if suggestions.is_empty() %} +
+ No suggestions available +
+{% endif %} diff --git a/ui/suite/partials/user_menu.html b/ui/suite/partials/user_menu.html new file mode 100644 index 000000000..ff9850abc --- /dev/null +++ b/ui/suite/partials/user_menu.html @@ -0,0 +1,166 @@ +
+
+
{{ user_initial }}
+ +
+ +
+ + + +
+ + + + + + + + Sign out + +
+ + diff --git a/ui/suite/tasks.html b/ui/suite/tasks.html new file mode 100644 index 000000000..2f04208e9 --- /dev/null +++ b/ui/suite/tasks.html @@ -0,0 +1,608 @@ +{% extends "base.html" %} + +{% block title %}Tasks - General Bots{% endblock %} + +{% block content %} +
+ +
+
+

+ + + + + Tasks +

+
+ + {{ total_count }} + Total + + + {{ active_count }} + Active + + + {{ completed_count }} + Completed + +
+
+
+ + +
+
+ + + + + +
+
+ + +
+ + + + +
+ + +
+
+
+
+ Loading tasks... +
+
+
+ + + +
+ + + + +{% endblock %}