aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2025-09-24 23:26:38 -0700
committerDhravya Shah <[email protected]>2025-09-24 23:26:38 -0700
commit697315b9dc64b1c7e5534ea64dcd22b13d57de24 (patch)
treeff296b7343e4ff86eaa8947ef8d0bc0c695b4d4f /apps
parentbump version (diff)
parentfix: SSR issue while localStorage is being accessed (#437) (diff)
downloadsupermemory-697315b9dc64b1c7e5534ea64dcd22b13d57de24.tar.xz
supermemory-697315b9dc64b1c7e5534ea64dcd22b13d57de24.zip
Merge branch 'main' of https://github.com/supermemoryai/supermemory
Diffstat (limited to 'apps')
-rw-r--r--apps/browser-extension/entrypoints/popup/App.tsx108
-rw-r--r--apps/browser-extension/utils/api.ts29
-rw-r--r--apps/browser-extension/utils/query-hooks.ts12
-rw-r--r--apps/browser-extension/utils/ui-components.ts52
-rw-r--r--apps/browser-extension/wxt.config.ts2
-rw-r--r--apps/web/components/connect-ai-modal.tsx226
-rw-r--r--apps/web/components/views/billing.tsx2
-rw-r--r--apps/web/components/views/profile.tsx2
8 files changed, 362 insertions, 71 deletions
diff --git a/apps/browser-extension/entrypoints/popup/App.tsx b/apps/browser-extension/entrypoints/popup/App.tsx
index 3e4f15e2..c6e8468c 100644
--- a/apps/browser-extension/entrypoints/popup/App.tsx
+++ b/apps/browser-extension/entrypoints/popup/App.tsx
@@ -1,11 +1,13 @@
import { useQueryClient } from "@tanstack/react-query"
import { useEffect, useState } from "react"
import "./App.css"
+import { validateAuthToken } from "../../utils/api"
import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants"
import {
useDefaultProject,
useProjects,
useSetDefaultProject,
+ useUserData,
} from "../../utils/query-hooks"
import type { Project } from "../../utils/types"
@@ -20,6 +22,7 @@ function App() {
"save",
)
const [autoSearchEnabled, setAutoSearchEnabled] = useState<boolean>(false)
+ const [authInvalidated, setAuthInvalidated] = useState<boolean>(false)
const queryClient = useQueryClient()
const { data: projects = [], isLoading: loadingProjects } = useProjects({
@@ -28,8 +31,12 @@ function App() {
const { data: defaultProject } = useDefaultProject({
enabled: userSignedIn,
})
+ const { data: userData, isLoading: loadingUserData } = useUserData({
+ enabled: userSignedIn,
+ })
const setDefaultProjectMutation = useSetDefaultProject()
+ // biome-ignore lint/correctness/useExhaustiveDependencies: suppress dependency analysis
useEffect(() => {
const checkAuthStatus = async () => {
try {
@@ -37,8 +44,28 @@ function App() {
STORAGE_KEYS.BEARER_TOKEN,
STORAGE_KEYS.AUTO_SEARCH_ENABLED,
])
- const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN]
- setUserSignedIn(isSignedIn)
+ const hasToken = !!result[STORAGE_KEYS.BEARER_TOKEN]
+
+ if (hasToken) {
+ const isTokenValid = await validateAuthToken()
+
+ if (isTokenValid) {
+ setUserSignedIn(true)
+ setAuthInvalidated(false)
+ } else {
+ await chrome.storage.local.remove([
+ STORAGE_KEYS.BEARER_TOKEN,
+ STORAGE_KEYS.USER_DATA,
+ STORAGE_KEYS.DEFAULT_PROJECT,
+ ])
+ queryClient.clear()
+ setUserSignedIn(false)
+ setAuthInvalidated(true)
+ }
+ } else {
+ setUserSignedIn(false)
+ setAuthInvalidated(false)
+ }
const autoSearchSetting =
result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false
@@ -46,6 +73,7 @@ function App() {
} catch (error) {
console.error("Error checking auth status:", error)
setUserSignedIn(false)
+ setAuthInvalidated(false)
} finally {
setLoading(false)
}
@@ -397,6 +425,34 @@ function App() {
</div>
) : (
<div className="flex flex-col gap-4 min-h-[200px]">
+ {/* Account Section */}
+ <div>
+ <h3 className="text-base font-semibold text-black mb-3">
+ Account
+ </h3>
+ <div className="p-3 bg-gray-50 rounded-lg border border-gray-200">
+ {loadingUserData ? (
+ <div className="text-sm text-gray-500">
+ Loading account data...
+ </div>
+ ) : userData?.email ? (
+ <div className="flex justify-between items-center">
+ <span className="text-xs font-medium text-black">
+ Email
+ </span>
+ <span className="text-sm text-gray-600">
+ {userData.email}
+ </span>
+ </div>
+ ) : (
+ <div className="text-sm text-gray-500">
+ No email found
+ </div>
+ )}
+ </div>
+ </div>
+
+ {/* Chat Integration Section */}
<div className="mb-4">
<h3 className="text-base font-semibold text-black mb-3">
Chat Integration
@@ -480,23 +536,37 @@ function App() {
</div>
) : (
<div className="text-center py-2">
- <div className="mb-8">
- <h2 className="m-0 mb-4 text-sm font-normal text-black leading-tight">
- Login to unlock all chrome extension features
- </h2>
-
- <ul className="list-none p-0 m-0 text-left">
- <li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
- Save any page to your supermemory
- </li>
- <li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
- Import all your Twitter / X Bookmarks
- </li>
- <li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
- Import your ChatGPT Memories
- </li>
- </ul>
- </div>
+ {authInvalidated ? (
+ <div className="mb-8">
+ <div className="p-3 mb-4 bg-red-50 border border-red-200 rounded-lg">
+ <h2 className="m-0 mb-2 text-sm font-semibold text-red-800 leading-tight">
+ Session Expired
+ </h2>
+ <p className="m-0 text-xs text-red-600 leading-tight">
+ Logged out since authentication was invalidated. Please
+ login again.
+ </p>
+ </div>
+ </div>
+ ) : (
+ <div className="mb-8">
+ <h2 className="m-0 mb-4 text-sm font-normal text-black leading-tight">
+ Login to unlock all chrome extension features
+ </h2>
+
+ <ul className="list-none p-0 m-0 text-left">
+ <li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
+ Save any page to your supermemory
+ </li>
+ <li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
+ Import all your Twitter / X Bookmarks
+ </li>
+ <li className="py-1.5 text-sm text-black relative pl-5 before:content-['•'] before:absolute before:left-0 before:text-black before:font-bold">
+ Import your ChatGPT Memories
+ </li>
+ </ul>
+ </div>
+ )}
<div className="mt-8">
<p className="m-0 mb-4 text-sm text-gray-500">
diff --git a/apps/browser-extension/utils/api.ts b/apps/browser-extension/utils/api.ts
index 2a4c838b..d98b39f5 100644
--- a/apps/browser-extension/utils/api.ts
+++ b/apps/browser-extension/utils/api.ts
@@ -100,6 +100,35 @@ export async function setDefaultProject(project: Project): Promise<void> {
}
/**
+ * Validate if current bearer token is still valid
+ */
+export async function validateAuthToken(): Promise<boolean> {
+ try {
+ await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects")
+ return true
+ } catch (error) {
+ if (error instanceof AuthenticationError) {
+ return false
+ }
+ console.error("Failed to validate auth token:", error)
+ return true
+ }
+}
+
+/**
+ * Get user data from storage
+ */
+export async function getUserData(): Promise<{ email?: string } | null> {
+ try {
+ const result = await chrome.storage.local.get([STORAGE_KEYS.USER_DATA])
+ return result[STORAGE_KEYS.USER_DATA] || null
+ } catch (error) {
+ console.error("Failed to get user data:", error)
+ return null
+ }
+}
+
+/**
* Save memory to Supermemory API
*/
export async function saveMemory(payload: MemoryPayload): Promise<unknown> {
diff --git a/apps/browser-extension/utils/query-hooks.ts b/apps/browser-extension/utils/query-hooks.ts
index 721a68ad..5c7cfc21 100644
--- a/apps/browser-extension/utils/query-hooks.ts
+++ b/apps/browser-extension/utils/query-hooks.ts
@@ -5,6 +5,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import {
fetchProjects,
getDefaultProject,
+ getUserData,
saveMemory,
searchMemories,
setDefaultProject,
@@ -15,6 +16,7 @@ import type { MemoryPayload } from "./types"
export const queryKeys = {
projects: ["projects"] as const,
defaultProject: ["defaultProject"] as const,
+ userData: ["userData"] as const,
}
// Projects Query
@@ -37,6 +39,16 @@ export function useDefaultProject(options?: { enabled?: boolean }) {
})
}
+// User Data Query
+export function useUserData(options?: { enabled?: boolean }) {
+ return useQuery({
+ queryKey: queryKeys.userData,
+ queryFn: getUserData,
+ staleTime: 5 * 60 * 1000, // 5 minutes
+ enabled: options?.enabled ?? true,
+ })
+}
+
// Set Default Project Mutation
export function useSetDefaultProject() {
const queryClient = useQueryClient()
diff --git a/apps/browser-extension/utils/ui-components.ts b/apps/browser-extension/utils/ui-components.ts
index 8a56ea5a..dabe4974 100644
--- a/apps/browser-extension/utils/ui-components.ts
+++ b/apps/browser-extension/utils/ui-components.ts
@@ -94,8 +94,8 @@ export function createToast(state: ToastState): HTMLElement {
const icon = document.createElement("div")
icon.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;"
- const text = document.createElement("span")
- text.style.fontWeight = "500"
+ let textElement: HTMLElement = document.createElement("span")
+ textElement.style.fontWeight = "500"
// Configure toast based on state
switch (state) {
@@ -113,17 +113,17 @@ export function createToast(state: ToastState): HTMLElement {
</svg>
`
icon.style.animation = "spin 1s linear infinite"
- text.textContent = "Adding to Memory..."
+ textElement.textContent = "Adding to Memory..."
break
case "success": {
const iconUrl = browser.runtime.getURL("/icon-16.png")
icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />`
- text.textContent = "Added to Memory"
+ textElement.textContent = "Added to Memory"
break
}
- case "error":
+ case "error": {
icon.innerHTML = `
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#ef4444"/>
@@ -131,12 +131,29 @@ export function createToast(state: ToastState): HTMLElement {
<path d="M9 9L15 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`
- text.textContent = "Failed to save memory / Make sure you are logged in"
+ const textContainer = document.createElement("div")
+ textContainer.style.cssText =
+ "display: flex; flex-direction: column; gap: 2px;"
+
+ const mainText = document.createElement("span")
+ mainText.style.cssText = "font-weight: 500; line-height: 1.2;"
+ mainText.textContent = "Failed to save memory"
+
+ const helperText = document.createElement("span")
+ helperText.style.cssText =
+ "font-size: 12px; color: #6b7280; font-weight: 400; line-height: 1.2;"
+ helperText.textContent = "Make sure you are logged in"
+
+ textContainer.appendChild(mainText)
+ textContainer.appendChild(helperText)
+
+ textElement = textContainer
break
+ }
}
toast.appendChild(icon)
- toast.appendChild(text)
+ toast.appendChild(textElement)
return toast
}
@@ -433,8 +450,25 @@ export const DOMUtils = {
</svg>
`
icon.style.animation = ""
- text.textContent =
- "Failed to save memory / Make sure you are logged in"
+
+ const textContainer = document.createElement("div")
+ textContainer.style.cssText =
+ "display: flex; flex-direction: column; gap: 2px;"
+
+ const mainText = document.createElement("span")
+ mainText.style.cssText = "font-weight: 500; line-height: 1.2;"
+ mainText.textContent = "Failed to save memory"
+
+ const helperText = document.createElement("span")
+ helperText.style.cssText =
+ "font-size: 12px; color: #6b7280; font-weight: 400; line-height: 1.2;"
+ helperText.textContent = "Make sure you are logged in"
+
+ textContainer.appendChild(mainText)
+ textContainer.appendChild(helperText)
+
+ text.innerHTML = ""
+ text.appendChild(textContainer)
}
// Auto-dismiss
diff --git a/apps/browser-extension/wxt.config.ts b/apps/browser-extension/wxt.config.ts
index 1bd7910d..b536b853 100644
--- a/apps/browser-extension/wxt.config.ts
+++ b/apps/browser-extension/wxt.config.ts
@@ -11,7 +11,7 @@ export default defineConfig({
manifest: {
name: "supermemory",
homepage_url: "https://supermemory.ai",
- version: "6.0.002",
+ version: "6.0.003",
permissions: ["contextMenus", "storage", "activeTab", "webRequest", "tabs"],
host_permissions: [
"*://x.com/*",
diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx
index 6500ba23..b9759d4e 100644
--- a/apps/web/components/connect-ai-modal.tsx
+++ b/apps/web/components/connect-ai-modal.tsx
@@ -27,6 +27,7 @@ import { useEffect, useState } from "react"
import { toast } from "sonner"
import { z } from "zod/v4"
import { analytics } from "@/lib/analytics"
+import { cn } from "@lib/utils"
const clients = {
cursor: "Cursor",
@@ -79,7 +80,19 @@ export function ConnectAIModal({
const setIsOpen = onOpenChange || setInternalIsOpen
const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState<string | null>("none")
- const projectId = localStorage.getItem("selectedProject") ?? "default"
+ const [cursorInstallTab, setCursorInstallTab] = useState<
+ "oneClick" | "manual"
+ >("oneClick")
+
+ const [projectId, setProjectId] = useState("default")
+
+ useEffect(() => {
+ if (typeof window !== "undefined") {
+ const storedProjectId =
+ localStorage.getItem("selectedProject") ?? "default"
+ setProjectId(storedProjectId)
+ }
+ }, [])
useEffect(() => {
analytics.mcpViewOpened()
@@ -164,6 +177,10 @@ export function ConnectAIModal({
return command
}
+ function getCursorDeeplink() {
+ return "https://cursor.com/en/install-mcp?name=supermemory-mcp&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9hcGkuc3VwZXJtZW1vcnkuYWkvbWNwIn0%3D"
+ }
+
const copyToClipboard = () => {
const command = generateInstallCommand()
navigator.clipboard.writeText(command)
@@ -252,21 +269,142 @@ export function ConnectAIModal({
</div>
</div>
- {/* Step 2: Project Selection or MCP URL */}
+ {/* Step 2: One-click Install for Cursor, Project Selection for others, or MCP URL */}
{selectedClient && (
<div className="space-y-4">
- <div className="flex items-center gap-3">
- <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
- 2
+ <div className="flex justify-between">
+ <div className="flex items-center gap-3">
+ <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
+ 2
+ </div>
+ <h3 className="text-sm font-medium">
+ {selectedClient === "cursor"
+ ? "Install Supermemory MCP"
+ : selectedClient === "mcp-url"
+ ? "MCP Server URL"
+ : "Select Target Project (Optional)"}
+ </h3>
+ </div>
+
+ <div
+ className={cn(
+ "flex-col gap-2 hidden",
+ selectedClient === "cursor" && "flex",
+ )}
+ >
+ {/* Tabs */}
+ <div className="flex justify-end">
+ <div className="flex bg-white/5 rounded-full p-1 border border-white/10">
+ <button
+ className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${
+ cursorInstallTab === "oneClick"
+ ? "bg-white/10 text-white border border-white/20"
+ : "text-white/60 hover:text-white/80"
+ }`}
+ onClick={() => setCursorInstallTab("oneClick")}
+ type="button"
+ >
+ One Click Install
+ </button>
+ <button
+ className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${
+ cursorInstallTab === "manual"
+ ? "bg-white/10 text-white border border-white/20"
+ : "text-white/60 hover:text-white/80"
+ }`}
+ onClick={() => setCursorInstallTab("manual")}
+ type="button"
+ >
+ Manual Install
+ </button>
+ </div>
+ </div>
</div>
- <h3 className="text-sm font-medium">
- {selectedClient === "mcp-url"
- ? "MCP Server URL"
- : "Select Target Project (Optional)"}
- </h3>
</div>
- {selectedClient === "mcp-url" ? (
+ {selectedClient === "cursor" ? (
+ <div className="space-y-4">
+ {/* Tab Content */}
+ {cursorInstallTab === "oneClick" ? (
+ <div className="space-y-4">
+ <div className="flex flex-col items-center gap-4 p-6 border border-green-500/20 rounded-lg bg-green-500/5">
+ <div className="text-center">
+ <p className="text-sm text-white/80 mb-2">
+ Click the button below to automatically install and
+ configure Supermemory in Cursor
+ </p>
+ <p className="text-xs text-white/50">
+ This will install the MCP server without any
+ additional setup required
+ </p>
+ </div>
+ <a
+ href={getCursorDeeplink()}
+ onClick={() => {
+ analytics.mcpInstallCmdCopied()
+ toast.success("Opening Cursor installer...")
+ }}
+ >
+ <img
+ alt="Add Supermemory MCP server to Cursor"
+ className="hover:opacity-80 transition-opacity cursor-pointer"
+ height="40"
+ src="https://cursor.com/deeplink/mcp-install-dark.svg"
+ />
+ </a>
+ </div>
+ <p className="text-xs text-white/40 text-center">
+ Make sure you have Cursor installed on your system
+ </p>
+ </div>
+ ) : (
+ <div className="space-y-4">
+ <p className="text-sm text-white/70">
+ Choose a project and follow the installation steps below
+ </p>
+ <div className="max-w-md">
+ <Select
+ disabled={isLoadingProjects}
+ onValueChange={setSelectedProject}
+ value={selectedProject || "none"}
+ >
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="Select project" />
+ </SelectTrigger>
+ <SelectContent className="bg-black/90 backdrop-blur-xl border-white/10">
+ <SelectItem
+ className="text-white hover:bg-white/10"
+ value="none"
+ >
+ Auto-select project
+ </SelectItem>
+ <SelectItem
+ className="text-white hover:bg-white/10"
+ value="sm_project_default"
+ >
+ Default Project
+ </SelectItem>
+ {projects
+ .filter(
+ (p: Project) =>
+ p.containerTag !== "sm_project_default",
+ )
+ .map((project: Project) => (
+ <SelectItem
+ className="text-white hover:bg-white/10"
+ key={project.id}
+ value={project.containerTag}
+ >
+ {project.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ </div>
+ )}
+ </div>
+ ) : selectedClient === "mcp-url" ? (
<div className="space-y-2">
<div className="relative">
<Input
@@ -336,39 +474,47 @@ export function ConnectAIModal({
</div>
)}
- {/* Step 3: Command Line */}
- {selectedClient && selectedClient !== "mcp-url" && (
- <div className="space-y-4">
- <div className="flex items-center gap-3">
- <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
- 3
+ {/* Step 3: Command Line - Show for manual installation or non-cursor clients */}
+ {selectedClient &&
+ selectedClient !== "mcp-url" &&
+ (selectedClient !== "cursor" || cursorInstallTab === "manual") && (
+ <div className="space-y-4">
+ <div className="flex items-center gap-3">
+ <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium">
+ 3
+ </div>
+ <h3 className="text-sm font-medium">
+ {selectedClient === "cursor" &&
+ cursorInstallTab === "manual"
+ ? "Manual Installation Command"
+ : "Installation Command"}
+ </h3>
</div>
- <h3 className="text-sm font-medium">Installation Command</h3>
- </div>
- <div className="relative">
- <Input
- className="font-mono text-xs w-full pr-10"
- readOnly
- value={generateInstallCommand()}
- />
- <Button
- className="absolute top-[-1px] right-0 cursor-pointer"
- onClick={copyToClipboard}
- variant="ghost"
- >
- <CopyIcon className="size-4" />
- </Button>
- </div>
+ <div className="relative">
+ <Input
+ className="font-mono text-xs w-full pr-10"
+ readOnly
+ value={generateInstallCommand()}
+ />
+ <Button
+ className="absolute top-[-1px] right-0 cursor-pointer"
+ onClick={copyToClipboard}
+ variant="ghost"
+ >
+ <CopyIcon className="size-4" />
+ </Button>
+ </div>
- <p className="text-xs text-white/50">
- Copy and run this command in your terminal to install the MCP
- server
- </p>
- </div>
- )}
+ <p className="text-xs text-white/50">
+ {selectedClient === "cursor" && cursorInstallTab === "manual"
+ ? "Copy and run this command in your terminal for manual installation (or switch to the one-click option above)"
+ : "Copy and run this command in your terminal to install the MCP server"}
+ </p>
+ </div>
+ )}
- {/* Blurred Command Placeholder */}
+ {/* Blurred Command Placeholder - Only show when no client selected */}
{!selectedClient && (
<div className="space-y-4">
<div className="flex items-center gap-3">
diff --git a/apps/web/components/views/billing.tsx b/apps/web/components/views/billing.tsx
index 033a3915..8b79eb23 100644
--- a/apps/web/components/views/billing.tsx
+++ b/apps/web/components/views/billing.tsx
@@ -216,7 +216,7 @@ export function BillingView() {
<ul className="space-y-2">
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
- 5000 memories
+ Unlimited memories
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
diff --git a/apps/web/components/views/profile.tsx b/apps/web/components/views/profile.tsx
index 9fa086ec..3d49d395 100644
--- a/apps/web/components/views/profile.tsx
+++ b/apps/web/components/views/profile.tsx
@@ -301,7 +301,7 @@ export function ProfileView() {
<ul className="space-y-2">
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
- 5000 memories
+ Unlimited memories
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />