AI Email Assistant for GMAIL

Assist in generating email replies using AI.

  1. // ==UserScript==
  2. // @name AI Email Assistant for GMAIL
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3.0
  5. // @description Assist in generating email replies using AI.
  6. // @author Morgan Schaefer
  7. // @match https://mail.google.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const LOCAL_STORAGE_KEY = 'openai_api_key';
  17.  
  18. function promptForApiKey() {
  19. let apiKey = window.prompt("Enter your OpenAI API Key:");
  20. if (apiKey) {
  21. localStorage.setItem(LOCAL_STORAGE_KEY, apiKey);
  22. return apiKey;
  23. }
  24. alert("API Key is required to use this script.");
  25. return null;
  26. }
  27.  
  28. function getApiKey() {
  29. let apiKey = localStorage.getItem(LOCAL_STORAGE_KEY);
  30. if (!apiKey) {
  31. apiKey = promptForApiKey();
  32. }
  33. return apiKey;
  34. }
  35.  
  36. const API_KEY = getApiKey();
  37. if (!API_KEY) return;
  38.  
  39. function createButton(label, onClick) {
  40. const button = document.createElement('button');
  41. button.textContent = label;
  42. styleButton(button);
  43. button.addEventListener('click', onClick);
  44. return button;
  45. }
  46.  
  47. function styleButton(button) {
  48. Object.assign(button.style, {
  49. margin: '5px',
  50. padding: '5px',
  51. backgroundColor: '#1a73e8',
  52. color: '#fff',
  53. border: 'none',
  54. borderRadius: '3px',
  55. cursor: 'pointer'
  56. });
  57. }
  58.  
  59. function appendButtonsToComposeWindow() {
  60. const composeWindow = document.querySelector('td.I5 form.bAs');
  61. if (composeWindow) {
  62. const targetTable = composeWindow.querySelector('table.IG');
  63. if (targetTable && !document.getElementById('ai-assistant-button')) {
  64. const newTr = document.createElement('tr');
  65. newTr.id = 'ai-assistant-row';
  66.  
  67. const newTd = document.createElement('td');
  68. newTd.colSpan = 2;
  69.  
  70. // Create the AI Assistant button
  71. const assistantButton = createButton('Assistant AI', onButtonClick);
  72. assistantButton.id = 'ai-assistant-button';
  73.  
  74. // Create the input field
  75. const inputField = document.createElement('input');
  76. inputField.type = 'text';
  77. inputField.id = 'ai-input-field';
  78. inputField.placeholder = 'Instructions supplémentaires...';
  79. styleInputField(inputField);
  80.  
  81. // Append the button and input field to the table cell
  82. newTd.appendChild(assistantButton);
  83. newTd.appendChild(inputField);
  84. newTr.appendChild(newTd);
  85. targetTable.querySelector('tbody').appendChild(newTr);
  86. }
  87. }
  88. }
  89.  
  90. function styleInputField(input) {
  91. Object.assign(input.style, {
  92. marginLeft: '10px',
  93. padding: '5px',
  94. border: '1px solid #ccc',
  95. borderRadius: '3px',
  96. width: '200px'
  97. });
  98. }
  99.  
  100. function getEmailAddresses() {
  101. const fromElement = document.querySelector('span#\\:vf');
  102. const toElement = document.querySelector('div.afZ.af1 div.akl');
  103.  
  104. const from = fromElement ? fromElement.textContent.trim() : 'Unknown Sender';
  105. const to = toElement ? toElement.textContent.trim() : 'Unknown Recipient';
  106.  
  107. return { from, to };
  108. }
  109.  
  110. function getConversationContent() {
  111. const messages = document.querySelectorAll('.ii.gt .a3s.aiL');
  112. return Array.from(messages).map(msg => {
  113. const parentContainer = msg.closest('.adn.ads');
  114. const senderNameElement = parentContainer.querySelector('.gD span');
  115. const senderName = senderNameElement ? senderNameElement.textContent : 'Unknown Sender';
  116.  
  117. const dateTimeElement = parentContainer.querySelector('.g3');
  118. const dateTime = dateTimeElement ? dateTimeElement.getAttribute('title') : 'Unknown Date/Time';
  119.  
  120. const messageContent = msg.innerText.trim();
  121. console.log(`Sender: ${senderName}, Date/Time: ${dateTime}`);
  122. return `Sender: ${senderName}, Date/Time: ${dateTime}\n${messageContent}`;
  123. }).join('\n\n').trim();
  124. }
  125.  
  126. async function fetchOpenAIResponse(endpoint, payload) {
  127. try {
  128. const response = await fetch(`https://api.openai.com/v1/${endpoint}`, {
  129. method: 'POST',
  130. headers: {
  131. 'Content-Type': 'application/json',
  132. 'Authorization': `Bearer ${API_KEY}`
  133. },
  134. body: JSON.stringify(payload)
  135. });
  136.  
  137. const data = await response.json();
  138. return response.ok ? data.choices.map(choice => choice.message.content.trim()) : ['Failed to generate response.'];
  139. } catch (error) {
  140. console.error('Error fetching AI responses:', error);
  141. return ['Failed to generate response.'];
  142. }
  143. }
  144.  
  145. async function generateAIResponses(prompt) {
  146. return fetchOpenAIResponse('chat/completions', {
  147. model: "gpt-4o-2024-08-06",
  148. messages: [
  149. { role: "system", content: `You are an assistant that responds in the same language as the input.` },
  150. { role: "user", content: prompt }
  151. ],
  152. max_tokens: 1500,
  153. temperature: 0.9
  154. });
  155. }
  156.  
  157. async function identifyKeyPointsAndVariables(conversation) {
  158. return fetchOpenAIResponse('chat/completions', {
  159. model: "gpt-4o-2024-08-06",
  160. messages: [
  161. { 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.` },
  162. { role: "user", content: `${conversation}` }
  163. ],
  164. max_tokens: 150
  165. });
  166. }
  167.  
  168. async function generateThreeDistinctResponses(keyPoints, additionalInstructions) {
  169. const initialResponse = await fetchOpenAIResponse('chat/completions', {
  170. model: "gpt-4o-2024-08-06",
  171. messages: [
  172. { 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.` },
  173. { 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}` }
  174. ],
  175. max_tokens: 150,
  176. n: 1,
  177. temperature: 0.8
  178. });
  179.  
  180. if (initialResponse && initialResponse.length > 0) {
  181. return initialResponse[0].split('\n').map(resp => resp.trim()).filter(Boolean).slice(0, 3);
  182. }
  183. return ['Failed to generate responses.'];
  184. }
  185.  
  186. async function insertResponseInEmailBody(emailBody, response) {
  187. const fragment = document.createDocumentFragment();
  188. response.split('\n').forEach((line) => {
  189. const textNode = document.createTextNode(line);
  190. fragment.appendChild(textNode);
  191. fragment.appendChild(document.createElement('br'));
  192. });
  193. emailBody.appendChild(fragment);
  194. }
  195.  
  196. async function onButtonClick() {
  197. const emailBody = document.querySelector('div[contenteditable="true"][role="textbox"]');
  198. if (emailBody) {
  199. emailBody.focus();
  200. const conversationContent = getConversationContent();
  201. const { from, to } = getEmailAddresses();
  202.  
  203. // Get the additional instructions from the input field
  204. const additionalInstructions = document.getElementById('ai-input-field').value || '';
  205.  
  206. try {
  207. const keyPointsAndVariables = await identifyKeyPointsAndVariables(conversationContent);
  208. const shortResponses = await generateThreeDistinctResponses(keyPointsAndVariables, additionalInstructions); // Pass additional instructions here
  209.  
  210. let buttonContainer = document.querySelector('#response-options-container');
  211. if (!buttonContainer) {
  212. buttonContainer = document.createElement('div');
  213. buttonContainer.id = 'response-options-container';
  214.  
  215. const composeWindow = document.querySelector('td.I5 form.bAs');
  216. if (composeWindow) {
  217. composeWindow.appendChild(buttonContainer);
  218. }
  219. }
  220.  
  221. displayResponseOptions(shortResponses, emailBody, conversationContent, buttonContainer, from, to, additionalInstructions);
  222. } catch (error) {
  223. console.error('Error inserting AI response:', error);
  224. }
  225. }
  226. }
  227.  
  228. function displayResponseOptions(responses, emailBody, conversationContent, buttonContainer, from, to, additionalInstructions) {
  229. responses.forEach((response) => {
  230. const responseButton = createButton(response, async () => {
  231. 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`;
  232. const aiResponses = await generateAIResponses(aiPrompt);
  233. await insertResponseInEmailBody(emailBody, aiResponses[0]);
  234. });
  235.  
  236. buttonContainer.appendChild(responseButton);
  237. });
  238. }
  239.  
  240. function observeDOMChanges() {
  241. const observer = new MutationObserver(() => appendButtonsToComposeWindow());
  242. observer.observe(document.body, { childList: true, subtree: true });
  243. }
  244.  
  245. observeDOMChanges();
  246.  
  247. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址