getsyntux 0.7.2 → 0.9.0-alpha
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 +111 -76
- package/dist/GeneratedUI-DFOjlyg6.d.mts +87 -0
- package/dist/client.d.mts +10 -6
- package/dist/client.mjs +10 -2
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +17 -23
- 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/dist/templates/spec.md +1 -0
- package/dist/templates/spec.ts +12 -0
- package/package.json +7 -2
- package/dist/templates/GeneratedUI.tsx +0 -103
- package/dist/templates/RerenderHandler.tsx +0 -8
- package/dist/types-C9N85B_g.d.mts +0 -28
package/README.md
CHANGED
|
@@ -10,17 +10,16 @@ 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
|
|
|
21
19
|
- ⚡ **Streamable** - display UI as you generate.
|
|
22
20
|
- 🎨 **Custom Components** - use your own React components.
|
|
23
21
|
- 💾 **Cacheable** - reuse generated UIs with new values.
|
|
22
|
+
- 🔄 **Reactive** - update the UI programmatically.
|
|
24
23
|
|
|
25
24
|
**How does it work?** *syntux* generates a JSON-DSL to represent the UI, known as the React Interface Schema. The specifics are in the FAQ [below](#faq).
|
|
26
25
|
|
|
@@ -30,7 +29,7 @@ For instance, if you provide an array `value` with 10,000 items, it will cost yo
|
|
|
30
29
|
|
|
31
30
|
### API
|
|
32
31
|
|
|
33
|
-
<i>syntux</i> is built for React
|
|
32
|
+
<i>syntux</i> is built for React, supporting Next.js, React Router / Remix and Astro.
|
|
34
33
|
|
|
35
34
|
One component is all you need:
|
|
36
35
|
|
|
@@ -42,52 +41,42 @@ const valueToDisplay = {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
<GeneratedUI
|
|
45
|
-
|
|
44
|
+
endpoint="/api/syntux"
|
|
46
45
|
value={valueToDisplay}
|
|
47
46
|
hint="UI should look like..."
|
|
48
47
|
/>
|
|
49
48
|
```
|
|
50
49
|
|
|
51
|
-
*syntux*
|
|
50
|
+
*syntux* reads the `value` and designs the UI to best display it, taking the `hint` into consideration.
|
|
52
51
|
|
|
53
52
|
> [!TIP]
|
|
54
|
-
> If you are passing in a **large array** as a value,
|
|
55
|
-
|
|
56
|
-
### Installation
|
|
57
|
-
|
|
58
|
-
In the root of your project:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
$ npx getsyntux@latest
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
This will automatically install the required components in the `lib/getsyntux` folder.
|
|
65
|
-
|
|
66
|
-
We use the [Vercel AI SDK](https://github.com/vercel/ai) to provide support for all LLM providers. To install the model providers:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
$ npm i ai
|
|
70
|
-
$ npm i @ai-sdk/anthropic (if you're using Claude)
|
|
71
|
-
```
|
|
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).
|
|
72
54
|
|
|
73
55
|
---
|
|
74
56
|
|
|
75
57
|
### Examples
|
|
76
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
|
+
|
|
77
63
|
#### Basic Example
|
|
78
64
|
|
|
79
65
|
Generate a simple UI with a hint:
|
|
80
66
|
|
|
81
67
|
```jsx
|
|
82
|
-
import { GeneratedUI } from
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
68
|
+
import { GeneratedUI } from 'getsyntux/client';
|
|
69
|
+
|
|
70
|
+
export default function Page() {
|
|
71
|
+
const value = { username: 'John', email: 'john@gmail.com', age: 22 };
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<GeneratedUI
|
|
75
|
+
endpoint="/api/syntux"
|
|
76
|
+
value={value}
|
|
77
|
+
hint="display as a profile card"
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
91
80
|
}
|
|
92
81
|
```
|
|
93
82
|
|
|
@@ -96,13 +85,21 @@ export default function Home(){
|
|
|
96
85
|
Cache generated UI based on a user ID:
|
|
97
86
|
|
|
98
87
|
```jsx
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
88
|
+
import { cache } from './api/syntux/route';
|
|
89
|
+
|
|
90
|
+
export default function Page() {
|
|
91
|
+
const userId = 10;
|
|
92
|
+
const value = { username: 'John', email: 'john@gmail.com', age: 22 };
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<GeneratedUI
|
|
96
|
+
endpoint="/api/syntux"
|
|
97
|
+
value={value}
|
|
98
|
+
hint="UI should look like..."
|
|
99
|
+
|
|
100
|
+
cached={cache.get(userId)}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
106
103
|
}
|
|
107
104
|
```
|
|
108
105
|
|
|
@@ -111,14 +108,33 @@ export default function Home(){
|
|
|
111
108
|
Use your own components, or someone else's (a library):
|
|
112
109
|
|
|
113
110
|
```jsx
|
|
114
|
-
import {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
111
|
+
import { GeneratedUI } from 'getsyntux/client';
|
|
112
|
+
import { Card, Avatar } from '@/components/ui';
|
|
113
|
+
|
|
114
|
+
export default function Page() {
|
|
115
|
+
const value = { username: 'John', email: 'john@gmail.com', avatar: '/john.png' };
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<GeneratedUI
|
|
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
|
+
]}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
122
138
|
}
|
|
123
139
|
```
|
|
124
140
|
|
|
@@ -130,56 +146,75 @@ export default function Home(){
|
|
|
130
146
|
|
|
131
147
|
Make sure components are marked with `"use client"`.
|
|
132
148
|
|
|
133
|
-
####
|
|
149
|
+
#### Reactivity
|
|
134
150
|
|
|
135
|
-
|
|
151
|
+
To regenerate the UI dynamically, in response to user action, create a separate endpoint:
|
|
136
152
|
|
|
137
153
|
```jsx
|
|
138
|
-
|
|
154
|
+
<GeneratedUI
|
|
155
|
+
endpoint="/api/syntux"
|
|
156
|
+
rerenderEndpoint="/api/syntux/rerender" /* <-- over here */
|
|
157
|
+
|
|
158
|
+
value={value}
|
|
159
|
+
hint="display as a profile card"
|
|
160
|
+
/>
|
|
161
|
+
```
|
|
139
162
|
|
|
140
|
-
|
|
141
|
-
const { value, setValue } = useSyntux();
|
|
163
|
+
Then use the `useSyntux` hook:
|
|
142
164
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
```jsx
|
|
166
|
+
"use client";
|
|
167
|
+
|
|
168
|
+
export default function CustomComponent() {
|
|
169
|
+
const { value, setValue } = useSyntux();
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<button
|
|
173
|
+
onClick={() => {
|
|
174
|
+
setValue(value, {
|
|
175
|
+
regenerate: true, // if false, treated as static
|
|
176
|
+
hint: "Change the style to be more..."
|
|
177
|
+
})
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
Update UI!
|
|
181
|
+
</button>
|
|
182
|
+
);
|
|
148
183
|
}
|
|
149
184
|
```
|
|
185
|
+
<h3 align="center" margin="0"><a href="https://docs.getsyntux.com/">➡️ view documentation</a></h3>
|
|
186
|
+
|
|
150
187
|
|
|
151
188
|
---
|
|
152
189
|
|
|
153
190
|
### FAQ
|
|
154
191
|
|
|
155
|
-
<details>
|
|
156
|
-
<summary>How expensive is generation?</summary>
|
|
157
|
-
|
|
158
|
-
*syntux* is highly optimized to save tokens. See [here](https://docs.getsyntux.com/advanced#cost-estimation) for a cost estimation table and an explanation.
|
|
159
|
-
</details>
|
|
160
|
-
|
|
161
192
|
<details>
|
|
162
193
|
<summary>How does generation work? (Does it generate source code?)</summary>
|
|
163
194
|
|
|
164
|
-
Generated
|
|
195
|
+
Generated interfaces must be *secure*, *reusable* and *cacheable*.
|
|
165
196
|
|
|
166
|
-
As such,
|
|
197
|
+
As such, syntux does not:
|
|
198
|
+
- generate code (HTML/JSX), or...
|
|
199
|
+
- hardcode the `value`
|
|
167
200
|
|
|
168
|
-
|
|
201
|
+
Instead, syntux generates a JSON-DSL representation of the UI, known as the React Interface Schema (RIS).
|
|
169
202
|
|
|
170
|
-
|
|
171
|
-
</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.
|
|
172
204
|
|
|
173
|
-
|
|
174
|
-
<summary>How does caching work?</summary>
|
|
205
|
+
An example of the RIS:
|
|
175
206
|
|
|
176
|
-
|
|
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
|
+
```
|
|
177
211
|
|
|
178
|
-
|
|
212
|
+
</details>
|
|
179
213
|
|
|
180
|
-
|
|
214
|
+
<details>
|
|
215
|
+
<summary>How expensive is generation?</summary>
|
|
181
216
|
|
|
182
|
-
|
|
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.
|
|
183
218
|
</details>
|
|
184
219
|
|
|
185
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,23 @@
|
|
|
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, a as ComponentMap, C as ChildrenMap } from './types-C9N85B_g.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;
|
|
20
|
+
rerender: RerenderContext;
|
|
17
21
|
}): react_jsx_runtime.JSX.Element;
|
|
18
22
|
|
|
19
23
|
interface RendererProps {
|
|
@@ -32,7 +36,7 @@ declare function Renderer(props: RendererProps): react_jsx_runtime.JSX.Element;
|
|
|
32
36
|
|
|
33
37
|
type SyntuxContextType = {
|
|
34
38
|
value: any;
|
|
35
|
-
setValue: (
|
|
39
|
+
setValue: (value: any, options?: RerenderOptions) => void;
|
|
36
40
|
};
|
|
37
41
|
declare const SyntuxContext: react.Context<SyntuxContextType>;
|
|
38
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 } from 'src/types';\r\nimport { ResponseParser } from '../ResponseParser';\r\nimport { Renderer } from './Renderer';\r\nimport { SyntuxContext } from './SyntuxContext';\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}: {\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}) {\r\n const [statefulValue, setStatefulValue] = useState(value); // stateful because changeable through context\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 useEffect(() => {\r\n // forcibly create a new one for HMR\r\n parser.current = new ResponseParser();\r\n\r\n const parseStream = async () => {\r\n for await (const delta of readStreamableValue(inputStream)) {\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\r\n parseStream().then(() => {\r\n parser.current.finish();\r\n forceUpdate();\r\n }).catch(() => {\r\n setErrored(true)\r\n });\r\n }, [inputStream]);\r\n\r\n const schema = parser?.current?.schema;\r\n\r\n const providerValue = useMemo(() => ({ value: statefulValue, setValue: setStatefulValue }), [statefulValue]);\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} /> \r\n } else {\r\n return <>{placeholder}</>\r\n }\r\n }\r\n\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 \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.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\n\r\nexport type SyntuxContextType = {\r\n value: any,\r\n setValue: (arg0: any) => 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}"],"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","_a","statefulValue","setStatefulValue","useState","forceUpdate","useReducer","x","parser","useRef","errored","setErrored","useEffect","ResponseParser","delta","readStreamableValue","schema","providerValue","useMemo","renderContent","Renderer","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,33 +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 { LanguageModel } from 'ai';
|
|
6
|
-
import { SyntuxComponent, AnimateOptions } from 'getsyntux';
|
|
7
|
-
|
|
8
|
-
interface GeneratedContentProps {
|
|
9
|
-
value: any;
|
|
10
|
-
model: LanguageModel;
|
|
11
|
-
components?: (SyntuxComponent | string)[];
|
|
12
|
-
hint?: string;
|
|
13
|
-
placeholder?: JSX.Element;
|
|
14
|
-
cached?: string;
|
|
15
|
-
onGenerate?: (arg0: string) => void;
|
|
16
|
-
skeletonize?: boolean;
|
|
17
|
-
onError?: (arg0: any) => void;
|
|
18
|
-
errorFallback?: JSX.Element;
|
|
19
|
-
animate?: AnimateOptions;
|
|
20
|
-
}
|
|
4
|
+
import 'react/jsx-runtime';
|
|
21
5
|
|
|
22
6
|
/**
|
|
23
7
|
* Converts a list of components into a dictionary for fast-retrieval
|
|
24
8
|
* during rendering.
|
|
25
9
|
*/
|
|
26
|
-
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;
|
|
27
20
|
/**
|
|
28
|
-
*
|
|
21
|
+
* Builds the AllowedComponents + ComponentContext header used as context for rerender requests.
|
|
29
22
|
*/
|
|
30
|
-
declare function
|
|
23
|
+
declare function constructRerenderContext(props: GeneratedUIProps): string;
|
|
31
24
|
/**
|
|
32
25
|
* generates a skeleton of the input value, ideal for large arrays or untrusted input.
|
|
33
26
|
* see the FAQ for more information: https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays.
|
|
@@ -41,6 +34,7 @@ declare function createSkeleton(input: any): any;
|
|
|
41
34
|
*/
|
|
42
35
|
declare class ResponseParser {
|
|
43
36
|
buffer: string;
|
|
37
|
+
total: string;
|
|
44
38
|
schema: UISchema;
|
|
45
39
|
/**
|
|
46
40
|
* Update schema with latest data chunk.
|
|
@@ -62,4 +56,4 @@ declare class ResponseParser {
|
|
|
62
56
|
finish(): void;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
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/dist/templates/spec.md
CHANGED
package/dist/templates/spec.ts
CHANGED
|
@@ -78,6 +78,17 @@ Input:
|
|
|
78
78
|
Output:
|
|
79
79
|
{ ... }
|
|
80
80
|
... more lines
|
|
81
|
+
|
|
82
|
+
Or for an existing UI:
|
|
83
|
+
|
|
84
|
+
<AllowedComponents>...</AllowedComponents>
|
|
85
|
+
<ComponentContext>...</ComponentContext>
|
|
86
|
+
<UserContext>...</UserContext>
|
|
87
|
+
<Existing>...</Existing>
|
|
88
|
+
|
|
89
|
+
Output:
|
|
90
|
+
{ ... }
|
|
91
|
+
... more lines
|
|
81
92
|
</output_formatting>
|
|
82
93
|
|
|
83
94
|
<reasoning_requirements>
|
|
@@ -88,6 +99,7 @@ Output:
|
|
|
88
99
|
|
|
89
100
|
<IMPORTANT>
|
|
90
101
|
Do NOT output anything EXCEPT the list of JSON.
|
|
102
|
+
Do NOT add event listeners.
|
|
91
103
|
</IMPORTANT>`;
|
|
92
104
|
|
|
93
105
|
export default spec;
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getsyntux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-alpha",
|
|
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,103 +0,0 @@
|
|
|
1
|
-
import { JSX } from 'react';
|
|
2
|
-
|
|
3
|
-
import { createStreamableValue } from '@ai-sdk/rsc';
|
|
4
|
-
import { LanguageModel, streamText } from 'ai';
|
|
5
|
-
|
|
6
|
-
import { AnimateOptions, ResponseParser, SyntuxComponent, UISchema, constructInput, generateComponentMap } 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
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Section of user interface for LLM to generate.
|
|
33
|
-
* @param values The values (object, primitive, or array) to be displayed.
|
|
34
|
-
* @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming
|
|
35
|
-
* @param components List of allowed components that LLM can use.
|
|
36
|
-
* @param hint Additional custom instructions for the LLM.
|
|
37
|
-
* @param placeholder A placeholder to show while awaiting streaming (NOT during streaming)
|
|
38
|
-
* @param cached Schema returned from onGenerate, used for caching UI
|
|
39
|
-
* @param onGenerate Callback which accepts a string, to be passed to `cached` to reuse same UI
|
|
40
|
-
* @param skeletonize Compresses value for large inputs (arrays) or untrusted input
|
|
41
|
-
* @param onError Callback which accepts an error, invoked when necessary. If not provided, runtime error occurs.
|
|
42
|
-
* @param errorFallback An element fallback to show if an error occurs during generation.
|
|
43
|
-
*/
|
|
44
|
-
export async function GeneratedUI(props: GeneratedContentProps) {
|
|
45
|
-
const input = constructInput(props);
|
|
46
|
-
|
|
47
|
-
const { value, model, components, placeholder, cached, onGenerate, onError, errorFallback, animate } = 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} animate={animate} />
|
|
62
|
-
} else {
|
|
63
|
-
return <></>;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const stream = createStreamableValue('');
|
|
68
|
-
(async () => {
|
|
69
|
-
let total = "";
|
|
70
|
-
let errored = false;
|
|
71
|
-
|
|
72
|
-
const { textStream } = await streamText({
|
|
73
|
-
model,
|
|
74
|
-
system: spec,
|
|
75
|
-
prompt: input,
|
|
76
|
-
onError: (err) => {
|
|
77
|
-
stream.error(err)
|
|
78
|
-
errored = true;
|
|
79
|
-
|
|
80
|
-
if (!onError) {
|
|
81
|
-
if (!errorFallback) {
|
|
82
|
-
throw err;
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
onError(err)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
for await (const delta of textStream) {
|
|
91
|
-
stream.update(delta);
|
|
92
|
-
total += delta;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!errored) {
|
|
96
|
-
stream.done();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (onGenerate) onGenerate(total);
|
|
100
|
-
})()
|
|
101
|
-
|
|
102
|
-
return <GeneratedClient value={value} allowedComponents={allowedComponents} inputStream={stream.value} placeholder={placeholder} errorFallback={errorFallback} animate={animate} />
|
|
103
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
type SchemaNode = {
|
|
2
|
-
id: string;
|
|
3
|
-
parentId: string | null;
|
|
4
|
-
type: string;
|
|
5
|
-
props?: Record<string, any>;
|
|
6
|
-
content?: any | {
|
|
7
|
-
"$bind": string;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
type ComponentMap = Record<string, SchemaNode>;
|
|
11
|
-
type ChildrenMap = Record<string, string[]>;
|
|
12
|
-
type UISchema = {
|
|
13
|
-
componentMap: ComponentMap;
|
|
14
|
-
childrenMap: ChildrenMap;
|
|
15
|
-
root: SchemaNode | null;
|
|
16
|
-
};
|
|
17
|
-
type SyntuxComponent = {
|
|
18
|
-
name: string;
|
|
19
|
-
props: string;
|
|
20
|
-
component: React.ComponentType<any>;
|
|
21
|
-
context?: string;
|
|
22
|
-
};
|
|
23
|
-
type AnimateOptions = {
|
|
24
|
-
offset: number;
|
|
25
|
-
duration: number;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type { AnimateOptions as A, ChildrenMap as C, SyntuxComponent as S, UISchema as U, ComponentMap as a, SchemaNode as b };
|