From 3a9c6810590dcff636e69b4e2f3a0ef616faf36c Mon Sep 17 00:00:00 2001 From: vinay-google Date: Wed, 22 Jan 2025 08:10:41 +0000 Subject: [PATCH 1/2] chore: Update Gmail sentiment analysis for the lab --- gmail-sentiment-analysis/Cards.gs | 15 +++---- gmail-sentiment-analysis/Gmail.gs | 50 ++++++++++++++++-------- gmail-sentiment-analysis/Vertex.gs | 29 +++++++++----- gmail-sentiment-analysis/appsscript.json | 3 +- 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/gmail-sentiment-analysis/Cards.gs b/gmail-sentiment-analysis/Cards.gs index 6a07c735d..f0360318d 100644 --- a/gmail-sentiment-analysis/Cards.gs +++ b/gmail-sentiment-analysis/Cards.gs @@ -16,28 +16,24 @@ limitations under the License. /** - * Builds the card for to display in the sidepanel of gmail. + * Builds the card to display in the sidepanel of gmail. * @return {CardService.Card} The card to show to the user. */ function buildCard_GmailHome(notifyOk = false) { - const imageUrl = 'https://icons.iconarchive.com/icons/roundicons/100-free-solid/48/spy-icon.png'; - const image = CardService.newImage() - .setImageUrl(imageUrl); + 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"); - const action = CardService.newAction() - .setFunctionName('analyzeSentiment'); + const action = CardService.newAction().setFunctionName('analyzeSentiment'); const button = CardService.newTextButton() .setText('Identify angry customers') .setOnClickAction(action) .setTextButtonStyle(CardService.TextButtonStyle.FILLED); - const buttonSet = CardService.newButtonSet() - .addButton(button); + const buttonSet = CardService.newButtonSet().addButton(button); const section = CardService.newCardSection() .setHeader("Emails sentiment analysis") @@ -51,9 +47,8 @@ function buildCard_GmailHome(notifyOk = false) { * This builds the card that contains the footer that informs * the user about the successful execution of the Add-on. */ - if (notifyOk == true) { - let fixedFooter = CardService.newFixedFooter() + const fixedFooter = CardService.newFixedFooter() .setPrimaryButton( CardService.newTextButton() .setText("Analysis complete") diff --git a/gmail-sentiment-analysis/Gmail.gs b/gmail-sentiment-analysis/Gmail.gs index 7e4b3c621..25c5d7483 100644 --- a/gmail-sentiment-analysis/Gmail.gs +++ b/gmail-sentiment-analysis/Gmail.gs @@ -20,30 +20,46 @@ limitations under the License. */ function analyzeSentiment() { - emailSentiment(); + analyzeAndLabelEmailSentiment(); return buildCard_GmailHome(true); } /** - * Gets the last 10 threads in the inbox and the corresponding messages. - * Fetches the label that should be applied to negative messages. - * The processSentiment is called on each message - * and tested with RegExp to check for a negative answer from the model + * Analyzes the sentiment of recent emails in the inbox and labels threads with + * negative sentiment as "UPSET TONE 😡". */ +function analyzeAndLabelEmailSentiment() { + const labelName = "UPSET TONE 😡"; + const maxThreads = 10; -function emailSentiment() { - const threads = GmailApp.getInboxThreads(0, 10); - const msgs = GmailApp.getMessagesForThreads(threads); - const label_upset = GmailApp.getUserLabelByName("UPSET TONE 😡"); - let currentPrediction; - - for (let i = 0; i < msgs.length; i++) { - for (let j = 0; j < msgs[i].length; j++) { - let emailText = msgs[i][j].getPlainBody(); - currentPrediction = processSentiment(emailText); - if (currentPrediction === true) { - label_upset.addToThread(msgs[i][j].getThread()); + // Get the label, or create it if it doesn't exist. + const label = GmailApp.getUserLabelByName(labelName) || GmailApp.createLabel(labelName); + + // Get the first 'maxThreads' threads from the inbox. + const threads = GmailApp.getInboxThreads(0, maxThreads); + + // Process each thread. + for (const thread of threads) { + const messages = thread.getMessages(); + + // Process each message within the thread. + for (const message of messages) { + const emailText = message.getPlainBody(); + const isUpset = isNegativeSentiment(emailText); + + if (isUpset) { + label.addToThread(thread); } } } } + +/** + * Determines if the given text has a negative sentiment. + * + * @param {string} text - The text to analyze. + * @returns {boolean} True if the sentiment is negative, false otherwise. + */ +function isNegativeSentiment(text) { + return processSentiment(text); +} diff --git a/gmail-sentiment-analysis/Vertex.gs b/gmail-sentiment-analysis/Vertex.gs index 25d1994a3..fccd311d8 100644 --- a/gmail-sentiment-analysis/Vertex.gs +++ b/gmail-sentiment-analysis/Vertex.gs @@ -14,8 +14,8 @@ 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 = 'europe-west2'; +const PROJECT_ID = 'qwiklabs-gcp-01-e6ca251b4715'; +const VERTEX_AI_LOCATION = 'us-central1'; const MODEL_ID = 'gemini-1.5-pro-002'; /** @@ -28,11 +28,7 @@ const MODEL_ID = 'gemini-1.5-pro-002'; */ function processSentiment(emailText) { - const prompt = ` - Analyze the following message: ${emailText}. - If the sentiment of this message is negative, answer with FALSE. - If the sentiment of this message is neutral or positive, answer with TRUE. - Do not use any other words than the ones requested in this prompt as a response!`; + const prompt = `Analyze the sentiment of the following message: ${emailText}`; const request = { "contents": [{ @@ -44,6 +40,20 @@ function processSentiment(emailText) { "generationConfig": { "temperature": 0.9, "maxOutputTokens": 1024, + "responseMimeType": "application/json", + "responseSchema": { + "type": "object", + "properties": { + "response": { + "type": "string", + "enum": [ + "positive", + "negative", + "neutral", + ] + } + } + } } }; @@ -62,8 +72,7 @@ function processSentiment(emailText) { const response = UrlFetchApp.fetch(url, fetchOptions); const payload = JSON.parse(response.getContentText()); + const text = JSON.parse(payload.candidates[0].content.parts[0].text); - const regex = /FALSE/; - - return regex.test(payload.candidates[0].content.parts[0].text); + return text.response === 'negative'; } diff --git a/gmail-sentiment-analysis/appsscript.json b/gmail-sentiment-analysis/appsscript.json index cf6273f63..9b2be96ac 100644 --- a/gmail-sentiment-analysis/appsscript.json +++ b/gmail-sentiment-analysis/appsscript.json @@ -5,14 +5,13 @@ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/script.locale", "https://www.googleapis.com/auth/gmail.addons.execute", - "https://mail.google.com/", "https://www.googleapis.com/auth/gmail.labels", "https://www.googleapis.com/auth/gmail.modify" ], "addOns": { "common": { "name": "Productivity toolbox", - "logoUrl": "https://icons.iconarchive.com/icons/roundicons/100-free-solid/64/spy-icon.png", + "logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/dynamic_feed/v6/black-24dp/1x/gm_dynamic_feed_black_24dp.png", "useLocaleFromApp": true }, "gmail": { From 8fbf646c7c81290c030f71ea0cd41536125a1d85 Mon Sep 17 00:00:00 2001 From: vinay-google Date: Wed, 22 Jan 2025 08:17:09 +0000 Subject: [PATCH 2/2] chore: Fix more --- gmail-sentiment-analysis/Gmail.gs | 2 +- gmail-sentiment-analysis/Vertex.gs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gmail-sentiment-analysis/Gmail.gs b/gmail-sentiment-analysis/Gmail.gs index 25c5d7483..d4e6fe2b2 100644 --- a/gmail-sentiment-analysis/Gmail.gs +++ b/gmail-sentiment-analysis/Gmail.gs @@ -61,5 +61,5 @@ function analyzeAndLabelEmailSentiment() { * @returns {boolean} True if the sentiment is negative, false otherwise. */ function isNegativeSentiment(text) { - return processSentiment(text); + return processSentiment(text) === 'negative'; } diff --git a/gmail-sentiment-analysis/Vertex.gs b/gmail-sentiment-analysis/Vertex.gs index fccd311d8..3f02fefff 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-01-e6ca251b4715'; +const PROJECT_ID = '[ADD YOUR GCP PROJECT ID HERE]'; const VERTEX_AI_LOCATION = 'us-central1'; const MODEL_ID = 'gemini-1.5-pro-002'; @@ -74,5 +74,5 @@ function processSentiment(emailText) { const payload = JSON.parse(response.getContentText()); const text = JSON.parse(payload.candidates[0].content.parts[0].text); - return text.response === 'negative'; + return text.response; }