Files
deerflow-code/frontend-web/src/components/workspace/messages/markdown-content.tsx
T
2026-05-25 18:20:40 +08:00

80 lines
2.3 KiB
TypeScript

"use client";
import { useMemo } from "react";
import type { AnchorHTMLAttributes } from "react";
import {
MessageResponse,
type MessageResponseProps,
} from "@/components/ai-elements/message";
import { streamdownPlugins } from "@/core/streamdown";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import { CitationLink } from "../citations/citation-link";
function isExternalUrl(href: string | undefined): boolean {
return !!href && /^https?:\/\//.test(href);
}
export type MarkdownContentProps = {
content: string;
isLoading: boolean;
rehypePlugins: MessageResponseProps["rehypePlugins"];
className?: string;
remarkPlugins?: MessageResponseProps["remarkPlugins"];
components?: MessageResponseProps["components"];
};
/** Renders markdown content. */
export function MarkdownContent({
content,
rehypePlugins,
className,
remarkPlugins = streamdownPlugins.remarkPlugins,
components: componentsFromProps,
}: MarkdownContentProps) {
const components = useMemo(() => {
return {
a: (props: AnchorHTMLAttributes<HTMLAnchorElement>) => {
if (typeof props.children === "string") {
const match = /^citation:(.+)$/.exec(props.children);
if (match) {
const [, text] = match;
return <CitationLink {...props}>{text}</CitationLink>;
}
}
const { className, target, rel, ...rest } = props;
const external = isExternalUrl(props.href);
return (
<a
{...rest}
className={cn(
"text-primary decoration-primary/30 hover:decoration-primary/60 underline underline-offset-2 transition-colors",
className,
)}
target={target ?? (external ? "_blank" : undefined)}
rel={rel ?? (external ? "noopener noreferrer" : undefined)}
/>
);
},
...componentsFromProps,
};
}, [componentsFromProps]);
if (!content) return null;
const displayContent = desensitize(content);
return (
<MessageResponse
className={className}
remarkPlugins={remarkPlugins}
rehypePlugins={rehypePlugins}
components={components}
>
{displayContent}
</MessageResponse>
);
}