page.mdx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import { pageMetadata } from "@/lib/page-metadata"
  2. export const metadata = pageMetadata("docs/ai-sdk")
  3. # AI SDK Integration
  4. 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.
  5. ## Installation
  6. ```bash
  7. npm install ai @ai-sdk/react
  8. ```
  9. ## Standalone Mode
  10. 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.
  11. ### API Route
  12. ```typescript
  13. // app/api/generate/route.ts
  14. import { streamText } from "ai";
  15. import { catalog } from "@/lib/catalog";
  16. export async function POST(req: Request) {
  17. const { prompt, currentTree } = await req.json();
  18. const systemPrompt = catalog.prompt();
  19. // Optionally include current UI state for context
  20. const contextPrompt = currentTree
  21. ? `\n\nCurrent UI state:\n${JSON.stringify(currentTree, null, 2)}`
  22. : "";
  23. const result = streamText({
  24. model: yourModel,
  25. system: systemPrompt + contextPrompt,
  26. prompt,
  27. });
  28. return result.toTextStreamResponse();
  29. }
  30. ```
  31. ### Client
  32. Use `useUIStream` on the client to compile the JSONL stream into a spec:
  33. ```tsx
  34. "use client";
  35. import { useUIStream, Renderer } from "@json-render/react";
  36. function GenerativeUI() {
  37. const { spec, isStreaming, error, send } = useUIStream({
  38. api: "/api/generate",
  39. });
  40. return (
  41. <div>
  42. <button
  43. onClick={() => send("Create a dashboard with metrics")}
  44. disabled={isStreaming}
  45. >
  46. {isStreaming ? "Generating..." : "Generate"}
  47. </button>
  48. {error && <p className="text-red-500">{error.message}</p>}
  49. <Renderer spec={spec} registry={registry} loading={isStreaming} />
  50. </div>
  51. );
  52. }
  53. ```
  54. ## Inline Mode
  55. 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.
  56. ### API Route
  57. Use `pipeJsonRender` to separate text from JSONL patches in the stream. Patches are emitted as data parts that the client can pick up.
  58. ```typescript
  59. // app/api/chat/route.ts
  60. import { streamText } from "ai";
  61. import { pipeJsonRender } from "@json-render/core";
  62. import {
  63. createUIMessageStream,
  64. createUIMessageStreamResponse,
  65. } from "ai";
  66. import { catalog } from "@/lib/catalog";
  67. export async function POST(req: Request) {
  68. const { messages } = await req.json();
  69. const result = streamText({
  70. model: yourModel,
  71. system: catalog.prompt({ mode: "inline" }),
  72. messages,
  73. });
  74. const stream = createUIMessageStream({
  75. execute: async ({ writer }) => {
  76. writer.merge(pipeJsonRender(result.toUIMessageStream()));
  77. },
  78. });
  79. return createUIMessageStreamResponse({ stream });
  80. }
  81. ```
  82. ### Client
  83. Use `useChat` from the AI SDK and `useJsonRenderMessage` from json-render to extract the spec from each message:
  84. ```tsx
  85. "use client";
  86. import { useChat } from "@ai-sdk/react";
  87. import { useJsonRenderMessage, Renderer } from "@json-render/react";
  88. function Chat() {
  89. const { messages, input, handleInputChange, handleSubmit } = useChat({
  90. api: "/api/chat",
  91. });
  92. return (
  93. <div>
  94. <div>
  95. {messages.map((msg) => (
  96. <ChatMessage key={msg.id} message={msg} />
  97. ))}
  98. </div>
  99. <form onSubmit={handleSubmit}>
  100. <input
  101. value={input}
  102. onChange={handleInputChange}
  103. placeholder="Ask something..."
  104. />
  105. <button type="submit">Send</button>
  106. </form>
  107. </div>
  108. );
  109. }
  110. function ChatMessage({ message }: { message: { parts: Array<{ type: string; text?: string; data?: unknown }> } }) {
  111. const { spec, text, hasSpec } = useJsonRenderMessage(message.parts);
  112. return (
  113. <div>
  114. {text && <p>{text}</p>}
  115. {hasSpec && spec && (
  116. <Renderer spec={spec} registry={registry} />
  117. )}
  118. </div>
  119. );
  120. }
  121. ```
  122. ## Prompt Engineering
  123. The `catalog.prompt()` method creates an optimized system prompt that:
  124. - Lists all available components and their props
  125. - Describes available actions
  126. - Specifies the expected output format (JSONL-only or text + JSONL depending on mode)
  127. - Includes examples for better generation
  128. ### Custom Rules
  129. Pass custom rules to tailor AI behavior:
  130. ```typescript
  131. const systemPrompt = catalog.prompt({
  132. customRules: [
  133. "Always use Card components for grouping related content",
  134. "Prefer horizontal layouts (Row) for metrics",
  135. "Use consistent spacing with padding=\"md\"",
  136. ],
  137. });
  138. ```
  139. ### Inline Mode Prompt
  140. ```typescript
  141. const inlinePrompt = catalog.prompt({ mode: "inline" });
  142. ```
  143. 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.
  144. ## Which Mode?
  145. <div className="my-6 overflow-x-auto">
  146. <table className="mdx-table w-full text-sm border-collapse">
  147. <thead>
  148. <tr>
  149. <th></th>
  150. <th>Standalone</th>
  151. <th>Inline</th>
  152. </tr>
  153. </thead>
  154. <tbody>
  155. <tr>
  156. <td>Output</td>
  157. <td>JSONL only</td>
  158. <td>Text + JSONL</td>
  159. </tr>
  160. <tr>
  161. <td>Text-only replies</td>
  162. <td>No</td>
  163. <td>Yes</td>
  164. </tr>
  165. <tr>
  166. <td>System prompt</td>
  167. <td><code>catalog.prompt()</code></td>
  168. <td><code>{"catalog.prompt({ mode: \"inline\" })"}</code></td>
  169. </tr>
  170. <tr>
  171. <td>Stream utility</td>
  172. <td><code>useUIStream</code></td>
  173. <td><code>pipeJsonRender</code> + <code>useJsonRenderMessage</code></td>
  174. </tr>
  175. <tr>
  176. <td>Use case</td>
  177. <td>Playgrounds, builders</td>
  178. <td>Chatbots, copilots</td>
  179. </tr>
  180. </tbody>
  181. </table>
  182. </div>
  183. Learn more in the [Generation Modes](/docs/generation-modes) guide.
  184. ## Next
  185. - Learn about [progressive streaming](/docs/streaming)
  186. - See the [chat example](https://github.com/vercel-labs/json-render/tree/main/examples/chat) for a complete implementation