diff --git a/.env.example b/.env.example index 7652c7a..3e8b368 100644 --- a/.env.example +++ b/.env.example @@ -20,5 +20,8 @@ BOT_TOKEN= BOT_PREFIX= BOT_OWNER_ID= -# 학습 (필수 아님) -TRAIN_USER_ID= \ No newline at end of file +# 챗봇 +CHATBOT_GEMINI_TOKEN= +CHATBOT_GEMINI_PROMPT_PATH= +CHATBOT_GEMINI_MODEL= # 기본 값은 gemini-2.5-flash +CHATBOT_TRAIN_USER_ID= # 필수 아님 \ No newline at end of file diff --git a/.gitignore b/.gitignore index a61dd0c..27bf7a8 100644 --- a/.gitignore +++ b/.gitignore @@ -127,4 +127,6 @@ $RECYCLE.BIN/ !.env.example export/ -data/ \ No newline at end of file +data/ + +*prompt.txt \ No newline at end of file diff --git a/chatbot/chatbot.go b/chatbot/chatbot.go new file mode 100644 index 0000000..d860ebb --- /dev/null +++ b/chatbot/chatbot.go @@ -0,0 +1,189 @@ +package chatbot + +import ( + "context" + "fmt" + "log" + "math/rand" + "sync" + + "git.wh64.net/muffin/goMuffin/configs" + "git.wh64.net/muffin/goMuffin/databases" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" + "go.mongodb.org/mongo-driver/v2/bson" + "google.golang.org/genai" +) + +type Chatbot struct { + Mode ChatbotMode + Gemini *genai.Client + systemPrompt string + s *discordgo.Session +} + +var ChatBot *Chatbot + +func New(s *discordgo.Session) error { + gemini, err := genai.NewClient(context.TODO(), &genai.ClientConfig{ + APIKey: configs.Config.Chatbot.Gemini.Token, + Backend: genai.BackendGeminiAPI, + }) + if err != nil { + return err + } + + ChatBot = &Chatbot{ + Mode: ChatbotAI, + Gemini: gemini, + s: s, + } + + prompt, err := loadPrompt() + if err != nil { + return err + } + + ChatBot.systemPrompt = prompt + return nil +} + +func (c *Chatbot) SetMode(mode ChatbotMode) *Chatbot { + c.Mode = mode + return c +} + +func (c *Chatbot) SwitchMode() *Chatbot { + switch c.Mode { + case ChatbotAI: + c.SetMode(ChatbotMuffin) + case ChatbotMuffin: + c.SetMode(ChatbotAI) + } + return c +} + +func (c *Chatbot) ModeString() string { + switch c.Mode { + case ChatbotAI: + return "AI모드" + case ChatbotMuffin: + return "머핀 모드" + default: + return "알 수 없음" + } +} + +func (c *Chatbot) ReloadPrompt() error { + prompt, err := loadPrompt() + if err != nil { + return err + } + + c.systemPrompt = prompt + return nil +} + +func getMuffinResponse(s *discordgo.Session, question string) (string, error) { + var data []databases.Text + var learnData []databases.Learn + var result string + var wg sync.WaitGroup + + ch1 := make(chan error) + ch2 := make(chan error) + x := rand.Intn(10) + + wg.Add(2) + + // 머핀 데이터 + go func() { + cur, err := databases.Database.Texts.Find(context.TODO(), bson.D{{Key: "persona", Value: "muffin"}}) + if err != nil { + ch1 <- err + } + + defer cur.Close(context.TODO()) + + cur.All(context.TODO(), &data) + ch1 <- nil + wg.Done() + }() + + // 지식 데이터 + go func() { + cur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{Key: "command", Value: question}}) + if err != nil { + ch2 <- err + } + + defer cur.Close(context.TODO()) + + cur.All(context.TODO(), &learnData) + ch2 <- nil + wg.Done() + }() + + wg.Wait() + select { + case err := <-ch1: + if err != nil { + return "에러 발생", fmt.Errorf("muffin data error\n%s", err.Error()) + } + case err := <-ch2: + if err != nil { + return "에러 발생", fmt.Errorf("learn data error\n%s", err.Error()) + } + } + + if x > 2 && len(learnData) != 0 { + data := learnData[rand.Intn(len(learnData))] + user, _ := s.User(data.UserId) + + result = + fmt.Sprintf("%s\n%s", data.Result, utils.InlineCode(fmt.Sprintf("%s님이 알려주셨어요.", user.Username))) + } else { + result = data[rand.Intn(len(data))].Text + } + return result, nil +} + +func getAIResponse(c *Chatbot, user *discordgo.User, question string) (string, error) { + contents, err := GetMemory(user.ID) + if err != nil { + ChatBot.Mode = ChatbotMuffin + return "AI에 문제가 생겼ㅇ어요.", err + } + + contents = append(contents, genai.NewContentFromText(question, genai.RoleUser)) + result, err := ChatBot.Gemini.Models.GenerateContent(context.TODO(), configs.Config.Chatbot.Gemini.Model, contents, &genai.GenerateContentConfig{ + SystemInstruction: genai.NewContentFromText(makePrompt(c.systemPrompt, user), genai.RoleUser), + }) + if err != nil { + ChatBot.Mode = ChatbotMuffin + return "AI에 문제가 생겼ㅇ어요.", err + } + + resultText := result.Text() + err = SaveMemory(&databases.InsertMemory{ + UserId: user.ID, + Content: question, + Answer: resultText, + }) + if err != nil { + return "", err + } + + log.Printf("%s TOKEN: %d", user.ID, result.UsageMetadata.PromptTokenCount) + + return resultText, nil +} + +func (c *Chatbot) GetResponse(user *discordgo.User, question string) (string, error) { + switch c.Mode { + case ChatbotMuffin: + return getMuffinResponse(c.s, question) + default: + return getAIResponse(c, user, question) + } +} diff --git a/chatbot/enum.go b/chatbot/enum.go new file mode 100644 index 0000000..a99968a --- /dev/null +++ b/chatbot/enum.go @@ -0,0 +1,8 @@ +package chatbot + +type ChatbotMode int + +const ( + ChatbotAI ChatbotMode = iota + ChatbotMuffin +) diff --git a/chatbot/memory.go b/chatbot/memory.go new file mode 100644 index 0000000..b2df66b --- /dev/null +++ b/chatbot/memory.go @@ -0,0 +1,36 @@ +package chatbot + +import ( + "context" + + "git.wh64.net/muffin/goMuffin/databases" + "go.mongodb.org/mongo-driver/v2/bson" + "google.golang.org/genai" +) + +func SaveMemory(data *databases.InsertMemory) error { + _, err := databases.Database.Memory.InsertOne(context.TODO(), *data) + return err +} + +func GetMemory(userId string) ([]*genai.Content, error) { + var data []databases.Memory + + memory := []*genai.Content{} + + cur, err := databases.Database.Memory.Find(context.TODO(), bson.D{{Key: "user_id", Value: userId}}) + if err != nil { + return memory, err + } + + cur.All(context.TODO(), &data) + + for _, data := range data { + memory = append(memory, + genai.NewContentFromText(data.Content, genai.RoleUser), + genai.NewContentFromText(data.Answer, genai.RoleModel), + ) + } + + return memory, nil +} diff --git a/chatbot/parser.go b/chatbot/parser.go new file mode 100644 index 0000000..f1e01d0 --- /dev/null +++ b/chatbot/parser.go @@ -0,0 +1,42 @@ +package chatbot + +import ( + "strings" + "time" + + "git.wh64.net/muffin/goMuffin/configs" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" +) + +func ParseResult(content string, s *discordgo.Session, m any) string { + result := content + + var user *discordgo.User + var joinedAt *time.Time + + switch m := m.(type) { + case *discordgo.MessageCreate: + user = m.Author + joinedAt = &m.Member.JoinedAt + case *utils.InteractionCreate: + user = m.Member.User + joinedAt = &m.Member.JoinedAt + } + + userCreatedAt, _ := discordgo.SnowflakeTimestamp(user.ID) + + result = strings.ReplaceAll(result, "{user.name}", user.Username) + result = strings.ReplaceAll(result, "{user.mention}", user.Mention()) + result = strings.ReplaceAll(result, "{user.globalName}", user.GlobalName) + result = strings.ReplaceAll(result, "{user.id}", user.ID) + result = strings.ReplaceAll(result, "{user.createdAt}", utils.Time(&userCreatedAt, utils.RelativeTime)) + result = strings.ReplaceAll(result, "{user.joinedAt}", utils.Time(joinedAt, utils.RelativeTime)) + + result = strings.ReplaceAll(result, "{muffin.version}", configs.MUFFIN_VERSION) + result = strings.ReplaceAll(result, "{muffin.updatedAt}", utils.Time(configs.UpdatedAt, utils.RelativeTime)) + result = strings.ReplaceAll(result, "{muffin.startedAt}", utils.Time(configs.StartedAt, utils.RelativeTime)) + result = strings.ReplaceAll(result, "{muffin.name}", s.State.User.Username) + result = strings.ReplaceAll(result, "{muffin.id}", s.State.User.ID) + return result +} diff --git a/chatbot/prompt.go b/chatbot/prompt.go new file mode 100644 index 0000000..644cc68 --- /dev/null +++ b/chatbot/prompt.go @@ -0,0 +1,34 @@ +package chatbot + +import ( + "fmt" + "os" + + "git.wh64.net/muffin/goMuffin/configs" + "github.com/bwmarrin/discordgo" +) + +func loadPrompt() (string, error) { + bin, err := os.ReadFile(configs.Config.Chatbot.Gemini.PromptPath) + if err != nil { + return "", err + } + + return string(bin), nil +} + +func makePrompt(systemPrompt string, user *discordgo.User) string { + if user.ID == configs.Config.Bot.OwnerId { + return fmt.Sprintf(systemPrompt, fmt.Sprintf( + "## User Information\n* **ID:** %s\n* **Name:** %s\n* **Other:** This user is your developer.", + user.ID, + user.GlobalName, + )) + } + + return fmt.Sprintf(systemPrompt, fmt.Sprintf( + "## User Information\n* **ID:** %s\n* **Name:** %s\n* **Other:** This user is **not** your developer.", + user.ID, + user.GlobalName, + )) +} diff --git a/commands/chat.go b/commands/chat.go new file mode 100644 index 0000000..bced5d8 --- /dev/null +++ b/commands/chat.go @@ -0,0 +1,49 @@ +package commands + +import ( + "log" + + "git.wh64.net/muffin/goMuffin/chatbot" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" +) + +var ChatCommand *Command = &Command{ + ApplicationCommand: &discordgo.ApplicationCommand{ + Name: "대화", + Description: "이 봇이랑 대화해요. (슬래시 커맨드 전용)", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "내용", + Description: "대화할 내용", + Required: true, + }, + }, + }, + DetailedDescription: &DetailedDescription{ + Usage: "/대화 (내용:대화할 내용)", + Examples: []string{"/대화 내용:안녕", "/대화 내용:냠냠"}, + }, + Category: Chatting, + RegisterApplicationCommand: true, + RegisterMessageCommand: false, + ChatInputRun: func(ctx *ChatInputContext) { + i := ctx.Inter + i.DeferReply(&discordgo.InteractionResponseData{}) + + str, err := chatbot.ChatBot.GetResponse(i.Member.User, i.Options["내용"].StringValue()) + if err != nil { + log.Println(err) + i.EditReply(&utils.InteractionEdit{ + Content: &str, + }) + return + } + + result := chatbot.ParseResult(str, ctx.Inter.Session, i) + i.EditReply(&utils.InteractionEdit{ + Content: &result, + }) + }, +} diff --git a/commands/dataLength.go b/commands/dataLength.go index bdd6b93..4489cbb 100644 --- a/commands/dataLength.go +++ b/commands/dataLength.go @@ -29,7 +29,6 @@ const ( userLearn ) -// var dataLengthCh chan chStruct = make(chan chStruct) var dataLengthWg sync.WaitGroup var DataLengthCommand *Command = &Command{ @@ -42,7 +41,9 @@ var DataLengthCommand *Command = &Command{ DetailedDescription: &DetailedDescription{ Usage: fmt.Sprintf("%s학습데이터량", configs.Config.Bot.Prefix), }, - Category: General, + Category: General, + RegisterApplicationCommand: true, + RegisterMessageCommand: true, MessageRun: func(ctx *MsgContext) { dataLengthRun(ctx.Msg.Session, ctx.Msg, ctx.Msg.Author.Username, ctx.Msg.Author.ID) }, diff --git a/commands/deleteLearnedData.go b/commands/deleteLearnedData.go index 7c2800e..6a2fda1 100644 --- a/commands/deleteLearnedData.go +++ b/commands/deleteLearnedData.go @@ -29,7 +29,9 @@ var DeleteLearnedDataCommand *Command = &Command{ Usage: fmt.Sprintf("%s삭제 (삭제할 단어)", configs.Config.Bot.Prefix), Examples: []string{fmt.Sprintf("%s삭제 머핀", configs.Config.Bot.Prefix)}, }, - Category: Chatting, + Category: Chatting, + RegisterApplicationCommand: true, + RegisterMessageCommand: true, MessageRun: func(ctx *MsgContext) { command := strings.Join(*ctx.Args, " ") if command == "" { diff --git a/commands/discommand.go b/commands/discommand.go index ea9998a..fe30ed4 100644 --- a/commands/discommand.go +++ b/commands/discommand.go @@ -3,6 +3,7 @@ package commands import ( "sync" + "git.wh64.net/muffin/goMuffin/configs" "git.wh64.net/muffin/goMuffin/utils" "github.com/bwmarrin/discordgo" ) @@ -24,11 +25,13 @@ type DetailedDescription struct { type Command struct { *discordgo.ApplicationCommand - Aliases []string - DetailedDescription *DetailedDescription - Category Category - MessageRun messageRun - ChatInputRun chatInputRun + Aliases []string + DetailedDescription *DetailedDescription + Category Category + RegisterApplicationCommand bool + RegisterMessageCommand bool + MessageRun messageRun + ChatInputRun chatInputRun } type DiscommandStruct struct { @@ -70,8 +73,9 @@ type Modal struct { } const ( - Chatting Category = "채팅" - General Category = "일반" + Chatting Category = "채팅" + General Category = "일반" + DeveloperOnly Category = "개발자 전용" ) var ( @@ -116,6 +120,19 @@ func (d *DiscommandStruct) LoadModal(m *Modal) { func (d *DiscommandStruct) MessageRun(name string, s *discordgo.Session, m *discordgo.MessageCreate, args []string) { if command, ok := d.Commands[name]; ok { + if command.Category == DeveloperOnly && m.Author.ID != configs.Config.Bot.OwnerId { + utils.NewMessageSender(&utils.MessageCreate{MessageCreate: m, Session: s}). + AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해당 명령어는 개발자만 사용 가능해요."})). + SetComponentsV2(true). + SetReply(true). + Send() + return + } + + if !command.RegisterMessageCommand { + return + } + command.MessageRun(&MsgContext{&utils.MessageCreate{ MessageCreate: m, Session: s, @@ -124,7 +141,7 @@ func (d *DiscommandStruct) MessageRun(name string, s *discordgo.Session, m *disc } func (d *DiscommandStruct) ChatInputRun(name string, s *discordgo.Session, i *discordgo.InteractionCreate) { - if command, ok := d.Commands[name]; ok { + if command, ok := d.Commands[name]; ok && command.RegisterApplicationCommand { command.ChatInputRun(&ChatInputContext{&utils.InteractionCreate{ InteractionCreate: i, Session: s, diff --git a/commands/help.go b/commands/help.go index d6fbd26..c92a574 100644 --- a/commands/help.go +++ b/commands/help.go @@ -28,7 +28,9 @@ var HelpCommand *Command = &Command{ Usage: fmt.Sprintf("%s도움말 [명령어]", configs.Config.Bot.Prefix), Examples: []string{fmt.Sprintf("%s도움말", configs.Config.Bot.Prefix), fmt.Sprintf("%s도움말 배워", configs.Config.Bot.Prefix)}, }, - Category: General, + Category: General, + RegisterApplicationCommand: true, + RegisterMessageCommand: true, MessageRun: func(ctx *MsgContext) { helpRun(ctx.Msg.Session, ctx.Msg, strings.Join(*ctx.Args, " ")) }, diff --git a/commands/information.go b/commands/information.go index 9788875..c981d51 100644 --- a/commands/information.go +++ b/commands/information.go @@ -16,7 +16,9 @@ var InformationCommand *Command = &Command{ DetailedDescription: &DetailedDescription{ Usage: fmt.Sprintf("%s정보", configs.Config.Bot.Prefix), }, - Category: General, + Category: General, + RegisterApplicationCommand: true, + RegisterMessageCommand: true, MessageRun: func(ctx *MsgContext) { informationRun(ctx.Msg.Session, ctx.Msg) }, diff --git a/commands/learn.go b/commands/learn.go index 75dfaf8..588e1e6 100644 --- a/commands/learn.go +++ b/commands/learn.go @@ -56,7 +56,9 @@ var LearnCommand *Command = &Command{ fmt.Sprintf("%s배워 \"나의 아이디를 알려줘\" \"너의 아이디는 {user.id}야.\"", configs.Config.Bot.Prefix), }, }, - Category: Chatting, + Category: Chatting, + RegisterApplicationCommand: true, + RegisterMessageCommand: true, MessageRun: func(ctx *MsgContext) { if len(*ctx.Args) < 2 { utils.NewMessageSender(ctx.Msg). @@ -140,6 +142,14 @@ func learnRun(m any, userId, command, result string) { } } + if len([]rune(command)) > 100 { + utils.NewMessageSender(m). + AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "단어는 100글자를 못 넘ㅇ어가요."})). + SetComponentsV2(true). + SetReply(true). + Send() + } + _, err := databases.Database.Learns.InsertOne(context.TODO(), databases.InsertLearn{ Command: command, Result: result, diff --git a/commands/learnedDataList.go b/commands/learnedDataList.go index 7e8b4d1..005b9d5 100644 --- a/commands/learnedDataList.go +++ b/commands/learnedDataList.go @@ -50,7 +50,9 @@ var LearnedDataListCommand *Command = &Command{ fmt.Sprintf("%s리스트 개수:10", configs.Config.Bot.Prefix), }, }, - Category: Chatting, + Category: Chatting, + RegisterApplicationCommand: true, + RegisterMessageCommand: true, MessageRun: func(ctx *MsgContext) { var length int diff --git a/commands/reloadPrompt.go b/commands/reloadPrompt.go new file mode 100644 index 0000000..3c4a033 --- /dev/null +++ b/commands/reloadPrompt.go @@ -0,0 +1,42 @@ +package commands + +import ( + "fmt" + "log" + + "git.wh64.net/muffin/goMuffin/chatbot" + "git.wh64.net/muffin/goMuffin/configs" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" +) + +var ReloadPromptCommand *Command = &Command{ + ApplicationCommand: &discordgo.ApplicationCommand{ + Name: "프롬프트재설정", + Description: "프롬프트를 다시 불러와요.", + }, + DetailedDescription: &DetailedDescription{ + Usage: fmt.Sprintf("%s프롬프트재설정", configs.Config.Bot.Prefix), + }, + Category: DeveloperOnly, + RegisterApplicationCommand: false, + RegisterMessageCommand: true, + MessageRun: func(ctx *MsgContext) { + err := chatbot.ChatBot.ReloadPrompt() + if err != nil { + log.Fatalln(err) + utils.NewMessageSender(ctx.Msg). + AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "프롬프트를 다시 불러오는 데 문제가 생겼어요."})). + SetComponentsV2(true). + SetReply(true). + Send() + return + } + + utils.NewMessageSender(ctx.Msg). + AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: "프롬프트를 다시 불러왔어요."})). + SetComponentsV2(true). + SetReply(true). + Send() + }, +} diff --git a/commands/switchMode.go b/commands/switchMode.go new file mode 100644 index 0000000..673668e --- /dev/null +++ b/commands/switchMode.go @@ -0,0 +1,32 @@ +package commands + +import ( + "fmt" + + "git.wh64.net/muffin/goMuffin/chatbot" + "git.wh64.net/muffin/goMuffin/configs" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" +) + +var SwitchModeCommand *Command = &Command{ + ApplicationCommand: &discordgo.ApplicationCommand{ + Name: "모드전환", + Description: "머핀봇의 대답을 변경합니다.", + }, + DetailedDescription: &DetailedDescription{ + Usage: fmt.Sprintf("%s모드전환", configs.Config.Bot.Prefix), + }, + Category: DeveloperOnly, + RegisterApplicationCommand: false, + RegisterMessageCommand: true, + MessageRun: func(ctx *MsgContext) { + chatbot.ChatBot.SwitchMode() + + utils.NewMessageSender(ctx.Msg). + AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("모드를 %s로 바꾸었어요.", chatbot.ChatBot.ModeString())})). + SetComponentsV2(true). + SetReply(true). + Send() + }, +} diff --git a/configs/config.go b/configs/config.go index bca47ac..21e272d 100644 --- a/configs/config.go +++ b/configs/config.go @@ -16,7 +16,18 @@ type botConfig struct { } type trainConfig struct { - UserID string + UserId string +} + +type geminiConfig struct { + Token string + PromptPath string + Model string +} + +type chatbotConfig struct { + Train trainConfig + Gemini geminiConfig } type databaseConfig struct { @@ -32,15 +43,18 @@ type databaseConfig struct { // MuffinConfig for Muffin bot type MuffinConfig struct { Bot botConfig - Train trainConfig Database databaseConfig + Chatbot chatbotConfig + + // Deprecated: Use Chatbot.Train + Train trainConfig } var Config *MuffinConfig func init() { godotenv.Load() - Config = &MuffinConfig{Bot: botConfig{}, Train: trainConfig{}, Database: databaseConfig{}} + Config = &MuffinConfig{} setConfig(Config) } @@ -57,20 +71,22 @@ func getValue(key string) string { } func setConfig(config *MuffinConfig) { - config.Bot.Prefix = getRequiredValue("BOT_PREFIX") - config.Bot.Token = getRequiredValue("BOT_TOKEN") - config.Bot.OwnerId = getRequiredValue("BOT_OWNER_ID") + config.Bot = botConfig{ + Prefix: getRequiredValue("BOT_PREFIX"), + Token: getRequiredValue("BOT_TOKEN"), + OwnerId: getRequiredValue("BOT_OWNER_ID"), + } - config.Train.UserID = getValue("TRAIN_USER_ID") - - config.Database.URL = getValue("DATABASE_URL") - config.Database.HostName = getValue("DATABASE_HOSTNAME") - config.Database.Password = getValue("DATABASE_PASSWORD") - config.Database.Username = getValue("DATABASE_USERNAME") - config.Database.AuthSource = getValue("DATABASE_AUTH_SOURCE") - config.Database.Name = getRequiredValue("DATABASE_NAME") + config.Database = databaseConfig{ + URL: getValue("DATABASE_URL"), + HostName: getValue("DATABASE_HOSTNAME"), + Password: getValue("DATABASE_PASSWORD"), + Username: getValue("DATABASE_USERNAME"), + AuthSource: getValue("DATABASE_AUTH_SOURCE"), + Name: getRequiredValue("DATABASE_NAME"), + } port, err := strconv.Atoi(getValue("DATABASE_PORT")) - if err != nil { + if getValue("DATABASE_PORT") != "" && err != nil { log.Println("[goMuffin] 'DATABASE_PORT'값을 int로 파싱할 수 없어요.") log.Fatalln(err) } @@ -84,4 +100,15 @@ func setConfig(config *MuffinConfig) { if config.Database.URL == "" { config.Database.URL = fmt.Sprintf("mongodb://%s:%s@%s:%d/?authSource=%s", config.Database.Username, config.Database.Password, config.Database.HostName, config.Database.Port, config.Database.AuthSource) } + + config.Chatbot = chatbotConfig{ + Gemini: geminiConfig{Token: getValue("CHATBOT_GEMINI_TOKEN"), PromptPath: getValue("CHATBOT_GEMINI_PROMPT_PATH"), Model: getValue("CHATBOT_GEMINI_MODEL")}, + Train: trainConfig{UserId: getValue("CHATBOT_TRAIN_USER_ID")}, + } + + if config.Chatbot.Gemini.Model == "" { + config.Chatbot.Gemini.Model = "gemini-2.0-flash" + } + + config.Train = config.Chatbot.Train } diff --git a/configs/version.go b/configs/version.go index 9573f87..2493fd2 100644 --- a/configs/version.go +++ b/configs/version.go @@ -7,7 +7,7 @@ import ( "git.wh64.net/muffin/goMuffin/utils" ) -const MUFFIN_VERSION = "0.0.0-souffle_canary.250531a" +const MUFFIN_VERSION = "0.0.0-madeleine_canary.250606a" var updatedString string = utils.RegexpDecimals.FindAllStringSubmatch(MUFFIN_VERSION, -1)[3][0] diff --git a/databases/Memory.go b/databases/Memory.go new file mode 100644 index 0000000..3c1509b --- /dev/null +++ b/databases/Memory.go @@ -0,0 +1,16 @@ +package databases + +import "go.mongodb.org/mongo-driver/v2/bson" + +type InsertMemory struct { + UserId string `bson:"user_id"` + Content string `bson:"content"` + Answer string `bson:"answer"` +} + +type Memory struct { + Id bson.ObjectID `bson:"_id"` + UserId string `bson:"user_id"` + Content string `bson:"content"` + Answer string `bson:"answer"` +} diff --git a/databases/database.go b/databases/database.go index 273a671..75a9cf8 100644 --- a/databases/database.go +++ b/databases/database.go @@ -12,6 +12,7 @@ type MuffinDatabase struct { Client *mongo.Client Learns *mongo.Collection Texts *mongo.Collection + Memory *mongo.Collection } var Database *MuffinDatabase @@ -34,5 +35,6 @@ func Connect() (*MuffinDatabase, error) { Client: client, Learns: client.Database(configs.Config.Database.Name).Collection("learn"), Texts: client.Database(configs.Config.Database.Name).Collection("text"), + Memory: client.Database(configs.Config.Database.Name).Collection("memory"), }, nil } diff --git a/go.mod b/go.mod index 82db85d..1e9ee47 100644 --- a/go.mod +++ b/go.mod @@ -8,18 +8,31 @@ require ( github.com/devproje/commando v0.1.0-alpha.1 github.com/joho/godotenv v1.5.1 go.mongodb.org/mongo-driver/v2 v2.1.0 + google.golang.org/genai v1.6.0 ) require ( + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v1.0.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.2 // indirect + google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index 56e6647..4bf725b 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,56 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= +cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/LoperLee/golang-hangul-toolkit v1.1.0 h1:JEyLpLyA2hDQwWY9oCprHClnKIdkYVOSJzAat2uFX/A= github.com/LoperLee/golang-hangul-toolkit v1.1.0/go.mod h1:CDbZ23/IL4v2ovWIOb7xDEiFcSc0pIIbbYTpg+gP+Sk= -github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= -github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/discordgo v0.28.2-0.20250520184322-b9883c495955 h1:ReUA/wL53HdO2jlzwwl5kVa2UJInBtHzqrvf3eVTEvk= github.com/bwmarrin/discordgo v0.28.2-0.20250520184322-b9883c495955/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/devproje/commando v0.1.0-alpha.1 h1:JU6CKIdt1otjUKh+asCJC0yTzwVj+4Yh8KoTdzaKAkU= github.com/devproje/commando v0.1.0-alpha.1/go.mod h1:OhrPX3mZUGSyEX/E7d1o0vaQIYkjG/N5rk6Nqwgyc7k= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -19,6 +58,14 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -30,20 +77,41 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.1.0 h1:/ELnVNjmfUKDsoBisXxuJL0noR9CfeUIrP7Yt3R+egg= go.mongodb.org/mongo-driver/v2 v2.1.0/go.mod h1:AWiLRShSrk5RHQS3AEn3RL19rqOzVq49MCpWQ3x/huI= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -59,6 +127,43 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genai v1.6.0 h1:aG0J3QF/Ad2GsjHvY8LjRp9hiDl4hvLJN98YwkLDqFE= +google.golang.org/genai v1.6.0/go.mod h1:TyfOKRz/QyCaj6f/ZDt505x+YreXnY40l2I6k8TvgqY= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/handler/messageCreate.go b/handler/messageCreate.go index fd0f3ee..73257fe 100644 --- a/handler/messageCreate.go +++ b/handler/messageCreate.go @@ -2,18 +2,16 @@ package handler import ( "context" - "fmt" "log" - "math/rand" "strings" "time" + "git.wh64.net/muffin/goMuffin/chatbot" "git.wh64.net/muffin/goMuffin/commands" "git.wh64.net/muffin/goMuffin/configs" "git.wh64.net/muffin/goMuffin/databases" "git.wh64.net/muffin/goMuffin/utils" "github.com/bwmarrin/discordgo" - "go.mongodb.org/mongo-driver/v2/bson" ) func argParser(content string) (args []string) { @@ -27,25 +25,6 @@ func argParser(content string) (args []string) { return } -func resultParser(content string, s *discordgo.Session, m *discordgo.MessageCreate) string { - result := content - userCreatedAt, _ := discordgo.SnowflakeTimestamp(m.Author.ID) - - result = strings.ReplaceAll(result, "{user.name}", m.Author.Username) - result = strings.ReplaceAll(result, "{user.mention}", m.Author.Mention()) - result = strings.ReplaceAll(result, "{user.globalName}", m.Author.GlobalName) - result = strings.ReplaceAll(result, "{user.id}", m.Author.ID) - result = strings.ReplaceAll(result, "{user.createdAt}", utils.Time(&userCreatedAt, utils.RelativeTime)) - result = strings.ReplaceAll(result, "{user.joinedAt}", utils.Time(&m.Member.JoinedAt, utils.RelativeTime)) - - result = strings.ReplaceAll(result, "{muffin.version}", configs.MUFFIN_VERSION) - result = strings.ReplaceAll(result, "{muffin.updatedAt}", utils.Time(configs.UpdatedAt, utils.RelativeTime)) - result = strings.ReplaceAll(result, "{muffin.startedAt}", utils.Time(configs.StartedAt, utils.RelativeTime)) - result = strings.ReplaceAll(result, "{muffin.name}", s.State.User.Username) - result = strings.ReplaceAll(result, "{muffin.id}", s.State.User.ID) - return result -} - // MessageCreate is handlers of messageCreate event func MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { config := configs.Config @@ -58,94 +37,28 @@ func MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { args := argParser(content) command := commands.Discommand.Aliases[args[0]] - if m.Author.ID == config.Train.UserID { - if _, err := databases.Database.Texts.InsertOne(context.TODO(), databases.InsertText{ - Text: content, - Persona: "muffin", - CreatedAt: time.Now(), - }); err != nil { - log.Fatalln(err) - } - } - - if command == "" { + if command == "" || command == "대화" { s.ChannelTyping(m.ChannelID) - var data []databases.Text - var learnData []databases.Learn - var filter bson.D - - ch := make(chan int) - x := rand.Intn(10) - - channel, _ := s.Channel(m.ChannelID) - if channel.NSFW { - filter = bson.D{{}} - - if _, err := databases.Database.Texts.InsertOne(context.TODO(), databases.InsertText{ - Text: content, - Persona: fmt.Sprintf("user:%s", m.Author.Username), - CreatedAt: time.Now(), - }); err != nil { - log.Fatalln(err) - } - - } else { - filter = bson.D{{Key: "persona", Value: "muffin"}} - } - - go func() { - cur, err := databases.Database.Texts.Find(context.TODO(), filter) - if err != nil { - log.Fatalln(err) - } - - defer cur.Close(context.TODO()) - - cur.All(context.TODO(), &data) - ch <- 1 - }() - go func() { - cur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{Key: "command", Value: content}}) - if err != nil { - log.Fatalln(err) - } - - defer cur.Close(context.TODO()) - - cur.All(context.TODO(), &learnData) - ch <- 1 - }() - - for range 2 { - <-ch - } - close(ch) - - if x > 2 && len(learnData) != 0 { - data := learnData[rand.Intn(len(learnData))] - user, _ := s.User(data.UserId) - result := resultParser(data.Result, s, m) - - utils.NewMessageSender(m). - SetContent(fmt.Sprintf("%s\n%s", result, utils.InlineCode(fmt.Sprintf("%s님이 알려주셨어요.", user.Username)))). - SetAllowedMentions(discordgo.MessageAllowedMentions{ - Roles: []string{}, - Parse: []discordgo.AllowedMentionType{}, - Users: []string{}, - }). + str, err := chatbot.ChatBot.GetResponse(m.Author, strings.TrimPrefix(content, "대화 ")) + if err != nil { + log.Println(err) + utils.NewMessageSender(&utils.MessageCreate{ + MessageCreate: m, + Session: s, + }). + SetContent(str). SetReply(true). Send() return } - utils.NewMessageSender(m). - SetContent(data[rand.Intn(len(data))].Text). - SetAllowedMentions(discordgo.MessageAllowedMentions{ - Roles: []string{}, - Parse: []discordgo.AllowedMentionType{}, - Users: []string{}, - }). + result := chatbot.ParseResult(str, s, m) + utils.NewMessageSender(&utils.MessageCreate{ + MessageCreate: m, + Session: s, + }). + SetContent(result). SetReply(true). Send() return @@ -154,6 +67,15 @@ func MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { commands.Discommand.MessageRun(command, s, m, args[1:]) return } else { + if m.Author.ID == config.Chatbot.Train.UserId { + if _, err := databases.Database.Texts.InsertOne(context.TODO(), databases.InsertText{ + Text: m.Content, + Persona: "muffin", + CreatedAt: time.Now(), + }); err != nil { + log.Fatalln(err) + } + } return } } diff --git a/main.go b/main.go index 837a888..1869ee5 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "syscall" "time" + "git.wh64.net/muffin/goMuffin/chatbot" "git.wh64.net/muffin/goMuffin/commands" "git.wh64.net/muffin/goMuffin/components" "git.wh64.net/muffin/goMuffin/configs" @@ -28,6 +29,9 @@ func init() { go commands.Discommand.LoadCommand(commands.LearnedDataListCommand) go commands.Discommand.LoadCommand(commands.InformationCommand) go commands.Discommand.LoadCommand(commands.DeleteLearnedDataCommand) + go commands.Discommand.LoadCommand(commands.ReloadPromptCommand) + go commands.Discommand.LoadCommand(commands.SwitchModeCommand) + go commands.Discommand.LoadCommand(commands.ChatCommand) go commands.Discommand.LoadComponent(components.DeleteLearnedDataComponent) go commands.Discommand.LoadComponent(components.PaginationEmbedComponent) @@ -56,7 +60,7 @@ func main() { command.Root("export", "머핀봇의 데이터를 추출합니다.", scripts.ExportData, types.OptionData{ Name: "type", - Desc: "파일형식을 지정합니다. (json, txt(txt는 머핀 데이터만 적용))", + Desc: "파일형식을 지정합니다. (json, jsonl, finetune)", Type: types.STRING, }, types.OptionData{ @@ -90,6 +94,8 @@ func main() { log.Fatalln(err) } + chatbot.New(dg) + defer dg.Close() // 봇의 상태메세지 변경 @@ -112,6 +118,10 @@ func main() { } } + if !cmd.RegisterApplicationCommand { + continue + } + go dg.ApplicationCommandCreate(dg.State.User.ID, "", cmd.ApplicationCommand) } diff --git a/scripts/export.go b/scripts/export.go index 3fb2b33..2e7d007 100644 --- a/scripts/export.go +++ b/scripts/export.go @@ -17,7 +17,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" ) -var date time.Time = time.Now() +type role string type textJSONLData struct { Text string `json:"text"` @@ -29,6 +29,26 @@ type learnJSONLData struct { Result string `json:"result"` } +type fineTuneMessageData struct { + Role role `json:"role"` + Content string `json:"content"` +} + +type fineTuneJSONLData struct { + Messages []fineTuneMessageData `json:"messages"` +} + +var date time.Time = time.Now() + +var ( + system role = "system" + user role = "user" + assistant role = "assistant" +) + +const SYSTEM_PROMPT = "당신은 머핀AI입니다. 질문을 최대한 분석하지 말고, 간단히하며, 고급 개념은 대답할 수 없습니다. " + + "모르면 모른다고 말해도 괜찮습니다. 말투는 친근하되 존댓말을 사용하여야 합니다. 그리고 대답을 길게 하지 말아야 합니다. 그리고 약간 엉뚱한 면이 있어야 합니다." + func getDate() string { year := strconv.Itoa(date.Year()) month := strconv.Itoa(int(date.Month())) @@ -90,26 +110,15 @@ func saveFileToJSON(path, name string, data any) error { return nil } -func saveFileToJSONL(path, name string, data any) error { +func saveFileToJSONL[T any](path, name string, data []T) error { var content string - switch data := data.(type) { - case []textJSONLData: - for _, data := range data { - bytes, err := json.Marshal(data) - if err != nil { - return err - } - content += string(bytes) + "\n" - } - case []learnJSONLData: - for _, data := range data { - bytes, err := json.Marshal(data) - if err != nil { - return err - } - content += string(bytes) + "\n" + for _, data := range data { + bytes, err := json.Marshal(data) + if err != nil { + return err } + content += string(bytes) + "\n" } f, err := os.Create(fmt.Sprintf("%s/%s.jsonl", path, name)) @@ -137,8 +146,8 @@ func ExportData(n *commando.Node) error { return err } - if fileType != "json" && fileType != "jsonl" { - return fmt.Errorf("파일 형식은 txt또는 json또는 jsonl이여야 해요") + if fileType != "json" && fileType != "jsonl" && fileType != "finetune" { + return fmt.Errorf("파일 형식은 txt또는 json또는 jsonl, finetune이여야 해요") } refined, err := option.ParseBool(*n.MustGetOpt("refined"), n) @@ -213,6 +222,33 @@ func ExportData(n *commando.Node) error { ch <- err return } + } else if fileType == "finetune" { + var newData []fineTuneJSONLData + + for _, data := range data { + newData = append(newData, fineTuneJSONLData{ + []fineTuneMessageData{ + { + Role: system, + Content: SYSTEM_PROMPT, + }, + { + Role: user, + Content: "", + }, + { + Role: assistant, + Content: data.Text, + }, + }, + }) + } + + err = saveFileToJSONL(path, "muffin-fine-tune", newData) + if err != nil { + ch <- err + return + } } fmt.Println("머핀 데이터 추출 완료") @@ -222,6 +258,10 @@ func ExportData(n *commando.Node) error { go func() { defer wg.Done() + if fileType == "finetune" { + return + } + var data []databases.Text cur, err := databases.Database.Texts.Find(context.TODO(), bson.D{ @@ -272,6 +312,10 @@ func ExportData(n *commando.Node) error { go func() { defer wg.Done() + if fileType == "finetune" { + return + } + var data []databases.Learn cur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{}})