json-ui-lite-rn 0.12.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.
Files changed (52) hide show
  1. package/README.md +230 -0
  2. package/lib/commonjs/GenUINode.js +113 -0
  3. package/lib/commonjs/GenUINode.js.map +1 -0
  4. package/lib/commonjs/GenerativeUIView.js +116 -0
  5. package/lib/commonjs/GenerativeUIView.js.map +1 -0
  6. package/lib/commonjs/index.js +78 -0
  7. package/lib/commonjs/index.js.map +1 -0
  8. package/lib/commonjs/package.json +1 -0
  9. package/lib/commonjs/parseGenUIProps.js +53 -0
  10. package/lib/commonjs/parseGenUIProps.js.map +1 -0
  11. package/lib/commonjs/prompt.js +27 -0
  12. package/lib/commonjs/prompt.js.map +1 -0
  13. package/lib/commonjs/registry.js +70 -0
  14. package/lib/commonjs/registry.js.map +1 -0
  15. package/lib/commonjs/tools.js +386 -0
  16. package/lib/commonjs/tools.js.map +1 -0
  17. package/lib/module/GenUINode.js +108 -0
  18. package/lib/module/GenUINode.js.map +1 -0
  19. package/lib/module/GenerativeUIView.js +111 -0
  20. package/lib/module/GenerativeUIView.js.map +1 -0
  21. package/lib/module/index.js +9 -0
  22. package/lib/module/index.js.map +1 -0
  23. package/lib/module/parseGenUIProps.js +49 -0
  24. package/lib/module/parseGenUIProps.js.map +1 -0
  25. package/lib/module/prompt.js +23 -0
  26. package/lib/module/prompt.js.map +1 -0
  27. package/lib/module/registry.js +66 -0
  28. package/lib/module/registry.js.map +1 -0
  29. package/lib/module/tools.js +383 -0
  30. package/lib/module/tools.js.map +1 -0
  31. package/lib/typescript/GenUINode.d.ts +12 -0
  32. package/lib/typescript/GenUINode.d.ts.map +1 -0
  33. package/lib/typescript/GenerativeUIView.d.ts +23 -0
  34. package/lib/typescript/GenerativeUIView.d.ts.map +1 -0
  35. package/lib/typescript/index.d.ts +8 -0
  36. package/lib/typescript/index.d.ts.map +1 -0
  37. package/lib/typescript/parseGenUIProps.d.ts +20 -0
  38. package/lib/typescript/parseGenUIProps.d.ts.map +1 -0
  39. package/lib/typescript/prompt.d.ts +12 -0
  40. package/lib/typescript/prompt.d.ts.map +1 -0
  41. package/lib/typescript/registry.d.ts +36 -0
  42. package/lib/typescript/registry.d.ts.map +1 -0
  43. package/lib/typescript/tools.d.ts +119 -0
  44. package/lib/typescript/tools.d.ts.map +1 -0
  45. package/package.json +56 -0
  46. package/src/GenUINode.tsx +115 -0
  47. package/src/GenerativeUIView.tsx +137 -0
  48. package/src/index.ts +25 -0
  49. package/src/parseGenUIProps.ts +59 -0
  50. package/src/prompt.ts +49 -0
  51. package/src/registry.ts +73 -0
  52. package/src/tools.ts +392 -0
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
5
+ import { GenUINode } from './GenUINode';
6
+ import { GEN_UI_STYLES } from './registry';
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ export function GenerativeUIView({
9
+ spec,
10
+ loading,
11
+ showCollapsibleJSON,
12
+ styles: stylesOverride,
13
+ GenUINodeComponent
14
+ }) {
15
+ const [expanded, setExpanded] = React.useState(false);
16
+ const normalized = React.useMemo(() => {
17
+ if (!spec?.root || !spec.elements) return null;
18
+ const rootElement = spec.elements[spec.root];
19
+ if (!rootElement) return null;
20
+ return spec;
21
+ }, [spec]);
22
+ const styleValidators = React.useMemo(() => ({
23
+ ...GEN_UI_STYLES,
24
+ ...stylesOverride
25
+ }), [stylesOverride]);
26
+ if (!normalized) {
27
+ return /*#__PURE__*/_jsx(View, {
28
+ style: styles.placeholder,
29
+ children: /*#__PURE__*/_jsx(Text, {
30
+ style: styles.placeholderText,
31
+ children: loading ? 'Loading…' : 'Use tools getGenUIRootNode, addNode, etc. to build the UI.'
32
+ })
33
+ });
34
+ }
35
+ const NodeRenderer = GenUINodeComponent ?? GenUINode;
36
+ return /*#__PURE__*/_jsxs(View, {
37
+ style: styles.wrapper,
38
+ children: [/*#__PURE__*/_jsx(NodeRenderer, {
39
+ nodeId: normalized.root,
40
+ elements: normalized.elements,
41
+ styles: styleValidators,
42
+ GenUINodeComponent: GenUINodeComponent
43
+ }), showCollapsibleJSON ? /*#__PURE__*/_jsxs(View, {
44
+ style: styles.jsonPanel,
45
+ children: [/*#__PURE__*/_jsxs(Pressable, {
46
+ onPress: () => setExpanded(prev => !prev),
47
+ style: styles.jsonHeader,
48
+ children: [/*#__PURE__*/_jsx(Text, {
49
+ style: styles.jsonHeaderText,
50
+ children: "UI JSON"
51
+ }), /*#__PURE__*/_jsx(Text, {
52
+ style: styles.jsonHeaderIcon,
53
+ children: expanded ? '[-]' : '[+]'
54
+ })]
55
+ }), expanded ? /*#__PURE__*/_jsx(Text, {
56
+ selectable: true,
57
+ style: styles.jsonText,
58
+ children: JSON.stringify(normalized, null, 2)
59
+ }) : null]
60
+ }) : null]
61
+ });
62
+ }
63
+ const styles = StyleSheet.create({
64
+ wrapper: {
65
+ flex: 1,
66
+ justifyContent: 'center',
67
+ gap: 8
68
+ },
69
+ jsonPanel: {
70
+ borderWidth: 1,
71
+ borderColor: '#e5e7eb',
72
+ borderRadius: 12,
73
+ paddingHorizontal: 12,
74
+ paddingVertical: 10,
75
+ backgroundColor: '#f9fafb'
76
+ },
77
+ jsonHeader: {
78
+ flexDirection: 'row',
79
+ justifyContent: 'space-between',
80
+ alignItems: 'center',
81
+ gap: 8
82
+ },
83
+ jsonHeaderText: {
84
+ fontSize: 13,
85
+ color: '#4b5563',
86
+ fontWeight: '600'
87
+ },
88
+ jsonHeaderIcon: {
89
+ fontSize: 12,
90
+ color: '#9ca3af',
91
+ fontWeight: '600'
92
+ },
93
+ jsonText: {
94
+ marginTop: 8,
95
+ fontSize: 12,
96
+ lineHeight: 18,
97
+ color: '#374151',
98
+ fontFamily: 'Menlo'
99
+ },
100
+ placeholder: {
101
+ flex: 1,
102
+ justifyContent: 'center',
103
+ padding: 24
104
+ },
105
+ placeholderText: {
106
+ fontSize: 15,
107
+ color: '#6b7280',
108
+ textAlign: 'center'
109
+ }
110
+ });
111
+ //# sourceMappingURL=GenerativeUIView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","Pressable","StyleSheet","Text","View","GenUINode","GEN_UI_STYLES","jsx","_jsx","jsxs","_jsxs","GenerativeUIView","spec","loading","showCollapsibleJSON","styles","stylesOverride","GenUINodeComponent","expanded","setExpanded","useState","normalized","useMemo","root","elements","rootElement","styleValidators","style","placeholder","children","placeholderText","NodeRenderer","wrapper","nodeId","jsonPanel","onPress","prev","jsonHeader","jsonHeaderText","jsonHeaderIcon","selectable","jsonText","JSON","stringify","create","flex","justifyContent","gap","borderWidth","borderColor","borderRadius","paddingHorizontal","paddingVertical","backgroundColor","flexDirection","alignItems","fontSize","color","fontWeight","marginTop","lineHeight","fontFamily","padding","textAlign"],"sourceRoot":"../../src","sources":["GenerativeUIView.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ export { GenerativeUIView } from './GenerativeUIView';
4
+ export { GenUINode } from './GenUINode';
5
+ export { parseGenUIElementProps } from './parseGenUIProps';
6
+ export { buildGenUISystemPrompt } from './prompt';
7
+ export { DEFAULT_GEN_UI_ROOT_ID, GEN_UI_NODE_HINTS, GEN_UI_NODE_NAMES, GEN_UI_NODE_NAMES_THAT_SUPPORT_CHILDREN, GEN_UI_STYLE_HINTS, GEN_UI_STYLES } from './registry';
8
+ export { createGenUITools } from './tools';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["GenerativeUIView","GenUINode","parseGenUIElementProps","buildGenUISystemPrompt","DEFAULT_GEN_UI_ROOT_ID","GEN_UI_NODE_HINTS","GEN_UI_NODE_NAMES","GEN_UI_NODE_NAMES_THAT_SUPPORT_CHILDREN","GEN_UI_STYLE_HINTS","GEN_UI_STYLES","createGenUITools"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SACEA,gBAAgB,QAEX,oBAAoB;AAC3B,SAASC,SAAS,QAA6B,aAAa;AAC5D,SAGEC,sBAAsB,QAEjB,mBAAmB;AAC1B,SACEC,sBAAsB,QAEjB,UAAU;AAEjB,SACEC,sBAAsB,EACtBC,iBAAiB,EACjBC,iBAAiB,EACjBC,uCAAuC,EACvCC,kBAAkB,EAClBC,aAAa,QACR,YAAY;AACnB,SAAuCC,gBAAgB,QAAQ,SAAS","ignoreList":[]}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Parses a JSON UI element's props into a baseStyle object (validated by style validators)
5
+ * and common fields (text, label). Use this in custom GenUINode implementations to reuse
6
+ * the same style and prop parsing as the default renderer.
7
+ */
8
+ export function parseGenUIElementProps(element, styleValidators, options) {
9
+ const {
10
+ nodeId = '',
11
+ type = ''
12
+ } = options ?? {};
13
+ const props = element.props ?? {};
14
+ const style = props.style;
15
+ const flex = props.flex;
16
+ const padding = props.padding;
17
+ const gap = props.gap;
18
+ const text = props.text ?? props.value ?? props.label;
19
+ const label = props.label ?? props.text;
20
+ const baseStyle = {
21
+ ...(flex != null ? {
22
+ flex
23
+ } : {}),
24
+ ...(padding != null ? {
25
+ padding
26
+ } : {}),
27
+ ...(gap != null ? {
28
+ gap
29
+ } : {}),
30
+ ...style
31
+ };
32
+ for (const key of Object.keys(props)) {
33
+ const validator = styleValidators[key];
34
+ if (validator) {
35
+ if (validator.safeParse(props[key]).success) {
36
+ baseStyle[key] = props[key];
37
+ } else {
38
+ console.warn(`[json-ui-lite-rn] Invalid style prop: ${key} for node ${nodeId} of type ${type}: ${props[key]}`);
39
+ }
40
+ }
41
+ }
42
+ return {
43
+ baseStyle,
44
+ text,
45
+ label,
46
+ props
47
+ };
48
+ }
49
+ //# sourceMappingURL=parseGenUIProps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["parseGenUIElementProps","element","styleValidators","options","nodeId","type","props","style","flex","padding","gap","text","value","label","baseStyle","key","Object","keys","validator","safeParse","success","console","warn"],"sourceRoot":"../../src","sources":["parseGenUIProps.ts"],"mappings":";;AAkBA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,sBAAsBA,CACpCC,OAAsB,EACtBC,eAAkC,EAClCC,OAAuC,EACrB;EAClB,MAAM;IAAEC,MAAM,GAAG,EAAE;IAAEC,IAAI,GAAG;EAAG,CAAC,GAAGF,OAAO,IAAI,CAAC,CAAC;EAChD,MAAMG,KAAK,GAAGL,OAAO,CAACK,KAAK,IAAI,CAAC,CAAC;EACjC,MAAMC,KAAK,GAAGD,KAAK,CAACC,KAA4C;EAChE,MAAMC,IAAI,GAAGF,KAAK,CAACE,IAA0B;EAC7C,MAAMC,OAAO,GAAGH,KAAK,CAACG,OAA6B;EACnD,MAAMC,GAAG,GAAGJ,KAAK,CAACI,GAAyB;EAC3C,MAAMC,IAAI,GAAIL,KAAK,CAACK,IAAI,IAAIL,KAAK,CAACM,KAAK,IAAIN,KAAK,CAACO,KAA4B;EAC7E,MAAMA,KAAK,GAAIP,KAAK,CAACO,KAAK,IAAIP,KAAK,CAACK,IAA2B;EAE/D,MAAMG,SAAS,GAAG;IAChB,IAAIN,IAAI,IAAI,IAAI,GAAG;MAAEA;IAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,IAAIC,OAAO,IAAI,IAAI,GAAG;MAAEA;IAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,IAAIC,GAAG,IAAI,IAAI,GAAG;MAAEA;IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,GAAGH;EACL,CAA4B;EAE5B,KAAK,MAAMQ,GAAG,IAAIC,MAAM,CAACC,IAAI,CAACX,KAAK,CAAC,EAAE;IACpC,MAAMY,SAAS,GAAGhB,eAAe,CAACa,GAAG,CAAC;IACtC,IAAIG,SAAS,EAAE;MACb,IAAIA,SAAS,CAACC,SAAS,CAACb,KAAK,CAACS,GAAG,CAAC,CAAC,CAACK,OAAO,EAAE;QAC3CN,SAAS,CAACC,GAAG,CAAC,GAAGT,KAAK,CAACS,GAAG,CAAC;MAC7B,CAAC,MAAM;QACLM,OAAO,CAACC,IAAI,CACV,yCAAyCP,GAAG,aAAaX,MAAM,YAAYC,IAAI,KAAKC,KAAK,CAACS,GAAG,CAAC,EAChG,CAAC;MACH;IACF;EACF;EAEA,OAAO;IAAED,SAAS;IAAEH,IAAI;IAAEE,KAAK;IAAEP;EAAM,CAAC;AAC1C","ignoreList":[]}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ import { GEN_UI_STYLE_HINTS } from './registry';
4
+ export function buildGenUISystemPrompt({
5
+ additionalInstructions,
6
+ requireLayoutReadBeforeAddingNodes = true,
7
+ styleHints = GEN_UI_STYLE_HINTS
8
+ } = {}) {
9
+ const styleKeysText = Object.entries(styleHints).map(([key, entry]) => {
10
+ let text = `${key} [${entry.type}]`;
11
+ if (entry.description) text += ` (${entry.description})`;
12
+ return text;
13
+ }).join(', ');
14
+ const parts = ['You are a helpful assistant.', 'You have tools to create and update UI nodes. Before any tool calls for UI, ALWAYS CALL getUILayout before and after.', 'Remember this is React Native, not web, and use simple props.', `If you set the "style" prop on a UI node, the possible keys are: ${styleKeysText}.`, 'Remember NEVER use web values.'];
15
+ if (requireLayoutReadBeforeAddingNodes) {
16
+ parts.push('BEFORE ADDING ANY UI ELEMENTS, GET THE WHOLE UI TREE with getGenUILayout.');
17
+ }
18
+ if (additionalInstructions?.trim()) {
19
+ parts.push(additionalInstructions.trim());
20
+ }
21
+ return parts.join(' ');
22
+ }
23
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["GEN_UI_STYLE_HINTS","buildGenUISystemPrompt","additionalInstructions","requireLayoutReadBeforeAddingNodes","styleHints","styleKeysText","Object","entries","map","key","entry","text","type","description","join","parts","push","trim"],"sourceRoot":"../../src","sources":["prompt.ts"],"mappings":";;AAAA,SAASA,kBAAkB,QAAQ,YAAY;AAgB/C,OAAO,SAASC,sBAAsBA,CAAC;EACrCC,sBAAsB;EACtBC,kCAAkC,GAAG,IAAI;EACzCC,UAAU,GAAGJ;AACgB,CAAC,GAAG,CAAC,CAAC,EAAE;EACrC,MAAMK,aAAa,GAAGC,MAAM,CAACC,OAAO,CAACH,UAAU,CAAC,CAC7CI,GAAG,CAAC,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK;IACrB,IAAIC,IAAI,GAAG,GAAGF,GAAG,KAAKC,KAAK,CAACE,IAAI,GAAG;IACnC,IAAIF,KAAK,CAACG,WAAW,EAAEF,IAAI,IAAI,KAAKD,KAAK,CAACG,WAAW,GAAG;IACxD,OAAOF,IAAI;EACb,CAAC,CAAC,CACDG,IAAI,CAAC,IAAI,CAAC;EAEb,MAAMC,KAAK,GAAG,CACZ,8BAA8B,EAC9B,uHAAuH,EACvH,+DAA+D,EAC/D,oEAAoEV,aAAa,GAAG,EACpF,gCAAgC,CACjC;EAED,IAAIF,kCAAkC,EAAE;IACtCY,KAAK,CAACC,IAAI,CACR,2EACF,CAAC;EACH;EAEA,IAAId,sBAAsB,EAAEe,IAAI,CAAC,CAAC,EAAE;IAClCF,KAAK,CAACC,IAAI,CAACd,sBAAsB,CAACe,IAAI,CAAC,CAAC,CAAC;EAC3C;EAEA,OAAOF,KAAK,CAACD,IAAI,CAAC,GAAG,CAAC;AACxB","ignoreList":[]}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ import { z } from 'zod';
4
+ export const DEFAULT_GEN_UI_ROOT_ID = 'root';
5
+ export const GEN_UI_NODE_NAMES = {
6
+ Text: 'Text',
7
+ Paragraph: 'Paragraph',
8
+ Label: 'Label',
9
+ Heading: 'Heading',
10
+ Button: 'Button',
11
+ TextInput: 'TextInput'
12
+ };
13
+ export const GEN_UI_NODE_NAMES_THAT_SUPPORT_CHILDREN = [];
14
+ export const GEN_UI_NODE_HINTS = {
15
+ Text: 'Single line text. Props: text [string], style [object].',
16
+ Paragraph: 'Long text. Props: text [string], style [object].',
17
+ Label: 'Small label. Props: text [string], style [object].',
18
+ Heading: 'Title text. Props: text [string], style [object].',
19
+ Button: 'Tap button. Props: text [string], style [object].',
20
+ TextInput: 'Single line text input. Props: placeholder [string], style [object].'
21
+ };
22
+ export const GEN_UI_STYLES = {
23
+ flex: z.number(),
24
+ padding: z.number(),
25
+ gap: z.number(),
26
+ backgroundColor: z.string(),
27
+ color: z.string(),
28
+ fontSize: z.number(),
29
+ fontWeight: z.string(),
30
+ textAlign: z.string()
31
+ };
32
+ export const GEN_UI_STYLE_HINTS = {
33
+ flex: {
34
+ type: 'number',
35
+ description: 'Flex grow/shrink basis value.'
36
+ },
37
+ padding: {
38
+ type: 'number',
39
+ description: 'Padding in density-independent px.'
40
+ },
41
+ gap: {
42
+ type: 'number',
43
+ description: 'Spacing between children in density-independent px.'
44
+ },
45
+ backgroundColor: {
46
+ type: 'string',
47
+ description: 'React Native color value.'
48
+ },
49
+ color: {
50
+ type: 'string',
51
+ description: 'Text color value.'
52
+ },
53
+ fontSize: {
54
+ type: 'number',
55
+ description: 'Font size in points.'
56
+ },
57
+ fontWeight: {
58
+ type: 'string',
59
+ description: 'Font weight string like "400", "600", "bold".'
60
+ },
61
+ textAlign: {
62
+ type: 'string',
63
+ description: 'Text alignment, for example "left", "center", "right".'
64
+ }
65
+ };
66
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["z","DEFAULT_GEN_UI_ROOT_ID","GEN_UI_NODE_NAMES","Text","Paragraph","Label","Heading","Button","TextInput","GEN_UI_NODE_NAMES_THAT_SUPPORT_CHILDREN","GEN_UI_NODE_HINTS","GEN_UI_STYLES","flex","number","padding","gap","backgroundColor","string","color","fontSize","fontWeight","textAlign","GEN_UI_STYLE_HINTS","type","description"],"sourceRoot":"../../src","sources":["registry.ts"],"mappings":";;AAAA,SAASA,CAAC,QAAQ,KAAK;AAavB,OAAO,MAAMC,sBAAsB,GAAG,MAAM;AAE5C,OAAO,MAAMC,iBAAiB,GAAG;EAC/BC,IAAI,EAAE,MAAM;EACZC,SAAS,EAAE,WAAW;EACtBC,KAAK,EAAE,OAAO;EACdC,OAAO,EAAE,SAAS;EAClBC,MAAM,EAAE,QAAQ;EAChBC,SAAS,EAAE;AACb,CAAU;AAEV,OAAO,MAAMC,uCAAiD,GAAG,EAAE;AAEnE,OAAO,MAAMC,iBAAiE,GAC5E;EACEP,IAAI,EAAE,yDAAyD;EAC/DC,SAAS,EAAE,kDAAkD;EAC7DC,KAAK,EAAE,oDAAoD;EAC3DC,OAAO,EAAE,mDAAmD;EAC5DC,MAAM,EAAE,mDAAmD;EAC3DC,SAAS,EACP;AACJ,CAAC;AAEH,OAAO,MAAMG,aAAa,GAAG;EAC3BC,IAAI,EAAEZ,CAAC,CAACa,MAAM,CAAC,CAAC;EAChBC,OAAO,EAAEd,CAAC,CAACa,MAAM,CAAC,CAAC;EACnBE,GAAG,EAAEf,CAAC,CAACa,MAAM,CAAC,CAAC;EACfG,eAAe,EAAEhB,CAAC,CAACiB,MAAM,CAAC,CAAC;EAC3BC,KAAK,EAAElB,CAAC,CAACiB,MAAM,CAAC,CAAC;EACjBE,QAAQ,EAAEnB,CAAC,CAACa,MAAM,CAAC,CAAC;EACpBO,UAAU,EAAEpB,CAAC,CAACiB,MAAM,CAAC,CAAC;EACtBI,SAAS,EAAErB,CAAC,CAACiB,MAAM,CAAC;AACtB,CAAC;AAED,OAAO,MAAMK,kBAGZ,GAAG;EACFV,IAAI,EAAE;IAAEW,IAAI,EAAE,QAAQ;IAAEC,WAAW,EAAE;EAAgC,CAAC;EACtEV,OAAO,EAAE;IACPS,IAAI,EAAE,QAAQ;IACdC,WAAW,EAAE;EACf,CAAC;EACDT,GAAG,EAAE;IACHQ,IAAI,EAAE,QAAQ;IACdC,WAAW,EAAE;EACf,CAAC;EACDR,eAAe,EAAE;IAAEO,IAAI,EAAE,QAAQ;IAAEC,WAAW,EAAE;EAA4B,CAAC;EAC7EN,KAAK,EAAE;IAAEK,IAAI,EAAE,QAAQ;IAAEC,WAAW,EAAE;EAAoB,CAAC;EAC3DL,QAAQ,EAAE;IAAEI,IAAI,EAAE,QAAQ;IAAEC,WAAW,EAAE;EAAuB,CAAC;EACjEJ,UAAU,EAAE;IACVG,IAAI,EAAE,QAAQ;IACdC,WAAW,EAAE;EACf,CAAC;EACDH,SAAS,EAAE;IACTE,IAAI,EAAE,QAAQ;IACdC,WAAW,EAAE;EACf;AACF,CAAC","ignoreList":[]}
@@ -0,0 +1,383 @@
1
+ "use strict";
2
+
3
+ import { tool } from 'ai';
4
+ import { z } from 'zod';
5
+ import { DEFAULT_GEN_UI_ROOT_ID, GEN_UI_NODE_HINTS, GEN_UI_NODE_NAMES_THAT_SUPPORT_CHILDREN } from './registry';
6
+
7
+ /**
8
+ * Sometimes LLMs call tools with a string instead of an object.
9
+ */
10
+ function smartParse(props) {
11
+ return typeof props === 'string' ? JSON.parse(props) : props;
12
+ }
13
+ const defaultCreateId = () => `UI-${Date.now().toString(36)}-${Math.random().toString(16).slice(2)}`;
14
+ const cloneSpec = spec => ({
15
+ ...spec,
16
+ elements: Object.fromEntries(Object.entries(spec.elements).map(([id, element]) => [id, {
17
+ ...element,
18
+ props: {
19
+ ...(element.props ?? {})
20
+ },
21
+ children: [...(element.children ?? [])]
22
+ }]))
23
+ });
24
+ let mutationQueue = Promise.resolve();
25
+ const withMutationLock = async run => {
26
+ let release = () => {};
27
+ const pending = new Promise(resolve => {
28
+ release = resolve;
29
+ });
30
+ const previous = mutationQueue;
31
+ mutationQueue = mutationQueue.then(() => pending);
32
+ await previous;
33
+ try {
34
+ return await run();
35
+ } finally {
36
+ release();
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Creates generative UI tools that read/update a JSON UI spec.
42
+ */
43
+ export function createGenUITools({
44
+ contextId,
45
+ getSpec,
46
+ updateSpec,
47
+ createId = defaultCreateId,
48
+ rootId = DEFAULT_GEN_UI_ROOT_ID,
49
+ nodeHints = GEN_UI_NODE_HINTS,
50
+ nodeNamesThatSupportChildren = GEN_UI_NODE_NAMES_THAT_SUPPORT_CHILDREN,
51
+ toolWrapper = (_, execute) => execute
52
+ }) {
53
+ // Serialize mutating tool calls to avoid interleaving writes.
54
+ let cachedSpec = null;
55
+ const readSpec = () => {
56
+ if (cachedSpec) return cloneSpec(cachedSpec);
57
+ const spec = getSpec(contextId);
58
+ if (!spec) return null;
59
+ cachedSpec = cloneSpec(spec);
60
+ return cloneSpec(cachedSpec);
61
+ };
62
+ const commitSpec = spec => {
63
+ cachedSpec = spec ? cloneSpec(spec) : null;
64
+ updateSpec(contextId, spec ? cloneSpec(spec) : null);
65
+ };
66
+ const getUIRootNode = tool({
67
+ description: 'Get the root node of the generative UI tree. Returns id, type, props, and children (array of { id, type }). Root always exists with id "root".',
68
+ inputSchema: z.object({}),
69
+ execute: toolWrapper('getUIRootNode', async () => {
70
+ const spec = readSpec();
71
+ if (!spec?.root || !spec.elements[spec.root]) return {
72
+ root: null
73
+ };
74
+ const element = spec.elements[spec.root];
75
+ const children = (element.children ?? []).map(id => ({
76
+ id,
77
+ type: spec.elements[id]?.type ?? 'unknown'
78
+ }));
79
+ return {
80
+ root: {
81
+ id: spec.root,
82
+ type: element.type,
83
+ props: element.props,
84
+ children
85
+ }
86
+ };
87
+ })
88
+ });
89
+ const getUINode = tool({
90
+ description: 'Get a node by id. Returns id, type, props, and children (array of { id, type }). If id is omitted, returns root node.',
91
+ inputSchema: z.object({
92
+ id: z.string().optional().describe('Node id; omit for root')
93
+ }),
94
+ execute: toolWrapper('getUINode', async ({
95
+ id
96
+ }) => {
97
+ const spec = readSpec();
98
+ if (!spec) return {
99
+ node: null
100
+ };
101
+ const nodeId = id ?? spec.root;
102
+ const element = spec.elements[nodeId];
103
+ if (!element) return {
104
+ node: null
105
+ };
106
+ const children = (element.children ?? []).map(childId => ({
107
+ id: childId,
108
+ type: spec.elements[childId]?.type ?? 'unknown'
109
+ }));
110
+ return {
111
+ node: {
112
+ id: nodeId,
113
+ type: element.type,
114
+ props: element.props,
115
+ children
116
+ }
117
+ };
118
+ })
119
+ });
120
+ const getUILayout = tool({
121
+ description: 'Get compact UI layout.',
122
+ inputSchema: z.object({}),
123
+ execute: toolWrapper('getUILayout', async () => {
124
+ const spec = readSpec();
125
+ if (!spec) return {
126
+ root: null,
127
+ nodes: []
128
+ };
129
+ const parentByChild = {};
130
+ for (const [id, element] of Object.entries(spec.elements)) {
131
+ for (const childId of element.children ?? []) {
132
+ parentByChild[childId] = id;
133
+ }
134
+ }
135
+ const nodes = Object.entries(spec.elements).map(([id, element]) => ({
136
+ id,
137
+ type: element.type,
138
+ parentId: parentByChild[id] ?? null,
139
+ children: element.children ?? [],
140
+ props: Object.keys(element.props ?? {})
141
+ }));
142
+ return {
143
+ root: spec.root,
144
+ nodes
145
+ };
146
+ })
147
+ });
148
+ const getAvailableUINodes = tool({
149
+ description: 'List nodes + props.',
150
+ inputSchema: z.object({}),
151
+ execute: toolWrapper('getAvailableUINodes', async () => ({
152
+ nodes: Object.entries(nodeHints).map(([name, props]) => ({
153
+ name,
154
+ props
155
+ }))
156
+ }))
157
+ });
158
+ const setUINodeProps = tool({
159
+ description: 'Set or add props for a node by id.',
160
+ inputSchema: z.object({
161
+ id: z.string().describe('Node id'),
162
+ props: z.string().describe('Props object for the node'),
163
+ replace: z.boolean().optional().describe('Replace existing props')
164
+ }),
165
+ execute: toolWrapper('setUINodeProps', async ({
166
+ id,
167
+ props: propsArg,
168
+ replace = false
169
+ }) => withMutationLock(async () => {
170
+ const parsedProps = smartParse(propsArg);
171
+ const spec = readSpec();
172
+ if (!spec) return {
173
+ success: false,
174
+ message: 'No UI spec'
175
+ };
176
+ if (!spec.elements[id]) {
177
+ return {
178
+ success: false,
179
+ message: 'Node not found'
180
+ };
181
+ }
182
+ const elements = {
183
+ ...spec.elements
184
+ };
185
+ const current = elements[id];
186
+ const nextProps = replace ? parsedProps : {
187
+ ...current.props,
188
+ ...parsedProps
189
+ };
190
+ elements[id] = {
191
+ ...current,
192
+ props: nextProps
193
+ };
194
+ commitSpec({
195
+ root: spec.root,
196
+ elements
197
+ });
198
+ return {
199
+ success: true
200
+ };
201
+ }))
202
+ });
203
+ const deleteUINode = tool({
204
+ description: 'Delete a node by id. Cannot delete the root node (id "root"). Removes the node and its reference from the parent\'s children.',
205
+ inputSchema: z.object({
206
+ id: z.string().describe('Node id to delete')
207
+ }),
208
+ execute: toolWrapper('deleteUINode', async ({
209
+ id
210
+ }) => withMutationLock(async () => {
211
+ if (id === rootId) {
212
+ return {
213
+ success: false,
214
+ message: 'Cannot delete root node'
215
+ };
216
+ }
217
+ const spec = readSpec();
218
+ if (!spec) return {
219
+ success: false,
220
+ message: 'No UI spec'
221
+ };
222
+ const elements = {
223
+ ...spec.elements
224
+ };
225
+ delete elements[id];
226
+ for (const key of Object.keys(elements)) {
227
+ const element = elements[key];
228
+ if (element.children?.includes(id)) {
229
+ elements[key] = {
230
+ ...element,
231
+ children: element.children.filter(childId => childId !== id)
232
+ };
233
+ }
234
+ }
235
+ commitSpec({
236
+ root: spec.root,
237
+ elements
238
+ });
239
+ return {
240
+ success: true
241
+ };
242
+ }))
243
+ });
244
+ const addUINode = tool({
245
+ description: 'Add a new node as a child of parentId. Creates element with type and props. Returns new node id. Props must be a valid JSON object.',
246
+ inputSchema: z.object({
247
+ parentId: z.string().optional().describe('Parent node id; omit for root'),
248
+ type: z.string().describe('Component type (e.g. Container, Column, Text, Button)'),
249
+ props: z.string().optional().describe('Props object for the node')
250
+ }),
251
+ execute: toolWrapper('addUINode', async ({
252
+ parentId,
253
+ type,
254
+ props: propsArg
255
+ }) => withMutationLock(async () => {
256
+ const parsedProps = smartParse(propsArg ?? '{}');
257
+ const spec = readSpec();
258
+ if (!spec) {
259
+ console.warn('[json-ui-lite-rn tool addNode] No UI spec, aborting');
260
+ return {
261
+ success: false,
262
+ message: 'No UI spec'
263
+ };
264
+ }
265
+ parentId ??= spec.root;
266
+ if (!spec.elements[parentId]) {
267
+ console.warn('[json-ui-lite-rn tool addNode] Parent not found, aborting');
268
+ return {
269
+ success: false,
270
+ message: 'Parent not found'
271
+ };
272
+ }
273
+ const newId = createId();
274
+ spec.elements[newId] = {
275
+ type,
276
+ props: parsedProps ?? {},
277
+ children: []
278
+ };
279
+ let parent = spec.elements[parentId];
280
+ if (!nodeNamesThatSupportChildren.includes(parent.type)) {
281
+ parent = spec.elements[spec.root];
282
+ parentId = spec.root;
283
+ }
284
+ spec.elements[parentId].children ??= [];
285
+ spec.elements[parentId].children.push(newId);
286
+ commitSpec({
287
+ root: spec.root,
288
+ elements: spec.elements
289
+ });
290
+ return {
291
+ success: true,
292
+ id: newId
293
+ };
294
+ }))
295
+ });
296
+ const reorderUINodes = tool({
297
+ description: 'Move one node among siblings by offset (negative = up, positive = down).',
298
+ inputSchema: z.object({
299
+ nodeId: z.string().describe('Node id to move'),
300
+ offset: z.number().describe('Relative index shift among siblings; negative moves earlier, positive moves later')
301
+ }),
302
+ execute: toolWrapper('reorderUINodes', async ({
303
+ nodeId,
304
+ offset
305
+ }) => withMutationLock(async () => {
306
+ const spec = readSpec();
307
+ if (!spec) return {
308
+ success: false,
309
+ message: 'No UI spec'
310
+ };
311
+ const findParentId = childId => {
312
+ for (const [id, element] of Object.entries(spec.elements)) {
313
+ if (element.children?.includes(childId)) return id;
314
+ }
315
+ return null;
316
+ };
317
+ const nodeParentId = findParentId(nodeId);
318
+ if (!nodeParentId) {
319
+ return {
320
+ success: false,
321
+ message: 'nodeId must exist and have a parent'
322
+ };
323
+ }
324
+ const parentId = nodeParentId;
325
+ const parent = spec.elements[parentId];
326
+ if (!parent) return {
327
+ success: false,
328
+ message: 'Parent not found'
329
+ };
330
+ const currentChildren = [...(parent.children ?? [])];
331
+ const nodeIndex = currentChildren.indexOf(nodeId);
332
+ if (nodeIndex === -1) {
333
+ return {
334
+ success: false,
335
+ message: 'nodeId must be a direct child'
336
+ };
337
+ }
338
+ if (offset === 0) {
339
+ return {
340
+ success: true,
341
+ parentId,
342
+ nodeId,
343
+ fromIndex: nodeIndex,
344
+ toIndex: nodeIndex,
345
+ appliedOffset: 0,
346
+ childIds: currentChildren
347
+ };
348
+ }
349
+ const maxIndex = currentChildren.length - 1;
350
+ const toIndex = Math.min(Math.max(nodeIndex + offset, 0), maxIndex);
351
+ currentChildren.splice(nodeIndex, 1);
352
+ currentChildren.splice(toIndex, 0, nodeId);
353
+ const elements = {
354
+ ...spec.elements
355
+ };
356
+ elements[parentId].children = currentChildren;
357
+ commitSpec({
358
+ root: spec.root,
359
+ elements
360
+ });
361
+ return {
362
+ success: true,
363
+ parentId,
364
+ nodeId,
365
+ fromIndex: nodeIndex,
366
+ toIndex,
367
+ appliedOffset: toIndex - nodeIndex,
368
+ childIds: currentChildren
369
+ };
370
+ }))
371
+ });
372
+ return {
373
+ getUIRootNode,
374
+ getUINode,
375
+ getUILayout,
376
+ getAvailableUINodes,
377
+ setUINodeProps,
378
+ deleteUINode,
379
+ addUINode,
380
+ reorderUINodes
381
+ };
382
+ }
383
+ //# sourceMappingURL=tools.js.map