@adia-ai/web-components 0.0.2 → 0.0.4
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 +29 -22
- package/a2ui/index.js +23 -17
- package/components/accordion/accordion.js +1 -1
- package/components/action-list/action-list.js +1 -1
- package/components/agent-artifact/agent-artifact.js +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
- package/components/agent-questions/agent-questions.js +1 -1
- package/components/agent-reasoning/agent-reasoning.js +1 -1
- package/components/agent-suggestions/agent-suggestions.js +1 -1
- package/components/agent-trace/agent-trace.js +1 -1
- package/components/alert/alert.js +1 -1
- package/components/avatar/avatar.js +1 -1
- package/components/badge/badge.js +1 -1
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.js +2 -2
- package/components/calendar-picker/calendar-picker.js +2 -2
- package/components/canvas/canvas.js +2 -2
- package/components/card/card.js +2 -2
- package/components/chart/chart.js +1 -1
- package/components/chat/chat-input.js +1 -1
- package/components/chat/chat.css +1 -2
- package/components/chat/chat.js +2 -2
- package/components/check/check.js +2 -2
- package/components/code/code.css +2 -0
- package/components/code/code.js +1 -1
- package/components/col/col.js +1 -1
- package/components/color-picker/color-picker.js +1 -1
- package/components/command/command.js +1 -1
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.js +1 -1
- package/components/drawer/drawer.js +1 -1
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.js +1 -1
- package/components/grid/grid.js +1 -1
- package/components/heatmap/heatmap.js +1 -1
- package/components/icon/icon.js +2 -2
- package/components/image/image.js +1 -1
- package/components/input/input.js +2 -2
- package/components/inspector/inspector.js +2 -2
- package/components/kbd/kbd.js +1 -1
- package/components/list/list.js +1 -1
- package/components/menu/menu.js +2 -2
- package/components/modal/modal.js +1 -1
- package/components/noodles/noodles.js +1 -1
- package/components/otp-input/otp-input.js +1 -1
- package/components/pagination/pagination.js +1 -1
- package/components/pane/pane.css +2 -2
- package/components/pane/pane.js +1 -1
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.js +2 -2
- package/components/progress/progress.js +1 -1
- package/components/progress-row/progress-row.js +1 -1
- package/components/radio/radio.js +2 -2
- package/components/range/range.js +1 -1
- package/components/rating/rating.js +1 -1
- package/components/richtext/richtext.js +2 -2
- package/components/row/row.js +2 -2
- package/components/search/search.js +1 -1
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.js +2 -2
- package/components/skeleton/skeleton.js +1 -1
- package/components/slider/slider.js +1 -1
- package/components/stack/stack.js +1 -1
- package/components/stat/stat.js +1 -1
- package/components/stepper/stepper.js +1 -1
- package/components/stream/stream.js +1 -1
- package/components/swiper/swiper.js +1 -1
- package/components/switch/switch.js +2 -2
- package/components/table/table.js +1 -1
- package/components/tabs/tab.js +1 -1
- package/components/tabs/tabs.js +1 -1
- package/components/tag/tag.js +1 -1
- package/components/text/text.js +1 -1
- package/components/textarea/textarea.js +1 -1
- package/components/timeline/timeline.js +1 -1
- package/components/toast/toast.js +1 -1
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toolbar/toolbar.js +2 -2
- package/components/tooltip/tooltip.js +2 -2
- package/components/tree/tree.js +1 -1
- package/components/upload/upload.js +1 -1
- package/core/markdown.js +1 -1
- package/core/provider.js +2 -2
- package/package.json +7 -3
- package/patterns/a2ui-root/a2ui-root.a2ui.json +118 -0
- package/{a2ui/root.js → patterns/a2ui-root/a2ui-root.js} +9 -4
- package/patterns/a2ui-root/a2ui-root.yaml +76 -0
- package/patterns/adia-chat/adia-chat.js +3 -3
- package/patterns/adia-chat/css/adia-chat.tokens.css +1 -1
- package/patterns/adia-editor/adia-editor.a2ui.json +12 -0
- package/patterns/adia-editor/adia-editor.js +1 -1
- package/patterns/adia-editor/adia-editor.yaml +17 -0
- package/patterns/adia-editor/css/adia-editor.layout.css +70 -3
- package/patterns/adia-editor/css/adia-editor.tokens.css +1 -1
- package/patterns/app-nav/app-nav.js +1 -1
- package/patterns/app-nav-group/app-nav-group.js +2 -2
- package/patterns/app-nav-item/app-nav-item.js +1 -1
- package/patterns/app-shell/app-shell.js +1 -1
- package/patterns/app-shell/css/app-shell.tokens.css +1 -1
- package/patterns/gen-ui/gen-ui.js +1 -1
- package/patterns/index.js +1 -0
- package/patterns/section-nav/section-nav.js +1 -1
- package/patterns/section-nav-group/section-nav-group.js +1 -1
- package/patterns/section-nav-item/section-nav-item.js +1 -1
- package/styles/tokens.css +12 -0
- package/traits/define.js +1 -1
- package/a2ui/dockables/action.js +0 -152
- package/a2ui/dockables/base.js +0 -30
- package/a2ui/dockables/controller.js +0 -97
- package/a2ui/dockables/data-source.js +0 -103
- package/a2ui/dockables/index.js +0 -6
- package/a2ui/dockables/lifecycle.js +0 -84
- package/a2ui/dockables/provider.js +0 -59
- package/a2ui/manifest-runtime.js +0 -226
- package/a2ui/registry.js +0 -200
- package/a2ui/renderer.js +0 -361
- package/a2ui/stream.js +0 -243
- package/a2ui/surface-manifest.js +0 -294
- package/a2ui/surface.js +0 -222
- package/a2ui/wire-factory.js +0 -134
- package/a2ui/wiring-engine.js +0 -209
- package/a2ui/wiring-registry.js +0 -342
- package/patterns/adia-chat/index.html +0 -93
- package/patterns/adia-editor/index.html +0 -179
- package/patterns/app-shell/index.html +0 -112
package/a2ui/renderer.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A2UI Renderer — processes A2UI messages and renders AdiaUI components.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* import { A2UIRenderer } from './renderer.js';
|
|
6
|
-
* const renderer = new A2UIRenderer(container);
|
|
7
|
-
* renderer.process(message);
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { resolveTag, registry } from './registry.js';
|
|
11
|
-
|
|
12
|
-
export class A2UIRenderer {
|
|
13
|
-
#container;
|
|
14
|
-
#registry;
|
|
15
|
-
#surfaces = new Map();
|
|
16
|
-
#elements = new Map();
|
|
17
|
-
#prevProps = new Map();
|
|
18
|
-
#queue = [];
|
|
19
|
-
#rafId = null;
|
|
20
|
-
#batching = false;
|
|
21
|
-
|
|
22
|
-
constructor(container, reg = registry, { batch = false } = {}) {
|
|
23
|
-
this.#container = container;
|
|
24
|
-
this.#registry = reg;
|
|
25
|
-
this.#batching = batch;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
process(message) {
|
|
29
|
-
if (!message) return;
|
|
30
|
-
if (this.#batching) {
|
|
31
|
-
this.#queue.push(message);
|
|
32
|
-
if (this.#rafId === null) {
|
|
33
|
-
this.#rafId = requestAnimationFrame(() => this.#flush());
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
this.#processOne(message);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
#flush() {
|
|
41
|
-
this.#rafId = null;
|
|
42
|
-
const batch = this.#queue.splice(0);
|
|
43
|
-
for (const msg of batch) this.#processOne(msg);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
#processOne(message) {
|
|
47
|
-
switch (message.type || message.messageType) {
|
|
48
|
-
case 'createSurface': this.#createSurface(message); break;
|
|
49
|
-
case 'updateComponents': this.#updateComponents(message); break;
|
|
50
|
-
case 'updateDataModel': this.#updateDataModel(message); break;
|
|
51
|
-
case 'wireComponents': this.#wireComponents(message); break;
|
|
52
|
-
case 'deleteSurface': this.#deleteSurface(message); break;
|
|
53
|
-
case 'meta': break; // LLM self-critique — not renderable
|
|
54
|
-
default: console.warn('A2UI: unknown message type', message.type);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ── wireComponents (lazy-loaded) ──
|
|
59
|
-
|
|
60
|
-
#wiringEngine = null;
|
|
61
|
-
|
|
62
|
-
async #wireComponents(message) {
|
|
63
|
-
if (!this.#wiringEngine) {
|
|
64
|
-
const { WiringEngine } = await import('./wiring-engine.js');
|
|
65
|
-
this.#wiringEngine = new WiringEngine({
|
|
66
|
-
updateDataModel: (surfaceId, path, data) => {
|
|
67
|
-
this.#updateDataModel({ surfaceId, path, value: data });
|
|
68
|
-
},
|
|
69
|
-
getElement: (surfaceId, componentId) => {
|
|
70
|
-
const surface = this.#surfaces.get(surfaceId);
|
|
71
|
-
return surface?.elements.get(componentId) || null;
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
await this.#wiringEngine.process(message);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async processStream(stream) {
|
|
79
|
-
for await (const message of stream) this.process(message);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ── createSurface ──
|
|
83
|
-
|
|
84
|
-
#createSurface({ surfaceId, catalogId }) {
|
|
85
|
-
if (this.#surfaces.has(surfaceId)) return;
|
|
86
|
-
|
|
87
|
-
const root = document.createElement('div');
|
|
88
|
-
root.setAttribute('data-a2ui-surface', surfaceId);
|
|
89
|
-
if (catalogId) root.setAttribute('data-catalog', catalogId);
|
|
90
|
-
this.#container.appendChild(root);
|
|
91
|
-
|
|
92
|
-
this.#surfaces.set(surfaceId, {
|
|
93
|
-
root,
|
|
94
|
-
elements: new Map(),
|
|
95
|
-
dataModel: {},
|
|
96
|
-
bindings: new Map(),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ── updateComponents ──
|
|
101
|
-
|
|
102
|
-
#updateComponents({ surfaceId, components }) {
|
|
103
|
-
let surface = this.#surfaces.get(surfaceId);
|
|
104
|
-
|
|
105
|
-
if (!surface) {
|
|
106
|
-
surface = {
|
|
107
|
-
root: this.#container,
|
|
108
|
-
elements: new Map(),
|
|
109
|
-
dataModel: {},
|
|
110
|
-
bindings: new Map(),
|
|
111
|
-
};
|
|
112
|
-
this.#surfaces.set(surfaceId, surface);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// First pass: create/update elements
|
|
116
|
-
for (const comp of components) {
|
|
117
|
-
if (!comp.id && comp.id !== 0) continue;
|
|
118
|
-
if (comp.component === 'ContextBindings') {
|
|
119
|
-
surface.contextBindings = comp;
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
let el = surface.elements.get(comp.id);
|
|
125
|
-
const tagName = comp.component === 'Text'
|
|
126
|
-
? this.#resolveTextTag(comp.variant, comp)
|
|
127
|
-
: resolveTag(comp.component, this.#registry);
|
|
128
|
-
|
|
129
|
-
if (!tagName) {
|
|
130
|
-
if (!el) {
|
|
131
|
-
el = document.createElement('div');
|
|
132
|
-
el.setAttribute('data-a2ui-id', comp.id);
|
|
133
|
-
el.setAttribute('data-a2ui-unknown', comp.component);
|
|
134
|
-
el.textContent = `[unknown: ${comp.component}]`;
|
|
135
|
-
surface.elements.set(comp.id, el);
|
|
136
|
-
this.#elements.set(comp.id, el);
|
|
137
|
-
}
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!el) {
|
|
142
|
-
el = document.createElement(tagName);
|
|
143
|
-
el.setAttribute('data-a2ui-id', comp.id);
|
|
144
|
-
surface.elements.set(comp.id, el);
|
|
145
|
-
this.#elements.set(comp.id, el);
|
|
146
|
-
} else if (el.localName !== tagName) {
|
|
147
|
-
const newEl = document.createElement(tagName);
|
|
148
|
-
newEl.setAttribute('data-a2ui-id', comp.id);
|
|
149
|
-
el.replaceWith(newEl);
|
|
150
|
-
el = newEl;
|
|
151
|
-
surface.elements.set(comp.id, el);
|
|
152
|
-
this.#elements.set(comp.id, el);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
comp._surfaceId = surfaceId;
|
|
156
|
-
|
|
157
|
-
const hasBindings = Object.values(comp).some(v => v && typeof v === 'object' && v.path);
|
|
158
|
-
if (hasBindings) surface.bindings.set(comp.id, comp);
|
|
159
|
-
|
|
160
|
-
this.#applyProps(el, comp);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
console.warn(`A2UI: component "${comp.id}" (${comp.component}) failed:`, err.message);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Build parent map for cycle detection
|
|
167
|
-
const parentMap = new Map();
|
|
168
|
-
for (const comp of components) {
|
|
169
|
-
for (const childId of (Array.isArray(comp.children) ? comp.children : [])) {
|
|
170
|
-
parentMap.set(childId, comp.id);
|
|
171
|
-
}
|
|
172
|
-
if (comp.child) parentMap.set(comp.child, comp.id);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const wouldCycle = (parentId, childId) => {
|
|
176
|
-
const visited = new Set();
|
|
177
|
-
let cursor = parentId;
|
|
178
|
-
while (cursor != null) {
|
|
179
|
-
if (cursor === childId) return true;
|
|
180
|
-
if (visited.has(cursor)) return true;
|
|
181
|
-
visited.add(cursor);
|
|
182
|
-
cursor = parentMap.get(cursor);
|
|
183
|
-
}
|
|
184
|
-
return false;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// Second pass: build tree
|
|
188
|
-
for (const comp of components) {
|
|
189
|
-
const el = surface.elements.get(comp.id);
|
|
190
|
-
if (!el) continue;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const childIds = Array.isArray(comp.children) ? comp.children : [];
|
|
194
|
-
for (const childId of childIds) {
|
|
195
|
-
if (childId === comp.id || wouldCycle(comp.id, childId)) continue;
|
|
196
|
-
const childEl = surface.elements.get(childId);
|
|
197
|
-
if (childEl && childEl.parentElement !== el) el.appendChild(childEl);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (comp.child && comp.child !== comp.id && !wouldCycle(comp.id, comp.child)) {
|
|
201
|
-
const childEl = surface.elements.get(comp.child);
|
|
202
|
-
if (childEl && childEl.parentElement !== el) el.appendChild(childEl);
|
|
203
|
-
}
|
|
204
|
-
} catch (err) {
|
|
205
|
-
console.warn(`A2UI: tree build failed for "${comp.id}":`, err.message);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Attach root
|
|
210
|
-
const rootComp = components.find(c => c.id === 'root');
|
|
211
|
-
if (rootComp) {
|
|
212
|
-
const rootEl = surface.elements.get('root');
|
|
213
|
-
if (rootEl && rootEl.parentElement !== surface.root) {
|
|
214
|
-
surface.root.appendChild(rootEl);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ── Apply props ──
|
|
220
|
-
|
|
221
|
-
static #JS_PROPS = new Set(['data', 'columns', 'options', 'itemRenderer', 'textContent']);
|
|
222
|
-
|
|
223
|
-
// Semantic HTML variants — these render as native tags (h1, p, small)
|
|
224
|
-
// Everything else renders as <text-ui> which is now display:inline
|
|
225
|
-
static #TEXT_TAG_MAP = {
|
|
226
|
-
h1: 'h1', h2: 'h2', h3: 'h3', h4: 'h4', h5: 'h5', h6: 'h6',
|
|
227
|
-
body: 'p', caption: 'small',
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
#resolveTextTag(variant, comp) {
|
|
231
|
-
// Semantic HTML variants get native tags (accessibility)
|
|
232
|
-
if (!comp?.slot && A2UIRenderer.#TEXT_TAG_MAP[variant]) {
|
|
233
|
-
return A2UIRenderer.#TEXT_TAG_MAP[variant];
|
|
234
|
-
}
|
|
235
|
-
// Everything else → text-ui (inline, supports truncate/lines/variant)
|
|
236
|
-
return 'text-ui';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
#applyProps(el, comp) {
|
|
240
|
-
const skip = new Set(['id', 'component', 'children', 'child', '_surfaceId']);
|
|
241
|
-
// Skip variant attr when the tag IS the variant (h1 for h1, p for body, small for caption)
|
|
242
|
-
if (comp.component === 'Text' && comp.variant && !comp.slot && A2UIRenderer.#TEXT_TAG_MAP[comp.variant]) {
|
|
243
|
-
skip.add('variant');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const prev = this.#prevProps.get(comp.id);
|
|
247
|
-
const next = {};
|
|
248
|
-
|
|
249
|
-
for (const [key, value] of Object.entries(comp)) {
|
|
250
|
-
if (skip.has(key)) continue;
|
|
251
|
-
|
|
252
|
-
const isBinding = value && typeof value === 'object' && value.path;
|
|
253
|
-
const resolved = this.#resolveValue(value, comp._surfaceId);
|
|
254
|
-
next[key] = resolved;
|
|
255
|
-
|
|
256
|
-
if (prev && Object.is(prev[key], resolved)) continue;
|
|
257
|
-
|
|
258
|
-
if (key === 'style' && typeof resolved === 'string') {
|
|
259
|
-
el.style.cssText = resolved;
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (A2UIRenderer.#JS_PROPS.has(key) && resolved != null) {
|
|
264
|
-
el[key] = resolved;
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (typeof resolved === 'boolean') {
|
|
269
|
-
if (resolved) el.setAttribute(key, '');
|
|
270
|
-
else el.removeAttribute(key);
|
|
271
|
-
} else if (resolved != null) {
|
|
272
|
-
el.setAttribute(this.#toAttr(key), String(resolved));
|
|
273
|
-
} else if (isBinding) {
|
|
274
|
-
el.removeAttribute(this.#toAttr(key));
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (prev) {
|
|
279
|
-
for (const key of Object.keys(prev)) {
|
|
280
|
-
if (!(key in next) && !skip.has(key)) el.removeAttribute(this.#toAttr(key));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this.#prevProps.set(comp.id, next);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
#toAttr(name) {
|
|
288
|
-
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
#resolveValue(value, surfaceId) {
|
|
292
|
-
if (value == null) return null;
|
|
293
|
-
if (typeof value !== 'object') return value;
|
|
294
|
-
if (value.path) {
|
|
295
|
-
const surface = surfaceId ? this.#surfaces.get(surfaceId) : null;
|
|
296
|
-
return surface ? this.#getByPath(surface.dataModel, value.path) : value.path;
|
|
297
|
-
}
|
|
298
|
-
return value;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
#getByPath(obj, path) {
|
|
302
|
-
if (!path || path === '/') return obj;
|
|
303
|
-
return path.split('/').filter(Boolean).reduce((o, k) => o?.[k], obj);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// ── updateDataModel ──
|
|
307
|
-
|
|
308
|
-
#updateDataModel({ surfaceId, path, value }) {
|
|
309
|
-
const surface = this.#surfaces.get(surfaceId);
|
|
310
|
-
if (!surface) return;
|
|
311
|
-
|
|
312
|
-
if (!path || path === '/') {
|
|
313
|
-
surface.dataModel = value ?? {};
|
|
314
|
-
} else {
|
|
315
|
-
const parts = path.split('/').filter(Boolean);
|
|
316
|
-
let cur = surface.dataModel;
|
|
317
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
318
|
-
if (cur[parts[i]] == null) cur[parts[i]] = {};
|
|
319
|
-
cur = cur[parts[i]];
|
|
320
|
-
}
|
|
321
|
-
cur[parts[parts.length - 1]] = value;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
for (const [compId, comp] of surface.bindings) {
|
|
325
|
-
const el = surface.elements.get(compId);
|
|
326
|
-
if (el) this.#applyProps(el, comp);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ── deleteSurface ──
|
|
331
|
-
|
|
332
|
-
#deleteSurface({ surfaceId }) {
|
|
333
|
-
const surface = this.#surfaces.get(surfaceId);
|
|
334
|
-
if (!surface) return;
|
|
335
|
-
this.#wiringEngine?.teardown(surfaceId);
|
|
336
|
-
for (const [id] of surface.elements) this.#elements.delete(id);
|
|
337
|
-
surface.root.remove();
|
|
338
|
-
this.#surfaces.delete(surfaceId);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// ── Public ──
|
|
342
|
-
|
|
343
|
-
getSurface(id) { return this.#surfaces.get(id); }
|
|
344
|
-
getElement(id) { return this.#elements.get(id); }
|
|
345
|
-
get surfaces() { return [...this.#surfaces.keys()]; }
|
|
346
|
-
|
|
347
|
-
reset() {
|
|
348
|
-
if (this.#rafId !== null) { cancelAnimationFrame(this.#rafId); this.#rafId = null; }
|
|
349
|
-
this.#queue.length = 0;
|
|
350
|
-
for (const [, s] of this.#surfaces) {
|
|
351
|
-
if (s.root === this.#container) s.root.innerHTML = '';
|
|
352
|
-
else s.root.remove();
|
|
353
|
-
}
|
|
354
|
-
this.#surfaces.clear();
|
|
355
|
-
this.#elements.clear();
|
|
356
|
-
this.#prevProps.clear();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
set batching(v) { this.#batching = !!v; }
|
|
360
|
-
get batching() { return this.#batching; }
|
|
361
|
-
}
|
package/a2ui/stream.js
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A2UI Stream Adapters — connect to various transports and yield A2UI messages.
|
|
3
|
-
*
|
|
4
|
-
* Each adapter returns an AsyncIterable<A2UIMessage>.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* const stream = sseStream('/api/agent');
|
|
8
|
-
* for await (const message of stream) { renderer.process(message); }
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* SSE (Server-Sent Events) stream adapter.
|
|
13
|
-
*/
|
|
14
|
-
export function sseStream(url, options = {}) {
|
|
15
|
-
let finalUrl = url;
|
|
16
|
-
if (options.catalog) {
|
|
17
|
-
const types = options.catalog instanceof Map ? [...options.catalog.keys()] : Object.keys(options.catalog);
|
|
18
|
-
const sep = url.includes('?') ? '&' : '?';
|
|
19
|
-
finalUrl = `${url}${sep}a2ui_catalog=${encodeURIComponent(types.join(','))}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
[Symbol.asyncIterator]() {
|
|
24
|
-
const eventSource = new EventSource(finalUrl);
|
|
25
|
-
const queue = [];
|
|
26
|
-
let resolve = null;
|
|
27
|
-
let done = false;
|
|
28
|
-
|
|
29
|
-
eventSource.onmessage = (e) => {
|
|
30
|
-
try {
|
|
31
|
-
const message = JSON.parse(e.data);
|
|
32
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: message, done: false }); }
|
|
33
|
-
else queue.push(message);
|
|
34
|
-
} catch { console.warn('A2UI SSE: invalid JSON', e.data); }
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
eventSource.onerror = () => {
|
|
38
|
-
done = true;
|
|
39
|
-
eventSource.close();
|
|
40
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: undefined, done: true }); }
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (options.signal) {
|
|
44
|
-
options.signal.addEventListener('abort', () => {
|
|
45
|
-
done = true;
|
|
46
|
-
eventSource.close();
|
|
47
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: undefined, done: true }); }
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
next() {
|
|
53
|
-
if (queue.length > 0) return Promise.resolve({ value: queue.shift(), done: false });
|
|
54
|
-
if (done) return Promise.resolve({ value: undefined, done: true });
|
|
55
|
-
return new Promise(r => { resolve = r; });
|
|
56
|
-
},
|
|
57
|
-
return() {
|
|
58
|
-
done = true;
|
|
59
|
-
eventSource.close();
|
|
60
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* WebSocket stream adapter.
|
|
69
|
-
*/
|
|
70
|
-
export function wsStream(url, options = {}) {
|
|
71
|
-
return {
|
|
72
|
-
[Symbol.asyncIterator]() {
|
|
73
|
-
const ws = new WebSocket(url);
|
|
74
|
-
const queue = [];
|
|
75
|
-
let resolve = null;
|
|
76
|
-
let done = false;
|
|
77
|
-
|
|
78
|
-
ws.onopen = () => {
|
|
79
|
-
if (options.catalog) {
|
|
80
|
-
const types = options.catalog instanceof Map ? [...options.catalog.keys()] : Object.keys(options.catalog);
|
|
81
|
-
ws.send(JSON.stringify({ type: 'a2ui:catalog', supportedTypes: types }));
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
ws.onmessage = (e) => {
|
|
86
|
-
try {
|
|
87
|
-
const message = JSON.parse(e.data);
|
|
88
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: message, done: false }); }
|
|
89
|
-
else queue.push(message);
|
|
90
|
-
} catch { console.warn('A2UI WS: invalid JSON', e.data); }
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
ws.onclose = () => {
|
|
94
|
-
done = true;
|
|
95
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: undefined, done: true }); }
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
ws.onerror = () => { done = true; ws.close(); };
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
next() {
|
|
102
|
-
if (queue.length > 0) return Promise.resolve({ value: queue.shift(), done: false });
|
|
103
|
-
if (done) return Promise.resolve({ value: undefined, done: true });
|
|
104
|
-
return new Promise(r => { resolve = r; });
|
|
105
|
-
},
|
|
106
|
-
return() { done = true; ws.close(); return Promise.resolve({ value: undefined, done: true }); },
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Array/mock stream adapter — for testing.
|
|
114
|
-
*/
|
|
115
|
-
export function mockStream(messages, delay = 0) {
|
|
116
|
-
return {
|
|
117
|
-
async *[Symbol.asyncIterator]() {
|
|
118
|
-
for (const msg of messages) {
|
|
119
|
-
if (delay > 0) await new Promise(r => setTimeout(r, delay));
|
|
120
|
-
yield msg;
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* MCP stream adapter — connects to MCP server, yields A2UI messages.
|
|
128
|
-
*/
|
|
129
|
-
export function mcpStream(url, options = {}) {
|
|
130
|
-
return {
|
|
131
|
-
async *[Symbol.asyncIterator]() {
|
|
132
|
-
const { signal, catalog, onAction } = options;
|
|
133
|
-
|
|
134
|
-
const ws = new WebSocket(url);
|
|
135
|
-
await new Promise((resolve, reject) => {
|
|
136
|
-
ws.onopen = resolve;
|
|
137
|
-
ws.onerror = reject;
|
|
138
|
-
if (signal) signal.addEventListener('abort', () => {
|
|
139
|
-
ws.close();
|
|
140
|
-
reject(new DOMException('Aborted', 'AbortError'));
|
|
141
|
-
}, { once: true });
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
if (catalog) {
|
|
145
|
-
const types = catalog instanceof Map ? [...catalog.keys()] : Object.keys(catalog);
|
|
146
|
-
ws.send(JSON.stringify({
|
|
147
|
-
jsonrpc: '2.0',
|
|
148
|
-
method: 'a2ui/catalog',
|
|
149
|
-
params: { supportedTypes: types },
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const queue = [];
|
|
154
|
-
let resolve = null;
|
|
155
|
-
let done = false;
|
|
156
|
-
|
|
157
|
-
ws.onmessage = (e) => {
|
|
158
|
-
try {
|
|
159
|
-
const rpc = JSON.parse(e.data);
|
|
160
|
-
|
|
161
|
-
if (rpc.result?.content) {
|
|
162
|
-
for (const block of rpc.result.content) {
|
|
163
|
-
if (block.type === 'resource' && block.resource?.mimeType === 'application/json+a2ui') {
|
|
164
|
-
const messages = JSON.parse(block.resource.text);
|
|
165
|
-
for (const msg of (Array.isArray(messages) ? messages : [messages])) {
|
|
166
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: msg, done: false }); }
|
|
167
|
-
else queue.push(msg);
|
|
168
|
-
}
|
|
169
|
-
} else if (block.type === 'text') {
|
|
170
|
-
try {
|
|
171
|
-
const msg = JSON.parse(block.text);
|
|
172
|
-
if (msg.type && (msg.type.startsWith('create') || msg.type.startsWith('update') || msg.type.startsWith('delete'))) {
|
|
173
|
-
if (resolve) { const r = resolve; resolve = null; r({ value: msg, done: false }); }
|
|
174
|
-
else queue.push(msg);
|
|
175
|
-
}
|
|
176
|
-
} catch { /* not A2UI */ }
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (rpc.method === 'a2ui/action' && onAction) {
|
|
182
|
-
onAction(rpc.params?.name, rpc.params?.arguments).then(result => {
|
|
183
|
-
ws.send(JSON.stringify({ jsonrpc: '2.0', id: rpc.id, result }));
|
|
184
|
-
}).catch(err => {
|
|
185
|
-
ws.send(JSON.stringify({ jsonrpc: '2.0', id: rpc.id, error: { code: -1, message: err.message } }));
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
} catch { console.warn('A2UI MCP: invalid message', e.data); }
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
ws.onclose = () => { done = true; if (resolve) { const r = resolve; resolve = null; r({ value: undefined, done: true }); } };
|
|
192
|
-
ws.onerror = () => { done = true; ws.close(); };
|
|
193
|
-
if (signal) signal.addEventListener('abort', () => { done = true; ws.close(); if (resolve) { const r = resolve; resolve = null; r({ value: undefined, done: true }); } });
|
|
194
|
-
|
|
195
|
-
while (!done || queue.length > 0) {
|
|
196
|
-
if (queue.length > 0) { yield queue.shift(); }
|
|
197
|
-
else if (done) { break; }
|
|
198
|
-
else {
|
|
199
|
-
const v = await new Promise(r => { resolve = r; });
|
|
200
|
-
if (v.done) break;
|
|
201
|
-
yield v.value;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* JSONL (newline-delimited JSON) stream adapter via fetch.
|
|
210
|
-
*/
|
|
211
|
-
export function jsonlStream(url, options = {}) {
|
|
212
|
-
return {
|
|
213
|
-
async *[Symbol.asyncIterator]() {
|
|
214
|
-
const headers = {};
|
|
215
|
-
if (options.catalog) {
|
|
216
|
-
const types = options.catalog instanceof Map ? [...options.catalog.keys()] : Object.keys(options.catalog);
|
|
217
|
-
headers['X-A2UI-Catalog'] = types.join(',');
|
|
218
|
-
}
|
|
219
|
-
const response = await fetch(url, { signal: options.signal, headers });
|
|
220
|
-
const reader = response.body.getReader();
|
|
221
|
-
const decoder = new TextDecoder();
|
|
222
|
-
let buffer = '';
|
|
223
|
-
|
|
224
|
-
while (true) {
|
|
225
|
-
const { done, value } = await reader.read();
|
|
226
|
-
if (done) break;
|
|
227
|
-
buffer += decoder.decode(value, { stream: true });
|
|
228
|
-
const lines = buffer.split('\n');
|
|
229
|
-
buffer = lines.pop();
|
|
230
|
-
for (const line of lines) {
|
|
231
|
-
const trimmed = line.trim();
|
|
232
|
-
if (!trimmed) continue;
|
|
233
|
-
try { yield JSON.parse(trimmed); }
|
|
234
|
-
catch { console.warn('A2UI JSONL: invalid JSON', trimmed); }
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (buffer.trim()) {
|
|
238
|
-
try { yield JSON.parse(buffer.trim()); }
|
|
239
|
-
catch { /* ignore */ }
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|