From 7b58f2057816788f5cdd3eafe80ceb9b35190669 Mon Sep 17 00:00:00 2001 From: HalcyonAlcedo <41666148+HalcyonAlcedo@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:50:53 +0800 Subject: [PATCH] =?UTF-8?q?=E7=8B=AC=E7=AB=8Brender=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E5=9B=BE=E7=89=87=E6=A8=A1=E5=BC=8F=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E5=9B=9E=E5=A4=8D=20(#204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复引用转发,默认bing模式并发 * 开启stream增加稳定性 * fix: remove queue element only in non-bing mode * 使用chatgpt-api自带的超时逻辑,文字过多时启动切换到图片输出防止被吞 * Update chat.js * 添加Bing专用的图片输出样式 * 添加chatgpt的新图片模式,临时处理切换api导致的对话异常 * 修改bing样式表 * 为图片添加外部页面缓存 * 为图片模式添加MathJax * feat: add switch for qrcode * 防止script攻击 * 修复网页模板错误 * 修复bing页面引用错误 * 缓存服务器异常时处理 * 添加默认配置加载 * 修复配置文件路径错误 * 删除重复的模板文件,修复二维码地址错误 * 修正图片渲染错误 * 修复引用渲染错误 * 二维码网址统一改为使用本地配置 * 添加关闭思考提示的配置项 * 修复在Windows上无法载入配置文件的问题 * 修复关闭qr的情况下渲染错误 * 改为使用base64传递返回数据 * 当异常过多时使用图片输出 * 添加锅巴面板配置支持 * 补充遗漏的默认配置 * 修复qr模式下引用未被传递的问题 * 修复未将引用数据传输给缓存服务器的问题 * 删除无用的bingTimeoutMs配置项 * 添加消息队列超时弹出 * 优化图片模式处理,解决对话队列卡住的问题 * 添加对图片ocr的支持 * 添加图片识别配置项 * 添加黑名单配置项 * 修复一些bug * 修改锅巴配置格式和描述 * 传入数据也使用markdown * 图片识别换行改为marked兼容 * 添加绘图CD配置项 * 独立render模块,添加图片回复引用 --------- Co-authored-by: ikechan8370 --- apps/chat.js | 8 ++--- apps/help.js | 3 +- config/config.example.js | 2 ++ guoba.support.js | 16 +++++++++ utils/common.js | 71 +++++++++++++++++++++++++++++++++++++++- utils/config.js | 1 + 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/apps/chat.js b/apps/chat.js index 1785ee4..16193d2 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid' import delay from 'delay' import { ChatGPTAPI } from 'chatgpt' import { ChatGPTClient, BingAIClient } from '@waylaidwanderer/chatgpt-api' -import { escapeHtml, getMessageById, makeForwardMsg, tryTimes, upsertMessage, randomString } from '../utils/common.js' +import { render, getMessageById, makeForwardMsg, tryTimes, upsertMessage, randomString } from '../utils/common.js' import { ChatGPTPuppeteer } from '../utils/browser.js' import { KeyvFile } from 'keyv-file' import { OfficialChatGPTClient } from '../utils/message.js' @@ -527,7 +527,7 @@ export class chatgpt extends plugin { } } } - await e.runtime.render('chatgpt-plugin', template, { + await e.reply(await render(e, 'chatgpt-plugin', template, { content: new Buffer.from(content).toString('base64'), prompt: new Buffer.from(prompt).toString('base64'), senderName: e.sender.nickname, @@ -535,7 +535,7 @@ export class chatgpt extends plugin { quotes: quote, cache: cacheData, version - }) + },{retType: Config.quoteReply ? 'base64' : ''}), e.isGroup && Config.quoteReply) } async sendMessage (prompt, conversation = {}, use, e) { @@ -717,7 +717,7 @@ export class chatgpt extends plugin { logger.mark('all conversations: ', conversations) } // let conversationsFirst10 = conversations.slice(0, 10) - await e.runtime.render('chatgpt-plugin', 'conversation/chatgpt', { conversations, version }) + await render(e, 'chatgpt-plugin', 'conversation/chatgpt', { conversations, version }) let text = '对话列表\n' text += '对话id | 对话发起者 \n' conversations.forEach(c => { diff --git a/apps/help.js b/apps/help.js index 1a548b3..c28904a 100644 --- a/apps/help.js +++ b/apps/help.js @@ -1,5 +1,6 @@ import plugin from '../../../lib/plugins/plugin.js' import { Config } from '../utils/config.js' +import { render } from '../utils/common.js' let version = Config.version let helpData = [ { @@ -131,6 +132,6 @@ export class help extends plugin { } async help (e) { - await e.runtime.render('chatgpt-plugin', 'help/index', { helpData, version }) + await render(e, 'chatgpt-plugin', 'help/index', { helpData, version }) } } diff --git a/config/config.example.js b/config/config.example.js index c462d85..9e3e404 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -22,6 +22,8 @@ export default { // conversationPreserveTime: 0, // 触发方式 可选值:at 或 prefix 。at模式下只有at机器人才会回复。prefix模式下不需要at,但需要添加前缀#chat // toggleMode: 'at', + // 是否在回复图片时引用原始消息 + // quoteReply: true, // 是否在图片模式中启用二维码。该对话内容将被发送至第三方服务器以进行渲染展示。改为false关闭该功能 // showQRCode: true, // 图片内容渲染服务器API地址 diff --git a/guoba.support.js b/guoba.support.js index ec871f0..9605158 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -87,6 +87,12 @@ export function supportGuoba() { ], }, }, + { + field: 'quoteReply', + label: '图片引用消息', + bottomHelpMessage: '在回复图片时引用原始消息', + component: 'Switch', + }, { field: 'showQRCode', label: '启用二维码', @@ -105,6 +111,16 @@ export function supportGuoba() { bottomHelpMessage: '图片内容渲染服务器开启预制访问代码,当渲染服务器访问较慢时可以开启,但无法保证访问代码可以正常访问页面。', component: 'Switch', }, + { + field: 'drawCD', + label: '绘图CD', + helpMessage: '单位:秒', + bottomHelpMessage: '绘图指令的CD时间,主人不受限制', + component: 'InputNumber', + componentProps: { + min: 0, + }, + }, { field: 'proxy', label: '代理服务器地址', diff --git a/utils/common.js b/utils/common.js index f9cd4cd..2ebcd9a 100644 --- a/utils/common.js +++ b/utils/common.js @@ -4,6 +4,7 @@ import { exec } from 'child_process' import lodash from 'lodash' import fs from 'node:fs' import path from 'node:path' +import puppeteer from '../../../lib/puppeteer/puppeteer.js' // export function markdownToText (markdown) { // return remark() // .use(stripMarkdown) @@ -217,4 +218,72 @@ export function mkdirs (dirname) { return true } } -} \ No newline at end of file +} + +/** + * + * @param pluginKey plugin key + * @param htmlPath html文件路径,相对于plugin resources目录 + * @param data 渲染数据 + * @param renderCfg 渲染配置 + * @param renderCfg.retType 返回值类型 + * * default/空:自动发送图片,返回true + * * msgId:自动发送图片,返回msg id + * * base64: 不自动发送图像,返回图像base64数据 + * @param renderCfg.beforeRender({data}) 可改写渲染的data数据 + * @returns {Promise} + */ +export async function render (e, pluginKey, htmlPath, data = {}, renderCfg = {}) { + // 处理传入的path + htmlPath = htmlPath.replace(/.html$/, '') + let paths = lodash.filter(htmlPath.split('/'), (p) => !!p) + htmlPath = paths.join('/') + // 创建目录 + const mkdir = (check) => { + let currDir = `${process.cwd()}/data` + for (let p of check.split('/')) { + currDir = `${currDir}/${p}` + if (!fs.existsSync(currDir)) { + fs.mkdirSync(currDir) + } + } + return currDir + } + mkdir(`html/${pluginKey}/${htmlPath}`) + // 自动计算pluResPath + let pluResPath = `../../../${lodash.repeat('../', paths.length)}plugins/${pluginKey}/resources/` + // 渲染data + data = { + ...data, + _plugin: pluginKey, + _htmlPath: htmlPath, + pluResPath, + tplFile: `./plugins/${pluginKey}/resources/${htmlPath}.html`, + saveId: data.saveId || data.save_id || paths[paths.length - 1], + pageGotoParams: { + waitUntil: 'networkidle0' + } + } + // 处理beforeRender + if (renderCfg.beforeRender) { + data = renderCfg.beforeRender({ data }) || data + } + // 保存模板数据 + if (process.argv.includes('web-debug')) { + // debug下保存当前页面的渲染数据,方便模板编写与调试 + // 由于只用于调试,开发者只关注自己当时开发的文件即可,暂不考虑app及plugin的命名冲突 + let saveDir = mkdir(`ViewData/${pluginKey}`) + let file = `${saveDir}/${data._htmlPath.split('/').join('_')}.json` + fs.writeFileSync(file, JSON.stringify(data)) + } + // 截图 + let base64 = await puppeteer.screenshot(`${pluginKey}/${htmlPath}`, data) + if (renderCfg.retType === 'base64') { + return base64 + } + let ret = true + if (base64) { + ret = await e.reply(base64) + } + return renderCfg.retType === 'msgId' ? ret : true +} diff --git a/utils/config.js b/utils/config.js index d569102..d7172f5 100644 --- a/utils/config.js +++ b/utils/config.js @@ -9,6 +9,7 @@ const defaultConfig = { autoUsePictureThreshold: 1200, conversationPreserveTime: 0, toggleMode: 'at', + quoteReply: true, showQRCode: true, cacheUrl: 'https://content.alcedogroup.com', cacheEntry: false,