Aller au contenu

Widgets UI

Imaginez un jeu de LEGO specialise : chaque brique a une forme precise (un graphique, un tableau, une carte) et s’emboite naturellement avec les autres. L’agent IA est le constructeur : il choisit les briques et les assemble pour creer un dashboard complet. Les widgets UI sont ces briques.

Un widget est un composant visuel declaratif que l’agent IA peut instancier en un seul appel d’outil. L’agent n’ecrit pas de HTML, pas de CSS, pas de JavaScript : il envoie un type et un objet data, et le widget se rend tout seul.

// L'agent appelle :
widget_display({ name: "stat", params: { label: "Revenue", value: "$142K", trend: "+12.4%", trendDir: "up" } })
// Le canvas affiche instantanement une carte KPI avec une fleche verte vers le haut

Sans widgets, un agent IA devrait generer du HTML brut pour afficher des donnees. Cela poserait trois problemes :

  1. Securite : du HTML genere par un LLM pourrait contenir du code malveillant
  2. Coherence : chaque dashboard aurait un style different
  3. Tokens : decrire du HTML consomme enormement de tokens

Les widgets resolvent tout cela : l’agent envoie des donnees structurees (JSON valide un schema), et le framework se charge du rendu.

Le flux de selection suit le systeme de recettes et de decouverte :

sequenceDiagram
participant LLM as Agent LLM
participant SR as search_recipes
participant GR as get_recipe
participant WD as widget_display
LLM->>SR: search_recipes("kpi")
SR-->>LLM: [{name: "stat", desc: "KPI..."}, {name: "stat-card", desc: "KPI enrichi..."}]
LLM->>GR: get_recipe("stat-card")
GR-->>LLM: {schema: {...}, recipe: "## Quand utiliser\nPour un KPI enrichi..."}
LLM->>WD: widget_display({name: "stat-card", params: {label: "Uptime", value: "99.9%"}})
WD-->>LLM: {widget: "stat-card", id: "w_a3f2"}

L’agent peut aussi utiliser les recettes WebMCP (fichiers .md) qui lui indiquent quel widget utiliser en fonction du type de donnees. Par exemple, la recette dashboard-kpi recommande : stat-card pour les metriques, chart pour les series temporelles, data-table pour les details.

Le projet supporte deux modes de rendu :

ModeTechnologieUsage
Svelte<BlockRenderer> / <WidgetRenderer>Apps SvelteKit (flex, viewer)
VanillamountWidget()Apps sans framework, integration externe

Le composant BlockRenderer recoit { type, data } et dispatche vers le widget Svelte correspondant :

<script>
import { BlockRenderer } from '@webmcp-auto-ui/ui';
</script>
<BlockRenderer type="stat" data={{ label: 'Revenue', value: '$142K' }} />
  • Blocs simples (stat, kv, list…) : donnees via la prop data
  • Blocs riches (stat-card, data-table…) : donnees via la prop spec
  • Types inconnus : affiche un placeholder [type]

Pour les apps sans framework :

import { mountWidget } from '@webmcp-auto-ui/core';
const container = document.getElementById('my-widget');
const cleanup = mountWidget(container, 'stat', { label: 'Revenue', value: '$142K' }, [autoui]);
// Nettoyer a la destruction
cleanup?.();

Chaque bloc rendu sur le canvas s’auto-enregistre comme outil WebMCP (via navigator.modelContext). Cela permet a l’agent de modifier les widgets deja affiches :

block_<id>_get → lire les donnees actuelles du widget
block_<id>_update → mettre a jour les donnees
block_<id>_remove → retirer le widget du canvas

Ce mecanisme cree une boucle de retro-action : l’agent affiche, l’utilisateur interagit, l’agent reagit.

Les widgets sont organises en 5 groupes :

GroupeWidgetsUsage
simplestat, kv, list, chart, alert, code, text, actions, tagsBlocs de base, donnees simples
richstat-card, data-table, timeline, profile, trombinoscope, json-viewer, hemicycle, chart-rich, cards, grid-data, sankey, logVisualisations complexes
mediagallery, carousel, mapImages et geographie
advancedd3, js-sandboxVisualisations custom D3, code arbitraire
canvasclear, update, move, resize, styleActions sur les widgets existants

Un chiffre cle avec tendance optionnelle. Le widget le plus simple et le plus utilise.

interface StatBlockData {
label: string; // "Revenue"
value: string; // "$142K"
trend?: string; // "+12.4%"
trendDir?: 'up' | 'down' | 'neutral';
}
{ "type": "stat", "data": { "label": "Revenue", "value": "$142K", "trend": "+12.4%", "trendDir": "up" } }

Ideal pour les metadonnees, les proprietes d’une entite, les details d’un enregistrement.

interface KVBlockData {
title?: string;
rows: [string, string][]; // [["Nom", "Dupont"], ["Age", "42"]]
}

Une liste simple a puces.

interface ListBlockData {
title?: string;
items: string[];
}

Pour un graphique a barres rapide. Pour des graphiques plus riches (pie, line, area), utiliser chart-rich.

interface ChartBlockData {
title?: string;
bars: [string, number][]; // [["Jan", 10], ["Fev", 20]]
}

Notification avec trois niveaux de severite.

interface AlertBlockData {
title?: string;
message?: string;
level?: 'info' | 'warn' | 'error';
}

Code source avec coloration syntaxique.

interface CodeBlockData {
lang?: string; // "python", "sql", "javascript"...
content?: string;
}

Texte libre, sans mise en forme complexe.

interface TextBlockData {
content?: string;
}

Rangee de boutons cliquables. Un bouton primary est mis en evidence.

interface ActionsBlockData {
buttons: { label: string; primary?: boolean }[];
}

Groupe de badges/etiquettes, avec etat actif optionnel.

interface TagsBlockData {
label?: string;
tags: { text: string; active?: boolean }[];
}

Version avancee de stat avec unite, delta, couleur de variante (success/warning/error/info).

interface StatCardSpec {
label?: string;
value?: unknown;
unit?: string; // "%", "EUR", "km"
delta?: string; // "+12%"
trend?: 'up' | 'down' | 'flat' | {
direction: 'up' | 'down' | 'flat';
value?: string;
positive?: boolean;
};
previousValue?: unknown;
variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
}

Tableau avec colonnes configurables, tri par clic, mode compact et raye.

interface DataTableSpec {
title?: string;
columns?: { key: string; label: string; align?: 'left' | 'center' | 'right'; type?: string }[];
rows?: Record<string, unknown>[];
compact?: boolean;
striped?: boolean;
}

Sequence d’evenements avec dates et statuts visuels.

interface TimelineSpec {
title?: string;
events?: {
date?: string;
title?: string;
description?: string;
status?: 'done' | 'active' | 'pending';
}[];
}

Carte de presentation avec avatar (initiales automatiques si pas d’URL), champs structures et statistiques.

interface ProfileSpec {
name?: string;
subtitle?: string;
avatar?: { src: string; alt?: string };
badge?: { text: string; variant?: string };
fields?: { label: string; value: string }[];
stats?: { label: string; value: string }[];
}

Grille de personnes avec nom, sous-titre et badge. Ideal pour les equipes, assemblees, jurys.

interface TrombinoscopeSpec {
title?: string;
people: { name: string; subtitle?: string; badge?: string; color?: string }[];
columns?: number;
}

Explore une structure JSON complexe avec deplier/replier par niveau.

interface JsonViewerSpec {
title?: string;
data: unknown;
maxDepth?: number;
expanded?: boolean;
}

Visualisation SVG de la composition d’une assemblee, avec couleurs par groupe politique.

interface HemicycleSpec {
title?: string;
groups?: { id: string; label: string; seats: number; color: string }[];
totalSeats?: number;
rows?: number;
}

Graphique avance supportant bar, line, area, pie et donut, avec plusieurs series de donnees.

interface ChartSpec {
title?: string;
type?: 'bar' | 'line' | 'area' | 'pie' | 'donut';
labels?: string[];
data?: { label?: string; values: number[]; color?: string }[];
legend?: boolean;
}

Affichage en grille de resultats, dossiers ou entites, avec titre, description et tags.

interface CardsSpec {
title?: string;
cards: { title: string; description?: string; subtitle?: string; tags?: string[] }[];
}

Grille de donnees tabulaires avec possibilite de colorer des cellules (heatmap).

interface GridDataSpec {
title?: string;
columns?: { key: string; label: string; width?: string }[];
rows: unknown[][]; // row-major: [[1, 2], [3, 4]]
highlights?: { row: number; col: number; color: string }[];
}

Visualise des flux entre noeuds : votes, co-signatures, parcours.

interface SankeySpec {
title?: string;
nodes?: { id: string; label: string; color?: string }[];
links?: { source: string; target: string; value: number }[];
}

Carte Leaflet avec marqueurs, fond sombre CARTO. Le module Leaflet est charge dynamiquement.

interface MapSpec {
title?: string;
center?: { lat: number; lng: number };
zoom?: number; // 1-18
height?: string; // CSS, ex: "400px"
markers?: { lat: number; lng: number; label?: string; color?: string }[];
}

Widget D3.js avec 4 presets integres : hex-heatmap, radial, treemap, force.

interface D3Spec {
title?: string;
preset: 'hex-heatmap' | 'radial' | 'treemap' | 'force';
data: unknown;
config?: Record<string, unknown>;
}

Iframe securise qui execute du code JS arbitraire avec acces au DOM et a fetch.

interface JsSandboxSpec {
title?: string;
code: string; // JS a executer
html?: string; // HTML initial dans div#root
css?: string; // CSS injecte dans le head
height?: string; // Hauteur CSS de l'iframe
}

Flux de logs avec niveau de severite, timestamp et source.

interface LogViewerSpec {
title?: string;
entries?: {
timestamp?: string;
level?: 'debug' | 'info' | 'warn' | 'error';
message: string;
source?: string;
}[];
}

Collection d’images avec lightbox navigable au clavier (Escape, fleches).

interface GallerySpec {
title?: string;
images?: { src: string; alt?: string; caption?: string }[];
columns?: number;
}

Diaporama avec navigation et auto-play optionnel.

interface CarouselSpec {
title?: string;
slides?: { src?: string; content?: string; title?: string; subtitle?: string }[];
autoPlay?: boolean;
interval?: number; // ms entre slides
}

Affiche les recettes disponibles sous forme de cartes cliquables. Le clic declenche une interaction qui charge le detail de la recette.


Ces widgets ne creent pas de nouveaux blocs — ils modifient les blocs existants.

ActionDescriptionParametres
clearVider le canvas entierementaucun
updateMettre a jour les donnees d’un bloc{id, data}
moveDeplacer un bloc{id, x, y}
resizeRedimensionner un bloc{id, width, height}
styleAppliquer des styles CSS{id, styles}

L’outil canvas agrege ces 5 actions :

canvas({ action: 'update', id: 'w_a3f2', params: { data: { value: "$155K" } } })
canvas({ action: 'move', id: 'w_a3f2', params: { x: 100, y: 200 } })
canvas({ action: 'clear' })
graph LR
R["Recettes WebMCP"] -->|recommandent| W["Widgets UI"]
CT["widget_display()"] -->|instancie| W
W -->|s'affichent sur| C["Canvas"]
C -->|expose| T["Outils par bloc<br/>block_id_get/update/remove"]
T -->|permettent a| LLM["Agent LLM<br/>de modifier les widgets"]
  • Recettes : les recettes WebMCP indiquent a l’agent quel widget utiliser en fonction des donnees
  • widget_display : l’outil qui instancie un widget sur le canvas
  • ToolLayers : les widgets sont exposes via la WebMcpLayer du serveur autoui
  • Canvas : la surface reactive ou les widgets sont rendus et mis a jour
ErreurConsequenceCorrection
chart pour pie/donutchart ne fait que des barresUtiliser chart-rich avec type: "pie"
Objets dans rows de grid-dataGrid attend unknown[][]Utiliser data-table pour des objets
Pas d’id sur les groups d’hemicycleEvenements de clic cassesToujours inclure id
columns.key ne correspond pas aux rowsCellules videskey doit correspondre aux cles des objets
URL d’avatar inventee par le LLMImage casseeNe jamais fabriquer d’URLs — le widget affiche les initiales
Plus de 200 lignes dans data-tableTableau tronque sans avertissementPaginer ou filtrer les donnees avant