import { useQueryClient } from "@tanstack/react-query" import { useEffect, useState } from "react" import "./App.css" import { validateAuthToken } from "../../utils/api" import { getSupermemoryLoginUrl, MESSAGE_TYPES, STORAGE_KEYS, UI_CONFIG, } from "../../utils/constants" import { useDefaultProject, useProjects, useSetDefaultProject, useUserData, } from "../../utils/query-hooks" import { autoSearchEnabled as autoSearchEnabledStorage, autoCapturePromptsEnabled as autoCapturePromptsEnabledStorage, bearerToken, defaultProject as defaultProjectStorage, userData as userDataStorage, } from "../../utils/storage" import type { Project } from "../../utils/types" import { RightArrow } from "@/components/icons" const Tooltip = ({ children, content, }: { children: React.ReactNode content: string }) => { const [isVisible, setIsVisible] = useState(false) return (
{isVisible && (
{content}
)}
) } const cardShadow = "2px 2px 2px 0 rgba(0, 0, 0, 0.50) inset, -1px -1px 1px 0 rgba(82, 89, 102, 0.08) inset" type ManualImportProvider = "gemini" const manualImportProviderConfig: Record< ManualImportProvider, { label: string; actionSource: string } > = { gemini: { label: "Gemini", actionSource: "gemini_manual_memory_import", }, } const manualMemoryImportPrompt = `Export all of my stored memories and any context you've learned about me from past conversations. Preserve my words verbatim where possible, especially for instructions and preferences. ## Categories (output in this order): 1. **Instructions**: Rules I've explicitly asked you to follow going forward - tone, format, style, "always do X", "never do Y", and corrections to your behavior. Only include rules from stored memories, not from conversations. 2. **Identity**: Name, age, location, education, family, relationships, languages, and personal interests. 3. **Career**: Current and past roles, companies, and general skill areas. 4. **Projects**: Projects I meaningfully built or committed to. Ideally ONE entry per project. Include what it does, current status, and any key decisions. Use the project name or a short descriptor as the first words of the entry. 5. **Preferences**: Opinions, tastes, and working-style preferences that apply broadly. ## Format: Use section headers for each category. Within each category, list one entry per line, sorted by oldest date first. Format each line as: [YYYY-MM-DD] - Entry content here. If no date is known, use [unknown] instead. ## Output: - Wrap the entire export in a single code block for easy copying. - After the code block, state whether this is the complete set or if more remain.` const normalizeManualMemoryImport = (value: string) => { const trimmed = value.trim() const codeBlockMatch = trimmed.match(/```(?:[\w-]+)?\s*([\s\S]*?)```/) return (codeBlockMatch?.[1] ?? trimmed).trim() } const OpenAILogo = ({ className }: { className?: string }) => ( OpenAI ) const ClaudeLogo = ({ className }: { className?: string }) => ( Claude ) const GeminiLogo = ({ className }: { className?: string }) => ( Gemini ) const XLogo = ({ className }: { className?: string }) => ( X Twitter Logo ) const GrokLogo = ({ className }: { className?: string }) => ( Grok ) const ChatAppsLogo = ({ className }: { className?: string }) => (
) const ImportCard = ({ icon, title, description, onClick, }: { icon: React.ReactNode title: string description?: string onClick: () => void }) => ( ) function App() { const [userSignedIn, setUserSignedIn] = useState(false) const [loading, setLoading] = useState(true) const [showProjectSelector, setShowProjectSelector] = useState(false) const [currentUrl, setCurrentUrl] = useState("") const [currentTitle, setCurrentTitle] = useState("") const [saving, setSaving] = useState(false) const [activeTab, setActiveTab] = useState<"save" | "imports" | "settings">( "save", ) const [showChatAppImports, setShowChatAppImports] = useState(false) const [manualImportProvider, setManualImportProvider] = useState(null) const [manualImportText, setManualImportText] = useState("") const [manualImportSaving, setManualImportSaving] = useState(false) const [manualImportSaved, setManualImportSaved] = useState(false) const [manualImportCopied, setManualImportCopied] = useState(false) const [manualImportError, setManualImportError] = useState("") const [autoSearchEnabled, setAutoSearchEnabled] = useState(false) const [autoCapturePromptsEnabled, setAutoCapturePromptsEnabled] = useState(false) const [authInvalidated, setAuthInvalidated] = useState(false) const [saveError, setSaveError] = useState(null) const queryClient = useQueryClient() const { data: projects = [], isLoading: loadingProjects } = useProjects({ enabled: userSignedIn, }) 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 { const [token, autoSearch, autoCapturePrompts] = await Promise.all([ bearerToken.getValue(), autoSearchEnabledStorage.getValue(), autoCapturePromptsEnabledStorage.getValue(), ]) const hasToken = !!token if (hasToken) { const isTokenValid = await validateAuthToken() if (isTokenValid) { setUserSignedIn(true) setAuthInvalidated(false) } else { await Promise.all([ bearerToken.removeValue(), userDataStorage.removeValue(), defaultProjectStorage.removeValue(), ]) queryClient.clear() setUserSignedIn(false) setAuthInvalidated(true) } } else { setUserSignedIn(false) setAuthInvalidated(false) } setAutoSearchEnabled(autoSearch ?? false) setAutoCapturePromptsEnabled(autoCapturePrompts ?? false) } catch (error) { console.error("Error checking auth status:", error) setUserSignedIn(false) setAuthInvalidated(false) } finally { 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) } } catch (error) { console.error("Error getting current tab:", error) } } checkAuthStatus() getCurrentTab() }, []) const handleProjectSelect = (project: Project) => { setDefaultProjectMutation.mutate(project, { onSuccess: () => { setShowProjectSelector(false) }, onError: (error) => { console.error("Error setting default project:", error) }, }) } const handleShowProjectSelector = () => { setShowProjectSelector(true) } // Reconcile stored default against live list: reset if deleted, refresh if renamed. useEffect(() => { if (projects.length === 0) return if (!defaultProject) { setDefaultProjectMutation.mutate(projects[0]) return } const live = projects.find((p) => p.id === defaultProject.id) if (!live) { setDefaultProjectMutation.mutate(projects[0]) } else if ( live.name !== defaultProject.name || live.containerTag !== defaultProject.containerTag ) { setDefaultProjectMutation.mutate(live) } }, [defaultProject, projects, setDefaultProjectMutation]) // biome-ignore lint/correctness/useExhaustiveDependencies: close space selector when tab changes useEffect(() => { setShowProjectSelector(false) setShowChatAppImports(false) setManualImportProvider(null) setManualImportText("") setManualImportSaved(false) setManualImportCopied(false) setManualImportError("") }, [activeTab]) const handleSaveCurrentPage = async () => { setSaving(true) setSaveError(null) try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true, }) const tab = tabs[0] let response: { success?: boolean; error?: string } | undefined if (tab?.id) { try { response = await chrome.tabs.sendMessage(tab.id, { action: MESSAGE_TYPES.SAVE_MEMORY, actionSource: "popup", }) } catch (contentScriptError) { console.warn("Content script save failed:", contentScriptError) } } if (response && !response.success) { throw new Error(response.error || "Failed to save current page") } if (!response) { const fallbackUrl = tab?.url || currentUrl const fallbackTitle = tab?.title || currentTitle || "Current Page" if (!fallbackUrl) { throw new Error("No active page URL found") } response = await chrome.runtime.sendMessage({ action: MESSAGE_TYPES.SAVE_MEMORY, actionSource: "popup_fallback", data: { url: fallbackUrl, title: fallbackTitle, content: `${fallbackTitle}\n\n${fallbackUrl}`, }, }) } if (response?.success) { if (tab?.id) { await chrome.tabs .sendMessage(tab.id, { action: MESSAGE_TYPES.SHOW_TOAST, state: "success", }) .catch(() => undefined) } window.close() return } throw new Error(response?.error || "Failed to save current page") } catch (error) { console.error("Failed to save current page:", error) setSaveError( error instanceof Error ? error.message : "Could not save page", ) 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) } } finally { setSaving(false) } } const handleAutoSearchToggle = async (enabled: boolean) => { try { await autoSearchEnabledStorage.setValue(enabled) setAutoSearchEnabled(enabled) } catch (error) { console.error("Error updating auto search setting:", error) } } const handleAutoCapturePromptsToggle = async (enabled: boolean) => { try { await autoCapturePromptsEnabledStorage.setValue(enabled) setAutoCapturePromptsEnabled(enabled) } catch (error) { console.error("Error updating auto capture prompts setting:", error) } } const handleTwitterBookmarksImport = async () => { const targetUrl = "https://x.com/i/bookmarks" try { const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true, }) const isOnBookmarksPage = activeTab?.url?.includes("x.com/i/bookmarks") || activeTab?.url?.includes("twitter.com/i/bookmarks") if (isOnBookmarksPage && activeTab?.id) { try { await chrome.tabs.sendMessage(activeTab.id, { action: MESSAGE_TYPES.TWITTER_IMPORT_OPEN_MODAL, }) } catch (error) { console.error("Failed to send message to content script:", error) const intentExpiry = Date.now() + UI_CONFIG.IMPORT_INTENT_TTL await chrome.storage.local.set({ [STORAGE_KEYS.TWITTER_BOOKMARKS_IMPORT_INTENT_UNTIL]: intentExpiry, }) await chrome.tabs.create({ url: targetUrl, }) } } else { const intentExpiry = Date.now() + UI_CONFIG.IMPORT_INTENT_TTL await chrome.storage.local.set({ [STORAGE_KEYS.TWITTER_BOOKMARKS_IMPORT_INTENT_UNTIL]: intentExpiry, }) await chrome.tabs.create({ url: targetUrl, }) } } catch (error) { console.error("Error opening Twitter import:", error) try { await chrome.tabs.create({ url: targetUrl, }) } catch (fallbackError) { console.error("Failed to open bookmarks page:", fallbackError) } } } const handleOpenManualMemoryImport = (provider: ManualImportProvider) => { setManualImportProvider(provider) setManualImportText("") setManualImportSaved(false) setManualImportCopied(false) setManualImportError("") } const handleCloseManualMemoryImport = () => { setManualImportProvider(null) setManualImportText("") setManualImportSaved(false) setManualImportCopied(false) setManualImportError("") } const handleCopyManualImportPrompt = async () => { try { await navigator.clipboard.writeText(manualMemoryImportPrompt) setManualImportCopied(true) window.setTimeout(() => setManualImportCopied(false), 1600) } catch (error) { console.error("Failed to copy memory import prompt:", error) setManualImportError( "Could not copy prompt. Select and copy it manually.", ) } } const handleManualMemoryImportSave = async () => { if (!manualImportProvider) return const content = normalizeManualMemoryImport(manualImportText) if (!content) { setManualImportError("Paste the exported memories first.") return } setManualImportSaving(true) setManualImportError("") try { const providerConfig = manualImportProviderConfig[manualImportProvider] const response = await chrome.runtime.sendMessage({ action: MESSAGE_TYPES.SAVE_MEMORY, actionSource: providerConfig.actionSource, data: { content, title: `${providerConfig.label} memories import`, }, }) if (!response?.success) { throw new Error(response?.error || "Could not add memories") } setManualImportSaved(true) window.setTimeout(() => { handleCloseManualMemoryImport() }, 1000) } catch (error) { console.error("Failed to add manual memory import:", error) setManualImportError( error instanceof Error ? error.message : "Could not add memories", ) } finally { setManualImportSaving(false) } } const handleSignOut = async () => { try { await Promise.all([ bearerToken.removeValue(), userDataStorage.removeValue(), defaultProjectStorage.removeValue(), ]) setUserSignedIn(false) queryClient.clear() } catch (error) { console.error("Error signing out:", error) } } if (loading) { return (
Loading...
) } return (
{userSignedIn ? (
{/* Tab Navigation */}
{/* Tab Content */} {activeTab === "save" ? (
{/* Current Page Info */}

{currentTitle || "Current Page"}

{currentUrl}

{/* Space Selection */}
Save to Space {showProjectSelector && ( )}
{showProjectSelector ? (
{loadingProjects ? (
Loading spaces...
) : ( projects.map((project) => ( )) )}
) : ( )}
{/* Save Button at Bottom */}
{saveError && (

{saveError}

)}
) : activeTab === "imports" ? (
{manualImportProvider ? (

Import{" "} { manualImportProviderConfig[manualImportProvider] .label }{" "} memories

Copy the prompt, paste the response here, then add it to supermemory.

1 Copy this prompt into chat
													{manualMemoryImportPrompt}
												
2 Paste results below