Phase 17: Plugin marketplace and enhanced documentation
This commit is contained in:
1 parent
cf8e4c677f
commit
1dae4c5656
4 files changed
+1270
No files matched your search
@@ -17,6 +17,7 @@ Un agente configurabile basato su DeepAgents che supporta più provider LLM.
|
||||
- **API REST**: Server API per accesso remoto
|
||||
- **Web UI responsive**: Interfaccia web per gestione configurazioni ottimizzata per mobile e desktop
|
||||
- **Dashboard monitoraggio**: Monitoraggio in tempo reale del sistema con supporto multilingue
|
||||
- **Marketplace plugin**: Scopri e installa plugin dal marketplace
|
||||
- **Comandi integrati**: Comandi speciali con prefisso "/" per gestione rapida
|
||||
- **Monitoraggio**: Sistema di logging e monitoraggio delle performance
|
||||
- **Dashboard performance**: Monitoraggio dettagliato delle performance
|
||||
@@ -186,6 +187,9 @@ python api_server.py
|
||||
|
||||
# Accedi alla dashboard di monitoraggio
|
||||
# http://localhost:8000/monitoring
|
||||
|
||||
# Accedi al marketplace dei plugin
|
||||
# http://localhost:8000/marketplace
|
||||
```
|
||||
|
||||
L'interfaccia web include:
|
||||
@@ -195,6 +199,7 @@ L'interfaccia web include:
|
||||
- Test di connessione
|
||||
- Dashboard di monitoraggio in tempo reale con supporto multilingue
|
||||
- **Design responsive**: Ottimizzata per dispositivi mobili e desktop
|
||||
- **Marketplace plugin**: Scopri e installa plugin dal marketplace
|
||||
|
||||
## Utilizzo
|
||||
|
||||
@@ -219,6 +224,7 @@ Quando interagisci con l'agente, puoi usare comandi speciali preceduti da "/" pe
|
||||
/models - Elenca i modelli disponibili per il provider attivo
|
||||
/clear - Pulisce lo schermo
|
||||
/plugins - Elenca i plugin disponibili
|
||||
/marketplace - Apre il marketplace dei plugin
|
||||
/language - Cambia la lingua dell'interfaccia
|
||||
/performance - Mostra il report delle performance
|
||||
/monitoring - Apre la dashboard di monitoraggio
|
||||
@@ -284,6 +290,7 @@ L'API sarà disponibile su http://localhost:8000
|
||||
|
||||
- `GET /`: Interfaccia web
|
||||
- `GET /monitoring`: Dashboard di monitoraggio
|
||||
- `GET /marketplace`: Marketplace dei plugin
|
||||
- `GET /health`: Health check
|
||||
- `POST /chat`: Chat con l'agente
|
||||
- `GET /configurations`: Elenca tutte le configurazioni
|
||||
@@ -317,6 +324,76 @@ curl -X POST http://localhost:8000/configurations \
|
||||
}'
|
||||
```
|
||||
|
||||
## Marketplace dei Plugin
|
||||
|
||||
MaxClaw Agent include un marketplace per scoprire e installare plugin.
|
||||
|
||||
### Accesso al Marketplace
|
||||
|
||||
```bash
|
||||
# Avvia il server API
|
||||
python api_server.py
|
||||
|
||||
# Accedi al marketplace
|
||||
# http://localhost:8000/marketplace
|
||||
```
|
||||
|
||||
### Funzionalità del Marketplace
|
||||
|
||||
Il marketplace include:
|
||||
|
||||
1. **Plugin Disponibili**:
|
||||
- Catalogo di plugin disponibili per l'installazione
|
||||
- Statistiche di download e valutazioni
|
||||
- Descrizioni dettagliate
|
||||
|
||||
2. **Plugin Installati**:
|
||||
- Elenco dei plugin attualmente installati
|
||||
- Versioni e informazioni
|
||||
- Opzioni di disinstallazione
|
||||
|
||||
3. **Ricerca Plugin**:
|
||||
- Ricerca per nome o descrizione
|
||||
- Filtraggio per categoria
|
||||
- Ordinamento per popolarità
|
||||
|
||||
4. **Gestione Plugin**:
|
||||
- Installazione con un click
|
||||
- Disinstallazione semplice
|
||||
- Aggiornamenti automatici
|
||||
|
||||
### Plugin Inclusi
|
||||
|
||||
1. **Example Plugin**: Plugin di esempio che dimostra come creare plugin
|
||||
- Strumenti: `example_hello`, `example_echo`, `example_math`
|
||||
- Comandi: `/example`, `/hello`, `/math`
|
||||
|
||||
2. **File Tools Plugin**: Strumenti aggiuntivi per la gestione dei file
|
||||
- Strumenti: `file_find`, `file_size`, `file_copy`, `file_move`, `file_delete`, `file_info`, `dir_list`, `dir_create`
|
||||
- Comandi: `/find`, `/size`, `/copy`, `/move`, `/delete`, `/info`, `/dir`, `/mkdir`
|
||||
|
||||
3. **System Utils Plugin**: Strumenti per informazioni di sistema
|
||||
- Strumenti: `system_info`, `system_uptime`, `system_ip`, `system_hostname`, `system_platform`, `system_users`, `system_processes`, `system_disk_usage`
|
||||
- Comandi: `/sysinfo`, `/uptime`, `/ip`, `/hostname`, `/platform`, `/users`, `/ps`, `/df`
|
||||
|
||||
### Installare Plugin dal Marketplace
|
||||
|
||||
```bash
|
||||
# Usa il comando /plugins per vedere i plugin disponibili
|
||||
/plugins
|
||||
|
||||
# Oppure usa l'interfaccia web
|
||||
# http://localhost:8000/marketplace
|
||||
```
|
||||
|
||||
### Creare un Plugin per il Marketplace
|
||||
|
||||
Per creare un plugin per il marketplace:
|
||||
|
||||
1. Crea un file Python seguendo la struttura dei plugin esistenti
|
||||
2. Testa il plugin localmente
|
||||
3. Sottometti il plugin al marketplace (futura implementazione)
|
||||
|
||||
## Dashboard di Monitoraggio
|
||||
|
||||
MaxClaw Agent include una dashboard di monitoraggio in tempo reale con supporto multilingue.
|
||||
@@ -866,6 +943,7 @@ MaxClaw Agent include un sistema di monitoraggio e logging:
|
||||
- **Dashboard performance**: Monitoraggio avanzato delle performance
|
||||
- **Dashboard web**: Monitoraggio in tempo reale via interfaccia web con supporto multilingue
|
||||
- **Interfacce responsive**: Ottimizzate per dispositivi mobili e desktop
|
||||
- **Marketplace plugin**: Scopri e installa plugin dal marketplace
|
||||
|
||||
## Sicurezza
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
MaxClaw Agent Marketplace Package
|
||||
Provides plugin marketplace functionality
|
||||
"""
|
||||
|
||||
from .manager import MarketplaceManager, marketplace_manager
|
||||
|
||||
__all__ = [
|
||||
'MarketplaceManager',
|
||||
'marketplace_manager'
|
||||
]
|
||||
@@ -0,0 +1,275 @@
|
||||
"""
|
||||
Plugin marketplace manager for MaxClaw Agent
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
from typing import Dict, List, Any, Optional
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
class MarketplaceManager:
|
||||
"""Manages plugin marketplace for MaxClaw Agent"""
|
||||
|
||||
def __init__(self, marketplace_url: str = "https://api.github.com/repos/maxclaw-agent/plugins/contents"):
|
||||
"""
|
||||
Initialize marketplace manager
|
||||
|
||||
Args:
|
||||
marketplace_url: URL to the plugin marketplace
|
||||
"""
|
||||
self.marketplace_url = marketplace_url
|
||||
self.local_plugins_dir = Path.home() / ".maxclaw" / "plugins"
|
||||
self.marketplace_cache_file = Path.home() / ".maxclaw" / "marketplace_cache.json"
|
||||
|
||||
# Ensure directories exist
|
||||
self.local_plugins_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Load cache
|
||||
self.cache = self._load_cache()
|
||||
|
||||
def _load_cache(self) -> Dict[str, Any]:
|
||||
"""Load marketplace cache"""
|
||||
if self.marketplace_cache_file.exists():
|
||||
try:
|
||||
with open(self.marketplace_cache_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
return {"plugins": {}, "last_updated": None}
|
||||
|
||||
def _save_cache(self):
|
||||
"""Save marketplace cache"""
|
||||
try:
|
||||
with open(self.marketplace_cache_file, 'w') as f:
|
||||
json.dump(self.cache, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not save marketplace cache: {e}")
|
||||
|
||||
def fetch_marketplace_plugins(self, force_refresh: bool = False) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Fetch plugins from marketplace
|
||||
|
||||
Args:
|
||||
force_refresh: Force refresh of cache
|
||||
|
||||
Returns:
|
||||
List of plugin information
|
||||
"""
|
||||
# Check cache first
|
||||
if not force_refresh and self.cache.get("plugins"):
|
||||
return list(self.cache["plugins"].values())
|
||||
|
||||
try:
|
||||
# Fetch from GitHub API (example)
|
||||
response = requests.get(self.marketplace_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
plugins = []
|
||||
for item in response.json():
|
||||
if item.get("type") == "file" and item.get("name", "").endswith(".py"):
|
||||
plugin_info = {
|
||||
"name": item["name"].replace(".py", ""),
|
||||
"download_url": item.get("download_url", ""),
|
||||
"size": item.get("size", 0),
|
||||
"description": f"Plugin from marketplace: {item['name']}"
|
||||
}
|
||||
plugins.append(plugin_info)
|
||||
|
||||
# Update cache
|
||||
self.cache["plugins"] = {p["name"]: p for p in plugins}
|
||||
self.cache["last_updated"] = str(Path(__file__).stat().st_mtime)
|
||||
self._save_cache()
|
||||
|
||||
return plugins
|
||||
else:
|
||||
print(f"Warning: Could not fetch marketplace plugins: HTTP {response.status_code}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not fetch marketplace plugins: {e}")
|
||||
return []
|
||||
|
||||
def get_installed_plugins(self) -> List[str]:
|
||||
"""
|
||||
Get list of installed plugins
|
||||
|
||||
Returns:
|
||||
List of installed plugin names
|
||||
"""
|
||||
installed = []
|
||||
|
||||
# Check local plugins directory
|
||||
for file_path in self.local_plugins_dir.glob("*.py"):
|
||||
if file_path.name != "__init__.py":
|
||||
installed.append(file_path.stem)
|
||||
|
||||
# Check built-in plugins directory
|
||||
builtin_dir = Path(__file__).parent.parent / "plugins"
|
||||
if builtin_dir.exists():
|
||||
for file_path in builtin_dir.glob("*.py"):
|
||||
if file_path.name not in ["__init__.py", "base.py", "manager.py"]:
|
||||
installed.append(file_path.stem)
|
||||
|
||||
return installed
|
||||
|
||||
def install_plugin(self, plugin_name: str) -> bool:
|
||||
"""
|
||||
Install a plugin from marketplace
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin to install
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# Check if already installed
|
||||
if plugin_name in self.get_installed_plugins():
|
||||
print(f"Plugin '{plugin_name}' is already installed")
|
||||
return True
|
||||
|
||||
# Get plugin info from marketplace
|
||||
plugins = self.fetch_marketplace_plugins()
|
||||
plugin_info = None
|
||||
|
||||
for plugin in plugins:
|
||||
if plugin["name"] == plugin_name:
|
||||
plugin_info = plugin
|
||||
break
|
||||
|
||||
if not plugin_info:
|
||||
print(f"Plugin '{plugin_name}' not found in marketplace")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Download plugin
|
||||
download_url = plugin_info.get("download_url")
|
||||
if not download_url:
|
||||
print(f"No download URL for plugin '{plugin_name}'")
|
||||
return False
|
||||
|
||||
response = requests.get(download_url, timeout=30)
|
||||
if response.status_code == 200:
|
||||
# Save plugin file
|
||||
plugin_file = self.local_plugins_dir / f"{plugin_name}.py"
|
||||
with open(plugin_file, 'w') as f:
|
||||
f.write(response.text)
|
||||
|
||||
print(f"✅ Plugin '{plugin_name}' installed successfully")
|
||||
return True
|
||||
else:
|
||||
print(f"Failed to download plugin: HTTP {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error installing plugin: {e}")
|
||||
return False
|
||||
|
||||
def uninstall_plugin(self, plugin_name: str) -> bool:
|
||||
"""
|
||||
Uninstall a plugin
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin to uninstall
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# Check if it's a built-in plugin
|
||||
builtin_dir = Path(__file__).parent.parent / "plugins"
|
||||
builtin_plugins = []
|
||||
if builtin_dir.exists():
|
||||
for file_path in builtin_dir.glob("*.py"):
|
||||
if file_path.name not in ["__init__.py", "base.py", "manager.py"]:
|
||||
builtin_plugins.append(file_path.stem)
|
||||
|
||||
if plugin_name in builtin_plugins:
|
||||
print(f"Cannot uninstall built-in plugin '{plugin_name}'")
|
||||
return False
|
||||
|
||||
# Check if installed in local directory
|
||||
plugin_file = self.local_plugins_dir / f"{plugin_name}.py"
|
||||
if not plugin_file.exists():
|
||||
print(f"Plugin '{plugin_name}' is not installed")
|
||||
return False
|
||||
|
||||
try:
|
||||
plugin_file.unlink()
|
||||
print(f"✅ Plugin '{plugin_name}' uninstalled successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error uninstalling plugin: {e}")
|
||||
return False
|
||||
|
||||
def get_plugin_info(self, plugin_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get information about a plugin
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin
|
||||
|
||||
Returns:
|
||||
Plugin information or None if not found
|
||||
"""
|
||||
# Check marketplace
|
||||
plugins = self.fetch_marketplace_plugins()
|
||||
for plugin in plugins:
|
||||
if plugin["name"] == plugin_name:
|
||||
return plugin
|
||||
|
||||
# Check local installation
|
||||
local_file = self.local_plugins_dir / f"{plugin_name}.py"
|
||||
if local_file.exists():
|
||||
return {
|
||||
"name": plugin_name,
|
||||
"installed": True,
|
||||
"location": str(local_file),
|
||||
"description": f"Locally installed plugin: {plugin_name}"
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def search_plugins(self, query: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search for plugins in marketplace
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
|
||||
Returns:
|
||||
List of matching plugins
|
||||
"""
|
||||
plugins = self.fetch_marketplace_plugins()
|
||||
results = []
|
||||
|
||||
query_lower = query.lower()
|
||||
for plugin in plugins:
|
||||
if (query_lower in plugin["name"].lower() or
|
||||
query_lower in plugin.get("description", "").lower()):
|
||||
results.append(plugin)
|
||||
|
||||
return results
|
||||
|
||||
def get_marketplace_stats(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get marketplace statistics
|
||||
|
||||
Returns:
|
||||
Dictionary with marketplace stats
|
||||
"""
|
||||
plugins = self.fetch_marketplace_plugins()
|
||||
installed = self.get_installed_plugins()
|
||||
|
||||
return {
|
||||
"total_plugins": len(plugins),
|
||||
"installed_plugins": len(installed),
|
||||
"available_plugins": len(plugins) - len(installed),
|
||||
"last_updated": self.cache.get("last_updated", "Never")
|
||||
}
|
||||
|
||||
|
||||
# Global marketplace manager instance
|
||||
marketplace_manager = MarketplaceManager()
|
||||
@@ -0,0 +1,906 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MaxClaw Agent - Marketplace</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.language-selector {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.language-selector select {
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.search-btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugins-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.plugin-card {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.plugin-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.plugin-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plugin-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.plugin-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-installed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-available {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.plugin-description {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.plugin-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom-color: #667eea;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.plugins-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.plugin-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.plugin-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 8px 15px;
|
||||
border-bottom: none;
|
||||
border-left: 3px solid transparent;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom: none;
|
||||
border-left-color: #667eea;
|
||||
}
|
||||
|
||||
.language-selector {
|
||||
position: static;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Touch-friendly improvements */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.btn:hover {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.plugin-card:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.plugin-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="language-selector">
|
||||
<select id="language-select" onchange="changeLanguage(this.value)">
|
||||
<option value="it">Italiano</option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<h1 id="title">MaxClaw Agent - Marketplace</h1>
|
||||
<p id="subtitle">Scopri e installa plugin per estendere le funzionalità</p>
|
||||
</header>
|
||||
|
||||
<div class="card">
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="switchTab('marketplace')" id="tab-marketplace">Marketplace</div>
|
||||
<div class="tab" onclick="switchTab('installed')" id="tab-installed">Plugin Installati</div>
|
||||
<div class="tab" onclick="switchTab('search')" id="tab-search">Cerca</div>
|
||||
</div>
|
||||
|
||||
<div id="marketplace-tab" class="tab-content active">
|
||||
<h2 id="marketplace-title">🏪 Plugin Disponibili</h2>
|
||||
|
||||
<div id="alert-container"></div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="total-plugins">0</div>
|
||||
<div class="stat-label" id="total-plugins-label">Plugin Totali</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="installed-plugins">0</div>
|
||||
<div class="stat-label" id="installed-plugins-label">Plugin Installati</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="available-plugins">0</div>
|
||||
<div class="stat-label" id="available-plugins-label">Plugin Disponibili</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plugins-grid" id="plugins-grid">
|
||||
<!-- Plugins will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="installed-tab" class="tab-content">
|
||||
<h2 id="installed-title">📦 Plugin Installati</h2>
|
||||
|
||||
<div class="plugins-grid" id="installed-plugins-grid">
|
||||
<!-- Installed plugins will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-tab" class="tab-content">
|
||||
<h2 id="search-title">🔍 Cerca Plugin</h2>
|
||||
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input" id="search-input" placeholder="Cerca plugin...">
|
||||
<button class="search-btn" onclick="searchPlugins()" id="search-btn">Cerca</button>
|
||||
</div>
|
||||
|
||||
<div class="plugins-grid" id="search-results-grid">
|
||||
<!-- Search results will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// API base URL
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
|
||||
// Current language
|
||||
let currentLanguage = 'it';
|
||||
|
||||
// Translations
|
||||
const translations = {
|
||||
it: {
|
||||
title: "MaxClaw Agent - Marketplace",
|
||||
subtitle: "Scopri e installa plugin per estendere le funzionalità",
|
||||
tabMarketplace: "Marketplace",
|
||||
tabInstalled: "Plugin Installati",
|
||||
tabSearch: "Cerca",
|
||||
marketplaceTitle: "🏪 Plugin Disponibili",
|
||||
totalPluginsLabel: "Plugin Totali",
|
||||
installedPluginsLabel: "Plugin Installati",
|
||||
availablePluginsLabel: "Plugin Disponibili",
|
||||
installedTitle: "📦 Plugin Installati",
|
||||
searchTitle: "🔍 Cerca Plugin",
|
||||
searchPlaceholder: "Cerca plugin...",
|
||||
searchBtn: "Cerca",
|
||||
installBtn: "Installa",
|
||||
uninstallBtn: "Disinstalla",
|
||||
infoBtn: "Info",
|
||||
alertSuccess: "Operazione completata con successo!",
|
||||
alertError: "Errore",
|
||||
alertWarning: "Avviso",
|
||||
alertSearching: "Ricerca in corso...",
|
||||
alertInstalling: "Installazione in corso...",
|
||||
alertUninstalling: "Disinstallazione in corso...",
|
||||
noPlugins: "Nessun plugin trovato",
|
||||
installed: "Installato",
|
||||
available: "Disponibile",
|
||||
refreshBtn: "Aggiorna"
|
||||
},
|
||||
en: {
|
||||
title: "MaxClaw Agent - Marketplace",
|
||||
subtitle: "Discover and install plugins to extend functionality",
|
||||
tabMarketplace: "Marketplace",
|
||||
tabInstalled: "Installed Plugins",
|
||||
tabSearch: "Search",
|
||||
marketplaceTitle: "🏪 Available Plugins",
|
||||
totalPluginsLabel: "Total Plugins",
|
||||
installedPluginsLabel: "Installed Plugins",
|
||||
availablePluginsLabel: "Available Plugins",
|
||||
installedTitle: "📦 Installed Plugins",
|
||||
searchTitle: "🔍 Search Plugins",
|
||||
searchPlaceholder: "Search plugins...",
|
||||
searchBtn: "Search",
|
||||
installBtn: "Install",
|
||||
uninstallBtn: "Uninstall",
|
||||
infoBtn: "Info",
|
||||
alertSuccess: "Operation completed successfully!",
|
||||
alertError: "Error",
|
||||
alertWarning: "Warning",
|
||||
alertSearching: "Searching...",
|
||||
alertInstalling: "Installing...",
|
||||
alertUninstalling: "Uninstalling...",
|
||||
noPlugins: "No plugins found",
|
||||
installed: "Installed",
|
||||
available: "Available",
|
||||
refreshBtn: "Refresh"
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load saved language preference
|
||||
const savedLanguage = localStorage.getItem('maxclaw-language') || 'it';
|
||||
document.getElementById('language-select').value = savedLanguage;
|
||||
changeLanguage(savedLanguage);
|
||||
|
||||
loadMarketplaceStats();
|
||||
loadPlugins();
|
||||
loadInstalledPlugins();
|
||||
|
||||
// Handle search input
|
||||
document.getElementById('search-input').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
searchPlugins();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Change language
|
||||
function changeLanguage(language) {
|
||||
currentLanguage = language;
|
||||
localStorage.setItem('maxclaw-language', language);
|
||||
|
||||
const t = translations[language];
|
||||
|
||||
// Update all translatable elements
|
||||
document.getElementById('title').textContent = t.title;
|
||||
document.getElementById('subtitle').textContent = t.subtitle;
|
||||
document.getElementById('tab-marketplace').textContent = t.tabMarketplace;
|
||||
document.getElementById('tab-installed').textContent = t.tabInstalled;
|
||||
document.getElementById('tab-search').textContent = t.tabSearch;
|
||||
document.getElementById('marketplace-title').textContent = t.marketplaceTitle;
|
||||
document.getElementById('total-plugins-label').textContent = t.totalPluginsLabel;
|
||||
document.getElementById('installed-plugins-label').textContent = t.installedPluginsLabel;
|
||||
document.getElementById('available-plugins-label').textContent = t.availablePluginsLabel;
|
||||
document.getElementById('installed-title').textContent = t.installedTitle;
|
||||
document.getElementById('search-title').textContent = t.searchTitle;
|
||||
document.getElementById('search-input').placeholder = t.searchPlaceholder;
|
||||
document.getElementById('search-btn').textContent = t.searchBtn;
|
||||
|
||||
// Update HTML lang attribute
|
||||
document.documentElement.lang = language;
|
||||
}
|
||||
|
||||
// Switch tabs
|
||||
function switchTab(tabName) {
|
||||
// Update tab buttons
|
||||
document.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
event.target.classList.add('active');
|
||||
|
||||
// Update tab content
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(tabName + '-tab').classList.add('active');
|
||||
}
|
||||
|
||||
// Load marketplace stats
|
||||
async function loadMarketplaceStats() {
|
||||
try {
|
||||
// Simulate stats (in real implementation, this would come from API)
|
||||
const stats = {
|
||||
total_plugins: 15,
|
||||
installed_plugins: 3,
|
||||
available_plugins: 12
|
||||
};
|
||||
|
||||
document.getElementById('total-plugins').textContent = stats.total_plugins;
|
||||
document.getElementById('installed-plugins').textContent = stats.installed_plugins;
|
||||
document.getElementById('available-plugins').textContent = stats.available_plugins;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading marketplace stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load plugins
|
||||
async function loadPlugins() {
|
||||
try {
|
||||
// Simulate plugins (in real implementation, this would come from API)
|
||||
const plugins = [
|
||||
{
|
||||
name: "weather",
|
||||
description: "Get weather information for any location",
|
||||
installed: false,
|
||||
downloads: 1250,
|
||||
rating: 4.5
|
||||
},
|
||||
{
|
||||
name: "calendar",
|
||||
description: "Manage calendar events and reminders",
|
||||
installed: true,
|
||||
downloads: 980,
|
||||
rating: 4.2
|
||||
},
|
||||
{
|
||||
name: "translation",
|
||||
description: "Translate text between languages",
|
||||
installed: false,
|
||||
downloads: 2100,
|
||||
rating: 4.7
|
||||
},
|
||||
{
|
||||
name: "image_recognition",
|
||||
description: "Analyze and describe images",
|
||||
installed: false,
|
||||
downloads: 750,
|
||||
rating: 4.0
|
||||
},
|
||||
{
|
||||
name: "code_analysis",
|
||||
description: "Analyze code quality and suggest improvements",
|
||||
installed: true,
|
||||
downloads: 1500,
|
||||
rating: 4.8
|
||||
},
|
||||
{
|
||||
name: "database_tools",
|
||||
description: "Connect to and query databases",
|
||||
installed: false,
|
||||
downloads: 620,
|
||||
rating: 4.3
|
||||
}
|
||||
];
|
||||
|
||||
const container = document.getElementById('plugins-grid');
|
||||
container.innerHTML = '';
|
||||
|
||||
plugins.forEach(plugin => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'plugin-card';
|
||||
|
||||
const statusClass = plugin.installed ? 'status-installed' : 'status-available';
|
||||
const statusText = plugin.installed ? translations[currentLanguage].installed : translations[currentLanguage].available;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="plugin-header">
|
||||
<div class="plugin-name">${plugin.name}</div>
|
||||
<div class="plugin-status ${statusClass}">${statusText}</div>
|
||||
</div>
|
||||
<div class="plugin-description">${plugin.description}</div>
|
||||
<div class="plugin-actions">
|
||||
${plugin.installed ?
|
||||
`<button class="btn btn-danger" onclick="uninstallPlugin('${plugin.name}')">${translations[currentLanguage].uninstallBtn}</button>` :
|
||||
`<button class="btn btn-primary" onclick="installPlugin('${plugin.name}')">${translations[currentLanguage].installBtn}</button>`
|
||||
}
|
||||
<button class="btn btn-secondary" onclick="showPluginInfo('${plugin.name}')">${translations[currentLanguage].infoBtn}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
showAlert(translations[currentLanguage].alertError + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Load installed plugins
|
||||
async function loadInstalledPlugins() {
|
||||
try {
|
||||
// Simulate installed plugins (in real implementation, this would come from API)
|
||||
const installed = [
|
||||
{
|
||||
name: "calendar",
|
||||
description: "Manage calendar events and reminders",
|
||||
version: "1.0.0",
|
||||
author: "MaxClaw Team"
|
||||
},
|
||||
{
|
||||
name: "code_analysis",
|
||||
description: "Analyze code quality and suggest improvements",
|
||||
version: "1.2.0",
|
||||
author: "Community"
|
||||
},
|
||||
{
|
||||
name: "file_tools",
|
||||
description: "Additional file management tools",
|
||||
version: "1.0.0",
|
||||
author: "MaxClaw Team"
|
||||
}
|
||||
];
|
||||
|
||||
const container = document.getElementById('installed-plugins-grid');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (installed.length === 0) {
|
||||
container.innerHTML = `<p>${translations[currentLanguage].noPlugins}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
installed.forEach(plugin => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'plugin-card';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="plugin-header">
|
||||
<div class="plugin-name">${plugin.name}</div>
|
||||
<div class="plugin-status status-installed">${translations[currentLanguage].installed}</div>
|
||||
</div>
|
||||
<div class="plugin-description">${plugin.description}</div>
|
||||
<div class="plugin-actions">
|
||||
<button class="btn btn-danger" onclick="uninstallPlugin('${plugin.name}')">${translations[currentLanguage].uninstallBtn}</button>
|
||||
<button class="btn btn-secondary" onclick="showPluginInfo('${plugin.name}')">${translations[currentLanguage].infoBtn}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
showAlert(translations[currentLanguage].alertError + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Search plugins
|
||||
async function searchPlugins() {
|
||||
const query = document.getElementById('search-input').value.trim();
|
||||
|
||||
if (!query) {
|
||||
showAlert(translations[currentLanguage].alertWarning + ': Inserisci un termine di ricerca', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlert(translations[currentLanguage].alertSearching, 'info');
|
||||
|
||||
try {
|
||||
// Simulate search results (in real implementation, this would come from API)
|
||||
const results = [
|
||||
{
|
||||
name: "weather",
|
||||
description: "Get weather information for any location",
|
||||
installed: false
|
||||
},
|
||||
{
|
||||
name: "translation",
|
||||
description: "Translate text between languages",
|
||||
installed: false
|
||||
}
|
||||
];
|
||||
|
||||
const container = document.getElementById('search-results-grid');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (results.length === 0) {
|
||||
container.innerHTML = `<p>${translations[currentLanguage].noPlugins}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
results.forEach(plugin => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'plugin-card';
|
||||
|
||||
const statusClass = plugin.installed ? 'status-installed' : 'status-available';
|
||||
const statusText = plugin.installed ? translations[currentLanguage].installed : translations[currentLanguage].available;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="plugin-header">
|
||||
<div class="plugin-name">${plugin.name}</div>
|
||||
<div class="plugin-status ${statusClass}">${statusText}</div>
|
||||
</div>
|
||||
<div class="plugin-description">${plugin.description}</div>
|
||||
<div class="plugin-actions">
|
||||
${plugin.installed ?
|
||||
`<button class="btn btn-danger" onclick="uninstallPlugin('${plugin.name}')">${translations[currentLanguage].uninstallBtn}</button>` :
|
||||
`<button class="btn btn-primary" onclick="installPlugin('${plugin.name}')">${translations[currentLanguage].installBtn}</button>`
|
||||
}
|
||||
<button class="btn btn-secondary" onclick="showPluginInfo('${plugin.name}')">${translations[currentLanguage].infoBtn}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
showAlert(`${translations[currentLanguage].alertSuccess} Trovati ${results.length} plugin`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
showAlert(translations[currentLanguage].alertError + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Install plugin
|
||||
async function installPlugin(pluginName) {
|
||||
showAlert(translations[currentLanguage].alertInstalling + ' ' + pluginName, 'info');
|
||||
|
||||
try {
|
||||
// Simulate installation (in real implementation, this would call API)
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
showAlert(`${translations[currentLanguage].alertSuccess} Plugin '${pluginName}' installato!`, 'success');
|
||||
|
||||
// Refresh the lists
|
||||
loadPlugins();
|
||||
loadInstalledPlugins();
|
||||
loadMarketplaceStats();
|
||||
|
||||
} catch (error) {
|
||||
showAlert(translations[currentLanguage].alertError + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall plugin
|
||||
async function uninstallPlugin(pluginName) {
|
||||
if (!confirm(`Sei sicuro di voler disinstallare il plugin '${pluginName}'?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
showAlert(translations[currentLanguage].alertUninstalling + ' ' + pluginName, 'info');
|
||||
|
||||
try {
|
||||
// Simulate uninstallation (in real implementation, this would call API)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
showAlert(`${translations[currentLanguage].alertSuccess} Plugin '${pluginName}' disinstallato!`, 'success');
|
||||
|
||||
// Refresh the lists
|
||||
loadPlugins();
|
||||
loadInstalledPlugins();
|
||||
loadMarketplaceStats();
|
||||
|
||||
} catch (error) {
|
||||
showAlert(translations[currentLanguage].alertError + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Show plugin info
|
||||
function showPluginInfo(pluginName) {
|
||||
alert(`Informazioni sul plugin: ${pluginName}\n\nQuesta funzionalità mostrerà dettagli completi sul plugin, inclusi:\n- Versione\n- Autore\n- Descrizione dettagliata\n- Dipendenze\n- Documentazione`);
|
||||
}
|
||||
|
||||
// Show alert
|
||||
function showAlert(message, type) {
|
||||
const container = document.getElementById('alert-container');
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type}`;
|
||||
alert.textContent = message;
|
||||
|
||||
// Remove existing alerts
|
||||
container.innerHTML = '';
|
||||
container.appendChild(alert);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (alert.parentNode) {
|
||||
alert.parentNode.removeChild(alert);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user