Skip to content

Vercel AI SDK

Use GistUI with streamText, useChat and the AI SDK's UI message stream, in a full-screen answer or a chat.

Updated View as Markdown

streamText works as it is. Pass GistUI’s prompt as system and choose how to return the stream.

You render with Return Client
<GistUI stream> result.toTextStreamResponse() stream={res.body}
useChat from @ai-sdk/react result.toUIMessageStreamResponse() source + streaming from the message text
@gistui/chat result.toUIMessageStreamResponse() the aiSdk() adapter
npm i ai @gistui/catalog

A single answer

app/api/answer/route.tsts
import { prompt } from "@gistui/catalog";
import { convertToModelMessages, streamText } from "ai";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: "openai/gpt-5", // a gateway model id, or a provider model such as openai("gpt-5")
    system: prompt().text,
    messages: await convertToModelMessages(messages),
  });
  return result.toTextStreamResponse();
}
app/answer.tsxtsx
"use client";
import "@gistui/styles/styles.css";
import { GistUI } from "@gistui/react";
import { ui } from "@gistui/react/ui";

export function Answer({ stream }: { stream: ReadableStream }) {
  return <GistUI library={ui} stream={stream} />;
}

A chat with useChat

Use the inline prompt, so a reply is chat text with the UI in gistui code fences. splitReply separates the two.

app/api/chat/route.tsts
import { prompt } from "@gistui/catalog";
import { convertToModelMessages, streamText } from "ai";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: "openai/gpt-5",
    system: prompt({ mode: "inline" }).text,
    messages: await convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
app/chat.tsxtsx
"use client";
import "@gistui/styles/styles.css";
import { useChat } from "@ai-sdk/react";
import { replyProgram, splitReply } from "@gistui/chat";
import { GistUI } from "@gistui/react";
import { ui } from "@gistui/react/ui";

export function Chat() {
  const { messages, sendMessage, status } = useChat();

  return messages.map((m, i) => {
    const text = m.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
    if (m.role === "user") return <p key={m.id}>{text}</p>;

    const streaming = status === "streaming" && i === messages.length - 1;
    const parts = splitReply(text, { streaming }); // [{ kind: "text", text }, { kind: "ui", source, open }]
    return (
      <div key={m.id}>
        {parts.filter((p) => p.kind === "text").map((p, j) => <p key={j}>{p.text}</p>)}
        <GistUI
          library={ui}
          source={replyProgram(parts)}
          streaming={streaming}
          onAction={(a) => (a.type === "send" || a.type === "submit") && sendMessage({ text: a.message })}
        />
      </div>
    );
  });
}

All the fences of one reply form one program, shown where the first fence is.

A chat with @gistui/chat

If you would rather not hold the conversation yourself, @gistui/chat has a store and ready React layouts. The same route as above works.

import { aiSdk, createChat, localThread } from "@gistui/chat";
import { Chat } from "@gistui/chat/react";
import "@gistui/chat/chat.css";
import "@gistui/styles/styles.css";
import { ui } from "@gistui/react/ui";

const store = createChat({ adapter: aiSdk({ url: "/api/chat" }), storage: localThread("support") });

<Chat store={store} library={ui} layout="sidebar" title="Assistant" />;

See Chat.

Tools

AI SDK tools (tools: { … } in streamText) run on your server while the model is answering, and they keep working unchanged. GistUI’s own tools are different: they are called from the rendered UI, in the browser, by @query. Use them when an answer should keep its data live after the model has finished. See Tools and live data.

Repair on the server

To store the repaired program rather than the raw one, pass the text stream through repairStream:

import { library, prompt } from "@gistui/catalog";
import { repairStream, toReadable } from "@gistui/server";

const result = streamText({ model, system: prompt().text, messages });
return new Response(toReadable(repairStream(result.textStream, library)));
Navigation

Type to search…

↑↓ navigate↵ selectEsc close