@webmcp-auto-ui/ui 2.5.19 → 2.5.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/ui",
3
- "version": "2.5.19",
3
+ "version": "2.5.21",
4
4
  "description": "Svelte 5 UI components — primitives, widgets, window manager",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -50,6 +50,7 @@
50
50
  "@types/d3": "^7.4.3",
51
51
  "bits-ui": "^2.17.2",
52
52
  "clsx": "^2.1.1",
53
+ "html-to-image": "^1.11.13",
53
54
  "tailwind-merge": "^3.5.0",
54
55
  "tailwind-variants": "^3.2.2"
55
56
  }
@@ -11,6 +11,7 @@
11
11
  compressHistory?: boolean;
12
12
  compressPreview?: number;
13
13
  contextRAGEnabled?: boolean;
14
+ ragResidueSize?: number;
14
15
  cacheEnabled?: boolean;
15
16
  temperature?: number;
16
17
  topK?: number;
@@ -29,6 +30,7 @@
29
30
  compressHistory = $bindable(false),
30
31
  compressPreview = $bindable(500),
31
32
  contextRAGEnabled = $bindable(false),
33
+ ragResidueSize = $bindable(200),
32
34
  cacheEnabled = $bindable(true),
33
35
  temperature = $bindable(0.7),
34
36
  topK = $bindable(10),
@@ -210,7 +212,7 @@
210
212
  <div>
211
213
  <label class="flex items-center gap-2 cursor-pointer select-none mb-1">
212
214
  <input type="checkbox" bind:checked={compressHistory} class="accent-accent w-3.5 h-3.5" />
213
- <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Compresser l'historique</span>
215
+ <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Tronquer l'historique</span>
214
216
  </label>
215
217
  {#if compressHistory}
216
218
  <div class="flex justify-between items-baseline mb-1">
@@ -230,9 +232,18 @@
230
232
  <span class="text-[8px] font-mono text-text2/40 ml-auto">experimental</span>
231
233
  </label>
232
234
  {#if contextRAGEnabled}
233
- <div class="text-[9px] font-mono text-text2/60 pl-5">
235
+ <div class="text-[9px] font-mono text-text2/60 pl-5 mb-2">
234
236
  Semantic context compaction via vector embeddings
235
237
  </div>
238
+ <div class="pl-5">
239
+ <div class="flex justify-between items-baseline mb-1">
240
+ <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Résidu inline (chars)</span>
241
+ <span class="font-mono text-xs text-text1">{ragResidueSize}</span>
242
+ </div>
243
+ <input type="range" bind:value={ragResidueSize}
244
+ min={0} max={2000} step={50}
245
+ class="w-full accent-accent" />
246
+ </div>
236
247
  {/if}
237
248
  </section>
238
249
 
@@ -3,6 +3,10 @@
3
3
  * Determines the best export format based on widget type and triggers a file download.
4
4
  */
5
5
 
6
+ import { toPng } from 'html-to-image';
7
+
8
+ const TARGET_PNG_WIDTH = 2048;
9
+
6
10
  // ── helpers ──────────────────────────────────────────────────────────────────
7
11
 
8
12
  function timestamp(): string {
@@ -81,54 +85,37 @@ function exportCsv(type: string, data: Record<string, unknown>): void {
81
85
  downloadFile(lines.join('\r\n'), filename(type, 'csv'), 'text/csv;charset=utf-8');
82
86
  }
83
87
 
84
- // ── PNG via SVG ───────────────────────────────────────────────────────────────
85
-
86
- function svgToPng(svgEl: SVGElement, name: string): void {
87
- // Inline computed styles on the SVG to improve fidelity when rendered off-DOM
88
- const svgData = new XMLSerializer().serializeToString(svgEl);
89
- const canvas = document.createElement('canvas');
90
- const ctx = canvas.getContext('2d')!;
91
- const img = new Image();
92
- const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
93
- const url = URL.createObjectURL(blob);
94
- img.onload = () => {
95
- canvas.width = img.naturalWidth || svgEl.clientWidth || 800;
96
- canvas.height = img.naturalHeight || svgEl.clientHeight || 600;
97
- ctx.drawImage(img, 0, 0);
98
- const pngUrl = canvas.toDataURL('image/png');
99
- downloadFile(pngUrl, name);
100
- URL.revokeObjectURL(url);
101
- };
102
- img.onerror = () => {
103
- // Fallback: try canvas.toDataURL directly if available
104
- URL.revokeObjectURL(url);
105
- };
106
- img.src = url;
107
- }
108
-
109
- function canvasToPng(canvasEl: HTMLCanvasElement, name: string): void {
110
- const url = canvasEl.toDataURL('image/png');
111
- downloadFile(url, name);
112
- }
88
+ // ── PNG via html-to-image ─────────────────────────────────────────────────────
113
89
 
114
- function exportPng(type: string, containerEl: HTMLElement): void {
90
+ async function exportPng(type: string, containerEl: HTMLElement): Promise<void> {
115
91
  const name = filename(type, 'png');
116
-
117
- // Prefer canvas (chart.js renders to <canvas>)
118
- const canvasEl = containerEl.querySelector('canvas');
119
- if (canvasEl) {
120
- canvasToPng(canvasEl, name);
121
- return;
92
+ // Use scroll dimensions to capture the FULL content, not just the visible viewport.
93
+ // Widgets like data-table can overflow horizontally; clientWidth would crop them.
94
+ const scrollW = Math.max(containerEl.scrollWidth, containerEl.clientWidth, 1);
95
+ const scrollH = Math.max(containerEl.scrollHeight, containerEl.clientHeight, 1);
96
+ const pixelRatio = TARGET_PNG_WIDTH / scrollW;
97
+
98
+ const bg = (typeof document !== 'undefined'
99
+ ? getComputedStyle(document.documentElement).getPropertyValue('--color-surface').trim()
100
+ : '') || '#ffffff';
101
+
102
+ try {
103
+ const dataUrl = await toPng(containerEl, {
104
+ pixelRatio,
105
+ width: scrollW,
106
+ height: scrollH,
107
+ cacheBust: true,
108
+ backgroundColor: bg,
109
+ style: {
110
+ // Force children visibility inside the cloned node so overflow content renders
111
+ overflow: 'visible',
112
+ },
113
+ });
114
+ downloadFile(dataUrl, name);
115
+ } catch (err) {
116
+ console.error('[exportWidget] PNG export failed for type', type, err);
117
+ alert(`Export PNG impossible : ${err instanceof Error ? err.message : String(err)}`);
122
118
  }
123
-
124
- // Fall back to SVG
125
- const svgEl = containerEl.querySelector('svg');
126
- if (svgEl) {
127
- svgToPng(svgEl as SVGElement, name);
128
- return;
129
- }
130
-
131
- console.warn('[exportWidget] No <canvas> or <svg> found in container for type:', type);
132
119
  }
133
120
 
134
121
  // ── Markdown ──────────────────────────────────────────────────────────────────
@@ -209,53 +196,39 @@ const HTML_FMT: ExportFormat = { id: 'html', label: 'HTML', icon: '🌐' };
209
196
  * Return the list of export formats available for a given widget type.
210
197
  */
211
198
  export function getExportFormats(type: string, containerEl?: HTMLElement): ExportFormat[] {
199
+ const base: ExportFormat[] = [PNG_FMT, JSON_FMT];
212
200
  switch (type) {
213
201
  case 'data-table':
214
202
  case 'grid-data':
215
203
  case 'kv':
216
204
  case 'list':
217
- return [CSV_FMT, JSON_FMT];
218
-
219
- case 'chart':
220
- case 'chart-rich':
221
- case 'sankey':
222
- case 'd3':
223
- case 'hemicycle':
224
- case 'map':
225
- return [PNG_FMT, JSON_FMT];
226
-
205
+ return [CSV_FMT, ...base];
227
206
  case 'text':
228
207
  case 'code':
229
208
  case 'log':
230
- return [MD_FMT, JSON_FMT];
231
-
209
+ return [MD_FMT, ...base];
232
210
  case 'js-sandbox':
233
- return [HTML_FMT, JSON_FMT];
234
-
235
- case 'gallery':
236
- case 'carousel':
237
- return [JSON_FMT];
238
-
211
+ return [HTML_FMT, ...base];
239
212
  default:
240
- return [JSON_FMT];
213
+ return base;
241
214
  }
242
215
  }
243
216
 
244
217
  /**
245
218
  * Export a widget in a specific format chosen by the user.
246
219
  */
247
- export function exportWidgetAs(
220
+ export async function exportWidgetAs(
248
221
  format: string,
249
222
  type: string,
250
223
  data: Record<string, unknown>,
251
224
  containerEl?: HTMLElement
252
- ): void {
225
+ ): Promise<void> {
253
226
  switch (format) {
254
227
  case 'csv':
255
228
  exportCsv(type, data);
256
229
  break;
257
230
  case 'png':
258
- if (containerEl) exportPng(type, containerEl);
231
+ if (containerEl) await exportPng(type, containerEl);
259
232
  else console.warn('[exportWidgetAs] containerEl required for PNG export');
260
233
  break;
261
234
  case 'md':
@@ -281,11 +254,11 @@ export function exportWidgetAs(
281
254
  * @param data The widget's data object
282
255
  * @param containerEl Optional DOM container — required for PNG exports
283
256
  */
284
- export function exportWidget(
257
+ export async function exportWidget(
285
258
  type: string,
286
259
  data: Record<string, unknown>,
287
260
  containerEl?: HTMLElement
288
- ): void {
261
+ ): Promise<void> {
289
262
  switch (type) {
290
263
  // ── CSV ──
291
264
  case 'data-table':
@@ -303,7 +276,7 @@ export function exportWidget(
303
276
  case 'hemicycle':
304
277
  case 'map':
305
278
  if (containerEl) {
306
- exportPng(type, containerEl);
279
+ await exportPng(type, containerEl);
307
280
  } else {
308
281
  console.warn('[exportWidget] containerEl required for PNG export of type:', type);
309
282
  }