chore(ui): Move zombie UI to old

This commit is contained in:
Richard Palethorpe
2025-04-01 16:44:35 +01:00
parent 99e0011920
commit 53d135bec9
23 changed files with 144 additions and 143 deletions

View File

@@ -0,0 +1,291 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Actions Playground</title>
{{template "old/views/partials/header"}}
</head>
<body>
{{template "old/views/partials/menu"}}
<!-- Toast for notifications -->
<div id="toast" class="toast">
<span id="toast-message"></span>
</div>
<div class="container mx-auto">
<header class="text-center mb-8">
<h1 class="text-4xl md:text-6xl font-bold">Actions Playground</h1>
<p class="mt-4 text-gray-400">Test and execute actions directly from the UI</p>
</header>
<section class="section-box mb-8">
<h2 class="mb-4">Select an Action</h2>
<div class="mb-4">
<label for="action-select" class="block mb-2">Available Actions:</label>
<select id="action-select" class="w-full">
<option value="">-- Select an action --</option>
<!-- Actions will be loaded here -->
</select>
</div>
</section>
<section id="config-section" class="section-box mb-8 hidden">
<h2 class="mb-4">Action Configuration</h2>
<form id="action-form">
<div class="mb-6">
<label for="config-json" class="block mb-2">Configuration (JSON):</label>
<textarea id="config-json" class="w-full" rows="5" placeholder='{"key": "value"}'>{}</textarea>
<p class="text-xs text-gray-400 mt-1">Enter JSON configuration for the action</p>
</div>
<div class="mb-6">
<label for="params-json" class="block mb-2">Parameters (JSON):</label>
<textarea id="params-json" class="w-full" rows="5" placeholder='{"key": "value"}'>{}</textarea>
<p class="text-xs text-gray-400 mt-1">Enter JSON parameters for the action</p>
</div>
<div class="flex justify-end">
<button type="submit" class="action-btn start-btn">
<i class="fas fa-play"></i> Execute Action
</button>
</div>
</form>
</section>
<section id="results-section" class="section-box mb-8 hidden">
<h2 class="mb-4">Action Results</h2>
<div id="action-results">
<!-- Results will appear here -->
</div>
</section>
<footer class="text-center text-gray-500 text-sm mb-8">
<p>&copy; 2025 LocalAgent.</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load available actions
fetchActions();
// Handle action selection
document.getElementById('action-select').addEventListener('change', function() {
const actionId = this.value;
if (actionId) {
document.getElementById('config-section').classList.remove('hidden');
} else {
document.getElementById('config-section').classList.add('hidden');
}
// Hide results when changing actions
document.getElementById('results-section').classList.add('hidden');
});
// Handle form submission
document.getElementById('action-form').addEventListener('submit', function(e) {
e.preventDefault();
const actionId = document.getElementById('action-select').value;
if (actionId) {
executeAction(actionId);
} else {
showToast('Please select an action first', 'error');
}
});
});
function fetchActions() {
fetch('/api/actions')
.then(response => response.json())
.then(actions => {
const select = document.getElementById('action-select');
// Clear existing options except the first one
while (select.options.length > 1) {
select.remove(1);
}
if (actions.length === 0) {
const option = document.createElement('option');
option.text = 'No actions available';
option.disabled = true;
select.add(option);
return;
}
// Add options for each action
actions.forEach(actionId => {
const option = document.createElement('option');
option.value = actionId;
option.text = actionId; // Using actionId as display text
select.add(option);
});
})
.catch(error => {
console.error('Error fetching actions:', error);
showToast('Failed to load actions: ' + error.message, 'error');
const select = document.getElementById('action-select');
const option = document.createElement('option');
option.text = 'Error loading actions';
option.disabled = true;
// Clear existing options except the first one
while (select.options.length > 1) {
select.remove(1);
}
select.add(option);
});
}
function executeAction(actionId) {
// Get the JSON data from textareas
let config = {};
let params = {};
try {
const configText = document.getElementById('config-json').value.trim();
if (configText && configText !== '{}') {
config = JSON.parse(configText);
}
const paramsText = document.getElementById('params-json').value.trim();
if (paramsText && paramsText !== '{}') {
params = JSON.parse(paramsText);
}
} catch (error) {
showToast('Invalid JSON: ' + error.message, 'error');
return;
}
// Show the results section with loading indicator
const resultsSection = document.getElementById('results-section');
resultsSection.classList.remove('hidden');
const resultDiv = document.getElementById('action-results');
resultDiv.innerHTML = `
<div class="flex justify-center items-center py-8">
<div class="loader"></div>
</div>
`;
// Execute the action
fetch(`/api/action/${actionId}/run`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
config: config,
params: params
})
})
.then(response => {
return response.json();
})
.then(result => {
if (result.error) {
throw new Error(result.error);
}
// Display the results
showActionResult(result);
showToast('Action executed successfully!', 'success');
})
.catch(error => {
resultDiv.innerHTML = `
<div class="alert alert-error" style="display: block;">
<i class="fas fa-exclamation-circle mr-2"></i> Error: ${error.message}
</div>
`;
showToast('Error executing action', 'error');
});
}
function showActionResult(result) {
const resultDiv = document.getElementById('action-results');
let html = '';
// Display result
if (result.Result) {
html += `
<div class="mb-4">
<h4 class="text-lg mb-2" style="color: var(--secondary);">Result:</h4>
<div class="code-terminal">
<pre>${escapeHtml(result.Result)}</pre>
</div>
</div>
`;
}
// Display metadata if available
if (result.Metadata && Object.keys(result.Metadata).length > 0) {
html += `
<div class="mb-4">
<h4 class="text-lg mb-2" style="color: var(--secondary);">Metadata:</h4>
<div class="code-terminal">
<pre>${escapeHtml(JSON.stringify(result.Metadata, null, 2))}</pre>
</div>
</div>
`;
}
if (!html) {
html = '<p class="text-gray-400">No results returned from the action.</p>';
}
resultDiv.innerHTML = html;
// Scroll to results
resultDiv.scrollIntoView({ behavior: 'smooth' });
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function showToast(message, type) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toast-message');
toastMessage.textContent = message;
toast.className = 'toast toast-' + type;
toast.classList.add('toast-visible');
setTimeout(() => {
toast.classList.remove('toast-visible');
}, 3000);
}
</script>
<style>
.loader {
width: 48px;
height: 48px;
border: 5px solid var(--tertiary);
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.code-terminal {
margin-top: 0;
}
</style>
</body>
</html>

332
webui/old/views/agents.html Normal file
View File

@@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent List</title>
{{template "old/views/partials/header"}}
<style>
.avatar-placeholder {
width: 96px;
height: 96px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #2a2a2a, #3a3a3a);
color: var(--primary);
font-size: 1.5rem;
margin-bottom: 1rem;
border: 2px solid var(--primary);
box-shadow: var(--neon-glow);
position: relative;
overflow: hidden;
}
.avatar-placeholder::after {
content: "";
position: absolute;
width: 100%;
height: 4px;
background: var(--primary);
bottom: 0;
left: 0;
animation: loading-progress 2s infinite linear;
}
@keyframes loading-progress {
0% { width: 0; }
50% { width: 100%; }
100% { width: 0; }
}
.placeholder-text {
z-index: 1;
}
</style>
</head>
<body>
{{template "old/views/partials/menu"}}
<!-- Toast for notifications -->
<div id="toast" class="toast">
<span id="toast-message"></span>
</div>
<div class="container mx-auto">
<header class="text-center mb-8">
<h1 class="text-4xl md:text-6xl font-bold">Agent List</h1>
<p class="mt-4 text-gray-400">Manage and interact with your AI agents</p>
</header>
<div class="button-container justify-center mb-6">
<a href="/old/create" class="action-btn start-btn">
<i class="fas fa-plus-circle"></i> Add New Agent
</a>
<button id="toggle-import" class="action-btn" style="background: linear-gradient(135deg, var(--tertiary), #4a76a8);">
<i class="fas fa-file-import"></i> Import Agent
</button>
</div>
<section id="import-section" class="hidden mb-8">
<div class="section-box">
<h2>Import Agent</h2>
<!-- Response Messages Container -->
<div id="response-container">
<!-- Success Alert -->
<div id="success-alert" class="alert alert-success" style="display: none;">
Agent imported successfully! The page will refresh in a moment.
</div>
<!-- Error Alert -->
<div id="error-alert" class="alert alert-error" style="display: none;">
<span id="error-message">Error importing agent.</span>
</div>
</div>
<form id='import-form' hx-encoding='multipart/form-data' hx-post='/settings/import' hx-target="#response-container" hx-swap="none">
<div class="mb-4">
<label for="file" class="block mb-2">Select Agent File:</label>
<input type='file' name='file' id='file'>
</div>
<div class="flex items-center">
<button id="import-button" type="submit" class="action-btn">
<i class="fas fa-cloud-upload-alt"></i> Import Agent
</button>
</div>
</form>
</div>
</section>
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{{ $status := .Status }}
{{ range .Agents }}
<div hx-ext="sse" data-agent-name="{{.}}" class="card">
<div class="flex flex-col items-center text-center p-4">
<div class="avatar-container mb-4">
<img src="/avatars/{{.}}.png" alt="{{.}}" class="w-24 h-24 rounded-full"
style="border: 2px solid var(--primary); box-shadow: var(--neon-glow); display: none;"
onload="this.style.display = 'block'; this.nextElementSibling.style.display = 'none';"
onerror="this.style.display = 'none'; this.nextElementSibling.style.display = 'flex';">
<div class="avatar-placeholder">
<span class="placeholder-text"><i class="fas fa-sync fa-spin"></i></span>
</div>
</div>
<h2>{{.}}</h2>
<div class="mb-4 flex items-center justify-center">
<span class="badge {{ if eq (index $status .) true }}badge-primary{{ else }}badge-secondary{{ end }} mr-2">
{{ if eq (index $status .) true }}Active{{ else }}Inactive{{ end }}
</span>
</div>
<div class="grid grid-cols-2 gap-2 w-full mb-4">
<a href="/old/status/{{.}}" class="action-btn flex items-center justify-center"
style="background: linear-gradient(135deg, #2a2a2a, #3a3a3a);">
<i class="fas fa-info-circle mr-2"></i> Status
</a>
<a href="/old/talk/{{.}}" class="action-btn flex items-center justify-center"
style="background: linear-gradient(135deg, #2a2a2a, #3a3a3a);">
<i class="fas fa-comments mr-2"></i> Talk
</a>
</div>
<div class="grid grid-cols-2 gap-2 w-full">
<button class="action-btn toggle-btn col-span-1"
data-agent="{{.}}"
data-active="{{ if eq (index $status .) true }}true{{ else }}false{{ end }}">
{{ if eq (index $status .) true }}
<i class="fas fa-pause"></i> Pause
{{ else }}
<i class="fas fa-play"></i> Start
{{ end }}
</button>
<a href="/old/settings/{{.}}" class="action-btn col-span-1 flex items-center justify-center"
style="background: linear-gradient(135deg, #2a2a2a, #3a3a3a);">
<i class="fas fa-cog"></i>
</a>
</div>
</div>
</div>
{{ end }}
</section>
<div class="user-info mt-8 mb-4">
<span></span>
<span class="timestamp"></span>
</div>
<footer class="text-center text-gray-500 text-sm mb-8">
<p>&copy; 2025 LocalAgent.</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Image loading handler
document.querySelectorAll('.avatar-container img').forEach(img => {
// Check if image is already cached
if (img.complete) {
if (img.naturalHeight === 0) {
// Image failed to load
img.style.display = 'none';
img.nextElementSibling.style.display = 'flex';
} else {
// Image loaded successfully
img.style.display = 'block';
img.nextElementSibling.style.display = 'none';
}
}
// onload and onerror handlers are already in the HTML
});
const importSection = document.getElementById('import-section');
const toggleImport = document.getElementById('toggle-import');
// Toggle import section visibility
toggleImport.addEventListener('click', function() {
importSection.classList.toggle('hidden');
// Add glitch effect when showing
if (!importSection.classList.contains('hidden')) {
importSection.style.animation = 'glitch 0.3s';
setTimeout(() => {
importSection.style.animation = '';
}, 300);
}
});
// Handle import form submission
document.getElementById('import-form').addEventListener('htmx:afterRequest', function(event) {
const xhr = event.detail.xhr;
const successAlert = document.getElementById('success-alert');
const errorAlert = document.getElementById('error-alert');
const errorMessage = document.getElementById('error-message');
// Hide both alerts initially
successAlert.style.display = 'none';
errorAlert.style.display = 'none';
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.status === "ok") {
// Show success message
successAlert.style.display = 'block';
showToast("Agent imported successfully!", "success");
// Refresh the page after a short delay
setTimeout(() => {
window.location.reload();
}, 2000);
} else if (response.error) {
// Show error message
errorMessage.textContent = response.error;
errorAlert.style.display = 'block';
showToast("Import failed: " + response.error, "error");
}
} catch (e) {
// Handle parsing error
errorMessage.textContent = "Invalid response format";
errorAlert.style.display = 'block';
showToast("Invalid response format", "error");
}
} else {
// Handle HTTP error
errorMessage.textContent = "Server error: " + xhr.status;
errorAlert.style.display = 'block';
showToast("Server error: " + xhr.status, "error");
}
});
// Handle toggle buttons - using pure JavaScript
document.querySelectorAll('.toggle-btn').forEach(button => {
button.addEventListener('click', function() {
const agent = this.getAttribute('data-agent');
const isActive = this.getAttribute('data-active') === 'true';
const endpoint = isActive ? `/api/agent/${agent}/pause` : `/api/agent/${agent}/start`;
// Add animation
this.style.animation = 'pulse 0.5s';
// Create a new XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('PUT', endpoint);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () => {
// Clear animation
this.style.animation = '';
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.status === "ok") {
// Toggle the button state
const newState = !isActive;
this.setAttribute('data-active', newState ? 'true' : 'false');
// Update button text and icon
if (newState) {
this.innerHTML = '<i class="fas fa-pause"></i> Pause';
} else {
this.innerHTML = '<i class="fas fa-play"></i> Start';
}
// Show success toast
const action = isActive ? 'pause' : 'start';
showToast(`Agent "${agent}" ${action}ed successfully`, 'success');
// Update the status badge
updateAgentStatus(agent, newState);
} else if (response.error) {
// Show error toast
showToast(`Error: ${response.error}`, 'error');
}
} catch (e) {
// Handle parsing error
showToast("Invalid response format", 'error');
console.error("Error parsing response:", e);
}
} else {
// Handle HTTP error
showToast(`Server error: ${xhr.status}`, 'error');
}
};
xhr.onerror = () => {
// Clear animation
this.style.animation = '';
showToast("Network error occurred", 'error');
console.error("Network error occurred");
};
// Send the request
xhr.send(JSON.stringify({}));
});
});
});
// Function to update agent status in the UI
function updateAgentStatus(agentName, isOnline) {
// Find the card for this agent
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
const agentTitle = card.querySelector('h2').textContent;
if (agentTitle === agentName) {
// Update the badge
const badge = card.querySelector('.badge');
if (isOnline) {
badge.className = 'badge badge-primary mr-2';
badge.textContent = 'Active';
} else {
badge.className = 'badge badge-secondary mr-2';
badge.textContent = 'Inactive';
}
}
});
}
</script>
</body>
</html>

91
webui/old/views/chat.html Normal file
View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Agent Interface</title>
{{template "old/views/partials/header"}}
<style>
body { overflow: hidden; }
.chat-container { height: 90vh; display: flex; flex-direction: column; }
.chat-messages { overflow-y: auto; flex-grow: 1; }
.htmx-indicator{
opacity:0;
transition: opacity 10ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
/* Loader (https://cssloaders.github.io/) */
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin:15px auto;
position: relative;
color: #FFF;
box-sizing: border-box;
animation: animloader 2s linear infinite;
}
@keyframes animloader {
0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
25% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px; }
50% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px; }
75% { box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
100% { box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px; }
}
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans" hx-ext="sse" sse-connect="/sse/{{.Name}}">
{{template "old/views/partials/menu"}}
<div class="chat-container bg-gray-800 shadow-lg rounded-lg" >
<!-- Chat Header -->
<div class="border-b border-gray-700 p-4">
<h1 class="text-lg font-semibold">Talk to '{{.Name}}'</h1>
</div>
<!-- Chat Messages -->
<div class="chat-messages p-4">
<!-- Client Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Clients:</h2>
<div id="clients" class="text-sm text-gray-300">
<!-- Status updates dynamically here -->
<div sse-swap="clients"></div>
</div>
</div>
<!-- HUD Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Status:</h2>
<div id="hud" class="text-sm text-gray-300">
<!-- Status updates dynamically here -->
<div sse-swap="hud"></div>
</div>
</div>
<div sse-swap="messages" hx-swap="beforeend" id="messages" hx-on:htmx:after-settle="document.getElementById('messages').scrollIntoView(false)"></div>
</div>
<!-- Agent Status Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Agent:</h2>
<div id="agentStatus" class="text-sm text-gray-300">
<!-- Status updates dynamically here -->
<div sse-swap="status" ></div>
</div>
</div>
<!-- Message Input -->
<div class="p-4 border-t border-gray-700">
<div sse-swap="message_status"></div>
<input id="inputMessage" name="message" type="text" hx-post="/old/chat/{{.Name}}" hx-target="#results" hx-indicator=".htmx-indicator"
class="p-2 border rounded w-full bg-gray-600 text-white placeholder-gray-300" placeholder="Type a message..." _="on htmx:afterRequest set my value to ''">
<div class="my-2 htmx-indicator" ></div>
<div id="results" class="flex justify-center"></div>
</div>
</div>
</body>
</html>

154
webui/old/views/create.html Normal file
View File

@@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Create New Agent</title>
{{template "old/views/partials/header"}}
<script src="/old/public/js/wizard.js"></script>
<link rel="stylesheet" href="/old/public/css/wizard.css">
<script src="/old/public/js/connector-templates.js"></script>
<script src="/old/public/js/agent-form.js"></script>
</head>
<body>
{{template "old/views/partials/menu"}}
<div class="container">
<div class="section-box">
<h1>Create New Agent</h1>
<form id="create-agent-form" action="/api/agent/create" method="POST">
{{template "old/views/partials/agent-form" . }}
<button type="submit" id="create-button" data-original-text="<i class='fas fa-robot'></i> Create Agent">
<i class="fas fa-robot"></i> Create Agent
</button>
</form>
</div>
<!-- Response Messages Container -->
<div id="response-container">
<!-- Alert messages will be shown here -->
<div id="success-alert" class="alert alert-success" style="display: none;">
Agent created successfully! Redirecting to agent list...
</div>
<div id="error-alert" class="alert alert-error" style="display: none;">
<span id="error-message">Error creating agent.</span>
</div>
</div>
</div>
<!-- Toast notification container -->
<div id="toast" class="toast">
<span id="toast-message"></span>
</div>
<script>
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const promptBlocks = `{{ range .PromptBlocks }}<option value="{{.}}">{{.}}</option>{{ end }}`;
document.addEventListener('DOMContentLoaded', function() {
// Initialize common form components
initAgentFormCommon({
actions: actions,
connectors: connectors,
promptBlocks: promptBlocks
});
// Form submission handling
const form = document.getElementById('create-agent-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
// Show loading state
const createButton = document.getElementById('create-button');
const originalButtonText = createButton.innerHTML;
createButton.setAttribute('data-original-text', originalButtonText);
createButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...';
createButton.disabled = true;
// Build a structured data object
const formData = new FormData(form);
const jsonData = AgentFormUtils.processFormData(formData);
// Process special fields
jsonData.connectors = AgentFormUtils.processConnectors(createButton);
if (jsonData.connectors === null) return; // Validation failed
jsonData.mcp_servers = AgentFormUtils.processMCPServers();
jsonData.actions = AgentFormUtils.processActions(createButton);
if (jsonData.actions === null) return; // Validation failed
jsonData.promptblocks = AgentFormUtils.processPromptBlocks(createButton);
if (jsonData.promptblocks === null) return; // Validation failed
// Send the structured data as JSON
fetch('/api/agent/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(jsonData)
})
.then(response => response.json())
.then(data => {
const successAlert = document.getElementById('success-alert');
const errorAlert = document.getElementById('error-alert');
const errorMessage = document.getElementById('error-message');
// Hide both alerts initially
successAlert.style.display = 'none';
errorAlert.style.display = 'none';
if (data.status === "ok") {
// Show success toast
showToast('Agent created successfully!', 'success');
// Show success message
successAlert.style.display = 'block';
// Redirect to agent list page after a delay
setTimeout(() => {
window.location.href = '/old/agents';
}, 2000);
} else if (data.error) {
// Show error toast
showToast('Error: ' + data.error, 'error');
// Show error message
errorMessage.textContent = data.error;
errorAlert.style.display = 'block';
// Restore button state
createButton.innerHTML = originalButtonText;
createButton.disabled = false;
} else {
// Handle unexpected response format
showToast('Unexpected response format', 'error');
errorMessage.textContent = "Unexpected response format";
errorAlert.style.display = 'block';
// Restore button state
createButton.innerHTML = originalButtonText;
createButton.disabled = false;
}
})
.catch(error => {
// Handle network or other errors
showToast('Network error: ' + error.message, 'error');
const errorAlert = document.getElementById('error-alert');
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = "Network error: " + error.message;
errorAlert.style.display = 'block';
// Restore button state
createButton.innerHTML = originalButtonText;
createButton.disabled = false;
});
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,600 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Create Agent Group</title>
{{template "old/views/partials/header"}}
<script src="/old/public/js/wizard.js"></script>
<link rel="stylesheet" href="/old/public/css/wizard.css">
<script src="/old/public/js/connector-templates.js"></script>
<script src="/old/public/js/agent-form.js"></script>
<style>
.agent-profile {
border: 1px solid var(--medium-bg);
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
background-color: var(--lighter-bg);
position: relative;
transition: all 0.3s ease;
}
.agent-profile:hover {
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.agent-profile h3 {
color: var(--primary);
text-shadow: var(--neon-glow);
margin-top: 0;
margin-bottom: 15px;
border-bottom: 1px solid var(--medium-bg);
padding-bottom: 10px;
}
.agent-profile .description {
color: var(--text);
font-size: 0.9rem;
margin-bottom: 15px;
}
.agent-profile .system-prompt {
background-color: var(--darker-bg);
border-radius: 6px;
padding: 10px;
font-size: 0.85rem;
max-height: 150px;
overflow-y: auto;
margin-bottom: 10px;
white-space: pre-wrap;
}
.agent-profile.selected {
border: 2px solid var(--primary);
background-color: rgba(var(--primary-rgb), 0.1);
}
.agent-profile .select-checkbox {
position: absolute;
top: 10px;
right: 10px;
}
.page-section {
display: none;
animation: fadeIn 0.5s;
}
.page-section.active {
display: block;
}
.progress-container {
display: flex;
justify-content: center;
margin-bottom: 30px;
}
.progress-step {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
padding: 0 20px;
}
.progress-step:not(:last-child)::after {
content: '';
position: absolute;
top: 12px;
right: -30px;
width: 60px;
height: 3px;
background-color: var(--medium-bg);
}
.progress-step.active:not(:last-child)::after {
background-color: var(--primary);
}
.step-circle {
width: 28px;
height: 28px;
border-radius: 50%;
background-color: var(--medium-bg);
display: flex;
justify-content: center;
align-items: center;
color: var(--text);
margin-bottom: 8px;
transition: all 0.3s ease;
}
.progress-step.active .step-circle {
background-color: var(--primary);
box-shadow: 0 0 10px var(--primary);
}
.step-label {
font-size: 0.9rem;
color: var(--muted-text);
transition: all 0.3s ease;
}
.progress-step.active .step-label {
color: var(--primary);
font-weight: bold;
}
.prompt-container {
margin-bottom: 30px;
}
.prompt-container textarea {
width: 100%;
min-height: 120px;
padding: 15px;
border-radius: 6px;
background-color: var(--lighter-bg);
border: 1px solid var(--medium-bg);
color: var(--text);
font-size: 1rem;
resize: vertical;
}
.action-buttons {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.select-all-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.loader {
display: none;
text-align: center;
margin: 40px 0;
}
.loader i {
color: var(--primary);
font-size: 2rem;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Make form elements auto in group mode */
#wizard-container .form-section {
height: auto;
overflow: visible;
}
.info-message {
background-color: rgba(var(--primary-rgb), 0.1);
border-left: 4px solid var(--primary);
padding: 15px;
margin: 20px 0;
border-radius: 0 8px 8px 0;
display: flex;
align-items: center;
}
.info-message i {
font-size: 1.5rem;
color: var(--primary);
margin-right: 15px;
}
.info-message-content {
flex: 1;
}
.info-message-content h4 {
margin-top: 0;
margin-bottom: 5px;
color: var(--primary);
}
.info-message-content p {
margin-bottom: 0;
}
</style>
</head>
<body>
{{template "old/views/partials/menu"}}
<div class="container">
<div class="section-box">
<h1>Create Agent Group</h1>
<!-- Progress Bar -->
<div class="progress-container">
<div class="progress-step active" data-step="1">
<div class="step-circle">1</div>
<div class="step-label">Generate Profiles</div>
</div>
<div class="progress-step" data-step="2">
<div class="step-circle">2</div>
<div class="step-label">Review & Select</div>
</div>
<div class="progress-step" data-step="3">
<div class="step-circle">3</div>
<div class="step-label">Configure Settings</div>
</div>
</div>
<!-- Step 1: Generate Profiles -->
<div id="step1" class="page-section active">
<h2>Generate Agent Profiles</h2>
<p>Describe the group of agents you want to create. Be specific about their roles, relationships, and purpose.</p>
<div class="prompt-container">
<textarea id="group-description" placeholder="Example: Create a team of agents for a software development project including a project manager, developer, tester, and designer. They should collaborate to build web applications."></textarea>
</div>
<div class="action-buttons">
<button type="button" id="generate-profiles-btn" class="action-btn">
<i class="fas fa-magic"></i> Generate Profiles
</button>
</div>
</div>
<!-- Loader -->
<div id="loader" class="loader">
<i class="fas fa-spinner fa-spin"></i>
<p>Generating agent profiles...</p>
</div>
<!-- Step 2: Review & Select Profiles -->
<div id="step2" class="page-section">
<h2>Review & Select Agent Profiles</h2>
<p>Select the agents you want to create. You can customize their details before creation.</p>
<div class="select-all-container">
<label for="select-all" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" id="select-all">
<span class="checkmark"></span>
</span>
Select All
</label>
</div>
<div id="agent-profiles-container">
<!-- Agent profiles will be generated here -->
</div>
<div class="action-buttons">
<button type="button" id="back-to-step1-btn" class="nav-btn">
<i class="fas fa-arrow-left"></i> Back
</button>
<button type="button" id="to-step3-btn" class="action-btn">
Continue <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>
<!-- Step 3: Common Settings -->
<div id="step3" class="page-section">
<h2>Configure Common Settings</h2>
<p>Configure common settings for all selected agents. These settings will be applied to each agent.</p>
<form id="group-settings-form">
<!-- Informative message about profile data -->
<div class="info-message">
<i class="fas fa-info-circle"></i>
<div class="info-message-content">
<h4>Basic Information from Profiles</h4>
<p>The name, description, and system prompt for each agent will be taken from the profiles you selected in the previous step.</p>
</div>
</div>
<!-- Use the existing agent-form partial -->
<div id="group-agent-form">
{{template "old/views/partials/agent-form" . }}
</div>
</form>
<div class="action-buttons">
<button type="button" id="back-to-step2-btn" class="nav-btn">
<i class="fas fa-arrow-left"></i> Back
</button>
<button type="button" id="create-group-btn" class="action-btn" data-original-text="<i class='fas fa-users'></i> Create Agent Group">
<i class="fas fa-users"></i> Create Agent Group
</button>
</div>
</div>
</div>
<!-- Response Messages Container -->
<div id="response-container">
<!-- Alert messages will be shown here -->
<div id="success-alert" class="alert alert-success" style="display: none;">
Agents created successfully! Redirecting to agent list...
</div>
<div id="error-alert" class="alert alert-error" style="display: none;">
<span id="error-message">Error creating agents.</span>
</div>
</div>
</div>
<!-- Toast notification container -->
<div id="toast" class="toast">
<span id="toast-message"></span>
</div>
<script>
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const promptBlocks = `{{ range .PromptBlocks }}<option value="{{.}}">{{.}}</option>{{ end }}`;
// Store generated agent profiles
let agentProfiles = [];
document.addEventListener('DOMContentLoaded', function() {
// Initialize the form components
initAgentFormCommon({
actions: actions,
connectors: connectors,
promptBlocks: promptBlocks
});
// Hide the Basic Information section
const basicSection = document.getElementById('basic-section');
if (basicSection) {
basicSection.style.display = 'none';
}
// Update the wizard navigation items to skip Basic Information
const basicNavItem = document.querySelector('.wizard-nav-item[data-target="basic-section"]');
if (basicNavItem) {
basicNavItem.style.display = 'none';
}
// Make sure Connectors section is active by default
const connectorsSection = document.getElementById('connectors-section');
if (connectorsSection) {
document.querySelectorAll('.form-section').forEach(section => {
section.classList.remove('active');
});
connectorsSection.classList.add('active');
}
// Update the active nav item
const connectorsNavItem = document.querySelector('.wizard-nav-item[data-target="connectors-section"]');
if (connectorsNavItem) {
document.querySelectorAll('.wizard-nav-item').forEach(item => {
item.classList.remove('active');
});
connectorsNavItem.classList.add('active');
}
// Update the current step label
const currentStepLabel = document.getElementById('currentStepLabel');
if (currentStepLabel) {
currentStepLabel.textContent = 'Connectors';
}
// Navigation between steps
const goToStep = (stepNumber) => {
// Hide all steps
document.querySelectorAll('.page-section').forEach(section => {
section.classList.remove('active');
});
// Show the target step
document.getElementById(`step${stepNumber}`).classList.add('active');
// Update progress bar
document.querySelectorAll('.progress-step').forEach(step => {
step.classList.remove('active');
if (parseInt(step.dataset.step) <= stepNumber) {
step.classList.add('active');
}
});
};
// Step 1: Generate Profiles
document.getElementById('generate-profiles-btn').addEventListener('click', function() {
const description = document.getElementById('group-description').value.trim();
if (!description) {
showToast('Please enter a description for your agent group', 'error');
return;
}
// Show loader
document.getElementById('loader').style.display = 'block';
document.getElementById('step1').style.display = 'none';
// Send request to generate profiles
fetch('/api/agent/group/generateProfiles', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ description: description })
})
.then(response => response.json())
.then(data => {
// Hide loader
document.getElementById('loader').style.display = 'none';
agentProfiles = Array.isArray(data) ? data : [];
if (agentProfiles.length === 0) {
showToast('No agent profiles were generated. Please try again with a more detailed description.', 'error');
document.getElementById('step1').style.display = 'block';
return;
}
// Render agent profiles
renderAgentProfiles();
// Go to step 2
goToStep(2);
})
.catch(error => {
document.getElementById('loader').style.display = 'none';
document.getElementById('step1').style.display = 'block';
showToast('Error generating profiles: ' + error.message, 'error');
console.error('Error:', error);
});
});
// Render agent profiles in step 2
function renderAgentProfiles() {
const container = document.getElementById('agent-profiles-container');
container.innerHTML = '';
agentProfiles.forEach((profile, index) => {
const profileElement = document.createElement('div');
profileElement.className = 'agent-profile';
profileElement.dataset.index = index;
profileElement.innerHTML = `
<label class="select-checkbox checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" class="profile-checkbox" checked>
<span class="checkmark"></span>
</span>
</label>
<h3>${profile.name}</h3>
<div class="description">${profile.description}</div>
<div class="system-prompt">${profile.system_prompt}</div>
`;
profileElement.querySelector('.profile-checkbox').addEventListener('change', function() {
profileElement.classList.toggle('selected', this.checked);
updateSelectAllCheckbox();
});
// Initially set as selected
profileElement.classList.add('selected');
container.appendChild(profileElement);
});
}
// Select all checkbox functionality
document.getElementById('select-all').addEventListener('change', function() {
const isChecked = this.checked;
document.querySelectorAll('.profile-checkbox').forEach(checkbox => {
checkbox.checked = isChecked;
checkbox.closest('.agent-profile').classList.toggle('selected', isChecked);
});
});
function updateSelectAllCheckbox() {
const checkboxes = document.querySelectorAll('.profile-checkbox');
const selectAllCheckbox = document.getElementById('select-all');
const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked);
const someChecked = Array.from(checkboxes).some(checkbox => checkbox.checked);
selectAllCheckbox.checked = allChecked;
selectAllCheckbox.indeterminate = !allChecked && someChecked;
}
// Navigation buttons
document.getElementById('back-to-step1-btn').addEventListener('click', () => goToStep(1));
document.getElementById('to-step3-btn').addEventListener('click', () => {
// Check if at least one profile is selected
const selectedProfiles = document.querySelectorAll('.profile-checkbox:checked');
if (selectedProfiles.length === 0) {
showToast('Please select at least one agent profile', 'error');
return;
}
goToStep(3);
});
document.getElementById('back-to-step2-btn').addEventListener('click', () => goToStep(2));
// Create group button
document.getElementById('create-group-btn').addEventListener('click', function() {
// Get selected profiles
const selectedProfileIndices = Array.from(document.querySelectorAll('.profile-checkbox:checked'))
.map(checkbox => parseInt(checkbox.closest('.agent-profile').dataset.index));
if (selectedProfileIndices.length === 0) {
showToast('Please select at least one agent profile', 'error');
return;
}
const selectedProfiles = selectedProfileIndices.map(index => agentProfiles[index]);
// Process form data for common settings
const formData = new FormData(document.getElementById('group-settings-form'));
const commonSettings = AgentFormUtils.processFormData(formData);
// Process special fields
commonSettings.connectors = AgentFormUtils.processConnectors(this);
if (commonSettings.connectors === null) return; // Validation failed
commonSettings.mcp_servers = AgentFormUtils.processMCPServers();
commonSettings.actions = AgentFormUtils.processActions(this);
if (commonSettings.actions === null) return; // Validation failed
commonSettings.promptblocks = AgentFormUtils.processPromptBlocks(this);
if (commonSettings.promptblocks === null) return; // Validation failed
// Show loading state
const createButton = document.getElementById('create-group-btn');
const originalButtonText = createButton.innerHTML;
createButton.setAttribute('data-original-text', originalButtonText);
createButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...';
createButton.disabled = true;
// Create payload
const payload = {
agents: selectedProfiles,
agent_config: commonSettings
};
// Send request to create agents
fetch('/api/agent/group/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
const successAlert = document.getElementById('success-alert');
const errorAlert = document.getElementById('error-alert');
const errorMessage = document.getElementById('error-message');
// Hide both alerts initially
successAlert.style.display = 'none';
errorAlert.style.display = 'none';
if (data.status === "ok") {
// Show success toast
showToast(`${selectedProfiles.length} agent(s) created successfully!`, 'success');
// Show success message
successAlert.style.display = 'block';
// Redirect to agent list page after a delay
setTimeout(() => {
window.location.href = '/old/agents';
}, 2000);
} else if (data.error) {
// Show error toast
showToast('Error: ' + data.error, 'error');
// Show error message
errorMessage.textContent = data.error;
errorAlert.style.display = 'block';
// Restore button state
createButton.innerHTML = originalButtonText;
createButton.disabled = false;
} else {
// Handle unexpected response format
showToast('Unexpected response format', 'error');
errorMessage.textContent = "Unexpected response format";
errorAlert.style.display = 'block';
// Restore button state
createButton.innerHTML = originalButtonText;
createButton.disabled = false;
}
})
.catch(error => {
// Handle network or other errors
showToast('Network error: ' + error.message, 'error');
const errorAlert = document.getElementById('error-alert');
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = "Network error: " + error.message;
errorAlert.style.display = 'block';
// Restore button state
createButton.innerHTML = originalButtonText;
createButton.disabled = false;
});
});
});
// Toast notification function - assuming this exists in your global scope
</script>
</body>
</html>

224
webui/old/views/index.html Normal file
View File

@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Assistant Dashboard</title>
{{template "old/views/partials/header"}}
<style>
.image-container {
display: flex;
justify-content: center;
margin: 40px 0;
position: relative;
}
.image-container img {
filter: drop-shadow(0 0 15px rgba(94, 0, 255, 0.6));
transition: all 0.5s ease;
}
.image-container:hover img {
transform: scale(1.05);
filter: drop-shadow(0 0 25px rgba(0, 255, 149, 0.8));
}
.image-container::after {
content: "";
position: absolute;
bottom: -15px;
left: 50%;
transform: translateX(-50%);
width: 40%;
height: 2px;
background: linear-gradient(90deg, transparent, var(--primary), var(--secondary), var(--tertiary), transparent);
}
.dashboard-title {
text-align: center;
margin-bottom: 40px;
font-size: 2.5rem;
letter-spacing: 2px;
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
color: var(--primary);
text-shadow: var(--neon-glow);
animation: gentlePulse 3s infinite;
}
/* Gentle pulse animation for the title */
@keyframes gentlePulse {
0% { text-shadow: 0 0 7px var(--primary), 0 0 10px var(--primary); }
50% { text-shadow: 0 0 12px var(--primary), 0 0 20px var(--primary); }
100% { text-shadow: 0 0 7px var(--primary), 0 0 10px var(--primary); }
}
/* Subtle glitch effect for hover */
@keyframes subtleGlitch {
0% { transform: translateX(-50%); }
20% { transform: translateX(-50%) translate(-1px, 1px); }
40% { transform: translateX(-50%) translate(-1px, -1px); }
60% { transform: translateX(-50%) translate(1px, 1px); }
80% { transform: translateX(-50%) translate(1px, -1px); }
100% { transform: translateX(-50%); }
}
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 30px;
margin-top: 30px;
}
.user-info {
position: absolute;
top: 20px;
right: 20px;
}
.dashboard-stats {
display: flex;
justify-content: center;
gap: 20px;
margin: 20px 0 40px;
}
.stat-item {
background: rgba(17, 17, 17, 0.7);
border-radius: 8px;
padding: 15px;
min-width: 150px;
text-align: center;
position: relative;
overflow: hidden;
border: 1px solid rgba(94, 0, 255, 0.2);
}
.stat-item::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
opacity: 0.7;
}
.stat-count {
font-size: 2rem;
font-weight: 700;
color: var(--primary);
text-shadow: var(--neon-glow);
}
.stat-label {
font-size: 0.9rem;
color: #cccccc;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Badge positioning */
.card {
position: relative;
}
.badge {
position: absolute;
top: 15px;
right: 15px;
}
</style>
</head>
<body>
{{template "old/views/partials/menu"}}
<div class="container">
<div class="image-container">
<img src="/old/public/logo_1.png" width="250" alt="Company Logo">
</div>
<h1 class="dashboard-title">LocalAgent</h1>
<!-- Simple stats display -->
<div class="dashboard-stats">
<div class="stat-item">
<div class="stat-count">{{.Actions}}</div>
<div class="stat-label">Available Actions</div>
</div>
<div class="stat-item">
<div class="stat-count">{{.Connectors}}</div>
<div class="stat-label">Available Connectors</div>
</div>
<div class="stat-item">
<div class="stat-count">{{ .AgentCount }}</div>
<div class="stat-label">Agents</div>
</div>
</div>
<div class="cards-container">
<!-- Card for Agent List Page -->
<a href="/old/agents" class="card-link">
<div class="card">
<h2><i class="fas fa-robot"></i> Agent List</h2>
<p>View and manage your list of agents, including detailed profiles and statistics.</p>
</div>
</a>
<!-- Card for Create Agent -->
<a href="/old/create" class="card-link">
<div class="card">
<h2><i class="fas fa-plus-circle"></i> Create Agent</h2>
<p>Create a new intelligent agent with custom behaviors, connectors, and actions.</p>
</div>
</a>
<!-- Additional Cards for Future Features -->
<a href="#" class="card-link">
<div class="card">
<h2><i class="fas fa-chart-line"></i> Analytics</h2>
<p>View performance metrics and insights from your agent operations.</p>
<span class="badge badge-secondary">Coming Soon</span>
</div>
</a>
<a href="#" class="card-link">
<div class="card">
<h2><i class="fas fa-cogs"></i> Settings</h2>
<p>Configure system preferences and global settings for all agents.</p>
<span class="badge badge-secondary">Coming Soon</span>
</div>
</a>
</div>
</div>
<!-- Toast notification container -->
<div id="toast" class="toast">
<span id="toast-message"></span>
</div>
<script>
// Add the controlled glitch effect to dashboard title
document.addEventListener('DOMContentLoaded', function() {
const title = document.querySelector('.dashboard-title');
title.addEventListener('mouseover', function() {
// Use the more subtle glitch animation
this.style.animation = 'subtleGlitch 0.5s infinite';
});
title.addEventListener('mouseout', function() {
// Return to gentle pulse animation
this.style.animation = 'gentlePulse 3s infinite';
});
// Welcome toast notification
setTimeout(() => {
showToast('Welcome to Smart Assistant Dashboard', 'success');
}, 1000);
});
</script>
</body>
</html>

215
webui/old/views/login.html Normal file
View File

@@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="en">
{{template "old/views/partials/header" .}}
<body class="bg-gradient-to-br from-gray-900 to-gray-950 text-gray-200">
<div class="flex flex-col min-h-screen">
{{template "old/views/partials/menu" .}}
<div class="container mx-auto px-4 py-8 flex-grow flex items-center justify-center">
<!-- Auth Card -->
<div class="max-w-md w-full bg-gray-800/90 border border-gray-700/50 rounded-xl overflow-hidden shadow-xl">
<div class="animation-container">
<div class="text-overlay">
<!-- <i class="fas fa-circle-nodes text-5xl text-blue-400 mb-2"></i> -->
</div>
</div>
<div class="p-8">
<div class="text-center mb-6">
<h2 class="text-2xl font-bold text-white">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-indigo-400">
Authorization Required
</span>
</h2>
<p class="text-gray-400 mt-2">Please enter your access token to continue</p>
</div>
<form id="login-form" class="space-y-6" onsubmit="login(); return false;">
<div>
<label for="token" class="block text-sm font-medium text-gray-300 mb-2">Access Token</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-key text-gray-500"></i>
</div>
<input
type="password"
id="token"
name="token"
placeholder="Enter your token"
class="bg-gray-700/50 border border-gray-600 text-white placeholder-gray-400 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5"
required
/>
</div>
</div>
<div>
<button
type="submit"
class="group w-full flex items-center justify-center bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-[1.02] hover:shadow-lg font-medium"
>
<i class="fas fa-sign-in-alt mr-2"></i>
<span>Login</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</button>
</div>
</form>
<div class="mt-8 pt-6 border-t border-gray-700/50 text-center text-sm text-gray-400">
<div class="flex items-center justify-center mb-2">
<i class="fas fa-shield-alt mr-2 text-blue-400"></i>
<span>Instance is token protected</span>
</div>
<p>Current time (UTC): <span id="current-time">{{.CurrentDate}}</span></p>
</div>
</div>
</div>
</div>
</div>
<script>
function login() {
const token = document.getElementById('token').value;
if (!token.trim()) {
// Show error with fading effect
const form = document.getElementById('login-form');
const errorMsg = document.createElement('div');
errorMsg.className = 'p-3 mt-4 bg-red-900/50 text-red-200 rounded-lg border border-red-700/50 text-sm flex items-center';
errorMsg.innerHTML = '<i class="fas fa-exclamation-circle mr-2"></i> Please enter a valid token';
// Remove any existing error message
const existingError = form.querySelector('.bg-red-900/50');
if (existingError) form.removeChild(existingError);
// Add new error message with animation
form.appendChild(errorMsg);
setTimeout(() => {
errorMsg.style.opacity = '0';
errorMsg.style.transition = 'opacity 0.5s ease';
setTimeout(() => errorMsg.remove(), 500);
}, 3000);
return;
}
var date = new Date();
date.setTime(date.getTime() + (24*60*60*1000));
document.cookie = `token=${token}; expires=${date.toGMTString()}; path=/`;
// Show loading state
const button = document.querySelector('button[type="submit"]');
const originalContent = button.innerHTML;
button.disabled = true;
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Authenticating...';
button.classList.add('bg-gray-600');
// Reload after short delay to show loading state
setTimeout(() => {
window.location.reload();
}, 800);
}
// Update current time
function updateCurrentTime() {
const timeElement = document.getElementById('current-time');
if (timeElement) {
const now = new Date();
const year = now.getUTCFullYear();
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
const day = String(now.getUTCDate()).padStart(2, '0');
const hours = String(now.getUTCHours()).padStart(2, '0');
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
timeElement.textContent = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
}
// Initialize current time and update it every second
updateCurrentTime();
setInterval(updateCurrentTime, 1000);
// Add subtle particle animation to the background
document.addEventListener('DOMContentLoaded', function() {
const animContainer = document.querySelector('.animation-container');
if (animContainer) {
const canvas = document.createElement('canvas');
animContainer.appendChild(canvas);
const ctx = canvas.getContext('2d');
canvas.width = animContainer.offsetWidth;
canvas.height = animContainer.offsetHeight;
// Create particles
const particles = [];
const particleCount = 30;
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: Math.random() * 3 + 1,
color: `rgba(${Math.random() * 50 + 50}, ${Math.random() * 100 + 100}, ${Math.random() * 155 + 100}, ${Math.random() * 0.4 + 0.1})`,
speedX: Math.random() * 0.5 - 0.25,
speedY: Math.random() * 0.5 - 0.25
});
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
particle.x += particle.speedX;
particle.y += particle.speedY;
// Bounce off edges
if (particle.x < 0 || particle.x > canvas.width) {
particle.speedX = -particle.speedX;
}
if (particle.y < 0 || particle.y > canvas.height) {
particle.speedY = -particle.speedY;
}
// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
});
// Connect nearby particles with lines
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(100, 150, 255, ${0.1 * (1 - distance / 100)})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
}
// Start animation
animate();
// Resize handling
window.addEventListener('resize', () => {
canvas.width = animContainer.offsetWidth;
canvas.height = animContainer.offsetHeight;
});
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,307 @@
<div class="agent-form-container">
<!-- Wizard Sidebar -->
<div class="wizard-sidebar">
<ul class="wizard-nav">
<li class="wizard-nav-item active" data-target="basic-section">
<i class="fas fa-info-circle"></i> Basic Information
</li>
<li class="wizard-nav-item" data-target="connectors-section">
<i class="fas fa-plug"></i> Connectors
</li>
<li class="wizard-nav-item" data-target="actions-section">
<i class="fas fa-bolt"></i> Actions
</li>
<li class="wizard-nav-item" data-target="mcp-section">
<i class="fas fa-server"></i> MCP Servers
</li>
<li class="wizard-nav-item" data-target="memory-section">
<i class="fas fa-memory"></i> Memory Settings
</li>
<li class="wizard-nav-item" data-target="model-section">
<i class="fas fa-robot"></i> Model Settings
</li>
<li class="wizard-nav-item" data-target="prompts-section">
<i class="fas fa-comment-alt"></i> Prompts & Goals
</li>
<li class="wizard-nav-item" data-target="advanced-section">
<i class="fas fa-cogs"></i> Advanced Settings
</li>
</ul>
</div>
<!-- Form Content Area -->
<div class="form-content-area">
<!-- Basic Information Section -->
<div class="form-section active" id="basic-section">
<h3 class="section-title">Basic Information</h3>
<div class="mb-4">
<label for="name">Name</label>
{{ if .Name }}
<input type="text" name="name" id="name" placeholder="Name" value="{{.Name}}" readonly >
{{ else }}
<input type="text" name="name" id="name" placeholder="Name">
{{ end }}
</div>
<div class="mb-4">
<label for="description">Description</label>
<textarea name="description" id="description" placeholder="Description"></textarea>
</div>
<div class="mb-4">
<label for="identity_guidance">Identity Guidance</label>
<textarea name="identity_guidance" id="identity_guidance" placeholder="Identity Guidance"></textarea>
</div>
<div class="mb-4">
<label for="random_identity" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="random_identity" id="random_identity">
<span class="checkmark"></span>
</span>
Random Identity
</label>
</div>
<div class="mb-4">
<label for="hud" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="hud" id="hud">
<span class="checkmark"></span>
</span>
HUD
</label>
</div>
</div>
<!-- Connectors Section -->
<div class="form-section" id="connectors-section">
<h3 class="section-title">Connectors</h3>
<div id="connectorsSection">
<!-- Connectors will be added here dynamically -->
</div>
<div class="button-container">
<button type="button" id="addConnectorButton" class="action-btn">
<i class="fas fa-plus-circle"></i> Add Connector
</button>
</div>
</div>
<!-- Actions Section -->
<div class="form-section" id="actions-section">
<h3 class="section-title">Actions</h3>
<div class="mb-4" id="action_box">
<!-- Actions will be added here dynamically -->
</div>
<div class="button-container">
<button id="action_button" type="button" class="action-btn">
<i class="fas fa-plus-circle"></i> Add Action
</button>
</div>
</div>
<!-- MCP Servers Section -->
<div class="form-section" id="mcp-section">
<h3 class="section-title">MCP Servers</h3>
<div id="mcpSection">
<!-- MCP servers will be added here dynamically -->
</div>
<div class="button-container">
<button type="button" id="addMCPButton" class="action-btn">
<i class="fas fa-plus-circle"></i> Add MCP Server
</button>
</div>
</div>
<!-- Memory Settings Section -->
<div class="form-section" id="memory-section">
<h3 class="section-title">Memory Settings</h3>
<div class="mb-4">
<label for="enable_kb" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="enable_kb" id="enable_kb">
<span class="checkmark"></span>
</span>
Enable Knowledge Base
</label>
</div>
<div class="mb-4">
<label for="kb_results">Knowledge Base Results</label>
<input type="number" name="kb_results" id="kb_results" placeholder="3">
</div>
<div class="mb-4">
<label for="long_term_memory" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="long_term_memory" id="long_term_memory">
<span class="checkmark"></span>
</span>
Long Term Memory
</label>
</div>
<div class="mb-4">
<label for="summary_long_term_memory" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="summary_long_term_memory" id="summary_long_term_memory">
<span class="checkmark"></span>
</span>
Long Term Memory (Summarize!)
</label>
</div>
</div>
<!-- Model Settings Section -->
<div class="form-section" id="model-section">
<h3 class="section-title">Model Settings</h3>
<div class="mb-4">
<label for="model">Model </label>
<input type="text" name="model" id="model" placeholder="Model name">
</div>
<div class="mb-4">
<label for="multimodal_model">Multimodal Model </label>
<input type="text" name="multimodal_model" id="multimodal_model" placeholder="Model name">
</div>
<div class="mb-4">
<label for="api_url">API URL </label>
<input type="text" name="api_url" id="api_url" placeholder="API URL">
</div>
<div class="mb-4">
<label for="api_key">API Key </label>
<input type="text" name="api_key" id="api_key" placeholder="API Key">
</div>
<div class="mb-4">
<label for="local_rag_url">LocalRAG API URL </label>
<input type="text" name="local_rag_url" id="local_rag_url" placeholder="LocalRAG API URL">
</div>
<div class="mb-4">
<label for="local_rag_api_key">LocalRAG API Key </label>
<input type="text" name="local_rag_api_key" id="local_rag_api_key" placeholder="LocalRAG API Key">
</div>
<div class="mb-4">
<label for="enable_reasoning" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="enable_reasoning" id="enable_reasoning">
<span class="checkmark"></span>
</span>
Enable Reasoning
</label>
</div>
</div>
<!-- Prompts & Goals Section -->
<div class="form-section" id="prompts-section">
<h3 class="section-title">Prompts & Goals</h3>
<div class="mb-4" id="dynamic_box">
<!-- Dynamic prompts will be added here dynamically -->
</div>
<div class="button-container">
<button id="dynamic_button" type="button" class="action-btn">
<i class="fas fa-plus-circle"></i> Add Dynamic Prompt
</button>
</div>
<div class="mb-4">
<label for="system_prompt">System Prompt</label>
<textarea name="system_prompt" id="system_prompt" placeholder="System prompt"></textarea>
</div>
<div class="mb-4">
<label for="permanent_goal">Permanent Goal</label>
<textarea name="permanent_goal" id="permanent_goal" placeholder="Permanent goal"></textarea>
</div>
</div>
<!-- Advanced Settings Section -->
<div class="form-section" id="advanced-section">
<h3 class="section-title">Advanced Settings</h3>
<div class="mb-4">
<label for="standalone_job" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="standalone_job" id="standalone_job">
<span class="checkmark"></span>
</span>
Standalone Job
</label>
</div>
<div class="mb-4">
<label for="initiate_conversations" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="initiate_conversations" id="initiate_conversations">
<span class="checkmark"></span>
</span>
Initiate Conversations
</label>
</div>
<div class="mb-4">
<label for="enable_planning" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="enable_planning" id="enable_planning">
<span class="checkmark"></span>
</span>
Enable Planning
</label>
</div>
<div class="mb-4">
<label for="can_stop_itself" class="checkbox-label">
<span class="checkbox-custom">
<input type="checkbox" name="can_stop_itself" id="can_stop_itself">
<span class="checkmark"></span>
</span>
Can Stop Itself
</label>
</div>
<div class="mb-4">
<label for="periodic_runs">Periodic Runs</label>
<input type="text" name="periodic_runs" id="periodic_runs" placeholder="Periodic Runs">
</div>
</div>
</div>
</div>
<!-- Add navigation controls at the bottom -->
<div class="wizard-controls">
<div class="wizard-controls-left">
<button type="button" id="prevSection" class="nav-btn">
<i class="fas fa-arrow-left"></i> Previous
</button>
</div>
<div class="wizard-controls-center">
<div class="progress-dots" id="progressDots">
<!-- Dots will be added dynamically via JS -->
</div>
<div class="progress-indicator">
<span id="currentStepLabel">Basic Information</span>
</div>
</div>
<div class="wizard-controls-right">
<button type="button" id="nextSection" class="nav-btn">
Next <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>

View File

@@ -0,0 +1,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Permanent+Marker&display=swap" rel="stylesheet">
<script src="https://unpkg.com/htmx.org"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<link rel="stylesheet" href="/old/public/css/styles.css">
<script src="/old/public/js/common.js"></script>

View File

@@ -0,0 +1,105 @@
<nav class="relative z-10 w-full" style="background-color: var(--darker-bg); border-bottom: 1px solid var(--medium-bg);">
<div class="px-6 sm:px-8 lg:px-10">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<!-- Logo container -->
<div class="flex-shrink-0">
<!-- Logo with glow effect -->
<a href="/old" class="flex items-center group">
<div class="relative">
<img src="/old/public/logo_1.png" alt="Logo" class="h-10 w-auto mr-4 transition-transform duration-300 group-hover:scale-105"
style="filter: drop-shadow(0 0 5px var(--primary));">
<!-- Animated scan line on hover -->
<div class="absolute inset-0 overflow-hidden opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-var(--primary) to-transparent opacity-30"
style="height: 10px; animation: scanline 1.5s linear infinite;"></div>
</div>
</div>
<span class="text-xl font-bold transition-colors duration-300"
style="color: var(--primary); text-shadow: var(--neon-glow);">LocalAgent</span>
</a>
</div>
<div class="hidden md:block ml-10">
<div class="flex space-x-4">
<a href="/old" class="px-3 py-2 rounded-md text-lg font-medium text-white hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
<i class="fas fa-home mr-2"></i> Home
<!-- Underline animation -->
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
style="background: linear-gradient(90deg, var(--primary), var(--secondary));"></span>
</a>
<a href="/old/agents" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
<i class="fas fa-users mr-2"></i> Agent List
<!-- Underline animation -->
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
style="background: linear-gradient(90deg, var(--secondary), var(--tertiary));"></span>
</a>
<a href="/old/actions-playground" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
<i class="fas fa-bolt mr-2"></i> Actions Playground
<!-- Underline animation -->
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
style="background: linear-gradient(90deg, var(--tertiary), var(--primary));"></span>
</a>
<a href="/old/group-create" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
<i class="fas fa-users-cog mr-2"></i> Create Agent Group
<!-- Underline animation -->
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
style="background: linear-gradient(90deg, var(--secondary), var(--primary));"></span>
</a>
</div>
</div>
</div>
<div class="flex items-center space-x-4">
<!-- Status badge -->
<div class="hidden md:flex items-center">
<span class="flex items-center text-sm">
<span class="w-2 h-2 rounded-full mr-2"
style="background-color: var(--primary); box-shadow: 0 0 5px var(--primary); animation: pulse 2s infinite;"></span>
<span>State: <span style="color: var(--secondary); text-shadow: var(--pink-glow);">active</span></span>
</span>
</div>
</div>
<div class="md:hidden flex items-center">
<button class="text-gray-400 hover:text-white focus:outline-none focus:text-white transition duration-300"
style="text-shadow: var(--neon-glow);"
onclick="toggleMobileMenu()">
<i class="fas fa-bars fa-lg"></i>
</button>
</div>
</div>
</div>
<!-- Mobile menu, toggle based on menu state -->
<div id="mobile-menu" class="md:hidden hidden"
style="background-color: var(--darker-bg); border-top: 1px solid var(--medium-bg);">
<div class="px-2 pt-2 pb-3 space-y-1">
<a href="/old" class="block px-3 py-2 rounded-md text-base font-medium text-white hover:bg-gray-800 transition duration-300"
style="border-left: 3px solid var(--primary);">
<i class="fas fa-home mr-2"></i> Home
</a>
<a href="/old/agents" class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:bg-gray-800 transition duration-300"
style="border-left: 3px solid var(--secondary);">
<i class="fas fa-users mr-2"></i> Agent List
</a>
<a href="/old/actions-playground" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
<i class="fas fa-bolt mr-2"></i> Actions Playground
<!-- Underline animation -->
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
style="background: linear-gradient(90deg, var(--tertiary), var(--primary));"></span>
</a>
<a href="/old/group-create" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
<i class="fas fa-users-cog mr-2"></i> Create Agent Group
<!-- Underline animation -->
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
style="background: linear-gradient(90deg, var(--secondary), var(--primary));"></span>
</a>
</div>
</div>
</nav>
<br>
<script>
function toggleMobileMenu() {
const mobileMenu = document.getElementById('mobile-menu');
mobileMenu.classList.toggle('hidden');
}
</script>

View File

@@ -0,0 +1,462 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Agent settings {{.Name}}</title>
{{template "old/views/partials/header"}}
<script src="/old/public/js/wizard.js"></script>
<link rel="stylesheet" href="/old/public/css/wizard.css">
<script src="/old/public/js/connector-templates.js"></script>
<script src="/old/public/js/agent-form.js"></script>
</head>
<body>
{{template "old/views/partials/menu"}}
<!-- Toast notification container -->
<div id="toast" class="toast">
<span id="toast-message"></span>
</div>
<div class="container">
<header class="text-center mb-8">
<h1 class="text-3xl md:text-5xl font-bold">Agent settings - {{.Name}}</h1>
</header>
<div class="max-w-4xl mx-auto">
<!-- Agent Configuration Form Section -->
<div class="section-box">
<h2>Edit Agent Configuration</h2>
<form id="edit-agent-form">
<input type="hidden" name="name" id="name" value="{{.Name}}">
{{template "old/views/partials/agent-form" .}}
<button type="submit" id="update-button" class="action-btn" data-original-text="<i class='fas fa-save'></i> Update Agent">
<i class="fas fa-save"></i> Update Agent
</button>
</form>
</div>
<div class="section-box">
<h2>Agent Control</h2>
<div class="button-container">
<button
class="action-btn toggle-btn"
data-agent="{{.Name}}"
data-active="{{.Status}}">
{{if .Status}}
<i class="fas fa-pause"></i> Pause Agent
{{else}}
<i class="fas fa-play"></i> Start Agent
{{end}}
</button>
</div>
</div>
<div class="section-box">
<h2>Export Data</h2>
<p class="mb-4">Export your agent configuration for backup or transfer.</p>
<button
class="action-btn"
onclick="window.location.href='/old/settings/export/{{.Name}}'">
<i class="fas fa-file-export"></i> Export Configuration
</button>
</div>
<div class="section-box">
<h2>Danger Zone</h2>
<p class="mb-4">Permanently delete this agent and all associated data. This action cannot be undone.</p>
<button
class="action-btn"
style="background: linear-gradient(135deg, #ff4545, var(--secondary)); color: white;"
hx-delete="/api/agent/{{.Name}}"
hx-swap="none"
data-action="delete"
data-agent="{{.Name}}">
<i class="fas fa-trash-alt"></i> Delete Agent
</button>
</div>
<div class="user-info">
<span>Agent: {{.Name}}</span>
<span class="timestamp">Last modified: <span id="current-date"></span></span>
</div>
</div>
</div>
<script>
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const promptBlocks = `{{ range .PromptBlocks }}<option value="{{.}}">{{.}}</option>{{ end }}`;
let agentConfig = null;
document.addEventListener('DOMContentLoaded', function() {
// Initialize common form components
initAgentFormCommon({
actions: actions,
connectors: connectors,
promptBlocks: promptBlocks
});
// Load agent configuration when page loads
loadAgentConfig();
// Add event listener for delete button
document.querySelectorAll('[data-action="delete"]').forEach(button => {
button.addEventListener('htmx:afterRequest', function(event) {
handleActionResponse(event, this);
});
});
// Handle toggle button
const toggleButton = document.querySelector('.toggle-btn');
if (toggleButton) {
toggleButton.addEventListener('click', function() {
const agent = this.getAttribute('data-agent');
const isActive = this.getAttribute('data-active') === "true";
const endpoint = isActive ? `/api/agent/${agent}/pause` : `/api/agent/${agent}/start`;
// Add animation
this.style.animation = 'pulse 0.5s';
// Create a new XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('PUT', endpoint);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () => {
// Clear animation
this.style.animation = '';
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.status === "ok") {
// Toggle the button state
const newState = !isActive;
this.setAttribute('data-active', newState.toString());
// Update button text and icon
if (newState) {
this.innerHTML = '<i class="fas fa-pause"></i> Pause Agent';
} else {
this.innerHTML = '<i class="fas fa-play"></i> Start Agent';
}
// Show success toast
const action = isActive ? 'pause' : 'start';
showToast(`Agent "${agent}" ${action}ed successfully`, 'success');
} else if (response.error) {
// Show error toast
showToast(`Error: ${response.error}`, 'error');
}
} catch (e) {
// Handle parsing error
showToast("Invalid response format", 'error');
console.error("Error parsing response:", e);
}
} else {
// Handle HTTP error
showToast(`Server error: ${xhr.status}`, 'error');
}
};
xhr.onerror = () => {
// Clear animation
this.style.animation = '';
showToast("Network error occurred", 'error');
console.error("Network error occurred");
};
// Send the request
xhr.send(JSON.stringify({}));
});
}
// Set current date for timestamp
const now = new Date();
document.getElementById('current-date').textContent = now.toISOString().split('T')[0];
// Handle form submission for updating agent
const form = document.getElementById('edit-agent-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
// Show a loading state
const updateButton = document.getElementById('update-button');
const originalButtonText = updateButton.innerHTML;
updateButton.setAttribute('data-original-text', originalButtonText);
updateButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Updating...';
updateButton.disabled = true;
// Build a structured data object
const formData = new FormData(form);
const jsonData = AgentFormUtils.processFormData(formData);
// Process special fields
jsonData.connectors = AgentFormUtils.processConnectors(updateButton);
if (jsonData.connectors === null) return; // Validation failed
jsonData.mcp_servers = AgentFormUtils.processMCPServers();
jsonData.actions = AgentFormUtils.processActions(updateButton);
if (jsonData.actions === null) return; // Validation failed
jsonData.promptblocks = AgentFormUtils.processPromptBlocks(updateButton);
if (jsonData.promptblocks === null) return; // Validation failed
console.log('Sending data:', jsonData);
// Send the structured data as JSON
fetch(`/api/agent/${jsonData.name}/config`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(jsonData)
})
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.error || `Server error: ${response.status}`);
});
}
return response.json();
})
.then(data => {
// Restore button state
updateButton.innerHTML = originalButtonText;
updateButton.disabled = false;
if (data.status === "ok") {
// Show success toast
showToast('Agent updated successfully!', 'success');
// Reload agent config to get updated values
setTimeout(() => {
loadAgentConfig();
}, 500);
} else if (data.error) {
// Show error toast
showToast('Error: ' + data.error, 'error');
} else {
// Handle unexpected response format
showToast('Unexpected response format', 'error');
}
})
.catch(error => {
// Handle network or other errors
showToast('Error: ' + error.message, 'error');
console.error('Update error:', error);
// Restore button state
updateButton.innerHTML = originalButtonText;
updateButton.disabled = false;
});
});
});
// Function to handle API responses for delete action
function handleActionResponse(event, button) {
const xhr = event.detail.xhr;
const action = button.getAttribute('data-action');
const agent = button.getAttribute('data-agent');
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.status === "ok") {
// Action successful
let message = "";
switch(action) {
case 'delete':
message = `Agent "${agent}" deleted successfully`;
// Redirect to agent list page after short delay for delete
setTimeout(() => {
window.location.href = "/old/agents";
}, 2000);
break;
default:
message = "Operation completed successfully";
}
// Show success message
showToast(message, 'success');
} else if (response.error) {
// Show error message
showToast(`Error: ${response.error}`, 'error');
}
} catch (e) {
// Handle JSON parsing error
showToast("Invalid response format", 'error');
}
} else {
// Handle HTTP error
showToast(`Server error: ${xhr.status}`, 'error');
}
}
// Load agent configuration from server
function loadAgentConfig() {
const agentName = document.getElementById('name').value;
fetch(`/api/agent/${agentName}/config`)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to load agent config: ${response.status}`);
}
return response.json();
})
.then(data => {
agentConfig = data;
populateFormWithConfig(data);
showToast('Agent configuration loaded', 'success');
})
.catch(error => {
console.error('Error loading agent config:', error);
showToast('Error loading agent configuration: ' + error.message, 'error');
});
}
// Populate form with agent configuration
function populateFormWithConfig(config) {
// Clear existing dynamic sections
document.getElementById('connectorsSection').innerHTML = '';
document.getElementById('mcpSection').innerHTML = '';
document.getElementById('action_box').innerHTML = '';
document.getElementById('dynamic_box').innerHTML = '';
// Populate simple fields
document.getElementById('hud').checked = config.hud || false;
document.getElementById('enable_kb').checked = config.enable_kb || false;
document.getElementById('enable_reasoning').checked = config.enable_reasoning || false;
document.getElementById('kb_results').value = config.kb_results || '';
document.getElementById('standalone_job').checked = config.standalone_job || false;
document.getElementById('initiate_conversations').checked = config.initiate_conversations || false;
document.getElementById('enable_planning').checked = config.enable_planning || false;
document.getElementById('can_stop_itself').checked = config.can_stop_itself || false;
document.getElementById('random_identity').checked = config.random_identity || false;
document.getElementById('long_term_memory').checked = config.long_term_memory || false;
document.getElementById('summary_long_term_memory').checked = config.summary_long_term_memory || false;
document.getElementById('identity_guidance').value = config.identity_guidance || '';
document.getElementById('description').value = config.description || '';
document.getElementById('periodic_runs').value = config.periodic_runs || '';
document.getElementById('model').value = config.model || '';
document.getElementById('multimodal_model').value = config.multimodal_model || '';
document.getElementById('api_url').value = config.api_url || '';
document.getElementById('api_key').value = config.api_key || '';
document.getElementById('local_rag_url').value = config.local_rag_url || '';
document.getElementById('local_rag_api_key').value = config.local_rag_token || '';
document.getElementById('permanent_goal').value = config.permanent_goal || '';
document.getElementById('system_prompt').value = config.system_prompt || '';
// Populate connectors
if (config.connectors && Array.isArray(config.connectors)) {
config.connectors.forEach((connector, index) => {
// Add connector section
document.getElementById('addConnectorButton').click();
// Find the added connector elements
const connectorType = document.getElementById(`connectorType${index}`);
// Set values
if (connectorType) {
// First set the connector type
AgentFormUtils.setSelectValue(connectorType, connector.type);
// Parse the config if it's a string (from backend)
let configObj = connector.config;
if (typeof connector.config === 'string') {
try {
configObj = JSON.parse(connector.config);
} catch (e) {
console.error('Error parsing connector config:', e);
configObj = {}; // Fallback to empty object if parsing fails
}
}
// Now render the appropriate form for this connector type with the config values
AgentFormUtils.renderConnectorForm(index, connector.type, configObj);
}
});
}
// Populate MCP servers
if (config.mcp_servers && Array.isArray(config.mcp_servers)) {
config.mcp_servers.forEach((server, index) => {
// Add MCP server section
document.getElementById('addMCPButton').click();
// Find the added MCP server elements
const mcpURL = document.getElementById(`mcpURL${index}`);
const mcpToken = document.getElementById(`mcpToken${index}`);
// Set values
if (mcpURL) {
// If server is a string (old format), use it as URL
if (typeof server === 'string') {
mcpURL.value = server;
}
// If server is an object (new format), extract URL
else if (typeof server === 'object' && server !== null) {
mcpURL.value = server.url || '';
if (mcpToken && server.token) {
mcpToken.value = server.token;
}
}
}
});
}
// Populate actions
if (config.actions && Array.isArray(config.actions)) {
config.actions.forEach((action, index) => {
// Add action section
document.getElementById('action_button').click();
// Find the added action elements
const actionName = document.getElementById(`actionsName${index}`);
const actionConfig = document.getElementById(`actionsConfig${index}`);
// Set values
if (actionName) {
AgentFormUtils.setSelectValue(actionName, action.name);
}
if (actionConfig) {
// Format the config value
AgentFormUtils.formatConfigValue(actionConfig, action.config);
}
});
}
// Populate prompt blocks
if (config.promptblocks && Array.isArray(config.promptblocks)) {
config.promptblocks.forEach((block, index) => {
// Add prompt block section
document.getElementById('dynamic_button').click();
// Find the added prompt block elements
const promptName = document.getElementById(`promptName${index}`);
const promptConfig = document.getElementById(`promptConfig${index}`);
// Set values
if (promptName) {
AgentFormUtils.setSelectValue(promptName, block.name);
}
if (promptConfig) {
// Format the config value
AgentFormUtils.formatConfigValue(promptConfig, block.config);
}
});
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Agent status</title>
{{template "old/views/partials/header"}}
<style>
body { overflow: hidden; }
.chat-container { height: 90vh; display: flex; flex-direction: column; }
.chat-messages { overflow-y: auto; flex-grow: 1; }
.htmx-indicator{
opacity:0;
transition: opacity 10ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
/* Loader (https://cssloaders.github.io/) */
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin:15px auto;
position: relative;
color: #FFF;
box-sizing: border-box;
animation: animloader 2s linear infinite;
}
@keyframes animloader {
0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
25% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px; }
50% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px; }
75% { box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
100% { box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px; }
}
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans" hx-ext="sse" sse-connect="/sse/{{.Name}}">
{{template "old/views/partials/menu"}}
<div class="chat-container bg-gray-800 shadow-lg rounded-lg" >
<!-- Chat Header -->
<div class="border-b border-gray-700 p-4">
<h1 class="text-lg font-semibold">{{.Name}}</h1>
</div>
<!-- Chat Messages -->
<div class="chat-messages p-4">
<div sse-swap="status" hx-swap="afterbegin" id="status"></div>
{{ range .History }}
<!-- Agent Status Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Agent:</h2>
<div id="agentStatus" class="text-sm text-gray-300">
Result: {{.Result}} Action: {{.Action}} Params: {{.Params}} Reasoning: {{.Reasoning}}
</div>
</div>
{{end}}
</div>
</div>
</body>
</html>