From d689bb433107fa4fe181879b96ad0c2c5aa72999 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Fri, 21 Mar 2025 23:51:55 +0100 Subject: [PATCH] feat(actions): add playground to test actions (#74) Signed-off-by: Ettore Di Giacinto --- services/actions.go | 108 ++++++------ webui/app.go | 44 +++++ webui/routes.go | 7 + webui/views/actions.html | 291 +++++++++++++++++++++++++++++++++ webui/views/partials/menu.html | 12 ++ 5 files changed, 416 insertions(+), 46 deletions(-) create mode 100644 webui/views/actions.html diff --git a/services/actions.go b/services/actions.go index 00d025d..735ce2c 100644 --- a/services/actions.go +++ b/services/actions.go @@ -3,6 +3,7 @@ package services import ( "context" "encoding/json" + "fmt" "github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/core/state" @@ -70,55 +71,70 @@ func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPo continue } - switch a.Name { - case ActionCustom: - customAction, err := action.NewCustom(config, "") - if err != nil { - xlog.Error("Error creating custom action", "error", err) - continue - } - allActions = append(allActions, customAction) - case ActionGenerateImage: - allActions = append(allActions, actions.NewGenImage(config)) - case ActionSearch: - allActions = append(allActions, actions.NewSearch(config)) - case ActionGithubIssueLabeler: - allActions = append(allActions, actions.NewGithubIssueLabeler(ctx, config)) - case ActionGithubIssueOpener: - allActions = append(allActions, actions.NewGithubIssueOpener(ctx, config)) - case ActionGithubIssueCloser: - allActions = append(allActions, actions.NewGithubIssueCloser(ctx, config)) - case ActionGithubIssueSearcher: - allActions = append(allActions, actions.NewGithubIssueSearch(ctx, config)) - case ActionGithubIssueReader: - allActions = append(allActions, actions.NewGithubIssueReader(ctx, config)) - case ActionGithubIssueCommenter: - allActions = append(allActions, actions.NewGithubIssueCommenter(ctx, config)) - case ActionGithubRepositoryGet: - allActions = append(allActions, actions.NewGithubRepositoryGetContent(ctx, config)) - case ActionGithubRepositoryCreateOrUpdate: - allActions = append(allActions, actions.NewGithubRepositoryCreateOrUpdateContent(ctx, config)) - case ActionGithubREADME: - allActions = append(allActions, actions.NewGithubRepositoryREADME(ctx, config)) - case ActionScraper: - allActions = append(allActions, actions.NewScraper(config)) - case ActionWikipedia: - allActions = append(allActions, actions.NewWikipedia(config)) - case ActionBrowse: - allActions = append(allActions, actions.NewBrowse(config)) - case ActionSendMail: - allActions = append(allActions, actions.NewSendMail(config)) - case ActionTwitterPost: - allActions = append(allActions, actions.NewPostTweet(config)) - case ActionCounter: - allActions = append(allActions, actions.NewCounter(config)) - case ActionCallAgents: - allActions = append(allActions, actions.NewCallAgent(config, pool)) - case ActionShellcommand: - allActions = append(allActions, actions.NewShell(config)) + a, err := Action(a.Name, config, pool) + if err != nil { + continue } + allActions = append(allActions, a) } return allActions } } + +func Action(name string, config map[string]string, pool *state.AgentPool) (agent.Action, error) { + var a agent.Action + var err error + + switch name { + case ActionCustom: + a, err = action.NewCustom(config, "") + case ActionGenerateImage: + a = actions.NewGenImage(config) + case ActionSearch: + a = actions.NewSearch(config) + case ActionGithubIssueLabeler: + a = actions.NewGithubIssueLabeler(context.Background(), config) + case ActionGithubIssueOpener: + a = actions.NewGithubIssueOpener(context.Background(), config) + case ActionGithubIssueCloser: + a = actions.NewGithubIssueCloser(context.Background(), config) + case ActionGithubIssueSearcher: + a = actions.NewGithubIssueSearch(context.Background(), config) + case ActionGithubIssueReader: + a = actions.NewGithubIssueReader(context.Background(), config) + case ActionGithubIssueCommenter: + a = actions.NewGithubIssueCommenter(context.Background(), config) + case ActionGithubRepositoryGet: + a = actions.NewGithubRepositoryGetContent(context.Background(), config) + case ActionGithubRepositoryCreateOrUpdate: + a = actions.NewGithubRepositoryCreateOrUpdateContent(context.Background(), config) + case ActionGithubREADME: + a = actions.NewGithubRepositoryREADME(context.Background(), config) + case ActionScraper: + a = actions.NewScraper(config) + case ActionWikipedia: + a = actions.NewWikipedia(config) + case ActionBrowse: + a = actions.NewBrowse(config) + case ActionSendMail: + a = actions.NewSendMail(config) + case ActionTwitterPost: + a = actions.NewPostTweet(config) + case ActionCounter: + a = actions.NewCounter(config) + case ActionCallAgents: + a = actions.NewCallAgent(config, pool) + case ActionShellcommand: + a = actions.NewShell(config) + default: + xlog.Error("Action not found", "name", name) + return nil, fmt.Errorf("Action not found") + } + + if err != nil { + return nil, err + } + + return a, nil +} diff --git a/webui/app.go b/webui/app.go index 08e1547..8f31641 100644 --- a/webui/app.go +++ b/webui/app.go @@ -1,6 +1,7 @@ package webui import ( + "context" "encoding/json" "fmt" "net/http" @@ -9,8 +10,10 @@ import ( "time" "github.com/mudler/LocalAgent/pkg/xlog" + "github.com/mudler/LocalAgent/services" "github.com/mudler/LocalAgent/webui/types" + "github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/sse" "github.com/mudler/LocalAgent/core/state" @@ -299,6 +302,47 @@ func (a *App) Chat(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 { + payload := struct { + Config map[string]string `json:"config"` + Params action.ActionParams `json:"params"` + }{} + + 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, "params", payload.Params) + a, err := services.Action(actionName, payload.Config, pool) + if err != nil { + xlog.Error("Error creating action", "error", err) + return errorJSONMessage(c, err.Error()) + } + + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Second) + defer cancel() + + res, err := a.Run(ctx, payload.Params) + if err != nil { + xlog.Error("Error running action", "error", err) + return errorJSONMessage(c, err.Error()) + } + + xlog.Info("Action executed", "action", actionName, "result", res) + return c.JSON(res) + } +} + +func (a *App) ListActions() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + return c.JSON(services.AvailableActions) + } +} + func (a *App) Responses(pool *state.AgentPool) func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error { var request types.RequestBody diff --git a/webui/routes.go b/webui/routes.go index c823fae..067fe63 100644 --- a/webui/routes.go +++ b/webui/routes.go @@ -130,10 +130,17 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) { }) }) + webapp.Get("/actions-playground", func(c *fiber.Ctx) error { + return c.Render("views/actions", fiber.Map{}) + }) + // New API endpoints for getting and updating agent configuration webapp.Get("/api/agent/:name/config", app.GetAgentConfig(pool)) webapp.Put("/api/agent/:name/config", app.UpdateAgentConfig(pool)) + webapp.Post("/action/:name/run", app.ExecuteAction(pool)) + webapp.Get("/actions", app.ListActions()) + webapp.Post("/settings/import", app.ImportAgent(pool)) webapp.Get("/settings/export/:name", app.ExportAgent(pool)) } diff --git a/webui/views/actions.html b/webui/views/actions.html new file mode 100644 index 0000000..f4e2c33 --- /dev/null +++ b/webui/views/actions.html @@ -0,0 +1,291 @@ + + + + + + Actions Playground + {{template "views/partials/header"}} + + + {{template "views/partials/menu"}} + + +
+ +
+ +
+
+

Actions Playground

+

Test and execute actions directly from the UI

+
+ +
+

Select an Action

+
+ + +
+
+ + + + + +
+

© 2025 LocalAgent.

+
+
+ + + + + + \ No newline at end of file diff --git a/webui/views/partials/menu.html b/webui/views/partials/menu.html index cfbf5d6..92c8c7f 100644 --- a/webui/views/partials/menu.html +++ b/webui/views/partials/menu.html @@ -33,6 +33,12 @@ + + Actions Playground + + + @@ -68,6 +74,12 @@ style="border-left: 3px solid var(--secondary);"> Agent List + + Actions Playground + + +