From 123e5304a7df2f4316488ed1986688cb25c74f3e Mon Sep 17 00:00:00 2001 From: zyc404 Date: Wed, 8 May 2024 13:26:42 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=AF=B9=E5=8E=9F=E5=A7=8B=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=BF=9B=E8=A1=8C=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/SydneyAIClient.js | 9 +++---- utils/common.js | 52 +++++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index dcc585d..0ac15e0 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. 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.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"}.' : '') if (!text) { previousMessages = pm } else { @@ -839,9 +839,10 @@ export default class SydneyAIClient { if (Config.enableGenerateSunoForger) { const sunoList = extractMarkdownJson(message.text) for (let suno of sunoList) { - if (suno.option == 'Suno') { - logger.info(`开始生成歌曲${suno.tags}`) - onSunoCreateRequest(suno) + if (suno.json.option == 'Suno') { + message.text = message.text.replace(suno.markdown, `歌曲 《${suno.json.title}》`) + logger.info(`开始生成歌曲${suno.json.tags}`) + onSunoCreateRequest(suno.json) } } } diff --git a/utils/common.js b/utils/common.js index f73d74f..fe67a8c 100644 --- a/utils/common.js +++ b/utils/common.js @@ -1250,28 +1250,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(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(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 } \ No newline at end of file From d111d2625e954f9752712239405e7042abfb9816 Mon Sep 17 00:00:00 2001 From: zyc404 Date: Wed, 8 May 2024 15:32:49 +0800 Subject: [PATCH 2/4] =?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 { From 8efcce45a04bda2bb8ec66e44026a635d97472bf Mon Sep 17 00:00:00 2001 From: zyc404 Date: Wed, 8 May 2024 15:42:35 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=8E=BB=E9=99=A4bard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/chat.js | 13 +- apps/management.js | 15 -- guoba.support.js | 22 -- model/conversation.js | 20 +- model/core.js | 50 ----- resources/view/setting_view.json | 25 --- utils/bard.js | 373 ------------------------------- utils/config.js | 3 - 8 files changed, 4 insertions(+), 517 deletions(-) delete mode 100644 utils/bard.js diff --git a/apps/chat.js b/apps/chat.js index bc1058e..3cbe2e9 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -735,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 @@ -797,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)]) }) @@ -826,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) } diff --git a/apps/management.js b/apps/management.js index 6bd69e2..33f40bd 100644 --- a/apps/management.js +++ b/apps/management.js @@ -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`) diff --git a/guoba.support.js b/guoba.support.js index 293d7ce..82ab825 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -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' diff --git a/model/conversation.js b/model/conversation.js index ced1084..bbe46b6 100644 --- a/model/conversation.js +++ b/model/conversation.js @@ -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:*') diff --git a/model/core.js b/model/core.js index 031e4d8..1157ced 100644 --- a/model/core.js +++ b/model/core.js @@ -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' @@ -706,55 +705,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, diff --git a/resources/view/setting_view.json b/resources/view/setting_view.json index a0c09ee..6a3a039 100644 --- a/resources/view/setting_view.json +++ b/resources/view/setting_view.json @@ -81,10 +81,6 @@ "label": "Azure OpenAI", "value": "azure" }, - { - "label": "Bard", - "value": "bard" - }, { "label": "ChatGPT API3", "value": "api3" @@ -819,27 +815,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", diff --git a/utils/bard.js b/utils/bard.js deleted file mode 100644 index a28e060..0000000 --- a/utils/bard.js +++ /dev/null @@ -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 diff --git a/utils/config.js b/utils/config.js index c103a7c..a265704 100644 --- a/utils/config.js +++ b/utils/config.js @@ -136,9 +136,6 @@ const defaultConfig = { // slackCozeEnableGlobalPreset: true, // slackCozeGlobalPreset: '', // slackCozeSpecifiedChannel: '', - bardPsid: '', - bardReverseProxy: '', - bardForceUseReverse: false, cloudTranscode: 'https://silk.201666.xyz', cloudRender: false, cloudMode: 'url', From 554f6a69f302106bc8e099c9bf574cfd2cd3ce48 Mon Sep 17 00:00:00 2001 From: zyc404 Date: Wed, 8 May 2024 16:35:51 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A8=A1=E5=BC=8F=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/chat.js | 2 +- model/core.js | 13 +++++++++++++ resources/view/setting_view.json | 24 ++++++++++++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/apps/chat.js b/apps/chat.js index 3cbe2e9..56236bf 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -832,7 +832,7 @@ export class chatgpt extends plugin { } } // 处理suno生成 - if ((use === 'bing' || use === 'xh') && Config.enableChatSuno) { + if ((use === 'bing' || use === 'xh' || use === 'gemini') && Config.enableChatSuno) { const sunoList = extractMarkdownJson(chatMessage.text) for (let suno of sunoList) { if (suno.json.option == 'Suno') { diff --git a/model/core.js b/model/core.js index 1157ced..74f153f 100644 --- a/model/core.js +++ b/model/core.js @@ -645,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) @@ -811,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') { @@ -834,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, diff --git a/resources/view/setting_view.json b/resources/view/setting_view.json index 6a3a039..78146ab 100644 --- a/resources/view/setting_view.json +++ b/resources/view/setting_view.json @@ -70,13 +70,29 @@ "value": "xh" }, { - "label": "Slack Claude", - "value": "claude" + "label": "通义千问", + "value": "qwen" }, { "label": "Gemini", "value": "gemini" }, + { + "label": "Slack Claude", + "value": "claude" + }, + { + "label": "Claude2", + "value": "claude2" + }, + { + "label": "ChatGLM4", + "value": "chatglm4" + }, + { + "label": "ChatGLM", + "value": "chatglm" + }, { "label": "Azure OpenAI", "value": "azure" @@ -85,10 +101,6 @@ "label": "ChatGPT API3", "value": "api3" }, - { - "label": "ChatGLM", - "value": "chatglm" - }, { "label": "浏览器", "value": "browser"