🆕 新增群聊对话记录总结
This commit is contained in:
parent
3da8b327d0
commit
b3ed0fcc6f
@ -90,6 +90,29 @@ func ChangeEnableGroupRankStatus(ctx *gin.Context) {
|
|||||||
ctx.String(http.StatusOK, "操作成功")
|
ctx.String(http.StatusOK, "操作成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangeEnableSummaryStatus
|
||||||
|
// @description: 修改是否开启聊天记录总结
|
||||||
|
// @param ctx
|
||||||
|
func ChangeEnableSummaryStatus(ctx *gin.Context) {
|
||||||
|
var p changeStatusParam
|
||||||
|
if err := ctx.ShouldBindJSON(&p); err != nil {
|
||||||
|
ctx.String(http.StatusBadRequest, "参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("待修改的群Id:%s", p.WxId)
|
||||||
|
|
||||||
|
err := client.MySQL.Model(&entity.Friend{}).
|
||||||
|
Where("wxid = ?", p.WxId).
|
||||||
|
Update("`enable_summary`", gorm.Expr(" !`enable_summary`")).Error
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("修改开启聊天记录总结失败:%s", err)
|
||||||
|
ctx.String(http.StatusInternalServerError, "操作失败: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.String(http.StatusOK, "操作成功")
|
||||||
|
}
|
||||||
|
|
||||||
// ChangeEnableWelcomeStatus
|
// ChangeEnableWelcomeStatus
|
||||||
// @description: 修改是否开启迎新
|
// @description: 修改是否开启迎新
|
||||||
// @param ctx
|
// @param ctx
|
||||||
|
11
config.yaml
11
config.yaml
@ -3,7 +3,7 @@ wechat:
|
|||||||
# 微信HOOK接口地址
|
# 微信HOOK接口地址
|
||||||
host: 10.0.0.73:19088
|
host: 10.0.0.73:19088
|
||||||
# 微信容器映射出来的vnc页面地址,没有就不填
|
# 微信容器映射出来的vnc页面地址,没有就不填
|
||||||
# vncUrl: http://192.168.1.175:19087/vnc_lite.html
|
# vncUrl: http://192.168.1.175:19087/vnc_lite.html
|
||||||
# 是否在启动的时候自动设置hook服务的回调
|
# 是否在启动的时候自动设置hook服务的回调
|
||||||
autoSetCallback: false
|
autoSetCallback: false
|
||||||
# 回调IP,如果是Docker运行,本参数必填(填auto表示自动,不适用于 docker 环境),如果Docker修改了映射,格式为 ip:port
|
# 回调IP,如果是Docker运行,本参数必填(填auto表示自动,不适用于 docker 环境),如果Docker修改了映射,格式为 ip:port
|
||||||
@ -23,12 +23,15 @@ mysql:
|
|||||||
schema: public # postgres 专用
|
schema: public # postgres 专用
|
||||||
|
|
||||||
task:
|
task:
|
||||||
enable: false
|
enable: true
|
||||||
syncFriends:
|
syncFriends:
|
||||||
enable: false
|
enable: false
|
||||||
cron: '*/5 * * * *' # 五分钟一次
|
cron: '*/5 * * * *' # 五分钟一次
|
||||||
waterGroup:
|
groupSummary:
|
||||||
enable: true
|
enable: true
|
||||||
|
cron: '30 0 * * *' # 每天0:30
|
||||||
|
waterGroup:
|
||||||
|
enable: false
|
||||||
cron:
|
cron:
|
||||||
yesterday: '30 9 * * *' # 每天9:30
|
yesterday: '30 9 * * *' # 每天9:30
|
||||||
week: '30 9 * * 1' # 每周一9:30
|
week: '30 9 * * 1' # 每周一9:30
|
||||||
@ -53,6 +56,8 @@ ai:
|
|||||||
enable: false
|
enable: false
|
||||||
# 模型,不填默认gpt-3.5-turbo-0613
|
# 模型,不填默认gpt-3.5-turbo-0613
|
||||||
model: gpt-3.5-turbo-0613
|
model: gpt-3.5-turbo-0613
|
||||||
|
# 群聊总结模型
|
||||||
|
summaryModel: gpt-4-0613
|
||||||
# OpenAI Api key
|
# OpenAI Api key
|
||||||
apiKey: sk-xxxx
|
apiKey: sk-xxxx
|
||||||
# 接口代理域名,不填默认ChatGPT官方地址
|
# 接口代理域名,不填默认ChatGPT官方地址
|
||||||
|
@ -5,6 +5,7 @@ package config
|
|||||||
type ai struct {
|
type ai struct {
|
||||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用AI
|
Enable bool `json:"enable" yaml:"enable"` // 是否启用AI
|
||||||
Model string `json:"model" yaml:"model"` // 模型
|
Model string `json:"model" yaml:"model"` // 模型
|
||||||
|
SummaryModel string `json:"summaryModel" yaml:"summaryModel"` // 总结模型
|
||||||
ApiKey string `json:"apiKey" yaml:"apiKey"` // API Key
|
ApiKey string `json:"apiKey" yaml:"apiKey"` // API Key
|
||||||
BaseUrl string `json:"baseUrl" yaml:"baseUrl"` // API地址
|
BaseUrl string `json:"baseUrl" yaml:"baseUrl"` // API地址
|
||||||
Personality string `json:"personality" yaml:"personality"` // 人设
|
Personality string `json:"personality" yaml:"personality"` // 人设
|
||||||
|
@ -6,6 +6,7 @@ type task struct {
|
|||||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用
|
Enable bool `json:"enable" yaml:"enable"` // 是否启用
|
||||||
SyncFriends syncFriends `json:"syncFriends" yaml:"syncFriends"` // 同步好友
|
SyncFriends syncFriends `json:"syncFriends" yaml:"syncFriends"` // 同步好友
|
||||||
WaterGroup waterGroup `json:"waterGroup" yaml:"waterGroup"` // 水群排行榜
|
WaterGroup waterGroup `json:"waterGroup" yaml:"waterGroup"` // 水群排行榜
|
||||||
|
GroupSummary syncFriends `json:"groupSummary" yaml:"groupSummary"` // 群聊总结
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncFriends
|
// syncFriends
|
||||||
|
@ -17,6 +17,7 @@ type Friend struct {
|
|||||||
AiModel string `json:"aiModel"` // AI模型
|
AiModel string `json:"aiModel"` // AI模型
|
||||||
EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行
|
EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行
|
||||||
EnableWelcome bool `json:"enableWelcome" gorm:"type:tinyint(1) default 0 not null"` // 是否启用迎新
|
EnableWelcome bool `json:"enableWelcome" gorm:"type:tinyint(1) default 0 not null"` // 是否启用迎新
|
||||||
|
EnableSummary bool `json:"enableSummary" gorm:"type:tinyint(1) default 0 not null"` // 是否启用总结
|
||||||
IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常
|
IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,4 +28,5 @@ func Init(g *gin.Engine) {
|
|||||||
api.PUT("/grouprank/status", app.ChangeEnableGroupRankStatus) // 修改是否开启水群排行榜状态
|
api.PUT("/grouprank/status", app.ChangeEnableGroupRankStatus) // 修改是否开启水群排行榜状态
|
||||||
api.PUT("/grouprank/skip", app.ChangeSkipGroupRankStatus) // 修改是否跳过水群排行榜状态
|
api.PUT("/grouprank/skip", app.ChangeSkipGroupRankStatus) // 修改是否跳过水群排行榜状态
|
||||||
api.GET("/group/users", app.GetGroupUsers) // 获取群成员列表
|
api.GET("/group/users", app.GetGroupUsers) // 获取群成员列表
|
||||||
|
api.PUT("/summary/status", app.ChangeEnableSummaryStatus) // 修改是否开启群聊总结状态
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,15 @@ func GetAllEnableChatRank() (records []entity.Friend, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllEnableSummary
|
||||||
|
// @description: 取出所有启用了总结的群组
|
||||||
|
// @return records
|
||||||
|
// @return err
|
||||||
|
func GetAllEnableSummary() (records []entity.Friend, err error) {
|
||||||
|
err = client.MySQL.Where("enable_summary = ?", 1).Where("wxid LIKE '%@chatroom'").Find(&records).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// CheckIsEnableCommand
|
// CheckIsEnableCommand
|
||||||
// @description: 检查用户是否启用了指令
|
// @description: 检查用户是否启用了指令
|
||||||
// @param userId
|
// @param userId
|
||||||
|
@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"go-wechat/client"
|
"go-wechat/client"
|
||||||
"go-wechat/entity"
|
"go-wechat/entity"
|
||||||
|
"go-wechat/vo"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -39,3 +40,22 @@ func SaveMessage(msg entity.Message) {
|
|||||||
go updateLastActive(msg)
|
go updateLastActive(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTextMessagesById
|
||||||
|
// @description: 根据群id或者用户Id获取消息
|
||||||
|
// @param id
|
||||||
|
// @return records
|
||||||
|
// @return err
|
||||||
|
func GetTextMessagesById(id string) (records []vo.TextMessageItem, err error) {
|
||||||
|
tx := client.MySQL.
|
||||||
|
Table("`t_message` AS tm").
|
||||||
|
Joins("LEFT JOIN t_group_user AS tgu ON tm.group_user = tgu.wxid AND tgu.group_id = tm.from_user").
|
||||||
|
Select("tgu.nickname", "IF( tm.type = 49, EXTRACTVALUE ( tm.content, \"/msg/appmsg/title\" ), tm.content ) AS message").
|
||||||
|
Where("tm.`from_user` = ?", id).
|
||||||
|
Where(`(tm.type = 1 OR ( tm.type = 49 AND EXTRACTVALUE ( tm.content, "/msg/appmsg/type" ) = '57' ))`).
|
||||||
|
Where("DATE ( tm.create_at ) = DATE ( CURDATE() - INTERVAL 1 DAY )").
|
||||||
|
Order("tm.create_at ASC")
|
||||||
|
|
||||||
|
err = tx.Find(&records).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
81
tasks/summary/summary.go
Normal file
81
tasks/summary/summary.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package summary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
"go-wechat/config"
|
||||||
|
"go-wechat/service"
|
||||||
|
"go-wechat/utils"
|
||||||
|
"go-wechat/vo"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AiSummary
|
||||||
|
// @description: AI总结群聊记录
|
||||||
|
func AiSummary() {
|
||||||
|
groups, err := service.GetAllEnableSummary()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取启用了聊天排行榜的群组失败, 错误信息: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
// 获取对话记录
|
||||||
|
var records []vo.TextMessageItem
|
||||||
|
if records, err = service.GetTextMessagesById(group.Wxid); err != nil {
|
||||||
|
log.Printf("获取群[%s]对话记录失败, 错误信息: %v", group.Wxid, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//if len(records) < 100 {
|
||||||
|
// log.Printf("群[%s]对话记录不足100条,跳过总结", group.Wxid)
|
||||||
|
// continue
|
||||||
|
//}
|
||||||
|
// 组装对话记录为字符串
|
||||||
|
var content []string
|
||||||
|
for _, record := range records {
|
||||||
|
content = append(content, fmt.Sprintf("%s: %s\n-----end-----", record.Nickname, record.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("请帮我总结一下一下的群聊内容的梗概(内容尽可能详细一些)。\n"+
|
||||||
|
"注意,他们可能是多个话题,请仔细甄别。\n"+
|
||||||
|
"每一行代表一个人的发言,每一行的的格式为: \n{nickname}: {content}\n-----end-----"+
|
||||||
|
"\n\n聊天记录如下: \n%s", 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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("群聊记录总结失败: %v", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回消息为空
|
||||||
|
if resp.Choices[0].Message.Content == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
replyMsg := fmt.Sprintf("#昨日消息总结\n\n%s", resp.Choices[0].Message.Content)
|
||||||
|
utils.SendMessage(group.Wxid, "", replyMsg, 0)
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/go-co-op/gocron"
|
"github.com/go-co-op/gocron"
|
||||||
"go-wechat/config"
|
"go-wechat/config"
|
||||||
"go-wechat/tasks/friends"
|
"go-wechat/tasks/friends"
|
||||||
|
"go-wechat/tasks/summary"
|
||||||
"go-wechat/tasks/watergroup"
|
"go-wechat/tasks/watergroup"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
@ -37,6 +38,13 @@ func InitTasks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 群聊总结
|
||||||
|
if config.Conf.Task.GroupSummary.Enable {
|
||||||
|
log.Printf("群聊总结任务已启用,执行表达式: %s", config.Conf.Task.GroupSummary.Cron)
|
||||||
|
_, _ = s.Cron(config.Conf.Task.GroupSummary.Cron).Do(summary.AiSummary)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 更新好友列表
|
// 更新好友列表
|
||||||
if config.Conf.Task.SyncFriends.Enable {
|
if config.Conf.Task.SyncFriends.Enable {
|
||||||
log.Printf("更新好友列表任务已启用,执行表达式: %s", config.Conf.Task.SyncFriends.Cron)
|
log.Printf("更新好友列表任务已启用,执行表达式: %s", config.Conf.Task.SyncFriends.Cron)
|
||||||
|
@ -108,6 +108,7 @@
|
|||||||
<th>是否在通讯录</th>
|
<th>是否在通讯录</th>
|
||||||
<th>是否启用AI</th>
|
<th>是否启用AI</th>
|
||||||
<th>是否启用水群排行榜</th>
|
<th>是否启用水群排行榜</th>
|
||||||
|
<th>是否启用聊天记录总结</th>
|
||||||
<th>是否启用迎新</th>
|
<th>是否启用迎新</th>
|
||||||
<th>是否启用指令</th>
|
<th>是否启用指令</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
@ -164,6 +165,15 @@
|
|||||||
<div class="swap-off">❌已禁用</div>
|
<div class="swap-off">❌已禁用</div>
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- EnableSummary -->
|
||||||
|
<label class="swap swap-flip {{ checkSwap .EnableSummary }}">
|
||||||
|
<input type="checkbox" onclick="changeSummaryEnableStatus({{.Wxid}})"/>
|
||||||
|
|
||||||
|
<div class="swap-on">✔️已启用</div>
|
||||||
|
<div class="swap-off">❌已禁用</div>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<label class="swap swap-flip {{ checkSwap .EnableWelcome }}">
|
<label class="swap swap-flip {{ checkSwap .EnableWelcome }}">
|
||||||
<input type="checkbox" onclick="changeWelcomeEnableStatus({{.Wxid}})"/>
|
<input type="checkbox" onclick="changeWelcomeEnableStatus({{.Wxid}})"/>
|
||||||
|
@ -12,6 +12,7 @@ function changeAiEnableStatus(wxId) {
|
|||||||
}
|
}
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||||
|
alert(`${response.data}`)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.log(`错误信息: ${error}`);
|
console.log(`错误信息: ${error}`);
|
||||||
alert("修改失败")
|
alert("修改失败")
|
||||||
@ -29,6 +30,25 @@ function changeGroupRankEnableStatus(wxId) {
|
|||||||
}
|
}
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||||
|
alert(`${response.data}`)
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.log(`错误信息: ${error}`);
|
||||||
|
alert("修改失败")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改水群排行榜状态
|
||||||
|
function changeSummaryEnableStatus(wxId) {
|
||||||
|
// console.log("修改聊天记录总结开启状态: ", wxId)
|
||||||
|
axios({
|
||||||
|
method: 'put',
|
||||||
|
url: '/api/summary/status',
|
||||||
|
data: {
|
||||||
|
wxId: wxId
|
||||||
|
}
|
||||||
|
}).then(function (response) {
|
||||||
|
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||||
|
alert(`${response.data}`)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.log(`错误信息: ${error}`);
|
console.log(`错误信息: ${error}`);
|
||||||
alert("修改失败")
|
alert("修改失败")
|
||||||
@ -45,6 +65,7 @@ function changeWelcomeEnableStatus(wxId) {
|
|||||||
}
|
}
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||||
|
alert(`${response.data}`)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.log(`错误信息: ${error}`);
|
console.log(`错误信息: ${error}`);
|
||||||
alert("修改失败")
|
alert("修改失败")
|
||||||
@ -61,6 +82,7 @@ function changeCommandEnableStatus(wxId) {
|
|||||||
}
|
}
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||||
|
alert(`${response.data}`)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.log(`错误信息: ${error}`);
|
console.log(`错误信息: ${error}`);
|
||||||
alert("修改失败")
|
alert("修改失败")
|
||||||
@ -79,6 +101,7 @@ function changeUserGroupRankSkipStatus(groupId, userId) {
|
|||||||
}
|
}
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||||
|
alert(`${response.data}`)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.log(`错误信息: ${error}`);
|
console.log(`错误信息: ${error}`);
|
||||||
alert("修改失败")
|
alert("修改失败")
|
||||||
|
@ -18,6 +18,7 @@ type FriendItem struct {
|
|||||||
EnableChatRank bool // 是否使用聊天排行
|
EnableChatRank bool // 是否使用聊天排行
|
||||||
EnableWelcome bool // 是否使用迎新
|
EnableWelcome bool // 是否使用迎新
|
||||||
EnableCommand bool // 是否启用指令
|
EnableCommand bool // 是否启用指令
|
||||||
|
EnableSummary bool // 是否启用总结
|
||||||
IsOk bool // 是否还在通讯库(群聊是要还在群里也算)
|
IsOk bool // 是否还在通讯库(群聊是要还在群里也算)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
vo/message.go
Normal file
8
vo/message.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package vo
|
||||||
|
|
||||||
|
// TextMessageItem
|
||||||
|
// @description: 文字消息
|
||||||
|
type TextMessageItem struct {
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user