Skip to content

Commit 6d2c6bb

Browse files
authored
Initial import of Drive rename demo from COL300/Next24 (#466)
* Initial import of Drive rename demo from COL300/Next24 * Update README.md
1 parent 1407dd3 commit 6d2c6bb

File tree

6 files changed

+663
-0
lines changed

6 files changed

+663
-0
lines changed

ai/drive-rename/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Google Workspace Add-on Drive - Name with Intelligence
2+
3+
## Project Description
4+
5+
Google Workspace Add-on for Google Drive, which uses AI to recommend new names for the selected Doc in Google Drive by passing the body of the document within the AI prompt for context.
6+
7+
## Prerequisites
8+
9+
* Google Cloud Project (aka Standard Cloud Project for Apps Script) with billing enabled
10+
11+
## Set up your environment
12+
13+
1. Create a Cloud Project
14+
1. Enable the Vertex AI API
15+
1. Enable Google Drive API
16+
1. Configure OAuth consent screen
17+
1. Create a Service Account and grant the role Service `Vertex AI User` 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 a standalone Apps Script project.
20+
1. From Project Settings, change project to GCP project number of Cloud Project from step 1
21+
1. Add a Script Property. Enter `model_id` as the property name and `gemini-pro` as the value.
22+
1. Add a Script Property. Enter `project_location` as the property name and `us-central1` as the value.
23+
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.
24+
1. Add `Google Drive API v3` advanced service.
25+
1. Add OAuth2 v43 Apps Script Library using the ID `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF`.
26+
1. Add the project code to Apps Script
27+
28+

ai/drive-rename/ai.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
const VERTEX_AI_LOCATION = PropertiesService.getScriptProperties().getProperty('project_location');
18+
const MODEL_ID = PropertiesService.getScriptProperties().getProperty('model_id');
19+
const SERVICE_ACCOUNT_KEY = PropertiesService.getScriptProperties().getProperty('service_account_key');
20+
21+
const STANDARD_PROMPT = `
22+
23+
Your task is to create 3 potential document names for this content.
24+
25+
Also, create a summary for this content, using 2 to 3 sentences, and don't include formatting.
26+
27+
Format the response as a JSON object with the first field called names and the summary field called summary.
28+
29+
The content is below:
30+
31+
`;
32+
33+
/**
34+
* Packages prompt and necessary settings, then sends a request to
35+
* Vertex API. Returns the response as an JSON object extracted from the
36+
* Vertex API response object.
37+
*
38+
* @param prompt - String representing your prompt for Gemini AI.
39+
*/
40+
function getAiSummary(prompt) {
41+
42+
const request = {
43+
"contents": [
44+
{
45+
"role": "user",
46+
"parts": [{
47+
text: STANDARD_PROMPT,
48+
},
49+
{
50+
"text": prompt
51+
}]
52+
}
53+
],
54+
"generationConfig": {
55+
"temperature": .2,
56+
"maxOutputTokens": 2048,
57+
"response_mime_type": "application/json"
58+
}
59+
}
60+
61+
const credentials = credentialsForVertexAI();
62+
63+
const fetchOptions = {
64+
method: 'POST',
65+
headers: {
66+
'Authorization': `Bearer ${credentials.accessToken}`
67+
},
68+
contentType: 'application/json',
69+
payload: JSON.stringify(request)
70+
}
71+
72+
const url = `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/projects/${credentials.projectId}/locations/${VERTEX_AI_LOCATION}/publishers/google/models/${MODEL_ID}:generateContent`
73+
74+
const response = UrlFetchApp.fetch(url, fetchOptions);
75+
76+
const payload = JSON.parse(response.getContentText());
77+
const jsonPayload = JSON.parse(payload.candidates[0].content.parts[0].text)
78+
79+
return jsonPayload
80+
81+
}
82+
83+
/**
84+
* Gets credentials required to call Vertex API using a Service Account.
85+
*
86+
*
87+
*/
88+
function credentialsForVertexAI() {
89+
const credentials = SERVICE_ACCOUNT_KEY;
90+
if (!credentials) {
91+
throw new Error("service_account_key script property must be set.");
92+
}
93+
94+
const parsedCredentials = JSON.parse(credentials);
95+
96+
const service = OAuth2.createService("Vertex")
97+
.setTokenUrl('https://oauth2.googleapis.com/token')
98+
.setPrivateKey(parsedCredentials['private_key'])
99+
.setIssuer(parsedCredentials['client_email'])
100+
.setPropertyStore(PropertiesService.getScriptProperties())
101+
.setScope("https://www.googleapis.com/auth/cloud-platform");
102+
return {
103+
projectId: parsedCredentials['project_id'],
104+
accessToken: service.getAccessToken(),
105+
}
106+
}

ai/drive-rename/appsscript.json

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"timeZone": "America/Los_Angeles",
3+
"exceptionLogging": "STACKDRIVER",
4+
"runtimeVersion": "V8",
5+
"dependencies": {
6+
"enabledAdvancedServices": [
7+
{
8+
"userSymbol": "Drive",
9+
"serviceId": "drive",
10+
"version": "v3"
11+
}
12+
]
13+
},
14+
"oauthScopes": [
15+
"https://www.googleapis.com/auth/script.external_request",
16+
"https://www.googleapis.com/auth/drive.addons.metadata.readonly",
17+
"https://www.googleapis.com/auth/drive.file",
18+
"https://www.googleapis.com/auth/drive",
19+
"https://www.googleapis.com/auth/drive.readonly",
20+
"https://www.googleapis.com/auth/documents"
21+
],
22+
"urlFetchWhitelist": [
23+
"https://*.googleusercontent.com/",
24+
"https://*.googleapis.com/"
25+
],
26+
"addOns": {
27+
"common": {
28+
"name": "Name with Intelligence",
29+
"logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/drive_file_rename_outline/v12/googblue-48dp/2x/gm_drive_file_rename_outline_googblue_48dp.png",
30+
"layoutProperties": {
31+
"primaryColor": "#4285f4",
32+
"secondaryColor": "#3f8bca"
33+
}
34+
},
35+
"drive": {
36+
"homepageTrigger": {
37+
"runFunction": "onHomepageOpened"
38+
},
39+
"onItemsSelectedTrigger": {
40+
"runFunction": "onDriveItemsSelected"
41+
}
42+
}
43+
}
44+
}

ai/drive-rename/drive.js

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
* Renames a file based on user selection / updates card.
19+
*
20+
* @param {!Event} e Add-on event context
21+
* @return {!Card}
22+
*/
23+
function renameFile(e) {
24+
25+
const newName = e.formInput.names
26+
const id = e.drive.activeCursorItem.id
27+
DriveApp.getFileById(id).setName(newName)
28+
29+
const eUpdated =
30+
{
31+
hostApp: 'drive',
32+
drive:
33+
{
34+
selectedItems: [[Object]],
35+
activeCursorItem:
36+
{
37+
title: newName,
38+
id: id,
39+
iconUrl: e.drive.activeCursorItem.iconUrl,
40+
mimeType: e.drive.activeCursorItem.mimeType
41+
},
42+
commonEventObject: { hostApp: 'DRIVE', platform: 'WEB' },
43+
clientPlatform: 'web'
44+
}
45+
}
46+
47+
return onCardUpdate(eUpdated)
48+
49+
}
50+
51+
/**
52+
* Redraws the same card to force AI to refresh its data.
53+
*
54+
* @param {!Event} e Add-on event context
55+
* @return {!Card}
56+
*/
57+
function updateCard(e) {
58+
59+
const id = e.drive.activeCursorItem.id
60+
61+
const eConverted =
62+
{
63+
hostApp: 'drive',
64+
drive:
65+
{
66+
selectedItems: [[Object]],
67+
activeCursorItem:
68+
{
69+
title: DriveApp.getFileById(id).getName(),
70+
id: id,
71+
iconUrl: e.drive.activeCursorItem.iconUrl,
72+
mimeType: e.drive.activeCursorItem.mimeType
73+
},
74+
commonEventObject: { hostApp: 'DRIVE', platform: 'WEB' },
75+
clientPlatform: 'web'
76+
}
77+
}
78+
79+
return onCardUpdate(eConverted)
80+
}
81+
82+
/**
83+
* Fetches the body of given document, using DocumentApp.
84+
*
85+
* @param {string} id The Google Document file ID.
86+
* @return {string} The body of the Google Document.
87+
*/
88+
function getDocumentBody(id) {
89+
90+
var doc = DocumentApp.openById(id);
91+
var body = doc.getBody();
92+
var text = body.getText();
93+
94+
return text;
95+
}
96+
97+
/**
98+
* Fetches the body of given document, using DocsApi.
99+
*
100+
* @param {string} id The Google Document file ID.
101+
* @return {string} The body of the Google Document.
102+
*/
103+
function getDocAPIBody(id) {
104+
105+
// Call DOC API REST endpoint to get the file
106+
let url = `https://docs.googleapis.com/v1/documents/${id}`;
107+
108+
var response = UrlFetchApp.fetch(url, {
109+
method: 'GET',
110+
headers: {
111+
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
112+
},
113+
muteHttpExceptions: true
114+
});
115+
116+
if (response.getResponseCode() !== 200) {
117+
throw new Error(`Drive API returned error \
118+
${response.getResponseCode()} :\
119+
${response.getContentText()}`);
120+
}
121+
122+
let file = response.getContentText();
123+
let data = JSON.parse(file);
124+
125+
return data.body.content;
126+
}
127+
128+
/**
129+
* Sends the given document to the trash folder.
130+
*
131+
* @param {!Event} e Add-on event context
132+
*/
133+
function moveFileToTrash(e) {
134+
135+
const id = e.drive.activeCursorItem.id
136+
const file = DriveApp.getFileById(id);
137+
file.setTrashed(true);
138+
}

ai/drive-rename/main.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
* Main entry point for add-on when opened.
19+
*
20+
* @param e - Add-on event context
21+
*/
22+
function onHomepageOpened(e) {
23+
const card = buildHomePage();
24+
25+
return {
26+
action: {
27+
navigations: [
28+
{
29+
pushCard: card
30+
}
31+
]
32+
}
33+
};
34+
}
35+
36+
/**
37+
* Handles selection of a file in Google Drive.
38+
*
39+
* @param e - Add-on event context
40+
*/
41+
function onDriveItemsSelected(e) {
42+
43+
return {
44+
action: {
45+
navigations: [
46+
{
47+
pushCard: buildSelectionPage(e)
48+
}
49+
]
50+
}
51+
}
52+
}
53+
54+
55+
/**
56+
* Handles the update of the card on demand.
57+
*
58+
* @param e - (Modified) add-on event context
59+
*/
60+
function onCardUpdate(e) {
61+
62+
return {
63+
action: {
64+
navigations: [
65+
{
66+
updateCard: buildSelectionPage(e)
67+
}
68+
]
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)