Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/browser-extension/entrypoints/content/chatgpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,16 @@ function setupChatGPTPromptCapture() {
document.body.setAttribute("data-chatgpt-prompt-capture-setup", "true")

const capturePromptContent = async (source: string) => {
const result = await chrome.storage.local.get([
STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED,
])
const autoCapturePromptsEnabled =
result[STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED] ?? false

if (!autoCapturePromptsEnabled) {
console.log("Auto capture prompts is disabled, skipping prompt capture")
return
}
const promptTextarea = document.getElementById("prompt-textarea")

let promptContent = ""
Expand Down
10 changes: 10 additions & 0 deletions apps/browser-extension/entrypoints/content/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,16 @@ function setupClaudePromptCapture() {
}
document.body.setAttribute("data-claude-prompt-capture-setup", "true")
const captureClaudePromptContent = async (source: string) => {
const result = await chrome.storage.local.get([
STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED,
])
const autoCapturePromptsEnabled =
result[STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED] ?? false

if (!autoCapturePromptsEnabled) {
console.log("Auto capture prompts is disabled, skipping prompt capture")
return
}
let promptContent = ""

const contentEditableDiv = document.querySelector(
Expand Down
77 changes: 49 additions & 28 deletions apps/browser-extension/entrypoints/content/t3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,32 +115,35 @@ function setupT3RouteChangeDetection() {

function addSupermemoryIconToT3Input() {
const targetContainers = document.querySelectorAll(
".flex.min-w-0.items-center.gap-2.overflow-hidden",
".flex.min-w-0.items-center.gap-2",
)

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(POSTHOG_EVENT_KEY.T3_CHAT_MEMORIES_SEARCHED)
})
const container = targetContainers[0]
if (!container) {
return
}

supermemoryIcon.id = `${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
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
}

container.insertBefore(supermemoryIcon, container.firstChild)
const supermemoryIcon = createT3InputBarElement(async () => {
await getRelatedMemoriesForT3(POSTHOG_EVENT_KEY.T3_CHAT_MEMORIES_SEARCHED)
})

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(actionSource: string) {
Expand All @@ -150,11 +153,9 @@ async function getRelatedMemoriesForT3(actionSource: string) {
const supermemoryContainer = document.querySelector(
'[data-supermemory-icon-added="true"]',
)
if (
supermemoryContainer?.parentElement?.parentElement?.previousElementSibling
) {
if (supermemoryContainer?.parentElement?.previousElementSibling) {
const textareaElement =
supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector(
supermemoryContainer.parentElement.previousElementSibling.querySelector(
"textarea",
)
userQuery = textareaElement?.value || ""
Expand Down Expand Up @@ -220,12 +221,9 @@ async function getRelatedMemoriesForT3(actionSource: string) {
const supermemoryContainer = document.querySelector(
'[data-supermemory-icon-added="true"]',
)
if (
supermemoryContainer?.parentElement?.parentElement
?.previousElementSibling
) {
if (supermemoryContainer?.parentElement?.previousElementSibling) {
textareaElement =
supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector(
supermemoryContainer.parentElement.previousElementSibling.querySelector(
"textarea",
)
}
Expand Down Expand Up @@ -499,6 +497,16 @@ function setupT3PromptCapture() {
document.body.setAttribute("data-t3-prompt-capture-setup", "true")

const captureT3PromptContent = async (source: string) => {
const result = await chrome.storage.local.get([
STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED,
])
const autoCapturePromptsEnabled =
result[STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED] ?? false

if (!autoCapturePromptsEnabled) {
console.log("Auto capture prompts is disabled, skipping prompt capture")
return
}
let promptContent = ""

const textarea = document.querySelector("textarea") as HTMLTextAreaElement
Expand Down Expand Up @@ -601,6 +609,19 @@ function setupT3PromptCapture() {
if (promptContent.trim()) {
console.log("T3 prompt submitted via Enter key:", promptContent)

const result = await chrome.storage.local.get([
STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED,
])
const autoCapturePromptsEnabled =
result[STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED] ?? false

if (!autoCapturePromptsEnabled) {
console.log(
"Auto capture prompts is disabled, skipping prompt capture",
)
return
}

try {
await browser.runtime.sendMessage({
action: MESSAGE_TYPES.CAPTURE_PROMPT,
Expand Down
109 changes: 96 additions & 13 deletions apps/browser-extension/entrypoints/popup/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,57 @@ import {
} from "../../utils/query-hooks"
import type { Project } from "../../utils/types"

const Tooltip = ({
children,
content,
}: {
children: React.ReactNode
content: string
}) => {
const [isVisible, setIsVisible] = useState(false)

return (
<div className="relative inline-flex items-center gap-1">
<button
type="button"
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
className="cursor-help bg-transparent border-none p-0 text-left"
>
{children}
</button>
<button
type="button"
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
className="cursor-help bg-transparent border-none p-0 text-gray-400 hover:text-gray-600 transition-colors"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<title>More information</title>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
</button>
{isVisible && (
<div className="absolute z-50 px-2 py-1 text-xs text-white bg-gray-800 rounded shadow-lg bottom-full right-0 mb-1 max-w-xs break-words">
{content}
<div className="absolute top-full right-4 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-800" />
</div>
)}
</div>
)
}

function App() {
const [userSignedIn, setUserSignedIn] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(true)
Expand All @@ -22,6 +73,8 @@ function App() {
"save",
)
const [autoSearchEnabled, setAutoSearchEnabled] = useState<boolean>(false)
const [autoCapturePromptsEnabled, setAutoCapturePromptsEnabled] =
useState<boolean>(false)
const [authInvalidated, setAuthInvalidated] = useState<boolean>(false)

const queryClient = useQueryClient()
Expand All @@ -43,6 +96,7 @@ function App() {
const result = await chrome.storage.local.get([
STORAGE_KEYS.BEARER_TOKEN,
STORAGE_KEYS.AUTO_SEARCH_ENABLED,
STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED,
])
const hasToken = !!result[STORAGE_KEYS.BEARER_TOKEN]

Expand Down Expand Up @@ -70,6 +124,10 @@ function App() {
const autoSearchSetting =
result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false
setAutoSearchEnabled(autoSearchSetting)

const autoCapturePromptsSetting =
result[STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED] ?? false
setAutoCapturePromptsEnabled(autoCapturePromptsSetting)
} catch (error) {
console.error("Error checking auth status:", error)
setUserSignedIn(false)
Expand Down Expand Up @@ -178,6 +236,17 @@ function App() {
}
}

const handleAutoCapturePromptsToggle = async (enabled: boolean) => {
try {
await chrome.storage.local.set({
[STORAGE_KEYS.AUTO_CAPTURE_PROMPTS_ENABLED]: enabled,
})
setAutoCapturePromptsEnabled(enabled)
} catch (error) {
console.error("Error updating auto capture prompts setting:", error)
}
}

const handleSignOut = async () => {
try {
await chrome.storage.local.remove([STORAGE_KEYS.BEARER_TOKEN])
Expand Down Expand Up @@ -457,15 +526,13 @@ function App() {
<h3 className="text-base font-semibold text-black mb-3">
Chat Integration
</h3>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex flex-col">
<span className="text-sm font-medium text-black">
Auto Search Memories
</span>
<span className="text-xs text-gray-500">
Automatically search your memories while typing in chat
apps
</span>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200 mb-3">
<div className="flex items-center">
<Tooltip content="Automatically search your memories while typing in chat apps">
<span className="text-sm font-medium text-black cursor-help">
Auto Search Memories
</span>
</Tooltip>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
Expand All @@ -479,10 +546,26 @@ function App() {
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-gray-700" />
</label>
</div>
<p className="text-xs text-gray-500 mt-2">
When enabled, supermemory will search your memories as you
type in ChatGPT, Claude, and T3.chat
</p>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex items-center">
<Tooltip content="Automatically save your prompts as memories in chat apps">
<span className="text-sm font-medium text-black cursor-help">
Auto Capture Prompts
</span>
</Tooltip>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
checked={autoCapturePromptsEnabled}
className="sr-only peer"
onChange={(e) =>
handleAutoCapturePromptsToggle(e.target.checked)
}
type="checkbox"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-gray-700" />
</label>
</div>
</div>
</div>
)}
Expand Down
1 change: 1 addition & 0 deletions apps/browser-extension/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const STORAGE_KEYS = {
TWITTER_AUTH_TOKEN: "twitter-auth-token",
DEFAULT_PROJECT: "sm-default-project",
AUTO_SEARCH_ENABLED: "sm-auto-search-enabled",
AUTO_CAPTURE_PROMPTS_ENABLED: "sm-auto-capture-prompts-enabled",
} as const

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/browser-extension/wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default defineConfig({
manifest: {
name: "supermemory",
homepage_url: "https://supermemory.ai",
version: "6.0.100",
version: "6.0.101",
permissions: ["contextMenus", "storage", "activeTab", "webRequest", "tabs"],
host_permissions: [
"*://x.com/*",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/views/chat/chat-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export function ChatMessages() {
},
},
}),
maxSteps: 2,
maxSteps: 10,
onFinish: (result) => {
const activeId = activeChatIdRef.current
if (!activeId) return
Expand Down
26 changes: 23 additions & 3 deletions packages/tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ const addTool = addMemoryTool(process.env.SUPERMEMORY_API_KEY!, {

#### AI SDK Middleware with Supermemory

> [!CAUTION]
> `withSupermemory` is in beta

- `withSupermemory` will take advantage supermemory profile v4 endpoint personalized based on container tag
- Make sure you have `SUPERMEMORY_API_KEY` in env

Expand All @@ -83,6 +80,27 @@ const result = await generateText({
console.log(result.text)
```

#### Conversation Grouping

Use the `conversationId` option to group messages into a single document for contextual memory generation:

```typescript
import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"

const modelWithMemory = withSupermemory(openai("gpt-5"), "user_id_life", {
conversationId: "conversation-456"
})

const result = await generateText({
model: modelWithMemory,
messages: [{ role: "user", content: "where do i live?" }],
})

console.log(result.text)
```

#### Verbose Mode

Enable verbose logging to see detailed information about memory search and transformation:
Expand Down Expand Up @@ -280,12 +298,14 @@ The `withSupermemory` middleware accepts additional configuration options:

```typescript
interface WithSupermemoryOptions {
conversationId?: string
verbose?: boolean
mode?: "profile" | "query" | "full"
addMemory?: "always" | "never"
}
```

- **conversationId**: Optional conversation ID to group messages into a single document for contextual memory generation
- **verbose**: Enable detailed logging of memory search and injection process (default: false)
- **mode**: Memory search mode - "profile" (default), "query", or "full"
- **addMemory**: Automatic memory storage mode - "always" or "never" (default: "never")
Expand Down
Loading
Loading