Skip to content

Commit

Permalink
add code.gs and livefunctions.gs to support more MX functions (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuwandavek authored Nov 8, 2024
1 parent 30086b9 commit e195e3a
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 12 deletions.
10 changes: 10 additions & 0 deletions extension/src/apps-script/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Development Notes

1. Copy the Code.gs, index.html files to the AppScript tab in doc/sheet: In the menu bar, `Extensions` -> `App Script`.

2. If you need to run the backend locally, you have to local tunnel your server so that Google App Script has access to it
```
ngrok http http://localhost:8000
```

3. Replace the server URL in both web env file, and in the Code.gs file
42 changes: 30 additions & 12 deletions extension/src/apps-script/gsheets/Code.gs
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
const LLM_RESPONSE_URL = "https://v1.minusxapi.com/planner/getLLMResponse";
const BASE_URL = "https://v1.minusxapi.com";
const LLM_RESPONSE_URL = BASE_URL + "/planner/getLLMResponse";
const SCRAPE_URL = BASE_URL + "/web/scrape";


function md5(inputString) {
return Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, inputString)
.reduce((output, byte) => output + (byte < 0 ? byte + 256 : byte).toString(16).padStart(2, '0'), '');
}

function getCache(a1Notation){
const key = "cell_" + a1Notation;
const cache = CacheService.getDocumentCache();
const cachedInputOutput = cache.get(key);
if (cachedInputOutput === null){
return null
}
else{
return cachedInputOutput.split("<delimiter>")
}
}

function updateCache(a1Notation, inputHash, output){
const key = "cell_" + a1Notation;
const cache = CacheService.getDocumentCache();
cache.put(key, inputHash + "<delimiter>" + output, 21600);
}

function gsheetSetUserToken(token) {
const scriptProperties = PropertiesService.getScriptProperties();
scriptProperties.setProperty('userToken', token);
}

function MX_WEB(columns, query) {
// columns = [0.2, 0.3]
// query = "Is this less than majority chance?"
function queryContent(content, query){
const scriptProperties = PropertiesService.getScriptProperties();
const userToken = scriptProperties.getProperty('userToken');
let dataArray;
if (Array.isArray(columns)) {
dataArray = columns.flat();
} else {
dataArray = [columns];
}
systemMessage = `You are used as a google apps script function. You will be given an array of cell values and a query and your response will be used to set the current cell value.
<CellValues>${JSON.stringify(dataArray)}</CellValues>
<CellValues>${content}</CellValues>
<Query>${query}</Query>`
let payload = {
"messages": [
Expand All @@ -28,7 +46,7 @@ function MX_WEB(columns, query) {
],
"actions": [],
"llmSettings": {
"model": "gpt-4o-mini",
"model": "gpt-4o",
"temperature": 0,
"response_format": {
"type": "text"
Expand Down
107 changes: 107 additions & 0 deletions extension/src/apps-script/gsheets/LiveFunctions.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Scrape the URL and answer the query
*
* @param {URL, query} Input the URL and the query.
* @return query response based on the contents of the URL
* @customfunction
*/

function MX_WEBASK(url, query) {
const currentA1Notation = SpreadsheetApp.getActiveRange().getA1Notation();
const hash = md5(url + "|" + query)
const cachedInputOutput = getCache(currentA1Notation);
let result;
if ((cachedInputOutput === null) || (cachedInputOutput[0] !== hash)) {
const webcontent = MX_WEBSCRAPE(url);
result = queryContent(webcontent, query);
} else {
result = cachedInputOutput[1];
}
updateCache(currentA1Notation, hash, result);
return result;
}

/**
* Scrape the URL
*
* @param {URL} Input the URL.
* @return The contents of the URL
* @customfunction
*/

function MX_WEBSCRAPE(url) {
// Check if the parameter is a range by attempting to use getA1Notation
try {
if (url.getA1Notation) {
throw new Error("Input cannot be a range.");
}
} catch (e) {
// Not a range; proceed to check if it's a URL
}

// Validate if the parameter is a URL
if (typeof url !== "string" || !/^https?:\/\/[^\s$.?#].[^\s]*$/.test(url)) {
throw new Error("Input must be a valid URL.");
}

// Fetch the URL and log the response
try {
const scriptProperties = PropertiesService.getScriptProperties();
const userToken = scriptProperties.getProperty('userToken');
let options = {
'method': 'POST',
'contentType': 'application/json',
'payload': JSON.stringify({"URL": url}),
'muteHttpExceptions': true,
'headers': {
'Authorization': `Bearer ${userToken}`
}
};
let response = UrlFetchApp.fetch(SCRAPE_URL, options);
responseCode = response.getResponseCode()
let result = response.getContentText();
if (responseCode == 200) {
return result
} else if (responseCode == 401) {
return 'Unauthorized. Please login to the MinusX sidebar'
} else if (responseCode == 402) {
return 'Credits Expired. Please add a membership to continue'
} else {
return `An error occured. Status Code: ${responseCode}`
}
} catch (e) {
Logger.log("Failed to fetch URL: " + e.message);
}
}

/**
* Ask MinusX a question based on a selected range
*
* @param {columns, query} Input the range and query.
* @return The answer based on selected cells and query.
* @customfunction
*/

function MX_ASK(columns, query) {
// columns = [0.2, 0.3]
// query = "Is this less than majority chance?"
let dataArray;
if (Array.isArray(columns)) {
dataArray = columns.flat();
} else {
dataArray = [columns];
}
const hash = md5(JSON.stringify(dataArray) + "|" + query)
const currentA1Notation = SpreadsheetApp.getActiveRange().getA1Notation();
const cachedInputOutput = getCache(currentA1Notation);
let result;
if ((cachedInputOutput === null) || (cachedInputOutput[0] !== hash)) {
result = queryContent(JSON.stringify(dataArray), query);
} else {
result = cachedInputOutput[1];
}
updateCache(currentA1Notation, hash, result);
return result;
}


0 comments on commit e195e3a

Please sign in to comment.