mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
commit
f789556a90
12 changed files with 154 additions and 563 deletions
42
apps/chat.js
42
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'
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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:*')
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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(',')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
373
utils/bard.js
373
utils/bard.js
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue