feat(ui): Action playground config and parameter forms (#129)

This commit is contained in:
Richard Palethorpe
2025-04-03 14:26:14 +01:00
committed by GitHub
parent ffee9d8307
commit 1eee5b5a32
3 changed files with 101 additions and 141 deletions

View File

@@ -1,5 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { Outlet, Link } from 'react-router-dom' import { Outlet, Link, useOutletContext } from 'react-router-dom'
import './App.css' import './App.css'
function App() { function App() {
@@ -19,6 +19,9 @@ function App() {
setMobileMenuOpen(!mobileMenuOpen); setMobileMenuOpen(!mobileMenuOpen);
}; };
// Provide showToast to children
const context = { showToast };
return ( return (
<div className="app-container"> <div className="app-container">
{/* Navigation Menu */} {/* Navigation Menu */}
@@ -110,7 +113,7 @@ function App() {
{/* Main Content Area */} {/* Main Content Area */}
<main className="main-content"> <main className="main-content">
<div className="container"> <div className="container">
<Outlet context={{ showToast }} /> <Outlet context={context} />
</div> </div>
</main> </main>

View File

@@ -1,14 +1,15 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useOutletContext, useNavigate } from 'react-router-dom'; import { useOutletContext, useNavigate } from 'react-router-dom';
import { actionApi } from '../utils/api'; import { actionApi } from '../utils/api';
import FormFieldDefinition from '../components/common/FormFieldDefinition';
function ActionsPlayground() { function ActionsPlayground() {
const { showToast } = useOutletContext(); const { showToast } = useOutletContext();
const navigate = useNavigate();
const [actions, setActions] = useState([]); const [actions, setActions] = useState([]);
const [selectedAction, setSelectedAction] = useState(''); const [selectedAction, setSelectedAction] = useState('');
const [configJson, setConfigJson] = useState('{}'); const [actionMeta, setActionMeta] = useState(null);
const [paramsJson, setParamsJson] = useState('{}'); const [configValues, setConfigValues] = useState({});
const [paramsValues, setParamsValues] = useState({});
const [result, setResult] = useState(null); const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [loadingActions, setLoadingActions] = useState(true); const [loadingActions, setLoadingActions] = useState(true);
@@ -24,19 +25,29 @@ function ActionsPlayground() {
// Fetch available actions // Fetch available actions
useEffect(() => { useEffect(() => {
const fetchActions = async () => { const fetchActions = async () => {
try {
const response = await actionApi.listActions(); const response = await actionApi.listActions();
setActions(response); setActions(response);
} catch (err) {
console.error('Error fetching actions:', err);
showToast('Failed to load actions', 'error');
} finally {
setLoadingActions(false); setLoadingActions(false);
}
}; };
fetchActions(); fetchActions();
}, [showToast]); }, []);
// Fetch action metadata when an action is selected
useEffect(() => {
if (selectedAction) {
const fetchActionMeta = async () => {
const response = await actionApi.getAgentConfigMeta();
const meta = response.actions.find(a => a.name === selectedAction);
setActionMeta(meta);
// Reset values when action changes
setConfigValues({});
setParamsValues({});
};
fetchActionMeta();
}
}, [selectedAction]);
// Handle action selection // Handle action selection
const handleActionChange = (e) => { const handleActionChange = (e) => {
@@ -44,13 +55,20 @@ function ActionsPlayground() {
setResult(null); setResult(null);
}; };
// Handle JSON input changes // Handle config field changes
const handleConfigChange = (e) => { const handleConfigChange = (fieldName, value) => {
setConfigJson(e.target.value); setConfigValues(prev => ({
...prev,
[fieldName]: value
}));
}; };
const handleParamsChange = (e) => { // Handle params field changes
setParamsJson(e.target.value); const handleParamsChange = (fieldName, value) => {
setParamsValues(prev => ({
...prev,
[fieldName]: value
}));
}; };
// Execute the selected action // Execute the selected action
@@ -66,31 +84,11 @@ function ActionsPlayground() {
setResult(null); setResult(null);
try { try {
// Parse JSON inputs
let config = {};
let params = {};
try {
config = JSON.parse(configJson);
} catch (err) {
showToast('Invalid configuration JSON', 'error');
setLoading(false);
return;
}
try {
params = JSON.parse(paramsJson);
} catch (err) {
showToast('Invalid parameters JSON', 'error');
setLoading(false);
return;
}
// Prepare action data // Prepare action data
const actionData = { const actionData = {
action: selectedAction, action: selectedAction,
config: config, config: configValues,
params: params params: paramsValues
}; };
// Execute action // Execute action
@@ -98,115 +96,66 @@ function ActionsPlayground() {
setResult(response); setResult(response);
showToast('Action executed successfully', 'success'); showToast('Action executed successfully', 'success');
} catch (err) { } catch (err) {
console.error('Error executing action:', err); showToast('Failed to execute action', 'error');
showToast(`Failed to execute action: ${err.message}`, 'error');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<div className="actions-playground-container"> <div className="actions-playground">
<header className="page-header">
<h1>Actions Playground</h1> <h1>Actions Playground</h1>
<p>Test and execute actions directly from the UI</p>
</header>
<div className="actions-playground-content"> <form onSubmit={handleExecuteAction}>
<div className="section-box"> <div className="form-group">
<h2>Select an Action</h2> <label htmlFor="actionSelect">Select Action</label>
<div className="form-group mb-4">
<label htmlFor="action-select">Available Actions:</label>
<select <select
id="action-select" id="actionSelect"
value={selectedAction} value={selectedAction}
onChange={handleActionChange} onChange={handleActionChange}
className="form-control"
disabled={loadingActions} disabled={loadingActions}
> >
<option value="">-- Select an action --</option> <option value="">Select an action...</option>
{actions.map((action) => ( {actions.map(action => (
<option key={action} value={action}>{action}</option> <option key={action} value={action}>{action}</option>
))} ))}
</select> </select>
</div> </div>
</div>
{selectedAction && ( {actionMeta && (
<div className="section-box"> <>
<h2>Action Configuration</h2> <h2>Configuration</h2>
<FormFieldDefinition
<form onSubmit={handleExecuteAction}> fields={actionMeta.configFields || []}
<div className="form-group mb-6"> values={configValues}
<label htmlFor="config-json">Configuration (JSON):</label>
<textarea
id="config-json"
value={configJson}
onChange={handleConfigChange} onChange={handleConfigChange}
className="form-control" idPrefix="config_"
rows="5"
placeholder='{"key": "value"}'
/> />
<p className="text-xs text-gray-400 mt-1">Enter JSON configuration for the action</p>
</div>
<div className="form-group mb-6"> <h2>Parameters</h2>
<label htmlFor="params-json">Parameters (JSON):</label> <FormFieldDefinition
<textarea fields={actionMeta.paramFields || []}
id="params-json" values={paramsValues}
value={paramsJson}
onChange={handleParamsChange} onChange={handleParamsChange}
className="form-control" idPrefix="param_"
rows="5"
placeholder='{"key": "value"}'
/> />
<p className="text-xs text-gray-400 mt-1">Enter JSON parameters for the action</p> </>
</div>
<div className="flex justify-end">
<button
type="submit"
className="action-btn"
disabled={loading}
>
{loading ? (
<><i className="fas fa-spinner fa-spin"></i> Executing...</>
) : (
<><i className="fas fa-play"></i> Execute Action</>
)} )}
<div className="form-group">
<button type="submit" disabled={loading || !selectedAction}>
{loading ? 'Executing...' : 'Execute Action'}
</button> </button>
</div> </div>
</form> </form>
</div>
)}
{result && ( {result && (
<div className="section-box"> <div className="result-section">
<h2>Action Results</h2> <h2>Result</h2>
<pre>{JSON.stringify(result, null, 2)}</pre>
<div className="result-container" style={{
maxHeight: '400px',
overflow: 'auto',
border: '1px solid rgba(94, 0, 255, 0.2)',
borderRadius: '4px',
padding: '10px',
backgroundColor: 'rgba(30, 30, 30, 0.7)'
}}>
{typeof result === 'object' ? (
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{JSON.stringify(result, null, 2)}
</pre>
) : (
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{result}
</pre>
)}
</div>
</div> </div>
)} )}
</div> </div>
</div>
); );
} }

View File

@@ -216,6 +216,14 @@ export const actionApi = {
return handleResponse(response); return handleResponse(response);
}, },
// Get agent configuration metadata
getAgentConfigMeta: async () => {
const response = await fetch(buildUrl(API_CONFIG.endpoints.agentConfigMetadata), {
headers: API_CONFIG.headers
});
return handleResponse(response);
},
// Execute an action for an agent // Execute an action for an agent
executeAction: async (name, actionData) => { executeAction: async (name, actionData) => {
const response = await fetch(buildUrl(API_CONFIG.endpoints.executeAction(name)), { const response = await fetch(buildUrl(API_CONFIG.endpoints.executeAction(name)), {