diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e0f6b27b..741e2b7b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,5 +22,3 @@ jobs: version: v1.62 args: --issues-exit-code=1 only-new-issues: false - skip-pkg-cache: true - skip-build-cache: true diff --git a/chat/chat.go b/chat/chat.go index 67f48dde..5528ded8 100644 --- a/chat/chat.go +++ b/chat/chat.go @@ -34,6 +34,33 @@ type chatContext struct { msg *Message } +// ChatInfo is input of ChatWith +// nolint: revive +type ChatInfo struct { + Setting + + Text string +} + +// Setting is settings of ChatWith +type Setting struct { + Stream bool + Reply bool + + Placeholder string + + Model string + Prompt string +} + +// GetPlaceholder get message placeholder +func (s *Setting) GetPlaceholder() string { + if s.Placeholder == "" { + return "正在思考..." + } + return s.Placeholder +} + // InitChat init chat service func InitChat() { if config.BotConfig.ChatConfig.Key != "" { @@ -61,38 +88,54 @@ func InitChat() { // GPTChat is handler for chat with GPT func GPTChat(ctx Context) error { - return chat(ctx, false) + return chatHandler(ctx, false) } // GPTChatWithStream is handler for chat with GPT, and use stream api func GPTChatWithStream(ctx Context) error { - return chat(ctx, true) + return chatHandler(ctx, true) } -func chat(ctx Context, stream bool) error { - if client == nil { - return nil - } - - _, arg, err := entities.CommandTakeArgs(ctx.Message(), 0) +func chatHandler(ctx Context, stream bool) error { + _, text, err := entities.CommandTakeArgs(ctx.Message(), 0) if err != nil { log.Error("[ChatGPT] Can't take args", zap.Error(err)) return ctx.Reply("嗦啥呢?") } - if len(arg) == 0 { + if len(text) == 0 { return ctx.Reply("您好,有什么问题可以为您解答吗?") } - if len(arg) > config.BotConfig.ChatConfig.PromptLimit { + if len(text) > config.BotConfig.ChatConfig.PromptLimit { return ctx.Reply("TLDR") } + return ChatWith(ctx, &ChatInfo{ + Text: text, + Setting: Setting{ + Stream: stream, + Reply: true, + }, + }) +} + +// ChatWith chat with GPT +// nolint: revive +func ChatWith(ctx Context, info *ChatInfo) error { + if client == nil { + return nil + } - req, err := generateRequest(ctx, arg, stream) + req, err := generateRequest(ctx, info) if err != nil { return err } - msg, err := util.SendReplyWithError(ctx.Chat(), "正在思考...", ctx.Message()) + var msg *Message + if info.Reply { + msg, err = util.SendReplyWithError(ctx.Chat(), info.GetPlaceholder(), ctx.Message()) + } else { + msg, err = util.SendMessageWithError(ctx.Chat(), info.GetPlaceholder(), ctx.Message()) + } if err != nil { return err } @@ -107,24 +150,30 @@ func chat(ctx Context, stream bool) error { } } -func generateRequest(ctx Context, arg string, stream bool) (*openai.ChatCompletionRequest, error) { +func generateRequest(ctx Context, info *ChatInfo) (*openai.ChatCompletionRequest, error) { chatCfg := config.BotConfig.ChatConfig req := openai.ChatCompletionRequest{ Model: openai.GPT3Dot5Turbo, MaxTokens: chatCfg.MaxTokens, Messages: []openai.ChatCompletionMessage{}, - Stream: stream, + Stream: info.Stream, Temperature: chatCfg.Temperature, } - if chatCfg.Model != "" { + if info.Model != "" { + req.Model = info.Model + } else if chatCfg.Model != "" { req.Model = chatCfg.Model } if len(req.Messages) == 0 && chatCfg.SystemPrompt != "" { + prompt := info.Prompt + if prompt == "" { + prompt = chatCfg.SystemPrompt + } req.Messages = append(req.Messages, openai.ChatCompletionMessage{ Role: openai.ChatMessageRoleSystem, - Content: chatCfg.SystemPrompt, + Content: prompt, }) } @@ -139,7 +188,7 @@ func generateRequest(ctx Context, arg string, stream bool) (*openai.ChatCompleti } } - req.Messages = append(req.Messages, openai.ChatCompletionMessage{Role: openai.ChatMessageRoleUser, Content: arg}) + req.Messages = append(req.Messages, openai.ChatCompletionMessage{Role: openai.ChatMessageRoleUser, Content: info.Text}) return &req, nil } diff --git a/chat/chat_customlized.go b/chat/chat_customlized.go deleted file mode 100644 index 7d7238dc..00000000 --- a/chat/chat_customlized.go +++ /dev/null @@ -1,75 +0,0 @@ -package chat - -import ( - "csust-got/config" - "csust-got/entities" - "csust-got/log" - "csust-got/util" - "encoding/json" - "go.uber.org/zap" - . "gopkg.in/telebot.v3" - "io" - "net/http" - "net/url" -) - -type chatCustModel struct { - Text string `json:"text"` -} - -// CustomModelChat 自定义的大语言模型 -func CustomModelChat(ctx Context) error { - if client == nil { - return nil - } - - log.Debug("[ChatGPT] CustomModelChat", zap.String("text", ctx.Message().Text)) - - _, arg, err := entities.CommandTakeArgs(ctx.Message(), 0) - if err != nil { - log.Error("[ChatGPT] CustomModelChat Can't take args", zap.Error(err)) - return ctx.Reply("嗦啥呢?") - } - arg += util.GetAllReplyMessagesText(ctx.Message()) - log.Debug("[ChatGPT] CustomModelChat", zap.String("text", arg)) - - if len(arg) > config.BotConfig.ChatConfig.PromptLimit { - return ctx.Reply("TLDR") - } - - msg, err := util.SendReplyWithError(ctx.Chat(), "正在思考...", ctx.Message()) - if err != nil { - return err - } - err = generateRequestCustomModelChat(arg, msg) - return err - -} - -func generateRequestCustomModelChat(arg string, msg *Message) error { - serverAddress := config.BotConfig.GenShinConfig.ApiServer + "/Chat" + "?text=" + url.QueryEscape(arg) - log.Info(serverAddress) - - data := chatCustModel{} - resp, err := http.Get(serverAddress) - if err != nil { - log.Error("[ChatGPT] CustomModelChat 连接chat api服务器失败", zap.Error(err)) - return err - } - body, _ := io.ReadAll(resp.Body) - if resp.StatusCode != 200 { - log.Error("[ChatGPT] CustomModelChat chat api服务器返回异常", zap.Int("status", resp.StatusCode), zap.String("body", string(body))) - return err - } - err = json.Unmarshal(body, &data) - if err != nil { - log.Error("[ChatGPT] CustomModelChat chat api服务器json反序列化失败", zap.Error(err), zap.String("body", string(body))) - return err - } - _, err = util.EditMessageWithError(msg, data.Text) - - if err != nil { - log.Error("[ChatGPT] CustomModelChat Can't edit message", zap.Error(err)) - } - return err -} diff --git a/chat/gacha_reply.go b/chat/gacha_reply.go index c06cb94e..f1e0d234 100644 --- a/chat/gacha_reply.go +++ b/chat/gacha_reply.go @@ -3,6 +3,8 @@ package chat import ( "csust-got/log" "csust-got/util/gacha" + "strings" + "go.uber.org/zap" "gopkg.in/telebot.v3" ) @@ -18,7 +20,7 @@ func GachaReplyHandler(ctx telebot.Context) { } else if len(msg.Caption) > 0 { text = msg.Caption } - if len(text) == 0 { + if len(text) == 0 || strings.HasPrefix(text, "/") { return } @@ -27,17 +29,17 @@ func GachaReplyHandler(ctx telebot.Context) { log.Error("[GaCha]: perform gacha failed", zap.Error(err)) return } - ctx.Message().Text = "/chat " + text + switch result { case 3: return case 4: - err = CustomModelChat(ctx) - if err != nil { - log.Error("[ChatGPT]: get a answer failed", zap.Error(err)) - } + // TODO: `ChatWith` a different prompt case 5: - err = GPTChat(ctx) + err = ChatWith(ctx, &ChatInfo{ + Text: text, + Setting: Setting{Stream: false, Reply: true}, + }) if err != nil { log.Error("[ChatGPT]: get a answer failed", zap.Error(err)) } diff --git a/main.go b/main.go index 4c59254b..5547840a 100644 --- a/main.go +++ b/main.go @@ -172,7 +172,6 @@ func registerBaseHandler(bot *Bot) { bot.Handle("/chat", chat.GPTChat, whiteMiddleware) bot.Handle("/chats", chat.GPTChatWithStream, whiteMiddleware) - bot.Handle("/qiuchat", chat.CustomModelChat, whiteMiddleware) // meilisearch handler bot.Handle("/search", meili.SearchHandle) @@ -441,7 +440,12 @@ func contentFilterMiddleware(next HandlerFunc) HandlerFunc { return next(ctx) } - go chat.GachaReplyHandler(ctx) + // DONE: gacha 会修改ctx.Message.Text,所以放到next之后,等dawu以后重构吧,详见 #501 + // 2024-12-17 [dawu]: 已经重构 + if m.Text != "" { + go chat.GachaReplyHandler(ctx) + } + return next(ctx) } } diff --git a/meili/bot_handle.go b/meili/bot_handle.go index 8db451d3..4a29f794 100644 --- a/meili/bot_handle.go +++ b/meili/bot_handle.go @@ -6,6 +6,8 @@ import ( "csust-got/log" "csust-got/util" "encoding/json" + "errors" + "fmt" "strconv" "github.com/meilisearch/meilisearch-go" @@ -65,7 +67,7 @@ func executeSearch(ctx Context) string { log.Debug("[GetChatMember]", zap.String("chatRecipient", ctx.Chat().Recipient()), zap.String("userRecipient", ctx.Sender().Recipient())) // parse option searchKeywordIdx := 0 - if command.Argc() > 2 { + if command.Argc() >= 2 { option := command.Arg(0) if option == "-id" { // when search by id, index 0 arg is "-id", 1 arg is id, pass rest to query @@ -73,19 +75,22 @@ func executeSearch(ctx Context) string { chatId, err = strconv.ParseInt(command.Arg(1), 10, 64) if err != nil { log.Error("[MeiliSearch]: Parse chat id failed", zap.String("Search args", command.ArgAllInOneFrom(0)), zap.Error(err)) - return err.Error() + return "Invalid chat id" } searchKeywordIdx = 2 } } if searchKeywordIdx > 0 { // check if user is a member of chat_id group - member, err := util.GetChatMember(ctx.Bot(), chatId, ctx.Sender().Recipient()) + member, err := ctx.Bot().ChatMemberOf(ChatID(chatId), ctx.Sender()) if err != nil { + if errors.Is(err, ErrChatNotFound) { + return "Chat not found" + } log.Error("[MeiliSearch]: Error in GetChatMember", zap.String("Search args", command.ArgAllInOneFrom(0)), zap.Error(err)) - return "Error when getting chat member" + return "Not sure if you are a member of the specified group" } - if member.Result.Status == "left" { + if member.Role == Left || member.Role == Kicked { log.Error("[MeiliSearch]: Not a member of the specified group", zap.String("Search args", command.ArgAllInOneFrom(0)), zap.Int64("chatId", chatId), zap.String("user", ctx.Sender().Recipient())) return "Not a member of the specified group" @@ -102,6 +107,11 @@ func executeSearch(ctx Context) string { SearchRequest: searchRequest, } } + + if query.Query == "" { + return fmt.Sprintf("search keyword is empty, use `%s ` to search", ctx.Message().Text) + } + result, err := SearchMeili(query) if err != nil { log.Error("[MeiliSearch]: search failed", zap.String("Search args", command.ArgAllInOneFrom(0)), zap.Error(err)) diff --git a/util/utils.go b/util/utils.go index 499e98fb..28159d4a 100644 --- a/util/utils.go +++ b/util/utils.go @@ -1,7 +1,6 @@ package util import ( - "encoding/json" "io" "math/rand" "net/http" @@ -18,24 +17,6 @@ import ( tb "gopkg.in/telebot.v3" ) -// ChatMemberResponse of GetChatMember method -type ChatMemberResponse struct { - Ok bool `json:"ok"` - Result struct { - User struct { - IsBot bool `json:"is_bot"` - Id int `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Username string `json:"username"` - LanguageCode string `json:"language_code"` - } `json:"user"` - Status string `json:"status"` - CustomTitle string `json:"custom_title"` - IsAnonymous bool `json:"is_anonymous"` - } `json:"result"` -} - // ParseNumberAndHandleError is used to get a number from string or reply a error msg when get error. func ParseNumberAndHandleError(m *tb.Message, ns string, rng IRange[int]) (number int, ok bool) { // message id is an int-type number @@ -145,25 +126,6 @@ func CanRestrictMembers(chat *tb.Chat, user *tb.User) bool { return member.CanRestrictMembers } -// GetChatMember can get chat member from chat. -func GetChatMember(bot *tb.Bot, chatID int64, userID string) (*ChatMemberResponse, error) { - params := map[string]string{ - "chat_id": strconv.FormatInt(chatID, 10), - "user_id": userID, - } - log.Debug("[GetChatMember]", zap.Int64("chat_id", chatID), zap.String("user_id", userID)) - data, err := bot.Raw("getChatMember", params) - if err != nil { - return nil, err - } - var chatMember ChatMemberResponse - if err := json.Unmarshal(data, &chatMember); err != nil { - return nil, err - } - log.Debug("[GetChatMember]", zap.ByteString("response", data), zap.Any("chatMember", &chatMember)) - return &chatMember, nil -} - // RandomChoice - rand one from slice. func RandomChoice[T any](s []T) T { var ret T