ngx-json-render 0.8.0 → 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 +63 -2
- package/fesm2022/ngx-json-render-devtools.mjs +262 -0
- package/fesm2022/ngx-json-render-devtools.mjs.map +1 -0
- package/fesm2022/ngx-json-render.mjs +49 -3
- package/fesm2022/ngx-json-render.mjs.map +1 -1
- package/package.json +10 -2
- package/schematics/ng-add/index.cjs +1 -1
- package/types/ngx-json-render-devtools.d.ts +67 -0
- package/types/ngx-json-render.d.ts +32 -2
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ the Material catalog has one too (`--skill ngx-json-render-material`).
|
|
|
62
62
|
|
|
63
63
|
Writing a catalog is the honest first step, but you do not have to take it to
|
|
64
64
|
see a spec render. [`ngx-json-render-material`](https://www.npmjs.com/package/ngx-json-render-material)
|
|
65
|
-
ships
|
|
65
|
+
ships 29 Angular Material components already registered, so a generated spec
|
|
66
66
|
renders with nothing else wired up:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
@@ -119,7 +119,7 @@ against `@hashbrownai/angular` 0.6.1, as published on 2026-09-30:
|
|
|
119
119
|
| What the model emits | A json-render spec, streamed as RFC 6902 patches — the format the React, Vue, Svelte and Solid renderers read, so a catalog and a spec move between frameworks unchanged | Hashbrown's own JSON, generated against a schema built from the components you expose; read by Hashbrown's Angular and React clients |
|
|
120
120
|
| Server | Anything that streams text: the [AI SDK](https://ai-sdk.dev) in a few lines, or your own endpoint; `catalog.prompt()` writes the system prompt | An [AG-UI](https://docs.ag-ui.com) endpoint, built with Hashbrown's adapters (OpenAI, Anthropic, Google, Azure, Bedrock, Ollama) or your own mapping |
|
|
121
121
|
| Angular | 20, 21 and 22, each tested in CI | 22 only since 0.6 (0.5 supported 20 and 21) |
|
|
122
|
-
| Components | Your own, or
|
|
122
|
+
| Components | Your own, or 29 ready-made Angular Material ones ([`ngx-json-render-material`](https://www.npmjs.com/package/ngx-json-render-material)) | Your own (`exposeComponent`, UI kits) |
|
|
123
123
|
| Beyond rendering | Rendering-focused: state and two-way bindings in the spec, actions, visibility, repeat, watch, confirmation dialogs | An agent toolkit: client-side tools, a QuickJS sandbox for model-written code, shared agent state, interrupts, streaming Markdown |
|
|
124
124
|
| License | Apache-2.0 | MIT |
|
|
125
125
|
|
|
@@ -306,6 +306,40 @@ app.post('/api/generate', async (req, res) => {
|
|
|
306
306
|
|
|
307
307
|
The patches apply to the `spec` signal as each line arrives, so the UI assembles on screen while the model is still generating — exactly what the [demo's Streaming tab](https://shteynu.github.io/ngx-json-render/) replays. Prefer structured output? `catalog.jsonSchema()` exports a JSON Schema for `streamObject`/tool calls, and `checkSpec(spec, 'strict', { catalog })` checks a finished spec against the catalog, props included — see [Checking what the model produced](#checking-what-the-model-produced) for why that and not `catalog.validate(spec)` alone.
|
|
308
308
|
|
|
309
|
+
### With Genkit
|
|
310
|
+
|
|
311
|
+
On Firebase, or anywhere else [Genkit](https://genkit.dev) already runs, the route has the same shape. `ai.generateStream` streams the model's text, and that text is the JSONL the system prompt asked for, so it goes to the browser unchanged:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// server.ts — Express with Genkit and its Google AI plugin
|
|
315
|
+
import express from 'express';
|
|
316
|
+
import { genkit } from 'genkit';
|
|
317
|
+
import { googleAI } from '@genkit-ai/google-genai';
|
|
318
|
+
import { buildUserPrompt } from '@json-render/core';
|
|
319
|
+
import { catalog } from './catalog';
|
|
320
|
+
|
|
321
|
+
const ai = genkit({ plugins: [googleAI()] }); // reads GEMINI_API_KEY
|
|
322
|
+
|
|
323
|
+
const app = express();
|
|
324
|
+
app.use(express.json());
|
|
325
|
+
|
|
326
|
+
app.post('/api/generate', async (req, res) => {
|
|
327
|
+
const { prompt, currentSpec } = req.body; // and `context`, yours to use
|
|
328
|
+
|
|
329
|
+
const { stream } = ai.generateStream({
|
|
330
|
+
model: googleAI.model('gemini-3.5-flash'),
|
|
331
|
+
system: catalog.prompt(),
|
|
332
|
+
prompt: buildUserPrompt({ prompt, currentSpec }), // currentSpec: refine, not restart
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
336
|
+
for await (const chunk of stream) res.write(chunk.text);
|
|
337
|
+
res.end();
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`buildUserPrompt` comes from `@json-render/core`. Given a `currentSpec`, it asks the model for patches to that spec, so `ui.send(prompt, { previousSpec })` refines the UI instead of regenerating it; without one, it adds a reminder of the order to stream in. Another model is another plugin and `model` line, such as `vertexAI` from the same package. Serve the text from a plain route like this one rather than through `expressHandler` from `@genkit-ai/express`: that wraps every chunk in its own server-sent event (`data: {"message": …}`), which `injectUIStream` does not read.
|
|
342
|
+
|
|
309
343
|
### Chat + GenUI
|
|
310
344
|
|
|
311
345
|
When the model answers in prose _and_ renders a UI in the same turn, reach for
|
|
@@ -346,6 +380,14 @@ readonly msg = jsonRenderMessage(() => this.message().parts);
|
|
|
346
380
|
// template: {{ msg.text() }} @if (msg.hasSpec()) { <json-render [spec]="msg.spec()" ... /> }
|
|
347
381
|
```
|
|
348
382
|
|
|
383
|
+
With `@ai-sdk/angular`, hand that component a copy of the message being
|
|
384
|
+
streamed. The SDK's `Chat` writes every chunk into one message object and
|
|
385
|
+
passes that same object back each time, so a signal input holding it never
|
|
386
|
+
changes, and the message renders empty even after the stream ends.
|
|
387
|
+
[`examples/ai-sdk-chat`](https://github.com/shteynu/ngx-json-render/tree/main/examples/ai-sdk-chat)
|
|
388
|
+
is the whole path, server to rendered message, and makes the copy in
|
|
389
|
+
`app.ts`.
|
|
390
|
+
|
|
349
391
|
Two things about the parts themselves, both the SDK's semantics rather than
|
|
350
392
|
this package's, and both silent when you get them wrong:
|
|
351
393
|
|
|
@@ -536,6 +578,23 @@ The spec renders while the model is still writing the tool call, from the host's
|
|
|
536
578
|
|
|
537
579
|
The server side is upstream's `@json-render/mcp`, which serves the view as a single HTML resource. [`projects/mcp-app`](https://github.com/shteynu/ngx-json-render/tree/main/projects/mcp-app) is a complete example with the Material catalog: the view, the build that inlines it into one page, and a server that works around two `createMcpApp` problems.
|
|
538
580
|
|
|
581
|
+
## Devtools
|
|
582
|
+
|
|
583
|
+
`ngx-json-render/devtools` is the Angular counterpart of `@json-render/devtools-react` (and its Vue, Svelte and Solid siblings): a floating panel with the spec tree, the live state, every action dispatched, every stream patch with its token usage, and the catalog, plus a picker that finds the element under the pointer. The panel itself is upstream's `@json-render/devtools`, an optional peer. It pins `@json-render/core` exactly, so install the version that matches your core:
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
npm install -D @json-render/devtools@0.21
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
```html
|
|
590
|
+
<json-render #renderer [spec]="ui.spec()" [registry]="registry" />
|
|
591
|
+
<json-render-devtools [renderer]="renderer" [catalog]="catalog" />
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Angular has no provider above `<json-render>` for the panel to read, so the renderer is passed in by template reference. Streams need no wiring: every `injectUIStream` and `injectChatUI` generation in the app shows up in the Stream tab. Pass `[chat]="chat"` and each assistant reply with a spec becomes a generation you can switch between. `Cmd/Ctrl+Shift+J` toggles the panel (`hotkey`), and `(event)` hands every recorded event to your own logging.
|
|
595
|
+
|
|
596
|
+
It renders nothing outside dev mode, and loads the panel with a dynamic `import()` that a production build removes, so you can leave the tag in your template.
|
|
597
|
+
|
|
539
598
|
## Testing
|
|
540
599
|
|
|
541
600
|
`ngx-json-render/testing` is a separate entry point, so nothing in it can reach
|
|
@@ -907,6 +966,8 @@ Schema alone (`ngx-json-render/schema`): `schema`, `AngularSchema`, `AngularSpec
|
|
|
907
966
|
|
|
908
967
|
MCP Apps (`ngx-json-render/mcp`): `injectJsonRenderApp`, `parseSpecFromToolResult`, `messageText`, and the types `JsonRenderApp`, `JsonRenderAppOptions`. Needs the optional peers `@modelcontextprotocol/ext-apps` and `@modelcontextprotocol/sdk`.
|
|
909
968
|
|
|
969
|
+
Devtools (`ngx-json-render/devtools`): `JsonRenderDevtools` (`<json-render-devtools>`), and the types `DevtoolsEvent`, `PanelPosition`. Needs the optional peer `@json-render/devtools`.
|
|
970
|
+
|
|
910
971
|
Testing (`ngx-json-render/testing`): `renderSpec`, `renderComponent`, `recordedTransport`, `specStream`, `usageLine`.
|
|
911
972
|
|
|
912
973
|
Everything from `@json-render/core` (types, `createStateStore`, `nestedToFlat`, prompt builders, spec validators, SpecStream compiler) composes with this package; the most common symbols are re-exported.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, inject, DestroyRef, isDevMode, afterNextRender, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { registerActionObserver, markDevtoolsActive } from '@json-render/core';
|
|
4
|
+
import { ɵregisterStreamObserver as _registerStreamObserver } from 'ngx-json-render';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A floating json-render devtools panel for an Angular app — the counterpart
|
|
8
|
+
* of `<JsonRenderDevtools />` in `@json-render/devtools-react`, `-vue`,
|
|
9
|
+
* `-svelte` and `-solid`, with the same tabs (Spec, State, Actions, Stream,
|
|
10
|
+
* Catalog) and the same element picker.
|
|
11
|
+
*
|
|
12
|
+
* Angular has no provider above `<json-render>` to read the renderer from, so
|
|
13
|
+
* the renderer is passed in by template reference. Streams need no wiring:
|
|
14
|
+
* every `injectUIStream` and `injectChatUI` generation in the app reaches the
|
|
15
|
+
* Stream tab.
|
|
16
|
+
*
|
|
17
|
+
* Renders nothing outside dev mode, and loads the panel itself
|
|
18
|
+
* (`@json-render/devtools`) with a dynamic `import()`, so a production build
|
|
19
|
+
* never fetches it.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```html
|
|
23
|
+
* <json-render #renderer [spec]="ui.spec()" [registry]="registry" />
|
|
24
|
+
* <json-render-devtools [renderer]="renderer" [catalog]="catalog" />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
class JsonRenderDevtools {
|
|
28
|
+
/** The renderer to inspect: its spec, state store and catalog. */
|
|
29
|
+
renderer = input(null, ...(ngDevMode ? [{ debugName: "renderer" }] : /* istanbul ignore next */ []));
|
|
30
|
+
/** Spec to show instead of the renderer's. */
|
|
31
|
+
spec = input(undefined, ...(ngDevMode ? [{ debugName: "spec" }] : /* istanbul ignore next */ []));
|
|
32
|
+
/** Catalog for the Catalog tab, if the renderer was not given one. */
|
|
33
|
+
catalog = input(undefined, ...(ngDevMode ? [{ debugName: "catalog" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/**
|
|
35
|
+
* A chat from `injectChatUI`: every assistant reply with a spec becomes a
|
|
36
|
+
* generation in the Spec tab's switcher.
|
|
37
|
+
*/
|
|
38
|
+
chat = input(null, ...(ngDevMode ? [{ debugName: "chat" }] : /* istanbul ignore next */ []));
|
|
39
|
+
initialOpen = input(false, ...(ngDevMode ? [{ debugName: "initialOpen" }] : /* istanbul ignore next */ []));
|
|
40
|
+
position = input('bottom-right', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
41
|
+
/** Toggle shortcut; `false` disables it. */
|
|
42
|
+
hotkey = input('mod+shift+j', ...(ngDevMode ? [{ debugName: "hotkey" }] : /* istanbul ignore next */ []));
|
|
43
|
+
/** Events kept in the ring buffer. Read once, at creation. */
|
|
44
|
+
bufferSize = input(500, ...(ngDevMode ? [{ debugName: "bufferSize" }] : /* istanbul ignore next */ []));
|
|
45
|
+
/** Pad the page while the panel is open instead of overlaying it. */
|
|
46
|
+
reserveSpace = input(true, ...(ngDevMode ? [{ debugName: "reserveSpace" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/** Let the viewer flip the panel between bottom and right dock. */
|
|
48
|
+
allowDockToggle = input(true, ...(ngDevMode ? [{ debugName: "allowDockToggle" }] : /* istanbul ignore next */ []));
|
|
49
|
+
/** Every event the panel records, for an app's own logging. */
|
|
50
|
+
event = output();
|
|
51
|
+
destroyRef = inject(DestroyRef);
|
|
52
|
+
events = null;
|
|
53
|
+
panel = null;
|
|
54
|
+
destroyed = false;
|
|
55
|
+
constructor() {
|
|
56
|
+
if (!isDevMode())
|
|
57
|
+
return;
|
|
58
|
+
this.destroyRef.onDestroy(() => (this.destroyed = true));
|
|
59
|
+
afterNextRender(() => {
|
|
60
|
+
void this.mount();
|
|
61
|
+
});
|
|
62
|
+
// The panel reads everything through getters, so it only needs telling
|
|
63
|
+
// when to re-read: whenever the spec, the chat or the renderer changes.
|
|
64
|
+
effect(() => {
|
|
65
|
+
this.currentSpec();
|
|
66
|
+
this.chat()?.messages();
|
|
67
|
+
this.renderer();
|
|
68
|
+
untracked(() => this.panel?.refresh());
|
|
69
|
+
});
|
|
70
|
+
// A state write becomes an event, which redraws the State tab — the
|
|
71
|
+
// store has no subscribe of the shape the panel's StateStore expects.
|
|
72
|
+
effect((onCleanup) => {
|
|
73
|
+
const store = this.renderer()?.stateStore;
|
|
74
|
+
if (!store)
|
|
75
|
+
return;
|
|
76
|
+
onCleanup(store.subscribeChanges((changes) => {
|
|
77
|
+
for (const change of changes) {
|
|
78
|
+
this.events?.push({
|
|
79
|
+
kind: 'state-set',
|
|
80
|
+
at: Date.now(),
|
|
81
|
+
path: change.path,
|
|
82
|
+
prev: undefined,
|
|
83
|
+
next: change.value,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
currentSpec() {
|
|
90
|
+
const override = this.spec();
|
|
91
|
+
if (override !== undefined)
|
|
92
|
+
return override;
|
|
93
|
+
const chat = this.chat();
|
|
94
|
+
if (chat) {
|
|
95
|
+
const replies = chat.messages().filter((m) => m.spec);
|
|
96
|
+
return replies.at(-1)?.spec ?? null;
|
|
97
|
+
}
|
|
98
|
+
return this.renderer()?.spec() ?? null;
|
|
99
|
+
}
|
|
100
|
+
currentCatalog() {
|
|
101
|
+
const own = this.catalog();
|
|
102
|
+
if (own !== undefined)
|
|
103
|
+
return own;
|
|
104
|
+
// The renderer's catalog input is typed structurally; a catalog made with
|
|
105
|
+
// `schema.createCatalog` is a core Catalog, which is what the tab reads.
|
|
106
|
+
const fromRenderer = this.renderer()?.catalog();
|
|
107
|
+
return fromRenderer && 'data' in fromRenderer
|
|
108
|
+
? fromRenderer
|
|
109
|
+
: null;
|
|
110
|
+
}
|
|
111
|
+
generations() {
|
|
112
|
+
const chat = this.chat();
|
|
113
|
+
if (!chat) {
|
|
114
|
+
const spec = this.currentSpec();
|
|
115
|
+
return spec ? [{ id: 'current', label: 'Current', spec }] : [];
|
|
116
|
+
}
|
|
117
|
+
return chat
|
|
118
|
+
.messages()
|
|
119
|
+
.filter((m) => m.role === 'assistant' && m.spec)
|
|
120
|
+
.map((m, i) => ({
|
|
121
|
+
id: m.id,
|
|
122
|
+
label: `Generation ${i + 1}`,
|
|
123
|
+
spec: m.spec,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
stateStore() {
|
|
127
|
+
const store = this.renderer()?.stateStore;
|
|
128
|
+
if (!store)
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
get: (path) => store.get(path),
|
|
132
|
+
set: (path, value) => store.set(path, value),
|
|
133
|
+
update: (updates) => store.update(updates),
|
|
134
|
+
getSnapshot: () => store.getSnapshot(),
|
|
135
|
+
subscribe: () => () => { },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async mount() {
|
|
139
|
+
// A production build defines `ngDevMode` as false, so the bundler drops
|
|
140
|
+
// the import below: nothing references the panel chunk, even if esbuild
|
|
141
|
+
// still writes it to disk.
|
|
142
|
+
if (typeof ngDevMode !== 'undefined' && !ngDevMode)
|
|
143
|
+
return;
|
|
144
|
+
const devtools = await import('@json-render/devtools');
|
|
145
|
+
if (this.destroyed)
|
|
146
|
+
return;
|
|
147
|
+
const events = devtools.createEventStore({
|
|
148
|
+
bufferSize: this.bufferSize(),
|
|
149
|
+
});
|
|
150
|
+
this.events = events;
|
|
151
|
+
const unsubscribeEvents = events.subscribe(() => {
|
|
152
|
+
const last = events.snapshot().at(-1);
|
|
153
|
+
if (last)
|
|
154
|
+
this.event.emit(last);
|
|
155
|
+
});
|
|
156
|
+
// Both observers come from the app's own `@json-render/core` and
|
|
157
|
+
// `ngx-json-render`, never through `@json-render/devtools`: the registries
|
|
158
|
+
// are module singletons, and devtools may carry a nested copy of core.
|
|
159
|
+
const unsubscribeActions = registerActionObserver({
|
|
160
|
+
onDispatch: (e) => events.push({
|
|
161
|
+
kind: 'action-dispatched',
|
|
162
|
+
at: e.at,
|
|
163
|
+
id: e.id,
|
|
164
|
+
name: e.name,
|
|
165
|
+
params: e.params,
|
|
166
|
+
}),
|
|
167
|
+
onSettle: (e) => events.push({
|
|
168
|
+
kind: 'action-settled',
|
|
169
|
+
at: e.at,
|
|
170
|
+
id: e.id,
|
|
171
|
+
ok: e.ok,
|
|
172
|
+
durationMs: e.durationMs,
|
|
173
|
+
result: e.result,
|
|
174
|
+
error: e.error !== undefined ? String(e.error) : undefined,
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
const unsubscribeStreams = _registerStreamObserver({
|
|
178
|
+
onStart: () => events.push({
|
|
179
|
+
kind: 'stream-lifecycle',
|
|
180
|
+
at: Date.now(),
|
|
181
|
+
phase: 'start',
|
|
182
|
+
}),
|
|
183
|
+
onPatch: (patch) => events.push({
|
|
184
|
+
kind: 'stream-patch',
|
|
185
|
+
at: Date.now(),
|
|
186
|
+
patch,
|
|
187
|
+
source: 'json',
|
|
188
|
+
}),
|
|
189
|
+
onText: (text) => events.push({ kind: 'stream-text', at: Date.now(), text }),
|
|
190
|
+
onUsage: (usage) => devtools.recordUsage(events, usage),
|
|
191
|
+
onEnd: (ok) => events.push({
|
|
192
|
+
kind: 'stream-lifecycle',
|
|
193
|
+
at: Date.now(),
|
|
194
|
+
phase: 'end',
|
|
195
|
+
ok,
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
const selection = devtools.createSelectionBus();
|
|
199
|
+
this.panel = devtools.createPanel({
|
|
200
|
+
context: {
|
|
201
|
+
events,
|
|
202
|
+
getSpec: () => this.currentSpec(),
|
|
203
|
+
getSpecs: () => this.generations(),
|
|
204
|
+
getCatalog: () => this.currentCatalog(),
|
|
205
|
+
getStateStore: () => this.stateStore(),
|
|
206
|
+
startPicker: (options) => devtools.startPicker(options),
|
|
207
|
+
selection,
|
|
208
|
+
activateTab: () => { },
|
|
209
|
+
},
|
|
210
|
+
tabs: [
|
|
211
|
+
devtools.specTab(),
|
|
212
|
+
devtools.stateTab(),
|
|
213
|
+
devtools.actionsTab(),
|
|
214
|
+
devtools.streamTab(),
|
|
215
|
+
devtools.catalogTab(),
|
|
216
|
+
],
|
|
217
|
+
initialOpen: this.initialOpen(),
|
|
218
|
+
position: this.position(),
|
|
219
|
+
hotkey: this.hotkey(),
|
|
220
|
+
reserveSpace: this.reserveSpace(),
|
|
221
|
+
allowDockToggle: this.allowDockToggle(),
|
|
222
|
+
});
|
|
223
|
+
const unsubscribeSelection = selection.subscribe((key) => {
|
|
224
|
+
if (key)
|
|
225
|
+
devtools.highlightElement(key);
|
|
226
|
+
});
|
|
227
|
+
// Makes every renderer wrap its elements in `data-jr-key` spans, which is
|
|
228
|
+
// what the picker and the highlight look for.
|
|
229
|
+
const releaseActive = markDevtoolsActive();
|
|
230
|
+
this.destroyRef.onDestroy(() => {
|
|
231
|
+
releaseActive();
|
|
232
|
+
unsubscribeSelection();
|
|
233
|
+
this.panel?.destroy();
|
|
234
|
+
this.panel = null;
|
|
235
|
+
unsubscribeStreams();
|
|
236
|
+
unsubscribeActions();
|
|
237
|
+
unsubscribeEvents();
|
|
238
|
+
this.events = null;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderDevtools, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
242
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.22", type: JsonRenderDevtools, isStandalone: true, selector: "json-render-devtools", inputs: { renderer: { classPropertyName: "renderer", publicName: "renderer", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, chat: { classPropertyName: "chat", publicName: "chat", isSignal: true, isRequired: false, transformFunction: null }, initialOpen: { classPropertyName: "initialOpen", publicName: "initialOpen", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null }, bufferSize: { classPropertyName: "bufferSize", publicName: "bufferSize", isSignal: true, isRequired: false, transformFunction: null }, reserveSpace: { classPropertyName: "reserveSpace", publicName: "reserveSpace", isSignal: true, isRequired: false, transformFunction: null }, allowDockToggle: { classPropertyName: "allowDockToggle", publicName: "allowDockToggle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { event: "event" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
243
|
+
}
|
|
244
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderDevtools, decorators: [{
|
|
245
|
+
type: Component,
|
|
246
|
+
args: [{
|
|
247
|
+
selector: 'json-render-devtools',
|
|
248
|
+
template: '',
|
|
249
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
250
|
+
}]
|
|
251
|
+
}], ctorParameters: () => [], propDecorators: { renderer: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderer", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], chat: [{ type: i0.Input, args: [{ isSignal: true, alias: "chat", required: false }] }], initialOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialOpen", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], bufferSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "bufferSize", required: false }] }], reserveSpace: [{ type: i0.Input, args: [{ isSignal: true, alias: "reserveSpace", required: false }] }], allowDockToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDockToggle", required: false }] }], event: [{ type: i0.Output, args: ["event"] }] } });
|
|
252
|
+
|
|
253
|
+
/*
|
|
254
|
+
* Public API Surface of ngx-json-render/devtools
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Generated bundle index. Do not edit.
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
export { JsonRenderDevtools };
|
|
262
|
+
//# sourceMappingURL=ngx-json-render-devtools.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-json-render-devtools.mjs","sources":["../../../projects/ngx-json-render/devtools/src/json-render-devtools.ts","../../../projects/ngx-json-render/devtools/src/public-api.ts","../../../projects/ngx-json-render/devtools/src/ngx-json-render-devtools.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n afterNextRender,\n effect,\n inject,\n input,\n isDevMode,\n output,\n untracked,\n} from '@angular/core';\nimport {\n type Catalog,\n type Spec,\n type StateStore,\n markDevtoolsActive,\n registerActionObserver,\n} from '@json-render/core';\nimport type {\n DevtoolsEvent,\n EventStore,\n PanelHandle,\n PanelPosition,\n SpecEntry,\n} from '@json-render/devtools';\nimport {\n type ChatUIReturn,\n type JsonRenderer,\n ɵregisterStreamObserver,\n} from 'ngx-json-render';\n\n/**\n * A floating json-render devtools panel for an Angular app — the counterpart\n * of `<JsonRenderDevtools />` in `@json-render/devtools-react`, `-vue`,\n * `-svelte` and `-solid`, with the same tabs (Spec, State, Actions, Stream,\n * Catalog) and the same element picker.\n *\n * Angular has no provider above `<json-render>` to read the renderer from, so\n * the renderer is passed in by template reference. Streams need no wiring:\n * every `injectUIStream` and `injectChatUI` generation in the app reaches the\n * Stream tab.\n *\n * Renders nothing outside dev mode, and loads the panel itself\n * (`@json-render/devtools`) with a dynamic `import()`, so a production build\n * never fetches it.\n *\n * @example\n * ```html\n * <json-render #renderer [spec]=\"ui.spec()\" [registry]=\"registry\" />\n * <json-render-devtools [renderer]=\"renderer\" [catalog]=\"catalog\" />\n * ```\n */\n@Component({\n selector: 'json-render-devtools',\n template: '',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class JsonRenderDevtools {\n /** The renderer to inspect: its spec, state store and catalog. */\n readonly renderer = input<JsonRenderer | null>(null);\n /** Spec to show instead of the renderer's. */\n readonly spec = input<Spec | null | undefined>(undefined);\n /** Catalog for the Catalog tab, if the renderer was not given one. */\n readonly catalog = input<Catalog | null | undefined>(undefined);\n /**\n * A chat from `injectChatUI`: every assistant reply with a spec becomes a\n * generation in the Spec tab's switcher.\n */\n readonly chat = input<ChatUIReturn | null>(null);\n readonly initialOpen = input(false);\n readonly position = input<PanelPosition>('bottom-right');\n /** Toggle shortcut; `false` disables it. */\n readonly hotkey = input<string | false>('mod+shift+j');\n /** Events kept in the ring buffer. Read once, at creation. */\n readonly bufferSize = input(500);\n /** Pad the page while the panel is open instead of overlaying it. */\n readonly reserveSpace = input(true);\n /** Let the viewer flip the panel between bottom and right dock. */\n readonly allowDockToggle = input(true);\n /** Every event the panel records, for an app's own logging. */\n readonly event = output<DevtoolsEvent>();\n\n private readonly destroyRef = inject(DestroyRef);\n private events: EventStore | null = null;\n private panel: PanelHandle | null = null;\n private destroyed = false;\n\n constructor() {\n if (!isDevMode()) return;\n\n this.destroyRef.onDestroy(() => (this.destroyed = true));\n afterNextRender(() => {\n void this.mount();\n });\n\n // The panel reads everything through getters, so it only needs telling\n // when to re-read: whenever the spec, the chat or the renderer changes.\n effect(() => {\n this.currentSpec();\n this.chat()?.messages();\n this.renderer();\n untracked(() => this.panel?.refresh());\n });\n\n // A state write becomes an event, which redraws the State tab — the\n // store has no subscribe of the shape the panel's StateStore expects.\n effect((onCleanup) => {\n const store = this.renderer()?.stateStore;\n if (!store) return;\n onCleanup(\n store.subscribeChanges((changes) => {\n for (const change of changes) {\n this.events?.push({\n kind: 'state-set',\n at: Date.now(),\n path: change.path,\n prev: undefined,\n next: change.value,\n });\n }\n }),\n );\n });\n }\n\n private currentSpec(): Spec | null {\n const override = this.spec();\n if (override !== undefined) return override;\n const chat = this.chat();\n if (chat) {\n const replies = chat.messages().filter((m) => m.spec);\n return replies.at(-1)?.spec ?? null;\n }\n return this.renderer()?.spec() ?? null;\n }\n\n private currentCatalog(): Catalog | null {\n const own = this.catalog();\n if (own !== undefined) return own;\n // The renderer's catalog input is typed structurally; a catalog made with\n // `schema.createCatalog` is a core Catalog, which is what the tab reads.\n const fromRenderer = this.renderer()?.catalog();\n return fromRenderer && 'data' in fromRenderer\n ? (fromRenderer as unknown as Catalog)\n : null;\n }\n\n private generations(): SpecEntry[] {\n const chat = this.chat();\n if (!chat) {\n const spec = this.currentSpec();\n return spec ? [{ id: 'current', label: 'Current', spec }] : [];\n }\n return chat\n .messages()\n .filter((m) => m.role === 'assistant' && m.spec)\n .map((m, i) => ({\n id: m.id,\n label: `Generation ${i + 1}`,\n spec: m.spec as Spec,\n }));\n }\n\n private stateStore(): StateStore | null {\n const store = this.renderer()?.stateStore;\n if (!store) return null;\n return {\n get: (path) => store.get(path),\n set: (path, value) => store.set(path, value),\n update: (updates) => store.update(updates),\n getSnapshot: () => store.getSnapshot(),\n subscribe: () => () => {},\n };\n }\n\n private async mount(): Promise<void> {\n // A production build defines `ngDevMode` as false, so the bundler drops\n // the import below: nothing references the panel chunk, even if esbuild\n // still writes it to disk.\n if (typeof ngDevMode !== 'undefined' && !ngDevMode) return;\n const devtools = await import('@json-render/devtools');\n if (this.destroyed) return;\n\n const events = devtools.createEventStore({\n bufferSize: this.bufferSize(),\n });\n this.events = events;\n\n const unsubscribeEvents = events.subscribe(() => {\n const last = events.snapshot().at(-1);\n if (last) this.event.emit(last);\n });\n\n // Both observers come from the app's own `@json-render/core` and\n // `ngx-json-render`, never through `@json-render/devtools`: the registries\n // are module singletons, and devtools may carry a nested copy of core.\n const unsubscribeActions = registerActionObserver({\n onDispatch: (e) =>\n events.push({\n kind: 'action-dispatched',\n at: e.at,\n id: e.id,\n name: e.name,\n params: e.params,\n }),\n onSettle: (e) =>\n events.push({\n kind: 'action-settled',\n at: e.at,\n id: e.id,\n ok: e.ok,\n durationMs: e.durationMs,\n result: e.result,\n error: e.error !== undefined ? String(e.error) : undefined,\n }),\n });\n\n const unsubscribeStreams = ɵregisterStreamObserver({\n onStart: () =>\n events.push({\n kind: 'stream-lifecycle',\n at: Date.now(),\n phase: 'start',\n }),\n onPatch: (patch) =>\n events.push({\n kind: 'stream-patch',\n at: Date.now(),\n patch,\n source: 'json',\n }),\n onText: (text) =>\n events.push({ kind: 'stream-text', at: Date.now(), text }),\n onUsage: (usage) => devtools.recordUsage(events, usage),\n onEnd: (ok) =>\n events.push({\n kind: 'stream-lifecycle',\n at: Date.now(),\n phase: 'end',\n ok,\n }),\n });\n\n const selection = devtools.createSelectionBus();\n this.panel = devtools.createPanel({\n context: {\n events,\n getSpec: () => this.currentSpec(),\n getSpecs: () => this.generations(),\n getCatalog: () => this.currentCatalog(),\n getStateStore: () => this.stateStore(),\n startPicker: (options) => devtools.startPicker(options),\n selection,\n activateTab: () => {},\n },\n tabs: [\n devtools.specTab(),\n devtools.stateTab(),\n devtools.actionsTab(),\n devtools.streamTab(),\n devtools.catalogTab(),\n ],\n initialOpen: this.initialOpen(),\n position: this.position(),\n hotkey: this.hotkey(),\n reserveSpace: this.reserveSpace(),\n allowDockToggle: this.allowDockToggle(),\n });\n\n const unsubscribeSelection = selection.subscribe((key) => {\n if (key) devtools.highlightElement(key);\n });\n // Makes every renderer wrap its elements in `data-jr-key` spans, which is\n // what the picker and the highlight look for.\n const releaseActive = markDevtoolsActive();\n\n this.destroyRef.onDestroy(() => {\n releaseActive();\n unsubscribeSelection();\n this.panel?.destroy();\n this.panel = null;\n unsubscribeStreams();\n unsubscribeActions();\n unsubscribeEvents();\n this.events = null;\n });\n }\n}\n","/*\n * Public API Surface of ngx-json-render/devtools\n */\n\nexport { JsonRenderDevtools } from './json-render-devtools';\nexport type { DevtoolsEvent, PanelPosition } from '@json-render/devtools';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["ɵregisterStreamObserver"],"mappings":"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"}
|
|
@@ -3139,6 +3139,34 @@ function applyPatch(spec, patch) {
|
|
|
3139
3139
|
return newSpec;
|
|
3140
3140
|
}
|
|
3141
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
|
+
|
|
3142
3170
|
/**
|
|
3143
3171
|
* Convert a flat element list to a Spec.
|
|
3144
3172
|
* Input elements use key/parentKey to establish identity and relationships.
|
|
@@ -3462,6 +3490,8 @@ function injectUIStream(options) {
|
|
|
3462
3490
|
? { ...previousSpec, elements: { ...previousSpec.elements } }
|
|
3463
3491
|
: { root: '', elements: {} };
|
|
3464
3492
|
spec.set(currentSpec);
|
|
3493
|
+
notifyStreamObservers('onStart');
|
|
3494
|
+
let failed = false;
|
|
3465
3495
|
const handleLine = (line) => {
|
|
3466
3496
|
const parsed = parseLine(line);
|
|
3467
3497
|
if (!parsed)
|
|
@@ -3469,11 +3499,13 @@ function injectUIStream(options) {
|
|
|
3469
3499
|
gate.commit(() => {
|
|
3470
3500
|
if (parsed.type === 'usage') {
|
|
3471
3501
|
usage.set(parsed.usage);
|
|
3502
|
+
notifyStreamObservers('onUsage', parsed.usage);
|
|
3472
3503
|
return;
|
|
3473
3504
|
}
|
|
3474
3505
|
rawLines.update((prev) => [...prev, line]);
|
|
3475
3506
|
currentSpec = applyPatch(currentSpec, parsed.patch);
|
|
3476
3507
|
spec.set(currentSpec);
|
|
3508
|
+
notifyStreamObservers('onPatch', parsed.patch);
|
|
3477
3509
|
});
|
|
3478
3510
|
};
|
|
3479
3511
|
try {
|
|
@@ -3504,6 +3536,7 @@ function injectUIStream(options) {
|
|
|
3504
3536
|
// not want rendered.
|
|
3505
3537
|
if (check.blocked || (validate === 'strict' && check.hasErrors)) {
|
|
3506
3538
|
const invalid = new Error(`Generated spec failed validation:\n${formatSpecCheckIssues(check.issues)}`);
|
|
3539
|
+
failed = true;
|
|
3507
3540
|
error.set(invalid);
|
|
3508
3541
|
options.onError?.(invalid);
|
|
3509
3542
|
return;
|
|
@@ -3514,6 +3547,7 @@ function injectUIStream(options) {
|
|
|
3514
3547
|
catch (err) {
|
|
3515
3548
|
if (isAbortError(err))
|
|
3516
3549
|
return;
|
|
3550
|
+
failed = true;
|
|
3517
3551
|
const resolvedError = err instanceof Error ? err : new Error(String(err));
|
|
3518
3552
|
gate.commit(() => {
|
|
3519
3553
|
error.set(resolvedError);
|
|
@@ -3522,6 +3556,7 @@ function injectUIStream(options) {
|
|
|
3522
3556
|
}
|
|
3523
3557
|
finally {
|
|
3524
3558
|
gate.commit(() => isStreaming.set(false));
|
|
3559
|
+
notifyStreamObservers('onEnd', !failed);
|
|
3525
3560
|
}
|
|
3526
3561
|
};
|
|
3527
3562
|
return {
|
|
@@ -3622,6 +3657,8 @@ function injectChatUI(options) {
|
|
|
3622
3657
|
messages.update((prev) => [...prev, userMessage, assistantMessage]);
|
|
3623
3658
|
isStreaming.set(true);
|
|
3624
3659
|
error.set(null);
|
|
3660
|
+
notifyStreamObservers('onStart');
|
|
3661
|
+
let failed = false;
|
|
3625
3662
|
// Mutable state for accumulating the assistant response
|
|
3626
3663
|
let accumulatedText = '';
|
|
3627
3664
|
let currentSpec = { root: '', elements: {} };
|
|
@@ -3633,12 +3670,18 @@ function injectChatUI(options) {
|
|
|
3633
3670
|
// touch, so the message can hold it directly — no snapshot copy.
|
|
3634
3671
|
currentSpec = applyPatch(currentSpec, patch);
|
|
3635
3672
|
const snapshot = currentSpec;
|
|
3636
|
-
gate.commit(() =>
|
|
3673
|
+
gate.commit(() => {
|
|
3674
|
+
messages.update((prev) => prev.map((m) => m.id === assistantId ? { ...m, spec: snapshot } : m));
|
|
3675
|
+
notifyStreamObservers('onPatch', patch);
|
|
3676
|
+
});
|
|
3637
3677
|
},
|
|
3638
3678
|
onText(line) {
|
|
3639
3679
|
accumulatedText += (accumulatedText ? '\n' : '') + line;
|
|
3640
3680
|
const snapshot = accumulatedText;
|
|
3641
|
-
gate.commit(() =>
|
|
3681
|
+
gate.commit(() => {
|
|
3682
|
+
messages.update((prev) => prev.map((m) => m.id === assistantId ? { ...m, text: snapshot } : m));
|
|
3683
|
+
notifyStreamObservers('onText', line);
|
|
3684
|
+
});
|
|
3642
3685
|
},
|
|
3643
3686
|
});
|
|
3644
3687
|
try {
|
|
@@ -3672,6 +3715,7 @@ function injectChatUI(options) {
|
|
|
3672
3715
|
// not want rendered.
|
|
3673
3716
|
if (check.blocked || (validate === 'strict' && check.hasErrors)) {
|
|
3674
3717
|
const invalid = new Error(`Generated spec failed validation:\n${formatSpecCheckIssues(check.issues)}`);
|
|
3718
|
+
failed = true;
|
|
3675
3719
|
error.set(invalid);
|
|
3676
3720
|
options.onError?.(invalid);
|
|
3677
3721
|
return;
|
|
@@ -3687,6 +3731,7 @@ function injectChatUI(options) {
|
|
|
3687
3731
|
dropEmptyPlaceholder();
|
|
3688
3732
|
if (isAbortError(err))
|
|
3689
3733
|
return;
|
|
3734
|
+
failed = true;
|
|
3690
3735
|
const resolvedError = err instanceof Error ? err : new Error(String(err));
|
|
3691
3736
|
gate.commit(() => {
|
|
3692
3737
|
error.set(resolvedError);
|
|
@@ -3695,6 +3740,7 @@ function injectChatUI(options) {
|
|
|
3695
3740
|
}
|
|
3696
3741
|
finally {
|
|
3697
3742
|
gate.commit(() => isStreaming.set(false));
|
|
3743
|
+
notifyStreamObservers('onEnd', !failed);
|
|
3698
3744
|
}
|
|
3699
3745
|
};
|
|
3700
3746
|
return {
|
|
@@ -3726,5 +3772,5 @@ function injectChatUI(options) {
|
|
|
3726
3772
|
* Generated bundle index. Do not edit.
|
|
3727
3773
|
*/
|
|
3728
3774
|
|
|
3729
|
-
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 };
|
|
3730
3776
|
//# sourceMappingURL=ngx-json-render.mjs.map
|