Scaffold Go plugin skeleton with Ollama client, conversation store, rate limiter
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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 <prompt> -- Chat with the default model
|
||||
- @mattermore model <name> <prompt> -- 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 <prompt> 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 <prompt>"), 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user