Files
LocalAGI/webui/react-ui/src/components/AgentForm.jsx
Richard Palethorpe a569e37a34 fix(ui): Send number input as number JSON not string (#130)
* fix(ui): Submit number fields as numbers not text

* fix(ui): Remove some debug messages
2025-04-03 15:27:23 +02:00

362 lines
13 KiB
JavaScript

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
// Import form sections
import BasicInfoSection from './agent-form-sections/BasicInfoSection';
import ConnectorsSection from './agent-form-sections/ConnectorsSection';
import ActionsSection from './agent-form-sections/ActionsSection';
import MCPServersSection from './agent-form-sections/MCPServersSection';
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,
formData,
setFormData,
onSubmit,
loading = false,
submitButtonText,
isGroupForm = false,
noFormWrapper = false,
metadata = null,
}) => {
const navigate = useNavigate();
const [activeSection, setActiveSection] = useState(isGroupForm ? 'model-section' : 'basic-section');
// Handle input changes
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target.name.target;
// Convert value to number if it's a number input
const processedValue = type === 'number' ? Number(value) : value;
if (name.includes('.')) {
const [parent, child] = name.split('.');
setFormData({
...formData,
[parent]: {
...formData[parent],
[child]: type === 'checkbox' ? checked : processedValue
}
});
} else {
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : processedValue
});
}
};
// Handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
if (onSubmit) {
onSubmit(e);
}
};
// Handle navigation between sections
const handleSectionChange = (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 || []),
{ type: '', config: '{}' }
]
});
};
// Handle removing a connector
const handleRemoveConnector = (index) => {
const updatedConnectors = [...formData.connectors];
updatedConnectors.splice(index, 1);
setFormData({
...formData,
connectors: updatedConnectors
});
};
const handleAddDynamicPrompt = () => {
setFormData({
...formData,
dynamicPrompts: [
...(formData.dynamicPrompts || []),
{ type: '', config: '{}' }
]
});
};
const handleRemoveDynamicPrompt = (index) => {
const updatedDynamicPrompts = [...formData.dynamicPrompts];
updatedDynamicPrompts.splice(index, 1);
setFormData({
...formData,
dynamicPrompts: updatedDynamicPrompts,
});
};
const handleDynamicPromptChange = (index, updatedPrompt) => {
const updatedPrompts = [...formData.dynamicPrompts];
updatedPrompts[index] = updatedPrompt;
setFormData({
...formData,
dynamicPrompts: updatedPrompts
});
};
// Handle adding an MCP server
const handleAddMCPServer = () => {
setFormData({
...formData,
mcp_servers: [
...(formData.mcp_servers || []),
{ url: '', token: '' }
]
});
};
// Handle removing an MCP server
const handleRemoveMCPServer = (index) => {
const updatedMCPServers = [...formData.mcp_servers];
updatedMCPServers.splice(index, 1);
setFormData({
...formData,
mcp_servers: updatedMCPServers
});
};
// Handle MCP server change
const handleMCPServerChange = (index, field, value) => {
const updatedMCPServers = [...formData.mcp_servers];
updatedMCPServers[index] = {
...updatedMCPServers[index],
[field]: value
};
setFormData({
...formData,
mcp_servers: updatedMCPServers
});
};
if (loading) {
return <div className="loading">Loading...</div>;
}
return (
<div className="agent-form-container">
{/* Wizard Sidebar */}
<div className="wizard-sidebar">
<ul className="wizard-nav">
{!isGroupForm && (
<li
className={`wizard-nav-item ${activeSection === 'basic-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('basic-section')}
>
<i className="fas fa-info-circle"></i>
Basic Information
</li>
)}
<li
className={`wizard-nav-item ${activeSection === 'model-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('model-section')}
>
<i className="fas fa-brain"></i>
Model Settings
</li>
<li
className={`wizard-nav-item ${activeSection === 'connectors-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('connectors-section')}
>
<i className="fas fa-plug"></i>
Connectors
</li>
<li
className={`wizard-nav-item ${activeSection === 'actions-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('actions-section')}
>
<i className="fas fa-bolt"></i>
Actions
</li>
<li
className={`wizard-nav-item ${activeSection === 'mcp-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('mcp-section')}
>
<i className="fas fa-server"></i>
MCP Servers
</li>
<li
className={`wizard-nav-item ${activeSection === 'memory-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('memory-section')}
>
<i className="fas fa-memory"></i>
Memory Settings
</li>
<li
className={`wizard-nav-item ${activeSection === 'prompts-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('prompts-section')}
>
<i className="fas fa-comment-alt"></i>
Prompts & Goals
</li>
<li
className={`wizard-nav-item ${activeSection === 'advanced-section' ? 'active' : ''}`}
onClick={() => handleSectionChange('advanced-section')}
>
<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>
{/* Form Content */}
<div className="form-content-area">
{noFormWrapper ? (
<div className='agent-form'>
{/* Form Sections */}
<div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}>
<BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'model-section' ? 'block' : 'none' }}>
<ModelSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorChange={handleConnectorChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
<ActionsSection formData={formData} setFormData={setFormData} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'mcp-section' ? 'block' : 'none' }}>
<MCPServersSection formData={formData} handleAddMCPServer={handleAddMCPServer} handleRemoveMCPServer={handleRemoveMCPServer} handleMCPServerChange={handleMCPServerChange} />
</div>
<div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}>
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}>
<PromptsGoalsSection
formData={formData}
handleInputChange={handleInputChange}
isGroupForm={isGroupForm}
metadata={metadata}
onAddPrompt={handleAddDynamicPrompt}
onRemovePrompt={handleRemoveDynamicPrompt}
handleDynamicPromptChange={handleDynamicPromptChange}
/>
</div>
<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>
{/* Form Sections */}
<div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}>
<BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'model-section' ? 'block' : 'none' }}>
<ModelSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorChange={handleConnectorChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
<ActionsSection formData={formData} setFormData={setFormData} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'mcp-section' ? 'block' : 'none' }}>
<MCPServersSection formData={formData} handleAddMCPServer={handleAddMCPServer} handleRemoveMCPServer={handleRemoveMCPServer} handleMCPServerChange={handleMCPServerChange} />
</div>
<div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}>
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}>
<PromptsGoalsSection
formData={formData}
handleInputChange={handleInputChange}
isGroupForm={isGroupForm}
metadata={metadata}
onAddPrompt={handleAddDynamicPrompt}
onRemovePrompt={handleRemoveDynamicPrompt}
handleDynamicPromptChange={handleDynamicPromptChange}
/>
</div>
<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>
</>
)}
{/* Form Controls */}
<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="action-btn" disabled={loading}>
<i className="fas fa-save"></i> {submitButtonText || (isEdit ? 'Update Agent' : 'Create Agent')}
</button>
</div>
</form>
)}
</div>
</div>
);
};
export default AgentForm;