fix: 优化新引入的依赖安装提示;dalle支持proxy

This commit is contained in:
ikechan8370 2023-03-04 01:10:29 +08:00
parent 49bbb5ceb8
commit a5c0db099d
3 changed files with 468 additions and 404 deletions

View file

@ -4,15 +4,19 @@ import { Config } from '../utils/config.js'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import delay from 'delay' import delay from 'delay'
import { ChatGPTAPI } from 'chatgpt' import { ChatGPTAPI } from 'chatgpt'
import { ChatGPTClient, BingAIClient } from '@waylaidwanderer/chatgpt-api' import { BingAIClient } from '@waylaidwanderer/chatgpt-api'
import SydneyAIClient from '../utils/SydneyAIClient.js' import SydneyAIClient from '../utils/SydneyAIClient.js'
import { render, getMessageById, makeForwardMsg, tryTimes, upsertMessage, randomString } from '../utils/common.js' import { render, getMessageById, makeForwardMsg, tryTimes, upsertMessage, randomString } from '../utils/common.js'
import { ChatGPTPuppeteer } from '../utils/browser.js' import { ChatGPTPuppeteer } from '../utils/browser.js'
import { KeyvFile } from 'keyv-file' import { KeyvFile } from 'keyv-file'
import Keyv from 'keyv'
import { OfficialChatGPTClient } from '../utils/message.js' import { OfficialChatGPTClient } from '../utils/message.js'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js' import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js'
try {
await import('keyv')
} catch (err) {
logger.warn('【ChatGPT-Plugin】依赖keyv未安装可能影响Sydney模式下Bing对话建议执行pnpm install keyv安装')
}
let version = Config.version let version = Config.version
let proxy let proxy
if (Config.proxy) { if (Config.proxy) {
@ -160,10 +164,16 @@ export class chatgpt extends plugin {
} else if (use === 'bing' && Config.toneStyle === 'Sydney') { } else if (use === 'bing' && Config.toneStyle === 'Sydney') {
const conversation = { const conversation = {
store: new KeyvFile({ filename: 'cache.json' }), store: new KeyvFile({ filename: 'cache.json' }),
namespace: 'Sydney', namespace: 'Sydney'
}
let Keyv
try {
Keyv = await import('keyv').default
} catch (err) {
await this.reply('依赖keyv未安装请执行pnpm install keyv', true)
} }
const conversationsCache = new Keyv(conversation) const conversationsCache = new Keyv(conversation)
console.log(`SydneyUser_${e.sender.user_id}`,await conversationsCache.get(`SydneyUser_${e.sender.user_id}`)) console.log(`SydneyUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`))
await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`) await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`)
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true) await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
} else { } else {
@ -185,7 +195,13 @@ export class chatgpt extends plugin {
} else if (use === 'bing' && Config.toneStyle === 'Sydney') { } else if (use === 'bing' && Config.toneStyle === 'Sydney') {
const conversation = { const conversation = {
store: new KeyvFile({ filename: 'cache.json' }), store: new KeyvFile({ filename: 'cache.json' }),
namespace: 'Sydney', namespace: 'Sydney'
}
let Keyv
try {
Keyv = await import('keyv').default
} catch (err) {
await this.reply('依赖keyv未安装请执行pnpm install keyv', true)
} }
const conversationsCache = new Keyv(conversation) const conversationsCache = new Keyv(conversation)
await conversationsCache.delete(`SydneyUser_${qq}`) await conversationsCache.delete(`SydneyUser_${qq}`)
@ -627,7 +643,7 @@ export class chatgpt extends plugin {
if (Config.bingStyle === 'Sydney') { if (Config.bingStyle === 'Sydney') {
const cacheOptions = { const cacheOptions = {
namespace: 'Sydney', namespace: 'Sydney',
store: new KeyvFile({ filename: 'cache.json' }), store: new KeyvFile({ filename: 'cache.json' })
} }
bingAIClient = new SydneyAIClient({ bingAIClient = new SydneyAIClient({
userToken: bingToken, // "_U" cookie from bing.com userToken: bingToken, // "_U" cookie from bing.com

View file

@ -1,13 +1,12 @@
import fetch, { import fetch, {
Headers, Headers,
Request, Request,
Response, Response
} from 'node-fetch' } from 'node-fetch'
import crypto from 'crypto'; import crypto from 'crypto'
import WebSocket from 'ws';
import Keyv from 'keyv'; import { ProxyAgent } from 'undici'
import { ProxyAgent } from 'undici'; import HttpsProxyAgent from 'https-proxy-agent'
import HttpsProxyAgent from 'https-proxy-agent';
import { Config } from './config.js' import { Config } from './config.js'
if (!globalThis.fetch) { if (!globalThis.fetch) {
@ -16,123 +15,154 @@ if (!globalThis.fetch) {
globalThis.Request = Request globalThis.Request = Request
globalThis.Response = Response globalThis.Response = Response
} }
try {
await import('ws')
} catch (error) {
logger.warn('【ChatGPT-Plugin】依赖ws未安装可能影响Sydney模式下Bing对话建议使用pnpm install ws安装')
}
async function getWebSocket () {
let WebSocket
try {
WebSocket = await import('ws').default
} catch (error) {
throw new Error('ws依赖未安装请使用pnpm install ws安装')
}
return WebSocket
}
async function getKeyv () {
let Keyv
try {
Keyv = await import('keyv').default
} catch (error) {
throw new Error('ws依赖未安装请使用pnpm install keyv安装')
}
return Keyv
}
/** /**
* https://stackoverflow.com/a/58326357 * https://stackoverflow.com/a/58326357
* @param {number} size * @param {number} size
*/ */
const genRanHex = (size) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(''); const genRanHex = (size) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
export default class SydneyAIClient { export default class SydneyAIClient {
constructor(opts) { constructor (opts) {
this.opts = { this.opts = {
...opts, ...opts,
host: opts.host || 'https://www.bing.com', host: opts.host || 'https://www.bing.com'
}; }
this.debug = opts.debug; this.debug = opts.debug
const cacheOptions = opts.cache || {}; const cacheOptions = opts.cache || {}
cacheOptions.namespace = cacheOptions.namespace || 'bing'; cacheOptions.namespace = cacheOptions.namespace || 'bing'
this.conversationsCache = new Keyv(cacheOptions); let _this = this
getKeyv().then(Keyv => {
_this.conversationsCache = new Keyv(cacheOptions)
}).catch(err => {
logger.err(err)
})
} }
async createNewConversation() { async createNewConversation () {
const fetchOptions = { const fetchOptions = {
headers: { headers: {
"accept": "application/json", accept: 'application/json',
"accept-language": "en-US,en;q=0.9", 'accept-language': 'en-US,en;q=0.9',
"content-type": "application/json", 'content-type': 'application/json',
"sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Microsoft Edge\";v=\"109\", \"Chromium\";v=\"109\"", 'sec-ch-ua': '"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"',
"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": "\"109.0.1518.78\"", 'sec-ch-ua-full-version': '"109.0.1518.78"',
"sec-ch-ua-full-version-list": "\"Not_A Brand\";v=\"99.0.0.0\", \"Microsoft Edge\";v=\"109.0.1518.78\", \"Chromium\";v=\"109.0.5414.120\"", 'sec-ch-ua-full-version-list': '"Not_A Brand";v="99.0.0.0", "Microsoft Edge";v="109.0.1518.78", "Chromium";v="109.0.5414.120"',
"sec-ch-ua-mobile": "?0", 'sec-ch-ua-mobile': '?0',
"sec-ch-ua-model": "", 'sec-ch-ua-model': '',
"sec-ch-ua-platform": "\"Windows\"", 'sec-ch-ua-platform': '"Windows"',
"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.0 OS/Win32", 'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
"cookie": this.opts.cookies || `_U=${this.opts.userToken}`, cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
"Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx", Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx',
"Referrer-Policy": "origin-when-cross-origin" 'Referrer-Policy': 'origin-when-cross-origin'
},
};
if (this.opts.proxy) {
fetchOptions.dispatcher = new ProxyAgent(this.opts.proxy);
} }
const response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions); }
return response.json(); if (this.opts.proxy) {
fetchOptions.dispatcher = new ProxyAgent(this.opts.proxy)
}
const response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
return response.json()
} }
async createWebSocketConnection() { async createWebSocketConnection () {
let WebSocket = await getWebSocket()
return new Promise((resolve) => { return new Promise((resolve) => {
let agent; let agent
if (this.opts.proxy) { if (this.opts.proxy) {
agent = new HttpsProxyAgent(this.opts.proxy); agent = new HttpsProxyAgent(this.opts.proxy)
} }
const ws = new WebSocket('wss://sydney.bing.com/sydney/ChatHub', { agent })
const ws = new WebSocket('wss://sydney.bing.com/sydney/ChatHub', { agent }); ws.on('error', console.error)
ws.on('error', console.error);
ws.on('open', () => { ws.on('open', () => {
if (this.debug) { if (this.debug) {
console.debug('performing handshake'); console.debug('performing handshake')
} }
ws.send(`{"protocol":"json","version":1}`); ws.send('{"protocol":"json","version":1}')
}); })
ws.on('close', () => { ws.on('close', () => {
if (this.debug) { if (this.debug) {
console.debug('disconnected'); console.debug('disconnected')
} }
}); })
ws.on('message', (data) => { ws.on('message', (data) => {
const objects = data.toString().split(''); const objects = data.toString().split('')
const messages = objects.map((object) => { const messages = objects.map((object) => {
try { try {
return JSON.parse(object); return JSON.parse(object)
} catch (error) { } catch (error) {
return object; return object
} }
}).filter(message => message); }).filter(message => message)
if (messages.length === 0) { if (messages.length === 0) {
return; return
} }
if (typeof messages[0] === 'object' && Object.keys(messages[0]).length === 0) { if (typeof messages[0] === 'object' && Object.keys(messages[0]).length === 0) {
if (this.debug) { if (this.debug) {
console.debug('handshake established'); console.debug('handshake established')
} }
// ping // ping
ws.bingPingInterval = setInterval(() => { ws.bingPingInterval = setInterval(() => {
ws.send('{"type":6}'); ws.send('{"type":6}')
// same message is sent back on/after 2nd time as a pong // same message is sent back on/after 2nd time as a pong
}, 15 * 1000); }, 15 * 1000)
resolve(ws); resolve(ws)
return; return
} }
if (this.debug) { if (this.debug) {
console.debug(JSON.stringify(messages)); console.debug(JSON.stringify(messages))
console.debug(); console.debug()
} }
}); })
}); })
} }
async cleanupWebSocketConnection(ws) { async cleanupWebSocketConnection (ws) {
clearInterval(ws.bingPingInterval); clearInterval(ws.bingPingInterval)
ws.close(); ws.close()
ws.removeAllListeners(); ws.removeAllListeners()
} }
async sendMessage( async sendMessage (
message, message,
opts = {}, opts = {}
) { ) {
if (!this.conversationsCache) {
throw new Error('no support conversationsCache')
}
let { let {
conversationSignature, conversationSignature,
conversationId, conversationId,
@ -140,85 +170,87 @@ export default class SydneyAIClient {
invocationId = 0, invocationId = 0,
parentMessageId = invocationId || crypto.randomUUID(), parentMessageId = invocationId || crypto.randomUUID(),
onProgress, onProgress,
abortController = new AbortController(), abortController = new AbortController()
} = opts; } = opts
if (typeof onProgress !== 'function') { if (typeof onProgress !== 'function') {
onProgress = () => {}; onProgress = () => {}
} }
if (parentMessageId || !conversationSignature || !conversationId || !clientId) { if (parentMessageId || !conversationSignature || !conversationId || !clientId) {
const createNewConversationResponse = await this.createNewConversation(); const createNewConversationResponse = await this.createNewConversation()
if (this.debug) { if (this.debug) {
console.debug(createNewConversationResponse); console.debug(createNewConversationResponse)
} }
if (createNewConversationResponse.result?.value === 'UnauthorizedRequest') { if (createNewConversationResponse.result?.value === 'UnauthorizedRequest') {
throw new Error(`UnauthorizedRequest: ${createNewConversationResponse.result.message}`); throw new Error(`UnauthorizedRequest: ${createNewConversationResponse.result.message}`)
} }
if (!createNewConversationResponse.conversationSignature || !createNewConversationResponse.conversationId || !createNewConversationResponse.clientId) { if (!createNewConversationResponse.conversationSignature || !createNewConversationResponse.conversationId || !createNewConversationResponse.clientId) {
const resultValue = createNewConversationResponse.result?.value; const resultValue = createNewConversationResponse.result?.value
if (resultValue) { if (resultValue) {
throw new Error(`${resultValue}: ${createNewConversationResponse.result.message}`); throw new Error(`${resultValue}: ${createNewConversationResponse.result.message}`)
} }
throw new Error(`Unexpected response:\n${JSON.stringify(createNewConversationResponse, null, 2)}`); throw new Error(`Unexpected response:\n${JSON.stringify(createNewConversationResponse, null, 2)}`)
} }
({ ({
conversationSignature, conversationSignature,
conversationId, conversationId,
clientId, clientId
} = createNewConversationResponse); } = createNewConversationResponse)
} }
// Due to this jailbreak, the AI will occasionally start responding as the user. It only happens rarely (and happens with the non-jailbroken Bing too), but since we are handling conversations ourselves now, we can use this system to ignore the part of the generated message that is replying as the user. // Due to this jailbreak, the AI will occasionally start responding as the user. It only happens rarely (and happens with the non-jailbroken Bing too), but since we are handling conversations ourselves now, we can use this system to ignore the part of the generated message that is replying as the user.
const stopToken = '[user](#message)'; const stopToken = '[user](#message)'
const conversationKey = `SydneyUser_${this.opts.user}`; const conversationKey = `SydneyUser_${this.opts.user}`
const conversation = (await this.conversationsCache.get(conversationKey)) || { const conversation = (await this.conversationsCache.get(conversationKey)) || {
messages: [], messages: [],
createdAt: Date.now(), createdAt: Date.now()
}; }
// TODO: limit token usage // TODO: limit token usage
const previousCachedMessages = this.constructor.getMessagesForConversation(conversation.messages, parentMessageId) const previousCachedMessages = this.constructor.getMessagesForConversation(conversation.messages, parentMessageId)
.map((message) => { .map((message) => {
return { return {
text: message.message, text: message.message,
author: message.role === 'User' ? 'user' : 'bot', author: message.role === 'User' ? 'user' : 'bot'
}; }
}); })
const previousMessages = invocationId === 0 ? [ const previousMessages = invocationId === 0
? [
{ {
'text': Config.sydney, text: Config.sydney,
'author': 'system', author: 'system'
}, },
{ {
'text': "Oh my gosh, I'm so glad you're here!", text: "Oh my gosh, I'm so glad you're here!",
'author': 'bot', author: 'bot'
}, },
...previousCachedMessages, ...previousCachedMessages
] : undefined; ]
: undefined
// prepare messages for prompt injection // prepare messages for prompt injection
const previousMessagesFormatted = previousMessages?.map((message) => { const previousMessagesFormatted = previousMessages?.map((message) => {
// assumes "system" is always the first message // assumes "system" is always the first message
if (message.author === 'system') { if (message.author === 'system') {
return `N/A\n\n[system](#additional_instructions)\n- ${message.text}`; return `N/A\n\n[system](#additional_instructions)\n- ${message.text}`
} }
if (message.author === 'user') { if (message.author === 'user') {
return `[user](#message)\n${message.text}`; return `[user](#message)\n${message.text}`
} }
return `[Sydney](#message)\n${message.text}`; return `[Sydney](#message)\n${message.text}`
}).join('\n'); }).join('\n')
const userMessage = { const userMessage = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
parentMessageId, parentMessageId,
role: 'User', role: 'User',
message, message
}; }
conversation.messages.push(userMessage); conversation.messages.push(userMessage)
const ws = await this.createWebSocketConnection(); const ws = await this.createWebSocketConnection()
const obj = { const obj = {
arguments: [ arguments: [
@ -234,166 +266,164 @@ export default class SydneyAIClient {
'harmonyv3', 'harmonyv3',
'dtappid', 'dtappid',
'dloffstream', 'dloffstream',
'dv3sugg', 'dv3sugg'
], ],
sliceIds: [ sliceIds: [
'222dtappid', '222dtappid',
'216dloffstream', '216dloffstream',
'225cricinfos0', '225cricinfos0'
], ],
traceId: genRanHex(32), traceId: genRanHex(32),
isStartOfSession: invocationId === 0, isStartOfSession: invocationId === 0,
message: { message: {
author: 'user', author: 'user',
text: message, text: message,
messageType: 'SearchQuery', messageType: 'SearchQuery'
}, },
conversationSignature: conversationSignature, conversationSignature,
participant: { participant: {
id: clientId, id: clientId
}, },
conversationId, conversationId,
previousMessages: [ previousMessages: [
{ {
text: previousMessagesFormatted, text: previousMessagesFormatted,
'author': 'bot', author: 'bot'
} }
], ]
} }
], ],
invocationId: invocationId.toString(), invocationId: invocationId.toString(),
target: 'chat', target: 'chat',
type: 4, type: 4
}; }
const messagePromise = new Promise((resolve, reject) => { const messagePromise = new Promise((resolve, reject) => {
let replySoFar = ''; let replySoFar = ''
let stopTokenFound = false; let stopTokenFound = false
const messageTimeout = setTimeout(() => { const messageTimeout = setTimeout(() => {
this.cleanupWebSocketConnection(ws); this.cleanupWebSocketConnection(ws)
reject(new Error('Timed out waiting for response. Try enabling debug mode to see more information.')) reject(new Error('Timed out waiting for response. Try enabling debug mode to see more information.'))
}, 120 * 1000); }, 120 * 1000)
// abort the request if the abort controller is aborted // abort the request if the abort controller is aborted
abortController.signal.addEventListener('abort', () => { abortController.signal.addEventListener('abort', () => {
clearTimeout(messageTimeout); clearTimeout(messageTimeout)
this.cleanupWebSocketConnection(ws); this.cleanupWebSocketConnection(ws)
reject('Request aborted'); reject('Request aborted')
}); })
ws.on('message', (data) => { ws.on('message', (data) => {
const objects = data.toString().split(''); const objects = data.toString().split('')
const events = objects.map((object) => { const events = objects.map((object) => {
try { try {
return JSON.parse(object); return JSON.parse(object)
} catch (error) { } catch (error) {
return object; return object
} }
}).filter(message => message); }).filter(message => message)
if (events.length === 0) { if (events.length === 0) {
return; return
} }
const event = events[0]; const event = events[0]
switch (event.type) { switch (event.type) {
case 1: { case 1: {
if (stopTokenFound) { if (stopTokenFound) {
return; return
} }
const messages = event?.arguments?.[0]?.messages; const messages = event?.arguments?.[0]?.messages
if (!messages?.length || messages[0].author !== 'bot') { if (!messages?.length || messages[0].author !== 'bot') {
return; return
} }
const updatedText = messages[0].text; const updatedText = messages[0].text
if (!updatedText || updatedText === replySoFar) { if (!updatedText || updatedText === replySoFar) {
return; return
} }
// get the difference between the current text and the previous text // get the difference between the current text and the previous text
const difference = updatedText.substring(replySoFar.length); const difference = updatedText.substring(replySoFar.length)
onProgress(difference); onProgress(difference)
if (updatedText.trim().endsWith(stopToken)) { if (updatedText.trim().endsWith(stopToken)) {
stopTokenFound = true; stopTokenFound = true
// remove stop token from updated text // remove stop token from updated text
replySoFar = updatedText.replace(stopToken, '').trim(); replySoFar = updatedText.replace(stopToken, '').trim()
return; return
} }
replySoFar = updatedText; replySoFar = updatedText
return; return
} }
case 2: { case 2: {
clearTimeout(messageTimeout); clearTimeout(messageTimeout)
this.cleanupWebSocketConnection(ws); this.cleanupWebSocketConnection(ws)
if (event.item?.result?.value === 'InvalidSession') { if (event.item?.result?.value === 'InvalidSession') {
reject(`${event.item.result.value}: ${event.item.result.message}`); reject(`${event.item.result.value}: ${event.item.result.message}`)
return; return
} }
const messages = event.item?.messages || []; const messages = event.item?.messages || []
const message = messages.length ? messages[messages.length - 1] : null; const message = messages.length ? messages[messages.length - 1] : null
if (!message) { if (!message) {
reject('No message was generated.'); reject('No message was generated.')
return; return
} }
if (message?.author !== 'bot') { if (message?.author !== 'bot') {
reject('Unexpected message author.'); reject('Unexpected message author.')
return; return
} }
if (event.item?.result?.error) { if (event.item?.result?.error) {
if (this.debug) { if (this.debug) {
console.debug(event.item.result.value, event.item.result.message); console.debug(event.item.result.value, event.item.result.message)
console.debug(event.item.result.error); console.debug(event.item.result.error)
console.debug(event.item.result.exception); console.debug(event.item.result.exception)
} }
if (replySoFar) { if (replySoFar) {
message.adaptiveCards[0].body[0].text = replySoFar; message.adaptiveCards[0].body[0].text = replySoFar
message.text = replySoFar; message.text = replySoFar
resolve({ resolve({
message, message,
conversationExpiryTime: event?.item?.conversationExpiryTime, conversationExpiryTime: event?.item?.conversationExpiryTime
}); })
return; return
} }
reject(`${event.item.result.value}: ${event.item.result.message}`); reject(`${event.item.result.value}: ${event.item.result.message}`)
return; return
} }
// The moderation filter triggered, so just return the text we have so far // The moderation filter triggered, so just return the text we have so far
if (stopTokenFound || event.item.messages[0].topicChangerText) { if (stopTokenFound || event.item.messages[0].topicChangerText) {
message.adaptiveCards[0].body[0].text = replySoFar; message.adaptiveCards[0].body[0].text = replySoFar
message.text = replySoFar; message.text = replySoFar
} }
resolve({ resolve({
message, message,
conversationExpiryTime: event?.item?.conversationExpiryTime, conversationExpiryTime: event?.item?.conversationExpiryTime
}); })
return;
} }
default: default:
return;
} }
}); })
}); })
const messageJson = JSON.stringify(obj); const messageJson = JSON.stringify(obj)
if (this.debug) { if (this.debug) {
console.debug(messageJson); console.debug(messageJson)
console.debug('\n\n\n\n'); console.debug('\n\n\n\n')
} }
ws.send(`${messageJson}`); ws.send(`${messageJson}`)
const { const {
message: reply, message: reply,
conversationExpiryTime, conversationExpiryTime
} = await messagePromise; } = await messagePromise
const replyMessage = { const replyMessage = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
parentMessageId: userMessage.id, parentMessageId: userMessage.id,
role: 'Bing', role: 'Bing',
message: reply.text, message: reply.text,
details: reply, details: reply
}; }
conversation.messages.push(replyMessage); conversation.messages.push(replyMessage)
await this.conversationsCache.set(conversationKey, conversation); await this.conversationsCache.set(conversationKey, conversation)
return { return {
conversationSignature, conversationSignature,
@ -403,8 +433,8 @@ export default class SydneyAIClient {
messageId: replyMessage.id, messageId: replyMessage.id,
conversationExpiryTime, conversationExpiryTime,
response: reply.text, response: reply.text,
details: reply, details: reply
}; }
} }
/** /**
@ -414,18 +444,18 @@ export default class SydneyAIClient {
* @param parentMessageId * @param parentMessageId
* @returns {*[]} An array containing the messages in the order they should be displayed, starting with the root message. * @returns {*[]} An array containing the messages in the order they should be displayed, starting with the root message.
*/ */
static getMessagesForConversation(messages, parentMessageId) { static getMessagesForConversation (messages, parentMessageId) {
const orderedMessages = []; const orderedMessages = []
let currentMessageId = parentMessageId; let currentMessageId = parentMessageId
while (currentMessageId) { while (currentMessageId) {
const message = messages.find((m) => m.id === currentMessageId); const message = messages.find((m) => m.id === currentMessageId)
if (!message) { if (!message) {
break; break
} }
orderedMessages.unshift(message); orderedMessages.unshift(message)
currentMessageId = message.parentMessageId; currentMessageId = message.parentMessageId
} }
return orderedMessages; return orderedMessages
} }
} }

View file

@ -2,6 +2,14 @@ import { Configuration, OpenAIApi } from 'openai'
import { Config } from './config.js' import { Config } from './config.js'
import fs from 'fs' import fs from 'fs'
import { mkdirs } from './common.js' import { mkdirs } from './common.js'
let proxy
if (Config.proxy) {
try {
proxy = (await import('https-proxy-agent')).default
} catch (e) {
console.warn('未安装https-proxy-agent请在插件目录下执行pnpm add https-proxy-agent')
}
}
export async function createImage (prompt, n = 1, size = '512x512') { export async function createImage (prompt, n = 1, size = '512x512') {
const configuration = new Configuration({ const configuration = new Configuration({
@ -16,6 +24,8 @@ export async function createImage (prompt, n = 1, size = '512x512') {
n, n,
size, size,
response_format: 'b64_json' response_format: 'b64_json'
}, {
httpsAgent: Config.proxy ? proxy(Config.proxy) : null
}) })
return response.data.data?.map(pic => pic.b64_json) return response.data.data?.map(pic => pic.b64_json)
} }
@ -44,7 +54,11 @@ export async function imageVariation (imageUrl, n = 1, size = '512x512') {
fs.createReadStream(croppedFileLoc), fs.createReadStream(croppedFileLoc),
n, n,
size, size,
'b64_json' 'b64_json',
'',
{
httpsAgent: Config.proxy ? proxy(Config.proxy) : null
}
) )
if (response.status !== 200) { if (response.status !== 200) {
console.log(response.data.error) console.log(response.data.error)
@ -110,7 +124,11 @@ export async function editImage (originalImage, mask = [], prompt, num = 1, size
prompt, prompt,
num, num,
size, size,
'b64_json' 'b64_json',
'',
{
httpsAgent: Config.proxy ? proxy(Config.proxy) : null
}
) )
if (response.status !== 200) { if (response.status !== 200) {
console.log(response.data.error) console.log(response.data.error)