diff --git a/apps/browser-extension/entrypoints/background.ts b/apps/browser-extension/entrypoints/background.ts
index cd64c8e2..12126115 100644
--- a/apps/browser-extension/entrypoints/background.ts
+++ b/apps/browser-extension/entrypoints/background.ts
@@ -106,6 +106,44 @@ export default defineBackground(() => {
}
};
+ const getRelatedMemories = async (data: any): Promise<{ success: boolean; data?: any; error?: string }> => {
+ try {
+ const result = await browser.storage.local.get(['bearerToken']);
+ const bearerToken = result.bearerToken;
+
+ if (!bearerToken) {
+ return { success: false, error: 'No authentication token found' };
+ }
+
+ const response = await fetch(`${API_ENDPOINTS.SUPERMEMORY_API}/v3/search`, {
+ method: 'POST',
+ credentials: 'omit',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${bearerToken}`,
+ },
+ body: JSON.stringify({
+ q: data
+ }),
+ });
+
+ if (!response.ok) {
+ return { success: false, error: `API call failed: ${response.status}` };
+ }
+
+ const responseData = await response.json();
+ const content = responseData.results[0].chunks[0].content;
+ console.log('Content:', content);
+ return { success: true, data: content };
+ }
+ catch(error){
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ };
+ }
+ }
+
/**
* Handle extension messages
*/
@@ -141,5 +179,20 @@ export default defineBackground(() => {
})();
return true;
}
+
+ if (message.action === MESSAGE_TYPES.GET_RELATED_MEMORIES) {
+ (async () => {
+ try {
+ const result = await getRelatedMemories(message.data);
+ sendResponse(result);
+ } catch (error) {
+ sendResponse({
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ });
+ }
+ })();
+ return true;
+ }
});
});
\ No newline at end of file
diff --git a/apps/browser-extension/entrypoints/content.ts b/apps/browser-extension/entrypoints/content.ts
index e9a31697..c4cb512b 100644
--- a/apps/browser-extension/entrypoints/content.ts
+++ b/apps/browser-extension/entrypoints/content.ts
@@ -1,4 +1,10 @@
-import { createTwitterImportButton, createTwitterImportUI, createSaveTweetElement, DOMUtils } from '../utils/ui-components';
+import {
+ createTwitterImportButton,
+ createTwitterImportUI,
+ createSaveTweetElement,
+ createChatGPTInputBarElement,
+ DOMUtils,
+} from '../utils/ui-components';
import { DOMAINS, ELEMENT_IDS, MESSAGE_TYPES } from '../utils/constants';
export default defineContentScript({
@@ -23,6 +29,7 @@ export default defineContentScript({
const observer = new MutationObserver(() => {
if (DOMUtils.isOnDomain(DOMAINS.CHATGPT)) {
addSupermemoryButtonToMemoriesDialog();
+ addSaveChatGPTElementBeforeComposerBtn();
}
if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) {
addTwitterImportButton();
@@ -35,16 +42,23 @@ export default defineContentScript({
subtree: true,
});
- if (window.location.hostname === 'chatgpt.com' || window.location.hostname === 'chat.openai.com') {
+ if (
+ window.location.hostname === 'chatgpt.com' ||
+ window.location.hostname === 'chat.openai.com'
+ ) {
addSupermemoryButtonToMemoriesDialog();
+ addSaveChatGPTElementBeforeComposerBtn();
}
- if (window.location.hostname === 'x.com' || window.location.hostname === 'twitter.com') {
+ if (
+ window.location.hostname === 'x.com' ||
+ window.location.hostname === 'twitter.com'
+ ) {
addTwitterImportButton();
//addSaveTweetElement();
}
};
- if (window.location.hostname === 'x.com' || window.location.hostname === 'twitter.com') {
+ if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) {
setTimeout(() => {
addTwitterImportButton(); // Wait 2 seconds for page to load
//addSaveTweetElement();
@@ -88,10 +102,33 @@ export default defineContentScript({
}
}
+ async function getRelatedMemories() {
+ try {
+ const userQuery =
+ document.getElementById('prompt-textarea')?.textContent || '';
+
+ const response = await browser.runtime.sendMessage({
+ action: MESSAGE_TYPES.GET_RELATED_MEMORIES,
+ data: userQuery,
+ });
+
+
+ if (response.success && response.data) {
+ const promptElement = document.getElementById('prompt-textarea');
+ if (promptElement) {
+ const currentContent = promptElement.innerHTML;
+ promptElement.innerHTML = currentContent + '
' + "Supermemories: " + response.data;
+ }
+ }
+ } catch (error) {
+ console.error('Error getting related memories:', error);
+ }
+ }
+
function addSupermemoryButtonToMemoriesDialog() {
const dialogs = document.querySelectorAll('[role="dialog"]');
let memoriesDialog: HTMLElement | null = null;
-
+
for (const dialog of dialogs) {
const headerText = dialog.querySelector('h2');
if (headerText && headerText.textContent?.includes('Saved memories')) {
@@ -99,27 +136,29 @@ export default defineContentScript({
break;
}
}
-
+
if (!memoriesDialog) return;
-
+
if (memoriesDialog.querySelector('#supermemory-save-button')) return;
-
- const deleteAllContainer = memoriesDialog.querySelector('.mt-5.flex.justify-end');
+
+ const deleteAllContainer = memoriesDialog.querySelector(
+ '.mt-5.flex.justify-end'
+ );
if (!deleteAllContainer) return;
-
+
const supermemoryButton = document.createElement('button');
supermemoryButton.id = 'supermemory-save-button';
supermemoryButton.className = 'btn relative btn-primary-outline mr-2';
-
+
const iconUrl = browser.runtime.getURL('/icon-16.png');
-
+
supermemoryButton.innerHTML = `

Save to Supermemory
`;
-
+
supermemoryButton.style.cssText = `
background: #1C2026 !important;
color: white !important;
@@ -131,36 +170,41 @@ export default defineContentScript({
margin-right: 8px !important;
cursor: pointer !important;
`;
-
+
supermemoryButton.addEventListener('mouseenter', () => {
supermemoryButton.style.backgroundColor = '#2B2E33';
});
-
+
supermemoryButton.addEventListener('mouseleave', () => {
supermemoryButton.style.backgroundColor = '#1C2026';
});
-
+
supermemoryButton.addEventListener('click', async () => {
await saveMemoriesToSupermemory();
});
-
- deleteAllContainer.insertBefore(supermemoryButton, deleteAllContainer.firstChild);
+
+ deleteAllContainer.insertBefore(
+ supermemoryButton,
+ deleteAllContainer.firstChild
+ );
}
async function saveMemoriesToSupermemory() {
try {
DOMUtils.showToast('loading');
-
- const memoriesTable = document.querySelector('[role="dialog"] table tbody');
+
+ const memoriesTable = document.querySelector(
+ '[role="dialog"] table tbody'
+ );
if (!memoriesTable) {
DOMUtils.showToast('error');
return;
}
-
+
const memoryRows = memoriesTable.querySelectorAll('tr');
const memories: string[] = [];
-
- memoryRows.forEach(row => {
+
+ memoryRows.forEach((row) => {
const memoryCell = row.querySelector('td .py-2.whitespace-pre-wrap');
if (memoryCell && memoryCell.textContent) {
memories.push(memoryCell.textContent.trim());
@@ -168,21 +212,21 @@ export default defineContentScript({
});
console.log('Memories:', memories);
-
+
if (memories.length === 0) {
DOMUtils.showToast('error');
return;
}
-
+
const combinedContent = `ChatGPT Saved Memories:\n\n${memories.map((memory, index) => `${index + 1}. ${memory}`).join('\n\n')}`;
-
+
const response = await browser.runtime.sendMessage({
action: 'saveMemory',
data: {
html: combinedContent,
},
});
-
+
if (response.success) {
DOMUtils.showToast('success');
} else {
@@ -194,7 +238,6 @@ export default defineContentScript({
}
}
-
function addTwitterImportButton() {
if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) {
return;
@@ -207,7 +250,7 @@ export default defineContentScript({
const button = createTwitterImportButton(() => {
showTwitterImportUI();
});
-
+
document.body.appendChild(button);
}
@@ -217,23 +260,25 @@ export default defineContentScript({
}
isTwitterImportOpen = true;
-
+
// Check if user is authenticated
browser.storage.local.get(['bearerToken'], ({ bearerToken }) => {
const isAuthenticated = !!bearerToken;
-
+
twitterImportUI = createTwitterImportUI(
hideTwitterImportUI,
async () => {
try {
- await browser.runtime.sendMessage({ type: MESSAGE_TYPES.BATCH_IMPORT_ALL });
+ await browser.runtime.sendMessage({
+ type: MESSAGE_TYPES.BATCH_IMPORT_ALL,
+ });
} catch (error) {
console.error('Error starting import:', error);
}
},
isAuthenticated
);
-
+
document.body.appendChild(twitterImportUI);
});
}
@@ -251,7 +296,7 @@ export default defineContentScript({
const statusDiv = twitterImportUI.querySelector('#twitter-import-status');
const button = twitterImportUI.querySelector('#twitter-import-button');
-
+
if (message.type === 'import-update') {
if (statusDiv) {
statusDiv.innerHTML = `
@@ -266,7 +311,7 @@ export default defineContentScript({
(button as HTMLButtonElement).textContent = 'Importing...';
}
}
-
+
if (message.type === 'import-done') {
if (statusDiv) {
statusDiv.innerHTML = `
@@ -276,28 +321,88 @@ export default defineContentScript({
`;
}
-
+
setTimeout(() => {
hideTwitterImportUI();
}, 3000);
}
}
+ function addSaveChatGPTElementBeforeComposerBtn() {
+ if (!DOMUtils.isOnDomain(DOMAINS.CHATGPT)) {
+ return;
+ }
+
+ const composerButtons = document.querySelectorAll('button.composer-btn');
+
+ composerButtons.forEach((button) => {
+ if (button.hasAttribute('data-supermemory-icon-added-before')) {
+ return;
+ }
+
+ const parent = button.parentElement;
+ if (!parent) return;
+
+ const parentSiblings = parent.parentElement?.children;
+ if (!parentSiblings) return;
+
+ let hasSpeechButtonSibling = false;
+ for (const sibling of parentSiblings) {
+ if (
+ sibling.getAttribute('data-testid') ===
+ 'composer-speech-button-container'
+ ) {
+ hasSpeechButtonSibling = true;
+ break;
+ }
+ }
+
+ if (!hasSpeechButtonSibling) return;
+
+ const grandParent = parent.parentElement;
+ if (!grandParent) return;
+
+ const existingIcon = grandParent.querySelector(
+ `#${ELEMENT_IDS.CHATGPT_INPUT_BAR_ELEMENT}-before-composer`
+ );
+ if (existingIcon) {
+ button.setAttribute('data-supermemory-icon-added-before', 'true');
+ return;
+ }
+
+ const saveChatGPTElement = createChatGPTInputBarElement(async () => {
+ await getRelatedMemories();
+ });
+
+ saveChatGPTElement.id = `${ELEMENT_IDS.CHATGPT_INPUT_BAR_ELEMENT}-before-composer-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
+
+ button.setAttribute('data-supermemory-icon-added-before', 'true');
+
+ grandParent.insertBefore(saveChatGPTElement, parent);
+ });
+ }
+
// TODO: Add Tweet Capture Functionality
function addSaveTweetElement() {
if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) {
return;
}
- const targetDivs = document.querySelectorAll('div.css-175oi2r.r-18u37iz.r-1h0z5md.r-1wron08');
-
+ const targetDivs = document.querySelectorAll(
+ 'div.css-175oi2r.r-18u37iz.r-1h0z5md.r-1wron08'
+ );
+
targetDivs.forEach((targetDiv) => {
if (targetDiv.hasAttribute('data-supermemory-icon-added')) {
return;
}
const previousElement = targetDiv.previousElementSibling;
- if (previousElement && previousElement.id && previousElement.id.startsWith(ELEMENT_IDS.SAVE_TWEET_ELEMENT)) {
+ if (
+ previousElement &&
+ previousElement.id &&
+ previousElement.id.startsWith(ELEMENT_IDS.SAVE_TWEET_ELEMENT)
+ ) {
targetDiv.setAttribute('data-supermemory-icon-added', 'true');
return;
}
@@ -305,11 +410,11 @@ export default defineContentScript({
const saveTweetElement = createSaveTweetElement(async () => {
await saveMemory();
});
-
+
saveTweetElement.id = `${ELEMENT_IDS.SAVE_TWEET_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
-
+
targetDiv.setAttribute('data-supermemory-icon-added', 'true');
-
+
targetDiv.parentNode?.insertBefore(saveTweetElement, targetDiv);
});
}
diff --git a/apps/browser-extension/utils/constants.ts b/apps/browser-extension/utils/constants.ts
index dad6530c..184a3cb7 100644
--- a/apps/browser-extension/utils/constants.ts
+++ b/apps/browser-extension/utils/constants.ts
@@ -30,6 +30,7 @@ export const ELEMENT_IDS = {
SUPERMEMORY_TOAST: 'supermemory-toast',
SUPERMEMORY_SAVE_BUTTON: 'supermemory-save-button',
SAVE_TWEET_ELEMENT: 'supermemory-save-tweet-element',
+ CHATGPT_INPUT_BAR_ELEMENT: 'supermemory-chatgpt-input-bar-element',
} as const;
/**
@@ -68,6 +69,7 @@ export const MESSAGE_TYPES = {
BATCH_IMPORT_ALL: 'batchImportAll',
IMPORT_UPDATE: 'import-update',
IMPORT_DONE: 'import-done',
+ GET_RELATED_MEMORIES: 'getRelatedMemories',
} as const;
export const CONTEXT_MENU_IDS = {
diff --git a/apps/browser-extension/utils/ui-components.ts b/apps/browser-extension/utils/ui-components.ts
index 062b809b..73422d55 100644
--- a/apps/browser-extension/utils/ui-components.ts
+++ b/apps/browser-extension/utils/ui-components.ts
@@ -287,6 +287,49 @@ export function createSaveTweetElement(onClick: () => void): HTMLElement {
return iconButton;
}
+/**
+ * Creates a save element button for ChatGPT input bar
+ * @param onClick - Click handler for the button
+ * @returns HTMLElement - The save button element
+ */
+export function createChatGPTInputBarElement(onClick: () => void): HTMLElement {
+ const iconButton = document.createElement('div');
+ iconButton.style.cssText = `
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ cursor: pointer;
+ transition: opacity 0.2s ease;
+ border-radius: 50%;
+ `;
+
+ // Use appropriate icon based on theme
+ const isDark = DOMUtils.isDarkMode();
+ const iconFileName = isDark ? '/dark-mode-icon.png' : '/light-mode-icon.png';
+ const iconUrl = browser.runtime.getURL(iconFileName);
+ iconButton.innerHTML = `
+
+ `;
+
+ iconButton.addEventListener('mouseenter', () => {
+ iconButton.style.opacity = '0.8';
+ });
+
+ iconButton.addEventListener('mouseleave', () => {
+ iconButton.style.opacity = '1';
+ });
+
+ iconButton.addEventListener('click', (event) => {
+ event.stopPropagation();
+ event.preventDefault();
+ onClick();
+ });
+
+ return iconButton;
+}
+
/**
* Utility functions for DOM manipulation
*/
@@ -300,6 +343,16 @@ export const DOMUtils = {
return domains.includes(window.location.hostname);
},
+ /**
+ * Detect if the page is in dark mode based on color-scheme style
+ * @returns boolean - true if dark mode, false if light mode
+ */
+ isDarkMode(): boolean {
+ const htmlElement = document.documentElement;
+ const style = htmlElement.getAttribute('style');
+ return style?.includes('color-scheme: dark') || false;
+ },
+
/**
* Check if element exists in DOM
* @param id - Element ID to check
diff --git a/apps/browser-extension/wxt.config.ts b/apps/browser-extension/wxt.config.ts
index d71bd0fc..8efd7a53 100644
--- a/apps/browser-extension/wxt.config.ts
+++ b/apps/browser-extension/wxt.config.ts
@@ -19,6 +19,8 @@ export default defineConfig({
'*://twitter.com/*',
'*://supermemory.ai/*',
'*://api.supermemory.ai/*',
+ '*://chatgpt.com/*',
+ '*://chat.openai.com/*',
],
web_accessible_resources: [
{
diff --git a/bun.lockb b/bun.lockb
index e20c9c8d..8454fa3d 100755
Binary files a/bun.lockb and b/bun.lockb differ