From 92272b2ec1fc6c24f390b7d75c7dc1c3b547b32a Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Tue, 8 Apr 2025 22:41:19 +0100 Subject: [PATCH] feat(ui): Action playground config and parameter forms Signed-off-by: Richard Palethorpe --- core/action/custom.go | 5 + webui/app.go | 24 +++ .../react-ui/src/pages/ActionsPlayground.jsx | 159 ++++++++++++++---- webui/react-ui/src/utils/api.js | 57 ++++++- webui/react-ui/src/utils/config.js | 1 + webui/routes.go | 1 + 6 files changed, 212 insertions(+), 35 deletions(-) diff --git a/core/action/custom.go b/core/action/custom.go index be8157d..af5d590 100644 --- a/core/action/custom.go +++ b/core/action/custom.go @@ -95,6 +95,11 @@ func (a *CustomAction) Run(ctx context.Context, params types.ActionParams) (type func (a *CustomAction) Definition() types.ActionDefinition { + if a.i == nil { + xlog.Error("Interpreter is not initialized for custom action", "action", a.config["name"]) + return types.ActionDefinition{} + } + v, err := a.i.Eval(fmt.Sprintf("%s.Definition", a.config["name"])) if err != nil { xlog.Error("Error getting custom action definition", "error", err) diff --git a/webui/app.go b/webui/app.go index 9fecefa..19632b4 100644 --- a/webui/app.go +++ b/webui/app.go @@ -419,6 +419,30 @@ func (a *App) Chat(pool *state.AgentPool) func(c *fiber.Ctx) error { } } +func (a *App) GetActionDefinition(pool *state.AgentPool) func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + payload := struct { + Config map[string]string `json:"config"` + }{} + + if err := c.BodyParser(&payload); err != nil { + xlog.Error("Error parsing action payload", "error", err) + return errorJSONMessage(c, err.Error()) + } + + actionName := c.Params("name") + + xlog.Debug("Executing action", "action", actionName, "config", payload.Config) + a, err := services.Action(actionName, "", payload.Config, pool, map[string]string{}) + if err != nil { + xlog.Error("Error creating action", "error", err) + return errorJSONMessage(c, err.Error()) + } + + return c.JSON(a.Definition()) + } +} + func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error { payload := struct { diff --git a/webui/react-ui/src/pages/ActionsPlayground.jsx b/webui/react-ui/src/pages/ActionsPlayground.jsx index 3218eb3..e5d16df 100644 --- a/webui/react-ui/src/pages/ActionsPlayground.jsx +++ b/webui/react-ui/src/pages/ActionsPlayground.jsx @@ -1,6 +1,11 @@ import { useState, useEffect } from 'react'; import { useOutletContext, useNavigate } from 'react-router-dom'; -import { actionApi } from '../utils/api'; +import { actionApi, agentApi } from '../utils/api'; +import FormFieldDefinition from '../components/common/FormFieldDefinition'; +import hljs from 'highlight.js/lib/core'; +import json from 'highlight.js/lib/languages/json'; +import 'highlight.js/styles/monokai.css'; +hljs.registerLanguage('json', json); function ActionsPlayground() { const { showToast } = useOutletContext(); @@ -12,6 +17,10 @@ function ActionsPlayground() { const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const [loadingActions, setLoadingActions] = useState(true); + const [actionMetadata, setActionMetadata] = useState(null); + const [agentMetadata, setAgentMetadata] = useState(null); + const [configFields, setConfigFields] = useState([]); + const [paramFields, setParamFields] = useState([]); // Update document title useEffect(() => { @@ -36,21 +45,106 @@ function ActionsPlayground() { }; fetchActions(); - }, [showToast]); + }, []); + + // Fetch agent metadata on mount + useEffect(() => { + const fetchAgentMetadata = async () => { + try { + const metadata = await agentApi.getAgentConfigMetadata(); + setAgentMetadata(metadata); + } catch (err) { + console.error('Error fetching agent metadata:', err); + showToast('Failed to load agent metadata', 'error'); + } + }; + + fetchAgentMetadata(); + }, []); + + // Fetch action definition when action is selected or config changes + useEffect(() => { + if (!selectedAction) return; + + const fetchActionDefinition = async () => { + try { + // Get config fields from agent metadata + const actionMeta = agentMetadata?.actions?.find(action => action.name === selectedAction); + const configFields = actionMeta?.fields || []; + console.debug('Config fields:', configFields); + setConfigFields(configFields); + + // Parse current config to pass to action definition + let currentConfig = {}; + try { + currentConfig = JSON.parse(configJson); + } catch (err) { + console.error('Error parsing current config:', err); + } + + // Get parameter fields from action definition + const paramFields = await actionApi.getActionDefinition(selectedAction, currentConfig); + console.debug('Parameter fields:', paramFields); + setParamFields(paramFields); + + // Reset JSON to match the new fields + setConfigJson(JSON.stringify(currentConfig, null, 2)); + setParamsJson(JSON.stringify({}, null, 2)); + setResult(null); + } catch (err) { + console.error('Error fetching action definition:', err); + showToast('Failed to load action definition', 'error'); + } + }; + + fetchActionDefinition(); + }, [selectedAction, agentMetadata]); // Handle action selection const handleActionChange = (e) => { setSelectedAction(e.target.value); + setConfigJson('{}'); + setParamsJson('{}'); setResult(null); }; - // Handle JSON input changes - const handleConfigChange = (e) => { - setConfigJson(e.target.value); + // Helper to generate onChange handlers for form fields + const makeFieldChangeHandler = (fields, updateFn) => (e) => { + let value; + if (e && e.target) { + const fieldName = e.target.name; + const fieldDef = fields.find(f => f.name === fieldName); + const fieldType = fieldDef ? fieldDef.type : undefined; + if (fieldType === 'checkbox') { + value = e.target.checked; + } else if (fieldType === 'number') { + value = e.target.value === '' ? '' : Number(e.target.value); + } else { + value = e.target.value; + } + updateFn(fieldName, value); + } }; - const handleParamsChange = (e) => { - setParamsJson(e.target.value); + // Handle form field changes + const handleConfigChange = (field, value) => { + try { + const config = JSON.parse(configJson); + config[field] = value; + setConfigJson(JSON.stringify(config, null, 2)); + } catch (err) { + console.error('Error updating config:', err); + } + }; + + const handleParamsChange = (field, value) => { + try { + const params = JSON.parse(paramsJson); + params[field] = value; + setParamsJson(JSON.stringify(params, null, 2)); + } catch (err) { + console.error('Error updating params:', err); + } }; // Execute the selected action @@ -135,34 +229,31 @@ function ActionsPlayground() { {selectedAction && (
-

Action Configuration

-
- -