mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-09 03:18:04 +00:00
352 lines
11 KiB
TypeScript
352 lines
11 KiB
TypeScript
import { createTwitterImportButton, createTwitterImportUI, createSaveTweetElement, DOMUtils } from '../utils/ui-components';
|
|
import { DOMAINS, ELEMENT_IDS, MESSAGE_TYPES } from '../utils/constants';
|
|
|
|
export default defineContentScript({
|
|
matches: ['<all_urls>'],
|
|
main() {
|
|
let twitterImportUI: HTMLElement | null = null;
|
|
let isTwitterImportOpen = false;
|
|
|
|
browser.runtime.onMessage.addListener(async (message) => {
|
|
if (message.action === MESSAGE_TYPES.SHOW_TOAST) {
|
|
DOMUtils.showToast(message.state);
|
|
} else if (message.action === MESSAGE_TYPES.SAVE_MEMORY) {
|
|
await saveMemory();
|
|
} else if (message.type === MESSAGE_TYPES.IMPORT_UPDATE) {
|
|
updateTwitterImportUI(message);
|
|
} else if (message.type === MESSAGE_TYPES.IMPORT_DONE) {
|
|
updateTwitterImportUI(message);
|
|
}
|
|
});
|
|
|
|
const observeForMemoriesDialog = () => {
|
|
const observer = new MutationObserver(() => {
|
|
if (DOMUtils.isOnDomain(DOMAINS.CHATGPT)) {
|
|
addSupermemoryButtonToMemoriesDialog();
|
|
}
|
|
if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) {
|
|
addTwitterImportButton();
|
|
//addSaveTweetElement();
|
|
}
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
|
|
if (window.location.hostname === 'chatgpt.com' || window.location.hostname === 'chat.openai.com') {
|
|
addSupermemoryButtonToMemoriesDialog();
|
|
}
|
|
if (window.location.hostname === 'x.com' || window.location.hostname === 'twitter.com') {
|
|
addTwitterImportButton();
|
|
//addSaveTweetElement();
|
|
}
|
|
};
|
|
|
|
if (window.location.hostname === 'x.com' || window.location.hostname === 'twitter.com') {
|
|
setTimeout(() => {
|
|
addTwitterImportButton(); // Wait 2 seconds for page to load
|
|
//addSaveTweetElement();
|
|
}, 2000);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', observeForMemoriesDialog);
|
|
} else {
|
|
observeForMemoriesDialog();
|
|
}
|
|
|
|
async function saveMemory() {
|
|
try {
|
|
DOMUtils.showToast('loading');
|
|
|
|
const highlightedText = window.getSelection()?.toString() || '';
|
|
|
|
const url = window.location.href;
|
|
|
|
const html = document.documentElement.outerHTML;
|
|
|
|
const response = await browser.runtime.sendMessage({
|
|
action: MESSAGE_TYPES.SAVE_MEMORY,
|
|
data: {
|
|
html,
|
|
highlightedText,
|
|
url,
|
|
},
|
|
});
|
|
|
|
console.log('Response from enxtension:', response);
|
|
if (response.success) {
|
|
DOMUtils.showToast('success');
|
|
} else {
|
|
DOMUtils.showToast('error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving memory:', error);
|
|
DOMUtils.showToast('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')) {
|
|
memoriesDialog = dialog as HTMLElement;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!memoriesDialog) return;
|
|
|
|
if (memoriesDialog.querySelector('#supermemory-save-button')) return;
|
|
|
|
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 = `
|
|
<div class="flex items-center justify-center gap-2">
|
|
<img src="${iconUrl}" alt="Supermemory" style="width: 16px; height: 16px; flex-shrink: 0; border-radius: 2px;" />
|
|
Save to Supermemory
|
|
</div>
|
|
`;
|
|
|
|
supermemoryButton.style.cssText = `
|
|
background: #1C2026 !important;
|
|
color: white !important;
|
|
border: 1px solid #1C2026 !important;
|
|
border-radius: 9999px !important;
|
|
padding: 10px 16px !important;
|
|
font-weight: 500 !important;
|
|
font-size: 14px !important;
|
|
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);
|
|
}
|
|
|
|
async function saveMemoriesToSupermemory() {
|
|
try {
|
|
DOMUtils.showToast('loading');
|
|
|
|
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 => {
|
|
const memoryCell = row.querySelector('td .py-2.whitespace-pre-wrap');
|
|
if (memoryCell && memoryCell.textContent) {
|
|
memories.push(memoryCell.textContent.trim());
|
|
}
|
|
});
|
|
|
|
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 {
|
|
DOMUtils.showToast('error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving memories to Supermemory:', error);
|
|
DOMUtils.showToast('error');
|
|
}
|
|
}
|
|
|
|
|
|
function addTwitterImportButton() {
|
|
if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) {
|
|
return;
|
|
}
|
|
|
|
if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) {
|
|
return;
|
|
}
|
|
|
|
const button = createTwitterImportButton(() => {
|
|
showTwitterImportUI();
|
|
});
|
|
|
|
document.body.appendChild(button);
|
|
}
|
|
|
|
function showTwitterImportUI() {
|
|
if (twitterImportUI) {
|
|
twitterImportUI.remove();
|
|
}
|
|
|
|
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 });
|
|
} catch (error) {
|
|
console.error('Error starting import:', error);
|
|
}
|
|
},
|
|
isAuthenticated
|
|
);
|
|
|
|
document.body.appendChild(twitterImportUI);
|
|
});
|
|
}
|
|
|
|
function hideTwitterImportUI() {
|
|
if (twitterImportUI) {
|
|
twitterImportUI.remove();
|
|
twitterImportUI = null;
|
|
}
|
|
isTwitterImportOpen = false;
|
|
}
|
|
|
|
function updateTwitterImportUI(message: any) {
|
|
if (!isTwitterImportOpen || !twitterImportUI) return;
|
|
|
|
const statusDiv = twitterImportUI.querySelector('#twitter-import-status');
|
|
const button = twitterImportUI.querySelector('#twitter-import-button');
|
|
|
|
if (message.type === 'import-update') {
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = `
|
|
<div style="display: flex; align-items: center; gap: 8px; color: #92400e; background: #fef3c7; border: 1px solid #f59e0b; border-radius: 8px; padding: 8px 12px; font-size: 13px;">
|
|
<div style="width: 12px; height: 12px; border: 2px solid #f59e0b; border-top: 2px solid transparent; border-radius: 50%; animation: spin 1s linear infinite;"></div>
|
|
<span>${message.importedMessage}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
if (button) {
|
|
(button as HTMLButtonElement).disabled = true;
|
|
(button as HTMLButtonElement).textContent = 'Importing...';
|
|
}
|
|
}
|
|
|
|
if (message.type === 'import-done') {
|
|
if (statusDiv) {
|
|
statusDiv.innerHTML = `
|
|
<div style="display: flex; align-items: center; gap: 8px; color: #0369a1; background: #f0f9ff; border: 1px solid #0ea5e9; border-radius: 8px; padding: 8px 12px; font-size: 13px;">
|
|
<span style="color: #059669;">✓</span>
|
|
<span>Successfully imported ${message.totalImported} tweets!</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
setTimeout(() => {
|
|
hideTwitterImportUI();
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
// 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');
|
|
|
|
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)) {
|
|
targetDiv.setAttribute('data-supermemory-icon-added', 'true');
|
|
return;
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
document.addEventListener('keydown', async (event) => {
|
|
if (
|
|
(event.ctrlKey || event.metaKey) &&
|
|
event.shiftKey &&
|
|
event.key === 'm'
|
|
) {
|
|
event.preventDefault();
|
|
await saveMemory();
|
|
}
|
|
});
|
|
|
|
window.addEventListener('message', (event) => {
|
|
if (event.source !== window) {
|
|
return;
|
|
}
|
|
const bearerToken = event.data.token;
|
|
|
|
if (bearerToken) {
|
|
if (
|
|
!(
|
|
window.location.hostname === 'localhost' ||
|
|
window.location.hostname === 'supermemory.ai' ||
|
|
window.location.hostname === 'app.supermemory.ai'
|
|
)
|
|
) {
|
|
console.log(
|
|
'Bearer token is only allowed to be used on localhost or supermemory.ai'
|
|
);
|
|
return;
|
|
}
|
|
|
|
chrome.storage.local.set({ bearerToken }, () => {});
|
|
}
|
|
});
|
|
},
|
|
});
|