Skip to content

Commit 1eee5b5

Browse files
authored
feat(ui): Action playground config and parameter forms (#129)
1 parent ffee9d8 commit 1eee5b5

File tree

3 files changed

+101
-141
lines changed

3 files changed

+101
-141
lines changed

webui/react-ui/src/App.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react'
2-
import { Outlet, Link } from 'react-router-dom'
2+
import { Outlet, Link, useOutletContext } from 'react-router-dom'
33
import './App.css'
44

55
function App() {
@@ -19,6 +19,9 @@ function App() {
1919
setMobileMenuOpen(!mobileMenuOpen);
2020
};
2121

22+
// Provide showToast to children
23+
const context = { showToast };
24+
2225
return (
2326
<div className="app-container">
2427
{/* Navigation Menu */}
@@ -110,7 +113,7 @@ function App() {
110113
{/* Main Content Area */}
111114
<main className="main-content">
112115
<div className="container">
113-
<Outlet context={{ showToast }} />
116+
<Outlet context={context} />
114117
</div>
115118
</main>
116119

webui/react-ui/src/pages/ActionsPlayground.jsx

Lines changed: 88 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { useState, useEffect } from 'react';
22
import { useOutletContext, useNavigate } from 'react-router-dom';
33
import { actionApi } from '../utils/api';
4+
import FormFieldDefinition from '../components/common/FormFieldDefinition';
45

56
function ActionsPlayground() {
67
const { showToast } = useOutletContext();
7-
const navigate = useNavigate();
88
const [actions, setActions] = useState([]);
99
const [selectedAction, setSelectedAction] = useState('');
10-
const [configJson, setConfigJson] = useState('{}');
11-
const [paramsJson, setParamsJson] = useState('{}');
10+
const [actionMeta, setActionMeta] = useState(null);
11+
const [configValues, setConfigValues] = useState({});
12+
const [paramsValues, setParamsValues] = useState({});
1213
const [result, setResult] = useState(null);
1314
const [loading, setLoading] = useState(false);
1415
const [loadingActions, setLoadingActions] = useState(true);
@@ -24,33 +25,50 @@ function ActionsPlayground() {
2425
// Fetch available actions
2526
useEffect(() => {
2627
const fetchActions = async () => {
27-
try {
28-
const response = await actionApi.listActions();
29-
setActions(response);
30-
} catch (err) {
31-
console.error('Error fetching actions:', err);
32-
showToast('Failed to load actions', 'error');
33-
} finally {
34-
setLoadingActions(false);
35-
}
28+
const response = await actionApi.listActions();
29+
setActions(response);
30+
setLoadingActions(false);
3631
};
3732

3833
fetchActions();
39-
}, [showToast]);
34+
}, []);
35+
36+
// Fetch action metadata when an action is selected
37+
useEffect(() => {
38+
if (selectedAction) {
39+
const fetchActionMeta = async () => {
40+
const response = await actionApi.getAgentConfigMeta();
41+
const meta = response.actions.find(a => a.name === selectedAction);
42+
setActionMeta(meta);
43+
// Reset values when action changes
44+
setConfigValues({});
45+
setParamsValues({});
46+
};
47+
48+
fetchActionMeta();
49+
}
50+
}, [selectedAction]);
4051

4152
// Handle action selection
4253
const handleActionChange = (e) => {
4354
setSelectedAction(e.target.value);
4455
setResult(null);
4556
};
4657

47-
// Handle JSON input changes
48-
const handleConfigChange = (e) => {
49-
setConfigJson(e.target.value);
58+
// Handle config field changes
59+
const handleConfigChange = (fieldName, value) => {
60+
setConfigValues(prev => ({
61+
...prev,
62+
[fieldName]: value
63+
}));
5064
};
5165

52-
const handleParamsChange = (e) => {
53-
setParamsJson(e.target.value);
66+
// Handle params field changes
67+
const handleParamsChange = (fieldName, value) => {
68+
setParamsValues(prev => ({
69+
...prev,
70+
[fieldName]: value
71+
}));
5472
};
5573

5674
// Execute the selected action
@@ -66,146 +84,77 @@ function ActionsPlayground() {
6684
setResult(null);
6785

6886
try {
69-
// Parse JSON inputs
70-
let config = {};
71-
let params = {};
72-
73-
try {
74-
config = JSON.parse(configJson);
75-
} catch (err) {
76-
showToast('Invalid configuration JSON', 'error');
77-
setLoading(false);
78-
return;
79-
}
80-
81-
try {
82-
params = JSON.parse(paramsJson);
83-
} catch (err) {
84-
showToast('Invalid parameters JSON', 'error');
85-
setLoading(false);
86-
return;
87-
}
88-
8987
// Prepare action data
9088
const actionData = {
9189
action: selectedAction,
92-
config: config,
93-
params: params
90+
config: configValues,
91+
params: paramsValues
9492
};
9593

9694
// Execute action
9795
const response = await actionApi.executeAction(selectedAction, actionData);
9896
setResult(response);
9997
showToast('Action executed successfully', 'success');
10098
} catch (err) {
101-
console.error('Error executing action:', err);
102-
showToast(`Failed to execute action: ${err.message}`, 'error');
99+
showToast('Failed to execute action', 'error');
103100
} finally {
104101
setLoading(false);
105102
}
106103
};
107104

108105
return (
109-
<div className="actions-playground-container">
110-
<header className="page-header">
111-
<h1>Actions Playground</h1>
112-
<p>Test and execute actions directly from the UI</p>
113-
</header>
106+
<div className="actions-playground">
107+
<h1>Actions Playground</h1>
114108

115-
<div className="actions-playground-content">
116-
<div className="section-box">
117-
<h2>Select an Action</h2>
118-
119-
<div className="form-group mb-4">
120-
<label htmlFor="action-select">Available Actions:</label>
121-
<select
122-
id="action-select"
123-
value={selectedAction}
124-
onChange={handleActionChange}
125-
className="form-control"
126-
disabled={loadingActions}
127-
>
128-
<option value="">-- Select an action --</option>
129-
{actions.map((action) => (
130-
<option key={action} value={action}>{action}</option>
131-
))}
132-
</select>
133-
</div>
109+
<form onSubmit={handleExecuteAction}>
110+
<div className="form-group">
111+
<label htmlFor="actionSelect">Select Action</label>
112+
<select
113+
id="actionSelect"
114+
value={selectedAction}
115+
onChange={handleActionChange}
116+
disabled={loadingActions}
117+
>
118+
<option value="">Select an action...</option>
119+
{actions.map(action => (
120+
<option key={action} value={action}>{action}</option>
121+
))}
122+
</select>
134123
</div>
135-
136-
{selectedAction && (
137-
<div className="section-box">
138-
<h2>Action Configuration</h2>
139-
140-
<form onSubmit={handleExecuteAction}>
141-
<div className="form-group mb-6">
142-
<label htmlFor="config-json">Configuration (JSON):</label>
143-
<textarea
144-
id="config-json"
145-
value={configJson}
146-
onChange={handleConfigChange}
147-
className="form-control"
148-
rows="5"
149-
placeholder='{"key": "value"}'
150-
/>
151-
<p className="text-xs text-gray-400 mt-1">Enter JSON configuration for the action</p>
152-
</div>
153-
154-
<div className="form-group mb-6">
155-
<label htmlFor="params-json">Parameters (JSON):</label>
156-
<textarea
157-
id="params-json"
158-
value={paramsJson}
159-
onChange={handleParamsChange}
160-
className="form-control"
161-
rows="5"
162-
placeholder='{"key": "value"}'
163-
/>
164-
<p className="text-xs text-gray-400 mt-1">Enter JSON parameters for the action</p>
165-
</div>
166-
167-
<div className="flex justify-end">
168-
<button
169-
type="submit"
170-
className="action-btn"
171-
disabled={loading}
172-
>
173-
{loading ? (
174-
<><i className="fas fa-spinner fa-spin"></i> Executing...</>
175-
) : (
176-
<><i className="fas fa-play"></i> Execute Action</>
177-
)}
178-
</button>
179-
</div>
180-
</form>
181-
</div>
182-
)}
183-
184-
{result && (
185-
<div className="section-box">
186-
<h2>Action Results</h2>
187-
188-
<div className="result-container" style={{
189-
maxHeight: '400px',
190-
overflow: 'auto',
191-
border: '1px solid rgba(94, 0, 255, 0.2)',
192-
borderRadius: '4px',
193-
padding: '10px',
194-
backgroundColor: 'rgba(30, 30, 30, 0.7)'
195-
}}>
196-
{typeof result === 'object' ? (
197-
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
198-
{JSON.stringify(result, null, 2)}
199-
</pre>
200-
) : (
201-
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
202-
{result}
203-
</pre>
204-
)}
205-
</div>
206-
</div>
124+
125+
{actionMeta && (
126+
<>
127+
<h2>Configuration</h2>
128+
<FormFieldDefinition
129+
fields={actionMeta.configFields || []}
130+
values={configValues}
131+
onChange={handleConfigChange}
132+
idPrefix="config_"
133+
/>
134+
135+
<h2>Parameters</h2>
136+
<FormFieldDefinition
137+
fields={actionMeta.paramFields || []}
138+
values={paramsValues}
139+
onChange={handleParamsChange}
140+
idPrefix="param_"
141+
/>
142+
</>
207143
)}
208-
</div>
144+
145+
<div className="form-group">
146+
<button type="submit" disabled={loading || !selectedAction}>
147+
{loading ? 'Executing...' : 'Execute Action'}
148+
</button>
149+
</div>
150+
</form>
151+
152+
{result && (
153+
<div className="result-section">
154+
<h2>Result</h2>
155+
<pre>{JSON.stringify(result, null, 2)}</pre>
156+
</div>
157+
)}
209158
</div>
210159
);
211160
}

webui/react-ui/src/utils/api.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ export const actionApi = {
216216
return handleResponse(response);
217217
},
218218

219+
// Get agent configuration metadata
220+
getAgentConfigMeta: async () => {
221+
const response = await fetch(buildUrl(API_CONFIG.endpoints.agentConfigMetadata), {
222+
headers: API_CONFIG.headers
223+
});
224+
return handleResponse(response);
225+
},
226+
219227
// Execute an action for an agent
220228
executeAction: async (name, actionData) => {
221229
const response = await fetch(buildUrl(API_CONFIG.endpoints.executeAction(name)), {

0 commit comments

Comments
 (0)