update Bing client

This commit is contained in:
josc146
2023-06-11 22:30:28 +08:00
parent d003d0a1a3
commit 0fd09ebca3
4 changed files with 604 additions and 12 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ export async function generateAnswersWithBingWebApi(
console.debug('mode', modelMode)
const bingAIClient = new BingAIClient({ userToken: accessToken })
const bingAIClient = new BingAIClient({ userToken: accessToken, features: { genImage: false } })
if (session.bingWeb_jailbreakConversationCache)
bingAIClient.conversationsCache.set(
session.bingWeb_jailbreakConversationId,
+2 -3
View File
@@ -60,9 +60,8 @@ export async function generateAnswersWithCustomApi(port, question, session, apiK
console.debug('json error', error)
return
}
if (data.response)
answer = data.response
if (data.response) answer = data.response
else
answer +=
data.choices[0]?.delta?.content ||
@@ -0,0 +1,512 @@
export default class BingImageCreator {
/**
* @constructor
* @param {Object} options - Options for BingImageCreator.
*/
constructor(options) {
this.setOptions(options)
}
/**
* Set options for BingImageCreator.
* @param {Object} options - Options for BingImageCreator. The format of the options is almost same as the bingAiClient options of 'node-chatgpt-api'.
*/
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
this.options = {
...this.options,
...options,
}
} else {
this.options = {
...options,
host: options.host || 'https://www.bing.com',
apipath: options.apipath || '/images/create?partner=sydney&re=1&showselective=1&sude=1',
ua:
options.ua ||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35',
xForwardedFor: this.constructor.getValidIPv4(options.xForwardedFor),
features: {
enableAnsCardSfx: true,
},
enableTelemetry: true,
telemetry: {
eventID: 'Codex',
instrumentedLinkName: 'CodexInstLink',
externalLinkName: 'CodexInstExtLink',
kSeedBase: 6500,
kSeedIncrement: 500,
instSuffix: 0,
instSuffixIncrement: 1,
},
}
}
this.apiurl = `${this.options.host}${this.options.apipath}`
this.telemetry = {
config: this.options,
currentKSeed: this.options.telemetry.kSeedBase,
instSuffix: this.options.telemetry.instSuffix,
getNextKSeed() {
// eslint-disable-next-line no-return-assign, no-sequences
return (this.currentKSeed += this.config.telemetry.kSeedIncrement), this.currentKSeed
},
getNextInstSuffix() {
// eslint-disable-next-line no-return-assign
return this.config.features.enableAnsCardSfx
? ((this.instSuffix += this.config.telemetry.instSuffixIncrement),
this.instSuffix > 1 ? `${this.instSuffix}` : '')
: ''
},
}
this.debug = this.options.debug
}
/**
* Get a valid IPv4 address string from input IP.
* @param {string} ip - A fixed IPv4 address or a range of IPv4 using CIDR notation.
* @returns {string} A valid IPv4 address or undefined.
* If 'ip' is a valid fixed IPv4 address, it returns 'ip' itself.
* If 'ip' is a range of IPv4 using CIDR notation, it returns a random address within the range.
* Otherwise, it returns undefined.
*/
static getValidIPv4(ip) {
const match =
!ip ||
ip.match(
/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/,
)
if (match) {
if (match[5]) {
const mask = parseInt(match[5], 10)
let [a, b, c, d] = ip.split('.').map((x) => parseInt(x, 10))
// eslint-disable-next-line no-bitwise
const max = (1 << (32 - mask)) - 1
const rand = Math.floor(Math.random() * max)
d += rand
c += Math.floor(d / 256)
d %= 256
b += Math.floor(c / 256)
c %= 256
a += Math.floor(b / 256)
b %= 256
return `${a}.${b}.${c}.${d}`
}
return ip
}
return undefined
}
/**
* Get fetchOptions of BingImageCreator.
* {Object} The fetch options used for BingImageCreator.
*/
get fetchOptions() {
let fetchOptions
return (
this.options.fetchOptions ??
(() => {
if (!fetchOptions) {
fetchOptions = {
headers: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'en-US,en;q=0.9',
'cache-control': 'no-cache',
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-bitness': '"64"',
'sec-ch-ua-full-version': '"113.0.1774.35"',
'sec-ch-ua-full-version-list':
'"Microsoft Edge";v="113.0.1774.35", "Chromium";v="113.0.5672.63", "Not-A.Brand";v="24.0.0.0"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-model': '""',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-platform-version': '"11.0.0"',
'sec-fetch-dest': 'iframe',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'same-origin',
cookie:
this.options.cookies ||
(this.options.userToken ? `_U=${this.options.userToken}` : undefined),
pragma: 'no-cache',
referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx',
'Referrer-Policy': 'origin-when-cross-origin',
// Workaround for request being blocked due to geolocation
...(this.options.xForwardedFor
? { 'x-forwarded-for': this.options.xForwardedFor }
: {}),
'upgrade-insecure-requests': '1',
'user-agent': this.options.ua,
'x-edge-shopping-flag': '1',
},
}
if (this.options.proxy) {
// fetchOptions.dispatcher = new ProxyAgent(this.options.proxy);
}
}
return fetchOptions
})()
)
}
/**
* Decode the HTML entities, a very lite version.
* @param {string} html - The HTML string to be decoded.
* @returns {string} Decoded string.
*/
static decodeHtmlLite(html) {
const entities = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&nbsp;': String.fromCharCode(160),
}
return html.replace(/&[a-z]+;/g, (match) => entities[match] || match)
}
/**
* Removes a specific HTML element and its corresponding closing tag from a web page string.
* @param {string} html - The web page string to be processed.
* @param {string} tag - The element tag to be removed, such as 'div'.
* @param {string} tagId - The id of the element to be removed, such as 'giloader'.
* @returns {string} A new web page string with the specified element and its closing tag removed.
*/
static removeHtmlTagLite(html, tag, tagId) {
// Create a regex, matches <tag id="tagId">, id can be at any available position.
const regex = new RegExp(`<${tag}[^>]*id="${tagId}"[^>]*>`)
// Find out the start and end position of <tag id="tagId">.
const match = regex.exec(html)
// return the original html if nothing matches.
if (!match) {
return html
}
const start = match.index
let end = match.index + match[0].length
// Count the nested tags, the initial value is 0.
let nested = 0
let i = end
let s = i - 1
let e = s
const tagStart = `<${tag} `
const tagEnd = `</${tag}>`
// loop the string, until find out its matched '</tag>'.
while (e > 0) {
if (e < i) {
e = html.indexOf(tagEnd, i)
}
if (e > 0) {
if (s > 0 && s < i) {
s = html.indexOf(tagStart, i)
}
if (s > 0) {
i = Math.min(s, e)
nested += i === s ? ((i += tagStart.length), 1) : ((i += tagEnd.length), -1)
} else {
i = e + tagEnd.length
nested -= 1
}
// If nested is -1, the matched '</tag>' is found.
if (nested === -1) {
// Update the end position, make it point to the position after </tag>.
end = i
// Break the loop;
break
}
}
}
// Remove the strings between the '<tag id="tagId">' and the matched '</tag>'.
return html.slice(0, start) + html.slice(end)
}
/**
* Delay the execution for a given time in millisecond unit.
* @param {number} ms - The time to be delayed in millisecond unit.
* @returns {Promise} A promise object that is used to wait.
*/
static sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* @typedef {Object} BicCreationResult
* @property {string} contentUrl - A URL pointing to the creation page.
* @property {string} pollingUrl - The URL to poll the image creation request.
* @property {string} contentHtml - The source code of the creation page.
* @property {string} prompt - The prompt for the image generation.
* @property {string} iframeid - The message ID refers to the image generation.
*/
/**
* Use BIC to generate images according to the given prompt and message ID.
* @param {string} prompt - The prompt for the image generation. It should be given by 'Sydney'.
* @param {string} messageId - The message ID refers to the message of 'Sydney'.
* @returns {BicCreationResult} A BicCreationResult object that contains the result of the creation.
*/
async genImagePage(prompt, messageId) {
let telemetryData = ''
if (this.options.enableTelemetry) {
telemetryData = `&kseed=${this.telemetry.getNextKSeed()}&SFX=${this.telemetry.getNextInstSuffix()}`
}
// https://www.bing.com/images/create?partner=sydney&re=1&showselective=1&sude=1&kseed=8000&SFX=3&q=${encodeURIComponent(prompt)}&iframeid=${messageId}
const url = `${this.apiurl}${telemetryData}&q=${encodeURIComponent(prompt)}${
messageId ? `&iframeid=${messageId}` : ''
}`
if (this.debug) {
console.debug(`The url of the request for image creation: ${url}`)
console.debug()
}
const response = await fetch(url, this.fetchOptions)
const { status } = response
if (this.debug) {
console.debug('The response of the request for image creation:')
console.debug(response)
console.debug()
}
if (status !== 200) {
throw new Error(`Bing Image Creator Error: response status = ${status}`)
}
const body = await response.text()
let regex = /<div id="gir" data-c="([^"]*)"/
const pollingUrl = regex.exec(body)?.[1]
if (!pollingUrl) {
regex = /<div class="gil_err_mt">(.*?)<\/div>/
const err = regex.exec(body)?.[1]
throw new Error(`Bing Image Creator Error: ${err}`)
}
return {
contentUrl: `${response.url}`,
pollingUrl: `${this.options.host}${this.constructor.decodeHtmlLite(pollingUrl)}`,
contentHtml: body,
prompt: `${prompt}`,
iframeid: `${messageId}`,
}
}
/**
* @typedef {Object} BicProgressContext
* @property {string} contentIframe - A iframe element points to the image creation page.
* Note: This parameter may or may not present, depending on the function you are currently calling
* or the stage of the function execution. For now, it's presented only when genImageIframeSsr calls
* the onProgress at the first time.
* @property {Date} pollingStartTime - The start time of the polling request.
* Note: This parameter may or may not present, depending on the function you are currently calling
* or the stage of the function execution. For now, it's presented only in any 'polling' stage callbacks.
*/
/**
* Polling the image creation request.
* @param {string} pollingUrl - The url to poll the image creation request.
* @param {function({BicProgressContext}):boolean} onProgress - A callback function that will be invoked intervally during the image generation.
* Return true to cancel creation.
* @returns {string} The result html string which contains the generated image links.
*/
async pollingImgRequest(pollingUrl, onProgress) {
let polling = true
let body
if (typeof onProgress !== 'function') {
onProgress = () => false
}
const pollingStartTime = new Date().getTime()
while (polling) {
if (this.debug) {
console.debug(`polling the image request: ${pollingUrl}`)
}
// eslint-disable-next-line no-await-in-loop
const response = await fetch(pollingUrl, this.fetchOptions)
const { status } = response
if (status !== 200) {
throw new Error(`Bing Image Creator Error: response status = ${status}`)
}
// eslint-disable-next-line no-await-in-loop
body = await response.text()
if (body && body.indexOf('errorMessage') === -1) {
polling = false
} else {
const cancelRequest = onProgress({ pollingStartTime })
if (cancelRequest) {
throw new Error('Bing Image Creator Error: cancelled')
}
// eslint-disable-next-line no-await-in-loop
await this.constructor.sleep(1000)
}
}
return body
}
/**
* Get a list of the generated images.
* @param {string} prompt - The prompt for the image generation. It should be given by 'Sydney'.
* @param {string} messageId - The message ID refers to the message of 'Sydney'.
* @param {boolean} removeSizeLimit - Set it to true to remove the parameters according to the sizes from the reslut image links.
* @param {function({BicProgressContext}):boolean} onProgress - A callback function that will be invoked intervally during the image generation.
* Return true to cancel creation.
* @returns {string[]} An array containing the url strings of the generated images.
*/
async genImageList(prompt, messageId, removeSizeLimit, onProgress) {
const { pollingUrl } = await this.genImagePage(prompt, messageId)
const resultHtml = await this.pollingImgRequest(pollingUrl, onProgress)
if (this.debug) {
console.debug('The result of the request for image creation:')
console.debug(resultHtml)
console.debug()
}
const regex = /(?<=src=")[^"]+(?=")/g
return Array.from(resultHtml.matchAll(regex), (match) =>
(() => {
const l = this.constructor.decodeHtmlLite(match[0])
return removeSizeLimit ? l.split('?w=')[0] : l
})(),
)
}
/**
* Create a html iframe element with the given src or srcdoc if isDoc is set to true.
* @param {string} src
* @param {boolean} isDoc
* @returns {string} The html string of the iframe created.
*/
createImageIframe(src, isDoc) {
return (
'<iframe role="presentation" style="position:relative;overflow:hidden;width:475px;height:520px;' +
'border:none;outline:none;padding:0px;margin:0px;display:flex;align-self:flex-start;border-radius:12px;' +
'box-shadow:0px 0.3px 0.9px rgba(0, 0, 0, 0.12), 0px 1.6px 3.6px rgba(0, 0, 0, 0.16);z-index: 1;" ' +
`${isDoc ? `srcdoc='${this.rewriteHtml(src)}'` : `src="${src}"`} />`
)
}
/**
* Rewrite the html by replacing the relative path with the absolute path and escaping the "'".
* @param {string} html
* @returns {string} The rewritten html.
*/
rewriteHtml(html) {
return html.replace(/'/g, '&#39;').replace(/="\//g, `="${this.options.host}/`)
}
/**
* Mix the the container page and the result page, and 'render' them together into an iframe.
* @param {string} containerHtml - The container page's html string.
* @param {string} resultHtml - The result page's html string.
* @returns {string} The html string of the iframe created.
*/
renderImageIframe(containerHtml, resultHtml) {
// "Render" it fastly.
// Note: It is heavily hard-coded and may break in future upgrades of the BingAI.
const renderedHtml = this.constructor
.removeHtmlTagLite(containerHtml, 'div', 'giloader')
.replace(/<div([^>]*)id="giric"([^>]*)>/, (match, group1, group2) => {
if (group1.indexOf(' style="') === -1 && group2.indexOf(' style="') === -1) {
return `<div${group1}id="giric"${group2} style="display: block;">`
}
return match
})
.replace(/(?<=<div[^>]*?id="giric"[^>]*?>)[\s\S]*?(?=<\/div>)/, `${resultHtml}`)
return this.createImageIframe(renderedHtml, true)
}
/**
* Create a server side render iframe which uses 'srcdoc' attribute to hold the rendered result page.
* Unlike genImageIframeSsrLite, it returns an iframe that contains the full content of the result page
* just like the original bing browser client does.
* @param {string} prompt - The prompt for the image generation. It should be given by 'Sydney'.
* @param {string} messageId - The message ID refers to the message of 'Sydney'.
* @param {function({BicProgressContext}):boolean} onProgress - A callback function that will be invoked intervally during the image generation.
* Return true to cancel creation.
* @returns {string}
*/
async genImageIframeSsr(prompt, messageId, onProgress) {
const { contentUrl, pollingUrl, contentHtml } = await this.genImagePage(prompt, messageId)
if (typeof onProgress === 'function') {
const cancelRequest = onProgress({ contentIframe: this.createImageIframe(contentUrl) })
if (cancelRequest) {
throw new Error('Bing Image Creator Error: cancelled')
}
}
const resultHtml = await this.pollingImgRequest(pollingUrl, onProgress)
return this.renderImageIframe(contentHtml, resultHtml)
}
/**
* Create a server side render iframe which uses 'srcdoc' attribute to hold the rendered result page.
* Unlike genImageIframeSsr, it returns an iframe that only contains the content of the image result page.
* @param {string} prompt - The prompt for the image generation. It should be given by 'Sydney'.
* @param {string} messageId - The message ID refers to the message of 'Sydney'.
* @param {function({BicProgressContext}):boolean} onProgress - A callback function that will be invoked intervally during the image generation.
* Return true to cancel creation.
* @returns {string} The html string of the iframe created.
*/
async genImageIframeSsrLite(prompt, messageId, onProgress) {
const { pollingUrl } = await this.genImagePage(prompt, messageId)
const resultHtml = await this.pollingImgRequest(pollingUrl, onProgress)
return this.createImageIframe(resultHtml, true)
}
/**
* Create a client side render iframe which just points to the image creation page.
* Note: If this element is returned to client side, the client must be logged in
* to bing.com in order to generate the image successfully. The user's cookie is
* required for the polling requests of the generation process.
* @param prompt {string} - The prompt for the image generation. It should be given by 'Sydney'.
* @param messageId {string} - The message ID refers to the message of 'Sydney'.
* @returns {string} The html string of the iframe created.
*/
async genImageIframeCsr(prompt, messageId) {
const { contentUrl } = await this.genImagePage(prompt, messageId)
return this.createImageIframe(contentUrl)
}
/**
* The pattern to match the inline image generation request.
*/
static get inlineImagePattern() {
return /!\[(.*?)\]\(#generative_image\)/g
}
/**
* Why is there such a function here? I have seen the messages with inline generative image style at a converation with bing, but only once.
* The message contains a markdown tag like '![prompt](#generative_image)', and can appear at the middle or end of the message.
* After starting a new conversation, I couldn't reproduce it anymore. Of course I tried various methods, but none of them works.
* Maybe it's a new function still in testing.
* Parse the message object or text, return the prompt for generative image if it exists.
* @param {string|object} message - The message to parese.
* @returns {string} The prompt for inline image generation request found in message, or undefined if it is not found.
*/
static parseInlineGenerativeImage(message) {
if (typeof message !== 'string') {
message = message.text
}
const match = BingImageCreator.inlineImagePattern.exec(message)
if (match) {
return match[1]
}
return undefined
}
}
+89 -8
View File
@@ -1,6 +1,7 @@
// https://github.com/waylaidwanderer/node-chatgpt-api
import { v4 as uuidv4 } from 'uuid'
import BingImageCreator from './BingImageCreator'
/**
* https://stackoverflow.com/a/58326357
@@ -30,9 +31,43 @@ export default class BingAIClient {
this.options = {
...options,
host: options.host || 'https://www.bing.com',
xForwardedFor: this.constructor.getValidIPv4(options.xForwardedFor),
features: {
genImage: options?.features?.genImage || false,
},
}
}
this.debug = this.options.debug
if (this.options.features.genImage) {
this.bic = new BingImageCreator(this.options)
}
}
static getValidIPv4(ip) {
const match =
!ip ||
ip.match(
/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/,
)
if (match) {
if (match[5]) {
const mask = parseInt(match[5], 10)
let [a, b, c, d] = ip.split('.').map((x) => parseInt(x, 10))
// eslint-disable-next-line no-bitwise
const max = (1 << (32 - mask)) - 1
const rand = Math.floor(Math.random() * max)
d += rand
c += Math.floor(d / 256)
d %= 256
b += Math.floor(c / 256)
c %= 256
a += Math.floor(b / 256)
b %= 256
return `${a}.${b}.${c}.${d}`
}
return ip
}
return undefined
}
async createNewConversation() {
@@ -41,12 +76,12 @@ export default class BingAIClient {
accept: 'application/json',
'accept-language': 'en-US,en;q=0.9',
'content-type': 'application/json',
'sec-ch-ua': '"Chromium";v="112", "Microsoft Edge";v="112", "Not:A-Brand";v="99"',
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-bitness': '"64"',
'sec-ch-ua-full-version': '"112.0.1722.7"',
'sec-ch-ua-full-version': '"113.0.1774.50"',
'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"',
'"Microsoft Edge";v="113.0.1774.50", "Chromium";v="113.0.5672.127", "Not-A.Brand";v="24.0.0.0"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-model': '""',
'sec-ch-ua-platform': '"Windows"',
@@ -54,14 +89,21 @@ export default class BingAIClient {
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'sec-ms-gec': genRanHex(64).toUpperCase(),
'sec-ms-gec-version': '1-115.0.1866.1',
'x-ms-client-request-id': uuidv4(),
'x-ms-useragent':
'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50',
cookie:
this.options.cookies ||
(this.options.userToken ? `_U=${this.options.userToken}` : undefined),
Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx',
Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1',
'Referrer-Policy': 'origin-when-cross-origin',
// Workaround for request being blocked due to geolocation
// 'x-forwarded-for': '1.1.1.1', // 1.1.1.1 seems to no longer work.
...(this.options.xForwardedFor ? { 'x-forwarded-for': this.options.xForwardedFor } : {}),
},
}
if (this.options.proxy) {
@@ -310,13 +352,16 @@ export default class BingAIClient {
'cricinfov2',
'dv3sugg',
'nojbfedge',
...(toneStyle === 'creative' && this.options.features.genImage ? ['gencontentv3'] : []),
],
sliceIds: ['222dtappid', '225cricinfo', '224locals0'],
traceId: genRanHex(32),
isStartOfSession: invocationId === 0,
message: {
author: 'user',
text: message,
text: jailbreakConversationId
? 'Continue the conversation in context. Assistant:'
: message,
messageType: jailbreakConversationId ? 'SearchQuery' : 'Chat',
},
conversationSignature,
@@ -369,7 +414,7 @@ export default class BingAIClient {
'Timed out waiting for response. Try enabling debug mode to see more information.',
),
)
}, 180 * 1000)
}, 300 * 1000)
// abort the request if the abort controller is aborted
abortController.signal.addEventListener('abort', () => {
@@ -378,7 +423,8 @@ export default class BingAIClient {
reject(new Error('Request aborted'))
})
ws.onmessage = (e) => {
let bicIframe
ws.onmessage = async (e) => {
const data = e.data
const objects = data.toString().split('')
const events = objects
@@ -403,6 +449,19 @@ export default class BingAIClient {
if (!messages?.length || messages[0].author !== 'bot') {
return
}
if (messages[0]?.contentType === 'IMAGE') {
// You will never get a message of this type without 'gencontentv3' being on.
bicIframe = this.bic
.genImageIframeSsr(messages[0].text, messages[0].messageId, (progress) =>
progress?.contentIframe ? onProgress(progress?.contentIframe) : null,
)
.catch((error) => {
onProgress(error.message)
bicIframe.isError = true
return error.message
})
return
}
const updatedText = messages[0].text
if (!updatedText || updatedText === replySoFar) {
return
@@ -427,7 +486,7 @@ export default class BingAIClient {
return
}
const messages = event.item?.messages || []
const eventMessage = messages.length ? messages[messages.length - 1] : null
let eventMessage = messages.length ? messages[messages.length - 1] : null
if (event.item?.result?.error) {
if (this.debug) {
console.debug(event.item.result.value, event.item.result.message)
@@ -472,6 +531,23 @@ export default class BingAIClient {
// delete useless suggestions from moderation filter
delete eventMessage.suggestedResponses
}
if (bicIframe) {
// the last messages will be a image creation event if bicIframe is present.
let i = messages.length - 1
while (eventMessage?.contentType === 'IMAGE' && i > 0) {
eventMessage = messages[(i -= 1)]
}
// wait for bicIframe to be completed.
// since we added a catch, we do not need to wrap this with a try catch block.
const imgIframe = await bicIframe
if (!imgIframe?.isError) {
eventMessage.adaptiveCards[0].body[0].text += imgIframe
} else {
eventMessage.text += `<br>${imgIframe}`
eventMessage.adaptiveCards[0].body[0].text = eventMessage.text
}
}
resolve({
message: eventMessage,
conversationExpiryTime: event?.item?.conversationExpiryTime,
@@ -488,6 +564,11 @@ export default class BingAIClient {
return
}
default:
if (event?.error) {
clearTimeout(messageTimeout)
this.constructor.cleanupWebSocketConnection(ws)
reject(new Error(`Event Type('${event.type}'): ${event.error}`))
}
// eslint-disable-next-line no-useless-return
return
}