@ai-gui/react 0.3.0 → 0.4.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/dist/index.cjs +62 -12
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +64 -14
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
//#endregion
|
|
25
25
|
const react = __toESM(require("react"));
|
|
26
26
|
const __ai_gui_core = __toESM(require("@ai-gui/core"));
|
|
27
|
+
const react_dom = __toESM(require("react-dom"));
|
|
27
28
|
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
28
29
|
|
|
29
30
|
//#region src/apply-patches.ts
|
|
@@ -113,37 +114,85 @@ function useActionState(runtime, key) {
|
|
|
113
114
|
//#endregion
|
|
114
115
|
//#region src/render-output.tsx
|
|
115
116
|
/** Translate a framework-neutral RenderOutput into React nodes. */
|
|
116
|
-
function renderOutput(out, key, sanitize) {
|
|
117
|
+
function renderOutput(out, key, sanitize, context) {
|
|
117
118
|
switch (out.kind) {
|
|
118
119
|
case "html": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: sanitizeOutput(out.html, sanitize) } }, key);
|
|
119
120
|
case "element": return (0, react.createElement)(out.tag, {
|
|
120
121
|
key,
|
|
121
122
|
...out.props
|
|
122
|
-
}, (out.children ?? []).map((c, i) => renderOutput(c, String(i), sanitize)));
|
|
123
|
+
}, (out.children ?? []).map((c, i) => renderOutput(c, String(i), sanitize, context)));
|
|
123
124
|
case "card": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
124
125
|
"data-aigui-card-fallback": true,
|
|
125
126
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: JSON.stringify(out.data, null, 2) })
|
|
126
127
|
}, key);
|
|
127
|
-
case "mount": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MountHost, {
|
|
128
|
+
case "mount": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MountHost, {
|
|
129
|
+
mount: out.mount,
|
|
130
|
+
context
|
|
131
|
+
}, key);
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
/** Host a framework-neutral imperative mount into a managed DOM element. */
|
|
131
|
-
function MountHost({ mount }) {
|
|
135
|
+
function MountHost({ mount, context }) {
|
|
132
136
|
const ref = (0, react.useRef)(null);
|
|
137
|
+
const [slots, setSlots] = (0, react.useState)([]);
|
|
133
138
|
(0, react.useEffect)(() => {
|
|
134
139
|
if (!ref.current) return;
|
|
135
|
-
const
|
|
140
|
+
const mountContext = createMountContext(context, setSlots);
|
|
141
|
+
const cleanup = mount(ref.current, mountContext);
|
|
136
142
|
return () => {
|
|
137
143
|
if (typeof cleanup === "function") cleanup();
|
|
138
144
|
};
|
|
139
|
-
}, [
|
|
140
|
-
|
|
145
|
+
}, [
|
|
146
|
+
mount,
|
|
147
|
+
context?.registry,
|
|
148
|
+
context?.onCardAction
|
|
149
|
+
]);
|
|
150
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
141
151
|
ref,
|
|
142
152
|
"data-aigui-mount": true
|
|
143
|
-
})
|
|
153
|
+
}), slots.map((slot) => (0, react_dom.createPortal)((0, react.createElement)(slot.Comp, {
|
|
154
|
+
data: slot.data,
|
|
155
|
+
onAction: (action) => context?.onCardAction?.({
|
|
156
|
+
...action,
|
|
157
|
+
cardType: slot.type
|
|
158
|
+
})
|
|
159
|
+
}), slot.host, slot.id))] });
|
|
160
|
+
}
|
|
161
|
+
let nextCardSlotId = 0;
|
|
162
|
+
function createMountContext(context, setSlots) {
|
|
163
|
+
return { mountCard(host, request) {
|
|
164
|
+
return mountCard(host, request, context, setSlots);
|
|
165
|
+
} };
|
|
166
|
+
}
|
|
167
|
+
function mountCard(host, request, context, setSlots) {
|
|
168
|
+
const Comp = context?.registry?.getRender(request.type);
|
|
169
|
+
if (!Comp) return void 0;
|
|
170
|
+
const id = String(nextCardSlotId++);
|
|
171
|
+
let destroyed = false;
|
|
172
|
+
setSlots((slots) => [...slots, {
|
|
173
|
+
id,
|
|
174
|
+
host,
|
|
175
|
+
type: request.type,
|
|
176
|
+
data: request.data,
|
|
177
|
+
Comp
|
|
178
|
+
}]);
|
|
179
|
+
return {
|
|
180
|
+
update(data) {
|
|
181
|
+
if (destroyed) return;
|
|
182
|
+
setSlots((slots) => slots.map((slot) => slot.id === id ? {
|
|
183
|
+
...slot,
|
|
184
|
+
data
|
|
185
|
+
} : slot));
|
|
186
|
+
},
|
|
187
|
+
destroy() {
|
|
188
|
+
if (destroyed) return;
|
|
189
|
+
destroyed = true;
|
|
190
|
+
setSlots((slots) => slots.filter((slot) => slot.id !== id));
|
|
191
|
+
}
|
|
192
|
+
};
|
|
144
193
|
}
|
|
145
194
|
/** Await an async RenderOutput, rendering a placeholder until it resolves. */
|
|
146
|
-
function AsyncOutput({ promise, sanitize }) {
|
|
195
|
+
function AsyncOutput({ promise, sanitize, context }) {
|
|
147
196
|
const [resolved, setResolved] = (0, react.useState)(null);
|
|
148
197
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
149
198
|
(0, react.useEffect)(() => {
|
|
@@ -162,7 +211,7 @@ function AsyncOutput({ promise, sanitize }) {
|
|
|
162
211
|
if (failed) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "data-aigui-async-error": true });
|
|
163
212
|
if (resolved === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "data-aigui-async-pending": true });
|
|
164
213
|
try {
|
|
165
|
-
return renderOutput(resolved, void 0, sanitize);
|
|
214
|
+
return renderOutput(resolved, void 0, sanitize, context);
|
|
166
215
|
} catch {
|
|
167
216
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "data-aigui-async-error": true });
|
|
168
217
|
}
|
|
@@ -185,9 +234,10 @@ function renderNode(node, ctx) {
|
|
|
185
234
|
const out = r(node);
|
|
186
235
|
if (out && typeof out.then === "function") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AsyncOutput, {
|
|
187
236
|
promise: out,
|
|
188
|
-
sanitize: ctx.sanitize
|
|
237
|
+
sanitize: ctx.sanitize,
|
|
238
|
+
context: ctx
|
|
189
239
|
}, node.key);
|
|
190
|
-
return renderOutput(out, node.key, ctx.sanitize);
|
|
240
|
+
return renderOutput(out, node.key, ctx.sanitize, ctx);
|
|
191
241
|
} catch {
|
|
192
242
|
return renderFallback(node, ctx);
|
|
193
243
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -69,7 +69,7 @@ declare const AIRenderer: react1.ForwardRefExoticComponent<AIRendererProps & rea
|
|
|
69
69
|
//#endregion
|
|
70
70
|
//#region src/render-output.d.ts
|
|
71
71
|
/** Translate a framework-neutral RenderOutput into React nodes. */
|
|
72
|
-
declare function renderOutput(out: RenderOutput, key?: string, sanitize?: RendererOptions["sanitize"]): ReactNode;
|
|
72
|
+
declare function renderOutput(out: RenderOutput, key?: string, sanitize?: RendererOptions["sanitize"], context?: RenderContext): ReactNode;
|
|
73
73
|
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/apply-patches.d.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ declare const AIRenderer: react1.ForwardRefExoticComponent<AIRendererProps & rea
|
|
|
69
69
|
//#endregion
|
|
70
70
|
//#region src/render-output.d.ts
|
|
71
71
|
/** Translate a framework-neutral RenderOutput into React nodes. */
|
|
72
|
-
declare function renderOutput(out: RenderOutput, key?: string, sanitize?: RendererOptions["sanitize"]): ReactNode;
|
|
72
|
+
declare function renderOutput(out: RenderOutput, key?: string, sanitize?: RendererOptions["sanitize"], context?: RenderContext): ReactNode;
|
|
73
73
|
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/apply-patches.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createElement, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
1
|
+
import { Fragment, createElement, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
import { Renderer, collectNodeRenderers, getIdleActionState, sanitizeHtml } from "@ai-gui/core";
|
|
3
|
-
import {
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
|
|
5
6
|
//#region src/apply-patches.ts
|
|
6
7
|
function applyPatches(nodes, patches) {
|
|
@@ -89,37 +90,85 @@ function useActionState(runtime, key) {
|
|
|
89
90
|
//#endregion
|
|
90
91
|
//#region src/render-output.tsx
|
|
91
92
|
/** Translate a framework-neutral RenderOutput into React nodes. */
|
|
92
|
-
function renderOutput(out, key, sanitize) {
|
|
93
|
+
function renderOutput(out, key, sanitize, context) {
|
|
93
94
|
switch (out.kind) {
|
|
94
95
|
case "html": return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: sanitizeOutput(out.html, sanitize) } }, key);
|
|
95
96
|
case "element": return createElement(out.tag, {
|
|
96
97
|
key,
|
|
97
98
|
...out.props
|
|
98
|
-
}, (out.children ?? []).map((c, i) => renderOutput(c, String(i), sanitize)));
|
|
99
|
+
}, (out.children ?? []).map((c, i) => renderOutput(c, String(i), sanitize, context)));
|
|
99
100
|
case "card": return /* @__PURE__ */ jsx("pre", {
|
|
100
101
|
"data-aigui-card-fallback": true,
|
|
101
102
|
children: /* @__PURE__ */ jsx("code", { children: JSON.stringify(out.data, null, 2) })
|
|
102
103
|
}, key);
|
|
103
|
-
case "mount": return /* @__PURE__ */ jsx(MountHost, {
|
|
104
|
+
case "mount": return /* @__PURE__ */ jsx(MountHost, {
|
|
105
|
+
mount: out.mount,
|
|
106
|
+
context
|
|
107
|
+
}, key);
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
/** Host a framework-neutral imperative mount into a managed DOM element. */
|
|
107
|
-
function MountHost({ mount }) {
|
|
111
|
+
function MountHost({ mount, context }) {
|
|
108
112
|
const ref = useRef(null);
|
|
113
|
+
const [slots, setSlots] = useState([]);
|
|
109
114
|
useEffect(() => {
|
|
110
115
|
if (!ref.current) return;
|
|
111
|
-
const
|
|
116
|
+
const mountContext = createMountContext(context, setSlots);
|
|
117
|
+
const cleanup = mount(ref.current, mountContext);
|
|
112
118
|
return () => {
|
|
113
119
|
if (typeof cleanup === "function") cleanup();
|
|
114
120
|
};
|
|
115
|
-
}, [
|
|
116
|
-
|
|
121
|
+
}, [
|
|
122
|
+
mount,
|
|
123
|
+
context?.registry,
|
|
124
|
+
context?.onCardAction
|
|
125
|
+
]);
|
|
126
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
117
127
|
ref,
|
|
118
128
|
"data-aigui-mount": true
|
|
119
|
-
})
|
|
129
|
+
}), slots.map((slot) => createPortal(createElement(slot.Comp, {
|
|
130
|
+
data: slot.data,
|
|
131
|
+
onAction: (action) => context?.onCardAction?.({
|
|
132
|
+
...action,
|
|
133
|
+
cardType: slot.type
|
|
134
|
+
})
|
|
135
|
+
}), slot.host, slot.id))] });
|
|
136
|
+
}
|
|
137
|
+
let nextCardSlotId = 0;
|
|
138
|
+
function createMountContext(context, setSlots) {
|
|
139
|
+
return { mountCard(host, request) {
|
|
140
|
+
return mountCard(host, request, context, setSlots);
|
|
141
|
+
} };
|
|
142
|
+
}
|
|
143
|
+
function mountCard(host, request, context, setSlots) {
|
|
144
|
+
const Comp = context?.registry?.getRender(request.type);
|
|
145
|
+
if (!Comp) return void 0;
|
|
146
|
+
const id = String(nextCardSlotId++);
|
|
147
|
+
let destroyed = false;
|
|
148
|
+
setSlots((slots) => [...slots, {
|
|
149
|
+
id,
|
|
150
|
+
host,
|
|
151
|
+
type: request.type,
|
|
152
|
+
data: request.data,
|
|
153
|
+
Comp
|
|
154
|
+
}]);
|
|
155
|
+
return {
|
|
156
|
+
update(data) {
|
|
157
|
+
if (destroyed) return;
|
|
158
|
+
setSlots((slots) => slots.map((slot) => slot.id === id ? {
|
|
159
|
+
...slot,
|
|
160
|
+
data
|
|
161
|
+
} : slot));
|
|
162
|
+
},
|
|
163
|
+
destroy() {
|
|
164
|
+
if (destroyed) return;
|
|
165
|
+
destroyed = true;
|
|
166
|
+
setSlots((slots) => slots.filter((slot) => slot.id !== id));
|
|
167
|
+
}
|
|
168
|
+
};
|
|
120
169
|
}
|
|
121
170
|
/** Await an async RenderOutput, rendering a placeholder until it resolves. */
|
|
122
|
-
function AsyncOutput({ promise, sanitize }) {
|
|
171
|
+
function AsyncOutput({ promise, sanitize, context }) {
|
|
123
172
|
const [resolved, setResolved] = useState(null);
|
|
124
173
|
const [failed, setFailed] = useState(false);
|
|
125
174
|
useEffect(() => {
|
|
@@ -138,7 +187,7 @@ function AsyncOutput({ promise, sanitize }) {
|
|
|
138
187
|
if (failed) return /* @__PURE__ */ jsx("span", { "data-aigui-async-error": true });
|
|
139
188
|
if (resolved === null) return /* @__PURE__ */ jsx("span", { "data-aigui-async-pending": true });
|
|
140
189
|
try {
|
|
141
|
-
return renderOutput(resolved, void 0, sanitize);
|
|
190
|
+
return renderOutput(resolved, void 0, sanitize, context);
|
|
142
191
|
} catch {
|
|
143
192
|
return /* @__PURE__ */ jsx("span", { "data-aigui-async-error": true });
|
|
144
193
|
}
|
|
@@ -161,9 +210,10 @@ function renderNode(node, ctx) {
|
|
|
161
210
|
const out = r(node);
|
|
162
211
|
if (out && typeof out.then === "function") return /* @__PURE__ */ jsx(AsyncOutput, {
|
|
163
212
|
promise: out,
|
|
164
|
-
sanitize: ctx.sanitize
|
|
213
|
+
sanitize: ctx.sanitize,
|
|
214
|
+
context: ctx
|
|
165
215
|
}, node.key);
|
|
166
|
-
return renderOutput(out, node.key, ctx.sanitize);
|
|
216
|
+
return renderOutput(out, node.key, ctx.sanitize, ctx);
|
|
167
217
|
} catch {
|
|
168
218
|
return renderFallback(node, ctx);
|
|
169
219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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,10 +50,11 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@ai-gui/core": "0.
|
|
53
|
+
"@ai-gui/core": "0.4.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"react": ">=18"
|
|
56
|
+
"react": ">=18",
|
|
57
|
+
"react-dom": ">=18"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"react": "^18.3.1",
|