dialogflow-prebuilt-agents/client_side_messenger/retail_assistant/retail_assistant.js (362 lines of code) (raw):
// listen to df-response
window.addEventListener('df-response-received', (event) => {
event.preventDefault(); // Dialogflow Messenger won't handle the responses.
// check if flow's responses is available
let flowPayloads = extractFlowPayloads(event.detail.raw);
// check if generative responses is available
let genResponses = extractGenerativeResponses(event.detail.raw);
if (genResponses) {
try {
const dfMessenger = document.querySelector('df-messenger');
for (const res of genResponses) {
if (res.hasOwnProperty('agentUtterance')) {
dfMessenger.renderCustomText(res.agentUtterance.text, true);
} else if (res.hasOwnProperty('toolUse')) {
let customPayload = extractCustomPayload(res.toolUse);
if (customPayload) {
dfMessenger.renderCustomCard(customPayload[0]);
}
} else if (
res && res.flowInvocation &&
res.flowInvocation.flowState == 'OUTPUT_STATE_OK') {
for (const flowPayload of flowPayloads) {
dfMessenger.renderCustomCard(flowPayload.payload.richContent[0]);
}
}
}
} catch (err) {
console.log('error in generative response: ', err);
}
}
});
/**
* Extract custom payload from dfTool
* @param {Object!} dfTool
* @return {?Object | boolean}
*/
function extractCustomPayload(dfTool) {
try {
if (dfTool.outputActionParameters && dfTool.outputActionParameters['200'] &&
dfTool.outputActionParameters['200'].payload &&
dfTool.outputActionParameters['200'].payload.richContent) {
return dfTool.outputActionParameters['200'].payload.richContent;
} else {
return false;
}
} catch (err) {
console.log('error in tool response: ', err);
}
}
/**
* Extract generative responses from dfResponse
* @param {Object!} dfResponse
* @return {?Object | boolean}
*/
function extractGenerativeResponses(dfResponse) {
try {
let dfActions =
dfResponse.queryResult.generativeInfo.actionTracingInfo.actions;
let filteredActions =
dfActions.filter(action => !action.hasOwnProperty('userUtterance'));
if (filteredActions.length > 0) return filteredActions;
return false;
} catch (err) {
return false;
}
}
/**
* Extract flow payloads from dfResponse
* @param {Object!} dfResponse
* @return {?Object | boolean}
*/
function extractFlowPayloads(dfResponse) {
try {
let flowResponses = dfResponse.queryResult.responseMessages;
let flowPayloads =
flowResponses.filter(response => response.hasOwnProperty('payload'));
if (flowPayloads.length > 0) return flowPayloads;
return false;
} catch (err) {
return false;
}
}
/**
* Setup html for custom components
*/
function setupHtml() {
console.log('setting up html');
// Create the review container
const reviewContainer = document.createElement('div');
reviewContainer.id = 'review-template';
reviewContainer.classList.add('review-container');
reviewContainer.style.display = 'none';
// Create the review element
const review = document.createElement('div');
review.classList.add('review');
// Create the review header
const reviewHeader = document.createElement('div');
reviewHeader.classList.add('review-header');
// Create the user avatar
const userAvatar = document.createElement('div');
userAvatar.classList.add('user-avatar');
// Create the user info
const userInfo = document.createElement('div');
userInfo.classList.add('user-info');
// Create the user name
const userName = document.createElement('div');
userName.classList.add('user-name');
// Create the user rating
const userRating = document.createElement('div');
userRating.classList.add('user-rating');
// Create the stars
const stars = document.createElement('span');
stars.classList.add('stars');
// Create the rating score
const ratingScore = document.createElement('span');
ratingScore.classList.add('rating-score');
// Create the review text
const reviewText = document.createElement('p');
reviewText.classList.add('review-text');
// Create the review title
const reviewTitle = document.createElement('p');
reviewTitle.classList.add('review-title');
// Append elements to their parents
userRating.appendChild(stars);
userRating.appendChild(ratingScore);
userInfo.appendChild(userName);
userInfo.appendChild(userRating);
reviewHeader.appendChild(userAvatar);
reviewHeader.appendChild(userInfo);
review.appendChild(reviewHeader);
review.appendChild(reviewText);
review.appendChild(reviewTitle);
reviewContainer.appendChild(review);
// Insert the review container next to the <df-messenger>
// dfMessenger.parentNode.insertBefore(reviewContainer,
// dfMessenger.nextSibling);
const chatWindowWrapper = document.querySelector('.chat-window-wrapper');
chatWindowWrapper.append(reviewContainer);
console.log({chatWindowWrapper});
}
// Element for retail example. Contact: bpataki@google.com
class RetailTemplate extends HTMLElement {
constructor() {
super();
this.dfPayload = null;
this.dfResponseId = null;
this.renderRoot = this.attachShadow({mode: 'open'});
}
/** Web component Lifecycle method. */
connectedCallback() {
this.renderRoot.appendChild(this._renderStyles());
this.renderRoot.appendChild(this._renderContent());
}
/**
* Render styles.
* @return {HTMLElement!}
*/
_renderStyles() {
const styles = document.createElement('style');
styles.textContent = `
.wrapper {
color: var(--df-messenger-default-text-color);
}
.title-link {
text-decoration: none;
color: var(--df-messenger-default-text-color);
}
.item-list {
display: flex;
width: 100%;
gap: 15px;
/* No flex wrap. */
overflow-x: auto;
}
.item {
width: 33%;
padding: 14px;
}
.item-title {
margin-top: 0;
font-size: 1.2em;
min-height: 50px;
}
.item-price {
font-size: 1.2em;
margin-top: 20px;
}
.item-image {
border-radius: 8px;
max-width: 250px;
max-height: 250px;
// box-shadow: 5px 0 20px 0 rgba(0, 0, 0, 0.1)
}
.item-description, .item-details {
padding: 8px 0;
}
.item-table {
font-size: var(--df-messenger-default-font-size);
}
.item-table td {
padding: 2px 8px;
}
.title {
display: none;
}
`;
return styles;
}
/**
* Render content.
* @return {HTMLElement!}
*/
_renderContent() {
const content = document.createElement('div');
content.classList.add('wrapper');
const itemList = document.createElement('div');
itemList.classList.add('item-list');
for (const item of this.dfPayload.items) {
itemList.appendChild(this._renderItem(item));
}
content.appendChild(itemList);
return content;
}
/**
* Render content.
* @param {Object!} itemPayload
* @return {HTMLElement!}
*/
_renderItem(itemPayload) {
let item = document.createElement('div');
item.classList.add('item');
console.log(itemPayload);
const itemData = itemPayload.product;
// console.log(itemData);
let title = document.createElement('h2');
title.classList.add('item-title');
title.textContent = itemData.title;
let imageWrapper = document.createElement('div');
let image = document.createElement('img');
image.classList.add('item-image');
image.src = itemData.images[0].uri;
imageWrapper.appendChild(image);
let price = document.createElement('div');
price.classList.add('item-price');
if (itemData.priceInfo) {
price.textContent = `${itemData.priceInfo.price} ${
itemData.priceInfo.currencyCode || '$'}`;
}
let description;
if (itemData.description) {
description = document.createElement('div');
description.classList.add('item-description');
description.textContent = itemData.description;
}
let details;
if (itemData.categories) {
details = document.createElement('div');
details.classList.add('item-details');
details.textContent = itemData.categories;
}
item.appendChild(title);
item.appendChild(imageWrapper);
// if (itemData.description) {
// item.appendChild(description); // uncomment to show the description
// }
if (itemData.categories) {
item.appendChild(details);
}
if (itemData.priceInfo) {
item.appendChild(price);
}
// item.appendChild(infoLink);
return item;
}
}
// Custom Template for rendering Customer Reviews.
class ReviewTemplate extends HTMLElement {
constructor() {
super();
this.dfPayload = null;
this.dfResponseId = null;
this.renderRoot = this.attachShadow({mode: 'open'});
}
/** Web component Lifecycle method. */
connectedCallback() {
this.renderRoot.appendChild(this._renderStyles());
this.renderRoot.appendChild(this._renderContent());
}
/**
* Render styles.
* @return {HTMLElement!}
*/
_renderStyles() {
const styles = document.createElement('style');
styles.textContent = `
.wrapper {
color: var(--df-messenger-default-text-color);
}
.title-link {
text-decoration: none;
color: var(--df-messenger-default-text-color);
}
.item-list {
display: flex;
width: 100%;
gap: 15px;
/* No flex wrap. */
overflow-x: auto;
}
.item {
width: 50%;
background: #E8F0FE;
border-radius: 15px;
padding: 16px;
}
.review {
background: #fff;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
padding: 20px;
// width: 30%; /* Adjust as necessary for your layout */
// margin: 10px;
box-sizing: border-box;
}
.review-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.user-avatar {
background-color: #0077CC;
color: #fff;
font-weight: bold;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.user-info {
flex-grow: 1;
}
.user-name {
font-size: 1.1em;
color: #333;
}
.user-rating {
display: flex;
align-items: center;
}
.stars {
color: #FFD700;
font-size: 1em;
}
.rating-score {
font-size: 0.8em;
color: #777;
margin-left: 5px;
}
.review-text {
font-size: 0.9em;
color: #555;
}
.review-title {
font-size: 0.9em;
font-weight: bold;
color: #555;
}
.review-text a {
color: #0077CC;
text-decoration: none;
}
`;
return styles;
}
/**
* Render content.
* @return {HTMLElement!}
*/
_renderContent() {
const content = document.createElement('div');
content.classList.add('wrapper');
const itemList = document.createElement('div');
itemList.classList.add('item-list');
const numReviews = this.dfPayload.items.length;
for (const item of this.dfPayload.items) {
itemList.appendChild(this._renderItem(item, numReviews));
}
content.appendChild(itemList);
return content;
}
/**
* Render content.
* @param {Object!} itemPayload
* @param {number} numReviews
* @return {HTMLElement!}
*/
_renderItem(itemPayload, numReviews) {
const item = document.createElement('div');
console.log(itemPayload);
// Clone our template node, replace the values, and re-insert into DOM.
let revTemplate =
document.getElementById('review-template').cloneNode(true);
revTemplate.id = '';
revTemplate.style.display = 'block';
item.appendChild(revTemplate);
let userAvatar = revTemplate.getElementsByClassName('user-avatar')[0];
userAvatar.textContent = itemPayload.user[0];
let userName = revTemplate.getElementsByClassName('user-name')[0];
userName.textContent = itemPayload.user;
let userRating = revTemplate.getElementsByClassName('rating-score')[0];
userRating.textContent = '(' + itemPayload.rating + '/5)';
let userDesc = revTemplate.getElementsByClassName('review-text')[0];
userDesc.textContent = itemPayload.desc;
let prodTitle = revTemplate.getElementsByClassName('review-title')[0];
prodTitle.textContent = 'Item: ' + itemPayload.title;
let stars = revTemplate.getElementsByClassName('stars')[0];
let numFilled = itemPayload.rating;
let numEmpty = 5 - numFilled;
for (let i = 0; i < itemPayload.rating; i++) {
stars.textContent += '★';
}
for (let i = 0; i < numEmpty; i++) {
stars.textContent += '☆';
}
return item;
}
}
// Enable custom elements.
(function() {
customElements.define('retail-template', RetailTemplate);
customElements.define('review-template', ReviewTemplate);
})();
setupHtml();