- // ==UserScript==
- // @name AI Email Assistant for GMAIL
- // @namespace http://tampermonkey.net/
- // @version 1.3.0
- // @description Assist in generating email replies using AI.
- // @author Morgan Schaefer
- // @match https://mail.google.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- const LOCAL_STORAGE_KEY = 'openai_api_key';
-
- function promptForApiKey() {
- let apiKey = window.prompt("Enter your OpenAI API Key:");
- if (apiKey) {
- localStorage.setItem(LOCAL_STORAGE_KEY, apiKey);
- return apiKey;
- }
- alert("API Key is required to use this script.");
- return null;
- }
-
- function getApiKey() {
- let apiKey = localStorage.getItem(LOCAL_STORAGE_KEY);
- if (!apiKey) {
- apiKey = promptForApiKey();
- }
- return apiKey;
- }
-
- const API_KEY = getApiKey();
- if (!API_KEY) return;
-
- function createButton(label, onClick) {
- const button = document.createElement('button');
- button.textContent = label;
- styleButton(button);
- button.addEventListener('click', onClick);
- return button;
- }
-
- function styleButton(button) {
- Object.assign(button.style, {
- margin: '5px',
- padding: '5px',
- backgroundColor: '#1a73e8',
- color: '#fff',
- border: 'none',
- borderRadius: '3px',
- cursor: 'pointer'
- });
- }
-
- function appendButtonsToComposeWindow() {
- const composeWindow = document.querySelector('td.I5 form.bAs');
- if (composeWindow) {
- const targetTable = composeWindow.querySelector('table.IG');
- if (targetTable && !document.getElementById('ai-assistant-button')) {
- const newTr = document.createElement('tr');
- newTr.id = 'ai-assistant-row';
-
- const newTd = document.createElement('td');
- newTd.colSpan = 2;
-
- // Create the AI Assistant button
- const assistantButton = createButton('Assistant AI', onButtonClick);
- assistantButton.id = 'ai-assistant-button';
-
- // Create the input field
- const inputField = document.createElement('input');
- inputField.type = 'text';
- inputField.id = 'ai-input-field';
- inputField.placeholder = 'Instructions supplémentaires...';
- styleInputField(inputField);
-
- // Append the button and input field to the table cell
- newTd.appendChild(assistantButton);
- newTd.appendChild(inputField);
- newTr.appendChild(newTd);
- targetTable.querySelector('tbody').appendChild(newTr);
- }
- }
- }
-
- function styleInputField(input) {
- Object.assign(input.style, {
- marginLeft: '10px',
- padding: '5px',
- border: '1px solid #ccc',
- borderRadius: '3px',
- width: '200px'
- });
- }
-
- function getEmailAddresses() {
- const fromElement = document.querySelector('span#\\:vf');
- const toElement = document.querySelector('div.afZ.af1 div.akl');
-
- const from = fromElement ? fromElement.textContent.trim() : 'Unknown Sender';
- const to = toElement ? toElement.textContent.trim() : 'Unknown Recipient';
-
- return { from, to };
- }
-
- function getConversationContent() {
- const messages = document.querySelectorAll('.ii.gt .a3s.aiL');
- return Array.from(messages).map(msg => {
- const parentContainer = msg.closest('.adn.ads');
- const senderNameElement = parentContainer.querySelector('.gD span');
- const senderName = senderNameElement ? senderNameElement.textContent : 'Unknown Sender';
-
- const dateTimeElement = parentContainer.querySelector('.g3');
- const dateTime = dateTimeElement ? dateTimeElement.getAttribute('title') : 'Unknown Date/Time';
-
- const messageContent = msg.innerText.trim();
- console.log(`Sender: ${senderName}, Date/Time: ${dateTime}`);
- return `Sender: ${senderName}, Date/Time: ${dateTime}\n${messageContent}`;
- }).join('\n\n').trim();
- }
-
- async function fetchOpenAIResponse(endpoint, payload) {
- try {
- const response = await fetch(`https://api.openai.com/v1/${endpoint}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${API_KEY}`
- },
- body: JSON.stringify(payload)
- });
-
- const data = await response.json();
- return response.ok ? data.choices.map(choice => choice.message.content.trim()) : ['Failed to generate response.'];
- } catch (error) {
- console.error('Error fetching AI responses:', error);
- return ['Failed to generate response.'];
- }
- }
-
- async function generateAIResponses(prompt) {
- return fetchOpenAIResponse('chat/completions', {
- model: "gpt-4o-2024-08-06",
- messages: [
- { role: "system", content: `You are an assistant that responds in the same language as the input.` },
- { role: "user", content: prompt }
- ],
- max_tokens: 1500,
- temperature: 0.9
- });
- }
-
- async function identifyKeyPointsAndVariables(conversation) {
- return fetchOpenAIResponse('chat/completions', {
- model: "gpt-4o-2024-08-06",
- messages: [
- { role: "system", content: `Tu es un assistant qui analyse un mail reçu et extrait les éléments de réponse que l'interlocuteur attend.` },
- { role: "user", content: `${conversation}` }
- ],
- max_tokens: 150
- });
- }
-
- async function generateThreeDistinctResponses(keyPoints, additionalInstructions) {
- const initialResponse = await fetchOpenAIResponse('chat/completions', {
- model: "gpt-4o-2024-08-06",
- messages: [
- { role: "system", content: `You are an assistant that provides concise and distinct responses. Generate three distinct short responses to the following key points. The response should not be longer than 6 words per key point.` },
- { role: "user", content: `Provide three distinct responses for these key points or questions:\n\n${keyPoints}\n\n the responses must be variation of the response : ${additionalInstructions}` }
- ],
- max_tokens: 150,
- n: 1,
- temperature: 0.8
- });
-
- if (initialResponse && initialResponse.length > 0) {
- return initialResponse[0].split('\n').map(resp => resp.trim()).filter(Boolean).slice(0, 3);
- }
- return ['Failed to generate responses.'];
- }
-
- async function insertResponseInEmailBody(emailBody, response) {
- const fragment = document.createDocumentFragment();
- response.split('\n').forEach((line) => {
- const textNode = document.createTextNode(line);
- fragment.appendChild(textNode);
- fragment.appendChild(document.createElement('br'));
- });
- emailBody.appendChild(fragment);
- }
-
- async function onButtonClick() {
- const emailBody = document.querySelector('div[contenteditable="true"][role="textbox"]');
- if (emailBody) {
- emailBody.focus();
- const conversationContent = getConversationContent();
- const { from, to } = getEmailAddresses();
-
- // Get the additional instructions from the input field
- const additionalInstructions = document.getElementById('ai-input-field').value || '';
-
- try {
- const keyPointsAndVariables = await identifyKeyPointsAndVariables(conversationContent);
- const shortResponses = await generateThreeDistinctResponses(keyPointsAndVariables, additionalInstructions); // Pass additional instructions here
-
- let buttonContainer = document.querySelector('#response-options-container');
- if (!buttonContainer) {
- buttonContainer = document.createElement('div');
- buttonContainer.id = 'response-options-container';
-
- const composeWindow = document.querySelector('td.I5 form.bAs');
- if (composeWindow) {
- composeWindow.appendChild(buttonContainer);
- }
- }
-
- displayResponseOptions(shortResponses, emailBody, conversationContent, buttonContainer, from, to, additionalInstructions);
- } catch (error) {
- console.error('Error inserting AI response:', error);
- }
- }
- }
-
- function displayResponseOptions(responses, emailBody, conversationContent, buttonContainer, from, to, additionalInstructions) {
- responses.forEach((response) => {
- const responseButton = createButton(response, async () => {
- const aiPrompt = `You are an email assistant tasked with generating a detailed response. The response is as follows:\n\nFrom: ${from}\nTo: ${to}\n\n${conversationContent}\n\n The reponse must be a elaboration of: ${additionalInstructions}\n\nBased on the above conversation, generate a detailed response using the selected short response option:\n\n${response}. You should generate only the body of the response`;
- const aiResponses = await generateAIResponses(aiPrompt);
- await insertResponseInEmailBody(emailBody, aiResponses[0]);
- });
-
- buttonContainer.appendChild(responseButton);
- });
- }
-
- function observeDOMChanges() {
- const observer = new MutationObserver(() => appendButtonsToComposeWindow());
- observer.observe(document.body, { childList: true, subtree: true });
- }
-
- observeDOMChanges();
-
- })();