Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,68 @@ const registry = {
};
```

### 3. Let AI Generate
### 3. Create Your API Route (backend)
`useUIStream` expects JSONL patches from your API in this exact format:
```jsonl
{"op":"set","path":"/root","value":"element-key"}
{"op":"set","path":"/elements/{key}","value":{"key":"...","type":"...","props":{...},"children":[...]}}
```

#### useUIStream Reference
```typescript
const { tree, isStreaming, error, send, clear } = useUIStream({
api: "/api/generate",
onComplete: (tree) => {},
onError: (error) => {},
});

// send() takes a STRING, not an object
await send("Create a dashboard");
```

#### Example API Route (Next.js + AI SDK)

```typescript
// app/api/generate/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

const SYSTEM_PROMPT = `Output JSONL patches to build UI. Format:
{"op":"set","path":"/root","value":"key"}
{"op":"set","path":"/elements/key","value":{"key":"...","type":"...","props":{...},"children":[...]}}

Rules: One JSON per line. No markdown. children is array of key strings.`;

export async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
system: SYSTEM_PROMPT,
prompt,
});
return result.toTextStreamResponse();
}
```

#### Rendering the Tree

The `Renderer` component requires context providers – use `JSONUIProvider`:

```tsx
import { Renderer, JSONUIProvider } from "@json-render/react";

function App() {
const { tree, isStreaming } = useUIStream({ api: "/api/generate" });

return (
<JSONUIProvider registry={registry}>
{tree && <Renderer tree={tree} registry={registry} loading={isStreaming} />}
</JSONUIProvider>
);
}
```

### 4. Let AI Generate

```tsx
import { DataProvider, ActionProvider, Renderer, useUIStream } from '@json-render/react';
Expand All @@ -91,7 +152,7 @@ function Dashboard() {
placeholder="Create a revenue dashboard..."
onKeyDown={(e) => e.key === 'Enter' && send(e.target.value)}
/>
<Renderer tree={tree} components={registry} />
<Renderer tree={tree} registry={registry} />
</ActionProvider>
</DataProvider>
);
Expand Down
7 changes: 4 additions & 3 deletions apps/web/app/docs/streaming/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ export default function StreamingPage() {
json-render uses JSONL (JSON Lines) streaming. As AI generates, each
line represents a patch operation:
</p>
<Code lang="json">{`{"op":"set","path":"/root","value":{"key":"root","type":"Card","props":{"title":"Dashboard"}}}
{"op":"add","path":"/root/children","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue"}}}
{"op":"add","path":"/root/children","value":{"key":"metric-2","type":"Metric","props":{"label":"Users"}}}`}</Code>
<Code lang="json">{`{"op":"set","path":"/root","value":"dashboard"}
{"op":"set","path":"/elements/dashboard","value":{"key":"dashboard","type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
{"op":"set","path":"/elements/metric-1","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue"}}}
{"op":"set","path":"/elements/metric-2","value":{"key":"metric-2","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">
Expand Down