Compare commits
1 Commits
mock-llm
...
action-pla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92272b2ec1 |
@@ -95,6 +95,11 @@ func (a *CustomAction) Run(ctx context.Context, params types.ActionParams) (type
|
|||||||
|
|
||||||
func (a *CustomAction) Definition() types.ActionDefinition {
|
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"]))
|
v, err := a.i.Eval(fmt.Sprintf("%s.Definition", a.config["name"]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("Error getting custom action definition", "error", err)
|
xlog.Error("Error getting custom action definition", "error", err)
|
||||||
|
|||||||
24
webui/app.go
24
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 {
|
func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
payload := struct {
|
payload := struct {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
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, 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() {
|
function ActionsPlayground() {
|
||||||
const { showToast } = useOutletContext();
|
const { showToast } = useOutletContext();
|
||||||
@@ -12,6 +17,10 @@ function ActionsPlayground() {
|
|||||||
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);
|
||||||
|
const [actionMetadata, setActionMetadata] = useState(null);
|
||||||
|
const [agentMetadata, setAgentMetadata] = useState(null);
|
||||||
|
const [configFields, setConfigFields] = useState([]);
|
||||||
|
const [paramFields, setParamFields] = useState([]);
|
||||||
|
|
||||||
// Update document title
|
// Update document title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -36,21 +45,106 @@ function ActionsPlayground() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchActions();
|
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
|
// Handle action selection
|
||||||
const handleActionChange = (e) => {
|
const handleActionChange = (e) => {
|
||||||
setSelectedAction(e.target.value);
|
setSelectedAction(e.target.value);
|
||||||
|
setConfigJson('{}');
|
||||||
|
setParamsJson('{}');
|
||||||
setResult(null);
|
setResult(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle JSON input changes
|
// Helper to generate onChange handlers for form fields
|
||||||
const handleConfigChange = (e) => {
|
const makeFieldChangeHandler = (fields, updateFn) => (e) => {
|
||||||
setConfigJson(e.target.value);
|
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) => {
|
// Handle form field changes
|
||||||
setParamsJson(e.target.value);
|
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
|
// Execute the selected action
|
||||||
@@ -135,34 +229,31 @@ function ActionsPlayground() {
|
|||||||
|
|
||||||
{selectedAction && (
|
{selectedAction && (
|
||||||
<div className="section-box">
|
<div className="section-box">
|
||||||
<h2>Action Configuration</h2>
|
|
||||||
|
|
||||||
<form onSubmit={handleExecuteAction}>
|
<form onSubmit={handleExecuteAction}>
|
||||||
<div className="form-group mb-6">
|
{configFields.length > 0 && (
|
||||||
<label htmlFor="config-json">Configuration (JSON):</label>
|
<>
|
||||||
<textarea
|
<h2>Configuration</h2>
|
||||||
id="config-json"
|
<FormFieldDefinition
|
||||||
value={configJson}
|
fields={configFields}
|
||||||
onChange={handleConfigChange}
|
values={JSON.parse(configJson)}
|
||||||
className="form-control"
|
onChange={makeFieldChangeHandler(configFields, handleConfigChange)}
|
||||||
rows="5"
|
idPrefix="config_"
|
||||||
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">
|
{paramFields.length > 0 && (
|
||||||
<label htmlFor="params-json">Parameters (JSON):</label>
|
<>
|
||||||
<textarea
|
<h2>Parameters</h2>
|
||||||
id="params-json"
|
<FormFieldDefinition
|
||||||
value={paramsJson}
|
fields={paramFields}
|
||||||
onChange={handleParamsChange}
|
values={JSON.parse(paramsJson)}
|
||||||
className="form-control"
|
onChange={makeFieldChangeHandler(paramFields, handleParamsChange)}
|
||||||
rows="5"
|
idPrefix="param_"
|
||||||
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">
|
<div className="flex justify-end">
|
||||||
<button
|
<button
|
||||||
@@ -194,9 +285,9 @@ function ActionsPlayground() {
|
|||||||
backgroundColor: 'rgba(30, 30, 30, 0.7)'
|
backgroundColor: 'rgba(30, 30, 30, 0.7)'
|
||||||
}}>
|
}}>
|
||||||
{typeof result === 'object' ? (
|
{typeof result === 'object' ? (
|
||||||
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
<pre className="hljs"><code>
|
||||||
{JSON.stringify(result, null, 2)}
|
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(result, null, 2), { language: 'json' }).value }}></div>
|
||||||
</pre>
|
</code></pre>
|
||||||
) : (
|
) : (
|
||||||
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||||
{result}
|
{result}
|
||||||
|
|||||||
57
webui/react-ui/src/utils/api.js
vendored
57
webui/react-ui/src/utils/api.js
vendored
@@ -24,6 +24,50 @@ const buildUrl = (endpoint) => {
|
|||||||
return `${API_CONFIG.baseUrl}${endpoint.startsWith('/') ? endpoint.substring(1) : endpoint}`;
|
return `${API_CONFIG.baseUrl}${endpoint.startsWith('/') ? endpoint.substring(1) : endpoint}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to convert ActionDefinition to FormFieldDefinition format
|
||||||
|
const convertActionDefinitionToFields = (definition) => {
|
||||||
|
if (!definition || !definition.Properties) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = [];
|
||||||
|
const required = definition.Required || [];
|
||||||
|
|
||||||
|
console.debug('Action definition:', definition);
|
||||||
|
|
||||||
|
Object.entries(definition.Properties).forEach(([name, property]) => {
|
||||||
|
const field = {
|
||||||
|
name,
|
||||||
|
label: name.charAt(0).toUpperCase() + name.slice(1),
|
||||||
|
type: 'text', // Default to text, we'll enhance this later
|
||||||
|
required: required.includes(name),
|
||||||
|
helpText: property.Description || '',
|
||||||
|
defaultValue: property.Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (property.enum && property.enum.length > 0) {
|
||||||
|
field.type = 'select';
|
||||||
|
field.options = property.enum;
|
||||||
|
} else {
|
||||||
|
switch (property.type) {
|
||||||
|
case 'integer':
|
||||||
|
field.type = 'number';
|
||||||
|
field.min = property.Minimum;
|
||||||
|
field.max = property.Maximum;
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
field.type = 'checkbox';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: Handle Object and Array types which require nested fields
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(field);
|
||||||
|
});
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
|
||||||
// Agent-related API calls
|
// Agent-related API calls
|
||||||
export const agentApi = {
|
export const agentApi = {
|
||||||
// Get list of all agents
|
// Get list of all agents
|
||||||
@@ -215,7 +259,18 @@ export const actionApi = {
|
|||||||
});
|
});
|
||||||
return handleResponse(response);
|
return handleResponse(response);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Get action definition
|
||||||
|
getActionDefinition: async (name, config = {}) => {
|
||||||
|
const response = await fetch(buildUrl(API_CONFIG.endpoints.actionDefinition(name)), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: API_CONFIG.headers,
|
||||||
|
body: JSON.stringify(config),
|
||||||
|
});
|
||||||
|
const definition = await handleResponse(response);
|
||||||
|
return convertActionDefinitionToFields(definition);
|
||||||
|
},
|
||||||
|
|
||||||
// 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)), {
|
||||||
|
|||||||
1
webui/react-ui/src/utils/config.js
vendored
1
webui/react-ui/src/utils/config.js
vendored
@@ -43,6 +43,7 @@ export const API_CONFIG = {
|
|||||||
|
|
||||||
// Action endpoints
|
// Action endpoints
|
||||||
listActions: '/api/actions',
|
listActions: '/api/actions',
|
||||||
|
actionDefinition: (name) => `/api/action/${name}/definition`,
|
||||||
executeAction: (name) => `/api/action/${name}/run`,
|
executeAction: (name) => `/api/action/${name}/run`,
|
||||||
|
|
||||||
// Status endpoint
|
// Status endpoint
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
|||||||
// Add endpoint for getting agent config metadata
|
// Add endpoint for getting agent config metadata
|
||||||
webapp.Get("/api/meta/agent/config", app.GetAgentConfigMeta())
|
webapp.Get("/api/meta/agent/config", app.GetAgentConfigMeta())
|
||||||
|
|
||||||
|
webapp.Post("/api/action/:name/definition", app.GetActionDefinition(pool))
|
||||||
webapp.Post("/api/action/:name/run", app.ExecuteAction(pool))
|
webapp.Post("/api/action/:name/run", app.ExecuteAction(pool))
|
||||||
webapp.Get("/api/actions", app.ListActions())
|
webapp.Get("/api/actions", app.ListActions())
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user