Widget Builder
Composizione visiva delle schede in chat — catalogo dei nodi, motore di template con $bind, $each e $if, trasformazioni e limiti.
Il Widget Builder è l'editor visuale che ti permette di comporre le schede interattive che l'assistente restituisce in chat: elenchi di slot disponibili, schede medico, riepiloghi di prenotazione, moduli di contatto.
I widget sono strutture JSON renderizzate dalla libreria condivisa
@blu-pantheon-s-r-l/widgets, la stessa usata dalla Console e da BluBot.
Lo stesso widget appare identico su web, WhatsApp Web View e app mobile.
Prima di iniziare
- Progetto esistente
- Ruolo con permesso identity:projects:write
- Conoscenza della struttura JSON restituita dai tuoi strumenti
L'interfaccia
| Area | Posizione | Funzione |
|---|---|---|
| Palette dei nodi | Colonna sinistra | Elenco dei nodi disponibili, raggruppati per famiglia |
| Canvas | Centro | Albero del widget; trascina per riordinare, seleziona per modificare |
| Proprietà | Colonna destra | Attributi del nodo selezionato e associazioni ai dati |
| Anteprima | Pannello inferiore | Rendering con dati di esempio, in tema chiaro e scuro |
| JSON | Scheda in alto | Sorgente modificabile a mano, con validazione in tempo reale |
Modifica bidirezionale
Canvas e JSON sono sincronizzati. Puoi incollare un JSON completo nella scheda sorgente e ritrovarlo immediatamente sul canvas.
Creare un widget
Assegna nome e chiave
La chiave, ad esempio slot_disponibili, è il riferimento usato dai flussi
e dagli strumenti per richiamare il widget. È immutabile.
Definisci i dati di esempio
Incolla una risposta reale del tuo strumento nella scheda Dati di esempio. L'anteprima userà questi valori.
Componi la struttura
Trascina i nodi dalla palette sul canvas partendo da un contenitore.
Collega i dati
Per ogni nodo testuale sostituisci il valore fisso con un'associazione
$bind al percorso corrispondente nei dati.
Valida e salva
Seleziona Valida: il builder verifica percorsi, profondità e URL. Poi seleziona Salva e pubblica il progetto.
Catalogo dei nodi
Contenitori
| Nodo | Descrizione | Proprietà principali |
|---|---|---|
card | Contenitore con bordo, intestazione e piè di pagina | title, subtitle, variant, padding |
stack | Impila i figli in verticale | gap, align |
row | Dispone i figli in orizzontale con a capo automatico | gap, justify, wrap |
grid | Griglia a colonne fisse o responsive | columns, gap, minItemWidth |
Testo
| Nodo | Descrizione | Proprietà principali |
|---|---|---|
text | Paragrafo o titolo | value, variant, weight, color, align |
badge | Etichetta compatta di stato | label, tone, icon |
divider | Linea di separazione | orientation, spacing |
Media
| Nodo | Descrizione | Proprietà principali |
|---|---|---|
image | Immagine con rapporto d'aspetto controllato | src, alt, ratio, fit, radius |
icon | Icona dal set della libreria | name, size, color |
video | Riproduttore incorporato | src, poster, autoplay, controls |
Interattivi
| Nodo | Descrizione | Proprietà principali |
|---|---|---|
button | Azione primaria o secondaria | label, action, variant, disabled |
link | Collegamento esterno o interno | label, href, target |
rating | Valutazione a stelle, in lettura o input | value, max, readOnly |
chips | Insieme di scelte rapide | items, multiple, action |
form | Contenitore di campi con invio | fields, submitLabel, action |
list | Elenco di righe con icona e azione | items, dense, divided |
table | Tabella con intestazioni | columns, rows, compact |
accordion | Sezioni comprimibili | items, multiple |
carousel | Scorrimento orizzontale di schede | items, itemsPerView, loop |
alert | Messaggio informativo o di errore | title, message, tone |
progress | Avanzamento lineare o circolare | value, max, variant, label |
Campi di modulo
| Nodo | Descrizione | Proprietà principali |
|---|---|---|
input | Campo testo, email, telefono, numero | name, label, type, required, pattern |
textarea | Testo su più righe | name, label, rows, maxLength |
select | Elenco a discesa | name, label, options, placeholder |
radio | Scelta singola | name, label, options |
checkbox | Scelta multipla o consenso | name, label, options, required |
Schede sanitarie
Nodi specializzati per il settore sanitario, con struttura predefinita e comportamento di prenotazione integrato.
| Nodo | Descrizione | Dati attesi |
|---|---|---|
cardDisponibilita | Slot prenotabili raggruppati per giorno | date, slots[], location |
cardMedici | Scheda del professionista | name, specialty, photoUrl, nextAvailable |
cardPrestazioni | Prestazione con prezzo e preparazione | name, price, duration, preparation |
cardVisitConfirmed | Riepilogo di prenotazione confermata | bookingId, datetime, doctor, location |
Perché usare le schede sanitarie
Incapsulano regole già validate: formato delle date in italiano, gestione degli slot
esauriti, avvisi di preparazione, azione di prenotazione con conferma.
Preferiscile alla composizione manuale con card e list.
Motore di template
Un widget è una struttura statica finché non colleghi i dati. Il motore di template interpreta tre marcatori all'interno del JSON.
Marcatore $bind
Sostituisce il valore con quello letto da un percorso dei dati.
{
"type": "text",
"value": { "$bind": "doctor.name" }
}Il percorso usa la notazione a punti e supporta gli indici numerici:
slots.0.time, patient.address.city.
Marcatore $each
Ripete il nodo figlio per ogni elemento di un array. All'interno del blocco, i percorsi sono relativi all'elemento corrente.
{
"type": "stack",
"$each": {
"source": "slots",
"as": "slot",
"template": {
"type": "button",
"label": { "$bind": "slot.time" },
"action": { "type": "book", "slotId": { "$bind": "slot.id" } }
}
}
}Sono disponibili le variabili implicite $index (base zero), $first e $last.
Marcatore $if
Include il nodo solo se la condizione è vera.
{
"type": "badge",
"label": "Ultimi posti",
"tone": "warning",
"$if": { "path": "slot.remaining", "op": "lte", "value": 3 }
}Operatori disponibili: eq, neq, gt, gte, lt, lte, in, exists, empty.
Trasformazioni
Le trasformazioni si applicano al valore letto da $bind tramite la chiave $transform.
Sono concatenabili: vengono eseguite nell'ordine indicato.
| Trasformazione | Effetto | Parametri |
|---|---|---|
uppercase | Converte in maiuscolo | — |
lowercase | Converte in minuscolo | — |
capitalize | Iniziale maiuscola | — |
trim | Rimuove gli spazi ai bordi | — |
default | Valore di ripiego se nullo o vuoto | value |
truncate | Tronca aggiungendo i puntini | length |
date_format | Formatta una data ISO | format, locale |
number_format | Formatta un numero | decimals, locale |
currency_format | Formatta un importo | currency, locale |
join | Unisce un array in stringa | separator |
json_stringify | Serializza in JSON | indent |
{
"type": "text",
"value": { "$bind": "doctor.lastName", "$transform": [{ "name": "uppercase" }] }
}{
"type": "text",
"value": {
"$bind": "prestazione.note",
"$transform": [
{ "name": "default", "value": "Nessuna preparazione particolare" },
{ "name": "truncate", "length": 120 }
]
}
}{
"type": "text",
"value": {
"$bind": "slot.startsAt",
"$transform": [{ "name": "date_format", "format": "EEEE d MMMM yyyy, HH:mm", "locale": "it-IT" }]
}
}{
"type": "row",
"children": [
{ "type": "text", "value": { "$bind": "prestazione.durata", "$transform": [{ "name": "number_format", "decimals": 0, "locale": "it-IT" }] } },
{ "type": "text", "value": { "$bind": "prestazione.prezzo", "$transform": [{ "name": "currency_format", "currency": "EUR", "locale": "it-IT" }] } }
]
}{
"type": "stack",
"children": [
{ "type": "text", "value": { "$bind": "prestazione.convenzioni", "$transform": [{ "name": "join", "separator": ", " }] } },
{ "type": "text", "variant": "mono", "value": { "$bind": "debug.payload", "$transform": [{ "name": "json_stringify", "indent": 2 }] } }
]
}Ordine delle trasformazioni
truncate prima di capitalize produce un risultato diverso da capitalize
prima di truncate. Metti sempre default come prima trasformazione della catena.
Esempio completo: slot disponibili
Widget che elenca gli appuntamenti prenotabili restituiti dallo strumento
verifica_disponibilita, descritto in Strumenti.
{
"version": "1.0",
"key": "slot_disponibili",
"root": {
"type": "card",
"title": { "$bind": "prestazione.nome" },
"subtitle": {
"$bind": "sede.indirizzo",
"$transform": [{ "name": "default", "value": "Sede di Milano" }]
},
"children": [
{
"type": "alert",
"tone": "info",
"message": { "$bind": "prestazione.preparazione" },
"$if": { "path": "prestazione.preparazione", "op": "exists" }
},
{
"type": "stack",
"gap": "md",
"$each": {
"source": "giorni",
"as": "giorno",
"template": {
"type": "stack",
"gap": "sm",
"children": [
{
"type": "text",
"variant": "h4",
"value": {
"$bind": "giorno.data",
"$transform": [{ "name": "date_format", "format": "EEEE d MMMM", "locale": "it-IT" }]
}
},
{
"type": "row",
"gap": "sm",
"wrap": true,
"$each": {
"source": "giorno.slots",
"as": "slot",
"template": {
"type": "button",
"variant": "secondary",
"disabled": { "$bind": "slot.occupato" },
"label": {
"$bind": "slot.orario",
"$transform": [{ "name": "date_format", "format": "HH:mm", "locale": "it-IT" }]
},
"action": {
"type": "tool",
"tool": "crea_prenotazione",
"params": { "slotId": { "$bind": "slot.id" } }
}
}
}
}
]
}
}
},
{
"type": "text",
"variant": "caption",
"value": "Nessuna disponibilità nei prossimi 14 giorni. Scrivi \"operatore\" per essere richiamato.",
"$if": { "path": "giorni", "op": "empty" }
},
{ "type": "divider" },
{
"type": "row",
"justify": "between",
"children": [
{
"type": "text",
"variant": "caption",
"value": {
"$bind": "prestazione.prezzo",
"$transform": [{ "name": "currency_format", "currency": "EUR", "locale": "it-IT" }]
}
},
{ "type": "link", "label": "Come raggiungerci", "href": "https://www.acme.it/sedi", "target": "_blank" }
]
}
]
}
}Anteprima e collaudo
- Dati di esempio: incolla il JSON reale dello strumento.
- Tema: verifica il rendering in chiaro e in scuro.
- Larghezza: prova a 320, 400 e 720 pixel (mobile, widget, schermo intero).
- Stati limite: array vuoto, campi nulli, testi molto lunghi, prezzi a zero.
Chiave del widget da usare nei flussi
slot_disponibiliInseriscila nell azione Mostra widget di un flusso conversazionale.
Limiti di sicurezza e validazione
| Limite | Valore | Comportamento al superamento |
|---|---|---|
| Profondità massima dell'albero | 12 livelli | Errore di validazione, salvataggio bloccato |
| Numero massimo di nodi | 300 per widget | Errore di validazione |
Elementi per $each | 100 | Gli elementi eccedenti vengono ignorati |
| Lunghezza di un valore testuale | 5.000 caratteri | Troncamento con avviso |
| Dimensione del JSON | 256 kB | Salvataggio rifiutato |
Nidificazione di $each | 3 livelli | Errore di validazione |
Regole di sicurezza applicate al rendering:
- Gli URL sono validati: sono ammessi solo
https:,mailto:etel:. Gli schemijavascript:edata:vengono rimossi. - Il contenuto testuale è sempre trattato come testo: l'HTML non viene interpretato.
- Le immagini vengono caricate solo da domini in allowlist, configurabili nel progetto.
- I nodi con
actiondi tipotoolinvocano solo strumenti collegati al progetto.
Nessun HTML arbitrario
Non esiste un nodo che renderizzi HTML grezzo. È una scelta deliberata per impedire attacchi di tipo cross-site scripting attraverso i dati restituiti dai tuoi sistemi.
Passi successivi
- Strumenti — produci i dati che alimentano i widget.
- Flussi conversazionali — decidi quando mostrare un widget.
- Riferimento API — restituisci widget direttamente dalle tue risposte.