bitbot/bot/chat.go

206 lines
6.0 KiB
Go

package bot
import (
"context"
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/charmbracelet/log"
openai "github.com/sashabaranov/go-openai"
)
const (
maxTokens = 2000
maxContextTokens = 4000
maxMessageTokens = 2000
systemMessageText = "your name is !bit you are a discord bot"
)
func populateConversationHistory(session *discordgo.Session, channelID string, conversationHistory []openai.ChatCompletionMessage) []openai.ChatCompletionMessage {
messages, err := session.ChannelMessages(channelID, 50, "", "", "")
if err != nil {
log.Error("Error retrieving channel history:", err)
return conversationHistory
}
totalTokens := 0
for _, msg := range conversationHistory {
totalTokens += len(msg.Content) + len(msg.Role) + 2
}
maxHistoryTokens := maxTokens - totalTokens
if maxHistoryTokens < 0 {
maxHistoryTokens = 0
}
// Iterate from the beginning of conversationHistory (oldest messages)
for i := 0; i < len(conversationHistory); i++ {
msg := conversationHistory[i]
tokens := len(msg.Content) + len(msg.Role) + 2 // Account for role and content tokens
if totalTokens-tokens >= maxHistoryTokens {
// Remove the oldest message
conversationHistory = conversationHistory[i+1:]
totalTokens -= tokens
break
}
}
// Add new messages from the channel
addedTokens := 0
for _, message := range messages {
if len(message.Content) > 0 {
tokens := len(message.Content) + 2 // Account for role and content tokens
if totalTokens+tokens <= maxContextTokens && addedTokens+tokens <= maxContextTokens {
conversationHistory = append(conversationHistory, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: message.Content,
})
totalTokens += tokens
addedTokens += tokens
} else {
if totalTokens+tokens > maxContextTokens {
log.Warn("Message token count exceeds maxContextTokens:", len(message.Content), len(message.Content)+2)
} else {
log.Warn("Conversation history length exceeds maxContextTokens:", len(conversationHistory), maxHistoryTokens)
}
break
}
}
}
return conversationHistory
}
func chatGPT(session *discordgo.Session, channelID string, message string, conversationHistory []openai.ChatCompletionMessage) {
client := openai.NewClient(OpenAIToken)
// Trim conversation history if it exceeds maxContextTokens
// totalTokens := 0
// trimmedMessages := []openai.ChatCompletionMessage{}
// for i := len(conversationHistory) - 1; i >= 0; i-- {
// msg := conversationHistory[i]
// tokens := len(msg.Content) + len(msg.Role) + 2 // Account for role and content tokens
// if totalTokens+tokens <= maxContextTokens {
// trimmedMessages = append([]openai.ChatCompletionMessage{msg}, trimmedMessages...)
// totalTokens += tokens
// } else {
// break
// }
// }
// Update conversationHistory with trimmed conversation history
//conversationHistory = trimmedMessages
// Add user message to conversation history
//userMessage := openai.ChatCompletionMessage{
// Role: openai.ChatMessageRoleUser,
// Content: message,
//}
//conversationHistory = append(conversationHistory, userMessage)
// Perform GPT-4 completion
log.Info("Starting completion...", conversationHistory)
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
MaxTokens: maxTokens,
FrequencyPenalty: 0.3,
PresencePenalty: 0.6,
Model: openai.GPT3Dot5Turbo,
Messages: conversationHistory, // Use trimmed conversation history
},
)
log.Info("completion done.")
// ...
// Handle API errors
if err != nil {
log.Error("Error connecting to the OpenAI API:", err)
return
}
// Paginate the response and send as separate messages with clickable emojis
gptResponse := resp.Choices[0].Message.Content
pageSize := maxMessageTokens
// Split the response into pages
var pages []string
for i := 0; i < len(gptResponse); i += pageSize {
end := i + pageSize
if end > len(gptResponse) {
end = len(gptResponse)
}
pages = append(pages, gptResponse[i:end])
}
// ...
// Send the first page
currentPage := 0
totalPages := len(pages)
embed := &discordgo.MessageEmbed{
Title: fmt.Sprintf("BitBot's Response (Page %d of %d)", currentPage+1, totalPages),
Description: pages[currentPage],
Color: 0x00ff00, // Green color
}
msg, err := session.ChannelMessageSendEmbed(channelID, embed)
if err != nil {
log.Error("Error sending embed message:", err)
return
}
// Add reaction emojis for pagination if there are multiple pages
if totalPages > 1 { // Only add reactions if there are multiple pages
err = session.MessageReactionAdd(channelID, msg.ID, "⬅️")
if err != nil {
log.Error("Error adding reaction emoji:", err)
return
}
err = session.MessageReactionAdd(channelID, msg.ID, "➡️")
if err != nil {
log.Error("Error adding reaction emoji:", err)
return
}
}
// Create a reaction handler function
session.AddHandler(func(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
// Call the reactionHandler function and pass totalPages
reactionHandler(s, r, currentPage, msg, pages, totalPages)
})
}
func reactionHandler(session *discordgo.Session, r *discordgo.MessageReactionAdd, currentPage int, msg *discordgo.Message, pages []string, totalPages int) {
// Check if the reaction is from the same user and message
if r.UserID == session.State.User.ID || r.MessageID != msg.ID {
return
}
// Handle pagination based on reaction
if r.Emoji.Name == "⬅️" {
if currentPage > 0 {
currentPage--
}
} else if r.Emoji.Name == "➡️" {
if currentPage < len(pages)-1 {
currentPage++
}
}
// Update the message with the new page
updatedEmbed := &discordgo.MessageEmbed{
Title: fmt.Sprintf("BitBot's Response (Page %d of %d)", currentPage+1, len(pages)),
Description: pages[currentPage],
Color: 0x00ff00, // Green color
}
_, err := session.ChannelMessageEditEmbed(r.ChannelID, r.MessageID, updatedEmbed)
if err != nil {
log.Error("Error editing embed message:", err)
}
}