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.
- package/README.md +230 -0
- package/lib/commonjs/GenUINode.js +113 -0
- package/lib/commonjs/GenUINode.js.map +1 -0
- package/lib/commonjs/GenerativeUIView.js +116 -0
- package/lib/commonjs/GenerativeUIView.js.map +1 -0
- package/lib/commonjs/index.js +78 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/parseGenUIProps.js +53 -0
- package/lib/commonjs/parseGenUIProps.js.map +1 -0
- package/lib/commonjs/prompt.js +27 -0
- package/lib/commonjs/prompt.js.map +1 -0
- package/lib/commonjs/registry.js +70 -0
- package/lib/commonjs/registry.js.map +1 -0
- package/lib/commonjs/tools.js +386 -0
- package/lib/commonjs/tools.js.map +1 -0
- package/lib/module/GenUINode.js +108 -0
- package/lib/module/GenUINode.js.map +1 -0
- package/lib/module/GenerativeUIView.js +111 -0
- package/lib/module/GenerativeUIView.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/parseGenUIProps.js +49 -0
- package/lib/module/parseGenUIProps.js.map +1 -0
- package/lib/module/prompt.js +23 -0
- package/lib/module/prompt.js.map +1 -0
- package/lib/module/registry.js +66 -0
- package/lib/module/registry.js.map +1 -0
- package/lib/module/tools.js +383 -0
- package/lib/module/tools.js.map +1 -0
- package/lib/typescript/GenUINode.d.ts +12 -0
- package/lib/typescript/GenUINode.d.ts.map +1 -0
- package/lib/typescript/GenerativeUIView.d.ts +23 -0
- package/lib/typescript/GenerativeUIView.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/parseGenUIProps.d.ts +20 -0
- package/lib/typescript/parseGenUIProps.d.ts.map +1 -0
- package/lib/typescript/prompt.d.ts +12 -0
- package/lib/typescript/prompt.d.ts.map +1 -0
- package/lib/typescript/registry.d.ts +36 -0
- package/lib/typescript/registry.d.ts.map +1 -0
- package/lib/typescript/tools.d.ts +119 -0
- package/lib/typescript/tools.d.ts.map +1 -0
- package/package.json +56 -0
- package/src/GenUINode.tsx +115 -0
- package/src/GenerativeUIView.tsx +137 -0
- package/src/index.ts +25 -0
- package/src/parseGenUIProps.ts +59 -0
- package/src/prompt.ts +49 -0
- package/src/registry.ts +73 -0
- 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
|