chore(ui): Move some field definitions server side

This commit is contained in:
Richard Palethorpe
2025-03-26 15:56:14 +00:00
parent 7fb99ecf21
commit 319caf8e91
62 changed files with 1534 additions and 1574 deletions

View File

@@ -1,169 +1,31 @@
import React from 'react';
import FallbackAction from './actions/FallbackAction';
import GithubIssueLabelerAction from './actions/GithubIssueLabelerAction';
import GithubIssueOpenerAction from './actions/GithubIssueOpenerAction';
import GithubIssueCloserAction from './actions/GithubIssueCloserAction';
import GithubIssueCommenterAction from './actions/GithubIssueCommenterAction';
import GithubRepositoryAction from './actions/GithubRepositoryAction';
import TwitterPostAction from './actions/TwitterPostAction';
import SendMailAction from './actions/SendMailAction';
import GenerateImageAction from './actions/GenerateImageAction';
import ConfigForm from './ConfigForm';
/**
* ActionForm component for configuring an action
* Renders action configuration forms based on field group metadata
*/
const ActionForm = ({ actions = [], onChange, onRemove, onAdd }) => {
// Available action types
const actionTypes = [
{ value: '', label: 'Select an action type' },
{ value: 'github-issue-labeler', label: 'GitHub Issue Labeler' },
{ value: 'github-issue-opener', label: 'GitHub Issue Opener' },
{ value: 'github-issue-closer', label: 'GitHub Issue Closer' },
{ value: 'github-issue-commenter', label: 'GitHub Issue Commenter' },
{ value: 'github-repository-get-content', label: 'GitHub Repository Get Content' },
{ value: 'github-repository-create-or-update-content', label: 'GitHub Repository Create/Update Content' },
{ value: 'github-readme', label: 'GitHub Readme' },
{ value: 'twitter-post', label: 'Twitter Post' },
{ value: 'send-mail', label: 'Send Email' },
{ value: 'search', label: 'Search' },
{ value: 'github-issue-searcher', label: 'GitHub Issue Searcher' },
{ value: 'github-issue-reader', label: 'GitHub Issue Reader' },
{ value: 'scraper', label: 'Web Scraper' },
{ value: 'wikipedia', label: 'Wikipedia' },
{ value: 'browse', label: 'Browse' },
{ value: 'generate_image', label: 'Generate Image' },
{ value: 'counter', label: 'Counter' },
{ value: 'call_agents', label: 'Call Agents' },
{ value: 'shell-command', label: 'Shell Command' },
{ value: 'custom', label: 'Custom' }
];
// Parse the config JSON string to an object
const parseConfig = (action) => {
if (!action || !action.config) return {};
try {
return JSON.parse(action.config || '{}');
} catch (error) {
console.error('Error parsing action config:', error);
return {};
}
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);
};
// Get a value from the config object
const getConfigValue = (action, key, defaultValue = '') => {
const config = parseConfig(action);
return config[key] !== undefined ? config[key] : defaultValue;
};
// Update a value in the config object
const onActionConfigChange = (index, key, value) => {
const action = actions[index];
const config = parseConfig(action);
config[key] = value;
onChange(index, {
...action,
config: JSON.stringify(config)
});
};
// Handle action type change
const handleActionTypeChange = (index, value) => {
const action = actions[index];
onChange(index, {
...action,
name: value
});
};
// Render the appropriate action component based on the action type
const renderActionComponent = (action, index) => {
// Common props for all action components
const actionProps = {
index,
onActionConfigChange: (key, value) => onActionConfigChange(index, key, value),
getConfigValue: (key, defaultValue) => getConfigValue(action, key, defaultValue)
};
switch (action.name) {
case 'github-issue-labeler':
return <GithubIssueLabelerAction {...actionProps} />;
case 'github-issue-opener':
return <GithubIssueOpenerAction {...actionProps} />;
case 'github-issue-closer':
return <GithubIssueCloserAction {...actionProps} />;
case 'github-issue-commenter':
return <GithubIssueCommenterAction {...actionProps} />;
case 'github-repository-get-content':
case 'github-repository-create-or-update-content':
case 'github-readme':
return <GithubRepositoryAction {...actionProps} />;
case 'twitter-post':
return <TwitterPostAction {...actionProps} />;
case 'send-mail':
return <SendMailAction {...actionProps} />;
case 'generate_image':
return <GenerateImageAction {...actionProps} />;
default:
return <FallbackAction {...actionProps} />;
}
};
// Render a specific action form
const renderActionForm = (action, index) => {
// Ensure action is an object with expected properties
const safeAction = action || {};
return (
<div key={index} className="connector-item mb-4">
<div className="connector-header">
<h4>Action #{index + 1}</h4>
<button
type="button"
className="remove-btn"
onClick={() => onRemove(index)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="connector-type mb-3">
<label htmlFor={`actionType${index}`}>Action Type</label>
<select
id={`actionType${index}`}
value={safeAction.name || ''}
onChange={(e) => handleActionTypeChange(index, e.target.value)}
className="form-control"
>
{actionTypes.map((type) => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
</div>
{/* Render specific action template based on type */}
{renderActionComponent(safeAction, index)}
</div>
);
};
return (
<div className="connectors-container">
{actions && actions.map((action, index) => (
renderActionForm(action, index)
))}
<button
type="button"
className="add-btn"
onClick={onAdd}
>
<i className="fas fa-plus"></i> Add Action
</button>
</div>
<ConfigForm
items={actions}
fieldGroups={fieldGroups}
onChange={handleActionChange}
onRemove={onRemove}
onAdd={onAdd}
itemType="action"
typeField="name"
addButtonText="Add Action"
/>
);
};

View File

@@ -19,7 +19,8 @@ const AgentForm = ({
loading = false,
submitButtonText,
isGroupForm = false,
noFormWrapper = false
noFormWrapper = false,
metadata = null
}) => {
const navigate = useNavigate();
const { showToast } = useOutletContext();
@@ -239,70 +240,70 @@ const AgentForm = ({
<div className='agent-form'>
{/* Form Sections */}
<div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}>
<BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} />
<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} />
<ModelSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} />
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
<ActionsSection formData={formData} setFormData={setFormData} />
<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} />
<MCPServersSection formData={formData} handleAddMCPServer={handleAddMCPServer} handleRemoveMCPServer={handleRemoveMCPServer} handleMCPServerChange={handleMCPServerChange} />
</div>
<div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}>
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} />
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}>
<PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} />
<PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}>
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} />
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
</div>
) : (
<form className="agent-form" onSubmit={handleSubmit}>
<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} />
<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} />
<ModelSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} />
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
<ActionsSection formData={formData} setFormData={setFormData} />
<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} />
<MCPServersSection formData={formData} handleAddMCPServer={handleAddMCPServer} handleRemoveMCPServer={handleRemoveMCPServer} handleMCPServerChange={handleMCPServerChange} />
</div>
<div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}>
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} />
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}>
<PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} />
<PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} metadata={metadata} />
</div>
<div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}>
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} />
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div>
{/* Form Controls */}

View File

@@ -0,0 +1,146 @@
import React, { useState } from 'react';
import FormFieldDefinition from './common/FormFieldDefinition';
/**
* ConfigForm - A generic component for handling configuration forms based on FieldGroups
*
* @param {Object} props Component props
* @param {Array} props.items - Array of configuration items (actions, connectors, etc.)
* @param {Array} props.fieldGroups - Array of FieldGroup objects that define the available types and their fields
* @param {Function} props.onChange - Callback when an item changes
* @param {Function} props.onRemove - Callback when an item is removed
* @param {Function} props.onAdd - Callback when a new item is added
* @param {String} props.itemType - Type of items being configured ('action', 'connector', etc.)
* @param {String} props.typeField - The field name that determines the item's type (e.g., 'name' for actions, 'type' for connectors)
* @param {String} props.addButtonText - Text for the add button
*/
const ConfigForm = ({
items = [],
fieldGroups = [],
onChange,
onRemove,
onAdd,
itemType = 'item',
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` },
...fieldGroups.map(group => ({
value: group.name,
label: group.label
}))
];
console.log(`${itemType} type options:`, typeOptions);
// Parse the config JSON string to an object
const parseConfig = (item) => {
if (!item || !item.config) return {};
try {
return typeof item.config === 'string'
? JSON.parse(item.config || '{}')
: item.config;
} catch (error) {
console.error(`Error parsing ${itemType} config:`, error);
return {};
}
};
// Handle item type change
const handleTypeChange = (index, value) => {
const item = items[index];
onChange(index, {
...item,
[typeField]: value
});
};
// Handle config field change
const handleConfigChange = (index, key, value) => {
const item = items[index];
const config = parseConfig(item);
config[key] = value;
onChange(index, {
...item,
config: JSON.stringify(config)
});
};
// Render a specific item form
const renderItemForm = (item, index) => {
// Ensure item is an object with expected properties
const safeItem = item || {};
const itemTypeName = safeItem[typeField] || '';
// 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"
onClick={() => onRemove(index)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="config-type mb-3">
<label htmlFor={`${itemType}Type${index}`}>{itemType.charAt(0).toUpperCase() + itemType.slice(1)} Type</label>
<select
id={`${itemType}Type${index}`}
value={safeItem[typeField] || ''}
onChange={(e) => handleTypeChange(index, e.target.value)}
className="form-control"
>
{typeOptions.map((type) => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
</div>
{/* Render fields based on the selected type */}
{fieldGroup && fieldGroup.fields && (
<FormFieldDefinition
fields={fieldGroup.fields}
values={parseConfig(item)}
onChange={(key, value) => handleConfigChange(index, key, value)}
idPrefix={`${itemType}-${index}-`}
/>
)}
</div>
);
};
return (
<div className="config-container">
{items && items.map((item, index) => (
renderItemForm(item, index)
))}
<button
type="button"
className="add-btn"
onClick={onAdd}
>
<i className="fas fa-plus"></i> {addButtonText}
</button>
</div>
);
};
export default ConfigForm;

View File

@@ -1,137 +1,48 @@
import { useState } from 'react';
// Import connector components
import TelegramConnector from './connectors/TelegramConnector';
import SlackConnector from './connectors/SlackConnector';
import DiscordConnector from './connectors/DiscordConnector';
import GithubIssuesConnector from './connectors/GithubIssuesConnector';
import GithubPRsConnector from './connectors/GithubPRsConnector';
import IRCConnector from './connectors/IRCConnector';
import TwitterConnector from './connectors/TwitterConnector';
import FallbackConnector from './connectors/FallbackConnector';
import React from 'react';
import ConfigForm from './ConfigForm';
/**
* ConnectorForm component
* Provides specific form templates for different connector types
* Renders connector configuration forms based on field group metadata
*/
function ConnectorForm({
connectors = [],
onAddConnector,
onRemoveConnector,
onConnectorNameChange,
onConnectorConfigChange
onConnectorConfigChange,
fieldGroups = []
}) {
const [newConfigKey, setNewConfigKey] = useState('');
// Render a specific connector form based on its type
const renderConnectorForm = (connector, index) => {
// Ensure connector is an object with expected properties
const safeConnector = connector || {};
return (
<div key={index} className="connector-item mb-4">
<div className="connector-header">
<h4>Connector #{index + 1}</h4>
<button
type="button"
className="remove-btn"
onClick={() => onRemoveConnector(index)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="connector-type mb-3">
<label htmlFor={`connectorName${index}`}>Connector Type</label>
<select
id={`connectorName${index}`}
value={safeConnector.type || ''}
onChange={(e) => onConnectorNameChange(index, e.target.value)}
className="form-control"
>
<option value="">Select a connector type</option>
<option value="telegram">Telegram</option>
<option value="slack">Slack</option>
<option value="discord">Discord</option>
<option value="github-issues">GitHub Issues</option>
<option value="github-prs">GitHub PRs</option>
<option value="irc">IRC</option>
<option value="twitter">Twitter</option>
<option value="custom">Custom</option>
</select>
</div>
{/* Render specific connector template based on type */}
{renderConnectorTemplate(safeConnector, index)}
</div>
);
};
// Get the appropriate form template based on connector type
const renderConnectorTemplate = (connector, index) => {
// Check if connector.type exists, if not use empty string to avoid errors
const connectorType = (connector.type || '').toLowerCase();
// Common props for all connector components
const connectorProps = {
connector,
index,
onConnectorConfigChange,
getConfigValue
};
switch (connectorType) {
case 'telegram':
return <TelegramConnector {...connectorProps} />;
case 'slack':
return <SlackConnector {...connectorProps} />;
case 'discord':
return <DiscordConnector {...connectorProps} />;
case 'github-issues':
return <GithubIssuesConnector {...connectorProps} />;
case 'github-prs':
return <GithubPRsConnector {...connectorProps} />;
case 'irc':
return <IRCConnector {...connectorProps} />;
case 'twitter':
return <TwitterConnector {...connectorProps} />;
default:
return <FallbackConnector {...connectorProps} />;
// 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);
}
};
// Helper function to safely get config values
const getConfigValue = (connector, key, defaultValue = '') => {
if (!connector || !connector.config) return defaultValue;
// If config is a string (JSON), try to parse it
let config = connector.config;
if (typeof config === 'string') {
try {
config = JSON.parse(config);
} catch (err) {
console.error('Error parsing config:', err);
return defaultValue;
}
}
return config[key] !== undefined ? config[key] : defaultValue;
// Handle adding a new connector
const handleAddConnector = () => {
console.log('Adding new connector');
onAddConnector();
};
return (
<div className="connectors-container">
{connectors && connectors.map((connector, index) => (
renderConnectorForm(connector, index)
))}
<button
type="button"
className="add-btn"
onClick={onAddConnector}
>
<i className="fas fa-plus"></i> Add Connector
</button>
</div>
<ConfigForm
items={connectors}
fieldGroups={fieldGroups}
onChange={handleConnectorChange}
onRemove={onRemoveConnector}
onAdd={handleAddConnector}
itemType="connector"
typeField="type"
addButtonText="Add Connector"
/>
);
}

View File

@@ -1,44 +0,0 @@
import React from 'react';
import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Base action component that renders form fields based on field definitions
*
* @param {Object} props Component props
* @param {number} props.index Action index
* @param {Function} props.onActionConfigChange Handler for config changes
* @param {Function} props.getConfigValue Helper to get config values
* @param {Array} props.fields Field definitions for this action
*/
const BaseAction = ({
index,
onActionConfigChange,
getConfigValue,
fields = []
}) => {
// Create an object with all the current values
const currentValues = {};
// Pre-populate with current values or defaults
fields.forEach(field => {
currentValues[field.name] = getConfigValue(field.name, field.defaultValue);
});
// Handle field value changes
const handleFieldChange = (name, value) => {
onActionConfigChange(name, value);
};
return (
<div className="action-template">
<FormFieldDefinition
fields={fields}
values={currentValues}
onChange={handleFieldChange}
idPrefix={`action${index}_`}
/>
</div>
);
};
export default BaseAction;

View File

@@ -1,16 +0,0 @@
import React from 'react';
/**
* FallbackAction component for actions without specific configuration
*/
const FallbackAction = ({ index, onActionConfigChange, getConfigValue }) => {
return (
<div className="fallback-action">
<p className="text-muted">
This action doesn't require any additional configuration.
</p>
</div>
);
};
export default FallbackAction;

View File

@@ -1,49 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* Generate Image action component
*/
const GenerateImageAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for Generate Image action
const fields = [
{
name: 'apiKey',
label: 'OpenAI API Key',
type: 'text',
defaultValue: '',
placeholder: 'sk-...',
helpText: 'Your OpenAI API key for image generation',
required: false,
},
{
name: 'apiURL',
label: 'API URL',
type: 'text',
defaultValue: '',
placeholder: 'http://localai:8081',
helpText: 'OpenAI compatible API endpoint URL',
required: false,
},
{
name: 'model',
label: 'Model',
type: 'text',
defaultValue: '',
placeholder: 'dall-e-3',
helpText: 'Image generation model',
required: false,
}
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GenerateImageAction;

View File

@@ -1,58 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* GitHub Issue Closer action component
*/
const GithubIssueCloserAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for GitHub Issue Closer action
const fields = [
{
name: 'token',
label: 'GitHub Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: '',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: '',
required: true,
},
{
name: 'customActionName',
label: 'Custom Action Name (Optional)',
type: 'text',
defaultValue: '',
placeholder: 'close_github_issue',
helpText: 'Custom name for this action (optional)',
required: false,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueCloserAction;

View File

@@ -1,58 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* GitHub Issue Commenter action component
*/
const GithubIssueCommenterAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for GitHub Issue Commenter action
const fields = [
{
name: 'token',
label: 'GitHub Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: 'Owner of the repository',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: 'Name of the repository',
required: true,
},
{
name: 'customActionName',
label: 'Custom Action Name (Optional)',
type: 'text',
defaultValue: '',
placeholder: 'comment_on_github_issue',
helpText: 'Custom name for this action (optional)',
required: false,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueCommenterAction;

View File

@@ -1,67 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* GitHub Issue Labeler action component
*/
const GithubIssueLabelerAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for GitHub Issue Labeler action
const fields = [
{
name: 'token',
label: 'GitHub Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: '',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: '',
required: true,
},
{
name: 'availableLabels',
label: 'Available Labels',
type: 'text',
defaultValue: 'bug,enhancement',
placeholder: 'bug,enhancement,documentation',
helpText: 'Comma-separated list of available labels',
required: true,
},
{
name: 'customActionName',
label: 'Custom Action Name (Optional)',
type: 'text',
defaultValue: '',
placeholder: 'add_label_to_issue',
helpText: 'Custom name for this action (optional)',
required: false,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueLabelerAction;

View File

@@ -1,58 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* GitHub Issue Opener action component
*/
const GithubIssueOpenerAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for GitHub Issue Opener action
const fields = [
{
name: 'token',
label: 'GitHub Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: '',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: '',
required: true,
},
{
name: 'customActionName',
label: 'Custom Action Name (Optional)',
type: 'text',
defaultValue: '',
placeholder: 'open_github_issue',
helpText: 'Custom name for this action (optional)',
required: false,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueOpenerAction;

View File

@@ -1,62 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* GitHub Repository action component for repository-related actions
* Used for:
* - github-repository-get-content
* - github-repository-create-or-update-content
* - github-readme
*/
const GithubRepositoryAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for GitHub Repository action
const fields = [
{
name: 'token',
label: 'GitHub Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: '',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: '',
required: true,
},
{
name: 'customActionName',
label: 'Custom Action Name (Optional)',
type: 'text',
defaultValue: '',
placeholder: 'github_repo_action',
helpText: 'Custom name for this action (optional)',
required: false,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubRepositoryAction;

View File

@@ -1,67 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* SendMail action component
*/
const SendMailAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for SendMail action
const fields = [
{
name: 'email',
label: 'Email',
type: 'email',
defaultValue: '',
placeholder: 'your-email@example.com',
helpText: 'Email address to send from',
required: true,
},
{
name: 'username',
label: 'Username',
type: 'text',
defaultValue: '',
placeholder: 'SMTP username (often same as email)',
helpText: '',
required: true,
},
{
name: 'password',
label: 'Password',
type: 'password',
defaultValue: '',
placeholder: 'SMTP password or app password',
helpText: 'For Gmail, use an app password',
required: true,
},
{
name: 'smtpHost',
label: 'SMTP Host',
type: 'text',
defaultValue: '',
placeholder: 'smtp.gmail.com',
helpText: '',
required: true,
},
{
name: 'smtpPort',
label: 'SMTP Port',
type: 'text',
defaultValue: '587',
placeholder: '587',
helpText: 'Common ports: 587 (TLS), 465 (SSL)',
required: true,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default SendMailAction;

View File

@@ -1,39 +0,0 @@
import React from 'react';
import BaseAction from './BaseAction';
/**
* Twitter Post action component
*/
const TwitterPostAction = ({ index, onActionConfigChange, getConfigValue }) => {
// Field definitions for Twitter Post action
const fields = [
{
name: 'token',
label: 'Twitter API Token',
type: 'text',
defaultValue: '',
placeholder: 'Twitter API token',
helpText: 'Twitter API token with posting permissions',
required: true,
},
{
name: 'noCharacterLimits',
label: 'Disable character limit (280 characters)',
type: 'checkbox',
defaultValue: 'false',
helpText: 'Enable to bypass the 280 character limit check',
required: false,
},
];
return (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default TwitterPostAction;

View File

@@ -4,7 +4,7 @@ import ActionForm from '../ActionForm';
/**
* ActionsSection component for the agent form
*/
const ActionsSection = ({ formData, setFormData }) => {
const ActionsSection = ({ formData, setFormData, metadata }) => {
// Handle action change
const handleActionChange = (index, updatedAction) => {
const updatedActions = [...(formData.actions || [])];
@@ -47,6 +47,7 @@ const ActionsSection = ({ formData, setFormData }) => {
onChange={handleActionChange}
onRemove={handleActionRemove}
onAdd={handleAddAction}
fieldGroups={metadata?.actions || []}
/>
</div>
);

View File

@@ -3,53 +3,20 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Advanced Settings section of the agent form
*
* @param {Object} props Component props
* @param {Object} props.formData Current form data values
* @param {Function} props.handleInputChange Handler for input changes
* @param {Object} props.metadata Field metadata from the backend
*/
const AdvancedSettingsSection = ({ formData, handleInputChange }) => {
// Define field definitions for Advanced Settings section
const fields = [
{
name: 'max_steps',
label: 'Max Steps',
type: 'number',
defaultValue: 10,
helpText: 'Maximum number of steps the agent can take',
required: true,
},
{
name: 'max_iterations',
label: 'Max Iterations',
type: 'number',
defaultValue: 5,
helpText: 'Maximum number of iterations for each step',
required: true,
},
{
name: 'autonomous',
label: 'Autonomous Mode',
type: 'checkbox',
defaultValue: false,
helpText: 'Allow the agent to operate autonomously',
},
{
name: 'verbose',
label: 'Verbose Mode',
type: 'checkbox',
defaultValue: false,
helpText: 'Enable detailed logging',
},
{
name: 'allow_code_execution',
label: 'Allow Code Execution',
type: 'checkbox',
defaultValue: false,
helpText: 'Allow the agent to execute code (use with caution)',
},
];
const AdvancedSettingsSection = ({ formData, handleInputChange, metadata }) => {
// Get fields from metadata
const fields = metadata?.AdvancedSettingsSection || [];
// Handle field value changes
const handleFieldChange = (name, value) => {
// For checkboxes, convert string 'true'/'false' to boolean
if (['autonomous', 'verbose', 'allow_code_execution'].includes(name)) {
const field = fields.find(f => f.name === name);
if (field && field.type === 'checkbox') {
handleInputChange({
target: {
name,

View File

@@ -3,54 +3,37 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Basic Information section of the agent form
*
* @param {Object} props Component props
* @param {Object} props.formData Current form data values
* @param {Function} props.handleInputChange Handler for input changes
* @param {boolean} props.isEdit Whether the form is in edit mode
* @param {boolean} props.isGroupForm Whether the form is for a group
* @param {Object} props.metadata Field metadata from the backend
*/
const BasicInfoSection = ({ formData, handleInputChange, isEdit, isGroupForm }) => {
const BasicInfoSection = ({ formData, handleInputChange, isEdit, isGroupForm, metadata }) => {
// In group form context, we hide the basic info section entirely
if (isGroupForm) {
return null;
}
// Define field definitions for Basic Information section
const fields = [
{
name: 'name',
label: 'Name',
type: 'text',
defaultValue: '',
required: true,
helpText: isEdit ? 'Agent name cannot be changed after creation' : '',
disabled: isEdit, // This will be handled in the component
},
{
name: 'description',
label: 'Description',
type: 'textarea',
defaultValue: '',
},
{
name: 'identity_guidance',
label: 'Identity Guidance',
type: 'textarea',
defaultValue: '',
},
{
name: 'random_identity',
label: 'Random Identity',
type: 'checkbox',
defaultValue: false,
},
{
name: 'hud',
label: 'HUD',
type: 'checkbox',
defaultValue: false,
// Get fields from metadata and apply any client-side overrides
const fields = metadata?.BasicInfoSection?.map(field => {
// Special case for name field in edit mode
if (field.name === 'name' && isEdit) {
return {
...field,
disabled: true,
helpText: 'Agent name cannot be changed after creation'
};
}
];
return field;
}) || [];
// Handle field value changes
const handleFieldChange = (name, value) => {
// For checkboxes, convert string 'true'/'false' to boolean
if (name === 'random_identity' || name === 'hud') {
const field = fields.find(f => f.name === name);
if (field && field.type === 'checkbox') {
handleInputChange({
target: {
name,

View File

@@ -9,7 +9,8 @@ const ConnectorsSection = ({
handleAddConnector,
handleRemoveConnector,
handleConnectorNameChange,
handleConnectorConfigChange
handleConnectorConfigChange,
metadata
}) => {
return (
<div id="connectors-section">
@@ -24,6 +25,7 @@ const ConnectorsSection = ({
onRemoveConnector={handleRemoveConnector}
onConnectorNameChange={handleConnectorNameChange}
onConnectorConfigChange={handleConnectorConfigChange}
fieldGroups={metadata?.connectors || []}
/>
</div>
);

View File

@@ -3,53 +3,35 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Memory Settings section of the agent form
*
* @param {Object} props Component props
* @param {Object} props.formData Current form data values
* @param {Function} props.handleInputChange Handler for input changes
* @param {Object} props.metadata Field metadata from the backend
*/
const MemorySettingsSection = ({ formData, handleInputChange }) => {
// Define field definitions for Memory Settings section
const fields = [
{
name: 'memory_provider',
label: 'Memory Provider',
type: 'select',
defaultValue: 'local',
options: [
{ value: 'local', label: 'Local' },
{ value: 'redis', label: 'Redis' },
{ value: 'postgres', label: 'PostgreSQL' },
],
},
{
name: 'memory_collection',
label: 'Memory Collection',
type: 'text',
defaultValue: '',
placeholder: 'agent_memories',
},
{
name: 'memory_url',
label: 'Memory URL',
type: 'text',
defaultValue: '',
placeholder: 'redis://localhost:6379',
helpText: 'Connection URL for Redis or PostgreSQL',
},
{
name: 'memory_window_size',
label: 'Memory Window Size',
type: 'number',
defaultValue: 10,
helpText: 'Number of recent messages to include in context window',
},
];
const MemorySettingsSection = ({ formData, handleInputChange, metadata }) => {
// Get fields from metadata
const fields = metadata?.MemorySettingsSection || [];
// Handle field value changes
const handleFieldChange = (name, value) => {
handleInputChange({
target: {
name,
value
}
});
const field = fields.find(f => f.name === name);
if (field && field.type === 'checkbox') {
handleInputChange({
target: {
name,
type: 'checkbox',
checked: value === 'true'
}
});
} else {
handleInputChange({
target: {
name,
value
}
});
}
};
return (

View File

@@ -3,60 +3,35 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Model Settings section of the agent form
*
* @param {Object} props Component props
* @param {Object} props.formData Current form data values
* @param {Function} props.handleInputChange Handler for input changes
* @param {Object} props.metadata Field metadata from the backend
*/
const ModelSettingsSection = ({ formData, handleInputChange }) => {
// Define field definitions for Model Settings section
const fields = [
{
name: 'model',
label: 'Model',
type: 'text',
defaultValue: '',
},
{
name: 'multimodal_model',
label: 'Multimodal Model',
type: 'text',
defaultValue: '',
},
{
name: 'api_url',
label: 'API URL',
type: 'text',
defaultValue: '',
},
{
name: 'api_key',
label: 'API Key',
type: 'password',
defaultValue: '',
},
{
name: 'temperature',
label: 'Temperature',
type: 'number',
defaultValue: 0.7,
min: 0,
max: 2,
step: 0.1,
},
{
name: 'max_tokens',
label: 'Max Tokens',
type: 'number',
defaultValue: 2000,
min: 1,
},
];
const ModelSettingsSection = ({ formData, handleInputChange, metadata }) => {
// Get fields from metadata
const fields = metadata?.ModelSettingsSection || [];
// Handle field value changes
const handleFieldChange = (name, value) => {
handleInputChange({
target: {
name,
value
}
});
const field = fields.find(f => f.name === name);
if (field && field.type === 'checkbox') {
handleInputChange({
target: {
name,
type: 'checkbox',
checked: value === 'true'
}
});
} else {
handleInputChange({
target: {
name,
value
}
});
}
};
return (

View File

@@ -3,64 +3,47 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Prompts & Goals section of the agent form
*
* @param {Object} props Component props
* @param {Object} props.formData Current form data values
* @param {Function} props.handleInputChange Handler for input changes
* @param {boolean} props.isGroupForm Whether the form is for a group
* @param {Object} props.metadata Field metadata from the backend
*/
const PromptsGoalsSection = ({ formData, handleInputChange, isGroupForm }) => {
// Define field definitions for Prompts & Goals section
const PromptsGoalsSection = ({ formData, handleInputChange, isGroupForm, metadata }) => {
// Get fields based on metadata and form context
const getFields = () => {
// Base fields that are always shown
const baseFields = [
{
name: 'goals',
label: 'Goals',
type: 'textarea',
defaultValue: '',
helpText: 'Define the agent\'s goals (one per line)',
rows: 5,
},
{
name: 'constraints',
label: 'Constraints',
type: 'textarea',
defaultValue: '',
helpText: 'Define the agent\'s constraints (one per line)',
rows: 5,
},
{
name: 'tools',
label: 'Tools',
type: 'textarea',
defaultValue: '',
helpText: 'Define the agent\'s tools (one per line)',
rows: 5,
},
];
// Only include system_prompt field if not in group form context
if (!isGroupForm) {
return [
{
name: 'system_prompt',
label: 'System Prompt',
type: 'textarea',
defaultValue: '',
helpText: 'Instructions that define the agent\'s behavior',
rows: 5,
},
...baseFields
];
if (!metadata?.PromptsGoalsSection) {
return [];
}
return baseFields;
// If in group form, filter out system_prompt
if (isGroupForm) {
return metadata.PromptsGoalsSection.filter(field => field.name !== 'system_prompt');
}
return metadata.PromptsGoalsSection;
};
// Handle field value changes
const handleFieldChange = (name, value) => {
handleInputChange({
target: {
name,
value
}
});
const field = getFields().find(f => f.name === name);
if (field && field.type === 'checkbox') {
handleInputChange({
target: {
name,
type: 'checkbox',
checked: value === 'true'
}
});
} else {
handleInputChange({
target: {
name,
value
}
});
}
};
return (

View File

@@ -24,6 +24,9 @@ const FormField = ({
helpText = '',
options = [],
required = false,
min = 0,
max = 2**31,
step = 1,
}) => {
// Create label with required indicator
const labelWithIndicator = required ? (
@@ -86,6 +89,25 @@ const FormField = ({
{helpText && <small className="form-text text-muted">{helpText}</small>}
</>
);
case 'number':
return (
<>
<label htmlFor={id}>{labelWithIndicator}</label>
<input
type="number"
id={id}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
className="form-control"
placeholder={placeholder}
required={required}
min={min}
max={max}
step={step}
/>
{helpText && <small className="form-text text-muted">{helpText}</small>}
</>
);
default:
return (
<>

View File

@@ -1,46 +0,0 @@
import React from 'react';
import FormFieldDefinition from '../common/FormFieldDefinition';
/**
* Base connector component that renders form fields based on field definitions
*
* @param {Object} props Component props
* @param {Object} props.connector Connector data
* @param {number} props.index Connector index
* @param {Function} props.onConnectorConfigChange Handler for config changes
* @param {Function} props.getConfigValue Helper to get config values
* @param {Array} props.fields Field definitions for this connector
*/
const BaseConnector = ({
connector,
index,
onConnectorConfigChange,
getConfigValue,
fields = []
}) => {
// Create an object with all the current values
const currentValues = {};
// Pre-populate with current values or defaults
fields.forEach(field => {
currentValues[field.name] = getConfigValue(connector, field.name, field.defaultValue);
});
// Handle field value changes
const handleFieldChange = (name, value) => {
onConnectorConfigChange(index, name, value);
};
return (
<div className="connector-template">
<FormFieldDefinition
fields={fields}
values={currentValues}
onChange={handleFieldChange}
idPrefix={`connector${index}_`}
/>
</div>
);
};
export default BaseConnector;

View File

@@ -1,41 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* Discord connector template
*/
const DiscordConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for Discord connector
const fields = [
{
name: 'token',
label: 'Discord Bot Token',
type: 'text',
defaultValue: '',
placeholder: 'Bot token from Discord Developer Portal',
helpText: 'Get this from the Discord Developer Portal',
required: true,
},
{
name: 'defaultChannel',
label: 'Default Channel',
type: 'text',
defaultValue: '',
placeholder: '123456789012345678',
helpText: 'Channel ID to always answer even if not mentioned',
required: false,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default DiscordConnector;

View File

@@ -1,71 +0,0 @@
import React, { useState } from 'react';
/**
* Fallback connector template for unknown connector types
*/
const FallbackConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
const [newConfigKey, setNewConfigKey] = useState('');
// Parse config if it's a string
let parsedConfig = connector.config;
if (typeof parsedConfig === 'string') {
try {
parsedConfig = JSON.parse(parsedConfig);
} catch (err) {
console.error('Error parsing config:', err);
parsedConfig = {};
}
} else if (!parsedConfig) {
parsedConfig = {};
}
// Handle adding a new custom field
const handleAddCustomField = () => {
if (newConfigKey) {
onConnectorConfigChange(index, newConfigKey, '');
setNewConfigKey('');
}
};
return (
<div className="connector-template">
{/* Individual field inputs */}
{parsedConfig && Object.entries(parsedConfig).map(([key, value]) => (
<div key={key} className="form-group mb-3">
<label htmlFor={`connector-${index}-${key}`}>{key}</label>
<input
type="text"
id={`connector-${index}-${key}`}
className="form-control"
value={value}
onChange={(e) => onConnectorConfigChange(index, key, e.target.value)}
/>
</div>
))}
{/* Add custom configuration field */}
<div className="add-config-field mt-4">
<h5>Add Custom Configuration Field</h5>
<div className="input-group mb-3">
<input
type="text"
placeholder="New config key"
className="form-control"
value={newConfigKey}
onChange={(e) => setNewConfigKey(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddCustomField()}
/>
<button
type="button"
className="btn btn-outline-primary"
onClick={handleAddCustomField}
>
<i className="fas fa-plus"></i> Add Field
</button>
</div>
</div>
</div>
);
};
export default FallbackConnector;

View File

@@ -1,71 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* GitHub Issues connector template
*/
const GithubIssuesConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for GitHub Issues connector
const fields = [
{
name: 'token',
label: 'GitHub Personal Access Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: '',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: '',
required: true,
},
{
name: 'replyIfNoReplies',
label: 'Reply Behavior',
type: 'select',
defaultValue: 'false',
options: [
{ value: 'false', label: 'Reply to all issues' },
{ value: 'true', label: 'Only reply to issues with no comments' },
],
helpText: '',
required: false,
},
{
name: 'pollInterval',
label: 'Poll Interval',
type: 'text',
defaultValue: '10m',
placeholder: '10m',
helpText: 'How often to check for new issues (e.g., 10m, 1h)',
required: false,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssuesConnector;

View File

@@ -1,71 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* GitHub PRs connector template
*/
const GithubPRsConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for GitHub PRs connector
const fields = [
{
name: 'token',
label: 'GitHub Personal Access Token',
type: 'text',
defaultValue: '',
placeholder: 'ghp_...',
helpText: 'Personal access token with repo scope',
required: true,
},
{
name: 'owner',
label: 'Repository Owner',
type: 'text',
defaultValue: '',
placeholder: 'username or organization',
helpText: '',
required: true,
},
{
name: 'repository',
label: 'Repository Name',
type: 'text',
defaultValue: '',
placeholder: 'repository-name',
helpText: '',
required: true,
},
{
name: 'replyIfNoReplies',
label: 'Reply Behavior',
type: 'select',
defaultValue: 'false',
options: [
{ value: 'false', label: 'Reply to all PRs' },
{ value: 'true', label: 'Only reply to PRs with no comments' },
],
helpText: '',
required: false,
},
{
name: 'pollInterval',
label: 'Poll Interval',
type: 'text',
defaultValue: '10m',
placeholder: '10m',
helpText: 'How often to check for new PRs (e.g., 10m, 1h)',
required: false,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubPRsConnector;

View File

@@ -1,67 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* IRC connector template
*/
const IRCConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for IRC connector
const fields = [
{
name: 'server',
label: 'IRC Server',
type: 'text',
defaultValue: '',
placeholder: 'irc.libera.chat',
helpText: 'IRC server address',
required: true,
},
{
name: 'port',
label: 'Port',
type: 'text',
defaultValue: '6667',
placeholder: '6667',
helpText: 'IRC server port',
required: true,
},
{
name: 'nickname',
label: 'Nickname',
type: 'text',
defaultValue: '',
placeholder: 'MyAgentBot',
helpText: 'Bot nickname',
required: true,
},
{
name: 'channel',
label: 'Channel',
type: 'text',
defaultValue: '',
placeholder: '#channel1',
helpText: 'Channel to join',
required: true,
},
{
name: 'alwaysReply',
label: 'Always Reply',
type: 'checkbox',
defaultValue: 'false',
helpText: 'If checked, the agent will reply to all messages in the channel',
required: false,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default IRCConnector;

View File

@@ -1,58 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* Slack connector template
*/
const SlackConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for Slack connector
const fields = [
{
name: 'appToken',
label: 'Slack App Token',
type: 'text',
defaultValue: '',
placeholder: 'xapp-...',
helpText: 'App-level token starting with xapp-',
required: true,
},
{
name: 'botToken',
label: 'Slack Bot Token',
type: 'text',
defaultValue: '',
placeholder: 'xoxb-...',
helpText: 'Bot token starting with xoxb-',
required: true,
},
{
name: 'channelID',
label: 'Slack Channel ID',
type: 'text',
defaultValue: '',
placeholder: 'C1234567890',
helpText: 'Optional: Specific channel ID to join',
required: false,
},
{
name: 'alwaysReply',
label: 'Always Reply',
type: 'checkbox',
defaultValue: 'false',
helpText: 'If checked, the agent will reply to all messages in the channel',
required: false,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default SlackConnector;

View File

@@ -1,32 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* Telegram connector template
*/
const TelegramConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for Telegram connector
const fields = [
{
name: 'token',
label: 'Telegram Bot Token',
type: 'text',
defaultValue: '',
placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
helpText: 'Get this from @BotFather on Telegram',
required: true,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default TelegramConnector;

View File

@@ -1,68 +0,0 @@
import React from 'react';
import BaseConnector from './BaseConnector';
/**
* Twitter connector template
*/
const TwitterConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
// Field definitions for Twitter connector
const fields = [
{
name: 'apiKey',
label: 'API Key',
type: 'text',
defaultValue: '',
placeholder: 'Twitter API Key',
helpText: '',
required: true,
},
{
name: 'apiSecret',
label: 'API Secret',
type: 'password',
defaultValue: '',
placeholder: 'Twitter API Secret',
helpText: '',
required: true,
},
{
name: 'accessToken',
label: 'Access Token',
type: 'text',
defaultValue: '',
placeholder: 'Twitter Access Token',
helpText: '',
required: true,
},
{
name: 'accessSecret',
label: 'Access Token Secret',
type: 'password',
defaultValue: '',
placeholder: 'Twitter Access Token Secret',
helpText: '',
required: true,
},
{
name: 'bearerToken',
label: 'Bearer Token',
type: 'password',
defaultValue: '',
placeholder: 'Twitter Bearer Token',
helpText: '',
required: true,
},
];
return (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default TwitterConnector;

View File

@@ -1,12 +1,14 @@
import { useState, useEffect } from 'react';
import { useParams, useOutletContext, useNavigate } from 'react-router-dom';
import { useAgent } from '../hooks/useAgent';
import { agentApi } from '../utils/api';
import AgentForm from '../components/AgentForm';
function AgentSettings() {
const { name } = useParams();
const { showToast } = useOutletContext();
const navigate = useNavigate();
const [metadata, setMetadata] = useState(null);
const [formData, setFormData] = useState({
name: '',
description: '',
@@ -47,9 +49,28 @@ function AgentSettings() {
deleteAgent
} = useAgent(name);
// 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();
}, []);
// Load agent data when component mounts
useEffect(() => {
if (agent) {
// Set form data from agent config
setFormData({
...formData,
...agent,
@@ -162,6 +183,7 @@ function AgentSettings() {
onSubmit={handleSubmit}
loading={loading}
submitButtonText="Save Changes"
metadata={metadata}
/>
</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 { agentApi } from '../utils/api';
import AgentForm from '../components/AgentForm';
@@ -7,6 +7,7 @@ function CreateAgent() {
const navigate = useNavigate();
const { showToast } = useOutletContext();
const [loading, setLoading] = useState(false);
const [metadata, setMetadata] = useState(null);
const [formData, setFormData] = useState({
name: '',
description: '',
@@ -37,6 +38,24 @@ function CreateAgent() {
avatar_style: 'default',
});
// 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 submission
const handleSubmit = async (e) => {
e.preventDefault();
@@ -80,6 +99,7 @@ function CreateAgent() {
loading={loading}
submitButtonText="Create Agent"
isEdit={false}
metadata={metadata}
/>
</div>
</div>

View File

@@ -42,6 +42,51 @@ export const agentApi = {
return handleResponse(response);
},
// Get agent configuration metadata
getAgentConfigMetadata: async () => {
const response = await fetch(buildUrl(API_CONFIG.endpoints.agentConfigMetadata), {
headers: API_CONFIG.headers
});
const metadata = await handleResponse(response);
// Process metadata to group by section
if (metadata) {
const groupedMetadata = {};
// Handle Fields - Group by section
if (metadata.Fields) {
metadata.Fields.forEach(field => {
const section = field.tags?.section || 'Other';
const sectionKey = `${section}Section`; // Add "Section" postfix
if (!groupedMetadata[sectionKey]) {
groupedMetadata[sectionKey] = [];
}
groupedMetadata[sectionKey].push(field);
});
}
// Pass through connectors and actions field groups directly
// Make sure to assign the correct metadata to each section
if (metadata.Connectors) {
console.log("Original Connectors metadata:", metadata.Connectors);
groupedMetadata.connectors = metadata.Connectors;
}
if (metadata.Actions) {
console.log("Original Actions metadata:", metadata.Actions);
groupedMetadata.actions = metadata.Actions;
}
console.log("Processed metadata:", groupedMetadata);
return groupedMetadata;
}
return metadata;
},
// Create a new agent
createAgent: async (config) => {
const response = await fetch(buildUrl(API_CONFIG.endpoints.createAgent), {

View File

@@ -20,6 +20,7 @@ export const API_CONFIG = {
// Agent endpoints
agents: '/api/agents',
agentConfig: (name) => `/api/agent/${name}/config`,
agentConfigMetadata: '/api/agent/config/metadata',
createAgent: '/create',
deleteAgent: (name) => `/delete/${name}`,
pauseAgent: (name) => `/pause/${name}`,