better-chatbot / src /components /pre-block.tsx
Bot
Initial commit for HF Spaces
05c5ed5
Raw
History Blame Contribute Delete
4.04 kB
"use client";
import type { JSX } from "react";
import {
bundledLanguages,
codeToHast,
type BundledLanguage,
} from "shiki/bundle/web";
import { Fragment, useLayoutEffect, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
import { safe } from "ts-safe";
import { cn } from "lib/utils";
import { useTheme } from "next-themes";
import { Button } from "ui/button";
import { CheckIcon, CopyIcon } from "lucide-react";
import JsonView from "ui/json-view";
import { useCopy } from "@/hooks/use-copy";
import dynamic from "next/dynamic";
// Dynamically import MermaidDiagram component
const MermaidDiagram = dynamic(
() => import("./mermaid-diagram").then((mod) => mod.MermaidDiagram),
{
loading: () => (
<div className="text-sm flex bg-accent/30 flex-col rounded-2xl relative my-4 overflow-hidden border">
<div className="w-full flex z-20 py-2 px-4 items-center">
<span className="text-sm text-muted-foreground">mermaid</span>
</div>
<div className="relative overflow-x-auto px-6 pb-6">
<div className="h-20 w-full flex items-center justify-center">
<span className="text-muted-foreground">
Loading Mermaid renderer...
</span>
</div>
</div>
</div>
),
ssr: false,
},
);
const PurePre = ({
children,
className,
code,
lang,
}: {
children: any;
className?: string;
code: string;
lang: string;
}) => {
const { copied, copy } = useCopy();
return (
<pre className={cn("relative", className)}>
<div className="p-1.5 border-b mb-4 z-20 bg-secondary">
<div className="w-full flex z-20 py-0.5 px-4 items-center">
<span className="text-sm text-muted-foreground">{lang}</span>
<Button
size="icon"
variant={copied ? "secondary" : "ghost"}
className="ml-auto z-10 p-3! size-2! rounded-sm"
onClick={() => {
copy(code);
}}
>
{copied ? <CheckIcon /> : <CopyIcon className="size-3!" />}
</Button>
</div>
</div>
<div className="relative overflow-x-auto px-6 pb-6">{children}</div>
</pre>
);
};
export async function Highlight(
code: string,
lang: BundledLanguage | (string & {}),
theme: string,
) {
const parsed: BundledLanguage = (
bundledLanguages[lang] ? lang : "md"
) as BundledLanguage;
if (lang === "json") {
return (
<PurePre code={code} lang={lang}>
<JsonView data={code} initialExpandDepth={3} />
</PurePre>
);
}
if (lang === "mermaid") {
return (
<PurePre code={code} lang={lang}>
<MermaidDiagram chart={code} />
</PurePre>
);
}
const out = await codeToHast(code, {
lang: parsed,
theme,
});
return toJsxRuntime(out, {
Fragment,
jsx,
jsxs,
components: {
pre: (props) => <PurePre {...props} code={code} lang={lang} />,
},
}) as JSX.Element;
}
export function PreBlock({ children }: { children: any }) {
const code = children.props.children;
const { theme } = useTheme();
const language = children.props.className?.split("-")?.[1] || "bash";
const [loading, setLoading] = useState(true);
const [component, setComponent] = useState<JSX.Element | null>(
<PurePre className="animate-pulse" code={code} lang={language}>
{children}
</PurePre>,
);
useLayoutEffect(() => {
safe()
.map(() =>
Highlight(
code,
language,
theme == "dark" ? "dark-plus" : "github-light",
),
)
.ifOk(setComponent)
.watch(() => setLoading(false));
}, [theme, language, code]);
// For other code blocks, render as before
return (
<div
className={cn(
loading && "animate-pulse",
"text-sm flex bg-secondary/40 shadow border flex-col rounded relative my-4 overflow-hidden",
)}
>
{component}
</div>
);
}