"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";`;
}