feat: add plugin aichat

This commit is contained in:
源文雨 2025-02-14 17:23:00 +09:00
parent 24955dc4a7
commit 735c3cdde1
6 changed files with 258 additions and 0 deletions

View File

@ -1537,6 +1537,17 @@ print("run[CQ:image,file="+j["img"]+"]")
### *低优先级*
<details>
<summary>OpenAI聊天</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat"`
- [x] 设置AI聊天触发概率10
- [x] 设置AI聊天密钥xxx
- [x] 设置AI聊天模型名xxx
- [x] 设置AI聊天系统提示词xxx
</details>
<details>
<summary>骂人</summary>

1
go.mod
View File

@ -21,6 +21,7 @@ require (
github.com/disintegration/imaging v1.6.2
github.com/fumiama/ahsai v0.1.0
github.com/fumiama/cron v1.3.0
github.com/fumiama/deepinfra v0.0.0-20250214072937-12ba46058885
github.com/fumiama/go-base16384 v1.7.0
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3

2
go.sum
View File

@ -57,6 +57,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4=
github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk=
github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo=
github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY=
github.com/fumiama/deepinfra v0.0.0-20250214072937-12ba46058885 h1:AHuorF/H+9q/+A3CclMbr5W+kbpaMw1r5E4UUC7ETUQ=
github.com/fumiama/deepinfra v0.0.0-20250214072937-12ba46058885/go.mod h1:pNn32xTo/u72cTCIq3EejJQPTZqg420Xb3XI+Ou7ZmU=
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=

View File

@ -167,6 +167,8 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat" // AI聊天
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse" // 骂人
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/thesaurus" // 词典匹配回复

51
plugin/aichat/list.go Normal file
View File

@ -0,0 +1,51 @@
package aichat
import (
"sync"
"github.com/fumiama/deepinfra"
"github.com/fumiama/deepinfra/model"
)
const listcap = 6
type list struct {
mu sync.RWMutex
m map[int64][]string
}
func newlist() list {
return list{
m: make(map[int64][]string, 64),
}
}
func (l *list) add(grp int64, txt string) {
l.mu.Lock()
defer l.mu.Unlock()
msgs, ok := l.m[grp]
if !ok {
msgs = make([]string, 1, listcap)
msgs[0] = txt
l.m[grp] = msgs
return
}
if len(msgs) < cap(msgs) {
msgs = append(msgs, txt)
l.m[grp] = msgs
return
}
copy(msgs[:], msgs[1:])
msgs[len(msgs)-1] = txt
l.m[grp] = msgs
}
func (l *list) body(mn, sysp string, grp int64) deepinfra.Model {
m := model.NewCustom(mn, "", 0.7, 0.9, 1024).System(sysp)
l.mu.RLock()
defer l.mu.RUnlock()
for _, msg := range l.m[grp] {
_ = m.User(msg)
}
return m
}

191
plugin/aichat/main.go Normal file
View File

@ -0,0 +1,191 @@
// Package aichat OpenAI聊天
package aichat
import (
"math/rand"
"os"
"strconv"
"strings"
"sync/atomic"
"unsafe"
"github.com/fumiama/deepinfra"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
)
var (
api *deepinfra.API
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Extra: control.ExtraFromString("aichat"),
Brief: "OpenAI聊天",
Help: "- 设置AI聊天触发概率10\n- 设置AI聊天密钥xxx\n- 设置AI聊天模型名xxx\n- 设置AI聊天系统提示词xxx",
PrivateDataFolder: "aichat",
})
lst = newlist()
)
var (
modelname = "deepseek-ai/DeepSeek-R1"
systemprompt = "你正在QQ群与用户聊天用户发送了消息。按自己的心情简短思考后条理清晰地回应**一句话**,禁止回应多句。"
)
func init() {
mf := en.DataFolder() + "model.txt"
sf := en.DataFolder() + "system.txt"
if file.IsExist(mf) {
data, err := os.ReadFile(mf)
if err != nil {
logrus.Warnln("read model", err)
} else {
modelname = string(data)
}
}
if file.IsExist(sf) {
data, err := os.ReadFile(sf)
if err != nil {
logrus.Warnln("read system", err)
} else {
systemprompt = string(data)
}
}
en.OnMessage(func(ctx *zero.Ctx) bool {
txt := ctx.ExtractPlainText()
ctx.State["aichat_txt"] = txt
return txt != ""
}).SetBlock(false).Handle(func(ctx *zero.Ctx) {
lst.add(ctx.Event.GroupID, ctx.State["aichat_txt"].(string))
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return
}
rate := c.GetData(gid)
if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) {
return
}
key := ""
err := c.GetExtra(&key)
if err != nil {
logrus.Warnln("ERROR: get extra err:", err)
return
}
if key == "" {
logrus.Warnln("ERROR: get extra err: empty key")
return
}
var x deepinfra.API
y := &x
if api == nil {
x = deepinfra.NewAPI(deepinfra.APIDeepInfra, key)
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&api)), unsafe.Pointer(&x))
} else {
y = api
}
data, err := y.Request(lst.body(modelname, systemprompt, gid))
if err != nil {
logrus.Warnln("[niniqun] post err:", err)
return
}
txt := strings.Trim(data, "\n  ")
if len(txt) > 0 {
lst.add(ctx.Event.GroupID, txt)
nick := zero.BotConfig.NickName[rand.Intn(len(zero.BotConfig.NickName))]
txt = strings.ReplaceAll(txt, "{name}", ctx.CardOrNickName(ctx.Event.UserID))
txt = strings.ReplaceAll(txt, "{me}", nick)
id := any(nil)
if ctx.Event.IsToMe {
id = ctx.Event.MessageID
}
for _, t := range strings.Split(txt, "{segment}") {
if t == "" {
continue
}
if id != nil {
id = ctx.SendChain(message.Reply(id), message.Text(t))
} else {
id = ctx.SendChain(message.Text(t))
}
process.SleepAbout1sTo2s()
}
}
})
en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
r, err := strconv.Atoi(args)
if err != nil {
ctx.SendChain(message.Text("ERROR: parse rate err: ", err))
return
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
c.SetData(gid, int64(r&0xff))
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天密钥", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
err := c.SetExtra(&args)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
})
en.OnPrefix("设置AI聊天模型名", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
modelname = args
err := os.WriteFile(mf, []byte(args), 0644)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
})
en.OnPrefix("设置AI聊天系统提示词", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
systemprompt = args
err := os.WriteFile(sf, []byte(args), 0644)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
})
}