Blu Pantheon
Configuration

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

ConsoleConfigurazioneWidget Builder
AreaPosizioneFunzione
Palette dei nodiColonna sinistraElenco dei nodi disponibili, raggruppati per famiglia
CanvasCentroAlbero del widget; trascina per riordinare, seleziona per modificare
ProprietàColonna destraAttributi del nodo selezionato e associazioni ai dati
AnteprimaPannello inferioreRendering con dati di esempio, in tema chiaro e scuro
JSONScheda in altoSorgente 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

ConsoleConfigurazioneWidget BuilderNuovo 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

NodoDescrizioneProprietà principali
cardContenitore con bordo, intestazione e piè di paginatitle, subtitle, variant, padding
stackImpila i figli in verticalegap, align
rowDispone i figli in orizzontale con a capo automaticogap, justify, wrap
gridGriglia a colonne fisse o responsivecolumns, gap, minItemWidth

Testo

NodoDescrizioneProprietà principali
textParagrafo o titolovalue, variant, weight, color, align
badgeEtichetta compatta di statolabel, tone, icon
dividerLinea di separazioneorientation, spacing

Media

NodoDescrizioneProprietà principali
imageImmagine con rapporto d'aspetto controllatosrc, alt, ratio, fit, radius
iconIcona dal set della librerianame, size, color
videoRiproduttore incorporatosrc, poster, autoplay, controls

Interattivi

NodoDescrizioneProprietà principali
buttonAzione primaria o secondarialabel, action, variant, disabled
linkCollegamento esterno o internolabel, href, target
ratingValutazione a stelle, in lettura o inputvalue, max, readOnly
chipsInsieme di scelte rapideitems, multiple, action
formContenitore di campi con inviofields, submitLabel, action
listElenco di righe con icona e azioneitems, dense, divided
tableTabella con intestazionicolumns, rows, compact
accordionSezioni comprimibiliitems, multiple
carouselScorrimento orizzontale di schedeitems, itemsPerView, loop
alertMessaggio informativo o di erroretitle, message, tone
progressAvanzamento lineare o circolarevalue, max, variant, label

Campi di modulo

NodoDescrizioneProprietà principali
inputCampo testo, email, telefono, numeroname, label, type, required, pattern
textareaTesto su più righename, label, rows, maxLength
selectElenco a discesaname, label, options, placeholder
radioScelta singolaname, label, options
checkboxScelta multipla o consensoname, label, options, required

Schede sanitarie

Nodi specializzati per il settore sanitario, con struttura predefinita e comportamento di prenotazione integrato.

NodoDescrizioneDati attesi
cardDisponibilitaSlot prenotabili raggruppati per giornodate, slots[], location
cardMediciScheda del professionistaname, specialty, photoUrl, nextAvailable
cardPrestazioniPrestazione con prezzo e preparazionename, price, duration, preparation
cardVisitConfirmedRiepilogo di prenotazione confermatabookingId, 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.

$bind — valore singolo
{
  "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.

$each — ripetizione su array
{
  "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.

$if — rendering condizionale
{
  "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.

TrasformazioneEffettoParametri
uppercaseConverte in maiuscolo—
lowercaseConverte in minuscolo—
capitalizeIniziale maiuscola—
trimRimuove gli spazi ai bordi—
defaultValore di ripiego se nullo o vuotovalue
truncateTronca aggiungendo i puntinilength
date_formatFormatta una data ISOformat, locale
number_formatFormatta un numerodecimals, locale
currency_formatFormatta un importocurrency, locale
joinUnisce un array in stringaseparator
json_stringifySerializza in JSONindent
uppercase, lowercase, capitalize, trim
{
  "type": "text",
  "value": { "$bind": "doctor.lastName", "$transform": [{ "name": "uppercase" }] }
}
default e truncate
{
  "type": "text",
  "value": {
    "$bind": "prestazione.note",
    "$transform": [
      { "name": "default", "value": "Nessuna preparazione particolare" },
      { "name": "truncate", "length": 120 }
    ]
  }
}
date_format
{
  "type": "text",
  "value": {
    "$bind": "slot.startsAt",
    "$transform": [{ "name": "date_format", "format": "EEEE d MMMM yyyy, HH:mm", "locale": "it-IT" }]
  }
}
number_format e currency_format
{
  "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" }] } }
  ]
}
join e json_stringify
{
  "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.

widget slot_disponibili
{
  "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

ConsoleConfigurazioneWidget Builderslot_disponibiliAnteprima
  • 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_disponibili

Inseriscila nell azione Mostra widget di un flusso conversazionale.

Limiti di sicurezza e validazione

LimiteValoreComportamento al superamento
Profondità massima dell'albero12 livelliErrore di validazione, salvataggio bloccato
Numero massimo di nodi300 per widgetErrore di validazione
Elementi per $each100Gli elementi eccedenti vengono ignorati
Lunghezza di un valore testuale5.000 caratteriTroncamento con avviso
Dimensione del JSON256 kBSalvataggio rifiutato
Nidificazione di $each3 livelliErrore di validazione

Regole di sicurezza applicate al rendering:

  • Gli URL sono validati: sono ammessi solo https:, mailto: e tel:. Gli schemi javascript: e data: 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 action di tipo tool invocano 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

Sur cette page