chore(ui): Move zombie UI to old
This commit is contained in:
291
webui/old/views/actions.html
Normal file
291
webui/old/views/actions.html
Normal 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>© 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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
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
332
webui/old/views/agents.html
Normal 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>© 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
91
webui/old/views/chat.html
Normal 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
154
webui/old/views/create.html
Normal 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>
|
||||
600
webui/old/views/group-create.html
Normal file
600
webui/old/views/group-create.html
Normal 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
224
webui/old/views/index.html
Normal 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
215
webui/old/views/login.html
Normal 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>
|
||||
307
webui/old/views/partials/agent-form.html
Normal file
307
webui/old/views/partials/agent-form.html
Normal 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>
|
||||
10
webui/old/views/partials/header.html
Normal file
10
webui/old/views/partials/header.html
Normal 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>
|
||||
105
webui/old/views/partials/menu.html
Normal file
105
webui/old/views/partials/menu.html
Normal 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>
|
||||
462
webui/old/views/settings.html
Normal file
462
webui/old/views/settings.html
Normal 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>
|
||||
61
webui/old/views/status.html
Normal file
61
webui/old/views/status.html
Normal 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>
|
||||
Reference in New Issue
Block a user