import { memo, useCallback, useEffect, useRef, useState } from "react"; import type { Components } from "react-markdown"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import "./markdown.web.css"; import type { ChatMarkdownProps } from "./markdown"; import { citationDomainLabel, closeUnterminatedFence, isExternalMarkdownUrl, parseCitationBadgeLabel, parseHexColorToken, sanitizeMarkdownUrl, } from "./markdown"; function CopyIcon() { return ( ); } function CheckIcon() { return ( ); } function ExternalLinkArrowIcon() { return ( ); } function ColorSwatchCode({ hex, children }: { hex: string; children: React.ReactNode }) { const [copied, setCopied] = useState(false); const timerRef = useRef(undefined); useEffect(() => () => window.clearTimeout(timerRef.current), []); const handleCopy = useCallback(() => { if (!navigator.clipboard) return; navigator.clipboard .writeText(hex) .then(() => { setCopied(true); window.clearTimeout(timerRef.current); timerRef.current = window.setTimeout(() => setCopied(false), 1200); }) .catch(() => {}); }, [hex]); return ( ); } function CodeBlock(props: React.ComponentPropsWithoutRef<"pre">) { const preRef = useRef(null); const resetTimerRef = useRef(undefined); const [copied, setCopied] = useState(false); const handleCopy = useCallback(() => { if (!navigator.clipboard) return; const text = preRef.current?.textContent ?? ""; navigator.clipboard .writeText(text) .then(() => { setCopied(true); window.clearTimeout(resetTimerRef.current); resetTimerRef.current = window.setTimeout(() => setCopied(false), 1500); }) .catch(() => {}); }, []); return (
      
    
); } const components: Components = { a({ node: _node, children, href, className, ...props }) { const external = isExternalMarkdownUrl(href); const childText = typeof children === "string" ? children : Array.isArray(children) && children.length === 1 && typeof children[0] === "string" ? children[0] : undefined; const citationNum = parseCitationBadgeLabel(childText, href); if (citationNum) { const domain = citationDomainLabel(href); return ( {citationNum} ); } const linkClass = external ? className ? `${className} rk-chat-markdown-external-link` : "rk-chat-markdown-external-link" : className; return ( {children} {external ? : null} ); }, code({ node: _node, className, children, ...props }) { const rawText = typeof children === "string" ? children : undefined; const hex = !className ? parseHexColorToken(rawText) : undefined; if (hex) { return {children}; } return ( {children} ); }, img({ node: _node, ...props }) { return {props.alt; }, pre({ node: _node, ...props }) { return ; }, }; export const ChatMarkdown = memo(function ChatMarkdown({ children, streaming = false, }: ChatMarkdownProps) { const source = streaming ? closeUnterminatedFence(children) : children; return (
sanitizeMarkdownUrl(url, true) ?? ""} > {source} {streaming ?
); }); export type { ChatMarkdownProps } from "./markdown";