JavaRush /Курсы /ChatGPT Apps /Inline‑паттерны: карточки, списки, карусели и CTA

Inline‑паттерны: карточки, списки, карусели и CTA

ChatGPT Apps
8 уровень , 1 лекция
Открыта

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‑виджет никогда не живёт в вакууме. Структура ответа обычно такая:

  1. модель решает использовать ваш App и вызвать инструмент;
  2. ваш MCP возвращает данные;
  3. ChatGPT рендерит inline‑виджет с этими данными;
  4. под ним модель дописывает небольшой 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 (массив подарков) и:

  1. Реализуйте три разных варианта UI в одном компоненте:
    • GiftGrid с карточками;
    • GiftList с текстовым списком;
    • GiftCarousel с навигацией «предыдущий/следующий».
  2. Добавьте к ним по одной‑две CTA‑кнопки:
    • для карточек — «Выбрать»;
    • для списка — «Подробнее»;
    • для карусели — тоже «Выбрать», плюс отдельная кнопка под виджетом «Показать ещё варианты».
  3. В зависимости от состояния (например, сколько всего подарков вернул инструмент) выбирайте, какой паттерн использовать:
    • если вариантов мало (≤ 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 оставить для быстрых превью и резюме действий.

1
Задача
ChatGPT Apps, 8 уровень, 1 лекция
Недоступна
Мини‑карточка в inline‑стиле + один CTA (openExternal)
Мини‑карточка в inline‑стиле + один CTA (openExternal)
1
Задача
ChatGPT Apps, 8 уровень, 1 лекция
Недоступна
Inline‑список (5–10 строк) + CTA “Подробнее” → follow‑up в чат
Inline‑список (5–10 строк) + CTA “Подробнее” → follow‑up в чат
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ