"use client"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { cn } from "@/lib/utils"; import Link from "next/link"; import type { Nodes } from "hast"; import { toJsxRuntime } from "hast-util-to-jsx-runtime"; import type { Components } from "hast-util-to-jsx-runtime"; import type { ComponentProps, HTMLAttributes, MouseEvent } from "react"; import { Fragment, memo, useCallback, useMemo, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; import remarkGfm from "remark-gfm"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; import remend from "remend"; import { unified } from "unified"; // Sanitize model output and reject unsafe URL protocols. const schema = { ...defaultSchema, attributes: { ...defaultSchema.attributes, // Preserve checked state for sanitized GFM task lists. input: [...(defaultSchema.attributes?.input ?? []), "checked"], }, }; const processor = unified() .use(remarkParse) .use(remarkGfm) .use(remarkRehype) .use(rehypeSanitize, schema); // Keep incomplete links as text while streaming. const REMEND_OPTIONS = { linkMode: "text-only" } as const; // Repair only streaming text; remend can corrupt settled italicized prices. const repair = (markdown: string, isStreaming: boolean) => isStreaming ? remend(markdown, REMEND_OPTIONS) : markdown; export interface LinkSafetyConfig { enabled: boolean; onLinkCheck?: (url: string) => boolean; } type AnchorProps = ComponentProps<"a"> & { linkSafety?: LinkSafetyConfig; onUntrusted: (url: string) => void; }; const LINK_CLASS = "font-medium underline underline-offset-4 hover:text-foreground"; const isRouteHref = (href: string) => /^[/#?]/.test(href); const MarkdownLink = ({ href, linkSafety, onUntrusted, ...props }: AnchorProps) => { const handleClick = useCallback( (event: MouseEvent) => { if (!href || !linkSafety?.enabled || !linkSafety.onLinkCheck) return; if (linkSafety.onLinkCheck(href)) return; event.preventDefault(); onUntrusted(href); }, [href, linkSafety, onUntrusted] ); if (href && isRouteHref(href)) { return ; } return ( ); }; const buildComponents = ( linkSafety: LinkSafetyConfig | undefined, onUntrusted: (url: string) => void ): Partial => ({ a: (props: ComponentProps<"a">) => ( ), blockquote: (props: ComponentProps<"blockquote">) => (
), code: ({ className, ...props }: ComponentProps<"code">) => { const isBlock = typeof className === "string" && className.includes("language-"); return ( ); }, em: (props: ComponentProps<"em">) => , h1: (props: ComponentProps<"h1">) => (

), h2: (props: ComponentProps<"h2">) => (

), h3: (props: ComponentProps<"h3">) => (

), h4: (props: ComponentProps<"h4">) => (

), h5: (props: ComponentProps<"h5">) => (

), h6: (props: ComponentProps<"h6">) => (
), hr: (props: ComponentProps<"hr">) => (
), input: (props: ComponentProps<"input">) => ( ), img: ({ alt, ...props }: ComponentProps<"img">) => ( // biome-ignore lint/nursery/noImgElement: model output, not a known asset {alt ), li: (props: ComponentProps<"li">) =>
  • , ol: (props: ComponentProps<"ol">) => (
      ), p: (props: ComponentProps<"p">) => (

      ), pre: (props: ComponentProps<"pre">) => (

        ),
        strong: (props: ComponentProps<"strong">) => (
          
        ),
        table: (props: ComponentProps<"table">) => (
          
      ), td: (props: ComponentProps<"td">) => (
      ), th: (props: ComponentProps<"th">) => ( ), ul: (props: ComponentProps<"ul">) => (
        ), }); export type MarkdownProps = Omit, "children"> & { children: string; isAnimating?: boolean; linkSafety?: LinkSafetyConfig; }; export const Markdown = memo( ({ children, className, isAnimating, linkSafety, ...props }: MarkdownProps) => { const [pending, setPending] = useState(null); const handleUntrusted = useCallback((url: string) => setPending(url), []); const content = useMemo(() => { const source = repair(children ?? "", isAnimating === true); const tree = processor.runSync(processor.parse(source)) as Nodes; return toJsxRuntime(tree, { components: buildComponents(linkSafety, handleUntrusted), Fragment, jsx, jsxs, }); }, [children, isAnimating, linkSafety, handleUntrusted]); return (
        *:first-child]:mt-0 [&>*:last-child]:mb-0", className )} {...props} > {content} !open && setPending(null)} open={pending !== null} > Leave this site? This link points somewhere outside the store. Continue only if you trust it.

        {pending}

        ); }, (prev, next) => prev.children === next.children && prev.isAnimating === next.isAnimating ); Markdown.displayName = "Markdown";