@ai-gui/vanilla 0.2.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/README.md +22 -6
- package/dist/index.cjs +245 -42
- package/dist/index.d.cts +23 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +245 -42
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,24 +11,37 @@ pnpm add @ai-gui/core @ai-gui/vanilla
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { CardRegistry } from "@ai-gui/core"
|
|
14
|
+
import { ActionRegistry, CardRegistry, CardStore, createActionRuntime } from "@ai-gui/core"
|
|
15
15
|
import { createRenderer } from "@ai-gui/vanilla"
|
|
16
16
|
|
|
17
17
|
const registry = new CardRegistry()
|
|
18
18
|
registry.register({
|
|
19
19
|
type: "weather",
|
|
20
20
|
description: "Weather summary",
|
|
21
|
-
//
|
|
22
|
-
render: (data: any, { onAction }: any) => {
|
|
21
|
+
// HTMLElement remains supported. Return a VanillaCardInstance for stateful cards.
|
|
22
|
+
render: (data: any, { state, onAction }: any) => {
|
|
23
23
|
const el = document.createElement("div")
|
|
24
24
|
el.textContent = `${data.city} — ${data.tempC}°C`
|
|
25
|
-
return
|
|
25
|
+
return {
|
|
26
|
+
element: el,
|
|
27
|
+
update(next: any, context: any) {
|
|
28
|
+
el.textContent = `${next.city} — ${next.tempC}°C (${context.state.status})`
|
|
29
|
+
},
|
|
30
|
+
destroy() {},
|
|
31
|
+
}
|
|
26
32
|
},
|
|
27
33
|
})
|
|
28
34
|
|
|
35
|
+
const actions = new ActionRegistry()
|
|
36
|
+
actions.register({ type: "refresh", run: async (params, { signal }) => fetch("/api/weather", { signal }) })
|
|
37
|
+
const cardStore = new CardStore({ registry })
|
|
38
|
+
const actionRuntime = createActionRuntime({ registry: actions, cardStore })
|
|
39
|
+
|
|
29
40
|
const r = createRenderer(document.getElementById("out")!, {
|
|
30
41
|
registry,
|
|
31
|
-
|
|
42
|
+
cardStore,
|
|
43
|
+
actionRuntime,
|
|
44
|
+
onCardAction: (action) => console.log("observed", action),
|
|
32
45
|
})
|
|
33
46
|
|
|
34
47
|
await r.feed(response.body!) // r.push(chunk) / r.reset() / r.destroy()
|
|
@@ -36,6 +49,9 @@ await r.feed(response.body!) // r.push(chunk) / r.reset() / r.destroy()
|
|
|
36
49
|
|
|
37
50
|
## Exports
|
|
38
51
|
|
|
39
|
-
- `createRenderer(el, { registry?, plugins?, sanitize?, onCardAction? })` → `{ push, feed, reset, destroy }`.
|
|
52
|
+
- `createRenderer(el, { registry?, cardStore?, plugins?, sanitize?, actionRuntime?, onCardAction? })` → `{ push, feed, reset, destroy }`.
|
|
53
|
+
- `VanillaCardInstance` is `{ element, update(data, { state, onAction }), destroy? }`. Its host and `element` stay mounted while AST data, store data, or action state changes.
|
|
54
|
+
- Legacy factories returning an `HTMLElement` remain supported. For cards with an `id` and a `cardStore`, the adapter may rebuild that child element after store updates so it displays current data.
|
|
55
|
+
- Cards with a valid top-level `id` register their initial data in the supplied external `CardStore`. The adapter subscribes but never clears that store; a shared store updates every renderer displaying the same card id.
|
|
40
56
|
|
|
41
57
|
`destroy()` tears down the host and cleans up any mounted interactive widgets. See the [root README](../../README.md) for cards, plugins, and `buildSystemPrompt`.
|
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ const __ai_gui_core = __toESM(require("@ai-gui/core"));
|
|
|
26
26
|
|
|
27
27
|
//#region src/render-output.ts
|
|
28
28
|
/** Translate a framework-neutral RenderOutput (from a plugin node renderer) into a DOM element. */
|
|
29
|
-
function renderOutputToElement(out, sanitize) {
|
|
29
|
+
function renderOutputToElement(out, sanitize, mountContext = {}) {
|
|
30
30
|
switch (out.kind) {
|
|
31
31
|
case "html": {
|
|
32
32
|
const el = document.createElement("div");
|
|
@@ -37,7 +37,7 @@ function renderOutputToElement(out, sanitize) {
|
|
|
37
37
|
const el = document.createElement(out.tag);
|
|
38
38
|
for (const [key, value] of Object.entries(out.props ?? {})) if (key === "class" || key === "className") el.className = String(value);
|
|
39
39
|
else el.setAttribute(key, String(value));
|
|
40
|
-
for (const child of out.children ?? []) el.appendChild(renderOutputToElement(child, sanitize));
|
|
40
|
+
for (const child of out.children ?? []) el.appendChild(renderOutputToElement(child, sanitize, mountContext));
|
|
41
41
|
return el;
|
|
42
42
|
}
|
|
43
43
|
case "card": {
|
|
@@ -53,7 +53,7 @@ function renderOutputToElement(out, sanitize) {
|
|
|
53
53
|
el.setAttribute("data-aigui-mount", "");
|
|
54
54
|
queueMicrotask(() => {
|
|
55
55
|
if (el.__aiguiDisposed) return;
|
|
56
|
-
const c = out.mount(el);
|
|
56
|
+
const c = out.mount(el, mountContext);
|
|
57
57
|
if (typeof c === "function") if (el.__aiguiDisposed) c();
|
|
58
58
|
else el.__aiguiCleanup = c;
|
|
59
59
|
});
|
|
@@ -70,25 +70,33 @@ function sanitizeOutput(html, sanitize) {
|
|
|
70
70
|
//#region src/render-node-dom.ts
|
|
71
71
|
function renderNodeToElement(node, ctx) {
|
|
72
72
|
const r = (ctx.nodeRenderers ?? (0, __ai_gui_core.collectNodeRenderers)(ctx.plugins))[node.type];
|
|
73
|
-
if (r)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
host.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
if (r) {
|
|
74
|
+
if (node.complete === false) {
|
|
75
|
+
const loading = document.createElement("div");
|
|
76
|
+
loading.setAttribute("data-aigui-block-loading", "");
|
|
77
|
+
loading.setAttribute("data-block-type", node.type);
|
|
78
|
+
return loading;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const out = r(node);
|
|
82
|
+
if (typeof out?.then === "function") {
|
|
83
|
+
const host = document.createElement("div");
|
|
84
|
+
host.setAttribute("data-aigui-async-pending", "");
|
|
85
|
+
out.then((res) => {
|
|
86
|
+
if (host.__aiguiDisposed) return;
|
|
87
|
+
host.removeAttribute("data-aigui-async-pending");
|
|
88
|
+
host.replaceChildren(renderOutputToElement(res, ctx.sanitize, createRenderMountContext(ctx)));
|
|
89
|
+
}, () => {
|
|
90
|
+
if (host.__aiguiDisposed) return;
|
|
91
|
+
host.removeAttribute("data-aigui-async-pending");
|
|
92
|
+
host.setAttribute("data-aigui-async-error", "");
|
|
93
|
+
});
|
|
94
|
+
return host;
|
|
95
|
+
}
|
|
96
|
+
return renderOutputToElement(out, ctx.sanitize, createRenderMountContext(ctx));
|
|
97
|
+
} catch {
|
|
98
|
+
return renderFallback(node, ctx);
|
|
88
99
|
}
|
|
89
|
-
return renderOutputToElement(out, ctx.sanitize);
|
|
90
|
-
} catch {
|
|
91
|
-
return renderFallback(node, ctx);
|
|
92
100
|
}
|
|
93
101
|
switch (node.type) {
|
|
94
102
|
case "heading": {
|
|
@@ -146,22 +154,191 @@ function renderCardElement(node, ctx) {
|
|
|
146
154
|
return pre;
|
|
147
155
|
}
|
|
148
156
|
const factory = ctx.registry?.getRender(card.type);
|
|
149
|
-
if (!factory)
|
|
150
|
-
const pre = document.createElement("pre");
|
|
151
|
-
pre.setAttribute("data-aigui-card-fallback", "");
|
|
152
|
-
const c = document.createElement("code");
|
|
153
|
-
c.textContent = JSON.stringify(card.data, null, 2);
|
|
154
|
-
pre.appendChild(c);
|
|
155
|
-
return pre;
|
|
156
|
-
}
|
|
157
|
+
if (!factory) return createCardFallback(card, ctx);
|
|
157
158
|
const host = document.createElement("div");
|
|
158
159
|
host.setAttribute("data-aigui-card", card.type);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}) }));
|
|
160
|
+
const controller = createCardController(host, card, factory, ctx);
|
|
161
|
+
host.__aiguiCardController = controller;
|
|
162
|
+
host.__aiguiCleanup = () => controller.destroy();
|
|
163
163
|
return host;
|
|
164
164
|
}
|
|
165
|
+
function createRenderMountContext(ctx) {
|
|
166
|
+
return { mountCard(host, request) {
|
|
167
|
+
return mountCardSlot(host, request, ctx);
|
|
168
|
+
} };
|
|
169
|
+
}
|
|
170
|
+
function mountCardSlot(host, request, ctx) {
|
|
171
|
+
const factory = ctx.registry?.getRender(request.type);
|
|
172
|
+
if (!factory) return void 0;
|
|
173
|
+
return createVanillaCardSlot(host, request.data, factory, () => ({
|
|
174
|
+
state: { status: "idle" },
|
|
175
|
+
onAction: (action) => ctx.onCardAction?.({
|
|
176
|
+
...action,
|
|
177
|
+
cardType: request.type
|
|
178
|
+
})
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
function createVanillaCardSlot(host, initialData, factory, context) {
|
|
182
|
+
let destroyed = false;
|
|
183
|
+
let instance;
|
|
184
|
+
let element;
|
|
185
|
+
const mount = (data) => {
|
|
186
|
+
const rendered = factory(data, context());
|
|
187
|
+
if (isVanillaCardInstance(rendered)) {
|
|
188
|
+
instance = rendered;
|
|
189
|
+
element = rendered.element;
|
|
190
|
+
} else {
|
|
191
|
+
instance = void 0;
|
|
192
|
+
element = rendered;
|
|
193
|
+
}
|
|
194
|
+
host.replaceChildren(element);
|
|
195
|
+
};
|
|
196
|
+
mount(initialData);
|
|
197
|
+
return {
|
|
198
|
+
element: () => element,
|
|
199
|
+
update(data) {
|
|
200
|
+
if (destroyed) return;
|
|
201
|
+
if (instance) instance.update(data, context());
|
|
202
|
+
else mount(data);
|
|
203
|
+
},
|
|
204
|
+
destroy() {
|
|
205
|
+
if (destroyed) return;
|
|
206
|
+
destroyed = true;
|
|
207
|
+
instance?.destroy?.();
|
|
208
|
+
instance = void 0;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function createCardFallback(card, ctx) {
|
|
213
|
+
const pre = document.createElement("pre");
|
|
214
|
+
pre.setAttribute("data-aigui-card-fallback", "");
|
|
215
|
+
const code = document.createElement("code");
|
|
216
|
+
pre.appendChild(code);
|
|
217
|
+
const render = (data) => {
|
|
218
|
+
pre.removeAttribute("data-aigui-card-missing");
|
|
219
|
+
code.textContent = JSON.stringify(data, null, 2);
|
|
220
|
+
};
|
|
221
|
+
const missing = () => {
|
|
222
|
+
pre.setAttribute("data-aigui-card-missing", "");
|
|
223
|
+
code.textContent = "Card data is unavailable";
|
|
224
|
+
};
|
|
225
|
+
let unsubscribe = () => {};
|
|
226
|
+
if (card.id !== void 0 && ctx.cardStore) {
|
|
227
|
+
try {
|
|
228
|
+
const record = ctx.cardStore.register({
|
|
229
|
+
id: card.id,
|
|
230
|
+
type: card.type,
|
|
231
|
+
data: card.data
|
|
232
|
+
});
|
|
233
|
+
render(record.data);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
pre.setAttribute("data-aigui-card-store-error", "");
|
|
236
|
+
code.textContent = error instanceof Error ? error.message : "Card registration failed";
|
|
237
|
+
}
|
|
238
|
+
unsubscribe = ctx.cardStore.subscribe(card.id, (next) => {
|
|
239
|
+
if (!next || next.type !== card.type) missing();
|
|
240
|
+
else {
|
|
241
|
+
pre.removeAttribute("data-aigui-card-store-error");
|
|
242
|
+
render(next.data);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
pre.__aiguiCleanup = unsubscribe;
|
|
246
|
+
} else render(card.data);
|
|
247
|
+
return pre;
|
|
248
|
+
}
|
|
249
|
+
function updateCardElement(el, node) {
|
|
250
|
+
return el.__aiguiCardController?.updateNode(node) ?? false;
|
|
251
|
+
}
|
|
252
|
+
function createCardController(host, initialCard, factory, ctx) {
|
|
253
|
+
let card = initialCard;
|
|
254
|
+
let destroyed = false;
|
|
255
|
+
let unsubscribe = () => {};
|
|
256
|
+
let slot;
|
|
257
|
+
let missing = false;
|
|
258
|
+
const id = card.id;
|
|
259
|
+
const onAction = (action) => ctx.onCardAction?.({
|
|
260
|
+
...action,
|
|
261
|
+
cardType: card.type,
|
|
262
|
+
...id === void 0 ? {} : { cardId: id }
|
|
263
|
+
});
|
|
264
|
+
let data = card.data;
|
|
265
|
+
let state = { status: "idle" };
|
|
266
|
+
let registrationError;
|
|
267
|
+
if (id !== void 0 && ctx.cardStore) try {
|
|
268
|
+
const record = ctx.cardStore.register({
|
|
269
|
+
id,
|
|
270
|
+
type: card.type,
|
|
271
|
+
data
|
|
272
|
+
});
|
|
273
|
+
data = record.data;
|
|
274
|
+
state = record.action;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
registrationError = error;
|
|
277
|
+
}
|
|
278
|
+
const mount = (nextData) => {
|
|
279
|
+
slot = createVanillaCardSlot(host, nextData, factory, () => ({
|
|
280
|
+
state,
|
|
281
|
+
onAction
|
|
282
|
+
}));
|
|
283
|
+
};
|
|
284
|
+
const update = (nextData, nextState) => {
|
|
285
|
+
if (destroyed) return;
|
|
286
|
+
data = nextData;
|
|
287
|
+
state = nextState;
|
|
288
|
+
host.removeAttribute("data-aigui-card-store-error");
|
|
289
|
+
if (missing) {
|
|
290
|
+
missing = false;
|
|
291
|
+
host.removeAttribute("data-aigui-card-missing");
|
|
292
|
+
const element = slot?.element();
|
|
293
|
+
if (element) host.replaceChildren(element);
|
|
294
|
+
}
|
|
295
|
+
if (slot) slot.update(data);
|
|
296
|
+
else mount(data);
|
|
297
|
+
};
|
|
298
|
+
const showMissing = () => {
|
|
299
|
+
if (destroyed || missing) return;
|
|
300
|
+
missing = true;
|
|
301
|
+
host.removeAttribute("data-aigui-card-store-error");
|
|
302
|
+
host.setAttribute("data-aigui-card-missing", "");
|
|
303
|
+
const fallback = document.createElement("code");
|
|
304
|
+
fallback.textContent = "Card data is unavailable";
|
|
305
|
+
host.replaceChildren(fallback);
|
|
306
|
+
};
|
|
307
|
+
if (registrationError === void 0) mount(data);
|
|
308
|
+
else {
|
|
309
|
+
host.setAttribute("data-aigui-card-store-error", "");
|
|
310
|
+
const code = document.createElement("code");
|
|
311
|
+
code.textContent = registrationError instanceof Error ? registrationError.message : "Card registration failed";
|
|
312
|
+
host.replaceChildren(code);
|
|
313
|
+
}
|
|
314
|
+
if (id !== void 0 && ctx.cardStore) unsubscribe = ctx.cardStore.subscribe(id, (record) => {
|
|
315
|
+
if (!record || record.type !== card.type) {
|
|
316
|
+
showMissing();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
update(record.data, record.action);
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
updateNode(node) {
|
|
323
|
+
const next = node.card;
|
|
324
|
+
if (!next?.complete || !next.valid || next.type !== card.type || next.id !== id) return false;
|
|
325
|
+
card = next;
|
|
326
|
+
if (id === void 0 || !ctx.cardStore) update(next.data, state);
|
|
327
|
+
return true;
|
|
328
|
+
},
|
|
329
|
+
destroy() {
|
|
330
|
+
if (destroyed) return;
|
|
331
|
+
destroyed = true;
|
|
332
|
+
unsubscribe();
|
|
333
|
+
unsubscribe = () => {};
|
|
334
|
+
slot?.destroy();
|
|
335
|
+
slot = void 0;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function isVanillaCardInstance(value) {
|
|
340
|
+
return !(value instanceof HTMLElement) && value !== null && typeof value === "object" && value.element instanceof HTMLElement && typeof value.update === "function";
|
|
341
|
+
}
|
|
165
342
|
|
|
166
343
|
//#endregion
|
|
167
344
|
//#region src/reconcile.ts
|
|
@@ -239,7 +416,7 @@ function updateElementInPlace(el, node, ctx) {
|
|
|
239
416
|
if (el.tagName !== "DIV") return false;
|
|
240
417
|
el.innerHTML = node.content ?? "";
|
|
241
418
|
return true;
|
|
242
|
-
case "card": return
|
|
419
|
+
case "card": return updateCardElement(el, node);
|
|
243
420
|
default:
|
|
244
421
|
if (el.tagName !== "DIV") return false;
|
|
245
422
|
el.innerHTML = node.html ?? node.content ?? "";
|
|
@@ -250,14 +427,20 @@ function updateElementInPlace(el, node, ctx) {
|
|
|
250
427
|
//#endregion
|
|
251
428
|
//#region src/create-renderer.ts
|
|
252
429
|
function createRenderer(el, options = {}) {
|
|
253
|
-
const { onCardAction,...rendererOpts } = options;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
430
|
+
const { actionRuntime, cardStore, onCardAction,...rendererOpts } = options;
|
|
431
|
+
let actionScope = {
|
|
432
|
+
owner: {},
|
|
433
|
+
controller: new AbortController()
|
|
434
|
+
};
|
|
435
|
+
const handleCardAction = (action) => {
|
|
436
|
+
if (actionRuntime) actionRuntime.dispatch({
|
|
437
|
+
...action,
|
|
438
|
+
params: action.params
|
|
439
|
+
}, {
|
|
440
|
+
owner: actionScope.owner,
|
|
441
|
+
signal: actionScope.controller.signal
|
|
442
|
+
}).catch(() => {});
|
|
443
|
+
onCardAction?.(action);
|
|
261
444
|
};
|
|
262
445
|
const state = createReconcileState();
|
|
263
446
|
let destroyed = false;
|
|
@@ -267,16 +450,35 @@ function createRenderer(el, options = {}) {
|
|
|
267
450
|
if (!destroyed) reconcile(el, nodes, ctx, state);
|
|
268
451
|
}
|
|
269
452
|
});
|
|
453
|
+
const ctx = {
|
|
454
|
+
registry: options.registry,
|
|
455
|
+
cardStore,
|
|
456
|
+
onCardAction: handleCardAction,
|
|
457
|
+
plugins: options.plugins,
|
|
458
|
+
nodeRenderers: (0, __ai_gui_core.collectNodeRenderers)(options.plugins, { debugTarget: renderer }),
|
|
459
|
+
sanitize: options.sanitize,
|
|
460
|
+
sanitized: true
|
|
461
|
+
};
|
|
270
462
|
const disposeAll = () => {
|
|
271
463
|
for (const entry of state.els.values()) disposeEl(entry.el);
|
|
272
464
|
};
|
|
465
|
+
const resetActionScope = () => {
|
|
466
|
+
actionScope.controller.abort();
|
|
467
|
+
actionScope = {
|
|
468
|
+
owner: {},
|
|
469
|
+
controller: new AbortController()
|
|
470
|
+
};
|
|
471
|
+
};
|
|
273
472
|
return {
|
|
473
|
+
debugSource: "renderer",
|
|
474
|
+
subscribeDebug: (listener) => renderer.subscribeDebug(listener),
|
|
274
475
|
push: (c) => {
|
|
275
476
|
if (!destroyed) renderer.push(c);
|
|
276
477
|
},
|
|
277
478
|
feed: (source, feedOptions) => destroyed ? Promise.resolve() : renderer.feed(source, feedOptions),
|
|
278
479
|
reset: () => {
|
|
279
480
|
if (!destroyed) {
|
|
481
|
+
resetActionScope();
|
|
280
482
|
disposeAll();
|
|
281
483
|
renderer.reset();
|
|
282
484
|
state.els.clear();
|
|
@@ -286,6 +488,7 @@ function createRenderer(el, options = {}) {
|
|
|
286
488
|
destroy: () => {
|
|
287
489
|
if (!destroyed) {
|
|
288
490
|
destroyed = true;
|
|
491
|
+
actionScope.controller.abort();
|
|
289
492
|
renderer.reset();
|
|
290
493
|
disposeAll();
|
|
291
494
|
state.els.clear();
|
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import { AIGuiPlugin, ASTNode, CardRegistry, FeedOptions, FeedSource, NodeRenderer, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
1
|
+
import { AIGuiPlugin, ASTNode, ActionRuntime, CardAction, CardRegistry, CardStore, DebugEventListener, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
2
2
|
|
|
3
3
|
//#region src/render-node-dom.d.ts
|
|
4
|
+
interface VanillaCardAction {
|
|
5
|
+
type: string;
|
|
6
|
+
params?: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface VanillaCardUpdateContext {
|
|
9
|
+
state: CardAction;
|
|
10
|
+
onAction: (action: VanillaCardAction) => void;
|
|
11
|
+
}
|
|
12
|
+
interface VanillaCardInstance {
|
|
13
|
+
element: HTMLElement;
|
|
14
|
+
update: (data: unknown, context: VanillaCardUpdateContext) => void;
|
|
15
|
+
destroy?: () => void;
|
|
16
|
+
}
|
|
17
|
+
type VanillaCardFactory = (data: unknown, context: VanillaCardUpdateContext) => HTMLElement | VanillaCardInstance;
|
|
4
18
|
interface DomRenderContext {
|
|
5
19
|
registry?: CardRegistry;
|
|
20
|
+
cardStore?: CardStore;
|
|
6
21
|
onCardAction?: (action: {
|
|
7
22
|
type: string;
|
|
8
23
|
params?: unknown;
|
|
9
24
|
cardType: string;
|
|
25
|
+
cardId?: string;
|
|
10
26
|
}) => void;
|
|
11
27
|
plugins?: AIGuiPlugin[];
|
|
12
28
|
nodeRenderers?: Record<string, NodeRenderer>;
|
|
@@ -18,9 +34,13 @@ declare function renderNodeToElement(node: ASTNode, ctx: DomRenderContext): HTML
|
|
|
18
34
|
//#endregion
|
|
19
35
|
//#region src/create-renderer.d.ts
|
|
20
36
|
interface CreateRendererOptions extends Omit<RendererOptions, "onPatch"> {
|
|
37
|
+
actionRuntime?: ActionRuntime;
|
|
38
|
+
cardStore?: CardStore;
|
|
21
39
|
onCardAction?: DomRenderContext["onCardAction"];
|
|
22
40
|
}
|
|
23
41
|
interface VanillaRenderer {
|
|
42
|
+
readonly debugSource: "renderer";
|
|
43
|
+
subscribeDebug: (listener: DebugEventListener) => () => void;
|
|
24
44
|
push: (chunk: string) => void;
|
|
25
45
|
feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
|
|
26
46
|
reset: () => void;
|
|
@@ -31,7 +51,7 @@ declare function createRenderer(el: HTMLElement, options?: CreateRendererOptions
|
|
|
31
51
|
//#endregion
|
|
32
52
|
//#region src/render-output.d.ts
|
|
33
53
|
/** Translate a framework-neutral RenderOutput (from a plugin node renderer) into a DOM element. */
|
|
34
|
-
declare function renderOutputToElement(out: RenderOutput, sanitize?: RendererOptions["sanitize"]): HTMLElement;
|
|
54
|
+
declare function renderOutputToElement(out: RenderOutput, sanitize?: RendererOptions["sanitize"], mountContext?: RenderMountContext): HTMLElement;
|
|
35
55
|
|
|
36
56
|
//#endregion
|
|
37
|
-
export { CreateRendererOptions, DomRenderContext, VanillaRenderer, createRenderer, renderNodeToElement, renderOutputToElement };
|
|
57
|
+
export { CreateRendererOptions, DomRenderContext, VanillaCardAction, VanillaCardFactory, VanillaCardInstance, VanillaCardUpdateContext, VanillaRenderer, createRenderer, renderNodeToElement, renderOutputToElement };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import { AIGuiPlugin, ASTNode, CardRegistry, FeedOptions, FeedSource, NodeRenderer, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
1
|
+
import { AIGuiPlugin, ASTNode, ActionRuntime, CardAction, CardRegistry, CardStore, DebugEventListener, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
2
2
|
|
|
3
3
|
//#region src/render-node-dom.d.ts
|
|
4
|
+
interface VanillaCardAction {
|
|
5
|
+
type: string;
|
|
6
|
+
params?: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface VanillaCardUpdateContext {
|
|
9
|
+
state: CardAction;
|
|
10
|
+
onAction: (action: VanillaCardAction) => void;
|
|
11
|
+
}
|
|
12
|
+
interface VanillaCardInstance {
|
|
13
|
+
element: HTMLElement;
|
|
14
|
+
update: (data: unknown, context: VanillaCardUpdateContext) => void;
|
|
15
|
+
destroy?: () => void;
|
|
16
|
+
}
|
|
17
|
+
type VanillaCardFactory = (data: unknown, context: VanillaCardUpdateContext) => HTMLElement | VanillaCardInstance;
|
|
4
18
|
interface DomRenderContext {
|
|
5
19
|
registry?: CardRegistry;
|
|
20
|
+
cardStore?: CardStore;
|
|
6
21
|
onCardAction?: (action: {
|
|
7
22
|
type: string;
|
|
8
23
|
params?: unknown;
|
|
9
24
|
cardType: string;
|
|
25
|
+
cardId?: string;
|
|
10
26
|
}) => void;
|
|
11
27
|
plugins?: AIGuiPlugin[];
|
|
12
28
|
nodeRenderers?: Record<string, NodeRenderer>;
|
|
@@ -18,9 +34,13 @@ declare function renderNodeToElement(node: ASTNode, ctx: DomRenderContext): HTML
|
|
|
18
34
|
//#endregion
|
|
19
35
|
//#region src/create-renderer.d.ts
|
|
20
36
|
interface CreateRendererOptions extends Omit<RendererOptions, "onPatch"> {
|
|
37
|
+
actionRuntime?: ActionRuntime;
|
|
38
|
+
cardStore?: CardStore;
|
|
21
39
|
onCardAction?: DomRenderContext["onCardAction"];
|
|
22
40
|
}
|
|
23
41
|
interface VanillaRenderer {
|
|
42
|
+
readonly debugSource: "renderer";
|
|
43
|
+
subscribeDebug: (listener: DebugEventListener) => () => void;
|
|
24
44
|
push: (chunk: string) => void;
|
|
25
45
|
feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
|
|
26
46
|
reset: () => void;
|
|
@@ -31,7 +51,7 @@ declare function createRenderer(el: HTMLElement, options?: CreateRendererOptions
|
|
|
31
51
|
//#endregion
|
|
32
52
|
//#region src/render-output.d.ts
|
|
33
53
|
/** Translate a framework-neutral RenderOutput (from a plugin node renderer) into a DOM element. */
|
|
34
|
-
declare function renderOutputToElement(out: RenderOutput, sanitize?: RendererOptions["sanitize"]): HTMLElement;
|
|
54
|
+
declare function renderOutputToElement(out: RenderOutput, sanitize?: RendererOptions["sanitize"], mountContext?: RenderMountContext): HTMLElement;
|
|
35
55
|
|
|
36
56
|
//#endregion
|
|
37
|
-
export { CreateRendererOptions, DomRenderContext, VanillaRenderer, createRenderer, renderNodeToElement, renderOutputToElement };
|
|
57
|
+
export { CreateRendererOptions, DomRenderContext, VanillaCardAction, VanillaCardFactory, VanillaCardInstance, VanillaCardUpdateContext, VanillaRenderer, createRenderer, renderNodeToElement, renderOutputToElement };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Renderer, collectNodeRenderers, sanitizeHtml } from "@ai-gui/core";
|
|
|
2
2
|
|
|
3
3
|
//#region src/render-output.ts
|
|
4
4
|
/** Translate a framework-neutral RenderOutput (from a plugin node renderer) into a DOM element. */
|
|
5
|
-
function renderOutputToElement(out, sanitize) {
|
|
5
|
+
function renderOutputToElement(out, sanitize, mountContext = {}) {
|
|
6
6
|
switch (out.kind) {
|
|
7
7
|
case "html": {
|
|
8
8
|
const el = document.createElement("div");
|
|
@@ -13,7 +13,7 @@ function renderOutputToElement(out, sanitize) {
|
|
|
13
13
|
const el = document.createElement(out.tag);
|
|
14
14
|
for (const [key, value] of Object.entries(out.props ?? {})) if (key === "class" || key === "className") el.className = String(value);
|
|
15
15
|
else el.setAttribute(key, String(value));
|
|
16
|
-
for (const child of out.children ?? []) el.appendChild(renderOutputToElement(child, sanitize));
|
|
16
|
+
for (const child of out.children ?? []) el.appendChild(renderOutputToElement(child, sanitize, mountContext));
|
|
17
17
|
return el;
|
|
18
18
|
}
|
|
19
19
|
case "card": {
|
|
@@ -29,7 +29,7 @@ function renderOutputToElement(out, sanitize) {
|
|
|
29
29
|
el.setAttribute("data-aigui-mount", "");
|
|
30
30
|
queueMicrotask(() => {
|
|
31
31
|
if (el.__aiguiDisposed) return;
|
|
32
|
-
const c = out.mount(el);
|
|
32
|
+
const c = out.mount(el, mountContext);
|
|
33
33
|
if (typeof c === "function") if (el.__aiguiDisposed) c();
|
|
34
34
|
else el.__aiguiCleanup = c;
|
|
35
35
|
});
|
|
@@ -46,25 +46,33 @@ function sanitizeOutput(html, sanitize) {
|
|
|
46
46
|
//#region src/render-node-dom.ts
|
|
47
47
|
function renderNodeToElement(node, ctx) {
|
|
48
48
|
const r = (ctx.nodeRenderers ?? collectNodeRenderers(ctx.plugins))[node.type];
|
|
49
|
-
if (r)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
host.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
if (r) {
|
|
50
|
+
if (node.complete === false) {
|
|
51
|
+
const loading = document.createElement("div");
|
|
52
|
+
loading.setAttribute("data-aigui-block-loading", "");
|
|
53
|
+
loading.setAttribute("data-block-type", node.type);
|
|
54
|
+
return loading;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const out = r(node);
|
|
58
|
+
if (typeof out?.then === "function") {
|
|
59
|
+
const host = document.createElement("div");
|
|
60
|
+
host.setAttribute("data-aigui-async-pending", "");
|
|
61
|
+
out.then((res) => {
|
|
62
|
+
if (host.__aiguiDisposed) return;
|
|
63
|
+
host.removeAttribute("data-aigui-async-pending");
|
|
64
|
+
host.replaceChildren(renderOutputToElement(res, ctx.sanitize, createRenderMountContext(ctx)));
|
|
65
|
+
}, () => {
|
|
66
|
+
if (host.__aiguiDisposed) return;
|
|
67
|
+
host.removeAttribute("data-aigui-async-pending");
|
|
68
|
+
host.setAttribute("data-aigui-async-error", "");
|
|
69
|
+
});
|
|
70
|
+
return host;
|
|
71
|
+
}
|
|
72
|
+
return renderOutputToElement(out, ctx.sanitize, createRenderMountContext(ctx));
|
|
73
|
+
} catch {
|
|
74
|
+
return renderFallback(node, ctx);
|
|
64
75
|
}
|
|
65
|
-
return renderOutputToElement(out, ctx.sanitize);
|
|
66
|
-
} catch {
|
|
67
|
-
return renderFallback(node, ctx);
|
|
68
76
|
}
|
|
69
77
|
switch (node.type) {
|
|
70
78
|
case "heading": {
|
|
@@ -122,22 +130,191 @@ function renderCardElement(node, ctx) {
|
|
|
122
130
|
return pre;
|
|
123
131
|
}
|
|
124
132
|
const factory = ctx.registry?.getRender(card.type);
|
|
125
|
-
if (!factory)
|
|
126
|
-
const pre = document.createElement("pre");
|
|
127
|
-
pre.setAttribute("data-aigui-card-fallback", "");
|
|
128
|
-
const c = document.createElement("code");
|
|
129
|
-
c.textContent = JSON.stringify(card.data, null, 2);
|
|
130
|
-
pre.appendChild(c);
|
|
131
|
-
return pre;
|
|
132
|
-
}
|
|
133
|
+
if (!factory) return createCardFallback(card, ctx);
|
|
133
134
|
const host = document.createElement("div");
|
|
134
135
|
host.setAttribute("data-aigui-card", card.type);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}) }));
|
|
136
|
+
const controller = createCardController(host, card, factory, ctx);
|
|
137
|
+
host.__aiguiCardController = controller;
|
|
138
|
+
host.__aiguiCleanup = () => controller.destroy();
|
|
139
139
|
return host;
|
|
140
140
|
}
|
|
141
|
+
function createRenderMountContext(ctx) {
|
|
142
|
+
return { mountCard(host, request) {
|
|
143
|
+
return mountCardSlot(host, request, ctx);
|
|
144
|
+
} };
|
|
145
|
+
}
|
|
146
|
+
function mountCardSlot(host, request, ctx) {
|
|
147
|
+
const factory = ctx.registry?.getRender(request.type);
|
|
148
|
+
if (!factory) return void 0;
|
|
149
|
+
return createVanillaCardSlot(host, request.data, factory, () => ({
|
|
150
|
+
state: { status: "idle" },
|
|
151
|
+
onAction: (action) => ctx.onCardAction?.({
|
|
152
|
+
...action,
|
|
153
|
+
cardType: request.type
|
|
154
|
+
})
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
function createVanillaCardSlot(host, initialData, factory, context) {
|
|
158
|
+
let destroyed = false;
|
|
159
|
+
let instance;
|
|
160
|
+
let element;
|
|
161
|
+
const mount = (data) => {
|
|
162
|
+
const rendered = factory(data, context());
|
|
163
|
+
if (isVanillaCardInstance(rendered)) {
|
|
164
|
+
instance = rendered;
|
|
165
|
+
element = rendered.element;
|
|
166
|
+
} else {
|
|
167
|
+
instance = void 0;
|
|
168
|
+
element = rendered;
|
|
169
|
+
}
|
|
170
|
+
host.replaceChildren(element);
|
|
171
|
+
};
|
|
172
|
+
mount(initialData);
|
|
173
|
+
return {
|
|
174
|
+
element: () => element,
|
|
175
|
+
update(data) {
|
|
176
|
+
if (destroyed) return;
|
|
177
|
+
if (instance) instance.update(data, context());
|
|
178
|
+
else mount(data);
|
|
179
|
+
},
|
|
180
|
+
destroy() {
|
|
181
|
+
if (destroyed) return;
|
|
182
|
+
destroyed = true;
|
|
183
|
+
instance?.destroy?.();
|
|
184
|
+
instance = void 0;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function createCardFallback(card, ctx) {
|
|
189
|
+
const pre = document.createElement("pre");
|
|
190
|
+
pre.setAttribute("data-aigui-card-fallback", "");
|
|
191
|
+
const code = document.createElement("code");
|
|
192
|
+
pre.appendChild(code);
|
|
193
|
+
const render = (data) => {
|
|
194
|
+
pre.removeAttribute("data-aigui-card-missing");
|
|
195
|
+
code.textContent = JSON.stringify(data, null, 2);
|
|
196
|
+
};
|
|
197
|
+
const missing = () => {
|
|
198
|
+
pre.setAttribute("data-aigui-card-missing", "");
|
|
199
|
+
code.textContent = "Card data is unavailable";
|
|
200
|
+
};
|
|
201
|
+
let unsubscribe = () => {};
|
|
202
|
+
if (card.id !== void 0 && ctx.cardStore) {
|
|
203
|
+
try {
|
|
204
|
+
const record = ctx.cardStore.register({
|
|
205
|
+
id: card.id,
|
|
206
|
+
type: card.type,
|
|
207
|
+
data: card.data
|
|
208
|
+
});
|
|
209
|
+
render(record.data);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
pre.setAttribute("data-aigui-card-store-error", "");
|
|
212
|
+
code.textContent = error instanceof Error ? error.message : "Card registration failed";
|
|
213
|
+
}
|
|
214
|
+
unsubscribe = ctx.cardStore.subscribe(card.id, (next) => {
|
|
215
|
+
if (!next || next.type !== card.type) missing();
|
|
216
|
+
else {
|
|
217
|
+
pre.removeAttribute("data-aigui-card-store-error");
|
|
218
|
+
render(next.data);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
pre.__aiguiCleanup = unsubscribe;
|
|
222
|
+
} else render(card.data);
|
|
223
|
+
return pre;
|
|
224
|
+
}
|
|
225
|
+
function updateCardElement(el, node) {
|
|
226
|
+
return el.__aiguiCardController?.updateNode(node) ?? false;
|
|
227
|
+
}
|
|
228
|
+
function createCardController(host, initialCard, factory, ctx) {
|
|
229
|
+
let card = initialCard;
|
|
230
|
+
let destroyed = false;
|
|
231
|
+
let unsubscribe = () => {};
|
|
232
|
+
let slot;
|
|
233
|
+
let missing = false;
|
|
234
|
+
const id = card.id;
|
|
235
|
+
const onAction = (action) => ctx.onCardAction?.({
|
|
236
|
+
...action,
|
|
237
|
+
cardType: card.type,
|
|
238
|
+
...id === void 0 ? {} : { cardId: id }
|
|
239
|
+
});
|
|
240
|
+
let data = card.data;
|
|
241
|
+
let state = { status: "idle" };
|
|
242
|
+
let registrationError;
|
|
243
|
+
if (id !== void 0 && ctx.cardStore) try {
|
|
244
|
+
const record = ctx.cardStore.register({
|
|
245
|
+
id,
|
|
246
|
+
type: card.type,
|
|
247
|
+
data
|
|
248
|
+
});
|
|
249
|
+
data = record.data;
|
|
250
|
+
state = record.action;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
registrationError = error;
|
|
253
|
+
}
|
|
254
|
+
const mount = (nextData) => {
|
|
255
|
+
slot = createVanillaCardSlot(host, nextData, factory, () => ({
|
|
256
|
+
state,
|
|
257
|
+
onAction
|
|
258
|
+
}));
|
|
259
|
+
};
|
|
260
|
+
const update = (nextData, nextState) => {
|
|
261
|
+
if (destroyed) return;
|
|
262
|
+
data = nextData;
|
|
263
|
+
state = nextState;
|
|
264
|
+
host.removeAttribute("data-aigui-card-store-error");
|
|
265
|
+
if (missing) {
|
|
266
|
+
missing = false;
|
|
267
|
+
host.removeAttribute("data-aigui-card-missing");
|
|
268
|
+
const element = slot?.element();
|
|
269
|
+
if (element) host.replaceChildren(element);
|
|
270
|
+
}
|
|
271
|
+
if (slot) slot.update(data);
|
|
272
|
+
else mount(data);
|
|
273
|
+
};
|
|
274
|
+
const showMissing = () => {
|
|
275
|
+
if (destroyed || missing) return;
|
|
276
|
+
missing = true;
|
|
277
|
+
host.removeAttribute("data-aigui-card-store-error");
|
|
278
|
+
host.setAttribute("data-aigui-card-missing", "");
|
|
279
|
+
const fallback = document.createElement("code");
|
|
280
|
+
fallback.textContent = "Card data is unavailable";
|
|
281
|
+
host.replaceChildren(fallback);
|
|
282
|
+
};
|
|
283
|
+
if (registrationError === void 0) mount(data);
|
|
284
|
+
else {
|
|
285
|
+
host.setAttribute("data-aigui-card-store-error", "");
|
|
286
|
+
const code = document.createElement("code");
|
|
287
|
+
code.textContent = registrationError instanceof Error ? registrationError.message : "Card registration failed";
|
|
288
|
+
host.replaceChildren(code);
|
|
289
|
+
}
|
|
290
|
+
if (id !== void 0 && ctx.cardStore) unsubscribe = ctx.cardStore.subscribe(id, (record) => {
|
|
291
|
+
if (!record || record.type !== card.type) {
|
|
292
|
+
showMissing();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
update(record.data, record.action);
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
updateNode(node) {
|
|
299
|
+
const next = node.card;
|
|
300
|
+
if (!next?.complete || !next.valid || next.type !== card.type || next.id !== id) return false;
|
|
301
|
+
card = next;
|
|
302
|
+
if (id === void 0 || !ctx.cardStore) update(next.data, state);
|
|
303
|
+
return true;
|
|
304
|
+
},
|
|
305
|
+
destroy() {
|
|
306
|
+
if (destroyed) return;
|
|
307
|
+
destroyed = true;
|
|
308
|
+
unsubscribe();
|
|
309
|
+
unsubscribe = () => {};
|
|
310
|
+
slot?.destroy();
|
|
311
|
+
slot = void 0;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function isVanillaCardInstance(value) {
|
|
316
|
+
return !(value instanceof HTMLElement) && value !== null && typeof value === "object" && value.element instanceof HTMLElement && typeof value.update === "function";
|
|
317
|
+
}
|
|
141
318
|
|
|
142
319
|
//#endregion
|
|
143
320
|
//#region src/reconcile.ts
|
|
@@ -215,7 +392,7 @@ function updateElementInPlace(el, node, ctx) {
|
|
|
215
392
|
if (el.tagName !== "DIV") return false;
|
|
216
393
|
el.innerHTML = node.content ?? "";
|
|
217
394
|
return true;
|
|
218
|
-
case "card": return
|
|
395
|
+
case "card": return updateCardElement(el, node);
|
|
219
396
|
default:
|
|
220
397
|
if (el.tagName !== "DIV") return false;
|
|
221
398
|
el.innerHTML = node.html ?? node.content ?? "";
|
|
@@ -226,14 +403,20 @@ function updateElementInPlace(el, node, ctx) {
|
|
|
226
403
|
//#endregion
|
|
227
404
|
//#region src/create-renderer.ts
|
|
228
405
|
function createRenderer(el, options = {}) {
|
|
229
|
-
const { onCardAction,...rendererOpts } = options;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
406
|
+
const { actionRuntime, cardStore, onCardAction,...rendererOpts } = options;
|
|
407
|
+
let actionScope = {
|
|
408
|
+
owner: {},
|
|
409
|
+
controller: new AbortController()
|
|
410
|
+
};
|
|
411
|
+
const handleCardAction = (action) => {
|
|
412
|
+
if (actionRuntime) actionRuntime.dispatch({
|
|
413
|
+
...action,
|
|
414
|
+
params: action.params
|
|
415
|
+
}, {
|
|
416
|
+
owner: actionScope.owner,
|
|
417
|
+
signal: actionScope.controller.signal
|
|
418
|
+
}).catch(() => {});
|
|
419
|
+
onCardAction?.(action);
|
|
237
420
|
};
|
|
238
421
|
const state = createReconcileState();
|
|
239
422
|
let destroyed = false;
|
|
@@ -243,16 +426,35 @@ function createRenderer(el, options = {}) {
|
|
|
243
426
|
if (!destroyed) reconcile(el, nodes, ctx, state);
|
|
244
427
|
}
|
|
245
428
|
});
|
|
429
|
+
const ctx = {
|
|
430
|
+
registry: options.registry,
|
|
431
|
+
cardStore,
|
|
432
|
+
onCardAction: handleCardAction,
|
|
433
|
+
plugins: options.plugins,
|
|
434
|
+
nodeRenderers: collectNodeRenderers(options.plugins, { debugTarget: renderer }),
|
|
435
|
+
sanitize: options.sanitize,
|
|
436
|
+
sanitized: true
|
|
437
|
+
};
|
|
246
438
|
const disposeAll = () => {
|
|
247
439
|
for (const entry of state.els.values()) disposeEl(entry.el);
|
|
248
440
|
};
|
|
441
|
+
const resetActionScope = () => {
|
|
442
|
+
actionScope.controller.abort();
|
|
443
|
+
actionScope = {
|
|
444
|
+
owner: {},
|
|
445
|
+
controller: new AbortController()
|
|
446
|
+
};
|
|
447
|
+
};
|
|
249
448
|
return {
|
|
449
|
+
debugSource: "renderer",
|
|
450
|
+
subscribeDebug: (listener) => renderer.subscribeDebug(listener),
|
|
250
451
|
push: (c) => {
|
|
251
452
|
if (!destroyed) renderer.push(c);
|
|
252
453
|
},
|
|
253
454
|
feed: (source, feedOptions) => destroyed ? Promise.resolve() : renderer.feed(source, feedOptions),
|
|
254
455
|
reset: () => {
|
|
255
456
|
if (!destroyed) {
|
|
457
|
+
resetActionScope();
|
|
256
458
|
disposeAll();
|
|
257
459
|
renderer.reset();
|
|
258
460
|
state.els.clear();
|
|
@@ -262,6 +464,7 @@ function createRenderer(el, options = {}) {
|
|
|
262
464
|
destroy: () => {
|
|
263
465
|
if (!destroyed) {
|
|
264
466
|
destroyed = true;
|
|
467
|
+
actionScope.controller.abort();
|
|
265
468
|
renderer.reset();
|
|
266
469
|
disposeAll();
|
|
267
470
|
state.els.clear();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/vanilla",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Framework-free DOM adapter for AIGUI — stream LLM output straight into the DOM.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
@@ -50,7 +50,7 @@
|
|
|
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
|
"scripts": {
|
|
56
56
|
"build": "tsdown",
|