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 схожих варіантів із візуалом, потрібна прокрутка довгий список подарунків

Розуміючи це, перейдемо до конкретики: як ці патерни втілюються в інтерфейсі та коді. Далі розглянемо кожен патерн в одному й тому самому форматі. Спершу — що це з погляду 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 — «Вибрати цей подарунок»; не намагаємося втиснути сюди пʼять різних варіантів;
  • компонент легко перевикористати як у 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 або:

  • змінюють фільтри/критерії підбору (новий виклик інструмента 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 «Перейти до оплати» має вести до виклику інструмента, що запускає ACP‑оформлення замовлення (до цього ми дійдемо в модулі про комерцію й оплату).

Ще одна корисна практика — не дублювати ChatGPT‑функції у CTA. Не потрібно робити кнопку «Запитати у ChatGPT»: у користувача вже є поле введення та голос. Настанови прямо радять уникати «дублювальних» елементів введення всередині картки.

7. Inline + follow‑up: гра вдвох

Inline‑віджет ніколи не живе у вакуумі. Структура відповіді зазвичай така:

  1. модель вирішує використати ваш застосунок і викликати інструмент;
  2. ваш MCP повертає дані;
  3. ChatGPT відображає inline‑віджет із цими даними;
  4. під ним модель дописує невеликий follow‑up‑текст і готові варіанти продовження.

Для GiftGenius це може виглядати так:

  • inline‑віджет: три картки подарунків із CTA «Вибрати»;
  • текст нижче:
    «Ось три ідеї для колеги: настільна лампа, курс із публічних виступів і подарункова картка кавʼярні. Можу: — показати лише варіанти до $30; — підібрати ще кілька ідей у схожому стилі; — допомогти одразу перейти до купівлі одного з них.»

Модель у follow‑up може посилатися на CTA вашого віджета («натисніть «Вибрати» під уподобаним варіантом») або пропонувати текстові команди, які знову призведуть до виклику інструмента й перемальовування inline‑UI.

Важливо памʼятати: віджет не зобовʼязаний уміти все. Іноді краще частину сценарію залишити на текстовий діалог, а віджет використовувати як «візуальний блок» усередині розмови.

8. Як це вбудовано в загальний процес GiftGenius

Щоб було зрозуміліше, зведемо все в просту діаграму послідовності:

sequenceDiagram
  participant U as Користувач
  participant C as ChatGPT
  participant A as Віджет GiftGenius
  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‑віджет — частина загального інтерфейсу ChatGPT, а не банер із 2007 року.

По‑друге, уникайте внутрішніх смуг прокрутки. Якщо ваша картка настільки довга, що всередині неї зʼявляється власна смуга прокрутки, щось пішло не так: або ви намагаєтеся вмістити туди надто багато вмісту, або патерн обрано неправильно (можливо, потрібен fullscreen).

По‑третє, тримайте щільність під контролем:

  • між картками має бути помітний проміжок;
  • CTA має бути зручною для натискання (адекватні внутрішні відступи);
  • текст — читабельним навіть на мобільних, без мікроскопічних шрифтів.

Усе це може звучати як «дизайнерські прискіпування», але практика показує: якщо inline‑віджет виглядає «рідним», модель охочіше його використовує, а користувачі менше плутаються.

10. Практика: як розвинути GiftGenius на основі лекції

Якщо ви хочете закріпити матеріал, ось простий практичний чекліст:

Спочатку візьміть поточний результат інструмента giftgenius.suggestGifts (масив подарунків) і:

  1. Реалізуйте три різні варіанти UI в одному компоненті:
    • GiftGrid із картками;
    • GiftList із текстовим списком;
    • GiftCarousel із навігацією «попередній/наступний».
  2. Додайте до них по одній‑дві CTA‑кнопки:
    • для карток — «Вибрати»;
    • для списку — «Детальніше»;
    • для каруселі — також «Вибрати», а ще — окрема кнопка під віджетом «Показати ще варіанти».
  3. Залежно від стану (наприклад, скільки всього подарунків повернув інструмент) обирайте, який патерн використовувати:
    • якщо варіантів мало (≤ 3) — сітка карток;
    • якщо багато текстових ідей — список;
    • якщо багато візуальних подарунків — карусель.

Так ви не лише потренуєтеся в UI, а й почнете думати про динамічний вибір патерну залежно від контексту, що дуже сподобається і користувачам, і рецензентам у Store.

Загалом inline‑патерни — це швидкий і легкий шар UI, який живе прямо у стрічці чату й не намагається замінити собою окремий застосунок. Картки, списки й каруселі закривають 80% типових завдань: показати варіанти, дати обрати й акуратно продовжити діалог.

У наступній лекції цього модуля подивимося, що робити, коли inline уже «не тягне»: розберемо fullscreen‑майстри, PiP‑режим і сценарії, де вашому застосунку справді потрібен окремий великий екран усередині ChatGPT.

11. Типові помилки при роботі з inline‑патернами

Помилка № 1: перетворювати inline‑віджет на мінісайт.
Іноді розробники намагаються втиснути в одну картку вкладки, акордеони, форми, таблицю та купу інших елементів. У результаті виходить важкий інтерфейс, який ламає ритм чату й стає незручним на мобільних пристроях. Настанови прямо кажуть: жодних глибоких навігацій і складних подань усередині 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 залишити для швидких превʼю та резюме дій.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ