1. 什麼是 inline 模式,為什麼它是「預設」
OpenAI 的官方指引強調:inline 顯示是 ChatGPT Apps 的主要模式。 Inline 小工具會直接在聊天訊息流中、模型回覆上方渲染,包含一個小型 UI 區塊(卡片、清單、輪播),以及其下方由 GPT 補充的後續訊息。
理念很簡單:不把使用者帶到另一個龐大的介面,而是在對話脈絡中直接給出一個精簡的視覺「閃現」: 模型解釋剛剛做了什麼、接下來能怎麼做,而小工具則乾淨地呈現結構與可用動作。
Inline 小工具的特性:
- 內容輕量,不需要很多步驟;
- 不把使用者帶進帶分頁或內部滾動條的複雜導覽;
- 一次解決一到兩個小任務:展示選項、讓使用者選擇、確認動作、顯示狀態。
Fullscreen 模式(下一講會介紹)用於大型精靈與複雜內容。此刻更重要的是: 預設先想 inline,只有在 inline 明顯不夠用時,才有意識地切換到 fullscreen。
本講的目標,是熟練運用三大 inline 樣式並在其上正確使用 CTA:
- 卡片
- 清單
- 輪播
以及替它們合理配置 CTA 按鈕(Call to Action)。
2. 什麼時候 inline 比 fullscreen 更好
簡化來說,inline 是「小幫手」,fullscreen 是「在 ChatGPT 內的一個獨立應用」。
Inline 特別適合以下情況:
- 需要展示多個選項並讓使用者選 1–2 個;
- 結果可以以緊湊的結構表達:禮物卡片、訂單摘要、迷你表格;
- 使用者要做的是短動作:例如「選擇」、「顯示更多資訊」、「調整篩選」;
- 對話仍是主角:GPT 負責說明、互動、補充,而小工具只是提供便利的表單或視覺化。
就 GiftGenius 而言,inline 的典型情境:
- 為特定對象展示 3–5 個最佳禮物;
- 提供快速篩選:「只看數位類禮物」;
- 確認選擇:「這裡是訂單摘要,都沒問題嗎?」。
Fullscreen 之後才用在三步驟的複雜結帳精靈。現在我們留在輕量區: 一次工具呼叫 → 一個 inline 小工具。
更直觀的對照表:
| 樣式 | 最適用情境 | GiftGenius 範例 |
|---|---|---|
| 卡片 | 1–3 個實體,含關鍵參數與 CTA | 3 個頂級禮物 |
| 清單 | 5–10 個文字項目,重點是可讀性 | 不含圖片的點子清單 |
| 輪播 | 3–8 個相似的可視化選項,需要橫向瀏覽 | 較長的禮物清單 |
理解上述差異後,接下來看具體實作:這些樣式在 UI 與程式碼中怎麼落地。 我們會用一致的節奏介紹每一種樣式:先談 UX、再給一個適用於 GiftGenius 的簡單 React 元件、最後說如何嵌入 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 清單,也能放在輪播內。
卡片如何融入整體小工具
假設我們有一個在透過 MCP 呼叫 giftgenius.suggestGifts 後取得的陣列 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 美元內」)。
簡易清單元件
做一個精簡清單,在右側放一個 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 檢視。
清單特別適合搭配 GPT 的後續建議(follow‑up)。 小工具顯示「候選」清單,在其下方 GPT 會寫類似:
「我可以縮小到 30 美元以內的禮物,或只顯示數位類。要選哪個?」並提供兩到三個 follow‑up 按鈕。
在後續章節我們還會討論,在不同情境下如何把 inline 小工具與 follow‑up 訊息組合得更恰當。
5. 輪播:當選項很多但彼此相似
輪播是一組橫向排列、可滑動或按鈕切換的卡片。 指引建議在展示少量相似元素(通常 3–8 個)時使用輪播,每個元素含圖片、標題與少量中繼資料。
核心想法是:使用者能快速掃描一組選項,而不會被無止境的縱向清單淹沒。
在 GiftGenius,輪播適合於:
- 有 10–15 個合適禮物,但 inline 小工具只需展示「精選 8 個」;
- 每個禮物都有不錯的視覺(圖片、樣式);
- 希望使用者在不往下捲太多的情況下來回瀏覽。
輪播的 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 文案應是明確的動詞:「顯示詳細資訊」、「加入清單」、「前往結帳」,而不是模糊的「OK」或「動作」。
按鈕越少,模型與使用者越不費力。別忘了,小工具的上方與下方還有文字回覆,以及 GPT 的 follow‑up 建議。
把 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 應該觸發啟動 ACP 結帳的 tool 呼叫(我們會在商務與支付模組討論)。
另一個實用建議—不要在 CTA 中重複 ChatGPT 的功能。別做「詢問 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 小工具 participant B as MCP/後端 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 是「對話主持人」,說明發生了什麼、提出下一步。
要讓這個流程好用:
- 不要讓小工具充斥太多動作;
- 卡片內容保持精簡;
- 思考每次 inline 顯示之後,哪些 follow‑up 選項最有幫助。
9. inline 樣式的視覺細節
我們會在本模組後續課程深入討論視覺設計,但有幾點對 inline 樣式特別重要,先提一下。
首先,確保你的卡片與清單不會看起來像是外來網站嵌在 ChatGPT 中。 配色與留白要乾淨,避免刺眼漸層與 Comic Sans。 Inline 小工具是 ChatGPT 整體 UI 的一部分,而不是 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,打亂聊天節奏,在行動裝置上也難用。 指引明確說了:卡片內避免深層導覽與複雜視圖;複雜情境請移到 fullscreen。
錯誤 №2:CTA 太多。
「不如在卡片上放『更多資訊』、『購買』、『加入收藏』、『分享』、『檢舉』、『產生賀卡』吧。」 最後使用者與模型都迷失,按到關鍵按鈕的機率反而下降。記住原則:一個主要 CTA,最多一個次要。 其餘情境交給 GPT 的 follow‑up 訊息或後續步驟。
錯誤 №3:在同一個回覆裡混用清單、卡片、輪播,且沒有理由。
相同內容一會兒用清單、一會兒用卡片、又換輪播,只因為「我們做得到」,會破壞一致性。 更好的做法是:對同一類輸出固定一種樣式(例如無圖點子用清單、有圖禮物用輪播),並保持一致。
錯誤 №4:卡片塞太多文字。
卡片裡放三段描述、三個價格、兩段「為什麼很棒」,就變成文字牆。 使用者不再「掃描」,只會直接略過。卡片請只留最重要的:標題、一個關鍵參數、簡短理由以及 CTA。 其他內容可以由旁邊的 GPT 文字回覆說明。
錯誤 №5:只靠 UI,不理會 follow‑up 對話。
有時會看到「全部用按鈕,使用者不必說話」的做法。這與 ChatGPT 的核心理念相違。 Inline 小工具應該輔助對話,而不是取代對話。別忘了規劃模型可在小工具下提供哪些 follow‑up 選項: 調整篩選、要求更多選項、進到下一步等。
錯誤 №6:忽視項目數量的限制。
在一個 inline 小工具中放 25 張卡片的輪播,或 50 項的清單,是逼使用者整段滑過的捷徑。 文件建議輪播 3–8 個、清單 5–10 個。若資料更多,加入「顯示更多」或「改以文字顯示全部」這類 CTA 很有幫助。
錯誤 №7:在該用 fullscreen 時還硬用 inline。
有時會貪心地「全部都用 inline」,即便已經有 4 個步驟、含十幾個欄位的表單、與大型表格。 最後不是做出怪物,就是在卡片內發明巢狀卷軸與假階段條。 一旦感覺步驟與欄位越堆越多,就是該考慮 fullscreen 精靈的訊號,而把 inline 留給快速預覽與動作摘要。
GO TO FULL VERSION