package agent import ( "encoding/json" "fmt" "os" "path" "regexp" "devops.lty.name/luo/chatai/internal/config" "devops.lty.name/luo/chatai/internal/log" "devops.lty.name/luo/chatai/web/assets" "github.com/google/uuid" ) type AgentConf struct { // AgentID is the unique identifier for the agent. Must match pattern `^[a-z0-9-_]{2,80}$`. AgentID string `json:"id"` // AgentName is the human-readable name for the agent. AgentName string `json:"name"` // AgentDescription is a short description of the agent. AgentDescription string `json:"desc"` // FooterHTML is the HTML content to be displayed in the footer of the agent chat page. FooterHTML string `json:"footer"` // PrimaryColor is the primary color for the agent chat page. PrimaryColor string `json:"primaryColor"` // SecondaryColor is the secondary color for the agent chat page. SecondaryColor string `json:"secondaryColor"` // AccentColor is the accent color for the agent chat page. AccentColor string `json:"accentColor"` Dir string `json:"-"` } type Agent struct { AgentConf Avater []byte SystemPrompt string InitialPrompt string } func (a *AgentConf) ReadAgent() *Agent { agent := &Agent{ AgentConf: *a, Avater: assets.DefaultAgentAvater, SystemPrompt: "你是一个聊天机器人。你应当尽力帮助用户。", InitialPrompt: "你好!", } avatar, err := os.ReadFile(path.Join(a.Dir, "avatar.webp")) if err == nil { agent.Avater = avatar } else { log.T("agent").Errf("Failed to load avatar for agent '%s': %v", a.AgentID, err) } systemPrompt, err := os.ReadFile(path.Join(a.Dir, "system_prompt.txt")) if err == nil { agent.SystemPrompt = string(systemPrompt) } else { log.T("agent").Errf("Failed to load system prompt for agent '%s': %v", a.AgentID, err) } initialPrompt, err := os.ReadFile(path.Join(a.Dir, "initial_prompt.txt")) if err == nil { agent.InitialPrompt = string(initialPrompt) } else { log.T("agent").Errf("Failed to load initial prompt for agent '%s': %v", a.AgentID, err) } return agent } func LoadAgentFromFile(agentJSONPath string) (*AgentConf, error) { conf := &AgentConf{ AgentID: uuid.New().String(), AgentName: config.DefaultAgentName, AgentDescription: config.DefaultAgentDescription, PrimaryColor: config.DefaultAgentPrimaryColor, SecondaryColor: config.DefaultAgentSecondaryColor, AccentColor: config.DefaultAgentAccentColor, Dir: path.Dir(agentJSONPath), } file, err := os.ReadFile(agentJSONPath) if err != nil { return nil, err } err = json.Unmarshal(file, conf) if err != nil { return nil, err } agentIDPatten := `^[a-z0-9-_]{2,80}$` if !regexp.MustCompile(agentIDPatten).MatchString(conf.AgentID) { return nil, fmt.Errorf("invalid agent ID: '%s' (require pattern: '%s')", conf.AgentID, agentIDPatten) } return conf, nil } func LoadAllAgentsFromDir(agentsDir string) (map[string]*AgentConf, error) { subDirs, err := os.ReadDir(agentsDir) if err != nil { return nil, fmt.Errorf("failed to read agents directory '%s': %w", agentsDir, err) } agents := map[string]*AgentConf{} for _, file := range subDirs { if !file.IsDir() { continue } agentPath := path.Join(agentsDir, file.Name(), "agent.json") agent, err := LoadAgentFromFile(agentPath) if err != nil { return nil, fmt.Errorf("failed to load agent from file '%s': %w", agentPath, err) } if agent.AgentID != file.Name() { return nil, fmt.Errorf("Agent ID '%s' does not match directory name '%s'", agent.AgentID, file.Name()) } agents[agent.AgentID] = agent } return agents, nil }