Skip to content

Commit 15e16df

Browse files
author
Kerwin
committed
feat: 前端页面设置ApiKey
fix: 登录之后默认无法发消息
1 parent d3213bb commit 15e16df

File tree

20 files changed

+604
-101
lines changed

20 files changed

+604
-101
lines changed

README.en.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ For all parameter variables, check [here](#docker-parameter-example) or see:
9191

9292
[] Login or Register
9393

94+
[] Set API key and other information on the front-end page.
95+
9496
[] Data import and export
9597

9698
[] Save message to local image

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787

8888
[] 支持用户登录注册
8989

90+
[] 前端页面设置 apikey 等信息
91+
9092
[] 数据导入、导出
9193

9294
[] 保存消息到本地图片

service/src/chatgpt/index.ts

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SocksProxyAgent } from 'socks-proxy-agent'
66
import { HttpsProxyAgent } from 'https-proxy-agent'
77
import fetch from 'node-fetch'
88
import axios from 'axios'
9+
import { getCacheConfig, getOriginConfig } from '../storage/config'
910
import { sendResponse } from '../utils'
1011
import { isNotEmptyString } from '../utils/is'
1112
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'
@@ -21,57 +22,57 @@ const ErrorCodeMessage: Record<string, string> = {
2122

2223
dotenv.config()
2324

24-
const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000
25-
2625
let apiModel: ApiModel
2726

28-
if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN)
29-
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
30-
3127
let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
3228

33-
(async () => {
29+
export async function initApi() {
3430
// More Info: https://github.com/transitive-bullshit/chatgpt-api
3531

36-
if (process.env.OPENAI_API_KEY) {
37-
const OPENAI_API_MODEL = process.env.OPENAI_API_MODEL
32+
const config = await getCacheConfig()
33+
if (!config.apiKey && !config.accessToken)
34+
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')
35+
36+
if (config.apiKey) {
37+
const OPENAI_API_MODEL = config.apiModel
3838
const model = isNotEmptyString(OPENAI_API_MODEL) ? OPENAI_API_MODEL : 'gpt-3.5-turbo'
3939

4040
const options: ChatGPTAPIOptions = {
41-
apiKey: process.env.OPENAI_API_KEY,
41+
apiKey: config.apiKey,
4242
completionParams: { model },
4343
debug: true,
4444
}
4545

46-
if (isNotEmptyString(process.env.OPENAI_API_BASE_URL))
47-
options.apiBaseUrl = process.env.OPENAI_API_BASE_URL
46+
if (isNotEmptyString(config.apiBaseUrl))
47+
options.apiBaseUrl = config.apiBaseUrl
4848

49-
setupProxy(options)
49+
await setupProxy(options)
5050

5151
api = new ChatGPTAPI({ ...options })
5252
apiModel = 'ChatGPTAPI'
5353
}
5454
else {
5555
const options: ChatGPTUnofficialProxyAPIOptions = {
56-
accessToken: process.env.OPENAI_ACCESS_TOKEN,
56+
accessToken: config.accessToken,
5757
debug: true,
5858
}
5959

60-
if (isNotEmptyString(process.env.API_REVERSE_PROXY))
61-
options.apiReverseProxyUrl = process.env.API_REVERSE_PROXY
60+
if (isNotEmptyString(config.reverseProxy))
61+
options.apiReverseProxyUrl = config.reverseProxy
6262

63-
setupProxy(options)
63+
await setupProxy(options)
6464

6565
api = new ChatGPTUnofficialProxyAPI({ ...options })
6666
apiModel = 'ChatGPTUnofficialProxyAPI'
6767
}
68-
})()
68+
}
6969

7070
async function chatReplyProcess(
7171
message: string,
7272
lastContext?: { conversationId?: string; parentMessageId?: string },
7373
process?: (chat: ChatMessage) => void,
7474
) {
75+
const timeoutMs = (await getCacheConfig()).timeoutMs
7576
try {
7677
let options: SendMessageOptions = { timeoutMs }
7778

@@ -101,8 +102,9 @@ async function chatReplyProcess(
101102
}
102103

103104
async function fetchBalance() {
104-
const OPENAI_API_KEY = process.env.OPENAI_API_KEY
105-
const OPENAI_API_BASE_URL = process.env.OPENAI_API_BASE_URL
105+
const config = await getCacheConfig()
106+
const OPENAI_API_KEY = config.apiKey
107+
const OPENAI_API_BASE_URL = config.apiBaseUrl
106108

107109
if (!isNotEmptyString(OPENAI_API_KEY))
108110
return Promise.resolve('-')
@@ -123,31 +125,28 @@ async function fetchBalance() {
123125
}
124126

125127
async function chatConfig() {
126-
const balance = await fetchBalance()
127-
const reverseProxy = process.env.API_REVERSE_PROXY ?? '-'
128-
const httpsProxy = (process.env.HTTPS_PROXY || process.env.ALL_PROXY) ?? '-'
129-
const socksProxy = (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT)
130-
? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`)
131-
: '-'
128+
const config = await getOriginConfig() as ModelConfig
129+
config.balance = await fetchBalance()
132130
return sendResponse<ModelConfig>({
133131
type: 'Success',
134-
data: { apiModel, reverseProxy, timeoutMs, socksProxy, httpsProxy, balance },
132+
data: config,
135133
})
136134
}
137135

138-
function setupProxy(options: ChatGPTAPIOptions | ChatGPTUnofficialProxyAPIOptions) {
139-
if (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) {
136+
async function setupProxy(options: ChatGPTAPIOptions | ChatGPTUnofficialProxyAPIOptions) {
137+
const config = await getCacheConfig()
138+
if (config.socksProxy) {
140139
const agent = new SocksProxyAgent({
141-
hostname: process.env.SOCKS_PROXY_HOST,
142-
port: process.env.SOCKS_PROXY_PORT,
140+
hostname: config.socksProxy.split(':')[0],
141+
port: parseInt(config.socksProxy.split(':')[1]),
143142
})
144143
options.fetch = (url, options) => {
145144
return fetch(url, { agent, ...options })
146145
}
147146
}
148147
else {
149-
if (process.env.HTTPS_PROXY || process.env.ALL_PROXY) {
150-
const httpsProxy = process.env.HTTPS_PROXY || process.env.ALL_PROXY
148+
if (config.httpsProxy || process.env.ALL_PROXY) {
149+
const httpsProxy = config.httpsProxy || process.env.ALL_PROXY
151150
if (httpsProxy) {
152151
const agent = new HttpsProxyAgent(httpsProxy)
153152
options.fetch = (url, options) => {
@@ -162,6 +161,8 @@ function currentModel(): ApiModel {
162161
return apiModel
163162
}
164163

164+
initApi()
165+
165166
export type { ChatContext, ChatMessage }
166167

167168
export { chatReplyProcess, chatConfig, currentModel }

service/src/index.ts

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import express from 'express'
22
import jwt from 'jsonwebtoken'
33
import { ObjectId } from 'mongodb'
44
import type { ChatContext, ChatMessage } from './chatgpt'
5-
import { chatConfig, chatReplyProcess, currentModel } from './chatgpt'
5+
import { chatConfig, chatReplyProcess, currentModel, initApi } from './chatgpt'
66
import { auth } from './middleware/auth'
7-
import type { ChatOptions, UserInfo } from './storage/model'
7+
import { clearConfigCache, getCacheConfig, getOriginConfig } from './storage/config'
8+
import type { ChatOptions, Config, MailConfig, SiteConfig, UserInfo } from './storage/model'
89
import { Status } from './storage/model'
9-
import { clearChat, createChatRoom, createUser, deleteChat, deleteChatRoom, existsChatRoom, getChat, getChatRooms, getChats, getUser, getUserById, insertChat, renameChatRoom, updateChat, updateUserInfo, verifyUser } from './storage/mongo'
10+
import { clearChat, createChatRoom, createUser, deleteChat, deleteChatRoom, existsChatRoom, getChat, getChatRooms, getChats, getUser, getUserById, insertChat, renameChatRoom, updateChat, updateConfig, updateUserInfo, verifyUser } from './storage/mongo'
11+
import { isNotEmptyString } from './utils/is'
1012
import { sendMail } from './utils/mail'
1113
import { checkUserVerify, getUserVerifyUrl, md5 } from './utils/security'
1214

@@ -175,14 +177,14 @@ router.post('/chat-process', auth, async (req, res) => {
175177

176178
router.post('/user-register', async (req, res) => {
177179
const { username, password } = req.body as { username: string; password: string }
178-
179-
if (process.env.REGISTER_ENABLED !== 'true') {
180+
const config = await getCacheConfig()
181+
if (config.siteConfig.registerEnabled) {
180182
res.send({ status: 'Fail', message: '注册账号功能未启用 | Register account is disabled!', data: null })
181183
return
182184
}
183-
if (typeof process.env.REGISTER_MAILS === 'string' && process.env.REGISTER_MAILS.length > 0) {
185+
if (isNotEmptyString(config.siteConfig.registerMails)) {
184186
let allowSuffix = false
185-
const emailSuffixs = process.env.REGISTER_MAILS.split(',')
187+
const emailSuffixs = config.siteConfig.registerMails.split(',')
186188
for (let index = 0; index < emailSuffixs.length; index++) {
187189
const element = emailSuffixs[index]
188190
allowSuffix = username.toLowerCase().endsWith(element)
@@ -207,13 +209,19 @@ router.post('/user-register', async (req, res) => {
207209
res.send({ status: 'Success', message: '注册成功 | Register success', data: null })
208210
}
209211
else {
210-
sendMail(username, getUserVerifyUrl(username))
212+
await sendMail(username, await getUserVerifyUrl(username))
211213
res.send({ status: 'Success', message: '注册成功, 去邮箱中验证吧 | Registration is successful, you need to go to email verification', data: null })
212214
}
213215
})
214216

215-
router.post('/config', async (req, res) => {
217+
router.post('/config', auth, async (req, res) => {
216218
try {
219+
const userId = new ObjectId(req.headers.userId.toString())
220+
221+
const user = await getUserById(userId)
222+
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
223+
throw new Error('无权限 | No permission.')
224+
217225
const response = await chatConfig()
218226
res.send(response)
219227
}
@@ -226,7 +234,7 @@ router.post('/session', async (req, res) => {
226234
try {
227235
const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
228236
const hasAuth = typeof AUTH_SECRET_KEY === 'string' && AUTH_SECRET_KEY.length > 0
229-
const allowRegister = process.env.REGISTER_ENABLED === 'true'
237+
const allowRegister = (await getCacheConfig()).siteConfig.registerEnabled
230238
res.send({ status: 'Success', message: '', data: { auth: hasAuth, allowRegister, model: currentModel() } })
231239
}
232240
catch (error) {
@@ -293,6 +301,77 @@ router.post('/verify', async (req, res) => {
293301
}
294302
})
295303

304+
router.post('/setting-base', auth, async (req, res) => {
305+
try {
306+
const { apiKey, apiModel, apiBaseUrl, accessToken, timeoutMs, socksProxy, httpsProxy } = req.body as Config
307+
const userId = new ObjectId(req.headers.userId.toString())
308+
309+
if (apiKey == null && accessToken == null)
310+
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable.')
311+
312+
const user = await getUserById(userId)
313+
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
314+
throw new Error('无权限 | No permission.')
315+
316+
const thisConfig = await getOriginConfig()
317+
thisConfig.apiKey = apiKey
318+
thisConfig.apiModel = apiModel
319+
thisConfig.apiBaseUrl = apiBaseUrl
320+
thisConfig.accessToken = accessToken
321+
thisConfig.timeoutMs = timeoutMs
322+
thisConfig.socksProxy = socksProxy
323+
thisConfig.httpsProxy = httpsProxy
324+
await updateConfig(thisConfig)
325+
clearConfigCache()
326+
initApi()
327+
const response = await chatConfig()
328+
res.send({ status: 'Success', message: '操作成功 | Successfully', data: response.data })
329+
}
330+
catch (error) {
331+
res.send({ status: 'Fail', message: error.message, data: null })
332+
}
333+
})
334+
335+
router.post('/setting-site', auth, async (req, res) => {
336+
try {
337+
const config = req.body as SiteConfig
338+
const userId = new ObjectId(req.headers.userId.toString())
339+
340+
const user = await getUserById(userId)
341+
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
342+
throw new Error('无权限 | No permission.')
343+
344+
const thisConfig = await getOriginConfig()
345+
thisConfig.siteConfig = config
346+
const result = await updateConfig(thisConfig)
347+
clearConfigCache()
348+
res.send({ status: 'Success', message: '操作成功 | Successfully', data: result.siteConfig })
349+
}
350+
catch (error) {
351+
res.send({ status: 'Fail', message: error.message, data: null })
352+
}
353+
})
354+
355+
router.post('/setting-mail', auth, async (req, res) => {
356+
try {
357+
const config = req.body as MailConfig
358+
const userId = new ObjectId(req.headers.userId.toString())
359+
360+
const user = await getUserById(userId)
361+
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
362+
throw new Error('无权限 | No permission.')
363+
364+
const thisConfig = await getOriginConfig()
365+
thisConfig.mailConfig = config
366+
const result = await updateConfig(thisConfig)
367+
clearConfigCache()
368+
res.send({ status: 'Success', message: '操作成功 | Successfully', data: result.mailConfig })
369+
}
370+
catch (error) {
371+
res.send({ status: 'Fail', message: error.message, data: null })
372+
}
373+
})
374+
296375
app.use('', router)
297376
app.use('/api', router)
298377

service/src/storage/config.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ObjectId } from 'mongodb'
2+
import { Config, MailConfig, SiteConfig } from './model'
3+
import { getConfig } from './mongo'
4+
5+
let cachedConfig: Config | undefined
6+
let cacheExpiration = 0
7+
8+
export async function getCacheConfig(): Promise<Config> {
9+
const now = Date.now()
10+
if (cachedConfig && cacheExpiration > now)
11+
return Promise.resolve(cachedConfig)
12+
13+
const loadedConfig = await getOriginConfig()
14+
15+
cachedConfig = loadedConfig
16+
cacheExpiration = now + 10 * 60 * 1000
17+
18+
return Promise.resolve(cachedConfig)
19+
}
20+
21+
export async function getOriginConfig() {
22+
let config = await getConfig()
23+
if (config == null) {
24+
config = new Config(new ObjectId(),
25+
!isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000,
26+
process.env.OPENAI_API_KEY,
27+
process.env.OPENAI_ACCESS_TOKEN,
28+
process.env.OPENAI_API_BASE_URL,
29+
process.env.OPENAI_API_MODEL || 'gpt-3.5-turbo',
30+
process.env.API_REVERSE_PROXY,
31+
(process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT)
32+
? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`)
33+
: '',
34+
process.env.HTTPS_PROXY,
35+
new SiteConfig(
36+
process.env.SITE_TITLE || 'ChatGpt Web',
37+
process.env.REGISTER_ENABLED === 'true',
38+
process.env.REGISTER_MAILS,
39+
process.env.SITE_DOMAIN),
40+
new MailConfig(process.env.SMTP_HOST,
41+
!isNaN(+process.env.SMTP_PORT) ? +process.env.SMTP_PORT : 465,
42+
process.env.SMTP_TSL === 'true',
43+
process.env.SMTP_USERNAME,
44+
process.env.SMTP_PASSWORD))
45+
}
46+
return config
47+
}
48+
49+
export function clearConfigCache() {
50+
cacheExpiration = 0
51+
cachedConfig = null
52+
}

0 commit comments

Comments
 (0)