1. inline モードとは何か、そしてなぜそれが「デフォルト」なのか
OpenAI の公式ガイドラインは強調します:inline 表示は ChatGPT Apps の基本モードです。 インライン・ウィジェットはチャットのタイムライン内でモデルの回答の上に直接レンダリングされ、小さな UI ブロック(カード、リスト、カルーセル)に加えて、その下に GPT からのフォローアップメッセージを含みます。
アイデアはシンプルです。ユーザーを大きな別 UI に連れ出すのではなく、会話の文脈の中でコンパクトなビジュアルの「スパイク」を提示します。 モデルが何が起きたかと次の選択肢を説明し、ウィジェットは構造と利用可能なアクションをすっきり見せます。
インライン・ウィジェットの特徴:
- 内容が軽量で、手順が多くない;
- タブや内部スクロールのある複雑なナビゲーションに誘導しない;
- 1~2 個の小さなタスクを解決する:オプションの提示、選択、アクションの確認、ステータス表示。
Fullscreen モード(これについては次の講義で扱います)は、大型ウィザードや重いコンテンツ向けです。ここで重要なのは別の点です。 デフォルトでは inline で考え、fullscreen は inline では明らかに足りないと判断できるときに意識的に使いましょう。
この講義の目的は、主要な 3 つの inline パターンを確実に使いこなし、その上で CTA を扱えるようにすることです。
- カード
- リスト
- カルーセル
そして、それらに適切に CTA ボタン(Call to Action)を付与する方法を学びます。
2. inline が fullscreen より適している場面
端的に言えば、inline モードは「素早いアシスタント」、fullscreen は「ChatGPT 内の別アプリ」です。
インラインが特に有効なのは次のようなときです。
- いくつかの候補を見せて 1~2 個選んでもらう必要がある;
- 結果がコンパクトな構造で表現できる:ギフトカード、注文サマリー、ミニテーブル;
- ユーザーが短いアクションを行う:「選ぶ」「詳細を表示」「フィルターを変更」;
- 対話が主役であり続ける:GPT が説明・コメントし、ウィジェットは簡便なフォームやビジュアルを提供するだけ。
GiftGenius の文脈で inline とは:
- 対象者向けのベストなギフトを 3~5 件表示する;
- 高速なフィルターを提供する:「デジタルギフトだけ見せて」;
- 選択を確認する:「これが注文の要約です。問題ありませんか?」。
Fullscreen は、後で 3 ステップの複雑なチェックアウト・ウィザードに役立つでしょう。今は軽量ゾーンに留まりましょう。 ツール呼び出し 1 回の結果 → 1 つの inline ウィジェット、が基本です。
分かりやすくするため、小さな表を示します。
| パターン | 最適な場面 | GiftGenius の例 |
|---|---|---|
| カード | 1~3 件のエンティティ(主要パラメータ+CTA) | トップギフト 3 件 |
| リスト | 5~10 のテキスト項目。可読性が重要 | 画像なしのアイデア一覧 |
| カルーセル | ビジュアル付きの類似候補を 3~8 件。横スクロールが必要 | ギフトの長い一覧 |
これを踏まえ、次は具体化に進みます。各パターンについて、同じ流れで見ていきます。まず UX の観点、次に GiftGenius 向けのシンプルな React コンポーネント、最後に inline ウィジェットへの組み込みです。
3. カード:inline UI の基本ブロック
Apps SDK におけるカードとは
OpenAI のガイドラインによれば、inline カードは、少量の構造化データと下部に 1~2 個のアクションを示す軽量なシングルユーザー・ウィジェットです。見出し、画像、数行のメタ情報、そして 1 つの主要な CTA ボタン(任意で副次ボタン)を持てます。
GiftGenius では、各カードが 1 つのギフトです。カードには次を載せると便利です。
- ギフト名;
- 価格;
- 対象(例:「同僚」「親しい友人」);
- なぜ良い選択なのかの短い説明;
- 「このギフトを選ぶ」や「詳しく見る」のボタン。
カードは自足的であるべきです。ぱっと見て、そのエンティティが何で、主要アクションが何かが分かること。
データ型とシンプルなコンポーネント 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 は 1 つ(「このギフトを選ぶ」)。ここに 5 つの選択肢を押し込まない;
- コンポーネントは inline のリストでもカルーセル内でも再利用しやすい。
カードを全体ウィジェットにどう組み込むか
ツール giftgenius.suggestGifts(MCP 経由)呼び出しの結果として得た配列 gifts があるとします。 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 以下の年末小物」)。
シンプルなリストコンポーネント
右側に 1 つの 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 は 1 つにする;
- 「リッチ」情報(説明、画像、レビューなど)は次のステップに回す。たとえばクリックで個別カードや fullscreen 表示へ。
リストは特に、GPT のフォローアップ提案と相性が良いです。 ウィジェットが「候補一覧」を示し、その下で GPT が次のように書き添えます。
「$30 以下のギフトに絞ることも、デジタルギフトだけにすることもできます。どれにしますか?」といった具合に、2~3 個のフォローアップボタンを提案します。
別のセクションで、さまざまなシナリオにおいて inline ウィジェットとフォローアップメッセージをどのように組み合わせるのがよいか、改めて扱います。
5. カルーセル:候補が多いが似通っているとき
カルーセルは、カードが横に並び、スワイプやナビゲーションボタンでめくっていく UI です。 ガイドラインでは、似通った要素の小さなリスト(通常 3~8 件)を表示する際にカルーセルを推奨しています。 各要素は画像、タイトル、少量のメタ情報を含みます。
要点は、縦に無限に伸びるリストで埋もれさせず、ユーザーが素早く候補群をスキャンできることです。
GiftGenius でカルーセルが役立つのは、次のような場合です。
- 10~15 件の適切なギフトがあるが、inline ウィジェットでは「旬の 8 件」だけを見せたい;
- 各ギフトに魅力的なビジュアル(画像やデザイン)がある;
- ユーザーに、チャットを大きくスクロールせず候補をめくってもらいたい。
カルーセルの UX ルール
ガイドラインとリサーチに基づくポイント:
- カルーセル内のカード数は3~8。それ以上は「さらに表示」を別コマンドで;
- 各カードは:
- 画像などのビジュアル要素を持つ;
- メタ情報は 2 行以内に収める;
- 明確な CTA を 1 つ持つ(例:「選ぶ」「詳しく見る」)。
- カード内部に複雑な入れ子ナビゲーション(タブやサブ遷移)は設けない;
- 内部(縦方向)のスクロールバーは避ける:高さは適切な範囲で可変にしても、カード内に独自スクロールは持たせない。
「1 枚ずつ見せる」最小実装のカルーセル
難しい横スクロールに踏み込まなくても、最小構成として「1 枚ずつ表示し、前後ボタンで切り替える」を実装できます。
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 のドキュメントは、かなり厳密な推奨を示しています。
- カードでは主要ボタンは最大 2 つ(1 つは主、もう 1 つは副);
- カルーセルでは、可能なら各アイテムに 1 つの CTA;
- CTA 文言は具体的な動詞にする:「詳細を表示」「リストに追加」「支払いに進む」など。抽象的な「OK」や「アクション」は避ける。
ボタンが少ないほど、モデルにもユーザーにも優しい設計になります。ウィジェットの上下にはテキストの回答や GPT のフォローアップ提案もあることを忘れないでください。
CTA をアプリのロジックに結び付ける
GiftGenius の多くの CTA は次のいずれかになります。
- フィルター/検索条件の変更(新しい tool 呼び出し 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 チェックアウトを開始するツール呼び出しにつなげる(これはコマース/決済のモジュールで扱います)。
もう 1 つの良い実践は、ChatGPT の機能を CTA で重複させないことです。「ChatGPT に聞く」ボタンは不要です。ユーザーにはすでに入力欄と音声があります。 ガイドラインは、カード内の「重複する」入力を避けるようはっきり勧めています。
7. Inline + follow‑up:二人三脚
インライン・ウィジェットは真空中で動くわけではありません。回答の構造は通常、次のようになります。
- モデルがあなたの App を使うと判断し、ツールを呼び出す;
- MCP がデータを返す;
- ChatGPT がそのデータで inline ウィジェットをレンダリングする;
- その下に、モデルが短いフォローアップテキストと次の選択肢を提案する。
GiftGenius の例:
- inline ウィジェット:CTA「選ぶ」付きのギフトカード 3 枚;
- 下のテキスト:
「同僚向けの 3 つのアイデアです:デスクライト、パブリックスピーキング講座、カフェのギフトカード。できますよ。 — $30 以下の候補だけを表示する — 似たスタイルでさらにいくつか探す — この中のいずれかの購入にすぐ進むお手伝いをする」
フォローアップでは、モデルがあなたのウィジェットの CTA を参照(「気に入ったものの『選ぶ』を押してください」)したり、テキストコマンドを提案して、 再び tool 呼び出しと inline UI の再描画につなげたりします。
覚えておきたいのは、ウィジェットがすべてを担う必要はないということ。ときには、シナリオの一部はテキスト対話に任せ、 ウィジェットを会話内の「ビジュアル・ブロック」として使うほうが良い場合もあります。
8. GiftGenius 全体フローへの組み込み
わかりやすいよう、簡単なシーケンス図にまとめます。
sequenceDiagram participant U as ユーザー participant C as ChatGPT participant A as GiftGenius ウィジェット participant B as MCP/Backend U->>C: "同僚向けに 50$ 以内でギフトを 3 つ選んで" C->>B: call_tool(giftgenius.suggestGifts) B-->>C: ベストな 3 件 C->>A: インライン・ウィジェットをレンダリング(カード/カルーセル) A-->>U: CTA「選ぶ」付きのカード U->>A: CTA をクリック A->>B: call_tool(giftgenius.startCheckout) B-->>A: ステータス / 決済リンク A-->>U: 選択の要約 / ステータス C-->>U: フォローアップ: "さらにアイデアを探したり、メッセージカード作成を手伝えます"
アーキテクチャの観点:
- MCP は「頭脳」(レコメンド、ビジネスロジック、ACP);
- ウィジェットは「顔」(カード/リスト/カルーセル);
- ChatGPT は「対話の進行役」。何が起きたかを説明し、次のステップを提案します。
このフローを使いやすくするために:
- ウィジェットにアクションを詰め込みすぎない;
- カード内のデータはコンパクトに保つ;
- 各 inline 表示の後に有用なフォローアップ選択肢が何かを考える。
9. inline パターンのビジュアル面について少し
ビジュアルデザインはこのモジュールの後半で詳しく扱いますが、inline パターンで重要な点をいくつか挙げておきます。
第一に、カードやリストがChatGPT の中に別サイトが埋まっているように見えないようにしてください。 配色と余白は丁寧に。毒々しいグラデーションや Comic Sans のような書体は避けましょう。 インライン・ウィジェットは ChatGPT 全体 UI の一部であり、2007 年のバナーではありません。
第二に、内部スクロールバーは避けます。 カードが長くなりすぎて内部にスクロールバーが出るようなら、何かが間違っています。 コンテンツを詰め込みすぎているか、パターンを誤選択しています(おそらく fullscreen が必要)。
第三に、情報密度をコントロールすること:
- カード同士の間には十分な間隔を確保する;
- CTA は押しやすいサイズ(適切なパディング)にする;
- テキストはモバイルでも読みやすく。極端に小さい文字は避ける。
これらは「デザイン上のこだわり」に聞こえるかもしれませんが、実務では、inline ウィジェットが「ネイティブ」に見えるほど、 モデルは積極的にそれを使い、ユーザーの混乱も減ります。
10. 実践:この講義を踏まえて GiftGenius を強化する
内容を定着させたい方への簡単なチェックリスト:
まずツール giftgenius.suggestGifts の現在の結果(ギフト配列)を取り、次を行います。
- 3 種類の UI を 1 つのコンポーネントで実装する:
- GiftGrid(カード);
- GiftList(テキストリスト);
- GiftCarousel(「前へ/次へ」ナビゲーション)。
- それぞれに 1~2 個のCTA ボタンを付ける:
- カードは「選ぶ」;
- リストは「詳しく見る」;
- カルーセルも「選ぶ」。さらにウィジェット下に「さらに候補を表示」のボタン。
- 状態(たとえばツールが返した総ギフト数)に応じて、使用するパターンを選ぶ:
- 候補が少ない(≤ 3)→ カードのグリッド;
- テキスト主体のアイデアが多い → リスト;
- ビジュアル中心のギフトが多い → カルーセル。
これにより UI の訓練になるだけでなく、コンテキストに応じたパターンの動的選択について考えるきっかけになります。 これはユーザーにも、ストアのレビュー担当にも好まれます。
総じて inline パターンは、チャットのタイムライン内に存在する素早く軽量な UI レイヤーであり、別アプリの代替を試みるものではありません。 カード、リスト、カルーセルで典型的な 80% のタスク(候補の提示、選択、対話の円滑な継続)をカバーできます。
次の講義では、inline では「もう厳しい」場合にどうするかを見ます。 fullscreen ウィザード、PiP モード、そして ChatGPT の中で本当に大きな画面が必要になるシナリオを扱います。
11. inline パターンでありがちな誤り
誤り #1: inline ウィジェットをミニサイト化する。
ときどき、1 枚のカードにタブ、アコーディオン、フォーム、テーブルなどを詰め込もうとする実装があります。結果として重い UI になり、 チャットのリズムを崩し、モバイルでは使いづらくなります。ガイドラインは明確です。inline カード内に深いナビゲーションや複雑なビューを入れない。 複雑なシナリオは fullscreen に切り出します。
誤り #2: CTA ボタンが多すぎる。
「カードに『詳しく』『購入』『お気に入り』『共有』『通報』『カードを生成』を全部入れよう」―― こうなるとユーザーもモデルも迷い、必要なボタンが押される確率は下がります。ルールを思い出しましょう。主要な CTA は 1 つ、最大でも副次を加えて 2 つまで。 残りは GPT のフォローアップや後続ステップに回しましょう。
誤り #3: 理由なくリスト、カード、カルーセルを一つの回答内で混在させる。
同じ内容を、できるからという理由だけで、あるときはリスト、あるときはカード、またあるときはカルーセルで表示すると、一貫性が失われます。 特定の出力タイプに対して 1 つのパターンを選び(例:画像のないアイデア → リスト、画像のあるギフト → カルーセル)、それに従いましょう。
誤り #4: カードをテキストで過密にする。
説明が 3 段落、価格が 3 つ、そして「なぜ良いか」が 2 ブロック――そんなカードはテキストの壁になります。 ユーザーは「スキャン」しなくなり、読み飛ばします。カードには最重要情報だけを残しましょう:タイトル、主要なパラメータ 1 つ、短い理由、そして CTA。 その他は隣接する GPT のテキスト回答で説明できます。
誤り #5: UI のみを頼り、フォローアップの対話を無視する。
「全部ボタンでやればよく、ユーザーは話さなくていい」という発想を見かけることがあります。これは ChatGPT の理念に反します。 インライン・ウィジェットは対話を補完するもので、置き換えるものではありません。ウィジェット下でモデルがどんなフォローアップを提案できるか(フィルター変更、候補を増やす、次のステップへ進む等)を考えておきましょう。
誤り #6: 要素数の制約を無視する。
1 つの inline ウィジェットに 25 枚のカルーセルカードや 50 項目のリスト――これはユーザーが丸ごとスクロールで飛ばしてしまう道です。 ドキュメントは、カルーセルは 3~8、リストは 5~10 を推奨しています。データが多い場合は「さらに表示」「すべてをテキストで表示」といった CTA を足しましょう。
誤り #7: inline で済まないのに fullscreen に移行しない。
ステップが 4 つ、入力項目が 10 個のフォーム、大きなテーブル――それでも「全部を inline でやる」誘惑はあります。 結果としてモンスター UI になったり、カード内に入れ子スクロールや疑似ステッパーを発明しがちです。 ステップ数や項目が増えてきたら、それは fullscreen ウィザードへ切り替えるサインです。inline はクイックなプレビューやアクションの要約に留めましょう。
GO TO FULL VERSION