├── README.md ├── app.py └── script.js /README.md: -------------------------------------------------------------------------------- 1 | # From Local LLMs to Google Sheets 2 | 3 | Locally run LLMs like `llama2`, `mistral`, `llava`, etc. on your computer and then get outputs inside Google Sheets. 4 | 5 | [Watch this YouTube video](https://www.youtube.com/watch?v=q6b9MIktSAc) for more information on this. 6 | 7 | ## Requirements 8 | 9 | - Python 10 | - [ollama](https://ollama.com/) 11 | - [ngrok](https://ngrok.com/) 12 | - Gmail account for Google Sheets 13 | 14 | ## Process 15 | 16 | ### 1. Install the `ollama` app on your computer 17 | 18 | And run the following command after installing: 19 | 20 | ``` 21 | ollama run mistral 22 | ``` 23 | 24 | If you want to use any other model, you can replace `mistral` with other models that [you can find here](https://ollama.com/library). 25 | 26 | ### 2. Run the Python code 27 | 28 | Open a folder in VS Code or in any other code editor, and create a app.py file, and copy-paste the following code in it: 29 | 30 | ```python 31 | from flask import Flask, request, jsonify 32 | import ollama 33 | 34 | app = Flask(__name__) 35 | 36 | @app.route('/api/chat', methods=['POST']) 37 | def chat(): 38 | data = request.json 39 | response = ollama.chat(model='mistral', messages=[{'role': 'user', 'content': data['content']}]) 40 | return jsonify(response['message']['content']) 41 | 42 | if __name__ == '__main__': 43 | app.run(debug=True, port=5001) 44 | ``` 45 | 46 | If you're using any other model than `mistral`, make sure to replace in the above code as well. 47 | 48 | ### 3. Install and run `ngrok` 49 | 50 | Install `ngrok` by following the instruction from the [official website](https://ngrok.com/). You will also have to sign up for a free account, and you'll get a auth token which you can run in the terminal as: 51 | 52 | ``` 53 | ngrok config add-authtoken 54 | ``` 55 | 56 | After this, run the following command and it will start listening to the http://localhost:5001 PORT: 57 | 58 | ``` 59 | ngrok http 5001 60 | ``` 61 | 62 | Copy the `https` forwarding URL you get, it will be used in the next step. 63 | 64 | ### 4. Prepare Google Sheets Apps Script 65 | 66 | Now, you just need to copy-paste the following script inside Google Sheets Apps Script: 67 | 68 | ```javascript 69 | function onOpen() { 70 | var ui = SpreadsheetApp.getUi(); 71 | ui.createMenu('🎉') 72 | .addItem("Fetch Ollama Data", "callOllamaAPI") 73 | .addToUi(); 74 | } 75 | 76 | function callOllamaAPI() { 77 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 78 | var settingsSheet = spreadsheet.getSheetByName('Settings'); 79 | 80 | // Fetch the settings from Settings Sheet 81 | var settingsRange = settingsSheet.getRange(2, 2, 5, 1); 82 | var settingsValues = settingsRange.getValues(); 83 | 84 | var startRow = Number(settingsValues[0][0]); 85 | var endRow = Number(settingsValues[1][0]); 86 | var dataSheet = spreadsheet.getSheetByName(settingsValues[2][0]); 87 | var promptColumns = settingsValues[3][0].split(',').map(function(item) { return letterToNum(item.trim()); }); 88 | var outputColumns = settingsValues[4][0].split(',').map(function(item) { return letterToNum(item.trim()); }); 89 | 90 | for (var i = startRow - 1; i < endRow; i++) { 91 | for (var j = 0; j < promptColumns.length; j++) { 92 | var promptCell = dataSheet.getRange(i + 1, promptColumns[j]); 93 | var finalPrompt = promptCell.getValue(); 94 | 95 | if (!finalPrompt.trim()) { 96 | continue; 97 | } 98 | 99 | var outputCell = dataSheet.getRange(i + 1, outputColumns[j]); 100 | 101 | if (outputCell.getValue() === '') { 102 | var ollamaData = { 103 | content: finalPrompt 104 | }, 105 | ollamaOptions = { 106 | method: 'post', 107 | contentType: 'application/json', 108 | payload: JSON.stringify(ollamaData) 109 | }; 110 | 111 | try { 112 | var ollamaResponse = UrlFetchApp.fetch(`/api/chat`, ollamaOptions); 113 | var ollamaTextResponse = ollamaResponse.getContentText(); 114 | // Remove the leading and trailing quotation marks from the JSON response and trim any leading/trailing whitespace 115 | var ollamaOutput = ollamaTextResponse.slice(1, -1).trim(); 116 | // If there's a trailing quotation mark left, remove it 117 | if (ollamaOutput.endsWith('"')) { 118 | ollamaOutput = ollamaOutput.substring(0, ollamaOutput.length - 1); 119 | } 120 | // Replace \n with actual new line characters and \" with " 121 | var formattedOutput = ollamaOutput.replace(/\\n/g, '\n').replace(/\\"/g, '"'); 122 | outputCell.setValue(formattedOutput); 123 | } catch(e) { 124 | console.error('Error calling Ollama API: ' + e.toString()); 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | function letterToNum(letter) { 132 | letter = letter.toUpperCase(); 133 | var column = 0, length = letter.length; 134 | for (var i = 0; i < length; i++) { 135 | column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1); 136 | } 137 | return column; 138 | } 139 | ``` 140 | 141 | Replace the `` placeholder with the copied `ngrok` URL. 142 | 143 | Save the code and run the script, you may need to authenticate for the very first time. 144 | 145 | And just like that, you should start seeing your Google Sheets getting outputs from local LLMs running on your computer. 146 | 147 | However, if you need more information, [this blog post](https://untalkedseo.com/locally-running-llms-in-google-sheets/) talks about the subject in even more detail. 148 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | import ollama 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/api/chat', methods=['POST']) 7 | def chat(): 8 | data = request.json 9 | response = ollama.chat(model='mistral', messages=[{'role': 'user', 'content': data['content']}]) 10 | return jsonify(response['message']['content']) 11 | 12 | if __name__ == '__main__': 13 | app.run(debug=True, port=5001) 14 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | function onOpen() { 2 | var ui = SpreadsheetApp.getUi(); 3 | ui.createMenu('🎉') 4 | .addItem("Fetch Ollama Data", "callOllamaAPI") 5 | .addToUi(); 6 | } 7 | 8 | function callOllamaAPI() { 9 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 10 | var settingsSheet = spreadsheet.getSheetByName('Settings'); 11 | 12 | // Fetch the settings from Settings Sheet 13 | var settingsRange = settingsSheet.getRange(2, 2, 5, 1); 14 | var settingsValues = settingsRange.getValues(); 15 | 16 | var startRow = Number(settingsValues[0][0]); 17 | var endRow = Number(settingsValues[1][0]); 18 | var dataSheet = spreadsheet.getSheetByName(settingsValues[2][0]); 19 | var promptColumns = settingsValues[3][0].split(',').map(function(item) { return letterToNum(item.trim()); }); 20 | var outputColumns = settingsValues[4][0].split(',').map(function(item) { return letterToNum(item.trim()); }); 21 | 22 | for (var i = startRow - 1; i < endRow; i++) { 23 | for (var j = 0; j < promptColumns.length; j++) { 24 | var promptCell = dataSheet.getRange(i + 1, promptColumns[j]); 25 | var finalPrompt = promptCell.getValue(); 26 | 27 | if (!finalPrompt.trim()) { 28 | continue; 29 | } 30 | 31 | var outputCell = dataSheet.getRange(i + 1, outputColumns[j]); 32 | 33 | if (outputCell.getValue() === '') { 34 | var ollamaData = { 35 | content: finalPrompt 36 | }, 37 | ollamaOptions = { 38 | method: 'post', 39 | contentType: 'application/json', 40 | payload: JSON.stringify(ollamaData) 41 | }; 42 | 43 | try { 44 | var ollamaResponse = UrlFetchApp.fetch(`/api/chat`, ollamaOptions); 45 | var ollamaTextResponse = ollamaResponse.getContentText(); 46 | // Remove the leading and trailing quotation marks from the JSON response and trim any leading/trailing whitespace 47 | var ollamaOutput = ollamaTextResponse.slice(1, -1).trim(); 48 | // If there's a trailing quotation mark left, remove it 49 | if (ollamaOutput.endsWith('"')) { 50 | ollamaOutput = ollamaOutput.substring(0, ollamaOutput.length - 1); 51 | } 52 | // Replace \n with actual new line characters and \" with " 53 | var formattedOutput = ollamaOutput.replace(/\\n/g, '\n').replace(/\\"/g, '"'); 54 | outputCell.setValue(formattedOutput); 55 | } catch(e) { 56 | console.error('Error calling Ollama API: ' + e.toString()); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | function letterToNum(letter) { 64 | letter = letter.toUpperCase(); 65 | var column = 0, length = letter.length; 66 | for (var i = 0; i < length; i++) { 67 | column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1); 68 | } 69 | return column; 70 | } 71 | --------------------------------------------------------------------------------