MemChatGPT: A Chrome Extension for Enhanced Productivity

Thomas Pernet
10 min readDec 2, 2023

--

Overview

Mem Pusher is an innovative Chrome extension designed to enhance productivity by seamlessly integrating with Mem, a note-taking and organization tool, and ChatGPT, a state-of-the-art language model developed by OpenAI. This extension allows users to quickly capture and push content from any web page to their Mem account, and to generate summaries or responses using ChatGPT, making it an invaluable tool for researchers, students, and professionals alike.

he first part of the post will guide you through using the extension, while the second part delves into the technical aspects, explaining the code structure and functionality in detail.

The code is available here https://github.com/thomaspernet/MemChatGPT-Extension/tree/main

MemChatGPT-Extension

MemChatGPT-Extension is a Chrome extension designed to enhance your workflow by integrating OpenAI’s ChatGPT with Mem AI. This tool allows you to extract content from web pages, process it through ChatGPT, and then seamlessly push it to Mem for organized storage.

Installation

To install MemChatGPT-Extension in Chrome:

  1. Clone or download the repository from GitHub.
  2. Open Chrome and navigate to chrome://extensions/.
  3. Enable “Developer mode” at the top right corner.
  4. Click on “Load unpacked” and select the directory of your downloaded extension.

Setting Up API Keys

Mem API Key

  1. Go to Mem AI API setup page.
  2. Create a new API key and securely copy it.
  3. Keep this key confidential.

ChatGPT API Key

  1. Visit OpenAI API keys page to set up your API key.
  2. Note: Obtaining this key requires a credit card. Refer to OpenAI’s pricing for detailed information.

Using the Extension

Templates for Mem

In MemChatGPT-Extension, templates are used to format content before pushing to Mem. Templates can include placeholders for title, date, and URL. Use {title}, {date}, and {url} as placeholders in your templates. For instance:

# {title}

- Date: {date}
- URL: {url}

Adding a Template

  1. Click on the “Create Mem Template” tab.
  2. Enter a name for your template.
  3. In the template area, enter your template using the placeholders.
  4. Click “Create Template”.

Prompts for ChatGPT

Prompts are used to guide the ChatGPT in processing your content.

Adding a Prompt

  1. Navigate to the “Create ChatGPT Prompt” tab.
  2. Provide a name for your prompt.
  3. Enter the ChatGPT prompt details in the provided area.
  4. Click “Create Prompt”.

Extracting and Processing Content

  1. In the Main tab, select the desired Mem template and ChatGPT prompt.
  2. Click “Extract Content” while on a web page to pull its text content.
  3. The content is processed through ChatGPT and displayed in the output area. You can edit this content before pushing it to Mem.

Pushing to Mem

  1. Optionally modify the processed content in the output area.
  2. Click “Push to Mem” to send the content to your Mem account.

Security Note

Remember to keep your API keys private and securely stored. Avoid sharing them or embedding them in shared code repositories.

Core Structure and Code Explained

Developing a Chrome extension involves several components, each playing a pivotal role. Let’s break down MemChatGPT’s anatomy:

Project Setup

First, organize your project folder. Here’s a basic structure:

MemChatGPT/

├── popup.html
├── popup.js
├── background.js
├── styles.css
└── manifest.json

Each file serves a specific purpose, forming the skeleton of our extension.

Manifest File (manifest.json)

The manifest.json file acts as a blueprint for the Chrome extension. It defines the extension's basic settings, permissions, and components. Here's a breakdown of the manifest.json for the Mem Pusher extension:

{
"manifest_version": 3,
"name": "Mem Pusher",
"version": "1.0",
"permissions": [
"activeTab",
"storage",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": "images/icon.png"
},
"host_permissions": ["<all_urls>"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
}

1. manifest_version

  • Purpose: Specifies the version of the manifest file format.
  • Explanation: Version 3 is the latest manifest version for Chrome extensions, offering improved security and performance features.

2. name

  • Purpose: Provides the name of the extension.
  • Explanation: “Mem Pusher” is the chosen name, appearing in the Chrome Web Store and the browser’s extension management page.

3. version

  • Purpose: Indicates the version of the extension.
  • Explanation: “1.0” signifies the initial release version. This is crucial for managing updates and version control.

4. permissions

  • Purpose: Lists the permissions the extension requires.
  • Explanation:
  • "activeTab": Allows the extension to interact with the active tab where the user invokes the extension.
  • "storage": Enables the extension to save data locally, essential for storing API keys and other settings.
  • "scripting": Grants the extension capabilities to inject scripts into web pages.

5. background

  • Purpose: Defines the background script of the extension.
  • Explanation:
  • "service_worker": "background.js": Specifies background.js as the service worker script, which runs in the background and handles tasks like API calls and other logic not tied to the UI.

6. action

  • Purpose: Defines the default UI for the extension.
  • Explanation:
  • "default_popup": "popup.html": Sets popup.html as the primary interface that appears when users click the extension icon.
  • "default_icon": "images/icon.png": Points to the icon displayed in the browser's toolbar.

7. host_permissions

  • Purpose: Specifies the URLs the extension can access.
  • Explanation:
  • "<all_urls>": Indicates that the extension can interact with all web pages.

8. content_scripts

  • Purpose: Injects JavaScript into specified web pages.
  • Explanation:
  • "matches": ["<all_urls>"]: Defines the pages where the extension's content scripts (in this case, content.js) will be injected. "<all_urls>" means the script will run on all pages.
  • "js": ["content.js"]: Points to the JavaScript file that contains the content script logic.

This configuration sets up the extension with necessary permissions and points to popup.html as the UI.

The Popup Interface (popup.html)

This HTML file creates the user interface of the extension’s popup. It’s where users interact with your extension.

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<!-- Add your UI components here -->
</body>
</html>

Logic Handling (popup.js)

popup.js is the heart of the Mem Pusher Chrome extension, where the main interactions and functionalities are defined. Here's an overview of its key components:

1. Initialization and Tab Setup

  • DOMContentLoaded Event: As soon as the popup loads, various initialization functions are called. These include setting up tab listeners, loading API keys, and opening the default tab.
document.addEventListener('DOMContentLoaded', () => {
setupTabListeners();
loadApiKey();
loadChatGPTApiKey();
openTab('Main'); // Open Main tab by default
loadExistingTemplates(); // Load existing templates
loadChatGPTPrompts(); // Load ChatGPT prompts immediately
});
  • Tab Listeners: The extension uses a tabbed interface. Each tab is associated with a specific content area. The setupTabListeners function attaches event listeners to these tabs, ensuring the correct content is displayed when a tab is clicked.
function setupTabListeners() {
document.getElementById('mainTab').addEventListener('click', () => openTab('Main'));
document.getElementById('memTemplateTab').addEventListener('click', () => openTab('CreateMemTemplate'));
document.getElementById('manageTemplatesTab').addEventListener('click', () => {
openTab('ManageTemplates');
loadExistingTemplates();
});
document.getElementById('createChatGPTPromptTab').addEventListener('click', () => openTab('CreateChatGPTPrompt'));
document.getElementById('manageChatGPTPromptsTab').addEventListener('click', () => {
openTab('ManageChatGPTPrompts');
loadChatGPTPrompts();
});
document.getElementById('apiKeysTab').addEventListener('click', () => openTab('ApiKeys'));
}

2. API Key Management

  • Loading API Keys: Functions loadApiKey and loadChatGPTApiKey retrieve the stored API keys for Mem and ChatGPT, respectively, from Chrome's storage.sync and populate the corresponding input fields if the keys are already saved.
function loadApiKey() {
chrome.storage.sync.get('api_mem', data => {
if (data.api_mem) {
document.getElementById('api_key').value = data.api_mem;
}
});
}

function loadChatGPTApiKey() {
chrome.storage.sync.get('api_chatgpt', data => {
if (data.api_chatgpt) {
document.getElementById('chatgpt_api_key').value = data.api_chatgpt;
}
});
}

here are two main types of storage available in chrome.storage:

  1. chrome.storage.sync: This stores data that is automatically synchronized across all instances of the browser where the user is logged in with the same Google account. It's useful for saving user preferences or other extension data that needs to be consistent across devices. However, it has a lower quota limit.
  2. chrome.storage.local: This stores data only on the local device. It's not synced across devices but has a much higher storage quota than chrome.storage.sync.

3. Template and Prompt Management

  • Template and Prompt Handling: Functions to create, edit, delete, and load templates and ChatGPT prompts are included. These functions interact with Chrome’s storage to maintain the state of user-defined templates and prompts.
function setupManageTemplatesTab() {
loadExistingTemplates();

const existingTemplateSelector = document.getElementById('existing_template_selector');
if (existingTemplateSelector) {
existingTemplateSelector.addEventListener('change', function() {
const selectedValue = this.value;
const editTemplateName = document.getElementById('edit_template_name');
const editTemplateArea = document.getElementById('edit_template_area');

chrome.storage.sync.get({ templates: {} }, function(data) {
if (data.templates[selectedValue]) {
editTemplateName.value = selectedValue;
editTemplateArea.value = data.templates[selectedValue];
}
});
});
}
}

4. Content Extraction and Processing

  • Content Extraction: The extractContentFromTab function is triggered when the user wants to extract content from the current tab. It injects a script (getPageContentScript) into the tab that extracts the text and initiates further processing.
  • ChatGPT Interaction: After extraction, the content is sent to ChatGPT for processing. The response is displayed in the extension’s popup window.
// This function triggers the content extraction in the current tab
function extractContentFromTab() {
displayLoadingIndicator(); // Show spinner when starting extraction

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
if (tabs.length > 0 && tabs[0].id) {
chrome.scripting.executeScript({
target: {tabId: tabs[0].id},
function: getPageContentScript
}, (injectionResults) => {
if (injectionResults && injectionResults[0]) {
const extractedContent = injectionResults[0].result;
const selectedPromptName = document.getElementById('chatgpt_prompt_selector').value;
const selectedModel = document.getElementById('chatgpt_model_selector').value;

getChatGPTPromptContent(selectedPromptName, function(promptContent) {
if (promptContent) {
sendContentToBackgroundForChatGPT(extractedContent, promptContent, selectedModel, function(err, response) {
if (!err) {
document.getElementById('output_area').textContent = response;
} else {
document.getElementById('output_area').textContent = err;
}
hideLoadingIndicator(); // Hide spinner after processing is complete
});
} else {
document.getElementById('output_area').textContent = 'Error: Prompt content not found';
hideLoadingIndicator();
}
});
} else if (chrome.runtime.lastError) {
document.getElementById('output_area').textContent = 'Error extracting content: ' + chrome.runtime.lastError.message;
hideLoadingIndicator();
} else {
document.getElementById('output_area').textContent = 'No content found';
hideLoadingIndicator();
}
});
}
});
}

5. Mem Integration

  • Push to Mem: The extension allows users to push content directly to Mem. The pushContentToMem function handles this interaction, sending the formatted content to Mem and providing a link to the created Mem.
function pushContentToMem(content) {
chrome.runtime.sendMessage({ message: 'pushToMem', content: content }, response => {
const memLinkElement = document.getElementById('mem_link');
if (response && response.url) {
memLinkElement.href = response.url;
memLinkElement.textContent = 'Open Mem URL';
memLinkElement.style.display = 'block';
} else {
memLinkElement.textContent = 'Error or no URL in response';
memLinkElement.style.display = 'block';
}
});
}

6. Auxiliary Functions

  • Utility Functions: Several helper functions are used for tasks like resetting selectors (resetSelector), adding options to selectors (addOptionToSelector), and handling loading indicators (displayLoadingIndicator and hideLoadingIndicator).

Background Operations (background.js)

The background.js script of the Mem Pusher Chrome extension handles the asynchronous tasks and interactions with external APIs. Here's a detailed breakdown of its key components:

1. Pushing Content to Mem

  • Function pushToMem: This function is responsible for sending a note to Mem. It constructs a POST request to Mem's API with the provided note content and the user's API key. Upon a successful request, it resolves the promise with the URL of the created Mem. This function encapsulates the interaction with Mem's API, abstracting the details of the HTTP request.
function pushToMem(note, apiKey) {
return new Promise((resolve, reject) => {
var apiUrl = "https://api.mem.ai/v0/mems";
var headers = {
"Authorization": "ApiAccessToken " + apiKey,
"Content-Type": "application/json"
};
var payload = {
"content": note
};
var options = {
"method": "post",
"headers": headers,
"body": JSON.stringify(payload)
};

fetch(apiUrl, options)
.then(response => response.json())
.then(data => {
if (data.url) {
resolve(data.url);
} else {
reject("Error: URL not found in response");
}
})
.catch(error => reject("Error: " + error.message));
});
}

2. Processing Requests with ChatGPT

  • Function processChatGPTRequest: This function interacts with the ChatGPT API. It constructs a request body with the user's prompt and the extracted content, then sends a POST request to OpenAI's ChatGPT API. The response from ChatGPT is then processed and returned. This function demonstrates how to integrate with a third-party AI service, handling the specifics of the API request.
function processChatGPTRequest(api_key, text, prompt, model) {
return new Promise((resolve, reject) => {
prompt = prompt + "\n" + text;
var url = "https://api.openai.com/v1/chat/completions";
var data = {
"model": model,
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
]
};
var options = {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + api_key
},
body: JSON.stringify(data)
};

fetch(url, options)
.then(response => response.json())
.then(data => {
if (data.choices && data.choices.length > 0) {
resolve(data.choices[0].message.content);
} else {
reject("No response from ChatGPT");
}
})
.catch(error => reject("Error: " + error.message));
});
}

3. Message Listeners and Asynchronous Handling

  • Chrome Runtime Message Listeners: The script listens for messages from other parts of the extension (like the popup script). There are two key listeners:
  • Push to Mem: Activated when a message with the action ‘pushToMem’ is received. It retrieves the API key from Chrome’s storage and calls pushToMem with the content.
  • Process with ChatGPT: Triggered by a message with the action ‘processWithChatGPT’. It fetches the ChatGPT API key from storage and invokes processChatGPTRequest with the necessary parameters.
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.message === 'pushToMem') {
chrome.storage.sync.get('api_mem', function(data) {
pushToMem(request.content, data.api_mem)
.then(url => sendResponse({ url: url }))
.catch(error => sendResponse({ error: error }));
});
return true; // Indicates that the response is asynchronous
}
});

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'processWithChatGPT') {
console.log("Received ChatGPT request in background:", request); // Log received request

chrome.storage.sync.get('api_chatgpt', function(data) {

// Call the function to process the ChatGPT request (modify this to suit your implementation)
processChatGPTRequest(data.api_chatgpt, request.content, request.prompt, request.model)
.then(response => {
console.log("ChatGPT processing successful:", response); // Log successful response
sendResponse({ output: response });
})
.catch(error => {
console.log("ChatGPT processing error:", error); // Log error
sendResponse({ error: 'Error: ' + error.message });
});
})

return true; // Indicates that the response is asynchronous
}
});

4. Asynchronous Response Handling

  • Both listeners use asynchronous functions and return responses via sendResponse. This is crucial for handling potentially long-running API requests without blocking the extension's other functionalities.

In conclusion, the Mem Pusher Chrome extension is a practical and efficient tool for anyone looking to enhance their digital workflow. It skillfully combines the functionalities of Mem and ChatGPT, making the process of capturing and organizing web content both effortless and intuitive. Whether for professional or personal use, this extension is sure to be a valuable addition to your digital toolkit.

--

--