ChatGPT a enfin lancé la Generative UI (interface générative) et a rebaptisé le domaine du jour au lendemain en Intelligent UI. Il fallait absolument qu'on décortique leur implémentation.
https://x.com/OpenAI/status/2107894997538525580
Cet article est une étude approfondie de la manière dont OpenAI a conçu son Intelligent UI phare : les différentes couches, les formats et le rendu natif sur le Web et le mobile.
Les briques élémentaires
L'implémentation de ChatGPT répartit le travail entre le modèle, le serveur backend et le client :
- Format d'inférence : le modèle écrit l'interface en DIL, un langage qui combine du Markdown avec des balises de type JSX et du JavaScript.
- Compilation côté serveur : le serveur convertit chaque réponse partielle en un programme JavaScript et un document JSON contenant du texte et des données.
- Runtime client : un environnement d'exécution isolé (sandbox) exécute le programme et génère des opérations d'interface.
- Rendu : ChatGPT applique ces opérations à ses propres composants natifs.
- Design system et catalogue : les composants, propriétés et design tokens mis à la disposition du modèle.

Diagramme d'architecture de l'Intelligent UI de ChatGPT
Format d'inférence
Voici ce que le modèle génère. Dans ChatGPT, il s'agit d'un langage qu'OpenAI appelle DIL : du Markdown pour le texte, des balises de type JSX pour les composants, et du JavaScript pour l'état et la logique. Nous allons suivre une petite réponse à travers toutes les couches :
1## Estimation du forfait équipe2Faites glisser le curseur pour voir le **prix mensuel** pour votre équipe.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}/mois</title>8</box>
Le titre et le paragraphe sont du Markdown classique. Les balises correspondent aux composants du catalogue de ChatGPT. Les deux lignes {@body …} sont du JavaScript : la première déclare un état, seats, et la seconde en déduit price. Le slider est lié à seats, donc le déplacer met à jour le prix.
Un format dédié est indispensable car le modèle génère l'interface token par token.
- Il doit être facile à écrire de manière fiable, c'est pourquoi il repose sur des notations que le modèle maîtrise déjà parfaitement.
- Il doit rester utilisable même lorsqu'il n'est généré qu'à moitié. Les instructions sont placées sur des lignes distinctes et tout élément ouvert peut être fermé automatiquement. Cela permet au serveur de couper une réponse partielle à sa dernière structure complète et de continuer à la compiler.
Compilation côté serveur
Le client n'exécute jamais la sortie du modèle telle quelle. Le serveur d'OpenAI la compile en un programme JavaScript et un document JSON, qui sont stockés avec le message (sous le nom model_dil_v2). La réponse se compile ainsi (formatée pour plus de lisibilité) :
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": "Estimation du forfait équipe",4 "1": "Faites glisser le curseur pour voir le ",5 "2": "prix mensuel",6 "3": " pour votre équipe.",7 "4": "$",8 "5": "/mois"9 },10 "appData": { "opGenui": { "componentResults": {}, "modelDataBindings": {} } }11}
Le Markdown est compilé dans la même arborescence que les composants. Le titre devient un title, le paragraphe un text contenant un bold, et leurs mots sont déplacés dans la table des constantes.
La compilation effectue un travail que chaque client devrait sinon répéter :
- Appels de fonctions simples. Le balisage devient des appels à __dil.jsx, ce qui permet à un runtime JavaScript d'évaluer le programme sans avoir besoin d'un parseur DIL.
- Isolation des erreurs. Les expressions sont encapsulées dans __dilSafe, de sorte qu'une expression qui lève une exception supprime un seul élément au lieu de faire planter tout le rendu.
- Texte dans une table séparée. Le texte statique est déplacé dans la table des constantes. Ainsi, pendant le streaming de la réponse, le texte qui s'allonge modifie les données plutôt que le programme.
- Clés d'état stables. Chaque élément d'état reçoit une clé ({ key: "seats" }), afin que sa valeur survive à chaque recompilation.
- Réparation et validation. Les instructions et balises incomplètes sont supprimées, les éléments non fermés sont clôturés, et les propriétés qui échouent à la validation par rapport au catalogue sont retirées et enregistrées sous forme de diagnostics.
Le document JSON contient les constantes de texte ainsi que toutes les données que le serveur résout pour la réponse, comme les résultats de recherche d'images (voir Données).
Runtime client
Le client reçoit le programme compilé et le document JSON. Son travail est réparti entre un runtime, qui exécute le programme, et un moteur de rendu, qui dessine le résultat.
Le programme étant du code généré par le modèle, il ne s'exécute pas directement dans la page ChatGPT. ChatGPT charge un iframe masqué (runner.html), isolé via allow-scripts et une politique de sécurité de contenu définie sur default-src 'none', qui lance un Web Worker.
- Verrouillage. Avant d'évaluer un programme, le worker retire de sa portée globale l'accès au réseau, les timers, la messagerie et l'évaluation dynamique de code, puis gèle les variables globales restantes.
- Évaluation. Il évalue ensuite le programme avec new Function. Les objets du runtime (DIL, __dil, GenUI) et les composants composites du catalogue lui sont transmis en tant que paramètres.
- Chien de garde. Un programme qui ne répond pas dans le délai imparti est mis en quarantaine, et le worker est redémarré.
Le runtime est un petit réconciliateur dans le style de React. Il effectue le rendu du composant et conserve l'état des hooks dans des emplacements clés. Il compare ensuite l'arborescence résultante avec la précédente et encode les différences sous forme de liste d'opérations. Il ne dessine rien lui-même.
L'exemple illustratif suivant montre les opérations issues d'un premier rendu, avec une ligne par nœud. Les entrées listant les noms de propriétés de chaque élément ont été omises :
1CREATE #1 title SET size = "lg" PLACE under root at 02CREATE #2 text "Estimation du forfait équipe" PLACE under #1 at 03CREATE #3 text PLACE under root at 14CREATE #4 text "Faites glisser le curseur pour voir le " PLACE under #3 at 05CREATE #5 bold PLACE under #3 at 16CREATE #6 text "prix mensuel" PLACE under #5 at 07CREATE #7 text " pour votre équipe." 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 "/mois" PLACE under #10 at 2
Les fonctions ne quittent jamais le worker ; le gestionnaire du slider n'est envoyé que sous forme d'identifiant (fn#1). Sur le réseau, les opérations sont encodées sous forme de séquence binaire d'entiers, les chaînes de caractères étant conservées dans une table séparée.
Rendu
La page ChatGPT applique les opérations à sa propre arborescence de composants. Chaque CREATE instancie un composant natif issu du design system de ChatGPT, et la page anime les modifications au fur et à mesure de leur arrivée. La page n'accepte les opérations que pour les types de composants connus, de sorte que la sortie du modèle ne peut pas introduire de balisage ou de styles arbitraires. Les exceptions sont les valeurs CSS brutes acceptées par certaines propriétés (voir Design system et catalogue) et les applications AppBlock, qui s'exécutent dans un iframe (voir Échappatoire).
L'interaction fonctionne dans le sens inverse. Lorsque l'utilisateur fait glisser le curseur sur 9, la page envoie l'identifiant et les arguments du gestionnaire au worker. Celui-ci appelle setSeats(9), recalcule le rendu et renvoie les opérations de mise à jour. Aucun appel au modèle n'est nécessaire.
Design system et catalogue
Le catalogue définit ce que le modèle peut demander. Il est indispensable car le modèle ne construit pas une interface à partir de règles brutes de mise en page et de style. Il choisit parmi les composants que ChatGPT sait déjà afficher et les stylise avec des design tokens comme padding={3}. Des valeurs CSS brutes, telles que des largeurs en pixels ou des couleurs hexadécimales, sont acceptées pour certaines propriétés, mais les design tokens restent privilégiés. Résultat :
- Les interfaces générées s'intègrent parfaitement au reste de ChatGPT sur toutes les plateformes.
- Le compilateur dispose d'un schéma pour vérifier la sortie. Une propriété inexistante sur un composant, ou un littéral de type incorrect, est supprimée lors de la compilation et consignée comme diagnostic.
Dans l'une des réponses que nous avons capturées, le compilateur a supprimé deux propriétés : fill sur une icône (diagnostic unknown_prop) et gap="1" sur une boîte (diagnostic invalid_literal).
Le catalogue se divise en trois parties :
- Composants natifs. Environ 70 composants sont définis dans le registre de composants du code client de ChatGPT ; 39 d'entre eux apparaissent dans les réponses que nous avons capturées.
- Design tokens pour l'espacement, le rayon, la couleur et la taille.
- Composants composites écrits par OpenAI en DIL et envoyés précompilés à la sandbox, comme les composants image et product. Dans nos captures, le modèle a utilisé ces composants sans jamais définir les siens.
Streaming
Le streaming de texte est simple : chaque nouveau token s'ajoute à ce qui est déjà affiché à l'écran. Le streaming d'une interface est plus complexe, pour trois raisons :
- La sortie n'est généralement pas encore exécutable. À tout instant, il s'agit d'un programme incomplet, avec une balise ou une expression encore ouverte, impossible à exécuter tel quel.
- L'interface doit rester fonctionnelle pendant sa génération. Les composants avec lesquels l'utilisateur a déjà interagi doivent conserver leur état.
- Certains contenus arrivent séparément. Les données comme les images proviennent du serveur, et non du texte.
Streaming côté serveur
Un simple flux de tokens ajoutés les uns aux autres ne suffit pas. ChatGPT streame plutôt des correctifs (patches) vers un message structuré qui regroupe le texte brut, le programme compilé et ses données.
La réponse parvient au navigateur via un flux d'événements envoyés par le serveur (POST /backend-api/f/conversation). Chaque événement est une mise à jour de type JSON-Patch appliquée au message en cours de construction. Un seul événement met généralement à jour simultanément le texte DIL brut et sa forme compilée. Voici une mise à jour extraite d'une réponse capturée, raccourcie :
1{"o": "patch", "v": [2 {"p": "/message/content/parts/0", "o": "append", "v": " Rôti d'agneau du dimanche entre amis — repas généreux, …"},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": "Voici un plan pour un vrai rôti d'agneau du dimanche entre amis — …"}},5 {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"1": "Puisque vous êtes"}},6 {"p": "/message/metadata/model_dil_v2/fallbackMarkdown", "o": "append", "v": " Rôti d'agneau du dimanche entre amis — …"}7]}
Le serveur ne compile pas de manière incrémentale. Toutes les quelques centaines de millisecondes, très probablement à chaque nouveau bloc de sortie du modèle, il recompile tout ce que le modèle a généré jusqu'alors et envoie le résultat. La compilation commence dès le premier token, avant même l'apparition de la moindre balise.
Le compilateur doit gérer :
- La compilation d'une réponse partiellement écrite
- La mise à jour du texte
- La mise à jour de l'interface
La chronologie ressemble à ceci :

GIF
Streaming côté client
La page transmet chaque nouvelle mise à jour au worker isolé. Celui-ci l'évalue, recalcule le rendu avec l'état existant et renvoie les opérations de mise à jour à la page. L'état conserve ses valeurs d'une recompilation à l'autre grâce aux clés ajoutées lors de la compilation. Si un nouveau programme échoue à l'évaluation ou au rendu, le worker conserve le dernier qui a fonctionné.
La page anime ensuite chaque modification :
- le texte apparaît en fondu sur 0,7 s ;
- les nouvelles lignes et éléments de grille glissent sur l'écran en 0,42 s ;
- les graphiques se dessinent sur 1,8 s ;
- les hauteurs des conteneurs font l'objet d'une transition au lieu de changer brusquement.
Échappatoire : AppBlock, une application dans un iframe

Application intégrée générée par ChatGPT
Certaines requêtes nécessitent des éléments pour lesquels les composants natifs ne sont pas conçus, comme une boîte à rythmes qui synthétise du son avec Web Audio. Pour cela, le modèle peut générer un AppBlock : une application web autonome en HTML, CSS et JavaScript, intégrée directement dans la réponse. En voici le début, raccourci :
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>
Les AppBlocks sont rendus différemment des composants de l'Intelligent UI.
La synthèse
Vous tapez un prompt. Le modèle commence à générer l'interface, le serveur la transforme en quelque chose que ChatGPT peut exécuter, et la page la construit pièce par pièce au fil du streaming de la réponse. Une fois affichée, déplacer un curseur ou cocher une case met à jour l'interface localement, sans solliciter à nouveau le modèle.
Un langage spécifique au modèle, une étape de compilation côté serveur, des moteurs de rendu natifs et un design system bien ancré : voilà ce qui fait tenir l'ensemble. Chaque élément joue un rôle crucial et, ensemble, ils offrent la prochaine génération d'interfaces natives IA à des milliards d'utilisateurs dans le monde. Quelle époque formidable !

Captures d'écran de l'Intelligent UI de ChatGPT
Méthodologie
Toutes nos observations proviennent de nos propres comptes ChatGPT, du trafic généré par l'application web ChatGPT et du JavaScript servi publiquement par chatgpt.com. Elles ont été réalisées en octobre 2026 avec GPT-6 et GPT-6 Thinking.
Analyse réalisée avec l'aide de Codex et Claude. Rédigé avec Codex, visualisations par Claude.
(Une version plus détaillée est disponible sur https://www.openui.com/blog/how-chatgpt-intelligent-ui-works





