diff --git a/web/src/components/markdown.tsx b/web/src/components/markdown.tsx
index 3b545a8d..f3e3fdfb 100644
--- a/web/src/components/markdown.tsx
+++ b/web/src/components/markdown.tsx
@@ -1 +1,321 @@
-CN/a
\ No newline at end of file
+'use client';
+
+import { cn } from '@/lib/utils';
+import { ImageIcon } from 'lucide-react';
+import Link from 'next/link';
+import {
+ JSX,
+ MouseEventHandler,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
+import ReactMarkdown from 'react-markdown';
+import rehypeHighlight from 'rehype-highlight';
+import rehypeHighlightLines from 'rehype-highlight-code-lines';
+import rehypeRaw from 'rehype-raw';
+import remarkDirective from 'remark-directive';
+import remarkFrontmatter from 'remark-frontmatter';
+import remarkGfm from 'remark-gfm';
+import remarkGithubAdmonitionsToDirectives from 'remark-github-admonitions-to-directives';
+import remarkHeaderId from 'remark-heading-id';
+import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
+import { AnchorLink } from './anchor-link';
+import { ChartMermaid } from './chart-mermaid';
+import { Skeleton } from './ui/skeleton';
+import { Table, TableBody, TableCell, TableHeader, TableRow } from './ui/table';
+import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
+
+import './markdown.css';
+
+const securityLink = (props: JSX.IntrinsicElements['a']) => {
+ const target = props.href?.match(/^http/) ? '_blank' : '_self';
+ const url = props.href?.replace(/\.md/, '');
+
+ const isNavLink = props.className?.includes('toc-link');
+ return isNavLink ? (
+
+
+
+
+
+ {props.children}
+
+
+ ) : (
+
+ {props.children}
+
+ );
+};
+
+const unSecurityLink = (props: JSX.IntrinsicElements['a']) => {
+ const url = props.href?.replace(/\.md/, '') || '/';
+ const isNavLink = props.className?.includes('toc-link');
+ const handleLinkClick: MouseEventHandler = (e) => {
+ if (!url.match(/http/)) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ };
+ return isNavLink ? (
+
+
+
+
+
+ {props.children}
+
+
+ ) : (
+
+ {props.children}
+
+ );
+};
+
+export const CustomImage = ({
+ src,
+ ...props
+}: JSX.IntrinsicElements['img']) => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [imageUrl, setImageUrl] = useState();
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const getImageSrc = useCallback(async () => {
+ if (typeof src !== 'string') return;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [path, queryString] = src.replace('asset://', '').split('?');
+ }, [src]);
+
+ useEffect(() => {}, []);
+
+ return;
+
+ return imageUrl ? (
+
+ ) : (
+
+
+
+ );
+};
+
+export const mdComponents = {
+ h1: (props: JSX.IntrinsicElements['h1']) => (
+
+ {props.children}
+
+ ),
+ h2: (props: JSX.IntrinsicElements['h2']) => (
+
+ {props.children}
+
+ ),
+ h3: (props: JSX.IntrinsicElements['h3']) => (
+
+ {props.children}
+
+ ),
+ h4: (props: JSX.IntrinsicElements['h4']) => (
+
+ {props.children}
+
+ ),
+ h5: (props: JSX.IntrinsicElements['h5']) => (
+
+ {props.children}
+
+ ),
+ h6: (props: JSX.IntrinsicElements['h6']) => (
+
+ {props.children}
+
+ ),
+ p: (props: JSX.IntrinsicElements['p']) => (
+ {props.children}
+ ),
+ blockquote: ({
+ className,
+ ...props
+ }: JSX.IntrinsicElements['blockquote']) => {
+ return (
+
+ {props.children}
+
+ );
+ },
+ img: ({ src, ...props }: JSX.IntrinsicElements['img']) => {
+ if (!src) {
+ return (
+
+
+
+ );
+ } else if (typeof src === 'string' && src.startsWith('asset://')) {
+ return ;
+ } else {
+ return (
+
+ );
+ }
+ },
+ pre: ({ className, ...props }: JSX.IntrinsicElements['pre']) => {
+ return (
+
+ {props.children}
+
+ );
+ },
+ code: ({ className, ...props }: JSX.IntrinsicElements['code']) => {
+ const match = /language-(\w+)/.exec(className || '');
+ const language = match?.[1];
+ if (language) {
+ if (language === 'mermaid') {
+ return (
+
+ {typeof props.children === 'string' ? props.children : ''}
+
+ );
+ } else {
+ return (
+
+ {props.children}
+
+ );
+ }
+ } else {
+ return (
+
+ {props.children}
+
+ );
+ }
+ },
+ ol: ({ className, ...props }: JSX.IntrinsicElements['ul']) => {
+ return (
+
+ );
+ },
+ ul: ({ className, ...props }: JSX.IntrinsicElements['ul']) => {
+ return (
+
+ );
+ },
+ li: ({ className, ...props }: JSX.IntrinsicElements['li']) => {
+ return (
+ {props.children}
+ );
+ },
+ nav: (props: JSX.IntrinsicElements['nav']) => {
+ if (props.className === 'toc') {
+ return ;
+ } else {
+ return ;
+ }
+ },
+ table: (props: JSX.IntrinsicElements['table']) => (
+
+ ),
+ thead: (props: JSX.IntrinsicElements['thead']) => ,
+ tbody: (props: JSX.IntrinsicElements['tbody']) => ,
+ tr: (props: JSX.IntrinsicElements['tr']) => ,
+ td: (props: JSX.IntrinsicElements['td']) => (
+ {props.children}
+ ),
+ th: (props: JSX.IntrinsicElements['th']) => (
+ {props.children}
+ ),
+};
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const mdRehypePlugins: any = [
+ rehypeRaw,
+ rehypeHighlight,
+ rehypeHighlightLines,
+];
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const mdRemarkPlugins: any = [
+ remarkGfm,
+ remarkFrontmatter,
+ remarkMdxFrontmatter,
+ remarkGithubAdmonitionsToDirectives,
+ remarkDirective,
+ [
+ remarkHeaderId,
+ {
+ defaults: true,
+ },
+ ],
+];
+
+export const Markdown = ({
+ rehypeToc = false,
+ security = false,
+ children,
+}: {
+ rehypeToc?: boolean;
+ security?: boolean;
+ children?: string;
+}) => {
+ const rehypePlugins = useMemo(() => {
+ const plugins = [...mdRehypePlugins];
+ if (rehypeToc) {
+ plugins.push([
+ rehypeToc,
+ {
+ headings: ['h2', 'h3', 'h4', 'h5', 'h6'],
+ },
+ ]);
+ }
+ return plugins;
+ }, [rehypeToc]);
+
+ return (
+ url}
+ components={{
+ a: security ? securityLink : unSecurityLink,
+ ...mdComponents,
+ }}
+ >
+ {children}
+
+ );
+};