ngx-json-render 0.7.8 → 0.8.1
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 +98 -4
- package/fesm2022/ngx-json-render-devtools.mjs +262 -0
- package/fesm2022/ngx-json-render-devtools.mjs.map +1 -0
- package/fesm2022/ngx-json-render-mcp.mjs +132 -0
- package/fesm2022/ngx-json-render-mcp.mjs.map +1 -0
- package/fesm2022/ngx-json-render-schema.mjs +107 -0
- package/fesm2022/ngx-json-render-schema.mjs.map +1 -0
- package/fesm2022/ngx-json-render.mjs +51 -95
- package/fesm2022/ngx-json-render.mjs.map +1 -1
- package/package.json +28 -2
- package/schematics/ng-add/index.cjs +1 -1
- package/types/ngx-json-render-devtools.d.ts +67 -0
- package/types/ngx-json-render-mcp.d.ts +93 -0
- package/types/ngx-json-render-schema.d.ts +74 -0
- package/types/ngx-json-render.d.ts +33 -72
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { defineSchema } from '@json-render/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The schema for ngx-json-render.
|
|
5
|
+
*
|
|
6
|
+
* Defines:
|
|
7
|
+
* - Spec: A flat tree of elements with keys, types, props, and children references
|
|
8
|
+
* - Catalog: Components with props schemas, and optional actions
|
|
9
|
+
*
|
|
10
|
+
* This is the same spec grammar the other json-render renderers use, so
|
|
11
|
+
* catalogs and specs are portable across frameworks.
|
|
12
|
+
*/
|
|
13
|
+
const schema = defineSchema((s) => ({
|
|
14
|
+
// What the AI-generated SPEC looks like
|
|
15
|
+
spec: s.object({
|
|
16
|
+
/** Root element key */
|
|
17
|
+
root: s.string(),
|
|
18
|
+
/** Flat map of elements by key */
|
|
19
|
+
elements: s.record(s.object({
|
|
20
|
+
/** Component type from catalog */
|
|
21
|
+
type: s.ref('catalog.components'),
|
|
22
|
+
/** Component props */
|
|
23
|
+
props: s.propsOf('catalog.components'),
|
|
24
|
+
/** Child element keys (flat reference) */
|
|
25
|
+
children: s.array(s.string()),
|
|
26
|
+
/** Visibility condition */
|
|
27
|
+
visible: { ...s.any(), ...s.optional() },
|
|
28
|
+
/** Repeat children from a state array */
|
|
29
|
+
repeat: { ...s.any(), ...s.optional() },
|
|
30
|
+
})),
|
|
31
|
+
}),
|
|
32
|
+
// What the CATALOG must provide
|
|
33
|
+
catalog: s.object({
|
|
34
|
+
/** Component definitions */
|
|
35
|
+
components: s.map({
|
|
36
|
+
/** Zod schema for component props */
|
|
37
|
+
props: s.zod(),
|
|
38
|
+
/** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
|
|
39
|
+
slots: s.array(s.string()),
|
|
40
|
+
/** Description for AI generation hints */
|
|
41
|
+
description: s.string(),
|
|
42
|
+
/** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
|
|
43
|
+
example: s.any(),
|
|
44
|
+
}),
|
|
45
|
+
/** Action definitions (optional) */
|
|
46
|
+
actions: s.map({
|
|
47
|
+
/** Zod schema for action params */
|
|
48
|
+
params: s.zod(),
|
|
49
|
+
/** Description for AI generation hints */
|
|
50
|
+
description: s.string(),
|
|
51
|
+
}),
|
|
52
|
+
}),
|
|
53
|
+
}), {
|
|
54
|
+
builtInActions: [
|
|
55
|
+
{
|
|
56
|
+
name: 'setState',
|
|
57
|
+
description: 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'pushState',
|
|
61
|
+
description: 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'removeState',
|
|
65
|
+
description: 'Remove an item from an array in state by index. Params: { statePath: string, index: number }',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'validateForm',
|
|
69
|
+
description: 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'submitForm',
|
|
73
|
+
description: "Validate all registered form fields and, only if every one passes, dispatch another action. Params: { action: string, params?: object, statePath?: string }. Writes the same { valid, errors } result as validateForm. Use this on a form's submit button: it is validateForm and the submit in one binding, and the submit is skipped when the form is invalid.",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
defaultRules: [
|
|
77
|
+
// Element integrity
|
|
78
|
+
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.",
|
|
79
|
+
'SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.',
|
|
80
|
+
'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
|
|
81
|
+
// Field placement
|
|
82
|
+
'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
|
|
83
|
+
'CRITICAL: The "on" field goes on the ELEMENT object, NOT inside "props". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',
|
|
84
|
+
// State and data
|
|
85
|
+
'When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).',
|
|
86
|
+
'When building repeating content backed by a state array (e.g. posts, products, items), use the "repeat" field on a container element. Example: { "type": "<ContainerComponent>", "props": {}, "repeat": { "statePath": "/posts", "key": "id" }, "children": ["post-card"] }. For a nested list stored on the enclosing item, use "repeat": { "statePath": { "$item": "comments" }, "key": "id" }. The $item statePath form is valid only inside another repeat. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
|
|
87
|
+
// Design quality
|
|
88
|
+
'Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.',
|
|
89
|
+
'For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.',
|
|
90
|
+
'Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.',
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* Public API Surface of ngx-json-render/schema
|
|
96
|
+
*
|
|
97
|
+
* The spec grammar alone, with nothing but `@json-render/core` behind it, so
|
|
98
|
+
* a server can define a catalog without loading Angular. The primary entry
|
|
99
|
+
* point re-exports it.
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generated bundle index. Do not edit.
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
export { schema };
|
|
107
|
+
//# sourceMappingURL=ngx-json-render-schema.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-json-render-schema.mjs","sources":["../../../projects/ngx-json-render/schema/src/schema.ts","../../../projects/ngx-json-render/schema/src/public-api.ts","../../../projects/ngx-json-render/schema/src/ngx-json-render-schema.ts"],"sourcesContent":["import { defineSchema } from '@json-render/core';\n\n/**\n * The schema for ngx-json-render.\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas, and optional actions\n *\n * This is the same spec grammar the other json-render renderers use, so\n * catalogs and specs are portable across frameworks.\n */\nexport const schema = defineSchema(\n (s) => ({\n // What the AI-generated SPEC looks like\n spec: s.object({\n /** Root element key */\n root: s.string(),\n /** Flat map of elements by key */\n elements: s.record(\n s.object({\n /** Component type from catalog */\n type: s.ref('catalog.components'),\n /** Component props */\n props: s.propsOf('catalog.components'),\n /** Child element keys (flat reference) */\n children: s.array(s.string()),\n /** Visibility condition */\n visible: { ...s.any(), ...s.optional() },\n /** Repeat children from a state array */\n repeat: { ...s.any(), ...s.optional() },\n }),\n ),\n }),\n\n // What the CATALOG must provide\n catalog: s.object({\n /** Component definitions */\n components: s.map({\n /** Zod schema for component props */\n props: s.zod(),\n /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */\n slots: s.array(s.string()),\n /** Description for AI generation hints */\n description: s.string(),\n /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */\n example: s.any(),\n }),\n /** Action definitions (optional) */\n actions: s.map({\n /** Zod schema for action params */\n params: s.zod(),\n /** Description for AI generation hints */\n description: s.string(),\n }),\n }),\n }),\n {\n builtInActions: [\n {\n name: 'setState',\n description:\n 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',\n },\n {\n name: 'pushState',\n description:\n 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {\"$state\":\"/path\"} refs and \"$id\" for auto IDs.',\n },\n {\n name: 'removeState',\n description:\n 'Remove an item from an array in state by index. Params: { statePath: string, index: number }',\n },\n {\n name: 'validateForm',\n description:\n 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',\n },\n {\n name: 'submitForm',\n description:\n \"Validate all registered form fields and, only if every one passes, dispatch another action. Params: { action: string, params?: object, statePath?: string }. Writes the same { valid, errors } result as validateForm. Use this on a form's submit button: it is validateForm and the submit in one binding, and the submit is skipped when the form is invalid.\",\n },\n ],\n defaultRules: [\n // Element integrity\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.\",\n 'SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.',\n 'REQUIRED FIELDS: Every element MUST include a \"children\" array. Leaf elements (text, badges, inputs, images) use an empty array: \"children\": []. Omitting \"children\" fails validation.',\n\n // Field placement\n 'CRITICAL: The \"visible\" field goes on the ELEMENT object, NOT inside \"props\". Correct: {\"type\":\"<ComponentName>\",\"props\":{},\"visible\":{\"$state\":\"/tab\",\"eq\":\"home\"},\"children\":[...]}.',\n 'CRITICAL: The \"on\" field goes on the ELEMENT object, NOT inside \"props\". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',\n\n // State and data\n 'When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).',\n 'When building repeating content backed by a state array (e.g. posts, products, items), use the \"repeat\" field on a container element. Example: { \"type\": \"<ContainerComponent>\", \"props\": {}, \"repeat\": { \"statePath\": \"/posts\", \"key\": \"id\" }, \"children\": [\"post-card\"] }. For a nested list stored on the enclosing item, use \"repeat\": { \"statePath\": { \"$item\": \"comments\" }, \"key\": \"id\" }. The $item statePath form is valid only inside another repeat. Inside repeated children, use { \"$item\": \"field\" } to read a field from the current item, and { \"$index\": true } for the current array index. For two-way binding to an item field use { \"$bindItem\": \"completed\" }. Do NOT hardcode individual elements for each array item.',\n\n // Design quality\n 'Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.',\n 'For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.',\n 'Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.',\n ],\n },\n);\n\n/**\n * Type for the Angular schema\n */\nexport type AngularSchema = typeof schema;\n\n/**\n * Infer the spec type from a catalog\n */\nexport type AngularSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n","/*\n * Public API Surface of ngx-json-render/schema\n *\n * The spec grammar alone, with nothing but `@json-render/core` behind it, so\n * a server can define a catalog without loading Angular. The primary entry\n * point re-exports it.\n */\n\nexport { schema, type AngularSchema, type AngularSpec } from './schema';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAEA;;;;;;;;;AASG;AACI,MAAM,MAAM,GAAG,YAAY,CAChC,CAAC,CAAC,MAAM;;AAEN,IAAA,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;;AAEb,QAAA,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;;QAEhB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAChB,CAAC,CAAC,MAAM,CAAC;;AAEP,YAAA,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC;;AAEjC,YAAA,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC;;YAEtC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;;AAE7B,YAAA,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;;AAExC,YAAA,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;AACxC,SAAA,CAAC,CACH;KACF,CAAC;;AAGF,IAAA,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC;;AAEhB,QAAA,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC;;AAEhB,YAAA,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE;;YAEd,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;;AAE1B,YAAA,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;;AAEvB,YAAA,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE;SACjB,CAAC;;AAEF,QAAA,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC;;AAEb,YAAA,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE;;AAEf,YAAA,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;SACxB,CAAC;KACH,CAAC;AACH,CAAA,CAAC,EACF;AACE,IAAA,cAAc,EAAE;AACd,QAAA;AACE,YAAA,IAAI,EAAE,UAAU;AAChB,YAAA,WAAW,EACT,qGAAqG;AACxG,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,WAAW;AACjB,YAAA,WAAW,EACT,4KAA4K;AAC/K,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,WAAW,EACT,8FAA8F;AACjG,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,cAAc;AACpB,YAAA,WAAW,EACT,+LAA+L;AAClM,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,YAAY;AAClB,YAAA,WAAW,EACT,kWAAkW;AACrW,SAAA;AACF,KAAA;AACD,IAAA,YAAY,EAAE;;QAEZ,2TAA2T;QAC3T,8MAA8M;QAC9M,wLAAwL;;QAGxL,wLAAwL;QACxL,8JAA8J;;QAG9J,0NAA0N;QAC1N,+sBAA+sB;;QAG/sB,gNAAgN;QAChN,kMAAkM;QAClM,6MAA6M;AAC9M,KAAA;AACF,CAAA;;ACxGH;;;;;;AAMG;;ACNH;;AAEG;;"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NgComponentOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { signal, Injectable, isDevMode, inject, computed, untracked, effect, DestroyRef, InjectionToken, input, output, viewChild, afterNextRender, ChangeDetectionStrategy, Component, Injector, forwardRef } from '@angular/core';
|
|
4
|
-
import { createStateStore, getByPath, runValidation, resolveAction, nextActionDispatchId, notifyActionDispatch, notifyActionSettle, executeAction, isDevtoolsActive, subscribeDevtoolsActive, resolveRepeatStatePath, evaluateVisibility, resolveElementProps, resolveBindings, resolveActionParam, autoFixSpec, validateSpec, createDirectiveRegistry, resolveRepeatItemStatePath, removeByPath, addByPath, setByPath, SPEC_DATA_PART_TYPE, nestedToFlat, createMixedStreamParser
|
|
4
|
+
import { createStateStore, getByPath, runValidation, resolveAction, nextActionDispatchId, notifyActionDispatch, notifyActionSettle, executeAction, isDevtoolsActive, subscribeDevtoolsActive, resolveRepeatStatePath, evaluateVisibility, resolveElementProps, resolveBindings, resolveActionParam, autoFixSpec, validateSpec, createDirectiveRegistry, resolveRepeatItemStatePath, removeByPath, addByPath, setByPath, SPEC_DATA_PART_TYPE, nestedToFlat, createMixedStreamParser } from '@json-render/core';
|
|
5
5
|
export { autoFixSpec, createStateStore, formatSpecIssues, nestedToFlat, validateSpec } from '@json-render/core';
|
|
6
|
+
export { schema } from 'ngx-json-render/schema';
|
|
6
7
|
|
|
7
8
|
const EMPTY = signal(undefined, ...(ngDevMode ? [{ debugName: "EMPTY" }] : /* istanbul ignore next */ []));
|
|
8
9
|
/**
|
|
@@ -3138,6 +3139,34 @@ function applyPatch(spec, patch) {
|
|
|
3138
3139
|
return newSpec;
|
|
3139
3140
|
}
|
|
3140
3141
|
|
|
3142
|
+
const observers = new Set();
|
|
3143
|
+
/**
|
|
3144
|
+
* Register an observer for every stream in the app. Returns the function that
|
|
3145
|
+
* removes it. Private API for `ngx-json-render/devtools`.
|
|
3146
|
+
*/
|
|
3147
|
+
function ɵregisterStreamObserver(observer) {
|
|
3148
|
+
observers.add(observer);
|
|
3149
|
+
return () => {
|
|
3150
|
+
observers.delete(observer);
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* Tell every observer. An observer that throws is a devtools bug, and it must
|
|
3155
|
+
* not become a failed generation in the app it is inspecting.
|
|
3156
|
+
*/
|
|
3157
|
+
function notifyStreamObservers(event, ...args) {
|
|
3158
|
+
if (observers.size === 0)
|
|
3159
|
+
return;
|
|
3160
|
+
for (const observer of observers) {
|
|
3161
|
+
try {
|
|
3162
|
+
observer[event]?.(...args);
|
|
3163
|
+
}
|
|
3164
|
+
catch (error) {
|
|
3165
|
+
console.error('[ngx-json-render] A stream observer threw:', error);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3141
3170
|
/**
|
|
3142
3171
|
* Convert a flat element list to a Spec.
|
|
3143
3172
|
* Input elements use key/parentKey to establish identity and relationships.
|
|
@@ -3461,6 +3490,8 @@ function injectUIStream(options) {
|
|
|
3461
3490
|
? { ...previousSpec, elements: { ...previousSpec.elements } }
|
|
3462
3491
|
: { root: '', elements: {} };
|
|
3463
3492
|
spec.set(currentSpec);
|
|
3493
|
+
notifyStreamObservers('onStart');
|
|
3494
|
+
let failed = false;
|
|
3464
3495
|
const handleLine = (line) => {
|
|
3465
3496
|
const parsed = parseLine(line);
|
|
3466
3497
|
if (!parsed)
|
|
@@ -3468,11 +3499,13 @@ function injectUIStream(options) {
|
|
|
3468
3499
|
gate.commit(() => {
|
|
3469
3500
|
if (parsed.type === 'usage') {
|
|
3470
3501
|
usage.set(parsed.usage);
|
|
3502
|
+
notifyStreamObservers('onUsage', parsed.usage);
|
|
3471
3503
|
return;
|
|
3472
3504
|
}
|
|
3473
3505
|
rawLines.update((prev) => [...prev, line]);
|
|
3474
3506
|
currentSpec = applyPatch(currentSpec, parsed.patch);
|
|
3475
3507
|
spec.set(currentSpec);
|
|
3508
|
+
notifyStreamObservers('onPatch', parsed.patch);
|
|
3476
3509
|
});
|
|
3477
3510
|
};
|
|
3478
3511
|
try {
|
|
@@ -3503,6 +3536,7 @@ function injectUIStream(options) {
|
|
|
3503
3536
|
// not want rendered.
|
|
3504
3537
|
if (check.blocked || (validate === 'strict' && check.hasErrors)) {
|
|
3505
3538
|
const invalid = new Error(`Generated spec failed validation:\n${formatSpecCheckIssues(check.issues)}`);
|
|
3539
|
+
failed = true;
|
|
3506
3540
|
error.set(invalid);
|
|
3507
3541
|
options.onError?.(invalid);
|
|
3508
3542
|
return;
|
|
@@ -3513,6 +3547,7 @@ function injectUIStream(options) {
|
|
|
3513
3547
|
catch (err) {
|
|
3514
3548
|
if (isAbortError(err))
|
|
3515
3549
|
return;
|
|
3550
|
+
failed = true;
|
|
3516
3551
|
const resolvedError = err instanceof Error ? err : new Error(String(err));
|
|
3517
3552
|
gate.commit(() => {
|
|
3518
3553
|
error.set(resolvedError);
|
|
@@ -3521,6 +3556,7 @@ function injectUIStream(options) {
|
|
|
3521
3556
|
}
|
|
3522
3557
|
finally {
|
|
3523
3558
|
gate.commit(() => isStreaming.set(false));
|
|
3559
|
+
notifyStreamObservers('onEnd', !failed);
|
|
3524
3560
|
}
|
|
3525
3561
|
};
|
|
3526
3562
|
return {
|
|
@@ -3621,6 +3657,8 @@ function injectChatUI(options) {
|
|
|
3621
3657
|
messages.update((prev) => [...prev, userMessage, assistantMessage]);
|
|
3622
3658
|
isStreaming.set(true);
|
|
3623
3659
|
error.set(null);
|
|
3660
|
+
notifyStreamObservers('onStart');
|
|
3661
|
+
let failed = false;
|
|
3624
3662
|
// Mutable state for accumulating the assistant response
|
|
3625
3663
|
let accumulatedText = '';
|
|
3626
3664
|
let currentSpec = { root: '', elements: {} };
|
|
@@ -3632,12 +3670,18 @@ function injectChatUI(options) {
|
|
|
3632
3670
|
// touch, so the message can hold it directly — no snapshot copy.
|
|
3633
3671
|
currentSpec = applyPatch(currentSpec, patch);
|
|
3634
3672
|
const snapshot = currentSpec;
|
|
3635
|
-
gate.commit(() =>
|
|
3673
|
+
gate.commit(() => {
|
|
3674
|
+
messages.update((prev) => prev.map((m) => m.id === assistantId ? { ...m, spec: snapshot } : m));
|
|
3675
|
+
notifyStreamObservers('onPatch', patch);
|
|
3676
|
+
});
|
|
3636
3677
|
},
|
|
3637
3678
|
onText(line) {
|
|
3638
3679
|
accumulatedText += (accumulatedText ? '\n' : '') + line;
|
|
3639
3680
|
const snapshot = accumulatedText;
|
|
3640
|
-
gate.commit(() =>
|
|
3681
|
+
gate.commit(() => {
|
|
3682
|
+
messages.update((prev) => prev.map((m) => m.id === assistantId ? { ...m, text: snapshot } : m));
|
|
3683
|
+
notifyStreamObservers('onText', line);
|
|
3684
|
+
});
|
|
3641
3685
|
},
|
|
3642
3686
|
});
|
|
3643
3687
|
try {
|
|
@@ -3671,6 +3715,7 @@ function injectChatUI(options) {
|
|
|
3671
3715
|
// not want rendered.
|
|
3672
3716
|
if (check.blocked || (validate === 'strict' && check.hasErrors)) {
|
|
3673
3717
|
const invalid = new Error(`Generated spec failed validation:\n${formatSpecCheckIssues(check.issues)}`);
|
|
3718
|
+
failed = true;
|
|
3674
3719
|
error.set(invalid);
|
|
3675
3720
|
options.onError?.(invalid);
|
|
3676
3721
|
return;
|
|
@@ -3686,6 +3731,7 @@ function injectChatUI(options) {
|
|
|
3686
3731
|
dropEmptyPlaceholder();
|
|
3687
3732
|
if (isAbortError(err))
|
|
3688
3733
|
return;
|
|
3734
|
+
failed = true;
|
|
3689
3735
|
const resolvedError = err instanceof Error ? err : new Error(String(err));
|
|
3690
3736
|
gate.commit(() => {
|
|
3691
3737
|
error.set(resolvedError);
|
|
@@ -3694,6 +3740,7 @@ function injectChatUI(options) {
|
|
|
3694
3740
|
}
|
|
3695
3741
|
finally {
|
|
3696
3742
|
gate.commit(() => isStreaming.set(false));
|
|
3743
|
+
notifyStreamObservers('onEnd', !failed);
|
|
3697
3744
|
}
|
|
3698
3745
|
};
|
|
3699
3746
|
return {
|
|
@@ -3716,97 +3763,6 @@ function injectChatUI(options) {
|
|
|
3716
3763
|
* - `ui-stream` / `chat-ui` — the two hooks, composed from the above.
|
|
3717
3764
|
*/
|
|
3718
3765
|
|
|
3719
|
-
/**
|
|
3720
|
-
* The schema for ngx-json-render.
|
|
3721
|
-
*
|
|
3722
|
-
* Defines:
|
|
3723
|
-
* - Spec: A flat tree of elements with keys, types, props, and children references
|
|
3724
|
-
* - Catalog: Components with props schemas, and optional actions
|
|
3725
|
-
*
|
|
3726
|
-
* This is the same spec grammar the other json-render renderers use, so
|
|
3727
|
-
* catalogs and specs are portable across frameworks.
|
|
3728
|
-
*/
|
|
3729
|
-
const schema = defineSchema((s) => ({
|
|
3730
|
-
// What the AI-generated SPEC looks like
|
|
3731
|
-
spec: s.object({
|
|
3732
|
-
/** Root element key */
|
|
3733
|
-
root: s.string(),
|
|
3734
|
-
/** Flat map of elements by key */
|
|
3735
|
-
elements: s.record(s.object({
|
|
3736
|
-
/** Component type from catalog */
|
|
3737
|
-
type: s.ref('catalog.components'),
|
|
3738
|
-
/** Component props */
|
|
3739
|
-
props: s.propsOf('catalog.components'),
|
|
3740
|
-
/** Child element keys (flat reference) */
|
|
3741
|
-
children: s.array(s.string()),
|
|
3742
|
-
/** Visibility condition */
|
|
3743
|
-
visible: { ...s.any(), ...s.optional() },
|
|
3744
|
-
/** Repeat children from a state array */
|
|
3745
|
-
repeat: { ...s.any(), ...s.optional() },
|
|
3746
|
-
})),
|
|
3747
|
-
}),
|
|
3748
|
-
// What the CATALOG must provide
|
|
3749
|
-
catalog: s.object({
|
|
3750
|
-
/** Component definitions */
|
|
3751
|
-
components: s.map({
|
|
3752
|
-
/** Zod schema for component props */
|
|
3753
|
-
props: s.zod(),
|
|
3754
|
-
/** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
|
|
3755
|
-
slots: s.array(s.string()),
|
|
3756
|
-
/** Description for AI generation hints */
|
|
3757
|
-
description: s.string(),
|
|
3758
|
-
/** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
|
|
3759
|
-
example: s.any(),
|
|
3760
|
-
}),
|
|
3761
|
-
/** Action definitions (optional) */
|
|
3762
|
-
actions: s.map({
|
|
3763
|
-
/** Zod schema for action params */
|
|
3764
|
-
params: s.zod(),
|
|
3765
|
-
/** Description for AI generation hints */
|
|
3766
|
-
description: s.string(),
|
|
3767
|
-
}),
|
|
3768
|
-
}),
|
|
3769
|
-
}), {
|
|
3770
|
-
builtInActions: [
|
|
3771
|
-
{
|
|
3772
|
-
name: 'setState',
|
|
3773
|
-
description: 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
|
|
3774
|
-
},
|
|
3775
|
-
{
|
|
3776
|
-
name: 'pushState',
|
|
3777
|
-
description: 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
|
|
3778
|
-
},
|
|
3779
|
-
{
|
|
3780
|
-
name: 'removeState',
|
|
3781
|
-
description: 'Remove an item from an array in state by index. Params: { statePath: string, index: number }',
|
|
3782
|
-
},
|
|
3783
|
-
{
|
|
3784
|
-
name: 'validateForm',
|
|
3785
|
-
description: 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
|
|
3786
|
-
},
|
|
3787
|
-
{
|
|
3788
|
-
name: 'submitForm',
|
|
3789
|
-
description: "Validate all registered form fields and, only if every one passes, dispatch another action. Params: { action: string, params?: object, statePath?: string }. Writes the same { valid, errors } result as validateForm. Use this on a form's submit button: it is validateForm and the submit in one binding, and the submit is skipped when the form is invalid.",
|
|
3790
|
-
},
|
|
3791
|
-
],
|
|
3792
|
-
defaultRules: [
|
|
3793
|
-
// Element integrity
|
|
3794
|
-
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.",
|
|
3795
|
-
'SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.',
|
|
3796
|
-
'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
|
|
3797
|
-
// Field placement
|
|
3798
|
-
'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
|
|
3799
|
-
'CRITICAL: The "on" field goes on the ELEMENT object, NOT inside "props". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',
|
|
3800
|
-
// State and data
|
|
3801
|
-
'When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).',
|
|
3802
|
-
'When building repeating content backed by a state array (e.g. posts, products, items), use the "repeat" field on a container element. Example: { "type": "<ContainerComponent>", "props": {}, "repeat": { "statePath": "/posts", "key": "id" }, "children": ["post-card"] }. For a nested list stored on the enclosing item, use "repeat": { "statePath": { "$item": "comments" }, "key": "id" }. The $item statePath form is valid only inside another repeat. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
|
|
3803
|
-
// Design quality
|
|
3804
|
-
'Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.',
|
|
3805
|
-
'For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.',
|
|
3806
|
-
'Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.',
|
|
3807
|
-
],
|
|
3808
|
-
});
|
|
3809
|
-
|
|
3810
3766
|
/*
|
|
3811
3767
|
* Public API Surface of ngx-json-render
|
|
3812
3768
|
*/
|
|
@@ -3816,5 +3772,5 @@ const schema = defineSchema((s) => ({
|
|
|
3816
3772
|
* Generated bundle index. Do not edit.
|
|
3817
3773
|
*/
|
|
3818
3774
|
|
|
3819
|
-
export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage,
|
|
3775
|
+
export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, ɵregisterStreamObserver };
|
|
3820
3776
|
//# sourceMappingURL=ngx-json-render.mjs.map
|