fix: api3传输形式优化

This commit is contained in:
ikechan8370 2023-04-04 16:08:14 +08:00
parent 820cf76eaf
commit ec82629238
2 changed files with 100 additions and 155 deletions

View file

@ -6,6 +6,8 @@
"@waylaidwanderer/chatgpt-api": "^1.33.2", "@waylaidwanderer/chatgpt-api": "^1.33.2",
"chatgpt": "^5.1.1", "chatgpt": "^5.1.1",
"delay": "^5.0.0", "delay": "^5.0.0",
"eventsource": "^2.0.2",
"http": "0.0.1-security",
"https-proxy-agent": "5.0.1", "https-proxy-agent": "5.0.1",
"keyv": "^4.5.2", "keyv": "^4.5.2",
"keyv-file": "^0.2.0", "keyv-file": "^0.2.0",
@ -14,10 +16,10 @@
"random": "^4.1.0", "random": "^4.1.0",
"undici": "^5.21.0", "undici": "^5.21.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"ws": "^8.13.0" "ws": "^8.13.0",
"eventsource-parser": "^1.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"eventsource-parser": "^1.0.0",
"jimp": "^0.22.7", "jimp": "^0.22.7",
"puppeteer-extra": "^3.3.6", "puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-recaptcha": "^3.6.8", "puppeteer-extra-plugin-recaptcha": "^3.6.8",

View file

@ -1,62 +1,27 @@
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { Config, officialChatGPTAPI } from './config.js' import { Config, officialChatGPTAPI } from './config.js'
import fetch from 'node-fetch' import https from 'https'
import delay from 'delay' import http from 'http'
import _ from 'lodash' import { createParser } from 'eventsource-parser'
// import { createParser } from 'eventsource-parser'
let createParser
try {
createParser = (await import('eventsource-parser')).createParser
} catch (e) {
console.warn('未安装eventsource-parser请在插件目录下执行pnpm i')
}
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')
}
}
// API3 // API3
export class OfficialChatGPTClient { export class OfficialChatGPTClient {
constructor (opts = {}) { constructor (opts = {}) {
const { const {
accessToken, accessToken,
apiReverseUrl, apiReverseUrl
timeoutMs
} = opts } = opts
this._accessToken = accessToken this._accessToken = accessToken
this._apiReverseUrl = apiReverseUrl this._apiReverseUrl = apiReverseUrl
this._timeoutMs = timeoutMs
this._fetch = (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
}
} }
async sendMessage (prompt, opts = {}) { async sendMessage (prompt, opts = {}) {
let { let {
timeoutMs = this._timeoutMs,
conversationId, conversationId,
parentMessageId = uuidv4(), parentMessageId = uuidv4(),
messageId = uuidv4(), messageId = uuidv4(),
action = 'next' action = 'next'
} = opts } = opts
let abortController = null
if (timeoutMs) {
abortController = new AbortController()
}
let url = this._apiReverseUrl || officialChatGPTAPI let url = this._apiReverseUrl || officialChatGPTAPI
if (this._apiReverseUrl && Config.proxy && !Config.apiForceUseReverse) { if (this._apiReverseUrl && Config.proxy && !Config.apiForceUseReverse) {
// 如果配了proxy而且有反代但是没开启强制反代 // 如果配了proxy而且有反代但是没开启强制反代
@ -80,96 +45,102 @@ export class OfficialChatGPTClient {
if (conversationId) { if (conversationId) {
body.conversation_id = conversationId body.conversation_id = conversationId
} }
let option = { let conversationResponse
method: 'POST', let statusCode
body: JSON.stringify(body), let requestP = new Promise((resolve, reject) => {
signal: abortController?.signal, let option = {
headers: { method: 'POST',
accept: 'text/event-stream', headers: {
'x-openai-assistant-app-id': '', accept: 'text/event-stream',
authorization: `Bearer ${this._accessToken}`, 'x-openai-assistant-app-id': '',
'content-type': 'application/json', authorization: `Bearer ${this._accessToken}`,
referer: 'https://chat.openai.com/chat', 'content-type': 'application/json',
library: 'chatgpt-plugin' referer: 'https://chat.openai.com/chat',
}, library: 'chatgpt-plugin'
referrer: 'https://chat.openai.com/chat' },
} referrer: 'https://chat.openai.com/chat'
const res = await this._fetch(url, option)
if (res.status === 403) {
await delay(500)
return await this.sendMessage(prompt, opts)
}
if (res.status !== 200) {
let body = await res.text()
if (body.indexOf('Conversation not found') > -1) {
throw new Error('对话不存在,请使用指令”#结束对话“结束当前对话后重新开始对话。')
} else {
throw new Error(body)
} }
} let requestLib = url.startsWith('https') ? https : http
if (createParser) { const req = requestLib.request(url, option, (res) => {
let conversationResponse statusCode = res.statusCode
const responseP = new Promise( let response
// eslint-disable-next-line no-async-promise-executor function onMessage (data) {
async (resolve, reject) => { if (data === '[DONE]') {
let response return resolve({
function onMessage (data) { error: null,
if (data === '[DONE]') { response,
return resolve({ conversationId,
error: null, messageId,
response, conversationResponse
conversationId, })
messageId,
conversationResponse
})
}
try {
const _checkJson = JSON.parse(data)
} catch (error) {
// console.log('warning: parse error.')
return
}
try {
const convoResponseEvent = JSON.parse(data)
conversationResponse = convoResponseEvent
if (convoResponseEvent.conversation_id) {
conversationId = convoResponseEvent.conversation_id
}
if (convoResponseEvent.message?.id) {
messageId = convoResponseEvent.message.id
}
const partialResponse =
convoResponseEvent.message?.content?.parts?.[0]
if (partialResponse) {
if (Config.debug) {
logger.info(JSON.stringify(convoResponseEvent))
}
response = partialResponse
}
} catch (err) {
console.warn('fetchSSE onMessage unexpected error', err)
reject(err)
}
} }
try {
const _checkJson = JSON.parse(data)
} catch (error) {
// console.log('warning: parse error.')
return
}
try {
const convoResponseEvent = JSON.parse(data)
conversationResponse = convoResponseEvent
if (convoResponseEvent.conversation_id) {
conversationId = convoResponseEvent.conversation_id
}
const parser = createParser((event) => { if (convoResponseEvent.message?.id) {
if (event.type === 'event') { messageId = convoResponseEvent.message.id
onMessage(event.data)
} }
})
res.body.on('readable', async () => { const partialResponse =
logger.mark('成功连接到chat.openai.com准备读取数据流') convoResponseEvent.message?.content?.parts?.[0]
let chunk if (partialResponse) {
while ((chunk = res.body.read()) !== null) { if (Config.debug) {
let str = chunk.toString() logger.info(JSON.stringify(convoResponseEvent))
parser.feed(str) }
response = partialResponse
} }
}) } catch (err) {
console.warn('fetchSSE onMessage unexpected error', err)
reject(err)
}
} }
)
let response = await responseP const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
const errBody = []
res.on('data', (chunk) => {
// logger.mark('成功连接到chat.openai.com准备读取数据流')
if (statusCode === 200) {
let str = chunk.toString()
parser.feed(str)
}
errBody.push(chunk)
})
// const body = []
// res.on('data', (chunk) => body.push(chunk))
res.on('end', () => {
const resString = Buffer.concat(errBody).toString()
reject(resString)
})
})
req.on('error', (err) => {
reject(err)
})
req.on('timeout', () => {
req.destroy()
reject(new Error('Request time out'))
})
req.write(JSON.stringify(body))
req.end()
})
const response = await requestP
if (statusCode === 200) {
return { return {
text: response.response, text: response.response,
conversationId: response.conversationId, conversationId: response.conversationId,
@ -177,36 +148,8 @@ export class OfficialChatGPTClient {
parentMessageId parentMessageId
} }
} else { } else {
logger.warn('未安装eventsource-parser强烈建议安装以提高API3响应性能在插件目录下执行pnpm i或pnpm add -w eventsource-parser') console.log(response)
const decoder = new TextDecoder('utf-8') throw new Error(response)
const bodyBytes = await res.arrayBuffer()
const bodyText = decoder.decode(bodyBytes)
const events = bodyText.split('\n\n').filter(item => !_.isEmpty(item))
let fullResponse
for (let i = 0; i < events.length; i++) {
let event = events[i]
event = _.trimStart(event, 'data: ')
try {
let tmp = JSON.parse(event)
if (tmp.message) {
fullResponse = tmp
}
} catch (err) {
// console.log(event)
}
}
if (Config.debug) {
logger.mark(JSON.stringify(fullResponse))
}
if (!fullResponse?.message) {
throw new Error(bodyText || 'unkown error, please check log')
}
return {
text: fullResponse.message.content.parts[0],
conversationId: fullResponse.conversation_id,
id: fullResponse.message.id,
parentMessageId
}
} }
} }
} }