@ai-gui/vue 0.1.0 → 0.2.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 CHANGED
@@ -28,18 +28,28 @@ const __ai_gui_core = __toESM(require("@ai-gui/core"));
28
28
  //#region src/use-ai-renderer.ts
29
29
  function useAIRenderer(options = {}) {
30
30
  const nodes = (0, vue.shallowRef)([]);
31
+ let active = true;
31
32
  const renderer = new __ai_gui_core.Renderer({
32
33
  ...options,
33
34
  plugins: options.plugins,
34
35
  onPatch: (_patches, snapshot) => {
35
- nodes.value = snapshot;
36
+ if (active) nodes.value = snapshot;
36
37
  }
37
38
  });
38
39
  return {
39
40
  nodes,
40
- push: (c) => renderer.push(c),
41
- feed: (s) => renderer.feed(s),
41
+ push: (c) => {
42
+ if (active) renderer.push(c);
43
+ },
44
+ feed: (source, feedOptions) => active ? renderer.feed(source, feedOptions) : Promise.resolve(),
42
45
  reset: () => {
46
+ if (active) {
47
+ renderer.reset();
48
+ nodes.value = [];
49
+ }
50
+ },
51
+ destroy: () => {
52
+ active = false;
43
53
  renderer.reset();
44
54
  nodes.value = [];
45
55
  }
@@ -61,6 +71,10 @@ const MountHost = (0, vue.defineComponent)({
61
71
  (0, vue.onMounted)(() => {
62
72
  if (elRef.value) cleanup = props.mount(elRef.value);
63
73
  });
74
+ (0, vue.watch)(() => props.mount, (mount) => {
75
+ if (typeof cleanup === "function") cleanup();
76
+ cleanup = elRef.value ? mount(elRef.value) : void 0;
77
+ });
64
78
  (0, vue.onBeforeUnmount)(() => {
65
79
  if (typeof cleanup === "function") cleanup();
66
80
  });
@@ -71,10 +85,10 @@ const MountHost = (0, vue.defineComponent)({
71
85
  }
72
86
  });
73
87
  /** Translate a framework-neutral RenderOutput into a Vue VNode. */
74
- function renderOutput(out) {
88
+ function renderOutput(out, sanitize) {
75
89
  switch (out.kind) {
76
- case "html": return (0, vue.h)("div", { innerHTML: (0, __ai_gui_core.sanitizeHtml)(out.html) });
77
- case "element": return (0, vue.h)(out.tag, out.props, (out.children ?? []).map(renderOutput));
90
+ 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)));
78
92
  case "mount": return (0, vue.h)(MountHost, { mount: out.mount });
79
93
  case "card": return (0, vue.h)("pre", { "data-aigui-card-fallback": "" }, [(0, vue.h)("code", JSON.stringify(out.data, null, 2))]);
80
94
  }
@@ -82,39 +96,77 @@ function renderOutput(out) {
82
96
  /** Await an async RenderOutput, rendering a placeholder until it resolves. */
83
97
  const AsyncOutput = (0, vue.defineComponent)({
84
98
  name: "AsyncOutput",
85
- props: { promise: {
86
- type: Object,
87
- required: true
88
- } },
99
+ props: {
100
+ promise: {
101
+ type: Object,
102
+ required: true
103
+ },
104
+ sanitize: {
105
+ type: [Boolean, Object],
106
+ default: void 0
107
+ }
108
+ },
89
109
  setup(props) {
90
110
  const resolved = (0, vue.ref)(null);
91
- props.promise.then((v) => {
92
- resolved.value = v;
111
+ const failed = (0, vue.ref)(false);
112
+ let generation = 0;
113
+ let active = true;
114
+ const awaitPromise = (promise) => {
115
+ const current = ++generation;
116
+ resolved.value = null;
117
+ failed.value = false;
118
+ promise.then((value) => {
119
+ if (active && current === generation) resolved.value = value;
120
+ }, () => {
121
+ if (active && current === generation) failed.value = true;
122
+ });
123
+ };
124
+ (0, vue.watch)(() => [(0, vue.toRaw)(props.promise)], ([promise]) => awaitPromise(promise), { immediate: true });
125
+ (0, vue.onBeforeUnmount)(() => {
126
+ active = false;
127
+ generation++;
93
128
  });
94
- return () => resolved.value ? renderOutput(resolved.value) : (0, vue.h)("span", { "data-aigui-async-pending": "" });
129
+ return () => {
130
+ if (failed.value) return (0, vue.h)("span", { "data-aigui-async-error": "" });
131
+ if (!resolved.value) return (0, vue.h)("span", { "data-aigui-async-pending": "" });
132
+ try {
133
+ return renderOutput(resolved.value, props.sanitize);
134
+ } catch {
135
+ return (0, vue.h)("span", { "data-aigui-async-error": "" });
136
+ }
137
+ };
95
138
  }
96
139
  });
140
+ function sanitizeOutput(html, sanitize) {
141
+ if (sanitize === false) return html;
142
+ return (0, __ai_gui_core.sanitizeHtml)(html, typeof sanitize === "object" ? sanitize : void 0);
143
+ }
97
144
 
98
145
  //#endregion
99
146
  //#region src/render-node.ts
100
147
  function renderNode(node, ctx) {
101
- const r = (0, __ai_gui_core.collectNodeRenderers)(ctx.plugins)[node.type];
102
- if (r) {
148
+ const r = (ctx.nodeRenderers ?? (0, __ai_gui_core.collectNodeRenderers)(ctx.plugins))[node.type];
149
+ if (r) try {
103
150
  const out = r(node);
104
151
  if (out && typeof out.then === "function") return (0, vue.h)(AsyncOutput, {
105
152
  key: node.key,
106
- promise: out
153
+ promise: out,
154
+ sanitize: ctx.sanitize
107
155
  });
108
- return renderOutput(out);
156
+ const vnode = renderOutput(out, ctx.sanitize);
157
+ vnode.key = node.key;
158
+ return vnode;
159
+ } catch {
160
+ return renderFallback(node, ctx);
109
161
  }
110
162
  switch (node.type) {
111
163
  case "heading": return (0, vue.h)(node.tag ?? "h1", {
112
164
  key: node.key,
113
- innerHTML: node.html ?? ""
165
+ innerHTML: renderHtml(node.html ?? "", ctx)
114
166
  });
115
167
  case "paragraph": return (0, vue.h)("p", {
116
168
  key: node.key,
117
- innerHTML: node.html ?? ""
169
+ innerHTML: renderHtml(node.html ?? "", ctx)
118
170
  });
119
171
  case "code": return (0, vue.h)("pre", {
120
172
  key: node.key,
@@ -123,13 +175,10 @@ function renderNode(node, ctx) {
123
175
  case "hr": return (0, vue.h)("hr", { key: node.key });
124
176
  case "html": return (0, vue.h)("div", {
125
177
  key: node.key,
126
- innerHTML: node.content ?? ""
178
+ innerHTML: renderHtml(node.content ?? "", ctx)
127
179
  });
128
180
  case "card": return renderCard(node, ctx);
129
- default: return (0, vue.h)("div", {
130
- key: node.key,
131
- innerHTML: node.html ?? (0, __ai_gui_core.sanitizeHtml)(node.content ?? "")
132
- });
181
+ default: return renderFallback(node, ctx);
133
182
  }
134
183
  }
135
184
  function renderCard(node, ctx) {
@@ -149,7 +198,7 @@ function renderCard(node, ctx) {
149
198
  key: node.key,
150
199
  "data-aigui-card-fallback": ""
151
200
  }, [(0, vue.h)("code", JSON.stringify(card.data, null, 2))]);
152
- return (0, vue.h)(Comp, {
201
+ return (0, vue.h)((0, vue.markRaw)((0, vue.toRaw)(Comp)), {
153
202
  key: node.key,
154
203
  data: card.data,
155
204
  onAction: (a) => ctx.onCardAction?.({
@@ -158,11 +207,22 @@ function renderCard(node, ctx) {
158
207
  })
159
208
  });
160
209
  }
210
+ function renderFallback(node, ctx) {
211
+ return (0, vue.h)("div", {
212
+ key: node.key,
213
+ innerHTML: renderHtml(node.html ?? node.content ?? "", ctx)
214
+ });
215
+ }
216
+ function renderHtml(html, ctx) {
217
+ if (ctx.sanitized || ctx.sanitize === false) return html;
218
+ return (0, __ai_gui_core.sanitizeHtml)(html, typeof ctx.sanitize === "object" ? ctx.sanitize : void 0);
219
+ }
161
220
 
162
221
  //#endregion
163
222
  //#region src/ai-renderer.ts
164
223
  const AIRenderer = (0, vue.defineComponent)({
165
224
  name: "AIRenderer",
225
+ emits: ["card-action"],
166
226
  props: {
167
227
  registry: {
168
228
  type: Object,
@@ -173,7 +233,7 @@ const AIRenderer = (0, vue.defineComponent)({
173
233
  default: void 0
174
234
  },
175
235
  sanitize: {
176
- type: Boolean,
236
+ type: [Boolean, Object],
177
237
  default: void 0
178
238
  },
179
239
  onCardAction: {
@@ -181,24 +241,49 @@ const AIRenderer = (0, vue.defineComponent)({
181
241
  default: void 0
182
242
  }
183
243
  },
184
- setup(props, { expose }) {
185
- const { nodes, push, feed, reset } = useAIRenderer({
244
+ setup(props, { emit, expose }) {
245
+ const current = (0, vue.shallowRef)(useAIRenderer({
186
246
  registry: props.registry,
187
247
  sanitize: props.sanitize,
188
248
  plugins: props.plugins
249
+ }));
250
+ const nodeRenderers = (0, vue.shallowRef)((0, __ai_gui_core.collectNodeRenderers)(props.plugins));
251
+ (0, vue.watch)(() => [
252
+ props.registry,
253
+ props.sanitize,
254
+ props.plugins
255
+ ], () => {
256
+ current.value.destroy();
257
+ current.value = useAIRenderer({
258
+ registry: props.registry,
259
+ sanitize: props.sanitize,
260
+ plugins: props.plugins
261
+ });
262
+ nodeRenderers.value = (0, __ai_gui_core.collectNodeRenderers)(props.plugins);
189
263
  });
264
+ (0, vue.onBeforeUnmount)(() => current.value.destroy());
265
+ const push = (chunk) => current.value.push(chunk);
266
+ const feed = (source, options) => current.value.feed(source, options);
267
+ const reset = () => current.value.reset();
190
268
  expose({
191
269
  push,
192
270
  feed,
193
271
  reset
194
272
  });
195
273
  return () => {
274
+ const onCardAction = (action) => {
275
+ props.onCardAction?.(action);
276
+ emit("card-action", action);
277
+ };
196
278
  const ctx = {
197
279
  registry: props.registry,
198
280
  plugins: props.plugins,
199
- onCardAction: props.onCardAction
281
+ nodeRenderers: nodeRenderers.value,
282
+ onCardAction,
283
+ sanitize: props.sanitize,
284
+ sanitized: true
200
285
  };
201
- return (0, vue.h)("div", { "data-aigui-renderer": "" }, nodes.value.map((n) => renderNode(n, ctx)));
286
+ return (0, vue.h)("div", { "data-aigui-renderer": "" }, current.value.nodes.value.map((n) => renderNode(n, ctx)));
202
287
  };
203
288
  }
204
289
  });
package/dist/index.d.cts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { ShallowRef, VNode } from "vue";
2
- import { AIGuiPlugin, ASTNode, CardRegistry, RenderOutput, RendererOptions } from "@ai-gui/core";
2
+ import { AIGuiPlugin, ASTNode, CardRegistry, FeedOptions, FeedSource, NodeRenderer, RenderOutput, RendererOptions } from "@ai-gui/core";
3
3
 
4
4
  //#region src/use-ai-renderer.d.ts
5
5
  interface UseAIRendererResult {
6
6
  nodes: ShallowRef<ASTNode[]>;
7
7
  push: (chunk: string) => void;
8
- feed: (source: AsyncIterable<string> | ReadableStream) => Promise<void>;
8
+ feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
9
9
  reset: () => void;
10
+ destroy: () => void;
10
11
  }
11
12
  declare function useAIRenderer(options?: Omit<RendererOptions, "onPatch">): UseAIRendererResult;
12
13
 
@@ -19,18 +20,21 @@ declare const AIRenderer: any;
19
20
  interface RenderContext {
20
21
  registry?: CardRegistry;
21
22
  plugins?: AIGuiPlugin[];
23
+ nodeRenderers?: Record<string, NodeRenderer>;
22
24
  onCardAction?: (action: {
23
25
  type: string;
24
26
  params?: unknown;
25
27
  cardType: string;
26
28
  }) => void;
29
+ sanitize?: RendererOptions["sanitize"];
30
+ sanitized?: boolean;
27
31
  }
28
32
  declare function renderNode(node: ASTNode, ctx: RenderContext): VNode;
29
33
 
30
34
  //#endregion
31
35
  //#region src/render-output.d.ts
32
36
  /** Translate a framework-neutral RenderOutput into a Vue VNode. */
33
- declare function renderOutput(out: RenderOutput): VNode;
37
+ declare function renderOutput(out: RenderOutput, sanitize?: RendererOptions["sanitize"]): VNode;
34
38
 
35
39
  //#endregion
36
40
  /** Await an async RenderOutput, rendering a placeholder until it resolves. */
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { ShallowRef, VNode } from "vue";
2
- import { AIGuiPlugin, ASTNode, CardRegistry, RenderOutput, RendererOptions } from "@ai-gui/core";
2
+ import { AIGuiPlugin, ASTNode, CardRegistry, FeedOptions, FeedSource, NodeRenderer, RenderOutput, RendererOptions } from "@ai-gui/core";
3
3
 
4
4
  //#region src/use-ai-renderer.d.ts
5
5
  interface UseAIRendererResult {
6
6
  nodes: ShallowRef<ASTNode[]>;
7
7
  push: (chunk: string) => void;
8
- feed: (source: AsyncIterable<string> | ReadableStream) => Promise<void>;
8
+ feed: (source: FeedSource, options?: FeedOptions) => Promise<void>;
9
9
  reset: () => void;
10
+ destroy: () => void;
10
11
  }
11
12
  declare function useAIRenderer(options?: Omit<RendererOptions, "onPatch">): UseAIRendererResult;
12
13
 
@@ -19,18 +20,21 @@ declare const AIRenderer: any;
19
20
  interface RenderContext {
20
21
  registry?: CardRegistry;
21
22
  plugins?: AIGuiPlugin[];
23
+ nodeRenderers?: Record<string, NodeRenderer>;
22
24
  onCardAction?: (action: {
23
25
  type: string;
24
26
  params?: unknown;
25
27
  cardType: string;
26
28
  }) => void;
29
+ sanitize?: RendererOptions["sanitize"];
30
+ sanitized?: boolean;
27
31
  }
28
32
  declare function renderNode(node: ASTNode, ctx: RenderContext): VNode;
29
33
 
30
34
  //#endregion
31
35
  //#region src/render-output.d.ts
32
36
  /** Translate a framework-neutral RenderOutput into a Vue VNode. */
33
- declare function renderOutput(out: RenderOutput): VNode;
37
+ declare function renderOutput(out: RenderOutput, sanitize?: RendererOptions["sanitize"]): VNode;
34
38
 
35
39
  //#endregion
36
40
  /** Await an async RenderOutput, rendering a placeholder until it resolves. */
package/dist/index.js CHANGED
@@ -1,21 +1,31 @@
1
- import { defineComponent, h, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
1
+ import { defineComponent, h, markRaw, onBeforeUnmount, onMounted, ref, 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
5
5
  function useAIRenderer(options = {}) {
6
6
  const nodes = shallowRef([]);
7
+ let active = true;
7
8
  const renderer = new Renderer({
8
9
  ...options,
9
10
  plugins: options.plugins,
10
11
  onPatch: (_patches, snapshot) => {
11
- nodes.value = snapshot;
12
+ if (active) nodes.value = snapshot;
12
13
  }
13
14
  });
14
15
  return {
15
16
  nodes,
16
- push: (c) => renderer.push(c),
17
- feed: (s) => renderer.feed(s),
17
+ push: (c) => {
18
+ if (active) renderer.push(c);
19
+ },
20
+ feed: (source, feedOptions) => active ? renderer.feed(source, feedOptions) : Promise.resolve(),
18
21
  reset: () => {
22
+ if (active) {
23
+ renderer.reset();
24
+ nodes.value = [];
25
+ }
26
+ },
27
+ destroy: () => {
28
+ active = false;
19
29
  renderer.reset();
20
30
  nodes.value = [];
21
31
  }
@@ -37,6 +47,10 @@ const MountHost = defineComponent({
37
47
  onMounted(() => {
38
48
  if (elRef.value) cleanup = props.mount(elRef.value);
39
49
  });
50
+ watch(() => props.mount, (mount) => {
51
+ if (typeof cleanup === "function") cleanup();
52
+ cleanup = elRef.value ? mount(elRef.value) : void 0;
53
+ });
40
54
  onBeforeUnmount(() => {
41
55
  if (typeof cleanup === "function") cleanup();
42
56
  });
@@ -47,10 +61,10 @@ const MountHost = defineComponent({
47
61
  }
48
62
  });
49
63
  /** Translate a framework-neutral RenderOutput into a Vue VNode. */
50
- function renderOutput(out) {
64
+ function renderOutput(out, sanitize) {
51
65
  switch (out.kind) {
52
- case "html": return h("div", { innerHTML: sanitizeHtml(out.html) });
53
- case "element": return h(out.tag, out.props, (out.children ?? []).map(renderOutput));
66
+ 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)));
54
68
  case "mount": return h(MountHost, { mount: out.mount });
55
69
  case "card": return h("pre", { "data-aigui-card-fallback": "" }, [h("code", JSON.stringify(out.data, null, 2))]);
56
70
  }
@@ -58,39 +72,77 @@ function renderOutput(out) {
58
72
  /** Await an async RenderOutput, rendering a placeholder until it resolves. */
59
73
  const AsyncOutput = defineComponent({
60
74
  name: "AsyncOutput",
61
- props: { promise: {
62
- type: Object,
63
- required: true
64
- } },
75
+ props: {
76
+ promise: {
77
+ type: Object,
78
+ required: true
79
+ },
80
+ sanitize: {
81
+ type: [Boolean, Object],
82
+ default: void 0
83
+ }
84
+ },
65
85
  setup(props) {
66
86
  const resolved = ref(null);
67
- props.promise.then((v) => {
68
- resolved.value = v;
87
+ const failed = ref(false);
88
+ let generation = 0;
89
+ let active = true;
90
+ const awaitPromise = (promise) => {
91
+ const current = ++generation;
92
+ resolved.value = null;
93
+ failed.value = false;
94
+ promise.then((value) => {
95
+ if (active && current === generation) resolved.value = value;
96
+ }, () => {
97
+ if (active && current === generation) failed.value = true;
98
+ });
99
+ };
100
+ watch(() => [toRaw(props.promise)], ([promise]) => awaitPromise(promise), { immediate: true });
101
+ onBeforeUnmount(() => {
102
+ active = false;
103
+ generation++;
69
104
  });
70
- return () => resolved.value ? renderOutput(resolved.value) : h("span", { "data-aigui-async-pending": "" });
105
+ return () => {
106
+ if (failed.value) return h("span", { "data-aigui-async-error": "" });
107
+ if (!resolved.value) return h("span", { "data-aigui-async-pending": "" });
108
+ try {
109
+ return renderOutput(resolved.value, props.sanitize);
110
+ } catch {
111
+ return h("span", { "data-aigui-async-error": "" });
112
+ }
113
+ };
71
114
  }
72
115
  });
116
+ function sanitizeOutput(html, sanitize) {
117
+ if (sanitize === false) return html;
118
+ return sanitizeHtml(html, typeof sanitize === "object" ? sanitize : void 0);
119
+ }
73
120
 
74
121
  //#endregion
75
122
  //#region src/render-node.ts
76
123
  function renderNode(node, ctx) {
77
- const r = collectNodeRenderers(ctx.plugins)[node.type];
78
- if (r) {
124
+ const r = (ctx.nodeRenderers ?? collectNodeRenderers(ctx.plugins))[node.type];
125
+ if (r) try {
79
126
  const out = r(node);
80
127
  if (out && typeof out.then === "function") return h(AsyncOutput, {
81
128
  key: node.key,
82
- promise: out
129
+ promise: out,
130
+ sanitize: ctx.sanitize
83
131
  });
84
- return renderOutput(out);
132
+ const vnode = renderOutput(out, ctx.sanitize);
133
+ vnode.key = node.key;
134
+ return vnode;
135
+ } catch {
136
+ return renderFallback(node, ctx);
85
137
  }
86
138
  switch (node.type) {
87
139
  case "heading": return h(node.tag ?? "h1", {
88
140
  key: node.key,
89
- innerHTML: node.html ?? ""
141
+ innerHTML: renderHtml(node.html ?? "", ctx)
90
142
  });
91
143
  case "paragraph": return h("p", {
92
144
  key: node.key,
93
- innerHTML: node.html ?? ""
145
+ innerHTML: renderHtml(node.html ?? "", ctx)
94
146
  });
95
147
  case "code": return h("pre", {
96
148
  key: node.key,
@@ -99,13 +151,10 @@ function renderNode(node, ctx) {
99
151
  case "hr": return h("hr", { key: node.key });
100
152
  case "html": return h("div", {
101
153
  key: node.key,
102
- innerHTML: node.content ?? ""
154
+ innerHTML: renderHtml(node.content ?? "", ctx)
103
155
  });
104
156
  case "card": return renderCard(node, ctx);
105
- default: return h("div", {
106
- key: node.key,
107
- innerHTML: node.html ?? sanitizeHtml(node.content ?? "")
108
- });
157
+ default: return renderFallback(node, ctx);
109
158
  }
110
159
  }
111
160
  function renderCard(node, ctx) {
@@ -125,7 +174,7 @@ function renderCard(node, ctx) {
125
174
  key: node.key,
126
175
  "data-aigui-card-fallback": ""
127
176
  }, [h("code", JSON.stringify(card.data, null, 2))]);
128
- return h(Comp, {
177
+ return h(markRaw(toRaw(Comp)), {
129
178
  key: node.key,
130
179
  data: card.data,
131
180
  onAction: (a) => ctx.onCardAction?.({
@@ -134,11 +183,22 @@ function renderCard(node, ctx) {
134
183
  })
135
184
  });
136
185
  }
186
+ function renderFallback(node, ctx) {
187
+ return h("div", {
188
+ key: node.key,
189
+ innerHTML: renderHtml(node.html ?? node.content ?? "", ctx)
190
+ });
191
+ }
192
+ function renderHtml(html, ctx) {
193
+ if (ctx.sanitized || ctx.sanitize === false) return html;
194
+ return sanitizeHtml(html, typeof ctx.sanitize === "object" ? ctx.sanitize : void 0);
195
+ }
137
196
 
138
197
  //#endregion
139
198
  //#region src/ai-renderer.ts
140
199
  const AIRenderer = defineComponent({
141
200
  name: "AIRenderer",
201
+ emits: ["card-action"],
142
202
  props: {
143
203
  registry: {
144
204
  type: Object,
@@ -149,7 +209,7 @@ const AIRenderer = defineComponent({
149
209
  default: void 0
150
210
  },
151
211
  sanitize: {
152
- type: Boolean,
212
+ type: [Boolean, Object],
153
213
  default: void 0
154
214
  },
155
215
  onCardAction: {
@@ -157,24 +217,49 @@ const AIRenderer = defineComponent({
157
217
  default: void 0
158
218
  }
159
219
  },
160
- setup(props, { expose }) {
161
- const { nodes, push, feed, reset } = useAIRenderer({
220
+ setup(props, { emit, expose }) {
221
+ const current = shallowRef(useAIRenderer({
162
222
  registry: props.registry,
163
223
  sanitize: props.sanitize,
164
224
  plugins: props.plugins
225
+ }));
226
+ const nodeRenderers = shallowRef(collectNodeRenderers(props.plugins));
227
+ watch(() => [
228
+ props.registry,
229
+ props.sanitize,
230
+ props.plugins
231
+ ], () => {
232
+ current.value.destroy();
233
+ current.value = useAIRenderer({
234
+ registry: props.registry,
235
+ sanitize: props.sanitize,
236
+ plugins: props.plugins
237
+ });
238
+ nodeRenderers.value = collectNodeRenderers(props.plugins);
165
239
  });
240
+ onBeforeUnmount(() => current.value.destroy());
241
+ const push = (chunk) => current.value.push(chunk);
242
+ const feed = (source, options) => current.value.feed(source, options);
243
+ const reset = () => current.value.reset();
166
244
  expose({
167
245
  push,
168
246
  feed,
169
247
  reset
170
248
  });
171
249
  return () => {
250
+ const onCardAction = (action) => {
251
+ props.onCardAction?.(action);
252
+ emit("card-action", action);
253
+ };
172
254
  const ctx = {
173
255
  registry: props.registry,
174
256
  plugins: props.plugins,
175
- onCardAction: props.onCardAction
257
+ nodeRenderers: nodeRenderers.value,
258
+ onCardAction,
259
+ sanitize: props.sanitize,
260
+ sanitized: true
176
261
  };
177
- return h("div", { "data-aigui-renderer": "" }, nodes.value.map((n) => renderNode(n, ctx)));
262
+ return h("div", { "data-aigui-renderer": "" }, current.value.nodes.value.map((n) => renderNode(n, ctx)));
178
263
  };
179
264
  }
180
265
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-gui/vue",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -23,14 +23,23 @@
23
23
  "homepage": "https://github.com/liliang-cn/aigui#readme",
24
24
  "bugs": "https://github.com/liliang-cn/aigui/issues",
25
25
  "type": "module",
26
+ "sideEffects": false,
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
26
30
  "main": "./dist/index.cjs",
27
31
  "module": "./dist/index.js",
28
32
  "types": "./dist/index.d.ts",
29
33
  "exports": {
30
34
  ".": {
31
- "types": "./dist/index.d.ts",
32
- "import": "./dist/index.js",
33
- "require": "./dist/index.cjs"
35
+ "import": {
36
+ "types": "./dist/index.d.ts",
37
+ "default": "./dist/index.js"
38
+ },
39
+ "require": {
40
+ "types": "./dist/index.d.cts",
41
+ "default": "./dist/index.cjs"
42
+ }
34
43
  }
35
44
  },
36
45
  "files": [
@@ -42,7 +51,7 @@
42
51
  "access": "public"
43
52
  },
44
53
  "dependencies": {
45
- "@ai-gui/core": "0.1.0"
54
+ "@ai-gui/core": "0.2.0"
46
55
  },
47
56
  "peerDependencies": {
48
57
  "vue": ">=3.3"
@@ -53,6 +62,7 @@
53
62
  },
54
63
  "scripts": {
55
64
  "build": "tsdown",
65
+ "test": "pnpm --dir ../.. exec vitest run --project vue",
56
66
  "typecheck": "tsc --noEmit"
57
67
  }
58
68
  }