feat: add picture mode

This commit is contained in:
葛胤池 2022-12-10 19:45:54 +08:00
parent 6f0e62b54d
commit 70044eb6c0
21 changed files with 663 additions and 371 deletions

View file

@ -14,31 +14,18 @@ pnpm install -w chatgpt undici
git clone https://github.com/ikechan8370/yunzai-chatgpt.git ./plugins/chatgpt git clone https://github.com/ikechan8370/yunzai-chatgpt.git ./plugins/chatgpt
``` ```
3. 修改配置 3. 修改配置
编辑`plugins/chatgpt/index.js`文件主要修改其中的`SESSION_TOKEN`常量修改为你的openai账号的token。token获取参见文。 编辑`plugins/chatgpt/config/index.js`文件主要修改其中的`SESSION_TOKEN`常量修改为你的openai账号的token。token获取参见文。
## 使用 ## 使用
### 默认方式
#chatgpt开头即可,例如:#chatgpt 介绍一下米哈游
![image](https://user-images.githubusercontent.com/21212372/205808552-a775cdea-0668-4273-865c-35c5d91ad37e.png)
图片仅供参考chatgpt在某些领域依然是人工智障但语言起码流畅自信多了
比如让他写代码
![image](https://user-images.githubusercontent.com/21212372/205810566-af10e141-1ab4-4629-998d-664eea3ad827.png)
比如让他写剧本
<img width="835" alt="image" src="https://user-images.githubusercontent.com/21212372/206387421-db00728d-1869-40f3-bde4-0dd6a4b67913.png">
### 群聊使用艾特(@)的方式
如果你的机器人插件少不担心冲突问题的话,将 `index.js` 重命名为 `index.js.bak`,将 `index_no#.js` 重命名为 `index.js`,此时将基于艾特模式进行聊天。
此时只需在群聊中@机器人+聊天内容即可。
![image](https://user-images.githubusercontent.com/21212372/206436999-c8d3bd48-aa39-496a-a71a-89164e9d7c18.png)
同时,此模式下私聊直接打字聊天即可,也无需加#chatgpt前缀
![image](https://user-images.githubusercontent.com/21212372/206437284-afed0fc6-caaa-4c6e-92e4-53fccbeff286.png)
### 基本使用
@机器人 发送聊内容即可
![img.png](resources/img/example1.png)
发挥你的想象力吧! 发挥你的想象力吧!
### 获取帮助
发送#chatgpt帮助
## 关于openai token获取 ## 关于openai token获取
1. 注册openai账号 1. 注册openai账号
进入https://chat.openai.com/ 选择signup注册。目前openai不对包括俄罗斯、乌克兰、伊朗、中国等国家和地区提供服务所以自行寻找办法使用其他国家和地区的ip登录。此外注册可能需要验证所在国家和地区的手机号码如果没有国外手机号可以试试解码网站收费的推荐https://sms-activate.org/。 进入https://chat.openai.com/ 选择signup注册。目前openai不对包括俄罗斯、乌克兰、伊朗、中国等国家和地区提供服务所以自行寻找办法使用其他国家和地区的ip登录。此外注册可能需要验证所在国家和地区的手机号码如果没有国外手机号可以试试解码网站收费的推荐https://sms-activate.org/。

281
apps/chat.js Normal file
View file

@ -0,0 +1,281 @@
import plugin from '../../../lib/plugins/plugin.js'
import { ChatGPTAPI } from 'chatgpt'
import _ from 'lodash'
import { Config } from '../config/index.js'
import showdown from 'showdown'
import mjAPI from 'mathjax-node'
// import showdownKatex from 'showdown-katex'
const SESSION_TOKEN = Config.token
const blockWords = '屏蔽词1,屏蔽词2,屏蔽词3'
const converter = new showdown.Converter({
extensions: [
// showdownKatex({
// delimiters: [
// { left: '$$', right: '$$', display: false },
// { left: '$', right: '$', display: false, inline: true },
// { left: '\\(', right: '\\)', display: false },
// { left: '\\[', right: '\\]', display: true }
// ]
// })
]
})
/**
* 每个对话保留的时长单个对话内ai是保留上下文的超时后销毁对话再次对话创建新的对话
* 单位
* @type {number}
*/
const CONVERSATION_PRESERVE_TIME = 600
mjAPI.config({
MathJax: {
// traditional MathJax configuration
}
})
mjAPI.start()
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'
},
{
reg: '#chatgpt图片模式',
fnc: 'switch2Picture'
},
{
reg: '#chatgpt文字模式',
fnc: 'switch2Text'
}
]
})
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<void>}
*/
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<void>}
*/
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('已结束当前对话,请@我进行聊天以开启新的对话', 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}的对话,他仍可以@我进行聊天以开启新的对话`, true)
}
}
}
async help (e) {
let response = 'chatgpt-plugin使用帮助文字版\n' +
'@我+聊天内容: 发起对话与AI进行聊天\n' +
'#chatgpt对话列表: 查看当前发起的对话\n' +
'#结束对话: 结束自己或@用户的对话\n' +
'#chatgpt帮助: 查看本帮助\n' +
'源代码https://github.com/ikechan8370/chatgpt-plugin'
await this.reply(response)
}
async switch2Picture (e) {
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
if (!userSetting) {
userSetting = { usePicture: true }
} else {
userSetting = JSON.parse(userSetting)
}
userSetting.usePicture = true
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
await this.reply('ChatGPT回复已转换为图片模式')
}
async switch2Text (e) {
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
if (!userSetting) {
userSetting = { usePicture: false }
} else {
userSetting = JSON.parse(userSetting)
}
userSetting.usePicture = false
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
await this.reply('ChatGPT回复已转换为文字模式')
}
/**
* #chatgpt
* @param e oicq传递的事件参数e
*/
async chatgpt (e) {
if (!e.msg || e.msg.startsWith('#')) {
return
}
if (e.isGroup && !e.atme) {
return
}
// let question = _.trimStart(e.msg, '#chatgpt')
let question = e.msg.trimStart()
// await e.runtime.render('chatgpt-plugin', 'content/index', { content: "", question })
// return
try {
await this.chatGPTApi.ensureAuth()
} catch (e) {
logger.error(e)
await this.reply(`OpenAI认证失败请检查Token${e}`, true)
return
}
await this.reply('我正在思考如何回复你,请稍等', true, 5)
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 })
let response = await c.sendMessage(question)
// console.log({c})
// console.log(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 })
// 检索是否有屏蔽词
const blockWord = blockWords.split(',').find(word => response.toLowerCase().includes(word.toLowerCase()))
if (blockWord) {
await this.reply('返回内容存在敏感词,我不想回答你', true)
return
}
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
if (userSetting) {
userSetting = JSON.parse(userSetting)
} else {
userSetting = {
usePicture: false
}
}
if (userSetting.usePicture) {
while (!response.trimEnd().endsWith('.') && !response.trimEnd().endsWith('。') && !response.trimEnd().endsWith('……')) {
await this.reply('内容有点多,我正在奋笔疾书,请再等一会', true, 5)
const responseAppend = await c.sendMessage('Continue')
// 检索是否有屏蔽词
const blockWord = blockWords.split(',').find(word => responseAppend.toLowerCase().includes(word.toLowerCase()))
if (blockWord) {
await this.reply('返回内容存在敏感词,我不想回答你', true)
return
}
if (responseAppend.indexOf('conversation') > -1 || responseAppend.startsWith("I'm sorry")) {
logger.warn('chatgpt might forgot what it had said')
break
}
// 更新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 })
response = response + responseAppend
}
// logger.info(response)
// markdown转为html
// todo部分数学公式可能还有问题
let converted = converter.makeHtml(response)
/** 最后回复消息 */
await e.runtime.render('chatgpt-plugin', 'content/index', { content: converted, question, senderName: e.sender.nickname })
} else {
await this.reply(`${response}`, e.isGroup)
}
} catch (e) {
logger.error(e)
await this.reply(`与OpenAI通信异常请稍后重试${e}`, true, { recallMsg: e.isGroup ? 10 : 0 })
}
}
}

76
apps/help.js Normal file
View file

@ -0,0 +1,76 @@
import plugin from '../../../lib/plugins/plugin.js'
let helpData = [
{
group: '聊天',
list: [
{
icon: 'chat',
title: '@我+聊天内容',
desc: '与机器人聊天'
},
{
icon: 'chat-private',
title: '私聊与我对话',
desc: '与机器人聊天'
},
{
icon: 'picture',
title: '#chatgpt图片模式',
desc: '机器人以图片形式回答'
},
{
icon: 'text',
title: '#chatgpt文本模式',
desc: '机器人以文本形式回答,默认选项'
},
]
},
{
group: '管理',
list: [
{
icon: 'list',
title: '#chatgpt对话列表',
desc: '查询当前哪些人正在与机器人聊天'
},
{
icon: 'destroy',
title: '#结束对话',
desc: '结束自己当前对话,下次开启对话机器人将遗忘掉本次对话内容。'
},
{
icon: 'destroy-other',
title: '#结束对话 @某人',
desc: '结束该用户当前对话,下次开启对话机器人将遗忘掉本次对话内容。'
},
{
icon: 'help',
title: '#chatgpt帮助',
desc: '获取本帮助'
}
]
}
]
export class help extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin帮助',
dsc: 'ChatGPT-Plugin帮助',
event: 'message',
priority: 500,
rule: [
{
reg: '^(#|[chatgpt|ChatGPT])*(命令|帮助|菜单|help|说明|功能|指令|使用说明)$',
fnc: 'help'
}
]
})
}
async help (e) {
await e.runtime.render('chatgpt-plugin', 'help/index', { helpData })
}
}

5
config/index.js Normal file
View file

@ -0,0 +1,5 @@
const SESSION_TOKEN = ''
export const Config = {
token: SESSION_TOKEN
}

186
index.js
View file

@ -1,176 +1,24 @@
import plugin from '../../lib/plugins/plugin.js' import fs from 'node:fs'
import { ChatGPTAPI } from 'chatgpt'
import _ from 'lodash'
const SESSION_TOKEN=''
const blockWords = '屏蔽词1,屏蔽词2,屏蔽词3'
/** const files = fs.readdirSync('./plugins/chatgpt-plugin/apps').filter(file => file.endsWith('.js'))
* 每个对话保留的时长单个对话内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: '^#chatgpt([\s\S]*)',
/** 执行方法 */
fnc: 'chatgpt'
},
{
reg: 'chatgpt对话列表',
fnc: 'getConversations',
permission: 'master'
},
{
reg: '^#结束对话([\s\S]*)',
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'
})
}
/** let ret = []
* 获取chatgpt当前对话列表
* @param e
* @returns {Promise<void>}
*/
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)
}
}
/** files.forEach((file) => {
* 销毁指定人的对话 ret.push(import(`./apps/${file}`))
* @param e })
* @returns {Promise<void>}
*/
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) { ret = await Promise.allSettled(ret)
let response = 'chatgpt-plugin使用帮助文字版\n' +
'#chatgpt+聊天内容: 发起对话与AI进行聊天\n' +
'chatgpt对话列表: 查看当前发起的对话\n' +
'#结束对话: 结束自己或@用户的对话\n' +
'chatgpt帮助: 查看本帮助\n' +
'源代码https://github.com/ikechan8370/chatgpt-plugin'
await this.reply(response)
}
/** let apps = {}
* #chatgpt for (let i in files) {
* @param e oicq传递的事件参数e let name = files[i].replace('.js', '')
*/
async chatgpt (e) { if (ret[i].status !== 'fulfilled') {
let question = _.trimStart(e.msg, '#chatgpt') logger.error(`载入插件错误:${logger.red(name)}`)
question = question.trimStart() logger.error(ret[i].reason)
try { continue
await this.chatGPTApi.ensureAuth()
} catch (e) {
logger.error(e)
await this.reply(`OpenAI认证失败请检查Token${e}`, true)
return
}
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 })
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 })
// 检索是否有屏蔽词
const blockWord = blockWords.split(',').find(word => response.toLowerCase().includes(word.toLowerCase()))
if (blockWord) {
await this.reply(`返回内容存在敏感词,我不想回答你`, true)
return
}
/** 最后回复消息 */
await this.reply(`${response}`, e.isGroup)
} catch (e) {
logger.error(e)
await this.reply(`与OpenAI通信异常请稍后重试${e}`, e.isGroup, { recallMsg: e.isGroup ? 10 : 0 })
}
} }
apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]
} }
export { apps }

View file

@ -1,182 +0,0 @@
import plugin from '../../lib/plugins/plugin.js'
import { ChatGPTAPI } from 'chatgpt'
import _ from 'lodash'
const SESSION_TOKEN = ''
const blockWords = '屏蔽词1,屏蔽词2,屏蔽词3'
/**
* 每个对话保留的时长单个对话内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<void>}
*/
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<void>}
*/
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('已结束当前对话,请@我进行聊天以开启新的对话', 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}的对话,他仍可以@我进行聊天以开启新的对话`, true)
}
}
}
async help (e) {
let response = 'chatgpt-plugin使用帮助文字版\n' +
'@我+聊天内容: 发起对话与AI进行聊天\n' +
'#chatgpt对话列表: 查看当前发起的对话\n' +
'#结束对话: 结束自己或@用户的对话\n' +
'#chatgpt帮助: 查看本帮助\n' +
'源代码https://github.com/ikechan8370/chatgpt-plugin'
await this.reply(response)
}
/**
* #chatgpt
* @param e oicq传递的事件参数e
*/
async chatgpt (e) {
if (!e.msg || e.msg.startsWith("#")) {
return
}
if (e.isGroup && !e.atme) {
return
}
// let question = _.trimStart(e.msg, '#chatgpt')
let question = e.msg.trimStart()
try {
await this.chatGPTApi.ensureAuth()
} catch (e) {
logger.error(e)
await this.reply(`OpenAI认证失败请检查Token${e}`, true)
return
}
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 })
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 })
// 检索是否有屏蔽词
const blockWord = blockWords.split(',').find(word => response.toLowerCase().includes(word.toLowerCase()))
if (blockWord) {
await this.reply(`返回内容存在敏感词,我不想回答你`, true)
return
}
/** 最后回复消息 */
await this.reply(`${response}`, e.isGroup)
} catch (e) {
logger.error(e)
await this.reply(`与OpenAI通信异常请稍后重试${e}`, true, { recallMsg: e.isGroup ? 10 : 0 })
}
}
}

View file

@ -0,0 +1,64 @@
@font-face {
font-family: "tttgbnumber";
src: url("../../../../../resources/font/tttgbnumber.ttf");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
font-family: sans-serif;
font-size: 16px;
width: 800px;
color: #1e1f20;
transform: scale(1.5);
transform-origin: 0 0;
}
.container {
width: 800px;
padding: 20px 15px 10px 15px;
background-color: #f5f6fb;
}
.head_box {
border-radius: 15px;
font-family: tttgbnumber;
padding: 10px 20px;
position: relative;
box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%);
}
.sender {
font-size: 12px;
color: #4d4d4d;
margin-bottom: 5px;
}
.question {
background: #009FFF; /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #ec2F4B, #009FFF); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #ec2F4B, #009FFF); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color: #000000;
text-shadow: 0 0 10px white;
font-weight: bold;
border-radius: 5px;
padding: 8px 10px;
margin-bottom: 10px;
}
.answer {
position: relative;
border-radius: 5px;
padding: 8px 10px;
background: #dbe9ff;
width: 100%;
}
.logo {
margin-top: 10px;
font-size: 14px;
font-family: "tttgbnumber";
text-align: center;
color: #7994a7;
}

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="stylesheet" type="text/css" href="{{pluResPath}}content/content.css" />
<link rel="shortcut icon" href="#" />
</head>
{{@headStyle}}
<body style="display:none;" onload="document.body.style.display='block'">
<div class="container" id="container">
<!-- <div class="sender">-->
<!-- {{senderName}}-->
<!-- </div>-->
<div class="question">
{{question}}
</div>
<div class="answer">
<div id="content">
{{@ content}}
</div>
</div>
<div class="logo">Response to {{senderName}} Created By Yunzai-Bot and ChatGPT-Plugin</div>
</div>
</body>
</html>

135
resources/help/help.css Normal file
View file

@ -0,0 +1,135 @@
@font-face {
font-family: "tttgbnumber";
src: url("../../../../../resources/font/tttgbnumber.ttf");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
font-family: sans-serif;
font-size: 16px;
width: 530px;
color: #1e1f20;
transform: scale(1.5);
transform-origin: 0 0;
}
.container {
width: 530px;
padding: 20px 15px 10px 15px;
background-color: #f5f6fb;
}
.head_box {
border-radius: 15px;
font-family: tttgbnumber;
padding: 10px 20px;
position: relative;
box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%);
}
.head_box .id_text {
font-size: 24px;
}
.head_box .day_text {
font-size: 20px;
}
.head_box .chatgpt_logo {
position: absolute;
top: 12px;
right: 15px;
width: 50px;
}
.base_info {
position: relative;
padding-left: 10px;
}
.uid {
font-family: tttgbnumber;
}
.data_box {
border-radius: 15px;
margin-top: 20px;
margin-bottom: 15px;
padding: 20px 0px 5px 0px;
background: #fff;
box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%);
position: relative;
}
.tab_lable {
position: absolute;
top: -10px;
left: -8px;
background: #d4b98c;
color: #fff;
font-size: 14px;
padding: 3px 10px;
border-radius: 15px 0px 15px 15px;
z-index: 20;
}
.data_line {
display: flex;
justify-content: space-around;
margin-bottom: 14px;
}
.data_line_item {
width: 100px;
text-align: center;
/*margin: 0 20px;*/
}
.num {
font-family: tttgbnumber;
font-size: 24px;
}
.data_box .lable {
font-size: 14px;
color: #7f858a;
line-height: 1;
margin-top: 3px;
}
.list{
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
.list .item {
width: 235px;
display: flex;
align-items: center;
background: #f1f1f1;
padding: 8px 6px 8px 6px;
border-radius: 8px;
margin: 0 0px 10px 10px;
}
.list .item .icon{
width: 24px;
height: 24px;
background-repeat: no-repeat;
background-size: 100% 100%;
position: relative;
flex-shrink: 0;
}
.list .item .title{
font-size: 16px;
margin-left: 6px;
line-height: 20px;
}
/* .list .item .title .text{
white-space: nowrap;
} */
.list .item .title .dec{
font-size: 12px;
color: #999;
margin-top: 2px;
}
.logo {
font-size: 14px;
font-family: "tttgbnumber";
text-align: center;
color: #7994a7;
}

38
resources/help/index.html Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<link rel="stylesheet" type="text/css" href="{{pluResPath}}help/help.css" />
<link rel="shortcut icon" href="#" />
</head>
{{@headStyle}}
<body>
<div class="container" id="container">
<div class="head_box">
<div class="id_text">ChatGPT-Plugin</div>
<h2 class="day_text">使用说明</h2>
<img class="chatgpt_logo" src="{{pluResPath}}img/icon/chatgpt.png"/>
</div>
{{each helpData val}}
<div class="data_box">
<div class="tab_lable">{{val.group}}</div>
<div class="list">
{{each val.list item}}
<div class="item">
<img class="icon" src="{{pluResPath}}img/icon/{{item.icon}}.png" />
<div class="title">
<div class="text">{{item.title}}</div>
<div class="dec">{{item.desc}}</div>
</div>
</div>
{{/each}}
</div>
</div>
{{/each}}
<div class="logo">Created By Yunzai-Bot and ChatGPT-Plugin</div>
</div>
</body>
</html>

BIN
resources/img/example1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
resources/img/icon/chat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
resources/img/icon/help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
resources/img/icon/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
resources/img/icon/text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

9
utils/text.js Normal file
View file

@ -0,0 +1,9 @@
/**
* 判断一段markdown文档中是否包含代码
* @param text
*/
export function codeExists (text = '') {
let regex = /^[\s\S]*\$.*\$[\s\S]*/
return regex.test(text)
}