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/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 }}