From 2ffc81b6d650e8f42b58d4048d6e7a076a1af7e7 Mon Sep 17 00:00:00 2001 From: Tejasdevs Date: Wed, 15 Oct 2025 14:40:51 +0530 Subject: [PATCH 1/2] add importAttribute --- server/src/core/http-server/old-server.js | 4 ++-- test/unit/unit.setup.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/core/http-server/old-server.js b/server/src/core/http-server/old-server.js index 7e6e062..cc04755 100644 --- a/server/src/core/http-server/old-server.js +++ b/server/src/core/http-server/old-server.js @@ -8,8 +8,8 @@ import Fastify from 'fastify' import fastifyStatic from '@fastify/static' import socketio from 'socket.io' -import { version } from '@@/package.json' -import { endpoints } from '@@/core/skills-endpoints.json' +import { version } from '@@/package.json' with { type: 'json' } +import { endpoints } from '@@/core/skills-endpoints.json' with { type: 'json' } import { HAS_LOGGER, HAS_OVER_HTTP, diff --git a/test/unit/unit.setup.js b/test/unit/unit.setup.js index d8aeb6e..2e3059e 100644 --- a/test/unit/unit.setup.js +++ b/test/unit/unit.setup.js @@ -1,6 +1,6 @@ import moment from 'moment-timezone' -import utterance_samples from '../../core/data/en/answers.json' +import utterance_samples from '../../core/data/en/answers.json' with { type: 'json' } jest.setTimeout(60000) From d1b385a52ec85b34739c6e9430721a2c5018f691 Mon Sep 17 00:00:00 2001 From: Tejasdevs Date: Wed, 15 Oct 2025 15:17:52 +0530 Subject: [PATCH 2/2] add new feature --- WIDGETS_GUIDE.md | 720 ++++++++++++++++++++++ WIDGETS_QUICKSTART.md | 457 ++++++++++++++ WIDGETS_README.md | 390 ++++++++++++ app/src/css/style.scss | 1 + app/src/css/widgets.scss | 532 ++++++++++++++++ app/src/index.html | 53 ++ app/src/js/main.js | 67 ++ app/src/js/widget-examples.js | 235 +++++++ app/src/js/widget-manager.js | 483 +++++++++++++++ server/src/core/http-server/old-server.js | 67 ++ 10 files changed, 3005 insertions(+) create mode 100644 WIDGETS_GUIDE.md create mode 100644 WIDGETS_QUICKSTART.md create mode 100644 WIDGETS_README.md create mode 100644 app/src/css/widgets.scss create mode 100644 app/src/js/widget-examples.js create mode 100644 app/src/js/widget-manager.js diff --git a/WIDGETS_GUIDE.md b/WIDGETS_GUIDE.md new file mode 100644 index 0000000..7e2e64a --- /dev/null +++ b/WIDGETS_GUIDE.md @@ -0,0 +1,720 @@ +# 🎨 Leon AI Dashboard Widgets Guide + +## Overview + +The Leon AI Dashboard now includes a powerful **Widgets System** that allows you to customize your dashboard with interactive UI elements. This guide will help you understand, use, and create your own widgets. + +--- + +## 📋 Table of Contents + +1. [Getting Started](#getting-started) +2. [Available Widget Types](#available-widget-types) +3. [Using Widgets](#using-widgets) +4. [Creating Custom Widgets](#creating-custom-widgets) +5. [Widget Placement](#widget-placement) +6. [API Reference](#api-reference) +7. [Beginner Examples](#beginner-examples) +8. [Advanced Customization](#advanced-customization) + +--- + +## 🚀 Getting Started + +### Accessing the Widgets Dashboard + +1. Start your Leon AI server +2. Open the web interface +3. Click the **"Widgets"** button in the top navigation +4. Click **"Add Widget"** to start adding widgets + +### Quick Start for Beginners + +The easiest way to add widgets is through the UI: + +1. Click **"Widgets"** button +2. Click **"Add Widget"** +3. Choose a widget type from the dialog +4. The widget will appear on your dashboard! + +--- + +## 🧩 Available Widget Types + +### 1. 🕐 Clock Widget +**Purpose:** Display current time and date + +**Features:** +- Real-time clock updates +- Customizable locale +- Optional seconds display +- Date formatting + +**Use Cases:** +- Keep track of time while working +- Display time in different timezones +- Quick reference for date and time + +--- + +### 2. 🌤️ Weather Widget +**Purpose:** Show weather information + +**Features:** +- Temperature display +- Weather condition +- Location name +- Refreshable data + +**Use Cases:** +- Check weather at a glance +- Plan your day +- Monitor conditions + +**Note:** Currently shows placeholder data. Can be connected to a weather API. + +--- + +### 3. 📝 Notes Widget +**Purpose:** Quick notepad for thoughts and reminders + +**Features:** +- Simple text area +- Auto-save on blur +- Persistent storage +- Resizable + +**Use Cases:** +- Quick notes during conversations +- Temporary reminders +- Draft messages +- Brainstorming + +--- + +### 4. 🔗 Quick Links Widget +**Purpose:** Bookmark your favorite websites + +**Features:** +- Clickable links +- Custom icons (emoji) +- Link titles +- Opens in new tab + +**Use Cases:** +- Frequently visited sites +- Project resources +- Documentation links +- Social media shortcuts + +**Example Links:** +```javascript +{ + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Docs', url: 'https://docs.example.com', icon: '📖' } + ] +} +``` + +--- + +### 5. 📊 Stats Widget +**Purpose:** Display key metrics and statistics + +**Features:** +- Multiple stat items +- Value and label display +- Grid layout +- Color-coded values + +**Use Cases:** +- Dashboard KPIs +- Task completion rates +- System metrics +- Progress tracking + +**Example Stats:** +```javascript +{ + stats: [ + { label: 'Tasks', value: '12' }, + { label: 'Completed', value: '8' }, + { label: 'Pending', value: '4' } + ] +} +``` + +--- + +### 6. ✅ Todo List Widget +**Purpose:** Manage your tasks and todos + +**Features:** +- Add new tasks +- Check off completed items +- Delete tasks +- Persistent storage +- Visual completion state + +**Use Cases:** +- Daily task management +- Project checklists +- Shopping lists +- Goal tracking + +--- + +### 7. 🎨 Custom Widget +**Purpose:** Create your own widget with custom HTML + +**Features:** +- Full HTML support +- Custom styling +- Flexible content +- Unlimited possibilities + +**Use Cases:** +- Embed external content +- Custom visualizations +- Specialized tools +- Unique layouts + +--- + +## 💡 Using Widgets + +### Adding a Widget (UI Method) + +1. Click **"Add Widget"** button +2. Select widget type from the dialog +3. Widget appears with default configuration +4. Customize the widget content + +### Widget Controls + +Each widget has three control buttons: + +- **↻ Refresh:** Reload widget data +- **⚙ Settings:** Configure widget (coming soon) +- **× Remove:** Delete the widget + +### Drag and Drop + +- **Grab:** Click and hold the widget header +- **Move:** Drag to desired position +- **Drop:** Release to place widget +- Widgets automatically reorder + +### Editing Widget Content + +Different widgets have different editing methods: + +- **Notes:** Type directly in the text area +- **Todo:** Use the input field and + button +- **Quick Links:** Edit via code (see API section) +- **Stats:** Update via code (see API section) + +--- + +## 🛠️ Creating Custom Widgets + +### Method 1: Using the Custom Widget Type + +```javascript +const myWidget = { + type: 'custom', + title: 'My Custom Widget', + content: ` +
+

Hello World!

+

This is my custom widget

+ +
+ `, + position: 0 +} + +await widgetManager.addWidget(myWidget) +``` + +### Method 2: Using Widget Examples + +```javascript +import { createCustomWidget } from './widget-examples' + +const widget = createCustomWidget( + 'My Widget', + '

Custom Content

' +) + +await widgetManager.addWidget(widget) +``` + +### Method 3: Programmatic Creation + +```javascript +// In your JavaScript code +import WidgetManager from './widget-manager' + +const widgetManager = new WidgetManager() +await widgetManager.init('widgets-container') + +// Add multiple widgets +const widgets = [ + { type: 'clock', title: 'Clock', position: 0 }, + { type: 'notes', title: 'Notes', position: 1 }, + { type: 'todo', title: 'Tasks', position: 2 } +] + +for (const widget of widgets) { + await widgetManager.addWidget(widget) +} +``` + +--- + +## 📍 Widget Placement + +### Grid Layout + +Widgets are displayed in a responsive grid: + +- **Desktop:** 3-4 columns (auto-fit) +- **Tablet:** 2 columns +- **Mobile:** 1 column + +### Positioning + +Widgets have a `position` property (0, 1, 2, ...): + +```javascript +{ + position: 0 // First widget +} +``` + +### Reordering + +**Via Drag & Drop:** +- Drag widget to new position +- Drop on another widget to swap + +**Via Code:** +```javascript +await widgetManager.updateWidget(widgetId, { + position: 2 +}) +``` + +--- + +## 📚 API Reference + +### WidgetManager Class + +#### Initialize +```javascript +const widgetManager = new WidgetManager() +await widgetManager.init('widgets-container') +``` + +#### Add Widget +```javascript +const widget = await widgetManager.addWidget({ + type: 'clock', + title: 'My Clock', + config: { showSeconds: true }, + position: 0 +}) +``` + +#### Update Widget +```javascript +await widgetManager.updateWidget(widgetId, { + title: 'Updated Title', + data: { newData: 'value' } +}) +``` + +#### Delete Widget +```javascript +await widgetManager.deleteWidget(widgetId) +``` + +#### Refresh Widget +```javascript +await widgetManager.refreshWidget(widgetId) +``` + +### Widget Configuration Object + +```javascript +{ + type: 'widget-type', // Required: 'clock', 'notes', 'todo', etc. + title: 'Widget Title', // Required: Display name + config: { // Optional: Widget-specific settings + showSeconds: true, + locale: 'en-US' + }, + data: { // Optional: Widget data + notes: 'Some text', + todos: [...] + }, + position: 0, // Optional: Display order + content: '' // Optional: For custom widgets +} +``` + +### Server API Endpoints + +#### GET /api/widgets +Get all widgets for a user +```javascript +fetch('/api/widgets?userId=default') +``` + +#### POST /api/widgets +Create a new widget +```javascript +fetch('/api/widgets', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: 'default', + widget: { type: 'clock', title: 'Clock' } + }) +}) +``` + +#### PUT /api/widgets/:id +Update a widget +```javascript +fetch(`/api/widgets/${widgetId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: 'default', + widget: { title: 'New Title' } + }) +}) +``` + +#### DELETE /api/widgets/:id +Delete a widget +```javascript +fetch(`/api/widgets/${widgetId}?userId=default`, { + method: 'DELETE' +}) +``` + +--- + +## 🎓 Beginner Examples + +### Example 1: Simple Dashboard Setup + +```javascript +// Create a basic dashboard with essential widgets +import WidgetManager from './widget-manager' +import { widgetExamples } from './widget-examples' + +const widgetManager = new WidgetManager() +await widgetManager.init('widgets-container') + +// Add clock +await widgetManager.addWidget(widgetExamples.clock()) + +// Add notes +await widgetManager.addWidget(widgetExamples.notes()) + +// Add todo list +await widgetManager.addWidget(widgetExamples.todo()) +``` + +### Example 2: Custom Quick Links + +```javascript +const myLinks = { + type: 'quick-links', + title: 'My Favorites', + data: { + links: [ + { title: 'Gmail', url: 'https://gmail.com', icon: '📧' }, + { title: 'Calendar', url: 'https://calendar.google.com', icon: '📅' }, + { title: 'Drive', url: 'https://drive.google.com', icon: '💾' } + ] + } +} + +await widgetManager.addWidget(myLinks) +``` + +### Example 3: Project Stats Dashboard + +```javascript +const projectStats = { + type: 'stats', + title: 'Project Progress', + data: { + stats: [ + { label: 'Total Tasks', value: '50' }, + { label: 'Completed', value: '35' }, + { label: 'In Progress', value: '10' }, + { label: 'Blocked', value: '5' } + ] + } +} + +await widgetManager.addWidget(projectStats) +``` + +### Example 4: Custom HTML Widget + +```javascript +const customWidget = { + type: 'custom', + title: 'Welcome Message', + content: ` +
+

Welcome to Leon AI!

+

Your personal AI assistant

+ +
+ ` +} + +await widgetManager.addWidget(customWidget) +``` + +--- + +## 🔧 Advanced Customization + +### Creating a New Widget Type + +#### Step 1: Add Renderer in widget-manager.js + +```javascript +renderWidgetContent(widget) { + switch (widget.type) { + // ... existing cases + case 'my-custom-type': + return this.renderMyCustomWidget(widget) + default: + return '

Unknown widget type

' + } +} + +renderMyCustomWidget(widget) { + return ` +
+

${widget.data.title}

+

${widget.data.description}

+
+ ${widget.data.content} +
+
+ ` +} +``` + +#### Step 2: Add Styles in widgets.scss + +```scss +.my-custom-widget { + padding: 1rem; + + h3 { + color: rgba(255, 255, 255, 0.9); + margin-bottom: 0.5rem; + } + + .custom-content { + background: rgba(255, 255, 255, 0.05); + padding: 1rem; + border-radius: 8px; + } +} +``` + +#### Step 3: Add Event Listeners (if needed) + +```javascript +attachWidgetTypeListeners(widgetElement, widget) { + switch (widget.type) { + // ... existing cases + case 'my-custom-type': + const button = widgetElement.querySelector('.custom-button') + button?.addEventListener('click', () => { + // Handle click + this.updateWidget(widget.id, { + data: { clicked: true } + }) + }) + break + } +} +``` + +#### Step 4: Use Your New Widget + +```javascript +const myWidget = { + type: 'my-custom-type', + title: 'My Custom Widget', + data: { + title: 'Hello', + description: 'This is my custom widget type', + content: 'Custom content here' + } +} + +await widgetManager.addWidget(myWidget) +``` + +### Connecting to External APIs + +```javascript +// Example: Real weather widget +async function createLiveWeatherWidget(city) { + // Fetch weather data from API + const response = await fetch( + `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY` + ) + const data = await response.json() + + return { + type: 'weather', + title: 'Live Weather', + config: { location: city }, + data: { + temp: Math.round(data.main.temp - 273.15), // Convert to Celsius + condition: data.weather[0].description, + location: city + } + } +} + +// Use it +const weatherWidget = await createLiveWeatherWidget('London') +await widgetManager.addWidget(weatherWidget) +``` + +### Widget Persistence + +Widgets are automatically saved to the server. To implement database persistence: + +1. Replace the `Map` in `old-server.js` with a database +2. Implement save/load functions +3. Connect to your preferred database (MongoDB, PostgreSQL, etc.) + +```javascript +// Example with a database +const widgetsStore = { + async get(userId) { + return await db.widgets.find({ userId }) + }, + async set(userId, widgets) { + await db.widgets.updateOne( + { userId }, + { $set: { widgets } }, + { upsert: true } + ) + } +} +``` + +--- + +## 🎯 Best Practices + +### 1. Widget Design +- Keep widgets focused on one purpose +- Use clear, descriptive titles +- Provide visual feedback for interactions +- Make content scannable + +### 2. Performance +- Avoid heavy computations in render functions +- Use efficient update mechanisms +- Limit the number of widgets (recommended: 6-12) +- Clean up event listeners when widgets are removed + +### 3. User Experience +- Provide default data for new widgets +- Show loading states when fetching data +- Handle errors gracefully +- Make widgets responsive + +### 4. Code Organization +- Keep widget logic in separate files +- Use the examples file as a template +- Document custom widget types +- Follow the existing code style + +--- + +## 🐛 Troubleshooting + +### Widget Not Appearing +- Check browser console for errors +- Verify widget type is valid +- Ensure widget-manager is initialized +- Check if widgets container exists + +### Styles Not Applied +- Verify widgets.scss is imported +- Check for CSS conflicts +- Clear browser cache +- Rebuild the application + +### Data Not Persisting +- Check server API endpoints +- Verify network requests in DevTools +- Check server console for errors +- Ensure userId is consistent + +### Drag and Drop Not Working +- Verify draggable attribute is set +- Check event listeners are attached +- Ensure no CSS conflicts with pointer-events +- Test in different browsers + +--- + +## 📖 Additional Resources + +- **Leon AI Documentation:** https://docs.getleon.ai +- **Widget Examples:** See `app/src/js/widget-examples.js` +- **Widget Manager:** See `app/src/js/widget-manager.js` +- **Styles:** See `app/src/css/widgets.scss` + +--- + +## 🤝 Contributing + +Want to add new widget types or improve existing ones? + +1. Fork the repository +2. Create a new widget type +3. Add documentation +4. Submit a pull request + +--- + +## 📝 License + +This widgets system is part of Leon AI and follows the same license. + +--- + +## 🎉 Happy Widget Building! + +Start customizing your Leon AI dashboard today. If you create something cool, share it with the community! + +**Questions?** Join the Leon AI Discord: https://discord.gg/MNQqqKg diff --git a/WIDGETS_QUICKSTART.md b/WIDGETS_QUICKSTART.md new file mode 100644 index 0000000..afbcd40 --- /dev/null +++ b/WIDGETS_QUICKSTART.md @@ -0,0 +1,457 @@ +# 🚀 Widgets Quick Start Guide + +## For Complete Beginners + +This guide will help you add customizable widgets to your Leon AI dashboard in just a few minutes! + +--- + +## ✨ What Are Widgets? + +Widgets are small, interactive UI elements you can add to your dashboard. Think of them like apps on your phone's home screen - each one does something useful! + +**Available Widgets:** +- 🕐 **Clock** - Shows current time and date +- 📝 **Notes** - Quick notepad for ideas +- ✅ **Todo List** - Manage your tasks +- 🔗 **Quick Links** - Bookmark favorite websites +- 📊 **Stats** - Display numbers and metrics +- 🌤️ **Weather** - Show weather info + +--- + +## 🎯 Method 1: Using the UI (Easiest!) + +### Step 1: Access Widgets +1. Start your Leon AI server +2. Open the web interface in your browser +3. Look for the **"Widgets"** button at the top +4. Click it! + +### Step 2: Add Your First Widget +1. Click the **"Add Widget"** button +2. You'll see a dialog with widget types +3. Click on any widget type (try **Clock** first!) +4. The widget appears instantly! 🎉 + +### Step 3: Customize Your Widget +- **Move it:** Drag the widget header to reorder +- **Remove it:** Click the **×** button +- **Refresh it:** Click the **↻** button +- **Edit content:** Type directly in Notes or Todo widgets + +### Step 4: Add More Widgets +Repeat Step 2 to add as many widgets as you want! + +--- + +## 💻 Method 2: Using Code (For Developers) + +### Basic Example + +Open your browser's developer console (F12) and paste this: + +```javascript +// Get the widget manager instance +const widgetManager = window.widgetManager + +// Add a clock widget +await widgetManager.addWidget({ + type: 'clock', + title: 'My Clock', + position: 0 +}) + +// Add a notes widget +await widgetManager.addWidget({ + type: 'notes', + title: 'Quick Notes', + data: { notes: 'Write your notes here...' }, + position: 1 +}) + +// Add a todo list +await widgetManager.addWidget({ + type: 'todo', + title: 'My Tasks', + data: { + todos: [ + { text: 'Learn Leon AI', completed: false }, + { text: 'Add widgets', completed: true } + ] + }, + position: 2 +}) +``` + +--- + +## 🎨 Widget Types Explained + +### 1. Clock Widget +```javascript +{ + type: 'clock', + title: 'Clock', + config: { + showSeconds: true, // Show seconds? true/false + locale: 'en-US' // Language format + } +} +``` + +### 2. Notes Widget +```javascript +{ + type: 'notes', + title: 'My Notes', + data: { + notes: 'Your text here...' + } +} +``` + +### 3. Todo Widget +```javascript +{ + type: 'todo', + title: 'Tasks', + data: { + todos: [ + { text: 'Task 1', completed: false }, + { text: 'Task 2', completed: true } + ] + } +} +``` + +### 4. Quick Links Widget +```javascript +{ + type: 'quick-links', + title: 'Bookmarks', + data: { + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Google', url: 'https://google.com', icon: '🔍' } + ] + } +} +``` + +### 5. Stats Widget +```javascript +{ + type: 'stats', + title: 'My Stats', + data: { + stats: [ + { label: 'Tasks', value: '10' }, + { label: 'Done', value: '7' } + ] + } +} +``` + +### 6. Weather Widget +```javascript +{ + type: 'weather', + title: 'Weather', + data: { + temp: '72', + condition: 'Sunny', + location: 'New York' + } +} +``` + +--- + +## 🛠️ Common Tasks + +### How to Update a Widget + +```javascript +// First, find the widget ID (shown in browser console) +const widgetId = 'widget-1234567890-abc' + +// Update the widget +await widgetManager.updateWidget(widgetId, { + title: 'New Title', + data: { notes: 'Updated content' } +}) +``` + +### How to Delete a Widget + +**Method 1 (Easy):** Click the **×** button on the widget + +**Method 2 (Code):** +```javascript +await widgetManager.deleteWidget('widget-1234567890-abc') +``` + +### How to Create a Custom Widget + +```javascript +await widgetManager.addWidget({ + type: 'custom', + title: 'My Custom Widget', + content: ` +
+

Hello!

+

This is my custom widget

+ +
+ ` +}) +``` + +--- + +## 📍 Widget Placement + +### Where Do Widgets Appear? + +Widgets are displayed in a **grid layout**: +- **Desktop:** 3-4 columns +- **Tablet:** 2 columns +- **Mobile:** 1 column (stacked) + +### How to Reorder Widgets + +**Method 1 (Drag & Drop):** +1. Click and hold the widget header +2. Drag to the desired position +3. Drop on another widget to swap places + +**Method 2 (Position Number):** +```javascript +// Set widget position (0 = first, 1 = second, etc.) +await widgetManager.updateWidget(widgetId, { + position: 0 // Move to first position +}) +``` + +--- + +## 🎓 Beginner-Friendly Examples + +### Example 1: Personal Dashboard + +Create a simple personal dashboard with essential widgets: + +```javascript +// Clock +await widgetManager.addWidget({ + type: 'clock', + title: 'Time', + position: 0 +}) + +// Quick notes +await widgetManager.addWidget({ + type: 'notes', + title: 'Scratch Pad', + position: 1 +}) + +// Daily tasks +await widgetManager.addWidget({ + type: 'todo', + title: 'Today', + data: { + todos: [ + { text: 'Morning meeting', completed: false }, + { text: 'Lunch at 12pm', completed: false }, + { text: 'Review code', completed: false } + ] + }, + position: 2 +}) +``` + +### Example 2: Developer Dashboard + +Perfect for coding sessions: + +```javascript +// Quick links to dev tools +await widgetManager.addWidget({ + type: 'quick-links', + title: 'Dev Tools', + data: { + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Stack Overflow', url: 'https://stackoverflow.com', icon: '📚' }, + { title: 'MDN Docs', url: 'https://developer.mozilla.org', icon: '📖' }, + { title: 'VS Code', url: 'vscode://file/', icon: '💻' } + ] + }, + position: 0 +}) + +// Project stats +await widgetManager.addWidget({ + type: 'stats', + title: 'Project Stats', + data: { + stats: [ + { label: 'Commits', value: '42' }, + { label: 'PRs', value: '5' }, + { label: 'Issues', value: '3' } + ] + }, + position: 1 +}) +``` + +### Example 3: Student Dashboard + +Great for studying and organizing: + +```javascript +// Study tasks +await widgetManager.addWidget({ + type: 'todo', + title: 'Study Plan', + data: { + todos: [ + { text: 'Read Chapter 5', completed: false }, + { text: 'Complete homework', completed: false }, + { text: 'Review notes', completed: false } + ] + }, + position: 0 +}) + +// Important links +await widgetManager.addWidget({ + type: 'quick-links', + title: 'Resources', + data: { + links: [ + { title: 'Course Portal', url: 'https://school.edu', icon: '🎓' }, + { title: 'Library', url: 'https://library.edu', icon: '📚' }, + { title: 'Calendar', url: 'https://calendar.edu', icon: '📅' } + ] + }, + position: 1 +}) + +// Study notes +await widgetManager.addWidget({ + type: 'notes', + title: 'Quick Notes', + data: { notes: 'Important formulas and concepts...' }, + position: 2 +}) +``` + +--- + +## 🎨 Styling Tips + +### Change Widget Colors (Advanced) + +Add custom CSS in your browser's developer tools: + +```css +/* Make a widget stand out */ +#widget-id { + border: 2px solid #1c75db; + background: rgba(28, 117, 219, 0.1); +} + +/* Customize widget title */ +.widget-title { + color: #ed297a; + font-size: 1.2rem; +} +``` + +--- + +## ❓ Troubleshooting + +### Widget Not Showing Up? + +1. **Check if widgets section is visible** + - Click the "Widgets" button at the top + +2. **Refresh the page** + - Press F5 or Ctrl+R + +3. **Check browser console** + - Press F12 and look for errors + +### Can't Edit Widget Content? + +- **Notes Widget:** Click inside the text area and type +- **Todo Widget:** Use the input field at the bottom +- **Other Widgets:** Update via code (see examples above) + +### Widgets Disappeared After Refresh? + +- Widgets are stored in memory by default +- To persist widgets, you need to implement database storage +- See the full documentation for database integration + +--- + +## 🚀 Next Steps + +### Ready for More? + +1. **Read the Full Guide:** Check out `WIDGETS_GUIDE.md` for advanced features +2. **Explore Examples:** Look at `app/src/js/widget-examples.js` +3. **Create Custom Types:** Learn to build your own widget types +4. **Connect APIs:** Integrate real data (weather, stocks, etc.) + +### Share Your Creations! + +Created something cool? Share it with the Leon AI community on Discord! + +--- + +## 📚 Quick Reference + +### Widget Structure +```javascript +{ + type: 'widget-type', // Required + title: 'Title', // Required + config: { ... }, // Optional + data: { ... }, // Optional + position: 0, // Optional + content: '' // For custom widgets +} +``` + +### Common Commands +```javascript +// Add widget +await widgetManager.addWidget({ type: 'clock', title: 'Clock' }) + +// Update widget +await widgetManager.updateWidget(id, { title: 'New Title' }) + +// Delete widget +await widgetManager.deleteWidget(id) + +// Refresh widget +await widgetManager.refreshWidget(id) +``` + +--- + +## 🎉 You're Ready! + +Start building your perfect dashboard now. Remember: +- Start simple with the UI +- Experiment with different widget types +- Use code for advanced customization +- Have fun! 🚀 + +**Need Help?** Join the Leon AI Discord community! diff --git a/WIDGETS_README.md b/WIDGETS_README.md new file mode 100644 index 0000000..5e69183 --- /dev/null +++ b/WIDGETS_README.md @@ -0,0 +1,390 @@ +# 🎨 Leon AI Dashboard Widgets + +A powerful, customizable widgets system for the Leon AI dashboard that allows users to add interactive UI elements to personalize their experience. + +--- + +## 🌟 Features + +- **6 Built-in Widget Types** - Clock, Weather, Notes, Quick Links, Stats, and Todo List +- **Custom Widgets** - Create your own with HTML/CSS/JavaScript +- **Drag & Drop** - Reorder widgets easily +- **Persistent Storage** - Widgets are saved via API +- **Responsive Design** - Works on desktop, tablet, and mobile +- **Real-time Updates** - Live clock and interactive elements +- **Beginner-Friendly** - Easy to use with UI or code + +--- + +## 📦 What's Included + +### Backend (Server) +- **File:** `server/src/core/http-server/old-server.js` +- **Features:** + - RESTful API endpoints for widgets (GET, POST, PUT, DELETE) + - In-memory storage (easily replaceable with database) + - User-specific widget management + +### Frontend (Client) +- **Widget Manager:** `app/src/js/widget-manager.js` + - Core widget management logic + - Rendering engine for all widget types + - Event handling and drag-drop functionality + +- **Widget Examples:** `app/src/js/widget-examples.js` + - Pre-built widget configurations + - Beginner-friendly examples + - Usage documentation + +- **Styles:** `app/src/css/widgets.scss` + - Beautiful, modern widget styling + - Responsive grid layout + - Glassmorphism effects + - Dark theme compatible + +- **HTML:** `app/src/index.html` + - Widgets section container + - Add widget dialog + - Toggle button integration + +--- + +## 🚀 Quick Start + +### For Users (UI Method) + +1. Start Leon AI server +2. Open web interface +3. Click **"Widgets"** button +4. Click **"Add Widget"** +5. Choose a widget type +6. Done! 🎉 + +### For Developers (Code Method) + +```javascript +import WidgetManager from './widget-manager' + +// Initialize +const widgetManager = new WidgetManager() +await widgetManager.init('widgets-container') + +// Add a widget +await widgetManager.addWidget({ + type: 'clock', + title: 'My Clock', + position: 0 +}) +``` + +--- + +## 📋 Available Widget Types + +| Widget | Icon | Description | Use Case | +|--------|------|-------------|----------| +| **Clock** | 🕐 | Real-time clock with date | Time tracking, timezone display | +| **Weather** | 🌤️ | Weather information | Quick weather check | +| **Notes** | 📝 | Simple notepad | Quick notes, reminders | +| **Quick Links** | 🔗 | Bookmarked links | Favorite websites, resources | +| **Stats** | 📊 | Display metrics | KPIs, progress tracking | +| **Todo** | ✅ | Task management | Daily tasks, checklists | +| **Custom** | 🎨 | Your own HTML | Unlimited possibilities | + +--- + +## 🎯 Widget Placement + +### Grid Layout +Widgets are displayed in a responsive grid: +- **Desktop:** 3-4 columns (auto-fit, min 280px) +- **Tablet:** 2 columns +- **Mobile:** 1 column (stacked) + +### Positioning +- Widgets have a `position` property (0, 1, 2, ...) +- Drag & drop to reorder +- Automatically saved + +--- + +## 🛠️ API Endpoints + +### GET `/api/widgets` +Get all widgets for a user +```javascript +GET /api/widgets?userId=default +``` + +### POST `/api/widgets` +Create a new widget +```javascript +POST /api/widgets +Body: { userId: 'default', widget: {...} } +``` + +### PUT `/api/widgets/:id` +Update a widget +```javascript +PUT /api/widgets/:id +Body: { userId: 'default', widget: {...} } +``` + +### DELETE `/api/widgets/:id` +Delete a widget +```javascript +DELETE /api/widgets/:id?userId=default +``` + +--- + +## 📖 Documentation + +- **Quick Start Guide:** `WIDGETS_QUICKSTART.md` - For beginners +- **Full Documentation:** `WIDGETS_GUIDE.md` - Comprehensive guide +- **Code Examples:** `app/src/js/widget-examples.js` - Ready-to-use examples + +--- + +## 🎓 Example Use Cases + +### Personal Dashboard +- Clock for time tracking +- Notes for quick thoughts +- Todo list for daily tasks + +### Developer Dashboard +- Quick links to GitHub, docs, tools +- Stats for project metrics +- Notes for code snippets + +### Student Dashboard +- Study task list +- Quick links to course materials +- Notes for important concepts + +### Business Dashboard +- Stats for KPIs +- Quick links to tools +- Todo for action items + +--- + +## 🔧 Customization + +### Create Custom Widget Type + +1. **Add renderer** in `widget-manager.js` +2. **Add styles** in `widgets.scss` +3. **Add event listeners** if needed +4. **Use your widget** + +Example: +```javascript +// 1. Add to renderWidgetContent() +case 'my-widget': + return this.renderMyWidget(widget) + +// 2. Create render function +renderMyWidget(widget) { + return `
${widget.data.content}
` +} + +// 3. Add styles +.my-widget { + padding: 1rem; + color: #fff; +} + +// 4. Use it +await widgetManager.addWidget({ + type: 'my-widget', + title: 'My Widget', + data: { content: 'Hello!' } +}) +``` + +--- + +## 💡 Best Practices + +### Design +- Keep widgets focused on one purpose +- Use clear, descriptive titles +- Provide visual feedback +- Make content scannable + +### Performance +- Avoid heavy computations +- Limit number of widgets (6-12 recommended) +- Clean up event listeners +- Use efficient updates + +### User Experience +- Provide default data +- Show loading states +- Handle errors gracefully +- Make widgets responsive + +--- + +## 🔄 Persistence + +### Current Implementation +- In-memory storage using JavaScript `Map` +- Data persists during server session +- Lost on server restart + +### Database Integration (Recommended) + +Replace the `widgetsStore` in `old-server.js`: + +```javascript +// MongoDB example +const widgetsStore = { + async get(userId) { + return await db.collection('widgets').findOne({ userId }) + }, + async set(userId, widgets) { + await db.collection('widgets').updateOne( + { userId }, + { $set: { widgets } }, + { upsert: true } + ) + } +} +``` + +--- + +## 🎨 Styling + +### Theme +- Dark theme by default +- Glassmorphism effects +- Neon accents (pink/blue) +- Smooth animations + +### Customization +All styles in `app/src/css/widgets.scss`: +- Widget container layout +- Individual widget styles +- Responsive breakpoints +- Color schemes + +--- + +## 🐛 Troubleshooting + +### Common Issues + +**Widgets not appearing?** +- Check if widgets section is visible (click "Widgets" button) +- Check browser console for errors +- Verify widget-manager is initialized + +**Styles not applied?** +- Ensure `widgets.scss` is imported in `style.scss` +- Clear browser cache +- Rebuild application + +**Data not persisting?** +- Check server API endpoints +- Verify network requests +- Check server console +- Implement database storage for permanent persistence + +--- + +## 🚀 Future Enhancements + +### Potential Features +- Widget marketplace +- More widget types (calendar, RSS feed, etc.) +- Widget themes +- Widget sharing +- Advanced settings dialog +- Widget templates +- Import/export widgets +- Widget analytics + +### API Integrations +- Real weather data +- Stock prices +- News feeds +- Social media +- Calendar events +- Email notifications + +--- + +## 📝 File Structure + +``` +Leon-AI/ +├── server/src/core/http-server/ +│ └── old-server.js # Widget API endpoints +├── app/src/ +│ ├── js/ +│ │ ├── widget-manager.js # Core widget logic +│ │ ├── widget-examples.js # Example widgets +│ │ └── main.js # Integration +│ ├── css/ +│ │ ├── widgets.scss # Widget styles +│ │ └── style.scss # Main styles (imports widgets) +│ └── index.html # HTML structure +├── WIDGETS_README.md # This file +├── WIDGETS_QUICKSTART.md # Beginner guide +└── WIDGETS_GUIDE.md # Full documentation +``` + +--- + +## 🤝 Contributing + +Want to improve the widgets system? + +1. Fork the repository +2. Create a feature branch +3. Add your improvements +4. Write documentation +5. Submit a pull request + +### Ideas for Contributions +- New widget types +- Better styling +- Performance improvements +- Database integration examples +- API integration examples +- More documentation + +--- + +## 📄 License + +Part of Leon AI - follows the same license as the main project. + +--- + +## 🎉 Get Started! + +Ready to customize your dashboard? + +1. **Beginners:** Start with `WIDGETS_QUICKSTART.md` +2. **Developers:** Check out `WIDGETS_GUIDE.md` +3. **Examples:** Look at `app/src/js/widget-examples.js` + +**Questions?** Join the Leon AI Discord community! + +--- + +## 📞 Support + +- **Documentation:** See `WIDGETS_GUIDE.md` +- **Examples:** See `WIDGETS_QUICKSTART.md` +- **Community:** https://discord.gg/MNQqqKg +- **Issues:** GitHub Issues + +--- + +**Made with ❤️ for the Leon AI community** diff --git a/app/src/css/style.scss b/app/src/css/style.scss index b83cc42..558654b 100644 --- a/app/src/css/style.scss +++ b/app/src/css/style.scss @@ -6,6 +6,7 @@ @import '@fontsource/source-sans-pro/900.css'; @import 'remixicon/fonts/remixicon.css'; @import 'voice-energy/main.scss'; +@import 'widgets.scss'; html, body, diff --git a/app/src/css/widgets.scss b/app/src/css/widgets.scss new file mode 100644 index 0000000..9093390 --- /dev/null +++ b/app/src/css/widgets.scss @@ -0,0 +1,532 @@ +/** + * Widget Styles + * Customizable dashboard widgets styling + */ + +/* Widgets Container */ +#widgets-container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; + padding: 1.5rem; + max-width: 1400px; + margin: 0 auto; + + @media (max-width: 768px) { + grid-template-columns: 1fr; + padding: 1rem; + gap: 1rem; + } +} + +/* Widget Base Styles */ +.widget { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 0; + backdrop-filter: blur(10px); + transition: all 0.3s ease; + cursor: move; + overflow: hidden; + + &:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + + &.dragging { + opacity: 0.5; + transform: scale(0.95); + } +} + +/* Widget Header */ +.widget-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.25rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.03); +} + +.widget-title { + margin: 0; + font-size: 1rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.9); + letter-spacing: 0.5px; +} + +.widget-controls { + display: flex; + gap: 0.5rem; +} + +.widget-btn { + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 6px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: rgba(255, 255, 255, 0.7); + font-size: 1.1rem; + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.2); + color: rgba(255, 255, 255, 1); + } + + &.widget-close:hover { + background: rgba(237, 41, 122, 0.3); + color: #ed297a; + } +} + +/* Widget Body */ +.widget-body { + padding: 1.25rem; + color: rgba(255, 255, 255, 0.85); + min-height: 120px; +} + +/* Clock Widget */ +.clock-widget { + text-align: center; + padding: 1rem 0; +} + +.clock-time { + font-size: 3rem; + font-weight: 700; + color: rgba(255, 255, 255, 0.95); + margin-bottom: 0.5rem; + font-variant-numeric: tabular-nums; + letter-spacing: 2px; +} + +.clock-date { + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.6); + font-weight: 400; +} + +/* Weather Widget */ +.weather-widget { + text-align: center; + padding: 1rem 0; +} + +.weather-location { + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.6); + margin-bottom: 0.75rem; +} + +.weather-temp { + font-size: 3.5rem; + font-weight: 700; + color: rgba(255, 255, 255, 0.95); + margin-bottom: 0.5rem; +} + +.weather-condition { + font-size: 1rem; + color: rgba(255, 255, 255, 0.7); + text-transform: capitalize; +} + +/* Notes Widget */ +.notes-widget { + height: 100%; +} + +.notes-input { + width: 100%; + min-height: 150px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 0.75rem; + color: rgba(255, 255, 255, 0.9); + font-family: inherit; + font-size: 0.9rem; + resize: vertical; + transition: all 0.2s ease; + + &:focus { + outline: none; + border-color: rgba(28, 117, 219, 0.5); + background: rgba(0, 0, 0, 0.3); + } + + &::placeholder { + color: rgba(255, 255, 255, 0.3); + } +} + +/* Quick Links Widget */ +.quick-links-widget { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.quick-link { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + text-decoration: none; + color: rgba(255, 255, 255, 0.85); + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(28, 117, 219, 0.5); + transform: translateX(4px); + } +} + +.quick-link-icon { + font-size: 1.5rem; + flex-shrink: 0; +} + +.quick-link-title { + font-size: 0.9rem; + font-weight: 500; +} + +/* Stats Widget */ +.stats-widget { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); + gap: 1rem; +} + +.stat-item { + text-align: center; + padding: 1rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + color: rgba(28, 117, 219, 0.9); + margin-bottom: 0.25rem; +} + +.stat-label { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.6); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Todo Widget */ +.todo-widget { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.todo-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + max-height: 300px; + overflow-y: auto; + padding-right: 0.5rem; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 3px; + } + + &::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; + + &:hover { + background: rgba(255, 255, 255, 0.3); + } + } +} + +.todo-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + transition: all 0.2s ease; + + &.completed { + opacity: 0.6; + + .todo-text { + text-decoration: line-through; + } + } + + input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + accent-color: #1c75db; + } +} + +.todo-text { + flex: 1; + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.85); +} + +.todo-delete { + background: rgba(237, 41, 122, 0.2); + border: none; + border-radius: 4px; + width: 24px; + height: 24px; + color: #ed297a; + cursor: pointer; + font-size: 1.2rem; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + + &:hover { + background: rgba(237, 41, 122, 0.4); + } +} + +.todo-input-container { + display: flex; + gap: 0.5rem; +} + +.todo-input { + flex: 1; + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 0.75rem; + color: rgba(255, 255, 255, 0.9); + font-family: inherit; + font-size: 0.9rem; + + &:focus { + outline: none; + border-color: rgba(28, 117, 219, 0.5); + } + + &::placeholder { + color: rgba(255, 255, 255, 0.3); + } +} + +.todo-add-btn { + background: rgba(28, 117, 219, 0.3); + border: 1px solid rgba(28, 117, 219, 0.5); + border-radius: 8px; + width: 40px; + color: #1c75db; + cursor: pointer; + font-size: 1.5rem; + transition: all 0.2s ease; + + &:hover { + background: rgba(28, 117, 219, 0.5); + color: #fff; + } +} + +/* Empty State */ +.empty-state { + text-align: center; + color: rgba(255, 255, 255, 0.4); + font-size: 0.9rem; + padding: 2rem 1rem; +} + +/* Add Widget Dialog */ +#add-widget-dialog { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + z-index: 1000; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.dialog-content { + background: rgba(20, 20, 30, 0.95); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 16px; + padding: 2rem; + max-width: 600px; + width: 100%; + max-height: 80vh; + overflow-y: auto; +} + +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + + h2 { + margin: 0; + color: rgba(255, 255, 255, 0.95); + font-size: 1.5rem; + } +} + +.dialog-close { + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + width: 36px; + height: 36px; + color: rgba(255, 255, 255, 0.7); + cursor: pointer; + font-size: 1.5rem; + transition: all 0.2s ease; + + &:hover { + background: rgba(237, 41, 122, 0.3); + color: #ed297a; + } +} + +.widget-types { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 1rem; +} + +.widget-type-card { + background: rgba(255, 255, 255, 0.05); + border: 2px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 1.5rem 1rem; + text-align: center; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(28, 117, 219, 0.5); + transform: translateY(-4px); + } + + .widget-type-icon { + font-size: 2.5rem; + margin-bottom: 0.75rem; + } + + .widget-type-name { + font-size: 0.9rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.9); + } + + .widget-type-desc { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.5); + margin-top: 0.5rem; + } +} + +/* Widget Controls Bar */ +#widgets-controls { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + background: rgba(255, 255, 255, 0.03); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + + h2 { + margin: 0; + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9); + } +} + +#add-widget-btn { + background: rgba(28, 117, 219, 0.3); + border: 1px solid rgba(28, 117, 219, 0.5); + border-radius: 8px; + padding: 0.75rem 1.5rem; + color: #1c75db; + cursor: pointer; + font-size: 0.9rem; + font-weight: 600; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.5rem; + + &:hover { + background: rgba(28, 117, 219, 0.5); + color: #fff; + transform: translateY(-2px); + } + + &::before { + content: '+'; + font-size: 1.25rem; + } +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .widget-title { + font-size: 0.9rem; + } + + .clock-time { + font-size: 2.5rem; + } + + .weather-temp { + font-size: 3rem; + } + + .dialog-content { + padding: 1.5rem; + } + + .widget-types { + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + } +} diff --git a/app/src/index.html b/app/src/index.html index 454c08a..68145db 100644 --- a/app/src/index.html +++ b/app/src/index.html @@ -300,7 +300,60 @@
+
+ + +
+
+

Dashboard Widgets

+ +
+
+
+ + +
+
+
+

Add Widget

+ +
+
+
+
🕐
+
Clock
+
Display current time and date
+
+
+
🌤️
+
Weather
+
Show weather information
+
+
+
📝
+
Notes
+
Quick notes and reminders
+
+
+
🔗
+
Quick Links
+
Favorite links and bookmarks
+
+
+
📊
+
Stats
+
Display statistics
+
+
+
+
Todo List
+
Manage your tasks
+
+
+
+
+

You can start to interact with Leon, don't be shy. diff --git a/app/src/js/main.js b/app/src/js/main.js index ba63f54..688a792 100644 --- a/app/src/js/main.js +++ b/app/src/js/main.js @@ -8,6 +8,7 @@ import Client from './client' // import Recorder from './recorder' // import listener from './listener' import { onkeydownstartrecording, onkeydowninput } from './onkeydown' +import WidgetManager from './widget-manager' const config = { app: 'webapp', @@ -60,6 +61,72 @@ document.addEventListener('DOMContentLoaded', async () => { client.updateMood(window.leonConfigInfo.mood) client.init() + // Initialize Widget Manager + const widgetManager = new WidgetManager() + await widgetManager.init('widgets-container') + + // Toggle widgets section + const toggleWidgetsBtn = document.querySelector('#toggle-widgets') + const widgetsSection = document.querySelector('#widgets-section') + const feedSection = document.querySelector('#feed') + + toggleWidgetsBtn.addEventListener('click', () => { + const isHidden = widgetsSection.classList.contains('hide') + if (isHidden) { + widgetsSection.classList.remove('hide') + feedSection.classList.add('hide') + toggleWidgetsBtn.textContent = 'Chat' + } else { + widgetsSection.classList.add('hide') + feedSection.classList.remove('hide') + toggleWidgetsBtn.textContent = 'Widgets' + } + }) + + // Add widget dialog handlers + const addWidgetDialog = document.querySelector('#add-widget-dialog') + const closeDialogBtn = document.querySelector('#close-dialog') + + closeDialogBtn.addEventListener('click', () => { + widgetManager.hideAddWidgetDialog() + }) + + // Handle widget type selection + document.querySelectorAll('.widget-type-card').forEach(card => { + card.addEventListener('click', async () => { + const widgetType = card.dataset.widgetType + const widgetConfig = { + type: widgetType, + title: card.querySelector('.widget-type-name').textContent, + position: widgetManager.widgets.length, + config: {}, + data: {} + } + + // Add default data for specific widget types + if (widgetType === 'quick-links') { + widgetConfig.data.links = [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Stack Overflow', url: 'https://stackoverflow.com', icon: '📚' } + ] + } else if (widgetType === 'stats') { + widgetConfig.data.stats = [ + { label: 'Tasks', value: '12' }, + { label: 'Completed', value: '8' } + ] + } else if (widgetType === 'weather') { + widgetConfig.data = { + temp: '72', + condition: 'Sunny', + location: 'Your City' + } + } + + await widgetManager.addWidget(widgetConfig) + widgetManager.hideAddWidgetDialog() + }) + }) + infoButton.addEventListener('click', () => { alert(JSON.stringify(infoToDisplay, null, 2)) }) diff --git a/app/src/js/widget-examples.js b/app/src/js/widget-examples.js new file mode 100644 index 0000000..3fd4bb5 --- /dev/null +++ b/app/src/js/widget-examples.js @@ -0,0 +1,235 @@ +/** + * Widget Examples for Beginners + * Simple, easy-to-understand widget implementations + */ + +/** + * EXAMPLE 1: Simple Clock Widget + * Shows current time that updates every second + */ +export const createSimpleClockWidget = () => { + return { + type: 'clock', + title: 'Clock', + config: { + showSeconds: true, + locale: 'en-US' + }, + position: 0 + } +} + +/** + * EXAMPLE 2: Personal Notes Widget + * A simple notepad for quick notes + */ +export const createNotesWidget = () => { + return { + type: 'notes', + title: 'My Notes', + data: { + notes: 'Write your notes here...' + }, + position: 1 + } +} + +/** + * EXAMPLE 3: Todo List Widget + * Manage your daily tasks + */ +export const createTodoWidget = () => { + return { + type: 'todo', + title: 'My Tasks', + data: { + todos: [ + { text: 'Learn about Leon AI', completed: false }, + { text: 'Create custom widgets', completed: false }, + { text: 'Explore widget features', completed: false } + ] + }, + position: 2 + } +} + +/** + * EXAMPLE 4: Quick Links Widget + * Bookmark your favorite websites + */ +export const createQuickLinksWidget = () => { + return { + type: 'quick-links', + title: 'Quick Links', + data: { + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Stack Overflow', url: 'https://stackoverflow.com', icon: '📚' }, + { title: 'MDN Docs', url: 'https://developer.mozilla.org', icon: '📖' }, + { title: 'Leon AI', url: 'https://getleon.ai', icon: '🤖' } + ] + }, + position: 3 + } +} + +/** + * EXAMPLE 5: Stats Widget + * Display key metrics and statistics + */ +export const createStatsWidget = () => { + return { + type: 'stats', + title: 'Dashboard Stats', + data: { + stats: [ + { label: 'Tasks', value: '12' }, + { label: 'Completed', value: '8' }, + { label: 'Pending', value: '4' } + ] + }, + position: 4 + } +} + +/** + * EXAMPLE 6: Weather Widget + * Display weather information + */ +export const createWeatherWidget = (location = 'Your City') => { + return { + type: 'weather', + title: 'Weather', + config: { + location: location + }, + data: { + temp: '72', + condition: 'Sunny', + location: location + }, + position: 5 + } +} + +/** + * EXAMPLE 7: Custom HTML Widget + * Create your own custom widget with HTML + */ +export const createCustomWidget = (title, htmlContent) => { + return { + type: 'custom', + title: title, + content: htmlContent, + position: 6 + } +} + +/** + * HOW TO USE THESE EXAMPLES: + * + * 1. Import the widget manager in your code: + * import WidgetManager from './widget-manager' + * import { createSimpleClockWidget } from './widget-examples' + * + * 2. Initialize the widget manager: + * const widgetManager = new WidgetManager() + * await widgetManager.init('widgets-container') + * + * 3. Add a widget using the examples: + * const clockWidget = createSimpleClockWidget() + * await widgetManager.addWidget(clockWidget) + * + * 4. Or create your own custom widget: + * const myWidget = { + * type: 'custom', + * title: 'My Custom Widget', + * content: '

Hello World!

This is my custom widget

', + * position: 0 + * } + * await widgetManager.addWidget(myWidget) + */ + +/** + * BEGINNER TIPS: + * + * 1. Widget Types: + * - 'clock': Shows current time + * - 'weather': Displays weather info + * - 'notes': Simple notepad + * - 'quick-links': Bookmark links + * - 'stats': Show statistics + * - 'todo': Task list + * - 'custom': Your own HTML + * + * 2. Widget Structure: + * { + * type: 'widget-type', // Required: Type of widget + * title: 'Widget Title', // Required: Display title + * config: { ... }, // Optional: Widget settings + * data: { ... }, // Optional: Widget data + * position: 0 // Optional: Display order + * } + * + * 3. Updating Widget Data: + * await widgetManager.updateWidget(widgetId, { + * data: { newData: 'value' } + * }) + * + * 4. Deleting a Widget: + * await widgetManager.deleteWidget(widgetId) + * + * 5. Widget Positions: + * - Widgets are displayed in a grid layout + * - Position determines the order (0, 1, 2, ...) + * - You can drag and drop to reorder + */ + +/** + * ADVANCED: Creating a Custom Widget Type + * + * To create a completely new widget type: + * + * 1. Add a new case in widget-manager.js renderWidgetContent(): + * case 'my-widget': + * return this.renderMyWidget(widget) + * + * 2. Create the render function: + * renderMyWidget(widget) { + * return ` + *
+ *

${widget.data.title}

+ *

${widget.data.content}

+ *
+ * ` + * } + * + * 3. Add styles in widgets.scss: + * .my-widget { + * padding: 1rem; + * h3 { color: #fff; } + * } + * + * 4. Use your new widget: + * const myCustomWidget = { + * type: 'my-widget', + * title: 'My Widget', + * data: { + * title: 'Hello', + * content: 'This is my custom widget type!' + * } + * } + */ + +// Export all examples as a collection +export const widgetExamples = { + clock: createSimpleClockWidget, + notes: createNotesWidget, + todo: createTodoWidget, + quickLinks: createQuickLinksWidget, + stats: createStatsWidget, + weather: createWeatherWidget, + custom: createCustomWidget +} + +export default widgetExamples diff --git a/app/src/js/widget-manager.js b/app/src/js/widget-manager.js new file mode 100644 index 0000000..8c748d3 --- /dev/null +++ b/app/src/js/widget-manager.js @@ -0,0 +1,483 @@ +/** + * Widget Manager - Handles dashboard widgets + * Provides functionality to create, manage, and render customizable widgets + */ + +class WidgetManager { + constructor() { + this.widgets = [] + this.container = null + this.apiBaseUrl = '/api/widgets' + this.userId = 'default' + this.draggedWidget = null + } + + /** + * Initialize the widget manager + */ + async init(containerId = 'widgets-container') { + this.container = document.getElementById(containerId) + if (!this.container) { + console.error(`Widget container #${containerId} not found`) + return + } + + await this.loadWidgets() + this.renderWidgets() + this.setupEventListeners() + } + + /** + * Load widgets from the server + */ + async loadWidgets() { + try { + const response = await fetch(`${this.apiBaseUrl}?userId=${this.userId}`) + const data = await response.json() + if (data.success) { + this.widgets = data.widgets + } + } catch (error) { + console.error('Failed to load widgets:', error) + } + } + + /** + * Add a new widget + */ + async addWidget(widgetConfig) { + try { + const response = await fetch(this.apiBaseUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userId, + widget: widgetConfig + }) + }) + const data = await response.json() + if (data.success) { + this.widgets.push(data.widget) + this.renderWidgets() + return data.widget + } + } catch (error) { + console.error('Failed to add widget:', error) + } + } + + /** + * Update an existing widget + */ + async updateWidget(widgetId, updates) { + try { + const response = await fetch(`${this.apiBaseUrl}/${widgetId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userId, + widget: updates + }) + }) + const data = await response.json() + if (data.success) { + const index = this.widgets.findIndex(w => w.id === widgetId) + if (index !== -1) { + this.widgets[index] = data.widget + this.renderWidgets() + } + } + } catch (error) { + console.error('Failed to update widget:', error) + } + } + + /** + * Delete a widget + */ + async deleteWidget(widgetId) { + try { + const response = await fetch(`${this.apiBaseUrl}/${widgetId}?userId=${this.userId}`, { + method: 'DELETE' + }) + const data = await response.json() + if (data.success) { + this.widgets = this.widgets.filter(w => w.id !== widgetId) + this.renderWidgets() + } + } catch (error) { + console.error('Failed to delete widget:', error) + } + } + + /** + * Render all widgets + */ + renderWidgets() { + if (!this.container) return + + this.container.innerHTML = '' + + // Sort widgets by position + const sortedWidgets = [...this.widgets].sort((a, b) => { + return (a.position || 0) - (b.position || 0) + }) + + sortedWidgets.forEach(widget => { + const widgetElement = this.createWidgetElement(widget) + this.container.appendChild(widgetElement) + }) + } + + /** + * Create a widget DOM element + */ + createWidgetElement(widget) { + const widgetDiv = document.createElement('div') + widgetDiv.className = `widget widget-${widget.type}` + widgetDiv.id = widget.id + widgetDiv.draggable = true + widgetDiv.dataset.widgetId = widget.id + + // Widget header + const header = document.createElement('div') + header.className = 'widget-header' + header.innerHTML = ` +

${widget.title || 'Widget'}

+
+ + + +
+ ` + + // Widget body + const body = document.createElement('div') + body.className = 'widget-body' + body.innerHTML = this.renderWidgetContent(widget) + + widgetDiv.appendChild(header) + widgetDiv.appendChild(body) + + // Add event listeners + this.attachWidgetEventListeners(widgetDiv, widget) + + return widgetDiv + } + + /** + * Render widget content based on type + */ + renderWidgetContent(widget) { + switch (widget.type) { + case 'clock': + return this.renderClockWidget(widget) + case 'weather': + return this.renderWeatherWidget(widget) + case 'notes': + return this.renderNotesWidget(widget) + case 'quick-links': + return this.renderQuickLinksWidget(widget) + case 'stats': + return this.renderStatsWidget(widget) + case 'todo': + return this.renderTodoWidget(widget) + case 'custom': + return widget.content || '

Custom widget content

' + default: + return '

Unknown widget type

' + } + } + + /** + * Clock widget renderer + */ + renderClockWidget(widget) { + const now = new Date() + const timeString = now.toLocaleTimeString(widget.config?.locale || 'en-US', { + hour: '2-digit', + minute: '2-digit', + second: widget.config?.showSeconds ? '2-digit' : undefined + }) + const dateString = now.toLocaleDateString(widget.config?.locale || 'en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }) + + return ` +
+
${timeString}
+
${dateString}
+
+ ` + } + + /** + * Weather widget renderer + */ + renderWeatherWidget(widget) { + const weather = widget.data || { + temp: '--', + condition: 'Loading...', + location: widget.config?.location || 'Unknown' + } + + return ` +
+
${weather.location}
+
${weather.temp}°
+
${weather.condition}
+
+ ` + } + + /** + * Notes widget renderer + */ + renderNotesWidget(widget) { + const notes = widget.data?.notes || '' + return ` +
+ +
+ ` + } + + /** + * Quick links widget renderer + */ + renderQuickLinksWidget(widget) { + const links = widget.data?.links || [] + const linksHtml = links.map(link => ` + + ${link.icon || '🔗'} + ${link.title} + + `).join('') + + return ` + + ` + } + + /** + * Stats widget renderer + */ + renderStatsWidget(widget) { + const stats = widget.data?.stats || [] + const statsHtml = stats.map(stat => ` +
+
${stat.value}
+
${stat.label}
+
+ `).join('') + + return ` +
+ ${statsHtml || '

No stats available

'} +
+ ` + } + + /** + * Todo widget renderer + */ + renderTodoWidget(widget) { + const todos = widget.data?.todos || [] + const todosHtml = todos.map((todo, index) => ` +
+ + ${todo.text} + +
+ `).join('') + + return ` +
+
+ ${todosHtml || '

No todos yet

'} +
+
+ + +
+
+ ` + } + + /** + * Attach event listeners to widget + */ + attachWidgetEventListeners(widgetElement, widget) { + // Close button + const closeBtn = widgetElement.querySelector('.widget-close') + closeBtn?.addEventListener('click', () => { + this.deleteWidget(widget.id) + }) + + // Refresh button + const refreshBtn = widgetElement.querySelector('.widget-refresh') + refreshBtn?.addEventListener('click', () => { + this.refreshWidget(widget.id) + }) + + // Drag events + widgetElement.addEventListener('dragstart', (e) => { + this.draggedWidget = widget.id + widgetElement.classList.add('dragging') + }) + + widgetElement.addEventListener('dragend', (e) => { + widgetElement.classList.remove('dragging') + this.draggedWidget = null + }) + + widgetElement.addEventListener('dragover', (e) => { + e.preventDefault() + }) + + widgetElement.addEventListener('drop', (e) => { + e.preventDefault() + if (this.draggedWidget && this.draggedWidget !== widget.id) { + this.swapWidgets(this.draggedWidget, widget.id) + } + }) + + // Widget-specific event listeners + this.attachWidgetTypeListeners(widgetElement, widget) + } + + /** + * Attach widget-type specific event listeners + */ + attachWidgetTypeListeners(widgetElement, widget) { + switch (widget.type) { + case 'notes': + const notesInput = widgetElement.querySelector('.notes-input') + notesInput?.addEventListener('blur', (e) => { + this.updateWidget(widget.id, { + data: { notes: e.target.value } + }) + }) + break + + case 'todo': + // Add todo + const addBtn = widgetElement.querySelector('.todo-add-btn') + const todoInput = widgetElement.querySelector('.todo-input') + addBtn?.addEventListener('click', () => { + if (todoInput.value.trim()) { + const todos = widget.data?.todos || [] + todos.push({ text: todoInput.value, completed: false }) + this.updateWidget(widget.id, { data: { todos } }) + todoInput.value = '' + } + }) + + // Toggle todo + widgetElement.querySelectorAll('.todo-item input[type="checkbox"]').forEach(checkbox => { + checkbox.addEventListener('change', (e) => { + const index = parseInt(e.target.dataset.index) + const todos = widget.data?.todos || [] + todos[index].completed = e.target.checked + this.updateWidget(widget.id, { data: { todos } }) + }) + }) + + // Delete todo + widgetElement.querySelectorAll('.todo-delete').forEach(btn => { + btn.addEventListener('click', (e) => { + const index = parseInt(e.target.dataset.index) + const todos = widget.data?.todos || [] + todos.splice(index, 1) + this.updateWidget(widget.id, { data: { todos } }) + }) + }) + break + + case 'clock': + // Update clock every second + const updateClock = () => { + const clockWidget = widgetElement.querySelector('.clock-widget') + if (clockWidget) { + clockWidget.innerHTML = this.renderClockWidget(widget).match(/
([\s\S]*)<\/div>/)[1] + } + } + setInterval(updateClock, 1000) + break + } + } + + /** + * Refresh a specific widget + */ + async refreshWidget(widgetId) { + const widget = this.widgets.find(w => w.id === widgetId) + if (widget) { + // Refresh widget data based on type + if (widget.type === 'weather') { + // Fetch weather data (placeholder) + console.log('Refreshing weather widget...') + } + this.renderWidgets() + } + } + + /** + * Swap positions of two widgets + */ + swapWidgets(widgetId1, widgetId2) { + const widget1 = this.widgets.find(w => w.id === widgetId1) + const widget2 = this.widgets.find(w => w.id === widgetId2) + + if (widget1 && widget2) { + const pos1 = widget1.position || 0 + const pos2 = widget2.position || 0 + + this.updateWidget(widgetId1, { position: pos2 }) + this.updateWidget(widgetId2, { position: pos1 }) + } + } + + /** + * Setup global event listeners + */ + setupEventListeners() { + // Add widget button + const addWidgetBtn = document.getElementById('add-widget-btn') + addWidgetBtn?.addEventListener('click', () => { + this.showAddWidgetDialog() + }) + } + + /** + * Show dialog to add a new widget + */ + showAddWidgetDialog() { + const dialog = document.getElementById('add-widget-dialog') + if (dialog) { + dialog.style.display = 'flex' + } + } + + /** + * Hide add widget dialog + */ + hideAddWidgetDialog() { + const dialog = document.getElementById('add-widget-dialog') + if (dialog) { + dialog.style.display = 'none' + } + } +} + +// Export for use in other modules +export default WidgetManager diff --git a/server/src/core/http-server/old-server.js b/server/src/core/http-server/old-server.js index cc04755..f053259 100644 --- a/server/src/core/http-server/old-server.js +++ b/server/src/core/http-server/old-server.js @@ -337,6 +337,73 @@ server.bootstrap = async () => { server.fastify.register(infoPlugin, { apiVersion }) + // Widgets API endpoints + const widgetsStore = new Map() // In-memory storage for widgets (can be replaced with DB) + + server.fastify.get('/api/widgets', async (request, reply) => { + const userId = request.query.userId || 'default' + const userWidgets = widgetsStore.get(userId) || [] + reply.send({ + widgets: userWidgets, + success: true + }) + }) + + server.fastify.post('/api/widgets', async (request, reply) => { + const { userId = 'default', widget } = request.body + const userWidgets = widgetsStore.get(userId) || [] + + const newWidget = { + id: `widget-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + ...widget, + createdAt: new Date().toISOString() + } + + userWidgets.push(newWidget) + widgetsStore.set(userId, userWidgets) + + reply.send({ + widget: newWidget, + success: true + }) + }) + + server.fastify.put('/api/widgets/:id', async (request, reply) => { + const { id } = request.params + const { userId = 'default', widget } = request.body + const userWidgets = widgetsStore.get(userId) || [] + + const index = userWidgets.findIndex(w => w.id === id) + if (index !== -1) { + userWidgets[index] = { ...userWidgets[index], ...widget, updatedAt: new Date().toISOString() } + widgetsStore.set(userId, userWidgets) + reply.send({ + widget: userWidgets[index], + success: true + }) + } else { + reply.statusCode = 404 + reply.send({ + message: 'Widget not found', + success: false + }) + } + }) + + server.fastify.delete('/api/widgets/:id', async (request, reply) => { + const { id } = request.params + const userId = request.query.userId || 'default' + const userWidgets = widgetsStore.get(userId) || [] + + const filteredWidgets = userWidgets.filter(w => w.id !== id) + widgetsStore.set(userId, filteredWidgets) + + reply.send({ + success: true, + message: 'Widget deleted' + }) + }) + if (HAS_OVER_HTTP) { server.fastify.register((instance, opts, next) => { instance.addHook('preHandler', keyMidd)