From 5add41c982040759eb7877eccf3763f25a0f82b9 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Wed, 19 Feb 2025 13:10:59 +0800 Subject: [PATCH] fix: post processors --- apps/chat.js | 51 ++++------------ utils/postprocessors/BasicProcessor.js | 72 +++++++++++++++++++++++ utils/postprocessors/ReasonerProcessor.js | 55 +++++++++++++++++ utils/tools/QueryStarRailTool.js | 2 +- 4 files changed, 138 insertions(+), 42 deletions(-) create mode 100644 utils/postprocessors/BasicProcessor.js create mode 100644 utils/postprocessors/ReasonerProcessor.js diff --git a/apps/chat.js b/apps/chat.js index 0f21bf0..76a522c 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -2,10 +2,8 @@ import plugin from '../../../lib/plugins/plugin.js' import common from '../../../lib/common/common.js' import _ from 'lodash' import { Config } from '../utils/config.js' -import { v4 as uuid } from 'uuid' import AzureTTS from '../utils/tts/microsoft-azure.js' import VoiceVoxTTS from '../utils/tts/voicevox.js' -import BingSunoClient from '../utils/BingSuno.js' import { completeJSON, formatDate, @@ -21,8 +19,7 @@ import { makeForwardMsg, randomString, render, - renderUrl, - extractMarkdownJson + renderUrl } from '../utils/common.js' import fetch from 'node-fetch' @@ -34,6 +31,7 @@ import XinghuoClient from '../utils/xinghuo/xinghuo.js' import { getProxy } from '../utils/proxy.js' import { generateSuggestedResponse } from '../utils/chat.js' import Core from '../model/core.js' +import { collectProcessors } from '../utils/postprocessors/BasicProcessor.js' let version = Config.version let proxy = getProxy() @@ -785,45 +783,16 @@ export class chatgpt extends plugin { await redis.set(key, JSON.stringify(previousConversation), Config.conversationPreserveTime > 0 ? { EX: Config.conversationPreserveTime } : {}) } } - // 处理suno生成 - if (Config.enableChatSuno) { - let client = new BingSunoClient() // 此处使用了bing的suno客户端,后续和本地suno合并 - const sunoList = extractMarkdownJson(chatMessage.text) - if (sunoList.length == 0) { - const lyrics = client.extractLyrics(chatMessage.text) - if (lyrics !== '') { - sunoList.push( - { - json: { option: 'Suno', tags: client.generateRandomStyle(), title: `${e.sender.nickname}之歌`, lyrics }, - markdown: null, - origin: lyrics - } - ) - } - } - for (let suno of sunoList) { - if (suno.json.option == 'Suno') { - chatMessage.text = chatMessage.text.replace(suno.origin, `歌曲 《${suno.json.title}》`) - logger.info(`开始生成歌曲${suno.json.tags}`) - redis.set(`CHATGPT:SUNO:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => { - try { - if (Config.SunoModel == 'local') { - // 调用本地Suno配置进行歌曲生成 - client.getLocalSuno(suno.json, e) - } else if (Config.SunoModel == 'api') { - // 调用第三方Suno配置进行歌曲生成 - client.getApiSuno(suno.json, e) - } - } catch (err) { - redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) - this.reply('歌曲生成失败:' + err) - } - }) - } - } - } let response = chatMessage?.text?.replace('\n\n\n', '\n') + let postProcessors = await collectProcessors('post') let thinking = chatMessage.thinking_text + for (let processor of postProcessors) { + let output = await processor.processInner({ + text: response, thinking + }) + response = output.text + thinking = output.thinking_text + } if (handler.has('chatgpt.response.post')) { logger.debug('调用后处理器: chatgpt.response.post') handler.call('chatgpt.response.post', this.e, { diff --git a/utils/postprocessors/BasicProcessor.js b/utils/postprocessors/BasicProcessor.js new file mode 100644 index 0000000..bc749f2 --- /dev/null +++ b/utils/postprocessors/BasicProcessor.js @@ -0,0 +1,72 @@ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +export class AbstractPostProcessor { + name = '' + + /** + * 类型 + * @type {'pre' | 'post'} + */ + type = 'post' + + /** + * + * @param {{ + * text: string, + * thinking_text?: string + * }} input + * @returns {Promise<{ + * text: string, + * thinking_text?: string + * }>} + */ + async processInner (input) {} +} +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +/** + * collect + * @param {'pre' | 'post' | undefined} type + * @return {Promise} + */ +export async function collectProcessors (type) { + const processors = [] + const directoryPath = __dirname // 当前目录 + + // 读取目录中的所有文件 + const files = fs.readdirSync(directoryPath) + + // 遍历所有文件,筛选出.js文件 + for (const file of files) { + if (file.endsWith('.js') && file !== 'BasicProcessor.js') { // 排除自己 + const fullPath = path.join(directoryPath, file) + try { + // 动态导入模块 + const module = await import(fullPath) + + // 遍历模块的所有导出成员 + for (const key of Object.keys(module)) { + const ExportedClass = module[key] + + // 确保它是一个类,并且继承了 AbstractPostProcessor + if (typeof ExportedClass === 'function' && + Object.getPrototypeOf(ExportedClass) !== null) { + const parent = Object.getPrototypeOf(ExportedClass) + if (parent.name === 'AbstractPostProcessor') { + let instance = new ExportedClass() + if (!type || instance.type === type) { + processors.push(instance) + } + } + } + } + } catch (err) { + // console.error(`Error processing file ${file}:`, err) + } + } + } + + return processors +} diff --git a/utils/postprocessors/ReasonerProcessor.js b/utils/postprocessors/ReasonerProcessor.js new file mode 100644 index 0000000..51741ec --- /dev/null +++ b/utils/postprocessors/ReasonerProcessor.js @@ -0,0 +1,55 @@ +import { AbstractPostProcessor } from './BasicProcessor.js' + +export class ReasonerProcessor extends AbstractPostProcessor { + constructor () { + super() + this.name = 'ReasonerPostProcessor' + this.type = 'post' + } + + /** + * + * @param {{ + * text: string, + * thinking_text?: string + * }} input + * @returns {Promise<{ + * text: string, + * thinking_text?: string + * }>} + */ + async processInner (input) { + // eslint-disable-next-line camelcase + const { text, thinking_text } = extractThinkingTextAndText(input.text) + return { + text, + // eslint-disable-next-line camelcase + thinking_text: input.thinking_text + thinking_text + } + } +} + +/** + * written by gpt-4o + * @param str + * @returns {{thinkingText: string, text: *}|{thinkingText: *, text: *}} + */ +const extractThinkingTextAndText = (str) => { + // 使用正则表达式提取think标签内容 + const thinkRegex = /(.*?)<\/think>/s + const match = str.match(thinkRegex) + + // 如果找到了标签内容 + if (match) { + // thinking_text就是标签内的内容 + const thinkingText = match[1].trim() + + // text就是标签后的部分 + const text = str.slice(match.index + match[0].length).trim() + + return { thinkingText, text } + } + + // 如果没有标签内容,返回空或原始内容 + return { thinkingText: '', text: str.trim() } +} diff --git a/utils/tools/QueryStarRailTool.js b/utils/tools/QueryStarRailTool.js index 49fa587..8fd847d 100644 --- a/utils/tools/QueryStarRailTool.js +++ b/utils/tools/QueryStarRailTool.js @@ -43,7 +43,7 @@ export class QueryStarRailTool extends AbstractTool { e.user_id = qq e.isSr = true await ProfileList.render(e) - return 'the player panel of genshin impact has been sent to group. you don\'t need text version' + return 'the player panel of star rail has been sent to group. you don\'t need text version' } } catch (err) { return `failed to query, error: ${err.toString()}`