ChatGPT нарешті запустив Generative UI, а вже наступного дня перейменував цю сферу на Intelligent UI. Нам було цікаво подивитися, як саме вони це реалізували.
https://x.com/OpenAI/status/2107894997538525580
Ця стаття — глибоке дослідження того, як OpenAI впровадили свій флагманський Intelligent UI: шари, формати та нативний рендеринг у вебі й на мобільних пристроях.
Будівельні блоки
Реалізація ChatGPT розподіляє роботу між моделлю, бекенд-сервером і клієнтом:
- Формат інференсу: модель описує інтерфейс мовою DIL, яка поєднує Markdown із JSX-подібними тегами та JavaScript.
- Компіляція на сервері: сервер перетворює кожну часткову відповідь на програму JavaScript і документ JSON із текстом і даними.
- Клієнтське середовище виконання: ізольоване середовище (sandbox) виконує програму та генерує операції для UI.
- Рендеринг: ChatGPT застосовує ці операції до власних нативних компонентів.
- Дизайн-система та каталог: компоненти, властивості й дизайн-токени, доступні моделі.

Схема архітектури Intelligent UI у ChatGPT
Формат інференсу
Це те, що генерує модель. У ChatGPT використовується мова, яку OpenAI називає DIL: Markdown для тексту, JSX-подібні теги для компонентів і JavaScript для стану та логіки. Простежимо шлях однієї невеликої відповіді через усі шари:
1## Оцінка тарифу для команди2Перетягніть повзунок, щоб побачити **місячну ціну** для вашої команди.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}/міс</title>8</box>
Заголовок і абзац — звичайний Markdown. Теги — це компоненти з каталогу ChatGPT. Два рядки {@body …} — це JavaScript: перший оголошує змінну стану seats, а другий обчислює на її основі price. Повзунок прив'язаний до seats, тому його рух автоматично оновлює ціну.
Окремий формат потрібен тому, що модель генерує інтерфейс токен за токеном.
- Він має легко й надійно генеруватися, тому базується на синтаксисі, який модель уже добре знає.
- Він має залишатися робочим навіть у незавершеному вигляді. Інструкції розташовуються в окремих рядках, а будь-який відкритий елемент може бути закритий автоматично. Це дає змогу серверу обрізати часткову відповідь на останній цілісній конструкції та все одно її скомпілювати.
Компіляція на сервері
Клієнт ніколи не виконує вихідний код моделі безпосередньо. Сервер OpenAI компілює його у програму JavaScript і документ JSON, які зберігаються разом із повідомленням (як model_dil_v2). Відповідь компілюється ось у таке (відформатовано для читабельності):
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": "Оцінка тарифу для команди",4 "1": "Перетягніть повзунок, щоб побачити ",5 "2": "місячну ціну",6 "3": " для вашої команди.",7 "4": "$",8 "5": "/міс"9 },10 "appData": { "opGenui": { "componentResults": {}, "modelDataBindings": {} } }11}
Markdown компілюється в те саме дерево, що й компоненти. Заголовок стає title, абзац — text із вкладеним bold, а їхні слова переносяться до таблиці констант.
Компіляція виконує роботу, яку інакше довелося б повторювати кожному клієнту:
- Звичайні виклики функцій. Розмітка перетворюється на виклики
__dil.jsx, тому середовище JavaScript може виконати програму без парсера DIL. - Ізоляція помилок. Вирази загортаються у
__dilSafe, тому помилка в одному виразі прибирає лише один елемент, а не зупиняє весь рендеринг. - Текст в окремій таблиці. Статичний текст переноситься до таблиці констант, тому під час стрімінгу відповіді зростання тексту змінює дані, а не саму програму.
- Стабільні ключі стану. Кожен елемент стану отримує ключ (
{ key: "seats" }), тому його значення зберігається після кожної перекомпіляції. - Відновлення та валідація. Незавершені інструкції й теги видаляються, незакриті елементи закриваються, а властивості, які не проходять валідацію за каталогом, прибираються та фіксуються як діагностичні повідомлення.
Документ JSON містить текстові константи та будь-які дані, які сервер підготував для відповіді, наприклад результати пошуку зображень (див. розділ «Дані»).
Клієнтське середовище виконання
Клієнт отримує скомпільовану програму та документ JSON. Його робота поділена між рантаймом, який виконує програму, і рендерером, який малює результат.
Програма написана моделлю, тому вона не запускається безпосередньо на сторінці ChatGPT. Натомість ChatGPT завантажує прихований iframe (runner.html), ізольований за допомогою allow-scripts і політики безпеки контенту default-src 'none', який запускає Web Worker.
- Lockdown (блокування). Перед виконанням програми воркер прибирає зі свого глобального скоупу доступ до мережі, таймери, обмін повідомленнями та динамічне виконання коду, а решту глобальних об'єктів заморожує.
- Виконання. Потім він виконує програму через
new Function. Об'єкти рантайму (DIL,__dil,GenUI) та складені компоненти з каталогу передаються як параметри. - Watchdog (сторожовий таймер). Якщо програма не відповідає протягом визначеного часу, вона поміщається в карантин, а воркер перезапускається.
Рантайм — це невеликий узгоджувач (reconciler) у стилі React. Він рендерить компонент і зберігає стан хуків у слотах із ключами. Потім порівнює отримане дерево з попереднім і кодує відмінності у вигляді списку операцій. Сам він нічого не малює.
Наведений нижче ілюстративний приклад показує операції першого рендеру, по одному рядку на вузол. Записи, де перелічуються назви властивостей кожного елемента, пропущені:
1CREATE #1 title SET size = "lg" PLACE under root at 02CREATE #2 text "Оцінка тарифу для команди" PLACE under #1 at 03CREATE #3 text PLACE under root at 14CREATE #4 text "Перетягніть повзунок, щоб побачити " PLACE under #3 at 05CREATE #5 bold PLACE under #3 at 16CREATE #6 text "місячну ціну" PLACE under #5 at 07CREATE #7 text " для вашої команди." 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 "/міс" PLACE under #10 at 2
Функції ніколи не покидають воркер; обробник повзунка передається лише як ідентифікатор (fn#1). Під час передавання мережею операції кодуються як бінарна послідовність цілих чисел, а рядки зберігаються в окремій таблиці.
Рендеринг
Сторінка ChatGPT застосовує операції до власного дерева компонентів. Кожна команда CREATE створює екземпляр нативного компонента з дизайн-системи ChatGPT, а сторінка анімує зміни в міру їх надходження. Сторінка приймає операції лише для відомих типів компонентів, тому вивід моделі не може додати довільну розмітку чи стилі. Винятками є необроблені CSS-значення, які приймають деякі властивості (див. «Дизайн-система та каталог»), і додатки AppBlock, що працюють в iframe (див. «Аварійний люк»).
Взаємодія працює у зворотному напрямку. Коли користувач перетягує повзунок на позначку 9, сторінка надсилає ідентифікатор обробника та аргументи воркеру. Воркер викликає setSeats(9), повторно рендерить інтерфейс і повертає операції оновлення. Жодного звернення до моделі при цьому не відбувається.
Дизайн-система та каталог
Каталог визначає, що саме може запросити модель. Він необхідний, бо модель не будує інтерфейс із нуля, спираючись на базові правила верстки та стилів. Вона обирає з компонентів, які ChatGPT уже вміє малювати, і стилізує їх за допомогою дизайн-токенів, як-от padding={3}. Для деяких властивостей допускаються чисті CSS-значення (ширина в пікселях, кольори у hex-форматі), але перевага надається дизайн-токенам. Як наслідок:
- Згенеровані інтерфейси виглядають органічно на всіх платформах, як решта ChatGPT.
- Компілятор має схему для перевірки виводу. Властивість, якої немає в компоненті, або літерал неправильного типу видаляється під час компіляції та фіксується як діагностичне повідомлення.
В одній із перехоплених нами відповідей компілятор видалив дві властивості: fill на іконці (діагностика unknown_prop) та gap="1" на блоці (діагностика invalid_literal).
Каталог складається з трьох частин:
- Нативні компоненти. Близько 70 компонентів визначено в реєстрі у клієнтському коді ChatGPT; 39 із них зустрічалися у перехоплених нами відповідях.
- Дизайн-токени для відступів, радіусів, кольорів і розмірів.
- Складені компоненти, написані OpenAI мовою DIL і передані в sandbox у готовому вигляді, наприклад компоненти зображень і товарів. У наших захоплених даних модель використовувала ці компоненти, але ніколи не створювала власних.
Стрімінг
Стрімінг тексту — річ проста: кожен новий токен додається до того, що вже на екрані. Але стрімити інтерфейс значно складніше, і ось чому:
- Вивід зазвичай ще не готовий до виконання. У більшість моментів це незавершена програма з відкритим тегом чи виразом, яку неможливо виконати в такому вигляді.
- Інтерфейс має працювати, поки він формується. Компоненти, з якими користувач уже взаємодіяв, повинні зберігати свій стан.
- Деякий контент надходить окремо. Дані, як-от зображення, приходять від сервера, а не з текстового потоку.
Стрімінг на стороні сервера
Звичайний потік токенів, що додаються один за одним, тут не спрацює. Замість цього ChatGPT стрімить патчі до структурованого повідомлення, яке містить сирий текст, скомпільовану програму та її дані пліч-о-пліч.
Відповідь надходить у браузер через потік server-sent events (POST /backend-api/f/conversation). Кожна подія — це оновлення у стилі JSON-Patch для повідомлення, що формується. Одна подія зазвичай оновлює і сирий текст DIL, і його скомпільовану форму одночасно. Ось скорочений приклад одного оновлення із перехопленої відповіді:
1{"o": "patch", "v": [2 {"p": "/message/content/parts/0", "o": "append", "v": " Недільна баранина з друзями — щедра їжа, …"},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": "Ось план справжньої недільної баранини з друзями — …"}},5 {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"1": "Оскільки ви"}},6 {"p": "/message/metadata/model_dil_v2/fallbackMarkdown", "o": "append", "v": " Недільна баранина з друзями — …"}7]}
Сервер не компілює код інкрементально. Кожні кілька сотень мілісекунд — найімовірніше, з кожним новим фрагментом виводу моделі — він перекомпільовує все, що модель згенерувала на цей момент, і надсилає результат. Компіляція починається з першого токена, ще до появи будь-якого тега.
Компілятор має подбати про:
- Компіляцію наполовину написаної відповіді
- Оновлення тексту
- Оновлення UI
Хронологія виглядає приблизно так:

GIF
Стрімінг на стороні клієнта
Сторінка передає кожне нове оновлення ізольованому воркеру. Воркер виконує його, повторно рендерить інтерфейс із наявним станом і надсилає операції оновлення на сторінку. Стан зберігає свої значення між перекомпіляціями завдяки ключам, доданим під час компіляції. Якщо нова програма не виконується або не рендериться, воркер залишає останню робочу версію.
Потім сторінка анімує кожну зміну:
- текст плавно з'являється за 0,7 с;
- нові рядки та елементи сітки висуваються за 0,42 с;
- графіки малюються за 1,8 с;
- висота контейнерів змінюється плавно, а не стрибком.
Аварійний люк: AppBlock — додаток в iframe

Вбудований додаток, згенерований ChatGPT
Деякі запити потребують речей, для яких нативні компоненти не призначені, наприклад драм-машини, що синтезує звук через Web Audio. Для таких випадків модель може створити AppBlock: автономний веб-додаток на HTML, CSS і JavaScript, вбудований у відповідь. Ось скорочений початок одного з них:
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>
AppBlocks рендеряться інакше, ніж компоненти Intelligent UI.
Збираємо все докупи
Ви вводите промпт. Модель починає писати інтерфейс, сервер перетворює його на щось, що може виконати ChatGPT, а сторінка збирає все по шматочках у міру надходження відповіді. Коли інтерфейс готовий, рух повзунка або клік по чекбоксу оновлює його локально, без повторного звернення до моделі.
Спеціалізована мова для моделі, етап серверної компіляції, нативні рендерери та продумана дизайн-система — усе це працює як єдиний механізм. Кожна частина виконує критично важливу роль, а разом вони забезпечують мільярди користувачів по всьому світу інтерфейсами нового покоління, створеними для AI. Який чудовий час, щоб жити!

Скріншоти Intelligent UI у ChatGPT
Методологія
Усі спостереження зроблені на основі наших власних акаунтів ChatGPT, трафіку, який генерує веб-додаток ChatGPT, та JavaScript-коду, який публічно віддає chatgpt.com. Дослідження проводилося у жовтні 2026 року з використанням GPT-6 та GPT-6 Thinking.
Аналіз виконано за допомогою Codex і Claude. Написано у Codex, візуалізації створені у Claude.
(Детальніша версія доступна за посиланням https://www.openui.com/blog/how-chatgpt-intelligent-ui-works





