Send prompt().text as the system message, ask for a stream, and pass the text deltas to the browser.
npm i @gistui/catalog @gistui/serveryarn add @gistui/catalog @gistui/serverpnpm add @gistui/catalog @gistui/serverbun add @gistui/catalog @gistui/serverWith fetch
import { prompt } from "@gistui/catalog";
import { textDeltas, toReadable } from "@gistui/server";
export async function POST(req: Request) {
const { messages } = await req.json();
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
model: "gpt-5",
stream: true,
messages: [{ role: "system", content: prompt().text }, ...messages],
}),
});
// A failed request has no stream to read: answer with its status instead.
if (!res.ok) return new Response(await res.text(), { status: res.status });
return new Response(toReadable(textDeltas(res, "openai")));
}With the openai SDK
The SDK gives you chunks; yield their text and wrap the generator with toReadable.
import OpenAI from "openai";
import { prompt } from "@gistui/catalog";
import { toReadable } from "@gistui/server";
const client = new OpenAI();
export async function POST(req: Request) {
const { messages } = await req.json();
const stream = await client.chat.completions.create({
model: "gpt-5",
stream: true,
messages: [{ role: "system", content: prompt().text }, ...messages],
});
async function* text() {
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) yield delta;
}
}
return new Response(toReadable(text()));
}Render it
import "@gistui/styles/styles.css";
import { GistUI } from "@gistui/react";
import { ui } from "@gistui/react/ui";
const res = await fetch("/api/answer", { method: "POST", body: JSON.stringify({ messages }) });
<GistUI library={ui} stream={res.body} onAction={handle} />;The same works in Vue, Svelte, Solid and vanilla JavaScript.
Validate and repair on the server
To log or store the repaired program, pass the text through repairStream. It forwards the text as it arrives and appends repairs at the end.
import { library } from "@gistui/catalog";
import { repairStream, textDeltas, toReadable } from "@gistui/server";
return new Response(toReadable(repairStream(textDeltas(res, "openai"), library)));In a chat, without your own route
For a prototype, @gistui/chat has an adapter that calls OpenAI directly. A key in the browser is readable by every visitor, so use this for local demos only; in production, point url at your own endpoint.
import { createChat, openai } from "@gistui/chat";
const store = createChat({
adapter: openai({ model: "gpt-5", url: "/api/chat" }), // your endpoint, OpenAI's request and response shape
system: chatPrompt, // prompt({ mode: "inline" }).text, sent by your server in production
});