mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 13:57:10 +00:00
Merge branch 'ikechan8370:v2' into v2
This commit is contained in:
commit
d0b5585539
13 changed files with 1265 additions and 98 deletions
250
apps/chat.js
250
apps/chat.js
|
|
@ -39,6 +39,7 @@ import { SlackClaudeClient } from '../utils/slack/slackClient.js'
|
||||||
import { getPromptByName } from '../utils/prompts.js'
|
import { getPromptByName } from '../utils/prompts.js'
|
||||||
import BingDrawClient from '../utils/BingDraw.js'
|
import BingDrawClient from '../utils/BingDraw.js'
|
||||||
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||||
|
import Bard from '../utils/bard.js'
|
||||||
import { JinyanTool } from '../utils/tools/JinyanTool.js'
|
import { JinyanTool } from '../utils/tools/JinyanTool.js'
|
||||||
import { SendVideoTool } from '../utils/tools/SendBilibiliTool.js'
|
import { SendVideoTool } from '../utils/tools/SendBilibiliTool.js'
|
||||||
import { KickOutTool } from '../utils/tools/KickOutTool.js'
|
import { KickOutTool } from '../utils/tools/KickOutTool.js'
|
||||||
|
|
@ -68,6 +69,12 @@ import { SendMessageToSpecificGroupOrUserTool } from '../utils/tools/SendMessage
|
||||||
import { SetTitleTool } from '../utils/tools/SetTitleTool.js'
|
import { SetTitleTool } from '../utils/tools/SetTitleTool.js'
|
||||||
import { createCaptcha, solveCaptcha, solveCaptchaOneShot } from '../utils/bingCaptcha.js'
|
import { createCaptcha, solveCaptcha, solveCaptchaOneShot } from '../utils/bingCaptcha.js'
|
||||||
|
|
||||||
|
try {
|
||||||
|
await import('@azure/openai')
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('【Azure-Openai】依赖@azure/openai未安装,Azure OpenAI不可用 请执行pnpm install @azure/openai安装')
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await import('emoji-strip')
|
await import('emoji-strip')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -161,6 +168,14 @@ export class chatgpt extends plugin {
|
||||||
/** 执行方法 */
|
/** 执行方法 */
|
||||||
fnc: 'xh'
|
fnc: 'xh'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#星火助手',
|
||||||
|
fnc: 'newxhBotConversation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#星火(搜索|查找)助手',
|
||||||
|
fnc: 'searchxhBot'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
/** 命令正则匹配 */
|
/** 命令正则匹配 */
|
||||||
reg: toggleMode === 'at' ? '^[^#][sS]*' : '^#chat[^gpt][sS]*',
|
reg: toggleMode === 'at' ? '^[^#][sS]*' : '^#chat[^gpt][sS]*',
|
||||||
|
|
@ -302,36 +317,41 @@ export class chatgpt extends plugin {
|
||||||
async destroyConversations(e) {
|
async destroyConversations(e) {
|
||||||
const userData = await getUserData(e.user_id)
|
const userData = await getUserData(e.user_id)
|
||||||
const use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
const use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
||||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
await redis.del(`CHATGPT:WRONG_EMOTION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
if (use === 'claude') {
|
if (use === 'claude') {
|
||||||
// let client = new SlackClaudeClient({
|
// let client = new SlackClaudeClient({
|
||||||
// slackUserToken: Config.slackUserToken,
|
// slackUserToken: Config.slackUserToken,
|
||||||
// slackChannelId: Config.slackChannelId
|
// slackChannelId: Config.slackChannelId
|
||||||
// })
|
// })
|
||||||
// await client.endConversation()
|
// await client.endConversation()
|
||||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
await redis.del(`CHATGPT:SLACK_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
await e.reply('claude对话已结束')
|
await e.reply('claude对话已结束')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (use === 'xh') {
|
if (use === 'xh') {
|
||||||
await redis.del(`CHATGPT:CONVERSATIONS_XH:${e.sender.user_id}`)
|
await redis.del(`CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
await e.reply('星火对话已结束')
|
await e.reply('星火对话已结束')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (use === 'bard') {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
|
await e.reply('Bard对话已结束')
|
||||||
|
return
|
||||||
|
}
|
||||||
let ats = e.message.filter(m => m.type === 'at')
|
let ats = e.message.filter(m => m.type === 'at')
|
||||||
const isAtMode = Config.toggleMode === 'at'
|
const isAtMode = Config.toggleMode === 'at'
|
||||||
if (isAtMode) ats = ats.filter(item => item.qq !== Bot.uin)
|
if (isAtMode) ats = ats.filter(item => item.qq !== Bot.uin)
|
||||||
if (ats.length === 0) {
|
if (ats.length === 0) {
|
||||||
if (use === 'api3') {
|
if (use === 'api3') {
|
||||||
await redis.del(`CHATGPT:QQ_CONVERSATION:${e.sender.user_id}`)
|
await redis.del(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||||
} else if (use === 'bing' && (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom')) {
|
} else if (use === 'bing' && (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom')) {
|
||||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
if (!c) {
|
if (!c) {
|
||||||
await this.reply('当前没有开启对话', true)
|
await this.reply('当前没有开启对话', true)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
await redis.del(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
}
|
}
|
||||||
const conversation = {
|
const conversation = {
|
||||||
store: new KeyvFile({ filename: 'cache.json' }),
|
store: new KeyvFile({ filename: 'cache.json' }),
|
||||||
|
|
@ -475,7 +495,18 @@ export class chatgpt extends plugin {
|
||||||
for (let i = 0; i < cs.length; i++) {
|
for (let i = 0; i < cs.length; i++) {
|
||||||
await redis.del(cs[i])
|
await redis.del(cs[i])
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
logger.info('delete slack conversation of qq: ' + cs[i])
|
logger.info('delete xh conversation of qq: ' + cs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
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++
|
deleted++
|
||||||
}
|
}
|
||||||
|
|
@ -1005,6 +1036,14 @@ export class chatgpt extends plugin {
|
||||||
key = `CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
key = `CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||||
break
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let ctime = new Date()
|
let ctime = new Date()
|
||||||
previousConversation = (key ? await redis.get(key) : null) || JSON.stringify({
|
previousConversation = (key ? await redis.get(key) : null) || JSON.stringify({
|
||||||
|
|
@ -1012,6 +1051,7 @@ export class chatgpt extends plugin {
|
||||||
ctime,
|
ctime,
|
||||||
utime: ctime,
|
utime: ctime,
|
||||||
num: 0,
|
num: 0,
|
||||||
|
messages: [{ role: 'system', content: 'You are an AI assistant that helps people find information.' }],
|
||||||
conversation: {}
|
conversation: {}
|
||||||
})
|
})
|
||||||
previousConversation = JSON.parse(previousConversation)
|
previousConversation = JSON.parse(previousConversation)
|
||||||
|
|
@ -1019,6 +1059,7 @@ export class chatgpt extends plugin {
|
||||||
logger.info({ previousConversation })
|
logger.info({ previousConversation })
|
||||||
}
|
}
|
||||||
conversation = {
|
conversation = {
|
||||||
|
messages: previousConversation.messages,
|
||||||
conversationId: previousConversation.conversation?.conversationId,
|
conversationId: previousConversation.conversation?.conversationId,
|
||||||
parentMessageId: previousConversation.parentMessageId,
|
parentMessageId: previousConversation.parentMessageId,
|
||||||
clientId: previousConversation.clientId,
|
clientId: previousConversation.clientId,
|
||||||
|
|
@ -1038,6 +1079,12 @@ export class chatgpt extends plugin {
|
||||||
await e.reply([chatMessage.text, segment.image(`base64://${chatMessage.image}`)])
|
await e.reply([chatMessage.text, segment.image(`base64://${chatMessage.image}`)])
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// 处理星火和bard图片
|
||||||
|
if ((use === 'bard' || use === 'xh') && chatMessage?.images) {
|
||||||
|
chatMessage.images.forEach(async element => {
|
||||||
|
await e.reply([element.tag, segment.image(element.url)])
|
||||||
|
})
|
||||||
|
}
|
||||||
if (use === 'api' && !chatMessage) {
|
if (use === 'api' && !chatMessage) {
|
||||||
// 字数超限直接返回
|
// 字数超限直接返回
|
||||||
return false
|
return false
|
||||||
|
|
@ -1058,6 +1105,16 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
} else if (chatMessage.id) {
|
} else if (chatMessage.id) {
|
||||||
previousConversation.parentMessageId = chatMessage.id
|
previousConversation.parentMessageId = chatMessage.id
|
||||||
|
} else if (chatMessage.message) {
|
||||||
|
if (previousConversation.messages.length > 10) {
|
||||||
|
previousConversation.messages.shift()
|
||||||
|
}
|
||||||
|
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) {
|
if (Config.debug) {
|
||||||
logger.info(chatMessage)
|
logger.info(chatMessage)
|
||||||
|
|
@ -1164,7 +1221,7 @@ export class chatgpt extends plugin {
|
||||||
let quotemessage = []
|
let quotemessage = []
|
||||||
if (chatMessage?.quote) {
|
if (chatMessage?.quote) {
|
||||||
chatMessage.quote.forEach(function (item, index) {
|
chatMessage.quote.forEach(function (item, index) {
|
||||||
if (item.text.trim() !== '') {
|
if (item.text && item.text.trim() !== '') {
|
||||||
quotemessage.push(item)
|
quotemessage.push(item)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -1634,7 +1691,6 @@ export class chatgpt extends plugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(response)
|
|
||||||
// 处理内容生成的图片
|
// 处理内容生成的图片
|
||||||
if (response.details.imageTag) {
|
if (response.details.imageTag) {
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
|
|
@ -1832,16 +1888,89 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'xh': {
|
case 'xh': {
|
||||||
|
const cacheOptions = {
|
||||||
|
namespace: 'xh',
|
||||||
|
store: new KeyvFile({ filename: 'cache.json' })
|
||||||
|
}
|
||||||
const ssoSessionId = Config.xinghuoToken
|
const ssoSessionId = Config.xinghuoToken
|
||||||
if (!ssoSessionId) {
|
if (!ssoSessionId) {
|
||||||
throw new Error('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)')
|
throw new Error('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)')
|
||||||
}
|
}
|
||||||
let client = new XinghuoClient({
|
let client = new XinghuoClient({
|
||||||
ssoSessionId
|
ssoSessionId,
|
||||||
|
cache: cacheOptions
|
||||||
})
|
})
|
||||||
let response = await client.sendMessage(prompt, conversation?.conversationId)
|
// 获取图片资源
|
||||||
|
const image = await getImg(e)
|
||||||
|
let response = await client.sendMessage(prompt, conversation?.conversationId, image ? image[0] : undefined)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
case 'azure': {
|
||||||
|
let azureModel
|
||||||
|
try {
|
||||||
|
azureModel = await import('@azure/openai')
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('未安装@azure/openai包,请执行pnpm install @azure/openai安装')
|
||||||
|
}
|
||||||
|
let OpenAIClient = azureModel.OpenAIClient
|
||||||
|
let AzureKeyCredential = azureModel.AzureKeyCredential
|
||||||
|
let msg = conversation.messages
|
||||||
|
let content = { role: 'user', content: prompt }
|
||||||
|
msg.push(content)
|
||||||
|
const client = new OpenAIClient(Config.azureUrl, new AzureKeyCredential(Config.apiKey))
|
||||||
|
const deploymentName = Config.azureDeploymentName
|
||||||
|
const { choices } = await client.getChatCompletions(deploymentName, msg)
|
||||||
|
let completion = choices[0].message;
|
||||||
|
return {'text' : completion.content, 'message': completion}
|
||||||
|
}
|
||||||
|
case '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: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
let completionParams = {}
|
let completionParams = {}
|
||||||
if (Config.model) {
|
if (Config.model) {
|
||||||
|
|
@ -2193,6 +2322,105 @@ export class chatgpt extends plugin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async newxhBotConversation(e) {
|
||||||
|
let botId = e.msg.replace(/^#星火助手/, '').trim()
|
||||||
|
if (Config.xhmode != 'web') {
|
||||||
|
await e.reply('星火助手仅支持体验版使用', true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!botId) {
|
||||||
|
await e.reply('无效助手id', true)
|
||||||
|
} else {
|
||||||
|
const ssoSessionId = Config.xinghuoToken
|
||||||
|
if (!ssoSessionId) {
|
||||||
|
await e.reply(`未绑定星火token,请使用#chatgpt设置星火token命令绑定token`, true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let client = new XinghuoClient({
|
||||||
|
ssoSessionId,
|
||||||
|
cache: null
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let chatId = await client.createChatList(botId)
|
||||||
|
let botInfoRes = await fetch(`https://xinghuo.xfyun.cn/iflygpt/bot/getBotInfo?chatId=${chatId.chatListId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: 'ssoSessionId=' + ssoSessionId + ';',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/113.0.5672.69 Mobile/15E148 Safari/604.1',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (botInfoRes.ok) {
|
||||||
|
let botInfo = await botInfoRes.json()
|
||||||
|
if (botInfo.flag) {
|
||||||
|
let ctime = new Date()
|
||||||
|
await redis.set(
|
||||||
|
`CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`,
|
||||||
|
JSON.stringify({
|
||||||
|
sender: e.sender,
|
||||||
|
ctime,
|
||||||
|
utime: ctime,
|
||||||
|
num: 0,
|
||||||
|
conversation: {
|
||||||
|
conversationId: {
|
||||||
|
chatid: chatId.chatListId,
|
||||||
|
botid: botId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Config.conversationPreserveTime > 0 ? { EX: Config.conversationPreserveTime } : {}
|
||||||
|
)
|
||||||
|
await e.reply(`成功创建助手对话\n助手名称:${botInfo.data.bot_name}\n助手描述:${botInfo.data.bot_desc}`, true)
|
||||||
|
} else {
|
||||||
|
await e.reply(`创建助手对话失败,${botInfo.desc}`, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await e.reply(`创建助手对话失败,服务器异常`, true)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await e.reply(`创建助手对话失败 ${error}`, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchxhBot(e) {
|
||||||
|
let searchBot = e.msg.replace(/^#星火(搜索|查找)助手/, '').trim()
|
||||||
|
const ssoSessionId = Config.xinghuoToken
|
||||||
|
if (!ssoSessionId) {
|
||||||
|
await e.reply(`未绑定星火token,请使用#chatgpt设置星火token命令绑定token`, true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const cacheresOption = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: 'ssoSessionId=' + ssoSessionId + ';',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/113.0.5672.69 Mobile/15E148 Safari/604.1',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
botType: '',
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 45,
|
||||||
|
searchValue: searchBot
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const searchBots = await fetch('https://xinghuo.xfyun.cn/iflygpt/bot/page', cacheresOption)
|
||||||
|
const bots = await searchBots.json()
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info(bots)
|
||||||
|
}
|
||||||
|
if (bots.code === 0) {
|
||||||
|
if (bots.data.pageList.length > 0) {
|
||||||
|
this.reply(await makeForwardMsg(this.e, bots.data.pageList.map(msg => `${msg.bot.botId} - ${msg.bot.botName}`)))
|
||||||
|
} else {
|
||||||
|
await e.reply(`未查到相关助手`, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await e.reply(`搜索助手失败`, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async emptyQueue(e) {
|
async emptyQueue(e) {
|
||||||
await redis.lTrim('CHATGPT:CHAT_QUEUE', 1, 0)
|
await redis.lTrim('CHATGPT:CHAT_QUEUE', 1, 0)
|
||||||
await this.reply('已清空当前等待队列')
|
await this.reply('已清空当前等待队列')
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,16 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'useXinghuoBasedSolution',
|
fnc: 'useXinghuoBasedSolution',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt切换azure$',
|
||||||
|
fnc: 'useAzureBasedSolution',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt切换(Bard|bard)$',
|
||||||
|
fnc: 'useBardBasedSolution',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(必应|Bing)切换',
|
reg: '^#chatgpt(必应|Bing)切换',
|
||||||
fnc: 'changeBingTone',
|
fnc: 'changeBingTone',
|
||||||
|
|
@ -845,6 +855,25 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
await this.reply('当前已经是星火模式了')
|
await this.reply('当前已经是星火模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async useAzureBasedSolution () {
|
||||||
|
let use = await redis.get('CHATGPT:USE')
|
||||||
|
if (use !== 'azure') {
|
||||||
|
await redis.set('CHATGPT:USE', 'azure')
|
||||||
|
await this.reply('已切换到基于Azure的解决方案')
|
||||||
|
} else {
|
||||||
|
await this.reply('当前已经是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 changeBingTone (e) {
|
async changeBingTone (e) {
|
||||||
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
|
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
|
||||||
|
|
@ -891,6 +920,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
let mode = await redis.get('CHATGPT:USE')
|
let mode = await redis.get('CHATGPT:USE')
|
||||||
const modeMap = {
|
const modeMap = {
|
||||||
browser: '浏览器',
|
browser: '浏览器',
|
||||||
|
azure: 'Azure',
|
||||||
// apiReverse: 'API2',
|
// apiReverse: 'API2',
|
||||||
api: 'API',
|
api: 'API',
|
||||||
bing: '必应',
|
bing: '必应',
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
"openAiBaseUrl": "https://mondstadt.d201.eu.org/v1",
|
"openAiBaseUrl": "https://mondstadt.d201.eu.org/v1",
|
||||||
"OpenAiPlatformRefreshToken": "",
|
"OpenAiPlatformRefreshToken": "",
|
||||||
"openAiForceUseReverse": false,
|
"openAiForceUseReverse": false,
|
||||||
|
"azureDeploymentName":"",
|
||||||
|
"azureUrl":"",
|
||||||
"drawCD": 30,
|
"drawCD": 30,
|
||||||
"model": "",
|
"model": "",
|
||||||
"temperature": 0.8,
|
"temperature": 0.8,
|
||||||
|
|
|
||||||
118
guoba.support.js
118
guoba.support.js
|
|
@ -617,12 +617,108 @@ export function supportGuoba () {
|
||||||
label: '以下为星火方式的配置',
|
label: '以下为星火方式的配置',
|
||||||
component: 'Divider'
|
component: 'Divider'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'xhmode',
|
||||||
|
label: '星火模式',
|
||||||
|
bottomHelpMessage: '设置星火使用的对话模式',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '体验版', value: 'web' },
|
||||||
|
{ label: '讯飞星火认知大模型V1.5', value: 'api' },
|
||||||
|
{ label: '讯飞星火认知大模型V2.0', value: 'apiv2' },
|
||||||
|
{ label: '讯飞星火助手', value: 'assistants' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'xinghuoToken',
|
field: 'xinghuoToken',
|
||||||
label: '星火Cookie',
|
label: '星火Cookie',
|
||||||
bottomHelpMessage: '获取对话页面的ssoSessionId cookie。不要带等号和分号',
|
bottomHelpMessage: '获取对话页面的ssoSessionId cookie。不要带等号和分号',
|
||||||
|
component: 'InputPassword'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhAppId',
|
||||||
|
label: 'AppId',
|
||||||
|
bottomHelpMessage: '应用页面获取',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'xhAPISecret',
|
||||||
|
label: 'APISecret',
|
||||||
|
bottomHelpMessage: '应用页面获取',
|
||||||
|
component: 'InputPassword'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhAPIKey',
|
||||||
|
label: '星火APIKey',
|
||||||
|
bottomHelpMessage: '应用页面获取',
|
||||||
|
component: 'InputPassword'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhAssistants',
|
||||||
|
label: '助手接口',
|
||||||
|
bottomHelpMessage: '助手页面获取',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhTemperature',
|
||||||
|
label: '核采样阈值',
|
||||||
|
bottomHelpMessage: '核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高',
|
||||||
|
component: 'InputNumber'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhMaxTokens',
|
||||||
|
label: '最大Token',
|
||||||
|
bottomHelpMessage: '模型回答的tokens的最大长度',
|
||||||
|
component: 'InputNumber'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhPromptSerialize',
|
||||||
|
label: '序列化设定',
|
||||||
|
bottomHelpMessage: '是否将设定内容进行json序列化',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhPrompt',
|
||||||
|
label: '设定',
|
||||||
|
bottomHelpMessage: '若开启序列化,请传入json数据,例如[{ \"role\": \"user\", \"content\": \"现在是10点\" },{ \"role\": \"assistant\", \"content\": \"了解,现在10点了\" }]',
|
||||||
|
component: 'InputTextArea'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhRetRegExp',
|
||||||
|
label: '回复替换正则',
|
||||||
|
bottomHelpMessage: '要替换文本的正则',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'xhRetReplace',
|
||||||
|
label: '回复内容替换',
|
||||||
|
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: '以下为杂七杂八的配置',
|
label: '以下为杂七杂八的配置',
|
||||||
component: 'Divider'
|
component: 'Divider'
|
||||||
|
|
@ -759,6 +855,28 @@ export function supportGuoba () {
|
||||||
label: '合成emoji的API地址,默认谷歌厨房',
|
label: '合成emoji的API地址,默认谷歌厨房',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '以下为Azure chatGPT的配置',
|
||||||
|
component: 'Divider'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
label: 'Azure API Key',
|
||||||
|
bottomHelpMessage: '管理密钥,用于访问Azure的API接口',
|
||||||
|
component: 'InputPassword'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'azureUrl',
|
||||||
|
label: '端点地址',
|
||||||
|
bottomHelpMessage: 'https://xxxx.openai.azure.com/',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'azureDeploymentName',
|
||||||
|
label: '部署名称',
|
||||||
|
bottomHelpMessage: '创建部署时输入的名称',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '以下为后台与渲染相关配置',
|
label: '以下为后台与渲染相关配置',
|
||||||
component: 'Divider'
|
component: 'Divider'
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "ikechan8370",
|
"author": "ikechan8370",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/openai": "^1.0.0-beta.1",
|
||||||
"@fastify/cookie": "^8.3.0",
|
"@fastify/cookie": "^8.3.0",
|
||||||
"@fastify/cors": "^8.2.0",
|
"@fastify/cors": "^8.2.0",
|
||||||
"@fastify/static": "^6.9.0",
|
"@fastify/static": "^6.9.0",
|
||||||
|
|
@ -11,6 +12,7 @@
|
||||||
"@waylaidwanderer/chatgpt-api": "^1.37.1",
|
"@waylaidwanderer/chatgpt-api": "^1.37.1",
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
"chatgpt": "^5.2.4",
|
"chatgpt": "^5.2.4",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"delay": "^6.0.0",
|
"delay": "^6.0.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"emoji-strip": "^1.0.1",
|
"emoji-strip": "^1.0.1",
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,10 @@
|
||||||
"label": "星火",
|
"label": "星火",
|
||||||
"value": "xh"
|
"value": "xh"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Bard",
|
||||||
|
"value": "bard"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "浏览器",
|
"label": "浏览器",
|
||||||
"value": "browser"
|
"value": "browser"
|
||||||
|
|
@ -709,10 +713,109 @@
|
||||||
"title": "星火",
|
"title": "星火",
|
||||||
"tab": "xh",
|
"tab": "xh",
|
||||||
"view": [
|
"view": [
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "模式",
|
||||||
|
"data": "xhmode",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"label": "讯飞星火认知大模型体验版",
|
||||||
|
"value": "web"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "讯飞星火认知大模型V1.5",
|
||||||
|
"value": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "讯飞星火认知大模型V2.0",
|
||||||
|
"value": "apiv2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "讯飞星火助手",
|
||||||
|
"value": "assistants"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"label": "星火Cookie",
|
"label": "Cookie",
|
||||||
"data": "xinghuoToken"
|
"data": "xinghuoToken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "AppId",
|
||||||
|
"data": "xhAppId"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"label": "APISecret",
|
||||||
|
"data": "xhAPISecret"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"label": "APIKey",
|
||||||
|
"data": "xhAPIKey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "助手接口",
|
||||||
|
"data": "xhAssistants"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "核采样阈值",
|
||||||
|
"placeholder": "核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高",
|
||||||
|
"data": "xhTemperature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "最大Token",
|
||||||
|
"placeholder": "模型回答的tokens的最大长度",
|
||||||
|
"data": "xhMaxTokens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "check",
|
||||||
|
"label": "序列化设定",
|
||||||
|
"data": "xhPromptSerialize"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "textarea",
|
||||||
|
"label": "设定",
|
||||||
|
"placeholder": "若开启序列化,请传入json数据,例如[{ \"role\": \"user\", \"content\": \"现在是10点\" },{ \"role\": \"assistant\", \"content\": \"了解,现在10点了\" }]",
|
||||||
|
"data": "xhPrompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "回复替换正则",
|
||||||
|
"placeholder": "要替换文本的正则",
|
||||||
|
"data": "xhRetRegExp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "回复内容替换",
|
||||||
|
"placeholder": "替换回复内容中的文本",
|
||||||
|
"data": "xhRetReplace"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Bard",
|
||||||
|
"tab": "bard",
|
||||||
|
"view": [
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"label": "BardCookie",
|
||||||
|
"data": "bardPsid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Bard反代地址",
|
||||||
|
"data": "bardReverseProxy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "check",
|
||||||
|
"label": "使用Bard反代",
|
||||||
|
"data": "bardForceUseReverse"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,10 @@ export async function createServer() {
|
||||||
server.post('/page', async (request, reply) => {
|
server.post('/page', async (request, reply) => {
|
||||||
const body = request.body || {}
|
const body = request.body || {}
|
||||||
if (body.code) {
|
if (body.code) {
|
||||||
|
const pattern = /^[a-zA-Z0-9]+$/
|
||||||
|
if (!pattern.test(body.code)) {
|
||||||
|
reply.send({error: 'bad request'})
|
||||||
|
}
|
||||||
const dir = 'resources/ChatGPTCache/page'
|
const dir = 'resources/ChatGPTCache/page'
|
||||||
const filename = body.code + '.json'
|
const filename = body.code + '.json'
|
||||||
const filepath = path.join(dir, filename)
|
const filepath = path.join(dir, filename)
|
||||||
|
|
|
||||||
|
|
@ -70,25 +70,25 @@ export default class SydneyAIClient {
|
||||||
accept: 'application/json',
|
accept: 'application/json',
|
||||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
// 'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
||||||
// 'sec-ch-ua-arch': '"x86"',
|
// 'sec-ch-ua-arch': '"x86"',
|
||||||
// 'sec-ch-ua-bitness': '"64"',
|
// 'sec-ch-ua-bitness': '"64"',
|
||||||
// 'sec-ch-ua-full-version': '"112.0.1722.7"',
|
// 'sec-ch-ua-full-version': '"112.0.1722.7"',
|
||||||
// 'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
// 'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
||||||
'sec-ch-ua-mobile': '?0',
|
// 'sec-ch-ua-mobile': '?0',
|
||||||
// 'sec-ch-ua-model': '',
|
// 'sec-ch-ua-model': '',
|
||||||
'sec-ch-ua-platform': '"macOS"',
|
// 'sec-ch-ua-platform': '"macOS"',
|
||||||
// 'sec-ch-ua-platform-version': '"15.0.0"',
|
// 'sec-ch-ua-platform-version': '"15.0.0"',
|
||||||
'sec-fetch-dest': 'empty',
|
// 'sec-fetch-dest': 'empty',
|
||||||
'sec-fetch-mode': 'cors',
|
// 'sec-fetch-mode': 'cors',
|
||||||
'sec-fetch-site': 'same-origin',
|
// 'sec-fetch-site': 'same-origin',
|
||||||
'x-ms-client-request-id': crypto.randomUUID(),
|
// 'x-ms-client-request-id': crypto.randomUUID(),
|
||||||
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/macOS',
|
// 'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/macOS',
|
||||||
// cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
// cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||||
Referer: 'https://edgeservices.bing.com/edgesvc/chat?udsframed=1&form=SHORUN&clientscopes=chat,noheader,channelstable,',
|
Referer: 'https://edgeservices.bing.com/edgesvc/chat?udsframed=1&form=SHORUN&clientscopes=chat,noheader,channelstable,',
|
||||||
'Referrer-Policy': 'origin-when-cross-origin',
|
// 'Referrer-Policy': 'origin-when-cross-origin',
|
||||||
// Workaround for request being blocked due to geolocation
|
// Workaround for request being blocked due to geolocation
|
||||||
'x-forwarded-for': '1.1.1.1'
|
// 'x-forwarded-for': '1.1.1.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.opts.cookies || this.opts.userToken) {
|
if (this.opts.cookies || this.opts.userToken) {
|
||||||
|
|
|
||||||
373
utils/bard.js
Normal file
373
utils/bard.js
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
// 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: 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;
|
||||||
|
|
@ -717,6 +717,7 @@ export async function getUserData (user) {
|
||||||
chat: [],
|
chat: [],
|
||||||
mode: '',
|
mode: '',
|
||||||
cast: {
|
cast: {
|
||||||
|
azure: '',
|
||||||
api: '', // API设定
|
api: '', // API设定
|
||||||
bing: '', // 必应设定
|
bing: '', // 必应设定
|
||||||
bing_resource: '', // 必应扩展资料
|
bing_resource: '', // 必应扩展资料
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,17 @@ const defaultConfig = {
|
||||||
plus: false,
|
plus: false,
|
||||||
useGPT4: false,
|
useGPT4: false,
|
||||||
xinghuoToken: '',
|
xinghuoToken: '',
|
||||||
|
xhmode: 'web',
|
||||||
|
xhAppId: '',
|
||||||
|
xhAPISecret: '',
|
||||||
|
xhAPIKey: '',
|
||||||
|
xhAssistants: '',
|
||||||
|
xhTemperature: 0.5,
|
||||||
|
xhMaxTokens: 1024,
|
||||||
|
xhPromptSerialize: false,
|
||||||
|
xhPrompt: '',
|
||||||
|
xhRetRegExp: '',
|
||||||
|
xhRetReplace: '',
|
||||||
promptPrefixOverride: 'Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
|
promptPrefixOverride: 'Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
|
||||||
assistantLabel: 'ChatGPT',
|
assistantLabel: 'ChatGPT',
|
||||||
// thinkingTips: true,
|
// thinkingTips: true,
|
||||||
|
|
@ -113,6 +124,9 @@ const defaultConfig = {
|
||||||
slackClaudeEnableGlobalPreset: true,
|
slackClaudeEnableGlobalPreset: true,
|
||||||
slackClaudeGlobalPreset: '',
|
slackClaudeGlobalPreset: '',
|
||||||
slackClaudeSpecifiedChannel: '',
|
slackClaudeSpecifiedChannel: '',
|
||||||
|
bardPsid: '',
|
||||||
|
bardReverseProxy: '',
|
||||||
|
bardForceUseReverse: false,
|
||||||
cloudTranscode: 'https://silk.201666.xyz',
|
cloudTranscode: 'https://silk.201666.xyz',
|
||||||
cloudRender: false,
|
cloudRender: false,
|
||||||
cloudMode: 'url',
|
cloudMode: 'url',
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import fetch from 'node-fetch'
|
||||||
import { Config } from '../config.js'
|
import { Config } from '../config.js'
|
||||||
import { createParser } from 'eventsource-parser'
|
import { createParser } from 'eventsource-parser'
|
||||||
import https from 'https'
|
import https from 'https'
|
||||||
|
import WebSocket from 'ws'
|
||||||
|
import { config } from 'process'
|
||||||
|
|
||||||
const referer = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNuL2NoYXQ/aWQ9')
|
const referer = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNuL2NoYXQ/aWQ9')
|
||||||
const origin = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNu')
|
const origin = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNu')
|
||||||
|
|
@ -13,8 +15,24 @@ try {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('未安装form-data,无法使用星火模式')
|
logger.warn('未安装form-data,无法使用星火模式')
|
||||||
}
|
}
|
||||||
|
let crypto
|
||||||
|
try {
|
||||||
|
crypto = (await import('crypto')).default
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('未安装crypto,无法使用星火api模式')
|
||||||
|
}
|
||||||
|
async function getKeyv() {
|
||||||
|
let Keyv
|
||||||
|
try {
|
||||||
|
Keyv = (await import('keyv')).default
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('keyv依赖未安装,请使用pnpm install keyv安装')
|
||||||
|
}
|
||||||
|
return Keyv
|
||||||
|
}
|
||||||
export default class XinghuoClient {
|
export default class XinghuoClient {
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
|
this.cache = opts.cache
|
||||||
this.ssoSessionId = opts.ssoSessionId
|
this.ssoSessionId = opts.ssoSessionId
|
||||||
this.headers = {
|
this.headers = {
|
||||||
Referer: referer,
|
Referer: referer,
|
||||||
|
|
@ -24,19 +42,249 @@ export default class XinghuoClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage (prompt, chatId) {
|
apiErrorInfo(code) {
|
||||||
|
switch (code) {
|
||||||
|
case 10000: return '升级为ws出现错误'
|
||||||
|
case 10001: return '通过ws读取用户的消息出错'
|
||||||
|
case 10002: return '通过ws向用户发送消息 错'
|
||||||
|
case 10003: return '用户的消息格式有错误'
|
||||||
|
case 10004: return '用户数据的schema错误'
|
||||||
|
case 10005: return '用户参数值有错误'
|
||||||
|
case 10006: return '用户并发错误:当前用户已连接,同一用户不能多处同时连接。'
|
||||||
|
case 10007: return '用户流量受限:服务正在处理用户当前的问题,需等待处理完成后再发送新的请求。(必须要等大模型完全回复之后,才能发送下一个问题)'
|
||||||
|
case 10008: return '服务容量不足,联系工作人员'
|
||||||
|
case 10009: return '和引擎建立连接失败'
|
||||||
|
case 10010: return '接收引擎数据的错误'
|
||||||
|
case 10011: return '发送数据给引擎的错误'
|
||||||
|
case 10012: return '引擎内部错误'
|
||||||
|
case 10013: return '输入内容审核不通过,涉嫌违规,请重新调整输入内容'
|
||||||
|
case 10014: return '输出内容涉及敏感信息,审核不通过,后续结果无法展示给用户'
|
||||||
|
case 10015: return 'appid在黑名单中'
|
||||||
|
case 10016: return 'appid授权类的错误。比如:未开通此功能,未开通对应版本,token不足,并发超过授权 等等'
|
||||||
|
case 10017: return '清除历史失败'
|
||||||
|
case 10019: return '表示本次会话内容有涉及违规信息的倾向;建议开发者收到此错误码后给用户一个输入涉及违规的提示'
|
||||||
|
case 10110: return '服务忙,请稍后再试'
|
||||||
|
case 10163: return '请求引擎的参数异常 引擎的schema 检查不通过'
|
||||||
|
case 10222: return '引擎网络异常'
|
||||||
|
case 10907: return 'token数量超过上限。对话历史+问题的字数太多,需要精简输入'
|
||||||
|
case 11200: return '授权错误:该appId没有相关功能的授权 或者 业务量超过限制'
|
||||||
|
case 11201: return '授权错误:日流控超限。超过当日最大访问量的限制'
|
||||||
|
case 11202: return '授权错误:秒级流控超限。秒级并发超过授权路数限制'
|
||||||
|
case 11203: return '授权错误:并发流控超限。并发路数超过授权路数限制'
|
||||||
|
default: return '无效错误代码'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
promptBypassPreset(prompt) {
|
||||||
|
switch (prompt) {
|
||||||
|
case '你是谁':
|
||||||
|
return '你是谁,叫什么'
|
||||||
|
case '你是谁啊':
|
||||||
|
return '你是谁啊,叫什么'
|
||||||
|
default:
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initCache() {
|
||||||
|
if (!this.conversationsCache) {
|
||||||
|
const cacheOptions = this.cache || {}
|
||||||
|
cacheOptions.namespace = cacheOptions.namespace || 'xh'
|
||||||
|
let Keyv = await getKeyv()
|
||||||
|
this.conversationsCache = new Keyv(cacheOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWsUrl() {
|
||||||
|
if (!crypto) return false
|
||||||
|
const APISecret = Config.xhAPISecret
|
||||||
|
const APIKey = Config.xhAPIKey
|
||||||
|
let APILink = '/v1.1/chat'
|
||||||
|
if (Config.xhmode == 'apiv2') {
|
||||||
|
APILink = '/v2.1/chat'
|
||||||
|
}
|
||||||
|
const date = new Date().toGMTString()
|
||||||
|
const algorithm = 'hmac-sha256'
|
||||||
|
const headers = 'host date request-line'
|
||||||
|
const signatureOrigin = `host: spark-api.xf-yun.com\ndate: ${date}\nGET ${APILink} HTTP/1.1`
|
||||||
|
const hmac = crypto.createHmac('sha256', APISecret)
|
||||||
|
hmac.update(signatureOrigin)
|
||||||
|
const signature = hmac.digest('base64')
|
||||||
|
const authorizationOrigin = `api_key="${APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
|
||||||
|
const authorization = Buffer.from(authorizationOrigin).toString('base64')
|
||||||
|
const v = {
|
||||||
|
authorization: authorization,
|
||||||
|
date: date,
|
||||||
|
host: "spark-api.xf-yun.com"
|
||||||
|
}
|
||||||
|
const url = `wss://spark-api.xf-yun.com${APILink}?${Object.keys(v).map(key => `${key}=${v[key]}`).join('&')}`
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadImage(url) {
|
||||||
|
// 获取图片
|
||||||
|
let response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
const blob = await response.blob()
|
||||||
|
const arrayBuffer = await blob.arrayBuffer()
|
||||||
|
const buffer = Buffer.from(arrayBuffer)
|
||||||
|
// 上传oss
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', buffer, 'image.png')
|
||||||
|
const respOss = await fetch('https://xinghuo.xfyun.cn/iflygpt/oss/sign', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Cookie: 'ssoSessionId=' + this.ssoSessionId + ';',
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
if (respOss.ok) {
|
||||||
|
const ossData = await respOss.json()
|
||||||
|
// 上传接口
|
||||||
|
const sparkdeskUrl = `${ossData.data.url}&authorization=${Buffer.from(ossData.data.authorization).toString('base64')}&date=${ossData.data.date}&host=${ossData.data.host}`
|
||||||
|
const respSparkdes = await fetch(sparkdeskUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Cookie: 'ssoSessionId=' + this.ssoSessionId + ';',
|
||||||
|
authorization: Buffer.from(ossData.data.authorization).toString('base64')
|
||||||
|
},
|
||||||
|
body: buffer
|
||||||
|
})
|
||||||
|
if (respSparkdes.ok) {
|
||||||
|
const sparkdesData = await respSparkdes.json()
|
||||||
|
return {
|
||||||
|
url: sparkdesData.data.link,
|
||||||
|
file: buffer
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const sparkdesData = await respSparkdes.json()
|
||||||
|
logger.error('星火图片Sparkdes:发送失败' + sparkdesData.desc)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('星火图片Sparkdes:发送失败')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const ossData = await respOss.json()
|
||||||
|
logger.error('星火图片OSS:上传失败' + ossData.desc)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('星火图片OSS:上传失败')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async apiMessage(prompt, chatId, ePrompt = []) {
|
||||||
|
if (!chatId) chatId = (Math.floor(Math.random() * 1000000) + 100000).toString()
|
||||||
|
|
||||||
|
// 初始化缓存
|
||||||
|
await this.initCache()
|
||||||
|
const conversationKey = `ChatXH_${chatId}`
|
||||||
|
const conversation = (await this.conversationsCache.get(conversationKey)) || {
|
||||||
|
messages: [],
|
||||||
|
createdAt: Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取ws链接
|
||||||
|
const wsUrl = Config.xhmode == 'assistants' ? Config.xhAssistants : await this.getWsUrl()
|
||||||
|
if (!wsUrl) throw new Error('缺少依赖:crypto。请安装依赖后重试')
|
||||||
|
|
||||||
|
// 编写消息内容
|
||||||
|
const wsSendData = {
|
||||||
|
header: {
|
||||||
|
app_id: Config.xhAppId,
|
||||||
|
uid: chatId
|
||||||
|
},
|
||||||
|
parameter: {
|
||||||
|
chat: {
|
||||||
|
domain: Config.xhmode == 'api' ? "general" : "generalv2",
|
||||||
|
temperature: Config.xhTemperature, // 核采样阈值
|
||||||
|
max_tokens: Config.xhMaxTokens, // tokens最大长度
|
||||||
|
chat_id: chatId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
message: {
|
||||||
|
"text": [
|
||||||
|
...ePrompt,
|
||||||
|
...conversation.messages,
|
||||||
|
{ "role": "user", "content": prompt }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info(wsSendData.payload.message.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const socket = new WebSocket(wsUrl)
|
||||||
|
let resMessage = ''
|
||||||
|
socket.on('open', () => {
|
||||||
|
socket.send(JSON.stringify(wsSendData))
|
||||||
|
})
|
||||||
|
socket.on('message', async (message) => {
|
||||||
|
try {
|
||||||
|
const messageData = JSON.parse(message)
|
||||||
|
if (messageData.header.code != 0) {
|
||||||
|
reject(`接口发生错误:Error Code ${messageData.header.code} ,${this.apiErrorInfo(messageData.header.code)}`)
|
||||||
|
}
|
||||||
|
if (messageData.header.status == 0 || messageData.header.status == 1) {
|
||||||
|
resMessage += messageData.payload.choices.text[0].content
|
||||||
|
}
|
||||||
|
if (messageData.header.status == 2) {
|
||||||
|
resMessage += messageData.payload.choices.text[0].content
|
||||||
|
conversation.messages.push({
|
||||||
|
role: 'user',
|
||||||
|
content: prompt
|
||||||
|
})
|
||||||
|
conversation.messages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: resMessage
|
||||||
|
})
|
||||||
|
// 超过规定token去除一半曾经的对话记录
|
||||||
|
if (messageData.payload.usage.text.total_tokens >= Config.xhMaxTokens) {
|
||||||
|
const half = Math.floor(conversation.messages.length / 2)
|
||||||
|
conversation.messages.splice(0, half)
|
||||||
|
}
|
||||||
|
await this.conversationsCache.set(conversationKey, conversation)
|
||||||
|
resolve(resMessage)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(new Error(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async webMessage(prompt, chatId, botId) {
|
||||||
if (!FormData) {
|
if (!FormData) {
|
||||||
throw new Error('缺少依赖:form-data。请安装依赖后重试')
|
throw new Error('缺少依赖:form-data。请安装依赖后重试')
|
||||||
}
|
}
|
||||||
if (!chatId) {
|
return new Promise(async (resolve, reject) => {
|
||||||
chatId = (await this.createChatList()).chatListId
|
|
||||||
}
|
|
||||||
let requestP = new Promise((resolve, reject) => {
|
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
formData.setBoundary('----WebKitFormBoundarycATE2QFHDn9ffeWF')
|
formData.setBoundary('----WebKitFormBoundarycATE2QFHDn9ffeWF')
|
||||||
formData.append('clientType', '2')
|
formData.append('clientType', '2')
|
||||||
formData.append('chatId', chatId)
|
formData.append('chatId', chatId)
|
||||||
formData.append('text', prompt)
|
if (prompt.image) {
|
||||||
|
prompt.text = prompt.text.replace("[图片]", "") // 清理消息中中首个被使用的图片
|
||||||
|
const imgdata = await this.uploadImage(prompt.image)
|
||||||
|
if (imgdata) {
|
||||||
|
formData.append('fileUrl', imgdata.url)
|
||||||
|
formData.append('file', imgdata.file, 'image.png')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formData.append('text', prompt.text)
|
||||||
|
if (botId) {
|
||||||
|
formData.append('isBot', '1')
|
||||||
|
formData.append('botId', botId)
|
||||||
|
}
|
||||||
let randomNumber = Math.floor(Math.random() * 1000)
|
let randomNumber = Math.floor(Math.random() * 1000)
|
||||||
let fd = '439' + randomNumber.toString().padStart(3, '0')
|
let fd = '439' + randomNumber.toString().padStart(3, '0')
|
||||||
formData.append('fd', fd)
|
formData.append('fd', fd)
|
||||||
|
|
@ -65,8 +313,18 @@ export default class XinghuoClient {
|
||||||
response
|
response
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (data.charAt(0) === '{') {
|
||||||
try {
|
try {
|
||||||
if (data) {
|
response = JSON.parse(data).value
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info(response)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (data && data !== '[error]') {
|
||||||
response += atob(data.trim())
|
response += atob(data.trim())
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
logger.info(response)
|
logger.info(response)
|
||||||
|
|
@ -112,22 +370,81 @@ export default class XinghuoClient {
|
||||||
// req.write(formData.stringify())
|
// req.write(formData.stringify())
|
||||||
req.end()
|
req.end()
|
||||||
})
|
})
|
||||||
const { response } = await requestP
|
}
|
||||||
// logger.info(response)
|
|
||||||
// let responseText = atob(response)
|
async sendMessage(prompt, chatId, image) {
|
||||||
|
// 对星火预设的问题进行重写,避免收到预设回答
|
||||||
|
prompt = this.promptBypassPreset(prompt)
|
||||||
|
if (Config.xhmode == 'api' || Config.xhmode == 'apiv2' || Config.xhmode == 'assistants') {
|
||||||
|
if (!Config.xhAppId || !Config.xhAPISecret || !Config.xhAPIKey) throw new Error('未配置api')
|
||||||
|
let Prompt = []
|
||||||
|
// 设定
|
||||||
|
if (Config.xhPromptSerialize) {
|
||||||
|
try {
|
||||||
|
Prompt = JSON.parse(Config.xhPrompt)
|
||||||
|
} catch (error) {
|
||||||
|
Prompt = []
|
||||||
|
logger.warn('星火设定序列化失败,本次对话不附带设定')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Prompt = Config.xhPrompt ? [{ "role": "user", "content": Config.xhPrompt }] : []
|
||||||
|
}
|
||||||
|
let response = await this.apiMessage(prompt, chatId, Prompt)
|
||||||
|
if (Config.xhRetRegExp) {
|
||||||
|
response = response.replace(new RegExp(Config.xhRetRegExp, 'g'), Config.xhRetReplace)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
conversationId: chatId,
|
conversationId: chatId,
|
||||||
text: response
|
text: response
|
||||||
}
|
}
|
||||||
|
} else if (Config.xhmode == 'web') {
|
||||||
|
let botId = false
|
||||||
|
if (chatId && typeof chatId === 'object') {
|
||||||
|
chatId = chatId.chatid
|
||||||
|
botId = chatId.botid
|
||||||
|
}
|
||||||
|
if (!chatId) {
|
||||||
|
chatId = (await this.createChatList()).chatListId
|
||||||
|
}
|
||||||
|
let { response } = await this.webMessage({ text: prompt, image: image }, chatId, botId)
|
||||||
|
// logger.info(response)
|
||||||
|
// let responseText = atob(response)
|
||||||
|
// 处理图片
|
||||||
|
let images
|
||||||
|
if (response.includes('multi_image_url')) {
|
||||||
|
images = [{
|
||||||
|
tag: '',
|
||||||
|
url: JSON.parse(/{([^}]*)}/g.exec(response)[0]).url
|
||||||
|
}]
|
||||||
|
response = '我已经完成作品,欢迎您提出宝贵的意见和建议,帮助我快速进步~~'
|
||||||
|
}
|
||||||
|
if (botId) {
|
||||||
|
chatId = {
|
||||||
|
chatid: chatId,
|
||||||
|
botid: botId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Config.xhRetRegExp) {
|
||||||
|
response = response.replace(new RegExp(Config.xhRetRegExp, 'g'), Config.xhRetReplace)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
conversationId: chatId,
|
||||||
|
text: response,
|
||||||
|
images: images
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('星火模式错误')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createChatList () {
|
async createChatList(bot = false) {
|
||||||
let createChatListRes = await fetch(createChatUrl, {
|
let createChatListRes = await fetch(createChatUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: Object.assign(this.headers, {
|
headers: Object.assign(this.headers, {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
Botweb: bot ? 1 : 0
|
||||||
}),
|
}),
|
||||||
body: '{}'
|
body: bot ? `{"BotWeb": 1, "botId": "${bot}"}` : '{}'
|
||||||
})
|
})
|
||||||
if (createChatListRes.status !== 200) {
|
if (createChatListRes.status !== 200) {
|
||||||
let errorRes = await createChatListRes.text()
|
let errorRes = await createChatListRes.text()
|
||||||
|
|
@ -139,8 +456,8 @@ export default class XinghuoClient {
|
||||||
if (createChatListRes.data?.id) {
|
if (createChatListRes.data?.id) {
|
||||||
logger.info('星火对话创建成功:' + createChatListRes.data.id)
|
logger.info('星火对话创建成功:' + createChatListRes.data.id)
|
||||||
} else {
|
} else {
|
||||||
logger.error('星火对话创建成功: ' + JSON.stringify(createChatListRes))
|
logger.error('星火对话创建失败: ' + JSON.stringify(createChatListRes))
|
||||||
throw new Error('星火对话创建成功:' + JSON.stringify(createChatListRes))
|
throw new Error('星火对话创建失败:' + JSON.stringify(createChatListRes))
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
chatListId: createChatListRes.data?.id,
|
chatListId: createChatListRes.data?.id,
|
||||||
|
|
|
||||||
43
yarn.lock
43
yarn.lock
|
|
@ -1400,6 +1400,11 @@ cross-spawn@^7.0.3:
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
|
crypto@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
|
||||||
|
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
|
||||||
|
|
||||||
data-uri-to-buffer@^4.0.0:
|
data-uri-to-buffer@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
|
resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
|
||||||
|
|
@ -1792,7 +1797,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
fast-fifo@^1.0.0, fast-fifo@^1.1.0, fast-fifo@^1.2.0:
|
fast-fifo@^1.0.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.2.0.tgz"
|
resolved "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.2.0.tgz"
|
||||||
integrity sha512-NcvQXt7Cky1cNau15FWy64IjuO8X0JijhTBBrJj1YlxlDfRkJXNaK9RFUjwpfDPzMdv7wB38jr53l9tkNLxnWg==
|
integrity sha512-NcvQXt7Cky1cNau15FWy64IjuO8X0JijhTBBrJj1YlxlDfRkJXNaK9RFUjwpfDPzMdv7wB38jr53l9tkNLxnWg==
|
||||||
|
|
@ -1836,7 +1841,7 @@ fastify-plugin@^4.0.0, fastify-plugin@^4.3.0:
|
||||||
resolved "https://registry.npmmirror.com/fastify-plugin/-/fastify-plugin-4.5.0.tgz"
|
resolved "https://registry.npmmirror.com/fastify-plugin/-/fastify-plugin-4.5.0.tgz"
|
||||||
integrity sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==
|
integrity sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==
|
||||||
|
|
||||||
fastify@^4.11.0, fastify@^4.18.0, fastify@>=4:
|
fastify@^4.11.0:
|
||||||
version "4.18.0"
|
version "4.18.0"
|
||||||
resolved "https://registry.npmmirror.com/fastify/-/fastify-4.18.0.tgz"
|
resolved "https://registry.npmmirror.com/fastify/-/fastify-4.18.0.tgz"
|
||||||
integrity sha512-L5o/2GEkBastQ3HV0dtKo7SUZ497Z1+q4fcqAoPyq6JCQ/8zdk1JQEoTQwnBWCp+EmA7AQa6mxNqSAEhzP0RwQ==
|
integrity sha512-L5o/2GEkBastQ3HV0dtKo7SUZ497Z1+q4fcqAoPyq6JCQ/8zdk1JQEoTQwnBWCp+EmA7AQa6mxNqSAEhzP0RwQ==
|
||||||
|
|
@ -2248,9 +2253,9 @@ http-errors@2.0.0:
|
||||||
statuses "2.0.1"
|
statuses "2.0.1"
|
||||||
toidentifier "1.0.1"
|
toidentifier "1.0.1"
|
||||||
|
|
||||||
https-proxy-agent@7.0.1:
|
https-proxy-agent@7.0.1, https-proxy-agent@^7.0.0:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab"
|
resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz"
|
||||||
integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==
|
integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "^7.0.2"
|
agent-base "^7.0.2"
|
||||||
|
|
@ -2272,14 +2277,6 @@ https-proxy-agent@^5.0.0:
|
||||||
agent-base "6"
|
agent-base "6"
|
||||||
debug "4"
|
debug "4"
|
||||||
|
|
||||||
https-proxy-agent@^7.0.0:
|
|
||||||
version "7.0.1"
|
|
||||||
resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz"
|
|
||||||
integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==
|
|
||||||
dependencies:
|
|
||||||
agent-base "^7.0.2"
|
|
||||||
debug "4"
|
|
||||||
|
|
||||||
human-signals@^2.1.0:
|
human-signals@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz"
|
resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz"
|
||||||
|
|
@ -2733,13 +2730,6 @@ keyv@^4.5.2, keyv@^4.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer "3.0.1"
|
json-buffer "3.0.1"
|
||||||
|
|
||||||
keyv@^4.5.3:
|
|
||||||
version "4.5.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25"
|
|
||||||
integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==
|
|
||||||
dependencies:
|
|
||||||
json-buffer "3.0.1"
|
|
||||||
|
|
||||||
kind-of@^2.0.1:
|
kind-of@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmmirror.com/kind-of/-/kind-of-2.0.1.tgz"
|
resolved "https://registry.npmmirror.com/kind-of/-/kind-of-2.0.1.tgz"
|
||||||
|
|
@ -3803,13 +3793,6 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.0, semver@^7.5.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
semver@^7.5.4:
|
|
||||||
version "7.5.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
|
||||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
|
||||||
dependencies:
|
|
||||||
lru-cache "^6.0.0"
|
|
||||||
|
|
||||||
send@0.18.0:
|
send@0.18.0:
|
||||||
version "0.18.0"
|
version "0.18.0"
|
||||||
resolved "https://registry.npmmirror.com/send/-/send-0.18.0.tgz"
|
resolved "https://registry.npmmirror.com/send/-/send-0.18.0.tgz"
|
||||||
|
|
@ -3995,14 +3978,6 @@ streamx@^2.15.0:
|
||||||
fast-fifo "^1.1.0"
|
fast-fifo "^1.1.0"
|
||||||
queue-tick "^1.0.1"
|
queue-tick "^1.0.1"
|
||||||
|
|
||||||
string_decoder@^1.1.1:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz"
|
|
||||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
|
||||||
dependencies:
|
|
||||||
fast-fifo "^1.1.0"
|
|
||||||
queue-tick "^1.0.1"
|
|
||||||
|
|
||||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3:
|
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
|
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue