Skip to content

Commit

Permalink
chore: version 2.10.0
Browse files Browse the repository at this point in the history
* feat: 权限验证功能

* chore: v2.10.0

* feat: 500 服务异常页面

* feat: 只有结束才会滚动到底部

* chore: 修改 CHANGELOG

* chore: 不存在时输出默认报错
  • Loading branch information
Chanzhaoyu committed Mar 7, 2023
1 parent a2ffa3c commit ffd4da9
Show file tree
Hide file tree
Showing 30 changed files with 376 additions and 60 deletions.
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
## v2.10.0

`2023-03-07`

- 老规矩,手动部署的同学需要删除 `node_modules` 安装包重新安装降低出错概率,其他部署不受影响,但是可能会有缓存问题。
- 虽然说了更新放缓,但是 `issues` 不看, `PR` 不改我睡不着,我的邮箱从每天早上`8`点到凌晨`12`永远在滴滴滴,所以求求各位,超时的`issues`自己关闭下哈,我真的需要缓冲一下。
- 演示图片请看最后

## Feature
- 添加权限功能,用法:`service/.env` 中的 `AUTH_SECRET_KEY` 变量添加密码
- 感谢 [PeterDaveHello](https://github.com/Chanzhaoyu/chatgpt-web/pull/348) 添加「繁体中文」翻译
- 感谢 [GermMC](https://github.com/Chanzhaoyu/chatgpt-web/pull/369) 添加聊天记录导入、导出、清空的功能
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/374) 添加会话保存为本地图片的功能


## Enhancement
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/363) 添加 `ctrl+enter` 发送消息
- 现在新消息只有在结束了之后才滚动到底部,而不是之前的强制性
- 优化部分代码

## BugFix
- 转义状态码前端显示,防止直接暴露 `key`(我可能需要更多的状态码补充)

## Other
- 更新依赖到最新

## 演示
> 不是界面最新效果,有美化改动
权限

![权限](https://user-images.githubusercontent.com/24789441/223438518-80d58d42-e344-4e39-b87c-251ff73925ed.png)

聊天记录导出

![聊天记录导出](https://user-images.githubusercontent.com/57023771/223372153-6d8e9ec1-d82c-42af-b4bd-232e50504a25.gif)

保存图片到本地

![保存图片到本地](https://user-images.githubusercontent.com/13901424/223423555-b69b95ef-8bcf-4951-a7c9-98aff2677e18.gif)

## v2.9.3

`2023-03-06`
Expand Down
6 changes: 5 additions & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pnpm dev
- `OPENAI_ACCESS_TOKEN` one of two, `OPENAI_API_KEY` takes precedence when both are present
- `OPENAI_API_BASE_URL` optional, available when `OPENAI_API_KEY` is set
- `API_REVERSE_PROXY` optional, available when `OPENAI_ACCESS_TOKEN` is set [Reference](#introduction)
- `AUTH_SECRET_KEY` Access Password,optional
- `TIMEOUT_MS` timeout, in milliseconds, optional
- `SOCKS_PROXY_HOST` optional, effective with SOCKS_PROXY_PORT
- `SOCKS_PROXY_PORT` optional, effective with SOCKS_PROXY_HOST
Expand Down Expand Up @@ -205,6 +206,8 @@ services:
OPENAI_API_BASE_URL: xxxx
# reverse proxy, optional
API_REVERSE_PROXY: xxx
# access password,optional
AUTH_SECRET_KEY: xxx
# timeout, in milliseconds, optional
TIMEOUT_MS: 60000
# socks proxy, optional, effective with SOCKS_PROXY_PORT
Expand All @@ -223,7 +226,8 @@ The `OPENAI_API_BASE_URL` is optional and only used when setting the `OPENAI_API
| Environment Variable | Required | Description |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `PORT` | Required | Default: `3002` |
| `TIMEOUT_MS` | Optional | Timeout in milliseconds. |
| `AUTH_SECRET_KEY` | Optional | access password |
| `TIMEOUT_MS` | Optional | Timeout in milliseconds |
| `OPENAI_API_KEY` | Optional | Required for `OpenAI API`. `apiKey` can be obtained from [here](https://platform.openai.com/overview). |
| `OPENAI_ACCESS_TOKEN`| Optional | Required for `Web API`. `accessToken` can be obtained from [here](https://chat.openai.com/api/auth/session).|
| `OPENAI_API_BASE_URL` | Optional, only for `OpenAI API` | API endpoint. |
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pnpm dev
- `OPENAI_ACCESS_TOKEN` 二选一,同时存在时,`OPENAI_API_KEY` 优先
- `OPENAI_API_BASE_URL` 可选,设置 `OPENAI_API_KEY` 时可用
- `API_REVERSE_PROXY` 可选,设置 `OPENAI_ACCESS_TOKEN` 时可用 [参考](#介绍)
- `AUTH_SECRET_KEY` 访问权限密钥,可选
- `TIMEOUT_MS` 超时,单位毫秒,可选
- `SOCKS_PROXY_HOST` 可选,和 SOCKS_PROXY_PORT 一起时生效
- `SOCKS_PROXY_PORT` 可选,和 SOCKS_PROXY_HOST 一起时生效
Expand Down Expand Up @@ -203,6 +204,8 @@ services:
OPENAI_API_BASE_URL: xxxx
# 反向代理,可选
API_REVERSE_PROXY: xxx
# 访问权限密钥,可选
AUTH_SECRET_KEY: xxx
# 超时,单位毫秒,可选
TIMEOUT_MS: 60000
# Socks代理,可选,和 SOCKS_PROXY_PORT 一起时生效
Expand All @@ -219,8 +222,9 @@ services:

| 环境变量名称 | 必填 | 备注 |
| --------------------- | ---------------------- | -------------------------------------------------------------------------------------------------- |
| `PORT` | 必填 | 默认 `3002` |
| `TIMEOUT_MS` | 可选 | 超时时间,单位毫秒, |
| `PORT` | 必填 | 默认 `3002`
| `AUTH_SECRET_KEY` | 可选 | 访问权限密钥 |
| `TIMEOUT_MS` | 可选 | 超时时间,单位毫秒 |
| `OPENAI_API_KEY` | `OpenAI API` 二选一 | 使用 `OpenAI API` 所需的 `apiKey` [(获取 apiKey)](https://platform.openai.com/overview) |
| `OPENAI_ACCESS_TOKEN` | `Web API` 二选一 | 使用 `Web API` 所需的 `accessToken` [(获取 accessToken)](https://chat.openai.com/api/auth/session) |
| `OPENAI_API_BASE_URL` | 可选,`OpenAI API` 时可用 | `API`接口地址 |
Expand Down
2 changes: 2 additions & 0 deletions docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ services:
OPENAI_API_BASE_URL: xxxx
# 反向代理,可选
API_REVERSE_PROXY: xxx
# 访问权限密钥,可选
AUTH_SECRET_KEY: xxx
# 超时,单位毫秒,可选
TIMEOUT_MS: 60000
# Socks代理,可选,和 SOCKS_PROXY_PORT 一起时生效
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chatgpt-web",
"version": "2.9.3",
"version": "2.10.0",
"private": false,
"description": "ChatGPT Web",
"author": "ChenZhaoYu <[email protected]>",
Expand Down
3 changes: 3 additions & 0 deletions service/.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ API_REVERSE_PROXY=
# timeout
TIMEOUT_MS=100000

# Secret key
AUTH_SECRET_KEY=

# Socks Proxy Host
SOCKS_PROXY_HOST=

Expand Down
2 changes: 1 addition & 1 deletion service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml"
},
"dependencies": {
"chatgpt": "^5.0.7",
"chatgpt": "^5.0.8",
"dotenv": "^16.0.3",
"esno": "^0.16.3",
"express": "^4.18.2",
Expand Down
8 changes: 4 additions & 4 deletions service/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 11 additions & 7 deletions service/src/chatgpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import { sendResponse } from '../utils'
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'

const ErrorCodeMessage: Record<string, string> = {
401: '提供错误的API密钥 | Incorrect API key provided',
429: '服务器限流,请稍后再试 | Server was limited, please try again later',
503: '服务器繁忙,请稍后再试 | Server is busy, please try again later',
500: '服务器繁忙,请稍后再试 | Server is busy, please try again later',
403: '服务器拒绝访问,请稍后再试 | Server refused to access, please try again later',
400: '[OpenAI] 模型的最大上下文长度是4096个令牌,请减少信息的长度。| This model\'s maximum context length is 4096 tokens.',
401: '[OpenAI] 提供错误的API密钥 | Incorrect API key provided',
403: '[OpenAI] 服务器拒绝访问,请稍后再试 | Server refused to access, please try again later',
429: '[OpenAI] 服务器限流,请稍后再试 | Server was limited, please try again later',
502: '[OpenAI] 错误的网关 | Bad Gateway',
503: '[OpenAI] 服务器繁忙,请稍后再试 | Server is busy, please try again later',
504: '[OpenAI] 网关超时 | Gateway Time-out',
500: '[OpenAI] 服务器繁忙,请稍后再试 | Internal Server Error',
}

dotenv.config()
Expand Down Expand Up @@ -106,10 +109,11 @@ async function chatReplyProcess(
return sendResponse({ type: 'Success', data: response })
}
catch (error: any) {
const code = error.statusCode || 'unknown'
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.statusCode}-${error.statusText}` })
return sendResponse({ type: 'Fail', message: error.message ?? 'Please check the back-end console' })
}
}

Expand Down
30 changes: 29 additions & 1 deletion service/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express'
import type { ChatContext, ChatMessage } from './chatgpt'
import { chatConfig, chatReplyProcess } from './chatgpt'
import { auth } from './middleware/auth'

const app = express()
const router = express.Router()
Expand All @@ -15,7 +16,7 @@ app.all('*', (_, res, next) => {
next()
})

router.post('/chat-process', async (req, res) => {
router.post('/chat-process', auth, async (req, res) => {
res.setHeader('Content-type', 'application/octet-stream')

try {
Expand Down Expand Up @@ -44,6 +45,33 @@ router.post('/config', async (req, res) => {
}
})

router.post('/session', async (req, res) => {
try {
const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
const hasAuth = typeof AUTH_SECRET_KEY === 'string' && AUTH_SECRET_KEY.length > 0
res.send({ status: 'Success', message: '', data: { auth: hasAuth } })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

router.post('/verify', async (req, res) => {
try {
const { token } = req.body as { token: string }
if (!token)
throw new Error('Secret key is empty')

if (process.env.AUTH_SECRET_KEY !== token)
throw new Error('密钥无效 | Secret key is invalid')

res.send({ status: 'Success', message: 'Verify successfully', data: null })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
}
})

app.use('', router)
app.use('/api', router)

Expand Down
19 changes: 19 additions & 0 deletions service/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const auth = async (req, res, next) => {
const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
if (typeof AUTH_SECRET_KEY === 'string' && AUTH_SECRET_KEY.length > 0) {
try {
const Authorization = req.header('Authorization')
if (!Authorization || Authorization.replace('Bearer ', '').trim() !== AUTH_SECRET_KEY.trim())
throw new Error('Error: 无访问权限 | No access rights')
next()
}
catch (error) {
res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })
}
}
else {
next()
}
}

export { auth }
13 changes: 13 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,16 @@ export function fetchChatAPIProcess<T = any>(
onDownloadProgress: params.onDownloadProgress,
})
}

export function fetchSession<T>() {
return post<T>({
url: '/session',
})
}

export function fetchVerify<T>(token: string) {
return post<T>({
url: '/verify',
data: { token },
})
}
1 change: 0 additions & 1 deletion src/icons/403.svg

This file was deleted.

5 changes: 5 additions & 0 deletions src/icons/403.vue

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/icons/500.vue

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export default {
wrong: 'Something went wrong, please try again later.',
success: 'Success',
failed: 'Failed',
verify: 'Verify',
unauthorizedTips: 'Unauthorized, please verify first.',
},
chat: {
placeholder: 'Ask me anything...(Shift + Enter = line break)',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export default {
wrong: '好像出错了,请稍后再试。',
success: '操作成功',
failed: '操作失败',
verify: '验证',
unauthorizedTips: '未经授权,请先进行验证。',
},
chat: {
placeholder: '来说点什么...(Shift + Enter = 换行)',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export default {
wrong: '好像出錯了,請稍後再試。',
success: '操作成功',
failed: '操作失敗',
verify: '驗證',
unauthorizedTips: '未經授權,請先進行驗證。',
},
chat: {
placeholder: '來講點什麼...(Shift + Enter = 換行)',
Expand Down
15 changes: 9 additions & 6 deletions src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { App } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import { setupPageGuard } from './permission'
import { ChatLayout } from '@/views/chat/layout'

const routes: RouteRecordRaw[] = [
Expand All @@ -18,18 +19,18 @@ const routes: RouteRecordRaw[] = [
],
},

{
path: '/403',
name: '403',
component: () => import('@/views/exception/403/index.vue'),
},

{
path: '/404',
name: '404',
component: () => import('@/views/exception/404/index.vue'),
},

{
path: '/500',
name: '500',
component: () => import('@/views/exception/500/index.vue'),
},

{
path: '/:pathMatch(.*)*',
name: 'notFound',
Expand All @@ -43,6 +44,8 @@ export const router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
})

setupPageGuard(router)

export async function setupRouter(app: App) {
app.use(router)
await router.isReady()
Expand Down
25 changes: 25 additions & 0 deletions src/router/permission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Router } from 'vue-router'
import { useAuthStoreWithout } from '@/store/modules/auth'

export function setupPageGuard(router: Router) {
router.beforeEach(async (from, to, next) => {
const authStore = useAuthStoreWithout()
if (!authStore.session) {
try {
const data = await authStore.getSession()
if (String(data.auth) === 'false' && authStore.token)
authStore.removeToken()
next()
}
catch (error) {
if (from.path !== '/500')
next({ name: '500' })
else
next()
}
}
else {
next()
}
})
}
15 changes: 15 additions & 0 deletions src/store/modules/auth/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ss } from '@/utils/storage'

const LOCAL_NAME = 'SECRET_TOKEN'

export function getToken() {
return ss.get(LOCAL_NAME)
}

export function setToken(token: string) {
return ss.set(LOCAL_NAME, token)
}

export function removeToken() {
return ss.remove(LOCAL_NAME)
}
Loading

0 comments on commit ffd4da9

Please sign in to comment.