fix(ui): Various

This commit is contained in:
Richard Palethorpe
2025-03-27 11:45:50 +00:00
parent 11231f23ea
commit c96c8d8009
17 changed files with 204 additions and 378 deletions

View File

@@ -100,7 +100,7 @@ func SearchConfigMeta() []config.Field {
Type: config.FieldTypeNumber, Type: config.FieldTypeNumber,
DefaultValue: 1, DefaultValue: 1,
Min: 1, Min: 1,
Max: 10, Max: 100,
Step: 1, Step: 1,
HelpText: "Number of search results to return", HelpText: "Number of search results to return",
}, },

View File

@@ -6,12 +6,7 @@ import ConfigForm from './ConfigForm';
* Renders action configuration forms based on field group metadata * Renders action configuration forms based on field group metadata
*/ */
const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = [] }) => { const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = [] }) => {
// Debug logging
console.log('ActionForm:', { actions, fieldGroups });
// Handle action change
const handleActionChange = (index, updatedAction) => { const handleActionChange = (index, updatedAction) => {
console.log('Action change:', { index, updatedAction });
onChange(index, updatedAction); onChange(index, updatedAction);
}; };

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useNavigate, useOutletContext } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
// Import form sections // Import form sections
import BasicInfoSection from './agent-form-sections/BasicInfoSection'; import BasicInfoSection from './agent-form-sections/BasicInfoSection';
@@ -10,6 +10,7 @@ import MemorySettingsSection from './agent-form-sections/MemorySettingsSection';
import ModelSettingsSection from './agent-form-sections/ModelSettingsSection'; import ModelSettingsSection from './agent-form-sections/ModelSettingsSection';
import PromptsGoalsSection from './agent-form-sections/PromptsGoalsSection'; import PromptsGoalsSection from './agent-form-sections/PromptsGoalsSection';
import AdvancedSettingsSection from './agent-form-sections/AdvancedSettingsSection'; import AdvancedSettingsSection from './agent-form-sections/AdvancedSettingsSection';
import ExportSection from './agent-form-sections/ExportSection';
const AgentForm = ({ const AgentForm = ({
isEdit = false, isEdit = false,
@@ -20,10 +21,9 @@ const AgentForm = ({
submitButtonText, submitButtonText,
isGroupForm = false, isGroupForm = false,
noFormWrapper = false, noFormWrapper = false,
metadata = null metadata = null,
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { showToast } = useOutletContext();
const [activeSection, setActiveSection] = useState(isGroupForm ? 'model-section' : 'basic-section'); const [activeSection, setActiveSection] = useState(isGroupForm ? 'model-section' : 'basic-section');
// Handle input changes // Handle input changes
@@ -57,16 +57,27 @@ const AgentForm = ({
// Handle navigation between sections // Handle navigation between sections
const handleSectionChange = (section) => { const handleSectionChange = (section) => {
console.log('Changing section to:', section);
setActiveSection(section); setActiveSection(section);
}; };
// Handle connector change (simplified)
const handleConnectorChange = (index, updatedConnector) => {
const updatedConnectors = [...formData.connectors];
updatedConnectors[index] = updatedConnector;
setFormData({
...formData,
connectors: updatedConnectors
});
};
// Handle adding a connector // Handle adding a connector
const handleAddConnector = () => { const handleAddConnector = () => {
setFormData({ setFormData({
...formData, ...formData,
connectors: [ connectors: [
...(formData.connectors || []), ...(formData.connectors || []),
{ name: '', config: '{}' } { type: '', config: '{}' }
] ]
}); });
}; };
@@ -81,55 +92,6 @@ const AgentForm = ({
}); });
}; };
// Handle connector name change
const handleConnectorNameChange = (index, value) => {
const updatedConnectors = [...formData.connectors];
updatedConnectors[index] = {
...updatedConnectors[index],
type: value
};
setFormData({
...formData,
connectors: updatedConnectors
});
};
// Handle connector config change
const handleConnectorConfigChange = (index, key, value) => {
const updatedConnectors = [...formData.connectors];
const currentConnector = updatedConnectors[index];
// Parse the current config if it's a string
let currentConfig = {};
if (typeof currentConnector.config === 'string') {
try {
currentConfig = JSON.parse(currentConnector.config);
} catch (err) {
console.error('Error parsing config:', err);
currentConfig = {};
}
} else if (currentConnector.config) {
currentConfig = currentConnector.config;
}
// Update the config with the new key-value pair
currentConfig = {
...currentConfig,
[key]: value
};
// Update the connector with the stringified config
updatedConnectors[index] = {
...currentConnector,
config: JSON.stringify(currentConfig)
};
setFormData({
...formData,
connectors: updatedConnectors
});
};
// Handle adding an MCP server // Handle adding an MCP server
const handleAddMCPServer = () => { const handleAddMCPServer = () => {
setFormData({ setFormData({
@@ -231,6 +193,17 @@ const AgentForm = ({
<i className="fas fa-cogs"></i> <i className="fas fa-cogs"></i>
Advanced Settings Advanced Settings
</li> </li>
{isEdit && (
<>
<li
className={`wizard-nav-item ${activeSection === 'export-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('export-section')}
>
<i className="fas fa-file-export"></i>
Export Data
</li>
</>
)}
</ul> </ul>
</div> </div>
@@ -248,7 +221,7 @@ const AgentForm = ({
</div> </div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} metadata={metadata} /> <ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorChange={handleConnectorChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
@@ -270,6 +243,14 @@ const AgentForm = ({
<div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}>
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} /> <AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
{isEdit && (
<>
<div style={{ display: activeSection === 'export-section' ? 'block' : 'none' }}>
<ExportSection agentName={formData.name} />
</div>
</>
)}
</div> </div>
) : ( ) : (
<form className="agent-form" onSubmit={handleSubmit} noValidate> <form className="agent-form" onSubmit={handleSubmit} noValidate>
@@ -283,7 +264,7 @@ const AgentForm = ({
</div> </div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} metadata={metadata} /> <ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorChange={handleConnectorChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
@@ -306,13 +287,21 @@ const AgentForm = ({
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} /> <AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
{isEdit && (
<>
<div style={{ display: activeSection === 'export-section' ? 'block' : 'none' }}>
<ExportSection agentName={formData.name} />
</div>
</>
)}
{/* Form Controls */} {/* Form Controls */}
<div className="form-actions"> <div className="form-actions" style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
<button type="button" className="btn btn-secondary" onClick={() => navigate('/agents')}> <button type="button" className="action-btn" onClick={() => navigate('/agents')}>
Cancel <i className="fas fa-times"></i> Cancel
</button> </button>
<button type="submit" className="btn btn-primary" disabled={loading}> <button type="submit" className="action-btn" disabled={loading}>
{submitButtonText || (isEdit ? 'Update Agent' : 'Create Agent')} <i className="fas fa-save"></i> {submitButtonText || (isEdit ? 'Update Agent' : 'Create Agent')}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -24,9 +24,6 @@ const ConfigForm = ({
typeField = 'type', typeField = 'type',
addButtonText = 'Add Item' addButtonText = 'Add Item'
}) => { }) => {
// Debug logging
console.log(`ConfigForm for ${itemType}:`, { items, fieldGroups });
// Generate options from fieldGroups // Generate options from fieldGroups
const typeOptions = [ const typeOptions = [
{ value: '', label: `Select a ${itemType} type` }, { value: '', label: `Select a ${itemType} type` },
@@ -36,8 +33,6 @@ const ConfigForm = ({
})) }))
]; ];
console.log(`${itemType} type options:`, typeOptions);
// Parse the config JSON string to an object // Parse the config JSON string to an object
const parseConfig = (item) => { const parseConfig = (item) => {
if (!item || !item.config) return {}; if (!item || !item.config) return {};
@@ -82,15 +77,13 @@ const ConfigForm = ({
// Find the field group that matches this item's type // Find the field group that matches this item's type
const fieldGroup = fieldGroups.find(group => group.name === itemTypeName); const fieldGroup = fieldGroups.find(group => group.name === itemTypeName);
console.log(`Item ${index} type: ${itemTypeName}, Found field group:`, fieldGroup);
return ( return (
<div key={index} className="config-item mb-4 card"> <div key={index} className="config-item mb-4 card">
<div className="config-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}> <div className="config-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h4 style={{ margin: 0 }}>{itemType.charAt(0).toUpperCase() + itemType.slice(1)} #{index + 1}</h4> <h4 style={{ margin: 0 }}>{itemType.charAt(0).toUpperCase() + itemType.slice(1)} #{index + 1}</h4>
<button <button
type="button" type="button"
className="remove-btn" className="action-btn delete-btn"
onClick={() => onRemove(index)} onClick={() => onRemove(index)}
> >
<i className="fas fa-times"></i> <i className="fas fa-times"></i>
@@ -134,7 +127,7 @@ const ConfigForm = ({
<button <button
type="button" type="button"
className="add-btn" className="action-btn"
onClick={onAdd} onClick={onAdd}
> >
<i className="fas fa-plus"></i> {addButtonText} <i className="fas fa-plus"></i> {addButtonText}

View File

@@ -9,36 +9,16 @@ function ConnectorForm({
connectors = [], connectors = [],
onAddConnector, onAddConnector,
onRemoveConnector, onRemoveConnector,
onConnectorNameChange, onChange,
onConnectorConfigChange,
fieldGroups = [] fieldGroups = []
}) { }) {
// Debug logging
console.log('ConnectorForm:', { connectors, fieldGroups });
// Handle connector change
const handleConnectorChange = (index, updatedConnector) => {
console.log('Connector change:', { index, updatedConnector });
if (updatedConnector.type !== connectors[index].type) {
onConnectorNameChange(index, updatedConnector.type);
} else {
onConnectorConfigChange(index, updatedConnector.config);
}
};
// Handle adding a new connector
const handleAddConnector = () => {
console.log('Adding new connector');
onAddConnector();
};
return ( return (
<ConfigForm <ConfigForm
items={connectors} items={connectors}
fieldGroups={fieldGroups} fieldGroups={fieldGroups}
onChange={handleConnectorChange} onChange={onChange}
onRemove={onRemoveConnector} onRemove={onRemoveConnector}
onAdd={handleAddConnector} onAdd={onAddConnector}
itemType="connector" itemType="connector"
typeField="type" typeField="type"
addButtonText="Add Connector" addButtonText="Add Connector"

View File

@@ -8,8 +8,7 @@ const ConnectorsSection = ({
formData, formData,
handleAddConnector, handleAddConnector,
handleRemoveConnector, handleRemoveConnector,
handleConnectorNameChange, handleConnectorChange,
handleConnectorConfigChange,
metadata metadata
}) => { }) => {
return ( return (
@@ -23,8 +22,7 @@ const ConnectorsSection = ({
connectors={formData.connectors || []} connectors={formData.connectors || []}
onAddConnector={handleAddConnector} onAddConnector={handleAddConnector}
onRemoveConnector={handleRemoveConnector} onRemoveConnector={handleRemoveConnector}
onConnectorNameChange={handleConnectorNameChange} onChange={handleConnectorChange}
onConnectorConfigChange={handleConnectorConfigChange}
fieldGroups={metadata?.connectors || []} fieldGroups={metadata?.connectors || []}
/> />
</div> </div>

View File

@@ -0,0 +1,28 @@
import React, { useEffect } from 'react';
const ExportSection = ({ agentName }) => {
useEffect(() => {
console.log('ExportSection rendered with agentName:', agentName);
}, [agentName]);
return (
<div className="">
<div className="section-title">
<h2>Export Data</h2>
</div>
<div className="section-content">
<p className="mb-4">Export your agent configuration for backup or transfer.</p>
<a
href={`/settings/export/${agentName}`}
className="action-btn"
style={{ display: 'inline-flex', alignItems: 'center', textDecoration: 'none' }}
>
<i className="fas fa-file-export"></i> Export Configuration
</a>
</div>
</div>
);
};
export default ExportSection;

View File

@@ -46,7 +46,7 @@ const MCPServersSection = ({
<h4>MCP Server #{index + 1}</h4> <h4>MCP Server #{index + 1}</h4>
<button <button
type="button" type="button"
className="remove-btn" className="action-btn delete-btn"
onClick={() => handleRemoveMCPServer(index)} onClick={() => handleRemoveMCPServer(index)}
> >
<i className="fas fa-times"></i> <i className="fas fa-times"></i>
@@ -64,7 +64,7 @@ const MCPServersSection = ({
<button <button
type="button" type="button"
className="add-btn" className="action-btn"
onClick={handleAddMCPServer} onClick={handleAddMCPServer}
> >
<i className="fas fa-plus"></i> Add MCP Server <i className="fas fa-plus"></i> Add MCP Server

View File

@@ -7,3 +7,5 @@ export { default as MemorySettingsSection } from './MemorySettingsSection';
export { default as PromptsGoalsSection } from './PromptsGoalsSection'; export { default as PromptsGoalsSection } from './PromptsGoalsSection';
export { default as AdvancedSettingsSection } from './AdvancedSettingsSection'; export { default as AdvancedSettingsSection } from './AdvancedSettingsSection';
export { default as FormNavSidebar } from './FormNavSidebar'; export { default as FormNavSidebar } from './FormNavSidebar';
export { default as ExportSection } from './ExportSection';
export { default as ControlSection } from './ControlSection';

View File

@@ -1,221 +1,5 @@
/* Agent Form Section Styles */
.form-section {
padding: 1.5rem;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 1.5rem;
}
.section-title {
font-size: 1.5rem;
margin-bottom: 1.5rem;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 0.5rem;
}
.section-description { .section-description {
color: #666; color: #cecece;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.hidden {
display: none;
}
.active {
display: block;
}
/* Form Controls */
.form-controls {
display: flex;
justify-content: flex-end;
margin-top: 2rem;
padding: 1rem;
background-color: #f8f9fa;
border-top: 1px solid #eee;
}
.submit-btn {
background-color: #4a6cf7;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: background-color 0.2s;
}
.submit-btn:hover {
background-color: #3a5ce5;
}
.submit-btn:disabled {
background-color: #a0a0a0;
cursor: not-allowed;
}
/* Error Message */
.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.close-btn {
background: none;
border: none;
color: #721c24;
cursor: pointer;
font-size: 1rem;
}
/* Navigation Sidebar */
.wizard-sidebar {
width: 250px;
background-color: #f8f9fa;
border-right: 1px solid #eee;
padding: 1.5rem 0;
}
.wizard-nav {
list-style: none;
padding: 0;
margin: 0;
}
.wizard-nav-item {
padding: 0.75rem 1.5rem;
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.75rem;
}
.wizard-nav-item:hover {
background-color: #e9ecef;
}
.wizard-nav-item.active {
background-color: #e9ecef;
border-left: 4px solid #4a6cf7;
font-weight: 600;
}
.wizard-nav-item i {
width: 20px;
text-align: center;
}
/* Form Layout */
.agent-form-container {
display: flex;
min-height: 80vh;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
}
.form-content-area {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
}
/* Input Styles */
input[type="text"],
input[type="password"],
input[type="number"],
textarea,
select {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
margin-top: 0.25rem;
}
textarea {
min-height: 100px;
resize: vertical;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
margin: 0;
}
/* Add and Remove Buttons */
.add-btn,
.remove-btn {
background: none;
border: none;
cursor: pointer;
transition: color 0.2s;
}
.add-btn {
color: #4a6cf7;
font-weight: 600;
padding: 0.5rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.add-btn:hover {
color: #3a5ce5;
}
.remove-btn {
color: #dc3545;
}
.remove-btn:hover {
color: #c82333;
}
/* Item Containers */
.action-item,
.mcp-server-item {
border: 1px solid #eee;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.action-header,
.mcp-server-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.action-header h4,
.mcp-server-header h4 {
margin: 0;
}

View File

@@ -33,6 +33,9 @@ const FormFieldDefinition = ({
helpText={field.helpText || ''} helpText={field.helpText || ''}
options={field.options || []} options={field.options || []}
required={field.required || false} required={field.required || false}
min={field.min || 0}
max={field.max || 2**31}
step={field.step || 1}
/> />
</div> </div>
))} ))}

View File

@@ -19,8 +19,21 @@ export function useAgent(agentName) {
setError(null); setError(null);
try { try {
// Fetch the agent configuration
const config = await agentApi.getAgentConfig(agentName); const config = await agentApi.getAgentConfig(agentName);
setAgent(config);
// Fetch the agent status
const response = await fetch(`/api/agent/${agentName}`);
if (!response.ok) {
throw new Error(`Failed to fetch agent status: ${response.status}`);
}
const statusData = await response.json();
// Combine configuration with active status
setAgent({
...config,
active: statusData.active
});
} catch (err) { } catch (err) {
setError(err.message || 'Failed to fetch agent configuration'); setError(err.message || 'Failed to fetch agent configuration');
console.error('Error fetching agent:', err); console.error('Error fetching agent:', err);
@@ -63,8 +76,13 @@ export function useAgent(agentName) {
} else { } else {
await agentApi.startAgent(agentName); await agentApi.startAgent(agentName);
} }
// Refresh agent data after status change
await fetchAgent(); // Update the agent's active status in the local state
setAgent(prevAgent => ({
...prevAgent,
active: !isActive
}));
return true; return true;
} catch (err) { } catch (err) {
setError(err.message || 'Failed to toggle agent status'); setError(err.message || 'Failed to toggle agent status');
@@ -73,7 +91,7 @@ export function useAgent(agentName) {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [agentName, fetchAgent]); }, [agentName]);
// Delete agent // Delete agent
const deleteAgent = useCallback(async () => { const deleteAgent = useCallback(async () => {

View File

@@ -13,29 +13,14 @@ function AgentsList() {
const fetchAgents = async () => { const fetchAgents = async () => {
setLoading(true); setLoading(true);
try { try {
const response = await fetch('/agents'); const response = await fetch('/api/agents');
const html = await response.text(); if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
}
// Create a temporary element to parse the HTML const data = await response.json();
const tempDiv = document.createElement('div'); setAgents(data.agents || []);
tempDiv.innerHTML = html; setStatuses(data.statuses || {});
// Extract agent names and statuses from the HTML
const agentElements = tempDiv.querySelectorAll('[data-agent]');
const agentList = [];
const statusMap = {};
agentElements.forEach(el => {
const name = el.getAttribute('data-agent');
const status = el.getAttribute('data-active') === 'true';
if (name) {
agentList.push(name);
statusMap[name] = status;
}
});
setAgents(agentList);
setStatuses(statusMap);
} catch (err) { } catch (err) {
console.error('Error fetching agents:', err); console.error('Error fetching agents:', err);
setError('Failed to load agents'); setError('Failed to load agents');
@@ -47,7 +32,7 @@ function AgentsList() {
// Toggle agent status (pause/start) // Toggle agent status (pause/start)
const toggleAgentStatus = async (name, isActive) => { const toggleAgentStatus = async (name, isActive) => {
try { try {
const endpoint = isActive ? `/pause/${name}` : `/start/${name}`; const endpoint = isActive ? `/api/agent/${name}/pause` : `/api/agent/${name}/start`;
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -64,8 +49,12 @@ function AgentsList() {
// Show success toast // Show success toast
const action = isActive ? 'paused' : 'started'; const action = isActive ? 'paused' : 'started';
showToast(`Agent "${name}" ${action} successfully`, 'success'); showToast(`Agent "${name}" ${action} successfully`, 'success');
// Refresh the agents list to ensure we have the latest data
fetchAgents();
} else { } else {
throw new Error(`Server responded with status: ${response.status}`); const errorData = await response.json().catch(() => null);
throw new Error(errorData?.error || `Server responded with status: ${response.status}`);
} }
} catch (err) { } catch (err) {
console.error(`Error toggling agent status:`, err); console.error(`Error toggling agent status:`, err);
@@ -80,7 +69,7 @@ function AgentsList() {
} }
try { try {
const response = await fetch(`/delete/${name}`, { const response = await fetch(`/api/agent/${name}`, {
method: 'DELETE', method: 'DELETE',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
@@ -88,11 +77,17 @@ function AgentsList() {
if (response.ok) { if (response.ok) {
// Remove from local state // Remove from local state
setAgents(prev => prev.filter(agent => agent !== name)); setAgents(prev => prev.filter(agent => agent !== name));
setStatuses(prev => {
const newStatuses = { ...prev };
delete newStatuses[name];
return newStatuses;
});
// Show success toast // Show success toast
showToast(`Agent "${name}" deleted successfully`, 'success'); showToast(`Agent "${name}" deleted successfully`, 'success');
} else { } else {
throw new Error(`Server responded with status: ${response.status}`); const errorData = await response.json().catch(() => null);
throw new Error(errorData?.error || `Server responded with status: ${response.status}`);
} }
} catch (err) { } catch (err) {
console.error(`Error deleting agent:`, err); console.error(`Error deleting agent:`, err);
@@ -117,7 +112,7 @@ function AgentsList() {
<div className="agents-container"> <div className="agents-container">
<header className="page-header"> <header className="page-header">
<h1>Manage Agents</h1> <h1>Manage Agents</h1>
<Link to="/create" className="create-btn"> <Link to="/create" className="action-btn">
<i className="fas fa-plus"></i> Create New Agent <i className="fas fa-plus"></i> Create New Agent
</Link> </Link>
</header> </header>
@@ -198,8 +193,8 @@ function AgentsList() {
<div className="no-agents"> <div className="no-agents">
<h2>No Agents Found</h2> <h2>No Agents Found</h2>
<p>Get started by creating your first agent</p> <p>Get started by creating your first agent</p>
<Link to="/create" className="create-agent-btn"> <Link to="/create" className="action-btn">
Create Agent <i className="fas fa-plus"></i> Create Agent
</Link> </Link>
</div> </div>
)} )}

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate, useOutletContext } from 'react-router-dom'; import { useNavigate, useOutletContext } from 'react-router-dom';
import { agentApi } from '../utils/api'; import { agentApi } from '../utils/api';
import AgentForm from '../components/AgentForm'; import AgentForm from '../components/AgentForm';
@@ -10,6 +10,7 @@ function GroupCreate() {
const [generatingProfiles, setGeneratingProfiles] = useState(false); const [generatingProfiles, setGeneratingProfiles] = useState(false);
const [activeStep, setActiveStep] = useState(1); const [activeStep, setActiveStep] = useState(1);
const [selectedProfiles, setSelectedProfiles] = useState([]); const [selectedProfiles, setSelectedProfiles] = useState([]);
const [metadata, setMetadata] = useState(null);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
description: '', description: '',
model: '', model: '',
@@ -20,6 +21,24 @@ function GroupCreate() {
profiles: [] profiles: []
}); });
// Fetch metadata on component mount
useEffect(() => {
const fetchMetadata = async () => {
try {
// Fetch metadata from the dedicated endpoint
const response = await agentApi.getAgentConfigMetadata();
if (response) {
setMetadata(response);
}
} catch (error) {
console.error('Error fetching metadata:', error);
// Continue without metadata, the form will use default fields
}
};
fetchMetadata();
}, []);
// Handle form field changes // Handle form field changes
const handleInputChange = (e) => { const handleInputChange = (e) => {
const { name, value, type } = e.target; const { name, value, type } = e.target;
@@ -281,6 +300,7 @@ function GroupCreate() {
submitButtonText="Create Group" submitButtonText="Create Group"
isGroupForm={true} isGroupForm={true}
noFormWrapper={true} noFormWrapper={true}
metadata={metadata}
/> />
</div> </div>

View File

@@ -20,11 +20,11 @@ function Home() {
try { try {
const agents = await agentApi.getAgents(); const agents = await agentApi.getAgents();
setStats({ setStats({
agents: agents.Agents || [], agents: agents.agents || [],
agentCount: agents.AgentCount || 0, agentCount: agents.agentCount || 0,
actions: agents.Actions || 0, actions: agents.actions || 0,
connectors: agents.Connectors || 0, connectors: agents.connectors || 0,
status: agents.Status || {}, status: agents.statuses || {},
}); });
} catch (err) { } catch (err) {
console.error('Error fetching dashboard data:', err); console.error('Error fetching dashboard data:', err);
@@ -115,14 +115,14 @@ function Home() {
</div> </div>
<h2><i className="fas fa-robot"></i> {agent}</h2> <h2><i className="fas fa-robot"></i> {agent}</h2>
<div className="agent-actions"> <div className="agent-actions">
<Link to={`/talk/${agent}`} className="agent-action"> <Link to={`/talk/${agent}`} className="action-btn chat-btn">
Chat <i className="fas fa-comment"></i> Chat
</Link> </Link>
<Link to={`/settings/${agent}`} className="agent-action"> <Link to={`/settings/${agent}`} className="action-btn settings-btn">
Settings <i className="fas fa-cog"></i> Settings
</Link> </Link>
<Link to={`/status/${agent}`} className="agent-action"> <Link to={`/status/${agent}`} className="action-btn status-btn">
Status <i className="fas fa-chart-line"></i> Status
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -20,11 +20,11 @@ export const API_CONFIG = {
// Agent endpoints // Agent endpoints
agents: '/api/agents', agents: '/api/agents',
agentConfig: (name) => `/api/agent/${name}/config`, agentConfig: (name) => `/api/agent/${name}/config`,
agentConfigMetadata: '/api/agent/config/metadata', agentConfigMetadata: '/api/meta/agent/config',
createAgent: '/create', createAgent: '/create',
deleteAgent: (name) => `/delete/${name}`, deleteAgent: (name) => `/api/agent/${name}`,
pauseAgent: (name) => `/pause/${name}`, pauseAgent: (name) => `/api/agent/${name}/pause`,
startAgent: (name) => `/start/${name}`, startAgent: (name) => `/api/agent/${name}/start`,
exportAgent: (name) => `/settings/export/${name}`, exportAgent: (name) => `/settings/export/${name}`,
importAgent: '/settings/import', importAgent: '/settings/import',

View File

@@ -130,6 +130,11 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
webapp.Put("/pause/:name", app.Pause(pool)) webapp.Put("/pause/:name", app.Pause(pool))
webapp.Put("/start/:name", app.Start(pool)) webapp.Put("/start/:name", app.Start(pool))
// JSON API endpoints for agent operations
webapp.Delete("/api/agent/:name", app.Delete(pool))
webapp.Put("/api/agent/:name/pause", app.Pause(pool))
webapp.Put("/api/agent/:name/start", app.Start(pool))
webapp.Post("/v1/responses", app.Responses(pool)) webapp.Post("/v1/responses", app.Responses(pool))
webapp.Get("/talk/:name", func(c *fiber.Ctx) error { webapp.Get("/talk/:name", func(c *fiber.Ctx) error {
@@ -172,8 +177,8 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
webapp.Get("/api/agent/:name/config", app.GetAgentConfig(pool)) webapp.Get("/api/agent/:name/config", app.GetAgentConfig(pool))
webapp.Put("/api/agent/:name/config", app.UpdateAgentConfig(pool)) webapp.Put("/api/agent/:name/config", app.UpdateAgentConfig(pool))
// Metadata endpoint for agent configuration fields // Add endpoint for getting agent config metadata
webapp.Get("/api/agent/config/metadata", app.GetAgentConfigMeta()) webapp.Get("/api/meta/agent/config", app.GetAgentConfigMeta())
webapp.Post("/action/:name/run", app.ExecuteAction(pool)) webapp.Post("/action/:name/run", app.ExecuteAction(pool))
webapp.Get("/actions", app.ListActions()) webapp.Get("/actions", app.ListActions())
@@ -195,11 +200,27 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
} }
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"Agents": agents, "agents": agents,
"AgentCount": len(agents), "agentCount": len(agents),
"Actions": len(services.AvailableActions), "actions": len(services.AvailableActions),
"Connectors": len(services.AvailableConnectors), "connectors": len(services.AvailableConnectors),
"Status": statuses, "statuses": statuses,
})
})
// API endpoint for getting a specific agent's details
webapp.Get("/api/agent/:name", func(c *fiber.Ctx) error {
name := c.Params("name")
agent := pool.GetAgent(name)
if agent == nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "Agent not found",
})
}
// Add the active status to the configuration
return c.JSON(fiber.Map{
"active": !agent.Paused(),
}) })
}) })