# @assistant-ui/react-generative-ui Generative UI tools for assistant-ui. ## Installation ```bash npm install @assistant-ui/react-generative-ui ``` ## Usage Declare the components the model is allowed to render in a library, then expose them through the `present` tool. The model emits a `{ $type, ...props }` tree (`$type` names the component, so a real `type` prop never collides); the tool renders it against the library. ```tsx import { JSONGenerativeUI } from "@assistant-ui/react-generative-ui"; import { Thread, Tools } from "@assistant-ui/react"; import { z } from "zod"; const generativeUI = new JSONGenerativeUI({ library: { Card: { description: "A card container with a title.", properties: z.object({ title: z.string() }), render: ({ title, children }) => (

{title}

{children}
), }, Text: { description: "A run of text.", properties: z.object({ tone: z.enum(["muted", "normal"]).optional() }), render: ({ tone, children }) =>

{children}

, }, }, }); const toolkit = { present: generativeUI.present(), }; function App() { return ( {/* MessageList, Composer, ... */} ); } ``` A node's `children` prop is rendered recursively, so components can be nested: ```json { "$type": "Card", "title": "Hello", "children": [{ "$type": "Text", "tone": "muted", "children": "Nested text" }] } ``` ## Streaming props By default a component renders only once its props have fully arrived, so `render` always sees complete props. Opt into rendering from partial props with `streamProperties`. When you do, `render` receives an injected `$status` prop that discriminates the props: while `"streaming"` they are `Partial`, and once `"done"` they are complete. ```tsx const generativeUI = new JSONGenerativeUI({ library: { Weather: { description: "A live weather card.", properties: z.object({ city: z.string(), temp: z.number() }), streamProperties: true, render: (props) => { if (props.$status === "done") { return {`${props.city}: ${props.temp}°`}; // complete props } return {props.city ?? "Loading…"}; // partial props }, }, }, }); ``` `render` is a function of `props`, not a React component — but it runs on its own fiber, so you can call hooks inside it as usual. ## `"use generative"` authoring The examples above wire the library up by hand on the client. With the `"use generative"` compiler (`@assistant-ui/next` or `@assistant-ui/vite`) you can instead colocate a component's `properties` schema with its `render` and expose the library as tools, and the build splits each half to the right target: the schema goes to the server (so the model sees the tool), the `render` stays on the client. ```tsx "use generative"; import { z } from "zod"; import { Weather } from "@/components/weather"; import { defineToolkit } from "@assistant-ui/react"; import { JSONGenerativeUI, defineGenerativeComponents, } from "@assistant-ui/react-generative-ui"; const generative = new JSONGenerativeUI({ library: defineGenerativeComponents({ Weather: { description: "Show a weather card.", properties: z.object({ city: z.string() }), render: (props) => , }, }), }); export default defineToolkit({ // `present` displays a component; `prompt_user` is a human-in-the-loop prompt. present: generative.present(), prompt_user: generative.promptUser(), }); ``` The model calls `present` with a node like `{ "$type": "Weather", "city": "SF" }`. Pass `present({ display: "standalone" })` to render the component on its own surface instead of inline. See the [`"use generative"` docs](https://www.assistant-ui.com/docs) for the build setup. ## License MIT