Skip to content

Commit

Permalink
Initial import of Drive rename demo from COL300/Next24 (#466)
Browse files Browse the repository at this point in the history
* Initial import of Drive rename demo from COL300/Next24

* Update README.md
  • Loading branch information
sqrrrl committed May 17, 2024
1 parent 1407dd3 commit 6d2c6bb
Show file tree
Hide file tree
Showing 6 changed files with 663 additions and 0 deletions.
28 changes: 28 additions & 0 deletions ai/drive-rename/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Google Workspace Add-on Drive - Name with Intelligence

## Project Description

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.

## Prerequisites

* Google Cloud Project (aka Standard Cloud Project for Apps Script) with billing enabled

## Set up your environment

1. Create a Cloud Project
1. Enable the Vertex AI API
1. Enable Google Drive API
1. Configure OAuth consent screen
1. Create a Service Account and grant the role Service `Vertex AI User` role
1. Create a private key with type JSON. This will download the JSON file for use in the next section.
1. Open a standalone Apps Script project.
1. From Project Settings, change project to GCP project number of Cloud Project from step 1
1. Add a Script Property. Enter `model_id` as the property name and `gemini-pro` as the value.
1. Add a Script Property. Enter `project_location` as the property name and `us-central1` as the value.
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.
1. Add `Google Drive API v3` advanced service.
1. Add OAuth2 v43 Apps Script Library using the ID `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF`.
1. Add the project code to Apps Script


106 changes: 106 additions & 0 deletions ai/drive-rename/ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
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 VERTEX_AI_LOCATION = PropertiesService.getScriptProperties().getProperty('project_location');
const MODEL_ID = PropertiesService.getScriptProperties().getProperty('model_id');
const SERVICE_ACCOUNT_KEY = PropertiesService.getScriptProperties().getProperty('service_account_key');

const STANDARD_PROMPT = `
Your task is to create 3 potential document names for this content.
Also, create a summary for this content, using 2 to 3 sentences, and don't include formatting.
Format the response as a JSON object with the first field called names and the summary field called summary.
The content is below:
`;

/**
* Packages prompt and necessary settings, then sends a request to
* Vertex API. Returns the response as an JSON object extracted from the
* Vertex API response object.
*
* @param prompt - String representing your prompt for Gemini AI.
*/
function getAiSummary(prompt) {

const request = {
"contents": [
{
"role": "user",
"parts": [{
text: STANDARD_PROMPT,
},
{
"text": prompt
}]
}
],
"generationConfig": {
"temperature": .2,
"maxOutputTokens": 2048,
"response_mime_type": "application/json"
}
}

const credentials = credentialsForVertexAI();

const fetchOptions = {
method: 'POST',
headers: {
'Authorization': `Bearer ${credentials.accessToken}`
},
contentType: 'application/json',
payload: JSON.stringify(request)
}

const url = `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/projects/${credentials.projectId}/locations/${VERTEX_AI_LOCATION}/publishers/google/models/${MODEL_ID}:generateContent`

const response = UrlFetchApp.fetch(url, fetchOptions);

const payload = JSON.parse(response.getContentText());
const jsonPayload = JSON.parse(payload.candidates[0].content.parts[0].text)

return jsonPayload

}

/**
* Gets credentials required to call Vertex API using a Service Account.
*
*
*/
function credentialsForVertexAI() {
const credentials = SERVICE_ACCOUNT_KEY;
if (!credentials) {
throw new Error("service_account_key script property must be set.");
}

const parsedCredentials = JSON.parse(credentials);

const service = OAuth2.createService("Vertex")
.setTokenUrl('https://oauth2.googleapis.com/token')
.setPrivateKey(parsedCredentials['private_key'])
.setIssuer(parsedCredentials['client_email'])
.setPropertyStore(PropertiesService.getScriptProperties())
.setScope("https://www.googleapis.com/auth/cloud-platform");
return {
projectId: parsedCredentials['project_id'],
accessToken: service.getAccessToken(),
}
}
44 changes: 44 additions & 0 deletions ai/drive-rename/appsscript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"timeZone": "America/Los_Angeles",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Drive",
"serviceId": "drive",
"version": "v3"
}
]
},
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/drive.addons.metadata.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/documents"
],
"urlFetchWhitelist": [
"https://*.googleusercontent.com/",
"https://*.googleapis.com/"
],
"addOns": {
"common": {
"name": "Name with Intelligence",
"logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/drive_file_rename_outline/v12/googblue-48dp/2x/gm_drive_file_rename_outline_googblue_48dp.png",
"layoutProperties": {
"primaryColor": "#4285f4",
"secondaryColor": "#3f8bca"
}
},
"drive": {
"homepageTrigger": {
"runFunction": "onHomepageOpened"
},
"onItemsSelectedTrigger": {
"runFunction": "onDriveItemsSelected"
}
}
}
}
138 changes: 138 additions & 0 deletions ai/drive-rename/drive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
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.
*/

/**
* Renames a file based on user selection / updates card.
*
* @param {!Event} e Add-on event context
* @return {!Card}
*/
function renameFile(e) {

const newName = e.formInput.names
const id = e.drive.activeCursorItem.id
DriveApp.getFileById(id).setName(newName)

const eUpdated =
{
hostApp: 'drive',
drive:
{
selectedItems: [[Object]],
activeCursorItem:
{
title: newName,
id: id,
iconUrl: e.drive.activeCursorItem.iconUrl,
mimeType: e.drive.activeCursorItem.mimeType
},
commonEventObject: { hostApp: 'DRIVE', platform: 'WEB' },
clientPlatform: 'web'
}
}

return onCardUpdate(eUpdated)

}

/**
* Redraws the same card to force AI to refresh its data.
*
* @param {!Event} e Add-on event context
* @return {!Card}
*/
function updateCard(e) {

const id = e.drive.activeCursorItem.id

const eConverted =
{
hostApp: 'drive',
drive:
{
selectedItems: [[Object]],
activeCursorItem:
{
title: DriveApp.getFileById(id).getName(),
id: id,
iconUrl: e.drive.activeCursorItem.iconUrl,
mimeType: e.drive.activeCursorItem.mimeType
},
commonEventObject: { hostApp: 'DRIVE', platform: 'WEB' },
clientPlatform: 'web'
}
}

return onCardUpdate(eConverted)
}

/**
* Fetches the body of given document, using DocumentApp.
*
* @param {string} id The Google Document file ID.
* @return {string} The body of the Google Document.
*/
function getDocumentBody(id) {

var doc = DocumentApp.openById(id);
var body = doc.getBody();
var text = body.getText();

return text;
}

/**
* Fetches the body of given document, using DocsApi.
*
* @param {string} id The Google Document file ID.
* @return {string} The body of the Google Document.
*/
function getDocAPIBody(id) {

// Call DOC API REST endpoint to get the file
let url = `https://docs.googleapis.com/v1/documents/${id}`;

var response = UrlFetchApp.fetch(url, {
method: 'GET',
headers: {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
},
muteHttpExceptions: true
});

if (response.getResponseCode() !== 200) {
throw new Error(`Drive API returned error \
${response.getResponseCode()} :\
${response.getContentText()}`);
}

let file = response.getContentText();
let data = JSON.parse(file);

return data.body.content;
}

/**
* Sends the given document to the trash folder.
*
* @param {!Event} e Add-on event context
*/
function moveFileToTrash(e) {

const id = e.drive.activeCursorItem.id
const file = DriveApp.getFileById(id);
file.setTrashed(true);
}
71 changes: 71 additions & 0 deletions ai/drive-rename/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
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.
*/

/**
* Main entry point for add-on when opened.
*
* @param e - Add-on event context
*/
function onHomepageOpened(e) {
const card = buildHomePage();

return {
action: {
navigations: [
{
pushCard: card
}
]
}
};
}

/**
* Handles selection of a file in Google Drive.
*
* @param e - Add-on event context
*/
function onDriveItemsSelected(e) {

return {
action: {
navigations: [
{
pushCard: buildSelectionPage(e)
}
]
}
}
}


/**
* Handles the update of the card on demand.
*
* @param e - (Modified) add-on event context
*/
function onCardUpdate(e) {

return {
action: {
navigations: [
{
updateCard: buildSelectionPage(e)
}
]
}
}
}
Loading

0 comments on commit 6d2c6bb

Please sign in to comment.