@webmcp-auto-ui/ui 2.5.27 → 2.5.29

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 (76) hide show
  1. package/package.json +18 -5
  2. package/src/agent/LLMSelector.svelte +11 -3
  3. package/src/agent/ModelCacheManager.svelte +359 -0
  4. package/src/index.ts +42 -30
  5. package/src/theme/scale.ts +128 -0
  6. package/src/widgets/WidgetRenderer.svelte +144 -107
  7. package/src/widgets/export-widget.ts +28 -1
  8. package/src/widgets/helpers/safe-image.ts +78 -0
  9. package/src/widgets/notebook/.gitkeep +0 -0
  10. package/src/widgets/notebook/chart-renderer.ts +63 -0
  11. package/src/widgets/notebook/executors/.gitkeep +1 -0
  12. package/src/widgets/notebook/executors/index.ts +4 -0
  13. package/src/widgets/notebook/executors/js-worker.ts +269 -0
  14. package/src/widgets/notebook/executors/sql.ts +206 -0
  15. package/src/widgets/notebook/import-modals.ts +560 -0
  16. package/src/widgets/notebook/left-pane.ts +256 -0
  17. package/src/widgets/notebook/notebook.ts +930 -0
  18. package/src/widgets/notebook/prose.ts +615 -0
  19. package/src/widgets/notebook/recipe-browser.ts +350 -0
  20. package/src/widgets/notebook/recipes/notebook.md +124 -0
  21. package/src/widgets/notebook/resource-extractor.ts +162 -0
  22. package/src/widgets/notebook/share-handlers.ts +222 -0
  23. package/src/widgets/notebook/shared.ts +1633 -0
  24. package/src/widgets/rich/cards.ts +181 -0
  25. package/src/widgets/rich/carousel.ts +319 -0
  26. package/src/widgets/rich/chart-rich.ts +386 -0
  27. package/src/widgets/rich/d3.ts +503 -0
  28. package/src/widgets/rich/data-table.ts +342 -0
  29. package/src/widgets/rich/gallery.ts +350 -0
  30. package/src/widgets/rich/grid-data.ts +173 -0
  31. package/src/widgets/rich/hemicycle.ts +313 -0
  32. package/src/widgets/rich/js-sandbox.ts +122 -0
  33. package/src/widgets/rich/json-viewer.ts +202 -0
  34. package/src/widgets/rich/log.ts +143 -0
  35. package/src/widgets/rich/map.ts +218 -0
  36. package/src/widgets/rich/profile.ts +256 -0
  37. package/src/widgets/rich/sankey.ts +257 -0
  38. package/src/widgets/rich/stat-card.ts +125 -0
  39. package/src/widgets/rich/timeline.ts +179 -0
  40. package/src/widgets/rich/trombinoscope.ts +246 -0
  41. package/src/widgets/simple/actions.ts +89 -0
  42. package/src/widgets/simple/alert.ts +100 -0
  43. package/src/widgets/simple/chart.ts +189 -0
  44. package/src/widgets/simple/code.ts +79 -0
  45. package/src/widgets/simple/kv.ts +68 -0
  46. package/src/widgets/simple/list.ts +89 -0
  47. package/src/widgets/simple/stat.ts +58 -0
  48. package/src/widgets/simple/tags.ts +125 -0
  49. package/src/widgets/simple/text.ts +198 -0
  50. package/src/widgets/SafeImage.svelte +0 -76
  51. package/src/widgets/rich/Cards.svelte +0 -39
  52. package/src/widgets/rich/Carousel.svelte +0 -88
  53. package/src/widgets/rich/Chart.svelte +0 -142
  54. package/src/widgets/rich/D3Widget.svelte +0 -378
  55. package/src/widgets/rich/DataTable.svelte +0 -62
  56. package/src/widgets/rich/Gallery.svelte +0 -94
  57. package/src/widgets/rich/GridData.svelte +0 -44
  58. package/src/widgets/rich/Hemicycle.svelte +0 -78
  59. package/src/widgets/rich/JsSandbox.svelte +0 -51
  60. package/src/widgets/rich/JsonViewer.svelte +0 -42
  61. package/src/widgets/rich/LogViewer.svelte +0 -24
  62. package/src/widgets/rich/MapView.svelte +0 -140
  63. package/src/widgets/rich/ProfileCard.svelte +0 -59
  64. package/src/widgets/rich/Sankey.svelte +0 -56
  65. package/src/widgets/rich/StatCard.svelte +0 -35
  66. package/src/widgets/rich/Timeline.svelte +0 -43
  67. package/src/widgets/rich/Trombinoscope.svelte +0 -48
  68. package/src/widgets/simple/ActionsBlock.svelte +0 -15
  69. package/src/widgets/simple/AlertBlock.svelte +0 -11
  70. package/src/widgets/simple/ChartBlock.svelte +0 -21
  71. package/src/widgets/simple/CodeBlock.svelte +0 -11
  72. package/src/widgets/simple/KVBlock.svelte +0 -16
  73. package/src/widgets/simple/ListBlock.svelte +0 -17
  74. package/src/widgets/simple/StatBlock.svelte +0 -14
  75. package/src/widgets/simple/TagsBlock.svelte +0 -15
  76. package/src/widgets/simple/TextBlock.svelte +0 -122
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/ui",
3
- "version": "2.5.27",
3
+ "version": "2.5.29",
4
4
  "description": "Svelte 5 UI components — primitives, widgets, window manager",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -15,7 +15,12 @@
15
15
  "./canvas": {
16
16
  "svelte": "./src/stores/canvas.svelte.ts",
17
17
  "import": "./src/stores/canvas.svelte.ts"
18
- }
18
+ },
19
+ "./widgets/notebook/*": {
20
+ "svelte": "./src/widgets/notebook/*",
21
+ "import": "./src/widgets/notebook/*"
22
+ },
23
+ "./widgets/notebook/recipes/*": "./src/widgets/notebook/recipes/*"
19
24
  },
20
25
  "scripts": {
21
26
  "build": "svelte-package -i src",
@@ -25,7 +30,13 @@
25
30
  "peerDependencies": {
26
31
  "d3": "^7.9.0",
27
32
  "leaflet": ">=1.9.0",
28
- "svelte": "^5.0.0"
33
+ "svelte": "^5.0.0",
34
+ "vega-embed": "^6.24.0"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "vega-embed": {
38
+ "optional": true
39
+ }
29
40
  },
30
41
  "devDependencies": {
31
42
  "@sveltejs/package": "^2.3.0",
@@ -46,14 +57,16 @@
46
57
  "auto-ui"
47
58
  ],
48
59
  "dependencies": {
49
- "@webmcp-auto-ui/sdk": "*",
50
60
  "@types/d3": "^7.4.3",
61
+ "@webmcp-auto-ui/core": "*",
62
+ "@webmcp-auto-ui/sdk": "*",
51
63
  "bits-ui": "^2.17.2",
52
64
  "clsx": "^2.1.1",
53
65
  "highlight.js": "^11.10.0",
54
66
  "html-to-image": "^1.11.13",
55
67
  "marked": "^14.1.3",
56
68
  "tailwind-merge": "^3.5.0",
57
- "tailwind-variants": "^3.2.2"
69
+ "tailwind-variants": "^3.2.2",
70
+ "turndown": "^7.2.4"
58
71
  }
59
72
  }
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
- import { listTransformersModels } from '@webmcp-auto-ui/agent';
2
+ import { listTransformersModels, listHawkModels } from '@webmcp-auto-ui/agent';
3
3
 
4
4
  export interface ModelOption {
5
5
  value: string;
6
6
  label: string;
7
- group: 'remote' | 'wasm' | 'transformers' | 'local';
7
+ group: 'remote' | 'wasm' | 'transformers' | 'local' | 'hawk';
8
8
  }
9
9
 
10
10
  const transformersOptions: ModelOption[] = listTransformersModels().map(({ id, entry }) => ({
@@ -13,15 +13,23 @@
13
13
  group: 'transformers' as const,
14
14
  }));
15
15
 
16
+ const hawkOptions: ModelOption[] = listHawkModels().map(m => ({
17
+ value: `hawk-${m.id}`,
18
+ label: m.label,
19
+ group: 'hawk' as const,
20
+ }));
21
+
16
22
  const DEFAULT_MODELS: ModelOption[] = [
17
23
  { value: 'haiku', label: 'claude-haiku-4-5', group: 'remote' },
18
24
  { value: 'gemma-e2b', label: 'Gemma E2B (MediaPipe)', group: 'wasm' },
19
25
  { value: 'gemma-e4b', label: 'Gemma E4B (MediaPipe)', group: 'wasm' },
20
26
  ...transformersOptions,
27
+ ...hawkOptions,
21
28
  ];
22
29
 
23
30
  const GROUP_LABELS: Record<string, string> = {
24
31
  remote: 'Remote',
32
+ hawk: 'Remote (Hawk)',
25
33
  wasm: 'In-Browser (MediaPipe)',
26
34
  transformers: 'In-Browser (Transformers.js)',
27
35
  local: 'Local',
@@ -36,7 +44,7 @@
36
44
  let { value, onchange, models = DEFAULT_MODELS, class: cls = '' }: Props = $props();
37
45
 
38
46
  const groups = $derived(
39
- ['remote', 'wasm', 'transformers', 'local'].filter(g => models.some(m => m.group === g))
47
+ ['remote', 'hawk', 'wasm', 'transformers', 'local'].filter(g => models.some(m => m.group === g))
40
48
  );
41
49
  </script>
42
50
 
@@ -0,0 +1,359 @@
1
+ <script lang="ts">
2
+ import {
3
+ listCachedModels, clearModelCache, clearAllModelCaches, type CachedModelInfo,
4
+ listAllStorage, deleteStorageEntry, clearAllStorage, type StorageEntry,
5
+ } from '@webmcp-auto-ui/agent';
6
+
7
+ interface Props {
8
+ class?: string;
9
+ }
10
+ let { class: cls = '' }: Props = $props();
11
+
12
+ let models = $state<CachedModelInfo[]>([]);
13
+ let storage = $state<StorageEntry[]>([]);
14
+ let usage = $state<number | null>(null);
15
+ let quota = $state<number | null>(null);
16
+ let loading = $state(true);
17
+ let supported = $state(true);
18
+ let busy = $state<string | null>(null);
19
+ let collapsed = $state(true);
20
+
21
+ let opfsCollapsed = $state(false);
22
+ let cacheCollapsed = $state(true);
23
+ let idbCollapsed = $state(true);
24
+
25
+ const totalSize = $derived(models.reduce((s, m) => s + m.size, 0));
26
+ const usagePct = $derived(quota && quota > 0 && usage !== null ? Math.min(100, (usage / quota) * 100) : 0);
27
+
28
+ const opfsExtras = $derived(storage.filter((e) => e.source === 'opfs'));
29
+ const cacheEntries = $derived(storage.filter((e) => e.source === 'cache-storage'));
30
+ const idbEntries = $derived(storage.filter((e) => e.source === 'indexeddb'));
31
+
32
+ const cacheTotalSize = $derived(cacheEntries.reduce((s, e) => s + e.size, 0));
33
+ const opfsExtrasTotalSize = $derived(opfsExtras.reduce((s, e) => s + e.size, 0));
34
+
35
+ function formatBytes(n: number): string {
36
+ if (!Number.isFinite(n) || n <= 0) return '0 B';
37
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
38
+ let v = n;
39
+ let i = 0;
40
+ while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
41
+ return `${v.toFixed(v >= 100 || i === 0 ? 0 : v >= 10 ? 1 : 2)} ${units[i]}`;
42
+ }
43
+
44
+ function formatDate(ms: number): string {
45
+ if (!ms) return '—';
46
+ const d = new Date(ms);
47
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
48
+ }
49
+
50
+ function formatSize(e: StorageEntry): string {
51
+ if (!e.sizeKnown) return 'taille inconnue';
52
+ return formatBytes(e.size);
53
+ }
54
+
55
+ async function refresh() {
56
+ loading = true;
57
+ try {
58
+ if (typeof navigator === 'undefined' || !navigator.storage?.getDirectory) {
59
+ supported = false;
60
+ return;
61
+ }
62
+ supported = true;
63
+ const [ms, sto] = await Promise.all([listCachedModels(), listAllStorage()]);
64
+ models = ms;
65
+ storage = sto;
66
+ try {
67
+ const est = await navigator.storage.estimate();
68
+ usage = est.usage ?? null;
69
+ quota = est.quota ?? null;
70
+ } catch {
71
+ usage = null;
72
+ quota = null;
73
+ }
74
+ } finally {
75
+ loading = false;
76
+ }
77
+ }
78
+
79
+ async function deleteOne(repo: string) {
80
+ if (!confirm(`Supprimer le cache "${repo}" ?`)) return;
81
+ busy = `opfs:${repo}`;
82
+ try {
83
+ await clearModelCache(repo);
84
+ await refresh();
85
+ } finally {
86
+ busy = null;
87
+ }
88
+ }
89
+
90
+ async function deleteAll() {
91
+ if (!confirm(`Supprimer TOUS les modèles en cache (${models.length} repo·s, ${formatBytes(totalSize)}) ?`)) return;
92
+ busy = '__opfs_all__';
93
+ try {
94
+ await clearAllModelCaches();
95
+ await refresh();
96
+ } finally {
97
+ busy = null;
98
+ }
99
+ }
100
+
101
+ async function deleteEntry(e: StorageEntry) {
102
+ const label = `${e.source} · ${e.key}`;
103
+ if (!confirm(`Supprimer ${label} ?`)) return;
104
+ busy = `${e.source}:${e.key}`;
105
+ try {
106
+ await deleteStorageEntry(e);
107
+ await refresh();
108
+ } finally {
109
+ busy = null;
110
+ }
111
+ }
112
+
113
+ async function deleteSource(source: 'opfs' | 'cache-storage' | 'indexeddb', count: number) {
114
+ if (!confirm(`Supprimer TOUT le ${source} (${count} entrée·s) ?`)) return;
115
+ busy = `__${source}_all__`;
116
+ try {
117
+ await clearAllStorage(source);
118
+ await refresh();
119
+ } finally {
120
+ busy = null;
121
+ }
122
+ }
123
+
124
+ $effect(() => { refresh(); });
125
+ </script>
126
+
127
+ <div class="flex flex-col gap-2 {cls}">
128
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
129
+ <div class="flex items-center gap-1 cursor-pointer select-none"
130
+ onclick={() => collapsed = !collapsed}>
131
+ <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Model cache (OPFS)</span>
132
+ <span class="text-[9px] text-text2/60 font-mono">
133
+ {#if !supported}(n/a){:else if loading}(…){:else}({models.length} · {formatBytes(totalSize)}){/if}
134
+ </span>
135
+ <span class="text-[10px] text-text2 ml-auto transition-transform {collapsed ? '' : 'rotate-90'}">{@html '&#x25B6;'}</span>
136
+ </div>
137
+
138
+ {#if !collapsed}
139
+ <div class="text-[9px] font-mono text-text2/50 -mt-1">OPFS + Cache Storage + IndexedDB</div>
140
+
141
+ {#if !supported}
142
+ <div class="text-[10px] font-mono text-text2/60 p-2 border border-border2 rounded">
143
+ OPFS indisponible dans ce navigateur.
144
+ </div>
145
+ {:else if loading}
146
+ <div class="text-[10px] font-mono text-text2/60">Lecture du cache…</div>
147
+ {:else}
148
+ {#if quota !== null && usage !== null}
149
+ <div class="flex flex-col gap-1">
150
+ <div class="flex items-center justify-between font-mono text-[10px] text-text2">
151
+ <span>{formatBytes(usage)} / {formatBytes(quota)}</span>
152
+ <span class="text-text2/60">origine entière · {usagePct.toFixed(1)}%</span>
153
+ </div>
154
+ <div class="h-1 bg-surface2 rounded overflow-hidden">
155
+ <div class="h-full bg-teal transition-all" style="width: {usagePct}%"></div>
156
+ </div>
157
+ </div>
158
+ {/if}
159
+
160
+ <!-- ── LLM models (OPFS webmcp-models) ───────────────────────────── -->
161
+ <div class="flex flex-col gap-1">
162
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
163
+ <div class="flex items-center gap-1 cursor-pointer select-none"
164
+ onclick={() => opfsCollapsed = !opfsCollapsed}>
165
+ <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">LLM models (OPFS)</span>
166
+ <span class="text-[9px] text-text2/60 font-mono">({models.length} · {formatBytes(totalSize)})</span>
167
+ <span class="text-[10px] text-text2 ml-auto transition-transform {opfsCollapsed ? '' : 'rotate-90'}">{@html '&#x25B6;'}</span>
168
+ </div>
169
+
170
+ {#if !opfsCollapsed}
171
+ <div class="flex items-center justify-between">
172
+ <span class="font-mono text-[10px] text-text2">
173
+ Modèles : <span class="text-text1">{models.length}</span> · {formatBytes(totalSize)}
174
+ </span>
175
+ {#if models.length > 0}
176
+ <button
177
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/40 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40"
178
+ disabled={busy !== null}
179
+ onclick={deleteAll}
180
+ >
181
+ {busy === '__opfs_all__' ? 'Suppression…' : 'Tout supprimer'}
182
+ </button>
183
+ {/if}
184
+ </div>
185
+
186
+ {#if models.length === 0}
187
+ <div class="text-[10px] font-mono text-text2/60 p-2 border border-dashed border-border2 rounded text-center">
188
+ Aucun modèle en cache.
189
+ </div>
190
+ {:else}
191
+ <ul class="flex flex-col gap-1">
192
+ {#each models as m (m.repo)}
193
+ <li class="flex items-center gap-2 p-2 rounded border border-border2 bg-surface2/40">
194
+ <div class="flex-1 min-w-0 flex flex-col gap-0.5">
195
+ <span class="font-mono text-[11px] text-text1 truncate" title={m.repo}>{m.repo}</span>
196
+ <span class="font-mono text-[9px] text-text2/60">
197
+ {formatBytes(m.size)} · {m.fileCount} fichier{m.fileCount > 1 ? 's' : ''} · {formatDate(m.lastModified)}
198
+ </span>
199
+ </div>
200
+ <button
201
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/30 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40 flex-shrink-0"
202
+ disabled={busy !== null}
203
+ onclick={() => deleteOne(m.repo)}
204
+ title="Supprimer ce cache"
205
+ >
206
+ {busy === `opfs:${m.repo}` ? '…' : 'Supprimer'}
207
+ </button>
208
+ </li>
209
+ {/each}
210
+ </ul>
211
+ {/if}
212
+
213
+ {#if opfsExtras.length > 0}
214
+ <div class="text-[9px] font-mono text-text2/50 mt-1">Autres OPFS : {opfsExtras.length} · {formatBytes(opfsExtrasTotalSize)}</div>
215
+ <ul class="flex flex-col gap-1">
216
+ {#each opfsExtras as e (e.key)}
217
+ <li class="flex items-center gap-2 p-2 rounded border border-border2 bg-surface2/40">
218
+ <div class="flex-1 min-w-0 flex flex-col gap-0.5">
219
+ <span class="font-mono text-[11px] text-text1 truncate flex items-center gap-1" title={e.key}>
220
+ {#if e.modelLike}<span class="text-accent text-[9px]">model</span>{/if}
221
+ {e.key}
222
+ </span>
223
+ <span class="font-mono text-[9px] text-text2/60">
224
+ {formatSize(e)} · {e.itemCount} fichier{e.itemCount > 1 ? 's' : ''} · {formatDate(e.lastModified)}
225
+ </span>
226
+ </div>
227
+ <button
228
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/30 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40 flex-shrink-0"
229
+ disabled={busy !== null}
230
+ onclick={() => deleteEntry(e)}
231
+ title="Supprimer ce répertoire OPFS"
232
+ >
233
+ {busy === `opfs:${e.key}` ? '…' : 'Supprimer'}
234
+ </button>
235
+ </li>
236
+ {/each}
237
+ </ul>
238
+ {/if}
239
+ {/if}
240
+ </div>
241
+
242
+ <!-- ── Cache Storage ─────────────────────────────────────────────── -->
243
+ <div class="flex flex-col gap-1">
244
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
245
+ <div class="flex items-center gap-1 cursor-pointer select-none"
246
+ onclick={() => cacheCollapsed = !cacheCollapsed}>
247
+ <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Cache Storage</span>
248
+ <span class="text-[9px] text-text2/60 font-mono">({cacheEntries.length} · {formatBytes(cacheTotalSize)})</span>
249
+ <span class="text-[10px] text-text2 ml-auto transition-transform {cacheCollapsed ? '' : 'rotate-90'}">{@html '&#x25B6;'}</span>
250
+ </div>
251
+
252
+ {#if !cacheCollapsed}
253
+ <div class="flex items-center justify-between">
254
+ <span class="font-mono text-[10px] text-text2">
255
+ Caches : <span class="text-text1">{cacheEntries.length}</span> · {formatBytes(cacheTotalSize)}
256
+ </span>
257
+ {#if cacheEntries.length > 0}
258
+ <button
259
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/40 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40"
260
+ disabled={busy !== null}
261
+ onclick={() => deleteSource('cache-storage', cacheEntries.length)}
262
+ >
263
+ {busy === '__cache-storage_all__' ? 'Suppression…' : 'Tout supprimer'}
264
+ </button>
265
+ {/if}
266
+ </div>
267
+
268
+ {#if cacheEntries.length === 0}
269
+ <div class="text-[10px] font-mono text-text2/60 p-2 border border-dashed border-border2 rounded text-center">
270
+ Aucun cache.
271
+ </div>
272
+ {:else}
273
+ <ul class="flex flex-col gap-1">
274
+ {#each cacheEntries as e (e.key)}
275
+ <li class="flex items-center gap-2 p-2 rounded border border-border2 bg-surface2/40">
276
+ <div class="flex-1 min-w-0 flex flex-col gap-0.5">
277
+ <span class="font-mono text-[11px] text-text1 truncate flex items-center gap-1" title={e.key}>
278
+ {#if e.modelLike}<span class="text-accent text-[9px]">model</span>{/if}
279
+ {e.key}
280
+ </span>
281
+ <span class="font-mono text-[9px] text-text2/60">
282
+ {formatSize(e)} · {e.itemCount} item{e.itemCount > 1 ? 's' : ''} · {formatDate(e.lastModified)}
283
+ </span>
284
+ </div>
285
+ <button
286
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/30 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40 flex-shrink-0"
287
+ disabled={busy !== null}
288
+ onclick={() => deleteEntry(e)}
289
+ title="Supprimer ce cache"
290
+ >
291
+ {busy === `cache-storage:${e.key}` ? '…' : 'Supprimer'}
292
+ </button>
293
+ </li>
294
+ {/each}
295
+ </ul>
296
+ {/if}
297
+ {/if}
298
+ </div>
299
+
300
+ <!-- ── IndexedDB ─────────────────────────────────────────────────── -->
301
+ <div class="flex flex-col gap-1">
302
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
303
+ <div class="flex items-center gap-1 cursor-pointer select-none"
304
+ onclick={() => idbCollapsed = !idbCollapsed}>
305
+ <span class="text-[9px] font-mono text-text2 uppercase tracking-wider">IndexedDB</span>
306
+ <span class="text-[9px] text-text2/60 font-mono">({idbEntries.length})</span>
307
+ <span class="text-[10px] text-text2 ml-auto transition-transform {idbCollapsed ? '' : 'rotate-90'}">{@html '&#x25B6;'}</span>
308
+ </div>
309
+
310
+ {#if !idbCollapsed}
311
+ <div class="flex items-center justify-between">
312
+ <span class="font-mono text-[10px] text-text2">
313
+ Bases : <span class="text-text1">{idbEntries.length}</span> · taille inconnue
314
+ </span>
315
+ {#if idbEntries.length > 0}
316
+ <button
317
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/40 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40"
318
+ disabled={busy !== null}
319
+ onclick={() => deleteSource('indexeddb', idbEntries.length)}
320
+ >
321
+ {busy === '__indexeddb_all__' ? 'Suppression…' : 'Tout supprimer'}
322
+ </button>
323
+ {/if}
324
+ </div>
325
+
326
+ {#if idbEntries.length === 0}
327
+ <div class="text-[10px] font-mono text-text2/60 p-2 border border-dashed border-border2 rounded text-center">
328
+ Aucune base IndexedDB.
329
+ </div>
330
+ {:else}
331
+ <ul class="flex flex-col gap-1">
332
+ {#each idbEntries as e (e.key)}
333
+ <li class="flex items-center gap-2 p-2 rounded border border-border2 bg-surface2/40">
334
+ <div class="flex-1 min-w-0 flex flex-col gap-0.5">
335
+ <span class="font-mono text-[11px] text-text1 truncate flex items-center gap-1" title={e.key}>
336
+ {#if e.modelLike}<span class="text-accent text-[9px]">model</span>{/if}
337
+ {e.key}
338
+ </span>
339
+ <span class="font-mono text-[9px] text-text2/60">
340
+ taille inconnue · version {e.itemCount}
341
+ </span>
342
+ </div>
343
+ <button
344
+ class="font-mono text-[10px] h-6 px-2 rounded border border-accent2/30 text-accent2 hover:bg-accent2/10 transition-colors disabled:opacity-40 flex-shrink-0"
345
+ disabled={busy !== null}
346
+ onclick={() => deleteEntry(e)}
347
+ title="Supprimer cette base"
348
+ >
349
+ {busy === `indexeddb:${e.key}` ? '…' : 'Supprimer'}
350
+ </button>
351
+ </li>
352
+ {/each}
353
+ </ul>
354
+ {/if}
355
+ {/if}
356
+ </div>
357
+ {/if}
358
+ {/if}
359
+ </div>
package/src/index.ts CHANGED
@@ -5,6 +5,8 @@ export { default as ThemeProvider, getTheme } from './theme/ThemeProvider.svelte
5
5
  export type { ThemeJSON } from './theme/ThemeProvider.svelte';
6
6
  export { DARK_TOKENS, LIGHT_TOKENS, THEME_MAP } from './theme/tokens.js';
7
7
  export type { ThemeMode, ThemeOverrides, ThemeTokens } from './theme/tokens.js';
8
+ export { getUIScale, setUIScale, toggleUIScale, initUIScale, isUIScaled } from './theme/scale.js';
9
+ export type { UIScale, UIScaleKey } from './theme/scale.js';
8
10
 
9
11
  // Primitives
10
12
  export { default as Card } from './primitives/Card.svelte';
@@ -16,38 +18,47 @@ export { default as MarkdownView } from './primitives/MarkdownView.svelte';
16
18
  export { default as CodeView } from './primitives/CodeView.svelte';
17
19
  export { renderMarkdown, highlightCode, createMarkdownRenderer } from './primitives/markdown-renderer.js';
18
20
 
19
- // Simple widgets (PJ blocks)
20
- export { default as StatBlock } from './widgets/simple/StatBlock.svelte';
21
- export { default as KVBlock } from './widgets/simple/KVBlock.svelte';
22
- export { default as ListBlock } from './widgets/simple/ListBlock.svelte';
23
- export { default as ChartBlock } from './widgets/simple/ChartBlock.svelte';
24
- export { default as AlertBlock } from './widgets/simple/AlertBlock.svelte';
25
- export { default as CodeBlock } from './widgets/simple/CodeBlock.svelte';
26
- export { default as TextBlock } from './widgets/simple/TextBlock.svelte';
27
- export { default as ActionsBlock } from './widgets/simple/ActionsBlock.svelte';
28
- export { default as TagsBlock } from './widgets/simple/TagsBlock.svelte';
21
+ // Simple widgets (vanilla renderers) — contract: render(container, data): () => void
22
+ export { render as renderStat } from './widgets/simple/stat.js';
23
+ export { render as renderKv } from './widgets/simple/kv.js';
24
+ export { render as renderList } from './widgets/simple/list.js';
25
+ export { render as renderChart } from './widgets/simple/chart.js';
26
+ export { render as renderAlert } from './widgets/simple/alert.js';
27
+ export { render as renderCode } from './widgets/simple/code.js';
28
+ export { render as renderText } from './widgets/simple/text.js';
29
+ export { render as renderActions } from './widgets/simple/actions.js';
30
+ export { render as renderTags } from './widgets/simple/tags.js';
29
31
 
30
- // Rich widgets (Archive)
31
- export { default as StatCard } from './widgets/rich/StatCard.svelte';
32
- export { default as DataTable } from './widgets/rich/DataTable.svelte';
33
- export { default as Timeline } from './widgets/rich/Timeline.svelte';
34
- export { default as ProfileCard } from './widgets/rich/ProfileCard.svelte';
35
- export { default as Trombinoscope } from './widgets/rich/Trombinoscope.svelte';
36
- export { default as JsonViewer } from './widgets/rich/JsonViewer.svelte';
37
- export { default as Hemicycle } from './widgets/rich/Hemicycle.svelte';
38
- export { default as Chart } from './widgets/rich/Chart.svelte';
39
- export { default as Cards } from './widgets/rich/Cards.svelte';
40
- export { default as GridData } from './widgets/rich/GridData.svelte';
41
- export { default as Sankey } from './widgets/rich/Sankey.svelte';
42
- export { default as MapView } from './widgets/rich/MapView.svelte';
43
- export { default as D3Widget } from './widgets/rich/D3Widget.svelte';
44
- export { default as JsSandbox } from './widgets/rich/JsSandbox.svelte';
45
- export { default as LogViewer } from './widgets/rich/LogViewer.svelte';
46
- export { default as Gallery } from './widgets/rich/Gallery.svelte';
47
- export { default as Carousel } from './widgets/rich/Carousel.svelte';
32
+ // Rich widgets (vanilla renderers)
33
+ export { render as renderStatCard } from './widgets/rich/stat-card.js';
34
+ export { render as renderDataTable } from './widgets/rich/data-table.js';
35
+ export { render as renderTimeline } from './widgets/rich/timeline.js';
36
+ export { render as renderProfile } from './widgets/rich/profile.js';
37
+ export { render as renderTrombinoscope } from './widgets/rich/trombinoscope.js';
38
+ export { render as renderJsonViewer } from './widgets/rich/json-viewer.js';
39
+ export { render as renderHemicycle } from './widgets/rich/hemicycle.js';
40
+ export { render as renderChartRich } from './widgets/rich/chart-rich.js';
41
+ export { render as renderCards } from './widgets/rich/cards.js';
42
+ export { render as renderGridData } from './widgets/rich/grid-data.js';
43
+ export { render as renderSankey } from './widgets/rich/sankey.js';
44
+ export { render as renderMap } from './widgets/rich/map.js';
45
+ export { render as renderD3 } from './widgets/rich/d3.js';
46
+ export { render as renderJsSandbox } from './widgets/rich/js-sandbox.js';
47
+ export { render as renderLog } from './widgets/rich/log.js';
48
+ export { render as renderGallery } from './widgets/rich/gallery.js';
49
+ export { render as renderCarousel } from './widgets/rich/carousel.js';
48
50
 
49
- // Safe image (URL validation + error fallback)
50
- export { default as SafeImage } from './widgets/SafeImage.svelte';
51
+ // Notebook widget renderer (vanilla)
52
+ export { render as renderNotebook } from './widgets/notebook/notebook.js';
53
+ export { render as renderRecipeBrowserWidget } from './widgets/notebook/recipe-browser.js';
54
+ // Notebook types (optional public API)
55
+ export type { NotebookState, NotebookCell } from './widgets/notebook/shared.js';
56
+ // Notebook cell extractors (for hosts that build notebooks from recipes/tools)
57
+ export { extractCellsFromRecipe, extractCellsFromTool, extractCellFromMarkdown, extractCellFromFence } from './widgets/notebook/resource-extractor.js';
58
+
59
+ // Safe image helper (URL validation + error fallback)
60
+ export { createSafeImage } from './widgets/helpers/safe-image.js';
61
+ export type { SafeImageOptions } from './widgets/helpers/safe-image.js';
51
62
 
52
63
  // Widget export utility
53
64
  export { exportWidget, getExportFormats, exportWidgetAs } from './widgets/export-widget.js';
@@ -90,6 +101,7 @@ export { default as ModelLoader } from './agent/ModelLoader.svelte';
90
101
  /** @deprecated Use ModelLoader instead. Alias maintained for backward compatibility. */
91
102
  export { default as GemmaLoader } from './agent/ModelLoader.svelte';
92
103
  export { default as McpStatus } from './agent/McpStatus.svelte';
104
+ export { default as ModelCacheManager } from './agent/ModelCacheManager.svelte';
93
105
  export { default as AgentProgress } from './agent/AgentProgress.svelte';
94
106
  export { default as McpConnector } from './agent/McpConnector.svelte';
95
107
  export { default as ChatPanel } from './agent/ChatPanel.svelte';