logoassistant-ui
Migrations

Migration to v0.8

This migration guide is for an upcoming release. More changes may land before v0.8.0 is released.

Styled Components moved to @assistant-ui/react-ui

All styled components (Thread, ThreadList, AssistantModal, makeMarkdownText, etc.) have been moved to a new package, @assistant-ui/react-ui.

To migrate, use the migration codemod:

# IMPORTANT: make sure to commit all changes to git / creating a backup before running the codemod
npx assistant-ui upgrade

Vercel AI SDK RSC requires additional setup

Built-in RSC support in assistant-ui has been removed, so an additional setup step is required. The RSC runtime now requires additional setup to display React Server Components.

import { RSCDisplay } from "@assistant-ui/react-ai-sdk";
 
// if you are using the default Thread component
// add RSCDisplay to assistantMessage.components.Text
<Thread assistantMessage={{ components: { Text: RSCDisplay } }} />
 
 
// if you are using unstyled primitives, update MyThread.tsx
<MessagePrimitive.Content components={{ Text: RSCDisplay }} />

Migrate away from UIContentPart

For instructions on migrating for Vercel AI SDK RSC, see section above. This migration guide is for users of useExternalStoreRuntime.

First, reconsider your approach.

Creating UI components in the convertMessage callback is considered an anti-pattern. The recommended alternative approach is to pass tool-call content parts, and use makeAssistantToolUI to map these tool calls to UI components.

This ensures that the data layer is separate and decoupled from the UI layer.

Example

Consider the following example, where you are using a UIContentPart to show a loading indicator.

bad.ts
// THIS IS BAD
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  if (message.isLoading) {
    return { content: [{ type: "ui", display:< MyLoader /> }] };
  }
  // ...
};
good.ts
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  if (message.isLoading) {
    return { content: [] };
  }
  // ...
};
 
// use the empty content part to show the loading indicator
<Thread assistantMessage={{ components: { Empty: MyLoader } }} />;

(if you are using unstyled primitives, update MyThread.tsx, and pass the component to MessagePrimitive.Content)

Example 2

Consider the following example, where you are displaying a custom chart based on data received from an external source.

bad.ts
// THIS IS BAD
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  return { content: [{ type: "ui", display: <MyChart data={message.chartData} /> }] };
};
good.ts
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  return {
    content: [
      {
        type: "tool-call",
        toolName: "chart",
        args: message.chartData,
      },
    ],
  };
};
 
const ChartToolUI = makeAssistantToolUI({
  toolName: "chart",
  render: ({ args }) => <MyChart data={args} />,
});
 
// use tool UI to display the chart
<Thread tools={[ChartToolUI]} />;

(if you are using unstyled primitives, render the <ChartToolUI /> component anywhere inside your AssistantRuntimeProvider)

Fallback Approach: Override ContentPartText

However, sometimes you receive UI components from an external source.

The example below assumes that your custom MyMessage type has a display field.

First, we define a dummy UI_PLACEHOLDER content part, which we will replace with the UI component later:

const UI_PLACEHOLDER = Object.freeze({
  type: "text",
  text: "UI content placeholder",
});
const convertMessage = (message: MyMessage): ThreadMessageLike => ({
  content: [
    // other content parts,
    UI_PLACEHOLDER,
  ],
});

Then, we define a custom TextContentPartComponent:

const MyText: TextContentPartComponent = () => {
  const isUIPlaceholder = useContentPart((p) => p === UI_PLACEHOLDER);
 
  // this assumes that you have a `display` field on your original message objects before conversion.
  const ui = useMessage((m) =>
    isUIPlaceholder ? getExternalStoreMessage(m).display : undefined,
  );
  if (ui) {
    return ui;
  }
 
  return <MarkdownText />; // your default text component
};

We pass this component to our Thread:

<Thread
  assistantMessage={{ components: { Text: MyText } }}
  userMessage={{ components: { Text: MyText } }}
/>

(if you are using unstyled primitives, update MyThread.tsx, and pass the component to MessagePrimitive.Content)

Now, the UI_PLACEHOLDER content part is replaced with the UI component we defined earlier.

On this page

Edit on Github