Phase 6: Web UI for configuration management and enhanced API server
This commit is contained in:
1 parent
5541770896
commit
6eace3abc0
3 files changed
+733
-11
No files matched your search
+691
@@ -0,0 +1,691 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MaxClaw Agent - Configurazione</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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
border-color: #667eea;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.config-list {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config-info h3 {
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.config-info p {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.config-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: 400px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px 15px;
|
||||
border-radius: 18px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
margin-left: auto;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.agent-message {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-input input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom-color: #667eea;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>MaxClaw Agent</h1>
|
||||
<p>Interfaccia di configurazione per l'agente AI</p>
|
||||
</header>
|
||||
|
||||
<div class="card">
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="switchTab('config')">Configurazione</div>
|
||||
<div class="tab" onclick="switchTab('chat')">Chat</div>
|
||||
<div class="tab" onclick="switchTab('status')">Stato</div>
|
||||
</div>
|
||||
|
||||
<div id="config-tab" class="tab-content active">
|
||||
<h2>Gestione Configurazioni</h2>
|
||||
|
||||
<div id="alert-container"></div>
|
||||
|
||||
<form id="config-form">
|
||||
<div class="form-group">
|
||||
<label for="config-name">Nome Configurazione</label>
|
||||
<input type="text" id="config-name" placeholder="es. mio-ollama" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="config-provider">Provider</label>
|
||||
<select id="config-provider" required>
|
||||
<option value="">Seleziona un provider</option>
|
||||
<option value="ollama">Ollama</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="config-model">Nome Modello</label>
|
||||
<input type="text" id="config-model" placeholder="es. gemma2:9b" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="api-key-group">
|
||||
<label for="config-api-key">Chiave API</label>
|
||||
<input type="password" id="config-api-key" placeholder="Inserisci la chiave API">
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="base-url-group">
|
||||
<label for="config-base-url">URL Base</label>
|
||||
<input type="text" id="config-base-url" placeholder="http://localhost:11434">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="config-set-active">
|
||||
Imposta come configurazione attiva
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">Salva Configurazione</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearForm()">Pulisci</button>
|
||||
</form>
|
||||
|
||||
<div class="config-list">
|
||||
<h3>Configurazioni Esistenti</h3>
|
||||
<div id="config-list-container">
|
||||
<!-- Configurations will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-tab" class="tab-content">
|
||||
<h2>Chat con MaxClaw</h2>
|
||||
|
||||
<div class="chat-container" id="chat-container">
|
||||
<div class="message agent-message">
|
||||
Ciao! Sono MaxClaw, il tuo agente AI. Come posso aiutarti oggi?
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input">
|
||||
<input type="text" id="chat-input" placeholder="Scrivi un messaggio...">
|
||||
<button class="btn" onclick="sendMessage()">Invia</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status-tab" class="tab-content">
|
||||
<h2>Stato del Sistema</h2>
|
||||
|
||||
<div id="status-container">
|
||||
<div class="alert alert-success">
|
||||
<strong>✅ Sistema operativo</strong><br>
|
||||
L'agente è pronto per l'uso.
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-info">
|
||||
<h3>Provider Attivo</h3>
|
||||
<p id="active-provider">Nessun provider configurato</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-info">
|
||||
<h3>Modello Attivo</h3>
|
||||
<p id="active-model">Nessun modello configurato</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-info">
|
||||
<h3>Connessione</h3>
|
||||
<p id="connection-status">Verifica in corso...</p>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="testConnection()">Testa Connessione</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// API base URL
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadConfigurations();
|
||||
loadActiveConfiguration();
|
||||
|
||||
// Handle form submission
|
||||
document.getElementById('config-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
saveConfiguration();
|
||||
});
|
||||
|
||||
// Handle provider change
|
||||
document.getElementById('config-provider').addEventListener('change', function() {
|
||||
updateFormFields();
|
||||
});
|
||||
|
||||
// Handle chat input
|
||||
document.getElementById('chat-input').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 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 configurations
|
||||
async function loadConfigurations() {
|
||||
try {
|
||||
const response = await fetch(API_BASE + '/configurations');
|
||||
const configs = await response.json();
|
||||
|
||||
const container = document.getElementById('config-list-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (configs.length === 0) {
|
||||
container.innerHTML = '<p>Nessuna configurazione trovata.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
configs.forEach(config => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'config-item';
|
||||
item.innerHTML = `
|
||||
<div class="config-info">
|
||||
<h3>${config.name}
|
||||
${config.is_active ? '<span class="status-badge status-active">Attivo</span>' : ''}
|
||||
</h3>
|
||||
<p>Provider: ${config.provider} | Modello: ${config.model_name}</p>
|
||||
</div>
|
||||
<div class="config-actions">
|
||||
<button class="btn btn-secondary" onclick="activateConfig('${config.name}')">Attiva</button>
|
||||
<button class="btn btn-secondary" onclick="testConfig('${config.name}')">Testa</button>
|
||||
<button class="btn btn-danger" onclick="deleteConfig('${config.name}')">Elimina</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(item);
|
||||
});
|
||||
} catch (error) {
|
||||
showAlert('Errore nel caricamento delle configurazioni: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Load active configuration
|
||||
async function loadActiveConfiguration() {
|
||||
try {
|
||||
const response = await fetch(API_BASE + '/configurations/active');
|
||||
const config = await response.json();
|
||||
|
||||
if (config.name) {
|
||||
document.getElementById('active-provider').textContent = config.provider;
|
||||
document.getElementById('active-model').textContent = config.model_name;
|
||||
} else {
|
||||
document.getElementById('active-provider').textContent = 'Nessun provider configurato';
|
||||
document.getElementById('active-model').textContent = 'Nessun modello configurato';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading active configuration:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
async function saveConfiguration() {
|
||||
const name = document.getElementById('config-name').value;
|
||||
const provider = document.getElementById('config-provider').value;
|
||||
const model = document.getElementById('config-model').value;
|
||||
const apiKey = document.getElementById('config-api-key').value;
|
||||
const baseUrl = document.getElementById('config-base-url').value;
|
||||
const setActive = document.getElementById('config-set-active').checked;
|
||||
|
||||
const config = {
|
||||
name: name,
|
||||
provider: provider,
|
||||
model_name: model,
|
||||
set_active: setActive
|
||||
};
|
||||
|
||||
if (apiKey) {
|
||||
config.api_key = apiKey;
|
||||
}
|
||||
|
||||
if (baseUrl) {
|
||||
config.base_url = baseUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE + '/configurations', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showAlert('Configurazione salvata con successo!', 'success');
|
||||
clearForm();
|
||||
loadConfigurations();
|
||||
loadActiveConfiguration();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showAlert('Errore: ' + error.detail, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert('Errore di connessione: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Activate configuration
|
||||
async function activateConfig(name) {
|
||||
try {
|
||||
const response = await fetch(API_BASE + `/configurations/${name}/activate`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showAlert(`Configurazione "${name}" attivata!`, 'success');
|
||||
loadConfigurations();
|
||||
loadActiveConfiguration();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showAlert('Errore: ' + error.detail, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert('Errore di connessione: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Test configuration
|
||||
async function testConfig(name) {
|
||||
try {
|
||||
const response = await fetch(API_BASE + `/configurations/${name}/test`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showAlert(`✅ ${result.message}`, 'success');
|
||||
} else {
|
||||
showAlert(`❌ ${result.message}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert('Errore di connessione: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Delete configuration
|
||||
async function deleteConfig(name) {
|
||||
if (!confirm(`Sei sicuro di voler eliminare la configurazione "${name}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE + `/configurations/${name}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showAlert(`Configurazione "${name}" eliminata!`, 'success');
|
||||
loadConfigurations();
|
||||
loadActiveConfiguration();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showAlert('Errore: ' + error.detail, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert('Errore di connessione: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Test connection
|
||||
async function testConnection() {
|
||||
document.getElementById('connection-status').textContent = 'Test in corso...';
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE + '/health');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'healthy') {
|
||||
document.getElementById('connection-status').textContent = '✅ Connessione riuscita';
|
||||
showAlert('Connessione al server riuscita!', 'success');
|
||||
} else {
|
||||
document.getElementById('connection-status').textContent = '❌ Connessione fallita';
|
||||
showAlert('Connessione al server fallita', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('connection-status').textContent = '❌ Errore di connessione';
|
||||
showAlert('Errore di connessione: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Send chat message
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('chat-input');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message) return;
|
||||
|
||||
// Add user message to chat
|
||||
addMessageToChat(message, 'user');
|
||||
input.value = '';
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE + '/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ message: message })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Add agent response to chat
|
||||
addMessageToChat(result.response, 'agent');
|
||||
} catch (error) {
|
||||
addMessageToChat('Errore: Impossibile connettersi al server', 'agent');
|
||||
}
|
||||
}
|
||||
|
||||
// Add message to chat
|
||||
function addMessageToChat(message, sender) {
|
||||
const container = document.getElementById('chat-container');
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${sender}-message`;
|
||||
messageDiv.textContent = message;
|
||||
container.appendChild(messageDiv);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
// Update form fields based on provider
|
||||
function updateFormFields() {
|
||||
const provider = document.getElementById('config-provider').value;
|
||||
const apiKeyGroup = document.getElementById('api-key-group');
|
||||
const baseUrlGroup = document.getElementById('base-url-group');
|
||||
const baseUrlInput = document.getElementById('config-base-url');
|
||||
|
||||
// Show/hide fields based on provider
|
||||
if (provider === 'openai' || provider === 'openrouter') {
|
||||
apiKeyGroup.style.display = 'block';
|
||||
} else {
|
||||
apiKeyGroup.style.display = 'none';
|
||||
}
|
||||
|
||||
if (provider === 'ollama' || provider === 'openrouter') {
|
||||
baseUrlGroup.style.display = 'block';
|
||||
if (provider === 'ollama') {
|
||||
baseUrlInput.placeholder = 'http://192.168.0.50:11434';
|
||||
} else {
|
||||
baseUrlInput.placeholder = 'https://openrouter.ai/api/v1';
|
||||
}
|
||||
} else {
|
||||
baseUrlGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Clear form
|
||||
function clearForm() {
|
||||
document.getElementById('config-form').reset();
|
||||
updateFormFields();
|
||||
}
|
||||
|
||||
// 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