Merge pull request #693 from HalcyonAlcedo/v2

将suno生成应用于更多模型
This commit is contained in:
HalcyonAlcedo 2024-05-08 16:38:44 +08:00 committed by GitHub
commit f789556a90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 154 additions and 563 deletions

View file

@ -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'
@ -733,10 +735,6 @@ export class chatgpt extends plugin {
key = `CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
case 'bard': {
key = `CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
case 'azure': {
key = `CHATGPT:CONVERSATIONS_AZURE:${e.sender.user_id}`
break
@ -795,8 +793,8 @@ export class chatgpt extends plugin {
if (chatMessage?.noMsg) {
return false
}
// 处理星火和bard图片
if ((use === 'bard' || use === 'xh') && chatMessage?.images) {
// 处理星火图片
if (use === 'xh' && chatMessage?.images) {
chatMessage.images.forEach(element => {
this.reply([element.tag, segment.image(element.url)])
})
@ -824,11 +822,6 @@ export class chatgpt extends plugin {
}
previousConversation.messages.push(chatMessage.message)
}
if (use === 'bard' && !chatMessage.error) {
previousConversation.parentMessageId = chatMessage.responseID
previousConversation.clientId = chatMessage.choiceID
previousConversation.invocationId = chatMessage._reqID
}
if (Config.debug) {
logger.info(chatMessage)
}
@ -838,6 +831,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' || use === 'gemini') && 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) {

View file

@ -123,11 +123,6 @@ export class ChatgptManagement extends plugin {
fnc: 'useAzureBasedSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(Bard|bard)$',
fnc: 'useBardBasedSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(通义千问|qwen|千问)$',
fnc: 'useQwenSolution',
@ -968,16 +963,6 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useBardBasedSolution () {
let use = await redis.get('CHATGPT:USE')
if (use !== 'bard') {
await redis.set('CHATGPT:USE', 'bard')
await this.reply('已切换到基于Bard的解决方案')
} else {
await this.reply('当前已经是Bard模式了')
}
}
async patchGemini () {
const _path = process.cwd()
let packageJson = fs.readFileSync(`${_path}/package.json`)

View file

@ -531,28 +531,6 @@ export function supportGuoba () {
bottomHelpMessage: '替换回复内容中的文本',
component: 'Input'
},
{
label: '以下为Bard方式的配置',
component: 'Divider'
},
{
field: 'bardPsid',
label: 'BardCookie',
bottomHelpMessage: '获取https://bard.google.com/页面的cookie可完整输入需至少包含__Secure-1PSID和__Secure-1PSIDTS',
component: 'Input'
},
{
field: 'bardReverseProxy',
label: 'Bard反代地址',
bottomHelpMessage: 'bard反代服务器地址用于绕过地区限制',
component: 'Input'
},
{
field: 'bardForceUseReverse',
label: 'Bard使用反代',
bottomHelpMessage: '开启后将通过反代访问bard',
component: 'Switch'
},
{
label: '以下为通义千问API方式的配置',
component: 'Divider'

View file

@ -3,8 +3,8 @@ import { Config } from '../utils/config.js'
import { KeyvFile } from 'keyv-file'
import _ from 'lodash'
export const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德', '双子星', '双子座', '智谱']
export const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard', 'gemini', 'gemini', 'chatglm4']
export const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '双子星', '双子座', '智谱']
export const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'gemini', 'gemini', 'chatglm4']
export class ConversationManager {
async endConversation (e) {
@ -35,11 +35,6 @@ export class ConversationManager {
await this.reply('星火对话已结束')
return
}
if (use === 'bard') {
await redis.del(`CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
await this.reply('Bard对话已结束')
return
}
let ats = e.message.filter(m => m.type === 'at')
const isAtMode = Config.toggleMode === 'at'
if (isAtMode) ats = ats.filter(item => item.qq !== getUin(e))
@ -259,17 +254,6 @@ export class ConversationManager {
}
break
}
case 'bard': {
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BARD:*')
for (let i = 0; i < cs.length; i++) {
await redis.del(cs[i])
if (Config.debug) {
logger.info('delete bard conversation of qq: ' + cs[i])
}
deleted++
}
break
}
case 'bing': {
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BING:*')
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')

View file

@ -25,7 +25,6 @@ import XinghuoClient from '../utils/xinghuo/xinghuo.js'
import { getMessageById, upsertMessage } from '../utils/history.js'
import { v4 as uuid } from 'uuid'
import fetch from 'node-fetch'
import Bard from '../utils/bard.js'
import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
import { resizeAndCropImage } from '../utils/dalle.js'
import fs from 'fs'
@ -646,6 +645,11 @@ class Core {
}
promptAddition && (prompt += promptAddition)
option.systemMessage = await handleSystem(e, opts.systemMessage)
/*
if (Config.enableChatSuno) {
option.systemMessage += '如果我要求你生成音乐或写歌你需要回复适合Suno生成音乐的信息。请使用Verse、Chorus、Bridge、Outro和End等关键字对歌词进行分段如[Verse]。返回的消息需要使用markdown包裹的JSON格式结构为```json{"option": "Suno", "tags": "style", "title": "title of the song", "lyrics": "lyrics"}```。'
}
*/
systemAddition && (option.systemMessage += systemAddition)
opts.completionParams.parameters.tools = Object.keys(funcMap)
.map(k => funcMap[k].function)
@ -706,55 +710,6 @@ class Core {
}
return msg
}
} else if (use === 'bard') {
// 处理cookie
const matchesPSID = /__Secure-1PSID=([^;]+)/.exec(Config.bardPsid)
const matchesPSIDTS = /__Secure-1PSIDTS=([^;]+)/.exec(Config.bardPsid)
const cookie = {
'__Secure-1PSID': matchesPSID[1],
'__Secure-1PSIDTS': matchesPSIDTS[1]
}
if (!matchesPSID[1] || !matchesPSIDTS[1]) {
throw new Error('未绑定bard')
}
// 处理图片
const image = await getImg(e)
let imageBuff
if (image) {
try {
let imgResponse = await fetch(image[0])
if (imgResponse.ok) {
imageBuff = await imgResponse.arrayBuffer()
}
} catch (error) {
logger.warn(`错误的图片链接${image[0]}`)
}
}
// 发送数据
let bot = new Bard(cookie, {
fetch,
bardURL: Config.bardForceUseReverse ? Config.bardReverseProxy : 'https://bard.google.com'
})
let chat = await bot.createChat(conversation?.conversationId
? {
conversationID: conversation.conversationId,
responseID: conversation.parentMessageId,
choiceID: conversation.clientId,
_reqID: conversation.invocationId
}
: {})
let response = await chat.ask(prompt, {
image: imageBuff,
format: Bard.JSON
})
return {
conversationId: response.ids.conversationID,
responseID: response.ids.responseID,
choiceID: response.ids.choiceID,
_reqID: response.ids._reqID,
text: response.content,
images: response.images
}
} else if (use === 'gemini') {
let client = new CustomGoogleGeminiClient({
e,
@ -861,6 +816,9 @@ class Core {
.join('\n')
}
}
if (Config.enableChatSuno) {
system += '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 ```json{"option": "Suno", "tags": "style", "title": "title of the song", "lyrics": "lyrics"}```.'
}
option.system = system
return await client.sendMessage(prompt, option)
} else if (use === 'chatglm4') {
@ -884,6 +842,11 @@ class Core {
let maxModelTokens = getMaxModelTokens(completionParams.model)
// let system = promptPrefix
let system = await handleSystem(e, promptPrefix, maxModelTokens)
/*
if (Config.enableChatSuno) {
system += '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 ```json{"option": "Suno", "tags": "style", "title": "title of the song", "lyrics": "lyrics"}```.'
}
*/
logger.debug(system)
let opts = {
apiBaseUrl: Config.openAiBaseUrl,

View file

@ -70,29 +70,37 @@
"value": "xh"
},
{
"label": "Slack Claude",
"value": "claude"
"label": "通义千问",
"value": "qwen"
},
{
"label": "Gemini",
"value": "gemini"
},
{
"label": "Azure OpenAI",
"value": "azure"
"label": "Slack Claude",
"value": "claude"
},
{
"label": "Bard",
"value": "bard"
"label": "Claude2",
"value": "claude2"
},
{
"label": "ChatGPT API3",
"value": "api3"
"label": "ChatGLM4",
"value": "chatglm4"
},
{
"label": "ChatGLM",
"value": "chatglm"
},
{
"label": "Azure OpenAI",
"value": "azure"
},
{
"label": "ChatGPT API3",
"value": "api3"
},
{
"label": "浏览器",
"value": "browser"
@ -580,11 +588,6 @@
"label": "允许生成歌曲等内容",
"data": "enableGenerateSuno"
},
{
"type": "check",
"label": "伪造歌曲生成",
"data": "enableGenerateSunoForger"
},
{
"type": "url",
"label": "必应验证码pass服务",
@ -603,12 +606,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": "前置对话第一轮(用户)",
@ -842,27 +839,6 @@
}
]
},
{
"title": "Bard",
"tab": "bard",
"view": [
{
"type": "password",
"label": "BardCookie",
"data": "bardPsid"
},
{
"type": "url",
"label": "Bard反代地址",
"data": "bardReverseProxy"
},
{
"type": "check",
"label": "使用Bard反代",
"data": "bardForceUseReverse"
}
]
},
{
"title": "通义千问",
"tab": "qwen",
@ -1066,6 +1042,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"
}
]
},

View file

@ -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(',')

View file

@ -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. 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,16 +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.option == 'Suno') {
logger.info(`开始生成歌曲${suno.tags}`)
onSunoCreateRequest(suno)
}
}
}
resolve({
message,
conversationExpiryTime: event?.item?.conversationExpiryTime

View file

@ -1,373 +0,0 @@
// https://github.com/EvanZhouDev/bard-ai
class Bard {
static JSON = 'json'
static MD = 'markdown'
// ID derived from Cookie
SNlM0e
// HTTPS Headers
#headers
// Resolution status of initialization call
#initPromise
#bardURL = 'https://bard.google.com'
// Wether or not to log events to console
#verbose = false
// Fetch function
#fetch = fetch
constructor (cookie, config) {
// Register some settings
if (config?.verbose == true) this.#verbose = true
if (config?.fetch) this.#fetch = config.fetch
// 可变更访问地址,利用反向代理绕过区域限制
if (config?.bardURL) this.#bardURL = config.bardURL
// If a Cookie is provided, initialize
if (cookie) {
this.#initPromise = this.#init(cookie)
} else {
throw new Error('Please provide a Cookie when initializing Bard.')
}
this.cookie = cookie
}
// You can also choose to initialize manually
async #init (cookie) {
this.#verbose && console.log('🚀 Starting intialization')
// Assign headers
this.#headers = {
Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1],
'X-Same-Domain': '1',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
Origin: this.#bardURL,
Referer: this.#bardURL,
Cookie: (typeof cookie === 'object') ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join('')) : ('__Secure-1PSID=' + cookie)
}
let responseText
// Attempt to retrieve SNlM0e
try {
this.#verbose &&
console.log('🔒 Authenticating your Google account')
responseText = await this.#fetch(this.#bardURL, {
method: 'GET',
headers: this.#headers,
credentials: 'include'
})
.then((response) => response.text())
} catch (e) {
// Failure to get server
throw new Error(
'Could not fetch Google Bard. You may be disconnected from internet: ' +
e
)
}
try {
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1]
// Assign SNlM0e and return it
this.SNlM0e = SNlM0e
this.#verbose && console.log('✅ Initialization finished\n')
return SNlM0e
} catch {
throw new Error(
'Could not use your Cookie. Make sure that you copied correctly the Cookie with name __Secure-1PSID exactly. If you are sure your cookie is correct, you may also have reached your rate limit.'
)
}
}
async #uploadImage (name, buffer) {
this.#verbose && console.log('🖼️ Starting image processing')
let size = buffer.byteLength
let formBody = [
`${encodeURIComponent('File name')}=${encodeURIComponent([name])}`
]
try {
this.#verbose &&
console.log('💻 Finding Google server destination')
let response = await this.#fetch(
'https://content-push.googleapis.com/upload/',
{
method: 'POST',
headers: {
'X-Goog-Upload-Command': 'start',
'X-Goog-Upload-Protocol': 'resumable',
'X-Goog-Upload-Header-Content-Length': size,
'X-Tenant-Id': 'bard-storage',
'Push-Id': 'feeds/mcudyrk2a4khkz'
},
body: formBody,
credentials: 'include'
}
)
const uploadUrl = response.headers.get('X-Goog-Upload-URL')
this.#verbose && console.log('📤 Sending your image')
response = await this.#fetch(uploadUrl, {
method: 'POST',
headers: {
'X-Goog-Upload-Command': 'upload, finalize',
'X-Goog-Upload-Offset': 0,
'X-Tenant-Id': 'bard-storage'
},
body: buffer,
credentials: 'include'
})
const imageFileLocation = await response.text()
this.#verbose && console.log('✅ Image finished working\n')
return imageFileLocation
} catch (e) {
throw new Error(
'Could not fetch Google Bard. You may be disconnected from internet: ' +
e
)
}
}
// Query Bard
async #query (message, config) {
let formatMarkdown = (text, images) => {
if (!images) return text
for (let imageData of images) {
const formattedTag = `!${imageData.tag}(${imageData.url})`
text = text.replace(
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
formattedTag
)
}
return text
}
let { ids, imageBuffer } = config
// Wait until after init
await this.#initPromise
this.#verbose && console.log('🔎 Starting Bard Query')
// If user has not run init
if (!this.SNlM0e) {
throw new Error(
"Please initialize Bard first. If you haven't passed in your Cookie into the class, run Bard.init(cookie)."
)
}
this.#verbose && console.log('🏗️ Building Request')
// HTTPS parameters
const params = {
bl: 'boq_assistant-bard-web-server_20230711.08_p0',
_reqID: ids?._reqID ?? '0',
rt: 'c'
}
// If IDs are provided, but doesn't have every one of the expected IDs, error
const messageStruct = [
[message],
null,
[null, null, null]
]
if (imageBuffer) {
let imageLocation = await this.#uploadImage(
'bard-ai_upload',
imageBuffer
)
messageStruct[0].push(0, null, [
[[imageLocation, 1], 'bard-ai_upload']
])
}
if (ids) {
const { conversationID, responseID, choiceID } = ids
messageStruct[2] = [conversationID, responseID, choiceID]
}
// HTTPs data
const data = {
'f.req': JSON.stringify([null, JSON.stringify(messageStruct)]),
at: this.SNlM0e
}
// URL that we are submitting to
const url = new URL(
'/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate',
this.#bardURL
)
// Append parameters to the URL
for (const key in params) {
url.searchParams.append(key, params[key])
}
// Encode the data
const formBody = Object.entries(data)
.map(
([property, value]) =>
`${encodeURIComponent(property)}=${encodeURIComponent(
value
)}`
)
.join('&')
this.#verbose && console.log('💭 Sending message to Bard')
// Send the fetch request
const chatData = await this.#fetch(url.toString(), {
method: 'POST',
headers: this.#headers,
body: formBody,
credentials: 'include'
})
.then((response) => {
return response.text()
})
.then((text) => {
return JSON.parse(text.split('\n')[3])[0][2]
})
.then((rawData) => JSON.parse(rawData))
this.#verbose && console.log('🧩 Parsing output')
// Get first Bard-recommended answer
const answer = chatData[4][0]
// Text of that answer
const text = answer[1][0]
// Get data about images in that answer
const images =
answer[4]?.map((x) => ({
tag: x[2],
url: x[3][0][0],
info: {
raw: x[0][0][0],
source: x[1][0][0],
alt: x[0][4],
website: x[1][1],
favicon: x[1][3]
}
})) ?? []
this.#verbose && console.log('✅ All done!\n')
// Put everything together and return
return {
content: formatMarkdown(text, images),
images,
ids: {
conversationID: chatData[1][0],
responseID: chatData[1][1],
choiceID: answer[0],
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000)
}
}
}
async #parseConfig (config) {
let result = {
useJSON: false,
imageBuffer: undefined, // Returns as {extension, filename}
ids: undefined
}
// Verify that format is one of the two types
if (config?.format) {
switch (config.format) {
case Bard.JSON:
result.useJSON = true
break
case Bard.MD:
result.useJSON = false
break
default:
throw new Error(
'Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output.'
)
}
}
// Verify that the image passed in is either a path to a jpeg, jpg, png, or webp, or that it is a Buffer
if (config?.image) {
if (
config.image instanceof ArrayBuffer
) {
result.imageBuffer = config.image
} else if (
typeof config.image === 'string' &&
/\.(jpeg|jpg|png|webp)$/.test(config.image)
) {
let fs
try {
fs = await import('fs')
} catch {
throw new Error(
'Loading from an image file path is not supported in a browser environment.'
)
}
result.imageBuffer = fs.readFileSync(
config.image
).buffer
} else {
throw new Error(
'Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer.'
)
}
}
// Verify that all values in IDs exist
if (config?.ids) {
if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) {
result.ids = config.ids
} else {
throw new Error(
'Please provide the IDs exported exactly as given.'
)
}
}
return result
}
// Ask Bard a question!
async ask (message, config) {
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config)
let response = await this.#query(message, { imageBuffer, ids })
return useJSON ? response : response.content
}
createChat (ids) {
let bard = this
class Chat {
ids = ids
async ask (message, config) {
let { useJSON, imageBuffer } = await bard.#parseConfig(config)
let response = await bard.#query(message, {
imageBuffer,
ids: this.ids
})
this.ids = response.ids
return useJSON ? response : response.content
}
export () {
return this.ids
}
}
return new Chat()
}
}
export default Bard

View file

@ -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
@ -1250,28 +1278,50 @@ function maskString (str) {
*/
export function extractMarkdownJson(text) {
const lines = text.split('\n')
const mdJson = []
let currentObj = null
const mdJsonPairs = []
let currentJson = ''
let currentMd = ''
lines.forEach(line => {
if (line.startsWith('```json') && !currentObj) {
// 开始一个新的JSON对象
currentObj = { json: '' }
} else if (line.startsWith('```') && currentObj) {
// 结束当前的JSON对象
if (line.startsWith('```json')) {
// 如果已经在一个 JSON 中,先结束当前的 JSON
if (currentJson) {
try {
const parsedJson = JSON.parse(fixNewlinesInJsonString(currentJson))
mdJsonPairs.push({ json: parsedJson, markdown: currentMd + '```' })
} catch (e) {
console.error('JSON解析错误:', e)
}
}
// 开始新的 JSON 和 markdown
currentJson = ''
currentMd = line + '\n'
} else if (line.startsWith('```') && currentJson) {
// 结束当前的 JSON
try {
// 尝试将JSON字符串转换为对象
currentObj.json = JSON.parse(currentObj.json)
mdJson.push(currentObj)
currentObj = null
const parsedJson = JSON.parse(fixNewlinesInJsonString(currentJson))
mdJsonPairs.push({ json: parsedJson, markdown: currentMd + line })
} catch (e) {
console.error('JSON解析错误:', e)
}
} else if (currentObj) {
// 将行添加到当前的JSON对象
currentObj.json += line
currentJson = ''
currentMd = ''
} else {
// 如果在 JSON 中,继续添加行
currentJson += line + (line ? '\n' : '')
currentMd += line + '\n'
}
})
return mdJson.map(obj => obj.json)
// 检查是否有未结束的 JSON
if (currentJson) {
try {
const parsedJson = JSON.parse(currentJson)
mdJsonPairs.push({ json: parsedJson, markdown: currentMd + '```' })
} catch (e) {
console.error('JSON解析错误:', e)
}
}
return mdJsonPairs
}

View file

@ -136,9 +136,6 @@ const defaultConfig = {
// slackCozeEnableGlobalPreset: true,
// slackCozeGlobalPreset: '',
// slackCozeSpecifiedChannel: '',
bardPsid: '',
bardReverseProxy: '',
bardForceUseReverse: false,
cloudTranscode: 'https://silk.201666.xyz',
cloudRender: false,
cloudMode: 'url',
@ -154,7 +151,6 @@ const defaultConfig = {
autoJapanese: false,
enableGenerateContents: false,
enableGenerateSuno: false,
enableGenerateSunoForger: false,
amapKey: '',
azSerpKey: '',
serpSource: 'ikechan8370',
@ -186,6 +182,8 @@ const defaultConfig = {
chatglmRefreshToken: '',
sunoSessToken: '',
sunoClientToken: '',
enableChatSuno: false,
SunoModel: 'local',
claudeApiKey: '',
claudeApiBaseUrl: 'http://claude-api.ikechan8370.com',

View file

@ -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 {