refactor: 替换 Markdown 渲染组件为 markdown-to-jsx
This commit is contained in:
3
src/web/css.d.ts
vendored
3
src/web/css.d.ts
vendored
@@ -1,4 +1 @@
|
||||
declare module "*.css";
|
||||
declare module "react-syntax-highlighter/dist/esm/styles/prism" {
|
||||
export { oneDark, oneLight } from "react-syntax-highlighter";
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { CopyOutlined } from "@ant-design/icons";
|
||||
import { CodeHighlighter } from "@ant-design/x";
|
||||
import { App, Button, Flex, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
|
||||
import { useIsDark } from "../../../shared/hooks/use-is-dark";
|
||||
|
||||
type SyntaxTheme = Record<string, Record<string, null | number | string>>;
|
||||
|
||||
const customOneDark: SyntaxTheme = {
|
||||
...(oneDark as SyntaxTheme),
|
||||
'pre[class*="language-"]': {
|
||||
...(oneDark as SyntaxTheme)['pre[class*="language-"]'],
|
||||
margin: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const customOneLight: SyntaxTheme = {
|
||||
...(oneLight as SyntaxTheme),
|
||||
'pre[class*="language-"]': {
|
||||
...(oneLight as SyntaxTheme)['pre[class*="language-"]'],
|
||||
margin: 0,
|
||||
},
|
||||
};
|
||||
|
||||
interface CodeBlockWithCopyProps {
|
||||
block?: boolean;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
lang?: string;
|
||||
streamStatus?: "done" | "loading";
|
||||
}
|
||||
|
||||
export function CodeBlockWithCopy({ block, children, className, lang }: CodeBlockWithCopyProps) {
|
||||
const { message } = App.useApp();
|
||||
const isDark = useIsDark();
|
||||
|
||||
if (!block) {
|
||||
return <code className={className}>{children}</code>;
|
||||
}
|
||||
|
||||
const codeText = extractText(children);
|
||||
const displayLang = lang ?? "plaintext";
|
||||
|
||||
const handleCopy = () => {
|
||||
void navigator.clipboard.writeText(codeText).then(() => {
|
||||
void message.success("已复制");
|
||||
});
|
||||
};
|
||||
|
||||
const header = (
|
||||
<Flex align="center" justify="space-between" style={{ padding: "0 4px" }}>
|
||||
<Typography.Text style={{ color: "var(--ant-color-text-quaternary)", fontSize: 12 }}>
|
||||
{displayLang}
|
||||
</Typography.Text>
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={handleCopy}
|
||||
size="small"
|
||||
style={{ color: "var(--ant-color-text-quaternary)" }}
|
||||
type="text"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<CodeHighlighter
|
||||
header={header}
|
||||
highlightProps={{ style: (isDark ? customOneDark : customOneLight) as React.CSSProperties }}
|
||||
lang={displayLang}
|
||||
>
|
||||
{codeText}
|
||||
</CodeHighlighter>
|
||||
);
|
||||
}
|
||||
|
||||
function extractText(children: React.ReactNode): string {
|
||||
return React.Children.toArray(children)
|
||||
.map((child) => (typeof child === "string" ? child : ""))
|
||||
.join("");
|
||||
}
|
||||
@@ -1,38 +1,22 @@
|
||||
import { XMarkdown } from "@ant-design/x-markdown";
|
||||
import "@ant-design/x-markdown/themes/dark.css";
|
||||
import "@ant-design/x-markdown/themes/light.css";
|
||||
import { Typography } from "antd";
|
||||
import Markdown from "markdown-to-jsx/react";
|
||||
|
||||
import type { PartProps } from "./types";
|
||||
|
||||
import { useIsDark } from "../../../shared/hooks/use-is-dark";
|
||||
import { CodeBlockWithCopy } from "./CodeBlockWithCopy";
|
||||
|
||||
interface TextPartProps extends PartProps {
|
||||
isStreaming: boolean;
|
||||
role: string;
|
||||
}
|
||||
|
||||
const xmarkdownComponents = {
|
||||
code: CodeBlockWithCopy,
|
||||
pre: ({ children }: { children?: React.ReactNode }) => <>{children}</>,
|
||||
};
|
||||
|
||||
export function TextPart({ isStreaming, part, role }: TextPartProps) {
|
||||
const text = typeof part["text"] === "string" ? part["text"] : "";
|
||||
const isDark = useIsDark();
|
||||
|
||||
return (
|
||||
<div className="part-body">
|
||||
{role === "user" ? (
|
||||
<Typography.Paragraph className="message-body-text">{text}</Typography.Paragraph>
|
||||
) : (
|
||||
<XMarkdown
|
||||
className={isDark ? "x-markdown-dark" : "x-markdown-light"}
|
||||
components={xmarkdownComponents}
|
||||
content={text}
|
||||
streaming={{ hasNextChunk: isStreaming }}
|
||||
/>
|
||||
<Markdown options={{ optimizeForStreaming: isStreaming }}>{text}</Markdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -239,32 +239,6 @@ body {
|
||||
--os-handle-interactive-area-offset: 4px;
|
||||
}
|
||||
|
||||
.x-markdown-light table,
|
||||
.x-markdown-dark table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.x-markdown-light th,
|
||||
.x-markdown-light td,
|
||||
.x-markdown-dark th,
|
||||
.x-markdown-dark td {
|
||||
border: 1px solid var(--ant-color-border);
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.x-markdown-light th,
|
||||
.x-markdown-dark th {
|
||||
background: var(--ant-color-fill-quaternary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.x-markdown-light .x-md-table-wrap,
|
||||
.x-markdown-dark .x-md-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.app-inbox-page {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user