{
  "title": "Response Renderer",
  "description": "Styled markdown renderer optimized for displaying AI-generated responses",
  "name": "native-ai-response-renderer",
  "dependencies": [
    "react-markdown",
    "remark-gfm"
  ],
  "registryDependencies": [],
  "type": "registry:ui",
  "files": [
    {
      "path": "components/paceui/native-ai/response-renderer/index.tsx",
      "type": "registry:ui",
      "content": "import React, { Children, type ReactNode, cloneElement, isValidElement, useMemo } from \"react\";\n\nimport { motion } from \"motion/react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\n\nexport type ResponseEffectType =\n    \"none\" | \"fade-in\" | \"fade-in-up\" | \"fade-in-down\" | \"blur-in\" | \"blur-in-up\" | \"shimmer\" | \"glow\";\n\nexport interface ResponseRendererProps {\n    content: string;\n    effect?: ResponseEffectType;\n    className?: string;\n}\n\ntype MDComponentProps = React.HTMLAttributes<HTMLElement> & { children?: ReactNode };\n\nconst EFFECT_CONFIGS: Record<string, any> = {\n    \"fade-in\": { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.25 } },\n    \"fade-in-up\": {\n        initial: { opacity: 0, y: 4 },\n        animate: { opacity: 1, y: 0 },\n        transition: { duration: 0.2, ease: \"easeOut\" },\n    },\n    \"fade-in-down\": {\n        initial: { opacity: 0, y: -4 },\n        animate: { opacity: 1, y: 0 },\n        transition: { duration: 0.2, ease: \"easeOut\" },\n    },\n    \"blur-in\": {\n        initial: { opacity: 0, filter: \"blur(3px)\", y: 2 },\n        animate: { opacity: 1, filter: \"blur(0px)\", y: 0 },\n        transition: { duration: 0.25, ease: \"easeOut\" },\n    },\n    \"blur-in-up\": {\n        initial: { opacity: 0, filter: \"blur(3.5px)\", y: 6 },\n        animate: { opacity: 1, filter: \"blur(0px)\", y: 0 },\n        transition: { duration: 0.25, ease: \"easeOut\" },\n    },\n    shimmer: {\n        initial: { opacity: 0, filter: \"brightness(1.8)\" },\n        animate: { opacity: 1, filter: \"brightness(1)\" },\n        transition: { duration: 0.3, ease: \"easeOut\" },\n    },\n    glow: {\n        initial: { opacity: 0, filter: \"drop-shadow(0px 0px 4px rgba(124, 58, 237, 0.6))\" },\n        animate: { opacity: 1, filter: \"drop-shadow(0px 0px 0px transparent)\" },\n        transition: { duration: 0.3, ease: \"easeOut\" },\n    },\n};\n\nconst AnimatedText = ({ children, effect = \"none\" }: { children: ReactNode; effect: ResponseEffectType }) => {\n    if (effect === \"none\" || !children) return <>{children}</>;\n\n    const animationConfig = EFFECT_CONFIGS[effect] || EFFECT_CONFIGS[\"fade-in\"];\n\n    const processNode = (node: ReactNode, index: number): ReactNode => {\n        if (typeof node === \"string\") {\n            return (\n                <span key={`wrap-${index}`} className=\"inline\">\n                    {(node.match(/(\\s+|\\S+)/g) || []).map((part, partIndex) =>\n                        /^\\s+$/.test(part) ? (\n                            <span key={`${index}-${partIndex}`}>{part}</span>\n                        ) : (\n                            <motion.span\n                                key={`${index}-${partIndex}`}\n                                className=\"inline-block origin-center whitespace-pre\"\n                                {...animationConfig}>\n                                {part}\n                            </motion.span>\n                        ),\n                    )}\n                </span>\n            );\n        }\n\n        if (isValidElement(node)) {\n            const element = node as React.ReactElement<{ children?: ReactNode }>;\n\n            if (element.type === \"code\" || element.type === \"pre\") return element;\n\n            if (element.props && element.props.children) {\n                return cloneElement(element, {\n                    key: element.key || `elem-${index}`,\n                    children: Children.map(element.props.children, processNode),\n                });\n            }\n        }\n        return node;\n    };\n\n    return <>{Children.map(children, processNode)}</>;\n};\n\nexport const ResponseRenderer = ({ content, effect = \"none\", className }: ResponseRendererProps) => {\n    const formattedContent = useMemo(() => formatStreamingMarkdown(content), [content]);\n\n    const components = useMemo(() => {\n        const withAnimation =\n            (Tag: React.ElementType, defaultClassName: string) =>\n            ({ children, ...props }: MDComponentProps) => (\n                <Tag className={defaultClassName} {...props}>\n                    <AnimatedText effect={effect}>{children}</AnimatedText>\n                </Tag>\n            );\n\n        return {\n            h1: withAnimation(\"h1\", \"mb-3 text-2xl font-medium\"),\n            h2: withAnimation(\"h2\", \"mb-2 text-xl font-medium\"),\n            h3: withAnimation(\"h3\", \"mb-1 text-lg font-medium\"),\n            ul: (props: MDComponentProps) => <ul className=\"mb-4 list-disc space-y-1 pl-6\" {...props} />,\n            li: withAnimation(\"li\", \"pl-1\"),\n            strong: withAnimation(\"span\", \"font-medium\"),\n            p: withAnimation(\"p\", \"mb-2 last:mb-0\"),\n            a: withAnimation(\"a\", \"text-primary underline underline-offset-4 transition-opacity hover:opacity-80\"),\n            table: (props: MDComponentProps) => (\n                <div className=\"my-4 w-full overflow-x-auto rounded-lg border\">\n                    <table className=\"w-full text-sm\" {...props} />\n                </div>\n            ),\n            thead: (props: MDComponentProps) => <thead className=\"bg-muted/50 border-b\" {...props} />,\n            tbody: (props: MDComponentProps) => <tbody className=\"[&_tr:last-child]:border-0\" {...props} />,\n            tr: (props: MDComponentProps) => <tr className=\"hover:bg-muted/50 border-b transition-colors\" {...props} />,\n            th: withAnimation(\"th\", \"text-muted-foreground h-10 px-4 text-left align-middle font-medium\"),\n            td: withAnimation(\"td\", \"p-4 align-middle\"),\n        };\n    }, [effect]);\n\n    return (\n        <div className={className}>\n            <ReactMarkdown remarkPlugins={[remarkGfm]} components={components as any}>\n                {formattedContent}\n            </ReactMarkdown>\n        </div>\n    );\n};\n\nexport function formatStreamingMarkdown(input: string): string {\n    if (!input) return \"\";\n    const trailingSpaces = input.match(/\\s+$/)?.[0] || \"\";\n    let str = trailingSpaces ? input.slice(0, -trailingSpaces.length) : input;\n    if (!str) return input;\n\n    str = str\n        .replace(/(?:^|\\s)(?:[*_~]+|`+|\\[|!\\[)$/, (match) => match.match(/^\\s+/)?.[0] || \"\")\n        .replace(/(?:^|\\n)(#{1,6})$/, \"\");\n    if (!str) return trailingSpaces;\n\n    let fence = \"\";\n    for (const line of str.split(\"\\n\")) {\n        const trimmedLine = line.trimStart();\n        if (trimmedLine.startsWith(\"```\") || trimmedLine.startsWith(\"~~~\")) {\n            fence = fence && trimmedLine.startsWith(fence) ? \"\" : fence || trimmedLine.slice(0, 3);\n        }\n    }\n    if (fence) return str + (str.endsWith(\"\\n\") ? \"\" : \"\\n\") + fence + trailingSpaces;\n\n    let inCode = \"\",\n        i = 0;\n    const stack: string[] = [];\n\n    while (i < str.length) {\n        if (str[i] === \"\\\\\" && ++i < str.length) {\n            i++;\n            continue;\n        }\n\n        if (str[i] === \"`\") {\n            const start = i;\n            while (i < str.length && str[i] === \"`\") i++;\n            const ticks = str.slice(start, i);\n            inCode = inCode ? (inCode === ticks ? \"\" : inCode) : ticks;\n            continue;\n        }\n\n        if (inCode) {\n            i++;\n            continue;\n        }\n\n        if (str[i] === \"~\" && str[i + 1] === \"~\") {\n            const prevChar = i > 0 ? str[i - 1] : \" \";\n            const nextChar = i + 2 < str.length ? str[i + 2] : \"\";\n            if (stack[stack.length - 1] === \"~~\" && prevChar !== \" \") stack.pop();\n            else if (nextChar !== \" \") stack.push(\"~~\");\n            i += 2;\n            continue;\n        }\n\n        if (str[i] === \"*\" || str[i] === \"_\") {\n            const char = str[i],\n                start = i;\n            while (i < str.length && str[i] === char) i++;\n\n            const delimiter = str.slice(start, i);\n            const prevChar = start > 0 ? str[start - 1] : \" \";\n            const nextChar = i < str.length ? str[i] : \"\";\n\n            const canOpen = nextChar !== \" \",\n                canClose = prevChar !== \" \",\n                topOfStack = stack[stack.length - 1] || \"\";\n\n            if (canClose && topOfStack.startsWith(char)) {\n                if (topOfStack === delimiter) stack.pop();\n                else if (i === str.length && delimiter.length < topOfStack.length) continue;\n                else if (canOpen) stack.push(delimiter);\n            } else if (canOpen) {\n                stack.push(delimiter);\n            }\n            continue;\n        }\n\n        if (str[i] === \"[\") {\n            if (str[i + 1] !== \" \") stack.push(\"]\");\n            i++;\n            continue;\n        }\n        if (str[i] === \"]\" && stack[stack.length - 1] === \"]\") {\n            stack.pop();\n            if (str[++i] === \"(\") {\n                stack.push(\")\");\n                i++;\n            }\n            continue;\n        }\n        if (str[i] === \")\" && stack[stack.length - 1] === \")\") {\n            stack.pop();\n            i++;\n            continue;\n        }\n\n        i++;\n    }\n\n    return str + (inCode || stack.reverse().join(\"\")) + trailingSpaces;\n}\n"
    }
  ],
  "mcp": {
    "description": "Device-local AI-powered functionality. Styled markdown renderer optimized for displaying AI-generated responses Relies on react-markdown, remark-gfm. Ideal for privacy-first AI interfaces, streaming text responses, and Chrome Gemini Nano integrations.",
    "tags": [
      "native-ai",
      "ui"
    ]
  }
}