This reverts commit d7cfa7f0b2.
This commit is contained in:
committed by
GitHub
parent
1e5b3f501f
commit
d54abc3ed0
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,7 +2,5 @@ models/
|
||||
data/
|
||||
pool
|
||||
uploads/
|
||||
volumes/
|
||||
local-agent-framework
|
||||
localagent
|
||||
LocalAgent
|
||||
localagent
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
)
|
||||
|
||||
type ConnectorConfig struct {
|
||||
@@ -63,6 +62,5 @@ type AgentConfig struct {
|
||||
type Connector interface {
|
||||
AgentResultCallback() func(state agent.ActionState)
|
||||
AgentReasoningCallback() func(state agent.ActionCurrentState) bool
|
||||
ConfigForm() metaform.Form
|
||||
Start(a *agent.Agent)
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package metaform
|
||||
|
||||
// Option represents a selectable option for FieldOption type
|
||||
type Option struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
type FieldKind string
|
||||
|
||||
const (
|
||||
FieldString FieldKind = "string"
|
||||
FieldNumber FieldKind = "number"
|
||||
FieldOptions FieldKind = "options"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
Kind FieldKind `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Required bool `json:"required"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Options []Option `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
type Form struct {
|
||||
Fields []Field
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
)
|
||||
|
||||
@@ -38,27 +37,6 @@ func (d *Discord) AgentReasoningCallback() func(state agent.ActionCurrentState)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discord) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "token",
|
||||
Label: "Bot Token",
|
||||
Required: true,
|
||||
Placeholder: "Your Discord bot token",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "defaultChannel",
|
||||
Label: "Default Channel",
|
||||
Required: true,
|
||||
Placeholder: "The default channel for the bot to join",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discord) Start(a *agent.Agent) {
|
||||
|
||||
Token := d.token
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
@@ -64,51 +63,6 @@ func (g *GithubIssues) AgentReasoningCallback() func(state agent.ActionCurrentSt
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GithubIssues) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "token",
|
||||
Label: "GitHub Token",
|
||||
Required: true,
|
||||
Placeholder: "Your GitHub personal access token",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "owner",
|
||||
Label: "Repository Owner",
|
||||
Required: true,
|
||||
Placeholder: "username or organization",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "repository",
|
||||
Label: "Repository Name",
|
||||
Required: true,
|
||||
Placeholder: "repo-name",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldOptions,
|
||||
Name: "replyIfNoReplies",
|
||||
Label: "Reply Behavior",
|
||||
Required: true,
|
||||
Options: []metaform.Option{
|
||||
{Value: "true", Label: "Reply only to issues with no comments"},
|
||||
{Value: "false", Label: "Reply to all issues"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "pollInterval",
|
||||
Label: "Poll Interval",
|
||||
Required: false,
|
||||
Placeholder: "10m",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GithubIssues) Start(a *agent.Agent) {
|
||||
// Start the connector
|
||||
g.agent = a
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
@@ -64,51 +63,6 @@ func (g *GithubPRs) AgentReasoningCallback() func(state agent.ActionCurrentState
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GithubPRs) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "token",
|
||||
Label: "GitHub Token",
|
||||
Required: true,
|
||||
Placeholder: "Your GitHub personal access token",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "owner",
|
||||
Label: "Repository Owner",
|
||||
Required: true,
|
||||
Placeholder: "username or organization",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "repository",
|
||||
Label: "Repository Name",
|
||||
Required: true,
|
||||
Placeholder: "repo-name",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldOptions,
|
||||
Name: "replyIfNoReplies",
|
||||
Label: "Reply Behavior",
|
||||
Required: true,
|
||||
Options: []metaform.Option{
|
||||
{Value: "true", Label: "Reply only to PRs with no comments"},
|
||||
{Value: "false", Label: "Reply to all PRs"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "pollInterval",
|
||||
Label: "Poll Interval",
|
||||
Required: true,
|
||||
Placeholder: "10m",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GithubPRs) Start(a *agent.Agent) {
|
||||
// Start the connector
|
||||
g.agent = a
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
"github.com/mudler/LocalAgent/services/actions"
|
||||
irc "github.com/thoj/go-ircevent"
|
||||
@@ -44,51 +43,6 @@ func (i *IRC) AgentReasoningCallback() func(state agent.ActionCurrentState) bool
|
||||
}
|
||||
}
|
||||
|
||||
func (i *IRC) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "server",
|
||||
Label: "Server",
|
||||
Required: true,
|
||||
Placeholder: "chat.freenode.net",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "port",
|
||||
Label: "Port",
|
||||
Required: true,
|
||||
Placeholder: "6667",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "nickname",
|
||||
Label: "Nickname",
|
||||
Required: true,
|
||||
Placeholder: "LocalAgentBot",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "channel",
|
||||
Label: "Channel",
|
||||
Required: true,
|
||||
Placeholder: "#general",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldOptions,
|
||||
Name: "alwaysReply",
|
||||
Label: "Always Reply",
|
||||
Required: true,
|
||||
Options: []metaform.Option{
|
||||
{Value: "false", Label: "Only when mentioned"},
|
||||
{Value: "true", Label: "Reply to all messages"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// cleanUpUsernameFromMessage removes the bot's nickname from the message
|
||||
func cleanUpMessage(message string, nickname string) string {
|
||||
cleaned := strings.ReplaceAll(message, nickname+":", "")
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
"github.com/mudler/LocalAgent/services/actions"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
@@ -53,44 +52,6 @@ func (t *Slack) AgentReasoningCallback() func(state agent.ActionCurrentState) bo
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Slack) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "appToken",
|
||||
Label: "App Token",
|
||||
Required: true,
|
||||
Placeholder: "xapp-...",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "botToken",
|
||||
Label: "Bot Token",
|
||||
Required: true,
|
||||
Placeholder: "xoxb-...",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "channelID",
|
||||
Label: "Channel ID",
|
||||
Required: false,
|
||||
Placeholder: "C12345678",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldOptions,
|
||||
Name: "alwaysReply",
|
||||
Label: "Always Reply",
|
||||
Required: false,
|
||||
Options: []metaform.Option{
|
||||
{Value: "false", Label: "Only when mentioned"},
|
||||
{Value: "true", Label: "Reply to all messages"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cleanUpUsernameFromMessage(message string, b *slack.AuthTestResponse) string {
|
||||
cleaned := strings.ReplaceAll(message, "<@"+b.UserID+">", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, "<@"+b.BotID+">", "")
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/go-telegram/bot"
|
||||
"github.com/go-telegram/bot/models"
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
)
|
||||
|
||||
type Telegram struct {
|
||||
@@ -38,20 +37,6 @@ func (t *Telegram) AgentReasoningCallback() func(state agent.ActionCurrentState)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Telegram) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "token",
|
||||
Label: "Bot Token",
|
||||
Required: true,
|
||||
Placeholder: "Your Telegram bot token from @BotFather",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Telegram) Start(a *agent.Agent) {
|
||||
ctx, cancel := signal.NotifyContext(a.Context(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os/signal"
|
||||
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
"github.com/mudler/LocalAgent/services/connectors/twitter"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
@@ -33,37 +32,6 @@ func (t *Twitter) AgentReasoningCallback() func(state agent.ActionCurrentState)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Twitter) ConfigForm() metaform.Form {
|
||||
return metaform.Form{
|
||||
Fields: []metaform.Field{
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "token",
|
||||
Label: "Token",
|
||||
Required: true,
|
||||
Placeholder: "elmo-...",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldString,
|
||||
Name: "botUsername",
|
||||
Label: "Bot Username",
|
||||
Required: true,
|
||||
Placeholder: "Chocolate Pineapple",
|
||||
},
|
||||
{
|
||||
Kind: metaform.FieldOptions,
|
||||
Name: "noCharacterLimit",
|
||||
Label: "No Character Limit",
|
||||
Required: false,
|
||||
Options: []metaform.Option{
|
||||
{Value: "false", Label: ""},
|
||||
{Value: "true", Label: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewTwitterConnector(config map[string]string) (*Twitter, error) {
|
||||
return &Twitter{
|
||||
token: config["token"],
|
||||
|
||||
41
webui/app.go
41
webui/app.go
@@ -10,13 +10,10 @@ import (
|
||||
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
"github.com/mudler/LocalAgent/webui/types"
|
||||
"github.com/mudler/LocalAgent/services"
|
||||
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
"github.com/mudler/LocalAgent/core/sse"
|
||||
"github.com/mudler/LocalAgent/core/state"
|
||||
"github.com/mudler/LocalAgent/pkg/metaform"
|
||||
"github.com/mudler/LocalAgent/services/connectors"
|
||||
|
||||
"github.com/donseba/go-htmx"
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
@@ -353,41 +350,3 @@ func (a *App) Responses(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||
return c.JSON(response)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) GetConnectorForm(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||
return func(c *fiber.Ctx) error {
|
||||
connectorType := c.Params("type")
|
||||
|
||||
// Create a temporary connector to get its form metadata
|
||||
var form metaform.Form
|
||||
|
||||
switch connectorType {
|
||||
case services.ConnectorTelegram:
|
||||
form = (&connectors.Telegram{}).ConfigForm()
|
||||
case services.ConnectorSlack:
|
||||
form = (&connectors.Slack{}).ConfigForm()
|
||||
case services.ConnectorDiscord:
|
||||
form = (&connectors.Discord{}).ConfigForm()
|
||||
case services.ConnectorGithubIssues:
|
||||
form = (&connectors.GithubIssues{}).ConfigForm()
|
||||
case services.ConnectorGithubPRs:
|
||||
form = (&connectors.GithubPRs{}).ConfigForm()
|
||||
case services.ConnectorIRC:
|
||||
form = (&connectors.IRC{}).ConfigForm()
|
||||
case services.ConnectorTwitter:
|
||||
form = (&connectors.Twitter{}).ConfigForm()
|
||||
default:
|
||||
return c.Status(404).JSON(fiber.Map{
|
||||
"error": "Connector type not found",
|
||||
})
|
||||
}
|
||||
|
||||
// Create a data structure to pass both the form and the connector type
|
||||
data := fiber.Map{
|
||||
"Form": form,
|
||||
"Type": connectorType,
|
||||
}
|
||||
|
||||
return c.Render("views/partials/metaform", data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,15 @@
|
||||
// Common utility functions for agent forms
|
||||
const AgentFormUtils = {
|
||||
// Add dynamic component based on template
|
||||
addDynamicComponent: function(sectionId, templateFunction, options = {}) {
|
||||
addDynamicComponent: function(sectionId, templateFunction, dataItems) {
|
||||
const section = document.getElementById(sectionId);
|
||||
if (!section) return;
|
||||
const newIndex = section.getElementsByClassName(dataItems.className).length;
|
||||
|
||||
const index = section.getElementsByClassName(options.className || 'dynamic-component').length;
|
||||
const templateData = { index, ...options };
|
||||
// Generate HTML from template function
|
||||
const newHtml = templateFunction(newIndex, dataItems);
|
||||
|
||||
// Create a new element from the template
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = templateFunction(index, templateData);
|
||||
const newElement = tempDiv.firstElementChild;
|
||||
|
||||
// Add the new element to the section
|
||||
section.appendChild(newElement);
|
||||
|
||||
// If it's a connector, add event listener for type change
|
||||
if (options.className === 'connector') {
|
||||
const newIndex = index;
|
||||
const connectorType = document.getElementById(`connectorType${newIndex}`);
|
||||
if (connectorType) {
|
||||
// Add event listener for future changes
|
||||
connectorType.addEventListener('change', function() {
|
||||
loadConnectorForm(newIndex, this.value, null);
|
||||
});
|
||||
|
||||
// If a connector type is already selected (default value), load its form immediately
|
||||
if (connectorType.value) {
|
||||
loadConnectorForm(newIndex, connectorType.value, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newElement;
|
||||
// Add to DOM
|
||||
section.insertAdjacentHTML('beforeend', newHtml);
|
||||
},
|
||||
|
||||
// Process form data into JSON structure
|
||||
@@ -84,57 +60,32 @@ const AgentFormUtils = {
|
||||
const connectorsElements = document.getElementsByClassName('connector');
|
||||
|
||||
for (let i = 0; i < connectorsElements.length; i++) {
|
||||
const typeSelect = document.getElementById(`connectorType${i}`);
|
||||
const typeField = document.getElementById(`connectorType${i}`);
|
||||
const configField = document.getElementById(`connectorConfig${i}`);
|
||||
|
||||
if (typeSelect) {
|
||||
const connectorType = typeSelect.value;
|
||||
const configContainer = document.getElementById(`connectorConfigContainer${i}`);
|
||||
|
||||
// Only process if we have a metaform
|
||||
if (configContainer && configContainer.querySelector('.metaform')) {
|
||||
try {
|
||||
// Get all form fields
|
||||
const fields = configContainer.querySelectorAll('.connector-field');
|
||||
let configObj = {};
|
||||
|
||||
// Process each field based on its type
|
||||
fields.forEach(field => {
|
||||
const fieldName = field.dataset.fieldName;
|
||||
const fieldType = field.dataset.fieldType;
|
||||
|
||||
// Convert value based on field type
|
||||
let value = field.value;
|
||||
if (fieldType === 'number' && value !== '') {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
|
||||
configObj[fieldName] = value;
|
||||
});
|
||||
|
||||
// Add the connector to the list
|
||||
connectors.push({
|
||||
type: connectorType,
|
||||
config: JSON.stringify(configObj)
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error processing connector ${i} form:`, err);
|
||||
showToast(`Error in connector ${i+1} configuration`, 'error');
|
||||
|
||||
// If button is provided, restore its state
|
||||
if (button) {
|
||||
const originalButtonText = button.getAttribute('data-original-text');
|
||||
button.innerHTML = originalButtonText;
|
||||
button.disabled = false;
|
||||
}
|
||||
|
||||
return null; // Indicate validation error
|
||||
}
|
||||
} else {
|
||||
// If no form is loaded, create an empty config
|
||||
if (typeField && configField) {
|
||||
try {
|
||||
// Validate JSON but send as string
|
||||
const configValue = configField.value.trim() || '{}';
|
||||
// Parse to validate but don't use the parsed object
|
||||
JSON.parse(configValue);
|
||||
|
||||
connectors.push({
|
||||
type: connectorType,
|
||||
config: '{}'
|
||||
type: typeField.value,
|
||||
config: configValue // Send the raw string, not parsed JSON
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error parsing connector ${i} config:`, err);
|
||||
showToast(`Error in connector ${i+1} configuration: Invalid JSON`, 'error');
|
||||
|
||||
// If button is provided, restore its state
|
||||
if (button) {
|
||||
const originalButtonText = button.getAttribute('data-original-text');
|
||||
button.innerHTML = originalButtonText;
|
||||
button.disabled = false;
|
||||
}
|
||||
|
||||
return null; // Indicate validation error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,16 +199,14 @@ const AgentFormUtils = {
|
||||
const parsed = JSON.parse(configValue);
|
||||
configElement.value = JSON.stringify(parsed, null, 2);
|
||||
} catch (e) {
|
||||
// If parsing fails, use the raw string
|
||||
configElement.value = configValue;
|
||||
console.warn("Failed to parse config JSON string:", e);
|
||||
configElement.value = configValue; // Keep as is if parsing fails
|
||||
}
|
||||
}
|
||||
// If it's already an object, stringify it
|
||||
else if (typeof configValue === 'object' && configValue !== null) {
|
||||
} else if (configValue !== undefined && configValue !== null) {
|
||||
// Direct object, just stringify with formatting
|
||||
configElement.value = JSON.stringify(configValue, null, 2);
|
||||
}
|
||||
// Default to empty object
|
||||
else {
|
||||
} else {
|
||||
// Default to empty object
|
||||
configElement.value = '{}';
|
||||
}
|
||||
},
|
||||
@@ -265,90 +214,21 @@ const AgentFormUtils = {
|
||||
// Helper function to set select value (with fallback if option doesn't exist)
|
||||
setSelectValue: function(selectElement, value) {
|
||||
// Check if the option exists
|
||||
let optionExists = false;
|
||||
for (let i = 0; i < selectElement.options.length; i++) {
|
||||
if (selectElement.options[i].value === value) {
|
||||
optionExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const optionExists = Array.from(selectElement.options).some(option => option.value === value);
|
||||
|
||||
// Set the value if the option exists
|
||||
if (optionExists) {
|
||||
selectElement.value = value;
|
||||
} else if (selectElement.options.length > 0) {
|
||||
// Otherwise select the first option
|
||||
selectElement.selectedIndex = 0;
|
||||
} else if (value) {
|
||||
// If value is provided but option doesn't exist, create a new option
|
||||
const newOption = document.createElement('option');
|
||||
newOption.value = value;
|
||||
newOption.text = value + ' (custom)';
|
||||
selectElement.add(newOption);
|
||||
selectElement.value = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to load connector form based on type
|
||||
function loadConnectorForm(index, connectorType, configData) {
|
||||
if (!connectorType) return;
|
||||
|
||||
const configContainer = document.getElementById(`connectorConfigContainer${index}`);
|
||||
if (!configContainer) return;
|
||||
|
||||
// Show loading indicator
|
||||
configContainer.innerHTML = '<div class="loading-spinner">Loading form...</div>';
|
||||
|
||||
// Fetch the form for the selected connector type
|
||||
fetch(`/settings/connector/form/${connectorType}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load connector form');
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(html => {
|
||||
// Replace the container content with the form
|
||||
configContainer.innerHTML = html;
|
||||
|
||||
// Store the connector type as a data attribute on the form
|
||||
const metaform = configContainer.querySelector('.metaform');
|
||||
if (metaform) {
|
||||
metaform.setAttribute('data-connector-type', connectorType);
|
||||
|
||||
// Add a hidden input to store the connector type
|
||||
const hiddenInput = document.createElement('input');
|
||||
hiddenInput.type = 'hidden';
|
||||
hiddenInput.name = 'connector-type';
|
||||
hiddenInput.value = connectorType;
|
||||
metaform.appendChild(hiddenInput);
|
||||
|
||||
// If we have config data, populate the form fields
|
||||
if (configData) {
|
||||
try {
|
||||
// Parse the config JSON
|
||||
const parsedConfig = JSON.parse(configData);
|
||||
|
||||
// Find all form fields
|
||||
const fields = metaform.querySelectorAll('.connector-field');
|
||||
|
||||
// Populate each field with the corresponding value from the config
|
||||
fields.forEach(field => {
|
||||
const fieldName = field.dataset.fieldName;
|
||||
if (parsedConfig[fieldName] !== undefined) {
|
||||
field.value = parsedConfig[fieldName];
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`Failed to populate connector form for ${connectorType}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading connector form:', error);
|
||||
configContainer.innerHTML = `
|
||||
<div class="error-message">
|
||||
<p>Failed to load connector form: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
// HTML Templates for dynamic elements
|
||||
const AgentFormTemplates = {
|
||||
// Connector template
|
||||
@@ -362,10 +242,9 @@ const AgentFormTemplates = {
|
||||
${data.options}
|
||||
</select>
|
||||
</div>
|
||||
<div id="connectorConfigContainer${index}" class="mb-4">
|
||||
<div class="text-center py-4">
|
||||
<p>Select a connector type to load its configuration form.</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="connectorConfig${index}">Connector Config (JSON)</label>
|
||||
<textarea id="connectorConfig${index}" name="connectors[${index}].config" placeholder='{"token":"sk-mg3.."}'>{}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -377,12 +256,12 @@ const AgentFormTemplates = {
|
||||
<div class="mcp_server mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>MCP Server ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="mcpURL${index}">Server URL</label>
|
||||
<input type="text" id="mcpURL${index}" name="mcp_servers[${index}].url" placeholder="https://example.com">
|
||||
<label for="mcpURL${index}">MCP Server URL</label>
|
||||
<input type="text" id="mcpURL${index}" name="mcp_servers[${index}].url" placeholder='https://...'>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="mcpToken${index}">API Token (Optional)</label>
|
||||
<input type="text" id="mcpToken${index}" name="mcp_servers[${index}].token" placeholder="API token">
|
||||
<label for="mcpToken${index}">Bearer Token</label>
|
||||
<input type="text" id="mcpToken${index}" name="mcp_servers[${index}].token" placeholder='Bearer token'>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -394,14 +273,14 @@ const AgentFormTemplates = {
|
||||
<div class="action mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>Action ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="actionsName${index}">Action Type</label>
|
||||
<label for="actionsName${index}">Action</label>
|
||||
<select name="actions[${index}].name" id="actionsName${index}">
|
||||
${data.options}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="actionsConfig${index}">Action Config (JSON)</label>
|
||||
<textarea id="actionsConfig${index}" name="actions[${index}].config" placeholder='{"param":"value"}'>{}</textarea>
|
||||
<textarea id="actionsConfig${index}" name="actions[${index}].config" placeholder='{"results":"5"}'>{}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -413,14 +292,14 @@ const AgentFormTemplates = {
|
||||
<div class="promptBlock mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>Prompt Block ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="promptName${index}">Prompt Block Type</label>
|
||||
<label for="promptName${index}">Block Prompt</label>
|
||||
<select name="promptblocks[${index}].name" id="promptName${index}">
|
||||
${data.options}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="promptConfig${index}">Prompt Block Config (JSON)</label>
|
||||
<textarea id="promptConfig${index}" name="promptblocks[${index}].config" placeholder='{"param":"value"}'>{}</textarea>
|
||||
<label for="promptConfig${index}">Prompt Config (JSON)</label>
|
||||
<textarea id="promptConfig${index}" name="promptblocks[${index}].config" placeholder='{"results":"5"}'>{}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -465,39 +344,25 @@ function initAgentFormCommon(options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
// Add additional CSS for loading spinner and error messages
|
||||
// Add additional CSS for checkbox labels
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.loading-spinner {
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.loading-spinner::after {
|
||||
content: '';
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spinner 1s linear infinite;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff5555;
|
||||
padding: 10px;
|
||||
border: 1px solid #ff5555;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.checkbox-label .checkbox-custom {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
@@ -113,7 +113,6 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
})
|
||||
})
|
||||
|
||||
webapp.Get("/settings/connector/form/:type", app.GetConnectorForm(pool))
|
||||
webapp.Get("/settings/:name", func(c *fiber.Ctx) error {
|
||||
status := false
|
||||
for _, a := range pool.List() {
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
{{/*
|
||||
Metaform Partial Template
|
||||
This template renders a form based on a Form struct from the metaform package.
|
||||
|
||||
Usage:
|
||||
{{template "partials/metaform.html" .}}
|
||||
|
||||
Where . contains Form and Type fields
|
||||
*/}}
|
||||
|
||||
<div class="metaform" data-connector-type="{{.Type}}">
|
||||
{{range .Form.Fields}}
|
||||
<div class="mb-4">
|
||||
<label for="connector_{{.Name}}">{{.Label}}{{if .Required}} <span class="required">*</span>{{end}}</label>
|
||||
|
||||
{{if eq .Kind "string"}}
|
||||
<input type="text"
|
||||
name="connector_field_{{.Name}}"
|
||||
id="connector_{{.Name}}"
|
||||
class="connector-field"
|
||||
data-field-name="{{.Name}}"
|
||||
data-field-type="string"
|
||||
{{if .Placeholder}}placeholder="{{.Placeholder}}"{{end}}
|
||||
{{if .Required}}required{{end}}>
|
||||
|
||||
{{else if eq .Kind "number"}}
|
||||
<input type="number"
|
||||
name="connector_field_{{.Name}}"
|
||||
id="connector_{{.Name}}"
|
||||
class="connector-field"
|
||||
data-field-name="{{.Name}}"
|
||||
data-field-type="number"
|
||||
{{if .Placeholder}}placeholder="{{.Placeholder}}"{{end}}
|
||||
{{if .Required}}required{{end}}>
|
||||
|
||||
{{else if eq .Kind "options"}}
|
||||
<select name="connector_field_{{.Name}}"
|
||||
id="connector_{{.Name}}"
|
||||
class="connector-field"
|
||||
data-field-name="{{.Name}}"
|
||||
data-field-type="options"
|
||||
{{if .Required}}required{{end}}>
|
||||
<option value="">Select an option</option>
|
||||
{{range .Options}}
|
||||
<option value="{{.Value}}">{{.Label}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.metaform .required {
|
||||
color: #ff5555;
|
||||
}
|
||||
|
||||
.metaform input[type="text"],
|
||||
.metaform input[type="number"],
|
||||
.metaform select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.metaform input[type="text"]:focus,
|
||||
.metaform input[type="number"]:focus,
|
||||
.metaform select:focus {
|
||||
border-color: #00b3ff;
|
||||
box-shadow: 0 0 5px rgba(0, 179, 255, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.metaform select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f0f0f0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 16px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.metaform label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #f0f0f0;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -190,16 +190,10 @@
|
||||
updateButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Updating...';
|
||||
updateButton.disabled = true;
|
||||
|
||||
// Get the agent name from the hidden input field
|
||||
const agentName = document.getElementById('name').value;
|
||||
|
||||
// Build a structured data object
|
||||
const formData = new FormData(form);
|
||||
const jsonData = AgentFormUtils.processFormData(formData);
|
||||
|
||||
// Ensure the name is set correctly
|
||||
jsonData.name = agentName;
|
||||
|
||||
// Process special fields
|
||||
jsonData.connectors = AgentFormUtils.processConnectors(updateButton);
|
||||
if (jsonData.connectors === null) return; // Validation failed
|
||||
@@ -215,7 +209,7 @@
|
||||
console.log('Sending data:', jsonData);
|
||||
|
||||
// Send the structured data as JSON
|
||||
fetch(`/api/agent/${agentName}/config`, {
|
||||
fetch(`/api/agent/${jsonData.name}/config`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -366,13 +360,16 @@
|
||||
|
||||
// Find the added connector elements
|
||||
const connectorType = document.getElementById(`connectorType${index}`);
|
||||
const connectorConfig = document.getElementById(`connectorConfig${index}`);
|
||||
|
||||
// Set connector type value
|
||||
// Set values
|
||||
if (connectorType) {
|
||||
AgentFormUtils.setSelectValue(connectorType, connector.type);
|
||||
|
||||
// Load the connector form with the existing config data
|
||||
loadConnectorForm(index, connector.type, connector.config);
|
||||
}
|
||||
|
||||
if (connectorConfig) {
|
||||
// Format the config value
|
||||
AgentFormUtils.formatConfigValue(connectorConfig, connector.config);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user