CodeGym /课程 /ChatGPT Apps /Inline‑模式:卡片、列表、轮播和 CTA

Inline‑模式:卡片、列表、轮播和 CTA

ChatGPT Apps
第 8 级 , 课程 1
可用

1. 什么是 inline 模式,以及为何它是“默认”

OpenAI 的官方指南强调:inline 显示是 ChatGPT Apps 的主要模式。 Inline 小部件直接渲染在聊天消息流中、模型回答的上方,包含一个小型 UI 区块(卡片、列表或轮播)以及其下方来自 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 视角是什么, 然后给出 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 美元以内,或者只显示数字类礼物。我们选哪个?”并给出两三个后续交互按钮。

在单独的章节中,我们还会讨论如何在不同场景下,把 inline 小部件与后续消息组合得更好。

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 文案应是明确的动词:“显示详情”“加入清单”“前往支付”,不要用抽象的“好的”或“操作”。

按钮越少,对模型和用户都越简单。别忘了,在小部件的上方和下方还有文本回答,以及 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 结账(我们会在“商业与支付”模块详细说明)。

另一个好实践——不要在 CTA 中重复 ChatGPT 的能力。别做“询问 ChatGPT”的按钮,用户已经有输入框与语音。 指南明确建议避免在卡片内重复输入方式。

7. Inline + 后续消息:双人配合

Inline 小部件从不孤立存在。一个回答通常是这样:

  1. 模型决定使用你的 App 并调用工具;
  2. 你的 MCP 返回数据;
  3. ChatGPT 渲染包含这些数据的 inline 小部件;
  4. 其下模型补充一段简短的后续文本,并给出继续的现成选项。

在 GiftGenius 中可能是这样:

  • inline 小部件:三张礼物卡片,按钮为 CTA “选择”;
  • 下方文本:
    “这是三种送同事的想法:台灯、演讲课程、咖啡店礼品卡。我可以: — 只显示 30 美元以内的选项; — 再挑几件风格相近的; — 直接帮你购买其中一个。”

模型在后续消息中可以引用你的小部件 CTA(“点击喜欢的选项下方的‘选择’”),也可以给出文本命令, 再次触发工具调用并重绘 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 展示后,哪些后续选项对用户最有用。

9. 关于 inline 模式的视觉细节

我们会在后续课程中深入聊视觉设计,这里先提几条对 inline 模式非常关键的点。

首先,确保你的卡片与列表看起来不像嵌进 ChatGPT 的外站。 配色与留白要克制,别用夸张的渐变或 Comic Sans 字体。 Inline 小部件是 ChatGPT 整体 UI 的一部分,不是 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 模式,以及你的 App 确实需要在 ChatGPT 内拥有一个大屏的场景。

11. 使用 inline 模式时的常见错误

错误 1:把 inline 小部件做成迷你网站。
有时开发者会试图把标签页、手风琴、表单、表格和一堆元素都塞进一张卡片。结果是沉重的 UI, 不仅打乱聊天的节奏,还在移动设备上很难用。指南明确指出:不要在 inline 卡片中做深层导航和复杂视图; 复杂场景请放到 fullscreen

错误 2:CTA 按钮过多。
“我们在卡片上放‘查看详情’、‘购买’、‘收藏’、‘分享’、‘投诉’、‘生成贺卡’吧。” 最后用户也迷糊,模型也发懵,点中“正确按钮”的概率下降。记住规则:一个主 CTA,最多再加一个次级。 其他场景更适合放到 GPT 的后续消息或后续步骤里。

错误 3:在一个回答里无缘由地把列表、卡片、轮播混着用。
如果相同的内容时而列表、时而卡片、时而轮播(“因为我们能这么做”),用户会失去一致性体验。 更好的做法是为特定输出选定一个模式(例如无图创意——列表,有图礼物——轮播)并坚持使用。

错误 4:卡片信息过载。
一张卡里有三段描述、三种价格、两个“为何很棒”的区块,会变成文字墙。 用户就不再“扫描”,而是直接滑过。尽量只保留最重要的信息:标题、一个关键参数、 一条简短理由和 CTA。其他内容可以放在旁边 GPT 的文本回答里解释。

错误 5:只依赖 UI,忽略后续对话。
有时会见到“全部用按钮,用户不需要对话”的做法。这与 ChatGPT 的理念相悖。 Inline 小部件应当补充对话,而不是替代它。别忘了设计模型在小部件下方可以提供的后续选项: 修改筛选、请求更多选项、进入下一步。

错误 6:忽视元素数量的限制。
在一个 inline 小部件里做 25 张卡片的轮播,或 50 项的列表,基本会被用户整体滑过。 文档建议轮播 3–8 个、列表 5–10 个。如果数据更多,建议加上 CTA,如“显示更多”或“全部以文本显示”。

错误 7:在该用 fullscreen 时仍勉强用 inline
有时会贪心地“都用 inline”,即使已经有 4 个步骤、十几个表单字段和大表格。 最后要么得到一个怪物,要么在卡片里造嵌套滚动与伪步骤器。 一旦感觉步骤和字段变多,就是考虑切到 fullscreen 向导的信号,把 inline 留给快速预览与行动摘要。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION