Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Waiting to run
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00
..

@assistant-ui/react-generative-ui

Generative UI tools for assistant-ui.

Installation

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.

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 }) => (
        <section className="card">
          <h3>{title}</h3>
          {children}
        </section>
      ),
    },
    Text: {
      description: "A run of text.",
      properties: z.object({ tone: z.enum(["muted", "normal"]).optional() }),
      render: ({ tone, children }) => <p data-tone={tone}>{children}</p>,
    },
  },
});

const toolkit = {
  present: generativeUI.present(),
};

function App() {
  return (
    <Thread>
      <Tools toolkit={toolkit} />
      {/* MessageList, Composer, ... */}
    </Thread>
  );
}

A node's children prop is rendered recursively, so components can be nested:

{
  "$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.

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 <Card>{`${props.city}: ${props.temp}°`}</Card>; // complete props
        }
        return <Card>{props.city ?? "Loading…"}</Card>; // 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.

"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) => <Weather {...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 for the build setup.

License

MIT