From 41fd60f580af9d61ae3048935026079433731b8d Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Thu, 8 Dec 2022 19:26:51 +0800 Subject: [PATCH] feat: ai mode --- README.md | 22 +++---- index.js | 24 +++++--- index_no#.js | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 index_no#.js diff --git a/README.md b/README.md index 92ac09b..80135b1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Node.js >= 16.8 ``` pnpm install -w chatgpt undici ``` -> 目前要求依赖chatgpt版本要大于2.0.0,如果报错可使用`pnpm update`更新一下。 +> chatgpt从2.0开始支持Conversation,因此要求依赖chatgpt版本要大于2.0.0,如果使用了低版本导致报错可使用`pnpm update`更新一下。 2. 克隆项目 ``` git clone https://github.com/ikechan8370/yunzai-chatgpt.git ./plugins/chatgpt @@ -17,6 +17,7 @@ git clone https://github.com/ikechan8370/yunzai-chatgpt.git ./plugins/chatgpt 编辑`plugins/chatgpt/index.js`文件主要修改其中的`SESSION_TOKEN`常量,修改为你的openai账号的token。token获取参见下文。 ## 使用 +### 默认方式 #chatgpt开头即可,例如:#chatgpt 介绍一下米哈游 ![image](https://user-images.githubusercontent.com/21212372/205808552-a775cdea-0668-4273-865c-35c5d91ad37e.png) (图片仅供参考,chatgpt在某些领域依然是人工智障,但语言起码流畅自信多了) @@ -27,6 +28,11 @@ git clone https://github.com/ikechan8370/yunzai-chatgpt.git ./plugins/chatgpt 比如让他写剧本 image +### 群聊使用艾特(@)的方式 +如果你的机器人插件少不担心冲突问题的话,将 `index.js` 重命名为 `index.js.bak`,将 `index_no#.js` 重命名为 `index.js`,此时将基于艾特模式进行聊天。\ +此时只需在群聊中@机器人+聊天内容即可。\ +同时,此模式下私聊直接打字聊天即可,也无需加#chatgpt前缀。 + 发挥你的想象力吧! ## 关于openai token获取 @@ -38,18 +44,10 @@ git clone https://github.com/ikechan8370/yunzai-chatgpt.git ./plugins/chatgpt 其他问题可以参考使用的api库https://github.com/transitive-bullshit/chatgpt-api -## 其他 -`index.js`文件中, -``` -this.chatGPTApi = new ChatGPTAPI({ - sessionToken: SESSION_TOKEN, - markdown: true, - userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36' -}) -``` -默认关闭了markdown支持,如果发现代码类回答显示有问题可以将其改为true试试。 -此外,该api响应速度可能由于模型本身及网络原因不会太快,请勿频繁重发。后续准备加入限速等功能。 +## 其他 + +该api响应速度可能由于模型本身及网络原因不会太快,请勿频繁重发。后续准备加入限速等功能。因网络问题和模型响应速度问题可能出现500、503、404等各种异常状态码,此时等待官方恢复即可。实测复杂的中文对话更容易触发503错误(超时)。如出现429则意味着超出了免费账户调用频率,只能暂时停用,放置一段时间再继续使用。 openai目前开放chatgpt模型的免费试用,在此期间本项目应该都可用,后续如果openai调整其收费策略,到时候视情况进行调整。 diff --git a/index.js b/index.js index 78adab2..35e27d3 100644 --- a/index.js +++ b/index.js @@ -101,10 +101,12 @@ export class chatgpt extends plugin { async help (e) { let response = 'chatgpt-plugin使用帮助文字版\n' + - '#chatgpt+聊天内容: 发起对话与AI进行聊天\n' + - 'chatgpt对话列表: 查看当前发起的对话\n' + - '#结束对话: 结束自己或@用户的对话' - await this.reply(response) + '#chatgpt+聊天内容: 发起对话与AI进行聊天\n' + + 'chatgpt对话列表: 查看当前发起的对话\n' + + '#结束对话: 结束自己或@用户的对话\n' + + 'chatgpt帮助: 查看本帮助\n' + + '源代码:https://github.com/ikechan8370/chatgpt-plugin' + await this.reply(response) } /** @@ -120,18 +122,20 @@ export class chatgpt extends plugin { let previousConversation = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) if (!previousConversation) { c = this.chatGPTApi.getConversation() + let ctime = new Date() previousConversation = { sender: e.sender, conversation: c, - ctime: new Date(), - utime: new Date(), + ctime, + utime: ctime, num: 0 } await redis.set(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`, JSON.stringify(previousConversation), { EX: CONVERSATION_PRESERVE_TIME }) } else { + previousConversation = JSON.parse(previousConversation) c = this.chatGPTApi.getConversation({ - conversationId: JSON.parse(previousConversation).conversation.conversationId, - parentMessageId: JSON.parse(previousConversation).conversation.parentMessageId + conversationId: previousConversation.conversation.conversationId, + parentMessageId: previousConversation.conversation.parentMessageId }) } try { @@ -147,10 +151,10 @@ export class chatgpt extends plugin { num: previousConversation.num + 1 }), { EX: CONVERSATION_PRESERVE_TIME }) /** 最后回复消息 */ - await this.reply(`${response}`, true) + await this.reply(`${response}`, e.isGroup) } catch (e) { logger.error(e) - await this.reply(`与OpenAI通信异常,请稍后重试:${e}`, true) + await this.reply(`与OpenAI通信异常,请稍后重试:${e}`, e.isGroup, { recallMsg: e.isGroup ? 10 : 0 }) } } } diff --git a/index_no#.js b/index_no#.js new file mode 100644 index 0000000..b55fde1 --- /dev/null +++ b/index_no#.js @@ -0,0 +1,167 @@ +import plugin from '../../lib/plugins/plugin.js' +import { ChatGPTAPI } from 'chatgpt' +import _ from 'lodash' +const SESSION_TOKEN = '' + +/** + * 每个对话保留的时长。单个对话内ai是保留上下文的。超时后销毁对话,再次对话创建新的对话。 + * 单位:秒 + * @type {number} + */ +const CONVERSATION_PRESERVE_TIME = 600 +export class chatgpt extends plugin { + constructor () { + super({ + /** 功能名称 */ + name: 'chatgpt', + /** 功能描述 */ + dsc: 'chatgpt from openai', + /** https://oicqjs.github.io/oicq/#events */ + event: 'message', + /** 优先级,数字越小等级越高 */ + priority: 5000, + rule: [ + { + /** 命令正则匹配 */ + reg: '^[^#][sS]*', + /** 执行方法 */ + fnc: 'chatgpt' + }, + { + reg: '#chatgpt对话列表', + fnc: 'getConversations', + permission: 'master' + }, + { + reg: '^#结束对话([sS]*)', + fnc: 'destroyConversations' + }, + { + reg: '#chatgpt帮助', + fnc: 'help' + } + ] + }) + this.chatGPTApi = new ChatGPTAPI({ + sessionToken: SESSION_TOKEN, + markdown: true, + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36' + }) + } + + /** + * 获取chatgpt当前对话列表 + * @param e + * @returns {Promise} + */ + async getConversations (e) { + let keys = await redis.keys('CHATGPT:CONVERSATIONS:*') + if (!keys || keys.length === 0) { + await this.reply('当前没有人正在与机器人对话', true) + } else { + let response = '当前对话列表:(格式为【开始时间 | qq昵称 | 对话长度 | 最后活跃时间】)\n' + await Promise.all(keys.map(async (key) => { + let conversation = await redis.get(key) + if (conversation) { + conversation = JSON.parse(conversation) + response += `${conversation.ctime} | ${conversation.sender.nickname} | ${conversation.num} | ${conversation.utime} \n` + } + })) + await this.reply(`${response}`, true) + } + } + + /** + * 销毁指定人的对话 + * @param e + * @returns {Promise} + */ + async destroyConversations (e) { + let ats = e.message.filter(m => m.type === 'at') + if (ats.length === 0) { + let c = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) + if (!c) { + await this.reply('当前没有开启对话', true) + } else { + await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) + await this.reply('已结束当前对话,请使用#chatgpt进行聊天以开启新的对话', true) + } + } else { + let at = ats[0] + let qq = at.qq + let atUser = _.trimStart(at.text, '@') + let c = await redis.get(`CHATGPT:CONVERSATIONS:${qq}`) + if (!c) { + await this.reply(`当前${atUser}没有开启对话`, true) + } else { + await redis.del(`CHATGPT:CONVERSATIONS:${qq}`) + await this.reply(`已结束${atUser}的对话,他仍可以使用#chatgpt进行聊天以开启新的对话`, true) + } + } + } + + async help (e) { + let response = 'chatgpt-plugin使用帮助文字版\n' + + '@我+聊天内容: 发起对话与AI进行聊天\n' + + '#chatgpt对话列表: 查看当前发起的对话\n' + + '#结束对话: 结束自己或@用户的对话\n' + + '#chatgpt帮助: 查看本帮助' + + '源代码:https://github.com/ikechan8370/chatgpt-plugin' + await this.reply(response) + } + + /** + * #chatgpt + * @param e oicq传递的事件参数e + */ + async chatgpt (e) { + if (e.msg.startsWith("#")) { + return + } + if (e.isGroup && !e.atme) { + return + } + // let question = _.trimStart(e.msg, '#chatgpt') + let question = e.msg.trimStart() + let c + logger.info(`chatgpt question: ${question}`) + let previousConversation = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) + if (!previousConversation) { + c = this.chatGPTApi.getConversation() + let ctime = new Date() + previousConversation = { + sender: e.sender, + conversation: c, + ctime, + utime: ctime, + num: 0 + } + await redis.set(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`, JSON.stringify(previousConversation), { EX: CONVERSATION_PRESERVE_TIME }) + } else { + previousConversation = JSON.parse(previousConversation) + c = this.chatGPTApi.getConversation({ + conversationId: previousConversation.conversation.conversationId, + parentMessageId: previousConversation.conversation.parentMessageId + }) + } + try { + // console.log({ c }) + await this.chatGPTApi.ensureAuth() + const response = await c.sendMessage(question) + logger.info(response) + // 更新redis中的conversation对象,因为send后c已经被自动更新了 + await redis.set(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`, JSON.stringify({ + sender: e.sender, + conversation: c, + ctime: previousConversation.ctime, + utime: new Date(), + num: previousConversation.num + 1 + }), { EX: CONVERSATION_PRESERVE_TIME }) + /** 最后回复消息 */ + await this.reply(`${response}`, e.isGroup) + } catch (e) { + logger.error(e) + await this.reply(`与OpenAI通信异常,请稍后重试:${e}`, true, { recallMsg: e.isGroup ? 10 : 0 }) + } + } +} \ No newline at end of file