diff --git a/.env.template b/.env.template index 0937fa9a6..33c0a8203 100644 --- a/.env.template +++ b/.env.template @@ -1,6 +1,6 @@ AXIOS_PROXY_HOST=127.0.0.1 AXIOS_PROXY_PORT=33210 -MONGODB_UR= +MONGODB_URI= MY_MAIL= MAILE_CODE= TOKEN_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index c66bf9086..bb50d5836 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts public/trainData/ -.vscode/ \ No newline at end of file +.vscode/ +platform.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 972de549c..ee5e92d62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,5 +55,12 @@ USER nextjs EXPOSE 3000 ENV PORT 3000 +ENV MAX_USER '' +ENV AXIOS_PROXY_HOST '' +ENV AXIOS_PROXY_PORT '' +ENV MONGODB_URI '' +ENV MY_MAIL '' +ENV MAILE_CODE '' +ENV TOKEN_KEY '' CMD ["node", "server.js"] diff --git a/README.md b/README.md index a8e8c227b..fb68b5a96 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ``` AXIOS_PROXY_HOST=axios代理地址,目前 openai 接口都需要走代理,本机的话就填 127.0.0.1 AXIOS_PROXY_PORT=代理端口 -MONGODB_UR=mongo数据库地址 +MONGODB_URI=mongo数据库地址 MY_MAIL=发送验证码邮箱 MAILE_CODE=邮箱秘钥 TOKEN_KEY=随便填一个,用于生成和校验token @@ -27,7 +27,7 @@ docker pull imageName docker stop doc-gpt || true docker rm doc-gpt || true # 运行时才把参数写入 -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 +docker run -d --network=host --name doc-gpt -e AXIOS_PROXY_HOST= -e AXIOS_PROXY_PORT= -e MAILE_CODE= -e TOKEN_KEY= -e MONGODB_URI= imageName ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. diff --git a/package.json b/package.json index bebac8c95..bd1b94ea0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@next/font": "13.1.6", "@reduxjs/toolkit": "^1.9.3", "@tanstack/react-query": "^4.24.10", + "@types/nprogress": "^0.2.0", "axios": "^1.3.3", "crypto": "^1.0.1", "dayjs": "^1.11.7", @@ -32,6 +33,7 @@ "mongoose": "^6.10.0", "next": "13.1.6", "nodemailer": "^6.9.1", + "nprogress": "^0.2.0", "openai": "^3.2.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe816d878..514126ade 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,7 @@ specifiers: '@types/jsonwebtoken': ^9.0.1 '@types/node': 18.14.0 '@types/nodemailer': ^6.4.7 + '@types/nprogress': ^0.2.0 '@types/react': 18.0.28 '@types/react-dom': 18.0.11 '@types/react-syntax-highlighter': ^15.5.6 @@ -33,6 +34,7 @@ specifiers: mongoose: ^6.10.0 next: 13.1.6 nodemailer: ^6.9.1 + nprogress: ^0.2.0 openai: ^3.2.1 prettier: ^2.8.4 react: 18.2.0 @@ -57,6 +59,7 @@ dependencies: '@next/font': registry.npmmirror.com/@next/font/13.1.6 '@reduxjs/toolkit': registry.npmmirror.com/@reduxjs/toolkit/1.9.3_react@18.2.0 '@tanstack/react-query': registry.npmmirror.com/@tanstack/react-query/4.24.10_biqbaboplfbrettd7655fr4n2y + '@types/nprogress': registry.npmmirror.com/@types/nprogress/0.2.0 axios: registry.npmmirror.com/axios/1.3.3 crypto: registry.npmmirror.com/crypto/1.0.1 dayjs: registry.npmmirror.com/dayjs/1.11.7 @@ -70,6 +73,7 @@ dependencies: mongoose: registry.npmmirror.com/mongoose/6.10.0 next: registry.npmmirror.com/next/13.1.6_wiv434v7erz4aedd5whhdwmpv4 nodemailer: registry.npmmirror.com/nodemailer/6.9.1 + nprogress: registry.npmmirror.com/nprogress/0.2.0 openai: registry.npmmirror.com/openai/3.2.1 react: registry.npmmirror.com/react/18.2.0 react-dom: registry.npmmirror.com/react-dom/18.2.0_react@18.2.0 @@ -1097,6 +1101,15 @@ packages: regenerator-runtime: registry.npmmirror.com/regenerator-runtime/0.13.11 dev: false + registry.npmmirror.com/@babel/runtime/7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz} + name: '@babel/runtime' + version: 7.21.0 + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: registry.npmmirror.com/regenerator-runtime/0.13.11 + dev: false + registry.npmmirror.com/@babel/types/7.21.0: resolution: {integrity: sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.21.0.tgz} name: '@babel/types' @@ -2433,7 +2446,7 @@ packages: version: 11.10.6 dependencies: '@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports/7.18.6 - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.20.13 + '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.21.0 '@emotion/hash': registry.npmmirror.com/@emotion/hash/0.9.0 '@emotion/memoize': registry.npmmirror.com/@emotion/memoize/0.8.0 '@emotion/serialize': registry.npmmirror.com/@emotion/serialize/1.1.1 @@ -3048,6 +3061,12 @@ packages: '@types/node': registry.npmmirror.com/@types/node/18.14.0 dev: true + registry.npmmirror.com/@types/nprogress/0.2.0: + resolution: {integrity: sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/nprogress/-/nprogress-0.2.0.tgz} + name: '@types/nprogress' + version: 0.2.0 + dev: false + registry.npmmirror.com/@types/parse-json/4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz} name: '@types/parse-json' @@ -3469,7 +3488,7 @@ packages: version: 3.1.0 engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.20.13 + '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.21.0 cosmiconfig: registry.npmmirror.com/cosmiconfig/7.1.0 resolve: registry.npmmirror.com/resolve/1.22.1 dev: false @@ -4329,7 +4348,7 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.20.13 + '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.21.0 aria-query: registry.npmmirror.com/aria-query/5.1.3 array-includes: registry.npmmirror.com/array-includes/3.1.6 array.prototype.flatmap: registry.npmmirror.com/array.prototype.flatmap/1.3.1 @@ -6481,6 +6500,12 @@ packages: path-key: registry.npmmirror.com/path-key/4.0.0 dev: true + registry.npmmirror.com/nprogress/0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz} + name: nprogress + version: 0.2.0 + dev: false + registry.npmmirror.com/object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz} name: object-assign @@ -6889,7 +6914,7 @@ packages: peerDependencies: react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.20.13 + '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.21.0 react: registry.npmmirror.com/react/18.2.0 dev: false @@ -6924,7 +6949,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.20.13 + '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.21.0 '@types/react': registry.npmmirror.com/@types/react/18.0.28 focus-lock: registry.npmmirror.com/focus-lock/0.11.6 prop-types: registry.npmmirror.com/prop-types/15.8.1 @@ -7110,7 +7135,7 @@ packages: name: redux version: 4.2.1 dependencies: - '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.20.13 + '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.21.0 dev: false registry.npmmirror.com/refractor/3.6.0: diff --git a/public/imgs/erweima.jpg b/public/imgs/erweima.jpg index 268b6e8e6..f09d1eff4 100644 Binary files a/public/imgs/erweima.jpg and b/public/imgs/erweima.jpg differ diff --git a/public/imgs/modelAvatar.png b/public/imgs/modelAvatar.png index 264453d9d..ce1ae793e 100644 Binary files a/public/imgs/modelAvatar.png and b/public/imgs/modelAvatar.png differ diff --git a/src/components/Layout/auth.tsx b/src/components/Layout/auth.tsx index a9e85fcfe..47e75d88f 100644 --- a/src/components/Layout/auth.tsx +++ b/src/components/Layout/auth.tsx @@ -7,6 +7,7 @@ import { useGlobalStore } from '@/store/global'; import { useQuery } from '@tanstack/react-query'; const unAuthPage: { [key: string]: boolean } = { + '/': true, '/login': true, '/chat': true }; @@ -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; diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 924456169..4cbea772c 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -51,10 +51,10 @@ const Layout = ({ children }: { children: JSX.Element }) => { return ( <> {!unShowLayoutRoute[router.pathname] ? ( - + {isPc ? ( <> - + diff --git a/src/components/Layout/navbar.tsx b/src/components/Layout/navbar.tsx index 2dcfd2f40..908cf8645 100644 --- a/src/components/Layout/navbar.tsx +++ b/src/components/Layout/navbar.tsx @@ -3,7 +3,6 @@ import { Box, Flex } from '@chakra-ui/react'; import Image from 'next/image'; import { useRouter } from 'next/router'; import Icon from '../Icon'; -import styles from './style.module.scss'; export enum NavbarTypeEnum { normal = 'normal', diff --git a/src/components/Markdown/codeLight.ts b/src/components/Markdown/codeLight.ts index d6f17fd10..309bb70c8 100644 --- a/src/components/Markdown/codeLight.ts +++ b/src/components/Markdown/codeLight.ts @@ -1,47 +1,5 @@ import React from 'react'; export const codeLight: { [key: string]: React.CSSProperties } = { - 'code[class*=language-]': { - color: '#d4d4d4', - fontSize: '13px', - textShadow: 'none', - fontFamily: 'Menlo,Monaco,Consolas,"Andale Mono","Ubuntu Mono","Courier New",monospace', - direction: 'ltr', - textAlign: 'left', - whiteSpace: 'pre', - wordSpacing: 'normal', - wordBreak: 'normal', - lineHeight: '1.5', - MozTabSize: '4', - OTabSize: '4', - tabSize: '4', - WebkitHyphens: 'none', - MozHyphens: 'none', - msHyphens: 'none', - hyphens: 'none' - }, - 'pre[class*=language-]': { - color: '#d4d4d4', - fontSize: '13px', - textShadow: 'none', - fontFamily: 'Menlo,Monaco,Consolas,"Andale Mono","Ubuntu Mono","Courier New",monospace', - direction: 'ltr', - textAlign: 'left', - whiteSpace: 'pre', - wordSpacing: 'normal', - wordBreak: 'normal', - lineHeight: '1.5', - MozTabSize: '4', - OTabSize: '4', - tabSize: '4', - WebkitHyphens: 'none', - MozHyphens: 'none', - msHyphens: 'none', - hyphens: 'none', - padding: '1em', - margin: '.5em 0', - overflow: 'auto', - background: '#1e1e1e' - }, 'code[class*=language-] ::selection': { textShadow: 'none', background: '#264f78' diff --git a/src/components/Markdown/index.module.scss b/src/components/Markdown/index.module.scss index 40ebc8453..44e047a36 100644 --- a/src/components/Markdown/index.module.scss +++ b/src/components/Markdown/index.module.scss @@ -27,96 +27,343 @@ opacity: 1; } } + +.markdown > *:first-child { + margin-top: 0 !important; +} +.markdown > *:last-child { + margin-bottom: 0 !important; +} +.markdown a.absent { + color: #cc0000; +} +.markdown a.anchor { + bottom: 0; + cursor: pointer; + display: block; + left: 0; + margin-left: -30px; + padding-left: 30px; + position: absolute; + top: 0; +} +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.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; + top: 15%; +} +.markdown h1:hover a.anchor .mini-icon-link, +.markdown h2:hover a.anchor .mini-icon-link, +.markdown h3:hover a.anchor .mini-icon-link, +.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; +} +.markdown h1 tt, +.markdown h1 code, +.markdown h2 tt, +.markdown h2 code, +.markdown h3 tt, +.markdown h3 code, +.markdown h4 tt, +.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 { + border-bottom: 1px solid #cccccc; + 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 { + 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-weight: bold; + margin: 15px 0 5px; + padding: 0; +} +.markdown dl dt:first-child { + padding: 0; +} +.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: #f8f8f8; +} +.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: #f8f8f8; + border-radius: 3px 3px 3px 3px; + margin: 0 2px; + padding: 0 5px; + white-space: nowrap; +} +.markdown pre > code { + background: none repeat scroll 0 0 transparent; + margin: 0; + padding: 0; + white-space: pre; +} +.markdown .highlight pre, +.markdown pre { + background-color: #f8f8f8; + font-size: 13px; + line-height: 19px; + overflow: auto; + padding: 6px 10px; +} +.markdown pre code, +.markdown pre tt { + background-color: transparent; + border: medium none; +} .markdown { - /* 标题样式 */ - h1 { - font-size: 1.8rem; - } - - h2 { - font-size: 1.6rem; - } - - h3 { - font-size: 1.4rem; - } - - h4 { - font-size: 1.2rem; - } - - h5 { - font-size: 1rem; - } - - h6 { - font-size: 0.83rem; - } - - /* 列表样式 */ - ol, - ul { - padding-left: 1.5rem; - margin-left: 1rem; - } - ul { - list-style: inside; - } - ol { - list-style: decimal; - } - - /* 链接样式 */ - a { - color: #0077cc; - text-decoration: none; - border-bottom: 1px solid #0077cc; - } - - a:hover { - color: #005580; - border-bottom-color: #005580; - } - - /* 图片样式 */ - img { - max-width: 100%; - max-height: 200px; - margin: auto; - } - - /* 强调样式 */ - em, - i { - font-style: italic; - } - - strong, - b { - font-weight: bold; - } - - /* 代码样式 */ - code { - border-radius: 3px; - width: 100%; - } - + font-size: 14px; + line-height: 1.6; + letter-spacing: 0.5px; + text-align: justify; pre { - padding: 10px 15px; + display: block; width: 100%; + padding: 15px; background-color: #222 !important; overflow-x: auto; } pre code { - display: block; - border: none; background-color: #222; color: #fff; - } - - p { - line-height: 1.7; + width: 100%; } } diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx index 0fe1f4b80..27a891f7f 100644 --- a/src/components/Markdown/index.tsx +++ b/src/components/Markdown/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, memo } from 'react'; +import React, { memo } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import styles from './index.module.scss'; @@ -24,10 +24,16 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); const code = String(children).replace(/\n$/, ''); - - return ( + return !inline ? ( - + {match?.[1]} copyData(code)} alignItems={'center'}> @@ -36,13 +42,17 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean {code} + ) : ( + + {children} + ); } }} diff --git a/src/constants/theme.ts b/src/constants/theme.ts index 5526b1c63..72661ab04 100644 --- a/src/constants/theme.ts +++ b/src/constants/theme.ts @@ -58,7 +58,11 @@ export const theme = extendTheme({ global: { 'html, body': { 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' } } }, diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 0a8496d03..375bcd771 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,11 +1,20 @@ import type { AppProps, NextWebVitalsMetric } from 'next/app'; +import Script from 'next/script'; import Head from 'next/head'; import { ChakraProvider } from '@chakra-ui/react'; import Layout from '@/components/Layout'; import { theme } from '@/constants/theme'; 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'; +//Binding events. +Router.events.on('routeChangeStart', () => NProgress.start()); +Router.events.on('routeChangeComplete', () => NProgress.done()); +Router.events.on('routeChangeError', () => NProgress.done()); + export default function App({ Component, pageProps }: AppProps) { // Create a client const queryClient = new QueryClient({ @@ -28,7 +37,6 @@ export default function App({ Component, pageProps }: AppProps) { content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" /> - @@ -37,6 +45,7 @@ export default function App({ Component, pageProps }: AppProps) { + ); } diff --git a/src/pages/api/user/sendEmail.ts b/src/pages/api/user/sendEmail.ts index c89e6ba24..6ff5ee2cb 100644 --- a/src/pages/api/user/sendEmail.ts +++ b/src/pages/api/user/sendEmail.ts @@ -2,13 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { AuthCode } from '@/service/models/authCode'; -import { connectToDatabase } from '@/service/mongo'; +import { connectToDatabase, User } from '@/service/mongo'; import { sendCode } from '@/service/utils/sendEmail'; import { EmailTypeEnum } from '@/constants/common'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { - const { email, type } = req.query; + const { email, type } = req.query as { email: string; type: `${EmailTypeEnum}` }; if (!email || !type) { throw new Error('缺少参数'); @@ -16,6 +16,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await connectToDatabase(); + // 注册人数限流 + if (type === EmailTypeEnum.register) { + const maxCount = process.env.MAX_USER ? +process.env.MAX_USER : Infinity; + const userCount = await User.count(); + + if (userCount >= maxCount) { + throw new Error('当前注册用户已满,请等待名额~'); + } + } + let code = ''; for (let i = 0; i < 6; i++) { code += Math.floor(Math.random() * 10); diff --git a/src/pages/chat/index.tsx b/src/pages/chat/index.tsx index 45b3e9f98..b84f57b44 100644 --- a/src/pages/chat/index.tsx +++ b/src/pages/chat/index.tsx @@ -18,10 +18,12 @@ import { useQuery } from '@tanstack/react-query'; import { useLoading } from '@/hooks/useLoading'; import { OpenAiModelEnum } from '@/constants/model'; +const textareaMinH = '22px'; + const Chat = () => { const { toast } = useToast(); const router = useRouter(); - const { media } = useScreen(); + const { isPc, media } = useScreen(); const { chatId, windowId } = router.query as { chatId: string; windowId?: string }; const ChatBox = useRef(null); const TextareaDom = useRef(null); @@ -32,7 +34,7 @@ const Chat = () => { const isChatting = useMemo(() => chatList[chatList.length - 1]?.status === 'loading', [chatList]); const lastWordHuman = useMemo(() => chatList[chatList.length - 1]?.obj === 'Human', [chatList]); - const { Loading } = useLoading(); + const { Loading, setIsLoading } = useLoading({ defaultLoading: true }); // 滚动到底部 const scrollToBottom = useCallback(() => { @@ -47,28 +49,36 @@ const Chat = () => { }, []); // 初始化聊天框 - useQuery([chatId, windowId], () => (chatId ? getInitChatSiteInfo(chatId, windowId) : null), { - cacheTime: 5 * 60 * 1000, - onSuccess(res) { - if (!res) return; - router.replace(`/chat?chatId=${chatId}&windowId=${res.windowId}`); + const { isInitialLoading } = useQuery( + [chatId, windowId], + () => (chatId ? getInitChatSiteInfo(chatId, windowId) : null), + { + cacheTime: 5 * 60 * 1000, + onSuccess(res) { + if (!res) return; + router.replace(`/chat?chatId=${chatId}&windowId=${res.windowId}`); - setChatSiteData(res.chatSite); - setChatList( - res.history.map((item) => ({ - ...item, - status: 'finish' - })) - ); - scrollToBottom(); - }, - onError() { - toast({ - title: '初始化异常', - status: 'error' - }); + setChatSiteData(res.chatSite); + setChatList( + res.history.map((item) => ({ + ...item, + status: 'finish' + })) + ); + scrollToBottom(); + setIsLoading(false); + }, + onError() { + toast({ + title: '初始化异常,请刷新', + status: 'error', + isClosable: true, + duration: 5000 + }); + setIsLoading(false); + } } - }); + ); // gpt3 方法 const gpt3ChatPrompt = useCallback( @@ -179,8 +189,9 @@ const Chat = () => { setTimeout(() => { scrollToBottom(); + /* 回到最小高度 */ if (TextareaDom.current) { - TextareaDom.current.style.height = 22 + 'px'; + TextareaDom.current.style.height = textareaMinH; } }, 100); @@ -242,7 +253,7 @@ const Chat = () => { }, [chatList, windowId]); return ( - + {/* 头部 */} { {/* 滚动到底部按键 */} - {/* 滚动到底部 */} {ChatBox.current && ChatBox.current.scrollHeight > 2 * ChatBox.current.clientHeight && ( { {lastWordHuman ? ( @@ -349,12 +360,12 @@ const Chat = () => { onChange={(e) => { const textarea = e.target; setInputVal(textarea.value); - - textarea.style.height = textarea.value.split('\n').length * 22 + 'px'; + textarea.style.height = textareaMinH; + textarea.style.height = `${textarea.scrollHeight}px`; }} onKeyDown={(e) => { // 触发快捷发送 - if (e.keyCode === 13 && !e.shiftKey) { + if (isPc && e.keyCode === 13 && !e.shiftKey) { sendPrompt(); e.preventDefault(); } @@ -382,7 +393,7 @@ const Chat = () => { )} - + ); }; diff --git a/src/pages/login/components/ForgetPasswordForm.tsx b/src/pages/login/components/ForgetPasswordForm.tsx index 8a4283baa..239e462ef 100644 --- a/src/pages/login/components/ForgetPasswordForm.tsx +++ b/src/pages/login/components/ForgetPasswordForm.tsx @@ -1,19 +1,12 @@ import React, { useState, Dispatch, useCallback } from 'react'; -import { - FormControl, - Box, - Input, - Button, - FormErrorMessage, - useToast, - Flex -} from '@chakra-ui/react'; +import { FormControl, Box, Input, Button, FormErrorMessage, Flex } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { PageTypeEnum } from '../../../constants/user'; import { postFindPassword } from '@/api/user'; import { useSendCode } from '@/hooks/useSendCode'; import type { ResLogin } from '@/api/response/user'; import { useScreen } from '@/hooks/useScreen'; +import { useToast } from '@/hooks/useToast'; interface Props { setPageType: Dispatch<`${PageTypeEnum}`>; @@ -28,7 +21,7 @@ interface RegisterType { } const RegisterForm = ({ setPageType, loginSuccess }: Props) => { - const toast = useToast(); + const { toast } = useToast(); const { mediaLgMd } = useScreen(); const { register, @@ -57,6 +50,10 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { async ({ email, code, password }: RegisterType) => { setRequesting(true); try { + toast({ + title: `密码已找回`, + status: 'success' + }); loginSuccess( await postFindPassword({ email, @@ -64,11 +61,6 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { password }) ); - toast({ - title: `密码已找回`, - status: 'success', - position: 'top' - }); } catch (error) { typeof error === 'string' && toast({ diff --git a/src/pages/login/components/LoginForm.tsx b/src/pages/login/components/LoginForm.tsx index 58d0b7ea7..fe30ce2ae 100644 --- a/src/pages/login/components/LoginForm.tsx +++ b/src/pages/login/components/LoginForm.tsx @@ -32,16 +32,16 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => { async ({ email, password }: LoginFormType) => { setRequesting(true); try { + toast({ + title: '登录成功', + status: 'success' + }); loginSuccess( await postLogin({ email, password }) ); - toast({ - title: '登录成功', - status: 'success' - }); } catch (error) { typeof error === 'string' && toast({ diff --git a/src/pages/login/components/RegisterForm.tsx b/src/pages/login/components/RegisterForm.tsx index 8851b13af..43807d776 100644 --- a/src/pages/login/components/RegisterForm.tsx +++ b/src/pages/login/components/RegisterForm.tsx @@ -1,19 +1,12 @@ import React, { useState, Dispatch, useCallback } from 'react'; -import { - FormControl, - Box, - Input, - Button, - FormErrorMessage, - useToast, - Flex -} from '@chakra-ui/react'; +import { FormControl, Box, Input, Button, FormErrorMessage, Flex } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { PageTypeEnum } from '@/constants/user'; import { postRegister } from '@/api/user'; import { useSendCode } from '@/hooks/useSendCode'; import type { ResLogin } from '@/api/response/user'; import { useScreen } from '@/hooks/useScreen'; +import { useToast } from '@/hooks/useToast'; interface Props { loginSuccess: (e: ResLogin) => void; @@ -28,7 +21,7 @@ interface RegisterType { } const RegisterForm = ({ setPageType, loginSuccess }: Props) => { - const toast = useToast(); + const { toast } = useToast(); const { mediaLgMd } = useScreen(); const { register, @@ -57,6 +50,10 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { async ({ email, password, code }: RegisterType) => { setRequesting(true); try { + toast({ + title: `注册成功`, + status: 'success' + }); loginSuccess( await postRegister({ email, @@ -64,17 +61,13 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { password }) ); - toast({ - title: `注册成功`, - status: 'success', - position: 'top' - }); } catch (error) { typeof error === 'string' && toast({ title: error, status: 'error', - position: 'top' + duration: 4000, + isClosable: true }); } setRequesting(false); diff --git a/src/pages/login/index.module.scss b/src/pages/login/index.module.scss index c4ae441b4..fe5c78ced 100644 --- a/src/pages/login/index.module.scss +++ b/src/pages/login/index.module.scss @@ -1,7 +1,5 @@ .loginPage { background: url('/icon/login-bg.svg') no-repeat; background-size: cover; - height: 100vh; - width: 100vw; user-select: none; } diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index e61f267d7..18fe7d487 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -40,7 +40,7 @@ const Login = () => { }; return ( - + { const { isPc } = useScreen(); const router = useRouter(); const [models, setModels] = useState([]); const [openCreateModel, setOpenCreateModel] = useState(false); - const { setLoading } = useGlobalStore(); + const { Loading, setIsLoading } = useLoading(); /* 加载模型 */ - const loadModels = useCallback(async () => { - setLoading(true); - try { - const res = await getMyModels(); + const { isLoading } = useQuery(['loadModels'], () => getMyModels(), { + onSuccess(res) { + if (!res) return; setModels(res); - } catch (err) { - console.log(err); } - setLoading(false); - }, [setLoading]); - useEffect(() => { - loadModels(); - }, [loadModels]); + }); /* 创建成功回调 */ const createModelSuccess = useCallback((data: ModelType) => { @@ -40,7 +34,7 @@ const ModelList = () => { /* 点前往聊天预览页 */ const handlePreviewChat = useCallback( async (modelId: string) => { - setLoading(true); + setIsLoading(true); try { const chatId = await getChatSiteId(modelId); @@ -50,9 +44,9 @@ const ModelList = () => { } catch (err) { console.log(err); } - setLoading(false); + setIsLoading(false); }, - [router, setLoading] + [router, setIsLoading] ); return ( @@ -83,6 +77,7 @@ const ModelList = () => { setCreateModelOpen={setOpenCreateModel} onSuccess={createModelSuccess} /> + ); }; diff --git a/src/service/mongo.ts b/src/service/mongo.ts index 33322e26c..e68deb885 100644 --- a/src/service/mongo.ts +++ b/src/service/mongo.ts @@ -8,7 +8,7 @@ export async function connectToDatabase() { 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' }); diff --git a/src/styles/reset.scss b/src/styles/reset.scss index bc7531aeb..8d168bca0 100644 --- a/src/styles/reset.scss +++ b/src/styles/reset.scss @@ -45,14 +45,6 @@ pre, samp { font-family: couriernew, courier, monospace; } -small { - font-size: 12px; -} -ul, -ol { - list-style: none; - padding: 0; -} a { text-decoration: none; } @@ -82,6 +74,9 @@ table { border-collapse: collapse; border-spacing: 0; } +#__next { + height: 100%; +} ::-webkit-scrollbar, ::-webkit-scrollbar { diff --git a/src/styles/scrollbar.module.scss b/src/styles/scrollbar.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/utils/tools.ts b/src/utils/tools.ts index bc8315dad..d57f71fce 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -8,19 +8,25 @@ export const useCopyData = () => { const { toast } = useToast(); return { copyData: (data: string, title: string = '复制成功') => { - const clipboardObj = navigator.clipboard; - clipboardObj - .writeText(data) - .then(() => { - toast({ - title, - status: 'success', - duration: 1000 - }); - }) - .catch((err) => { - console.log(err); + try { + const textarea = document.createElement('textarea'); + textarea.value = data; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); + toast({ + title, + status: 'success', + duration: 1000 }); + } catch (error) { + console.log(error); + toast({ + title: '复制失败', + status: 'error' + }); + } } }; };