@mdocui/react 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -0
- package/dist/index.cjs +5093 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +81 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +5033 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# @mdocui/react
|
|
2
|
+
|
|
3
|
+
React adapter for [mdocui](https://github.com/mdocui/mdocui) -- provides a streaming `Renderer` component, the `useRenderer` hook, and 22 default UI components for LLM generative UI powered by Markdoc `{% %}` tag syntax.
|
|
4
|
+
|
|
5
|
+
Part of the [mdocui](https://github.com/mdocui/mdocui) monorepo.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @mdocui/react @mdocui/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`@mdocui/core` is a peer dependency.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Renderer, useRenderer, defaultComponents, createDefaultRegistry } from '@mdocui/react'
|
|
19
|
+
|
|
20
|
+
const registry = createDefaultRegistry()
|
|
21
|
+
|
|
22
|
+
function Chat() {
|
|
23
|
+
const { nodes, meta, isStreaming, push, done, reset } = useRenderer({ registry })
|
|
24
|
+
|
|
25
|
+
// Feed chunks from your LLM stream
|
|
26
|
+
async function onStream(reader: ReadableStreamDefaultReader<string>) {
|
|
27
|
+
reset()
|
|
28
|
+
while (true) {
|
|
29
|
+
const { value, done: streamDone } = await reader.read()
|
|
30
|
+
if (streamDone) break
|
|
31
|
+
push(value)
|
|
32
|
+
}
|
|
33
|
+
done()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Renderer
|
|
38
|
+
nodes={nodes}
|
|
39
|
+
components={defaultComponents}
|
|
40
|
+
isStreaming={isStreaming}
|
|
41
|
+
onAction={(event) => console.log('action:', event)}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## `useRenderer`
|
|
50
|
+
|
|
51
|
+
Hook that wraps `@mdocui/core`'s `StreamingParser`. Manages parse state in React and returns reactive AST nodes.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const { nodes, meta, isStreaming, push, done, reset } = useRenderer({ registry })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Options
|
|
58
|
+
|
|
59
|
+
| Option | Type | Description |
|
|
60
|
+
|--------|------|-------------|
|
|
61
|
+
| `registry` | `ComponentRegistry` | Component registry from `@mdocui/core` |
|
|
62
|
+
|
|
63
|
+
### Return value (`UseRendererReturn`)
|
|
64
|
+
|
|
65
|
+
| Field | Type | Description |
|
|
66
|
+
|-------|------|-------------|
|
|
67
|
+
| `nodes` | `ASTNode[]` | Current parsed AST |
|
|
68
|
+
| `meta` | `ParseMeta` | Parse errors, node count, completion status |
|
|
69
|
+
| `isStreaming` | `boolean` | `true` between the first `push` and `done` |
|
|
70
|
+
| `push(chunk)` | `(chunk: string) => void` | Feed a text chunk from the LLM stream |
|
|
71
|
+
| `done()` | `() => void` | Signal the stream has ended; flushes the parser |
|
|
72
|
+
| `reset()` | `() => void` | Clear all state for a new conversation turn |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## `Renderer`
|
|
77
|
+
|
|
78
|
+
Renders an `ASTNode[]` tree into React elements using a component map.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<Renderer
|
|
82
|
+
nodes={nodes}
|
|
83
|
+
components={defaultComponents}
|
|
84
|
+
onAction={handleAction}
|
|
85
|
+
isStreaming={isStreaming}
|
|
86
|
+
renderProse={(content, key) => <Markdown key={key}>{content}</Markdown>}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Props (`RendererProps`)
|
|
91
|
+
|
|
92
|
+
| Prop | Type | Default | Description |
|
|
93
|
+
|------|------|---------|-------------|
|
|
94
|
+
| `nodes` | `ASTNode[]` | required | AST from `useRenderer` or `StreamingParser` |
|
|
95
|
+
| `components` | `ComponentMap` | required | Map of tag name to React component |
|
|
96
|
+
| `onAction` | `ActionHandler` | no-op | Callback for interactive events |
|
|
97
|
+
| `isStreaming` | `boolean` | `false` | Whether the LLM is still streaming |
|
|
98
|
+
| `renderProse` | `(content: string, key: string) => ReactNode` | plain `<span>` | Custom renderer for prose nodes (see below) |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## `renderProse` -- Markdown Rendering
|
|
103
|
+
|
|
104
|
+
By default, prose nodes render as plain `<span>` elements. To render markdown, pass a `renderProse` function:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import ReactMarkdown from 'react-markdown'
|
|
108
|
+
|
|
109
|
+
<Renderer
|
|
110
|
+
nodes={nodes}
|
|
111
|
+
components={defaultComponents}
|
|
112
|
+
renderProse={(content, key) => <ReactMarkdown key={key}>{content}</ReactMarkdown>}
|
|
113
|
+
/>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Any markdown library works -- `react-markdown`, `marked`, `markdown-it`, etc.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Components (22)
|
|
121
|
+
|
|
122
|
+
All default components are available via `defaultComponents` and registered in `createDefaultRegistry()`.
|
|
123
|
+
|
|
124
|
+
### Layout (7)
|
|
125
|
+
|
|
126
|
+
| Component | Tag | Description |
|
|
127
|
+
|-----------|-----|-------------|
|
|
128
|
+
| Stack | `stack` | Vertical or horizontal flex container. Props: `direction?`, `gap?`, `align?` |
|
|
129
|
+
| Grid | `grid` | CSS grid layout. Props: `cols?`, `gap?` |
|
|
130
|
+
| Card | `card` | Bordered container. Props: `title?`, `variant?` |
|
|
131
|
+
| Divider | `divider` | Horizontal separator line. Self-closing. |
|
|
132
|
+
| Accordion | `accordion` | Collapsible section. Props: `title`, `open?` |
|
|
133
|
+
| Tabs | `tabs` | Tabbed container. Props: `labels`, `active?` |
|
|
134
|
+
| Tab | `tab` | Single tab panel inside `tabs`. Props: `label` |
|
|
135
|
+
|
|
136
|
+
### Interactive (6)
|
|
137
|
+
|
|
138
|
+
| Component | Tag | Description |
|
|
139
|
+
|-----------|-----|-------------|
|
|
140
|
+
| Button | `button` | Action button. Props: `action`, `label`, `variant?`, `disabled?` |
|
|
141
|
+
| ButtonGroup | `button-group` | Row of buttons. Props: `direction?` |
|
|
142
|
+
| Input | `input` | Text input field. Props: `name`, `label?`, `placeholder?`, `type?`, `required?` |
|
|
143
|
+
| Select | `select` | Dropdown select. Props: `name`, `label?`, `options`, `placeholder?`, `required?` |
|
|
144
|
+
| Checkbox | `checkbox` | Toggle checkbox. Props: `name`, `label`, `checked?` |
|
|
145
|
+
| Form | `form` | Groups inputs; submits collected state. Props: `name`, `action?` |
|
|
146
|
+
|
|
147
|
+
### Data (4)
|
|
148
|
+
|
|
149
|
+
| Component | Tag | Description |
|
|
150
|
+
|-----------|-----|-------------|
|
|
151
|
+
| Chart | `chart` | Bar, line, pie, or donut chart. Props: `type`, `labels`, `values`, `title?` |
|
|
152
|
+
| Table | `table` | Data table. Props: `headers`, `rows`, `caption?` |
|
|
153
|
+
| Stat | `stat` | Key metric display. Props: `label`, `value`, `change?`, `trend?` |
|
|
154
|
+
| Progress | `progress` | Progress bar. Props: `value`, `label?`, `max?` |
|
|
155
|
+
|
|
156
|
+
### Content (5)
|
|
157
|
+
|
|
158
|
+
| Component | Tag | Description |
|
|
159
|
+
|-----------|-----|-------------|
|
|
160
|
+
| Callout | `callout` | Alert / notice block. Props: `type`, `title?`. Accepts body content. |
|
|
161
|
+
| Badge | `badge` | Inline label. Props: `label`, `variant?` |
|
|
162
|
+
| Image | `image` | Inline image. Props: `src`, `alt`, `width?`, `height?` |
|
|
163
|
+
| CodeBlock | `code-block` | Syntax-highlighted code. Props: `language?`, `title?`, `code` |
|
|
164
|
+
| Link | `link` | Action link. Props: `action`, `label`, `url?` |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## `ComponentProps`
|
|
169
|
+
|
|
170
|
+
Every component in the `ComponentMap` receives the same props interface:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
interface ComponentProps {
|
|
174
|
+
name: string // tag name, e.g. "button"
|
|
175
|
+
props: Record<string, unknown> // parsed attributes from the tag
|
|
176
|
+
children?: React.ReactNode // rendered child nodes (for body tags)
|
|
177
|
+
onAction: ActionHandler // fire an ActionEvent
|
|
178
|
+
isStreaming: boolean // whether the LLM is still streaming
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## `onAction` Event Handling
|
|
185
|
+
|
|
186
|
+
Interactive components fire `ActionEvent` objects through the `onAction` callback:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
interface ActionEvent {
|
|
190
|
+
type: 'button_click' | 'form_submit' | 'select_change' | 'link_click'
|
|
191
|
+
action: string
|
|
192
|
+
label?: string
|
|
193
|
+
formName?: string
|
|
194
|
+
formState?: Record<string, unknown>
|
|
195
|
+
tagName: string
|
|
196
|
+
params?: Record<string, unknown>
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
function handleAction(event: ActionEvent) {
|
|
202
|
+
switch (event.type) {
|
|
203
|
+
case 'button_click':
|
|
204
|
+
if (event.action === 'continue') {
|
|
205
|
+
// Send event.label as a new user message
|
|
206
|
+
}
|
|
207
|
+
break
|
|
208
|
+
case 'form_submit':
|
|
209
|
+
console.log(event.formName, event.formState)
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
<Renderer nodes={nodes} components={defaultComponents} onAction={handleAction} />
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Custom Component Map
|
|
220
|
+
|
|
221
|
+
Override or extend the defaults by merging your own components:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { defaultComponents } from '@mdocui/react'
|
|
225
|
+
import type { ComponentProps } from '@mdocui/react'
|
|
226
|
+
|
|
227
|
+
function MyCard({ props, children }: ComponentProps) {
|
|
228
|
+
return (
|
|
229
|
+
<div className="my-card">
|
|
230
|
+
{props.title && <h3>{props.title as string}</h3>}
|
|
231
|
+
{children}
|
|
232
|
+
</div>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const components = {
|
|
237
|
+
...defaultComponents,
|
|
238
|
+
card: MyCard,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
<Renderer nodes={nodes} components={components} />
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Context
|
|
247
|
+
|
|
248
|
+
`useMdocUI()` provides the renderer context from inside any component in the tree:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { useMdocUI } from '@mdocui/react'
|
|
252
|
+
|
|
253
|
+
function MyComponent() {
|
|
254
|
+
const { onAction, isStreaming, formState, setFormField } = useMdocUI()
|
|
255
|
+
// ...
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Must be used inside a `<Renderer />`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
See the root [mdocui](https://github.com/mdocui/mdocui) repository for license details.
|