Skip to content

Commit e233cc8

Browse files
authored
Initial import of autosummarize demo from COL300 (Next 24) (googleworkspace#465)
1 parent f398fdb commit e233cc8

File tree

6 files changed

+806
-0
lines changed

6 files changed

+806
-0
lines changed

ai/autosummarize/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Editor Add-on: Sheets - AutoSummarize AI
2+
3+
## Project Description
4+
5+
Google Workspace Editor Add-on for Google Sheets that uses AI to create AI summaries in bulk for a listing of Google Docs, Sheets, and Slides files.
6+
7+
8+
## Prerequisites
9+
10+
* Google Cloud Project (aka Standard Cloud Project for Apps Script) with billing enabled
11+
12+
## Set up your environment
13+
14+
15+
1. Create a Cloud Project
16+
1. Enable the Vertex AI API
17+
1. Create a Service Account and grant the role Service Account Token Creator Role
18+
1. Create a private key with type JSON. This will download the JSON file for use in the next section.
19+
1. Open an Apps Script Project bound to a Google Sheets Spreadsheet.
20+
1. Rename the script to `Autosummarize AI`.
21+
1. From Project Settings, change project to GCP project number of Cloud Project from step 1
22+
1. Add a Script Property. Enter `model_id` as the property name and `gemini-pro-vision` as the value.
23+
1. Add a Script Property. Enter `project_location` as the property name and `us-central1` as the value.
24+
1. Add a Script Property. Enter `service_account_key` as the property name and paste the JSON key from the service account as the value.
25+
1. Add `OAuth2 v43` Apps Script Library using the ID `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF`.
26+
1. Enable the `Drive v3` advanced service.
27+
1. Add the project code to Apps Script
28+
29+
30+
## Usage
31+
32+
1. Insert one or more links to any Google Doc, Sheet, or Slides files in a column.
33+
1. Select one or more of the links in the sheet.
34+
1. From the `Sheets` menu, select `Extensions > AutoSummarize AI > Open AutoSummarize AI`
35+
1. Click Get summaries button.

ai/autosummarize/appsscript.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"timeZone": "America/Denver",
3+
"exceptionLogging": "STACKDRIVER",
4+
"runtimeVersion": "V8",
5+
"dependencies": {
6+
"libraries": [
7+
{
8+
"userSymbol": "OAuth2",
9+
"libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF",
10+
"version": "43",
11+
"developmentMode": false
12+
}
13+
],
14+
"enabledAdvancedServices": [
15+
{
16+
"userSymbol": "Drive",
17+
"version": "v3",
18+
"serviceId": "drive"
19+
}
20+
]
21+
}
22+
}

ai/autosummarize/gemini.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright 2024 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
function scriptPropertyWithDefault(key, defaultValue = undefined) {
18+
const scriptProperties = PropertiesService.getScriptProperties();
19+
const value = scriptProperties.getProperty(key);
20+
if (value) {
21+
return value;
22+
}
23+
return defaultValue;
24+
}
25+
26+
const VERTEX_AI_LOCATION = scriptPropertyWithDefault('project_location', 'us-central1');
27+
const MODEL_ID = scriptPropertyWithDefault('model_id', 'gemini-pro-vision');
28+
const SERVICE_ACCOUNT_KEY = scriptPropertyWithDefault('service_account_key');
29+
30+
31+
/**
32+
* Packages prompt and necessary settings, then sends a request to
33+
* Vertex API. Returns the response as an JSON object extracted from the
34+
* Vertex API response object.
35+
*
36+
* @param {string} prompt The prompt to senb to Vertex AI API.
37+
* @param {string} options.temperature The temperature setting set by user.
38+
* @param {string} options.tokens The number of tokens to limit to the prompt.
39+
*/
40+
function getAiSummary(parts, options = {}) {
41+
options = Object.assign({}, { temperature: 0.1, tokens: 8192}, options ?? {})
42+
const request = {
43+
"contents": [
44+
{
45+
"role": "user",
46+
"parts": parts,
47+
}
48+
],
49+
"generationConfig": {
50+
"temperature": options.temperature,
51+
"topK": 1,
52+
"topP": 1,
53+
"maxOutputTokens": options.tokens,
54+
"stopSequences": []
55+
},
56+
}
57+
58+
const credentials = credentialsForVertexAI();
59+
60+
const fetchOptions = {
61+
method: 'POST',
62+
headers: {
63+
'Authorization': `Bearer ${credentials.accessToken}`
64+
},
65+
contentType: 'application/json',
66+
muteHttpExceptions: true,
67+
payload: JSON.stringify(request)
68+
}
69+
70+
const url = `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/projects/${credentials.projectId}` +
71+
`/locations/${VERTEX_AI_LOCATION}/publishers/google/models/${MODEL_ID}:generateContent`
72+
const response = UrlFetchApp.fetch(url, fetchOptions);
73+
74+
75+
const responseCode = response.getResponseCode();
76+
if (responseCode >= 400) {
77+
throw new Error(`Unable to process file: Error code ${responseCode}`);
78+
}
79+
80+
const responseText = response.getContentText();
81+
const parsedResponse = JSON.parse(responseText);
82+
if (parsedResponse.error) {
83+
throw new Error(parsedResponse.error.message);
84+
}
85+
const text = parsedResponse.candidates[0].content.parts[0].text
86+
return text
87+
}
88+
89+
/**
90+
* Gets credentials required to call Vertex API using a Service Account.
91+
* Requires use of Service Account Key stored with project
92+
*
93+
* @return {!Object} Containing the Cloud Project Id and the access token.
94+
*/
95+
function credentialsForVertexAI() {
96+
const credentials = SERVICE_ACCOUNT_KEY;
97+
if (!credentials) {
98+
throw new Error("service_account_key script property must be set.");
99+
}
100+
101+
const parsedCredentials = JSON.parse(credentials);
102+
const service = OAuth2.createService("Vertex")
103+
.setTokenUrl('https://oauth2.googleapis.com/token')
104+
.setPrivateKey(parsedCredentials['private_key'])
105+
.setIssuer(parsedCredentials['client_email'])
106+
.setPropertyStore(PropertiesService.getScriptProperties())
107+
.setScope("https://www.googleapis.com/auth/cloud-platform");
108+
return {
109+
projectId: parsedCredentials['project_id'],
110+
accessToken: service.getAccessToken(),
111+
}
112+
}

ai/autosummarize/main.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
Copyright 2024 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/**
18+
* Creates a menu entry in the Google Sheets Extensions menu when the document is opened.
19+
*
20+
* @param {object} e The event parameter for a simple onOpen trigger.
21+
*/
22+
function onOpen(e) {
23+
SpreadsheetApp.getUi().createAddonMenu()
24+
.addItem('📄 Open AutoSummarize AI', 'showSidebar')
25+
.addSeparator()
26+
.addItem('❎ Quick summary', 'doAutoSummarizeAI')
27+
.addItem('❌ Remove all summaries', 'removeAllSummaries')
28+
.addToUi();
29+
}
30+
31+
/**
32+
* Runs when the add-on is installed; calls onOpen() to ensure menu creation and
33+
* any other initializion work is done immediately. This method is only used by
34+
* the desktop add-on and is never called by the mobile version.
35+
*
36+
* @param {object} e The event parameter for a simple onInstall trigger.
37+
*/
38+
function onInstall(e) {
39+
onOpen(e);
40+
}
41+
42+
/**
43+
* Opens sidebar in Sheets with AutoSummarize AI interface.
44+
*/
45+
function showSidebar() {
46+
const ui = HtmlService.createHtmlOutputFromFile('sidebar')
47+
.setTitle('AutoSummarize AI');
48+
SpreadsheetApp.getUi().showSidebar(ui);
49+
}
50+
51+
52+
/**
53+
* Deletes all of the AutoSummarize AI created sheets
54+
* i.e. any sheets with prefix of 'AutoSummarize AI'
55+
*/
56+
function removeAllSummaries() {
57+
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
58+
const allSheets = spreadsheet.getSheets();
59+
60+
allSheets.forEach(function (sheet) {
61+
const sheetName = sheet.getName();
62+
// Check if the sheet name starts with "AutoSummarize AI"
63+
if (sheetName.startsWith("AutoSummarize AI")) {
64+
spreadsheet.deleteSheet(sheet)
65+
}
66+
});
67+
}
68+
69+
/**
70+
* Wrapper function for add-on.
71+
*/
72+
function doAutoSummarizeAI(customPrompt1, customPrompt2, temperature = .1, tokens = 2048) {
73+
// Get selected cell values.
74+
console.log("Getting selection...");
75+
let selection = SpreadsheetApp.getSelection()
76+
.getActiveRange()
77+
.getRichTextValues()
78+
.map(value => {
79+
if (value[0].getLinkUrl()) {
80+
return value[0].getLinkUrl();
81+
}
82+
return value[0].getText();
83+
});
84+
85+
// Get AI summary
86+
const data = summarizeFiles(selection, customPrompt1, customPrompt2, temperature, tokens);
87+
88+
// Add and format a new new sheet.
89+
const now = new Date();
90+
const nowFormatted = Utilities.formatDate(now, now.getTimezoneOffset().toString(), "MM/dd HH:mm");
91+
let sheetName = `AutoSummarize AI (${nowFormatted})`;
92+
if (SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName)) {
93+
sheetName = `AutoSummarize AI (${nowFormatted}:${now.getSeconds()})`;
94+
}
95+
let aiSheet = SpreadsheetApp.getActiveSpreadsheet()
96+
.insertSheet()
97+
.setName(sheetName);
98+
let aiSheetHeaderStyle = SpreadsheetApp.newTextStyle()
99+
.setFontSize(12)
100+
.setBold(true)
101+
.setFontFamily("Google Sans")
102+
.setForegroundColor("#ffffff")
103+
.build();
104+
let aiSheetValuesStyle = SpreadsheetApp.newTextStyle()
105+
.setFontSize(10)
106+
.setBold(false)
107+
.setFontFamily("Google Sans")
108+
.setForegroundColor("#000000")
109+
.build();
110+
aiSheet.getRange("A1:E1")
111+
.setBackground("#434343")
112+
.setTextStyle(aiSheetHeaderStyle)
113+
.setValues([["Link", "Title",`Summary from Gemini AI [Temperature: ${temperature}]`, `Custom Prompt #1: ${customPrompt1}`, `Custom Prompt #2: ${customPrompt2}`]])
114+
.setWrap(true);
115+
aiSheet.setColumnWidths(1, 1, 100);
116+
aiSheet.setColumnWidths(2, 1, 300);
117+
aiSheet.setColumnWidths(3, 3, 300);
118+
119+
// Copy results
120+
aiSheet
121+
.getRange(`A2:E${data.length + 1}`)
122+
.setValues(data);
123+
124+
aiSheet.getRange(`A2:E${data.length + 1}`)
125+
.setBackground("#ffffff")
126+
.setTextStyle(aiSheetValuesStyle)
127+
.setWrapStrategy(SpreadsheetApp.WrapStrategy.CLIP)
128+
.setVerticalAlignment("top");
129+
aiSheet.getRange(`C2:E${data.length + 1}`)
130+
.setBackground("#efefef")
131+
.setWrapStrategy(SpreadsheetApp.WrapStrategy.WRAP);
132+
133+
aiSheet.deleteColumns(8, 19);
134+
aiSheet.deleteRows(aiSheet.getLastRow() + 1, aiSheet.getMaxRows() - aiSheet.getLastRow());
135+
}

0 commit comments

Comments
 (0)