mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 12:28:17 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b37565376c | ||
|
|
17528a901d | ||
|
|
6a2abe0c1e | ||
|
|
6b49c4574d |
@@ -4,6 +4,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.pnp
|
.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
# Local env files
|
# Local env files
|
||||||
.env*
|
.env*
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export const metadata = pageMetadata("docs/ai-sdk")
|
|||||||
|
|
||||||
# AI SDK Integration
|
# AI SDK Integration
|
||||||
|
|
||||||
Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless streaming. json-render supports two modes: **Generate** (standalone UI) and **Chat** (UI embedded in conversation). See [Generation Modes](/docs/generation-modes) for a detailed comparison.
|
Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless streaming. json-render supports two modes: **Standalone** (standalone UI) and **Inline** (UI embedded in conversation). See [Generation Modes](/docs/generation-modes) for a detailed comparison.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -11,9 +11,9 @@ Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless str
|
|||||||
npm install ai @ai-sdk/react
|
npm install ai @ai-sdk/react
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generate Mode
|
## Standalone Mode
|
||||||
|
|
||||||
In generate mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.
|
In standalone mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.
|
||||||
|
|
||||||
### API Route
|
### API Route
|
||||||
|
|
||||||
@@ -73,9 +73,9 @@ function GenerativeUI() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Chat Mode
|
## Inline Mode
|
||||||
|
|
||||||
In chat mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.
|
In inline mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.
|
||||||
|
|
||||||
### API Route
|
### API Route
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
const result = streamText({
|
const result = streamText({
|
||||||
model: yourModel,
|
model: yourModel,
|
||||||
system: catalog.prompt({ mode: "chat" }),
|
system: catalog.prompt({ mode: "inline" }),
|
||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,13 +182,13 @@ const systemPrompt = catalog.prompt({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Chat Mode Prompt
|
### Inline Mode Prompt
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||||
```
|
```
|
||||||
|
|
||||||
In chat mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.
|
In inline mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.
|
||||||
|
|
||||||
## Which Mode?
|
## Which Mode?
|
||||||
|
|
||||||
@@ -197,8 +197,8 @@ In chat mode, the prompt instructs the AI to respond conversationally first, the
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Generate</th>
|
<th>Standalone</th>
|
||||||
<th>Chat</th>
|
<th>Inline</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -215,7 +215,7 @@ In chat mode, the prompt instructs the AI to respond conversationally first, the
|
|||||||
<tr>
|
<tr>
|
||||||
<td>System prompt</td>
|
<td>System prompt</td>
|
||||||
<td><code>catalog.prompt()</code></td>
|
<td><code>catalog.prompt()</code></td>
|
||||||
<td><code>{"catalog.prompt({ mode: \"chat\" })"}</code></td>
|
<td><code>{"catalog.prompt({ mode: \"inline\" })"}</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Stream utility</td>
|
<td>Stream utility</td>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ interface Catalog {
|
|||||||
interface PromptOptions {
|
interface PromptOptions {
|
||||||
system?: string; // Custom system message intro
|
system?: string; // Custom system message intro
|
||||||
customRules?: string[]; // Additional rules to append
|
customRules?: string[]; // Additional rules to append
|
||||||
mode?: "generate" | "chat"; // Output mode (default: "generate")
|
mode?: "standalone" | "inline" | "generate" | "chat"; // Output mode (default: "standalone")
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SpecValidationResult<T> {
|
interface SpecValidationResult<T> {
|
||||||
@@ -370,7 +370,7 @@ Most users should use `pipeJsonRender()` instead, which wraps this transform for
|
|||||||
|
|
||||||
### createMixedStreamParser
|
### createMixedStreamParser
|
||||||
|
|
||||||
Parse a mixed stream of text and JSONL patches (used for Chat + GenUI mode):
|
Parse a mixed stream of text and JSONL patches (used for Inline mode):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createMixedStreamParser } from '@json-render/core';
|
import { createMixedStreamParser } from '@json-render/core';
|
||||||
@@ -389,7 +389,7 @@ parser.flush();
|
|||||||
|
|
||||||
### pipeJsonRender
|
### pipeJsonRender
|
||||||
|
|
||||||
Pipe an AI SDK `UIMessageStream` through the json-render transform. Lines that parse as JSONL patches are emitted as `data-spec` parts; everything else passes through as text. Used in Chat mode API routes.
|
Pipe an AI SDK `UIMessageStream` through the json-render transform. Lines that parse as JSONL patches are emitted as `data-spec` parts; everything else passes through as text. Used in Inline mode API routes.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { pipeJsonRender } from '@json-render/core';
|
import { pipeJsonRender } from '@json-render/core';
|
||||||
@@ -403,7 +403,7 @@ const stream = createUIMessageStream({
|
|||||||
return createUIMessageStreamResponse({ stream });
|
return createUIMessageStreamResponse({ stream });
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Generation Modes](/docs/generation-modes) for full Chat mode setup.
|
See [Generation Modes](/docs/generation-modes) for full Inline mode setup.
|
||||||
|
|
||||||
### SpecStream Types
|
### SpecStream Types
|
||||||
|
|
||||||
|
|||||||
@@ -341,6 +341,8 @@ February 2026
|
|||||||
|
|
||||||
### New: Chat Mode (Inline GenUI)
|
### New: Chat Mode (Inline GenUI)
|
||||||
|
|
||||||
|
> **Note:** These modes were renamed in v0.12.1 — "Generate" is now "Standalone" and "Chat" is now "Inline". The old names are accepted as deprecated aliases.
|
||||||
|
|
||||||
json-render now supports two generation modes: **Generate** (JSONL-only, the default) and **Chat** (text + JSONL inline). Chat mode lets the AI respond conversationally with embedded UI specs, ideal for chatbots and copilot experiences.
|
json-render now supports two generation modes: **Generate** (JSONL-only, the default) and **Chat** (text + JSONL inline). Chat mode lets the AI respond conversationally with embedded UI specs, ideal for chatbots and copilot experiences.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ export const metadata = pageMetadata("docs/generation-modes")
|
|||||||
|
|
||||||
# Generation Modes
|
# Generation Modes
|
||||||
|
|
||||||
json-render supports two modes for AI-generated UI: **Generate mode** for standalone UI and **Chat mode** for inline UI within a conversation.
|
json-render supports two modes for AI-generated UI: **Standalone mode** for standalone UI and **Inline mode** for inline UI within a conversation.
|
||||||
|
|
||||||
The mode controls how the AI formats its output and how your app processes the stream. The underlying JSONL patch format is the same in both modes.
|
The mode controls how the AI formats its output and how your app processes the stream. The underlying JSONL patch format is the same in both modes.
|
||||||
|
|
||||||
<GenerationModesDiagram />
|
<GenerationModesDiagram />
|
||||||
|
|
||||||
## Generate Mode (Standalone)
|
## Standalone Mode
|
||||||
|
|
||||||
In generate mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.
|
In standalone mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.
|
||||||
|
|
||||||
This is the default mode and is ideal for:
|
This is the default mode and is ideal for:
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ This is the default mode and is ideal for:
|
|||||||
```typescript
|
```typescript
|
||||||
import { streamText } from "ai";
|
import { streamText } from "ai";
|
||||||
|
|
||||||
// Generate mode is the default (no mode option needed)
|
// Standalone mode is the default (no mode option needed)
|
||||||
const systemPrompt = catalog.prompt({
|
const systemPrompt = catalog.prompt({
|
||||||
customRules: [
|
customRules: [
|
||||||
"Use Card as root for forms and small UIs.",
|
"Use Card as root for forms and small UIs.",
|
||||||
@@ -74,9 +74,9 @@ The AI outputs only JSONL — one patch per line, no surrounding text:
|
|||||||
{"op":"add","path":"/elements/submit","value":{"type":"Button","props":{"label":"Sign In"}}}
|
{"op":"add","path":"/elements/submit","value":{"type":"Button","props":{"label":"Sign In"}}}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Chat Mode (Inline)
|
## Inline Mode
|
||||||
|
|
||||||
In chat mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).
|
In inline mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).
|
||||||
|
|
||||||
This is ideal for:
|
This is ideal for:
|
||||||
|
|
||||||
@@ -92,8 +92,8 @@ import { streamText } from "ai";
|
|||||||
import { pipeJsonRender } from "@json-render/core";
|
import { pipeJsonRender } from "@json-render/core";
|
||||||
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
||||||
|
|
||||||
// Enable chat mode
|
// Enable inline mode
|
||||||
const systemPrompt = catalog.prompt({ mode: "chat" });
|
const systemPrompt = catalog.prompt({ mode: "inline" });
|
||||||
|
|
||||||
const result = streamText({
|
const result = streamText({
|
||||||
model: yourModel,
|
model: yourModel,
|
||||||
@@ -180,8 +180,8 @@ If the user asks a simple question ("what does BTC stand for?"), the AI replies
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
<th>Generate</th>
|
<th>Standalone</th>
|
||||||
<th>Chat</th>
|
<th>Inline</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -198,7 +198,7 @@ If the user asks a simple question ("what does BTC stand for?"), the AI replies
|
|||||||
<tr>
|
<tr>
|
||||||
<td>System prompt</td>
|
<td>System prompt</td>
|
||||||
<td><code>{"catalog.prompt()"}</code></td>
|
<td><code>{"catalog.prompt()"}</code></td>
|
||||||
<td><code>{'catalog.prompt({ mode: "chat" })'}</code></td>
|
<td><code>{'catalog.prompt({ mode: "inline" })'}</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Stream utility</td>
|
<td>Stream utility</td>
|
||||||
|
|||||||
@@ -309,10 +309,35 @@ const catalog = defineCatalog(schema, {
|
|||||||
|
|
||||||
const prompt = catalog.prompt();
|
const prompt = catalog.prompt();
|
||||||
|
|
||||||
// Chat mode prompt
|
// Inline mode prompt (formerly "chat")
|
||||||
|
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generation Modes
|
||||||
|
|
||||||
|
The generation mode values passed to `catalog.prompt()` have been renamed for clarity:
|
||||||
|
|
||||||
|
- `"generate"` is now `"standalone"`
|
||||||
|
- `"chat"` is now `"inline"`
|
||||||
|
|
||||||
|
The old names are accepted as deprecated aliases, so existing code will continue to work. Update when convenient.
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const prompt = catalog.prompt({ mode: "generate" });
|
||||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const prompt = catalog.prompt({ mode: "standalone" });
|
||||||
|
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||||
|
```
|
||||||
|
|
||||||
|
The default mode (when no `mode` option is provided) is `"standalone"`, which behaves identically to the previous `"generate"` default.
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
`ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
|
`ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ The result is a native UI built from your own components — not an iframe, not
|
|||||||
- **[Streaming](/docs/streaming)** — Render progressively as the AI responds. Each JSONL patch adds to the spec and the UI updates in real time.
|
- **[Streaming](/docs/streaming)** — Render progressively as the AI responds. Each JSONL patch adds to the spec and the UI updates in real time.
|
||||||
- **[Data Binding](/docs/data-binding)** — Bind props to runtime data with `$state` paths, repeat elements over arrays, and wire two-way input bindings.
|
- **[Data Binding](/docs/data-binding)** — Bind props to runtime data with `$state` paths, repeat elements over arrays, and wire two-way input bindings.
|
||||||
- **[Visibility](/docs/visibility)** — Show or hide elements based on state conditions. The AI can generate conditional UIs without writing logic.
|
- **[Visibility](/docs/visibility)** — Show or hide elements based on state conditions. The AI can generate conditional UIs without writing logic.
|
||||||
- **[Generation Modes](/docs/generation-modes)** — Generate standalone UI (playground/builder) or inline UI within a chat conversation.
|
- **[Generation Modes](/docs/generation-modes)** — Standalone mode for full-page generated UI or inline mode for UI embedded in a conversation.
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ function FormUI() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatModeDiagram() {
|
function InlineModeDiagram() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
||||||
Chat Mode
|
Inline Mode
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-col">
|
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-col">
|
||||||
{/* Chat area */}
|
{/* Chat area */}
|
||||||
@@ -69,11 +69,11 @@ function ChatModeDiagram() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GenerateModeDiagram() {
|
function StandaloneModeDiagram() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
||||||
Generate Mode
|
Standalone Mode
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-row">
|
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-row">
|
||||||
{/* Left panel - prompt */}
|
{/* Left panel - prompt */}
|
||||||
@@ -104,10 +104,10 @@ export function GenerationModesDiagram() {
|
|||||||
<div className="not-prose my-8">
|
<div className="not-prose my-8">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||||
<div className="h-[280px]">
|
<div className="h-[280px]">
|
||||||
<ChatModeDiagram />
|
<InlineModeDiagram />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-[280px]">
|
<div className="h-[280px]">
|
||||||
<GenerateModeDiagram />
|
<StandaloneModeDiagram />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ When the user asks for a quiz, test, or Q&A, build an interactive experience:
|
|||||||
4. You can also add a final score section that becomes visible when all questions are submitted.
|
4. You can also add a final score section that becomes visible when all questions are submitted.
|
||||||
|
|
||||||
${explorerCatalog.prompt({
|
${explorerCatalog.prompt({
|
||||||
mode: "chat",
|
mode: "inline",
|
||||||
customRules: [
|
customRules: [
|
||||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ INPUT COMPONENTS:
|
|||||||
- Button: Clickable button. Use on.press to trigger actions.
|
- Button: Clickable button. Use on.press to trigger actions.
|
||||||
|
|
||||||
${explorerCatalog.prompt({
|
${explorerCatalog.prompt({
|
||||||
mode: "chat",
|
mode: "inline",
|
||||||
customRules: [
|
customRules: [
|
||||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/codegen"
|
"directory": "packages/codegen"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ Schema options:
|
|||||||
| `ActionBinding` | Action binding with `action`, `params`, `confirm`, `preventDefault`, etc. |
|
| `ActionBinding` | Action binding with `action`, `params`, `confirm`, `preventDefault`, etc. |
|
||||||
| `BuiltInAction` | Built-in action definition with `name` and `description` |
|
| `BuiltInAction` | Built-in action definition with `name` and `description` |
|
||||||
|
|
||||||
### Chat Mode (Mixed Streams)
|
### Inline Mode (Mixed Streams)
|
||||||
|
|
||||||
| Export | Purpose |
|
| Export | Purpose |
|
||||||
|--------|---------|
|
|--------|---------|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/core"
|
"directory": "packages/core"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ describe("catalog.prompt", () => {
|
|||||||
expect(prompt).toContain("Keep UIs simple");
|
expect(prompt).toContain("Keep UIs simple");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("generates chat mode prompt when mode is chat", () => {
|
it("generates inline mode prompt when mode is inline", () => {
|
||||||
const catalog = defineCatalog(testSchema, {
|
const catalog = defineCatalog(testSchema, {
|
||||||
components: {
|
components: {
|
||||||
Text: {
|
Text: {
|
||||||
@@ -268,13 +268,13 @@ describe("catalog.prompt", () => {
|
|||||||
},
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
});
|
});
|
||||||
const prompt = catalog.prompt({ mode: "chat" });
|
const prompt = catalog.prompt({ mode: "inline" });
|
||||||
expect(prompt).toContain("```spec");
|
expect(prompt).toContain("```spec");
|
||||||
expect(prompt).toContain("conversationally");
|
expect(prompt).toContain("conversationally");
|
||||||
expect(prompt).toContain("text + JSONL");
|
expect(prompt).toContain("text + JSONL");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("generates generate mode prompt by default", () => {
|
it("generates standalone mode prompt by default", () => {
|
||||||
const catalog = defineCatalog(testSchema, {
|
const catalog = defineCatalog(testSchema, {
|
||||||
components: {
|
components: {
|
||||||
Text: {
|
Text: {
|
||||||
@@ -290,6 +290,38 @@ describe("catalog.prompt", () => {
|
|||||||
expect(prompt).not.toContain("conversationally");
|
expect(prompt).not.toContain("conversationally");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts deprecated 'chat' as alias for 'inline'", () => {
|
||||||
|
const catalog = defineCatalog(testSchema, {
|
||||||
|
components: {
|
||||||
|
Text: {
|
||||||
|
props: z.object({}),
|
||||||
|
description: "",
|
||||||
|
slots: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
|
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||||
|
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||||
|
expect(chatPrompt).toEqual(inlinePrompt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts deprecated 'generate' as alias for 'standalone'", () => {
|
||||||
|
const catalog = defineCatalog(testSchema, {
|
||||||
|
components: {
|
||||||
|
Text: {
|
||||||
|
props: z.object({}),
|
||||||
|
description: "",
|
||||||
|
slots: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
|
const standalonePrompt = catalog.prompt({ mode: "standalone" });
|
||||||
|
const generatePrompt = catalog.prompt({ mode: "generate" });
|
||||||
|
expect(generatePrompt).toEqual(standalonePrompt);
|
||||||
|
});
|
||||||
|
|
||||||
it("uses actual catalog component names in examples", () => {
|
it("uses actual catalog component names in examples", () => {
|
||||||
const catalog = defineCatalog(testSchema, {
|
const catalog = defineCatalog(testSchema, {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -112,11 +112,14 @@ export interface PromptOptions {
|
|||||||
/**
|
/**
|
||||||
* Output mode for the generated prompt.
|
* Output mode for the generated prompt.
|
||||||
*
|
*
|
||||||
* - `"generate"` (default): The LLM should output only JSONL patches (no prose).
|
* - `"standalone"` (default): The LLM should output only JSONL patches (no prose).
|
||||||
* - `"chat"`: The LLM should respond conversationally first, then output JSONL patches.
|
* - `"inline"`: The LLM should respond conversationally first, then output JSONL patches.
|
||||||
* Includes rules about interleaving text with JSONL and not wrapping in code fences.
|
* Includes rules about interleaving text with JSONL and not wrapping in code fences.
|
||||||
|
*
|
||||||
|
* @deprecated `"generate"` — use `"standalone"` instead.
|
||||||
|
* @deprecated `"chat"` — use `"inline"` instead.
|
||||||
*/
|
*/
|
||||||
mode?: "generate" | "chat";
|
mode?: "standalone" | "inline" | "generate" | "chat";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -563,15 +566,28 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
|
|||||||
const {
|
const {
|
||||||
system = "You are a UI generator that outputs JSON.",
|
system = "You are a UI generator that outputs JSON.",
|
||||||
customRules = [],
|
customRules = [],
|
||||||
mode = "generate",
|
mode: rawMode = "standalone",
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
|
const mode: "standalone" | "inline" =
|
||||||
|
rawMode === "chat"
|
||||||
|
? (console.warn(
|
||||||
|
'[json-render] mode "chat" is deprecated, use "inline" instead',
|
||||||
|
),
|
||||||
|
"inline")
|
||||||
|
: rawMode === "generate"
|
||||||
|
? (console.warn(
|
||||||
|
'[json-render] mode "generate" is deprecated, use "standalone" instead',
|
||||||
|
),
|
||||||
|
"standalone")
|
||||||
|
: rawMode;
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
lines.push(system);
|
lines.push(system);
|
||||||
lines.push("");
|
lines.push("");
|
||||||
|
|
||||||
// Output format section - explain JSONL streaming patch format
|
// Output format section - explain JSONL streaming patch format
|
||||||
if (mode === "chat") {
|
if (mode === "inline") {
|
||||||
lines.push("OUTPUT FORMAT (text + JSONL, RFC 6902 JSON Patch):");
|
lines.push("OUTPUT FORMAT (text + JSONL, RFC 6902 JSON Patch):");
|
||||||
lines.push(
|
lines.push(
|
||||||
"You respond conversationally. When generating UI, first write a brief explanation (1-3 sentences), then output JSONL patch lines wrapped in a ```spec code fence.",
|
"You respond conversationally. When generating UI, first write a brief explanation (1-3 sentences), then output JSONL patch lines wrapped in a ```spec code fence.",
|
||||||
@@ -1014,7 +1030,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
|
|||||||
// Rules
|
// Rules
|
||||||
lines.push("RULES:");
|
lines.push("RULES:");
|
||||||
const baseRules =
|
const baseRules =
|
||||||
mode === "chat"
|
mode === "inline"
|
||||||
? [
|
? [
|
||||||
"When generating UI, wrap all JSONL patches in a ```spec code fence - one JSON object per line inside the fence",
|
"When generating UI, wrap all JSONL patches in a ```spec code fence - one JSON object per line inside the fence",
|
||||||
"Write a brief conversational response before any JSONL output",
|
"Write a brief conversational response before any JSONL output",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/image"
|
"directory": "packages/image"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/jotai"
|
"directory": "packages/jotai"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/mcp"
|
"directory": "packages/mcp"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/react-email"
|
"directory": "packages/react-email"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/react-native"
|
"directory": "packages/react-native"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/react-pdf"
|
"directory": "packages/react-pdf"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/react"
|
"directory": "packages/react"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/redux"
|
"directory": "packages/redux"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/remotion"
|
"directory": "packages/remotion"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/shadcn"
|
"directory": "packages/shadcn"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/svelte"
|
"directory": "packages/svelte"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/vue"
|
"directory": "packages/vue"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/xstate"
|
"directory": "packages/xstate"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
"directory": "packages/zustand"
|
"directory": "packages/zustand"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
"homepage": "https://json-render.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user