@@ -5,14 +5,14 @@ import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
55import { SocksProxyAgent } from 'socks-proxy-agent'
66import httpsProxyAgent from 'https-proxy-agent'
77import fetch from 'node-fetch'
8- import type { AuditConfig , CHATMODEL } from 'src/storage/model'
8+ import type { AuditConfig , CHATMODEL , KeyConfig , UserInfo } from 'src/storage/model'
99import jwt_decode from 'jwt-decode'
1010import dayjs from 'dayjs'
1111import type { TextAuditService } from '../utils/textAudit'
1212import { textAuditServices } from '../utils/textAudit'
13- import { getCacheConfig , getOriginConfig } from '../storage/config'
13+ import { getCacheApiKeys , getCacheConfig , getOriginConfig } from '../storage/config'
1414import { sendResponse } from '../utils'
15- import { isNotEmptyString } from '../utils/is'
15+ import { hasAnyRole , isNotEmptyString } from '../utils/is'
1616import type { ChatContext , ChatGPTUnofficialProxyAPIOptions , JWT , ModelConfig } from '../types'
1717import { getChatByMessageId } from '../storage/mongo'
1818import type { RequestOptions } from './types'
@@ -32,20 +32,17 @@ const ErrorCodeMessage: Record<string, string> = {
3232
3333let auditService : TextAuditService
3434
35- export async function initApi ( chatModel : CHATMODEL ) {
35+ export async function initApi ( key : KeyConfig , chatModel : CHATMODEL ) {
3636 // More Info: https://github.com/transitive-bullshit/chatgpt-api
3737
3838 const config = await getCacheConfig ( )
39- if ( ! config . apiKey && ! config . accessToken )
40- throw new Error ( 'Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable' )
41-
4239 const model = chatModel as string
4340
44- if ( config . apiModel === 'ChatGPTAPI' ) {
41+ if ( key . keyModel === 'ChatGPTAPI' ) {
4542 const OPENAI_API_BASE_URL = config . apiBaseUrl
4643
4744 const options : ChatGPTAPIOptions = {
48- apiKey : config . apiKey ,
45+ apiKey : key . key ,
4946 completionParams : { model } ,
5047 debug : ! config . apiDisableDebug ,
5148 messageStore : undefined ,
@@ -73,7 +70,7 @@ export async function initApi(chatModel: CHATMODEL) {
7370 }
7471 else {
7572 const options : ChatGPTUnofficialProxyAPIOptions = {
76- accessToken : config . accessToken ,
73+ accessToken : key . key ,
7774 apiReverseProxyUrl : isNotEmptyString ( config . reverseProxy ) ? config . reverseProxy : 'https://ai.fakeopen.com/api/conversation' ,
7875 model,
7976 debug : ! config . apiDisableDebug ,
@@ -86,27 +83,30 @@ export async function initApi(chatModel: CHATMODEL) {
8683}
8784
8885async function chatReplyProcess ( options : RequestOptions ) {
89- const config = await getCacheConfig ( )
9086 const model = options . chatModel
87+ const key = options . key
88+ if ( key == null || key === undefined )
89+ throw new Error ( '没有可用的配置。请再试一次 | No available configuration. Please try again.' )
90+
9191 const { message, lastContext, process, systemMessage, temperature, top_p } = options
9292
9393 try {
9494 const timeoutMs = ( await getCacheConfig ( ) ) . timeoutMs
9595 let options : SendMessageOptions = { timeoutMs }
9696
97- if ( config . apiModel === 'ChatGPTAPI' ) {
97+ if ( key . keyModel === 'ChatGPTAPI' ) {
9898 if ( isNotEmptyString ( systemMessage ) )
9999 options . systemMessage = systemMessage
100100 options . completionParams = { model, temperature, top_p }
101101 }
102102
103103 if ( lastContext != null ) {
104- if ( config . apiModel === 'ChatGPTAPI' )
104+ if ( key . keyModel === 'ChatGPTAPI' )
105105 options . parentMessageId = lastContext . parentMessageId
106106 else
107107 options = { ...lastContext }
108108 }
109- const api = await initApi ( model )
109+ const api = await initApi ( key , model )
110110 const response = await api . sendMessage ( message , {
111111 ...options ,
112112 onProgress : ( partialResponse ) => {
@@ -123,6 +123,9 @@ async function chatReplyProcess(options: RequestOptions) {
123123 return sendResponse ( { type : 'Fail' , message : ErrorCodeMessage [ code ] } )
124124 return sendResponse ( { type : 'Fail' , message : error . message ?? 'Please check the back-end console' } )
125125 }
126+ finally {
127+ releaseApiKey ( key )
128+ }
126129}
127130
128131export function initAuditService ( audit : AuditConfig ) {
@@ -304,6 +307,39 @@ async function getMessageById(id: string): Promise<ChatMessage | undefined> {
304307 else { return undefined }
305308}
306309
310+ const _lockedKeys : string [ ] = [ ]
311+ async function randomKeyConfig ( keys : KeyConfig [ ] ) : Promise < KeyConfig | null > {
312+ if ( keys . length <= 0 )
313+ return null
314+ let unsedKeys = keys . filter ( d => ! _lockedKeys . includes ( d . key ) )
315+ const start = Date . now ( )
316+ while ( unsedKeys . length <= 0 ) {
317+ if ( Date . now ( ) - start > 3000 )
318+ break
319+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
320+ unsedKeys = keys . filter ( d => ! _lockedKeys . includes ( d . key ) )
321+ }
322+ if ( unsedKeys . length <= 0 )
323+ return null
324+ const thisKey = unsedKeys [ Math . floor ( Math . random ( ) * unsedKeys . length ) ]
325+ _lockedKeys . push ( thisKey . key )
326+ return thisKey
327+ }
328+
329+ async function getRandomApiKey ( user : UserInfo ) : Promise < KeyConfig | undefined > {
330+ const keys = ( await getCacheApiKeys ( ) ) . filter ( d => hasAnyRole ( d . userRoles , user . roles ) )
331+ return randomKeyConfig ( keys )
332+ }
333+
334+ async function releaseApiKey ( key : KeyConfig ) {
335+ if ( key == null || key === undefined )
336+ return
337+
338+ const index = _lockedKeys . indexOf ( key . key )
339+ if ( index >= 0 )
340+ _lockedKeys . splice ( index , 1 )
341+ }
342+
307343export type { ChatContext , ChatMessage }
308344
309- export { chatReplyProcess , chatConfig , containsSensitiveWords }
345+ export { chatReplyProcess , chatConfig , containsSensitiveWords , getRandomApiKey }
0 commit comments