mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-05 05:16:03 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db51fd0ad7 | ||
|
|
256bc4dc1e | ||
|
|
d2bd6e23b6 | ||
|
|
bb12b48887 | ||
|
|
a58e55daf3 | ||
|
|
23a05fe5b0 | ||
|
|
3a63630068 | ||
|
|
565066bbcd | ||
|
|
c10f72cf4c | ||
|
|
af8c21f3d4 | ||
|
|
6f6c3af302 | ||
|
|
61a47808c8 | ||
|
|
e02765bf95 | ||
|
|
b69f193a3e | ||
|
|
7c6526d1ea | ||
|
|
b8776fba65 | ||
|
|
38357dd68d | ||
|
|
d1c2453310 | ||
|
|
ebc1ac50c6 | ||
|
|
892610872f | ||
|
|
a990a40850 | ||
|
|
3f29464dbd | ||
|
|
998d07f3b4 | ||
|
|
949bc6268c | ||
|
|
2c03e5a77e | ||
|
|
aad62dfa6f | ||
|
|
08e27d07ea | ||
|
|
1fddd244e5 | ||
|
|
d85b4b1cf0 | ||
|
|
09fca2c292 | ||
|
|
feda3d18fb | ||
|
|
eb6e5d0756 | ||
|
|
7386daad28 | ||
|
|
3f290b2e1a | ||
|
|
43519ffe80 |
21
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
21
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -26,8 +26,8 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: 系统环境
|
label: 系统环境
|
||||||
description: 操作系统、系统架构。
|
description: 操作系统、系统架构、**主机地理位置**,地理位置最好写清楚,涉及网络问题排查。
|
||||||
placeholder: 例如: CentOS x64、Windows11
|
placeholder: 例如: CentOS x64 中国大陆、Windows11 美国
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
@@ -37,15 +37,28 @@ body:
|
|||||||
placeholder: 例如: Python 3.10
|
placeholder: 例如: Python 3.10
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: QChatGPT版本
|
||||||
|
description: QChatGPT版本号
|
||||||
|
placeholder: 例如: v2.6.0,可以使用`!version`命令查看
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 异常情况
|
label: 异常情况
|
||||||
description: 完整描述异常情况,什么时候发生的、发生了什么
|
description: 完整描述异常情况,什么时候发生的、发生了什么,尽可能详细
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 日志信息
|
label: 日志信息
|
||||||
description: 请提供完整的 **登录框架 和 QChatGPT控制台**的相关日志信息(若有),不提供日志信息**无法**为您排查问题
|
description: 请提供完整的 **登录框架 和 QChatGPT控制台**的相关日志信息(若有),不提供日志信息**无法**为您排查问题,请尽可能详细
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 启用的插件
|
||||||
|
description: 有些情况可能和插件功能有关,建议提供插件启用情况。可以使用`!plugin`命令查看已启用的插件
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: 需求建议
|
name: 需求建议
|
||||||
title: "[Feature]: "
|
title: "[Feature]: "
|
||||||
labels: ["enhancement"]
|
labels: ["改进"]
|
||||||
description: "新功能或现有功能优化请使用这个模板;不符合类别的issue将被直接关闭"
|
description: "新功能或现有功能优化请使用这个模板;不符合类别的issue将被直接关闭"
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|||||||
24
.github/ISSUE_TEMPLATE/submit-plugin.yml
vendored
Normal file
24
.github/ISSUE_TEMPLATE/submit-plugin.yml
vendored
Normal 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
|
||||||
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,4 +30,5 @@ qcapi
|
|||||||
claude.json
|
claude.json
|
||||||
bard.json
|
bard.json
|
||||||
/*yaml
|
/*yaml
|
||||||
!/docker-compose.yaml
|
!/docker-compose.yaml
|
||||||
|
res/instance_id.json
|
||||||
351
README.md
351
README.md
@@ -1,30 +1,27 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="res/logo.png" alt="QChatGPT" width="120" />
|
<img src="https://qchatgpt.rockchin.top/logo.png" alt="QChatGPT" width="180" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
# QChatGPT
|
# QChatGPT
|
||||||
|
|
||||||
|
<blockquote> 🥳 QChatGPT 一周年啦,感谢大家的支持!欢迎前往<a href="https://github.com/RockChinQ/QChatGPT/discussions/627">讨论</a>。</blockquote>
|
||||||
|
|
||||||
[](https://github.com/RockChinQ/QChatGPT/releases/latest)
|
[](https://github.com/RockChinQ/QChatGPT/releases/latest)
|
||||||
<a href="https://hub.docker.com/repository/docker/rockchin/qchatgpt">
|
<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">
|
<img src="https://img.shields.io/docker/pulls/rockchin/qchatgpt?color=blue" alt="docker pull">
|
||||||
</a>
|
</a>
|
||||||

|

|
||||||
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
||||||
<a href="https://github.com/RockChinQ/QChatGPT/wiki">
|
<br/>
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/%E6%9F%A5%E7%9C%8B-%E9%A1%B9%E7%9B%AEWiki-blue">
|
|
||||||
</a><br/>
|
|
||||||
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=66-aWvn8cbP4c1ut_1YYkvvGVeEtyTH8&authKey=pTaKBK5C%2B8dFzQ4XlENf6MHTCLaHnlKcCRx7c14EeVVlpX2nRSaS8lJm8YeM4mCU&noverify=0&group_code=195992197">
|
<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">
|
<img alt="Static Badge" src="https://img.shields.io/badge/%E5%AE%98%E6%96%B9%E7%BE%A4-195992197-purple">
|
||||||
</a>
|
</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=738382634">
|
<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=738382634">
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/%E7%A4%BE%E5%8C%BA%E7%BE%A4-738382634-purple">
|
<img alt="Static Badge" src="https://img.shields.io/badge/%E7%A4%BE%E5%8C%BA%E7%BE%A4-738382634-purple">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://qchatgpt.rockchin.top">
|
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/%E6%9F%A5%E7%9C%8B-%E7%A4%BE%E5%8C%BA%E7%BC%96%E5%86%99%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C-blue">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.bilibili.com/video/BV14h4y1w7TC">
|
<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">
|
<img alt="Static Badge" src="https://img.shields.io/badge/%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B-208647">
|
||||||
</a>
|
</a>
|
||||||
@@ -32,338 +29,12 @@
|
|||||||
<img alt="Static Badge" src="https://img.shields.io/badge/Linux%E9%83%A8%E7%BD%B2%E8%A7%86%E9%A2%91-208647">
|
<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>
|
</a>
|
||||||
|
|
||||||
<blockquote> 🥳 QChatGPT 一周年啦,感谢大家的支持!欢迎前往<a href="https://github.com/RockChinQ/QChatGPT/discussions/627">讨论</a>。</blockquote>
|
<a href="https://qchatgpt.rockchin.top">项目主页</a> |
|
||||||
|
<a href="https://qchatgpt.rockchin.top/posts/feature.html">功能介绍</a> |
|
||||||
<!-- <details>
|
<a href="https://qchatgpt.rockchin.top/posts/deploy/">部署文档</a> |
|
||||||
<summary>回复效果演示(带有联网插件)</summary>
|
<a href="https://qchatgpt.rockchin.top/posts/error/">常见问题</a> |
|
||||||
<img alt="联网演示GIF" src="res/webwlkr-demo.gif" width="300px">
|
<a href="https://qchatgpt.rockchin.top/posts/plugin/intro.html">插件介绍</a> |
|
||||||
</details> -->
|
<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>
|
||||||
|
|
||||||
<img alt="回复效果(带有联网插件)" src="res/QChatGPT-1211.png" width="500px"/>
|
|
||||||
|
|
||||||
|
<img alt="回复效果(带有联网插件)" src="https://qchatgpt.rockchin.top/assets/image/QChatGPT-1211.png" width="500px"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>
|
|
||||||
|
|
||||||
## 🍺模型一览和功能点
|
|
||||||
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
### 文字对话
|
|
||||||
|
|
||||||
- OpenAI GPT-3.5模型(ChatGPT API), 本项目原生支持, 默认使用
|
|
||||||
- OpenAI GPT-3模型, 本项目原生支持, 部署完成后前往`config.py`切换
|
|
||||||
- OpenAI GPT-4模型, 本项目原生支持, 目前需要您的账户通过OpenAI的内测申请, 请前往`config.py`切换
|
|
||||||
- ChatGPT网页版GPT-3.5模型, 由[插件](https://github.com/RockChinQ/revLibs)接入
|
|
||||||
- ChatGPT网页版GPT-4模型, 目前需要ChatGPT Plus订阅, 由[插件](https://github.com/RockChinQ/revLibs)接入
|
|
||||||
- New Bing逆向库, 由[插件](https://github.com/RockChinQ/revLibs)接入
|
|
||||||
- HuggingChat, 由[插件](https://github.com/RockChinQ/revLibs)接入, 仅支持英文
|
|
||||||
- Claude, 由[插件](https://github.com/RockChinQ/revLibs)接入
|
|
||||||
- Google Bard, 由[插件](https://github.com/RockChinQ/revLibs)接入
|
|
||||||
- Google Gemini Pro、Azure、Anthropic Claude、智谱 ChatGLM、百度文心一言、讯飞星火认知、阿里通义千问、360 智脑等官方接口, 通过[One API](https://github.com/songquanpeng/one-api)接入
|
|
||||||
|
|
||||||
### 模型聚合平台
|
|
||||||
|
|
||||||
- [One API](https://github.com/songquanpeng/one-api), Azure、Anthropic Claude、Google Gemini Pro、智谱 ChatGLM、百度文心一言、讯飞星火认知、阿里通义千问以及 360 智脑等模型的官方接口转换成 OpenAI API 接入,QChatGPT 原生支持,您需要先配置 One API,之后在`config.py`中设置反向代理和`One API`的密钥后使用。
|
|
||||||
- [gpt4free](https://github.com/xtekky/gpt4free), 破解以免费使用多个平台的各种文字模型, 由[插件](https://github.com/RockChinQ/revLibs)接入, 无需鉴权, 稳定性较差。
|
|
||||||
- [Poe](https://poe.com), 破解免费使用Poe上多个平台的模型, 由[oliverkirk-sudo/ChatPoeBot](https://github.com/oliverkirk-sudo/ChatPoeBot)接入(由于 Poe 上可用的大部分模型现已通过[revLibs插件](https://github.com/RockChinQ/revLubs)或其他方式接入,此插件现已停止维护)。
|
|
||||||
|
|
||||||
### 故事续写
|
|
||||||
|
|
||||||
- NovelAI API, 由[插件](https://github.com/dominoar/QCPNovelAi)接入
|
|
||||||
|
|
||||||
### 图片绘制
|
|
||||||
|
|
||||||
- OpenAI DALL·E模型, 本项目原生支持, 使用方法查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/1-%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)
|
|
||||||
- NovelAI API, 由[插件](https://github.com/dominoar/QCPNovelAi)接入
|
|
||||||
|
|
||||||
### 语音生成
|
|
||||||
|
|
||||||
- TTS+VITS, 由[插件](https://github.com/dominoar/QChatPlugins)接入
|
|
||||||
- Plachta/VITS-Umamusume-voice-synthesizer, 由[插件](https://github.com/oliverkirk-sudo/chat_voice)接入
|
|
||||||
|
|
||||||
|
|
||||||
安装[此插件](https://github.com/RockChinQ/Switcher),即可在使用中切换文字模型。
|
|
||||||
|
|
||||||
### 功能点
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>✅支持敏感词过滤,避免账号风险</summary>
|
|
||||||
|
|
||||||
- 难以监测机器人与用户对话时的内容,故引入此功能以减少机器人风险
|
|
||||||
- 加入了百度云内容审核,在`config.py`中修改`baidu_check`的值,并填写`baidu_api_key`和`baidu_secret_key`以开启此功能
|
|
||||||
- 编辑`sensitive.json`,并在`config.py`中修改`sensitive_word_filter`的值以开启此功能
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>✅群内多种响应规则,不必at</summary>
|
|
||||||
|
|
||||||
- 默认回复`ai`作为前缀或`@`机器人的消息
|
|
||||||
- 详细见`config.py`中的`response_rules`字段
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>✅完善的多api-key管理,超额自动切换</summary>
|
|
||||||
|
|
||||||
- 支持配置多个`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/1-%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>
|
|
||||||
|
|
||||||
- 自行实现插件加载器及相关支持
|
|
||||||
- 支持GPT的Function Calling功能
|
|
||||||
- 详细查看[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/5-%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>
|
|
||||||
<details>
|
|
||||||
<summary>✅支持使用网络代理</summary>
|
|
||||||
|
|
||||||
- 目前已支持正向代理访问接口
|
|
||||||
- 详细请查看config.py中的`openai_config`的说明
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>✅支持自定义提示内容</summary>
|
|
||||||
|
|
||||||
- 允许用户自定义报错、帮助等提示信息
|
|
||||||
- 请查看`tips.py`
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### 🏞️截图
|
|
||||||
|
|
||||||
<img alt="私聊GPT-3.5" src="res/screenshots/person_gpt3.5.png" width="400"/>
|
|
||||||
<br/>
|
|
||||||
<img alt="群聊GPT-3.5" src="res/screenshots/group_gpt3.5.png" width="400"/>
|
|
||||||
<br/>
|
|
||||||
<img alt="New Bing" src="res/screenshots/person_newbing.png" width="400"/>
|
|
||||||
|
|
||||||
详情请查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/1-%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>
|
|
||||||
|
|
||||||
## 🚀部署和使用
|
|
||||||
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
> **NOTE**
|
|
||||||
> - 部署过程中遇到任何问题,请先在[QChatGPT](https://github.com/RockChinQ/QChatGPT/issues)或[qcg-installer](https://github.com/RockChinQ/qcg-installer/issues)的issue里进行搜索
|
|
||||||
> - QChatGPT需要Python版本>=3.9
|
|
||||||
> - 官方群和社区群群号请见文档顶部
|
|
||||||
|
|
||||||
### - 注册OpenAI账号
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>点此查看步骤</summary>
|
|
||||||
|
|
||||||
> 若您要直接使用非OpenAI的模型(如New Bing),可跳过此步骤,直接进行之后的部署,完成后按照相关插件的文档进行配置即可
|
|
||||||
|
|
||||||
参考以下文章自行注册
|
|
||||||
|
|
||||||
> [国内注册ChatGPT的方法(100%可用)](https://www.pythonthree.com/register-openai-chatgpt/)
|
|
||||||
> [手把手教你如何注册ChatGPT,超级详细](https://guxiaobei.com/51461)
|
|
||||||
|
|
||||||
注册成功后请前往[个人中心查看](https://beta.openai.com/account/api-keys)api_key
|
|
||||||
完成注册后,使用以下自动化或手动部署步骤
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### - Docker或自动化部署
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>展开查看,以下方式二选一,Linux首选Docker,Windows首选安装器</summary>
|
|
||||||
|
|
||||||
#### Docker方式
|
|
||||||
|
|
||||||
> docker方式较为复杂,若您不**熟悉**docker的操作及相关知识,强烈建议您使用其他方式部署,我们**不会且难以**解决您主机上多个容器的连接问题。
|
|
||||||
|
|
||||||
请查看[此文档](res/docs/docker_deployment.md)
|
|
||||||
|
|
||||||
#### 安装器方式
|
|
||||||
|
|
||||||
使用[此安装器](https://github.com/RockChinQ/qcg-installer)(若无法访问请到[Gitee](https://gitee.com/RockChin/qcg-installer))进行部署
|
|
||||||
|
|
||||||
- 安装器目前仅支持部分平台,请到仓库文档查看,其他平台请手动部署
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### - 手动部署
|
|
||||||
<details>
|
|
||||||
<summary>手动部署适用于所有平台</summary>
|
|
||||||
|
|
||||||
- 请使用Python 3.9.x以上版本
|
|
||||||
|
|
||||||
#### ① 配置QQ登录框架
|
|
||||||
|
|
||||||
目前支持mirai和go-cqhttp,配置任意一个即可
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>mirai</summary>
|
|
||||||
|
|
||||||
1. 按照[此教程](https://yiri-mirai.wybxc.cc/tutorials/01/configuration)配置Mirai及mirai-api-http
|
|
||||||
2. 启动mirai-console后,使用`login`命令登录QQ账号,保持mirai-console运行状态
|
|
||||||
3. 在下一步配置主程序时请在config.py中将`msg_source_adapter`设为`yirimirai`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>go-cqhttp</summary>
|
|
||||||
|
|
||||||
1. 按照[此文档](https://github.com/RockChinQ/QChatGPT/wiki/9-go-cqhttp%E9%85%8D%E7%BD%AE)配置go-cqhttp
|
|
||||||
2. 启动go-cqhttp,确保登录成功,保持运行
|
|
||||||
3. 在下一步配置主程序时请在config.py中将`msg_source_adapter`设为`nakuru`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### ② 配置主程序
|
|
||||||
|
|
||||||
1. 克隆此项目
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/RockChinQ/QChatGPT
|
|
||||||
cd QChatGPT
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 安装依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip3 install requests -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
**部署完成后必看: [命令说明](https://github.com/RockChinQ/QChatGPT/wiki/1-%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)**
|
|
||||||
|
|
||||||
所有功能查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/1-%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>
|
|
||||||
|
|
||||||
## 🧩插件生态
|
|
||||||
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
⭐我们已经支持了[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling),请查看[Wiki内容函数](https://github.com/RockChinQ/QChatGPT/wiki/6-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8-%E5%86%85%E5%AE%B9%E5%87%BD%E6%95%B0)
|
|
||||||
|
|
||||||
> 使用方法见:[Wiki插件使用](https://github.com/RockChinQ/QChatGPT/wiki/5-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)
|
|
||||||
> 开发教程见:[Wiki插件开发](https://github.com/RockChinQ/QChatGPT/wiki/7-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91)
|
|
||||||
|
|
||||||
|
|
||||||
[所有插件列表](https://github.com/stars/RockChinQ/lists/qchatgpt-%E6%8F%92%E4%BB%B6),欢迎提出issue以提交新的插件
|
|
||||||
|
|
||||||
### 部分插件
|
|
||||||
|
|
||||||
- [WebwlkrPlugin](https://github.com/RockChinQ/WebwlkrPlugin) - 让机器人能联网!!
|
|
||||||
- [revLibs](https://github.com/RockChinQ/revLibs) - 将ChatGPT网页版、Claude、Bard、Hugging Chat等破解版接入此项目,关于[官方接口和网页版有什么区别](https://github.com/RockChinQ/QChatGPT/wiki/8-%E5%AE%98%E6%96%B9%E6%8E%A5%E5%8F%A3%E3%80%81ChatGPT%E7%BD%91%E9%A1%B5%E7%89%88%E3%80%81ChatGPT-API%E5%8C%BA%E5%88%AB)
|
|
||||||
- [Switcher](https://github.com/RockChinQ/Switcher) - 支持通过命令切换使用的模型
|
|
||||||
- [hello_plugin](https://github.com/RockChinQ/hello_plugin) - `hello_plugin` 的储存库形式,插件开发模板
|
|
||||||
- [oliverkirk-sudo/chat_voice](https://github.com/oliverkirk-sudo/chat_voice) - 文字转语音输出,支持HuggingFace上的[VITS模型](https://huggingface.co/spaces/Plachta/VITS-Umamusume-voice-synthesizer),azure语音合成,vits本地语音合成,sovits语音合成
|
|
||||||
- [RockChinQ/WaitYiYan](https://github.com/RockChinQ/WaitYiYan) - 实时获取百度`文心一言`等待列表人数
|
|
||||||
- [chordfish-k/QChartGPT_Emoticon_Plugin](https://github.com/chordfish-k/QChartGPT_Emoticon_Plugin) - 使机器人根据回复内容发送表情包
|
|
||||||
- [oliverkirk-sudo/ChatPoeBot](https://github.com/oliverkirk-sudo/ChatPoeBot) - 接入[Poe](https://poe.com/)上的机器人
|
|
||||||
- [lieyanqzu/WeatherPlugin](https://github.com/lieyanqzu/WeatherPlugin) - 天气查询插件
|
|
||||||
- [SysStatPlugin](https://github.com/RockChinQ/SysStatPlugin) - 查看系统状态
|
|
||||||
- [oliverkirk-sudo/qchat_system_status](https://github.com/oliverkirk-sudo/qchat_system_status) - 以图片的形式输出系统状态
|
|
||||||
- [oliverkirk-sudo/QChatAIPaint](https://github.com/oliverkirk-sudo/QChatAIPaint) - 基于[Holara](https://holara.ai/)的ai绘图插件
|
|
||||||
- [oliverkirk-sudo/QChatCodeRunner](https://github.com/oliverkirk-sudo/QChatCodeRunner) - 基于[CodeRunner-Plugin](https://github.com/oliverkirk-sudo/CodeRunner-Plugin)的代码运行与图表生成插件
|
|
||||||
- [oliverkirk-sudo/QChatWeather](https://github.com/oliverkirk-sudo/QChatWeather) - 生成好看的天气图片,基于和风天气
|
|
||||||
- [oliverkirk-sudo/QChatMarkdown](https://github.com/oliverkirk-sudo/QChatMarkdown) - 将机器人输出的markdown转换为图片,基于[playwright](https://playwright.dev/python/docs/intro)
|
|
||||||
- [ruuuux/WikipediaSearch](https://github.com/ruuuux/WikipediaSearch) - Wikipedia 搜索插件
|
|
||||||
- [zuo-shi-yun/discountAssistant](https://github.com/zuo-shi-yun/discountAssistant) - 自动筛选并发送羊毛群内的优惠券
|
|
||||||
- [zuo-shi-yun/Gatekeeper ](https://github.com/zuo-shi-yun/Gatekeeper) - QChatGPT的看门狗,包含黑白名单、临时用户机制
|
|
||||||
- [zuo-shi-yun/AutoSwitchProxy](https://github.com/zuo-shi-yun/AutoSwitchProxy) - 自动切换Clash代理模式以节省流量
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
<summary>
|
|
||||||
|
|
||||||
## 😘致谢和赞赏
|
|
||||||
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
- [@the-lazy-me](https://github.com/the-lazy-me) 为本项目制作[视频教程](https://www.bilibili.com/video/BV1Y14y1Q7kQ)
|
|
||||||
- [@mikumifa](https://github.com/mikumifa) 本项目Docker部署仓库开发者
|
|
||||||
- [@dominoar](https://github.com/dominoar) 为本项目开发多种插件
|
|
||||||
- [@万神的星空](https://github.com/qq255204159) 整合包发行
|
|
||||||
- [@ljcduo](https://github.com/ljcduo) GPT-4 API内测账号提供
|
|
||||||
|
|
||||||
以及所有[贡献者](https://github.com/RockChinQ/QChatGPT/graphs/contributors)和其他为本项目提供支持的朋友们。
|
|
||||||
|
|
||||||
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|||||||
31
main.py
31
main.py
@@ -122,7 +122,7 @@ def complete_tips():
|
|||||||
non_exist_keys = []
|
non_exist_keys = []
|
||||||
|
|
||||||
is_integrity = True
|
is_integrity = True
|
||||||
logging.info("检查tips模块完整性.")
|
logging.debug("检查tips模块完整性.")
|
||||||
tips_template = importlib.import_module('tips-custom-template')
|
tips_template = importlib.import_module('tips-custom-template')
|
||||||
tips = importlib.import_module('tips')
|
tips = importlib.import_module('tips')
|
||||||
for key in dir(tips_template):
|
for key in dir(tips_template):
|
||||||
@@ -145,6 +145,10 @@ async def start_process(first_time_init=False):
|
|||||||
global known_exception_caught
|
global known_exception_caught
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
|
||||||
|
# 计算host和instance标识符
|
||||||
|
import pkg.audit.identifier
|
||||||
|
pkg.audit.identifier.init()
|
||||||
|
|
||||||
# 加载配置
|
# 加载配置
|
||||||
cfg_inst: pymodule_cfg.PythonModuleConfigFile = pymodule_cfg.PythonModuleConfigFile(
|
cfg_inst: pymodule_cfg.PythonModuleConfigFile = pymodule_cfg.PythonModuleConfigFile(
|
||||||
'config.py',
|
'config.py',
|
||||||
@@ -158,6 +162,7 @@ async def start_process(first_time_init=False):
|
|||||||
complete_tips()
|
complete_tips()
|
||||||
|
|
||||||
cfg = pkg.utils.context.get_config_manager().data
|
cfg = pkg.utils.context.get_config_manager().data
|
||||||
|
|
||||||
# 更新openai库到最新版本
|
# 更新openai库到最新版本
|
||||||
if 'upgrade_dependencies' not in cfg or cfg['upgrade_dependencies']:
|
if 'upgrade_dependencies' not in cfg or cfg['upgrade_dependencies']:
|
||||||
print("正在更新依赖库,请等待...")
|
print("正在更新依赖库,请等待...")
|
||||||
@@ -204,6 +209,24 @@ async def start_process(first_time_init=False):
|
|||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("请输入数字")
|
print("请输入数字")
|
||||||
|
|
||||||
|
# 初始化中央服务器 API 交互实例
|
||||||
|
from pkg.utils.center import apigroup
|
||||||
|
from pkg.utils.center import v2 as center_v2
|
||||||
|
|
||||||
|
center_v2_api = center_v2.V2CenterAPI(
|
||||||
|
basic_info={
|
||||||
|
"host_id": pkg.audit.identifier.identifier['host_id'],
|
||||||
|
"instance_id": pkg.audit.identifier.identifier['instance_id'],
|
||||||
|
"semantic_version": pkg.utils.updater.get_current_tag(),
|
||||||
|
"platform": sys.platform,
|
||||||
|
},
|
||||||
|
runtime_info={
|
||||||
|
"admin_id": "{}".format(cfg['admin_qq']),
|
||||||
|
"msg_source": cfg['msg_source_adapter'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
pkg.utils.context.set_center_v2_api(center_v2_api)
|
||||||
|
|
||||||
import pkg.openai.manager
|
import pkg.openai.manager
|
||||||
import pkg.database.manager
|
import pkg.database.manager
|
||||||
@@ -375,6 +398,12 @@ async def start_process(first_time_init=False):
|
|||||||
if len(new_announcement) > 0:
|
if len(new_announcement) > 0:
|
||||||
for announcement in new_announcement:
|
for announcement in new_announcement:
|
||||||
logging.critical("[公告]<{}> {}".format(announcement['time'], announcement['content']))
|
logging.critical("[公告]<{}> {}".format(announcement['time'], announcement['content']))
|
||||||
|
|
||||||
|
# 发送统计数据
|
||||||
|
pkg.utils.context.get_center_v2_api().main.post_announcement_showed(
|
||||||
|
[announcement['id'] for announcement in new_announcement]
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("获取公告失败:{}".format(e))
|
logging.warning("获取公告失败:{}".format(e))
|
||||||
|
|
||||||
|
|||||||
83
pkg/audit/identifier.py
Normal file
83
pkg/audit/identifier.py
Normal 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)
|
||||||
@@ -91,7 +91,7 @@ class DatabaseManager:
|
|||||||
`json` text not null
|
`json` text not null
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
print('Database initialized.')
|
# print('Database initialized.')
|
||||||
|
|
||||||
# session持久化
|
# session持久化
|
||||||
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
|
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from openai.types.chat import chat_completion_message
|
|||||||
|
|
||||||
from .model import RequestBase
|
from .model import RequestBase
|
||||||
from .. import funcmgr
|
from .. import funcmgr
|
||||||
|
from ...plugin import host
|
||||||
|
from ...utils import context
|
||||||
|
|
||||||
|
|
||||||
class ChatCompletionRequest(RequestBase):
|
class ChatCompletionRequest(RequestBase):
|
||||||
@@ -189,6 +191,16 @@ class ChatCompletionRequest(RequestBase):
|
|||||||
ret = "error: execute function failed: {}".format(str(e))
|
ret = "error: execute function failed: {}".format(str(e))
|
||||||
logging.error("函数执行失败: {}".format(str(e)))
|
logging.error("函数执行失败: {}".format(str(e)))
|
||||||
|
|
||||||
|
# 上报数据
|
||||||
|
plugin_info = host.get_plugin_info_for_audit(func_name.split('-')[0])
|
||||||
|
audit_func_name = func_name.split('-')[1]
|
||||||
|
audit_func_desc = funcmgr.get_func_schema(func_name)['description']
|
||||||
|
context.get_center_v2_api().usage.post_function_record(
|
||||||
|
plugin=plugin_info,
|
||||||
|
function_name=audit_func_name,
|
||||||
|
function_description=audit_func_desc,
|
||||||
|
)
|
||||||
|
|
||||||
self.append_message(
|
self.append_message(
|
||||||
role="function",
|
role="function",
|
||||||
content=json.dumps(ret, ensure_ascii=False),
|
content=json.dumps(ret, ensure_ascii=False),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class KeysManager:
|
|||||||
if self.api_key[key_name] not in self.exceeded:
|
if self.api_key[key_name] not in self.exceeded:
|
||||||
self.using_key = self.api_key[key_name]
|
self.using_key = self.api_key[key_name]
|
||||||
|
|
||||||
logging.info("使用api-key:" + key_name)
|
logging.debug("使用api-key:" + key_name)
|
||||||
|
|
||||||
# 触发插件事件
|
# 触发插件事件
|
||||||
args = {
|
args = {
|
||||||
|
|||||||
@@ -261,6 +261,8 @@ class Session:
|
|||||||
|
|
||||||
pending_res_text = ""
|
pending_res_text = ""
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
# TODO 对不起,我知道这样非常非常屎山,但我之后会重构的
|
# TODO 对不起,我知道这样非常非常屎山,但我之后会重构的
|
||||||
for resp in context.get_openai_manager().request_completion(prompts):
|
for resp in context.get_openai_manager().request_completion(prompts):
|
||||||
|
|
||||||
@@ -349,6 +351,26 @@ class Session:
|
|||||||
self.just_switched_to_exist_session = False
|
self.just_switched_to_exist_session = False
|
||||||
self.set_ongoing()
|
self.set_ongoing()
|
||||||
|
|
||||||
|
# 上报使用量数据
|
||||||
|
session_type = session_name_spt[0]
|
||||||
|
session_id = session_name_spt[1]
|
||||||
|
|
||||||
|
ability_provider = "QChatGPT.Text"
|
||||||
|
usage = total_tokens
|
||||||
|
model_name = context.get_config_manager().data['completion_api_params']['model']
|
||||||
|
response_seconds = int(time.time() - start_time)
|
||||||
|
retry_times = -1 # 暂不记录
|
||||||
|
|
||||||
|
context.get_center_v2_api().usage.post_query_record(
|
||||||
|
session_type=session_type,
|
||||||
|
session_id=session_id,
|
||||||
|
query_ability_provider=ability_provider,
|
||||||
|
usage=usage,
|
||||||
|
model_name=model_name,
|
||||||
|
response_seconds=response_seconds,
|
||||||
|
retry_times=retry_times
|
||||||
|
)
|
||||||
|
|
||||||
return res_ans if res_ans[0] != '\n' else res_ans[1:], finish_reason, funcs
|
return res_ans if res_ans[0] != '\n' else res_ans[1:], finish_reason, funcs
|
||||||
|
|
||||||
# 删除上一回合并返回上一回合的问题
|
# 删除上一回合并返回上一回合的问题
|
||||||
|
|||||||
@@ -84,23 +84,34 @@ def iter_plugins_name():
|
|||||||
__current_module_path__ = ""
|
__current_module_path__ = ""
|
||||||
|
|
||||||
|
|
||||||
def walk_plugin_path(module, prefix='', path_prefix=''):
|
def walk_plugin_path(module, prefix="", path_prefix=""):
|
||||||
global __current_module_path__
|
global __current_module_path__
|
||||||
"""遍历插件路径"""
|
"""遍历插件路径"""
|
||||||
for item in pkgutil.iter_modules(module.__path__):
|
for item in pkgutil.iter_modules(module.__path__):
|
||||||
if item.ispkg:
|
if item.ispkg:
|
||||||
logging.debug("扫描插件包: plugins/{}".format(path_prefix + item.name))
|
logging.debug("扫描插件包: plugins/{}".format(path_prefix + item.name))
|
||||||
walk_plugin_path(__import__(module.__name__ + '.' + item.name, fromlist=['']),
|
walk_plugin_path(
|
||||||
prefix + item.name + '.', path_prefix + item.name + '/')
|
__import__(module.__name__ + "." + item.name, fromlist=[""]),
|
||||||
|
prefix + item.name + ".",
|
||||||
|
path_prefix + item.name + "/",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
logging.debug("扫描插件模块: plugins/{}".format(path_prefix + item.name + '.py'))
|
logging.debug(
|
||||||
__current_module_path__ = "plugins/"+path_prefix + item.name + '.py'
|
"扫描插件模块: plugins/{}".format(path_prefix + item.name + ".py")
|
||||||
|
)
|
||||||
|
__current_module_path__ = "plugins/" + path_prefix + item.name + ".py"
|
||||||
|
|
||||||
importlib.import_module(module.__name__ + '.' + item.name)
|
importlib.import_module(module.__name__ + "." + item.name)
|
||||||
logging.debug('加载模块: plugins/{} 成功'.format(path_prefix + item.name + '.py'))
|
logging.debug(
|
||||||
|
"加载模块: plugins/{} 成功".format(path_prefix + item.name + ".py")
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
logging.error('加载模块: plugins/{} 失败: {}'.format(path_prefix + item.name + '.py', sys.exc_info()))
|
logging.error(
|
||||||
|
"加载模块: plugins/{} 失败: {}".format(
|
||||||
|
path_prefix + item.name + ".py", sys.exc_info()
|
||||||
|
)
|
||||||
|
)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
@@ -108,7 +119,7 @@ def load_plugins():
|
|||||||
"""加载插件"""
|
"""加载插件"""
|
||||||
logging.debug("加载插件")
|
logging.debug("加载插件")
|
||||||
PluginHost()
|
PluginHost()
|
||||||
walk_plugin_path(__import__('plugins'))
|
walk_plugin_path(__import__("plugins"))
|
||||||
|
|
||||||
logging.debug(__plugins__)
|
logging.debug(__plugins__)
|
||||||
|
|
||||||
@@ -132,7 +143,7 @@ def load_plugins():
|
|||||||
|
|
||||||
def initialize_plugins():
|
def initialize_plugins():
|
||||||
"""初始化插件"""
|
"""初始化插件"""
|
||||||
logging.info("初始化插件")
|
logging.debug("初始化插件")
|
||||||
import pkg.plugin.models as models
|
import pkg.plugin.models as models
|
||||||
|
|
||||||
successfully_initialized_plugins = []
|
successfully_initialized_plugins = []
|
||||||
@@ -141,14 +152,14 @@ def initialize_plugins():
|
|||||||
# if not plugin['enabled']:
|
# if not plugin['enabled']:
|
||||||
# continue
|
# continue
|
||||||
try:
|
try:
|
||||||
models.__current_registering_plugin__ = plugin['name']
|
models.__current_registering_plugin__ = plugin["name"]
|
||||||
plugin['instance'] = plugin["class"](plugin_host=context.get_plugin_host())
|
plugin["instance"] = plugin["class"](plugin_host=context.get_plugin_host())
|
||||||
# logging.info("插件 {} 已初始化".format(plugin['name']))
|
# logging.info("插件 {} 已初始化".format(plugin['name']))
|
||||||
successfully_initialized_plugins.append(plugin['name'])
|
successfully_initialized_plugins.append(plugin["name"])
|
||||||
except:
|
except:
|
||||||
logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
logging.error("插件{}初始化时发生错误: {}".format(plugin["name"], sys.exc_info()))
|
||||||
logging.debug(traceback.format_exc())
|
logging.debug(traceback.format_exc())
|
||||||
|
|
||||||
logging.info("以下插件已初始化: {}".format(", ".join(successfully_initialized_plugins)))
|
logging.info("以下插件已初始化: {}".format(", ".join(successfully_initialized_plugins)))
|
||||||
|
|
||||||
|
|
||||||
@@ -172,9 +183,12 @@ def get_github_plugin_repo_label(repo_url: str) -> list[str]:
|
|||||||
"""获取username, repo"""
|
"""获取username, repo"""
|
||||||
|
|
||||||
# 提取 username/repo , 正则表达式
|
# 提取 username/repo , 正则表达式
|
||||||
repo = re.findall(r'(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)', repo_url)
|
repo = re.findall(
|
||||||
|
r"(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)",
|
||||||
|
repo_url,
|
||||||
|
)
|
||||||
|
|
||||||
if len(repo) > 0: # github
|
if len(repo) > 0: # github
|
||||||
return repo[0].split("/")
|
return repo[0].split("/")
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -183,53 +197,52 @@ def get_github_plugin_repo_label(repo_url: str) -> list[str]:
|
|||||||
def download_plugin_source_code(repo_url: str, target_path: str) -> str:
|
def download_plugin_source_code(repo_url: str, target_path: str) -> str:
|
||||||
"""下载插件源码"""
|
"""下载插件源码"""
|
||||||
# 检查源类型
|
# 检查源类型
|
||||||
|
|
||||||
# 提取 username/repo , 正则表达式
|
# 提取 username/repo , 正则表达式
|
||||||
repo = get_github_plugin_repo_label(repo_url)
|
repo = get_github_plugin_repo_label(repo_url)
|
||||||
|
|
||||||
target_path += repo[1]
|
target_path += repo[1]
|
||||||
|
|
||||||
if repo is not None: # github
|
if repo is not None: # github
|
||||||
logging.info("从 GitHub 下载插件源码...")
|
logging.info("从 GitHub 下载插件源码...")
|
||||||
|
|
||||||
zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD"
|
zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD"
|
||||||
|
|
||||||
zip_resp = requests.get(
|
zip_resp = requests.get(
|
||||||
url=zipball_url,
|
url=zipball_url, proxies=network.wrapper_proxies(), stream=True
|
||||||
proxies=network.wrapper_proxies(),
|
|
||||||
stream=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if zip_resp.status_code != 200:
|
if zip_resp.status_code != 200:
|
||||||
raise Exception("下载源码失败: {}".format(zip_resp.text))
|
raise Exception("下载源码失败: {}".format(zip_resp.text))
|
||||||
|
|
||||||
if os.path.exists("temp/"+target_path):
|
if os.path.exists("temp/" + target_path):
|
||||||
shutil.rmtree("temp/"+target_path)
|
shutil.rmtree("temp/" + target_path)
|
||||||
|
|
||||||
if os.path.exists(target_path):
|
if os.path.exists(target_path):
|
||||||
shutil.rmtree(target_path)
|
shutil.rmtree(target_path)
|
||||||
|
|
||||||
os.makedirs("temp/"+target_path)
|
os.makedirs("temp/" + target_path)
|
||||||
|
|
||||||
with open("temp/"+target_path+"/source.zip", "wb") as f:
|
with open("temp/" + target_path + "/source.zip", "wb") as f:
|
||||||
for chunk in zip_resp.iter_content(chunk_size=1024):
|
for chunk in zip_resp.iter_content(chunk_size=1024):
|
||||||
if chunk:
|
if chunk:
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
|
|
||||||
logging.info("下载完成, 解压...")
|
logging.info("下载完成, 解压...")
|
||||||
import zipfile
|
import zipfile
|
||||||
with zipfile.ZipFile("temp/"+target_path+"/source.zip", 'r') as zip_ref:
|
|
||||||
zip_ref.extractall("temp/"+target_path)
|
with zipfile.ZipFile("temp/" + target_path + "/source.zip", "r") as zip_ref:
|
||||||
os.remove("temp/"+target_path+"/source.zip")
|
zip_ref.extractall("temp/" + target_path)
|
||||||
|
os.remove("temp/" + target_path + "/source.zip")
|
||||||
|
|
||||||
# 目标是 username-repo-hash , 用正则表达式提取完整的文件夹名,复制到 plugins/repo
|
# 目标是 username-repo-hash , 用正则表达式提取完整的文件夹名,复制到 plugins/repo
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
# 获取解压后的文件夹名
|
# 获取解压后的文件夹名
|
||||||
unzip_dir = glob.glob("temp/"+target_path+"/*")[0]
|
unzip_dir = glob.glob("temp/" + target_path + "/*")[0]
|
||||||
|
|
||||||
# 复制到 plugins/repo
|
# 复制到 plugins/repo
|
||||||
shutil.copytree(unzip_dir, target_path+"/")
|
shutil.copytree(unzip_dir, target_path + "/")
|
||||||
|
|
||||||
# 删除解压后的文件夹
|
# 删除解压后的文件夹
|
||||||
shutil.rmtree(unzip_dir)
|
shutil.rmtree(unzip_dir)
|
||||||
@@ -237,18 +250,20 @@ def download_plugin_source_code(repo_url: str, target_path: str) -> str:
|
|||||||
logging.info("解压完成")
|
logging.info("解压完成")
|
||||||
else:
|
else:
|
||||||
raise Exception("暂不支持的源类型,请使用 GitHub 仓库发行插件。")
|
raise Exception("暂不支持的源类型,请使用 GitHub 仓库发行插件。")
|
||||||
|
|
||||||
return repo[1]
|
return repo[1]
|
||||||
|
|
||||||
|
|
||||||
def check_requirements(path: str):
|
def check_requirements(path: str):
|
||||||
# 检查此目录是否包含requirements.txt
|
# 检查此目录是否包含requirements.txt
|
||||||
if os.path.exists(path+"/requirements.txt"):
|
if os.path.exists(path + "/requirements.txt"):
|
||||||
logging.info("检测到requirements.txt,正在安装依赖")
|
logging.info("检测到requirements.txt,正在安装依赖")
|
||||||
import pkg.utils.pkgmgr
|
import pkg.utils.pkgmgr
|
||||||
pkg.utils.pkgmgr.install_requirements(path+"/requirements.txt")
|
|
||||||
|
pkg.utils.pkgmgr.install_requirements(path + "/requirements.txt")
|
||||||
|
|
||||||
import pkg.utils.log as log
|
import pkg.utils.log as log
|
||||||
|
|
||||||
log.reset_logging()
|
log.reset_logging()
|
||||||
|
|
||||||
|
|
||||||
@@ -257,25 +272,43 @@ def install_plugin(repo_url: str):
|
|||||||
|
|
||||||
repo_label = download_plugin_source_code(repo_url, "plugins/")
|
repo_label = download_plugin_source_code(repo_url, "plugins/")
|
||||||
|
|
||||||
check_requirements("plugins/"+repo_label)
|
check_requirements("plugins/" + repo_label)
|
||||||
|
|
||||||
metadata.set_plugin_metadata(repo_label, repo_url, int(time.time()), "HEAD")
|
metadata.set_plugin_metadata(repo_label, repo_url, int(time.time()), "HEAD")
|
||||||
|
|
||||||
|
# 上报安装记录
|
||||||
|
context.get_center_v2_api().plugin.post_install_record(
|
||||||
|
plugin={
|
||||||
|
"name": "unknown",
|
||||||
|
"remote": repo_url,
|
||||||
|
"author": "unknown",
|
||||||
|
"version": "HEAD",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def uninstall_plugin(plugin_name: str) -> str:
|
def uninstall_plugin(plugin_name: str) -> str:
|
||||||
"""卸载插件"""
|
"""卸载插件"""
|
||||||
if plugin_name not in __plugins__:
|
if plugin_name not in __plugins__:
|
||||||
raise Exception("插件不存在")
|
raise Exception("插件不存在")
|
||||||
|
|
||||||
|
plugin_info = get_plugin_info_for_audit(plugin_name)
|
||||||
|
|
||||||
# 获取文件夹路径
|
# 获取文件夹路径
|
||||||
plugin_path = __plugins__[plugin_name]['path'].replace("\\", "/")
|
plugin_path = __plugins__[plugin_name]["path"].replace("\\", "/")
|
||||||
|
|
||||||
# 剪切路径为plugins/插件名
|
# 剪切路径为plugins/插件名
|
||||||
plugin_path = plugin_path.split("plugins/")[1].split("/")[0]
|
plugin_path = plugin_path.split("plugins/")[1].split("/")[0]
|
||||||
|
|
||||||
# 删除文件夹
|
# 删除文件夹
|
||||||
shutil.rmtree("plugins/"+plugin_path)
|
shutil.rmtree("plugins/" + plugin_path)
|
||||||
return "plugins/"+plugin_path
|
|
||||||
|
# 上报卸载记录
|
||||||
|
context.get_center_v2_api().plugin.post_remove_record(
|
||||||
|
plugin=plugin_info
|
||||||
|
)
|
||||||
|
|
||||||
|
return "plugins/" + plugin_path
|
||||||
|
|
||||||
|
|
||||||
def update_plugin(plugin_name: str):
|
def update_plugin(plugin_name: str):
|
||||||
@@ -287,12 +320,26 @@ def update_plugin(plugin_name: str):
|
|||||||
|
|
||||||
if meta == {}:
|
if meta == {}:
|
||||||
raise Exception("没有此插件元数据信息,无法更新")
|
raise Exception("没有此插件元数据信息,无法更新")
|
||||||
|
|
||||||
remote_url = meta['source']
|
|
||||||
if remote_url == "https://github.com/RockChinQ/QChatGPT" or remote_url == "https://gitee.com/RockChin/QChatGPT" \
|
|
||||||
or remote_url == "" or remote_url is None or remote_url == "http://github.com/RockChinQ/QChatGPT" or remote_url == "http://gitee.com/RockChin/QChatGPT":
|
|
||||||
raise Exception("插件没有远程地址记录,无法更新")
|
|
||||||
|
|
||||||
|
old_plugin_info = get_plugin_info_for_audit(plugin_name)
|
||||||
|
|
||||||
|
context.get_center_v2_api().plugin.post_update_record(
|
||||||
|
plugin=old_plugin_info,
|
||||||
|
old_version=old_plugin_info['version'],
|
||||||
|
new_version='HEAD',
|
||||||
|
)
|
||||||
|
|
||||||
|
remote_url = meta["source"]
|
||||||
|
if (
|
||||||
|
remote_url == "https://github.com/RockChinQ/QChatGPT"
|
||||||
|
or remote_url == "https://gitee.com/RockChin/QChatGPT"
|
||||||
|
or remote_url == ""
|
||||||
|
or remote_url is None
|
||||||
|
or remote_url == "http://github.com/RockChinQ/QChatGPT"
|
||||||
|
or remote_url == "http://gitee.com/RockChin/QChatGPT"
|
||||||
|
):
|
||||||
|
raise Exception("插件没有远程地址记录,无法更新")
|
||||||
|
|
||||||
# 重新安装插件
|
# 重新安装插件
|
||||||
logging.info("正在重新安装插件以进行更新...")
|
logging.info("正在重新安装插件以进行更新...")
|
||||||
|
|
||||||
@@ -301,7 +348,7 @@ def update_plugin(plugin_name: str):
|
|||||||
|
|
||||||
def get_plugin_name_by_path_name(plugin_path_name: str) -> str:
|
def get_plugin_name_by_path_name(plugin_path_name: str) -> str:
|
||||||
for k, v in __plugins__.items():
|
for k, v in __plugins__.items():
|
||||||
if v['path'] == "plugins/"+plugin_path_name+"/main.py":
|
if v["path"] == "plugins/" + plugin_path_name + "/main.py":
|
||||||
return k
|
return k
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -309,8 +356,8 @@ def get_plugin_name_by_path_name(plugin_path_name: str) -> str:
|
|||||||
def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str:
|
def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str:
|
||||||
if plugin_name not in __plugins__:
|
if plugin_name not in __plugins__:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
plugin_main_module_path = __plugins__[plugin_name]['path']
|
plugin_main_module_path = __plugins__[plugin_name]["path"]
|
||||||
|
|
||||||
plugin_main_module_path = plugin_main_module_path.replace("\\", "/")
|
plugin_main_module_path = plugin_main_module_path.replace("\\", "/")
|
||||||
|
|
||||||
@@ -319,8 +366,29 @@ def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str:
|
|||||||
return spt[1]
|
return spt[1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_info_for_audit(plugin_name: str) -> dict:
|
||||||
|
"""获取插件信息"""
|
||||||
|
if plugin_name not in __plugins__:
|
||||||
|
return {}
|
||||||
|
plugin = __plugins__[plugin_name]
|
||||||
|
|
||||||
|
name = plugin["name"]
|
||||||
|
meta = metadata.get_plugin_metadata(get_plugin_path_name_by_plugin_name(name))
|
||||||
|
remote = meta["source"] if meta != {} else ""
|
||||||
|
author = plugin["author"]
|
||||||
|
version = plugin["version"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"remote": remote,
|
||||||
|
"author": author,
|
||||||
|
"version": version,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EventContext:
|
class EventContext:
|
||||||
"""事件上下文"""
|
"""事件上下文"""
|
||||||
|
|
||||||
eid = 0
|
eid = 0
|
||||||
"""事件编号"""
|
"""事件编号"""
|
||||||
|
|
||||||
@@ -395,6 +463,7 @@ class EventContext:
|
|||||||
def emit(event_name: str, **kwargs) -> EventContext:
|
def emit(event_name: str, **kwargs) -> EventContext:
|
||||||
"""触发事件"""
|
"""触发事件"""
|
||||||
import pkg.utils.context as context
|
import pkg.utils.context as context
|
||||||
|
|
||||||
if context.get_plugin_host() is None:
|
if context.get_plugin_host() is None:
|
||||||
return None
|
return None
|
||||||
return context.get_plugin_host().emit(event_name, **kwargs)
|
return context.get_plugin_host().emit(event_name, **kwargs)
|
||||||
@@ -443,9 +512,10 @@ class PluginHost:
|
|||||||
|
|
||||||
event_context = EventContext(event_name)
|
event_context = EventContext(event_name)
|
||||||
logging.debug("触发事件: {} ({})".format(event_name, event_context.eid))
|
logging.debug("触发事件: {} ({})".format(event_name, event_context.eid))
|
||||||
|
|
||||||
|
emitted_plugins = []
|
||||||
for plugin in iter_plugins():
|
for plugin in iter_plugins():
|
||||||
|
if not plugin["enabled"]:
|
||||||
if not plugin['enabled']:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# if plugin['instance'] is None:
|
# if plugin['instance'] is None:
|
||||||
@@ -457,9 +527,11 @@ class PluginHost:
|
|||||||
# logging.error("插件 {} 初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
# logging.error("插件 {} 初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
||||||
# continue
|
# continue
|
||||||
|
|
||||||
if 'hooks' not in plugin or event_name not in plugin['hooks']:
|
if "hooks" not in plugin or event_name not in plugin["hooks"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
emitted_plugins.append(plugin['name'])
|
||||||
|
|
||||||
hooks = []
|
hooks = []
|
||||||
if event_name in plugin["hooks"]:
|
if event_name in plugin["hooks"]:
|
||||||
hooks = plugin["hooks"][event_name]
|
hooks = plugin["hooks"][event_name]
|
||||||
@@ -467,27 +539,44 @@ class PluginHost:
|
|||||||
try:
|
try:
|
||||||
already_prevented_default = event_context.is_prevented_default()
|
already_prevented_default = event_context.is_prevented_default()
|
||||||
|
|
||||||
kwargs['host'] = context.get_plugin_host()
|
kwargs["host"] = context.get_plugin_host()
|
||||||
kwargs['event'] = event_context
|
kwargs["event"] = event_context
|
||||||
|
|
||||||
hook(plugin['instance'], **kwargs)
|
hook(plugin["instance"], **kwargs)
|
||||||
|
|
||||||
if event_context.is_prevented_default() and not already_prevented_default:
|
if (
|
||||||
logging.debug("插件 {} 已要求阻止事件 {} 的默认行为".format(plugin['name'], event_name))
|
event_context.is_prevented_default()
|
||||||
|
and not already_prevented_default
|
||||||
|
):
|
||||||
|
logging.debug(
|
||||||
|
"插件 {} 已要求阻止事件 {} 的默认行为".format(plugin["name"], event_name)
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("插件{}响应事件{}时发生错误".format(plugin['name'], event_name))
|
logging.error("插件{}响应事件{}时发生错误".format(plugin["name"], event_name))
|
||||||
logging.error(traceback.format_exc())
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
# print("done:{}".format(plugin['name']))
|
# print("done:{}".format(plugin['name']))
|
||||||
if event_context.is_prevented_postorder():
|
if event_context.is_prevented_postorder():
|
||||||
logging.debug("插件 {} 阻止了后序插件的执行".format(plugin['name']))
|
logging.debug("插件 {} 阻止了后序插件的执行".format(plugin["name"]))
|
||||||
break
|
break
|
||||||
|
|
||||||
logging.debug("事件 {} ({}) 处理完毕,返回值: {}".format(event_name, event_context.eid,
|
logging.debug(
|
||||||
event_context.__return_value__))
|
"事件 {} ({}) 处理完毕,返回值: {}".format(
|
||||||
|
event_name, event_context.eid, event_context.__return_value__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(emitted_plugins) > 0:
|
||||||
|
plugins_info = [get_plugin_info_for_audit(p) for p in emitted_plugins]
|
||||||
|
|
||||||
|
context.get_center_v2_api().usage.post_event_record(
|
||||||
|
plugins=plugins_info,
|
||||||
|
event_name=event_name,
|
||||||
|
)
|
||||||
|
|
||||||
return event_context
|
return event_context
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import pkgutil
|
|||||||
import traceback
|
import traceback
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
__command_list__ = {}
|
|
||||||
|
|
||||||
import tips as tips_custom
|
import tips as tips_custom
|
||||||
|
|
||||||
|
|
||||||
|
__command_list__ = {}
|
||||||
"""命令树
|
"""命令树
|
||||||
|
|
||||||
结构:
|
结构:
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ from .. import aamgr
|
|||||||
def config_operation(cmd, params):
|
def config_operation(cmd, params):
|
||||||
reply = []
|
reply = []
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
config = pkg.utils.context.get_config()
|
# config = pkg.utils.context.get_config()
|
||||||
|
cfg_mgr = pkg.utils.context.get_config_manager()
|
||||||
|
|
||||||
|
false = False
|
||||||
|
true = True
|
||||||
|
|
||||||
reply_str = ""
|
reply_str = ""
|
||||||
if len(params) == 0:
|
if len(params) == 0:
|
||||||
reply = ["[bot]err:请输入!cmd cfg查看使用方法"]
|
reply = ["[bot]err:请输入!cmd cfg查看使用方法"]
|
||||||
@@ -14,25 +19,25 @@ def config_operation(cmd, params):
|
|||||||
cfg_name = params[0]
|
cfg_name = params[0]
|
||||||
if cfg_name == 'all':
|
if cfg_name == 'all':
|
||||||
reply_str = "[bot]所有配置项:\n\n"
|
reply_str = "[bot]所有配置项:\n\n"
|
||||||
for cfg in dir(config):
|
for cfg in cfg_mgr.data.keys():
|
||||||
if not cfg.startswith('__') and not cfg == 'logging':
|
if not cfg.startswith('__') and not cfg == 'logging':
|
||||||
# 根据配置项类型进行格式化,如果是字典则转换为json并格式化
|
# 根据配置项类型进行格式化,如果是字典则转换为json并格式化
|
||||||
if isinstance(getattr(config, cfg), str):
|
if isinstance(cfg_mgr.data[cfg], str):
|
||||||
reply_str += "{}: \"{}\"\n".format(cfg, getattr(config, cfg))
|
reply_str += "{}: \"{}\"\n".format(cfg, cfg_mgr.data[cfg])
|
||||||
elif isinstance(getattr(config, cfg), dict):
|
elif isinstance(cfg_mgr.data[cfg], dict):
|
||||||
# 不进行unicode转义,并格式化
|
# 不进行unicode转义,并格式化
|
||||||
reply_str += "{}: {}\n".format(cfg,
|
reply_str += "{}: {}\n".format(cfg,
|
||||||
json.dumps(getattr(config, cfg),
|
json.dumps(cfg_mgr.data[cfg],
|
||||||
ensure_ascii=False, indent=4))
|
ensure_ascii=False, indent=4))
|
||||||
else:
|
else:
|
||||||
reply_str += "{}: {}\n".format(cfg, getattr(config, cfg))
|
reply_str += "{}: {}\n".format(cfg, cfg_mgr.data[cfg])
|
||||||
reply = [reply_str]
|
reply = [reply_str]
|
||||||
else:
|
else:
|
||||||
cfg_entry_path = cfg_name.split('.')
|
cfg_entry_path = cfg_name.split('.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(params) == 1:
|
if len(params) == 1: # 未指定配置值,返回配置项值
|
||||||
cfg_entry = getattr(config, cfg_entry_path[0])
|
cfg_entry = cfg_mgr.data[cfg_entry_path[0]]
|
||||||
if len(cfg_entry_path) > 1:
|
if len(cfg_entry_path) > 1:
|
||||||
for i in range(1, len(cfg_entry_path)):
|
for i in range(1, len(cfg_entry_path)):
|
||||||
cfg_entry = cfg_entry[cfg_entry_path[i]]
|
cfg_entry = cfg_entry[cfg_entry_path[i]]
|
||||||
@@ -48,23 +53,10 @@ def config_operation(cmd, params):
|
|||||||
reply = [reply_str]
|
reply = [reply_str]
|
||||||
else:
|
else:
|
||||||
cfg_value = " ".join(params[1:])
|
cfg_value = " ".join(params[1:])
|
||||||
# 类型转换,如果是json则转换为字典
|
|
||||||
# if cfg_value == 'true':
|
|
||||||
# cfg_value = True
|
|
||||||
# elif cfg_value == 'false':
|
|
||||||
# cfg_value = False
|
|
||||||
# elif cfg_value.isdigit():
|
|
||||||
# cfg_value = int(cfg_value)
|
|
||||||
# elif cfg_value.startswith('{') and cfg_value.endswith('}'):
|
|
||||||
# cfg_value = json.loads(cfg_value)
|
|
||||||
# else:
|
|
||||||
# try:
|
|
||||||
# cfg_value = float(cfg_value)
|
|
||||||
# except ValueError:
|
|
||||||
# pass
|
|
||||||
cfg_value = eval(cfg_value)
|
cfg_value = eval(cfg_value)
|
||||||
|
|
||||||
cfg_entry = getattr(config, cfg_entry_path[0])
|
cfg_entry = cfg_mgr.data[cfg_entry_path[0]]
|
||||||
if len(cfg_entry_path) > 1:
|
if len(cfg_entry_path) > 1:
|
||||||
for i in range(1, len(cfg_entry_path) - 1):
|
for i in range(1, len(cfg_entry_path) - 1):
|
||||||
cfg_entry = cfg_entry[cfg_entry_path[i]]
|
cfg_entry = cfg_entry[cfg_entry_path[i]]
|
||||||
@@ -74,14 +66,12 @@ def config_operation(cmd, params):
|
|||||||
else:
|
else:
|
||||||
reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
|
reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
|
||||||
else:
|
else:
|
||||||
setattr(config, cfg_entry_path[0], cfg_value)
|
cfg_mgr.data[cfg_entry_path[0]] = cfg_value
|
||||||
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
|
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
||||||
# else:
|
|
||||||
# reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ class QQBotManager:
|
|||||||
else:
|
else:
|
||||||
self.adapter = context.get_qqbot_manager().adapter
|
self.adapter = context.get_qqbot_manager().adapter
|
||||||
self.bot_account_id = context.get_qqbot_manager().bot_account_id
|
self.bot_account_id = context.get_qqbot_manager().bot_account_id
|
||||||
|
|
||||||
|
# 保存 account_id 到审计模块
|
||||||
|
from ..utils.center import apigroup
|
||||||
|
apigroup.APIGroup._runtime_info['account_id'] = "{}".format(self.bot_account_id)
|
||||||
|
|
||||||
context.set_qqbot_manager(self)
|
context.set_qqbot_manager(self)
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
|
|
||||||
processing.append(session_name)
|
processing.append(session_name)
|
||||||
try:
|
try:
|
||||||
|
msg_type = ''
|
||||||
if text_message.startswith('!') or text_message.startswith("!"): # 指令
|
if text_message.startswith('!') or text_message.startswith("!"): # 指令
|
||||||
|
msg_type = 'command'
|
||||||
# 触发插件事件
|
# 触发插件事件
|
||||||
args = {
|
args = {
|
||||||
'launcher_type': launcher_type,
|
'launcher_type': launcher_type,
|
||||||
@@ -110,6 +112,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
mgr, config, launcher_type, launcher_id, sender_id, is_admin(sender_id))
|
mgr, config, launcher_type, launcher_id, sender_id, is_admin(sender_id))
|
||||||
|
|
||||||
else: # 消息
|
else: # 消息
|
||||||
|
msg_type = 'message'
|
||||||
# 限速丢弃检查
|
# 限速丢弃检查
|
||||||
# print(ratelimit.__crt_minute_usage__[session_name])
|
# print(ratelimit.__crt_minute_usage__[session_name])
|
||||||
if config['rate_limit_strategy'] == "drop":
|
if config['rate_limit_strategy'] == "drop":
|
||||||
@@ -154,7 +157,9 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
"回复[{}]文字消息:{}".format(session_name,
|
"回复[{}]文字消息:{}".format(session_name,
|
||||||
reply[0][:min(100, len(reply[0]))] + (
|
reply[0][:min(100, len(reply[0]))] + (
|
||||||
"..." if len(reply[0]) > 100 else "")))
|
"..." if len(reply[0]) > 100 else "")))
|
||||||
reply = [mgr.reply_filter.process(reply[0])]
|
if msg_type == 'message':
|
||||||
|
reply = [mgr.reply_filter.process(reply[0])]
|
||||||
|
|
||||||
reply = blob.check_text(reply[0])
|
reply = blob.check_text(reply[0])
|
||||||
else:
|
else:
|
||||||
logging.info("回复[{}]消息".format(session_name))
|
logging.info("回复[{}]消息".format(session_name))
|
||||||
|
|||||||
0
pkg/utils/center/__init__.py
Normal file
0
pkg/utils/center/__init__.py
Normal file
88
pkg/utils/center/apigroup.py
Normal file
88
pkg/utils/center/apigroup.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import abc
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class APIGroup(metaclass=abc.ABCMeta):
|
||||||
|
"""API 组抽象类"""
|
||||||
|
_basic_info: dict = None
|
||||||
|
_runtime_info: dict = None
|
||||||
|
|
||||||
|
prefix = None
|
||||||
|
|
||||||
|
def __init__(self, prefix: str):
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
|
def do(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
path: str,
|
||||||
|
data: dict = None,
|
||||||
|
params: dict = None,
|
||||||
|
headers: dict = {},
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
"""执行一个请求"""
|
||||||
|
def thr_wrapper(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
path: str,
|
||||||
|
data: dict = None,
|
||||||
|
params: dict = None,
|
||||||
|
headers: dict = {},
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
url = self.prefix + path
|
||||||
|
data = json.dumps(data)
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
|
||||||
|
ret = requests.request(
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
data=data,
|
||||||
|
params=params,
|
||||||
|
headers=headers,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.debug("data: %s", data)
|
||||||
|
|
||||||
|
logging.debug("ret: %s", ret.json())
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug("上报数据失败: %s", e)
|
||||||
|
|
||||||
|
thr = threading.Thread(target=thr_wrapper, args=(
|
||||||
|
self,
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
data,
|
||||||
|
params,
|
||||||
|
headers,
|
||||||
|
), kwargs=kwargs)
|
||||||
|
thr.start()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
0
pkg/utils/center/groups/__init__.py
Normal file
0
pkg/utils/center/groups/__init__.py
Normal file
48
pkg/utils/center/groups/main.py
Normal file
48
pkg/utils/center/groups/main.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .. import apigroup
|
||||||
|
|
||||||
|
|
||||||
|
class V2MainDataAPI(apigroup.APIGroup):
|
||||||
|
"""主程序相关 数据API"""
|
||||||
|
|
||||||
|
def __init__(self, prefix: str):
|
||||||
|
super().__init__(prefix+"/main")
|
||||||
|
|
||||||
|
def post_update_record(
|
||||||
|
self,
|
||||||
|
spent_seconds: int,
|
||||||
|
infer_reason: str,
|
||||||
|
old_version: str,
|
||||||
|
new_version: str,
|
||||||
|
):
|
||||||
|
"""提交更新记录"""
|
||||||
|
return 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_announcement_showed(
|
||||||
|
self,
|
||||||
|
ids: list[int],
|
||||||
|
):
|
||||||
|
"""提交公告已阅"""
|
||||||
|
return self.do(
|
||||||
|
"POST",
|
||||||
|
"/announcement",
|
||||||
|
data={
|
||||||
|
"basic": self.basic_info(),
|
||||||
|
"announcement_info": {
|
||||||
|
"ids": ids,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
58
pkg/utils/center/groups/plugin.py
Normal file
58
pkg/utils/center/groups/plugin.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .. import apigroup
|
||||||
|
|
||||||
|
|
||||||
|
class V2PluginDataAPI(apigroup.APIGroup):
|
||||||
|
"""插件数据相关 API"""
|
||||||
|
|
||||||
|
def __init__(self, prefix: str):
|
||||||
|
super().__init__(prefix+"/plugin")
|
||||||
|
|
||||||
|
def post_install_record(
|
||||||
|
self,
|
||||||
|
plugin: dict
|
||||||
|
):
|
||||||
|
"""提交插件安装记录"""
|
||||||
|
return self.do(
|
||||||
|
"POST",
|
||||||
|
"/install",
|
||||||
|
data={
|
||||||
|
"basic": self.basic_info(),
|
||||||
|
"plugin": plugin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_remove_record(
|
||||||
|
self,
|
||||||
|
plugin: dict
|
||||||
|
):
|
||||||
|
"""提交插件卸载记录"""
|
||||||
|
return self.do(
|
||||||
|
"POST",
|
||||||
|
"/remove",
|
||||||
|
data={
|
||||||
|
"basic": self.basic_info(),
|
||||||
|
"plugin": plugin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_update_record(
|
||||||
|
self,
|
||||||
|
plugin: dict,
|
||||||
|
old_version: str,
|
||||||
|
new_version: str,
|
||||||
|
):
|
||||||
|
"""提交插件更新记录"""
|
||||||
|
return self.do(
|
||||||
|
"POST",
|
||||||
|
"/update",
|
||||||
|
data={
|
||||||
|
"basic": self.basic_info(),
|
||||||
|
"plugin": plugin,
|
||||||
|
"update_info": {
|
||||||
|
"old_version": old_version,
|
||||||
|
"new_version": new_version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
81
pkg/utils/center/groups/usage.py
Normal file
81
pkg/utils/center/groups/usage.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .. import apigroup
|
||||||
|
|
||||||
|
|
||||||
|
class V2UsageDataAPI(apigroup.APIGroup):
|
||||||
|
"""使用量数据相关 API"""
|
||||||
|
|
||||||
|
def __init__(self, prefix: str):
|
||||||
|
super().__init__(prefix+"/usage")
|
||||||
|
|
||||||
|
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 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_event_record(
|
||||||
|
self,
|
||||||
|
plugins: list[dict],
|
||||||
|
event_name: str,
|
||||||
|
):
|
||||||
|
"""提交事件触发记录"""
|
||||||
|
return self.do(
|
||||||
|
"POST",
|
||||||
|
"/event",
|
||||||
|
data={
|
||||||
|
"basic": self.basic_info(),
|
||||||
|
"runtime": self.runtime_info(),
|
||||||
|
"plugins": plugins,
|
||||||
|
"event_info": {
|
||||||
|
"name": event_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_function_record(
|
||||||
|
self,
|
||||||
|
plugin: dict,
|
||||||
|
function_name: str,
|
||||||
|
function_description: str,
|
||||||
|
):
|
||||||
|
"""提交内容函数使用记录"""
|
||||||
|
return self.do(
|
||||||
|
"POST",
|
||||||
|
"/function",
|
||||||
|
data={
|
||||||
|
"basic": self.basic_info(),
|
||||||
|
"plugin": plugin,
|
||||||
|
"function_info": {
|
||||||
|
"name": function_name,
|
||||||
|
"description": function_description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
35
pkg/utils/center/v2.py
Normal file
35
pkg/utils/center/v2.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from . import apigroup
|
||||||
|
from .groups import main
|
||||||
|
from .groups import usage
|
||||||
|
from .groups import plugin
|
||||||
|
|
||||||
|
|
||||||
|
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, 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)
|
||||||
|
self.usage = usage.V2UsageDataAPI(BACKEND_URL)
|
||||||
|
self.plugin = plugin.V2PluginDataAPI(BACKEND_URL)
|
||||||
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ from ..openai import manager as openai_mgr
|
|||||||
from ..qqbot import manager as qqbot_mgr
|
from ..qqbot import manager as qqbot_mgr
|
||||||
from ..config import manager as config_mgr
|
from ..config import manager as config_mgr
|
||||||
from ..plugin import host as plugin_host
|
from ..plugin import host as plugin_host
|
||||||
|
from .center import v2 as center_v2
|
||||||
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
@@ -114,3 +115,16 @@ def get_thread_ctl() -> threadctl.ThreadCtl:
|
|||||||
t: threadctl.ThreadCtl = context['pool_ctl']
|
t: threadctl.ThreadCtl = context['pool_ctl']
|
||||||
context_lock.release()
|
context_lock.release()
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
def set_center_v2_api(inst: center_v2.V2CenterAPI):
|
||||||
|
context_lock.acquire()
|
||||||
|
context['center_v2_api'] = inst
|
||||||
|
context_lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def get_center_v2_api() -> center_v2.V2CenterAPI:
|
||||||
|
context_lock.acquire()
|
||||||
|
t: center_v2.V2CenterAPI = context['center_v2_api']
|
||||||
|
context_lock.release()
|
||||||
|
return t
|
||||||
7
pkg/utils/platform.py
Normal file
7
pkg/utils/platform.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def get_platform() -> str:
|
||||||
|
"""获取当前平台"""
|
||||||
|
return sys.platform
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import network
|
from . import network
|
||||||
|
from . import context
|
||||||
|
|
||||||
|
|
||||||
def check_dulwich_closure():
|
def check_dulwich_closure():
|
||||||
@@ -107,7 +111,10 @@ def compare_version_str(v0: str, v1: str) -> int:
|
|||||||
|
|
||||||
def update_all(cli: bool = False) -> bool:
|
def update_all(cli: bool = False) -> bool:
|
||||||
"""检查更新并下载源码"""
|
"""检查更新并下载源码"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
current_tag = get_current_tag()
|
current_tag = get_current_tag()
|
||||||
|
old_tag = current_tag
|
||||||
|
|
||||||
rls_list = get_release_list()
|
rls_list = get_release_list()
|
||||||
|
|
||||||
@@ -200,6 +207,13 @@ def update_all(cli: bool = False) -> bool:
|
|||||||
with open("current_tag", "w") as f:
|
with open("current_tag", "w") as f:
|
||||||
f.write(current_tag)
|
f.write(current_tag)
|
||||||
|
|
||||||
|
context.get_center_v2_api().main.post_update_record(
|
||||||
|
spent_seconds=int(time.time()-start_time),
|
||||||
|
infer_reason="update",
|
||||||
|
old_version=old_tag,
|
||||||
|
new_version=current_tag,
|
||||||
|
)
|
||||||
|
|
||||||
# 通知管理员
|
# 通知管理员
|
||||||
if not cli:
|
if not cli:
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ nakuru-project-idk
|
|||||||
CallingGPT
|
CallingGPT
|
||||||
tiktoken
|
tiktoken
|
||||||
PyYaml
|
PyYaml
|
||||||
|
aiohttp
|
||||||
@@ -310,6 +310,8 @@ require_ver("v2.5.1", "v2.6.0") # 要求最低版本为 v2.5.1, 同时要求最
|
|||||||
|
|
||||||
### 说明
|
### 说明
|
||||||
|
|
||||||
|
> 下一版本将会添加统一的插件API,欢迎在[此讨论](https://github.com/RockChinQ/QChatGPT/discussions/637)回复您的需求!
|
||||||
|
|
||||||
事件处理函数将会获得一系列参数,可以在`kwargs`中取出。
|
事件处理函数将会获得一系列参数,可以在`kwargs`中取出。
|
||||||
其中`host`参数(`pkg.plugin.host.PluginHost`类的实例)是插件宿主,提供与主程序各个模块交互的一些方法。
|
其中`host`参数(`pkg.plugin.host.PluginHost`类的实例)是插件宿主,提供与主程序各个模块交互的一些方法。
|
||||||
`event`参数(`pkg.plugin.host.EventContext`类的实例)是事件执行期间的上下文,提供对此次事件执行的一些操作方法。
|
`event`参数(`pkg.plugin.host.EventContext`类的实例)是事件执行期间的上下文,提供对此次事件执行的一些操作方法。
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
|
||||||
|
|
||||||
欢迎查看QChatGPT的Wiki页。
|
欢迎查看QChatGPT的Wiki页。
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|||||||
43
tests/identifier_test/host_identifier.py
Normal file
43
tests/identifier_test/host_identifier.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 向 ~/.qchatgpt 写入一个 标识符
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.expanduser('~/.qchatgpt')):
|
||||||
|
os.mkdir(os.path.expanduser('~/.qchatgpt'))
|
||||||
|
|
||||||
|
identifier = {
|
||||||
|
"host_id": "host_"+str(uuid.uuid4()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.expanduser('~/.qchatgpt/host.json')):
|
||||||
|
print('create ~/.qchatgpt/host.json')
|
||||||
|
with open(os.path.expanduser('~/.qchatgpt/host.json'), 'w') as f:
|
||||||
|
json.dump(identifier, f)
|
||||||
|
else:
|
||||||
|
print('load ~/.qchatgpt/host.json')
|
||||||
|
with open(os.path.expanduser('~/.qchatgpt/host.json'), 'r') as f:
|
||||||
|
identifier = json.load(f)
|
||||||
|
|
||||||
|
print(identifier)
|
||||||
|
|
||||||
|
instance_id = {
|
||||||
|
"host_id": identifier['host_id'],
|
||||||
|
"instance_id": "instance_"+str(uuid.uuid4()),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 实例 id
|
||||||
|
if os.path.exists("res/instance_id.json"):
|
||||||
|
with open("res/instance_id.json", 'r') as f:
|
||||||
|
instance_id = json.load(f)
|
||||||
|
|
||||||
|
if instance_id['host_id'] != identifier['host_id']:
|
||||||
|
os.remove("res/instance_id.json")
|
||||||
|
|
||||||
|
if not os.path.exists("res/instance_id.json"):
|
||||||
|
print('create res/instance_id.json')
|
||||||
|
with open("res/instance_id.json", 'w') as f:
|
||||||
|
json.dump(instance_id, f)
|
||||||
|
|
||||||
|
print(instance_id)
|
||||||
Reference in New Issue
Block a user