@dudousxd/nestjs-agent-react 0.13.0 → 0.15.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 CHANGED
@@ -111,7 +111,7 @@ function Chat() {
111
111
  | On the instance | What it is |
112
112
  |---|---|
113
113
  | `items` | The mounted window, oldest first. Each item carries `blocks`, `text`, `usage`, `timestamp`, `isStreaming`, `isLastAssistant`, and its action machines. |
114
- | `items[i].blocks` | `{ kind: 'text' \| 'reasoning' \| 'tools' \| 'sources' \| 'elicitation' \| 'ui' }`. A `ui` block is a component the server pushed (`{ id, component, props, version }`) — look `component` up in your own registry. A `tools` block is a run of CONSECUTIVE tool parts — any other part between two calls (including a `step-start` marker) ends the run. A `reasoning` block carries `isOpen`/`toggle`, open while it streams. A `sources` block appears only under `sources: true`, an `elicitation` block only under `onAnswer` — see below. |
114
+ | `items[i].blocks` | `{ kind: 'text' \| 'reasoning' \| 'tools' \| 'sources' \| 'elicitation' \| 'ui' }`. A `ui` block is a component the server pushed (`{ id, component, props, version, toolCallId }`) — draw it with `<GenerativeUI>` (below) or look `component` up in your own registry. A `tools` block is a run of CONSECUTIVE tool parts — any other part between two calls (including a `step-start` marker) ends the run. A `reasoning` block carries `isOpen`/`toggle`, open while it streams. A `sources` block appears only under `sources: true`, an `elicitation` block only under `onAnswer` — see below. |
115
115
  | `items[i].blocks[n]` (`tools`) | Also carries `calls`: the same parts, each with `{ toolCallId, name, toolKind, parentId, children, approval, isAwaitingApproval, approve, reject, error }`, and `roots`: the same calls as a tree (a call nested under another by the stream's `parentId` sits in its parent's `children`). `approval` is `{ approver, expiresAt, reason, status, remember, decidedBy, decidedVia, decisionReason }` when the runner said who has to decide, else `null` — `status` is `pending` / `approved` / `rejected` / `expired`. |
116
116
  | `items[i].copy` | `{ available, copied, copy() }` — `copied` flashes for `copyResetMs` (default 1500). |
117
117
  | `items[i].edit` | `{ available, isEditing, draft, canSave, start(), cancel(), setDraft(), save(), getTextareaProps() }`. The prop-getter focuses with the caret at the end, saves on Enter, cancels on Escape. |
@@ -348,6 +348,51 @@ type-ahead narrows the list it received; it never re-derives which skills apply.
348
348
  The shadcn `ChatComposer` wires all of this for you: pass `autocompleteSources` and it renders the
349
349
  menu (`ChatCommandPalette`, grown into the combobox's listbox) above the composer card.
350
350
 
351
+ ### Generative UI (`/genui`)
352
+
353
+ `@dudousxd/nestjs-agent-react/genui` draws server-pushed components (`ui` frames, persisted `ui[]`)
354
+ with YOUR renderers. Headless: it adds no element and no style — only what your registry renders.
355
+
356
+ ```tsx
357
+ import { GenerativeUI, type GenuiRegistry } from '@dudousxd/nestjs-agent-react/genui';
358
+ import { catalog } from './catalog'; // optional: a @dudousxd/nestjs-agent-genui catalog
359
+
360
+ const registry: GenuiRegistry = { DataTable: MyTable, Chart: MyChart, Card: MyCard, Text: MyText };
361
+
362
+ <MessageItem
363
+ message={message}
364
+ renderUi={(block) => (
365
+ <GenerativeUI
366
+ part={block}
367
+ registry={registry}
368
+ catalog={catalog}
369
+ resolveComponent={(name, version) => loadTenantComponent(name, version)}
370
+ fallback={({ reason, item }) => <UnknownComponent name={item.component} reason={reason} />}
371
+ loading={<Skeleton />}
372
+ />
373
+ )}
374
+ />;
375
+ ```
376
+
377
+ | Prop | |
378
+ |---|---|
379
+ | `part` | A transcript `ui` block, a `data-ui` message part, or a stored `{ id, component, props, version? }`. |
380
+ | `registry` | Component name → your renderer. It receives the props spread (and `children` as a tree layout node). |
381
+ | `catalog?` | Validate props before drawing (`catalog.validateSync` when it can, so no placeholder flashes). Components the catalog does not know render unvalidated. |
382
+ | `resolveComponent?(name, version)` | For components the registry lacks, e.g. a tenant's own, at the exact version a message was rendered with. Sync or async, cached per resolver/name/version. |
383
+ | `fallback?` | A node, or `({ reason: 'unknown' \| 'invalid' \| 'error', item, issues?, error? }) => node`. Default: nothing. |
384
+ | `loading?`, `onError?` | While a resolver or async validation is pending; a renderer threw (each item has its own error boundary). |
385
+
386
+ A `genui:tree` frame (tree mode) is drawn node by node through the same registry, catalog and
387
+ fallbacks, each node in its own boundary; put a `genui:tree` entry in the registry to take over.
388
+ `useGenerativeUI(part, options)` returns the state (`ready` with `Component`/`props`, `loading`,
389
+ `problem`) for your own chrome; wrap it in `<GenerativeUIScope>` when it may draw a tree.
390
+
391
+ **json-render** (optional peer `@json-render/react` >= 0.21):
392
+ `@dudousxd/nestjs-agent-react/genui/json-render` renders tree frames through json-render's
393
+ `Renderer` — `registry[GENUI_TREE_COMPONENT] = jsonRenderTree(toJsonRenderRegistry(registry))` —
394
+ plus `JsonRenderTree` and `treeToJsonRenderSpec`.
395
+
351
396
  ### Designed components, as copy-in source
352
397
 
353
398
  `MessageList`/`MessageItem`/`ChatInput` are deliberately minimal. For a styled, accessible chat
@@ -423,12 +468,101 @@ disables itself while busy never noticed; a double-submitted one, and StrictMode
423
468
  effect, did.
424
469
 
425
470
  `chat` is the AI SDK v7 `useChat` return value (`messages`, `status`, `sendMessage`, `stop`, …) spread
426
- together with the extras: `runId`/`activeRunId`, `client` (the raw `AgentClient`), thread list/CRUD
471
+ together with the extras: `runId`/`activeRunId`, `connection`, `getThreadId`, `backend` (the `AgentBackend`; also
472
+ under its old name `client`), thread list/CRUD
427
473
  (`threads`, `loadThreads`, `loadThread`, `deleteThread`, `forkThread`, `renameThread`, `promoteThread`,
428
474
  `truncateFromMessage`), `quota`/`loadQuota`, `cancel`, HITL `approve`/`reject` and `answer`/`skip`, and `regenerate`.
429
475
  `MyToolCard`'s `part` prop above types as the exported `AnyToolUIPart` (`ToolUIPart | DynamicToolUIPart`
430
476
  — `MessageItem` uses the same union for its own `renderToolPart` callback).
431
477
 
478
+ ### Your own backend (`AgentBackend`)
479
+
480
+ Everything the hooks ask of a server goes through one interface, `AgentBackend`: start a turn
481
+ (`openChatStream`), attach to a streaming run (`resumeChatStream`), cancel, list/get/update/delete
482
+ threads — required — plus optional fork/promote/truncate, approve/reject/answer/skip, upload, tools,
483
+ skills, quota and message feedback. `AgentClient` (fetch over this library's routes) is the default.
484
+ An app with its own client, auth scheme or server implements the interface and passes it in:
485
+
486
+ ```ts
487
+ import type { AgentBackend } from '@dudousxd/nestjs-agent-react';
488
+
489
+ const backend: AgentBackend = {
490
+ async openChatStream({ body, headers, signal }) {
491
+ const res = await fetch('/api/chat', {
492
+ method: 'POST',
493
+ credentials: 'include',
494
+ headers: { 'content-type': 'application/json', ...csrfHeader(), ...headers },
495
+ body: JSON.stringify(body),
496
+ signal,
497
+ });
498
+ if (!res.ok || !res.body) throw new Error(`chat failed: ${res.status}`);
499
+ return { body: res.body }; // SSE bytes in docs/stream-protocol.md framing
500
+ },
501
+ async resumeChatStream({ runId, after, signal }) {
502
+ const res = await fetch(`/api/chat/${runId}/stream${after ? `?after=${after}` : ''}`, {
503
+ credentials: 'include',
504
+ signal,
505
+ });
506
+ return res.status === 404 ? null : { body: res.body! };
507
+ },
508
+ cancelStream: (runId) => api.chat.cancel(runId),
509
+ listThreads: () => api.threads.list(),
510
+ getThread: (id) => api.threads.get(id),
511
+ updateThread: (id, patch) => api.threads.update(id, patch),
512
+ deleteThread: (id) => api.threads.remove(id),
513
+ setMessageFeedback: (id, input) => api.messages.feedback(id, input),
514
+ };
515
+
516
+ const chat = useAgentChat({ backend }); // chat.backend === backend, typed as yours
517
+ ```
518
+
519
+ Calling a hook method whose optional backend member is missing throws
520
+ `AgentBackendUnsupportedError`. The body a backend receives is `{ message, threadId?, agent?,
521
+ attachments?, pageContext?, regenerate? }`; the SSE it returns and the REST shapes are in
522
+ [docs/stream-protocol.md](../../docs/stream-protocol.md).
523
+
524
+ **Cookie session + CSRF with the default client.** `credentials` and `getHeaders` are all it takes —
525
+ `getHeaders` runs per request, so a rotated token is picked up:
526
+
527
+ ```ts
528
+ const readCookie = (name: string) =>
529
+ decodeURIComponent(document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))?.[1] ?? '');
530
+
531
+ useAgentChat({
532
+ baseUrl: '/api',
533
+ credentials: 'include', // 'same-origin' (the fetch default) is enough when the API is same-origin
534
+ getHeaders: () => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') }),
535
+ });
536
+ ```
537
+
538
+ ### Reconnecting a dropped stream
539
+
540
+ A server that numbers its frames (SSE `id:`, as this library's does) can be resumed: when the
541
+ connection drops mid-run, the transport re-attaches with `GET <base>/chat/:runId/stream?after=<last
542
+ id>`, backing off 0.5s, 1s, 2s… (`reconnect: { maxAttempts, baseDelayMs, maxDelayMs }`, or `false`).
543
+ Meanwhile `chat.status` is `'reconnecting'` (a busy status to `useChatTranscript`) and
544
+ `chat.connection` says which attempt it is on. If the run ended while the client was away (the
545
+ resume answers 404), the hook reloads the thread so the full answer replaces the partial one. After
546
+ the last failed attempt the turn ends with an error.
547
+
548
+ ### Thread list and message feedback
549
+
550
+ ```tsx
551
+ import { useMessageFeedback, useThreads } from '@dudousxd/nestjs-agent-react';
552
+
553
+ const { threads, isLoading, rename, remove, refresh } = useThreads({ backend: chat.backend });
554
+ const feedback = useMessageFeedback({ backend: chat.backend, threadId: chat.getThreadId });
555
+
556
+ <button aria-pressed={feedback.feedbackOf(message)?.value === 'up'}
557
+ onClick={() => feedback.toggle(message, 'up')}>Helpful</button>
558
+ ```
559
+
560
+ `useThreads` refreshes itself when a chat on the same backend creates a thread or settles a run,
561
+ and patches a streamed title in place; `rename`/`remove` are optimistic and roll back on failure.
562
+ `useMessageFeedback` reads a replayed message's rating from `message.metadata.feedback`, rates
563
+ through `POST <base>/messages/:id/feedback`, and maps a message streamed in this session to the row
564
+ its run persisted (the live message carries `metadata.runId`).
565
+
432
566
  ### The transport, standalone
433
567
 
434
568
  `AgentChatTransport` is a plain AI SDK v7 `ChatTransport` — wire it straight into `useChat` for the SSE
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/genui/json-render.tsx
22
+ var json_render_exports = {};
23
+ __export(json_render_exports, {
24
+ JsonRenderTree: () => JsonRenderTree,
25
+ jsonRenderTree: () => jsonRenderTree,
26
+ toJsonRenderRegistry: () => toJsonRenderRegistry,
27
+ treeToJsonRenderSpec: () => treeToJsonRenderSpec
28
+ });
29
+ module.exports = __toCommonJS(json_render_exports);
30
+ var import_react = require("@json-render/react");
31
+ var import_react2 = require("react");
32
+ function treeToJsonRenderSpec(root) {
33
+ const elements = {};
34
+ let next = 0;
35
+ const visit = /* @__PURE__ */ __name((node) => {
36
+ const id = `el-${next++}`;
37
+ const entry = {
38
+ type: node.type,
39
+ props: node.props ?? {},
40
+ children: []
41
+ };
42
+ elements[id] = entry;
43
+ for (const child of node.children ?? []) entry.children.push(visit(child));
44
+ return id;
45
+ }, "visit");
46
+ return {
47
+ root: visit(root),
48
+ elements
49
+ };
50
+ }
51
+ __name(treeToJsonRenderSpec, "treeToJsonRenderSpec");
52
+ function toJsonRenderRegistry(registry) {
53
+ return Object.fromEntries(Object.entries(registry).map(([name, Renderer2]) => [
54
+ name,
55
+ ({ element, children }) => /* @__PURE__ */ React.createElement(Renderer2, element.props ?? {}, children)
56
+ ]));
57
+ }
58
+ __name(toJsonRenderRegistry, "toJsonRenderRegistry");
59
+ function JsonRenderTree({ root, registry, initialState }) {
60
+ const spec = (0, import_react2.useMemo)(() => root === void 0 ? null : treeToJsonRenderSpec(root), [
61
+ root
62
+ ]);
63
+ if (spec === null) return null;
64
+ return /* @__PURE__ */ React.createElement(import_react.JSONUIProvider, {
65
+ registry,
66
+ ...initialState !== void 0 ? {
67
+ initialState
68
+ } : {}
69
+ }, /* @__PURE__ */ React.createElement(import_react.Renderer, {
70
+ spec,
71
+ registry
72
+ }));
73
+ }
74
+ __name(JsonRenderTree, "JsonRenderTree");
75
+ function jsonRenderTree(registry) {
76
+ return /* @__PURE__ */ __name(function JsonRenderTreeFrame({ root }) {
77
+ return /* @__PURE__ */ React.createElement(JsonRenderTree, {
78
+ registry,
79
+ ...root !== void 0 ? {
80
+ root
81
+ } : {}
82
+ });
83
+ }, "JsonRenderTreeFrame");
84
+ }
85
+ __name(jsonRenderTree, "jsonRenderTree");
86
+ // Annotate the CommonJS export names for ESM import in node:
87
+ 0 && (module.exports = {
88
+ JsonRenderTree,
89
+ jsonRenderTree,
90
+ toJsonRenderRegistry,
91
+ treeToJsonRenderSpec
92
+ });
93
+ //# sourceMappingURL=genui-json-render.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/genui/json-render.tsx"],"sourcesContent":["/**\n * Optional json-render adapter for tree frames (`@dudousxd/nestjs-agent-react/genui/json-render`).\n * Needs the optional peer `@json-render/react` (>= 0.21). Use it when your app already renders\n * json-render specs and wants tree frames to go through the same `Renderer` (its state, actions and\n * visibility machinery); otherwise `<GenerativeUI>` renders trees natively.\n *\n * ```tsx\n * const registry = { ...myComponents, [GENUI_TREE_COMPONENT]: jsonRenderTree(myJsonRenderRegistry) };\n * ```\n */\nimport { type ComponentRegistry, JSONUIProvider, Renderer } from '@json-render/react';\nimport { type ReactNode, useMemo } from 'react';\nimport type { GenerativeUIElement, GenuiRegistry, GenuiRenderer } from './types.js';\n\n/** A json-render flat spec: `{ root, elements: { [id]: { type, props, children } } }`. */\nexport interface JsonRenderFlatSpec {\n root: string;\n elements: Record<string, { type: string; props: Record<string, unknown>; children: string[] }>;\n}\n\n/** A tree frame's `root` as a json-render flat spec (ids `el-0`, `el-1`, … depth first). */\nexport function treeToJsonRenderSpec(root: GenerativeUIElement): JsonRenderFlatSpec {\n const elements: JsonRenderFlatSpec['elements'] = {};\n let next = 0;\n const visit = (node: GenerativeUIElement): string => {\n const id = `el-${next++}`;\n const entry = { type: node.type, props: node.props ?? {}, children: [] as string[] };\n elements[id] = entry;\n for (const child of node.children ?? []) entry.children.push(visit(child));\n return id;\n };\n return { root: visit(root), elements };\n}\n\n/**\n * Adapt a genui registry (renderers taking props + `children`) to a json-render registry (renderers\n * taking `{ element, children }`), so one set of components serves both.\n */\nexport function toJsonRenderRegistry(registry: GenuiRegistry): ComponentRegistry {\n return Object.fromEntries(\n Object.entries(registry).map(([name, Renderer]) => [\n name,\n ({ element, children }: { element: { props?: unknown }; children?: ReactNode }) => (\n <Renderer {...((element.props ?? {}) as Record<string, unknown>)}>{children}</Renderer>\n ),\n ]),\n );\n}\n\nexport interface JsonRenderTreeProps {\n root?: GenerativeUIElement;\n registry: ComponentRegistry;\n /** json-render state the spec's `$state` expressions read. */\n initialState?: Record<string, unknown>;\n}\n\n/** Render a tree frame's `root` with json-render. */\nexport function JsonRenderTree({ root, registry, initialState }: JsonRenderTreeProps) {\n const spec = useMemo(() => (root === undefined ? null : treeToJsonRenderSpec(root)), [root]);\n if (spec === null) return null;\n return (\n <JSONUIProvider registry={registry} {...(initialState !== undefined ? { initialState } : {})}>\n <Renderer spec={spec as never} registry={registry} />\n </JSONUIProvider>\n );\n}\n\n/** A registry entry for `genui:tree` that renders through json-render with `registry`. */\nexport function jsonRenderTree(registry: ComponentRegistry): GenuiRenderer<{\n root?: GenerativeUIElement;\n}> {\n return function JsonRenderTreeFrame({ root }: { root?: GenerativeUIElement }) {\n return <JsonRenderTree registry={registry} {...(root !== undefined ? { root } : {})} />;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;AAUA,mBAAiE;AACjE,IAAAA,gBAAwC;AAUjC,SAASC,qBAAqBC,MAAyB;AAC5D,QAAMC,WAA2C,CAAC;AAClD,MAAIC,OAAO;AACX,QAAMC,QAAQ,wBAACC,SAAAA;AACb,UAAMC,KAAK,MAAMH,MAAAA;AACjB,UAAMI,QAAQ;MAAEC,MAAMH,KAAKG;MAAMC,OAAOJ,KAAKI,SAAS,CAAC;MAAGC,UAAU,CAAA;IAAe;AACnFR,aAASI,EAAAA,IAAMC;AACf,eAAWI,SAASN,KAAKK,YAAY,CAAA,EAAIH,OAAMG,SAASE,KAAKR,MAAMO,KAAAA,CAAAA;AACnE,WAAOL;EACT,GANc;AAOd,SAAO;IAAEL,MAAMG,MAAMH,IAAAA;IAAOC;EAAS;AACvC;AAXgBF;AAiBT,SAASa,qBAAqBC,UAAuB;AAC1D,SAAOC,OAAOC,YACZD,OAAOE,QAAQH,QAAAA,EAAUI,IAAI,CAAC,CAACC,MAAMC,SAAAA,MAAc;IACjDD;IACA,CAAC,EAAEE,SAASX,SAAQ,MAClB,sBAAA,cAACU,WAAeC,QAAQZ,SAAS,CAAC,GAAiCC,QAAAA;GAEtE,CAAA;AAEL;AATgBG;AAmBT,SAASS,eAAe,EAAErB,MAAMa,UAAUS,aAAY,GAAuB;AAClF,QAAMC,WAAOC,uBAAQ,MAAOxB,SAASyB,SAAY,OAAO1B,qBAAqBC,IAAAA,GAAQ;IAACA;GAAK;AAC3F,MAAIuB,SAAS,KAAM,QAAO;AAC1B,SACE,sBAAA,cAACG,6BAAAA;IAAeb;IAAqB,GAAIS,iBAAiBG,SAAY;MAAEH;IAAa,IAAI,CAAC;KACxF,sBAAA,cAACH,uBAAAA;IAASI;IAAqBV;;AAGrC;AARgBQ;AAWT,SAASM,eAAed,UAA2B;AAGxD,SAAO,gCAASe,oBAAoB,EAAE5B,KAAI,GAAkC;AAC1E,WAAO,sBAAA,cAACqB,gBAAAA;MAAeR;MAAqB,GAAIb,SAASyB,SAAY;QAAEzB;MAAK,IAAI,CAAC;;EACnF,GAFO;AAGT;AANgB2B;","names":["import_react","treeToJsonRenderSpec","root","elements","next","visit","node","id","entry","type","props","children","child","push","toJsonRenderRegistry","registry","Object","fromEntries","entries","map","name","Renderer","element","JsonRenderTree","initialState","spec","useMemo","undefined","JSONUIProvider","jsonRenderTree","JsonRenderTreeFrame"]}
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+ import { ComponentRegistry } from '@json-render/react';
3
+ import { c as GenerativeUIElement, i as GenuiRenderer, h as GenuiRegistry } from './types-DFD-soWU.cjs';
4
+
5
+ /** A json-render flat spec: `{ root, elements: { [id]: { type, props, children } } }`. */
6
+ interface JsonRenderFlatSpec {
7
+ root: string;
8
+ elements: Record<string, {
9
+ type: string;
10
+ props: Record<string, unknown>;
11
+ children: string[];
12
+ }>;
13
+ }
14
+ /** A tree frame's `root` as a json-render flat spec (ids `el-0`, `el-1`, … depth first). */
15
+ declare function treeToJsonRenderSpec(root: GenerativeUIElement): JsonRenderFlatSpec;
16
+ /**
17
+ * Adapt a genui registry (renderers taking props + `children`) to a json-render registry (renderers
18
+ * taking `{ element, children }`), so one set of components serves both.
19
+ */
20
+ declare function toJsonRenderRegistry(registry: GenuiRegistry): ComponentRegistry;
21
+ interface JsonRenderTreeProps {
22
+ root?: GenerativeUIElement;
23
+ registry: ComponentRegistry;
24
+ /** json-render state the spec's `$state` expressions read. */
25
+ initialState?: Record<string, unknown>;
26
+ }
27
+ /** Render a tree frame's `root` with json-render. */
28
+ declare function JsonRenderTree({ root, registry, initialState }: JsonRenderTreeProps): React.JSX.Element | null;
29
+ /** A registry entry for `genui:tree` that renders through json-render with `registry`. */
30
+ declare function jsonRenderTree(registry: ComponentRegistry): GenuiRenderer<{
31
+ root?: GenerativeUIElement;
32
+ }>;
33
+
34
+ export { type JsonRenderFlatSpec, JsonRenderTree, type JsonRenderTreeProps, jsonRenderTree, toJsonRenderRegistry, treeToJsonRenderSpec };
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+ import { ComponentRegistry } from '@json-render/react';
3
+ import { c as GenerativeUIElement, i as GenuiRenderer, h as GenuiRegistry } from './types-DFD-soWU.js';
4
+
5
+ /** A json-render flat spec: `{ root, elements: { [id]: { type, props, children } } }`. */
6
+ interface JsonRenderFlatSpec {
7
+ root: string;
8
+ elements: Record<string, {
9
+ type: string;
10
+ props: Record<string, unknown>;
11
+ children: string[];
12
+ }>;
13
+ }
14
+ /** A tree frame's `root` as a json-render flat spec (ids `el-0`, `el-1`, … depth first). */
15
+ declare function treeToJsonRenderSpec(root: GenerativeUIElement): JsonRenderFlatSpec;
16
+ /**
17
+ * Adapt a genui registry (renderers taking props + `children`) to a json-render registry (renderers
18
+ * taking `{ element, children }`), so one set of components serves both.
19
+ */
20
+ declare function toJsonRenderRegistry(registry: GenuiRegistry): ComponentRegistry;
21
+ interface JsonRenderTreeProps {
22
+ root?: GenerativeUIElement;
23
+ registry: ComponentRegistry;
24
+ /** json-render state the spec's `$state` expressions read. */
25
+ initialState?: Record<string, unknown>;
26
+ }
27
+ /** Render a tree frame's `root` with json-render. */
28
+ declare function JsonRenderTree({ root, registry, initialState }: JsonRenderTreeProps): React.JSX.Element | null;
29
+ /** A registry entry for `genui:tree` that renders through json-render with `registry`. */
30
+ declare function jsonRenderTree(registry: ComponentRegistry): GenuiRenderer<{
31
+ root?: GenerativeUIElement;
32
+ }>;
33
+
34
+ export { type JsonRenderFlatSpec, JsonRenderTree, type JsonRenderTreeProps, jsonRenderTree, toJsonRenderRegistry, treeToJsonRenderSpec };
@@ -0,0 +1,67 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/genui/json-render.tsx
5
+ import { JSONUIProvider, Renderer } from "@json-render/react";
6
+ import { useMemo } from "react";
7
+ function treeToJsonRenderSpec(root) {
8
+ const elements = {};
9
+ let next = 0;
10
+ const visit = /* @__PURE__ */ __name((node) => {
11
+ const id = `el-${next++}`;
12
+ const entry = {
13
+ type: node.type,
14
+ props: node.props ?? {},
15
+ children: []
16
+ };
17
+ elements[id] = entry;
18
+ for (const child of node.children ?? []) entry.children.push(visit(child));
19
+ return id;
20
+ }, "visit");
21
+ return {
22
+ root: visit(root),
23
+ elements
24
+ };
25
+ }
26
+ __name(treeToJsonRenderSpec, "treeToJsonRenderSpec");
27
+ function toJsonRenderRegistry(registry) {
28
+ return Object.fromEntries(Object.entries(registry).map(([name, Renderer2]) => [
29
+ name,
30
+ ({ element, children }) => /* @__PURE__ */ React.createElement(Renderer2, element.props ?? {}, children)
31
+ ]));
32
+ }
33
+ __name(toJsonRenderRegistry, "toJsonRenderRegistry");
34
+ function JsonRenderTree({ root, registry, initialState }) {
35
+ const spec = useMemo(() => root === void 0 ? null : treeToJsonRenderSpec(root), [
36
+ root
37
+ ]);
38
+ if (spec === null) return null;
39
+ return /* @__PURE__ */ React.createElement(JSONUIProvider, {
40
+ registry,
41
+ ...initialState !== void 0 ? {
42
+ initialState
43
+ } : {}
44
+ }, /* @__PURE__ */ React.createElement(Renderer, {
45
+ spec,
46
+ registry
47
+ }));
48
+ }
49
+ __name(JsonRenderTree, "JsonRenderTree");
50
+ function jsonRenderTree(registry) {
51
+ return /* @__PURE__ */ __name(function JsonRenderTreeFrame({ root }) {
52
+ return /* @__PURE__ */ React.createElement(JsonRenderTree, {
53
+ registry,
54
+ ...root !== void 0 ? {
55
+ root
56
+ } : {}
57
+ });
58
+ }, "JsonRenderTreeFrame");
59
+ }
60
+ __name(jsonRenderTree, "jsonRenderTree");
61
+ export {
62
+ JsonRenderTree,
63
+ jsonRenderTree,
64
+ toJsonRenderRegistry,
65
+ treeToJsonRenderSpec
66
+ };
67
+ //# sourceMappingURL=genui-json-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/genui/json-render.tsx"],"sourcesContent":["/**\n * Optional json-render adapter for tree frames (`@dudousxd/nestjs-agent-react/genui/json-render`).\n * Needs the optional peer `@json-render/react` (>= 0.21). Use it when your app already renders\n * json-render specs and wants tree frames to go through the same `Renderer` (its state, actions and\n * visibility machinery); otherwise `<GenerativeUI>` renders trees natively.\n *\n * ```tsx\n * const registry = { ...myComponents, [GENUI_TREE_COMPONENT]: jsonRenderTree(myJsonRenderRegistry) };\n * ```\n */\nimport { type ComponentRegistry, JSONUIProvider, Renderer } from '@json-render/react';\nimport { type ReactNode, useMemo } from 'react';\nimport type { GenerativeUIElement, GenuiRegistry, GenuiRenderer } from './types.js';\n\n/** A json-render flat spec: `{ root, elements: { [id]: { type, props, children } } }`. */\nexport interface JsonRenderFlatSpec {\n root: string;\n elements: Record<string, { type: string; props: Record<string, unknown>; children: string[] }>;\n}\n\n/** A tree frame's `root` as a json-render flat spec (ids `el-0`, `el-1`, … depth first). */\nexport function treeToJsonRenderSpec(root: GenerativeUIElement): JsonRenderFlatSpec {\n const elements: JsonRenderFlatSpec['elements'] = {};\n let next = 0;\n const visit = (node: GenerativeUIElement): string => {\n const id = `el-${next++}`;\n const entry = { type: node.type, props: node.props ?? {}, children: [] as string[] };\n elements[id] = entry;\n for (const child of node.children ?? []) entry.children.push(visit(child));\n return id;\n };\n return { root: visit(root), elements };\n}\n\n/**\n * Adapt a genui registry (renderers taking props + `children`) to a json-render registry (renderers\n * taking `{ element, children }`), so one set of components serves both.\n */\nexport function toJsonRenderRegistry(registry: GenuiRegistry): ComponentRegistry {\n return Object.fromEntries(\n Object.entries(registry).map(([name, Renderer]) => [\n name,\n ({ element, children }: { element: { props?: unknown }; children?: ReactNode }) => (\n <Renderer {...((element.props ?? {}) as Record<string, unknown>)}>{children}</Renderer>\n ),\n ]),\n );\n}\n\nexport interface JsonRenderTreeProps {\n root?: GenerativeUIElement;\n registry: ComponentRegistry;\n /** json-render state the spec's `$state` expressions read. */\n initialState?: Record<string, unknown>;\n}\n\n/** Render a tree frame's `root` with json-render. */\nexport function JsonRenderTree({ root, registry, initialState }: JsonRenderTreeProps) {\n const spec = useMemo(() => (root === undefined ? null : treeToJsonRenderSpec(root)), [root]);\n if (spec === null) return null;\n return (\n <JSONUIProvider registry={registry} {...(initialState !== undefined ? { initialState } : {})}>\n <Renderer spec={spec as never} registry={registry} />\n </JSONUIProvider>\n );\n}\n\n/** A registry entry for `genui:tree` that renders through json-render with `registry`. */\nexport function jsonRenderTree(registry: ComponentRegistry): GenuiRenderer<{\n root?: GenerativeUIElement;\n}> {\n return function JsonRenderTreeFrame({ root }: { root?: GenerativeUIElement }) {\n return <JsonRenderTree registry={registry} {...(root !== undefined ? { root } : {})} />;\n };\n}\n"],"mappings":";;;;AAUA,SAAiCA,gBAAgBC,gBAAgB;AACjE,SAAyBC,eAAe;AAUjC,SAASC,qBAAqBC,MAAyB;AAC5D,QAAMC,WAA2C,CAAC;AAClD,MAAIC,OAAO;AACX,QAAMC,QAAQ,wBAACC,SAAAA;AACb,UAAMC,KAAK,MAAMH,MAAAA;AACjB,UAAMI,QAAQ;MAAEC,MAAMH,KAAKG;MAAMC,OAAOJ,KAAKI,SAAS,CAAC;MAAGC,UAAU,CAAA;IAAe;AACnFR,aAASI,EAAAA,IAAMC;AACf,eAAWI,SAASN,KAAKK,YAAY,CAAA,EAAIH,OAAMG,SAASE,KAAKR,MAAMO,KAAAA,CAAAA;AACnE,WAAOL;EACT,GANc;AAOd,SAAO;IAAEL,MAAMG,MAAMH,IAAAA;IAAOC;EAAS;AACvC;AAXgBF;AAiBT,SAASa,qBAAqBC,UAAuB;AAC1D,SAAOC,OAAOC,YACZD,OAAOE,QAAQH,QAAAA,EAAUI,IAAI,CAAC,CAACC,MAAMC,SAAAA,MAAc;IACjDD;IACA,CAAC,EAAEE,SAASX,SAAQ,MAClB,sBAAA,cAACU,WAAeC,QAAQZ,SAAS,CAAC,GAAiCC,QAAAA;GAEtE,CAAA;AAEL;AATgBG;AAmBT,SAASS,eAAe,EAAErB,MAAMa,UAAUS,aAAY,GAAuB;AAClF,QAAMC,OAAOC,QAAQ,MAAOxB,SAASyB,SAAY,OAAO1B,qBAAqBC,IAAAA,GAAQ;IAACA;GAAK;AAC3F,MAAIuB,SAAS,KAAM,QAAO;AAC1B,SACE,sBAAA,cAACG,gBAAAA;IAAeb;IAAqB,GAAIS,iBAAiBG,SAAY;MAAEH;IAAa,IAAI,CAAC;KACxF,sBAAA,cAACH,UAAAA;IAASI;IAAqBV;;AAGrC;AARgBQ;AAWT,SAASM,eAAed,UAA2B;AAGxD,SAAO,gCAASe,oBAAoB,EAAE5B,KAAI,GAAkC;AAC1E,WAAO,sBAAA,cAACqB,gBAAAA;MAAeR;MAAqB,GAAIb,SAASyB,SAAY;QAAEzB;MAAK,IAAI,CAAC;;EACnF,GAFO;AAGT;AANgB2B;","names":["JSONUIProvider","Renderer","useMemo","treeToJsonRenderSpec","root","elements","next","visit","node","id","entry","type","props","children","child","push","toJsonRenderRegistry","registry","Object","fromEntries","entries","map","name","Renderer","element","JsonRenderTree","initialState","spec","useMemo","undefined","JSONUIProvider","jsonRenderTree","JsonRenderTreeFrame"]}