From 319caf8e916a2786b6cc5be4795d969a8b99a190 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Wed, 26 Mar 2025 15:56:14 +0000 Subject: [PATCH] chore(ui): Move some field definitions server side --- core/state/config.go | 208 +++++++++++++++++- pkg/config/meta.go | 42 ++++ services/actions.go | 108 ++++++++- services/actions/genimage.go | 30 +++ services/actions/githubissuecloser.go | 34 +++ services/actions/githubissuecomment.go | 34 +++ services/actions/githubissuelabeler.go | 41 ++++ services/actions/githubissueopener.go | 34 +++ services/actions/githubissuereader.go | 34 +++ services/actions/githubissuesearch.go | 34 +++ .../githubrepositorycreateupdatecontent.go | 34 +++ .../actions/githubrepositorygetcontent.go | 34 +++ services/actions/githubrepositoryreadme.go | 20 ++ services/actions/search.go | 17 ++ services/actions/sendmail.go | 43 ++++ services/actions/shell.go | 38 ++++ services/actions/twitter_post.go | 20 ++ services/connectors.go | 41 ++++ services/connectors/discord.go | 23 ++ services/connectors/githubissue.go | 37 ++++ services/connectors/githubpr.go | 37 ++++ services/connectors/irc.go | 42 ++++ services/connectors/slack.go | 35 +++ services/connectors/telegram.go | 25 +++ services/connectors/twitter.go | 24 ++ webui/app.go | 12 + webui/react-ui/src/components/ActionForm.jsx | 180 ++------------- webui/react-ui/src/components/AgentForm.jsx | 37 ++-- webui/react-ui/src/components/ConfigForm.jsx | 146 ++++++++++++ .../react-ui/src/components/ConnectorForm.jsx | 147 +++---------- .../src/components/actions/BaseAction.jsx | 44 ---- .../src/components/actions/FallbackAction.jsx | 16 -- .../actions/GenerateImageAction.jsx | 49 ----- .../actions/GithubIssueCloserAction.jsx | 58 ----- .../actions/GithubIssueCommenterAction.jsx | 58 ----- .../actions/GithubIssueLabelerAction.jsx | 67 ------ .../actions/GithubIssueOpenerAction.jsx | 58 ----- .../actions/GithubRepositoryAction.jsx | 62 ------ .../src/components/actions/SendMailAction.jsx | 67 ------ .../components/actions/TwitterPostAction.jsx | 39 ---- .../agent-form-sections/ActionsSection.jsx | 3 +- .../AdvancedSettingsSection.jsx | 53 +---- .../agent-form-sections/BasicInfoSection.jsx | 59 ++--- .../agent-form-sections/ConnectorsSection.jsx | 4 +- .../MemorySettingsSection.jsx | 68 +++--- .../ModelSettingsSection.jsx | 75 +++---- .../PromptsGoalsSection.jsx | 85 +++---- .../src/components/common/FormField.jsx | 22 ++ .../components/connectors/BaseConnector.jsx | 46 ---- .../connectors/DiscordConnector.jsx | 41 ---- .../connectors/FallbackConnector.jsx | 71 ------ .../connectors/GithubIssuesConnector.jsx | 71 ------ .../connectors/GithubPRsConnector.jsx | 71 ------ .../components/connectors/IRCConnector.jsx | 67 ------ .../components/connectors/SlackConnector.jsx | 58 ----- .../connectors/TelegramConnector.jsx | 32 --- .../connectors/TwitterConnector.jsx | 68 ------ webui/react-ui/src/pages/AgentSettings.jsx | 22 ++ webui/react-ui/src/pages/CreateAgent.jsx | 22 +- webui/react-ui/src/utils/api.js | 45 ++++ webui/react-ui/src/utils/config.js | 1 + webui/routes.go | 15 +- 62 files changed, 1534 insertions(+), 1574 deletions(-) create mode 100644 pkg/config/meta.go create mode 100644 webui/react-ui/src/components/ConfigForm.jsx delete mode 100644 webui/react-ui/src/components/actions/BaseAction.jsx delete mode 100644 webui/react-ui/src/components/actions/FallbackAction.jsx delete mode 100644 webui/react-ui/src/components/actions/GenerateImageAction.jsx delete mode 100644 webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx delete mode 100644 webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx delete mode 100644 webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx delete mode 100644 webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx delete mode 100644 webui/react-ui/src/components/actions/GithubRepositoryAction.jsx delete mode 100644 webui/react-ui/src/components/actions/SendMailAction.jsx delete mode 100644 webui/react-ui/src/components/actions/TwitterPostAction.jsx delete mode 100644 webui/react-ui/src/components/connectors/BaseConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/DiscordConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/FallbackConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/GithubIssuesConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/GithubPRsConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/IRCConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/SlackConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/TelegramConnector.jsx delete mode 100644 webui/react-ui/src/components/connectors/TwitterConnector.jsx diff --git a/core/state/config.go b/core/state/config.go index 2a9084d..2b2afb1 100644 --- a/core/state/config.go +++ b/core/state/config.go @@ -5,6 +5,7 @@ import ( "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" ) type ConnectorConfig struct { @@ -35,7 +36,7 @@ type AgentConfig struct { MCPServers []agent.MCPServer `json:"mcp_servers" form:"mcp_servers"` Description string `json:"description" form:"description"` - // This is what needs to be part of ActionsConfig + Model string `json:"model" form:"model"` MultimodalModel string `json:"multimodal_model" form:"multimodal_model"` APIURL string `json:"api_url" form:"api_url"` @@ -61,6 +62,211 @@ type AgentConfig struct { SummaryLongTermMemory bool `json:"summary_long_term_memory" form:"summary_long_term_memory"` } +type AgentConfigMeta struct { + Fields []config.Field + Connectors []config.FieldGroup + Actions []config.FieldGroup + PromptBlocks []config.Field + MCPServers []config.Field +} + +func NewAgentConfigMeta(actionsConfig []config.FieldGroup, connectorsConfig []config.FieldGroup) AgentConfigMeta { + return AgentConfigMeta{ + Fields: []config.Field{ + { + Name: "name", + Label: "Name", + Type: "text", + DefaultValue: "", + Required: true, + Tags: config.Tags{Section: "BasicInfo"}, + }, + { + Name: "description", + Label: "Description", + Type: "textarea", + DefaultValue: "", + Tags: config.Tags{Section: "BasicInfo"}, + }, + { + Name: "identity_guidance", + Label: "Identity Guidance", + Type: "textarea", + DefaultValue: "", + Tags: config.Tags{Section: "BasicInfo"}, + }, + { + Name: "random_identity", + Label: "Random Identity", + Type: "checkbox", + DefaultValue: false, + Tags: config.Tags{Section: "BasicInfo"}, + }, + { + Name: "hud", + Label: "HUD", + Type: "checkbox", + DefaultValue: false, + Tags: config.Tags{Section: "BasicInfo"}, + }, + { + Name: "model", + Label: "Model", + Type: "text", + DefaultValue: "", + Tags: config.Tags{Section: "ModelSettings"}, + }, + { + Name: "multimodal_model", + Label: "Multimodal Model", + Type: "text", + DefaultValue: "", + Tags: config.Tags{Section: "ModelSettings"}, + }, + { + Name: "api_url", + Label: "API URL", + Type: "text", + DefaultValue: "", + Tags: config.Tags{Section: "ModelSettings"}, + }, + { + Name: "api_key", + Label: "API Key", + Type: "password", + DefaultValue: "", + Tags: config.Tags{Section: "ModelSettings"}, + }, + { + Name: "local_rag_url", + Label: "Local RAG URL", + Type: "text", + DefaultValue: "", + Tags: config.Tags{Section: "ModelSettings"}, + }, + { + Name: "local_rag_api_key", + Label: "Local RAG API Key", + Type: "password", + DefaultValue: "", + Tags: config.Tags{Section: "ModelSettings"}, + }, + { + Name: "enable_kb", + Label: "Enable Knowledge Base", + Type: "checkbox", + DefaultValue: false, + Tags: config.Tags{Section: "MemorySettings"}, + }, + { + Name: "kb_results", + Label: "Knowledge Base Results", + Type: "number", + DefaultValue: 5, + Min: 1, + Step: 1, + Tags: config.Tags{Section: "MemorySettings"}, + }, + { + Name: "long_term_memory", + Label: "Long Term Memory", + Type: "checkbox", + DefaultValue: false, + Tags: config.Tags{Section: "MemorySettings"}, + }, + { + Name: "summary_long_term_memory", + Label: "Summary Long Term Memory", + Type: "checkbox", + DefaultValue: false, + Tags: config.Tags{Section: "MemorySettings"}, + }, + { + Name: "system_prompt", + Label: "System Prompt", + Type: "textarea", + DefaultValue: "", + HelpText: "Instructions that define the agent's behavior and capabilities", + Tags: config.Tags{Section: "PromptsGoals"}, + }, + { + Name: "permanent_goal", + Label: "Permanent Goal", + Type: "textarea", + DefaultValue: "", + HelpText: "Long-term objective for the agent to pursue", + Tags: config.Tags{Section: "PromptsGoals"}, + }, + { + Name: "standalone_job", + Label: "Standalone Job", + Type: "checkbox", + DefaultValue: false, + HelpText: "Run as a standalone job without user interaction", + Tags: config.Tags{Section: "AdvancedSettings"}, + }, + { + Name: "initiate_conversations", + Label: "Initiate Conversations", + Type: "checkbox", + DefaultValue: false, + HelpText: "Allow agent to start conversations on its own", + Tags: config.Tags{Section: "AdvancedSettings"}, + }, + { + Name: "enable_planning", + Label: "Enable Planning", + Type: "checkbox", + DefaultValue: false, + HelpText: "Enable agent to create and execute plans", + Tags: config.Tags{Section: "AdvancedSettings"}, + }, + { + Name: "can_stop_itself", + Label: "Can Stop Itself", + Type: "checkbox", + DefaultValue: false, + HelpText: "Allow agent to terminate its own execution", + Tags: config.Tags{Section: "AdvancedSettings"}, + }, + { + Name: "periodic_runs", + Label: "Periodic Runs", + Type: "text", + DefaultValue: "", + Placeholder: "10m", + HelpText: "Duration for scheduling periodic agent runs", + Tags: config.Tags{Section: "AdvancedSettings"}, + }, + { + Name: "enable_reasoning", + Label: "Enable Reasoning", + Type: "checkbox", + DefaultValue: false, + HelpText: "Enable agent to explain its reasoning process", + Tags: config.Tags{Section: "AdvancedSettings"}, + }, + }, + MCPServers: []config.Field{ + { + Name: "url", + Label: "URL", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "token", + Label: "API Key", + Type: config.FieldTypeText, + Required: true, + }, + }, + PromptBlocks: []config.Field{}, + Connectors: connectorsConfig, + Actions: actionsConfig, + } +} + type Connector interface { AgentResultCallback() func(state types.ActionState) AgentReasoningCallback() func(state types.ActionCurrentState) bool diff --git a/pkg/config/meta.go b/pkg/config/meta.go new file mode 100644 index 0000000..a427222 --- /dev/null +++ b/pkg/config/meta.go @@ -0,0 +1,42 @@ +package config + +type FieldType string + +const ( + FieldTypeNumber FieldType = "number" + FieldTypeText FieldType = "text" + FieldTypeTextarea FieldType = "textarea" + FieldTypeCheckbox FieldType = "checkbox" + FieldTypeSelect FieldType = "select" +) + +type Tags struct { + Section string `json:"section,omitempty"` +} + +type FieldOption struct { + Value string `json:"value"` + Label string `json:"label"` +} + +type Field struct { + Name string `json:"name"` + Type FieldType `json:"type"` + Label string `json:"label"` + DefaultValue any `json:"defaultValue"` + Placeholder string `json:"placeholder,omitempty"` + HelpText string `json:"helpText,omitempty"` + Required bool `json:"required,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Options []FieldOption `json:"options,omitempty"` + Min float32 `json:"min,omitempty"` + Max float32 `json:"max,omitempty"` + Step float32 `json:"step,omitempty"` + Tags Tags `json:"tags,omitempty"` +} + +type FieldGroup struct { + Name string `json:"name"` + Label string `json:"label"` + Fields []Field `json:"fields"` +} diff --git a/services/actions.go b/services/actions.go index 173cff5..29a9690 100644 --- a/services/actions.go +++ b/services/actions.go @@ -8,6 +8,7 @@ import ( "github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/core/state" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/services/actions" @@ -30,7 +31,7 @@ const ( ActionWikipedia = "wikipedia" ActionBrowse = "browse" ActionTwitterPost = "twitter-post" - ActionSendMail = "send_mail" + ActionSendMail = "send-mail" ActionGenerateImage = "generate_image" ActionCounter = "counter" ActionCallAgents = "call_agents" @@ -138,3 +139,108 @@ func Action(name string, config map[string]string, pool *state.AgentPool) (types return a, nil } + +func ActionsConfigMeta() []config.FieldGroup { + return []config.FieldGroup{ + { + Name: "search", + Label: "Search", + Fields: actions.SearchConfigMeta(), + }, + { + Name: "generate_image", + Label: "Generate Image", + Fields: actions.GenImageConfigMeta(), + }, + { + Name: "github-issue-labeler", + Label: "GitHub Issue Labeler", + Fields: actions.GithubIssueLabelerConfigMeta(), + }, + { + Name: "github-issue-opener", + Label: "GitHub Issue Opener", + Fields: actions.GithubIssueOpenerConfigMeta(), + }, + { + Name: "github-issue-closer", + Label: "GitHub Issue Closer", + Fields: actions.GithubIssueCloserConfigMeta(), + }, + { + Name: "github-issue-commenter", + Label: "GitHub Issue Commenter", + Fields: actions.GithubIssueCommenterConfigMeta(), + }, + { + Name: "github-issue-reader", + Label: "GitHub Issue Reader", + Fields: actions.GithubIssueReaderConfigMeta(), + }, + { + Name: "github-issue-searcher", + Label: "GitHub Issue Search", + Fields: actions.GithubIssueSearchConfigMeta(), + }, + { + Name: "github-repository-get-content", + Label: "GitHub Repository Get Content", + Fields: actions.GithubRepositoryGetContentConfigMeta(), + }, + { + Name: "github-repository-create-or-update-content", + Label: "GitHub Repository Create/Update Content", + Fields: actions.GithubRepositoryCreateOrUpdateContentConfigMeta(), + }, + { + Name: "github-readme", + Label: "GitHub Repository README", + Fields: actions.GithubRepositoryREADMEConfigMeta(), + }, + { + Name: "twitter-post", + Label: "Twitter Post", + Fields: actions.TwitterPostConfigMeta(), + }, + { + Name: "send-mail", + Label: "Send Mail", + Fields: actions.SendMailConfigMeta(), + }, + { + Name: "shell-command", + Label: "Shell Command", + Fields: actions.ShellConfigMeta(), + }, + { + Name: "custom", + Label: "Custom", + Fields: []config.Field{}, + }, + { + Name: "scraper", + Label: "Scraper", + Fields: []config.Field{}, + }, + { + Name: "wikipedia", + Label: "Wikipedia", + Fields: []config.Field{}, + }, + { + Name: "browse", + Label: "Browse", + Fields: []config.Field{}, + }, + { + Name: "counter", + Label: "Counter", + Fields: []config.Field{}, + }, + { + Name: "call_agents", + Label: "Call Agents", + Fields: []config.Field{}, + }, + } +} diff --git a/services/actions/genimage.go b/services/actions/genimage.go index bd4c609..fe296f3 100644 --- a/services/actions/genimage.go +++ b/services/actions/genimage.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -96,3 +97,32 @@ func (a *GenImageAction) Definition() types.ActionDefinition { func (a *GenImageAction) Plannable() bool { return true } + +// GenImageConfigMeta returns the metadata for GenImage action configuration fields +func GenImageConfigMeta() []config.Field { + return []config.Field{ + { + Name: "apiKey", + Label: "API Key", + Type: config.FieldTypeText, + Required: true, + HelpText: "OpenAI API key for image generation", + }, + { + Name: "apiURL", + Label: "API URL", + Type: config.FieldTypeText, + Required: true, + DefaultValue: "https://api.openai.com/v1", + HelpText: "OpenAI API URL", + }, + { + Name: "model", + Label: "Model", + Type: config.FieldTypeText, + Required: true, + DefaultValue: "dall-e-3", + HelpText: "Image generation model to use (e.g., dall-e-3)", + }, + } +} diff --git a/services/actions/githubissuecloser.go b/services/actions/githubissuecloser.go index ba94a71..c3e1935 100644 --- a/services/actions/githubissuecloser.go +++ b/services/actions/githubissuecloser.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -118,3 +119,36 @@ func (g *GithubIssuesCloser) Definition() types.ActionDefinition { func (a *GithubIssuesCloser) Plannable() bool { return true } + +// GithubIssueCloserConfigMeta returns the metadata for GitHub Issue Closer action configuration fields +func GithubIssueCloserConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubissuecomment.go b/services/actions/githubissuecomment.go index 42497e8..7296c73 100644 --- a/services/actions/githubissuecomment.go +++ b/services/actions/githubissuecomment.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -105,3 +106,36 @@ func (g *GithubIssuesCommenter) Definition() types.ActionDefinition { func (a *GithubIssuesCommenter) Plannable() bool { return true } + +// GithubIssueCommenterConfigMeta returns the metadata for GitHub Issue Commenter action configuration fields +func GithubIssueCommenterConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubissuelabeler.go b/services/actions/githubissuelabeler.go index 055ae51..cf73914 100644 --- a/services/actions/githubissuelabeler.go +++ b/services/actions/githubissuelabeler.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -120,3 +121,43 @@ func (g *GithubIssuesLabeler) Definition() types.ActionDefinition { func (a *GithubIssuesLabeler) Plannable() bool { return true } + +// GithubIssueLabelerConfigMeta returns the metadata for GitHub Issue Labeler action configuration fields +func GithubIssueLabelerConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "availableLabels", + Label: "Available Labels", + Type: config.FieldTypeText, + HelpText: "Comma-separated list of available labels", + DefaultValue: "bug,enhancement", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubissueopener.go b/services/actions/githubissueopener.go index bbff91d..dbbc061 100644 --- a/services/actions/githubissueopener.go +++ b/services/actions/githubissueopener.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -111,3 +112,36 @@ func (g *GithubIssuesOpener) Definition() types.ActionDefinition { func (a *GithubIssuesOpener) Plannable() bool { return true } + +// GithubIssueOpenerConfigMeta returns the metadata for GitHub Issue Opener action configuration fields +func GithubIssueOpenerConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubissuereader.go b/services/actions/githubissuereader.go index 2f6168c..df5cfac 100644 --- a/services/actions/githubissuereader.go +++ b/services/actions/githubissuereader.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -99,3 +100,36 @@ func (g *GithubIssuesReader) Definition() types.ActionDefinition { func (a *GithubIssuesReader) Plannable() bool { return true } + +// GithubIssueReaderConfigMeta returns the metadata for GitHub Issue Reader action configuration fields +func GithubIssueReaderConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubissuesearch.go b/services/actions/githubissuesearch.go index daa5358..bcf7bc4 100644 --- a/services/actions/githubissuesearch.go +++ b/services/actions/githubissuesearch.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -108,3 +109,36 @@ func (g *GithubIssueSearch) Definition() types.ActionDefinition { func (a *GithubIssueSearch) Plannable() bool { return true } + +// GithubIssueSearchConfigMeta returns the metadata for GitHub Issue Search action configuration fields +func GithubIssueSearchConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubrepositorycreateupdatecontent.go b/services/actions/githubrepositorycreateupdatecontent.go index 2a8398d..67befa3 100644 --- a/services/actions/githubrepositorycreateupdatecontent.go +++ b/services/actions/githubrepositorycreateupdatecontent.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -144,3 +145,36 @@ func (g *GithubRepositoryCreateOrUpdateContent) Definition() types.ActionDefinit func (a *GithubRepositoryCreateOrUpdateContent) Plannable() bool { return true } + +// GithubRepositoryCreateOrUpdateContentConfigMeta returns the metadata for GitHub Repository Create/Update Content action configuration fields +func GithubRepositoryCreateOrUpdateContentConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubrepositorygetcontent.go b/services/actions/githubrepositorygetcontent.go index 61c39d5..dd6e7c0 100644 --- a/services/actions/githubrepositorygetcontent.go +++ b/services/actions/githubrepositorygetcontent.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -109,3 +110,36 @@ func (g *GithubRepositoryGetContent) Definition() types.ActionDefinition { func (a *GithubRepositoryGetContent) Plannable() bool { return true } + +// GithubRepositoryGetContentConfigMeta returns the metadata for GitHub Repository Get Content action configuration fields +func GithubRepositoryGetContentConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository name", + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub repository owner", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/githubrepositoryreadme.go b/services/actions/githubrepositoryreadme.go index 03f0be8..5d0f349 100644 --- a/services/actions/githubrepositoryreadme.go +++ b/services/actions/githubrepositoryreadme.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -75,3 +76,22 @@ func (g *GithubRepositoryREADME) Definition() types.ActionDefinition { func (a *GithubRepositoryREADME) Plannable() bool { return true } + +// GithubRepositoryREADMEConfigMeta returns the metadata for GitHub Repository README action configuration fields +func GithubRepositoryREADMEConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "GitHub API token with repository access", + }, + { + Name: "customActionName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + } +} diff --git a/services/actions/search.go b/services/actions/search.go index 27130f6..ed09ac1 100644 --- a/services/actions/search.go +++ b/services/actions/search.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" "github.com/tmc/langchaingo/tools/duckduckgo" "mvdan.cc/xurls/v2" @@ -89,3 +90,19 @@ func (a *SearchAction) Definition() types.ActionDefinition { func (a *SearchAction) Plannable() bool { return true } + +// SearchConfigMeta returns the metadata for Search action configuration fields +func SearchConfigMeta() []config.Field { + return []config.Field{ + { + Name: "results", + Label: "Number of Results", + Type: config.FieldTypeNumber, + DefaultValue: 1, + Min: 1, + Max: 10, + Step: 1, + HelpText: "Number of search results to return", + }, + } +} diff --git a/services/actions/sendmail.go b/services/actions/sendmail.go index bc46a13..c6a4785 100644 --- a/services/actions/sendmail.go +++ b/services/actions/sendmail.go @@ -6,6 +6,7 @@ import ( "net/smtp" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -80,3 +81,45 @@ func (a *SendMailAction) Definition() types.ActionDefinition { func (a *SendMailAction) Plannable() bool { return true } + +// SendMailConfigMeta returns the metadata for SendMail action configuration fields +func SendMailConfigMeta() []config.Field { + return []config.Field{ + { + Name: "smtpHost", + Label: "SMTP Host", + Type: config.FieldTypeText, + Required: true, + HelpText: "SMTP server host (e.g., smtp.gmail.com)", + }, + { + Name: "smtpPort", + Label: "SMTP Port", + Type: config.FieldTypeText, + Required: true, + DefaultValue: "587", + HelpText: "SMTP server port (e.g., 587)", + }, + { + Name: "username", + Label: "SMTP Username", + Type: config.FieldTypeText, + Required: true, + HelpText: "SMTP username/email address", + }, + { + Name: "password", + Label: "SMTP Password", + Type: config.FieldTypeText, + Required: true, + HelpText: "SMTP password or app password", + }, + { + Name: "email", + Label: "From Email", + Type: config.FieldTypeText, + Required: true, + HelpText: "Sender email address", + }, + } +} diff --git a/services/actions/shell.go b/services/actions/shell.go index 9f95c27..a21636a 100644 --- a/services/actions/shell.go +++ b/services/actions/shell.go @@ -6,6 +6,7 @@ import ( "log" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/sashabaranov/go-openai/jsonschema" "golang.org/x/crypto/ssh" ) @@ -96,6 +97,43 @@ func (a *ShellAction) Definition() types.ActionDefinition { } } +// ShellConfigMeta returns the metadata for Shell action configuration fields +func ShellConfigMeta() []config.Field { + return []config.Field{ + { + Name: "privateKey", + Label: "Private Key", + Type: config.FieldTypeTextarea, + Required: true, + HelpText: "SSH private key for connecting to remote servers", + }, + { + Name: "user", + Label: "Default User", + Type: config.FieldTypeText, + HelpText: "Default SSH user for connecting to remote servers", + }, + { + Name: "host", + Label: "Default Host", + Type: config.FieldTypeText, + HelpText: "Default host for SSH connections (e.g., hostname:port)", + }, + { + Name: "customName", + Label: "Custom Action Name", + Type: config.FieldTypeText, + HelpText: "Custom name for this action", + }, + { + Name: "customDescription", + Label: "Custom Description", + Type: config.FieldTypeTextarea, + HelpText: "Custom description for this action", + }, + } +} + func sshCommand(privateKey, command, user, host string) (string, error) { // Create signer from private key string key, err := ssh.ParsePrivateKey([]byte(privateKey)) diff --git a/services/actions/twitter_post.go b/services/actions/twitter_post.go index d0bd417..1921ad0 100644 --- a/services/actions/twitter_post.go +++ b/services/actions/twitter_post.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/services/connectors/twitter" "github.com/sashabaranov/go-openai/jsonschema" ) @@ -62,3 +63,22 @@ func (a *PostTweetAction) Definition() types.ActionDefinition { func (a *PostTweetAction) Plannable() bool { return true } + +// TwitterPostConfigMeta returns the metadata for Twitter Post action configuration fields +func TwitterPostConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "Twitter API Token", + Type: config.FieldTypeText, + Required: true, + HelpText: "Twitter API token for posting tweets", + }, + { + Name: "noCharacterLimit", + Label: "No Character Limit", + Type: config.FieldTypeCheckbox, + HelpText: "If checked, tweets longer than the character limit will be split into multiple tweets", + }, + } +} diff --git a/services/connectors.go b/services/connectors.go index 50317b0..7ad9ed5 100644 --- a/services/connectors.go +++ b/services/connectors.go @@ -3,6 +3,7 @@ package services import ( "encoding/json" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/services/connectors" @@ -69,3 +70,43 @@ func Connectors(a *state.AgentConfig) []state.Connector { } return conns } + +func ConnectorsConfigMeta() []config.FieldGroup { + return []config.FieldGroup{ + { + Name: "discord", + Label: "Discord", + Fields: connectors.DiscordConfigMeta(), + }, + { + Name: "slack", + Label: "Slack", + Fields: connectors.SlackConfigMeta(), + }, + { + Name: "telegram", + Label: "Telegram", + Fields: connectors.TelegramConfigMeta(), + }, + { + Name: "github-issues", + Label: "GitHub Issues", + Fields: connectors.GithubIssueConfigMeta(), + }, + { + Name: "github-prs", + Label: "GitHub PRs", + Fields: connectors.GithubPRConfigMeta(), + }, + { + Name: "irc", + Label: "IRC", + Fields: connectors.IRCConfigMeta(), + }, + { + Name: "twitter", + Label: "Twitter", + Fields: connectors.TwitterConfigMeta(), + }, + } +} diff --git a/services/connectors/discord.go b/services/connectors/discord.go index 15b5e08..1d95ada 100644 --- a/services/connectors/discord.go +++ b/services/connectors/discord.go @@ -6,6 +6,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/sashabaranov/go-openai" ) @@ -34,6 +35,28 @@ func NewDiscord(config map[string]string) *Discord { } } +func DiscordConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "Discord Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "defaultChannel", + Label: "Default Channel", + Type: config.FieldTypeText, + }, + { + Name: "lastMessageDuration", + Label: "Last Message Duration", + Type: config.FieldTypeText, + DefaultValue: "5m", + }, + } +} + func (d *Discord) AgentResultCallback() func(state types.ActionState) { return func(state types.ActionState) { // Send the result to the bot diff --git a/services/connectors/githubissue.go b/services/connectors/githubissue.go index 67c1b75..0b87854 100644 --- a/services/connectors/githubissue.go +++ b/services/connectors/githubissue.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/sashabaranov/go-openai" @@ -195,3 +196,39 @@ func (g *GithubIssues) issuesService() { } } } + +// GithubIssueConfigMeta returns the metadata for GitHub Issues connector configuration fields +func GithubIssueConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "replyIfNoReplies", + Label: "Reply If No Replies", + Type: config.FieldTypeCheckbox, + }, + { + Name: "pollInterval", + Label: "Poll Interval", + Type: config.FieldTypeText, + DefaultValue: "10m", + HelpText: "How often to check for new issues (e.g., 10m, 1h)", + }, + } +} diff --git a/services/connectors/githubpr.go b/services/connectors/githubpr.go index 3041f73..70ef408 100644 --- a/services/connectors/githubpr.go +++ b/services/connectors/githubpr.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/sashabaranov/go-openai" @@ -195,3 +196,39 @@ func (g *GithubPRs) prService() { } } } + +// GithubPRConfigMeta returns the metadata for GitHub PR connector configuration fields +func GithubPRConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "GitHub Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "repository", + Label: "Repository", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "owner", + Label: "Owner", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "replyIfNoReplies", + Label: "Reply If No Replies", + Type: config.FieldTypeCheckbox, + }, + { + Name: "pollInterval", + Label: "Poll Interval", + Type: config.FieldTypeText, + DefaultValue: "10m", + HelpText: "How often to check for new PRs (e.g., 10m, 1h)", + }, + } +} diff --git a/services/connectors/irc.go b/services/connectors/irc.go index 58bc54e..6221d39 100644 --- a/services/connectors/irc.go +++ b/services/connectors/irc.go @@ -7,6 +7,7 @@ import ( "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/services/actions" "github.com/sashabaranov/go-openai" @@ -207,3 +208,44 @@ func (i *IRC) Start(a *agent.Agent) { // Start the IRC client in a goroutine go i.conn.Loop() } + +// IRCConfigMeta returns the metadata for IRC connector configuration fields +func IRCConfigMeta() []config.Field { + return []config.Field{ + { + Name: "server", + Label: "IRC Server", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "port", + Label: "Port", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "nickname", + Label: "Nickname", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "channel", + Label: "Channel", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "alwaysReply", + Label: "Always Reply", + Type: config.FieldTypeCheckbox, + }, + { + Name: "lastMessageDuration", + Label: "Last Message Duration", + Type: config.FieldTypeText, + DefaultValue: "5m", + }, + } +} diff --git a/services/connectors/slack.go b/services/connectors/slack.go index 557d0ee..36814ed 100644 --- a/services/connectors/slack.go +++ b/services/connectors/slack.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xstrings" "github.com/mudler/LocalAgent/services/actions" @@ -784,3 +785,37 @@ func (t *Slack) Start(a *agent.Agent) { client.RunContext(a.Context()) } + +// SlackConfigMeta returns the metadata for Slack connector configuration fields +func SlackConfigMeta() []config.Field { + return []config.Field{ + { + Name: "appToken", + Label: "App Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "botToken", + Label: "Bot Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "channelID", + Label: "Channel ID", + Type: config.FieldTypeText, + }, + { + Name: "alwaysReply", + Label: "Always Reply", + Type: config.FieldTypeCheckbox, + }, + { + Name: "lastMessageDuration", + Label: "Last Message Duration", + Type: config.FieldTypeText, + DefaultValue: "5m", + }, + } +} diff --git a/services/connectors/telegram.go b/services/connectors/telegram.go index 65e378b..2ff9806 100644 --- a/services/connectors/telegram.go +++ b/services/connectors/telegram.go @@ -14,6 +14,7 @@ import ( "github.com/go-telegram/bot/models" "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xstrings" "github.com/mudler/LocalAgent/services/actions" @@ -203,3 +204,27 @@ func NewTelegramConnector(config map[string]string) (*Telegram, error) { conversationTracker: NewConversationTracker[int64](duration), }, nil } + +// TelegramConfigMeta returns the metadata for Telegram connector configuration fields +func TelegramConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "Telegram Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "admins", + Label: "Admins", + Type: config.FieldTypeText, + HelpText: "Comma-separated list of Telegram usernames that are allowed to interact with the bot", + }, + { + Name: "lastMessageDuration", + Label: "Last Message Duration", + Type: config.FieldTypeText, + DefaultValue: "5m", + }, + } +} diff --git a/services/connectors/twitter.go b/services/connectors/twitter.go index adab389..6f3c595 100644 --- a/services/connectors/twitter.go +++ b/services/connectors/twitter.go @@ -8,6 +8,7 @@ import ( "github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/types" + "github.com/mudler/LocalAgent/pkg/config" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/services/connectors/twitter" "github.com/sashabaranov/go-openai" @@ -134,3 +135,26 @@ func (t *Twitter) run(a *agent.Agent) error { return nil } + +// TwitterConfigMeta returns the metadata for Twitter connector configuration fields +func TwitterConfigMeta() []config.Field { + return []config.Field{ + { + Name: "token", + Label: "Twitter API Token", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "botUsername", + Label: "Bot Username", + Type: config.FieldTypeText, + Required: true, + }, + { + Name: "noCharacterLimit", + Label: "No Character Limit", + Type: config.FieldTypeCheckbox, + }, + } +} diff --git a/webui/app.go b/webui/app.go index 53a593d..a4d4a25 100644 --- a/webui/app.go +++ b/webui/app.go @@ -477,3 +477,15 @@ func (a *App) CreateGroup(pool *state.AgentPool) func(c *fiber.Ctx) error { return statusJSONMessage(c, "ok") } } + +// GetAgentConfigMeta returns the metadata for agent configuration fields +func (a *App) GetAgentConfigMeta() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + // Create a new instance of AgentConfigMeta + configMeta := state.NewAgentConfigMeta( + services.ActionsConfigMeta(), + services.ConnectorsConfigMeta(), + ) + return c.JSON(configMeta) + } +} diff --git a/webui/react-ui/src/components/ActionForm.jsx b/webui/react-ui/src/components/ActionForm.jsx index 721d12f..1976bb0 100644 --- a/webui/react-ui/src/components/ActionForm.jsx +++ b/webui/react-ui/src/components/ActionForm.jsx @@ -1,169 +1,31 @@ import React from 'react'; -import FallbackAction from './actions/FallbackAction'; -import GithubIssueLabelerAction from './actions/GithubIssueLabelerAction'; -import GithubIssueOpenerAction from './actions/GithubIssueOpenerAction'; -import GithubIssueCloserAction from './actions/GithubIssueCloserAction'; -import GithubIssueCommenterAction from './actions/GithubIssueCommenterAction'; -import GithubRepositoryAction from './actions/GithubRepositoryAction'; -import TwitterPostAction from './actions/TwitterPostAction'; -import SendMailAction from './actions/SendMailAction'; -import GenerateImageAction from './actions/GenerateImageAction'; +import ConfigForm from './ConfigForm'; /** * ActionForm component for configuring an action + * Renders action configuration forms based on field group metadata */ -const ActionForm = ({ actions = [], onChange, onRemove, onAdd }) => { - // Available action types - const actionTypes = [ - { value: '', label: 'Select an action type' }, - { value: 'github-issue-labeler', label: 'GitHub Issue Labeler' }, - { value: 'github-issue-opener', label: 'GitHub Issue Opener' }, - { value: 'github-issue-closer', label: 'GitHub Issue Closer' }, - { value: 'github-issue-commenter', label: 'GitHub Issue Commenter' }, - { value: 'github-repository-get-content', label: 'GitHub Repository Get Content' }, - { value: 'github-repository-create-or-update-content', label: 'GitHub Repository Create/Update Content' }, - { value: 'github-readme', label: 'GitHub Readme' }, - { value: 'twitter-post', label: 'Twitter Post' }, - { value: 'send-mail', label: 'Send Email' }, - { value: 'search', label: 'Search' }, - { value: 'github-issue-searcher', label: 'GitHub Issue Searcher' }, - { value: 'github-issue-reader', label: 'GitHub Issue Reader' }, - { value: 'scraper', label: 'Web Scraper' }, - { value: 'wikipedia', label: 'Wikipedia' }, - { value: 'browse', label: 'Browse' }, - { value: 'generate_image', label: 'Generate Image' }, - { value: 'counter', label: 'Counter' }, - { value: 'call_agents', label: 'Call Agents' }, - { value: 'shell-command', label: 'Shell Command' }, - { value: 'custom', label: 'Custom' } - ]; - - // Parse the config JSON string to an object - const parseConfig = (action) => { - if (!action || !action.config) return {}; - - try { - return JSON.parse(action.config || '{}'); - } catch (error) { - console.error('Error parsing action config:', error); - return {}; - } +const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = [] }) => { + // Debug logging + console.log('ActionForm:', { actions, fieldGroups }); + + // Handle action change + const handleActionChange = (index, updatedAction) => { + console.log('Action change:', { index, updatedAction }); + onChange(index, updatedAction); }; - - // Get a value from the config object - const getConfigValue = (action, key, defaultValue = '') => { - const config = parseConfig(action); - return config[key] !== undefined ? config[key] : defaultValue; - }; - - // Update a value in the config object - const onActionConfigChange = (index, key, value) => { - const action = actions[index]; - const config = parseConfig(action); - config[key] = value; - - onChange(index, { - ...action, - config: JSON.stringify(config) - }); - }; - - // Handle action type change - const handleActionTypeChange = (index, value) => { - const action = actions[index]; - onChange(index, { - ...action, - name: value - }); - }; - - // Render the appropriate action component based on the action type - const renderActionComponent = (action, index) => { - // Common props for all action components - const actionProps = { - index, - onActionConfigChange: (key, value) => onActionConfigChange(index, key, value), - getConfigValue: (key, defaultValue) => getConfigValue(action, key, defaultValue) - }; - - switch (action.name) { - case 'github-issue-labeler': - return ; - case 'github-issue-opener': - return ; - case 'github-issue-closer': - return ; - case 'github-issue-commenter': - return ; - case 'github-repository-get-content': - case 'github-repository-create-or-update-content': - case 'github-readme': - return ; - case 'twitter-post': - return ; - case 'send-mail': - return ; - case 'generate_image': - return ; - default: - return ; - } - }; - - // Render a specific action form - const renderActionForm = (action, index) => { - // Ensure action is an object with expected properties - const safeAction = action || {}; - - return ( -
-
-

Action #{index + 1}

- -
- -
- - -
- - {/* Render specific action template based on type */} - {renderActionComponent(safeAction, index)} -
- ); - }; - + return ( -
- {actions && actions.map((action, index) => ( - renderActionForm(action, index) - ))} - - -
+ ); }; diff --git a/webui/react-ui/src/components/AgentForm.jsx b/webui/react-ui/src/components/AgentForm.jsx index 5d87146..1c601b5 100644 --- a/webui/react-ui/src/components/AgentForm.jsx +++ b/webui/react-ui/src/components/AgentForm.jsx @@ -19,7 +19,8 @@ const AgentForm = ({ loading = false, submitButtonText, isGroupForm = false, - noFormWrapper = false + noFormWrapper = false, + metadata = null }) => { const navigate = useNavigate(); const { showToast } = useOutletContext(); @@ -239,70 +240,70 @@ const AgentForm = ({
{/* Form Sections */}
- +
- +
- +
- +
- +
- +
- +
- +
) : ( -
+ {/* Form Sections */}
- +
- +
- +
- +
- +
- +
- +
- +
{/* Form Controls */} diff --git a/webui/react-ui/src/components/ConfigForm.jsx b/webui/react-ui/src/components/ConfigForm.jsx new file mode 100644 index 0000000..934cbdf --- /dev/null +++ b/webui/react-ui/src/components/ConfigForm.jsx @@ -0,0 +1,146 @@ +import React, { useState } from 'react'; +import FormFieldDefinition from './common/FormFieldDefinition'; + +/** + * ConfigForm - A generic component for handling configuration forms based on FieldGroups + * + * @param {Object} props Component props + * @param {Array} props.items - Array of configuration items (actions, connectors, etc.) + * @param {Array} props.fieldGroups - Array of FieldGroup objects that define the available types and their fields + * @param {Function} props.onChange - Callback when an item changes + * @param {Function} props.onRemove - Callback when an item is removed + * @param {Function} props.onAdd - Callback when a new item is added + * @param {String} props.itemType - Type of items being configured ('action', 'connector', etc.) + * @param {String} props.typeField - The field name that determines the item's type (e.g., 'name' for actions, 'type' for connectors) + * @param {String} props.addButtonText - Text for the add button + */ +const ConfigForm = ({ + items = [], + fieldGroups = [], + onChange, + onRemove, + onAdd, + itemType = 'item', + typeField = 'type', + addButtonText = 'Add Item' +}) => { + // Debug logging + console.log(`ConfigForm for ${itemType}:`, { items, fieldGroups }); + + // Generate options from fieldGroups + const typeOptions = [ + { value: '', label: `Select a ${itemType} type` }, + ...fieldGroups.map(group => ({ + value: group.name, + label: group.label + })) + ]; + + console.log(`${itemType} type options:`, typeOptions); + + // Parse the config JSON string to an object + const parseConfig = (item) => { + if (!item || !item.config) return {}; + + try { + return typeof item.config === 'string' + ? JSON.parse(item.config || '{}') + : item.config; + } catch (error) { + console.error(`Error parsing ${itemType} config:`, error); + return {}; + } + }; + + // Handle item type change + const handleTypeChange = (index, value) => { + const item = items[index]; + onChange(index, { + ...item, + [typeField]: value + }); + }; + + // Handle config field change + const handleConfigChange = (index, key, value) => { + const item = items[index]; + const config = parseConfig(item); + config[key] = value; + + onChange(index, { + ...item, + config: JSON.stringify(config) + }); + }; + + // Render a specific item form + const renderItemForm = (item, index) => { + // Ensure item is an object with expected properties + const safeItem = item || {}; + const itemTypeName = safeItem[typeField] || ''; + + // Find the field group that matches this item's type + const fieldGroup = fieldGroups.find(group => group.name === itemTypeName); + + console.log(`Item ${index} type: ${itemTypeName}, Found field group:`, fieldGroup); + + return ( +
+
+

{itemType.charAt(0).toUpperCase() + itemType.slice(1)} #{index + 1}

+ +
+ +
+ + +
+ + {/* Render fields based on the selected type */} + {fieldGroup && fieldGroup.fields && ( + handleConfigChange(index, key, value)} + idPrefix={`${itemType}-${index}-`} + /> + )} +
+ ); + }; + + return ( +
+ {items && items.map((item, index) => ( + renderItemForm(item, index) + ))} + + +
+ ); +}; + +export default ConfigForm; diff --git a/webui/react-ui/src/components/ConnectorForm.jsx b/webui/react-ui/src/components/ConnectorForm.jsx index 3ed28e4..107010c 100644 --- a/webui/react-ui/src/components/ConnectorForm.jsx +++ b/webui/react-ui/src/components/ConnectorForm.jsx @@ -1,137 +1,48 @@ -import { useState } from 'react'; - -// Import connector components -import TelegramConnector from './connectors/TelegramConnector'; -import SlackConnector from './connectors/SlackConnector'; -import DiscordConnector from './connectors/DiscordConnector'; -import GithubIssuesConnector from './connectors/GithubIssuesConnector'; -import GithubPRsConnector from './connectors/GithubPRsConnector'; -import IRCConnector from './connectors/IRCConnector'; -import TwitterConnector from './connectors/TwitterConnector'; -import FallbackConnector from './connectors/FallbackConnector'; +import React from 'react'; +import ConfigForm from './ConfigForm'; /** * ConnectorForm component - * Provides specific form templates for different connector types + * Renders connector configuration forms based on field group metadata */ function ConnectorForm({ connectors = [], onAddConnector, onRemoveConnector, onConnectorNameChange, - onConnectorConfigChange + onConnectorConfigChange, + fieldGroups = [] }) { - const [newConfigKey, setNewConfigKey] = useState(''); - - // Render a specific connector form based on its type - const renderConnectorForm = (connector, index) => { - // Ensure connector is an object with expected properties - const safeConnector = connector || {}; - - return ( -
-
-

Connector #{index + 1}

- -
- -
- - -
- - {/* Render specific connector template based on type */} - {renderConnectorTemplate(safeConnector, index)} -
- ); - }; - - // Get the appropriate form template based on connector type - const renderConnectorTemplate = (connector, index) => { - // Check if connector.type exists, if not use empty string to avoid errors - const connectorType = (connector.type || '').toLowerCase(); - - // Common props for all connector components - const connectorProps = { - connector, - index, - onConnectorConfigChange, - getConfigValue - }; - - switch (connectorType) { - case 'telegram': - return ; - case 'slack': - return ; - case 'discord': - return ; - case 'github-issues': - return ; - case 'github-prs': - return ; - case 'irc': - return ; - case 'twitter': - return ; - default: - return ; + // Debug logging + console.log('ConnectorForm:', { connectors, fieldGroups }); + + // Handle connector change + const handleConnectorChange = (index, updatedConnector) => { + console.log('Connector change:', { index, updatedConnector }); + if (updatedConnector.type !== connectors[index].type) { + onConnectorNameChange(index, updatedConnector.type); + } else { + onConnectorConfigChange(index, updatedConnector.config); } }; - // Helper function to safely get config values - const getConfigValue = (connector, key, defaultValue = '') => { - if (!connector || !connector.config) return defaultValue; - - // If config is a string (JSON), try to parse it - let config = connector.config; - if (typeof config === 'string') { - try { - config = JSON.parse(config); - } catch (err) { - console.error('Error parsing config:', err); - return defaultValue; - } - } - - return config[key] !== undefined ? config[key] : defaultValue; + // Handle adding a new connector + const handleAddConnector = () => { + console.log('Adding new connector'); + onAddConnector(); }; return ( -
- {connectors && connectors.map((connector, index) => ( - renderConnectorForm(connector, index) - ))} - - -
+ ); } diff --git a/webui/react-ui/src/components/actions/BaseAction.jsx b/webui/react-ui/src/components/actions/BaseAction.jsx deleted file mode 100644 index 10ba5f5..0000000 --- a/webui/react-ui/src/components/actions/BaseAction.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import FormFieldDefinition from '../common/FormFieldDefinition'; - -/** - * Base action component that renders form fields based on field definitions - * - * @param {Object} props Component props - * @param {number} props.index Action index - * @param {Function} props.onActionConfigChange Handler for config changes - * @param {Function} props.getConfigValue Helper to get config values - * @param {Array} props.fields Field definitions for this action - */ -const BaseAction = ({ - index, - onActionConfigChange, - getConfigValue, - fields = [] -}) => { - // Create an object with all the current values - const currentValues = {}; - - // Pre-populate with current values or defaults - fields.forEach(field => { - currentValues[field.name] = getConfigValue(field.name, field.defaultValue); - }); - - // Handle field value changes - const handleFieldChange = (name, value) => { - onActionConfigChange(name, value); - }; - - return ( -
- -
- ); -}; - -export default BaseAction; diff --git a/webui/react-ui/src/components/actions/FallbackAction.jsx b/webui/react-ui/src/components/actions/FallbackAction.jsx deleted file mode 100644 index 26f770d..0000000 --- a/webui/react-ui/src/components/actions/FallbackAction.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -/** - * FallbackAction component for actions without specific configuration - */ -const FallbackAction = ({ index, onActionConfigChange, getConfigValue }) => { - return ( -
-

- This action doesn't require any additional configuration. -

-
- ); -}; - -export default FallbackAction; diff --git a/webui/react-ui/src/components/actions/GenerateImageAction.jsx b/webui/react-ui/src/components/actions/GenerateImageAction.jsx deleted file mode 100644 index 5f7a91f..0000000 --- a/webui/react-ui/src/components/actions/GenerateImageAction.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * Generate Image action component - */ -const GenerateImageAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for Generate Image action - const fields = [ - { - name: 'apiKey', - label: 'OpenAI API Key', - type: 'text', - defaultValue: '', - placeholder: 'sk-...', - helpText: 'Your OpenAI API key for image generation', - required: false, - }, - { - name: 'apiURL', - label: 'API URL', - type: 'text', - defaultValue: '', - placeholder: 'http://localai:8081', - helpText: 'OpenAI compatible API endpoint URL', - required: false, - }, - { - name: 'model', - label: 'Model', - type: 'text', - defaultValue: '', - placeholder: 'dall-e-3', - helpText: 'Image generation model', - required: false, - } - ]; - - return ( - - ); -}; - -export default GenerateImageAction; diff --git a/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx b/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx deleted file mode 100644 index 7823ace..0000000 --- a/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * GitHub Issue Closer action component - */ -const GithubIssueCloserAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for GitHub Issue Closer action - const fields = [ - { - name: 'token', - label: 'GitHub Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: '', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: '', - required: true, - }, - { - name: 'customActionName', - label: 'Custom Action Name (Optional)', - type: 'text', - defaultValue: '', - placeholder: 'close_github_issue', - helpText: 'Custom name for this action (optional)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubIssueCloserAction; diff --git a/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx b/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx deleted file mode 100644 index 4470fa7..0000000 --- a/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * GitHub Issue Commenter action component - */ -const GithubIssueCommenterAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for GitHub Issue Commenter action - const fields = [ - { - name: 'token', - label: 'GitHub Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: 'Owner of the repository', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: 'Name of the repository', - required: true, - }, - { - name: 'customActionName', - label: 'Custom Action Name (Optional)', - type: 'text', - defaultValue: '', - placeholder: 'comment_on_github_issue', - helpText: 'Custom name for this action (optional)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubIssueCommenterAction; diff --git a/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx b/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx deleted file mode 100644 index aec3cbe..0000000 --- a/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * GitHub Issue Labeler action component - */ -const GithubIssueLabelerAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for GitHub Issue Labeler action - const fields = [ - { - name: 'token', - label: 'GitHub Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: '', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: '', - required: true, - }, - { - name: 'availableLabels', - label: 'Available Labels', - type: 'text', - defaultValue: 'bug,enhancement', - placeholder: 'bug,enhancement,documentation', - helpText: 'Comma-separated list of available labels', - required: true, - }, - { - name: 'customActionName', - label: 'Custom Action Name (Optional)', - type: 'text', - defaultValue: '', - placeholder: 'add_label_to_issue', - helpText: 'Custom name for this action (optional)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubIssueLabelerAction; diff --git a/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx b/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx deleted file mode 100644 index 57c93ab..0000000 --- a/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * GitHub Issue Opener action component - */ -const GithubIssueOpenerAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for GitHub Issue Opener action - const fields = [ - { - name: 'token', - label: 'GitHub Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: '', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: '', - required: true, - }, - { - name: 'customActionName', - label: 'Custom Action Name (Optional)', - type: 'text', - defaultValue: '', - placeholder: 'open_github_issue', - helpText: 'Custom name for this action (optional)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubIssueOpenerAction; diff --git a/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx b/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx deleted file mode 100644 index bf94d76..0000000 --- a/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * GitHub Repository action component for repository-related actions - * Used for: - * - github-repository-get-content - * - github-repository-create-or-update-content - * - github-readme - */ -const GithubRepositoryAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for GitHub Repository action - const fields = [ - { - name: 'token', - label: 'GitHub Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: '', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: '', - required: true, - }, - { - name: 'customActionName', - label: 'Custom Action Name (Optional)', - type: 'text', - defaultValue: '', - placeholder: 'github_repo_action', - helpText: 'Custom name for this action (optional)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubRepositoryAction; diff --git a/webui/react-ui/src/components/actions/SendMailAction.jsx b/webui/react-ui/src/components/actions/SendMailAction.jsx deleted file mode 100644 index 0f6b192..0000000 --- a/webui/react-ui/src/components/actions/SendMailAction.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * SendMail action component - */ -const SendMailAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for SendMail action - const fields = [ - { - name: 'email', - label: 'Email', - type: 'email', - defaultValue: '', - placeholder: 'your-email@example.com', - helpText: 'Email address to send from', - required: true, - }, - { - name: 'username', - label: 'Username', - type: 'text', - defaultValue: '', - placeholder: 'SMTP username (often same as email)', - helpText: '', - required: true, - }, - { - name: 'password', - label: 'Password', - type: 'password', - defaultValue: '', - placeholder: 'SMTP password or app password', - helpText: 'For Gmail, use an app password', - required: true, - }, - { - name: 'smtpHost', - label: 'SMTP Host', - type: 'text', - defaultValue: '', - placeholder: 'smtp.gmail.com', - helpText: '', - required: true, - }, - { - name: 'smtpPort', - label: 'SMTP Port', - type: 'text', - defaultValue: '587', - placeholder: '587', - helpText: 'Common ports: 587 (TLS), 465 (SSL)', - required: true, - }, - ]; - - return ( - - ); -}; - -export default SendMailAction; diff --git a/webui/react-ui/src/components/actions/TwitterPostAction.jsx b/webui/react-ui/src/components/actions/TwitterPostAction.jsx deleted file mode 100644 index 9bfa061..0000000 --- a/webui/react-ui/src/components/actions/TwitterPostAction.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import BaseAction from './BaseAction'; - -/** - * Twitter Post action component - */ -const TwitterPostAction = ({ index, onActionConfigChange, getConfigValue }) => { - // Field definitions for Twitter Post action - const fields = [ - { - name: 'token', - label: 'Twitter API Token', - type: 'text', - defaultValue: '', - placeholder: 'Twitter API token', - helpText: 'Twitter API token with posting permissions', - required: true, - }, - { - name: 'noCharacterLimits', - label: 'Disable character limit (280 characters)', - type: 'checkbox', - defaultValue: 'false', - helpText: 'Enable to bypass the 280 character limit check', - required: false, - }, - ]; - - return ( - - ); -}; - -export default TwitterPostAction; diff --git a/webui/react-ui/src/components/agent-form-sections/ActionsSection.jsx b/webui/react-ui/src/components/agent-form-sections/ActionsSection.jsx index 6b29d1c..8fd6638 100644 --- a/webui/react-ui/src/components/agent-form-sections/ActionsSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/ActionsSection.jsx @@ -4,7 +4,7 @@ import ActionForm from '../ActionForm'; /** * ActionsSection component for the agent form */ -const ActionsSection = ({ formData, setFormData }) => { +const ActionsSection = ({ formData, setFormData, metadata }) => { // Handle action change const handleActionChange = (index, updatedAction) => { const updatedActions = [...(formData.actions || [])]; @@ -47,6 +47,7 @@ const ActionsSection = ({ formData, setFormData }) => { onChange={handleActionChange} onRemove={handleActionRemove} onAdd={handleAddAction} + fieldGroups={metadata?.actions || []} /> ); diff --git a/webui/react-ui/src/components/agent-form-sections/AdvancedSettingsSection.jsx b/webui/react-ui/src/components/agent-form-sections/AdvancedSettingsSection.jsx index b5072be..f391bcb 100644 --- a/webui/react-ui/src/components/agent-form-sections/AdvancedSettingsSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/AdvancedSettingsSection.jsx @@ -3,53 +3,20 @@ import FormFieldDefinition from '../common/FormFieldDefinition'; /** * Advanced Settings section of the agent form + * + * @param {Object} props Component props + * @param {Object} props.formData Current form data values + * @param {Function} props.handleInputChange Handler for input changes + * @param {Object} props.metadata Field metadata from the backend */ -const AdvancedSettingsSection = ({ formData, handleInputChange }) => { - // Define field definitions for Advanced Settings section - const fields = [ - { - name: 'max_steps', - label: 'Max Steps', - type: 'number', - defaultValue: 10, - helpText: 'Maximum number of steps the agent can take', - required: true, - }, - { - name: 'max_iterations', - label: 'Max Iterations', - type: 'number', - defaultValue: 5, - helpText: 'Maximum number of iterations for each step', - required: true, - }, - { - name: 'autonomous', - label: 'Autonomous Mode', - type: 'checkbox', - defaultValue: false, - helpText: 'Allow the agent to operate autonomously', - }, - { - name: 'verbose', - label: 'Verbose Mode', - type: 'checkbox', - defaultValue: false, - helpText: 'Enable detailed logging', - }, - { - name: 'allow_code_execution', - label: 'Allow Code Execution', - type: 'checkbox', - defaultValue: false, - helpText: 'Allow the agent to execute code (use with caution)', - }, - ]; +const AdvancedSettingsSection = ({ formData, handleInputChange, metadata }) => { + // Get fields from metadata + const fields = metadata?.AdvancedSettingsSection || []; // Handle field value changes const handleFieldChange = (name, value) => { - // For checkboxes, convert string 'true'/'false' to boolean - if (['autonomous', 'verbose', 'allow_code_execution'].includes(name)) { + const field = fields.find(f => f.name === name); + if (field && field.type === 'checkbox') { handleInputChange({ target: { name, diff --git a/webui/react-ui/src/components/agent-form-sections/BasicInfoSection.jsx b/webui/react-ui/src/components/agent-form-sections/BasicInfoSection.jsx index 7d74ecc..7f81dbe 100644 --- a/webui/react-ui/src/components/agent-form-sections/BasicInfoSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/BasicInfoSection.jsx @@ -3,54 +3,37 @@ import FormFieldDefinition from '../common/FormFieldDefinition'; /** * Basic Information section of the agent form + * + * @param {Object} props Component props + * @param {Object} props.formData Current form data values + * @param {Function} props.handleInputChange Handler for input changes + * @param {boolean} props.isEdit Whether the form is in edit mode + * @param {boolean} props.isGroupForm Whether the form is for a group + * @param {Object} props.metadata Field metadata from the backend */ -const BasicInfoSection = ({ formData, handleInputChange, isEdit, isGroupForm }) => { +const BasicInfoSection = ({ formData, handleInputChange, isEdit, isGroupForm, metadata }) => { // In group form context, we hide the basic info section entirely if (isGroupForm) { return null; } - // Define field definitions for Basic Information section - const fields = [ - { - name: 'name', - label: 'Name', - type: 'text', - defaultValue: '', - required: true, - helpText: isEdit ? 'Agent name cannot be changed after creation' : '', - disabled: isEdit, // This will be handled in the component - }, - { - name: 'description', - label: 'Description', - type: 'textarea', - defaultValue: '', - }, - { - name: 'identity_guidance', - label: 'Identity Guidance', - type: 'textarea', - defaultValue: '', - }, - { - name: 'random_identity', - label: 'Random Identity', - type: 'checkbox', - defaultValue: false, - }, - { - name: 'hud', - label: 'HUD', - type: 'checkbox', - defaultValue: false, + // Get fields from metadata and apply any client-side overrides + const fields = metadata?.BasicInfoSection?.map(field => { + // Special case for name field in edit mode + if (field.name === 'name' && isEdit) { + return { + ...field, + disabled: true, + helpText: 'Agent name cannot be changed after creation' + }; } - ]; + return field; + }) || []; // Handle field value changes const handleFieldChange = (name, value) => { - // For checkboxes, convert string 'true'/'false' to boolean - if (name === 'random_identity' || name === 'hud') { + const field = fields.find(f => f.name === name); + if (field && field.type === 'checkbox') { handleInputChange({ target: { name, diff --git a/webui/react-ui/src/components/agent-form-sections/ConnectorsSection.jsx b/webui/react-ui/src/components/agent-form-sections/ConnectorsSection.jsx index 45dd8c0..c02ca13 100644 --- a/webui/react-ui/src/components/agent-form-sections/ConnectorsSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/ConnectorsSection.jsx @@ -9,7 +9,8 @@ const ConnectorsSection = ({ handleAddConnector, handleRemoveConnector, handleConnectorNameChange, - handleConnectorConfigChange + handleConnectorConfigChange, + metadata }) => { return (
@@ -24,6 +25,7 @@ const ConnectorsSection = ({ onRemoveConnector={handleRemoveConnector} onConnectorNameChange={handleConnectorNameChange} onConnectorConfigChange={handleConnectorConfigChange} + fieldGroups={metadata?.connectors || []} />
); diff --git a/webui/react-ui/src/components/agent-form-sections/MemorySettingsSection.jsx b/webui/react-ui/src/components/agent-form-sections/MemorySettingsSection.jsx index 2ee2b9e..f8368dd 100644 --- a/webui/react-ui/src/components/agent-form-sections/MemorySettingsSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/MemorySettingsSection.jsx @@ -3,53 +3,35 @@ import FormFieldDefinition from '../common/FormFieldDefinition'; /** * Memory Settings section of the agent form + * + * @param {Object} props Component props + * @param {Object} props.formData Current form data values + * @param {Function} props.handleInputChange Handler for input changes + * @param {Object} props.metadata Field metadata from the backend */ -const MemorySettingsSection = ({ formData, handleInputChange }) => { - // Define field definitions for Memory Settings section - const fields = [ - { - name: 'memory_provider', - label: 'Memory Provider', - type: 'select', - defaultValue: 'local', - options: [ - { value: 'local', label: 'Local' }, - { value: 'redis', label: 'Redis' }, - { value: 'postgres', label: 'PostgreSQL' }, - ], - }, - { - name: 'memory_collection', - label: 'Memory Collection', - type: 'text', - defaultValue: '', - placeholder: 'agent_memories', - }, - { - name: 'memory_url', - label: 'Memory URL', - type: 'text', - defaultValue: '', - placeholder: 'redis://localhost:6379', - helpText: 'Connection URL for Redis or PostgreSQL', - }, - { - name: 'memory_window_size', - label: 'Memory Window Size', - type: 'number', - defaultValue: 10, - helpText: 'Number of recent messages to include in context window', - }, - ]; +const MemorySettingsSection = ({ formData, handleInputChange, metadata }) => { + // Get fields from metadata + const fields = metadata?.MemorySettingsSection || []; // Handle field value changes const handleFieldChange = (name, value) => { - handleInputChange({ - target: { - name, - value - } - }); + const field = fields.find(f => f.name === name); + if (field && field.type === 'checkbox') { + handleInputChange({ + target: { + name, + type: 'checkbox', + checked: value === 'true' + } + }); + } else { + handleInputChange({ + target: { + name, + value + } + }); + } }; return ( diff --git a/webui/react-ui/src/components/agent-form-sections/ModelSettingsSection.jsx b/webui/react-ui/src/components/agent-form-sections/ModelSettingsSection.jsx index 550dc77..a9ecff6 100644 --- a/webui/react-ui/src/components/agent-form-sections/ModelSettingsSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/ModelSettingsSection.jsx @@ -3,60 +3,35 @@ import FormFieldDefinition from '../common/FormFieldDefinition'; /** * Model Settings section of the agent form + * + * @param {Object} props Component props + * @param {Object} props.formData Current form data values + * @param {Function} props.handleInputChange Handler for input changes + * @param {Object} props.metadata Field metadata from the backend */ -const ModelSettingsSection = ({ formData, handleInputChange }) => { - // Define field definitions for Model Settings section - const fields = [ - { - name: 'model', - label: 'Model', - type: 'text', - defaultValue: '', - }, - { - name: 'multimodal_model', - label: 'Multimodal Model', - type: 'text', - defaultValue: '', - }, - { - name: 'api_url', - label: 'API URL', - type: 'text', - defaultValue: '', - }, - { - name: 'api_key', - label: 'API Key', - type: 'password', - defaultValue: '', - }, - { - name: 'temperature', - label: 'Temperature', - type: 'number', - defaultValue: 0.7, - min: 0, - max: 2, - step: 0.1, - }, - { - name: 'max_tokens', - label: 'Max Tokens', - type: 'number', - defaultValue: 2000, - min: 1, - }, - ]; +const ModelSettingsSection = ({ formData, handleInputChange, metadata }) => { + // Get fields from metadata + const fields = metadata?.ModelSettingsSection || []; // Handle field value changes const handleFieldChange = (name, value) => { - handleInputChange({ - target: { - name, - value - } - }); + const field = fields.find(f => f.name === name); + if (field && field.type === 'checkbox') { + handleInputChange({ + target: { + name, + type: 'checkbox', + checked: value === 'true' + } + }); + } else { + handleInputChange({ + target: { + name, + value + } + }); + } }; return ( diff --git a/webui/react-ui/src/components/agent-form-sections/PromptsGoalsSection.jsx b/webui/react-ui/src/components/agent-form-sections/PromptsGoalsSection.jsx index 7b65aa8..655c19f 100644 --- a/webui/react-ui/src/components/agent-form-sections/PromptsGoalsSection.jsx +++ b/webui/react-ui/src/components/agent-form-sections/PromptsGoalsSection.jsx @@ -3,64 +3,47 @@ import FormFieldDefinition from '../common/FormFieldDefinition'; /** * Prompts & Goals section of the agent form + * + * @param {Object} props Component props + * @param {Object} props.formData Current form data values + * @param {Function} props.handleInputChange Handler for input changes + * @param {boolean} props.isGroupForm Whether the form is for a group + * @param {Object} props.metadata Field metadata from the backend */ -const PromptsGoalsSection = ({ formData, handleInputChange, isGroupForm }) => { - // Define field definitions for Prompts & Goals section +const PromptsGoalsSection = ({ formData, handleInputChange, isGroupForm, metadata }) => { + // Get fields based on metadata and form context const getFields = () => { - // Base fields that are always shown - const baseFields = [ - { - name: 'goals', - label: 'Goals', - type: 'textarea', - defaultValue: '', - helpText: 'Define the agent\'s goals (one per line)', - rows: 5, - }, - { - name: 'constraints', - label: 'Constraints', - type: 'textarea', - defaultValue: '', - helpText: 'Define the agent\'s constraints (one per line)', - rows: 5, - }, - { - name: 'tools', - label: 'Tools', - type: 'textarea', - defaultValue: '', - helpText: 'Define the agent\'s tools (one per line)', - rows: 5, - }, - ]; - - // Only include system_prompt field if not in group form context - if (!isGroupForm) { - return [ - { - name: 'system_prompt', - label: 'System Prompt', - type: 'textarea', - defaultValue: '', - helpText: 'Instructions that define the agent\'s behavior', - rows: 5, - }, - ...baseFields - ]; + if (!metadata?.PromptsGoalsSection) { + return []; } - - return baseFields; + + // If in group form, filter out system_prompt + if (isGroupForm) { + return metadata.PromptsGoalsSection.filter(field => field.name !== 'system_prompt'); + } + + return metadata.PromptsGoalsSection; }; // Handle field value changes const handleFieldChange = (name, value) => { - handleInputChange({ - target: { - name, - value - } - }); + const field = getFields().find(f => f.name === name); + if (field && field.type === 'checkbox') { + handleInputChange({ + target: { + name, + type: 'checkbox', + checked: value === 'true' + } + }); + } else { + handleInputChange({ + target: { + name, + value + } + }); + } }; return ( diff --git a/webui/react-ui/src/components/common/FormField.jsx b/webui/react-ui/src/components/common/FormField.jsx index f3b44b9..ed688ca 100644 --- a/webui/react-ui/src/components/common/FormField.jsx +++ b/webui/react-ui/src/components/common/FormField.jsx @@ -24,6 +24,9 @@ const FormField = ({ helpText = '', options = [], required = false, + min = 0, + max = 2**31, + step = 1, }) => { // Create label with required indicator const labelWithIndicator = required ? ( @@ -86,6 +89,25 @@ const FormField = ({ {helpText && {helpText}} ); + case 'number': + return ( + <> + + onChange(e.target.value)} + className="form-control" + placeholder={placeholder} + required={required} + min={min} + max={max} + step={step} + /> + {helpText && {helpText}} + + ); default: return ( <> diff --git a/webui/react-ui/src/components/connectors/BaseConnector.jsx b/webui/react-ui/src/components/connectors/BaseConnector.jsx deleted file mode 100644 index ff7c497..0000000 --- a/webui/react-ui/src/components/connectors/BaseConnector.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import FormFieldDefinition from '../common/FormFieldDefinition'; - -/** - * Base connector component that renders form fields based on field definitions - * - * @param {Object} props Component props - * @param {Object} props.connector Connector data - * @param {number} props.index Connector index - * @param {Function} props.onConnectorConfigChange Handler for config changes - * @param {Function} props.getConfigValue Helper to get config values - * @param {Array} props.fields Field definitions for this connector - */ -const BaseConnector = ({ - connector, - index, - onConnectorConfigChange, - getConfigValue, - fields = [] -}) => { - // Create an object with all the current values - const currentValues = {}; - - // Pre-populate with current values or defaults - fields.forEach(field => { - currentValues[field.name] = getConfigValue(connector, field.name, field.defaultValue); - }); - - // Handle field value changes - const handleFieldChange = (name, value) => { - onConnectorConfigChange(index, name, value); - }; - - return ( -
- -
- ); -}; - -export default BaseConnector; diff --git a/webui/react-ui/src/components/connectors/DiscordConnector.jsx b/webui/react-ui/src/components/connectors/DiscordConnector.jsx deleted file mode 100644 index f22b0e0..0000000 --- a/webui/react-ui/src/components/connectors/DiscordConnector.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * Discord connector template - */ -const DiscordConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for Discord connector - const fields = [ - { - name: 'token', - label: 'Discord Bot Token', - type: 'text', - defaultValue: '', - placeholder: 'Bot token from Discord Developer Portal', - helpText: 'Get this from the Discord Developer Portal', - required: true, - }, - { - name: 'defaultChannel', - label: 'Default Channel', - type: 'text', - defaultValue: '', - placeholder: '123456789012345678', - helpText: 'Channel ID to always answer even if not mentioned', - required: false, - }, - ]; - - return ( - - ); -}; - -export default DiscordConnector; diff --git a/webui/react-ui/src/components/connectors/FallbackConnector.jsx b/webui/react-ui/src/components/connectors/FallbackConnector.jsx deleted file mode 100644 index c6c3d52..0000000 --- a/webui/react-ui/src/components/connectors/FallbackConnector.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useState } from 'react'; - -/** - * Fallback connector template for unknown connector types - */ -const FallbackConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - const [newConfigKey, setNewConfigKey] = useState(''); - - // Parse config if it's a string - let parsedConfig = connector.config; - if (typeof parsedConfig === 'string') { - try { - parsedConfig = JSON.parse(parsedConfig); - } catch (err) { - console.error('Error parsing config:', err); - parsedConfig = {}; - } - } else if (!parsedConfig) { - parsedConfig = {}; - } - - // Handle adding a new custom field - const handleAddCustomField = () => { - if (newConfigKey) { - onConnectorConfigChange(index, newConfigKey, ''); - setNewConfigKey(''); - } - }; - - return ( -
- {/* Individual field inputs */} - {parsedConfig && Object.entries(parsedConfig).map(([key, value]) => ( -
- - onConnectorConfigChange(index, key, e.target.value)} - /> -
- ))} - - {/* Add custom configuration field */} -
-
Add Custom Configuration Field
-
- setNewConfigKey(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleAddCustomField()} - /> - -
-
-
- ); -}; - -export default FallbackConnector; diff --git a/webui/react-ui/src/components/connectors/GithubIssuesConnector.jsx b/webui/react-ui/src/components/connectors/GithubIssuesConnector.jsx deleted file mode 100644 index cbc73ad..0000000 --- a/webui/react-ui/src/components/connectors/GithubIssuesConnector.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * GitHub Issues connector template - */ -const GithubIssuesConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for GitHub Issues connector - const fields = [ - { - name: 'token', - label: 'GitHub Personal Access Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: '', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: '', - required: true, - }, - { - name: 'replyIfNoReplies', - label: 'Reply Behavior', - type: 'select', - defaultValue: 'false', - options: [ - { value: 'false', label: 'Reply to all issues' }, - { value: 'true', label: 'Only reply to issues with no comments' }, - ], - helpText: '', - required: false, - }, - { - name: 'pollInterval', - label: 'Poll Interval', - type: 'text', - defaultValue: '10m', - placeholder: '10m', - helpText: 'How often to check for new issues (e.g., 10m, 1h)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubIssuesConnector; diff --git a/webui/react-ui/src/components/connectors/GithubPRsConnector.jsx b/webui/react-ui/src/components/connectors/GithubPRsConnector.jsx deleted file mode 100644 index 5039070..0000000 --- a/webui/react-ui/src/components/connectors/GithubPRsConnector.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * GitHub PRs connector template - */ -const GithubPRsConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for GitHub PRs connector - const fields = [ - { - name: 'token', - label: 'GitHub Personal Access Token', - type: 'text', - defaultValue: '', - placeholder: 'ghp_...', - helpText: 'Personal access token with repo scope', - required: true, - }, - { - name: 'owner', - label: 'Repository Owner', - type: 'text', - defaultValue: '', - placeholder: 'username or organization', - helpText: '', - required: true, - }, - { - name: 'repository', - label: 'Repository Name', - type: 'text', - defaultValue: '', - placeholder: 'repository-name', - helpText: '', - required: true, - }, - { - name: 'replyIfNoReplies', - label: 'Reply Behavior', - type: 'select', - defaultValue: 'false', - options: [ - { value: 'false', label: 'Reply to all PRs' }, - { value: 'true', label: 'Only reply to PRs with no comments' }, - ], - helpText: '', - required: false, - }, - { - name: 'pollInterval', - label: 'Poll Interval', - type: 'text', - defaultValue: '10m', - placeholder: '10m', - helpText: 'How often to check for new PRs (e.g., 10m, 1h)', - required: false, - }, - ]; - - return ( - - ); -}; - -export default GithubPRsConnector; diff --git a/webui/react-ui/src/components/connectors/IRCConnector.jsx b/webui/react-ui/src/components/connectors/IRCConnector.jsx deleted file mode 100644 index 89be1c4..0000000 --- a/webui/react-ui/src/components/connectors/IRCConnector.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * IRC connector template - */ -const IRCConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for IRC connector - const fields = [ - { - name: 'server', - label: 'IRC Server', - type: 'text', - defaultValue: '', - placeholder: 'irc.libera.chat', - helpText: 'IRC server address', - required: true, - }, - { - name: 'port', - label: 'Port', - type: 'text', - defaultValue: '6667', - placeholder: '6667', - helpText: 'IRC server port', - required: true, - }, - { - name: 'nickname', - label: 'Nickname', - type: 'text', - defaultValue: '', - placeholder: 'MyAgentBot', - helpText: 'Bot nickname', - required: true, - }, - { - name: 'channel', - label: 'Channel', - type: 'text', - defaultValue: '', - placeholder: '#channel1', - helpText: 'Channel to join', - required: true, - }, - { - name: 'alwaysReply', - label: 'Always Reply', - type: 'checkbox', - defaultValue: 'false', - helpText: 'If checked, the agent will reply to all messages in the channel', - required: false, - }, - ]; - - return ( - - ); -}; - -export default IRCConnector; diff --git a/webui/react-ui/src/components/connectors/SlackConnector.jsx b/webui/react-ui/src/components/connectors/SlackConnector.jsx deleted file mode 100644 index 489f2ee..0000000 --- a/webui/react-ui/src/components/connectors/SlackConnector.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * Slack connector template - */ -const SlackConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for Slack connector - const fields = [ - { - name: 'appToken', - label: 'Slack App Token', - type: 'text', - defaultValue: '', - placeholder: 'xapp-...', - helpText: 'App-level token starting with xapp-', - required: true, - }, - { - name: 'botToken', - label: 'Slack Bot Token', - type: 'text', - defaultValue: '', - placeholder: 'xoxb-...', - helpText: 'Bot token starting with xoxb-', - required: true, - }, - { - name: 'channelID', - label: 'Slack Channel ID', - type: 'text', - defaultValue: '', - placeholder: 'C1234567890', - helpText: 'Optional: Specific channel ID to join', - required: false, - }, - { - name: 'alwaysReply', - label: 'Always Reply', - type: 'checkbox', - defaultValue: 'false', - helpText: 'If checked, the agent will reply to all messages in the channel', - required: false, - }, - ]; - - return ( - - ); -}; - -export default SlackConnector; diff --git a/webui/react-ui/src/components/connectors/TelegramConnector.jsx b/webui/react-ui/src/components/connectors/TelegramConnector.jsx deleted file mode 100644 index c66c2de..0000000 --- a/webui/react-ui/src/components/connectors/TelegramConnector.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * Telegram connector template - */ -const TelegramConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for Telegram connector - const fields = [ - { - name: 'token', - label: 'Telegram Bot Token', - type: 'text', - defaultValue: '', - placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', - helpText: 'Get this from @BotFather on Telegram', - required: true, - }, - ]; - - return ( - - ); -}; - -export default TelegramConnector; diff --git a/webui/react-ui/src/components/connectors/TwitterConnector.jsx b/webui/react-ui/src/components/connectors/TwitterConnector.jsx deleted file mode 100644 index 7f394b0..0000000 --- a/webui/react-ui/src/components/connectors/TwitterConnector.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import BaseConnector from './BaseConnector'; - -/** - * Twitter connector template - */ -const TwitterConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => { - // Field definitions for Twitter connector - const fields = [ - { - name: 'apiKey', - label: 'API Key', - type: 'text', - defaultValue: '', - placeholder: 'Twitter API Key', - helpText: '', - required: true, - }, - { - name: 'apiSecret', - label: 'API Secret', - type: 'password', - defaultValue: '', - placeholder: 'Twitter API Secret', - helpText: '', - required: true, - }, - { - name: 'accessToken', - label: 'Access Token', - type: 'text', - defaultValue: '', - placeholder: 'Twitter Access Token', - helpText: '', - required: true, - }, - { - name: 'accessSecret', - label: 'Access Token Secret', - type: 'password', - defaultValue: '', - placeholder: 'Twitter Access Token Secret', - helpText: '', - required: true, - }, - { - name: 'bearerToken', - label: 'Bearer Token', - type: 'password', - defaultValue: '', - placeholder: 'Twitter Bearer Token', - helpText: '', - required: true, - }, - ]; - - return ( - - ); -}; - -export default TwitterConnector; diff --git a/webui/react-ui/src/pages/AgentSettings.jsx b/webui/react-ui/src/pages/AgentSettings.jsx index e5b3d5a..44364e9 100644 --- a/webui/react-ui/src/pages/AgentSettings.jsx +++ b/webui/react-ui/src/pages/AgentSettings.jsx @@ -1,12 +1,14 @@ import { useState, useEffect } from 'react'; import { useParams, useOutletContext, useNavigate } from 'react-router-dom'; import { useAgent } from '../hooks/useAgent'; +import { agentApi } from '../utils/api'; import AgentForm from '../components/AgentForm'; function AgentSettings() { const { name } = useParams(); const { showToast } = useOutletContext(); const navigate = useNavigate(); + const [metadata, setMetadata] = useState(null); const [formData, setFormData] = useState({ name: '', description: '', @@ -47,9 +49,28 @@ function AgentSettings() { deleteAgent } = useAgent(name); + // Fetch metadata on component mount + useEffect(() => { + const fetchMetadata = async () => { + try { + // Fetch metadata from the dedicated endpoint + const response = await agentApi.getAgentConfigMetadata(); + if (response) { + setMetadata(response); + } + } catch (error) { + console.error('Error fetching metadata:', error); + // Continue without metadata, the form will use default fields + } + }; + + fetchMetadata(); + }, []); + // Load agent data when component mounts useEffect(() => { if (agent) { + // Set form data from agent config setFormData({ ...formData, ...agent, @@ -162,6 +183,7 @@ function AgentSettings() { onSubmit={handleSubmit} loading={loading} submitButtonText="Save Changes" + metadata={metadata} /> diff --git a/webui/react-ui/src/pages/CreateAgent.jsx b/webui/react-ui/src/pages/CreateAgent.jsx index 0c3027d..a511ac7 100644 --- a/webui/react-ui/src/pages/CreateAgent.jsx +++ b/webui/react-ui/src/pages/CreateAgent.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useNavigate, useOutletContext } from 'react-router-dom'; import { agentApi } from '../utils/api'; import AgentForm from '../components/AgentForm'; @@ -7,6 +7,7 @@ function CreateAgent() { const navigate = useNavigate(); const { showToast } = useOutletContext(); const [loading, setLoading] = useState(false); + const [metadata, setMetadata] = useState(null); const [formData, setFormData] = useState({ name: '', description: '', @@ -37,6 +38,24 @@ function CreateAgent() { avatar_style: 'default', }); + // Fetch metadata on component mount + useEffect(() => { + const fetchMetadata = async () => { + try { + // Fetch metadata from the dedicated endpoint + const response = await agentApi.getAgentConfigMetadata(); + if (response) { + setMetadata(response); + } + } catch (error) { + console.error('Error fetching metadata:', error); + // Continue without metadata, the form will use default fields + } + }; + + fetchMetadata(); + }, []); + // Handle form submission const handleSubmit = async (e) => { e.preventDefault(); @@ -80,6 +99,7 @@ function CreateAgent() { loading={loading} submitButtonText="Create Agent" isEdit={false} + metadata={metadata} /> diff --git a/webui/react-ui/src/utils/api.js b/webui/react-ui/src/utils/api.js index 161daba..7b0ddc9 100644 --- a/webui/react-ui/src/utils/api.js +++ b/webui/react-ui/src/utils/api.js @@ -42,6 +42,51 @@ export const agentApi = { return handleResponse(response); }, + // Get agent configuration metadata + getAgentConfigMetadata: async () => { + const response = await fetch(buildUrl(API_CONFIG.endpoints.agentConfigMetadata), { + headers: API_CONFIG.headers + }); + const metadata = await handleResponse(response); + + // Process metadata to group by section + if (metadata) { + const groupedMetadata = {}; + + // Handle Fields - Group by section + if (metadata.Fields) { + metadata.Fields.forEach(field => { + const section = field.tags?.section || 'Other'; + const sectionKey = `${section}Section`; // Add "Section" postfix + + if (!groupedMetadata[sectionKey]) { + groupedMetadata[sectionKey] = []; + } + + groupedMetadata[sectionKey].push(field); + }); + } + + // Pass through connectors and actions field groups directly + // Make sure to assign the correct metadata to each section + if (metadata.Connectors) { + console.log("Original Connectors metadata:", metadata.Connectors); + groupedMetadata.connectors = metadata.Connectors; + } + + if (metadata.Actions) { + console.log("Original Actions metadata:", metadata.Actions); + groupedMetadata.actions = metadata.Actions; + } + + console.log("Processed metadata:", groupedMetadata); + + return groupedMetadata; + } + + return metadata; + }, + // Create a new agent createAgent: async (config) => { const response = await fetch(buildUrl(API_CONFIG.endpoints.createAgent), { diff --git a/webui/react-ui/src/utils/config.js b/webui/react-ui/src/utils/config.js index 0c3318c..dfbadbe 100644 --- a/webui/react-ui/src/utils/config.js +++ b/webui/react-ui/src/utils/config.js @@ -20,6 +20,7 @@ export const API_CONFIG = { // Agent endpoints agents: '/api/agents', agentConfig: (name) => `/api/agent/${name}/config`, + agentConfigMetadata: '/api/agent/config/metadata', createAgent: '/create', deleteAgent: (name) => `/delete/${name}`, pauseAgent: (name) => `/pause/${name}`, diff --git a/webui/routes.go b/webui/routes.go index db7e445..03685da 100644 --- a/webui/routes.go +++ b/webui/routes.go @@ -171,6 +171,9 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) { // 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)) + + // Metadata endpoint for agent configuration fields + webapp.Get("/api/agent/config/metadata", app.GetAgentConfigMeta()) webapp.Post("/action/:name/run", app.ExecuteAction(pool)) webapp.Get("/actions", app.ListActions()) @@ -190,13 +193,13 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) { } statuses[a] = !agent.Paused() } - + return c.JSON(fiber.Map{ - "Agents": agents, + "Agents": agents, "AgentCount": len(agents), - "Actions": len(services.AvailableActions), + "Actions": len(services.AvailableActions), "Connectors": len(services.AvailableConnectors), - "Status": statuses, + "Status": statuses, }) }) @@ -206,9 +209,9 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) { if history == nil { history = &state.Status{ActionResults: []types.ActionState{}} } - + return c.JSON(fiber.Map{ - "Name": c.Params("name"), + "Name": c.Params("name"), "History": Reverse(history.Results()), }) })