리치 응답 (Dialogflow)

Dialogflow에서 탐색

계속을 클릭하여 Dialogflow의 응답 샘플을 가져옵니다. 그런 다음 아래 단계에 따라 샘플을 배포하고 테스트합니다.

  1. 에이전트 이름을 입력하고 샘플의 새 Dialogflow 에이전트를 만듭니다.
  2. 에이전트 가져오기가 완료되면 Go to agent(에이전트로 이동)를 클릭합니다.
  3. 기본 탐색 메뉴에서 Fulfillment로 이동합니다.
  4. 인라인 편집기를 사용 설정한 다음 배포를 클릭합니다. 편집기에 샘플 코드가 포함되어 있습니다.
  5. 기본 탐색 메뉴에서 통합으로 이동한 다음 Google 어시스턴트를 클릭합니다.
  6. 표시되는 모달 창에서 Auto-preview changes를 사용 설정하고 Test를 클릭하여 작업 시뮬레이터를 엽니다.
  7. 시뮬레이터에서 Talk to my test app를 입력하여 샘플을 테스트합니다.
계속

시각적 요소를 표시하여 작업과의 사용자 상호작용을 개선하려면 리치 응답을 사용합니다. 이러한 시각적 요소는 대화를 계속하는 방법에 관한 힌트를 제공할 수 있습니다.

리치 응답은 화면 전용 또는 오디오 및 화면 환경에 표시될 수 있습니다. 여기에는 다음 구성요소가 포함될 수 있습니다.

대화 디자인 가이드라인을 검토하여 이러한 시각적 요소를 작업에 통합하는 방법을 알아볼 수도 있습니다.

속성

다양한 응답에는 다음과 같은 요구사항과 사용자가 구성할 수 있는 선택적 속성이 있습니다.

  • actions.capability.SCREEN_OUTPUT 기능이 있는 노출 영역에서 지원됩니다.
  • 리치 응답의 첫 번째 항목은 단순 응답이어야 합니다.
  • 최대 2개의 간단한 응답입니다.
  • 기본 카드 최대 1개 또는 StructuredResponse
  • 최대 8개의 추천 칩.
  • 추천 칩은 FinalResponse에서 허용되지 않습니다.
  • 스마트 디스플레이에서 웹으로 연결하는 기능은 현재 지원되지 않습니다.

다음 섹션에서는 다양한 유형의 리치 응답을 작성하는 방법을 보여줍니다.

기본 카드

그림 1. 기본 카드의 예 (스마트폰)

기본 카드에는 다음을 포함할 수 있는 정보가 표시됩니다.

  • 이미지
  • 제목
  • 부제목
  • 텍스트 본문
  • 연결 버튼
  • 테두리

기본 카드는 주로 디스플레이용으로 사용합니다. 간결하고 사용자에게 핵심 (또는 요약) 정보를 제시하며 개발자가 선택할 경우 사용자가 자세히 알아볼 수 있도록 설계되었습니다 (웹링크 사용).

대부분의 경우 카드 아래에 추천 칩을 추가하여 대화를 계속하거나 피벗해야 합니다.

채팅 풍선의 카드에 표시된 정보는 어떤 경우에도 반복하지 마세요.

속성

기본 카드 응답 유형에는 다음과 같은 요구사항과 사용자가 구성할 수 있는 선택적 속성이 있습니다.

  • actions.capability.SCREEN_OUTPUT 기능이 있는 노출 영역에서 지원됩니다.
  • 서식이 지정된 텍스트(이미지가 없는 경우 필수)
    • 기본적으로 일반 텍스트입니다.
    • 링크를 포함해서는 안 됩니다.
    • 이미지는 10줄, 이미지가 없으면 15줄로 제한됩니다. 글자 수는 약 500자 (이미지 포함) 또는 750자 (이미지 제외)입니다. 또한 화면이 작은 휴대전화는 화면이 큰 휴대전화보다 먼저 텍스트를 자릅니다. 텍스트에 행이 너무 많으면 마지막 단어 나누기에서 생략 부호와 함께 잘립니다.
    • 마크다운의 제한된 하위 집합이 지원됩니다.
      • 줄 바꿈 뒤에 이중 공백이 있고 그 뒤에 \n
      • **bold**
      • *italics*
  • 이미지(서식이 지정된 텍스트가 없는 경우 필수)
    • 모든 이미지의 높이는 192dp가 되어야 합니다.
    • 이미지의 가로세로 비율이 화면과 다르면 이미지가 중앙에 배치되고 세로 또는 가로 가장자리에 회색 막대가 표시됩니다.
    • 이미지 소스가 URL입니다.
    • 모션 GIF는 허용됩니다.

선택사항

  • 제목
    • 일반 텍스트
    • 글꼴 및 크기가 수정되었습니다.
    • 최대 한 줄이며 추가 문자는 잘립니다.
    • 제목을 지정하지 않으면 카드 높이가 접힙니다.
  • 부제목
    • 일반 텍스트
    • 글꼴 및 글꼴 크기를 수정했습니다.
    • 최대 한 줄이며 추가 문자는 잘립니다.
    • 부제목이 지정되지 않으면 카드 높이가 접힙니다.
  • 링크 버튼
    • 링크 제목은 필수 항목입니다.
    • 최대 1개의 링크
    • 개발자 도메인 외부의 사이트로 연결되는 링크는 허용됩니다.
    • 링크 텍스트에 오해의 소지가 없어야 합니다. 이는 승인 절차에서 확인됩니다.
    • 기본 카드에는 링크가 없는 상호작용 기능이 없습니다. 링크를 탭하면 사용자가 링크로 이동하지만 카드의 기본 본문은 비활성 상태로 유지됩니다.
  • 테두리
    • 카드와 이미지 컨테이너 사이의 테두리를 조정하여 기본 카드의 표시 방식을 맞춤설정할 수 있습니다.
    • JSON 문자열 속성 imageDisplayOptions을 설정하여 구성
그림 2. 기본 카드의 예 (스마트 디스플레이)

샘플 코드

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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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?"
              }
            }
          ]
        }
      }
    }
  ]
}
그림 3. 캐러셀 탐색 예 (스마트폰)

탐색 캐러셀은 사용자가 세로로 스크롤하고 컬렉션의 타일을 선택할 수 있는 리치 응답입니다. 탐색 캐러셀은 웹브라우저 (또는 모든 타일이 AMP를 지원하는 경우 AMP 브라우저)에서 선택한 타일을 열어 웹 콘텐츠용으로 특별히 설계되었습니다. 탐색 캐러셀은 나중에 찾아볼 수 있도록 사용자의 어시스턴트 표시 경로에도 유지됩니다.

속성

탐색 캐러셀 응답 유형에는 다음과 같은 요구사항과 사용자가 구성할 수 있는 선택적 속성이 있습니다.

  • actions.capability.SCREEN_OUTPUTactions.capability.WEB_BROWSER 기능이 모두 있는 노출 영역에서 지원됩니다. 이 응답 유형은 현재 스마트 디스플레이에서 사용할 수 없습니다.
  • 캐러셀 둘러보기
    • 타일은 최대 10개까지 사용할 수 있습니다.
    • 타일은 최소 2개입니다.
    • 캐러셀의 카드는 모두 웹 콘텐츠로 연결되어야 합니다 (AMP 콘텐츠 권장).
      • 사용자가 AMP 뷰어로 이동하려면 AMP 콘텐츠 타일의 urlHintType을 'AMP_CONTENT'로 설정해야 합니다.
  • 캐러셀 타일 둘러보기
    • 카드 일관성 (필수):
      • 탐색 캐러셀의 모든 카드에는 동일한 구성요소가 있어야 합니다. 예를 들어 한 카드에 이미지 필드가 있으면 캐러셀의 나머지 카드에도 이미지 필드가 있어야 합니다.
      • 탐색 캐러셀의 모든 타일이 AMP 지원 콘텐츠로 연결되는 경우 사용자는 추가 기능이 있는 AMP 브라우저로 이동됩니다. 타일이 AMP가 아닌 콘텐츠로 연결되는 경우 모든 타일은 사용자를 웹브라우저로 안내합니다.
    • 이미지(선택사항)
      • 이미지의 너비는 128dp, 너비 232dp가 됩니다.
      • 이미지의 가로세로 비율이 이미지 경계 상자와 일치하지 않으면 이미지가 양쪽에 막대로 가운데에 배치됩니다. 스마트폰에서 이미지는 모서리가 둥근 정사각형을 중심으로 합니다.
      • 이미지 링크가 깨지면 자리표시자 이미지가 대신 사용됩니다.
      • 이미지에는 대체 텍스트가 필요합니다.
    • 제목 (필수 항목)
      • 기본 텍스트 카드와 동일한 서식 지정 옵션
      • 제목은 고유해야 합니다 (음성 선택을 지원하기 위해).
      • 텍스트는 최대 2줄까지 입력할 수 있습니다.
      • 글꼴 크기 16sp
    • 설명(선택사항)
      • 기본 텍스트 카드와 동일한 서식 지정 옵션
      • 텍스트는 최대 4줄까지 입력할 수 있습니다.
      • 생략 부호 (...)로 잘림
      • 글꼴 크기 14sp, 회색
    • 바닥글(선택사항)
      • 글꼴 및 글꼴 크기를 수정했습니다.
      • 텍스트는 한 줄 이내여야 합니다.
      • 생략 부호 (...)로 잘림
      • 하단에 고정되도록 본문 텍스트 줄이 적은 타일은 하위 텍스트 위에 공백이 있을 수 있습니다.
      • 글꼴 크기 14sp, 회색
  • 상호작용
    • 사용자는 세로로 스크롤하여 항목을 볼 수 있습니다.
    • 카드 탭하기: 항목을 탭하면 사용자가 브라우저로 이동하며 연결된 페이지가 표시됩니다.
  • 음성 입력
    • 마이크 동작
      • 탐색 캐러셀이 사용자에게 전송되더라도 마이크가 다시 열리지 않습니다.
      • 사용자는 계속해서 마이크를 탭하거나 어시스턴트 ('Hey Google')를 호출하여 마이크를 다시 열 수 있습니다.

안내

탐색 캐러셀이 전송된 후에도 마이크는 기본적으로 닫힌 상태로 유지됩니다. 나중에 대화를 계속하려면 캐러셀 아래에 추천 칩을 추가하는 것이 좋습니다.

목록에 추천 검색어 칩으로 표시된 옵션을 반복하지 마세요. 이 컨텍스트의 칩은 대화를 피벗하는 데 사용됩니다 (선택 선택이 아님).

목록과 마찬가지로 캐러셀 카드에 포함된 채팅 풍선은 오디오 (TTS/SSML)의 하위 집합입니다. 여기서 오디오 (TTS/SSML)는 캐러셀의 첫 번째 카드를 통합하므로 캐러셀의 모든 요소를 읽지 않는 것이 좋습니다. 첫 번째 항목과 그 이유를 언급하는 것이 좋습니다 (예: 가장 인기 있는 항목, 가장 최근에 구매한 항목, 가장 많이 논의된 항목).

샘플 코드

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',
      }),
    ],
  }));
});
@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();
}
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',
    }),
  ],
}));
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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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"
        }
      ]
    }
  ]
}

선택된 항목 처리

캐러셀이 브라우저 핸드오프를 처리하므로 탐색 캐러셀 항목과의 사용자 상호작용에는 후속 조치 처리가 필요하지 않습니다. 사용자가 탐색 캐러셀 항목과 상호작용한 후에는 마이크가 다시 열리지 않으므로 위의 안내에 따라 대화를 종료하거나 응답에 추천 칩을 포함해야 합니다.

추천 칩

그림 4. 추천 칩 예 (스마트폰)

추천 검색어 칩을 사용하여 대답에 힌트를 줌으로써 대화를 계속하거나 방향을 전환할 수 있습니다. 대화 중에 기본 클릭 유도 문구가 있는 경우 이를 첫 번째 추천 칩으로 나열하는 것이 좋습니다.

가능하면 한 가지 주요 제안을 채팅 풍선의 일부로 포함해야 하지만, 응답이나 채팅 대화가 자연스러운 경우에만 사용하세요.

속성

추천 칩에는 다음과 같은 요구사항과 사용자가 구성할 수 있는 선택적 속성이 있습니다.

  • actions.capability.SCREEN_OUTPUT 기능이 있는 노출 영역에서 지원됩니다.
  • 추천 칩을 웹에 연결하려면 노출 영역에 actions.capability.WEB_BROWSER 기능도 있어야 합니다. 이 기능은 현재 스마트 디스플레이에서 사용할 수 없습니다.
  • 칩은 최대 8개입니다.
  • 최대 텍스트 길이는 25자(영문 기준)입니다.
  • 일반 텍스트만 지원합니다.

그림 5. 추천 칩의 예 (스마트 디스플레이)

샘플 코드

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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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/"
          }
        }
      }
    }
  ]
}

미디어 응답

그림 6. 미디어 응답 예 (스마트폰)

미디어 응답을 사용하면 작업에서 재생 시간이 SSML의 240초 제한보다 긴 오디오 콘텐츠를 재생할 수 있습니다. 미디어 응답의 기본 구성요소는 단일 트랙 카드입니다. 이 카드를 통해 사용자는 다음 작업을 실행할 수 있습니다.

  • 지난 10초를 다시 재생합니다.
  • 30초 동안 앞으로 건너뜁니다.
  • 미디어 콘텐츠의 전체 길이를 봅니다.
  • 오디오 재생 진행률 표시기를 확인합니다.
  • 경과된 재생 시간을 봅니다.

미디어 응답은 음성 상호작용을 위해 다음과 같은 오디오 컨트롤을 지원합니다.

  • “Hey Google, 재생해 줘.”
  • “Hey Google, 일시중지해 줘.”
  • “Hey Google, 중지해 줘.”
  • “Hey Google, 다시 시작해 줘.”

사용자는 "Hey Google, 볼륨 높여 줘" 또는 "Hey Google, 볼륨 50퍼센트로 설정해 줘"와 같이 말하여 볼륨을 제어할 수도 있습니다. 유사한 학습 문구를 처리하는 경우 작업의 인텐트가 우선 적용됩니다. 작업에 특별한 이유가 없다면 어시스턴트가 이러한 사용자 요청을 처리하도록 합니다.

속성

미디어 응답에는 다음과 같은 요구사항과 사용자가 구성할 수 있는 선택적 속성이 있습니다.

  • actions.capability.MEDIA_RESPONSE_AUDIO 기능이 있는 노출 영역에서 지원됩니다.
  • 재생할 오디오는 올바른 형식의 .mp3 파일에 있어야 합니다. 실시간 스트리밍은 지원되지 않습니다.
  • 재생할 미디어 파일은 HTTPS URL로 지정해야 합니다.
  • 이미지(선택사항)
    • 선택사항으로 아이콘이나 이미지를 포함할 수 있습니다.
    • 아이콘
      • 아이콘은 미디어 플레이어 카드 오른쪽에 테두리 없는 썸네일로 표시됩니다.
      • 크기는 36 x 36dp여야 합니다. 더 큰 이미지는 크기에 맞게 크기가 조정됩니다.
    • 이미지
      • 이미지 컨테이너의 높이는 192dp가 됩니다.
      • 이미지는 미디어 플레이어 카드 상단에 표시되며 카드의 전체 너비에 걸쳐 있습니다. 대부분의 이미지는 상단 또는 측면에 막대와 함께 표시됩니다.
      • 모션 GIF는 허용됩니다.
    • 이미지 소스를 URL로 지정해야 합니다.
    • 모든 이미지에 대체 텍스트가 필요합니다.

노출 영역의 동작

미디어 응답은 Android 휴대전화와 Google Home에서 지원됩니다. 미디어 응답의 동작은 사용자가 작업과 상호작용하는 노출 영역에 따라 다릅니다.

Android 휴대전화에서 사용자는 다음 조건 중 하나가 충족되면 미디어 응답을 볼 수 있습니다.

  • Google 어시스턴트가 포그라운드에 있고 휴대전화 화면은 켜져 있습니다.
  • 사용자가 오디오가 재생되는 동안 Google 어시스턴트를 종료하고 재생 완료 후 10분 이내에 Google 어시스턴트로 돌아옵니다. Google 어시스턴트로 돌아오면 사용자에게 미디어 카드와 추천 칩이 표시됩니다.
  • 어시스턴트를 사용하면 사용자가 "볼륨 높여 줘" 또는 "볼륨 50퍼센트로 설정해 줘"와 같이 말하여 대화 작업 내에서 기기 볼륨을 제어할 수 있습니다. 유사한 학습 문구를 처리하는 인텐트가 있는 경우 인텐트가 우선 적용됩니다. 작업에 특별한 이유가 없다면 어시스턴트가 이러한 사용자 요청을 처리하도록 하는 것이 좋습니다.

휴대전화가 잠겨 있을 때도 미디어 컨트롤을 사용할 수 있습니다. Android에서는 알림 영역에도 컨트롤이 표시됩니다.

그림 7. 미디어 응답 예 (스마트 디스플레이)

샘플 코드

다음 코드 샘플은 미디어를 포함하도록 리치 응답을 업데이트하는 방법을 보여줍니다.

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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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"
            }
          ]
        }
      }
    }
  ]
}

안내

응답에는 mediaTypeAUDIO이고 리치 응답의 항목 배열 내에 mediaObject가 포함된 mediaResponse가 포함되어야 합니다. 미디어 응답은 단일 미디어 객체를 지원합니다. 미디어 객체에는 오디오 파일의 콘텐츠 URL이 포함되어야 합니다. 미디어 객체는 선택적으로 이름, 하위 텍스트(설명), 아이콘 또는 이미지 URL을 포함할 수 있습니다.

휴대전화와 Google Home에서 작업이 오디오 재생을 완료하면 Google 어시스턴트는 미디어 응답이 FinalResponse인지 확인합니다. 그렇지 않으면 처리에 콜백을 전송하여 사용자에게 응답할 수 있도록 합니다.

응답이 FinalResponse가 아닌 경우 작업에 추천 칩이 포함되어야 합니다.

재생 완료 후 콜백 처리

작업은 사용자에게 후속 조치 (예: 다른 노래 재생)를 요청하는 actions.intent.MEDIA_STATUS 인텐트를 처리해야 합니다. 미디어 재생이 완료되면 작업에서 이 콜백을 수신합니다. 콜백에서 MEDIA_STATUS 인수에는 현재 미디어에 관한 상태 정보가 포함됩니다. 상태 값은 FINISHED 또는 STATUS_UNSPECIFIED입니다.

Dialogflow 사용

Dialogflow에서 대화형 분기를 수행하려면 미디어 응답을 지원하는 노출 영역에서만 트리거되도록 인텐트에 actions_capability_media_response_audio 입력 컨텍스트를 설정해야 합니다.

처리 빌드

아래의 코드 스니펫은 작업의 처리 코드를 작성하는 방법을 보여줍니다. Dialogflow를 사용하는 경우 actions.intent.MEDIA_STATUSactions_intent_MEDIA_STATUS 이벤트를 수신하는 인텐트에 지정된 작업 이름(예: '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은 웹훅 요청을 설명합니다.

{
  "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은 웹훅 요청을 설명합니다.

{
  "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"
}

표 카드

표 카드를 사용하면 응답에 표 형식 데이터 (예: 스포츠 순위, 선거 결과, 항공편)를 표시할 수 있습니다. 어시스턴트가 테이블 카드에 표시해야 하는 열과 행 (각각 최대 3개)을 정의할 수 있습니다. 또한 우선순위와 함께 추가 열과 행을 정의할 수도 있습니다.

표는 정적 데이터를 표시하고 목록 요소와 같이 상호작용할 수 없으므로 세로 목록과 다릅니다.

그림 8. 표 카드의 예 (스마트 디스플레이)

속성

테이블 카드에는 다음과 같은 요구사항과 구성할 수 있는 선택적 속성이 있습니다.

  • actions.capability.SCREEN_OUTPUT 기능이 있는 노출 영역에서 지원됩니다.

다음 섹션에는 표 카드의 요소를 맞춤설정하는 방법이 요약되어 있습니다.

이름 선택사항 맞춤설정 가능 맞춤설정 참고사항
title 지원됨 지원됨 표의 전체 제목입니다. 자막이 설정된 경우 설정해야 합니다. 글꼴 모음과 색상을 맞춤설정할 수 있습니다.
subtitle 지원됨 No 테이블의 부제목입니다.
image 지원됨 지원됨 테이블과 연결된 이미지입니다.
Row No 지원됨

테이블의 행 데이터입니다. Cell 객체의 배열과 행 뒤에 구분선이 있어야 하는지를 나타내는 divider_after 속성으로 구성됩니다.

처음 3개 행은 항상 표시되지만 다른 행은 특정 노출 영역에 표시되지 않을 수 있습니다.

시뮬레이터로 테스트하여 지정된 노출 영역에 표시되는 행을 확인하세요. WEB_BROWSER 기능을 지원하는 노출 영역에서 사용자가 더 많은 데이터가 포함된 웹페이지를 가리키도록 할 수 있습니다. 현재 스마트 디스플레이에서는 웹에 연결할 수 없습니다.

ColumnProperties 지원됨 지원됨 열의 헤더 및 정렬입니다. header 속성 (열의 헤더 텍스트를 나타냄)과 horizontal_alignment 속성 (HorizontalAlignment 유형)으로 구성됩니다.
Cell No 지원됨 행의 셀을 설명합니다. 각 셀에는 텍스트 값을 나타내는 문자열이 포함됩니다. 셀의 텍스트를 맞춤설정할 수 있습니다.
Button 지원됨 지원됨 일반적으로 카드 하단에 표시되는 버튼 객체입니다. 테이블 카드에는 버튼이 1개만 있을 수 있습니다. 버튼 색상을 맞춤설정할 수 있습니다.
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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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은 웹훅 응답을 설명합니다.

{
  "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?"
              }
            }
          ]
        }
      }
    }
  ]
}

응답 맞춤설정

맞춤 테마를 만들어 리치 응답의 모양을 변경할 수 있습니다. 작업 프로젝트의 테마를 정의하면 프로젝트의 작업 전반에 걸쳐 풍부한 응답이 테마에 따라 스타일이 지정됩니다. 이 맞춤 브랜딩은 사용자가 화면이 있는 노출 영역에서 작업을 호출할 때 대화에 고유한 디자인과 분위기를 정의하는 데 유용할 수 있습니다.

커스텀 응답 테마를 설정하려면 다음 단계를 따르세요.

  1. Actions 콘솔에서 개발 > 테마 맞춤설정으로 이동합니다.
  2. 다음 중 일부 또는 전체를 설정합니다.
    • 배경 색상: 카드의 배경으로 사용됩니다. 일반적으로 카드 콘텐츠를 쉽게 읽을 수 있도록 배경에 밝은 색상을 사용해야 합니다.
    • 기본 색상은 카드 헤더 텍스트와 UI 요소의 기본 색상입니다. 일반적으로 배경과 대비되도록 더 어두운 기본 색상을 사용해야 합니다.
    • 글꼴 모음은 제목 및 기타 눈에 띄는 텍스트 요소에 사용되는 글꼴의 유형을 설명합니다.
    • 이미지 모서리 스타일은 카드 모서리 모양을 변경할 수 있습니다.
    • 배경 이미지는 배경 색상 대신 맞춤 이미지를 사용합니다. 노출 영역 기기가 가로 모드 또는 세로 모드인 경우 각각 두 개의 다른 이미지를 제공해야 합니다. 배경 이미지를 사용하면 기본 색상은 흰색으로 설정됩니다.
  3. 저장을 클릭합니다.
그림 9. Actions 콘솔에서 테마 맞춤설정하기