22package aichat
33
44import (
5+ "errors"
56 "math/rand"
67 "strconv"
78 "strings"
4647 "- 设置AI聊天(不)以AI语音输出\n " +
4748 "- 查看AI聊天配置\n " +
4849 "- 重置AI聊天\n " +
49- "- 群聊总结 [消息数目]|群聊总结 1000\n " ,
50+ "- 群聊总结 [消息数目]|群聊总结 1000\n " +
51+ "- /gpt [内容] (使用大模型聊天)\n " ,
52+
5053 PrivateDataFolder : "aichat" ,
5154 })
5255)
6164 limit = ctxext .NewLimiterManager (time .Second * 30 , 1 )
6265)
6366
67+ // getModelParams 获取模型参数:温度(float32(temp)/100)、TopP和最大长度
68+ func getModelParams (temp int64 ) (temperature float32 , topp float32 , maxn uint ) {
69+ // 处理温度参数
70+ if temp <= 0 {
71+ temp = 70 // default setting
72+ }
73+ if temp > 100 {
74+ temp = 100
75+ }
76+ temperature = float32 (temp ) / 100
77+
78+ // 处理TopP参数
79+ topp = cfg .TopP
80+ if topp == 0 {
81+ topp = 0.9
82+ }
83+
84+ // 处理最大长度参数
85+ maxn = cfg .MaxN
86+ if maxn == 0 {
87+ maxn = 4096
88+ }
89+
90+ return temperature , topp , maxn
91+ }
92+
6493func init () {
6594 en .OnMessage (ensureconfig , func (ctx * zero.Ctx ) bool {
6695 return ctx .ExtractPlainText () != "" &&
@@ -88,39 +117,25 @@ func init() {
88117 return
89118 }
90119
91- if temp <= 0 {
92- temp = 70 // default setting
93- }
94- if temp > 100 {
95- temp = 100
96- }
120+ temperature , topp , maxn := getModelParams (temp )
97121
98122 x := deepinfra .NewAPI (cfg .API , cfg .Key )
99123 var mod model.Protocol
100- maxn := cfg .MaxN
101- if maxn == 0 {
102- maxn = 4096
103- }
104- topp := cfg .TopP
105- if topp == 0 {
106- topp = 0.9
107- }
108-
109124 switch cfg .Type {
110125 case 0 :
111126 mod = model .NewOpenAI (
112127 cfg .ModelName , cfg .Separator ,
113- float32 ( temp ) / 100 , topp , maxn ,
128+ temperature , topp , maxn ,
114129 )
115130 case 1 :
116131 mod = model .NewOLLaMA (
117132 cfg .ModelName , cfg .Separator ,
118- float32 ( temp ) / 100 , topp , maxn ,
133+ temperature , topp , maxn ,
119134 )
120135 case 2 :
121136 mod = model .NewGenAI (
122137 cfg .ModelName ,
123- float32 ( temp ) / 100 , topp , maxn ,
138+ temperature , topp , maxn ,
124139 )
125140 default :
126141 logrus .Warnln ("[aichat] unsupported AI type" , cfg .Type )
@@ -319,17 +334,26 @@ func init() {
319334 // 添加群聊总结功能
320335 en .OnRegex (`^群聊总结\s?(\d*)$` , ensureconfig , zero .OnlyGroup , zero .AdminPermission ).SetBlock (true ).Limit (limit .LimitByGroup ).Handle (func (ctx * zero.Ctx ) {
321336 ctx .SendChain (message .Text ("少女思考中..." ))
337+ gid := ctx .Event .GroupID
338+ if gid == 0 {
339+ gid = - ctx .Event .UserID
340+ }
341+ c , ok := ctx .State ["manager" ].(* ctrl.Control [* zero.Ctx ])
342+ if ! ok {
343+ return
344+ }
345+ rate := c .GetData (gid )
346+ temp := (rate >> 8 ) & 0xff
322347 p , _ := strconv .ParseInt (ctx .State ["regex_matched" ].([]string )[1 ], 10 , 64 )
323348 if p > 1000 {
324349 p = 1000
325350 }
326351 if p == 0 {
327352 p = 200
328353 }
329- gid := ctx .Event .GroupID
330354 group := ctx .GetGroupInfo (gid , false )
331355 if group .MemberCount == 0 {
332- ctx .SendChain (message .Text (zero .BotConfig .NickName [0 ], "未加入" , group .Name , "(" , gid , "),无法获取摘要 " ))
356+ ctx .SendChain (message .Text (zero .BotConfig .NickName [0 ], "未加入" , group .Name , "(" , gid , "),无法获取总结 " ))
333357 return
334358 }
335359
@@ -350,8 +374,13 @@ func init() {
350374 return
351375 }
352376
353- // 调用大模型API进行摘要
354- summary , err := summarizeMessages (messages )
377+ // 构造总结请求提示
378+ summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n " +
379+ strings .Join (messages , "\n " )
380+
381+ // 调用大模型API进行总结
382+ summary , err := llmchat (summaryPrompt , temp )
383+
355384 if err != nil {
356385 ctx .SendChain (message .Text ("ERROR: " , err ))
357386 return
@@ -367,34 +396,143 @@ func init() {
367396 b .WriteString (" 条消息总结:\n \n " )
368397 b .WriteString (summary )
369398
370- // 分割总结内容为多段
371- parts := strings .Split (b .String (), "\n \n " )
372- msg := make (message.Message , 0 , len (parts ))
373- for _ , part := range parts {
374- if part != "" {
375- msg = append (msg , ctxext .FakeSenderForwardNode (ctx , message .Text (part )))
399+ // 分割总结内容为多段(按1000字符长度切割)
400+ summaryText := b .String ()
401+ msg := make (message.Message , 0 )
402+ for len (summaryText ) > 0 {
403+ if len (summaryText ) <= 1000 {
404+ msg = append (msg , ctxext .FakeSenderForwardNode (ctx , message .Text (summaryText )))
405+ break
376406 }
407+
408+ // 查找1000字符内的最后一个换行符,尽量在换行处分割
409+ chunk := summaryText [:1000 ]
410+ lastNewline := strings .LastIndex (chunk , "\n " )
411+ if lastNewline > 0 {
412+ chunk = summaryText [:lastNewline + 1 ]
413+ }
414+
415+ msg = append (msg , ctxext .FakeSenderForwardNode (ctx , message .Text (chunk )))
416+ summaryText = summaryText [len (chunk ):]
417+ }
418+ if len (msg ) > 0 {
419+ ctx .Send (msg )
420+ }
421+ })
422+
423+ // 添加 /gpt 命令处理(同时支持回复消息和直接使用)
424+ en .OnKeyword ("/gpt" , ensureconfig ).SetBlock (true ).Handle (func (ctx * zero.Ctx ) {
425+ gid := ctx .Event .GroupID
426+ if gid == 0 {
427+ gid = - ctx .Event .UserID
428+ }
429+ c , ok := ctx .State ["manager" ].(* ctrl.Control [* zero.Ctx ])
430+ if ! ok {
431+ return
432+ }
433+ rate := c .GetData (gid )
434+ temp := (rate >> 8 ) & 0xff
435+ text := ctx .MessageString ()
436+
437+ var query string
438+ var replyContent string
439+
440+ // 检查是否是回复消息 (使用MessageElement检查而不是CQ码)
441+ for _ , elem := range ctx .Event .Message {
442+ if elem .Type == "reply" {
443+ // 提取被回复的消息ID
444+ replyIDStr := elem .Data ["id" ]
445+ replyID , err := strconv .ParseInt (replyIDStr , 10 , 64 )
446+ if err == nil {
447+ // 获取被回复的消息内容
448+ replyMsg := ctx .GetMessage (replyID )
449+ if replyMsg .Elements != nil {
450+ replyContent = replyMsg .Elements .ExtractPlainText ()
451+ }
452+ }
453+ break // 找到回复元素后退出循环
454+ }
455+ }
456+
457+ // 提取 /gpt 后面的内容
458+ parts := strings .SplitN (text , "/gpt" , 2 )
459+
460+ var gContent string
461+ if len (parts ) > 1 {
462+ gContent = strings .TrimSpace (parts [1 ])
463+ }
464+
465+ // 组合内容:优先使用回复内容,如果同时有/gpt内容则拼接
466+ switch {
467+ case replyContent != "" && gContent != "" :
468+ query = replyContent + "\n " + gContent
469+ case replyContent != "" :
470+ query = replyContent
471+ case gContent != "" :
472+ query = gContent
473+ default :
474+ return
475+ }
476+
477+ // 调用大模型API进行聊天
478+ reply , err := llmchat (query , temp )
479+ if err != nil {
480+ ctx .SendChain (message .Text ("ERROR: " , err ))
481+ return
482+ }
483+
484+ // 分割总结内容为多段(按1000字符长度切割)
485+ msg := make (message.Message , 0 )
486+ for len (reply ) > 0 {
487+ if len (reply ) <= 1000 {
488+ msg = append (msg , ctxext .FakeSenderForwardNode (ctx , message .Text (reply )))
489+ break
490+ }
491+
492+ // 查找1000字符内的最后一个换行符,尽量在换行处分割
493+ chunk := reply [:1000 ]
494+ lastNewline := strings .LastIndex (chunk , "\n " )
495+ if lastNewline > 0 {
496+ chunk = reply [:lastNewline + 1 ]
497+ }
498+
499+ msg = append (msg , ctxext .FakeSenderForwardNode (ctx , message .Text (chunk )))
500+ reply = reply [len (chunk ):]
377501 }
378502 if len (msg ) > 0 {
379503 ctx .Send (msg )
380504 }
381505 })
382506}
383507
384- // summarizeMessages 调用大模型API进行消息摘要
385- func summarizeMessages (messages []string ) (string , error ) {
386- // 使用现有的AI配置进行摘要
387- x := deepinfra .NewAPI (cfg .API , cfg .Key )
388- mod := model .NewOpenAI (
389- cfg .ModelName , cfg .Separator ,
390- float32 (70 )/ 100 , 0.9 , 4096 ,
391- )
508+ // llmchat 调用大模型API包装
509+ func llmchat (prompt string , temp int64 ) (string , error ) {
510+ temperature , topp , maxn := getModelParams (temp ) // 使用默认温度70
392511
393- // 构造摘要请求提示
394- summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n \n " +
395- strings .Join (messages , "\n ---\n " )
512+ x := deepinfra .NewAPI (cfg .API , cfg .Key )
513+ var mod model.Protocol
514+ switch cfg .Type {
515+ case 0 :
516+ mod = model .NewOpenAI (
517+ cfg .ModelName , cfg .Separator ,
518+ temperature , topp , maxn ,
519+ )
520+ case 1 :
521+ mod = model .NewOLLaMA (
522+ cfg .ModelName , cfg .Separator ,
523+ temperature , topp , maxn ,
524+ )
525+ case 2 :
526+ mod = model .NewGenAI (
527+ cfg .ModelName ,
528+ temperature , topp , maxn ,
529+ )
530+ default :
531+ logrus .Warnln ("[aichat] unsupported AI type" , cfg .Type )
532+ return "" , errors .New ("不支持的AI类型" )
533+ }
396534
397- data , err := x .Request (mod .User (summaryPrompt ))
535+ data , err := x .Request (mod .User (prompt ))
398536 if err != nil {
399537 return "" , err
400538 }
0 commit comments