diff --git a/apps/chat.js b/apps/chat.js index 22e5567..b682e87 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -1942,7 +1942,8 @@ export class chatgpt extends plugin { let response = await client.sendMessage(prompt, { e, chatId: conversation?.conversationId, - image: image ? image[0] : undefined + image: image ? image[0] : undefined, + system: Config.xhPrompt }) return response } else if (use === 'azure') { diff --git a/apps/entertainment.js b/apps/entertainment.js index 3b888d8..31db478 100644 --- a/apps/entertainment.js +++ b/apps/entertainment.js @@ -56,7 +56,7 @@ export class Entertainment extends plugin { fnc: 'wordcloud_new' }, { - reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)', + reg: '^#((寄批踢|gpt|GPT)?翻[sS]*|chatgpt翻译帮助)', fnc: 'translate' }, { diff --git a/guoba.support.js b/guoba.support.js index ab6bf59..9115d88 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -27,184 +27,6 @@ export function supportGuoba () { configInfo: { // 配置项 schemas schemas: [ - { - field: 'blockWords', - label: '输出黑名单', - bottomHelpMessage: '检查输出结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开', - component: 'InputTextArea' - }, - { - field: 'promptBlockWords', - label: '输入黑名单', - bottomHelpMessage: '检查输入结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开', - component: 'InputTextArea' - }, - { - field: 'whitelist', - label: '对话白名单', - bottomHelpMessage: '默认设置为添加群号。优先级高于黑名单。\n' + - '注意:需要添加QQ号时在前面添加^(例如:^123456),此全局添加白名单,即除白名单以外的所有人都不能使用插件对话。\n' + - '如果需要在某个群里独享moment,即群聊中只有白名单上的qq号能用,则使用(群号^qq)的格式(例如:123456^123456)。\n' + - '白名单优先级:混合制 > qq > 群号。\n' + - '黑名单优先级: 群号 > qq > 混合制。', - component: 'Input' - }, - { - field: 'blacklist', - label: '对话黑名单', - bottomHelpMessage: '参考白名单设置规则。', - component: 'Input' - }, - { - field: 'imgOcr', - label: '图片识别', - bottomHelpMessage: '是否识别消息中图片的文字内容,需要同时包含图片和消息才生效', - component: 'Switch' - }, - { - field: 'enablePrivateChat', - label: '是否允许私聊机器人', - component: 'Switch' - }, - { - field: 'defaultUsePicture', - label: '全局图片模式', - bottomHelpMessage: '全局默认以图片形式回复', - component: 'Switch' - }, - { - field: 'defaultUseTTS', - label: '全局语音模式', - bottomHelpMessage: '全局默认以语音形式回复,使用默认角色音色', - component: 'Switch' - }, - { - field: 'ttsMode', - label: '语音模式源', - bottomHelpMessage: '语音模式下使用何种语音源进行文本->音频转换', - component: 'Select', - componentProps: { - options: [ - { - label: 'vits-uma-genshin-honkai', - value: 'vits-uma-genshin-honkai' - }, - { - label: '微软Azure', - value: 'azure' - }, - { - label: 'VoiceVox', - value: 'voicevox' - } - ] - } - }, - { - field: 'defaultTTSRole', - label: 'vits默认角色', - bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定', - component: 'Select', - componentProps: { - options: [{ - label: '随机', - value: '随机' - }].concat(speakers.map(s => { return { label: s, value: s } })) - } - }, - { - field: 'azureTTSSpeaker', - label: 'Azure默认角色', - bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定', - component: 'Select', - componentProps: { - options: [{ - label: '随机', - value: '随机' - }, - ...azureRoleList.flatMap(item => [ - item.roleInfo - ]).map(s => ({ - label: s, - value: s - }))] - } - }, - { - field: 'voicevoxTTSSpeaker', - label: 'VoiceVox默认角色', - bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定', - component: 'Select', - componentProps: { - options: [{ - label: '随机', - value: '随机' - }, - ...voxRoleList.flatMap(item => [ - ...item.styles.map(style => `${item.name}-${style.name}`), - item.name - ]).map(s => ({ - label: s, - value: s - }))] - } - }, - { - field: 'ttsRegex', - label: '语音过滤正则表达式', - bottomHelpMessage: '语音模式下,配置此项以过滤不想被读出来的内容。表达式测试地址:https://www.runoob.com/regexp/regexp-syntax.html', - component: 'Input' - }, - { - field: 'ttsAutoFallbackThreshold', - label: '语音转文字阈值', - helpMessage: '语音模式下,字数超过这个阈值就降级为文字', - bottomHelpMessage: '语音转为文字的阈值', - component: 'InputNumber', - componentProps: { - min: 0, - max: 299 - } - }, - { - field: 'alsoSendText', - label: '语音同时发送文字', - bottomHelpMessage: '语音模式下,同时发送文字版,避免音质较低听不懂', - component: 'Switch' - }, - { - field: 'autoJapanese', - label: 'vits模式日语输出', - bottomHelpMessage: '使用vits语音时,将机器人的文字回复翻译成日文后获取语音。' + - '若想使用插件的翻译功能,发送"#chatgpt翻译帮助"查看使用方法,支持图片翻译,引用翻译...', - component: 'Switch' - }, - { - field: 'autoUsePicture', - label: '长文本自动转图片', - bottomHelpMessage: '字数大于阈值会自动用图片发送,即使是文本模式', - component: 'Switch' - }, - { - field: 'autoUsePictureThreshold', - label: '自动转图片阈值', - helpMessage: '长文本自动转图片开启后才生效', - bottomHelpMessage: '自动转图片的字数阈值', - component: 'InputNumber', - componentProps: { - min: 0 - } - }, - { - field: 'conversationPreserveTime', - label: '对话保留时长', - helpMessage: '单位:秒', - bottomHelpMessage: '每个人发起的对话保留时长。超过这个时长没有进行对话,再进行对话将开启新的对话。', - component: 'InputNumber', - componentProps: { - min: 0 - } - }, { field: 'toggleMode', label: '触发方式', @@ -217,45 +39,12 @@ export function supportGuoba () { ] } }, - { - field: 'groupMerge', - label: '群组消息合并', - bottomHelpMessage: '开启后,群聊消息将被视为同一对话', - component: 'Switch' - }, { field: 'allowOtherMode', label: '允许其他模式', bottomHelpMessage: '开启后,则允许用户使用#chat1/#chat3/#chatglm/#bing等命令无视全局模式进行聊天', component: 'Switch' }, - { - field: 'quoteReply', - label: '图片引用消息', - bottomHelpMessage: '在回复图片时引用原始消息', - component: 'Switch' - }, - { - field: 'showQRCode', - label: '启用二维码', - bottomHelpMessage: '在图片模式中启用二维码。该对话内容将被发送至第三方服务器以进行渲染展示,如果不希望对话内容被上传到第三方服务器请关闭此功能', - component: 'Switch' - }, - { - field: 'drawCD', - label: '绘图CD', - helpMessage: '单位:秒', - bottomHelpMessage: '绘图指令的CD时间,主人不受限制', - component: 'InputNumber', - componentProps: { - min: 0 - } - }, - { - field: 'enableDraw', - label: '绘图功能开关', - component: 'Switch' - }, { field: 'proxy', label: '代理服务器地址', @@ -268,6 +57,20 @@ export function supportGuoba () { bottomHelpMessage: '将输出更多调试信息,如果不希望控制台刷屏的话,可以关闭', component: 'Switch' }, + { + field: 'translateSource', + label: '翻译来源', + bottomHelpMessage: '#gpt翻译使用的AI来源', + component: 'Select', + componentProps: { + options: [ + { label: 'OpenAI', value: 'openai' }, + { label: 'Gemini', value: 'gemini' }, + { label: '星火', value: 'xh' }, + { label: '通义千问', value: 'qwen' } + ] + } + }, { label: '以下为服务超时配置。', component: 'Divider' @@ -792,6 +595,221 @@ export function supportGuoba () { bottomHelpMessage: '对https://generativelanguage.googleapis.com的反代', component: 'Input' }, + { + label: '以下为一些杂项配置。', + component: 'Divider' + }, + { + field: 'blockWords', + label: '输出黑名单', + bottomHelpMessage: '检查输出结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开', + component: 'InputTextArea' + }, + { + field: 'promptBlockWords', + label: '输入黑名单', + bottomHelpMessage: '检查输入结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开', + component: 'InputTextArea' + }, + { + field: 'whitelist', + label: '对话白名单', + bottomHelpMessage: '默认设置为添加群号。优先级高于黑名单。\n' + + '注意:需要添加QQ号时在前面添加^(例如:^123456),此全局添加白名单,即除白名单以外的所有人都不能使用插件对话。\n' + + '如果需要在某个群里独享moment,即群聊中只有白名单上的qq号能用,则使用(群号^qq)的格式(例如:123456^123456)。\n' + + '白名单优先级:混合制 > qq > 群号。\n' + + '黑名单优先级: 群号 > qq > 混合制。', + component: 'Input' + }, + { + field: 'blacklist', + label: '对话黑名单', + bottomHelpMessage: '参考白名单设置规则。', + component: 'Input' + }, + { + field: 'imgOcr', + label: '图片识别', + bottomHelpMessage: '是否识别消息中图片的文字内容,需要同时包含图片和消息才生效', + component: 'Switch' + }, + { + field: 'enablePrivateChat', + label: '是否允许私聊机器人', + component: 'Switch' + }, + { + field: 'defaultUsePicture', + label: '全局图片模式', + bottomHelpMessage: '全局默认以图片形式回复', + component: 'Switch' + }, + { + field: 'defaultUseTTS', + label: '全局语音模式', + bottomHelpMessage: '全局默认以语音形式回复,使用默认角色音色', + component: 'Switch' + }, + { + field: 'ttsMode', + label: '语音模式源', + bottomHelpMessage: '语音模式下使用何种语音源进行文本->音频转换', + component: 'Select', + componentProps: { + options: [ + { + label: 'vits-uma-genshin-honkai', + value: 'vits-uma-genshin-honkai' + }, + { + label: '微软Azure', + value: 'azure' + }, + { + label: 'VoiceVox', + value: 'voicevox' + } + ] + } + }, + { + field: 'defaultTTSRole', + label: 'vits默认角色', + bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定', + component: 'Select', + componentProps: { + options: [{ + label: '随机', + value: '随机' + }].concat(speakers.map(s => { return { label: s, value: s } })) + } + }, + { + field: 'azureTTSSpeaker', + label: 'Azure默认角色', + bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定', + component: 'Select', + componentProps: { + options: [{ + label: '随机', + value: '随机' + }, + ...azureRoleList.flatMap(item => [ + item.roleInfo + ]).map(s => ({ + label: s, + value: s + }))] + } + }, + { + field: 'voicevoxTTSSpeaker', + label: 'VoiceVox默认角色', + bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定', + component: 'Select', + componentProps: { + options: [{ + label: '随机', + value: '随机' + }, + ...voxRoleList.flatMap(item => [ + ...item.styles.map(style => `${item.name}-${style.name}`), + item.name + ]).map(s => ({ + label: s, + value: s + }))] + } + }, + { + field: 'ttsRegex', + label: '语音过滤正则表达式', + bottomHelpMessage: '语音模式下,配置此项以过滤不想被读出来的内容。表达式测试地址:https://www.runoob.com/regexp/regexp-syntax.html', + component: 'Input' + }, + { + field: 'ttsAutoFallbackThreshold', + label: '语音转文字阈值', + helpMessage: '语音模式下,字数超过这个阈值就降级为文字', + bottomHelpMessage: '语音转为文字的阈值', + component: 'InputNumber', + componentProps: { + min: 0, + max: 299 + } + }, + { + field: 'alsoSendText', + label: '语音同时发送文字', + bottomHelpMessage: '语音模式下,同时发送文字版,避免音质较低听不懂', + component: 'Switch' + }, + { + field: 'autoJapanese', + label: 'vits模式日语输出', + bottomHelpMessage: '使用vits语音时,将机器人的文字回复翻译成日文后获取语音。' + + '若想使用插件的翻译功能,发送"#chatgpt翻译帮助"查看使用方法,支持图片翻译,引用翻译...', + component: 'Switch' + }, + { + field: 'autoUsePicture', + label: '长文本自动转图片', + bottomHelpMessage: '字数大于阈值会自动用图片发送,即使是文本模式', + component: 'Switch' + }, + { + field: 'autoUsePictureThreshold', + label: '自动转图片阈值', + helpMessage: '长文本自动转图片开启后才生效', + bottomHelpMessage: '自动转图片的字数阈值', + component: 'InputNumber', + componentProps: { + min: 0 + } + }, + { + field: 'conversationPreserveTime', + label: '对话保留时长', + helpMessage: '单位:秒', + bottomHelpMessage: '每个人发起的对话保留时长。超过这个时长没有进行对话,再进行对话将开启新的对话。', + component: 'InputNumber', + componentProps: { + min: 0 + } + }, + { + field: 'groupMerge', + label: '群组消息合并', + bottomHelpMessage: '开启后,群聊消息将被视为同一对话', + component: 'Switch' + }, + { + field: 'quoteReply', + label: '图片引用消息', + bottomHelpMessage: '在回复图片时引用原始消息', + component: 'Switch' + }, + { + field: 'showQRCode', + label: '启用二维码', + bottomHelpMessage: '在图片模式中启用二维码。该对话内容将被发送至第三方服务器以进行渲染展示,如果不希望对话内容被上传到第三方服务器请关闭此功能', + component: 'Switch' + }, + { + field: 'drawCD', + label: '绘图CD', + helpMessage: '单位:秒', + bottomHelpMessage: '绘图指令的CD时间,主人不受限制', + component: 'InputNumber', + componentProps: { + min: 0 + } + }, + { + field: 'enableDraw', + label: '绘图功能开关', + component: 'Switch' + }, { label: '以下为Suno音乐合成的配置。', component: 'Divider' diff --git a/utils/config.js b/utils/config.js index df31894..5e9dd77 100644 --- a/utils/config.js +++ b/utils/config.js @@ -177,6 +177,7 @@ const defaultConfig = { chatglmRefreshToken: '', sunoSessToken: '', sunoClientToken: '', + translateSource: 'openai', version: 'v2.7.10' } const _path = process.cwd() diff --git a/utils/translate.js b/utils/translate.js index 96e1d11..706da20 100644 --- a/utils/translate.js +++ b/utils/translate.js @@ -1,5 +1,13 @@ import md5 from 'md5' import _ from 'lodash' +import { Config } from './config.js' +import { ChatGPTAPI } from './openai/chatgpt-api.js' +import { newFetch } from './proxy.js' +import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js' +import XinghuoClient from './xinghuo/xinghuo.js' +import {getImg, getMessageById, upsertMessage} from './common.js' +import {QwenApi} from "./alibaba/qwen-api.js"; +import {v4 as uuid} from "uuid"; // 代码参考:https://github.com/yeyang52/yenai-plugin/blob/b50b11338adfa5a4ef93912eefd2f1f704e8b990/model/api/funApi.js#L25 export const translateLangSupports = [ @@ -20,7 +28,7 @@ export const translateLangSupports = [ { code: 'zh-CHS', label: '中文', abbr: '中', alphabet: 'Z' } ] const API_ERROR = '出了点小问题,待会再试试吧' -export async function translate (msg, to = 'auto') { +export async function translateOld (msg, to = 'auto') { let from = 'auto' if (to !== 'auto') to = translateLangSupports.find(item => item.abbr == to)?.code if (!to) return `未找到翻译的语种,支持的语言为:\n${translateLangSupports.map(item => item.abbr).join(',')}\n` @@ -95,3 +103,101 @@ export async function translate (msg, to = 'auto') { return API_ERROR } } + +/** + * + * @param msg 要翻译的 + * @param from 语种 + * @param to 语种 + * @param ai ai来源,支持openai, gemini, xh, qwen + * @returns {Promise<*|string>} + */ +export async function translate (msg, to = '中', from = 'auto', ai = Config.translateSource) { + try { + const lang = translateLangSupports.find(item => item.abbr == to)?.code + if (!lang) return `未找到翻译的语种,支持的语言为:\n${translateLangSupports.map(item => item.abbr).join(',')}\n` + // if ai is not in the list, throw error + if (!['openai', 'gemini', 'xh', 'qwen'].includes(ai)) throw new Error('ai来源错误') + let system = `You will be provided with a sentence in the language with language code [${from}], and your task is to translate it into [${lang}]. Just print the result without any other words.` + switch (ai) { + case 'openai': { + let api = new ChatGPTAPI({ + apiBaseUrl: Config.openAiBaseUrl, + apiKey: Config.apiKey, + fetch: newFetch + }) + const res = await api.sendMessage(msg, { + systemMessage: system, + completionParams: { + model: 'gpt-3.5-turbo' + } + }) + return res.text + } + case 'gemini': { + let client = new CustomGoogleGeminiClient({ + key: Config.geminiKey, + model: Config.geminiModel, + baseUrl: Config.geminiBaseUrl, + debug: Config.debug + }) + let option = { + stream: false, + onProgress: (data) => { + if (Config.debug) { + logger.info(data) + } + }, + system + } + let res = await client.sendMessage(msg, option) + return res.text + } + case 'xh': { + let client = new XinghuoClient({ + ssoSessionId: Config.xinghuoToken + }) + let response = await client.sendMessage(msg, { system }) + return response.text + } + case 'qwen': { + let completionParams = { + parameters: { + top_p: Config.qwenTopP || 0.5, + top_k: Config.qwenTopK || 50, + seed: Config.qwenSeed > 0 ? Config.qwenSeed : Math.floor(Math.random() * 114514), + temperature: Config.qwenTemperature || 1, + enable_search: !!Config.qwenEnableSearch + } + } + if (Config.qwenModel) { + completionParams.model = Config.qwenModel + } + let opts = { + apiKey: Config.qwenApiKey, + debug: false, + systemMessage: system, + completionParams, + fetch: newFetch + } + let client = new QwenApi(opts) + let option = { + timeoutMs: 600000, + completionParams + } + let result + try { + result = await client.sendMessage(msg, option) + } catch (err) { + logger.error(err) + throw new Error(err) + } + return result.text + } + } + } catch (e) { + logger.error(e) + logger.info('基于LLM的翻译失败,转用老版翻译') + return await translateOld(msg, to) + } +} diff --git a/utils/xinghuo/xinghuo.js b/utils/xinghuo/xinghuo.js index 3040f79..ba6e5bb 100644 --- a/utils/xinghuo/xinghuo.js +++ b/utils/xinghuo/xinghuo.js @@ -395,7 +395,7 @@ export default class XinghuoClient { logger.warn('星火设定序列化失败,本次对话不附带设定') } } else { - Prompt = Config.xhPrompt ? [{ role: 'system', content: Config.xhPrompt }] : [] + Prompt = option.system ? [{ role: 'system', content: option.system }] : [] } if (Config.xhPromptEval) { Prompt.forEach(obj => {