package main import ( "fmt" "strings" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" ) const helpText = `### Mattermore -- AI Chat Agent **@mention the bot in any channel to start a conversation.** **Commands:** - @mattermore -- Chat with the default model - @mattermore model -- Use a specific model - @mattermore new -- Reset conversation context - @mattermore model list -- List available models - @mattermore help -- Show this help - @mattermore stop -- End the current conversation You can also use /ai as an alternative. The bot responds only when addressed and disengages naturally. ` // ExecuteCommand handles the /ai slash command. func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { trigger := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(args.Command), "/ai")) parts := parseArgs(trigger) switch { case len(parts) == 0 || parts[0] == "help": return p.response(helpText), nil case parts[0] == "new": if err := p.conversationStore.Reset(args.UserId, args.ChannelId); err != nil { return p.response("Failed to reset conversation."), nil } return p.response("Conversation reset. Start fresh with @mattermore "), nil case parts[0] == "model" && len(parts) >= 2 && parts[1] == "list": return p.handleModelList(args) case parts[0] == "model" && len(parts) >= 3: modelName := parts[1] prompt := strings.Join(parts[2:], " ") return p.handlePrompt(args, modelName, prompt) default: return p.handlePrompt(args, "", strings.Join(parts, " ")) } } // handlePrompt processes a user prompt and returns the AI response. func (p *Plugin) handlePrompt(args *model.CommandArgs, modelName, prompt string) (*model.CommandResponse, *model.AppError) { cfg := p.getConfiguration() // Check rate limit. if !p.rateLimiter.Allow(args.UserId) { return p.response("Rate limit exceeded. Please wait before sending another request."), nil } // Check authorization. if users := cfg.allowedUsers(); users != nil && !users[args.UserId] { return p.response("You are not authorized to use this bot."), nil } // Check stop words before sending to Ollama. if stopWordMatch(prompt, cfg.stopWordSet()) { return p.response("Goodbye! Feel free to @mention me again anytime."), nil } // Build the conversation context. var messages []ChatMessage messages, err := p.conversationStore.BuildMessages(args.UserId, args.ChannelId, prompt) if err != nil { return p.response("Failed to build conversation context."), nil } // Determine model to use. activeModel := cfg.DefaultModel if modelName != "" { activeModel = modelName } // Send to Ollama. resp, err := p.ollamaClient.ChatCompletion(&ChatRequest{ Model: activeModel, Messages: messages, Options: map[string]any{}, }) if err != nil { return p.response(fmt.Sprintf("Failed to get response from Ollama: %v", err)), nil } return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: resp.Message.Content, }, nil } // handleModelList queries Ollama for available models and returns them. func (p *Plugin) handleModelList(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { models, err := p.ollamaClient.ListModels() if err != nil { return p.response("Failed to fetch models from Ollama."), nil } var b strings.Builder b.WriteString("### Available Models\n\n") for _, m := range models { b.WriteString(fmt.Sprintf("- %s\n", m)) } return p.response(b.String()), nil } // parseArgs splits a command string into tokens. func parseArgs(input string) []string { return strings.Fields(input) } // response creates a standard ephemeral command response. func (p *Plugin) response(text string) *model.CommandResponse { return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: text, } }