Compare commits

...

939 Commits

Author SHA1 Message Date
Junyan Qin fd10db3c75 ci: fix 2024-02-21 13:56:38 +00:00
Junyan Qin db4c658980 chore: test 2024-02-21 13:52:54 +00:00
Junyan Qin 0ee88674f8 ci: update 2024-02-21 13:52:33 +00:00
Junyan Qin 3540759682 chore: release v3.0.1.1 2024-02-21 13:46:38 +00:00
Junyan Qin 44cc8f15b4 Merge pull request #695 from RockChinQ/ci/arm-image
CI: 构建arm64镜像
2024-02-21 21:45:40 +08:00
Junyan Qin 59f821bf0a ci: 构建arm64镜像 2024-02-21 13:44:07 +00:00
RockChinQ 80858672b0 perf: 控制台输出请求响应过程 2024-02-20 22:56:42 +08:00
RockChinQ 3258d5b255 chore: aiocqhttp默认监听地址改为0.0.0.0 2024-02-20 20:13:46 +08:00
RockChinQ e8c8cc0a9c chore: release v3.0.1 2024-02-20 11:48:26 +08:00
Junyan Qin 570c19f29f Merge pull request #693 from RockChinQ/fix/3.9-compability
Fix: 针对python3.9的兼容性
2024-02-20 11:47:49 +08:00
RockChinQ ee93fd8636 hotfix: 针对python3.9的兼容性 2024-02-20 11:47:04 +08:00
RockChinQ 1e6c32ffc7 fix: 'VersionManager' object has no attribute 'get_release_list' 2024-02-20 09:54:02 +08:00
RockChinQ 3ef2fb958c chore: release v3.0.0 2024-02-19 22:04:41 +08:00
RockChinQ 97edfe7cd7 doc: 整理README 2024-02-19 22:03:27 +08:00
Junyan Qin 1bdc96f8b2 Merge pull request #669 from RockChinQ/feat/asyncio
Refactor: 异步架构
2024-02-19 21:59:41 +08:00
RockChinQ 4ef285aee9 chore: 删除无用文件 2024-02-19 21:51:56 +08:00
RockChinQ 6ccee3b7cf chore: 删除 README_en.md 2024-02-19 21:48:52 +08:00
RockChinQ 082731ba32 fix: !version 命令获取最新版本失败时导致命令失败 2024-02-19 21:47:51 +08:00
RockChinQ 0bf85fb644 fix: msg_source无法通过审计接口发给center 2024-02-19 21:41:53 +08:00
RockChinQ 5ce1759dd9 fix: 启动后未进行对话时,!list会 越界异常 2024-02-19 21:40:34 +08:00
RockChinQ 1e016dfa24 ci: 修改工作流文件名 2024-02-19 20:37:40 +08:00
RockChinQ 7b3bb53f06 ci: 更换基础镜像 2024-02-19 20:36:26 +08:00
RockChinQ 53d0059848 perf: 不再需要exit来退出程序 2024-02-19 19:27:42 +08:00
RockChinQ 9a85178a29 deps: 重新添加nakuru 2024-02-19 19:17:18 +08:00
RockChinQ d74681a128 deps: 删除无用依赖 2024-02-19 18:59:52 +08:00
RockChinQ 06c8773975 perf: 优化控制台输出 2024-02-16 14:11:22 +08:00
RockChinQ ae358dd6d0 fix: 昨天错误的shutdown_trigger逻辑 2024-02-16 13:08:26 +08:00
RockChinQ 7174cbf41f feat: 支持 ctrl+c 退出 2024-02-15 22:21:56 +08:00
RockChinQ f73d69e814 perf: 添加未启用适配器时的警告 2024-02-15 16:12:42 +08:00
Junyan Qin 8af174127d Merge pull request #685 from RockChinQ/feat/run-multi-adapter
Feat: 支持同时运行多个适配器
2024-02-12 13:38:56 +08:00
RockChinQ 991a0aa5f6 fix: 修复nakuru无法运行的问题 2024-02-12 13:37:41 +08:00
RockChinQ abc19e78b8 feat: 命令行退出方式 2024-02-11 23:35:05 +08:00
RockChinQ 836df87e18 feat: 删除过时配置 2024-02-11 23:11:13 +08:00
RockChinQ 9cad94e961 feat: 支持同时运行多个平台适配器 2024-02-11 23:07:38 +08:00
Junyan Qin b9568eb558 doc(README.md): 更新社区群群号 2024-02-11 09:47:21 +08:00
RockChinQ f951625025 chore: 修改推荐的docker-compose.yaml配置 2024-02-08 13:45:26 +08:00
RockChinQ c2b3b53c12 chore: 修改启动相关 2024-02-08 13:40:25 +08:00
RockChinQ d95e18c202 chore: 整理代码 2024-02-08 13:37:27 +08:00
Junyan Qin e705e707e5 Merge pull request #680 from RockChinQ/feat/nakuru
Feat: 恢复nakuru使用
2024-02-08 13:14:53 +08:00
RockChinQ 2fa5d7608f chore: 删除无效代码 2024-02-08 13:13:35 +08:00
RockChinQ f9a3e99795 feat: 恢复nakuru使用 2024-02-08 13:12:33 +08:00
RockChinQ d86ad25f86 feat: 正向代理支持 2024-02-07 23:58:22 +08:00
Junyan Qin cf583486e3 Merge pull request #679 from RockChinQ/feat/botpy-qq
Feat: 接入 QQ 官方 API
2024-02-07 23:29:56 +08:00
RockChinQ 7366ca59c7 chore: 忽略botpy.log 2024-02-07 23:27:10 +08:00
RockChinQ 12820e6c64 feat: 支持qq-botpy 2024-02-07 23:21:32 +08:00
Junyan Qin 71b54fd684 Merge pull request #678 from RockChinQ/feat/aiocqhttp
Feat: 适配aiocqhttp
2024-02-07 20:23:43 +08:00
RockChinQ aeb1912db6 feat: 适配aiocqhttp 2024-02-07 20:03:46 +08:00
Junyan Qin 84b2867148 Merge pull request #677 from RockChinQ/refactor/asyncio/config
Refactor: 配置文件重构
2024-02-07 00:09:23 +08:00
RockChinQ 5880dacad8 ci: 修改dockerfile 2024-02-07 00:07:55 +08:00
RockChinQ b5b67ad958 refactor: 恢复命令权限设置 2024-02-06 23:57:21 +08:00
RockChinQ 2a913ed24c chore: 删除过时文件 2024-02-06 21:29:31 +08:00
RockChinQ aab56294ba chore: 删除字体文件 2024-02-06 21:28:24 +08:00
RockChinQ 26912ef976 chore: 删除多余文件 2024-02-06 21:28:01 +08:00
RockChinQ c1fed3410b chore: 删除过时的配置文件 2024-02-06 21:27:14 +08:00
RockChinQ c853bba4ba refactor: 配置文件均改为json 2024-02-06 21:26:03 +08:00
RockChinQ f340a44abf feat: 恢复ratelimit 2024-02-01 18:38:20 +08:00
RockChinQ 0dec10ddf2 chore: 删除tests目录 2024-02-01 18:38:04 +08:00
RockChinQ 7026abe56a perf: 完善openai异常处理 2024-02-01 18:11:47 +08:00
RockChinQ a9d92115f8 feat: chat前的前文剪裁逻辑 2024-02-01 17:42:51 +08:00
RockChinQ 6f2d7d96d0 perf: 完善历史消息处理逻辑 2024-02-01 16:43:44 +08:00
RockChinQ 532a713355 refactor: 独立出预处理阶段 2024-02-01 16:35:00 +08:00
RockChinQ 976a9de39c refactor: 分隔LLM请求过程和消息封装过程 2024-02-01 15:48:26 +08:00
RockChinQ 32162afa65 refactor: 恢复所有审计API调用 2024-01-31 00:02:19 +08:00
RockChinQ c1c751a9ab feat: 更新操作 2024-01-30 22:50:52 +08:00
RockChinQ b749ba587d feat: 恢复强制消息延迟 2024-01-30 21:56:25 +08:00
GitHub Actions b2741686fd Update override-all.json 2024-01-30 13:45:50 +00:00
RockChinQ 94bf7739a0 chore: 默认回复函数响应 2024-01-30 21:45:31 +08:00
RockChinQ 33d600fb6b refactor: 恢复插件事件调用 2024-01-30 21:45:17 +08:00
RockChinQ e2de3d0102 feat: 删除部分插件事件 2024-01-30 17:47:03 +08:00
RockChinQ 6b76adc00e feat: 添加事件对象 2024-01-30 17:24:22 +08:00
RockChinQ 61f4cb2f65 perf: 完善模型信息 2024-01-30 16:58:11 +08:00
RockChinQ 28bd232dda feat: 添加更多LLM模型 2024-01-30 16:29:54 +08:00
RockChinQ e9e458c877 feat: 公告和更新检查 2024-01-30 16:13:33 +08:00
RockChinQ 437971ded8 feat: 应用层异常处理 2024-01-30 14:58:34 +08:00
RockChinQ 3945ac95d1 refactor: 审计api改为异步 2024-01-29 21:58:47 +08:00
RockChinQ 13ab647dc0 perf: 完善插件加载流程 2024-01-29 21:41:20 +08:00
RockChinQ c75b0ce8fb perf: 优化代码声明 2024-01-29 21:31:11 +08:00
RockChinQ 6cc4688660 refactor: 重构插件系统 2024-01-29 21:22:27 +08:00
RockChinQ b730f17eb6 chore: 修改包名 2024-01-28 19:20:10 +08:00
RockChinQ 698782c537 chore: 整理文件 2024-01-28 18:45:18 +08:00
Junyan Qin 2b0faea8ec Merge pull request #673 from RockChinQ/refactor/asyncio/control-flow
Refactor: 请求处理控制流
2024-01-28 18:41:59 +08:00
RockChinQ d130c376f4 chore: 删除命令权限同步脚本 2024-01-28 18:40:10 +08:00
RockChinQ 238c55a40e chore: 删除已弃用的文件 2024-01-28 18:38:47 +08:00
RockChinQ b5924bb34f refactor: 添加更新命令 2024-01-28 18:27:48 +08:00
RockChinQ 1368ee22b2 refactor: 命令基本完成 2024-01-28 18:21:43 +08:00
RockChinQ 2a0cf57303 refactor: 命令处理基础 2024-01-28 00:16:42 +08:00
RockChinQ f10af09bd2 refactor: AI对话基本完成 2024-01-27 21:50:40 +08:00
RockChinQ 850a4eeb7c refactor: 重构openai包基础组件架构 2024-01-27 00:06:38 +08:00
RockChinQ 411034902a feat: 启动时展示asciiart 2024-01-27 00:05:55 +08:00
RockChinQ 1900ddacbb chore: 删除 qqbot 包中的流程代码 2024-01-26 15:54:24 +08:00
RockChinQ 8d084427d2 refactor: 请求处理控制流基础架构 2024-01-26 15:51:49 +08:00
Junyan Qin a064c24f60 Merge pull request #670 from RockChinQ/refactor/asyncio/simplify-qqbot-mgr
Refactor: 简化和调整qqbot包架构
2024-01-25 22:39:25 +08:00
RockChinQ b43882aad0 refactor: 独立ratelimiter包 2024-01-25 22:35:15 +08:00
RockChinQ f4ead5ec5c refactor: 独立resprule为单独的包 2024-01-25 18:07:28 +08:00
RockChinQ ea9ae85428 refactor: 独立长消息处理为longtext包 2024-01-25 17:05:09 +08:00
RockChinQ a9a798b19d refactor: filter和ignore独立成新的cntfilter包 2024-01-25 15:28:23 +08:00
RockChinQ f4ae9df3bf refactor: 重构会话封禁功能处理逻辑 2024-01-24 23:38:13 +08:00
RockChinQ f3bcff1261 chore: banlist模版移至根目录 2024-01-24 23:33:48 +08:00
RockChinQ b4bd86549e chore: banlist模版移至根目录 2024-01-24 23:33:19 +08:00
RockChinQ a975718a64 refactor: 暂时删除对热重载的支持 2024-01-24 22:29:19 +08:00
RockChinQ 3d06a18bcb refactor: 简化私聊群聊共同处理代码 2024-01-24 17:00:56 +08:00
RockChinQ a236089785 refactor: 独立resprule模块 2024-01-24 16:11:56 +08:00
RockChinQ 2f877965cf chore: 删除部分注释代码 2024-01-23 23:27:55 +08:00
RockChinQ ad5ef95e65 refactor: yirimirai 适配器实现异步 2024-01-23 22:28:30 +08:00
RockChinQ 8d35ecd711 refactor: 基本启动流程 2024-01-23 20:55:20 +08:00
RockChinQ e63c6ac723 feat: 删除main.py中init_db函数 2024-01-23 15:42:23 +08:00
RockChinQ 0984c19fd9 doc(README): 说明 Python 版本 2024-01-22 20:37:29 +08:00
RockChinQ a10d3213fd chore: release v2.6.10 2024-01-19 15:50:15 +08:00
RockChinQ f52a0eb02f perf: 连接go-cqhttp时不使用代理 2024-01-19 15:49:42 +08:00
Junyan Qin 1ea8da69a2 Merge pull request #667 from RockChinQ/chore/remove-legacy-code
Chore: 移除过时的兼容性处理代码
2024-01-18 01:02:32 +08:00
RockChinQ 5bbc38a7a3 chore: 移除过时的兼容性处理代码 2024-01-18 00:52:29 +08:00
RockChinQ aa433bd5ab fix: 修复文字转图片模块初始化时的bug 2024-01-17 20:07:35 +08:00
RockChinQ 2c5933da0b chore: 删除updater中不再使用的代码 2024-01-15 22:35:14 +08:00
RockChinQ 77bc6fbf59 fix(list): 列出不存在的页时失败 2024-01-15 21:44:53 +08:00
Junyan Qin 701cb7be40 Merge pull request #661 from RockChinQ/perf/audit-v2
Feat: 优化 v2 审计 API 调用逻辑
2024-01-12 20:18:30 +08:00
RockChinQ ab8d77c968 feat: 删除 v1 审计 API 调用逻辑 2024-01-12 20:06:18 +08:00
RockChinQ 6c03fe678a feat: 允许用户关闭数据上报 2024-01-12 17:20:39 +08:00
RockChinQ 41b30238c3 chore: 指令全部改为命令 2024-01-12 16:48:47 +08:00
RockChinQ aa768459c0 perf: 配置项目标值不合法时的输出 2024-01-12 16:29:04 +08:00
RockChinQ 28014512f7 fix(cconfig): cfg 命令找不到配置项时的处理错误 2024-01-12 16:25:10 +08:00
RockChinQ f9a99eed66 chore: 删除已被OpenAI弃用的模型 (#658) 2024-01-12 14:48:49 +08:00
Junyan Qin 461b574e09 Merge pull request #659 from RockChinQ/fix/resend-command-failed
Fix: resend 命令失效
2024-01-12 14:40:07 +08:00
RockChinQ 36c192ff6b fix: resend 命令失效 2024-01-12 14:31:29 +08:00
RockChinQ 101625965c chore: 删除对 credit 的引用 2024-01-12 10:18:10 +08:00
RockChinQ 83177a3416 chore: 移除弃用的 credit.py 模块 2024-01-12 10:09:53 +08:00
Junyan Qin c3904786e1 doc(README.md): 添加链接 2024-01-10 23:11:02 +08:00
RockChinQ b31c34905a test: 自动上传覆盖率 2023-12-28 16:14:54 +08:00
RockChinQ 41cbe91870 doc(README): 添加测试覆盖率徽章 2023-12-28 16:03:55 +08:00
Junyan Qin 872b16b779 ci: 删除注释 2023-12-27 16:00:18 +00:00
Junyan Qin 9f3cc9c293 test: 修正错误的引号 2023-12-27 15:56:52 +00:00
Junyan Qin 2d148c4970 test: 处理多行响应值 2023-12-27 15:52:12 +00:00
Junyan Qin 0869b57741 test: install jq 2023-12-27 15:48:26 +00:00
Junyan Qin af225aa18f test: 错误的逻辑 2023-12-27 15:44:24 +00:00
Junyan Qin 06f3c5d32b test: 分支名获取方式 2023-12-27 15:39:08 +00:00
Junyan Qin 4e71a08b57 test: 完善issues_comment时的pr分支获取逻辑 2023-12-27 15:35:25 +00:00
Junyan Qin bf5ebc9245 test: 错误的触发名称 2023-12-27 15:23:53 +00:00
Junyan Qin fba81582ab test: 完善触发方式 2023-12-27 15:16:07 +00:00
Junyan Qin b4645168f9 Merge pull request #649 from RockChinQ/test/systematical-test
Test: 集成qcg-tester
2023-12-27 22:50:35 +08:00
Junyan Qin d00c68e329 test: 允许手动触发 2023-12-27 14:49:00 +00:00
Junyan Qin cb636b96bf test: 集成qcg-tester 2023-12-27 14:47:02 +00:00
GitHub Actions 12468b5b15 Update override-all.json 2023-12-23 02:32:13 +00:00
RockChinQ 6a5414b5fd chore: prompt_submit_length默认改为3072 2023-12-23 10:31:56 +08:00
RockChinQ db51fd0ad7 chore: release v2.6.9 2023-12-22 18:34:35 +08:00
Junyan Qin 256bc4dc1e Merge pull request #644 from RockChinQ/feat/online-data-analysis
Feat: v2 数据统计接口
2023-12-22 18:33:50 +08:00
RockChinQ d2bd6e23b6 chore: 删除调试输出 2023-12-22 14:36:52 +08:00
RockChinQ bb12b48887 feat: usage.query完成 2023-12-22 12:38:27 +08:00
RockChinQ a58e55daf3 chore: 更新issue模板 2023-12-22 11:11:31 +08:00
RockChinQ 23a05fe5b0 chore: 完善issue模板 2023-12-22 11:03:25 +08:00
RockChinQ 3a63630068 feat: account_id 设置逻辑 2023-12-21 18:51:10 +08:00
RockChinQ 565066bbcd feat: 插件相关上报 API 2023-12-21 18:46:48 +08:00
RockChinQ c10f72cf4c feat: 内容函数调用报告 2023-12-21 18:36:02 +08:00
RockChinQ af8c21f3d4 feat: 完善 插件事件调用报告 2023-12-21 18:19:04 +08:00
RockChinQ 6f6c3af302 feat: 插件事件触发报告 2023-12-21 18:04:16 +08:00
RockChinQ 61a47808c8 chore: typo 2023-12-21 17:35:20 +08:00
RockChinQ e02765bf95 feat: main.announcement 接口 2023-12-21 17:11:45 +08:00
RockChinQ b69f193a3e feat: main.update 接口完成 2023-12-21 17:03:58 +08:00
RockChinQ 7c6526d1ea feat: 改为同步 2023-12-21 16:48:50 +08:00
RockChinQ b8776fba65 chore: stash 2023-12-21 16:44:21 +08:00
RockChinQ 38357dd68d perf: 简化启动输出 2023-12-21 16:28:45 +08:00
RockChinQ d1c2453310 feat: 启动时初始化中央服务器 API 交互类 2023-12-21 16:21:24 +08:00
RockChinQ ebc1ac50c6 doc: 更新 README 2023-12-21 10:22:53 +08:00
RockChinQ 892610872f chore: 更新 submit-plugin 模板 2023-12-21 10:20:19 +08:00
RockChinQ a990a40850 chore: 更新issues模板 2023-12-21 10:19:02 +08:00
RockChinQ 3f29464dbd feat: 标识符生成器模块 2023-12-20 22:26:51 +08:00
RockChinQ 998d07f3b4 doc(wiki): 添加已迁移说明 2023-12-20 22:10:19 +08:00
Junyan Qin 949bc6268c Update README.md 2023-12-20 22:05:12 +08:00
Junyan Qin 2c03e5a77e doc(README): 更改效果图为主页中的图片 2023-12-20 21:54:20 +08:00
Junyan Qin aad62dfa6f Merge pull request #642 from RockChinQ/doc/document-replacing
Doc: 替换主文档
2023-12-20 21:47:11 +08:00
Junyan Qin 08e27d07ea 更新 README.md 2023-12-20 21:44:08 +08:00
Junyan Qin 1fddd244e5 更新 README.md 2023-12-20 21:43:48 +08:00
Junyan Qin d85b4b1cf0 doc(README.md): 替换logo为主页上的链接 2023-12-20 21:43:03 +08:00
RockChinQ 09fca2c292 doc(README): 应用更改 2023-12-20 21:34:44 +08:00
RockChinQ feda3d18fb doc: 修改主页布局 2023-12-20 17:57:28 +08:00
Junyan Qin eb6e5d0756 Merge pull request #640 from RockChinQ/fix/cfg-command
Fix: cfg 命令无法使用
2023-12-19 17:40:33 +08:00
RockChinQ 7386daad28 fix: cfg 命令无法使用 (#638) 2023-12-19 17:37:40 +08:00
RockChinQ 3f290b2e1a feat: 命令回复不再通过敏感词检查 2023-12-18 16:31:45 +08:00
RockChinQ 43519ffe80 doc(wiki): 添加插件 API 讨论链接 2023-12-17 23:25:56 +08:00
RockChinQ c8bb3d612a chore: release v2.6.8 2023-12-17 23:00:25 +08:00
Junyan Qin bc48b7e623 Merge pull request #636 from RockChinQ/feat/google-gemini
Feat: 支持 Google Gemini Pro 模型
2023-12-17 22:59:34 +08:00
RockChinQ d59d5797f6 doc(README.md): 删除 PaLM-2 说明 2023-12-17 22:55:06 +08:00
RockChinQ 11d3c1e650 doc(README.md): 添加模型说明 2023-12-17 22:53:50 +08:00
RockChinQ 8cfd9e6694 chore: 添加配置项说明 2023-12-17 22:48:48 +08:00
RockChinQ d3f401c54d feat: 通过 one-api 支持google gemini 2023-12-17 22:36:30 +08:00
Junyan Qin a889170d1a Merge pull request #634 from zuo-shi-yun/master
添加AutoSwitchProxy插件
2023-12-17 16:19:47 +08:00
zuo-shi-yun 459e9f9322 添加AutoSwitchProxy插件 2023-12-17 13:15:33 +08:00
Junyan Qin 707afdcdf9 Update bug-report.yml 2023-12-15 10:38:04 +08:00
RockChinQ ad1cf379c4 doc: 删除公告 2023-12-11 21:57:57 +08:00
RockChinQ 582277fe2d doc: 更新 效果图 2023-12-11 21:56:00 +08:00
RockChinQ 14b9f814c7 chore: release v2.6.7 2023-12-09 22:25:44 +08:00
Junyan Qin b11e5d99b0 Merge pull request #628 from RockChinQ/fix/image-generating
Fix: openai>=1.0时绘图命令不兼容
2023-12-09 22:22:42 +08:00
GitHub Actions 9590718da4 Update override-all.json 2023-12-09 14:17:55 +00:00
RockChinQ 8c2b53cffb fix: openai>=1.0时绘图命令不兼容 2023-12-09 22:17:26 +08:00
Junyan Qin 5a85c073a8 Update README.md 2023-12-08 17:03:16 +08:00
Junyan Qin 2d2fbd0a8b fix: 首次启动时无法创建配置文件 2023-12-08 07:27:23 +00:00
Junyan Qin 1b25a05122 Update README.md 2023-12-06 19:29:31 +08:00
RockChinQ 709cc1140b chore: 发布公告 2023-12-06 19:27:04 +08:00
Junyan Qin 1730962636 Merge pull request #625 from zuo-shi-yun/master
添加看门狗插件
2023-12-03 10:03:35 +08:00
zuo-shi-yun a1de4f6f7a 添加看门狗插件 2023-12-02 23:58:18 +08:00
Junyan Qin a5ccda5ed6 doc: 更新 NOTE 和 WARNING 的格式 2023-12-01 02:28:47 +00:00
Junyan Qin f035e654ba Merge pull request #623 from zuo-shi-yun/master
添加discountAssistant插件
2023-12-01 10:04:49 +08:00
zuo-shi-yun 151d3e9f66 添加discountAssistant插件 2023-11-30 23:53:43 +08:00
Junyan Qin c79207e197 Merge pull request #618 from RockChinQ/refactor/config-manager
Refactor: 使用 配置管理器 统一管理配置文件
2023-11-27 00:02:52 +08:00
RockChinQ f9d461d9a1 feat: 移除过时的配置模块处理逻辑 2023-11-27 00:00:22 +08:00
RockChinQ 3e17bbb90f refactor: 适配配置管理器读取方式 2023-11-26 23:58:06 +08:00
RockChinQ 549a7eff7f refactor(qqbot): 适配配置管理器 2023-11-26 23:04:14 +08:00
RockChinQ db2e366014 feat: 实现配置文件管理器并适配main.py中的引用 2023-11-26 22:46:27 +08:00
RockChinQ 26e4215054 feat: 新的override逻辑 2023-11-26 22:25:54 +08:00
RockChinQ 5f07ff8145 refactor: 启动流程现在异步 2023-11-26 22:19:36 +08:00
GitHub Actions e396ba4649 Update override-all.json 2023-11-26 13:54:00 +00:00
RockChinQ d1dff6dedd feat(main.py): 将配置加载流程放到start函数 2023-11-26 21:53:35 +08:00
RockChinQ 419354cb07 feat: 添加用于覆盖率测试的退出代码 2023-11-26 17:42:25 +08:00
RockChinQ 7708eaa82c perf: 为 context.py 中的方法添加类型提示 2023-11-26 17:33:13 +08:00
RockChinQ 9fccf84987 chore: release v2.6.6 2023-11-22 19:20:47 +08:00
Junyan Qin 0f59788184 Merge pull request #610 from RockChinQ/feat/no-reload-after-updating
Feat: 更新后不再自动热重载
2023-11-22 19:19:22 +08:00
RockChinQ 0ad52bcd3f perf: 优化输出文字 2023-11-22 19:17:23 +08:00
RockChinQ d7d710ec07 feat: 更新后不再自动热重载 2023-11-22 19:08:33 +08:00
GitHub Actions 75a9a3e9af Update override-all.json 2023-11-22 11:06:11 +00:00
RockChinQ 70503bedb7 feat: 现在默认关闭强制延迟 2023-11-22 19:05:51 +08:00
Junyan Qin 7890eac3f8 Merge pull request #608 from RockChinQ/fix/reverse-proxy-invalid
Fix: 反向代理设置无效
2023-11-21 15:45:49 +08:00
RockChinQ e15f3595b3 fix: 反向代理设置无效 2023-11-21 15:44:07 +08:00
RockChinQ eebd6a6ba3 chore: release v2.6.5 2023-11-14 23:16:02 +08:00
Junyan Qin 0407f3e4ac Merge pull request #599 from RockChinQ/refactor/modern-openai-api-style
Refactor: 修改 情景预设 置入风格
2023-11-14 21:36:25 +08:00
RockChinQ 5abca84437 debug: 添加请求参数输出 2023-11-14 21:35:02 +08:00
GitHub Actions d2776cc1e6 Update override-all.json 2023-11-14 13:06:22 +00:00
RockChinQ 9fe0ee2b77 refactor: 使用system role置入default prompt 2023-11-14 21:06:00 +08:00
Junyan Qin b68daac323 Merge pull request #598 from RockChinQ/perf/import-style
Refactor: 修改引入风格
2023-11-13 22:00:27 +08:00
RockChinQ 665de5dc43 refactor: 修改引入风格 2023-11-13 21:59:23 +08:00
RockChinQ e3b280758c chore: 发布更新公告 2023-11-13 18:03:26 +08:00
RockChinQ 374ae25d9c fix: 启动时自动解决依赖后不正确的异常处理 2023-11-12 23:16:09 +08:00
RockChinQ c86529ac99 feat: 启动时不再自动更新websockets依赖 2023-11-12 22:59:49 +08:00
RockChinQ 6309f1fb78 chore(deps): 更换为自有分支yiri-mirai-rc 2023-11-12 20:31:07 +08:00
RockChinQ c246fb6d8e chore: release v2.6.4 2023-11-12 14:42:48 +08:00
RockChinQ ec6c041bcf ci(Dockerfile): 修复依赖安装问题 2023-11-12 14:42:07 +08:00
RockChinQ 2da5a9f3c7 ci(Dockerfile): 显式更新httpcore httpx和openai库 2023-11-12 14:18:42 +08:00
Junyan Qin 4e0df52d7c Merge pull request #592 from RockChinQ/fix/plugin-downloading
Feat: 通过 GitHub API 进行插件安装和更新
2023-11-12 14:07:52 +08:00
RockChinQ 71b8bf13e4 fix: 插件加载bug 2023-11-12 13:52:04 +08:00
RockChinQ a8b1e6ce91 ci: test 2023-11-12 12:05:04 +08:00
RockChinQ 1419d7611d ci(cmdpriv): 本地测试通过 2023-11-12 12:03:52 +08:00
RockChinQ 89c83ebf20 fix: 错误的判空变量 2023-11-12 11:30:10 +08:00
RockChinQ 76d7db88ea feat: 基于元数据记录的插件更新实现 2023-11-11 23:17:28 +08:00
RockChinQ 67a208bc90 feat: 添加插件元数据操作模块 2023-11-11 17:38:52 +08:00
RockChinQ acbd55ded2 feat: 插件安装改为直接下载源码 2023-11-10 23:01:56 +08:00
Junyan Qin 11a240a6d1 Merge pull request #591 from RockChinQ/feat/new-model-names
Feat: 更新模型索引
2023-11-10 21:23:22 +08:00
RockChinQ 97c85abbe7 feat: 更新模型索引 2023-11-10 21:16:33 +08:00
RockChinQ 06a0cd2a3d chore: 发布兼容性问题公告 2023-11-10 12:20:29 +08:00
GitHub Actions 572b215df8 Update override-all.json 2023-11-10 04:04:45 +00:00
RockChinQ 2c542bf412 chore: 不再默认在启动时升级依赖库 2023-11-10 12:04:25 +08:00
RockChinQ 1576ba7a01 chore: release v2.6.3 2023-11-10 12:01:20 +08:00
Junyan Qin 45e4096a12 Merge pull request #587 from RockChinQ/hotfix/openai-1.0-adaptation
Feat: 适配openai>=1.0.0
2023-11-10 11:49:20 +08:00
GitHub Actions 8a1d4fe287 Update override-all.json 2023-11-10 03:47:30 +00:00
RockChinQ 98f880ebc2 chore: 群内回复不再默认引用原消息 2023-11-10 11:47:10 +08:00
RockChinQ 2b852853f3 feat: 适配completion和chat_completions 2023-11-10 11:31:14 +08:00
RockChinQ c7a9988033 feat: 以新的方式设置正向代理 2023-11-10 10:54:03 +08:00
RockChinQ c475eebe1c chore: 不再限制openai版本 2023-11-10 10:14:11 +08:00
RockChinQ 0fe7355ae0 hotfix: 适配openai>=1.0.0 2023-11-10 10:13:50 +08:00
Junyan Qin 57de96e3a2 chore(requirements.txt): 锁定openai版本到0.28.1 2023-11-10 09:31:27 +08:00
Junyan Qin 70571cef50 Update README.md 2023-10-02 17:31:08 +08:00
Junyan Qin 0b6deb3340 Update README.md 2023-10-02 17:23:36 +08:00
Junyan Qin dcda85a825 Merge pull request #580 from RockChinQ/dependabot/pip/openai-approx-eq-0.28.1
chore(deps): update openai requirement from ~=0.28.0 to ~=0.28.1
2023-10-02 16:10:37 +08:00
dependabot[bot] 9d3bff018b chore(deps): update openai requirement from ~=0.28.0 to ~=0.28.1
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.28.0...v0.28.1)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 08:09:30 +00:00
RockChinQ 051376e0d2 Release v2.6.1 2023-09-28 12:18:46 +00:00
Junyan Qin a113785211 Merge pull request #578 from RockChinQ/fix/blocked-audit-upload
[Fix] 阻塞地发送审计报告数据
2023-09-28 20:17:26 +08:00
RockChinQ 3f4ed4dc3c fix: 阻塞地发送审计报告数据 2023-09-28 12:16:30 +00:00
Junyan Qin ac80764fae Merge pull request #577 from RockChinQ/doc/deadlinks-in-wiki
[Doc] 修复wiki中的死链
2023-09-28 20:04:01 +08:00
RockChinQ e43afd4891 doc: 修复wiki中的死链 2023-09-28 12:03:27 +00:00
RockChinQ f1aea1d495 doc: 统一改称指令为命令 2023-09-28 11:46:33 +00:00
GitHub Actions 0e2a5db104 Update override-all.json 2023-09-26 16:10:22 +00:00
Junyan Qin 3a4c9771fa feat(config): 默认超时时间改为两分钟 2023-09-27 00:09:58 +08:00
RockChinQ f4f8ef9523 ci: 工作流统一双空格缩进 2023-09-13 08:27:47 +00:00
RockChinQ b9ace69a72 Release v2.6.0 2023-09-13 08:13:24 +00:00
RockChinQ aef0b2a26e ci: 修复GITHUB_REF判断逻辑 2023-09-13 08:12:46 +00:00
RockChinQ f7712d71ec feat(pkgmgr): 使用清华源执行pip操作 2023-09-13 07:54:53 +00:00
RockChinQ e94b44e3b8 chore: 更新.gitignore 2023-09-13 07:22:12 +00:00
Junyan Qin 524e863c78 Merge pull request #567 from ruuuux/patch-1
添加 WikipediaSearch 插件
2023-09-13 11:57:54 +08:00
ruuuux bbc80ac901 添加 WikipediaSearch 插件 2023-09-13 11:55:11 +08:00
GitHub Actions f969ddd6ca Update override-all.json 2023-09-13 03:09:55 +00:00
RockChinQ 1cc9781333 chore(config): 添加 One API 的注释说明 2023-09-13 03:09:35 +00:00
Junyan Qin a609801bae Merge pull request #551 from flashszn/master
加入one-api项目支持的国内大模型
2023-09-13 11:00:21 +08:00
RockChinQ d8b606d372 doc(README.md): 添加 One API 支持公告 2023-09-13 02:45:04 +00:00
RockChinQ 572a440e65 doc(README.md): 添加 One API 的说明 2023-09-13 02:41:22 +00:00
RockChinQ 6e4eeae9b7 doc: 添加one-api模型注释说明 2023-09-13 02:34:11 +00:00
Shi Zhenning 1a73669df8 加入符合oneapi项目接口的国内模型
oneapi是一个api整合项目,通过这个项目的反代理,可以像使用gpt系列的/v1/completion接口一样调用国内的大模型,仅仅需要更改一下模型名字
2023-09-13 02:34:11 +00:00
RockChinQ 91ebaf1122 doc(README.md): 添加内容 2023-09-12 13:26:41 +00:00
RockChinQ 46703eb906 doc(README.md): docker部署说明 2023-09-12 13:09:42 +00:00
Junyan Qin b9dd9d5193 Merge pull request #566 from RockChinQ/docker-deployment
[CI] Docker 部署最佳实践
2023-09-12 21:06:53 +08:00
RockChinQ 884481a4ec doc(README.md): 镜像徽章 2023-09-12 13:04:18 +00:00
RockChinQ 9040b37a63 chore: 默认安装PyYaml依赖 2023-09-12 12:53:16 +00:00
RockChinQ 99d47b2fa2 doc: 修改Docker部署指引 2023-09-12 12:53:03 +00:00
RockChinQ 6575359a94 doc: 添加docker部署指南 2023-09-12 12:51:06 +00:00
RockChinQ a2fc726372 deploy: 添加docker-compose.yaml 2023-09-12 12:50:49 +00:00
RockChinQ 3bfce8ab51 ci: 优化docker镜像构建脚本 2023-09-12 10:21:40 +00:00
RockChinQ ff9a9830f2 chore: 更新requirements.txt 2023-09-12 10:21:19 +00:00
RockChinQ e2b59e8efe ci: 更新Dockerfile 2023-09-12 10:21:03 +00:00
Junyan Qin 04dad9757f Merge pull request #565 from RockChinQ/docker-image-test
[CI] Docker 部署脚本同步
2023-09-12 16:34:38 +08:00
Junyan Qin 75ea1080ad Merge pull request #351 from q123458384/patch-2
Create build_docker_image.yml
2023-09-12 15:57:44 +08:00
Junyan Qin e25b064319 doc(README.md): 为群号添加链接 2023-09-10 23:22:38 +08:00
Junyan Qin 5d0dbc40ce doc(README.md): 添加社区使用手册链接 2023-09-10 23:17:22 +08:00
Junyan Qin beae8de5eb Merge pull request #563 from RockChinQ/dependabot/pip/dulwich-approx-eq-0.21.6
chore(deps): update dulwich requirement from ~=0.21.5 to ~=0.21.6
2023-09-04 17:23:52 +08:00
dependabot[bot] c4ff30c722 chore(deps): update dulwich requirement from ~=0.21.5 to ~=0.21.6
Updates the requirements on [dulwich](https://github.com/dulwich/dulwich) to permit the latest version.
- [Release notes](https://github.com/dulwich/dulwich/releases)
- [Changelog](https://github.com/jelmer/dulwich/blob/master/NEWS)
- [Commits](https://github.com/dulwich/dulwich/compare/dulwich-0.21.5...dulwich-0.21.6)

---
updated-dependencies:
- dependency-name: dulwich
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 09:09:35 +00:00
Junyan Qin 6f4ecb101b Merge pull request #562 from RockChinQ/dependabot/pip/openai-approx-eq-0.28.0
chore(deps): update openai requirement from ~=0.27.9 to ~=0.28.0
2023-09-04 17:08:47 +08:00
dependabot[bot] 9f9b0ef846 chore(deps): update openai requirement from ~=0.27.9 to ~=0.28.0
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.9...v0.28.0)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 08:06:36 +00:00
RockChinQ de6957062c chore(config): 修改公用反代地址 2023-09-02 19:48:45 +08:00
Junyan Qin 0a9b43e6fa doc(README.md):社区群群号 2023-09-01 08:49:47 +08:00
Junyan Qin 5b0edd9937 Merge pull request #559 from oliverkirk-sudo/master
新增插件
2023-08-31 18:14:52 +08:00
oliverkirk-sudo 8a400d202a Update README.md
添加插件
2023-08-31 18:10:46 +08:00
RockChinQ 5a1e9f7fb2 doc(README.md): 修改徽章样式 2023-08-31 08:40:11 +00:00
RockChinQ e03af75cf8 doc: 更新部署节说明 2023-08-31 02:26:13 +00:00
RockChinQ 0da4919255 doc: 整理README.md格式 2023-08-31 02:23:30 +00:00
RockChinQ 914e566d1f doc(README.md): 更新wiki链接 2023-08-30 08:46:17 +00:00
RockChinQ 6ec2b653fe doc(wiki): 为wiki页标号 2023-08-30 08:41:59 +00:00
RockChinQ ba0a088b9c doc(wiki): 常见问题标号 2023-08-30 08:38:44 +00:00
RockChinQ 478e83bcd9 ci: 更新wiki同步工作流 2023-08-30 08:38:26 +00:00
RockChinQ 386124a3b9 doc(wiki): 页面标号 2023-08-30 08:36:30 +00:00
RockChinQ ff5e7c16d1 doc(wiki): 插件相关文档typo 2023-08-30 08:34:03 +00:00
RockChinQ 7ff7a66012 doc: 更新gpt4free的说明文档 2023-08-29 14:42:44 +08:00
Junyan Qin c99dfb8a86 Merge pull request #557 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.9
chore(deps): update openai requirement from ~=0.27.8 to ~=0.27.9
2023-08-28 16:29:55 +08:00
dependabot[bot] 10f9d4c6b3 chore(deps): update openai requirement from ~=0.27.8 to ~=0.27.9
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.8...v0.27.9)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 08:29:19 +00:00
Junyan Qin d347813411 Merge pull request #556 from oliverkirk-sudo/master
Update README.md
2023-08-24 17:06:29 +08:00
oliverkirk-sudo 7a93898b3f Update README.md 2023-08-24 17:00:18 +08:00
Junyan Qin c057ea900f Merge pull request #548 from oliverkirk-sudo/master
修改插件信息
2023-08-15 09:35:55 +08:00
oliverkirk-sudo 512266e74f 修改插件信息 2023-08-14 23:21:51 +08:00
RockChinQ e36aee11c7 doc(README.md): 更新README.md 2023-08-14 19:12:39 +08:00
RockChinQ 97421299f5 doc(README.md): Claude和Bard的说明 2023-08-14 19:11:48 +08:00
Junyan Qin bc41e5aa80 Update README.md 2023-08-14 16:57:55 +08:00
RockChinQ 2fa30e7def doc(README.md): 徽章格式 2023-08-14 16:56:50 +08:00
RockChinQ 1c6a7d9ba5 doc(README.md): 添加群号徽章 2023-08-14 16:09:11 +08:00
RockChinQ 47435c42a5 doc(README.md): 更新视频教程 2023-08-12 12:26:00 +08:00
Junyan Qin 39a1b421e6 doc(CONTRIBUTING.md): 添加字段使用规范 2023-08-09 20:12:46 +08:00
Junyan Qin b5edf2295b doc(CONTRIBUTING.md): 添加代码规范 2023-08-08 20:14:00 +08:00
RockChinQ fb650a3d7a chore: config.py添加反代地址 2023-08-07 11:43:28 +08:00
Junyan Qin 521541f311 Merge pull request #534 from RockChinQ/doc-add-gif
[Doc] 添加演示GIF图
2023-08-06 17:20:49 +08:00
Junyan Qin 7020abadbf Add files via upload 2023-08-06 17:19:54 +08:00
Junyan Qin d95fb3b5be Delete webwlkr-demo.gif 2023-08-06 17:18:58 +08:00
Junyan Qin 3e524dc790 Add files via upload 2023-08-06 17:18:00 +08:00
Junyan Qin a64940bff8 Update README.md 2023-08-06 17:13:10 +08:00
Junyan Qin c739290f0b Add files via upload 2023-08-06 17:07:22 +08:00
RockChinQ af292fe050 Release v2.5.2 2023-08-06 14:58:13 +08:00
Junyan Qin 634c7fb302 Merge pull request #533 from RockChinQ/perf-function-call-process
[Perf] 优化函数调用的底层逻辑
2023-08-06 14:52:01 +08:00
RockChinQ 33efb94013 feat: 应用cmdpriv时忽略不存在的命令 2023-08-06 14:50:22 +08:00
GitHub Actions 549e4dc02e Update override-all.json 2023-08-06 06:41:00 +00:00
RockChinQ 3d40909c02 feat: 不再默认启用trace_function_calls 2023-08-06 14:40:35 +08:00
RockChinQ 1aef81e38f perf: 修改网络问题时的报错 2023-08-06 12:17:04 +08:00
RockChinQ 1b0ae8da58 refactor: session append重命名为query 2023-08-05 22:00:32 +08:00
GitHub Actions Bot 7979a8e97f Update cmdpriv-template.json 2023-08-05 13:52:03 +00:00
RockChinQ 080e53d9a9 feat: 刪除continue命令 2023-08-05 21:51:34 +08:00
GitHub Actions 89bb364b16 Update override-all.json 2023-08-05 13:44:30 +00:00
RockChinQ 3586cd941f feat: 支持跟踪函数调用过程并默认启用 2023-08-05 21:44:11 +08:00
RockChinQ 054d0839ac fix: 未序列化的function_call属性 2023-08-04 19:08:48 +08:00
RockChinQ dd75f98d85 feat: 世界上最先进的调用流程 2023-08-04 18:41:04 +08:00
RockChinQ ec23bb5268 doc(README.md): 添加视频教程链接 2023-08-04 17:22:13 +08:00
Junyan Qin bc99db4fc1 Merge pull request #531 from RockChinQ/feat-load-balance
[Feat] api-key主动负载均衡
2023-08-04 17:14:03 +08:00
RockChinQ c8275fcfbf feat(openai): 支持apikey主动切换策略 2023-08-04 17:10:07 +08:00
GitHub Actions a345043c30 Update override-all.json 2023-08-04 07:21:52 +00:00
RockChinQ 382d37d479 chore: 添加key切换策略配置项 2023-08-04 15:21:31 +08:00
RockChinQ 32c144a75d doc(README.md): 增加简介 2023-08-03 23:40:01 +08:00
RockChinQ 7ca2aa5e39 doc(wiki): 说明逆向库插件也支持函数调用 2023-08-03 18:44:16 +08:00
RockChinQ 86cc4a23ac fix: func命令列表标号未自增 2023-08-03 17:53:43 +08:00
RockChinQ 08d1e138bd doc: 删除过时公告 2023-08-02 21:46:59 +08:00
Junyan Qin a9fe86542f Merge pull request #530 from RockChinQ/doc-readme
[Docs] 重新整理README.md格式
2023-08-02 21:09:39 +08:00
RockChinQ 4e29776fcd doc: 整理插件生态章节 2023-08-02 21:07:31 +08:00
RockChinQ ee3eae8f4d doc: 完善徽章 2023-08-02 21:06:34 +08:00
RockChinQ a84575858a doc: 整理徽章 2023-08-02 21:04:23 +08:00
RockChinQ ac472291c7 doc: 赞赏章节 2023-08-02 20:59:01 +08:00
RockChinQ f304873c6a doc(wiki): 内容函数页 2023-08-02 20:59:01 +08:00
RockChinQ 18caf8face doc: 致谢章节 2023-08-02 20:59:01 +08:00
RockChinQ d21115aaa8 doc: 优化起始章节 2023-08-02 20:59:01 +08:00
RockChinQ a05ecd2e7f doc: 更多section 2023-08-02 20:59:01 +08:00
RockChinQ 32a725126d doc: 模型适配一览 2023-08-02 20:59:01 +08:00
RockChinQ 0528690622 doc: 修改logo 2023-08-02 20:51:07 +08:00
Junyan Qin 819339142e Merge pull request #529 from RockChinQ/feat-funcs-called-args
[Feat] NormalMessageResponded添加func_called参数
2023-08-02 18:02:48 +08:00
RockChinQ 1d0573e7ff feat: NormalMessageResponded添加func_called参数 2023-08-02 18:01:02 +08:00
RockChinQ 00623bc431 typo(plugin): 插件执行报错提示 2023-08-02 11:35:20 +08:00
Junyan Qin c872264456 Merge pull request #525 from RockChinQ/feat-finish-reason-param
[Feat] 为NormalMessageResponded事件添加finish_reason参数
2023-08-01 14:40:14 +08:00
RockChinQ 1336d3cb9a fix: chat_completion不传回finish_reason的问题 2023-08-01 14:39:57 +08:00
RockChinQ d1459578cd doc(wiki): 插件开发页说明 2023-08-01 14:33:32 +08:00
RockChinQ 8a67fcf40f feat: 为NormalMessageResponded事件添加finish_reason参数 2023-08-01 14:31:38 +08:00
RockChinQ 7930370aa9 chore: 发布函数调用功能公告 2023-08-01 10:50:23 +08:00
RockChinQ 0b854bdcf1 feat(chat_completion): 不生成到stop以使max_tokens参数生效 2023-08-01 10:26:23 +08:00
Junyan Qin cba6aab48d Merge pull request #524 from RockChinQ/feat-at-sender
[Feat] 支持在群内回复时at发送者
2023-08-01 10:14:34 +08:00
GitHub Actions 12a9ca7a77 Update override-all.json 2023-08-01 02:13:35 +00:00
RockChinQ a6cbd226e1 feat: 支持设置群内回复时at发送者 2023-08-01 10:13:15 +08:00
RockChinQ 3577e62b41 perf: 简化启动时输出 2023-07-31 21:11:28 +08:00
RockChinQ f86e69fcd1 perf: 简化启动时的输出信息 2023-07-31 21:05:23 +08:00
RockChinQ 292e00b078 perf: 简化启动时的输出信息 2023-07-31 21:04:59 +08:00
RockChinQ 2a91497bcf chore: .gitignore排除qcapi/ 2023-07-31 20:23:54 +08:00
RockChinQ b0cca0a4c2 Release v2.5.1 2023-07-31 18:12:59 +08:00
Junyan Qin a2bda85a9c Merge pull request #523 from RockChinQ/feat-prompt-preprocess-event
[Feat] 新增PromptPreprocessing事件
2023-07-31 17:55:06 +08:00
RockChinQ 20677cff86 doc(wiki): 插件开发页增加版本断言说明 2023-07-31 17:53:33 +08:00
RockChinQ c8af5d8445 feat: 添加版本断言函数require_ver 2023-07-31 17:46:30 +08:00
RockChinQ 2dbe984539 doc(wiki): 添加事件wiki说明 2023-07-31 17:27:28 +08:00
RockChinQ 6b8fa664f1 feat: 新增PromptPreprocessing事件 2023-07-31 17:21:09 +08:00
RockChinQ 2b9612e933 chore: 提交部分测试文件 2023-07-31 16:24:39 +08:00
RockChinQ 749d0219fb chore: 删除弃用模块 2023-07-31 16:23:31 +08:00
Junyan Qin a11a152bd7 ci: 解决sync-wiki.yml异常退出问题 2023-07-31 15:41:37 +08:00
Junyan Qin fc803a3742 Merge pull request #522 from RockChinQ/feat-generating-stop-case
[Feat] 新增!continue命令
2023-07-31 15:34:30 +08:00
GitHub Actions Bot 13a1e15f24 Update cmdpriv-template.json 2023-07-31 07:24:14 +00:00
RockChinQ 3f41b94da5 feat: 完善命令文档 2023-07-31 15:23:42 +08:00
RockChinQ 0fb5bfda20 ci: 添加tiktoken依赖 2023-07-31 15:20:23 +08:00
RockChinQ dc1fd73ebb feat: 添加continue命令 2023-07-31 15:17:49 +08:00
Junyan Qin 161b694f71 Merge pull request #521 from RockChinQ/fix-usage-not-reported
[Fix] text的使用量未上报
2023-07-31 14:31:48 +08:00
RockChinQ 45d1c89e45 fix: text的使用量未上报 2023-07-31 14:28:48 +08:00
Junyan Qin e26664aa51 Merge pull request #520 from RockChinQ/feat-accurately-calculate-tokens
feat: 使用tiktoken计算tokens数
2023-07-31 12:16:10 +08:00
RockChinQ e29691efbd feat: 使用tiktoken计算tokens数 2023-07-31 11:59:22 +08:00
RockChinQ 6d45327882 debug: 接口底层添加返回数据debug信息 2023-07-31 10:37:45 +08:00
RockChinQ fbd41eef49 chore: 删除devcontainer.json 2023-07-31 10:37:14 +08:00
Junyan Qin 0a30c88322 doc(README.md): 插件列表 2023-07-31 00:07:39 +08:00
Junyan Qin 4f5af0e8c8 Merge pull request #518 from RockChinQ/fix-cannot-disable-funcs-dynamically
[Fix] plugin启用禁用命令对内容函数不生效
2023-07-30 23:56:01 +08:00
RockChinQ df3f0fd159 fix: plugin启用禁用命令对内容函数不生效 2023-07-30 23:54:56 +08:00
RockChinQ f2493c79dd doc(wiki): 添加联网内容函数提问示例 2023-07-29 19:34:47 +08:00
RockChinQ a86a035b6b doc: 更新README.md 2023-07-29 19:26:28 +08:00
RockChinQ 7995793bfd doc(wiki): 添加内容函数页 2023-07-29 19:24:56 +08:00
RockChinQ a56b340646 Release v2.5.0 2023-07-29 18:59:25 +08:00
Junyan Qin 7473cdfe16 Merge pull request #513 from RockChinQ/feat-function-calling-integration
[Feat] 支持GPT的函数调用功能
2023-07-29 18:57:29 +08:00
RockChinQ 24273ac158 doc: README添加内容函数相关内容 2023-07-29 18:55:18 +08:00
RockChinQ fe6275000e doc(wiki): 更新wiki插件页 2023-07-29 18:40:49 +08:00
RockChinQ 5fbf369f82 doc(wiki): 更新插件页 2023-07-29 18:37:03 +08:00
Junyan Qin 4400475ffa chore: 添加Webwlkr插件示例 2023-07-29 17:41:56 +08:00
GitHub Actions Bot 796eb7c95d Update cmdpriv-template.json 2023-07-29 09:30:22 +00:00
RockChinQ 89a01378e7 ci: 跑工作流 2023-07-29 17:29:52 +08:00
RockChinQ f4735e5e30 ci(cmd_priv): 添加CallingGPT依赖 2023-07-29 17:28:11 +08:00
RockChinQ f1bb3045aa feat: 添加func命令 2023-07-29 17:26:07 +08:00
RockChinQ 96e474a555 feat: 插件开关对其内容函数生效 2023-07-29 17:10:47 +08:00
RockChinQ 833d29b101 typo: enable->enabled 2023-07-29 16:55:01 +08:00
RockChinQ dce6734ba2 feat: 改为推荐使用func()装饰器注册内容函数 2023-07-29 16:51:19 +08:00
RockChinQ 0481167dc6 feat: 改为在start流程设置openai.proxy 2023-07-29 16:36:31 +08:00
RockChinQ a002f93f7b chore: 删除过时代码 2023-07-29 16:30:09 +08:00
RockChinQ 3c894fe70e feat: chat_completion的函数开关支持 2023-07-29 16:29:16 +08:00
RockChinQ 8c69b8a1d9 feat: 内容函数全局开关支持 2023-07-29 16:28:18 +08:00
Junyan Qin a9dae05303 doc(README.md): 修改社区群群号 2023-07-29 13:31:58 +08:00
RockChinQ ae6994e241 feat(contentPlugin): 完成基本的内容函数调用功能 2023-07-28 19:03:02 +08:00
Rock Chin caa72fa40c feat: 在插件层面初步支持内容函数 2023-07-27 14:27:36 +08:00
Junyan Qin 46cc9220c3 Merge pull request #506 from RockChinQ/perf-persist-dprompt-when-auto-reset
[Perf] 在session自动重置时保留非default的prompt
2023-07-07 17:53:29 +08:00
Rock Chin ddb56d7a8e fix: reset命令错误的逻辑 2023-07-07 17:49:43 +08:00
Rock Chin a0267416d7 fix: 修复reset逻辑导致的无法初始化情景预设问题 2023-07-07 16:37:05 +08:00
Rock Chin 56e1ef3602 fix: 修复reset可能引起的bug 2023-07-07 16:35:37 +08:00
Rock Chin b4fc1057d1 perf: 在session自动重置时保留非default的prompt (#494) 2023-07-06 23:09:39 +08:00
Rock Chin 06037df607 ci: 仅在master分支运行sync-wiki工作流 2023-06-20 22:42:18 +08:00
Rock Chin dce134d08d Release v2.4.7 2023-06-16 19:43:13 +08:00
Junyan Qin cca471d068 Merge pull request #500 from RockChinQ/perf-more-model-support
Perf more model support
2023-06-16 19:40:29 +08:00
Rock Chin ddb211b74a feat: 支持新的模型 2023-06-16 19:35:26 +08:00
Rock Chin cef70751ff chore: 修改配置文件说明 2023-06-16 19:35:06 +08:00
JunYan Qin 2d2219fc6e 更新 README.md 2023-06-13 11:59:30 +08:00
JunYan Qin 514a6b4192 更新 README.md 2023-06-13 11:59:07 +08:00
JunYan Qin 7a552b3434 Merge pull request #496 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.8
chore(deps): update openai requirement from ~=0.27.7 to ~=0.27.8
2023-06-12 23:20:02 +08:00
dependabot[bot] ecebd1b0e0 chore(deps): update openai requirement from ~=0.27.7 to ~=0.27.8
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.7...v0.27.8)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 09:01:51 +00:00
Rock Chin 8dc34d2a88 doc(README.md): 添加卖号网站 2023-06-12 14:13:36 +08:00
Rock Chin d52644ceec doc: 更新README.md 2023-06-12 12:24:59 +08:00
Rock Chin 3052510591 Release v2.4.6 2023-06-08 14:01:18 +08:00
Rock Chin 777a5617db Merge pull request #492 from RockChinQ/feat-ignore-major-vernum
[Feat] 更新时忽略主版本号不同的版本
2023-06-08 14:00:32 +08:00
Rock Chin e17c1087e9 feat(updater.py): 更新时忽略主版本号不同的版本 2023-06-08 13:57:24 +08:00
Rock Chin 633695175a Merge pull request #491 from RockChinQ/feat-tokens-auto-reset
[Feat] token超限报错时自动重置会话
2023-06-08 13:49:49 +08:00
Rock Chin 9e78bf3d21 perf: 更严格的重置条件判断 2023-06-08 13:49:20 +08:00
Rock Chin 43aa68a55d feat: 支持在token超限时自动重置会话 2023-06-08 13:45:54 +08:00
Rock Chin b8308f8c57 Merge branch 'feat-tokens-auto-reset' of https://github.com/RockChinQ/QChatGPT into feat-tokens-auto-reset 2023-06-08 13:43:36 +08:00
Rock Chin 466bfbddeb perf: 提示语格式 2023-06-08 13:43:33 +08:00
GitHub Actions b6da07b225 Update override-all.json 2023-06-08 05:20:55 +00:00
Rock Chin 2f2159239a chore: 添加开关和提示语配置项 2023-06-08 13:20:33 +08:00
Rock Chin 67d1ca8a65 Merge pull request #490 from RockChinQ/feat-global-group-private-enable
[Feat] 支持设置全局群聊/私聊消息禁用
2023-06-07 23:49:53 +08:00
Rock Chin 497a393e83 doc: 修改wiki 2023-06-07 23:49:09 +08:00
Rock Chin 782c0e22ea feat: 支持设置全局群聊、私聊禁用 2023-06-07 23:47:13 +08:00
Rock Chin 2932fc6dfd chore(banlist-template.py): 添加配置项 2023-06-07 23:23:21 +08:00
Rock Chin 0a9eab2113 chore(requirements.txt): 更新requests版本 2023-06-06 09:37:00 +08:00
Rock Chin 50a673a8ec doc: 添加插件列表list 2023-06-05 22:37:19 +08:00
Rock Chin 9e25d0f9e4 Release v2.4.5 2023-05-31 18:31:25 +08:00
Rock Chin 23cd7be711 Merge pull request #487 from RockChinQ/feat-banlist-syntax-check
feat: 初始化流程异常处理
2023-05-31 18:25:46 +08:00
Rock Chin 025b9e33f1 feat: 初始化流程异常处理 2023-05-31 18:24:01 +08:00
Rock Chin bab2f64913 doc(README_en.md): 添加wakapi计时 2023-05-29 11:12:07 +08:00
Rock Chin b00e09aa9c doc: 添加wakapi计时 2023-05-29 11:10:49 +08:00
Rock Chin 0b109fdc7a Merge pull request #479 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.7
chore(deps): update openai requirement from ~=0.27.6 to ~=0.27.7
2023-05-22 17:13:50 +08:00
dependabot[bot] 018fea2ddb chore(deps): update openai requirement from ~=0.27.6 to ~=0.27.7
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.6...v0.27.7)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 09:05:00 +00:00
Rock Chin f8a3cc4352 doc: 折起OpenAI注册步骤 2023-05-21 18:25:35 +08:00
Rock Chin 6ab853acc1 doc: 修改关于HuggingChat的说明 2023-05-21 17:50:35 +08:00
Rock Chin e825dea02f chore: 排除hugchat.json 2023-05-21 17:47:42 +08:00
Rock Chin cf8740d16e Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-05-21 17:33:47 +08:00
Rock Chin 9c4809e26f chore: 发布revLibs相关公告 2023-05-21 17:33:44 +08:00
Rock Chin 0a232fd9ef Merge pull request #477 from RockChinQ/feature-detailed-cfg-cmd
[Feat] 支持使用!cfg指令修改子配置项
2023-05-21 15:59:59 +08:00
Rock Chin 23016a0791 doc: 更新wiki说明 2023-05-21 15:58:21 +08:00
Rock Chin cdcc67ff23 feat(!cfg): 使用eval()函数进行类型转换 2023-05-21 15:53:56 +08:00
Rock Chin 92274bfc34 feat(!cfg): 支持使用点号索引子配置项 2023-05-21 15:49:56 +08:00
Rock Chin 2fed6f61ba Release v2.4.4 2023-05-21 15:15:28 +08:00
Rock Chin 59b2cd26d2 Merge pull request #476 from RockChinQ/hotfix-471-at-no-response-aft-reload
[Fix] 热重载之后不响应群内at
2023-05-21 15:12:43 +08:00
Rock Chin f7b87e99d2 fix(manager.py): 热重载之后不响应群内at 2023-05-21 15:11:34 +08:00
Rock Chin 70bc985145 perf(nakuru.py): access-token被拒时报警 2023-05-18 21:06:32 +08:00
Rock Chin 070dbe9108 chore: 排除venv/目录 2023-05-18 21:05:45 +08:00
Rock Chin a63fa6d955 chore: yiri-mirai使用0.2.7 2023-05-18 21:05:30 +08:00
Rock Chin c7703809b0 Merge pull request #475 from RockChinQ/actively-delay
[Feat] 支持设置消息回复强制延迟以降低风控概率
2023-05-18 20:15:52 +08:00
GitHub Actions 37eb74338f Update override-all.json 2023-05-18 12:14:19 +00:00
Rock Chin 77d5585b7c feat: 修改强制延迟默认范围 2023-05-18 20:13:53 +08:00
Rock Chin 6cab3ef029 Merge branch 'actively-delay' of https://github.com/RockChinQ/QChatGPT into actively-delay 2023-05-18 20:12:39 +08:00
Rock Chin 820a7b78fc feat: 处理过程支持强制延迟 2023-05-18 20:12:36 +08:00
GitHub Actions c51dffef3a Update override-all.json 2023-05-18 12:10:33 +00:00
Rock Chin 983bc3da3c chore: 添加强制延迟配置项 2023-05-18 20:10:08 +08:00
Rock Chin 09be956a58 Merge pull request #474 from RockChinQ/command-notfound-err
[Perf] 修改指令不存在时的提示信息
2023-05-18 19:45:25 +08:00
Rock Chin 5eded50c53 perf: 修改指令不存在时的提示信息 2023-05-18 19:44:20 +08:00
Rock Chin 6d8eebd314 doc: 添加微信赞赏码 2023-05-16 15:36:07 +08:00
Rock Chin 19a0572b5f Release v2.4.3.1 2023-05-15 17:38:03 +08:00
Rock Chin 6272e98474 Merge pull request #467 from RockChinQ/perf-plugin-update
[Perf] 优化插件更新相关操作
2023-05-14 18:45:36 +08:00
Rock Chin 45042fe7d4 doc: 更新插件更新命令wiki 2023-05-14 18:44:14 +08:00
Rock Chin d85e840126 perf: 优化插件更新操作,支持更新单个插件 2023-05-14 18:41:20 +08:00
Rock Chin 804889f1de perf: 加载模块的输出改为debug级别 2023-05-14 17:30:05 +08:00
Rock Chin 919c996434 doc: 添加HuggingChat 2023-05-14 17:14:46 +08:00
Rock Chin 00823b3d62 doc(README): 添加HuggingChat 2023-05-14 17:14:28 +08:00
Rock Chin af54efd24a doc(README.md): 添加系统状态插件 2023-05-14 14:58:48 +08:00
Rock Chin b1c9b121f6 Update go-cqhttp配置.md 2023-05-08 21:51:03 +08:00
Rock Chin 7b5649d153 Merge pull request #461 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.6
chore(deps): update openai requirement from ~=0.27.5 to ~=0.27.6
2023-05-08 18:48:20 +08:00
dependabot[bot] 52bf716d84 chore(deps): update openai requirement from ~=0.27.5 to ~=0.27.6
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.5...v0.27.6)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 10:34:21 +00:00
Rock Chin c149dd7b66 Merge pull request #462 from RockChinQ/dependabot/pip/dulwich-approx-eq-0.21.5
chore(deps): update dulwich requirement from ~=0.21.3 to ~=0.21.5
2023-05-08 18:24:59 +08:00
dependabot[bot] 65d5a1ed63 chore(deps): update dulwich requirement from ~=0.21.3 to ~=0.21.5
Updates the requirements on [dulwich](https://github.com/dulwich/dulwich) to permit the latest version.
- [Release notes](https://github.com/dulwich/dulwich/releases)
- [Changelog](https://github.com/jelmer/dulwich/blob/master/NEWS)
- [Commits](https://github.com/dulwich/dulwich/compare/dulwich-0.21.3...dulwich-0.21.5)

---
updated-dependencies:
- dependency-name: dulwich
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 09:01:55 +00:00
Rock Chin 5516754bbb doc(README.md): 添加docker部署提示 2023-05-02 14:30:25 +08:00
Rock Chin 08082f2ee3 Merge pull request #452 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.5
chore(deps): update openai requirement from ~=0.27.4 to ~=0.27.5
2023-05-01 17:26:18 +08:00
dependabot[bot] 8489266080 chore(deps): update openai requirement from ~=0.27.4 to ~=0.27.5
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.4...v0.27.5)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 09:06:44 +00:00
Rock Chin 51c7e0b235 doc(README.md): 四群群号 2023-04-28 00:37:29 +08:00
Rock Chin 628b6b0bb4 Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-27 15:01:47 +08:00
Rock Chin 7e024d860d doc: 增加LightQChat的公告 2023-04-27 15:01:44 +08:00
Rock Chin c2f6273f70 Merge pull request #442 from oliverkirk-sudo/master
修复异常输出时的类型问题
2023-04-26 17:31:19 +08:00
oliverkirk-sudo 96e401ec7b 修复异常输出时的类型问题 2023-04-26 17:27:33 +08:00
Rock Chin ae8ac65447 feat: 更换使用清华源 (#438) 2023-04-26 11:52:07 +08:00
Rock Chin 2d4f59f36e doc: 强调02 2023-04-26 11:18:07 +08:00
Rock Chin 0e85467e02 Release v2.4.2 2023-04-25 10:27:57 +08:00
Rock Chin eb41cf5481 fix(plugin.py): 兼容性问题 2023-04-25 10:27:07 +08:00
Rock Chin b970a42d07 fix(plugin.py): send_message封装实现的兼容性问题 2023-04-25 10:26:03 +08:00
Rock Chin 8c9d123e1c Merge pull request #433 from RockChinQ/detailed-response-rules
[Feat] 细化到单个群的响应规则
2023-04-25 09:39:56 +08:00
Rock Chin ab2a95e347 Merge branch 'detailed-response-rules' of https://github.com/RockChinQ/QChatGPT into detailed-response-rules 2023-04-25 09:31:56 +08:00
Rock Chin 2184c558a4 feat: 支持配置细化到单个群的响应规则 2023-04-25 09:31:44 +08:00
GitHub Actions 83cb8588fd Update override-all.json 2023-04-25 01:28:56 +00:00
Rock Chin 007e82c533 feat: 配置文件支持 2023-04-25 09:28:31 +08:00
Rock Chin 499f8580a7 doc: 修改wiki格式 2023-04-25 08:45:58 +08:00
Rock Chin a7dc3c5dab Release v2.4.1 2023-04-25 00:01:40 +08:00
Rock Chin d01d3a3c53 perf: 启动时提示使用的QQ号 2023-04-24 23:57:57 +08:00
Rock Chin 580e062dbf feat: 上报使用量时带上msg_source_adapter 2023-04-24 23:51:00 +08:00
Rock Chin c8cee8410c doc: 完善格式 2023-04-24 20:04:33 +08:00
Rock Chin 6bf331c2e3 doc: 完善wiki 2023-04-24 19:53:20 +08:00
Rock Chin 4c4930737c chore: issue模板新增登录框架字段 2023-04-24 19:28:00 +08:00
Rock Chin 9de01e9525 Release v2.4.0 2023-04-24 16:09:46 +08:00
Rock Chin c6a16f5974 Merge pull request #427 from RockChinQ/nakuru-support
[Feat] 支持通过nakuru-project框架连接go-cqhttp
2023-04-24 16:07:12 +08:00
Rock Chin 253ef44d17 chore: 公告 2023-04-24 16:05:47 +08:00
Rock Chin 15a1f00b73 doc(README.md): 添加go-cqhttp公告 2023-04-24 16:04:25 +08:00
Rock Chin b5fa2ea8b8 feat(main.py): 添加nakuru-project-idk的依赖更新项 2023-04-24 16:01:43 +08:00
Rock Chin 449e024771 doc: 添加针对老用户的说明 2023-04-24 15:59:07 +08:00
Rock Chin 1bee7a146b feat: 支持语音组件 2023-04-24 15:55:21 +08:00
Rock Chin 270a632789 doc: 修改标号 2023-04-24 15:48:28 +08:00
Rock Chin 418bb05b4c doc: 添加go-cqhttp配置说明 2023-04-24 15:46:58 +08:00
Rock Chin 052b834151 doc: 完善config-template.py的说明 2023-04-24 15:46:26 +08:00
Rock Chin 58ee204a75 doc: wiki添加go-cqhttp配置步骤 2023-04-24 15:41:28 +08:00
Rock Chin 0a02ee8c04 feat: 启动时添加nakuru的提示检查 2023-04-24 15:04:07 +08:00
Rock Chin 950ef4a181 doc: 更新README.md 2023-04-24 14:57:28 +08:00
Rock Chin 7b7cdd8adb perf: 在日志文件包含输出文件路径 2023-04-24 13:52:22 +08:00
Rock Chin 471768e760 feat: 支持发送转发消息 2023-04-24 12:46:33 +08:00
Rock Chin c7517d31a4 chore: 更换使用nakuru-project-idk包 2023-04-24 11:37:01 +08:00
Rock Chin 7d10d0398e fix: nakuru热重载失败 2023-04-24 11:21:51 +08:00
Rock Chin a2bc25c08b feat: 支持引用原消息回复 2023-04-24 10:57:43 +08:00
Rock Chin 3cb49fe2d8 feat: 支持检测群内禁言 2023-04-24 10:34:51 +08:00
Rock Chin 5b96ac122f feat: 适配nakuru基本功能 2023-04-23 23:40:08 +08:00
Rock Chin 612033f478 feat: nakuru适配器基础模型 2023-04-23 15:58:37 +08:00
GitHub Actions 48ee940d8e Update override-all.json 2023-04-23 01:32:36 +00:00
Rock Chin e74df0b37d chore: 添加nakuru相关配置; 使用nakuru-project-test临时包 2023-04-23 09:32:01 +08:00
GitHub Actions 640afdc49c Update override-all.json 2023-04-22 13:51:02 +00:00
Rock Chin 6b39df5b9b chore: 删除NoneBot2相关配置 2023-04-22 21:50:41 +08:00
Rock Chin e7e698765e fix(plugin.py): 缺少的换行符 2023-04-22 17:40:41 +08:00
Rock Chin 43fea13dab Merge pull request #418 from RockChinQ/im-impl-decoupling
[Refactor] 新增抽象层以解耦消息来源(MessageSource)组件
2023-04-21 18:10:42 +08:00
GitHub Actions bc899e5bd0 Update override-all.json 2023-04-21 09:52:31 +00:00
Rock Chin 160086feb9 refactor: 完成MessageSource适配器解耦 2023-04-21 17:51:58 +08:00
Rock Chin 016391c976 refactor: 不再向QQBotManager中传递config中可读的参数 2023-04-21 17:15:32 +08:00
Rock Chin 91746448a3 feat: 消息源适配器模型及YiriMirai的适配器 2023-04-21 16:36:59 +08:00
Rock Chin 5cb0543237 doc(README.md): 更新wiki链接 2023-04-20 20:50:00 +08:00
Rock Chin fac29a24a8 doc(README.md): social.png更改成圆角 2023-04-20 10:54:06 +08:00
Rock Chin 4d3a2a21d0 Update README_en.md 2023-04-20 00:22:05 +08:00
Rock Chin 6d4f88041c Update README.md 2023-04-20 00:21:37 +08:00
Rock Chin 18587d3690 doc(README.md): 修改social图格式 2023-04-20 00:15:11 +08:00
Rock Chin 423090dccd doc(README.md): 更改使用social图 2023-04-20 00:13:11 +08:00
Rock Chin 78e88baab3 doc(README.md): 优化LOGO图格式 2023-04-20 00:08:00 +08:00
Rock Chin 6a276767b3 doc(README.md): 添加LOGO 2023-04-20 00:06:52 +08:00
Rock Chin 2cb26c7c70 doc: 添加LOGO文件 2023-04-20 00:04:01 +08:00
Rock Chin ff66c88060 doc(README.md): 优化图片格式 2023-04-17 10:18:23 +08:00
Rock Chin 611e82b8f9 doc(README.md): 添加使用截图 2023-04-17 10:15:50 +08:00
Rock Chin 59bdee7137 feat: 添加IM框架模型 2023-04-15 23:38:52 +08:00
Rock Chin e8dbd426ae Release v2.3.9 2023-04-15 17:36:59 +08:00
Rock Chin 40d6e809a0 Merge pull request #417 from RockChinQ/354-feature-single-concurrency
[Feat] 支持设置单会话内同时仅处理一条消息
2023-04-15 17:35:36 +08:00
GitHub Actions 236c540d18 Update override-all.json 2023-04-15 09:34:16 +00:00
Rock Chin d6ca059f6c feat: 支持设置单会话内同时仅处理一条消息 2023-04-15 17:33:57 +08:00
Rock Chin 52c06a60ca fix: 公告功能bug 2023-04-15 16:54:50 +08:00
Rock Chin 6353644ec3 test: 测试公告 2023-04-15 16:49:11 +08:00
Rock Chin 20df9ded3d Merge pull request #416 from RockChinQ/413-feature-json-format-anouns
[Feat] 支持JSON格式的公告
2023-04-15 16:47:03 +08:00
Rock Chin 7569b18a4c feat: 支持JSON格式的公告 2023-04-15 16:45:26 +08:00
Rock Chin b9da4f4951 Merge pull request #415 from RockChinQ/413-feature-json-format-anouns
[Feat] 新增`announcement.json`文件
2023-04-15 16:33:03 +08:00
Rock Chin 89b9e29257 Update pull_request_template.md 2023-04-15 16:25:24 +08:00
Rock Chin d605de9de4 feat: 添加公告模板及公告发布脚本 2023-04-15 09:38:46 +08:00
Rock Chin d46c94d7c3 Release v2.3.8 2023-04-14 23:47:00 +08:00
Rock Chin 2db9c00530 Merge pull request #414 from RockChinQ/detailed-rate-limit
[Feat] 速度限制支持细化到单个人或群
2023-04-14 19:46:24 +08:00
GitHub Actions 66d8d159f9 Update override-all.json 2023-04-14 11:44:26 +00:00
Rock Chin 9fa1446284 feat: 支持细化到个人和群的限速 2023-04-14 19:44:03 +08:00
Rock Chin b3e4cb48c7 Merge pull request #412 from RockChinQ/349-bugfix-auto-deps-solving-failure
[Fix] 循环依赖导致的依赖自动解决失败
2023-04-14 18:44:40 +08:00
Rock Chin 0bca7b2247 fix: 循环引用导致的依赖自动解决失败 2023-04-14 18:42:09 +08:00
Rock Chin 7812e03c9d chore: 删除requirements.txt中对websockets的版本要求以防冲突 2023-04-14 18:27:44 +08:00
Rock Chin 7a852ae5af Merge pull request #410 from 2675hujilo/tips
删除tips-custom-template.py中无用字段
2023-04-14 17:43:30 +08:00
26751 706d9e61c1 删除tips-custom-template.py中无用字段 2023-04-14 02:00:45 +08:00
Rock Chin 8f0ed4ff4b Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-12 15:28:59 +08:00
Rock Chin 3415b6f121 doc: 添加lieyanqzu/WeatherPlugin 2023-04-12 15:28:56 +08:00
Rock Chin 256ba6fb86 Merge pull request #406 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.4
chore(deps): update openai requirement from ~=0.27.2 to ~=0.27.4
2023-04-10 18:31:39 +08:00
dependabot[bot] d30b2b9afe chore(deps): update openai requirement from ~=0.27.2 to ~=0.27.4
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.2...v0.27.4)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 09:03:02 +00:00
Rock Chin be943ca1fc doc: 链接文档 2023-04-08 19:39:42 +08:00
Rock Chin 1ddab2a97a doc: README.md in English 2023-04-08 19:32:31 +08:00
Rock Chin e15fd4695c Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-08 18:26:10 +08:00
Rock Chin ffa4b1b4a1 fix(modelmgr): 使用异步请求时的异常类型丢失 2023-04-08 18:26:08 +08:00
Rock Chin f8eee3a2a6 Merge pull request #399 from RockChinQ/optional-config-override
[Feat] override.json可选应用
2023-04-08 16:26:56 +08:00
Rock Chin eeee7a8343 feat: 仅在提供命令行参数时应用override.json的内容 2023-04-08 16:21:40 +08:00
Rock Chin 8447b73fcb doc(README.md): 删除ChatAPI2D插件 2023-04-08 16:15:11 +08:00
Rock Chin 2863945d5f feat(config-template): 更改为常量表示超时时间 2023-04-08 15:36:35 +08:00
Rock Chin cb1f8ca6f7 doc(README.md): 添加wenyinos/ChatAPI2D插件 2023-04-08 00:33:47 +08:00
Rock Chin 1d9964bcb1 Release v2.3.7 2023-04-08 00:21:21 +08:00
GitHub Actions Bot 15cb8016d3 Update cmdpriv-template.json 2023-04-07 16:20:13 +00:00
Rock Chin 895cc0a2c5 ci: test 2023-04-08 00:19:37 +08:00
Rock Chin 20bf349e4e ci: cmdpriv模板脚本 2023-04-08 00:18:00 +08:00
Rock Chin e297763da1 fix: !cfg指令失效 2023-04-08 00:13:19 +08:00
Rock Chin e471970654 ci: test 2023-04-07 20:32:51 +08:00
Rock Chin 12faaaced8 ci: 仅在wiki文件更新时提交 2023-04-07 20:31:33 +08:00
Rock Chin 083cbc55cc Release v2.3.6 2023-04-07 17:15:17 +08:00
Rock Chin 8aa7a3273d Merge pull request #390 from RockChinQ/customizable-tips
[Feat] 支持自定义提示消息
2023-04-07 17:13:28 +08:00
Rock Chin 255e2c4385 doc: 添加自定义提示消息的说明 2023-04-07 17:12:33 +08:00
Rock Chin 9856306870 feat: 修改文件生成顺序 2023-04-07 17:10:44 +08:00
GitHub Actions 527ab8b8a7 Update override-all.json 2023-04-07 09:08:21 +00:00
Rock Chin f8e19ba9b3 feat: 删除config-template.py中多余的属性 2023-04-07 17:07:43 +08:00
GitHub Actions 7649dbfbbc Update override-all.json 2023-04-07 09:00:40 +00:00
Rock Chin 81e734644d feat: 删除config-template.py中的help_message模板 2023-04-07 17:00:16 +08:00
Rock Chin ae55cf5b1e feat: 适配help指令 2023-04-07 16:59:51 +08:00
Rock Chin af539546ef Merge pull request #356 from 2675hujilo/tips
[Feat] 支持自定义提示消息
2023-04-07 16:43:58 +08:00
Rock Chin 0031ce57d0 Merge branch 'customizable-tips' into tips 2023-04-07 16:40:26 +08:00
Rock Chin 2f48a2ce57 Merge branch 'customizable-tips' of https://github.com/RockChinQ/QChatGPT into customizable-tips 2023-04-07 16:39:48 +08:00
Rock Chin 6068ab7100 feat: 修改help_message为主线的内容 2023-04-07 16:39:25 +08:00
GitHub Actions 29a7dccef4 Update override-all.json 2023-04-07 08:34:23 +00:00
Rock Chin e2073da86e Merge branch '2675hujilo-tips' into customizable-tips 2023-04-07 16:32:32 +08:00
2675hujilo ae079526f7 删除tips-customs-template.py中不必要注释 2023-04-07 16:29:09 +08:00
26751 947bae8e26 删除tips-customs-template.py中不必要注释
Signed-off-by: 26751 <2675174581@qq.com>
2023-04-07 16:22:22 +08:00
Rock Chin a68e29dff6 feat: tips模块完整性检查 2023-04-07 16:02:22 +08:00
Rock Chin a588d7f960 feat: 热重载加上tips模块 2023-04-07 13:28:07 +08:00
Rock Chin 66224e5a32 fix: 热重载后未检查配置文件存在性 2023-04-07 13:25:57 +08:00
Rock Chin 07abad6a14 feat: 将tips的值统一为str类型 2023-04-07 13:23:58 +08:00
Rock Chin 83d02aaaac chore: 修改配置文件名称 2023-04-07 13:20:57 +08:00
Rock Chin 5a27ac165e Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-06 21:37:56 +08:00
Rock Chin bd9a523233 Release v2.3.5 2023-04-06 21:37:51 +08:00
Rock Chin 43959b158f Merge pull request #385 from RockChinQ/impl-337-bugfix-version-ignorance
[Feat] 更新逻辑优化
2023-04-06 21:36:53 +08:00
Rock Chin d81b457bba feat: 更新完成后不展示更新前版本的更新日志 (#340) 2023-04-06 21:34:30 +08:00
Rock Chin b40d639785 feat: 忽略第四位版本号 2023-04-06 21:31:56 +08:00
Rock Chin 0a8d8f4f66 Merge pull request #381 from RockChinQ/impl-339-redundance-comp-check
[Chore] 删除冗余的兼容性检查判断
2023-04-06 21:03:33 +08:00
Rock Chin d16cb25cde chore: 删除冗余的兼容性检查判断 2023-04-06 20:34:56 +08:00
Rock Chin 7aef1758e0 ci: test 2023-04-06 18:41:21 +08:00
Rock Chin 9758756fdd ci: 错误的路径 2023-04-06 18:39:21 +08:00
Rock Chin 13ef35f96f fix: 热重载后!draw无法使用的问题 2023-04-06 18:37:07 +08:00
Rock Chin 6b8c1209b7 chore: 整理根目录文件 2023-04-06 17:23:30 +08:00
Rock Chin 7184f3053a doc: README.md添加社区群说明 2023-04-06 15:55:48 +08:00
Rock Chin b83eac10e6 doc: 完善wiki 2023-04-06 15:20:08 +08:00
Rock Chin cb42eaef69 test: Home.md 2023-04-06 15:18:35 +08:00
Rock Chin 0dfd636a7e ci: 工作流 2023-04-06 15:18:02 +08:00
Rock Chin 21ff0fd258 test: 测试wiki同步工作流 2023-04-06 15:13:40 +08:00
Rock Chin c2eaeb2c72 chore: wiki同步工作流 2023-04-06 15:12:12 +08:00
Rock Chin 2a414a4bea chore: 提交wiki文件到res/wiki 2023-04-06 15:07:25 +08:00
Rock Chin fc0c38c8af chore: 删除子模块 2023-04-06 10:13:34 +08:00
Rock Chin 595e6c8a0c chore: 删除子模块 2023-04-06 10:13:08 +08:00
Rock Chin ced16fd221 chore: 移动docker部署教程 2023-04-06 10:10:09 +08:00
Rock Chin 0817c3f148 chore: 将工作流脚本移动到res/scripts 2023-04-06 10:08:15 +08:00
Rock Chin fb40af81ac doc: 完善文档 2023-04-06 09:44:07 +08:00
Rock Chin 1c5ad05e89 typo: plugin命令的提示错字 2023-04-06 09:29:45 +08:00
Rock Chin 86bef566c4 Release v2.3.4 2023-04-05 17:13:05 +08:00
Rock Chin 0983ccb61e doc: 添加模型切换器插件 2023-04-05 16:59:06 +08:00
Rock Chin a1d9f469c0 doc: 添加模型切换器插件 2023-04-05 16:58:15 +08:00
Rock Chin 952124f783 feat: 禁用的插件仍进行初始化 2023-04-05 16:50:35 +08:00
GitHub Actions 6be12e8ace Update override-all.json 2023-04-05 07:48:46 +00:00
Rock Chin 0799f380e1 feat: 更改默认help_message 2023-04-05 15:48:21 +08:00
Rock Chin f65270ee7e feat: 启动时输出mah相关配置项 2023-04-05 15:46:49 +08:00
Rock Chin 414910719c Release v2.3.3 2023-04-05 09:57:21 +08:00
Rock Chin 10a1e8faa6 fix: 回复内容不完整问题 (#208) 2023-04-05 09:56:27 +08:00
Rock Chin 4eea21927e doc: 补充手动部署中缺失的requests库 (#375) 2023-04-04 16:49:59 +08:00
Rock Chin 48c7f659f9 Release v2.3.2 2023-04-04 03:22:19 +00:00
Rock Chin b33333f4aa Merge pull request #372 from RockChinQ/363-bug-helpmessage-creditapi
[Fix] help_message问题、额度检测接口问题
2023-04-04 11:20:34 +08:00
Rock Chin 9edb32b081 feat: usage命令不再显示额度 2023-04-04 03:15:07 +00:00
Rock Chin c9b25fe806 doc: cmds指令的说明 2023-04-03 14:55:01 +00:00
GitHub Actions Bot b6ee3939be Update cmdpriv-template.json 2023-04-03 14:41:25 +00:00
Rock Chin e5485cddd0 feat: 更改使用!cmd指令查看指令列表 2023-04-03 14:40:27 +00:00
Rock Chin ac81597236 feat: 插件更新异常处理 2023-04-03 14:09:30 +00:00
Rock Chin 58d991df0a Merge pull request #368 from zyckk4/docstring-improvements
[Chore] 统一docstring格式
2023-04-03 22:02:11 +08:00
Rock Chin 3f8e380da4 Merge pull request #369 from zyckk4/fix-type-hint
[Fix] 修复一处类型注解的错误
2023-04-03 13:39:56 +08:00
zyckk4 ae831a2654 [Fix] 修复一处类型注解的错误 2023-04-03 10:13:20 +08:00
zyckk4 ae72cf2283 chore: 统一docstring格式 2023-04-03 00:19:28 +08:00
Rock Chin 8164f4b506 Release v2.3.1 2023-04-02 16:32:52 +08:00
Rock Chin 9617be0ca4 fix: 未指定utf-8保存已输出的公告 2023-04-02 16:30:42 +08:00
Rock Chin f079d7b9fa fix: Windows上无法读取和应用命令权限配置的问题 2023-04-02 16:24:30 +08:00
Rock Chin 00afda452f Merge pull request #365 from zyckk4/style-improvements
去除行尾空格
2023-04-02 16:04:52 +08:00
zyckk4 70386abadd 去除行尾空格 2023-04-02 14:43:34 +08:00
26751 5865ac017c 增加tips_custom.py提示
Signed-off-by: 26751 <2675174581@qq.com>
2023-04-02 13:46:15 +08:00
26751 4061a92f8e 删除override-all.json中无效的字段
Signed-off-by: 26751 <2675174581@qq.com>
2023-04-02 13:36:51 +08:00
2675hujilo d37c31b31c Update tips_custom_template.py 2023-04-01 18:43:03 +08:00
2675hujilo 973ef0078f Delete tips_custom.py 2023-04-01 18:36:33 +08:00
26751 48dcd257da Signed-off-by: 26751 <2675174581@qq.com> 2023-04-01 18:33:37 +08:00
26751 da03911610 Signed-off-by: 26751 <2675174581@qq.com> 2023-04-01 16:39:02 +08:00
Rock Chin aba9d945b5 doc: 收起功能概述 2023-04-01 09:59:33 +08:00
26751 b6f7f3b73f Signed-off-by: 26751 <2675174581@qq.com> 2023-04-01 02:35:27 +08:00
26751 2050d20ea7 Signed-off-by: 26751 <2675174581@qq.com> 2023-04-01 02:23:40 +08:00
26751 ac1fb4a63a 修改自定义提示语 2023-04-01 01:02:59 +08:00
Rock Chin ced38490e1 chore: 兼容性问题公告 2023-03-31 21:37:35 +08:00
Rock Chin ad28b69198 doc: 添加ChatPoeBot插件链接 (#352) 2023-03-31 21:31:40 +08:00
crosscc 8c67d3c58f Create build_docker_image.yml
利用github action 自动构建docker镜像:
## 1、
`workflow_dispatch:` 是需要作者在action手动点击进行构建

```
  release:
    types: [published]
```
这个是发布release的时候自动构建镜像。根据作者需求启用或者删除注释掉

## 2、
`tag: latest,${{ steps.get_version.outputs.VERSION }}`是可以镜像打标为latest和release发布的版本号

## 3、
docker hub userid 在setting创建secrets, name=DOCKER_USERNAME   value=dockerid
docker hub password,在setting创建secrets, name=DOCKER_PASSWORD   value=dockerpassword

这样作者就不用在自己机器上构建docker镜像,利用action 自动完成全平台镜像 速度也快。
2023-03-31 16:22:20 +08:00
Rock Chin 7171817de8 Release v2.3.0 2023-03-31 07:42:06 +00:00
GitHub Actions Bot 73f9d674e1 Update cmdpriv-template.json 2023-03-31 07:40:07 +00:00
Rock Chin 5e046399f8 test: 删除测试文件 2023-03-31 07:39:35 +00:00
GitHub Actions Bot 4966cd9ac7 Update cmdpriv-template.json 2023-03-31 07:35:48 +00:00
Rock Chin da936ecfe3 test: ci 2023-03-31 07:35:11 +00:00
Rock Chin 89e10d43de ci: 解决所有依赖 2023-03-31 07:34:45 +00:00
Rock Chin 3bf289af69 test: 测试 2023-03-31 07:29:23 +00:00
Rock Chin c7c9a6c5ca ci: 运行前完善配置文件 2023-03-31 07:28:33 +00:00
Rock Chin aee8446a23 test: 测试工作流 2023-03-31 07:25:53 +00:00
Rock Chin 2bb4f1fbb8 ci: 工作流 2023-03-31 07:25:27 +00:00
Rock Chin 6e7b0ee4ff test: 测试工作流 2023-03-31 07:24:17 +00:00
Rock Chin 204f5b9a54 ci: 工作流语法错误 2023-03-31 07:23:35 +00:00
Rock Chin 8c41e3506f test: 测试工作流 2023-03-31 07:22:33 +00:00
Rock Chin c2c33e45b8 ci: 更新工作流文件 2023-03-31 07:21:03 +00:00
Rock Chin 1acaf4e58b Merge pull request #336 from RockChinQ/cmds-permission-ctrl
[Refactor&Feat] 命令节点权限控制
2023-03-31 15:18:44 +08:00
Rock Chin eca80d5a4c ci: 添加cmdpriv-template.json的自动化生成脚本 2023-03-31 07:18:08 +00:00
Rock Chin f538957be9 doc: 更新wiki 2023-03-31 07:06:42 +00:00
Rock Chin 82a839a60a doc: 完善命令权限功能说明 2023-03-31 07:06:18 +00:00
Rock Chin df494da9e4 feat: 支持命令限权 2023-03-31 06:49:13 +00:00
Rock Chin 1ea53f7f04 Merge pull request #342 from q123458384/patch-1
Update docker_deploy.md
2023-03-30 22:30:34 +08:00
Rock Chin ac6d695f6d doc: 完善主程序容器启动指令的挂载项 2023-03-30 21:26:10 +08:00
Rock Chin 73dccb21f5 feat: 添加指令权限配置文件 2023-03-30 11:29:04 +00:00
Rock Chin 4221102ad5 chore: 删除过时的命令架构文件 2023-03-30 11:12:27 +00:00
Rock Chin b100f12e7f refactor: 完成所有指令 2023-03-30 11:11:39 +00:00
Rock Chin 2069ba6836 refactor: system类命令 2023-03-30 03:38:33 +00:00
crosscc ea57976808 Update docker_deploy.md
2.1中 `network host` 就是开放容器内的所有端口,和 `-p 端口:端口` 不共用
2.1中  `-v ./qq/xxx` 在群晖中不能用,改成了`${PWD}/qq/xxx`
3 中 容器名和上面的重复了,映射整个目录会无法运行,改成只映射 config.py

以上是我docker部署中遇到的问题及修改
2023-03-29 16:44:16 +08:00
Rock Chin 4055d3542b refactor: 完成会话管理相关指令 2023-03-28 13:47:45 +00:00
Rock Chin 0b0271a1f4 refactor: 更改使用装饰器注册命令 2023-03-28 12:53:46 +00:00
Rock Chin e03585ad4d feat: 扁平化储存命令 2023-03-28 12:18:19 +00:00
Rock Chin 11a385791e doc: 添加贡献相关说明 2023-03-28 12:52:37 +08:00
Rock Chin e228225178 refactor: 指令注册架构 2023-03-28 03:12:19 +00:00
Rock Chin 1c96d971e1 Update bug-report.yml 2023-03-27 21:22:56 +08:00
Rock Chin b799de7995 refactor: 迁移旧的处理模块 2023-03-27 13:09:40 +00:00
Rock Chin b01d246555 doc: 删除安装器使用警告 2023-03-27 18:52:40 +08:00
Rock Chin 9363b073cf Merge pull request #334 from maimierjiafude/patch-1
[Fix] 修改模块无法找到的问题
2023-03-27 18:51:05 +08:00
maimierjiafude 12ca04ac6f 修改模块无法找到的问题 2023-03-27 18:45:29 +08:00
Rock Chin 51737c28bd Delete 需求建议.md 2023-03-27 11:31:05 +08:00
Rock Chin 50d5ec224a Create feature-request.yml 2023-03-27 11:30:40 +08:00
Rock Chin 95a7397d14 Update bug-report.yml 2023-03-27 11:23:10 +08:00
Rock Chin aedac6d22c Create bug-report.yml 2023-03-27 11:21:45 +08:00
Rock Chin d522975ecc Delete 漏洞反馈.yml 2023-03-27 11:17:14 +08:00
Rock Chin 68fda8d7f3 Update 漏洞反馈.yml 2023-03-27 11:16:48 +08:00
Rock Chin b0cfec9913 Update 漏洞反馈.yml 2023-03-27 11:11:07 +08:00
Rock Chin ba8eba1581 Update 漏洞反馈.yml 2023-03-27 11:10:41 +08:00
Rock Chin f9eaed41c1 Update 漏洞反馈.yml 2023-03-27 11:07:16 +08:00
Rock Chin 1202a62df7 Update 漏洞反馈.yml 2023-03-27 11:06:11 +08:00
Rock Chin 8c1f7796f6 Update 漏洞反馈.yml 2023-03-27 11:02:18 +08:00
Rock Chin 42aee35789 Update 漏洞反馈.yml 2023-03-27 11:01:47 +08:00
Rock Chin b628849caa Update 漏洞反馈.yml 2023-03-27 11:00:21 +08:00
Rock Chin 031f08b0d4 Rename 漏洞反馈.md to 漏洞反馈.yml 2023-03-27 10:57:40 +08:00
Rock Chin fab6f9b93f Update 漏洞反馈.md 2023-03-27 10:57:00 +08:00
GitHub Actions 564c5d937d Update override-all.json 2023-03-26 15:45:06 +00:00
Rock Chin 2d3bb01487 debug: 测试完毕 2023-03-26 23:44:49 +08:00
GitHub Actions 607ea2d293 Update override-all.json 2023-03-26 15:43:54 +00:00
Rock Chin d817b53780 debug: 测试工作流 2023-03-26 23:43:34 +08:00
Rock Chin e8a2cbe06a Rename update override-all.json to update-override-all.yml 2023-03-26 23:42:42 +08:00
Rock Chin d2b0577752 Update update override-all.json 2023-03-26 23:41:15 +08:00
Rock Chin b4edd5cbad Update update override-all.json 2023-03-26 23:38:38 +08:00
Rock Chin 348477747e debug: 测试override-all.json工作流 2023-03-26 23:35:44 +08:00
Rock Chin bb7ee174ea Create update override-all.json 2023-03-26 23:34:50 +08:00
Rock Chin ab5add14ef chore: 完善override-all.json 2023-03-26 15:27:17 +00:00
Rock Chin 44f4820cee Merge pull request #332 from RockChinQ/reverse-proxy
[Feat] 支持反向代理
2023-03-26 22:51:06 +08:00
Rock Chin 8f1609b944 doc: 完善反代地址说明 2023-03-26 14:50:03 +00:00
Rock Chin 66b5b75631 feat: 支持反向代理 2023-03-26 13:50:43 +00:00
Rock Chin 17e293afe8 Merge pull request #325 from RockChinQ/fix-289-full-default-compatibility
[Feat] 完善情景预设相关内容
2023-03-26 21:40:36 +08:00
Rock Chin 1cf35f59fd Merge branch 'master' into fix-289-full-default-compatibility 2023-03-26 21:40:21 +08:00
Rock Chin bb4b897934 feat(dprompt.py): 解耦完成 2023-03-26 13:28:26 +00:00
Rock Chin 0eaf1af2e3 doc: 添加Python环境冲突警告 2023-03-26 15:25:21 +08:00
Rock Chin f70c12540b Merge pull request #327 from mikumifa/master
Dockerfile部署
2023-03-25 23:12:52 +08:00
Rock Chin 479fe73c24 doc: 在README.md链接docker教程 2023-03-25 23:12:26 +08:00
Rock Chin f6cad85476 feat: 使用normal作为情景预设默认模式的名称 2023-03-24 20:02:50 +08:00
mikumifa 888197e6ce Dockerfile部署 2023-03-24 19:58:27 +08:00
Rock Chin e634305759 doc: 完善full_scenario的说明 2023-03-24 11:30:53 +00:00
Rock Chin fe054211f4 chore: 代码格式优化 2023-03-23 23:44:10 +08:00
Rock Chin f102a29ea0 Merge pull request #323 from RockChinQ/multi-threads-control
[Feat] 基于线程池的多线程控制方案
2023-03-23 22:56:51 +08:00
Rock Chin 2b8bd45bcd Merge branch 'master' into multi-threads-control 2023-03-23 21:43:41 +08:00
Rock Chin 7f730c4be0 Merge pull request #252 from LINSTCL/multi-threads-control
添加线程控制类,修改main结构,修改启动流程
2023-03-23 21:35:22 +08:00
Rock Chin b6e31cac23 fix: 重载时重复调用load_config() 2023-03-23 21:29:51 +08:00
Rock Chin 9fe4f218d5 chore: config-template格式 2023-03-23 21:09:40 +08:00
LINSTCL cc38cc2676 修复bug 2023-03-23 16:43:41 +08:00
LINSTCL f56c6876d1 暂时解决reload后的config无法加载问题 2023-03-23 16:42:15 +08:00
LINSTCL 196e424c88 添加说明 2023-03-23 16:37:01 +08:00
Rock Chin 9270dc2c52 Release v2.2.5 2023-03-20 14:02:38 +00:00
Rock Chin 14aec251b4 Merge pull request #315 from RockChinQ/impl-312
[Feat] 访问GitHub API时使用openai_config中设置的代理地址
2023-03-20 21:49:33 +08:00
Rock Chin d2a7a57245 feat: 为GitHub API的访问使用代理 (#312) 2023-03-20 13:40:23 +00:00
Rock Chin 1964fc76c8 doc: 完善wiki指引 2023-03-20 13:25:02 +00:00
Rock Chin b8d4b490ce doc: 添加部署说明 2023-03-20 13:12:25 +00:00
Rock Chin 76891e4855 doc: 添加指令说明指引 2023-03-20 13:09:05 +00:00
Rock Chin 3d868b3a39 Merge pull request #308 from RockChinQ/plugin-ctrl-cmd
[Feat] 解耦指令处理、完善插件管理指令
2023-03-20 21:04:06 +08:00
Rock Chin 7b56bcf7a9 feat: 添加插件启用禁用指令 2023-03-20 13:02:30 +00:00
Rock Chin f96ae56bce feat: 支持指令删除插件 (#286) 2023-03-20 12:50:25 +00:00
Rock Chin d52108f4e1 doc: 完善README.md 2023-03-20 12:49:18 +00:00
Rock Chin 5f07b7ad1f refactor: 完成所有指令重构 2023-03-20 12:06:02 +00:00
Rock Chin cda10cf1a6 Update 漏洞反馈.md 2023-03-20 19:17:53 +08:00
Rock Chin d226b8ebc5 doc: 完善文档 (#310) 2023-03-20 14:46:39 +08:00
Rock Chin d08794579c feat: 现有指令占位 2023-03-19 14:33:01 +00:00
Rock Chin 7450494741 Update pull_request_template.md 2023-03-19 20:33:23 +08:00
Rock Chin 36dca7ae2f feat: 添加指令抽象类 2023-03-19 12:27:21 +00:00
Rock Chin 5dae777e79 doc: 添加wiki为submodule 2023-03-19 09:43:45 +00:00
Rock Chin e518d172d7 Merge pull request #304 from RockChinQ/bd-check-exception
[Perf] 百度云审核的异常处理
2023-03-19 17:13:37 +08:00
Rock Chin af29277acd feat: 长消息检查函数不再检查敏感词 2023-03-19 09:06:32 +00:00
Rock Chin 79bfa0792d feat: 删除print调试信息 2023-03-19 08:45:54 +00:00
Rock Chin cf23c5d31c Release v2.2.4 2023-03-19 08:38:07 +00:00
Rock Chin 84418a296b doc: 完善pr模板 2023-03-19 08:37:23 +00:00
Rock Chin 5f83cc6bb7 Merge pull request #300 from RockChinQ/token-process
[Perf] Tokens相关处理逻辑优化
2023-03-19 16:35:25 +08:00
Rock Chin cde168c93c doc: full_scenario的编写教程 (#301) 2023-03-19 08:32:34 +00:00
Rock Chin fed24c0748 doc: 添加chordfish-k/QChartGPT_Emoticon_Plugin 2023-03-19 13:35:20 +08:00
Rock Chin b45d11b3c3 Update pull_request_template.md 2023-03-19 11:28:38 +08:00
Rock Chin 84d9af69bb Update pull_request_template.md 2023-03-19 11:28:17 +08:00
Rock Chin 684d356646 Update pull_request_template.md 2023-03-19 11:17:07 +08:00
Rock Chin 975300c9fc Create pull_request_template.md 2023-03-19 11:15:45 +08:00
Rock Chin ca349e33fc feat: 实现新的前文剪切模式 2023-03-18 15:57:28 +00:00
Rock Chin ccf62fe95c doc: 致谢GPT-4内测提供者 2023-03-18 22:28:06 +08:00
Rock Chin d056cb6769 feat: 数据库接口支持 2023-03-18 12:57:36 +00:00
Rock Chin b0016eebf9 feat: 添加override-all.json 2023-03-18 20:44:14 +08:00
Rock Chin 0490ad9207 test: token计数测试 2023-03-18 11:26:18 +00:00
Rock Chin 4a20ae236b doc: README.md格式错误 2023-03-18 09:15:26 +00:00
Rock Chin 9be1c7fc6f doc: 添加WaitYiYan插件链接 2023-03-18 08:17:51 +00:00
Rock Chin 5621d32b30 doc: GPT-4说明 2023-03-18 04:42:46 +00:00
Rock Chin b7642fe876 feat: 支持GPT-4 API 2023-03-18 04:38:48 +00:00
Rock Chin c842485d33 perf: 尝试安装依赖时的逻辑 2023-03-17 07:49:27 +00:00
Rock Chin 341444ef1c chore: 添加devcontainer配置 2023-03-17 07:39:16 +00:00
Rock Chin 66f5a219d2 feat: 不再提示InvalidRequestError的可能原因 2023-03-16 21:10:10 +08:00
Rock Chin cf678aa345 feat: 修改日志初始化顺序 2023-03-16 20:55:57 +08:00
Rock Chin d1549b3df0 chore: 代码格式优化 2023-03-16 20:22:18 +08:00
Rock Chin 002919fffe doc: 优化README.md格式 2023-03-16 19:38:35 +08:00
Rock Chin 087d097204 feat: 不再默认提供max_tokens 2023-03-16 13:37:48 +08:00
Rock Chin ca4eeda6f0 doc: 添加oliverkirk-sudo的文字转语音插件 2023-03-16 09:08:00 +08:00
Rock Chin 94543a4708 Merge pull request #282 from systemtang/bugfix
[Feat] 修复usage命令的代理问题
2023-03-16 08:53:25 +08:00
Rock Chin d4738dfb46 Release v2.2.3 2023-03-15 22:50:40 +08:00
Rock Chin 3bdf6810aa fix: 消息处理时的错误 2023-03-15 22:47:20 +08:00
systemt f489c2f3b4 修复usage命令的代理问题 2023-03-15 21:04:55 +08:00
Rock Chin a724bfe155 Release v2.2.2 2023-03-15 20:39:10 +08:00
Rock Chin 179a372bfe feat: 更改到process.py处理长消息 2023-03-15 20:33:44 +08:00
Rock Chin 651d765ab0 doc: 添加New Bing说明 2023-03-15 17:33:31 +08:00
Rock Chin 7ddc853f63 chore: 忽略保存的公告 2023-03-15 15:50:14 +08:00
Rock Chin 1bd1bfc725 chore: 删除测试公告 2023-03-15 15:47:24 +08:00
Rock Chin f6ec0fda7a Merge pull request #280 from RockChinQ/announcement
[Feat] 添加公告输出功能
2023-03-15 15:46:58 +08:00
Rock Chin 7be368ae8c feat: 添加公告功能 2023-03-15 15:43:36 +08:00
Rock Chin f67db2617b debug: 测试公告内容1 2023-03-15 15:37:07 +08:00
Rock Chin ed5bf8100f chore: 添加公告内容 2023-03-15 15:22:19 +08:00
Rock Chin 0ef8a1c9ae chore: 为new bing忽略cookies.json 2023-03-15 11:24:45 +08:00
Rock Chin 32460cbf78 doc: 添加GPT-4公告 2023-03-15 11:04:10 +08:00
Rock Chin 6f6c9c222c doc: 添加网页版GPT-4说明 2023-03-15 10:57:29 +08:00
Rock Chin 438d0ed1ea Merge pull request #277 from zyckk4/dev
chore: 去除多余import
2023-03-14 13:11:47 +08:00
zyckk4 3ef1c71cad chore: 去除多余import 2023-03-14 13:03:50 +08:00
Rock Chin aaadf6b8ba doc: 部署方式依赖项指令 2023-03-14 10:57:02 +08:00
Rock Chin 6af614f319 doc: 整理致谢列表 2023-03-14 10:54:46 +08:00
Rock Chin c75dbd67df doc: 整理致谢列表 2023-03-14 10:53:32 +08:00
Rock Chin dc3d186e2a Merge pull request #274 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.2
chore(deps): update openai requirement from ~=0.27.0 to ~=0.27.2
2023-03-13 17:48:10 +08:00
dependabot[bot] 44550feddd chore(deps): update openai requirement from ~=0.27.0 to ~=0.27.2
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.0...v0.27.2)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 09:05:46 +00:00
Rock Chin a0810d5f63 Merge pull request #271 from RockChinQ/config-covering
feat: 支持json格式的配置文件 (#265)
2023-03-13 11:05:11 +08:00
Rock Chin cfc97fb22d feat: 支持json格式的配置文件 (#265) 2023-03-13 10:58:15 +08:00
Rock Chin d67dbe8062 doc: 添加JSON格式情景预设的说明 2023-03-13 10:31:21 +08:00
Rock Chin e89035e11c Release v2.2.1 2023-03-12 22:43:39 +08:00
Rock Chin 2ea711e629 fix: 更新包中包含新文件时更新失败 2023-03-12 22:43:02 +08:00
Rock Chin a716f071be Release v2.2.0 2023-03-12 20:48:15 +08:00
Rock Chin 3450a91824 Merge pull request #262 from chordfish-k/json_scenario
[Feat] 情景预设(人格)完善
2023-03-12 20:40:20 +08:00
Rock Chin d2c2b457e5 fix: !list指令显示的是机器人第一次回复 (#264) 2023-03-12 20:31:28 +08:00
Rock Chin 9cd7e49804 feat: 分离储存会话情景预设和对话内容 2023-03-11 23:44:22 +08:00
Rock Chin e9155e836f feat: 允许通过前缀指定使用的JSON情景 2023-03-10 23:49:41 +08:00
Rock Chin ed248539c7 doc: 标记二群已满 2023-03-10 23:33:54 +08:00
Rock Chin 54cc75506f feat: 使用模板储存默认的json格式的情景预设 2023-03-10 23:26:36 +08:00
Rock Chin 4269c7927e chore: typo 2023-03-10 23:14:32 +08:00
Rock Chin 064ac7f603 feat: 添加窗口处于暂停模式的提示 2023-03-10 22:50:07 +08:00
Rock Chin 48ccf15273 Merge pull request #263 from RockChinQ/history-deletion
[Feat] 支持删除指定当前会话的指定或全部历史记录 (#239)
2023-03-10 22:40:19 +08:00
Rock Chin b920ced6d4 feat: !delhst 指令支持管理员删除会话历史记录 2023-03-10 21:20:19 +08:00
Rock Chin 69610a674c perf: 更改help中指令信息帮助 2023-03-10 21:11:55 +08:00
Rock Chin 1828e34190 feat: 支持删除指定当前会话的指定或全部历史记录 (#239) 2023-03-10 21:04:20 +08:00
chordfish d53f4e3917 adjust:修改,去除neko.json,以及一些占位的变量等 2023-03-10 19:37:29 +08:00
chordfish 01706d5b4e Delete mesugaki.json 2023-03-10 16:17:47 +08:00
chordfish 8916b8a450 Update manager.py 2023-03-10 16:17:07 +08:00
chordfish ed33af5638 Update README.md 2023-03-10 14:10:54 +08:00
chordfish c94a9e1ae6 bug:修复上次更新后不响应的问题 2023-03-10 13:55:56 +08:00
chordfish e2e93afd06 bug:修复上次更新后不响应的问题 2023-03-10 13:03:25 +08:00
chordfish a810158d5b bug:修复上次更新后不响应的问题 2023-03-10 12:43:07 +08:00
chordfish 5a5ebb95fc bug:修复上次更新后不响应的问题 2023-03-10 12:35:58 +08:00
chordfish 61dd9e29c0 Merge branch 'master' into json_scenario 2023-03-10 10:20:18 +08:00
chordfish ac65d81ba1 adjust:整理代码,仅添加json方式的prompt读取 2023-03-10 10:13:40 +08:00
LINSTCL 3aca987176 暴力修复程序无法退出的bug 2023-03-10 09:35:59 +08:00
chordfish 7288d3cb15 删除一部分注释和调试信息 2023-03-09 21:20:59 +08:00
chordfish 7477c7c67f 删除一部分注释和调试内容 2023-03-09 21:16:15 +08:00
chordfish 453952859e Merge branch 'full_scenario' 2023-03-09 21:08:47 +08:00
chordfish 85d46089e3 已按要求修改 2023-03-09 19:53:31 +08:00
chordfish 3b55f706de 修正防人格否定的一个Bug 2023-03-09 19:22:37 +08:00
chordfish f448276423 Merge commit '830ee704da0903a8922dc757381cdf6fd68870a3' 2023-03-09 18:34:03 +08:00
chordfish 830ee704da Bug修复 2023-03-09 18:32:39 +08:00
chordfish 393369e446 Merge branch 'master' of https://github.com/chordfish-k/QChatGPT 2023-03-09 18:29:37 +08:00
chordfish 2cc6a09905 Bug修复 2023-03-09 18:29:31 +08:00
chordfish d7d9d88e16 适配线程版本 2023-03-09 17:56:57 +08:00
chordfish 357d6aaf75 更新配置文件 2023-03-09 15:52:18 +08:00
chordfish 8059c422e3 Update README.md 2023-03-09 14:48:21 +08:00
chordfish b336e1334d Update README.md 2023-03-09 14:47:39 +08:00
chordfish 12a0942ddb 初步追加通过json导入messages数组的方式进行情景预设 2023-03-09 14:44:33 +08:00
Rock Chin 7e5a77f77e doc: 添加致谢https://github.com/qq255204159 2023-03-08 16:16:41 +08:00
LINSTCL e0caeb5dd2 Fix bugs 2023-03-08 16:08:09 +08:00
LINSTCL 77076f3bdd 添加线程控制类,修改main结构,修改启动流程 2023-03-08 15:21:37 +08:00
Rock Chin 2933d4843f Release v2.1.4 2023-03-07 08:50:43 +08:00
Rock Chin c5de978098 Merge pull request #236 from RockChinQ/fix-234
[Fix] !reload 重新加载以后首次对话报错
2023-03-07 08:47:53 +08:00
Rock Chin 8b9cfab072 doc(main.py): 优化注释 2023-03-07 08:46:20 +08:00
Rock Chin ea5f3c222f fix: 修改主线程main流程以初步修复 2023-03-06 20:53:40 +08:00
Rock Chin 36bcbca15b Merge pull request #233 from RockChinQ/respond-rule
[Feat] 支持设置不响应群内at消息及随机响应
2023-03-06 17:55:19 +08:00
Rock Chin 2b2060e71b feat: 支持设置不响应群内at消息;支持设置随机响应概率 2023-03-06 17:50:34 +08:00
Rock Chin 451688f2df Merge pull request #232 from RockChinQ/sensitive-mask
[Feat] 支持更换敏感词的掩盖字符
2023-03-06 15:27:15 +08:00
Rock Chin d993852de7 feat: 支持将敏感词替换成整个字符串 2023-03-06 15:26:06 +08:00
Rock Chin 9d73770a4e feat: 支持更换敏感词的掩盖字符 2023-03-06 15:07:10 +08:00
Rock Chin 2541acf9d2 fix: 赞赏码base64值错误 2023-03-06 14:16:25 +08:00
Rock Chin a1bfbad24e Release v2.1.3 2023-03-06 12:41:35 +08:00
Rock Chin 8af4918048 Merge pull request #230 from LINSTCL/config_integrity_check
添加配置文件完整性校验
2023-03-06 12:35:59 +08:00
Rock Chin 49f4ab0ec8 perf: 完整性检查忽略__开头的属性 2023-03-06 12:34:08 +08:00
LINSTCL 85c623fb0f 修改提示逻辑 2023-03-06 11:27:16 +08:00
Rock Chin 9e28298250 perf: 完善未启动情况下的自动更新 2023-03-06 11:18:31 +08:00
Rock Chin 7a04ef0985 feat: 未启动状态下的自动更新 (#223) 2023-03-06 11:04:25 +08:00
LINSTCL 83005e9ba9 添加配置文件完整性校验 2023-03-06 09:40:33 +08:00
Rock Chin f0c78f0529 Merge pull request #222 from LINSTCL/threadpool-optimization
使用线程池控制线程数量,防止高并发崩溃
2023-03-06 08:51:47 +08:00
Rock Chin 3f638adcf9 perf(qqbot/manager.py): 优化控制台日志显示 2023-03-06 08:50:28 +08:00
Rock Chin d9405d8d5d fix: main.py的字段版本兼容性问题 2023-03-06 08:48:50 +08:00
Rock Chin 606713a418 Merge pull request #228 from yichuxue/patch-1
启动时,更新openai和pillow库超时问题
2023-03-06 08:44:29 +08:00
Rock Chin 52102f0d0a feat(deps): trusted-host参数 2023-03-06 08:43:51 +08:00
Rock Chin 61c29829ed Release v2.1.2 2023-03-06 08:35:04 +08:00
依初雪 df30931aad 启动openai和pillow库超时问题
主要改动如下:
1、在ensure_dependencies函数更更新包时,出现超时的情况,指定更新源 https://pypi.douban.com/simple/
2023-03-06 00:32:46 +08:00
Rock Chin 5afcc03e8b fix: 错误的!version指令处理逻辑 2023-03-05 20:07:08 +08:00
Rock Chin fbeb4673f4 Merge pull request #226 from RockChinQ/text2img-perf
[Feat] 不再自带字体文件
2023-03-05 19:59:16 +08:00
Rock Chin 4aba319560 fix: 错误的加载过程 2023-03-05 19:57:39 +08:00
Rock Chin 74f79e002c perf: 优化字体加载过程 2023-03-05 19:54:51 +08:00
Rock Chin 2668ef2b3f feat: 不再自带字体文件 2023-03-05 19:36:09 +08:00
Rock Chin 74c018e271 Merge pull request #225 from RockChinQ/fix-switch-exce
[Fix] 修复插件开关问题
2023-03-05 17:36:03 +08:00
Rock Chin 64776fd601 doc: OpenAI注册教程链接 2023-03-05 16:47:42 +08:00
LINSTCL 59877bf71d 添加日志输出 2023-03-05 16:47:07 +08:00
LINSTCL d2800ac58b 使用线程池控制线程数量,防止高并发崩溃 2023-03-05 16:41:12 +08:00
Rock Chin ffef944119 fix: 热重载后插件开关状态被重置 (#177) 2023-03-05 16:04:45 +08:00
Rock Chin 651b291ef6 doc: 添加部分注释 2023-03-05 15:39:13 +08:00
Rock Chin e4b581f197 doc: 致谢添加贡献者 2023-03-05 14:37:14 +08:00
Rock Chin 4f3939e2d9 Merge pull request #219 from LINSTCL/modelmgr_optimization
优化模型接口底层的异常处理
2023-03-05 14:18:24 +08:00
LINSTCL 1048ca612d 补充错误情况 2023-03-05 14:06:07 +08:00
LINSTCL b1a2d21ee9 优化异常处理 2023-03-05 13:52:43 +08:00
Rock Chin dd4e8bdc8b perf: 优化版本识别逻辑 2023-03-05 12:26:51 +08:00
Rock Chin e28c9bae0c feat: 修改上报功能识别版本的逻辑 2023-03-05 12:21:28 +08:00
225 changed files with 9984 additions and 4905 deletions
+64
View File
@@ -0,0 +1,64 @@
name: 漏洞反馈
description: 报错或漏洞请使用这个模板创建,不使用此模板创建的异常、漏洞相关issue将被直接关闭
title: "[Bug]: "
labels: ["bug?"]
body:
- type: dropdown
attributes:
label: 部署方式
description: "主程序使用的部署方式"
options:
- 手动部署
- 安装器部署
- 一键安装包部署
- Docker部署
validations:
required: true
- type: dropdown
attributes:
label: 登录框架
description: "连接QQ使用的框架"
options:
- Mirai
- go-cqhttp
validations:
required: false
- type: input
attributes:
label: 系统环境
description: 操作系统、系统架构、**主机地理位置**,地理位置最好写清楚,涉及网络问题排查。
placeholder: 例如: CentOS x64 中国大陆、Windows11 美国
validations:
required: true
- type: input
attributes:
label: Python环境
description: 运行程序的Python版本
placeholder: 例如: Python 3.10
validations:
required: true
- type: input
attributes:
label: QChatGPT版本
description: QChatGPT版本号
placeholder: 例如: v2.6.0,可以使用`!version`命令查看
validations:
required: true
- type: textarea
attributes:
label: 异常情况
description: 完整描述异常情况,什么时候发生的、发生了什么,尽可能详细
validations:
required: true
- type: textarea
attributes:
label: 日志信息
description: 请提供完整的 **登录框架 和 QChatGPT控制台**的相关日志信息(若有),不提供日志信息**无法**为您排查问题,请尽可能详细
validations:
required: false
- type: textarea
attributes:
label: 启用的插件
description: 有些情况可能和插件功能有关,建议提供插件启用情况。可以使用`!plugin`命令查看已启用的插件
validations:
required: false
@@ -0,0 +1,21 @@
name: 需求建议
title: "[Feature]: "
labels: ["改进"]
description: "新功能或现有功能优化请使用这个模板;不符合类别的issue将被直接关闭"
body:
- type: dropdown
attributes:
label: 这是一个?
description: 新功能建议还是现有功能优化
options:
- 新功能
- 现有功能优化
validations:
required: true
- type: textarea
attributes:
label: 详细描述
description: 详细描述,越详细越好
validations:
required: true
+24
View File
@@ -0,0 +1,24 @@
name: 提交新插件
title: "[Plugin]: 请求登记新插件"
labels: ["独立插件"]
description: "本模板供且仅供提交新插件使用"
body:
- type: input
attributes:
label: 插件名称
description: 填写插件的名称
validations:
required: true
- type: textarea
attributes:
label: 插件代码库地址
description: 仅支持 Github
validations:
required: true
- type: textarea
attributes:
label: 插件简介
description: 插件的简介
validations:
required: true
-24
View File
@@ -1,24 +0,0 @@
---
name: 漏洞反馈
about: 报错或漏洞请使用这个模板创建
title: "[BUG]"
labels: 'bug'
assignees: ''
---
请认真按照实际情况填写以下信息!!!!
**运行环境**
- 部署方式:
手动部署/自动部署/Docker部署
- 系统环境:
例如: Centos x64
- Python环境(仅手动部署填写):
例如: Python 3.10.9
**描述漏洞**
什么时候发生的,mirai还是主程序,越详细越好
**完整报错信息**
完整的报错信息
-10
View File
@@ -1,10 +0,0 @@
---
name: 需求建议
about: 软件优化建议请使用这个模板创建
title: "[ENHANCE]"
labels: 'enhancement'
assignees: ''
---
不是需求建议请勿填写此模板!!!!
+1 -2
View File
@@ -10,6 +10,5 @@ updates:
schedule: schedule:
interval: "weekly" interval: "weekly"
allow: allow:
- dependency-name: "yiri-mirai" - dependency-name: "yiri-mirai-rc"
- dependency-name: "dulwich"
- dependency-name: "openai" - dependency-name: "openai"
+25
View File
@@ -0,0 +1,25 @@
## 概述
实现/解决/优化的内容:
### 事务
- [ ] 已阅读仓库[贡献指引](https://github.com/RockChinQ/QChatGPT/blob/master/CONTRIBUTING.md)
- [ ] 已与维护者在issues或其他平台沟通此PR大致内容
## 以下内容可在起草PR后、合并PR前逐步完成
### 功能
- [ ] 已编写完善的配置文件字段说明(若有新增)
- [ ] 已编写面向用户的新功能说明(若有必要)
- [ ] 已测试新功能或更改
### 兼容性
- [ ] 已处理版本兼容性
- [ ] 已处理插件兼容问题
### 风险
可能导致或已知的问题:
+48
View File
@@ -0,0 +1,48 @@
name: Build Docker Image
on:
#防止fork乱用action设置只能手动触发构建
workflow_dispatch:
## 发布release的时候会自动构建
release:
types: [published]
jobs:
publish-docker-image:
runs-on: ubuntu-latest
name: Build image
steps:
- name: Checkout
uses: actions/checkout@v2
- name: judge has env GITHUB_REF # 如果没有GITHUB_REF环境变量,则把github.ref变量赋值给GITHUB_REF
run: |
if [ -z "$GITHUB_REF" ]; then
export GITHUB_REF=${{ github.ref }}
echo $GITHUB_REF
fi
# - name: Check GITHUB_REF env
# run: echo $GITHUB_REF
# - name: Get version # 在 GitHub Actions 运行环境
# id: get_version
# if: (startsWith(env.GITHUB_REF, 'refs/tags/')||startsWith(github.ref, 'refs/tags/')) && startsWith(github.repository, 'RockChinQ/QChatGPT')
# run: export GITHUB_REF=${GITHUB_REF/refs\/tags\//}
- name: Check version
id: check_version
run: |
echo $GITHUB_REF
# 如果是tag,则去掉refs/tags/前缀
if [[ $GITHUB_REF == refs/tags/* ]]; then
echo "It's a tag"
echo $GITHUB_REF
echo $GITHUB_REF | awk -F '/' '{print $3}'
echo ::set-output name=version::$(echo $GITHUB_REF | awk -F '/' '{print $3}')
else
echo "It's not a tag"
echo $GITHUB_REF
echo ::set-output name=version::${GITHUB_REF}
fi
- name: Login to Registry
run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}
- name: Create Buildx
run: docker buildx create --name mybuilder --use
- name: Build # image name: rockchin/qchatgpt:<VERSION>
run: docker buildx build --platform linux/arm64,linux/amd64 -t rockchin/qchatgpt:${{ steps.check_version.outputs.version }} -t rockchin/qchatgpt:latest . --push
+43
View File
@@ -0,0 +1,43 @@
name: Update Wiki
on:
push:
branches:
- master
paths:
- 'res/wiki/**'
jobs:
update-wiki:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Clone Wiki Repository
uses: actions/checkout@v2
with:
repository: RockChinQ/QChatGPT.wiki
path: wiki
- name: Delete old wiki content
run: |
rm -rf wiki/*
- name: Copy res/wiki content to wiki
run: |
cp -r res/wiki/* wiki/
- name: Check for changes
run: |
cd wiki
if git diff --quiet; then
echo "No changes to commit."
exit 0
fi
- name: Commit and Push Changes
run: |
cd wiki
git add .
git commit -m "Update wiki"
git push
+80
View File
@@ -0,0 +1,80 @@
name: Test Pull Request
on:
pull_request:
types: [ready_for_review]
paths:
# 任何py文件改动都会触发
- '**.py'
pull_request_review:
types: [submitted]
issue_comment:
types: [created]
# 允许手动触发
workflow_dispatch:
jobs:
perform-test:
runs-on: ubuntu-latest
# 如果事件为pull_request_review且review状态为approved,则执行
if: >
github.event_name == 'pull_request' ||
(github.event_name == 'pull_request_review' && github.event.review.state == 'APPROVED') ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request != '' && contains(github.event.comment.body, '/test') && github.event.comment.user.login == 'RockChinQ')
steps:
# 签出测试工程仓库代码
- name: Checkout
uses: actions/checkout@v2
with:
# 仓库地址
repository: RockChinQ/qcg-tester
# 仓库路径
path: qcg-tester
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install dependencies
run: |
cd qcg-tester
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Get PR details
id: get-pr
if: github.event_name == 'issue_comment'
uses: octokit/request-action@v2.x
with:
route: GET /repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set PR source branch as env variable
if: github.event_name == 'issue_comment'
run: |
PR_SOURCE_BRANCH=$(echo '${{ steps.get-pr.outputs.data }}' | jq -r '.head.ref')
echo "BRANCH=$PR_SOURCE_BRANCH" >> $GITHUB_ENV
- name: Set PR Branch as bash env
if: github.event_name != 'issue_comment'
run: |
echo "BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV
- name: Set OpenAI API Key from Secrets
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" >> $GITHUB_ENV
- name: Set OpenAI Reverse Proxy URL from Secrets
run: |
echo "OPENAI_REVERSE_PROXY=${{ secrets.OPENAI_REVERSE_PROXY }}" >> $GITHUB_ENV
- name: Run test
run: |
cd qcg-tester
python main.py
- name: Upload coverage reports to Codecov
run: |
cd qcg-tester/resource/QChatGPT
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
./codecov -t ${{ secrets.CODECOV_TOKEN }}
+24 -1
View File
@@ -1,4 +1,4 @@
config.py /config.py
.idea/ .idea/
__pycache__/ __pycache__/
database.db database.db
@@ -12,3 +12,26 @@ logs/
sensitive.json sensitive.json
temp/ temp/
current_tag current_tag
scenario/
!scenario/default-template.json
override.json
cookies.json
res/announcement_saved
res/announcement_saved.json
cmdpriv.json
tips.py
.venv
bin/
.vscode
test_*
venv/
hugchat.json
qcapi
claude.json
bard.json
/*yaml
!/docker-compose.yaml
res/instance_id.json
.DS_Store
/data
botpy.log
+7
View File
@@ -17,3 +17,10 @@
- 解决本项目或衍生项目的issues中亟待解决的问题 - 解决本项目或衍生项目的issues中亟待解决的问题
- 阅读并完善本项目文档 - 阅读并完善本项目文档
- 在各个社交媒体撰写本项目教程等 - 在各个社交媒体撰写本项目教程等
### 代码规范
- 代码中的注解`务必`符合Google风格的规范
- 模块顶部的引入代码请遵循`系统模块``第三方库模块``自定义模块`的顺序进行引入
- `不要`直接引入模块的特定属性,而是引入这个模块,再通过`xxx.yyy`的形式使用属性
- 任何作用域的字段`必须`先声明后使用,并在声明处注明类型提示
+10
View File
@@ -0,0 +1,10 @@
FROM python:3.10.13-slim
WORKDIR /app
COPY . .
RUN apt update \
&& apt install gcc -y \
&& python -m pip install -r requirements.txt
CMD [ "python", "main.py" ]
+48 -234
View File
@@ -1,235 +1,49 @@
# QChatGPT🤖
> 2023/3/3 官方接口疑似被墙,可考虑使用网络代理 [#198](https://github.com/RockChinQ/QChatGPT/issues/198)
> 2023/3/3 现已在主线支持官方ChatGPT接口,使用方法查看[#195](https://github.com/RockChinQ/QChatGPT/issues/195)
> 2023/3/2 OpenAI已发布ChatGPT官方接口,我们正在全力接入,预计明日前完成,请查看[此PR](https://github.com/RockChinQ/QChatGPT/pull/194)
> 2023/2/16 现已支持接入ChatGPT网页版,详情请完成部署并查看底部**插件**小节或[此仓库](https://github.com/RockChinQ/revLibs)
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息 <p align="center">
- 由bilibili TheLazy制作的[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP) <img src="https://qchatgpt.rockchin.top/logo.png" alt="QChatGPT" width="180" />
- 交流、答疑群: ~~204785790~~(已满)、691226829、656285629 </p>
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
- QQ频道机器人见[QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT) <div align="center">
通过调用OpenAI的ChatGPT等语言模型来实现一个更加智能的QQ机器人 # QChatGPT
## 🍺模型适配一览 [![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/QChatGPT)](https://github.com/RockChinQ/QChatGPT/releases/latest)
<a href="https://hub.docker.com/repository/docker/rockchin/qchatgpt">
### 文字对话 <img src="https://img.shields.io/docker/pulls/rockchin/qchatgpt?color=blue" alt="docker pull">
</a>
- OpenAI GPT-3.5模型(ChatGPT API), 本项目原生支持, 默认使用 ![Wakapi Count](https://wakapi.dev/api/badge/RockChinQ/interval:any/project:QChatGPT)
- OpenAI GPT-3模型, 本项目原生支持, 部署完成后前往config.py切换 <a href="https://codecov.io/gh/RockChinQ/QChatGPT" >
- ChatGPT网页版逆向API, 由[插件](https://github.com/RockChinQ/revLibs)接入 <img src="https://codecov.io/gh/RockChinQ/QChatGPT/graph/badge.svg?token=pjxYIL2kbC"/>
</a>
### 故事续写 <br/>
<img src="https://img.shields.io/badge/python-3.9 | 3.10 | 3.11-blue.svg" alt="python">
- NovelAI API, 由[插件](https://github.com/dominoar/QCPNovelAi)接入 <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=66-aWvn8cbP4c1ut_1YYkvvGVeEtyTH8&authKey=pTaKBK5C%2B8dFzQ4XlENf6MHTCLaHnlKcCRx7c14EeVVlpX2nRSaS8lJm8YeM4mCU&noverify=0&group_code=195992197">
<img alt="Static Badge" src="https://img.shields.io/badge/%E5%AE%98%E6%96%B9%E7%BE%A4-195992197-purple">
### 图片绘制 </a>
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=nC80H57wmKPwRDLFeQrDDjVl81XuC21P&authKey=2wTUTfoQ5v%2BD4C5zfpuR%2BSPMDqdXgDXA%2FS2wHI1NxTfWIG%2B%2FqK08dgyjMMOzhXa9&noverify=0&group_code=248432104">
- OpenAI DALL·E模型, 本项目原生支持, 使用方法查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%8A%9F%E8%83%BD%E7%82%B9%E5%88%97%E4%B8%BE) <img alt="Static Badge" src="https://img.shields.io/badge/%E7%A4%BE%E5%8C%BA%E7%BE%A4-248432104-purple">
- NovelAI API, 由[插件](https://github.com/dominoar/QCPNovelAi)接入 </a>
<a href="https://www.bilibili.com/video/BV14h4y1w7TC">
### 语音生成 <img alt="Static Badge" src="https://img.shields.io/badge/%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B-208647">
</a>
- TTS+VITS, 由[插件](https://github.com/dominoar/QChatPlugins)接入 <a href="https://www.bilibili.com/video/BV11h4y1y74H">
<img alt="Static Badge" src="https://img.shields.io/badge/Linux%E9%83%A8%E7%BD%B2%E8%A7%86%E9%A2%91-208647">
## ✅功能 </a>
<details> ## 使用文档
<summary>✅支持敏感词过滤,避免账号风险</summary>
<a href="https://qchatgpt.rockchin.top">项目主页</a>
- 难以监测机器人与用户对话时的内容,故引入此功能以减少机器人风险 <a href="https://qchatgpt.rockchin.top/posts/feature.html">功能介绍</a>
- 加入了百度云内容审核,在`config.py`中修改`baidu_check`的值,并填写`baidu_api_key``baidu_secret_key`以开启此功能 <a href="https://qchatgpt.rockchin.top/posts/deploy/">部署文档</a>
- 编辑`sensitive.json`,并在`config.py`中修改`sensitive_word_filter`的值以开启此功能 <a href="https://qchatgpt.rockchin.top/posts/error/">常见问题</a>
</details> <a href="https://qchatgpt.rockchin.top/posts/plugin/intro.html">插件介绍</a>
<a href="https://github.com/RockChinQ/QChatGPT/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">提交插件</a>
<details>
<summary>✅群内多种响应规则,不必at</summary> ## 相关链接
- 默认回复`ai`作为前缀或`@`机器人的消息 <a href="https://github.com/RockChinQ/qcg-installer">安装器源码</a>
- 详细见`config.py`中的`response_rules`字段 <a href="https://github.com/RockChinQ/qcg-tester">测试工程源码</a>
</details> <a href="https://github.com/the-lazy-me/QChatGPT-Wiki">官方文档储存库</a>
<details> <img alt="回复效果(带有联网插件)" src="https://qchatgpt.rockchin.top/assets/image/QChatGPT-1211.png" width="500px"/>
<summary>✅完善的多api-key管理,超额自动切换</summary> </div>
- 支持配置多个`api-key`,内部统计使用量并在超额时自动切换
- 请在`config.py`中修改`openai_config`的值以设置`api-key`
- 可以在`config.py`中修改`api_key_fee_threshold`来自定义切换阈值
- 运行期间向机器人说`!usage`以查看当前使用情况
</details>
<details>
<summary>✅支持预设指令文字</summary>
- 支持以自然语言预设文字,自定义机器人人格等信息
- 详见`config.py`中的`default_prompt`部分
- 支持设置多个预设情景,并通过!reset、!default等指令控制,详细请查看[wiki指令](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%8C%87%E4%BB%A4)
</details>
<details>
<summary>✅支持对话、绘图等模型,可玩性更高</summary>
- 现已支持OpenAI的对话`Completion API`和绘图`Image API`
- 向机器人发送指令`!draw <prompt>`即可使用绘图模型
</details>
<details>
<summary>✅支持指令控制热重载、热更新</summary>
- 允许在运行期间修改`config.py`或其他代码后,以管理员账号向机器人发送指令`!reload`进行热重载,无需重启
- 运行期间允许以管理员账号向机器人发送指令`!update`进行热更新,拉取远程最新代码并执行热重载
</details>
<details>
<summary>✅支持插件加载🧩</summary>
- 自行实现插件加载器及相关支持
- 详细查看[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)
</details>
<details>
<summary>✅私聊、群聊黑名单机制</summary>
- 支持将人或群聊加入黑名单以忽略其消息
- 详见Wiki`加入黑名单`
</details>
<details>
<summary>✅长消息处理策略</summary>
- 支持将长消息转换成图片或消息记录组件,避免消息刷屏
- 请查看`config.py``blob_message_strategy`等字段
</details>
<details>
<summary>✅回复速度限制</summary>
- 支持限制单会话内每分钟可进行的对话次数
- 具有“等待”和“丢弃”两种策略
- “等待”策略:在获取到回复后,等待直到此次响应时间达到对话响应时间均值
- “丢弃”策略:此分钟内对话次数达到限制时,丢弃之后的对话
- 详细请查看config.py中的相关配置
</details>
详情请查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%8A%9F%E8%83%BD%E7%82%B9%E5%88%97%E4%B8%BE)
## 🔩部署
**部署过程中遇到任何问题,请先在[QChatGPT](https://github.com/RockChinQ/QChatGPT/issues)或[qcg-installer](https://github.com/RockChinQ/qcg-installer/issues)的issue里进行搜索**
### - 注册OpenAI账号
**可以直接进群找群主购买**
或参考以下文章自行注册
> ~~[只需 1 元搞定 ChatGPT 注册](https://zhuanlan.zhihu.com/p/589470082)~~(已失效)
> [手把手教你如何注册ChatGPT,超级详细](https://guxiaobei.com/51461)
注册成功后请前往[个人中心查看](https://beta.openai.com/account/api-keys)api_key
完成注册后,使用以下自动化或手动部署步骤
### - 自动化部署
<details>
<summary>展开查看,以下方式二选一,Linux首选DockerWindows首选安装器</summary>
#### Docker方式
请查看此仓库[mikumifa/QChatGPT-Docker-Installer](https://github.com/mikumifa/QChatGPT-Docker-Installer)
#### 安装器方式
使用[此安装器](https://github.com/RockChinQ/qcg-installer)(若无法访问请到[Gitee](https://gitee.com/RockChin/qcg-installer))进行部署
- 安装器目前仅支持部分平台,请到仓库文档查看,其他平台请手动部署
</details>
### - 手动部署
<details>
<summary>手动部署适用于所有平台</summary>
- 请使用Python 3.9.x以上版本
#### 配置Mirai
按照[此教程](https://yiri-mirai.wybxc.cc/tutorials/01/configuration)配置Mirai及YiriMirai
启动mirai-console后,使用`login`命令登录QQ账号,保持mirai-console运行状态
#### 配置主程序
1. 克隆此项目
```bash
git clone https://github.com/RockChinQ/QChatGPT
cd QChatGPT
```
2. 安装依赖
```bash
pip3 install yiri-mirai openai colorlog func_timeout
pip3 install dulwich
```
3. 运行一次主程序,生成配置文件
```bash
python3 main.py
```
4. 编辑配置文件`config.py`
按照文件内注释填写配置信息
5. 运行主程序
```bash
python3 main.py
```
无报错信息即为运行成功
**常见问题**
- mirai登录提示`QQ版本过低`,见[此issue](https://github.com/RockChinQ/QChatGPT/issues/137)
- 如提示安装`uvicorn``hypercorn`请*不要*安装,这两个不是必需的,目前存在未知原因bug
- 如报错`TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary`, 请参考 [此处](https://github.com/RockChinQ/QChatGPT/issues/5)
</details>
## 🚀使用
查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F)
## 🧩插件生态
现已支持自行开发插件对功能进行扩展或自定义程序行为
详见[Wiki插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)
开发教程见[Wiki插件开发页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91)
### 示例插件
`tests/plugin_examples`目录下,将其整个目录复制到`plugins`目录下即可使用
- `cmdcn` - 主程序指令中文形式
- `hello_plugin` - 在收到消息`hello`时回复相应消息
- `urlikethisijustsix` - 收到冒犯性消息时回复相应消息
### 更多
欢迎提交新的插件
- [revLibs](https://github.com/RockChinQ/revLibs) - 将ChatGPT网页版接入此项目,关于[官方接口和网页版有什么区别](https://github.com/RockChinQ/QChatGPT/wiki/%E5%AE%98%E6%96%B9%E6%8E%A5%E5%8F%A3%E4%B8%8EChatGPT%E7%BD%91%E9%A1%B5%E7%89%88)
- [hello_plugin](https://github.com/RockChinQ/hello_plugin) - `hello_plugin` 的储存库形式,插件开发模板
- [dominoar/QChatPlugins](https://github.com/dominoar/QchatPlugins) - dominoar编写的诸多新功能插件(语言输出、Ranimg、屏蔽词规则等)
- [dominoar/QCP-NovelAi](https://github.com/dominoar/QCP-NovelAi) - NovelAI 故事叙述与绘画
## 😘致谢
- [@the-lazy-me](https://github.com/the-lazy-me) 为本项目制作[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)
- [@mikumifa](https://github.com/mikumifa) 本项目Docker部署仓库开发者
- [@dominoar](https://github.com/dominoar) 为本项目开发多种插件
- [@hissincn](https://github.com/hissincn) 本项目贡献者
- [@LINSTCL](https://github.com/LINSTCL) GPT-3.5官方模型适配贡献者
以及其他所有为本项目提供支持的朋友们。
## 👍赞赏
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/>
-20
View File
@@ -1,20 +0,0 @@
# 是否启用禁用列表
enable = True
# 禁用规则(黑名单)
# person为个人,其中的QQ号会被禁止与机器人进行私聊或群聊交互
# 示例: person = [2854196310, 1234567890, 9876543210]
# group为群组,其中的群号会被禁止与机器人进行交互
# 示例: group = [123456789, 987654321, 1234567890]
#
# 支持正则表达式,字符串都将被识别为正则表达式,例如:
# person = [12345678, 87654321, "2854.*"]
# group = [123456789, 987654321, "1234.*"]
# 若要排除某个QQ号或群号(即允许使用),可以在前面加上"!",例如:
# person = ["!1234567890"]
# group = ["!987654321"]
# 排除规则优先级高于包含规则,即如果同时存在包含规则和排除规则,排除规则将生效,例如:
# person = ["1234.*", "!1234567890"]
# 那么1234567890将不会被禁用,而其他以1234开头的QQ号都会被禁用
person = [2854196310] # 2854196310是Q群管家机器人的QQ号,默认屏蔽以免出现循环
group = [204785790, 691226829] # 本项目交流群的群号,默认屏蔽,避免在交流群测试机器人
-245
View File
@@ -1,245 +0,0 @@
# 配置文件: 注释里标[必需]的参数必须修改, 其他参数根据需要修改, 但请勿删除
import logging
# [必需] Mirai的配置
# 请到配置mirai的步骤中的教程查看每个字段的信息
# adapter: 选择适配器,目前支持HTTPAdapter和WebSocketAdapter
# host: 运行mirai的主机地址
# port: 运行mirai的主机端口
# verifyKey: mirai-api-http的verifyKey
# qq: 机器人的QQ号
#
# 注意: QQ机器人配置不支持热重载及热更新
mirai_http_api_config = {
"adapter": "WebSocketAdapter",
"host": "localhost",
"port": 8080,
"verifyKey": "yirimirai",
"qq": 1234567890
}
# [必需] OpenAI的配置
# api_key: OpenAI的API Key
# http_proxy: 请求OpenAI时使用的代理,None为不使用,https和socks5暂不能使用
# 若只有一个api-key,请直接修改以下内容中的"openai_api_key"为你的api-key
#
# 如准备了多个api-key,可以以字典的形式填写,程序会自动选择可用的api-key
# 例如
# openai_config = {
# "api_key": {
# "default": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# "key1": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# "key2": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# },
# "http_proxy": "http://127.0.0.1:12345"
# }
openai_config = {
"api_key": {
"default": "openai_api_key"
},
"http_proxy": None
}
# [必需] 管理员QQ号,用于接收报错等通知及执行管理员级别指令
# 支持多个管理员,可以使用list形式设置,例如:
# admin_qq = [12345678, 87654321]
admin_qq = 0
# 情景预设(机器人人格)
# 每个会话的预设信息,影响所有会话,无视指令重置
# 可以通过这个字段指定某些情况的回复,可直接用自然语言描述指令
# 例如:
# default_prompt = "如果我之后想获取帮助,请你说“输入!help获取帮助”"
# 这样用户在不知所措的时候机器人就会提示其输入!help获取帮助
# 可参考 https://github.com/PlexPt/awesome-chatgpt-prompts-zh
#
# 如果需要多个情景预设,并在运行期间方便切换,请使用字典的形式填写,例如
# default_prompt = {
# "default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
# "linux-terminal": "我想让你充当 Linux 终端。我将输入命令,您将回复终端应显示的内容。",
# "en-dict": "我想让你充当英英词典,对于给出的英文单词,你要给出其中文意思以及英文解释,并且给出一个例句,此外不要有其他反馈。",
# }
#
# 在使用期间即可通过指令:
# !reset [名称]
# 来使用指定的情景预设重置会话
# 例如:
# !reset linux-terminal
# 若不指定名称,则使用默认情景预设
#
# 也可以使用指令:
# !default <名称>
# 将指定的情景预设设置为默认情景预设
# 例如:
# !default linux-terminal
# 之后的会话重置时若不指定名称,则使用linux-terminal情景预设
#
# 还可以加载文件中的预设文字,使用方法请查看:https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E9%A2%84%E8%AE%BE%E6%96%87%E5%AD%97
default_prompt = {
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
}
# 群内响应规则
# 符合此消息的群内消息即使不包含at机器人也会响应
# 支持消息前缀匹配及正则表达式匹配
# 注意:由消息前缀(prefix)匹配的消息中将会删除此前缀,正则表达式(regexp)匹配的消息不会删除匹配的部分
# 前缀匹配优先级高于正则表达式匹配
# 正则表达式简明教程:https://www.runoob.com/regexp/regexp-tutorial.html
response_rules = {
"prefix": ["/ai", "!ai", "ai", "ai"],
"regexp": [] # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办"
}
# 消息忽略规则
# 适用于私聊及群聊
# 符合此规则的消息将不会被响应
# 支持消息前缀匹配及正则表达式匹配
# 此设置优先级高于response_rules
# 用以过滤mirai等其他层级的指令
# @see https://github.com/RockChinQ/QChatGPT/issues/165
ignore_rules = {
"prefix": ["/"],
"regexp": []
}
# 是否检查收到的消息中是否包含敏感词
# 若收到的消息无法通过下方指定的敏感词检查策略,则发送提示信息
income_msg_check = False
# 敏感词过滤开关,以同样数量的*代替敏感词回复
# 请在sensitive.json中添加敏感词
sensitive_word_filter = True
# 是否启用百度云内容安全审核
# 注册方式查看 https://cloud.baidu.com/doc/ANTIPORN/s/Wkhu9d5iy
baidu_check = False
# 百度云API_KEY 24位英文数字字符串
baidu_api_key = ""
# 百度云SECRET_KEY 32位的英文数字字符串
baidu_secret_key = ""
# 不合规消息自定义返回
inappropriate_message_tips = "[百度云]请珍惜机器人,当前返回内容不合规"
# 启动时是否发送赞赏码
# 仅当使用量已经超过2048字时发送
encourage_sponsor_at_start = True
# 每次向OpenAI接口发送对话记录上下文的字符数
# 最大不超过(4096 - max_tokens)个字符,max_tokens为下方completion_api_params中的max_tokens
# 注意:较大的prompt_submit_length会导致OpenAI账户额度消耗更快
prompt_submit_length = 1024
# OpenAI补全API的参数
# 请在下方填写模型,程序自动选择接口
# 现已支持的模型有:
#
# 'gpt-3.5-turbo'
# 'gpt-3.5-turbo-0301'
# 'text-davinci-003'
# 'text-davinci-002'
# 'code-davinci-002'
# 'code-cushman-001'
# 'text-curie-001'
# 'text-babbage-001'
# 'text-ada-001'
#
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/completions/create
completion_api_params = {
"model": "gpt-3.5-turbo",
"temperature": 0.9, # 数值越低得到的回答越理性,取值范围[0, 1]
"max_tokens": 1024, # 每次获取OpenAI接口响应的文字量上限, 不高于4096
"top_p": 1, # 生成的文本的文本与要求的符合度, 取值范围[0, 1]
"frequency_penalty": 0.2,
"presence_penalty": 1.0,
}
# OpenAI的Image API的参数
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/images/create
image_api_params = {
"size": "256x256", # 图片尺寸,支持256x256, 512x512, 1024x1024
}
# 群内回复消息时是否引用原消息
quote_origin = True
# 回复绘图时是否包含图片描述
include_image_description = True
# 消息处理的超时时间,单位为秒
process_message_timeout = 30
# 回复消息时是否显示[GPT]前缀
show_prefix = False
# 应用长消息处理策略的阈值
# 当回复消息长度超过此值时,将使用长消息处理策略
blob_message_threshold = 256
# 长消息处理策略
# - "image": 将长消息转换为图片发送
# - "forward": 将长消息转换为转发消息组件发送
blob_message_strategy = "forward"
# 消息处理超时重试次数
retry_times = 3
# 消息处理出错时是否向用户隐藏错误详细信息
# 设置为True时,仅向管理员发送错误详细信息
# 设置为False时,向用户及管理员发送错误详细信息
hide_exce_info_to_user = False
# 消息处理出错时向用户发送的提示信息
# 仅当hide_exce_info_to_user为True时生效
# 设置为空字符串时,不发送提示信息
alter_tip_message = '出错了,请稍后再试'
# 每个会话的过期时间,单位为秒
# 默认值20分钟
session_expire_time = 60 * 20
# 会话限速
# 单会话内每分钟可进行的对话次数
# 若不需要限速,可以设置为一个很大的值
# 默认值60次,基本上不会触发限速
rate_limitation = 60
# 会话限速策略
# - "wait": 每次对话获取到回复时,等待一定时间再发送回复,保证其不会超过限速均值
# - "drop": 此分钟内,若对话次数超过限速次数,则丢弃之后的对话,每自然分钟重置
rate_limit_strategy = "wait"
# drop策略时,超过限速均值时,丢弃的对话的提示信息
# 仅当rate_limitation_strategy为"drop"时生效
# 若设置为空字符串,则不发送提示信息
rate_limit_drop_tip = "本分钟对话次数超过限速次数,此对话被丢弃"
# 是否在启动时进行依赖库更新
upgrade_dependencies = True
# 是否上报统计信息
# 用于统计机器人的使用情况,不会收集任何用户信息
# 仅上报时间、字数使用量、绘图使用量,其他信息不会上报
report_usage = True
# 日志级别
logging_level = logging.INFO
# 定制帮助消息
help_message = """此机器人通过调用OpenAI的GPT-3大型语言模型生成回复,不具有情感。
你可以用自然语言与其交流,回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。
了解此项目请找QQ 1010553892 联系作者
请不要用其生成整篇文章或大段代码,因为每次只会向模型提交少部分文字,生成大部分文字会产生偏题、前后矛盾等问题
每次会话最后一次交互后{}分钟后会自动结束,结束后将开启新会话,如需继续前一次会话请发送 !last 重新开启
欢迎到github.com/RockChinQ/QChatGPT 给个star
帮助信息:
!help - 显示帮助
!reset - 重置会话
!last - 切换到前一次的对话
!next - 切换到后一次的对话
!prompt - 显示当前对话所有内容
!list - 列出所有历史会话
!usage - 列出各个api-key的使用量""".format(session_expire_time // 60)
+10
View File
@@ -0,0 +1,10 @@
version: "3"
services:
qchatgpt:
image: rockchin/qchatgpt:latest
volumes:
- ./data:/app/data
- ./plugins:/app/plugins
restart: on-failure
# 根据具体环境配置网络
+12 -357
View File
@@ -1,363 +1,18 @@
import importlib import asyncio
import os
import shutil
import threading
import time
import logging
import sys
try:
import colorlog
except ImportError:
# 尝试安装
import pkg.utils.pkgmgr as pkgmgr
pkgmgr.install_requirements("requirements.txt")
try:
import colorlog
except ImportError:
print("依赖不满足,请查看 https://github.com/RockChinQ/qcg-installer/issues/15")
sys.exit(1)
import colorlog
import requests
import websockets.exceptions
from urllib3.exceptions import InsecureRequestWarning
sys.path.append(".") asciiart = r"""
___ ___ _ _ ___ ___ _____
log_colors_config = { / _ \ / __| |_ __ _| |_ / __| _ \_ _|
'DEBUG': 'green', # cyan white | (_) | (__| ' \/ _` | _| (_ | _/ | |
'INFO': 'white', \__\_\\___|_||_\__,_|\__|\___|_| |_|
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
}
def init_db():
import pkg.database.manager
database = pkg.database.manager.DatabaseManager()
database.initialize_database()
def ensure_dependencies():
import pkg.utils.pkgmgr as pkgmgr
pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade"])
known_exception_caught = False
log_file_name = "qchatgpt.log"
def init_runtime_log_file():
"""为此次运行生成日志文件
格式: qchatgpt-yyyy-MM-dd-HH-mm-ss.log
"""
global log_file_name
# 检查logs目录是否存在
if not os.path.exists("logs"):
os.mkdir("logs")
# 检查本目录是否有qchatgpt.log,若有,移动到logs目录
if os.path.exists("qchatgpt.log"):
shutil.move("qchatgpt.log", "logs/qchatgpt.legacy.log")
log_file_name = "logs/qchatgpt-%s.log" % time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
def reset_logging():
global log_file_name
assert os.path.exists('config.py')
config = importlib.import_module('config')
import pkg.utils.context
if pkg.utils.context.context['logger_handler'] is not None:
logging.getLogger().removeHandler(pkg.utils.context.context['logger_handler'])
for handler in logging.getLogger().handlers:
logging.getLogger().removeHandler(handler)
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
filename=log_file_name, # log日志输出的文件位置和文件名
format="[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s",
# 日志输出的格式
# -8表示占位符,让输出左对齐,输出长度都为8位
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
)
sh = logging.StreamHandler()
sh.setLevel(config.logging_level)
sh.setFormatter(colorlog.ColoredFormatter(
fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : "
"%(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
log_colors=log_colors_config
))
logging.getLogger().addHandler(sh)
pkg.utils.context.context['logger_handler'] = sh
return sh
def main(first_time_init=False):
global known_exception_caught
import config
# 更新openai库到最新版本
if not hasattr(config, 'upgrade_dependencies') or config.upgrade_dependencies:
print("正在更新依赖库,请等待...")
if not hasattr(config, 'upgrade_dependencies'):
print("这个操作不是必须的,如果不想更新,请在config.py中添加upgrade_dependencies=False")
else:
print("这个操作不是必须的,如果不想更新,请在config.py中将upgrade_dependencies设置为False")
try:
ensure_dependencies()
except Exception as e:
print("更新openai库失败:{}, 请忽略或自行更新".format(e))
known_exception_caught = False
try:
# 导入config.py
assert os.path.exists('config.py')
config = importlib.import_module('config')
import pkg.utils.context
pkg.utils.context.set_config(config)
init_runtime_log_file()
sh = reset_logging()
# 检查是否设置了管理员
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
# logging.warning("未设置管理员QQ,管理员权限指令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
while True:
try:
config.admin_qq = int(input("未设置管理员QQ,管理员权限指令及运行告警将无法使用,请输入管理员QQ号: "))
# 写入到文件
# 读取文件
config_file_str = ""
with open("config.py", "r", encoding="utf-8") as f:
config_file_str = f.read()
# 替换
config_file_str = config_file_str.replace("admin_qq = 0", "admin_qq = " + str(config.admin_qq))
# 写入
with open("config.py", "w", encoding="utf-8") as f:
f.write(config_file_str)
print("管理员QQ已设置,如需修改请修改config.py中的admin_qq字段")
time.sleep(4)
break
except ValueError:
print("请输入数字")
import pkg.openai.manager
import pkg.database.manager
import pkg.openai.session
import pkg.qqbot.manager
import pkg.openai.dprompt
pkg.openai.dprompt.read_prompt_from_file()
pkg.utils.context.context['logger_handler'] = sh
# 主启动流程
database = pkg.database.manager.DatabaseManager()
database.initialize_database()
openai_interact = pkg.openai.manager.OpenAIInteract(config.openai_config['api_key'])
# 加载所有未超时的session
pkg.openai.session.load_sessions()
# 初始化qq机器人
qqbot = pkg.qqbot.manager.QQBotManager(mirai_http_api_config=config.mirai_http_api_config,
timeout=config.process_message_timeout, retry=config.retry_times,
first_time_init=first_time_init)
# 加载插件
import pkg.plugin.host
pkg.plugin.host.load_plugins()
pkg.plugin.host.initialize_plugins()
if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程
import mirai.exceptions
def run_bot_wrapper():
global known_exception_caught
try:
qqbot.bot.run()
except TypeError as e:
if str(e).__contains__("argument 'debug'"):
logging.error(
"连接bot失败:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/82".format(e))
known_exception_caught = True
elif str(e).__contains__("As of 3.10, the *loop*"):
logging.error(
"Websockets版本过低:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/5".format(e))
known_exception_caught = True
except websockets.exceptions.InvalidStatus as e:
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
known_exception_caught = True
except mirai.exceptions.NetworkError as e:
logging.error("连接mirai-api-http失败:{}, 请检查是否已按照文档启动mirai".format(e))
known_exception_caught = True
except Exception as e:
if str(e).__contains__("404"):
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
known_exception_caught = True
elif str(e).__contains__("signal only works in main thread"):
logging.error(
"hypercorn异常:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/86".format(
e))
known_exception_caught = True
elif str(e).__contains__("did not receive a valid HTTP"):
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
else:
logging.error(
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e))
known_exception_caught = True
raise e
qq_bot_thread = threading.Thread(target=run_bot_wrapper, args=(), daemon=True)
qq_bot_thread.start()
finally:
time.sleep(12)
if first_time_init:
if not known_exception_caught:
logging.info('程序启动完成,如长时间未显示 ”成功登录到账号xxxxx“ ,并且不回复消息,请查看 '
'https://github.com/RockChinQ/QChatGPT/issues/37')
else:
sys.exit(1)
else:
logging.info('热重载完成')
# 发送赞赏码
if hasattr(config, 'encourage_sponsor_at_start') \
and config.encourage_sponsor_at_start \
and pkg.utils.context.get_openai_manager().audit_mgr.get_total_text_length() >= 2048:
logging.info("发送赞赏码")
from mirai import MessageChain, Plain, Image
import pkg.utils.constants
message_chain = MessageChain([
Plain("自2022年12月初以来,开发者已经花费了大量时间和精力来维护本项目,如果您觉得本项目对您有帮助,欢迎赞赏开发者,"
"以支持项目稳定运行😘"),
Image(base64=pkg.utils.constants.alipay_qr_b64),
Image(base64=pkg.utils.constants.wechat_qr_b64),
Plain("BTC: 3N4Azee63vbBB9boGv9Rjf4N5SocMe5eCq\nXMR: 89LS21EKQuDGkyQoe2nDupiuWXk4TVD6FALvSKv5owfmeJEPFpHeMsZLYtLiJ6GxLrhsRe5gMs6MyMSDn4GNQAse2Mae4KE\n\n"),
Plain("(本消息仅在启动时发送至管理员,如果您不想再看到此消息,请在config.py中将encourage_sponsor_at_start设置为False)")
])
pkg.utils.context.get_qqbot_manager().notify_admin_message_chain(message_chain)
time.sleep(5)
import pkg.utils.updater
try:
if pkg.utils.updater.is_new_version_available():
pkg.utils.context.get_qqbot_manager().notify_admin("新版本可用,请发送 !update 进行自动更新\n更新日志:\n{}".format("\n".join(pkg.utils.updater.get_rls_notes())))
else:
logging.info("当前已是最新版本")
except Exception as e:
logging.warning("检查更新失败:{}".format(e))
while True:
try:
time.sleep(10)
if qqbot != pkg.utils.context.get_qqbot_manager(): # 已经reload了
logging.info("以前的main流程由于reload退出")
break
except KeyboardInterrupt:
stop()
print("程序退出")
sys.exit(0)
def stop():
import pkg.utils.context
import pkg.qqbot.manager
import pkg.openai.session
try:
import pkg.plugin.host
pkg.plugin.host.unload_plugins()
qqbot_inst = pkg.utils.context.get_qqbot_manager()
assert isinstance(qqbot_inst, pkg.qqbot.manager.QQBotManager)
for session in pkg.openai.session.sessions:
logging.info('持久化session: %s', session)
pkg.openai.session.sessions[session].persistence()
pkg.utils.context.get_database_manager().close()
except Exception as e:
if not isinstance(e, KeyboardInterrupt):
raise e
⭐️开源地址: https://github.com/RockChinQ/QChatGPT
📖文档地址: https://q.rkcn.top
"""
if __name__ == '__main__': if __name__ == '__main__':
# 检查是否有config.py,如果没有就把config-template.py复制一份,并退出程序 print(asciiart)
if not os.path.exists('config.py'):
shutil.copy('config-template.py', 'config.py')
print('请先在config.py中填写配置')
sys.exit(0)
# 检查是否有banlist.py,如果没有就把banlist-template.py复制一份 from pkg.core import boot
if not os.path.exists('banlist.py'): asyncio.run(boot.main())
shutil.copy('banlist-template.py', 'banlist.py')
# 检查是否有sensitive.json
if not os.path.exists("sensitive.json"):
shutil.copy("sensitive-template.json", "sensitive.json")
# 检查temp目录
if not os.path.exists("temp/"):
os.mkdir("temp/")
# 检查并创建plugins、prompts目录
check_path = ["plugins", "prompts"]
for path in check_path:
if not os.path.exists(path):
os.mkdir(path)
if len(sys.argv) > 1 and sys.argv[1] == 'init_db':
init_db()
sys.exit(0)
elif len(sys.argv) > 1 and sys.argv[1] == 'update':
try:
try:
import pkg.utils.pkgmgr
pkg.utils.pkgmgr.ensure_dulwich()
except:
pass
from dulwich import porcelain
repo = porcelain.open_repo('.')
porcelain.pull(repo)
except ModuleNotFoundError:
print("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
sys.exit(0)
# import pkg.utils.configmgr
#
# pkg.utils.configmgr.set_config_and_reload("quote_origin", False)
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
main(True)
+3
View File
@@ -0,0 +1,3 @@
"""
审计相关操作
"""
+89
View File
@@ -0,0 +1,89 @@
from __future__ import annotations
import abc
import uuid
import json
import logging
import asyncio
import aiohttp
import requests
from ...core import app
class APIGroup(metaclass=abc.ABCMeta):
"""API 组抽象类"""
_basic_info: dict = None
_runtime_info: dict = None
prefix = None
ap: app.Application
def __init__(self, prefix: str, ap: app.Application):
self.prefix = prefix
self.ap = ap
async def _do(
self,
method: str,
path: str,
data: dict = None,
params: dict = None,
headers: dict = {},
**kwargs
):
self._runtime_info['account_id'] = "-1"
url = self.prefix + path
data = json.dumps(data)
headers['Content-Type'] = 'application/json'
try:
async with aiohttp.ClientSession() as session:
async with session.request(
method,
url,
data=data,
params=params,
headers=headers,
**kwargs
) as resp:
self.ap.logger.debug("data: %s", data)
self.ap.logger.debug("ret: %s", await resp.text())
except Exception as e:
self.ap.logger.debug(f'上报失败: {e}')
async def do(
self,
method: str,
path: str,
data: dict = None,
params: dict = None,
headers: dict = {},
**kwargs
) -> asyncio.Task:
"""执行请求"""
asyncio.create_task(self._do(method, path, data, params, headers, **kwargs))
def gen_rid(
self
):
"""生成一个请求 ID"""
return str(uuid.uuid4())
def basic_info(
self
):
"""获取基本信息"""
basic_info = APIGroup._basic_info.copy()
basic_info['rid'] = self.gen_rid()
return basic_info
def runtime_info(
self
):
"""获取运行时信息"""
return APIGroup._runtime_info
+55
View File
@@ -0,0 +1,55 @@
from __future__ import annotations
from .. import apigroup
from ....core import app
class V2MainDataAPI(apigroup.APIGroup):
"""主程序相关 数据API"""
def __init__(self, prefix: str, ap: app.Application):
self.ap = ap
super().__init__(prefix+"/main", ap)
async def do(self, *args, **kwargs):
if not self.ap.system_cfg.data['report-usage']:
return None
return await super().do(*args, **kwargs)
async def post_update_record(
self,
spent_seconds: int,
infer_reason: str,
old_version: str,
new_version: str,
):
"""提交更新记录"""
return await self.do(
"POST",
"/update",
data={
"basic": self.basic_info(),
"update_info": {
"spent_seconds": spent_seconds,
"infer_reason": infer_reason,
"old_version": old_version,
"new_version": new_version,
}
}
)
async def post_announcement_showed(
self,
ids: list[int],
):
"""提交公告已阅"""
return await self.do(
"POST",
"/announcement",
data={
"basic": self.basic_info(),
"announcement_info": {
"ids": ids,
}
}
)
+65
View File
@@ -0,0 +1,65 @@
from __future__ import annotations
from ....core import app
from .. import apigroup
class V2PluginDataAPI(apigroup.APIGroup):
"""插件数据相关 API"""
def __init__(self, prefix: str, ap: app.Application):
self.ap = ap
super().__init__(prefix+"/plugin", ap)
async def do(self, *args, **kwargs):
if not self.ap.system_cfg.data['report-usage']:
return None
return await super().do(*args, **kwargs)
async def post_install_record(
self,
plugin: dict
):
"""提交插件安装记录"""
return await self.do(
"POST",
"/install",
data={
"basic": self.basic_info(),
"plugin": plugin,
}
)
async def post_remove_record(
self,
plugin: dict
):
"""提交插件卸载记录"""
return await self.do(
"POST",
"/remove",
data={
"basic": self.basic_info(),
"plugin": plugin,
}
)
async def post_update_record(
self,
plugin: dict,
old_version: str,
new_version: str,
):
"""提交插件更新记录"""
return await self.do(
"POST",
"/update",
data={
"basic": self.basic_info(),
"plugin": plugin,
"update_info": {
"old_version": old_version,
"new_version": new_version,
}
}
)
+88
View File
@@ -0,0 +1,88 @@
from __future__ import annotations
from .. import apigroup
from ....core import app
class V2UsageDataAPI(apigroup.APIGroup):
"""使用量数据相关 API"""
def __init__(self, prefix: str, ap: app.Application):
self.ap = ap
super().__init__(prefix+"/usage", ap)
async def do(self, *args, **kwargs):
if not self.ap.system_cfg.data['report-usage']:
return None
return await super().do(*args, **kwargs)
async def post_query_record(
self,
session_type: str,
session_id: str,
query_ability_provider: str,
usage: int,
model_name: str,
response_seconds: int,
retry_times: int,
):
"""提交请求记录"""
return await self.do(
"POST",
"/query",
data={
"basic": self.basic_info(),
"runtime": self.runtime_info(),
"session_info": {
"type": session_type,
"id": session_id,
},
"query_info": {
"ability_provider": query_ability_provider,
"usage": usage,
"model_name": model_name,
"response_seconds": response_seconds,
"retry_times": retry_times,
}
}
)
async def post_event_record(
self,
plugins: list[dict],
event_name: str,
):
"""提交事件触发记录"""
return await self.do(
"POST",
"/event",
data={
"basic": self.basic_info(),
"runtime": self.runtime_info(),
"plugins": plugins,
"event_info": {
"name": event_name,
}
}
)
async def post_function_record(
self,
plugin: dict,
function_name: str,
function_description: str,
):
"""提交内容函数使用记录"""
return await self.do(
"POST",
"/function",
data={
"basic": self.basic_info(),
"plugin": plugin,
"function_info": {
"name": function_name,
"description": function_description,
}
}
)
+37
View File
@@ -0,0 +1,37 @@
from __future__ import annotations
import logging
from . import apigroup
from .groups import main
from .groups import usage
from .groups import plugin
from ...core import app
BACKEND_URL = "https://api.qchatgpt.rockchin.top/api/v2"
class V2CenterAPI:
"""中央服务器 v2 API 交互类"""
main: main.V2MainDataAPI = None
"""主 API 组"""
usage: usage.V2UsageDataAPI = None
"""使用量 API 组"""
plugin: plugin.V2PluginDataAPI = None
"""插件 API 组"""
def __init__(self, ap: app.Application, basic_info: dict = None, runtime_info: dict = None):
"""初始化"""
logging.debug("basic_info: %s, runtime_info: %s", basic_info, runtime_info)
apigroup.APIGroup._basic_info = basic_info
apigroup.APIGroup._runtime_info = runtime_info
self.main = main.V2MainDataAPI(BACKEND_URL, ap)
self.usage = usage.V2UsageDataAPI(BACKEND_URL, ap)
self.plugin = plugin.V2PluginDataAPI(BACKEND_URL, ap)
-113
View File
@@ -1,113 +0,0 @@
import hashlib
import json
import logging
import requests
import pkg.utils.context
import pkg.utils.updater
class DataGatherer:
"""数据收集器"""
usage = {}
"""以key值md5为key,{
"text": {
"text-davinci-003": 文字量:int,
},
"image": {
"256x256": 图片数量:int,
}
}为值的字典"""
version_str = "undetermined"
def __init__(self):
self.load_from_db()
try:
self.version_str = pkg.utils.updater.get_commit_id_and_time_and_msg()[:40 if len(pkg.utils.updater.get_commit_id_and_time_and_msg()) > 40 else len(pkg.utils.updater.get_commit_id_and_time_and_msg())]
except:
pass
def report_to_server(self, subservice_name: str, count: int):
try:
config = pkg.utils.context.get_config()
if hasattr(config, "report_usage") and not config.report_usage:
return
res = requests.get("http://rockchin.top:18989/usage?service_name=qchatgpt.{}&version={}&count={}".format(subservice_name, self.version_str, count))
if res.status_code != 200 or res.text != "ok":
logging.warning("report to server failed, status_code: {}, text: {}".format(res.status_code, res.text))
except:
return
def get_usage(self, key_md5):
return self.usage[key_md5] if key_md5 in self.usage else {}
def report_text_model_usage(self, model, total_tokens):
key_md5 = pkg.utils.context.get_openai_manager().key_mgr.get_using_key_md5()
if key_md5 not in self.usage:
self.usage[key_md5] = {}
if "text" not in self.usage[key_md5]:
self.usage[key_md5]["text"] = {}
if model not in self.usage[key_md5]["text"]:
self.usage[key_md5]["text"][model] = 0
length = total_tokens
self.usage[key_md5]["text"][model] += length
self.dump_to_db()
self.report_to_server("text", length)
def report_image_model_usage(self, size):
key_md5 = pkg.utils.context.get_openai_manager().key_mgr.get_using_key_md5()
if key_md5 not in self.usage:
self.usage[key_md5] = {}
if "image" not in self.usage[key_md5]:
self.usage[key_md5]["image"] = {}
if size not in self.usage[key_md5]["image"]:
self.usage[key_md5]["image"][size] = 0
self.usage[key_md5]["image"][size] += 1
self.dump_to_db()
self.report_to_server("image", 1)
def get_text_length_of_key(self, key):
key_md5 = hashlib.md5(key.encode('utf-8')).hexdigest()
if key_md5 not in self.usage:
return 0
if "text" not in self.usage[key_md5]:
return 0
# 遍历其中所有模型,求和
return sum(self.usage[key_md5]["text"].values())
def get_image_count_of_key(self, key):
key_md5 = hashlib.md5(key.encode('utf-8')).hexdigest()
if key_md5 not in self.usage:
return 0
if "image" not in self.usage[key_md5]:
return 0
# 遍历其中所有模型,求和
return sum(self.usage[key_md5]["image"].values())
def get_total_text_length(self):
total = 0
for key in self.usage:
if "text" not in self.usage[key]:
continue
total += sum(self.usage[key]["text"].values())
return total
def dump_to_db(self):
pkg.utils.context.get_database_manager().dump_usage_json(self.usage)
def load_from_db(self):
json_str = pkg.utils.context.get_database_manager().load_usage_json()
if json_str is not None:
self.usage = json.loads(json_str)
+83
View File
@@ -0,0 +1,83 @@
import os
import uuid
import json
import time
identifier = {
'host_id': '',
'instance_id': '',
'host_create_ts': 0,
'instance_create_ts': 0,
}
HOST_ID_FILE = os.path.expanduser('~/.qchatgpt/host_id.json')
INSTANCE_ID_FILE = 'res/instance_id.json'
def init():
global identifier
if not os.path.exists(os.path.expanduser('~/.qchatgpt')):
os.mkdir(os.path.expanduser('~/.qchatgpt'))
if not os.path.exists(HOST_ID_FILE):
new_host_id = 'host_'+str(uuid.uuid4())
new_host_create_ts = int(time.time())
with open(HOST_ID_FILE, 'w') as f:
json.dump({
'host_id': new_host_id,
'host_create_ts': new_host_create_ts
}, f)
identifier['host_id'] = new_host_id
identifier['host_create_ts'] = new_host_create_ts
else:
loaded_host_id = ''
loaded_host_create_ts = 0
with open(HOST_ID_FILE, 'r') as f:
file_content = json.load(f)
loaded_host_id = file_content['host_id']
loaded_host_create_ts = file_content['host_create_ts']
identifier['host_id'] = loaded_host_id
identifier['host_create_ts'] = loaded_host_create_ts
# 检查实例 id
if os.path.exists(INSTANCE_ID_FILE):
instance_id = {}
with open(INSTANCE_ID_FILE, 'r') as f:
instance_id = json.load(f)
if instance_id['host_id'] != identifier['host_id']: # 如果实例 id 不是当前主机的,删除
os.remove(INSTANCE_ID_FILE)
if not os.path.exists(INSTANCE_ID_FILE):
new_instance_id = 'instance_'+str(uuid.uuid4())
new_instance_create_ts = int(time.time())
with open(INSTANCE_ID_FILE, 'w') as f:
json.dump({
'host_id': identifier['host_id'],
'instance_id': new_instance_id,
'instance_create_ts': new_instance_create_ts
}, f)
identifier['instance_id'] = new_instance_id
identifier['instance_create_ts'] = new_instance_create_ts
else:
loaded_instance_id = ''
loaded_instance_create_ts = 0
with open(INSTANCE_ID_FILE, 'r') as f:
file_content = json.load(f)
loaded_instance_id = file_content['instance_id']
loaded_instance_create_ts = file_content['instance_create_ts']
identifier['instance_id'] = loaded_instance_id
identifier['instance_create_ts'] = loaded_instance_create_ts
def print_out():
global identifier
print(identifier)
+125
View File
@@ -0,0 +1,125 @@
from __future__ import annotations
import typing
from ..core import app, entities as core_entities
from ..provider import entities as llm_entities
from . import entities, operator, errors
from ..config import manager as cfg_mgr
from .operators import func, plugin, default, reset, list as list_cmd, last, next, delc, resend, prompt, cmd, help, version, update
class CommandManager:
"""命令管理器
"""
ap: app.Application
cmd_list: list[operator.CommandOperator]
def __init__(self, ap: app.Application):
self.ap = ap
async def initialize(self):
# 设置各个类的路径
def set_path(cls: operator.CommandOperator, ancestors: list[str]):
cls.path = '.'.join(ancestors + [cls.name])
for op in operator.preregistered_operators:
if op.parent_class == cls:
set_path(op, ancestors + [cls.name])
for cls in operator.preregistered_operators:
if cls.parent_class is None:
set_path(cls, [])
# 应用命令权限配置
for cls in operator.preregistered_operators:
if cls.path in self.ap.command_cfg.data['privilege']:
cls.lowest_privilege = self.ap.command_cfg.data['privilege'][cls.path]
# 实例化所有类
self.cmd_list = [cls(self.ap) for cls in operator.preregistered_operators]
# 设置所有类的子节点
for cmd in self.cmd_list:
cmd.children = [child for child in self.cmd_list if child.parent_class == cmd.__class__]
# 初始化所有类
for cmd in self.cmd_list:
await cmd.initialize()
async def _execute(
self,
context: entities.ExecuteContext,
operator_list: list[operator.CommandOperator],
operator: operator.CommandOperator = None
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行命令
"""
found = False
if len(context.crt_params) > 0:
for oper in operator_list:
if (context.crt_params[0] == oper.name \
or context.crt_params[0] in oper.alias) \
and (oper.parent_class is None or oper.parent_class == operator.__class__):
found = True
context.crt_command = context.crt_params[0]
context.crt_params = context.crt_params[1:]
async for ret in self._execute(
context,
oper.children,
oper
):
yield ret
break
if not found:
if operator is None:
yield entities.CommandReturn(
error=errors.CommandNotFoundError(context.crt_params[0])
)
else:
if operator.lowest_privilege > context.privilege:
yield entities.CommandReturn(
error=errors.CommandPrivilegeError(operator.name)
)
else:
async for ret in operator.execute(context):
yield ret
async def execute(
self,
command_text: str,
query: core_entities.Query,
session: core_entities.Session
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行命令
"""
privilege = 1
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.system_cfg.data['admin-sessions']:
privilege = 2
ctx = entities.ExecuteContext(
query=query,
session=session,
command_text=command_text,
command='',
crt_command='',
params=command_text.split(' '),
crt_params=command_text.split(' '),
privilege=privilege
)
async for ret in self._execute(
ctx,
self.cmd_list
):
yield ret
+42
View File
@@ -0,0 +1,42 @@
from __future__ import annotations
import typing
import pydantic
import mirai
from ..core import app, entities as core_entities
from . import errors, operator
class CommandReturn(pydantic.BaseModel):
text: typing.Optional[str]
"""文本
"""
image: typing.Optional[mirai.Image]
error: typing.Optional[errors.CommandError]= None
class Config:
arbitrary_types_allowed = True
class ExecuteContext(pydantic.BaseModel):
query: core_entities.Query
session: core_entities.Session
command_text: str
command: str
crt_command: str
params: list[str]
crt_params: list[str]
privilege: int
+33
View File
@@ -0,0 +1,33 @@
class CommandError(Exception):
def __init__(self, message: str = None):
self.message = message
def __str__(self):
return self.message
class CommandNotFoundError(CommandError):
def __init__(self, message: str = None):
super().__init__("未知命令: "+message)
class CommandPrivilegeError(CommandError):
def __init__(self, message: str = None):
super().__init__("权限不足: "+message)
class ParamNotEnoughError(CommandError):
def __init__(self, message: str = None):
super().__init__("参数不足: "+message)
class CommandOperationError(CommandError):
def __init__(self, message: str = None):
super().__init__("操作失败: "+message)
+78
View File
@@ -0,0 +1,78 @@
from __future__ import annotations
import typing
import abc
from ..core import app, entities as core_entities
from . import entities
preregistered_operators: list[typing.Type[CommandOperator]] = []
def operator_class(
name: str,
help: str,
usage: str = None,
alias: list[str] = [],
privilege: int=1, # 1为普通用户,2为管理员
parent_class: typing.Type[CommandOperator] = None
) -> typing.Callable[[typing.Type[CommandOperator]], typing.Type[CommandOperator]]:
def decorator(cls: typing.Type[CommandOperator]) -> typing.Type[CommandOperator]:
cls.name = name
cls.alias = alias
cls.help = help
cls.usage = usage
cls.parent_class = parent_class
cls.lowest_privilege = privilege
preregistered_operators.append(cls)
return cls
return decorator
class CommandOperator(metaclass=abc.ABCMeta):
"""命令算子
"""
ap: app.Application
name: str
"""名称,搜索到时若符合则使用"""
path: str
"""路径,所有父节点的name的连接,用于定义命令权限"""
alias: list[str]
"""同name"""
help: str
"""此节点的帮助信息"""
usage: str = None
parent_class: typing.Union[typing.Type[CommandOperator], None] = None
"""父节点类。标记以供管理器在初始化时编织父子关系。"""
lowest_privilege: int = 0
"""最低权限。若权限低于此值,则不予执行。"""
children: list[CommandOperator]
"""子节点。解析命令时,若节点有子节点,则以下一个参数去匹配子节点,
若有匹配中的,转移到子节点中执行,若没有匹配中的或没有子节点,执行此节点。"""
def __init__(self, ap: app.Application):
self.ap = ap
self.children = []
async def initialize(self):
pass
@abc.abstractmethod
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
pass
+50
View File
@@ -0,0 +1,50 @@
from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="cmd",
help='显示命令列表',
usage='!cmd\n!cmd <命令名称>'
)
class CmdOperator(operator.CommandOperator):
"""命令列表
"""
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
if len(context.crt_params) == 0:
reply_str = "当前所有命令: \n\n"
for cmd in self.ap.cmd_mgr.cmd_list:
if cmd.parent_class is None:
reply_str += f"{cmd.name}: {cmd.help}\n"
reply_str += "\n使用 !cmd <命令名称> 查看命令的详细帮助"
yield entities.CommandReturn(text=reply_str.strip())
else:
cmd_name = context.crt_params[0]
cmd = None
for _cmd in self.ap.cmd_mgr.cmd_list:
if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and (_cmd.parent_class is None):
cmd = _cmd
break
if cmd is None:
yield entities.CommandReturn(error=errors.CommandNotFoundError(cmd_name))
else:
reply_str = f"{cmd.name}: {cmd.help}\n\n"
reply_str += f"使用方法: \n{cmd.usage}"
yield entities.CommandReturn(text=reply_str.strip())
+62
View File
@@ -0,0 +1,62 @@
from __future__ import annotations
import typing
import traceback
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="default",
help="操作情景预设",
usage='!default\n!default set <指定情景预设为默认>'
)
class DefaultOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
reply_str = "当前所有情景预设: \n\n"
for prompt in self.ap.prompt_mgr.get_all_prompts():
content = ""
for msg in prompt.messages:
content += f" {msg.role}: {msg.content}"
reply_str += f"名称: {prompt.name}\n内容: \n{content}\n\n"
reply_str += f"当前会话使用的是: {context.session.use_prompt_name}"
yield entities.CommandReturn(text=reply_str.strip())
@operator.operator_class(
name="set",
help="设置当前会话默认情景预设",
parent_class=DefaultOperator
)
class DefaultSetOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供情景预设名称'))
else:
prompt_name = context.crt_params[0]
try:
prompt = await self.ap.prompt_mgr.get_prompt_by_prefix(prompt_name)
if prompt is None:
yield entities.CommandReturn(error=errors.CommandError("设置当前会话默认情景预设失败: 未找到情景预设 {}".format(prompt_name)))
else:
context.session.use_prompt_name = prompt.name
yield entities.CommandReturn(text=f"已设置当前会话默认情景预设为 {prompt_name}, !reset 后生效")
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("设置当前会话默认情景预设失败: "+str(e)))
+62
View File
@@ -0,0 +1,62 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="del",
help="删除当前会话的历史记录",
usage='!del <序号>\n!del all'
)
class DelOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if context.session.conversations:
delete_index = 0
if len(context.crt_params) > 0:
try:
delete_index = int(context.crt_params[0])
except:
yield entities.CommandReturn(error=errors.CommandOperationError('索引必须是整数'))
return
if delete_index < 0 or delete_index >= len(context.session.conversations):
yield entities.CommandReturn(error=errors.CommandOperationError('索引超出范围'))
return
# 倒序
to_delete_index = len(context.session.conversations)-1-delete_index
if context.session.conversations[to_delete_index] == context.session.using_conversation:
context.session.using_conversation = None
del context.session.conversations[to_delete_index]
yield entities.CommandReturn(text=f"已删除对话: {delete_index}")
else:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
@operator.operator_class(
name="all",
help="删除此会话的所有历史记录",
parent_class=DelOperator
)
class DelAllOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
context.session.conversations = []
context.session.using_conversation = None
yield entities.CommandReturn(text="已删除所有对话")
+27
View File
@@ -0,0 +1,27 @@
from __future__ import annotations
from typing import AsyncGenerator
from .. import operator, entities, cmdmgr
@operator.operator_class(name="func", help="查看所有已注册的内容函数", usage='!func')
class FuncOperator(operator.CommandOperator):
async def execute(
self, context: entities.ExecuteContext
) -> AsyncGenerator[entities.CommandReturn, None]:
reply_str = "当前已加载的内容函数: \n\n"
index = 1
all_functions = await self.ap.tool_mgr.get_all_functions()
for func in all_functions:
reply_str += "{}. {}{}:\n{}\n\n".format(
index,
("(已禁用) " if not func.enable else ""),
func.name,
func.description,
)
index += 1
yield entities.CommandReturn(text=reply_str)
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name='help',
help='显示帮助',
usage='!help\n!help <命令名称>'
)
class HelpOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
help = self.ap.system_cfg.data['help-message']
help += '\n发送命令 !cmd 可查看命令列表'
yield entities.CommandReturn(text=help)
+36
View File
@@ -0,0 +1,36 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="last",
help="切换到前一个对话",
usage='!last'
)
class LastOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if context.session.conversations:
# 找到当前会话的上一个会话
for index in range(len(context.session.conversations)-1, -1, -1):
if context.session.conversations[index] == context.session.using_conversation:
if index == 0:
yield entities.CommandReturn(error=errors.CommandOperationError('已经是第一个对话了'))
return
else:
context.session.using_conversation = context.session.conversations[index-1]
time_str = context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S")
yield entities.CommandReturn(text=f"已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}")
return
else:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
+56
View File
@@ -0,0 +1,56 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="list",
help="列出此会话中的所有历史对话",
usage='!list\n!list <页码>'
)
class ListOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
page = 0
if len(context.crt_params) > 0:
try:
page = int(context.crt_params[0]-1)
except:
yield entities.CommandReturn(error=errors.CommandOperationError('页码应为整数'))
return
record_per_page = 10
content = ''
index = 0
using_conv_index = 0
for conv in context.session.conversations[::-1]:
time_str = conv.create_time.strftime("%Y-%m-%d %H:%M:%S")
if conv == context.session.using_conversation:
using_conv_index = index
if index >= page * record_per_page and index < (page + 1) * record_per_page:
content += f"{index} {time_str}: {conv.messages[0].content if len(conv.messages) > 0 else '无内容'}\n"
index += 1
if content == '':
content = ''
else:
if context.session.using_conversation is None:
content += "\n当前处于新会话"
else:
content += f"\n当前会话: {using_conv_index} {context.session.using_conversation.create_time.strftime('%Y-%m-%d %H:%M:%S')}: {context.session.using_conversation.messages[0].content if len(context.session.using_conversation.messages) > 0 else '无内容'}"
yield entities.CommandReturn(text=f"{page + 1} 页 (时间倒序):\n{content}")
+35
View File
@@ -0,0 +1,35 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="next",
help="切换到后一个对话",
usage='!next'
)
class NextOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if context.session.conversations:
# 找到当前会话的下一个会话
for index in range(len(context.session.conversations)):
if context.session.conversations[index] == context.session.using_conversation:
if index == len(context.session.conversations)-1:
yield entities.CommandReturn(error=errors.CommandOperationError('已经是最后一个对话了'))
return
else:
context.session.using_conversation = context.session.conversations[index+1]
time_str = context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S")
yield entities.CommandReturn(text=f"已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}")
return
else:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
+237
View File
@@ -0,0 +1,237 @@
from __future__ import annotations
import typing
import traceback
from .. import operator, entities, cmdmgr, errors
from ...core import app
@operator.operator_class(
name="plugin",
help="插件操作",
usage="!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>"
)
class PluginOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
plugin_list = self.ap.plugin_mgr.plugins
reply_str = "所有插件({}):\n".format(len(plugin_list))
idx = 0
for plugin in plugin_list:
reply_str += "\n#{} {} {}\n{}\nv{}\n作者: {}\n"\
.format((idx+1), plugin.plugin_name,
"[已禁用]" if not plugin.enabled else "",
plugin.plugin_description,
plugin.plugin_version, plugin.plugin_author)
# TODO 从元数据调远程地址
idx += 1
yield entities.CommandReturn(text=reply_str)
@operator.operator_class(
name="get",
help="安装插件",
privilege=2,
parent_class=PluginOperator
)
class PluginGetOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件仓库地址'))
else:
repo = context.crt_params[0]
yield entities.CommandReturn(text="正在安装插件...")
try:
await self.ap.plugin_mgr.install_plugin(repo)
yield entities.CommandReturn(text="插件安装成功,请重启程序以加载插件")
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件安装失败: "+str(e)))
@operator.operator_class(
name="update",
help="更新插件",
privilege=2,
parent_class=PluginOperator
)
class PluginUpdateOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
else:
plugin_name = context.crt_params[0]
try:
plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name)
if plugin_container is not None:
yield entities.CommandReturn(text="正在更新插件...")
await self.ap.plugin_mgr.update_plugin(plugin_name)
yield entities.CommandReturn(text="插件更新成功,请重启程序以加载插件")
else:
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: 未找到插件"))
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e)))
@operator.operator_class(
name="all",
help="更新所有插件",
privilege=2,
parent_class=PluginUpdateOperator
)
class PluginUpdateAllOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
plugins = [
p.plugin_name
for p in self.ap.plugin_mgr.plugins
]
if plugins:
yield entities.CommandReturn(text="正在更新插件...")
updated = []
try:
for plugin_name in plugins:
await self.ap.plugin_mgr.update_plugin(plugin_name)
updated.append(plugin_name)
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e)))
yield entities.CommandReturn(text="已更新插件: {}".format(", ".join(updated)))
else:
yield entities.CommandReturn(text="没有可更新的插件")
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e)))
@operator.operator_class(
name="del",
help="删除插件",
privilege=2,
parent_class=PluginOperator
)
class PluginDelOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
else:
plugin_name = context.crt_params[0]
try:
plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name)
if plugin_container is not None:
yield entities.CommandReturn(text="正在删除插件...")
await self.ap.plugin_mgr.uninstall_plugin(plugin_name)
yield entities.CommandReturn(text="插件删除成功,请重启程序以加载插件")
else:
yield entities.CommandReturn(error=errors.CommandError("插件删除失败: 未找到插件"))
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件删除失败: "+str(e)))
async def update_plugin_status(plugin_name: str, new_status: bool, ap: app.Application):
if ap.plugin_mgr.get_plugin_by_name(plugin_name) is not None:
for plugin in ap.plugin_mgr.plugins:
if plugin.plugin_name == plugin_name:
plugin.enabled = new_status
for func in plugin.content_functions:
func.enable = new_status
await ap.plugin_mgr.setting.dump_container_setting(ap.plugin_mgr.plugins)
break
return True
else:
return False
@operator.operator_class(
name="on",
help="启用插件",
privilege=2,
parent_class=PluginOperator
)
class PluginEnableOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
else:
plugin_name = context.crt_params[0]
try:
if await update_plugin_status(plugin_name, True, self.ap):
yield entities.CommandReturn(text="已启用插件: {}".format(plugin_name))
else:
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name)))
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: "+str(e)))
@operator.operator_class(
name="off",
help="禁用插件",
privilege=2,
parent_class=PluginOperator
)
class PluginDisableOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
else:
plugin_name = context.crt_params[0]
try:
if await update_plugin_status(plugin_name, False, self.ap):
yield entities.CommandReturn(text="已禁用插件: {}".format(plugin_name))
else:
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name)))
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: "+str(e)))
+29
View File
@@ -0,0 +1,29 @@
from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="prompt",
help="查看当前对话的前文",
usage='!prompt'
)
class PromptOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
if context.session.using_conversation is None:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
else:
reply_str = '当前对话所有内容:\n\n'
for msg in context.session.using_conversation.messages:
reply_str += f"{msg.role}: {msg.content}\n"
yield entities.CommandReturn(text=reply_str)
+34
View File
@@ -0,0 +1,34 @@
from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="resend",
help="重发当前会话的最后一条消息",
usage='!resend'
)
class ResendOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
# 回滚到最后一条用户message前
if context.session.using_conversation is None:
yield entities.CommandReturn(error=errors.CommandError("当前没有对话"))
else:
conv_msg = context.session.using_conversation.messages
# 倒序一直删到最后一条用户message
while len(conv_msg) > 0 and conv_msg[-1].role != 'user':
conv_msg.pop()
if len(conv_msg) > 0:
# 删除最后一条用户message
conv_msg.pop()
# 不重发了,提示用户已删除就行了
yield entities.CommandReturn(text="已删除最后一次请求记录")
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="reset",
help="重置当前会话",
usage='!reset'
)
class ResetOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
context.session.using_conversation = None
yield entities.CommandReturn(text="已重置当前会话")
+30
View File
@@ -0,0 +1,30 @@
from __future__ import annotations
import typing
import traceback
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="update",
help="更新程序",
usage='!update',
privilege=2
)
class UpdateCommand(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
yield entities.CommandReturn(text="正在进行更新...")
if await self.ap.ver_mgr.update_all():
yield entities.CommandReturn(text="更新完成,请重启程序以应用更新")
else:
yield entities.CommandReturn(text="当前已是最新版本")
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("更新失败: "+str(e)))
+27
View File
@@ -0,0 +1,27 @@
from __future__ import annotations
import typing
from .. import operator, cmdmgr, entities, errors
@operator.operator_class(
name="version",
help="显示版本信息",
usage='!version'
)
class VersionCommand(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
reply_str = f"当前版本: \n{self.ap.ver_mgr.get_current_version()}"
try:
if await self.ap.ver_mgr.is_new_version_available():
reply_str += "\n\n有新版本可用, 使用 !update 更新"
except:
pass
yield entities.CommandReturn(text=reply_str.strip())
+47
View File
@@ -0,0 +1,47 @@
import os
import shutil
import json
from .. import model as file_model
class JSONConfigFile(file_model.ConfigFile):
"""JSON配置文件"""
config_file_name: str = None
"""配置文件名"""
template_file_name: str = None
"""模板文件名"""
def __init__(self, config_file_name: str, template_file_name: str) -> None:
self.config_file_name = config_file_name
self.template_file_name = template_file_name
def exists(self) -> bool:
return os.path.exists(self.config_file_name)
async def create(self):
shutil.copyfile(self.template_file_name, self.config_file_name)
async def load(self) -> dict:
if not self.exists():
await self.create()
with open(self.config_file_name, 'r', encoding='utf-8') as f:
cfg = json.load(f)
# 从模板文件中进行补全
with open(self.template_file_name, 'r', encoding='utf-8') as f:
template_cfg = json.load(f)
for key in template_cfg:
if key not in cfg:
cfg[key] = template_cfg[key]
return cfg
async def save(self, cfg: dict):
with open(self.config_file_name, 'w', encoding='utf-8') as f:
json.dump(cfg, f, indent=4, ensure_ascii=False)
+62
View File
@@ -0,0 +1,62 @@
import os
import shutil
import importlib
import logging
from .. import model as file_model
class PythonModuleConfigFile(file_model.ConfigFile):
"""Python模块配置文件"""
config_file_name: str = None
"""配置文件名"""
template_file_name: str = None
"""模板文件名"""
def __init__(self, config_file_name: str, template_file_name: str) -> None:
self.config_file_name = config_file_name
self.template_file_name = template_file_name
def exists(self) -> bool:
return os.path.exists(self.config_file_name)
async def create(self):
shutil.copyfile(self.template_file_name, self.config_file_name)
async def load(self) -> dict:
module_name = os.path.splitext(os.path.basename(self.config_file_name))[0]
module = importlib.import_module(module_name)
cfg = {}
allowed_types = (int, float, str, bool, list, dict)
for key in dir(module):
if key.startswith('__'):
continue
if not isinstance(getattr(module, key), allowed_types):
continue
cfg[key] = getattr(module, key)
# 从模板模块文件中进行补全
module_name = os.path.splitext(os.path.basename(self.template_file_name))[0]
module = importlib.import_module(module_name)
for key in dir(module):
if key.startswith('__'):
continue
if not isinstance(getattr(module, key), allowed_types):
continue
if key not in cfg:
cfg[key] = getattr(module, key)
return cfg
async def save(self, data: dict):
logging.warning('Python模块配置文件不支持保存')
+53
View File
@@ -0,0 +1,53 @@
from __future__ import annotations
from . import model as file_model
from .impls import pymodule, json as json_file
managers: ConfigManager = []
class ConfigManager:
"""配置文件管理器"""
file: file_model.ConfigFile = None
"""配置文件实例"""
data: dict = None
"""配置数据"""
def __init__(self, cfg_file: file_model.ConfigFile) -> None:
self.file = cfg_file
self.data = {}
async def load_config(self):
self.data = await self.file.load()
async def dump_config(self):
await self.file.save(self.data)
async def load_python_module_config(config_name: str, template_name: str) -> ConfigManager:
"""加载Python模块配置文件"""
cfg_inst = pymodule.PythonModuleConfigFile(
config_name,
template_name
)
cfg_mgr = ConfigManager(cfg_inst)
await cfg_mgr.load_config()
return cfg_mgr
async def load_json_config(config_name: str, template_name: str) -> ConfigManager:
"""加载JSON配置文件"""
cfg_inst = json_file.JSONConfigFile(
config_name,
template_name
)
cfg_mgr = ConfigManager(cfg_inst)
await cfg_mgr.load_config()
return cfg_mgr
+27
View File
@@ -0,0 +1,27 @@
import abc
class ConfigFile(metaclass=abc.ABCMeta):
"""配置文件抽象类"""
config_file_name: str = None
"""配置文件名"""
template_file_name: str = None
"""模板文件名"""
@abc.abstractmethod
def exists(self) -> bool:
pass
@abc.abstractmethod
async def create(self):
pass
@abc.abstractmethod
async def load(self) -> dict:
pass
@abc.abstractmethod
async def save(self, data: dict):
pass
+108
View File
@@ -0,0 +1,108 @@
from __future__ import annotations
import logging
import asyncio
import traceback
import aioconsole
from ..platform import manager as im_mgr
from ..provider.session import sessionmgr as llm_session_mgr
from ..provider.requester import modelmgr as llm_model_mgr
from ..provider.sysprompt import sysprompt as llm_prompt_mgr
from ..provider.tools import toolmgr as llm_tool_mgr
from ..config import manager as config_mgr
from ..audit.center import v2 as center_mgr
from ..command import cmdmgr
from ..plugin import manager as plugin_mgr
from . import pool, controller
from ..pipeline import stagemgr
from ..utils import version as version_mgr, proxy as proxy_mgr
class Application:
im_mgr: im_mgr.PlatformManager = None
cmd_mgr: cmdmgr.CommandManager = None
sess_mgr: llm_session_mgr.SessionManager = None
model_mgr: llm_model_mgr.ModelManager = None
prompt_mgr: llm_prompt_mgr.PromptManager = None
tool_mgr: llm_tool_mgr.ToolManager = None
command_cfg: config_mgr.ConfigManager = None
pipeline_cfg: config_mgr.ConfigManager = None
platform_cfg: config_mgr.ConfigManager = None
provider_cfg: config_mgr.ConfigManager = None
system_cfg: config_mgr.ConfigManager = None
ctr_mgr: center_mgr.V2CenterAPI = None
plugin_mgr: plugin_mgr.PluginManager = None
query_pool: pool.QueryPool = None
ctrl: controller.Controller = None
stage_mgr: stagemgr.StageManager = None
ver_mgr: version_mgr.VersionManager = None
proxy_mgr: proxy_mgr.ProxyManager = None
logger: logging.Logger = None
def __init__(self):
pass
async def initialize(self):
pass
async def run(self):
await self.plugin_mgr.load_plugins()
await self.plugin_mgr.initialize_plugins()
tasks = []
try:
tasks = [
asyncio.create_task(self.im_mgr.run()),
asyncio.create_task(self.ctrl.run())
]
# async def interrupt(tasks):
# await asyncio.sleep(1.5)
# while await aioconsole.ainput("使用 ctrl+c 或 'exit' 退出程序 > ") != 'exit':
# pass
# for task in tasks:
# task.cancel()
# await interrupt(tasks)
import signal
def signal_handler(sig, frame):
for task in tasks:
task.cancel()
self.logger.info("程序退出.")
exit(0)
signal.signal(signal.SIGINT, signal_handler)
await asyncio.gather(*tasks, return_exceptions=True)
except asyncio.CancelledError:
pass
except Exception as e:
self.logger.error(f"应用运行致命异常: {e}")
self.logger.debug(f"Traceback: {traceback.format_exc()}")
+149
View File
@@ -0,0 +1,149 @@
from __future__ import print_function
import os
import sys
from .bootutils import files
from .bootutils import deps
from .bootutils import log
from .bootutils import config
from . import app
from . import pool
from . import controller
from ..pipeline import stagemgr
from ..audit import identifier
from ..provider.session import sessionmgr as llm_session_mgr
from ..provider.requester import modelmgr as llm_model_mgr
from ..provider.sysprompt import sysprompt as llm_prompt_mgr
from ..provider.tools import toolmgr as llm_tool_mgr
from ..platform import manager as im_mgr
from ..command import cmdmgr
from ..plugin import manager as plugin_mgr
from ..audit.center import v2 as center_v2
from ..utils import version, proxy, announce
use_override = False
async def make_app() -> app.Application:
global use_override
generated_files = await files.generate_files()
if generated_files:
print("以下文件不存在,已自动生成,请按需修改配置文件后重启:")
for file in generated_files:
print("-", file)
sys.exit(0)
missing_deps = await deps.check_deps()
if missing_deps:
print("以下依赖包未安装,将自动安装,请完成后重启程序:")
for dep in missing_deps:
print("-", dep)
await deps.install_deps(missing_deps)
sys.exit(0)
qcg_logger = await log.init_logging()
# 生成标识符
identifier.init()
# ========== 加载配置文件 ==========
command_cfg = await config.load_json_config("data/config/command.json", "templates/command.json")
pipeline_cfg = await config.load_json_config("data/config/pipeline.json", "templates/pipeline.json")
platform_cfg = await config.load_json_config("data/config/platform.json", "templates/platform.json")
provider_cfg = await config.load_json_config("data/config/provider.json", "templates/provider.json")
system_cfg = await config.load_json_config("data/config/system.json", "templates/system.json")
# ========== 构建应用实例 ==========
ap = app.Application()
ap.logger = qcg_logger
ap.command_cfg = command_cfg
ap.pipeline_cfg = pipeline_cfg
ap.platform_cfg = platform_cfg
ap.provider_cfg = provider_cfg
ap.system_cfg = system_cfg
proxy_mgr = proxy.ProxyManager(ap)
await proxy_mgr.initialize()
ap.proxy_mgr = proxy_mgr
ver_mgr = version.VersionManager(ap)
await ver_mgr.initialize()
ap.ver_mgr = ver_mgr
center_v2_api = center_v2.V2CenterAPI(
ap,
basic_info={
"host_id": identifier.identifier["host_id"],
"instance_id": identifier.identifier["instance_id"],
"semantic_version": ver_mgr.get_current_version(),
"platform": sys.platform,
},
runtime_info={
"admin_id": "{}".format(system_cfg.data["admin-sessions"]),
"msg_source": str([
adapter_cfg['adapter'] if 'adapter' in adapter_cfg else 'unknown'
for adapter_cfg in platform_cfg.data['platform-adapters'] if adapter_cfg['enable']
]),
},
)
ap.ctr_mgr = center_v2_api
# 发送公告
ann_mgr = announce.AnnouncementManager(ap)
await ann_mgr.show_announcements()
ap.query_pool = pool.QueryPool()
await ap.ver_mgr.show_version_update()
plugin_mgr_inst = plugin_mgr.PluginManager(ap)
await plugin_mgr_inst.initialize()
ap.plugin_mgr = plugin_mgr_inst
cmd_mgr_inst = cmdmgr.CommandManager(ap)
await cmd_mgr_inst.initialize()
ap.cmd_mgr = cmd_mgr_inst
llm_model_mgr_inst = llm_model_mgr.ModelManager(ap)
await llm_model_mgr_inst.initialize()
ap.model_mgr = llm_model_mgr_inst
llm_session_mgr_inst = llm_session_mgr.SessionManager(ap)
await llm_session_mgr_inst.initialize()
ap.sess_mgr = llm_session_mgr_inst
llm_prompt_mgr_inst = llm_prompt_mgr.PromptManager(ap)
await llm_prompt_mgr_inst.initialize()
ap.prompt_mgr = llm_prompt_mgr_inst
llm_tool_mgr_inst = llm_tool_mgr.ToolManager(ap)
await llm_tool_mgr_inst.initialize()
ap.tool_mgr = llm_tool_mgr_inst
im_mgr_inst = im_mgr.PlatformManager(ap=ap)
await im_mgr_inst.initialize()
ap.im_mgr = im_mgr_inst
stage_mgr = stagemgr.StageManager(ap)
await stage_mgr.initialize()
ap.stage_mgr = stage_mgr
ctrl = controller.Controller(ap)
ap.ctrl = ctrl
await ap.initialize()
return ap
async def main():
app_inst = await make_app()
await app_inst.run()
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
import json
from ...config import manager as config_mgr
from ...config.impls import pymodule
load_python_module_config = config_mgr.load_python_module_config
load_json_config = config_mgr.load_json_config
async def override_config_manager(cfg_mgr: config_mgr.ConfigManager) -> list[str]:
override_json = json.load(open("override.json", "r", encoding="utf-8"))
overrided = []
config = cfg_mgr.data
for key in override_json:
if key in config:
config[key] = override_json[key]
overrided.append(key)
return overrided
+32
View File
@@ -0,0 +1,32 @@
import pip
required_deps = {
"requests": "requests",
"openai": "openai",
"colorlog": "colorlog",
"mirai": "yiri-mirai-rc",
"PIL": "pillow",
"nakuru": "nakuru-project-idk",
"CallingGPT": "CallingGPT",
"tiktoken": "tiktoken",
"yaml": "pyyaml",
"aiohttp": "aiohttp",
}
async def check_deps() -> list[str]:
global required_deps
missing_deps = []
for dep in required_deps:
try:
__import__(dep)
except ImportError:
missing_deps.append(dep)
return missing_deps
async def install_deps(deps: list[str]):
global required_deps
for dep in deps:
pip.main(["install", required_deps[dep]])
+43
View File
@@ -0,0 +1,43 @@
from __future__ import annotations
import os
import shutil
import sys
required_files = {
"plugins/__init__.py": "templates/__init__.py",
"plugins/plugins.json": "templates/plugin-settings.json",
"data/config/command.json": "templates/command.json",
"data/config/pipeline.json": "templates/pipeline.json",
"data/config/platform.json": "templates/platform.json",
"data/config/provider.json": "templates/provider.json",
"data/config/system.json": "templates/system.json",
"data/config/sensitive-words.json": "templates/sensitive-words.json",
"data/scenario/default.json": "templates/scenario-template.json",
}
required_paths = [
"temp",
"data",
"data/prompts",
"data/scenario",
"data/logs",
"data/config",
"plugins"
]
async def generate_files() -> list[str]:
global required_files, required_paths
for required_paths in required_paths:
if not os.path.exists(required_paths):
os.mkdir(required_paths)
generated_files = []
for file in required_files:
if not os.path.exists(file):
shutil.copyfile(required_files[file], file)
generated_files.append(file)
return generated_files
+57
View File
@@ -0,0 +1,57 @@
import logging
import os
import sys
import time
import colorlog
log_colors_config = {
"DEBUG": "green", # cyan white
"INFO": "white",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "cyan",
}
async def init_logging() -> logging.Logger:
level = logging.INFO
if "DEBUG" in os.environ and os.environ["DEBUG"] in ["true", "1"]:
level = logging.DEBUG
log_file_name = "data/logs/qcg-%s.log" % time.strftime(
"%Y-%m-%d-%H-%M-%S", time.localtime()
)
qcg_logger = logging.getLogger("qcg")
qcg_logger.setLevel(level)
color_formatter = colorlog.ColoredFormatter(
fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
log_colors=log_colors_config,
)
stream_handler = logging.StreamHandler(sys.stdout)
log_handlers: logging.Handler = [stream_handler, logging.FileHandler(log_file_name)]
for handler in log_handlers:
handler.setLevel(level)
handler.setFormatter(color_formatter)
qcg_logger.addHandler(handler)
qcg_logger.debug("日志初始化完成,日志级别:%s" % level)
logging.basicConfig(
level=logging.INFO, # 设置日志输出格式
format="[DEPR][%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s",
# 日志输出的格式
# -8表示占位符,让输出左对齐,输出长度都为8位
datefmt="%Y-%m-%d %H:%M:%S", # 时间输出的格式
handlers=[logging.NullHandler()],
)
return qcg_logger
+161
View File
@@ -0,0 +1,161 @@
from __future__ import annotations
import asyncio
import typing
import traceback
from . import app, entities
from ..pipeline import entities as pipeline_entities
from ..plugin import events
class Controller:
"""总控制器
"""
ap: app.Application
semaphore: asyncio.Semaphore = None
"""请求并发控制信号量"""
def __init__(self, ap: app.Application):
self.ap = ap
self.semaphore = asyncio.Semaphore(self.ap.system_cfg.data['pipeline-concurrency'])
async def consumer(self):
"""事件处理循环
"""
try:
while True:
selected_query: entities.Query = None
# 取请求
async with self.ap.query_pool:
queries: list[entities.Query] = self.ap.query_pool.queries
for query in queries:
session = await self.ap.sess_mgr.get_session(query)
self.ap.logger.debug(f"Checking query {query} session {session}")
if not session.semaphore.locked():
selected_query = query
await session.semaphore.acquire()
break
if selected_query: # 找到了
queries.remove(selected_query)
else: # 没找到 说明:没有请求 或者 所有query对应的session都已达到并发上限
await self.ap.query_pool.condition.wait()
continue
if selected_query:
async def _process_query(selected_query):
async with self.semaphore: # 总并发上限
await self.process_query(selected_query)
async with self.ap.query_pool:
(await self.ap.sess_mgr.get_session(selected_query)).semaphore.release()
# 通知其他协程,有新的请求可以处理了
self.ap.query_pool.condition.notify_all()
asyncio.create_task(_process_query(selected_query))
except Exception as e:
# traceback.print_exc()
self.ap.logger.error(f"控制器循环出错: {e}")
self.ap.logger.debug(f"Traceback: {traceback.format_exc()}")
async def _check_output(self, query: entities.Query, result: pipeline_entities.StageProcessResult):
"""检查输出
"""
if result.user_notice:
await self.ap.im_mgr.send(
query.message_event,
result.user_notice,
query.adapter
)
if result.debug_notice:
self.ap.logger.debug(result.debug_notice)
if result.console_notice:
self.ap.logger.info(result.console_notice)
if result.error_notice:
self.ap.logger.error(result.error_notice)
async def _execute_from_stage(
self,
stage_index: int,
query: entities.Query,
):
"""从指定阶段开始执行
如何看懂这里为什么这么写?
去问 GPT-4:
Q1: 现在有一个责任链,其中有多个stage,query对象在其中传递,stage.process可能返回Result也有可能返回typing.AsyncGenerator[Result, None]
如果返回的是生成器,需要挨个生成result,检查是否result中是否要求继续,如果要求继续就进行下一个stage。如果此次生成器产生的result处理完了,就继续生成下一个result,
调用后续的stage,直到该生成器全部生成完。责任链中可能有多个stage会返回生成器
Q2: 不是这样的,你可能理解有误。如果我们责任链上有这些Stage:
A B C D E F G
如果所有的stage都返回Result,且所有Result都要求继续,那么执行顺序是:
A B C D E F G
现在假设C返回的是AsyncGenerator,那么执行顺序是:
A B C D E F G C D E F G C D E F G ...
Q3: 但是如果不止一个stage会返回生成器呢?
"""
i = stage_index
while i < len(self.ap.stage_mgr.stage_containers):
stage_container = self.ap.stage_mgr.stage_containers[i]
result = stage_container.inst.process(query, stage_container.inst_name)
if isinstance(result, typing.Coroutine):
result = await result
if isinstance(result, pipeline_entities.StageProcessResult): # 直接返回结果
self.ap.logger.debug(f"Stage {stage_container.inst_name} processed query {query} res {result}")
await self._check_output(query, result)
if result.result_type == pipeline_entities.ResultType.INTERRUPT:
self.ap.logger.debug(f"Stage {stage_container.inst_name} interrupted query {query}")
break
elif result.result_type == pipeline_entities.ResultType.CONTINUE:
query = result.new_query
elif isinstance(result, typing.AsyncGenerator): # 生成器
self.ap.logger.debug(f"Stage {stage_container.inst_name} processed query {query} gen")
async for sub_result in result:
self.ap.logger.debug(f"Stage {stage_container.inst_name} processed query {query} res {sub_result}")
await self._check_output(query, sub_result)
if sub_result.result_type == pipeline_entities.ResultType.INTERRUPT:
self.ap.logger.debug(f"Stage {stage_container.inst_name} interrupted query {query}")
break
elif sub_result.result_type == pipeline_entities.ResultType.CONTINUE:
query = sub_result.new_query
await self._execute_from_stage(i + 1, query)
break
i += 1
async def process_query(self, query: entities.Query):
"""处理请求
"""
self.ap.logger.debug(f"Processing query {query}")
try:
await self._execute_from_stage(0, query)
except Exception as e:
self.ap.logger.error(f"处理请求时出错 query_id={query.query_id}: {e}")
self.ap.logger.debug(f"Traceback: {traceback.format_exc()}")
# traceback.print_exc()
finally:
self.ap.logger.debug(f"Query {query} processed")
async def run(self):
"""运行控制器
"""
await self.consumer()
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
import enum
import typing
import datetime
import asyncio
import pydantic
import mirai
from ..provider import entities as llm_entities
from ..provider.requester import entities
from ..provider.sysprompt import entities as sysprompt_entities
from ..provider.tools import entities as tools_entities
from ..platform import adapter as msadapter
class LauncherTypes(enum.Enum):
PERSON = 'person'
"""私聊"""
GROUP = 'group'
"""群聊"""
class Query(pydantic.BaseModel):
"""一次请求的信息封装"""
query_id: int
"""请求ID,添加进请求池时生成"""
launcher_type: LauncherTypes
"""会话类型,platform设置"""
launcher_id: int
"""会话IDplatform设置"""
sender_id: int
"""发送者IDplatform设置"""
message_event: mirai.MessageEvent
"""事件,platform收到的事件"""
message_chain: mirai.MessageChain
"""消息链,platform收到的消息链"""
adapter: msadapter.MessageSourceAdapter
"""适配器对象"""
session: typing.Optional[Session] = None
"""会话对象,由前置处理器设置"""
messages: typing.Optional[list[llm_entities.Message]] = []
"""历史消息列表,由前置处理器设置"""
prompt: typing.Optional[sysprompt_entities.Prompt] = None
"""情景预设内容,由前置处理器设置"""
user_message: typing.Optional[llm_entities.Message] = None
"""此次请求的用户消息对象,由前置处理器设置"""
use_model: typing.Optional[entities.LLMModelInfo] = None
"""使用的模型,由前置处理器设置"""
use_funcs: typing.Optional[list[tools_entities.LLMFunction]] = None
"""使用的函数,由前置处理器设置"""
resp_messages: typing.Optional[list[llm_entities.Message]] = []
"""由provider生成的回复消息对象列表"""
resp_message_chain: typing.Optional[mirai.MessageChain] = None
"""回复消息链,从resp_messages包装而得"""
class Config:
arbitrary_types_allowed = True
class Conversation(pydantic.BaseModel):
"""对话"""
prompt: sysprompt_entities.Prompt
messages: list[llm_entities.Message]
create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
update_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
use_model: entities.LLMModelInfo
use_funcs: typing.Optional[list[tools_entities.LLMFunction]]
class Session(pydantic.BaseModel):
"""会话"""
launcher_type: LauncherTypes
launcher_id: int
sender_id: typing.Optional[int] = 0
use_prompt_name: typing.Optional[str] = 'default'
using_conversation: typing.Optional[Conversation] = None
conversations: typing.Optional[list[Conversation]] = []
create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
update_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
semaphore: typing.Optional[asyncio.Semaphore] = None
class Config:
arbitrary_types_allowed = True
+57
View File
@@ -0,0 +1,57 @@
from __future__ import annotations
import asyncio
import mirai
from . import entities
from ..platform import adapter as msadapter
class QueryPool:
query_id_counter: int = 0
pool_lock: asyncio.Lock
queries: list[entities.Query]
condition: asyncio.Condition
def __init__(self):
self.query_id_counter = 0
self.pool_lock = asyncio.Lock()
self.queries = []
self.condition = asyncio.Condition(self.pool_lock)
async def add_query(
self,
launcher_type: entities.LauncherTypes,
launcher_id: int,
sender_id: int,
message_event: mirai.MessageEvent,
message_chain: mirai.MessageChain,
adapter: msadapter.MessageSourceAdapter
) -> entities.Query:
async with self.condition:
query = entities.Query(
query_id=self.query_id_counter,
launcher_type=launcher_type,
launcher_id=launcher_id,
sender_id=sender_id,
message_event=message_event,
message_chain=message_chain,
resp_messages=[],
resp_message_chain=None,
adapter=adapter
)
self.queries.append(query)
self.query_id_counter += 1
self.condition.notify_all()
async def __aenter__(self):
await self.pool_lock.acquire()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
self.pool_lock.release()
-299
View File
@@ -1,299 +0,0 @@
import hashlib
import json
import logging
import time
from sqlite3 import Cursor
import sqlite3
import pkg.utils.context
# 数据库管理
# 为其他模块提供数据库操作接口
class DatabaseManager:
conn = None
cursor = None
def __init__(self):
self.reconnect()
pkg.utils.context.set_database_manager(self)
# 连接到数据库文件
def reconnect(self):
self.conn = sqlite3.connect('database.db', check_same_thread=False)
self.cursor = self.conn.cursor()
def close(self):
self.conn.close()
def execute(self, *args, **kwargs) -> Cursor:
# logging.debug('SQL: {}'.format(sql))
c = self.cursor.execute(*args, **kwargs)
self.conn.commit()
return c
# 初始化数据库的函数
def initialize_database(self):
self.execute("""
create table if not exists `sessions` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` varchar(255) not null,
`type` varchar(255) not null,
`number` bigint not null,
`create_timestamp` bigint not null,
`last_interact_timestamp` bigint not null,
`status` varchar(255) not null default 'on_going',
`prompt` text not null
)
""")
self.execute("""
create table if not exists `account_fee`(
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`key_md5` varchar(255) not null,
`timestamp` bigint not null,
`fee` DECIMAL(12,6) not null
)
""")
self.execute("""
create table if not exists `account_usage`(
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`json` text not null
)
""")
print('Database initialized.')
# session持久化
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
last_interact_timestamp: int, prompt: str):
# 检查是否已经有了此name和create_timestamp的session
# 如果有,就更新prompt和last_interact_timestamp
# 如果没有,就插入一条新的记录
self.execute("""
select count(*) from `sessions` where `type` = '{}' and `number` = {} and `create_timestamp` = {}
""".format(subject_type, subject_number, create_timestamp))
count = self.cursor.fetchone()[0]
if count == 0:
sql = """
insert into `sessions` (`name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`)
values (?, ?, ?, ?, ?, ?)
"""
self.execute(sql,
("{}_{}".format(subject_type, subject_number), subject_type, subject_number, create_timestamp,
last_interact_timestamp, prompt))
else:
sql = """
update `sessions` set `last_interact_timestamp` = ?, `prompt` = ?
where `type` = ? and `number` = ? and `create_timestamp` = ?
"""
self.execute(sql, (last_interact_timestamp, prompt, subject_type,
subject_number, create_timestamp))
# 显式关闭一个session
def explicit_close_session(self, session_name: str, create_timestamp: int):
self.execute("""
update `sessions` set `status` = 'explicitly_closed' where `name` = '{}' and `create_timestamp` = {}
""".format(session_name, create_timestamp))
def set_session_ongoing(self, session_name: str, create_timestamp: int):
self.execute("""
update `sessions` set `status` = 'on_going' where `name` = '{}' and `create_timestamp` = {}
""".format(session_name, create_timestamp))
# 设置session为过期
def set_session_expired(self, session_name: str, create_timestamp: int):
self.execute("""
update `sessions` set `status` = 'expired' where `name` = '{}' and `create_timestamp` = {}
""".format(session_name, create_timestamp))
# 从数据库加载还没过期的session数据
def load_valid_sessions(self) -> dict:
# 从数据库中加载所有还没过期的session
config = pkg.utils.context.get_config()
self.execute("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
from `sessions` where `last_interact_timestamp` > {}
""".format(int(time.time()) - config.session_expire_time))
results = self.cursor.fetchall()
sessions = {}
for result in results:
session_name = result[0]
subject_type = result[1]
subject_number = result[2]
create_timestamp = result[3]
last_interact_timestamp = result[4]
prompt = result[5]
status = result[6]
# 当且仅当最后一个该对象的会话是on_going状态时,才会被加载
if status == 'on_going':
sessions[session_name] = {
'subject_type': subject_type,
'subject_number': subject_number,
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt
}
else:
if session_name in sessions:
del sessions[session_name]
return sessions
# 获取此session_name前一个session的数据
def last_session(self, session_name: str, cursor_timestamp: int):
self.execute("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
from `sessions` where `name` = '{}' and `last_interact_timestamp` < {} order by `last_interact_timestamp` desc
limit 1
""".format(session_name, cursor_timestamp))
results = self.cursor.fetchall()
if len(results) == 0:
return None
result = results[0]
session_name = result[0]
subject_type = result[1]
subject_number = result[2]
create_timestamp = result[3]
last_interact_timestamp = result[4]
prompt = result[5]
status = result[6]
return {
'subject_type': subject_type,
'subject_number': subject_number,
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt
}
# 获取此session_name后一个session的数据
def next_session(self, session_name: str, cursor_timestamp: int):
self.execute("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
from `sessions` where `name` = '{}' and `last_interact_timestamp` > {} order by `last_interact_timestamp` asc
limit 1
""".format(session_name, cursor_timestamp))
results = self.cursor.fetchall()
if len(results) == 0:
return None
result = results[0]
session_name = result[0]
subject_type = result[1]
subject_number = result[2]
create_timestamp = result[3]
last_interact_timestamp = result[4]
prompt = result[5]
status = result[6]
return {
'subject_type': subject_type,
'subject_number': subject_number,
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt
}
# 列出与某个对象的所有对话session
def list_history(self, session_name: str, capacity: int, page: int):
self.execute("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
from `sessions` where `name` = '{}' order by `last_interact_timestamp` desc limit {} offset {}
""".format(session_name, capacity, capacity * page))
results = self.cursor.fetchall()
sessions = []
for result in results:
session_name = result[0]
subject_type = result[1]
subject_number = result[2]
create_timestamp = result[3]
last_interact_timestamp = result[4]
prompt = result[5]
status = result[6]
sessions.append({
'subject_type': subject_type,
'subject_number': subject_number,
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt
})
return sessions
# 将apikey的使用量存进数据库
def dump_api_key_usage(self, api_keys: dict, usage: dict):
logging.debug('dumping api key usage...')
logging.debug(api_keys)
logging.debug(usage)
for api_key in api_keys:
# 计算key的md5值
key_md5 = hashlib.md5(api_keys[api_key].encode('utf-8')).hexdigest()
# 获取使用量
usage_count = 0
if key_md5 in usage:
usage_count = usage[key_md5]
# 将使用量存进数据库
# 先检查是否已存在
self.execute("""
select count(*) from `api_key_usage` where `key_md5` = '{}'""".format(key_md5))
result = self.cursor.fetchone()
if result[0] == 0:
# 不存在则插入
self.execute("""
insert into `api_key_usage` (`key_md5`, `usage`,`timestamp`) values ('{}', {}, {})
""".format(key_md5, usage_count, int(time.time())))
else:
# 存在则更新,timestamp设置为当前
self.execute("""
update `api_key_usage` set `usage` = {}, `timestamp` = {} where `key_md5` = '{}'
""".format(usage_count, int(time.time()), key_md5))
def load_api_key_usage(self):
self.execute("""
select `key_md5`, `usage` from `api_key_usage`
""")
results = self.cursor.fetchall()
usage = {}
for result in results:
key_md5 = result[0]
usage_count = result[1]
usage[key_md5] = usage_count
return usage
def dump_usage_json(self, usage: dict):
json_str = json.dumps(usage)
self.execute("""
select count(*) from `account_usage`""")
result = self.cursor.fetchone()
if result[0] == 0:
# 不存在则插入
self.execute("""
insert into `account_usage` (`json`) values ('{}')
""".format(json_str))
else:
# 存在则更新
self.execute("""
update `account_usage` set `json` = '{}' where `id` = 1
""".format(json_str))
def load_usage_json(self):
self.execute("""
select `json` from `account_usage` order by id desc limit 1
""")
result = self.cursor.fetchone()
if result is None:
return None
else:
return result[0]
-74
View File
@@ -1,74 +0,0 @@
# 多情景预设值管理
__current__ = "default"
__prompts_from_files__ = {}
def read_prompt_from_file() -> str:
"""从文件读取预设值"""
# 读取prompts/目录下的所有文件,以文件名为键,文件内容为值
# 保存在__prompts_from_files__中
global __prompts_from_files__
import os
__prompts_from_files__ = {}
for file in os.listdir("prompts"):
with open(os.path.join("prompts", file), encoding="utf-8") as f:
__prompts_from_files__[file] = f.read()
def get_prompt_dict() -> dict:
"""获取预设值字典"""
import config
default_prompt = config.default_prompt
if type(default_prompt) == str:
default_prompt = {"default": default_prompt}
elif type(default_prompt) == dict:
pass
else:
raise TypeError("default_prompt must be str or dict")
# 将文件中的预设值合并到default_prompt中
for key in __prompts_from_files__:
default_prompt[key] = __prompts_from_files__[key]
return default_prompt
def set_current(name):
global __current__
for key in get_prompt_dict():
if key.lower().startswith(name.lower()):
__current__ = key
return
raise KeyError("未找到情景预设: " + name)
def get_current():
global __current__
return __current__
def set_to_default():
global __current__
default_dict = get_prompt_dict()
if "default" in default_dict:
__current__ = "default"
else:
__current__ = list(default_dict.keys())[0]
def get_prompt(name: str = None) -> str:
"""获取预设值"""
if name is None:
name = get_current()
default_dict = get_prompt_dict()
for key in default_dict:
if key.lower().startswith(name.lower()):
return default_dict[key]
raise KeyError("未找到情景预设: " + name)
-84
View File
@@ -1,84 +0,0 @@
# 此模块提供了维护api-key的各种功能
import hashlib
import logging
import pkg.plugin.host as plugin_host
import pkg.plugin.models as plugin_models
class KeysManager:
api_key = {}
# api-key的使用量
# 其中键为api-key的md5值,值为使用量
using_key = ""
alerted = []
# 在此list中的都是经超额报错标记过的api-key
# 记录的是key值,仅在运行时有效
exceeded = []
def get_using_key(self):
return self.using_key
def get_using_key_md5(self):
return hashlib.md5(self.using_key.encode('utf-8')).hexdigest()
def __init__(self, api_key):
# if hasattr(config, 'api_key_usage_threshold'):
# self.api_key_usage_threshold = config.api_key_usage_threshold
if type(api_key) is dict:
self.api_key = api_key
elif type(api_key) is str:
self.api_key = {
"default": api_key
}
elif type(api_key) is list:
for i in range(len(api_key)):
self.api_key[str(i)] = api_key[i]
# 从usage中删除未加载的api-key的记录
# 不删了,也许会运行时添加曾经有记录的api-key
self.auto_switch()
# 根据tested自动切换到可用的api-key
# 返回是否切换成功, 切换后的api-key的别名
def auto_switch(self) -> (bool, str):
for key_name in self.api_key:
if self.api_key[key_name] not in self.exceeded:
self.using_key = self.api_key[key_name]
logging.info("使用api-key:" + key_name)
# 触发插件事件
args = {
"key_name": key_name,
"key_list": self.api_key.keys()
}
_ = plugin_host.emit(plugin_models.KeySwitched, **args)
return True, key_name
self.using_key = list(self.api_key.values())[0]
logging.info("使用api-key:" + list(self.api_key.keys())[0])
return False, ""
def add(self, key_name, key):
self.api_key[key_name] = key
# 设置当前使用的api-key使用量超限
# 这是在尝试调用api时发生超限异常时调用的
def set_current_exceeded(self):
# md5 = hashlib.md5(self.using_key.encode('utf-8')).hexdigest()
# self.usage[md5] = self.api_key_usage_threshold
# self.fee[md5] = self.api_key_fee_threshold
self.exceeded.append(self.using_key)
def get_key_name(self, api_key):
"""根据api-key获取其别名"""
for key_name in self.api_key:
if self.api_key[key_name] == api_key:
return key_name
return ""
-75
View File
@@ -1,75 +0,0 @@
import logging
import openai
import pkg.openai.keymgr
import pkg.utils.context
import pkg.audit.gatherer
from pkg.openai.modelmgr import ModelRequest, create_openai_model_request
# 为其他模块提供与OpenAI交互的接口
class OpenAIInteract:
api_params = {}
key_mgr: pkg.openai.keymgr.KeysManager = None
audit_mgr: pkg.audit.gatherer.DataGatherer = None
default_image_api_params = {
"size": "256x256",
}
def __init__(self, api_key: str):
# self.api_key = api_key
self.key_mgr = pkg.openai.keymgr.KeysManager(api_key)
self.audit_mgr = pkg.audit.gatherer.DataGatherer()
logging.info("文字总使用量:%d", self.audit_mgr.get_total_text_length())
openai.api_key = self.key_mgr.get_using_key()
pkg.utils.context.set_openai_manager(self)
# 请求OpenAI Completion
def request_completion(self, prompts):
config = pkg.utils.context.get_config()
# 根据模型选择使用的接口
ai: ModelRequest = create_openai_model_request(
config.completion_api_params['model'],
'user',
config.openai_config["http_proxy"] if "http_proxy" in config.openai_config else None
)
ai.request(
prompts,
**config.completion_api_params
)
response = ai.get_response()
logging.debug("OpenAI response: %s", response)
if 'model' in config.completion_api_params:
self.audit_mgr.report_text_model_usage(config.completion_api_params['model'],
ai.get_total_tokens())
elif 'engine' in config.completion_api_params:
self.audit_mgr.report_text_model_usage(config.completion_api_params['engine'],
response['usage']['total_tokens'])
return ai.get_message()
def request_image(self, prompt):
config = pkg.utils.context.get_config()
params = config.image_api_params if hasattr(config, "image_api_params") else self.default_image_api_params
response = openai.Image.create(
prompt=prompt,
n=1,
**params
)
self.audit_mgr.report_image_model_usage(params['size'])
return response
-152
View File
@@ -1,152 +0,0 @@
# 提供与模型交互的抽象接口
import openai, logging, threading, asyncio
COMPLETION_MODELS = {
'text-davinci-003',
'text-davinci-002',
'code-davinci-002',
'code-cushman-001',
'text-curie-001',
'text-babbage-001',
'text-ada-001',
}
CHAT_COMPLETION_MODELS = {
'gpt-3.5-turbo',
'gpt-3.5-turbo-0301',
}
EDIT_MODELS = {
}
IMAGE_MODELS = {
}
class ModelRequest():
"""GPT父类"""
can_chat = False
runtime:threading.Thread = None
ret = ""
proxy:str = None
def __init__(self, model_name, user_name, request_fun, http_proxy:str = None):
self.model_name = model_name
self.user_name = user_name
self.request_fun = request_fun
if http_proxy != None:
self.proxy = http_proxy
openai.proxy = self.proxy
async def __a_request__(self, **kwargs):
self.ret = await self.request_fun(**kwargs)
def request(self, **kwargs):
if self.proxy != None: #异步请求
loop = asyncio.new_event_loop()
self.runtime = threading.Thread(
target=loop.run_until_complete,
args=(self.__a_request__(**kwargs),)
)
self.runtime.start()
else: #同步请求
self.ret = self.request_fun(**kwargs)
def __msg_handle__(self, msg):
"""将prompt dict转换成接口需要的格式"""
return msg
def ret_handle(self):
'''
API消息返回处理函数
若重写该方法,应检查异步线程状态,或在需要检查处super该方法
'''
if self.runtime != None and isinstance(self.runtime, threading.Thread):
self.runtime.join()
return
def get_total_tokens(self):
try:
return self.ret['usage']['total_tokens']
except Exception:
return 0
def get_message(self):
return self.message
def get_response(self):
return self.ret
class ChatCompletionModel(ModelRequest):
"""ChatCompletion类模型"""
Chat_role = ['system', 'user', 'assistant']
def __init__(self, model_name, user_name, http_proxy:str = None, **kwargs):
if http_proxy == None:
request_fun = openai.ChatCompletion.create
else:
request_fun = openai.ChatCompletion.acreate
self.can_chat = True
super().__init__(model_name, user_name, request_fun, http_proxy, **kwargs)
def request(self, prompts, **kwargs):
prompts = self.__msg_handle__(prompts)
kwargs['messages'] = prompts
super().request(**kwargs)
self.ret_handle()
def __msg_handle__(self, msgs):
temp_msgs = []
# 把msgs拷贝进temp_msgs
for msg in msgs:
temp_msgs.append(msg.copy())
return temp_msgs
def get_message(self):
return self.ret["choices"][0]["message"]['content'] #需要时直接加载加快请求速度,降低内存消耗
class CompletionModel(ModelRequest):
"""Completion类模型"""
def __init__(self, model_name, user_name, http_proxy:str = None, **kwargs):
if http_proxy == None:
request_fun = openai.Completion.create
else:
request_fun = openai.Completion.acreate
super().__init__(model_name, user_name, request_fun, http_proxy, **kwargs)
def request(self, prompts, **kwargs):
prompts = self.__msg_handle__(prompts)
kwargs['prompt'] = prompts
super().request(**kwargs)
self.ret_handle()
def __msg_handle__(self, msgs):
prompt = ''
for msg in msgs:
prompt = prompt + "{}: {}\n".format(msg['role'], msg['content'])
# for msg in msgs:
# if msg['role'] == 'assistant':
# prompt = prompt + "{}\n".format(msg['content'])
# else:
# prompt = prompt + "{}:{}\n".format(msg['role'] , msg['content'])
prompt = prompt + "assistant: "
return prompt
def get_message(self):
return self.ret["choices"][0]["text"]
def create_openai_model_request(model_name: str, user_name: str = 'user', http_proxy:str = None) -> ModelRequest:
"""使用给定的模型名称创建模型请求对象"""
if model_name in CHAT_COMPLETION_MODELS:
model = ChatCompletionModel(model_name, user_name, http_proxy)
elif model_name in COMPLETION_MODELS:
model = CompletionModel(model_name, user_name, http_proxy)
else :
log = "找不到模型[{}],请检查配置文件".format(model_name)
logging.error(log)
raise IndexError(log)
logging.debug("使用接口[{}]创建模型请求[{}]".format(model.__class__.__name__, model_name))
return model
-28
View File
@@ -1,28 +0,0 @@
# 计费模块
# 已弃用 https://github.com/RockChinQ/QChatGPT/issues/81
import logging
pricing = {
"base": { # 文字模型单位是1000字符
"text-davinci-003": 0.02,
},
"image": {
"256x256": 0.016,
"512x512": 0.018,
"1024x1024": 0.02,
}
}
def language_base_price(model, text):
salt_rate = 0.93
length = ((len(text.encode('utf-8')) - len(text)) / 2 + len(text)) * salt_rate
logging.debug("text length: %d" % length)
return pricing["base"][model] * length / 1000
def image_price(size):
logging.debug("image size: %s" % size)
return pricing["image"][size]
-356
View File
@@ -1,356 +0,0 @@
import logging
import threading
import time
import json
import pkg.openai.manager
import pkg.openai.modelmgr
import pkg.database.manager
import pkg.utils.context
import pkg.plugin.host as plugin_host
import pkg.plugin.models as plugin_models
# 运行时保存的所有session
sessions = {}
class SessionOfflineStatus:
ON_GOING = 'on_going'
EXPLICITLY_CLOSED = 'explicitly_closed'
# 重置session.prompt
def reset_session_prompt(session_name, prompt):
# 备份原始数据
bak_path = 'logs/{}-{}.bak'.format(
session_name,
time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
)
f = open(bak_path, 'w+')
f.write(prompt)
f.close()
# 生成新数据
config = pkg.utils.context.get_config()
prompt = [
{
'role': 'system',
'content': config.default_prompt['default']
}
]
# 警告
logging.warning(
"""
用户[{}]的数据已被重置,有可能是因为数据版本过旧或存储错误
原始数据将备份在:
{}""".format(session_name, bak_path)
)
return prompt
# 从数据加载session
def load_sessions():
global sessions
db_inst = pkg.utils.context.get_database_manager()
session_data = db_inst.load_valid_sessions()
for session_name in session_data:
logging.info('加载session: {}'.format(session_name))
temp_session = Session(session_name)
temp_session.name = session_name
temp_session.create_timestamp = session_data[session_name]['create_timestamp']
temp_session.last_interact_timestamp = session_data[session_name]['last_interact_timestamp']
try:
temp_session.prompt = json.loads(session_data[session_name]['prompt'])
except Exception:
temp_session.prompt = reset_session_prompt(session_name, session_data[session_name]['prompt'])
temp_session.persistence()
sessions[session_name] = temp_session
# 获取指定名称的session,如果不存在则创建一个新的
def get_session(session_name: str):
global sessions
if session_name not in sessions:
sessions[session_name] = Session(session_name)
return sessions[session_name]
def dump_session(session_name: str):
global sessions
if session_name in sessions:
assert isinstance(sessions[session_name], Session)
sessions[session_name].persistence()
del sessions[session_name]
# 通用的OpenAI API交互session
# session内部保留了对话的上下文,
# 收到用户消息后,将上下文提交给OpenAI API生成回复
class Session:
name = ''
prompt = []
create_timestamp = 0
last_interact_timestamp = 0
just_switched_to_exist_session = False
response_lock = None
# 加锁
def acquire_response_lock(self):
logging.debug('{},lock acquire,{}'.format(self.name, self.response_lock))
self.response_lock.acquire()
logging.debug('{},lock acquire successfully,{}'.format(self.name, self.response_lock))
# 释放锁
def release_response_lock(self):
if self.response_lock.locked():
logging.debug('{},lock release,{}'.format(self.name, self.response_lock))
self.response_lock.release()
logging.debug('{},lock release successfully,{}'.format(self.name, self.response_lock))
# 从配置文件获取会话预设信息
def get_default_prompt(self, use_default: str=None):
config = pkg.utils.context.get_config()
import pkg.openai.dprompt as dprompt
if use_default is None:
current_default_prompt = dprompt.get_prompt(dprompt.get_current())
else:
current_default_prompt = dprompt.get_prompt(use_default)
return [
{
'role': 'user',
'content': current_default_prompt
},{
'role': 'assistant',
'content': 'ok'
}
]
def __init__(self, name: str):
self.name = name
self.create_timestamp = int(time.time())
self.last_interact_timestamp = int(time.time())
self.schedule()
self.response_lock = threading.Lock()
self.prompt = self.get_default_prompt()
# 设定检查session最后一次对话是否超过过期时间的计时器
def schedule(self):
threading.Thread(target=self.expire_check_timer_loop, args=(self.create_timestamp,)).start()
# 检查session是否已经过期
def expire_check_timer_loop(self, create_timestamp: int):
global sessions
while True:
time.sleep(60)
# 不是此session已更换,退出
if self.create_timestamp != create_timestamp or self not in sessions.values():
return
config = pkg.utils.context.get_config()
if int(time.time()) - self.last_interact_timestamp > config.session_expire_time:
logging.info('session {} 已过期'.format(self.name))
# 触发插件事件
args = {
'session_name': self.name,
'session': self,
'session_expire_time': config.session_expire_time
}
event = pkg.plugin.host.emit(plugin_models.SessionExpired, **args)
if event.is_prevented_default():
return
self.reset(expired=True, schedule_new=False)
# 删除此session
del sessions[self.name]
return
# 请求回复
# 这个函数是阻塞的
def append(self, text: str) -> str:
self.last_interact_timestamp = int(time.time())
# 触发插件事件
if self.prompt == self.get_default_prompt():
args = {
'session_name': self.name,
'session': self,
'default_prompt': self.prompt,
}
event = pkg.plugin.host.emit(plugin_models.SessionFirstMessageReceived, **args)
if event.is_prevented_default():
return None
config = pkg.utils.context.get_config()
max_length = config.prompt_submit_length if hasattr(config, "prompt_submit_length") else 1024
# 向API请求补全
message = pkg.utils.context.get_openai_manager().request_completion(
self.cut_out(text, max_length),
)
# 成功获取,处理回复
res_test = message
res_ans = res_test
# 去除开头可能的提示
res_ans_spt = res_test.split("\n\n")
if len(res_ans_spt) > 1:
del (res_ans_spt[0])
res_ans = '\n\n'.join(res_ans_spt)
# 将此次对话的双方内容加入到prompt中
self.prompt.append({'role':'user', 'content':text})
self.prompt.append({'role':'assistant', 'content':res_ans})
if self.just_switched_to_exist_session:
self.just_switched_to_exist_session = False
self.set_ongoing()
return res_ans if res_ans[0]!='\n' else res_ans[1:]
# 删除上一回合并返回上一回合的问题
def undo(self) -> str:
self.last_interact_timestamp = int(time.time())
# 删除最后两个消息
if len(self.prompt) < 2:
raise Exception('之前无对话,无法撤销')
question = self.prompt[-2]['content']
self.prompt = self.prompt[:-2]
# 返回上一回合的问题
return question
# 构建对话体
def cut_out(self, msg: str, max_tokens: int) -> list:
"""将现有prompt进行切割处理,使得新的prompt长度不超过max_tokens"""
# 如果用户消息长度超过max_tokens,直接返回
temp_prompt = [
{
'role': 'user',
'content': msg
}
]
token_count = len(msg)
# 倒序遍历prompt
for i in range(len(self.prompt) - 1, -1, -1):
if token_count >= max_tokens:
break
# 将prompt加到temp_prompt头部
temp_prompt.insert(0, self.prompt[i])
token_count += len(self.prompt[i]['content'])
logging.debug('cut_out: {}'.format(str(temp_prompt)))
return temp_prompt
# 持久化session
def persistence(self):
if self.prompt == self.get_default_prompt():
return
db_inst = pkg.utils.context.get_database_manager()
name_spt = self.name.split('_')
subject_type = name_spt[0]
subject_number = int(name_spt[1])
db_inst.persistence_session(subject_type, subject_number, self.create_timestamp, self.last_interact_timestamp,
json.dumps(self.prompt))
# 重置session
def reset(self, explicit: bool = False, expired: bool = False, schedule_new: bool = True, use_prompt: str = None):
if self.prompt[-1]['role'] != "system":
self.persistence()
if explicit:
# 触发插件事件
args = {
'session_name': self.name,
'session': self
}
# 此事件不支持阻止默认行为
_ = pkg.plugin.host.emit(plugin_models.SessionExplicitReset, **args)
pkg.utils.context.get_database_manager().explicit_close_session(self.name, self.create_timestamp)
if expired:
pkg.utils.context.get_database_manager().set_session_expired(self.name, self.create_timestamp)
self.prompt = self.get_default_prompt(use_prompt)
self.create_timestamp = int(time.time())
self.last_interact_timestamp = int(time.time())
self.just_switched_to_exist_session = False
# self.response_lock = threading.Lock()
if schedule_new:
self.schedule()
# 将本session的数据库状态设置为on_going
def set_ongoing(self):
pkg.utils.context.get_database_manager().set_session_ongoing(self.name, self.create_timestamp)
# 切换到上一个session
def last_session(self):
last_one = pkg.utils.context.get_database_manager().last_session(self.name, self.last_interact_timestamp)
if last_one is None:
return None
else:
self.persistence()
self.create_timestamp = last_one['create_timestamp']
self.last_interact_timestamp = last_one['last_interact_timestamp']
try:
self.prompt = json.loads(last_one['prompt'])
except json.decoder.JSONDecodeError:
self.prompt = reset_session_prompt(self.name, last_one['prompt'])
self.persistence()
self.just_switched_to_exist_session = True
return self
# 切换到下一个session
def next_session(self):
next_one = pkg.utils.context.get_database_manager().next_session(self.name, self.last_interact_timestamp)
if next_one is None:
return None
else:
self.persistence()
self.create_timestamp = next_one['create_timestamp']
self.last_interact_timestamp = next_one['last_interact_timestamp']
try:
self.prompt = json.loads(next_one['prompt'])
except json.decoder.JSONDecodeError:
self.prompt = reset_session_prompt(self.name, next_one['prompt'])
self.persistence()
self.just_switched_to_exist_session = True
return self
def list_history(self, capacity: int = 10, page: int = 0):
return pkg.utils.context.get_database_manager().list_history(self.name, capacity, page)
def draw_image(self, prompt: str):
return pkg.utils.context.get_openai_manager().request_image(prompt)
View File
View File
+45
View File
@@ -0,0 +1,45 @@
from __future__ import annotations
import re
from .. import stage, entities, stagemgr
from ...core import entities as core_entities
from ...config import manager as cfg_mgr
@stage.stage_class('BanSessionCheckStage')
class BanSessionCheckStage(stage.PipelineStage):
async def initialize(self):
pass
async def process(
self,
query: core_entities.Query,
stage_inst_name: str
) -> entities.StageProcessResult:
found = False
mode = self.ap.pipeline_cfg.data['access-control']['mode']
sess_list = self.ap.pipeline_cfg.data['access-control'][mode]
if (query.launcher_type == 'group' and 'group_*' in sess_list) \
or (query.launcher_type == 'person' and 'person_*' in sess_list):
found = True
else:
for sess in sess_list:
if sess == f"{query.launcher_type}_{query.launcher_id}":
found = True
break
result = False
if mode == 'blacklist':
result = found
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE if not result else entities.ResultType.INTERRUPT,
new_query=query,
debug_notice=f'根据访问控制忽略消息: {query.launcher_type}_{query.launcher_id}' if result else ''
)
View File
+133
View File
@@ -0,0 +1,133 @@
from __future__ import annotations
import mirai
from ...core import app
from .. import stage, entities, stagemgr
from ...core import entities as core_entities
from ...config import manager as cfg_mgr
from . import filter, entities as filter_entities
from .filters import cntignore, banwords, baiduexamine
@stage.stage_class('PostContentFilterStage')
@stage.stage_class('PreContentFilterStage')
class ContentFilterStage(stage.PipelineStage):
filter_chain: list[filter.ContentFilter]
def __init__(self, ap: app.Application):
self.filter_chain = []
super().__init__(ap)
async def initialize(self):
self.filter_chain.append(cntignore.ContentIgnore(self.ap))
if self.ap.pipeline_cfg.data['check-sensitive-words']:
self.filter_chain.append(banwords.BanWordFilter(self.ap))
if self.ap.pipeline_cfg.data['baidu-cloud-examine']['enable']:
self.filter_chain.append(baiduexamine.BaiduCloudExamine(self.ap))
for filter in self.filter_chain:
await filter.initialize()
async def _pre_process(
self,
message: str,
query: core_entities.Query,
) -> entities.StageProcessResult:
"""请求llm前处理消息
只要有一个不通过就不放行,只放行 PASS 的消息
"""
if not self.ap.pipeline_cfg.data['income-msg-check']:
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
else:
for filter in self.filter_chain:
if filter_entities.EnableStage.PRE in filter.enable_stages:
result = await filter.process(message)
if result.level in [
filter_entities.ResultLevel.BLOCK,
filter_entities.ResultLevel.MASKED
]:
return entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
user_notice=result.user_notice,
console_notice=result.console_notice
)
elif result.level == filter_entities.ResultLevel.PASS: # 传到下一个
message = result.replacement
query.message_chain = mirai.MessageChain(
mirai.Plain(message)
)
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
async def _post_process(
self,
message: str,
query: core_entities.Query,
) -> entities.StageProcessResult:
"""请求llm后处理响应
只要是 PASS 或者 MASKED 的就通过此 filter,将其 replacement 设置为message,进入下一个 filter
"""
if message is None:
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
else:
message = message.strip()
for filter in self.filter_chain:
if filter_entities.EnableStage.POST in filter.enable_stages:
result = await filter.process(message)
if result.level == filter_entities.ResultLevel.BLOCK:
return entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
user_notice=result.user_notice,
console_notice=result.console_notice
)
elif result.level in [
filter_entities.ResultLevel.PASS,
filter_entities.ResultLevel.MASKED
]:
message = result.replacement
query.resp_messages[-1].content = message
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
async def process(
self,
query: core_entities.Query,
stage_inst_name: str
) -> entities.StageProcessResult:
"""处理
"""
if stage_inst_name == 'PreContentFilterStage':
return await self._pre_process(
str(query.message_chain).strip(),
query
)
elif stage_inst_name == 'PostContentFilterStage':
return await self._post_process(
query.resp_messages[-1].content,
query
)
else:
raise ValueError(f'未知的 stage_inst_name: {stage_inst_name}')
+64
View File
@@ -0,0 +1,64 @@
import typing
import enum
import pydantic
class ResultLevel(enum.Enum):
"""结果等级"""
PASS = enum.auto()
"""通过"""
WARN = enum.auto()
"""警告"""
MASKED = enum.auto()
"""已掩去"""
BLOCK = enum.auto()
"""阻止"""
class EnableStage(enum.Enum):
"""启用阶段"""
PRE = enum.auto()
"""预处理"""
POST = enum.auto()
"""后处理"""
class FilterResult(pydantic.BaseModel):
level: ResultLevel
replacement: str
"""替换后的消息"""
user_notice: str
"""不通过时,用户提示消息"""
console_notice: str
"""不通过时,控制台提示消息"""
class ManagerResultLevel(enum.Enum):
"""处理器结果等级"""
CONTINUE = enum.auto()
"""继续"""
INTERRUPT = enum.auto()
"""中断"""
class FilterManagerResult(pydantic.BaseModel):
level: ManagerResultLevel
replacement: str
"""替换后的消息"""
user_notice: str
"""用户提示消息"""
console_notice: str
"""控制台提示消息"""
+34
View File
@@ -0,0 +1,34 @@
# 内容过滤器的抽象类
from __future__ import annotations
import abc
from ...core import app
from . import entities
class ContentFilter(metaclass=abc.ABCMeta):
ap: app.Application
def __init__(self, ap: app.Application):
self.ap = ap
@property
def enable_stages(self):
"""启用的阶段
"""
return [
entities.EnableStage.PRE,
entities.EnableStage.POST
]
async def initialize(self):
"""初始化过滤器
"""
pass
@abc.abstractmethod
async def process(self, message: str) -> entities.FilterResult:
"""处理消息
"""
raise NotImplementedError
@@ -0,0 +1,61 @@
from __future__ import annotations
import aiohttp
from .. import entities
from .. import filter as filter_model
BAIDU_EXAMINE_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={}"
BAIDU_EXAMINE_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token"
class BaiduCloudExamine(filter_model.ContentFilter):
"""百度云内容审核"""
async def _get_token(self) -> str:
async with aiohttp.ClientSession() as session:
async with session.post(
BAIDU_EXAMINE_TOKEN_URL,
params={
"grant_type": "client_credentials",
"client_id": self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-key'],
"client_secret": self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-secret']
}
) as resp:
return (await resp.json())['access_token']
async def process(self, message: str) -> entities.FilterResult:
async with aiohttp.ClientSession() as session:
async with session.post(
BAIDU_EXAMINE_URL.format(await self._get_token()),
headers={'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'},
data=f"text={message}".encode('utf-8')
) as resp:
result = await resp.json()
if "error_code" in result:
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,
replacement=message,
user_notice='',
console_notice=f"百度云判定出错,错误信息:{result['error_msg']}"
)
else:
conclusion = result["conclusion"]
if conclusion in ("合规"):
return entities.FilterResult(
level=entities.ResultLevel.PASS,
replacement=message,
user_notice='',
console_notice=f"百度云判定结果:{conclusion}"
)
else:
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,
replacement=message,
user_notice="消息中存在不合适的内容, 请修改",
console_notice=f"百度云判定结果:{conclusion}"
)
@@ -0,0 +1,44 @@
from __future__ import annotations
import re
from .. import filter as filter_model
from .. import entities
from ....config import manager as cfg_mgr
class BanWordFilter(filter_model.ContentFilter):
"""根据内容禁言"""
sensitive: cfg_mgr.ConfigManager
async def initialize(self):
self.sensitive = await cfg_mgr.load_json_config(
"data/config/sensitive-words.json",
"templates/sensitive-words.json"
)
async def process(self, message: str) -> entities.FilterResult:
found = False
for word in self.sensitive.data['words']:
match = re.findall(word, message)
if len(match) > 0:
found = True
for i in range(len(match)):
if self.sensitive.data['mask_word'] == "":
message = message.replace(
match[i], self.sensitive.data['mask'] * len(match[i])
)
else:
message = message.replace(
match[i], self.sensitive.data['mask_word']
)
return entities.FilterResult(
level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS,
replacement=message,
user_notice='消息中存在不合适的内容, 请修改' if found else '',
console_notice=''
)
@@ -0,0 +1,43 @@
from __future__ import annotations
import re
from .. import entities
from .. import filter as filter_model
class ContentIgnore(filter_model.ContentFilter):
"""根据内容忽略消息"""
@property
def enable_stages(self):
return [
entities.EnableStage.PRE,
]
async def process(self, message: str) -> entities.FilterResult:
if 'prefix' in self.ap.pipeline_cfg.data['ignore-rules']:
for rule in self.ap.pipeline_cfg.data['ignore-rules']['prefix']:
if message.startswith(rule):
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,
replacement='',
user_notice='',
console_notice='根据 ignore_rules 中的 prefix 规则,忽略消息'
)
if 'regexp' in self.ap.pipeline_cfg.data['ignore-rules']:
for rule in self.ap.pipeline_cfg.data['ignore-rules']['regexp']:
if re.search(rule, message):
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,
replacement='',
user_notice='',
console_notice='根据 ignore_rules 中的 regexp 规则,忽略消息'
)
return entities.FilterResult(
level=entities.ResultLevel.PASS,
replacement=message,
user_notice='',
console_notice=''
)
+40
View File
@@ -0,0 +1,40 @@
from __future__ import annotations
import enum
import typing
import pydantic
import mirai
import mirai.models.message as mirai_message
from ..core import entities
class ResultType(enum.Enum):
CONTINUE = enum.auto()
"""继续流水线"""
INTERRUPT = enum.auto()
"""中断流水线"""
class StageProcessResult(pydantic.BaseModel):
result_type: ResultType
new_query: entities.Query
user_notice: typing.Optional[typing.Union[str, list[mirai_message.MessageComponent], mirai.MessageChain, None]] = []
"""只要设置了就会发送给用户"""
# TODO delete
# admin_notice: typing.Optional[typing.Union[str, list[mirai_message.MessageComponent], mirai.MessageChain, None]] = []
"""只要设置了就会发送给管理员"""
console_notice: typing.Optional[str] = ''
"""只要设置了就会输出到控制台"""
debug_notice: typing.Optional[str] = ''
error_notice: typing.Optional[str] = ''
View File
+59
View File
@@ -0,0 +1,59 @@
from __future__ import annotations
import os
import traceback
from PIL import Image, ImageDraw, ImageFont
from mirai.models.message import MessageComponent, Plain, MessageChain
from ...core import app
from . import strategy
from .strategies import image, forward
from .. import stage, entities, stagemgr
from ...core import entities as core_entities
from ...config import manager as cfg_mgr
@stage.stage_class("LongTextProcessStage")
class LongTextProcessStage(stage.PipelineStage):
strategy_impl: strategy.LongTextStrategy
async def initialize(self):
config = self.ap.platform_cfg.data['long-text-process']
if config['strategy'] == 'image':
use_font = config['font-path']
try:
# 检查是否存在
if not os.path.exists(use_font):
# 若是windows系统,使用微软雅黑
if os.name == "nt":
use_font = "C:/Windows/Fonts/msyh.ttc"
if not os.path.exists(use_font):
self.ap.logger.warn("未找到字体文件,且无法使用Windows自带字体,更换为转发消息组件以发送长消息,您可以在config.py中调整相关设置。")
config['blob_message_strategy'] = "forward"
else:
self.ap.logger.info("使用Windows自带字体:" + use_font)
config['font-path'] = use_font
else:
self.ap.logger.warn("未找到字体文件,且无法使用系统自带字体,更换为转发消息组件以发送长消息,您可以在config.py中调整相关设置。")
self.ap.platform_cfg.data['long-text-process']['strategy'] = "forward"
except:
traceback.print_exc()
self.ap.logger.error("加载字体文件失败({}),更换为转发消息组件以发送长消息,您可以在config.py中调整相关设置。".format(use_font))
self.ap.platform_cfg.data['long-text-process']['strategy'] = "forward"
if config['strategy'] == 'image':
self.strategy_impl = image.Text2ImageStrategy(self.ap)
elif config['strategy'] == 'forward':
self.strategy_impl = forward.ForwardComponentStrategy(self.ap)
await self.strategy_impl.initialize()
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
if len(str(query.resp_message_chain)) > self.ap.platform_cfg.data['long-text-process']['threshold']:
query.resp_message_chain = MessageChain(await self.strategy_impl.process(str(query.resp_message_chain), query))
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
@@ -0,0 +1,63 @@
# 转发消息组件
from __future__ import annotations
import typing
from mirai.models import MessageChain
from mirai.models.message import MessageComponent, ForwardMessageNode
from mirai.models.base import MiraiBaseModel
from .. import strategy as strategy_model
from ....core import entities as core_entities
class ForwardMessageDiaplay(MiraiBaseModel):
title: str = "群聊的聊天记录"
brief: str = "[聊天记录]"
source: str = "聊天记录"
preview: typing.List[str] = []
summary: str = "查看x条转发消息"
class Forward(MessageComponent):
"""合并转发。"""
type: str = "Forward"
"""消息组件类型。"""
display: ForwardMessageDiaplay
"""显示信息"""
node_list: typing.List[ForwardMessageNode]
"""转发消息节点列表。"""
def __init__(self, *args, **kwargs):
if len(args) == 1:
self.node_list = args[0]
super().__init__(**kwargs)
super().__init__(*args, **kwargs)
def __str__(self):
return '[聊天记录]'
class ForwardComponentStrategy(strategy_model.LongTextStrategy):
async def process(self, message: str, query: core_entities.Query) -> list[MessageComponent]:
display = ForwardMessageDiaplay(
title="群聊的聊天记录",
brief="[聊天记录]",
source="聊天记录",
preview=["QQ用户: "+message],
summary="查看1条转发消息"
)
node_list = [
ForwardMessageNode(
sender_id=query.adapter.bot_account_id,
sender_name='QQ用户',
message_chain=MessageChain([message])
)
]
forward = Forward(
display=display,
node_list=node_list
)
return [forward]
+198
View File
@@ -0,0 +1,198 @@
from __future__ import annotations
import typing
import os
import base64
import time
import re
from PIL import Image, ImageDraw, ImageFont
from mirai.models import MessageChain, Image as ImageComponent
from mirai.models.message import MessageComponent
from .. import strategy as strategy_model
from ....core import entities as core_entities
class Text2ImageStrategy(strategy_model.LongTextStrategy):
text_render_font: ImageFont.FreeTypeFont
async def initialize(self):
self.text_render_font = ImageFont.truetype(self.ap.platform_cfg.data['long-text-process']['font-path'], 32, encoding="utf-8")
async def process(self, message: str, query: core_entities.Query) -> list[MessageComponent]:
img_path = self.text_to_image(
text_str=message,
save_as='temp/{}.png'.format(int(time.time()))
)
compressed_path, size = self.compress_image(
img_path,
outfile="temp/{}_compressed.png".format(int(time.time()))
)
with open(compressed_path, 'rb') as f:
img = f.read()
b64 = base64.b64encode(img)
# 删除图片
os.remove(img_path)
if os.path.exists(compressed_path):
os.remove(compressed_path)
return [
ImageComponent(
base64=b64.decode('utf-8'),
)
]
def indexNumber(self, path=''):
"""
查找字符串中数字所在串中的位置
:param path:目标字符串
:return:<class 'list'>: <class 'list'>: [['1', 16], ['2', 35], ['1', 51]]
"""
kv = []
nums = []
beforeDatas = re.findall('[\d]+', path)
for num in beforeDatas:
indexV = []
times = path.count(num)
if times > 1:
if num not in nums:
indexs = re.finditer(num, path)
for index in indexs:
iV = []
i = index.span()[0]
iV.append(num)
iV.append(i)
kv.append(iV)
nums.append(num)
else:
index = path.find(num)
indexV.append(num)
indexV.append(index)
kv.append(indexV)
# 根据数字位置排序
indexSort = []
resultIndex = []
for vi in kv:
indexSort.append(vi[1])
indexSort.sort()
for i in indexSort:
for v in kv:
if i == v[1]:
resultIndex.append(v)
return resultIndex
def get_size(self, file):
# 获取文件大小:KB
size = os.path.getsize(file)
return size / 1024
def get_outfile(self, infile, outfile):
if outfile:
return outfile
dir, suffix = os.path.splitext(infile)
outfile = '{}-out{}'.format(dir, suffix)
return outfile
def compress_image(self, infile, outfile='', kb=100, step=20, quality=90):
"""不改变图片尺寸压缩到指定大小
:param infile: 压缩源文件
:param outfile: 压缩文件保存地址
:param mb: 压缩目标,KB
:param step: 每次调整的压缩比率
:param quality: 初始压缩比率
:return: 压缩文件地址,压缩文件大小
"""
o_size = self.get_size(infile)
if o_size <= kb:
return infile, o_size
outfile = self.get_outfile(infile, outfile)
while o_size > kb:
im = Image.open(infile)
im.save(outfile, quality=quality)
if quality - step < 0:
break
quality -= step
o_size = self.get_size(outfile)
return outfile, self.get_size(outfile)
def text_to_image(self, text_str: str, save_as="temp.png", width=800):
text_str = text_str.replace("\t", " ")
# 分行
lines = text_str.split('\n')
# 计算并分割
final_lines = []
text_width = width-80
self.ap.logger.debug("lines: {}, text_width: {}".format(lines, text_width))
for line in lines:
# 如果长了就分割
line_width = self.text_render_font.getlength(line)
self.ap.logger.debug("line_width: {}".format(line_width))
if line_width < text_width:
final_lines.append(line)
continue
else:
rest_text = line
while True:
# 分割最前面的一行
point = int(len(rest_text) * (text_width / line_width))
# 检查断点是否在数字中间
numbers = self.indexNumber(rest_text)
for number in numbers:
if number[1] < point < number[1] + len(number[0]) and number[1] != 0:
point = number[1]
break
final_lines.append(rest_text[:point])
rest_text = rest_text[point:]
line_width = self.text_render_font.getlength(rest_text)
if line_width < text_width:
final_lines.append(rest_text)
break
else:
continue
# 准备画布
img = Image.new('RGBA', (width, max(280, len(final_lines) * 35 + 65)), (255, 255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGBA')
self.ap.logger.debug("正在绘制图片...")
# 绘制正文
line_number = 0
offset_x = 20
offset_y = 30
for final_line in final_lines:
draw.text((offset_x, offset_y + 35 * line_number), final_line, fill=(0, 0, 0), font=self.text_render_font)
# 遍历此行,检查是否有emoji
idx_in_line = 0
for ch in final_line:
# 检查字符占位宽
char_code = ord(ch)
if char_code >= 127:
idx_in_line += 1
else:
idx_in_line += 0.5
line_number += 1
self.ap.logger.debug("正在保存图片...")
img.save(save_as)
return save_as
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
import abc
import typing
import mirai
from mirai.models.message import MessageComponent
from ...core import app
from ...core import entities as core_entities
class LongTextStrategy(metaclass=abc.ABCMeta):
ap: app.Application
def __init__(self, ap: app.Application):
self.ap = ap
async def initialize(self):
pass
@abc.abstractmethod
async def process(self, message: str, query: core_entities.Query) -> list[MessageComponent]:
return []
View File
+79
View File
@@ -0,0 +1,79 @@
from __future__ import annotations
from .. import stage, entities, stagemgr
from ...core import entities as core_entities
from ...provider import entities as llm_entities
from ...plugin import events
@stage.stage_class("PreProcessor")
class PreProcessor(stage.PipelineStage):
"""预处理器
"""
async def process(
self,
query: core_entities.Query,
stage_inst_name: str,
) -> entities.StageProcessResult:
"""处理
"""
session = await self.ap.sess_mgr.get_session(query)
conversation = await self.ap.sess_mgr.get_conversation(session)
# 从会话取出消息和情景预设到query
query.session = session
query.prompt = conversation.prompt.copy()
query.messages = conversation.messages.copy()
query.user_message = llm_entities.Message(
role='user',
content=str(query.message_chain).strip()
)
query.use_model = conversation.use_model
query.use_funcs = conversation.use_funcs
# =========== 触发事件 PromptPreProcessing
session = query.session
event_ctx = await self.ap.plugin_mgr.emit_event(
event=events.PromptPreProcessing(
session_name=f'{session.launcher_type.value}_{session.launcher_id}',
default_prompt=query.prompt.messages,
prompt=query.messages,
query=query
)
)
query.prompt.messages = event_ctx.event.default_prompt
query.messages = event_ctx.event.prompt
# 根据模型max_tokens剪裁
max_tokens = min(query.use_model.max_tokens, self.ap.pipeline_cfg.data['submit-messages-tokens'])
test_messages = query.prompt.messages + query.messages + [query.user_message]
while await query.use_model.tokenizer.count_token(test_messages, query.use_model) > max_tokens:
# 前文都pop完了,还是大于max_tokens,由于prompt和user_messages不能删减,报错
if len(query.prompt.messages) == 0:
return entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
user_notice='输入内容过长,请减少情景预设或者输入内容长度',
console_notice='输入内容过长,请减少情景预设或者输入内容长度,或者增大配置文件中的 submit-messages-tokens 项(但不能超过所用模型最大tokens数)'
)
query.messages.pop(0) # pop第一个肯定是role=user的
# 继续pop到第二个role=user前一个
while len(query.messages) > 0 and query.messages[0].role != 'user':
query.messages.pop(0)
test_messages = query.prompt.messages + query.messages + [query.user_message]
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
View File
+34
View File
@@ -0,0 +1,34 @@
from __future__ import annotations
import abc
from ...core import app
from ...core import entities as core_entities
from .. import entities
class MessageHandler(metaclass=abc.ABCMeta):
ap: app.Application
def __init__(self, ap: app.Application):
self.ap = ap
async def initialize(self):
pass
@abc.abstractmethod
async def handle(
self,
query: core_entities.Query,
) -> entities.StageProcessResult:
raise NotImplementedError
def cut_str(self, s: str) -> str:
"""
取字符串第一行,最多20个字符,若有多行,或超过20个字符,则加省略号
"""
s0 = s.split('\n')[0]
if len(s0) > 20 or '\n' in s:
s0 = s0[:20] + '...'
return s0
+113
View File
@@ -0,0 +1,113 @@
from __future__ import annotations
import typing
import time
import traceback
import mirai
from .. import handler
from ... import entities
from ....core import entities as core_entities
from ....provider import entities as llm_entities
from ....plugin import events
class ChatMessageHandler(handler.MessageHandler):
async def handle(
self,
query: core_entities.Query,
) -> typing.AsyncGenerator[entities.StageProcessResult, None]:
"""处理
"""
# 取session
# 取conversation
# 调API
# 生成器
# 触发插件事件
event_class = events.PersonNormalMessageReceived if query.launcher_type == core_entities.LauncherTypes.PERSON else events.GroupNormalMessageReceived
event_ctx = await self.ap.plugin_mgr.emit_event(
event=event_class(
launcher_type=query.launcher_type.value,
launcher_id=query.launcher_id,
sender_id=query.sender_id,
text_message=str(query.message_chain),
query=query
)
)
if event_ctx.is_prevented_default():
if event_ctx.event.reply is not None:
query.resp_message_chain = mirai.MessageChain(event_ctx.event.reply)
yield entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
else:
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query
)
else:
if not self.ap.provider_cfg.data['enable-chat']:
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
)
if event_ctx.event.alter is not None:
query.message_chain = mirai.MessageChain([
mirai.Plain(event_ctx.event.alter)
])
query.messages.append(
query.user_message
)
text_length = 0
start_time = time.time()
try:
async for result in query.use_model.requester.request(query):
query.resp_messages.append(result)
self.ap.logger.info(f'对话({query.query_id})响应: {self.cut_str(result.readable_str())}')
if result.content is not None:
text_length += len(result.content)
yield entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
except Exception as e:
self.ap.logger.error(f'对话({query.query_id})请求失败: {str(e)}')
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
user_notice='请求失败' if self.ap.platform_cfg.data['hide-exception-info'] else f'{e}',
error_notice=f'{e}',
debug_notice=traceback.format_exc()
)
finally:
query.session.using_conversation.messages.append(query.user_message)
query.session.using_conversation.messages.extend(query.resp_messages)
await self.ap.ctr_mgr.usage.post_query_record(
session_type=query.session.launcher_type.value,
session_id=str(query.session.launcher_id),
query_ability_provider="QChatGPT.Chat",
usage=text_length,
model_name=query.use_model.name,
response_seconds=int(time.time() - start_time),
retry_times=-1,
)
+121
View File
@@ -0,0 +1,121 @@
from __future__ import annotations
import typing
import mirai
from .. import handler
from ... import entities
from ....core import entities as core_entities
from ....provider import entities as llm_entities
from ....plugin import events
class CommandHandler(handler.MessageHandler):
async def handle(
self,
query: core_entities.Query,
) -> typing.AsyncGenerator[entities.StageProcessResult, None]:
"""处理
"""
event_class = events.PersonCommandSent if query.launcher_type == core_entities.LauncherTypes.PERSON else events.GroupCommandSent
privilege = 1
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.system_cfg.data['admin-sessions']:
privilege = 2
spt = str(query.message_chain).strip().split(' ')
event_ctx = await self.ap.plugin_mgr.emit_event(
event=event_class(
launcher_type=query.launcher_type.value,
launcher_id=query.launcher_id,
sender_id=query.sender_id,
command=spt[0],
params=spt[1:] if len(spt) > 1 else [],
text_message=str(query.message_chain),
is_admin=(privilege==2),
query=query
)
)
if event_ctx.is_prevented_default():
if event_ctx.event.reply is not None:
mc = mirai.MessageChain(event_ctx.event.reply)
query.resp_messages.append(
llm_entities.Message(
role='command',
content=str(mc),
)
)
yield entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
else:
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query
)
else:
if event_ctx.event.alter is not None:
query.message_chain = mirai.MessageChain([
mirai.Plain(event_ctx.event.alter)
])
session = await self.ap.sess_mgr.get_session(query)
command_text = str(query.message_chain).strip()[1:]
async for ret in self.ap.cmd_mgr.execute(
command_text=command_text,
query=query,
session=session
):
if ret.error is not None:
# query.resp_message_chain = mirai.MessageChain([
# mirai.Plain(str(ret.error))
# ])
query.resp_messages.append(
llm_entities.Message(
role='command',
content=str(ret.error),
)
)
self.ap.logger.info(f'命令({query.query_id})报错: {self.cut_str(str(ret.error))}')
yield entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
elif ret.text is not None:
# query.resp_message_chain = mirai.MessageChain([
# mirai.Plain(ret.text)
# ])
query.resp_messages.append(
llm_entities.Message(
role='command',
content=ret.text,
)
)
self.ap.logger.info(f'命令返回: {self.cut_str(ret.text)}')
yield entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
else:
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query
)
+45
View File
@@ -0,0 +1,45 @@
from __future__ import annotations
from ...core import app, entities as core_entities
from . import handler
from .handlers import chat, command
from .. import entities
from .. import stage, entities, stagemgr
from ...core import entities as core_entities
from ...config import manager as cfg_mgr
@stage.stage_class("MessageProcessor")
class Processor(stage.PipelineStage):
cmd_handler: handler.MessageHandler
chat_handler: handler.MessageHandler
async def initialize(self):
self.cmd_handler = command.CommandHandler(self.ap)
self.chat_handler = chat.ChatMessageHandler(self.ap)
await self.cmd_handler.initialize()
await self.chat_handler.initialize()
async def process(
self,
query: core_entities.Query,
stage_inst_name: str,
) -> entities.StageProcessResult:
"""处理
"""
message_text = str(query.message_chain).strip()
self.ap.logger.info(f"处理 {query.launcher_type.value}_{query.launcher_id} 的请求({query.query_id}): {message_text}")
async def generator():
if message_text.startswith('!') or message_text.startswith(''):
async for result in self.cmd_handler.handle(query):
yield result
else:
async for result in self.chat_handler.handle(query):
yield result
return generator()
View File

Some files were not shown because too many files have changed in this diff Show More