chore(ui): Move some field definitions server side

This commit is contained in:
Richard Palethorpe
2025-03-26 15:56:14 +00:00
parent 7fb99ecf21
commit 319caf8e91
62 changed files with 1534 additions and 1574 deletions

View File

@@ -5,6 +5,7 @@ import (
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
) )
type ConnectorConfig struct { type ConnectorConfig struct {
@@ -35,7 +36,7 @@ type AgentConfig struct {
MCPServers []agent.MCPServer `json:"mcp_servers" form:"mcp_servers"` MCPServers []agent.MCPServer `json:"mcp_servers" form:"mcp_servers"`
Description string `json:"description" form:"description"` Description string `json:"description" form:"description"`
// This is what needs to be part of ActionsConfig
Model string `json:"model" form:"model"` Model string `json:"model" form:"model"`
MultimodalModel string `json:"multimodal_model" form:"multimodal_model"` MultimodalModel string `json:"multimodal_model" form:"multimodal_model"`
APIURL string `json:"api_url" form:"api_url"` 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"` 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 { type Connector interface {
AgentResultCallback() func(state types.ActionState) AgentResultCallback() func(state types.ActionState)
AgentReasoningCallback() func(state types.ActionCurrentState) bool AgentReasoningCallback() func(state types.ActionCurrentState) bool

42
pkg/config/meta.go Normal file
View File

@@ -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"`
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/core/action"
"github.com/mudler/LocalAgent/core/state" "github.com/mudler/LocalAgent/core/state"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/mudler/LocalAgent/services/actions" "github.com/mudler/LocalAgent/services/actions"
@@ -30,7 +31,7 @@ const (
ActionWikipedia = "wikipedia" ActionWikipedia = "wikipedia"
ActionBrowse = "browse" ActionBrowse = "browse"
ActionTwitterPost = "twitter-post" ActionTwitterPost = "twitter-post"
ActionSendMail = "send_mail" ActionSendMail = "send-mail"
ActionGenerateImage = "generate_image" ActionGenerateImage = "generate_image"
ActionCounter = "counter" ActionCounter = "counter"
ActionCallAgents = "call_agents" ActionCallAgents = "call_agents"
@@ -138,3 +139,108 @@ func Action(name string, config map[string]string, pool *state.AgentPool) (types
return a, nil 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{},
},
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -96,3 +97,32 @@ func (a *GenImageAction) Definition() types.ActionDefinition {
func (a *GenImageAction) Plannable() bool { func (a *GenImageAction) Plannable() bool {
return true 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)",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -118,3 +119,36 @@ func (g *GithubIssuesCloser) Definition() types.ActionDefinition {
func (a *GithubIssuesCloser) Plannable() bool { func (a *GithubIssuesCloser) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -105,3 +106,36 @@ func (g *GithubIssuesCommenter) Definition() types.ActionDefinition {
func (a *GithubIssuesCommenter) Plannable() bool { func (a *GithubIssuesCommenter) Plannable() bool {
return true 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",
},
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -120,3 +121,43 @@ func (g *GithubIssuesLabeler) Definition() types.ActionDefinition {
func (a *GithubIssuesLabeler) Plannable() bool { func (a *GithubIssuesLabeler) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -111,3 +112,36 @@ func (g *GithubIssuesOpener) Definition() types.ActionDefinition {
func (a *GithubIssuesOpener) Plannable() bool { func (a *GithubIssuesOpener) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -99,3 +100,36 @@ func (g *GithubIssuesReader) Definition() types.ActionDefinition {
func (a *GithubIssuesReader) Plannable() bool { func (a *GithubIssuesReader) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -108,3 +109,36 @@ func (g *GithubIssueSearch) Definition() types.ActionDefinition {
func (a *GithubIssueSearch) Plannable() bool { func (a *GithubIssueSearch) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -144,3 +145,36 @@ func (g *GithubRepositoryCreateOrUpdateContent) Definition() types.ActionDefinit
func (a *GithubRepositoryCreateOrUpdateContent) Plannable() bool { func (a *GithubRepositoryCreateOrUpdateContent) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -109,3 +110,36 @@ func (g *GithubRepositoryGetContent) Definition() types.ActionDefinition {
func (a *GithubRepositoryGetContent) Plannable() bool { func (a *GithubRepositoryGetContent) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -75,3 +76,22 @@ func (g *GithubRepositoryREADME) Definition() types.ActionDefinition {
func (a *GithubRepositoryREADME) Plannable() bool { func (a *GithubRepositoryREADME) Plannable() bool {
return true 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",
},
}
}

View File

@@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
"github.com/tmc/langchaingo/tools/duckduckgo" "github.com/tmc/langchaingo/tools/duckduckgo"
"mvdan.cc/xurls/v2" "mvdan.cc/xurls/v2"
@@ -89,3 +90,19 @@ func (a *SearchAction) Definition() types.ActionDefinition {
func (a *SearchAction) Plannable() bool { func (a *SearchAction) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"net/smtp" "net/smtp"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -80,3 +81,45 @@ func (a *SendMailAction) Definition() types.ActionDefinition {
func (a *SendMailAction) Plannable() bool { func (a *SendMailAction) Plannable() bool {
return true 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",
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"log" "log"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
"golang.org/x/crypto/ssh" "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) { func sshCommand(privateKey, command, user, host string) (string, error) {
// Create signer from private key string // Create signer from private key string
key, err := ssh.ParsePrivateKey([]byte(privateKey)) key, err := ssh.ParsePrivateKey([]byte(privateKey))

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/services/connectors/twitter" "github.com/mudler/LocalAgent/services/connectors/twitter"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
) )
@@ -62,3 +63,22 @@ func (a *PostTweetAction) Definition() types.ActionDefinition {
func (a *PostTweetAction) Plannable() bool { func (a *PostTweetAction) Plannable() bool {
return true 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",
},
}
}

View File

@@ -3,6 +3,7 @@ package services
import ( import (
"encoding/json" "encoding/json"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/mudler/LocalAgent/services/connectors" "github.com/mudler/LocalAgent/services/connectors"
@@ -69,3 +70,43 @@ func Connectors(a *state.AgentConfig) []state.Connector {
} }
return conns 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(),
},
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/sashabaranov/go-openai" "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) { func (d *Discord) AgentResultCallback() func(state types.ActionState) {
return func(state types.ActionState) { return func(state types.ActionState) {
// Send the result to the bot // Send the result to the bot

View File

@@ -8,6 +8,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/sashabaranov/go-openai" "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)",
},
}
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/google/go-github/v69/github" "github.com/google/go-github/v69/github"
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/sashabaranov/go-openai" "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)",
},
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/mudler/LocalAgent/services/actions" "github.com/mudler/LocalAgent/services/actions"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
@@ -207,3 +208,44 @@ func (i *IRC) Start(a *agent.Agent) {
// Start the IRC client in a goroutine // Start the IRC client in a goroutine
go i.conn.Loop() 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",
},
}
}

View File

@@ -10,6 +10,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/mudler/LocalAgent/pkg/xstrings" "github.com/mudler/LocalAgent/pkg/xstrings"
"github.com/mudler/LocalAgent/services/actions" "github.com/mudler/LocalAgent/services/actions"
@@ -784,3 +785,37 @@ func (t *Slack) Start(a *agent.Agent) {
client.RunContext(a.Context()) 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",
},
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/go-telegram/bot/models" "github.com/go-telegram/bot/models"
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/mudler/LocalAgent/pkg/xstrings" "github.com/mudler/LocalAgent/pkg/xstrings"
"github.com/mudler/LocalAgent/services/actions" "github.com/mudler/LocalAgent/services/actions"
@@ -203,3 +204,27 @@ func NewTelegramConnector(config map[string]string) (*Telegram, error) {
conversationTracker: NewConversationTracker[int64](duration), conversationTracker: NewConversationTracker[int64](duration),
}, nil }, 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",
},
}
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/mudler/LocalAgent/core/agent" "github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/types" "github.com/mudler/LocalAgent/core/types"
"github.com/mudler/LocalAgent/pkg/config"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/mudler/LocalAgent/services/connectors/twitter" "github.com/mudler/LocalAgent/services/connectors/twitter"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
@@ -134,3 +135,26 @@ func (t *Twitter) run(a *agent.Agent) error {
return nil 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,
},
}
}

View File

@@ -477,3 +477,15 @@ func (a *App) CreateGroup(pool *state.AgentPool) func(c *fiber.Ctx) error {
return statusJSONMessage(c, "ok") 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)
}
}

View File

@@ -1,169 +1,31 @@
import React from 'react'; import React from 'react';
import FallbackAction from './actions/FallbackAction'; import ConfigForm from './ConfigForm';
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';
/** /**
* ActionForm component for configuring an action * ActionForm component for configuring an action
* Renders action configuration forms based on field group metadata
*/ */
const ActionForm = ({ actions = [], onChange, onRemove, onAdd }) => { const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = [] }) => {
// Available action types // Debug logging
const actionTypes = [ console.log('ActionForm:', { actions, fieldGroups });
{ 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 // Handle action change
const parseConfig = (action) => { const handleActionChange = (index, updatedAction) => {
if (!action || !action.config) return {}; console.log('Action change:', { index, updatedAction });
onChange(index, updatedAction);
try {
return JSON.parse(action.config || '{}');
} catch (error) {
console.error('Error parsing action config:', error);
return {};
}
};
// 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 <GithubIssueLabelerAction {...actionProps} />;
case 'github-issue-opener':
return <GithubIssueOpenerAction {...actionProps} />;
case 'github-issue-closer':
return <GithubIssueCloserAction {...actionProps} />;
case 'github-issue-commenter':
return <GithubIssueCommenterAction {...actionProps} />;
case 'github-repository-get-content':
case 'github-repository-create-or-update-content':
case 'github-readme':
return <GithubRepositoryAction {...actionProps} />;
case 'twitter-post':
return <TwitterPostAction {...actionProps} />;
case 'send-mail':
return <SendMailAction {...actionProps} />;
case 'generate_image':
return <GenerateImageAction {...actionProps} />;
default:
return <FallbackAction {...actionProps} />;
}
};
// Render a specific action form
const renderActionForm = (action, index) => {
// Ensure action is an object with expected properties
const safeAction = action || {};
return (
<div key={index} className="connector-item mb-4">
<div className="connector-header">
<h4>Action #{index + 1}</h4>
<button
type="button"
className="remove-btn"
onClick={() => onRemove(index)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="connector-type mb-3">
<label htmlFor={`actionType${index}`}>Action Type</label>
<select
id={`actionType${index}`}
value={safeAction.name || ''}
onChange={(e) => handleActionTypeChange(index, e.target.value)}
className="form-control"
>
{actionTypes.map((type) => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
</div>
{/* Render specific action template based on type */}
{renderActionComponent(safeAction, index)}
</div>
);
}; };
return ( return (
<div className="connectors-container"> <ConfigForm
{actions && actions.map((action, index) => ( items={actions}
renderActionForm(action, index) fieldGroups={fieldGroups}
))} onChange={handleActionChange}
onRemove={onRemove}
<button onAdd={onAdd}
type="button" itemType="action"
className="add-btn" typeField="name"
onClick={onAdd} addButtonText="Add Action"
> />
<i className="fas fa-plus"></i> Add Action
</button>
</div>
); );
}; };

View File

@@ -19,7 +19,8 @@ const AgentForm = ({
loading = false, loading = false,
submitButtonText, submitButtonText,
isGroupForm = false, isGroupForm = false,
noFormWrapper = false noFormWrapper = false,
metadata = null
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { showToast } = useOutletContext(); const { showToast } = useOutletContext();
@@ -239,19 +240,19 @@ const AgentForm = ({
<div className='agent-form'> <div className='agent-form'>
{/* Form Sections */} {/* Form Sections */}
<div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}>
<BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} /> <BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'model-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'model-section' ? 'block' : 'none' }}>
<ModelSettingsSection formData={formData} handleInputChange={handleInputChange} /> <ModelSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} /> <ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
<ActionsSection formData={formData} setFormData={setFormData} /> <ActionsSection formData={formData} setFormData={setFormData} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'mcp-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'mcp-section' ? 'block' : 'none' }}>
@@ -259,34 +260,34 @@ const AgentForm = ({
</div> </div>
<div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}>
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} /> <MemorySettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}>
<PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} /> <PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}>
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} /> <AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
</div> </div>
) : ( ) : (
<form className="agent-form" onSubmit={handleSubmit}> <form className="agent-form" onSubmit={handleSubmit} noValidate>
{/* Form Sections */} {/* Form Sections */}
<div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'basic-section' ? 'block' : 'none' }}>
<BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} /> <BasicInfoSection formData={formData} handleInputChange={handleInputChange} isEdit={isEdit} isGroupForm={isGroupForm} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'model-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'model-section' ? 'block' : 'none' }}>
<ModelSettingsSection formData={formData} handleInputChange={handleInputChange} /> <ModelSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'connectors-section' ? 'block' : 'none' }}>
<ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} /> <ConnectorsSection formData={formData} handleAddConnector={handleAddConnector} handleRemoveConnector={handleRemoveConnector} handleConnectorNameChange={handleConnectorNameChange} handleConnectorConfigChange={handleConnectorConfigChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'actions-section' ? 'block' : 'none' }}>
<ActionsSection formData={formData} setFormData={setFormData} /> <ActionsSection formData={formData} setFormData={setFormData} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'mcp-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'mcp-section' ? 'block' : 'none' }}>
@@ -294,15 +295,15 @@ const AgentForm = ({
</div> </div>
<div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'memory-section' ? 'block' : 'none' }}>
<MemorySettingsSection formData={formData} handleInputChange={handleInputChange} /> <MemorySettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'prompts-section' ? 'block' : 'none' }}>
<PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} /> <PromptsGoalsSection formData={formData} handleInputChange={handleInputChange} isGroupForm={isGroupForm} metadata={metadata} />
</div> </div>
<div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}> <div style={{ display: activeSection === 'advanced-section' ? 'block' : 'none' }}>
<AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} /> <AdvancedSettingsSection formData={formData} handleInputChange={handleInputChange} metadata={metadata} />
</div> </div>
{/* Form Controls */} {/* Form Controls */}

View File

@@ -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 (
<div key={index} className="config-item mb-4 card">
<div className="config-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h4 style={{ margin: 0 }}>{itemType.charAt(0).toUpperCase() + itemType.slice(1)} #{index + 1}</h4>
<button
type="button"
className="remove-btn"
onClick={() => onRemove(index)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="config-type mb-3">
<label htmlFor={`${itemType}Type${index}`}>{itemType.charAt(0).toUpperCase() + itemType.slice(1)} Type</label>
<select
id={`${itemType}Type${index}`}
value={safeItem[typeField] || ''}
onChange={(e) => handleTypeChange(index, e.target.value)}
className="form-control"
>
{typeOptions.map((type) => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
</div>
{/* Render fields based on the selected type */}
{fieldGroup && fieldGroup.fields && (
<FormFieldDefinition
fields={fieldGroup.fields}
values={parseConfig(item)}
onChange={(key, value) => handleConfigChange(index, key, value)}
idPrefix={`${itemType}-${index}-`}
/>
)}
</div>
);
};
return (
<div className="config-container">
{items && items.map((item, index) => (
renderItemForm(item, index)
))}
<button
type="button"
className="add-btn"
onClick={onAdd}
>
<i className="fas fa-plus"></i> {addButtonText}
</button>
</div>
);
};
export default ConfigForm;

View File

@@ -1,137 +1,48 @@
import { useState } from 'react'; import React from 'react';
import ConfigForm from './ConfigForm';
// 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';
/** /**
* ConnectorForm component * ConnectorForm component
* Provides specific form templates for different connector types * Renders connector configuration forms based on field group metadata
*/ */
function ConnectorForm({ function ConnectorForm({
connectors = [], connectors = [],
onAddConnector, onAddConnector,
onRemoveConnector, onRemoveConnector,
onConnectorNameChange, onConnectorNameChange,
onConnectorConfigChange
}) {
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 (
<div key={index} className="connector-item mb-4">
<div className="connector-header">
<h4>Connector #{index + 1}</h4>
<button
type="button"
className="remove-btn"
onClick={() => onRemoveConnector(index)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="connector-type mb-3">
<label htmlFor={`connectorName${index}`}>Connector Type</label>
<select
id={`connectorName${index}`}
value={safeConnector.type || ''}
onChange={(e) => onConnectorNameChange(index, e.target.value)}
className="form-control"
>
<option value="">Select a connector type</option>
<option value="telegram">Telegram</option>
<option value="slack">Slack</option>
<option value="discord">Discord</option>
<option value="github-issues">GitHub Issues</option>
<option value="github-prs">GitHub PRs</option>
<option value="irc">IRC</option>
<option value="twitter">Twitter</option>
<option value="custom">Custom</option>
</select>
</div>
{/* Render specific connector template based on type */}
{renderConnectorTemplate(safeConnector, index)}
</div>
);
};
// 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, onConnectorConfigChange,
getConfigValue fieldGroups = []
}; }) {
// Debug logging
console.log('ConnectorForm:', { connectors, fieldGroups });
switch (connectorType) { // Handle connector change
case 'telegram': const handleConnectorChange = (index, updatedConnector) => {
return <TelegramConnector {...connectorProps} />; console.log('Connector change:', { index, updatedConnector });
case 'slack': if (updatedConnector.type !== connectors[index].type) {
return <SlackConnector {...connectorProps} />; onConnectorNameChange(index, updatedConnector.type);
case 'discord': } else {
return <DiscordConnector {...connectorProps} />; onConnectorConfigChange(index, updatedConnector.config);
case 'github-issues':
return <GithubIssuesConnector {...connectorProps} />;
case 'github-prs':
return <GithubPRsConnector {...connectorProps} />;
case 'irc':
return <IRCConnector {...connectorProps} />;
case 'twitter':
return <TwitterConnector {...connectorProps} />;
default:
return <FallbackConnector {...connectorProps} />;
} }
}; };
// Helper function to safely get config values // Handle adding a new connector
const getConfigValue = (connector, key, defaultValue = '') => { const handleAddConnector = () => {
if (!connector || !connector.config) return defaultValue; console.log('Adding new connector');
onAddConnector();
// 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;
}; };
return ( return (
<div className="connectors-container"> <ConfigForm
{connectors && connectors.map((connector, index) => ( items={connectors}
renderConnectorForm(connector, index) fieldGroups={fieldGroups}
))} onChange={handleConnectorChange}
onRemove={onRemoveConnector}
<button onAdd={handleAddConnector}
type="button" itemType="connector"
className="add-btn" typeField="type"
onClick={onAddConnector} addButtonText="Add Connector"
> />
<i className="fas fa-plus"></i> Add Connector
</button>
</div>
); );
} }

View File

@@ -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 (
<div className="action-template">
<FormFieldDefinition
fields={fields}
values={currentValues}
onChange={handleFieldChange}
idPrefix={`action${index}_`}
/>
</div>
);
};
export default BaseAction;

View File

@@ -1,16 +0,0 @@
import React from 'react';
/**
* FallbackAction component for actions without specific configuration
*/
const FallbackAction = ({ index, onActionConfigChange, getConfigValue }) => {
return (
<div className="fallback-action">
<p className="text-muted">
This action doesn't require any additional configuration.
</p>
</div>
);
};
export default FallbackAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GenerateImageAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueCloserAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueCommenterAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueLabelerAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssueOpenerAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubRepositoryAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default SendMailAction;

View File

@@ -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 (
<BaseAction
index={index}
onActionConfigChange={onActionConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default TwitterPostAction;

View File

@@ -4,7 +4,7 @@ import ActionForm from '../ActionForm';
/** /**
* ActionsSection component for the agent form * ActionsSection component for the agent form
*/ */
const ActionsSection = ({ formData, setFormData }) => { const ActionsSection = ({ formData, setFormData, metadata }) => {
// Handle action change // Handle action change
const handleActionChange = (index, updatedAction) => { const handleActionChange = (index, updatedAction) => {
const updatedActions = [...(formData.actions || [])]; const updatedActions = [...(formData.actions || [])];
@@ -47,6 +47,7 @@ const ActionsSection = ({ formData, setFormData }) => {
onChange={handleActionChange} onChange={handleActionChange}
onRemove={handleActionRemove} onRemove={handleActionRemove}
onAdd={handleAddAction} onAdd={handleAddAction}
fieldGroups={metadata?.actions || []}
/> />
</div> </div>
); );

View File

@@ -3,53 +3,20 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/** /**
* Advanced Settings section of the agent form * 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 }) => { const AdvancedSettingsSection = ({ formData, handleInputChange, metadata }) => {
// Define field definitions for Advanced Settings section // Get fields from metadata
const fields = [ const fields = metadata?.AdvancedSettingsSection || [];
{
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)',
},
];
// Handle field value changes // Handle field value changes
const handleFieldChange = (name, value) => { const handleFieldChange = (name, value) => {
// For checkboxes, convert string 'true'/'false' to boolean const field = fields.find(f => f.name === name);
if (['autonomous', 'verbose', 'allow_code_execution'].includes(name)) { if (field && field.type === 'checkbox') {
handleInputChange({ handleInputChange({
target: { target: {
name, name,

View File

@@ -3,54 +3,37 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/** /**
* Basic Information section of the agent form * 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 // In group form context, we hide the basic info section entirely
if (isGroupForm) { if (isGroupForm) {
return null; return null;
} }
// Define field definitions for Basic Information section // Get fields from metadata and apply any client-side overrides
const fields = [ const fields = metadata?.BasicInfoSection?.map(field => {
{ // Special case for name field in edit mode
name: 'name', if (field.name === 'name' && isEdit) {
label: 'Name', return {
type: 'text', ...field,
defaultValue: '', disabled: true,
required: true, helpText: 'Agent name cannot be changed after creation'
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,
} }
]; return field;
}) || [];
// Handle field value changes // Handle field value changes
const handleFieldChange = (name, value) => { const handleFieldChange = (name, value) => {
// For checkboxes, convert string 'true'/'false' to boolean const field = fields.find(f => f.name === name);
if (name === 'random_identity' || name === 'hud') { if (field && field.type === 'checkbox') {
handleInputChange({ handleInputChange({
target: { target: {
name, name,

View File

@@ -9,7 +9,8 @@ const ConnectorsSection = ({
handleAddConnector, handleAddConnector,
handleRemoveConnector, handleRemoveConnector,
handleConnectorNameChange, handleConnectorNameChange,
handleConnectorConfigChange handleConnectorConfigChange,
metadata
}) => { }) => {
return ( return (
<div id="connectors-section"> <div id="connectors-section">
@@ -24,6 +25,7 @@ const ConnectorsSection = ({
onRemoveConnector={handleRemoveConnector} onRemoveConnector={handleRemoveConnector}
onConnectorNameChange={handleConnectorNameChange} onConnectorNameChange={handleConnectorNameChange}
onConnectorConfigChange={handleConnectorConfigChange} onConnectorConfigChange={handleConnectorConfigChange}
fieldGroups={metadata?.connectors || []}
/> />
</div> </div>
); );

View File

@@ -3,53 +3,35 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/** /**
* Memory Settings section of the agent form * 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 }) => { const MemorySettingsSection = ({ formData, handleInputChange, metadata }) => {
// Define field definitions for Memory Settings section // Get fields from metadata
const fields = [ const fields = metadata?.MemorySettingsSection || [];
{
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',
},
];
// Handle field value changes // Handle field value changes
const handleFieldChange = (name, value) => { const handleFieldChange = (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({ handleInputChange({
target: { target: {
name, name,
value value
} }
}); });
}
}; };
return ( return (

View File

@@ -3,60 +3,35 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/** /**
* Model Settings section of the agent form * 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 }) => { const ModelSettingsSection = ({ formData, handleInputChange, metadata }) => {
// Define field definitions for Model Settings section // Get fields from metadata
const fields = [ const fields = metadata?.ModelSettingsSection || [];
{
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,
},
];
// Handle field value changes // Handle field value changes
const handleFieldChange = (name, value) => { const handleFieldChange = (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({ handleInputChange({
target: { target: {
name, name,
value value
} }
}); });
}
}; };
return ( return (

View File

@@ -3,64 +3,47 @@ import FormFieldDefinition from '../common/FormFieldDefinition';
/** /**
* Prompts & Goals section of the agent form * 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 }) => { const PromptsGoalsSection = ({ formData, handleInputChange, isGroupForm, metadata }) => {
// Define field definitions for Prompts & Goals section // Get fields based on metadata and form context
const getFields = () => { const getFields = () => {
// Base fields that are always shown if (!metadata?.PromptsGoalsSection) {
const baseFields = [ return [];
{
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
];
} }
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 // Handle field value changes
const handleFieldChange = (name, value) => { const handleFieldChange = (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({ handleInputChange({
target: { target: {
name, name,
value value
} }
}); });
}
}; };
return ( return (

View File

@@ -24,6 +24,9 @@ const FormField = ({
helpText = '', helpText = '',
options = [], options = [],
required = false, required = false,
min = 0,
max = 2**31,
step = 1,
}) => { }) => {
// Create label with required indicator // Create label with required indicator
const labelWithIndicator = required ? ( const labelWithIndicator = required ? (
@@ -86,6 +89,25 @@ const FormField = ({
{helpText && <small className="form-text text-muted">{helpText}</small>} {helpText && <small className="form-text text-muted">{helpText}</small>}
</> </>
); );
case 'number':
return (
<>
<label htmlFor={id}>{labelWithIndicator}</label>
<input
type="number"
id={id}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
className="form-control"
placeholder={placeholder}
required={required}
min={min}
max={max}
step={step}
/>
{helpText && <small className="form-text text-muted">{helpText}</small>}
</>
);
default: default:
return ( return (
<> <>

View File

@@ -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 (
<div className="connector-template">
<FormFieldDefinition
fields={fields}
values={currentValues}
onChange={handleFieldChange}
idPrefix={`connector${index}_`}
/>
</div>
);
};
export default BaseConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default DiscordConnector;

View File

@@ -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 (
<div className="connector-template">
{/* Individual field inputs */}
{parsedConfig && Object.entries(parsedConfig).map(([key, value]) => (
<div key={key} className="form-group mb-3">
<label htmlFor={`connector-${index}-${key}`}>{key}</label>
<input
type="text"
id={`connector-${index}-${key}`}
className="form-control"
value={value}
onChange={(e) => onConnectorConfigChange(index, key, e.target.value)}
/>
</div>
))}
{/* Add custom configuration field */}
<div className="add-config-field mt-4">
<h5>Add Custom Configuration Field</h5>
<div className="input-group mb-3">
<input
type="text"
placeholder="New config key"
className="form-control"
value={newConfigKey}
onChange={(e) => setNewConfigKey(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddCustomField()}
/>
<button
type="button"
className="btn btn-outline-primary"
onClick={handleAddCustomField}
>
<i className="fas fa-plus"></i> Add Field
</button>
</div>
</div>
</div>
);
};
export default FallbackConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubIssuesConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default GithubPRsConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default IRCConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default SlackConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default TelegramConnector;

View File

@@ -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 (
<BaseConnector
connector={connector}
index={index}
onConnectorConfigChange={onConnectorConfigChange}
getConfigValue={getConfigValue}
fields={fields}
/>
);
};
export default TwitterConnector;

View File

@@ -1,12 +1,14 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams, useOutletContext, useNavigate } from 'react-router-dom'; import { useParams, useOutletContext, useNavigate } from 'react-router-dom';
import { useAgent } from '../hooks/useAgent'; import { useAgent } from '../hooks/useAgent';
import { agentApi } from '../utils/api';
import AgentForm from '../components/AgentForm'; import AgentForm from '../components/AgentForm';
function AgentSettings() { function AgentSettings() {
const { name } = useParams(); const { name } = useParams();
const { showToast } = useOutletContext(); const { showToast } = useOutletContext();
const navigate = useNavigate(); const navigate = useNavigate();
const [metadata, setMetadata] = useState(null);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
description: '', description: '',
@@ -47,9 +49,28 @@ function AgentSettings() {
deleteAgent deleteAgent
} = useAgent(name); } = 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 // Load agent data when component mounts
useEffect(() => { useEffect(() => {
if (agent) { if (agent) {
// Set form data from agent config
setFormData({ setFormData({
...formData, ...formData,
...agent, ...agent,
@@ -162,6 +183,7 @@ function AgentSettings() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
loading={loading} loading={loading}
submitButtonText="Save Changes" submitButtonText="Save Changes"
metadata={metadata}
/> />
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate, useOutletContext } from 'react-router-dom'; import { useNavigate, useOutletContext } from 'react-router-dom';
import { agentApi } from '../utils/api'; import { agentApi } from '../utils/api';
import AgentForm from '../components/AgentForm'; import AgentForm from '../components/AgentForm';
@@ -7,6 +7,7 @@ function CreateAgent() {
const navigate = useNavigate(); const navigate = useNavigate();
const { showToast } = useOutletContext(); const { showToast } = useOutletContext();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [metadata, setMetadata] = useState(null);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
description: '', description: '',
@@ -37,6 +38,24 @@ function CreateAgent() {
avatar_style: 'default', 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 // Handle form submission
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
@@ -80,6 +99,7 @@ function CreateAgent() {
loading={loading} loading={loading}
submitButtonText="Create Agent" submitButtonText="Create Agent"
isEdit={false} isEdit={false}
metadata={metadata}
/> />
</div> </div>
</div> </div>

View File

@@ -42,6 +42,51 @@ export const agentApi = {
return handleResponse(response); 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 // Create a new agent
createAgent: async (config) => { createAgent: async (config) => {
const response = await fetch(buildUrl(API_CONFIG.endpoints.createAgent), { const response = await fetch(buildUrl(API_CONFIG.endpoints.createAgent), {

View File

@@ -20,6 +20,7 @@ export const API_CONFIG = {
// Agent endpoints // Agent endpoints
agents: '/api/agents', agents: '/api/agents',
agentConfig: (name) => `/api/agent/${name}/config`, agentConfig: (name) => `/api/agent/${name}/config`,
agentConfigMetadata: '/api/agent/config/metadata',
createAgent: '/create', createAgent: '/create',
deleteAgent: (name) => `/delete/${name}`, deleteAgent: (name) => `/delete/${name}`,
pauseAgent: (name) => `/pause/${name}`, pauseAgent: (name) => `/pause/${name}`,

View File

@@ -172,6 +172,9 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
webapp.Get("/api/agent/:name/config", app.GetAgentConfig(pool)) webapp.Get("/api/agent/:name/config", app.GetAgentConfig(pool))
webapp.Put("/api/agent/:name/config", app.UpdateAgentConfig(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.Post("/action/:name/run", app.ExecuteAction(pool))
webapp.Get("/actions", app.ListActions()) webapp.Get("/actions", app.ListActions())