@webmcp-auto-ui/agent 2.5.25 → 2.5.26

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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/src/autoui-server.ts +17 -0
  3. package/src/diagnostics.ts +6 -6
  4. package/src/discovery-cache.ts +17 -3
  5. package/src/index.ts +3 -3
  6. package/src/loop.ts +27 -22
  7. package/src/providers/wasm.ts +185 -345
  8. package/src/recipes/_generated.ts +273 -0
  9. package/src/recipes/canary-data.md +50 -0
  10. package/src/recipes/canary-display.md +99 -0
  11. package/src/recipes/canary-middle.md +32 -0
  12. package/src/recipes/hummingbird-data.md +32 -0
  13. package/src/recipes/hummingbird-display.md +36 -0
  14. package/src/recipes/hummingbird-middle.md +18 -0
  15. package/src/tool-layers.ts +303 -31
  16. package/src/types.ts +6 -1
  17. package/tests/loop.test.ts +2 -2
  18. package/src/providers/gemma.worker.legacy.ts +0 -123
  19. package/src/providers/litert.worker.ts +0 -294
  20. package/src/recipes/widgets/actions.md +0 -28
  21. package/src/recipes/widgets/alert.md +0 -27
  22. package/src/recipes/widgets/cards.md +0 -41
  23. package/src/recipes/widgets/carousel.md +0 -39
  24. package/src/recipes/widgets/chart-rich.md +0 -51
  25. package/src/recipes/widgets/chart.md +0 -32
  26. package/src/recipes/widgets/code.md +0 -21
  27. package/src/recipes/widgets/d3.md +0 -36
  28. package/src/recipes/widgets/data-table.md +0 -46
  29. package/src/recipes/widgets/gallery.md +0 -39
  30. package/src/recipes/widgets/grid-data.md +0 -57
  31. package/src/recipes/widgets/hemicycle.md +0 -43
  32. package/src/recipes/widgets/js-sandbox.md +0 -32
  33. package/src/recipes/widgets/json-viewer.md +0 -27
  34. package/src/recipes/widgets/kv.md +0 -31
  35. package/src/recipes/widgets/list.md +0 -24
  36. package/src/recipes/widgets/log.md +0 -39
  37. package/src/recipes/widgets/map.md +0 -49
  38. package/src/recipes/widgets/profile.md +0 -49
  39. package/src/recipes/widgets/recipe-browser.md +0 -102
  40. package/src/recipes/widgets/sankey.md +0 -54
  41. package/src/recipes/widgets/stat-card.md +0 -43
  42. package/src/recipes/widgets/stat.md +0 -35
  43. package/src/recipes/widgets/tags.md +0 -30
  44. package/src/recipes/widgets/text.md +0 -19
  45. package/src/recipes/widgets/timeline.md +0 -38
  46. package/src/recipes/widgets/trombinoscope.md +0 -39
@@ -1,294 +0,0 @@
1
- /**
2
- * LiteRT Worker — MediaPipe LlmInference backend
3
- * Uses @mediapipe/tasks-genai with WebGPU for Gemma 4 models
4
- *
5
- * Messages IN: { type: 'init', model?: string }
6
- * { type: 'chat', id: string, prompt: string, maxTokens?: number, temperature?: number, topK?: number }
7
- * { type: 'abort', id: string }
8
- * Messages OUT: { type: 'progress', progress: number, status: string, loaded?: number, total?: number }
9
- * { type: 'ready' }
10
- * { type: 'token', id: string, token: string }
11
- * { type: 'done', id: string, text: string, stats?: { tokensPerSec: number, totalTokens: number, latencyMs: number } }
12
- * { type: 'error', id: string | null, message: string }
13
- */
14
-
15
- /**
16
- * CRITICAL POLYFILL for module workers.
17
- *
18
- * @mediapipe/tasks-genai loads WASM support scripts with this runtime check:
19
- *
20
- * async function lr(url) {
21
- * if (typeof importScripts !== "function") {
22
- * document.createElement("script") … // crashes in a worker (no DOM)
23
- * }
24
- * try { importScripts(url) } catch (e) {
25
- * if (!(e instanceof TypeError)) throw e;
26
- * await self.import(url); // dynamic-import fallback
27
- * }
28
- * }
29
- *
30
- * In ES module workers `importScripts` is undefined, so the library falls
31
- * into the DOM branch and crashes. We install a stub that throws TypeError
32
- * so the library takes the worker branch and reaches `self.import()` which
33
- * works perfectly in module workers.
34
- *
35
- * This works because `lr()` checks `typeof importScripts` at CALL TIME (when
36
- * FilesetResolver.forGenAiTasks() runs), not at module parse time. The
37
- * polyfill is set up synchronously at module load, well before any API call.
38
- */
39
- if (typeof (globalThis as any).importScripts !== 'function') {
40
- (globalThis as any).importScripts = (..._args: string[]) => {
41
- throw new TypeError('importScripts is not supported in module workers');
42
- };
43
- }
44
- // MediaPipe falls back to `self.import(url)` which doesn't exist in Chrome.
45
- // Provide it as an alias to dynamic `import()`.
46
- if (typeof (self as any).import !== 'function') {
47
- (self as any).import = (url: string) => import(/* @vite-ignore */ url);
48
- }
49
-
50
- import { FilesetResolver, LlmInference } from '@mediapipe/tasks-genai';
51
-
52
- const LITERT_MODELS: Record<string, { repo: string; file: string }> = {
53
- 'gemma-e2b': { repo: 'litert-community/gemma-4-E2B-it-litert-lm', file: 'gemma-4-E2B-it-web.task' },
54
- 'gemma-e4b': { repo: 'litert-community/gemma-4-E4B-it-litert-lm', file: 'gemma-4-E4B-it-web.task' },
55
- };
56
-
57
- let inference: LlmInference | null = null;
58
- let cancelRequested = false;
59
-
60
- /**
61
- * Download model with OPFS caching, returning a ReadableStream.
62
- * Follows the same streaming pattern as the official MediaPipe sample:
63
- * the stream reader is passed directly to LlmInference as modelAssetBuffer
64
- * to avoid buffering multi-GB models entirely in RAM.
65
- */
66
- async function getModelStream(
67
- url: string,
68
- filename: string,
69
- progressCb: (p: number, loaded: number, total: number) => void,
70
- ): Promise<ReadableStream<Uint8Array>> {
71
- const root = await navigator.storage.getDirectory();
72
- const modelsDir = await root.getDirectoryHandle('webmcp-models', { create: true });
73
-
74
- // ── OPFS cache hit ───────────────────────────────────────────────
75
- try {
76
- const cached = await modelsDir.getFileHandle(filename);
77
- const file = await cached.getFile();
78
- // Verify the file is complete (size file stores expected size)
79
- try {
80
- const sizeHandle = await modelsDir.getFileHandle(filename + '_size');
81
- const sizeFile = await sizeHandle.getFile();
82
- const expectedSize = parseInt(await sizeFile.text());
83
- if (file.size !== expectedSize) {
84
- // Corrupt cache — remove and re-download
85
- await modelsDir.removeEntry(filename);
86
- await modelsDir.removeEntry(filename + '_size');
87
- throw new Error('cache size mismatch');
88
- }
89
- } catch {
90
- // No size file but model exists — use it (legacy cache)
91
- }
92
- progressCb(1, file.size, file.size);
93
- return file.stream() as ReadableStream<Uint8Array>;
94
- } catch {
95
- // Cache miss — download from network
96
- }
97
-
98
- // ── Network download ─────────────────────────────────────────────
99
- // HEAD request first to get content-length for progress
100
- let expectedSize = 0;
101
- try {
102
- const head = await fetch(url, { method: 'HEAD' });
103
- if (head.ok) expectedSize = parseInt(head.headers.get('content-length') ?? '0', 10);
104
- } catch { /* non-fatal */ }
105
-
106
- const response = await fetch(url);
107
- if (!response.ok) throw new Error(`Download failed: ${response.status} ${response.statusText}`);
108
- if (!response.body) throw new Error('Response body is null');
109
-
110
- const total = expectedSize || parseInt(response.headers.get('content-length') ?? '0', 10);
111
-
112
- // Tee: one stream for consumer, one for background OPFS caching
113
- const [streamForConsumer, streamForCache] = response.body.tee();
114
-
115
- // Background cache (non-blocking, fire-and-forget)
116
- (async () => {
117
- try {
118
- // Write expected size first
119
- const sizeHandle = await modelsDir.getFileHandle(filename + '_size', { create: true });
120
- const sizeWritable = await sizeHandle.createWritable();
121
- await sizeWritable.write(new TextEncoder().encode(String(total)));
122
- await sizeWritable.close();
123
-
124
- const handle = await modelsDir.getFileHandle(filename, { create: true });
125
- const writable = await handle.createWritable();
126
- await streamForCache.pipeTo(writable);
127
- } catch {
128
- // OPFS write failure is non-fatal — model still usable from stream
129
- try {
130
- await modelsDir.removeEntry(filename).catch(() => {});
131
- await modelsDir.removeEntry(filename + '_size').catch(() => {});
132
- } catch { /* ignore cleanup errors */ }
133
- }
134
- })();
135
-
136
- // Wrap with progress reporting
137
- let loaded = 0;
138
- const progressTransform = new TransformStream<Uint8Array, Uint8Array>({
139
- transform(chunk, controller) {
140
- loaded += chunk.length;
141
- progressCb(total > 0 ? loaded / total : 0, loaded, total);
142
- controller.enqueue(chunk);
143
- },
144
- });
145
-
146
- return streamForConsumer.pipeThrough(progressTransform);
147
- }
148
-
149
- self.onmessage = async (e: MessageEvent) => {
150
- const { type, id, model: modelId, prompt } = e.data as {
151
- type: string; id?: string; model?: string; prompt?: string;
152
- };
153
-
154
- if (type === 'init') {
155
- try {
156
- const key = modelId ?? 'gemma-e2b';
157
- const { repo, file } = LITERT_MODELS[key] ?? LITERT_MODELS['gemma-e2b'];
158
- const url = `https://huggingface.co/${repo}/resolve/main/${file}`;
159
-
160
- self.postMessage({ type: 'progress', progress: 0, status: 'downloading', loaded: 0, total: 0 });
161
-
162
- const modelStream = await getModelStream(url, file, (p, loaded, total) => {
163
- self.postMessage({ type: 'progress', progress: p, status: 'downloading', loaded, total });
164
- });
165
-
166
- self.postMessage({ type: 'progress', progress: 1, status: 'initializing', loaded: 0, total: 0 });
167
-
168
- // Resolve the GenAI WASM fileset from CDN (pinned version).
169
- // Second arg = true → use the ES module variant (genai_wasm_module_internal.js)
170
- // so that `self.import()` works correctly in the module worker fallback path.
171
- const genaiFileset = await FilesetResolver.forGenAiTasks(
172
- 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai@0.10.27/wasm',
173
- /* useModuleVariant */ true,
174
- );
175
-
176
- // WebGPU device is required for LiteRT models
177
- const gpuDevice = await LlmInference.createWebGpuDevice();
178
-
179
- // Pass stream reader as modelAssetBuffer — same pattern as the official
180
- // MediaPipe sample (avoids buffering the entire model in RAM).
181
- // The MediaPipe API accepts ReadableStreamDefaultReader in this slot.
182
- inference = await LlmInference.createFromOptions(genaiFileset, {
183
- baseOptions: {
184
- modelAssetBuffer: modelStream.getReader() as unknown as Uint8Array,
185
- delegate: 'GPU',
186
- },
187
- gpuOptions: { device: gpuDevice },
188
- maxTokens: 8192,
189
- temperature: 0.7,
190
- topK: 40,
191
- });
192
-
193
- self.postMessage({ type: 'ready' });
194
- } catch (err) {
195
- self.postMessage({ type: 'error', id: null, message: String(err) });
196
- }
197
- return;
198
- }
199
-
200
- if (type === 'chat' && id && prompt) {
201
- if (!inference) {
202
- self.postMessage({ type: 'error', id, message: 'Model not initialized' });
203
- return;
204
- }
205
-
206
- const maxTokens = (e.data as { maxTokens?: number }).maxTokens;
207
- const temperature = (e.data as { temperature?: number }).temperature;
208
- const topK = (e.data as { topK?: number }).topK;
209
-
210
- if (maxTokens !== undefined || temperature !== undefined || topK !== undefined) {
211
- try {
212
- await inference.setOptions({
213
- ...(maxTokens !== undefined ? { maxTokens } : {}),
214
- ...(temperature !== undefined ? { temperature } : {}),
215
- ...(topK !== undefined ? { topK } : {}),
216
- });
217
- } catch {
218
- // setOptions failure is non-fatal — use defaults
219
- }
220
- }
221
-
222
- cancelRequested = false;
223
- let fullText = '';
224
- let tokenCount = 0;
225
- const t0 = performance.now();
226
-
227
- try {
228
- const result = await inference.generateResponse(prompt, (partialResult: string, done: boolean) => {
229
- if (cancelRequested) {
230
- inference?.cancelProcessing();
231
- return;
232
- }
233
-
234
- fullText += partialResult;
235
- tokenCount++;
236
- self.postMessage({ type: 'token', id, token: partialResult });
237
-
238
- if (done) {
239
- const latencyMs = performance.now() - t0;
240
- self.postMessage({
241
- type: 'done',
242
- id,
243
- text: fullText,
244
- stats: {
245
- tokensPerSec: tokenCount / (latencyMs / 1000),
246
- totalTokens: tokenCount,
247
- latencyMs,
248
- },
249
- });
250
- }
251
- });
252
-
253
- // Fallback if the streaming callback didn't fire 'done'
254
- if (result && !fullText) {
255
- fullText = result;
256
- const latencyMs = performance.now() - t0;
257
- self.postMessage({
258
- type: 'done',
259
- id,
260
- text: fullText,
261
- stats: {
262
- tokensPerSec: tokenCount / (latencyMs / 1000),
263
- totalTokens: tokenCount,
264
- latencyMs,
265
- },
266
- });
267
- }
268
- } catch (err) {
269
- const msg = String(err);
270
- if (cancelRequested || msg.includes('cancel')) {
271
- const latencyMs = performance.now() - t0;
272
- self.postMessage({
273
- type: 'done',
274
- id,
275
- text: fullText,
276
- stats: {
277
- tokensPerSec: tokenCount > 0 ? tokenCount / (latencyMs / 1000) : 0,
278
- totalTokens: tokenCount,
279
- latencyMs,
280
- },
281
- });
282
- } else {
283
- self.postMessage({ type: 'error', id, message: msg });
284
- }
285
- }
286
- return;
287
- }
288
-
289
- if (type === 'abort' && id) {
290
- cancelRequested = true;
291
- inference?.cancelProcessing();
292
- return;
293
- }
294
- };
@@ -1,28 +0,0 @@
1
- ---
2
- widget: actions
3
- description: Row of action buttons
4
- group: simple
5
- schema:
6
- type: object
7
- required:
8
- - buttons
9
- properties:
10
- buttons:
11
- type: array
12
- items:
13
- type: object
14
- required:
15
- - label
16
- properties:
17
- label:
18
- type: string
19
- primary:
20
- type: boolean
21
- ---
22
-
23
- ## When to use
24
- Offer action choices to the user — confirmation, navigation, or selection among multiple options.
25
-
26
- ## How to use
27
- 1. Identify the relevant actions based on context
28
- 2. Call `autoui_webmcp_widget_display('actions', { buttons: [{ label: 'Confirm', primary: true }, { label: 'Cancel' }] })`
@@ -1,27 +0,0 @@
1
- ---
2
- widget: alert
3
- description: System alert or notification
4
- group: simple
5
- schema:
6
- type: object
7
- required:
8
- - title
9
- properties:
10
- title:
11
- type: string
12
- message:
13
- type: string
14
- level:
15
- type: string
16
- enum:
17
- - info
18
- - warn
19
- - error
20
- ---
21
-
22
- ## When to use
23
- Signal important information, a warning, or an error to the user. Useful after an action that requires attention (e.g. threshold exceeded, operation failed).
24
-
25
- ## How to use
26
- 1. Determine the alert level based on context ('info', 'warn', 'error')
27
- 2. Call `autoui_webmcp_widget_display('alert', { title: 'Quota exceeded', message: 'Storage usage is above 90%', level: 'warn' })`
@@ -1,41 +0,0 @@
1
- ---
2
- widget: cards
3
- description: Card grid with title, description, and tags
4
- group: rich
5
- schema:
6
- type: object
7
- required:
8
- - cards
9
- properties:
10
- title:
11
- type: string
12
- cards:
13
- type: array
14
- items:
15
- type: object
16
- required:
17
- - title
18
- properties:
19
- title:
20
- type: string
21
- description:
22
- type: string
23
- subtitle:
24
- type: string
25
- tags:
26
- type: array
27
- items:
28
- type: string
29
- ---
30
-
31
- ## When to use
32
- Display a collection of rich items — products, articles, projects, events. Each card combines a title, description, and tags. Prefer `list` for simple items without structure.
33
-
34
- ## How to use
35
- 1. Retrieve the collection via MCP
36
- 2. Call `autoui_webmcp_widget_display('cards', { title: 'Active projects', cards: [{ title: 'UI Redesign', description: 'Migration to Svelte 5', subtitle: 'Q2 2024', tags: ['frontend', 'high priority'] }] })`
37
-
38
- ## Common mistakes
39
- - NEVER fabricate image URLs for the `image` field. Use ONLY the URLs returned by MCP tools. If no URL is available, do not include an image field — the widget renders correctly without it.
40
- - STRICTLY FORBIDDEN: placeholder URLs (`via.placeholder.com`, `placehold.co`, `dummyimage.com`, `?text=...`). Omit the `image` field rather than using a placeholder.
41
- - Always provide a `title` for each card
@@ -1,39 +0,0 @@
1
- ---
2
- widget: carousel
3
- description: Slide carousel (images or content)
4
- group: media
5
- schema:
6
- type: object
7
- required:
8
- - slides
9
- properties:
10
- title:
11
- type: string
12
- slides:
13
- type: array
14
- items:
15
- type: object
16
- properties:
17
- src:
18
- type: string
19
- title:
20
- type: string
21
- subtitle:
22
- type: string
23
- content:
24
- type: string
25
- autoPlay:
26
- type: boolean
27
- interval:
28
- type: number
29
- ---
30
-
31
- ## When to use
32
- For sequential content browsing — presentations, step-by-step tutorials, narrative galleries. Prefer `gallery` for an overview in grid layout.
33
-
34
- ## How to use
35
- 1. Retrieve or compose the slides
36
- 2. Call `autoui_webmcp_widget_display('carousel', { title: 'Presentation', slides: [{ title: 'Step 1', content: 'Project introduction...' }, { title: 'Step 2', src: 'https://...', subtitle: 'Architecture' }], autoPlay: false })`
37
-
38
- ## Common mistakes
39
- - Never fabricate image URLs for `src` — only use URLs returned by MCP tools
@@ -1,51 +0,0 @@
1
- ---
2
- widget: chart-rich
3
- description: Advanced multi-type chart (bar, line, area, pie)
4
- group: rich
5
- schema:
6
- type: object
7
- required:
8
- - data
9
- properties:
10
- title:
11
- type: string
12
- type:
13
- type: string
14
- enum:
15
- - bar
16
- - line
17
- - area
18
- - pie
19
- - donut
20
- labels:
21
- type: array
22
- items:
23
- type: string
24
- data:
25
- type: array
26
- items:
27
- type: object
28
- required:
29
- - values
30
- properties:
31
- label:
32
- type: string
33
- values:
34
- type: array
35
- items:
36
- type: number
37
- color:
38
- type: string
39
- ---
40
-
41
- ## When to use
42
- For multi-series charts or types other than simple bars (lines, areas, pies, donuts). Prefer `chart` for a basic single-series bar chart.
43
-
44
- ## How to use
45
- 1. Fetch data via MCP
46
- 2. Structure into series with `labels` (X axis) and `data` (value series)
47
- 3. Call `autoui_webmcp_widget_display('chart-rich', { title: 'Monthly Trend', type: 'line', labels: ['Jan', 'Feb', 'Mar'], data: [{ label: '2024', values: [10, 20, 15], color: '#4CAF50' }] })`
48
-
49
- ## Common mistakes
50
- - The number of `values` in each series must match the number of `labels`
51
- - Do not confuse with `chart` (simple widget) — `chart-rich` uses a different data format
@@ -1,32 +0,0 @@
1
- ---
2
- widget: chart
3
- description: Simple bar chart
4
- group: simple
5
- schema:
6
- type: object
7
- required:
8
- - bars
9
- properties:
10
- title:
11
- type: string
12
- bars:
13
- type: array
14
- items:
15
- type: array
16
- items:
17
- - type: string
18
- - type: number
19
- minItems: 2
20
- maxItems: 2
21
- ---
22
-
23
- ## When to use
24
- Use for a quick bar chart with simple categorical data. Prefer `chart-rich` for multi-series charts, line charts, or pie charts.
25
-
26
- ## How to use
27
- 1. Fetch the data via MCP (e.g. counts by category)
28
- 2. Format as an array of `[label, value]` pairs
29
- 3. Call `autoui_webmcp_widget_display('chart', { title: 'Sales by region', bars: [['North', 150], ['South', 230], ['East', 180]] })`
30
-
31
- ## Common mistakes
32
- - Swapping label and value in pairs — the format is `[string, number]`, not `[number, string]`
@@ -1,21 +0,0 @@
1
- ---
2
- widget: code
3
- description: Code block with syntax highlighting
4
- group: simple
5
- schema:
6
- type: object
7
- required:
8
- - content
9
- properties:
10
- lang:
11
- type: string
12
- content:
13
- type: string
14
- ---
15
-
16
- ## When to use
17
- Display source code, snippets, shell commands, or any monospace-formatted output. Specify `lang` to enable syntax highlighting.
18
-
19
- ## How to use
20
- 1. Fetch or generate the code based on the request
21
- 2. Call `autoui_webmcp_widget_display('code', { lang: 'python', content: 'def hello():\n print("Hello")' })`
@@ -1,36 +0,0 @@
1
- ---
2
- widget: d3
3
- description: D3.js visualization with presets (heatmap, radial, treemap, force)
4
- group: advanced
5
- schema:
6
- type: object
7
- required:
8
- - preset
9
- - data
10
- properties:
11
- title:
12
- type: string
13
- preset:
14
- type: string
15
- enum:
16
- - hex-heatmap
17
- - radial
18
- - treemap
19
- - force
20
- data:
21
- type: object
22
- config:
23
- type: object
24
- ---
25
-
26
- ## When to use
27
- For advanced visualizations not covered by other widgets — hexagonal heatmaps, radial charts, hierarchical treemaps, force graphs. Choose the preset based on the data type.
28
-
29
- ## How to use
30
- 1. Retrieve data via MCP
31
- 2. Choose the appropriate preset: `hex-heatmap` for spatial density, `radial` for cyclical data, `treemap` for hierarchies, `force` for relationship graphs
32
- 3. Call `autoui_webmcp_widget_display('d3', { title: 'Budget breakdown', preset: 'treemap', data: { name: 'Budget', children: [{ name: 'R&D', value: 500 }, { name: 'Marketing', value: 300 }] } })`
33
-
34
- ## Common mistakes
35
- - The structure of `data` depends on the chosen preset — consult the preset documentation
36
- - Do not confuse with `chart-rich` which is simpler but covers bar/line/pie
@@ -1,46 +0,0 @@
1
- ---
2
- widget: data-table
3
- description: Sortable data table with configurable columns
4
- group: rich
5
- schema:
6
- type: object
7
- required:
8
- - rows
9
- properties:
10
- title:
11
- type: string
12
- columns:
13
- type: array
14
- items:
15
- type: object
16
- required:
17
- - key
18
- - label
19
- properties:
20
- key:
21
- type: string
22
- label:
23
- type: string
24
- align:
25
- type: string
26
- enum:
27
- - left
28
- - center
29
- - right
30
- rows:
31
- type: array
32
- items:
33
- type: object
34
- ---
35
-
36
- ## When to use
37
- Display tabular data with multiple columns — query results, record lists, inventories. Prefer `kv` for a single entity, `list` for a single column.
38
-
39
- ## How to use
40
- 1. Fetch data via MCP (e.g. SQL result, list of objects)
41
- 2. Optional: define `columns` to control column order and labels
42
- 3. Call `autoui_webmcp_widget_display('data-table', { title: 'Users', columns: [{ key: 'name', label: 'Name' }, { key: 'email', label: 'Email' }], rows: [{ name: 'Alice', email: 'alice@ex.com' }] })`
43
-
44
- ## Common mistakes
45
- - Forgetting that `rows` is an array of objects (not an array of arrays)
46
- - Defining `columns.key` values that do not match the keys in the `rows` objects
@@ -1,39 +0,0 @@
1
- ---
2
- widget: gallery
3
- description: Image gallery in grid layout
4
- group: media
5
- schema:
6
- type: object
7
- required:
8
- - images
9
- properties:
10
- title:
11
- type: string
12
- images:
13
- type: array
14
- items:
15
- type: object
16
- required:
17
- - src
18
- properties:
19
- src:
20
- type: string
21
- alt:
22
- type: string
23
- caption:
24
- type: string
25
- columns:
26
- type: number
27
- ---
28
-
29
- ## When to use
30
- Display a collection of images in a grid — photo gallery, image search results, portfolio. Prefer `carousel` for sequential browsing.
31
-
32
- ## How to use
33
- 1. Retrieve image URLs via MCP (never fabricate URLs)
34
- 2. Call `autoui_webmcp_widget_display('gallery', { title: 'Site photos', images: [{ src: 'https://...', alt: 'Main view', caption: 'North facade' }], columns: 3 })`
35
-
36
- ## Common mistakes
37
- - NEVER fabricate image URLs — only use those returned by MCP tools
38
- - STRICTLY FORBIDDEN: placeholder URLs (`via.placeholder.com`, `placehold.co`, `dummyimage.com`, `?text=...`, `example.com/image.jpg`). If no real image is available, do NOT display a gallery — use a `text` or `cards` widget without an image instead
39
- - Always provide an `alt` for accessibility