대화상자는 채팅 앱이 사용자와 상호작용하기 위해 여는 카드 기반 인터페이스 창입니다. 사용자가 여러 단계 프로세스를 완료할 수 있도록 앱에서 순차 대화상자를 열 수 있습니다.
대화상자는 다음과 같은 다양한 유형의 사용자 상호작용에 유용합니다.
- 사용자로부터 정보 수집
- 웹 서비스로 사용자 인증
- Chat 앱 설정 구성하기
이 가이드에서는 채팅 앱에 대화상자를 구현하는 방법을 설명합니다.
기본 요건
대화상자를 구현하고 이 가이드의 예를 실행하려면 다음이 필요합니다.
Node.js
- Google Chat에 액세스할 수 있는 Google Workspace 계정
- Chat 앱: Chat 앱을 빌드하려면 이 빠른 시작을 따르세요.
- 슬래시 명령어에 응답하여 대화상자를 여는 경우 Chat 앱에 구성된 슬래시 명령어
Apps Script
- Google Chat에 액세스할 수 있는 Google Workspace 계정
- Chat 앱: Chat 앱을 빌드하려면 이 빠른 시작을 따르세요.
- 슬래시 명령어에 응답하여 대화상자를 여는 경우 Chat 앱에 구성된 슬래시 명령어
Python
- Google Chat에 액세스할 수 있는 Google Workspace 계정
- Chat 앱: Chat 앱을 빌드하려면 이 빠른 시작을 따르세요.
- 슬래시 명령어에 응답하여 대화상자를 여는 경우 Chat 앱에 구성된 슬래시 명령어
대화상자 작동 방식
사용자가 카드에서 버튼을 클릭하거나 슬래시 명령어를 실행하면 Chat 앱이 대화상자를 열어 사용자와 상호작용할 수 있습니다.


이 대화상자는 사용자가 텍스트 위젯과 같은 위젯을 사용하여 정보를 입력할 수 있도록 하여 사용자 상호작용을 촉진합니다. Chat 앱은 사용자로부터 많은 정보를 수집할 때 순차 대화상자를 열 수 있습니다.


완료되면 Chat 앱에서 사용자가 대화상자에 입력한 값을 JSON으로 수신합니다. 사용자에게 문자 메시지 또는 카드로 응답하여 상호작용이 성공했다고 알립니다.
대화상자 열기
Chat 앱은 사용자의 응답으로 대화상자를 열 수 있습니다.
사용자가 대화상자를 요청하면 Chat 앱에서 Google Chat Event
을 수신하며, 여기에서
isDialogEvent
은true
입니다.DialogEventType
사용자가 대화상자를 여는지(REQUEST_DIALOG
), 대화상자를 클릭하는지(SUBMIT_DIALOG
) 또는 대화상자를 취소하는지(CANCEL_DIALOG
)를 지정합니다.
예를 들어 사용자가 대화상자를 열면 Chat 앱이 Google Chat에서 다음과 같은 Event
을 수신합니다.
JSON
{
"type": enum (EventType),
"eventTime": string,
"threadKey": string,
"message": {
object (Message)
},
"user": {
object (User)
},
"space": {
object (Space)
},
"action": {
object (FormAction)
},
"configCompleteRedirectUrl": string,
"isDialogEvent": true,
"dialogEventType": "REQUEST_DIALOG",
"common": {
object (CommonEventObject)
}
}
Chat 앱은 대화상자의 JSON 설명이 포함된 DialogAction
와 함께 "type": "DIALOG"
의 ActionResponse
를 반환하여 대화상자를 열 수 있습니다.
JSON
{
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "contactName"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
}
카드의 버튼 클릭에 대한 응답으로 대화상자 열기
사용자가 카드의 버튼을 클릭하면 다음과 같은 앱 Event
이 수신됩니다.
EventType
은(는)CARD_CLICKED
입니다.DialogEventType
은(는)REQUEST_DIALOG
입니다.
대화상자를 열려면 다음을 지정하세요.
onClick.action.function
- 대화상자를 여는 함수의 이름입니다. 이 함수는 다음을 반환해야 합니다."type": "DIALOG"
의ActionResponse
- 대화상자의 JSON 설명이 포함된
DialogAction
OPEN_DIALOG
(으)로onClick.action.interaction
이 속성은 앱에서 대화상자를 열기를 Google Chat에 알립니다.
이 예에서 Chat 앱은 대화상자를 여는 버튼이 있는 카드로 MESSAGE
Event
에 응답합니다.
Node.js
/**
* Responds to messages that have links whose URLs
* match URL patterns configured for link previews.
*
* @param {Object} event The event object from Chat
* API.
*
* @return {Object} Response from the Chat app
* attached to the message with the previewed link.
*/
exports.onMessage = function onMessage(req, res) {
// Store the Google Chat event as a variable.
var event = req.body;
if (req.method === "GET" || !event.message) {
res.send("Hello! This function is meant to be used in a Google Chat " +
"Space.");
}
// Responds with a card that prompts the user to add a contact
else {
res.json({
"cardsV2": [{
"cardId": "addContact",
"card": {
"header": {
"title": "Rolodex",
"subtitle": "Manage your contacts!",
"imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
"imageType": "CIRCLE"
},
"sections": [
{
"widgets": [
{
"buttonList": {
"buttons": [
{
"text": "Add Contact",
"onClick": {
"action": {
"function": "openDialog",
"interaction": "OPEN_DIALOG"
}
}
}
]
}
}
]
}
]
}
}]
});
}
// Respond to button clicks on attached cards
if(event.type === "CARD_CLICKED") {
if (event.common.invokedFunction == "openDialog") {
res.openDialog(event);
};
/**
* Opens and starts a dialog that allows users to add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
res.json({
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "name"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
});
};
Apps Script
/**
* Responds to a MESSAGE event in Google Chat with a card with a button
* that opens a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in response to a card's button click.
*/
function onMessage(event) {
return {
"cardsV2": [{
"cardId": "addContact",
"card": {
"header": {
"title": "Rolodex",
"subtitle": "Manage your contacts!",
"imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
"imageType": "CIRCLE"
},
"sections": [
{
"widgets": [
{
"buttonList": {
"buttons": [
{
"text": "Add Contact",
"onClick": {
"action": {
"function": "openDialog",
"interaction": "OPEN_DIALOG"
}
}
}
]
},
"horizontalAlignment": "CENTER"
}
]
}
]
}
}]
};
}
/**
* Responds to a CARD_CLICKED event in Google Chat.
*
* @param {Object} event the event object from Google Chat
*/
function onCardClick(event) {
if (event.common.invokedFunction == "openDialog") {
return openDialog(event);
}
}
/**
* Opens a dialog in Google Chat.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function openDialog(event) {
return {
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "contactName"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
};
}
Python
from typing import Any, Mapping
import flask
import functions_framework
@functions_framework.http
def main(req: flask.Request) -> Mapping[str, Any]:
"""Responds to a MESSAGE event in Google Chat that includes the /createContact
slash command by opening a dialog.
Args:
req (flask.Request): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
if req.method == 'GET':
return 'Sorry, this function must be called from a Google Chat.'
request = req.get_json(silent=True)
if request.get('type') == 'CARD_CLICKED':
if request.get('common', dict()).get('invokedFunction') == 'open_dialog':
return open_dialog(request)
else:
return {
'cardsV2': [{
'cardId': 'addContact',
'card': {
'header': {
'title': 'Rolodex',
'subtitle': 'Manage your contacts!',
'imageUrl': 'https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png',
'imageType': 'CIRCLE'
},
'sections': [
{
'widgets': [
{
'buttonList': {
'buttons': [
{
'text': 'Add Contact',
'onClick': {
'action': {
'function': 'open_dialog',
'interaction': 'OPEN_DIALOG'
}
}
}
]
}
}
]
}
]
}
}]
}
def open_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
"""Opens a dialog in Google Chat.
Args:
request (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
return {
'action_response': {
'type': 'DIALOG',
'dialog_action': {
'dialog': {
'body': {
'sections': [
{
'header': 'Add new contact',
'widgets': [
{
'textInput': {
'label': 'Name',
'type': 'SINGLE_LINE',
'name': 'name'
}
},
{
'textInput': {
'label': 'Address',
'type': 'MULTIPLE_LINE',
'name': 'address'
}
},
{
'decoratedText': {
'text': 'Add to favorites',
'switchControl': {
'controlType': 'SWITCH',
'name': 'saveFavorite'
}
}
},
{
'decoratedText': {
'text': 'Merge with existing contacts',
'switchControl': {
'controlType': 'SWITCH',
'name': 'mergeContact',
'selected': True
}
}
},
{
'buttonList': {
'buttons': [
{
'text': 'Next',
'onClick': {
'action': {
'function': 'open_sequential_dialog'
}
}
}
]
}
}
]
}
]
}
}
}
}
}
슬래시 명령어에 대한 응답으로 대화상자 열기
사용자가 슬래시 명령어가 포함된 대화상자를 열면 앱에서 다음과 같은 Event
이 수신됩니다.
EventType
은(는)MESSAGE
입니다.DialogEventType
은(는)REQUEST_DIALOG
입니다.
대화상자를 열려면 다음과 같이 응답하세요.
"type": "DIALOG"
의ActionResponse
- 대화상자의 JSON 설명이 포함된
DialogAction
이 예시에서는 채팅 앱이 대화상자를 열어 /createContact
슬래시 명령어에 응답합니다.
Node.js
/**
* Responds to messages that have links whose URLs
* match URL patterns configured for link previews.
*
* @param {Object} event The event object from Chat
* API.
*
* @return {Object} Response from the Chat app
* attached to the message with the previewed link.
*/
exports.onMessage = function onMessage(req, res) {
// Store the Google Chat event as a variable.
var event = req.body;
if (req.method === "GET" || !event.message) {
res.send("Hello! This function is meant to be used in a Google Chat " +
"Space.");
}
// Checks for the presence of event.message.slashCommand.
// If the slash command is "/help", responds with a text message.
// If the slash command is "/createContact", opens a dialog.
if (event.message.slashCommand) {
switch (event.message.slashCommand.commandId) {
case 1: // /help
res.json({"text": "Contact bot helps you update your address book!"});
case 2: // /createContact
res.openDialog(event);
}
}
};
/**
* Opens and starts a dialog that allows users to add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
res.json({
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "name"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
});
};
Apps Script
/**
* Responds to a MESSAGE event in Google Chat that includes the /createContact
* slash command by opening a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in response to a slash command.
*/
function onMessage(event) {
// Checks for the presence of event.message.slashCommand.
// If the slash command is "/help", responds with a text message.
// If the slash command is "/createContact", opens a dialog.
if (event.message.slashCommand) {
switch (event.message.slashCommand.commandId) {
case 1: // /help
return {"text": "Contact bot helps you update your address book!"}
case 2: // /createContact
return openDialog(event);
}
}
}
/**
* Opens a dialog in Google Chat.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function openDialog(event) {
return {
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "contactName"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
};
}
Python
from typing import Any, Mapping
import flask
import functions_framework
@functions_framework.http
def main(req: flask.Request) -> Mapping[str, Any]:
"""Responds to a MESSAGE event in Google Chat that includes the /createContact
slash command by opening a dialog.
Args:
req (flask.Request): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a slash command.
"""
if req.method == 'GET':
return 'Sorry, this function must be called from a Google Chat.'
request = req.get_json(silent=True)
if slash_command := request.get('message', dict()).get('slashCommand'):
command_id = slash_command['commandId']
if command_id == 1:
return {'text': 'Contact bot helps you update your address book!'}
elif command_id == 2:
return open_dialog(request)
def open_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
"""Opens a dialog in Google Chat.
Args:
request (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a slash command.
"""
return {
'action_response': {
'type': 'DIALOG',
'dialog_action': {
'dialog': {
'body': {
'sections': [
{
'header': 'Add new contact',
'widgets': [
{
'textInput': {
'label': 'Name',
'type': 'SINGLE_LINE',
'name': 'name'
}
},
{
'textInput': {
'label': 'Address',
'type': 'MULTIPLE_LINE',
'name': 'address'
}
},
{
'decoratedText': {
'text': 'Add to favorites',
'switchControl': {
'controlType': 'SWITCH',
'name': 'saveFavorite'
}
}
},
{
'decoratedText': {
'text': 'Merge with existing contacts',
'switchControl': {
'controlType': 'SWITCH',
'name': 'mergeContact',
'selected': True
}
}
},
{
'buttonList': {
'buttons': [
{
'text': 'Next',
'onClick': {
'action': {
'function': 'open_sequential_dialog'
}
}
}
]
}
}
]
}
]
}
}
}
}
}
여러 카드 인터페이스 연결
사용자 상호작용에서 카드가 두 개 이상 필요한 경우 다른 대화상자를 반환하여 같은 대화상자에서 새 카드를 열 수 있습니다.
사용자가 대화상자의 버튼을 클릭하면 앱은 다음과 같은 Event
을 수신합니다.
EventType
은(는)CARD_CLICKED
입니다.DialogEventType
은(는)SUBMIT_DIALOG
입니다.
대화상자가 이미 열려 있으므로 대화 카드에서 대화상자를 열 때처럼 onClick.action.interaction
를 지정하지 마세요. 대신 대화상자를 여는 함수의 이름으로 onClick.action.function
만 반환합니다.
이 예시에서는 채팅 앱이 다른 대화상자를 열어 대화상자 버튼 클릭으로부터 CARD_CLICKED
Event
에 응답합니다.
Node.js
// Respond to button clicks on attached cards
if(event.type === "CARD_CLICKED") {
// Open the first dialog.
if (event.common.invokedFunction == "openDialog") {
res.openDialog(event);
}
// Open the second dialog.
if (event.common.invokedFunction == "openSequentialDialog") {
res.openSequentialDialog(event);
}
}
/**
* Opens and starts a dialog that allows users to add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
res.json({
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "name"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
});
};
/**
* Opens a second dialog that allows users to add more contact details.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openSequentialDialog(event) {
res.json({
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Notes",
"type": "MULTIPLE_LINE",
"name": "notes"
}
},
{
"selectionInput": {
"type": "RADIO_BUTTON",
"label": "Contact type",
"name": "contactType",
"items": [
{
"text": "Work",
"value": "Work",
"selected": false
},
{
"text": "Personal",
"value": "Personal",
"selected": false
}
]
}
},
{
"buttonList": {
"buttons": [
{
"text": "Submit",
"onClick": {
"action": {
"function": "confirmDialogSuccess",
"parameters": [
{
"key": "confirmDialogSuccess",
"value": "confirmDialogSuccess"
}
]
}
}
}
]
},
"horizontalAlignment": "END"
}
]
}
]
}
}
}
}
});
Apps Script
/**
* Responds to a CARD_CLICKED event in Google Chat.
*
* @param {Object} event the event object from Google Chat
*/
function onCardClick(event) {
// When a user clicks a card, the app checks to see which function to run.
if (event.common.invokedFunction == "openDialog") {
return openDialog(event);
}
if (event.common.invokedFunction == "openSequentialDialog") {
return openSequentialDialog(event);
}
}
/**
* Opens and starts a dialog that allows users to add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
return {
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "contactName"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
// Specifies which function to run
// in response to the card click.
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
};
}
/**
* Opens a second dialog that allows users to add more contact details.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openSequentialDialog(event) {
return {
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Notes",
"type": "MULTIPLE_LINE",
"name": "notes"
}
},
{
"selectionInput": {
"type": "RADIO_BUTTON",
"label": "Contact type",
"name": "contactType",
"items": [
{
"text": "Work",
"value": "Work",
"selected": false
},
{
"text": "Personal",
"value": "Personal",
"selected": false
}
]
}
},
{
"buttonList": {
"buttons": [
{
"text": "Submit",
"onClick": {
"action": {
// Specifies which function to run
// in response to the card click.
"function": "receiveDialog",
"parameters": [
{
"key": "receiveDialog",
"value": "receiveDialog"
}
]
}
}
}
]
},
"horizontalAlignment": "END"
}
]
}
]
}
}
}
}
};
}
Python
from typing import Any, Mapping
import flask
import functions_framework
@functions_framework.http
def main(req: flask.Request) -> Mapping[str, Any]:
"""Responds to a MESSAGE event in Google Chat that includes the /createContact
slash command by opening a dialog.
Args:
req (flask.Request): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
if req.method == 'GET':
return 'Sorry, this function must be called from a Google Chat.'
request = req.get_json(silent=True)
if request.get('type') == 'CARD_CLICKED':
if invoked_function := request.get('common', dict()).get('invokedFunction'):
if invoked_function == 'open_dialog':
return open_dialog(request)
elif invoked_function == 'open_sequential_dialog':
return open_dialog(request)
def open_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
"""Opens a dialog in Google Chat.
Args:
request (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
return {
'action_response': {
'type': 'DIALOG',
'dialog_action': {
'dialog': {
'body': {
'sections': [
{
'header': 'Add new contact',
'widgets': [
{
'textInput': {
'label': 'Name',
'type': 'SINGLE_LINE',
'name': 'name'
}
},
{
'textInput': {
'label': 'Address',
'type': 'MULTIPLE_LINE',
'name': 'address'
}
},
{
'decoratedText': {
'text': 'Add to favorites',
'switchControl': {
'controlType': 'SWITCH',
'name': 'saveFavorite'
}
}
},
{
'decoratedText': {
'text': 'Merge with existing contacts',
'switchControl': {
'controlType': 'SWITCH',
'name': 'mergeContact',
'selected': True
}
}
},
{
'buttonList': {
'buttons': [
{
'text': 'Next',
'onClick': {
'action': {
'function': 'open_sequential_dialog'
}
}
}
]
}
}
]
}
]
}
}
}
}
}
def open_sequential_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
"""Opens a second dialog that allows users to add more contact details.
Args:
request (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
return {
'action_response': {
'type': 'DIALOG',
'dialog_action': {
'dialog': {
'body': {
'sections': [
{
'header': 'Add new contact',
'widgets': [
{
'textInput': {
'label': 'Notes',
'type': 'MULTIPLE_LINE',
'name': 'notes'
}
},
{
'selectionInput': {
'type': 'RADIO_BUTTON',
'label': 'Contact type',
'name': 'contactType',
'items': [
{
'text': 'Work',
'value': 'Work',
'selected': False
},
{
'text': 'Personal',
'value': 'Personal',
'selected': False
}
]
}
},
{
'buttonList': {
'buttons': [
{
'text': 'Submit',
'onClick': {
'action': {
'function': 'receiveDialog',
'parameters': [
{
'key': 'receiveDialog',
'value': 'receiveDialog'
}
]
}
}
}
]
},
'horizontalAlignment': 'END'
}
]
}
]
}
}
}
}
}
대화상자에서 양식 데이터 수신
사용자가 대화상자의 버튼을 클릭하면 사용자가 입력한 데이터가 Chat 앱에 제출되고 앱은 다음과 같은 Event
을 수신합니다.
EventType
은(는)CARD_CLICKED
입니다.DialogEventType
은(는)SUBMIT_DIALOG
입니다.
사용자가 대화상자에 입력하는 데이터는 Event
에서 Event.common.formInputs
로 제공됩니다. 키는 각 대화상자 위젯에 할당된 문자열 ID이고 각 위젯의 사용자 입력을 나타내는 값입니다. 다양한 객체는 서로 다른 입력 데이터 유형을 나타냅니다. 예를 들어 Event.common.formInputs.stringInputs
은 문자열 입력을 나타냅니다.
사용자가 대화상자를 제출하면 Chat 앱이 Google Chat에서 다음과 같은 Event
을 수신합니다.
JSON
{
"type": enum (EventType),
"eventTime": string,
"threadKey": string,
"message": {
object (Message)
},
"user": {
object (User)
},
"space": {
object (Space)
},
"action": {
object (FormAction)
},
"configCompleteRedirectUrl": string,
// Indicates that this event is dialog-related.
"isDialogEvent": true,
// Indicates that a user clicked a button, and all data
// they entered in the dialog is included in Event.common.formInputs.
"dialogEventType": "SUBMIT_DIALOG",
"common": {
"userLocale": string,
"hostApp": enum (HostApp),
"platform": enum (Platform),
"timeZone": {
object (TimeZone)
},
// Represents user data entered in a dialog
"formInputs": {
// Represents string data entered in a dialog, like text input fields
// and check boxes
"stringInputs": {
// An array of strings entered by the user in a dialog.
"value": [
string
]
}
},
"parameters": {
string: string,
...
},
"invokedFunction": string
}
}
앱은 event.common.formInputs.NAME.stringInputs.value[0]
에서 사용자가 처음 입력한 값에 액세스할 수 있습니다. 여기서 NAME는 TextInput
위젯의 name
필드입니다.
대화상자 양식 데이터를 수신한 후 채팅 앱은 수신 확인을 확인하거나 오류를 반환하여 응답해야 합니다. 두 작업은 모두 ActionResponse
를 반환하여 처리됩니다.
- 수신 확인을 확인하려면
"actionStatus": "OK"
이 있는ActionResponse
로 응답합니다. - 오류를 반환하려면
"actionStatus": "ERROR MESSAGE"
이 있는ActionResponse
로 응답합니다.
다음 예에서는 'name' 값이 있는지 확인합니다. 이 속성이 없으면 앱에서 오류를 반환합니다. 이 필드가 있으면 앱이 양식 데이터의 수신을 확인하고 대화상자를 닫습니다.
Node.js
/**
* Checks for a form input error, the absence of
* a "name" value, and returns an error if absent.
* Otherwise, confirms successful receipt of a dialog.
*
* Confirms successful receipt of a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function receiveDialog(event) {
// Checks to make sure the user entered a name
// in a dialog. If no name value detected, returns
// an error message.
if (event.common.formInputs.WIDGET_NAME.stringInputs.value[0] == "") {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": {
"statusCode": "OK",
"userFacingMessage": "Don't forget to name your new contact!"
}
}
};
// Otherwise the app indicates that it received
// form data from the dialog. Any value other than "OK"
// gets returned as an error. "OK" is interpreted as
// code 200, and the dialog closes.
} else {
res.json({
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "OK"
}
}
});
}
}
Apps Script
/**
* Checks for a form input error, the absence of
* a "name" value, and returns an error if absent.
* Otherwise, confirms successful receipt of a dialog.
*
* Confirms successful receipt of a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function receiveDialog(event) {
// Checks to make sure the user entered a name
// in a dialog. If no name value detected, returns
// an error message.
//
// If using the Apps Script V8 runtime, remove the
// empty string from the name of the widget. Instead, specify:
// event.common.formInputs.WIDGET_NAME.stringInputs.value[0]
if (event.common.formInputs.WIDGET_NAME[""].stringInputs.value[0] == "") {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "Don't forget to name your new contact!"
}
}
};
// Otherwise the app indicates that it received
// form data from the dialog. Any value other than "OK"
// gets returned as an error. "OK" is interpreted as
// code 200, and the dialog closes.
} else {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "OK"
}
}
};
}
}
Python
def receive_dialog(event: Mapping[str, Any]) -> Mapping[str, Any]:
"""Checks for a form input error, the absence of a "name" value, and returns
an error if absent. Otherwise, confirms successful receipt of a dialog.
Args:
event (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: the response.
"""
if common := event.get('common'):
if form_inputs := common.get('formInputs'):
if contact_name := form_inputs.get('WIDGET_NAME'):
if string_inputs := contact_name.get('stringInputs')
if name := string_inputs.get('value'):
return {
'actionResponse': {
'type': 'DIALOG',
'dialogAction': {
'actionStatus': 'OK'
}
}
}
else:
return {
'actionResponse': {
'type': 'DIALOG',
'dialogAction': {
'actionStatus': 'Don't forget to name your new contact!'
}
}
}
문자 메시지 또는 카드로 대화상자에 응답
간단한 SMS나 카드로 대화상자에 응답하려면 서비스 계정으로 인증한 다음 Google Chat REST API를 사용해 spaces.messages.create
을 호출하여 비동기 답장을 보내세요.
전체 예: 채팅 앱 관리자인 Rolodex 담당자
이 예시에서는 채팅 앱이 대화상자를 열어 사용자가 이름, 이메일, 주소와 같은 연락처 세부정보를 추가할 수 있도록 합니다.
Node.js
/**
* Responds to messages that have links whose URLs
* match URL patterns configured for link previews.
*
* @param {Object} event The event object from Chat
* API.
*
* @return {Object} Response from the Chat app
* attached to the message with the previewed link.
*/
exports.onMessage = function onMessage(req, res) {
// Store the Google Chat event as a variable.
var event = req.body;
if (req.method === "GET" || !event.message) {
res.send("Hello! This function is meant to be used in a Google Chat " +
"Space.");
}
// Checks for the presence of event.message.slashCommand.
// If the slash command is "/help", responds with a text message.
// If the slash command is "/createContact", opens a dialog.
if (event.message.slashCommand) {
switch (event.message.slashCommand.commandId) {
case 1: // /help
res.json({"text": "Contact bot helps you update your address book!"});
case 2: // /createContact
res.openDialog(event);
}
}
// If the Chat app doesn"t detect a slash command, it responds
// with a card that prompts the user to add a contact
else {
res.json({
"cardsV2": [{
"cardId": "addContact",
"card": {
"header": {
"title": "Rolodex",
"subtitle": "Manage your contacts!",
"imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
"imageType": "CIRCLE"
},
"sections": [
{
"widgets": [
{
"buttonList": {
"buttons": [
{
"text": "Add Contact",
"onClick": {
"action": {
"function": "openDialog",
"interaction": "OPEN_DIALOG"
}
}
}
]
}
}
]
}
]
}
}]
});
}
// Respond to button clicks on attached cards
if(event.type === "CARD_CLICKED") {
if (event.common.invokedFunction == "openDialog") {
res.openDialog(event);
}
if (event.common.invokedFunction == "openSequentialDialog") {
res.openSequentialDialog(event);
}
if (event.common.invokedFunction == "confirmDialogSuccess") {
res.confirmDialogSuccess(event);
}
}
};
/**
* Opens and starts a dialog that allows users to add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
res.json({
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "name"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
});
};
/**
* Opens a second dialog that allows users to add more contact details.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openSequentialDialog(event) {
res.json({
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Notes",
"type": "MULTIPLE_LINE",
"name": "notes"
}
},
{
"selectionInput": {
"type": "RADIO_BUTTON",
"label": "Contact type",
"name": "contactType",
"items": [
{
"text": "Work",
"value": "Work",
"selected": false
},
{
"text": "Personal",
"value": "Personal",
"selected": false
}
]
}
},
{
"buttonList": {
"buttons": [
{
"text": "Submit",
"onClick": {
"action": {
"function": "confirmDialogSuccess",
"parameters": [
{
"key": "confirmDialogSuccess",
"value": "confirmDialogSuccess"
}
]
}
}
}
]
},
"horizontalAlignment": "END"
}
]
}
]
}
}
}
}
});
}
/**
* Checks for a form input error, the absence of
* a "name" value, and returns an error if absent.
* Otherwise, confirms successful receipt of a dialog.
*
* Confirms successful receipt of a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function receiveDialog(event) {
// Checks to make sure the user entered a name
// in a dialog. If no name value detected, returns
// an error message.
if (event.common.formInputs.contactName.stringInputs.value[0] == "") {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": {
"statusCode": "OK",
"userFacingMessage": "Don't forget to name your new contact!"
}
}
};
// Otherwise the app indicates that it received
// form data from the dialog. Any value other than "OK"
// gets returned as an error. "OK" is interpreted as
// code 200, and the dialog closes.
} else {
res.json({
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "OK"
}
}
});
}
}
Apps Script
/**
* Responds to a MESSAGE event in Google Chat.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in response to a slash command
* or a card"s button click.
*/
function onMessage(event) {
// Checks for the presence of event.message.slashCommand.
// If the slash command is "/help", responds with a text message.
// If the slash command is "/createContact", opens a dialog.
if (event.message.slashCommand) {
switch (event.message.slashCommand.commandId) {
case 1: // /help
return {"text": "Contact bot helps you update your address book!"}
case 2: // /createContact
return openDialog(event);
}
}
// If the Chat app doesn"t detect a slash command, it responds
// with a card that prompts the user to add a contact
else {
return {
"cardsV2": [{
"cardId": "addContact",
"card": {
"header": {
"title": "Rolodex",
"subtitle": "Manage your contacts!",
"imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
"imageType": "CIRCLE"
},
"sections": [
{
"widgets": [
{
"buttonList": {
"buttons": [
{
"text": "Add Contact",
"onClick": {
"action": {
"function": "openDialog",
"interaction": "OPEN_DIALOG"
}
}
}
]
}
}
]
}
]
}
}]
};
}
}
/**
* Responds to a CARD_CLICKED event in Google Chat.
*
* @param {Object} event the event object from Google Chat
*/
function onCardClick(event) {
if (event.common.invokedFunction == "openDialog") {
return openDialog(event);
}
if (event.common.invokedFunction == "openSequentialDialog") {
return openSequentialDialog(event);
}
if (event.common.invokedFunction == "receiveDialog") {
return receiveDialog(event);
}
}
/**
* Opens and starts a dialog that allows users to add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
return {
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Name",
"type": "SINGLE_LINE",
"name": "contactName"
}
},
{
"textInput": {
"label": "Address",
"type": "MULTIPLE_LINE",
"name": "address"
}
},
{
"decoratedText": {
"text": "Add to favorites",
"switchControl": {
"controlType": "SWITCH",
"name": "saveFavorite"
}
}
},
{
"decoratedText": {
"text": "Merge with existing contacts",
"switchControl": {
"controlType": "SWITCH",
"name": "mergeContact",
"selected": true
}
}
},
{
"buttonList": {
"buttons": [
{
"text": "Next",
"onClick": {
"action": {
"function": "openSequentialDialog"
}
}
}
]
}
}
]
}
]
}
}
}
}
};
}
/**
* Opens a second dialog that allows users to add more contact details.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openSequentialDialog(event) {
return {
"action_response": {
"type": "DIALOG",
"dialog_action": {
"dialog": {
"body": {
"sections": [
{
"header": "Add new contact",
"widgets": [
{
"textInput": {
"label": "Notes",
"type": "MULTIPLE_LINE",
"name": "notes"
}
},
{
"selectionInput": {
"type": "RADIO_BUTTON",
"label": "Contact type",
"name": "contactType",
"items": [
{
"text": "Work",
"value": "Work",
"selected": false
},
{
"text": "Personal",
"value": "Personal",
"selected": false
}
]
}
},
{
"buttonList": {
"buttons": [
{
"text": "Submit",
"onClick": {
"action": {
"function": "receiveDialog",
"parameters": [
{
"key": "receiveDialog",
"value": "receiveDialog"
}
]
}
}
}
]
},
"horizontalAlignment": "END"
}
]
}
]
}
}
}
}
};
}
/**
* Checks for a form input error, the absence of
* a "name" value, and returns an error if absent.
* Otherwise, confirms successful receipt of a dialog.
*
* Confirms successful receipt of a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function receiveDialog(event) {
// Checks to make sure the user entered a name
// in a dialog. If no name value detected, returns
// an error message.
if (event.common.formInputs.contactName.stringInputs.value[0] == "") {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "Don't forget to name your new contact!"
}
}
};
// Otherwise the app indicates that it received
// form data from the dialog. Any value other than "OK"
// gets returned as an error. "OK" is interpreted as
// code 200, and the dialog closes.
} else {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "OK"
}
}
};
}
}
Python
from typing import Any, Mapping
import flask
import functions_framework
@functions_framework.http
def main(req: flask.Request) -> Mapping[str, Any]:
"""Responds to a MESSAGE event in Google Chat that includes the /createContact
slash command by opening a dialog.
Args:
req (flask.Request): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
if req.method == 'GET':
return 'Sorry, this function must be called from a Google Chat.'
request = req.get_json(silent=True)
if request.get('type') == 'CARD_CLICKED':
invoked_function = request.get('common', dict()).get('invokedFunction')
if invoked_function == 'open_dialog':
return open_dialog(request)
elif invoked_function == 'open_sequential_dialog':
return open_dialog(request)
elif invoked_function == "receive_dialog":
return receive_dialog(request)
else:
return {
'cardsV2': [{
'cardId': 'addContact',
'card': {
'header': {
'title': 'Rolodex',
'subtitle': 'Manage your contacts!',
'imageUrl': 'https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png',
'imageType': 'CIRCLE'
},
'sections': [
{
'widgets': [
{
'buttonList': {
'buttons': [
{
'text': 'Add Contact',
'onClick': {
'action': {
'function': 'open_dialog',
'interaction': 'OPEN_DIALOG'
}
}
}
]
}
}
]
}
]
}
}]
}
def open_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
"""Opens a dialog in Google Chat.
Args:
request (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
return {
'action_response': {
'type': 'DIALOG',
'dialog_action': {
'dialog': {
'body': {
'sections': [
{
'header': 'Add new contact',
'widgets': [
{
'textInput': {
'label': 'Name',
'type': 'SINGLE_LINE',
'name': 'name'
}
},
{
'textInput': {
'label': 'Address',
'type': 'MULTIPLE_LINE',
'name': 'address'
}
},
{
'decoratedText': {
'text': 'Add to favorites',
'switchControl': {
'controlType': 'SWITCH',
'name': 'saveFavorite'
}
}
},
{
'decoratedText': {
'text': 'Merge with existing contacts',
'switchControl': {
'controlType': 'SWITCH',
'name': 'mergeContact',
'selected': True
}
}
},
{
'buttonList': {
'buttons': [
{
'text': 'Next',
'onClick': {
'action': {
'function': 'open_sequential_dialog'
}
}
}
]
}
}
]
}
]
}
}
}
}
}
def open_sequential_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
"""Opens a second dialog that allows users to add more contact details.
Args:
request (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: open a Dialog in response to a card's button click.
"""
return {
'action_response': {
'type': 'DIALOG',
'dialog_action': {
'dialog': {
'body': {
'sections': [
{
'header': 'Add new contact',
'widgets': [
{
'textInput': {
'label': 'Notes',
'type': 'MULTIPLE_LINE',
'name': 'notes'
}
},
{
'selectionInput': {
'type': 'RADIO_BUTTON',
'label': 'Contact type',
'name': 'contactType',
'items': [
{
'text': 'Work',
'value': 'Work',
'selected': False
},
{
'text': 'Personal',
'value': 'Personal',
'selected': False
}
]
}
},
{
'buttonList': {
'buttons': [
{
'text': 'Submit',
'onClick': {
'action': {
'function': 'receive_dialog',
'parameters': [
{
'key': 'receiveDialog',
'value': 'receiveDialog'
}
]
}
}
}
]
},
'horizontalAlignment': 'END'
}
]
}
]
}
}
}
}
}
def receive_dialog(event: Mapping[str, Any]) -> Mapping[str, Any]:
"""Checks for a form input error, the absence of a "name" value, and returns
an error if absent. Otherwise, confirms successful receipt of a dialog.
Args:
event (Mapping[str, Any]): the event object from Chat API.
Returns:
Mapping[str, Any]: the response.
"""
if event.get('common', dict()) \
.get('formInputs', dict()).get('contactName', dict()) \
.get('stringInputs').get('value', list()):
return {
'actionResponse': {
'type': 'DIALOG',
'dialogAction': {
'actionStatus': 'OK'
}
}
}
else:
return {
'actionResponse': {
'type': 'DIALOG',
'dialogAction': {
'actionStatus': "Don't forget to name your new contact!"
}
}
}
제한사항 및 고려사항
다음 대화상자 제한사항 및 고려사항에 유의하세요.
- 지원 중단된 카드 v1은 대화상자에서 지원되지 않습니다. 대신 카드 v2를 사용하세요.
- 일부 카드 위젯 및 속성은 아직 지원되지 않으며 대화상자 카드에 추가하면 렌더링되지 않습니다 (빠른 지원을 위해 노력하고 있음). 아직 지원되지 않는 위젯 및 속성을 확인하려면 Chat REST API 참조의 카드를 참고하세요. 위젯 또는 속성이 지원되지 않는 경우 설명에는 "Chat 앱에서 지원되지 않음'이라는 문구가 포함됩니다.
- 다음 사용자 입력 데이터 유형은 대화상자에서 지원되지 않습니다 (하지만 곧 지원될 예정임).
- 서버 생성 자동 완성 추천은 지원되지 않지만 대화상자 응답과 함께 반환되는 추천은 지원됩니다.
onChangeAction
(스위치 전환과 같이 위젯이 변경될 때 실행할Action
을 지정하는 일부 카드 위젯의 속성(예:SwitchControl
))은 지원되지 않습니다.
대화상자 디버그
대화상자를 구현할 때 앱의 로그를 읽어 Chat 앱을 디버그해야 할 수 있습니다. 로그를 읽으려면 Google Cloud Console에서 로그 탐색기로 이동하세요.