diff --git a/apps/browser-extension/entrypoints/content.ts b/apps/browser-extension/entrypoints/content.ts index 97f5fc44..a66532f5 100644 --- a/apps/browser-extension/entrypoints/content.ts +++ b/apps/browser-extension/entrypoints/content.ts @@ -6,6 +6,8 @@ import { } from "../utils/constants" import { createChatGPTInputBarElement, + createClaudeInputBarElement, + createT3InputBarElement, createSaveTweetElement, createTwitterImportButton, createTwitterImportUI, @@ -36,6 +38,12 @@ export default defineContentScript({ addSupermemoryButtonToMemoriesDialog() addSaveChatGPTElementBeforeComposerBtn() } + if (DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { + addSupermemoryIconToClaudeInput() + } + if (DOMUtils.isOnDomain(DOMAINS.T3)) { + addSupermemoryIconToT3Input() + } if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { addTwitterImportButton() //addSaveTweetElement(); @@ -46,21 +54,6 @@ export default defineContentScript({ childList: true, subtree: true, }) - - 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" - ) { - addTwitterImportButton() - //addSaveTweetElement(); - } } if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { @@ -70,6 +63,18 @@ export default defineContentScript({ }, 2000) } + if (DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { + setTimeout(() => { + 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) + } + if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", observeForMemoriesDialog) } else { @@ -390,6 +395,206 @@ export default defineContentScript({ }) } + function addSupermemoryIconToClaudeInput() { + if (!DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { + 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 + } + + const existingIcon = container.querySelector( + `#${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}`, + ) + if (existingIcon) { + container.setAttribute("data-supermemory-icon-added", "true") + return + } + + const supermemoryIcon = createClaudeInputBarElement(async () => { + await getRelatedMemoriesForClaude() + }) + + 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.insertBefore(supermemoryIcon, container.firstChild) + }) + } + + async function getRelatedMemoriesForClaude() { + try { + 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 || "" + } + + if (!userQuery.trim()) { + const textareaElement = document.querySelector('div[contenteditable="true"]') as HTMLElement + userQuery = 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 + if (text?.trim()) { + userQuery = text.trim() + break + } + } + } + + console.log("Claude query extracted:", userQuery) + + if (!userQuery.trim()) { + console.log("No query text found") + DOMUtils.showToast("error") + return + } + + const response = await browser.runtime.sendMessage({ + action: MESSAGE_TYPES.GET_RELATED_MEMORIES, + data: userQuery, + }) + + console.log("Claude memories response:", response) + + if (response.success && response.data) { + const textareaElement = document.querySelector('div[contenteditable="true"]') as HTMLElement + + if (textareaElement) { + const currentContent = textareaElement.innerHTML + textareaElement.innerHTML = `${currentContent}
Supermemories: ${response.data}` + + textareaElement.dispatchEvent(new Event('input', { bubbles: true })) + } else { + 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) + } + } + + function addSupermemoryIconToT3Input() { + if (!DOMUtils.isOnDomain(DOMAINS.T3)) { + 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 + } + + const existingIcon = container.querySelector( + `#${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}`, + ) + if (existingIcon) { + container.setAttribute("data-supermemory-icon-added", "true") + return + } + + const supermemoryIcon = createT3InputBarElement(async () => { + await getRelatedMemoriesForT3() + }) + + 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.insertBefore(supermemoryIcon, container.firstChild) + }) + } + + async function getRelatedMemoriesForT3() { + try { + let userQuery = "" + + const supermemoryContainer = document.querySelector('[data-supermemory-icon-added="true"]') + if (supermemoryContainer?.parentElement?.parentElement?.previousElementSibling) { + const textareaElement = supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector('textarea') + userQuery = textareaElement?.value || "" + } + + if (!userQuery.trim()) { + const textareaElement = document.querySelector('div[contenteditable="true"]') as HTMLElement + userQuery = textareaElement?.innerText || textareaElement?.textContent || "" + } + + if (!userQuery.trim()) { + const textareas = document.querySelectorAll('textarea') + for (const textarea of textareas) { + const text = (textarea as HTMLTextAreaElement).value + if (text?.trim()) { + userQuery = text.trim() + break + } + } + } + + console.log("T3 query extracted:", userQuery) + + if (!userQuery.trim()) { + console.log("No query text found") + return + } + + const response = await browser.runtime.sendMessage({ + action: MESSAGE_TYPES.GET_RELATED_MEMORIES, + data: userQuery, + }) + + console.log("T3 memories response:", response) + + if (response.success && response.data) { + let textareaElement = null + const supermemoryContainer = document.querySelector('[data-supermemory-icon-added="true"]') + if (supermemoryContainer?.parentElement?.parentElement?.previousElementSibling) { + textareaElement = supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector('textarea') + } + + if (!textareaElement) { + textareaElement = document.querySelector('div[contenteditable="true"]') as HTMLElement + } + + if (textareaElement) { + if (textareaElement.tagName === 'TEXTAREA') { + const currentContent = (textareaElement as HTMLTextAreaElement).value + ;(textareaElement as HTMLTextAreaElement).value = `${currentContent}\n\nSupermemories: ${response.data}` + } else { + const currentContent = textareaElement.innerHTML + textareaElement.innerHTML = `${currentContent}
Supermemories: ${response.data}` + } + + textareaElement.dispatchEvent(new Event('input', { bubbles: true })) + } else { + 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) + } + } + // TODO: Add Tweet Capture Functionality function _addSaveTweetElement() { if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) { diff --git a/apps/browser-extension/utils/constants.ts b/apps/browser-extension/utils/constants.ts index b499a359..7634759b 100644 --- a/apps/browser-extension/utils/constants.ts +++ b/apps/browser-extension/utils/constants.ts @@ -35,6 +35,8 @@ export const ELEMENT_IDS = { SUPERMEMORY_SAVE_BUTTON: "sm-save-button", SAVE_TWEET_ELEMENT: "sm-save-tweet-element", CHATGPT_INPUT_BAR_ELEMENT: "sm-chatgpt-input-bar-element", + CLAUDE_INPUT_BAR_ELEMENT: "sm-claude-input-bar-element", + T3_INPUT_BAR_ELEMENT: "sm-t3-input-bar-element", } as const /** @@ -53,6 +55,8 @@ export const UI_CONFIG = { export const DOMAINS = { TWITTER: ["x.com", "twitter.com"], CHATGPT: ["chatgpt.com", "chat.openai.com"], + CLAUDE: ["claude.ai"], + T3: ["t3.chat"], SUPERMEMORY: ["localhost", "supermemory.ai", "app.supermemory.ai"], } as const diff --git a/apps/browser-extension/utils/ui-components.ts b/apps/browser-extension/utils/ui-components.ts index c160d63e..9c060017 100644 --- a/apps/browser-extension/utils/ui-components.ts +++ b/apps/browser-extension/utils/ui-components.ts @@ -369,6 +369,96 @@ export function createChatGPTInputBarElement(onClick: () => void): HTMLElement { return iconButton } +/** + * Creates a save element button for Claude input bar + * @param onClick - Click handler for the button + * @returns HTMLElement - The save button element + */ +export function createClaudeInputBarElement(onClick: () => void): HTMLElement { + const iconButton = document.createElement("div") + iconButton.style.cssText = ` + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + cursor: pointer; + transition: all 0.2s ease; + border-radius: 6px; + background: transparent; + border: 1px solid rgba(0, 0, 0, 0.1); + ` + + const iconFileName = "/icon-16.png" + const iconUrl = browser.runtime.getURL(iconFileName) + iconButton.innerHTML = ` + Get Related Memories from supermemory + ` + + iconButton.addEventListener("mouseenter", () => { + iconButton.style.backgroundColor = "rgba(0, 0, 0, 0.05)" + iconButton.style.borderColor = "rgba(0, 0, 0, 0.2)" + }) + + iconButton.addEventListener("mouseleave", () => { + iconButton.style.backgroundColor = "transparent" + iconButton.style.borderColor = "rgba(0, 0, 0, 0.1)" + }) + + iconButton.addEventListener("click", (event) => { + event.stopPropagation() + event.preventDefault() + onClick() + }) + + return iconButton +} + +/** + * Creates a save element button for T3.chat input bar + * @param onClick - Click handler for the button + * @returns HTMLElement - The save button element + */ +export function createT3InputBarElement(onClick: () => void): HTMLElement { + const iconButton = document.createElement("div") + iconButton.style.cssText = ` + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + cursor: pointer; + transition: all 0.2s ease; + border-radius: 6px; + background: transparent; + border: 1px solid rgba(0, 0, 0, 0.1); + ` + + const iconFileName = "/icon-16.png" + const iconUrl = browser.runtime.getURL(iconFileName) + iconButton.innerHTML = ` + Get Related Memories from supermemory + ` + + iconButton.addEventListener("mouseenter", () => { + iconButton.style.backgroundColor = "rgba(0, 0, 0, 0.05)" + iconButton.style.borderColor = "rgba(0, 0, 0, 0.2)" + }) + + iconButton.addEventListener("mouseleave", () => { + iconButton.style.backgroundColor = "transparent" + iconButton.style.borderColor = "rgba(0, 0, 0, 0.1)" + }) + + iconButton.addEventListener("click", (event) => { + event.stopPropagation() + event.preventDefault() + onClick() + }) + + return iconButton +} + /** * Utility functions for DOM manipulation */