page.mdx 4.8 KB

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