ngoctuanai's picture
Upload 142 files
c742717
raw
history blame
8.11 kB
import * as dotenv from 'dotenv'
import 'isomorphic-fetch'
import type { ChatGPTAPIOptions, ChatMessage, SendMessageOptions } from 'chatgpt'
import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
import { SocksProxyAgent } from 'socks-proxy-agent'
import httpsProxyAgent from 'https-proxy-agent'
import fetch from 'node-fetch'
import { sendResponse } from '../utils'
import { isNotEmptyString } from '../utils/is'
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'
import type { RequestOptions, SetProxyOptions, UsageResponse } from './types'
const { HttpsProxyAgent } = httpsProxyAgent
dotenv.config()
const ErrorCodeMessage: Record<string, string> = {
401: '[OpenAI] Đã cung cấp khóa API không chính xác',
403: '[OpenAI] Máy chủ từ chối truy cập, vui lòng thử lại sau',
502: '[OpenAI] Cổng xấu',
503: '[OpenAI] Máy chủ đang bận, vui lòng thử lại sau',
504: '[OpenAI] Hết thời gian yêu cầu',
500: '[OpenAI] Lỗi máy chủ nội bộ',
429: '[OpenAI] Máy chủ quá tải',
}
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 100 * 1000
const disableDebug: boolean = process.env.OPENAI_API_DISABLE_DEBUG === 'true'
let apiModel: ApiModel
const model = isNotEmptyString(process.env.OPENAI_API_MODEL) ? process.env.OPENAI_API_MODEL : 'gpt-3.5-turbo'
if (!isNotEmptyString(process.env.OPENAI_API_KEY) && !isNotEmptyString(process.env.OPENAI_ACCESS_TOKEN))
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
(async () => {
// More Info: https://github.com/transitive-bullshit/chatgpt-api
if (isNotEmptyString(process.env.OPENAI_API_KEY)) {
const OPENAI_API_BASE_URL = process.env.OPENAI_API_BASE_URL
let randomApiKey = process.env.OPENAI_API_KEY;
if (isNotEmptyString(process.env.OPENAI_API_KEY_ARR)){
const OPENAI_API_KEY_ARR = JSON.parse(process.env.OPENAI_API_KEY_ARR);
const randomIndex = Math.floor(Math.random() * OPENAI_API_KEY_ARR.length);
randomApiKey = OPENAI_API_KEY_ARR[randomIndex];
}
const options: ChatGPTAPIOptions = {
apiKey: randomApiKey,
completionParams: { model },
debug: !disableDebug,
}
// increase max token limit if use gpt-4
if (model.toLowerCase().includes('gpt-4')) {
// if use 32k model
if (model.toLowerCase().includes('32k')) {
options.maxModelTokens = 32768
options.maxResponseTokens = 8192
}
else {
options.maxModelTokens = 8192
options.maxResponseTokens = 2048
}
}
if (isNotEmptyString(OPENAI_API_BASE_URL))
options.apiBaseUrl = `${OPENAI_API_BASE_URL}/v1`
setupProxy(options)
api = new ChatGPTAPI({ ...options })
apiModel = 'ChatGPTAPI'
}
else {
console.log('OPENAI_ACCESS_TOKEN',OPENAI_ACCESS_TOKEN);
const options: ChatGPTUnofficialProxyAPIOptions = {
accessToken: process.env.OPENAI_ACCESS_TOKEN,
apiReverseProxyUrl: isNotEmptyString(process.env.API_REVERSE_PROXY) ? process.env.API_REVERSE_PROXY : 'https://ai.fakeopen.com/api/conversation',
model,
debug: !disableDebug,
}
setupProxy(options)
api = new ChatGPTUnofficialProxyAPI({ ...options })
apiModel = 'ChatGPTUnofficialProxyAPI'
}
})()
async function chatReplyProcess(options: RequestOptions) {
const { message, lastContext, process, systemMessage, temperature, top_p } = options
try {
let options: SendMessageOptions = { timeoutMs }
if (apiModel === 'ChatGPTAPI') {
if (isNotEmptyString(systemMessage))
options.systemMessage = systemMessage
options.completionParams = { model, temperature, top_p }
}
if (lastContext != null) {
if (apiModel === 'ChatGPTAPI')
options.parentMessageId = lastContext.parentMessageId
else
options = { ...lastContext }
}
const response = await api.sendMessage(message, {
...options,
onProgress: (partialResponse) => {
process?.(partialResponse)
},
})
return sendResponse({ type: 'Success', data: response })
}
catch (error: any) {
const code = error.statusCode
global.console.log(error)
if (Reflect.has(ErrorCodeMessage, code))
return sendResponse({ type: 'Fail', message: ErrorCodeMessage[code] })
return sendResponse({ type: 'Fail', message: error.message ?? 'Please check the back-end console' })
}
}
async function fetchUsage() {
let OPENAI_API_KEY = process.env.OPENAI_API_KEY
const OPENAI_API_BASE_URL = process.env.OPENAI_API_BASE_URL
if (isNotEmptyString(process.env.OPENAI_API_KEY_ARR)){
const OPENAI_API_KEY_ARR = JSON.parse(process.env.OPENAI_API_KEY_ARR);
const randomIndex = Math.floor(Math.random() * OPENAI_API_KEY_ARR.length);
OPENAI_API_KEY = OPENAI_API_KEY_ARR[randomIndex];
}
if (!isNotEmptyString(OPENAI_API_KEY))
return Promise.resolve('-')
const API_BASE_URL = isNotEmptyString(OPENAI_API_BASE_URL)
? OPENAI_API_BASE_URL
: 'https://api.openai.com'
const [startDate, endDate] = formatDate()
// 每月使用量
const urlUsage = `${API_BASE_URL}/v1/dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`
const headers = {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
}
const options = {} as SetProxyOptions
setupProxy(options)
try {
// 获取已使用量
const useResponse = await options.fetch(urlUsage, { headers })
if (!useResponse.ok)
throw new Error('获取使用量失败')
const usageData = await useResponse.json() as UsageResponse
const usage = Math.round(usageData.total_usage) / 100
return Promise.resolve(usage ? `$${usage}` : '-')
}
catch (error) {
global.console.log(error)
return Promise.resolve('-')
}
}
function formatDate(): string[] {
const today = new Date()
const year = today.getFullYear()
const month = today.getMonth() + 1
const lastDay = new Date(year, month, 0)
const formattedFirstDay = `${year}-${month.toString().padStart(2, '0')}-01`
const formattedLastDay = `${year}-${month.toString().padStart(2, '0')}-${lastDay.getDate().toString().padStart(2, '0')}`
return [formattedFirstDay, formattedLastDay]
}
async function chatConfig() {
const usage = await fetchUsage()
const reverseProxy = process.env.API_REVERSE_PROXY ?? '-'
const httpsProxy = (process.env.HTTPS_PROXY || process.env.ALL_PROXY) ?? '-'
const socksProxy = (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT)
? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`)
: '-'
return sendResponse<ModelConfig>({
type: 'Success',
data: { apiModel, reverseProxy, timeoutMs, socksProxy, httpsProxy, usage },
})
}
function setupProxy(options: SetProxyOptions) {
if (isNotEmptyString(process.env.SOCKS_PROXY_HOST) && isNotEmptyString(process.env.SOCKS_PROXY_PORT)) {
const agent = new SocksProxyAgent({
hostname: process.env.SOCKS_PROXY_HOST,
port: process.env.SOCKS_PROXY_PORT,
userId: isNotEmptyString(process.env.SOCKS_PROXY_USERNAME) ? process.env.SOCKS_PROXY_USERNAME : undefined,
password: isNotEmptyString(process.env.SOCKS_PROXY_PASSWORD) ? process.env.SOCKS_PROXY_PASSWORD : undefined,
})
options.fetch = (url, options) => {
return fetch(url, { agent, ...options })
}
}
else if (isNotEmptyString(process.env.HTTPS_PROXY) || isNotEmptyString(process.env.ALL_PROXY)) {
const httpsProxy = process.env.HTTPS_PROXY || process.env.ALL_PROXY
if (httpsProxy) {
const agent = new HttpsProxyAgent(httpsProxy)
options.fetch = (url, options) => {
return fetch(url, { agent, ...options })
}
}
}
else {
options.fetch = (url, options) => {
return fetch(url, { ...options })
}
}
}
function currentModel(): ApiModel {
return apiModel
}
export type { ChatContext, ChatMessage }
export { chatReplyProcess, chatConfig, currentModel }