Conversational Actions will be deprecated on June 13, 2023. For more information, see Conversational Actions sunset.

리치 응답 (Dialogflow)

Dialogflow 살펴보기

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

  1. 에이전트 이름을 입력하고 샘플의 새 Dialogflow 에이전트를 만듭니다.
  2. 에이전트 가져오기가 완료되면 에이전트로 이동을 클릭합니다.
  3. 기본 탐색 메뉴에서 처리로 이동합니다.
  4. 인라인 편집기를 사용 설정한 후 배포를 클릭합니다. 편집기에 샘플 코드가 포함되어 있습니다.
  5. 기본 탐색 메뉴에서 통합으로 이동한 다음 Google 어시스턴트를 클릭합니다.
  6. 표시되는 모달 창에서 변경사항 자동 미리보기를 사용 설정하고 테스트를 클릭하여 작업 시뮬레이터를 엽니다.
  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?');
});

자바

@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?');

자바

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(길이) x 232dp(너비)가 되어야 합니다.
      • 이미지 가로세로 비율이 이미지 경계 상자와 일치하지 않으면 이미지의 두 쪽에 막대가 가운데에 배치됩니다. 스마트폰에서는 이미지의 모서리가 둥근 정사각형이 됩니다.
      • 이미지 링크가 깨진 경우 자리표시자 이미지가 대신 사용됩니다.
      • 이미지에 대체 텍스트가 필요합니다.
    • 제목 (필수)
      • 기본 텍스트 카드와 동일한 서식 지정 옵션입니다.
      • 고유한 이름을 사용해야 합니다 (음성 선택 지원).
      • 텍스트 최대 2줄
      • 글꼴 크기 16 sp.
    • 설명(선택사항)
      • 기본 텍스트 카드와 동일한 서식 지정 옵션입니다.
      • 텍스트 최대 4줄
      • 줄임표로 잘림 (...)
      • 글꼴 크기는 14sp, 회색입니다.
    • 바닥글(선택사항)
      • 글꼴 및 글꼴 크기가 고정되었습니다.
      • 최대 한 줄의 텍스트
      • 줄임표로 잘림 (...)
      • 하단에 고정되므로 본문 텍스트가 적은 타일은 하위 텍스트 위에 공백이 있을 수 있습니다.
      • 글꼴 크기는 14sp, 회색입니다.
  • 상호작용
    • 사용자는 세로로 스크롤하여 항목을 볼 수 있습니다.
    • 탭 카드: 항목을 탭하면 사용자가 브라우저로 연결되고 링크된 페이지가 표시됩니다.
  • 음성 입력
    • 마이크 동작
      • 사용자에게 탐색 캐러셀이 전송되어도 마이크가 다시 열리지 않습니다.
      • 사용자는 여전히 마이크를 탭하거나 어시스턴트('OK 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?'); ;
});

자바

@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?');

자바

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. 미디어 응답 예시 (스마트폰)

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

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

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

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

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

속성

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

  • actions.capability.MEDIA_RESPONSE_AUDIO 기능이 있는 표시 경로에서 지원됩니다.
  • 재생할 오디오가 올바른 형식의 .mp3 파일로 되어 있어야 합니다. 실시간 스트리밍은 지원되지 않습니다.
  • 재생할 미디어 파일을 HTTPS URL로 지정해야 합니다.
  • 이미지(선택사항)
    • 원하는 경우 아이콘 또는 이미지를 포함할 수 있습니다.
    • 아이콘
      • 아이콘은 미디어 플레이어 카드 오른쪽에 테두리 없는 썸네일로 표시됩니다.
      • 크기는 36x36dp여야 합니다. 이미지가 큰 이미지는 이에 맞게 크기가 조정됩니다.
    • 이미지
      • 이미지 컨테이너의 높이는 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']));
});

자바

@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']));

자바

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

안내

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

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

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

재생 완료 후 콜백 처리

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

Dialogflow 사용

Dialogflow에서 대화형 브랜치를 실행하려는 경우 인텐트에 actions_capability_media_response_audio의 입력 컨텍스트를 설정하여 미디어 응답을 지원하는 노출 영역에서만 트리거되도록 해야 합니다.

처리 빌드

아래의 코드 스니펫은 작업의 fulfillment 코드를 작성하는 방법을 보여줍니다. 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?');
});

자바

@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?');
});

자바

@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 아니요 테이블의 부제목입니다.
image 표와 연결된 이미지입니다.
Row 아니요

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

처음 3개 행은 항상 표시되지만 다른 행에는 표시되지 않을 수도 있습니다.

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

ColumnProperties 열의 헤더와 정렬입니다. header 속성 (열의 헤더 텍스트를 나타냄)과 horizontal_alignment 속성 (HorizontalAlignment 유형)으로 구성됩니다.
Cell 아니요 행에 있는 셀을 설명합니다. 각 셀에는 텍스트 값을 나타내는 문자열이 포함됩니다. 셀의 텍스트를 맞춤설정할 수 있습니다.
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?');
});

자바

@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?');

자바

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?');
});

자바

@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?');

자바

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 콘솔에서 테마 맞춤설정