From 1452ff424e64cb22dc443935e545ecc85a6eeac6 Mon Sep 17 00:00:00 2001 From: Mohammad Al-Ansari Date: Thu, 23 Jan 2025 19:45:05 +0000 Subject: [PATCH 1/4] refactor: Refactored UI, consolidated sample generation code updated add-on name add-on name --- gmail-sentiment-analysis/Cards.gs | 50 +++++------ gmail-sentiment-analysis/Code.gs | 6 +- gmail-sentiment-analysis/Gmail.gs | 42 ++++++++- gmail-sentiment-analysis/Samples.gs | 55 ------------ gmail-sentiment-analysis/Vertex.gs | 105 ++++++----------------- gmail-sentiment-analysis/appsscript.json | 16 ++-- 6 files changed, 98 insertions(+), 176 deletions(-) delete mode 100644 gmail-sentiment-analysis/Samples.gs diff --git a/gmail-sentiment-analysis/Cards.gs b/gmail-sentiment-analysis/Cards.gs index a520eda8c..dde0a71fc 100644 --- a/gmail-sentiment-analysis/Cards.gs +++ b/gmail-sentiment-analysis/Cards.gs @@ -1,5 +1,5 @@ /* -Copyright 2024 Google LLC +Copyright 2024-2025 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,52 +20,46 @@ limitations under the License. * @return {CardService.Card} The card to show to the user. */ -function buildCard_GmailHome(notifyOk = false) { +function buildHomepageCard() { const imageUrl = 'https://fonts.gstatic.com/s/i/googlematerialicons/dynamic_feed/v6/black-24dp/1x/gm_dynamic_feed_black_24dp.png'; const cardHeader = CardService.newCardHeader() .setImageUrl(imageUrl) .setImageStyle(CardService.ImageStyle.CIRCLE) - .setTitle("Analyze your GMail"); + .setTitle("Analyze your Gmail"); - const action = CardService.newAction().setFunctionName('analyzeSentiment'); - const button = CardService.newTextButton() - .setText('Identify angry customers') - .setOnClickAction(action) - .setTextButtonStyle(CardService.TextButtonStyle.FILLED); + const analyzeSentimentAction = CardService.newAction().setFunctionName('analyzeSentiment'); + const analyzeSentimentBtn = CardService.newTextButton() + .setText('Analyze emails') + .setOnClickAction(analyzeSentimentAction) + .setTextButtonStyle(CardService.TextButtonStyle.FILLED) + .setBackgroundColor('#FF0000'); + + const generateSampleEmailAction = CardService.newAction().setFunctionName('generateSampleEmails'); - const generateSampleEmailsAction = CardService.newAction() - .setFunctionName('generateSampleEmails'); const generateSampleEmailsBtn = CardService.newTextButton() .setText('Generate sample emails') - .setOnClickAction(generateSampleEmailsAction) + .setOnClickAction(generateSampleEmailAction) .setTextButtonStyle(CardService.TextButtonStyle.FILLED) .setBackgroundColor('#34A853'); - const buttonSet = CardService.newButtonSet().addButton(button).addButton(generateSampleEmailsBtn); + const buttonSet = CardService.newButtonSet().addButton(generateSampleEmailsBtn).addButton(analyzeSentimentBtn); const section = CardService.newCardSection() - .setHeader("Emails sentiment analysis") .addWidget(buttonSet); const card = CardService.newCardBuilder() .setHeader(cardHeader) .addSection(section); - /** - * This builds the card that contains the footer that informs - * the user about the successful execution of the Add-on. - */ - if (notifyOk) { - const fixedFooter = CardService.newFixedFooter() - .setPrimaryButton( - CardService.newTextButton() - .setText("Analysis complete") - .setOnClickAction( - CardService.newAction() - .setFunctionName( - "buildCard_GmailHome"))); - card.setFixedFooter(fixedFooter); - } return card.build(); } + +function buildNotificationResponse(notificationText) { + const notification = CardService.newNotification().setText(notificationText); + + const actionResponse = CardService.newActionResponseBuilder() + .setNotification(notification); + + return actionResponse.build(); +} \ No newline at end of file diff --git a/gmail-sentiment-analysis/Code.gs b/gmail-sentiment-analysis/Code.gs index b599ca2cf..fd0b8816d 100644 --- a/gmail-sentiment-analysis/Code.gs +++ b/gmail-sentiment-analysis/Code.gs @@ -18,6 +18,6 @@ limitations under the License. * Callback for rendering the homepage card. * @return {CardService.Card} The card to show to the user. */ -function onHomepage(e) { - return buildCard_GmailHome(); -} +function onHomepageTrigger(e) { + return buildHomepageCard(); +} \ No newline at end of file diff --git a/gmail-sentiment-analysis/Gmail.gs b/gmail-sentiment-analysis/Gmail.gs index 64ac50675..98487a8ec 100644 --- a/gmail-sentiment-analysis/Gmail.gs +++ b/gmail-sentiment-analysis/Gmail.gs @@ -1,5 +1,5 @@ /* -Copyright 2024 Google LLC +Copyright 2024-2025 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ limitations under the License. function analyzeSentiment() { analyzeAndLabelEmailSentiment(); - return buildCard_GmailHome(true); + return buildNotificationResponse("Successfully completed sentiment analysis"); } /** @@ -63,3 +63,41 @@ function analyzeAndLabelEmailSentiment() { function isNegativeSentiment(text) { return processSentiment(text) === 'negative'; } + +/** + * Create sample emails + */ +function generateSampleEmails() { + // Get active user's email + const userEmail = Session.getActiveUser().getEmail(); + + // Send emails + GmailApp.sendEmail( + userEmail, + "Thank you for amazing service!", + "Hi, I really enjoyed working with you. Thank you again!", + { + name: 'Customer A', + }, + ); + + GmailApp.sendEmail( + userEmail, + "Request for information", + "Hello, I need more information on your recent product launch. Thank you.", + { + name: 'Customer B', + }, + ); + + GmailApp.sendEmail( + userEmail, + "Complaint!", + "Hello, You are late in delivery, again. Please contact me ASAP before I cancel our subscription.", + { + name: 'Customer C', + }, + ); + + return buildNotificationResponse("Successfully generated sample emails"); +} \ No newline at end of file diff --git a/gmail-sentiment-analysis/Samples.gs b/gmail-sentiment-analysis/Samples.gs deleted file mode 100644 index 3c906772b..000000000 --- a/gmail-sentiment-analysis/Samples.gs +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2025 Google LLC - -Licensed under the Apache License, Version 2.0 (the 'License'); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an 'AS IS' BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/** - * Create sample emails - */ -function generateSampleEmails() { - // Get active user's email - const userEmail = Session.getActiveUser().getEmail(); - - // Send emails - GmailApp.sendEmail( - userEmail, - 'Thank you for amazing service!', - 'Hi, I really enjoyed working with you. Thank you again!', - { - name: 'Customer A', - }, - ); - - GmailApp.sendEmail( - userEmail, - 'Request for information', - 'Hello, I need more information on your recent product launch. Thank you.', - { - name: 'Customer B', - }, - ); - - GmailApp.sendEmail( - userEmail, - 'Complaint!', - '', - { - name: 'Customer C', - htmlBody: `

Hello, You are late in delivery, again.

-

Please contact me ASAP before I cancel our subscription.

`, - }, - ); - - console.log('Sample email generation completed.') -} diff --git a/gmail-sentiment-analysis/Vertex.gs b/gmail-sentiment-analysis/Vertex.gs index 7c01b7a8e..6018fe6ae 100644 --- a/gmail-sentiment-analysis/Vertex.gs +++ b/gmail-sentiment-analysis/Vertex.gs @@ -1,82 +1,27 @@ -/* -Copyright 2024 Google LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -const PROJECT_ID = '[ADD YOUR GCP PROJECT ID HERE]'; -const VERTEX_AI_LOCATION = 'us-central1'; -const MODEL_ID = 'gemini-1.5-pro-002'; - -/** - * Packages prompt and necessary settings, then sends a request to - * Vertex API. - * A check is performed to see if the response from Vertex AI contains FALSE as a value. - * Returns the outcome of that check which is a boolean. - * - * @param emailText - Email message that is sent to the model. - */ - -function processSentiment(emailText) { - const prompt = `Analyze the sentiment of the following message: ${emailText}`; - - const request = { - "contents": [{ - "role": "user", - "parts": [{ - "text": prompt, - }] - }], - "generationConfig": { - "temperature": 0.9, - "maxOutputTokens": 1024, - "responseMimeType": "application/json", - "responseSchema": { - "type": "object", - "properties": { - "response": { - "type": "string", - "enum": [ - "positive", - "negative", - "neutral", - ] - } - } +{ + "timeZone": "America/Toronto", + "oauthScopes": [ + "https://www.googleapis.com/auth/script.external_request", + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/script.locale", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/gmail.addons.execute", + "https://www.googleapis.com/auth/gmail.labels", + "https://www.googleapis.com/auth/gmail.modify" + ], + "addOns": { + "common": { + "name": "Sentiment Analysis", + "logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/dynamic_feed/v6/black-24dp/1x/gm_dynamic_feed_black_24dp.png", + "useLocaleFromApp": true + }, + "gmail": { + "homepageTrigger": { + "runFunction": "onHomepageTrigger", + "enabled": true } } - }; - - const fetchOptions = { - method: 'POST', - headers: { - 'Authorization': `Bearer ${ScriptApp.getOAuthToken()}` - }, - contentType: 'application/json', - muteHttpExceptions: true, - payload: JSON.stringify(request), - } - - const url = - `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/` + - `projects/${PROJECT_ID}/` + - `locations/${VERTEX_AI_LOCATION}/` + - `publishers/google/` + - `models/${MODEL_ID}:generateContent`; - - const response = UrlFetchApp.fetch(url, fetchOptions); - const payload = JSON.parse(response.getContentText()); - const text = JSON.parse(payload.candidates[0].content.parts[0].text); - - return text.response; -} + }, + "exceptionLogging": "STACKDRIVER", + "runtimeVersion": "V8" +} \ No newline at end of file diff --git a/gmail-sentiment-analysis/appsscript.json b/gmail-sentiment-analysis/appsscript.json index 803c363e8..6018fe6ae 100644 --- a/gmail-sentiment-analysis/appsscript.json +++ b/gmail-sentiment-analysis/appsscript.json @@ -1,27 +1,27 @@ { - "timeZone": "America/Los_Angeles", + "timeZone": "America/Toronto", "oauthScopes": [ + "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/script.locale", + "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/gmail.labels", - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/script.external_request", - "https://www.googleapis.com/auth/script.locale", - "https://www.googleapis.com/auth/userinfo.email" + "https://www.googleapis.com/auth/gmail.modify" ], "addOns": { "common": { - "name": "Productivity toolbox", + "name": "Sentiment Analysis", "logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/dynamic_feed/v6/black-24dp/1x/gm_dynamic_feed_black_24dp.png", "useLocaleFromApp": true }, "gmail": { "homepageTrigger": { - "runFunction": "onHomepage", + "runFunction": "onHomepageTrigger", "enabled": true } } }, "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8" -} +} \ No newline at end of file From aa83438a0d8b47ee4e1ddd1be74e75b4a44ec8b0 Mon Sep 17 00:00:00 2001 From: Mohammad Al-Ansari Date: Thu, 23 Jan 2025 19:53:55 +0000 Subject: [PATCH 2/4] fix: reverted sample code to previous version --- gmail-sentiment-analysis/Gmail.gs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gmail-sentiment-analysis/Gmail.gs b/gmail-sentiment-analysis/Gmail.gs index 98487a8ec..0a27a483e 100644 --- a/gmail-sentiment-analysis/Gmail.gs +++ b/gmail-sentiment-analysis/Gmail.gs @@ -74,8 +74,8 @@ function generateSampleEmails() { // Send emails GmailApp.sendEmail( userEmail, - "Thank you for amazing service!", - "Hi, I really enjoyed working with you. Thank you again!", + 'Thank you for amazing service!', + 'Hi, I really enjoyed working with you. Thank you again!', { name: 'Customer A', }, @@ -83,8 +83,8 @@ function generateSampleEmails() { GmailApp.sendEmail( userEmail, - "Request for information", - "Hello, I need more information on your recent product launch. Thank you.", + 'Request for information', + 'Hello, I need more information on your recent product launch. Thank you.', { name: 'Customer B', }, @@ -92,10 +92,12 @@ function generateSampleEmails() { GmailApp.sendEmail( userEmail, - "Complaint!", - "Hello, You are late in delivery, again. Please contact me ASAP before I cancel our subscription.", + 'Complaint!', + '', { name: 'Customer C', + htmlBody: `

Hello, You are late in delivery, again.

+

Please contact me ASAP before I cancel our subscription.

`, }, ); From 77f8acd645706f2baa2939b4b9c4e3b9f4e41e69 Mon Sep 17 00:00:00 2001 From: Mohammad Al-Ansari Date: Thu, 23 Jan 2025 19:55:10 +0000 Subject: [PATCH 3/4] fix: updated to correct code, switched to use flash model --- gmail-sentiment-analysis/Vertex.gs | 99 ++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/gmail-sentiment-analysis/Vertex.gs b/gmail-sentiment-analysis/Vertex.gs index 6018fe6ae..2b50c9aff 100644 --- a/gmail-sentiment-analysis/Vertex.gs +++ b/gmail-sentiment-analysis/Vertex.gs @@ -1,27 +1,78 @@ -{ - "timeZone": "America/Toronto", - "oauthScopes": [ - "https://www.googleapis.com/auth/script.external_request", - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/script.locale", - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/gmail.addons.execute", - "https://www.googleapis.com/auth/gmail.labels", - "https://www.googleapis.com/auth/gmail.modify" - ], - "addOns": { - "common": { - "name": "Sentiment Analysis", - "logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/dynamic_feed/v6/black-24dp/1x/gm_dynamic_feed_black_24dp.png", - "useLocaleFromApp": true - }, - "gmail": { - "homepageTrigger": { - "runFunction": "onHomepageTrigger", - "enabled": true +/* +Copyright 2024-2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const PROJECT_ID = 'qwiklabs-gcp-02-66afcfbf5a65'; +const VERTEX_AI_LOCATION = 'us-central1'; +const MODEL_ID = 'gemini-1.5-flash'; + +/** + * Packages prompt and necessary settings, then sends a request to + * Vertex API. + * A check is performed to see if the response from Vertex AI contains FALSE as a value. + * Returns the outcome of that check which is a boolean. + * + * @param emailText - Email message that is sent to the model. + */ + +function processSentiment(emailText) { + const prompt = `Analyze the sentiment of the following message: ${emailText}`; + + const request = { + "contents": [{ + "role": "user", + "parts": [{ + "text": prompt, + }] + }], + "generationConfig": { + "temperature": 0.9, + "maxOutputTokens": 1024, + "responseMimeType": "application/json", + "responseSchema": { + "type": "object", + "properties": { + "response": { + "type": "string", + "enum": [ + "positive", + "negative", + "neutral", + ] + } + } } } - }, - "exceptionLogging": "STACKDRIVER", - "runtimeVersion": "V8" + }; + + const fetchOptions = { + method: 'POST', + headers: { + 'Authorization': `Bearer ${ScriptApp.getOAuthToken()}` + }, + contentType: 'application/json', + muteHttpExceptions: true, + payload: JSON.stringify(request), + } + + const url = `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/` + + `locations/${VERTEX_AI_LOCATION}/publishers/google/models/${MODEL_ID}:generateContent` + + const response = UrlFetchApp.fetch(url, fetchOptions); + const payload = JSON.parse(response.getContentText()); + const text = JSON.parse(payload.candidates[0].content.parts[0].text); + + return text.response; } \ No newline at end of file From 6a4258c55930de0663d3398f712c2d3e4cf273bd Mon Sep 17 00:00:00 2001 From: Mohammad Al-Ansari Date: Thu, 23 Jan 2025 19:56:35 +0000 Subject: [PATCH 4/4] fix: placed project placeholder back --- gmail-sentiment-analysis/Vertex.gs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmail-sentiment-analysis/Vertex.gs b/gmail-sentiment-analysis/Vertex.gs index 2b50c9aff..221907a81 100644 --- a/gmail-sentiment-analysis/Vertex.gs +++ b/gmail-sentiment-analysis/Vertex.gs @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const PROJECT_ID = 'qwiklabs-gcp-02-66afcfbf5a65'; +const PROJECT_ID = '[ADD YOUR GCP PROJECT ID HERE]'; const VERTEX_AI_LOCATION = 'us-central1'; const MODEL_ID = 'gemini-1.5-flash';