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