fix(ui): Various
This commit is contained in:
@@ -100,7 +100,7 @@ func SearchConfigMeta() []config.Field {
|
||||
Type: config.FieldTypeNumber,
|
||||
DefaultValue: 1,
|
||||
Min: 1,
|
||||
Max: 10,
|
||||
Max: 100,
|
||||
Step: 1,
|
||||
HelpText: "Number of search results to return",
|
||||
},
|
||||
|
||||
@@ -6,12 +6,7 @@ import ConfigForm from './ConfigForm';
|
||||
* Renders action configuration forms based on field group metadata
|
||||
*/
|
||||
const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = [] }) => {
|
||||
// Debug logging
|
||||
console.log('ActionForm:', { actions, fieldGroups });
|
||||
|
||||
// Handle action change
|
||||
const handleActionChange = (index, updatedAction) => {
|
||||
console.log('Action change:', { index, updatedAction });
|
||||
onChange(index, updatedAction);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate, useOutletContext } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// Import form sections
|
||||
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 PromptsGoalsSection from './agent-form-sections/PromptsGoalsSection';
|
||||
import AdvancedSettingsSection from './agent-form-sections/AdvancedSettingsSection';
|
||||
import ExportSection from './agent-form-sections/ExportSection';
|
||||
|
||||
const AgentForm = ({
|
||||
isEdit = false,
|
||||
@@ -20,10 +21,9 @@ const AgentForm = ({
|
||||
submitButtonText,
|
||||
isGroupForm = false,
|
||||
noFormWrapper = false,
|
||||
metadata = null
|
||||
metadata = null,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useOutletContext();
|
||||
const [activeSection, setActiveSection] = useState(isGroupForm ? 'model-section' : 'basic-section');
|
||||
|
||||
// Handle input changes
|
||||
@@ -57,16 +57,27 @@ const AgentForm = ({
|
||||
|
||||
// Handle navigation between sections
|
||||
const handleSectionChange = (section) => {
|
||||
console.log('Changing section to:', 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
|
||||
const handleAddConnector = () => {
|
||||
setFormData({
|
||||
...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
|
||||
const handleAddMCPServer = () => {
|
||||
setFormData({
|
||||
@@ -231,6 +193,17 @@ const AgentForm = ({
|
||||
<i className="fas fa-cogs"></i>
|
||||
Advanced Settings
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@@ -248,7 +221,7 @@ const AgentForm = ({
|
||||
</div>
|
||||
|
||||
<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 style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
|
||||
@@ -270,6 +243,14 @@ const AgentForm = ({
|
||||
<div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}>
|
||||
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
|
||||
</div>
|
||||
|
||||
{isEdit && (
|
||||
<>
|
||||
<div style={{ display: activeSection === 'export-section' ? 'block' : 'none' }}>
|
||||
<ExportSection agentName={formData.name} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<form className="agent-form" onSubmit={handleSubmit} noValidate>
|
||||
@@ -283,7 +264,7 @@ const AgentForm = ({
|
||||
</div>
|
||||
|
||||
<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 style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
|
||||
@@ -306,13 +287,21 @@ const AgentForm = ({
|
||||
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
|
||||
</div>
|
||||
|
||||
{isEdit && (
|
||||
<>
|
||||
<div style={{ display: activeSection === 'export-section' ? 'block' : 'none' }}>
|
||||
<ExportSection agentName={formData.name} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Form Controls */}
|
||||
<div className="form-actions">
|
||||
<button type="button" className="btn btn-secondary" onClick={() => navigate('/agents')}>
|
||||
Cancel
|
||||
<div className="form-actions" style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
|
||||
<button type="button" className="action-btn" onClick={() => navigate('/agents')}>
|
||||
<i className="fas fa-times"></i> Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={loading}>
|
||||
{submitButtonText || (isEdit ? 'Update Agent' : 'Create Agent')}
|
||||
<button type="submit" className="action-btn" disabled={loading}>
|
||||
<i className="fas fa-save"></i> {submitButtonText || (isEdit ? 'Update Agent' : 'Create Agent')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -24,9 +24,6 @@ const ConfigForm = ({
|
||||
typeField = 'type',
|
||||
addButtonText = 'Add Item'
|
||||
}) => {
|
||||
// Debug logging
|
||||
console.log(`ConfigForm for ${itemType}:`, { items, fieldGroups });
|
||||
|
||||
// Generate options from fieldGroups
|
||||
const typeOptions = [
|
||||
{ value: '', label: `Select a ${itemType} type` },
|
||||
@@ -35,8 +32,6 @@ const ConfigForm = ({
|
||||
label: group.label
|
||||
}))
|
||||
];
|
||||
|
||||
console.log(`${itemType} type options:`, typeOptions);
|
||||
|
||||
// Parse the config JSON string to an object
|
||||
const parseConfig = (item) => {
|
||||
@@ -82,15 +77,13 @@ const ConfigForm = ({
|
||||
// Find the field group that matches this item's type
|
||||
const fieldGroup = fieldGroups.find(group => group.name === itemTypeName);
|
||||
|
||||
console.log(`Item ${index} type: ${itemTypeName}, Found field group:`, fieldGroup);
|
||||
|
||||
return (
|
||||
<div key={index} className="config-item mb-4 card">
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
className="remove-btn"
|
||||
className="action-btn delete-btn"
|
||||
onClick={() => onRemove(index)}
|
||||
>
|
||||
<i className="fas fa-times"></i>
|
||||
@@ -134,7 +127,7 @@ const ConfigForm = ({
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="add-btn"
|
||||
className="action-btn"
|
||||
onClick={onAdd}
|
||||
>
|
||||
<i className="fas fa-plus"></i> {addButtonText}
|
||||
|
||||
@@ -9,36 +9,16 @@ function ConnectorForm({
|
||||
connectors = [],
|
||||
onAddConnector,
|
||||
onRemoveConnector,
|
||||
onConnectorNameChange,
|
||||
onConnectorConfigChange,
|
||||
onChange,
|
||||
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 (
|
||||
<ConfigForm
|
||||
items={connectors}
|
||||
fieldGroups={fieldGroups}
|
||||
onChange={handleConnectorChange}
|
||||
onChange={onChange}
|
||||
onRemove={onRemoveConnector}
|
||||
onAdd={handleAddConnector}
|
||||
onAdd={onAddConnector}
|
||||
itemType="connector"
|
||||
typeField="type"
|
||||
addButtonText="Add Connector"
|
||||
|
||||
@@ -8,8 +8,7 @@ const ConnectorsSection = ({
|
||||
formData,
|
||||
handleAddConnector,
|
||||
handleRemoveConnector,
|
||||
handleConnectorNameChange,
|
||||
handleConnectorConfigChange,
|
||||
handleConnectorChange,
|
||||
metadata
|
||||
}) => {
|
||||
return (
|
||||
@@ -23,8 +22,7 @@ const ConnectorsSection = ({
|
||||
connectors={formData.connectors || []}
|
||||
onAddConnector={handleAddConnector}
|
||||
onRemoveConnector={handleRemoveConnector}
|
||||
onConnectorNameChange={handleConnectorNameChange}
|
||||
onConnectorConfigChange={handleConnectorConfigChange}
|
||||
onChange={handleConnectorChange}
|
||||
fieldGroups={metadata?.connectors || []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
@@ -46,7 +46,7 @@ const MCPServersSection = ({
|
||||
<h4>MCP Server #{index + 1}</h4>
|
||||
<button
|
||||
type="button"
|
||||
className="remove-btn"
|
||||
className="action-btn delete-btn"
|
||||
onClick={() => handleRemoveMCPServer(index)}
|
||||
>
|
||||
<i className="fas fa-times"></i>
|
||||
@@ -64,7 +64,7 @@ const MCPServersSection = ({
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="add-btn"
|
||||
className="action-btn"
|
||||
onClick={handleAddMCPServer}
|
||||
>
|
||||
<i className="fas fa-plus"></i> Add MCP Server
|
||||
|
||||
@@ -7,3 +7,5 @@ export { default as MemorySettingsSection } from './MemorySettingsSection';
|
||||
export { default as PromptsGoalsSection } from './PromptsGoalsSection';
|
||||
export { default as AdvancedSettingsSection } from './AdvancedSettingsSection';
|
||||
export { default as FormNavSidebar } from './FormNavSidebar';
|
||||
export { default as ExportSection } from './ExportSection';
|
||||
export { default as ControlSection } from './ControlSection';
|
||||
|
||||
@@ -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 {
|
||||
color: #666;
|
||||
color: #cecece;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ const FormFieldDefinition = ({
|
||||
helpText={field.helpText || ''}
|
||||
options={field.options || []}
|
||||
required={field.required || false}
|
||||
min={field.min || 0}
|
||||
max={field.max || 2**31}
|
||||
step={field.step || 1}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
26
webui/react-ui/src/hooks/useAgent.js
vendored
26
webui/react-ui/src/hooks/useAgent.js
vendored
@@ -19,8 +19,21 @@ export function useAgent(agentName) {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Fetch the agent configuration
|
||||
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) {
|
||||
setError(err.message || 'Failed to fetch agent configuration');
|
||||
console.error('Error fetching agent:', err);
|
||||
@@ -63,8 +76,13 @@ export function useAgent(agentName) {
|
||||
} else {
|
||||
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;
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to toggle agent status');
|
||||
@@ -73,7 +91,7 @@ export function useAgent(agentName) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [agentName, fetchAgent]);
|
||||
}, [agentName]);
|
||||
|
||||
// Delete agent
|
||||
const deleteAgent = useCallback(async () => {
|
||||
|
||||
@@ -13,29 +13,14 @@ function AgentsList() {
|
||||
const fetchAgents = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch('/agents');
|
||||
const html = await response.text();
|
||||
const response = await fetch('/api/agents');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server responded with status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Create a temporary element to parse the HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
|
||||
// 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);
|
||||
const data = await response.json();
|
||||
setAgents(data.agents || []);
|
||||
setStatuses(data.statuses || {});
|
||||
} catch (err) {
|
||||
console.error('Error fetching agents:', err);
|
||||
setError('Failed to load agents');
|
||||
@@ -47,7 +32,7 @@ function AgentsList() {
|
||||
// Toggle agent status (pause/start)
|
||||
const toggleAgentStatus = async (name, isActive) => {
|
||||
try {
|
||||
const endpoint = isActive ? `/pause/${name}` : `/start/${name}`;
|
||||
const endpoint = isActive ? `/api/agent/${name}/pause` : `/api/agent/${name}/start`;
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -64,8 +49,12 @@ function AgentsList() {
|
||||
// Show success toast
|
||||
const action = isActive ? 'paused' : 'started';
|
||||
showToast(`Agent "${name}" ${action} successfully`, 'success');
|
||||
|
||||
// Refresh the agents list to ensure we have the latest data
|
||||
fetchAgents();
|
||||
} 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) {
|
||||
console.error(`Error toggling agent status:`, err);
|
||||
@@ -80,7 +69,7 @@ function AgentsList() {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/delete/${name}`, {
|
||||
const response = await fetch(`/api/agent/${name}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
@@ -88,11 +77,17 @@ function AgentsList() {
|
||||
if (response.ok) {
|
||||
// Remove from local state
|
||||
setAgents(prev => prev.filter(agent => agent !== name));
|
||||
setStatuses(prev => {
|
||||
const newStatuses = { ...prev };
|
||||
delete newStatuses[name];
|
||||
return newStatuses;
|
||||
});
|
||||
|
||||
// Show success toast
|
||||
showToast(`Agent "${name}" deleted successfully`, 'success');
|
||||
} 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) {
|
||||
console.error(`Error deleting agent:`, err);
|
||||
@@ -117,7 +112,7 @@ function AgentsList() {
|
||||
<div className="agents-container">
|
||||
<header className="page-header">
|
||||
<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
|
||||
</Link>
|
||||
</header>
|
||||
@@ -198,8 +193,8 @@ function AgentsList() {
|
||||
<div className="no-agents">
|
||||
<h2>No Agents Found</h2>
|
||||
<p>Get started by creating your first agent</p>
|
||||
<Link to="/create" className="create-agent-btn">
|
||||
Create Agent
|
||||
<Link to="/create" className="action-btn">
|
||||
<i className="fas fa-plus"></i> Create Agent
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate, useOutletContext } from 'react-router-dom';
|
||||
import { agentApi } from '../utils/api';
|
||||
import AgentForm from '../components/AgentForm';
|
||||
@@ -10,6 +10,7 @@ function GroupCreate() {
|
||||
const [generatingProfiles, setGeneratingProfiles] = useState(false);
|
||||
const [activeStep, setActiveStep] = useState(1);
|
||||
const [selectedProfiles, setSelectedProfiles] = useState([]);
|
||||
const [metadata, setMetadata] = useState(null);
|
||||
const [formData, setFormData] = useState({
|
||||
description: '',
|
||||
model: '',
|
||||
@@ -20,6 +21,24 @@ function GroupCreate() {
|
||||
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
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value, type } = e.target;
|
||||
@@ -281,6 +300,7 @@ function GroupCreate() {
|
||||
submitButtonText="Create Group"
|
||||
isGroupForm={true}
|
||||
noFormWrapper={true}
|
||||
metadata={metadata}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ function Home() {
|
||||
try {
|
||||
const agents = await agentApi.getAgents();
|
||||
setStats({
|
||||
agents: agents.Agents || [],
|
||||
agentCount: agents.AgentCount || 0,
|
||||
actions: agents.Actions || 0,
|
||||
connectors: agents.Connectors || 0,
|
||||
status: agents.Status || {},
|
||||
agents: agents.agents || [],
|
||||
agentCount: agents.agentCount || 0,
|
||||
actions: agents.actions || 0,
|
||||
connectors: agents.connectors || 0,
|
||||
status: agents.statuses || {},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching dashboard data:', err);
|
||||
@@ -115,14 +115,14 @@ function Home() {
|
||||
</div>
|
||||
<h2><i className="fas fa-robot"></i> {agent}</h2>
|
||||
<div className="agent-actions">
|
||||
<Link to={`/talk/${agent}`} className="agent-action">
|
||||
Chat
|
||||
<Link to={`/talk/${agent}`} className="action-btn chat-btn">
|
||||
<i className="fas fa-comment"></i> Chat
|
||||
</Link>
|
||||
<Link to={`/settings/${agent}`} className="agent-action">
|
||||
Settings
|
||||
<Link to={`/settings/${agent}`} className="action-btn settings-btn">
|
||||
<i className="fas fa-cog"></i> Settings
|
||||
</Link>
|
||||
<Link to={`/status/${agent}`} className="agent-action">
|
||||
Status
|
||||
<Link to={`/status/${agent}`} className="action-btn status-btn">
|
||||
<i className="fas fa-chart-line"></i> Status
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
8
webui/react-ui/src/utils/config.js
vendored
8
webui/react-ui/src/utils/config.js
vendored
@@ -20,11 +20,11 @@ export const API_CONFIG = {
|
||||
// Agent endpoints
|
||||
agents: '/api/agents',
|
||||
agentConfig: (name) => `/api/agent/${name}/config`,
|
||||
agentConfigMetadata: '/api/agent/config/metadata',
|
||||
agentConfigMetadata: '/api/meta/agent/config',
|
||||
createAgent: '/create',
|
||||
deleteAgent: (name) => `/delete/${name}`,
|
||||
pauseAgent: (name) => `/pause/${name}`,
|
||||
startAgent: (name) => `/start/${name}`,
|
||||
deleteAgent: (name) => `/api/agent/${name}`,
|
||||
pauseAgent: (name) => `/api/agent/${name}/pause`,
|
||||
startAgent: (name) => `/api/agent/${name}/start`,
|
||||
exportAgent: (name) => `/settings/export/${name}`,
|
||||
importAgent: '/settings/import',
|
||||
|
||||
|
||||
@@ -130,6 +130,11 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
webapp.Put("/pause/:name", app.Pause(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.Get("/talk/:name", func(c *fiber.Ctx) error {
|
||||
@@ -171,9 +176,9 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
// New API endpoints for getting and updating agent configuration
|
||||
webapp.Get("/api/agent/:name/config", app.GetAgentConfig(pool))
|
||||
webapp.Put("/api/agent/:name/config", app.UpdateAgentConfig(pool))
|
||||
|
||||
// Metadata endpoint for agent configuration fields
|
||||
webapp.Get("/api/agent/config/metadata", app.GetAgentConfigMeta())
|
||||
|
||||
// Add endpoint for getting agent config metadata
|
||||
webapp.Get("/api/meta/agent/config", app.GetAgentConfigMeta())
|
||||
|
||||
webapp.Post("/action/:name/run", app.ExecuteAction(pool))
|
||||
webapp.Get("/actions", app.ListActions())
|
||||
@@ -195,11 +200,27 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"Agents": agents,
|
||||
"AgentCount": len(agents),
|
||||
"Actions": len(services.AvailableActions),
|
||||
"Connectors": len(services.AvailableConnectors),
|
||||
"Status": statuses,
|
||||
"agents": agents,
|
||||
"agentCount": len(agents),
|
||||
"actions": len(services.AvailableActions),
|
||||
"connectors": len(services.AvailableConnectors),
|
||||
"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(),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user