bitbot/bot/chat.go

216 lines
6.4 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 = "0. your name is !bit you are a discord bot 1. Identify the key points or main ideas of the original answers.\n2. Summarize each answer using concise and informative language.\n3. Prioritize clarity and brevity, capturing the essence of the information provided.\n4. Trim down unnecessary details and avoid elaboration.\n5. Make sure the summarized answers still convey accurate and meaningful information."
)
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
}
for _, message := range messages {
if message.Author.ID == session.State.User.ID {
continue // Skip the bot's own messages
}
if len(message.Content) > 0 {
tokens := len(message.Content) + 2 // Account for role and content tokens
if totalTokens+tokens <= maxContextTokens && len(conversationHistory) < maxHistoryTokens {
conversationHistory = append(conversationHistory, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: message.Content,
})
totalTokens += 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)
// Retrieve recent messages from the channel
channelMessages, err := session.ChannelMessages(channelID, 50, "", "", "")
if err != nil {
log.Error("Error retrieving channel messages:", err)
}
// Convert channel messages to chat completion messages
for _, msg := range channelMessages {
if msg.Author.ID != session.State.User.ID && len(msg.Content) > 0 {
conversationHistory = append(conversationHistory, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: msg.Content,
})
}
}
// Combine messages from conversation history
messages := []openai.ChatCompletionMessage{}
// Add conversation history to messages
messages = append(messages, conversationHistory...)
// Add user message to conversation history
userMessage := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: message,
}
messages = append(messages, userMessage)
// Trim conversation history if it exceeds maxContextTokens
totalTokens := 0
trimmedMessages := []openai.ChatCompletionMessage{}
for i := 0; i < len(messages); i++ {
msg := messages[i]
tokens := len(msg.Content) + len(msg.Role) + 2 // Account for role and content tokens
if totalTokens+tokens <= maxContextTokens {
trimmedMessages = append(trimmedMessages, msg)
totalTokens += tokens
} else {
break
}
}
// Update messages with trimmed conversation history
messages = trimmedMessages
// Perform GPT-4 completion
log.Info("Starting completion...")
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
MaxTokens: maxTokens,
FrequencyPenalty: 0.3,
PresencePenalty: 0.6,
Model: openai.GPT3Dot5Turbo,
Messages: messages,
},
)
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)
}
}