getsyntux 0.8.0 → 0.9.0
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 +73 -129
- package/dist/GeneratedUI-DFOjlyg6.d.mts +87 -0
- package/dist/client.d.mts +9 -6
- package/dist/client.mjs +10 -2
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +16 -27
- package/dist/index.mjs +9 -7
- package/dist/index.mjs.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/server.d.mts +27 -0
- package/dist/server.mjs +10 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +7 -2
- package/dist/templates/GeneratedUI.tsx +0 -92
- package/dist/templates/RerenderHandler.tsx +0 -54
- package/dist/types-0XgxLwqz.d.mts +0 -45
package/README.md
CHANGED
|
@@ -10,11 +10,9 @@ You give it a <code>value</code> and it designs the UI to display it.
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
https://
|
|
14
|
-
|
|
15
|
-
*syntux* is designed to **display data**. Do not let this fact intimidate you - that is simply a testament to how token-efficient it is.
|
|
13
|
+

|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
https://github.com/user-attachments/assets/c85de55d-21f3-43ff-8bd3-d6ede2171447
|
|
18
16
|
|
|
19
17
|
### Features
|
|
20
18
|
|
|
@@ -31,7 +29,7 @@ For instance, if you provide an array `value` with 10,000 items, it will cost yo
|
|
|
31
29
|
|
|
32
30
|
### API
|
|
33
31
|
|
|
34
|
-
<i>syntux</i> is built for React
|
|
32
|
+
<i>syntux</i> is built for React, supporting Next.js, React Router / Remix and Astro.
|
|
35
33
|
|
|
36
34
|
One component is all you need:
|
|
37
35
|
|
|
@@ -43,52 +41,42 @@ const valueToDisplay = {
|
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
<GeneratedUI
|
|
46
|
-
|
|
44
|
+
endpoint="/api/syntux"
|
|
47
45
|
value={valueToDisplay}
|
|
48
46
|
hint="UI should look like..."
|
|
49
47
|
/>
|
|
50
48
|
```
|
|
51
49
|
|
|
52
|
-
*syntux*
|
|
50
|
+
*syntux* reads the `value` and designs the UI to best display it, taking the `hint` into consideration.
|
|
53
51
|
|
|
54
52
|
> [!TIP]
|
|
55
|
-
> If you are passing in a **large array** as a value,
|
|
56
|
-
|
|
57
|
-
### Installation
|
|
58
|
-
|
|
59
|
-
In the root of your project:
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
$ npx getsyntux@latest
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
This will automatically install the required components in the `lib/getsyntux` folder.
|
|
66
|
-
|
|
67
|
-
We use the [Vercel AI SDK](https://github.com/vercel/ai) to provide support for all LLM providers. To install the model providers:
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
$ npm i ai
|
|
71
|
-
$ npm i @ai-sdk/anthropic (if you're using Claude)
|
|
72
|
-
```
|
|
53
|
+
> If you are passing in a **large array** as a value, use the `skeletonize` property. See [the explanation](https://docs.getsyntux.com/usage/advanced#skeletonize-property).
|
|
73
54
|
|
|
74
55
|
---
|
|
75
56
|
|
|
76
57
|
### Examples
|
|
77
58
|
|
|
59
|
+
The following examples are meant to give you an idea of how *syntux* works.
|
|
60
|
+
|
|
61
|
+
See [wiki: Installation](https://docs.getsyntux.com/installation) once you're ready to begin.
|
|
62
|
+
|
|
78
63
|
#### Basic Example
|
|
79
64
|
|
|
80
65
|
Generate a simple UI with a hint:
|
|
81
66
|
|
|
82
67
|
```jsx
|
|
83
|
-
import { GeneratedUI } from
|
|
84
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
68
|
+
import { GeneratedUI } from 'getsyntux/client';
|
|
85
69
|
|
|
86
|
-
|
|
87
|
-
const
|
|
70
|
+
export default function Page() {
|
|
71
|
+
const value = { username: 'John', email: 'john@gmail.com', age: 22 };
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
73
|
+
return (
|
|
74
|
+
<GeneratedUI
|
|
75
|
+
endpoint="/api/syntux"
|
|
76
|
+
value={value}
|
|
77
|
+
hint="display as a profile card"
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
92
80
|
}
|
|
93
81
|
```
|
|
94
82
|
|
|
@@ -97,21 +85,19 @@ export default function Home(){
|
|
|
97
85
|
Cache generated UI based on a user ID:
|
|
98
86
|
|
|
99
87
|
```jsx
|
|
100
|
-
|
|
88
|
+
import { cache } from './api/syntux/route';
|
|
101
89
|
|
|
102
|
-
export default function
|
|
103
|
-
const
|
|
104
|
-
const
|
|
90
|
+
export default function Page() {
|
|
91
|
+
const userId = 10;
|
|
92
|
+
const value = { username: 'John', email: 'john@gmail.com', age: 22 };
|
|
105
93
|
|
|
106
94
|
return (
|
|
107
95
|
<GeneratedUI
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}}
|
|
96
|
+
endpoint="/api/syntux"
|
|
97
|
+
value={value}
|
|
98
|
+
hint="UI should look like..."
|
|
112
99
|
|
|
113
|
-
|
|
114
|
-
value={valueToDisplay}
|
|
100
|
+
cached={cache.get(userId)}
|
|
115
101
|
/>
|
|
116
102
|
);
|
|
117
103
|
}
|
|
@@ -122,26 +108,31 @@ export default function Home() {
|
|
|
122
108
|
Use your own components, or someone else's (a library):
|
|
123
109
|
|
|
124
110
|
```jsx
|
|
125
|
-
import {
|
|
111
|
+
import { GeneratedUI } from 'getsyntux/client';
|
|
112
|
+
import { Card, Avatar } from '@/components/ui';
|
|
126
113
|
|
|
127
|
-
export default function
|
|
128
|
-
const
|
|
114
|
+
export default function Page() {
|
|
115
|
+
const value = { username: 'John', email: 'john@gmail.com', avatar: '/john.png' };
|
|
129
116
|
|
|
130
117
|
return (
|
|
131
118
|
<GeneratedUI
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
119
|
+
endpoint="/api/syntux"
|
|
120
|
+
value={value}
|
|
121
|
+
hint="use custom components when possible"
|
|
122
|
+
|
|
123
|
+
components={[
|
|
124
|
+
{
|
|
125
|
+
name: 'Card',
|
|
126
|
+
props: '{ title: string, body: string }',
|
|
127
|
+
component: Card,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'Avatar',
|
|
131
|
+
props: '{ src: string, alt: string }',
|
|
132
|
+
component: Avatar,
|
|
133
|
+
context: 'Displays a circular profile image.', /* optional */
|
|
134
|
+
},
|
|
135
|
+
]}
|
|
145
136
|
/>
|
|
146
137
|
);
|
|
147
138
|
}
|
|
@@ -155,47 +146,21 @@ export default function Home() {
|
|
|
155
146
|
|
|
156
147
|
Make sure components are marked with `"use client"`.
|
|
157
148
|
|
|
158
|
-
####
|
|
159
|
-
|
|
160
|
-
Use the `useSyntux` hook to retrieve and update the `value` inside a custom component:
|
|
161
|
-
|
|
162
|
-
```jsx
|
|
163
|
-
"use client";
|
|
164
|
-
|
|
165
|
-
export default function CustomComponent() {
|
|
166
|
-
const { value, setValue } = useSyntux();
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<button
|
|
170
|
-
onClick={() => {
|
|
171
|
-
const newArr = [...value];
|
|
172
|
-
newArr.push({ ... });
|
|
173
|
-
setValue(newArr);
|
|
174
|
-
}}
|
|
175
|
-
>
|
|
176
|
-
Add value
|
|
177
|
-
</button>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### Regenerate UI (dynamic)
|
|
149
|
+
#### Reactivity
|
|
183
150
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
**The server action is already provided.** However, you will need to configure it (i.e., add an API key etc,.)
|
|
151
|
+
To regenerate the UI dynamically, in response to user action, create a separate endpoint:
|
|
187
152
|
|
|
188
153
|
```jsx
|
|
189
|
-
|
|
154
|
+
<GeneratedUI
|
|
155
|
+
endpoint="/api/syntux"
|
|
156
|
+
rerenderEndpoint="/api/syntux/rerender" /* <-- over here */
|
|
190
157
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
value={valueToDisplay}
|
|
194
|
-
rerender={rerenderAction}
|
|
158
|
+
value={value}
|
|
159
|
+
hint="display as a profile card"
|
|
195
160
|
/>
|
|
196
161
|
```
|
|
197
162
|
|
|
198
|
-
|
|
163
|
+
Then use the `useSyntux` hook:
|
|
199
164
|
|
|
200
165
|
```jsx
|
|
201
166
|
"use client";
|
|
@@ -217,60 +182,39 @@ export default function CustomComponent() {
|
|
|
217
182
|
);
|
|
218
183
|
}
|
|
219
184
|
```
|
|
185
|
+
<h3 align="center" margin="0"><a href="https://docs.getsyntux.com/">➡️ view documentation</a></h3>
|
|
220
186
|
|
|
221
|
-
The new user interface will be streamed.
|
|
222
|
-
|
|
223
|
-
#### Customize animation
|
|
224
|
-
|
|
225
|
-
By default, new elements fade in from below when mounted.
|
|
226
|
-
|
|
227
|
-
This motion cannot yet be customized. However, the duration and offset can, using the `animate` property:
|
|
228
|
-
|
|
229
|
-
```jsx
|
|
230
|
-
<GeneratedUI
|
|
231
|
-
model={anthropic("claude-sonnet-4-5")}
|
|
232
|
-
value={valueToDisplay}
|
|
233
|
-
animate={{
|
|
234
|
-
offset: 10, // pixels
|
|
235
|
-
duration: 100 // ms
|
|
236
|
-
}}
|
|
237
|
-
/>
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
In order to disable the animation, set `offset` to 0 or `duration` to 0.
|
|
241
187
|
|
|
242
188
|
---
|
|
243
189
|
|
|
244
190
|
### FAQ
|
|
245
191
|
|
|
246
|
-
<details>
|
|
247
|
-
<summary>How expensive is generation?</summary>
|
|
248
|
-
|
|
249
|
-
*syntux* is highly optimized to save tokens. See [here](https://docs.getsyntux.com/advanced#cost-estimation) for a cost estimation table and an explanation.
|
|
250
|
-
</details>
|
|
251
|
-
|
|
252
192
|
<details>
|
|
253
193
|
<summary>How does generation work? (Does it generate source code?)</summary>
|
|
254
194
|
|
|
255
|
-
Generated
|
|
195
|
+
Generated interfaces must be *secure*, *reusable* and *cacheable*.
|
|
256
196
|
|
|
257
|
-
As such,
|
|
197
|
+
As such, syntux does not:
|
|
198
|
+
- generate code (HTML/JSX), or...
|
|
199
|
+
- hardcode the `value`
|
|
258
200
|
|
|
259
|
-
|
|
201
|
+
Instead, syntux generates a JSON-DSL representation of the UI, known as the React Interface Schema (RIS).
|
|
260
202
|
|
|
261
|
-
|
|
262
|
-
</details>
|
|
203
|
+
The RIS **does not hardcode values**. It **binds** to properties of the `value` and has **built-in iterators**, making it reusable and token-efficient for arrays.
|
|
263
204
|
|
|
264
|
-
|
|
265
|
-
<summary>How does caching work?</summary>
|
|
205
|
+
An example of the RIS:
|
|
266
206
|
|
|
267
|
-
|
|
207
|
+
```json
|
|
208
|
+
{"id":"loop_1", "parentId":"root", "type":"__ForEach__", "props":{"source":"authors"}}
|
|
209
|
+
{"id":"card_1", "parentId":"loop_1", "type":"div", "props":{"className":"card"}, "content": {"$bind": "$item.name"}}
|
|
210
|
+
```
|
|
268
211
|
|
|
269
|
-
|
|
212
|
+
</details>
|
|
270
213
|
|
|
271
|
-
|
|
214
|
+
<details>
|
|
215
|
+
<summary>How expensive is generation?</summary>
|
|
272
216
|
|
|
273
|
-
|
|
217
|
+
*syntux* is highly optimized to save tokens. See [here](https://docs.getsyntux.com/usage/advanced#cost-estimation) for a cost estimation table and an explanation.
|
|
274
218
|
</details>
|
|
275
219
|
|
|
276
220
|
<details>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { JSX } from 'react';
|
|
3
|
+
|
|
4
|
+
type SchemaNode = {
|
|
5
|
+
id: string;
|
|
6
|
+
parentId: string | null;
|
|
7
|
+
type: string;
|
|
8
|
+
props?: Record<string, any>;
|
|
9
|
+
content?: any | {
|
|
10
|
+
"$bind": string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
type ComponentMap = Record<string, SchemaNode>;
|
|
14
|
+
type ChildrenMap = Record<string, string[]>;
|
|
15
|
+
type UISchema = {
|
|
16
|
+
componentMap: ComponentMap;
|
|
17
|
+
childrenMap: ChildrenMap;
|
|
18
|
+
root: SchemaNode | null;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* for providing context on custom components
|
|
22
|
+
* used for AllowedComponents and ComponentContext
|
|
23
|
+
*/
|
|
24
|
+
type ComponentMetadata = {
|
|
25
|
+
name: string;
|
|
26
|
+
props: string;
|
|
27
|
+
context?: string;
|
|
28
|
+
};
|
|
29
|
+
type SyntuxComponent = ComponentMetadata & {
|
|
30
|
+
component: React.ComponentType<any>;
|
|
31
|
+
};
|
|
32
|
+
type AnimateOptions = {
|
|
33
|
+
offset: number;
|
|
34
|
+
duration: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* setValue will not send a request if regenerate is false.
|
|
38
|
+
* however, the value will still be updated (statically).
|
|
39
|
+
* as opposed to a falsy options existence check, this is more robust for DX.
|
|
40
|
+
*/
|
|
41
|
+
type RerenderOptions = {
|
|
42
|
+
regenerate: boolean;
|
|
43
|
+
hint: string;
|
|
44
|
+
};
|
|
45
|
+
type RerenderContext = {
|
|
46
|
+
context: string;
|
|
47
|
+
endpoint?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
interface GeneratedUIProps {
|
|
51
|
+
value: any;
|
|
52
|
+
endpoint: string;
|
|
53
|
+
hint?: string;
|
|
54
|
+
components?: (SyntuxComponent | string)[];
|
|
55
|
+
placeholder?: JSX.Element;
|
|
56
|
+
cached?: string;
|
|
57
|
+
onGenerate?: (schema: string) => void;
|
|
58
|
+
skeletonize?: boolean;
|
|
59
|
+
errorFallback?: JSX.Element;
|
|
60
|
+
animate?: AnimateOptions;
|
|
61
|
+
rerenderEndpoint?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Section of user interface for LLM to generate.
|
|
65
|
+
*
|
|
66
|
+
* Required:
|
|
67
|
+
* @param value The value (object, primitive, or array) to be displayed.
|
|
68
|
+
* @param endpoint The relative URL endpoint created with createSyntuxHandler.
|
|
69
|
+
*
|
|
70
|
+
* Optional:
|
|
71
|
+
* @param hint Custom instructions for the LLM.
|
|
72
|
+
* @param components List of allowed components that the LLM can use.
|
|
73
|
+
* @param placeholder Element to be displayed whilst awaiting streaming to begin.
|
|
74
|
+
* @param errorFallback Element to be displayed if an error occurs.
|
|
75
|
+
* @param animate configuration for on-mount animation
|
|
76
|
+
* @param rerenderEndpoint The relative URL endpoint for regeneration.
|
|
77
|
+
*
|
|
78
|
+
* Caching:
|
|
79
|
+
* @param cached Pre-generated schema string (from onGenerate), skips API call.
|
|
80
|
+
* @param onGenerate Callback which accepts the generated schema, for reuse.
|
|
81
|
+
*
|
|
82
|
+
* Advanced:
|
|
83
|
+
* @param skeletonize compresses the value for large inputs (arrays) or untrusted input
|
|
84
|
+
*/
|
|
85
|
+
declare function GeneratedUI(props: GeneratedUIProps): react_jsx_runtime.JSX.Element;
|
|
86
|
+
|
|
87
|
+
export { type AnimateOptions as A, type ComponentMetadata as C, type GeneratedUIProps as G, type RerenderContext as R, type SyntuxComponent as S, type UISchema as U, type ChildrenMap as a, type ComponentMap as b, type RerenderOptions as c, type SchemaNode as d, GeneratedUI as e };
|
package/dist/client.d.mts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
import { A as AnimateOptions, R as RerenderContext, b as ComponentMap, a as ChildrenMap, c as RerenderOptions } from './GeneratedUI-DFOjlyg6.mjs';
|
|
2
|
+
export { e as GeneratedUI, G as GeneratedUIProps } from './GeneratedUI-DFOjlyg6.mjs';
|
|
1
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { StreamableValue } from '@ai-sdk/rsc';
|
|
3
4
|
import * as react from 'react';
|
|
4
5
|
import react__default, { JSX, ComponentType } from 'react';
|
|
5
|
-
import { A as AnimateOptions, R as RerenderContext, a as ComponentMap, C as ChildrenMap, b as RerenderOptions } from './types-0XgxLwqz.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Internal client component that handles streaming, parsing, and rendering.
|
|
9
|
+
* For most use cases, use GeneratedUI instead.
|
|
9
10
|
*/
|
|
10
|
-
declare function GeneratedClient({ value, allowedComponents,
|
|
11
|
+
declare function GeneratedClient({ value, allowedComponents, endpoint, fetchBody, placeholder, errorFallback, animate, onGenerate, rerender, }: {
|
|
11
12
|
value: any;
|
|
12
13
|
allowedComponents: Record<string, react__default.ComponentType<any> | string>;
|
|
13
|
-
|
|
14
|
+
endpoint: string;
|
|
15
|
+
fetchBody: object;
|
|
14
16
|
placeholder?: JSX.Element;
|
|
15
17
|
errorFallback?: JSX.Element;
|
|
16
18
|
animate?: AnimateOptions;
|
|
19
|
+
onGenerate?: (schema: string) => void;
|
|
17
20
|
rerender: RerenderContext;
|
|
18
21
|
}): react_jsx_runtime.JSX.Element;
|
|
19
22
|
|
|
@@ -33,7 +36,7 @@ declare function Renderer(props: RendererProps): react_jsx_runtime.JSX.Element;
|
|
|
33
36
|
|
|
34
37
|
type SyntuxContextType = {
|
|
35
38
|
value: any;
|
|
36
|
-
setValue: (value: any, options?: RerenderOptions) =>
|
|
39
|
+
setValue: (value: any, options?: RerenderOptions) => void;
|
|
37
40
|
};
|
|
38
41
|
declare const SyntuxContext: react.Context<SyntuxContextType>;
|
|
39
42
|
declare function useSyntux(): SyntuxContextType;
|
package/dist/client.mjs
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
`);return e.length>1?(e.slice(0,e.length-1).forEach(
|
|
1
|
+
"use client";var S=class{buffer="";total="";schema={childrenMap:{},componentMap:{},root:null};addDelta(n){this.total+=n,this.buffer+=n;let e=this.buffer.split(`
|
|
2
|
+
`);return e.length>1?(e.slice(0,e.length-1).forEach(r=>this.handleLine(r)),this.buffer=e[e.length-1],!0):!1}handleLine(n){try{let e=JSON.parse(n),{childrenMap:r,componentMap:a}=this.schema;a[e.id]=e,e.parentId===null?this.schema.root=e:(r[e.parentId]||(r[e.parentId]=[]),r[e.parentId].push(e.id))}catch{}}finish(){this.handleLine(this.buffer),this.buffer=""}};function j(t){return t.reduce((n,e)=>typeof e=="string"?(n[e]=e,n):(n[e.name]=e.component,n),{})}function K({value:t,skeletonize:n=!1,components:e,hint:r}){let a=(e==null?void 0:e.map(o=>typeof o=="string"?o:o.name).join(","))||"",m=e==null?void 0:e.filter(o=>typeof o!="string"),y=(m==null?void 0:m.map(o=>o.context?`${o.name} [props: ${o.props}, details: ${o.context}]`:`${o.name} [props: ${o.props}]`).join(","))||"",p=JSON.stringify(n?T(t):t);return`<AllowedComponents>${a}</AllowedComponents>
|
|
3
|
+
<ComponentContext>${y}</ComponentContext>
|
|
4
|
+
<UserContext>${r||""}</UserContext>
|
|
5
|
+
<IsSkeleton>${n.toString()}</IsSkeleton>
|
|
6
|
+
<Value>
|
|
7
|
+
${p}
|
|
8
|
+
</Value>`}function N(t){return K(t).split(`
|
|
9
|
+
`).slice(0,2).join(`
|
|
10
|
+
`)}function T(t){return t===null?"null":typeof t!="object"?typeof t:Array.isArray(t)?t.length==0?"null":[T(t[0])]:Object.entries(t).reduce((n,[e,r])=>(n[e]=T(r),n),{})}import{useCallback as oe,useEffect as ie,useMemo as ae,useReducer as se,useRef as le,useState as V}from"react";import{Fragment as Q,useEffect as Z,useState as ee}from"react";import{Fragment as _,jsx as M}from"react/jsx-runtime";import{createElement as te}from"react";var X=(t,n)=>n==="$"?t:n.split(".").reduce((e,r)=>e==null?void 0:e[r],t),U=(t,n,e)=>e.startsWith("$item.")?(e=e.slice(6),X(n,e)):e==="$item"?n:X(t,e),k=new Set(["dangerouslySetInnerHTML"]),D=t=>t.length>2&&t.startsWith("on")&&t[2]===t[2].toUpperCase(),B=(t,n,e)=>{if(!e)return e;if("$bind"in e){let r=U(t,n,e.$bind);return Object.keys(r).forEach(a=>{(k.has(a)||D(a)&&typeof r[a]!="function")&&delete r[a]}),r}return Object.keys(e).forEach(r=>{if(k.has(r)){delete e[r];return}let a=e[r];typeof a=="object"&&(e[r]=B(t,n,a),D(r)&&typeof e[r]!="function"&&delete e[r])}),e},L=(t,n,e)=>typeof e=="object"?U(t,n,e.$bind):e;function R(t){var I,P,v;let[n,e]=ee(!1);Z(()=>{let i=requestAnimationFrame(()=>e(!0));return()=>cancelAnimationFrame(i)},[]);let{id:r,componentMap:a,childrenMap:m,global:y,local:p,allowedComponents:o,animate:s}=t,l=a[r];if(l.type==="TEXT")return M(_,{children:L(y,p,l.content)});let C=(I=l.props)==null?void 0:I.source;if(l.type==="__ForEach__"&&C){let i=U(y,p,C);if(!Array.isArray(i))return null;let h=m[l.id];return M(_,{children:h==null?void 0:h.map((x,$)=>M(Q,{children:i.map((b,A)=>te(R,{...t,id:x,local:b,key:A}))},$))})}let d=o[l.type]||l.type,c={...B(y,p,l.props)};c.style={...c.style||{}};let g=((P=c.style)==null?void 0:P.opacity)??1;c.style.opacity=n?g:0,c.style.transform=n?"translateY(0)":`translateY(${(s==null?void 0:s.offset)??10}px)`,c.style.transition=`opacity ${(s==null?void 0:s.duration)??200}ms ease-out, transform ${(s==null?void 0:s.duration)??200}ms ease-out`,c.style.willChange="opacity, transform";let u=L(y,p,l.content),f=((v=m[l.id])==null?void 0:v.map((i,h)=>M(R,{...t,id:i},h)))||[],w=[u,...f].filter(i=>i!=null);return w.length>0?M(d,{...c,children:w}):M(d,{...c})}import{createContext as ne,useContext as re}from"react";var F=ne(null);function ge(){let t=re(F);if(!t)throw new Error("useSyntux must be used inside a GeneratedUI.");return t}import{Fragment as H,jsx as O}from"react/jsx-runtime";function z({value:t,allowedComponents:n,endpoint:e,fetchBody:r,placeholder:a,errorFallback:m,animate:y,onGenerate:p,rerender:o}){var v;let[s,l]=V(t),[,C]=se(i=>i+1,0),d=le(null),[E,c]=V(!1),[g,u]=V(()=>({url:e,body:r}));ie(()=>{let i=!0;return d.current=new S,c(!1),(async()=>{var x,$;try{let b=await fetch(g.url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g.body)});if(!b.ok||!b.body)throw new Error(`HTTP ${b.status}`);let A=b.body.getReader(),W=new TextDecoder;for(;;){let{done:Y,value:q}=await A.read();if(!i||Y)break;let J=W.decode(q);d.current&&J!==void 0&&d.current.addDelta(J)&&C()}i&&((x=d.current)==null||x.finish(),C(),p==null||p((($=d.current)==null?void 0:$.total)??""))}catch{i&&c(!0)}})(),()=>{i=!1}},[g]);let f=(v=d.current)==null?void 0:v.schema,w=oe((i,h)=>{var x;if(!h||!h.regenerate)l(i);else{if(!o.endpoint)throw new Error("No rerenderEndpoint provided. Pass rerenderEndpoint to <GeneratedUI>.");l(i),u({url:o.endpoint,body:{context:o.context,existing:((x=d.current)==null?void 0:x.total)??"",hint:h.hint}})}},[o.endpoint,o.context]),I=ae(()=>({value:s,setValue:w}),[s,w]),P=()=>E&&m?O(H,{children:m}):f!=null&&f.root?O(R,{id:f.root.id,componentMap:f.componentMap,childrenMap:f.childrenMap,allowedComponents:n,global:s,local:s,animate:y}):O(H,{children:a});return O(F.Provider,{value:I,children:P()})}import{Fragment as ce,jsx as G}from"react/jsx-runtime";function Te(t){let{endpoint:n,value:e,hint:r,components:a,skeletonize:m,placeholder:y,cached:p,onGenerate:o,errorFallback:s,animate:l,rerenderEndpoint:C}=t,d=j(a||[]);if(p){let u=new S;u.addDelta(p),u.finish();let f=u.schema;return f.root?G(R,{id:f.root.id,componentMap:f.componentMap,childrenMap:f.childrenMap,allowedComponents:d,global:e,local:e,animate:l}):G(ce,{})}let E=(a||[]).map(u=>typeof u=="string"?u:{name:u.name,props:u.props,context:u.context}),c={value:e,hint:r,components:E,skeletonize:m},g=N(t);return G(z,{value:e,allowedComponents:d,endpoint:n,fetchBody:c,placeholder:y,errorFallback:s,animate:l,onGenerate:o,rerender:{context:g,endpoint:C}})}export{z as GeneratedClient,Te as GeneratedUI,R as Renderer,F as SyntuxContext,ge as useSyntux};
|
|
3
11
|
//# sourceMappingURL=client.mjs.map
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/GeneratedClient.tsx","../src/ResponseParser.ts","../src/client/Renderer.tsx","../src/client/SyntuxContext.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { StreamableValue, readStreamableValue } from '@ai-sdk/rsc';\r\nimport React, { JSX, useEffect, useMemo, useReducer, useRef, useState } from 'react';\r\nimport { AnimateOptions, RerenderContext, RerenderOptions } from 'src/types';\r\nimport { ResponseParser } from 'src/ResponseParser';\r\nimport { Renderer } from './Renderer';\r\nimport { SyntuxContext } from './SyntuxContext';\r\n\r\n\r\n\r\n/**\r\n * Client wrapper for Renderer that handles streaming and parsing with server.\r\n */\r\nexport function GeneratedClient({\r\n value,\r\n allowedComponents,\r\n inputStream,\r\n placeholder,\r\n errorFallback,\r\n animate,\r\n rerender\r\n}: {\r\n value: any,\r\n allowedComponents: Record<string, React.ComponentType<any> | string>,\r\n inputStream: StreamableValue<string>,\r\n placeholder?: JSX.Element,\r\n errorFallback?: JSX.Element,\r\n animate?: AnimateOptions,\r\n rerender: RerenderContext\r\n}) {\r\n const [statefulValue, setStatefulValue] = useState(value); // stateful because changeable through context\r\n const [statefulInputStream, setStatefulInputStream] = useState(inputStream);\r\n\r\n const [, forceUpdate] = useReducer(x => x + 1, 0);\r\n const parser = useRef<ResponseParser | null>(null);\r\n const [errored, setErrored] = useState(false)\r\n\r\n // HMR support\r\n useEffect(() => {\r\n setStatefulInputStream(inputStream)\r\n }, [inputStream])\r\n\r\n useEffect(() => {\r\n /**\r\n * flag to avoid conflicting streams from mutating UI.\r\n */\r\n let isActive = true;\r\n\r\n // forcibly create a new one for HMR\r\n parser.current = new ResponseParser();\r\n\r\n const parseStream = async () => {\r\n try {\r\n for await (const delta of readStreamableValue(statefulInputStream)) {\r\n if (!isActive) break;\r\n\r\n if (parser.current && delta !== undefined) {\r\n if (parser.current.addDelta(delta)) {\r\n forceUpdate();\r\n }\r\n }\r\n }\r\n\r\n if (isActive) {\r\n parser.current?.finish();\r\n forceUpdate();\r\n }\r\n } catch (err) {\r\n if (isActive) setErrored(true);\r\n }\r\n };\r\n\r\n parseStream();\r\n\r\n return () => {\r\n isActive = false;\r\n }\r\n }, [statefulInputStream]);\r\n\r\n const schema = parser?.current?.schema;\r\n\r\n\r\n const renderContent = () => {\r\n if (errored && errorFallback) return <>{errorFallback}</>\r\n\r\n if (schema?.root) {\r\n return <Renderer id={schema.root.id} componentMap={schema.componentMap} childrenMap={schema.childrenMap} allowedComponents={allowedComponents} global={statefulValue} local={statefulValue} animate={animate} />\r\n } else {\r\n return <>{placeholder}</>\r\n }\r\n }\r\n\r\n const modifyValue = async (value: any, options?: RerenderOptions): Promise<null> | null => {\r\n if (!options || !options.regenerate) {\r\n setStatefulValue(value);\r\n } else {\r\n if (!rerender.action) {\r\n throw new Error(\"No rerender server action provided. Use the 'rerender' prop.\")\r\n } else {\r\n if (parser.current) {\r\n setStatefulValue(value);\r\n return new Promise(async (resolve) => {\r\n const { value } = await rerender.action(rerender.context, parser.current.total, options.hint);\r\n setStatefulInputStream(value);\r\n resolve(null);\r\n })\r\n }\r\n }\r\n }\r\n }\r\n\r\n const providerValue = useMemo(() => ({\r\n value: statefulValue, setValue: modifyValue\r\n }), [statefulValue]);\r\n return (\r\n <>\r\n <SyntuxContext.Provider value={providerValue}>\r\n {renderContent()}\r\n </SyntuxContext.Provider >\r\n </>\r\n )\r\n}\r\n","import { SchemaNode, UISchema } from \"./types\";\r\n\r\n/**\r\n * Utility class for parsing UISchema from stream.\r\n */\r\nexport class ResponseParser {\r\n buffer = \"\"; // unflushed existing deltas w/o newline\r\n total = \"\"; // accumulator\r\n\r\n // schema assembled thus far\r\n schema: UISchema = {\r\n childrenMap: {},\r\n componentMap: {},\r\n root: null\r\n }\r\n\r\n /**\r\n * Update schema with latest data chunk.\r\n * \r\n * Handles multiline input gracefully; can be used to load entire schemas from cache.\r\n * \r\n * @param delta delta from stream.\r\n * @returns true if update is warranted, false otherwise.\r\n */\r\n addDelta(delta: string) {\r\n this.total += delta;\r\n this.buffer += delta;\r\n const split = this.buffer.split(\"\\n\")\r\n if (split.length > 1) {\r\n split.slice(0, split.length - 1).forEach((line) => this.handleLine(line));\r\n this.buffer = split[split.length - 1];\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Parses a single line (full JSON object) and updates schema.\r\n * Generally should not be used when streaming data.\r\n */\r\n handleLine(line: string) {\r\n try {\r\n const node: SchemaNode = JSON.parse(line);\r\n\r\n const { childrenMap, componentMap } = this.schema;\r\n\r\n componentMap[node.id] = node;\r\n if (node.parentId === null) {\r\n this.schema.root = node;\r\n } else {\r\n if (!childrenMap[node.parentId]) childrenMap[node.parentId] = []\r\n childrenMap[node.parentId].push(node.id)\r\n }\r\n } catch (err) { /* probably markdown or generation inconsistency */ }\r\n }\r\n\r\n /**\r\n * Clears the buffer and handles any remaining information within.\r\n */\r\n finish(){\r\n this.handleLine(this.buffer);\r\n this.buffer = \"\";\r\n }\r\n}","\"use client\";\r\n\r\nimport { ComponentType, Fragment, useEffect, useState } from 'react';\r\nimport { AnimateOptions, ChildrenMap, ComponentMap } from '../types';\r\n\r\n/**\r\n * lightweight implementation of lodash.get\r\n */\r\nconst resolvePath = (obj: any, path: string) => {\r\n if (path === '$') return obj;\r\n return path.split('.').reduce((acc, curr) => acc?.[curr], obj)\r\n}\r\n\r\n/**\r\n * parses binding protocol and performs property lookup w/ scope resolution\r\n */\r\nconst get = (global: any, local: any, path: string) => {\r\n if (path.startsWith(\"$item.\")) {\r\n path = path.slice(6)\r\n return resolvePath(local, path);\r\n } else {\r\n if (path === \"$item\") return local;\r\n return resolvePath(global, path);\r\n }\r\n}\r\n\r\n\r\nconst blacklistedProps = new Set([\"dangerouslySetInnerHTML\"])\r\n/**\r\n * recursively parses props for bindings, replacing with true values\r\n */\r\nconst resolveProps = (global: any, local: any, props: any) => {\r\n if (!props) return props;\r\n\r\n if (\"$bind\" in props) { // $bind may be falsy value\r\n const resolved = get(global, local, props.$bind);\r\n Object.keys(resolved).forEach((key) => {\r\n if (blacklistedProps.has(key)) {\r\n delete resolved[key];\r\n }\r\n })\r\n return resolved;\r\n }\r\n\r\n Object.keys(props).forEach((key) => {\r\n if (blacklistedProps.has(key)) {\r\n delete props[key];\r\n return;\r\n }\r\n\r\n const val = props[key];\r\n if (typeof val === \"object\") {\r\n props[key] = resolveProps(global, local, val);\r\n }\r\n })\r\n return props;\r\n}\r\n\r\n/**\r\n * output node.content, with check for $bind\r\n*/\r\nconst renderContent = (global: any, local: any, content: any) => {\r\n if (typeof content === \"object\") {\r\n return get(global, local, content.$bind);\r\n } else {\r\n return content;\r\n }\r\n}\r\n\r\nexport interface RendererProps {\r\n id: string;\r\n componentMap: ComponentMap;\r\n childrenMap: ChildrenMap;\r\n allowedComponents: Record<string, ComponentType<any> | string>;\r\n global: any;\r\n local: any;\r\n animate?: AnimateOptions;\r\n}\r\n\r\n/**\r\n * Renders a UISchema recursively, in accordance to the spec.\r\n */\r\nexport function Renderer(props: RendererProps) {\r\n const [isVisible, setIsVisible] = useState(false);\r\n\r\n useEffect(() => {\r\n const frame = requestAnimationFrame(() => setIsVisible(true));\r\n return () => cancelAnimationFrame(frame)\r\n }, [])\r\n\r\n const {\r\n id, componentMap, childrenMap, global, local, allowedComponents, animate\r\n } = props;\r\n const element = componentMap[id];\r\n\r\n if (element.type === \"TEXT\") return <>{renderContent(global, local, element.content)}</>\r\n\r\n const sourceArrPath = element.props?.source;\r\n if (element.type === '__ForEach__' && sourceArrPath) {\r\n const sourceArr = get(global, local, sourceArrPath)\r\n if (!Array.isArray(sourceArr)) return null;\r\n\r\n const childrenArr = childrenMap[element.id];\r\n return <>{childrenArr?.map((childId: string, index: number) => <Fragment key={index}>\r\n {sourceArr.map((item: any, index1: number) => <Renderer {...props} id={childId} local={item} key={index1} />)}\r\n </Fragment>)}</>\r\n }\r\n\r\n const Component = allowedComponents[element.type] || element.type;\r\n const componentProps = resolveProps(global, local, element.props);\r\n\r\n const animatedProps = {...componentProps}\r\n animatedProps.style = {...(animatedProps.style) || {}}\r\n\r\n const initialOpacity = animatedProps.style?.opacity ?? 1;\r\n animatedProps.style.opacity = isVisible ? initialOpacity : 0;\r\n animatedProps.style.transform = isVisible ? 'translateY(0)' : `translateY(${animate?.offset ?? 10}px)`;\r\n animatedProps.style.transition = `opacity ${animate?.duration ?? 200}ms ease-out, transform ${animate?.duration ?? 200}ms ease-out`;\r\n animatedProps.style.willChange = 'opacity, transform';\r\n\r\n const contentNode = renderContent(global, local, element.content);\r\n const childNodes = childrenMap[element.id]?.map((childId: string, index: number) => {\r\n return <Renderer\r\n key={index}\r\n {...props}\r\n id={childId}\r\n />\r\n }) || []\r\n\r\n const nodesToRender = [contentNode, ...childNodes].filter(node => node !== null && node !== undefined) // 0 is falsy\r\n\r\n if (nodesToRender.length > 0) {\r\n return <Component {...animatedProps}>\r\n {nodesToRender}\r\n </Component>\r\n }\r\n\r\n return <Component {...animatedProps}/>\r\n}\r\n","import { createContext, useContext } from \"react\";\r\nimport { RerenderOptions } from \"src/types\";\r\n\r\nexport type SyntuxContextType = {\r\n value: any,\r\n setValue: (value: any, options?: RerenderOptions) => Promise<null> | null\r\n}\r\n\r\nexport const SyntuxContext = createContext<SyntuxContextType | null>(null)\r\n\r\nexport function useSyntux(){\r\n const context = useContext(SyntuxContext);\r\n if(!context) throw new Error(\"useSyntux must be used inside a GeneratedUI.\");\r\n return context;\r\n}"],"mappings":"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","names":["readStreamableValue","useEffect","useMemo","useReducer","useRef","useState","ResponseParser","delta","split","line","node","childrenMap","componentMap","Fragment","useEffect","useState","jsx","createElement","resolvePath","obj","path","acc","curr","get","global","local","blacklistedProps","resolveProps","props","resolved","key","val","renderContent","content","Renderer","_a","_b","_c","isVisible","setIsVisible","frame","id","componentMap","childrenMap","allowedComponents","animate","element","sourceArrPath","sourceArr","childrenArr","childId","index","item","index1","Component","animatedProps","initialOpacity","contentNode","childNodes","nodesToRender","node","createContext","useContext","SyntuxContext","useSyntux","context","Fragment","jsx","GeneratedClient","value","allowedComponents","inputStream","placeholder","errorFallback","animate","rerender","_a","statefulValue","setStatefulValue","useState","statefulInputStream","setStatefulInputStream","forceUpdate","useReducer","x","parser","useRef","errored","setErrored","useEffect","isActive","ResponseParser","delta","readStreamableValue","schema","renderContent","Renderer","modifyValue","options","resolve","providerValue","useMemo","SyntuxContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/ResponseParser.ts","../src/util.ts","../src/client/GeneratedClient.tsx","../src/client/Renderer.tsx","../src/client/SyntuxContext.tsx","../src/client/GeneratedUI.tsx"],"sourcesContent":["import { SchemaNode, UISchema } from \"./types\";\r\n\r\n/**\r\n * Utility class for parsing UISchema from stream.\r\n */\r\nexport class ResponseParser {\r\n buffer = \"\"; // unflushed existing deltas w/o newline\r\n total = \"\"; // accumulator\r\n\r\n // schema assembled thus far\r\n schema: UISchema = {\r\n childrenMap: {},\r\n componentMap: {},\r\n root: null\r\n }\r\n\r\n /**\r\n * Update schema with latest data chunk.\r\n * \r\n * Handles multiline input gracefully; can be used to load entire schemas from cache.\r\n * \r\n * @param delta delta from stream.\r\n * @returns true if update is warranted, false otherwise.\r\n */\r\n addDelta(delta: string) {\r\n this.total += delta;\r\n this.buffer += delta;\r\n const split = this.buffer.split(\"\\n\")\r\n if (split.length > 1) {\r\n split.slice(0, split.length - 1).forEach((line) => this.handleLine(line));\r\n this.buffer = split[split.length - 1];\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Parses a single line (full JSON object) and updates schema.\r\n * Generally should not be used when streaming data.\r\n */\r\n handleLine(line: string) {\r\n try {\r\n const node: SchemaNode = JSON.parse(line);\r\n\r\n const { childrenMap, componentMap } = this.schema;\r\n\r\n componentMap[node.id] = node;\r\n if (node.parentId === null) {\r\n this.schema.root = node;\r\n } else {\r\n if (!childrenMap[node.parentId]) childrenMap[node.parentId] = []\r\n childrenMap[node.parentId].push(node.id)\r\n }\r\n } catch (err) { /* probably markdown or generation inconsistency */ }\r\n }\r\n\r\n /**\r\n * Clears the buffer and handles any remaining information within.\r\n */\r\n finish(){\r\n this.handleLine(this.buffer);\r\n this.buffer = \"\";\r\n }\r\n}","import { GeneratedUIProps } from \"./client\";\r\nimport { ComponentMetadata, SyntuxComponent } from \"./types\";\r\n\r\n/**\r\n * Converts a list of components into a dictionary for fast-retrieval\r\n * during rendering.\r\n */\r\nexport function generateComponentMap(allowedComponents: (SyntuxComponent | string)[]) {\r\n return allowedComponents.reduce((acc: Record<string, React.ComponentType<any> | string>, curr: SyntuxComponent | string) => {\r\n if (typeof curr === \"string\") {\r\n acc[curr] = curr;\r\n return acc;\r\n }\r\n\r\n acc[curr.name] = curr.component;\r\n return acc;\r\n }, {})\r\n}\r\n\r\n/**\r\n * Creates LLM input in accordance to the spec.\r\n */\r\nexport function constructInput({\r\n value, skeletonize = false, components, hint\r\n}: {\r\n value: any;\r\n components?: (ComponentMetadata | string)[];\r\n hint?: string;\r\n skeletonize?: boolean;\r\n}) {\r\n const allowedComponents = components?.map((item: ComponentMetadata | string) => {\r\n if (typeof item === \"string\") return item;\r\n return item.name;\r\n }).join(',') || \"\"\r\n\r\n const customComponents = components?.filter((item): item is ComponentMetadata => typeof item !== \"string\");\r\n const componentContext = customComponents?.map((item) => {\r\n if (!item.context) {\r\n return `${item.name} [props: ${item.props}]`\r\n } else {\r\n return `${item.name} [props: ${item.props}, details: ${item.context}]`\r\n }\r\n }).join(',') || \"\"\r\n\r\n const inputValue = JSON.stringify(skeletonize ? createSkeleton(value) : value)\r\n\r\n return `<AllowedComponents>${allowedComponents}</AllowedComponents>\\n<ComponentContext>${componentContext}</ComponentContext>\\n<UserContext>${hint || \"\"}</UserContext>\\n<IsSkeleton>${skeletonize.toString()}</IsSkeleton>\\n<Value>\\n${inputValue}\\n</Value>`\r\n}\r\n\r\n/**\r\n * Builds the AllowedComponents + ComponentContext header used as context for rerender requests.\r\n */\r\nexport function constructRerenderContext(props: GeneratedUIProps) {\r\n return constructInput(props).split('\\n').slice(0, 2).join('\\n');\r\n}\r\n\r\n/**\r\n * generates a skeleton of the input value, ideal for large arrays or untrusted input.\r\n * see the FAQ for more information: https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays.\r\n *\r\n * *important*: assumes arrays are non-polymorphic\r\n */\r\nexport function createSkeleton(input: any) {\r\n if (input === null) return \"null\";\r\n\r\n if (typeof input !== \"object\") return typeof input;\r\n\r\n if (Array.isArray(input)) {\r\n if (input.length == 0) {\r\n return \"null\"; // ignore this field completely\r\n } else {\r\n return [createSkeleton(input[0])]\r\n }\r\n }\r\n return Object.entries(input).reduce((acc, [key, value]) => {\r\n acc[key] = createSkeleton(value);\r\n return acc;\r\n }, {})\r\n}\r\n","\"use client\";\r\n\r\nimport React, { JSX, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\r\nimport { AnimateOptions, RerenderContext, RerenderOptions } from '../types';\r\nimport { ResponseParser } from '../ResponseParser';\r\nimport { Renderer } from './Renderer';\r\nimport { SyntuxContext } from './SyntuxContext';\r\n\r\n// stateful, see below\r\ntype FetchConfig = {\r\n url: string;\r\n body: object;\r\n};\r\n\r\n/**\r\n * Internal client component that handles streaming, parsing, and rendering.\r\n * For most use cases, use GeneratedUI instead.\r\n */\r\nexport function GeneratedClient({\r\n value,\r\n allowedComponents,\r\n endpoint,\r\n fetchBody,\r\n placeholder,\r\n errorFallback,\r\n animate,\r\n onGenerate,\r\n rerender,\r\n}: {\r\n value: any;\r\n allowedComponents: Record<string, React.ComponentType<any> | string>;\r\n endpoint: string;\r\n fetchBody: object;\r\n placeholder?: JSX.Element;\r\n errorFallback?: JSX.Element;\r\n animate?: AnimateOptions;\r\n onGenerate?: (schema: string) => void;\r\n rerender: RerenderContext;\r\n}) {\r\n const [statefulValue, setStatefulValue] = useState(value);\r\n const [, forceUpdate] = useReducer(x => x + 1, 0);\r\n const parser = useRef<ResponseParser | null>(null);\r\n const [errored, setErrored] = useState(false);\r\n\r\n /**\r\n * single source of truth for useEffect rerenders.\r\n * body is intentionally vague, stringified very casually later.\r\n */\r\n const [fetchConfig, setFetchConfig] = useState<FetchConfig>(() => ({ url: endpoint, body: fetchBody }));\r\n\r\n useEffect(() => {\r\n /**\r\n * flag to avoid conflicting streams from mutating UI.\r\n */\r\n let isActive = true;\r\n parser.current = new ResponseParser();\r\n setErrored(false);\r\n\r\n const initiateStream = async () => {\r\n try {\r\n const response = await fetch(fetchConfig.url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(fetchConfig.body),\r\n });\r\n\r\n if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);\r\n\r\n const reader = response.body.getReader();\r\n const decoder = new TextDecoder();\r\n\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (!isActive) break;\r\n if (done) break;\r\n\r\n const delta = decoder.decode(value);\r\n\r\n if(parser.current && delta !== undefined){\r\n if(parser.current.addDelta(delta)){\r\n forceUpdate();\r\n }\r\n }\r\n }\r\n\r\n if (isActive) {\r\n parser.current?.finish();\r\n forceUpdate();\r\n onGenerate?.(parser.current?.total ?? '');\r\n }\r\n } catch (err) {\r\n if (isActive) setErrored(true);\r\n }\r\n };\r\n\r\n initiateStream();\r\n return () => { isActive = false; };\r\n }, [fetchConfig]);\r\n\r\n const schema = parser.current?.schema;\r\n\r\n const modifyValue = useCallback((value: any, options?: RerenderOptions) => {\r\n if (!options || !options.regenerate) {\r\n setStatefulValue(value);\r\n } else {\r\n if (!rerender.endpoint) {\r\n throw new Error(\"No rerenderEndpoint provided. Pass rerenderEndpoint to <GeneratedUI>.\");\r\n }\r\n setStatefulValue(value);\r\n setFetchConfig({\r\n url: rerender.endpoint,\r\n body: {\r\n context: rerender.context,\r\n existing: parser.current?.total ?? '',\r\n hint: options.hint,\r\n },\r\n });\r\n }\r\n }, [rerender.endpoint, rerender.context]);\r\n\r\n const providerValue = useMemo(() => ({\r\n value: statefulValue,\r\n setValue: modifyValue,\r\n }), [statefulValue, modifyValue]);\r\n\r\n const renderContent = () => {\r\n if (errored && errorFallback) return <>{errorFallback}</>;\r\n if (schema?.root) {\r\n return <Renderer\r\n id={schema.root.id}\r\n componentMap={schema.componentMap}\r\n childrenMap={schema.childrenMap}\r\n allowedComponents={allowedComponents}\r\n global={statefulValue}\r\n local={statefulValue}\r\n animate={animate}\r\n />;\r\n }\r\n return <>{placeholder}</>;\r\n };\r\n\r\n return (\r\n <SyntuxContext.Provider value={providerValue}>\r\n {renderContent()}\r\n </SyntuxContext.Provider>\r\n );\r\n}\r\n","\"use client\";\r\n\r\nimport { ComponentType, Fragment, useEffect, useState } from 'react';\r\nimport { AnimateOptions, ChildrenMap, ComponentMap } from '../types';\r\n\r\n/**\r\n * lightweight implementation of lodash.get\r\n */\r\nconst resolvePath = (obj: any, path: string) => {\r\n if (path === '$') return obj;\r\n return path.split('.').reduce((acc, curr) => acc?.[curr], obj)\r\n}\r\n\r\n/**\r\n * parses binding protocol and performs property lookup w/ scope resolution\r\n */\r\nconst get = (global: any, local: any, path: string) => {\r\n if (path.startsWith(\"$item.\")) {\r\n path = path.slice(6)\r\n return resolvePath(local, path);\r\n } else {\r\n if (path === \"$item\") return local;\r\n return resolvePath(global, path);\r\n }\r\n}\r\n\r\n\r\nconst blacklistedProps = new Set([\"dangerouslySetInnerHTML\"])\r\n\r\n/**\r\n * LLM hallucinations sometimes cause erroneous event handler insertion.\r\n * light detection for camelCase and on[...]\r\n */\r\nconst isEventHandlerKey = (key: string) => key.length > 2 && key.startsWith('on') && key[2] === key[2].toUpperCase();\r\n\r\n/**\r\n * recursively parses props for bindings, replacing with true values\r\n */\r\nconst resolveProps = (global: any, local: any, props: any) => {\r\n if (!props) return props;\r\n\r\n if (\"$bind\" in props) { // $bind may be falsy value\r\n const resolved = get(global, local, props.$bind);\r\n Object.keys(resolved).forEach((key) => {\r\n if (blacklistedProps.has(key) || (isEventHandlerKey(key) && typeof resolved[key] !== 'function')) {\r\n delete resolved[key];\r\n }\r\n })\r\n return resolved;\r\n }\r\n\r\n Object.keys(props).forEach((key) => {\r\n if (blacklistedProps.has(key)) {\r\n delete props[key];\r\n return;\r\n }\r\n\r\n const val = props[key];\r\n if (typeof val === \"object\") {\r\n props[key] = resolveProps(global, local, val);\r\n if (isEventHandlerKey(key) && typeof props[key] !== 'function') {\r\n delete props[key];\r\n }\r\n }\r\n })\r\n return props;\r\n}\r\n\r\n/**\r\n * output node.content, with check for $bind\r\n*/\r\nconst renderContent = (global: any, local: any, content: any) => {\r\n if (typeof content === \"object\") {\r\n return get(global, local, content.$bind);\r\n } else {\r\n return content;\r\n }\r\n}\r\n\r\nexport interface RendererProps {\r\n id: string;\r\n componentMap: ComponentMap;\r\n childrenMap: ChildrenMap;\r\n allowedComponents: Record<string, ComponentType<any> | string>;\r\n global: any;\r\n local: any;\r\n animate?: AnimateOptions;\r\n}\r\n\r\n/**\r\n * Renders a UISchema recursively, in accordance to the spec.\r\n */\r\nexport function Renderer(props: RendererProps) {\r\n const [isVisible, setIsVisible] = useState(false);\r\n\r\n useEffect(() => {\r\n const frame = requestAnimationFrame(() => setIsVisible(true));\r\n return () => cancelAnimationFrame(frame)\r\n }, [])\r\n\r\n const {\r\n id, componentMap, childrenMap, global, local, allowedComponents, animate\r\n } = props;\r\n const element = componentMap[id];\r\n\r\n if (element.type === \"TEXT\") return <>{renderContent(global, local, element.content)}</>\r\n\r\n const sourceArrPath = element.props?.source;\r\n if (element.type === '__ForEach__' && sourceArrPath) {\r\n const sourceArr = get(global, local, sourceArrPath)\r\n if (!Array.isArray(sourceArr)) return null;\r\n\r\n const childrenArr = childrenMap[element.id];\r\n return <>{childrenArr?.map((childId: string, index: number) => <Fragment key={index}>\r\n {sourceArr.map((item: any, index1: number) => <Renderer {...props} id={childId} local={item} key={index1} />)}\r\n </Fragment>)}</>\r\n }\r\n\r\n const Component = allowedComponents[element.type] || element.type;\r\n const componentProps = resolveProps(global, local, element.props);\r\n\r\n const animatedProps = {...componentProps}\r\n animatedProps.style = {...(animatedProps.style) || {}}\r\n\r\n const initialOpacity = animatedProps.style?.opacity ?? 1;\r\n animatedProps.style.opacity = isVisible ? initialOpacity : 0;\r\n animatedProps.style.transform = isVisible ? 'translateY(0)' : `translateY(${animate?.offset ?? 10}px)`;\r\n animatedProps.style.transition = `opacity ${animate?.duration ?? 200}ms ease-out, transform ${animate?.duration ?? 200}ms ease-out`;\r\n animatedProps.style.willChange = 'opacity, transform';\r\n\r\n const contentNode = renderContent(global, local, element.content);\r\n const childNodes = childrenMap[element.id]?.map((childId: string, index: number) => {\r\n return <Renderer\r\n key={index}\r\n {...props}\r\n id={childId}\r\n />\r\n }) || []\r\n\r\n const nodesToRender = [contentNode, ...childNodes].filter(node => node !== null && node !== undefined) // 0 is falsy\r\n\r\n if (nodesToRender.length > 0) {\r\n return <Component {...animatedProps}>\r\n {nodesToRender}\r\n </Component>\r\n }\r\n\r\n return <Component {...animatedProps}/>\r\n}\r\n","import { createContext, useContext } from \"react\";\r\nimport { RerenderOptions } from \"src/types\";\r\n\r\nexport type SyntuxContextType = {\r\n value: any,\r\n setValue: (value: any, options?: RerenderOptions) => void\r\n}\r\n\r\nexport const SyntuxContext = createContext<SyntuxContextType | null>(null)\r\n\r\nexport function useSyntux(){\r\n const context = useContext(SyntuxContext);\r\n if(!context) throw new Error(\"useSyntux must be used inside a GeneratedUI.\");\r\n return context;\r\n}","\"use client\";\n\nimport { JSX } from 'react';\nimport { ResponseParser } from '../ResponseParser';\nimport { AnimateOptions, ComponentMetadata, SyntuxComponent, UISchema } from '../types';\nimport { constructRerenderContext, generateComponentMap } from '../util';\nimport { GeneratedClient } from './GeneratedClient';\nimport { Renderer } from './Renderer';\n\nexport interface GeneratedUIProps {\n value: any;\n endpoint: string;\n hint?: string;\n components?: (SyntuxComponent | string)[];\n placeholder?: JSX.Element;\n cached?: string;\n onGenerate?: (schema: string) => void;\n skeletonize?: boolean;\n errorFallback?: JSX.Element;\n animate?: AnimateOptions;\n rerenderEndpoint?: string;\n}\n\n/**\n * Section of user interface for LLM to generate.\n * \n * Required:\n * @param value The value (object, primitive, or array) to be displayed.\n * @param endpoint The relative URL endpoint created with createSyntuxHandler.\n * \n * Optional:\n * @param hint Custom instructions for the LLM.\n * @param components List of allowed components that the LLM can use.\n * @param placeholder Element to be displayed whilst awaiting streaming to begin.\n * @param errorFallback Element to be displayed if an error occurs.\n * @param animate configuration for on-mount animation\n * @param rerenderEndpoint The relative URL endpoint for regeneration.\n * \n * Caching:\n * @param cached Pre-generated schema string (from onGenerate), skips API call.\n * @param onGenerate Callback which accepts the generated schema, for reuse.\n * \n * Advanced:\n * @param skeletonize compresses the value for large inputs (arrays) or untrusted input\n */\nexport function GeneratedUI(props: GeneratedUIProps) {\n const {\n endpoint,\n value,\n hint,\n components,\n skeletonize,\n placeholder,\n cached,\n onGenerate,\n errorFallback,\n animate,\n rerenderEndpoint,\n } = props;\n\n const allowedComponents = generateComponentMap(components || []);\n\n // prerender if cached\n if (cached) {\n const parser = new ResponseParser();\n parser.addDelta(cached);\n parser.finish();\n\n const schema: UISchema = parser.schema;\n\n if (schema.root) {\n return <Renderer\n id={schema.root.id}\n componentMap={schema.componentMap}\n childrenMap={schema.childrenMap}\n allowedComponents={allowedComponents}\n global={value}\n local={value}\n animate={animate}\n />;\n }\n\n return <></>; // probably bad schema\n }\n\n /**\n * serialize generation information.\n */\n const componentsMetadata: (ComponentMetadata | string)[] = (components || []).map((comp: ComponentMetadata | string) => {\n if (typeof comp === 'string') return comp;\n\n return {\n name: comp.name,\n props: comp.props,\n context: comp.context\n }\n })\n\n const fetchBody = { value, hint, components: componentsMetadata, skeletonize };\n const rerenderContext = constructRerenderContext(props);\n\n return (\n <GeneratedClient\n value={value}\n allowedComponents={allowedComponents}\n endpoint={endpoint}\n fetchBody={fetchBody}\n placeholder={placeholder}\n errorFallback={errorFallback}\n animate={animate}\n onGenerate={onGenerate}\n rerender={{ context: rerenderContext, endpoint: rerenderEndpoint }}\n />\n );\n}\n"],"mappings":"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","names":["ResponseParser","delta","split","line","node","childrenMap","componentMap","generateComponentMap","allowedComponents","acc","curr","constructInput","value","skeletonize","components","hint","item","customComponents","componentContext","inputValue","createSkeleton","constructRerenderContext","props","input","key","useCallback","useEffect","useMemo","useReducer","useRef","useState","Fragment","useEffect","useState","jsx","createElement","resolvePath","obj","path","acc","curr","get","global","local","blacklistedProps","isEventHandlerKey","key","resolveProps","props","resolved","val","renderContent","content","Renderer","_a","_b","_c","isVisible","setIsVisible","frame","id","componentMap","childrenMap","allowedComponents","animate","element","sourceArrPath","sourceArr","childrenArr","childId","index","item","index1","Component","animatedProps","initialOpacity","contentNode","childNodes","nodesToRender","node","createContext","useContext","SyntuxContext","useSyntux","context","Fragment","jsx","GeneratedClient","value","allowedComponents","endpoint","fetchBody","placeholder","errorFallback","animate","onGenerate","rerender","_a","statefulValue","setStatefulValue","useState","forceUpdate","useReducer","x","parser","useRef","errored","setErrored","fetchConfig","setFetchConfig","useEffect","isActive","ResponseParser","_b","response","reader","decoder","done","delta","schema","modifyValue","useCallback","options","providerValue","useMemo","renderContent","Renderer","SyntuxContext","Fragment","jsx","GeneratedUI","props","endpoint","value","hint","components","skeletonize","placeholder","cached","onGenerate","errorFallback","animate","rerenderEndpoint","allowedComponents","generateComponentMap","parser","ResponseParser","schema","Renderer","componentsMetadata","comp","fetchBody","rerenderContext","constructRerenderContext","GeneratedClient"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,37 +1,26 @@
|
|
|
1
|
-
import { S as SyntuxComponent
|
|
2
|
-
export { A as AnimateOptions,
|
|
1
|
+
import { C as ComponentMetadata, G as GeneratedUIProps, S as SyntuxComponent, U as UISchema } from './GeneratedUI-DFOjlyg6.mjs';
|
|
2
|
+
export { A as AnimateOptions, a as ChildrenMap, b as ComponentMap, R as RerenderContext, c as RerenderOptions, d as SchemaNode } from './GeneratedUI-DFOjlyg6.mjs';
|
|
3
3
|
import * as react from 'react';
|
|
4
|
-
import
|
|
5
|
-
import { StreamableValue } from '@ai-sdk/rsc';
|
|
6
|
-
import { LanguageModel } from 'ai';
|
|
7
|
-
import { SyntuxComponent, AnimateOptions } from 'getsyntux';
|
|
8
|
-
|
|
9
|
-
interface GeneratedContentProps {
|
|
10
|
-
value: any;
|
|
11
|
-
model: LanguageModel;
|
|
12
|
-
components?: (SyntuxComponent | string)[];
|
|
13
|
-
hint?: string;
|
|
14
|
-
placeholder?: JSX.Element;
|
|
15
|
-
cached?: string;
|
|
16
|
-
onGenerate?: (arg0: string) => void;
|
|
17
|
-
skeletonize?: boolean;
|
|
18
|
-
onError?: (arg0: any) => void;
|
|
19
|
-
errorFallback?: JSX.Element;
|
|
20
|
-
animate?: AnimateOptions;
|
|
21
|
-
rerender?: (arg0: string, arg1: string, arg2: string) => Promise<{
|
|
22
|
-
value: StreamableValue;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
4
|
+
import 'react/jsx-runtime';
|
|
25
5
|
|
|
26
6
|
/**
|
|
27
7
|
* Converts a list of components into a dictionary for fast-retrieval
|
|
28
8
|
* during rendering.
|
|
29
9
|
*/
|
|
30
|
-
declare function generateComponentMap(allowedComponents: (SyntuxComponent
|
|
10
|
+
declare function generateComponentMap(allowedComponents: (SyntuxComponent | string)[]): Record<string, string | react.ComponentType<any>>;
|
|
11
|
+
/**
|
|
12
|
+
* Creates LLM input in accordance to the spec.
|
|
13
|
+
*/
|
|
14
|
+
declare function constructInput({ value, skeletonize, components, hint }: {
|
|
15
|
+
value: any;
|
|
16
|
+
components?: (ComponentMetadata | string)[];
|
|
17
|
+
hint?: string;
|
|
18
|
+
skeletonize?: boolean;
|
|
19
|
+
}): string;
|
|
31
20
|
/**
|
|
32
|
-
*
|
|
21
|
+
* Builds the AllowedComponents + ComponentContext header used as context for rerender requests.
|
|
33
22
|
*/
|
|
34
|
-
declare function
|
|
23
|
+
declare function constructRerenderContext(props: GeneratedUIProps): string;
|
|
35
24
|
/**
|
|
36
25
|
* generates a skeleton of the input value, ideal for large arrays or untrusted input.
|
|
37
26
|
* see the FAQ for more information: https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays.
|
|
@@ -67,4 +56,4 @@ declare class ResponseParser {
|
|
|
67
56
|
finish(): void;
|
|
68
57
|
}
|
|
69
58
|
|
|
70
|
-
export { ResponseParser, SyntuxComponent
|
|
59
|
+
export { ComponentMetadata, ResponseParser, SyntuxComponent, UISchema, constructInput, constructRerenderContext, createSkeleton, generateComponentMap };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
function h(
|
|
2
|
-
<ComponentContext>${
|
|
3
|
-
<UserContext>${
|
|
4
|
-
<IsSkeleton>${
|
|
1
|
+
function h(t){return t.reduce((n,e)=>typeof e=="string"?(n[e]=e,n):(n[e.name]=e.component,n),{})}function u({value:t,skeletonize:n=!1,components:e,hint:o}){let a=(e==null?void 0:e.map(r=>typeof r=="string"?r:r.name).join(","))||"",i=e==null?void 0:e.filter(r=>typeof r!="string"),p=(i==null?void 0:i.map(r=>r.context?`${r.name} [props: ${r.props}, details: ${r.context}]`:`${r.name} [props: ${r.props}]`).join(","))||"",f=JSON.stringify(n?s(t):t);return`<AllowedComponents>${a}</AllowedComponents>
|
|
2
|
+
<ComponentContext>${p}</ComponentContext>
|
|
3
|
+
<UserContext>${o||""}</UserContext>
|
|
4
|
+
<IsSkeleton>${n.toString()}</IsSkeleton>
|
|
5
5
|
<Value>
|
|
6
|
-
${
|
|
7
|
-
</Value>`}function
|
|
8
|
-
`)
|
|
6
|
+
${f}
|
|
7
|
+
</Value>`}function d(t){return u(t).split(`
|
|
8
|
+
`).slice(0,2).join(`
|
|
9
|
+
`)}function s(t){return t===null?"null":typeof t!="object"?typeof t:Array.isArray(t)?t.length==0?"null":[s(t[0])]:Object.entries(t).reduce((n,[e,o])=>(n[e]=s(o),n),{})}var l=class{buffer="";total="";schema={childrenMap:{},componentMap:{},root:null};addDelta(n){this.total+=n,this.buffer+=n;let e=this.buffer.split(`
|
|
10
|
+
`);return e.length>1?(e.slice(0,e.length-1).forEach(o=>this.handleLine(o)),this.buffer=e[e.length-1],!0):!1}handleLine(n){try{let e=JSON.parse(n),{childrenMap:o,componentMap:a}=this.schema;a[e.id]=e,e.parentId===null?this.schema.root=e:(o[e.parentId]||(o[e.parentId]=[]),o[e.parentId].push(e.id))}catch{}}finish(){this.handleLine(this.buffer),this.buffer=""}};export{l as ResponseParser,u as constructInput,d as constructRerenderContext,s as createSkeleton,h as generateComponentMap};
|
|
9
11
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util.ts","../src/ResponseParser.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/util.ts","../src/ResponseParser.ts"],"sourcesContent":["import { GeneratedUIProps } from \"./client\";\r\nimport { ComponentMetadata, SyntuxComponent } from \"./types\";\r\n\r\n/**\r\n * Converts a list of components into a dictionary for fast-retrieval\r\n * during rendering.\r\n */\r\nexport function generateComponentMap(allowedComponents: (SyntuxComponent | string)[]) {\r\n return allowedComponents.reduce((acc: Record<string, React.ComponentType<any> | string>, curr: SyntuxComponent | string) => {\r\n if (typeof curr === \"string\") {\r\n acc[curr] = curr;\r\n return acc;\r\n }\r\n\r\n acc[curr.name] = curr.component;\r\n return acc;\r\n }, {})\r\n}\r\n\r\n/**\r\n * Creates LLM input in accordance to the spec.\r\n */\r\nexport function constructInput({\r\n value, skeletonize = false, components, hint\r\n}: {\r\n value: any;\r\n components?: (ComponentMetadata | string)[];\r\n hint?: string;\r\n skeletonize?: boolean;\r\n}) {\r\n const allowedComponents = components?.map((item: ComponentMetadata | string) => {\r\n if (typeof item === \"string\") return item;\r\n return item.name;\r\n }).join(',') || \"\"\r\n\r\n const customComponents = components?.filter((item): item is ComponentMetadata => typeof item !== \"string\");\r\n const componentContext = customComponents?.map((item) => {\r\n if (!item.context) {\r\n return `${item.name} [props: ${item.props}]`\r\n } else {\r\n return `${item.name} [props: ${item.props}, details: ${item.context}]`\r\n }\r\n }).join(',') || \"\"\r\n\r\n const inputValue = JSON.stringify(skeletonize ? createSkeleton(value) : value)\r\n\r\n return `<AllowedComponents>${allowedComponents}</AllowedComponents>\\n<ComponentContext>${componentContext}</ComponentContext>\\n<UserContext>${hint || \"\"}</UserContext>\\n<IsSkeleton>${skeletonize.toString()}</IsSkeleton>\\n<Value>\\n${inputValue}\\n</Value>`\r\n}\r\n\r\n/**\r\n * Builds the AllowedComponents + ComponentContext header used as context for rerender requests.\r\n */\r\nexport function constructRerenderContext(props: GeneratedUIProps) {\r\n return constructInput(props).split('\\n').slice(0, 2).join('\\n');\r\n}\r\n\r\n/**\r\n * generates a skeleton of the input value, ideal for large arrays or untrusted input.\r\n * see the FAQ for more information: https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays.\r\n *\r\n * *important*: assumes arrays are non-polymorphic\r\n */\r\nexport function createSkeleton(input: any) {\r\n if (input === null) return \"null\";\r\n\r\n if (typeof input !== \"object\") return typeof input;\r\n\r\n if (Array.isArray(input)) {\r\n if (input.length == 0) {\r\n return \"null\"; // ignore this field completely\r\n } else {\r\n return [createSkeleton(input[0])]\r\n }\r\n }\r\n return Object.entries(input).reduce((acc, [key, value]) => {\r\n acc[key] = createSkeleton(value);\r\n return acc;\r\n }, {})\r\n}\r\n","import { SchemaNode, UISchema } from \"./types\";\r\n\r\n/**\r\n * Utility class for parsing UISchema from stream.\r\n */\r\nexport class ResponseParser {\r\n buffer = \"\"; // unflushed existing deltas w/o newline\r\n total = \"\"; // accumulator\r\n\r\n // schema assembled thus far\r\n schema: UISchema = {\r\n childrenMap: {},\r\n componentMap: {},\r\n root: null\r\n }\r\n\r\n /**\r\n * Update schema with latest data chunk.\r\n * \r\n * Handles multiline input gracefully; can be used to load entire schemas from cache.\r\n * \r\n * @param delta delta from stream.\r\n * @returns true if update is warranted, false otherwise.\r\n */\r\n addDelta(delta: string) {\r\n this.total += delta;\r\n this.buffer += delta;\r\n const split = this.buffer.split(\"\\n\")\r\n if (split.length > 1) {\r\n split.slice(0, split.length - 1).forEach((line) => this.handleLine(line));\r\n this.buffer = split[split.length - 1];\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Parses a single line (full JSON object) and updates schema.\r\n * Generally should not be used when streaming data.\r\n */\r\n handleLine(line: string) {\r\n try {\r\n const node: SchemaNode = JSON.parse(line);\r\n\r\n const { childrenMap, componentMap } = this.schema;\r\n\r\n componentMap[node.id] = node;\r\n if (node.parentId === null) {\r\n this.schema.root = node;\r\n } else {\r\n if (!childrenMap[node.parentId]) childrenMap[node.parentId] = []\r\n childrenMap[node.parentId].push(node.id)\r\n }\r\n } catch (err) { /* probably markdown or generation inconsistency */ }\r\n }\r\n\r\n /**\r\n * Clears the buffer and handles any remaining information within.\r\n */\r\n finish(){\r\n this.handleLine(this.buffer);\r\n this.buffer = \"\";\r\n }\r\n}"],"mappings":"AAOO,SAASA,EAAqBC,EAAiD,CAClF,OAAOA,EAAkB,OAAO,CAACC,EAAwDC,IACjF,OAAOA,GAAS,UAChBD,EAAIC,CAAI,EAAIA,EACLD,IAGXA,EAAIC,EAAK,IAAI,EAAIA,EAAK,UACfD,GACR,CAAC,CAAC,CACT,CAKO,SAASE,EAAe,CAC3B,MAAAC,EAAO,YAAAC,EAAc,GAAO,WAAAC,EAAY,KAAAC,CAC5C,EAKG,CACC,IAAMP,GAAoBM,GAAA,YAAAA,EAAY,IAAKE,GACnC,OAAOA,GAAS,SAAiBA,EAC9BA,EAAK,MACb,KAAK,OAAQ,GAEVC,EAAmBH,GAAA,YAAAA,EAAY,OAAQE,GAAoC,OAAOA,GAAS,UAC3FE,GAAmBD,GAAA,YAAAA,EAAkB,IAAKD,GACvCA,EAAK,QAGC,GAAGA,EAAK,IAAI,YAAYA,EAAK,KAAK,cAAcA,EAAK,OAAO,IAF5D,GAAGA,EAAK,IAAI,YAAYA,EAAK,KAAK,KAI9C,KAAK,OAAQ,GAEVG,EAAa,KAAK,UAAUN,EAAcO,EAAeR,CAAK,EAAIA,CAAK,EAE7E,MAAO,sBAAsBJ,CAAiB;AAAA,oBAA2CU,CAAgB;AAAA,eAAqCH,GAAQ,EAAE;AAAA,cAA+BF,EAAY,SAAS,CAAC;AAAA;AAAA,EAA2BM,CAAU;AAAA,SACtP,CAKO,SAASE,EAAyBC,EAAyB,CAC9D,OAAOX,EAAeW,CAAK,EAAE,MAAM;AAAA,CAAI,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK;AAAA,CAAI,CAClE,CAQO,SAASF,EAAeG,EAAY,CACvC,OAAIA,IAAU,KAAa,OAEvB,OAAOA,GAAU,SAAiB,OAAOA,EAEzC,MAAM,QAAQA,CAAK,EACfA,EAAM,QAAU,EACT,OAEA,CAACH,EAAeG,EAAM,CAAC,CAAC,CAAC,EAGjC,OAAO,QAAQA,CAAK,EAAE,OAAO,CAACd,EAAK,CAACe,EAAKZ,CAAK,KACjDH,EAAIe,CAAG,EAAIJ,EAAeR,CAAK,EACxBH,GACR,CAAC,CAAC,CACT,CCzEO,IAAMgB,EAAN,KAAqB,CACxB,OAAS,GACT,MAAQ,GAGR,OAAmB,CACf,YAAa,CAAC,EACd,aAAc,CAAC,EACf,KAAM,IACV,EAUA,SAASC,EAAe,CACpB,KAAK,OAASA,EACd,KAAK,QAAUA,EACf,IAAMC,EAAQ,KAAK,OAAO,MAAM;AAAA,CAAI,EACpC,OAAIA,EAAM,OAAS,GACfA,EAAM,MAAM,EAAGA,EAAM,OAAS,CAAC,EAAE,QAASC,GAAS,KAAK,WAAWA,CAAI,CAAC,EACxE,KAAK,OAASD,EAAMA,EAAM,OAAS,CAAC,EAC7B,IAEJ,EACX,CAMA,WAAWC,EAAc,CACrB,GAAI,CACA,IAAMC,EAAmB,KAAK,MAAMD,CAAI,EAElC,CAAE,YAAAE,EAAa,aAAAC,CAAa,EAAI,KAAK,OAE3CA,EAAaF,EAAK,EAAE,EAAIA,EACpBA,EAAK,WAAa,KAClB,KAAK,OAAO,KAAOA,GAEdC,EAAYD,EAAK,QAAQ,IAAGC,EAAYD,EAAK,QAAQ,EAAI,CAAC,GAC/DC,EAAYD,EAAK,QAAQ,EAAE,KAAKA,EAAK,EAAE,EAE/C,MAAc,CAAsD,CACxE,CAKA,QAAQ,CACJ,KAAK,WAAW,KAAK,MAAM,EAC3B,KAAK,OAAS,EAClB,CACJ","names":["generateComponentMap","allowedComponents","acc","curr","constructInput","value","skeletonize","components","hint","item","customComponents","componentContext","inputValue","createSkeleton","constructRerenderContext","props","input","key","ResponseParser","delta","split","line","node","childrenMap","componentMap"]}
|
package/dist/metafile-esm.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"src/types.ts":{"bytes":
|
|
1
|
+
{"inputs":{"src/types.ts":{"bytes":1308,"imports":[],"format":"esm"},"src/util.ts":{"bytes":2873,"imports":[{"path":"./client","kind":"import-statement","external":true},{"path":"./types","kind":"import-statement","external":true}],"format":"esm"},"src/ResponseParser.ts":{"bytes":1923,"imports":[{"path":"./types","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":84,"imports":[{"path":"src/types.ts","kind":"import-statement","original":"./types"},{"path":"src/util.ts","kind":"import-statement","original":"./util"},{"path":"src/ResponseParser.ts","kind":"import-statement","original":"./ResponseParser"}],"format":"esm"},"src/client/Renderer.tsx":{"bytes":5020,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"../types","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true}],"format":"esm"},"src/client/SyntuxContext.tsx":{"bytes":481,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/types","kind":"import-statement","external":true}],"format":"esm"},"src/client/GeneratedClient.tsx":{"bytes":4814,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"../types","kind":"import-statement","external":true},{"path":"src/ResponseParser.ts","kind":"import-statement","original":"../ResponseParser"},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./Renderer"},{"path":"src/client/SyntuxContext.tsx","kind":"import-statement","original":"./SyntuxContext"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/client/GeneratedUI.tsx":{"bytes":3547,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/ResponseParser.ts","kind":"import-statement","original":"../ResponseParser"},{"path":"../types","kind":"import-statement","external":true},{"path":"src/util.ts","kind":"import-statement","original":"../util"},{"path":"src/client/GeneratedClient.tsx","kind":"import-statement","original":"./GeneratedClient"},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./Renderer"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/client.ts":{"bytes":172,"imports":[{"path":"src/client/GeneratedUI.tsx","kind":"import-statement","original":"./client/GeneratedUI"},{"path":"src/client/GeneratedClient.tsx","kind":"import-statement","original":"./client/GeneratedClient"},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./client/Renderer"},{"path":"src/client/SyntuxContext.tsx","kind":"import-statement","original":"./client/SyntuxContext"}],"format":"esm"},"src/server/index.ts":{"bytes":2077,"imports":[{"path":"ai","kind":"import-statement","external":true},{"path":"src/util.ts","kind":"import-statement","original":"../util"}],"format":"esm"},"src/bin/cli_util.mjs":{"bytes":115,"imports":[{"path":"chalk","kind":"import-statement","external":true}],"format":"esm"},"src/bin/commands/init.mjs":{"bytes":3700,"imports":[{"path":"commander","kind":"import-statement","external":true},{"path":"fs-extra","kind":"import-statement","external":true},{"path":"path","kind":"import-statement","external":true},{"path":"child_process","kind":"import-statement","external":true},{"path":"prompts","kind":"import-statement","external":true},{"path":"chalk","kind":"import-statement","external":true},{"path":"src/bin/cli_util.mjs","kind":"import-statement","original":"../cli_util.mjs"},{"path":"url","kind":"import-statement","external":true}],"format":"esm"},"src/bin/commands/generate.mjs":{"bytes":2933,"imports":[{"path":"commander","kind":"import-statement","external":true},{"path":"path","kind":"import-statement","external":true},{"path":"react-docgen-typescript","kind":"import-statement","external":true},{"path":"prompts","kind":"import-statement","external":true},{"path":"chalk","kind":"import-statement","external":true},{"path":"fs-extra","kind":"import-statement","external":true},{"path":"src/bin/cli_util.mjs","kind":"import-statement","original":"../cli_util.mjs"}],"format":"esm"},"src/bin/cli.mjs":{"bytes":482,"imports":[{"path":"commander","kind":"import-statement","external":true},{"path":"src/bin/commands/init.mjs","kind":"import-statement","original":"./commands/init.mjs"},{"path":"src/bin/commands/generate.mjs","kind":"import-statement","original":"./commands/generate.mjs"}],"format":"esm"}},"outputs":{"dist/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":7402},"dist/index.mjs":{"imports":[],"exports":["ResponseParser","constructInput","constructRerenderContext","createSkeleton","generateComponentMap"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":0},"src/util.ts":{"bytesInOutput":863},"src/ResponseParser.ts":{"bytesInOutput":508}},"bytes":1496},"dist/client.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":29788},"dist/client.mjs":{"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"exports":["GeneratedClient","GeneratedUI","Renderer","SyntuxContext","useSyntux"],"entryPoint":"src/client.ts","inputs":{"src/ResponseParser.ts":{"bytesInOutput":508},"src/util.ts":{"bytesInOutput":863},"src/client/GeneratedClient.tsx":{"bytesInOutput":1522},"src/client/Renderer.tsx":{"bytesInOutput":1870},"src/client/SyntuxContext.tsx":{"bytesInOutput":176},"src/client/GeneratedUI.tsx":{"bytesInOutput":716},"src/client.ts":{"bytesInOutput":0}},"bytes":5765},"dist/server.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":7351},"dist/server.mjs":{"imports":[{"path":"ai","kind":"import-statement","external":true}],"exports":["createSyntuxHandler","createSyntuxRerenderHandler"],"entryPoint":"src/server/index.ts","inputs":{"src/server/index.ts":{"bytesInOutput":524},"src/util.ts":{"bytesInOutput":708}},"bytes":1299},"dist/bin/cli.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":12417},"dist/bin/cli.mjs":{"imports":[{"path":"commander","kind":"import-statement","external":true},{"path":"commander","kind":"import-statement","external":true},{"path":"fs-extra","kind":"import-statement","external":true},{"path":"path","kind":"import-statement","external":true},{"path":"child_process","kind":"import-statement","external":true},{"path":"prompts","kind":"import-statement","external":true},{"path":"chalk","kind":"import-statement","external":true},{"path":"chalk","kind":"import-statement","external":true},{"path":"url","kind":"import-statement","external":true},{"path":"commander","kind":"import-statement","external":true},{"path":"path","kind":"import-statement","external":true},{"path":"react-docgen-typescript","kind":"import-statement","external":true},{"path":"prompts","kind":"import-statement","external":true},{"path":"chalk","kind":"import-statement","external":true},{"path":"fs-extra","kind":"import-statement","external":true}],"exports":[],"entryPoint":"src/bin/cli.mjs","inputs":{"src/bin/cli.mjs":{"bytesInOutput":250},"src/bin/commands/init.mjs":{"bytesInOutput":1851},"src/bin/cli_util.mjs":{"bytesInOutput":78},"src/bin/commands/generate.mjs":{"bytesInOutput":1443}},"bytes":3643}}}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LanguageModel } from 'ai';
|
|
2
|
+
|
|
3
|
+
interface SyntuxHandlerOptions {
|
|
4
|
+
model: LanguageModel;
|
|
5
|
+
spec: string;
|
|
6
|
+
onGenerate?: (schema: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* HTTP handler for UI generation requests. Framework-agnostic.
|
|
10
|
+
*
|
|
11
|
+
* Important: does not support rerendering. Use `createSyntuxRerenderHandler`.
|
|
12
|
+
*
|
|
13
|
+
* @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming.
|
|
14
|
+
* @param spec The model specification to use.
|
|
15
|
+
* @param onGenerate Schema generation callback, used for caching.
|
|
16
|
+
*/
|
|
17
|
+
declare function createSyntuxHandler(options: SyntuxHandlerOptions): (request: Request) => Promise<Response>;
|
|
18
|
+
/**
|
|
19
|
+
* HTTP handler for UI rerender requests. Framework-agnostic.
|
|
20
|
+
*
|
|
21
|
+
* @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming.
|
|
22
|
+
* @param spec The model specification to use.
|
|
23
|
+
* @param onGenerate Schema generation callback, used for caching.
|
|
24
|
+
*/
|
|
25
|
+
declare function createSyntuxRerenderHandler(options: SyntuxHandlerOptions): (request: Request) => Promise<Response>;
|
|
26
|
+
|
|
27
|
+
export { type SyntuxHandlerOptions, createSyntuxHandler, createSyntuxRerenderHandler };
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{streamText as d}from"ai";function u({value:e,skeletonize:r=!1,components:n,hint:o}){let a=(n==null?void 0:n.map(t=>typeof t=="string"?t:t.name).join(","))||"",s=n==null?void 0:n.filter(t=>typeof t!="string"),i=(s==null?void 0:s.map(t=>t.context?`${t.name} [props: ${t.props}, details: ${t.context}]`:`${t.name} [props: ${t.props}]`).join(","))||"",x=JSON.stringify(r?p(e):e);return`<AllowedComponents>${a}</AllowedComponents>
|
|
2
|
+
<ComponentContext>${i}</ComponentContext>
|
|
3
|
+
<UserContext>${o||""}</UserContext>
|
|
4
|
+
<IsSkeleton>${r.toString()}</IsSkeleton>
|
|
5
|
+
<Value>
|
|
6
|
+
${x}
|
|
7
|
+
</Value>`}function p(e){return e===null?"null":typeof e!="object"?typeof e:Array.isArray(e)?e.length==0?"null":[p(e[0])]:Object.entries(e).reduce((r,[n,o])=>(r[n]=p(o),r),{})}function y(e){return async r=>{let{value:n,hint:o,components:a,skeletonize:s}=await r.json(),i=u({value:n,hint:o,components:a,skeletonize:s});return l(e.model,e.spec,i,e.onGenerate)}}function C(e){return async r=>{let{context:n,existing:o,hint:a}=await r.json(),s=`${n}
|
|
8
|
+
<Existing>${o}</Existing>
|
|
9
|
+
<UserContext>${a}</UserContext>`;return l(e.model,e.spec,s,e.onGenerate)}}function l(e,r,n,o){return d({model:e,system:r,prompt:n,onFinish:({text:s})=>o==null?void 0:o(s)}).toTextStreamResponse()}export{y as createSyntuxHandler,C as createSyntuxRerenderHandler};
|
|
10
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/index.ts","../src/util.ts"],"sourcesContent":["import { LanguageModel, streamText } from 'ai';\nimport { constructInput } from '../util';\nexport interface SyntuxHandlerOptions {\n model: LanguageModel;\n spec: string;\n onGenerate?: (schema: string) => void;\n}\n\n/**\n * HTTP handler for UI generation requests. Framework-agnostic.\n * \n * Important: does not support rerendering. Use `createSyntuxRerenderHandler`.\n * \n * @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming.\n * @param spec The model specification to use.\n * @param onGenerate Schema generation callback, used for caching.\n */\nexport function createSyntuxHandler(options: SyntuxHandlerOptions) {\n return async (request: Request): Promise<Response> => {\n const { value, hint, components, skeletonize } = await request.json();\n const prompt = constructInput({ value, hint, components, skeletonize });\n\n return makeStreamResponse(options.model, options.spec, prompt, options.onGenerate);\n };\n}\n\n/**\n * HTTP handler for UI rerender requests. Framework-agnostic.\n *\n * @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming.\n * @param spec The model specification to use.\n * @param onGenerate Schema generation callback, used for caching.\n */\nexport function createSyntuxRerenderHandler(options: SyntuxHandlerOptions) {\n return async (request: Request): Promise<Response> => {\n const { context, existing, hint } = await request.json();\n const prompt = `${context}\\n<Existing>${existing}</Existing>\\n<UserContext>${hint}</UserContext>`;\n return makeStreamResponse(options.model, options.spec, prompt, options.onGenerate);\n };\n}\n\n\n/**\n * utility function for creating responses using the Stream API.\n */\nfunction makeStreamResponse(\n model: LanguageModel,\n spec: string,\n prompt: string,\n onGenerate?: (s: string) => void,\n): Response {\n const result = streamText({\n model,\n system: spec,\n prompt,\n onFinish: ({ text }) => onGenerate?.(text),\n });\n return result.toTextStreamResponse();\n}\n\n","import { GeneratedUIProps } from \"./client\";\r\nimport { ComponentMetadata, SyntuxComponent } from \"./types\";\r\n\r\n/**\r\n * Converts a list of components into a dictionary for fast-retrieval\r\n * during rendering.\r\n */\r\nexport function generateComponentMap(allowedComponents: (SyntuxComponent | string)[]) {\r\n return allowedComponents.reduce((acc: Record<string, React.ComponentType<any> | string>, curr: SyntuxComponent | string) => {\r\n if (typeof curr === \"string\") {\r\n acc[curr] = curr;\r\n return acc;\r\n }\r\n\r\n acc[curr.name] = curr.component;\r\n return acc;\r\n }, {})\r\n}\r\n\r\n/**\r\n * Creates LLM input in accordance to the spec.\r\n */\r\nexport function constructInput({\r\n value, skeletonize = false, components, hint\r\n}: {\r\n value: any;\r\n components?: (ComponentMetadata | string)[];\r\n hint?: string;\r\n skeletonize?: boolean;\r\n}) {\r\n const allowedComponents = components?.map((item: ComponentMetadata | string) => {\r\n if (typeof item === \"string\") return item;\r\n return item.name;\r\n }).join(',') || \"\"\r\n\r\n const customComponents = components?.filter((item): item is ComponentMetadata => typeof item !== \"string\");\r\n const componentContext = customComponents?.map((item) => {\r\n if (!item.context) {\r\n return `${item.name} [props: ${item.props}]`\r\n } else {\r\n return `${item.name} [props: ${item.props}, details: ${item.context}]`\r\n }\r\n }).join(',') || \"\"\r\n\r\n const inputValue = JSON.stringify(skeletonize ? createSkeleton(value) : value)\r\n\r\n return `<AllowedComponents>${allowedComponents}</AllowedComponents>\\n<ComponentContext>${componentContext}</ComponentContext>\\n<UserContext>${hint || \"\"}</UserContext>\\n<IsSkeleton>${skeletonize.toString()}</IsSkeleton>\\n<Value>\\n${inputValue}\\n</Value>`\r\n}\r\n\r\n/**\r\n * Builds the AllowedComponents + ComponentContext header used as context for rerender requests.\r\n */\r\nexport function constructRerenderContext(props: GeneratedUIProps) {\r\n return constructInput(props).split('\\n').slice(0, 2).join('\\n');\r\n}\r\n\r\n/**\r\n * generates a skeleton of the input value, ideal for large arrays or untrusted input.\r\n * see the FAQ for more information: https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays.\r\n *\r\n * *important*: assumes arrays are non-polymorphic\r\n */\r\nexport function createSkeleton(input: any) {\r\n if (input === null) return \"null\";\r\n\r\n if (typeof input !== \"object\") return typeof input;\r\n\r\n if (Array.isArray(input)) {\r\n if (input.length == 0) {\r\n return \"null\"; // ignore this field completely\r\n } else {\r\n return [createSkeleton(input[0])]\r\n }\r\n }\r\n return Object.entries(input).reduce((acc, [key, value]) => {\r\n acc[key] = createSkeleton(value);\r\n return acc;\r\n }, {})\r\n}\r\n"],"mappings":"AAAA,OAAwB,cAAAA,MAAkB,KCsBnC,SAASC,EAAe,CAC3B,MAAAC,EAAO,YAAAC,EAAc,GAAO,WAAAC,EAAY,KAAAC,CAC5C,EAKG,CACC,IAAMC,GAAoBF,GAAA,YAAAA,EAAY,IAAKG,GACnC,OAAOA,GAAS,SAAiBA,EAC9BA,EAAK,MACb,KAAK,OAAQ,GAEVC,EAAmBJ,GAAA,YAAAA,EAAY,OAAQG,GAAoC,OAAOA,GAAS,UAC3FE,GAAmBD,GAAA,YAAAA,EAAkB,IAAKD,GACvCA,EAAK,QAGC,GAAGA,EAAK,IAAI,YAAYA,EAAK,KAAK,cAAcA,EAAK,OAAO,IAF5D,GAAGA,EAAK,IAAI,YAAYA,EAAK,KAAK,KAI9C,KAAK,OAAQ,GAEVG,EAAa,KAAK,UAAUP,EAAcQ,EAAeT,CAAK,EAAIA,CAAK,EAE7E,MAAO,sBAAsBI,CAAiB;AAAA,oBAA2CG,CAAgB;AAAA,eAAqCJ,GAAQ,EAAE;AAAA,cAA+BF,EAAY,SAAS,CAAC;AAAA;AAAA,EAA2BO,CAAU;AAAA,SACtP,CAeO,SAASE,EAAeC,EAAY,CACvC,OAAIA,IAAU,KAAa,OAEvB,OAAOA,GAAU,SAAiB,OAAOA,EAEzC,MAAM,QAAQA,CAAK,EACfA,EAAM,QAAU,EACT,OAEA,CAACD,EAAeC,EAAM,CAAC,CAAC,CAAC,EAGjC,OAAO,QAAQA,CAAK,EAAE,OAAO,CAACC,EAAK,CAACC,EAAKC,CAAK,KACjDF,EAAIC,CAAG,EAAIH,EAAeI,CAAK,EACxBF,GACR,CAAC,CAAC,CACT,CD7DO,SAASG,EAAoBC,EAA+B,CAC/D,MAAO,OAAOC,GAAwC,CAClD,GAAM,CAAE,MAAAC,EAAO,KAAAC,EAAM,WAAAC,EAAY,YAAAC,CAAY,EAAI,MAAMJ,EAAQ,KAAK,EAC9DK,EAASC,EAAe,CAAE,MAAAL,EAAO,KAAAC,EAAM,WAAAC,EAAY,YAAAC,CAAY,CAAC,EAEtE,OAAOG,EAAmBR,EAAQ,MAAOA,EAAQ,KAAMM,EAAQN,EAAQ,UAAU,CACrF,CACJ,CASO,SAASS,EAA4BT,EAA+B,CACvE,MAAO,OAAOC,GAAwC,CAClD,GAAM,CAAE,QAAAS,EAAS,SAAAC,EAAU,KAAAR,CAAK,EAAI,MAAMF,EAAQ,KAAK,EACjDK,EAAS,GAAGI,CAAO;AAAA,YAAeC,CAAQ;AAAA,eAA6BR,CAAI,iBACjF,OAAOK,EAAmBR,EAAQ,MAAOA,EAAQ,KAAMM,EAAQN,EAAQ,UAAU,CACrF,CACJ,CAMA,SAASQ,EACLI,EACAC,EACAP,EACAQ,EACQ,CAOR,OANeC,EAAW,CACtB,MAAAH,EACA,OAAQC,EACR,OAAAP,EACA,SAAU,CAAC,CAAE,KAAAU,CAAK,IAAMF,GAAA,YAAAA,EAAaE,EACzC,CAAC,EACa,qBAAqB,CACvC","names":["streamText","constructInput","value","skeletonize","components","hint","allowedComponents","item","customComponents","componentContext","inputValue","createSkeleton","createSkeleton","input","acc","key","value","createSyntuxHandler","options","request","value","hint","components","skeletonize","prompt","constructInput","makeStreamResponse","createSyntuxRerenderHandler","context","existing","model","spec","onGenerate","streamText","text"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getsyntux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Build generative UIs for the web.",
|
|
5
|
+
"keywords": ["generative-ui"],
|
|
5
6
|
"exports": {
|
|
6
7
|
".": {
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
@@ -12,6 +13,11 @@
|
|
|
12
13
|
"types": "./dist/client.d.ts",
|
|
13
14
|
"import": "./dist/client.mjs",
|
|
14
15
|
"require": "./dist/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./server": {
|
|
18
|
+
"types": "./dist/server.d.ts",
|
|
19
|
+
"import": "./dist/server.mjs",
|
|
20
|
+
"require": "./dist/server.js"
|
|
15
21
|
}
|
|
16
22
|
},
|
|
17
23
|
"bin": {
|
|
@@ -28,7 +34,6 @@
|
|
|
28
34
|
},
|
|
29
35
|
"homepage": "https://github.com/puffinsoft/syntux#readme",
|
|
30
36
|
"dependencies": {
|
|
31
|
-
"@ai-sdk/rsc": "^2.0.3",
|
|
32
37
|
"@types/node": "^25.0.2",
|
|
33
38
|
"@types/react": "^19.2.7",
|
|
34
39
|
"commander": "^14.0.2",
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { JSX } from 'react';
|
|
2
|
-
|
|
3
|
-
import { createStreamableValue, StreamableValue } from '@ai-sdk/rsc';
|
|
4
|
-
import { LanguageModel, streamText } from 'ai';
|
|
5
|
-
|
|
6
|
-
import { AnimateOptions, constructInput, generateComponentMap, ResponseParser, SyntuxComponent, UISchema } from "getsyntux";
|
|
7
|
-
import { GeneratedClient, Renderer } from 'getsyntux/client';
|
|
8
|
-
|
|
9
|
-
import spec from './spec';
|
|
10
|
-
|
|
11
|
-
export interface GeneratedContentProps {
|
|
12
|
-
value: any;
|
|
13
|
-
model: LanguageModel;
|
|
14
|
-
components?: (SyntuxComponent | string)[];
|
|
15
|
-
hint?: string;
|
|
16
|
-
placeholder?: JSX.Element;
|
|
17
|
-
cached?: string;
|
|
18
|
-
/*
|
|
19
|
-
* ^ this is a string for two reasons:
|
|
20
|
-
* - it is easier to store
|
|
21
|
-
* - it is parsed then mutated at runtime. This avoids unintended side effects.
|
|
22
|
-
*/
|
|
23
|
-
onGenerate?: (arg0: string) => void;
|
|
24
|
-
skeletonize?: boolean;
|
|
25
|
-
|
|
26
|
-
onError?: (arg0: any) => void;
|
|
27
|
-
errorFallback?: JSX.Element;
|
|
28
|
-
animate?: AnimateOptions
|
|
29
|
-
|
|
30
|
-
rerender?: (arg0: string, arg1: string, arg2: string) => Promise<{ value: StreamableValue }>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Section of user interface for LLM to generate.
|
|
35
|
-
* @param values The values (object, primitive, or array) to be displayed.
|
|
36
|
-
* @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming
|
|
37
|
-
* @param components List of allowed components that LLM can use.
|
|
38
|
-
* @param hint Additional custom instructions for the LLM.
|
|
39
|
-
* @param placeholder A placeholder to show while awaiting streaming (NOT during streaming)
|
|
40
|
-
* @param cached Schema returned from onGenerate, used for caching UI
|
|
41
|
-
* @param onGenerate Callback which accepts a string, to be passed to `cached` to reuse same UI
|
|
42
|
-
*/
|
|
43
|
-
export async function GeneratedUI(props: GeneratedContentProps) {
|
|
44
|
-
const input = constructInput(props);
|
|
45
|
-
const rerenderContext = input.split('\n').slice(0, 2);
|
|
46
|
-
|
|
47
|
-
const { value, model, components, placeholder, cached, onGenerate, rerender } = props;
|
|
48
|
-
|
|
49
|
-
const allowedComponents = generateComponentMap(components || []);
|
|
50
|
-
|
|
51
|
-
// prerender if cached
|
|
52
|
-
if(cached){
|
|
53
|
-
const parser = new ResponseParser();
|
|
54
|
-
parser.addDelta(cached);
|
|
55
|
-
parser.finish();
|
|
56
|
-
|
|
57
|
-
const schema: UISchema = parser.schema;
|
|
58
|
-
|
|
59
|
-
if(schema.root){
|
|
60
|
-
return <Renderer id={schema.root.id} componentMap={schema.componentMap} childrenMap={schema.childrenMap}
|
|
61
|
-
allowedComponents={allowedComponents} global={value} local={value} />
|
|
62
|
-
} else {
|
|
63
|
-
return <></>;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const stream = createStreamableValue('');
|
|
68
|
-
|
|
69
|
-
(async () => {
|
|
70
|
-
let total = "";
|
|
71
|
-
|
|
72
|
-
const { textStream } = await streamText({
|
|
73
|
-
model,
|
|
74
|
-
system: spec,
|
|
75
|
-
prompt: input
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
for await(const delta of textStream){
|
|
79
|
-
stream.update(delta);
|
|
80
|
-
total += delta;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
stream.done();
|
|
84
|
-
|
|
85
|
-
if(onGenerate) onGenerate(total);
|
|
86
|
-
})()
|
|
87
|
-
|
|
88
|
-
return <GeneratedClient value={value} allowedComponents={allowedComponents} inputStream={stream.value} placeholder={placeholder} rerender={{
|
|
89
|
-
context: rerenderContext.join('\n'),
|
|
90
|
-
action: rerender
|
|
91
|
-
}} />
|
|
92
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* this file is optional!
|
|
5
|
-
* you can delete it if you do not need rerender functionality.
|
|
6
|
-
*
|
|
7
|
-
* run "npm i @ai-sdk/anthropic" if you want to use Claude.
|
|
8
|
-
*/
|
|
9
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
10
|
-
import { createStreamableValue } from "@ai-sdk/rsc";
|
|
11
|
-
import { streamText } from "ai";
|
|
12
|
-
|
|
13
|
-
import spec from "./spec";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* IMPORTANT: replace the below with your own model.
|
|
17
|
-
*/
|
|
18
|
-
const anthropic = createAnthropic({
|
|
19
|
-
apiKey: "..."
|
|
20
|
-
})
|
|
21
|
-
const model = anthropic("claude-haiku-4-5");
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The server action for rerendering the UI.
|
|
25
|
-
* This is an UNSECURED ENDPOINT. Please modify it to perform your own authentication etc,.
|
|
26
|
-
*
|
|
27
|
-
* @param context information about allowed components, component context etc,.
|
|
28
|
-
* @param existing the current UI schema.
|
|
29
|
-
* @param hint the update request.
|
|
30
|
-
*/
|
|
31
|
-
export async function rerenderAction(context: string, existing: string, hint: string) {
|
|
32
|
-
const stream = createStreamableValue('');
|
|
33
|
-
(async () => {
|
|
34
|
-
let total = "";
|
|
35
|
-
let errored = false;
|
|
36
|
-
|
|
37
|
-
const { textStream } = await streamText({
|
|
38
|
-
model,
|
|
39
|
-
system: spec,
|
|
40
|
-
prompt: `${context}\n<Existing>${existing}</Existing>\n<UserContext>${hint}</UserContext>`,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
for await (const delta of textStream) {
|
|
44
|
-
stream.update(delta);
|
|
45
|
-
total += delta;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!errored) {
|
|
49
|
-
stream.done();
|
|
50
|
-
}
|
|
51
|
-
})()
|
|
52
|
-
|
|
53
|
-
return { value: stream.value }
|
|
54
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { StreamableValue } from '@ai-sdk/rsc';
|
|
2
|
-
|
|
3
|
-
type SchemaNode = {
|
|
4
|
-
id: string;
|
|
5
|
-
parentId: string | null;
|
|
6
|
-
type: string;
|
|
7
|
-
props?: Record<string, any>;
|
|
8
|
-
content?: any | {
|
|
9
|
-
"$bind": string;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
type ComponentMap = Record<string, SchemaNode>;
|
|
13
|
-
type ChildrenMap = Record<string, string[]>;
|
|
14
|
-
type UISchema = {
|
|
15
|
-
componentMap: ComponentMap;
|
|
16
|
-
childrenMap: ChildrenMap;
|
|
17
|
-
root: SchemaNode | null;
|
|
18
|
-
};
|
|
19
|
-
type SyntuxComponent = {
|
|
20
|
-
name: string;
|
|
21
|
-
props: string;
|
|
22
|
-
component: React.ComponentType<any>;
|
|
23
|
-
context?: string;
|
|
24
|
-
};
|
|
25
|
-
type AnimateOptions = {
|
|
26
|
-
offset: number;
|
|
27
|
-
duration: number;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* setValue will not send a request if regenerate is false.
|
|
31
|
-
* however, the value will still be updated (statically).
|
|
32
|
-
* as opposed to a falsy options existence check, this is more robust for DX.
|
|
33
|
-
*/
|
|
34
|
-
type RerenderOptions = {
|
|
35
|
-
regenerate: boolean;
|
|
36
|
-
hint: string;
|
|
37
|
-
};
|
|
38
|
-
type RerenderContext = {
|
|
39
|
-
context: string;
|
|
40
|
-
action?: (arg0: string, arg1: string, arg2: string) => Promise<{
|
|
41
|
-
value: StreamableValue;
|
|
42
|
-
}>;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type { AnimateOptions as A, ChildrenMap as C, RerenderContext as R, SyntuxComponent as S, UISchema as U, ComponentMap as a, RerenderOptions as b, SchemaNode as c };
|