chatgpt-plugin/utils/message.js
2023-03-30 11:39:30 +08:00

212 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { v4 as uuidv4 } from 'uuid'
import { Config, officialChatGPTAPI } from './config.js'
import fetch from 'node-fetch'
import delay from 'delay'
import _ from 'lodash'
// 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
export class OfficialChatGPTClient {
constructor (opts = {}) {
const {
accessToken,
apiReverseUrl,
timeoutMs
} = opts
this._accessToken = accessToken
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 = {}) {
let {
timeoutMs = this._timeoutMs,
conversationId,
parentMessageId = uuidv4(),
messageId = uuidv4(),
action = 'next'
} = opts
let abortController = null
if (timeoutMs) {
abortController = new AbortController()
}
let url = this._apiReverseUrl || officialChatGPTAPI
if (this._apiReverseUrl && Config.proxy && !Config.apiForceUseReverse) {
// 如果配了proxy而且有反代但是没开启强制反代
url = officialChatGPTAPI
}
const body = {
action,
messages: [
{
id: messageId,
role: 'user',
content: {
content_type: 'text',
parts: [prompt]
}
}
],
model: Config.useGPT4 ? 'gpt-4' : 'text-davinci-002-render-sha',
parent_message_id: parentMessageId
}
if (conversationId) {
body.conversation_id = conversationId
}
let option = {
method: 'POST',
body: JSON.stringify(body),
signal: abortController?.signal,
headers: {
accept: 'text/event-stream',
'x-openai-assistant-app-id': '',
authorization: `Bearer ${this._accessToken}`,
'content-type': 'application/json',
referer: 'https://chat.openai.com/chat',
library: 'chatgpt-plugin'
},
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)
}
}
if (createParser) {
let conversationResponse
const responseP = new Promise(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
let response
function onMessage (data) {
if (data === '[DONE]') {
return resolve({
error: null,
response,
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)
}
}
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
res.body.on('readable', async () => {
logger.mark('成功连接到chat.openai.com准备读取数据流')
let chunk
while ((chunk = res.body.read()) !== null) {
let str = chunk.toString()
parser.feed(str)
}
})
}
)
let response = await responseP
return {
text: response.response,
conversationId: response.conversationId,
id: response.messageId,
parentMessageId
}
} else {
logger.warn('未安装eventsource-parser强烈建议安装以提高API3响应性能在插件目录下执行pnpm i或pnpm add -w eventsource-parser')
const decoder = new TextDecoder('utf-8')
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
}
}
}
}