| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import { Code } from "@/components/code";
- export const metadata = {
- title: "Streaming | json-render",
- };
- export default function StreamingPage() {
- return (
- <article>
- <h1 className="text-3xl font-bold mb-4">Streaming</h1>
- <p className="text-muted-foreground mb-8">
- Progressively render UI as AI generates it.
- </p>
- <h2 className="text-xl font-semibold mt-12 mb-4">SpecStream Format</h2>
- <p className="text-sm text-muted-foreground mb-4">
- json-render uses <strong>SpecStream</strong>, a JSONL-based streaming
- format where each line is a JSON patch operation that progressively
- builds your spec:
- </p>
- <Code lang="json">{`{"op":"set","path":"/root","value":"root"}
- {"op":"set","path":"/elements/root","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
- {"op":"set","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue"}}}
- {"op":"set","path":"/elements/metric-2","value":{"type":"Metric","props":{"label":"Users"}}}`}</Code>
- <h2 className="text-xl font-semibold mt-12 mb-4">useUIStream Hook</h2>
- <p className="text-sm text-muted-foreground mb-4">
- The hook handles parsing and state management:
- </p>
- <Code lang="tsx">{`import { useUIStream } from '@json-render/react';
- function App() {
- const {
- spec, // Current UI spec state
- isStreaming, // True while streaming
- error, // Any error that occurred
- send, // Function to start generation
- clear, // Function to reset spec and error
- } = useUIStream({
- api: '/api/generate',
- onComplete: (spec) => {}, // Optional: called when streaming completes
- onError: (error) => {}, // Optional: called when an error occurs
- });
- }`}</Code>
- <h2 className="text-xl font-semibold mt-12 mb-4">Patch Operations</h2>
- <p className="text-sm text-muted-foreground mb-4">
- Supported operations:
- </p>
- <ul className="list-disc list-inside text-sm text-muted-foreground space-y-2 mb-4">
- <li>
- <code className="text-foreground">set</code> — Set the value at a path
- (creates if needed)
- </li>
- <li>
- <code className="text-foreground">add</code> — Add to an array at a
- path
- </li>
- <li>
- <code className="text-foreground">replace</code> — Replace value at a
- path
- </li>
- <li>
- <code className="text-foreground">remove</code> — Remove value at a
- path
- </li>
- </ul>
- <h2 className="text-xl font-semibold mt-12 mb-4">Path Format</h2>
- <p className="text-sm text-muted-foreground mb-4">
- Paths use a key-based format for elements:
- </p>
- <Code lang="bash">{`/root -> Root element
- /root/children -> Children of root
- /elements/card-1 -> Element with key "card-1"
- /elements/card-1/children -> Children of card-1`}</Code>
- <h2 className="text-xl font-semibold mt-12 mb-4">Server-Side Setup</h2>
- <p className="text-sm text-muted-foreground mb-4">
- Ensure your API route streams properly:
- </p>
- <Code lang="typescript">{`import { streamText } from 'ai';
- import { catalog } from '@/lib/catalog';
- export async function POST(req: Request) {
- const { prompt } = await req.json();
-
- const result = streamText({
- model: 'anthropic/claude-haiku-4.5',
- system: catalog.prompt(),
- prompt,
- });
- // Return as a streaming response
- return result.toTextStreamResponse();
- }`}</Code>
- <h2 className="text-xl font-semibold mt-12 mb-4">
- Progressive Rendering
- </h2>
- <p className="text-sm text-muted-foreground mb-4">
- The Renderer automatically updates as the spec changes:
- </p>
- <Code lang="tsx">{`function App() {
- const { spec, isStreaming } = useUIStream({ api: '/api/generate' });
- return (
- <div>
- {isStreaming && <LoadingIndicator />}
- <Renderer spec={spec} registry={registry} loading={isStreaming} />
- </div>
- );
- }`}</Code>
- <h2 className="text-xl font-semibold mt-12 mb-4">Aborting Streams</h2>
- <p className="text-sm text-muted-foreground mb-4">
- Calling <code className="text-foreground">send</code> again
- automatically aborts the previous request. Use{" "}
- <code className="text-foreground">clear</code> to reset the spec and
- error state:
- </p>
- <Code lang="tsx">{`function App() {
- const { isStreaming, send, clear } = useUIStream({
- api: '/api/generate',
- });
- return (
- <div>
- <button onClick={() => send('Create dashboard')}>
- Generate
- </button>
- <button onClick={clear}>Reset</button>
- </div>
- );
- }`}</Code>
- <h2 className="text-xl font-semibold mt-12 mb-4">
- Low-Level SpecStream API
- </h2>
- <p className="text-sm text-muted-foreground mb-4">
- For custom streaming implementations, use the SpecStream compiler
- directly:
- </p>
- <Code lang="typescript">{`import { createSpecStreamCompiler } from '@json-render/core';
- // Create a compiler for your spec type
- const compiler = createSpecStreamCompiler<MySpec>();
- // Process streaming chunks from AI
- async function processStream(reader: ReadableStreamDefaultReader) {
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
-
- const { result, newPatches } = compiler.push(value);
-
- if (newPatches.length > 0) {
- // Update UI with partial result
- setSpec(result);
- }
- }
-
- // Get final compiled result
- return compiler.getResult();
- }`}</Code>
- <h3 className="text-lg font-semibold mt-8 mb-4">One-Shot Compilation</h3>
- <p className="text-sm text-muted-foreground mb-4">
- For non-streaming scenarios, compile entire SpecStream at once:
- </p>
- <Code lang="typescript">{`import { compileSpecStream } from '@json-render/core';
- const jsonl = \`{"op":"set","path":"/root","value":{"type":"Card"}}
- {"op":"set","path":"/root/props","value":{"title":"Hello"}}\`;
- const spec = compileSpecStream<MySpec>(jsonl);
- // { root: { type: "Card", props: { title: "Hello" } } }`}</Code>
- </article>
- );
- }
|