Files
Alfred/src/web/features/chat/parts/HighlightBlock.tsx

72 lines
1.8 KiB
TypeScript

import { CopyOutlined } from "@ant-design/icons";
import { App, Button } from "antd";
import { useCallback, useEffect, useState } from "react";
import { codeToHtml } from "shiki";
import { useIsDark } from "../../../shared/hooks/use-is-dark";
interface HighlightBlockProps {
code: string;
isStreaming: boolean;
lang: string;
}
export function HighlightBlock({ code, isStreaming, lang }: HighlightBlockProps) {
const { message } = App.useApp();
const isDark = useIsDark();
const [highlighted, setHighlighted] = useState<null | string>(null);
const handleCopy = useCallback(() => {
navigator.clipboard.writeText(code).then(
() => message.success("已复制"),
() => message.error("复制失败"),
);
}, [code, message]);
useEffect(() => {
if (isStreaming || !code) return;
let cancelled = false;
codeToHtml(code, {
lang,
theme: isDark ? "github-dark" : "github-light",
})
.then((html) => {
if (!cancelled) setHighlighted(html);
})
.catch(() => {
if (!cancelled) setHighlighted(null);
});
return () => {
cancelled = true;
};
}, [code, lang, isDark, isStreaming]);
if (isStreaming) {
return (
<pre className="code-block">
<code>{code}</code>
</pre>
);
}
return (
<div className="code-block">
<div className="code-block-header">
<span className="code-block-lang">{lang}</span>
<Button className="btn-dimmed" icon={<CopyOutlined />} onClick={handleCopy} size="small" type="text" />
</div>
{highlighted ? (
<div className="code-block-body" dangerouslySetInnerHTML={{ __html: highlighted }} />
) : (
<div className="code-block-body">
<pre>
<code>{code}</code>
</pre>
</div>
)}
</div>
);
}