Commit 17364e9d by Archer

conflict

perf: 聊天页优化

perf: md解析样式

perf: ui调整

perf: 懒加载和动态加载优化

perf: 去除console,

perf: 图片cdn

feat: 图片地址

perf: 登录顺序

feat: 流优化
parent 23908232
AXIOS_PROXY_HOST=127.0.0.1 AXIOS_PROXY_HOST=127.0.0.1
AXIOS_PROXY_PORT=33210 AXIOS_PROXY_PORT=33210
MONGODB_UR= MONGODB_URI=
MY_MAIL= MY_MAIL=
MAILE_CODE= MAILE_CODE=
TOKEN_KEY= TOKEN_KEY=
\ No newline at end of file
...@@ -34,6 +34,6 @@ yarn-error.log* ...@@ -34,6 +34,6 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
public/trainData/ /public/trainData/
.vscode/ /.vscode/
platform.json platform.json
\ No newline at end of file
{
"editor.formatOnType": true,
"editor.formatOnSave": true ,
"prettier.tabWidth": 2
}
\ No newline at end of file
...@@ -58,7 +58,7 @@ ENV PORT 3000 ...@@ -58,7 +58,7 @@ ENV PORT 3000
ENV MAX_USER '' ENV MAX_USER ''
ENV AXIOS_PROXY_HOST '' ENV AXIOS_PROXY_HOST ''
ENV AXIOS_PROXY_PORT '' ENV AXIOS_PROXY_PORT ''
ENV MONGODB_UR '' ENV MONGODB_URI ''
ENV MY_MAIL '' ENV MY_MAIL ''
ENV MAILE_CODE '' ENV MAILE_CODE ''
ENV TOKEN_KEY '' ENV TOKEN_KEY ''
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
``` ```
AXIOS_PROXY_HOST=axios代理地址,目前 openai 接口都需要走代理,本机的话就填 127.0.0.1 AXIOS_PROXY_HOST=axios代理地址,目前 openai 接口都需要走代理,本机的话就填 127.0.0.1
AXIOS_PROXY_PORT=代理端口 AXIOS_PROXY_PORT=代理端口
MONGODB_UR=mongo数据库地址 MONGODB_URI=mongo数据库地址
MY_MAIL=发送验证码邮箱 MY_MAIL=发送验证码邮箱
MAILE_CODE=邮箱秘钥 MAILE_CODE=邮箱秘钥
TOKEN_KEY=随便填一个,用于生成和校验token TOKEN_KEY=随便填一个,用于生成和校验token
...@@ -15,22 +15,62 @@ TOKEN_KEY=随便填一个,用于生成和校验token ...@@ -15,22 +15,62 @@ TOKEN_KEY=随便填一个,用于生成和校验token
```bash ```bash
pnpm dev pnpm dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## 部署 ## 部署
```bash ```bash
# 本地 docker 打包 # 本地 docker 打包
docker build -t imageName . docker build -t imageName:tag .
docker push imageName docker push imageName:tag
```
# 服务器拉取部署 服务器请准备好 docker, mongo,nginx和代理。 镜像走本机的代理,所以用 host,port改成代理的端口,clash一般都是7890。
docker pull imageName
```bash
# 服务器拉取部署, imageName 替换成镜像名
docker pull imageName:tag
# 获取本地旧镜像ID
OLD_IMAGE_ID=$(docker images imageName -f "dangling=true" -q)
docker stop doc-gpt || true docker stop doc-gpt || true
docker rm doc-gpt || true docker rm doc-gpt || true
# 运行时才把参数写入 docker run -d --network=host --name doc-gpt \
docker run -d --network=host --name doc-gpt -e AXIOS_PROXY_HOST= -e AXIOS_PROXY_PORT= -e MAILE_CODE= -e TOKEN_KEY= -e MONGODB_UR= imageName -e MAX_USER=50 \
-e AXIOS_PROXY_HOST=127.0.0.1 \
-e AXIOS_PROXY_PORT=7890 \
-e MY_MAIL=your email\
-e MAILE_CODE=your email code \
-e TOKEN_KEY=任意一个内容 \
-e MONGODB_URI="mongodb://aha:ROOT_root123@127.0.0.0:27017/?authSource=admin&readPreference=primary&appname=MongoDB%20Compass&ssl=false" \
imageName:tag
docker logs doc-gpt
# 删除本地旧镜像
if [ ! -z "$OLD_IMAGE_ID" ]; then
docker rmi $OLD_IMAGE_ID
fi
```
### docker 安装
```bash
# 安装docker
curl -sSL https://get.daocloud.io/docker | sh
sudo systemctl start docker
```
### mongo 安装
```bash
docker pull mongo:6.0.4
docker stop mongo
docker rm mongo
docker run -d --name mongo \
-e MONGO_INITDB_ROOT_USERNAME= \
-e MONGO_INITDB_ROOT_PASSWORD= \
-v /root/service/mongo:/data/db \
mongo:6.0.4
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
# 介绍页 # 介绍页
...@@ -70,4 +110,4 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the ...@@ -70,4 +110,4 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
### 其他问题 ### 其他问题
还有其他问题,可以加我 wx,拉个交流群大家一起聊聊。 还有其他问题,可以加我 wx,拉个交流群大家一起聊聊。
![](/imgs/erweima.jpg) ![](/icon/erweima.jpg)
\ No newline at end of file \ No newline at end of file
...@@ -6,7 +6,17 @@ const isDev = process.env.NODE_ENV === 'development'; ...@@ -6,7 +6,17 @@ const isDev = process.env.NODE_ENV === 'development';
const nextConfig = { const nextConfig = {
output: 'standalone', output: 'standalone',
reactStrictMode: false, reactStrictMode: false,
compress: true compress: true,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'docgpt-1301319986.cos.ap-shanghai.myqcloud.com',
port: '',
pathname: '/**'
}
]
}
}; };
module.exports = nextConfig; module.exports = nextConfig;
...@@ -19,11 +19,10 @@ ...@@ -19,11 +19,10 @@
"@next/font": "13.1.6", "@next/font": "13.1.6",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.3",
"@tanstack/react-query": "^4.24.10", "@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
"axios": "^1.3.3", "axios": "^1.3.3",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"formidable": "^2.1.1", "formidable": "^2.1.1",
"framer-motion": "^9.0.6", "framer-motion": "^9.0.6",
"hyperdown": "^2.4.29", "hyperdown": "^2.4.29",
...@@ -32,13 +31,16 @@ ...@@ -32,13 +31,16 @@
"mongoose": "^6.10.0", "mongoose": "^6.10.0",
"next": "13.1.6", "next": "13.1.6",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.1",
"nprogress": "^0.2.0",
"openai": "^3.2.1", "openai": "^3.2.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.43.1", "react-hook-form": "^7.43.1",
"react-markdown": "^8.0.5", "react-markdown": "^8.0.5",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"rehype-katex": "^6.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sass": "^1.58.3", "sass": "^1.58.3",
"sharp": "^0.31.3", "sharp": "^0.31.3",
"tunnel": "^0.0.6", "tunnel": "^0.0.6",
...@@ -56,6 +58,8 @@ ...@@ -56,6 +58,8 @@
"@types/react-syntax-highlighter": "^15.5.6", "@types/react-syntax-highlighter": "^15.5.6",
"@types/tunnel": "^0.0.3", "@types/tunnel": "^0.0.3",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.1.2", "lint-staged": "^13.1.2",
"prettier": "^2.8.4" "prettier": "^2.8.4"
......
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17" height="12" viewBox="0 0 17 12" fill="none"><g opacity="1" transform="translate(0.70001220703125 0.2001953125) rotate(0 7.5 5.5)"><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0 5) rotate(0 7.5 0.5)" d="M0,0.5L15,0.5 " /><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(0 0) rotate(0 7.5 0.5)" d="M0,0.5L15,0.5 " /><path id="Path" style="stroke:#A0A5BA; stroke-width:1.4; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(7 10) rotate(0 4 0.5)" d="M0,0.5L8,0.5 " /></g></svg>
\ No newline at end of file
<svg viewBox="0 0 1024 1024" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" id="图层_1"><defs><style>.cls-1{fill:url(#未命名的渐变_46);}.cls-2{fill:url(#未命名的渐变_46-2);}.cls-3{fill:url(#未命名的渐变_59);}.cls-4{fill:url(#未命名的渐变_46-3);}.cls-5{fill:url(#未命名的渐变_61);}.cls-6{fill:url(#未命名的渐变_46-4);}.cls-7{fill:url(#未命名的渐变_59-2);}.cls-8{fill:url(#未命名的渐变_61-2);}.cls-9{fill:url(#未命名的渐变_46-5);}.cls-10{fill:url(#未命名的渐变_46-6);}.cls-11{fill:url(#未命名的渐变_37);}.cls-12{fill:url(#未命名的渐变_37-2);}.cls-13{fill:url(#未命名的渐变_37-3);}.cls-14{fill:url(#未命名的渐变_37-4);}.cls-15{fill:url(#未命名的渐变_37-5);}.cls-16{fill:url(#未命名的渐变_37-6);}.cls-17{fill:url(#未命名的渐变_46-7);}</style><linearGradient gradientUnits="userSpaceOnUse" y2="473.26" x2="878.62" y1="655.89" x1="807.82" id="未命名的渐变_46"><stop stop-color="#5496e8" offset="0"></stop><stop stop-color="#3462bf" offset="1"></stop></linearGradient><linearGradient xlink:href="#未命名的渐变_46" y2="490.59" x2="619.58" y1="586.01" x1="571.36" id="未命名的渐变_46-2"></linearGradient><linearGradient gradientUnits="userSpaceOnUse" y2="540.54" x2="784.89" y1="749.85" x1="340.28" id="未命名的渐变_59"><stop stop-color="#35a2e6" offset="0"></stop><stop stop-color="#2256bd" offset="1"></stop></linearGradient><linearGradient xlink:href="#未命名的渐变_46" y2="634.74" x2="555.72" y1="708.62" x1="417.21" id="未命名的渐变_46-3"></linearGradient><linearGradient gradientUnits="userSpaceOnUse" y2="347.11" x2="586.71" y1="363.53" x1="748.14" id="未命名的渐变_61"><stop stop-color="#5496e8" offset="0"></stop><stop stop-color="#2a56b0" offset="1"></stop></linearGradient><linearGradient xlink:href="#未命名的渐变_46" y2="509.85" x2="404.48" y1="622.02" x1="373.02" id="未命名的渐变_46-4"></linearGradient><linearGradient xlink:href="#未命名的渐变_59" y2="179.17" x2="550.55" y1="227.77" x1="501.24" id="未命名的渐变_59-2"></linearGradient><linearGradient xlink:href="#未命名的渐变_61" y2="282.77" x2="492.74" y1="354.59" x1="459.22" id="未命名的渐变_61-2"></linearGradient><linearGradient xlink:href="#未命名的渐变_46" y2="88.18" x2="508.63" y1="279.71" x1="478.54" id="未命名的渐变_46-5"></linearGradient><linearGradient xlink:href="#未命名的渐变_46" y2="352.71" x2="464.27" y1="815.1" x1="231.7" id="未命名的渐变_46-6"></linearGradient><linearGradient gradientUnits="userSpaceOnUse" y2="401.04" x2="1033.08" y1="1071.37" x1="598.05" id="未命名的渐变_37"><stop stop-color="#8ffbff" offset="0"></stop><stop stop-color="#4a83ff" offset="1"></stop></linearGradient><linearGradient xlink:href="#未命名的渐变_37" y2="282.69" x2="850.73" y1="953.03" x1="415.7" id="未命名的渐变_37-2"></linearGradient><linearGradient xlink:href="#未命名的渐变_37" y2="274.94" x2="838.77" y1="945.27" x1="403.74" id="未命名的渐变_37-3"></linearGradient><linearGradient xlink:href="#未命名的渐变_37" y2="249.95" x2="800.28" y1="920.28" x1="365.24" id="未命名的渐变_37-4"></linearGradient><linearGradient xlink:href="#未命名的渐变_37" y2="194.17" x2="714.32" y1="864.5" x1="279.29" id="未命名的渐变_37-5"></linearGradient><linearGradient xlink:href="#未命名的渐变_37" y2="109.99" x2="584.6" y1="780.32" x1="149.57" id="未命名的渐变_37-6"></linearGradient><linearGradient xlink:href="#未命名的渐变_46" y2="558.04" x2="166" y1="775.55" x1="110.6" id="未命名的渐变_46-7"></linearGradient></defs><title>2.5D</title><path d="M874.19,517.9l.07-.16a48,48,0,0,1,7.53-13.13l.5-.63A44.59,44.59,0,0,1,893,494.62c.41-.26.84-.49,1.26-.73a37.5,37.5,0,0,1,13.38-4.81,32.39,32.39,0,0,1,11.66.59,30.26,30.26,0,0,1,7.92,2.76l-89.8-48.85c-.37-.2-.74-.39-1.11-.58a30.1,30.1,0,0,0-5.77-2.13c-.32-.08-.71,0-1-.05a32.21,32.21,0,0,0-11.66-.59,36.81,36.81,0,0,0-10.54,3.17c-1,.46-1.9,1.1-2.85,1.64-.43.24-.84.46-1.25.73-.58.36-1.21.63-1.77,1a43.66,43.66,0,0,0-4.39,3.47,45.81,45.81,0,0,0-3.71,3.75c-.31.36-.58.76-.89,1.13-.17.21-.32.41-.49.62-.64.8-1.31,1.57-1.9,2.41a47.14,47.14,0,0,0-5.64,10.74l-.06.14,0,.11a45.65,45.65,0,0,0-2.83,15.64l-.24,131.71c0,14.11,6.75,25.49,16.93,31L888,696.38c-10.18-5.54-17-16.92-16.93-31l.24-131.71A45.61,45.61,0,0,1,874.19,517.9Z" class="cls-1"></path><path d="M613.08,563a68.2,68.2,0,0,1,2-7.16c.18-.5.46-1,.65-1.45a68.93,68.93,0,0,1,3.93-8.34c.42-.74.9-1.45,1.34-2.18a69.61,69.61,0,0,1,4.62-6.67c.5-.64,1-1.28,1.49-1.9a66.92,66.92,0,0,1,6.87-7.07c.5-.44,1.05-.81,1.56-1.24a62.68,62.68,0,0,1,6.22-4.51c1.24-.79,2.52-1.5,3.82-2.2,1.47-.8,3-1.49,4.52-2.15A53.5,53.5,0,0,1,663.89,514a46.57,46.57,0,0,1,13.25.2c.87.13,1.75.22,2.6.4a43.58,43.58,0,0,1,12.09,4.23L602,469.95q-.8-.43-1.61-.83a43.17,43.17,0,0,0-8.3-3.07c-.7-.18-1.47-.19-2.18-.33s-1.72-.27-2.59-.4a46.4,46.4,0,0,0-13.26-.2,53.55,53.55,0,0,0-13.79,4.15c-.53.23-1.14.25-1.66.5-1,.47-1.9,1.13-2.87,1.65-1.29.7-2.56,1.4-3.81,2.19-.62.4-1.31.68-1.92,1.11-1.5,1-2.9,2.24-4.32,3.42-.51.42-1.05.79-1.55,1.23-.17.15-.35.26-.52.41a66.57,66.57,0,0,0-5.39,5.45c-.34.38-.62.82-1,1.21-.52.62-1,1.26-1.49,1.9-.77,1-1.6,1.91-2.31,2.93-.84,1.19-1.54,2.49-2.3,3.74-.44.73-.92,1.43-1.34,2.18-.21.37-.46.7-.66,1.07a68.64,68.64,0,0,0-3.31,7.35c-.18.46-.44.89-.61,1.36,0,.06-.06.12-.08.18a67.57,67.57,0,0,0-2,7.46c-.28,1.16-.72,2.26-.94,3.43a64.26,64.26,0,0,0-1.09,11.57,54.1,54.1,0,0,0,6.69,26.76,45.3,45.3,0,0,0,17.78,17.71l89.8,48.85c-14.75-8-24.51-24.39-24.48-44.48A64.15,64.15,0,0,1,612,566.9C612.26,565.56,612.76,564.3,613.08,563Z" class="cls-2"></path><path d="M766,687.29c.12-.34.16-.71.29-1A223.94,223.94,0,0,0,776,650.8c.05-.28.07-.56.12-.84a213.87,213.87,0,0,0,3.5-37.69c.13-67.83-32.77-122.94-82.29-149.87l-89.8-48.85c49.51,26.93,82.41,82,82.29,149.87a214,214,0,0,1-3.5,37.74c0,.26-.06.53-.11.78a224,224,0,0,1-9.72,35.46c-.18.49-.3,1-.48,1.49a230.45,230.45,0,0,1-12,26.6c-.88,1.69-1.73,3.39-2.65,5.05a233.55,233.55,0,0,1-12.79,20.73c-.61.87-1.29,1.67-1.91,2.53q-5.89,8.2-12.45,15.8c-1.49,1.74-3,3.43-4.58,5.13q-6.11,6.69-12.7,12.83c-1.31,1.23-2.58,2.52-3.92,3.72a214.75,214.75,0,0,1-18.81,14.88c-1.26.89-2.57,1.69-3.85,2.55a200.5,200.5,0,0,1-26,15c-.68.32-1.38.55-2.06.86a185.55,185.55,0,0,1-20.36,7.83c-2,.64-4,1.26-6,1.83a174.34,174.34,0,0,1-23.47,5.18l-91.56,12.87a161.8,161.8,0,0,1-28.77,1.28c-3-.11-5.83-.56-8.75-.84a150.88,150.88,0,0,1-18.76-2.87c-3.09-.7-6.12-1.51-9.13-2.39a145.43,145.43,0,0,1-18-6.64c-3.57-1.59-7.29-2.89-10.71-4.75l89.8,48.85q2.66,1.45,5.39,2.79c1.69.83,3.6,1.2,5.32,2a144.81,144.81,0,0,0,18,6.64c1.58.46,3,1.29,4.6,1.7s3.06.36,4.53.69a150.51,150.51,0,0,0,18.76,2.87c2.93.28,5.77.72,8.75.84a161.65,161.65,0,0,0,28.78-1.28l91.56-12.87a174.28,174.28,0,0,0,23.46-5.18c2-.57,4-1.19,6-1.83a185.6,185.6,0,0,0,20.38-7.84c.55-.25,1.13-.39,1.67-.64.13-.06.24-.15.36-.21a199.65,199.65,0,0,0,26.05-15c.75-.5,1.59-.83,2.33-1.34.53-.36,1-.82,1.51-1.19a214.42,214.42,0,0,0,18.83-14.9c.36-.32.76-.57,1.12-.88,1-.88,1.82-1.91,2.78-2.8q6.6-6.15,12.72-12.84c.84-.92,1.8-1.72,2.63-2.66.69-.78,1.26-1.68,1.94-2.47q6.54-7.61,12.45-15.8c.5-.7,1.11-1.33,1.6-2,.11-.16.19-.33.3-.49a233.55,233.55,0,0,0,12.79-20.73c.42-.77,1-1.46,1.38-2.24s.79-1.91,1.27-2.82a230.91,230.91,0,0,0,12-26.6C765.85,687.58,765.93,687.44,766,687.29Z" class="cls-3"></path><path d="M633.5,672.12l-89.8-48.85-.89-.46a24.1,24.1,0,0,0-4.63-1.71c-.25-.07-.56,0-.82,0a25.69,25.69,0,0,0-9.31-.48L406,637.73a30,30,0,0,0-8.58,2.58c-.75.35-1.44.84-2.17,1.25-.35.2-.69.38-1,.6-.51.31-1.06.55-1.55.89a35.48,35.48,0,0,0-3.52,2.79,36.72,36.72,0,0,0-3,3c-.22.25-.4.53-.61.78l-.36.46c-.56.69-1.13,1.35-1.64,2.08a36.62,36.62,0,0,0-4.41,8.38c0,.07-.07.13-.09.2l-.06.14a36.33,36.33,0,0,0-2.25,12.47c0,11.17,5.43,20.27,13.61,24.72l89.8,48.85c-8.18-4.45-13.63-13.55-13.61-24.72a36.39,36.39,0,0,1,2.32-12.61l.08-.18a38,38,0,0,1,6-10.47l.36-.46A35.79,35.79,0,0,1,484,691c.34-.21.69-.4,1-.6a30.47,30.47,0,0,1,10.75-3.83l122-17.15a25.87,25.87,0,0,1,9.31.48A24.29,24.29,0,0,1,633.5,672.12Z" class="cls-4"></path><path d="M642,365.21a217.11,217.11,0,0,1,29.95,2.87c1.75.3,3.45.72,5.19,1.06A208.62,208.62,0,0,1,703.14,376c1.41.47,2.87.84,4.27,1.33a205.92,205.92,0,0,1,29,12.89l-89.8-48.85q-3.78-2.06-7.64-4a206.15,206.15,0,0,0-21.4-8.94c-1.4-.5-2.86-.86-4.27-1.33-4.6-1.53-9.16-3.13-13.9-4.34-4-1-8.09-1.7-12.16-2.49-1.74-.34-3.43-.77-5.19-1.06a216.49,216.49,0,0,0-29.94-2.87c-2.15-.06-4.31,0-6.47,0a230.81,230.81,0,0,0-32.35,2l-9.23,1.3,89.8,48.85,9.23-1.3a231.12,231.12,0,0,1,32.35-2C637.64,365.18,639.81,365.15,642,365.21Z" class="cls-5"></path><path d="M399.8,592.92a68,68,0,0,1,2-7.14c.16-.44.4-.84.57-1.27a69.26,69.26,0,0,1,4-8.52c.41-.74.89-1.43,1.32-2.15a69.67,69.67,0,0,1,4.61-6.69c.5-.64,1-1.29,1.5-1.91a67,67,0,0,1,6.86-7.08c.47-.41,1-.76,1.46-1.15a62.59,62.59,0,0,1,6.3-4.59c1.22-.79,2.49-1.48,3.76-2.17,1.49-.81,3-1.51,4.57-2.18A52.91,52.91,0,0,1,450.33,544a47.23,47.23,0,0,1,13.42.19c.88.13,1.76.23,2.62.4a43.73,43.73,0,0,1,12.2,4.26L388.77,500q-.8-.43-1.61-.83a43.61,43.61,0,0,0-8.36-3.09c-.72-.18-1.5-.19-2.23-.34s-1.73-.27-2.62-.4a47.07,47.07,0,0,0-13.42-.19,52.78,52.78,0,0,0-13.59,4.09c-.53.23-1.12.25-1.65.49-1,.48-1.94,1.15-2.93,1.69-1.27.69-2.53,1.38-3.75,2.16-.6.38-1.26.66-1.85,1.07-1.55,1.07-3,2.31-4.46,3.53-.48.4-1,.74-1.45,1.15-.15.13-.31.23-.46.36a66.52,66.52,0,0,0-5.39,5.45c-.35.4-.65.86-1,1.27-.52.62-1,1.27-1.5,1.91-.75,1-1.56,1.87-2.26,2.87-.86,1.22-1.57,2.54-2.35,3.82-.44.72-.91,1.41-1.32,2.15-.2.36-.45.68-.64,1a69.26,69.26,0,0,0-3.37,7.48c-.17.43-.41.83-.57,1.27l-.07.17a68.41,68.41,0,0,0-1.91,7c-.33,1.33-.83,2.6-1.08,4a64.34,64.34,0,0,0-1.09,11.56c0,15.1,5.48,28.1,14.45,37a44.1,44.1,0,0,0,10,7.51L422.13,653c-14.73-8-24.54-24.4-24.5-44.54a64.34,64.34,0,0,1,1.09-11.56C399,595.52,399.46,594.25,399.8,592.92Z" class="cls-6"></path><path d="M595.12,247.9a27.73,27.73,0,0,0,1.73-9.48c0-8.95-4.25-16.14-10.68-19.64L461,150.72c6.44,3.5,10.7,10.69,10.68,19.64a27.66,27.66,0,0,1-1.81,9.67c0,.07-.07.15-.1.22a30,30,0,0,1-4.73,8.13l-.27.34a28.4,28.4,0,0,1-6.82,5.88c-.27.17-.54.32-.82.47a24,24,0,0,1-8.5,3,20.06,20.06,0,0,1-12.5-2.14L561.29,264l.71.37a19.23,19.23,0,0,0,3.68,1.36,20.83,20.83,0,0,0,8.11.41,23.62,23.62,0,0,0,6.76-2c.61-.28,1.16-.67,1.75-1,.28-.16.54-.3.81-.47s.82-.43,1.2-.69a27.84,27.84,0,0,0,2.79-2.21,29.12,29.12,0,0,0,2.35-2.38c.17-.19.31-.4.47-.6s.18-.24.28-.36c.45-.55.91-1.09,1.32-1.67a28.18,28.18,0,0,0,3.39-6.42c0-.08.08-.16.11-.25S595.09,248,595.12,247.9Z" class="cls-7"></path><polygon points="431.13 258.66 430.99 329.93 520.8 378.78 520.93 307.51 431.13 258.66" class="cls-8"></polygon><path d="M597.89,158.31l-.08,0,.08,0-89.8-48.85q-1.13-.61-2.29-1.18a61.88,61.88,0,0,0-11.87-4.38c-1-.26-2.13-.27-3.17-.48-1.23-.25-2.47-.39-3.73-.57a66.86,66.86,0,0,0-19-.27,76.16,76.16,0,0,0-19.65,5.91c-.76.33-1.62.36-2.36.71-1.37.65-2.62,1.56-4,2.28-1.86,1-3.69,2-5.47,3.15-.91.58-1.9,1-2.79,1.61-2.05,1.42-4,3.07-5.89,4.66-.77.64-1.59,1.19-2.35,1.86-.27.23-.57.42-.83.66a94.92,94.92,0,0,0-7.65,7.72c-.45.51-.83,1.1-1.27,1.62-.72.85-1.36,1.73-2.05,2.61-1.14,1.44-2.35,2.82-3.41,4.33s-2.13,3.44-3.18,5.16c-.64,1-1.31,2-1.91,3.1-.31.55-.69,1-1,1.6a97.58,97.58,0,0,0-4.52,10c-.31.77-.72,1.47-1,2.25,0,.1-.09.19-.13.29a96.34,96.34,0,0,0-2.75,10c-.45,1.82-1.13,3.55-1.48,5.39A91.21,91.21,0,0,0,392.8,194c-.05,28.52,13.9,51.51,34.79,62.87l89.8,48.85c-20.9-11.37-34.84-34.35-34.79-62.87a91.32,91.32,0,0,1,1.55-16.46c.32-1.75,1-3.39,1.4-5.11a95.69,95.69,0,0,1,3-10.6c.29-.78.71-1.49,1-2.26A97.46,97.46,0,0,1,495,196.81c.6-1.07,1.29-2.08,1.93-3.13a97.92,97.92,0,0,1,6.57-9.46c.69-.87,1.35-1.78,2.07-2.63a94.69,94.69,0,0,1,9.75-10c.76-.67,1.58-1.23,2.35-1.87a89.15,89.15,0,0,1,8.67-6.26c1.78-1.14,3.62-2.16,5.47-3.16,2.06-1.11,4.19-2.07,6.33-3a76.25,76.25,0,0,1,19.64-5.91,67,67,0,0,1,19,.27c1.25.18,2.5.32,3.73.57A62.18,62.18,0,0,1,597.89,158.31Z" class="cls-9"></path><path d="M263.77,648.58c.75-6,1.44-12.09,2.54-18,.67-3.59,1.65-7.1,2.44-10.66,1.18-5.3,2.24-10.63,3.69-15.84,1-3.75,2.38-7.39,3.55-11.09,1.53-4.83,3-9.71,4.71-14.45a326.25,326.25,0,0,1,16.92-37.59c1.23-2.37,2.42-4.76,3.72-7.1a329.68,329.68,0,0,1,18.06-29.28c.88-1.27,1.88-2.43,2.78-3.69q8.27-11.5,17.47-22.18c2.12-2.46,4.29-4.87,6.48-7.26q8.63-9.46,18-18.14c1.83-1.71,3.59-3.51,5.46-5.18a304,304,0,0,1,26.57-21c1.78-1.25,3.63-2.38,5.43-3.59a282.22,282.22,0,0,1,36.76-21.2c.94-.44,1.93-.77,2.88-1.21A262.85,262.85,0,0,1,470,390c2.79-.9,5.6-1.77,8.42-2.58a246.28,246.28,0,0,1,33.17-7.32l9.23-1.3L431,329.93l-9.23,1.3a246.35,246.35,0,0,0-33.17,7.32c-2.83.81-5.62,1.67-8.42,2.58a262.44,262.44,0,0,0-28.79,11.07c-.77.35-1.6.55-2.36.91-.18.08-.33.21-.51.3a281.63,281.63,0,0,0-36.76,21.2c-1.06.71-2.24,1.17-3.29,1.9-.75.52-1.4,1.17-2.14,1.7a302.65,302.65,0,0,0-26.56,21c-.5.44-1.07.79-1.57,1.24-1.38,1.24-2.57,2.7-3.93,4q-9.3,8.68-17.93,18.11c-1.19,1.3-2.54,2.43-3.71,3.75-1,1.11-1.8,2.39-2.77,3.52q-9.16,10.66-17.43,22.12c-.75,1-1.64,2-2.37,3-.17.24-.29.5-.46.74a329.93,329.93,0,0,0-18,29.26c-.59,1.07-1.35,2-1.93,3.13-.69,1.28-1.12,2.69-1.79,4a326,326,0,0,0-16.92,37.59c-.08.2-.19.39-.27.6-1.67,4.55-3,9.24-4.45,13.88-1.17,3.7-2.51,7.32-3.55,11.07-1.44,5.22-2.51,10.54-3.69,15.85-.79,3.56-1.78,7.07-2.44,10.66-1.07,5.76-1.72,11.62-2.46,17.46-.41,3.22-1,6.4-1.34,9.64q-1.28,13.65-1.33,27.49c-.18,95.89,46.18,173.62,116,211.62l89.8,48.85c-69.85-38-116.2-115.73-116-211.62q0-13.84,1.33-27.51C262.81,654.58,263.4,651.6,263.77,648.58Z" class="cls-10"></path><path d="M907.68,489.08c-20,2.81-36.33,22.7-36.37,44.56l-.24,131.71c0,21.86,16.23,37.17,36.22,34.36,20.25-2.85,36.59-22.74,36.63-44.6l.24-131.71C944.21,501.54,927.93,486.23,907.68,489.08Z" class="cls-11"></path><path d="M663.89,514c-29.19,4.1-52.91,33-53,64.49-.06,31.23,23.56,53.45,52.76,49.35,28.93-4.07,52.65-32.94,52.71-64.17C716.45,532.13,692.82,509.91,663.89,514Z" class="cls-12"></path><path d="M617.85,669.43l-122,17.15c-16,2.25-29.22,18-29.25,35.61,0,17.32,13.09,29.66,29.14,27.41l122-17.15c16-2.26,29.22-18.3,29.25-35.61C647,679.24,633.9,667.18,617.85,669.43Z" class="cls-13"></path><path d="M603.13,367.21l-9.23,1.3.13-71.27c22.89-17,38.53-45.36,38.58-75.46.08-44.93-33.58-76.16-74.8-70.36s-75.12,46.52-75.21,91.45c-.06,30.1,15.48,54,38.32,64.65l-.13,71.27-9.23,1.3c-137.81,19.37-250.1,156.3-250.38,305S372.76,939.07,510.57,919.7l91.56-12.87c137.81-19.37,250.31-156,250.58-304.85S740.94,347.84,603.13,367.21ZM557.72,208.43c12.18-1.71,22,7.54,22,20.84,0,12.85-9.9,24.87-22.08,26.59s-22.15-7.52-22.13-20.37C535.53,222.19,545.41,210.16,557.72,208.43Zm44.55,619.82-91.56,12.87c-97.51,13.7-176.51-60.94-176.31-166.3s79.51-202.14,177-215.84L603,446.12C700.5,432.41,779.8,507,779.61,612.26S699.79,814.55,602.28,828.26Z" class="cls-14"></path><path d="M503.09,593.61c.06-31.51-23.56-53.73-52.76-49.63-28.93,4.07-52.65,32.94-52.71,64.45-.06,31.23,23.56,53.45,52.5,49.38C479.32,653.71,503,624.84,503.09,593.61Z" class="cls-15"></path><path d="M206.51,587.62c-20.25,2.85-36.59,22.74-36.63,44.6l-.24,131.71c0,21.86,16.23,37.17,36.49,34.32,20-2.81,36.59-22.74,36.63-44.6L243,621.94C243,600.08,226.5,584.81,206.51,587.62Z" class="cls-16"></path><path d="M172.77,616.48c0-.06.06-.12.08-.18a47.78,47.78,0,0,1,7.55-13.13c.16-.2.32-.41.48-.61a44.76,44.76,0,0,1,10.82-9.37c.42-.26.86-.5,1.29-.75a38.15,38.15,0,0,1,13.53-4.83,31.94,31.94,0,0,1,11.55.6A30.43,30.43,0,0,1,226,591l-89.8-48.85c-.37-.2-.74-.39-1.12-.58a30.14,30.14,0,0,0-5.8-2.14c-.31-.08-.69,0-1-.05a31.84,31.84,0,0,0-11.55-.6A37.6,37.6,0,0,0,106,542c-1,.45-1.85,1.07-2.78,1.6-.44.25-.86.47-1.28.74-.62.38-1.28.67-1.87,1.08a44,44,0,0,0-4.4,3.48,45.86,45.86,0,0,0-3.71,3.75c-.29.33-.55.71-.83,1.05s-.32.41-.48.62c-.66.82-1.35,1.62-2,2.48a47.86,47.86,0,0,0-3,4.81A48.58,48.58,0,0,0,83,567.45c0,.06-.06.11-.08.18l-.06.12a45.54,45.54,0,0,0-2.83,15.61l-.24,131.71a40.62,40.62,0,0,0,1.2,10,32.43,32.43,0,0,0,15.71,21l89.8,48.85c-10.2-5.55-16.93-16.9-16.91-31l.24-131.71A45.53,45.53,0,0,1,172.77,616.48Z" class="cls-17"></path></svg>
\ No newline at end of file
import { GET, POST, DELETE } from './request'; import { GET, POST, DELETE } from './request';
import { ChatItemType, ChatSiteType, ChatSiteItemType } from '@/types/chat'; import { ChatItemType, ChatSiteType, ChatSiteItemType } from '@/types/chat';
import axios from 'axios';
/** /**
* 获取一个聊天框的ID * 获取一个聊天框的ID
...@@ -56,7 +57,7 @@ export const postChatGptPrompt = ({ ...@@ -56,7 +57,7 @@ export const postChatGptPrompt = ({
}); });
/* 获取 Chat 的 Event 对象,进行持续通信 */ /* 获取 Chat 的 Event 对象,进行持续通信 */
export const getChatGPTSendEvent = (chatId: string, windowId: string) => export const getChatGPTSendEvent = (chatId: string, windowId: string) =>
new EventSource(`/api/chat/chatGpt?chatId=${chatId}&windowId=${windowId}`); new EventSource(`/api/chat/chatGpt?chatId=${chatId}&windowId=${windowId}&date=${Date.now()}`);
/** /**
* 删除最后一句 * 删除最后一句
......
...@@ -34,7 +34,7 @@ function responseSuccess(response: AxiosResponse<ResponseDataType>) { ...@@ -34,7 +34,7 @@ function responseSuccess(response: AxiosResponse<ResponseDataType>) {
*/ */
function checkRes(data: ResponseDataType) { function checkRes(data: ResponseDataType) {
if (data === undefined) { if (data === undefined) {
console.log(data, 'data is empty'); console.error(data, 'data is empty');
return Promise.reject('服务器异常'); return Promise.reject('服务器异常');
} else if (data.code < 200 || data.code >= 400) { } else if (data.code < 200 || data.code >= 400) {
return Promise.reject(data.message); return Promise.reject(data.message);
...@@ -49,21 +49,20 @@ function responseError(err: any) { ...@@ -49,21 +49,20 @@ function responseError(err: any) {
console.error('请求错误', err); console.error('请求错误', err);
if (!err) { if (!err) {
return Promise.reject('未知错误'); return Promise.reject({ message: '未知错误' });
} }
if (typeof err === 'string') { if (typeof err === 'string') {
return Promise.reject(err); return Promise.reject({ message: err });
} }
if (err.response) { if (err.response) {
// 有报错响应 // 有报错响应
const res = err.response; const res = err.response;
/* token过期,判断请求token与本地是否相同,若不同需要重发 */
if (res.data.code in TOKEN_ERROR_CODE) { if (res.data.code in TOKEN_ERROR_CODE) {
clearToken(); clearToken();
return Promise.reject('token过期,重新登录'); return Promise.reject({ message: 'token过期,重新登录' });
} }
} }
return Promise.reject('未知错误'); return Promise.reject(err);
} }
/* 创建请求实例 */ /* 创建请求实例 */
......
...@@ -7,6 +7,7 @@ import { useGlobalStore } from '@/store/global'; ...@@ -7,6 +7,7 @@ import { useGlobalStore } from '@/store/global';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
const unAuthPage: { [key: string]: boolean } = { const unAuthPage: { [key: string]: boolean } = {
'/': true,
'/login': true, '/login': true,
'/chat': true '/chat': true
}; };
...@@ -24,10 +25,10 @@ const Auth = ({ children }: { children: JSX.Element }) => { ...@@ -24,10 +25,10 @@ const Auth = ({ children }: { children: JSX.Element }) => {
useQuery( useQuery(
[router.pathname, userInfo], [router.pathname, userInfo],
() => { () => {
setLoading(true);
if (unAuthPage[router.pathname] === true || userInfo) { if (unAuthPage[router.pathname] === true || userInfo) {
return setLoading(false); return setLoading(false);
} else { } else {
setLoading(true);
return getTokenLogin(); return getTokenLogin();
} }
}, },
...@@ -38,7 +39,7 @@ const Auth = ({ children }: { children: JSX.Element }) => { ...@@ -38,7 +39,7 @@ const Auth = ({ children }: { children: JSX.Element }) => {
} }
}, },
onError(error) { onError(error) {
console.log(error); console.error(error);
router.push('/login'); router.push('/login');
toast(); toast();
}, },
...@@ -48,7 +49,7 @@ const Auth = ({ children }: { children: JSX.Element }) => { ...@@ -48,7 +49,7 @@ const Auth = ({ children }: { children: JSX.Element }) => {
} }
); );
return userInfo || unAuthPage[router.pathname] === true ? <>{children}</> : null; return userInfo || unAuthPage[router.pathname] === true ? children : null;
}; };
export default Auth; export default Auth;
...@@ -43,18 +43,16 @@ const navbarList = [ ...@@ -43,18 +43,16 @@ const navbarList = [
const Layout = ({ children }: { children: JSX.Element }) => { const Layout = ({ children }: { children: JSX.Element }) => {
const { isPc } = useScreen(); const { isPc } = useScreen();
const router = useRouter(); const router = useRouter();
const { Loading } = useLoading({ const { Loading } = useLoading({ defaultLoading: true });
defaultLoading: true
});
const { loading } = useGlobalStore(); const { loading } = useGlobalStore();
return ( return (
<> <>
{!unShowLayoutRoute[router.pathname] ? ( {!unShowLayoutRoute[router.pathname] ? (
<Box minHeight={'100vh'} backgroundColor={'gray.100'}> <Box h={'100%'} backgroundColor={'gray.100'} overflow={'auto'}>
{isPc ? ( {isPc ? (
<> <>
<Box h={'100vh'} position={'fixed'} left={0} top={0} w={'80px'}> <Box h={'100%'} position={'fixed'} left={0} top={0} w={'80px'}>
<Navbar navbarList={navbarList} /> <Navbar navbarList={navbarList} />
</Box> </Box>
<Box ml={'80px'} p={7}> <Box ml={'80px'} p={7}>
......
...@@ -3,7 +3,6 @@ import { Box, Flex } from '@chakra-ui/react'; ...@@ -3,7 +3,6 @@ import { Box, Flex } from '@chakra-ui/react';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Icon from '../Icon'; import Icon from '../Icon';
import styles from './style.module.scss';
export enum NavbarTypeEnum { export enum NavbarTypeEnum {
normal = 'normal', normal = 'normal',
...@@ -35,7 +34,7 @@ const Navbar = ({ ...@@ -35,7 +34,7 @@ const Navbar = ({
> >
{/* logo */} {/* logo */}
<Box pb={4}> <Box pb={4}>
<Image src={'/logo.svg'} width={50} height={100} alt=""></Image> <Image src={'/icon/logo.png'} width={'35'} height={'35'} alt=""></Image>
</Box> </Box>
{/* 导航列表 */} {/* 导航列表 */}
<Box flex={1}> <Box flex={1}>
...@@ -47,6 +46,7 @@ const Navbar = ({ ...@@ -47,6 +46,7 @@ const Navbar = ({
alignItems={'center'} alignItems={'center'}
justifyContent={'center'} justifyContent={'center'}
onClick={() => onClick={() =>
!item.activeLink.includes(router.pathname) &&
router.push(item.link, undefined, { router.push(item.link, undefined, {
shallow: true shallow: true
}) })
......
...@@ -45,15 +45,15 @@ const NavbarPhone = ({ ...@@ -45,15 +45,15 @@ const NavbarPhone = ({
</Flex> </Flex>
<Drawer isOpen={isOpen} placement="left" size={'xs'} onClose={onClose}> <Drawer isOpen={isOpen} placement="left" size={'xs'} onClose={onClose}>
<DrawerOverlay /> <DrawerOverlay />
<DrawerContent maxWidth={'60vw'}> <DrawerContent maxWidth={'50vw'}>
<DrawerBody p={4}> <DrawerBody p={4}>
<Box pb={4}> <Box py={4}>
<Image src={'/logo.svg'} w={'100%'} h={'70px'} pt={2} alt=""></Image> <Image src={'/icon/logo.png'} margin={'auto'} w={'35'} h={'35'} alt=""></Image>
</Box> </Box>
{navbarList.map((item) => ( {navbarList.map((item) => (
<Flex <Flex
key={item.label} key={item.label}
mb={4} mb={5}
alignItems={'center'} alignItems={'center'}
justifyContent={'center'} justifyContent={'center'}
onClick={() => { onClick={() => {
...@@ -61,8 +61,7 @@ const NavbarPhone = ({ ...@@ -61,8 +61,7 @@ const NavbarPhone = ({
onClose(); onClose();
}} }}
cursor={'pointer'} cursor={'pointer'}
fontSize={'sm'} h={'60px'}
h={'65px'}
borderRadius={'md'} borderRadius={'md'}
{...(item.activeLink.includes(router.pathname) {...(item.activeLink.includes(router.pathname)
? { ? {
......
...@@ -27,96 +27,356 @@ ...@@ -27,96 +27,356 @@
opacity: 1; opacity: 1;
} }
} }
.markdown {
/* 标题样式 */
h1 {
font-size: 1.8rem;
}
h2 {
font-size: 1.6rem;
}
h3 {
font-size: 1.4rem;
}
h4 {
font-size: 1.2rem;
}
h5 { .markdown > *:first-child {
font-size: 1rem; margin-top: 0 !important;
} }
.markdown > *:last-child {
h6 { margin-bottom: 0 !important;
font-size: 0.83rem; }
} .markdown a.absent {
color: #cc0000;
/* 列表样式 */ }
ol, .markdown a.anchor {
ul { bottom: 0;
padding-left: 1.5rem; cursor: pointer;
margin-left: 1rem; display: block;
} left: 0;
ul { margin-left: -30px;
list-style: inside; padding-left: 30px;
} position: absolute;
ol { top: 0;
list-style: decimal; }
} .markdown h1,
.markdown h2,
/* 链接样式 */ .markdown h3,
a { .markdown h4,
color: #0077cc; .markdown h5,
.markdown h6 {
cursor: text;
font-weight: bold;
margin: 20px 0 10px;
padding: 0;
position: relative;
}
.markdown h1 .mini-icon-link,
.markdown h2 .mini-icon-link,
.markdown h3 .mini-icon-link,
.markdown h4 .mini-icon-link,
.markdown h5 .mini-icon-link,
.markdown h6 .mini-icon-link {
color: #000000;
display: none;
}
.markdown h1:hover a.anchor,
.markdown h2:hover a.anchor,
.markdown h3:hover a.anchor,
.markdown h4:hover a.anchor,
.markdown h5:hover a.anchor,
.markdown h6:hover a.anchor {
line-height: 1;
margin-left: -22px;
padding-left: 0;
text-decoration: none; text-decoration: none;
border-bottom: 1px solid #0077cc; top: 15%;
} }
.markdown h1:hover a.anchor .mini-icon-link,
a:hover { .markdown h2:hover a.anchor .mini-icon-link,
color: #005580; .markdown h3:hover a.anchor .mini-icon-link,
border-bottom-color: #005580; .markdown h4:hover a.anchor .mini-icon-link,
} .markdown h5:hover a.anchor .mini-icon-link,
.markdown h6:hover a.anchor .mini-icon-link {
/* 图片样式 */ display: inline-block;
img { }
max-width: 100%; .markdown h1 tt,
max-height: 200px; .markdown h1 code,
margin: auto; .markdown h2 tt,
} .markdown h2 code,
.markdown h3 tt,
/* 强调样式 */ .markdown h3 code,
em, .markdown h4 tt,
i { .markdown h4 code,
.markdown h5 tt,
.markdown h5 code,
.markdown h6 tt,
.markdown h6 code {
font-size: inherit;
}
.markdown h1 {
color: #000000;
font-size: 28px;
}
.markdown h2 {
color: #000000;
font-size: 24px;
}
.markdown h3 {
font-size: 18px;
}
.markdown h4 {
font-size: 16px;
}
.markdown h5 {
font-size: 14px;
}
.markdown h6 {
color: #777777;
font-size: 14px;
}
.markdown p,
.markdown blockquote,
.markdown ul,
.markdown ol,
.markdown dl,
.markdown table,
.markdown pre {
margin: 15px 0;
}
.markdown hr {
background: url('https://a248.e.akamai.net/assets.github.com/assets/primer/markdown/dirty-shade-350cca8f57223ebd53603021b2e670f4f319f1b7.png')
repeat-x scroll 0 0 transparent;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}
.markdown > h2:first-child,
.markdown > h1:first-child,
.markdown > h1:first-child + h2,
.markdown > h3:first-child,
.markdown > h4:first-child,
.markdown > h5:first-child,
.markdown > h6:first-child {
margin-top: 0;
padding-top: 0;
}
.markdown a:first-child h1,
.markdown a:first-child h2,
.markdown a:first-child h3,
.markdown a:first-child h4,
.markdown a:first-child h5,
.markdown a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
.markdown h1 + p,
.markdown h2 + p,
.markdown h3 + p,
.markdown h4 + p,
.markdown h5 + p,
.markdown h6 + p {
margin-top: 0;
}
.markdown li p.first {
display: inline-block;
}
.markdown ul,
.markdown ol {
padding-left: 30px;
}
.markdown ul.no-list,
.markdown ol.no-list {
list-style-type: none;
padding: 0;
}
.markdown ul li > *:first-child,
.markdown ol li > *:first-child {
margin-top: 0;
}
.markdown ul ul,
.markdown ul ol,
.markdown ol ol,
.markdown ol ul {
margin-bottom: 0;
}
.markdown dl {
padding: 0;
}
.markdown dl dt {
font-size: 14px;
font-style: italic; font-style: italic;
}
strong,
b {
font-weight: bold; font-weight: bold;
} margin: 15px 0 5px;
padding: 0;
/* 代码样式 */ }
code { .markdown dl dt:first-child {
border-radius: 3px; padding: 0;
width: 100%; }
} .markdown dl dt > *:first-child {
margin-top: 0;
}
.markdown dl dt > *:last-child {
margin-bottom: 0;
}
.markdown dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
.markdown dl dd > *:first-child {
margin-top: 0;
}
.markdown dl dd > *:last-child {
margin-bottom: 0;
}
.markdown blockquote {
border-left: 4px solid #dddddd;
color: #777777;
padding: 0 15px;
}
.markdown blockquote > *:first-child {
margin-top: 0;
}
.markdown blockquote > *:last-child {
margin-bottom: 0;
}
.markdown table th {
font-weight: bold;
}
.markdown table th,
.markdown table td {
border: 1px solid #cccccc;
padding: 6px 13px;
}
.markdown table tr {
background-color: #ffffff;
border-top: 1px solid #cccccc;
}
.markdown table tr:nth-child(2n) {
background-color: #f0f0f0;
}
.markdown img {
max-width: 100%;
}
.markdown span.frame {
display: block;
overflow: hidden;
}
.markdown span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
margin: 13px 0 0;
overflow: hidden;
padding: 7px;
width: auto;
}
.markdown span.frame span img {
display: block;
float: left;
}
.markdown span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0;
}
.markdown span.align-center {
clear: both;
display: block;
overflow: hidden;
}
.markdown span.align-center > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: center;
}
.markdown span.align-center span img {
margin: 0 auto;
text-align: center;
}
.markdown span.align-right {
clear: both;
display: block;
overflow: hidden;
}
.markdown span.align-right > span {
display: block;
margin: 13px 0 0;
overflow: hidden;
text-align: right;
}
.markdown span.align-right span img {
margin: 0;
text-align: right;
}
.markdown span.float-left {
display: block;
float: left;
margin-right: 13px;
overflow: hidden;
}
.markdown span.float-left span {
margin: 13px 0 0;
}
.markdown span.float-right {
display: block;
float: right;
margin-left: 13px;
overflow: hidden;
}
.markdown span.float-right > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: right;
}
.markdown code,
.markdown tt {
background-color: #f0f0f0;
border: 1px solid #eaeaea;
border-radius: 3px 3px 3px 3px;
margin: 0 2px;
padding: 0 5px;
}
.markdown pre > code {
background: none repeat scroll 0 0 transparent;
border: medium none;
margin: 0;
padding: 0;
white-space: pre;
}
.markdown .highlight pre,
.markdown pre {
background-color: #f0f0f0;
border: 1px solid #cccccc;
border-radius: 3px 3px 3px 3px;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
}
.markdown pre code,
.markdown pre tt {
background-color: transparent;
border: medium none;
}
.markdown {
font-size: 14px;
line-height: 1.6;
letter-spacing: 0.5px;
text-align: justify;
pre { pre {
padding: 10px 15px; display: block;
width: 100%; width: 100%;
padding: 15px;
margin: 0;
border: none;
border-radius: 0;
background-color: #222 !important; background-color: #222 !important;
overflow-x: auto; overflow-x: auto;
} }
pre code { pre code {
display: block;
border: none;
background-color: #222; background-color: #222;
color: #fff; color: #fff;
width: 100%;
font-family: 'Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji';
} }
p { a {
line-height: 1.7; text-decoration: underline;
color: var(--chakra-colors-blue-600);
} }
} }
import React, { useMemo, memo } from 'react'; import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { codeLight } from './codeLight'; import { codeLight } from './codeLight';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools'; import { useCopyData } from '@/utils/tools';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean }) => { const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean }) => {
// const formatSource = useMemo(() => source.replace(/\n/g, '\n'), [source]); const formatSource = useMemo(() => source.replace(/\n/g, ' \n'), [source]);
const { copyData } = useCopyData(); const { copyData } = useCopyData();
return ( return (
<ReactMarkdown <ReactMarkdown
className={`${styles.markdown} ${ className={`${styles.markdown} ${
isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : '' isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''
}`} }`}
rehypePlugins={[remarkGfm]} remarkPlugins={[remarkMath]}
skipHtml={true} rehypePlugins={[remarkGfm, rehypeKatex]}
components={{ components={{
p: 'div',
pre: 'div', pre: 'div',
code({ node, inline, className, children, ...props }) { code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || ''); const match = /language-(\w+)/.exec(className || '');
const code = String(children).replace(/\n$/, ''); const code = String(children).replace(/\n$/, '');
return !inline ? (
return (
<Box my={3} borderRadius={'md'} overflow={'hidden'}> <Box my={3} borderRadius={'md'} overflow={'hidden'}>
<Flex py={2} px={5} backgroundColor={'#323641'} color={'#fff'} fontSize={'sm'}> <Flex
py={2}
px={5}
backgroundColor={'#323641'}
color={'#fff'}
fontSize={'sm'}
userSelect={'none'}
>
<Box flex={1}>{match?.[1]}</Box> <Box flex={1}>{match?.[1]}</Box>
<Flex cursor={'pointer'} onClick={() => copyData(code)} alignItems={'center'}> <Flex cursor={'pointer'} onClick={() => copyData(code)} alignItems={'center'}>
<Icon name={'icon-fuzhi'} width={15} height={15} color={'#fff'}></Icon> <Icon name={'icon-fuzhi'} width={15} height={15} color={'#fff'}></Icon>
...@@ -36,18 +44,23 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean ...@@ -36,18 +44,23 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean
</Flex> </Flex>
<SyntaxHighlighter <SyntaxHighlighter
style={codeLight as any} style={codeLight as any}
showLineNumbers
language={match?.[1]} language={match?.[1]}
PreTag="pre"
{...props} {...props}
> >
{code} {code}
</SyntaxHighlighter> </SyntaxHighlighter>
</Box> </Box>
) : (
<code className={className} {...props}>
{children}
</code>
); );
} }
}} }}
linkTarget="_blank"
> >
{source} {formatSource}
</ReactMarkdown> </ReactMarkdown>
); );
}; };
......
...@@ -6,8 +6,7 @@ export enum EmailTypeEnum { ...@@ -6,8 +6,7 @@ export enum EmailTypeEnum {
export const introPage = ` export const introPage = `
## 欢迎使用 Doc GPT ## 欢迎使用 Doc GPT
时间比较赶,介绍没来得及完善,先直接上怎么使用: 时间比较赶,介绍没来得及完善,先直接上怎么使用:
1. 使用邮箱注册账号。 1. 使用邮箱注册账号。
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。 2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
3. 进入模型页,创建一个模型,建议直接用 ChatGPT。 3. 进入模型页,创建一个模型,建议直接用 ChatGPT。
...@@ -39,6 +38,5 @@ export const introPage = ` ...@@ -39,6 +38,5 @@ export const introPage = `
* 分享链接应为:http://docgpt.ahapocket.cn/chat?chatId=6402c9f64cb5d6283f764 * 分享链接应为:http://docgpt.ahapocket.cn/chat?chatId=6402c9f64cb5d6283f764
### 其他问题 ### 其他问题
还有其他问题,可以加我 wx,拉个交流群大家一起聊聊。 还有其他问题,可以加我 wx: YNyiqi,拉个交流群大家一起聊聊。
![](/imgs/erweima.jpg)
`; `;
...@@ -58,7 +58,11 @@ export const theme = extendTheme({ ...@@ -58,7 +58,11 @@ export const theme = extendTheme({
global: { global: {
'html, body': { 'html, body': {
color: 'blackAlpha.800', color: 'blackAlpha.800',
fontSize: '14px' fontSize: '14px',
fontFamily:
'Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji',
height: '100%',
overflowY: 'auto'
} }
} }
}, },
......
import { useState, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { import {
AlertDialog, AlertDialog,
AlertDialogBody, AlertDialogBody,
...@@ -17,12 +17,16 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont ...@@ -17,12 +17,16 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont
const cancelCb = useRef<any>(); const cancelCb = useRef<any>();
return { return {
openConfirm: (confirm?: any, cancel?: any) => { openConfirm: useCallback(
(confirm?: any, cancel?: any) => {
onOpen(); onOpen();
confirmCb.current = confirm; confirmCb.current = confirm;
cancelCb.current = cancel; cancelCb.current = cancel;
}, },
ConfirmChild: () => ( [onOpen]
),
ConfirmChild: useCallback(
() => (
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}> <AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
<AlertDialogOverlay> <AlertDialogOverlay>
<AlertDialogContent> <AlertDialogContent>
...@@ -44,7 +48,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont ...@@ -44,7 +48,7 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont
</Button> </Button>
<Button <Button
colorScheme="blue" colorScheme="blue"
ml={3} ml={4}
onClick={() => { onClick={() => {
onClose(); onClose();
typeof confirmCb.current === 'function' && confirmCb.current(); typeof confirmCb.current === 'function' && confirmCb.current();
...@@ -56,6 +60,8 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont ...@@ -56,6 +60,8 @@ export const useConfirm = ({ title = '提示', content }: { title?: string; cont
</AlertDialogContent> </AlertDialogContent>
</AlertDialogOverlay> </AlertDialogOverlay>
</AlertDialog> </AlertDialog>
),
[content, isOpen, onClose, title]
) )
}; };
}; };
import { useState } from 'react'; import { useState, useCallback } from 'react';
import { Spinner, Flex } from '@chakra-ui/react'; import { Spinner, Flex } from '@chakra-ui/react';
export const useLoading = (props?: { defaultLoading: boolean }) => { export const useLoading = (props?: { defaultLoading: boolean }) => {
const [isLoading, setIsLoading] = useState(props?.defaultLoading || false); const [isLoading, setIsLoading] = useState(props?.defaultLoading || false);
const Loading = ({ const Loading = useCallback(
loading, ({ loading, fixed = true }: { loading?: boolean; fixed?: boolean }): JSX.Element | null => {
fixed = true
}: {
loading?: boolean;
fixed?: boolean;
}): JSX.Element | null => {
return isLoading || loading ? ( return isLoading || loading ? (
<Flex <Flex
position={fixed ? 'fixed' : 'absolute'} position={fixed ? 'fixed' : 'absolute'}
...@@ -26,7 +21,9 @@ export const useLoading = (props?: { defaultLoading: boolean }) => { ...@@ -26,7 +21,9 @@ export const useLoading = (props?: { defaultLoading: boolean }) => {
<Spinner thickness="4px" speed="0.65s" emptyColor="gray.200" color="blue.500" size="xl" /> <Spinner thickness="4px" speed="0.65s" emptyColor="gray.200" color="blue.500" size="xl" />
</Flex> </Flex>
) : null; ) : null;
}; },
[isLoading]
);
return { return {
isLoading, isLoading,
......
...@@ -11,6 +11,6 @@ export function useScreen() { ...@@ -11,6 +11,6 @@ export function useScreen() {
isPc, isPc,
mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]), mediaLgMd: useMemo(() => (isPc ? 'lg' : 'md'), [isPc]),
mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]), mediaMdSm: useMemo(() => (isPc ? 'md' : 'sm'), [isPc]),
media: (pc: number | string, phone: number | string) => (isPc ? pc : phone) media: (pc: any, phone: any) => (isPc ? pc : phone)
}; };
} }
import type { AppProps, NextWebVitalsMetric } from 'next/app'; import type { AppProps, NextWebVitalsMetric } from 'next/app';
import Script from 'next/script';
import Head from 'next/head'; import Head from 'next/head';
import { ChakraProvider } from '@chakra-ui/react'; import { ChakraProvider } from '@chakra-ui/react';
import Layout from '@/components/Layout'; import Layout from '@/components/Layout';
import { theme } from '@/constants/theme'; import { theme } from '@/constants/theme';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import NProgress from 'nprogress'; //nprogress module
import Router from 'next/router';
import 'nprogress/nprogress.css';
import '../styles/reset.scss'; import '../styles/reset.scss';
export default function App({ Component, pageProps }: AppProps) { //Binding events.
// Create a client Router.events.on('routeChangeStart', () => NProgress.start());
const queryClient = new QueryClient({ Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
// Create a client
const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
...@@ -16,8 +24,9 @@ export default function App({ Component, pageProps }: AppProps) { ...@@ -16,8 +24,9 @@ export default function App({ Component, pageProps }: AppProps) {
cacheTime: 0 cacheTime: 0
} }
} }
}); });
export default function App({ Component, pageProps }: AppProps) {
return ( return (
<> <>
<Head> <Head>
...@@ -28,8 +37,8 @@ export default function App({ Component, pageProps }: AppProps) { ...@@ -28,8 +37,8 @@ export default function App({ Component, pageProps }: AppProps) {
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;"
/> />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<script src="/iconfont.js" async></script>
</Head> </Head>
<Script src="/iconfont.js" strategy="afterInteractive"></Script>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}> <ChakraProvider theme={theme}>
<Layout> <Layout>
......
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase, Chat, ChatWindow } from '@/service/mongo'; import { Readable } from 'stream';
import { connectToDatabase, ChatWindow } from '@/service/mongo';
import type { ModelType } from '@/types/model'; import type { ModelType } from '@/types/model';
import { getOpenAIApi, authChat } from '@/service/utils/chat'; import { getOpenAIApi, authChat } from '@/service/utils/chat';
import { openaiProxy } from '@/service/utils/tools'; import { openaiProxy } from '@/service/utils/tools';
...@@ -9,12 +9,23 @@ import { ChatItemType } from '@/types/chat'; ...@@ -9,12 +9,23 @@ import { ChatItemType } from '@/types/chat';
/* 发送提示词 */ /* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
res.writeHead(200, { res.setHeader('Connection', 'keep-alive');
Connection: 'keep-alive', res.setHeader('Cache-Control', 'no-cache');
'Content-Encoding': 'none', res.setHeader('Content-Type', 'text/event-stream');
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream' const responseData: string[] = [];
const stream = new Readable({
read(size) {
const data = responseData.shift() || null;
this.push(data);
}
});
res.on('close', () => {
res.end();
stream.destroy();
}); });
const { chatId, windowId } = req.query as { chatId: string; windowId: string }; const { chatId, windowId } = req.query as { chatId: string; windowId: string };
try { try {
...@@ -47,9 +58,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -47,9 +58,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map( const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
(item: ChatItemType) => ({ (item: ChatItemType) => ({
role: map[item.obj], role: map[item.obj],
content: item.value content: item.value.replace(/(\n| )/g, '')
}) })
); );
// 第一句话,强调代码类型
formatPrompts.unshift({
role: ChatCompletionRequestMessageRoleEnum.System,
content:
'If the content is code or code blocks, please mark the code type as accurately as possible!'
});
// 获取 chatAPI // 获取 chatAPI
const chatAPI = getOpenAIApi(userApiKey); const chatAPI = getOpenAIApi(userApiKey);
...@@ -68,8 +85,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -68,8 +85,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const reg = /{"content"(.*)"}/g; const reg = /{"content"(.*)"}/g;
// @ts-ignore // @ts-ignore
const match = chatResponse.data.match(reg); const match = chatResponse.data.match(reg);
if (!match) return;
let AIResponse = ''; let AIResponse = '';
if (match) {
// 循环给 stream push 内容
match.forEach((item: string, i: number) => { match.forEach((item: string, i: number) => {
try { try {
const json = JSON.parse(item); const json = JSON.parse(item);
...@@ -77,15 +97,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -77,15 +97,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (i === 0 && json.content?.startsWith('\n')) return; if (i === 0 && json.content?.startsWith('\n')) return;
AIResponse += json.content; AIResponse += json.content;
const content = json.content.replace(/\n/g, '<br/>'); // 无法直接传输\n const content = json.content.replace(/\n/g, '<br/>'); // 无法直接传输\n
content && res.write(`data: ${content}\n\n`); if (content) {
responseData.push(`event: responseData\ndata: ${content}\n\n`);
// res.write(`event: responseData\n`)
// res.write(`data: ${content}\n\n`)
}
} catch (err) { } catch (err) {
err; err;
} }
}); });
}
res.write(`data: [DONE]\n\n`);
responseData.push(`event: done\ndata: \n\n`);
// 存入库 // 存入库
(async () => {
await ChatWindow.findByIdAndUpdate(windowId, { await ChatWindow.findByIdAndUpdate(windowId, {
$push: { $push: {
content: { content: {
...@@ -95,16 +119,41 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -95,16 +119,41 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}, },
updateTime: Date.now() updateTime: Date.now()
}); });
})();
res.end();
} catch (err: any) { } catch (err: any) {
console.log(err?.response?.data || err); let errorText = err;
if (err.code === 'ECONNRESET') {
errorText = '服务器代理出错';
} else {
switch (err?.response?.data?.error?.code) {
case 'invalid_api_key':
errorText = 'API-KEY不合法';
break;
case 'context_length_exceeded':
errorText = '内容超长了,请重置对话';
break;
case 'rate_limit_reached':
errorText = '同时访问用户过多,请稍后再试';
break;
case null:
errorText = 'OpenAI 服务器访问超时';
break;
default:
errorText = '服务器异常';
}
}
console.error(errorText);
responseData.push(`event: serviceError\ndata: ${errorText}\n\n`);
// 删除最一条数据库记录, 也就是预发送的那一条 // 删除最一条数据库记录, 也就是预发送的那一条
(async () => {
await ChatWindow.findByIdAndUpdate(windowId, { await ChatWindow.findByIdAndUpdate(windowId, {
$pop: { content: 1 }, $pop: { content: 1 },
updateTime: Date.now() updateTime: Date.now()
}); });
})();
res.end();
} }
// 开启 stream 传输
stream.pipe(res);
} }
...@@ -23,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -23,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
// 安全校验 // 安全校验
if (chat.loadAmount === 0 || chat.expiredTime < Date.now()) { if (!chat || chat.loadAmount === 0 || chat.expiredTime < Date.now()) {
throw new Error('聊天框已过期'); throw new Error('聊天框已过期');
} }
...@@ -82,7 +82,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -82,7 +82,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
} }
}); });
} catch (err) { } catch (err) {
console.log(err);
jsonRes(res, { jsonRes(res, {
code: 500, code: 500,
error: err error: err
......
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
if (req.method !== 'GET') return;
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Encoding': 'none',
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream'
});
let val = 0;
const timer = setInterval(() => {
console.log('发送消息', val);
res.write(`data: ${val++}\n\n`);
if (val > 30) {
clearInterval(timer);
res.write(`data: [DONE]\n\n`);
res.end();
}
}, 500);
}
...@@ -13,15 +13,19 @@ import { Textarea, Box, Flex, Button } from '@chakra-ui/react'; ...@@ -13,15 +13,19 @@ import { Textarea, Box, Flex, Button } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import Markdown from '@/components/Markdown';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading';
import { OpenAiModelEnum } from '@/constants/model'; import { OpenAiModelEnum } from '@/constants/model';
import dynamic from 'next/dynamic';
import { useGlobalStore } from '@/store/global';
const Markdown = dynamic(() => import('@/components/Markdown'));
const textareaMinH = '22px';
const Chat = () => { const Chat = () => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const { media } = useScreen(); const { isPc, media } = useScreen();
const { chatId, windowId } = router.query as { chatId: string; windowId?: string }; const { chatId, windowId } = router.query as { chatId: string; windowId?: string };
const ChatBox = useRef<HTMLDivElement>(null); const ChatBox = useRef<HTMLDivElement>(null);
const TextareaDom = useRef<HTMLTextAreaElement>(null); const TextareaDom = useRef<HTMLTextAreaElement>(null);
...@@ -32,7 +36,7 @@ const Chat = () => { ...@@ -32,7 +36,7 @@ const Chat = () => {
const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]); const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]);
const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]); const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]);
const { Loading } = useLoading(); const { setLoading } = useGlobalStore();
// 滚动到底部 // 滚动到底部
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
...@@ -47,7 +51,14 @@ const Chat = () => { ...@@ -47,7 +51,14 @@ const Chat = () => {
}, []); }, []);
// 初始化聊天框 // 初始化聊天框
useQuery([chatId, windowId], () => (chatId ? getInitChatSiteInfo(chatId, windowId) : null), { useQuery(
[chatId, windowId],
() => {
if (!chatId) return null;
setLoading(true);
return getInitChatSiteInfo(chatId, windowId);
},
{
cacheTime: 5 * 60 * 1000, cacheTime: 5 * 60 * 1000,
onSuccess(res) { onSuccess(res) {
if (!res) return; if (!res) return;
...@@ -61,14 +72,19 @@ const Chat = () => { ...@@ -61,14 +72,19 @@ const Chat = () => {
})) }))
); );
scrollToBottom(); scrollToBottom();
setLoading(false);
}, },
onError() { onError(e: any) {
toast({ toast({
title: '初始化异常', title: e?.message || '初始化异常,请检查地址',
status: 'error' status: 'error',
isClosable: true,
duration: 5000
}); });
setLoading(false);
} }
}); }
);
// gpt3 方法 // gpt3 方法
const gpt3ChatPrompt = useCallback( const gpt3ChatPrompt = useCallback(
...@@ -107,36 +123,55 @@ const Chat = () => { ...@@ -107,36 +123,55 @@ const Chat = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const event = getChatGPTSendEvent(chatId, windowId); const event = getChatGPTSendEvent(chatId, windowId);
event.onmessage = ({ data }) => { // 30s 收不到消息就报错
if (data === '[DONE]') { let timer = setTimeout(() => {
event.close();
reject('服务器超时');
}, 300000);
event.addEventListener('responseData', ({ data }) => {
/* 重置定时器 */
clearTimeout(timer);
timer = setTimeout(() => {
event.close(); event.close();
reject('服务器超时');
}, 300000);
const msg = data.replace(/<br\/>/g, '\n');
setChatList((state) => setChatList((state) =>
state.map((item, index) => { state.map((item, index) => {
if (index !== state.length - 1) return item; if (index !== state.length - 1) return item;
return { return {
...item, ...item,
status: 'finish' value: item.value + msg
}; };
}) })
); );
resolve(''); });
} else if (data) { event.addEventListener('done', () => {
const msg = data.replace(/<br\/>/g, '\n'); clearTimeout(timer);
event.close();
setChatList((state) => setChatList((state) =>
state.map((item, index) => { state.map((item, index) => {
if (index !== state.length - 1) return item; if (index !== state.length - 1) return item;
return { return {
...item, ...item,
value: item.value + msg status: 'finish'
}; };
}) })
); );
} resolve('');
}; });
event.onerror = (err) => { event.addEventListener('serviceError', ({ data: err }) => {
clearTimeout(timer);
event.close();
console.error(err, '==='); console.error(err, '===');
reject(typeof err === 'string' ? err : '对话出现不知名错误~');
});
event.onerror = (err) => {
clearTimeout(timer);
event.close(); event.close();
reject('对话出现错误'); console.error(err);
reject(typeof err === 'string' ? err : '对话出现不知名错误~');
}; };
}); });
}, },
...@@ -179,8 +214,9 @@ const Chat = () => { ...@@ -179,8 +214,9 @@ const Chat = () => {
setTimeout(() => { setTimeout(() => {
scrollToBottom(); scrollToBottom();
/* 回到最小高度 */
if (TextareaDom.current) { if (TextareaDom.current) {
TextareaDom.current.style.height = 22 + 'px'; TextareaDom.current.style.height = textareaMinH;
} }
}, 100); }, 100);
...@@ -242,7 +278,7 @@ const Chat = () => { ...@@ -242,7 +278,7 @@ const Chat = () => {
}, [chatList, windowId]); }, [chatList, windowId]);
return ( return (
<Flex h={'100vh'} flexDirection={'column'} overflowY={'hidden'}> <Flex height={'100%'} flexDirection={'column'}>
{/* 头部 */} {/* 头部 */}
<Flex <Flex
px={4} px={4}
...@@ -258,7 +294,6 @@ const Chat = () => { ...@@ -258,7 +294,6 @@ const Chat = () => {
<Icon name={'icon-zhongzhi'} width={20} height={20} color={'#718096'}></Icon> <Icon name={'icon-zhongzhi'} width={20} height={20} color={'#718096'}></Icon>
</Box> </Box>
{/* 滚动到底部按键 */} {/* 滚动到底部按键 */}
{/* 滚动到底部 */}
{ChatBox.current && ChatBox.current.scrollHeight > 2 * ChatBox.current.clientHeight && ( {ChatBox.current && ChatBox.current.scrollHeight > 2 * ChatBox.current.clientHeight && (
<Box ml={10} cursor={'pointer'} onClick={scrollToBottom}> <Box ml={10} cursor={'pointer'} onClick={scrollToBottom}>
<Icon <Icon
...@@ -281,29 +316,44 @@ const Chat = () => { ...@@ -281,29 +316,44 @@ const Chat = () => {
borderBottom={'1px solid rgba(0,0,0,0.1)'} borderBottom={'1px solid rgba(0,0,0,0.1)'}
> >
<Flex maxW={'800px'} m={'auto'} alignItems={'flex-start'}> <Flex maxW={'800px'} m={'auto'} alignItems={'flex-start'}>
<Box mr={4}> <Box mr={media(4, 1)}>
<Image <Image
src={item.obj === 'Human' ? '/imgs/human.png' : '/imgs/modelAvatar.png'} src={item.obj === 'Human' ? '/icon/human.png' : '/icon/logo.png'}
alt="/imgs/modelAvatar.png" alt="/icon/logo.png"
width={30} width={30}
height={30} height={30}
></Image> />
</Box> </Box>
<Box flex={'1 0 0'} w={0} overflowX={'auto'}> <Box flex={'1 0 0'} w={0} overflowX={'auto'}>
{item.obj === 'AI' ? (
<Markdown <Markdown
source={item.value} source={item.value}
isChatting={isChatting && index === chatList.length - 1} isChatting={isChatting && index === chatList.length - 1}
/> />
) : (
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
)}
</Box> </Box>
</Flex> </Flex>
</Box> </Box>
))} ))}
</Box> </Box>
{/* 空内容提示 */}
{/* {
chatList.length === 0 && (
<>
<Card>
内容太长
</Card>
</>
)
} */}
<Box <Box
m={media('20px auto', '0 auto')} m={media('20px auto', '0 auto')}
w={media('100vw', '100%')} w={media('100vw', '100%')}
maxW={'800px'} maxW={media('800px', 'auto')}
boxShadow={'0 -14px 30px rgba(255,255,255,0.6)'} boxShadow={'0 -14px 30px rgba(255,255,255,0.6)'}
borderTop={media('none', '1px solid rgba(0,0,0,0.1)')}
> >
{lastWordHuman ? ( {lastWordHuman ? (
<Box textAlign={'center'}> <Box textAlign={'center'}>
...@@ -349,12 +399,12 @@ const Chat = () => { ...@@ -349,12 +399,12 @@ const Chat = () => {
onChange={(e) => { onChange={(e) => {
const textarea = e.target; const textarea = e.target;
setInputVal(textarea.value); setInputVal(textarea.value);
textarea.style.height = textareaMinH;
textarea.style.height = textarea.value.split('\n').length * 22 + 'px'; textarea.style.height = `${textarea.scrollHeight}px`;
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
// 触发快捷发送 // 触发快捷发送
if (e.keyCode === 13 && !e.shiftKey) { if (isPc && e.keyCode === 13 && !e.shiftKey) {
sendPrompt(); sendPrompt();
e.preventDefault(); e.preventDefault();
} }
...@@ -382,7 +432,6 @@ const Chat = () => { ...@@ -382,7 +432,6 @@ const Chat = () => {
</Box> </Box>
)} )}
</Box> </Box>
<Loading loading={!chatSiteData} />
</Flex> </Flex>
); );
}; };
......
import React, { useEffect } from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { Card } from '@chakra-ui/react';
import { Card, Text, Box, Heading, Flex } from '@chakra-ui/react';
import Markdown from '@/components/Markdown'; import Markdown from '@/components/Markdown';
import { introPage } from '@/constants/common'; import { introPage } from '@/constants/common';
const Home = () => { const Home = () => {
const router = useRouter();
return ( return (
<Card p={5} lineHeight={2}> <Card p={5} lineHeight={2}>
<Markdown source={introPage} isChatting={false} /> <Markdown source={introPage} isChatting={false} />
......
import React, { useState, Dispatch, useCallback } from 'react'; import React, { useState, Dispatch, useCallback } from 'react';
import { import { FormControl, Box, Input, Button, FormErrorMessage, Flex } from '@chakra-ui/react';
FormControl,
Box,
Input,
Button,
FormErrorMessage,
useToast,
Flex
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { PageTypeEnum } from '../../../constants/user'; import { PageTypeEnum } from '../../../constants/user';
import { postFindPassword } from '@/api/user'; import { postFindPassword } from '@/api/user';
import { useSendCode } from '@/hooks/useSendCode'; import { useSendCode } from '@/hooks/useSendCode';
import type { ResLogin } from '@/api/response/user'; import type { ResLogin } from '@/api/response/user';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useToast } from '@/hooks/useToast';
interface Props { interface Props {
setPageType: Dispatch<`${PageTypeEnum}`>; setPageType: Dispatch<`${PageTypeEnum}`>;
...@@ -28,7 +21,7 @@ interface RegisterType { ...@@ -28,7 +21,7 @@ interface RegisterType {
} }
const RegisterForm = ({ setPageType, loginSuccess }: Props) => { const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
const toast = useToast(); const { toast } = useToast();
const { mediaLgMd } = useScreen(); const { mediaLgMd } = useScreen();
const { const {
register, register,
...@@ -66,8 +59,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { ...@@ -66,8 +59,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
); );
toast({ toast({
title: `密码已找回`, title: `密码已找回`,
status: 'success', status: 'success'
position: 'top'
}); });
} catch (error) { } catch (error) {
typeof error === 'string' && typeof error === 'string' &&
......
.loginPage { .loginPage {
background: url('/icon/login-bg.svg') no-repeat; background: url('/icon/login-bg.svg') no-repeat;
background-size: cover; background-size: cover;
height: 100vh;
width: 100vw;
user-select: none; user-select: none;
} }
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback, useMemo } from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { Box, Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user'; import { PageTypeEnum } from '@/constants/user';
import LoginForm from './components/LoginForm';
import RegisterForm from './components/RegisterForm';
import ForgetPasswordForm from './components/ForgetPasswordForm';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import type { ResLogin } from '@/api/response/user'; import type { ResLogin } from '@/api/response/user';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import dynamic from 'next/dynamic';
const LoginForm = dynamic(() => import('./components/LoginForm'));
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const Login = () => { const Login = () => {
const router = useRouter(); const router = useRouter();
const { isPc } = useScreen(); const { isPc } = useScreen();
...@@ -24,23 +26,20 @@ const Login = () => { ...@@ -24,23 +26,20 @@ const Login = () => {
[router, setUserInfo] [router, setUserInfo]
); );
const map = { function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
[PageTypeEnum.login]: { const TypeMap = {
Component: <LoginForm setPageType={setPageType} loginSuccess={loginSuccess} />, [PageTypeEnum.login]: LoginForm,
img: '/icon/loginLeft.svg' [PageTypeEnum.register]: RegisterForm,
}, [PageTypeEnum.forgetPassword]: ForgetPasswordForm
[PageTypeEnum.register]: {
Component: <RegisterForm setPageType={setPageType} loginSuccess={loginSuccess} />,
img: '/icon/loginLeft.svg'
},
[PageTypeEnum.forgetPassword]: {
Component: <ForgetPasswordForm setPageType={setPageType} loginSuccess={loginSuccess} />,
img: '/icon/loginLeft.svg'
}
}; };
const Component = TypeMap[type];
return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
}
return ( return (
<Box className={styles.loginPage} p={isPc ? '10vh 10vw' : 0}> <Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}>
<Flex <Flex
maxW={'1240px'} maxW={'1240px'}
m={'auto'} m={'auto'}
...@@ -54,7 +53,7 @@ const Login = () => { ...@@ -54,7 +53,7 @@ const Login = () => {
> >
{isPc && ( {isPc && (
<Image <Image
src={map[pageType].img} src={'/icon/loginLeft.svg'}
order={pageType === PageTypeEnum.login ? 0 : 2} order={pageType === PageTypeEnum.login ? 0 : 2}
flex={'1 0 0'} flex={'1 0 0'}
w="0" w="0"
...@@ -76,7 +75,7 @@ const Login = () => { ...@@ -76,7 +75,7 @@ const Login = () => {
px={10} px={10}
borderRadius={isPc ? 'md' : 'none'} borderRadius={isPc ? 'md' : 'none'}
> >
{map[pageType].Component} <DynamicComponent type={pageType} />
</Box> </Box>
</Flex> </Flex>
</Box> </Box>
......
...@@ -25,11 +25,9 @@ interface CreateFormType { ...@@ -25,11 +25,9 @@ interface CreateFormType {
} }
const CreateModel = ({ const CreateModel = ({
isOpen,
setCreateModelOpen, setCreateModelOpen,
onSuccess onSuccess
}: { }: {
isOpen: boolean;
setCreateModelOpen: Dispatch<boolean>; setCreateModelOpen: Dispatch<boolean>;
onSuccess: Dispatch<ModelType>; onSuccess: Dispatch<ModelType>;
}) => { }) => {
...@@ -72,7 +70,7 @@ const CreateModel = ({ ...@@ -72,7 +70,7 @@ const CreateModel = ({
return ( return (
<> <>
<Modal isOpen={isOpen} onClose={() => setCreateModelOpen(false)}> <Modal isOpen={true} onClose={() => setCreateModelOpen(false)}>
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>创建模型</ModalHeader> <ModalHeader>创建模型</ModalHeader>
......
import React, { useCallback } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react'; import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react';
import type { ModelType } from '@/types/model'; import type { ModelType } from '@/types/model';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
...@@ -7,17 +7,17 @@ import { putModelById } from '@/api/model'; ...@@ -7,17 +7,17 @@ import { putModelById } from '@/api/model';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
const ModelEditForm = ({ model }: { model: ModelType }) => { const ModelEditForm = ({ model }: { model?: ModelType }) => {
const isInit = useRef(false);
const { const {
register, register,
handleSubmit, handleSubmit,
reset,
formState: { errors } formState: { errors }
} = useForm<ModelType>({ } = useForm<ModelType>();
defaultValues: model
});
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const { toast } = useToast(); const { toast } = useToast();
const { isPc } = useScreen(); const { media } = useScreen();
const onclickSave = useCallback( const onclickSave = useCallback(
async (data: ModelType) => { async (data: ModelType) => {
...@@ -34,7 +34,7 @@ const ModelEditForm = ({ model }: { model: ModelType }) => { ...@@ -34,7 +34,7 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
status: 'success' status: 'success'
}); });
} catch (err) { } catch (err) {
console.log(err); console.error(err);
toast({ toast({
title: err as string, title: err as string,
status: 'success' status: 'success'
...@@ -61,8 +61,16 @@ const ModelEditForm = ({ model }: { model: ModelType }) => { ...@@ -61,8 +61,16 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
}); });
}, [errors, toast]); }, [errors, toast]);
/* model 只会改变一次 */
useEffect(() => {
if (model && !isInit.current) {
reset(model);
isInit.current = true;
}
}, [model, reset]);
return ( return (
<Grid gridTemplateColumns={isPc ? '1fr 1fr' : '1fr'} gridGap={5}> <Grid gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<Card p={4}> <Card p={4}>
<Flex justifyContent={'space-between'} alignItems={'center'}> <Flex justifyContent={'space-between'} alignItems={'center'}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
...@@ -83,7 +91,7 @@ const ModelEditForm = ({ model }: { model: ModelType }) => { ...@@ -83,7 +91,7 @@ const ModelEditForm = ({ model }: { model: ModelType }) => {
<FormControl mt={5}> <FormControl mt={5}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>对话模型:</Box> <Box flex={'0 0 80px'}>对话模型:</Box>
<Box>{model.service.modelName}</Box> <Box>{model?.service.modelName}</Box>
</Flex> </Flex>
</FormControl> </FormControl>
<FormControl mt={5}> <FormControl mt={5}>
......
import React, { useEffect, useCallback, useState } from 'react'; import React, { useEffect, useCallback, useState } from 'react';
import { Box, Card, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { Box, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import { ModelType } from '@/types/model'; import { ModelType } from '@/types/model';
import { getModelTrainings } from '@/api/model'; import { getModelTrainings } from '@/api/model';
import type { TrainingItemType } from '@/types/training'; import type { TrainingItemType } from '@/types/training';
...@@ -29,7 +29,7 @@ const Training = ({ model }: { model: ModelType }) => { ...@@ -29,7 +29,7 @@ const Training = ({ model }: { model: ModelType }) => {
const res = await getModelTrainings(id); const res = await getModelTrainings(id);
setRecords(res); setRecords(res);
} catch (error) { } catch (error) {
console.log(error); console.error(error);
} }
}, []); }, []);
...@@ -38,7 +38,7 @@ const Training = ({ model }: { model: ModelType }) => { ...@@ -38,7 +38,7 @@ const Training = ({ model }: { model: ModelType }) => {
}, [loadTrainingRecords, model]); }, [loadTrainingRecords, model]);
return ( return (
<Card p={4} h={'100%'}> <>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
训练记录: {model.trainingTimes}次 训练记录: {model.trainingTimes}次
</Box> </Box>
...@@ -63,7 +63,7 @@ const Training = ({ model }: { model: ModelType }) => { ...@@ -63,7 +63,7 @@ const Training = ({ model }: { model: ModelType }) => {
</Tbody> </Tbody>
</Table> </Table>
</TableContainer> </TableContainer>
</Card> </>
); );
}; };
......
...@@ -11,12 +11,14 @@ import { useGlobalStore } from '@/store/global'; ...@@ -11,12 +11,14 @@ import { useGlobalStore } from '@/store/global';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import ModelEditForm from './components/ModelEditForm'; import ModelEditForm from './components/ModelEditForm';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
import Training from './components/Training'; import dynamic from 'next/dynamic';
const Training = dynamic(() => import('./components/Training'));
const ModelDetail = () => { const ModelDetail = () => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const { isPc } = useScreen(); const { isPc, media } = useScreen();
const { setLoading } = useGlobalStore(); const { setLoading } = useGlobalStore();
const { openConfirm, ConfirmChild } = useConfirm({ const { openConfirm, ConfirmChild } = useConfirm({
content: '确认删除该模型?' content: '确认删除该模型?'
...@@ -39,10 +41,8 @@ const ModelDetail = () => { ...@@ -39,10 +41,8 @@ const ModelDetail = () => {
const res = await getModelById(modelId as string); const res = await getModelById(modelId as string);
res.security.expiredTime /= 60 * 60 * 1000; res.security.expiredTime /= 60 * 60 * 1000;
setModel(res); setModel(res);
console.log(res);
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
setLoading(false); setLoading(false);
}, [modelId, setLoading]); }, [modelId, setLoading]);
...@@ -63,7 +63,7 @@ const ModelDetail = () => { ...@@ -63,7 +63,7 @@ const ModelDetail = () => {
}); });
router.replace('/model/list'); router.replace('/model/list');
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
setLoading(false); setLoading(false);
}, [setLoading, model, router, toast]); }, [setLoading, model, router, toast]);
...@@ -77,7 +77,7 @@ const ModelDetail = () => { ...@@ -77,7 +77,7 @@ const ModelDetail = () => {
router.push(`/chat?chatId=${chatId}`); router.push(`/chat?chatId=${chatId}`);
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
setLoading(false); setLoading(false);
}, [setLoading, model, router]); }, [setLoading, model, router]);
...@@ -105,7 +105,7 @@ const ModelDetail = () => { ...@@ -105,7 +105,7 @@ const ModelDetail = () => {
title: typeof err === 'string' ? err : '文件格式错误', title: typeof err === 'string' ? err : '文件格式错误',
status: 'error' status: 'error'
}); });
console.log(err); console.error(err);
} }
setLoading(false); setLoading(false);
}, },
...@@ -121,22 +121,21 @@ const ModelDetail = () => { ...@@ -121,22 +121,21 @@ const ModelDetail = () => {
await putModelTrainingStatus(model._id); await putModelTrainingStatus(model._id);
loadModel(); loadModel();
} catch (error) { } catch (error) {
console.log(error); console.error(error);
} }
setLoading(false); setLoading(false);
}, [setLoading, loadModel, model]); }, [setLoading, loadModel, model]);
return ( return (
<> <>
{!!model && (
<>
{/* 头部 */} {/* 头部 */}
<Card px={6} py={3}> <Card px={6} py={3}>
{isPc ? ( {isPc ? (
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box fontSize={'xl'} fontWeight={'bold'}> <Box fontSize={'xl'} fontWeight={'bold'}>
{model.name} 配置 {model?.name || '模型'} 配置
</Box> </Box>
{!!model && (
<Tag <Tag
ml={2} ml={2}
variant="solid" variant="solid"
...@@ -146,6 +145,7 @@ const ModelDetail = () => { ...@@ -146,6 +145,7 @@ const ModelDetail = () => {
> >
{formatModelStatus[model.status].text} {formatModelStatus[model.status].text}
</Tag> </Tag>
)}
<Box flex={1} /> <Box flex={1} />
<Button variant={'outline'} onClick={handlePreviewChat}> <Button variant={'outline'} onClick={handlePreviewChat}>
对话体验 对话体验
...@@ -155,11 +155,13 @@ const ModelDetail = () => { ...@@ -155,11 +155,13 @@ const ModelDetail = () => {
<> <>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}> <Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
{model.name} 配置 {model?.name || '模型'} 配置
</Box> </Box>
{!!model && (
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}> <Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}>
{formatModelStatus[model.status].text} {formatModelStatus[model.status].text}
</Tag> </Tag>
)}
</Flex> </Flex>
<Box mt={4} textAlign={'right'}> <Box mt={4} textAlign={'right'}>
<Button variant={'outline'} onClick={handlePreviewChat}> <Button variant={'outline'} onClick={handlePreviewChat}>
...@@ -174,9 +176,9 @@ const ModelDetail = () => { ...@@ -174,9 +176,9 @@ const ModelDetail = () => {
<ModelEditForm model={model} /> <ModelEditForm model={model} />
</Box> </Box>
{/* 其他配置 */} {/* 其他配置 */}
<Grid mt={5} gridTemplateColumns={isPc ? '1fr 1fr' : '1fr'} gridGap={5}> <Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<Training model={model} /> <Card p={4}>{!!model && <Training model={model} />}</Card>
<Card h={'100%'} p={4}> <Card p={4}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
神奇操作 神奇操作
</Box> </Box>
...@@ -234,8 +236,6 @@ const ModelDetail = () => { ...@@ -234,8 +236,6 @@ const ModelDetail = () => {
</Flex> </Flex>
</Card> </Card>
</Grid> </Grid>
</>
)}
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}> <Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
<input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} /> <input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} />
</Box> </Box>
......
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Box, Button, Flex, Card } from '@chakra-ui/react'; import { Box, Button, Flex, Card } from '@chakra-ui/react';
import { getMyModels } from '@/api/model'; import { getMyModels } from '@/api/model';
import { getChatSiteId } from '@/api/chat'; import { getChatSiteId } from '@/api/chat';
import { ModelType } from '@/types/model'; import { ModelType } from '@/types/model';
import CreateModel from './components/CreateModel';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import ModelTable from './components/ModelTable'; import ModelTable from './components/ModelTable';
import ModelPhoneList from './components/ModelPhoneList'; import ModelPhoneList from './components/ModelPhoneList';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import { useGlobalStore } from '@/store/global'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading';
import dynamic from 'next/dynamic';
const CreateModel = dynamic(() => import('./components/CreateModel'));
const ModelList = () => { const ModelList = () => {
const { isPc } = useScreen(); const { isPc } = useScreen();
const router = useRouter(); const router = useRouter();
const [models, setModels] = useState<ModelType[]>([]); const [models, setModels] = useState<ModelType[]>([]);
const [openCreateModel, setOpenCreateModel] = useState(false); const [openCreateModel, setOpenCreateModel] = useState(false);
const { setLoading } = useGlobalStore(); const { Loading, setIsLoading } = useLoading();
/* 加载模型 */ /* 加载模型 */
const loadModels = useCallback(async () => { const { isLoading } = useQuery(['loadModels'], () => getMyModels(), {
setLoading(true); onSuccess(res) {
try { if (!res) return;
const res = await getMyModels();
setModels(res); setModels(res);
} catch (err) {
console.log(err);
} }
setLoading(false); });
}, [setLoading]);
useEffect(() => {
loadModels();
}, [loadModels]);
/* 创建成功回调 */ /* 创建成功回调 */
const createModelSuccess = useCallback((data: ModelType) => { const createModelSuccess = useCallback((data: ModelType) => {
...@@ -40,7 +36,7 @@ const ModelList = () => { ...@@ -40,7 +36,7 @@ const ModelList = () => {
/* 点前往聊天预览页 */ /* 点前往聊天预览页 */
const handlePreviewChat = useCallback( const handlePreviewChat = useCallback(
async (modelId: string) => { async (modelId: string) => {
setLoading(true); setIsLoading(true);
try { try {
const chatId = await getChatSiteId(modelId); const chatId = await getChatSiteId(modelId);
...@@ -48,11 +44,11 @@ const ModelList = () => { ...@@ -48,11 +44,11 @@ const ModelList = () => {
shallow: true shallow: true
}); });
} catch (err) { } catch (err) {
console.log(err); console.error(err);
} }
setLoading(false); setIsLoading(false);
}, },
[router, setLoading] [router, setIsLoading]
); );
return ( return (
...@@ -78,11 +74,11 @@ const ModelList = () => { ...@@ -78,11 +74,11 @@ const ModelList = () => {
)} )}
</Box> </Box>
{/* 创建弹窗 */} {/* 创建弹窗 */}
<CreateModel {openCreateModel && (
isOpen={openCreateModel} <CreateModel setCreateModelOpen={setOpenCreateModel} onSuccess={createModelSuccess} />
setCreateModelOpen={setOpenCreateModel} )}
onSuccess={createModelSuccess}
/> <Loading loading={isLoading} />
</Box> </Box>
); );
}; };
......
...@@ -8,7 +8,7 @@ export async function connectToDatabase() { ...@@ -8,7 +8,7 @@ export async function connectToDatabase() {
return cachedClient; return cachedClient;
} }
cachedClient = await mongoose.connect(process.env.MONGODB_UR as string, { cachedClient = await mongoose.connect(process.env.MONGODB_URI as string, {
dbName: 'doc_gpt' dbName: 'doc_gpt'
}); });
......
...@@ -24,8 +24,8 @@ export const jsonRes = ( ...@@ -24,8 +24,8 @@ export const jsonRes = (
typeof error === 'string' typeof error === 'string'
? error ? error
: openaiError[error?.response?.data?.message] || error?.message || '请求错误'; : openaiError[error?.response?.data?.message] || error?.message || '请求错误';
console.error(error);
console.log(msg); console.error(msg);
} }
res.json({ res.json({
......
...@@ -34,7 +34,7 @@ export const sendCode = (email: string, code: string, type: `${EmailTypeEnum}`) ...@@ -34,7 +34,7 @@ export const sendCode = (email: string, code: string, type: `${EmailTypeEnum}`)
}; };
mailTransport.sendMail(options, function (err, msg) { mailTransport.sendMail(options, function (err, msg) {
if (err) { if (err) {
console.log(err); console.error(err);
reject('邮箱异常'); reject('邮箱异常');
} else { } else {
resolve(''); resolve('');
...@@ -53,7 +53,7 @@ export const sendTrainSucceed = (email: string, modelName: string) => { ...@@ -53,7 +53,7 @@ export const sendTrainSucceed = (email: string, modelName: string) => {
}; };
mailTransport.sendMail(options, function (err, msg) { mailTransport.sendMail(options, function (err, msg) {
if (err) { if (err) {
console.log(err); console.error(err);
reject('邮箱异常'); reject('邮箱异常');
} else { } else {
resolve(''); resolve('');
......
...@@ -24,63 +24,9 @@ td, ...@@ -24,63 +24,9 @@ td,
svg { svg {
margin: 0; margin: 0;
} }
body,
button, #__next {
input, height: 100%;
select,
textarea {
font: 12px/1.5tahoma, arial, \5b8b\4f53;
}
// h1, h2, h3, h4, h5, h6{ font-size:100%; }
address,
cite,
dfn,
em,
var {
font-style: normal;
}
code,
kbd,
pre,
samp {
font-family: couriernew, courier, monospace;
}
small {
font-size: 12px;
}
ul,
ol {
list-style: none;
padding: 0;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
sup {
vertical-align: text-top;
}
sub {
vertical-align: text-bottom;
}
legend {
color: #000;
}
fieldset,
img {
border: 0;
}
button,
input,
select,
textarea {
font-size: 100%;
}
table {
border-collapse: collapse;
border-spacing: 0;
} }
::-webkit-scrollbar, ::-webkit-scrollbar,
......
...@@ -8,20 +8,26 @@ export const useCopyData = () => { ...@@ -8,20 +8,26 @@ export const useCopyData = () => {
const { toast } = useToast(); const { toast } = useToast();
return { return {
copyData: (data: string, title: string = '复制成功') => { copyData: (data: string, title: string = '复制成功') => {
const clipboardObj = navigator.clipboard; try {
clipboardObj const textarea = document.createElement('textarea');
.writeText(data) textarea.value = data;
.then(() => { document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
toast({ toast({
title, title,
status: 'success', status: 'success',
duration: 1000 duration: 1000
}); });
}) } catch (error) {
.catch((err) => { console.error(error);
console.log(err); toast({
title: '复制失败',
status: 'error'
}); });
} }
}
}; };
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment