From 57c41a7db2762a546c627e07052a382645c014e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?=
<41315874+fumiama@users.noreply.github.com>
Date: Sat, 3 Jan 2026 23:36:49 +0800
Subject: [PATCH] feat(aichat): agent add memory
---
README.md | 23 +-
data | 2 +-
go.mod | 30 +--
go.sum | 80 +++----
main.go | 3 +
plugin/aichat/cfg.go | 302 -------------------------
plugin/aichat/main.go | 402 +++++-----------------------------
plugin/aichat/storage.go | 53 -----
plugin/aichat/storage_test.go | 149 -------------
plugin/aichatcfg/main.go | 144 ++++++++++++
plugin/llm/main.go | 246 +++++++++++++++++++++
11 files changed, 512 insertions(+), 922 deletions(-)
delete mode 100644 plugin/aichat/cfg.go
delete mode 100644 plugin/aichat/storage.go
delete mode 100644 plugin/aichat/storage_test.go
create mode 100644 plugin/aichatcfg/main.go
create mode 100644 plugin/llm/main.go
diff --git a/README.md b/README.md
index 4fd937f9..2c365b1b 100644
--- a/README.md
+++ b/README.md
@@ -917,6 +917,15 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 疯狂星期四
+
+
+ 大模型聊天和群聊总结
+
+ `_ "github.com/FloatTech/ZeroBot-Plugin/plugin/llm"`
+
+ - [x] 群聊总结 [消息数目]|群聊总结 1000
+ - [x] /gpt [内容](使用大模型聊天)
+
kokomi原神面板
@@ -1604,9 +1613,9 @@ print("run[CQ:image,file="+j["img"]+"]")
### *低优先级*
- OpenAI聊天
+ 大模型聊天和Agent配置
- `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat"`
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichatcfg"`
- [x] 设置AI聊天触发概率10
- [x] 设置AI聊天温度80
@@ -1626,8 +1635,14 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置AI聊天(不)以AI语音输出
- [x] 查看AI聊天配置
- [x] 重置AI聊天
- - [x] 群聊总结 [消息数目]|群聊总结 1000
- - [x] /gpt [内容](使用大模型聊天)
+
+
+
+ 大模型聊天和Agent
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat"`
+
+ - [x] (随意聊天, 概率匹配)
diff --git a/data b/data
index 1b0abcd3..74e3bf5d 160000
--- a/data
+++ b/data
@@ -1 +1 @@
-Subproject commit 1b0abcd3fe4943fa3298885cf0311e8d94a02c0b
+Subproject commit 74e3bf5dc8639de19b1d4a41c79b0a4be14c4667
diff --git a/go.mod b/go.mod
index 82cba57b..a6b164eb 100644
--- a/go.mod
+++ b/go.mod
@@ -12,11 +12,11 @@ require (
github.com/FloatTech/sqlite v1.7.2
github.com/FloatTech/ttl v0.0.0-20250224045156-012b1463287d
github.com/FloatTech/zbpctrl v1.7.1
- github.com/FloatTech/zbputils v1.7.2-0.20251223092310-25b804fef625
+ github.com/FloatTech/zbputils v1.7.2-0.20260103151557-34c60f3591d5
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
github.com/RomiChan/websocket v1.4.3-0.20251002072000-d3eb41798438
github.com/Tnze/go-mc v1.20.2
- github.com/antchfx/htmlquery v1.3.4
+ github.com/antchfx/htmlquery v1.3.5
github.com/corona10/goimagehash v1.1.1-0.20240121134706-d8115886f360
github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3
github.com/disintegration/imaging v1.6.2
@@ -24,7 +24,7 @@ require (
github.com/fumiama/cron v1.3.0
github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a
github.com/fumiama/go-base16384 v1.7.1
- github.com/fumiama/go-onebot-agent v0.0.0-20251221163750-c11c679e4636
+ github.com/fumiama/go-onebot-agent v0.0.0-20260103153303-915960b3a069
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
github.com/fumiama/imgsz v0.0.4
@@ -43,33 +43,33 @@ require (
github.com/mroth/weightedrand v1.0.0
github.com/notnil/chess v1.10.0
github.com/pkg/errors v0.9.1
- github.com/shirou/gopsutil/v4 v4.25.11
+ github.com/shirou/gopsutil/v4 v4.25.12
github.com/sirupsen/logrus v1.9.3
github.com/tidwall/gjson v1.18.0
github.com/wcharczuk/go-chart/v2 v2.1.2
- github.com/wdvxdr1123/ZeroBot v1.8.2
- gitlab.com/gomidi/midi/v2 v2.3.16
+ github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260103120253-8a8f1347f983
+ gitlab.com/gomidi/midi/v2 v2.3.18
golang.org/x/image v0.34.0
golang.org/x/sys v0.39.0
golang.org/x/text v0.32.0
)
require (
- github.com/PuerkitoBio/goquery v1.10.3 // indirect
+ github.com/PuerkitoBio/goquery v1.8.0 // indirect
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
- github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
- github.com/andybalholm/cascadia v1.3.3 // indirect
+ github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect
+ github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/antchfx/xpath v1.3.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/ebitengine/oto/v3 v3.4.0 // indirect
+ github.com/ebitengine/oto/v3 v3.3.2 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect
github.com/fumiama/go-simple-protobuf v0.2.0 // indirect
github.com/fumiama/gofastTEA v0.1.3 // indirect
github.com/fumiama/orbyte v0.0.0-20251002065953-3bb358367eb5 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
- github.com/go-ole/go-ole v1.3.0 // indirect
- github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/gopxl/beep/v2 v2.1.1 // indirect
github.com/jfreymuth/oggvorbis v1.0.5 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
@@ -77,9 +77,9 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect
- github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mmcdole/goxpp v1.1.1 // indirect
+ github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
@@ -88,7 +88,7 @@ require (
github.com/pkumza/numcn v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
- github.com/tetratelabs/wazero v1.9.0 // indirect
+ github.com/tetratelabs/wazero v1.5.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
diff --git a/go.sum b/go.sum
index fc5d32ec..9a8472f1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,5 @@
github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw=
github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/FloatTech/AnimeAPI v1.7.1-0.20251028071248-0c948e3db65c h1:fmvlRUzwoK6KdoRSW+XeTQ9myKHimd0pV6GbmRJLNRo=
github.com/FloatTech/AnimeAPI v1.7.1-0.20251028071248-0c948e3db65c/go.mod h1:cuDd67B23xmICSmFBhWzXN51blod2BlM1liN9Ux0pSc=
github.com/FloatTech/floatbox v0.0.0-20251002074805-f95cbc7edb31 h1:2K+/M64ixD1Pg5hr00Nbxr7GoWQOgahvpmp1pAMnrYc=
@@ -17,11 +16,11 @@ github.com/FloatTech/ttl v0.0.0-20250224045156-012b1463287d h1:mUQ/c3wXKsUGa4Sg9
github.com/FloatTech/ttl v0.0.0-20250224045156-012b1463287d/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/FloatTech/zbpctrl v1.7.1 h1:0yPEmCForhyMbnhTckmjDUFFDZgQp1RjO2bVF4ZVqOs=
github.com/FloatTech/zbpctrl v1.7.1/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE=
-github.com/FloatTech/zbputils v1.7.2-0.20251223092310-25b804fef625 h1:7CnuhyOR1WhA5A/Pf2Lh0iOQBvZnMz1KLXWB/hJbKwc=
-github.com/FloatTech/zbputils v1.7.2-0.20251223092310-25b804fef625/go.mod h1:LBanthv/2ExuzTMCuMDXLkvSPxDJTyNp4MrLoJQzIL4=
+github.com/FloatTech/zbputils v1.7.2-0.20260103151557-34c60f3591d5 h1:kN8EtrKmcK1g+WMb2czOLpiNyagljO7f9BqdK6KcAaY=
+github.com/FloatTech/zbputils v1.7.2-0.20260103151557-34c60f3591d5/go.mod h1:1qUet9ztj6DMabvtCNl4vVwb6mVSYUHeAbpkY3CBMvk=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
-github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
-github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
+github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
+github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
github.com/RomiChan/websocket v1.4.3-0.20251002072000-d3eb41798438 h1:I0bdwHZ+2DY45b39xPoTD2u+Z8zhvBuu9aZfjMZeiZM=
@@ -30,17 +29,13 @@ github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5UaBEQLjcvn7aAU01nqU/NUyOBEU+ew=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
-github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
-github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
+github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
-github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
-github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
-github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
-github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
-github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
-github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM=
-github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
+github.com/antchfx/htmlquery v1.3.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=
+github.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=
github.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ=
github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/corona10/goimagehash v1.1.1-0.20240121134706-d8115886f360 h1:SvD9vQN+3r0wskoSrQ7IOyDmOtRIXhT3rlnf819r/bY=
@@ -56,8 +51,8 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/ebitengine/oto/v3 v3.4.0 h1:br0PgASsEWaoWn38b2Goe7m1GKFYfNgnsjSd5Gg+/bQ=
-github.com/ebitengine/oto/v3 v3.4.0/go.mod h1:IOleLVD0m+CMak3mRVwsYY8vTctQgOM0iiL6S7Ar7eI=
+github.com/ebitengine/oto/v3 v3.3.2 h1:VTWBsKX9eb+dXzaF4jEwQbs4yWIdXukJ0K40KgkpYlg=
+github.com/ebitengine/oto/v3 v3.3.2/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 h1:BBade+JlV/f7JstZ4pitd4tHhpN+w+6I+LyOS7B4fyU=
@@ -72,8 +67,8 @@ github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a h1:a0+2vaXajfxsN
github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a/go.mod h1:uqsWK/GM9OvKV0pXZOQB63rWugBbiXInY8E1JoRKhkg=
github.com/fumiama/go-base16384 v1.7.1 h1:1P1x6FWRvd7PtbH4idDAGWAjKKcVxggxlROYKRXbw58=
github.com/fumiama/go-base16384 v1.7.1/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
-github.com/fumiama/go-onebot-agent v0.0.0-20251221163750-c11c679e4636 h1:PzjoPyYQnrdhsiPj366q6QwyN2amw/msIciRAtt+P7s=
-github.com/fumiama/go-onebot-agent v0.0.0-20251221163750-c11c679e4636/go.mod h1:rTrS23rvTYuZcSngENJTvcBFTz1nGsImSv+bW7yfhqs=
+github.com/fumiama/go-onebot-agent v0.0.0-20260103153303-915960b3a069 h1:s+0YH5thC576Lggf7v3SBN3RQYDMytS5ZtKCMMtuY1E=
+github.com/fumiama/go-onebot-agent v0.0.0-20260103153303-915960b3a069/go.mod h1:rTrS23rvTYuZcSngENJTvcBFTz1nGsImSv+bW7yfhqs=
github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=
github.com/fumiama/go-registry v0.2.7/go.mod h1:m+wp5fF8dYgVoFkBPZl+vlK90loymaJE0JCtocVQLEs=
github.com/fumiama/go-simple-protobuf v0.2.0 h1:ACyN1MAlu7pDR3EszWgzUeNP+IRsSHwH6V9JCJA5R5o=
@@ -101,9 +96,8 @@ github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6/go.mod h1:lEaZsT
github.com/gabriel-vasile/mimetype v1.0.4/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
-github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@@ -112,9 +106,9 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZ
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@@ -139,7 +133,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY=
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
@@ -150,16 +143,16 @@ github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d h1:hTRDIpJ1FjS9ULJuEzu69n
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d/go.mod h1:7xD3p0XnHvJFQ3t/stEJd877CSIMkH/fACVWen5pYnc=
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5 h1:wnbHIeP1UX8ClYEWKGnw66PfYvReCHu9G5lXSte3Sqc=
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5/go.mod h1:7KaV9YIR92M1FpbczAcfYQ3UZ5ayT27pNtunDmXvLBo=
-github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
-github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
-github.com/mmcdole/goxpp v1.1.1 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8=
-github.com/mmcdole/goxpp v1.1.1/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
+github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk=
+github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -187,8 +180,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
-github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
+github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
+github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -197,8 +190,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
-github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
+github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
+github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
@@ -213,19 +206,16 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
-github.com/wdvxdr1123/ZeroBot v1.8.2 h1:H4qNHgeYLLm3ID5T9MKnO4fI0SWWl0rFCGLCUr8u10M=
-github.com/wdvxdr1123/ZeroBot v1.8.2/go.mod h1:trueIIVRywKJa3ov4QphzVvzYzgCNrlXdf9JvPJOFW8=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260103120253-8a8f1347f983 h1:hb2FUDooAf3u32wCTgJcBBuGPZF1sjgj8NfJaSy529s=
+github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260103120253-8a8f1347f983/go.mod h1:trueIIVRywKJa3ov4QphzVvzYzgCNrlXdf9JvPJOFW8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-gitlab.com/gomidi/midi/v2 v2.3.16 h1:yufWSENyjnJ4LFQa9BerzUm4E4aLfTyzw5nmnCteO0c=
-gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
+gitlab.com/gomidi/midi/v2 v2.3.18 h1:sj2fOhtvOe+zI8YJe8qTxLw5zv0ntULLUDwcFOaZQbI=
+gitlab.com/gomidi/midi/v2 v2.3.18/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
@@ -237,7 +227,6 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -250,8 +239,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
@@ -262,7 +251,6 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@@ -275,15 +263,13 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -304,6 +290,7 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@@ -316,7 +303,6 @@ golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@@ -324,14 +310,12 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
diff --git a/main.go b/main.go
index acf9cd4c..6e41ab2d 100644
--- a/main.go
+++ b/main.go
@@ -112,6 +112,7 @@ import (
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jptingroom" // 日语听力学习材料
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/kfccrazythursday" // 疯狂星期四
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/llm" // 大模型聊天和群聊总结
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolimi" // 桑帛云 API
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/magicprompt" // magicprompt吟唱提示
@@ -180,6 +181,8 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichatcfg" // AI聊天配置
+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat" // AI聊天
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse" // 骂人
diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go
deleted file mode 100644
index bf6dc7c7..00000000
--- a/plugin/aichat/cfg.go
+++ /dev/null
@@ -1,302 +0,0 @@
-package aichat
-
-import (
- "errors"
- "fmt"
- "strconv"
- "strings"
-
- ctrl "github.com/FloatTech/zbpctrl"
- "github.com/FloatTech/zbputils/chat"
- "github.com/fumiama/deepinfra"
- "github.com/fumiama/deepinfra/model"
- "github.com/sirupsen/logrus"
- zero "github.com/wdvxdr1123/ZeroBot"
- "github.com/wdvxdr1123/ZeroBot/message"
-)
-
-var (
- cfg = newconfig()
-)
-
-var (
- apitypes = map[string]uint8{
- "OpenAI": 0,
- "OLLaMA": 1,
- "GenAI": 2,
- }
- apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"}
-)
-
-// ModelType 支持打印 string 并生产 protocal
-type ModelType int
-
-func newModelType(typ string) (ModelType, error) {
- t, ok := apitypes[typ]
- if !ok {
- return 0, errors.New("未知类型 " + typ)
- }
- return ModelType(t), nil
-}
-
-func (mt ModelType) String() string {
- return apilist[mt]
-}
-
-func (mt ModelType) protocol(modn string, temp float32, topp float32, maxn uint) (mod model.Protocol, err error) {
- switch cfg.Type {
- case 0:
- mod = model.NewOpenAI(
- modn, cfg.Separator,
- temp, topp, maxn,
- )
- case 1:
- mod = model.NewOLLaMA(
- modn, cfg.Separator,
- temp, topp, maxn,
- )
- case 2:
- mod = model.NewGenAI(
- modn,
- temp, topp, maxn,
- )
- default:
- err = errors.New("unsupported model type " + strconv.Itoa(int(cfg.Type)))
- }
- return
-}
-
-// ModelBool 支持打印成 "是/否"
-type ModelBool bool
-
-func (mb ModelBool) String() string {
- if mb {
- return "是"
- }
- return "否"
-}
-
-// ModelKey 支持隐藏密钥
-type ModelKey string
-
-func (mk ModelKey) String() string {
- if len(mk) == 0 {
- return "未设置"
- }
- if len(mk) <= 4 {
- return "****"
- }
- key := string(mk)
- return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:]
-}
-
-type config struct {
- ModelName string
- ImageModelName string
- AgentModelName string
- Type ModelType
- ImageType ModelType
- AgentType ModelType
- MaxN uint
- TopP float32
- SystemP string
- API string
- ImageAPI string
- AgentAPI string
- Key ModelKey
- ImageKey ModelKey
- AgentKey ModelKey
- Separator string
- NoSystemP ModelBool
-}
-
-func newconfig() config {
- return config{
- ModelName: model.ModelDeepDeek,
- SystemP: chat.SystemPrompt,
- API: deepinfra.OpenAIDeepInfra,
- }
-}
-
-func (c *config) String() string {
- topp, maxn := c.mparams()
- sb := strings.Builder{}
- sb.WriteString(fmt.Sprintf("• 模型名:%s\n", c.ModelName))
- sb.WriteString(fmt.Sprintf("• 图像模型名:%s\n", c.ImageModelName))
- sb.WriteString(fmt.Sprintf("• Agent模型名:%s\n", c.AgentModelName))
- sb.WriteString(fmt.Sprintf("• 接口类型:%v\n", c.Type))
- sb.WriteString(fmt.Sprintf("• 图像接口类型:%v\n", c.ImageType))
- sb.WriteString(fmt.Sprintf("• Agent接口类型:%v\n", c.AgentType))
- sb.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn))
- sb.WriteString(fmt.Sprintf("• TopP:%.1f\n", topp))
- sb.WriteString(fmt.Sprintf("• 系统提示词:%s\n", c.SystemP))
- sb.WriteString(fmt.Sprintf("• 接口地址:%s\n", c.API))
- sb.WriteString(fmt.Sprintf("• 图像接口地址:%s\n", c.ImageAPI))
- sb.WriteString(fmt.Sprintf("• Agent接口地址:%s\n", c.AgentAPI))
- sb.WriteString(fmt.Sprintf("• 密钥:%v\n", c.Key))
- sb.WriteString(fmt.Sprintf("• 图像密钥:%v\n", c.ImageKey))
- sb.WriteString(fmt.Sprintf("• Agent密钥:%v\n", c.AgentKey))
- sb.WriteString(fmt.Sprintf("• 分隔符:%s\n", c.Separator))
- sb.WriteString(fmt.Sprintf("• 支持系统提示词:%v\n", !c.NoSystemP))
- return sb.String()
-}
-
-func (c *config) isvalid() bool {
- return c.ModelName != "" && c.API != "" && c.Key != ""
-}
-
-// 获取全局模型参数:TopP和最大长度
-func (c *config) mparams() (topp float32, maxn uint) {
- // 处理TopP参数
- topp = c.TopP
- if topp == 0 {
- topp = 0.9
- }
-
- // 处理最大长度参数
- maxn = c.MaxN
- if maxn == 0 {
- maxn = 4096
- }
-
- return topp, maxn
-}
-
-func ensureconfig(ctx *zero.Ctx) bool {
- c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- if !ok {
- return false
- }
- if !cfg.isvalid() {
- err := c.GetExtra(&cfg)
- if err != nil {
- logrus.Warnln("ERROR: get extra err:", err)
- }
- if !cfg.isvalid() {
- cfg = newconfig()
- }
- }
- return true
-}
-
-func newextrasetstr[T ~string](ptr *T) func(ctx *zero.Ctx) {
- return 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
- }
- *ptr = T(args)
- err := c.SetExtra(&cfg)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: set extra err: ", err))
- return
- }
- ctx.SendChain(message.Text("成功"))
- }
-}
-
-func newextrasetbool[T ~bool](ptr *T) func(ctx *zero.Ctx) {
- return func(ctx *zero.Ctx) {
- args := ctx.State["regex_matched"].([]string)
- isno := args[1] == "不"
- c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- if !ok {
- ctx.SendChain(message.Text("ERROR: no such plugin"))
- return
- }
- *ptr = T(isno)
- err := c.SetExtra(&cfg)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: set extra err: ", err))
- return
- }
- ctx.SendChain(message.Text("成功"))
- }
-}
-
-func newextrasetuint(ptr *uint) func(ctx *zero.Ctx) {
- return 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
- }
- n, err := strconv.ParseUint(args, 10, 64)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: parse args err: ", err))
- return
- }
- *ptr = uint(n)
- err = c.SetExtra(&cfg)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: set extra err: ", err))
- return
- }
- ctx.SendChain(message.Text("成功"))
- }
-}
-
-func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) {
- return 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
- }
- n, err := strconv.ParseFloat(args, 32)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: parse args err: ", err))
- return
- }
- *ptr = float32(n)
- err = c.SetExtra(&cfg)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: set extra err: ", err))
- return
- }
- ctx.SendChain(message.Text("成功"))
- }
-}
-
-func newextrasetmodeltype(ptr *ModelType) func(ctx *zero.Ctx) {
- return 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
- }
- typ, err := newModelType(args)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- *ptr = typ
- err = c.SetExtra(&cfg)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: set extra err: ", err))
- return
- }
- ctx.SendChain(message.Text("成功"))
- }
-}
diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go
index 5413ba14..e17e4ac7 100644
--- a/plugin/aichat/main.go
+++ b/plugin/aichat/main.go
@@ -1,18 +1,14 @@
-// Package aichat OpenAI聊天和群聊总结
+// Package aichat 大模型聊天和Agent
package aichat
import (
"encoding/json"
"math/rand"
- "strconv"
"strings"
- "time"
"github.com/fumiama/deepinfra"
- "github.com/fumiama/deepinfra/model"
goba "github.com/fumiama/go-onebot-agent"
"github.com/sirupsen/logrus"
- "github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/extension/single"
@@ -23,7 +19,6 @@ import (
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/chat"
"github.com/FloatTech/zbputils/control"
- "github.com/FloatTech/zbputils/ctxext"
)
var (
@@ -31,27 +26,8 @@ var (
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Extra: control.ExtraFromString("aichat"),
- Brief: "OpenAI聊天",
- Help: "- 设置AI聊天触发概率10\n" +
- "- 设置AI聊天温度80\n" +
- "- 设置AI聊天(|识图|Agent)接口类型[OpenAI|OLLaMA|GenAI]\n" +
- "- 设置AI聊天(不)使用Agent模式\n" +
- "- 设置AI聊天(不)支持系统提示词\n" +
- "- 设置AI聊天(|识图|Agent)接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
- "- 设置AI聊天(|识图|Agent)密钥xxx\n" +
- "- 设置AI聊天(|识图|Agent)模型名Qwen/Qwen3-8B\n" +
- "- 查看AI聊天系统提示词\n" +
- "- 重置AI聊天系统提示词\n" +
- "- 设置AI聊天系统提示词xxx\n" +
- "- 设置AI聊天分隔符(留空则清除)\n" +
- "- 设置AI聊天(不)响应AT\n" +
- "- 设置AI聊天最大长度4096\n" +
- "- 设置AI聊天TopP 0.9\n" +
- "- 设置AI聊天(不)以AI语音输出\n" +
- "- 查看AI聊天配置\n" +
- "- 重置AI聊天\n" +
- "- 群聊总结 [消息数目]|群聊总结 1000\n" +
- "- /gpt [内容] (使用大模型聊天)\n",
+ Brief: "大模型聊天和Agent",
+ Help: "- (随意聊天, 概率匹配)",
PrivateDataFolder: "aichat",
}).ApplySingle(single.New(
@@ -66,46 +42,50 @@ var (
)
var (
- limit = ctxext.NewLimiterManager(time.Second*30, 1)
+ fastfailnorecord = false
)
func init() {
- en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
+ en.OnMessage(chat.EnsureConfig, func(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
- stor, err := newstorage(ctx, gid)
- if err != nil {
- logrus.Warnln("ERROR: ", err)
+ stor, ok := ctx.State[zero.StateKeyPrefixKeep+"aichatcfg_stor__"].(chat.Storage)
+ if !ok {
+ logrus.Warnln("ERROR: cannot get stor")
return false
}
- ctx.State["__aichat_stor__"] = stor
- return ctx.ExtractPlainText() != "" &&
- (!stor.noreplyat() || (stor.noreplyat() && !ctx.Event.IsToMe))
+ if !(ctx.ExtractPlainText() != "" &&
+ (!stor.NoReplyAt() || (stor.NoReplyAt() && !ctx.Event.IsToMe))) {
+ return false
+ }
+ rate := stor.Rate()
+ if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) {
+ return false
+ }
+ if chat.AC.Key == "" {
+ logrus.Warnln("ERROR: get extra err: empty key")
+ return false
+ }
+ if ctx.Event.IsToMe {
+ ctx.Block()
+ }
+ return true
}).SetBlock(false).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
- stor := ctx.State["__aichat_stor__"].(storage)
- rate := stor.rate()
- if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) {
- return
- }
- if ctx.Event.IsToMe {
- ctx.Block()
- }
- if cfg.Key == "" {
- logrus.Warnln("ERROR: get extra err: empty key")
- return
- }
- temperature := stor.temp()
- topp, maxn := cfg.mparams()
+ stor := ctx.State[zero.StateKeyPrefixKeep+"aichatcfg_stor__"].(chat.Storage)
+ temperature := stor.Temp()
+ topp, maxn := chat.AC.MParams()
- if !stor.noagent() && cfg.AgentAPI != "" && cfg.AgentModelName != "" {
- x := deepinfra.NewAPI(cfg.AgentAPI, string(cfg.AgentKey))
- mod, err := cfg.Type.protocol(cfg.AgentModelName, temperature, topp, maxn)
+ logrus.Debugln("[aichat] agent mode test: noagent", stor.NoAgent(), "hasapi", chat.AC.AgentAPI != "", "hasmodel", chat.AC.AgentModelName != "")
+ if !stor.NoAgent() && chat.AC.AgentAPI != "" && chat.AC.AgentModelName != "" {
+ logrus.Debugln("[aichat] enter agent mode")
+ x := deepinfra.NewAPI(chat.AC.AgentAPI, string(chat.AC.AgentKey))
+ mod, err := chat.AC.Type.Protocol(chat.AC.AgentModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
@@ -117,24 +97,35 @@ func init() {
role = goba.PermRoleOwner
}
}
- ag := chat.AgentOf(ctx.Event.SelfID)
- if cfg.ImageAPI != "" && !ag.CanViewImage() {
- mod, err := cfg.ImageType.protocol(cfg.ImageModelName, temperature, topp, maxn)
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ logrus.Warnln("ERROR: cannot get ctrl mamager")
+ }
+ ag := chat.AgentOf(ctx.Event.SelfID, c.Service)
+ logrus.Debugln("[aichat] got agent")
+ if chat.AC.ImageAPI != "" && !ag.CanViewImage() {
+ mod, err := chat.AC.ImageType.Protocol(chat.AC.ImageModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
- ag.SetViewImageAPI(deepinfra.NewAPI(cfg.ImageAPI, string(cfg.ImageKey)), mod)
+ ag.SetViewImageAPI(deepinfra.NewAPI(chat.AC.ImageAPI, string(chat.AC.ImageKey)), mod)
+ logrus.Debugln("[aichat] agent set img")
}
ctx.NoTimeout()
+ logrus.Debugln("[aichat] agent set no timeout")
hasresp := false
for i := 0; i < 8; i++ { // 最大运行 8 轮因为问答上下文只有 16
reqs := chat.CallAgent(ag, zero.SuperUserPermission(ctx), x, mod, gid, role)
if len(reqs) == 0 {
+ logrus.Debugln("[aichat] agent call got empty response")
break
}
hasresp = true
for _, req := range reqs {
+ if req.Action == goba.SVM { // is a fake action
+ continue
+ }
resp := ctx.CallAction(req.Action, req.Params)
logrus.Infoln("[aichat] agent get resp:", reqs)
ag.AddResponse(gid, &goba.APIResponse{
@@ -147,19 +138,19 @@ func init() {
}
}
if hasresp {
- ag.AddTerminus(gid)
return
}
// no response, fall back to normal chat
+ logrus.Debugln("[aichat] agent fell back to normal chat")
}
- x := deepinfra.NewAPI(cfg.API, string(cfg.Key))
- mod, err := cfg.Type.protocol(cfg.ModelName, temperature, topp, maxn)
+ x := deepinfra.NewAPI(chat.AC.API, string(chat.AC.Key))
+ mod, err := chat.AC.Type.Protocol(chat.AC.ModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
- data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, bool(cfg.NoSystemP)))
+ data, err := x.Request(chat.GetChatContext(mod, gid, chat.AC.SystemP, bool(chat.AC.NoSystemP)))
if err != nil {
logrus.Warnln("[aichat] post err:", err)
return
@@ -182,7 +173,7 @@ func init() {
logrus.Infoln("[aichat] 回复内容:", t)
recCfg := airecord.GetConfig()
record := ""
- if !fastfailnorecord && !stor.norecord() {
+ if !fastfailnorecord && !stor.NoRecord() {
record = ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, t)
if record != "" {
ctx.SendChain(message.Record(record))
@@ -199,293 +190,4 @@ func init() {
}
}
})
- en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).
- Handle(ctxext.NewStorageSaveBitmapHandler(bitmaprate, 0, 100))
- en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).
- Handle(ctxext.NewStorageSaveBitmapHandler(bitmaptemp, 0, 100))
- en.OnPrefix("设置AI聊天接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetmodeltype(&cfg.Type))
- en.OnPrefix("设置AI聊天识图接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetmodeltype(&cfg.ImageType))
- en.OnPrefix("设置AI聊天Agent接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetmodeltype(&cfg.AgentType))
- en.OnPrefix("设置AI聊天接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.API))
- en.OnPrefix("设置AI聊天识图接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.ImageAPI))
- en.OnPrefix("设置AI聊天Agent接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.AgentAPI))
- en.OnPrefix("设置AI聊天密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.Key))
- en.OnPrefix("设置AI聊天识图密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.ImageKey))
- en.OnPrefix("设置AI聊天Agent密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.AgentKey))
- en.OnPrefix("设置AI聊天模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.ModelName))
- en.OnPrefix("设置AI聊天识图模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.ImageModelName))
- en.OnPrefix("设置AI聊天Agent模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.AgentModelName))
- en.OnPrefix("设置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.SystemP))
- en.OnFullMatch("查看AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- ctx.SendChain(message.Text(cfg.SystemP))
- })
- en.OnFullMatch("重置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- if !ok {
- ctx.SendChain(message.Text("ERROR: no such plugin"))
- return
- }
- cfg.SystemP = chat.SystemPrompt
- err := c.SetExtra(&cfg)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: set extra err: ", err))
- return
- }
- ctx.SendChain(message.Text("成功"))
- })
- en.OnPrefix("设置AI聊天分隔符", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetstr(&cfg.Separator))
- en.OnRegex("^设置AI聊天(不)?响应AT$", ensureconfig, zero.SuperUserPermission).SetBlock(true).
- Handle(ctxext.NewStorageSaveBoolHandler(bitmapnrat))
- en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetbool(&cfg.NoSystemP))
- en.OnRegex("^设置AI聊天(不)?使用Agent模式$", ensureconfig, zero.SuperUserPermission).SetBlock(true).
- Handle(ctxext.NewStorageSaveBoolHandler(bitmapnagt))
- en.OnPrefix("设置AI聊天最大长度", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetuint(&cfg.MaxN))
- en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
- Handle(newextrasetfloat32(&cfg.TopP))
- en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.AdminPermission).SetBlock(true).
- Handle(ctxext.NewStorageSaveBoolHandler(bitmapnrec))
- en.OnFullMatch("查看AI聊天配置", ensureconfig, zero.SuperUserPermission).SetBlock(true).
- Handle(func(ctx *zero.Ctx) {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- stor, err := newstorage(ctx, gid)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(
- message.Text(
- "【当前AI聊天本群配置】\n",
- "• 触发概率:", int(stor.rate()), "\n",
- "• 温度:", stor.temp(), "\n",
- "• 以AI语音输出:", ModelBool(!stor.norecord()), "\n",
- "• 使用Agent:", ModelBool(!stor.noagent()), "\n",
- "• 响应@:", ModelBool(!stor.noreplyat()), "\n",
- ),
- message.Text("【当前AI聊天全局配置】\n", &cfg),
- )
- })
- en.OnFullMatch("重置AI聊天", ensureconfig, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- chat.ResetChat()
- ctx.SendChain(message.Text("成功"))
- })
-
- // 添加群聊总结功能
- en.OnRegex(`^群聊总结\s?(\d*)$`, ensureconfig, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(limit.LimitByGroup).Handle(func(ctx *zero.Ctx) {
- ctx.SendChain(message.Text("少女思考中..."))
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
- if p > 1000 {
- p = 1000
- }
- if p == 0 {
- p = 200
- }
- group := ctx.GetGroupInfo(gid, false)
- if group.MemberCount == 0 {
- ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取总结"))
- return
- }
-
- var messages []string
-
- h := ctx.GetGroupMessageHistory(gid, 0, p, false)
- h.Get("messages").ForEach(func(_, msgObj gjson.Result) bool {
- nickname := msgObj.Get("sender.nickname").Str
- text := strings.TrimSpace(message.ParseMessageFromString(msgObj.Get("raw_message").Str).ExtractPlainText())
- if text != "" {
- messages = append(messages, nickname+": "+text)
- }
- return true
- })
-
- if len(messages) == 0 {
- ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息"))
- return
- }
-
- // 构造总结请求提示 (使用通用版省流提示词)
- // 使用反引号定义多行字符串,更清晰
- promptTemplate := `请对以下群聊对话进行【极简总结】。
-要求:
-1. 剔除客套与废话,直击主题。
-2. 使用 Markdown 列表格式。
-3. 按以下结构输出:
- - 🎯 核心议题:(一句话概括)
- - 💡 关键观点/结论:(提取3-5个重点)
- - ✅ 下一步/待办:(如果有,明确谁做什么)
-
-群聊对话内容如下:
-`
- summaryPrompt := promptTemplate + strings.Join(messages, "\n")
-
- stor, err := newstorage(ctx, gid)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- // 调用大模型API进行总结
- summary, err := llmchat(summaryPrompt, stor.temp())
-
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
-
- var b strings.Builder
- b.WriteString("群 ")
- b.WriteString(group.Name)
- b.WriteByte('(')
- b.WriteString(strconv.FormatInt(gid, 10))
- b.WriteString(") 的 ")
- b.WriteString(strconv.FormatInt(p, 10))
- b.WriteString(" 条消息总结:\n\n")
- b.WriteString(summary)
-
- // 分割总结内容为多段(按1000字符长度切割)
- summaryText := b.String()
- msg := make(message.Message, 0)
- for len(summaryText) > 0 {
- if len(summaryText) <= 1000 {
- msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(summaryText)))
- break
- }
-
- // 查找1000字符内的最后一个换行符,尽量在换行处分割
- chunk := summaryText[:1000]
- lastNewline := strings.LastIndex(chunk, "\n")
- if lastNewline > 0 {
- chunk = summaryText[:lastNewline+1]
- }
-
- msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
- summaryText = summaryText[len(chunk):]
- }
- if len(msg) > 0 {
- ctx.Send(msg)
- }
- })
-
- // 添加 /gpt 命令处理(同时支持回复消息和直接使用)
- en.OnKeyword("/gpt", ensureconfig).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- text := ctx.MessageString()
-
- var query string
- var replyContent string
-
- // 检查是否是回复消息 (使用MessageElement检查而不是CQ码)
- for _, elem := range ctx.Event.Message {
- if elem.Type == "reply" {
- // 提取被回复的消息ID
- replyIDStr := elem.Data["id"]
- replyID, err := strconv.ParseInt(replyIDStr, 10, 64)
- if err == nil {
- // 获取被回复的消息内容
- replyMsg := ctx.GetMessage(replyID)
- if replyMsg.Elements != nil {
- replyContent = replyMsg.Elements.ExtractPlainText()
- }
- }
- break // 找到回复元素后退出循环
- }
- }
-
- // 提取 /gpt 后面的内容
- parts := strings.SplitN(text, "/gpt", 2)
-
- var gContent string
- if len(parts) > 1 {
- gContent = strings.TrimSpace(parts[1])
- }
-
- // 组合内容:优先使用回复内容,如果同时有/gpt内容则拼接
- switch {
- case replyContent != "" && gContent != "":
- query = replyContent + "\n" + gContent
- case replyContent != "":
- query = replyContent
- case gContent != "":
- query = gContent
- default:
- return
- }
-
- stor, err := newstorage(ctx, gid)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- // 调用大模型API进行聊天
- reply, err := llmchat(query, stor.temp())
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
-
- // 分割总结内容为多段(按1000字符长度切割)
- msg := make(message.Message, 0)
- for len(reply) > 0 {
- if len(reply) <= 1000 {
- msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(reply)))
- break
- }
-
- // 查找1000字符内的最后一个换行符,尽量在换行处分割
- chunk := reply[:1000]
- lastNewline := strings.LastIndex(chunk, "\n")
- if lastNewline > 0 {
- chunk = reply[:lastNewline+1]
- }
-
- msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
- reply = reply[len(chunk):]
- }
- if len(msg) > 0 {
- ctx.Send(msg)
- }
- })
-}
-
-// llmchat 调用大模型API包装
-func llmchat(prompt string, temp float32) (string, error) {
- topp, maxn := cfg.mparams()
-
- x := deepinfra.NewAPI(cfg.API, string(cfg.Key))
-
- mod, err := cfg.Type.protocol(cfg.ModelName, temp, topp, maxn)
- if err != nil {
- return "", nil
- }
-
- data, err := x.Request(mod.User(model.NewContentText(prompt)))
- if err != nil {
- return "", err
- }
-
- return strings.TrimSpace(data), nil
}
diff --git a/plugin/aichat/storage.go b/plugin/aichat/storage.go
deleted file mode 100644
index 0c0c5417..00000000
--- a/plugin/aichat/storage.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package aichat
-
-import (
- "github.com/FloatTech/zbputils/ctxext"
- zero "github.com/wdvxdr1123/ZeroBot"
-)
-
-const (
- bitmaprate = 0x0000ff
- bitmaptemp = 0x00ff00
- bitmapnagt = 0x010000
- bitmapnrec = 0x020000
- bitmapnrat = 0x040000
-)
-
-var (
- fastfailnorecord = false
-)
-
-type storage ctxext.Storage
-
-func newstorage(ctx *zero.Ctx, gid int64) (storage, error) {
- s, err := ctxext.NewStorage(ctx, gid)
- return storage(s), err
-}
-
-func (s storage) rate() uint8 {
- return uint8((ctxext.Storage)(s).Get(bitmaprate))
-}
-
-func (s storage) temp() float32 {
- temp := int8((ctxext.Storage)(s).Get(bitmaptemp))
- // 处理温度参数
- if temp <= 0 {
- temp = 70 // default setting
- }
- if temp > 100 {
- temp = 100
- }
- return float32(temp) / 100
-}
-
-func (s storage) noagent() bool {
- return (ctxext.Storage)(s).GetBool(bitmapnagt)
-}
-
-func (s storage) norecord() bool {
- return (ctxext.Storage)(s).GetBool(bitmapnrec)
-}
-
-func (s storage) noreplyat() bool {
- return (ctxext.Storage)(s).GetBool(bitmapnrat)
-}
diff --git a/plugin/aichat/storage_test.go b/plugin/aichat/storage_test.go
deleted file mode 100644
index f9793b27..00000000
--- a/plugin/aichat/storage_test.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package aichat
-
-import (
- "testing"
-
- "github.com/FloatTech/zbputils/ctxext"
-)
-
-func TestStorage_rate(t *testing.T) {
- s := storage(ctxext.Storage(0))
-
- // 测试默认值
- if rate := s.rate(); rate != 0 {
- t.Errorf("default rate() = %v, want 0", rate)
- }
-
- // 设置值并测试
- s = storage((ctxext.Storage)(s).Set(int64(100), bitmaprate))
- if rate := s.rate(); rate != 100 {
- t.Errorf("rate() after set = %v, want 100", rate)
- }
-}
-
-func TestStorage_temp(t *testing.T) {
- s := storage(ctxext.Storage(0))
-
- tests := []struct {
- name string
- setValue int64
- expected float32
- }{
- {"default temp (0)", 0, 0.70}, // 默认值 70/100
- {"valid temp 50", 50, 0.50}, // 50/100 = 0.50
- {"valid temp 80", 80, 0.80}, // 80/100 = 0.80
- {"max temp 100", 100, 1.00}, // 100/100 = 1.00
- {"over max temp", 127, 1.00}, // 限制为 100/100 = 1.00
- {"negative temp", -10, 0.70}, // 默认值 70/100
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s = storage((ctxext.Storage)(s).Set(tt.setValue, bitmaptemp))
-
- result := s.temp()
- if result != tt.expected {
- t.Errorf("temp() = %v, want %v", result, tt.expected)
- }
- })
- }
-}
-
-func TestStorage_noagent(t *testing.T) {
- s := storage(ctxext.Storage(0))
-
- // 测试默认值
- if noagent := s.noagent(); noagent != false {
- t.Errorf("default noagent() = %v, want false", noagent)
- }
-
- // 设置为 true 并测试
- s = storage((ctxext.Storage)(s).Set(1, bitmapnagt))
- if noagent := s.noagent(); noagent != true {
- t.Errorf("noagent() after set true = %v, want true", noagent)
- }
-}
-
-func TestStorage_norecord(t *testing.T) {
- s := storage(ctxext.Storage(0))
-
- // 测试默认值
- if norecord := s.norecord(); norecord != false {
- t.Errorf("default norecord() = %v, want false", norecord)
- }
-
- // 设置为 true 并测试
- s = storage((ctxext.Storage)(s).Set(1, bitmapnrec))
- if norecord := s.norecord(); norecord != true {
- t.Errorf("norecord() after set true = %v, want true", norecord)
- }
-}
-
-func TestStorage_noreplyat(t *testing.T) {
- s := storage(ctxext.Storage(0))
-
- // 测试默认值
- if noreplyat := s.noreplyat(); noreplyat != false {
- t.Errorf("default noreplyat() = %v, want false", noreplyat)
- }
-
- // 设置为 true 并测试
- s = storage((ctxext.Storage)(s).Set(1, bitmapnrat))
- if noreplyat := s.noreplyat(); noreplyat != true {
- t.Errorf("noreplyat() after set true = %v, want true", noreplyat)
- }
-}
-
-func TestStorage_Integration(t *testing.T) {
- s := storage(ctxext.Storage(0))
-
- // 设置各种值
- s = storage((ctxext.Storage)(s).Set(int64(75), bitmaprate))
- s = storage((ctxext.Storage)(s).Set(int64(85), bitmaptemp))
- s = storage((ctxext.Storage)(s).Set(1, bitmapnagt))
- s = storage((ctxext.Storage)(s).Set(0, bitmapnrec))
- s = storage((ctxext.Storage)(s).Set(1, bitmapnrat))
-
- // 验证所有方法
- if rate := s.rate(); rate != 75 {
- t.Errorf("rate() = %v, want 75", rate)
- }
-
- if temp := s.temp(); temp != 0.85 {
- t.Errorf("temp() = %v, want 0.85", temp)
- }
-
- if noagent := s.noagent(); !noagent {
- t.Errorf("noagent() = %v, want true", noagent)
- }
-
- if norecord := s.norecord(); norecord {
- t.Errorf("norecord() = %v, want false", norecord)
- }
-
- if noreplyat := s.noreplyat(); !noreplyat {
- t.Errorf("noreplyat() = %v, want true", noreplyat)
- }
-}
-
-func BenchmarkStorage_rate(b *testing.B) {
- s := storage(ctxext.Storage(0))
-
- s = storage((ctxext.Storage)(s).Set(int64(100), bitmaprate))
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- s.rate()
- }
-}
-
-func BenchmarkStorage_temp(b *testing.B) {
- s := storage(ctxext.Storage(0))
-
- s = storage((ctxext.Storage)(s).Set(int64(80), bitmaptemp))
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- s.temp()
- }
-}
diff --git a/plugin/aichatcfg/main.go b/plugin/aichatcfg/main.go
new file mode 100644
index 00000000..3e6886d0
--- /dev/null
+++ b/plugin/aichatcfg/main.go
@@ -0,0 +1,144 @@
+// Package aichatcfg aichat 的配置, 优先级要比 aichat 高
+package aichatcfg
+
+import (
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/chat"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+)
+
+var (
+ // en data [8 temp] [8 rate] LSB
+ en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Extra: control.ExtraFromString("aichat"),
+ Brief: "aichat 的配置",
+ Help: "- 设置AI聊天触发概率10\n" +
+ "- 设置AI聊天温度80\n" +
+ "- 设置AI聊天(|识图|Agent)接口类型[OpenAI|OLLaMA|GenAI]\n" +
+ "- 设置AI聊天(不)使用Agent模式\n" +
+ "- 设置AI聊天(不)支持系统提示词\n" +
+ "- 设置AI聊天(|识图|Agent)接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
+ "- 设置AI聊天(|识图|Agent)密钥xxx\n" +
+ "- 设置AI聊天(|识图|Agent)模型名Qwen/Qwen3-8B\n" +
+ "- 查看AI聊天系统提示词\n" +
+ "- 重置AI聊天系统提示词\n" +
+ "- 设置AI聊天系统提示词xxx\n" +
+ "- 设置AI聊天分隔符(留空则清除)\n" +
+ "- 设置AI聊天(不)响应AT\n" +
+ "- 设置AI聊天最大长度4096\n" +
+ "- 设置AI聊天TopP 0.9\n" +
+ "- 设置AI聊天(不)以AI语音输出\n" +
+ "- 查看AI聊天配置\n" +
+ "- 重置AI聊天\n",
+ })
+)
+
+func init() {
+ en.UsePreHandler(func(ctx *zero.Ctx) bool {
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ stor, err := chat.NewStorage(ctx, gid)
+ if err != nil {
+ logrus.Warnln("ERROR: ", err)
+ return false
+ }
+ ctx.State[zero.StateKeyPrefixKeep+"aichatcfg_stor__"] = stor
+ return true
+ })
+ en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).
+ Handle(ctxext.NewStorageSaveBitmapHandler(chat.BitmapRate, 0, 100))
+ en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).
+ Handle(ctxext.NewStorageSaveBitmapHandler(chat.BitmapTemp, 0, 100))
+ en.OnPrefix("设置AI聊天接口类型", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetModelType(&chat.AC.Type))
+ en.OnPrefix("设置AI聊天识图接口类型", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetModelType(&chat.AC.ImageType))
+ en.OnPrefix("设置AI聊天Agent接口类型", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetModelType(&chat.AC.AgentType))
+ en.OnPrefix("设置AI聊天接口地址", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.API))
+ en.OnPrefix("设置AI聊天识图接口地址", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.ImageAPI))
+ en.OnPrefix("设置AI聊天Agent接口地址", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.AgentAPI))
+ en.OnPrefix("设置AI聊天密钥", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.Key))
+ en.OnPrefix("设置AI聊天识图密钥", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.ImageKey))
+ en.OnPrefix("设置AI聊天Agent密钥", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.AgentKey))
+ en.OnPrefix("设置AI聊天模型名", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.ModelName))
+ en.OnPrefix("设置AI聊天识图模型名", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.ImageModelName))
+ en.OnPrefix("设置AI聊天Agent模型名", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.AgentModelName))
+ en.OnPrefix("设置AI聊天系统提示词", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.SystemP))
+ en.OnFullMatch("查看AI聊天系统提示词", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text(chat.AC.SystemP))
+ })
+ en.OnFullMatch("重置AI聊天系统提示词", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ chat.AC.SystemP = chat.SystemPrompt
+ err := c.SetExtra(&chat.AC)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ })
+ en.OnPrefix("设置AI聊天分隔符", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetStr(&chat.AC.Separator))
+ en.OnRegex("^设置AI聊天(不)?响应AT$", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).
+ Handle(ctxext.NewStorageSaveBoolHandler(chat.BitmapNrat))
+ en.OnRegex("^设置AI聊天(不)?支持系统提示词$", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetBool(&chat.AC.NoSystemP))
+ en.OnRegex("^设置AI聊天(不)?使用Agent模式$", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).
+ Handle(ctxext.NewStorageSaveBoolHandler(chat.BitmapNagt))
+ en.OnPrefix("设置AI聊天最大长度", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetUint(&chat.AC.MaxN))
+ en.OnPrefix("设置AI聊天TopP", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(chat.NewExtraSetFloat32(&chat.AC.TopP))
+ en.OnRegex("^设置AI聊天(不)?以AI语音输出$", chat.EnsureConfig, zero.AdminPermission).SetBlock(true).
+ Handle(ctxext.NewStorageSaveBoolHandler(chat.BitmapNrec))
+ en.OnFullMatch("查看AI聊天配置", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ stor, err := chat.NewStorage(ctx, gid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(
+ message.Text(
+ "【当前AI聊天本群配置】\n",
+ "• 触发概率:", int(stor.Rate()), "\n",
+ "• 温度:", stor.Temp(), "\n",
+ "• 以AI语音输出:", chat.ModelBool(!stor.NoRecord()), "\n",
+ "• 使用Agent:", chat.ModelBool(!stor.NoAgent()), "\n",
+ "• 响应@:", chat.ModelBool(!stor.NoReplyAt()), "\n",
+ ),
+ message.Text("【当前AI聊天全局配置】\n", &chat.AC),
+ )
+ })
+ en.OnFullMatch("重置AI聊天", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ chat.ResetChat()
+ ctx.SendChain(message.Text("成功"))
+ })
+}
diff --git a/plugin/llm/main.go b/plugin/llm/main.go
new file mode 100644
index 00000000..71c9ba88
--- /dev/null
+++ b/plugin/llm/main.go
@@ -0,0 +1,246 @@
+// Package llm 大模型聊天和群聊总结
+package llm
+
+import (
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/fumiama/deepinfra"
+ "github.com/fumiama/deepinfra/model"
+ "github.com/tidwall/gjson"
+
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/extension/single"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/chat"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+)
+
+var (
+ // en data [8 temp] [8 rate] LSB
+ en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "大模型聊天和群聊总结",
+ Help: "- 群聊总结 [消息数目]|群聊总结 1000\n" +
+ "- /gpt [内容] (使用大模型聊天)\n",
+ }).ApplySingle(single.New(
+ single.WithKeyFn(func(ctx *zero.Ctx) int64 {
+ if ctx.Event.GroupID == 0 {
+ return -ctx.Event.UserID
+ }
+ return ctx.Event.GroupID
+ }),
+ // no post option, silently quit
+ ))
+)
+
+var (
+ limit = ctxext.NewLimiterManager(time.Second*30, 1)
+)
+
+func init() {
+ // 添加群聊总结功能
+ en.OnRegex(`^群聊总结\s?(\d*)$`, chat.EnsureConfig, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(limit.LimitByGroup).Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text("少女思考中..."))
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
+ if p > 1000 {
+ p = 1000
+ }
+ if p == 0 {
+ p = 200
+ }
+ group := ctx.GetGroupInfo(gid, false)
+ if group.MemberCount == 0 {
+ ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取总结"))
+ return
+ }
+
+ var messages []string
+
+ h := ctx.GetGroupMessageHistory(gid, 0, p, false)
+ h.Get("messages").ForEach(func(_, msgObj gjson.Result) bool {
+ nickname := msgObj.Get("sender.nickname").Str
+ text := strings.TrimSpace(message.ParseMessageFromString(msgObj.Get("raw_message").Str).ExtractPlainText())
+ if text != "" {
+ messages = append(messages, nickname+": "+text)
+ }
+ return true
+ })
+
+ if len(messages) == 0 {
+ ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息"))
+ return
+ }
+
+ // 构造总结请求提示 (使用通用版省流提示词)
+ // 使用反引号定义多行字符串,更清晰
+ promptTemplate := `请对以下群聊对话进行【极简总结】。
+要求:
+1. 剔除客套与废话,直击主题。
+2. 使用 Markdown 列表格式。
+3. 按以下结构输出:
+ - 🎯 核心议题:(一句话概括)
+ - 💡 关键观点/结论:(提取3-5个重点)
+ - ✅ 下一步/待办:(如果有,明确谁做什么)
+
+群聊对话内容如下:
+`
+ summaryPrompt := promptTemplate + strings.Join(messages, "\n")
+
+ stor, err := chat.NewStorage(ctx, gid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ // 调用大模型API进行总结
+ summary, err := llmchat(summaryPrompt, stor.Temp())
+
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+
+ var b strings.Builder
+ b.WriteString("群 ")
+ b.WriteString(group.Name)
+ b.WriteByte('(')
+ b.WriteString(strconv.FormatInt(gid, 10))
+ b.WriteString(") 的 ")
+ b.WriteString(strconv.FormatInt(p, 10))
+ b.WriteString(" 条消息总结:\n\n")
+ b.WriteString(summary)
+
+ // 分割总结内容为多段(按1000字符长度切割)
+ summaryText := b.String()
+ msg := make(message.Message, 0)
+ for len(summaryText) > 0 {
+ if len(summaryText) <= 1000 {
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(summaryText)))
+ break
+ }
+
+ // 查找1000字符内的最后一个换行符,尽量在换行处分割
+ chunk := summaryText[:1000]
+ lastNewline := strings.LastIndex(chunk, "\n")
+ if lastNewline > 0 {
+ chunk = summaryText[:lastNewline+1]
+ }
+
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
+ summaryText = summaryText[len(chunk):]
+ }
+ if len(msg) > 0 {
+ ctx.Send(msg)
+ }
+ })
+
+ // 添加 /gpt 命令处理(同时支持回复消息和直接使用)
+ en.OnKeyword("/gpt", chat.EnsureConfig).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ text := ctx.MessageString()
+
+ var query string
+ var replyContent string
+
+ // 检查是否是回复消息 (使用MessageElement检查而不是CQ码)
+ for _, elem := range ctx.Event.Message {
+ if elem.Type == "reply" {
+ // 提取被回复的消息ID
+ replyIDStr := elem.Data["id"]
+ replyID, err := strconv.ParseInt(replyIDStr, 10, 64)
+ if err == nil {
+ // 获取被回复的消息内容
+ replyMsg := ctx.GetMessage(replyID)
+ if replyMsg.Elements != nil {
+ replyContent = replyMsg.Elements.ExtractPlainText()
+ }
+ }
+ break // 找到回复元素后退出循环
+ }
+ }
+
+ // 提取 /gpt 后面的内容
+ parts := strings.SplitN(text, "/gpt", 2)
+
+ var gContent string
+ if len(parts) > 1 {
+ gContent = strings.TrimSpace(parts[1])
+ }
+
+ // 组合内容:优先使用回复内容,如果同时有/gpt内容则拼接
+ switch {
+ case replyContent != "" && gContent != "":
+ query = replyContent + "\n" + gContent
+ case replyContent != "":
+ query = replyContent
+ case gContent != "":
+ query = gContent
+ default:
+ return
+ }
+
+ stor, err := chat.NewStorage(ctx, gid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ // 调用大模型API进行聊天
+ reply, err := llmchat(query, stor.Temp())
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+
+ // 分割总结内容为多段(按1000字符长度切割)
+ msg := make(message.Message, 0)
+ for len(reply) > 0 {
+ if len(reply) <= 1000 {
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(reply)))
+ break
+ }
+
+ // 查找1000字符内的最后一个换行符,尽量在换行处分割
+ chunk := reply[:1000]
+ lastNewline := strings.LastIndex(chunk, "\n")
+ if lastNewline > 0 {
+ chunk = reply[:lastNewline+1]
+ }
+
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
+ reply = reply[len(chunk):]
+ }
+ if len(msg) > 0 {
+ ctx.Send(msg)
+ }
+ })
+}
+
+// llmchat 调用大模型API包装
+func llmchat(prompt string, temp float32) (string, error) {
+ topp, maxn := chat.AC.MParams()
+
+ x := deepinfra.NewAPI(chat.AC.API, string(chat.AC.Key))
+
+ mod, err := chat.AC.Type.Protocol(chat.AC.ModelName, temp, topp, maxn)
+ if err != nil {
+ return "", nil
+ }
+
+ data, err := x.Request(mod.User(model.NewContentText(prompt)))
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(data), nil
+}