diff options
| author | MaheshtheDev <[email protected]> | 2025-09-08 05:19:10 +0000 |
|---|---|---|
| committer | MaheshtheDev <[email protected]> | 2025-09-08 05:19:10 +0000 |
| commit | 3e5ea2fd9ed210644ae29b013b579703e30986de (patch) | |
| tree | 7de64cf7bddca72cf56e89ee4320005dad9f2ac5 /apps/browser-extension/entrypoints | |
| parent | fix: billing page (#416) (diff) | |
| download | supermemory-3e5ea2fd9ed210644ae29b013b579703e30986de.tar.xz supermemory-3e5ea2fd9ed210644ae29b013b579703e30986de.zip | |
extension: updated telemetry and batch upload (#415)
Diffstat (limited to 'apps/browser-extension/entrypoints')
| -rw-r--r-- | apps/browser-extension/entrypoints/background.ts | 130 | ||||
| -rw-r--r-- | apps/browser-extension/entrypoints/content.ts | 565 | ||||
| -rw-r--r-- | apps/browser-extension/entrypoints/popup/App.tsx | 194 | ||||
| -rw-r--r-- | apps/browser-extension/entrypoints/popup/style.css | 1 | ||||
| -rw-r--r-- | apps/browser-extension/entrypoints/welcome/Welcome.tsx | 6 |
5 files changed, 456 insertions, 440 deletions
diff --git a/apps/browser-extension/entrypoints/background.ts b/apps/browser-extension/entrypoints/background.ts index 1f4ae24e..05fef55d 100644 --- a/apps/browser-extension/entrypoints/background.ts +++ b/apps/browser-extension/entrypoints/background.ts @@ -1,47 +1,52 @@ -import { getDefaultProject, saveMemory, searchMemories } from "../utils/api"; +import { getDefaultProject, saveMemory, searchMemories } from "../utils/api" import { CONTAINER_TAGS, CONTEXT_MENU_IDS, MESSAGE_TYPES, -} from "../utils/constants"; -import { captureTwitterTokens } from "../utils/twitter-auth"; + POSTHOG_EVENT_KEY, +} from "../utils/constants" +import { trackEvent } from "../utils/posthog" +import { captureTwitterTokens } from "../utils/twitter-auth" import { type TwitterImportConfig, TwitterImporter, -} from "../utils/twitter-import"; +} from "../utils/twitter-import" import type { ExtensionMessage, MemoryData, MemoryPayload, -} from "../utils/types"; +} from "../utils/types" export default defineBackground(() => { - let twitterImporter: TwitterImporter | null = null; + let twitterImporter: TwitterImporter | null = null - browser.runtime.onInstalled.addListener((details) => { + browser.runtime.onInstalled.addListener(async (details) => { browser.contextMenus.create({ id: CONTEXT_MENU_IDS.SAVE_TO_SUPERMEMORY, title: "Save to supermemory", contexts: ["selection", "page", "link"], - }); + }) - // Open welcome tab on first install if (details.reason === "install") { + await trackEvent("extension_installed", { + reason: details.reason, + version: browser.runtime.getManifest().version, + }) browser.tabs.create({ url: browser.runtime.getURL("/welcome.html"), - }); + }) } - }); + }) // Intercept Twitter requests to capture authentication headers. browser.webRequest.onBeforeSendHeaders.addListener( (details) => { - captureTwitterTokens(details); - return {}; + captureTwitterTokens(details) + return {} }, { urls: ["*://x.com/*", "*://twitter.com/*"] }, ["requestHeaders", "extraHeaders"], - ); + ) // Handle context menu clicks. browser.contextMenus.onClicked.addListener(async (info, tab) => { @@ -50,27 +55,28 @@ export default defineBackground(() => { try { await browser.tabs.sendMessage(tab.id, { action: MESSAGE_TYPES.SAVE_MEMORY, - }); + actionSource: "context_menu", + }) } catch (error) { - console.error("Failed to send message to content script:", error); + console.error("Failed to send message to content script:", error) } } } - }); + }) // Send message to current active tab. const sendMessageToCurrentTab = async (message: string) => { const tabs = await browser.tabs.query({ active: true, currentWindow: true, - }); + }) if (tabs.length > 0 && tabs[0].id) { await browser.tabs.sendMessage(tabs[0].id, { type: MESSAGE_TYPES.IMPORT_UPDATE, importedMessage: message, - }); + }) } - }; + } /** * Send import completion message @@ -79,61 +85,71 @@ export default defineBackground(() => { const tabs = await browser.tabs.query({ active: true, currentWindow: true, - }); + }) if (tabs.length > 0 && tabs[0].id) { await browser.tabs.sendMessage(tabs[0].id, { type: MESSAGE_TYPES.IMPORT_DONE, totalImported, - }); + }) } - }; + } /** * Save memory to supermemory API */ const saveMemoryToSupermemory = async ( data: MemoryData, + actionSource: string, ): Promise<{ success: boolean; data?: unknown; error?: string }> => { try { - let containerTag: string = CONTAINER_TAGS.DEFAULT_PROJECT; + let containerTag: string = CONTAINER_TAGS.DEFAULT_PROJECT try { - const defaultProject = await getDefaultProject(); + const defaultProject = await getDefaultProject() if (defaultProject?.containerTag) { - containerTag = defaultProject.containerTag; + containerTag = defaultProject.containerTag } } catch (error) { - console.warn("Failed to get default project, using fallback:", error); + console.warn("Failed to get default project, using fallback:", error) } const payload: MemoryPayload = { containerTags: [containerTag], content: `${data.highlightedText}\n\n${data.html}\n\n${data?.url}`, metadata: { sm_source: "consumer" }, - }; + } + + const responseData = await saveMemory(payload) + + await trackEvent(POSTHOG_EVENT_KEY.SAVE_MEMORY_ATTEMPTED, { + source: `${POSTHOG_EVENT_KEY.SOURCE}_${actionSource}`, + has_highlight: !!data.highlightedText, + url_domain: data.url ? new URL(data.url).hostname : undefined, + }) - const responseData = await saveMemory(payload); - return { success: true, data: responseData }; + return { success: true, data: responseData } } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error", - }; + } } - }; + } const getRelatedMemories = async ( data: string, + eventSource: string, ): Promise<{ success: boolean; data?: unknown; error?: string }> => { try { const responseData = await searchMemories(data) const response = responseData as { results?: Array<{ memory?: string }> } - let memories = ""; + let memories = "" response.results?.forEach((result, index) => { memories += `[${index + 1}] ${result.memory} ` - }) + }) console.log("Memories:", memories) + await trackEvent(eventSource) return { success: true, data: memories } } catch (error) { return { @@ -141,7 +157,7 @@ export default defineBackground(() => { error: error instanceof Error ? error.message : "Unknown error", } } - }; + } /** * Handle extension messages @@ -154,48 +170,52 @@ export default defineBackground(() => { onProgress: sendMessageToCurrentTab, onComplete: sendImportDoneMessage, onError: async (error: Error) => { - await sendMessageToCurrentTab(`Error: ${error.message}`); + await sendMessageToCurrentTab(`Error: ${error.message}`) }, - }; + } - twitterImporter = new TwitterImporter(importConfig); - twitterImporter.startImport().catch(console.error); - sendResponse({ success: true }); - return true; + twitterImporter = new TwitterImporter(importConfig) + twitterImporter.startImport().catch(console.error) + sendResponse({ success: true }) + return true } // Handle regular memory save request if (message.action === MESSAGE_TYPES.SAVE_MEMORY) { - (async () => { + ;(async () => { try { const result = await saveMemoryToSupermemory( message.data as MemoryData, - ); - sendResponse(result); + message.actionSource || "unknown", + ) + sendResponse(result) } catch (error) { sendResponse({ success: false, error: error instanceof Error ? error.message : "Unknown error", - }); + }) } - })(); - return true; + })() + return true } if (message.action === MESSAGE_TYPES.GET_RELATED_MEMORIES) { - (async () => { + ;(async () => { try { - const result = await getRelatedMemories(message.data as string); - sendResponse(result); + const result = await getRelatedMemories( + message.data as string, + message.actionSource || "unknown", + ) + sendResponse(result) } catch (error) { sendResponse({ success: false, error: error instanceof Error ? error.message : "Unknown error", - }); + }) } - })(); - return true; + })() + return true } }, - ); -}); + ) +}) diff --git a/apps/browser-extension/entrypoints/content.ts b/apps/browser-extension/entrypoints/content.ts index 6c8a96c5..e755efa6 100644 --- a/apps/browser-extension/entrypoints/content.ts +++ b/apps/browser-extension/entrypoints/content.ts @@ -2,94 +2,105 @@ import { DOMAINS, ELEMENT_IDS, MESSAGE_TYPES, + POSTHOG_EVENT_KEY, STORAGE_KEYS, -} from "../utils/constants"; +} from "../utils/constants" +import { trackEvent } from "../utils/posthog" import { createChatGPTInputBarElement, createClaudeInputBarElement, - createSaveTweetElement, createT3InputBarElement, createTwitterImportButton, - createTwitterImportUI, DOMUtils, -} from "../utils/ui-components"; +} from "../utils/ui-components" 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); + DOMUtils.showToast(message.state) } else if (message.action === MESSAGE_TYPES.SAVE_MEMORY) { - await saveMemory(); + await saveMemory() } else if (message.type === MESSAGE_TYPES.IMPORT_UPDATE) { - updateTwitterImportUI(message); + updateTwitterImportUI(message) } else if (message.type === MESSAGE_TYPES.IMPORT_DONE) { - updateTwitterImportUI(message); + updateTwitterImportUI(message) } - }); + }) const observeForMemoriesDialog = () => { const observer = new MutationObserver(() => { if (DOMUtils.isOnDomain(DOMAINS.CHATGPT)) { - addSupermemoryButtonToMemoriesDialog(); - addSaveChatGPTElementBeforeComposerBtn(); + addSupermemoryButtonToMemoriesDialog() + addSaveChatGPTElementBeforeComposerBtn() } if (DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { - addSupermemoryIconToClaudeInput(); + addSupermemoryIconToClaudeInput() } if (DOMUtils.isOnDomain(DOMAINS.T3)) { - addSupermemoryIconToT3Input(); + addSupermemoryIconToT3Input() } - if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { - addTwitterImportButton(); - //addSaveTweetElement(); + if ( + DOMUtils.isOnDomain(DOMAINS.TWITTER) && + window.location.pathname === "/i/bookmarks" + ) { + addTwitterImportButton() + } else if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { + if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON) + } } - }); + }) observer.observe(document.body, { childList: true, subtree: true, - }); - }; + }) + } - if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { + if ( + DOMUtils.isOnDomain(DOMAINS.TWITTER) && + window.location.pathname === "/i/bookmarks" + ) { setTimeout(() => { - addTwitterImportButton(); // Wait 2 seconds for page to load + addTwitterImportButton() // Wait 2 seconds for page to load //addSaveTweetElement(); - }, 2000); + }, 2000) + } else if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { + if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON) + } } if (DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { setTimeout(() => { - addSupermemoryIconToClaudeInput(); // Wait 2 seconds for Claude page to load - }, 2000); + addSupermemoryIconToClaudeInput() // Wait 2 seconds for Claude page to load + }, 2000) } if (DOMUtils.isOnDomain(DOMAINS.T3)) { setTimeout(() => { - addSupermemoryIconToT3Input(); // Wait 2 seconds for T3 page to load - }, 2000); + addSupermemoryIconToT3Input() // Wait 2 seconds for T3 page to load + }, 2000) } if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", observeForMemoriesDialog); + document.addEventListener("DOMContentLoaded", observeForMemoriesDialog) } else { - observeForMemoriesDialog(); + observeForMemoriesDialog() } async function saveMemory() { try { - DOMUtils.showToast("loading"); + DOMUtils.showToast("loading") - const highlightedText = window.getSelection()?.toString() || ""; + const highlightedText = window.getSelection()?.toString() || "" - const url = window.location.href; + const url = window.location.href - const html = document.documentElement.outerHTML; + const html = document.documentElement.outerHTML const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.SAVE_MEMORY, @@ -98,75 +109,77 @@ export default defineContentScript({ highlightedText, url, }, - }); + actionSource: "context_menu", + }) - console.log("Response from enxtension:", response); + console.log("Response from enxtension:", response) if (response.success) { - DOMUtils.showToast("success"); + DOMUtils.showToast("success") } else { - DOMUtils.showToast("error"); + DOMUtils.showToast("error") } } catch (error) { - console.error("Error saving memory:", error); - DOMUtils.showToast("error"); + console.error("Error saving memory:", error) + DOMUtils.showToast("error") } } - async function getRelatedMemories() { + async function getRelatedMemories(actionSource: string) { try { const userQuery = - document.getElementById("prompt-textarea")?.textContent || ""; + document.getElementById("prompt-textarea")?.textContent || "" const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.GET_RELATED_MEMORIES, data: userQuery, - }); + actionSource: actionSource, + }) if (response.success && response.data) { - const promptElement = document.getElementById("prompt-textarea"); + const promptElement = document.getElementById("prompt-textarea") if (promptElement) { - const currentContent = promptElement.innerHTML; - promptElement.innerHTML = `${currentContent}<br>Supermemories: ${response.data}`; + const currentContent = promptElement.innerHTML + promptElement.innerHTML = `${currentContent}<br>Supermemories: ${response.data}` } } } catch (error) { - console.error("Error getting related memories:", error); + console.error("Error getting related memories:", error) } } function addSupermemoryButtonToMemoriesDialog() { - const dialogs = document.querySelectorAll('[role="dialog"]'); - let memoriesDialog: HTMLElement | null = null; + const dialogs = document.querySelectorAll('[role="dialog"]') + let memoriesDialog: HTMLElement | null = null for (const dialog of dialogs) { - const headerText = dialog.querySelector("h2"); + const headerText = dialog.querySelector("h2") if (headerText?.textContent?.includes("Saved memories")) { - memoriesDialog = dialog as HTMLElement; - break; + memoriesDialog = dialog as HTMLElement + break } } - if (!memoriesDialog) return; + if (!memoriesDialog) return - if (memoriesDialog.querySelector("#supermemory-save-button")) return; + if (memoriesDialog.querySelector("#supermemory-save-button")) return const deleteAllContainer = memoriesDialog.querySelector( ".mt-5.flex.justify-end", - ); - if (!deleteAllContainer) return; + ) + if (!deleteAllContainer) return - const supermemoryButton = document.createElement("button"); - supermemoryButton.id = "supermemory-save-button"; - supermemoryButton.className = "btn relative btn-primary-outline mr-2"; + 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"); + 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; @@ -178,388 +191,352 @@ export default defineContentScript({ font-size: 14px !important; margin-right: 8px !important; cursor: pointer !important; - `; + ` supermemoryButton.addEventListener("mouseenter", () => { - supermemoryButton.style.backgroundColor = "#2B2E33"; - }); + supermemoryButton.style.backgroundColor = "#2B2E33" + }) supermemoryButton.addEventListener("mouseleave", () => { - supermemoryButton.style.backgroundColor = "#1C2026"; - }); + supermemoryButton.style.backgroundColor = "#1C2026" + }) supermemoryButton.addEventListener("click", async () => { - await saveMemoriesToSupermemory(); - }); + await saveMemoriesToSupermemory() + }) deleteAllContainer.insertBefore( supermemoryButton, deleteAllContainer.firstChild, - ); + ) } async function saveMemoriesToSupermemory() { try { - DOMUtils.showToast("loading"); + DOMUtils.showToast("loading") const memoriesTable = document.querySelector( '[role="dialog"] table tbody', - ); + ) if (!memoriesTable) { - DOMUtils.showToast("error"); - return; + DOMUtils.showToast("error") + return } - const memoryRows = memoriesTable.querySelectorAll("tr"); - const memories: string[] = []; + const memoryRows = memoriesTable.querySelectorAll("tr") + const memories: string[] = [] memoryRows.forEach((row) => { - const memoryCell = row.querySelector("td .py-2.whitespace-pre-wrap"); + const memoryCell = row.querySelector("td .py-2.whitespace-pre-wrap") if (memoryCell?.textContent) { - memories.push(memoryCell.textContent.trim()); + memories.push(memoryCell.textContent.trim()) } - }); + }) - console.log("Memories:", memories); + console.log("Memories:", memories) if (memories.length === 0) { - DOMUtils.showToast("error"); - return; + DOMUtils.showToast("error") + return } - const combinedContent = `ChatGPT Saved Memories:\n\n${memories.map((memory, index) => `${index + 1}. ${memory}`).join("\n\n")}`; + const combinedContent = `ChatGPT Saved Memories:\n\n${memories.map((memory, index) => `${index + 1}. ${memory}`).join("\n\n")}` const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.SAVE_MEMORY, data: { html: combinedContent, }, - }); + actionSource: "chatgpt_memories_dialog", + }) - console.log({ response }); + console.log({ response }) if (response.success) { - DOMUtils.showToast("success"); + DOMUtils.showToast("success") } else { - DOMUtils.showToast("error"); + DOMUtils.showToast("error") } } catch (error) { - console.error("Error saving memories to supermemory:", error); - DOMUtils.showToast("error"); + console.error("Error saving memories to supermemory:", error) + DOMUtils.showToast("error") } } function addTwitterImportButton() { if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) { - return; + return } // Only show the import button on the bookmarks page if (window.location.pathname !== "/i/bookmarks") { - return; + return } if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { - return; - } - - const button = createTwitterImportButton(() => { - showTwitterImportUI(); - }); - - document.body.appendChild(button); - } - - function showTwitterImportUI() { - if (twitterImportUI) { - twitterImportUI.remove(); + return } - isTwitterImportOpen = true; - - // Check if user is authenticated - browser.storage.local.get([STORAGE_KEYS.BEARER_TOKEN], (result) => { - const isAuthenticated = !!result[STORAGE_KEYS.BEARER_TOKEN]; - - twitterImportUI = createTwitterImportUI( - hideTwitterImportUI, - async () => { - try { - await browser.runtime.sendMessage({ - type: MESSAGE_TYPES.BATCH_IMPORT_ALL, - }); - } catch (error) { - console.error("Error starting import:", error); - } - }, - isAuthenticated, - ); + const button = createTwitterImportButton(async () => { + try { + await browser.runtime.sendMessage({ + type: MESSAGE_TYPES.BATCH_IMPORT_ALL, + }) + await trackEvent(POSTHOG_EVENT_KEY.TWITTER_IMPORT_STARTED, { + source: `${POSTHOG_EVENT_KEY.SOURCE}_content_script`, + }) + } catch (error) { + console.error("Error starting import:", error) + } + }) - document.body.appendChild(twitterImportUI); - }); + document.body.appendChild(button) } - function hideTwitterImportUI() { - if (twitterImportUI) { - twitterImportUI.remove(); - twitterImportUI = null; - } - isTwitterImportOpen = false; - } function updateTwitterImportUI(message: { - type: string; - importedMessage?: string; - totalImported?: number; + type: string + importedMessage?: string + totalImported?: number }) { - if (!isTwitterImportOpen || !twitterImportUI) return; + const importButton = document.getElementById(ELEMENT_IDS.TWITTER_IMPORT_BUTTON) + if (!importButton) return - const statusDiv = twitterImportUI.querySelector( - `#${ELEMENT_IDS.TWITTER_IMPORT_STATUS}`, - ); - const button = twitterImportUI.querySelector( - `#${ELEMENT_IDS.TWITTER_IMPORT_BTN}`, - ); + const iconUrl = browser.runtime.getURL("/icon-16.png") if (message.type === MESSAGE_TYPES.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..."; - } + importButton.innerHTML = ` + <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> + <span style="font-weight: 500; font-size: 14px;">${message.importedMessage}</span> + ` + importButton.style.cursor = "default" } if (message.type === MESSAGE_TYPES.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> - `; - } - + importButton.innerHTML = ` + <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> + <span style="font-weight: 500; font-size: 14px; color: #059669;">✓ Imported ${message.totalImported} tweets!</span> + ` + setTimeout(() => { - hideTwitterImportUI(); - }, 3000); + importButton.innerHTML = ` + <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> + <span style="font-weight: 500; font-size: 14px;">Import Bookmarks</span> + ` + importButton.style.cursor = "pointer" + }, 3000) } } function addSaveChatGPTElementBeforeComposerBtn() { if (!DOMUtils.isOnDomain(DOMAINS.CHATGPT)) { - return; + return } - const composerButtons = document.querySelectorAll("button.composer-btn"); + const composerButtons = document.querySelectorAll("button.composer-btn") composerButtons.forEach((button) => { if (button.hasAttribute("data-supermemory-icon-added-before")) { - return; + return } - const parent = button.parentElement; - if (!parent) return; + const parent = button.parentElement + if (!parent) return - const parentSiblings = parent.parentElement?.children; - if (!parentSiblings) return; + const parentSiblings = parent.parentElement?.children + if (!parentSiblings) return - let hasSpeechButtonSibling = false; + let hasSpeechButtonSibling = false for (const sibling of parentSiblings) { if ( sibling.getAttribute("data-testid") === "composer-speech-button-container" ) { - hasSpeechButtonSibling = true; - break; + hasSpeechButtonSibling = true + break } } - if (!hasSpeechButtonSibling) return; + if (!hasSpeechButtonSibling) return - const grandParent = parent.parentElement; - if (!grandParent) 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; + button.setAttribute("data-supermemory-icon-added-before", "true") + return } const saveChatGPTElement = createChatGPTInputBarElement(async () => { - await getRelatedMemories(); - }); + await getRelatedMemories( + POSTHOG_EVENT_KEY.CHATGPT_CHAT_MEMORIES_SEARCHED, + ) + }) - saveChatGPTElement.id = `${ELEMENT_IDS.CHATGPT_INPUT_BAR_ELEMENT}-before-composer-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; + 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"); + button.setAttribute("data-supermemory-icon-added-before", "true") - grandParent.insertBefore(saveChatGPTElement, parent); - }); + grandParent.insertBefore(saveChatGPTElement, parent) + }) } function addSupermemoryIconToClaudeInput() { if (!DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { - return; + return } const targetContainers = document.querySelectorAll( ".relative.flex-1.flex.items-center.gap-2.shrink.min-w-0", - ); + ) targetContainers.forEach((container) => { if (container.hasAttribute("data-supermemory-icon-added")) { - return; + return } const existingIcon = container.querySelector( `#${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}`, - ); + ) if (existingIcon) { - container.setAttribute("data-supermemory-icon-added", "true"); - return; + container.setAttribute("data-supermemory-icon-added", "true") + return } const supermemoryIcon = createClaudeInputBarElement(async () => { - await getRelatedMemoriesForClaude(); - }); + await getRelatedMemoriesForClaude() + }) - supermemoryIcon.id = `${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; + supermemoryIcon.id = `${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}` - container.setAttribute("data-supermemory-icon-added", "true"); + container.setAttribute("data-supermemory-icon-added", "true") - container.insertBefore(supermemoryIcon, container.firstChild); - }); + container.insertBefore(supermemoryIcon, container.firstChild) + }) } async function getRelatedMemoriesForClaude() { try { - let userQuery = ""; + let userQuery = "" const supermemoryContainer = document.querySelector( '[data-supermemory-icon-added="true"]', - ); + ) if (supermemoryContainer?.parentElement?.previousElementSibling) { const pTag = supermemoryContainer.parentElement.previousElementSibling.querySelector( "p", - ); - userQuery = pTag?.innerText || pTag?.textContent || ""; + ) + userQuery = pTag?.innerText || pTag?.textContent || "" } if (!userQuery.trim()) { const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement; + ) as HTMLElement userQuery = - textareaElement?.innerText || textareaElement?.textContent || ""; + textareaElement?.innerText || textareaElement?.textContent || "" } if (!userQuery.trim()) { const inputElements = document.querySelectorAll( 'div[contenteditable="true"], textarea, input[type="text"]', - ); + ) for (const element of inputElements) { const text = (element as HTMLElement).innerText || - (element as HTMLInputElement).value; + (element as HTMLInputElement).value if (text?.trim()) { - userQuery = text.trim(); - break; + userQuery = text.trim() + break } } } - console.log("Claude query extracted:", userQuery); + console.log("Claude query extracted:", userQuery) if (!userQuery.trim()) { - console.log("No query text found"); - DOMUtils.showToast("error"); - return; + console.log("No query text found") + DOMUtils.showToast("error") + return } const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.GET_RELATED_MEMORIES, data: userQuery, - }); + actionSource: POSTHOG_EVENT_KEY.CLAUDE_CHAT_MEMORIES_SEARCHED, + }) - console.log("Claude memories response:", response); + console.log("Claude memories response:", response) if (response.success && response.data) { const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement; + ) as HTMLElement if (textareaElement) { - const currentContent = textareaElement.innerHTML; - textareaElement.innerHTML = `${currentContent}<br>Supermemories: ${response.data}`; + const currentContent = textareaElement.innerHTML + textareaElement.innerHTML = `${currentContent}<br>Supermemories: ${response.data}` - textareaElement.dispatchEvent( - new Event("input", { bubbles: true }), - ); + textareaElement.dispatchEvent(new Event("input", { bubbles: true })) } else { - console.log("Could not find Claude input area"); + console.log("Could not find Claude input area") } } else { console.log( "Failed to get memories:", response.error || "Unknown error", - ); + ) } } catch (error) { - console.error("Error getting related memories for Claude:", error); + console.error("Error getting related memories for Claude:", error) } } function addSupermemoryIconToT3Input() { if (!DOMUtils.isOnDomain(DOMAINS.T3)) { - return; + return } const targetContainers = document.querySelectorAll( ".flex.min-w-0.items-center.gap-2.overflow-hidden", - ); + ) targetContainers.forEach((container) => { if (container.hasAttribute("data-supermemory-icon-added")) { - return; + return } const existingIcon = container.querySelector( `#${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}`, - ); + ) if (existingIcon) { - container.setAttribute("data-supermemory-icon-added", "true"); - return; + container.setAttribute("data-supermemory-icon-added", "true") + return } const supermemoryIcon = createT3InputBarElement(async () => { - await getRelatedMemoriesForT3(); - }); + await getRelatedMemoriesForT3() + }) - supermemoryIcon.id = `${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; + supermemoryIcon.id = `${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}` - container.setAttribute("data-supermemory-icon-added", "true"); + container.setAttribute("data-supermemory-icon-added", "true") - container.insertBefore(supermemoryIcon, container.firstChild); - }); + container.insertBefore(supermemoryIcon, container.firstChild) + }) } async function getRelatedMemoriesForT3() { try { - let userQuery = ""; + let userQuery = "" const supermemoryContainer = document.querySelector( '[data-supermemory-icon-added="true"]', - ); + ) if ( supermemoryContainer?.parentElement?.parentElement ?.previousElementSibling @@ -567,48 +544,49 @@ export default defineContentScript({ const textareaElement = supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector( "textarea", - ); - userQuery = textareaElement?.value || ""; + ) + userQuery = textareaElement?.value || "" } if (!userQuery.trim()) { const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement; + ) as HTMLElement userQuery = - textareaElement?.innerText || textareaElement?.textContent || ""; + textareaElement?.innerText || textareaElement?.textContent || "" } if (!userQuery.trim()) { - const textareas = document.querySelectorAll("textarea"); + const textareas = document.querySelectorAll("textarea") for (const textarea of textareas) { - const text = (textarea as HTMLTextAreaElement).value; + const text = (textarea as HTMLTextAreaElement).value if (text?.trim()) { - userQuery = text.trim(); - break; + userQuery = text.trim() + break } } } - console.log("T3 query extracted:", userQuery); + console.log("T3 query extracted:", userQuery) if (!userQuery.trim()) { - console.log("No query text found"); - return; + console.log("No query text found") + return } const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.GET_RELATED_MEMORIES, data: userQuery, - }); + actionSource: POSTHOG_EVENT_KEY.T3_CHAT_MEMORIES_SEARCHED, + }) - console.log("T3 memories response:", response); + console.log("T3 memories response:", response) if (response.success && response.data) { - let textareaElement = null; + let textareaElement = null const supermemoryContainer = document.querySelector( '[data-supermemory-icon-added="true"]', - ); + ) if ( supermemoryContainer?.parentElement?.parentElement ?.previousElementSibling @@ -616,75 +594,41 @@ export default defineContentScript({ textareaElement = supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector( "textarea", - ); + ) } if (!textareaElement) { textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement; + ) as HTMLElement } if (textareaElement) { if (textareaElement.tagName === "TEXTAREA") { const currentContent = (textareaElement as HTMLTextAreaElement) - .value; - (textareaElement as HTMLTextAreaElement).value = - `${currentContent}\n\nSupermemories: ${response.data}`; + .value + ;(textareaElement as HTMLTextAreaElement).value = + `${currentContent}\n\nSupermemories: ${response.data}` } else { - const currentContent = textareaElement.innerHTML; - textareaElement.innerHTML = `${currentContent}<br>Supermemories: ${response.data}`; + const currentContent = textareaElement.innerHTML + textareaElement.innerHTML = `${currentContent}<br>Supermemories: ${response.data}` } - textareaElement.dispatchEvent( - new Event("input", { bubbles: true }), - ); + textareaElement.dispatchEvent(new Event("input", { bubbles: true })) } else { - console.log("Could not find T3 input area"); + console.log("Could not find T3 input area") } } else { console.log( "Failed to get memories:", response.error || "Unknown error", - ); + ) } } catch (error) { - console.error("Error getting related memories for T3:", error); + console.error("Error getting related memories for T3:", error) } } - // 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?.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 ( @@ -692,18 +636,18 @@ export default defineContentScript({ event.shiftKey && event.key === "m" ) { - event.preventDefault(); - await saveMemory(); + event.preventDefault() + await saveMemory() } - }); + }) window.addEventListener("message", (event) => { if (event.source !== window) { - return; + return } - const bearerToken = event.data.token; - - if (bearerToken) { + const bearerToken = event.data.token + const userData = event.data.userData + if (bearerToken && userData) { if ( !( window.location.hostname === "localhost" || @@ -712,18 +656,19 @@ export default defineContentScript({ ) ) { console.log( - "Bearer token is only allowed to be used on localhost or supermemory.ai", - ); - return; + "Bearer token and user data is only allowed to be used on localhost or supermemory.ai", + ) + return } chrome.storage.local.set( { [STORAGE_KEYS.BEARER_TOKEN]: bearerToken, + [STORAGE_KEYS.USER_DATA]: userData, }, () => {}, - ); + ) } - }); + }) }, -}); +}) diff --git a/apps/browser-extension/entrypoints/popup/App.tsx b/apps/browser-extension/entrypoints/popup/App.tsx index 3f960b34..ddb498c6 100644 --- a/apps/browser-extension/entrypoints/popup/App.tsx +++ b/apps/browser-extension/entrypoints/popup/App.tsx @@ -1,118 +1,152 @@ -import { useQueryClient } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import "./App.css"; -import { STORAGE_KEYS } from "../../utils/constants"; +import { useQueryClient } from "@tanstack/react-query" +import { useEffect, useState } from "react" +import "./App.css" +import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants" import { useDefaultProject, useProjects, useSetDefaultProject, -} from "../../utils/query-hooks"; -import type { Project } from "../../utils/types"; +} from "../../utils/query-hooks" +import type { Project } from "../../utils/types" function App() { - const [userSignedIn, setUserSignedIn] = useState<boolean>(false); - const [loading, setLoading] = useState<boolean>(true); - const [showProjectSelector, setShowProjectSelector] = - useState<boolean>(false); - const [currentUrl, setCurrentUrl] = useState<string>(""); - const [currentTitle, setCurrentTitle] = useState<string>(""); - const [saving, setSaving] = useState<boolean>(false); - const [activeTab, setActiveTab] = useState<"save" | "imports">("save"); + const [userSignedIn, setUserSignedIn] = useState<boolean>(false) + const [loading, setLoading] = useState<boolean>(true) + const [showProjectSelector, setShowProjectSelector] = useState<boolean>(false) + const [currentUrl, setCurrentUrl] = useState<string>("") + const [currentTitle, setCurrentTitle] = useState<string>("") + const [saving, setSaving] = useState<boolean>(false) + const [activeTab, setActiveTab] = useState<"save" | "imports">("save") - const queryClient = useQueryClient(); + const queryClient = useQueryClient() const { data: projects = [], isLoading: loadingProjects } = useProjects({ enabled: userSignedIn, - }); + }) const { data: defaultProject } = useDefaultProject({ enabled: userSignedIn, - }); - const setDefaultProjectMutation = useSetDefaultProject(); + }) + const setDefaultProjectMutation = useSetDefaultProject() useEffect(() => { const checkAuthStatus = async () => { try { const result = await chrome.storage.local.get([ STORAGE_KEYS.BEARER_TOKEN, - ]); - const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN]; - setUserSignedIn(isSignedIn); + ]) + const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN] + setUserSignedIn(isSignedIn) } catch (error) { - console.error("Error checking auth status:", error); - setUserSignedIn(false); + console.error("Error checking auth status:", error) + setUserSignedIn(false) } finally { - setLoading(false); + setLoading(false) } - }; + } const getCurrentTab = async () => { try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true, - }); + }) if (tabs.length > 0 && tabs[0].url && tabs[0].title) { - setCurrentUrl(tabs[0].url); - setCurrentTitle(tabs[0].title); + setCurrentUrl(tabs[0].url) + setCurrentTitle(tabs[0].title) } } catch (error) { - console.error("Error getting current tab:", error); + console.error("Error getting current tab:", error) } - }; + } - checkAuthStatus(); - getCurrentTab(); - }, []); + checkAuthStatus() + getCurrentTab() + }, []) const handleProjectSelect = (project: Project) => { setDefaultProjectMutation.mutate(project, { onSuccess: () => { - setShowProjectSelector(false); + setShowProjectSelector(false) }, onError: (error) => { - console.error("Error setting default project:", error); + console.error("Error setting default project:", error) }, - }); - }; + }) + } const handleShowProjectSelector = () => { - setShowProjectSelector(true); - }; + setShowProjectSelector(true) + } useEffect(() => { if (!defaultProject && projects.length > 0) { - const firstProject = projects[0]; - setDefaultProjectMutation.mutate(firstProject); + const firstProject = projects[0] + setDefaultProjectMutation.mutate(firstProject) } - }, [defaultProject, projects, setDefaultProjectMutation]); + }, [defaultProject, projects, setDefaultProjectMutation]) const handleSaveCurrentPage = async () => { - setSaving(true); + setSaving(true) + try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true, - }); + }) if (tabs.length > 0 && tabs[0].id) { - await chrome.tabs.sendMessage(tabs[0].id, { - action: "saveMemory", - }); + const response = await chrome.tabs.sendMessage(tabs[0].id, { + action: MESSAGE_TYPES.SAVE_MEMORY, + actionSource: "popup", + }) + + if (response?.success) { + await chrome.tabs.sendMessage(tabs[0].id, { + action: MESSAGE_TYPES.SHOW_TOAST, + state: "success", + }) + } else { + await chrome.tabs.sendMessage(tabs[0].id, { + action: MESSAGE_TYPES.SHOW_TOAST, + state: "error", + }) + } + + window.close() } } catch (error) { - console.error("Failed to save current page:", error); + console.error("Failed to save current page:", error) + + try { + const tabs = await chrome.tabs.query({ + active: true, + currentWindow: true, + }) + if (tabs.length > 0 && tabs[0].id) { + await chrome.tabs.sendMessage(tabs[0].id, { + action: MESSAGE_TYPES.SHOW_TOAST, + state: "error", + }) + } + } catch (toastError) { + console.error("Failed to show error toast:", toastError) + } + + window.close() } finally { - setSaving(false); + setSaving(false) } - }; + } const handleSignOut = async () => { try { - await chrome.storage.local.remove([STORAGE_KEYS.BEARER_TOKEN]); - setUserSignedIn(false); - queryClient.clear(); + await chrome.storage.local.remove([STORAGE_KEYS.BEARER_TOKEN]) + await chrome.storage.local.remove([STORAGE_KEYS.USER_DATA]) + await chrome.storage.local.remove([STORAGE_KEYS.DEFAULT_PROJECT]) + setUserSignedIn(false) + queryClient.clear() } catch (error) { - console.error("Error signing out:", error); + console.error("Error signing out:", error) } - }; + } if (loading) { return ( @@ -131,7 +165,7 @@ function App() { <div>Loading...</div> </div> </div> - ); + ) } return ( @@ -267,11 +301,11 @@ function App() { <div className="flex flex-col gap-4"> <div className="flex flex-col gap-2"> <button - className="w-full py-3 px-3 bg-white text-black border border-gray-200 rounded-md text-sm font-medium cursor-pointer flex items-center justify-center transition-colors duration-200 hover:bg-gray-50" + className="w-full py-3 px-3 bg-white text-black border border-gray-200 rounded-md text-sm font-medium cursor-pointer flex items-center justify-start transition-colors duration-200 hover:bg-gray-50" onClick={() => { chrome.tabs.create({ url: "https://chatgpt.com/#settings/Personalization", - }); + }) }} type="button" > @@ -286,17 +320,33 @@ function App() { <title>OpenAI</title> <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" /> </svg> - Import ChatGPT Memories + <div className="text-left"> + <p>Import ChatGPT Memories</p> + <p className="m-0 text-[10px] text-gray-500 leading-tight"> + open 'manage', save your memories to supermemory + </p> + </div> </button> </div> <div className="flex flex-col gap-2"> <button className="w-full py-3 px-3 bg-white text-black border border-gray-200 rounded-md text-sm font-medium cursor-pointer flex items-center justify-center transition-colors duration-200 outline-none appearance-none hover:bg-gray-50 focus:outline-none" - onClick={() => { - chrome.tabs.create({ - url: "https://x.com/i/bookmarks", - }); + onClick={async () => { + const [activeTab] = await chrome.tabs.query({ + active: true, + currentWindow: true, + }) + + const targetUrl = "https://x.com/i/bookmarks" + + if (activeTab?.url === targetUrl) { + return + } + + await chrome.tabs.create({ + url: targetUrl, + }) }} type="button" > @@ -310,11 +360,13 @@ function App() { <title>X Twitter Logo</title> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /> </svg> - Import X Bookmarks + <div className="text-left"> + <p>Import X/Twitter Bookmarks</p> + <p className="m-0 text-[10px] text-gray-500 leading-tight"> + Click on supermemory on top right to import bookmarks + </p> + </div> </button> - <p className="m-0 text-xs text-gray-500 leading-tight pl-1"> - Click on supermemory on top right to import bookmarks - </p> </div> </div> </div> @@ -393,7 +445,7 @@ function App() { <button className="bg-transparent border-none text-blue-500 cursor-pointer underline text-sm p-0 hover:text-blue-700" onClick={() => { - window.open("mailto:[email protected]", "_blank"); + window.open("mailto:[email protected]", "_blank") }} type="button" > @@ -408,7 +460,7 @@ function App() { url: import.meta.env.PROD ? "https://app.supermemory.ai/login" : "http://localhost:3000/login", - }); + }) }} type="button" > @@ -419,7 +471,7 @@ function App() { )} </div> </div> - ); + ) } -export default App; +export default App diff --git a/apps/browser-extension/entrypoints/popup/style.css b/apps/browser-extension/entrypoints/popup/style.css index 684e1ac6..ea67f153 100644 --- a/apps/browser-extension/entrypoints/popup/style.css +++ b/apps/browser-extension/entrypoints/popup/style.css @@ -15,7 +15,6 @@ -webkit-text-size-adjust: 100%; } - @media (prefers-color-scheme: light) { :root { color: #213547; diff --git a/apps/browser-extension/entrypoints/welcome/Welcome.tsx b/apps/browser-extension/entrypoints/welcome/Welcome.tsx index 00bd01cc..9463eba4 100644 --- a/apps/browser-extension/entrypoints/welcome/Welcome.tsx +++ b/apps/browser-extension/entrypoints/welcome/Welcome.tsx @@ -77,7 +77,7 @@ function Welcome() { url: import.meta.env.PROD ? "https://app.supermemory.ai/login" : "http://localhost:3000/login", - }); + }) }} type="button" > @@ -101,7 +101,7 @@ function Welcome() { </div> </div> </div> - ); + ) } -export default Welcome; +export default Welcome |