@ai-gui/react 0.3.0 → 0.4.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 +91 -20
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +93 -22
- 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
|
}
|
|
@@ -181,16 +230,11 @@ function renderNode(node, ctx) {
|
|
|
181
230
|
"data-aigui-block-loading": "",
|
|
182
231
|
"data-block-type": node.type
|
|
183
232
|
}, node.key);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}, node.key);
|
|
190
|
-
return renderOutput(out, node.key, ctx.sanitize);
|
|
191
|
-
} catch {
|
|
192
|
-
return renderFallback(node, ctx);
|
|
193
|
-
}
|
|
233
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginOutputHost, {
|
|
234
|
+
node,
|
|
235
|
+
renderer: r,
|
|
236
|
+
context: ctx
|
|
237
|
+
}, node.key);
|
|
194
238
|
}
|
|
195
239
|
switch (node.type) {
|
|
196
240
|
case "heading": return (0, react.createElement)(node.tag ?? "h1", {
|
|
@@ -208,6 +252,33 @@ function renderNode(node, ctx) {
|
|
|
208
252
|
default: return renderFallback(node, ctx);
|
|
209
253
|
}
|
|
210
254
|
}
|
|
255
|
+
function PluginOutputHost({ node, renderer, context }) {
|
|
256
|
+
const cache = (0, react.useRef)();
|
|
257
|
+
const signature = nodeSignature(node);
|
|
258
|
+
try {
|
|
259
|
+
if (!cache.current || cache.current.renderer !== renderer || cache.current.signature !== signature) cache.current = {
|
|
260
|
+
renderer,
|
|
261
|
+
signature,
|
|
262
|
+
output: renderer(node)
|
|
263
|
+
};
|
|
264
|
+
const output = cache.current.output;
|
|
265
|
+
if (output && typeof output.then === "function") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AsyncOutput, {
|
|
266
|
+
promise: output,
|
|
267
|
+
sanitize: context.sanitize,
|
|
268
|
+
context
|
|
269
|
+
});
|
|
270
|
+
return renderOutput(output, void 0, context.sanitize, context);
|
|
271
|
+
} catch {
|
|
272
|
+
return renderFallback(node, context);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function nodeSignature(node) {
|
|
276
|
+
try {
|
|
277
|
+
return JSON.stringify(node);
|
|
278
|
+
} catch {
|
|
279
|
+
return node;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
211
282
|
function renderFallback(node, ctx) {
|
|
212
283
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: renderHtml(node.html ?? node.content ?? "", ctx) } }, node.key);
|
|
213
284
|
}
|
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
|
}
|
|
@@ -157,16 +206,11 @@ function renderNode(node, ctx) {
|
|
|
157
206
|
"data-aigui-block-loading": "",
|
|
158
207
|
"data-block-type": node.type
|
|
159
208
|
}, node.key);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}, node.key);
|
|
166
|
-
return renderOutput(out, node.key, ctx.sanitize);
|
|
167
|
-
} catch {
|
|
168
|
-
return renderFallback(node, ctx);
|
|
169
|
-
}
|
|
209
|
+
return /* @__PURE__ */ jsx(PluginOutputHost, {
|
|
210
|
+
node,
|
|
211
|
+
renderer: r,
|
|
212
|
+
context: ctx
|
|
213
|
+
}, node.key);
|
|
170
214
|
}
|
|
171
215
|
switch (node.type) {
|
|
172
216
|
case "heading": return createElement(node.tag ?? "h1", {
|
|
@@ -184,6 +228,33 @@ function renderNode(node, ctx) {
|
|
|
184
228
|
default: return renderFallback(node, ctx);
|
|
185
229
|
}
|
|
186
230
|
}
|
|
231
|
+
function PluginOutputHost({ node, renderer, context }) {
|
|
232
|
+
const cache = useRef();
|
|
233
|
+
const signature = nodeSignature(node);
|
|
234
|
+
try {
|
|
235
|
+
if (!cache.current || cache.current.renderer !== renderer || cache.current.signature !== signature) cache.current = {
|
|
236
|
+
renderer,
|
|
237
|
+
signature,
|
|
238
|
+
output: renderer(node)
|
|
239
|
+
};
|
|
240
|
+
const output = cache.current.output;
|
|
241
|
+
if (output && typeof output.then === "function") return /* @__PURE__ */ jsx(AsyncOutput, {
|
|
242
|
+
promise: output,
|
|
243
|
+
sanitize: context.sanitize,
|
|
244
|
+
context
|
|
245
|
+
});
|
|
246
|
+
return renderOutput(output, void 0, context.sanitize, context);
|
|
247
|
+
} catch {
|
|
248
|
+
return renderFallback(node, context);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function nodeSignature(node) {
|
|
252
|
+
try {
|
|
253
|
+
return JSON.stringify(node);
|
|
254
|
+
} catch {
|
|
255
|
+
return node;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
187
258
|
function renderFallback(node, ctx) {
|
|
188
259
|
return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: renderHtml(node.html ?? node.content ?? "", ctx) } }, node.key);
|
|
189
260
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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,10 +50,11 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@ai-gui/core": "0.
|
|
53
|
+
"@ai-gui/core": "0.4.1"
|
|
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",
|