@ai-gui/react 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -126,7 +126,7 @@ function useActionState(runtime, key) {
126
126
  /** Translate a framework-neutral RenderOutput into React nodes. */
127
127
  function renderOutput(out, key, sanitize, context) {
128
128
  switch (out.kind) {
129
- case "html": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: sanitizeOutput(out.html, sanitize) } }, key);
129
+ case "html": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: (0, __ai_gui_core.sanitizeRenderedHtml)(out.html, sanitize, out.trusted) } }, key);
130
130
  case "element": return (0, react.createElement)(out.tag, {
131
131
  key,
132
132
  ...out.props
@@ -226,10 +226,6 @@ function AsyncOutput({ promise, sanitize, context }) {
226
226
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "data-aigui-async-error": true });
227
227
  }
228
228
  }
229
- function sanitizeOutput(html, sanitize) {
230
- if (sanitize === false) return html;
231
- return (0, __ai_gui_core.sanitizeHtml)(html, typeof sanitize === "object" ? sanitize : void 0);
232
- }
233
229
 
234
230
  //#endregion
235
231
  //#region src/render-node.tsx
@@ -396,6 +392,7 @@ const AIRenderer = (0, react.forwardRef)(function AIRenderer$1(props, ref) {
396
392
  const { renderer, nodes, push, feed, reset: resetRenderer } = useAIRenderer(opts);
397
393
  const actionScope = (0, react.useRef)(createActionScope());
398
394
  const rendered = (0, react.useRef)("");
395
+ const root = (0, react.useRef)(null);
399
396
  (0, react.useEffect)(() => () => {
400
397
  actionScope.current.controller.abort();
401
398
  actionScope.current = createActionScope();
@@ -447,16 +444,19 @@ const AIRenderer = (0, react.forwardRef)(function AIRenderer$1(props, ref) {
447
444
  }
448
445
  onCardAction?.(action);
449
446
  }, [actionRuntime, onCardAction]);
447
+ const exportImages = (0, react.useCallback)((options) => root.current ? (0, __ai_gui_core.exportRenderedImages)(root.current, options) : Promise.resolve([]), []);
450
448
  (0, react.useImperativeHandle)(ref, () => ({
451
449
  debugSource: "renderer",
452
450
  subscribeDebug: (listener) => renderer.subscribeDebug(listener),
453
451
  push,
454
452
  feed,
453
+ exportImages,
455
454
  reset
456
455
  }), [
457
456
  renderer,
458
457
  push,
459
458
  feed,
459
+ exportImages,
460
460
  reset
461
461
  ]);
462
462
  const nodeRenderers = (0, react.useMemo)(() => (0, __ai_gui_core.collectNodeRenderers)(plugins, { debugTarget: renderer }), [plugins, renderer]);
@@ -473,6 +473,7 @@ const AIRenderer = (0, react.forwardRef)(function AIRenderer$1(props, ref) {
473
473
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
474
474
  className,
475
475
  "data-aigui-renderer": true,
476
+ ref: root,
476
477
  children: nodes.map((n) => renderNode(n, ctx))
477
478
  });
478
479
  });
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AIGuiPlugin, ASTNode, ActionRuntime, ActionState, CardAction, CardRegistry, CardStore, DebugEventListener, FeedOptions, FeedSource, NodeRenderer, Patch, RenderOutput, Renderer, RendererOptions } from "@ai-gui/core";
1
+ import { AIGuiPlugin, ASTNode, ActionRuntime, ActionState, CardAction, CardRegistry, CardStore, DebugEventListener, ExportImageOptions, ExportedImage, FeedOptions, FeedSource, NodeRenderer, Patch, RenderOutput, Renderer, RendererOptions } from "@ai-gui/core";
2
2
  import * as react0 from "react";
3
3
  import * as react1 from "react";
4
4
  import { ReactNode } from "react";
@@ -53,6 +53,13 @@ interface AIRendererHandle {
53
53
  subscribeDebug: (listener: DebugEventListener) => () => void;
54
54
  push: (chunk: string) => void;
55
55
  feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
56
+ /**
57
+ * Export every drawing currently rendered, as PNG data URLs.
58
+ *
59
+ * The element the drawings live in belongs to the renderer, so a host offering "save this chart"
60
+ * would otherwise have to wrap it in one of its own just to find them.
61
+ */
62
+ exportImages: (options?: ExportImageOptions) => Promise<ExportedImage[]>;
56
63
  reset: () => void;
57
64
  }
58
65
  interface AIRendererProps {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react0 from "react";
2
2
  import * as react1 from "react";
3
3
  import { ReactNode } from "react";
4
- import { AIGuiPlugin, ASTNode, ActionRuntime, ActionState, CardAction, CardRegistry, CardStore, DebugEventListener, FeedOptions, FeedSource, NodeRenderer, Patch, RenderOutput, Renderer, RendererOptions } from "@ai-gui/core";
4
+ import { AIGuiPlugin, ASTNode, ActionRuntime, ActionState, CardAction, CardRegistry, CardStore, DebugEventListener, ExportImageOptions, ExportedImage, FeedOptions, FeedSource, NodeRenderer, Patch, RenderOutput, Renderer, RendererOptions } from "@ai-gui/core";
5
5
 
6
6
  //#region src/use-ai-renderer.d.ts
7
7
  interface UseAIRendererResult {
@@ -53,6 +53,13 @@ interface AIRendererHandle {
53
53
  subscribeDebug: (listener: DebugEventListener) => () => void;
54
54
  push: (chunk: string) => void;
55
55
  feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
56
+ /**
57
+ * Export every drawing currently rendered, as PNG data URLs.
58
+ *
59
+ * The element the drawings live in belongs to the renderer, so a host offering "save this chart"
60
+ * would otherwise have to wrap it in one of its own just to find them.
61
+ */
62
+ exportImages: (options?: ExportImageOptions) => Promise<ExportedImage[]>;
56
63
  reset: () => void;
57
64
  }
58
65
  interface AIRendererProps {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Fragment, createElement, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
- import { Renderer, collectNodeRenderers, getIdleActionState, sanitizeHtml } from "@ai-gui/core";
2
+ import { Renderer, collectNodeRenderers, exportRenderedImages, getIdleActionState, sanitizeHtml, sanitizeRenderedHtml } from "@ai-gui/core";
3
3
  import { createPortal } from "react-dom";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
 
@@ -102,7 +102,7 @@ function useActionState(runtime, key) {
102
102
  /** Translate a framework-neutral RenderOutput into React nodes. */
103
103
  function renderOutput(out, key, sanitize, context) {
104
104
  switch (out.kind) {
105
- case "html": return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: sanitizeOutput(out.html, sanitize) } }, key);
105
+ case "html": return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: sanitizeRenderedHtml(out.html, sanitize, out.trusted) } }, key);
106
106
  case "element": return createElement(out.tag, {
107
107
  key,
108
108
  ...out.props
@@ -202,10 +202,6 @@ function AsyncOutput({ promise, sanitize, context }) {
202
202
  return /* @__PURE__ */ jsx("span", { "data-aigui-async-error": true });
203
203
  }
204
204
  }
205
- function sanitizeOutput(html, sanitize) {
206
- if (sanitize === false) return html;
207
- return sanitizeHtml(html, typeof sanitize === "object" ? sanitize : void 0);
208
- }
209
205
 
210
206
  //#endregion
211
207
  //#region src/render-node.tsx
@@ -372,6 +368,7 @@ const AIRenderer = forwardRef(function AIRenderer$1(props, ref) {
372
368
  const { renderer, nodes, push, feed, reset: resetRenderer } = useAIRenderer(opts);
373
369
  const actionScope = useRef(createActionScope());
374
370
  const rendered = useRef("");
371
+ const root = useRef(null);
375
372
  useEffect(() => () => {
376
373
  actionScope.current.controller.abort();
377
374
  actionScope.current = createActionScope();
@@ -423,16 +420,19 @@ const AIRenderer = forwardRef(function AIRenderer$1(props, ref) {
423
420
  }
424
421
  onCardAction?.(action);
425
422
  }, [actionRuntime, onCardAction]);
423
+ const exportImages = useCallback((options) => root.current ? exportRenderedImages(root.current, options) : Promise.resolve([]), []);
426
424
  useImperativeHandle(ref, () => ({
427
425
  debugSource: "renderer",
428
426
  subscribeDebug: (listener) => renderer.subscribeDebug(listener),
429
427
  push,
430
428
  feed,
429
+ exportImages,
431
430
  reset
432
431
  }), [
433
432
  renderer,
434
433
  push,
435
434
  feed,
435
+ exportImages,
436
436
  reset
437
437
  ]);
438
438
  const nodeRenderers = useMemo(() => collectNodeRenderers(plugins, { debugTarget: renderer }), [plugins, renderer]);
@@ -449,6 +449,7 @@ const AIRenderer = forwardRef(function AIRenderer$1(props, ref) {
449
449
  return /* @__PURE__ */ jsx("div", {
450
450
  className,
451
451
  "data-aigui-renderer": true,
452
+ ref: root,
452
453
  children: nodes.map((n) => renderNode(n, ctx))
453
454
  });
454
455
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-gui/react",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "React adapter for AIGUI — stream LLM output into live React components with cards, plugins, and interactive widgets.",
5
5
  "keywords": [
6
6
  "llm",
@@ -50,7 +50,7 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@ai-gui/core": "0.5.0"
53
+ "@ai-gui/core": "0.6.1"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": ">=18",