"use client"; import { useState } from "react"; import ShikiHighlighter from "react-shiki"; import { CheckIcon, CopyIcon } from "lucide-react"; import type { BuilderConfig } from "./types"; import { BORDER_RADIUS_CLASS, FONT_SIZE_CLASS, MESSAGE_SPACING_CLASS, isLightColor, } from "@/lib/builder-utils"; import { analytics } from "@/lib/analytics"; interface BuilderCodeOutputProps { config: BuilderConfig; } export function BuilderCodeOutput({ config }: BuilderCodeOutputProps) { const [copied, setCopied] = useState(false); const componentCode = generateComponentCode(config); const handleCopy = async () => { analytics.builder.codeCopied(); await navigator.clipboard.writeText(componentCode); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (
thread.tsx
{componentCode.trim()}
); } function generateComponentCode(config: BuilderConfig): string { const { components, styles } = config; const iconImports = generateIconImports(config); const externalImports = [ iconImports, `import {`, ` ActionBarPrimitive,`, ` AuiIf,`, components.branchPicker && ` BranchPickerPrimitive,`, ` ComposerPrimitive,`, ` ErrorPrimitive,`, ` MessagePrimitive,`, ` ThreadPrimitive,`, `} from "@assistant-ui/react";`, components.markdown && components.typingIndicator === "dot" && `import "@assistant-ui/react-markdown/styles/dot.css";`, ] .filter(Boolean) .join("\n"); const internalImports = [ `import { Button } from "@/components/ui/button";`, `import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";`, components.markdown && `import { MarkdownText } from "@/components/assistant-ui/markdown-text";`, components.markdown && `import { ToolFallback } from "@/components/assistant-ui/tool-fallback";`, components.reasoning && `import { Reasoning, ReasoningGroup } from "@/components/assistant-ui/reasoning";`, components.sources && `import { Sources } from "@/components/assistant-ui/sources";`, components.attachments && `import { ComposerAddAttachment, ComposerAttachments, UserMessageAttachments, } from "@/components/assistant-ui/attachment";`, `import { cn } from "@/lib/utils";`, ] .filter(Boolean) .join("\n"); const borderRadiusClass = BORDER_RADIUS_CLASS[styles.borderRadius]; const fontSizeClass = FONT_SIZE_CLASS[styles.fontSize]; const messageSpacingClass = MESSAGE_SPACING_CLASS[styles.messageSpacing]; const accentColor = styles.colors.accent.light; const accentForeground = isLightColor(accentColor) ? "#000000" : "#ffffff"; const cssVariables = ` "--thread-max-width": "${styles.maxWidth}", "--accent-color": "${accentColor}", "--accent-foreground": "${accentForeground}",`; const fontFamilyStyle = styles.fontFamily !== "system-ui" ? `\n fontFamily: "${styles.fontFamily}",` : ""; const threadComponent = ` export function Thread() { return ( ${ components.threadWelcome ? ` s.thread.isEmpty}> ` : "" } ${components.scrollToBottom ? "" : ""} ); }`; const welcomeComponent = components.threadWelcome ? ` function ThreadWelcome() { return (
Hello there!
How can I help you today?
${ components.suggestions ? `
{/* Add your suggestions here */}
` : "" }
); }` : ""; const composerComponent = ` function Composer() { return ( ${components.attachments ? "" : ""} ); } function ComposerAction() { return (
${components.attachments ? "" : "
"} !s.thread.isRunning}> s.thread.isRunning}>
); }`; const scrollToBottomComponent = components.scrollToBottom ? ` function ThreadScrollToBottom() { return ( ); }` : ""; const animationClass = styles.animations ? " fade-in slide-in-from-bottom-1 animate-in duration-150" : ""; const userMessageComponent = ` function UserMessage() { return ( ${components.attachments ? "" : ""}
${ components.editMessage ? `
` : "" }
${components.branchPicker ? `` : ""}
); } ${ components.editMessage ? `function UserActionBar() { return ( ); }` : "" }`; const assistantMessageRootClass = `relative mx-auto w-full max-w-[var(--thread-max-width)] ${messageSpacingClass}${animationClass}`; // Build MessagePrimitive.Parts components object const partsComponents: string[] = []; if (components.markdown) { partsComponents.push(`Text: MarkdownText`); partsComponents.push(`tools: { Fallback: ToolFallback }`); } if (components.reasoning) { partsComponents.push(`Reasoning`); partsComponents.push(`ReasoningGroup`); } if (components.sources) { partsComponents.push(`Source: Sources`); } const partsComponentsStr = partsComponents.length > 0 ? ` components={{ ${partsComponents.join(",\n ")}, }}` : ""; const assistantMessageComponent = ` function AssistantMessage() { return ( ${ components.avatar ? `
` : "" }
${ components.loadingIndicator !== "none" ? ` s.thread.isRunning && s.message.content.length === 0}>
${ components.loadingIndicator === "text" ? ` ${components.loadingText}` : "" }
` : "" }
${components.branchPicker ? "" : ""}
${ components.followUpSuggestions ? ` !s.thread.isRunning}>
Tell me more Explain differently
` : "" }
); } function MessageError() { return ( ); }`; const feedbackButtons = components.actionBar.feedback ? ` ` : ""; const actionBarComponent = ` function AssistantActionBar() { return ( ${ components.actionBar.copy ? ` s.message.isCopied}> !s.message.isCopied}> ` : "" } ${ components.actionBar.reload ? ` ` : "" } ${ components.actionBar.speak ? ` ` : "" }${feedbackButtons} ); }`; const branchPickerComponent = components.branchPicker ? ` function BranchPicker({ className, ...rest }: { className?: string }) { return ( / ); }` : ""; const editComposerComponent = components.editMessage ? ` function EditComposer() { return (
); }` : ""; const allImports = `"use client"; ${externalImports} ${internalImports}`; return [ allImports, threadComponent, welcomeComponent, composerComponent, scrollToBottomComponent, userMessageComponent, editComposerComponent, assistantMessageComponent, actionBarComponent, branchPickerComponent, ] .filter(Boolean) .join("\n"); } function generateIconImports(config: BuilderConfig): string { const { components } = config; const icons: string[] = ["ArrowUpIcon", "DownloadIcon", "SquareIcon"]; if (components.scrollToBottom) icons.push("ArrowDownIcon"); if (components.editMessage) icons.push("PencilIcon"); if (components.branchPicker) icons.push("ChevronLeftIcon", "ChevronRightIcon"); if (components.actionBar.copy) icons.push("CheckIcon", "CopyIcon"); if (components.actionBar.reload) icons.push("RefreshCwIcon"); if (components.actionBar.speak) icons.push("Volume2Icon"); if (components.actionBar.feedback) icons.push("ThumbsUpIcon", "ThumbsDownIcon"); if (components.avatar) icons.push("BotIcon", "UserIcon"); if (components.loadingIndicator !== "none") icons.push("LoaderIcon"); return `import {\n ${[...new Set(icons)].sort().join(",\n ")},\n} from "lucide-react";`; }