getsyntux 0.4.0 → 0.6.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 CHANGED
@@ -1,18 +1,22 @@
1
1
  ![](https://raw.githubusercontent.com/puffinsoft/syntux/HEAD/docs/images/banner.png)
2
2
 
3
3
  <p align="center">
4
- <i>syntux</i> is the generative UI library for the web. It lets you build generative UIs that are <b><i>consistent</i></b> and <b><i>flexible</i></b>.
4
+ <i>syntux</i> is the generative UI library for the web.
5
+ </p>
6
+
7
+ <p align="center">
8
+ You give it a <code>value</code> and it designs the UI to display it.
5
9
  </p>
6
10
 
7
11
  ---
8
12
 
9
- https://github.com/user-attachments/assets/a930d35d-92ab-45f4-9f71-f7bc0380c4f1
13
+ https://github.com/user-attachments/assets/694d4646-c36d-4c19-a111-86e546484101
10
14
 
11
15
  - ⚡ **Streamable** - display UI as you generate.
12
16
  - 🎨 **Custom Components** - use your own React components.
13
17
  - 💾 **Cacheable** - reuse generated UIs with new values.
14
18
 
15
- ⚠️ this library is still in **beta**. All npm versions are **stable**, but the API may change across versions.
19
+ **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).
16
20
 
17
21
  <h3 align="center" margin="0"><a href="https://github.com/puffinsoft/syntux/wiki">➡️ view documentation</a></h3>
18
22
 
@@ -40,6 +44,9 @@ const valueToDisplay = {
40
44
 
41
45
  *syntux* takes the `value` into consideration and designs a UI to best display it. `value` can be anything; an object, array or primitive.
42
46
 
47
+ > [!TIP]
48
+ > If you are passing in a **large array** as a value, or an object with untrusted input, use the `skeletonize` property. See [the explanation](https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays).
49
+
43
50
  ### Installation
44
51
 
45
52
  In the root of your project:
@@ -109,18 +116,46 @@ export default function Home(){
109
116
  }
110
117
  ```
111
118
 
112
- The component definitions (the `components` array above) can be generated automatically:
119
+ <sup>
113
120
 
121
+ **Note**: the `components` array above can be generated automatically with `npx getsyntux generate-defs <component.tsx>`. See the [documentation](https://github.com/puffinsoft/syntux/wiki).
122
+
123
+ </sup>
124
+
125
+ Make sure components are marked with `"use client"`.
126
+
127
+ #### Custom actions
128
+
129
+ Perform server actions, attached automatically to component events:
130
+
131
+ ```jsx
132
+ import { defineTool } from "getsyntux";
133
+
134
+ export default function Home(){
135
+ const valueToDisplay = { ... };
136
+
137
+ <GeneratedUI actions = {{
138
+ "delete": defineTool(async (id: string) => { "use server"; /* ... */ }, "id: string", "deletes post with id"),
139
+ "refresh": defineTool(async () => { "use server"; /* ... */})
140
+ }} />
141
+ }
114
142
  ```
115
- $ npx getsyntux generate-defs ./path/to/component.tsx
116
- ```
143
+ <sup>
117
144
 
118
- See the [documentation](https://github.com/puffinsoft/syntux/wiki) for an in-depth explanation.
145
+ **Note**: The name of the action should specify its purpose (it is seen by the LLM). Use `defineTool` to add further context to actions. See the [documentation](https://github.com/puffinsoft/syntux/wiki).
146
+
147
+ </sup>
119
148
 
120
149
  ---
121
150
 
122
151
  ### FAQ
123
152
 
153
+ <details>
154
+ <summary>How expensive is generation?</summary>
155
+
156
+ *syntux* is highly optimized to save tokens. See [here](https://github.com/puffinsoft/syntux/wiki/FAQ#how-expensive-is-this) for a cost estimation table and an explanation.
157
+ </details>
158
+
124
159
  <details>
125
160
  <summary>How does generation work? (Does it generate source code?)</summary>
126
161
 
package/dist/client.d.mts CHANGED
@@ -1,16 +1,17 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { StreamableValue } from '@ai-sdk/rsc';
3
3
  import react__default, { JSX, ComponentType } from 'react';
4
- import { C as ComponentMap, b as ChildrenMap } from './types-DejIW5JZ.mjs';
4
+ import { C as ContextfulAction, b as ComponentMap, c as ChildrenMap } from './types-CtXPasY-.mjs';
5
5
 
6
6
  /**
7
7
  * Client wrapper for Renderer that handles streaming and parsing with server.
8
8
  */
9
- declare function GeneratedClient({ value, allowedComponents, inputStream, placeholder }: {
9
+ declare function GeneratedClient({ value, allowedComponents, inputStream, placeholder, actions }: {
10
10
  value: any;
11
11
  allowedComponents: Record<string, react__default.ComponentType<any> | string>;
12
12
  inputStream: StreamableValue<string>;
13
13
  placeholder?: JSX.Element;
14
+ actions?: Record<string, ContextfulAction>;
14
15
  }): react_jsx_runtime.JSX.Element;
15
16
 
16
17
  interface RendererProps {
@@ -20,6 +21,7 @@ interface RendererProps {
20
21
  allowedComponents: Record<string, ComponentType<any> | string>;
21
22
  global: any;
22
23
  local: any;
24
+ actions: Record<string, ContextfulAction>;
23
25
  }
24
26
  /**
25
27
  * Renders a UISchema recursively, in accordance to the spec.
package/dist/client.mjs CHANGED
@@ -1,3 +1,3 @@
1
- "use client";import{readStreamableValue as N}from"@ai-sdk/rsc";import{useEffect as _,useReducer as A,useRef as F}from"react";import{Fragment as k}from"react";import{Fragment as S,jsx as p,jsxs as x}from"react/jsx-runtime";import{createElement as v}from"react";var M=(t,n)=>n==="$"?t:n.split(".").reduce((e,r)=>e==null?void 0:e[r],t),h=(t,n,e)=>e.startsWith("$item.")?(e=e.slice(6),M(n,e)):e==="$item"?n:M(t,e),R=(t,n,e)=>e&&("$bind"in e?h(t,n,e.$bind):(Object.keys(e).forEach(r=>{let i=e[r];typeof i=="object"&&(e[r]=R(t,n,i))}),e)),C=(t,n,e)=>typeof e=="object"?h(t,n,e.$bind):e,T=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function d(t){var g;let{id:n,componentMap:e,childrenMap:r,global:i,local:a,allowedComponents:s}=t,o=e[n];if(o.type==="TEXT")return p(S,{children:C(i,a,o.content)});let c=(g=o.props)==null?void 0:g.source;if(o.type==="__ForEach__"&&c){let u=h(i,a,c);if(!Array.isArray(u))return null;let m=r[o.id];return p(S,{children:m==null?void 0:m.map((E,P)=>p(k,{children:u.map((I,$)=>v(d,{...t,id:E,local:I,key:$}))},P))})}let l=s[o.type]||o.type,b=R(i,a,o.props);return typeof l=="string"&&T.has(l)?p(l,{...b}):x(l,{...b,children:[C(i,a,o.content),r[o.id]&&r[o.id].map((u,m)=>p(d,{...t,id:u},m))]})}var f=class{buffer="";schema={childrenMap:{},componentMap:{},root:null};addDelta(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:i}=this.schema;i[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=""}};import{Fragment as w,jsx as y}from"react/jsx-runtime";function K({value:t,allowedComponents:n,inputStream:e,placeholder:r}){var o;let[,i]=A(c=>c+1,0),a=F(null);_(()=>{a.current||(a.current=new f),(async()=>{for await(let l of N(e))a.current&&l!==void 0&&a.current.addDelta(l)&&i()})().then(()=>{a.current.finish(),i()})},[e]);let s=(o=a==null?void 0:a.current)==null?void 0:o.schema;return y(w,{children:s!=null&&s.root?y(d,{id:s.root.id,componentMap:s.componentMap,childrenMap:s.childrenMap,allowedComponents:n,global:t,local:t}):y(w,{children:r})})}export{K as GeneratedClient,d as Renderer};
1
+ "use client";import{readStreamableValue as J}from"@ai-sdk/rsc";import{useEffect as O,useReducer as U,useRef as V}from"react";import{Fragment as _}from"react";import{Fragment as A,jsx as d}from"react/jsx-runtime";import{createElement as F}from"react";var R=(r,t)=>t==="$"?r:t.split(".").reduce((e,i)=>e==null?void 0:e[i],r),p=(r,t,e)=>e.startsWith("$item.")?(e=e.slice(6),R(t,e)):e==="$item"?t:R(r,e),$=new Set(["dangerouslySetInnerHTML"]),P=(r,t,e,i)=>{if(!e)return e;if("$action"in e){let n=i[e.$action];if(!n)return()=>{};let s=e.args.map(o=>o&&typeof o=="object"&&"$bind"in o?p(r,t,o.$bind):o);return o=>{n.fn(...s)}}if("$bind"in e){let n=p(r,t,e.$bind);return Object.keys(n).forEach(s=>{$.has(s)&&delete n[s]}),n}return Object.keys(e).forEach(n=>{if($.has(n)){delete e[n];return}let s=e[n];typeof s=="object"&&(e[n]=P(r,t,s,i))}),e},x=(r,t,e)=>typeof e=="object"?p(r,t,e.$bind):e;function h(r){var M,S;let{id:t,componentMap:e,childrenMap:i,global:n,local:s,allowedComponents:o,actions:c}=r,a=e[t];if(a.type==="TEXT")return d(A,{children:x(n,s,a.content)});let f=(M=a.props)==null?void 0:M.source;if(a.type==="__ForEach__"&&f){let l=p(n,s,f);if(!Array.isArray(l))return null;let m=i[a.id];return d(A,{children:m==null?void 0:m.map((T,N)=>d(_,{children:l.map((v,L)=>F(h,{...r,id:T,local:v,key:L}))},N))})}let u=o[a.type]||a.type,g=P(n,s,a.props,c),E=x(n,s,a.content),I=((S=i[a.id])==null?void 0:S.map((l,m)=>d(h,{...r,id:l},m)))||[],C=[E,...I].filter(l=>l!=null);return C.length>0?d(u,{...g,children:C}):d(u,{...g})}var y=class{buffer="";schema={childrenMap:{},componentMap:{},root:null};addDelta(t){this.buffer+=t;let e=this.buffer.split(`
2
+ `);return e.length>1?(e.slice(0,e.length-1).forEach(i=>this.handleLine(i)),this.buffer=e[e.length-1],!0):!1}handleLine(t){try{let e=JSON.parse(t),{childrenMap:i,componentMap:n}=this.schema;n[e.id]=e,e.parentId===null?this.schema.root=e:(i[e.parentId]||(i[e.parentId]=[]),i[e.parentId].push(e.id))}catch{}}finish(){this.handleLine(this.buffer),this.buffer=""}};import{Fragment as w,jsx as b}from"react/jsx-runtime";function ee({value:r,allowedComponents:t,inputStream:e,placeholder:i,actions:n}){var a;let[,s]=U(f=>f+1,0),o=V(null);O(()=>{o.current=new y,(async()=>{for await(let u of J(e))o.current&&u!==void 0&&o.current.addDelta(u)&&s()})().then(()=>{o.current.finish(),s()})},[e]);let c=(a=o==null?void 0:o.current)==null?void 0:a.schema;return b(w,{children:c!=null&&c.root?b(h,{id:c.root.id,componentMap:c.componentMap,childrenMap:c.childrenMap,allowedComponents:t,global:r,local:r,actions:n||{}}):b(w,{children:i})})}export{ee as GeneratedClient,h as Renderer};
3
3
  //# sourceMappingURL=client.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/GeneratedClient.tsx","../src/client/Renderer.tsx","../src/ResponseParser.ts"],"sourcesContent":["\"use client\";\r\n\r\nimport { StreamableValue, readStreamableValue } from '@ai-sdk/rsc';\r\nimport React, { JSX, useEffect, useReducer, useRef, useState } from 'react';\r\nimport { Renderer } from './Renderer';\r\nimport { ResponseParser } from '../ResponseParser';\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}: {\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}) {\r\n const [, forceUpdate] = useReducer(x => x + 1, 0);\r\n const parser = useRef<ResponseParser | null>(null);\r\n\r\n useEffect(() => {\r\n if (!parser.current) {\r\n parser.current = new ResponseParser();\r\n }\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 });\r\n }, [inputStream]);\r\n\r\n const schema = parser?.current?.schema;\r\n\r\n return (\r\n <>\r\n {schema?.root ?\r\n <Renderer id={schema.root.id} componentMap={schema.componentMap} childrenMap={schema.childrenMap} allowedComponents={allowedComponents} global={value} local={value} />\r\n : <>{placeholder}</>}\r\n </>\r\n )\r\n}\r\n","\"use client\";\r\n\r\nimport { ComponentType, Fragment } from 'react'\r\nimport { ChildrenMap, ComponentMap, SchemaNode } 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\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 if (\"$bind\" in props) return get(global, local, props.$bind); // $bind may be falsy value\r\n Object.keys(props).forEach((key) => {\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\nconst voidElements = new Set([\r\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\r\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\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}\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 {\r\n id, componentMap, childrenMap, global, local, allowedComponents\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 if(typeof Component === \"string\" && voidElements.has(Component)){\r\n return <Component {...componentProps} />\r\n }\r\n\r\n return <Component {...componentProps}>\r\n {renderContent(global, local, element.content)}\r\n {childrenMap[element.id] && 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 </Component>\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}"],"mappings":"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","names":["readStreamableValue","useEffect","useReducer","useRef","Fragment","jsx","jsxs","createElement","resolvePath","obj","path","acc","curr","get","global","local","resolveProps","props","key","val","renderContent","content","voidElements","Renderer","_a","id","componentMap","childrenMap","allowedComponents","element","sourceArrPath","sourceArr","childrenArr","childId","index","item","index1","Component","componentProps","ResponseParser","delta","split","line","node","childrenMap","componentMap","Fragment","jsx","GeneratedClient","value","allowedComponents","inputStream","placeholder","_a","forceUpdate","useReducer","x","parser","useRef","useEffect","ResponseParser","delta","readStreamableValue","schema","Renderer"]}
1
+ {"version":3,"sources":["../src/client/GeneratedClient.tsx","../src/client/Renderer.tsx","../src/ResponseParser.ts"],"sourcesContent":["\"use client\";\r\n\r\nimport { StreamableValue, readStreamableValue } from '@ai-sdk/rsc';\r\nimport React, { JSX, useEffect, useReducer, useRef, useState } from 'react';\r\nimport { Renderer } from './Renderer';\r\nimport { ResponseParser } from '../ResponseParser';\r\nimport { ContextfulAction } from 'src/types';\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 actions\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 actions?: Record<string, ContextfulAction>\r\n}) {\r\n const [, forceUpdate] = useReducer(x => x + 1, 0);\r\n const parser = useRef<ResponseParser | null>(null);\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 });\r\n }, [inputStream]);\r\n\r\n const schema = parser?.current?.schema;\r\n\r\n return (\r\n <>\r\n {schema?.root ?\r\n <Renderer id={schema.root.id} componentMap={schema.componentMap} childrenMap={schema.childrenMap} allowedComponents={allowedComponents} global={value} local={value} actions={actions || {}} />\r\n : <>{placeholder}</>}\r\n </>\r\n )\r\n}\r\n","\"use client\";\r\n\r\nimport { ComponentType, Fragment } from 'react'\r\nimport { ChildrenMap, ComponentMap, ContextfulAction, SchemaNode } 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, actions: Record<string, ContextfulAction>) => {\r\n if (!props) return props;\r\n if (\"$action\" in props) {\r\n const action = actions[props.$action];\r\n if (!action) return () => { };\r\n\r\n const resolvedArgs = props.args.map((arg: any) => {\r\n if (arg && typeof arg === \"object\" && \"$bind\" in arg) {\r\n return get(global, local, arg.$bind);\r\n } else {\r\n return arg;\r\n }\r\n })\r\n\r\n return (e: any) => {\r\n action.fn(...resolvedArgs)\r\n }\r\n }\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, actions);\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 actions: Record<string, ContextfulAction>;\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 {\r\n id, componentMap, childrenMap, global, local, allowedComponents, actions\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, actions);\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 {...componentProps}>\r\n {nodesToRender}\r\n </Component>\r\n }\r\n\r\n return <Component {...componentProps} />\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}"],"mappings":"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","names":["readStreamableValue","useEffect","useReducer","useRef","Fragment","jsx","createElement","resolvePath","obj","path","acc","curr","get","global","local","blacklistedProps","resolveProps","props","actions","action","resolvedArgs","arg","e","resolved","key","val","renderContent","content","Renderer","_a","_b","id","componentMap","childrenMap","allowedComponents","element","sourceArrPath","sourceArr","childrenArr","childId","index","item","index1","Component","componentProps","contentNode","childNodes","nodesToRender","node","ResponseParser","delta","split","line","node","childrenMap","componentMap","Fragment","jsx","GeneratedClient","value","allowedComponents","inputStream","placeholder","actions","_a","forceUpdate","useReducer","x","parser","useRef","useEffect","ResponseParser","delta","readStreamableValue","schema","Renderer"]}
package/dist/index.d.mts CHANGED
@@ -1,14 +1,15 @@
1
- import { S as SyntuxComponent$1, U as UISchema } from './types-DejIW5JZ.mjs';
2
- export { b as ChildrenMap, C as ComponentMap, a as SchemaNode } from './types-DejIW5JZ.mjs';
1
+ import { S as SyntuxComponent$1, C as ContextfulAction$1, U as UISchema } from './types-CtXPasY-.mjs';
2
+ export { c as ChildrenMap, b as ComponentMap, a as SchemaNode } from './types-CtXPasY-.mjs';
3
3
  import * as react from 'react';
4
4
  import { JSX } from 'react';
5
5
  import { LanguageModel } from 'ai';
6
- import { SyntuxComponent } from 'getsyntux';
6
+ import { SyntuxComponent, ContextfulAction } from 'getsyntux';
7
7
 
8
8
  interface GeneratedContentProps {
9
9
  value: any;
10
10
  model: LanguageModel;
11
11
  components?: (SyntuxComponent | string)[];
12
+ actions?: Record<string, ContextfulAction>;
12
13
  hint?: string;
13
14
  placeholder?: JSX.Element;
14
15
  cached?: string;
@@ -24,7 +25,7 @@ declare function generateComponentMap(allowedComponents: (SyntuxComponent$1 | st
24
25
  /**
25
26
  * Creates LLM input in accordance to the spec
26
27
  */
27
- declare function constructInput({ value, skeletonize, components, hint }: GeneratedContentProps): string;
28
+ declare function constructInput({ value, skeletonize, components, hint, actions }: GeneratedContentProps): string;
28
29
  /**
29
30
  * generates a skeleton of the input value, ideal for large arrays or untrusted input.
30
31
  * see the FAQ for more information: https://github.com/puffinsoft/syntux/wiki/FAQ#handling-untrusted-input--large-arrays.
@@ -32,6 +33,13 @@ declare function constructInput({ value, skeletonize, components, hint }: Genera
32
33
  * *important*: assumes arrays are non-polymorphic
33
34
  */
34
35
  declare function create_skeleton(input: any): any;
36
+ /**
37
+ * attaches metadata to callback to allow LLM understanding
38
+ * @param fn the action to be binded to an event on the UI
39
+ * @param params the parameters the function accepts, in Typescript format (e.g., "id: number, name: string")
40
+ * @param context a description of what the function does, for better LLM context
41
+ */
42
+ declare function defineTool(fn: Function, params?: string, context?: string): ContextfulAction$1;
35
43
 
36
44
  /**
37
45
  * Utility class for parsing UISchema from stream.
@@ -59,4 +67,4 @@ declare class ResponseParser {
59
67
  finish(): void;
60
68
  }
61
69
 
62
- export { ResponseParser, SyntuxComponent$1 as SyntuxComponent, UISchema, constructInput, create_skeleton, generateComponentMap };
70
+ export { ContextfulAction$1 as ContextfulAction, ResponseParser, SyntuxComponent$1 as SyntuxComponent, UISchema, constructInput, create_skeleton, defineTool, generateComponentMap };
package/dist/index.mjs CHANGED
@@ -1,9 +1,13 @@
1
- function h(n){return n.reduce((t,e)=>typeof e=="string"?(t[e]=e,t):(t[e.name]=e.component,t),{})}function d({value:n,skeletonize:t=!1,components:e,hint:o}){let s=(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"),a=(i==null?void 0:i.map(r=>r.context?`${r.name} [props: ${r.props}, details: ${r.context}]`:`${r.name} [props: ${r.props}]`).join(","))||"",l=o,u=JSON.stringify(t?f(n):n);return`<AllowedComponents>${s}</AllowedComponents>
2
- <ComponentContext>${a}</ComponentContext>
3
- <UserContext>${l||""}</UserContext>
4
- <IsSkeleton>${t.toString()}</IsSkeleton>
1
+ function x(e){return e.reduce((n,t)=>typeof t=="string"?(n[t]=t,n):(n[t.name]=t.component,n),{})}function m({value:e,skeletonize:n=!1,components:t,hint:o,actions:i}){let u=(t==null?void 0:t.map(r=>typeof r=="string"?r:r.name).join(","))||"",s=t==null?void 0:t.filter(r=>typeof r!="string"),c=(s==null?void 0:s.map(r=>r.context?`${r.name} [props: ${r.props}, details: ${r.context}]`:`${r.name} [props: ${r.props}]`).join(","))||"",h=o,l="";i&&(l=`
2
+ `+Object.keys(i).map(r=>{let f=i[r];return`- ${r}(${f.params}): ${f.context}`}).join(`
3
+ `)+`
4
+ `);let d=JSON.stringify(n?a(e):e);return`<AllowedComponents>${u}</AllowedComponents>
5
+ <ComponentContext>${c}</ComponentContext>
6
+ <UserContext>${h||""}</UserContext>
7
+ <AvailableActions>${l}</AvailableActions>
8
+ <IsSkeleton>${n.toString()}</IsSkeleton>
5
9
  <Value>
6
- ${u}
7
- </Value>`}function f(n){return n===null?"null":typeof n!="object"?typeof n:Array.isArray(n)?n.length==0?"null":[f(n[0])]:Object.entries(n).reduce((t,[e,o])=>(t[e]=f(o),t),{})}var p=class{buffer="";schema={childrenMap:{},componentMap:{},root:null};addDelta(t){this.buffer+=t;let e=this.buffer.split(`
8
- `);return e.length>1?(e.slice(0,e.length-1).forEach(o=>this.handleLine(o)),this.buffer=e[e.length-1],!0):!1}handleLine(t){try{let e=JSON.parse(t),{childrenMap:o,componentMap:s}=this.schema;s[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{p as ResponseParser,d as constructInput,f as create_skeleton,h as generateComponentMap};
10
+ ${d}
11
+ </Value>`}function a(e){return e===null?"null":typeof e!="object"?typeof e:Array.isArray(e)?e.length==0?"null":[a(e[0])]:Object.entries(e).reduce((n,[t,o])=>(n[t]=a(o),n),{})}function C(e,n,t){return{fn:e,params:n||"",context:t||"no context provided"}}var p=class{buffer="";schema={childrenMap:{},componentMap:{},root:null};addDelta(n){this.buffer+=n;let t=this.buffer.split(`
12
+ `);return t.length>1?(t.slice(0,t.length-1).forEach(o=>this.handleLine(o)),this.buffer=t[t.length-1],!0):!1}handleLine(n){try{let t=JSON.parse(n),{childrenMap:o,componentMap:i}=this.schema;i[t.id]=t,t.parentId===null?this.schema.root=t:(o[t.parentId]||(o[t.parentId]=[]),o[t.parentId].push(t.id))}catch{}}finish(){this.handleLine(this.buffer),this.buffer=""}};export{p as ResponseParser,m as constructInput,a as create_skeleton,C as defineTool,x as generateComponentMap};
9
13
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/util.ts","../src/ResponseParser.ts"],"sourcesContent":["import { GeneratedContentProps } from \"./templates/GeneratedUI\";\r\nimport { 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}: GeneratedContentProps) {\r\n const allowedComponents = components?.map((item: SyntuxComponent | 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 SyntuxComponent => 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 userContext = hint;\r\n const inputValue = JSON.stringify(skeletonize ? create_skeleton(value) : value)\r\n\r\n return `<AllowedComponents>${allowedComponents}</AllowedComponents>\\n<ComponentContext>${componentContext}</ComponentContext>\\n<UserContext>${userContext || \"\"}</UserContext>\\n<IsSkeleton>${skeletonize.toString()}</IsSkeleton>\\n<Value>\\n${inputValue}\\n</Value>`\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 create_skeleton(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 [create_skeleton(input[0])]\r\n }\r\n }\r\n return Object.entries(input).reduce((acc, [key, value]) => {\r\n acc[key] = create_skeleton(value);\r\n return acc;\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}"],"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,EAA0B,CACtB,IAAMP,GAAoBM,GAAA,YAAAA,EAAY,IAAKE,GACnC,OAAOA,GAAS,SAAiBA,EAC9BA,EAAK,MACb,KAAK,OAAQ,GAEVC,EAAmBH,GAAA,YAAAA,EAAY,OAAQE,GAAkC,OAAOA,GAAS,UACzFE,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,EAAcJ,EACdK,EAAa,KAAK,UAAUP,EAAcQ,EAAgBT,CAAK,EAAIA,CAAK,EAE9E,MAAO,sBAAsBJ,CAAiB;AAAA,oBAA2CU,CAAgB;AAAA,eAAqCC,GAAe,EAAE;AAAA,cAA+BN,EAAY,SAAS,CAAC;AAAA;AAAA,EAA2BO,CAAU;AAAA,SAC7P,CAQO,SAASC,EAAgBC,EAAY,CACxC,OAAIA,IAAU,KAAa,OAEvB,OAAOA,GAAU,SAAiB,OAAOA,EAEzC,MAAM,QAAQA,CAAK,EACfA,EAAM,QAAU,EACT,OAEA,CAACD,EAAgBC,EAAM,CAAC,CAAC,CAAC,EAGlC,OAAO,QAAQA,CAAK,EAAE,OAAO,CAACb,EAAK,CAACc,EAAKX,CAAK,KACjDH,EAAIc,CAAG,EAAIF,EAAgBT,CAAK,EACzBH,GACR,CAAC,CAAC,CACT,CC9DO,IAAMe,EAAN,KAAqB,CACxB,OAAS,GAGT,OAAmB,CACf,YAAa,CAAC,EACd,aAAc,CAAC,EACf,KAAM,IACV,EAUA,SAASC,EAAe,CACpB,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","userContext","inputValue","create_skeleton","input","key","ResponseParser","delta","split","line","node","childrenMap","componentMap"]}
1
+ {"version":3,"sources":["../src/util.ts","../src/ResponseParser.ts"],"sourcesContent":["import { GeneratedContentProps } from \"./templates/GeneratedUI\";\r\nimport { ContextfulAction, 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, actions\r\n}: GeneratedContentProps) {\r\n const allowedComponents = components?.map((item: SyntuxComponent | 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 SyntuxComponent => 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 userContext = hint;\r\n\r\n let availableActions = \"\";\r\n if(actions){\r\n availableActions = \"\\n\" + Object.keys(actions).map((key: string) => {\r\n const action: ContextfulAction = actions[key];\r\n return `- ${key}(${action.params}): ${action.context}`\r\n }).join('\\n') + \"\\n\"\r\n }\r\n\r\n const inputValue = JSON.stringify(skeletonize ? create_skeleton(value) : value)\r\n\r\n return `<AllowedComponents>${allowedComponents}</AllowedComponents>\\n<ComponentContext>${componentContext}</ComponentContext>\\n<UserContext>${userContext || \"\"}</UserContext>\\n<AvailableActions>${availableActions}</AvailableActions>\\n<IsSkeleton>${skeletonize.toString()}</IsSkeleton>\\n<Value>\\n${inputValue}\\n</Value>`\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 create_skeleton(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 [create_skeleton(input[0])]\r\n }\r\n }\r\n return Object.entries(input).reduce((acc, [key, value]) => {\r\n acc[key] = create_skeleton(value);\r\n return acc;\r\n }, {})\r\n}\r\n\r\n/**\r\n * attaches metadata to callback to allow LLM understanding\r\n * @param fn the action to be binded to an event on the UI\r\n * @param params the parameters the function accepts, in Typescript format (e.g., \"id: number, name: string\")\r\n * @param context a description of what the function does, for better LLM context\r\n */\r\nexport function defineTool(fn: Function, params?: string, context?: string): ContextfulAction {\r\n return {\r\n fn, \r\n params: params || \"\",\r\n context: context || \"no context provided\"\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}"],"mappings":"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","names":["generateComponentMap","allowedComponents","acc","curr","constructInput","value","skeletonize","components","hint","actions","item","customComponents","componentContext","userContext","availableActions","key","action","inputValue","create_skeleton","input","defineTool","fn","params","context","ResponseParser","delta","split","line","node","childrenMap","componentMap"]}
@@ -1 +1 @@
1
- {"inputs":{"src/types.ts":{"bytes":583,"imports":[],"format":"esm"},"src/util.ts":{"bytes":2560,"imports":[{"path":"./templates/GeneratedUI","kind":"import-statement","external":true},{"path":"./types","kind":"import-statement","external":true}],"format":"esm"},"src/ResponseParser.ts":{"bytes":1865,"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":3397,"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/GeneratedClient.tsx":{"bytes":1544,"imports":[{"path":"@ai-sdk/rsc","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./Renderer"},{"path":"src/ResponseParser.ts","kind":"import-statement","original":"../ResponseParser"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/client.ts":{"bytes":92,"imports":[{"path":"src/client/GeneratedClient.tsx","kind":"import-statement","original":"./client/GeneratedClient"},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./client/Renderer"}],"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":6856},"dist/index.mjs":{"imports":[],"exports":["ResponseParser","constructInput","create_skeleton","generateComponentMap"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":0},"src/util.ts":{"bytesInOutput":809},"src/ResponseParser.ts":{"bytesInOutput":485}},"bytes":1390},"dist/client.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":11528},"dist/client.mjs":{"imports":[{"path":"@ai-sdk/rsc","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","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"exports":["GeneratedClient","Renderer"],"entryPoint":"src/client.ts","inputs":{"src/client/GeneratedClient.tsx":{"bytesInOutput":663},"src/client/Renderer.tsx":{"bytesInOutput":1153},"src/ResponseParser.ts":{"bytesInOutput":485},"src/client.ts":{"bytesInOutput":0}},"bytes":2358},"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}}}
1
+ {"inputs":{"src/types.ts":{"bytes":683,"imports":[],"format":"esm"},"src/util.ts":{"bytes":3487,"imports":[{"path":"./templates/GeneratedUI","kind":"import-statement","external":true},{"path":"./types","kind":"import-statement","external":true}],"format":"esm"},"src/ResponseParser.ts":{"bytes":1865,"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":4350,"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/GeneratedClient.tsx":{"bytes":1681,"imports":[{"path":"@ai-sdk/rsc","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./Renderer"},{"path":"src/ResponseParser.ts","kind":"import-statement","original":"../ResponseParser"},{"path":"src/types","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/client.ts":{"bytes":92,"imports":[{"path":"src/client/GeneratedClient.tsx","kind":"import-statement","original":"./client/GeneratedClient"},{"path":"src/client/Renderer.tsx","kind":"import-statement","original":"./client/Renderer"}],"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":8268},"dist/index.mjs":{"imports":[],"exports":["ResponseParser","constructInput","create_skeleton","defineTool","generateComponentMap"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":0},"src/util.ts":{"bytesInOutput":1050},"src/ResponseParser.ts":{"bytesInOutput":485}},"bytes":1647},"dist/client.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":13364},"dist/client.mjs":{"imports":[{"path":"@ai-sdk/rsc","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","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"exports":["GeneratedClient","Renderer"],"entryPoint":"src/client.ts","inputs":{"src/client/GeneratedClient.tsx":{"bytesInOutput":675},"src/client/Renderer.tsx":{"bytesInOutput":1393},"src/ResponseParser.ts":{"bytesInOutput":485},"src/client.ts":{"bytesInOutput":0}},"bytes":2611},"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}}}
@@ -4,7 +4,7 @@ import { createStreamableValue } from '@ai-sdk/rsc';
4
4
  import { LanguageModel, streamText } from 'ai';
5
5
 
6
6
  import { GeneratedClient, Renderer } from 'getsyntux/client';
7
- import { ResponseParser, SyntuxComponent, UISchema, constructInput, generateComponentMap } from "getsyntux";
7
+ import { ContextfulAction, ResponseParser, SyntuxComponent, UISchema, constructInput, generateComponentMap } from "getsyntux";
8
8
 
9
9
  import spec from './spec';
10
10
 
@@ -12,6 +12,7 @@ export interface GeneratedContentProps {
12
12
  value: any;
13
13
  model: LanguageModel;
14
14
  components?: (SyntuxComponent | string)[];
15
+ actions?: Record<string, ContextfulAction>;
15
16
  hint?: string;
16
17
  placeholder?: JSX.Element;
17
18
  cached?: string;
@@ -29,6 +30,7 @@ export interface GeneratedContentProps {
29
30
  * @param values The values (object, primitive, or array) to be displayed.
30
31
  * @param model The LanguageModel (as provided from AI SDK) to use. Must support streaming
31
32
  * @param components List of allowed components that LLM can use.
33
+ * @param actions Map of callbacks that can be attached to events (e.g., onClick, onMouseOver) by LLM.
32
34
  * @param hint Additional custom instructions for the LLM.
33
35
  * @param placeholder A placeholder to show while awaiting streaming (NOT during streaming)
34
36
  * @param cached Schema returned from onGenerate, used for caching UI
@@ -38,7 +40,7 @@ export interface GeneratedContentProps {
38
40
  export async function GeneratedUI(props: GeneratedContentProps) {
39
41
  const input = constructInput(props);
40
42
 
41
- const { value, model, components, placeholder, cached, onGenerate } = props;
43
+ const { value, model, components, placeholder, cached, onGenerate, actions } = props;
42
44
 
43
45
  const allowedComponents = generateComponentMap(components || []);
44
46
 
@@ -52,7 +54,7 @@ export async function GeneratedUI(props: GeneratedContentProps) {
52
54
 
53
55
  if(schema.root){
54
56
  return <Renderer id={schema.root.id} componentMap={schema.componentMap} childrenMap={schema.childrenMap}
55
- allowedComponents={allowedComponents} global={value} local={value} />
57
+ allowedComponents={allowedComponents} global={value} local={value} actions={actions || {}}/>
56
58
  } else {
57
59
  return <></>;
58
60
  }
@@ -79,5 +81,5 @@ export async function GeneratedUI(props: GeneratedContentProps) {
79
81
  if(onGenerate) onGenerate(total);
80
82
  })()
81
83
 
82
- return <GeneratedClient value={value} allowedComponents={allowedComponents} inputStream={stream.value} placeholder={placeholder} />
84
+ return <GeneratedClient value={value} allowedComponents={allowedComponents} inputStream={stream.value} placeholder={placeholder} actions={actions} />
83
85
  }
@@ -32,6 +32,24 @@ Use "$bind" in `content` or `props` to link data.
32
32
  Use "$bind": "$" to reference the global object itself, useful when the value itself is an array and you need to loop through it.
33
33
  </binding_rules>
34
34
 
35
+ <action_rules>
36
+ In the props, to attach event handlers (like onClick), use the "$action" schema.
37
+ Format: { "$action": "actionName", "args": [arg1, arg2, ...] }
38
+
39
+ Argument Rules:
40
+ - Hardcoded value: Pass the literal value directly (e.g., "dark_mode", 15, true).
41
+ - Dynamic value: Use a binding object (e.g., { "$bind": "$item.id" }).
42
+
43
+ Example:
44
+ "props": {
45
+ "onClick": {
46
+ "$action": "addToCart",
47
+ "args": [ { "$bind": "$item.id" }, 1 ]
48
+ }
49
+ }
50
+ This calls the `addToCart` action with the item's ID and the number 1.
51
+ </action_rules>
52
+
35
53
  <iteration>
36
54
  To render arrays, use the `__ForEach__` component.
37
55
  To define a loop:
@@ -50,8 +68,13 @@ In the example above, card_1 is the template that repeats for every author. It s
50
68
  * `AllowedComponents` is a comma-separated list, lowercase for Native HTML tags, Uppercase for Custom React Components. If none are provided, you can use any HTML tag. If they are, only use them to the best of your ability.
51
69
  * `ComponentContext` defines the Typescript interface for custom components. Components are separated by a comma, in the format `ComponentName [props: { ... }, details: "..."]`. The `props` indicate what `props` it must accept, in Typescript format. The `details` is an optional field, and describes what the component does. DO NOT hallucinate props. Use the details to better your understanding of how to generate the UI.
52
70
  2. Parse Context: Read `UserContext` for specific design requests.
53
- 3. Parse Data: Analyze `Value` to determine structure.
54
- 4. Check Skeleton: Read `IsSkeleton`. If it is `true`, that means all the property values have been replaced by the *type* of the value. If it is `false`, then you are seeing the raw values of each property.
71
+ 3. Parse Actions: Read `AvailableActions`.
72
+ - This list contains functions you can attach to events (onClick, onHover, etc.).
73
+ - The format is `actionName(params): description`.
74
+ - ONLY use actions listed here. Do not invent function names.
75
+ - Pay close attention to the `params` signature to ensure you pass the correct data types in the `args` array.
76
+ 4. Parse Data: Analyze `Value` to determine structure.
77
+ 5. Check Skeleton: Read `IsSkeleton`. If it is `true`, that means all the property values have been replaced by the *type* of the value. If it is `false`, then you are seeing the raw values of each property.
55
78
  </input_processing_rules>
56
79
 
57
80
  <output_formatting>
@@ -61,6 +84,7 @@ Input:
61
84
  <AllowedComponents>...</AllowedComponents>
62
85
  <ComponentContext>...</ComponentContext>
63
86
  <UserContext>...</UserContext>
87
+ <AvailableActions>...</AvailableActions>
64
88
  <IsSkeleton>...</IsSkeleton>
65
89
  <Value>...</Value>
66
90
 
@@ -38,6 +38,24 @@ Use "$bind" in \`content\` or \`props\` to link data.
38
38
  Use "$bind": "$" to reference the global object itself, useful when the value itself is an array and you need to loop through it.
39
39
  </binding_rules>
40
40
 
41
+ <action_rules>
42
+ In the props, to attach event handlers (like onClick), use the "$action" schema.
43
+ Format: { "$action": "actionName", "args": [arg1, arg2, ...] }
44
+
45
+ Argument Rules:
46
+ - Hardcoded value: Pass the literal value directly (e.g., "dark_mode", 15, true).
47
+ - Dynamic value: Use a binding object (e.g., { "$bind": "$item.id" }).
48
+
49
+ Example:
50
+ "props": {
51
+ "onClick": {
52
+ "$action": "addToCart",
53
+ "args": [ { "$bind": "$item.id" }, 1 ]
54
+ }
55
+ }
56
+ This calls the \`addToCart\` action with the item's ID and the number 1.
57
+ </action_rules>
58
+
41
59
  <iteration>
42
60
  To render arrays, use the \`__ForEach__\` component.
43
61
  To define a loop:
@@ -56,8 +74,13 @@ In the example above, card_1 is the template that repeats for every author. It s
56
74
  * \`AllowedComponents\` is a comma-separated list, lowercase for Native HTML tags, Uppercase for Custom React Components. If none are provided, you can use any HTML tag. If they are, only use them to the best of your ability.
57
75
  * \`ComponentContext\` defines the Typescript interface for custom components. Components are separated by a comma, in the format \`ComponentName [props: { ... }, details: "..."]\`. The \`props\` indicate what \`props\` it must accept, in Typescript format. The \`details\` is an optional field, and describes what the component does. DO NOT hallucinate props. Use the details to better your understanding of how to generate the UI.
58
76
  2. Parse Context: Read \`UserContext\` for specific design requests.
59
- 3. Parse Data: Analyze \`Value\` to determine structure.
60
- 4. Check Skeleton: Read \`IsSkeleton\`. If it is \`true\`, that means all the property values have been replaced by the *type* of the value. If it is \`false\`, then you are seeing the raw values of each property. Use this information to bind properly.
77
+ 3. Parse Actions: Read \`AvailableActions\`.
78
+ - This list contains functions you can attach to events (onClick, onHover, etc.).
79
+ - The format is \`actionName(params): description\`.
80
+ - ONLY use actions listed here. Do not invent function names.
81
+ - Pay close attention to the \`params\` signature to ensure you pass the correct data types in the \`args\` array.
82
+ 4. Parse Data: Analyze \`Value\` to determine structure.
83
+ 5. Check Skeleton: Read \`IsSkeleton\`. If it is \`true\`, that means all the property values have been replaced by the *type* of the value. If it is \`false\`, then you are seeing the raw values of each property.
61
84
  </input_processing_rules>
62
85
 
63
86
  <output_formatting>
@@ -67,6 +90,7 @@ Input:
67
90
  <AllowedComponents>...</AllowedComponents>
68
91
  <ComponentContext>...</ComponentContext>
69
92
  <UserContext>...</UserContext>
93
+ <AvailableActions>...</AvailableActions>
70
94
  <IsSkeleton>...</IsSkeleton>
71
95
  <Value>...</Value>
72
96
 
@@ -20,5 +20,10 @@ type SyntuxComponent = {
20
20
  component: React.ComponentType<any>;
21
21
  context?: string;
22
22
  };
23
+ type ContextfulAction = {
24
+ fn: Function;
25
+ params: string;
26
+ context: string;
27
+ };
23
28
 
24
- export type { ComponentMap as C, SyntuxComponent as S, UISchema as U, SchemaNode as a, ChildrenMap as b };
29
+ export type { ContextfulAction as C, SyntuxComponent as S, UISchema as U, SchemaNode as a, ComponentMap as b, ChildrenMap as c };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getsyntux",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "The declarative generative-UI library.",
5
5
  "exports": {
6
6
  ".": {