@json-render/react 0.2.0 → 0.4.2
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 +250 -183
- package/dist/chunk-IGPI5WNB.mjs +52 -0
- package/dist/chunk-IGPI5WNB.mjs.map +1 -0
- package/dist/index.d.mts +188 -16
- package/dist/index.d.ts +188 -16
- package/dist/index.js +195 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +153 -38
- package/dist/index.mjs.map +1 -1
- package/dist/schema.d.mts +106 -0
- package/dist/schema.d.ts +106 -0
- package/dist/schema.js +77 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.mjs +9 -0
- package/dist/schema.mjs.map +1 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -37,8 +37,12 @@ __export(index_exports, {
|
|
|
37
37
|
Renderer: () => Renderer,
|
|
38
38
|
ValidationProvider: () => ValidationProvider,
|
|
39
39
|
VisibilityProvider: () => VisibilityProvider,
|
|
40
|
+
createRenderer: () => createRenderer,
|
|
40
41
|
createRendererFromCatalog: () => createRendererFromCatalog,
|
|
42
|
+
defineRegistry: () => defineRegistry,
|
|
43
|
+
elementTreeSchema: () => elementTreeSchema,
|
|
41
44
|
flatToTree: () => flatToTree,
|
|
45
|
+
schema: () => schema,
|
|
42
46
|
useAction: () => useAction,
|
|
43
47
|
useActions: () => useActions,
|
|
44
48
|
useData: () => useData,
|
|
@@ -64,6 +68,16 @@ function DataProvider({
|
|
|
64
68
|
children
|
|
65
69
|
}) {
|
|
66
70
|
const [data, setData] = (0, import_react.useState)(initialData);
|
|
71
|
+
const initialDataJsonRef = (0, import_react.useRef)(JSON.stringify(initialData));
|
|
72
|
+
(0, import_react.useEffect)(() => {
|
|
73
|
+
const newJson = JSON.stringify(initialData);
|
|
74
|
+
if (newJson !== initialDataJsonRef.current) {
|
|
75
|
+
initialDataJsonRef.current = newJson;
|
|
76
|
+
if (initialData && Object.keys(initialData).length > 0) {
|
|
77
|
+
setData((prev) => ({ ...prev, ...initialData }));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, [initialData]);
|
|
67
81
|
const get = (0, import_react.useCallback)((path) => (0, import_core.getByPath)(data, path), [data]);
|
|
68
82
|
const set = (0, import_react.useCallback)(
|
|
69
83
|
(path, value2) => {
|
|
@@ -109,12 +123,12 @@ function useData() {
|
|
|
109
123
|
return ctx;
|
|
110
124
|
}
|
|
111
125
|
function useDataValue(path) {
|
|
112
|
-
const {
|
|
113
|
-
return
|
|
126
|
+
const { data } = useData();
|
|
127
|
+
return (0, import_core.getByPath)(data, path);
|
|
114
128
|
}
|
|
115
129
|
function useDataBinding(path) {
|
|
116
|
-
const {
|
|
117
|
-
const value =
|
|
130
|
+
const { data, set } = useData();
|
|
131
|
+
const value = (0, import_core.getByPath)(data, path);
|
|
118
132
|
const setValue = (0, import_react.useCallback)(
|
|
119
133
|
(newValue) => set(path, newValue),
|
|
120
134
|
[path, set]
|
|
@@ -515,11 +529,58 @@ function useFieldValidation(path, config) {
|
|
|
515
529
|
};
|
|
516
530
|
}
|
|
517
531
|
|
|
532
|
+
// src/schema.ts
|
|
533
|
+
var import_core5 = require("@json-render/core");
|
|
534
|
+
var schema = (0, import_core5.defineSchema)((s) => ({
|
|
535
|
+
// What the AI-generated SPEC looks like
|
|
536
|
+
spec: s.object({
|
|
537
|
+
/** Root element key */
|
|
538
|
+
root: s.string(),
|
|
539
|
+
/** Flat map of elements by key */
|
|
540
|
+
elements: s.record(
|
|
541
|
+
s.object({
|
|
542
|
+
/** Unique key for this element */
|
|
543
|
+
key: s.string(),
|
|
544
|
+
/** Component type from catalog */
|
|
545
|
+
type: s.ref("catalog.components"),
|
|
546
|
+
/** Component props */
|
|
547
|
+
props: s.propsOf("catalog.components"),
|
|
548
|
+
/** Child element keys (flat reference) */
|
|
549
|
+
children: s.array(s.string()),
|
|
550
|
+
/** Parent element key (null for root) */
|
|
551
|
+
parentKey: s.string(),
|
|
552
|
+
/** Visibility condition */
|
|
553
|
+
visible: s.any()
|
|
554
|
+
})
|
|
555
|
+
)
|
|
556
|
+
}),
|
|
557
|
+
// What the CATALOG must provide
|
|
558
|
+
catalog: s.object({
|
|
559
|
+
/** Component definitions */
|
|
560
|
+
components: s.map({
|
|
561
|
+
/** Zod schema for component props */
|
|
562
|
+
props: s.zod(),
|
|
563
|
+
/** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
|
|
564
|
+
slots: s.array(s.string()),
|
|
565
|
+
/** Description for AI generation hints */
|
|
566
|
+
description: s.string()
|
|
567
|
+
}),
|
|
568
|
+
/** Action definitions (optional) */
|
|
569
|
+
actions: s.map({
|
|
570
|
+
/** Zod schema for action params */
|
|
571
|
+
params: s.zod(),
|
|
572
|
+
/** Description for AI generation hints */
|
|
573
|
+
description: s.string()
|
|
574
|
+
})
|
|
575
|
+
})
|
|
576
|
+
}));
|
|
577
|
+
var elementTreeSchema = schema;
|
|
578
|
+
|
|
518
579
|
// src/renderer.tsx
|
|
519
580
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
520
581
|
function ElementRenderer({
|
|
521
582
|
element,
|
|
522
|
-
|
|
583
|
+
spec,
|
|
523
584
|
registry,
|
|
524
585
|
loading,
|
|
525
586
|
fallback
|
|
@@ -535,7 +596,7 @@ function ElementRenderer({
|
|
|
535
596
|
return null;
|
|
536
597
|
}
|
|
537
598
|
const children = element.children?.map((childKey) => {
|
|
538
|
-
const childElement =
|
|
599
|
+
const childElement = spec.elements[childKey];
|
|
539
600
|
if (!childElement) {
|
|
540
601
|
return null;
|
|
541
602
|
}
|
|
@@ -543,7 +604,7 @@ function ElementRenderer({
|
|
|
543
604
|
ElementRenderer,
|
|
544
605
|
{
|
|
545
606
|
element: childElement,
|
|
546
|
-
|
|
607
|
+
spec,
|
|
547
608
|
registry,
|
|
548
609
|
loading,
|
|
549
610
|
fallback
|
|
@@ -553,11 +614,11 @@ function ElementRenderer({
|
|
|
553
614
|
});
|
|
554
615
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Component, { element, onAction: execute, loading, children });
|
|
555
616
|
}
|
|
556
|
-
function Renderer({
|
|
557
|
-
if (!
|
|
617
|
+
function Renderer({ spec, registry, loading, fallback }) {
|
|
618
|
+
if (!spec || !spec.root) {
|
|
558
619
|
return null;
|
|
559
620
|
}
|
|
560
|
-
const rootElement =
|
|
621
|
+
const rootElement = spec.elements[spec.root];
|
|
561
622
|
if (!rootElement) {
|
|
562
623
|
return null;
|
|
563
624
|
}
|
|
@@ -565,7 +626,7 @@ function Renderer({ tree, registry, loading, fallback }) {
|
|
|
565
626
|
ElementRenderer,
|
|
566
627
|
{
|
|
567
628
|
element: rootElement,
|
|
568
|
-
|
|
629
|
+
spec,
|
|
569
630
|
registry,
|
|
570
631
|
loading,
|
|
571
632
|
fallback
|
|
@@ -614,10 +675,93 @@ function createRendererFromCatalog(_catalog, registry) {
|
|
|
614
675
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Renderer, { ...props, registry });
|
|
615
676
|
};
|
|
616
677
|
}
|
|
678
|
+
function defineRegistry(_catalog, options) {
|
|
679
|
+
const registry = {};
|
|
680
|
+
if (options.components) {
|
|
681
|
+
for (const [name, componentFn] of Object.entries(options.components)) {
|
|
682
|
+
registry[name] = ({
|
|
683
|
+
element,
|
|
684
|
+
children,
|
|
685
|
+
onAction,
|
|
686
|
+
loading
|
|
687
|
+
}) => {
|
|
688
|
+
return componentFn({
|
|
689
|
+
props: element.props,
|
|
690
|
+
children,
|
|
691
|
+
onAction,
|
|
692
|
+
loading
|
|
693
|
+
});
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const actionMap = options.actions ? Object.entries(options.actions) : [];
|
|
698
|
+
const handlers = (getSetData, getData) => {
|
|
699
|
+
const result = {};
|
|
700
|
+
for (const [name, actionFn] of actionMap) {
|
|
701
|
+
result[name] = async (params) => {
|
|
702
|
+
const setData = getSetData();
|
|
703
|
+
const data = getData();
|
|
704
|
+
if (setData) {
|
|
705
|
+
await actionFn(params, setData, data);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return result;
|
|
710
|
+
};
|
|
711
|
+
const executeAction2 = async (actionName, params, setData, data = {}) => {
|
|
712
|
+
const entry = actionMap.find(([name]) => name === actionName);
|
|
713
|
+
if (entry) {
|
|
714
|
+
await entry[1](params, setData, data);
|
|
715
|
+
} else {
|
|
716
|
+
console.warn(`Unknown action: ${actionName}`);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
return { registry, handlers, executeAction: executeAction2 };
|
|
720
|
+
}
|
|
721
|
+
function createRenderer(catalog, components) {
|
|
722
|
+
const registry = components;
|
|
723
|
+
return function CatalogRenderer({
|
|
724
|
+
spec,
|
|
725
|
+
data,
|
|
726
|
+
onAction,
|
|
727
|
+
onDataChange,
|
|
728
|
+
loading,
|
|
729
|
+
authState,
|
|
730
|
+
fallback
|
|
731
|
+
}) {
|
|
732
|
+
const actionHandlers = onAction ? {
|
|
733
|
+
__default__: (params) => {
|
|
734
|
+
const actionName = params.__actionName__;
|
|
735
|
+
const actionParams = params.__actionParams__;
|
|
736
|
+
return onAction(actionName, actionParams);
|
|
737
|
+
}
|
|
738
|
+
} : void 0;
|
|
739
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
740
|
+
DataProvider,
|
|
741
|
+
{
|
|
742
|
+
initialData: data,
|
|
743
|
+
authState,
|
|
744
|
+
onDataChange,
|
|
745
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ValidationProvider, { children: [
|
|
746
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
747
|
+
Renderer,
|
|
748
|
+
{
|
|
749
|
+
spec,
|
|
750
|
+
registry,
|
|
751
|
+
loading,
|
|
752
|
+
fallback
|
|
753
|
+
}
|
|
754
|
+
),
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConfirmationDialogManager, {})
|
|
756
|
+
] }) }) })
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
};
|
|
760
|
+
}
|
|
617
761
|
|
|
618
762
|
// src/hooks.ts
|
|
619
763
|
var import_react5 = require("react");
|
|
620
|
-
var
|
|
764
|
+
var import_core6 = require("@json-render/core");
|
|
621
765
|
function parsePatchLine(line) {
|
|
622
766
|
try {
|
|
623
767
|
const trimmed = line.trim();
|
|
@@ -629,33 +773,33 @@ function parsePatchLine(line) {
|
|
|
629
773
|
return null;
|
|
630
774
|
}
|
|
631
775
|
}
|
|
632
|
-
function applyPatch(
|
|
633
|
-
const
|
|
776
|
+
function applyPatch(spec, patch) {
|
|
777
|
+
const newSpec = { ...spec, elements: { ...spec.elements } };
|
|
634
778
|
switch (patch.op) {
|
|
635
779
|
case "set":
|
|
636
780
|
case "add":
|
|
637
781
|
case "replace": {
|
|
638
782
|
if (patch.path === "/root") {
|
|
639
|
-
|
|
640
|
-
return
|
|
783
|
+
newSpec.root = patch.value;
|
|
784
|
+
return newSpec;
|
|
641
785
|
}
|
|
642
786
|
if (patch.path.startsWith("/elements/")) {
|
|
643
787
|
const pathParts = patch.path.slice("/elements/".length).split("/");
|
|
644
788
|
const elementKey = pathParts[0];
|
|
645
|
-
if (!elementKey) return
|
|
789
|
+
if (!elementKey) return newSpec;
|
|
646
790
|
if (pathParts.length === 1) {
|
|
647
|
-
|
|
791
|
+
newSpec.elements[elementKey] = patch.value;
|
|
648
792
|
} else {
|
|
649
|
-
const element =
|
|
793
|
+
const element = newSpec.elements[elementKey];
|
|
650
794
|
if (element) {
|
|
651
795
|
const propPath = "/" + pathParts.slice(1).join("/");
|
|
652
796
|
const newElement = { ...element };
|
|
653
|
-
(0,
|
|
797
|
+
(0, import_core6.setByPath)(
|
|
654
798
|
newElement,
|
|
655
799
|
propPath,
|
|
656
800
|
patch.value
|
|
657
801
|
);
|
|
658
|
-
|
|
802
|
+
newSpec.elements[elementKey] = newElement;
|
|
659
803
|
}
|
|
660
804
|
}
|
|
661
805
|
}
|
|
@@ -665,26 +809,26 @@ function applyPatch(tree, patch) {
|
|
|
665
809
|
if (patch.path.startsWith("/elements/")) {
|
|
666
810
|
const elementKey = patch.path.slice("/elements/".length).split("/")[0];
|
|
667
811
|
if (elementKey) {
|
|
668
|
-
const { [elementKey]: _, ...rest } =
|
|
669
|
-
|
|
812
|
+
const { [elementKey]: _, ...rest } = newSpec.elements;
|
|
813
|
+
newSpec.elements = rest;
|
|
670
814
|
}
|
|
671
815
|
}
|
|
672
816
|
break;
|
|
673
817
|
}
|
|
674
818
|
}
|
|
675
|
-
return
|
|
819
|
+
return newSpec;
|
|
676
820
|
}
|
|
677
821
|
function useUIStream({
|
|
678
822
|
api,
|
|
679
823
|
onComplete,
|
|
680
824
|
onError
|
|
681
825
|
}) {
|
|
682
|
-
const [
|
|
826
|
+
const [spec, setSpec] = (0, import_react5.useState)(null);
|
|
683
827
|
const [isStreaming, setIsStreaming] = (0, import_react5.useState)(false);
|
|
684
828
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
685
829
|
const abortControllerRef = (0, import_react5.useRef)(null);
|
|
686
830
|
const clear = (0, import_react5.useCallback)(() => {
|
|
687
|
-
|
|
831
|
+
setSpec(null);
|
|
688
832
|
setError(null);
|
|
689
833
|
}, []);
|
|
690
834
|
const send = (0, import_react5.useCallback)(
|
|
@@ -693,8 +837,9 @@ function useUIStream({
|
|
|
693
837
|
abortControllerRef.current = new AbortController();
|
|
694
838
|
setIsStreaming(true);
|
|
695
839
|
setError(null);
|
|
696
|
-
|
|
697
|
-
|
|
840
|
+
const previousSpec = context?.previousSpec;
|
|
841
|
+
let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
|
|
842
|
+
setSpec(currentSpec);
|
|
698
843
|
try {
|
|
699
844
|
const response = await fetch(api, {
|
|
700
845
|
method: "POST",
|
|
@@ -702,12 +847,22 @@ function useUIStream({
|
|
|
702
847
|
body: JSON.stringify({
|
|
703
848
|
prompt,
|
|
704
849
|
context,
|
|
705
|
-
|
|
850
|
+
currentSpec
|
|
706
851
|
}),
|
|
707
852
|
signal: abortControllerRef.current.signal
|
|
708
853
|
});
|
|
709
854
|
if (!response.ok) {
|
|
710
|
-
|
|
855
|
+
let errorMessage = `HTTP error: ${response.status}`;
|
|
856
|
+
try {
|
|
857
|
+
const errorData = await response.json();
|
|
858
|
+
if (errorData.message) {
|
|
859
|
+
errorMessage = errorData.message;
|
|
860
|
+
} else if (errorData.error) {
|
|
861
|
+
errorMessage = errorData.error;
|
|
862
|
+
}
|
|
863
|
+
} catch {
|
|
864
|
+
}
|
|
865
|
+
throw new Error(errorMessage);
|
|
711
866
|
}
|
|
712
867
|
const reader = response.body?.getReader();
|
|
713
868
|
if (!reader) {
|
|
@@ -724,19 +879,19 @@ function useUIStream({
|
|
|
724
879
|
for (const line of lines) {
|
|
725
880
|
const patch = parsePatchLine(line);
|
|
726
881
|
if (patch) {
|
|
727
|
-
|
|
728
|
-
|
|
882
|
+
currentSpec = applyPatch(currentSpec, patch);
|
|
883
|
+
setSpec({ ...currentSpec });
|
|
729
884
|
}
|
|
730
885
|
}
|
|
731
886
|
}
|
|
732
887
|
if (buffer.trim()) {
|
|
733
888
|
const patch = parsePatchLine(buffer);
|
|
734
889
|
if (patch) {
|
|
735
|
-
|
|
736
|
-
|
|
890
|
+
currentSpec = applyPatch(currentSpec, patch);
|
|
891
|
+
setSpec({ ...currentSpec });
|
|
737
892
|
}
|
|
738
893
|
}
|
|
739
|
-
onComplete?.(
|
|
894
|
+
onComplete?.(currentSpec);
|
|
740
895
|
} catch (err) {
|
|
741
896
|
if (err.name === "AbortError") {
|
|
742
897
|
return;
|
|
@@ -756,7 +911,7 @@ function useUIStream({
|
|
|
756
911
|
};
|
|
757
912
|
}, []);
|
|
758
913
|
return {
|
|
759
|
-
|
|
914
|
+
spec,
|
|
760
915
|
isStreaming,
|
|
761
916
|
error,
|
|
762
917
|
send,
|
|
@@ -799,8 +954,12 @@ function flatToTree(elements) {
|
|
|
799
954
|
Renderer,
|
|
800
955
|
ValidationProvider,
|
|
801
956
|
VisibilityProvider,
|
|
957
|
+
createRenderer,
|
|
802
958
|
createRendererFromCatalog,
|
|
959
|
+
defineRegistry,
|
|
960
|
+
elementTreeSchema,
|
|
803
961
|
flatToTree,
|
|
962
|
+
schema,
|
|
804
963
|
useAction,
|
|
805
964
|
useActions,
|
|
806
965
|
useData,
|