import type { BuilderConfig } from "@/components/builder/types"; const REGISTRY_BASE_URL = "https://r.assistant-ui.com"; export function determineRegistryDependencies(config: BuilderConfig): string[] { const { components } = config; const deps: string[] = [ "button", `${REGISTRY_BASE_URL}/tooltip-icon-button.json`, ]; if (components.markdown) { deps.push(`${REGISTRY_BASE_URL}/markdown-text.json`); deps.push(`${REGISTRY_BASE_URL}/tool-fallback.json`); } if (components.attachments) { deps.push(`${REGISTRY_BASE_URL}/attachment.json`); } return deps; } export function generateCssVars( config: BuilderConfig, mode: "light" | "dark", ): Record { const { styles } = config; const vars: Record = {}; const accentColor = mode === "light" ? styles.colors.accent.light : styles.colors.accent.dark; vars["--aui-accent"] = accentColor; vars["--aui-accent-foreground"] = isLightColor(accentColor) ? "#000000" : "#ffffff"; if (styles.colors.background) { vars["--aui-background"] = mode === "light" ? styles.colors.background.light : styles.colors.background.dark; } if (styles.colors.foreground) { vars["--aui-foreground"] = mode === "light" ? styles.colors.foreground.light : styles.colors.foreground.dark; } if (styles.colors.muted) { vars["--aui-muted"] = mode === "light" ? styles.colors.muted.light : styles.colors.muted.dark; } if (styles.colors.mutedForeground) { vars["--aui-muted-foreground"] = mode === "light" ? styles.colors.mutedForeground.light : styles.colors.mutedForeground.dark; } if (styles.colors.border) { vars["--aui-border"] = mode === "light" ? styles.colors.border.light : styles.colors.border.dark; } if (styles.colors.userMessage) { vars["--aui-user-message"] = mode === "light" ? styles.colors.userMessage.light : styles.colors.userMessage.dark; } if (styles.colors.composer) { vars["--aui-composer"] = mode === "light" ? styles.colors.composer.light : styles.colors.composer.dark; } vars["--aui-max-width"] = styles.maxWidth; vars["--aui-border-radius"] = getBorderRadiusValue(styles.borderRadius); vars["--aui-font-family"] = styles.fontFamily; return vars; } function getBorderRadiusValue(radius: string): string { const map: Record = { none: "0", sm: "0.125rem", md: "0.375rem", lg: "0.5rem", full: "1.5rem", }; return map[radius] || "0.5rem"; } function isLightColor(hexColor: string): boolean { const hex = hexColor.replace("#", ""); const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; return luminance > 0.5; } export function generateRegistryJson(config: BuilderConfig) { const registryDependencies = determineRegistryDependencies(config); const threadCode = generateThreadCode(config); return { name: "assistant-ui-thread", type: "registry:block", dependencies: [ "@assistant-ui/react", "@assistant-ui/react-ui", "lucide-react", ...(config.components.markdown ? ["@assistant-ui/react-markdown"] : []), ], registryDependencies, files: [ { path: "components/assistant-ui/thread.tsx", content: threadCode, type: "registry:component", }, ], cssVars: { light: generateCssVars(config, "light"), dark: generateCssVars(config, "dark"), }, }; } function generateThreadCode(config: BuilderConfig): string { const { components, styles } = config; const externalImports = [ generateIconImports(config), `import {`, ` ActionBarPrimitive,`, ` AuiIf,`, components.branchPicker ? ` BranchPickerPrimitive,` : null, ` ComposerPrimitive,`, ` ErrorPrimitive,`, ` MessagePrimitive,`, ` ThreadPrimitive,`, `} from "@assistant-ui/react";`, components.markdown && components.typingIndicator === "dot" ? `import "@assistant-ui/react-markdown/styles/dot.css";` : null, ] .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";` : null, components.markdown ? `import { ToolFallback } from "@/components/assistant-ui/tool-fallback";` : null, components.attachments ? `import { ComposerAddAttachment, ComposerAttachments, UserMessageAttachments, } from "@/components/assistant-ui/attachment";` : null, `import { cn } from "@/lib/utils";`, ] .filter(Boolean) .join("\n"); const imports = `"use client"; ${externalImports} ${internalImports}`; const borderRadiusClass = getBorderRadiusClass(styles.borderRadius); const fontSizeClass = getFontSizeClass(styles.fontSize); const messageSpacingClass = getMessageSpacingClass(styles.messageSpacing); const accentColor = styles.colors.accent.light; const accentForeground = isLightColor(accentColor) ? "#000000" : "#ffffff"; const threadComponent = ` export function Thread() { return ( ${ components.threadWelcome ? ` s.thread.isEmpty}> ` : "" } ${components.scrollToBottom ? "" : ""} ); }`; const additionalComponents = [ components.threadWelcome ? generateWelcomeComponent(config, borderRadiusClass) : "", generateComposerComponent(config, borderRadiusClass), components.scrollToBottom ? generateScrollToBottomComponent() : "", generateUserMessageComponent( config, borderRadiusClass, messageSpacingClass, ), components.editMessage ? generateEditComposerComponent(borderRadiusClass) : "", generateAssistantMessageComponent(config, messageSpacingClass), generateActionBarComponent(config), components.branchPicker ? generateBranchPickerComponent() : "", ] .filter(Boolean) .join("\n"); return imports + threadComponent + additionalComponents; } 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"); if (components.reasoning) icons.push("ChevronDownIcon"); return `import {\n ${[...new Set(icons)].sort().join(",\n ")},\n} from "lucide-react";`; } function getBorderRadiusClass(radius: string): string { return ( { none: "rounded-none", sm: "rounded-sm", md: "rounded-md", lg: "rounded-lg", full: "rounded-3xl", }[radius] || "rounded-lg" ); } function getFontSizeClass(fontSize: string): string { return ( { sm: "text-sm", base: "text-base", lg: "text-lg", }[fontSize] || "text-base" ); } function getMessageSpacingClass(spacing: string): string { return ( { compact: "py-2", comfortable: "py-4", spacious: "py-6", }[spacing] || "py-4" ); } function generateWelcomeComponent( config: BuilderConfig, borderRadiusClass: string, ): string { const { components } = config; return ` function ThreadWelcome() { return (
Hello there!
How can I help you today?
${ components.suggestions ? `
` : "" }
); }`; } function generateComposerComponent( config: BuilderConfig, borderRadiusClass: string, ): string { const { components } = config; return ` function Composer() { return ( ${components.attachments ? "" : ""} ); } function ComposerAction() { return (
${components.attachments ? "" : "
"} !s.thread.isRunning}> s.thread.isRunning}>
); }`; } function generateScrollToBottomComponent(): string { return ` function ThreadScrollToBottom() { return ( ); }`; } function generateUserMessageComponent( config: BuilderConfig, borderRadiusClass: string, messageSpacingClass: string, ): string { const { components, styles } = config; const animationClass = styles.animations ? " fade-in slide-in-from-bottom-1 animate-in duration-150" : ""; return ` function UserMessage() { return ( ${components.attachments ? "" : ""}
${ components.editMessage ? `
` : "" }
${components.branchPicker ? `` : ""}
); } ${ components.editMessage ? `function UserActionBar() { return ( ); }` : "" }`; } function generateEditComposerComponent(borderRadiusClass: string): string { return ` function EditComposer() { return (
); }`; } function generateAssistantMessageComponent( config: BuilderConfig, messageSpacingClass: string, ): string { const { components, styles } = config; const animationClass = styles.animations ? " fade-in slide-in-from-bottom-1 animate-in duration-150" : ""; const reasoningSection = components.reasoning ? `
Thinking...
` : ""; return ` function AssistantMessage() { return ( ${ components.avatar ? `
` : "" }
${reasoningSection} ${ 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 ( ); }`; } function generateActionBarComponent(config: BuilderConfig): string { const { components } = config; const feedbackButtons = components.actionBar.feedback ? ` ` : ""; return ` function AssistantActionBar() { return ( ${ components.actionBar.copy ? ` s.message.isCopied}> !s.message.isCopied}> ` : "" } ${ components.actionBar.reload ? ` ` : "" } ${ components.actionBar.speak ? ` ` : "" }${feedbackButtons} ); }`; } function generateBranchPickerComponent(): string { return ` function BranchPicker({ className, ...rest }: { className?: string }) { return ( / ); }`; }