Created
April 20, 2025 18:30
-
-
Save xaif/0a2793532fdf3198b5184e806e6a0d16 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Calls the OpenAI API to perform an action based on an instruction and optional cell range. | |
* Uses the Chat Completions endpoint with the latest available GPT-4 class model by default. | |
* | |
* @param {string} instruction The prompt or instruction for the AI. | |
* @param {string|Array<Array<string>>} [range] Optional. A cell range (e.g., A2:C2) or an array of values. | |
* @param {string} [model] Optional. Override the default OpenAI model (e.g., "gpt-4-turbo", "gpt-3.5-turbo"). | |
* @return The text generated by the OpenAI API. | |
* @customfunction | |
*/ | |
function XAI(instruction, range, model) { | |
// --- Configuration --- | |
const API_KEY_PROPERTY = "OPENAI_API_KEY"; // Key for storing API key in User Properties User Properties | |
// NOTE: Using gpt-4o as the latest flagship model. Update this if a newer model ID is released. | |
const DEFAULT_MODEL = "gpt-4o"; | |
const API_URL = "https://api.openai.com/v1/chat/completions"; | |
// Max tokens for the response. Adjust as needed, considering model limits and cost. | |
const MAX_TOKENS = 300; | |
const TEMPERATURE = 0.7; // Adjust creativity (0=deterministic, 1=max creative) | |
// --------------------- | |
if (!instruction) { | |
throw new Error("Instruction parameter is required."); | |
} | |
const apiKey = PropertiesService.getUserProperties().getProperty( | |
API_KEY_PROPERTY | |
); | |
if (!apiKey) { | |
throw new Error( | |
"OpenAI API key not found. Please set it using the 'XAI OpenAI > Set OpenAI API Key' menu." | |
); | |
} | |
let rangeContent = ""; | |
if (range) { | |
if (Array.isArray(range)) { | |
// Handle 2D array passed by Sheets for multi-cell ranges | |
rangeContent = range.map((row) => row.join(" ")).join("\n"); | |
} else { | |
// Handle single cell value | |
rangeContent = String(range); | |
} | |
} | |
// Construct the prompt for the Chat API | |
let userPrompt = instruction; | |
if (rangeContent) { | |
userPrompt += "\n\n--- Data from Spreadsheet ---\n" + rangeContent; | |
} | |
const messages = [ | |
{ | |
role: "system", | |
content: | |
"You are an AI assistant integrated into Google Sheets. Respond *only* with the direct answer or generated text requested by the user. Do not include introductory phrases, explanations, apologies, or conversational filler like 'Certainly!', 'Here is...', 'Okay, ...'. Just provide the core result.", | |
}, | |
{ role: "user", content: userPrompt }, | |
]; | |
const payload = { | |
model: model || DEFAULT_MODEL, // Use specified model or the default | |
messages: messages, | |
max_tokens: MAX_TOKENS, | |
temperature: TEMPERATURE, | |
n: 1, // We only want one response choice | |
stop: null, // You can specify stop sequences if needed | |
}; | |
const options = { | |
method: "post", | |
contentType: "application/json", | |
headers: { | |
Authorization: "Bearer " + apiKey, | |
}, | |
payload: JSON.stringify(payload), | |
muteHttpExceptions: true, // Important: Allows us to handle errors gracefully | |
}; | |
try { | |
const response = UrlFetchApp.fetch(API_URL, options); | |
const responseCode = response.getResponseCode(); | |
const responseBody = response.getContentText(); | |
if (responseCode === 200) { | |
const jsonResponse = JSON.parse(responseBody); | |
if ( | |
jsonResponse.choices && | |
jsonResponse.choices.length > 0 && | |
jsonResponse.choices[0].message && | |
jsonResponse.choices[0].message.content | |
) { | |
return jsonResponse.choices[0].message.content.trim(); | |
} else { | |
Logger.log("Unexpected OpenAI response format: " + responseBody); | |
return "Error: Unexpected response format from OpenAI."; | |
} | |
} else { | |
Logger.log( | |
"OpenAI API Error - Code: " + responseCode + ", Body: " + responseBody | |
); | |
// Try to parse the error message from OpenAI's response | |
try { | |
const errorJson = JSON.parse(responseBody); | |
if (errorJson.error && errorJson.error.message) { | |
return "Error: " + errorJson.error.message; | |
} | |
} catch (e) { | |
// Ignore parsing error, return generic message | |
} | |
return "Error: Failed to call OpenAI API (Code: " + responseCode + ")"; | |
} | |
} catch (error) { | |
Logger.log("Script Error: " + error); | |
return "Error: " + error.message; | |
} | |
} | |
// --- Helper Functions for API Key Management --- | |
/** | |
* Adds a custom menu to the spreadsheet UI to set the API key. | |
*/ | |
function onOpen() { | |
SpreadsheetApp.getUi() | |
.createMenu("Xaif's OpenAI") | |
.addItem("Set OpenAI API Key", "setOpenAIApiKey") | |
.addToUi(); | |
} | |
/** | |
* Prompts the user to enter their OpenAI API key and stores it securely | |
* in User Properties (scoped to the user and script). | |
*/ | |
function setOpenAIApiKey() { | |
const ui = SpreadsheetApp.getUi(); | |
const promptTitle = "Set OpenAI API Key"; | |
const promptMessage = | |
"Enter your OpenAI API key (it will be stored securely per user):"; | |
const result = ui.prompt(promptTitle, promptMessage, ui.ButtonSet.OK_CANCEL); | |
const button = result.getSelectedButton(); | |
const apiKey = result.getResponseText(); | |
if (button == ui.Button.OK && apiKey && apiKey.trim() !== "") { | |
PropertiesService.getUserProperties().setProperty("OPENAI_API_KEY", apiKey); | |
ui.alert("OpenAI API Key set successfully for your user account."); | |
} else if (button == ui.Button.CANCEL) { | |
ui.alert("API Key setup cancelled."); | |
} else if (button == ui.Button.CLOSE) { | |
ui.alert("API Key setup cancelled."); | |
} else if (button == ui.Button.OK && (!apiKey || apiKey.trim() === "")) { | |
ui.alert("API Key cannot be empty. Setup cancelled."); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment