@ai-gui/vue 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 +12 -4
- package/dist/index.cjs +237 -30
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +237 -31
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,13 +13,18 @@ pnpm add @ai-gui/core @ai-gui/vue
|
|
|
13
13
|
```vue
|
|
14
14
|
<script setup lang="ts">
|
|
15
15
|
import { ref } from "vue"
|
|
16
|
-
import { CardRegistry } from "@ai-gui/core"
|
|
17
|
-
import { AIRenderer } from "@ai-gui/vue"
|
|
16
|
+
import { ActionRegistry, CardRegistry, CardStore, createActionRuntime } from "@ai-gui/core"
|
|
17
|
+
import { AIRenderer, useActionState } from "@ai-gui/vue"
|
|
18
18
|
|
|
19
19
|
const registry = new CardRegistry()
|
|
20
20
|
// A card render component receives props `data` and emits `action`.
|
|
21
21
|
registry.register({ type: "weather", description: "Weather summary", render: WeatherCard })
|
|
22
22
|
|
|
23
|
+
const actions = new ActionRegistry()
|
|
24
|
+
actions.register({ type: "refresh", run: async (params, { signal }) => fetch("/api/weather", { signal }) })
|
|
25
|
+
const cardStore = new CardStore({ registry })
|
|
26
|
+
const actionRuntime = createActionRuntime({ registry: actions, cardStore })
|
|
27
|
+
|
|
23
28
|
const r = ref<InstanceType<typeof AIRenderer>>()
|
|
24
29
|
|
|
25
30
|
async function ask() {
|
|
@@ -30,13 +35,16 @@ async function ask() {
|
|
|
30
35
|
</script>
|
|
31
36
|
|
|
32
37
|
<template>
|
|
33
|
-
<AIRenderer ref="r" :registry="registry" @card-action="(a) =>
|
|
38
|
+
<AIRenderer ref="r" :registry="registry" :card-store="cardStore" :action-runtime="actionRuntime" @card-action="(a) => console.log(a)" />
|
|
34
39
|
</template>
|
|
35
40
|
```
|
|
36
41
|
|
|
37
42
|
## Exports
|
|
38
43
|
|
|
39
|
-
- `<AIRenderer :registry :plugins :sanitize @card-action />` — a render-function component; imperative `push` / `feed` / `reset` via a template ref (exposed).
|
|
44
|
+
- `<AIRenderer :registry :card-store :plugins :sanitize :action-runtime @card-action />` — a render-function component; imperative `push` / `feed` / `reset` via a template ref (exposed).
|
|
40
45
|
- `useAIRenderer()` — composable returning `{ nodes, push, feed, reset }`.
|
|
46
|
+
- `useActionState(runtime, key)` — reactive action lifecycle state.
|
|
47
|
+
|
|
48
|
+
Cards with a top-level `id` subscribe to the supplied `CardStore`. Their Vue component receives `data` and `state` props and emits `action`; store updates preserve the component instance and local refs.
|
|
41
49
|
|
|
42
50
|
Card component contract: props `data`, `emits: ['action']`. See the [root README](../../README.md) for cards, plugins, and `buildSystemPrompt`.
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ function useAIRenderer(options = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
return {
|
|
40
|
+
renderer,
|
|
40
41
|
nodes,
|
|
41
42
|
push: (c) => {
|
|
42
43
|
if (active) renderer.push(c);
|
|
@@ -56,24 +57,41 @@ function useAIRenderer(options = {}) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/use-action-state.ts
|
|
62
|
+
function useActionState(runtime, key) {
|
|
63
|
+
const state = (0, vue.shallowRef)(runtime.getState(key));
|
|
64
|
+
const unsubscribe = runtime.subscribe((next) => {
|
|
65
|
+
if (next.key === key) state.value = next;
|
|
66
|
+
});
|
|
67
|
+
(0, vue.onScopeDispose)(unsubscribe);
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
//#endregion
|
|
60
72
|
//#region src/render-output.ts
|
|
61
73
|
/** Host element for imperative `mount` outputs, wired to Vue's lifecycle. */
|
|
62
74
|
const MountHost = (0, vue.defineComponent)({
|
|
63
75
|
name: "MountHost",
|
|
64
|
-
props: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
props: {
|
|
77
|
+
mount: {
|
|
78
|
+
type: Function,
|
|
79
|
+
required: true
|
|
80
|
+
},
|
|
81
|
+
context: {
|
|
82
|
+
type: Object,
|
|
83
|
+
required: true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
68
86
|
setup(props) {
|
|
69
87
|
const elRef = (0, vue.ref)(null);
|
|
70
88
|
let cleanup;
|
|
71
89
|
(0, vue.onMounted)(() => {
|
|
72
|
-
if (elRef.value) cleanup = props.mount(elRef.value);
|
|
90
|
+
if (elRef.value) cleanup = props.mount(elRef.value, props.context);
|
|
73
91
|
});
|
|
74
92
|
(0, vue.watch)(() => props.mount, (mount) => {
|
|
75
93
|
if (typeof cleanup === "function") cleanup();
|
|
76
|
-
cleanup = elRef.value ? mount(elRef.value) : void 0;
|
|
94
|
+
cleanup = elRef.value ? mount(elRef.value, props.context) : void 0;
|
|
77
95
|
});
|
|
78
96
|
(0, vue.onBeforeUnmount)(() => {
|
|
79
97
|
if (typeof cleanup === "function") cleanup();
|
|
@@ -85,11 +103,14 @@ const MountHost = (0, vue.defineComponent)({
|
|
|
85
103
|
}
|
|
86
104
|
});
|
|
87
105
|
/** Translate a framework-neutral RenderOutput into a Vue VNode. */
|
|
88
|
-
function renderOutput(out, sanitize) {
|
|
106
|
+
function renderOutput(out, sanitize, context = {}) {
|
|
89
107
|
switch (out.kind) {
|
|
90
108
|
case "html": return (0, vue.h)("div", { innerHTML: sanitizeOutput(out.html, sanitize) });
|
|
91
|
-
case "element": return (0, vue.h)(out.tag, out.props, (out.children ?? []).map((child) => renderOutput(child, sanitize)));
|
|
92
|
-
case "mount": return (0, vue.h)(MountHost, {
|
|
109
|
+
case "element": return (0, vue.h)(out.tag, out.props, (out.children ?? []).map((child) => renderOutput(child, sanitize, context)));
|
|
110
|
+
case "mount": return (0, vue.h)(MountHost, {
|
|
111
|
+
mount: out.mount,
|
|
112
|
+
context
|
|
113
|
+
});
|
|
93
114
|
case "card": return (0, vue.h)("pre", { "data-aigui-card-fallback": "" }, [(0, vue.h)("code", JSON.stringify(out.data, null, 2))]);
|
|
94
115
|
}
|
|
95
116
|
}
|
|
@@ -104,6 +125,10 @@ const AsyncOutput = (0, vue.defineComponent)({
|
|
|
104
125
|
sanitize: {
|
|
105
126
|
type: [Boolean, Object],
|
|
106
127
|
default: void 0
|
|
128
|
+
},
|
|
129
|
+
context: {
|
|
130
|
+
type: Object,
|
|
131
|
+
default: () => ({})
|
|
107
132
|
}
|
|
108
133
|
},
|
|
109
134
|
setup(props) {
|
|
@@ -130,13 +155,42 @@ const AsyncOutput = (0, vue.defineComponent)({
|
|
|
130
155
|
if (failed.value) return (0, vue.h)("span", { "data-aigui-async-error": "" });
|
|
131
156
|
if (!resolved.value) return (0, vue.h)("span", { "data-aigui-async-pending": "" });
|
|
132
157
|
try {
|
|
133
|
-
return renderOutput(resolved.value, props.sanitize);
|
|
158
|
+
return renderOutput(resolved.value, props.sanitize, props.context);
|
|
134
159
|
} catch {
|
|
135
160
|
return (0, vue.h)("span", { "data-aigui-async-error": "" });
|
|
136
161
|
}
|
|
137
162
|
};
|
|
138
163
|
}
|
|
139
164
|
});
|
|
165
|
+
function createRenderMountContext(registry, onCardAction) {
|
|
166
|
+
return { mountCard(host, request) {
|
|
167
|
+
const component = registry?.getRender(request.type);
|
|
168
|
+
if (!component) return void 0;
|
|
169
|
+
const card = (0, vue.markRaw)((0, vue.toRaw)(component));
|
|
170
|
+
let data = request.data;
|
|
171
|
+
let destroyed = false;
|
|
172
|
+
const renderCard$1 = () => (0, vue.render)((0, vue.h)(card, {
|
|
173
|
+
data,
|
|
174
|
+
onAction: (action) => onCardAction?.({
|
|
175
|
+
...action,
|
|
176
|
+
cardType: request.type
|
|
177
|
+
})
|
|
178
|
+
}), host);
|
|
179
|
+
renderCard$1();
|
|
180
|
+
return {
|
|
181
|
+
update(nextData) {
|
|
182
|
+
if (destroyed) return;
|
|
183
|
+
data = nextData;
|
|
184
|
+
renderCard$1();
|
|
185
|
+
},
|
|
186
|
+
destroy() {
|
|
187
|
+
if (destroyed) return;
|
|
188
|
+
destroyed = true;
|
|
189
|
+
(0, vue.render)(null, host);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
} };
|
|
193
|
+
}
|
|
140
194
|
function sanitizeOutput(html, sanitize) {
|
|
141
195
|
if (sanitize === false) return html;
|
|
142
196
|
return (0, __ai_gui_core.sanitizeHtml)(html, typeof sanitize === "object" ? sanitize : void 0);
|
|
@@ -146,18 +200,27 @@ function sanitizeOutput(html, sanitize) {
|
|
|
146
200
|
//#region src/render-node.ts
|
|
147
201
|
function renderNode(node, ctx) {
|
|
148
202
|
const r = (ctx.nodeRenderers ?? (0, __ai_gui_core.collectNodeRenderers)(ctx.plugins))[node.type];
|
|
149
|
-
if (r)
|
|
150
|
-
|
|
151
|
-
if (out && typeof out.then === "function") return (0, vue.h)(AsyncOutput, {
|
|
203
|
+
if (r) {
|
|
204
|
+
if (node.complete === false) return (0, vue.h)("div", {
|
|
152
205
|
key: node.key,
|
|
153
|
-
|
|
154
|
-
|
|
206
|
+
"data-aigui-block-loading": "",
|
|
207
|
+
"data-block-type": node.type
|
|
155
208
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
209
|
+
try {
|
|
210
|
+
const out = r(node);
|
|
211
|
+
const mountContext = createRenderMountContext(ctx.registry, ctx.onCardAction);
|
|
212
|
+
if (out && typeof out.then === "function") return (0, vue.h)(AsyncOutput, {
|
|
213
|
+
key: node.key,
|
|
214
|
+
promise: out,
|
|
215
|
+
sanitize: ctx.sanitize,
|
|
216
|
+
context: mountContext
|
|
217
|
+
});
|
|
218
|
+
const vnode = renderOutput(out, ctx.sanitize, mountContext);
|
|
219
|
+
vnode.key = node.key;
|
|
220
|
+
return vnode;
|
|
221
|
+
} catch {
|
|
222
|
+
return renderFallback(node, ctx);
|
|
223
|
+
}
|
|
161
224
|
}
|
|
162
225
|
switch (node.type) {
|
|
163
226
|
case "heading": return (0, vue.h)(node.tag ?? "h1", {
|
|
@@ -194,6 +257,19 @@ function renderCard(node, ctx) {
|
|
|
194
257
|
"data-aigui-card-invalid": ""
|
|
195
258
|
}, [(0, vue.h)("code", JSON.stringify(card.data, null, 2))]);
|
|
196
259
|
const Comp = ctx.registry?.getRender(card.type);
|
|
260
|
+
if (card.id && ctx.cardStore) return (0, vue.h)(CardHost, {
|
|
261
|
+
key: node.key,
|
|
262
|
+
component: Comp ? (0, vue.markRaw)((0, vue.toRaw)(Comp)) : void 0,
|
|
263
|
+
store: ctx.cardStore,
|
|
264
|
+
cardId: card.id,
|
|
265
|
+
cardType: card.type,
|
|
266
|
+
initialData: card.data,
|
|
267
|
+
onAction: (a) => ctx.onCardAction?.({
|
|
268
|
+
...a,
|
|
269
|
+
cardType: card.type,
|
|
270
|
+
cardId: card.id
|
|
271
|
+
})
|
|
272
|
+
});
|
|
197
273
|
if (!Comp) return (0, vue.h)("pre", {
|
|
198
274
|
key: node.key,
|
|
199
275
|
"data-aigui-card-fallback": ""
|
|
@@ -207,6 +283,85 @@ function renderCard(node, ctx) {
|
|
|
207
283
|
})
|
|
208
284
|
});
|
|
209
285
|
}
|
|
286
|
+
const CardHost = (0, vue.defineComponent)({
|
|
287
|
+
name: "AIGuiCardHost",
|
|
288
|
+
props: {
|
|
289
|
+
component: {
|
|
290
|
+
type: [Object, Function],
|
|
291
|
+
default: void 0
|
|
292
|
+
},
|
|
293
|
+
store: {
|
|
294
|
+
type: Object,
|
|
295
|
+
required: true
|
|
296
|
+
},
|
|
297
|
+
cardId: {
|
|
298
|
+
type: String,
|
|
299
|
+
required: true
|
|
300
|
+
},
|
|
301
|
+
cardType: {
|
|
302
|
+
type: String,
|
|
303
|
+
required: true
|
|
304
|
+
},
|
|
305
|
+
initialData: {
|
|
306
|
+
type: null,
|
|
307
|
+
required: true
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
emits: ["action"],
|
|
311
|
+
setup(props, { emit }) {
|
|
312
|
+
const record = (0, vue.shallowRef)();
|
|
313
|
+
const failed = (0, vue.shallowRef)(false);
|
|
314
|
+
let unsubscribe;
|
|
315
|
+
let stopWatch;
|
|
316
|
+
const bind = ([store, cardId, cardType, initialData]) => {
|
|
317
|
+
unsubscribe?.();
|
|
318
|
+
unsubscribe = void 0;
|
|
319
|
+
failed.value = false;
|
|
320
|
+
record.value = void 0;
|
|
321
|
+
unsubscribe = store.subscribe(cardId, (next) => {
|
|
322
|
+
if (!next || next.type !== cardType) {
|
|
323
|
+
failed.value = true;
|
|
324
|
+
record.value = void 0;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
failed.value = false;
|
|
328
|
+
record.value = next;
|
|
329
|
+
});
|
|
330
|
+
try {
|
|
331
|
+
const current = store.register({
|
|
332
|
+
id: cardId,
|
|
333
|
+
type: cardType,
|
|
334
|
+
data: initialData
|
|
335
|
+
});
|
|
336
|
+
if (current.type !== cardType) throw new Error(`Card type mismatch for "${cardId}"`);
|
|
337
|
+
record.value = current;
|
|
338
|
+
} catch {
|
|
339
|
+
failed.value = true;
|
|
340
|
+
record.value = void 0;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
(0, vue.onMounted)(() => {
|
|
344
|
+
stopWatch = (0, vue.watch)(() => [
|
|
345
|
+
props.store,
|
|
346
|
+
props.cardId,
|
|
347
|
+
props.cardType,
|
|
348
|
+
props.initialData
|
|
349
|
+
], bind, { immediate: true });
|
|
350
|
+
});
|
|
351
|
+
(0, vue.onBeforeUnmount)(() => {
|
|
352
|
+
stopWatch?.();
|
|
353
|
+
unsubscribe?.();
|
|
354
|
+
});
|
|
355
|
+
return () => {
|
|
356
|
+
if (failed.value || !record.value || !props.component) return (0, vue.h)("pre", { "data-aigui-card-fallback": "" }, [(0, vue.h)("code", JSON.stringify(record.value?.data ?? props.initialData, null, 2))]);
|
|
357
|
+
return (0, vue.h)((0, vue.markRaw)((0, vue.toRaw)(props.component)), {
|
|
358
|
+
data: record.value.data,
|
|
359
|
+
state: record.value.action,
|
|
360
|
+
onAction: (action) => emit("action", action)
|
|
361
|
+
});
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
});
|
|
210
365
|
function renderFallback(node, ctx) {
|
|
211
366
|
return (0, vue.h)("div", {
|
|
212
367
|
key: node.key,
|
|
@@ -236,7 +391,19 @@ const AIRenderer = (0, vue.defineComponent)({
|
|
|
236
391
|
type: [Boolean, Object],
|
|
237
392
|
default: void 0
|
|
238
393
|
},
|
|
239
|
-
|
|
394
|
+
actionRuntime: {
|
|
395
|
+
type: Object,
|
|
396
|
+
default: void 0
|
|
397
|
+
},
|
|
398
|
+
cardStore: {
|
|
399
|
+
type: Object,
|
|
400
|
+
default: void 0
|
|
401
|
+
},
|
|
402
|
+
debug: {
|
|
403
|
+
type: Boolean,
|
|
404
|
+
default: false
|
|
405
|
+
},
|
|
406
|
+
onDebugEvent: {
|
|
240
407
|
type: Function,
|
|
241
408
|
default: void 0
|
|
242
409
|
}
|
|
@@ -245,38 +412,77 @@ const AIRenderer = (0, vue.defineComponent)({
|
|
|
245
412
|
const current = (0, vue.shallowRef)(useAIRenderer({
|
|
246
413
|
registry: props.registry,
|
|
247
414
|
sanitize: props.sanitize,
|
|
248
|
-
plugins: props.plugins
|
|
415
|
+
plugins: props.plugins,
|
|
416
|
+
debug: props.debug,
|
|
417
|
+
onDebugEvent: props.onDebugEvent
|
|
249
418
|
}));
|
|
250
|
-
const nodeRenderers = (0, vue.shallowRef)((0, __ai_gui_core.collectNodeRenderers)(props.plugins));
|
|
419
|
+
const nodeRenderers = (0, vue.shallowRef)((0, __ai_gui_core.collectNodeRenderers)(props.plugins, { debugTarget: current.value.renderer }));
|
|
420
|
+
let actionScope = {
|
|
421
|
+
controller: new AbortController(),
|
|
422
|
+
owner: {}
|
|
423
|
+
};
|
|
424
|
+
const resetActionScope = () => {
|
|
425
|
+
actionScope.controller.abort();
|
|
426
|
+
actionScope = {
|
|
427
|
+
controller: new AbortController(),
|
|
428
|
+
owner: {}
|
|
429
|
+
};
|
|
430
|
+
};
|
|
251
431
|
(0, vue.watch)(() => [
|
|
252
432
|
props.registry,
|
|
253
433
|
props.sanitize,
|
|
254
|
-
props.plugins
|
|
434
|
+
props.plugins,
|
|
435
|
+
props.debug,
|
|
436
|
+
props.onDebugEvent
|
|
255
437
|
], () => {
|
|
438
|
+
resetActionScope();
|
|
256
439
|
current.value.destroy();
|
|
257
440
|
current.value = useAIRenderer({
|
|
258
441
|
registry: props.registry,
|
|
259
442
|
sanitize: props.sanitize,
|
|
260
|
-
plugins: props.plugins
|
|
443
|
+
plugins: props.plugins,
|
|
444
|
+
debug: props.debug,
|
|
445
|
+
onDebugEvent: props.onDebugEvent
|
|
261
446
|
});
|
|
262
|
-
nodeRenderers.value = (0, __ai_gui_core.collectNodeRenderers)(props.plugins);
|
|
447
|
+
nodeRenderers.value = (0, __ai_gui_core.collectNodeRenderers)(props.plugins, { debugTarget: current.value.renderer });
|
|
448
|
+
});
|
|
449
|
+
(0, vue.watch)(() => props.actionRuntime, resetActionScope);
|
|
450
|
+
(0, vue.onBeforeUnmount)(() => {
|
|
451
|
+
resetActionScope();
|
|
452
|
+
current.value.destroy();
|
|
263
453
|
});
|
|
264
|
-
(0, vue.onBeforeUnmount)(() => current.value.destroy());
|
|
265
454
|
const push = (chunk) => current.value.push(chunk);
|
|
266
455
|
const feed = (source, options) => current.value.feed(source, options);
|
|
267
|
-
const reset = () =>
|
|
456
|
+
const reset = () => {
|
|
457
|
+
resetActionScope();
|
|
458
|
+
current.value.reset();
|
|
459
|
+
};
|
|
268
460
|
expose({
|
|
461
|
+
debugSource: "renderer",
|
|
462
|
+
subscribeDebug: (listener) => current.value.renderer.subscribeDebug(listener),
|
|
269
463
|
push,
|
|
270
464
|
feed,
|
|
271
465
|
reset
|
|
272
466
|
});
|
|
273
467
|
return () => {
|
|
274
468
|
const onCardAction = (action) => {
|
|
275
|
-
props.
|
|
469
|
+
if (props.actionRuntime) {
|
|
470
|
+
const request = {
|
|
471
|
+
type: action.type,
|
|
472
|
+
params: action.params,
|
|
473
|
+
cardType: action.cardType,
|
|
474
|
+
...action.cardId === void 0 ? {} : { cardId: action.cardId }
|
|
475
|
+
};
|
|
476
|
+
props.actionRuntime.dispatch(request, {
|
|
477
|
+
signal: actionScope.controller.signal,
|
|
478
|
+
owner: actionScope.owner
|
|
479
|
+
}).catch(() => {});
|
|
480
|
+
}
|
|
276
481
|
emit("card-action", action);
|
|
277
482
|
};
|
|
278
483
|
const ctx = {
|
|
279
484
|
registry: props.registry,
|
|
485
|
+
cardStore: props.cardStore,
|
|
280
486
|
plugins: props.plugins,
|
|
281
487
|
nodeRenderers: nodeRenderers.value,
|
|
282
488
|
onCardAction,
|
|
@@ -292,4 +498,5 @@ const AIRenderer = (0, vue.defineComponent)({
|
|
|
292
498
|
exports.AIRenderer = AIRenderer
|
|
293
499
|
exports.renderNode = renderNode
|
|
294
500
|
exports.renderOutput = renderOutput
|
|
295
|
-
exports.useAIRenderer = useAIRenderer
|
|
501
|
+
exports.useAIRenderer = useAIRenderer
|
|
502
|
+
exports.useActionState = useActionState
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ShallowRef, VNode } from "vue";
|
|
2
|
-
import { AIGuiPlugin, ASTNode, CardRegistry, FeedOptions, FeedSource, NodeRenderer, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
2
|
+
import { AIGuiPlugin, ASTNode, ActionRuntime, ActionState, CardRegistry, CardStore, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, Renderer, RendererOptions } from "@ai-gui/core";
|
|
3
3
|
|
|
4
4
|
//#region src/use-ai-renderer.d.ts
|
|
5
5
|
interface UseAIRendererResult {
|
|
6
|
+
renderer: Renderer;
|
|
6
7
|
nodes: ShallowRef<ASTNode[]>;
|
|
7
8
|
push: (chunk: string) => void;
|
|
8
9
|
feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
|
|
@@ -11,6 +12,10 @@ interface UseAIRendererResult {
|
|
|
11
12
|
}
|
|
12
13
|
declare function useAIRenderer(options?: Omit<RendererOptions, "onPatch">): UseAIRendererResult;
|
|
13
14
|
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/use-action-state.d.ts
|
|
17
|
+
declare function useActionState(runtime: ActionRuntime, key: string): ShallowRef<ActionState>;
|
|
18
|
+
|
|
14
19
|
//#endregion
|
|
15
20
|
//#region src/ai-renderer.d.ts
|
|
16
21
|
declare const AIRenderer: any;
|
|
@@ -19,12 +24,14 @@ declare const AIRenderer: any;
|
|
|
19
24
|
//#region src/render-node.d.ts
|
|
20
25
|
interface RenderContext {
|
|
21
26
|
registry?: CardRegistry;
|
|
27
|
+
cardStore?: CardStore;
|
|
22
28
|
plugins?: AIGuiPlugin[];
|
|
23
29
|
nodeRenderers?: Record<string, NodeRenderer>;
|
|
24
30
|
onCardAction?: (action: {
|
|
25
31
|
type: string;
|
|
26
32
|
params?: unknown;
|
|
27
33
|
cardType: string;
|
|
34
|
+
cardId?: string;
|
|
28
35
|
}) => void;
|
|
29
36
|
sanitize?: RendererOptions["sanitize"];
|
|
30
37
|
sanitized?: boolean;
|
|
@@ -34,9 +41,9 @@ declare function renderNode(node: ASTNode, ctx: RenderContext): VNode;
|
|
|
34
41
|
//#endregion
|
|
35
42
|
//#region src/render-output.d.ts
|
|
36
43
|
/** Translate a framework-neutral RenderOutput into a Vue VNode. */
|
|
37
|
-
declare function renderOutput(out: RenderOutput, sanitize?: RendererOptions["sanitize"]): VNode;
|
|
44
|
+
declare function renderOutput(out: RenderOutput, sanitize?: RendererOptions["sanitize"], context?: RenderMountContext): VNode;
|
|
38
45
|
|
|
39
46
|
//#endregion
|
|
40
47
|
/** Await an async RenderOutput, rendering a placeholder until it resolves. */
|
|
41
48
|
|
|
42
|
-
export { AIRenderer, RenderContext, UseAIRendererResult, renderNode, renderOutput, useAIRenderer };
|
|
49
|
+
export { AIRenderer, RenderContext, UseAIRendererResult, renderNode, renderOutput, useAIRenderer, useActionState };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ShallowRef, VNode } from "vue";
|
|
2
|
-
import { AIGuiPlugin, ASTNode, CardRegistry, FeedOptions, FeedSource, NodeRenderer, RenderOutput, RendererOptions } from "@ai-gui/core";
|
|
2
|
+
import { AIGuiPlugin, ASTNode, ActionRuntime, ActionState, CardRegistry, CardStore, FeedOptions, FeedSource, NodeRenderer, RenderMountContext, RenderOutput, Renderer, RendererOptions } from "@ai-gui/core";
|
|
3
3
|
|
|
4
4
|
//#region src/use-ai-renderer.d.ts
|
|
5
5
|
interface UseAIRendererResult {
|
|
6
|
+
renderer: Renderer;
|
|
6
7
|
nodes: ShallowRef<ASTNode[]>;
|
|
7
8
|
push: (chunk: string) => void;
|
|
8
9
|
feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
|
|
@@ -11,6 +12,10 @@ interface UseAIRendererResult {
|
|
|
11
12
|
}
|
|
12
13
|
declare function useAIRenderer(options?: Omit<RendererOptions, "onPatch">): UseAIRendererResult;
|
|
13
14
|
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/use-action-state.d.ts
|
|
17
|
+
declare function useActionState(runtime: ActionRuntime, key: string): ShallowRef<ActionState>;
|
|
18
|
+
|
|
14
19
|
//#endregion
|
|
15
20
|
//#region src/ai-renderer.d.ts
|
|
16
21
|
declare const AIRenderer: any;
|
|
@@ -19,12 +24,14 @@ declare const AIRenderer: any;
|
|
|
19
24
|
//#region src/render-node.d.ts
|
|
20
25
|
interface RenderContext {
|
|
21
26
|
registry?: CardRegistry;
|
|
27
|
+
cardStore?: CardStore;
|
|
22
28
|
plugins?: AIGuiPlugin[];
|
|
23
29
|
nodeRenderers?: Record<string, NodeRenderer>;
|
|
24
30
|
onCardAction?: (action: {
|
|
25
31
|
type: string;
|
|
26
32
|
params?: unknown;
|
|
27
33
|
cardType: string;
|
|
34
|
+
cardId?: string;
|
|
28
35
|
}) => void;
|
|
29
36
|
sanitize?: RendererOptions["sanitize"];
|
|
30
37
|
sanitized?: boolean;
|
|
@@ -34,9 +41,9 @@ declare function renderNode(node: ASTNode, ctx: RenderContext): VNode;
|
|
|
34
41
|
//#endregion
|
|
35
42
|
//#region src/render-output.d.ts
|
|
36
43
|
/** Translate a framework-neutral RenderOutput into a Vue VNode. */
|
|
37
|
-
declare function renderOutput(out: RenderOutput, sanitize?: RendererOptions["sanitize"]): VNode;
|
|
44
|
+
declare function renderOutput(out: RenderOutput, sanitize?: RendererOptions["sanitize"], context?: RenderMountContext): VNode;
|
|
38
45
|
|
|
39
46
|
//#endregion
|
|
40
47
|
/** Await an async RenderOutput, rendering a placeholder until it resolves. */
|
|
41
48
|
|
|
42
|
-
export { AIRenderer, RenderContext, UseAIRendererResult, renderNode, renderOutput, useAIRenderer };
|
|
49
|
+
export { AIRenderer, RenderContext, UseAIRendererResult, renderNode, renderOutput, useAIRenderer, useActionState };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, h, markRaw, onBeforeUnmount, onMounted, ref, shallowRef, toRaw, watch } from "vue";
|
|
1
|
+
import { defineComponent, h, markRaw, onBeforeUnmount, onMounted, onScopeDispose, ref, render, shallowRef, toRaw, watch } from "vue";
|
|
2
2
|
import { Renderer, collectNodeRenderers, sanitizeHtml } from "@ai-gui/core";
|
|
3
3
|
|
|
4
4
|
//#region src/use-ai-renderer.ts
|
|
@@ -13,6 +13,7 @@ function useAIRenderer(options = {}) {
|
|
|
13
13
|
}
|
|
14
14
|
});
|
|
15
15
|
return {
|
|
16
|
+
renderer,
|
|
16
17
|
nodes,
|
|
17
18
|
push: (c) => {
|
|
18
19
|
if (active) renderer.push(c);
|
|
@@ -32,24 +33,41 @@ function useAIRenderer(options = {}) {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/use-action-state.ts
|
|
38
|
+
function useActionState(runtime, key) {
|
|
39
|
+
const state = shallowRef(runtime.getState(key));
|
|
40
|
+
const unsubscribe = runtime.subscribe((next) => {
|
|
41
|
+
if (next.key === key) state.value = next;
|
|
42
|
+
});
|
|
43
|
+
onScopeDispose(unsubscribe);
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
//#endregion
|
|
36
48
|
//#region src/render-output.ts
|
|
37
49
|
/** Host element for imperative `mount` outputs, wired to Vue's lifecycle. */
|
|
38
50
|
const MountHost = defineComponent({
|
|
39
51
|
name: "MountHost",
|
|
40
|
-
props: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
props: {
|
|
53
|
+
mount: {
|
|
54
|
+
type: Function,
|
|
55
|
+
required: true
|
|
56
|
+
},
|
|
57
|
+
context: {
|
|
58
|
+
type: Object,
|
|
59
|
+
required: true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
44
62
|
setup(props) {
|
|
45
63
|
const elRef = ref(null);
|
|
46
64
|
let cleanup;
|
|
47
65
|
onMounted(() => {
|
|
48
|
-
if (elRef.value) cleanup = props.mount(elRef.value);
|
|
66
|
+
if (elRef.value) cleanup = props.mount(elRef.value, props.context);
|
|
49
67
|
});
|
|
50
68
|
watch(() => props.mount, (mount) => {
|
|
51
69
|
if (typeof cleanup === "function") cleanup();
|
|
52
|
-
cleanup = elRef.value ? mount(elRef.value) : void 0;
|
|
70
|
+
cleanup = elRef.value ? mount(elRef.value, props.context) : void 0;
|
|
53
71
|
});
|
|
54
72
|
onBeforeUnmount(() => {
|
|
55
73
|
if (typeof cleanup === "function") cleanup();
|
|
@@ -61,11 +79,14 @@ const MountHost = defineComponent({
|
|
|
61
79
|
}
|
|
62
80
|
});
|
|
63
81
|
/** Translate a framework-neutral RenderOutput into a Vue VNode. */
|
|
64
|
-
function renderOutput(out, sanitize) {
|
|
82
|
+
function renderOutput(out, sanitize, context = {}) {
|
|
65
83
|
switch (out.kind) {
|
|
66
84
|
case "html": return h("div", { innerHTML: sanitizeOutput(out.html, sanitize) });
|
|
67
|
-
case "element": return h(out.tag, out.props, (out.children ?? []).map((child) => renderOutput(child, sanitize)));
|
|
68
|
-
case "mount": return h(MountHost, {
|
|
85
|
+
case "element": return h(out.tag, out.props, (out.children ?? []).map((child) => renderOutput(child, sanitize, context)));
|
|
86
|
+
case "mount": return h(MountHost, {
|
|
87
|
+
mount: out.mount,
|
|
88
|
+
context
|
|
89
|
+
});
|
|
69
90
|
case "card": return h("pre", { "data-aigui-card-fallback": "" }, [h("code", JSON.stringify(out.data, null, 2))]);
|
|
70
91
|
}
|
|
71
92
|
}
|
|
@@ -80,6 +101,10 @@ const AsyncOutput = defineComponent({
|
|
|
80
101
|
sanitize: {
|
|
81
102
|
type: [Boolean, Object],
|
|
82
103
|
default: void 0
|
|
104
|
+
},
|
|
105
|
+
context: {
|
|
106
|
+
type: Object,
|
|
107
|
+
default: () => ({})
|
|
83
108
|
}
|
|
84
109
|
},
|
|
85
110
|
setup(props) {
|
|
@@ -106,13 +131,42 @@ const AsyncOutput = defineComponent({
|
|
|
106
131
|
if (failed.value) return h("span", { "data-aigui-async-error": "" });
|
|
107
132
|
if (!resolved.value) return h("span", { "data-aigui-async-pending": "" });
|
|
108
133
|
try {
|
|
109
|
-
return renderOutput(resolved.value, props.sanitize);
|
|
134
|
+
return renderOutput(resolved.value, props.sanitize, props.context);
|
|
110
135
|
} catch {
|
|
111
136
|
return h("span", { "data-aigui-async-error": "" });
|
|
112
137
|
}
|
|
113
138
|
};
|
|
114
139
|
}
|
|
115
140
|
});
|
|
141
|
+
function createRenderMountContext(registry, onCardAction) {
|
|
142
|
+
return { mountCard(host, request) {
|
|
143
|
+
const component = registry?.getRender(request.type);
|
|
144
|
+
if (!component) return void 0;
|
|
145
|
+
const card = markRaw(toRaw(component));
|
|
146
|
+
let data = request.data;
|
|
147
|
+
let destroyed = false;
|
|
148
|
+
const renderCard$1 = () => render(h(card, {
|
|
149
|
+
data,
|
|
150
|
+
onAction: (action) => onCardAction?.({
|
|
151
|
+
...action,
|
|
152
|
+
cardType: request.type
|
|
153
|
+
})
|
|
154
|
+
}), host);
|
|
155
|
+
renderCard$1();
|
|
156
|
+
return {
|
|
157
|
+
update(nextData) {
|
|
158
|
+
if (destroyed) return;
|
|
159
|
+
data = nextData;
|
|
160
|
+
renderCard$1();
|
|
161
|
+
},
|
|
162
|
+
destroy() {
|
|
163
|
+
if (destroyed) return;
|
|
164
|
+
destroyed = true;
|
|
165
|
+
render(null, host);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
} };
|
|
169
|
+
}
|
|
116
170
|
function sanitizeOutput(html, sanitize) {
|
|
117
171
|
if (sanitize === false) return html;
|
|
118
172
|
return sanitizeHtml(html, typeof sanitize === "object" ? sanitize : void 0);
|
|
@@ -122,18 +176,27 @@ function sanitizeOutput(html, sanitize) {
|
|
|
122
176
|
//#region src/render-node.ts
|
|
123
177
|
function renderNode(node, ctx) {
|
|
124
178
|
const r = (ctx.nodeRenderers ?? collectNodeRenderers(ctx.plugins))[node.type];
|
|
125
|
-
if (r)
|
|
126
|
-
|
|
127
|
-
if (out && typeof out.then === "function") return h(AsyncOutput, {
|
|
179
|
+
if (r) {
|
|
180
|
+
if (node.complete === false) return h("div", {
|
|
128
181
|
key: node.key,
|
|
129
|
-
|
|
130
|
-
|
|
182
|
+
"data-aigui-block-loading": "",
|
|
183
|
+
"data-block-type": node.type
|
|
131
184
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
185
|
+
try {
|
|
186
|
+
const out = r(node);
|
|
187
|
+
const mountContext = createRenderMountContext(ctx.registry, ctx.onCardAction);
|
|
188
|
+
if (out && typeof out.then === "function") return h(AsyncOutput, {
|
|
189
|
+
key: node.key,
|
|
190
|
+
promise: out,
|
|
191
|
+
sanitize: ctx.sanitize,
|
|
192
|
+
context: mountContext
|
|
193
|
+
});
|
|
194
|
+
const vnode = renderOutput(out, ctx.sanitize, mountContext);
|
|
195
|
+
vnode.key = node.key;
|
|
196
|
+
return vnode;
|
|
197
|
+
} catch {
|
|
198
|
+
return renderFallback(node, ctx);
|
|
199
|
+
}
|
|
137
200
|
}
|
|
138
201
|
switch (node.type) {
|
|
139
202
|
case "heading": return h(node.tag ?? "h1", {
|
|
@@ -170,6 +233,19 @@ function renderCard(node, ctx) {
|
|
|
170
233
|
"data-aigui-card-invalid": ""
|
|
171
234
|
}, [h("code", JSON.stringify(card.data, null, 2))]);
|
|
172
235
|
const Comp = ctx.registry?.getRender(card.type);
|
|
236
|
+
if (card.id && ctx.cardStore) return h(CardHost, {
|
|
237
|
+
key: node.key,
|
|
238
|
+
component: Comp ? markRaw(toRaw(Comp)) : void 0,
|
|
239
|
+
store: ctx.cardStore,
|
|
240
|
+
cardId: card.id,
|
|
241
|
+
cardType: card.type,
|
|
242
|
+
initialData: card.data,
|
|
243
|
+
onAction: (a) => ctx.onCardAction?.({
|
|
244
|
+
...a,
|
|
245
|
+
cardType: card.type,
|
|
246
|
+
cardId: card.id
|
|
247
|
+
})
|
|
248
|
+
});
|
|
173
249
|
if (!Comp) return h("pre", {
|
|
174
250
|
key: node.key,
|
|
175
251
|
"data-aigui-card-fallback": ""
|
|
@@ -183,6 +259,85 @@ function renderCard(node, ctx) {
|
|
|
183
259
|
})
|
|
184
260
|
});
|
|
185
261
|
}
|
|
262
|
+
const CardHost = defineComponent({
|
|
263
|
+
name: "AIGuiCardHost",
|
|
264
|
+
props: {
|
|
265
|
+
component: {
|
|
266
|
+
type: [Object, Function],
|
|
267
|
+
default: void 0
|
|
268
|
+
},
|
|
269
|
+
store: {
|
|
270
|
+
type: Object,
|
|
271
|
+
required: true
|
|
272
|
+
},
|
|
273
|
+
cardId: {
|
|
274
|
+
type: String,
|
|
275
|
+
required: true
|
|
276
|
+
},
|
|
277
|
+
cardType: {
|
|
278
|
+
type: String,
|
|
279
|
+
required: true
|
|
280
|
+
},
|
|
281
|
+
initialData: {
|
|
282
|
+
type: null,
|
|
283
|
+
required: true
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
emits: ["action"],
|
|
287
|
+
setup(props, { emit }) {
|
|
288
|
+
const record = shallowRef();
|
|
289
|
+
const failed = shallowRef(false);
|
|
290
|
+
let unsubscribe;
|
|
291
|
+
let stopWatch;
|
|
292
|
+
const bind = ([store, cardId, cardType, initialData]) => {
|
|
293
|
+
unsubscribe?.();
|
|
294
|
+
unsubscribe = void 0;
|
|
295
|
+
failed.value = false;
|
|
296
|
+
record.value = void 0;
|
|
297
|
+
unsubscribe = store.subscribe(cardId, (next) => {
|
|
298
|
+
if (!next || next.type !== cardType) {
|
|
299
|
+
failed.value = true;
|
|
300
|
+
record.value = void 0;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
failed.value = false;
|
|
304
|
+
record.value = next;
|
|
305
|
+
});
|
|
306
|
+
try {
|
|
307
|
+
const current = store.register({
|
|
308
|
+
id: cardId,
|
|
309
|
+
type: cardType,
|
|
310
|
+
data: initialData
|
|
311
|
+
});
|
|
312
|
+
if (current.type !== cardType) throw new Error(`Card type mismatch for "${cardId}"`);
|
|
313
|
+
record.value = current;
|
|
314
|
+
} catch {
|
|
315
|
+
failed.value = true;
|
|
316
|
+
record.value = void 0;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
onMounted(() => {
|
|
320
|
+
stopWatch = watch(() => [
|
|
321
|
+
props.store,
|
|
322
|
+
props.cardId,
|
|
323
|
+
props.cardType,
|
|
324
|
+
props.initialData
|
|
325
|
+
], bind, { immediate: true });
|
|
326
|
+
});
|
|
327
|
+
onBeforeUnmount(() => {
|
|
328
|
+
stopWatch?.();
|
|
329
|
+
unsubscribe?.();
|
|
330
|
+
});
|
|
331
|
+
return () => {
|
|
332
|
+
if (failed.value || !record.value || !props.component) return h("pre", { "data-aigui-card-fallback": "" }, [h("code", JSON.stringify(record.value?.data ?? props.initialData, null, 2))]);
|
|
333
|
+
return h(markRaw(toRaw(props.component)), {
|
|
334
|
+
data: record.value.data,
|
|
335
|
+
state: record.value.action,
|
|
336
|
+
onAction: (action) => emit("action", action)
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
});
|
|
186
341
|
function renderFallback(node, ctx) {
|
|
187
342
|
return h("div", {
|
|
188
343
|
key: node.key,
|
|
@@ -212,7 +367,19 @@ const AIRenderer = defineComponent({
|
|
|
212
367
|
type: [Boolean, Object],
|
|
213
368
|
default: void 0
|
|
214
369
|
},
|
|
215
|
-
|
|
370
|
+
actionRuntime: {
|
|
371
|
+
type: Object,
|
|
372
|
+
default: void 0
|
|
373
|
+
},
|
|
374
|
+
cardStore: {
|
|
375
|
+
type: Object,
|
|
376
|
+
default: void 0
|
|
377
|
+
},
|
|
378
|
+
debug: {
|
|
379
|
+
type: Boolean,
|
|
380
|
+
default: false
|
|
381
|
+
},
|
|
382
|
+
onDebugEvent: {
|
|
216
383
|
type: Function,
|
|
217
384
|
default: void 0
|
|
218
385
|
}
|
|
@@ -221,38 +388,77 @@ const AIRenderer = defineComponent({
|
|
|
221
388
|
const current = shallowRef(useAIRenderer({
|
|
222
389
|
registry: props.registry,
|
|
223
390
|
sanitize: props.sanitize,
|
|
224
|
-
plugins: props.plugins
|
|
391
|
+
plugins: props.plugins,
|
|
392
|
+
debug: props.debug,
|
|
393
|
+
onDebugEvent: props.onDebugEvent
|
|
225
394
|
}));
|
|
226
|
-
const nodeRenderers = shallowRef(collectNodeRenderers(props.plugins));
|
|
395
|
+
const nodeRenderers = shallowRef(collectNodeRenderers(props.plugins, { debugTarget: current.value.renderer }));
|
|
396
|
+
let actionScope = {
|
|
397
|
+
controller: new AbortController(),
|
|
398
|
+
owner: {}
|
|
399
|
+
};
|
|
400
|
+
const resetActionScope = () => {
|
|
401
|
+
actionScope.controller.abort();
|
|
402
|
+
actionScope = {
|
|
403
|
+
controller: new AbortController(),
|
|
404
|
+
owner: {}
|
|
405
|
+
};
|
|
406
|
+
};
|
|
227
407
|
watch(() => [
|
|
228
408
|
props.registry,
|
|
229
409
|
props.sanitize,
|
|
230
|
-
props.plugins
|
|
410
|
+
props.plugins,
|
|
411
|
+
props.debug,
|
|
412
|
+
props.onDebugEvent
|
|
231
413
|
], () => {
|
|
414
|
+
resetActionScope();
|
|
232
415
|
current.value.destroy();
|
|
233
416
|
current.value = useAIRenderer({
|
|
234
417
|
registry: props.registry,
|
|
235
418
|
sanitize: props.sanitize,
|
|
236
|
-
plugins: props.plugins
|
|
419
|
+
plugins: props.plugins,
|
|
420
|
+
debug: props.debug,
|
|
421
|
+
onDebugEvent: props.onDebugEvent
|
|
237
422
|
});
|
|
238
|
-
nodeRenderers.value = collectNodeRenderers(props.plugins);
|
|
423
|
+
nodeRenderers.value = collectNodeRenderers(props.plugins, { debugTarget: current.value.renderer });
|
|
424
|
+
});
|
|
425
|
+
watch(() => props.actionRuntime, resetActionScope);
|
|
426
|
+
onBeforeUnmount(() => {
|
|
427
|
+
resetActionScope();
|
|
428
|
+
current.value.destroy();
|
|
239
429
|
});
|
|
240
|
-
onBeforeUnmount(() => current.value.destroy());
|
|
241
430
|
const push = (chunk) => current.value.push(chunk);
|
|
242
431
|
const feed = (source, options) => current.value.feed(source, options);
|
|
243
|
-
const reset = () =>
|
|
432
|
+
const reset = () => {
|
|
433
|
+
resetActionScope();
|
|
434
|
+
current.value.reset();
|
|
435
|
+
};
|
|
244
436
|
expose({
|
|
437
|
+
debugSource: "renderer",
|
|
438
|
+
subscribeDebug: (listener) => current.value.renderer.subscribeDebug(listener),
|
|
245
439
|
push,
|
|
246
440
|
feed,
|
|
247
441
|
reset
|
|
248
442
|
});
|
|
249
443
|
return () => {
|
|
250
444
|
const onCardAction = (action) => {
|
|
251
|
-
props.
|
|
445
|
+
if (props.actionRuntime) {
|
|
446
|
+
const request = {
|
|
447
|
+
type: action.type,
|
|
448
|
+
params: action.params,
|
|
449
|
+
cardType: action.cardType,
|
|
450
|
+
...action.cardId === void 0 ? {} : { cardId: action.cardId }
|
|
451
|
+
};
|
|
452
|
+
props.actionRuntime.dispatch(request, {
|
|
453
|
+
signal: actionScope.controller.signal,
|
|
454
|
+
owner: actionScope.owner
|
|
455
|
+
}).catch(() => {});
|
|
456
|
+
}
|
|
252
457
|
emit("card-action", action);
|
|
253
458
|
};
|
|
254
459
|
const ctx = {
|
|
255
460
|
registry: props.registry,
|
|
461
|
+
cardStore: props.cardStore,
|
|
256
462
|
plugins: props.plugins,
|
|
257
463
|
nodeRenderers: nodeRenderers.value,
|
|
258
464
|
onCardAction,
|
|
@@ -265,4 +471,4 @@ const AIRenderer = defineComponent({
|
|
|
265
471
|
});
|
|
266
472
|
|
|
267
473
|
//#endregion
|
|
268
|
-
export { AIRenderer, renderNode, renderOutput, useAIRenderer };
|
|
474
|
+
export { AIRenderer, renderNode, renderOutput, useAIRenderer, useActionState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Vue 3 adapter for AIGUI — stream LLM output into live Vue components with cards and plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@ai-gui/core": "0.
|
|
54
|
+
"@ai-gui/core": "0.4.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"vue": ">=3.3"
|