From d111d2625e954f9752712239405e7042abfb9816 Mon Sep 17 00:00:00 2001 From: zyc404 Date: Wed, 8 May 2024 15:32:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9suno=E4=BC=AA=E9=80=A0?= =?UTF-8?q?=E7=94=9F=E6=88=90=E7=AD=96=E7=95=A5=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9B=B4=E5=A4=9A=E6=A8=A1=E5=9E=8B=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/chat.js | 29 ++++++++++++++++++++++++++++- resources/view/setting_view.json | 28 +++++++++++++++++----------- utils/BingSuno.js | 6 +++--- utils/SydneyAIClient.js | 13 +------------ utils/common.js | 32 ++++++++++++++++++++++++++++++-- utils/config.js | 3 ++- utils/xinghuo/xinghuo.js | 5 +++++ 7 files changed, 86 insertions(+), 30 deletions(-) diff --git a/apps/chat.js b/apps/chat.js index 20f305d..bc1058e 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -5,6 +5,7 @@ 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, @@ -20,7 +21,8 @@ import { makeForwardMsg, randomString, render, - renderUrl + renderUrl, + extractMarkdownJson } from '../utils/common.js' import fetch from 'node-fetch' @@ -838,6 +840,31 @@ export class chatgpt extends plugin { await redis.set(key, JSON.stringify(previousConversation), Config.conversationPreserveTime > 0 ? { EX: Config.conversationPreserveTime } : {}) } } + // 处理suno生成 + if ((use === 'bing' || use === 'xh') && Config.enableChatSuno) { + const sunoList = extractMarkdownJson(chatMessage.text) + for (let suno of sunoList) { + if (suno.json.option == 'Suno') { + chatMessage.text = chatMessage.text.replace(suno.markdown, `歌曲 《${suno.json.title}》`) + logger.info(`开始生成歌曲${suno.json.tags}`) + let client = new BingSunoClient() // 此处使用了bing的suno客户端,后续和本地suno合并 + 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 mood = 'blandness' if (!response) { diff --git a/resources/view/setting_view.json b/resources/view/setting_view.json index 01253fa..a0c09ee 100644 --- a/resources/view/setting_view.json +++ b/resources/view/setting_view.json @@ -580,11 +580,6 @@ "label": "允许生成歌曲等内容", "data": "enableGenerateSuno" }, - { - "type": "check", - "label": "伪造歌曲生成", - "data": "enableGenerateSunoForger" - }, { "type": "url", "label": "必应验证码pass服务", @@ -603,12 +598,6 @@ "data": "bingSuno", "items": [ { "label": "Bing", "value": "bing" }, { "label": "本地", "value": "local" }, { "label": "第三方", "value": "api" } ] }, - { - "type": "url", - "label": "第三方歌曲生成API地址", - "placeholder": "https://github.com/gcui-art/suno-api的api地址", - "data": "bingSunoApi" - }, { "type": "textarea", "label": "前置对话第一轮(用户)", @@ -1054,6 +1043,23 @@ "label": "client token", "placeholder": "suno的__client token,需要与sunoSessToken一一对应数量相同,多个用逗号隔开", "data": "sunoClientToken" + }, + { + "type": "check", + "label": "允许聊天指令声音音乐", + "data": "enableChatSuno" + }, + { + "type": "select", + "label": "调用模式", + "data": "SunoModel", + "items": [ { "label": "本地", "value": "local" }, { "label": "第三方", "value": "api" } ] + }, + { + "type": "url", + "label": "第三方歌曲生成API地址", + "placeholder": "https://github.com/gcui-art/suno-api的api地址", + "data": "bingSunoApi" } ] }, diff --git a/utils/BingSuno.js b/utils/BingSuno.js index 25c4b1b..30ca66a 100644 --- a/utils/BingSuno.js +++ b/utils/BingSuno.js @@ -69,10 +69,10 @@ export default class BingSunoClient { sunoURL, prompt: prompt.songPrompt } - await e.reply('Bing Suno 生成中,请稍后') + await e.reply('Suno 生成中,请稍后') this.replyMsg(sunoDisplayResult, e) } else { - await e.reply('Bing Suno 数据获取失败') + await e.reply('Suno 数据获取失败') redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) } redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) @@ -84,7 +84,7 @@ export default class BingSunoClient { redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) return true } - let description = prompt.songPrompt + let description = prompt.songPrompt || prompt.lyrics await e.reply('正在生成,请稍后') try { let sessTokens = Config.sunoSessToken.split(',') diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index 0ac15e0..fcac721 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -338,7 +338,7 @@ export default class SydneyAIClient { ((Config.enableGroupContext && groupId) ? groupContextTip : '') + ((Config.enforceMaster && master) ? masterTip : '') + (Config.sydneyMood ? moodTip : '') + - ((!Config.enableGenerateSuno && Config.bingSuno != 'bing' && Config.enableGenerateSunoForger) ? 'If I ask you to generate music or write songs, you need to reply with information suitable for Suno to generate music. Please use keywords such as Verse, Chorus, Bridge, Outro, and End to segment the lyrics, such as [Verse], The returned message is in JSON format, with a structure of {"option": "Suno", "tags": "style", "title": "title of the song", "lyrics": "lyrics"}.' : '') + ((!Config.enableGenerateSuno && Config.enableChatSuno) ? 'If I ask you to generate music or write songs, you need to reply with information suitable for Suno to generate music. Please use keywords such as Verse, Chorus, Bridge, Outro, and End to segment the lyrics, such as [Verse], The returned message is in JSON format, with a structure of {"option": "Suno", "tags": "style", "title": "title of the song", "lyrics": "lyrics"}.' : '') if (!text) { previousMessages = pm } else { @@ -835,17 +835,6 @@ export default class SydneyAIClient { message.adaptiveCards = adaptiveCardsSoFar message.text = replySoFar.join('') } - // 伪造歌曲生成 - if (Config.enableGenerateSunoForger) { - const sunoList = extractMarkdownJson(message.text) - for (let suno of sunoList) { - if (suno.json.option == 'Suno') { - message.text = message.text.replace(suno.markdown, `歌曲 《${suno.json.title}》`) - logger.info(`开始生成歌曲${suno.json.tags}`) - onSunoCreateRequest(suno.json) - } - } - } resolve({ message, conversationExpiryTime: event?.item?.conversationExpiryTime diff --git a/utils/common.js b/utils/common.js index fe67a8c..f0d0848 100644 --- a/utils/common.js +++ b/utils/common.js @@ -1243,6 +1243,34 @@ function maskString (str) { return firstThreeChars + maskedChars + lastThreeChars } +/** + * generated by ai + * @param rawJsonString + * @returns {string} + */ +function fixNewlinesInJsonString(rawJsonString) { + // 标记是否在字符串内 + let inString = false + // 结果字符串 + let result = '' + for (let i = 0; i < rawJsonString.length; i++) { + const currentChar = rawJsonString[i] + const nextChar = i + 1 < rawJsonString.length ? rawJsonString[i + 1] : '' + // 检查当前字符是否为双引号,且不是转义的双引号 + if (currentChar === '"' && (i === 0 || rawJsonString[i - 1] !== '\\')) { + inString = !inString // 切换在字符串内的标记 + } + // 如果在字符串内且遇到换行符,则替换为\\n + if (inString && (currentChar === '\n' || (currentChar === '\r' && nextChar === '\n'))) { + result += '\\n' + if (currentChar === '\r') i++ // 跳过\n + } else { + result += currentChar + } + } + return result +} + /** * generated by ai * @param text @@ -1259,7 +1287,7 @@ export function extractMarkdownJson(text) { // 如果已经在一个 JSON 中,先结束当前的 JSON if (currentJson) { try { - const parsedJson = JSON.parse(currentJson) + const parsedJson = JSON.parse(fixNewlinesInJsonString(currentJson)) mdJsonPairs.push({ json: parsedJson, markdown: currentMd + '```' }) } catch (e) { console.error('JSON解析错误:', e) @@ -1271,7 +1299,7 @@ export function extractMarkdownJson(text) { } else if (line.startsWith('```') && currentJson) { // 结束当前的 JSON try { - const parsedJson = JSON.parse(currentJson) + const parsedJson = JSON.parse(fixNewlinesInJsonString(currentJson)) mdJsonPairs.push({ json: parsedJson, markdown: currentMd + line }) } catch (e) { console.error('JSON解析错误:', e) diff --git a/utils/config.js b/utils/config.js index f78cd3b..c103a7c 100644 --- a/utils/config.js +++ b/utils/config.js @@ -154,7 +154,6 @@ const defaultConfig = { autoJapanese: false, enableGenerateContents: false, enableGenerateSuno: false, - enableGenerateSunoForger: false, amapKey: '', azSerpKey: '', serpSource: 'ikechan8370', @@ -186,6 +185,8 @@ const defaultConfig = { chatglmRefreshToken: '', sunoSessToken: '', sunoClientToken: '', + enableChatSuno: false, + SunoModel: 'local', claudeApiKey: '', claudeApiBaseUrl: 'http://claude-api.ikechan8370.com', diff --git a/utils/xinghuo/xinghuo.js b/utils/xinghuo/xinghuo.js index ba6e5bb..910186c 100644 --- a/utils/xinghuo/xinghuo.js +++ b/utils/xinghuo/xinghuo.js @@ -397,6 +397,11 @@ export default class XinghuoClient { } else { Prompt = option.system ? [{ role: 'system', content: option.system }] : [] } + if (Config.enableChatSuno) { + Prompt.unshift( + { role: 'system', content: '如果我要求你生成音乐或写歌,你需要回复适合Suno生成音乐的信息。请使用Verse、Chorus、Bridge、Outro和End等关键字对歌词进行分段,如[Verse]。返回的消息需要使用markdown包裹的JSON格式,结构为```json{"option": "Suno", "tags": "style", "title": "title of the song", "lyrics": "lyrics"}```。' } + ) + } if (Config.xhPromptEval) { Prompt.forEach(obj => { try {