1. Что такое inline‑режим и почему это «дефолт»
Официальные гайдлайны OpenAI подчёркивают: inline‑отображение — основной режим для ChatGPT Apps. Inline‑виджет рендерится прямо в ленте чата поверх ответа модели и включает небольшой UI‑блок (карточку, список, карусель) плюс follow‑up‑сообщение от GPT под ним.
Идея простая: вместо того чтобы уводить пользователя в отдельный большой интерфейс, мы даём ему компактный визуальный «всплеск» прямо в контексте разговора: модель поясняет, что произошло и какие дальше варианты, а виджет аккуратно показывает структуру и доступные действия.
Inline‑виджет:
- лёгкий по содержанию и не требует много шагов;
- не заводит пользователя в сложную навигацию с табами и внутренними полосами прокрутки;
- решает одну‑две маленькие задачи: показать список опций, дать выбрать, подтвердить действие, показать статус.
Fullscreen‑режим (о нём в следующей лекции) нужен для крупных мастеров и сложного контента. Сейчас важно другое: по умолчанию думайте inline, а fullscreen включайте осознанно, когда inline уже явно не справляется.
Наша задача в этой лекции — научиться уверенно оперировать тремя основными inline‑паттернами и поверх них работать с CTA:
- карточки
- списки
- карусели
а также грамотно навешивать на них CTA‑кнопки (Call to Action).
2. Когда inline лучше fullscreen
Если упростить, inline‑режим — это «быстрый помощник», а fullscreen — «отдельное приложение внутри ChatGPT».
Inline особенно хорош, когда:
- нужно показать несколько вариантов и дать выбрать один-два;
- результат можно выразить в компактной структуре: карточка подарка, сводка по заказу, мини‑табличка;
- пользователь выполняет короткое действие: «выбрать», «показать подробнее», «изменить фильтр»;
- диалог остаётся главным: GPT объясняет, шутит, комментирует, а виджет просто даёт удобную форму или визуал.
В терминах GiftGenius inline — это:
- показать 3–5 лучших подарков для выбранного человека;
- дать быстрый фильтр: «покажи только цифровые подарки»;
- подтвердить выбор: «вот резюме заказа, всё ок?».
Fullscreen вам пригодится позже для трёхшагового мастера сложного чекаута. Сейчас же мы остаёмся в лёгкой зоне: один результат вызова инструмента → один inline‑виджет.
Для наглядности — небольшая таблица:
| Паттерн | Когда идеально подходит | Пример в GiftGenius |
|---|---|---|
| Карточка | 1–3 сущности с ключевыми параметрами и CTA | 3 топ‑подарка |
| Список | 5–10 текстовых пунктов, важна читаемость | список идей без картинок |
| Карусель | 3–8 похожих вариантов с визуалом, нужна прокрутка | длинный список подарков |
Осознавая это, давайте спустимся на конкретику: как эти паттерны воплощаются в UI и коде. Дальше пойдём по каждому паттерну в одном и том же формате: сначала — что это с точки зрения UX, потом — простая React‑компонента под GiftGenius и, наконец, как всё это встраивается в inline‑виджет.
3. Карточки: основной кирпич inline‑UI
Что такое карточка в контексте Apps SDK
Согласно гайдлайнам OpenAI, inline‑карточка — это лёгкий, однопользовательский виджет, который показывает небольшое количество структурированных данных и 1–2 действия внизу. Она может иметь заголовок, картинку, пару строк метаданных и одну первичную CTA‑кнопку (плюс необязательную вторичную).
В GiftGenius каждая карточка — это один подарок. В ней удобно разместить:
- название подарка;
- цену;
- для кого подходит (например, «коллега», «близкий друг»);
- короткое объяснение, почему это хороший вариант;
- кнопку «Выбрать этот подарок» или «Подробнее».
Карточка должна быть самодостаточной: пользователь, глянув на неё, уже понимает, что за сущность перед ним и что главное действие.
Тип данных и простая компонентка GiftCard
Сначала определим тип данных для подарка. Пусть у нас уже есть ToolOutput с массивом таких объектов; здесь нас интересует только UI‑часть.
// Общая структура подарка для UI
export type GiftSuggestion = {
id: string;
title: string;
priceLabel: string; // например "≈ 40 $"
recipientLabel: string; // "для коллеги"
reason?: string; // пояснение от модели
imageUrl?: string;
};
Теперь сделаем простую React‑компоненту карточки:
type GiftCardProps = {
gift: GiftSuggestion;
onSelect: (gift: GiftSuggestion) => void;
};
export function GiftCard({ gift, onSelect }: GiftCardProps) {
return (
<div className="flex flex-col gap-2 rounded-lg border p-3">
<div className="text-sm font-medium">{gift.title}</div>
<div className="text-xs text-muted-foreground">
Для: {gift.recipientLabel} · {gift.priceLabel}
</div>
{gift.reason && (
<div className="text-xs text-muted-foreground">{gift.reason}</div>
)}
<button
className="mt-2 self-start rounded bg-primary px-3 py-1 text-xs text-primary-foreground"
onClick={() => onSelect(gift)}
>
Выбрать этот подарок
</button>
</div>
);
}
Несколько нюансов сразу:
- мы не перегружаем карточку текстом, максимум 2–3 строки метаданных и короткое объяснение;
- одна основная CTA — «Выбрать этот подарок»; не пытаемся впихнуть сюда 5 разных вариантов;
- компоненту легко переиспользовать как в inline‑списке, так и внутри карусели.
Как карточки вписываются в общий виджет
Предположим, что у нас есть массив gifts, полученный после вызова инструмента giftgenius.suggestGifts через наш MCP. Виджет в inline‑режиме может просто отрисовать их сеткой 1–3 колонки.
type GiftGridProps = {
gifts: GiftSuggestion[];
onSelect: (gift: GiftSuggestion) => void;
};
export function GiftGrid({ gifts, onSelect }: GiftGridProps) {
return (
<div className="grid gap-3 sm:grid-cols-2">
{gifts.map((gift) => (
<GiftCard key={gift.id} gift={gift} onSelect={onSelect} />
))}
</div>
);
}
Здесь мы:
- используем сетку на 1–2 колонки, чтобы не превращать виджет в «кирпичную стену»;
- легко можем ограничить число карточек, например, показывать только первые 3–6.
Сам обработчик onSelect может вызывать инструмент чекаута или просто сохранять выбор в Widget State и дать модели продолжить диалог. Простейший пример интеграции с инструментом:
async function handleSelect(gift: GiftSuggestion) {
await window.openai.actions.call("giftgenius.startCheckout", {
giftId: gift.id,
});
}
Здесь window.openai.actions.call — это мост для вызова зарегистрированного MCP‑инструмента прямо из виджета.
Обычно после такого вызова модель покажет либо статус, либо откроет следующий виджет (например, резюме заказа). Главное — не пытаться делать весь чекаут логикой внутри карточки; карточка должна запустить понятный следующий шаг.
4. Списки: когда визуал не главное
Если карточка — это небольшой «постер», то список — это аккуратный текстовый перечень. Документация и UX‑рекомендации показывают, что списки хороши, когда важнее содержание текста, а не яркий визуальный акцент.
Список подойдёт, когда:
- нужно показать 5–10 вариантов, но они не требуют картинки;
- пользователь хочет просто «пробежаться глазами» по названиям и кратким описаниям;
- действия у всех пунктов одинаковые, и UI не должен отвлекать.
Примеры в GiftGenius:
- список «быстрых» идей подарков без подробностей;
- список избранных категорий: «для коллег», «для родителей», «для детей»;
- список сохранённых подборок («Подарки для HR‑отдела», «Новогодние мелочи до $20»).
Простой компонент списка
Сделаем компактный список с одной CTA‑кнопкой «Подробнее» справа.
type GiftListProps = {
gifts: GiftSuggestion[];
onSelect: (gift: GiftSuggestion) => void;
};
export function GiftList({ gifts, onSelect }: GiftListProps) {
return (
<ul className="flex flex-col gap-2">
{gifts.map((gift) => (
<li
key={gift.id}
className="flex items-center justify-between rounded-md border px-3 py-2 text-sm"
>
<span className="truncate">{gift.title}</span>
<button
className="text-xs text-primary"
onClick={() => onSelect(gift)}
>
Подробнее
</button>
</li>
))}
</ul>
);
}
Тут мы:
- даём заголовку truncate, чтобы длинные названия не ломали верстку;
- снова используем одну CTA на элемент;
- оставляем всю «богатую» информацию (описание, картинку, отзывы) для следующего шага — например, при клике открывать отдельную карточку или fullscreen‑просмотр.
Список особенно хорошо комбинируется с follow‑up‑предложениями от GPT. Виджет показывает список «кандидатов», а под ним GPT пишет что‑то вроде:
«Могу сузить до подарков до $30 или показать только цифровые. Что выберем?» и предлагает две-три кнопки follow‑up.
В отдельном разделе мы ещё разберём, как именно лучше комбинировать inline‑виджеты и follow‑up‑сообщения в разных сценариях.
5. Карусели: когда вариантов много, но всё похоже
Карусель — это набор карточек, расположенных горизонтально и пролистываемых свайпом или кнопками навигации. Гайдлайны рекомендуют использовать карусели, когда вы показываете небольшой список похожих элементов (обычно 3–8), каждый из которых содержит изображение, заголовок и немного метаданных.
Главная идея: пользователь может быстро просканировать набор вариантов, не засыпав под бесконечным вертикальным списком.
В GiftGenius карусель пригодится, если:
- у нас есть 10–15 подходящих подарков, но inline‑виджет должен показать только «горячую восьмёрку»;
- каждый подарок визуально приятен (картинка, оформление);
- важно, чтобы пользователь перелистывал варианты, не уезжая далеко вниз по чату.
UX‑правила для каруселей
На основе гайдлайнов и ресёрча:
- количество карточек в карусели — от 3 до 8; если их больше, лучше дать отдельную команду «Показать ещё»;
- каждая карточка:
- должна иметь картинку или иной визуальный элемент;
- не должна содержать больше двух строк текста‑метаданных;
- имеет одну понятную CTA, например «Выбрать» или «Подробнее»;
- никаких сложных вложенных навигаций (табы, подпереходы) внутри карточки;
- избегаем внутренних (вертикальных) полос прокрутки: пусть высота карточки адаптируется до разумного лимита, но без собственной полосы прокрутки.
Простая карусель по принципу «одна карточка за раз»
Чтобы не лезть в сложный горизонтальный скролл, можно реализовать самый простой вариант: показывать одну карточку за раз и дать кнопки «предыдущий/следующий».
import { useState } from "react";
type GiftCarouselProps = {
gifts: GiftSuggestion[];
onSelect: (gift: GiftSuggestion) => void;
};
export function GiftCarousel({ gifts, onSelect }: GiftCarouselProps) {
const [index, setIndex] = useState(0);
const gift = gifts[index];
return (
<div className="flex flex-col gap-2">
<GiftCard gift={gift} onSelect={onSelect} />
<div className="flex items-center justify-between text-xs">
<button
disabled={index === 0}
onClick={() => setIndex((i) => i - 1)}
>
← Предыдущий
</button>
<span>
{index + 1} / {gifts.length}
</span>
<button
disabled={index === gifts.length - 1}
onClick={() => setIndex((i) => i + 1)}
>
Следующий →
</button>
</div>
</div>
);
}
Это уже даёт ощущение «карусели», но при этом:
- код остаётся компактным;
- не нужно бороться с шириной контейнера и горизонтальными скроллами внутри виджета;
- легко ограничить gifts до 8 элементов перед передачей компоненту.
Если хочется более «настоящей» карусели, можно использовать overflow-x-auto и фиксированную ширину карточек, но это тот случай, когда проще взять готовый компонент из UI‑библиотеки (shadcn/ui, Radix‑совместимые решения и т.п.), а не изобретать собственный с нуля.
6. CTA‑кнопки: мало, понятно, по делу
CTA (Call to Action) — сердце любого inline‑паттерна. Именно кнопки превращают ваш виджет из картиночки в рабочий инструмент.
Основные принципы
Документация OpenAI даёт довольно жёсткие рекомендации:
- на карточке — максимум две первичные кнопки (одна основная, вторая — второстепенная);
- в карусели — по одной CTA на элемент по возможности;
- текст CTA должен быть конкретным глаголом: «Показать подробности», «Добавить в список», «Перейти к оплате», а не абстрактным «Ок» или «Действие».
Чем меньше кнопок, тем проще модели и пользователю. Не забывайте, что над и под виджетом ещё есть текстовая часть ответа, плюс follow‑up‑предложения GPT.
Привязка CTA к логике приложения
В нашем GiftGenius большинство CTA будет либо:
- изменять фильтры/критерии подбора (новый tool‑call giftgenius.refineSearch),
- запускать чекаут (giftgenius.startCheckout),
- открывать внешний сайт (через openExternal, уже знакомый вам по ранним лекциям).
Пример простого обработчика для CTA «Изменить фильтры»:
async function handleRefineFilters(gift: GiftSuggestion) {
await window.openai.actions.call("giftgenius.refineSearch", {
baseGiftId: gift.id,
});
}
С точки зрения UX очень важно проговаривать в system‑инструкциях, когда и какие именно CTA‑кнопки модель должна предлагать. Например:
- если пользователь просит «покажи ещё варианты», лучше показать новую карусель с кнопкой «Выбрать»;
- если дело дошло до покупки, CTA «Перейти к оплате» должна вести к tool‑вызову, запускающему ACP‑чекаут (к этому мы придём в модуле про коммерцию и оплаты).
Ещё одна полезная практика — не дублировать ChatGPT‑функции в CTA. Не нужно делать кнопку «Спросить у ChatGPT», у пользователя уже есть поле ввода и голос. Гайдлайны прямо рекомендуют избегать «дублирующих» вводов внутри карты.
7. Inline + follow‑up: игра вдвоём
Inline‑виджет никогда не живёт в вакууме. Структура ответа обычно такая:
- модель решает использовать ваш App и вызвать инструмент;
- ваш MCP возвращает данные;
- ChatGPT рендерит inline‑виджет с этими данными;
- под ним модель дописывает небольшой follow‑up‑текст и готовые варианты продолжения.
Для GiftGenius это может выглядеть так:
- inline‑виджет: три карточки подарков с CTA «Выбрать»;
- текст ниже:
«Вот три идеи для коллеги: настольная лампа, курс по публичным выступлениям и подарочная карта кафе. Могу: — показать только варианты до $30; — подобрать ещё несколько идей в похожем стиле; — помочь сразу перейти к покупке одного из них.»
Модель в follow‑up может ссылаться на CTA вашего виджета («нажмите “Выбрать” под понравившимся вариантом») или предлагать текстовые команды, которые снова приведут к tool‑call и перерисовке inline‑UI.
Важно помнить: виджет не обязан уметь всё. Иногда лучше часть сценария оставить на текстовый диалог, а виджет использовать как «визуальный блок» внутри разговора.
8. Как это встроено в общий флоу GiftGenius
Чтобы было понятнее, сведём всё в простую диаграмму последовательности:
sequenceDiagram participant U as Пользователь participant C as ChatGPT participant A as GiftGenius Widget participant B as MCP/Backend U->>C: "Подбери 3 подарка до 50$ для коллеги" C->>B: call_tool(giftgenius.suggestGifts) B-->>C: 3 лучших варианта C->>A: рендер inline-виджета (карточки/карусель) A-->>U: карточки с CTA "Выбрать" U->>A: клик по CTA A->>B: call_tool(giftgenius.startCheckout) B-->>A: статус / ссылка на оплату A-->>U: резюме выбора / статус C-->>U: follow-up: "Могу подобрать ещё идеи или помочь с открыткой"
С архитектурной точки зрения:
- MCP остаётся «мозгами» (подбор, бизнес‑логика, ACP),
- виджет — «лицом» (карточки/списки/карусели),
- ChatGPT — «ведущим диалога», который объясняет, что произошло, и предлагает следующие шаги.
Чтобы этот флоу был удобным:
- не перегружайте виджет действиями;
- держите данные в карточках компактными;
- обдумывайте, какие follow‑up‑варианты будут полезны после каждого inline‑отображения.
9. Немного о визуальной стороне inline‑паттернов
Мы детально поговорим о визуальном дизайне в одной из следующих лекций модуля, но несколько моментов, критичных для inline‑паттернов, лучше упомянуть уже сейчас.
Во‑первых, убедитесь, что ваши карточки и списки не выглядят как чужой сайт внутри ChatGPT. Цвета и отступы должны быть аккуратными, без кислотных градиентов и шрифтов Comic Sans. Inline‑виджет — часть общего UI ChatGPT, а не баннер из 2007 года.
Во‑вторых, избегайте внутренних полос прокрутки. Если ваша карточка настолько длинная, что внутри неё появляется собственная полоса прокрутки, что‑то пошло не так: либо вы пытаетесь впихнуть туда слишком много контента, либо паттерн выбран неправильно (возможно, нужен fullscreen).
В‑третьих, держите плотность под контролем:
- между карточками должен быть видимый зазор;
- CTA должна быть легко нажимаемой (адекватный padding);
- текст — читаемым даже на мобильниках, без микроскопических шрифтов.
Всё это может звучать как «дизайнерские придирки», но практика показывает: если inline‑виджет выглядит как «родной», модель охотнее его использует, а пользователи меньше путаются.
10. Практика: как развить GiftGenius на основе лекции
Если вы хотите закрепить материал, есть простой практический чек‑лист:
Сначала возьмите текущий результат инструмента giftgenius.suggestGifts (массив подарков) и:
- Реализуйте три разных варианта UI в одном компоненте:
- GiftGrid с карточками;
- GiftList с текстовым списком;
- GiftCarousel с навигацией «предыдущий/следующий».
- Добавьте к ним по одной‑две CTA‑кнопки:
- для карточек — «Выбрать»;
- для списка — «Подробнее»;
- для карусели — тоже «Выбрать», плюс отдельная кнопка под виджетом «Показать ещё варианты».
- В зависимости от состояния (например, сколько всего подарков вернул инструмент) выбирайте, какой паттерн использовать:
- если вариантов мало (≤ 3) — сетка карточек;
- если много текстовых идей — список;
- если много визуальных подарков — карусель.
Так вы не только потренируетесь в UI, но и начнёте думать о динамическом выборе паттерна в зависимости от контекста, что очень понравится и пользователям, и ревьюерам в Store.
В общем inline‑паттерны — это быстрый, лёгкий слой UI, который живёт прямо в ленте чата и не пытается заменить собой отдельное приложение. Карточки, списки и карусели закрывают 80% типовых задач: показать варианты, дать выбрать и аккуратно продолжить диалог.
В следующей лекции этого модуля мы посмотрим, что делать, когда inline уже «не тянет»: разберём fullscreen‑мастера, PiP‑режим и сценарии, где вашему App действительно нужен отдельный большой экран внутри ChatGPT.
11. Типичные ошибки при работе с inline‑паттернами
Ошибка №1: превращать inline‑виджет в мини‑сайт.
Иногда разработчики пытаются впихнуть в одну карточку вкладки, аккордеоны, формы, таблицу и кучу других элементов. В результате получается тяжёлый UI, который ломает ритм чата и становится неудобен на мобильных устройствах. Гайдлайны прямо говорят: никаких глубоких навигаций и сложных вьюшек внутри inline‑карточек; сложные сценарии выносятся в fullscreen.
Ошибка №2: слишком много CTA‑кнопок.
«А давайте на карточке сделаем “Подробнее”, “Купить”, “В избранное”, “Поделиться”, “Пожаловаться” и “Сгенерировать открытку”». В итоге пользователь теряется, модель тоже, и вероятность, что нажмут нужную кнопку, падает. Помните правило: одна основная CTA и максимум одна вторичная. Остальные сценарии лучше вынести в follow‑up‑сообщение GPT или последующие шаги.
Ошибка №3: смешивать список, карточки и карусель в одном ответе без причины.
Если одно и то же содержимое показывается то списком, то карточками, то каруселью «просто потому что мы так можем», пользователь теряет ощущение консистентности. Лучше выбрать один паттерн на конкретный тип выдачи (например, идеи без картинок — список, подарки с картинками — карусель) и держаться его.
Ошибка №4: делать карточки перегруженными текстом.
Карточка, в которой три абзаца описания, три цены и два блока «почему это классно», превращается в стену текста. Пользователь перестаёт её «сканировать», а просто прокручивает мимо. Старайтесь оставлять в карточке только самое важное: заголовок, один ключевой параметр, одну короткую причину и CTA. Всё остальное можно объяснить в текстовом ответе GPT рядом.
Ошибка №5: полагаться только на UI и игнорировать follow‑up‑диалог.
Иногда можно встретить подход «всё делаем через кнопки, пользователю не надо разговаривать». Это противоречит самой идее ChatGPT. Inline‑виджет должен дополнять диалог, а не подменять его. Не забывайте продумывать, какие follow‑up‑варианты модель может предложить под виджетом: изменить фильтры, запросить больше вариантов, перейти к следующему шагу.
Ошибка №6: игнорировать ограничения по количеству элементов.
Карусель из 25 карточек или список из 50 пунктов внутри одного inline‑виджета — верный путь к тому, чтобы пользователь заскроллил мимо всего. Документация рекомендует 3–8 элементов в карусели и 5–10 позиций в списке. Если данных больше, полезно добавить CTA вроде «Показать ещё» или «Показать всё текстом».
Ошибка №7: использовать inline там, где уже нужен fullscreen.
Бывает соблазн «всё делать inline», даже когда уже есть 4 шага, формы с десятком полей и большие таблицы. В итоге вы либо получаете монстра, либо начинаете изобретать вложенные скроллы и псевдостепперы внутри карточек. Как только чувствуете, что шагов и полей становится много — это сигнал задуматься о переходе к fullscreen‑мастеру, а inline оставить для быстрых превью и резюме действий.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ