chore(ui): Move some field definitions server side
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
146
webui/react-ui/src/components/ConfigForm.jsx
Normal file
146
webui/react-ui/src/components/ConfigForm.jsx
Normal 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;
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
45
webui/react-ui/src/utils/api.js
vendored
45
webui/react-ui/src/utils/api.js
vendored
@@ -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), {
|
||||
|
||||
1
webui/react-ui/src/utils/config.js
vendored
1
webui/react-ui/src/utils/config.js
vendored
@@ -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}`,
|
||||
|
||||
Reference in New Issue
Block a user