feat: 注册限流配置

feat: 页面加载动画
feat: md样式优化
feat: 移动端全屏覆盖
This commit is contained in:
archer
2023-03-04 13:30:20 +08:00
parent 0ecf576e4e
commit 2cc32d1806
28 changed files with 515 additions and 252 deletions

View File

@@ -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=

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ yarn-error.log*
next-env.d.ts
public/trainData/
.vscode/
platform.json

View File

@@ -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"]

View File

@@ -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.

View File

@@ -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",

37
pnpm-lock.yaml generated
View File

@@ -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:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -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;

View File

@@ -51,10 +51,10 @@ const Layout = ({ children }: { children: JSX.Element }) => {
return (
<>
{!unShowLayoutRoute[router.pathname] ? (
<Box minHeight={'100vh'} backgroundColor={'gray.100'}>
<Box data-test="ss" h={'100%'} backgroundColor={'gray.100'} overflow={'auto'}>
{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} />
</Box>
<Box ml={'80px'} p={7}>

View File

@@ -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',

View File

@@ -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'

View File

@@ -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%;
}
}

View File

@@ -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 ? (
<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>
<Flex cursor={'pointer'} onClick={() => copyData(code)} alignItems={'center'}>
<Icon name={'icon-fuzhi'} width={15} height={15} color={'#fff'}></Icon>
@@ -36,13 +42,17 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean
</Flex>
<SyntaxHighlighter
style={codeLight as any}
showLineNumbers
language={match?.[1]}
language={match?.[1] || 'bash'}
PreTag="pre"
{...props}
>
{code}
</SyntaxHighlighter>
</Box>
) : (
<code className={className} {...props}>
{children}
</code>
);
}
}}

View File

@@ -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'
}
}
},

View File

@@ -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;"
/>
<link rel="icon" href="/favicon.ico" />
<script src="/iconfont.js" async></script>
</Head>
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}>
@@ -37,6 +45,7 @@ export default function App({ Component, pageProps }: AppProps) {
</Layout>
</ChakraProvider>
</QueryClientProvider>
<Script src="/iconfont.js"></Script>
</>
);
}

View File

@@ -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);

View File

@@ -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<HTMLDivElement>(null);
const TextareaDom = useRef<HTMLTextAreaElement>(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 (
<Flex h={'100vh'} flexDirection={'column'} overflowY={'hidden'}>
<Flex height={'100%'} flexDirection={'column'}>
{/* 头部 */}
<Flex
px={4}
@@ -258,7 +269,6 @@ const Chat = () => {
<Icon name={'icon-zhongzhi'} width={20} height={20} color={'#718096'}></Icon>
</Box>
{/* 滚动到底部按键 */}
{/* 滚动到底部 */}
{ChatBox.current && ChatBox.current.scrollHeight > 2 * ChatBox.current.clientHeight && (
<Box ml={10} cursor={'pointer'} onClick={scrollToBottom}>
<Icon
@@ -302,8 +312,9 @@ const Chat = () => {
<Box
m={media('20px auto', '0 auto')}
w={media('100vw', '100%')}
maxW={'800px'}
maxW={media('800px', 'auto')}
boxShadow={'0 -14px 30px rgba(255,255,255,0.6)'}
borderTop={media('none', '1px solid rgba(0,0,0,0.1)')}
>
{lastWordHuman ? (
<Box textAlign={'center'}>
@@ -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 = () => {
</Box>
)}
</Box>
<Loading loading={!chatSiteData} />
<Loading />
</Flex>
);
};

View File

@@ -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({

View File

@@ -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({

View File

@@ -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);

View File

@@ -1,7 +1,5 @@
.loginPage {
background: url('/icon/login-bg.svg') no-repeat;
background-size: cover;
height: 100vh;
width: 100vw;
user-select: none;
}

View File

@@ -40,7 +40,7 @@ const Login = () => {
};
return (
<Box className={styles.loginPage} p={isPc ? '10vh 10vw' : 0}>
<Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}>
<Flex
maxW={'1240px'}
m={'auto'}

View File

@@ -8,29 +8,23 @@ import { useRouter } from 'next/router';
import ModelTable from './components/ModelTable';
import ModelPhoneList from './components/ModelPhoneList';
import { useScreen } from '@/hooks/useScreen';
import { useGlobalStore } from '@/store/global';
import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading';
const ModelList = () => {
const { isPc } = useScreen();
const router = useRouter();
const [models, setModels] = useState<ModelType[]>([]);
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}
/>
<Loading loading={isLoading} />
</Box>
);
};

View File

@@ -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'
});

View File

@@ -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 {

View File

@@ -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'
});
}
}
};
};