"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, type ReactNode, } from "react"; import { AssistantRuntimeProvider, ThreadPrimitive, ComposerPrimitive, MessagePrimitive, ErrorPrimitive, AuiIf, useAuiState, Tools, Suggestions, } from "@assistant-ui/react"; import { useChatRuntime, AssistantChatTransport, } from "@assistant-ui/react-ai-sdk"; import { lastAssistantMessageIsCompleteWithToolCalls } from "ai"; import { SendHorizontal, SquareIcon } from "lucide-react"; import { createPlaygroundChatToolkit, type PartialBuilderConfig, } from "@/lib/playground-chat-toolkit"; import { useAui } from "@assistant-ui/store"; import type { BuilderConfig } from "./types"; import { applyDiff } from "@/lib/playground-url-state"; const PLAYGROUND_SUGGESTIONS = [ { title: "Make it look like", label: "ChatGPT", prompt: "Make it look like ChatGPT", }, { title: "Switch to", label: "dark mode", prompt: "Switch to dark mode with blue accents", }, { title: "Enable all", label: "features", prompt: "Enable all features like attachments, avatars, and feedback", }, ]; // --- Context (shares runtime between mobile sheet + desktop sidebar) --- type PlaygroundChatContextValue = { runtime: ReturnType; aui: ReturnType; }; const PlaygroundChatContext = createContext( null, ); function usePlaygroundChat() { const ctx = useContext(PlaygroundChatContext); if (!ctx) throw new Error( "usePlaygroundChat must be used within PlaygroundChatProvider", ); return ctx; } // --- Provider (plain context, no AssistantRuntimeProvider) --- interface PlaygroundChatProviderProps { config: BuilderConfig; setConfig: (config: BuilderConfig) => void; children: ReactNode; } export function PlaygroundChatProvider({ config, setConfig, children, }: PlaygroundChatProviderProps) { const configRef = useRef(config); configRef.current = config; const onConfigUpdate = useCallback( (update: PartialBuilderConfig) => { const { customCSS, ...rest } = update; const merged = applyDiff( rest as Record, configRef.current, ); if (customCSS !== undefined) { merged.customCSS = customCSS ? [configRef.current.customCSS, customCSS].filter(Boolean).join("\n") : ""; } setConfig(merged); }, [setConfig], ); const toolkit = useMemo( () => createPlaygroundChatToolkit(onConfigUpdate), [onConfigUpdate], ); const transport = useMemo( () => new AssistantChatTransport({ api: "/api/playground-chat", prepareSendMessagesRequest: async (options) => ({ body: { ...options.body, messages: options.messages, builderConfig: configRef.current, }, }), }), [], ); const runtime = useChatRuntime({ transport, sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, }); const aui = useAui( { tools: Tools({ toolkit }), suggestions: Suggestions(PLAYGROUND_SUGGESTIONS), }, { parent: null }, ); const value = useMemo(() => ({ runtime, aui }), [runtime, aui]); return ( {children} ); } // --- Thread UI (each mount creates its own AssistantRuntimeProvider scope) --- export function PlaygroundChatThread({ onRunningChange, }: { onRunningChange?: (isRunning: boolean) => void; }) { const { runtime, aui } = usePlaygroundChat(); return ( {onRunningChange && }

Describe how you want your chat to look

e.g. "make it look like ChatGPT" or "use dark mode with rounded corners"

{PLAYGROUND_SUGGESTIONS.map((s) => ( {s.title}{" "} {s.label} ))}
); } /** Bridges runtime state to the parent; resets on unmount. */ function RunningObserver({ onRunningChange, }: { onRunningChange: (isRunning: boolean) => void; }) { const isRunning = useAuiState((s) => s.thread.isRunning); useEffect(() => { onRunningChange(isRunning); return () => onRunningChange(false); }, [isRunning, onRunningChange]); return null; } function Composer() { return (