diff --git a/app/other.go b/app/other.go new file mode 100644 index 0000000..d5bae40 --- /dev/null +++ b/app/other.go @@ -0,0 +1,56 @@ +package app + +import ( + "fmt" + "github.com/gin-gonic/gin" + "go-wechat/model/vo" + "go-wechat/service" + "go-wechat/utils" + "log" + "net/http" +) + +// SendAiSummary +// @description: 发送AI摘要 +// @param ctx +func SendAiSummary(ctx *gin.Context) { + // 获取群Id + groupId := ctx.Query("id") + if groupId == "" { + ctx.String(http.StatusForbidden, "群Id不能为空") + return + } + + // 取出群名称 + groupInfo, err := service.GetFriendInfoById(groupId) + if err != nil { + ctx.String(http.StatusInternalServerError, "获取群信息失败") + return + } + + // 获取对话记录 + var records []vo.TextMessageItem + if records, err = service.GetTextMessagesById(groupId); err != nil { + log.Printf("获取群[%s]对话记录失败, 错误信息: %v", groupId, err) + ctx.String(http.StatusInternalServerError, "获取群对话记录失败") + return + } + if len(records) < 10 { + ctx.String(http.StatusForbidden, "群对话记录不足10条,建议自己看") + return + } + + // 组装对话记录为字符串 + var replyMsg string + replyMsg, err = utils.GetAiSummary(groupInfo.Nickname, records) + if err != nil { + log.Printf("群聊记录总结失败: %v", err.Error()) + ctx.String(http.StatusInternalServerError, "群聊消息总结失败,错误信息: "+err.Error()) + return + } + + replyMsg = fmt.Sprintf("#昨日消息总结\n又是一天过去了,让我们一起来看看昨儿群友们都聊了什么有趣的话题吧~\n\n%s", replyMsg) + log.Printf("群[%s]对话记录总结成功,总结内容: %s", groupInfo.Nickname, replyMsg) + _ = utils.SendMessage(groupId, "", replyMsg, 0) + ctx.String(http.StatusOK, "操作完成") +} diff --git a/config.yaml b/config.yaml index 9134afc..0798b85 100644 --- a/config.yaml +++ b/config.yaml @@ -102,6 +102,7 @@ ai: # 人设 personality: 你的名字叫张三,你是一个百科机器人,你的爱好是看电影,你的性格是开朗的,你的专长是讲故事,你的梦想是当一名童话故事作家。你对政治没有一点儿兴趣,也不会讨论任何与政治相关的话题,你甚至可以拒绝回答这一类话题。 models: + - name: 默认 - name: ChatGPT-4 model: gpt-4 canManager: false diff --git a/main.go b/main.go index ece9e8d..a3130a2 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,8 @@ import ( "html/template" "log" "net/http" + "os" + "strconv" "strings" "time" ) @@ -22,6 +24,13 @@ func init() { initialization.Plugin() // 注册插件 tasks.InitTasks() // 初始化定时任务 mq.Init() // 初始化MQ + + if flag, _ := strconv.ParseBool(os.Getenv("DONT_SEND")); flag { + log.Printf("已设置环境变量DONT_SEND,不发送消息") + } + if flag, _ := strconv.ParseBool(os.Getenv("DONT_SAVE")); flag { + log.Printf("已设置环境变量DONT_SAVE,不保存消息") + } } func main() { diff --git a/router/router.go b/router/router.go index a88367d..ab23210 100644 --- a/router/router.go +++ b/router/router.go @@ -28,6 +28,7 @@ func Init(g *gin.Engine) { api.POST("/ai/model", app.ChangeUseAiModel) // 修改使用的AI模型 api.POST("/ai/free", app.ChangeAiFreeLimit) // 修改AI免费次数 api.POST("/ai/assistant", app.ChangeUseAiAssistant) // 修改使用的AI助手 + api.POST("/ai/summary", app.SendAiSummary) // 手动发送AI聊天总结 api.PUT("/welcome/status", app.ChangeEnableWelcomeStatus) // 修改是否开启迎新状态 api.PUT("/command/status", app.ChangeEnableCommandStatus) // 修改是否开启指令状态 api.PUT("/news/status", app.ChangeEnableNewsStatus) // 修改是否开启早报状态 diff --git a/tasks/summary/summary.go b/tasks/summary/summary.go index adfa2ab..c542041 100644 --- a/tasks/summary/summary.go +++ b/tasks/summary/summary.go @@ -1,15 +1,11 @@ package summary import ( - "context" "fmt" - "github.com/sashabaranov/go-openai" - "go-wechat/config" "go-wechat/model/vo" "go-wechat/service" "go-wechat/utils" "log" - "strings" "time" ) @@ -37,63 +33,21 @@ func AiSummary() { continue } // 组装对话记录为字符串 - var content []string - for _, record := range records { - content = append(content, fmt.Sprintf(`{"%s": "%s"}--end--`, record.Nickname, strings.ReplaceAll(record.Message, "\n", "。。"))) - } - - msgTmp := `请帮我总结一下一下的群聊内容的梗概,生成的梗概需要尽可能详细,需要带上一些聊天关键信息,并且带上群友名字。 -注意,他们可能是多个话题,请仔细甄别。 -每一行代表一个人的发言,每一行的的格式为: {"{nickname}": "{content}"}--end-- - -群名称: %s -聊天记录如下: -%s -` - - msg := fmt.Sprintf(msgTmp, group.Nickname, strings.Join(content, "\n")) - - // AI总结 - messages := []openai.ChatCompletionMessage{ - { - Role: openai.ChatMessageRoleUser, - Content: msg, - }, - } - - // 默认使用AI回复 - conf := openai.DefaultConfig(config.Conf.Ai.ApiKey) - if config.Conf.Ai.BaseUrl != "" { - conf.BaseURL = fmt.Sprintf("%s/v1", config.Conf.Ai.BaseUrl) - } - ai := openai.NewClientWithConfig(conf) - var resp openai.ChatCompletionResponse - resp, err = ai.CreateChatCompletion( - context.Background(), - openai.ChatCompletionRequest{ - Model: config.Conf.Ai.SummaryModel, - Messages: messages, - }, - ) - + var replyMsg string + replyMsg, err = utils.GetAiSummary(group.Nickname, records) if err != nil { log.Printf("群聊记录总结失败: %v", err.Error()) - utils.SendMessage(group.Wxid, "", "#昨日消息总结\n\n群聊消息总结失败,错误信息: "+err.Error(), 0) + _ = utils.SendMessage(group.Wxid, "", "#昨日消息总结\n\n群聊消息总结失败,错误信息: "+err.Error(), 0) continue } - // 返回消息为空 - if resp.Choices[0].Message.Content == "" { - utils.SendMessage(group.Wxid, "", "#昨日消息总结\n\n群聊消息总结失败,AI返回结果为空", 0) - continue - } - replyMsg := fmt.Sprintf("#昨日消息总结\n又是一天过去了,让我们一起来看看昨儿群友们都聊了什么有趣的话题吧~\n\n%s", resp.Choices[0].Message.Content) + replyMsg = fmt.Sprintf("#昨日消息总结\n又是一天过去了,让我们一起来看看昨儿群友们都聊了什么有趣的话题吧~\n\n%s", replyMsg) //log.Printf("群[%s]对话记录总结成功,总结内容: %s", group.Wxid, replyMsg) - utils.SendMessage(group.Wxid, "", replyMsg, 0) + _ = utils.SendMessage(group.Wxid, "", replyMsg, 0) - // 判断耗时是否达到15秒,不足就等待 - if used := time.Now().Sub(start); used < 15*time.Second { - time.Sleep(15*time.Second - used) + // 判断耗时是否达到一分钟,不足就等待 + if used := time.Now().Sub(start); used < time.Minute { + time.Sleep(time.Minute - used) } } } diff --git a/utils/send.go b/utils/send.go index 6318072..b951255 100644 --- a/utils/send.go +++ b/utils/send.go @@ -8,6 +8,8 @@ import ( "go-wechat/common/current" "go-wechat/config" "log" + "os" + "strconv" "time" ) @@ -17,6 +19,10 @@ import ( // @param atId // @param msg func SendMessage(toId, atId, msg string, retryCount int) (err error) { + if flag, _ := strconv.ParseBool(os.Getenv("DONT_SEND")); flag { + return + } + if retryCount > 5 { log.Printf("重试五次失败,停止发送") err = errors.New("重试五次失败,停止发送") @@ -62,6 +68,10 @@ func SendMessage(toId, atId, msg string, retryCount int) (err error) { // @param imgPath string 图片路径 // @param retryCount int 重试次数 func SendImage(toId, imgPath string, retryCount int) { + if flag, _ := strconv.ParseBool(os.Getenv("DONT_SEND")); flag { + return + } + if retryCount > 5 { log.Printf("重试五次失败,停止发送") return @@ -94,6 +104,10 @@ func SendImage(toId, imgPath string, retryCount int) { // @param emotionHash string 表情包hash(md5值) // @param retryCount int 重试次数 func SendEmotion(toId, emotionHash string, retryCount int) { + if flag, _ := strconv.ParseBool(os.Getenv("DONT_SEND")); flag { + return + } + if retryCount > 5 { log.Printf("重试五次失败,停止发送") return @@ -200,6 +214,10 @@ func QuitChatroom(chatRoomId string, retryCount int) { // @param url string 链接 // @param retryCount int 重试次数 func SendPublicMsg(wxId, title, digest, url string, retryCount int) { + if flag, _ := strconv.ParseBool(os.Getenv("DONT_SEND")); flag { + return + } + if retryCount > 5 { log.Printf("重试五次失败,停止发送") return diff --git a/utils/summary.go b/utils/summary.go new file mode 100644 index 0000000..bb98b46 --- /dev/null +++ b/utils/summary.go @@ -0,0 +1,108 @@ +package utils + +import ( + "context" + "errors" + "fmt" + "github.com/sashabaranov/go-openai" + "go-wechat/config" + "go-wechat/model/vo" + "log" + "strings" +) + +// GetAiSummary +// @description: AI总结群聊记录 +// @param groupName +// @param records +// @return replyMsg +// @return err +func GetAiSummary(groupName string, records []vo.TextMessageItem) (replyMsg string, err error) { + // 组装对话记录为字符串 + var content []string + for _, record := range records { + content = append(content, fmt.Sprintf(`{"%s": "%s"}--end--`, record.Nickname, strings.ReplaceAll(record.Message, "\n", "。。"))) + } + + prompt := `# Role: 数据分析师和信息整理专家 + # Background: 用户需要从群聊记录中提取有价值的信息,包括技术话题讨论、工具分享、网站或资源链接。 + # Profile: 你是一位专业的数据分析师,擅长从大量文本中提取关键信息,并能够高效地整理和总结内容。 + # Skills: 数据分析、信息提取、内容总结、网络资源评估。 + # Goals: 设计一个流程,帮助用户识别群聊中的技术话题、共享的工具和资源,并高效地总结每个话题的要点。 + # Constrains: 该流程需要简洁明了,易于用户理解和操作,同时确保不遗漏任何重要信息。 + # OutputFormat: 结构化的文本总结,包含关键讨论点、结论等。 + # Workflow: + ## 1. 快速浏览群聊记录 + - 通过快速浏览聊天记录,初步识别出主要话题。 + ## 2. 识别和分类主要话题 + - 识别群聊中的主要话题,分类讨论内容。 + - 每个话题独立提取,避免混淆。 + - 话题识别尽可能准确,确保不遗漏重要信息。 + ## 3. 编写总结概括 + - 对每个话题进行总结,包含以下内容: + - 关键讨论点:主要的讨论内容和观点。 + - 参与成员:参与讨论的成员的昵称。 + - 结论:得出的结论或共识,内容要尽可能详细。 + ## 4. 组织信息 + - 以逻辑清晰、易于理解的方式组织信息。 + - 使用结构化文本,使总结内容易于阅读。 + ## 5. 生成最终总结 + - 根据上述步骤,生成每个话题的详细总结。 + - 确保每个总结完整且有条理。 + - 返回数据格式为结构化文本,方便用户查阅。 + ## 6. 群聊记录消息格式 + 格式: {"{nickname}": "{content}"}--end-- + # Examples: + - 话题:技术讨论 - AI在医疗领域的应用 + - 关键讨论点:讨论了AI在医疗诊断中的应用前景和现有挑战。 + - 成员:张三、李四、王五。 + - 结论:AI技术有望提高医疗诊断的准确性和效率,但数据隐私和模型解释性仍需解决。 + + - 话题:美食讨论 - 杭州到底有没有美食 + - 关键讨论点:讨论了杭州的美食有哪些、分布地区、评价如何。 + - 成员:张三、李四、王五。 + - 结论:杭州好像没有美食。 +` + + msg := fmt.Sprintf("群名称: %s\n聊天记录如下:\n%s", groupName, strings.Join(content, "\n")) + + // AI总结 + messages := []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleSystem, + Content: prompt, + }, + { + Role: openai.ChatMessageRoleUser, + Content: msg, + }, + } + + // 默认使用AI回复 + conf := openai.DefaultConfig(config.Conf.Ai.ApiKey) + if config.Conf.Ai.BaseUrl != "" { + conf.BaseURL = fmt.Sprintf("%s/v1", config.Conf.Ai.BaseUrl) + } + ai := openai.NewClientWithConfig(conf) + var resp openai.ChatCompletionResponse + resp, err = ai.CreateChatCompletion( + context.Background(), + openai.ChatCompletionRequest{ + Model: config.Conf.Ai.SummaryModel, + Messages: messages, + }, + ) + + if err != nil { + log.Printf("群聊记录总结失败: %v", err.Error()) + return + } + + // 返回消息为空 + if resp.Choices[0].Message.Content == "" { + err = errors.New("AI返回结果为空") + return + } + replyMsg = resp.Choices[0].Message.Content + return +} diff --git a/views/components.html b/views/components.html index 02b7413..7e5a0a2 100644 --- a/views/components.html +++ b/views/components.html @@ -57,6 +57,9 @@ {{define "summary"}} +{{ if eq .EnableSummary true }} + +{{ end }}