feat (extension): add claude.ai and t3.chat integration support (#400)

This commit is contained in:
MaheshtheDev 2025-09-01 04:02:40 +00:00
parent 3157b987fc
commit bc2443e15c
3 changed files with 314 additions and 15 deletions

View file

@ -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}<br>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}<br>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)) {

View file

@ -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

View file

@ -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 = `
<img src="${iconUrl}" width="20" height="20" alt="Get Related Memories from supermemory" style="border-radius: 4px;" />
`
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 = `
<img src="${iconUrl}" width="20" height="20" alt="Get Related Memories from supermemory" style="border-radius: 4px;" />
`
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
*/