doc gpt V0.2
This commit is contained in:
23
src/components/Icon/index.tsx
Normal file
23
src/components/Icon/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
type TIconfont = {
|
||||
name: string;
|
||||
color?: string;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function Icon({ name, color = 'inherit', width = 16, height = 16, className = '' }: TIconfont) {
|
||||
const style = {
|
||||
fill: color,
|
||||
width,
|
||||
height
|
||||
};
|
||||
|
||||
return (
|
||||
<svg className={`icon ${className}`} aria-hidden="true" style={style}>
|
||||
<use xlinkHref={`#${name}`}></use>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
54
src/components/Layout/auth.tsx
Normal file
54
src/components/Layout/auth.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { getTokenLogin } from '@/api/user';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
const unAuthPage: { [key: string]: boolean } = {
|
||||
'/login': true,
|
||||
'/chat': true
|
||||
};
|
||||
|
||||
const Auth = ({ children }: { children: JSX.Element }) => {
|
||||
const router = useRouter();
|
||||
const toast = useToast({
|
||||
title: '请先登录',
|
||||
position: 'top',
|
||||
status: 'warning'
|
||||
});
|
||||
const { userInfo, setUserInfo } = useUserStore();
|
||||
const { setLoading } = useGlobalStore();
|
||||
|
||||
useQuery(
|
||||
[router.pathname, userInfo],
|
||||
() => {
|
||||
setLoading(true);
|
||||
if (unAuthPage[router.pathname] === true || userInfo) {
|
||||
return setLoading(false);
|
||||
} else {
|
||||
return getTokenLogin();
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess(user) {
|
||||
if (user) {
|
||||
setUserInfo(user);
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
console.log(error);
|
||||
router.push('/login');
|
||||
toast();
|
||||
},
|
||||
onSettled() {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return userInfo || unAuthPage[router.pathname] === true ? <>{children}</> : null;
|
||||
};
|
||||
|
||||
export default Auth;
|
||||
95
src/components/Layout/index.tsx
Normal file
95
src/components/Layout/index.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import Navbar from './navbar';
|
||||
import NavbarPhone from './navbarPhone';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import Auth from './auth';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
const unShowLayoutRoute: { [key: string]: boolean } = {
|
||||
'/login': true,
|
||||
'/chat': true
|
||||
};
|
||||
|
||||
const navbarList = [
|
||||
{
|
||||
label: '介绍',
|
||||
icon: 'icon-gongzuotai-01',
|
||||
link: '/',
|
||||
activeLink: ['/']
|
||||
},
|
||||
{
|
||||
label: '模型',
|
||||
icon: 'icon-moxing',
|
||||
link: '/model/list',
|
||||
activeLink: ['/model/list', '/model/detail']
|
||||
},
|
||||
// {
|
||||
// label: '数据',
|
||||
// icon: 'icon-datafull',
|
||||
// link: '/training/dataList',
|
||||
// activeLink: ['/training/dataList']
|
||||
// },
|
||||
{
|
||||
label: '账号',
|
||||
icon: 'icon-yonghu-yuan',
|
||||
link: '/number/setting',
|
||||
activeLink: ['/number/setting']
|
||||
}
|
||||
];
|
||||
|
||||
const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
const { isPc } = useScreen();
|
||||
const router = useRouter();
|
||||
const { Loading } = useLoading({
|
||||
defaultLoading: true
|
||||
});
|
||||
const { loading } = useGlobalStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{!unShowLayoutRoute[router.pathname] ? (
|
||||
<Box minHeight={'100vh'} backgroundColor={'gray.100'}>
|
||||
{isPc ? (
|
||||
<>
|
||||
<Box h={'100vh'} position={'fixed'} left={0} top={0} w={'80px'}>
|
||||
<Navbar navbarList={navbarList} />
|
||||
</Box>
|
||||
<Box ml={'80px'} p={7}>
|
||||
<Box maxW={'1100px'} m={'auto'}>
|
||||
<Auth>{children}</Auth>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Box pt={'60px'}>
|
||||
<Box
|
||||
h={'60px'}
|
||||
position={'fixed'}
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={100}
|
||||
borderBottom={'1px solid rgba(0,0,0,0.1)'}
|
||||
>
|
||||
<NavbarPhone navbarList={navbarList} />
|
||||
</Box>
|
||||
<Box py={3} px={4}>
|
||||
<Auth>{children}</Auth>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Auth>
|
||||
<>{children}</>
|
||||
</Auth>
|
||||
)}
|
||||
{loading && <Loading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
87
src/components/Layout/navbar.tsx
Normal file
87
src/components/Layout/navbar.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
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',
|
||||
small = 'small'
|
||||
}
|
||||
|
||||
const Navbar = ({
|
||||
navbarList
|
||||
}: {
|
||||
navbarList: {
|
||||
label: string;
|
||||
icon: string;
|
||||
link: string;
|
||||
activeLink: string[];
|
||||
}[];
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
py={3}
|
||||
backgroundColor={'white'}
|
||||
h={'100%'}
|
||||
w={'100%'}
|
||||
boxShadow={'4px 0px 4px 0px rgba(43, 45, 55, 0.01)'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
{/* logo */}
|
||||
<Box pb={4}>
|
||||
<Image src={'/logo.svg'} width={50} height={100} alt=""></Image>
|
||||
</Box>
|
||||
{/* 导航列表 */}
|
||||
<Box flex={1}>
|
||||
{navbarList.map((item) => (
|
||||
<Flex
|
||||
key={item.label}
|
||||
mb={4}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
onClick={() =>
|
||||
router.push(item.link, undefined, {
|
||||
shallow: true
|
||||
})
|
||||
}
|
||||
cursor={'pointer'}
|
||||
fontSize={'sm'}
|
||||
w={'60px'}
|
||||
h={'70px'}
|
||||
borderRadius={'sm'}
|
||||
{...(item.activeLink.includes(router.pathname)
|
||||
? {
|
||||
color: '#2B6CB0',
|
||||
backgroundColor: '#BEE3F8'
|
||||
}
|
||||
: {
|
||||
color: '#4A5568',
|
||||
backgroundColor: 'transparent'
|
||||
})}
|
||||
>
|
||||
<Icon
|
||||
name={item.icon}
|
||||
width={24}
|
||||
height={24}
|
||||
color={item.activeLink.includes(router.pathname) ? '#2B6CB0' : '#4A5568'}
|
||||
/>
|
||||
<Box mt={1}>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
{/* 通知 icon */}
|
||||
{/* <Flex className={styles.informIcon} mb={5} justifyContent={'center'}>
|
||||
<Icon name={'icon-tongzhi'} width={28} height={28} color={'#718096'}></Icon>
|
||||
</Flex> */}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
99
src/components/Layout/navbarPhone.tsx
Normal file
99
src/components/Layout/navbarPhone.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Icon from '../Icon';
|
||||
import {
|
||||
Flex,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerFooter,
|
||||
DrawerOverlay,
|
||||
DrawerContent,
|
||||
Box,
|
||||
useDisclosure,
|
||||
Button,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
const NavbarPhone = ({
|
||||
navbarList
|
||||
}: {
|
||||
navbarList: {
|
||||
label: string;
|
||||
icon: string;
|
||||
link: string;
|
||||
activeLink: string[];
|
||||
}[];
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
h={'100%'}
|
||||
justifyContent={'space-between'}
|
||||
backgroundColor={'white'}
|
||||
position={'relative'}
|
||||
px={7}
|
||||
>
|
||||
<Box onClick={onOpen}>
|
||||
<Icon name="icon-caidan" width={20} height={20}></Icon>
|
||||
</Box>
|
||||
{/* <Icon name="icon-tongzhi" width={20} height={20}></Icon> */}
|
||||
</Flex>
|
||||
<Drawer isOpen={isOpen} placement="left" size={'xs'} onClose={onClose}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent maxWidth={'60vw'}>
|
||||
<DrawerBody p={4}>
|
||||
<Box pb={4}>
|
||||
<Image src={'/logo.svg'} w={'100%'} h={'70px'} pt={2} alt=""></Image>
|
||||
</Box>
|
||||
{navbarList.map((item) => (
|
||||
<Flex
|
||||
key={item.label}
|
||||
mb={4}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
onClick={() => {
|
||||
router.push(item.link);
|
||||
onClose();
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
fontSize={'sm'}
|
||||
h={'65px'}
|
||||
borderRadius={'md'}
|
||||
{...(item.activeLink.includes(router.pathname)
|
||||
? {
|
||||
color: '#2B6CB0',
|
||||
backgroundColor: '#BEE3F8'
|
||||
}
|
||||
: {
|
||||
color: '#4A5568',
|
||||
backgroundColor: 'transparent'
|
||||
})}
|
||||
>
|
||||
<Icon
|
||||
name={item.icon}
|
||||
width={24}
|
||||
height={24}
|
||||
color={item.activeLink.includes(router.pathname) ? '#2B6CB0' : '#4A5568'}
|
||||
/>
|
||||
<Box ml={5}>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</DrawerBody>
|
||||
|
||||
<DrawerFooter px={2}>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavbarPhone;
|
||||
6
src/components/Layout/style.module.scss
Normal file
6
src/components/Layout/style.module.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.informIcon {
|
||||
svg {
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
283
src/components/Markdown/codeLight.ts
Normal file
283
src/components/Markdown/codeLight.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
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'
|
||||
},
|
||||
'code[class*=language-]::selection': {
|
||||
textShadow: 'none',
|
||||
background: '#264f78'
|
||||
},
|
||||
'pre[class*=language-] ::selection': {
|
||||
textShadow: 'none',
|
||||
background: '#264f78'
|
||||
},
|
||||
'pre[class*=language-]::selection': {
|
||||
textShadow: 'none',
|
||||
background: '#264f78'
|
||||
},
|
||||
':not(pre)>code[class*=language-]': {
|
||||
padding: '.1em .3em',
|
||||
borderRadius: '.3em',
|
||||
color: '#db4c69',
|
||||
background: '#1e1e1e'
|
||||
},
|
||||
'.namespace': {
|
||||
opacity: '0.7'
|
||||
},
|
||||
'doctype.doctype-tag': {
|
||||
color: '#569cd6'
|
||||
},
|
||||
'doctype.name': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
comment: {
|
||||
color: '#6a9955'
|
||||
},
|
||||
prolog: {
|
||||
color: '#6a9955'
|
||||
},
|
||||
'.language-html .language-css .token.punctuation': {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
'.language-html .language-javascript .token.punctuation': {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
punctuation: {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
boolean: {
|
||||
color: '#569cd6'
|
||||
},
|
||||
constant: {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
inserted: {
|
||||
color: '#b5cea8'
|
||||
},
|
||||
number: {
|
||||
color: '#b5cea8'
|
||||
},
|
||||
property: {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
symbol: {
|
||||
color: '#b5cea8'
|
||||
},
|
||||
tag: {
|
||||
color: '#569cd6'
|
||||
},
|
||||
unit: {
|
||||
color: '#b5cea8'
|
||||
},
|
||||
'attr-name': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
builtin: {
|
||||
color: '#ce9178'
|
||||
},
|
||||
char: {
|
||||
color: '#ce9178'
|
||||
},
|
||||
deleted: {
|
||||
color: '#ce9178'
|
||||
},
|
||||
selector: {
|
||||
color: '#d7ba7d'
|
||||
},
|
||||
string: {
|
||||
color: '#ce9178'
|
||||
},
|
||||
'.language-css .token.string.url': {
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
entity: {
|
||||
color: '#569cd6'
|
||||
},
|
||||
operator: {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
'operator.arrow': {
|
||||
color: '#569cd6'
|
||||
},
|
||||
atrule: {
|
||||
color: '#ce9178'
|
||||
},
|
||||
'atrule.rule': {
|
||||
color: '#c586c0'
|
||||
},
|
||||
'atrule.url': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'atrule.url.function': {
|
||||
color: '#dcdcaa'
|
||||
},
|
||||
'atrule.url.punctuation': {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
keyword: {
|
||||
color: '#569cd6'
|
||||
},
|
||||
'keyword.control-flow': {
|
||||
color: '#c586c0'
|
||||
},
|
||||
'keyword.module': {
|
||||
color: '#c586c0'
|
||||
},
|
||||
function: {
|
||||
color: '#dcdcaa'
|
||||
},
|
||||
'function.maybe-class-name': {
|
||||
color: '#dcdcaa'
|
||||
},
|
||||
regex: {
|
||||
color: '#d16969'
|
||||
},
|
||||
important: {
|
||||
color: '#569cd6'
|
||||
},
|
||||
italic: {
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
'class-name': {
|
||||
color: '#4ec9b0'
|
||||
},
|
||||
'maybe-class-name': {
|
||||
color: '#4ec9b0'
|
||||
},
|
||||
console: {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
parameter: {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
interpolation: {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'punctuation.interpolation-punctuation': {
|
||||
color: '#569cd6'
|
||||
},
|
||||
'exports.maybe-class-name': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'imports.maybe-class-name': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
variable: {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
escape: {
|
||||
color: '#d7ba7d'
|
||||
},
|
||||
'tag.punctuation': {
|
||||
color: 'grey'
|
||||
},
|
||||
cdata: {
|
||||
color: 'grey'
|
||||
},
|
||||
'attr-value': {
|
||||
color: '#ce9178'
|
||||
},
|
||||
'attr-value.punctuation': {
|
||||
color: '#ce9178'
|
||||
},
|
||||
'attr-value.punctuation.attr-equals': {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
namespace: {
|
||||
color: '#4ec9b0'
|
||||
},
|
||||
'code[class*=language-javascript]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'code[class*=language-jsx]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'code[class*=language-tsx]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'code[class*=language-typescript]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'pre[class*=language-javascript]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'pre[class*=language-jsx]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'pre[class*=language-tsx]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'pre[class*=language-typescript]': {
|
||||
color: '#9cdcfe'
|
||||
},
|
||||
'code[class*=language-css]': {
|
||||
color: '#ce9178'
|
||||
},
|
||||
'pre[class*=language-css]': {
|
||||
color: '#ce9178'
|
||||
},
|
||||
'code[class*=language-html]': {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
'pre[class*=language-html]': {
|
||||
color: '#d4d4d4'
|
||||
},
|
||||
'.language-regex .token.anchor': {
|
||||
color: '#dcdcaa'
|
||||
},
|
||||
'.language-html .token.punctuation': {
|
||||
color: 'grey'
|
||||
},
|
||||
'pre[class*=language-]>code[class*=language-]': {
|
||||
position: 'relative',
|
||||
zIndex: '1'
|
||||
},
|
||||
'.line-highlight.line-highlight': {
|
||||
background: '#f7ebc6',
|
||||
boxShadow: 'inset 5px 0 0 #f7d87c',
|
||||
zIndex: '0'
|
||||
}
|
||||
};
|
||||
122
src/components/Markdown/index.module.scss
Normal file
122
src/components/Markdown/index.module.scss
Normal file
@@ -0,0 +1,122 @@
|
||||
.waitingAnimation::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
transform: translate(4px, 2px) scaleY(1.3);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
animation: blink 0.6s infinite;
|
||||
}
|
||||
.animation {
|
||||
:last-child::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
transform: translate(4px, 2px) scaleY(1.3);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
animation: blink 0.6s infinite;
|
||||
}
|
||||
}
|
||||
@keyframes blink {
|
||||
from,
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.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%;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px 15px;
|
||||
width: 100%;
|
||||
background-color: #222 !important;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
pre code {
|
||||
display: block;
|
||||
border: none;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.7;
|
||||
}
|
||||
}
|
||||
55
src/components/Markdown/index.tsx
Normal file
55
src/components/Markdown/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { useMemo, memo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import styles from './index.module.scss';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { codeLight } from './codeLight';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useCopyData } from '@/utils/tools';
|
||||
import Icon from '@/components/Icon';
|
||||
|
||||
const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean }) => {
|
||||
// const formatSource = useMemo(() => source.replace(/\n/g, '\n'), [source]);
|
||||
const { copyData } = useCopyData();
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={`${styles.markdown} ${
|
||||
isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''
|
||||
}`}
|
||||
rehypePlugins={[remarkGfm]}
|
||||
skipHtml={true}
|
||||
components={{
|
||||
p: 'div',
|
||||
pre: 'div',
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const code = String(children).replace(/\n$/, '');
|
||||
|
||||
return (
|
||||
<Box my={3} borderRadius={'md'} overflow={'hidden'}>
|
||||
<Flex py={2} px={5} backgroundColor={'#323641'} color={'#fff'} fontSize={'sm'}>
|
||||
<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>
|
||||
<Box ml={1}>复制代码</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<SyntaxHighlighter
|
||||
style={codeLight as any}
|
||||
showLineNumbers
|
||||
language={match?.[1]}
|
||||
{...props}
|
||||
>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{source}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Markdown);
|
||||
Reference in New Issue
Block a user