在 Dialogflow 中探索
点击继续,将我们的响应示例导入 Dialogflow 中。然后,按照以下步骤部署和测试该示例:
- 输入代理名称并为示例创建新的 Dialogflow 代理。
- 代理导入完成后,点击转至代理 (Go to agent)。
- 在主导航菜单中,前往 Fulfillment。
- 启用内嵌编辑器,然后点击部署。编辑器包含示例代码。
- 在主导航菜单中,点击 Integrations(集成),然后点击 Google Assistant。
- 在显示的模态窗口中,启用 Auto-preview changes,然后点击 Test 打开 Actions 模拟器。
- 在模拟器中,输入
Talk to my test app
以测试示例!
如果您想显示视觉元素以增强用户与 Action 的互动,请使用丰富响应。这些视觉元素可以提供有关如何继续对话的提示。
丰富响应可能出现在仅屏幕或音频和屏幕体验中。它们可以包含以下组件:
您还可以查看我们的对话设计指南,了解如何在 Action 中融入这些视觉元素。
属性
富响应具有以下要求和您可以配置的可选属性:
- 在具有
actions.capability.SCREEN_OUTPUT
功能的 Surface 上受支持。 - 富响应中的第一项必须是简单响应。
- 最多两个简单的响应。
- 最多一张基本卡或
StructuredResponse
。 - 最多 8 个建议条状标签。
FinalResponse
中不允许使用建议内容信息条- 目前不支持从智能显示屏链接到网页。
以下几个部分介绍如何构建各种类型的富响应。
基本卡片
基本卡片会显示可能包含以下信息:
- 图片
- 标题
- 副标题
- 文本正文
- “关联”按钮
- 边区
基本卡片主要用于显示目的。它们旨在简洁明了地向用户显示关键(或摘要)信息,并允许用户在您选择的情况下(使用网页链接)了解详情。
在大多数情况下,您应在卡片下方添加建议内容信息卡,以便继续或转换对话。
应不惜一切代价避免在聊天气泡中重复卡片中显示的信息。
属性
基本卡片响应类型具有以下要求以及您可以配置的可选属性:
- 在具有
actions.capability.SCREEN_OUTPUT
功能的 Surface 上受支持。 - 设置了格式的文本(如果没有图片,则必须提供)
- 默认使用纯文本。
- 不得包含链接。
- 图片不得超过 10 行,不带图片时则不得超过 15 行。这约为 500(含图片)或 750(不含图片)字符。相较于大屏手机,小屏手机也会提前截断文本。如果文本包含的行数太多,它会在最后一个单词中断处以省略号截断。
- 仅支持有限的 Markdown 子集:
- 换行,一个双空格后跟 \n
**bold**
*italics*
- 图片(如果没有格式化文本,则必须提供图片)
- 所有图片的高度强制为 192dp。
- 如果图片的宽高比与屏幕不同,则会居中显示图片的垂直或水平边缘上的灰条。
- 图片来源是一个网址。
- 允许使用动态 GIF。
可选
- 标题
- 纯文本。
- 固定的字体和大小。
- 最多 1 行;多出的字符会被截断。
- 如果未指定标题,卡片高度会收起。
- 副标题
- 纯文本。
- 已固定字体和字号。
- 最多 1 行;多出的字符会被截断。
- 如果未指定副标题,卡片高度会收起。
- 链接按钮
- 链接标题为必填项
- 最多一个链接
- 允许提供指向开发者网域外网站的链接。
- 链接文字不得误导用户。这将在审批流程中进行检查。
- 如果没有链接,基本卡片就没有互动功能。用户点按链接后,会转到该链接,同时卡片正文仍处于非活动状态。
- 边框
- 您可以调整卡片和图片容器之间的边框,以自定义基本卡片的呈现方式。
- 通过设置 JSON 字符串属性
imageDisplayOptions
进行配置
示例代码
Node.js
app.intent('Basic Card', (conv) => { if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask(`Here's an example of a basic card.`); conv.ask(new BasicCard({ text: `This is a basic card. Text in a basic card can include "quotes" and most other unicode characters including emojis. Basic cards also support some markdown formatting like *emphasis* or _italics_, **strong** or __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other things like line \nbreaks`, // Note the two spaces before '\n' required for // a line break to be rendered in the card. subtitle: 'This is a subtitle', title: 'Title: this is a title', buttons: new Button({ title: 'This is a button', url: 'https://assistant.google.com/', }), image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), display: 'CROPPED', })); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("Basic Card") public ActionResponse basicCard(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } // Prepare formatted text for card String text = "This is a basic card. Text in a basic card can include \"quotes\" and\n" + " most other unicode characters including emoji \uD83D\uDCF1. Basic cards also support\n" + " some markdown formatting like *emphasis* or _italics_, **strong** or\n" + " __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other\n" + " things like line \\nbreaks"; // Note the two spaces before '\n' required for // a line break to be rendered in the card. responseBuilder .add("Here's an example of a basic card.") .add( new BasicCard() .setTitle("Title: this is a title") .setSubtitle("This is a subtitle") .setFormattedText(text) .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setImageDisplayOptions("CROPPED") .setButtons( new ArrayList<Button>( Arrays.asList( new Button() .setTitle("This is a Button") .setOpenUrlAction( new OpenUrlAction().setUrl("https://assistant.google.com")))))) .add("Which response would you like to see next?"); return responseBuilder.build(); }
Node.js
if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask(`Here's an example of a basic card.`); conv.ask(new BasicCard({ text: `This is a basic card. Text in a basic card can include "quotes" and most other unicode characters including emojis. Basic cards also support some markdown formatting like *emphasis* or _italics_, **strong** or __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other things like line \nbreaks`, // Note the two spaces before '\n' required for // a line break to be rendered in the card. subtitle: 'This is a subtitle', title: 'Title: this is a title', buttons: new Button({ title: 'This is a button', url: 'https://assistant.google.com/', }), image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), display: 'CROPPED', })); conv.ask('Which response would you like to see next?');
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } // Prepare formatted text for card String text = "This is a basic card. Text in a basic card can include \"quotes\" and\n" + " most other unicode characters including emoji \uD83D\uDCF1. Basic cards also support\n" + " some markdown formatting like *emphasis* or _italics_, **strong** or\n" + " __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other\n" + " things like line \\nbreaks"; // Note the two spaces before '\n' required for // a line break to be rendered in the card. responseBuilder .add("Here's an example of a basic card.") .add( new BasicCard() .setTitle("Title: this is a title") .setSubtitle("This is a subtitle") .setFormattedText(text) .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setImageDisplayOptions("CROPPED") .setButtons( new ArrayList<Button>( Arrays.asList( new Button() .setTitle("This is a Button") .setOpenUrlAction( new OpenUrlAction().setUrl("https://assistant.google.com")))))) .add("Which response would you like to see next?"); return responseBuilder.build();
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "Here's an example of a basic card." } }, { "basicCard": { "title": "Title: this is a title", "subtitle": "This is a subtitle", "formattedText": "This is a basic card. Text in a basic card can include \"quotes\" and\n most other unicode characters including emojis. Basic cards also support\n some markdown formatting like *emphasis* or _italics_, **strong** or\n __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other\n things like line \nbreaks", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" }, "buttons": [ { "title": "This is a button", "openUrlAction": { "url": "https://assistant.google.com/" } } ], "imageDisplayOptions": "CROPPED" } }, { "simpleResponse": { "textToSpeech": "Which response would you like to see next?" } } ] } } } }
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TEXT" } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "Here's an example of a basic card." } }, { "basicCard": { "title": "Title: this is a title", "subtitle": "This is a subtitle", "formattedText": "This is a basic card. Text in a basic card can include \"quotes\" and\n most other unicode characters including emojis. Basic cards also support\n some markdown formatting like *emphasis* or _italics_, **strong** or\n __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other\n things like line \nbreaks", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" }, "buttons": [ { "title": "This is a button", "openUrlAction": { "url": "https://assistant.google.com/" } } ], "imageDisplayOptions": "CROPPED" } }, { "simpleResponse": { "textToSpeech": "Which response would you like to see next?" } } ] } } } ] }
浏览轮播界面
浏览轮播界面是一种丰富的响应,可让用户垂直滚动并选择集合中的功能块。浏览轮播界面专为网页内容而设计,具体方法是在网络浏览器(或 AMP 浏览器,如果所有图块均已启用 AMP 的情况下)中打开所选图块。浏览轮播界面也会一直保留在用户的 Google 助理 surface 中,以供日后浏览。
属性
浏览轮播界面响应类型具有以下要求以及您可以配置的可选属性:
- 在同时具有
actions.capability.SCREEN_OUTPUT
和actions.capability.WEB_BROWSER
功能的 Surface 上受支持。智能显示屏目前不支持此响应类型。 - 浏览轮播界面
- 最多 10 个图块。
- 至少两个板块。
- 轮播界面中的功能块必须全部链接到 Web 内容(建议使用 AMP 内容)。
- 为了能够将用户转到 AMP 查看器,AMP 内容图块上的
urlHintType
必须设置为“AMP_CONTENT”。
- 为了能够将用户转到 AMP 查看器,AMP 内容图块上的
- 浏览轮播界面图块
- 图块一致性(必需):
- 浏览轮播界面中的所有图块都必须具有相同的组件。例如,如果一个功能块具有图片字段,则轮播界面中的其他功能块也必须包含图片字段。
- 如果浏览轮播界面中的所有图块都链接到已启用 AMP 的内容,则会将用户转到具有额外功能的 AMP 浏览器。如果有任何功能块链接到非 AMP 内容,则所有功能块都会将用户引导至网络浏览器。
- 图片(可选)
- 强制图片尺寸为 128 dp 高 x 232 dp 宽。
- 如果图片宽高比与图片边界框不匹配,则该图片的两侧会居中显示。在智能手机上,图片居中显示在一个带圆角的方形区域内。
- 如果图片链接已损坏,则使用占位符图片。
- 图片中需要有替代文本。
- 标题(必填)
- 格式选项与基本文本卡片相同。
- 标题必须是唯一的(以便支持语音选择)。
- 最多可输入 2 行文字。
- 字体大小为 16 sp.
- 说明(可选)
- 格式选项与基本文本卡片相同。
- 最多可输入四行文字。
- 使用省略号 (...) 截断
- 字体大小为 14sp,灰色。
- 页脚(可选)
- 已固定字体和字号。
- 最多 1 行文字。
- 使用省略号 (...) 截断
- 锚定在底部,因此正文文本行较少的图块可能会在辅助文本上方有空白。
- 字体大小为 14sp,灰色。
- 图块一致性(必需):
- 互动
- 用户可以垂直滚动来查看项目。
- 点按卡片:点按商品会将用户转到浏览器,并显示关联的页面。
- 语音输入
- 麦克风行为
- 向用户发送浏览轮播界面时,麦克风不会重新打开。
- 用户仍然可以点按麦克风或调用 Google 助理(“Ok Google”)来重新打开麦克风。
- 麦克风行为
指导方针
默认情况下,发送浏览轮播界面后,麦克风会保持关闭状态。如果您随后想继续对话,我们强烈建议您在轮播界面下方添加建议条状标签。
切勿以建议内容信息卡的形式重复列表中显示的选项。此上下文中的条状标签用于透视对话(而不是用于选择选项)。
与列表一样,轮播卡片随附的聊天气泡是音频的子集 (TTS/SSML)。此处的音频 (TTS/SSML) 集成了轮播界面中的第一个功能块,我们还强烈建议不要读取轮播界面中的所有元素。最好提及第一件商品及其展示原因(例如最热门商品、最近购买的商品、最受关注的商品)。
示例代码
Node.js
app.intent('Browsing Carousel', (conv) => { if (!conv.screen || !conv.surface.capabilities.has('actions.capability.WEB_BROWSER')) { conv.ask('Sorry, try this on a phone or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask(`Here's an example of a browsing carousel.`); conv.ask(new BrowseCarousel({ items: [ new BrowseCarouselItem({ title: 'Title of item 1', url: 'https://example.com', description: 'Description of item 1', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), footer: 'Item 1 footer', }), new BrowseCarouselItem({ title: 'Title of item 2', url: 'https://example.com', description: 'Description of item 2', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), footer: 'Item 2 footer', }), ], })); });
Java
@ForIntent("Browsing Carousel") public ActionResponse browseCarousel(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue()) || !request.hasCapability(Capability.WEB_BROWSER.getValue())) { return responseBuilder .add("Sorry, try this on a phone or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("Here's an example of a browsing carousel.") .add( new CarouselBrowse() .setItems( new ArrayList<CarouselBrowseItem>( Arrays.asList( new CarouselBrowseItem() .setTitle("Title of item 1") .setDescription("Description of item 1") .setOpenUrlAction(new OpenUrlAction().setUrl("https://example.com")) .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setFooter("Item 1 footer"), new CarouselBrowseItem() .setTitle("Title of item 2") .setDescription("Description of item 2") .setOpenUrlAction(new OpenUrlAction().setUrl("https://example.com")) .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setFooter("Item 2 footer"))))); return responseBuilder.build(); }
Node.js
if (!conv.screen || !conv.surface.capabilities.has('actions.capability.WEB_BROWSER')) { conv.ask('Sorry, try this on a phone or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask(`Here's an example of a browsing carousel.`); conv.ask(new BrowseCarousel({ items: [ new BrowseCarouselItem({ title: 'Title of item 1', url: 'https://example.com', description: 'Description of item 1', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), footer: 'Item 1 footer', }), new BrowseCarouselItem({ title: 'Title of item 2', url: 'https://example.com', description: 'Description of item 2', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Image alternate text', }), footer: 'Item 2 footer', }), ], }));
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue()) || !request.hasCapability(Capability.WEB_BROWSER.getValue())) { return responseBuilder .add("Sorry, try this on a phone or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("Here's an example of a browsing carousel.") .add( new CarouselBrowse() .setItems( new ArrayList<CarouselBrowseItem>( Arrays.asList( new CarouselBrowseItem() .setTitle("Title of item 1") .setDescription("Description of item 1") .setOpenUrlAction(new OpenUrlAction().setUrl("https://example.com")) .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setFooter("Item 1 footer"), new CarouselBrowseItem() .setTitle("Title of item 2") .setDescription("Description of item 2") .setOpenUrlAction(new OpenUrlAction().setUrl("https://example.com")) .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Image alternate text")) .setFooter("Item 2 footer"))))); return responseBuilder.build();
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "Here's an example of a browsing carousel." } }, { "carouselBrowse": { "items": [ { "title": "Title of item 1", "openUrlAction": { "url": "https://example.com" }, "description": "Description of item 1", "footer": "Item 1 footer", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" } }, { "title": "Title of item 2", "openUrlAction": { "url": "https://example.com" }, "description": "Description of item 2", "footer": "Item 2 footer", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Image alternate text" } } ] } } ] } } } }
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "expectUserResponse": true, "expectedInputs": [ { "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "Here's an example of a browsing carousel." } }, { "carouselBrowse": { "items": [ { "description": "Description of item 1", "footer": "Item 1 footer", "image": { "accessibilityText": "Image alternate text", "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png" }, "openUrlAction": { "url": "https://example.com" }, "title": "Title of item 1" }, { "description": "Description of item 2", "footer": "Item 2 footer", "image": { "accessibilityText": "Image alternate text", "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png" }, "openUrlAction": { "url": "https://example.com" }, "title": "Title of item 2" } ] } } ] } }, "possibleIntents": [ { "intent": "actions.intent.TEXT" } ] } ] }
处理所选项
用户与浏览轮播项之间的互动无需后续执行方式,因为轮播界面会处理浏览器移交。请注意,当用户与浏览轮播项互动后,麦克风不会重新打开,因此您应该根据上述指南结束对话或在响应中添加建议内容信息卡。
建议内容信息条
使用建议内容信息卡来提示回复,以便继续或转变对话。 如果在对话期间有主要号召性用语,请考虑将其列为第一个建议内容信息条。
您应该尽可能将一条关键建议纳入聊天气泡中,但只有在回复或聊天对话自然而然的情况下才这么做。
属性
建议内容信息卡具有以下要求和您可以配置的可选属性:
- 在具有
actions.capability.SCREEN_OUTPUT
功能的 Surface 上受支持。 - 如需将建议内容信息条链接到 Web,Surface 还必须具有
actions.capability.WEB_BROWSER
功能。此功能目前不适用于智能显示屏。 - 最多 8 个条状标签。
- 文本长度不得超过 25 个字符。
仅支持纯文本。
示例代码
Node.js
app.intent('Suggestion Chips', (conv) => { if (!conv.screen) { conv.ask('Chips can be demonstrated on screen devices.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('These are suggestion chips.'); conv.ask(new Suggestions('Suggestion 1')); conv.ask(new Suggestions(['Suggestion 2', 'Suggestion 3'])); conv.ask(new LinkOutSuggestion({ name: 'Suggestion Link', url: 'https://assistant.google.com/', })); conv.ask('Which type of response would you like to see next?'); ; });
Java
@ForIntent("Suggestion Chips") public ActionResponse suggestionChips(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("These are suggestion chips.") .addSuggestions(new String[] {"Suggestion 1", "Suggestion 2", "Suggestion 3"}) .add( new LinkOutSuggestion() .setDestinationName("Suggestion Link") .setUrl("https://assistant.google.com/")) .add("Which type of response would you like to see next?"); return responseBuilder.build(); }
Node.js
if (!conv.screen) { conv.ask('Chips can be demonstrated on screen devices.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('These are suggestion chips.'); conv.ask(new Suggestions('Suggestion 1')); conv.ask(new Suggestions(['Suggestion 2', 'Suggestion 3'])); conv.ask(new LinkOutSuggestion({ name: 'Suggestion Link', url: 'https://assistant.google.com/', })); conv.ask('Which type of response would you like to see next?');
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("These are suggestion chips.") .addSuggestions(new String[] {"Suggestion 1", "Suggestion 2", "Suggestion 3"}) .add( new LinkOutSuggestion() .setDestinationName("Suggestion Link") .setUrl("https://assistant.google.com/")) .add("Which type of response would you like to see next?"); return responseBuilder.build();
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "These are suggestion chips." } }, { "simpleResponse": { "textToSpeech": "Which type of response would you like to see next?" } } ], "suggestions": [ { "title": "Suggestion 1" }, { "title": "Suggestion 2" }, { "title": "Suggestion 3" } ], "linkOutSuggestion": { "destinationName": "Suggestion Link", "url": "https://assistant.google.com/" } } } } }
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TEXT" } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "These are suggestion chips." } }, { "simpleResponse": { "textToSpeech": "Which type of response would you like to see next?" } } ], "suggestions": [ { "title": "Suggestion 1" }, { "title": "Suggestion 2" }, { "title": "Suggestion 3" } ], "linkOutSuggestion": { "destinationName": "Suggestion Link", "url": "https://assistant.google.com/" } } } } ] }
媒体响应
通过媒体响应,您的 Action 可以播放播放时长超过 SSML 的 240 秒限制的音频内容。媒体响应的主要组件是单轨卡片。该卡片允许用户执行以下操作:
- 重放最后 10 秒。
- 快进 30 秒。
- 查看媒体内容的总长度。
- 查看音频播放的进度指示器。
- 查看已播时长。
媒体响应支持以下语音交互音频控件:
- “Ok Google,播放。”
- “Ok Google,暂停。”
- “Ok Google,停止。”
- “Ok Google,重新开始。”
用户还可以通过说出“Hey Google,调高音量。”或“Hey Google,把音量设为 50%”这类指令来控制音量。您的 Action 中的 intent 处理类似的训练短语时,会优先执行。除非您的 Action 有特定原因,否则让 Google 助理处理这些用户请求。
属性
媒体响应具有以下要求和您可以配置的可选属性:
- 在具有
actions.capability.MEDIA_RESPONSE_AUDIO
功能的 Surface 上受支持。 - 要播放的音频必须是格式正确的
.mp3
文件。不支持直播。 - 必须将要播放的媒体文件指定为 HTTPS 网址。
- 图片(可选)
- 您可以视需要添加图标或图片。
- 图标
- 您的图标将以无边框缩略图的形式显示在媒体播放器卡片的右侧。
- 尺寸应为 36 x 36 dp。对于较大的图片,系统会将其调整为合适的尺寸。
- 图片
- 图片容器的高度为 192dp。
- 您的图片会显示在媒体播放器卡片的顶部,并占据卡片的整个宽度。大多数图片的顶部或两侧都会显示条形。
- 允许使用动态 GIF。
- 您必须将图片来源指定为网址。
- 所有图片都必须提供替代文本。
在 surface 上的行为
Android 手机和 Google Home 支持媒体响应。媒体响应的行为取决于用户与 Action 互动的表面。
在 Android 手机上,满足以下任一条件时,用户都能看到媒体响应:
- Google 助理位于前台,手机屏幕处于开启状态。
- 用户在音频播放期间离开 Google 助理,并在播放完成后 10 分钟内返回到 Google 助理。返回 Google 助理后,用户会看到媒体卡片和建议内容信息卡。
- Google 助理可让用户在对话 Action 中控制设备音量,只需说出“调高音量”或“把音量调到 50%”之类的语音指令即可。如果您有用于处理类似训练短语的意图,则您的意图优先。我们建议您让 Google 助理处理这些用户请求,除非您的 Action 有特定原因。
手机处于锁定状态时,可以使用媒体控件。在 Android 设备上,这些控件也会显示在通知区域中。
示例代码
以下代码示例展示了如何更新富媒体响应以包含媒体内容。
Node.js
app.intent('Media Response', (conv) => { if (!conv.surface.capabilities .has('actions.capability.MEDIA_RESPONSE_AUDIO')) { conv.ask('Sorry, this device does not support audio playback.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('This is a media response example.'); conv.ask(new MediaObject({ name: 'Jazz in Paris', url: 'https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3', description: 'A funky Jazz tune', icon: new Image({ url: 'https://storage.googleapis.com/automotive-media/album_art.jpg', alt: 'Album cover of an ocean view', }), })); conv.ask(new Suggestions(['Basic Card', 'List', 'Carousel', 'Browsing Carousel'])); });
Java
@ForIntent("Media Response") public ActionResponse mediaResponse(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.MEDIA_RESPONSE_AUDIO.getValue())) { return responseBuilder .add("Sorry, this device does not support audio playback.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a media response example.") .add( new MediaResponse() .setMediaObjects( new ArrayList<MediaObject>( Arrays.asList( new MediaObject() .setName("Jazz in Paris") .setDescription("A funky Jazz tune") .setContentUrl( "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3") .setIcon( new Image() .setUrl( "https://storage.googleapis.com/automotive-media/album_art.jpg") .setAccessibilityText("Album cover of an ocean view"))))) .setMediaType("AUDIO")) .addSuggestions(new String[] {"Basic Card", "List", "Carousel", "Browsing Carousel"}); return responseBuilder.build(); }
Node.js
if (!conv.surface.capabilities .has('actions.capability.MEDIA_RESPONSE_AUDIO')) { conv.ask('Sorry, this device does not support audio playback.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('This is a media response example.'); conv.ask(new MediaObject({ name: 'Jazz in Paris', url: 'https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3', description: 'A funky Jazz tune', icon: new Image({ url: 'https://storage.googleapis.com/automotive-media/album_art.jpg', alt: 'Album cover of an ocean view', }), })); conv.ask(new Suggestions(['Basic Card', 'List', 'Carousel', 'Browsing Carousel']));
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.MEDIA_RESPONSE_AUDIO.getValue())) { return responseBuilder .add("Sorry, this device does not support audio playback.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a media response example.") .add( new MediaResponse() .setMediaObjects( new ArrayList<MediaObject>( Arrays.asList( new MediaObject() .setName("Jazz in Paris") .setDescription("A funky Jazz tune") .setContentUrl( "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3") .setIcon( new Image() .setUrl( "https://storage.googleapis.com/automotive-media/album_art.jpg") .setAccessibilityText("Album cover of an ocean view"))))) .setMediaType("AUDIO")) .addSuggestions(new String[] {"Basic Card", "List", "Carousel", "Browsing Carousel"}); return responseBuilder.build();
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "This is a media response example." } }, { "mediaResponse": { "mediaType": "AUDIO", "mediaObjects": [ { "contentUrl": "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3", "description": "A funky Jazz tune", "icon": { "url": "https://storage.googleapis.com/automotive-media/album_art.jpg", "accessibilityText": "Album cover of an ocean view" }, "name": "Jazz in Paris" } ] } } ], "suggestions": [ { "title": "Basic Card" }, { "title": "List" }, { "title": "Carousel" }, { "title": "Browsing Carousel" } ] } } } }
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TEXT" } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "This is a media response example." } }, { "mediaResponse": { "mediaType": "AUDIO", "mediaObjects": [ { "contentUrl": "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3", "description": "A funky Jazz tune", "icon": { "url": "https://storage.googleapis.com/automotive-media/album_art.jpg", "accessibilityText": "Album cover of an ocean view" }, "name": "Jazz in Paris" } ] } } ], "suggestions": [ { "title": "Basic Card" }, { "title": "List" }, { "title": "Carousel" }, { "title": "Browsing Carousel" } ] } } } ] }
指导方针
您的响应必须包含一个 mediaType
为 AUDIO
且在富响应的 item 数组中包含 mediaObject
的 mediaResponse
。媒体响应支持单个媒体对象。媒体对象必须包含音频文件的内容网址。媒体对象可以有选择性地包含名称、辅助文本(说明)以及图标或图片网址。
在手机和 Google Home 上,当您的 Action 完成音频播放时,Google 助理会检查媒体响应是否为 FinalResponse
。否则,它会向您的执行方式发送回调,从而允许您响应用户。
如果响应不是 FinalResponse
,则您的 Action 必须包含建议条状标签。
处理播放完成后的回调
您的 Action 应处理 actions.intent.MEDIA_STATUS
intent,以提示用户进行跟进(例如,播放另一首歌曲)。媒体播放完成后,您的 Action 会收到此回调。在该回调中,MEDIA_STATUS
参数包含有关当前媒体的状态信息。状态值为 FINISHED
或 STATUS_UNSPECIFIED
。
使用 Dialogflow
如果要在 Dialogflow 中执行对话分支,则需要为 intent 设置 actions_capability_media_response_audio
的输入上下文,以确保其仅在支持媒体响应的 Surface 上触发。
构建执行方式
以下代码段展示了如何为 Action 编写执行方式代码。如果您使用的是 Dialogflow,请将 actions.intent.MEDIA_STATUS
替换为接收 actions_intent_MEDIA_STATUS
事件的 intent 中指定的操作名称(例如“media.status.update”)。
Node.js
app.intent('Media Status', (conv) => { const mediaStatus = conv.arguments.get('MEDIA_STATUS'); let response = 'Unknown media status received.'; if (mediaStatus && mediaStatus.status === 'FINISHED') { response = 'Hope you enjoyed the tune!'; } conv.ask(response); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("Media Status") public ActionResponse mediaStatus(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); String mediaStatus = request.getMediaStatus(); String response = "Unknown media status received."; if (mediaStatus != null && mediaStatus.equals("FINISHED")) { response = "Hope you enjoyed the tune!"; } responseBuilder.add(response); responseBuilder.add("Which response would you like to see next?"); return responseBuilder.build(); }
Node.js
app.intent('actions.intent.MEDIA_STATUS', (conv) => { const mediaStatus = conv.arguments.get('MEDIA_STATUS'); let response = 'Unknown media status received.'; if (mediaStatus && mediaStatus.status === 'FINISHED') { response = 'Hope you enjoyed the tune!'; } conv.ask(response); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("actions.intent.MEDIA_STATUS") public ActionResponse mediaStatus(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); String mediaStatus = request.getMediaStatus(); String response = "Unknown media status received."; if (mediaStatus != null && mediaStatus.equals("FINISHED")) { response = "Hope you enjoyed the tune!"; } responseBuilder.add(response); responseBuilder.add("Which response would you like to see next?"); return responseBuilder.build(); }
JSON
请注意,以下 JSON 描述了 webhook 请求。
{ "responseId": "151b68df-98de-41fb-94b5-caeace90a7e9-21947381", "queryResult": { "queryText": "actions_intent_MEDIA_STATUS", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "Webhook failed for intent: Media Status", "fulfillmentMessages": [ { "text": { "text": [ "Webhook failed for intent: Media Status" ] } } ], "outputContexts": [ { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_media_response_audio" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_account_linking" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_web_browser" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_screen_output" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_capability_audio_output" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/google_assistant_input_type_voice" }, { "name": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA/contexts/actions_intent_media_status", "parameters": { "MEDIA_STATUS": { "@type": "type.googleapis.com/google.actions.v2.MediaStatus", "status": "FINISHED" } } } ], "intent": { "name": "projects/df-responses-kohler/agent/intents/068b27d3-c148-4044-bfab-dfa37eebd90d", "displayName": "Media Status" }, "intentDetectionConfidence": 1, "languageCode": "en" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "user": { "locale": "en-US", "lastSeen": "2019-08-04T23:57:15Z", "userVerificationStatus": "VERIFIED" }, "conversation": { "conversationId": "ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA", "type": "ACTIVE", "conversationToken": "[]" }, "inputs": [ { "intent": "actions.intent.MEDIA_STATUS", "rawInputs": [ { "inputType": "VOICE" } ], "arguments": [ { "name": "MEDIA_STATUS", "extension": { "@type": "type.googleapis.com/google.actions.v2.MediaStatus", "status": "FINISHED" } } ] } ], "surface": { "capabilities": [ { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.ACCOUNT_LINKING" }, { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" } ] }, "isInSandbox": true, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.SCREEN_OUTPUT" } ] } ], "requestType": "SIMULATOR" } }, "session": "projects/df-responses-kohler/agent/sessions/ABwppHHsebncupHK11oKhsCTgyH96GRNYH-xpeeMTqb-cvOxbd67QenbRlZM4bGAIB8_KXdTfI7-7lYVKN1ovAhCaA" }
JSON
请注意,以下 JSON 描述了 webhook 请求。
{ "user": { "locale": "en-US", "lastSeen": "2019-08-06T07:38:40Z", "userVerificationStatus": "VERIFIED" }, "conversation": { "conversationId": "ABwppHGcqunXh1M6IE0lu2sVqXdpJfdpC5FWMkMSXQskK1nzb4IkSUSRqQzoEr0Ly0z_G3mwyZlk5rFtd1w", "type": "NEW" }, "inputs": [ { "intent": "actions.intent.MEDIA_STATUS", "rawInputs": [ { "inputType": "VOICE" } ], "arguments": [ { "name": "MEDIA_STATUS", "extension": { "@type": "type.googleapis.com/google.actions.v2.MediaStatus", "status": "FINISHED" } } ] } ], "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.ACCOUNT_LINKING" } ] }, "isInSandbox": true, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.WEB_BROWSER" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.SCREEN_OUTPUT" } ] } ], "requestType": "SIMULATOR" }
表格卡片
利用表格卡片,您可以在响应中显示表格数据(例如,体育积分榜、选举结果和航班)。您可以定义 Google 助理需要在表格卡片中显示的列和行(每个列和行最多 3 个)。您还可以定义其他列和行及其优先级。
表与垂直列表的不同,因为表显示静态数据且无法像列表元素一样交互。
属性
表格卡片具有以下要求和您可以配置的可选属性:
- 在具有
actions.capability.SCREEN_OUTPUT
功能的 Surface 上受支持。
以下部分总结了如何自定义表格卡片中的元素。
名称 | 可选 | 可自定义 | 自定义备注 |
---|---|---|---|
title |
是 | 是 | 表格的总体标题。如果设置了副标题,则必须设置。您可以自定义字体系列和颜色。 |
subtitle |
是 | 否 | 表格的副标题。 |
image |
是 | 是 | 与表关联的图片。 |
Row |
否 | 是 |
表的行数据。由 前 3 行一定会显示,但其他行可能不会在某些 surface 中显示。 请使用模拟器进行测试,以查看针对给定 surface 显示了哪些行。在支持 |
ColumnProperties |
是 | 是 | 列的标题和对齐方式。由 header 属性(表示列的标题文本)和 horizontal_alignment 属性(类型为 HorizontalAlignment )组成。 |
Cell |
否 | 是 | 描述行中的一个单元格。每个单元格包含一个表示文本值的字符串。您可以自定义单元格中的文本。 |
Button |
是 | 是 | 一种按钮对象,通常显示在卡片底部。一张表格卡片只能有一个按钮。您可以自定义按钮颜色。 |
HorizontalAlignment |
是 | 是 | 在单元格内水平对齐内容。值可以是 LEADING 、CENTER 或 TRAILING 。如果未指定,内容将与单元格的前边缘对齐。 |
示例代码
以下代码段展示了如何实现简单的表格卡片:
Node.js
app.intent('Simple Table Card', (conv) => { if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('This is a simple table example.'); conv.ask(new Table({ dividers: true, columns: ['header 1', 'header 2', 'header 3'], rows: [ ['row 1 item 1', 'row 1 item 2', 'row 1 item 3'], ['row 2 item 1', 'row 2 item 2', 'row 2 item 3'], ], })); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("Simple Table Card") public ActionResponse simpleTable(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a simple table example.") .add( new TableCard() .setColumnProperties( Arrays.asList( new TableCardColumnProperties().setHeader("header 1"), new TableCardColumnProperties().setHeader("header 2"), new TableCardColumnProperties().setHeader("header 3"))) .setRows( Arrays.asList( new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 1 item 1"), new TableCardCell().setText("row 1 item 2"), new TableCardCell().setText("row 1 item 3"))), new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 2 item 1"), new TableCardCell().setText("row 2 item 2"), new TableCardCell().setText("row 2 item 3")))))); return responseBuilder.build(); }
Node.js
if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('This is a simple table example.'); conv.ask(new Table({ dividers: true, columns: ['header 1', 'header 2', 'header 3'], rows: [ ['row 1 item 1', 'row 1 item 2', 'row 1 item 3'], ['row 2 item 1', 'row 2 item 2', 'row 2 item 3'], ], })); conv.ask('Which response would you like to see next?');
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a simple table example.") .add( new TableCard() .setColumnProperties( Arrays.asList( new TableCardColumnProperties().setHeader("header 1"), new TableCardColumnProperties().setHeader("header 2"), new TableCardColumnProperties().setHeader("header 3"))) .setRows( Arrays.asList( new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 1 item 1"), new TableCardCell().setText("row 1 item 2"), new TableCardCell().setText("row 1 item 3"))), new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 2 item 1"), new TableCardCell().setText("row 2 item 2"), new TableCardCell().setText("row 2 item 3")))))); return responseBuilder.build();
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "This is a simple table example." } }, { "tableCard": { "rows": [ { "cells": [ { "text": "row 1 item 1" }, { "text": "row 1 item 2" }, { "text": "row 1 item 3" } ], "dividerAfter": true }, { "cells": [ { "text": "row 2 item 1" }, { "text": "row 2 item 2" }, { "text": "row 2 item 3" } ], "dividerAfter": true } ], "columnProperties": [ { "header": "header 1" }, { "header": "header 2" }, { "header": "header 3" } ] } }, { "simpleResponse": { "textToSpeech": "Which response would you like to see next?" } } ] } } } }
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "expectUserResponse": true, "expectedInputs": [ { "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "This is a simple table example." } }, { "tableCard": { "columnProperties": [ { "header": "header 1" }, { "header": "header 2" }, { "header": "header 3" } ], "rows": [ { "cells": [ { "text": "row 1 item 1" }, { "text": "row 1 item 2" }, { "text": "row 1 item 3" } ], "dividerAfter": true }, { "cells": [ { "text": "row 2 item 1" }, { "text": "row 2 item 2" }, { "text": "row 2 item 3" } ], "dividerAfter": true } ] } }, { "simpleResponse": { "textToSpeech": "Which response would you like to see next?" } } ] } }, "possibleIntents": [ { "intent": "actions.intent.TEXT" } ] } ] }
以下代码段展示了如何实现复杂的表格卡片:
Node.js
app.intent('Advanced Table Card', (conv) => { if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('This is a table with all the possible fields.'); conv.ask(new Table({ title: 'Table Title', subtitle: 'Table Subtitle', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Alt Text', }), columns: [ { header: 'header 1', align: 'CENTER', }, { header: 'header 2', align: 'LEADING', }, { header: 'header 3', align: 'TRAILING', }, ], rows: [ { cells: ['row 1 item 1', 'row 1 item 2', 'row 1 item 3'], dividerAfter: false, }, { cells: ['row 2 item 1', 'row 2 item 2', 'row 2 item 3'], dividerAfter: true, }, { cells: ['row 3 item 1', 'row 3 item 2', 'row 3 item 3'], }, ], buttons: new Button({ title: 'Button Text', url: 'https://assistant.google.com', }), })); conv.ask('Which response would you like to see next?'); });
Java
@ForIntent("Advanced Table Card") public ActionResponse advancedTable(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a table with all the possible fields.") .add( new TableCard() .setTitle("Table Title") .setSubtitle("Table Subtitle") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Alt text")) .setButtons( Arrays.asList( new Button() .setTitle("Button Text") .setOpenUrlAction( new OpenUrlAction().setUrl("https://assistant.google.com")))) .setColumnProperties( Arrays.asList( new TableCardColumnProperties() .setHeader("header 1") .setHorizontalAlignment("CENTER"), new TableCardColumnProperties() .setHeader("header 2") .setHorizontalAlignment("LEADING"), new TableCardColumnProperties() .setHeader("header 3") .setHorizontalAlignment("TRAILING"))) .setRows( Arrays.asList( new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 1 item 1"), new TableCardCell().setText("row 1 item 2"), new TableCardCell().setText("row 1 item 3"))) .setDividerAfter(false), new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 2 item 1"), new TableCardCell().setText("row 2 item 2"), new TableCardCell().setText("row 2 item 3"))) .setDividerAfter(true), new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 2 item 1"), new TableCardCell().setText("row 2 item 2"), new TableCardCell().setText("row 2 item 3")))))); return responseBuilder.build(); }
Node.js
if (!conv.screen) { conv.ask('Sorry, try this on a screen device or select the ' + 'phone surface in the simulator.'); conv.ask('Which response would you like to see next?'); return; } conv.ask('This is a table with all the possible fields.'); conv.ask(new Table({ title: 'Table Title', subtitle: 'Table Subtitle', image: new Image({ url: 'https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png', alt: 'Alt Text', }), columns: [ { header: 'header 1', align: 'CENTER', }, { header: 'header 2', align: 'LEADING', }, { header: 'header 3', align: 'TRAILING', }, ], rows: [ { cells: ['row 1 item 1', 'row 1 item 2', 'row 1 item 3'], dividerAfter: false, }, { cells: ['row 2 item 1', 'row 2 item 2', 'row 2 item 3'], dividerAfter: true, }, { cells: ['row 3 item 1', 'row 3 item 2', 'row 3 item 3'], }, ], buttons: new Button({ title: 'Button Text', url: 'https://assistant.google.com', }), })); conv.ask('Which response would you like to see next?');
Java
ResponseBuilder responseBuilder = getResponseBuilder(request); if (!request.hasCapability(Capability.SCREEN_OUTPUT.getValue())) { return responseBuilder .add("Sorry, try ths on a screen device or select the phone surface in the simulator.") .add("Which response would you like to see next?") .build(); } responseBuilder .add("This is a table with all the possible fields.") .add( new TableCard() .setTitle("Table Title") .setSubtitle("Table Subtitle") .setImage( new Image() .setUrl( "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png") .setAccessibilityText("Alt text")) .setButtons( Arrays.asList( new Button() .setTitle("Button Text") .setOpenUrlAction( new OpenUrlAction().setUrl("https://assistant.google.com")))) .setColumnProperties( Arrays.asList( new TableCardColumnProperties() .setHeader("header 1") .setHorizontalAlignment("CENTER"), new TableCardColumnProperties() .setHeader("header 2") .setHorizontalAlignment("LEADING"), new TableCardColumnProperties() .setHeader("header 3") .setHorizontalAlignment("TRAILING"))) .setRows( Arrays.asList( new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 1 item 1"), new TableCardCell().setText("row 1 item 2"), new TableCardCell().setText("row 1 item 3"))) .setDividerAfter(false), new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 2 item 1"), new TableCardCell().setText("row 2 item 2"), new TableCardCell().setText("row 2 item 3"))) .setDividerAfter(true), new TableCardRow() .setCells( Arrays.asList( new TableCardCell().setText("row 2 item 1"), new TableCardCell().setText("row 2 item 2"), new TableCardCell().setText("row 2 item 3")))))); return responseBuilder.build();
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "This is a table with all the possible fields." } }, { "tableCard": { "title": "Table Title", "subtitle": "Table Subtitle", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Alt Text" }, "rows": [ { "cells": [ { "text": "row 1 item 1" }, { "text": "row 1 item 2" }, { "text": "row 1 item 3" } ], "dividerAfter": false }, { "cells": [ { "text": "row 2 item 1" }, { "text": "row 2 item 2" }, { "text": "row 2 item 3" } ], "dividerAfter": true }, { "cells": [ { "text": "row 3 item 1" }, { "text": "row 3 item 2" }, { "text": "row 3 item 3" } ] } ], "columnProperties": [ { "header": "header 1", "horizontalAlignment": "CENTER" }, { "header": "header 2", "horizontalAlignment": "LEADING" }, { "header": "header 3", "horizontalAlignment": "TRAILING" } ], "buttons": [ { "title": "Button Text", "openUrlAction": { "url": "https://assistant.google.com" } } ] } }, { "simpleResponse": { "textToSpeech": "Which response would you like to see next?" } } ] } } } }
JSON
请注意,以下 JSON 描述了 webhook 响应。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TEXT" } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "This is a table with all the possible fields." } }, { "tableCard": { "title": "Table Title", "subtitle": "Table Subtitle", "image": { "url": "https://storage.googleapis.com/actionsresources/logo_assistant_2x_64dp.png", "accessibilityText": "Alt Text" }, "rows": [ { "cells": [ { "text": "row 1 item 1" }, { "text": "row 1 item 2" }, { "text": "row 1 item 3" } ], "dividerAfter": false }, { "cells": [ { "text": "row 2 item 1" }, { "text": "row 2 item 2" }, { "text": "row 2 item 3" } ], "dividerAfter": true }, { "cells": [ { "text": "row 3 item 1" }, { "text": "row 3 item 2" }, { "text": "row 3 item 3" } ] } ], "columnProperties": [ { "header": "header 1", "horizontalAlignment": "CENTER" }, { "header": "header 2", "horizontalAlignment": "LEADING" }, { "header": "header 3", "horizontalAlignment": "TRAILING" } ], "buttons": [ { "title": "Button Text", "openUrlAction": { "url": "https://assistant.google.com" } } ] } }, { "simpleResponse": { "textToSpeech": "Which response would you like to see next?" } } ] } } } ] }
自定义响应
您可以通过创建自定义主题来更改丰富回复的外观。如果您为 Actions 项目定义了主题,项目 Action 中的丰富响应将根据您的主题设置样式。当用户通过屏幕在 Surface 上调用您的 Action 时,这种自定义品牌信息有助于为对话定义独特的外观和风格。
如需设置自定义响应主题,请执行以下操作:
- 在 Actions 控制台中,依次转到 Develop > Theme custom。
- 设置以下任一或所有项:
- 背景颜色:用作卡片背景。一般来说,您应该为背景使用浅色,以便卡片的内容易于阅读。
- 主要颜色是卡片标题文本和界面元素的主颜色。通常,您应该使用较深的主要颜色与背景形成鲜明对比。
- 字体系列描述了用于标题和其他醒目文本元素的字体类型。
- 图片角样式可能会改变卡片角的外观。
- 背景图片使用自定义图片代替背景颜色。当 Surface 设备处于横屏模式或竖屏模式时,您需要分别提供两张不同的图片。请注意,如果您使用背景图片,则主要颜色会设置为白色。
- 点击保存。