ChatGPT ha lanciato la Generative UI (finalmente) e nel giro di una notte ha ribattezzato il settore Intelligent UI. Dovevamo assolutamente capire come l'hanno implementata.
https://x.com/OpenAI/status/2107894997538525580
Questo articolo è un'analisi approfondita di come OpenAI ha realizzato la sua Intelligent UI di punta: i livelli, i formati e il rendering nativo su Web e Mobile.
I mattoni fondamentali
L'implementazione di ChatGPT divide il lavoro tra il modello, il server di backend e il client:
- Formato di inferenza: il modello scrive l'interfaccia in DIL, che combina Markdown con tag in stile JSX e JavaScript.
- Compilazione lato server: il server converte ogni risposta parziale in un programma JavaScript e in un documento JSON contenente testo e dati.
- Runtime del client: un runtime in sandbox esegue il programma e genera le operazioni per l'interfaccia utente.
- Rendering: ChatGPT applica queste operazioni ai propri componenti nativi.
- Design system e catalogo: i componenti, le proprietà e i design token a disposizione del modello.

Diagramma dell'architettura della Intelligent UI di ChatGPT
Formato di inferenza
È ciò che scrive il modello. In ChatGPT si tratta di un linguaggio che OpenAI chiama DIL: Markdown per il testo, tag simili a JSX per i componenti e JavaScript per lo stato e la logica. Seguiremo una piccola risposta attraverso tutti i livelli:
1## Stima del piano team2Trascina il cursore per vedere il **prezzo mensile** per il tuo team.3{@body const [seats,setSeats] = DIL.useState(8)}4{@body const price = seats*29}5<box border padding={3} gap={2}>6 <slider min={1} max={50} value={seats} onChange={setSeats}/>7 <title size="xl">${price}/mese</title>8</box>
Il titolo e il paragrafo sono normale Markdown. I tag sono componenti presi dal catalogo di ChatGPT. Le due righe {@body …} sono JavaScript: la prima dichiara uno stato, seats, e la seconda ne ricava price. Lo slider è collegato a seats, quindi spostandolo si aggiorna il prezzo.
Serve un formato dedicato perché il modello genera l'interfaccia token per token.
- Deve essere facile da scrivere in modo affidabile, per questo è costruito su notazioni che il modello conosce già bene.
- Deve restare utilizzabile anche quando è scritto solo a metà. Le istruzioni stanno su righe separate e qualsiasi elemento aperto può essere chiuso automaticamente. Così il server può interrompere una risposta parziale all'ultimo costrutto completo e compilarlo comunque.
Compilazione lato server
Il client non esegue mai l'output del modello così com'è. Il server di OpenAI lo compila in un programma JavaScript e in un documento JSON, che vengono salvati insieme al messaggio (come model_dil_v2). La risposta viene compilata in questo modo (formattato per maggiore leggibilità):
1function __dilSafe(evaluate, failureValue) {2 try { return evaluate(); } catch { return failureValue; }3}45DIL.render(__dil.jsx(() => {6 const __dilConstants = DIL.useConstants();7 const __dilModelDataBindings = DIL.useAppData((appData) => appData.opGenui?.modelDataBindings ?? {});8 const [seats, setSeats] = DIL.useState(8, { key: "seats" });9 const price = __dilSafe(() => seats * 29, undefined);10 return __dil.jsx(__dil.Fragment, null,11 __dil.jsx("title", { size: "lg" }, __dilConstants["0"]),12 __dil.jsx("text", null, __dilConstants["1"], __dil.jsx("bold", null, __dilConstants["2"]), __dilConstants["3"]),13 __dil.jsx("box", { border: true, padding: 3, gap: 2 },14 __dilSafe(() => __dil.jsx("slider", { min: 1, max: 50, value: seats, onChange: setSeats }), null),15 __dil.jsx("title", { size: "xl" }, __dilConstants["4"], __dilSafe(() => price, null), __dilConstants["5"])));16}, { key: "body:2" }));
1{2 "constants": {3 "0": "Stima del piano team",4 "1": "Trascina il cursore per vedere il ",5 "2": "prezzo mensile",6 "3": " per il tuo team.",7 "4": "$",8 "5": "/mese"9 },10 "appData": { "opGenui": { "componentResults": {}, "modelDataBindings": {} } }11}
Il Markdown viene compilato nello stesso albero dei componenti. L'intestazione diventa un title, il paragrafo un text con un bold al suo interno, e le parole finiscono nella tabella delle costanti.
La compilazione svolge un lavoro che altrimenti ogni client dovrebbe ripetere:
- Semplici chiamate di funzione. Il markup diventa una serie di chiamate a __dil.jsx, così un runtime JavaScript può valutare il programma senza bisogno di un parser per DIL.
- Isolamento degli errori. Le espressioni sono racchiuse in __dilSafe: se un'espressione genera un errore, viene rimosso solo quell'elemento invece di bloccare l'intero rendering.
- Testo in una tabella separata. Il testo statico finisce nella tabella constants; in questo modo, durante lo streaming della risposta, il testo che cresce modifica i dati e non il programma.
- Chiavi di stato stabili. Ogni porzione di stato riceve una chiave ({ key: "seats" }), così il suo valore sopravvive a ogni ricompilazione.
- Riparazione e validazione. Le istruzioni e i tag incompleti vengono scartati, gli elementi non chiusi vengono chiusi, e le proprietà che non superano la validazione rispetto al catalogo vengono rimosse e registrate come diagnostica.
Il documento JSON contiene le costanti di testo e tutti i dati che il server risolve per la risposta, come i risultati della ricerca di immagini (vedi Dati).
Runtime del client
Il client riceve il programma compilato e il documento JSON. Il suo lavoro è diviso tra un runtime, che esegue il programma, e un renderer, che disegna il risultato.
Poiché il programma è codice scritto dal modello, non viene eseguito direttamente nella pagina di ChatGPT. ChatGPT carica un iframe nascosto (runner.html), isolato in sandbox con allow-scripts e una content security policy impostata su default-src 'none', che avvia un Web Worker.
- Lockdown. Prima di valutare un programma, il worker rimuove dal proprio scope globale l'accesso alla rete, i timer, la messaggistica e la valutazione dinamica del codice, congelando le restanti variabili globali.
- Valutazione. Poi valuta il programma con new Function. Gli oggetti del runtime (DIL, __dil, GenUI) e i componenti compositi del catalogo vengono passati come parametri.
- Watchdog. Se un programma non risponde entro un certo timeout, viene messo in quarantena e il worker viene riavviato.
Il runtime è un piccolo reconciler in stile React. Esegue il rendering del componente e mantiene lo stato degli hook in slot con chiave. Confronta poi l'albero risultante con quello precedente e codifica le differenze in un elenco di operazioni. Non disegna nulla.
L'esempio illustrativo qui sotto mostra le operazioni di un primo rendering, con una riga per nodo. Le voci che elencano i nomi delle proprietà di ogni elemento sono state omesse:
1CREATE #1 title SET size = "lg" PLACE under root at 02CREATE #2 text "Stima del piano team" PLACE under #1 at 03CREATE #3 text PLACE under root at 14CREATE #4 text "Trascina il cursore per vedere il " PLACE under #3 at 05CREATE #5 bold PLACE under #3 at 16CREATE #6 text "prezzo mensile" PLACE under #5 at 07CREATE #7 text " per il tuo team." PLACE under #3 at 28CREATE #8 box SET border = true, padding = 3, gap = 2 PLACE under root at 29CREATE #9 slider SET min = 1, max = 50, value = 8, onChange = fn#1 PLACE under #8 at 010CREATE #10 title SET size = "xl" PLACE under #8 at 111CREATE #11 text "$" PLACE under #10 at 012CREATE #12 text "232" PLACE under #10 at 113CREATE #13 text "/mese" PLACE under #10 at 2
Le funzioni non lasciano mai il worker; il gestore dello slider viene inviato solo come identificatore (fn#1). Durante la trasmissione, le operazioni sono codificate come una sequenza binaria di interi, con le stringhe conservate in una tabella separata.
Rendering
La pagina di ChatGPT applica le operazioni al proprio albero dei componenti. Ogni CREATE istanzia un componente nativo dal design system di ChatGPT, e la pagina anima le modifiche man mano che arrivano. La pagina accetta operazioni solo per i tipi di componente noti, quindi l'output del modello non può introdurre markup o stili arbitrari. Fanno eccezione i valori CSS puri accettati da alcune proprietà (vedi Design system e catalogo) e le app AppBlock, che girano in un iframe (vedi Via di fuga).
L'interazione funziona in direzione opposta. Quando l'utente trascina lo slider su 9, la pagina invia l'identificatore del gestore e gli argomenti al worker. Il worker chiama setSeats(9), riesegue il rendering e restituisce le operazioni di aggiornamento. Senza alcuna chiamata al modello.
Design system e catalogo
Il catalogo definisce cosa può richiedere il modello. È necessario perché il modello non costruisce un'interfaccia partendo da regole grezze di layout e stile. Sceglie invece tra i componenti che ChatGPT sa già disegnare e li personalizza con design token come padding={3}. Per alcune proprietà sono ammessi valori CSS puri, come larghezze in pixel e colori esadecimali, ma i design token restano la scelta preferita. Di conseguenza:
- Le interfacce generate sono coerenti con il resto di ChatGPT su ogni piattaforma.
- Il compilatore ha uno schema su cui verificare l'output. Una proprietà inesistente su un componente, o un valore letterale del tipo sbagliato, viene rimossa durante la compilazione e registrata come diagnostica.
In una risposta che abbiamo catturato, il compilatore ha rimosso due proprietà: fill su un'icona (diagnostica unknown_prop) e gap="1" su un box (diagnostica invalid_literal).
Il catalogo si divide in tre parti:
- Componenti nativi. Circa 70 componenti sono definiti nel registro dei componenti all'interno del codice client di ChatGPT; 39 di questi compaiono nelle risposte che abbiamo analizzato.
- Design token per spaziatura, raggio, colore e dimensione.
- Componenti compositi scritti da OpenAI in DIL e inviati precompilati alla sandbox, come i componenti image e product. Nelle nostre analisi, il modello ha usato questi componenti senza mai definirne di propri.
Streaming
Lo streaming del testo è semplice: ogni nuovo token viene aggiunto a ciò che è già sullo schermo. Fare lo streaming di un'interfaccia è più complesso, per tre motivi:
- Di solito l'output non è ancora eseguibile. Nella maggior parte dei momenti è un programma incompleto, con un tag o un'espressione ancora aperta, e non può essere eseguito così com'è.
- L'interfaccia deve continuare a funzionare mentre cresce. I componenti con cui l'utente ha già interagito devono mantenere il loro stato.
- Alcuni contenuti arrivano separatamente. Dati come le immagini provengono dal server, non dal testo.
Streaming lato server
Un semplice flusso di token concatenati non basta. ChatGPT trasmette invece in streaming delle patch verso un messaggio strutturato che affianca il testo grezzo, il programma compilato e i relativi dati.
La risposta arriva al browser tramite un flusso di eventi inviati dal server (POST /backend-api/f/conversation). Ogni evento è un aggiornamento in stile JSON-Patch al messaggio in fase di costruzione. Un singolo evento di solito aggiorna contemporaneamente il testo DIL grezzo e la sua forma compilata. Ecco un aggiornamento estratto da una risposta catturata, abbreviato:
1{"o": "patch", "v": [2 {"p": "/message/content/parts/0", "o": "append", "v": " Arrosto di agnello della domenica con gli amici — cibo abbondante, …"},3 {"p": "/message/metadata/model_dil_v2/code", "o": "replace", "v": "DIL.render(__dil.jsx(()=>{…"},4 {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"0": "Ecco un piano per un vero arrosto di agnello della domenica con gli amici — …"}},5 {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"1": "Visto che sei"}},6 {"p": "/message/metadata/model_dil_v2/fallbackMarkdown", "o": "append", "v": " Arrosto di agnello della domenica con gli amici — …"}7]}
Il server non compila in modo incrementale. Ogni poche centinaia di millisecondi, molto probabilmente a ogni nuovo blocco di output del modello, ricompila tutto ciò che il modello ha scritto fino a quel momento e invia il risultato. La compilazione inizia con il primo token, prima ancora che compaia un qualsiasi tag.
Il compilatore deve occuparsi di:
- Compilare una risposta scritta a metà
- Aggiornare il testo
- Aggiornare l'interfaccia utente
La linea temporale appare più o meno così:

GIF
Streaming lato client
La pagina passa ogni nuovo aggiornamento al worker in sandbox. Il worker lo valuta, riesegue il rendering con lo stato esistente e invia le operazioni di aggiornamento alla pagina. Lo stato conserva i suoi valori tra una ricompilazione e l'altra grazie alle chiavi aggiunte durante la compilazione. Se un nuovo programma non riesce a essere valutato o renderizzato, il worker mantiene l'ultimo che ha funzionato.
La pagina anima poi ogni cambiamento:
- il testo appare con una dissolvenza in 0,7 s;
- le nuove righe e gli elementi della griglia scorrono dentro in 0,42 s;
- i grafici si disegnano in 1,8 s;
- le altezze dei contenitori cambiano con una transizione fluida, senza salti improvvisi.
Via di fuga: AppBlock, un'app in un iframe

App inline generata da ChatGPT
Alcune richieste richiedono funzionalità per cui i componenti nativi non sono progettati, come una drum machine che sintetizza il suono con Web Audio. In questi casi, il modello può scrivere un AppBlock: una web app autonoma in HTML, CSS e JavaScript, incorporata nella risposta. Eccone l'inizio, abbreviato:
1<AppBlock title="Drum Lab" icon="app-chatgpt" variant="inline" app_block_id="drum-lab-01">2<div id="dl" class="w-full min-w-0 space-y-4 text-base">3 <style>4 #dl{color:var(--viz-text)}#dl button{touch-action:manipulation}#dl .panel{background:var(--viz-panel);border:1px solid var(--viz-border);border-radius:15px}…5 </style>6 …7 <button id="dl-play" class="btn" style="background:var(--viz-text);color:var(--viz-card);min-width:100px">▶ Play</button>8 …9</div>10<script>11(function(){12const root=document.getElementById('dl');if(root.dataset.init)return;root.dataset.init="yes";13…14function audioInit(){if(!audio){const C=window.AudioContext||window.webkitAudioContext; if(!C)return false;audio=new C();…15…16})();17</script>18</AppBlock>
Gli AppBlock vengono renderizzati in modo diverso rispetto ai componenti della Intelligent UI.
Tiriamo le somme
Scrivi un prompt. Il modello inizia a scrivere l'interfaccia, il server la trasforma in qualcosa che ChatGPT può eseguire e la pagina la costruisce pezzo per pezzo man mano che la risposta arriva in streaming. Una volta pronta, spostare uno slider o spuntare una casella aggiorna l'interfaccia localmente, senza dover interrogare di nuovo il modello.
Un linguaggio specifico per il modello, una fase di compilazione sul server, renderer nativi e un design system ben definito fanno funzionare il tutto. Ogni elemento svolge un ruolo cruciale e, insieme, portano la prossima generazione di interfacce AI native a miliardi di utenti in tutto il mondo. Che epoca fantastica in cui vivere!

Screenshot della Intelligent UI di ChatGPT
Metodologia
Tutte le osservazioni derivano dai nostri account ChatGPT, dal traffico generato dalla web app di ChatGPT e dal JavaScript reso pubblico da chatgpt.com. Sono state effettuate nell'ottobre 2026 con GPT-6 e GPT-6 Thinking.
Analisi realizzata con l'aiuto di Codex e Claude. Scritto con Codex, visualizzazioni a cura di Claude.
(Una versione più approfondita è disponibile su https://www.openui.com/blog/how-chatgpt-intelligent-ui-works





