page.mdx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { pageMetadata } from "@/lib/page-metadata"
  2. export const metadata = pageMetadata("docs/streaming")
  3. # Streaming
  4. Progressively render UI as AI generates it.
  5. ## SpecStream Format
  6. json-render uses **SpecStream**, a JSONL-based streaming format where each line is a JSON patch operation that progressively builds your spec:
  7. ```json
  8. {"op":"add","path":"/root","value":"root"}
  9. {"op":"add","path":"/elements/root","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
  10. {"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue"}}}
  11. {"op":"add","path":"/elements/metric-2","value":{"type":"Metric","props":{"label":"Users"}}}
  12. ```
  13. ## Patch Operations (RFC 6902)
  14. SpecStream uses [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations:
  15. - `add` — Add a value at a path (creates or replaces for objects, inserts for arrays)
  16. - `remove` — Remove the value at a path
  17. - `replace` — Replace an existing value at a path
  18. - `move` — Move a value from one path to another (requires `from`)
  19. - `copy` — Copy a value from one path to another (requires `from`)
  20. - `test` — Assert that a value at a path equals the given value
  21. ## Path Format
  22. Paths follow JSON Pointer (RFC 6901) into the spec object:
  23. ```bash
  24. /root -> Root element key (string)
  25. /elements/card-1 -> Element with key "card-1"
  26. /elements/card-1/props -> Props of card-1
  27. /elements/card-1/children -> Children of card-1
  28. ```
  29. ## Server-Side Setup
  30. Ensure your API route streams properly:
  31. ```typescript
  32. import { streamText } from 'ai';
  33. import { catalog } from '@/lib/catalog';
  34. export async function POST(req: Request) {
  35. const { prompt } = await req.json();
  36. const result = streamText({
  37. model: 'anthropic/claude-haiku-4.5',
  38. system: catalog.prompt(),
  39. prompt,
  40. });
  41. // Return as a streaming response
  42. return result.toTextStreamResponse();
  43. }
  44. ```
  45. ## Low-Level SpecStream API
  46. For custom or framework-agnostic streaming implementations, use the SpecStream compiler from `@json-render/core` directly:
  47. ```typescript
  48. import { createSpecStreamCompiler } from '@json-render/core';
  49. // Create a compiler for your spec type
  50. const compiler = createSpecStreamCompiler<MySpec>();
  51. const decoder = new TextDecoder();
  52. // Process streaming chunks from AI
  53. async function processStream(reader: ReadableStreamDefaultReader<Uint8Array>) {
  54. while (true) {
  55. const { done, value } = await reader.read();
  56. if (done) break;
  57. // Decode the Uint8Array chunk to a string
  58. const chunk = decoder.decode(value, { stream: true });
  59. const { result, newPatches } = compiler.push(chunk);
  60. if (newPatches.length > 0) {
  61. // Update UI with partial result
  62. setSpec(result);
  63. }
  64. }
  65. // Get final compiled result
  66. return compiler.getResult();
  67. }
  68. ```
  69. ### One-Shot Compilation
  70. For non-streaming scenarios, compile entire SpecStream at once:
  71. ```typescript
  72. import { compileSpecStream } from '@json-render/core';
  73. const jsonl = `{"op":"add","path":"/root","value":"card-1"}
  74. {"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Hello"},"children":[]}}`;
  75. const spec = compileSpecStream<Spec>(jsonl);
  76. // { root: "card-1", elements: { "card-1": { type: "Card", props: { title: "Hello" }, children: [] } } }
  77. ```
  78. ## Usage with React
  79. `@json-render/react` provides the `useUIStream` hook, which wraps the low-level compiler in a React-friendly API with state management, error handling, and abort support.
  80. ### useUIStream Hook
  81. ```tsx
  82. import { useUIStream } from '@json-render/react';
  83. function App() {
  84. const {
  85. spec, // Current UI spec state
  86. isStreaming, // True while streaming
  87. error, // Any error that occurred
  88. send, // Function to start generation
  89. clear, // Function to reset spec and error
  90. } = useUIStream({
  91. api: '/api/generate',
  92. onComplete: (spec) => {}, // Optional: called when streaming completes
  93. onError: (error) => {}, // Optional: called when an error occurs
  94. });
  95. }
  96. ```
  97. ### Progressive Rendering
  98. The Renderer automatically updates as the spec changes:
  99. ```tsx
  100. function App() {
  101. const { spec, isStreaming } = useUIStream({ api: '/api/generate' });
  102. return (
  103. <div>
  104. {isStreaming && <LoadingIndicator />}
  105. <Renderer spec={spec} registry={registry} loading={isStreaming} />
  106. </div>
  107. );
  108. }
  109. ```
  110. ### Aborting Streams
  111. Calling `send` again automatically aborts the previous request. Use `clear` to reset the spec and error state:
  112. ```tsx
  113. function App() {
  114. const { isStreaming, send, clear } = useUIStream({
  115. api: '/api/generate',
  116. });
  117. return (
  118. <div>
  119. <button onClick={() => send('Create dashboard')}>
  120. Generate
  121. </button>
  122. <button onClick={clear}>Reset</button>
  123. </div>
  124. );
  125. }
  126. ```
  127. See the [@json-render/react API reference](/docs/api/react) for full `useUIStream` documentation.