package main import ( "fmt" "net/url" "strings" ) // configuration holds the plugin settings from the System Console. type configuration struct { OllamaURL string DefaultModel string Persona string CustomPersona string Brevity string RepeatPenalty int FrequencyPenalty int MaxTokens int RateLimitPerMinute int AllowedUserIDs string StopWords string DebugLogging bool } // 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 { p.API.LogError("Config load failed", "error", err.Error()) return fmt.Errorf("failed to load plugin configuration: %w", err) } if c.DebugLogging { p.API.LogInfo("Config loaded", "model", c.DefaultModel, "persona", c.Persona, "brevity", c.Brevity, "repeat", c.RepeatPenalty, "freq", c.FrequencyPenalty, "tokens", c.MaxTokens) } if err := p.setConfiguration(&c); err != nil { p.API.LogError("Config validate failed", "error", err.Error()) return fmt.Errorf("failed to set configuration: %w", err) } // Pass the debug logging flag to the Ollama client. if p.ollamaClient != nil { p.ollamaClient.SetLogging(c.DebugLogging) } 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 }