@ai-gui/vanilla 0.4.4 → 0.6.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 +39 -10
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +40 -11
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,7 @@ function renderOutputToElement(out, sanitize, mountContext = {}) {
|
|
|
30
30
|
switch (out.kind) {
|
|
31
31
|
case "html": {
|
|
32
32
|
const el = document.createElement("div");
|
|
33
|
-
el.innerHTML =
|
|
33
|
+
el.innerHTML = (0, __ai_gui_core.sanitizeRenderedHtml)(out.html, sanitize, out.trusted);
|
|
34
34
|
return el;
|
|
35
35
|
}
|
|
36
36
|
case "element": {
|
|
@@ -61,10 +61,6 @@ function renderOutputToElement(out, sanitize, mountContext = {}) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
function sanitizeOutput(html, sanitize) {
|
|
65
|
-
if (sanitize === false) return html;
|
|
66
|
-
return (0, __ai_gui_core.sanitizeHtml)(html, typeof sanitize === "object" ? sanitize : void 0);
|
|
67
|
-
}
|
|
68
64
|
|
|
69
65
|
//#endregion
|
|
70
66
|
//#region src/render-node-dom.ts
|
|
@@ -78,7 +74,7 @@ function renderNodeToElement(node, ctx) {
|
|
|
78
74
|
return loading;
|
|
79
75
|
}
|
|
80
76
|
try {
|
|
81
|
-
const out = r(node);
|
|
77
|
+
const out = r(node, { theme: ctx.theme });
|
|
82
78
|
if (typeof out?.then === "function") {
|
|
83
79
|
const host = document.createElement("div");
|
|
84
80
|
host.setAttribute("data-aigui-async-pending", "");
|
|
@@ -427,7 +423,9 @@ function updateElementInPlace(el, node, ctx) {
|
|
|
427
423
|
//#endregion
|
|
428
424
|
//#region src/create-renderer.ts
|
|
429
425
|
function createRenderer(el, options = {}) {
|
|
430
|
-
const { actionRuntime, cardStore, onCardAction,...rendererOpts } = options;
|
|
426
|
+
const { actionRuntime, cardStore, onCardAction, theme, onRender,...rendererOpts } = options;
|
|
427
|
+
let rendered = "";
|
|
428
|
+
let latestNodes = [];
|
|
431
429
|
let actionScope = {
|
|
432
430
|
owner: {},
|
|
433
431
|
controller: new AbortController()
|
|
@@ -447,7 +445,10 @@ function createRenderer(el, options = {}) {
|
|
|
447
445
|
const renderer = new __ai_gui_core.Renderer({
|
|
448
446
|
...rendererOpts,
|
|
449
447
|
onPatch: (_patches, nodes) => {
|
|
450
|
-
if (
|
|
448
|
+
if (destroyed) return;
|
|
449
|
+
latestNodes = nodes;
|
|
450
|
+
reconcile(el, nodes, ctx, state);
|
|
451
|
+
onRender?.(nodes);
|
|
451
452
|
}
|
|
452
453
|
});
|
|
453
454
|
const ctx = {
|
|
@@ -457,7 +458,8 @@ function createRenderer(el, options = {}) {
|
|
|
457
458
|
plugins: options.plugins,
|
|
458
459
|
nodeRenderers: (0, __ai_gui_core.collectNodeRenderers)(options.plugins, { debugTarget: renderer }),
|
|
459
460
|
sanitize: options.sanitize,
|
|
460
|
-
sanitized: true
|
|
461
|
+
sanitized: true,
|
|
462
|
+
theme
|
|
461
463
|
};
|
|
462
464
|
const disposeAll = () => {
|
|
463
465
|
for (const entry of state.els.values()) disposeEl(entry.el);
|
|
@@ -473,8 +475,33 @@ function createRenderer(el, options = {}) {
|
|
|
473
475
|
debugSource: "renderer",
|
|
474
476
|
subscribeDebug: (listener) => renderer.subscribeDebug(listener),
|
|
475
477
|
push: (c) => {
|
|
476
|
-
if (!destroyed)
|
|
478
|
+
if (!destroyed) {
|
|
479
|
+
renderer.push(c);
|
|
480
|
+
rendered += c;
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
setText: (text) => {
|
|
484
|
+
if (destroyed || text === rendered) return;
|
|
485
|
+
if (text.startsWith(rendered)) renderer.push(text.slice(rendered.length));
|
|
486
|
+
else {
|
|
487
|
+
resetActionScope();
|
|
488
|
+
disposeAll();
|
|
489
|
+
renderer.reset();
|
|
490
|
+
state.els.clear();
|
|
491
|
+
el.replaceChildren();
|
|
492
|
+
renderer.push(text);
|
|
493
|
+
}
|
|
494
|
+
rendered = text;
|
|
495
|
+
},
|
|
496
|
+
setTheme: (next) => {
|
|
497
|
+
if (destroyed || ctx.theme === next) return;
|
|
498
|
+
ctx.theme = next;
|
|
499
|
+
disposeAll();
|
|
500
|
+
state.els.clear();
|
|
501
|
+
el.replaceChildren();
|
|
502
|
+
reconcile(el, latestNodes, ctx, state);
|
|
477
503
|
},
|
|
504
|
+
exportImages: (exportOptions) => (0, __ai_gui_core.exportRenderedImages)(el, exportOptions),
|
|
478
505
|
feed: (source, feedOptions) => destroyed ? Promise.resolve() : renderer.feed(source, feedOptions),
|
|
479
506
|
reset: () => {
|
|
480
507
|
if (!destroyed) {
|
|
@@ -483,11 +510,13 @@ function createRenderer(el, options = {}) {
|
|
|
483
510
|
renderer.reset();
|
|
484
511
|
state.els.clear();
|
|
485
512
|
el.replaceChildren();
|
|
513
|
+
rendered = "";
|
|
486
514
|
}
|
|
487
515
|
},
|
|
488
516
|
destroy: () => {
|
|
489
517
|
if (!destroyed) {
|
|
490
518
|
destroyed = true;
|
|
519
|
+
rendered = "";
|
|
491
520
|
actionScope.controller.abort();
|
|
492
521
|
renderer.reset();
|
|
493
522
|
disposeAll();
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AIGuiPlugin, ASTNode, ActionRuntime, CardAction, CardRegistry, CardStore, DebugEventListener, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
1
|
+
import { AIGuiPlugin, ASTNode, ActionRuntime, CardAction, CardRegistry, CardStore, DebugEventListener, ExportImageOptions, ExportedImage, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
2
2
|
|
|
3
3
|
//#region src/render-node-dom.d.ts
|
|
4
4
|
interface VanillaCardAction {
|
|
@@ -28,6 +28,8 @@ interface DomRenderContext {
|
|
|
28
28
|
nodeRenderers?: Record<string, NodeRenderer>;
|
|
29
29
|
sanitize?: RendererOptions["sanitize"];
|
|
30
30
|
sanitized?: boolean;
|
|
31
|
+
/** The host's colour scheme, handed to every plugin that renders a node. */
|
|
32
|
+
theme?: string;
|
|
31
33
|
}
|
|
32
34
|
declare function renderNodeToElement(node: ASTNode, ctx: DomRenderContext): HTMLElement;
|
|
33
35
|
|
|
@@ -37,12 +39,28 @@ interface CreateRendererOptions extends Omit<RendererOptions, "onPatch"> {
|
|
|
37
39
|
actionRuntime?: ActionRuntime;
|
|
38
40
|
cardStore?: CardStore;
|
|
39
41
|
onCardAction?: DomRenderContext["onCardAction"];
|
|
42
|
+
/** The host's colour scheme, handed to every plugin. Change it later with `setTheme`. */
|
|
43
|
+
theme?: string;
|
|
44
|
+
/** Called with the nodes on screen whenever they change. */
|
|
45
|
+
onRender?: (nodes: ASTNode[]) => void;
|
|
40
46
|
}
|
|
41
47
|
interface VanillaRenderer {
|
|
42
48
|
readonly debugSource: "renderer";
|
|
43
49
|
subscribeDebug: (listener: DebugEventListener) => () => void;
|
|
44
50
|
push: (chunk: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Render this whole text, pushing only what it adds to what is already on screen.
|
|
53
|
+
*
|
|
54
|
+
* Streaming an answer means calling this with a longer string each time. Working out the delta
|
|
55
|
+
* is the renderer's job — a caller doing it itself has to remember what it sent and notice when
|
|
56
|
+
* the next text is not a continuation.
|
|
57
|
+
*/
|
|
58
|
+
setText: (text: string) => void;
|
|
59
|
+
/** Redraw the plugins for another colour scheme. */
|
|
60
|
+
setTheme: (theme: string | undefined) => void;
|
|
45
61
|
feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
|
|
62
|
+
/** Export every drawing currently rendered, as PNG data URLs. */
|
|
63
|
+
exportImages: (options?: ExportImageOptions) => Promise<ExportedImage[]>;
|
|
46
64
|
reset: () => void;
|
|
47
65
|
destroy: () => void;
|
|
48
66
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AIGuiPlugin, ASTNode, ActionRuntime, CardAction, CardRegistry, CardStore, DebugEventListener, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
1
|
+
import { AIGuiPlugin, ASTNode, ActionRuntime, CardAction, CardRegistry, CardStore, DebugEventListener, ExportImageOptions, ExportedImage, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
2
2
|
|
|
3
3
|
//#region src/render-node-dom.d.ts
|
|
4
4
|
interface VanillaCardAction {
|
|
@@ -28,6 +28,8 @@ interface DomRenderContext {
|
|
|
28
28
|
nodeRenderers?: Record<string, NodeRenderer>;
|
|
29
29
|
sanitize?: RendererOptions["sanitize"];
|
|
30
30
|
sanitized?: boolean;
|
|
31
|
+
/** The host's colour scheme, handed to every plugin that renders a node. */
|
|
32
|
+
theme?: string;
|
|
31
33
|
}
|
|
32
34
|
declare function renderNodeToElement(node: ASTNode, ctx: DomRenderContext): HTMLElement;
|
|
33
35
|
|
|
@@ -37,12 +39,28 @@ interface CreateRendererOptions extends Omit<RendererOptions, "onPatch"> {
|
|
|
37
39
|
actionRuntime?: ActionRuntime;
|
|
38
40
|
cardStore?: CardStore;
|
|
39
41
|
onCardAction?: DomRenderContext["onCardAction"];
|
|
42
|
+
/** The host's colour scheme, handed to every plugin. Change it later with `setTheme`. */
|
|
43
|
+
theme?: string;
|
|
44
|
+
/** Called with the nodes on screen whenever they change. */
|
|
45
|
+
onRender?: (nodes: ASTNode[]) => void;
|
|
40
46
|
}
|
|
41
47
|
interface VanillaRenderer {
|
|
42
48
|
readonly debugSource: "renderer";
|
|
43
49
|
subscribeDebug: (listener: DebugEventListener) => () => void;
|
|
44
50
|
push: (chunk: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Render this whole text, pushing only what it adds to what is already on screen.
|
|
53
|
+
*
|
|
54
|
+
* Streaming an answer means calling this with a longer string each time. Working out the delta
|
|
55
|
+
* is the renderer's job — a caller doing it itself has to remember what it sent and notice when
|
|
56
|
+
* the next text is not a continuation.
|
|
57
|
+
*/
|
|
58
|
+
setText: (text: string) => void;
|
|
59
|
+
/** Redraw the plugins for another colour scheme. */
|
|
60
|
+
setTheme: (theme: string | undefined) => void;
|
|
45
61
|
feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
|
|
62
|
+
/** Export every drawing currently rendered, as PNG data URLs. */
|
|
63
|
+
exportImages: (options?: ExportImageOptions) => Promise<ExportedImage[]>;
|
|
46
64
|
reset: () => void;
|
|
47
65
|
destroy: () => void;
|
|
48
66
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Renderer, collectNodeRenderers, sanitizeHtml } from "@ai-gui/core";
|
|
1
|
+
import { Renderer, collectNodeRenderers, exportRenderedImages, sanitizeHtml, sanitizeRenderedHtml } 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. */
|
|
@@ -6,7 +6,7 @@ function renderOutputToElement(out, sanitize, mountContext = {}) {
|
|
|
6
6
|
switch (out.kind) {
|
|
7
7
|
case "html": {
|
|
8
8
|
const el = document.createElement("div");
|
|
9
|
-
el.innerHTML =
|
|
9
|
+
el.innerHTML = sanitizeRenderedHtml(out.html, sanitize, out.trusted);
|
|
10
10
|
return el;
|
|
11
11
|
}
|
|
12
12
|
case "element": {
|
|
@@ -37,10 +37,6 @@ function renderOutputToElement(out, sanitize, mountContext = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
function sanitizeOutput(html, sanitize) {
|
|
41
|
-
if (sanitize === false) return html;
|
|
42
|
-
return sanitizeHtml(html, typeof sanitize === "object" ? sanitize : void 0);
|
|
43
|
-
}
|
|
44
40
|
|
|
45
41
|
//#endregion
|
|
46
42
|
//#region src/render-node-dom.ts
|
|
@@ -54,7 +50,7 @@ function renderNodeToElement(node, ctx) {
|
|
|
54
50
|
return loading;
|
|
55
51
|
}
|
|
56
52
|
try {
|
|
57
|
-
const out = r(node);
|
|
53
|
+
const out = r(node, { theme: ctx.theme });
|
|
58
54
|
if (typeof out?.then === "function") {
|
|
59
55
|
const host = document.createElement("div");
|
|
60
56
|
host.setAttribute("data-aigui-async-pending", "");
|
|
@@ -403,7 +399,9 @@ function updateElementInPlace(el, node, ctx) {
|
|
|
403
399
|
//#endregion
|
|
404
400
|
//#region src/create-renderer.ts
|
|
405
401
|
function createRenderer(el, options = {}) {
|
|
406
|
-
const { actionRuntime, cardStore, onCardAction,...rendererOpts } = options;
|
|
402
|
+
const { actionRuntime, cardStore, onCardAction, theme, onRender,...rendererOpts } = options;
|
|
403
|
+
let rendered = "";
|
|
404
|
+
let latestNodes = [];
|
|
407
405
|
let actionScope = {
|
|
408
406
|
owner: {},
|
|
409
407
|
controller: new AbortController()
|
|
@@ -423,7 +421,10 @@ function createRenderer(el, options = {}) {
|
|
|
423
421
|
const renderer = new Renderer({
|
|
424
422
|
...rendererOpts,
|
|
425
423
|
onPatch: (_patches, nodes) => {
|
|
426
|
-
if (
|
|
424
|
+
if (destroyed) return;
|
|
425
|
+
latestNodes = nodes;
|
|
426
|
+
reconcile(el, nodes, ctx, state);
|
|
427
|
+
onRender?.(nodes);
|
|
427
428
|
}
|
|
428
429
|
});
|
|
429
430
|
const ctx = {
|
|
@@ -433,7 +434,8 @@ function createRenderer(el, options = {}) {
|
|
|
433
434
|
plugins: options.plugins,
|
|
434
435
|
nodeRenderers: collectNodeRenderers(options.plugins, { debugTarget: renderer }),
|
|
435
436
|
sanitize: options.sanitize,
|
|
436
|
-
sanitized: true
|
|
437
|
+
sanitized: true,
|
|
438
|
+
theme
|
|
437
439
|
};
|
|
438
440
|
const disposeAll = () => {
|
|
439
441
|
for (const entry of state.els.values()) disposeEl(entry.el);
|
|
@@ -449,8 +451,33 @@ function createRenderer(el, options = {}) {
|
|
|
449
451
|
debugSource: "renderer",
|
|
450
452
|
subscribeDebug: (listener) => renderer.subscribeDebug(listener),
|
|
451
453
|
push: (c) => {
|
|
452
|
-
if (!destroyed)
|
|
454
|
+
if (!destroyed) {
|
|
455
|
+
renderer.push(c);
|
|
456
|
+
rendered += c;
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
setText: (text) => {
|
|
460
|
+
if (destroyed || text === rendered) return;
|
|
461
|
+
if (text.startsWith(rendered)) renderer.push(text.slice(rendered.length));
|
|
462
|
+
else {
|
|
463
|
+
resetActionScope();
|
|
464
|
+
disposeAll();
|
|
465
|
+
renderer.reset();
|
|
466
|
+
state.els.clear();
|
|
467
|
+
el.replaceChildren();
|
|
468
|
+
renderer.push(text);
|
|
469
|
+
}
|
|
470
|
+
rendered = text;
|
|
471
|
+
},
|
|
472
|
+
setTheme: (next) => {
|
|
473
|
+
if (destroyed || ctx.theme === next) return;
|
|
474
|
+
ctx.theme = next;
|
|
475
|
+
disposeAll();
|
|
476
|
+
state.els.clear();
|
|
477
|
+
el.replaceChildren();
|
|
478
|
+
reconcile(el, latestNodes, ctx, state);
|
|
453
479
|
},
|
|
480
|
+
exportImages: (exportOptions) => exportRenderedImages(el, exportOptions),
|
|
454
481
|
feed: (source, feedOptions) => destroyed ? Promise.resolve() : renderer.feed(source, feedOptions),
|
|
455
482
|
reset: () => {
|
|
456
483
|
if (!destroyed) {
|
|
@@ -459,11 +486,13 @@ function createRenderer(el, options = {}) {
|
|
|
459
486
|
renderer.reset();
|
|
460
487
|
state.els.clear();
|
|
461
488
|
el.replaceChildren();
|
|
489
|
+
rendered = "";
|
|
462
490
|
}
|
|
463
491
|
},
|
|
464
492
|
destroy: () => {
|
|
465
493
|
if (!destroyed) {
|
|
466
494
|
destroyed = true;
|
|
495
|
+
rendered = "";
|
|
467
496
|
actionScope.controller.abort();
|
|
468
497
|
renderer.reset();
|
|
469
498
|
disposeAll();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/vanilla",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.6.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsdown",
|