diff --git a/README.md b/README.md
index c682394..6d0e971 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@

-

@@ -18,7 +18,7 @@
### 推荐的相关文档和参考资料
本README
-[手册](https://chatgptplugin.ikechan8370.com/)
+[手册](https://yunzai.chat)
[文档1(建设中)](https://chatgpt-docs.err0r.top/)
[插件常见问题(鹤望兰版)](https://chatgptplugin.ikechan8370.com/guide/)
[Yunzai常见问题(LUCK小运版)](https://www.wolai.com/oA43vuW71aBnv7UsEysn4T)
@@ -52,16 +52,6 @@ Node.js >= 18 / Node.js >= 14(with node-fetch)
## 安装与使用方法
### 安装
-
-在安装之前,请先判断自己需要使用哪种模式,本插件支持官方API/第三方API/~~浏览器~~/必应四种模式。也可以选择**我全都要**(通过qq发送命令`#chatgpt切换浏览器/API/API3/Bing`实时切换)
-
-> #### API模式和浏览器模式如何选择?
->
-> * API模式会调用OpenAI官方提供的gpt-3.5-turbo API,ChatGPT官网同款模型,只需要提供API Key。一般情况下,该种方式响应速度更快,可配置项多,且不会像chatGPT官网一样总出现不可用的现象,但注意API调用是收费的,新用户有18美元试用金可用于支付,价格为`$0.0020/ 1K tokens`。(问题和回答**加起来**算token)
-> * API3模式会调用第三方提供的官网反代API,他会帮你绕过CF防护,需要提供ChatGPT的Token。效果与官网和浏览器一致,但稳定性不一定。发送#chatgpt设置token来设置token。
-> * (Deprecated)浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站,使得获得和官方以及API2模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问ChatGPT的IP地址),且响应速度不如API,而且高峰期容易无法使用。一般作为API3的下位替代。
-> * 必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing登录Cookie方可使用。强烈推荐
-
1. 进入 Yunzai根目录
2. 请将 chatgpt-plugin 放置在 Yunzai-Bot 的 plugins 目录下
@@ -83,12 +73,8 @@ pnpm i
如果是手工下载的 zip 压缩包,请将解压后的 chatgpt-plugin 文件夹(请删除压缩自带的-master或版本号后缀)放置在 Yunzai-Bot 目录下的 plugins 文件夹内
-> ~~浏览器模式仅为备选,如您需要使用浏览器模式,您还需要有**桌面环境**,优先级建议:API≈必应>API3>浏览器~~\
-> ~~2.20更新:必应被大削,变得蠢了,建议还是API/API3优先~~\
-> 4.2更新:必应站起来了,必应天下第一。建议都用必应,别用API/API3了。浏览器模式除非极其特殊的需求否则强烈建议不使用,已经不维护了。
-
3. 修改配置
-**本插件配置项比较多,强烈建议使用后台面板或[锅巴面板](https://github.com/guoba-yunzai/Guoba-Plugin)修改**
+**本插件配置项比较多,强烈建议使用后台工具箱或[锅巴面板](https://github.com/guoba-yunzai/Guoba-Plugin)修改**
或者创建和编辑config/config.json文件。
diff --git a/apps/chat.js b/apps/chat.js
index d6f65c3..b407296 100644
--- a/apps/chat.js
+++ b/apps/chat.js
@@ -77,6 +77,7 @@ import {solveCaptchaOneShot} from '../utils/bingCaptcha.js'
import {ClaudeAIClient} from '../utils/claude.ai/index.js'
import {getProxy} from '../utils/proxy.js'
import {QwenApi} from '../utils/alibaba/qwen-api.js'
+import {getChatHistoryGroup} from '../utils/chat.js'
try {
await import('@azure/openai')
@@ -565,6 +566,18 @@ export class chatgpt extends plugin {
}
break
}
+ case 'qwen': {
+ let qcs = await redis.keys('CHATGPT:CONVERSATIONS_QWEN:*')
+ for (let i = 0; i < qcs.length; i++) {
+ await redis.del(qcs[i])
+ // todo clean last message id
+ if (Config.debug) {
+ logger.info('delete qwen conversation bind: ' + qcs[i])
+ }
+ deleted++
+ }
+ break
+ }
}
await this.reply(`结束了${deleted}个用户的对话。`, true)
}
@@ -1090,9 +1103,7 @@ export class chatgpt extends plugin {
logger.mark({ conversation })
}
let chatMessage = await this.sendMessage(prompt, conversation, use, e)
- if (chatMessage.image) {
- this.setContext('solveBingCaptcha', false, 60)
- await e.reply([chatMessage.text, segment.image(`base64://${chatMessage.image}`)])
+ if (chatMessage?.noMsg) {
return false
}
// 处理星火和bard图片
@@ -1650,28 +1661,7 @@ export class chatgpt extends plugin {
if (master && !e.group) {
opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname
}
- let latestChats = await e.group.getChatHistory(0, 1)
- if (latestChats.length > 0) {
- let latestChat = latestChats[0]
- if (latestChat) {
- let seq = latestChat.seq
- let chats = []
- while (chats.length < Config.groupContextLength) {
- let chatHistory = await e.group.getChatHistory(seq, 20)
- chats.push(...chatHistory)
- }
- chats = chats.slice(0, Config.groupContextLength)
- let mm = await e.group.getMemberMap()
- chats.forEach(chat => {
- let sender = mm.get(chat.sender.user_id)
- if (sender) {
- chat.sender = sender
- }
- })
- // console.log(chats)
- opt.chats = chats
- }
- }
+ opt.chats = await getChatHistoryGroup(e, Config.groupContextLength)
} catch (err) {
logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err)
}
@@ -1778,7 +1768,7 @@ export class chatgpt extends plugin {
const { maxConv } = error
if (message && typeof message === 'string' && message.indexOf('CaptchaChallenge') > -1) {
if (bingToken) {
- if (maxConv > 20) {
+ if (maxConv >= 20) {
// maxConv为30说明token有效,可以通过解验证码码服务过码
await e.reply('出现必应验证码,尝试解决中')
try {
@@ -1857,10 +1847,10 @@ export class chatgpt extends plugin {
text: errorMessage,
error: true
}
- } else {
+ } else if (response?.response) {
return {
text: response?.response,
- quote: response.quote,
+ quote: response?.quote,
suggestedResponses: response.suggestedResponses,
conversationId: response.conversationId,
clientId: response.clientId,
@@ -1869,6 +1859,11 @@ export class chatgpt extends plugin {
parentMessageId: response.apology ? conversation.parentMessageId : response.messageId,
bingToken
}
+ } else {
+ logger.debug('no message')
+ return {
+ noMsg: true
+ }
}
}
case 'api3': {
@@ -2147,7 +2142,7 @@ export class chatgpt extends plugin {
opt.groupId = e.group_id
opt.qq = e.sender.user_id
opt.nickname = e.sender.card
- opt.groupName = e.group.name
+ opt.groupName = e.group.name || e.group_name
opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname
let master = (await getMasterQQ())[0]
if (master && e.group) {
@@ -2156,21 +2151,7 @@ export class chatgpt extends plugin {
if (master && !e.group) {
opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname
}
- let latestChat = await e.group.getChatHistory(0, 1)
- let seq = latestChat[0].seq
- let chats = []
- while (chats.length < Config.groupContextLength) {
- let chatHistory = await e.group.getChatHistory(seq, 20)
- chats.push(...chatHistory.reverse())
- }
- chats = chats.slice(0, Config.groupContextLength)
- // 太多可能会干扰AI对自身qq号和用户qq的判断,感觉gpt3.5也处理不了那么多信息
- chats = chats > 50 ? 50 : chats
- let mm = await e.group.getMemberMap()
- chats.forEach(chat => {
- let sender = mm.get(chat.sender.user_id)
- chat.sender = sender
- })
+ let chats = await getChatHistoryGroup(e, Config.groupContextLength)
opt.chats = chats
const namePlaceholder = '[name]'
const defaultBotName = 'ChatGPT'
diff --git a/apps/entertainment.js b/apps/entertainment.js
index fb8ff7f..eb6e9d8 100644
--- a/apps/entertainment.js
+++ b/apps/entertainment.js
@@ -50,6 +50,10 @@ export class Entertainment extends plugin {
reg: '^#(|最新)词云(\\d{1,2}h{0,1}|)$',
fnc: 'wordcloud_latest'
},
+ {
+ reg: '^#(我的)?(本月|本周|今日)?词云$',
+ fnc: 'wordcloud_new'
+ },
{
reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)',
fnc: 'translate'
@@ -215,10 +219,10 @@ ${translateLangLabels}
const regExp = /词云(\d{0,2})(|h)/
const match = e.msg.trim().match(regExp)
- const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h
+ const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h
if (duration > 24) {
- await e.reply('最多只能统计24小时内的记录哦')
+ await e.reply('最多只能统计24小时内的记录哦,你可以使用#本周词云和#本月词云获取更长时间的统计~')
return false
}
await e.reply('在统计啦,请稍等...')
@@ -236,6 +240,56 @@ ${translateLangLabels}
}
}
+ async wordcloud_new (e) {
+ if (e.isGroup) {
+ let groupId = e.group_id
+ let userId
+ if (e.msg.includes('我的')) {
+ userId = e.sender.user_id
+ }
+ let at = e.message.find(m => m.type === 'at')
+ if (at) {
+ userId = at.qq
+ }
+ let lock = await redis.get(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
+ if (lock) {
+ await e.reply('别着急,上次统计还没完呢')
+ return true
+ }
+ await e.reply('在统计啦,请稍等...')
+ let duration = 24
+ if (e.msg.includes('本周')) {
+ const now = new Date() // Get the current date and time
+ let day = now.getDay()
+ let diff = now.getDate() - day + (day === 0 ? -6 : 1)
+ const startOfWeek = new Date(new Date().setDate(diff))
+ startOfWeek.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day)
+ duration = (now - startOfWeek) / 1000 / 60 / 60
+ } else if (e.msg.includes('本月')) {
+ const now = new Date() // Get the current date and time
+ const startOfMonth = new Date(new Date().setDate(0))
+ startOfMonth.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day)
+ duration = (now - startOfMonth) / 1000 / 60 / 60
+ } else {
+ // 默认今天
+ const now = new Date()
+ const startOfToday = new Date() // Get the current date and time
+ startOfToday.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day)
+ duration = (now - startOfToday) / 1000 / 60 / 60
+ }
+ await redis.set(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`, '1', { EX: 600 })
+ try {
+ await makeWordcloud(e, e.group_id, duration, userId)
+ } catch (err) {
+ logger.error(err)
+ await e.reply(err)
+ }
+ await redis.del(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
+ } else {
+ await e.reply('请在群里发送此命令')
+ }
+ }
+
async combineEmoj (e) {
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
diff --git a/apps/prompts.js b/apps/prompts.js
index 3b5098c..a2b2809 100644
--- a/apps/prompts.js
+++ b/apps/prompts.js
@@ -157,7 +157,8 @@ export class help extends plugin {
const keyMap = {
api: 'promptPrefixOverride',
Custom: 'sydney',
- claude: 'slackClaudeGlobalPreset'
+ claude: 'slackClaudeGlobalPreset',
+ qwen: 'promptPrefixOverride'
}
if (keyMap[use]) {
diff --git a/client/BaseClient.js b/client/BaseClient.js
new file mode 100644
index 0000000..46232df
--- /dev/null
+++ b/client/BaseClient.js
@@ -0,0 +1,101 @@
+/**
+ * Base LLM Chat Client \
+ * All the Chat Models should extend this class
+ *
+ * @since 2023-10-26
+ * @author ikechan8370
+ */
+export class BaseClient {
+ /**
+ * create a new client
+ *
+ * @param props required fields: e, getMessageById, upsertMessage
+ */
+ constructor (props = {}) {
+ this.supportFunction = false
+ this.maxToken = 4096
+ this.tools = []
+ const {
+ e, getMessageById, upsertMessage
+ } = props
+ this.e = e
+ this.getMessageById = getMessageById
+ this.upsertMessage = upsertMessage
+ }
+
+ /**
+ * get a message according to the id. note that conversationId is not needed
+ *
+ * @type function
+ * @param {string} id
+ * @return {Promise