diff --git a/.env b/.env index 1977a1e..4d00c89 100644 --- a/.env +++ b/.env @@ -2,3 +2,5 @@ VITE_GLOB_API_URL=/api VITE_APP_API_BASE_URL=http://localhost:3002/ + +VITE_GLOB_API_TIMEOUT=100000 diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b084f7..5d48163 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "chatgpt", "chenzhaoyu", "commitlint", + "davinci", "dockerhub", "esno", "GPTAPI", diff --git a/CHANGELOG.md b/CHANGELOG.md index d67631a..847ed0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v2.6.0 + +`2023-02-21` +### Feature +- 新增对 `网页 accessToken` 调用 `ChatGPT`,更智能不过不太稳定 [#51](https://github.com/Chanzhaoyu/chatgpt-web/issues/51) +- 前端页面设置按钮显示查看当前后端服务配置 + +### Enhancement +- 新增 `TIMEOUT_MS` 环境变量设定后端超时时常(单位:毫秒)[#62](https://github.com/Chanzhaoyu/chatgpt-web/issues/62) + ## v2.5.2 `2023-02-21` diff --git a/README.md b/README.md index 3c2143b..2ce4626 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,43 @@ # ChatGPT Web -使用 express 和 vue3 搭建的 ChartGPT 演示网页 +使用 `express` 和 `vue3` 搭建的支持 `ChatGPT` 双模型演示网页 ![cover](./docs/cover.png) ![cover2](./docs/cover2.png) -> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。 +## 介绍 + +支持双模型,提供了两种非官方 `ChatGPT API` 方法 + +| 方式 | 免费? | 可靠性 | 质量 | +| ---- | ---- | ---- | ---- | +| `ChatGPTAPI(GPT-3)` | 否 | 可靠 | 较笨 | +| `ChatGPTUnofficialProxyAPI(网页 accessToken)` | 是 | 不可靠 | 聪明 | + +***Note:*** 网页 `accessToken` 存在大约 8 小时,而且国内地区网络问题更推荐使用 `GPT-3` 的方式 + +对比: +1. `ChatGPTAPI` 使用 `text-davinci-003` 通过官方`OpenAI`补全`API`模拟`ChatGPT`(最稳健的方法,但它不是免费的,并且没有使用针对聊天进行微调的模型) +2. `ChatGPTUnofficialProxyAPI` 使用非官方代理服务器访问 `ChatGPT` 的后端`API`,绕过`Cloudflare`(使用真实的的`ChatGPT`,非常轻量级,但依赖于第三方服务器,并且有速率限制) + +切换方式: +1. 进入 `service/.env` 文件 +2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview) +3. 使用 `Web API` 请填写 `OPENAI_ACCESS_TOKEN` 字段 [(获取 accessToken)](https://chat.openai.com/api/auth/session) +4. 同时存在时以 `OpenAI API Key` 优先 + +反向代理: + +`ChatGPTUnofficialProxyAPI`时可用 [详情](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) + +```shell +# service/.env +API_REVERSE_PROXY= +``` ## 待实现路线 +[✓] 双模型 + [✓] 多会话储存和上下文逻辑 [✓] 对代码等消息类型的格式化美化处理 @@ -34,12 +64,17 @@ node -v npm install pnpm -g ``` -### OpenAI API Key -注册并获取 [OpenAI API key](https://platform.openai.com/overview) 并填写到本地环境变量 +### 填写密钥 +获取 `Openai Api Key` 或 `accessToken` 并填写本地环境变量 [跳转](#介绍) + ``` # service/.env 文件 -OPENAI_API_KEY='Your key' +# OpenAI API Key - https://platform.openai.com/overview +OPENAI_API_KEY= + +# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response +OPENAI_ACCESS_TOKEN= ``` ## 安装依赖 @@ -60,7 +95,7 @@ pnpm install pnpm bootstrap ``` -## 运行 +## 测试环境运行 ### 后端服务 进入文件夹 `/service` 运行以下命令 @@ -69,7 +104,7 @@ pnpm bootstrap pnpm start ``` -### 网页 +### 前端网页 根目录下运行以下命令 ```shell pnpm dev @@ -78,6 +113,16 @@ pnpm dev ## 打包 ### 使用 Docker + +### Docker 参数示例 + +- `OPENAI_API_KEY` 二选一 +- `OPENAI_ACCESS_TOKEN` 二选一,同时存在时,`OPENAI_API_KEY` 优先 +- `API_REVERSE_PROXY` 可选,设置 `OPENAI_ACCESS_TOKEN` 时可用 [参考](#介绍) +- `TIMEOUT_MS` 超时,单位毫秒,可选 + +![docker](./docs/docker.png) + ### Docker build & Run ```bash @@ -106,7 +151,14 @@ services: ports: - 3002:3002 environment: + # 二选一 OPENAI_API_KEY: xxxxxx + # 二选一 + OPENAI_ACCESS_TOKEN: xxxxxx + # 反向代理,可选 + API_REVERSE_PROXY: xxx + # 超时,单位毫秒,可选 + TIMEOUT_MS: 60000 ``` @@ -129,7 +181,7 @@ pnpm prod PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可 -### 前端打包 +### 前端网页 1、修改根目录下 `.env` 内 `VITE_APP_API_BASE_URL` 为你的实际后端接口地址 diff --git a/docs/docker.png b/docs/docker.png new file mode 100644 index 0000000..660c05d Binary files /dev/null and b/docs/docker.png differ diff --git a/package.json b/package.json index d740f9a..6b96d34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chatgpt-web", - "version": "2.5.2", + "version": "2.6.0", "private": false, "description": "ChatGPT Web", "author": "ChenZhaoYu ", diff --git a/service/.env b/service/.env index 1c3f2e6..0bb4ad6 100644 --- a/service/.env +++ b/service/.env @@ -1,2 +1,11 @@ # OpenAI API Key - https://platform.openai.com/overview OPENAI_API_KEY= + +# change this to an `accessToken` extracted from the ChatGPT site's `https://chat.openai.com/api/auth/session` response +OPENAI_ACCESS_TOKEN= + +# Reverse Proxy +API_REVERSE_PROXY= + +# timeout +TIMEOUT_MS=60000 diff --git a/service/package.json b/service/package.json index 9b168b7..054a07b 100644 --- a/service/package.json +++ b/service/package.json @@ -23,7 +23,7 @@ "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml" }, "dependencies": { - "chatgpt": "^4.7.1", + "chatgpt": "^4.7.2", "dotenv": "^16.0.3", "esno": "^0.16.3", "express": "^4.18.2", diff --git a/service/pnpm-lock.yaml b/service/pnpm-lock.yaml index b67e754..e4800ef 100644 --- a/service/pnpm-lock.yaml +++ b/service/pnpm-lock.yaml @@ -4,7 +4,7 @@ specifiers: '@antfu/eslint-config': ^0.35.2 '@types/express': ^4.17.17 '@types/node': ^18.14.0 - chatgpt: ^4.7.1 + chatgpt: ^4.7.2 dotenv: ^16.0.3 eslint: ^8.34.0 esno: ^0.16.3 @@ -15,7 +15,7 @@ specifiers: typescript: ^4.9.5 dependencies: - chatgpt: 4.7.1 + chatgpt: 4.7.2 dotenv: 16.0.3 esno: 0.16.3 express: 4.18.2 @@ -878,8 +878,8 @@ packages: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} dev: true - /chatgpt/4.7.1: - resolution: {integrity: sha512-FRXfpjn//Y4gUMcUo60W5byJ0CbFhC1Xp3PCm2qcBthBYFbC+LyRrwxvSVTdNwgQywHV4Pc5SFkaZ72f5rCNdQ==} + /chatgpt/4.7.2: + resolution: {integrity: sha512-c5CNqvB98IMEz/Byopwu5FlXGS3w/3iNiZITdDlcZLue4VSjEfzMRWrOrdGidzcE+ud2My6nO8/sSnY7W04WJA==} engines: {node: '>=14'} hasBin: true dependencies: diff --git a/service/src/chatgpt.ts b/service/src/chatgpt.ts index c3fca55..4e8bebc 100644 --- a/service/src/chatgpt.ts +++ b/service/src/chatgpt.ts @@ -1,27 +1,46 @@ import * as dotenv from 'dotenv' import 'isomorphic-fetch' import type { ChatGPTAPI, SendMessageOptions } from 'chatgpt' +import { ChatGPTUnofficialProxyAPI } from 'chatgpt' import { sendResponse } from './utils' +dotenv.config() + +let apiModel: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined + export interface ChatContext { conversationId?: string parentMessageId?: string } -dotenv.config() - -const apiKey = process.env.OPENAI_API_KEY +const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000 -if (apiKey === undefined) - throw new Error('OPENAI_API_KEY is not defined') +if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN) + throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable') -let api: ChatGPTAPI +let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI // To use ESM in CommonJS, you can use a dynamic import (async () => { // More Info: https://github.com/transitive-bullshit/chatgpt-api const { ChatGPTAPI } = await import('chatgpt') - api = new ChatGPTAPI({ apiKey: process.env.OPENAI_API_KEY }) + + if (process.env.OPENAI_API_KEY) { + api = new ChatGPTAPI({ apiKey: process.env.OPENAI_API_KEY }) + apiModel = 'ChatGPTAPI' + } + else { + let options = {} + + if (process.env.API_REVERSE_PROXY) + options = { apiReverseProxyUrl: process.env.API_REVERSE_PROXY } + + api = new ChatGPTUnofficialProxyAPI({ + accessToken: process.env.OPENAI_ACCESS_TOKEN, + ...options, + }) + apiModel = 'ChatGPTUnofficialProxyAPI' + } })() async function chatReply( @@ -32,7 +51,7 @@ async function chatReply( return sendResponse({ type: 'Fail', message: 'Message is empty' }) try { - let options: SendMessageOptions = { timeoutMs: 30 * 1000 } + let options: SendMessageOptions = { timeoutMs } if (lastContext) options = { ...lastContext } @@ -46,4 +65,15 @@ async function chatReply( } } -export { chatReply } +async function chatConfig() { + return sendResponse({ + type: 'Success', + data: { + apiModel, + reverseProxy: process.env.API_REVERSE_PROXY, + timeoutMs, + }, + }) +} + +export { chatReply, chatConfig } diff --git a/service/src/index.ts b/service/src/index.ts index c723db6..01faf4a 100644 --- a/service/src/index.ts +++ b/service/src/index.ts @@ -1,6 +1,6 @@ import express from 'express' import type { ChatContext } from './chatgpt' -import { chatReply } from './chatgpt' +import { chatConfig, chatReply } from './chatgpt' const app = express() const router = express.Router() @@ -26,6 +26,16 @@ router.post('/chat', async (req, res) => { } }) +router.post('/config', async (req, res) => { + try { + const response = await chatConfig() + res.send(response) + } + catch (error) { + res.send(error) + } +}) + app.use('', router) app.use('/api', router) diff --git a/src/api/index.ts b/src/api/index.ts index 47fafd2..1d3811f 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -12,3 +12,9 @@ export function fetchChatAPI( signal, }) } + +export function fetchChatConfig() { + return post({ + url: '/config', + }) +} diff --git a/src/components/common/Setting/index.vue b/src/components/common/Setting/index.vue new file mode 100644 index 0000000..0fcb4a1 --- /dev/null +++ b/src/components/common/Setting/index.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 47dbe5a..16b6d10 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -2,5 +2,6 @@ import HoverButton from './HoverButton/index.vue' import NaiveProvider from './NaiveProvider/index.vue' import SvgIcon from './SvgIcon/index.vue' import UserAvatar from './UserAvatar/index.vue' +import Setting from './Setting/index.vue' -export { HoverButton, NaiveProvider, SvgIcon, UserAvatar } +export { HoverButton, NaiveProvider, SvgIcon, UserAvatar, Setting } diff --git a/src/typings/env.d.ts b/src/typings/env.d.ts index ca60025..54408d7 100644 --- a/src/typings/env.d.ts +++ b/src/typings/env.d.ts @@ -2,5 +2,6 @@ interface ImportMetaEnv { readonly VITE_GLOB_API_URL: string; + readonly VITE_GLOB_API_TIMEOUT: string; readonly VITE_APP_API_BASE_URL: string; } diff --git a/src/utils/request/axios.ts b/src/utils/request/axios.ts index cde4e73..06f993c 100644 --- a/src/utils/request/axios.ts +++ b/src/utils/request/axios.ts @@ -2,7 +2,7 @@ import axios, { type AxiosResponse } from 'axios' const service = axios.create({ baseURL: import.meta.env.VITE_GLOB_API_URL, - timeout: 30 * 1000, + timeout: !isNaN(+import.meta.env.VITE_GLOB_API_TIMEOUT) ? Number(import.meta.env.VITE_GLOB_API_TIMEOUT) : 60 * 1000, }) service.interceptors.request.use( diff --git a/src/views/chat/layout/sider/Footer.vue b/src/views/chat/layout/sider/Footer.vue new file mode 100644 index 0000000..4dda2d6 --- /dev/null +++ b/src/views/chat/layout/sider/Footer.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/views/chat/layout/sider/index.vue b/src/views/chat/layout/sider/index.vue index 3fb3b70..b8f47c0 100644 --- a/src/views/chat/layout/sider/index.vue +++ b/src/views/chat/layout/sider/index.vue @@ -3,7 +3,7 @@ import type { CSSProperties } from 'vue' import { computed, watch } from 'vue' import { NButton, NLayoutSider } from 'naive-ui' import List from './List.vue' -import { HoverButton, SvgIcon, UserAvatar } from '@/components/common' +import Footer from './Footer.vue' import { useAppStore, useChatStore } from '@/store' import { useBasicLayout } from '@/hooks/useBasicLayout' @@ -67,14 +67,7 @@ watch( -
- - - - - - -
+