package main import ( "fmt" "net/url" "strings" ) // configuration holds the plugin settings from the System Console. type configuration struct { OllamaURL string DefaultModel string SystemPrompt string MaxTokens int RateLimitPerMinute int AllowedUserIDs string StopWords string } // getConfiguration returns the current configuration, safe for concurrent use. func (p *Plugin) getConfiguration() *configuration { p.configLock.RLock() defer p.configLock.RUnlock() return p.configuration } // setConfiguration updates the configuration and validates it. func (p *Plugin) setConfiguration(c *configuration) error { p.configLock.Lock() defer p.configLock.Unlock() if c == nil { return fmt.Errorf("nil configuration") } if err := c.validate(); err != nil { return fmt.Errorf("invalid configuration: %w", err) } p.configuration = c return nil } // validate checks that the configuration values are usable. func (c *configuration) validate() error { if c.OllamaURL == "" { return fmt.Errorf("OllamaURL must not be empty") } u, err := url.Parse(c.OllamaURL) if err != nil { return fmt.Errorf("OllamaURL is not a valid URL: %w", err) } if u.Scheme != "http" && u.Scheme != "https" { return fmt.Errorf("OllamaURL must use http or https scheme, got %q", u.Scheme) } if c.DefaultModel == "" { return fmt.Errorf("DefaultModel must not be empty") } if c.MaxTokens < 0 { return fmt.Errorf("MaxTokens must not be negative") } if c.RateLimitPerMinute < 0 { return fmt.Errorf("RateLimitPerMinute must not be negative") } return nil } // OnConfigurationChange is called by the Mattermost server when the plugin // configuration is changed in the System Console. func (p *Plugin) OnConfigurationChange() error { var c configuration // Load from the System Console settings. if err := p.API.LoadPluginConfiguration(&c); err != nil { return fmt.Errorf("failed to load plugin configuration: %w", err) } if err := p.setConfiguration(&c); err != nil { return fmt.Errorf("failed to set configuration: %w", err) } return nil } // allowedUsers returns the set of user IDs allowed to use the plugin. // An empty set means all users are allowed. func (c *configuration) allowedUsers() map[string]bool { if strings.TrimSpace(c.AllowedUserIDs) == "" { return nil } users := make(map[string]bool) for _, id := range strings.Split(c.AllowedUserIDs, ",") { users[strings.TrimSpace(id)] = true } return users } // stopWordSet returns the set of stop words for the engagement engine. func (c *configuration) stopWordSet() map[string]bool { if strings.TrimSpace(c.StopWords) == "" { return map[string]bool{ "thanks": true, "thank you": true, "bye": true, "goodbye": true, "that's all": true, "done": true, "stop": true, "quit": true, "end": true, } } words := make(map[string]bool) for _, w := range strings.Split(c.StopWords, ",") { words[strings.TrimSpace(strings.ToLower(w))] = true } return words }