mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-23 00:16:48 +00:00
chore: merge with web UI project
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { CheckOutlined, CopyOutlined } from "@ant-design/icons";
|
||||
import { useMemo, useState } from "react";
|
||||
import ReactMarkdown, {
|
||||
type Options as ReactMarkdownOptions,
|
||||
} from "react-markdown";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import "katex/dist/katex.min.css";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "~/components/ui/tooltip";
|
||||
import { rehypeSplitWordsIntoSpans } from "~/core/rehype";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export function Markdown({
|
||||
className,
|
||||
children,
|
||||
style,
|
||||
enableCopy,
|
||||
animate = false,
|
||||
...props
|
||||
}: ReactMarkdownOptions & {
|
||||
className?: string;
|
||||
enableCopy?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
animate?: boolean;
|
||||
}) {
|
||||
const rehypePlugins = useMemo(() => {
|
||||
if (animate) {
|
||||
return [rehypeKatex, rehypeSplitWordsIntoSpans];
|
||||
}
|
||||
return [rehypeKatex];
|
||||
}, [animate]);
|
||||
return (
|
||||
<div
|
||||
className={cn(className, "markdown flex flex-col gap-4")}
|
||||
style={style}
|
||||
>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={rehypePlugins}
|
||||
components={{
|
||||
a: ({ href, children }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{dropMarkdownQuote(processKatexInMarkdown(children))}
|
||||
</ReactMarkdown>
|
||||
{enableCopy && typeof children === "string" && (
|
||||
<div className="flex">
|
||||
<CopyButton content={children} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CopyButton({ content }: { content: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-full"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{copied ? (
|
||||
<CheckOutlined className="h-4 w-4" />
|
||||
) : (
|
||||
<CopyOutlined className="h-4 w-4" />
|
||||
)}{" "}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Copy</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
function processKatexInMarkdown(markdown?: string | null) {
|
||||
if (!markdown) return markdown;
|
||||
|
||||
const markdownWithKatexSyntax = markdown
|
||||
.replace(/\\\\\[/g, "$$$$") // Replace '\\[' with '$$'
|
||||
.replace(/\\\\\]/g, "$$$$") // Replace '\\]' with '$$'
|
||||
.replace(/\\\\\(/g, "$$$$") // Replace '\\(' with '$$'
|
||||
.replace(/\\\\\)/g, "$$$$") // Replace '\\)' with '$$'
|
||||
.replace(/\\\[/g, "$$$$") // Replace '\[' with '$$'
|
||||
.replace(/\\\]/g, "$$$$") // Replace '\]' with '$$'
|
||||
.replace(/\\\(/g, "$$$$") // Replace '\(' with '$$'
|
||||
.replace(/\\\)/g, "$$$$"); // Replace '\)' with '$$';
|
||||
return markdownWithKatexSyntax;
|
||||
}
|
||||
|
||||
function dropMarkdownQuote(markdown?: string | null) {
|
||||
if (!markdown) return markdown;
|
||||
return markdown
|
||||
.replace(/^```markdown\n/gm, "")
|
||||
.replace(/^```text\n/gm, "")
|
||||
.replace(/^```\n/gm, "")
|
||||
.replace(/\n```$/gm, "");
|
||||
}
|
||||
Reference in New Issue
Block a user