Files
langcode_agent/web/index.html
T

998 lines
36 KiB
HTML

<!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;
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;
}
.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;
display: inline-block;
margin: 5px;
}
.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;
flex-direction: column;
gap: 10px;
}
.config-info h3 {
color: #333;
margin-bottom: 5px;
}
.config-info p {
color: #666;
font-size: 14px;
}
.config-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.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;
flex-wrap: wrap;
}
.chat-input input {
flex: 1;
min-width: 200px;
}
.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;
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;
}
.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;
}
/* 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;
}
.form-group {
margin-bottom: 15px;
}
label {
font-size: 14px;
}
input, select, textarea {
padding: 10px;
font-size: 14px;
}
.btn {
padding: 10px 20px;
font-size: 14px;
width: 100%;
margin: 5px 0;
}
.config-item {
padding: 12px;
}
.config-actions {
flex-direction: column;
}
.config-actions .btn {
width: 100%;
}
.chat-container {
height: 300px;
padding: 10px;
}
.message {
max-width: 90%;
padding: 8px 12px;
font-size: 14px;
}
.chat-input {
flex-direction: column;
}
.chat-input input {
min-width: 100%;
}
.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;
}
.btn {
padding: 8px 16px;
font-size: 13px;
}
.chat-container {
height: 250px;
}
}
/* Touch-friendly improvements */
@media (hover: none) and (pointer: coarse) {
.btn:hover {
background: #667eea;
}
.btn:active {
background: #5a6fd8;
transform: scale(0.98);
}
input:focus, select:focus, textarea:focus {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
}
</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</h1>
<p id="subtitle">Interfaccia di configurazione per l'agente AI</p>
</header>
<div class="card">
<div class="tabs">
<div class="tab active" onclick="switchTab('config')" id="tab-config">Configurazione</div>
<div class="tab" onclick="switchTab('chat')" id="tab-chat">Chat</div>
<div class="tab" onclick="switchTab('status')" id="tab-status">Stato</div>
</div>
<div id="config-tab" class="tab-content active">
<h2 id="config-title">Gestione Configurazioni</h2>
<div id="alert-container"></div>
<form id="config-form">
<div class="form-group">
<label for="config-name" id="name-label">Nome Configurazione</label>
<input type="text" id="config-name" placeholder="es. mio-ollama" required>
</div>
<div class="form-group">
<label for="config-provider" id="provider-label">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>
<option value="anthropic">Anthropic</option>
<option value="google">Google</option>
</select>
</div>
<div class="form-group">
<label for="config-model" id="model-label">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" id="api-key-label">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" id="base-url-label">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">
<span id="set-active-label">Imposta come configurazione attiva</span>
</label>
</div>
<button type="submit" class="btn" id="save-btn">Salva Configurazione</button>
<button type="button" class="btn btn-secondary" onclick="clearForm()" id="clear-btn">Pulisci</button>
</form>
<div class="config-list">
<h3 id="existing-configs-title">Configurazioni Esistenti</h3>
<div id="config-list-container">
<!-- Configurations will be loaded here -->
</div>
</div>
</div>
<div id="chat-tab" class="tab-content">
<h2 id="chat-title">Chat con MaxClaw</h2>
<div class="chat-container" id="chat-container">
<div class="message agent-message" id="welcome-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()" id="send-btn">Invia</button>
</div>
</div>
<div id="status-tab" class="tab-content">
<h2 id="status-title">Stato del Sistema</h2>
<div id="status-container">
<div class="alert alert-success" id="system-status-alert">
<strong id="system-ok">✅ Sistema operativo</strong><br>
<span id="system-ready">L'agente è pronto per l'uso.</span>
</div>
<div class="config-item">
<div class="config-info">
<h3 id="active-provider-title">Provider Attivo</h3>
<p id="active-provider">Nessun provider configurato</p>
</div>
</div>
<div class="config-item">
<div class="config-info">
<h3 id="active-model-title">Modello Attivo</h3>
<p id="active-model">Nessun modello configurato</p>
</div>
</div>
<div class="config-item">
<div class="config-info">
<h3 id="connection-title">Connessione</h3>
<p id="connection-status">Verifica in corso...</p>
</div>
<button class="btn btn-secondary" onclick="testConnection()" id="test-connection-btn">Testa Connessione</button>
</div>
</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",
subtitle: "Interfaccia di configurazione per l'agente AI",
tabConfig: "Configurazione",
tabChat: "Chat",
tabStatus: "Stato",
configTitle: "Gestione Configurazioni",
nameLabel: "Nome Configurazione",
providerLabel: "Provider",
modelLabel: "Nome Modello",
apiKeyLabel: "Chiave API",
baseUrlLabel: "URL Base",
setActiveLabel: "Imposta come configurazione attiva",
saveBtn: "Salva Configurazione",
clearBtn: "Pulisci",
existingConfigsTitle: "Configurazioni Esistenti",
chatTitle: "Chat con MaxClaw",
welcomeMessage: "Ciao! Sono MaxClaw, il tuo agente AI. Come posso aiutarti oggi?",
chatPlaceholder: "Scrivi un messaggio...",
sendBtn: "Invia",
statusTitle: "Stato del Sistema",
systemOk: "✅ Sistema operativo",
systemReady: "L'agente è pronto per l'uso.",
activeProviderTitle: "Provider Attivo",
activeModelTitle: "Modello Attivo",
connectionTitle: "Connessione",
testConnectionBtn: "Testa Connessione",
alertSuccess: "Configurazione salvata con successo!",
alertError: "Errore",
alertCancelled: "Annullato.",
alertTesting: "Test in corso...",
alertConnectionSuccess: "Connessione riuscita!",
alertConnectionFailed: "Connessione fallita",
noProvider: "Nessun provider configurato",
noModel: "Nessun modello configurato",
checking: "Verifica in corso...",
active: "Attivo",
inactive: "Inattivo",
test: "Testa",
delete: "Elimina",
activate: "Attiva"
},
en: {
title: "MaxClaw Agent",
subtitle: "Configuration interface for AI agent",
tabConfig: "Configuration",
tabChat: "Chat",
tabStatus: "Status",
configTitle: "Configuration Management",
nameLabel: "Configuration Name",
providerLabel: "Provider",
modelLabel: "Model Name",
apiKeyLabel: "API Key",
baseUrlLabel: "Base URL",
setActiveLabel: "Set as active configuration",
saveBtn: "Save Configuration",
clearBtn: "Clear",
existingConfigsTitle: "Existing Configurations",
chatTitle: "Chat with MaxClaw",
welcomeMessage: "Hello! I'm MaxClaw, your AI agent. How can I help you today?",
chatPlaceholder: "Type a message...",
sendBtn: "Send",
statusTitle: "System Status",
systemOk: "✅ System operational",
systemReady: "The agent is ready to use.",
activeProviderTitle: "Active Provider",
activeModelTitle: "Active Model",
connectionTitle: "Connection",
testConnectionBtn: "Test Connection",
alertSuccess: "Configuration saved successfully!",
alertError: "Error",
alertCancelled: "Cancelled.",
alertTesting: "Testing...",
alertConnectionSuccess: "Connection successful!",
alertConnectionFailed: "Connection failed",
noProvider: "No provider configured",
noModel: "No model configured",
checking: "Checking...",
active: "Active",
inactive: "Inactive",
test: "Test",
delete: "Delete",
activate: "Activate"
}
};
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Load saved language preference
const savedLanguage = localStorage.getItem('maxclaw-language') || 'it';
document.getElementById('language-select').value = savedLanguage;
changeLanguage(savedLanguage);
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();
}
});
});
// 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-config').textContent = t.tabConfig;
document.getElementById('tab-chat').textContent = t.tabChat;
document.getElementById('tab-status').textContent = t.tabStatus;
document.getElementById('config-title').textContent = t.configTitle;
document.getElementById('name-label').textContent = t.nameLabel;
document.getElementById('provider-label').textContent = t.providerLabel;
document.getElementById('model-label').textContent = t.modelLabel;
document.getElementById('api-key-label').textContent = t.apiKeyLabel;
document.getElementById('base-url-label').textContent = t.baseUrlLabel;
document.getElementById('set-active-label').textContent = t.setActiveLabel;
document.getElementById('save-btn').textContent = t.saveBtn;
document.getElementById('clear-btn').textContent = t.clearBtn;
document.getElementById('existing-configs-title').textContent = t.existingConfigsTitle;
document.getElementById('chat-title').textContent = t.chatTitle;
document.getElementById('welcome-message').textContent = t.welcomeMessage;
document.getElementById('chat-input').placeholder = t.chatPlaceholder;
document.getElementById('send-btn').textContent = t.sendBtn;
document.getElementById('status-title').textContent = t.statusTitle;
document.getElementById('system-ok').textContent = t.systemOk;
document.getElementById('system-ready').textContent = t.systemReady;
document.getElementById('active-provider-title').textContent = t.activeProviderTitle;
document.getElementById('active-model-title').textContent = t.activeModelTitle;
document.getElementById('connection-title').textContent = t.connectionTitle;
document.getElementById('test-connection-btn').textContent = t.testConnectionBtn;
// 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 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>${translations[currentLanguage].noProvider}</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">${translations[currentLanguage].active}</span>` : ''}
</h3>
<p>${translations[currentLanguage].providerLabel}: ${config.provider} | ${translations[currentLanguage].modelLabel}: ${config.model_name}</p>
</div>
<div class="config-actions">
<button class="btn btn-secondary" onclick="activateConfig('${config.name}')">${translations[currentLanguage].activate}</button>
<button class="btn btn-secondary" onclick="testConfig('${config.name}')">${translations[currentLanguage].test}</button>
<button class="btn btn-danger" onclick="deleteConfig('${config.name}')">${translations[currentLanguage].delete}</button>
</div>
`;
container.appendChild(item);
});
} catch (error) {
showAlert(translations[currentLanguage].alertError + ': ' + 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 = translations[currentLanguage].noProvider;
document.getElementById('active-model').textContent = translations[currentLanguage].noModel;
}
} 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(translations[currentLanguage].alertSuccess, 'success');
clearForm();
loadConfigurations();
loadActiveConfiguration();
} else {
const error = await response.json();
showAlert(translations[currentLanguage].alertError + ': ' + error.detail, 'error');
}
} catch (error) {
showAlert(translations[currentLanguage].alertError + ': ' + 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(`${translations[currentLanguage].alertSuccess} "${name}"`, 'success');
loadConfigurations();
loadActiveConfiguration();
} else {
const error = await response.json();
showAlert(translations[currentLanguage].alertError + ': ' + error.detail, 'error');
}
} catch (error) {
showAlert(translations[currentLanguage].alertError + ': ' + 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(translations[currentLanguage].alertError + ': ' + 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(`${translations[currentLanguage].alertSuccess} "${name}"`, 'success');
loadConfigurations();
loadActiveConfiguration();
} else {
const error = await response.json();
showAlert(translations[currentLanguage].alertError + ': ' + error.detail, 'error');
}
} catch (error) {
showAlert(translations[currentLanguage].alertError + ': ' + error.message, 'error');
}
}
// Test connection
async function testConnection() {
document.getElementById('connection-status').textContent = translations[currentLanguage].alertTesting;
try {
const response = await fetch(API_BASE + '/health');
const result = await response.json();
if (result.status === 'healthy') {
document.getElementById('connection-status').textContent = translations[currentLanguage].alertConnectionSuccess;
showAlert(translations[currentLanguage].alertConnectionSuccess, 'success');
} else {
document.getElementById('connection-status').textContent = translations[currentLanguage].alertConnectionFailed;
showAlert(translations[currentLanguage].alertConnectionFailed, 'error');
}
} catch (error) {
document.getElementById('connection-status').textContent = translations[currentLanguage].alertConnectionFailed;
showAlert(translations[currentLanguage].alertError + ': ' + 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(translations[currentLanguage].alertError + ': ' + error.message, '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' || provider === 'anthropic' || provider === 'google') {
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>