Skip to content

Commit 9bee766

Browse files
author
Kerwin
committed
feat: support multiple key random usage(Close #155, Close #138)
1 parent 1b120f8 commit 9bee766

File tree

27 files changed

+703
-131
lines changed

27 files changed

+703
-131
lines changed

README.en.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
[] Users manager
2323

24+
[] Random Key
25+
2426
</br>
2527

2628
## Screenshots
@@ -32,6 +34,7 @@
3234
![cover3](./docs/basesettings.jpg)
3335
![cover3](./docs/prompt_en.jpg)
3436
![cover3](./docs/user-manager.jpg)
37+
![cover3](./docs/key-manager-en.jpg)
3538

3639
- [ChatGPT Web](#chatgpt-web)
3740
- [Introduction](#introduction)

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
[] 每个会话设置独有 Prompt
2121

2222
[] 用户管理
23+
24+
[] 多 Key 随机
2325
</br>
2426

2527
## 截图
@@ -31,6 +33,7 @@
3133
![cover3](./docs/basesettings.jpg)
3234
![cover3](./docs/prompt.jpg)
3335
![cover3](./docs/user-manager.jpg)
36+
![cover3](./docs/key-manager.jpg)
3437

3538
- [ChatGPT Web](#chatgpt-web)
3639
- [介绍](#介绍)
@@ -410,7 +413,18 @@ A: 一种可能原因是经过 Nginx 反向代理,开启了 buffer,则 Nginx
410413
</a>
411414

412415
## 赞助
413-
如果你觉得这个项目对你有帮助,请给我点个Star。
416+
如果你觉得这个项目对你有帮助,请给我点个Star。并且情况允许的话,可以给我一点点支持,总之非常感谢支持~
417+
418+
<div style="display: flex; gap: 20px;">
419+
<div style="text-align: center">
420+
<img style="max-width: 100%" src="./docs/wechat.png" alt="微信" />
421+
<p>WeChat Pay</p>
422+
</div>
423+
<div style="text-align: center">
424+
<img style="max-width: 100%" src="./docs/alipay.png" alt="支付宝" />
425+
<p>Alipay</p>
426+
</div>
427+
</div>
414428

415429
## License
416430
MIT © [Kerwin1202](./license)

docker-compose/docker-compose.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ services:
1111
- database
1212
environment:
1313
TZ: Asia/Shanghai
14-
# 二选一
15-
OPENAI_API_KEY:
16-
# 二选一
17-
OPENAI_ACCESS_TOKEN:
18-
# API接口地址,可选,设置 OPENAI_API_KEY 时可用
19-
OPENAI_API_BASE_URL:
20-
# ChatGPTAPI 或者 ChatGPTUnofficialProxyAPI
21-
OPENAI_API_MODEL:
22-
# 反向代理,可选
23-
API_REVERSE_PROXY:
2414
# 访问jwt加密参数,可选 不为空则允许登录 同时需要设置 MONGODB_URL
2515
AUTH_SECRET_KEY:
2616
# 每小时最大请求次数,可选,默认无限
@@ -35,8 +25,6 @@ services:
3525
SOCKS_PROXY_USERNAME:
3626
# Socks代理密码,可选,和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效
3727
SOCKS_PROXY_PASSWORD:
38-
# HTTPS_PROXY 代理,可选
39-
HTTPS_PROXY: http://xxxx:7890
4028
# 网站名称
4129
SITE_TITLE: ChatGpt Web
4230
# mongodb 的连接字符串

docs/alipay.png

60.9 KB
Loading

docs/key-manager-en.jpg

257 KB
Loading

docs/key-manager.jpg

246 KB
Loading

docs/wechat.png

108 KB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chatgpt-web",
3-
"version": "2.12.4",
3+
"version": "2.13.0",
44
"private": false,
55
"description": "ChatGPT Web",
66
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",

service/src/chatgpt/index.ts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
55
import { SocksProxyAgent } from 'socks-proxy-agent'
66
import httpsProxyAgent from 'https-proxy-agent'
77
import fetch from 'node-fetch'
8-
import type { AuditConfig, CHATMODEL } from 'src/storage/model'
8+
import type { AuditConfig, CHATMODEL, KeyConfig, UserInfo } from 'src/storage/model'
99
import jwt_decode from 'jwt-decode'
1010
import dayjs from 'dayjs'
1111
import type { TextAuditService } from '../utils/textAudit'
1212
import { textAuditServices } from '../utils/textAudit'
13-
import { getCacheConfig, getOriginConfig } from '../storage/config'
13+
import { getCacheApiKeys, getCacheConfig, getOriginConfig } from '../storage/config'
1414
import { sendResponse } from '../utils'
15-
import { isNotEmptyString } from '../utils/is'
15+
import { hasAnyRole, isNotEmptyString } from '../utils/is'
1616
import type { ChatContext, ChatGPTUnofficialProxyAPIOptions, JWT, ModelConfig } from '../types'
1717
import { getChatByMessageId } from '../storage/mongo'
1818
import type { RequestOptions } from './types'
@@ -32,20 +32,17 @@ const ErrorCodeMessage: Record<string, string> = {
3232

3333
let 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

8885
async 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

128131
export 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+
307343
export type { ChatContext, ChatMessage }
308344

309-
export { chatReplyProcess, chatConfig, containsSensitiveWords }
345+
export { chatReplyProcess, chatConfig, containsSensitiveWords, getRandomApiKey }

service/src/chatgpt/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ChatMessage } from 'chatgpt'
2-
import type { CHATMODEL } from 'src/storage/model'
2+
import type { CHATMODEL, KeyConfig } from 'src/storage/model'
33

44
export interface RequestOptions {
55
message: string
@@ -9,6 +9,7 @@ export interface RequestOptions {
99
temperature?: number
1010
top_p?: number
1111
chatModel: CHATMODEL
12+
key: KeyConfig
1213
}
1314

1415
export interface BalanceResponse {

0 commit comments

Comments
 (0)