[
  {
    "path": ".eslintignore",
    "content": "dist/\nnode_modules/\n*.min.js\nicons/\npackage-lock.json\n*.log\n.env*\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"root\": true,\n  \"env\": {\n    \"browser\": true,\n    \"es2022\": true,\n    \"webextensions\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"prettier\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaVersion\": \"latest\",\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint\", \"prettier\", \"import\"],\n  \"rules\": {\n    \"prettier/prettier\": \"error\",\n    \"@typescript-eslint/no-unused-vars\": [\"error\", { \"argsIgnorePattern\": \"^_\" }],\n    \"@typescript-eslint/no-explicit-any\": \"error\",\n    \"@typescript-eslint/no-non-null-assertion\": \"warn\",\n    \"@typescript-eslint/explicit-function-return-type\": \"off\",\n    \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n    \"@typescript-eslint/no-empty-function\": \"warn\",\n    \"@typescript-eslint/no-inferrable-types\": \"off\",\n    \"no-var\": \"error\",\n    \"no-console\": \"off\",\n    \"no-debugger\": \"error\",\n    \"no-duplicate-imports\": \"error\",\n    \"no-unused-expressions\": \"error\",\n    \"eqeqeq\": [\"error\", \"always\"],\n    \"curly\": [\"error\", \"all\"],\n    \"import/order\": [\n      \"error\",\n      {\n        \"groups\": [\n          \"builtin\",\n          \"external\",\n          \"internal\",\n          \"parent\",\n          \"sibling\",\n          \"index\"\n        ],\n        \"newlines-between\": \"always\",\n        \"alphabetize\": {\n          \"order\": \"asc\",\n          \"caseInsensitive\": true\n        }\n      }\n    ]\n  },\n  \"ignorePatterns\": [\n    \"dist/\",\n    \"node_modules/\",\n    \"*.min.js\",\n    \"icons/\"\n  ]\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Node modules\nnode_modules/\n\n# Build output\ndist/\nbuild/\n\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# OS generated files\n.DS_Store\nThumbs.db\n\n# Environment files\n.env\n.env.local\n.env.*.local\n\n# Chrome extension specific\n*.crx\n*.pem\n\n# IDE files\n.vscode/\n.idea/\n*.sublime-workspace\n*.sublime-project\n"
  },
  {
    "path": ".prettierignore",
    "content": "dist/\nnode_modules/\n*.min.js\nicons/\npackage-lock.json\n*.log\n.env*\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": true,\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"avoid\",\n  \"endOfLine\": \"lf\",\n  \"quoteProps\": \"as-needed\",\n  \"bracketSameLine\": false,\n  \"proseWrap\": \"preserve\",\n  \"overrides\": [\n    {\n      \"files\": \"*.html\",\n      \"options\": {\n        \"parser\": \"html\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Deshraj Yadav\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "> [!CAUTION]\n> ## This project has been archived\n>\n> **mem0-chrome-extension** is no longer actively maintained and this repository is now a public archive.\n>\n> Thank you to the 600+ stargazers, 97 forkers, and every contributor who helped shape this project. Your support and feedback meant the world to us.\n>\n> **Looking for memory across your AI assistants?** Check out [Mem0](https://www.mem0.ai) for the latest.\n>\n> You're welcome to fork this repo and build on it — the MIT license still applies.\n\n# Mem0 Chrome Extension — Cross-LLM Memory\n\nMem0 brings ChatGPT-style memory to all your favorite AI assistants. Share context seamlessly across ChatGPT, Claude, Perplexity, and more, making your AI interactions more personalized and efficient.\n\n<a href=\"https://chromewebstore.google.com/detail/claude-memory/onihkkbipkfeijkadecaafbgagkhglop?hl=en-GB&utm_source=ext_sidebar\" style=\"display: inline-block; padding: 8px 12px; background-color: white; color: #3c4043; text-decoration: none; font-family: 'Roboto', Arial, sans-serif; font-size: 14px; font-weight: 500; border-radius: 4px; border: 1px solid #dadce0; box-shadow: 0 1px 2px rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\">\n  <img src=\"https://www.google.com/chrome/static/images/chrome-logo.svg\" alt=\"Chrome logo\" style=\"height: 24px; vertical-align: middle; margin-right: 8px;\">\n  Add to Chrome, It's Free\n</a>\n<br>\n<br>\n\nBuilt using [Mem0](https://www.mem0.ai) ❤️\n\n\n## Demo\n\nWatch the Mem0 Chrome Extension in action (full-resolution video available [here](https://www.youtube.com/watch?v=cByzXztn-YY)):\n\nhttps://github.com/user-attachments/assets/a069a178-631e-4b35-a182-9f4fef7735c4\n\n\n## Features\n\n- **Universal Memory Layer:** Share context across ChatGPT, Claude, Perplexity, and more\n- **Smart Context Detection:** Automatically captures relevant information from your conversations\n- **Intelligent Memory Retrieval:** Surfaces relevant memories at the right time\n- **One-click sync** with existing ChatGPT memories\n- **Memory dashboard** to manage all memories\n\n## Installation\n\n> **Note:** Make sure you have [Node.js](https://nodejs.org/) installed before proceeding.\n\n1. Clone this repository.\n2. Navigate to the directory where you cloned the repository.\n3. Run `npm install` to install dependencies.\n4. Run `npm run build` to build the extension.\n5. The built files will be in the `dist` directory.\n6. Open Google Chrome and navigate to `chrome://extensions`.\n7. Enable \"Developer mode\" in the top right corner.\n8. Click \"Load unpacked\" and select the `dist` directory containing the extension files.\n9. The Mem0 Chrome Extension should now appear in your Chrome toolbar.\n\n\n## Usage\n\n1. After installation, look for the Mem0 icon in your Chrome toolbar\n2. Sign in with Google\n3. Start chatting with any supported AI assistant\n4. For ChatGPT and Perplexity, just press enter while chatting as you would normally\n5. On Claude, click the Mem0 button or use shortcut ^ + M\n\n## ❤️ Free to Use\n\nMem0 is completely free with:\n\n- No usage limits\n- No ads\n- All features included\n\n## Configuration\n\n- API Key: Required for connecting to the Mem0 API. Obtain this from your Mem0 Dashboard.\n- User ID: Your unique identifier in the Mem0 system. If not provided, it defaults to 'chrome-extension-user'.\n\n## Troubleshooting\n\nIf you encounter any issues:\n\n- Check your internet connection\n- Verify you're signed in correctly\n- Clear your browser cache if needed\n- Contact support if issues persist\n\n## Privacy and Data Security\n\nYour messages are sent to the Mem0 API for extracting and retrieving memories.\n\n## Contributing\n\nContributions to improve Mem0 Chrome Extension are welcome. Please feel free to submit pull requests or open issues for bugs and feature requests.\n\n## License\nMIT License\n"
  },
  {
    "path": "docs/types.md",
    "content": "# Types Documentation\n\nThis section contains TypeScript type definitions for the Mem0 Chrome Extension. The types are organized into several files based on their functionality.\n\n## Overview\n\n### `types/api.ts`\nDefines types for API interactions:\n- `MessageRole` - Enum for message roles (User, Assistant)\n- `ApiMessage` - Message structure with role and content\n- `ApiMemoryRequest` - Request payload for memory API calls\n- `MemorySearchResponse` - Array of memory search results\n- `LoginData` - User authentication data structure\n- `DEFAULT_USER_ID` - Default user ID constant\n- `SOURCE` - Extension source identifier\n\n### `types/browser.ts`\nBrowser-specific type definitions:\n- `OnCommittedDetails` - Web navigation event details\n- `JsonPrimitive` - JSON primitive value types\n- `JsonValue` - Recursive JSON value type\n- `JsonObject` - JSON object structure\n- `HistoryStateData` - Browser history state data\n- `HistoryUrl` - Browser history URL type\n\n### `types/dom.ts`\nDOM-related type extensions and global declarations:\n- `ExtendedHTMLElement` - Extended HTML element with cleanup methods\n- `ExtendedDocument` - Extended document with mem0-specific properties\n- `ExtendedElement` - Extended element with additional properties\n- `ModalDimensions` - Modal size and pagination settings\n- `ModalPosition` - Modal positioning coordinates\n- `MutableMutationObserver` - Extended mutation observer with timers\n- Global interface extensions for `Element`, `CSSStyleDeclaration`, and `Window`\n\n### `types/memory.ts`\nCore memory-related types:\n- `MemoryItem` - Individual memory structure with id, text, and categories\n- `Memory` - Simplified memory structure\n- `MemorySearchItem` - Search result item from API\n- `MemoriesResponse` - API response wrapper for memories\n- `OpenMemoryPrompts` - Prompt templates and regex patterns\n- `OptionalApiParams` - Optional parameters for API calls (org_id, project_id)\n\n### `types/messages.ts`\nMessage passing between extension components:\n- `ToastVariant` - Enum for toast notification types (SUCCESS, ERROR)\n- `MessageType` - Enum for different message types\n- `SidebarAction` - Enum for sidebar actions (TOGGLE_SIDEBAR, OPEN_POPUP, etc.)\n- `SelectionContextPayload` - Payload for selection context messages\n- `SelectionContextResponse` - Response structure for selection context\n- `GetSelectionContextMessage` - Message type for getting selection context\n- `ToastMessage` - Toast notification message structure\n- `SelectionContextMessage` - Union type for selection context messages\n- `SendResponse` - Response callback type\n- Various sidebar action message types\n\n### `types/organizations.ts`\nOrganization and project management:\n- `Organization` - Organization structure with org_id and name\n- `Project` - Project structure with project_id and name\n\n### `types/providers.ts`\nAI provider definitions:\n- `Provider` - Enum for supported AI providers\n- `Category` - Enum for memory categories (BOOKMARK, NAVIGATION, SEARCH)\n\n### `types/settings.ts`\nUser settings and preferences:\n- `UserSettings` - User preference structure with API keys, memory settings, and thresholds\n- `SidebarSettings` - Sidebar-specific settings with organization and project info\n- `Settings` - Legacy settings structure for compatibility\n\n### `types/storage.ts`\nChrome storage key definitions:\n- `StorageKey` - Enum for all storage keys used in the extension\n- `StorageItems` - Type mapping for storage values (required fields)\n- `StorageData` - Type mapping for storage values (optional fields)\n"
  },
  {
    "path": "manifest.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"OpenMemory\",\n  \"version\": \"1.3.17\",\n  \"description\": \"🧠 OpenMemory keeps your conversations in sync. 🔄 No more repeating yourself—just seamless AI collaboration! ✨\",\n  \"icons\": {\n    \"16\": \"icons/icon16.png\",\n    \"48\": \"icons/icon48.png\",\n    \"128\": \"icons/icon128.png\"\n  },\n  \"permissions\": [\"storage\", \"activeTab\", \"contextMenus\", \"scripting\", \"webNavigation\"],\n  \"host_permissions\": [\n    \"https://api.mem0.ai/*\",\n    \"https://app.mem0.ai/*\",\n    \"https://claude.ai/*\"\n  ],\n  \"action\": {\n    \"default_popup\": \"src/popup.html\",\n    \"default_icon\": {\n      \"16\": \"icons/icon16.png\",\n      \"48\": \"icons/icon48.png\",\n      \"128\": \"icons/icon128.png\"\n    }\n  },\n  \"background\": {\n    \"service_worker\": \"src/background.ts\",\n    \"type\": \"module\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"https://claude.ai/*\"],\n      \"js\": [\"src/claude/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://chat.openai.com/*\", \"https://chatgpt.com/*\"],\n      \"js\": [\"src/chatgpt/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://www.perplexity.ai/*\"],\n      \"js\": [\"src/perplexity/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://app.mem0.ai/*\"],\n      \"js\": [\"src/mem0/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://grok.com/*\", \"https://x.com/i/grok*\"],\n      \"js\": [\"src/grok/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://chat.deepseek.com/*\"],\n      \"js\": [\"src/deepseek/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://gemini.google.com/*\"],\n      \"js\": [\"src/gemini/content.ts\"]\n    },\n    {\n      \"matches\": [\"https://replit.com/*\"],\n      \"js\": [\"src/replit/content.ts\"]\n    },\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/sidebar.ts\"],\n      \"run_at\": \"document_end\"\n    }, \n    {\n      \"matches\": [\"<all_urls>\"], \n      \"js\": [\"src/selection_context.ts\"], \n      \"run_at\": \"document_idle\",\n      \"all_frames\": true\n    }\n    ,\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/search_tracker.ts\"],\n      \"run_at\": \"document_idle\"\n    }\n  ],\n  \"web_accessible_resources\": [\n    {\n      \"resources\": [\"icons/*\"],\n      \"matches\": [\"<all_urls>\"]\n    }\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mem0-extension\",\n  \"description\": \"🧠 OpenMemory keeps your conversations in sync. 🔄 No more repeating yourself—just seamless AI collaboration! ✨\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"lint\": \"eslint src --ext .ts,.tsx --fix\",\n    \"lint:check\": \"eslint src --ext .ts,.tsx\",\n    \"format\": \"prettier --write src/**/*.{ts,html}\",\n    \"format:check\": \"prettier --check src/**/*.{ts,html}\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"devDependencies\": {\n    \"@crxjs/vite-plugin\": \"^2.2.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.18.0\",\n    \"@typescript-eslint/parser\": \"^7.18.0\",\n    \"chrome-types\": \"^0.1.375\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-prettier\": \"^5.1.3\",\n    \"prettier\": \"^3.3.3\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^7.1.4\"\n  }\n}\n"
  },
  {
    "path": "privacy-policy.md",
    "content": "# Privacy Policy for Claude Memory\n\nLast updated: 09-06-2024\n\n## Introduction\n\nWelcome to Claude Memory (\"we\", \"our\", or \"us\"). We are committed to protecting your personal information and your right to privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our Chrome extension.\n\n## Information We Collect\n\nWe may collect the following types of information:\n\nPersonal Information: Full name and email address through which you sign up on the Mem0 platform.\nUsage Data: Every time you use the extension, we collect the message that you are sending to Claude.\n\n## How We Use Your Information\n\nWe use the collected information for various purposes, including:\n\n- To provide and maintain our extension\n- To notify you about changes to our extension\n- To provide customer support\n- To gather analysis or valuable information so that we can improve our extension\n- To monitor the usage of our extension\n- To detect, prevent and address technical issues\n\n## Data Retention\n\nWe will retain your information for as long as necessary to fulfill the purposes outlined in this Privacy Policy, unless a longer retention period is required or permitted by law.\n\n## Data Security\n\nWe use commercially acceptable means to protect your personal information. However, no method of transmission over the Internet or method of electronic storage is 100% secure.\n\n## Your Data Protection Rights\n\nDepending on your location, you may have certain rights regarding your personal information, such as the right to access, correct, or delete your data. Please contact us to exercise these rights.\n\n## Changes to This Privacy Policy\n\nWe may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the \"Last updated\" date.\n\n## Contact Us\n\nIf you have any questions about this Privacy Policy, please contact us at: deshraj@mem0.ai\n"
  },
  {
    "path": "src/background.ts",
    "content": "import { initContextMenuMemory } from './context-menu-memory';\nimport { initDirectUrlTracking } from './direct-url-tracker';\nimport { type OpenDashboardMessage, SidebarAction } from './types/messages';\n\nchrome.runtime.onInstalled.addListener(() => {\n  chrome.storage.sync.set({ memory_enabled: true }, () => {\n    console.log('Memory enabled set to true on install/update');\n  });\n});\n\nchrome.runtime.onMessage.addListener((request: OpenDashboardMessage) => {\n  if (request.action === SidebarAction.OPEN_DASHBOARD && request.url) {\n    chrome.tabs.create({ url: request.url });\n  }\n  return undefined;\n});\n\nchrome.runtime.onMessage.addListener(\n  (request: { action?: string }, sender: chrome.runtime.MessageSender) => {\n    if (request.action === SidebarAction.SIDEBAR_SETTINGS) {\n      const tabId = sender.tab?.id;\n      if (tabId !== null && tabId !== undefined) {\n        chrome.tabs.sendMessage(tabId, { action: SidebarAction.SIDEBAR_SETTINGS });\n      }\n    }\n    return undefined;\n  }\n);\n\ninitContextMenuMemory();\ninitDirectUrlTracking();\n"
  },
  {
    "path": "src/chatgpt/content.ts",
    "content": "/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport { DEFAULT_USER_ID, type LoginData, MessageRole } from '../types/api';\nimport type { HistoryStateData } from '../types/browser';\nimport type { MemoryItem, MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { type StorageData, StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI } from '../utils/util_positioning';\n\nexport {};\n\nlet isProcessingMem0: boolean = false;\n\n// Initialize the MutationObserver variable\nlet observer: MutationObserver;\nlet memoryModalShown: boolean = false;\n\n// Global variable to store all memories\nlet allMemories: string[] = [];\n\n// Track added memories by ID\nconst allMemoriesById: Set<string> = new Set<string>();\n\n// Reference to the modal overlay for updates\nlet currentModalOverlay: HTMLDivElement | null = null;\n\n// Store dragged position\nlet draggedPosition: { top: number; left: number } | null = null;\n\nlet inputValueCopy: string = '';\n\nlet currentModalSourceButtonId: string | null = null;\n\nconst chatgptSearch = createOrchestrator({\n  fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n    const data = await new Promise<SearchStorage>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as SearchStorage);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      return [];\n    }\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const threshold =\n      data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n        ? data[StorageKey.SIMILARITY_THRESHOLD]\n        : 0.1;\n    const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    const payload = {\n      query,\n      filters: { user_id: userId },\n      rerank: true,\n      threshold: threshold,\n      top_k: topK,\n      filter_memories: false,\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      ...optionalParams,\n    };\n\n    const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify(payload),\n      signal: opts && opts.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error(`API request failed with status ${res.status}`);\n    }\n    return await res.json();\n  },\n\n  // Don't render on prefetch. When modal is open, update it.\n  onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n    if (!memoryModalShown) {\n      return;\n    }\n    const memoryItems = ((responseData as MemorySearchItem[]) || []).map(\n      (item: MemorySearchItem) => ({\n        id: String(item.id),\n        text: item.memory,\n        categories: item.categories || [],\n      })\n    );\n    createMemoryModal(memoryItems, false, currentModalSourceButtonId);\n  },\n\n  onError: function () {\n    if (memoryModalShown) {\n      createMemoryModal([], false, currentModalSourceButtonId);\n    }\n  },\n\n  minLength: 3,\n  debounceMs: 75,\n  cacheTTL: 60000,\n});\n\nfunction createMemoryModal(\n  memoryItems: MemoryItem[],\n  isLoading: boolean = false,\n  sourceButtonId: string | null = null\n): void {\n  // Close existing modal if it exists\n  if (memoryModalShown && currentModalOverlay) {\n    document.body.removeChild(currentModalOverlay);\n  }\n\n  memoryModalShown = true;\n  let currentMemoryIndex = 0;\n\n  // Calculate modal dimensions (estimated)\n  const modalWidth = 447;\n  let modalHeight = 400; // Default height\n  let memoriesPerPage = 3; // Default number of memories per page\n\n  let topPosition: number = 0;\n  let leftPosition: number = 0;\n\n  // Use dragged position if available, otherwise calculate based on button\n  if (draggedPosition) {\n    topPosition = draggedPosition.top;\n    leftPosition = draggedPosition.left;\n  } else if (sourceButtonId === 'mem0-icon-button') {\n    // Position relative to the mem0-icon-button (in the input area)\n    const iconButton = document.querySelector('#mem0-icon-button');\n    if (iconButton) {\n      const buttonRect = iconButton.getBoundingClientRect();\n\n      // Determine if there's enough space above the button\n      const spaceAbove = buttonRect.top;\n      const viewportHeight = window.innerHeight;\n\n      // Calculate position - for icon button, prefer to show ABOVE\n      leftPosition = buttonRect.left - modalWidth + buttonRect.width;\n\n      // Make sure modal doesn't go off-screen to the left\n      leftPosition = Math.max(leftPosition, 10);\n\n      // For icon button, show above if enough space, otherwise below\n      if (spaceAbove >= modalHeight + 10) {\n        // Place above\n        topPosition = buttonRect.top - modalHeight - 10;\n      } else {\n        // Not enough space above, place below\n        topPosition = buttonRect.bottom + 10;\n\n        // Check if it's in the lower half of the screen\n        if (buttonRect.bottom > viewportHeight / 2) {\n          modalHeight = 300; // Reduced height\n          memoriesPerPage = 2; // Show only 2 memories\n        }\n      }\n    } else {\n      // Fallback to input-based positioning\n      positionRelativeToInput();\n    }\n  } else if (sourceButtonId === 'sync-button') {\n    // Position relative to the sync button\n    const syncButton = document.querySelector('#sync-button');\n    if (syncButton) {\n      const buttonRect = syncButton.getBoundingClientRect();\n      const viewportHeight = window.innerHeight;\n\n      // Position below the sync button by default\n      leftPosition = buttonRect.left;\n      topPosition = buttonRect.bottom + 10;\n\n      // Check if it's in the lower half of the screen\n      if (buttonRect.bottom > viewportHeight / 2) {\n        modalHeight = 300; // Reduced height\n        memoriesPerPage = 2; // Show only 2 memories\n      }\n\n      // Make sure modal doesn't go off-screen to the right\n      leftPosition = Math.min(leftPosition, window.innerWidth - modalWidth - 10);\n    } else {\n      // Fallback to input-based positioning\n      positionRelativeToInput();\n    }\n  } else {\n    // Default positioning relative to the input field\n    positionRelativeToInput();\n  }\n\n  // Helper function to position modal relative to input field\n  function positionRelativeToInput() {\n    const inputElement =\n      document.querySelector('#prompt-textarea') ||\n      document.querySelector('div[contenteditable=\"true\"]') ||\n      document.querySelector('textarea');\n\n    if (!inputElement) {\n      console.error('Input element not found');\n      return;\n    }\n\n    // Get the position and dimensions of the input field\n    const inputRect = inputElement.getBoundingClientRect();\n\n    // Determine if there's enough space below the input field\n    const viewportHeight = window.innerHeight;\n    const spaceBelow = viewportHeight - inputRect.bottom;\n\n    // Position the modal aligned to the right of the input\n    leftPosition = Math.max(inputRect.right - 20 - modalWidth, 10); // 20px offset from right edge\n\n    // Decide whether to place modal above or below based on available space\n    if (spaceBelow >= modalHeight) {\n      // Place below the input\n      topPosition = inputRect.bottom + 10;\n\n      // Check if it's in the lower half of the screen\n      if (inputRect.bottom > viewportHeight / 2) {\n        modalHeight = 300; // Reduced height\n        memoriesPerPage = 2; // Show only 2 memories\n      }\n    } else {\n      // Place above the input if not enough space below\n      topPosition = inputRect.top - modalHeight - 10;\n    }\n  }\n\n  // Create modal overlay\n  const modalOverlay = document.createElement('div');\n  modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n  // Save reference to current modal overlay\n  currentModalOverlay = modalOverlay;\n\n  // Add event listener to close modal when clicking outside\n  modalOverlay.addEventListener('click', event => {\n    // Only close if clicking directly on the overlay, not its children\n    if (event.target === modalOverlay) {\n      closeModal();\n    }\n  });\n\n  // Create modal container with positioning\n  const modalContainer = document.createElement('div');\n  modalContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    position: absolute;\n    top: ${topPosition}px;\n    left: ${leftPosition}px;\n    pointer-events: auto;\n    border: 1px solid #27272A;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    overflow: hidden;\n  `;\n\n  // Create modal header\n  const modalHeader = document.createElement('div');\n  modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #232325;\n    flex-shrink: 0;\n    cursor: move;\n    user-select: none;\n  `;\n\n  // Create header left section with just the logo\n  const headerLeft = document.createElement('div');\n  headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  `;\n\n  // Add Mem0 logo (updated to SVG)\n  const logoImg = document.createElement('img');\n  logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n    margin-right: 8px;\n  `;\n\n  // Add \"OpenMemory\" title\n  const title = document.createElement('div');\n  title.textContent = 'OpenMemory';\n  title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: white;\n  `;\n\n  // Create header right section\n  const headerRight = document.createElement('div');\n  headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n  `;\n\n  // Create Add to Prompt button with arrow\n  const addToPromptBtn = document.createElement('button');\n  addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color: white;\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: black;\n  `;\n  addToPromptBtn.textContent = 'Add to Prompt';\n\n  // Add arrow icon to button\n  const arrowIcon = document.createElement('span');\n  arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>\n`;\n  addToPromptBtn.appendChild(arrowIcon);\n\n  // (Removed) LLM button – auto-rerank is now handled on modal open\n  // Create settings button\n  const settingsBtn = document.createElement('button');\n  settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n  settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n  // Add click event to open app.mem0.ai in a new tab\n  settingsBtn.addEventListener('click', () => {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n      memoryModalShown = false;\n      currentModalOverlay = null;\n    }\n\n    chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n  });\n\n  // Add hover effect for the settings button\n  settingsBtn.addEventListener('mouseenter', () => {\n    settingsBtn.style.opacity = '1';\n  });\n  settingsBtn.addEventListener('mouseleave', () => {\n    settingsBtn.style.opacity = '0.6';\n  });\n\n  // Add drag functionality to modal header\n  let isDragging = false;\n  const dragOffset = { x: 0, y: 0 };\n\n  modalHeader.addEventListener('mousedown', (e: MouseEvent) => {\n    // Don't start dragging if clicking on buttons\n    const target = e.target as HTMLElement;\n    if (target?.closest('button')) {\n      return;\n    }\n\n    isDragging = true;\n    modalHeader.style.cursor = 'grabbing';\n\n    const modalRect = modalContainer.getBoundingClientRect();\n    dragOffset.x = e.clientX - modalRect.left;\n    dragOffset.y = e.clientY - modalRect.top;\n\n    e.preventDefault();\n  });\n\n  document.addEventListener('mousemove', e => {\n    if (!isDragging) {\n      return;\n    }\n\n    const newLeft = e.clientX - dragOffset.x;\n    const newTop = e.clientY - dragOffset.y;\n\n    // Constrain to viewport\n    const maxLeft = window.innerWidth - modalWidth;\n    const maxTop = window.innerHeight - modalHeight;\n\n    const constrainedLeft = Math.max(0, Math.min(newLeft, maxLeft));\n    const constrainedTop = Math.max(0, Math.min(newTop, maxTop));\n\n    modalContainer.style.left = `${constrainedLeft}px`;\n    modalContainer.style.top = `${constrainedTop}px`;\n\n    // Store the dragged position\n    draggedPosition = {\n      left: constrainedLeft,\n      top: constrainedTop,\n    };\n\n    e.preventDefault();\n  });\n\n  document.addEventListener('mouseup', () => {\n    if (isDragging) {\n      isDragging = false;\n      modalHeader.style.cursor = 'move';\n    }\n  });\n\n  // Content section\n  const contentSection = document.createElement('div');\n  const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n  contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n  // Create memories counter\n  const memoriesCounter = document.createElement('div');\n  memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n  // Update counter text based on loading state and number of memories\n  if (isLoading) {\n    memoriesCounter.textContent = `Loading Relevant Memories...`;\n  } else {\n    memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n  }\n\n  // Calculate max height for memories content based on modal height\n  const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n  // Create memories content container with adjusted height\n  const memoriesContent = document.createElement('div');\n  memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: none;\n    -ms-overflow-style: none;\n  `;\n  memoriesContent.style.cssText += '::-webkit-scrollbar { display: none; }';\n\n  // Track currently expanded memory\n  let currentlyExpandedMemory: HTMLElement | null = null;\n\n  // Function to create skeleton loading items (adjusted for different heights)\n  function createSkeletonItems() {\n    memoriesContent.innerHTML = '';\n\n    for (let i = 0; i < memoriesPerPage; i++) {\n      const skeletonItem = document.createElement('div');\n      skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #27272A;\n        border-radius: 8px;\n        height: 72px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n      const skeletonText = document.createElement('div');\n      skeletonText.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n      const skeletonText2 = document.createElement('div');\n      skeletonText2.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n      const skeletonActions = document.createElement('div');\n      skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n      const skeletonButton1 = document.createElement('div');\n      skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      const skeletonButton2 = document.createElement('div');\n      skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      skeletonActions.appendChild(skeletonButton1);\n      skeletonActions.appendChild(skeletonButton2);\n\n      const textContainer = document.createElement('div');\n      textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      textContainer.appendChild(skeletonText);\n      textContainer.appendChild(skeletonText2);\n\n      skeletonItem.appendChild(textContainer);\n      skeletonItem.appendChild(skeletonActions);\n      memoriesContent.appendChild(skeletonItem);\n    }\n\n    // Add keyframe animation to document if not exists\n    if (!document.getElementById('skeleton-animation')) {\n      const style = document.createElement('style');\n      style.id = 'skeleton-animation';\n      style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n      document.head.appendChild(style);\n    }\n  }\n\n  // Function to expand memory\n  function expandMemory(\n    memoryContainer: HTMLDivElement,\n    memoryText: HTMLDivElement,\n    contentWrapper: HTMLDivElement,\n    removeButton: HTMLButtonElement,\n    isExpanded: { value: boolean }\n  ) {\n    if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n      currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n    }\n\n    isExpanded.value = true;\n    memoryText.style.webkitLineClamp = 'unset';\n    memoryText.style.height = 'auto';\n    contentWrapper.style.overflowY = 'auto';\n    contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n    contentWrapper.style.scrollbarWidth = 'none';\n    // contentWrapper.style.msOverflowStyle is non-standard; omit to satisfy TS\n    contentWrapper.style.cssText += '::-webkit-scrollbar { display: none; }';\n    memoryContainer.style.backgroundColor = '#1C1C1E';\n    memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n    memoryContainer.style.overflow = 'hidden';\n    removeButton.style.display = 'flex';\n    currentlyExpandedMemory = memoryContainer;\n\n    // Scroll to make expanded memory visible if needed\n    memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n  }\n\n  // Function to collapse memory\n  function collapseMemory(\n    memoryContainer: HTMLDivElement,\n    memoryText: HTMLDivElement,\n    contentWrapper: HTMLDivElement,\n    removeButton: HTMLButtonElement,\n    isExpanded: { value: boolean }\n  ) {\n    isExpanded.value = false;\n    memoryText.style.webkitLineClamp = '2';\n    memoryText.style.height = '42px';\n    contentWrapper.style.overflowY = 'visible';\n    memoryContainer.style.backgroundColor = '#27272A';\n    memoryContainer.style.maxHeight = '72px';\n    memoryContainer.style.overflow = 'hidden';\n    removeButton.style.display = 'none';\n    currentlyExpandedMemory = null;\n  }\n\n  // Function to show memories with adjusted count based on modal position\n  function showMemories() {\n    memoriesContent.innerHTML = '';\n\n    if (isLoading) {\n      createSkeletonItems();\n      return;\n    }\n\n    if (memoryItems.length === 0) {\n      showEmptyState();\n      // Disable navigation buttons when there are no memories\n      updateNavigationState(0, 0);\n      return;\n    }\n\n    // Use the dynamically set memoriesPerPage value\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n\n    // Calculate total pages and current page\n    const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    // Update navigation buttons state\n    updateNavigationState(currentPage, totalPages);\n\n    for (let i = 0; i < memoriesToShow; i++) {\n      const memoryIndex = currentMemoryIndex + i;\n      if (memoryIndex >= memoryItems.length) {\n        break;\n      } // Stop if we've reached the end\n\n      const memory = memoryItems[memoryIndex]!;\n\n      // Skip memories that have been added already\n      if (allMemoriesById.has(String(memory.id))) {\n        continue;\n      }\n\n      // Ensure memory has an ID\n      if (!memory.id) {\n        memory.id = `memory-${Date.now()}-${memoryIndex}`;\n      }\n\n      const memoryContainer = document.createElement('div');\n      memoryContainer.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px; \n        background-color: #27272A;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s ease;\n        min-height: 72px; \n        max-height: 72px; \n        overflow: hidden;\n        flex-shrink: 0;\n      `;\n\n      const memoryText = document.createElement('div');\n      memoryText.style.cssText = `\n        font-size: 14px;\n        line-height: 1.5;\n        color: #D4D4D8;\n        flex-grow: 1;\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        transition: all 0.2s ease;\n        height: 42px; /* Height for 2 lines of text */\n      `;\n      memoryText.textContent = memory.text || '';\n\n      const actionsContainer = document.createElement('div');\n      actionsContainer.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n        flex-shrink: 0;\n      `;\n\n      // Add button\n      const addButton = document.createElement('button');\n      addButton.style.cssText = `\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 100%;\n        transition: all 0.2s ease;\n      `;\n\n      addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      </svg>`;\n\n      // Add click handler for add button\n      addButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        sendExtensionEvent('memory_injection', {\n          provider: 'chatgpt',\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          browser: getBrowser(),\n          injected_all: false,\n          memory_id: memory.id,\n        });\n\n        // Add this memory\n        allMemoriesById.add(String(memory.id));\n        allMemories.push(String(memory.text || ''));\n        updateInputWithMemories();\n\n        // Remove this memory from the list\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n          // If we're on a page that's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      // Menu button\n      const menuButton = document.createElement('button');\n      menuButton.style.cssText = `\n        background: none;\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        color: #A1A1AA;\n      `;\n      menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n        <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n      </svg>`;\n\n      // Track expanded state using object to maintain reference\n      const isExpanded = { value: false };\n\n      // Create remove button (hidden by default)\n      const removeButton = document.createElement('button');\n      removeButton.style.cssText = `\n        display: none;\n        align-items: center;\n        gap: 6px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 8px;\n        padding: 2px 4px;\n        border: none;\n        cursor: pointer;\n        font-size: 13px;\n        margin-top: 12px;\n        width: fit-content;\n      `;\n      removeButton.innerHTML = `\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>\n        Remove\n      `;\n\n      // Create content wrapper for text and remove button\n      const contentWrapper = document.createElement('div');\n      contentWrapper.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      contentWrapper.appendChild(memoryText);\n      contentWrapper.appendChild(removeButton);\n\n      memoryContainer.addEventListener('collapse', () => {\n        collapseMemory(memoryContainer, memoryText, contentWrapper, removeButton, isExpanded);\n      });\n\n      menuButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        if (isExpanded.value) {\n          collapseMemory(memoryContainer, memoryText, contentWrapper, removeButton, isExpanded);\n        } else {\n          expandMemory(memoryContainer, memoryText, contentWrapper, removeButton, isExpanded);\n        }\n      });\n\n      // Add click handler for remove button\n      removeButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        // Remove from memoryItems\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n\n          // If we're on the last page and it's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      actionsContainer.appendChild(addButton);\n      actionsContainer.appendChild(menuButton);\n\n      memoryContainer.appendChild(contentWrapper);\n      memoryContainer.appendChild(actionsContainer);\n      memoriesContent.appendChild(memoryContainer);\n\n      // Add hover effect\n      memoryContainer.addEventListener('mouseenter', () => {\n        memoryContainer.style.backgroundColor = isExpanded.value ? '#18181B' : '#323232';\n      });\n      memoryContainer.addEventListener('mouseleave', () => {\n        memoryContainer.style.backgroundColor = isExpanded.value ? '#1C1C1E' : '#27272A';\n      });\n    }\n\n    // If after filtering for already added memories, there are no items to show,\n    // check if we need to go to previous page\n    if (memoriesContent.children.length === 0 && memoryItems.length > 0) {\n      if (currentMemoryIndex > 0) {\n        currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n        showMemories();\n      } else {\n        showEmptyState();\n      }\n    }\n  }\n\n  // Function to show empty state\n  function showEmptyState(): void {\n    memoriesContent.innerHTML = '';\n\n    const emptyContainer = document.createElement('div');\n    emptyContainer.style.cssText = `\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 32px 16px;\n      text-align: center;\n      flex: 1;\n      min-height: 200px;\n    `;\n\n    const emptyIcon = document.createElement('div');\n    emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#71717A\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>`;\n    emptyIcon.style.marginBottom = '16px';\n\n    const emptyText = document.createElement('div');\n    emptyText.textContent = 'No relevant memories found';\n    emptyText.style.cssText = `\n      color: #71717A;\n      font-size: 14px;\n      font-weight: 500;\n    `;\n\n    emptyContainer.appendChild(emptyIcon);\n    emptyContainer.appendChild(emptyText);\n    memoriesContent.appendChild(emptyContainer);\n  }\n\n  // Update navigation button states\n  function updateNavigationState(currentPage: number, totalPages: number): void {\n    // If there are no memories or total pages is 0, disable both buttons\n    if (memoryItems.length === 0 || totalPages === 0) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n      return;\n    }\n\n    if (currentPage <= 1) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n    } else {\n      prevButton.disabled = false;\n      prevButton.style.opacity = '1';\n      prevButton.style.cursor = 'pointer';\n    }\n\n    if (currentPage >= totalPages) {\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n    } else {\n      nextButton.disabled = false;\n      nextButton.style.opacity = '1';\n      nextButton.style.cursor = 'pointer';\n    }\n  }\n\n  // Navigation section at bottom\n  const navigationSection = document.createElement('div');\n  navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n  // Navigation buttons\n  const prevButton = document.createElement('button');\n  prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  prevButton.style.cssText = `\n    background: #27272A;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  const nextButton = document.createElement('button');\n  nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  nextButton.style.cssText = prevButton.style.cssText;\n\n  // Add navigation button handlers\n  prevButton.addEventListener('click', () => {\n    if (currentMemoryIndex >= memoriesPerPage) {\n      currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n      showMemories();\n    }\n  });\n\n  nextButton.addEventListener('click', () => {\n    if (currentMemoryIndex + memoriesPerPage < memoryItems.length) {\n      currentMemoryIndex = currentMemoryIndex + memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  // Add hover effects\n  [prevButton, nextButton].forEach(button => {\n    button.addEventListener('mouseenter', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#323232';\n      }\n    });\n    button.addEventListener('mouseleave', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#27272A';\n      }\n    });\n  });\n\n  // Assemble modal\n  headerLeft.appendChild(logoImg);\n  headerLeft.appendChild(title);\n  headerRight.appendChild(addToPromptBtn);\n  headerRight.appendChild(settingsBtn);\n  // No LLM button; auto-rerank happens below if enabled\n\n  modalHeader.appendChild(headerLeft);\n  modalHeader.appendChild(headerRight);\n\n  contentSection.appendChild(memoriesCounter);\n  contentSection.appendChild(memoriesContent);\n\n  navigationSection.appendChild(prevButton);\n  navigationSection.appendChild(nextButton);\n\n  modalContainer.appendChild(modalHeader);\n  modalContainer.appendChild(contentSection);\n  modalContainer.appendChild(navigationSection);\n\n  modalOverlay.appendChild(modalContainer);\n\n  // Append to body\n  document.body.appendChild(modalOverlay);\n\n  // Show initial memories\n  showMemories();\n\n  // Function to close the modal\n  function closeModal() {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n    }\n    currentModalOverlay = null;\n    memoryModalShown = false;\n    // Reset dragged position when modal is explicitly closed\n    draggedPosition = null;\n  }\n\n  // Update Add to Prompt button click handler\n  addToPromptBtn.addEventListener('click', () => {\n    // Only add memories that are not already added\n    const newMemories = memoryItems\n      .filter(memory => !allMemoriesById.has(String(memory.id)) && !memory.removed)\n      .map(memory => {\n        allMemoriesById.add(String(memory.id));\n        return String(memory.text || '');\n      });\n\n    sendExtensionEvent('memory_injection', {\n      provider: 'chatgpt',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n      injected_all: true,\n      memory_count: newMemories.length,\n    });\n\n    // Add all new memories to allMemories\n    allMemories.push(...newMemories);\n\n    // Update the input with all memories\n    if (allMemories.length > 0) {\n      updateInputWithMemories();\n      closeModal();\n    } else {\n      // If no new memories were added but we have existing ones, just close\n      if (allMemoriesById.size > 0) {\n        closeModal();\n      }\n    }\n\n    // Remove all added memories from the memoryItems list\n    for (let i = memoryItems.length - 1; i >= 0; i--) {\n      if (allMemoriesById.has(String(memoryItems[i]?.id))) {\n        memoryItems.splice(i, 1);\n      }\n    }\n  });\n}\n\n// Shared function to update the input field with all collected memories\nfunction updateInputWithMemories(): void {\n  const inputElement =\n    document.querySelector('#prompt-textarea') ||\n    document.querySelector('div[contenteditable=\"true\"]') ||\n    document.querySelector('textarea');\n\n  if (inputElement && allMemories.length > 0) {\n    // Get the content without any existing memory wrappers\n    const baseContent = getContentWithoutMemories();\n\n    // Create the memory wrapper with all collected memories\n    let memoriesContent =\n      '<div id=\"mem0-wrapper\" contenteditable=\"false\" style=\"background-color: rgb(220, 252, 231); padding: 8px; border-radius: 4px; margin-top: 8px; margin-bottom: 8px;\">';\n    memoriesContent += OPENMEMORY_PROMPTS.memory_header_html_strong;\n\n    // Add all memories to the content\n    allMemories.forEach((mem, idx) => {\n      const safe = (mem || '').toString();\n      memoriesContent += `<div data-mem0-idx=\"${idx}\" style=\"user-select: text;\">- ${safe}</div>`;\n    });\n    memoriesContent += '</div>';\n\n    // Add the final content to the input\n    if (inputElement.tagName.toLowerCase() === 'div') {\n      inputElement.innerHTML = `${baseContent}<div><br></div>${memoriesContent}`;\n    } else {\n      (inputElement as HTMLTextAreaElement).value = `${baseContent}\\n${memoriesContent}`;\n    }\n\n    // Make only the wrapper non-editable; allow user to select/copy text inside\n    try {\n      const wrapper = document.getElementById('mem0-wrapper');\n      if (wrapper) {\n        wrapper.setAttribute('contenteditable', 'false');\n        wrapper.style.userSelect = 'text';\n      }\n    } catch {\n      // Ignore errors when setting contenteditable\n    }\n\n    inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n  }\n}\n\n// Function to get the content without any memory wrappers\nfunction getContentWithoutMemories(message?: string): string {\n  if (typeof message === 'string') {\n    return message;\n  }\n\n  const inputElement =\n    (document.querySelector('#prompt-textarea') as HTMLTextAreaElement | HTMLDivElement) ||\n    (document.querySelector('div[contenteditable=\"true\"]') as HTMLDivElement) ||\n    (document.querySelector('textarea') as HTMLTextAreaElement);\n\n  if (!inputElement) {\n    return '';\n  }\n\n  let content =\n    (inputElement as HTMLTextAreaElement)?.value ||\n    inputElement.textContent ||\n    (inputElement as HTMLDivElement).innerHTML;\n\n  if (\n    message &&\n    (!content ||\n      content.trim() ===\n        '<p data-placeholder=\"Ask anything\" class=\"placeholder\"><br class=\"ProseMirror-trailingBreak\"></p>')\n  ) {\n    content = message;\n  }\n\n  // Remove any memory wrappers\n  content = content.replace(/<div id=\"mem0-wrapper\"[\\s\\S]*?<\\/div>/g, '');\n\n  // Remove any memory headers using shared prompts (HTML and plain variants)\n  try {\n    const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n    const MEM0_HTML = OPENMEMORY_PROMPTS.memory_header_html_regex;\n    content = content.replace(MEM0_HTML, '');\n    content = content.replace(MEM0_PLAIN, '');\n  } catch {\n    // Ignore errors during re-initialization\n  }\n\n  // Clean up any leftover paragraph markers\n  content = content.replace(/<p><br class=\"ProseMirror-trailingBreak\"><\\/p><p>$/g, '');\n\n  // Replace <p> with nothing\n  content = content.replace(/<p>[\\s\\S]*?<\\/p>/g, '');\n\n  return content.trim();\n}\n\n// Add an event listener for the send button to clear memories after sending\nfunction addSendButtonListener(): void {\n  const sendButton = document.querySelector('#composer-submit-button') as HTMLButtonElement;\n\n  if (sendButton && !sendButton.dataset.mem0Listener) {\n    sendButton.dataset.mem0Listener = 'true';\n    sendButton.addEventListener('click', function () {\n      // Capture and save memory asynchronously\n      captureAndStoreMemory();\n\n      // Clear all memories after sending\n      setTimeout(() => {\n        allMemories = [];\n        allMemoriesById.clear();\n      }, 100);\n    });\n\n    // Also handle Enter key press\n    const inputElement =\n      (document.querySelector('#prompt-textarea') as HTMLTextAreaElement | HTMLDivElement) ||\n      (document.querySelector('div[contenteditable=\"true\"]') as HTMLDivElement) ||\n      (document.querySelector('textarea') as HTMLTextAreaElement);\n\n    if (inputElement && !inputElement.dataset.mem0KeyListener) {\n      inputElement.dataset.mem0KeyListener = 'true';\n      (inputElement as HTMLElement).addEventListener('keydown', function (event: KeyboardEvent) {\n        // Check if Enter was pressed without Shift (standard send behavior)\n\n        inputValueCopy =\n          (inputElement as HTMLTextAreaElement)?.value ||\n          inputElement.textContent ||\n          inputValueCopy;\n\n        if (event.key === 'Enter' && !event.shiftKey) {\n          // Capture and save memory asynchronously\n          captureAndStoreMemory();\n\n          // Clear all memories after sending\n          setTimeout(() => {\n            allMemories = [];\n            allMemoriesById.clear();\n          }, 100);\n        }\n      });\n    }\n  }\n}\n\n// Function to capture and store memory asynchronously\nfunction captureAndStoreMemory(): void {\n  // Get the message content\n  // id is prompt-textarea\n  const inputElement =\n    (document.querySelector('#prompt-textarea') as HTMLTextAreaElement | HTMLDivElement) ||\n    (document.querySelector('div[contenteditable=\"true\"]') as HTMLDivElement) ||\n    (document.querySelector('textarea') as HTMLTextAreaElement) ||\n    (document.querySelector('textarea[data-virtualkeyboard=\"true\"]') as HTMLTextAreaElement);\n\n  if (!inputElement) {\n    return;\n  }\n\n  // Get raw content from the input element\n  let message = inputElement.textContent || (inputElement as HTMLTextAreaElement)?.value;\n\n  if (!message || message.trim() === '') {\n    message = inputValueCopy;\n  }\n\n  if (!message || message.trim() === '') {\n    return;\n  }\n\n  // Clean the message of any memory wrapper content\n  message = getContentWithoutMemories(message);\n\n  // Skip if message is empty after cleaning\n  if (!message || message.trim() === '') {\n    return;\n  }\n\n  // Asynchronously store the memory\n  chrome.storage.sync.get(\n    [\n      StorageKey.API_KEY,\n      StorageKey.USER_ID_CAMEL,\n      StorageKey.ACCESS_TOKEN,\n      StorageKey.MEMORY_ENABLED,\n      StorageKey.SELECTED_ORG,\n      StorageKey.SELECTED_PROJECT,\n      StorageKey.USER_ID,\n    ],\n    function (items) {\n      // Skip if memory is disabled or no credentials\n      if (\n        items[StorageKey.MEMORY_ENABLED] === false ||\n        (!items[StorageKey.API_KEY] && !items[StorageKey.ACCESS_TOKEN])\n      ) {\n        return;\n      }\n\n      const authHeader = items[StorageKey.ACCESS_TOKEN]\n        ? `Bearer ${items[StorageKey.ACCESS_TOKEN]}`\n        : `Token ${items[StorageKey.API_KEY]}`;\n\n      const userId =\n        items[StorageKey.USER_ID_CAMEL] || items[StorageKey.USER_ID] || DEFAULT_USER_ID;\n\n      // Get recent messages for context (if available)\n      const messages = getLastMessages(2);\n      messages.push({ role: MessageRole.User, content: message });\n\n      const optionalParams: OptionalApiParams = {};\n      if (items[StorageKey.SELECTED_ORG]) {\n        optionalParams.org_id = items[StorageKey.SELECTED_ORG];\n      }\n      if (items[StorageKey.SELECTED_PROJECT]) {\n        optionalParams.project_id = items[StorageKey.SELECTED_PROJECT];\n      }\n\n      // Send memory to mem0 API asynchronously without waiting for response\n      const storagePayload = {\n        messages: messages,\n        user_id: userId,\n        infer: true,\n        metadata: {\n          provider: 'ChatGPT',\n        },\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        ...optionalParams,\n      };\n\n      fetch('https://api.mem0.ai/v1/memories/', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: authHeader,\n        },\n        body: JSON.stringify(storagePayload),\n      }).catch(error => {\n        console.error('Error saving memory:', error);\n      });\n    }\n  );\n}\n\nasync function updateNotificationDot(): Promise<void> {\n  const memoryEnabled = await getMemoryEnabledState();\n  if (!memoryEnabled) {\n    return;\n  }\n\n  const input =\n    document.querySelector('#prompt-textarea') ||\n    document.querySelector('div[contenteditable=\"true\"]') ||\n    document.querySelector('textarea');\n  const host = document.getElementById('mem0-icon-button'); // shadow host\n  if (!input || !host) {\n    setTimeout(updateNotificationDot, 1000);\n    return;\n  }\n\n  const set = () => {\n    const txt = input.textContent || input.value || '';\n    host.setAttribute('data-has-text', txt.trim() ? '1' : '0');\n  };\n\n  const mo = new MutationObserver(set);\n  mo.observe(input, { childList: true, characterData: true, subtree: true });\n  input.addEventListener('input', set);\n  input.addEventListener('keyup', set);\n  input.addEventListener('focus', set);\n  set();\n}\n\n// Modified function to handle Mem0 modal instead of direct injection\nasync function handleMem0Modal(sourceButtonId: string | null = null): Promise<void> {\n  const memoryEnabled = await getMemoryEnabledState();\n  if (!memoryEnabled) {\n    return;\n  }\n\n  // Check if user is logged in\n  const loginData = await new Promise<LoginData>(resolve => {\n    chrome.storage.sync.get(\n      [StorageKey.API_KEY, StorageKey.USER_ID_CAMEL, StorageKey.ACCESS_TOKEN],\n      function (items) {\n        resolve(items);\n      }\n    );\n  });\n\n  // If no API key and no access token, show login popup\n  if (!loginData[StorageKey.API_KEY] && !loginData[StorageKey.ACCESS_TOKEN]) {\n    showLoginPopup();\n    return;\n  }\n\n  const mem0Button = document.querySelector('#mem0-icon-button') as HTMLElement;\n\n  let message = getInputValue();\n  // If no message, show a popup and return\n  if (!message || message.trim() === '') {\n    if (mem0Button) {\n      showButtonPopup(mem0Button as HTMLElement, 'Please enter some text first');\n    }\n    return;\n  }\n\n  try {\n    const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n    message = message.replace(MEM0_PLAIN, '').trim();\n  } catch {\n    // Ignore errors during re-initialization\n  }\n  const endIndex = message.indexOf('</p>');\n  if (endIndex !== -1) {\n    message = message.slice(0, endIndex + 4);\n  }\n\n  if (isProcessingMem0) {\n    return;\n  }\n\n  isProcessingMem0 = true;\n\n  // Show the loading modal immediately with the source button ID\n  createMemoryModal([], true, sourceButtonId);\n\n  try {\n    const data = await new Promise<StorageData>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n\n    if (!apiKey && !accessToken) {\n      isProcessingMem0 = false;\n      return;\n    }\n\n    sendExtensionEvent('modal_clicked', {\n      provider: 'chatgpt',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n    });\n\n    const messages = getLastMessages(2);\n    messages.push({ role: MessageRole.User, content: message });\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    currentModalSourceButtonId = sourceButtonId;\n    chatgptSearch.runImmediate(message);\n  } catch (error) {\n    console.error('Error:', error);\n    // Still show the modal but with empty state if there was an error\n    createMemoryModal([], false, sourceButtonId);\n    throw error;\n  } finally {\n    isProcessingMem0 = false;\n  }\n}\n\n// Function to show a small popup message near the button\nfunction showButtonPopup(button: HTMLElement, message: string): void {\n  let host = button || document.getElementById('mem0-icon-button');\n  if (!host) {\n    return;\n  }\n  let root = host.shadowRoot || host;\n  // Remove any existing popups\n  const existingPopup = root.querySelector('.mem0-button-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  // Also hide any hover popover that might be showing\n  const hoverPopover = document.querySelector('.mem0-button-popover') as HTMLElement;\n  if (hoverPopover) {\n    hoverPopover.style.opacity = '0';\n    hoverPopover.style.display = 'none';\n  }\n\n  const popup = document.createElement('div');\n  popup.className = 'mem0-button-popup';\n\n  popup.style.cssText = `\n    position: absolute;\n    top: -40px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: #1C1C1E;\n    border: 1px solid #27272A;\n    color: white;\n    padding: 8px 12px;\n    border-radius: 6px;\n    font-size: 12px;\n    white-space: nowrap;\n    z-index: 10001;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n  `;\n\n  popup.textContent = message;\n\n  // Create arrow\n  const arrow = document.createElement('div');\n  arrow.style.cssText = `\n    position: absolute;\n    bottom: -5px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n    width: 10px;\n    height: 10px;\n    background-color: #1C1C1E;\n    border-right: 1px solid #27272A;\n    border-bottom: 1px solid #27272A;\n  `;\n\n  popup.appendChild(arrow);\n  root.appendChild(popup);\n\n  setTimeout(function () {\n    if (popup.isConnected) {\n      popup.remove();\n    }\n  }, 3000);\n\n  // Position relative to button\n  // button.style.position = 'relative';\n  // button.appendChild(popup);\n\n  // // Auto-remove after 3 seconds\n  // setTimeout(() => {\n  //   if (document.body.contains(popup)) {\n  //     popup.remove();\n  //   }\n  // }, 3000);\n}\n\n// Safe no-op to prevent ReferenceError if auto-inject prefetch isn't defined elsewhere\nfunction setupAutoInjectPrefetch() {\n  try {\n    // Intentionally left blank; legacy callers expect this to exist.\n    // Inline hint handles lightweight suggestion awareness.\n  } catch {\n    // Ignore errors during re-initialization\n  }\n}\n\n(function () {\n  if (!OPENMEMORY_UI || !OPENMEMORY_UI.mountOnEditorFocus) {\n    return;\n  }\n\n  // 1) Try to mount immediately from cached anchor on page load (before focus)\n  try {\n    // Skip if already mounted (e.g., hot reload / rapid SPA replace)\n    if (!document.getElementById('mem0-icon-button')) {\n      OPENMEMORY_UI.resolveCachedAnchor(\n        { learnKey: location.host + ':' + location.pathname },\n        null,\n        24 * 60 * 60 * 1000\n      )\n        .then(function (hit) {\n          if (!hit || !hit.el) {\n            return;\n          }\n          // Reuse the same render and placement as the focus-driven path\n          let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n          let host = hs.host,\n            shadow = hs.shadow;\n          host.id = 'mem0-icon-button';\n          let unplace = OPENMEMORY_UI.applyPlacement({\n            container: host,\n            anchor: hit.el,\n            placement: hit.placement || {\n              strategy: 'inline',\n              where: 'beforeend',\n              inlineAlign: 'end',\n            },\n          });\n\n          let style = document.createElement('style');\n          style.textContent = `\n          :host { position: relative; }\n          .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center;\n            justify-content:center; width:32px; height:32px; border-radius:50%; }\n          .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n          .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px;\n            background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n          :host([data-has-text=\"1\"]) .dot { display:block; }\n        `;\n          let btn = document.createElement('button');\n          btn.className = 'mem0-btn';\n          let img = document.createElement('img');\n          img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n          let dot = document.createElement('div');\n          dot.className = 'dot';\n          btn.appendChild(img);\n          shadow.append(style, btn, dot);\n\n          // Nudge to the left of mic if present in same anchor\n          try {\n            let mic =\n              hit.el &&\n              (hit.el.querySelector('button[aria-label=\"Dictate button\"]') ||\n                hit.el.querySelector('button[aria-label*=\"mic\" i]') ||\n                hit.el.querySelector('button[aria-label*=\"voice\" i]'));\n            if (mic && hit.el) {\n              let child: Element | null = mic;\n              while (child && child.parentElement !== hit.el) {\n                child = child.parentElement;\n              }\n              if (child && child.parentElement === hit.el) {\n                hit.el.insertBefore(host, child);\n              }\n            }\n          } catch {\n            // Ignore errors during re-initialization\n          }\n\n          btn.addEventListener('click', function () {\n            handleMem0Modal('mem0-icon-button');\n          });\n          if (typeof updateNotificationDot === 'function') {\n            setTimeout(updateNotificationDot, 0);\n          }\n\n          // If the anchor disappears, allow normal focus flow to re-mount\n          const removal = new MutationObserver(function () {\n            if (!document.contains(hit.el) || !document.contains(host)) {\n              try {\n                unplace();\n              } catch {\n                // Ignore errors during re-initialization\n              }\n              try {\n                removal.disconnect();\n              } catch {\n                // Ignore errors during re-initialization\n              }\n            }\n          });\n          removal.observe(document.documentElement, { childList: true, subtree: true });\n        })\n        .catch(function () {\n          // Ignore errors during re-initialization\n        });\n    }\n  } catch {\n    // Ignore errors during re-initialization\n  }\n\n  // 2) Standard focus-driven mount\n  OPENMEMORY_UI.mountOnEditorFocus({\n    existingHostSelector: '#mem0-icon-button',\n    editorSelector:\n      typeof SITE_CONFIG !== 'undefined' &&\n      SITE_CONFIG.chatgpt &&\n      SITE_CONFIG.chatgpt.editorSelector\n        ? SITE_CONFIG.chatgpt.editorSelector\n        : 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n    deriveAnchor:\n      typeof SITE_CONFIG !== 'undefined' &&\n      SITE_CONFIG.chatgpt &&\n      typeof SITE_CONFIG.chatgpt.deriveAnchor === 'function'\n        ? SITE_CONFIG.chatgpt.deriveAnchor\n        : function (editor) {\n            return editor.closest('form') || editor.parentElement;\n          },\n    placement:\n      typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.chatgpt && SITE_CONFIG.chatgpt.placement\n        ? SITE_CONFIG.chatgpt.placement\n        : { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n    render: function (shadow: ShadowRoot, host: HTMLElement, anchor: Element | null) {\n      host.id = 'mem0-icon-button'; // existing code relies on this\n      let style = document.createElement('style');\n      style.textContent = `\n        :host { position: relative; }\n        .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center;\n          justify-content:center; width:32px; height:32px; border-radius:50%; }\n        .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n        .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px;\n          background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n        :host([data-has-text=\"1\"]) .dot { display:block; }\n      `;\n      let btn = document.createElement('button');\n      btn.className = 'mem0-btn';\n      let img = document.createElement('img');\n      img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n      let dot = document.createElement('div');\n      dot.className = 'dot';\n      btn.appendChild(img);\n      shadow.append(style, btn, dot);\n\n      try {\n        let cfg =\n          typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.chatgpt ? SITE_CONFIG.chatgpt : null;\n        let mic = null;\n        if (cfg && Array.isArray(cfg.adjacentTargets)) {\n          for (let i = 0; i < cfg.adjacentTargets.length; i++) {\n            let sel = cfg.adjacentTargets[i];\n            if (sel) {\n              mic = anchor && anchor.querySelector(sel);\n              if (mic) {\n                break;\n              }\n            }\n          }\n        }\n        if (mic && anchor) {\n          let child: Element | null = mic;\n          while (child && child.parentElement !== anchor) {\n            child = child.parentElement;\n          }\n          if (child && child.parentElement === anchor) {\n            anchor.insertBefore(host, child);\n          }\n          host.style.marginRight = ''; // rely on container gap\n          host.style.marginLeft = '';\n        } else {\n          host.style.marginLeft = '4px'; // mild fallback spacing\n        }\n      } catch (_e) {\n        // Ignore errors during re-initialization\n      }\n\n      btn.addEventListener('click', function () {\n        handleMem0Modal('mem0-icon-button');\n      });\n\n      if (typeof updateNotificationDot === 'function') {\n        setTimeout(updateNotificationDot, 0);\n      }\n    },\n    // Optional safety net if deriveAnchor fails\n    fallback: function () {\n      let cfg =\n        typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.chatgpt ? SITE_CONFIG.chatgpt : null;\n      OPENMEMORY_UI.mountResilient({\n        anchors: [\n          {\n            find: function () {\n              let sel =\n                (cfg && cfg.editorSelector) ||\n                'textarea, [contenteditable=\"true\"], input[type=\"text\"]';\n              let ed = document.querySelector(sel);\n              if (!ed) {\n                return null;\n              }\n              try {\n                return cfg && typeof cfg.deriveAnchor === 'function'\n                  ? cfg.deriveAnchor(ed)\n                  : ed.closest('form') || ed.parentElement;\n              } catch (_) {\n                return ed.closest('form') || ed.parentElement;\n              }\n            },\n          },\n        ],\n        placement: (cfg && cfg.placement) || {\n          strategy: 'inline',\n          where: 'beforeend',\n          inlineAlign: 'end',\n        },\n        enableFloatingFallback: true,\n        render: function (shadow: ShadowRoot, host: HTMLElement, anchor: Element | null) {\n          host.id = 'mem0-icon-button'; // host is the shadow root container\n          let style = document.createElement('style');\n          style.textContent = `\n            :host { position: relative; }\n            .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center;\n              justify-content:center; width:32px; height:32px; border-radius:50%; }\n            .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n            .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px;\n              background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n            :host([data-has-text=\"1\"]) .dot { display:block; }\n          `;\n          let btn = document.createElement('button');\n          btn.className = 'mem0-btn';\n          let img = document.createElement('img');\n          img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n          let dot = document.createElement('div');\n          dot.className = 'dot';\n          btn.appendChild(img);\n          shadow.append(style, btn, dot);\n          btn.addEventListener('click', function () {\n            handleMem0Modal('mem0-icon-button');\n          });\n\n          // Move host to the left of the mic inside the same toolbar container\n          try {\n            let cfg =\n              typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.chatgpt\n                ? SITE_CONFIG.chatgpt\n                : null;\n            let mic = null;\n            if (cfg && Array.isArray(cfg.adjacentTargets)) {\n              for (let i = 0; i < cfg.adjacentTargets.length; i++) {\n                let sel = cfg.adjacentTargets[i];\n                if (sel) {\n                  mic = anchor && anchor.querySelector(sel);\n                  if (mic) {\n                    break;\n                  }\n                }\n              }\n            } else {\n              mic =\n                anchor &&\n                (anchor.querySelector('button[aria-label=\"Dictate button\"]') ||\n                  anchor.querySelector('button[aria-label*=\"mic\" i]') ||\n                  anchor.querySelector('button[aria-label*=\"voice\" i]'));\n            }\n            if (mic && anchor) {\n              let child: Element | null = mic;\n              while (child && child.parentElement !== anchor) {\n                child = child.parentElement;\n              }\n              if (child && child.parentElement === anchor) {\n                anchor.insertBefore(host, child);\n              }\n              host.style.marginRight = ''; // rely on container gap\n              host.style.marginLeft = '';\n            } else {\n              host.style.marginLeft = '4px'; // mild fallback spacing\n            }\n          } catch (_e) {\n            // Ignore errors during re-initialization\n          }\n\n          if (typeof updateNotificationDot === 'function') {\n            setTimeout(updateNotificationDot, 0);\n          }\n        },\n      });\n    },\n    persistCache: true,\n    cacheTtlMs: 24 * 60 * 60 * 1000,\n  });\n})();\n\nfunction getLastMessages(count: number): Array<{ role: MessageRole; content: string }> {\n  const messageContainer = document.querySelector('.flex.flex-col.text-sm.md\\\\:pb-9');\n  if (!messageContainer) {\n    return [];\n  }\n\n  const messageElements = Array.from(messageContainer.children).reverse();\n  const messages: Array<{ role: MessageRole; content: string }> = [];\n\n  for (const element of messageElements) {\n    if (messages.length >= count) {\n      break;\n    }\n\n    const userElement = element.querySelector('[data-message-author-role=\"user\"]');\n    const assistantElement = element.querySelector('[data-message-author-role=\"assistant\"]');\n\n    if (userElement) {\n      const content = userElement.querySelector('.whitespace-pre-wrap')?.textContent?.trim() || '';\n      messages.unshift({ role: MessageRole.User, content });\n    } else if (assistantElement) {\n      const content = assistantElement.querySelector('.markdown')?.textContent?.trim() || '';\n      messages.unshift({ role: MessageRole.Assistant, content });\n    }\n  }\n\n  return messages;\n}\n\nfunction getInputValue(): string {\n  const inputElement =\n    (document.querySelector('#prompt-textarea') as HTMLTextAreaElement | HTMLDivElement) ||\n    (document.querySelector('div[contenteditable=\"true\"]') as HTMLDivElement) ||\n    (document.querySelector('textarea') as HTMLTextAreaElement);\n\n  return inputElement\n    ? inputElement.textContent || (inputElement as HTMLTextAreaElement)?.value || ''\n    : '';\n}\n\nlet chatgptBackgroundSearchHandler: ((this: Element, ev: Event) => void) | null = null;\n\nfunction hookBackgroundSearchTyping() {\n  const inputElement =\n    document.querySelector('#prompt-textarea') ||\n    document.querySelector('div[contenteditable=\"true\"]') ||\n    document.querySelector('textarea');\n  if (!inputElement) {\n    return;\n  }\n\n  if (inputElement.dataset.mem0BackgroundHooked) {\n    return; \n  }\n\n  inputElement.dataset.mem0BackgroundHooked = 'true'; \n\n  if (!chatgptBackgroundSearchHandler) {\n    chatgptBackgroundSearchHandler = function () {\n      const text = getInputValue() || '';\n      console.log(\"Background search for:\", text); \n      chatgptSearch.setText(text);\n    };\n  }\n  inputElement.addEventListener(\n    'input',\n    chatgptBackgroundSearchHandler as (this: Element, ev: Event) => void\n  );\n  inputElement.addEventListener(\n    'keyup',\n    chatgptBackgroundSearchHandler as (this: Element, ev: Event) => void\n  );\n}\n\nfunction addSyncButton(): void {\n  const buttonContainer = document.querySelector('div.mt-5.flex.justify-end');\n  if (buttonContainer) {\n    let syncButton = document.querySelector('#sync-button') as HTMLButtonElement;\n\n    // If the syncButton does not exist, create it\n    if (!syncButton) {\n      syncButton = document.createElement('button');\n      syncButton.id = 'sync-button';\n      syncButton.className = 'btn relative btn-neutral mr-2';\n      syncButton.style.color = 'rgb(213, 213, 213)';\n      syncButton.style.backgroundColor = 'transparent';\n      syncButton.innerHTML =\n        '<div id=\"sync-button-content\" class=\"flex items-center justify-center font-semibold\">Sync Memory</div>';\n      syncButton.style.border = '1px solid rgb(213, 213, 213)';\n      syncButton.style.fontSize = '12px';\n      syncButton.style.fontWeight = '500';\n      // add margin right to syncButton\n      syncButton.style.marginRight = '8px';\n\n      const syncIcon = document.createElement('img');\n      syncIcon.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n      syncIcon.style.width = '16px';\n      syncIcon.style.height = '16px';\n      syncIcon.style.marginRight = '8px';\n\n      syncButton.prepend(syncIcon);\n\n      syncButton.addEventListener('click', handleSyncClick);\n\n      syncButton.addEventListener('mouseenter', () => {\n        if (!syncButton!.disabled) {\n          syncButton!.style.filter = 'opacity(0.7)';\n        }\n      });\n      syncButton.addEventListener('mouseleave', () => {\n        if (!syncButton!.disabled) {\n          syncButton!.style.filter = 'opacity(1)';\n        }\n      });\n    }\n\n    if (!buttonContainer.contains(syncButton)) {\n      buttonContainer.insertBefore(syncButton, buttonContainer.firstChild);\n    }\n\n    // Update sync button state\n    const updateSyncButtonState = (): void => {\n      // Define when the sync button should be enabled or disabled\n      (syncButton as HTMLButtonElement).disabled = false; // For example, always enabled\n      // Update opacity or pointer events if needed\n      if ((syncButton as HTMLButtonElement).disabled) {\n        (syncButton as HTMLButtonElement).style.opacity = '0.5';\n        (syncButton as HTMLButtonElement).style.pointerEvents = 'none';\n      } else {\n        (syncButton as HTMLButtonElement).style.opacity = '1';\n        (syncButton as HTMLButtonElement).style.pointerEvents = 'auto';\n      }\n    };\n\n    updateSyncButtonState();\n  } else {\n    // If resetMemoriesButton or specificTable is not found, remove syncButton from DOM\n    const existingSyncButton = document.querySelector('#sync-button');\n    if (existingSyncButton && existingSyncButton.parentNode) {\n      existingSyncButton.parentNode.removeChild(existingSyncButton);\n    }\n  }\n}\n\nfunction handleSyncClick(): void {\n  getMemoryEnabledState().then(memoryEnabled => {\n    if (!memoryEnabled) {\n      const btn = document.querySelector('#sync-button') as HTMLElement;\n      if (btn) {\n        showSyncPopup(btn, 'Memory is disabled');\n      }\n      return;\n    }\n\n    const table = document.querySelector('table.w-full.border-separate.border-spacing-0');\n    const syncButton = document.querySelector('#sync-button') as HTMLButtonElement;\n\n    if (table && syncButton) {\n      const rows = table.querySelectorAll('tbody tr');\n      const memories: Array<{ role: string; content: string }> = [];\n\n      // Change sync button state to loading\n      setSyncButtonLoadingState(true);\n\n      let syncedCount = 0;\n      const totalCount = rows.length;\n\n      rows.forEach(row => {\n        const cells = row.querySelectorAll('td');\n        if (cells.length >= 1 && cells[0]) {\n          const content =\n            cells[0].querySelector('div.whitespace-pre-wrap')?.textContent?.trim() || '';\n\n          const memory = {\n            role: MessageRole.User,\n            content: `Remember this about me: ${content}`,\n          };\n\n          memories.push(memory);\n\n          sendMemoryToMem0(memory, false)\n            .then(() => {\n              syncedCount++;\n              if (syncedCount === totalCount) {\n                showSyncPopup(syncButton, `${syncedCount} memories synced`);\n                setSyncButtonLoadingState(false);\n                // Open the modal with memories after syncing\n                // handleMem0Modal('sync-button');\n              }\n            })\n            .catch(() => {\n              if (syncedCount === totalCount) {\n                showSyncPopup(syncButton, `${syncedCount}/${totalCount} memories synced`);\n                setSyncButtonLoadingState(false);\n                // Open the modal with memories after syncing\n                // handleMem0Modal('sync-button');\n              }\n            });\n        }\n      });\n\n      sendMemoriesToMem0(memories)\n        .then(() => {\n          if (syncButton) {\n            showSyncPopup(syncButton, `${memories.length} memories synced`);\n          }\n          setSyncButtonLoadingState(false);\n          // Open the modal with memories after syncing\n          handleMem0Modal('sync-button');\n        })\n        .catch(error => {\n          console.error('Error syncing memories:', error);\n          if (syncButton) {\n            showSyncPopup(syncButton, 'Error syncing memories');\n          }\n          setSyncButtonLoadingState(false);\n          // Open the modal even if there was an error\n          handleMem0Modal('sync-button');\n        });\n    } else {\n      console.error('Table or Sync button not found');\n    }\n  });\n}\n\n// New function to send memories in batch\nfunction sendMemoriesToMem0(memories: Array<{ role: string; content: string }>): Promise<void> {\n  return new Promise<void>((resolve, reject) => {\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.USER_ID_CAMEL,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.USER_ID,\n      ],\n      function (items) {\n        if (items[StorageKey.API_KEY] || items[StorageKey.ACCESS_TOKEN]) {\n          const authHeader = items[StorageKey.ACCESS_TOKEN]\n            ? `Bearer ${items[StorageKey.ACCESS_TOKEN]}`\n            : `Token ${items[StorageKey.API_KEY]}`;\n          const userId =\n            items[StorageKey.USER_ID_CAMEL] || items[StorageKey.USER_ID] || DEFAULT_USER_ID;\n\n          const optionalParams: OptionalApiParams = {};\n          if (items[StorageKey.SELECTED_ORG]) {\n            optionalParams.org_id = items[StorageKey.SELECTED_ORG];\n          }\n          if (items[StorageKey.SELECTED_PROJECT]) {\n            optionalParams.project_id = items[StorageKey.SELECTED_PROJECT];\n          }\n\n          fetch('https://api.mem0.ai/v1/memories/', {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n              Authorization: authHeader,\n            },\n            body: JSON.stringify({\n              messages: memories,\n              user_id: userId,\n              infer: true,\n              metadata: {\n                provider: 'ChatGPT',\n              },\n              source: 'OPENMEMORY_CHROME_EXTENSION',\n              ...optionalParams,\n            }),\n          })\n            .then(response => {\n              if (!response.ok) {\n                reject(`Failed to add memories: ${response.status}`);\n              } else {\n                resolve();\n              }\n            })\n            .catch(error => reject(`Error sending memories to Mem0: ${error}`));\n        } else {\n          reject('API Key/Access Token not set');\n        }\n      }\n    );\n  });\n}\n\nfunction setSyncButtonLoadingState(isLoading: boolean): void {\n  const syncButton = document.querySelector('#sync-button') as HTMLButtonElement;\n  const syncButtonContent = document.querySelector('#sync-button-content') as HTMLElement;\n  if (syncButton) {\n    if (isLoading) {\n      syncButton.disabled = true;\n      syncButton.style.cursor = 'wait';\n      document.body.style.cursor = 'wait';\n      syncButton.style.opacity = '0.7';\n      if (syncButtonContent) {\n        syncButtonContent.textContent = 'Syncing...';\n      }\n    } else {\n      syncButton.disabled = false;\n      syncButton.style.cursor = 'pointer';\n      syncButton.style.opacity = '1';\n      document.body.style.cursor = 'default';\n      if (syncButtonContent) {\n        syncButtonContent.textContent = 'Sync Memory';\n      }\n    }\n  }\n}\n\nfunction showSyncPopup(button: HTMLElement, message: string): void {\n  const popup = document.createElement('div');\n\n  // Create and add the (i) icon\n  const infoIcon = document.createElement('span');\n  infoIcon.textContent = 'ⓘ ';\n  infoIcon.style.marginRight = '3px';\n\n  popup.appendChild(infoIcon);\n  popup.appendChild(document.createTextNode(message));\n\n  popup.style.cssText = `\n        position: absolute;\n        top: 50%;\n        left: -160px;\n        transform: translateY(-50%);\n        background-color: #171717;\n        color: white;\n        padding: 6px 8px;\n        border-radius: 6px;\n        font-size: 12px;\n        white-space: nowrap;\n        z-index: 1000;\n    `;\n\n  button.style.position = 'relative';\n  button.appendChild(popup);\n\n  setTimeout(() => {\n    popup.remove();\n  }, 3000);\n}\n\nfunction sendMemoryToMem0(\n  memory: { role: string; content: string },\n  infer: boolean = true\n): Promise<void> {\n  return new Promise<void>((resolve, reject) => {\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.USER_ID_CAMEL,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.USER_ID,\n      ],\n      function (items) {\n        if (items[StorageKey.API_KEY] || items[StorageKey.ACCESS_TOKEN]) {\n          const authHeader = items[StorageKey.ACCESS_TOKEN]\n            ? `Bearer ${items[StorageKey.ACCESS_TOKEN]}`\n            : `Token ${items[StorageKey.API_KEY]}`;\n          const userId =\n            items[StorageKey.USER_ID_CAMEL] || items[StorageKey.USER_ID] || DEFAULT_USER_ID;\n\n          const optionalParams: OptionalApiParams = {};\n          if (items[StorageKey.SELECTED_ORG]) {\n            optionalParams.org_id = items[StorageKey.SELECTED_ORG];\n          }\n          if (items[StorageKey.SELECTED_PROJECT]) {\n            optionalParams.project_id = items[StorageKey.SELECTED_PROJECT];\n          }\n\n          fetch('https://api.mem0.ai/v1/memories/', {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n              Authorization: authHeader,\n            },\n            body: JSON.stringify({\n              messages: [{ content: memory.content, role: MessageRole.User }],\n              user_id: userId,\n              infer: infer,\n              metadata: {\n                provider: 'ChatGPT',\n              },\n              source: 'OPENMEMORY_CHROME_EXTENSION',\n              ...optionalParams,\n            }),\n          })\n            .then(response => {\n              if (!response.ok) {\n                reject(`Failed to add memory: ${response.status}`);\n              } else {\n                resolve();\n              }\n            })\n            .catch(error => reject(`Error sending memory to Mem0: ${error}`));\n        } else {\n          reject('API Key/Access Token not set');\n        }\n      }\n    );\n  });\n}\n\n// Add this new function to get the memory_enabled state\nfunction getMemoryEnabledState(): Promise<boolean> {\n  return new Promise<boolean>(resolve => {\n    chrome.storage.sync.get([StorageKey.MEMORY_ENABLED], function (result) {\n      resolve(result.memory_enabled !== false); // Default to true if not set\n    });\n  });\n}\n\n// Returns whether auto-inject is enabled (default: false if not present)\n// (auto-inject helpers removed)\n\n// Update the initialization function to add the Mem0 icon button but not intercept Enter key\nfunction initializeMem0Integration(): void {\n  document.addEventListener('DOMContentLoaded', () => {\n    addSyncButton();\n    // (async () => await addMem0IconButton())();\n    addSendButtonListener();\n    // (async () => await updateNotificationDot())();\n    hookBackgroundSearchTyping();\n    setupAutoInjectPrefetch();\n  });\n\n  document.addEventListener('keydown', function (event) {\n    if (event.ctrlKey && event.key === 'm') {\n      event.preventDefault();\n      (async () => {\n        await handleMem0Modal('mem0-icon-button');\n      })();\n    }\n  });\n\n  // Remove global Enter interception previously added for auto-inject\n\n  observer = new MutationObserver(() => {\n    addSyncButton();\n    // (async () => await addMem0IconButton())();\n    addSendButtonListener();\n    // (async () => await updateNotificationDot())();\n    hookBackgroundSearchTyping();\n    setupAutoInjectPrefetch();\n  });\n\n  observer.observe(document.body, { childList: true, subtree: true });\n\n  // Add a MutationObserver to watch for changes in the DOM but don't intercept Enter key\n  const observerForUI = new MutationObserver(() => {\n    // (async () => await addMem0IconButton())();\n    addSendButtonListener();\n    // (async () => await updateNotificationDot())();\n    hookBackgroundSearchTyping();\n    setupAutoInjectPrefetch();\n  });\n\n  observerForUI.observe(document.body, {\n    childList: true,\n    subtree: true,\n  });\n}\n\n// (global auto-inject interceptors removed)\n\n// Function to show login popup\nfunction showLoginPopup() {\n  // First remove any existing popups\n  const existingPopup = document.querySelector('#mem0-login-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  // Create popup container\n  const popupOverlay = document.createElement('div');\n  popupOverlay.id = 'mem0-login-popup';\n  popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 10001;\n  `;\n\n  const popupContainer = document.createElement('div');\n  popupContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  `;\n\n  // Close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #A1A1AA;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n  closeButton.innerHTML = '&times;';\n  closeButton.addEventListener('click', () => {\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Logo and heading\n  const logoContainer = document.createElement('div');\n  logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n  const logo = document.createElement('img');\n  logo.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logo.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const logoDark = document.createElement('img');\n  logoDark.src = chrome.runtime.getURL('icons/mem0-icon-black.png');\n  logoDark.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const heading = document.createElement('h2');\n  heading.textContent = 'Sign in to OpenMemory';\n  heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 600;\n  `;\n\n  logoContainer.appendChild(heading);\n\n  // Message\n  const message = document.createElement('p');\n  message.textContent =\n    'Please sign in to access your memories and personalize your conversations!';\n  message.style.cssText = `\n    margin-bottom: 24px;\n    color: #D4D4D8;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n  // Sign in button\n  const signInButton = document.createElement('button');\n  signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: white;\n    color: black;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  // Add text in span for better centering\n  const signInText = document.createElement('span');\n  signInText.textContent = 'Sign in with Mem0';\n\n  signInButton.appendChild(logoDark);\n  signInButton.appendChild(signInText);\n\n  signInButton.addEventListener('mouseenter', () => {\n    signInButton.style.backgroundColor = '#f5f5f5';\n  });\n\n  signInButton.addEventListener('mouseleave', () => {\n    signInButton.style.backgroundColor = 'white';\n  });\n\n  // Open sign-in page when clicked\n  signInButton.addEventListener('click', () => {\n    window.open('https://app.mem0.ai/login', '_blank');\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Assemble popup\n  popupContainer.appendChild(logoContainer);\n  popupContainer.appendChild(message);\n  popupContainer.appendChild(signInButton);\n\n  popupOverlay.appendChild(popupContainer);\n  popupOverlay.appendChild(closeButton);\n\n  // Add click event to close when clicking outside\n  popupOverlay.addEventListener('click', e => {\n    if (e.target === popupOverlay) {\n      document.body.removeChild(popupOverlay);\n    }\n  });\n\n  // Add to body\n  document.body.appendChild(popupOverlay);\n}\n\ninitializeMem0Integration();\n// --- SPA navigation handling and extension context guard (mirrors Claude) ---\nlet chatgptExtensionContextValid = true;\nlet chatgptCurrentUrl = window.location.href;\n\nfunction chatgptCheckExtensionContext() {\n  try {\n    // chrome.runtime may throw if context invalidated\n    // Using optional chaining to avoid ReferenceError\n    // lastError exists only after an API call; treat presence of runtime as validity\n    const isValid = !!(chrome && chrome.runtime);\n    if (chatgptExtensionContextValid && !isValid) {\n      chatgptExtensionContextValid = false;\n    }\n    return isValid;\n  } catch {\n    chatgptExtensionContextValid = false;\n    return false;\n  }\n}\n\nfunction chatgptDetectNavigation() {\n  const newUrl = window.location.href;\n  if (newUrl !== chatgptCurrentUrl) {\n    chatgptCurrentUrl = newUrl;\n\n    // Re-initialize UI after small delay for DOM to settle\n    setTimeout(() => {\n      try {\n        addSyncButton();\n        // (async () => await addMem0IconButton())();\n        addSendButtonListener();\n        // (async () => await updateNotificationDot())();\n      } catch {\n        // Ignore errors when setting contenteditable\n      }\n    }, 300);\n  }\n}\n\n// Poll for SPA navigations and context validity\nsetInterval(() => {\n  chatgptCheckExtensionContext();\n  chatgptDetectNavigation();\n}, 1000);\n\n// Hook browser history navigation\nwindow.addEventListener('popstate', () => setTimeout(chatgptDetectNavigation, 100));\nconst chatgptOriginalPushState = history.pushState;\nhistory.pushState = function (data: HistoryStateData, unused: string, url?: string | URL | null) {\n  chatgptOriginalPushState.call(history, data, unused, url);\n  setTimeout(chatgptDetectNavigation, 100);\n};\nconst chatgptOriginalReplaceState = history.replaceState;\nhistory.replaceState = function (\n  data: HistoryStateData,\n  unused: string,\n  url?: string | URL | null\n) {\n  chatgptOriginalReplaceState.call(history, data, unused, url);\n  setTimeout(chatgptDetectNavigation, 100);\n};\n"
  },
  {
    "path": "src/claude/content.ts",
    "content": "import { MessageRole } from '../types/api';\nimport type { HistoryStateData } from '../types/browser';\nimport type { ExtendedDocument, ExtendedElement } from '../types/dom';\nimport type { MemoryItem, MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { type StorageData, StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI, type Placement } from '../utils/util_positioning';\n\n// Chrome runtime types\ninterface ChromeRuntimeLastError {\n  message?: string;\n}\n\ninterface ChromeRuntimeWithLastError {\n  lastError?: ChromeRuntimeLastError;\n}\n\ninterface ChromeRuntime extends ChromeRuntimeWithLastError {\n  // Add other chrome.runtime properties as needed\n}\n\nexport {};\n\n// Global variables to store all memories\nlet allMemories: string[] = [];\nlet memoryModalShown: boolean = false;\nlet isProcessingMem0: boolean = false;\nlet memoryEnabled: boolean = true;\n\n// Cache of the latest typed text to avoid race when the editor is cleared\nlet lastTyped = '';\n// Timestamp of when a send was initiated (to prevent duplicate fallback posts)\nlet lastSendInitiatedAt = 0;\n\nlet currentModalSourceButtonId: string | null = null;\n\nconst claudeSearch = createOrchestrator({\n  fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n    const data = await new Promise<SearchStorage>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as SearchStorage);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      return [];\n    }\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const threshold =\n      data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n        ? data[StorageKey.SIMILARITY_THRESHOLD]\n        : 0.1;\n    const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    // Clean query by stripping any appended memory header/content (debounced path)\n    const cleanQuery = (function () {\n      try {\n        const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n        return String(query).replace(MEM0_PLAIN, '').trim();\n      } catch (_e) {\n        return query;\n      }\n    })();\n\n    const payload = {\n      query: cleanQuery,\n      filters: { user_id: userId },\n      rerank: true,\n      threshold,\n      top_k: topK,\n      filter_memories: false,\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      ...optionalParams,\n    };\n\n    const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify(payload),\n      signal: opts && opts.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error(`API request failed with status ${res.status}`);\n    }\n    return await res.json();\n  },\n\n  onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n    if (!memoryModalShown) {\n      return;\n    }\n    const memoryItems = (responseData || []).map((item: MemorySearchItem, index: number) => ({\n      id: String(item.id || `memory-${Date.now()}-${index}`),\n      text: item.memory,\n      categories: item.categories || [],\n    }));\n    createMemoryModal(memoryItems, false, currentModalSourceButtonId);\n  },\n\n  onError: function () {\n    if (memoryModalShown) {\n      createMemoryModal([], false, currentModalSourceButtonId);\n    }\n  },\n\n  minLength: 3,\n  debounceMs: 75,\n  cacheTTL: 60000,\n});\n\n// Sliding window for conversation context\nlet conversationHistory: Array<{ role: MessageRole; content: string; timestamp: number }> = [];\nconst MAX_CONVERSATION_HISTORY = 12; // Keep last 12 messages (6 pairs of user/assistant)\n\n// Function to add message to conversation history with sliding window\nfunction addToConversationHistory(role: MessageRole, content: string) {\n  if (!content || !content.trim()) {\n    return;\n  }\n\n  const trimmedContent = content.trim();\n\n  // Check for duplicate - don't add if the last message is identical\n  if (conversationHistory.length > 0) {\n    const lastMessage = conversationHistory[conversationHistory.length - 1];\n    if (lastMessage && lastMessage.role === role && lastMessage.content === trimmedContent) {\n      return;\n    }\n  }\n\n  const message = {\n    role: role,\n    content: trimmedContent,\n    timestamp: Date.now(),\n  };\n\n  // Add to history\n  conversationHistory.push(message);\n\n  // Maintain sliding window - remove oldest messages if we exceed limit\n  if (conversationHistory.length > MAX_CONVERSATION_HISTORY) {\n    conversationHistory.splice(0, conversationHistory.length - MAX_CONVERSATION_HISTORY);\n  }\n}\n\n// Function to get conversation context for memory creation\nfunction getConversationContext(includeCurrent: boolean = true) {\n  if (conversationHistory.length === 0) {\n    return [];\n  }\n\n  // Get the last 6 messages for context (excluding current if requested)\n  const contextSize = 6;\n  let contextMessages = [...conversationHistory];\n\n  if (!includeCurrent && contextMessages.length > 0) {\n    // Remove the last message if it's the current user message\n    contextMessages = contextMessages.slice(0, -1);\n  }\n\n  // Get last N messages\n  const context = contextMessages.slice(-contextSize).map(msg => ({\n    role: msg.role,\n    content: msg.content,\n  }));\n\n  return context;\n}\n\n// Function to initialize conversation history from existing messages on page\nfunction initializeConversationHistoryFromDOM() {\n  const messageContainer = document.querySelector(\n    '.flex-1.flex.flex-col.gap-3.px-4.max-w-3xl.mx-auto.w-full'\n  );\n\n  if (!messageContainer) {\n    return;\n  }\n\n  const messageElements = Array.from(messageContainer.children);\n\n  // Process existing messages in chronological order\n  messageElements.forEach(element => {\n    const userElement = element.querySelector('.font-user-message');\n    const assistantElement = element.querySelector('.font-claude-message');\n\n    if (userElement) {\n      const content = (userElement.textContent || '').trim();\n      if (content) {\n        addToConversationHistory(MessageRole.User, content);\n      }\n    } else if (assistantElement) {\n      const content = (assistantElement.textContent || '').trim();\n      if (content) {\n        addToConversationHistory(MessageRole.Assistant, content);\n      }\n    }\n  });\n}\n\n// Initialize the MutationObserver variable\n// let observer: MutationObserver;\n// let inputObserver: MutationObserver;\n// let debounceTimer: number | undefined;\n\n// Track added memories by ID\nconst allMemoriesById: Set<string> = new Set<string>();\n\n// Reference to the modal overlay for updates\nlet currentModalOverlay:\n  | HTMLDivElement\n  | (HTMLDivElement & { _cleanupDragEvents?: () => void })\n  | null = null;\n\n// Track the current modal container and placement cleanup\nlet currentModalContainer: HTMLDivElement | null = null;\nlet currentModalUnplace: (() => void) | null = null;\n\n// Function to get memory enabled state from storage\nasync function getMemoryEnabledState() {\n  return new Promise(resolve => {\n    // Check if extension context is valid\n    if (!chrome || !chrome.storage || !chrome.storage.sync) {\n      console.log('⚠️ Chrome extension context invalid, defaulting to enabled');\n      resolve(true); // Default to enabled if we can't check\n      return;\n    }\n\n    try {\n      chrome.storage.sync.get(StorageKey.MEMORY_ENABLED, function (data) {\n        try {\n          // @ts-ignore\n          if (chrome.runtime && chrome.runtime.lastError) {\n            console.log(\n              '⚠️ Chrome storage error, defaulting to enabled:',\n              (chrome.runtime as ChromeRuntime).lastError\n            );\n            resolve(true); // Default to enabled if error\n            return;\n          }\n          console.log('🧠 Memory enabled from storage:', data.memory_enabled);\n          resolve(data.memory_enabled);\n        } catch {\n          // Ignore errors when checking chrome.runtime.lastError\n        }\n      });\n    } catch (error) {\n      console.log('⚠️ Exception getting memory state, defaulting to enabled:', error);\n      resolve(true); // Default to enabled if exception\n    }\n  });\n}\n\n// Function to remove mem0 button if it exists\nfunction removeMemButton(): void {\n  const mem0Button = document.querySelector('#mem0-button');\n  if (mem0Button) {\n    const buttonContainer = mem0Button.closest('div');\n    if (buttonContainer) {\n      buttonContainer.remove();\n    } else {\n      mem0Button.remove();\n    }\n  }\n\n  // Also remove tooltip if it exists\n  const tooltip = document.querySelector('#mem0-tooltip');\n  if (tooltip) {\n    tooltip.remove();\n  }\n}\n\n// function addMem0Button(): void {\n//   // Legacy addMem0Button removed; OPENMEMORY_UI handles icon mounting\n// }\n\nfunction createPopup(container: HTMLElement, position: string = 'top'): HTMLElement {\n  const popup = document.createElement('div');\n  popup.className = 'mem0-popup';\n  let positionStyles = '';\n\n  if (position === 'top') {\n    positionStyles = `\n      bottom: 100%;\n      left: 50%;\n      transform: translateX(-40%);\n      margin-bottom: 11px;\n    `;\n  } else if (position === 'right') {\n    positionStyles = `\n      top: 50%;\n      left: 100%;\n      transform: translateY(-50%);\n      margin-left: 11px;\n    `;\n  }\n\n  popup.style.cssText = `\n  display: none;\n  position: absolute;\n  background-color: #21201C;\n  color: white;\n  padding: 6px 8px;\n  border-radius: 6px;\n  font-size: 12px;\n  z-index: 10000;\n  white-space: nowrap;\n  box-shadow: 0 2px 5px rgba(0,0,0,0.2);\n  ${positionStyles}\n      `;\n  container.appendChild(popup);\n  return popup;\n}\n\nconst sendButton = document.querySelector('button[aria-label=\"Send Message\"]');\nconst sendUpButton = document.querySelector('button[aria-label=\"Send message\"]');\nconst screenshotButton = document.querySelector('button[aria-label=\"Capture screenshot\"]');\nconst inputToolsMenuButton = document.querySelector('#input-tools-menu-trigger');\n\n// Check for any existing mem0 buttons before creating legacy button\nconst hasAnyMem0Button =\n  document.querySelector('#mem0-button') ||\n  document.getElementById('mem0-icon-button') ||\n  document.querySelector('[id*=\"mem0\"], .mem0-btn');\n\nif (inputToolsMenuButton && !hasAnyMem0Button) {\n  const buttonContainer = document.createElement('div');\n  buttonContainer.style.position = 'relative';\n  buttonContainer.style.display = 'inline-block';\n\n  const mem0Button = document.createElement('button');\n  mem0Button.id = 'mem0-button';\n  mem0Button.className = inputToolsMenuButton.className;\n  mem0Button.style.marginLeft = '0px';\n  mem0Button.setAttribute('aria-label', 'Add memories to your prompt');\n\n  const mem0Icon = document.createElement('img');\n  mem0Icon.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n  mem0Icon.style.width = '16px';\n  mem0Icon.style.height = '16px';\n  mem0Icon.style.borderRadius = '50%';\n\n  const popup = createPopup(buttonContainer, 'top');\n  mem0Button.appendChild(mem0Icon);\n  mem0Button.addEventListener('click', () => {\n    if (memoryEnabled) {\n      // Hide the tooltip if it's showing\n      const tooltip = document.querySelector('#mem0-tooltip');\n      if (tooltip) {\n        tooltip.style.display = 'none';\n      }\n      handleMem0Modal(popup);\n    }\n  });\n\n  // Create notification dot\n  const notificationDot = document.createElement('div');\n  notificationDot.id = 'mem0-notification-dot';\n  notificationDot.style.cssText = `\n    position: absolute;\n    top: -3px;\n    right: -3px;\n    width: 10px;\n    height: 10px;\n    background-color: rgb(128, 221, 162);\n    border-radius: 50%;\n    border: 2px solid #1C1C1E;\n    display: none;\n    z-index: 1001;\n    pointer-events: none;\n  `;\n  mem0Button.appendChild(notificationDot);\n\n  // Add keyframe animation for the dot\n  if (!document.getElementById('notification-dot-animation')) {\n    const style = document.createElement('style');\n    style.id = 'notification-dot-animation';\n    style.innerHTML = `\n      @keyframes popIn {\n        0% { transform: scale(0); }\n        50% { transform: scale(1.2); }\n        100% { transform: scale(1); }\n      }\n      \n      #mem0-notification-dot.active {\n        display: block !important;\n        animation: popIn 0.3s ease-out forwards;\n      }\n    `;\n    document.head.appendChild(style);\n  }\n\n  buttonContainer.appendChild(mem0Button);\n\n  const tooltip = document.createElement('div');\n  tooltip.id = 'mem0-tooltip';\n  tooltip.textContent = 'Add memories to your prompt';\n  tooltip.style.cssText = `\n              display: none;\n              position: fixed;\n              background-color: black;\n              color: white;\n              padding: 3px 7px;\n              border-radius: 6px;\n              font-size: 12px;\n              z-index: 10000;\n              pointer-events: none;\n              white-space: nowrap;\n              transform: translateX(-50%);\n          `;\n  document.body.appendChild(tooltip);\n\n  mem0Button.addEventListener('mouseenter', () => {\n    // Hide any existing popup first\n    const existingMem0Popup = document.querySelector('.mem0-popup[style*=\"display: block\"]');\n    if (existingMem0Popup && existingMem0Popup !== popup) {\n      existingMem0Popup.style.display = 'none';\n    }\n\n    const rect = mem0Button.getBoundingClientRect();\n    const buttonCenterX = rect.left + rect.width / 2;\n\n    // Set initial tooltip properties\n    tooltip.style.display = 'block';\n\n    // Once displayed, we can get its height and set proper positioning\n    const tooltipHeight = (tooltip as HTMLElement).offsetHeight || 24; // Default height if not yet rendered\n\n    tooltip.style.left = `${buttonCenterX}px`;\n    tooltip.style.top = `${rect.top - tooltipHeight - 10}px`; // Position 10px above button\n  });\n\n  mem0Button.addEventListener('mouseleave', () => {\n    tooltip.style.display = 'none';\n  });\n\n  // Find the parent container to place the button at the same level as input-tools-menu\n  const parentContainer =\n    inputToolsMenuButton.closest('.relative.flex-1.flex.items-center.gap-2') ||\n    inputToolsMenuButton.closest('.relative.flex-1') ||\n    ((inputToolsMenuButton.parentNode as HTMLElement)?.parentNode?.parentNode?.parentNode\n      ?.parentNode as HTMLElement);\n\n  if (parentContainer) {\n    // Find the third position in the container - after the first two divs\n    // Looking for the flex-row div to insert before it\n    const flexRowDiv = parentContainer.querySelector('.flex.flex-row.items-center.gap-2.min-w-0');\n\n    // Find the tools div that we want to position after\n    const toolsDiv = inputToolsMenuButton.closest('div > div > div > div')?.parentNode?.parentNode;\n\n    // Make sure our button is the third div in the container\n    if (flexRowDiv && toolsDiv) {\n      // Insert right after the tools div and before the flex-row div\n      parentContainer.insertBefore(buttonContainer, flexRowDiv);\n    } else {\n      // Fallback to just append to the parent\n      parentContainer.appendChild(buttonContainer);\n    }\n  } else {\n    // Fallback to original behavior if parent not found\n    inputToolsMenuButton.parentNode?.insertBefore(\n      buttonContainer,\n      inputToolsMenuButton.nextSibling\n    );\n  }\n\n  // Update notification dot\n  updateNotificationDot();\n} else if (\n  window.location.href.includes('claude.ai/new') &&\n  screenshotButton &&\n  !hasAnyMem0Button\n) {\n  const buttonContainer = document.createElement('div');\n  buttonContainer.style.position = 'relative';\n  buttonContainer.style.display = 'inline-block';\n\n  const mem0Button = document.createElement('button');\n  mem0Button.id = 'mem0-button';\n  mem0Button.className = screenshotButton.className;\n  mem0Button.style.marginLeft = '0px';\n  mem0Button.setAttribute('aria-label', 'Add memories to your prompt');\n\n  const mem0Icon = document.createElement('img');\n  mem0Icon.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n  mem0Icon.style.width = '16px';\n  mem0Icon.style.height = '16px';\n  mem0Icon.style.borderRadius = '50%';\n\n  const popup = createPopup(buttonContainer, 'right');\n  mem0Button.appendChild(mem0Icon);\n  mem0Button.addEventListener('click', () => {\n    if (memoryEnabled) {\n      // Hide the tooltip if it's showing\n      const tooltip = document.querySelector('#mem0-tooltip');\n      if (tooltip) {\n        tooltip.style.display = 'none';\n      }\n\n      handleMem0Modal(popup);\n    }\n  });\n\n  // Create notification dot\n  const notificationDot = document.createElement('div');\n  notificationDot.id = 'mem0-notification-dot';\n  notificationDot.style.cssText = `\n        position: absolute;\n        top: -3px;\n        right: -3px;\n        width: 10px;\n        height: 10px;\n        background-color: rgb(128, 221, 162);\n        border-radius: 50%;\n        border: 2px solid #1C1C1E;\n        display: none;\n        z-index: 1001;\n        pointer-events: none;\n      `;\n  mem0Button.appendChild(notificationDot);\n\n  buttonContainer.appendChild(mem0Button);\n\n  const tooltip = document.createElement('div');\n  tooltip.id = 'mem0-tooltip';\n  tooltip.textContent = 'Add memories to your prompt';\n  tooltip.style.cssText = `\n              display: none;\n              position: fixed;\n              background-color: black;\n              color: white;\n              padding: 3px 7px;\n              border-radius: 6px;\n              font-size: 12px;\n              z-index: 10000;\n              pointer-events: none;\n              white-space: nowrap;\n              transform: translateX(-50%);\n          `;\n  document.body.appendChild(tooltip);\n\n  mem0Button.addEventListener('mouseenter', () => {\n    // Hide any existing popup first\n    const existingMem0Popup = document.querySelector('.mem0-popup[style*=\"display: block\"]');\n    if (existingMem0Popup && existingMem0Popup !== popup) {\n      existingMem0Popup.style.display = 'none';\n    }\n\n    const rect = mem0Button.getBoundingClientRect();\n    const buttonCenterX = rect.left + rect.width / 2;\n\n    // Set initial tooltip properties\n    tooltip.style.display = 'block';\n\n    // Once displayed, we can get its height and set proper positioning\n    const tooltipHeight = tooltip.offsetHeight || 24; // Default height if not yet rendered\n\n    tooltip.style.left = `${buttonCenterX}px`;\n    tooltip.style.top = `${rect.top - tooltipHeight - 10}px`; // Position 10px above button\n  });\n\n  mem0Button.addEventListener('mouseleave', () => {\n    tooltip.style.display = 'none';\n  });\n\n  screenshotButton.parentNode?.insertBefore(buttonContainer, screenshotButton.nextSibling);\n\n  // Update notification dot\n  updateNotificationDot();\n} else if ((sendButton || sendUpButton) && !hasAnyMem0Button) {\n  const targetButton = sendButton || sendUpButton;\n  if (targetButton) {\n    // Find the parent container of the send button\n    const buttonParent = targetButton.parentNode;\n    if (buttonParent) {\n      const buttonContainer = document.createElement('div');\n      buttonContainer.style.position = 'relative';\n      buttonContainer.style.display = 'inline-block';\n      buttonContainer.style.marginRight = '12px';\n\n      const mem0Button = document.createElement('button');\n      mem0Button.id = 'mem0-button';\n      mem0Button.style.cssText = `\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        width: 32px;\n        height: 32px;\n        padding: 0;\n        background: transparent;\n        border: none;\n        cursor: pointer;\n        border-radius: 8px;\n        position: relative;\n        transition: background-color 0.3s ease;\n      `;\n      mem0Button.setAttribute('aria-label', 'Add memories to your prompt');\n\n      const mem0Icon = document.createElement('img');\n      mem0Icon.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n      mem0Icon.style.width = '20px';\n      mem0Icon.style.height = '20px';\n      mem0Icon.style.borderRadius = '50%';\n\n      // Create notification dot\n      const notificationDot = document.createElement('div');\n      notificationDot.id = 'mem0-notification-dot';\n      notificationDot.style.cssText = `\n        position: absolute;\n        top: 0px;\n        right: 0px;\n        width: 10px;\n        height: 10px;\n        background-color: rgb(128, 221, 162);\n        border-radius: 50%;\n        border: 2px solid #1C1C1E;\n        display: none;\n        z-index: 1001;\n        pointer-events: none;\n      `;\n      const popup = createPopup(buttonContainer, 'top');\n      mem0Button.appendChild(mem0Icon);\n      mem0Button.appendChild(notificationDot);\n      mem0Button.addEventListener('click', () => {\n        if (memoryEnabled) {\n          // Hide the tooltip if it's showing\n          const tooltip = document.querySelector('#mem0-tooltip');\n          if (tooltip) {\n            tooltip.style.display = 'none';\n          }\n\n          handleMem0Modal(popup);\n        }\n      });\n\n      const tooltip = document.createElement('div');\n      tooltip.id = 'mem0-tooltip';\n      tooltip.textContent = 'Add memories to your prompt';\n      tooltip.style.cssText = `\n                  display: none;\n                  position: fixed;\n                  background-color: black;\n                  color: white;\n                  padding: 3px 7px;\n                  border-radius: 6px;\n                  font-size: 12px;\n                  z-index: 10000;\n                  pointer-events: none;\n                  white-space: nowrap;\n                  transform: translateX(-50%);\n              `;\n      document.body.appendChild(tooltip);\n\n      mem0Button.addEventListener('mouseenter', () => {\n        // Hide any existing popup first\n        const existingMem0Popup = document.querySelector('.mem0-popup[style*=\"display: block\"]');\n        if (existingMem0Popup && existingMem0Popup !== popup) {\n          existingMem0Popup.style.display = 'none';\n        }\n\n        const rect = mem0Button.getBoundingClientRect();\n        const buttonCenterX = rect.left + rect.width / 2;\n\n        // Set initial tooltip properties\n        tooltip.style.display = 'block';\n\n        // Once displayed, we can get its height and set proper positioning\n        const tooltipHeight = tooltip.offsetHeight || 24; // Default height if not yet rendered\n\n        tooltip.style.left = `${buttonCenterX}px`;\n        tooltip.style.top = `${rect.top - tooltipHeight - 10}px`; // Position 10px above button\n      });\n\n      mem0Button.addEventListener('mouseleave', () => {\n        mem0Button.style.backgroundColor = 'transparent';\n        popup.style.display = 'none';\n      });\n\n      // Set popover text\n      popup.textContent = 'Add memories to your prompt';\n\n      buttonContainer.appendChild(mem0Button);\n\n      // Insert the button before the send button\n      if (buttonParent.querySelector('button[aria-label=\"Send message\"]')) {\n        buttonParent.insertBefore(\n          buttonContainer,\n          buttonParent.querySelector('button[aria-label=\"Send message\"]')\n        );\n      } else {\n        buttonParent.insertBefore(buttonContainer, targetButton);\n      }\n\n      // Update notification dot\n      updateNotificationDot();\n    }\n  }\n}\n\n// Send button listeners are now handled in initializeMem0Integration for better reliability\n\n// Also handle Enter key press for sending messages\nconst inputElement =\n  document.querySelector('div[contenteditable=\"true\"]') ||\n  document.querySelector('textarea') ||\n  document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n  document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n\nif (inputElement && !inputElement.dataset.mem0KeyListener) {\n  inputElement.dataset.mem0KeyListener = 'true';\n  inputElement.addEventListener('keydown', function (event: Event) {\n    const keyboardEvent = event as KeyboardEvent;\n    // Check if Enter was pressed without Shift (standard send behavior)\n    if (\n      keyboardEvent.key === 'Enter' &&\n      !keyboardEvent.shiftKey &&\n      !keyboardEvent.ctrlKey &&\n      !keyboardEvent.metaKey\n    ) {\n      // Don't process for textarea which may want newlines\n      if (inputElement.tagName.toLowerCase() !== 'textarea') {\n        // Snapshot before send\n        const current = getInputValue();\n        if (current && current.trim() !== '') {\n          lastTyped = current;\n        }\n        lastSendInitiatedAt = Date.now();\n        // Capture and save memory asynchronously\n        captureAndStoreMemory(lastTyped);\n\n        // Clear all memories after sending\n        setTimeout(() => {\n          allMemories = [];\n          allMemoriesById.clear();\n        }, 100);\n      }\n    }\n  });\n  // Keep a live cache during typing to improve reliability\n  if (!inputElement.dataset.mem0CacheListener) {\n    inputElement.dataset.mem0CacheListener = 'true';\n    const updateCache = () => {\n      const val = getInputValue();\n      if (val && val.trim() !== '') {\n        lastTyped = val;\n      }\n    };\n    inputElement.addEventListener('input', updateCache, true);\n    inputElement.addEventListener('compositionend', updateCache, true);\n  }\n}\n\n// Update notification dot state\nupdateNotificationDot();\n\n// Using MemoryItem from src/types/content-scripts.ts\n\nfunction createMemoryModal(\n  memoryItems: MemoryItem[],\n  isLoading: boolean = false,\n  sourceButtonId: string | null = null\n): void {\n  console.log('🎯 createMemoryModal called', {\n    memoryItems,\n    isLoading,\n    sourceButtonId,\n    memoryModalShown,\n  });\n  // Close existing modal if it exists\n  if (memoryModalShown) {\n    console.log('🗂️ Closing existing modal');\n    try {\n      if (typeof currentModalUnplace === 'function') {\n        currentModalUnplace();\n      }\n    } catch {\n      // Ignore error\n    }\n    if (currentModalContainer && currentModalContainer.isConnected) {\n      try {\n        currentModalContainer.remove();\n      } catch {\n        // Ignore error\n      }\n    }\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n    }\n  }\n\n  memoryModalShown = true;\n  console.log('✅ Modal state set to shown');\n  let currentMemoryIndex = 0;\n\n  // Calculate modal dimensions (estimated)\n  const modalWidth = 447;\n  let modalHeight = 400; // Default height\n  let memoriesPerPage = 3; // Default number of memories per page\n\n  // Resolve anchor element\n  function resolveAnchor() {\n    if (sourceButtonId) {\n      const byId = document.getElementById(sourceButtonId);\n      if (byId) {\n        return byId;\n      }\n    }\n\n    const iconBtn = document.querySelector('#mem0-icon-button');\n    if (iconBtn) {\n      return iconBtn;\n    }\n    return (\n      document.querySelector('div[contenteditable=\"true\"]') ||\n      document.querySelector('textarea') ||\n      document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n      document.querySelector('p[data-placeholder=\"Reply to Claude...\"]')\n    );\n  }\n\n  const anchorEl = resolveAnchor();\n\n  // Choose placement side and adjust height/page density\n  let placementSide = 'bottom';\n  // let placementAlign = 'end';\n  if (anchorEl) {\n    const r = anchorEl.getBoundingClientRect();\n    const viewportHeight = window.innerHeight;\n    if (r.top >= modalHeight + 10) {\n      placementSide = 'top';\n      memoriesPerPage = 3;\n    } else {\n      placementSide = 'bottom';\n      if (r.bottom > viewportHeight / 2) {\n        modalHeight = 300;\n        memoriesPerPage = 2;\n      }\n    }\n  }\n\n  // Create modal overlay\n  const modalOverlay = document.createElement('div');\n  modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n  // Save reference to current modal overlay\n  currentModalOverlay = modalOverlay;\n\n  // Add event listener to close modal when clicking outside\n  modalOverlay.addEventListener('click', event => {\n    // Only close if clicking directly on the overlay, not its children\n    if (event.target === modalOverlay) {\n      closeModal();\n    }\n  });\n\n  // Create modal container (placement handled by OPENMEMORY_UI)\n  const modalContainer = document.createElement('div');\n  modalContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    pointer-events: auto;\n    border: 1px solid #27272A;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    overflow: hidden;\n  `;\n\n  // Place near the anchor using the new positioning util\n  let unplace = () => {\n    // Ignore error\n  };\n  if (OPENMEMORY_UI && anchorEl) {\n    // Prefer SITE_CONFIG for Claude to dock within the composer box\n    let cfg = typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.claude ? SITE_CONFIG.claude : null;\n    let placementCfg = (cfg && cfg.placement) || {\n      strategy: 'dock' as const,\n      container: 'form',\n      side: (placementSide === 'top' ? 'top' : 'bottom') as 'top' | 'bottom',\n      align: 'start' as const,\n      gap: 8,\n    };\n    unplace = OPENMEMORY_UI.applyPlacement({\n      container: modalContainer,\n      anchor: anchorEl,\n      placement: placementCfg,\n    });\n  } else {\n    // Fallback placement\n    document.body.appendChild(modalContainer);\n    Object.assign(modalContainer.style, {\n      position: 'fixed',\n      left: '50%',\n      top: '20%',\n      transform: 'translateX(-50%)',\n      zIndex: '2147483647',\n    });\n  }\n\n  // Track current container/unplace for cleanup on next open\n  currentModalContainer = modalContainer;\n  currentModalUnplace = unplace;\n\n  // Create modal header\n  const modalHeader = document.createElement('div');\n  modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #232325;\n    flex-shrink: 0;\n    cursor: move;\n    user-select: none;\n  `;\n\n  // Add drag functionality\n  let isDragging = false;\n  let dragOffset = { x: 0, y: 0 };\n\n  modalHeader.addEventListener('mousedown', e => {\n    // Only start drag if clicking on the header itself, not buttons\n    if (\n      e.target === modalHeader ||\n      e.target === title ||\n      e.target === logoImg ||\n      e.target === headerLeft\n    ) {\n      isDragging = true;\n      const rect = modalContainer.getBoundingClientRect();\n      dragOffset.x = e.clientX - rect.left;\n      dragOffset.y = e.clientY - rect.top;\n\n      // Switch to fixed positioning for dragging\n      modalContainer.style.position = 'fixed';\n      modalContainer.style.left = rect.left + 'px';\n      modalContainer.style.top = rect.top + 'px';\n      modalContainer.style.transform = 'none';\n      modalContainer.style.zIndex = '2147483647';\n\n      e.preventDefault();\n    }\n  });\n\n  document.addEventListener('mousemove', e => {\n    if (isDragging && modalContainer) {\n      const newX = e.clientX - dragOffset.x;\n      const newY = e.clientY - dragOffset.y;\n\n      // Keep modal within viewport bounds\n      const maxX = window.innerWidth - modalContainer.offsetWidth;\n      const maxY = window.innerHeight - modalContainer.offsetHeight;\n\n      modalContainer.style.left = Math.max(0, Math.min(newX, maxX)) + 'px';\n      modalContainer.style.top = Math.max(0, Math.min(newY, maxY)) + 'px';\n    }\n  });\n\n  document.addEventListener('mouseup', () => {\n    isDragging = false;\n  });\n\n  // Create header left section with just the logo\n  const headerLeft = document.createElement('div');\n  headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  `;\n\n  // Add Mem0 logo\n  const logoImg = document.createElement('img');\n  logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n    margin-right: 10px;\n  `;\n\n  // Add \"OpenMemory\" title\n  const title = document.createElement('div');\n  title.textContent = 'OpenMemory';\n  title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: white;\n  `;\n\n  // Create header right section\n  const headerRight = document.createElement('div');\n  headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n  `;\n\n  // Create Add to Prompt button with arrow\n  const addToPromptBtn = document.createElement('button');\n  addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color: white;\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: black;\n  `;\n  addToPromptBtn.textContent = 'Add to Prompt';\n\n  // Add arrow icon to button\n  const arrowIcon = document.createElement('span');\n  arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  addToPromptBtn.appendChild(arrowIcon);\n\n  // Create settings button\n  const settingsBtn = document.createElement('button');\n  settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n  settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n  // Add click event to open app.mem0.ai in a new tab\n  settingsBtn.addEventListener('click', () => {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n      memoryModalShown = false;\n      currentModalOverlay = null;\n    }\n\n    chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n  });\n\n  // Add hover effect for the settings button\n  settingsBtn.addEventListener('mouseenter', () => {\n    settingsBtn.style.opacity = '1';\n  });\n  settingsBtn.addEventListener('mouseleave', () => {\n    settingsBtn.style.opacity = '0.6';\n  });\n\n  // Content section\n  const contentSection = document.createElement('div');\n  const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n  contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n  // Create memories counter\n  const memoriesCounter = document.createElement('div');\n  memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n  // Update counter text based on loading state and number of memories\n  if (isLoading) {\n    memoriesCounter.textContent = `Loading Relevant Memories...`;\n  } else if (memoryItems.length === 0) {\n    memoriesCounter.textContent = `No Relevant Memories`;\n  } else {\n    memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n  }\n\n  // Calculate max height for memories content based on modal height\n  const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n  // Create memories content container with adjusted height\n  const memoriesContent = document.createElement('div');\n  memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: none;\n    -ms-overflow-style: none;\n  `;\n  memoriesContent.style.cssText += '::-webkit-scrollbar { display: none; }';\n\n  // Track currently expanded memory\n  let currentlyExpandedMemory: HTMLElement | null = null;\n\n  // Function to create skeleton loading items\n  function createSkeletonItems() {\n    memoriesContent.innerHTML = '';\n\n    for (let i = 0; i < memoriesPerPage; i++) {\n      const skeletonItem = document.createElement('div');\n      skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #27272A;\n        border-radius: 8px;\n        height: 72px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n      const skeletonText = document.createElement('div');\n      skeletonText.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n      const skeletonText2 = document.createElement('div');\n      skeletonText2.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n      const skeletonActions = document.createElement('div');\n      skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n      const skeletonButton1 = document.createElement('div');\n      skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      const skeletonButton2 = document.createElement('div');\n      skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      skeletonActions.appendChild(skeletonButton1);\n      skeletonActions.appendChild(skeletonButton2);\n\n      const textContainer = document.createElement('div');\n      textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      textContainer.appendChild(skeletonText);\n      textContainer.appendChild(skeletonText2);\n\n      skeletonItem.appendChild(textContainer);\n      skeletonItem.appendChild(skeletonActions);\n      memoriesContent.appendChild(skeletonItem);\n    }\n\n    // Add keyframe animation to document if not exists\n    if (!document.getElementById('skeleton-animation')) {\n      const style = document.createElement('style');\n      style.id = 'skeleton-animation';\n      style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n      document.head.appendChild(style);\n    }\n  }\n\n  // Function to expand memory\n  function expandMemory(\n    memoryContainer: HTMLDivElement,\n    memoryText: HTMLDivElement,\n    contentWrapper: HTMLDivElement,\n    removeButton: HTMLButtonElement,\n    isExpanded: { value: boolean }\n  ) {\n    if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n      currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n    }\n\n    isExpanded.value = true;\n    memoryText.style.webkitLineClamp = 'unset';\n    memoryText.style.height = 'auto';\n    contentWrapper.style.overflowY = 'auto';\n    contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n    contentWrapper.style.scrollbarWidth = 'none';\n    contentWrapper.style.msOverflowStyle = 'none';\n    contentWrapper.style.cssText += '::-webkit-scrollbar { display: none; }';\n    memoryContainer.style.backgroundColor = '#1C1C1E';\n    memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n    memoryContainer.style.overflow = 'hidden';\n    removeButton.style.display = 'flex';\n    currentlyExpandedMemory = memoryContainer;\n\n    // Scroll to make expanded memory visible if needed\n    memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n  }\n\n  // Function to collapse memory\n  function collapseMemory(\n    memoryContainer: HTMLDivElement,\n    memoryText: HTMLDivElement,\n    contentWrapper: HTMLDivElement,\n    removeButton: HTMLButtonElement,\n    isExpanded: { value: boolean }\n  ) {\n    isExpanded.value = false;\n    memoryText.style.webkitLineClamp = '2';\n    memoryText.style.height = '42px';\n    contentWrapper.style.overflowY = 'visible';\n    memoryContainer.style.backgroundColor = '#27272A';\n    memoryContainer.style.maxHeight = '72px';\n    memoryContainer.style.overflow = 'hidden';\n    removeButton.style.display = 'none';\n    currentlyExpandedMemory = null;\n  }\n\n  // Function to show memories with adjusted count based on modal position\n  function showMemories() {\n    memoriesContent.innerHTML = '';\n\n    if (isLoading) {\n      createSkeletonItems();\n      return;\n    }\n\n    if (memoryItems.length === 0) {\n      showEmptyState();\n      updateNavigationState(0, 0);\n      return;\n    }\n\n    // Reset Add to Prompt button state\n    if (addToPromptBtn) {\n      addToPromptBtn.disabled = false;\n      addToPromptBtn.style.opacity = '1';\n      addToPromptBtn.style.cursor = 'pointer';\n    }\n\n    // Use the dynamically set memoriesPerPage value\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n\n    // Calculate total pages and current page\n    const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    // Update navigation buttons state\n    updateNavigationState(currentPage, totalPages);\n\n    for (let i = 0; i < memoriesToShow; i++) {\n      const memoryIndex = currentMemoryIndex + i;\n      if (memoryIndex >= memoryItems.length) {\n        break;\n      } // Stop if we've reached the end\n\n      const memory = memoryItems[memoryIndex];\n      if (!memory) {\n        continue;\n      }\n\n      // Skip memories that have been added already\n      if (allMemoriesById.has(String(memory.id))) {\n        continue;\n      }\n\n      // Ensure memory has an ID\n      if (!memory.id) {\n        memory.id = `memory-${Date.now()}-${memoryIndex}`;\n      }\n\n      const memoryContainer = document.createElement('div');\n      memoryContainer.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px; \n        background-color: #27272A;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s ease;\n        min-height: 72px; \n        max-height: 72px; \n        overflow: hidden;\n        flex-shrink: 0;\n      `;\n\n      const memoryText = document.createElement('div');\n      memoryText.style.cssText = `\n        font-size: 14px;\n        line-height: 1.5;\n        color: #D4D4D8;\n        flex-grow: 1;\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        transition: all 0.2s ease;\n        height: 42px; /* Height for 2 lines of text */\n      `;\n      memoryText.textContent = memory.text || '';\n\n      const actionsContainer = document.createElement('div');\n      actionsContainer.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n        flex-shrink: 0;\n      `;\n\n      // Add button\n      const addButton = document.createElement('button');\n      addButton.style.cssText = `\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 100%;\n        transition: all 0.2s ease;\n      `;\n\n      addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      </svg>`;\n\n      // Add click handler for add button\n      addButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        sendExtensionEvent('memory_injection', {\n          provider: 'claude',\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          browser: getBrowser(),\n          injected_all: false,\n          memory_id: memory.id,\n        });\n\n        // Mark this memory as added\n        allMemoriesById.add(String(memory.id));\n\n        // Add this memory to existing ones instead of replacing\n        allMemories.push(String(memory.text || ''));\n\n        // Update the input with all memories\n        updateInputWithMemories();\n\n        // Remove this memory from the list\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n          // If we're on a page that's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n\n        // Don't close the modal, allow adding more memories\n      });\n\n      // Menu button\n      const menuButton = document.createElement('button');\n      menuButton.style.cssText = `\n        background: none;\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        color: #A1A1AA;\n      `;\n      menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n        <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n      </svg>`;\n\n      // Track expanded state using object to maintain reference\n      const isExpanded = { value: false };\n\n      // Create remove button (hidden by default)\n      const removeButton = document.createElement('button');\n      removeButton.style.cssText = `\n        display: none;\n        align-items: center;\n        gap: 6px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 8px;\n        padding: 2px 4px;\n        border: none;\n        cursor: pointer;\n        font-size: 13px;\n        margin-top: 12px;\n        width: fit-content;\n      `;\n      removeButton.innerHTML = `\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>\n        Remove\n      `;\n\n      // Create content wrapper for text and remove button\n      const contentWrapper = document.createElement('div');\n      contentWrapper.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      contentWrapper.appendChild(memoryText);\n      contentWrapper.appendChild(removeButton);\n\n      memoryContainer.addEventListener('collapse', () => {\n        collapseMemory(memoryContainer, memoryText, contentWrapper, removeButton, isExpanded);\n      });\n\n      menuButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        if (isExpanded.value) {\n          collapseMemory(memoryContainer, memoryText, contentWrapper, removeButton, isExpanded);\n        } else {\n          expandMemory(memoryContainer, memoryText, contentWrapper, removeButton, isExpanded);\n        }\n      });\n\n      // Add click handler for remove button\n      removeButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        // Remove from memoryItems\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n\n          // If we're on the last page and it's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      actionsContainer.appendChild(addButton);\n      actionsContainer.appendChild(menuButton);\n\n      memoryContainer.appendChild(contentWrapper);\n      memoryContainer.appendChild(actionsContainer);\n      memoriesContent.appendChild(memoryContainer);\n\n      // Add hover effect\n      memoryContainer.addEventListener('mouseenter', () => {\n        memoryContainer.style.backgroundColor = isExpanded.value ? '#18181B' : '#323232';\n      });\n      memoryContainer.addEventListener('mouseleave', () => {\n        memoryContainer.style.backgroundColor = isExpanded.value ? '#1C1C1E' : '#27272A';\n      });\n    }\n\n    // If after filtering for already added memories, there are no items to show,\n    // check if we need to go to previous page\n    if (memoriesContent.children.length === 0 && memoryItems.length > 0) {\n      if (currentMemoryIndex > 0) {\n        currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n        showMemories();\n      } else {\n        updateNavigationState(0, 0);\n        showEmptyState();\n      }\n    }\n  }\n\n  // Function to show empty state\n  function showEmptyState() {\n    memoriesContent.innerHTML = '';\n    memoriesCounter.textContent = 'No Relevant Memories';\n\n    const emptyContainer = document.createElement('div');\n    emptyContainer.style.cssText = `\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 32px 16px;\n      text-align: center;\n      flex: 1;\n      min-height: 200px;\n    `;\n\n    const emptyIcon = document.createElement('div');\n    emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#71717A\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>`;\n    emptyIcon.style.marginBottom = '16px';\n\n    const emptyText = document.createElement('div');\n    emptyText.textContent = 'No relevant memories found';\n    emptyText.style.cssText = `\n      color: #71717A;\n      font-size: 14px;\n      font-weight: 500;\n    `;\n\n    emptyContainer.appendChild(emptyIcon);\n    emptyContainer.appendChild(emptyText);\n    memoriesContent.appendChild(emptyContainer);\n\n    // Disable the Add to Prompt button when there are no memories\n    if (addToPromptBtn) {\n      addToPromptBtn.disabled = true;\n      addToPromptBtn.style.opacity = '0.5';\n      addToPromptBtn.style.cursor = 'not-allowed';\n    }\n  }\n\n  // Update navigation button states\n  function updateNavigationState(currentPage: number, totalPages: number): void {\n    if (memoryItems.length === 0 || totalPages === 0) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n      return;\n    }\n\n    if (currentPage <= 1) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n    } else {\n      prevButton.disabled = false;\n      prevButton.style.opacity = '1';\n      prevButton.style.cursor = 'pointer';\n    }\n\n    if (currentPage >= totalPages) {\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n    } else {\n      nextButton.disabled = false;\n      nextButton.style.opacity = '1';\n      nextButton.style.cursor = 'pointer';\n    }\n  }\n\n  // Navigation section at bottom\n  const navigationSection = document.createElement('div');\n  navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n  // Navigation buttons\n  const prevButton = document.createElement('button');\n  prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  prevButton.style.cssText = `\n    background: #27272A;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  const nextButton = document.createElement('button');\n  nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  nextButton.style.cssText = prevButton.style.cssText;\n\n  // Add navigation button handlers\n  prevButton.addEventListener('click', () => {\n    if (currentMemoryIndex >= memoriesPerPage) {\n      currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n      showMemories();\n    }\n  });\n\n  nextButton.addEventListener('click', () => {\n    if (currentMemoryIndex + memoriesPerPage < memoryItems.length) {\n      currentMemoryIndex = currentMemoryIndex + memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  // Add hover effects\n  [prevButton, nextButton].forEach(button => {\n    button.addEventListener('mouseenter', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#323232';\n      }\n    });\n    button.addEventListener('mouseleave', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#27272A';\n      }\n    });\n  });\n\n  // Assemble modal\n  headerLeft.appendChild(logoImg);\n  headerLeft.appendChild(title);\n  headerRight.appendChild(addToPromptBtn);\n  headerRight.appendChild(settingsBtn);\n\n  modalHeader.appendChild(headerLeft);\n  modalHeader.appendChild(headerRight);\n\n  // Drag behavior removed in favor of utility-based placement\n\n  contentSection.appendChild(memoriesCounter);\n  contentSection.appendChild(memoriesContent);\n\n  navigationSection.appendChild(prevButton);\n  navigationSection.appendChild(nextButton);\n\n  modalContainer.appendChild(modalHeader);\n  modalContainer.appendChild(contentSection);\n  modalContainer.appendChild(navigationSection);\n\n  // When using floating placement, modalContainer is already in the DOM.\n  // Always append overlay last so outside-click works and layering is correct.\n  document.body.appendChild(modalOverlay);\n\n  // Show initial memories or loading state\n  if (isLoading) {\n    createSkeletonItems();\n  } else if (memoryItems.length === 0) {\n    showEmptyState();\n  } else {\n    showMemories();\n  }\n\n  // Function to close the modal\n  function closeModal(): void {\n    console.log('🚪 Closing modal');\n    try {\n      if (typeof unplace === 'function') {\n        unplace();\n      }\n    } catch {\n      // Ignore error\n    }\n    try {\n      if (typeof currentModalUnplace === 'function') {\n        currentModalUnplace();\n      }\n    } catch {\n      // Ignore error\n    }\n\n    if (currentModalContainer && currentModalContainer.isConnected) {\n      try {\n        currentModalContainer.remove();\n      } catch {\n        // Ignore error\n      }\n    }\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      // Clean up drag event listeners\n\n      document.body.removeChild(currentModalOverlay);\n    }\n    if (modalOverlay && document.body.contains(modalOverlay)) {\n      document.body.removeChild(modalOverlay);\n    }\n    currentModalOverlay = null;\n    currentModalContainer = null;\n    currentModalUnplace = null;\n    memoryModalShown = false;\n    isProcessingMem0 = false;\n  }\n\n  // Update Add to Prompt button click handler\n  addToPromptBtn.addEventListener('click', () => {\n    // Only add memories that are not already added\n    const newMemories = memoryItems\n      .filter(memory => !allMemoriesById.has(String(memory.id)) && !memory.removed)\n      .map(memory => {\n        allMemoriesById.add(String(memory.id));\n        return String(memory.text || '');\n      });\n\n    sendExtensionEvent('memory_injection', {\n      provider: 'claude',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n      injected_all: true,\n      memory_count: newMemories.length,\n    });\n\n    // Add new memories to allMemories (don't replace existing ones)\n    if (newMemories.length > 0) {\n      // Add new memories to the existing array\n      allMemories = [...allMemories, ...newMemories];\n\n      // Update the input with all memories\n      updateInputWithMemories();\n    }\n\n    // Close the modal\n    closeModal();\n\n    // Remove all added memories from the memoryItems list\n    for (let i = memoryItems.length - 1; i >= 0; i--) {\n      if (allMemoriesById.has(String(memoryItems[i]?.id))) {\n        memoryItems.splice(i, 1);\n      }\n    }\n  });\n}\n\n// Shared function to update the input field with all collected memories\nfunction updateInputWithMemories() {\n  // Find the input element (prioritizing the ProseMirror div with contenteditable=\"true\")\n  let inputElement = document.querySelector('div[contenteditable=\"true\"].ProseMirror');\n\n  // If ProseMirror not found, try other input elements\n  if (!inputElement) {\n    inputElement =\n      document.querySelector('div[contenteditable=\"true\"]') ||\n      document.querySelector('textarea') ||\n      document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n      document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n  }\n\n  if (inputElement && allMemories.length > 0) {\n    // Define the header text\n    const headerText = OPENMEMORY_PROMPTS.memory_header_text;\n\n    // Check if ProseMirror editor\n    if (inputElement.classList.contains('ProseMirror')) {\n      // First check if the header already exists\n      const headerExists = Array.from(inputElement.querySelectorAll('p strong')).some(el =>\n        (el.textContent || '').includes('Here is some of my memories')\n      );\n\n      if (headerExists) {\n        // Get all existing memory paragraphs\n        const paragraphs = Array.from(inputElement.querySelectorAll('p')) as HTMLElement[];\n        let headerIndex = -1;\n        const existingMemories = [];\n\n        // Find the index of the header paragraph\n        for (let i = 0; i < paragraphs.length; i++) {\n          const strongEl = paragraphs[i]?.querySelector('strong');\n          if (strongEl && (strongEl.textContent || '').includes('Here is some of my memories')) {\n            headerIndex = i;\n            break;\n          }\n        }\n\n        // Collect all existing memories after the header\n        if (headerIndex >= 0) {\n          for (let i = headerIndex + 1; i < paragraphs.length; i++) {\n            const para = paragraphs[i];\n            if (!para) {\n              continue;\n            }\n            const text = (para.textContent || '').trim();\n            if (text.startsWith('-')) {\n              existingMemories.push(text.substring(1).trim());\n            }\n          }\n\n          // Keep everything up to and including the header paragraph\n          const newHTML = Array.from(paragraphs)\n            .slice(0, headerIndex + 1)\n            .map(p => p.outerHTML)\n            .join('');\n\n          // Combine existing and new memories, avoiding duplicates\n          const combinedMemories = [...existingMemories];\n\n          // Add new memories if they don't already exist\n          allMemories.forEach(mem => {\n            if (!combinedMemories.includes(mem)) {\n              combinedMemories.push(mem);\n            }\n          });\n\n          // Add the memories after the header\n          const memoriesHTML = combinedMemories.map(mem => `<p>- ${mem}</p>`).join('');\n\n          // Set the new HTML content\n          inputElement.innerHTML = newHTML + memoriesHTML;\n        }\n      } else {\n        // Header doesn't exist, get the content without any existing memory wrappers\n        const baseContent = getContentWithoutMemories(undefined);\n\n        // Create the memory section\n        let memoriesContent = `<p><strong>${headerText}</strong></p>`;\n\n        // Add all memories to the content with proper paragraph tags\n        memoriesContent += allMemories.map(mem => `<p>- ${mem}</p>`).join('');\n\n        // If empty, replace the entire content\n        if (\n          !baseContent ||\n          baseContent.trim() === '' ||\n          (inputElement.querySelectorAll('p').length === 1 &&\n            inputElement.querySelector('p.is-empty') !== null)\n        ) {\n          inputElement.innerHTML = memoriesContent;\n        } else {\n          // Otherwise append after a line break\n          inputElement.innerHTML = `${baseContent}<p><br></p>${memoriesContent}`;\n        }\n      }\n\n      // Dispatch proper events for ProseMirror\n      const inputEvent = new InputEvent('input', {\n        bubbles: true,\n        cancelable: true,\n        inputType: 'insertText',\n      });\n      inputElement.dispatchEvent(inputEvent);\n\n      // Also dispatch a change event\n      const changeEvent = new Event('change', { bubbles: true });\n      inputElement.dispatchEvent(changeEvent);\n    } else if (inputElement.tagName.toLowerCase() === 'div') {\n      // For normal contenteditable divs\n      // Check if the header already exists\n      if (inputElement.innerHTML.includes(headerText)) {\n        // Find the header position and extract existing memories\n        const htmlParts = inputElement.innerHTML.split(headerText);\n        if (htmlParts.length > 1) {\n          const beforeHeader = htmlParts[0];\n          const afterHeader = htmlParts[1];\n\n          // Extract existing memories from the content after the header\n          const tempDiv = document.createElement('div');\n          tempDiv.innerHTML = afterHeader || '';\n          const existingMemories: string[] = [];\n\n          // Find all paragraphs that start with a dash\n          Array.from(tempDiv.querySelectorAll('p')).forEach(p => {\n            const text = (p.textContent || '').trim();\n            if (text.startsWith('-')) {\n              existingMemories.push(text.substring(1).trim());\n            }\n          });\n\n          // Combine existing and new memories, avoiding duplicates\n          const combinedMemories = [...existingMemories];\n\n          // Add new memories if they don't already exist\n          allMemories.forEach(mem => {\n            if (!combinedMemories.includes(mem)) {\n              combinedMemories.push(mem);\n            }\n          });\n\n          // Create HTML with header and all memories\n          let newHTML = beforeHeader + `<p><strong>${headerText}</strong></p>`;\n          combinedMemories.forEach(mem => {\n            newHTML += `<p>- ${mem}</p>`;\n          });\n\n          inputElement.innerHTML = newHTML;\n        }\n      } else {\n        // Header doesn't exist\n        const baseContent = getContentWithoutMemories(undefined);\n        let memoriesContent = `<p><strong>${headerText}</strong></p>`;\n\n        allMemories.forEach(mem => {\n          memoriesContent += `<p>- ${mem}</p>`;\n        });\n\n        inputElement.innerHTML = `${baseContent}${baseContent ? '<p><br></p>' : ''}${memoriesContent}`;\n      }\n\n      // Dispatch input event\n      inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n    } else if (\n      inputElement.tagName.toLowerCase() === 'p' &&\n      (inputElement.getAttribute('data-placeholder') === 'How can I help you today?' ||\n        inputElement.getAttribute('data-placeholder') === 'Reply to Claude...')\n    ) {\n      // For p element placeholders\n      // Check if the header already exists\n      if ((inputElement.textContent || '').includes(headerText)) {\n        // Find the header position and extract existing memories\n        const textParts = (inputElement.textContent || '').split(headerText);\n        if (textParts.length > 1) {\n          const beforeHeader = textParts[0];\n          const afterHeader = textParts[1];\n\n          // Extract existing memories\n          const existingMemories: string[] = [];\n          const memoryLines = (afterHeader || '').split('\\n');\n\n          memoryLines.forEach(line => {\n            const trimmed = line.trim();\n            if (trimmed.startsWith('-')) {\n              existingMemories.push(trimmed.substring(1).trim());\n            }\n          });\n\n          // Combine existing and new memories, avoiding duplicates\n          const combinedMemories = [...existingMemories];\n\n          // Add new memories if they don't already exist\n          allMemories.forEach(mem => {\n            if (!combinedMemories.includes(mem)) {\n              combinedMemories.push(mem);\n            }\n          });\n\n          // Create text with header and all memories\n          const newText =\n            beforeHeader + headerText + '\\n\\n' + combinedMemories.map(mem => `- ${mem}`).join('\\n');\n\n          inputElement.textContent = newText;\n        }\n      } else {\n        // Header doesn't exist\n        const baseContent = getContentWithoutMemories(undefined);\n\n        inputElement.textContent = `${baseContent}${baseContent ? '\\n\\n' : ''}${headerText}\\n\\n${allMemories.map(mem => `- ${mem}`).join('\\n')}`;\n      }\n\n      // Dispatch various events\n      inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n      inputElement.dispatchEvent(new Event('focus', { bubbles: true }));\n      inputElement.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true }));\n      inputElement.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));\n      inputElement.dispatchEvent(new Event('change', { bubbles: true }));\n    } else {\n      // For textarea\n      // Check if the header already exists\n      if (((inputElement as HTMLTextAreaElement).value || '').includes(headerText)) {\n        // Find the header position and extract existing memories\n        const valueParts = ((inputElement as HTMLTextAreaElement).value || '').split(headerText);\n        if (valueParts.length > 1) {\n          const beforeHeader = valueParts[0];\n          const afterHeader = valueParts[1];\n\n          // Extract existing memories\n          const existingMemories: string[] = [];\n          const memoryLines = (afterHeader || '').split('\\n');\n\n          memoryLines.forEach(line => {\n            const trimmed = line.trim();\n            if (trimmed.startsWith('-')) {\n              existingMemories.push(trimmed.substring(1).trim());\n            }\n          });\n\n          // Combine existing and new memories, avoiding duplicates\n          const combinedMemories = [...existingMemories];\n\n          // Add new memories if they don't already exist\n          allMemories.forEach(mem => {\n            if (!combinedMemories.includes(mem)) {\n              combinedMemories.push(mem);\n            }\n          });\n\n          // Create text with header and all memories\n          const newValue =\n            beforeHeader + headerText + '\\n\\n' + combinedMemories.map(mem => `- ${mem}`).join('\\n');\n\n          inputElement.value = newValue;\n        }\n      } else {\n        // Header doesn't exist\n        const baseContent = getContentWithoutMemories(undefined);\n\n        (inputElement as HTMLTextAreaElement).value =\n          `${baseContent}${baseContent ? '\\n\\n' : ''}${headerText}\\n\\n${allMemories.map(mem => `- ${mem}`).join('\\n')}`;\n      }\n\n      // Dispatch input event\n      inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n    }\n\n    // Focus the input element to ensure the user can continue typing\n    (inputElement as HTMLElement).focus();\n  }\n}\n\n// Function to get the content without any memory wrappers\nfunction getContentWithoutMemories(providedMessage: string | undefined) {\n  // Find the input element (prioritizing the ProseMirror div with contenteditable=\"true\")\n  let inputElement = document.querySelector('div[contenteditable=\"true\"].ProseMirror');\n\n  // If ProseMirror not found, try other input elements\n  if (!inputElement) {\n    inputElement =\n      document.querySelector('div[contenteditable=\"true\"]') ||\n      document.querySelector('textarea') ||\n      document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n      document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n  }\n\n  // If a message is provided, operate on it; otherwise read from DOM\n  let content = '';\n\n  if (typeof providedMessage === 'string') {\n    content = providedMessage;\n  } else {\n    if (!inputElement) {\n      return '';\n    }\n    if (inputElement.classList.contains('ProseMirror')) {\n      // For ProseMirror, get the innerHTML for proper structure handling\n      content = inputElement.innerHTML;\n    } else if (inputElement.tagName.toLowerCase() === 'div') {\n      // For normal contenteditable divs\n      content = inputElement.innerHTML;\n    } else if (\n      inputElement.tagName.toLowerCase() === 'p' &&\n      (inputElement.getAttribute('data-placeholder') === 'How can I help you today?' ||\n        inputElement.getAttribute('data-placeholder') === 'Reply to Claude...')\n    ) {\n      // For p element placeholders\n      content = inputElement.innerHTML || inputElement.textContent || '';\n    } else {\n      // For textarea\n      content = (inputElement as HTMLTextAreaElement).value || '';\n    }\n  }\n\n  // Remove any memory headers and content\n  // Match both HTML and plain text variants\n\n  // HTML variant\n  try {\n    const MEM0_HTML = OPENMEMORY_PROMPTS.memory_header_html_regex;\n    const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n    content = content.replace(MEM0_HTML, '');\n    content = content.replace(MEM0_PLAIN, '');\n  } catch {\n    // Ignore errors when processing memory content\n  }\n\n  // Also clean up any empty paragraphs at the end\n  content = content.replace(/<p><br><\\/p>$/g, '');\n  content = content.replace(\n    /<p class=\"is-empty\"><br class=\"ProseMirror-trailingBreak\"><\\/p>$/g,\n    ''\n  );\n\n  return content.trim();\n}\n\n// New function to handle the memory modal\nasync function handleMem0Modal(\n  popup: HTMLElement | null,\n  clickSendButton: boolean = false,\n  sourceButtonId: string | null = null\n): Promise<void> {\n  console.log('🚀 handleMem0Modal called', {\n    popup,\n    clickSendButton,\n    sourceButtonId,\n    isProcessingMem0,\n    memoryModalShown,\n  });\n  if (isProcessingMem0) {\n    console.log('⏸️ Already processing, returning early');\n    return;\n  }\n\n  if (memoryModalShown) {\n    console.log('📱 Modal already shown, returning early');\n    return;\n  }\n  // First check if memory is enabled\n  const enabled = await getMemoryEnabledState();\n  console.log('🧠 Memory enabled state:', enabled);\n  if (enabled === false) {\n    console.log('❌ Memory disabled, not showing modal');\n    return; // Don't show modal or login popup if memory is disabled\n  }\n\n  isProcessingMem0 = true;\n\n  // Set loading state for button\n  setButtonLoadingState();\n\n  // Hide any tooltip that might be showing\n  const tooltip = document.querySelector('#mem0-tooltip');\n  if (tooltip) {\n    tooltip.style.display = 'none';\n  }\n\n  try {\n    const data = await new Promise<StorageData>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items);\n        }\n      );\n    });\n\n    const apiKey = data.apiKey;\n    const userId = data.userId || data.user_id || 'chrome-extension-user';\n    const accessToken = data.access_token;\n\n    if (!apiKey && !accessToken) {\n      // Show login popup instead of error message\n      isProcessingMem0 = false;\n      setButtonLoadingState();\n\n      showLoginPopup();\n      return;\n    }\n\n    let message = getInputValue();\n    console.log('📝 Input message:', message);\n\n    if (!message || message.trim() === '') {\n      console.log('❌ No input message, showing popup');\n      if (popup) {\n        // Hide any existing tooltip first\n        const tooltip = document.querySelector('#mem0-tooltip');\n        if (tooltip) {\n          tooltip.style.display = 'none';\n        }\n\n        showPopup(popup, 'Please enter some text first');\n      }\n\n      isProcessingMem0 = false;\n      setButtonLoadingState();\n      return;\n    }\n\n    console.log('✅ All checks passed, creating memory modal');\n    // Now we can show the loading modal since we have text input\n    createMemoryModal([], true, sourceButtonId);\n\n    // Clean the message by removing any existing memory wrappers\n    message = getContentWithoutMemories(undefined);\n    // Strip HTML tags to ensure clean text for search (fix for <p> tag issue)\n    const tempDiv = document.createElement('div');\n    tempDiv.innerHTML = message;\n    message = tempDiv.textContent || tempDiv.innerText || message;\n    message = message.trim();\n\n    sendExtensionEvent('modal_clicked', {\n      provider: 'claude',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n    });\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n\n    const messages = getConversationContext(false); // Use sliding window context\n    messages.push({ role: MessageRole.User, content: message });\n\n    // If clickSendButton is true, click the send button\n    if (clickSendButton) {\n      const sendButton =\n        (document.querySelector('button[aria-label=\"Send Message\"]') as HTMLElement) ||\n        (document.querySelector('button[aria-label=\"Send message\"]') as HTMLElement);\n\n      if (sendButton) {\n        setTimeout(() => {\n          (sendButton as HTMLElement).click();\n        }, 100);\n      }\n    }\n\n    const optionalParams: OptionalApiParams = {};\n    if (data.selected_org) {\n      optionalParams.org_id = data.selected_org;\n    }\n    if (data.selected_project) {\n      optionalParams.project_id = data.selected_project;\n    }\n\n    // Use raw input for search key to match typing cache; cleaning happens in fetch\n    currentModalSourceButtonId = sourceButtonId;\n    try {\n      const rawInput = (function () {\n        const el =\n          document.querySelector('div[contenteditable=\"true\"]') ||\n          document.querySelector('textarea') ||\n          document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n          document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n        if (!el) {\n          return message;\n        }\n        const val = (el.textContent || el.value || '').trim();\n        return val || message;\n      })();\n      console.log(\"Claude modal search for:\", rawInput);\n      console.log(\"Cache state:\", claudeSearch.getState()); \n      claudeSearch.runImmediate(rawInput);\n    } catch (_) {\n      claudeSearch.runImmediate(message);\n    }\n\n    // New add memory API call (non-blocking)\n    fetch('https://api.mem0.ai/v1/memories/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify({\n        messages: messages,\n        user_id: userId,\n        infer: true,\n        metadata: {\n          provider: 'Claude',\n        },\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        ...optionalParams,\n      }),\n    })\n      .then(response => {\n        if (!response.ok) {\n          // Silent failure for background memory addition\n        }\n      })\n      .catch(() => {\n        // Silent failure for background memory addition\n      });\n  } catch {\n    if (popup) {\n      showPopup(popup, 'Failed to send message to Mem0');\n    }\n  } finally {\n    isProcessingMem0 = false;\n    setButtonLoadingState();\n  }\n}\n\nfunction setButtonLoadingState(): void {\n  // Legacy mem0-button state removed; no-op\n}\n\nfunction showPopup(popup: HTMLElement, message: string): void {\n  // Legacy tooltip suppressed; keep popup container-only usage\n  if (!popup) {\n    return;\n  }\n  Array.from(document.querySelectorAll('#mem0-tooltip')).forEach(p => {\n    try {\n      p.remove();\n    } catch {\n      // Ignore error\n    }\n  });\n  popup.textContent = message;\n\n  popup.style.display = 'block';\n  setTimeout(() => {\n    popup.style.display = 'none';\n  }, 2000);\n}\n\nfunction getInputValue(): string | null {\n  const inputElement =\n    document.querySelector('div[contenteditable=\"true\"]') ||\n    document.querySelector('textarea') ||\n    document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n    document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n\n  if (!inputElement) {\n    return null;\n  }\n\n  // For the p element placeholders specifically\n  if (\n    inputElement.tagName.toLowerCase() === 'p' &&\n    (inputElement.getAttribute('data-placeholder') === 'How can I help you today?' ||\n      inputElement.getAttribute('data-placeholder') === 'Reply to Claude...')\n  ) {\n    return inputElement.textContent || '';\n  }\n\n  return inputElement.textContent || (inputElement as HTMLTextAreaElement)?.value || null;\n}\n\nlet claudeBackgroundSearchHandler: (() => void) | null = null;\n\nfunction hookClaudeBackgroundSearchTyping() {\n  \n  const inputElement =\n    document.querySelector('div[contenteditable=\"true\"]') ||\n    document.querySelector('textarea') ||\n    document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n    document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n  \n  if (!inputElement) {\n    return;\n  }\n  \n  if (inputElement.dataset.claudeBackgroundHooked) {\n    return; \n  }\n\n  inputElement.dataset.claudeBackgroundHooked = 'true'; \n\n  if (!claudeBackgroundSearchHandler) {\n    claudeBackgroundSearchHandler = function () {\n      let text = getInputValue() || '';\n      try {\n        const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n        text = text.replace(MEM0_PLAIN, '').trim();\n      } catch {}\n      console.log(\"Claude background search triggered:\", text); \n      claudeSearch.setText(text);\n    };\n  }\n  inputElement.addEventListener('input', claudeBackgroundSearchHandler);\n  inputElement.addEventListener('keyup', claudeBackgroundSearchHandler);\n}\n\n// Auto-inject support: simple debounce and config\nasync function updateMemoryEnabled() {\n  memoryEnabled = Boolean(await getMemoryEnabledState());\n\n  // If memory is disabled, remove the button completely\n  if (memoryEnabled === false) {\n    removeMemButton();\n  } else {\n    // If memory is enabled, ensure the button is added\n    // addMem0Button(); // Removed: OPENMEMORY_UI handles icon mounting\n  }\n}\n\nfunction initializeMem0Integration(): void {\n  console.log('🚀 initializeMem0Integration started');\n  updateMemoryEnabled();\n  hookClaudeBackgroundSearchTyping(); \n  if (!(OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus)) {\n    // addMem0Button(); // Removed: OPENMEMORY_UI handles icon mounting\n  }\n\n  // Prime the cache so the very first send is captured\n  const _initVal = getInputValue();\n  if (_initVal && _initVal.trim()) {\n    lastTyped = _initVal;\n  }\n\n  // Ensure send button listeners are attached early and repeatedly\n  const ensureSendButtonListeners = () => {\n    const allSendButtons = [\n      document.querySelector('button[aria-label=\"Send Message\"]'),\n      document.querySelector('button[aria-label=\"Send message\"]'),\n    ].filter(Boolean);\n\n    allSendButtons.forEach(sendBtn => {\n      if (sendBtn && !sendBtn.dataset.mem0Listener) {\n        sendBtn.dataset.mem0Listener = 'true';\n\n        // Snapshot current input as early as possible (before Claude clears it)\n        sendBtn.addEventListener(\n          'pointerdown',\n          function () {\n            const current = getInputValue();\n            if (current && current.trim() !== '') {\n              lastTyped = current;\n            }\n            lastSendInitiatedAt = Date.now();\n          },\n          true\n        );\n\n        // Use capture-phase click so we run before Claude's handler\n        sendBtn.addEventListener(\n          'click',\n          function () {\n            // Capture and save memory with snapshot fallback\n            captureAndStoreMemory(lastTyped);\n\n            // Clear all memories after sending\n            setTimeout(() => {\n              allMemories = [];\n              allMemoriesById.clear();\n            }, 100);\n          },\n          true\n        );\n      }\n    });\n  };\n\n  // Attach listeners immediately\n  ensureSendButtonListeners();\n\n  // Also attach them repeatedly during early page load\n  const earlyAttachInterval = setInterval(() => {\n    ensureSendButtonListeners();\n  }, 100);\n\n  // Stop the aggressive checking after page is more stable\n  setTimeout(() => {\n    clearInterval(earlyAttachInterval);\n  }, 5000);\n\n  // Refresh cache whenever the editor gains focus\n  if (!(document as ExtendedDocument).__mem0FocusPrimed) {\n    (document as ExtendedDocument).__mem0FocusPrimed = true;\n    document.addEventListener(\n      'focusin',\n      e => {\n        const target = e.target as Element | null;\n        const el =\n          target &&\n          (target as ExtendedElement).closest &&\n          (target as ExtendedElement).closest(\n            'div[contenteditable=\"true\"], textarea, p[data-placeholder=\"How can I help you today?\"], p[data-placeholder=\"Reply to Claude...\"]'\n          );\n        if (el) {\n          const v = getInputValue();\n          if (v && v.trim()) {\n            lastTyped = v;\n          }\n        }\n      },\n      true\n    );\n  }\n\n  document.addEventListener('keydown', function (event) {\n    if (event.ctrlKey && event.key === 'm') {\n      event.preventDefault();\n      console.log('⌨️ Ctrl+M pressed', { memoryEnabled, memoryModalShown, isProcessingMem0 });\n\n      // If modal is already shown, close it instead of creating duplicate\n      if (memoryModalShown) {\n        console.log('🔄 Modal already shown, closing it');\n        if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n          document.body.removeChild(currentModalOverlay);\n        }\n        memoryModalShown = false;\n        currentModalOverlay = null;\n        return;\n      }\n\n      if (memoryEnabled && !isProcessingMem0) {\n        const popup = document.querySelector('.mem0-popup');\n        if (popup) {\n          (async () => {\n            await handleMem0Modal(popup as HTMLElement, false);\n          })();\n        } else {\n          // If no popup is available, use the mem0-icon-button as source\n          (async () => {\n            await handleMem0Modal(null, false, 'mem0-icon-button');\n          })();\n        }\n      }\n    }\n  });\n\n  // Cache-first mount for Claude (left/bottom placement relative to textarea)\n  console.log('🎯 Starting cache-first mount for Claude');\n\n  function renderMem0Icon(shadow: ShadowRoot, host: HTMLElement) {\n    console.log('🎨 renderMem0Icon called', { shadow, host });\n    let style = document.createElement('style');\n    style.textContent = `\n        :host { position: relative; }\n        .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n        .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n        .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n        :host([data-has-text=\"1\"]) .dot { display:block; }\n      `;\n    let btn = document.createElement('button');\n    btn.className = 'mem0-btn';\n    let img = document.createElement('img');\n    img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n    let dot = document.createElement('div');\n    dot.className = 'dot';\n    btn.appendChild(img);\n    shadow.append(style, btn, dot);\n    btn.addEventListener('click', function () {\n      console.log('🖱️ Mem0 button clicked!');\n      handleMem0Modal(null, false, 'mem0-icon-button');\n    });\n    if (typeof updateNotificationDot === 'function') {\n      setTimeout(updateNotificationDot, 0);\n    }\n  }\n\n  try {\n    console.log('🔍 Checking for existing button and OPENMEMORY_UI', {\n      existingButton: !!document.getElementById('mem0-icon-button'),\n      hasOpenMemoryUI: !!OPENMEMORY_UI,\n    });\n\n    // Only create button if none exists (including shadow DOM buttons)\n    const existingButton =\n      document.getElementById('mem0-icon-button') ||\n      document.querySelector('[id*=\"mem0\"]') ||\n      document.querySelector('.mem0-btn');\n\n    if (!existingButton && OPENMEMORY_UI) {\n      console.log('🚀 Starting OPENMEMORY_UI.resolveCachedAnchor');\n      OPENMEMORY_UI.resolveCachedAnchor(\n        { learnKey: location.host + ':' + location.pathname },\n        null,\n        24 * 60 * 60 * 1000\n      )\n        .then(function (hit: { el: Element; placement: Placement | null } | null) {\n          console.log('🎯 resolveCachedAnchor result:', hit);\n          if (!hit || !hit.el) {\n            console.log('❌ No anchor found, cannot create button');\n            return;\n          }\n          console.log('✅ Anchor found, creating button');\n          let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n          let host = hs.host,\n            shadow = hs.shadow;\n          host.id = 'mem0-icon-button';\n          let cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.claude ? SITE_CONFIG.claude : null;\n          let placement = hit.placement ||\n            (cfg && cfg.placement) || {\n              strategy: 'dock',\n              container: 'form',\n              side: 'bottom',\n              align: 'start',\n              gap: 8,\n            };\n          OPENMEMORY_UI.applyPlacement({ container: host, anchor: hit.el, placement: placement });\n\n          renderMem0Icon(shadow, host);\n        })\n        .catch(function (error: Error) {\n          console.error('❌ Error in resolveCachedAnchor:', error);\n        });\n    } else {\n      console.log('❌ Cannot create button:', {\n        buttonExists: !!document.getElementById('mem0-icon-button'),\n        hasOpenMemoryUI: !!OPENMEMORY_UI,\n      });\n    }\n  } catch (error) {\n    // Ignore errors during re-initialization\n    console.error('❌ Error in cache-first mount:', error);\n  }\n\n  // Focus-driven mount for Claude\n  console.log('🎯 Checking focus-driven mount');\n  if (\n    OPENMEMORY_UI &&\n    OPENMEMORY_UI.mountOnEditorFocus &&\n    !document.querySelector('[id*=\"mem0\"], .mem0-btn')\n  ) {\n    console.log('🚀 Starting focus-driven mount');\n    OPENMEMORY_UI.mountOnEditorFocus({\n      existingHostSelector: '#mem0-icon-button, [id*=\"mem0\"], .mem0-btn',\n      editorSelector:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.claude &&\n        SITE_CONFIG.claude.editorSelector\n          ? SITE_CONFIG.claude.editorSelector\n          : 'div[contenteditable=\"true\"], textarea, p[data-placeholder], [contenteditable=\"true\"]',\n      deriveAnchor:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.claude &&\n        typeof SITE_CONFIG.claude.deriveAnchor === 'function'\n          ? SITE_CONFIG.claude.deriveAnchor\n          : function (editor: Element) {\n              return editor.closest('form') || editor.parentElement;\n            },\n      placement:\n        typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.claude && SITE_CONFIG.claude.placement\n          ? SITE_CONFIG.claude.placement\n          : { strategy: 'dock', container: 'form', side: 'bottom', align: 'start', gap: 8 },\n      render: function (shadow: ShadowRoot, host: HTMLElement) {\n        host.id = 'mem0-icon-button';\n        return renderMem0Icon(shadow, host);\n      },\n      fallback: function () {\n        try {\n          let cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.claude ? SITE_CONFIG.claude : null;\n          if (!cfg || !OPENMEMORY_UI || !OPENMEMORY_UI.mountResilient) {\n            return;\n          }\n          OPENMEMORY_UI.mountResilient({\n            anchors: cfg.fallbackAnchors || [],\n            placement: cfg.placement,\n            enableFloatingFallback: true,\n            render: function (shadow: ShadowRoot, host: HTMLElement) {\n              host.id = 'mem0-icon-button';\n              return renderMem0Icon(shadow, host);\n            },\n          });\n        } catch {\n          // Ignore error\n        }\n      },\n    });\n  }\n\n  // Global early keydown capture for Enter to snapshot the very first send\n  if (!(document as ExtendedDocument).__mem0EnterCapture) {\n    (document as ExtendedDocument).__mem0EnterCapture = true;\n    document.addEventListener(\n      'keydown',\n      e => {\n        if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {\n          const v = getInputValue();\n          if (v && v.trim()) {\n            lastTyped = v;\n            lastSendInitiatedAt = Date.now();\n          }\n        }\n      },\n      true\n    );\n  }\n\n  // Global submit capture to catch earliest send even if buttons/forms change\n  if (!(document as ExtendedDocument).__mem0SubmitCapture) {\n    (document as ExtendedDocument).__mem0SubmitCapture = true;\n    document.addEventListener(\n      'submit',\n      () => {\n        const v = getInputValue();\n        if (v && v.trim()) {\n          lastTyped = v;\n        }\n        lastSendInitiatedAt = Date.now();\n        captureAndStoreMemory(lastTyped);\n      },\n      true\n    );\n  }\n\n  // Find the input element and observe it\n  // function observeInput() {\n  //   const inputElement =\n  //     document.querySelector('div[contenteditable=\"true\"]') ||\n  //     document.querySelector('textarea') ||\n  //     document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n  //     document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n\n  //   if (inputElement) {\n  //     inputObserver = new MutationObserver(() => {\n  //       // Observer callback - can be empty for now\n  //     });\n  //     inputObserver.observe(inputElement, {\n  //       childList: true,\n  //       characterData: true,\n  //       subtree: true,\n  //     });\n  //   } else {\n  //     // If no input element found, try again later\n  //     setTimeout(observeInput, 1000);\n  //   }\n  // }\n\n  chrome.storage.onChanged.addListener((changes, namespace) => {\n    if (namespace === 'sync' && changes.memory_enabled) {\n      updateMemoryEnabled();\n    }\n  });\n\n  // Fallback: observe chat thread for newly added user bubbles and post if we missed send\n  const ensureThreadObserver = () => {\n    const thread = document.querySelector(\n      '.flex-1.flex.flex-col.gap-3.px-4.max-w-3xl.mx-auto.w-full'\n    );\n    if (!thread) {\n      setTimeout(ensureThreadObserver, 1000);\n      return;\n    }\n    if ((thread as ExtendedElement).__mem0Observed) {\n      return;\n    }\n    (thread as ExtendedElement).__mem0Observed = true;\n\n    // Track processed messages to avoid duplicates\n    const processedMessages = new Set();\n\n    const observer = new MutationObserver(mutations => {\n      for (let i = 0; i < mutations.length; i++) {\n        const m = mutations[i];\n        if (!m) {\n          continue;\n        }\n        const added = m.addedNodes;\n        for (let j = 0; j < added.length; j++) {\n          const n = added[j];\n          if (!n) {\n            continue;\n          }\n          const node = (n as ExtendedElement).nodeType === 1 ? (n as Element) : null;\n          if (!node) {\n            continue;\n          }\n          const userEl = (node as ExtendedElement).matches?.('.font-user-message')\n            ? node\n            : (node as ExtendedElement).querySelector?.('.font-user-message');\n          if (userEl) {\n            const text = (userEl.textContent || '').trim();\n            if (text) {\n              // Create a simple hash of the message to avoid duplicates\n              const messageHash = text.length + '_' + text.substring(0, 50);\n\n              // Skip if we've already processed this exact message recently\n              if (processedMessages.has(messageHash)) {\n                return;\n              }\n\n              // Add to processed set and clean up old entries periodically\n              processedMessages.add(messageHash);\n              if (processedMessages.size > 10) {\n                const entries = Array.from(processedMessages);\n                processedMessages.clear();\n                // Keep the last 5 entries\n                entries.slice(-5).forEach(entry => processedMessages.add(entry));\n              }\n\n              // If we just initiated a send, give primary handlers a brief head start\n              const justInitiated = Date.now() - lastSendInitiatedAt < 500; // Reduced from 1200ms to 500ms\n\n              if (justInitiated) {\n                // For very recent sends, delay slightly to let primary handlers run first\n                setTimeout(() => {\n                  // Double-check if we still need to process this message\n                  if (!processedMessages.has(messageHash + '_processed')) {\n                    processedMessages.add(messageHash + '_processed');\n                    captureAndStoreMemory(text);\n                  }\n                }, 200);\n              } else {\n                // For older messages or when no recent send detected, process immediately\n                processedMessages.add(messageHash + '_processed');\n                captureAndStoreMemory(text);\n              }\n\n              // Update lastSendInitiatedAt to help coordinate with other handlers\n              lastSendInitiatedAt = Date.now();\n              return;\n            }\n          }\n        }\n      }\n    });\n    observer.observe(thread, { childList: true, subtree: true });\n  };\n  ensureThreadObserver();\n}\n\n// Function to show login popup\nfunction showLoginPopup() {\n  // First remove any existing popups\n  const existingPopup = document.querySelector('#mem0-login-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  // Create popup container\n  const popupOverlay = document.createElement('div');\n  popupOverlay.id = 'mem0-login-popup';\n  popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 10001;\n  `;\n\n  const popupContainer = document.createElement('div');\n  popupContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  `;\n\n  // Close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #A1A1AA;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n  closeButton.innerHTML = '&times;';\n  closeButton.addEventListener('click', () => {\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Logo and heading\n  const logoContainer = document.createElement('div');\n  logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n  const logo = document.createElement('img');\n  logo.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logo.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const logoDark = document.createElement('img');\n  logoDark.src = chrome.runtime.getURL('icons/mem0-icon-black.png');\n  logoDark.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const heading = document.createElement('h2');\n  heading.textContent = 'Sign in to OpenMemory';\n  heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 600;\n  `;\n\n  logoContainer.appendChild(heading);\n\n  // Message\n  const message = document.createElement('p');\n  message.textContent =\n    'Please sign in to access your memories and personalize your conversations!';\n  message.style.cssText = `\n    margin-bottom: 24px;\n    color: #D4D4D8;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n  // Sign in button\n  const signInButton = document.createElement('button');\n  signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: white;\n    color: black;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  // Add text in span for better centering\n  const signInText = document.createElement('span');\n  signInText.textContent = 'Sign in with Mem0';\n\n  signInButton.appendChild(logoDark);\n  signInButton.appendChild(signInText);\n\n  signInButton.addEventListener('mouseenter', () => {\n    signInButton.style.backgroundColor = '#f5f5f5';\n  });\n\n  signInButton.addEventListener('mouseleave', () => {\n    signInButton.style.backgroundColor = 'white';\n  });\n\n  // Open sign-in page when clicked\n  signInButton.addEventListener('click', () => {\n    window.open('https://app.mem0.ai/login', '_blank');\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Assemble popup\n  popupContainer.appendChild(logoContainer);\n  popupContainer.appendChild(message);\n  popupContainer.appendChild(signInButton);\n\n  popupOverlay.appendChild(popupContainer);\n  popupOverlay.appendChild(closeButton);\n\n  // Add click event to close when clicking outside\n  popupOverlay.addEventListener('click', e => {\n    if (e.target === popupOverlay) {\n      document.body.removeChild(popupOverlay);\n    }\n  });\n\n  // Add to body\n  document.body.appendChild(popupOverlay);\n}\n\n// Function to capture and store memory asynchronously\nasync function captureAndStoreMemory(snapshot: string) {\n  // Check if extension context is valid\n  if (!chrome || !chrome.storage) {\n    return;\n  }\n\n  try {\n    // Check if memory is enabled\n    const memoryEnabled = await getMemoryEnabledState();\n    if (memoryEnabled === false) {\n      return; // Don't process memories if disabled\n    }\n  } catch (_) {\n    // Ignore error\n    return;\n  }\n\n  // Use the provided snapshot directly if available, otherwise try to get from input\n  let message = typeof snapshot === 'string' && snapshot.trim() !== '' ? snapshot : '';\n\n  if (!message) {\n    // Find the input element (prioritizing the ProseMirror div with contenteditable=\"true\")\n    let inputElement = document.querySelector('div[contenteditable=\"true\"].ProseMirror');\n\n    // If ProseMirror not found, try other input elements\n    if (!inputElement) {\n      inputElement =\n        document.querySelector('div[contenteditable=\"true\"]') ||\n        document.querySelector('textarea') ||\n        document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n        document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n    }\n\n    if (!inputElement) {\n      return;\n    }\n\n    if (inputElement.classList.contains('ProseMirror')) {\n      // For ProseMirror, get the textContent for plain text\n      message = inputElement.textContent || '';\n    } else if (inputElement.tagName.toLowerCase() === 'div') {\n      message = inputElement.textContent || '';\n    } else if (inputElement.tagName.toLowerCase() === 'p') {\n      message = inputElement.textContent || '';\n    } else {\n      message = inputElement.value || '';\n    }\n  }\n\n  if (!message || message.trim() === '') {\n    return;\n  }\n\n  // For ProseMirror, the getContentWithoutMemories returns HTML, so we need to extract text\n  if (typeof snapshot !== 'string' || !snapshot.trim()) {\n    message = getContentWithoutMemories(message);\n  }\n\n  // Skip if message is empty after processing\n  if (!message || message.trim() === '') {\n    return;\n  }\n\n  // Asynchronously store the memory\n\n  try {\n    // Check extension context again before storage access\n    if (!chrome || !chrome.storage || !chrome.storage.sync) {\n      return;\n    }\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.USER_ID_CAMEL,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.MEMORY_ENABLED,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.USER_ID,\n      ],\n      function (items) {\n        // Check for chrome.runtime.lastError which indicates extension context issues\n        try {\n          // @ts-ignore\n          if (chrome.runtime && (chrome.runtime as ChromeRuntime).lastError) {\n            return;\n          }\n        } catch {\n          // Ignore errors when checking chrome.runtime.lastError\n        }\n        // Skip if memory is disabled or no credentials\n        if (items.memory_enabled === false || (!items.apiKey && !items.access_token)) {\n          return;\n        }\n\n        const authHeader = items.access_token\n          ? `Bearer ${items.access_token}`\n          : `Token ${items.apiKey}`;\n\n        const userId = items.userId || items.user_id || 'chrome-extension-user';\n\n        // Get recent messages for context using sliding window\n        const contextMessages = getConversationContext(false); // Don't include current message yet\n        contextMessages.push({ role: MessageRole.User, content: message }); // Add current message\n\n        const optionalParams: OptionalApiParams = {};\n\n        if (items.selected_org) {\n          optionalParams.org_id = items.selected_org;\n        }\n        if (items.selected_project) {\n          optionalParams.project_id = items.selected_project;\n        }\n\n        // Send memory to mem0 API asynchronously without waiting for response\n        fetch('https://api.mem0.ai/v1/memories/', {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: authHeader,\n          },\n          body: JSON.stringify({\n            messages: contextMessages,\n            user_id: userId,\n            infer: true,\n            metadata: {\n              provider: 'Claude',\n            },\n            source: 'OPENMEMORY_CHROME_EXTENSION',\n            ...optionalParams,\n          }),\n        })\n          .then(response => {\n            if (!response.ok) {\n              // Silent failure for background memory addition\n            }\n          })\n          .catch(() => {\n            // Silent failure for background memory addition\n          });\n      }\n    );\n  } catch {\n    // Silent failure for background memory addition\n  }\n}\n\n// Function to update the notification dot\nfunction updateNotificationDot() {\n  // Find all Mem0 notification dots\n  const notificationDots = document.querySelectorAll('#mem0-notification-dot');\n  if (!notificationDots.length) {\n    return;\n  }\n\n  // Find the input element (prioritizing the ProseMirror div with contenteditable=\"true\")\n  let inputElement = document.querySelector('div[contenteditable=\"true\"].ProseMirror');\n\n  // If ProseMirror not found, try other input elements\n  if (!inputElement) {\n    inputElement =\n      document.querySelector('div[contenteditable=\"true\"]') ||\n      document.querySelector('textarea') ||\n      document.querySelector('p[data-placeholder=\"How can I help you today?\"]') ||\n      document.querySelector('p[data-placeholder=\"Reply to Claude...\"]');\n  }\n\n  if (!inputElement) {\n    return;\n  }\n\n  // Function to check if input has text\n  const checkForText = () => {\n    let hasText = false;\n\n    // Check for text based on the input type\n    if (inputElement.classList.contains('ProseMirror')) {\n      // For ProseMirror, check if it has any content other than just a placeholder <p>\n      const paragraphs = inputElement.querySelectorAll('p');\n\n      // Check if there's text content or if there are multiple paragraphs (not just empty placeholder)\n      const textContent = (inputElement.textContent || '').trim();\n      hasText =\n        textContent !== '' ||\n        paragraphs.length > 1 ||\n        (paragraphs.length === 1 &&\n          !(paragraphs[0] as HTMLElement | undefined)?.classList?.contains('is-empty'));\n    } else if (inputElement.tagName.toLowerCase() === 'p') {\n      // For p elements with placeholder\n      hasText = (inputElement.textContent || '').trim() !== '';\n    } else if (inputElement.tagName.toLowerCase() === 'div') {\n      // For normal contenteditable divs\n      hasText = (inputElement.textContent || '').trim() !== '';\n    } else {\n      // For textareas\n      hasText = (inputElement.value || '').trim() !== '';\n    }\n\n    // Update all notification dots\n    notificationDots.forEach(notificationDot => {\n      if (hasText) {\n        notificationDot.classList.add('active');\n        notificationDot.style.display = 'block';\n      } else {\n        notificationDot.classList.remove('active');\n        notificationDot.style.display = 'none';\n      }\n    });\n  };\n\n  // Setup mutation observer for the input element to detect changes\n  const observer = new MutationObserver(checkForText);\n  observer.observe(inputElement, {\n    childList: true,\n    characterData: true,\n    subtree: true,\n    attributes: true,\n    attributeFilter: ['class'],\n  });\n\n  // Also listen for direct input events\n  inputElement.addEventListener('input', checkForText);\n  inputElement.addEventListener('keyup', checkForText);\n  inputElement.addEventListener('focus', checkForText);\n\n  // Initial check\n  checkForText();\n\n  // Force another check after a small delay to ensure DOM is fully loaded\n  setTimeout(checkForText, 500);\n}\n\n// Enhanced DOM-based message detection since CSP blocks network interception\nlet domMonitoringActive = false;\n\nfunction setupEnhancedDOMMonitoring() {\n  if (domMonitoringActive) {\n    return;\n  }\n\n  domMonitoringActive = true;\n\n  // Enhanced real-time message monitoring with multiple strategies\n  function setupRealTimeMessageMonitoring() {\n    const threadSelector = '.flex-1.flex.flex-col.gap-3.px-4.max-w-3xl.mx-auto.w-full';\n\n    // Strategy 1: Monitor for new user message elements\n    const messageObserver = new MutationObserver(mutations => {\n      mutations.forEach(mutation => {\n        mutation.addedNodes.forEach(node => {\n          if (node.nodeType === 1) {\n            const el = node as Element;\n            // Look for user messages\n            const userMessage = el.querySelector('.font-user-message');\n            if (userMessage) {\n              const text = (userMessage.textContent || '').trim();\n              if (text) {\n                // Add to conversation history\n                addToConversationHistory(MessageRole.User, text);\n\n                setTimeout(() => {\n                  captureAndStoreMemory(text);\n                  setTimeout(() => {\n                    allMemories = [];\n                    allMemoriesById.clear();\n                  }, 100);\n                }, 50);\n              }\n            }\n\n            // Also check if the node itself is a user message\n            if (\n              (el as ExtendedElement).classList &&\n              (el as ExtendedElement).classList.contains('font-user-message')\n            ) {\n              const text = (el.textContent || '').trim();\n              if (text) {\n                // Add to conversation history\n                addToConversationHistory(MessageRole.User, text);\n\n                setTimeout(() => {\n                  captureAndStoreMemory(text);\n                  setTimeout(() => {\n                    allMemories = [];\n                    allMemoriesById.clear();\n                  }, 100);\n                }, 50);\n              }\n            }\n\n            // Also look for Claude's assistant messages\n            const assistantMessage = el.querySelector('.font-claude-message');\n            if (assistantMessage) {\n              const text = (assistantMessage.textContent || '').trim();\n              if (text) {\n                // Add to conversation history\n                addToConversationHistory(MessageRole.Assistant, text);\n              }\n            }\n\n            // Check if the node itself is an assistant message\n            if (\n              (el as ExtendedElement).classList &&\n              (el as ExtendedElement).classList.contains('font-claude-message')\n            ) {\n              const text = (el.textContent || '').trim();\n              if (text) {\n                // Add to conversation history\n                addToConversationHistory(MessageRole.Assistant, text);\n              }\n            }\n          }\n        });\n      });\n    });\n\n    // Find and observe the thread\n    const thread = document.querySelector(threadSelector);\n    if (thread) {\n      messageObserver.observe(thread, {\n        childList: true,\n        subtree: true,\n        attributes: false,\n        characterData: false,\n      });\n    } else {\n      // Retry finding the thread\n      setTimeout(setupRealTimeMessageMonitoring, 1000);\n    }\n  }\n\n  // Strategy 2: Monitor input clearing as a signal that message was sent\n  function setupInputClearingMonitor() {\n    const inputSelectors = [\n      'div[contenteditable=\"true\"].ProseMirror',\n      'div[contenteditable=\"true\"]',\n      'textarea',\n      'p[data-placeholder=\"How can I help you today?\"]',\n      'p[data-placeholder=\"Reply to Claude...\"]',\n    ];\n\n    let lastInputValue = '';\n    let inputClearingObserver: MutationObserver | undefined;\n\n    function findAndObserveInput() {\n      for (const selector of inputSelectors) {\n        const input = document.querySelector(selector);\n        if (input) {\n          // Disconnect any existing observer\n          if (inputClearingObserver) {\n            inputClearingObserver.disconnect();\n          }\n\n          inputClearingObserver = new MutationObserver(() => {\n            const currentValue = getInputValue() || '';\n\n            // Check if input was cleared (had content, now empty)\n            if (lastInputValue.trim() && !currentValue.trim()) {\n              // Add to conversation history\n              addToConversationHistory(MessageRole.User, lastInputValue);\n\n              setTimeout(() => {\n                captureAndStoreMemory(lastInputValue);\n                setTimeout(() => {\n                  allMemories = [];\n                  allMemoriesById.clear();\n                }, 100);\n              }, 50);\n            }\n\n            lastInputValue = currentValue;\n          });\n\n          inputClearingObserver.observe(input, {\n            childList: true,\n            subtree: true,\n            characterData: true,\n            attributes: true,\n          });\n\n          // Also listen for input events\n          input.addEventListener('input', () => {\n            lastInputValue = getInputValue() || '';\n          });\n\n          break;\n        }\n      }\n    }\n\n    findAndObserveInput();\n\n    // Re-find input periodically in case DOM changes\n    setInterval(findAndObserveInput, 5000);\n  }\n\n  // Start all monitoring strategies\n  setupRealTimeMessageMonitoring();\n  setupInputClearingMonitor();\n}\n\n// CSP blocks script injection, so focus on content script level approaches\n\n// Add extension context monitoring\nlet extensionContextValid = true;\nlet currentUrl = window.location.href;\n\nfunction checkExtensionContext() {\n  // @ts-ignore\n  const isValid = !!(chrome && chrome.runtime);\n  if (extensionContextValid && !isValid) {\n    extensionContextValid = false;\n  }\n  return isValid;\n}\n\n// Function to detect URL changes (SPA navigation)\nfunction detectNavigation() {\n  const newUrl = window.location.href;\n  if (newUrl !== currentUrl) {\n    const wasNewChat = currentUrl.includes('/new') || currentUrl.includes('/chat/new');\n    const isNewChat = newUrl.includes('/new') || newUrl.includes('/chat/new');\n    const isDifferentChat =\n      currentUrl.includes('/chat/') && newUrl.includes('/chat/') && currentUrl !== newUrl;\n\n    // Clear conversation history when navigating to a new chat or different chat\n    if (isNewChat || isDifferentChat || wasNewChat) {\n      conversationHistory = [];\n    }\n\n    // Reset DOM monitoring flag so it can be re-setup for new page\n    domMonitoringActive = false;\n\n    currentUrl = newUrl;\n\n    // Re-initialize everything after navigation\n    setTimeout(() => {\n      // Re-initialize conversation history from new DOM\n      initializeConversationHistoryFromDOM();\n\n      hookClaudeBackgroundSearchTyping(); \n\n      // Re-add buttons and listeners\n      if (!(OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus)) {\n        // addMem0Button(); // Removed: OPENMEMORY_UI handles icon mounting\n      }\n\n      // Re-setup enhanced DOM monitoring for new page\n      setupEnhancedDOMMonitoring();\n\n      // Update notification dot\n      updateNotificationDot();\n    }, 500); // Small delay to let DOM update\n  }\n}\n\n// Check for navigation every 1 second (more frequent than context check)\nsetInterval(() => {\n  checkExtensionContext();\n  detectNavigation();\n}, 1000);\n\n// Also listen for browser navigation events for faster detection\nwindow.addEventListener('popstate', () => {\n  setTimeout(detectNavigation, 100);\n});\n\n// Override pushState to catch programmatic navigation\nconst originalPushState = history.pushState;\nhistory.pushState = function (data: HistoryStateData, unused: string, url?: string | URL | null) {\n  originalPushState.call(history, data, unused, url);\n  setTimeout(detectNavigation, 100);\n};\n\n// Override replaceState to catch programmatic navigation\nconst originalReplaceState = history.replaceState;\nhistory.replaceState = function (\n  data: HistoryStateData,\n  unused: string,\n  url?: string | URL | null\n) {\n  originalReplaceState.call(history, data, unused, url);\n  setTimeout(detectNavigation, 100);\n};\n\n// Initialize conversation history from existing messages\ninitializeConversationHistoryFromDOM();\n\n// Set up enhanced DOM monitoring\nsetupEnhancedDOMMonitoring();\n\n// Main initialization\nconsole.log('🎯 Starting main initialization');\ninitializeMem0Integration();\n"
  },
  {
    "path": "src/context-menu-memory.ts",
    "content": "import { type ApiMemoryRequest, DEFAULT_USER_ID, MessageRole, SOURCE } from './types/api';\nimport {\n  MessageType,\n  type SelectionContextResponse,\n  type ToastMessage,\n  ToastVariant,\n} from './types/messages';\nimport { Category, Provider } from './types/providers';\nimport type { Settings } from './types/settings';\nimport { StorageKey } from './types/storage';\n\nexport function initContextMenuMemory(): void {\n  try {\n    chrome.contextMenus.create(\n      {\n        id: 'mem0.saveSelection',\n        title: 'Save to OpenMemory',\n        contexts: ['selection'],\n      },\n      () => {\n        /* no-op */\n      }\n    );\n  } catch {\n    // ignore\n  }\n\n  chrome.contextMenus.onClicked.addListener(\n    async (info: chrome.contextMenus.OnClickData, tab?: chrome.tabs.Tab) => {\n      if (\n        !tab ||\n        tab.id === null ||\n        tab.id === undefined ||\n        info.menuItemId !== 'mem0.saveSelection'\n      ) {\n        return;\n      }\n\n      const tabId = tab.id; // Type narrowing - we know tab.id is not null or undefined here\n\n      const selection = String(info.selectionText || '').trim();\n      if (!selection) {\n        toast(tabId, 'Select text first', ToastVariant.ERROR);\n        return;\n      }\n\n      const settings = await getSettings();\n      if (!settings.hasCreds) {\n        toast(tabId, 'Sign in required', ToastVariant.ERROR);\n        return;\n      }\n      if (settings.memoryEnabled === false) {\n        toast(tabId, 'Memory is disabled in settings', ToastVariant.ERROR);\n        return;\n      }\n\n      const title = tab.title || '';\n      const url = info.pageUrl || tab.url || '';\n\n      let ctx = await requestSelectionContext(tabId);\n      if (ctx && ctx.error) {\n        await tryInjectSelectionScript(tabId);\n        ctx = await requestSelectionContext(tabId);\n      }\n      const content = composeBasic({ selection, title, url });\n\n      try {\n        const ok = await addMemory(content, settings);\n        toast(\n          tabId,\n          ok ? 'Saved to OpenMemory' : 'Failed to save',\n          ok ? ToastVariant.SUCCESS : ToastVariant.ERROR\n        );\n      } catch (err) {\n        console.error('Failed to add memory:', err);\n        toast(tabId, 'Failed to save', ToastVariant.ERROR);\n      }\n    }\n  );\n}\n\nfunction toast(tabId: number, message: string, variant: ToastVariant = ToastVariant.SUCCESS): void {\n  try {\n    const msg: ToastMessage = { type: MessageType.TOAST, payload: { message, variant } };\n    chrome.tabs.sendMessage(tabId, msg);\n  } catch {\n    // Best effort only\n  }\n}\n\nfunction normalize(text: string): string {\n  return (text || '').replace(/\\s+/g, ' ').trim();\n}\n\nfunction clamp(text: string, max: number): string {\n  if (!text) {\n    return text;\n  }\n  if (text.length <= max) {\n    return text;\n  }\n  return text.slice(0, max - 1).trimEnd() + '…';\n}\n\nfunction composeBasic({ selection }: { selection: string; title: string; url: string }): string {\n  const s = clamp(normalize(selection), 700);\n  // Return raw selection only (no prefixes). We keep title/url only in metadata.\n  return s;\n}\n\nfunction requestSelectionContext(tabId: number): Promise<SelectionContextResponse> {\n  return new Promise(resolve => {\n    try {\n      chrome.tabs.sendMessage(\n        tabId,\n        { type: MessageType.GET_SELECTION_CONTEXT },\n        undefined,\n        (resp?: SelectionContextResponse) => {\n          resolve(resp || { error: 'no-response' });\n        }\n      );\n    } catch (e) {\n      resolve({ error: String(e) });\n    }\n  });\n}\n\nasync function tryInjectSelectionScript(tabId: number): Promise<boolean> {\n  try {\n    if (!chrome.scripting) {\n      return false;\n    }\n    await chrome.scripting.executeScript({\n      target: { tabId },\n      files: ['selection_context.ts'],\n    });\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction getSettings(): Promise<Settings> {\n  return new Promise<Settings>(resolve => {\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.USER_ID,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.MEMORY_ENABLED,\n      ],\n      d => {\n        resolve({\n          hasCreds: Boolean(d[StorageKey.API_KEY] || d[StorageKey.ACCESS_TOKEN]),\n          apiKey: d[StorageKey.API_KEY],\n          accessToken: d[StorageKey.ACCESS_TOKEN],\n          userId: d[StorageKey.USER_ID] || DEFAULT_USER_ID,\n          orgId: d[StorageKey.SELECTED_ORG],\n          projectId: d[StorageKey.SELECTED_PROJECT],\n          memoryEnabled: d[StorageKey.MEMORY_ENABLED] !== false,\n        });\n      }\n    );\n  });\n}\n\nasync function addMemory(content: string, settings: Settings): Promise<boolean> {\n  const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n  if (settings.accessToken) {\n    headers.Authorization = `Bearer ${settings.accessToken}`;\n  } else if (settings.apiKey) {\n    headers.Authorization = `Token ${settings.apiKey}`;\n  } else {\n    throw new Error('Missing credentials');\n  }\n\n  const body: ApiMemoryRequest = {\n    messages: [{ role: MessageRole.User, content }],\n    user_id: settings.userId,\n    metadata: {\n      provider: Provider.ContextMenu,\n      category: Category.BOOKMARK,\n    },\n    source: SOURCE,\n  };\n  if (settings.orgId) {\n    body.org_id = settings.orgId;\n  }\n  if (settings.projectId) {\n    body.project_id = settings.projectId;\n  }\n\n  const controller = new AbortController();\n  const timeout = setTimeout(() => controller.abort(), 10000);\n  try {\n    const res = await fetch('https://api.mem0.ai/v1/memories/', {\n      method: 'POST',\n      headers,\n      body: JSON.stringify(body),\n      signal: controller.signal,\n    });\n    return res.ok;\n  } finally {\n    clearTimeout(timeout);\n  }\n}\n"
  },
  {
    "path": "src/deepseek/content.ts",
    "content": "import { MessageRole } from '../types/api';\nimport type { ExtendedHTMLElement } from '../types/dom';\nimport type { MemoryItem, MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI, type Placement } from '../utils/util_positioning';\n\nexport {};\n\nconst INPUT_SELECTOR = \"#chat-input, textarea, [contenteditable='true']\";\n\n// Helper function to check if a node matches ignored selectors\nfunction isIgnoredNode(node: Element, ignoredSelectors: string[]): boolean {\n  if (node.nodeType !== Node.ELEMENT_NODE) {\n    return false;\n  }\n\n  // Check self\n  for (const selector of ignoredSelectors) {\n    if (node.matches && node.matches(selector)) {\n      return true;\n    }\n  }\n\n  // Check parents up to 3 levels\n  let parent: HTMLElement | null = node.parentElement;\n  let level = 0;\n  while (parent && level < 3) {\n    for (const selector of ignoredSelectors) {\n      if (parent.matches && parent.matches(selector)) {\n        return true;\n      }\n    }\n    parent = parent.parentElement;\n    level++;\n  }\n\n  return false;\n}\n\n// Function to expand memory\nfunction expandMemory(\n  memoryContainer: HTMLElement,\n  memoryText: HTMLElement,\n  contentWrapper: HTMLElement,\n  removeButton: HTMLElement,\n  currentlyExpandedMemory: HTMLElement | null,\n  memoriesContent: HTMLElement\n) {\n  if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n    currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n  }\n\n  memoryText.style.webkitLineClamp = 'unset';\n  memoryText.style.height = 'auto';\n  contentWrapper.style.overflowY = 'auto';\n  contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n  contentWrapper.style.scrollbarWidth = 'none';\n  contentWrapper.style.msOverflowStyle = 'none';\n  contentWrapper.style.cssText += '::-webkit-scrollbar { display: none; }';\n  memoryContainer.style.backgroundColor = '#1C1C1E';\n  memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n  memoryContainer.style.overflow = 'hidden';\n  removeButton.style.display = 'flex';\n  currentlyExpandedMemory = memoryContainer;\n\n  // Scroll to make expanded memory visible if needed\n  memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n}\n\n// Function to collapse memory\nfunction collapseMemory(\n  memoryContainer: HTMLElement,\n  memoryText: HTMLElement,\n  contentWrapper: HTMLElement,\n  removeButton: HTMLElement\n) {\n  memoryText.style.webkitLineClamp = '2';\n  memoryText.style.height = '42px';\n  contentWrapper.style.overflowY = 'visible';\n  memoryContainer.style.backgroundColor = '#27272A';\n  memoryContainer.style.maxHeight = '52px';\n  memoryContainer.style.overflow = 'hidden';\n  removeButton.style.display = 'none';\n}\n\n// Initialize memory tracking variables\nlet isProcessingMem0: boolean = false;\nlet observer: MutationObserver;\nlet memoryModalShown: boolean = false;\nlet allMemories: string[] = [];\nlet allMemoriesById: Set<string> = new Set<string>();\nlet currentModalOverlay: HTMLDivElement | null = null;\nlet mem0ButtonCheckInterval: ReturnType<typeof setInterval> | null = null; // Add interval variable for button checks\nlet modalDragPosition: { left: number; top: number } | null = null; // Store the dragged position of the modal\n\n// Using MemoryItem from src/types/content-scripts.ts (includes memory field for compatibility)\n\n// Function to remove the Mem0 icon button when memory is disabled\nfunction removeMem0IconButton() {\n  const iconButton = document.querySelector('#mem0-icon-button');\n  if (iconButton) {\n    const buttonContainer = iconButton.closest('div');\n    if (buttonContainer && buttonContainer.id !== 'mem0-custom-container') {\n      // Only remove the button, not the container unless it's our custom one\n      try {\n        buttonContainer.removeChild(iconButton);\n      } catch {\n        // If removal fails, try removing just the button\n        iconButton.remove();\n      }\n    } else {\n      // Remove the button directly\n      iconButton.remove();\n    }\n  }\n\n  // Also remove custom container if it exists\n  const customContainer = document.querySelector('#mem0-custom-container');\n  if (customContainer) {\n    customContainer.remove();\n  }\n}\n\nfunction getInputElement() {\n  // Try finding with the more specific selector first\n  const inputElement = document.querySelector(INPUT_SELECTOR);\n\n  if (inputElement) {\n    return inputElement;\n  }\n\n  // If not found, try a more general approach\n\n  // Try finding by common input attributes\n  const textareas = document.querySelectorAll('textarea');\n  if (textareas.length > 0) {\n    // Return the textarea that's visible and has the largest area (likely the main input)\n    let bestMatch = null;\n    let largestArea = 0;\n\n    Array.from(textareas).forEach(textarea => {\n      const rect = (textarea as HTMLTextAreaElement).getBoundingClientRect();\n      const isVisible = rect.width > 0 && rect.height > 0;\n      const area = rect.width * rect.height;\n\n      if (isVisible && area > largestArea) {\n        largestArea = area;\n        bestMatch = textarea as HTMLTextAreaElement;\n      }\n    });\n\n    if (bestMatch) {\n      return bestMatch;\n    }\n  }\n\n  // Try contenteditable divs\n  const editableDivs = document.querySelectorAll('[contenteditable=\"true\"]');\n  if (editableDivs.length > 0) {\n    return editableDivs[0];\n  }\n\n  // Try any element with role=\"textbox\"\n  const textboxes = document.querySelectorAll('[role=\"textbox\"]');\n  if (textboxes.length > 0) {\n    return textboxes[0];\n  }\n\n  return null;\n}\n\nfunction getSendButtonElement(): HTMLElement | null {\n  try {\n    // Strategy 1: Look for buttons with send-like characteristics\n    const buttons = document.querySelectorAll('div[role=\"button\"]');\n\n    if (buttons.length === 0) {\n      return null;\n    }\n\n    // Get the input element to help with positioning-based detection\n    const inputElement = getInputElement();\n    const inputRect = inputElement ? (inputElement as HTMLElement).getBoundingClientRect() : null;\n\n    // Find candidate buttons that might be send buttons\n    let bestSendButton: HTMLElement | null = null;\n    let bestScore = 0;\n\n    Array.from(buttons).forEach(btn => {\n      const button = btn as HTMLElement;\n      // Skip if button is not visible or has no size\n      const buttonRect = button.getBoundingClientRect();\n      if (buttonRect.width === 0 || buttonRect.height === 0) {\n        return;\n      }\n\n      let score = 0;\n\n      // 1. Check if it has an SVG (likely an icon button)\n      const svg = button.querySelector('svg');\n      if (svg) {\n        score += 2;\n      }\n\n      // 2. Check if it has no text content (icon-only buttons)\n      const buttonText = (button.textContent || '').trim();\n      if (buttonText === '') {\n        score += 2;\n      }\n\n      // 3. Check if it contains a paper airplane shape (common in send buttons)\n      const paths = svg ? svg.querySelectorAll('path') : [];\n      if (paths.length > 0) {\n        score += 1;\n      }\n\n      // 4. Check positioning relative to input (send buttons are usually close to input)\n      if (inputRect) {\n        // Check if button is positioned to the right of input\n        if (buttonRect.left > inputRect.left) {\n          score += 1;\n        }\n\n        // Check if button is at similar height to input\n        if (Math.abs(buttonRect.top - inputRect.top) < 100) {\n          score += 2;\n        }\n\n        // Check if button is very close to input (right next to it)\n        if (Math.abs(buttonRect.left - (inputRect.right + 20)) < 40) {\n          score += 3;\n        }\n      }\n\n      // 5. Check for DeepSeek specific classes\n      if (button.classList.contains('ds-button--primary')) {\n        score += 2;\n      }\n\n      // Update best match if this button has a higher score\n      if (score > bestScore) {\n        bestScore = score;\n        bestSendButton = button;\n      }\n    });\n\n    // Return best match if score is reasonable\n    if (bestScore >= 4) {\n      return bestSendButton;\n    }\n\n    // Strategy 2: Look for buttons positioned at the right of the input\n    if (inputElement && inputRect) {\n      // Find buttons positioned to the right of the input\n      const rightButtons = Array.from(buttons).filter(btn => {\n        const buttonRect = (btn as HTMLElement).getBoundingClientRect();\n        return (\n          buttonRect.left > inputRect.right - 50 && // To the right\n          Math.abs(buttonRect.top - inputRect.top) < 50\n        ); // Similar height\n      });\n\n      // Sort by horizontal proximity to input\n      rightButtons.sort((a, b) => {\n        const aRect = (a as HTMLElement).getBoundingClientRect();\n        const bRect = (b as HTMLElement).getBoundingClientRect();\n        return aRect.left - inputRect.right - (bRect.left - inputRect.right);\n      });\n\n      // Return the closest button\n      if (rightButtons.length > 0) {\n        return (rightButtons[0] as HTMLElement) || null;\n      }\n    }\n\n    // Strategy 3: Last resort - take the last button with an SVG\n    const svgButtons = Array.from(buttons).filter(btn => (btn as HTMLElement).querySelector('svg'));\n    if (svgButtons.length > 0) {\n      return svgButtons[svgButtons.length - 1] as HTMLElement;\n    }\n\n    return null;\n  } catch {\n    return null; // Return null on error instead of failing\n  }\n}\n\n// Updated handleEnterKey with additional safety checks\nasync function handleEnterKey(event: KeyboardEvent) {\n  try {\n    // Safety check - only proceed if we can identify an input element\n    const inputElement = getInputElement();\n    if (!inputElement) {\n      return; // Skip processing if no input found\n    }\n\n    // Only handle Enter without Shift and when target is the input element\n    if (event.key === 'Enter' && !event.shiftKey && event.target === inputElement) {\n      // Don't prevent default behavior yet until we've checked memory state\n\n      // Check if memory is enabled\n      let memoryEnabled = false;\n      try {\n        memoryEnabled = await getMemoryEnabledState();\n      } catch {\n        return; // Don't interfere if we can't check memory state\n      }\n\n      if (!memoryEnabled) {\n        return; // Let the default behavior proceed\n      }\n\n      // At this point, we know memory is enabled so let's handle the Enter key\n\n      // Now prevent default since we'll handle the send ourselves\n      event.preventDefault();\n      event.stopPropagation();\n\n      // Process memories and then send\n      try {\n        await handleMem0Processing();\n      } catch {\n        triggerSendAction();\n      }\n    }\n  } catch {\n    // Don't interfere with normal behavior if something goes wrong\n  }\n}\n\nfunction initializeMem0Integration(): void {\n  // Global flag to track initialization state\n  window.mem0Initialized = window.mem0Initialized || false;\n\n  // Reset initialization flag on navigation or visibility change\n  document.addEventListener('visibilitychange', () => {\n    if (document.visibilityState === 'visible') {\n      // Page likely navigated or became visible, reset initialization\n      if (window.mem0Initialized) {\n        setTimeout(() => {\n          if (!document.querySelector('#mem0-icon-button')) {\n            window.mem0Initialized = false;\n            stageCriticalInit();\n          }\n        }, 1000);\n      }\n    }\n  });\n\n  // Avoid duplicating initialization\n  if (window.mem0Initialized) {\n    if (!document.querySelector('#mem0-icon-button')) {\n      addMem0IconButton();\n    }\n    return;\n  }\n\n  // Step 1: Wait for the page to be fully loaded before doing anything\n  if (document.readyState !== 'complete') {\n    window.addEventListener('load', function () {\n      setTimeout(stageCriticalInit, 500); // Reduced wait time after load\n    });\n  } else {\n    // Page is already loaded, wait a moment and then initialize\n    setTimeout(stageCriticalInit, 500); // Reduced wait time\n  }\n\n  // Stage 1: Initialize critical features (keyboard shortcuts, basic listeners)\n  function stageCriticalInit() {\n    try {\n      // Early exit if already initialized\n      if (window.mem0Initialized) {\n        return;\n      }\n\n      // Add keyboard event listeners\n      addKeyboardListeners();\n\n      // Add send button listener (non-blocking)\n      setTimeout(() => {\n        try {\n          addSendButtonListener();\n        } catch {\n          // Ignore errors\n        }\n      }, 2000);\n\n      // Start background search typing hook once\n      try {\n        hookDeepseekBackgroundSearchTyping();\n      } catch {\n        // Ignore errors\n      }\n      // Wait additional time for UI to stabilize\n      setTimeout(stageUIInit, 1000); // Reduced time\n    } catch {\n      // Don't mark as initialized on error\n    }\n  }\n\n  // Stage 2: Initialize UI components after the DOM has settled\n  function stageUIInit() {\n    try {\n      // Early exit if already initialized\n      if (window.mem0Initialized) {\n        return;\n      }\n\n      // Set up the observer to detect UI changes\n      setupObserver();\n\n      // Mark as initialized once we've completed both stages\n      window.mem0Initialized = true;\n\n      // Clear any existing interval\n      if (mem0ButtonCheckInterval) {\n        clearInterval(mem0ButtonCheckInterval);\n      }\n\n      // Set up periodic checks for button presence - check memory state first\n      mem0ButtonCheckInterval = setInterval(async () => {\n        try {\n          const memoryEnabled = await getMemoryEnabledState();\n          if (memoryEnabled) {\n            if (!document.querySelector('#mem0-icon-button')) {\n              addMem0IconButton();\n            }\n          } else {\n            removeMem0IconButton();\n          }\n        } catch {\n          // On error, don't do anything\n        }\n      }, 5000); // Check every 5 seconds\n\n      // Final check after more time\n      setTimeout(async () => {\n        try {\n          const memoryEnabled = await getMemoryEnabledState();\n          if (memoryEnabled) {\n            if (!document.querySelector('#mem0-icon-button')) {\n              addMem0IconButton();\n            }\n          } else {\n            removeMem0IconButton();\n          }\n        } catch {\n          // On error, don't do anything\n        }\n      }, 5000);\n    } catch {\n      // Ignore errors\n    }\n  }\n\n  // Add keyboard listeners with error handling\n  function addKeyboardListeners() {\n    try {\n      // Skip if already added\n      if (window.mem0KeyboardListenersAdded) {\n        return;\n      }\n\n      // Listen for Enter key to handle memory processing\n      document.addEventListener('keydown', handleEnterKey, true);\n\n      // Listen for Ctrl+M to open the modal directly\n      document.addEventListener('keydown', function (event) {\n        if (event.ctrlKey && event.key === 'm') {\n          event.preventDefault();\n          (async () => {\n            try {\n              await handleMem0Modal('mem0-icon-button');\n            } catch {\n              // Ignore errors\n            }\n          })();\n        }\n      });\n\n      window.mem0KeyboardListenersAdded = true;\n    } catch {\n      // Ignore errors\n    }\n  }\n\n  // Set up mutation observer with throttling and filtering\n  function setupObserver(): void {\n    try {\n      // Disconnect existing observer if any\n      if (observer) {\n        observer.disconnect();\n      }\n\n      // Track when we last processed mutations\n      let lastObserverRun = 0;\n      const MIN_THROTTLE_MS = 3000; // Reduced from 10s to 3s\n\n      const ignoredSelectors = [\n        '#mem0-icon-button',\n        '.mem0-tooltip',\n        '.mem0-tooltip-arrow',\n        '#mem0-notification-dot',\n        '#mem0-icon-button *', // Any children of the button\n      ];\n\n      observer = new MutationObserver(mutations => {\n        // Skip mutations on ignored elements\n        const shouldIgnore = mutations.every(mutation => {\n          // Check if the mutation target or parents match any ignored selectors\n          const isIgnoredElement =\n            (mutation.target as Node).nodeType === Node.ELEMENT_NODE\n              ? isIgnoredNode(mutation.target as Element, ignoredSelectors)\n              : false;\n\n          // Check added nodes for tooltip/button related elements\n          if (mutation.type === 'childList') {\n            const addedIgnored = Array.from(mutation.addedNodes).some(node => {\n              return (\n                node.nodeType === Node.ELEMENT_NODE &&\n                isIgnoredNode(node as Element, ignoredSelectors)\n              );\n            });\n            if (addedIgnored) {\n              return true;\n            }\n          }\n\n          return isIgnoredElement;\n        });\n\n        if (shouldIgnore) {\n          return; // Skip these mutations\n        }\n\n        // Check if the button exists - no action needed if it does\n        if (document.querySelector('#mem0-icon-button')) {\n          return;\n        }\n\n        // Apply throttling\n        const now = Date.now();\n        if (now - lastObserverRun < MIN_THROTTLE_MS) {\n          return; // Too soon, skip\n        }\n\n        // Process mutations - just check and add button\n        lastObserverRun = now;\n        addMem0IconButton();\n      });\n\n      // Helper function to check if a node matches ignored selectors\n      // isIgnoredNode function is defined above\n\n      // Only observe high-level document changes to detect navigation\n      observer.observe(document.body, {\n        childList: true,\n        subtree: true,\n        attributes: false,\n        attributeFilter: ['class', 'style'], // Only observe class/style changes\n      });\n    } catch {\n      // Ignore errors\n    }\n  }\n}\n\nasync function getMemoryEnabledState(): Promise<boolean> {\n  return new Promise<boolean>(resolve => {\n    chrome.storage.sync.get(\n      [StorageKey.MEMORY_ENABLED, StorageKey.API_KEY, StorageKey.ACCESS_TOKEN],\n      data => {\n        // Check if memory is enabled AND if we have auth credentials\n        const hasAuth = !!data.apiKey || !!data.access_token;\n        const memoryEnabled = !!data.memory_enabled;\n\n        // Only consider logged in if both memory is enabled and auth credentials exist\n        resolve(!!(memoryEnabled && hasAuth));\n      }\n    );\n  });\n}\n\nfunction getInputElementValue(): string | null {\n  const inputElement = getInputElement();\n  const el = inputElement as HTMLTextAreaElement | HTMLDivElement | null;\n  if (!el) {\n    return null;\n  }\n  // Prefer textContent for contenteditable\n  const text = (el as HTMLDivElement).textContent ?? (el as HTMLTextAreaElement).value ?? null;\n  return text;\n}\n\nfunction getAuthDetails(): Promise<{ apiKey: string; accessToken: string; userId: string }> {\n  return new Promise(resolve => {\n    chrome.storage.sync.get(\n      [StorageKey.API_KEY, StorageKey.ACCESS_TOKEN, StorageKey.USER_ID_CAMEL],\n      items => {\n        resolve({\n          apiKey: items.apiKey || null,\n          accessToken: items.access_token || null,\n          userId: items.userId || 'chrome-extension-user',\n        });\n      }\n    );\n  });\n}\n\nconst MEM0_API_BASE_URL = 'https://api.mem0.ai';\n\nlet currentModalSourceButtonId: string | null = null;\n\nconst deepseekSearch = createOrchestrator({\n  fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n    const data = await new Promise<SearchStorage>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as SearchStorage);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      return [];\n    }\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const threshold =\n      data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n        ? data[StorageKey.SIMILARITY_THRESHOLD]\n        : 0.1;\n    const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    const payload = {\n      query,\n      filters: { user_id: userId },\n      rerank: true,\n      threshold: threshold,\n      top_k: topK,\n      filter_memories: false,\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      ...optionalParams,\n    };\n\n    const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify(payload),\n      signal: opts && opts.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error(`API request failed with status ${res.status}`);\n    }\n    return await res.json();\n  },\n\n  // Don’t render on prefetch. When modal is open, update it.\n  onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n    if (!memoryModalShown) {\n      return;\n    }\n    const memoryItems = ((responseData as MemorySearchItem[]) || []).map(\n      (item: MemorySearchItem) => ({\n        id: String(item.id),\n        text: item.memory,\n        categories: item.categories || [],\n      })\n    );\n    createMemoryModal(memoryItems, false, currentModalSourceButtonId);\n  },\n\n  onError: function () {\n    if (memoryModalShown) {\n      createMemoryModal([], false, currentModalSourceButtonId);\n    }\n  },\n\n  minLength: 3,\n  debounceMs: 75,\n  cacheTTL: 60000,\n});\n\nlet deepseekBackgroundSearchHandler: (() => void) | null = null;\nfunction hookDeepseekBackgroundSearchTyping() {\n  const inputEl = getInputElement();\n  if (!inputEl) {\n    return;\n  }\n\n  if (inputEl.dataset.deepseekBackgroundHooked) {\n    return;\n  }\n  inputEl.dataset.deepseekBackgroundHooked = 'true';\n\n  if (!deepseekBackgroundSearchHandler) {\n    deepseekBackgroundSearchHandler = function () {\n      const text = getInputElementValue() || '';\n      deepseekSearch.setText(text);\n    };\n  }\n  inputEl.addEventListener('input', deepseekBackgroundSearchHandler);\n  inputEl.addEventListener('keyup', deepseekBackgroundSearchHandler);\n}\n\n// async function searchMemories(query: string): Promise<MemoryItem[]> {\n//   try {\n//     const items = await chrome.storage.sync.get([\n//       StorageKey.API_KEY,\n//       StorageKey.USER_ID_CAMEL,\n//       StorageKey.ACCESS_TOKEN,\n//       StorageKey.SELECTED_ORG,\n//       StorageKey.SELECTED_PROJECT,\n//       StorageKey.USER_ID,\n//       StorageKey.SIMILARITY_THRESHOLD,\n//       StorageKey.TOP_K,\n//     ]);\n//     const userId = items.userId || items.user_id || \"chrome-extension-user\";\n//     const threshold = items.similarity_threshold !== undefined ? items.similarity_threshold : 0.1;\n//     const topK = items.top_k !== undefined ? items.top_k : 10;\n\n//     if (!items.access_token && !items.apiKey) {\n//       throw new Error(\"Authentication details missing\");\n//     }\n\n//     const optionalParams: OptionalApiParams = {};\n//     if (items.selected_org) {\n//       optionalParams[\"org_id\"] = items.selected_org;\n//     }\n//     if (items.selected_project) {\n//       optionalParams[\"project_id\"] = items.selected_project;\n//     }\n\n//     const headers: Record<string, string> = {\n//       \"Content-Type\": \"application/json\",\n//     };\n//     if (items.access_token) {\n//       headers[\"Authorization\"] = `Bearer ${items.access_token}`;\n//     } else {\n//       headers[\"Authorization\"] = `Api-Key ${items.apiKey}`;\n//     }\n\n//     const url = `${MEM0_API_BASE_URL}/v2/memories/search/`;\n//     const body = JSON.stringify({\n//       query: query,\n//       filters: {\n//         user_id: userId,\n//       },\n//       rerank: true,\n//       threshold: threshold,\n//       top_k: topK,\n//       filter_memories: false,\n//       // llm_rerank: true,\n//       source: \"OPENMEMORY_CHROME_EXTENSION\",\n//       ...optionalParams,\n//     });\n\n//     const response = await fetch(url, {\n//       method: \"POST\",\n//       headers: headers,\n//       body: body,\n//     });\n\n//     if (!response.ok) {\n//       throw new Error(`HTTP error! status: ${response.status}`);\n//     }\n\n//     const data = await response.json();\n\n//     const memoryItems: MemoryItem[] = (data as MemorySearchResponse).map(item => ({\n//       id: item.id,\n//       text: item.text || item.memory,\n//       created_at: item.created_at,\n//       user_id: item.user_id,\n//       memory: item.memory,\n//     }));\n\n//     return memoryItems;\n//   } catch {\n//     // Error preparing search request\n//     return [];\n//   }\n// }\n\nfunction addMemory(memoryText: string) {\n  return new Promise((resolve, reject) => {\n    (async () => {\n      try {\n        const items = await chrome.storage.sync.get([\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n        ]);\n        const userId = items.userId || items.user_id || 'chrome-extension-user';\n\n        if (!items.access_token && !items.apiKey) {\n          // No API Key or Access Token found for adding memory\n          return reject(new Error('Authentication details missing'));\n        }\n\n        const optionalParams: OptionalApiParams = {};\n        if (items.selected_org) {\n          optionalParams['org_id'] = items.selected_org;\n        }\n        if (items.selected_project) {\n          optionalParams['project_id'] = items.selected_project;\n        }\n\n        const headers: Record<string, string> = {\n          'Content-Type': 'application/json',\n        };\n        if (items.access_token) {\n          headers['Authorization'] = `Bearer ${items.access_token}`;\n        } else {\n          headers['Authorization'] = `Api-Key ${items.apiKey}`;\n        }\n\n        const url = `${MEM0_API_BASE_URL}/v1/memories/`;\n        const body = JSON.stringify({\n          messages: [\n            {\n              role: MessageRole.User,\n              content: memoryText,\n            },\n          ],\n          user_id: userId,\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          ...optionalParams,\n        });\n\n        fetch(url, {\n          method: 'POST',\n          headers: headers,\n          body: body,\n        })\n          .then(response => {\n            if (!response.ok) {\n              return response\n                .json()\n                .then(errorData => {\n                  // Mem0 API Add Memory Error Response Body\n                  throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);\n                })\n                .catch(() => {\n                  // Failed to parse add memory error response body\n                  throw new Error(`HTTP error! status: ${response.status}`);\n                });\n            }\n            if (response.status === 204) {\n              return null;\n            }\n            return response.json();\n          })\n          .then(data => {\n            resolve(data);\n          })\n          .catch(error => {\n            // Error adding memory directly\n            reject(error);\n          });\n      } catch (error) {\n        // Error preparing add memory request\n        reject(error);\n      }\n    })();\n  });\n}\n\nasync function triggerSendAction(): Promise<void> {\n  try {\n    // Get send button with multiple attempts if needed\n    let sendButton = getSendButtonElement();\n    let attempts = 0;\n\n    // If button not found, try again a few times with increasing delays\n    while (!sendButton && attempts < 3) {\n      attempts++;\n      await new Promise(resolve => setTimeout(resolve, attempts * 300));\n      sendButton = getSendButtonElement();\n    }\n\n    if (sendButton) {\n      // Check if button is disabled\n      const isDisabled =\n        sendButton.getAttribute('aria-disabled') === 'true' ||\n        sendButton.classList.contains('disabled') ||\n        sendButton.classList.contains('ds-button--disabled') ||\n        sendButton.hasAttribute('disabled') ||\n        (sendButton as ExtendedHTMLElement).disabled;\n\n      if (!isDisabled) {\n        // Try multiple click strategies\n        try {\n          // Strategy 1: Native click() method\n          sendButton.click();\n\n          // Strategy 2: After a short delay, try a MouseEvent if the first click didn't work\n          setTimeout(() => {\n            try {\n              // Check if the input field is now empty (indicating message was sent)\n              const inputElement = getInputElement() as HTMLTextAreaElement | HTMLDivElement;\n              const inputValue = inputElement\n                ? (\n                    (inputElement as HTMLDivElement).textContent ||\n                    (inputElement as HTMLTextAreaElement).value ||\n                    ''\n                  ).trim()\n                : null;\n\n              // If input is still not empty, try alternative click method\n              if (inputValue && inputValue.length > 0) {\n                const clickEvent = new MouseEvent('click', {\n                  bubbles: true,\n                  cancelable: true,\n                  view: window,\n                });\n                sendButton.dispatchEvent(clickEvent);\n              }\n            } catch {\n              // Ignore errors\n            }\n          }, 200);\n\n          // Strategy 3: As a last resort, try to focus and press Enter\n          setTimeout(() => {\n            try {\n              const inputElement = getInputElement() as HTMLTextAreaElement | HTMLDivElement;\n              const inputValue = inputElement\n                ? (\n                    (inputElement as HTMLDivElement).textContent ||\n                    (inputElement as HTMLTextAreaElement).value ||\n                    ''\n                  ).trim()\n                : null;\n\n              // If input is still not empty, try pressing Enter\n              if (inputValue && inputValue.length > 0) {\n                (inputElement as HTMLElement).focus();\n                const enterEvent = new KeyboardEvent('keydown', {\n                  key: 'Enter',\n                  code: 'Enter',\n                  keyCode: 13,\n                  which: 13,\n                  bubbles: true,\n                  cancelable: true,\n                });\n                if (inputElement) {\n                  (inputElement as HTMLElement).dispatchEvent(enterEvent);\n                }\n              }\n            } catch {\n              // Ignore errors\n            }\n          }, 500);\n        } catch {\n          // Ignore errors\n        }\n      } else {\n        // Button is disabled\n      }\n    } else {\n      // No send button found\n    }\n  } catch {\n    // Ignore errors\n  }\n}\n\nasync function handleMem0Processing(): Promise<void> {\n  try {\n    // Check if we're already processing (prevent double processing)\n    if (isProcessingMem0) {\n      return;\n    }\n\n    isProcessingMem0 = true;\n\n    // Get the current input value\n    const originalPrompt = getInputElementValue();\n    if (!originalPrompt || originalPrompt.trim() === '') {\n      isProcessingMem0 = false;\n      triggerSendAction();\n      return;\n    }\n\n    // Trigger the send action\n    await triggerSendAction();\n\n    // Add the user's input as a new memory\n    try {\n      if (originalPrompt.trim().length > 5) {\n        // Only add non-trivial prompts\n        await addMemory(originalPrompt);\n      }\n    } catch {\n      // Continue regardless of error adding memory\n    }\n\n    // Reset state after a short delay\n    setTimeout(() => {\n      isProcessingMem0 = false;\n      allMemories = []; // Clear loaded memories\n      allMemoriesById = new Set();\n    }, 1000);\n  } catch {\n    // Reset processing state and trigger send as fallback\n    isProcessingMem0 = false;\n    triggerSendAction();\n  }\n}\n\n// Function to create a memory modal\nfunction createMemoryModal(\n  memoryItems: MemoryItem[],\n  isLoading: boolean = false,\n  sourceButtonId: string | null = null\n): void {\n  // Close existing modal if it exists (but preserve drag position for updates)\n  if (memoryModalShown && currentModalOverlay) {\n    document.body.removeChild(currentModalOverlay);\n  }\n\n  memoryModalShown = true;\n  let currentMemoryIndex = 0;\n\n  // Calculate modal dimensions\n  const modalWidth = 447;\n  let modalHeight = 400; // Default height\n  let memoriesPerPage = 3; // Default number of memories per page\n\n  let topPosition: number | undefined;\n  let leftPosition: number | undefined;\n\n  // Check if we have a stored drag position and use it\n  if (modalDragPosition) {\n    topPosition = modalDragPosition.top;\n    leftPosition = modalDragPosition.left;\n  } else {\n    // Different positioning based on which button triggered the modal\n    if (sourceButtonId === 'mem0-icon-button') {\n      // Position relative to the mem0-icon-button\n      const iconButton = document.querySelector('#mem0-icon-button');\n      if (iconButton) {\n        const buttonRect = iconButton.getBoundingClientRect();\n\n        // Determine if there's enough space above the button\n        const spaceAbove = buttonRect.top;\n        const viewportHeight = window.innerHeight;\n\n        leftPosition = buttonRect.left - modalWidth + buttonRect.width;\n        leftPosition = Math.max(leftPosition, 10);\n\n        if (spaceAbove >= modalHeight + 10) {\n          // Place above\n          topPosition = buttonRect.top - modalHeight - 10;\n        } else {\n          // Not enough space above, place below\n          topPosition = buttonRect.bottom + 10;\n\n          if (buttonRect.bottom > viewportHeight / 2) {\n            modalHeight = 300; // Reduced height\n            memoriesPerPage = 2; // Show only 2 memories\n          }\n        }\n      } else {\n        // Fallback to input-based positioning\n        positionRelativeToInput();\n      }\n    } else {\n      // Default positioning relative to the input field\n      positionRelativeToInput();\n    }\n  }\n\n  // Helper function to position modal relative to input field\n  function positionRelativeToInput() {\n    const inputElement = getInputElement();\n\n    if (!inputElement) {\n      return;\n    }\n\n    // Get the position and dimensions of the input field\n    const inputRect = inputElement.getBoundingClientRect();\n\n    // Determine if there's enough space below the input field\n    const viewportHeight = window.innerHeight;\n    const spaceBelow = viewportHeight - inputRect.bottom;\n\n    // Position the modal aligned to the right of the input\n    leftPosition = Math.max(inputRect.right - 20 - modalWidth, 10); // 20px offset from right edge\n\n    // Decide whether to place modal above or below based on available space\n    if (spaceBelow >= modalHeight) {\n      // Place below the input\n      topPosition = inputRect.bottom + 10;\n\n      // Check if it's in the lower half of the screen\n      if (inputRect.bottom > viewportHeight / 2) {\n        modalHeight = 300; // Reduced height\n        memoriesPerPage = 2; // Show only 2 memories\n      }\n    } else {\n      // Place above the input if not enough space below\n      topPosition = inputRect.top - modalHeight - 10;\n    }\n  }\n\n  // Create modal overlay\n  const modalOverlay = document.createElement('div');\n  modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n  // Save reference to current modal overlay\n  currentModalOverlay = modalOverlay;\n\n  // Add event listener to close modal when clicking outside\n  modalOverlay.addEventListener('click', (event: MouseEvent) => {\n    // Only close if clicking directly on the overlay, not its children\n    if (event.target === modalOverlay) {\n      closeModal();\n    }\n  });\n\n  // Create modal container with positioning\n  const modalContainer = document.createElement('div');\n  modalContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    position: absolute;\n    top: ${topPosition}px;\n    left: ${leftPosition}px;\n    pointer-events: auto;\n    border: 1px solid #27272A;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    overflow: hidden;\n  `;\n\n  // Create modal header\n  const modalHeader = document.createElement('div');\n  modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #232325;\n    flex-shrink: 0;\n    cursor: move;\n    user-select: none;\n  `;\n\n  // Create header left section with logo\n  const headerLeft = document.createElement('div');\n  headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    pointer-events: none;\n  `;\n\n  // Add Mem0 logo\n  const logoImg = document.createElement('img');\n  logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n    margin-right: 10px;\n  `;\n\n  // OpenMemory titel\n  const title = document.createElement('div');\n  title.textContent = 'OpenMemory';\n  title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: white;\n  `;\n\n  // Create header right section\n  const headerRight = document.createElement('div');\n  headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n    pointer-events: auto;\n  `;\n\n  // Create Add to Prompt button with arrow\n  const addToPromptBtn = document.createElement('button');\n  addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color: white;\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: black;\n  `;\n  addToPromptBtn.textContent = 'Add to Prompt';\n\n  // Add arrow icon to button\n  const arrowIcon = document.createElement('span');\n  arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>\n`;\n  addToPromptBtn.appendChild(arrowIcon);\n\n  // Create settings button\n  const settingsBtn = document.createElement('button');\n  settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n  settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n  // Add click event to open app.mem0.ai in a new tab\n  settingsBtn.addEventListener('click', () => {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n      memoryModalShown = false;\n      currentModalOverlay = null;\n    }\n\n    chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n  });\n\n  // Add hover effect for the settings button\n  settingsBtn.addEventListener('mouseenter', () => {\n    settingsBtn.style.opacity = '1';\n  });\n  settingsBtn.addEventListener('mouseleave', () => {\n    settingsBtn.style.opacity = '0.6';\n  });\n\n  // Content section\n  const contentSection = document.createElement('div');\n  const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n  contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n  // Create memories counter\n  const memoriesCounter = document.createElement('div');\n  memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n  // Update counter text based on loading state and number of memories\n  if (isLoading) {\n    memoriesCounter.textContent = `Loading Relevant Memories...`;\n  } else {\n    memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n  }\n\n  // Calculate max height for memories content based on modal height\n  const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n  // Create memories content container with adjusted height\n  const memoriesContent = document.createElement('div');\n  memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: none;\n    -ms-overflow-style: none;\n  `;\n  memoriesContent.style.cssText += '::-webkit-scrollbar { display: none; }';\n\n  // Track currently expanded memory\n  let currentlyExpandedMemory: HTMLElement | null = null;\n\n  // Function to create skeleton loading items\n  function createSkeletonItems() {\n    memoriesContent.innerHTML = '';\n\n    for (let i = 0; i < memoriesPerPage; i++) {\n      const skeletonItem = document.createElement('div');\n      skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #27272A;\n        border-radius: 8px;\n        height: 52px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n      const skeletonText = document.createElement('div');\n      skeletonText.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n      const skeletonText2 = document.createElement('div');\n      skeletonText2.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n      const skeletonActions = document.createElement('div');\n      skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n      const skeletonButton1 = document.createElement('div');\n      skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      const skeletonButton2 = document.createElement('div');\n      skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      skeletonActions.appendChild(skeletonButton1);\n      skeletonActions.appendChild(skeletonButton2);\n\n      const textContainer = document.createElement('div');\n      textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      textContainer.appendChild(skeletonText);\n      textContainer.appendChild(skeletonText2);\n\n      skeletonItem.appendChild(textContainer);\n      skeletonItem.appendChild(skeletonActions);\n      memoriesContent.appendChild(skeletonItem);\n    }\n\n    // Add keyframe animation to document if not exists\n    if (!document.getElementById('skeleton-animation')) {\n      const style = document.createElement('style');\n      style.id = 'skeleton-animation';\n      style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n      document.head.appendChild(style);\n    }\n  }\n\n  // Navigation section at bottom\n  const navigationSection = document.createElement('div');\n  navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n  // Navigation buttons\n  const prevButton = document.createElement('button');\n  prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  prevButton.style.cssText = `\n    background: #27272A;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  const nextButton = document.createElement('button');\n  nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  nextButton.style.cssText = prevButton.style.cssText;\n\n  // Add click handlers for navigation buttons\n  prevButton.addEventListener('click', () => {\n    // Calculate current page information\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n    // const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    if (currentPage > 1) {\n      currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n      showMemories();\n    }\n  });\n\n  nextButton.addEventListener('click', () => {\n    // Calculate current page information\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n    const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    if (currentPage < totalPages) {\n      currentMemoryIndex = currentMemoryIndex + memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  // Assemble modal\n  headerLeft.appendChild(logoImg);\n  headerLeft.appendChild(title);\n  headerRight.appendChild(addToPromptBtn);\n  headerRight.appendChild(settingsBtn);\n\n  modalHeader.appendChild(headerLeft);\n  modalHeader.appendChild(headerRight);\n\n  contentSection.appendChild(memoriesCounter);\n  contentSection.appendChild(memoriesContent);\n\n  navigationSection.appendChild(prevButton);\n  navigationSection.appendChild(nextButton);\n\n  modalContainer.appendChild(modalHeader);\n  modalContainer.appendChild(contentSection);\n  modalContainer.appendChild(navigationSection);\n\n  modalOverlay.appendChild(modalContainer);\n\n  // Add drag functionality\n  let isDragging = false;\n  const dragOffset = { x: 0, y: 0 };\n\n  modalHeader.addEventListener('mousedown', (e: MouseEvent) => {\n    isDragging = true;\n    const containerRect = modalContainer.getBoundingClientRect();\n    dragOffset.x = e.clientX - containerRect.left;\n    dragOffset.y = e.clientY - containerRect.top;\n\n    modalHeader.style.cursor = 'grabbing';\n    document.addEventListener('mousemove', handleMouseMove);\n    document.addEventListener('mouseup', handleMouseUp);\n\n    e.preventDefault();\n  });\n\n  function handleMouseMove(e: MouseEvent) {\n    if (!isDragging) {\n      return;\n    }\n\n    const newLeft = e.clientX - dragOffset.x;\n    const newTop = e.clientY - dragOffset.y;\n\n    // Keep modal within viewport bounds\n    const maxLeft = window.innerWidth - modalWidth;\n    const maxTop = window.innerHeight - modalHeight;\n\n    const constrainedLeft = Math.max(0, Math.min(newLeft, maxLeft));\n    const constrainedTop = Math.max(0, Math.min(newTop, maxTop));\n\n    modalContainer.style.left = constrainedLeft + 'px';\n    modalContainer.style.top = constrainedTop + 'px';\n\n    // Store the position for future modal recreations\n    modalDragPosition = {\n      left: constrainedLeft,\n      top: constrainedTop,\n    };\n  }\n\n  function handleMouseUp() {\n    isDragging = false;\n    modalHeader.style.cursor = 'move';\n    document.removeEventListener('mousemove', handleMouseMove);\n    document.removeEventListener('mouseup', handleMouseUp);\n  }\n\n  // Append to body\n  document.body.appendChild(modalOverlay);\n\n  // Show initial memories or loading state\n  if (isLoading) {\n    createSkeletonItems();\n  } else {\n    showMemories();\n  }\n\n  // Function to close the modal\n  function closeModal() {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n    }\n    currentModalOverlay = null;\n    memoryModalShown = false;\n    // Reset drag position when modal is truly closed by user action\n    modalDragPosition = null;\n  }\n\n  // Function to show memories\n  function showMemories() {\n    memoriesContent.innerHTML = '';\n\n    if (isLoading) {\n      createSkeletonItems();\n      return;\n    }\n\n    if (memoryItems.length === 0) {\n      showEmptyState(memoriesContent);\n      updateNavigationState(prevButton, nextButton, 0, 0);\n      return;\n    }\n\n    // Use the dynamically set memoriesPerPage value\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n\n    // Calculate total pages and current page\n    const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    // Update navigation buttons state\n    updateNavigationState(prevButton, nextButton, currentPage, totalPages);\n\n    for (let i = 0; i < memoriesToShow; i++) {\n      const memoryIndex = currentMemoryIndex + i;\n      if (memoryIndex >= memoryItems.length) {\n        break;\n      }\n\n      const memory = memoryItems[memoryIndex];\n      if (!memory) {\n        continue;\n      }\n\n      // Skip memories that have been added already\n      if (allMemoriesById.has(String(memory.id))) {\n        continue;\n      }\n\n      // Ensure memory has an ID\n      if (!memory.id) {\n        memory.id = `memory-${Date.now()}-${memoryIndex}`;\n      }\n\n      const memoryContainer = document.createElement('div');\n      memoryContainer.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px; \n        background-color: #27272A;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s ease;\n        min-height: 52px; \n        max-height: 52px; \n        overflow: hidden;\n        flex-shrink: 0;\n      `;\n\n      const memoryText = document.createElement('div');\n      memoryText.style.cssText = `\n        font-size: 14px;\n        line-height: 1.5;\n        color: #D4D4D8;\n        flex-grow: 1;\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        transition: all 0.2s ease;\n        height: 42px;\n      `;\n      memoryText.textContent = memory.memory || memory.text || '';\n\n      // Create remove button (hidden by default)\n      const removeButton = document.createElement('button');\n      removeButton.style.cssText = `\n        display: none;\n        align-items: center;\n        gap: 6px;\n        background:rgba(54, 54, 54, 0.71);\n        color:rgb(199, 199, 201);\n        border-radius: 8px;\n        padding: 2px 4px;\n        border: none;\n        cursor: pointer;\n        font-size: 13px;\n        margin-top: 12px;\n        width: fit-content;\n      `;\n      removeButton.innerHTML = `\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>\n        Remove\n      `;\n\n      // Create content wrapper for text and remove button\n      const contentWrapper = document.createElement('div');\n      contentWrapper.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      contentWrapper.appendChild(memoryText);\n      contentWrapper.appendChild(removeButton);\n\n      const actionsContainer = document.createElement('div');\n      actionsContainer.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n        flex-shrink: 0;\n      `;\n\n      // Add button\n      const addButton = document.createElement('button');\n      addButton.style.cssText = `\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        height: 28px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 100%;\n        transition: all 0.2s ease;\n      `;\n\n      addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      </svg>`;\n\n      // Add click handler for add button\n      addButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        sendExtensionEvent('memory_injection', {\n          provider: 'deepseek',\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          browser: getBrowser(),\n          injected_all: false,\n          memory_id: memory.id,\n        });\n\n        // Add this memory\n        allMemoriesById.add(String(memory.id));\n        allMemories.push(String(memory.memory || memory.text || ''));\n        updateInputWithMemories();\n\n        // Remove this memory from the list\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      // Menu button (more options)\n      const menuButton = document.createElement('button');\n      menuButton.style.cssText = `\n        background: none;\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        color: #A1A1AA;\n      `;\n      menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n        <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n      </svg>`;\n\n      // Track expanded state\n      let isExpanded = false;\n\n      // Function to expand memory\n      const expandMemoryHandler = () => {\n        expandMemory(\n          memoryContainer,\n          memoryText,\n          contentWrapper,\n          removeButton,\n          currentlyExpandedMemory,\n          memoriesContent\n        );\n      };\n\n      // Function to collapse memory\n      const collapseMemoryHandler = () => {\n        collapseMemory(memoryContainer, memoryText, contentWrapper, removeButton);\n      };\n\n      // Add collapse event listener\n      memoryContainer.addEventListener('collapse', collapseMemoryHandler);\n\n      // Add click handler for the menu button\n      menuButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        if (isExpanded) {\n          collapseMemoryHandler();\n        } else {\n          expandMemoryHandler();\n        }\n      });\n\n      // Add click handler for remove button\n      removeButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        // Remove from memoryItems\n        const index = memoryItems.findIndex(m => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n          // const newTotalPages = Math.ceil(memoryItems.length / memoriesPerPage);\n\n          // If we're on the last page and it's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      actionsContainer.appendChild(addButton);\n      actionsContainer.appendChild(menuButton);\n\n      memoryContainer.appendChild(contentWrapper);\n      memoryContainer.appendChild(actionsContainer);\n      memoriesContent.appendChild(memoryContainer);\n\n      // Add hover effect\n      memoryContainer.addEventListener('mouseenter', () => {\n        memoryContainer.style.backgroundColor = isExpanded ? '#1C1C1E' : '#323232';\n      });\n\n      memoryContainer.addEventListener('mouseleave', () => {\n        memoryContainer.style.backgroundColor = isExpanded ? '#1C1C1E' : '#27272A';\n      });\n\n      // Add click handler to expand/collapse when clicking on memory\n      memoryContainer.addEventListener('click', () => {\n        if (isExpanded) {\n          collapseMemoryHandler();\n        } else {\n          expandMemoryHandler();\n        }\n      });\n    }\n  }\n\n  // Update Add to Prompt button click handler\n  addToPromptBtn.addEventListener('click', () => {\n    // Only add memories that are not already added\n    const newMemories = memoryItems\n      .filter(memory => !allMemoriesById.has(String(memory.id)))\n      .map(memory => {\n        allMemoriesById.add(String(memory.id));\n        return String(memory.memory || memory.text || '');\n      });\n\n    sendExtensionEvent('memory_injection', {\n      provider: 'deepseek',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n      injected_all: true,\n      memory_count: newMemories.length,\n    });\n    // Add all new memories to allMemories\n    allMemories.push(...newMemories);\n\n    // Update the input with all memories\n    if (allMemories.length > 0) {\n      updateInputWithMemories();\n      closeModal();\n    } else {\n      // If no new memories were added but we have existing ones, just close\n      if (allMemoriesById.size > 0) {\n        closeModal();\n      }\n    }\n  });\n}\n\n// Function to show empty state with specific container\nfunction showEmptyState(container: HTMLElement) {\n  if (!container) {\n    return;\n  }\n\n  container.innerHTML = '';\n\n  const emptyContainer = document.createElement('div');\n  emptyContainer.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    padding: 32px 16px;\n    text-align: center;\n    flex: 1;\n    min-height: 200px;\n  `;\n\n  const emptyIcon = document.createElement('div');\n  emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#71717A\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  emptyIcon.style.marginBottom = '16px';\n\n  const emptyText = document.createElement('div');\n  emptyText.textContent = 'No relevant memories found';\n  emptyText.style.cssText = `\n    color: #71717A;\n    font-size: 14px;\n    font-weight: 500;\n  `;\n\n  emptyContainer.appendChild(emptyIcon);\n  emptyContainer.appendChild(emptyText);\n  container.appendChild(emptyContainer);\n}\n\n// Update navigation button states with specific buttons\nfunction updateNavigationState(\n  prevButton: HTMLButtonElement,\n  nextButton: HTMLButtonElement,\n  currentPage: number,\n  totalPages: number\n) {\n  if (!prevButton || !nextButton) {\n    return;\n  }\n\n  if (totalPages === 0) {\n    prevButton.disabled = true;\n    prevButton.style.opacity = '0.5';\n    prevButton.style.cursor = 'not-allowed';\n    nextButton.disabled = true;\n    nextButton.style.opacity = '0.5';\n    nextButton.style.cursor = 'not-allowed';\n    return;\n  }\n\n  if (currentPage <= 1) {\n    prevButton.disabled = true;\n    prevButton.style.opacity = '0.5';\n    prevButton.style.cursor = 'not-allowed';\n  } else {\n    prevButton.disabled = false;\n    prevButton.style.opacity = '1';\n    prevButton.style.cursor = 'pointer';\n  }\n\n  if (currentPage >= totalPages) {\n    nextButton.disabled = true;\n    nextButton.style.opacity = '0.5';\n    nextButton.style.cursor = 'not-allowed';\n  } else {\n    nextButton.disabled = false;\n    nextButton.style.opacity = '1';\n    nextButton.style.cursor = 'pointer';\n  }\n}\n\n// Function to apply memories to the input field\nfunction updateInputWithMemories(): void {\n  const inputElement = getInputElement();\n\n  if (inputElement && allMemories.length > 0) {\n    // Get the content without any existing memory wrappers\n    const baseContent = getContentWithoutMemories();\n\n    // Create the memory wrapper with all collected memories\n    let memoriesContent = '\\n\\n' + OPENMEMORY_PROMPTS.memory_header_text + '\\n';\n    // Add all memories to the content\n    allMemories.forEach(mem => {\n      memoriesContent += `- ${mem}\\n`;\n    });\n\n    // Add the final content to the input\n    (inputElement as HTMLTextAreaElement).value = `${baseContent}${memoriesContent}`;\n    (inputElement as HTMLElement).dispatchEvent(new Event('input', { bubbles: true }));\n    (inputElement as HTMLElement).focus();\n  }\n}\n\n// Function to get the content without any memory wrappers\nfunction getContentWithoutMemories(): string {\n  const inputElement = getInputElement();\n  if (!inputElement) {\n    return '';\n  }\n\n  let content =\n    (inputElement as HTMLDivElement).textContent ||\n    (inputElement as HTMLTextAreaElement).value ||\n    '';\n\n  // Remove memories section\n  content = content.replace(/\\n\\nHere is some of my memories[\\s\\S]*$/, '');\n\n  return content.trim();\n}\n\n// Function to handle the Mem0 modal\nasync function handleMem0Modal(sourceButtonId: string | null = null): Promise<void> {\n  try {\n    // First check if memory is enabled (user is logged in)\n    const memoryEnabled = await getMemoryEnabledState();\n    if (!memoryEnabled) {\n      // User is not logged in, show login modal\n      showLoginModal();\n      return;\n    }\n\n    // Get current input text\n    const message = getInputElementValue();\n\n    // If no message, show a guidance popover and return\n    if (!message || message.trim() === '') {\n      showGuidancePopover();\n      return;\n    }\n\n    if (isProcessingMem0) {\n      return;\n    }\n\n    isProcessingMem0 = true;\n\n    // Show the loading modal immediately\n    createMemoryModal([], true, sourceButtonId);\n\n    try {\n      const auth = await getAuthDetails();\n      if (!auth.apiKey && !auth.accessToken) {\n        isProcessingMem0 = false;\n        showLoginModal();\n        return;\n      }\n\n      sendExtensionEvent('modal_clicked', {\n        provider: 'deepseek',\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        browser: getBrowser(),\n      });\n      currentModalSourceButtonId = sourceButtonId;\n      deepseekSearch.runImmediate(message);\n\n      addMemory(message).catch(() => {\n        // Ignore errors\n      });\n    } catch {\n      // Error in handleMem0Modal\n      createMemoryModal([], false, sourceButtonId);\n    } finally {\n      isProcessingMem0 = false;\n    }\n  } catch {\n    isProcessingMem0 = false;\n  }\n}\n\n// Function to show a guidance popover when input is empty\nfunction showGuidancePopover(): void {\n  // First remove any existing popovers\n  const existingPopover = document.getElementById('mem0-guidance-popover');\n  if (existingPopover) {\n    document.body.removeChild(existingPopover);\n  }\n\n  // Get the Mem0 button to position relative to it\n  const mem0Button = document.getElementById('mem0-icon-button');\n  if (!mem0Button) {\n    return;\n  }\n\n  const buttonRect = mem0Button.getBoundingClientRect();\n\n  // Create the popover\n  const popover = document.createElement('div');\n  popover.id = 'mem0-guidance-popover';\n  popover.style.cssText = `\n    position: fixed;\n    background-color: #1C1C1E;\n    color: white;\n    padding: 12px 16px;\n    border-radius: 8px;\n    font-size: 14px;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    z-index: 10002;\n    max-width: 250px;\n    border: 1px solid #383838;\n    top: ${buttonRect.bottom + 10}px;\n    left: ${buttonRect.left - 110}px;\n  `;\n\n  // Add content to the popover\n  popover.innerHTML = `\n    <div style=\"font-weight: 600; margin-bottom: 8px; color: #F8FAFF;\">No Input Detected</div>\n    <div style=\"color: #D4D4D8; line-height: 1.4;\">\n      Please type your message in the input field first to add or search memories.\n    </div>\n  `;\n\n  // Add close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 8px;\n    right: 8px;\n    background: none;\n    border: none;\n    color: #A1A1AA;\n    cursor: pointer;\n    padding: 4px;\n    line-height: 1;\n  `;\n  closeButton.innerHTML = `\n    <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" fill=\"none\">\n      <path d=\"M18 6L6 18M6 6l12 12\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>\n  `;\n  closeButton.addEventListener('click', () => {\n    if (document.body.contains(popover)) {\n      document.body.removeChild(popover);\n    }\n  });\n\n  // Add arrow\n  const arrow = document.createElement('div');\n  arrow.style.cssText = `\n    position: absolute;\n    top: -6px;\n    left: 120px;\n    width: 12px;\n    height: 12px;\n    background: #1C1C1E;\n    transform: rotate(45deg);\n    border-left: 1px solid #383838;\n    border-top: 1px solid #383838;\n  `;\n\n  popover.appendChild(closeButton);\n  popover.appendChild(arrow);\n  document.body.appendChild(popover);\n\n  // Auto-close after 5 seconds\n  setTimeout(() => {\n    if (document.body.contains(popover)) {\n      document.body.removeChild(popover);\n    }\n  }, 5000);\n}\n\n// Function to show login modal\nfunction showLoginModal(): void {\n  // First check if modal already exists\n  if (document.getElementById('mem0-login-popup')) {\n    return;\n  }\n\n  // Create popup overlay\n  const popupOverlay = document.createElement('div');\n  popupOverlay.id = 'mem0-login-popup';\n  popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 100000;\n  `;\n\n  // Create popup container\n  const popupContainer = document.createElement('div');\n  popupContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  `;\n\n  // Close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #A1A1AA;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n  closeButton.innerHTML = '&times;';\n  closeButton.addEventListener('click', () => {\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Logo and heading\n  const logoContainer = document.createElement('div');\n  logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n  const logo = document.createElement('img');\n  logo.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logo.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const logoDark = document.createElement('img');\n  logoDark.src = chrome.runtime.getURL('icons/mem0-icon-black.png');\n  logoDark.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const heading = document.createElement('h2');\n  heading.textContent = 'Sign in to OpenMemory';\n  heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 500;\n  `;\n\n  logoContainer.appendChild(heading);\n\n  // Message\n  const message = document.createElement('p');\n  message.textContent =\n    'Please sign in to access your memories and personalize your conversations!';\n  message.style.cssText = `\n    margin-bottom: 24px;\n    color: #D4D4D8;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n  // Sign in button\n  const signInButton = document.createElement('button');\n  signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: white;\n    color: black;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  // Add text in span for better centering\n  const signInText = document.createElement('span');\n  signInText.textContent = 'Sign in with Mem0';\n\n  signInButton.appendChild(logoDark);\n  signInButton.appendChild(signInText);\n\n  signInButton.addEventListener('mouseenter', () => {\n    signInButton.style.backgroundColor = '#f5f5f5';\n  });\n\n  signInButton.addEventListener('mouseleave', () => {\n    signInButton.style.backgroundColor = 'white';\n  });\n\n  // Open sign-in page when clicked\n  signInButton.addEventListener('click', () => {\n    // Send message to background script to handle authentication\n    try {\n      chrome.runtime.sendMessage({ action: SidebarAction.SHOW_LOGIN_POPUP });\n    } catch {\n      // Ignore errors\n    }\n    // Fallback: open the login page directly\n    window.open('https://app.mem0.ai/login', '_blank');\n\n    // Close the modal\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Assemble popup\n  popupContainer.appendChild(logoContainer);\n  popupContainer.appendChild(message);\n  popupContainer.appendChild(signInButton);\n\n  popupOverlay.appendChild(popupContainer);\n  popupOverlay.appendChild(closeButton);\n\n  // Add click event to close when clicking outside\n  popupOverlay.addEventListener('click', (e: MouseEvent) => {\n    if (e.target === popupOverlay) {\n      document.body.removeChild(popupOverlay);\n    }\n  });\n\n  // Add to body\n  document.body.appendChild(popupOverlay);\n}\n\n// Function to add the Mem0 icon button - enhanced with error handling and return status\nfunction addMem0IconButton() {\n  try {\n    // Prefer OPENMEMORY_UI mounts; fall back to legacy injection only if unavailable\n    if (OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus) {\n      try {\n        if (!document.getElementById('mem0-icon-button')) {\n          OPENMEMORY_UI.resolveCachedAnchor(\n            { learnKey: location.host + ':' + location.pathname },\n            null,\n            24 * 60 * 60 * 1000\n          ).then(function (hit: { el: Element; placement: Placement | null } | null) {\n            if (!hit || !hit.el) {\n              return;\n            }\n            let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n            let host = hs.host,\n              shadow = hs.shadow;\n            host.id = 'mem0-icon-button';\n            let cfg =\n              typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.deepseek\n                ? SITE_CONFIG.deepseek\n                : null;\n            let placement = hit.placement ||\n              (cfg && cfg.placement) || {\n                strategy: 'float',\n                placement: 'right-center',\n                gap: 12,\n              };\n            OPENMEMORY_UI.applyPlacement({ container: host, anchor: hit.el, placement: placement });\n            let style = document.createElement('style');\n            style.textContent = `\n                :host { position: relative; }\n                .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n                .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n                .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n                :host([data-has-text=\"1\"]) .dot { display:block; }\n              `;\n            let btn = document.createElement('button');\n            btn.className = 'mem0-btn';\n            let img = document.createElement('img');\n            img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n            let dot = document.createElement('div');\n            dot.className = 'dot';\n            btn.appendChild(img);\n            shadow.append(style, btn, dot);\n            btn.addEventListener('click', function () {\n              handleMem0Modal('mem0-icon-button');\n            });\n            if (typeof updateNotificationDot === 'function') {\n              setTimeout(updateNotificationDot, 0);\n            }\n          });\n        }\n      } catch {\n        // Ignore errors during re-initialization\n      }\n\n      OPENMEMORY_UI.mountOnEditorFocus({\n        existingHostSelector: '#mem0-icon-button',\n        editorSelector:\n          typeof SITE_CONFIG !== 'undefined' &&\n          SITE_CONFIG.deepseek &&\n          SITE_CONFIG.deepseek.editorSelector\n            ? SITE_CONFIG.deepseek.editorSelector\n            : 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n        deriveAnchor:\n          typeof SITE_CONFIG !== 'undefined' &&\n          SITE_CONFIG.deepseek &&\n          typeof SITE_CONFIG.deepseek.deriveAnchor === 'function'\n            ? SITE_CONFIG.deepseek.deriveAnchor\n            : function (editor: Element) {\n                return editor.closest('form') || editor.parentElement;\n              },\n        placement:\n          typeof SITE_CONFIG !== 'undefined' &&\n          SITE_CONFIG.deepseek &&\n          SITE_CONFIG.deepseek.placement\n            ? SITE_CONFIG.deepseek.placement\n            : { strategy: 'float', placement: 'right-center', gap: 12 },\n        render: function (shadow: ShadowRoot, host: HTMLElement) {\n          host.id = 'mem0-icon-button';\n          let style = document.createElement('style');\n          style.textContent = `\n            :host { position: relative; }\n            .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n            .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n            .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n            :host([data-has-text=\"1\"]) .dot { display:block; }\n          `;\n          let btn = document.createElement('button');\n          btn.className = 'mem0-btn';\n          let img = document.createElement('img');\n          img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n          let dot = document.createElement('div');\n          dot.className = 'dot';\n          btn.appendChild(img);\n          shadow.append(style, btn, dot);\n          btn.addEventListener('click', function () {\n            handleMem0Modal('mem0-icon-button');\n          });\n          if (typeof updateNotificationDot === 'function') {\n            setTimeout(updateNotificationDot, 0);\n          }\n        },\n        fallback: function () {\n          let cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.deepseek\n              ? SITE_CONFIG.deepseek\n              : null;\n          return OPENMEMORY_UI.mountResilient({\n            anchors: [\n              {\n                find: function () {\n                  let sel =\n                    (cfg && cfg.editorSelector) ||\n                    'textarea, [contenteditable=\"true\"], input[type=\"text\"]';\n                  let ed = document.querySelector(sel);\n                  if (!ed) {\n                    return null;\n                  }\n                  try {\n                    return cfg && typeof cfg.deriveAnchor === 'function'\n                      ? cfg.deriveAnchor(ed)\n                      : ed.closest('form') || ed.parentElement;\n                  } catch (_) {\n                    return ed.closest('form') || ed.parentElement;\n                  }\n                },\n              },\n            ],\n            placement: (cfg && cfg.placement) || {\n              strategy: 'float',\n              placement: 'right-center',\n              gap: 12,\n            },\n            enableFloatingFallback: true,\n            render: function (shadow: ShadowRoot, host: HTMLElement) {\n              host.id = 'mem0-icon-button';\n              let style = document.createElement('style');\n              style.textContent = `\n                :host { position: relative; }\n                .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n                .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n                .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n                :host([data-has-text=\"1\"]) .dot { display:block; }\n              `;\n              let btn = document.createElement('button');\n              btn.className = 'mem0-btn';\n              let img = document.createElement('img');\n              img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n              let dot = document.createElement('div');\n              dot.className = 'dot';\n              btn.appendChild(img);\n              shadow.append(style, btn, dot);\n              btn.addEventListener('click', function () {\n                handleMem0Modal('mem0-icon-button');\n              });\n              if (typeof updateNotificationDot === 'function') {\n                setTimeout(updateNotificationDot, 0);\n              }\n            },\n          });\n        },\n      });\n      return { success: true, status: 'openmemory_ui_mount' };\n    }\n    // Check if memory is enabled before adding the button\n    getMemoryEnabledState()\n      .then(memoryEnabled => {\n        if (!memoryEnabled) {\n          removeMem0IconButton();\n          return;\n        }\n\n        // Continue with button creation if memory is enabled\n        createAndAddButton();\n      })\n      .catch(() => {\n        // If we can't check memory state, don't add the button\n      });\n\n    return { success: true, status: 'checking_memory_state' };\n  } catch {\n    return { success: false, status: 'unexpected_error', error: 'Unknown error' };\n  }\n\n  // Helper function to create and add the button\n  function createAndAddButton() {\n    // Check if the button already exists\n    if (document.querySelector('#mem0-icon-button')) {\n      return { success: true, status: 'already_exists' };\n    }\n\n    // Wait for input element to be available before trying to add the button\n    const inputElement = getInputElement();\n    if (!inputElement) {\n      // Retry in 1 second\n      setTimeout(addMem0IconButton, 1000);\n      return { success: false, status: 'no_input_element' };\n    }\n\n    // Try multiple approaches to find placement locations\n    let searchButton = null;\n    let buttonContainer = null;\n    let status = 'searching';\n\n    // Approach 1: Look for the search button by class and specific selectors\n    searchButton = document.querySelector('div[role=\"button\"] .ds-button__icon + span');\n    if (searchButton && (searchButton.textContent || '').trim().toLowerCase() === 'search') {\n      const parentBtn = searchButton.closest('div[role=\"button\"]');\n      buttonContainer = parentBtn ? (parentBtn.parentElement as HTMLElement) : null;\n      if (buttonContainer) {\n        status = 'found_search_button';\n      }\n    } else {\n      // Try alternative selector\n      const allButtons = document.querySelectorAll('div[role=\"button\"]');\n      Array.from(allButtons).some(btn => {\n        if ((btn.textContent || '').trim().toLowerCase() === 'search') {\n          searchButton = btn.querySelector('span');\n          buttonContainer = (btn as HTMLElement).parentElement as HTMLElement;\n          status = 'found_search_button_alt';\n          return true;\n        }\n        return false;\n      });\n    }\n\n    // Approach 2: Look for any toolbar or button container\n    if (!buttonContainer) {\n      const toolbars = document.querySelectorAll('.toolbar, .button-container, .controls');\n      if (toolbars.length > 0) {\n        buttonContainer = toolbars[0];\n        status = 'found_toolbar';\n      }\n    }\n\n    // Approach 3: Try to find the input field and place it near there\n    if (!buttonContainer) {\n      if (inputElement && inputElement.parentElement) {\n        // Try going up a few levels to find a good container\n        let parent: HTMLElement = inputElement.parentElement as HTMLElement;\n        let level = 0;\n        while (parent && level < 3) {\n          const buttons = parent.querySelectorAll('div[role=\"button\"]');\n          if (buttons.length > 0) {\n            buttonContainer = parent;\n            status = 'found_input_parent_with_buttons';\n            break;\n          }\n          parent = parent.parentElement as HTMLElement;\n          level++;\n        }\n\n        // If still not found, use direct parent\n        if (!buttonContainer) {\n          buttonContainer = inputElement.parentElement;\n          status = 'found_input_parent';\n        }\n      }\n    }\n\n    // Approach 4: Look for a div with role=\"toolbar\"\n    if (!buttonContainer) {\n      const toolbars = document.querySelectorAll('div[role=\"toolbar\"]');\n      if (toolbars.length > 0) {\n        buttonContainer = toolbars[0];\n        status = 'found_role_toolbar';\n      }\n    }\n\n    // If we couldn't find a suitable container, create one near the input\n    if (!buttonContainer && inputElement) {\n      buttonContainer = document.createElement('div');\n      buttonContainer.id = 'mem0-custom-container';\n      buttonContainer.style.cssText = `\n        display: flex;\n        position: absolute;\n        top: ${inputElement.getBoundingClientRect().top - 40}px;\n        left: ${inputElement.getBoundingClientRect().right - 100}px;\n        z-index: 1000;\n      `;\n      document.body.appendChild(buttonContainer);\n      status = 'created_custom_container';\n    }\n\n    // If we couldn't find a suitable container, bail out\n    if (!buttonContainer) {\n      return { success: false, status: 'no_container' };\n    }\n\n    // Remove existing button if any\n    const existingButton = document.querySelector('#mem0-icon-button');\n    if (existingButton) {\n      try {\n        if (existingButton.parentElement) {\n          existingButton.parentElement.removeChild(existingButton);\n        }\n      } catch {\n        // Ignore errors\n      }\n    }\n\n    // Create button container\n    const mem0ButtonContainer = document.createElement('div');\n    mem0ButtonContainer.style.cssText = `\n      display: inline-flex;\n      position: relative;\n      margin: 0 4px;\n      align-items: center;\n    `;\n\n    // Create notification dot\n    const notificationDot = document.createElement('div');\n    notificationDot.id = 'mem0-notification-dot';\n    notificationDot.style.cssText = `\n      position: absolute;\n      top: -3px;\n      right: -3px;\n      width: 8px;\n      height: 8px;\n      background-color: rgb(128, 221, 162);\n      border-radius: 50%;\n      border: 1px solid #1C1C1E;\n      display: none;\n      z-index: 1001;\n      pointer-events: none;\n    `;\n\n    // Add keyframe animation for the dot\n    if (!document.getElementById('notification-dot-animation')) {\n      try {\n        const style = document.createElement('style');\n        style.id = 'notification-dot-animation';\n        style.innerHTML = `\n          @keyframes popIn {\n            0% { transform: scale(0); }\n            50% { transform: scale(1.2); }\n            100% { transform: scale(1); }\n          }\n          \n          #mem0-notification-dot.active {\n            display: block !important;\n            animation: popIn 0.3s ease-out forwards;\n          }\n        `;\n        document.head.appendChild(style);\n      } catch {\n        // Ignore errors\n      }\n    }\n\n    // Create the button to match DeepSeek style\n    const mem0Button = document.createElement('div');\n    mem0Button.id = 'mem0-icon-button';\n    mem0Button.setAttribute('role', 'button');\n    mem0Button.className = 'ds-button ds-button--rect ds-button--m';\n    mem0Button.tabIndex = 0 as number;\n    mem0Button.style.cssText = `\n      cursor: pointer;\n      height: 30px;\n      display: inline-flex;\n      margin-left: -2px;\n      align-items: center;\n      padding: 0px 6px;\n      border: 1px solid rgb(95, 95, 95);\n      border-radius: 16px;\n      background-color: rgba(255, 255, 255, 0.0);\n      transition: background-color 0.2s;\n    `;\n\n    // Rest of the existing button creation code...\n    // Create the icon container\n    const iconContainer = document.createElement('div');\n    iconContainer.className = 'ds-button__icon';\n    iconContainer.style.cssText = `\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      margin-right: 2px;\n    `;\n\n    // Create the icon\n    const icon = document.createElement('img');\n    icon.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n    icon.style.cssText = `\n      width: 14px;\n      height: 14px;\n      border-radius: 100%;\n    `;\n\n    // Create button text\n    const buttonText = document.createElement('span');\n    buttonText.style.cssText = `\n      color: #F8FAFF;\n      font-size: 12px;\n    `;\n    buttonText.textContent = 'Memory';\n\n    // Create tooltip with improved stability\n    const tooltip = document.createElement('div');\n    tooltip.className = 'mem0-tooltip';\n    tooltip.style.cssText = `\n      position: absolute;\n      bottom: 40px;\n      left: 50%;\n      transform: translateX(-50%);\n      background-color: #1C1C1E;\n      color: white;\n      padding: 6px 10px;\n      border-radius: 6px;\n      font-size: 12px;\n      white-space: nowrap;\n      z-index: 10001;\n      display: none;\n      transition: opacity 0.2s;\n      opacity: 0;\n      pointer-events: none;\n    `;\n    tooltip.textContent = 'Add memories to your prompt';\n\n    // Add arrow to tooltip\n    const arrow = document.createElement('div');\n    arrow.className = 'mem0-tooltip-arrow';\n    arrow.style.cssText = `\n      position: absolute;\n      top: 100%;\n      left: 50%;\n      transform: translateX(-50%) rotate(45deg);\n      width: 8px;\n      height: 8px;\n      background-color: #1C1C1E;\n      pointer-events: none;\n    `;\n    tooltip.appendChild(arrow);\n\n    // Show/hide tooltip using more stable approach with a data attribute\n    let tooltipVisible = false;\n\n    mem0Button.addEventListener('mouseenter', () => {\n      if (tooltipVisible) {\n        return;\n      }\n      tooltipVisible = true;\n\n      tooltip.style.display = 'block';\n      requestAnimationFrame(() => {\n        tooltip.style.opacity = '1';\n        mem0Button.style.backgroundColor = '#424451';\n      });\n    });\n\n    mem0Button.addEventListener('mouseleave', () => {\n      if (!tooltipVisible) {\n        return;\n      }\n      tooltipVisible = false;\n\n      tooltip.style.opacity = '0';\n      setTimeout(() => {\n        if (!tooltipVisible) {\n          tooltip.style.display = 'none';\n          mem0Button.style.backgroundColor = 'rgba(41, 41, 46, 0.5)';\n        }\n      }, 200);\n    });\n\n    // Add click event to open memories modal - also check memory state again\n    mem0Button.addEventListener('click', async () => {\n      try {\n        const memoryEnabled = await getMemoryEnabledState();\n        if (memoryEnabled) {\n          await handleMem0Modal('mem0-icon-button');\n        } else {\n          // Show login modal for non-logged in users\n          showLoginModal();\n\n          // Remove the button since memory is disabled\n          removeMem0IconButton();\n        }\n      } catch {\n        showLoginModal();\n      }\n    });\n\n    // Assemble the button\n    iconContainer.appendChild(icon);\n    mem0Button.appendChild(iconContainer);\n    mem0Button.appendChild(buttonText);\n    mem0Button.appendChild(notificationDot);\n    mem0ButtonContainer.appendChild(mem0Button);\n    mem0ButtonContainer.appendChild(tooltip);\n\n    // Insert the button in the appropriate position\n    try {\n      if (status === 'found_search_button' || status === 'found_search_button_alt') {\n        // Position after the search button (to the right)\n        const searchButtonParent = searchButton ? searchButton.closest('div[role=\"button\"]') : null;\n        if (searchButtonParent && searchButtonParent.nextSibling) {\n          buttonContainer.insertBefore(mem0ButtonContainer, searchButtonParent.nextSibling);\n        } else {\n          buttonContainer.appendChild(mem0ButtonContainer);\n        }\n      } else if (status === 'found_toolbar' || status === 'found_role_toolbar') {\n        // Find an appropriate position in the toolbar - prefer the right side\n        const lastChild = buttonContainer.lastChild;\n        if (lastChild) {\n          buttonContainer.insertBefore(mem0ButtonContainer, null); // append to end\n        } else {\n          buttonContainer.appendChild(mem0ButtonContainer);\n        }\n      } else if (status === 'created_custom_container') {\n        // Custom container - just append\n        buttonContainer.appendChild(mem0ButtonContainer);\n      } else {\n        // Other cases - try to position after any buttons in the container\n        const buttons = buttonContainer.querySelectorAll('div[role=\"button\"]');\n        if (buttons.length > 0) {\n          const lastButton = buttons[buttons.length - 1];\n          buttonContainer.insertBefore(\n            mem0ButtonContainer,\n            lastButton && lastButton.nextSibling ? lastButton.nextSibling : null\n          );\n        } else {\n          buttonContainer.appendChild(mem0ButtonContainer);\n        }\n      }\n\n      // Only log the first time, not on subsequent calls\n      if (!window.mem0ButtonAdded) {\n        window.mem0ButtonAdded = true;\n      }\n    } catch {\n      return { success: false, status: 'insert_failed', error: 'Insert failed' };\n    }\n\n    // Update notification dot based on input content\n    try {\n      updateNotificationDot();\n    } catch {\n      // Ignore errors\n    }\n\n    return { success: true, status: status };\n  }\n}\n\n// Function to update the notification dot\nfunction updateNotificationDot() {\n  const inputElement = getInputElement();\n  const notificationDot = document.querySelector('#mem0-notification-dot');\n\n  if (inputElement && notificationDot) {\n    // Function to check if input has text\n    const checkForText = () => {\n      const inputText = inputElement.value || '';\n      const hasText = inputText.trim() !== '';\n\n      if (hasText) {\n        notificationDot.classList.add('active');\n        notificationDot.style.display = 'block';\n      } else {\n        notificationDot.classList.remove('active');\n        notificationDot.style.display = 'none';\n      }\n    };\n\n    // Set up an observer to watch for changes to the input field\n    const inputObserver = new MutationObserver(checkForText);\n\n    // Start observing the input element\n    inputObserver.observe(inputElement, {\n      attributes: true,\n      attributeFilter: ['value'],\n    });\n\n    if (!inputElement.dataset.deepseekNotificationHooked) {\n      inputElement.dataset.deepseekNotificationHooked = 'true';\n      inputElement.addEventListener('input', checkForText);\n    }\n\n    // Initial check\n    checkForText();\n  }\n}\n\n// Add a function to clear memories after sending a message\nfunction addSendButtonListener(): void {\n  // Get all potential buttons\n  const allButtons = document.querySelectorAll('div[role=\"button\"]');\n\n  // Log details of each button for debugging\n  Array.from(allButtons).forEach(() => {\n    // Debug logging\n  });\n\n  // Try to get the send button\n  const sendButton = getSendButtonElement();\n\n  if (sendButton) {\n    if (!sendButton.dataset.mem0Listener) {\n      sendButton.dataset.mem0Listener = 'true';\n      sendButton.addEventListener('click', function () {\n        // Clear all memories after sending\n        setTimeout(() => {\n          allMemories = [];\n          allMemoriesById.clear();\n        }, 100);\n      });\n    } else {\n      // Button already has listener\n    }\n  } else {\n    // No send button found\n  }\n}\n\n// Call the initialization function\ninitializeMem0Integration();\n"
  },
  {
    "path": "src/direct-url-tracker.ts",
    "content": "import { type ApiMemoryRequest, DEFAULT_USER_ID, MessageRole, SOURCE } from './types/api';\nimport type { OnCommittedDetails } from './types/browser';\nimport { Category, Provider } from './types/providers';\nimport type { Settings } from './types/settings';\nimport { StorageKey } from './types/storage';\n\nfunction getSettings(): Promise<Settings> {\n  return new Promise(resolve => {\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.USER_ID,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.MEMORY_ENABLED,\n      ],\n      d => {\n        resolve({\n          hasCreds: Boolean(d[StorageKey.API_KEY] || d[StorageKey.ACCESS_TOKEN]),\n          apiKey: d[StorageKey.API_KEY],\n          accessToken: d[StorageKey.ACCESS_TOKEN],\n          userId: d[StorageKey.USER_ID] || DEFAULT_USER_ID,\n          orgId: d[StorageKey.SELECTED_ORG],\n          projectId: d[StorageKey.SELECTED_PROJECT],\n          memoryEnabled: d[StorageKey.MEMORY_ENABLED] !== false,\n        });\n      }\n    );\n  });\n}\n\nasync function addMemory(content: string, settings: Settings, pageUrl: string): Promise<boolean> {\n  const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n  if (settings.accessToken) {\n    headers.Authorization = `Bearer ${settings.accessToken}`;\n  } else if (settings.apiKey) {\n    headers.Authorization = `Token ${settings.apiKey}`;\n  } else {\n    throw new Error('Missing credentials');\n  }\n\n  const body: ApiMemoryRequest = {\n    messages: [{ role: MessageRole.User, content }],\n    user_id: settings.userId,\n    metadata: {\n      provider: Provider.DirectURL,\n      category: Category.NAVIGATION,\n      page_url: pageUrl || '',\n    },\n    source: SOURCE,\n  };\n  if (settings.orgId) {\n    body.org_id = settings.orgId;\n  }\n  if (settings.projectId) {\n    body.project_id = settings.projectId;\n  }\n\n  const controller = new AbortController();\n  const timeout = setTimeout(() => controller.abort(), 10000);\n  try {\n    const res = await fetch('https://api.mem0.ai/v1/memories/', {\n      method: 'POST',\n      headers,\n      body: JSON.stringify(body),\n      signal: controller.signal,\n    });\n    return res.ok;\n  } finally {\n    clearTimeout(timeout);\n  }\n}\n\nfunction shouldTrackTyped(details: OnCommittedDetails): boolean {\n  if (!details || details.frameId !== 0) {\n    return false;\n  }\n  const url = details.url || '';\n  if (!/^https?:\\/\\//i.test(url)) {\n    return false;\n  }\n  const type = details.transitionType || '';\n  const qualifiers = details.transitionQualifiers || [];\n  if (type === 'typed') {\n    return true;\n  }\n  if (qualifiers && qualifiers.includes('from_address_bar')) {\n    return true;\n  }\n  return false;\n}\n\nexport function initDirectUrlTracking(): void {\n  try {\n    chrome.webNavigation.onCommitted.addListener(async (details: OnCommittedDetails) => {\n      try {\n        if (!shouldTrackTyped(details)) {\n          return;\n        }\n        const url = details.url;\n        if (!url) {\n          return;\n        }\n        if (isSearchResultsUrl(url)) {\n          return;\n        }\n\n        const settings = await getSettings();\n        if (!settings.hasCreds || settings.memoryEnabled === false) {\n          return;\n        }\n        // Gate by track_searches toggle (default OFF  if undefined). We treat typed URL as part of tracking searches/history.\n        const allow = await new Promise<boolean>(resolve => {\n          try {\n            chrome.storage.sync.get([StorageKey.TRACK_SEARCHES], d => {\n              resolve(d[StorageKey.TRACK_SEARCHES] === true);\n            });\n          } catch {\n            resolve(false);\n          }\n        });\n        if (!allow) {\n          return;\n        }\n\n        const hostname = (() => {\n          try {\n            return new URL(url).hostname;\n          } catch {\n            return '';\n          }\n        })();\n        const ts = formatTimestamp();\n        const content = `User visited ${url}${hostname ? ` (${hostname})` : ''} on ${ts.date} at ${ts.time}`;\n        await addMemory(content, settings, url);\n      } catch {\n        // no-op\n      }\n    });\n  } catch {\n    // no-op\n  }\n}\n\nfunction isSearchResultsUrl(urlString: string): boolean {\n  try {\n    const u = new URL(urlString);\n    const host = u.hostname || '';\n    const path = u.pathname || '';\n    const params = u.searchParams || new URLSearchParams();\n\n    if (\n      (/^.*\\.google\\.[^\\\\/]+$/.test(host) ||\n        host === 'google.com' ||\n        host.endsWith('.google.com')) &&\n      path.startsWith('/search')\n    ) {\n      if (params.get('q')) {\n        return true;\n      }\n    }\n    if (host.endsWith('bing.com') && (path === '/search' || path === '/')) {\n      if (params.get('q')) {\n        return true;\n      }\n    }\n    if (host === 'search.brave.com' && (path === '/search' || path === '/images')) {\n      if (params.get('q')) {\n        return true;\n      }\n    }\n    if (host === 'search.arc.net' && path.startsWith('/search')) {\n      if (params.get('q') || params.get('query')) {\n        return true;\n      }\n    }\n    return false;\n  } catch {\n    return false;\n  }\n}\n\nfunction formatTimestamp(): { date: string; time: string } {\n  try {\n    const now = new Date();\n    const date = now.toLocaleDateString(undefined, {\n      year: 'numeric',\n      month: 'short',\n      day: 'numeric',\n    });\n    const time = now.toLocaleTimeString(undefined, {\n      hour: 'numeric',\n      minute: '2-digit',\n    });\n    return { date, time };\n  } catch {\n    return {\n      date: new Date().toISOString().slice(0, 10),\n      time: new Date().toISOString().slice(11, 16),\n    };\n  }\n}\n"
  },
  {
    "path": "src/gemini/content.ts",
    "content": "import { MessageRole } from '../types/api';\nimport type { ExtendedElement, MutableMutationObserver } from '../types/dom';\nimport type { MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { type StorageData, StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI, type Placement } from '../utils/util_positioning';\n\nexport {};\n\nlet isProcessingMem0 = false;\n\nlet memoryModalShown: boolean = false;\n\n// Global variable to store all memories\nlet allMemories: string[] = [];\n\n// Track added memories by ID\nconst allMemoriesById: Set<string> = new Set<string>();\n\n// Reference to the modal overlay for updates\nlet currentModalOverlay: HTMLDivElement | null = null;\n\nlet inputObserver: MutationObserver | null = null;\n\n// **PERFORMANCE FIX: Add initialization flags and cleanup variables**\nlet isInitialized: boolean = false;\nlet sendListenerAdded: boolean = false;\nlet mainObserver: MutableMutationObserver | null = null;\nlet notificationObserver: MutationObserver | null = null;\nlet setupRetryCount: number = 0;\nconst MAX_SETUP_RETRIES: number = 10;\n\n// **TIMING FIX: Add periodic element detection**\nlet elementDetectionInterval: number | null = null;\nlet lastFoundTextarea: HTMLElement | null = null;\nlet lastFoundSendButton: HTMLButtonElement | null = null;\n\nconst geminiSearch = createOrchestrator({\n  fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n    const data = await new Promise<SearchStorage>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as SearchStorage);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      return [];\n    }\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const threshold =\n      data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n        ? data[StorageKey.SIMILARITY_THRESHOLD]\n        : 0.1;\n    const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    const payload = {\n      query,\n      filters: { user_id: userId },\n      rerank: true,\n      threshold: threshold,\n      top_k: topK,\n      filter_memories: false,\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      ...optionalParams,\n    };\n\n    const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify(payload),\n      signal: opts && opts.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error(`API request failed with status ${res.status}`);\n    }\n    return await res.json();\n  },\n\n  // Don’t render on prefetch. When modal is open, update it.\n  onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n    if (!memoryModalShown) {\n      return;\n    }\n    const memoryItems = ((responseData as MemorySearchItem[]) || []).map(\n      (item: MemorySearchItem) => ({\n        id: String(item.id),\n        text: item.memory,\n        categories: item.categories || [],\n      })\n    );\n    createMemoryModal(memoryItems, false);\n  },\n\n  onError: function () {\n    if (memoryModalShown) {\n      createMemoryModal([], false);\n    }\n  },\n\n  minLength: 3,\n  debounceMs: 75,\n  cacheTTL: 60000,\n});\n\nlet geminiBackgroundSearchHandler: (() => void) | null = null;\nfunction hookGeminiBackgroundSearchTyping() {\n  const textarea = getTextarea();\n  if (!textarea) {\n    return;\n  }\n\n  if (textarea.dataset.geminiBackgroundHooked) {\n    return;\n  }\n  textarea.dataset.geminiBackgroundHooked = 'true';\n\n  if (!geminiBackgroundSearchHandler) {\n    geminiBackgroundSearchHandler = function () {\n      const text = (textarea.textContent || textarea.innerText || '').trim();\n      (geminiSearch as { setText: (text: string) => void }).setText(text);\n    };\n  }\n  textarea.addEventListener('input', geminiBackgroundSearchHandler);\n  textarea.addEventListener('keyup', geminiBackgroundSearchHandler);\n}\n\nfunction getTextarea(): HTMLElement | null {\n  const selectors = [\n    'rich-textarea .ql-editor[contenteditable=\"true\"]',\n    'rich-textarea .ql-editor.textarea',\n    '.ql-editor[aria-label=\"Enter a prompt here\"]',\n    '.ql-editor.textarea.new-input-ui',\n    '.text-input-field_textarea .ql-editor',\n    'div[contenteditable=\"true\"][role=\"textbox\"][aria-label=\"Enter a prompt here\"]',\n  ];\n\n  for (const selector of selectors) {\n    const textarea = document.querySelector(selector) as HTMLElement | null;\n    if (textarea) {\n      // **TIMING FIX: Store reference for comparison**\n      if (lastFoundTextarea !== textarea) {\n        lastFoundTextarea = textarea;\n        // Reset listener flag when new textarea is found\n        if (textarea.dataset.mem0KeyListener !== 'true') {\n          sendListenerAdded = false;\n        }\n      }\n\n      // **PERFORMANCE FIX: Trigger listener setup if not done yet**\n      if (!sendListenerAdded) {\n        setTimeout(() => {\n          if (!sendListenerAdded) {\n            addSendButtonListener();\n          }\n        }, 500);\n      }\n      return textarea;\n    }\n  }\n  return null;\n}\n\n// **TIMING FIX: Add function to detect send button**\nfunction getSendButton(): HTMLButtonElement | null {\n  const selectors = [\n    'button[aria-label=\"Send message\"]',\n    'button[data-testid=\"send-button\"]',\n    'button[type=\"submit\"]:not([aria-label*=\"attachment\"])',\n    '.send-button',\n    'button[aria-label*=\"Send\"]',\n    'button[title*=\"Send\"]',\n  ];\n\n  for (const selector of selectors) {\n    const button = document.querySelector(selector) as HTMLButtonElement | null;\n    if (button) {\n      // **TIMING FIX: Store reference for comparison**\n      if (lastFoundSendButton !== button) {\n        lastFoundSendButton = button;\n        // Reset listener flag when new button is found\n        if (button.dataset.mem0Listener !== 'true') {\n          sendListenerAdded = false;\n        }\n      }\n\n      return button;\n    }\n  }\n  return null;\n}\n\n// **TIMING FIX: Add periodic element detection**\nfunction startElementDetection(): void {\n  if (elementDetectionInterval) {\n    clearInterval(elementDetectionInterval);\n  }\n\n  elementDetectionInterval = window.setInterval(() => {\n    const textarea = getTextarea();\n    const sendButton = getSendButton();\n\n    // If we found elements and listeners aren't set up, try to set them up\n    if ((textarea || sendButton) && !sendListenerAdded) {\n      addSendButtonListener();\n    }\n\n    // If both elements are found and listeners are set up, we can reduce frequency\n    if (textarea && sendButton && sendListenerAdded) {\n      if (elementDetectionInterval) {\n        clearInterval(elementDetectionInterval);\n      }\n      // Check less frequently once everything is set up\n      elementDetectionInterval = window.setInterval(() => {\n        const currentTextarea = getTextarea();\n        const currentSendButton = getSendButton();\n\n        if ((!currentTextarea || !currentSendButton) && sendListenerAdded) {\n          sendListenerAdded = false;\n          lastFoundTextarea = null;\n          lastFoundSendButton = null;\n        }\n      }, 5000); // Check every 5 seconds for maintenance\n    }\n  }, 1000); // Check every second initially\n}\n\nfunction setupInputObserver(): void {\n  // **PERFORMANCE FIX: Prevent multiple observers and add retry limit**\n  if (inputObserver) {\n    return;\n  }\n\n  const textarea = getTextarea();\n  if (!textarea) {\n    if (setupRetryCount < MAX_SETUP_RETRIES) {\n      setupRetryCount++;\n      setTimeout(setupInputObserver, 500);\n    }\n    return;\n  }\n\n  // **PERFORMANCE FIX: Reset retry count on success**\n  setupRetryCount = 0;\n}\n\nfunction setInputValue(inputElement: HTMLElement, value: string): void {\n  if (inputElement) {\n    // For contenteditable divs, we need to set innerHTML or textContent\n    if (inputElement.contentEditable === 'true') {\n      // Clear existing content\n      inputElement.innerHTML = '';\n\n      // Split the value by newlines and create paragraph elements\n      const lines = value.split('\\n');\n      lines.forEach(line => {\n        const p = document.createElement('p');\n        if (line.trim() === '') {\n          p.innerHTML = '<br>';\n        } else {\n          p.textContent = line;\n        }\n        inputElement.appendChild(p);\n      });\n\n      // Trigger input event\n      inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n\n      // Focus and set cursor to end\n      inputElement.focus();\n\n      // Set cursor to end of content\n      const range = document.createRange();\n      const selection = window.getSelection();\n      range.selectNodeContents(inputElement);\n      range.collapse(false);\n      if (selection) {\n        selection.removeAllRanges();\n        selection.addRange(range);\n      }\n    } else {\n      // Fallback for regular input/textarea elements\n      (inputElement as HTMLTextAreaElement).value = value;\n      inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n    }\n  }\n}\n\n// Function to get the content without any memory wrappers\nfunction getContentWithoutMemories(): string {\n  const inputElement = getTextarea();\n\n  if (!inputElement) {\n    return '';\n  }\n\n  let content = inputElement.textContent || '';\n\n  // Remove any memory headers and content\n  const memoryPrefix = OPENMEMORY_PROMPTS.memory_header_text;\n  const prefixIndex = content.indexOf(memoryPrefix);\n  if (prefixIndex !== -1) {\n    content = content.substring(0, prefixIndex).trim();\n  }\n\n  // Also try with regex pattern\n  try {\n    const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n    content = content.replace(MEM0_PLAIN, '').trim();\n  } catch {\n    // Ignore regex errors\n  }\n\n  return content;\n}\n\n// Function to check if memory is enabled\nfunction getMemoryEnabledState(): Promise<boolean> {\n  return new Promise<boolean>(resolve => {\n    chrome.storage.sync.get([StorageKey.MEMORY_ENABLED], function (result) {\n      resolve(result.memory_enabled !== false); // Default to true if not set\n    });\n  });\n}\n\n// Track if memory has been captured for this session to prevent duplicates\nlet memoryCaptured = false;\nlet lastCapturedMessage = '';\n\n// Add a function to handle send button actions and clear memories after sending\nfunction addSendButtonListener(): void {\n  // **PERFORMANCE FIX: Prevent duplicate listener registration**\n  if (sendListenerAdded) {\n    return;\n  }\n\n  // Handle capturing and storing the current message\n  function captureAndStoreMemory(): void {\n    const textarea = getTextarea();\n    if (!textarea) {\n      return;\n    }\n\n    const message = ((textarea as HTMLElement).textContent || '').trim();\n    if (!message) {\n      return;\n    }\n\n    // Clean message from any existing memory content\n    const cleanMessage = getContentWithoutMemories();\n\n    // Prevent duplicate captures for the same message\n    if (memoryCaptured && lastCapturedMessage === cleanMessage) {\n      return;\n    }\n\n    memoryCaptured = true;\n    lastCapturedMessage = cleanMessage;\n\n    // Reset the capture flag after a short delay\n    setTimeout(() => {\n      memoryCaptured = false;\n      lastCapturedMessage = '';\n    }, 1000);\n\n    // Asynchronously store the memory\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.USER_ID_CAMEL,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.MEMORY_ENABLED,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.USER_ID,\n      ],\n      function (items) {\n        // Skip if memory is disabled or no credentials\n        if (items.memory_enabled === false || (!items.apiKey && !items.access_token)) {\n          return;\n        }\n\n        const authHeader = items.access_token\n          ? `Bearer ${items.access_token}`\n          : `Token ${items.apiKey}`;\n\n        const userId = items.userId || items.user_id || 'chrome-extension-user';\n\n        const optionalParams: OptionalApiParams = {};\n        if (items.selected_org) {\n          optionalParams.org_id = items.selected_org;\n        }\n        if (items.selected_project) {\n          optionalParams.project_id = items.selected_project;\n        }\n        // Send memory to mem0 API asynchronously without waiting for response\n        fetch('https://api.mem0.ai/v1/memories/', {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: authHeader,\n          },\n          body: JSON.stringify({\n            messages: [{ role: MessageRole.User, content: cleanMessage }],\n            user_id: userId,\n            infer: true,\n            metadata: {\n              provider: 'Gemini',\n            },\n            source: 'OPENMEMORY_CHROME_EXTENSION',\n            ...optionalParams,\n          }),\n        }).catch(() => {\n          // Ignore errors\n        });\n      }\n    );\n\n    // Clear all memories after sending\n    setTimeout(() => {\n      allMemories = [];\n      allMemoriesById.clear();\n    }, 100);\n  }\n\n  // **TIMING FIX: Use the new getSendButton function**\n  const sendButton = getSendButton();\n\n  if (sendButton && !sendButton.dataset.mem0Listener) {\n    sendButton.dataset.mem0Listener = 'true';\n    sendButton.addEventListener('click', function () {\n      captureAndStoreMemory();\n    });\n  }\n\n  // Handle textarea for Enter key press separately\n  const textarea = getTextarea();\n\n  if (textarea) {\n    if (!textarea.dataset.mem0KeyListener) {\n      textarea.dataset.mem0KeyListener = 'true';\n      textarea.addEventListener('keydown', function (event) {\n        // Check if Enter was pressed without Shift (standard send behavior)\n        if (event.key === 'Enter' && !event.shiftKey) {\n          // Don't capture here if send button will also trigger\n          // The send button click will handle the capture\n          return;\n        }\n      });\n    }\n  }\n\n  // **TIMING FIX: Only mark as added if we actually found and set up both elements**\n  if (textarea && sendButton) {\n    sendListenerAdded = true;\n  }\n}\n\nfunction injectMem0Button(): void {\n  // Prefer OPENMEMORY_UI mounts; fall back to legacy injection only if unavailable\n  if (OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus) {\n    try {\n      if (!document.getElementById('mem0-icon-button')) {\n        OPENMEMORY_UI.resolveCachedAnchor(\n          { learnKey: location.host + ':' + location.pathname },\n          null,\n          24 * 60 * 60 * 1000\n        ).then(function (hit: { el: Element; placement: Placement | null } | null) {\n          if (!hit || !hit.el) {\n            return;\n          }\n          let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n          let host = hs.host,\n            shadow = hs.shadow;\n          host.id = 'mem0-icon-button';\n          let cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.gemini ? SITE_CONFIG.gemini : null;\n          let placement = hit.placement ||\n            (cfg && cfg.placement) || {\n              strategy: 'inline' as const,\n              where: 'beforeend' as const,\n              inlineAlign: 'end' as const,\n            };\n          OPENMEMORY_UI.applyPlacement({ container: host, anchor: hit.el, placement: placement });\n          let style = document.createElement('style');\n          style.textContent = `\n              :host { position: relative; }\n              .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n              .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n              .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n              :host([data-has-text=\"1\"]) .dot { display:block; }\n            `;\n          let btn = document.createElement('button');\n          btn.className = 'mem0-btn';\n          let img = document.createElement('img');\n          img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n          let dot = document.createElement('div');\n          dot.className = 'dot';\n          btn.appendChild(img);\n          shadow.append(style, btn, dot);\n          btn.addEventListener('click', function () {\n            handleMem0Modal();\n          });\n          if (typeof updateNotificationDot === 'function') {\n            setTimeout(updateNotificationDot, 0);\n          }\n        });\n      }\n    } catch {\n      // Ignore errors during re-initialization\n    }\n\n    OPENMEMORY_UI.mountOnEditorFocus({\n      existingHostSelector: '#mem0-icon-button',\n      editorSelector:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.gemini &&\n        SITE_CONFIG.gemini.editorSelector\n          ? SITE_CONFIG.gemini.editorSelector\n          : 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n      deriveAnchor:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.gemini &&\n        typeof SITE_CONFIG.gemini.deriveAnchor === 'function'\n          ? SITE_CONFIG.gemini.deriveAnchor\n          : function (editor: Element) {\n              return editor.closest('form') || editor.parentElement;\n            },\n      placement:\n        typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.gemini && SITE_CONFIG.gemini.placement\n          ? SITE_CONFIG.gemini.placement\n          : { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n      render: function (shadow: ShadowRoot, host: HTMLElement) {\n        host.id = 'mem0-icon-button';\n        let style = document.createElement('style');\n        style.textContent = `\n          :host { position: relative; }\n          .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n          .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n          .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n          :host([data-has-text=\"1\"]) .dot { display:block; }\n        `;\n        let btn = document.createElement('button');\n        btn.className = 'mem0-btn';\n        let img = document.createElement('img');\n        img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n        let dot = document.createElement('div');\n        dot.className = 'dot';\n        btn.appendChild(img);\n        shadow.append(style, btn, dot);\n        btn.addEventListener('click', function () {\n          handleMem0Modal();\n        });\n        if (typeof updateNotificationDot === 'function') {\n          setTimeout(updateNotificationDot, 0);\n        }\n      },\n      fallback: function () {\n        let cfg =\n          typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.gemini ? SITE_CONFIG.gemini : null;\n        return OPENMEMORY_UI.mountResilient({\n          anchors: [\n            {\n              find: function () {\n                let sel =\n                  (cfg && cfg.editorSelector) ||\n                  'textarea, [contenteditable=\"true\"], input[type=\"text\"]';\n                let ed = document.querySelector(sel);\n                if (!ed) {\n                  return null;\n                }\n                try {\n                  return cfg && typeof cfg.deriveAnchor === 'function'\n                    ? cfg.deriveAnchor(ed)\n                    : ed.closest('form') || ed.parentElement;\n                } catch (_) {\n                  return ed.closest('form') || ed.parentElement;\n                }\n              },\n            },\n          ],\n          placement: (cfg && cfg.placement) || {\n            strategy: 'inline',\n            where: 'beforeend',\n            inlineAlign: 'end',\n          },\n          enableFloatingFallback: true,\n          render: function (shadow: ShadowRoot, host: HTMLElement) {\n            host.id = 'mem0-icon-button';\n            let style = document.createElement('style');\n            style.textContent = `\n              :host { position: relative; }\n              .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n              .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n              .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n              :host([data-has-text=\"1\"]) .dot { display:block; }\n            `;\n            let btn = document.createElement('button');\n            btn.className = 'mem0-btn';\n            let img = document.createElement('img');\n            img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n            let dot = document.createElement('div');\n            dot.className = 'dot';\n            btn.appendChild(img);\n            shadow.append(style, btn, dot);\n            btn.addEventListener('click', function () {\n              handleMem0Modal();\n            });\n            if (typeof updateNotificationDot === 'function') {\n              setTimeout(updateNotificationDot, 0);\n            }\n          },\n        });\n      },\n    });\n    return;\n  }\n  let buttonRetryCount = 0;\n  const maxButtonRetries = 10;\n\n  // Function to periodically check and add the button if the parent element exists\n  async function tryAddButton() {\n    // **PERFORMANCE FIX: Add retry limit**\n    if (buttonRetryCount >= maxButtonRetries) {\n      return;\n    }\n    buttonRetryCount++;\n\n    // First check if memory is enabled\n    const memoryEnabled = await getMemoryEnabledState();\n\n    // Remove existing button if memory is disabled\n    if (!memoryEnabled) {\n      const existingButton = document.querySelector('#mem0-icon-button');\n      if (existingButton && existingButton.parentElement) {\n        existingButton.parentElement.remove();\n      }\n      // **PERFORMANCE FIX: Reset retry count and set longer timeout**\n      buttonRetryCount = 0;\n      setTimeout(tryAddButton, 10000);\n      return;\n    }\n\n    // Look for the toolbox-drawer container\n    const toolboxDrawer = document.querySelector('toolbox-drawer .toolbox-drawer-container');\n\n    if (!toolboxDrawer) {\n      setTimeout(tryAddButton, 1000);\n      return;\n    }\n\n    // Check if our button already exists\n    if (document.querySelector('#mem0-icon-button')) {\n      // **PERFORMANCE FIX: Reset retry count on success**\n      buttonRetryCount = 0;\n      return;\n    }\n\n    // **PERFORMANCE FIX: Reset retry count when successfully creating button**\n    buttonRetryCount = 0;\n\n    // Create mem0 button container to match toolbox-drawer-item structure\n    const mem0ButtonContainer = document.createElement('toolbox-drawer-item');\n    mem0ButtonContainer.className =\n      'mat-mdc-tooltip-trigger toolbox-drawer-item-button ng-tns-c1279795495-8 mat-mdc-tooltip-disabled ng-star-inserted';\n    mem0ButtonContainer.style.position = 'relative'; // For popover positioning\n    mem0ButtonContainer.style.backgroundColor = 'transparent';\n    mem0ButtonContainer.style.border = 'none';\n\n    // Create mem0 button to match the toolbox drawer button style\n    const mem0Button = document.createElement('button');\n    mem0Button.className =\n      'mat-ripple mat-mdc-tooltip-trigger toolbox-drawer-item-button gds-label-l is-mobile ng-star-inserted';\n    mem0Button.setAttribute('matripple', '');\n    mem0Button.setAttribute('aria-pressed', 'false');\n    mem0Button.setAttribute('aria-label', 'Mem0');\n    mem0Button.id = 'mem0-icon-button';\n    mem0Button.style.backgroundColor = 'transparent';\n    mem0Button.style.border = 'none';\n\n    // Create the button label div to match other toolbox items\n    const buttonLabel = document.createElement('div');\n    buttonLabel.className = 'toolbox-drawer-button-label label';\n    buttonLabel.style.cssText = `\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      font-family: 'Google Sans', Roboto, sans-serif;\n      font-size: 14px;\n      font-weight: 500;\n      background-color: transparent;\n    `;\n\n    // Create notification dot\n    const notificationDot = document.createElement('div');\n    notificationDot.id = 'mem0-notification-dot';\n    notificationDot.style.cssText = `\n      position: absolute;\n      top: -2px;\n      right: -2px;\n      width: 8px;\n      height: 8px;\n      background-color: #34a853;\n      border-radius: 50%;\n      border: 1px solid #fff;\n      display: none;\n      z-index: 1001;\n      pointer-events: none;\n    `;\n\n    // Add keyframe animation for the dot\n    if (!document.getElementById('notification-dot-animation')) {\n      const style = document.createElement('style');\n      style.id = 'notification-dot-animation';\n      style.innerHTML = `\n        @keyframes popIn {\n          0% { transform: scale(0); }\n          50% { transform: scale(1.2); }\n          100% { transform: scale(1); }\n        }\n        \n        #mem0-notification-dot.active {\n          display: block !important;\n          animation: popIn 0.3s ease-out forwards;\n        }\n      `;\n      document.head.appendChild(style);\n    }\n\n    // Add icon and text to button label\n    const iconImg = document.createElement('img');\n    iconImg.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n    iconImg.style.cssText = `\n      width: 18px;\n      height: 18px;\n      border-radius: 50%;\n    `;\n\n    const labelText = document.createElement('span');\n    labelText.textContent = 'Mem0';\n\n    buttonLabel.appendChild(iconImg);\n    buttonLabel.appendChild(labelText);\n    mem0Button.appendChild(buttonLabel);\n\n    // Create popover element (hidden by default)\n    const popover = document.createElement('div');\n    popover.className = 'mem0-button-popover';\n    popover.style.cssText = `\n      position: absolute;\n      bottom: 48px;\n      left: 50%;\n      transform: translateX(-50%);\n      background-color: #2d2e30;\n      border: 1px solid #5f6368;\n      color: white;\n      padding: 8px 12px;\n      border-radius: 6px;\n      font-size: 12px;\n      white-space: nowrap;\n      z-index: 10001;\n      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n      display: none;\n      transition: opacity 0.2s;\n      font-family: 'Google Sans', Roboto, sans-serif;\n    `;\n    popover.textContent = 'Add memories to your prompt';\n\n    // Add arrow\n    const arrow = document.createElement('div');\n    arrow.style.cssText = `\n      position: absolute;\n      top: 100%;\n      left: 50%;\n      transform: translateX(-50%) rotate(45deg);\n      width: 8px;\n      height: 8px;\n      background-color: #2d2e30;\n      border-right: 1px solid #5f6368;\n      border-bottom: 1px solid #5f6368;\n    `;\n    popover.appendChild(arrow);\n\n    // Add hover event for popover\n    mem0ButtonContainer.addEventListener('mouseenter', () => {\n      popover.style.display = 'block';\n      setTimeout(() => (popover.style.opacity = '1'), 10);\n    });\n\n    mem0ButtonContainer.addEventListener('mouseleave', () => {\n      popover.style.opacity = '0';\n      setTimeout(() => (popover.style.display = 'none'), 200);\n    });\n\n    // Add click event to the mem0 button to show memory modal\n    mem0Button.addEventListener('click', function () {\n      // Check if the memories are enabled\n      getMemoryEnabledState().then(memoryEnabled => {\n        if (memoryEnabled) {\n          handleMem0Modal();\n        } else {\n          // If memories are disabled, open options\n          chrome.runtime.sendMessage({ action: SidebarAction.OPEN_OPTIONS });\n        }\n      });\n    });\n\n    // Assemble button components\n    mem0ButtonContainer.appendChild(mem0Button);\n    mem0ButtonContainer.appendChild(notificationDot);\n    mem0ButtonContainer.appendChild(popover);\n\n    // Insert the button into the toolbox drawer\n    toolboxDrawer.appendChild(mem0ButtonContainer);\n\n    // Update notification dot based on input content\n    updateNotificationDot();\n\n    // Ensure notification dot is updated after DOM is fully loaded\n    setTimeout(updateNotificationDot, 500);\n  }\n\n  // Start trying to add the button\n  tryAddButton();\n}\n\n// Function to update notification dot visibility based on text in the input\nfunction updateNotificationDot(): void {\n  const textarea = getTextarea();\n  const notificationDot = document.querySelector('#mem0-notification-dot');\n\n  if (textarea && notificationDot) {\n    // Function to check if input has text\n    const checkForText = () => {\n      const inputText = (textarea as HTMLElement).textContent || '';\n      const hasText = inputText.trim() !== '';\n\n      if (hasText) {\n        notificationDot.classList.add('active');\n        // Force display style\n        notificationDot.style.display = 'block';\n      } else {\n        notificationDot.classList.remove('active');\n        notificationDot.style.display = 'none';\n      }\n    };\n\n    // **PERFORMANCE FIX: Clean up existing observer first**\n    if (notificationObserver) {\n      notificationObserver.disconnect();\n    }\n\n    // Set up an observer to watch for changes to the input field\n    notificationObserver = new MutationObserver(checkForText);\n\n    // Start observing the input element\n    notificationObserver.observe(textarea as Node, {\n      characterData: true,\n      subtree: true,\n      childList: true,\n    });\n\n    if (!textarea.dataset.geminiNotificationHooked) {\n      textarea.dataset.geminiNotificationHooked = 'true';\n      textarea.addEventListener('input', checkForText);\n      textarea.addEventListener('keyup', checkForText);\n      textarea.addEventListener('focus', checkForText);\n    }\n\n    // Initial check\n    checkForText();\n\n    // Force check after a small delay to ensure DOM is fully loaded\n    setTimeout(checkForText, 500);\n  } else {\n    // **PERFORMANCE FIX: Add retry limit for notification dot setup**\n    let notificationRetryCount = 0;\n    const maxNotificationRetries = 5;\n\n    const retryNotificationSetup = () => {\n      if (notificationRetryCount < maxNotificationRetries) {\n        notificationRetryCount++;\n        setTimeout(updateNotificationDot, 1000);\n      }\n    };\n\n    retryNotificationSetup();\n  }\n}\n\n// Shared function to update the input field with all collected memories\nfunction updateInputWithMemories(): void {\n  const inputElement = getTextarea();\n\n  if (inputElement && allMemories.length > 0) {\n    // Get the content without any existing memory wrappers\n    const baseContent = getContentWithoutMemories();\n\n    // Create the memory string with all collected memories\n    let memoriesContent = '\\n\\n' + OPENMEMORY_PROMPTS.memory_header_text + '\\n';\n    // Add all memories to the content\n    allMemories.forEach((mem, index) => {\n      memoriesContent += `- ${mem}`;\n      if (index < allMemories.length - 1) {\n        memoriesContent += '\\n';\n      }\n    });\n\n    // Add the final content to the input\n    setInputValue(inputElement, baseContent + memoriesContent);\n  }\n}\n\n// Function to show a small popup message near the button\nfunction showButtonPopup(button: HTMLElement, message: string): void {\n  // Remove any existing popups\n  const existingPopup = document.querySelector('.mem0-button-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  const popup = document.createElement('div');\n  popup.className = 'mem0-button-popup';\n\n  popup.style.cssText = `\n    position: absolute;\n    top: -40px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: #2d2e30;\n    border: 1px solid #5f6368;\n    color: white;\n    padding: 8px 12px;\n    border-radius: 6px;\n    font-size: 12px;\n    white-space: nowrap;\n    z-index: 10001;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n    font-family: 'Google Sans', Roboto, sans-serif;\n  `;\n\n  popup.textContent = message;\n\n  // Create arrow\n  const arrow = document.createElement('div');\n  arrow.style.cssText = `\n    position: absolute;\n    bottom: -5px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n    width: 8px;\n    height: 8px;\n    background-color: #2d2e30;\n    border-right: 1px solid #5f6368;\n    border-bottom: 1px solid #5f6368;\n  `;\n\n  popup.appendChild(arrow);\n\n  // Position relative to button\n  button.style.position = 'relative';\n  button.appendChild(popup);\n\n  // Auto-remove after 3 seconds\n  setTimeout(() => {\n    if (popup && popup.parentElement) {\n      popup.remove();\n    }\n  }, 3000);\n}\n\n// Function to show login popup\nfunction showLoginPopup(): void {\n  // First remove any existing popups\n  const existingPopup = document.querySelector('#mem0-login-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  // Create popup container\n  const popupOverlay = document.createElement('div');\n  popupOverlay.id = 'mem0-login-popup';\n  popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 10001;\n  `;\n\n  const popupContainer = document.createElement('div');\n  popupContainer.style.cssText = `\n    background-color: #2d2e30;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: 'Google Sans', Roboto, sans-serif;\n  `;\n\n  // Close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #9aa0a6;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n  closeButton.innerHTML = '&times;';\n  closeButton.addEventListener('click', () => {\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Logo and heading\n  const logoContainer = document.createElement('div');\n  logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n  const heading = document.createElement('h2');\n  heading.textContent = 'Sign in to OpenMemory';\n  heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 600;\n  `;\n\n  logoContainer.appendChild(heading);\n\n  // Message\n  const message = document.createElement('p');\n  message.textContent = 'Please sign in to access your memories and enhance your conversations!';\n  message.style.cssText = `\n    margin-bottom: 24px;\n    color: #e8eaed;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n  // Sign in button\n  const signInButton = document.createElement('button');\n  signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: #1a73e8;\n    color: white;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n    font-family: 'Google Sans', Roboto, sans-serif;\n    gap: 8px;\n  `;\n\n  // Add logo and text\n  const logoDark = document.createElement('img');\n  logoDark.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoDark.style.cssText = `\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n  `;\n\n  const signInText = document.createElement('span');\n  signInText.textContent = 'Sign in with OpenMemory';\n\n  signInButton.appendChild(logoDark);\n  signInButton.appendChild(signInText);\n\n  signInButton.addEventListener('mouseenter', () => {\n    signInButton.style.backgroundColor = '#1557b0';\n  });\n\n  signInButton.addEventListener('mouseleave', () => {\n    signInButton.style.backgroundColor = '#1a73e8';\n  });\n\n  // Open sign-in page when clicked\n  signInButton.addEventListener('click', () => {\n    window.open('https://app.mem0.ai/login', '_blank');\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Assemble popup\n  popupContainer.appendChild(logoContainer);\n  popupContainer.appendChild(message);\n  popupContainer.appendChild(signInButton);\n\n  popupOverlay.appendChild(popupContainer);\n  popupOverlay.appendChild(closeButton);\n\n  // Add click event to close when clicking outside\n  popupOverlay.addEventListener('click', (e: MouseEvent) => {\n    if (e.target === popupOverlay) {\n      document.body.removeChild(popupOverlay);\n    }\n  });\n\n  // Add to body\n  document.body.appendChild(popupOverlay);\n}\n\nfunction createMemoryModal(\n  memoryItems: Array<{ id?: string; text: string; categories?: string[] }>,\n  isLoading: boolean = false\n): void {\n  // Close existing modal if it exists\n  if (memoryModalShown && currentModalOverlay) {\n    document.body.removeChild(currentModalOverlay);\n  }\n\n  memoryModalShown = true;\n  let currentMemoryIndex = 0;\n\n  // Calculate modal dimensions (estimated)\n  const modalWidth = 447;\n  let modalHeight = 400; // Default height\n  let memoriesPerPage = 3; // Default number of memories per page\n\n  let topPosition: number;\n  let leftPosition: number;\n\n  // Position relative to the Mem0 button\n  const mem0Button =\n    (document.querySelector('#mem0-icon-button') as HTMLElement) ||\n    (document.querySelector('button[aria-label=\"Mem0\"]') as HTMLElement);\n\n  if (mem0Button) {\n    const buttonRect = mem0Button.getBoundingClientRect();\n\n    // Determine if there's enough space below the button\n    const viewportHeight = window.innerHeight;\n    const spaceBelow = viewportHeight - buttonRect.bottom;\n\n    // Position the modal centered under the button\n    leftPosition = Math.max(buttonRect.left + buttonRect.width / 2 - modalWidth / 2, 10);\n    // Ensure the modal doesn't go off the right edge of the screen\n    const rightEdgePosition = leftPosition + modalWidth;\n    if (rightEdgePosition > window.innerWidth - 10) {\n      leftPosition = window.innerWidth - modalWidth - 10;\n    }\n\n    if (spaceBelow >= modalHeight) {\n      // Place below the button\n      topPosition = buttonRect.bottom + 10;\n    } else {\n      // Place above the button if not enough space below\n      topPosition = buttonRect.top - modalHeight - 10;\n      // Check if it's in the upper half of the screen\n      if (buttonRect.top < viewportHeight / 2) {\n        modalHeight = 300; // Reduced height\n        memoriesPerPage = 2; // Show only 2 memories\n      }\n    }\n  } else {\n    // Fallback positioning\n    topPosition = 100;\n    leftPosition = window.innerWidth / 2 - modalWidth / 2;\n  }\n\n  // Create modal overlay\n  const modalOverlay = document.createElement('div');\n  modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n  // Save reference to current modal overlay\n  currentModalOverlay = modalOverlay;\n\n  // Add event listener to close modal when clicking outside\n  modalOverlay.addEventListener('click', (event: MouseEvent) => {\n    // Only close if clicking directly on the overlay, not its children\n    if (event.target === modalOverlay) {\n      closeModal();\n    }\n  });\n\n  // Create modal container with positioning\n  const modalContainer = document.createElement('div');\n  modalContainer.style.cssText = `\n    background-color: #2d2e30;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    position: absolute;\n    top: ${topPosition}px;\n    left: ${leftPosition}px;\n    pointer-events: auto;\n    border: 1px solid #5f6368;\n    font-family: 'Google Sans', Roboto, sans-serif;\n    overflow: hidden;\n  `;\n\n  // Create modal header\n  const modalHeader = document.createElement('div');\n  modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #35373a;\n    flex-shrink: 0;\n  `;\n\n  // Create header left section with just the logo\n  const headerLeft = document.createElement('div');\n  headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  `;\n\n  // Add Mem0 logo\n  const logoImg = document.createElement('img');\n  logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n  `;\n\n  // Add \"OpenMemory\" title\n  const title = document.createElement('div');\n  title.textContent = 'OpenMemory';\n  title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: white;\n    margin-left: 8px;\n  `;\n\n  // Create header right section\n  const headerRight = document.createElement('div');\n  headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n  `;\n\n  // Create Add to Prompt button with arrow\n  const addToPromptBtn = document.createElement('button');\n  addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color:rgb(27, 27, 27);\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: white;\n    transition: background-color 0.2s;\n  `;\n  addToPromptBtn.textContent = 'Add to Prompt';\n\n  // Add arrow icon to button\n  const arrowIcon = document.createElement('span');\n  arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>\n`;\n\n  arrowIcon.style.position = 'relative';\n  arrowIcon.style.top = '2px';\n  addToPromptBtn.appendChild(arrowIcon);\n\n  // Add hover effect for Add to Prompt button\n  addToPromptBtn.addEventListener('mouseenter', () => {\n    addToPromptBtn.style.backgroundColor = 'rgb(36, 36, 36)';\n  });\n  addToPromptBtn.addEventListener('mouseleave', () => {\n    addToPromptBtn.style.backgroundColor = 'rgb(27, 27, 27)';\n  });\n\n  // Create settings button\n  const settingsBtn = document.createElement('button');\n  settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n  settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n  // Add click event to open app.mem0.ai in a new tab\n  settingsBtn.addEventListener('click', () => {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n      memoryModalShown = false;\n      currentModalOverlay = null;\n    }\n\n    chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n  });\n\n  // Add hover effect for the settings button\n  settingsBtn.addEventListener('mouseenter', () => {\n    settingsBtn.style.opacity = '1';\n  });\n  settingsBtn.addEventListener('mouseleave', () => {\n    settingsBtn.style.opacity = '0.6';\n  });\n\n  // Content section\n  const contentSection = document.createElement('div');\n  const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n  contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n  // Create memories counter\n  const memoriesCounter = document.createElement('div');\n  memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n  // Update counter text based on loading state and number of memories\n  if (isLoading) {\n    memoriesCounter.textContent = `Loading Relevant Memories...`;\n  } else {\n    // Filter out memories that have already been added for accurate count\n    const availableMemoriesCount = memoryItems.filter(\n      memory => memory && memory.id && !allMemoriesById.has(memory.id)\n    ).length;\n    memoriesCounter.textContent = `${availableMemoriesCount} Relevant Memories`;\n  }\n\n  // Calculate max height for memories content based on modal height\n  const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n  // Create memories content container with adjusted height\n  const memoriesContent = document.createElement('div');\n  memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: thin;\n    scrollbar-color: #5f6368 transparent;\n  `;\n\n  // Track currently expanded memory\n  let currentlyExpandedMemory: HTMLElement | null = null;\n\n  // Function to create skeleton loading items (adjusted for different heights)\n  function createSkeletonItems() {\n    memoriesContent.innerHTML = '';\n\n    for (let i = 0; i < memoriesPerPage; i++) {\n      const skeletonItem = document.createElement('div');\n      skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #3c4043;\n        border-radius: 8px;\n        height: 72px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n      const skeletonText = document.createElement('div');\n      skeletonText.style.cssText = `\n        background-color: #5f6368;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n      const skeletonText2 = document.createElement('div');\n      skeletonText2.style.cssText = `\n        background-color: #5f6368;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n      const skeletonActions = document.createElement('div');\n      skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n      const skeletonButton1 = document.createElement('div');\n      skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #5f6368;\n      `;\n\n      const skeletonButton2 = document.createElement('div');\n      skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #5f6368;\n      `;\n\n      skeletonActions.appendChild(skeletonButton1);\n      skeletonActions.appendChild(skeletonButton2);\n\n      const textContainer = document.createElement('div');\n      textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      textContainer.appendChild(skeletonText);\n      textContainer.appendChild(skeletonText2);\n\n      skeletonItem.appendChild(textContainer);\n      skeletonItem.appendChild(skeletonActions);\n      memoriesContent.appendChild(skeletonItem);\n    }\n\n    // Add keyframe animation to document if not exists\n    if (!document.getElementById('skeleton-animation')) {\n      const style = document.createElement('style');\n      style.id = 'skeleton-animation';\n      style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n      document.head.appendChild(style);\n    }\n  }\n\n  // Function to show memories with adjusted count based on modal position\n  function showMemories() {\n    memoriesContent.innerHTML = '';\n\n    if (isLoading) {\n      createSkeletonItems();\n      return;\n    }\n\n    // Filter out memories that have already been added\n    const availableMemories = memoryItems.filter(memory => {\n      const hasId = memory && memory.id;\n      const isAlreadyAdded = hasId && allMemoriesById.has(String(memory.id));\n      return hasId && !isAlreadyAdded;\n    });\n\n    // Update counter with actual available memories count\n    memoriesCounter.textContent = isLoading\n      ? 'Loading Relevant Memories...'\n      : `${availableMemories.length} Relevant Memories`;\n\n    if (availableMemories.length === 0) {\n      showEmptyState();\n      updateNavigationState(0, 0);\n      return;\n    }\n\n    // Use the dynamically set memoriesPerPage value\n    const memoriesToShow = Math.min(memoriesPerPage, availableMemories.length);\n\n    // Calculate total pages and current page based on available memories\n    const totalPages = Math.ceil(availableMemories.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    // Adjust currentMemoryIndex if it exceeds available memories\n    if (currentMemoryIndex >= availableMemories.length) {\n      currentMemoryIndex = Math.max(0, availableMemories.length - memoriesToShow);\n    }\n\n    // Update navigation buttons state\n    updateNavigationState(currentPage, totalPages);\n\n    for (let i = 0; i < memoriesToShow; i++) {\n      const memoryIndex = currentMemoryIndex + i;\n      if (memoryIndex >= availableMemories.length) {\n        break;\n      } // Stop if we've reached the end\n\n      const memory = availableMemories[memoryIndex];\n      if (!memory) {\n        continue;\n      }\n\n      // Ensure memory has an ID\n      if (!memory.id) {\n        memory.id = `memory-${Date.now()}-${memoryIndex}`;\n      }\n\n      const memoryContainer = document.createElement('div');\n      memoryContainer.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px; \n        background-color: #3c4043;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s ease;\n        min-height: 56px; \n        max-height: 56px; \n        overflow: hidden;\n        flex-shrink: 0;\n      `;\n\n      const memoryText = document.createElement('div');\n      memoryText.style.cssText = `\n        font-size: 14px;\n        line-height: 1.5;\n        color: #e8eaed;\n        flex-grow: 1;\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        transition: all 0.2s ease;\n        height: 42px; /* Height for 2 lines of text */\n      `;\n      memoryText.textContent = memory.text;\n\n      const actionsContainer = document.createElement('div');\n      actionsContainer.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n        flex-shrink: 0;\n      `;\n\n      // Add button\n      const addButton = document.createElement('button');\n      addButton.style.cssText = `\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        background: #5f6368;\n        color: #e8eaed;\n        border-radius: 100%;\n        width: 28px;\n        height: 28px;\n        transition: all 0.2s ease;\n      `;\n\n      addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      </svg>`;\n\n      // Add hover effect for add button\n      addButton.addEventListener('mouseenter', () => {\n        addButton.style.backgroundColor = 'rgb(36, 36, 36)';\n      });\n      addButton.addEventListener('mouseleave', () => {\n        addButton.style.backgroundColor = '#5f6368';\n      });\n\n      // Add click handler for add button\n      addButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        sendExtensionEvent('memory_injection', {\n          provider: 'gemini',\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          browser: getBrowser(),\n          injected_all: false,\n          memory_id: memory.id,\n        });\n\n        // Add this memory\n        allMemoriesById.add(String(memory.id));\n        allMemories.push(String(memory.text || ''));\n        updateInputWithMemories();\n\n        // Refresh the memories display (no need to remove from memoryItems)\n        showMemories();\n      });\n\n      // Menu button\n      const menuButton = document.createElement('button');\n      menuButton.style.cssText = `\n        background: none;\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        color: #9aa0a6;\n      `;\n      menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n        <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n      </svg>`;\n\n      // Track expanded state\n      let isExpanded = false;\n\n      // Create remove button (hidden by default)\n      const removeButton = document.createElement('button');\n      removeButton.style.cssText = `\n        display: none;\n        align-items: center;\n        gap: 6px;\n        background: #5f6368;\n        color: #e8eaed;\n        border-radius: 8px;\n        padding: 2px 4px;\n        border: none;\n        cursor: pointer;\n        font-size: 13px;\n        margin-top: 12px;\n        width: fit-content;\n        transition: background-color 0.2s;\n      `;\n      removeButton.innerHTML = `\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>\n        Remove\n      `;\n\n      // Add hover effect for remove button\n      removeButton.addEventListener('mouseenter', () => {\n        removeButton.style.backgroundColor = '#ea4335';\n      });\n      removeButton.addEventListener('mouseleave', () => {\n        removeButton.style.backgroundColor = '#5f6368';\n      });\n\n      // Create content wrapper for text and remove button\n      const contentWrapper = document.createElement('div');\n      contentWrapper.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      contentWrapper.appendChild(memoryText);\n      contentWrapper.appendChild(removeButton);\n\n      // Function to expand memory\n      const expandMemory = () => {\n        if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n          currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n        }\n\n        isExpanded = true;\n        memoryText.style.webkitLineClamp = 'unset';\n        memoryText.style.height = 'auto';\n        contentWrapper.style.overflowY = 'auto';\n        contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n        contentWrapper.style.scrollbarWidth = 'thin';\n        contentWrapper.style.scrollbarColor = '#5f6368 transparent';\n        memoryContainer.style.backgroundColor = '#2d2e30';\n        memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n        memoryContainer.style.overflow = 'hidden';\n        removeButton.style.display = 'flex';\n        currentlyExpandedMemory = memoryContainer;\n\n        // Scroll to make expanded memory visible if needed\n        memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n      };\n\n      // Function to collapse memory\n      const collapseMemory = () => {\n        isExpanded = false;\n        memoryText.style.webkitLineClamp = '2';\n        memoryText.style.height = '42px';\n        contentWrapper.style.overflowY = 'visible';\n        memoryContainer.style.backgroundColor = '#3c4043';\n        memoryContainer.style.maxHeight = '72px';\n        memoryContainer.style.overflow = 'hidden';\n        removeButton.style.display = 'none';\n        currentlyExpandedMemory = null;\n      };\n\n      memoryContainer.addEventListener('collapse', collapseMemory);\n\n      menuButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        if (isExpanded) {\n          collapseMemory();\n        } else {\n          expandMemory();\n        }\n      });\n\n      // Add click handler for remove button\n      removeButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        // Remove from memoryItems\n        const index = memoryItems.findIndex(m => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Refresh the memories display\n          showMemories();\n        }\n      });\n\n      actionsContainer.appendChild(addButton);\n      actionsContainer.appendChild(menuButton);\n\n      memoryContainer.appendChild(contentWrapper);\n      memoryContainer.appendChild(actionsContainer);\n      memoriesContent.appendChild(memoryContainer);\n\n      // Add hover effect\n      memoryContainer.addEventListener('mouseenter', () => {\n        memoryContainer.style.backgroundColor = isExpanded ? '#25272a' : '#484b4f';\n      });\n      memoryContainer.addEventListener('mouseleave', () => {\n        memoryContainer.style.backgroundColor = isExpanded ? '#2d2e30' : '#3c4043';\n      });\n    }\n\n    // If after filtering for already added memories, there are no items to show,\n    // check if we need to go to previous page\n    if (memoriesContent.children.length === 0 && availableMemories.length > 0) {\n      if (currentMemoryIndex > 0) {\n        currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n        showMemories();\n      } else {\n        showEmptyState();\n      }\n    }\n  }\n\n  // Function to show empty state\n  function showEmptyState() {\n    memoriesContent.innerHTML = '';\n\n    const emptyContainer = document.createElement('div');\n    emptyContainer.style.cssText = `\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 32px 16px;\n      text-align: center;\n      flex: 1;\n      min-height: 200px;\n    `;\n\n    const emptyIcon = document.createElement('div');\n    emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9aa0a6\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>`;\n    emptyIcon.style.marginBottom = '16px';\n\n    const emptyText = document.createElement('div');\n    emptyText.textContent = 'No relevant memories found';\n    emptyText.style.cssText = `\n      color: #9aa0a6;\n      font-size: 14px;\n      font-weight: 500;\n    `;\n\n    emptyContainer.appendChild(emptyIcon);\n    emptyContainer.appendChild(emptyText);\n    memoriesContent.appendChild(emptyContainer);\n  }\n\n  // Add content to modal\n  contentSection.appendChild(memoriesCounter);\n  contentSection.appendChild(memoriesContent);\n\n  // Navigation section at bottom\n  const navigationSection = document.createElement('div');\n  navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n  // Navigation buttons\n  const prevButton = document.createElement('button');\n  prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#9aa0a6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  prevButton.style.cssText = `\n    background: #3c4043;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  const nextButton = document.createElement('button');\n  nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#9aa0a6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  nextButton.style.cssText = prevButton.style.cssText;\n\n  // Add navigation button handlers\n  prevButton.addEventListener('click', () => {\n    if (currentMemoryIndex >= memoriesPerPage) {\n      currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n      showMemories();\n    }\n  });\n\n  nextButton.addEventListener('click', () => {\n    const availableMemories = memoryItems.filter(memory => !allMemoriesById.has(String(memory.id)));\n    if (currentMemoryIndex + memoriesPerPage < availableMemories.length) {\n      currentMemoryIndex = currentMemoryIndex + memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  // Add hover effects\n  [prevButton, nextButton].forEach(button => {\n    button.addEventListener('mouseenter', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#484b4f';\n      }\n    });\n    button.addEventListener('mouseleave', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#3c4043';\n      }\n    });\n  });\n\n  navigationSection.appendChild(prevButton);\n  navigationSection.appendChild(nextButton);\n\n  // Assemble modal\n  headerLeft.appendChild(logoImg);\n  headerLeft.appendChild(title);\n  headerRight.appendChild(addToPromptBtn);\n  headerRight.appendChild(settingsBtn);\n\n  modalHeader.appendChild(headerLeft);\n  modalHeader.appendChild(headerRight);\n\n  modalContainer.appendChild(modalHeader);\n  modalContainer.appendChild(contentSection);\n  modalContainer.appendChild(navigationSection);\n\n  modalOverlay.appendChild(modalContainer);\n\n  // Append to body\n  document.body.appendChild(modalOverlay);\n\n  // Show initial memories\n  showMemories();\n\n  // Update navigation button states\n  function updateNavigationState(currentPage: number, totalPages: number): void {\n    if (memoryItems.length === 0 || totalPages === 0) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n      return;\n    }\n\n    if (currentPage <= 1) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n    } else {\n      prevButton.disabled = false;\n      prevButton.style.opacity = '1';\n      prevButton.style.cursor = 'pointer';\n    }\n\n    if (currentPage >= totalPages) {\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n    } else {\n      nextButton.disabled = false;\n      nextButton.style.opacity = '1';\n      nextButton.style.cursor = 'pointer';\n    }\n  }\n\n  // Update Add to Prompt button click handler\n  addToPromptBtn.addEventListener('click', () => {\n    // Only add memories that are not already added\n    const newMemories = memoryItems\n      .filter(memory => !allMemoriesById.has(String(memory.id)))\n      .map(memory => {\n        allMemoriesById.add(String(memory.id));\n        return String(memory.text || '');\n      });\n\n    sendExtensionEvent('memory_injection', {\n      provider: 'gemini',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n      injected_all: true,\n      memory_count: newMemories.length,\n    });\n    // Add all new memories to allMemories\n    allMemories.push(...newMemories);\n\n    // Update the input with all memories\n    if (allMemories.length > 0) {\n      updateInputWithMemories();\n      closeModal();\n    } else {\n      // If no new memories were added but we have existing ones, just close\n      if (allMemoriesById.size > 0) {\n        closeModal();\n      }\n    }\n  });\n\n  // Function to close the modal\n  function closeModal(): void {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n    }\n    currentModalOverlay = null;\n    memoryModalShown = false;\n  }\n}\n\n// Handler for the modal approach\nasync function handleMem0Modal(): Promise<void> {\n  // Check if there are actually memories in the current prompt\n  const currentPrompt = getTextarea() ? (getTextarea() as HTMLElement).textContent || '' : '';\n  const hasMemoriesInPrompt = currentPrompt.includes(OPENMEMORY_PROMPTS.memory_marker_prefix);\n\n  if (!hasMemoriesInPrompt) {\n    // If there are no memories in the current prompt, clear the tracking\n    allMemoriesById.clear();\n    allMemories = [];\n  }\n\n  const memoryEnabled = await getMemoryEnabledState();\n  if (!memoryEnabled) {\n    return;\n  }\n\n  // Check if user is logged in\n  const loginData: StorageData = await new Promise<StorageData>(resolve => {\n    chrome.storage.sync.get(\n      [StorageKey.API_KEY, StorageKey.USER_ID_CAMEL, StorageKey.ACCESS_TOKEN],\n      function (items) {\n        resolve(items);\n      }\n    );\n  });\n\n  // If no API key and no access token, show login popup\n  if (!loginData.apiKey && !loginData.access_token) {\n    showLoginPopup();\n    return;\n  }\n\n  const textarea = getTextarea();\n  let message = textarea ? ((textarea as HTMLElement).textContent || '').trim() : '';\n\n  // If no message, show a popup and return\n  if (!message) {\n    // Show message that requires input\n    const mem0Button =\n      (document.querySelector('#mem0-icon-button') as HTMLElement) ||\n      (document.querySelector('button[aria-label=\"Mem0\"]') as HTMLElement);\n\n    if (mem0Button) {\n      showButtonPopup(mem0Button as HTMLElement, 'Please enter some text first');\n    }\n    return;\n  }\n\n  // Clean the message of any existing memory content\n  message = getContentWithoutMemories();\n\n  if (isProcessingMem0) {\n    // Don't return, allow the modal to open anyway\n  }\n\n  isProcessingMem0 = true;\n\n  // Add a timeout to reset the flag if something goes wrong\n  const timeoutId = setTimeout(() => {\n    isProcessingMem0 = false;\n  }, 30000); // 30 second timeout\n\n  // Show the loading modal immediately with the source button ID\n  createMemoryModal([], true);\n\n  try {\n    const data: StorageData = await new Promise<StorageData>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    // const threshold = data.similarity_threshold !== undefined ? data.similarity_threshold : 0.1;\n    // const topK = data.top_k !== undefined ? data.top_k : 10;\n\n    if (!apiKey && !accessToken) {\n      isProcessingMem0 = false;\n      return;\n    }\n\n    sendExtensionEvent('modal_clicked', {\n      provider: 'gemini',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n    });\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n\n    const messages = [{ role: MessageRole.User, content: message }];\n\n    const optionalParams: OptionalApiParams = {};\n\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    (geminiSearch as { runImmediate: (message: string) => void }).runImmediate(message);\n\n    // Proceed with adding memory asynchronously without awaiting\n    fetch('https://api.mem0.ai/v1/memories/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify({\n        messages: messages,\n        user_id: userId,\n        infer: true,\n        metadata: {\n          provider: 'Gemini',\n        },\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        ...optionalParams,\n      }),\n    }).catch(() => {\n      // Ignore errors\n    });\n  } catch {\n    // Still show the modal but with empty state if there was an error\n    createMemoryModal([], false);\n  } finally {\n    clearTimeout(timeoutId);\n    isProcessingMem0 = false;\n  }\n}\n\nfunction initializeMem0Integration(): void {\n  // **PERFORMANCE FIX: Prevent multiple initializations**\n  if (isInitialized) {\n    return;\n  }\n\n  try {\n    setupInputObserver();\n    try {\n      hookGeminiBackgroundSearchTyping();\n    } catch {\n      // Ignore errors\n    }\n    injectMem0Button();\n    addSendButtonListener();\n\n    // **TIMING FIX: Start periodic element detection**\n    startElementDetection();\n\n    // **PERFORMANCE FIX: Clean up existing main observer**\n    if (mainObserver) {\n      mainObserver.disconnect();\n      if (mainObserver.memoryStateInterval) {\n        clearInterval(mainObserver.memoryStateInterval);\n      }\n    }\n\n    // **PERFORMANCE FIX: Consolidated debounced observer with self-trigger prevention**\n    let isObserverRunning = false; // Prevent self-triggering\n\n    mainObserver = new MutationObserver(async mutations => {\n      // **PERFORMANCE FIX: Prevent observer self-triggering**\n      if (isObserverRunning) {\n        return;\n      }\n\n      // **PERFORMANCE FIX: Filter out our own changes**\n      const relevantMutations = mutations.filter(mutation => {\n        // Skip mutations on our own elements\n        const targetEl = mutation.target as Element;\n        if (\n          targetEl &&\n          ((targetEl as ExtendedElement).id === 'mem0-notification-dot' ||\n            targetEl.classList?.contains('mem0-button-popover') ||\n            targetEl.classList?.contains('toolbox-drawer-item') ||\n            (targetEl as ExtendedElement).querySelector?.('[aria-label=\"Mem0\"]'))\n        ) {\n          return false;\n        }\n\n        // Only care about significant structural changes\n        if (mutation.type === 'childList') {\n          // Only if nodes were added/removed, not just text changes\n          return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;\n        }\n\n        return (\n          mutation.type === 'attributes' &&\n          (mutation.attributeName === 'class' || mutation.attributeName === 'id')\n        );\n      });\n\n      if (relevantMutations.length === 0) {\n        return;\n      }\n\n      // Clear existing debounce timer\n      if (mainObserver) {\n        clearTimeout(mainObserver.debounceTimer);\n      }\n\n      // Debounce the actual work\n      if (mainObserver) {\n        mainObserver.debounceTimer = window.setTimeout(async () => {\n          if (isObserverRunning) {\n            return;\n          }\n\n          isObserverRunning = true;\n\n          try {\n            // **PERFORMANCE FIX: Temporarily disconnect observer during DOM modifications**\n            if (mainObserver) {\n              mainObserver.disconnect();\n            }\n\n            // Check memory state first\n            const memoryEnabled = await getMemoryEnabledState();\n\n            // Only inject the button if memory is enabled\n            if (memoryEnabled) {\n              if (!document.querySelector('#mem0-icon-button')) {\n                injectMem0Button();\n              }\n              if (!sendListenerAdded) {\n                addSendButtonListener();\n              }\n              updateNotificationDot();\n            } else {\n              const host = document.querySelector('#mem0-icon-button') as HTMLElement | null;\n              if (host && host.parentElement) {\n                host.parentElement.remove();\n              }\n            }\n\n            // **PERFORMANCE FIX: Reconnect observer after DOM modifications**\n            setTimeout(() => {\n              if (mainObserver) {\n                mainObserver.observe(document.body, {\n                  childList: true,\n                  subtree: true,\n                  attributeFilter: ['class', 'id'],\n                });\n              }\n            }, 100);\n          } catch {\n            // Reconnect observer even if there was an error\n            setTimeout(() => {\n              if (mainObserver) {\n                mainObserver.observe(document.body, {\n                  childList: true,\n                  subtree: true,\n                  attributeFilter: ['class', 'id'],\n                });\n              }\n            }, 100);\n          } finally {\n            isObserverRunning = false;\n          }\n        }, 500);\n      } // Increased debounce to 500ms\n    });\n\n    // Add keyboard shortcut for Ctrl+M\n    document.addEventListener('keydown', function (event: KeyboardEvent) {\n      if (event.ctrlKey && event.key === 'm') {\n        event.preventDefault();\n        (async () => {\n          await handleMem0Modal();\n        })();\n      }\n    });\n\n    // **PERFORMANCE FIX: Observe with more specific targeting**\n    mainObserver.observe(document.body, {\n      childList: true,\n      subtree: true,\n      attributeFilter: ['class', 'id'], // Only observe relevant attribute changes\n    });\n\n    // **PERFORMANCE FIX: Reduce polling frequency and add cleanup**\n    const memoryStateCheckInterval = window.setInterval(async () => {\n      try {\n        const memoryEnabled = await getMemoryEnabledState();\n        if (!memoryEnabled) {\n          const existingButton = document.querySelector('#mem0-icon-button');\n          if (existingButton && existingButton.parentElement) {\n            existingButton.parentElement.remove();\n          }\n        } else if (!document.querySelector('#mem0-icon-button')) {\n          injectMem0Button();\n        }\n      } catch {\n        // Ignore errors\n      }\n    }, 15000); // Increased from 10s to 15s to reduce frequency further\n\n    // Store reference for cleanup\n    mainObserver.memoryStateInterval = memoryStateCheckInterval;\n\n    // **PERFORMANCE FIX: Mark as initialized**\n    isInitialized = true;\n  } catch {\n    // Ignore errors\n  }\n}\n\n// **PERFORMANCE FIX: Add cleanup function**\nfunction cleanup(): void {\n  if (mainObserver) {\n    mainObserver.disconnect();\n    if (mainObserver.memoryStateInterval) {\n      clearInterval(mainObserver.memoryStateInterval);\n    }\n    if (mainObserver.debounceTimer) {\n      clearTimeout(mainObserver.debounceTimer);\n    }\n  }\n\n  if (notificationObserver) {\n    notificationObserver.disconnect();\n  }\n\n  if (inputObserver) {\n    inputObserver.disconnect();\n  }\n\n  // **TIMING FIX: Clean up element detection interval**\n  if (elementDetectionInterval) {\n    clearInterval(elementDetectionInterval);\n  }\n\n  // Reset flags\n  isInitialized = false;\n  sendListenerAdded = false;\n  setupRetryCount = 0;\n  lastFoundTextarea = null;\n  lastFoundSendButton = null;\n}\n\n// **PERFORMANCE FIX: Clean up on page unload**\nwindow.addEventListener('beforeunload', cleanup);\n\n// Initialize the integration when the page loads\ninitializeMem0Integration();\n"
  },
  {
    "path": "src/grok/content.ts",
    "content": "/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport { MessageRole } from '../types/api';\nimport type { MemoryItem, MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { type StorageItems, StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI, type Placement } from '../utils/util_positioning';\n\nexport {};\n\nlet isProcessingMem0 = false;\n\nlet memoryModalShown: boolean = false;\n\n// Global variable to store all memories\nlet allMemories: string[] = [];\n\n// Track added memories by ID\nconst allMemoriesById: Set<string> = new Set<string>();\n\n// Reference to the modal overlay for updates\nlet currentModalOverlay: HTMLDivElement | null = null;\n\n// Track modal position for drag functionality\nconst modalPosition: { x: number | null; y: number | null } = { x: null, y: null };\n\nconst grokSearch = createOrchestrator({\n  fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n    const data = await new Promise<SearchStorage>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as SearchStorage);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      return [];\n    }\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const threshold =\n      data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n        ? data[StorageKey.SIMILARITY_THRESHOLD]\n        : 0.1;\n    const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    // Clean query by stripping any appended memory header/content (debounced path)\n    const cleanQuery = (function () {\n      try {\n        const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n        return String(query).replace(MEM0_PLAIN, '').trim();\n      } catch (_e) {\n        return query;\n      }\n    })();\n\n    const payload = {\n      query: cleanQuery,\n      filters: { user_id: userId },\n      rerank: true,\n      threshold: threshold,\n      top_k: topK,\n      filter_memories: false,\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      ...optionalParams,\n    };\n\n    const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify(payload),\n      signal: opts && opts.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error(`API request failed with status ${res.status}`);\n    }\n    return await res.json();\n  },\n\n  // Don’t render on prefetch. When modal is open, update it.\n  onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n    if (!memoryModalShown) {\n      return;\n    }\n    const memoryItems = ((responseData as MemorySearchItem[]) || []).map(\n      (item: MemorySearchItem) => ({\n        id: String(item.id),\n        text: item.memory,\n        categories: item.categories || [],\n      })\n    );\n    createMemoryModal(memoryItems, false);\n  },\n\n  onError: function () {\n    if (memoryModalShown) {\n      createMemoryModal([], false);\n    }\n  },\n\n  minLength: 3,\n  debounceMs: 75,\n  cacheTTL: 60000,\n});\n\nfunction getTextarea(): HTMLTextAreaElement | null {\n  const selectors = [\n    'textarea.w-full.px-2.\\\\@\\\\[480px\\\\]\\\\/input\\\\:px-3.bg-transparent.focus\\\\:outline-none.text-primary.align-bottom.min-h-14.pt-5.my-0.mb-5',\n    'textarea.w-full.px-2.\\\\@\\\\[480px\\\\]\\\\/input\\\\:px-3.pt-5.mb-5.bg-transparent.focus\\\\:outline-none.text-primary.align-bottom',\n    'textarea[dir=\"auto\"][spellcheck=\"false\"][placeholder=\"Ask anything\"]',\n    'textarea[dir=\"auto\"][spellcheck=\"false\"][placeholder=\"Ask follow-up\"]',\n    'textarea[dir=\"auto\"][spellcheck=\"false\"]',\n    'textarea[aria-label=\"Ask Grok anything\"]',\n  ];\n\n  for (const selector of selectors) {\n    const textarea = document.querySelector(selector) as HTMLTextAreaElement | null;\n    if (textarea) {\n      return textarea;\n    }\n  }\n  return null;\n}\n\nlet grokBackgroundSearchHandler: (() => void) | null = null;\n\nfunction hookGrokBackgroundSearchTyping() {\n  const textarea = getTextarea();\n  if (!textarea) {\n    return;\n  }\n\n  if (textarea.dataset.grokBackgroundHooked) {\n    return; \n  }\n\n  textarea.dataset.grokBackgroundHooked = 'true'; \n\n  if (!grokBackgroundSearchHandler) {\n    grokBackgroundSearchHandler = function () {\n      let text = textarea.value || '';\n      (grokSearch as { setText: (text: string) => void }).setText(text);\n    };\n  }\n  textarea.addEventListener('input', grokBackgroundSearchHandler);\n  textarea.addEventListener('keyup', grokBackgroundSearchHandler);\n}\n\nfunction setupInputObserver(): void {\n  const textarea = getTextarea();\n  if (!textarea) {\n    setTimeout(setupInputObserver, 500);\n    return;\n  }\n\n  hookGrokBackgroundSearchTyping(); \n}\n\nfunction setInputValue(inputElement: HTMLTextAreaElement | null, value: string): void {\n  if (inputElement) {\n    inputElement.value = value;\n    inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n  }\n}\n\n// Add a function to handle send button actions and clear memories after sending\n// function addSendButtonListener(): void {\n//   const selectors = [\n//     'button.group.flex.flex-col.justify-center.rounded-full[type=\"submit\"]',\n//     'button.group.flex.flex-col.justify-center.rounded-full.focus\\\\:outline-none.focus-visible\\\\:outline-none[type=\"submit\"]',\n//     'button[type=\"submit\"]:not([aria-label=\"Submit attachment\"])',\n//     'button[aria-label=\"Grok something\"][role=\"button\"]',\n//     'button[aria-label=\"Submit\"][type=\"submit\"]',\n//     'button[type=\"submit\"].group.flex.flex-col.justify-center.rounded-full',\n//   ];\n\n//   // Handle capturing and storing the current message\n//   function captureAndStoreMemory(): void {\n//     const textarea = getTextarea();\n//     if (!textarea) {\n//       return;\n//     }\n\n//     const message = (textarea.value || '').trim();\n//     if (!message) {\n//       return;\n//     }\n\n//     // Clean message from any existing memory content\n//     const cleanMessage = getContentWithoutMemories();\n\n//     // Asynchronously store the memory\n//     chrome.storage.sync.get(\n//       [\n//         StorageKey.API_KEY,\n//         StorageKey.USER_ID_CAMEL,\n//         StorageKey.ACCESS_TOKEN,\n//         StorageKey.MEMORY_ENABLED,\n//         StorageKey.SELECTED_ORG,\n//         StorageKey.SELECTED_PROJECT,\n//         StorageKey.USER_ID,\n//       ],\n//       function (items) {\n//         // Skip if memory is disabled or no credentials\n//         if (items.memory_enabled === false || (!items.apiKey && !items.access_token)) {\n//           return;\n//         }\n\n//         const authHeader = items.access_token\n//           ? `Bearer ${items.access_token}`\n//           : `Token ${items.apiKey}`;\n\n//         const userId = items.userId || items.user_id || 'chrome-extension-user';\n\n//         const optionalParams: OptionalApiParams = {};\n//         if (items.selected_org) {\n//           optionalParams.org_id = items.selected_org;\n//         }\n//         if (items.selected_project) {\n//           optionalParams.project_id = items.selected_project;\n//         }\n\n//         try {\n//           const textarea = getTextarea();\n//           const rawInput = textarea && textarea.value ? textarea.value.trim() : message;\n//           grokSearch.runImmediate(rawInput || message);\n//         } catch (_) {\n//           grokSearch.runImmediate(message);\n//         }\n//         // Send memory to mem0 API asynchronously without waiting for response\n//         const storagePayload = {\n//           messages: [{ role: MessageRole.User, content: cleanMessage }],\n//           user_id: userId,\n//           infer: true,\n//           metadata: {\n//             provider: 'Grok',\n//           },\n//           source: 'OPENMEMORY_CHROME_EXTENSION',\n//           ...optionalParams,\n//         };\n\n//         fetch('https://api.mem0.ai/v1/memories/', {\n//           method: 'POST',\n//           headers: {\n//             'Content-Type': 'application/json',\n//             Authorization: authHeader,\n//           },\n//           body: JSON.stringify(storagePayload),\n//         }).catch(error => {\n//           console.error('Error saving memory:', error);\n//         });\n//       }\n//     );\n\n//     // Clear all memories after sending\n//     setTimeout(() => {\n//       allMemories = [];\n//       allMemoriesById.clear();\n//     }, 100);\n//   }\n\n//   // Find and add listeners to the send button\n//   let sendButton = null;\n//   for (const selector of selectors) {\n//     sendButton = document.querySelector(selector);\n//     if (sendButton && !sendButton.dataset.mem0Listener) {\n//       sendButton.dataset.mem0Listener = 'true';\n//       sendButton.addEventListener('click', function () {\n//         captureAndStoreMemory();\n//       });\n\n//       // Also handle textarea for Enter key press\n//       const textarea = getTextarea();\n//       if (textarea && !textarea.dataset.mem0KeyListener) {\n//         textarea.dataset.mem0KeyListener = 'true';\n//         textarea.addEventListener('keydown', function (event: KeyboardEvent) {\n//           // Check if Enter was pressed without Shift (standard send behavior)\n//           if (event.key === 'Enter' && !event.shiftKey) {\n//             captureAndStoreMemory();\n//           }\n//         });\n//       }\n\n//       break;\n//     }\n//   }\n// }\n\nfunction initializeMem0Integration(): void {\n  setupInputObserver();\n  hookGrokBackgroundSearchTyping(); \n\n  // Set up mutation observer to reinject elements when DOM changes\n  // Cache-first mount (before focus)\n  try {\n    if (!document.getElementById('mem0-icon-button') && OPENMEMORY_UI) {\n      OPENMEMORY_UI.resolveCachedAnchor(\n        { learnKey: location.host + ':' + location.pathname },\n        null,\n        24 * 60 * 60 * 1000\n      ).then(function (hit) {\n        if (!hit || !hit.el) {\n          return;\n        }\n        let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n        let host = hs.host,\n          shadow = hs.shadow;\n        host.id = 'mem0-icon-button';\n        const cfg =\n          typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.grok ? SITE_CONFIG.grok : null;\n        let placement = hit.placement ||\n          (cfg && cfg.placement) || { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' };\n        OPENMEMORY_UI.applyPlacement({ container: host, anchor: hit.el, placement: placement });\n\n        let style = document.createElement('style');\n        style.textContent = `\n          :host { position: relative; }\n          .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n          .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n          .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n          :host([data-has-text=\"1\"]) .dot { display:block; }\n        `;\n        let btn = document.createElement('button');\n        btn.className = 'mem0-btn';\n        let img = document.createElement('img');\n        img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n        let dot = document.createElement('div');\n        dot.className = 'dot';\n        btn.appendChild(img);\n        shadow.append(style, btn, dot);\n        btn.addEventListener('click', function () {\n          handleMem0Modal();\n        });\n        if (typeof updateNotificationDot === 'function') {\n          setTimeout(updateNotificationDot, 0);\n        }\n\n        // Try to move immediately to the right of the \"Auto\" button\n        try {\n          let anchor = hit.el;\n          const cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.grok ? SITE_CONFIG.grok : null;\n          let autoBtn = Array.from(anchor.querySelectorAll('button,[role=\"button\"]')).find(\n            function (b: Element) {\n              let txt = (b.textContent || '').trim();\n              return cfg && cfg.autoButtonTextPattern\n                ? cfg.autoButtonTextPattern.test(txt)\n                : /\\bAuto\\b/i.test(txt);\n            }\n          );\n          if (autoBtn) {\n            let child: Element | null = autoBtn;\n            while (child && child.parentElement !== anchor) {\n              child = child.parentElement;\n            }\n            if (child && child.parentElement === anchor && anchor) {\n              anchor.insertBefore(host, child.nextSibling);\n              let gap = getComputedStyle(anchor).gap;\n              if (!gap || gap === 'normal') {\n                gap = '4px';\n              }\n              host.style.marginLeft = gap;\n              host.style.marginRight = '0';\n              host.style.display = 'inline-flex';\n              host.style.alignItems = 'center';\n              host.style.flexShrink = '0';\n            }\n          }\n        } catch {\n          // Ignore errors during re-initialization\n        }\n      });\n    }\n  } catch {\n    // Ignore errors during re-initialization\n  }\n\n  // Focus-driven mount\n  if (OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus) {\n    OPENMEMORY_UI.mountOnEditorFocus({\n      existingHostSelector: '#mem0-icon-button',\n      editorSelector:\n        typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.grok && SITE_CONFIG.grok.editorSelector\n          ? SITE_CONFIG.grok.editorSelector\n          : 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n      deriveAnchor:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.grok &&\n        typeof SITE_CONFIG.grok.deriveAnchor === 'function'\n          ? SITE_CONFIG.grok.deriveAnchor\n          : function (editor: Element) {\n              return editor.closest('form') || editor.parentElement || document.body;\n            },\n      placement:\n        typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.grok && SITE_CONFIG.grok.placement\n          ? SITE_CONFIG.grok.placement\n          : { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n      render: function (shadow: ShadowRoot, host: HTMLElement, anchor: Element | null) {\n        host.id = 'mem0-icon-button';\n        let style = document.createElement('style');\n        style.textContent = `\n          :host { position: relative; }\n          .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n          .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n          .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n          :host([data-has-text=\"1\"]) .dot { display:block; }\n        `;\n        let btn = document.createElement('button');\n        btn.className = 'mem0-btn';\n        let img = document.createElement('img');\n        img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n        let dot = document.createElement('div');\n        dot.className = 'dot';\n        btn.appendChild(img);\n        shadow.append(style, btn, dot);\n        btn.addEventListener('click', function () {\n          handleMem0Modal();\n        });\n        if (typeof updateNotificationDot === 'function') {\n          setTimeout(updateNotificationDot, 0);\n        }\n\n        // Move host to immediately after the \"Auto\" button if present and normalize spacing\n        try {\n          let autoBtn =\n            anchor &&\n            Array.from(anchor.querySelectorAll('button,[role=\"button\"]')).find(function (\n              b: Element\n            ) {\n              return /\\bAuto\\b/i.test((b.textContent || '').trim());\n            });\n          if (autoBtn) {\n            let child: Element | null = autoBtn;\n            while (child && child.parentElement !== anchor) {\n              child = child.parentElement;\n            }\n            if (child && child.parentElement === anchor && anchor) {\n              anchor.insertBefore(host, child.nextSibling);\n              // Use container gap for spacing; don't add extra margin to avoid double spacing\n              host.style.marginLeft = '0';\n              // Rely on container gap; no additional margin\n              host.style.marginLeft = '0';\n              host.style.marginRight = '0';\n              host.style.display = 'inline-flex';\n              host.style.alignItems = 'center';\n              host.style.flexShrink = '0';\n            }\n          }\n        } catch {\n          // Ignore errors during re-initialization\n        }\n      },\n    });\n  }\n\n  document.addEventListener('keydown', function (event: KeyboardEvent) {\n    if (event.ctrlKey && event.key === 'm') {\n      event.preventDefault();\n      (async () => {\n        await handleMem0Modal();\n      })();\n    }\n  });\n}\n\n// function injectMem0Button(): void {\n//   // Function to periodically check and add the button if the parent element exists\n//   async function tryAddButton() {\n//     // First check if memory is enabled\n//     const memoryEnabled = await getMemoryEnabledState();\n\n//     // Remove existing button if memory is disabled\n//     if (!memoryEnabled) {\n//       const existingContainer = document.querySelector('#mem0-button-container');\n//       if (existingContainer) {\n//         existingContainer.remove();\n//       }\n//       // Check again after some time in case the state changes\n//       setTimeout(tryAddButton, 5000);\n//       return;\n//     }\n\n//     // Check if our button already exists\n//     if (\n//       document.querySelector('button[aria-label=\"OpenMemory\"]') ||\n//       document.querySelector('#mem0-button-container')\n//     ) {\n//       return;\n//     }\n\n//     // Look specifically for the Auto button to position next to it\n//     let referenceButton = null;\n//     const textarea = getTextarea();\n\n//     if (textarea) {\n//       // Find the Auto button by looking in the immediate parent container of the textarea\n//       // This is more specific and should avoid finding multiple buttons\n//       let container = textarea.parentElement;\n//       while (container && !referenceButton) {\n//         const buttons = container.querySelectorAll('button');\n//         for (let i = 0; i < buttons.length; i++) {\n//           const btn = buttons[i]!;\n//           // Check if this button contains \"Auto\" text and is visible\n//           if (btn.textContent && btn.textContent.trim() === 'Auto' && btn.offsetParent !== null) {\n//             referenceButton = btn;\n//             break;\n//           }\n//         }\n//         // Move to parent if we haven't found the Auto button yet\n//         container = container.parentElement;\n//         // Don't go too far up the DOM tree\n//         if (container === document.body) {\n//           break;\n//         }\n//       }\n//     }\n\n//     if (!referenceButton) {\n//       // If we can't find the Auto button, wait and try again\n//       setTimeout(tryAddButton, 1000);\n//       return;\n//     }\n\n//     const parentDiv = referenceButton.parentElement;\n//     if (!parentDiv) {\n//       setTimeout(tryAddButton, 1000);\n//       return;\n//     }\n\n//     // Create mem0 button container\n//     const mem0ButtonContainer = document.createElement('div');\n//     mem0ButtonContainer.id = 'mem0-button-container';\n//     mem0ButtonContainer.style.position = 'relative'; // For positioning popover\n//     mem0ButtonContainer.style.marginLeft = '4px'; // Smaller margin to be closer to Auto button\n//     mem0ButtonContainer.style.display = 'flex';\n//     mem0ButtonContainer.style.alignItems = 'center'; // Ensure vertical alignment\n\n//     // Create mem0 button\n//     const mem0Button = document.createElement('button');\n//     mem0Button.className = referenceButton.className;\n//     mem0Button.setAttribute('type', 'button');\n//     mem0Button.setAttribute('tabindex', '0');\n//     mem0Button.setAttribute('aria-pressed', 'false');\n//     mem0Button.setAttribute('aria-label', 'OpenMemory');\n//     mem0Button.setAttribute('data-state', 'closed');\n//     mem0Button.id = 'mem0-icon-button';\n\n//     // Add additional styling to match the Auto button better\n//     mem0Button.style.minWidth = 'auto';\n//     mem0Button.style.padding = '0';\n//     mem0Button.style.width = '32px';\n//     mem0Button.style.height = '32px';\n//     mem0Button.style.display = 'flex';\n//     mem0Button.style.alignItems = 'center';\n//     mem0Button.style.justifyContent = 'center';\n//     mem0Button.style.flexShrink = '0'; // Prevent shrinking\n//     mem0Button.style.margin = '0'; // Reset any inherited margins\n\n//     // Create notification dot\n//     const notificationDot = document.createElement('div');\n//     notificationDot.id = 'mem0-notification-dot';\n//     notificationDot.style.cssText = `\n//       position: absolute;\n//       top: -2px;\n//       right: -2px;\n//       width: 8px;\n//       height: 8px;\n//       background-color:rgb(128, 221, 162);\n//       border-radius: 50%;\n//       border: 1px solid #1C1C1E;\n//       display: none;\n//       z-index: 1001;\n//       pointer-events: none;\n//     `;\n\n//     // Add keyframe animation for the dot\n//     if (!document.getElementById('notification-dot-animation')) {\n//       const style = document.createElement('style');\n//       style.id = 'notification-dot-animation';\n//       style.innerHTML = `\n//         @keyframes popIn {\n//           0% { transform: scale(0); }\n//           50% { transform: scale(1.2); }\n//           100% { transform: scale(1); }\n//         }\n\n//         #mem0-notification-dot.active {\n//           display: block !important;\n//           animation: popIn 0.3s ease-out forwards;\n//         }\n//       `;\n//       document.head.appendChild(style);\n//     }\n\n//     // Create button content - icon only, similar to Claude style\n//     mem0Button.innerHTML = `\n//       <img src=\"${chrome.runtime.getURL('icons/mem0-claude-icon-p.png')}\"\n//       width=\"18\" height=\"18\" style=\"display: block;\">\n//     `;\n\n//     // Create popover element (hidden by default)\n//     const popover = document.createElement('div');\n//     popover.className = 'mem0-button-popover';\n//     popover.style.cssText = `\n//       position: absolute;\n//       bottom: 48px;\n//       left: 50%;\n//       transform: translateX(-50%);\n//       background-color: #1C1C1E;\n//       border: 1px solid #27272A;\n//       color: white;\n//       padding: 8px 12px;\n//       border-radius: 6px;\n//       font-size: 12px;\n//       white-space: nowrap;\n//       z-index: 10001;\n//       box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n//       display: none;\n//       transition: opacity 0.2s;\n//     `;\n//     popover.textContent = 'Add memories to your prompt';\n\n//     // Add arrow\n//     const arrow = document.createElement('div');\n//     arrow.style.cssText = `\n//       position: absolute;\n//       top: 100%;\n//       left: 50%;\n//       transform: translateX(-50%) rotate(45deg);\n//       width: 10px;\n//       height: 10px;\n//       background-color: #1C1C1E;\n//       border-right: 1px solid #27272A;\n//       border-bottom: 1px solid #27272A;\n//     `;\n//     popover.appendChild(arrow);\n\n//     // Add hover event for popover\n//     mem0ButtonContainer.addEventListener('mouseenter', () => {\n//       popover.style.display = 'block';\n//       setTimeout(() => (popover.style.opacity = '1'), 10);\n//     });\n\n//     mem0ButtonContainer.addEventListener('mouseleave', () => {\n//       popover.style.opacity = '0';\n//       setTimeout(() => (popover.style.display = 'none'), 200);\n//     });\n\n//     // Add click event to the mem0 button to show memory modal\n//     mem0Button.addEventListener('click', function () {\n//       // Check if the memories are enabled\n//       getMemoryEnabledState().then(memoryEnabled => {\n//         if (memoryEnabled) {\n//           handleMem0Modal();\n//         } else {\n//           // If memories are disabled, open options\n//           chrome.runtime.sendMessage({ action: SidebarAction.OPEN_OPTIONS });\n//         }\n//       });\n//     });\n\n//     // Assemble button components\n//     mem0ButtonContainer.appendChild(mem0Button);\n//     mem0ButtonContainer.appendChild(notificationDot);\n//     mem0ButtonContainer.appendChild(popover);\n\n//     // Insert after the Auto button (or reference button if Auto not found)\n//     parentDiv.insertBefore(mem0ButtonContainer, referenceButton.nextSibling);\n\n//     // Update notification dot based on input content\n//     updateNotificationDot();\n\n//     // Ensure notification dot is updated after DOM is fully loaded\n//     setTimeout(updateNotificationDot, 500);\n//   }\n\n//   // Start trying to add the button\n//   tryAddButton();\n\n//   // Also observe DOM changes to add button when needed\n//   const observer = new MutationObserver(() => {\n//     if (!document.querySelector('#mem0-button-container')) {\n//       tryAddButton();\n//     }\n\n//     // Also update notification dot when DOM changes\n//     updateNotificationDot();\n//     hookGrokBackgroundSearchTyping();\n//   });\n\n//   observer.observe(document.body, {\n//     childList: true,\n//     subtree: true,\n//   });\n// }\n\n// Function to update notification dot visibility based on text in the input\nfunction updateNotificationDot(): void {\n  const textarea = getTextarea();\n  const host = document.getElementById('mem0-icon-button');\n\n  if (textarea && host) {\n    // Function to check if input has text\n    const checkForText = () => {\n      const inputText = textarea.value || '';\n      host.setAttribute('data-has-text', inputText.trim() ? '1' : '0');\n    };\n\n    // Observe and listen for changes\n    const mo = new MutationObserver(checkForText);\n    mo.observe(textarea, { characterData: true, subtree: true });\n\n    if (!textarea.dataset.grokCheckTextHooked) {\n      textarea.dataset.grokCheckTextHooked = 'true'; \n      textarea.addEventListener('input', checkForText);\n      textarea.addEventListener('keyup', checkForText);\n      textarea.addEventListener('focus', checkForText);\n    }\n    \n    checkForText();\n    setTimeout(checkForText, 500);\n  } else {\n    setTimeout(updateNotificationDot, 1000);\n  }\n}\n\nfunction createMemoryModal(memoryItems: MemoryItem[], isLoading: boolean = false) {\n  // Close existing modal if it exists\n  if (memoryModalShown && currentModalOverlay) {\n    document.body.removeChild(currentModalOverlay);\n  }\n\n  memoryModalShown = true;\n  let currentMemoryIndex = 0;\n\n  // Calculate modal dimensions (estimated)\n  const modalWidth = 447;\n  let modalHeight = 400; // Default height\n  let memoriesPerPage = 3; // Default number of memories per page\n\n  let topPosition;\n  let leftPosition;\n\n  // Use stored position if available, otherwise calculate based on button position\n  if (modalPosition.x !== null && modalPosition.y !== null) {\n    leftPosition = modalPosition.x;\n    topPosition = modalPosition.y;\n  } else {\n    // Position relative to the OpenMemory button\n    const mem0Button =\n      document.getElementById('mem0-icon-button') ||\n      document.querySelector('button[aria-label=\"OpenMemory\"]');\n\n    if (mem0Button) {\n      const buttonRect = mem0Button.getBoundingClientRect();\n\n      // Determine if there's enough space below the button\n      const viewportHeight = window.innerHeight;\n      const spaceBelow = viewportHeight - buttonRect.bottom;\n\n      // Position the modal centered under the button\n      leftPosition = Math.max(buttonRect.left + buttonRect.width / 2 - modalWidth / 2, 10);\n      // Ensure the modal doesn't go off the right edge of the screen\n      const rightEdgePosition = leftPosition + modalWidth;\n      if (rightEdgePosition > window.innerWidth - 10) {\n        leftPosition = window.innerWidth - modalWidth - 10;\n      }\n\n      if (spaceBelow >= modalHeight) {\n        // Place below the button\n        topPosition = buttonRect.bottom + 10;\n      } else {\n        // Place above the button if not enough space below\n        topPosition = buttonRect.top - modalHeight - 10;\n        // Check if it's in the upper half of the screen\n        if (buttonRect.top < viewportHeight / 2) {\n          modalHeight = 300; // Reduced height\n          memoriesPerPage = 2; // Show only 2 memories\n        }\n      }\n    } else {\n      // Fallback positioning\n      topPosition = 100;\n      leftPosition = window.innerWidth / 2 - modalWidth / 2;\n    }\n\n    // Store the initial position\n    modalPosition.x = leftPosition;\n    modalPosition.y = topPosition;\n  }\n\n  // Create modal overlay\n  const modalOverlay = document.createElement('div');\n  modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n  // Save reference to current modal overlay\n  currentModalOverlay = modalOverlay;\n\n  // Add event listener to close modal when clicking outside\n  modalOverlay.addEventListener('click', event => {\n    // Only close if clicking directly on the overlay, not its children\n    if (event.target === modalOverlay) {\n      closeModal();\n    }\n  });\n\n  // Cache-first mount for Grok (aim to sit left of the send/auto area)\n  try {\n    if (!document.getElementById('mem0-icon-button') && OPENMEMORY_UI) {\n      OPENMEMORY_UI.resolveCachedAnchor(\n        { learnKey: location.host + ':' + location.pathname },\n        null,\n        24 * 60 * 60 * 1000\n      ).then(function (hit: { el: Element; placement: Placement | null } | null) {\n        if (!hit || !hit.el) {\n          return;\n        }\n        let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n        let host = hs.host,\n          shadow = hs.shadow;\n        host.id = 'mem0-icon-button';\n        let placement = hit.placement || {\n          strategy: 'inline',\n          where: 'beforeend',\n          inlineAlign: 'end',\n        };\n        OPENMEMORY_UI.applyPlacement({ container: host, anchor: hit.el, placement: placement });\n\n        let style = document.createElement('style');\n        style.textContent = `\n            :host { position: relative; }\n            .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n            .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n            .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n            :host([data-has-text=\"1\"]) .dot { display:block; }\n          `;\n        let btn = document.createElement('button');\n        btn.className = 'mem0-btn';\n        let img = document.createElement('img');\n        img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n        let dot = document.createElement('div');\n        dot.className = 'dot';\n        btn.appendChild(img);\n        shadow.append(style, btn, dot);\n        btn.addEventListener('click', function () {\n          handleMem0Modal();\n        });\n        if (typeof updateNotificationDot === 'function') {\n          setTimeout(updateNotificationDot, 0);\n        }\n      });\n    }\n  } catch (_) {\n    // Ignore errors during re-initialization\n  }\n\n  // Focus-driven mount for Grok\n  if (OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus) {\n    OPENMEMORY_UI.mountOnEditorFocus({\n      existingHostSelector: '#mem0-icon-button',\n      editorSelector: 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n      deriveAnchor: function (editor: Element) {\n        return editor.closest('form') || editor.parentElement;\n      },\n      placement: { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n      render: function (shadow: ShadowRoot, host: HTMLElement) {\n        host.id = 'mem0-icon-button';\n        let style = document.createElement('style');\n        style.textContent = `\n          :host { position: relative; }\n          .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n          .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n          .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n          :host([data-has-text=\"1\"]) .dot { display:block; }\n        `;\n        let btn = document.createElement('button');\n        btn.className = 'mem0-btn';\n        let img = document.createElement('img');\n        img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n        let dot = document.createElement('div');\n        dot.className = 'dot';\n        btn.appendChild(img);\n        shadow.append(style, btn, dot);\n        btn.addEventListener('click', function () {\n          handleMem0Modal();\n        });\n        if (typeof updateNotificationDot === 'function') {\n          setTimeout(updateNotificationDot, 0);\n        }\n      },\n    });\n  }\n\n  // Create modal container with positioning\n  const modalContainer = document.createElement('div');\n  modalContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    position: absolute;\n    top: ${topPosition}px;\n    left: ${leftPosition}px;\n    pointer-events: auto;\n    border: 1px solid #27272A;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    overflow: hidden;\n  `;\n\n  // Add drag functionality\n  let isDraggingModal: boolean = false;\n  const modalDragOffset: { x: number; y: number } = { x: 0, y: 0 };\n\n  function handleDragStart(e: MouseEvent) {\n    // Don't start dragging if clicking on buttons or interactive elements\n    const target = e.target as HTMLElement | null;\n    if (target && (target.tagName === 'BUTTON' || target.closest('button'))) {\n      return;\n    }\n\n    isDraggingModal = true;\n    const rect = modalContainer.getBoundingClientRect();\n    modalDragOffset.x = e.clientX - rect.left;\n    modalDragOffset.y = e.clientY - rect.top;\n    document.addEventListener('mousemove', handleDragMove);\n    document.addEventListener('mouseup', handleDragEnd);\n    modalContainer.style.transition = 'none';\n  }\n\n  function handleDragMove(e: MouseEvent) {\n    if (!isDraggingModal) {\n      return;\n    }\n\n    e.preventDefault();\n    const newX = e.clientX - modalDragOffset.x;\n    const newY = e.clientY - modalDragOffset.y;\n\n    // Constrain to viewport\n    const maxX = window.innerWidth - modalWidth;\n    const maxY = window.innerHeight - modalHeight;\n\n    const constrainedX = Math.max(0, Math.min(newX, maxX));\n    const constrainedY = Math.max(0, Math.min(newY, maxY));\n\n    modalContainer.style.left = constrainedX + 'px';\n    modalContainer.style.top = constrainedY + 'px';\n\n    // Update stored position\n    modalPosition.x = constrainedX;\n    modalPosition.y = constrainedY;\n  }\n\n  function handleDragEnd() {\n    isDraggingModal = false;\n    document.removeEventListener('mousemove', handleDragMove);\n    document.removeEventListener('mouseup', handleDragEnd);\n    modalContainer.style.transition = '';\n  }\n\n  // Create modal header\n  const modalHeader = document.createElement('div');\n  modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #232325;\n    flex-shrink: 0;\n    cursor: move;\n    user-select: none;\n  `;\n\n  // Create header left section with just the logo\n  const headerLeft = document.createElement('div');\n  headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  `;\n\n  // Add Mem0 logo (updated to SVG)\n  const logoImg = document.createElement('img');\n  logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n  `;\n\n  // Add \"OpenMemory\" title\n  const title = document.createElement('div');\n  title.textContent = 'OpenMemory';\n  title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: white;\n    margin-left: 8px;\n  `;\n\n  // Create header right section\n  const headerRight = document.createElement('div');\n  headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n  `;\n\n  // Create Add to Prompt button with arrow\n  const addToPromptBtn = document.createElement('button');\n  addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color: white;\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: black;\n  `;\n  addToPromptBtn.textContent = 'Add to Prompt';\n\n  // Add arrow icon to button\n  const arrowIcon = document.createElement('span');\n  arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>\n`;\n  addToPromptBtn.appendChild(arrowIcon);\n\n  // Create settings button\n  const settingsBtn = document.createElement('button');\n  settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n  settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n  // Add click event to open app.mem0.ai in a new tab\n  settingsBtn.addEventListener('click', () => {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n      memoryModalShown = false;\n      currentModalOverlay = null;\n    }\n\n    chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n  });\n\n  // Add hover effect for the settings button\n  settingsBtn.addEventListener('mouseenter', () => {\n    settingsBtn.style.opacity = '1';\n  });\n  settingsBtn.addEventListener('mouseleave', () => {\n    settingsBtn.style.opacity = '0.6';\n  });\n\n  // Add drag event listener to header\n  modalHeader.addEventListener('mousedown', handleDragStart);\n\n  // Content section\n  const contentSection = document.createElement('div');\n  const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n  contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n  // Create memories counter\n  const memoriesCounter = document.createElement('div');\n  memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n  // Update counter text based on loading state and number of memories\n  if (isLoading) {\n    memoriesCounter.textContent = `Loading Relevant Memories...`;\n  } else {\n    memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n  }\n\n  // Calculate max height for memories content based on modal height\n  const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n  // Create memories content container with adjusted height\n  const memoriesContent = document.createElement('div');\n  memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: none;\n    -ms-overflow-style: none;\n  `;\n  memoriesContent.style.cssText += '::-webkit-scrollbar { display: none; }';\n\n  // Track currently expanded memory\n  let currentlyExpandedMemory: HTMLElement | null = null;\n\n  // Function to create skeleton loading items (adjusted for different heights)\n  function createSkeletonItems() {\n    memoriesContent.innerHTML = '';\n\n    for (let i = 0; i < memoriesPerPage; i++) {\n      const skeletonItem = document.createElement('div');\n      skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #27272A;\n        border-radius: 8px;\n        height: 72px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n      const skeletonText = document.createElement('div');\n      skeletonText.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n      const skeletonText2 = document.createElement('div');\n      skeletonText2.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n      const skeletonActions = document.createElement('div');\n      skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n      const skeletonButton1 = document.createElement('div');\n      skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      const skeletonButton2 = document.createElement('div');\n      skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      skeletonActions.appendChild(skeletonButton1);\n      skeletonActions.appendChild(skeletonButton2);\n\n      const textContainer = document.createElement('div');\n      textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      textContainer.appendChild(skeletonText);\n      textContainer.appendChild(skeletonText2);\n\n      skeletonItem.appendChild(textContainer);\n      skeletonItem.appendChild(skeletonActions);\n      memoriesContent.appendChild(skeletonItem);\n    }\n\n    // Add keyframe animation to document if not exists\n    if (!document.getElementById('skeleton-animation')) {\n      const style = document.createElement('style');\n      style.id = 'skeleton-animation';\n      style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n      document.head.appendChild(style);\n    }\n  }\n\n  // Function to show memories with adjusted count based on modal position\n  function showMemories() {\n    memoriesContent.innerHTML = '';\n\n    if (isLoading) {\n      createSkeletonItems();\n      return;\n    }\n\n    if (memoryItems.length === 0) {\n      showEmptyState();\n      updateNavigationState(0, 0);\n      return;\n    }\n\n    // Use the dynamically set memoriesPerPage value\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n\n    // Calculate total pages and current page\n    const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    // Update navigation buttons state\n    updateNavigationState(currentPage, totalPages);\n\n    for (let i = 0; i < memoriesToShow; i++) {\n      const memoryIndex = currentMemoryIndex + i;\n      if (memoryIndex >= memoryItems.length) {\n        break;\n      } // Stop if we've reached the end\n\n      const memory = memoryItems[memoryIndex]!;\n\n      // Skip memories that have been added already\n      if (allMemoriesById.has(String(memory.id))) {\n        continue;\n      }\n\n      // Ensure memory has an ID\n      if (!memory.id) {\n        memory.id = `memory-${Date.now()}-${memoryIndex}`;\n      }\n\n      const memoryContainer = document.createElement('div');\n      memoryContainer.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px; \n        background-color: #27272A;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s ease;\n        min-height: 72px; \n        max-height: 72px; \n        overflow: hidden;\n        flex-shrink: 0;\n      `;\n\n      const memoryText = document.createElement('div');\n      memoryText.style.cssText = `\n        font-size: 14px;\n        line-height: 1.5;\n        color: #D4D4D8;\n        flex-grow: 1;\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        transition: all 0.2s ease;\n        height: 42px; /* Height for 2 lines of text */\n      `;\n      memoryText.textContent = memory.text || '';\n\n      const actionsContainer = document.createElement('div');\n      actionsContainer.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n        flex-shrink: 0;\n      `;\n\n      // Add button\n      const addButton = document.createElement('button');\n      addButton.style.cssText = `\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 100%;\n        transition: all 0.2s ease;\n      `;\n\n      addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      </svg>`;\n\n      // Add click handler for add button\n      addButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n\n        sendExtensionEvent('memory_injection', {\n          provider: 'grok',\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          browser: getBrowser(),\n          injected_all: false,\n          memory_id: memory.id,\n        });\n\n        // Add this memory\n        allMemoriesById.add(String(memory.id));\n        allMemories.push(String(memory.text || ''));\n        updateInputWithMemories();\n\n        // Remove this memory from the list\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n          // If we're on a page that's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      // Menu button\n      const menuButton = document.createElement('button');\n      menuButton.style.cssText = `\n        background: none;\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        color: #A1A1AA;\n      `;\n      menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n        <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n      </svg>`;\n\n      // Track expanded state\n      let isExpanded = false;\n\n      // Create remove button (hidden by default)\n      const removeButton = document.createElement('button');\n      removeButton.style.cssText = `\n        display: none;\n        align-items: center;\n        gap: 6px;\n        background:rgb(66, 66, 69);\n        color:rgb(199, 199, 201);\n        border-radius: 8px;\n        padding: 2px 4px;\n        border: none;\n        cursor: pointer;\n        font-size: 13px;\n        margin-top: 12px;\n        width: fit-content;\n      `;\n      removeButton.innerHTML = `\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>\n        Remove\n      `;\n\n      // Create content wrapper for text and remove button\n      const contentWrapper = document.createElement('div');\n      contentWrapper.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      contentWrapper.appendChild(memoryText);\n      contentWrapper.appendChild(removeButton);\n\n      // Function to expand memory\n      const expandMemory = () => {\n        if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n          currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n        }\n\n        isExpanded = true;\n        memoryText.style.webkitLineClamp = 'unset';\n        memoryText.style.height = 'auto';\n        contentWrapper.style.overflowY = 'auto';\n        contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n        contentWrapper.style.scrollbarWidth = 'none';\n        contentWrapper.style.msOverflowStyle = 'none';\n        contentWrapper.style.cssText += '::-webkit-scrollbar { display: none; }';\n        memoryContainer.style.backgroundColor = '#1C1C1E';\n        memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n        memoryContainer.style.overflow = 'hidden';\n        removeButton.style.display = 'flex';\n        currentlyExpandedMemory = memoryContainer;\n\n        // Scroll to make expanded memory visible if needed\n        memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n      };\n\n      // Function to collapse memory\n      const collapseMemory = () => {\n        isExpanded = false;\n        memoryText.style.webkitLineClamp = '2';\n        memoryText.style.height = '42px';\n        contentWrapper.style.overflowY = 'visible';\n        memoryContainer.style.backgroundColor = '#27272A';\n        memoryContainer.style.maxHeight = '72px';\n        memoryContainer.style.overflow = 'hidden';\n        removeButton.style.display = 'none';\n        currentlyExpandedMemory = null;\n      };\n\n      memoryContainer.addEventListener('collapse', collapseMemory);\n\n      menuButton.addEventListener('click', e => {\n        e.stopPropagation();\n        if (isExpanded) {\n          collapseMemory();\n        } else {\n          expandMemory();\n        }\n      });\n\n      // Add click handler for remove button\n      removeButton.addEventListener('click', (e: MouseEvent) => {\n        e.stopPropagation();\n        // Remove from memoryItems\n        const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n        if (index !== -1) {\n          memoryItems.splice(index, 1);\n\n          // Recalculate pagination after removing an item\n\n          // If we're on the last page and it's now empty, go to previous page\n          if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n            currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          }\n\n          memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n          showMemories();\n        }\n      });\n\n      actionsContainer.appendChild(addButton);\n      actionsContainer.appendChild(menuButton);\n\n      memoryContainer.appendChild(contentWrapper);\n      memoryContainer.appendChild(actionsContainer);\n      memoriesContent.appendChild(memoryContainer);\n\n      // Add hover effect\n      memoryContainer.addEventListener('mouseenter', () => {\n        memoryContainer.style.backgroundColor = isExpanded ? '#18181B' : '#323232';\n      });\n      memoryContainer.addEventListener('mouseleave', () => {\n        memoryContainer.style.backgroundColor = isExpanded ? '#1C1C1E' : '#27272A';\n      });\n    }\n\n    // If after filtering for already added memories, there are no items to show,\n    // check if we need to go to previous page\n    if (memoriesContent.children.length === 0 && memoryItems.length > 0) {\n      if (currentMemoryIndex > 0) {\n        currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n        showMemories();\n      } else {\n        showEmptyState();\n      }\n    }\n  }\n\n  // Function to show empty state\n  function showEmptyState() {\n    memoriesContent.innerHTML = '';\n\n    const emptyContainer = document.createElement('div');\n    emptyContainer.style.cssText = `\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 32px 16px;\n      text-align: center;\n      flex: 1;\n      min-height: 200px;\n    `;\n\n    const emptyIcon = document.createElement('div');\n    emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#71717A\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>`;\n    emptyIcon.style.marginBottom = '16px';\n\n    const emptyText = document.createElement('div');\n    emptyText.textContent = 'No relevant memories found';\n    emptyText.style.cssText = `\n      color: #71717A;\n      font-size: 14px;\n      font-weight: 500;\n    `;\n\n    emptyContainer.appendChild(emptyIcon);\n    emptyContainer.appendChild(emptyText);\n    memoriesContent.appendChild(emptyContainer);\n  }\n\n  // Add content to modal\n  contentSection.appendChild(memoriesCounter);\n  contentSection.appendChild(memoriesContent);\n\n  // Navigation section at bottom\n  const navigationSection = document.createElement('div');\n  navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n  // Navigation buttons\n  const prevButton = document.createElement('button');\n  prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  prevButton.style.cssText = `\n    background: #27272A;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  const nextButton = document.createElement('button');\n  nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  nextButton.style.cssText = prevButton.style.cssText;\n\n  // Add navigation button handlers\n  prevButton.addEventListener('click', () => {\n    if (currentMemoryIndex >= memoriesPerPage) {\n      currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n      showMemories();\n    }\n  });\n\n  nextButton.addEventListener('click', () => {\n    if (currentMemoryIndex + memoriesPerPage < memoryItems.length) {\n      currentMemoryIndex = currentMemoryIndex + memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  // Add hover effects\n  [prevButton, nextButton].forEach(button => {\n    button.addEventListener('mouseenter', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#323232';\n      }\n    });\n    button.addEventListener('mouseleave', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#27272A';\n      }\n    });\n  });\n\n  navigationSection.appendChild(prevButton);\n  navigationSection.appendChild(nextButton);\n\n  // Assemble modal\n  headerLeft.appendChild(logoImg);\n  headerLeft.appendChild(title);\n  headerRight.appendChild(addToPromptBtn);\n  headerRight.appendChild(settingsBtn);\n\n  modalHeader.appendChild(headerLeft);\n  modalHeader.appendChild(headerRight);\n\n  modalContainer.appendChild(modalHeader);\n  modalContainer.appendChild(contentSection);\n  modalContainer.appendChild(navigationSection);\n\n  modalOverlay.appendChild(modalContainer);\n\n  // Append to body\n  document.body.appendChild(modalOverlay);\n\n  // Show initial memories\n  showMemories();\n\n  // Update navigation button states\n  function updateNavigationState(currentPage: number, totalPages: number) {\n    if (memoryItems.length === 0 || totalPages === 0) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n      return;\n    }\n\n    if (currentPage <= 1) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n    } else {\n      prevButton.disabled = false;\n      prevButton.style.opacity = '1';\n      prevButton.style.cursor = 'pointer';\n    }\n\n    if (currentPage >= totalPages) {\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n    } else {\n      nextButton.disabled = false;\n      nextButton.style.opacity = '1';\n      nextButton.style.cursor = 'pointer';\n    }\n  }\n\n  // Update Add to Prompt button click handler\n  addToPromptBtn.addEventListener('click', () => {\n    // Only add memories that are not already added\n    const newMemories = memoryItems\n      .filter(memory => !allMemoriesById.has(String(memory.id)) && !memory.removed)\n      .map(memory => {\n        allMemoriesById.add(String(memory.id));\n        return String(memory.text || '');\n      });\n\n    sendExtensionEvent('memory_injection', {\n      provider: 'grok',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n      injected_all: true,\n      memory_count: newMemories.length,\n    });\n\n    // Add all new memories to allMemories\n    allMemories.push(...newMemories);\n\n    // Update the input with all memories\n    if (allMemories.length > 0) {\n      updateInputWithMemories();\n      closeModal();\n    } else {\n      // If no new memories were added but we have existing ones, just close\n      if (allMemoriesById.size > 0) {\n        closeModal();\n      }\n    }\n\n    // Remove all added memories from the memoryItems list\n    for (let i = memoryItems.length - 1; i >= 0; i--) {\n      if (allMemoriesById.has(String(memoryItems[i]?.id))) {\n        memoryItems.splice(i, 1);\n      }\n    }\n  });\n\n  // Function to close the modal\n  function closeModal() {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n    }\n    currentModalOverlay = null;\n    memoryModalShown = false;\n    // Reset modal position when closing completely\n    modalPosition.x = null;\n    modalPosition.y = null;\n  }\n}\n\n// Shared function to update the input field with all collected memories\nfunction updateInputWithMemories() {\n  const inputElement = getTextarea();\n\n  if (inputElement && allMemories.length > 0) {\n    // Get the content without any existing memory wrappers\n    const baseContent = getContentWithoutMemories();\n\n    // Create the memory string with all collected memories\n    let memoriesContent = '\\n\\n' + OPENMEMORY_PROMPTS.memory_header_text + '\\n';\n    // Add all memories to the content\n    allMemories.forEach((mem, index) => {\n      memoriesContent += `- ${mem}`;\n      if (index < allMemories.length - 1) {\n        memoriesContent += '\\n';\n      }\n    });\n\n    // Add the final content to the input\n    setInputValue(inputElement, baseContent + memoriesContent);\n  }\n}\n\n// Function to get the content without any memory wrappers\nfunction getContentWithoutMemories(): string {\n  const inputElement = getTextarea();\n\n  if (!inputElement) {\n    return '';\n  }\n\n  let content: string = inputElement.value || '';\n\n  // Remove any memory headers and content\n  const memoryPrefix = OPENMEMORY_PROMPTS.memory_header_text;\n  const prefixIndex = content.indexOf(memoryPrefix);\n  if (prefixIndex !== -1) {\n    content = content.substring(0, prefixIndex).trim();\n  }\n\n  // Also try with regex pattern\n  try {\n    const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n    content = content.replace(MEM0_PLAIN, '').trim();\n  } catch {\n    // Ignore regex errors\n  }\n\n  return content;\n}\n\n// Function to check if memory is enabled\nfunction getMemoryEnabledState(): Promise<boolean> {\n  return new Promise<boolean>(resolve => {\n    chrome.storage.sync.get([StorageKey.MEMORY_ENABLED], function (result) {\n      resolve(result.memory_enabled !== false); // Default to true if not set\n    });\n  });\n}\n\n// Handler for the modal approach\nasync function handleMem0Modal() {\n  const memoryEnabled = await getMemoryEnabledState();\n  if (!memoryEnabled) {\n    return;\n  }\n\n  // Check if user is logged in\n  const loginData = await new Promise<StorageItems>(resolve => {\n    chrome.storage.sync.get(\n      [StorageKey.API_KEY, StorageKey.USER_ID_CAMEL, StorageKey.ACCESS_TOKEN],\n      function (items) {\n        resolve(items as StorageItems);\n      }\n    );\n  });\n\n  // If no API key and no access token, show login popup\n  if (!loginData.apiKey && !loginData.access_token) {\n    showLoginPopup();\n    return;\n  }\n\n  const textarea = getTextarea();\n  let message = textarea ? (textarea.value || '').trim() : '';\n\n  // If no message, show a popup and return\n  if (!message) {\n    // Show message that requires input\n    const mem0Button = document.querySelector(\n      'button[aria-label=\"OpenMemory\"]'\n    ) as HTMLElement | null;\n    if (mem0Button) {\n      showButtonPopup(mem0Button, 'Please enter some text first');\n    }\n    return;\n  }\n\n  // Clean the message of any existing memory content\n  message = getContentWithoutMemories();\n\n  if (isProcessingMem0) {\n    return;\n  }\n\n  isProcessingMem0 = true;\n\n  // Show the loading modal immediately with the source button ID\n  createMemoryModal([], true);\n\n  try {\n    const data = await new Promise<StorageItems>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as StorageItems);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const userId = (data[StorageKey.USER_ID_CAMEL] ||\n      data[StorageKey.USER_ID] ||\n      'chrome-extension-user') as string;\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      isProcessingMem0 = false;\n      return;\n    }\n\n    sendExtensionEvent('modal_clicked', {\n      provider: 'grok',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n    });\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n\n    const messages = [{ role: MessageRole.User, content: message }];\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    try {\n      const textarea = getTextarea();\n      const rawInput2 = textarea && textarea.value ? textarea.value.trim() : message;\n      grokSearch.runImmediate(rawInput2 || message);\n    } catch (_) {\n      grokSearch.runImmediate(message);\n    }\n\n    // Proceed with adding memory asynchronously without awaiting\n    fetch('https://api.mem0.ai/v1/memories/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify({\n        messages: messages,\n        user_id: userId,\n        infer: true,\n        metadata: {\n          provider: 'Grok',\n        },\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        ...optionalParams,\n      }),\n    }).catch(error => {\n      console.error('Error adding memory:', error);\n    });\n  } catch (error) {\n    console.error('Error:', error);\n    // Still show the modal but with empty state if there was an error\n    createMemoryModal([], false);\n  } finally {\n    isProcessingMem0 = false;\n  }\n}\n\n// Function to show a small popup message near the button\nfunction showButtonPopup(button: HTMLElement, message: string): void {\n  // Remove any existing popups\n  const existingPopup = document.querySelector('.mem0-button-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  const popup = document.createElement('div');\n  popup.className = 'mem0-button-popup';\n\n  popup.style.cssText = `\n    position: absolute;\n    top: -40px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: #1C1C1E;\n    border: 1px solid #27272A;\n    color: white;\n    padding: 8px 12px;\n    border-radius: 6px;\n    font-size: 12px;\n    white-space: nowrap;\n    z-index: 10001;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n  `;\n\n  popup.textContent = message;\n\n  // Create arrow\n  const arrow = document.createElement('div');\n  arrow.style.cssText = `\n    position: absolute;\n    bottom: -5px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n    width: 10px;\n    height: 10px;\n    background-color: #1C1C1E;\n    border-right: 1px solid #27272A;\n    border-bottom: 1px solid #27272A;\n  `;\n\n  popup.appendChild(arrow);\n\n  // Position relative to button\n  button.style.position = 'relative';\n  button.appendChild(popup);\n\n  // Auto-remove after 3 seconds\n  setTimeout(() => {\n    if (popup && popup.parentElement) {\n      popup.remove();\n    }\n  }, 3000);\n}\n\n// Function to show login popup\nfunction showLoginPopup(): void {\n  // First remove any existing popups\n  const existingPopup = document.querySelector('#mem0-login-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  // Create popup container\n  const popupOverlay = document.createElement('div');\n  popupOverlay.id = 'mem0-login-popup';\n  popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 10001;\n  `;\n\n  const popupContainer = document.createElement('div');\n  popupContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  `;\n\n  // Close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #A1A1AA;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n  closeButton.innerHTML = '&times;';\n  closeButton.addEventListener('click', () => {\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Logo and heading\n  const logoContainer = document.createElement('div');\n  logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n  const logo = document.createElement('img');\n  logo.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logo.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const logoDark = document.createElement('img');\n  logoDark.src = chrome.runtime.getURL('icons/mem0-icon-black.png');\n  logoDark.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const heading = document.createElement('h2');\n  heading.textContent = 'Sign in to OpenMemory';\n  heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 600;\n  `;\n\n  logoContainer.appendChild(heading);\n\n  // Message\n  const message = document.createElement('p');\n  message.textContent = 'Please sign in to access your memories and enhance your conversations!';\n  message.style.cssText = `\n    margin-bottom: 24px;\n    color: #D4D4D8;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n  // Sign in button\n  const signInButton = document.createElement('button');\n  signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: white;\n    color: black;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  // Add text in span for better centering\n  const signInText = document.createElement('span');\n  signInText.textContent = 'Sign in with Mem0';\n\n  signInButton.appendChild(logoDark);\n  signInButton.appendChild(signInText);\n\n  signInButton.addEventListener('mouseenter', () => {\n    signInButton.style.backgroundColor = '#f5f5f5';\n  });\n\n  signInButton.addEventListener('mouseleave', () => {\n    signInButton.style.backgroundColor = 'white';\n  });\n\n  // Open sign-in page when clicked\n  signInButton.addEventListener('click', () => {\n    window.open('https://app.mem0.ai/login', '_blank');\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Assemble popup\n  popupContainer.appendChild(logoContainer);\n  popupContainer.appendChild(message);\n  popupContainer.appendChild(signInButton);\n\n  popupOverlay.appendChild(popupContainer);\n  popupOverlay.appendChild(closeButton);\n\n  // Add click event to close when clicking outside\n  popupOverlay.addEventListener('click', e => {\n    if (e.target === popupOverlay) {\n      document.body.removeChild(popupOverlay);\n    }\n  });\n\n  // Add to body\n  document.body.appendChild(popupOverlay);\n}\n\ninitializeMem0Integration();\n"
  },
  {
    "path": "src/mem0/content.ts",
    "content": "import { getBrowser, sendExtensionEvent } from '../utils/util_functions';\n\nfunction fetchAndSaveSession() {\n  fetch('https://app.mem0.ai/api/auth/session')\n    .then(response => response.json())\n    .then(data => {\n      if (data && data.access_token) {\n        chrome.storage.sync.set({ access_token: data.access_token });\n        chrome.storage.sync.set({ userLoggedIn: true });\n        //Track successful login\n        sendExtensionEvent('login_success', {\n          browser: getBrowser(),\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n        });\n      }\n    })\n    .catch(error => {\n      console.error('Error fetching session:', error);\n    });\n}\n\n// Check if the URL contains the login page and update userLoggedIn\nif (window.location.href.includes('https://app.mem0.ai/login')) {\n  chrome.storage.sync.set({ userLoggedIn: false });\n}\n\nfetchAndSaveSession();\n"
  },
  {
    "path": "src/perplexity/content.ts",
    "content": "/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport { MessageRole } from '../types/api';\nimport type { MemoryItem, MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { type StorageItems, StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI, type Placement } from '../utils/util_positioning';\n\nexport {};\n\n// Add global variables for memory modal\nlet memoryModalShown: boolean = false;\nlet allMemories: string[] = [];\n// Track added memories by ID\nconst allMemoriesById: Set<string> = new Set<string>();\n// Reference to the modal overlay for updates\nlet currentModalOverlay: HTMLDivElement | null = null;\n// Add a variable to track the submit button observer\nlet submitButtonObserver: MutationObserver | null = null;\n// Add variable to track if mem0 processing is happening\nlet isProcessingMem0: boolean = false;\n// Track modal position for dragging\nlet modalPosition: { top: number | null; left: number | null } = { top: null, left: null };\nlet isDragging: boolean = false;\n\nconst perplexitySearch = createOrchestrator({\n  fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n    const data = await new Promise<SearchStorage>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as SearchStorage);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    if (!apiKey && !accessToken) {\n      return [];\n    }\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n    const userId =\n      data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n    const threshold =\n      data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n        ? data[StorageKey.SIMILARITY_THRESHOLD]\n        : 0.1;\n    const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    const payload = {\n      query,\n      filters: { user_id: userId },\n      rerank: true,\n      threshold: threshold,\n      top_k: topK,\n      filter_memories: false,\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      ...optionalParams,\n    };\n\n    const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify(payload),\n      signal: opts && opts.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error(`API request failed with status ${res.status}`);\n    }\n    return await res.json();\n  },\n\n  // Don’t render on prefetch. When modal is open, update it.\n  onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n    if (!memoryModalShown) {\n      return;\n    }\n    const memoryItems = ((responseData as MemorySearchItem[]) || []).map(\n      (item: MemorySearchItem) => ({\n        id: String(item.id),\n        text: item.memory,\n        categories: item.categories || [],\n      })\n    );\n    createMemoryModal(memoryItems, false);\n  },\n\n  onError: function () {\n    if (memoryModalShown) {\n      createMemoryModal([], false);\n    }\n  },\n\n  minLength: 3,\n  debounceMs: 75,\n  cacheTTL: 60000,\n});\n\nlet perplexityBackgroundSearchHandler: (() => void) | null = null;\nfunction hookPerplexityBackgroundSearchTyping() {\n  const textarea = getTextarea();\n  if (!textarea) {\n    return;\n  }\n\n  if (textarea.dataset.perplexityBackgroundHooked) {\n    return;\n  }\n  textarea.dataset.perplexityBackgroundHooked = 'true';\n\n  if (!perplexityBackgroundSearchHandler) {\n    perplexityBackgroundSearchHandler = function () {\n      const text = getInputText(textarea).trim();\n      (perplexitySearch as { setText: (text: string) => void }).setText(text);\n    };\n  }\n  textarea.addEventListener('input', perplexityBackgroundSearchHandler);\n  textarea.addEventListener('keyup', perplexityBackgroundSearchHandler);\n}\n\nfunction getTextarea(): HTMLElement | null {\n  return (\n    document.querySelector('textarea[id=\"ask-input\"]') || // Follow-up screen textarea\n    document.querySelector('textarea[placeholder=\"Ask a follow-up…\"]') || // Follow-up screen textarea\n    document.querySelector('div[contenteditable=\"true\"][id=\"ask-input\"]') || // Main screen Lexical editor\n    document.querySelector('div[contenteditable=\"true\"][aria-placeholder=\"Ask anything…\"]') || // Main screen Lexical editor\n    document.querySelector('textarea[placeholder=\"Ask anything…\"]') // Fallback for older versions\n  );\n}\n\n// Helper function to get text content from either textarea or contenteditable div\nfunction getInputText(inputElement: HTMLElement | null): string {\n  if (!inputElement) {\n    return '';\n  }\n\n  if (inputElement.tagName === 'TEXTAREA') {\n    return (inputElement as HTMLTextAreaElement).value || '';\n  } else if (inputElement.contentEditable === 'true') {\n    // For Lexical editor, properly handle the structure\n    const paragraph = inputElement.querySelector('p[dir=\"ltr\"]') as HTMLElement | null;\n    if (paragraph) {\n      let text = '';\n      const childNodes = paragraph.childNodes;\n\n      for (let i = 0; i < childNodes.length; i++) {\n        const node = childNodes[i] as HTMLElement | Node;\n        if (node.nodeType === Node.TEXT_NODE) {\n          text += node.textContent || '';\n        } else if (\n          (node as HTMLElement).tagName === 'SPAN' &&\n          (node as HTMLElement).getAttribute('data-lexical-text') === 'true'\n        ) {\n          text += (node as HTMLElement).textContent || '';\n        } else if ((node as HTMLElement).tagName === 'BR') {\n          text += '\\n';\n        }\n      }\n\n      return text;\n    }\n\n    // Fallback to textContent if structure is different\n    return inputElement.textContent || '';\n  }\n\n  return '';\n}\n\n// Helper function to set text content for either textarea or contenteditable div\nfunction setInputText(inputElement: HTMLElement | null, text: string): void {\n  console.log('setInputText called with text:', text);\n  console.log('inputElement:', inputElement);\n\n  if (!inputElement) {\n    console.log('No input element, returning');\n    return;\n  }\n\n  if (inputElement.tagName === 'TEXTAREA') {\n    console.log('Using textarea approach');\n    (inputElement as HTMLTextAreaElement).value = text;\n    inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n  } else if (inputElement.contentEditable === 'true') {\n    console.log('Using contenteditable approach for Lexical editor');\n\n    // New approach: Use clipboard with actual paste event\n    console.log('Attempting clipboard-based approach');\n\n    // Focus the input first\n    inputElement.focus();\n\n    // Select all existing content\n    document.execCommand('selectAll', false, '');\n\n    // Try to write to clipboard and then trigger paste\n    if (navigator.clipboard && navigator.clipboard.writeText) {\n      console.log('Using modern Clipboard API');\n      navigator.clipboard\n        .writeText(text)\n        .then(() => {\n          console.log('Text written to clipboard successfully');\n\n          // Wait a bit then trigger paste\n          setTimeout(() => {\n            // Create and dispatch a paste event\n            const pasteEvent = new ClipboardEvent('paste', {\n              bubbles: true,\n              cancelable: true,\n              clipboardData: new DataTransfer(),\n            });\n\n            // Add the text to clipboard data\n            if (pasteEvent.clipboardData) {\n              pasteEvent.clipboardData.setData('text/plain', text);\n            }\n\n            console.log('Dispatching paste event');\n            const pasteResult = inputElement.dispatchEvent(pasteEvent);\n            console.log('Paste event result:', pasteResult);\n\n            // Check if it worked\n            setTimeout(() => {\n              console.log('Content after paste event:', getInputText(inputElement));\n\n              // If paste event didn't work, try execCommand paste\n              if (!getInputText(inputElement).includes(text.substring(0, 10))) {\n                console.log('Paste event failed, trying execCommand paste');\n                const execPasteResult = document.execCommand('paste');\n                console.log('execCommand paste result:', execPasteResult);\n\n                setTimeout(() => {\n                  console.log('Content after execCommand paste:', getInputText(inputElement));\n\n                  // If still not working, try the typing simulation\n                  if (!getInputText(inputElement).includes(text.substring(0, 10))) {\n                    console.log('All clipboard approaches failed, trying typing simulation');\n                    simulateTyping(inputElement, text);\n                  }\n                }, 100);\n              }\n            }, 100);\n          }, 100);\n        })\n        .catch(error => {\n          console.log('Clipboard write failed:', error);\n          // Fallback to typing simulation\n          simulateTyping(inputElement, text);\n        });\n    } else {\n      console.log('Clipboard API not available, falling back to typing simulation');\n      simulateTyping(inputElement, text);\n    }\n  }\n}\n\n// Helper function to simulate typing using Selection API\nfunction simulateTyping(inputElement: HTMLElement, text: string): void {\n  console.log('simulateTyping called with text:', text);\n  console.log('inputElement:', inputElement);\n\n  inputElement.focus();\n  console.log('Input element focused');\n\n  // Try using Selection API with Range to insert text\n  const selection = window.getSelection();\n\n  // Clear existing content by selecting all\n  if (!selection) {\n    return;\n  }\n  selection.selectAllChildren(inputElement);\n  console.log('Selected all children');\n\n  // Try to delete existing content first\n  selection.deleteFromDocument();\n  console.log('Deleted existing content');\n\n  // Now try to insert the new text using different methods\n\n  // Method 1: Try using insertText with Selection API\n  console.log('Attempting Method 1: Selection API insertText');\n  try {\n    if (!selection) {\n      throw new Error('No selection');\n    }\n    const range = selection.getRangeAt(0);\n    range.deleteContents();\n    range.insertNode(document.createTextNode(text));\n    console.log('Method 1 succeeded - inserted text node');\n    console.log('Content after Method 1:', getInputText(inputElement));\n\n    // If this worked, we're done\n    if (getInputText(inputElement).includes(text.substring(0, 10))) {\n      console.log('Method 1 worked! Content updated successfully');\n      return;\n    }\n  } catch (error) {\n    console.log('Method 1 failed:', error);\n  }\n\n  // Method 2: Try using execCommand with composition events\n  console.log('Attempting Method 2: execCommand with composition');\n  try {\n    // Start composition\n    const compositionStart = new CompositionEvent('compositionstart', {\n      bubbles: true,\n      cancelable: true,\n      data: '',\n    });\n    inputElement.dispatchEvent(compositionStart);\n\n    // Update composition\n    const compositionUpdate = new CompositionEvent('compositionupdate', {\n      bubbles: true,\n      cancelable: true,\n      data: text,\n    });\n    inputElement.dispatchEvent(compositionUpdate);\n\n    // End composition\n    const compositionEnd = new CompositionEvent('compositionend', {\n      bubbles: true,\n      cancelable: true,\n      data: text,\n    });\n    inputElement.dispatchEvent(compositionEnd);\n\n    console.log('Method 2 composition events dispatched');\n    console.log('Content after Method 2:', getInputText(inputElement));\n\n    // If this worked, we're done\n    if (getInputText(inputElement).includes(text.substring(0, 10))) {\n      console.log('Method 2 worked! Content updated successfully');\n      return;\n    }\n  } catch (error) {\n    console.log('Method 2 failed:', error);\n  }\n\n  // Method 3: Try direct DOM manipulation with mutation observer disabled\n  console.log('Attempting Method 3: Direct DOM manipulation');\n  try {\n    // Find or create the paragraph structure\n    let paragraph = inputElement.querySelector('p[dir=\"ltr\"]');\n    if (!paragraph) {\n      paragraph = document.createElement('p');\n      paragraph.setAttribute('dir', 'ltr');\n      inputElement.appendChild(paragraph);\n    }\n\n    // Create a span with the text\n    const span = document.createElement('span');\n    span.setAttribute('data-lexical-text', 'true');\n    span.textContent = text;\n\n    // Clear existing content and add new span\n    paragraph.innerHTML = '';\n    paragraph.appendChild(span);\n\n    console.log('Method 3 DOM manipulation completed');\n    console.log('Content after Method 3:', getInputText(inputElement));\n\n    // Dispatch events to notify Lexical\n    inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n    inputElement.dispatchEvent(new Event('change', { bubbles: true }));\n\n    // If this worked, we're done\n    if (getInputText(inputElement).includes(text.substring(0, 10))) {\n      console.log('Method 3 worked! Content updated successfully');\n      return;\n    }\n  } catch (error) {\n    console.log('Method 3 failed:', error);\n  }\n\n  // Method 4: Try using keyboard simulation\n  console.log('Attempting Method 4: Keyboard simulation');\n  try {\n    // Clear content with Ctrl+A and Delete\n    inputElement.dispatchEvent(\n      new KeyboardEvent('keydown', { key: 'a', ctrlKey: true, bubbles: true })\n    );\n    inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));\n\n    // Type each character with keyboard events\n    for (let i = 0; i < text.length; i++) {\n      const char = text.charAt(i);\n      inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true }));\n      inputElement.dispatchEvent(new KeyboardEvent('keypress', { key: char, bubbles: true }));\n      inputElement.dispatchEvent(new KeyboardEvent('keyup', { key: char, bubbles: true }));\n    }\n\n    console.log('Method 4 keyboard simulation completed');\n    console.log('Content after Method 4:', getInputText(inputElement));\n  } catch (error) {\n    console.log('Method 4 failed:', error);\n  }\n\n  console.log('All methods attempted. Final content:', getInputText(inputElement));\n}\n\n// Function to add the mem0 button to the UI\nasync function addMem0Button() {\n  // First check if memory is enabled\n  const memoryEnabled = await getMemoryEnabledState();\n  if (!memoryEnabled) {\n    // If memory is disabled, remove the button if it exists\n    const existingButton = document.querySelector('.mem0-button-wrapper');\n    if (existingButton) {\n      existingButton.remove();\n    }\n    return;\n  }\n\n  // Prefer OPENMEMORY_UI mounts; if available, use them instead of manual injection\n  if (OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus) {\n    try {\n      if (!document.getElementById('mem0-icon-button')) {\n        OPENMEMORY_UI.resolveCachedAnchor(\n          { learnKey: location.host + ':' + location.pathname },\n          null,\n          24 * 60 * 60 * 1000\n        ).then(function (hit: { el: Element; placement: Placement | null } | null) {\n          if (!hit || !hit.el) {\n            return;\n          }\n          let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n          let host = hs.host,\n            shadow = hs.shadow;\n          host.id = 'mem0-icon-button';\n          const cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.perplexity\n              ? SITE_CONFIG.perplexity\n              : null;\n          let placement = hit.placement ||\n            (cfg && cfg.placement) || {\n              strategy: 'inline',\n              where: 'beforeend',\n              inlineAlign: 'end',\n            };\n          OPENMEMORY_UI.applyPlacement({ container: host, anchor: hit.el, placement: placement });\n          let style = document.createElement('style');\n          style.textContent = `\n              :host { position: relative; }\n              .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n              .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n              .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n              :host([data-has-text=\"1\"]) .dot { display:block; }\n            `;\n          let btn = document.createElement('button');\n          btn.className = 'mem0-btn';\n          let img = document.createElement('img');\n          img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n          let dot = document.createElement('div');\n          dot.className = 'dot';\n          btn.appendChild(img);\n          shadow.append(style, btn, dot);\n          btn.addEventListener('click', function () {\n            handleMem0Modal();\n          });\n          if (typeof updateNotificationDot === 'function') {\n            setTimeout(updateNotificationDot, 0);\n          }\n\n          try {\n            const cfg =\n              typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.perplexity\n                ? SITE_CONFIG.perplexity\n                : null;\n            let modelSel = cfg && cfg.modelButtonSelector;\n            let sendSel = cfg && cfg.sendButtonSelector;\n            let modelBtn = modelSel ? document.querySelector(modelSel) : null;\n            let anchorBtn = modelBtn || (sendSel ? document.querySelector(sendSel) : null);\n            if (anchorBtn) {\n              let container = anchorBtn.parentElement || anchorBtn;\n              let probe: Element | null = container;\n              let hops = 0;\n              while (probe && hops < 5) {\n                let cs = getComputedStyle(probe);\n                if (cs.display === 'flex' && cs.flexDirection !== 'column') {\n                  container = probe as HTMLElement;\n                  break;\n                }\n                probe = probe.parentElement;\n                hops++;\n              }\n              if (modelBtn) {\n                container.insertBefore(host, anchorBtn.nextSibling);\n              } else {\n                if (host.parentElement !== container || host !== container.firstElementChild) {\n                  container.insertBefore(host, container.firstElementChild || anchorBtn);\n                }\n              }\n              host.style.marginLeft = '0';\n              host.style.marginRight = '0';\n              if (OPENMEMORY_UI && OPENMEMORY_UI.saveAnchorHint) {\n                try {\n                  OPENMEMORY_UI.saveAnchorHint(\n                    { learnKey: location.host + ':' + location.pathname },\n                    container,\n                    { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n                    true\n                  );\n                } catch (_) {\n                  // Ignore errors during re-initialization\n                }\n              }\n            }\n          } catch (_) {\n            // Ignore errors during re-initialization\n          }\n        });\n      }\n    } catch (_) {\n      // Ignore errors during re-initialization\n    }\n\n    OPENMEMORY_UI.mountOnEditorFocus({\n      existingHostSelector: '#mem0-icon-button, .mem0-root',\n      editorSelector:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.perplexity &&\n        SITE_CONFIG.perplexity.editorSelector\n          ? SITE_CONFIG.perplexity.editorSelector\n          : 'textarea, [contenteditable], input[type=\"text\"]',\n      deriveAnchor:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.perplexity &&\n        typeof SITE_CONFIG.perplexity.deriveAnchor === 'function'\n          ? SITE_CONFIG.perplexity.deriveAnchor\n          : function (editor: Element) {\n              return editor.closest('form') || editor.parentElement;\n            },\n      placement:\n        typeof SITE_CONFIG !== 'undefined' &&\n        SITE_CONFIG.perplexity &&\n        SITE_CONFIG.perplexity.placement\n          ? SITE_CONFIG.perplexity.placement\n          : { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n      render: function (shadow: ShadowRoot, host: HTMLElement) {\n        host.id = 'mem0-icon-button';\n        let style = document.createElement('style');\n        style.textContent = `\n          :host { position: relative; }\n          .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n          .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n          .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n          :host([data-has-text=\"1\"]) .dot { display:block; }\n        `;\n        let btn = document.createElement('button');\n        btn.className = 'mem0-btn';\n        let img = document.createElement('img');\n        img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n        let dot = document.createElement('div');\n        dot.className = 'dot';\n        btn.appendChild(img);\n        shadow.append(style, btn, dot);\n        btn.addEventListener('click', function () {\n          handleMem0Modal();\n        });\n        if (typeof updateNotificationDot === 'function') {\n          setTimeout(updateNotificationDot, 0);\n        }\n\n        try {\n          // Dedupe existing hosts, keep the current one\n          Array.from(document.querySelectorAll('#mem0-icon-button, .mem0-root'))\n            .filter(function (n) {\n              return n !== host;\n            })\n            .forEach(function (n) {\n              try {\n                n.remove();\n              } catch (_) {\n                // Ignore errors during re-initialization\n              }\n            });\n          let cfg =\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.perplexity\n              ? SITE_CONFIG.perplexity\n              : null;\n          let modelSel = cfg && cfg.modelButtonSelector;\n          let sendSel = cfg && cfg.sendButtonSelector;\n          let modelBtn = modelSel ? document.querySelector(modelSel) : null;\n          let anchorBtn = modelBtn || (sendSel ? document.querySelector(sendSel) : null);\n          if (anchorBtn) {\n            let container = anchorBtn.parentElement || anchorBtn;\n            let probe: Element | null = container;\n            let hops = 0;\n            while (probe && hops < 5) {\n              let cs = getComputedStyle(probe);\n              if (cs.display === 'flex' && cs.flexDirection !== 'column') {\n                container = probe as HTMLElement;\n                break;\n              }\n              probe = probe.parentElement;\n              hops++;\n            }\n            // Always place as the left-most icon in the group\n            let first = container.firstElementChild || anchorBtn;\n            if (host.parentElement !== container || host !== first) {\n              container.insertBefore(host, first);\n            }\n            let gap = getComputedStyle(container).gap;\n            if (!gap || gap === 'normal') {\n              gap = '8px';\n            }\n            host.style.marginLeft = gap;\n            host.style.marginRight = '0';\n          }\n        } catch (_) {\n          // Ignore errors during re-initialization\n        }\n      },\n      fallback: function () {\n        let cfg =\n          typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.perplexity\n            ? SITE_CONFIG.perplexity\n            : null;\n        return OPENMEMORY_UI.mountResilient({\n          anchors: [\n            {\n              find: function () {\n                let sel =\n                  (cfg && cfg.editorSelector) || 'textarea, [contenteditable], input[type=\"text\"]';\n                let ed = document.querySelector(sel);\n                if (!ed) {\n                  return null;\n                }\n                try {\n                  return cfg && typeof cfg.deriveAnchor === 'function'\n                    ? cfg.deriveAnchor(ed)\n                    : ed.closest('form') || ed.parentElement;\n                } catch (_) {\n                  // Ignore errors during re-initialization\n                  return ed.closest('form') || ed.parentElement;\n                }\n              },\n            },\n          ],\n          placement: (cfg && cfg.placement) || {\n            strategy: 'inline',\n            where: 'beforeend',\n            inlineAlign: 'end',\n          },\n          enableFloatingFallback: true,\n          render: function (shadow: ShadowRoot, host: HTMLElement) {\n            host.id = 'mem0-icon-button';\n            let style = document.createElement('style');\n            style.textContent = `\n              :host { position: relative; }\n              .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:50%; }\n              .mem0-btn img { width:18px; height:18px; border-radius:50%; }\n              .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n              :host([data-has-text=\"1\"]) .dot { display:block; }\n            `;\n            let btn = document.createElement('button');\n            btn.className = 'mem0-btn';\n            let img = document.createElement('img');\n            img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n            let dot = document.createElement('div');\n            dot.className = 'dot';\n            btn.appendChild(img);\n            shadow.append(style, btn, dot);\n            btn.addEventListener('click', function () {\n              handleMem0Modal();\n            });\n            if (typeof updateNotificationDot === 'function') {\n              setTimeout(updateNotificationDot, 0);\n            }\n\n            try {\n              Array.from(document.querySelectorAll('#mem0-icon-button, .mem0-root'))\n                .filter(function (n) {\n                  return n !== host;\n                })\n                .forEach(function (n) {\n                  try {\n                    n.remove();\n                  } catch (_) {\n                    // Ignore errors during re-initialization\n                  }\n                });\n              let cfg =\n                typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.perplexity\n                  ? SITE_CONFIG.perplexity\n                  : null;\n              let modelSel = cfg && cfg.modelButtonSelector;\n              let sendSel = cfg && cfg.sendButtonSelector;\n              let modelBtn = modelSel ? document.querySelector(modelSel) : null;\n              let anchorBtn = modelBtn || (sendSel ? document.querySelector(sendSel) : null);\n              if (anchorBtn) {\n                let container = anchorBtn.parentElement || anchorBtn;\n                let probe: Element | null = container;\n                let hops = 0;\n                while (probe && hops < 5) {\n                  let cs = getComputedStyle(probe);\n                  if (cs.display === 'flex' && cs.flexDirection !== 'column') {\n                    container = probe as HTMLElement;\n                    break;\n                  }\n                  probe = probe.parentElement;\n                  hops++;\n                }\n                if (modelBtn) {\n                  container.insertBefore(host, anchorBtn.nextSibling);\n                } else {\n                  if (host.parentElement !== container || host.nextSibling !== anchorBtn) {\n                    container.insertBefore(host, anchorBtn);\n                  }\n                }\n                let gap = getComputedStyle(container).gap;\n                if (!gap || gap === 'normal') {\n                  gap = '8px';\n                }\n                host.style.marginLeft = gap;\n                host.style.marginRight = '0';\n              }\n            } catch (_) {\n              // Ignore errors during re-initialization\n            }\n          },\n        });\n      },\n    });\n    return;\n  }\n\n  // Create a wrapper for the button and tooltip\n  const mem0ButtonWrapper = document.createElement('div');\n  mem0ButtonWrapper.className = 'mem0-button-wrapper';\n  mem0ButtonWrapper.style.cssText = `\n    position: relative;\n    display: inline-block;\n  `;\n\n  // Create tooltip element\n  const tooltip = document.createElement('div');\n  tooltip.className = 'mem0-tooltip';\n  tooltip.style.cssText = `\n    visibility: hidden;\n    background-color: #27272A;\n    color: #fff;\n    text-align: center;\n    border-radius: 6px;\n    padding: 6px 10px;\n    position: absolute;\n    z-index: 10000;\n    bottom: 125%;\n    left: 50%;\n    transform: translateX(-50%);\n    opacity: 0;\n    transition: opacity 0.3s;\n    font-size: 12px;\n    white-space: nowrap;\n    pointer-events: none;\n    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);\n    border: 1px solid #3B3B3F;\n  `;\n  tooltip.textContent = 'Add memories to your prompt';\n\n  // Add tooltip arrow\n  const tooltipArrow = document.createElement('div');\n  tooltipArrow.style.cssText = `\n    content: \"\";\n    position: absolute;\n    top: 100%;\n    left: 50%;\n    margin-left: -5px;\n    border-width: 5px;\n    border-style: solid;\n    border-color: #27272A transparent transparent transparent;\n  `;\n\n  tooltip.appendChild(tooltipArrow);\n  mem0ButtonWrapper.appendChild(tooltip);\n\n  // Create the mem0 button\n  const mem0Button = document.createElement('button');\n  mem0Button.className =\n    'mem0-claude-btn focus-visible:bg-offsetPlus dark:focus-visible:bg-offsetPlusDark hover:bg-offsetPlus text-textOff dark:text-textOffDark hover:text-textMain dark:hover:bg-offsetPlusDark dark:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-lg cursor-pointer active:scale-[0.97] active:duration-150 active:ease-outExpo origin-center whitespace-nowrap inline-flex text-sm h-8 aspect-[9/8]';\n  mem0Button.setAttribute('aria-label', 'Mem0 AI');\n  mem0Button.setAttribute('type', 'button');\n  mem0Button.style.position = 'relative';\n\n  // Create notification dot\n  const notificationDot = document.createElement('div');\n  notificationDot.id = 'mem0-notification-dot';\n  notificationDot.style.cssText = `\n    position: absolute;\n    top: -3px;\n    right: -3px;\n    width: 10px;\n    height: 10px;\n    background-color: rgb(128, 221, 162);\n    border-radius: 50%;\n    border: 2px solid #18181B;\n    display: none;\n    z-index: 1001;\n    pointer-events: none;\n  `;\n\n  // Add keyframe animation for the dot\n  if (!document.getElementById('notification-dot-animation')) {\n    const style = document.createElement('style');\n    style.id = 'notification-dot-animation';\n    style.innerHTML = `\n      @keyframes popIn {\n        0% { transform: scale(0); }\n        50% { transform: scale(1.2); }\n        100% { transform: scale(1); }\n      }\n      \n      #mem0-notification-dot.active {\n        display: block !important;\n        animation: popIn 0.3s ease-out forwards;\n      }\n    `;\n    document.head.appendChild(style);\n  }\n\n  // Create inner structure similar to other buttons\n  mem0Button.innerHTML = `\n    <div class=\"flex items-center min-w-0 font-medium gap-1.5 justify-center\">\n      <div class=\"flex shrink-0 items-center justify-center size-4\">\n        <img src=\"${chrome.runtime.getURL('icons/mem0-claude-icon-p.png')}\" alt=\"Mem0 AI\" width=\"14\" height=\"14\" />\n      </div>\n    </div>\n  `;\n\n  // Add the notification dot to the button\n  mem0Button.appendChild(notificationDot);\n  // Try to find the right-side icons container near the submit button and prepend there\n  let iconsContainer = null;\n  let modelBtn = null;\n  try {\n    // Prefer explicit model button from site config\n    try {\n      let cfg =\n        typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.perplexity\n          ? SITE_CONFIG.perplexity\n          : null;\n      let modelSel = cfg && cfg.modelButtonSelector;\n      if (modelSel) {\n        modelBtn = document.querySelector(modelSel);\n      }\n    } catch (_) {\n      // Ignore errors during re-initialization\n    }\n\n    // 1) Exact class combo (from screenshot) – Tailwind classes require escaping ':'\n    iconsContainer = document.querySelector(\n      'div.bg-raised.dark\\\\:bg-offset.flex.items-center.justify-self-end.rounded-full'\n    );\n    // 2) Slightly relaxed class combo\n    if (!iconsContainer) {\n      iconsContainer = document.querySelector('div.bg-raised.flex.items-center.rounded-full');\n    }\n    // 3) Attribute-contains selector as a robust fallback\n    if (!iconsContainer) {\n      iconsContainer = document.querySelector(\n        'div[class*=\"bg-raised\"][class*=\"items-center\"][class*=\"rounded-full\"]'\n      );\n    }\n    // 4) Fallback based on Submit button proximity\n    if (!iconsContainer) {\n      const submitBtn = document.querySelector('button[aria-label=\"Submit\"]');\n      if (submitBtn && submitBtn.parentElement) {\n        const sibling = submitBtn.parentElement.previousElementSibling;\n        if (sibling && sibling.querySelectorAll('button').length > 0) {\n          iconsContainer = sibling;\n        }\n        if (!iconsContainer) {\n          const candidates = submitBtn.parentElement.querySelectorAll('div');\n          for (const c of Array.from(candidates)) {\n            if (c.querySelectorAll('button').length >= 2) {\n              iconsContainer = c;\n              break;\n            }\n          }\n        }\n      }\n    }\n  } catch (_) {\n    // Ignore errors during re-initialization\n  }\n\n  // If model button found, insert right after it; else, insert at start of icon group\n  if (modelBtn && modelBtn.parentElement) {\n    let container = modelBtn.parentElement;\n    // If the immediate parent is not a horizontal flex, walk up a few levels to find one\n    try {\n      let probe: Element | null = container;\n      let hops = 0;\n      while (probe && hops < 5) {\n        let cs = getComputedStyle(probe);\n        if (cs.display === 'flex' && cs.flexDirection !== 'column') {\n          container = probe as HTMLElement;\n          break;\n        }\n        probe = probe.parentElement;\n        hops++;\n      }\n    } catch (_) {\n      // Ignore errors during re-initialization\n    }\n    // Place as the first icon in the group\n    container.insertBefore(mem0ButtonWrapper, container.firstElementChild);\n  } else if (iconsContainer) {\n    iconsContainer.insertBefore(mem0ButtonWrapper, iconsContainer.firstChild);\n  }\n\n  // Add the button to the wrapper first\n  mem0ButtonWrapper.appendChild(mem0Button);\n\n  // Find the input container and insert the button to the LEFT of the input\n  let inputContainer: HTMLElement | null = null;\n  const inputEl = getTextarea();\n\n  if (inputEl) {\n    // Try to find the input container by looking for the parent that contains the input\n    let currentElement: HTMLElement | null = inputEl as HTMLElement;\n    while (currentElement && currentElement !== document.body) {\n      // Look for a container that has flex layout and contains the input\n      const computedStyle = window.getComputedStyle(currentElement);\n      if (computedStyle.display === 'flex' && currentElement.contains(inputEl)) {\n        inputContainer = currentElement;\n        break;\n      }\n      currentElement = currentElement.parentElement;\n    }\n\n    // Fallback: use the direct parent of the input\n    if (!inputContainer) {\n      inputContainer = inputEl.parentElement;\n    }\n  }\n\n  if (!inputContainer) {\n    setTimeout(addMem0Button, 500);\n    return;\n  }\n\n  // Insert the button as the FIRST child of the input container (leftmost position)\n  inputContainer.insertBefore(mem0ButtonWrapper, inputContainer.firstChild);\n\n  // Style the button to match the input area\n  mem0Button.style.width = '32px';\n  mem0Button.style.height = '32px';\n  mem0Button.style.borderRadius = '8px';\n  mem0Button.style.background = 'transparent';\n  mem0Button.style.display = 'inline-flex';\n  mem0Button.style.alignItems = 'center';\n  mem0Button.style.justifyContent = 'center';\n  mem0Button.style.marginRight = '8px';\n  mem0ButtonWrapper.style.display = 'inline-flex';\n  mem0ButtonWrapper.style.alignItems = 'center';\n  mem0ButtonWrapper.style.justifyContent = 'center';\n\n  // Add hover effect for tooltip\n  mem0ButtonWrapper.addEventListener('mouseenter', () => {\n    tooltip.style.visibility = 'visible';\n    tooltip.style.opacity = '1';\n  });\n\n  mem0ButtonWrapper.addEventListener('mouseleave', () => {\n    tooltip.style.visibility = 'hidden';\n    tooltip.style.opacity = '0';\n  });\n\n  // Add click event listener - modified to check login first and fix empty text case\n  mem0Button.addEventListener('click', () => {\n    // Get the current input text\n    const textarea = getTextarea();\n\n    if (textarea && getInputText(textarea).trim()) {\n      // If there's text in the input, process memories\n      handleMem0Modal('mem0-icon-button');\n    } else {\n      // If no text, check login status first\n      chrome.storage.sync.get(\n        [StorageKey.API_KEY, StorageKey.USER_ID_CAMEL, StorageKey.ACCESS_TOKEN],\n        function (items) {\n          if (!items.apiKey && !items.access_token) {\n            // Not logged in, show login popup\n            showLoginPopup();\n          } else {\n            // Logged in but no text, show tooltip message\n            const originalText = tooltip.textContent;\n            tooltip.textContent = 'Add some text to find memories';\n            tooltip.style.visibility = 'visible';\n            tooltip.style.opacity = '1';\n\n            // Reset the tooltip after a delay\n            setTimeout(() => {\n              tooltip.textContent = originalText;\n              if (!mem0ButtonWrapper.matches(':hover')) {\n                tooltip.style.visibility = 'hidden';\n                tooltip.style.opacity = '0';\n              }\n            }, 1500);\n          }\n        }\n      );\n    }\n  });\n\n  // Setup the notification dot based on input content\n  updateNotificationDot();\n}\n\n// Function to update the notification dot based on input content\nfunction updateNotificationDot() {\n  const textarea = getTextarea();\n  const host = document.getElementById('mem0-icon-button');\n  if (!textarea || !host) {\n    setTimeout(updateNotificationDot, 500);\n    return;\n  }\n\n  const applyState = () => {\n    const inputText = getInputText(textarea) || '';\n    const hasText = inputText.trim() !== '';\n    try {\n      host.setAttribute('data-has-text', hasText ? '1' : '0');\n    } catch (_) {\n      // Ignore errors during re-initialization\n    }\n  };\n\n  // Observe and listen for changes\n  try {\n    const mo = new MutationObserver(applyState);\n    mo.observe(textarea, { attributes: true, childList: true, characterData: true, subtree: true });\n  } catch (_) {\n    // Ignore errors during re-initialization\n  }\n\n  if (!textarea.dataset.perplexityApplyStateHooked) {\n    textarea.dataset.perplexityApplyStateHooked = 'true';\n    textarea.addEventListener('input', applyState);\n    textarea.addEventListener('keyup', applyState);\n    textarea.addEventListener('focus', applyState);\n  }\n  applyState();\n  setTimeout(applyState, 300);\n}\n\n// Function to create memory modal\nfunction createMemoryModal(\n  memoryItems: MemoryItem[],\n  isLoading: boolean = false,\n  sourceButtonId: string | null = null\n) {\n  // Close existing modal if it exists\n  if (memoryModalShown && currentModalOverlay) {\n    document.body.removeChild(currentModalOverlay);\n  }\n\n  memoryModalShown = true;\n  let currentMemoryIndex = 0;\n\n  // Calculate modal dimensions (estimated)\n  const modalWidth = 447;\n  let modalHeight = 400; // Default height\n  let memoriesPerPage = 3; // Default number of memories per page\n\n  let topPosition;\n  let leftPosition;\n\n  // Use saved position if available (for dragged modals)\n  if (modalPosition.top !== null && modalPosition.left !== null) {\n    topPosition = modalPosition.top;\n    leftPosition = modalPosition.left;\n  } else {\n    // Different positioning based on which button triggered the modal\n    if (sourceButtonId === 'mem0-icon-button') {\n      // Anchor to the Mem0 host/button in the right icon group when present\n      const iconButton =\n        document.querySelector('#mem0-icon-button') || document.querySelector('.mem0-claude-btn');\n      if (iconButton) {\n        const buttonRect = iconButton.getBoundingClientRect();\n\n        const viewportHeight = window.innerHeight;\n\n        // Place the modal immediately to the left of the icon group\n        leftPosition = Math.max(10, buttonRect.left - modalWidth - 10);\n        // Vertically center relative to the icon height, but keep on-screen\n        topPosition = buttonRect.top + buttonRect.height / 2 - modalHeight / 2;\n        topPosition = Math.max(10, Math.min(topPosition, viewportHeight - modalHeight - 10));\n      } else {\n        // Fallback to default positioning\n        positionDefault();\n      }\n    } else {\n      // Default positioning\n      positionDefault();\n    }\n  }\n\n  // Helper function for default positioning\n  function positionDefault() {\n    // Prefer the actual Mem0 host if present; otherwise, try to use the right icon group container\n    let anchor =\n      document.querySelector('#mem0-icon-button') || document.querySelector('.mem0-claude-btn');\n    if (!anchor) {\n      // Heuristics to find the right-side icon group near the submit button\n      try {\n        anchor =\n          document.querySelector(\n            'div.bg-raised.dark\\\\:bg-offset.flex.items-center.justify-self-end.rounded-full'\n          ) ||\n          document.querySelector('div.bg-raised.flex.items-center.rounded-full') ||\n          document.querySelector(\n            'div[class*=\"bg-raised\"][class*=\"items-center\"][class*=\"rounded-full\"]'\n          );\n        if (!anchor) {\n          const submitBtn = document.querySelector('button[aria-label=\"Submit\"]');\n          if (submitBtn && submitBtn.parentElement) {\n            const sibling = submitBtn.parentElement.previousElementSibling;\n            if (sibling && sibling.querySelectorAll('button').length > 0) {\n              anchor = sibling;\n            }\n          }\n        }\n      } catch (_) {\n        // Ignore errors during re-initialization\n      }\n    }\n\n    if (!anchor) {\n      console.error('Mem0 anchor not found for positioning');\n      // As a last resort, place near center\n      leftPosition = Math.max(10, (window.innerWidth - modalWidth) / 2);\n      topPosition = Math.max(10, (window.innerHeight - modalHeight) / 2);\n      return;\n    }\n\n    const rect = anchor.getBoundingClientRect();\n    const viewportHeight = window.innerHeight;\n    // Place the modal immediately to the left of the anchor/group\n    leftPosition = Math.max(10, rect.left - modalWidth - 10);\n    // Vertically center relative to the group, clamped to viewport\n    topPosition = rect.top + rect.height / 2 - modalHeight / 2;\n    topPosition = Math.max(10, Math.min(topPosition, viewportHeight - modalHeight - 10));\n  }\n\n  // Create modal overlay\n  const modalOverlay = document.createElement('div');\n  modalOverlay.id = 'mem0-modal-overlay';\n  modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n  // Save reference to current modal overlay\n  currentModalOverlay = modalOverlay;\n\n  // Add event listener to close modal when clicking outside\n  modalOverlay.addEventListener('click', event => {\n    // Only close if clicking directly on the overlay, not its children\n    if (event.target === modalOverlay) {\n      closeModal();\n    }\n  });\n\n  // Create modal container with positioning\n  const modalContainer = document.createElement('div');\n\n  // Position the modal below or above the button\n  modalContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    position: absolute;\n    top: ${topPosition}px;\n    left: ${leftPosition}px;\n    pointer-events: auto;\n    border: 1px solid #27272A;\n    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    overflow: hidden;\n  `;\n\n  // Create modal header\n  const modalHeader = document.createElement('div');\n  modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #232325;\n    flex-shrink: 0;\n    cursor: move;\n    user-select: none;\n  `;\n\n  // Create header left section with logo and title\n  const headerLeft = document.createElement('div');\n  headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  `;\n\n  // Add Mem0 logo and title to header\n  const logoImg = document.createElement('img');\n  logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n  `;\n\n  // Create title element\n  const title = document.createElement('div');\n  title.textContent = 'OpenMemory';\n  title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-left: 8px;\n  `;\n\n  // Create header right section with Add to Prompt button\n  const headerRight = document.createElement('div');\n  headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n  `;\n\n  // Create Add to Prompt button with arrow\n  const addToPromptBtn = document.createElement('button');\n  addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color: white;\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: black;\n  `;\n  addToPromptBtn.textContent = 'Add to Prompt';\n\n  // Add arrow icon to button\n  const arrowIcon = document.createElement('span');\n  arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>\n`;\n  addToPromptBtn.appendChild(arrowIcon);\n\n  // Add click handler for the Add to Prompt button\n  addToPromptBtn.addEventListener('click', () => {\n    // Only add memories that are not already added\n    const newMemories = memoryItems\n      .filter(memory => !allMemoriesById.has(String(memory.id)))\n      .map(memory => {\n        allMemoriesById.add(String(memory.id));\n        return String(memory.text || '');\n      });\n\n    sendExtensionEvent('memory_injection', {\n      provider: 'perplexity',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n      injected_all: true,\n      memory_count: newMemories.length,\n    });\n\n    // Add all new memories to allMemories\n    allMemories.push(...newMemories);\n\n    // Update the input with all memories\n    if (allMemories.length > 0) {\n      updateInputWithMemories();\n      closeModal();\n    } else {\n      // If no new memories were added but we have existing ones, just close\n      if (allMemoriesById.size > 0) {\n        closeModal();\n      }\n    }\n\n    // Remove all added memories from the memoryItems list\n    for (let i = memoryItems.length - 1; i >= 0; i--) {\n      if (allMemoriesById.has(String(memoryItems[i]?.id))) {\n        memoryItems.splice(i, 1);\n      }\n    }\n  });\n\n  // Create settings button\n  const settingsBtn = document.createElement('button');\n  settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n  settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n  // Add click event to open app.mem0.ai in a new tab\n  settingsBtn.addEventListener('click', () => {\n    if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n      document.body.removeChild(currentModalOverlay);\n      memoryModalShown = false;\n      currentModalOverlay = null;\n    }\n\n    chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n  });\n\n  // Add hover effect for the settings button\n  settingsBtn.addEventListener('mouseenter', () => {\n    settingsBtn.style.opacity = '1';\n  });\n  settingsBtn.addEventListener('mouseleave', () => {\n    settingsBtn.style.opacity = '0.6';\n  });\n\n  // Assemble header\n  headerLeft.appendChild(logoImg);\n  headerLeft.appendChild(title);\n\n  headerRight.appendChild(addToPromptBtn);\n  headerRight.appendChild(settingsBtn);\n\n  modalHeader.appendChild(headerLeft);\n  modalHeader.appendChild(headerRight);\n\n  // Add drag functionality\n  let startX = 0;\n  let startY = 0;\n  let initialX = 0;\n  let initialY = 0;\n\n  modalHeader.addEventListener('mousedown', (e: MouseEvent) => {\n    const target = e.target as HTMLElement | null;\n    if (target && (target === modalHeader || modalHeader.contains(target))) {\n      // Don't start drag if clicking on buttons\n      if (target.tagName === 'BUTTON' || target.closest('button')) {\n        return;\n      }\n\n      isDragging = true;\n      startX = e.clientX;\n      startY = e.clientY;\n\n      // Get current position\n      const rect = modalContainer.getBoundingClientRect();\n      initialX = rect.left;\n      initialY = rect.top;\n\n      modalContainer.style.transition = 'none';\n      modalContainer.style.opacity = '1';\n      document.addEventListener('mousemove', handleMouseMove);\n      document.addEventListener('mouseup', handleMouseUp);\n\n      e.preventDefault();\n    }\n  });\n\n  function handleMouseMove(e: MouseEvent) {\n    if (!isDragging) {\n      return;\n    }\n\n    const deltaX = e.clientX - startX;\n    const deltaY = e.clientY - startY;\n\n    let newX = initialX + deltaX;\n    let newY = initialY + deltaY;\n\n    // Keep modal within viewport bounds\n    const maxX = window.innerWidth - modalWidth;\n    const maxY = window.innerHeight - modalHeight;\n\n    newX = Math.max(0, Math.min(newX, maxX));\n    newY = Math.max(0, Math.min(newY, maxY));\n\n    modalContainer.style.left = newX + 'px';\n    modalContainer.style.top = newY + 'px';\n\n    // Update stored position\n    modalPosition.left = newX;\n    modalPosition.top = newY;\n  }\n\n  function handleMouseUp() {\n    if (isDragging) {\n      isDragging = false;\n      modalContainer.style.transition = '';\n      modalContainer.style.opacity = '1';\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    }\n  }\n\n  // Content section\n  const contentSection = document.createElement('div');\n  const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n  contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n  // Create memories counter\n  const memoriesCounter = document.createElement('div');\n  memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n  // Update counter text based on loading state and number of memories\n  if (isLoading) {\n    memoriesCounter.textContent = `Loading Relevant Memories...`;\n  } else {\n    memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n  }\n\n  // Calculate max height for memories content based on modal height\n  const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n  // Create memories content container with adjusted height\n  const memoriesContent = document.createElement('div');\n  memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: none;\n    -ms-overflow-style: none;\n  `;\n  memoriesContent.style.cssText += '::-webkit-scrollbar { display: none; }';\n\n  // Track currently expanded memory\n  let currentlyExpandedMemory: HTMLElement | null = null;\n\n  // Create category section\n  const categorySection = document.createElement('div');\n  categorySection.style.cssText = `\n    display: flex;\n    gap: 8px;\n    padding: 0 8px;\n  `;\n\n  // Function to create skeleton loading items (adjusted for different heights)\n  function createSkeletonItems() {\n    memoriesContent.innerHTML = '';\n\n    for (let i = 0; i < memoriesPerPage; i++) {\n      const skeletonItem = document.createElement('div');\n      skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #27272A;\n        border-radius: 8px;\n        height: 72px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n      const skeletonText = document.createElement('div');\n      skeletonText.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n      const skeletonText2 = document.createElement('div');\n      skeletonText2.style.cssText = `\n        background-color: #383838;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n      const skeletonActions = document.createElement('div');\n      skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n      const skeletonButton1 = document.createElement('div');\n      skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      const skeletonButton2 = document.createElement('div');\n      skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #383838;\n      `;\n\n      skeletonActions.appendChild(skeletonButton1);\n      skeletonActions.appendChild(skeletonButton2);\n\n      const textContainer = document.createElement('div');\n      textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n      textContainer.appendChild(skeletonText);\n      textContainer.appendChild(skeletonText2);\n\n      skeletonItem.appendChild(textContainer);\n      skeletonItem.appendChild(skeletonActions);\n      memoriesContent.appendChild(skeletonItem);\n    }\n\n    // Add keyframe animation to document if not exists\n    if (!document.getElementById('skeleton-animation')) {\n      const style = document.createElement('style');\n      style.id = 'skeleton-animation';\n      style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n      document.head.appendChild(style);\n    }\n  }\n\n  // Function to show memories with adjusted count based on modal position\n  function showMemories() {\n    memoriesContent.innerHTML = '';\n\n    if (isLoading) {\n      createSkeletonItems();\n      return;\n    }\n\n    if (memoryItems.length === 0) {\n      showEmptyState();\n      updateNavigationState(0, 0);\n      return;\n    }\n\n    // Use the dynamically set memoriesPerPage value\n    const memoriesToShow = Math.min(memoriesPerPage, memoryItems.length);\n\n    // Calculate total pages and current page\n    const totalPages = Math.ceil(memoryItems.length / memoriesToShow);\n    const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n    // Reset currentMemoryIndex if it's beyond the available memories\n    if (currentMemoryIndex >= memoryItems.length) {\n      currentMemoryIndex = 0;\n    }\n\n    // Update navigation buttons state\n    updateNavigationState(currentPage, totalPages);\n\n    // Count how many memories we've displayed\n    let displayedCount = 0;\n\n    // Start from the current index\n    let index = currentMemoryIndex;\n\n    while (displayedCount < memoriesToShow && index < memoryItems.length) {\n      const memory = memoryItems[index]!;\n\n      // Only display memories that haven't been added yet\n      if (!allMemoriesById.has(String(memory.id))) {\n        // Ensure memory has an ID\n        if (!memory.id) {\n          memory.id = `memory-${Date.now()}-${index}`;\n        }\n\n        const memoryContainer = document.createElement('div');\n        memoryContainer.style.cssText = `\n          display: flex;\n          flex-direction: row;\n          align-items: flex-start;\n          justify-content: space-between;\n          padding: 12px; \n          background-color: #27272A;\n          border-radius: 8px;\n          cursor: pointer;\n          transition: all 0.2s ease;\n          min-height: 72px; \n          max-height: 72px; \n          overflow: hidden;\n          flex-shrink: 0;\n        `;\n\n        const memoryText = document.createElement('div');\n        memoryText.style.cssText = `\n          font-size: 14px;\n          line-height: 1.5;\n          color: #D4D4D8;\n          flex-grow: 1;\n          display: -webkit-box;\n          -webkit-line-clamp: 2;\n          -webkit-box-orient: vertical;\n          overflow: hidden;\n          transition: all 0.2s ease;\n          height: 42px; /* Height for 2 lines of text */\n        `;\n        memoryText.textContent = String(memory.text || '');\n\n        const actionsContainer = document.createElement('div');\n        actionsContainer.style.cssText = `\n          display: flex;\n          gap: 4px;\n          margin-left: 10px;\n          flex-shrink: 0;\n        `;\n\n        // Add button\n        const addButton = document.createElement('button');\n        addButton.style.cssText = `\n          border: none;\n          cursor: pointer;\n          padding: 4px;\n          background:rgb(66, 66, 69);\n          color:rgb(199, 199, 201);\n          border-radius: 100%;\n          transition: all 0.2s ease;\n        `;\n\n        addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>`;\n\n        // Add click handler for add button\n        addButton.addEventListener('click', (e: MouseEvent) => {\n          e.stopPropagation();\n\n          sendExtensionEvent('memory_injection', {\n            provider: 'perplexity',\n            source: 'OPENMEMORY_CHROME_EXTENSION',\n            browser: getBrowser(),\n            injected_all: false,\n            memory_id: memory.id,\n          });\n\n          // Add this memory\n          allMemoriesById.add(String(memory.id));\n          allMemories.push(String(memory.text || ''));\n          updateInputWithMemories();\n\n          // Remove this memory from the list\n          const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n          if (index !== -1) {\n            memoryItems.splice(index, 1);\n\n            // Recalculate pagination after removing an item\n            // If we're on a page that's now empty, go to previous page\n            if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n              currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n            }\n\n            memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n            showMemories();\n          }\n        });\n\n        // Menu button\n        const menuButton = document.createElement('button');\n        menuButton.style.cssText = `\n          background: none;\n          border: none;\n          cursor: pointer;\n          padding: 4px;\n          color: #A1A1AA;\n        `;\n        menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n          <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n          <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n        </svg>`;\n\n        // Track expanded state\n        let isExpanded = false;\n\n        // Create remove button (hidden by default)\n        const removeButton = document.createElement('button');\n        removeButton.style.cssText = `\n          display: none;\n          align-items: center;\n          gap: 6px;\n          background:rgb(66, 66, 69);\n          color:rgb(199, 199, 201);\n          border-radius: 8px;\n          padding: 2px 4px;\n          border: none;\n          cursor: pointer;\n          font-size: 13px;\n          margin-top: 12px;\n          width: fit-content;\n        `;\n        removeButton.innerHTML = `\n          <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n          </svg>\n          Remove\n        `;\n\n        // Create content wrapper for text and remove button\n        const contentWrapper = document.createElement('div');\n        contentWrapper.style.cssText = `\n          display: flex;\n          flex-direction: column;\n          flex-grow: 1;\n        `;\n        contentWrapper.appendChild(memoryText);\n        contentWrapper.appendChild(removeButton);\n\n        // Function to expand memory\n        const expandMemory = () => {\n          if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n            currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n          }\n\n          isExpanded = true;\n          memoryText.style.webkitLineClamp = 'unset';\n          memoryText.style.height = 'auto';\n          contentWrapper.style.overflowY = 'auto';\n          contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n          contentWrapper.style.scrollbarWidth = 'none';\n          contentWrapper.style.msOverflowStyle = 'none';\n          contentWrapper.style.cssText += '::-webkit-scrollbar { display: none; }';\n          memoryContainer.style.backgroundColor = '#1C1C1E';\n          memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n          memoryContainer.style.overflow = 'hidden';\n          removeButton.style.display = 'flex';\n          currentlyExpandedMemory = memoryContainer;\n\n          // Scroll to make expanded memory visible if needed\n          memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n        };\n\n        // Function to collapse memory\n        const collapseMemory = () => {\n          isExpanded = false;\n          memoryText.style.webkitLineClamp = '2';\n          memoryText.style.height = '42px';\n          contentWrapper.style.overflowY = 'visible';\n          memoryContainer.style.backgroundColor = '#27272A';\n          memoryContainer.style.maxHeight = '72px';\n          memoryContainer.style.overflow = 'hidden';\n          removeButton.style.display = 'none';\n          currentlyExpandedMemory = null;\n        };\n\n        memoryContainer.addEventListener('collapse', collapseMemory);\n\n        menuButton.addEventListener('click', e => {\n          e.stopPropagation();\n          if (isExpanded) {\n            collapseMemory();\n          } else {\n            expandMemory();\n          }\n        });\n\n        // Add click handler for remove button\n        removeButton.addEventListener('click', (e: MouseEvent) => {\n          e.stopPropagation();\n          // Remove from memoryItems\n          const index = memoryItems.findIndex((m: MemoryItem) => m.id === memory.id);\n          if (index !== -1) {\n            memoryItems.splice(index, 1);\n\n            // If we're on the last page and it's now empty, go to previous page\n            if (currentMemoryIndex > 0 && currentMemoryIndex >= memoryItems.length) {\n              currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n            }\n\n            memoriesCounter.textContent = `${memoryItems.length} Relevant Memories`;\n            showMemories();\n          }\n        });\n\n        actionsContainer.appendChild(addButton);\n        actionsContainer.appendChild(menuButton);\n\n        memoryContainer.appendChild(contentWrapper);\n        memoryContainer.appendChild(actionsContainer);\n        memoriesContent.appendChild(memoryContainer);\n\n        // Add hover effect\n        memoryContainer.addEventListener('mouseenter', () => {\n          memoryContainer.style.backgroundColor = isExpanded ? '#18181B' : '#323232';\n        });\n        memoryContainer.addEventListener('mouseleave', () => {\n          memoryContainer.style.backgroundColor = isExpanded ? '#1C1C1E' : '#27272A';\n        });\n\n        // Increment displayed count\n        displayedCount++;\n      }\n\n      // Move to next memory\n      index++;\n    }\n\n    // If we didn't display any memories but there are available ones,\n    // reset the index and try again (this handles the case where all visible memories\n    // have been filtered out)\n    if (displayedCount === 0 && memoryItems.length > 0) {\n      currentMemoryIndex = 0;\n      showMemories();\n    } else if (displayedCount === 0) {\n      // If truly no memories available, show empty state\n      showEmptyState();\n    }\n  }\n\n  // Function to show empty state\n  function showEmptyState() {\n    memoriesContent.innerHTML = '';\n\n    const emptyContainer = document.createElement('div');\n    emptyContainer.style.cssText = `\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 32px 16px;\n      text-align: center;\n      flex: 1;\n      min-height: 200px;\n    `;\n\n    const emptyIcon = document.createElement('div');\n    emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#71717A\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>`;\n    emptyIcon.style.marginBottom = '16px';\n\n    const emptyText = document.createElement('div');\n    emptyText.textContent = 'No relevant memories found';\n    emptyText.style.cssText = `\n      color: #71717A;\n      font-size: 14px;\n      font-weight: 500;\n    `;\n\n    emptyContainer.appendChild(emptyIcon);\n    emptyContainer.appendChild(emptyText);\n    memoriesContent.appendChild(emptyContainer);\n  }\n\n  // Navigation section at bottom\n  const navigationSection = document.createElement('div');\n  navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n  // Navigation buttons\n  const prevButton = document.createElement('button');\n  prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  prevButton.style.cssText = `\n    background: #27272A;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  const nextButton = document.createElement('button');\n  nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#A1A1AA\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n  nextButton.style.cssText = prevButton.style.cssText;\n\n  // Update navigation button states\n  function updateNavigationState(currentPage: number, totalPages: number) {\n    if (memoryItems.length === 0 || totalPages === 0) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n      return;\n    }\n\n    if (isLoading || currentPage <= 1) {\n      prevButton.disabled = true;\n      prevButton.style.opacity = '0.5';\n      prevButton.style.cursor = 'not-allowed';\n    } else {\n      prevButton.disabled = false;\n      prevButton.style.opacity = '1';\n      prevButton.style.cursor = 'pointer';\n    }\n\n    if (isLoading || currentPage >= totalPages) {\n      nextButton.disabled = true;\n      nextButton.style.opacity = '0.5';\n      nextButton.style.cursor = 'not-allowed';\n    } else {\n      nextButton.disabled = false;\n      nextButton.style.opacity = '1';\n      nextButton.style.cursor = 'pointer';\n    }\n  }\n\n  // Add navigation button handlers\n  prevButton.addEventListener('click', () => {\n    if (!isLoading && currentMemoryIndex > 0) {\n      currentMemoryIndex -= memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  nextButton.addEventListener('click', () => {\n    if (!isLoading && currentMemoryIndex < memoryItems.length - memoriesPerPage) {\n      currentMemoryIndex += memoriesPerPage;\n      showMemories();\n    }\n  });\n\n  // Add hover effects\n  [prevButton, nextButton].forEach(button => {\n    button.addEventListener('mouseenter', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#323232';\n      }\n    });\n    button.addEventListener('mouseleave', () => {\n      if (!button.disabled) {\n        button.style.backgroundColor = '#27272A';\n      }\n    });\n  });\n\n  // Assemble modal\n  contentSection.appendChild(memoriesCounter);\n  contentSection.appendChild(memoriesContent);\n\n  modalContainer.appendChild(modalHeader);\n  modalContainer.appendChild(contentSection);\n\n  // Only add navigation when not in loading state\n  if (!isLoading) {\n    modalContainer.appendChild(navigationSection);\n    navigationSection.appendChild(prevButton);\n    navigationSection.appendChild(nextButton);\n  }\n\n  modalOverlay.appendChild(modalContainer);\n\n  // Append to body\n  document.body.appendChild(modalOverlay);\n\n  // Show the first memory and update navigation\n  showMemories();\n  updateNavigationState(1, Math.ceil(memoryItems.length / memoriesPerPage));\n}\n\n// Shared function to update the input field with all collected memories\nfunction updateInputWithMemories() {\n  const inputElement = getTextarea();\n\n  if (!inputElement || allMemories.length === 0) {\n    return;\n  }\n\n  // First, remove any existing memory content from the input\n  let currentContent = getInputText(inputElement);\n  const memoryMarker = '\\n\\n' + OPENMEMORY_PROMPTS.memory_marker_prefix;\n\n  if (currentContent.includes(memoryMarker)) {\n    currentContent = currentContent.substring(0, currentContent.indexOf(memoryMarker)).trim();\n  }\n\n  // Create the memory content string\n  let memoriesContent = '\\n\\n' + OPENMEMORY_PROMPTS.memory_header_text + '\\n';\n\n  // Add all memories to the content\n  allMemories.forEach((mem, index) => {\n    memoriesContent += `- ${mem}`;\n    if (index < allMemories.length - 1) {\n      memoriesContent += '\\n';\n    }\n  });\n\n  // Set the input value with the cleaned content + memories\n  setInputValue(inputElement, currentContent + memoriesContent);\n}\n\n// Add a function to get the memory_enabled state\nfunction getMemoryEnabledState(): Promise<boolean> {\n  return new Promise<boolean>(resolve => {\n    chrome.storage.sync.get([StorageKey.MEMORY_ENABLED], function (result) {\n      resolve(result.memory_enabled !== false); // Default to true if not set\n    });\n  });\n}\n\n// Function to capture and store the current message as a memory\nfunction captureAndStoreMemory() {\n  // Get the message content\n  const textarea = getTextarea();\n  if (!textarea) {\n    return;\n  }\n\n  // Get raw content from the input element\n  let message = getInputText(textarea);\n\n  if (!message || message.trim() === '') {\n    return;\n  }\n\n  // Skip if message contains the memory wrapper\n  if ((message || '').includes('Here is some of my memories to help')) {\n    // Extract only the user's original message\n    const parts = (message || '').split('Here is some of my memories to help');\n    message = (parts[0] || '').trim();\n  }\n\n  // Skip if message is empty after cleaning\n  if (!message || message.trim() === '') {\n    return;\n  }\n\n  // Asynchronously store the memory\n  chrome.storage.sync.get(\n    [\n      StorageKey.API_KEY,\n      StorageKey.USER_ID_CAMEL,\n      StorageKey.ACCESS_TOKEN,\n      StorageKey.MEMORY_ENABLED,\n      StorageKey.SELECTED_ORG,\n      StorageKey.SELECTED_PROJECT,\n      StorageKey.USER_ID,\n    ],\n    function (items) {\n      // Skip if memory is disabled or no credentials\n      if (items.memory_enabled === false || (!items.apiKey && !items.access_token)) {\n        return;\n      }\n\n      const authHeader = items.access_token\n        ? `Bearer ${items.access_token}`\n        : `Token ${items.apiKey}`;\n\n      const userId = items.userId || items.user_id || 'chrome-extension-user';\n\n      const optionalParams: OptionalApiParams = {};\n      if (items.selected_org) {\n        optionalParams.org_id = items.selected_org;\n      }\n      if (items.selected_project) {\n        optionalParams.project_id = items.selected_project;\n      }\n\n      // Send memory to mem0 API asynchronously without waiting for response\n      fetch('https://api.mem0.ai/v1/memories/', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: authHeader,\n        },\n        body: JSON.stringify({\n          messages: [{ role: MessageRole.User, content: message }],\n          user_id: userId,\n          infer: true,\n          metadata: {\n            provider: 'Perplexity',\n          },\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          ...optionalParams,\n        }),\n      }).catch(error => {\n        console.error('Error saving memory:', error);\n      });\n    }\n  );\n}\n\n// Modify the setupSubmitButtonListener function to call captureAndStoreMemory\nfunction setupSubmitButtonListener() {\n  // Find the submit button\n  const submitButton = document.querySelector('button[aria-label=\"Submit\"]');\n  if (!submitButton) {\n    setTimeout(setupSubmitButtonListener, 500);\n    return;\n  }\n\n  // Check if we already added a listener\n  if (submitButton.dataset.mem0Listener) {\n    return;\n  }\n\n  // Mark the button as having our listener\n  submitButton.dataset.mem0Listener = 'true';\n\n  // Add click event listener to the submit button\n  submitButton.addEventListener('click', () => {\n    // Capture and save memory before clearing\n    captureAndStoreMemory();\n\n    // Give a small delay to allow the submission to process\n    setTimeout(() => {\n      // Clear all memories\n      allMemories = [];\n      console.log('Message sent, memories cleared');\n    }, 100);\n  });\n\n  // Also monitor for Enter key submission\n  const textarea = getTextarea();\n  if (textarea && !textarea.dataset.mem0EnterListener) {\n    textarea.dataset.mem0EnterListener = 'true';\n\n    textarea.addEventListener('keydown', (event: KeyboardEvent) => {\n      if (event.key === 'Enter' && !event.shiftKey) {\n        // Capture and save memory before clearing\n        captureAndStoreMemory();\n\n        // User pressed Enter to submit\n        setTimeout(() => {\n          // Clear all memories\n          allMemories = [];\n          console.log('Message sent via Enter key, memories cleared');\n        }, 100);\n      }\n    });\n  }\n\n  // Set up a MutationObserver to monitor conversation flow and clear memories after answers appear\n  setupConversationObserver();\n}\n\n// Monitor the conversation for new responses\nfunction setupConversationObserver() {\n  // If we already have an observer, disconnect it\n  if (submitButtonObserver) {\n    submitButtonObserver.disconnect();\n  }\n\n  // Find the conversation container\n  const conversationContainer = document.querySelector('main');\n  if (!conversationContainer) {\n    setTimeout(setupConversationObserver, 1000);\n    return;\n  }\n\n  // Create a new observer\n  submitButtonObserver = new MutationObserver(mutations => {\n    for (const mutation of mutations) {\n      if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {\n        // Check if a new answer block has been added\n        const answersAdded = Array.from(mutation.addedNodes).some(node => {\n          if (node.nodeType === Node.ELEMENT_NODE) {\n            const el = node as Element;\n            return el.classList.contains('answer-container');\n          }\n          return false;\n        });\n\n        if (answersAdded) {\n          // New answer appeared, clear memories\n          allMemories = [];\n          console.log('New answer detected, memories cleared');\n        }\n      }\n    }\n  });\n\n  // Start observing\n  submitButtonObserver.observe(conversationContainer, {\n    childList: true,\n    subtree: true,\n  });\n}\n\nfunction setupInputObserver() {\n  const textarea = getTextarea();\n  if (!textarea) {\n    setTimeout(setupInputObserver, 500);\n    return;\n  }\n  // Remove Enter key event listeners\n}\n\nasync function handleMem0Processing(\n  capturedText?: string,\n  clickSendButton: boolean = false,\n  sourceButtonId: string | null = null\n) {\n  const textarea = getTextarea();\n  if (!textarea) {\n    console.error('No input textarea found');\n    return;\n  }\n\n  const message = capturedText || getInputText(textarea).trim();\n\n  // Store the original message to preserve it\n  const originalMessage = message;\n\n  if (!message) {\n    console.error('No input message found');\n    return;\n  }\n\n  // If already processing, don't start another operation\n  if (isProcessingMem0) {\n    return;\n  }\n\n  isProcessingMem0 = true;\n\n  try {\n    const data = await new Promise<StorageItems>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.MEMORY_ENABLED,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n          StorageKey.SIMILARITY_THRESHOLD,\n          StorageKey.TOP_K,\n        ],\n        function (items) {\n          resolve(items as StorageItems);\n        }\n      );\n    });\n\n    const apiKey = data[StorageKey.API_KEY];\n    const userId = (data[StorageKey.USER_ID_CAMEL] ||\n      data[StorageKey.USER_ID] ||\n      'chrome-extension-user') as string;\n    const accessToken = data[StorageKey.ACCESS_TOKEN];\n    const memoryEnabled = data[StorageKey.MEMORY_ENABLED] !== false; // Default to true if not set\n\n    const optionalParams: OptionalApiParams = {};\n    if (data[StorageKey.SELECTED_ORG]) {\n      optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n    }\n    if (data[StorageKey.SELECTED_PROJECT]) {\n      optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n    }\n\n    if (!apiKey && !accessToken) {\n      console.error('No API Key or Access Token found');\n      isProcessingMem0 = false;\n      // Show login popup instead of just returning\n      showLoginPopup();\n      return;\n    }\n\n    if (!memoryEnabled) {\n      console.log('Memory is disabled. Skipping API calls.');\n      if (clickSendButton) {\n        clickSendButtonWithDelay();\n      }\n      isProcessingMem0 = false;\n      return;\n    }\n\n    // Show loading modal now that we've confirmed credentials and memory enabled\n    createMemoryModal([], true, sourceButtonId);\n\n    sendExtensionEvent('modal_clicked', {\n      provider: 'perplexity',\n      source: 'OPENMEMORY_CHROME_EXTENSION',\n      browser: getBrowser(),\n    });\n\n    const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n\n    const messages = [{ role: MessageRole.User, content: message }];\n\n    // Use orchestrator immediate run\n    perplexitySearch.runImmediate(message);\n\n    // If no memories found, the createMemoryModal function will show empty state\n\n    // Only send the message if explicitly requested and modal isn't shown\n    if (clickSendButton && !memoryModalShown) {\n      clickSendButtonWithDelay();\n    }\n\n    // Preserve original text regardless\n    setInputValue(textarea, originalMessage);\n\n    // New add memory API call (non-blocking)\n    fetch('https://api.mem0.ai/v1/memories/', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: authHeader,\n      },\n      body: JSON.stringify({\n        messages: messages,\n        user_id: userId,\n        infer: true,\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        metadata: {\n          provider: 'Perplexity',\n        },\n        ...optionalParams,\n      }),\n    })\n      .then(response => {\n        if (!response.ok) {\n          console.error(`Failed to add memory: ${response.status}`);\n        }\n      })\n      .catch(error => {\n        console.error('Error adding memory:', error);\n      });\n  } catch (error) {\n    console.error('Error:', error);\n    // Ensure the original message is preserved even if there's an error\n    const inputElement = getTextarea();\n    if (inputElement && originalMessage) {\n      setInputValue(inputElement, originalMessage);\n    }\n    // Close the modal if there was an error\n    closeModal();\n  } finally {\n    isProcessingMem0 = false;\n  }\n}\n\n// Thin wrapper to trigger memory flow from the UI button\nfunction handleMem0Modal(sourceButtonId: string | null = null) {\n  try {\n    const textarea = getTextarea();\n    const captured = textarea ? (getInputText(textarea) || '').trim() : '';\n    handleMem0Processing(captured, false, sourceButtonId);\n  } catch (_) {\n    // Ignore errors during re-initialization\n  }\n}\n\nfunction setInputValue(inputElement: HTMLElement | null, value: string) {\n  if (inputElement) {\n    setInputText(inputElement, value);\n  }\n}\n\nfunction clickSendButtonWithDelay() {\n  setTimeout(() => {\n    const sendButton = document.querySelector(\n      'button[aria-label=\"Submit\"]'\n    ) as HTMLButtonElement | null;\n    if (sendButton) {\n      sendButton.click();\n      // Clear memories after clicking the send button\n      setTimeout(() => {\n        allMemories = [];\n        console.log('Message sent via clickSendButtonWithDelay, memories cleared');\n      }, 100);\n    } else {\n      console.error('Send button not found');\n    }\n  }, 0);\n}\n\nfunction initializeMem0Integration() {\n  // First check if memory is enabled\n  getMemoryEnabledState().then(memoryEnabled => {\n    if (!memoryEnabled) {\n      // If memory is disabled, remove any existing button\n      const existingButton = document.querySelector('.mem0-button-wrapper');\n      if (existingButton) {\n        existingButton.remove();\n      }\n      return;\n    }\n\n    setupInputObserver();\n    try {\n      hookPerplexityBackgroundSearchTyping();\n    } catch {\n      // Ignore errors\n    }\n\n    // Add the Mem0 button to the UI\n    addMem0Button();\n\n    // Set up the submit button listener to clear memories\n    setupSubmitButtonListener();\n\n    // Add DOM mutation observer to monitor for UI changes\n    const bodyObserver = new MutationObserver(() => {\n      addMem0Button();\n      setupSubmitButtonListener();\n      updateNotificationDot();\n    });\n\n    bodyObserver.observe(document.body, {\n      childList: true,\n      subtree: true,\n    });\n\n    // Re-check periodically in case of navigation or UI changes\n    setInterval(() => {\n      addMem0Button();\n      setupSubmitButtonListener();\n      updateNotificationDot();\n    }, 3000);\n\n    // Set up keyboard shortcut to trigger Mem0 (Ctrl+M)\n    document.addEventListener('keydown', function (event: KeyboardEvent) {\n      if (event.ctrlKey && event.key === 'm') {\n        event.preventDefault();\n        const textarea = getTextarea();\n        if (textarea && getInputText(textarea).trim()) {\n          handleMem0Modal('mem0-icon-button');\n        }\n      }\n    });\n  });\n}\n\ninitializeMem0Integration();\n\n// Function to show login popup\nfunction showLoginPopup() {\n  // First remove any existing popups\n  const existingPopup = document.querySelector('#mem0-login-popup');\n  if (existingPopup) {\n    existingPopup.remove();\n  }\n\n  // Create popup container\n  const popupOverlay = document.createElement('div');\n  popupOverlay.id = 'mem0-login-popup';\n  popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 10001;\n  `;\n\n  const popupContainer = document.createElement('div');\n  popupContainer.style.cssText = `\n    background-color: #1C1C1E;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    position: relative;\n  `;\n\n  // Close button\n  const closeButton = document.createElement('button');\n  closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #A1A1AA;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n  closeButton.innerHTML = '&times;';\n  closeButton.addEventListener('click', () => {\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Logo and heading\n  const logoContainer = document.createElement('div');\n  logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n  const logo = document.createElement('img');\n  logo.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n  logo.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const logoDark = document.createElement('img');\n  logoDark.src = chrome.runtime.getURL('icons/mem0-icon-black.png');\n  logoDark.style.cssText = `\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    margin-right: 12px;\n  `;\n\n  const heading = document.createElement('h2');\n  heading.textContent = 'Sign in to OpenMemory';\n  heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 600;\n  `;\n\n  logoContainer.appendChild(heading);\n\n  // Message\n  const message = document.createElement('p');\n  message.textContent = 'Please sign in to access your memories and enhance your conversations!';\n  message.style.cssText = `\n    margin-bottom: 24px;\n    color: #D4D4D8;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n  // Sign in button\n  const signInButton = document.createElement('button');\n  signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: white;\n    color: black;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n  // Add text in span for better centering\n  const signInText = document.createElement('span');\n  signInText.textContent = 'Sign in with Mem0';\n\n  signInButton.appendChild(logoDark);\n  signInButton.appendChild(signInText);\n\n  signInButton.addEventListener('mouseenter', () => {\n    signInButton.style.backgroundColor = '#f5f5f5';\n  });\n\n  signInButton.addEventListener('mouseleave', () => {\n    signInButton.style.backgroundColor = 'white';\n  });\n\n  // Open sign-in page when clicked\n  signInButton.addEventListener('click', () => {\n    window.open('https://app.mem0.ai/login', '_blank');\n    document.body.removeChild(popupOverlay);\n  });\n\n  // Assemble popup\n  popupContainer.appendChild(closeButton);\n  popupContainer.appendChild(logoContainer);\n  popupContainer.appendChild(message);\n  popupContainer.appendChild(signInButton);\n\n  popupOverlay.appendChild(popupContainer);\n\n  // Add click event to close when clicking outside\n  popupOverlay.addEventListener('click', e => {\n    if (e.target === popupOverlay) {\n      document.body.removeChild(popupOverlay);\n    }\n  });\n\n  // Add to body\n  document.body.appendChild(popupOverlay);\n}\n\n// Global closeModal function to fix the reference error\nfunction closeModal() {\n  if (memoryModalShown && currentModalOverlay) {\n    document.body.removeChild(currentModalOverlay);\n    memoryModalShown = false;\n    // Reset modal position when closing\n    modalPosition = { top: null, left: null };\n  }\n}\n"
  },
  {
    "path": "src/popup.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Mem0 Sign In</title>\n    <style>\n      body {\n        font-family:\n          -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,\n          'Open Sans', 'Helvetica Neue', sans-serif;\n        width: 300px;\n        margin: 0;\n        padding: 20px;\n        background-color: #1c1c1e;\n        color: white;\n      }\n      .logo-container {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        margin-bottom: 16px;\n      }\n      .logo {\n        width: 24px;\n        height: 24px;\n        border-radius: 50%;\n        margin-right: 12px;\n      }\n      .heading {\n        margin: 0;\n        font-size: 18px;\n        font-weight: 600;\n      }\n      .content {\n        text-align: center;\n      }\n      .message {\n        margin-bottom: 24px;\n        color: #d4d4d8;\n        font-size: 14px;\n        line-height: 1.5;\n        text-align: center;\n      }\n      .button {\n        width: 100%;\n        padding: 10px;\n        background-color: white;\n        color: black;\n        border: none;\n        border-radius: 8px;\n        font-size: 14px;\n        font-weight: 600;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        cursor: pointer;\n        transition: background-color 0.2s;\n        margin-top: 20px;\n      }\n      .button:hover {\n        background-color: #f5f5f5;\n      }\n      .google-icon {\n        margin-right: 10px;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"logo-container\">\n      <h2 class=\"heading\">Sign in to OpenMemory</h2>\n    </div>\n    <div class=\"content\">\n      <p class=\"message\">\n        Please sign in to access your memories and personalize your conversations!\n      </p>\n      <button id=\"googleSignInButton\" class=\"button\">\n        <img src=\"../icons/mem0-icon-black.png\" alt=\"Mem0 Logo\" class=\"logo\" />\n        <span>Sign in with Mem0</span>\n      </button>\n    </div>\n    <script type=\"module\" src=\"/src/popup.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/popup.ts",
    "content": "import { DEFAULT_USER_ID } from './types/api';\nimport { SidebarAction } from './types/messages';\nimport { StorageKey } from './types/storage';\ndocument.addEventListener('DOMContentLoaded', () => {\n  const googleSignInButton = document.getElementById('googleSignInButton') as HTMLButtonElement;\n\n  const checkAuth = (): void => {\n    chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], data => {\n      if (data[StorageKey.API_KEY] || data[StorageKey.ACCESS_TOKEN]) {\n        chrome.tabs.query({ active: true, currentWindow: true }, tabs => {\n          const tabId = tabs[0]?.id;\n          if (tabId !== null && tabId !== undefined) {\n            chrome.tabs.sendMessage(tabId, { action: SidebarAction.TOGGLE_SIDEBAR });\n          }\n          window.close();\n        });\n      }\n    });\n  };\n\n  if (googleSignInButton) {\n    googleSignInButton.addEventListener('click', () => {\n      chrome.storage.sync.set({ [StorageKey.USER_ID_CAMEL]: DEFAULT_USER_ID });\n      chrome.storage.sync.get([StorageKey.USER_LOGGED_IN], data => {\n        const url = data[StorageKey.USER_LOGGED_IN]\n          ? 'https://app.mem0.ai/extension?source=chrome-extension'\n          : 'https://app.mem0.ai/login?source=chrome-extension';\n        chrome.tabs.create({ url }, () => {\n          window.close();\n        });\n      });\n    });\n  }\n  checkAuth();\n});\n"
  },
  {
    "path": "src/replit/content.ts",
    "content": "/* eslint-disable @typescript-eslint/no-non-null-assertion */\n/* eslint-disable no-inner-declarations */\nimport { MessageRole } from '../types/api';\nimport type { ExtendedHTMLElement } from '../types/dom';\nimport type { MemoryItem, MemorySearchItem, OptionalApiParams } from '../types/memory';\nimport { SidebarAction } from '../types/messages';\nimport { type StorageItems, StorageKey } from '../types/storage';\nimport { createOrchestrator, type SearchStorage } from '../utils/background_search';\nimport { OPENMEMORY_PROMPTS } from '../utils/llm_prompts';\nimport { SITE_CONFIG } from '../utils/site_config';\nimport { getBrowser, sendExtensionEvent } from '../utils/util_functions';\nimport { OPENMEMORY_UI, type Placement } from '../utils/util_positioning';\n\n// Local types for this file\ntype MutableMutationObserver = MutationObserver & {\n  memoryStateInterval?: ReturnType<typeof setInterval>;\n  debounceTimer?: ReturnType<typeof setTimeout>;\n};\n\nexport {};\n\ntry {\n  let isProcessingMem0: boolean = false;\n\n  let memoryModalShown: boolean = false;\n\n  // Global variable to store all memories\n  let allMemories: string[] = [];\n\n  // Track added memories by ID\n  const allMemoriesById: Set<string> = new Set<string>();\n\n  // Reference to the modal overlay for updates\n  let currentModalOverlay: HTMLDivElement | null = null;\n\n  let inputObserver: MutationObserver | null = null;\n  let lastInputValue: string = '';\n\n  // Global flags to prevent duplicate initialization\n  let isInitialized: boolean = false;\n  // let buttonInjected: boolean = false;\n  let sendListenerAdded: boolean = false;\n\n  // Store references to observers for cleanup\n  let mainObserver: MutableMutationObserver | null = null;\n  let notificationObserver: MutationObserver | null = null;\n\n  const replitSearch = createOrchestrator({\n    fetch: async function (query: string, opts: { signal?: AbortSignal }) {\n      const data = await new Promise<SearchStorage>(resolve => {\n        chrome.storage.sync.get(\n          [\n            StorageKey.API_KEY,\n            StorageKey.USER_ID_CAMEL,\n            StorageKey.ACCESS_TOKEN,\n            StorageKey.SELECTED_ORG,\n            StorageKey.SELECTED_PROJECT,\n            StorageKey.USER_ID,\n            StorageKey.SIMILARITY_THRESHOLD,\n            StorageKey.TOP_K,\n          ],\n          function (items) {\n            resolve(items as SearchStorage);\n          }\n        );\n      });\n\n      const apiKey = data[StorageKey.API_KEY];\n      const accessToken = data[StorageKey.ACCESS_TOKEN];\n      if (!apiKey && !accessToken) {\n        return [];\n      }\n\n      const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n      const userId =\n        data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user';\n      const threshold =\n        data[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n          ? data[StorageKey.SIMILARITY_THRESHOLD]\n          : 0.1;\n      const topK = data[StorageKey.TOP_K] !== undefined ? data[StorageKey.TOP_K] : 10;\n\n      const optionalParams: OptionalApiParams = {};\n      if (data[StorageKey.SELECTED_ORG]) {\n        optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n      }\n      if (data[StorageKey.SELECTED_PROJECT]) {\n        optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n      }\n\n      const payload = {\n        query,\n        filters: { user_id: userId },\n        rerank: true,\n        threshold: threshold,\n        top_k: topK,\n        filter_memories: false,\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        ...optionalParams,\n      };\n\n      const res = await fetch('https://api.mem0.ai/v2/memories/search/', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: authHeader,\n        },\n        body: JSON.stringify(payload),\n        signal: opts && opts.signal,\n      });\n\n      if (!res.ok) {\n        throw new Error(`API request failed with status ${res.status}`);\n      }\n      return await res.json();\n    },\n\n    // Don’t render on prefetch. When modal is open, update it.\n    onSuccess: function (normQuery: string, responseData: MemorySearchItem[]) {\n      if (!memoryModalShown) {\n        return;\n      }\n      const memoryItems = ((responseData as MemorySearchItem[]) || []).map(\n        (item: MemorySearchItem) => ({\n          id: String(item.id),\n          text: item.memory,\n          categories: item.categories || [],\n        })\n      );\n      createMemoryModal(memoryItems, false);\n    },\n\n    onError: function () {\n      if (memoryModalShown) {\n        createMemoryModal([], false);\n      }\n    },\n\n    minLength: 3,\n    debounceMs: 75,\n    cacheTTL: 60000,\n  });\n\n  let replitBackgroundSearchHandler: (() => void) | null = null;\n  function hookReplitBackgroundSearchTyping() {\n    const textarea = getTextarea();\n    if (!textarea) {\n      return;\n    }\n\n    if (textarea.dataset.replitBackgroundHooked) {\n      return;\n    }\n    textarea.dataset.replitBackgroundHooked = 'true';  \n\n    if (!replitBackgroundSearchHandler) {\n      replitBackgroundSearchHandler = function () {\n        const text = (textarea.textContent || textarea.innerText || '').trim();\n        replitSearch.setText(text);\n      };\n    }\n    textarea.addEventListener('input', replitBackgroundSearchHandler);\n    textarea.addEventListener('keyup', replitBackgroundSearchHandler);\n  }\n\n  function getTextarea(): HTMLElement | null {\n    const selectors = [\n      'div[contenteditable=\"true\"][class=\"cm-content cm-lineWrapping\"][role=\"textbox\"]',\n      'div.cm-content.cm-lineWrapping[contenteditable=\"true\"]',\n      '.cm-content[contenteditable=\"true\"]',\n      'div[contenteditable=\"true\"].cm-content',\n      'div.cm-content[role=\"textbox\"]',\n      '.cm-content',\n      'div[contenteditable=\"true\"]',\n      '[contenteditable=\"true\"]',\n    ];\n\n    // First try our specific selectors\n    for (const selector of selectors) {\n      const textarea = document.querySelector(selector) as HTMLElement | null;\n      if (textarea) {\n        // If textarea is found but listeners haven't been set up, trigger a retry\n        if (!sendListenerAdded) {\n          setTimeout(() => {\n            if (!sendListenerAdded) {\n              addSendButtonListener();\n            }\n          }, 500);\n        }\n\n        return textarea;\n      }\n    }\n\n    return null;\n  }\n\n  function setupInputObserver(): void {\n    // Don't set up if already exists\n    if (inputObserver) {\n      return;\n    }\n\n    const textarea = getTextarea();\n    if (!textarea) {\n      // Only retry a limited number of times to prevent infinite recursion\n      let retryCount = 0;\n      const maxRetries = 10;\n\n      const retrySetup = () => {\n        if (retryCount < maxRetries) {\n          retryCount++;\n          setTimeout(setupInputObserver, 500);\n        }\n      };\n\n      retrySetup();\n      return;\n    }\n\n    // Set initial value\n    lastInputValue = textarea.textContent || (textarea as ExtendedHTMLElement).innerText || '';\n\n    inputObserver = new MutationObserver(mutations => {\n      for (const mutation of mutations) {\n        if (mutation.type === 'characterData' || mutation.type === 'childList') {\n          const newValue =\n            textarea.textContent || (textarea as ExtendedHTMLElement).innerText || '';\n          if (newValue !== lastInputValue) {\n            lastInputValue = newValue;\n          }\n        }\n      }\n    });\n\n    inputObserver.observe(textarea, {\n      childList: true,\n      characterData: true,\n      subtree: true,\n    });\n\n    // Add input listener only once\n    if (!textarea.dataset.mem0InputListener) {\n      textarea.dataset.mem0InputListener = 'true';\n      textarea.addEventListener('input', function (this: HTMLElement) {\n        const newValue = this.textContent || (this as ExtendedHTMLElement).innerText || '';\n        if (newValue !== lastInputValue) {\n          lastInputValue = newValue;\n        }\n      });\n    }\n  }\n\n  function setInputValue(\n    inputElement: HTMLElement | HTMLInputElement | HTMLTextAreaElement | null,\n    value: string\n  ): void {\n    if (inputElement) {\n      // For contenteditable divs, we need to set innerHTML or textContent\n      if (inputElement.contentEditable === 'true') {\n        // Clear existing content\n        inputElement.innerHTML = '';\n\n        // Split the value by newlines and create div elements for Replit's CodeMirror\n        const lines = value.split('\\n');\n        lines.forEach((line: string, index: number) => {\n          const div = document.createElement('div');\n          div.className = 'cm-line';\n          if (index === 0) {\n            div.className += ' cm-replit-active-line';\n          }\n\n          if (line.trim() === '') {\n            div.innerHTML = '<br>';\n          } else {\n            div.textContent = line;\n          }\n          inputElement.appendChild(div);\n        });\n\n        lastInputValue = value;\n\n        // Trigger input event\n        inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n\n        // Focus and set cursor to end\n        inputElement.focus();\n\n        // Set cursor to end of content\n        const range = document.createRange();\n        const selection = window.getSelection();\n        range.selectNodeContents(inputElement);\n        range.collapse(false);\n        if (selection) {\n          selection.removeAllRanges();\n          selection.addRange(range);\n        }\n      } else {\n        // Fallback for regular input/textarea elements\n        (inputElement as HTMLInputElement | HTMLTextAreaElement).value = value;\n        lastInputValue = value;\n        inputElement.dispatchEvent(new Event('input', { bubbles: true }));\n      }\n    }\n  }\n\n  // Function to get the content without any memory wrappers\n  function getContentWithoutMemories(message: string | null = null): string {\n    let content: string;\n\n    if (message) {\n      // Use provided message\n      content = message;\n    } else {\n      // Fall back to reading from textarea\n      const inputElement = getTextarea();\n      if (!inputElement) {\n        return '';\n      }\n      content = inputElement.textContent || (inputElement as ExtendedHTMLElement).innerText || '';\n    }\n\n    // Remove any memory headers and content\n    const memoryPrefix = OPENMEMORY_PROMPTS.memory_header_text;\n    const prefixIndex = content.indexOf(memoryPrefix);\n    if (prefixIndex !== -1) {\n      content = content.substring(0, prefixIndex).trim();\n    }\n\n    // Also try with regex pattern\n    try {\n      const MEM0_PLAIN = OPENMEMORY_PROMPTS.memory_header_plain_regex;\n      content = content.replace(MEM0_PLAIN, '').trim();\n    } catch {\n      // Ignore regex errors\n    }\n\n    return content;\n  }\n\n  // Function to check if memory is enabled\n  function getMemoryEnabledState(): Promise<boolean> {\n    return new Promise<boolean>(resolve => {\n      chrome.storage.sync.get([StorageKey.MEMORY_ENABLED], function (result) {\n        resolve(result.memory_enabled !== false); // Default to true if not set\n      });\n    });\n  }\n\n  // Track if memory has been captured for this session to prevent duplicates\n  let memoryCaptured = false;\n  let lastCapturedMessage = '';\n\n  // Add a function to handle send button actions and clear memories after sending\n  function addSendButtonListener(): void {\n    const selectors = [\n      'button[data-cy=\"ai-prompt-submit\"]',\n      'button[data-cy=\"ai-chat-send-button\"]',\n      'button[aria-label=\"Send\"]',\n      'button[type=\"button\"][aria-label=\"Send\"]',\n      '.useView_view__C2mnv[aria-label=\"Send\"]',\n      'button[type=\"submit\"]',\n      'button:has(svg[data-testid=\"send\"])',\n      'button:has([data-testid=\"send\"])',\n    ];\n\n    // Handle capturing and storing the current message\n    function captureAndStoreMemory(): void {\n      const textarea = getTextarea();\n      if (!textarea) {\n        return;\n      }\n\n      // Get message from textarea first, then fall back to lastInputValue if textarea is empty\n      let message = (\n        textarea.textContent ||\n        (textarea as ExtendedHTMLElement).innerText ||\n        ''\n      ).trim();\n\n      // If textarea is empty (happens when Enter is pressed), use the stored value\n      if (!message && lastInputValue) {\n        message = lastInputValue.trim();\n      }\n\n      if (!message) {\n        return;\n      }\n\n      // Clean message from any existing memory content\n      const cleanMessage = getContentWithoutMemories(message);\n\n      // Prevent duplicate captures for the same message\n      if (memoryCaptured && lastCapturedMessage === cleanMessage) {\n        return;\n      }\n\n      memoryCaptured = true;\n      lastCapturedMessage = cleanMessage;\n\n      // Reset the capture flag after a short delay\n      setTimeout(() => {\n        memoryCaptured = false;\n        lastCapturedMessage = '';\n      }, 1000);\n\n      // Asynchronously store the memory\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.USER_ID_CAMEL,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.MEMORY_ENABLED,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.USER_ID,\n        ],\n        function (items) {\n          // Skip if memory is disabled or no credentials\n          if (items.memory_enabled === false || (!items.apiKey && !items.access_token)) {\n            return;\n          }\n\n          const authHeader = items.access_token\n            ? `Bearer ${items.access_token}`\n            : `Token ${items.apiKey}`;\n\n          const userId = items.userId || items.user_id || 'chrome-extension-user';\n\n          const optionalParams: OptionalApiParams = {};\n          if (items.selected_org) {\n            optionalParams.org_id = items.selected_org;\n          }\n          if (items.selected_project) {\n            optionalParams.project_id = items.selected_project;\n          }\n\n          // Send memory to mem0 API asynchronously without waiting for response\n          fetch('https://api.mem0.ai/v1/memories/', {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n              Authorization: authHeader,\n            },\n            body: JSON.stringify({\n              messages: [{ role: MessageRole.User, content: cleanMessage }],\n              user_id: userId,\n              infer: true,\n              metadata: {\n                provider: 'Replit',\n              },\n              source: 'OPENMEMORY_CHROME_EXTENSION',\n              ...optionalParams,\n            }),\n          })\n            .then(response => {\n              return response.json();\n            })\n            .then(() => {\n              // Memory saved successfully\n            })\n            .catch(error => {\n              console.error('[Mem0 Replit] Error saving memory:', error);\n            });\n        }\n      );\n\n      // Clear all memories after sending\n      setTimeout(() => {\n        allMemories = [];\n        allMemoriesById.clear();\n      }, 100);\n    }\n\n    // Find and add listeners to the send button - check each time\n    let sendButton: HTMLElement | null = null;\n    let sendButtonFound = false;\n\n    for (const selector of selectors) {\n      sendButton = document.querySelector(selector);\n      if (sendButton) {\n        if (!sendButton.dataset.mem0Listener) {\n          sendButton.dataset.mem0Listener = 'true';\n          sendButton.addEventListener('click', function () {\n            captureAndStoreMemory();\n          });\n          sendButtonFound = true;\n          break;\n        } else {\n          sendButtonFound = true;\n          break;\n        }\n      }\n    }\n\n    // Handle textarea for Enter key press - check each time\n    const textarea = getTextarea();\n    if (textarea) {\n      if (!textarea.dataset.mem0KeyListener) {\n        textarea.dataset.mem0KeyListener = 'true';\n\n        // Add keydown listener for Enter key\n        textarea.addEventListener('keydown', function (event: KeyboardEvent) {\n          // Update lastInputValue for non-control keys\n          if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {\n            setTimeout(() => {\n              lastInputValue =\n                textarea.textContent || (textarea as ExtendedHTMLElement).innerText || '';\n            }, 0);\n          }\n\n          // Check if Enter was pressed without Shift (standard send behavior)\n          if (event.key === 'Enter' && !event.shiftKey) {\n            // Small delay to ensure content is captured before send\n            setTimeout(() => {\n              captureAndStoreMemory();\n            }, 10);\n          }\n        });\n\n        // Also add keyup listener as backup\n        textarea.addEventListener('keyup', function () {\n          lastInputValue =\n            textarea.textContent || (textarea as ExtendedHTMLElement).innerText || '';\n        });\n      }\n    }\n\n    // Only set the flag if we successfully found and set up listeners\n    if (sendButtonFound || textarea) {\n      sendListenerAdded = true;\n    } else {\n      // Don't set the flag so we keep trying\n      sendListenerAdded = false;\n    }\n  }\n\n  // Handler for the modal approach\n  async function handleMem0Modal() {\n    // Prevent multiple simultaneous modals\n    if (isProcessingMem0) {\n      return;\n    }\n\n    // Clear only the modal display tracking, but keep the actual added memories\n    // allMemoriesById tracks what memories have been shown in modals\n    allMemoriesById.clear();\n    // Do NOT clear allMemories here - we want to keep previously added memories\n\n    const memoryEnabled = await getMemoryEnabledState();\n    if (!memoryEnabled) {\n      return;\n    }\n\n    // Check if user is logged in\n    const loginData = await new Promise<StorageItems>(resolve => {\n      chrome.storage.sync.get(\n        [StorageKey.API_KEY, StorageKey.USER_ID_CAMEL, StorageKey.ACCESS_TOKEN],\n        function (items) {\n          resolve(items as StorageItems);\n        }\n      );\n    });\n\n    // If no API key and no access token, show login popup\n    if (!loginData.apiKey && !loginData.access_token) {\n      showLoginPopup();\n      return;\n    }\n\n    const textarea = getTextarea();\n    let message = textarea\n      ? (textarea.textContent || (textarea as ExtendedHTMLElement).innerText || '').trim()\n      : '';\n\n    // If no message, show a popup and return\n    if (!message) {\n      // Show message that requires input\n      const mem0Button = document.querySelector('button[aria-label=\"Mem0\"]') as HTMLElement | null;\n      if (mem0Button) {\n        showButtonPopup(mem0Button, 'Please enter some text first');\n      }\n      return;\n    }\n\n    // Clean the message of any existing memory content\n    message = getContentWithoutMemories();\n\n    isProcessingMem0 = true;\n\n    // Add a timeout to reset the flag if something goes wrong\n    const timeoutId = setTimeout((): void => {\n      isProcessingMem0 = false;\n    }, 30000); // 30 second timeout\n\n    // Show the loading modal immediately with the source button ID\n    createMemoryModal([], true);\n\n    try {\n      const data = await new Promise<StorageItems>(resolve => {\n        chrome.storage.sync.get(\n          [\n            StorageKey.API_KEY,\n            StorageKey.USER_ID_CAMEL,\n            StorageKey.ACCESS_TOKEN,\n            StorageKey.SELECTED_ORG,\n            StorageKey.SELECTED_PROJECT,\n            StorageKey.USER_ID,\n            StorageKey.SIMILARITY_THRESHOLD,\n            StorageKey.TOP_K,\n          ],\n          function (items) {\n            resolve(items as StorageItems);\n          }\n        );\n      });\n\n      const apiKey = data[StorageKey.API_KEY];\n      const userId = (data[StorageKey.USER_ID_CAMEL] ||\n        data[StorageKey.USER_ID] ||\n        'chrome-extension-user') as string;\n      const accessToken = data[StorageKey.ACCESS_TOKEN];\n\n      const optionalParams: OptionalApiParams = {};\n\n      if (data[StorageKey.SELECTED_ORG]) {\n        optionalParams.org_id = data[StorageKey.SELECTED_ORG];\n      }\n      if (data[StorageKey.SELECTED_PROJECT]) {\n        optionalParams.project_id = data[StorageKey.SELECTED_PROJECT];\n      }\n\n      if (!apiKey && !accessToken) {\n        isProcessingMem0 = false;\n        return;\n      }\n\n      sendExtensionEvent('modal_clicked', {\n        provider: 'replit',\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        browser: getBrowser(),\n      });\n\n      const authHeader = accessToken ? `Bearer ${accessToken}` : `Token ${apiKey}`;\n\n      const messages = [{ role: MessageRole.User, content: message }];\n\n      // Use orchestrator immediate run\n      replitSearch.runImmediate(message);\n      // Proceed with adding memory asynchronously without awaiting\n      fetch('https://api.mem0.ai/v1/memories/', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: authHeader,\n        },\n        body: JSON.stringify({\n          messages: messages,\n          user_id: userId,\n          infer: true,\n          metadata: {\n            provider: 'Replit',\n          },\n          source: 'OPENMEMORY_CHROME_EXTENSION',\n          ...optionalParams,\n        }),\n      }).catch(error => {\n        console.error('Error adding memory:', error);\n      });\n    } catch (error) {\n      console.error('Error:', error);\n      // Still show the modal but with empty state if there was an error\n      createMemoryModal([], false);\n    } finally {\n      clearTimeout(timeoutId);\n      isProcessingMem0 = false;\n    }\n  }\n\n  function initializeMem0Integration() {\n    try {\n      // Prevent duplicate initialization\n      if (isInitialized) {\n        return;\n      }\n\n      isInitialized = true;\n\n      setupInputObserver();\n      try {\n        hookReplitBackgroundSearchTyping();\n      } catch {\n        // Ignore background search errors\n      }\n\n      if (OPENMEMORY_UI && OPENMEMORY_UI.mountOnEditorFocus) {\n        try {\n          if (!document.getElementById('mem0-icon-button')) {\n            OPENMEMORY_UI.resolveCachedAnchor(\n              { learnKey: location.host + ':' + location.pathname },\n              null,\n              24 * 60 * 60 * 1000\n            ).then(function (hit: { el: Element; placement: Placement | null } | null) {\n              if (!hit || !hit.el) {\n                return;\n              }\n              let hs = OPENMEMORY_UI.createShadowRootHost('mem0-root');\n              let host = hs.host,\n                shadow = hs.shadow;\n              host.id = 'mem0-icon-button';\n              let cfg =\n                typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.replit\n                  ? SITE_CONFIG.replit\n                  : null;\n              let placement = hit.placement ||\n                (cfg && cfg.placement) || { strategy: 'float', placement: 'right-center', gap: 12 };\n              OPENMEMORY_UI.applyPlacement({\n                container: host,\n                anchor: hit.el,\n                placement: placement,\n              });\n              let style = document.createElement('style');\n              style.textContent = `\n                :host { position: relative; }\n                .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:40px; height:40px; border-radius:50%; box-shadow: 0 6px 16px rgba(0,0,0,0.3); background:#1C1C1E; }\n                .mem0-btn img { width:20px; height:20px; border-radius:50%; }\n                .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n                :host([data-has-text=\"1\"]) .dot { display:block; }\n              `;\n              let btn = document.createElement('button');\n              btn.className = 'mem0-btn';\n              let img = document.createElement('img');\n              img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n              let dot = document.createElement('div');\n              dot.className = 'dot';\n              btn.appendChild(img);\n              shadow.append(style, btn, dot);\n              btn.addEventListener('click', function () {\n                handleMem0Modal();\n              });\n              if (typeof updateNotificationDot === 'function') {\n                setTimeout(updateNotificationDot, 0);\n              }\n            });\n          }\n        } catch (_) {\n          // Ignore errors during re-initialization\n        }\n\n        OPENMEMORY_UI.mountOnEditorFocus({\n          existingHostSelector: '#mem0-icon-button',\n          editorSelector:\n            typeof SITE_CONFIG !== 'undefined' &&\n            SITE_CONFIG.replit &&\n            SITE_CONFIG.replit.editorSelector\n              ? SITE_CONFIG.replit.editorSelector\n              : 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n          deriveAnchor:\n            typeof SITE_CONFIG !== 'undefined' &&\n            SITE_CONFIG.replit &&\n            typeof SITE_CONFIG.replit.deriveAnchor === 'function'\n              ? SITE_CONFIG.replit.deriveAnchor\n              : function (editor: Element) {\n                  return editor.closest('form') || editor.parentElement || document.body;\n                },\n          placement:\n            typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.replit && SITE_CONFIG.replit.placement\n              ? SITE_CONFIG.replit.placement\n              : { strategy: 'float', placement: 'right-center', gap: 12 },\n          render: function (shadow: ShadowRoot, host: HTMLElement) {\n            host.id = 'mem0-icon-button';\n            let style = document.createElement('style');\n            style.textContent = `\n              :host { position: relative; }\n              .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:40px; height:40px; border-radius:50%; box-shadow: 0 6px 16px rgba(0,0,0,0.3); background:#1C1C1E; }\n              .mem0-btn img { width:20px; height:20px; border-radius:50%; }\n              .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n              :host([data-has-text=\"1\"]) .dot { display:block; }\n            `;\n            let btn = document.createElement('button');\n            btn.className = 'mem0-btn';\n            let img = document.createElement('img');\n            img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n            let dot = document.createElement('div');\n            dot.className = 'dot';\n            btn.appendChild(img);\n            shadow.append(style, btn, dot);\n            btn.addEventListener('click', function () {\n              handleMem0Modal();\n            });\n            if (typeof updateNotificationDot === 'function') {\n              setTimeout(updateNotificationDot, 0);\n            }\n          },\n          fallback: function () {\n            let cfg =\n              typeof SITE_CONFIG !== 'undefined' && SITE_CONFIG.replit ? SITE_CONFIG.replit : null;\n            return OPENMEMORY_UI.mountResilient({\n              anchors: [\n                {\n                  find: function () {\n                    let sel =\n                      (cfg && cfg.editorSelector) ||\n                      'textarea, [contenteditable=\"true\"], input[type=\"text\"]';\n                    let ed = document.querySelector(sel);\n                    if (!ed) {\n                      return null;\n                    }\n                    try {\n                      return cfg && typeof cfg.deriveAnchor === 'function'\n                        ? cfg.deriveAnchor(ed)\n                        : ed.closest('form') || ed.parentElement || document.body;\n                    } catch (_) {\n                      return ed.closest('form') || ed.parentElement || document.body;\n                    }\n                  },\n                },\n              ],\n              placement: (cfg && cfg.placement) || {\n                strategy: 'float',\n                placement: 'right-center',\n                gap: 12,\n              },\n              enableFloatingFallback: true,\n              render: function (shadow: ShadowRoot, host: HTMLElement) {\n                host.id = 'mem0-icon-button';\n                let style = document.createElement('style');\n                style.textContent = `\n                  :host { position: relative; }\n                  .mem0-btn { all: initial; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; width:40px; height:40px; border-radius:50%; box-shadow: 0 6px 16px rgba(0,0,0,0.3); background:#1C1C1E; }\n                  .mem0-btn img { width:20px; height:20px; border-radius:50%; }\n                  .dot { position:absolute; top:-2px; right:-2px; width:8px; height:8px; background:#80DDA2; border-radius:50%; border:2px solid #1C1C1E; display:none; }\n                  :host([data-has-text=\"1\"]) .dot { display:block; }\n                `;\n                let btn = document.createElement('button');\n                btn.className = 'mem0-btn';\n                let img = document.createElement('img');\n                img.src = chrome.runtime.getURL('icons/mem0-claude-icon-p.png');\n                let dot = document.createElement('div');\n                dot.className = 'dot';\n                btn.appendChild(img);\n                shadow.append(style, btn, dot);\n                btn.addEventListener('click', function () {\n                  handleMem0Modal();\n                });\n                if (typeof updateNotificationDot === 'function') {\n                  setTimeout(updateNotificationDot, 0);\n                }\n              },\n            });\n          },\n        });\n      }\n\n      addSendButtonListener();\n    } catch (error) {\n      console.error('[Mem0] Error during initialization:', error);\n      isInitialized = false; // Reset so we can try again\n    }\n\n    // Set up a single, more efficient mutation observer\n    if (mainObserver) {\n      mainObserver.disconnect();\n    }\n\n    mainObserver = new MutationObserver(async () => {\n      // Debounce the observer to prevent excessive calls\n      if (mainObserver && (mainObserver as MutableMutationObserver).debounceTimer) {\n        clearTimeout((mainObserver as MutableMutationObserver).debounceTimer);\n      }\n      if (!mainObserver) {\n        return;\n      }\n      (mainObserver as MutableMutationObserver).debounceTimer = setTimeout(async () => {\n        // Check memory state first\n        const memoryEnabled = await getMemoryEnabledState();\n\n        if (!memoryEnabled) {\n          // Remove the button if memory is disabled\n          const existingButton = document.querySelector('button[aria-label=\"Mem0\"]');\n          if (existingButton && existingButton.parentElement) {\n            existingButton.parentElement.remove();\n            // buttonInjected = false;\n          }\n        }\n\n        // Add send button listener if not already added or if elements might have changed\n        if (memoryEnabled && !sendListenerAdded) {\n          addSendButtonListener();\n        }\n\n        // Update notification dot\n        updateNotificationDot();\n      }, 100); // Reduce debounce to 100ms for faster response\n    });\n\n    // Add keyboard shortcut for Ctrl+M (only once)\n    if (!document.body.dataset.mem0KeyboardListener) {\n      document.body.dataset.mem0KeyboardListener = 'true';\n      document.addEventListener('keydown', function (event: KeyboardEvent) {\n        if (event.ctrlKey && event.key === 'm') {\n          event.preventDefault();\n          (async () => {\n            await handleMem0Modal();\n          })();\n        }\n      });\n    }\n\n    // Observe with more specific targeting to reduce noise\n    mainObserver.observe(document.body, {\n      childList: true,\n      subtree: true,\n      // Only observe specific changes that matter\n      attributeFilter: ['class', 'style'],\n    });\n\n    // Replace periodic button injection checks; OPENMEMORY_UI handles mounting\n    const memoryStateCheckInterval = setInterval(async () => {\n      const memoryEnabled = await getMemoryEnabledState();\n      const buttonExists = document.querySelector('button[aria-label=\"Mem0\"]');\n\n      if (!memoryEnabled && buttonExists) {\n        buttonExists.parentElement?.remove();\n        // buttonInjected = false;\n      }\n    }, 10000);\n\n    // Store interval reference for cleanup if needed\n    if (mainObserver) {\n      (mainObserver as MutableMutationObserver).memoryStateInterval = memoryStateCheckInterval;\n    }\n  }\n\n  // Initialize the integration when the page loads\n\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', () => {\n      initializeMem0Integration();\n    });\n  } else {\n    initializeMem0Integration();\n  }\n\n  // Also try to initialize after a delay as a fallback\n  setTimeout(() => {\n    if (!isInitialized) {\n      initializeMem0Integration();\n    }\n  }, 2000);\n\n  // Add another retry after 5 seconds\n  setTimeout(() => {\n    return;\n  }, 5000);\n\n  // Shared function to update the input field with all collected memories\n  function updateInputWithMemories() {\n    const inputElement = getTextarea();\n\n    if (inputElement && allMemories.length > 0) {\n      // Get the content without any existing memory wrappers\n      const baseContent = getContentWithoutMemories();\n\n      // Create the memory string with all collected memories\n      let memoriesContent = '\\n\\n' + OPENMEMORY_PROMPTS.memory_header_text + '\\n';\n\n      // Add all memories to the content\n      allMemories.forEach((mem, index) => {\n        memoriesContent += `- ${mem}`;\n        if (index < allMemories.length - 1) {\n          memoriesContent += '\\n';\n        }\n      });\n\n      // Add the final content to the input\n      setInputValue(inputElement, baseContent + memoriesContent);\n    }\n  }\n\n  // Function to show a small popup message near the button\n  function showButtonPopup(button: HTMLElement, message: string): void {\n    // Remove any existing popups\n    const existingPopup = document.querySelector('.mem0-button-popup');\n    if (existingPopup) {\n      existingPopup.remove();\n    }\n\n    const popup = document.createElement('div');\n    popup.className = 'mem0-button-popup';\n\n    popup.style.cssText = `\n    position: absolute;\n    top: -40px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: #2d2e30;\n    border: 1px solid #5f6368;\n    color: white;\n    padding: 8px 12px;\n    border-radius: 6px;\n    font-size: 12px;\n    white-space: nowrap;\n    z-index: 10001;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  `;\n\n    popup.textContent = message;\n\n    // Create arrow\n    const arrow = document.createElement('div');\n    arrow.style.cssText = `\n    position: absolute;\n    bottom: -5px;\n    left: 50%;\n    transform: translateX(-50%) rotate(45deg);\n    width: 8px;\n    height: 8px;\n    background-color: #2d2e30;\n    border-right: 1px solid #5f6368;\n    border-bottom: 1px solid #5f6368;\n  `;\n\n    popup.appendChild(arrow);\n\n    // Position relative to button\n    button.style.position = 'relative';\n    button.appendChild(popup);\n\n    // Auto-remove after 3 seconds\n    setTimeout(() => {\n      if (popup && popup.parentElement) {\n        popup.remove();\n      }\n    }, 3000);\n  }\n\n  // Function to show login popup\n  function showLoginPopup() {\n    // First remove any existing popups\n    const existingPopup = document.querySelector('#mem0-login-popup');\n    if (existingPopup) {\n      existingPopup.remove();\n    }\n\n    // Create popup container\n    const popupOverlay = document.createElement('div');\n    popupOverlay.id = 'mem0-login-popup';\n    popupOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    z-index: 10001;\n  `;\n\n    const popupContainer = document.createElement('div');\n    popupContainer.style.cssText = `\n    background-color: #2d2e30;\n    border-radius: 12px;\n    width: 320px;\n    padding: 24px;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  `;\n\n    // Close button\n    const closeButton = document.createElement('button');\n    closeButton.style.cssText = `\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    background: none;\n    border: none;\n    color: #9aa0a6;\n    font-size: 16px;\n    cursor: pointer;\n  `;\n    closeButton.innerHTML = '&times;';\n    closeButton.addEventListener('click', () => {\n      document.body.removeChild(popupOverlay);\n    });\n\n    // Logo and heading\n    const logoContainer = document.createElement('div');\n    logoContainer.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 16px;\n  `;\n\n    const heading = document.createElement('h2');\n    heading.textContent = 'Sign in to OpenMemory';\n    heading.style.cssText = `\n    margin: 0;\n    font-size: 18px;\n    font-weight: 600;\n  `;\n\n    logoContainer.appendChild(heading);\n\n    // Message\n    const message = document.createElement('p');\n    message.textContent = 'Please sign in to access your memories and enhance your conversations!';\n    message.style.cssText = `\n    margin-bottom: 24px;\n    color: #e8eaed;\n    font-size: 14px;\n    line-height: 1.5;\n    text-align: center;\n  `;\n\n    // Sign in button\n    const signInButton = document.createElement('button');\n    signInButton.style.cssText = `\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    padding: 10px;\n    background-color: #1a73e8;\n    color: white;\n    border: none;\n    border-radius: 8px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background-color 0.2s;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    gap: 8px;\n  `;\n\n    // Add logo and text\n    const logoDark = document.createElement('img');\n    logoDark.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n    logoDark.style.cssText = `\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n  `;\n\n    const signInText = document.createElement('span');\n    signInText.textContent = 'Sign in with OpenMemory';\n\n    signInButton.appendChild(logoDark);\n    signInButton.appendChild(signInText);\n\n    signInButton.addEventListener('mouseenter', () => {\n      signInButton.style.backgroundColor = '#1557b0';\n    });\n\n    signInButton.addEventListener('mouseleave', () => {\n      signInButton.style.backgroundColor = '#1a73e8';\n    });\n\n    // Open sign-in page when clicked\n    signInButton.addEventListener('click', () => {\n      window.open('https://app.mem0.ai/login', '_blank');\n      document.body.removeChild(popupOverlay);\n    });\n\n    // Assemble popup\n    popupContainer.appendChild(logoContainer);\n    popupContainer.appendChild(message);\n    popupContainer.appendChild(signInButton);\n\n    popupOverlay.appendChild(popupContainer);\n    popupOverlay.appendChild(closeButton);\n\n    // Add click event to close when clicking outside\n    popupOverlay.addEventListener('click', e => {\n      if (e.target === popupOverlay) {\n        document.body.removeChild(popupOverlay);\n      }\n    });\n\n    // Add to body\n    document.body.appendChild(popupOverlay);\n  }\n\n  function createMemoryModal(memoryItems: MemoryItem[], isLoading: boolean = false) {\n    // Preserve current modal position if it exists\n    let preservedPosition: { top: number; left: number } | null = null;\n    if (memoryModalShown && currentModalOverlay) {\n      const existingModal = currentModalOverlay.querySelector('div[style*=\"position: absolute\"]');\n      if (existingModal) {\n        preservedPosition = {\n          top: parseInt(existingModal.style.top) || 0,\n          left: parseInt(existingModal.style.left) || 0,\n        };\n      }\n      document.body.removeChild(currentModalOverlay);\n    }\n\n    memoryModalShown = true;\n    let currentMemoryIndex = 0;\n\n    // Calculate modal dimensions (estimated)\n    const modalWidth = 447;\n    let modalHeight = 400; // Default height\n    let memoriesPerPage = 3; // Default number of memories per page\n\n    let topPosition;\n    let leftPosition;\n\n    // Use preserved position if available, otherwise calculate new position\n    if (preservedPosition) {\n      topPosition = preservedPosition.top;\n      leftPosition = preservedPosition.left;\n\n      // Ensure the preserved position is still within viewport bounds\n      const maxX = window.innerWidth - modalWidth;\n      const maxY = window.innerHeight - modalHeight;\n      leftPosition = Math.max(0, Math.min(leftPosition, maxX));\n      topPosition = Math.max(0, Math.min(topPosition, maxY));\n    } else {\n      // Position relative to the Mem0 button (original logic)\n      const mem0Button = document.querySelector('#mem0-icon-button');\n\n      if (mem0Button) {\n        const buttonRect = mem0Button.getBoundingClientRect();\n\n        // Determine if there's enough space below the button\n        const viewportHeight = window.innerHeight;\n        const spaceBelow = viewportHeight - buttonRect.bottom;\n\n        // Position the modal centered under the button\n        leftPosition = Math.max(buttonRect.left + buttonRect.width / 2 - modalWidth / 2, 10);\n        // Ensure the modal doesn't go off the right edge of the screen\n        const rightEdgePosition = leftPosition + modalWidth;\n        if (rightEdgePosition > window.innerWidth - 10) {\n          leftPosition = window.innerWidth - modalWidth - 10;\n        }\n\n        if (spaceBelow >= modalHeight) {\n          // Place below the button\n          topPosition = buttonRect.bottom + 10;\n        } else {\n          // Place above the button if not enough space below\n          topPosition = buttonRect.top - modalHeight - 10;\n          // Check if it's in the upper half of the screen\n          if (buttonRect.top < viewportHeight / 2) {\n            modalHeight = 300; // Reduced height\n            memoriesPerPage = 2; // Show only 2 memories\n          }\n        }\n      } else {\n        // Fallback positioning\n        topPosition = 100;\n        leftPosition = window.innerWidth / 2 - modalWidth / 2;\n      }\n    }\n\n    // Create modal overlay\n    const modalOverlay = document.createElement('div');\n    modalOverlay.style.cssText = `\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    display: flex;\n    z-index: 10000;\n    pointer-events: auto;\n  `;\n\n    // Save reference to current modal overlay\n    currentModalOverlay = modalOverlay;\n\n    // Add event listener to close modal when clicking outside\n    modalOverlay.addEventListener('click', event => {\n      // Only close if clicking directly on the overlay, not its children\n      if (event.target === modalOverlay) {\n        closeModal();\n      }\n    });\n\n    // Create modal container with positioning\n    const modalContainer = document.createElement('div');\n    modalContainer.style.cssText = `\n    background-color: #2d2e30;\n    border-radius: 12px;\n    width: ${modalWidth}px;\n    height: ${modalHeight}px;\n    display: flex;\n    flex-direction: column;\n    color: white;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n    position: absolute;\n    top: ${topPosition}px;\n    left: ${leftPosition}px;\n    pointer-events: auto;\n    border: 1px solid #5f6368;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n    overflow: hidden;\n    cursor: move;\n    `;\n\n    // Add drag functionality\n    let isDragging = false;\n    let dragStartX = 0;\n    let dragStartY = 0;\n    let modalStartX = 0;\n    let modalStartY = 0;\n\n    function startDrag(e: MouseEvent) {\n      // Only allow dragging from the header area\n      const target = e.target as HTMLElement | null;\n      if ((target && target.closest('.mem0-modal-header')) || e.target === modalContainer) {\n        isDragging = true;\n        dragStartX = e.clientX;\n        dragStartY = e.clientY;\n        modalStartX = parseInt(modalContainer.style.left);\n        modalStartY = parseInt(modalContainer.style.top);\n        modalContainer.style.cursor = 'grabbing';\n        e.preventDefault();\n      }\n    }\n\n    function doDrag(e: MouseEvent) {\n      if (!isDragging) {\n        return;\n      }\n\n      const deltaX = e.clientX - dragStartX;\n      const deltaY = e.clientY - dragStartY;\n\n      let newX = modalStartX + deltaX;\n      let newY = modalStartY + deltaY;\n\n      // Constrain to viewport\n      const maxX = window.innerWidth - modalWidth;\n      const maxY = window.innerHeight - modalHeight;\n\n      newX = Math.max(0, Math.min(newX, maxX));\n      newY = Math.max(0, Math.min(newY, maxY));\n\n      modalContainer.style.left = newX + 'px';\n      modalContainer.style.top = newY + 'px';\n    }\n\n    function stopDrag() {\n      isDragging = false;\n      modalContainer.style.cursor = 'move';\n    }\n\n    modalContainer.addEventListener('mousedown', startDrag);\n    document.addEventListener('mousemove', doDrag);\n    document.addEventListener('mouseup', stopDrag);\n\n    // Create modal header\n    const modalHeader = document.createElement('div');\n    modalHeader.className = 'mem0-modal-header';\n    modalHeader.style.cssText = `\n    display: flex;\n    align-items: center;\n    padding: 10px 16px;\n    justify-content: space-between;\n    background-color: #35373a;\n    flex-shrink: 0;\n    cursor: grab;\n  `;\n\n    modalHeader.addEventListener('mousedown', () => {\n      modalHeader.style.cursor = 'grabbing';\n    });\n\n    modalHeader.addEventListener('mouseup', () => {\n      modalHeader.style.cursor = 'grab';\n    });\n\n    // Create header left section with just the logo\n    const headerLeft = document.createElement('div');\n    headerLeft.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n  `;\n\n    // Add Mem0 logo\n    const logoImg = document.createElement('img');\n    logoImg.src = chrome.runtime.getURL('icons/mem0-claude-icon.png');\n    logoImg.style.cssText = `\n    width: 26px;\n    height: 26px;\n    border-radius: 50%;\n  `;\n\n    // Add \"OpenMemory\" title\n    const title = document.createElement('div');\n    title.textContent = 'OpenMemory';\n    title.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: white;\n    margin-left: 8px;\n  `;\n\n    // Create header right section\n    const headerRight = document.createElement('div');\n    headerRight.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n  `;\n\n    // Create Add to Prompt button with arrow\n    const addToPromptBtn = document.createElement('button');\n    addToPromptBtn.style.cssText = `\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    padding: 5px 16px;\n    gap: 8px;\n    background-color:rgb(27, 27, 27);\n    border: none;\n    border-radius: 8px;\n    cursor: pointer;\n    font-size: 12px;\n    font-weight: 600;\n    color: white;\n    transition: background-color 0.2s;\n  `;\n    addToPromptBtn.textContent = 'Add to Prompt';\n\n    // Add arrow icon to button\n    const arrowIcon = document.createElement('span');\n    arrowIcon.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M5 12h14M12 5l7 7-7 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>\n`;\n\n    arrowIcon.style.position = 'relative';\n    arrowIcon.style.top = '2px';\n    addToPromptBtn.appendChild(arrowIcon);\n\n    // Add hover effect for Add to Prompt button\n    addToPromptBtn.addEventListener('mouseenter', () => {\n      addToPromptBtn.style.backgroundColor = 'rgb(36, 36, 36)';\n    });\n    addToPromptBtn.addEventListener('mouseleave', () => {\n      addToPromptBtn.style.backgroundColor = 'rgb(27, 27, 27)';\n    });\n\n    // Create settings button\n    const settingsBtn = document.createElement('button');\n    settingsBtn.style.cssText = `\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 8px;\n    opacity: 0.6;\n    transition: opacity 0.2s;\n  `;\n    settingsBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#FFFFFF\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M12 15a3 3 0 100-6 3 3 0 000 6z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    <path d=\"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n\n    // Add click event to open app.mem0.ai in a new tab\n    settingsBtn.addEventListener('click', () => {\n      if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n        document.body.removeChild(currentModalOverlay);\n        memoryModalShown = false;\n        currentModalOverlay = null;\n      }\n\n      chrome.runtime.sendMessage({ action: SidebarAction.SIDEBAR_SETTINGS });\n    });\n\n    // Add hover effect for the settings button\n    settingsBtn.addEventListener('mouseenter', () => {\n      settingsBtn.style.opacity = '1';\n    });\n    settingsBtn.addEventListener('mouseleave', () => {\n      settingsBtn.style.opacity = '0.6';\n    });\n\n    // Content section\n    const contentSection = document.createElement('div');\n    const contentSectionHeight = modalHeight - 130; // Account for header and navigation\n    contentSection.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    padding: 0 16px;\n    gap: 12px;\n    overflow: hidden;\n    flex: 1;\n    height: ${contentSectionHeight}px;\n  `;\n\n    // Create memories counter\n    const memoriesCounter = document.createElement('div');\n    memoriesCounter.style.cssText = `\n    font-size: 16px;\n    font-weight: 600;\n    color: #FFFFFF;\n    margin-top: 16px;\n    flex-shrink: 0;\n  `;\n\n    // Update counter text based on loading state and number of memories\n    if (isLoading) {\n      memoriesCounter.textContent = `Loading Relevant Memories...`;\n    } else {\n      // Filter out memories that have already been added for accurate count\n      const availableMemoriesCount = memoryItems.filter(\n        (memory: MemoryItem) => memory && memory.id && !allMemoriesById.has(memory.id)\n      ).length;\n      memoriesCounter.textContent = `${availableMemoriesCount} Relevant Memories`;\n    }\n\n    // Calculate max height for memories content based on modal height\n    const memoriesContentMaxHeight = contentSectionHeight - 40; // Account for memories counter\n\n    // Create memories content container with adjusted height\n    const memoriesContent = document.createElement('div');\n    memoriesContent.style.cssText = `\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    overflow-y: auto;\n    flex: 1;\n    max-height: ${memoriesContentMaxHeight}px;\n    padding-right: 8px;\n    margin-right: -8px;\n    scrollbar-width: thin;\n    scrollbar-color: #5f6368 transparent;\n  `;\n\n    // Track currently expanded memory\n    let currentlyExpandedMemory: HTMLElement | null = null;\n\n    // Function to create skeleton loading items (adjusted for different heights)\n    function createSkeletonItems() {\n      memoriesContent.innerHTML = '';\n\n      for (let i = 0; i < memoriesPerPage; i++) {\n        const skeletonItem = document.createElement('div');\n        skeletonItem.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px;\n        background-color: #3c4043;\n        border-radius: 8px;\n        height: 72px;\n        flex-shrink: 0;\n        animation: pulse 1.5s infinite ease-in-out;\n      `;\n\n        const skeletonText = document.createElement('div');\n        skeletonText.style.cssText = `\n        background-color: #5f6368;\n        border-radius: 4px;\n        height: 14px;\n        width: 85%;\n        margin-bottom: 8px;\n      `;\n\n        const skeletonText2 = document.createElement('div');\n        skeletonText2.style.cssText = `\n        background-color: #5f6368;\n        border-radius: 4px;\n        height: 14px;\n        width: 65%;\n      `;\n\n        const skeletonActions = document.createElement('div');\n        skeletonActions.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n      `;\n\n        const skeletonButton1 = document.createElement('div');\n        skeletonButton1.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #5f6368;\n      `;\n\n        const skeletonButton2 = document.createElement('div');\n        skeletonButton2.style.cssText = `\n        width: 20px;\n        height: 20px;\n        border-radius: 50%;\n        background-color: #5f6368;\n      `;\n\n        skeletonActions.appendChild(skeletonButton1);\n        skeletonActions.appendChild(skeletonButton2);\n\n        const textContainer = document.createElement('div');\n        textContainer.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n        textContainer.appendChild(skeletonText);\n        textContainer.appendChild(skeletonText2);\n\n        skeletonItem.appendChild(textContainer);\n        skeletonItem.appendChild(skeletonActions);\n        memoriesContent.appendChild(skeletonItem);\n      }\n\n      // Add keyframe animation to document if not exists\n      if (!document.getElementById('skeleton-animation')) {\n        const style = document.createElement('style');\n        style.id = 'skeleton-animation';\n        style.innerHTML = `\n        @keyframes pulse {\n          0% { opacity: 0.6; }\n          50% { opacity: 0.8; }\n          100% { opacity: 0.6; }\n        }\n      `;\n        document.head.appendChild(style);\n      }\n    }\n\n    // Function to show empty state\n    function showEmptyState() {\n      memoriesContent.innerHTML = '';\n\n      const emptyContainer = document.createElement('div');\n      emptyContainer.style.cssText = `\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 32px 16px;\n      text-align: center;\n      flex: 1;\n      min-height: 200px;\n    `;\n\n      const emptyIcon = document.createElement('div');\n      emptyIcon.innerHTML = `<svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9aa0a6\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path d=\"M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v10a2 2 0 01-2 2h-4M3 21h4a2 2 0 002-2v-4m-6 6V9m18 12a9 9 0 11-18 0 9 9 0 0118 0z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n    </svg>`;\n      emptyIcon.style.marginBottom = '16px';\n\n      const emptyText = document.createElement('div');\n      emptyText.textContent = 'No relevant memories found';\n      emptyText.style.cssText = `\n      color: #9aa0a6;\n      font-size: 14px;\n      font-weight: 500;\n    `;\n\n      emptyContainer.appendChild(emptyIcon);\n      emptyContainer.appendChild(emptyText);\n      memoriesContent.appendChild(emptyContainer);\n    }\n\n    // Add content to modal\n    contentSection.appendChild(memoriesCounter);\n    contentSection.appendChild(memoriesContent);\n\n    // Navigation section at bottom\n    const navigationSection = document.createElement('div');\n    navigationSection.style.cssText = `\n    display: flex;\n    justify-content: center;\n    gap: 12px;\n    padding: 10px;\n    border-top: none;\n    flex-shrink: 0;\n  `;\n\n    // Navigation buttons\n    const prevButton = document.createElement('button');\n    prevButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M15 19l-7-7 7-7\" stroke=\"#9aa0a6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n    prevButton.style.cssText = `\n    background: #3c4043;\n    border: none;\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  `;\n\n    const nextButton = document.createElement('button');\n    nextButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M9 5l7 7-7 7\" stroke=\"#9aa0a6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n  </svg>`;\n    nextButton.style.cssText = prevButton.style.cssText;\n\n    navigationSection.appendChild(prevButton);\n    navigationSection.appendChild(nextButton);\n\n    // Assemble modal\n    headerLeft.appendChild(logoImg);\n    headerLeft.appendChild(title);\n    headerRight.appendChild(addToPromptBtn);\n    headerRight.appendChild(settingsBtn);\n\n    modalHeader.appendChild(headerLeft);\n    modalHeader.appendChild(headerRight);\n\n    modalContainer.appendChild(modalHeader);\n    modalContainer.appendChild(contentSection);\n    modalContainer.appendChild(navigationSection);\n\n    modalOverlay.appendChild(modalContainer);\n\n    // Append to body\n    document.body.appendChild(modalOverlay);\n\n    // Function to show memories with adjusted count based on modal position\n    function showMemories() {\n      memoriesContent.innerHTML = '';\n\n      if (isLoading) {\n        createSkeletonItems();\n        return;\n      }\n\n      // Filter out memories that have already been added\n      const availableMemories = memoryItems.filter((memory: MemoryItem) => {\n        const hasId = memory && typeof memory.id === 'string';\n        const isAlreadyAdded = hasId && allMemoriesById.has(memory.id as string);\n        return hasId && !isAlreadyAdded;\n      });\n\n      // Update counter with actual available memories count\n      memoriesCounter.textContent = isLoading\n        ? 'Loading Relevant Memories...'\n        : `${availableMemories.length} Relevant Memories`;\n\n      if (availableMemories.length === 0) {\n        showEmptyState();\n        updateNavigationState(0, 0);\n        return;\n      }\n\n      // Use the dynamically set memoriesPerPage value\n      const memoriesToShow = Math.min(memoriesPerPage, availableMemories.length);\n\n      // Calculate total pages and current page based on available memories\n      const totalPages = Math.ceil(availableMemories.length / memoriesToShow);\n      const currentPage = Math.floor(currentMemoryIndex / memoriesToShow) + 1;\n\n      // Adjust currentMemoryIndex if it exceeds available memories\n      if (currentMemoryIndex >= availableMemories.length) {\n        currentMemoryIndex = Math.max(0, availableMemories.length - memoriesToShow);\n      }\n\n      // Update navigation buttons state\n      updateNavigationState(currentPage, totalPages);\n\n      for (let i = 0; i < memoriesToShow; i++) {\n        const memoryIndex = currentMemoryIndex + i;\n        if (memoryIndex >= availableMemories.length) {\n          break;\n        } // Stop if we've reached the end\n\n        const memory = availableMemories[memoryIndex]!;\n\n        // Ensure memory has an ID\n        if (!memory.id) {\n          memory.id = `memory-${Date.now()}-${memoryIndex}`;\n        }\n\n        const memoryContainer = document.createElement('div');\n        memoryContainer.style.cssText = `\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: space-between;\n        padding: 12px; \n        background-color: #3c4043;\n        border-radius: 8px;\n        cursor: pointer;\n        transition: all 0.2s ease;\n        min-height: 68px; \n        max-height: 68px; \n        overflow: hidden;\n        flex-shrink: 0;\n      `;\n\n        const memoryText = document.createElement('div');\n        memoryText.style.cssText = `\n        font-size: 14px;\n        line-height: 1.5;\n        color: #e8eaed;\n        flex-grow: 1;\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        overflow: hidden;\n        transition: all 0.2s ease;\n        height: 42px; /* Height for 2 lines of text */\n      `;\n        memoryText.textContent = String(memory.text || '');\n\n        const actionsContainer = document.createElement('div');\n        actionsContainer.style.cssText = `\n        display: flex;\n        gap: 4px;\n        margin-left: 10px;\n        flex-shrink: 0;\n      `;\n\n        // Add button\n        const addButton = document.createElement('button');\n        addButton.style.cssText = `\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        background: #5f6368;\n        color: #e8eaed;\n        border-radius: 100%;\n        width: 28px;\n        height: 28px;\n        transition: all 0.2s ease;\n      `;\n\n        addButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <path d=\"M12 5v14M5 12h14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      </svg>`;\n\n        // Add hover effect for add button\n        addButton.addEventListener('mouseenter', () => {\n          addButton.style.backgroundColor = 'rgb(36, 36, 36)';\n        });\n        addButton.addEventListener('mouseleave', () => {\n          addButton.style.backgroundColor = '#5f6368';\n        });\n\n        // Add click handler for add button\n        addButton.addEventListener('click', (e: MouseEvent) => {\n          e.stopPropagation();\n\n          sendExtensionEvent('memory_injection', {\n            provider: 'replit',\n            source: 'OPENMEMORY_CHROME_EXTENSION',\n            browser: getBrowser(),\n            injected_all: false,\n            memory_id: memory.id,\n          });\n\n          // Add this memory\n          allMemoriesById.add(String(memory.id));\n          allMemories.push(String(memory.text || ''));\n          updateInputWithMemories();\n\n          // Refresh the memories display (no need to remove from memoryItems)\n          showMemories();\n        });\n\n        // Menu button\n        const menuButton = document.createElement('button');\n        menuButton.style.cssText = `\n        background: none;\n        border: none;\n        cursor: pointer;\n        padding: 4px;\n        color: #9aa0a6;\n      `;\n        menuButton.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n        <circle cx=\"12\" cy=\"12\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"5\" r=\"2\"/>\n        <circle cx=\"12\" cy=\"19\" r=\"2\"/>\n      </svg>`;\n\n        // Track expanded state\n        let isExpanded = false;\n\n        // Create remove button (hidden by default)\n        const removeButton = document.createElement('button');\n        removeButton.style.cssText = `\n        display: none;\n        align-items: center;\n        gap: 6px;\n        background: #5f6368;\n        color: #e8eaed;\n        border-radius: 8px;\n        padding: 2px 4px;\n        border: none;\n        cursor: pointer;\n        font-size: 13px;\n        margin-top: 12px;\n        width: fit-content;\n        transition: background-color 0.2s;\n      `;\n        removeButton.innerHTML = `\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n        </svg>\n        Remove\n      `;\n\n        // Add hover effect for remove button\n        removeButton.addEventListener('mouseenter', () => {\n          removeButton.style.backgroundColor = '#ea4335';\n        });\n        removeButton.addEventListener('mouseleave', () => {\n          removeButton.style.backgroundColor = '#5f6368';\n        });\n\n        // Create content wrapper for text and remove button\n        const contentWrapper = document.createElement('div');\n        contentWrapper.style.cssText = `\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n      `;\n        contentWrapper.appendChild(memoryText);\n        contentWrapper.appendChild(removeButton);\n\n        // Function to expand memory\n        function expandMemory() {\n          if (currentlyExpandedMemory && currentlyExpandedMemory !== memoryContainer) {\n            currentlyExpandedMemory.dispatchEvent(new Event('collapse'));\n          }\n\n          isExpanded = true;\n          memoryText.style.webkitLineClamp = 'unset';\n          memoryText.style.height = 'auto';\n          contentWrapper.style.overflowY = 'auto';\n          contentWrapper.style.maxHeight = '240px'; // Limit height to prevent overflow\n          contentWrapper.style.scrollbarWidth = 'thin';\n          contentWrapper.style.scrollbarColor = '#5f6368 transparent';\n          memoryContainer.style.backgroundColor = '#2d2e30';\n          memoryContainer.style.maxHeight = '300px'; // Allow expansion but within container\n          memoryContainer.style.overflow = 'hidden';\n          removeButton.style.display = 'flex';\n          currentlyExpandedMemory = memoryContainer;\n\n          // Scroll to make expanded memory visible if needed\n          memoriesContent.scrollTop = memoryContainer.offsetTop - memoriesContent.offsetTop;\n        }\n\n        // Function to collapse memory\n        function collapseMemory() {\n          isExpanded = false;\n          memoryText.style.webkitLineClamp = '2';\n          memoryText.style.height = '42px';\n          contentWrapper.style.overflowY = 'visible';\n          memoryContainer.style.backgroundColor = '#3c4043';\n          memoryContainer.style.maxHeight = '72px';\n          memoryContainer.style.overflow = 'hidden';\n          removeButton.style.display = 'none';\n          currentlyExpandedMemory = null;\n        }\n\n        memoryContainer.addEventListener('collapse', collapseMemory);\n\n        menuButton.addEventListener('click', e => {\n          e.stopPropagation();\n          if (isExpanded) {\n            collapseMemory();\n          } else {\n            expandMemory();\n          }\n        });\n\n        // Add click handler for remove button\n        removeButton.addEventListener('click', (e: MouseEvent) => {\n          e.stopPropagation();\n          // Remove from memoryItems\n          const index = memoryItems.findIndex(m => m.id === memory.id);\n          if (index !== -1) {\n            memoryItems.splice(index, 1);\n\n            // Refresh the memories display\n            showMemories();\n          }\n        });\n\n        actionsContainer.appendChild(addButton);\n        actionsContainer.appendChild(menuButton);\n\n        memoryContainer.appendChild(contentWrapper);\n        memoryContainer.appendChild(actionsContainer);\n        memoriesContent.appendChild(memoryContainer);\n\n        // Add hover effect\n        memoryContainer.addEventListener('mouseenter', () => {\n          memoryContainer.style.backgroundColor = isExpanded ? '#25272a' : '#484b4f';\n        });\n        memoryContainer.addEventListener('mouseleave', () => {\n          memoryContainer.style.backgroundColor = isExpanded ? '#2d2e30' : '#3c4043';\n        });\n      }\n\n      // If after filtering for already added memories, there are no items to show,\n      // check if we need to go to previous page\n      if (memoriesContent.children.length === 0 && availableMemories.length > 0) {\n        if (currentMemoryIndex > 0) {\n          currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n          showMemories();\n        } else {\n          showEmptyState();\n        }\n      }\n    }\n\n    // Add navigation button handlers\n    prevButton.addEventListener('click', () => {\n      if (currentMemoryIndex >= memoriesPerPage) {\n        currentMemoryIndex = Math.max(0, currentMemoryIndex - memoriesPerPage);\n        showMemories();\n      }\n    });\n\n    nextButton.addEventListener('click', () => {\n      const availableMemories = memoryItems.filter(\n        (memory: MemoryItem) => !allMemoriesById.has(memory.id as string)\n      );\n      if (currentMemoryIndex + memoriesPerPage < availableMemories.length) {\n        currentMemoryIndex = currentMemoryIndex + memoriesPerPage;\n        showMemories();\n      }\n    });\n\n    // Add hover effects\n    [prevButton, nextButton].forEach(button => {\n      button.addEventListener('mouseenter', () => {\n        if (!button.disabled) {\n          button.style.backgroundColor = '#484b4f';\n        }\n      });\n      button.addEventListener('mouseleave', () => {\n        if (!button.disabled) {\n          button.style.backgroundColor = '#3c4043';\n        }\n      });\n    });\n\n    // Show initial memories\n    showMemories();\n\n    // Update navigation button states\n    function updateNavigationState(currentPage: number, totalPages: number) {\n      if (memoryItems.length === 0 || totalPages === 0) {\n        prevButton.disabled = true;\n        prevButton.style.opacity = '0.5';\n        prevButton.style.cursor = 'not-allowed';\n        nextButton.disabled = true;\n        nextButton.style.opacity = '0.5';\n        nextButton.style.cursor = 'not-allowed';\n        return;\n      }\n\n      if (currentPage <= 1) {\n        prevButton.disabled = true;\n        prevButton.style.opacity = '0.5';\n        prevButton.style.cursor = 'not-allowed';\n      } else {\n        prevButton.disabled = false;\n        prevButton.style.opacity = '1';\n        prevButton.style.cursor = 'pointer';\n      }\n\n      if (currentPage >= totalPages) {\n        nextButton.disabled = true;\n        nextButton.style.opacity = '0.5';\n        nextButton.style.cursor = 'not-allowed';\n      } else {\n        nextButton.disabled = false;\n        nextButton.style.opacity = '1';\n        nextButton.style.cursor = 'pointer';\n      }\n    }\n\n    // Update Add to Prompt button click handler\n    addToPromptBtn.addEventListener('click', () => {\n      // Only add memories that are not already added\n      const newMemories = memoryItems\n        .filter((memory: MemoryItem) => !allMemoriesById.has(memory.id as string))\n        .map((memory: MemoryItem) => {\n          allMemoriesById.add(memory.id as string);\n          return memory.text;\n        });\n\n      sendExtensionEvent('memory_injection', {\n        provider: 'replit',\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        browser: getBrowser(),\n        injected_all: true,\n        memory_count: newMemories.length,\n      });\n\n      // Add all new memories to allMemories\n      allMemories.push(...newMemories);\n\n      // Update the input with all memories\n      if (allMemories.length > 0) {\n        updateInputWithMemories();\n        closeModal();\n      } else {\n        // If no new memories were added but we have existing ones, just close\n        if (allMemoriesById.size > 0) {\n          closeModal();\n        }\n      }\n    });\n\n    // Function to close the modal\n    function closeModal() {\n      if (currentModalOverlay && document.body.contains(currentModalOverlay)) {\n        // Clean up drag event listeners\n        document.removeEventListener('mousemove', doDrag);\n        document.removeEventListener('mouseup', stopDrag);\n\n        document.body.removeChild(currentModalOverlay);\n      }\n      currentModalOverlay = null;\n      memoryModalShown = false;\n    }\n  }\n  // function injectMem0Button() {}\n  // Function to update notification dot visibility based on text in the input\n  function updateNotificationDot() {\n    const textarea = getTextarea();\n    const notificationDot = document.querySelector('#mem0-notification-dot');\n\n    if (!textarea || !notificationDot) {\n      return;\n    }\n\n    // Prevent duplicate observers\n    if (notificationObserver) {\n      notificationObserver.disconnect();\n    }\n\n    // Function to check if input has text\n    const checkForText = () => {\n      const inputText = textarea.textContent || textarea.innerText || '';\n      const hasText = inputText.trim() !== '';\n\n      if (hasText) {\n        notificationDot.classList.add('active');\n        // Force display style\n        notificationDot.style.display = 'block';\n      } else {\n        notificationDot.classList.remove('active');\n        notificationDot.style.display = 'none';\n      }\n    };\n\n    // Set up a single observer to watch for changes to the input field\n    notificationObserver = new MutationObserver(checkForText);\n\n    // Start observing the input element\n    notificationObserver.observe(textarea, {\n      characterData: true,\n      subtree: true,\n      childList: true,\n    });\n\n    // Add event listeners only if not already added\n    if (!textarea.dataset.mem0NotificationListener) {\n      textarea.dataset.mem0NotificationListener = 'true';\n      textarea.addEventListener('input', checkForText);\n      textarea.addEventListener('keyup', checkForText);\n      textarea.addEventListener('focus', checkForText);\n    }\n\n    // Initial check\n    checkForText();\n  }\n} catch (error) {\n  console.error('[Mem0] Critical error in content script:', error);\n}\n"
  },
  {
    "path": "src/search_tracker.ts",
    "content": "import { DEFAULT_USER_ID } from './types/api';\nimport type { HistoryStateData, HistoryUrl } from './types/browser';\nimport type { Settings } from './types/settings';\nimport { StorageKey } from './types/storage';\n\n(function () {\n  // Utilities\n  function normalize(text: string): string {\n    return (text || '').replace(/\\s+/g, ' ').trim();\n  }\n\n  function getSettings(): Promise<Settings> {\n    return new Promise<Settings>(resolve => {\n      chrome.storage.sync.get(\n        [\n          StorageKey.API_KEY,\n          StorageKey.ACCESS_TOKEN,\n          StorageKey.USER_ID,\n          StorageKey.SELECTED_ORG,\n          StorageKey.SELECTED_PROJECT,\n          StorageKey.MEMORY_ENABLED,\n        ],\n        d => {\n          resolve({\n            hasCreds: Boolean(d[StorageKey.API_KEY] || d[StorageKey.ACCESS_TOKEN]),\n            apiKey: d[StorageKey.API_KEY],\n            accessToken: d[StorageKey.ACCESS_TOKEN],\n            userId: d[StorageKey.USER_ID] || DEFAULT_USER_ID,\n            orgId: d[StorageKey.SELECTED_ORG],\n            projectId: d[StorageKey.SELECTED_PROJECT],\n            memoryEnabled: d[StorageKey.MEMORY_ENABLED] !== false,\n          });\n        }\n      );\n    });\n  }\n\n  function maybeSend(engine: string, query: string): void {\n    const q = normalize(query);\n    if (!q || q.length < 2) {\n      return;\n    }\n\n    getSettings().then(async settings => {\n      if (!settings.hasCreds || settings.memoryEnabled === false) {\n        return;\n      }\n      // Gate by track_searches toggle (default off if undefined)\n      const allow = await new Promise<boolean>(resolve => {\n        try {\n          chrome.storage.sync.get([StorageKey.TRACK_SEARCHES], d => {\n            resolve(d[StorageKey.TRACK_SEARCHES] === true);\n          });\n        } catch {\n          resolve(false);\n        }\n      });\n      if (!allow) {\n        return;\n      }\n    });\n  }\n\n  // URL based capture for results pages\n  function urlCapture(): void {\n    const host = location.hostname || '';\n    const path = location.pathname || '';\n    const params = new URLSearchParams(location.search || '');\n\n    // Google results\n    if (/(^|\\.)google\\./.test(host) && path.startsWith('/search')) {\n      const q = params.get('q');\n      if (q) {\n        maybeSend('Google', q);\n      }\n      return;\n    }\n\n    // Bing results\n    if (host.endsWith('bing.com') && (path === '/search' || path === '/')) {\n      const q = params.get('q');\n      if (q) {\n        maybeSend('Bing', q);\n      }\n      return;\n    }\n\n    // Brave results\n    if (host === 'search.brave.com' && (path === '/search' || path === '/images')) {\n      const q = params.get('q');\n      if (q) {\n        maybeSend('Brave', q);\n      }\n      return;\n    }\n\n    // Arc results\n    if (host === 'search.arc.net' && (path === '/search' || path.startsWith('/search'))) {\n      const q = params.get('q') || params.get('query');\n      if (q) {\n        maybeSend('Arc', q);\n      }\n      return;\n    }\n  }\n\n  function installSpaUrlWatcher(): void {\n    const origPush = history.pushState.bind(history);\n    const origReplace = history.replaceState.bind(history);\n    const onUrlChange = () => {\n      try {\n        urlCapture();\n      } catch {\n        return;\n      }\n    };\n\n    history.pushState = function (data: HistoryStateData, unused: string, url?: HistoryUrl) {\n      origPush(data, unused, url);\n      onUrlChange();\n    };\n\n    history.replaceState = function (data: HistoryStateData, unused: string, url?: HistoryUrl) {\n      origReplace(data, unused, url);\n      onUrlChange();\n    };\n\n    window.addEventListener('popstate', onUrlChange);\n  }\n\n  // Run\n  urlCapture();\n  installSpaUrlWatcher();\n})();\n"
  },
  {
    "path": "src/selection_context.ts",
    "content": "import {\n  MessageType,\n  type SelectionContextMessage,\n  type SelectionContextPayload,\n  type SendResponse,\n  ToastVariant,\n} from './types/messages';\n\n(function () {\n  chrome.runtime.onMessage.addListener(\n    (msg: SelectionContextMessage, _sender, sendResponse: SendResponse) => {\n      if (msg && msg.type === MessageType.GET_SELECTION_CONTEXT) {\n        try {\n          const payload: SelectionContextPayload = {\n            selection: getSelectedText(),\n            title: document.title || '',\n            url: location.href,\n          };\n          sendResponse({ type: MessageType.SELECTION_CONTEXT, payload });\n        } catch (e) {\n          sendResponse({ type: MessageType.SELECTION_CONTEXT, error: String(e) });\n        }\n        return true;\n      }\n\n      if (msg && msg.type === MessageType.TOAST) {\n        const { message, variant = ToastVariant.SUCCESS } = msg.payload || {};\n        showToast(message || '', variant);\n      }\n    }\n  );\n\n  function getSelectedText(): string {\n    try {\n      const sel = window.getSelection && window.getSelection();\n      const text = sel ? sel.toString().trim() : '';\n      return text;\n    } catch {\n      return '';\n    }\n  }\n\n  function showToast(message: string, variant: ToastVariant = ToastVariant.SUCCESS): void {\n    try {\n      const id = 'mem0-context-toast';\n      const existing = document.getElementById(id);\n      if (existing) {\n        existing.remove();\n      }\n\n      const el = document.createElement('div');\n      el.id = id;\n      el.textContent = message;\n      el.style.cssText = `\n            position: fixed;\n            top: 16px;\n            right: 16px;\n            z-index: 2147483647;\n            background: ${variant === ToastVariant.ERROR ? '#7f1d1d' : '#14532d'};\n            color: #fff;\n            padding: 10px 12px;\n            border-radius: 8px;\n            font-size: 13px;\n            box-shadow: 0 6px 18px rgba(0,0,0,0.25);\n            max-width: 360px;\n          `;\n      document.body.appendChild(el);\n      setTimeout(() => {\n        el.remove();\n      }, 2200);\n    } catch {\n      // no-op\n    }\n  }\n})();\n"
  },
  {
    "path": "src/sidebar.ts",
    "content": "import { DEFAULT_USER_ID } from './types/api';\nimport type { MemoriesResponse, Memory } from './types/memory';\nimport { SidebarAction, type SidebarActionMessage } from './types/messages';\nimport type { Organization, Project } from './types/organizations';\nimport type { SidebarSettings } from './types/settings';\nimport { StorageKey } from './types/storage';\nimport { getBrowser, sendExtensionEvent } from './utils/util_functions';\n\n(function () {\n  let sidebarVisible = false;\n\n  function initializeMem0Sidebar(): void {\n    // Listen for messages from the extension\n    chrome.runtime.onMessage.addListener(\n      (request: SidebarActionMessage | { action: SidebarAction.SIDEBAR_SETTINGS }) => {\n        if (request.action === SidebarAction.TOGGLE_SIDEBAR) {\n          chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], function (data) {\n            if (data.apiKey || data.access_token) {\n              toggleSidebar();\n            } else {\n              chrome.runtime.sendMessage({ action: SidebarAction.OPEN_POPUP });\n            }\n          });\n        }\n        if (request.action === SidebarAction.SIDEBAR_SETTINGS) {\n          chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], function (data) {\n            if (data[StorageKey.API_KEY] || data[StorageKey.ACCESS_TOKEN]) {\n              toggleSidebar();\n\n              setTimeout(() => {\n                const settingsTabButton = document.querySelector<HTMLButtonElement>(\n                  '.tab-button[data-tab=\"settings\"]'\n                );\n                settingsTabButton?.click();\n              }, 200);\n            }\n          });\n        }\n        return undefined;\n      }\n    );\n  }\n\n  function toggleSidebar(): void {\n    // Track extension usage when sidebar is toggled\n    if (typeof sendExtensionEvent === 'function') {\n      sendExtensionEvent('extension_browser_icon_clicked', {\n        browser: getBrowser(),\n        source: 'OPENMEMORY_CHROME_EXTENSION',\n        tab_url: window.location.href,\n      });\n    }\n    const sidebar = document.getElementById('mem0-sidebar');\n    if (sidebar) {\n      // If sidebar exists, toggle its visibility\n      sidebarVisible = !sidebarVisible;\n      sidebar.style.right = sidebarVisible ? '0px' : '-600px';\n\n      // Add or remove click listener based on sidebar visibility\n      if (sidebarVisible) {\n        document.addEventListener('click', handleOutsideClick);\n        document.addEventListener('keydown', handleEscapeKey);\n        fetchMemoriesAndCount();\n      } else {\n        document.removeEventListener('click', handleOutsideClick);\n        document.removeEventListener('keydown', handleEscapeKey);\n      }\n    } else {\n      // If sidebar doesn't exist, create it\n      createSidebar();\n      sidebarVisible = true;\n      document.addEventListener('click', handleOutsideClick);\n      document.addEventListener('keydown', handleEscapeKey);\n    }\n  }\n\n  function handleEscapeKey(event: KeyboardEvent): void {\n    if (event.key === 'Escape') {\n      const searchInput = document.querySelector('.search-memory');\n\n      if (searchInput) {\n        closeSearchInput();\n      } else {\n        toggleSidebar();\n      }\n    }\n  }\n\n  function handleOutsideClick(event: MouseEvent): void {\n    const sidebar = document.getElementById('mem0-sidebar');\n    if (\n      sidebar &&\n      !sidebar.contains(event.target as Node) &&\n      !(event.target as HTMLElement)?.closest?.('.mem0-toggle-btn')\n    ) {\n      toggleSidebar();\n    }\n  }\n\n  function createSidebar(): void {\n    if (document.getElementById('mem0-sidebar')) {\n      return;\n    }\n\n    const sidebarContainer = document.createElement('div');\n    sidebarContainer.id = 'mem0-sidebar';\n\n    // Create fixed header\n    const fixedHeader = document.createElement('div');\n    fixedHeader.className = 'fixed-header';\n    fixedHeader.innerHTML = `\n        <div class=\"header\">\n          <div class=\"logo-container\">\n            <img src=${chrome.runtime.getURL('icons/mem0-claude-icon.png')} class=\"openmemory-icon\" alt=\"OpenMemory Logo\">\n            <span class=\"openmemory-logo\">OpenMemory</span>\n          </div>\n          <div class=\"header-buttons\">\n            <button id=\"closeBtn\" class=\"close-button\" title=\"Close\">\n              <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                <path d=\"M18 6L6 18\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n                <path d=\"M6 6L18 18\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n              </svg>\n            </button>\n          </div>\n        </div>\n      `;\n\n    // Create a container for search inputs\n    const inputContainer = document.createElement('div');\n    inputContainer.className = 'input-container';\n    fixedHeader.appendChild(inputContainer);\n\n    // Create tabs\n    const tabsContainer = document.createElement('div');\n    tabsContainer.className = 'tabs-container';\n    tabsContainer.innerHTML = `\n      <div class=\"tabs\">\n        <button class=\"tab-button active\" data-tab=\"memories\">Recent Memories</button>\n        <button class=\"tab-button\" data-tab=\"settings\">Settings</button>\n      </div>\n    `;\n    fixedHeader.appendChild(tabsContainer);\n\n    sidebarContainer.appendChild(fixedHeader);\n\n    // Create content container\n    const contentContainer = document.createElement('div');\n    contentContainer.className = 'content';\n\n    // Create memory count display\n    const memoryCountContainer = document.createElement('div');\n    memoryCountContainer.className = 'total-memories';\n    memoryCountContainer.innerHTML = `\n      <div class=\"total-memories-content\">\n        <div>\n          <p class=\"total-memories-label\">Total Memories</p>\n          <h3 class=\"memory-count loading\">Loading...</h3>\n        </div>\n        <button id=\"openDashboardBtn\" class=\"dashboard-button\">\n          Open Dashboard\n          <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" class=\"external-link-icon\">\n            <path d=\"M7 17L17 7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n            <path d=\"M7 7H17V17\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n          </svg>\n        </button>\n      </div>\n    `;\n\n    // Create memories tab content\n    const memoriesTabContent = document.createElement('div');\n    memoriesTabContent.className = 'tab-content active';\n    memoriesTabContent.id = 'memories-tab';\n    memoriesTabContent.appendChild(memoryCountContainer);\n\n    // Add memories section\n    const memoriesSection = document.createElement('div');\n    memoriesSection.className = 'section';\n    memoriesSection.innerHTML = `\n      <h2 class=\"section-title\">Recent Memories</h2>\n      <div class=\"memory-cards\">\n        <div class=\"memory-loader\">\n          <div class=\"loader\"></div>\n        </div>\n      </div>\n    `;\n    memoriesTabContent.appendChild(memoriesSection);\n\n    // Create settings tab content\n    const settingsTabContent = document.createElement('div');\n    settingsTabContent.className = 'tab-content';\n    settingsTabContent.id = 'settings-tab';\n\n    // Move memory suggestions to settings tab\n    const memoryToggleSection = document.createElement('div');\n    memoryToggleSection.className = 'section';\n    memoryToggleSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Memory Suggestions</h2>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"mem0Toggle\">\n          <span class=\"slider\"></span>\n        </label>\n      </div>\n      <p class=\"section-description\">Get relevant memories suggested while interacting with AI Agents</p>\n    `;\n    settingsTabContent.appendChild(memoryToggleSection);\n\n    // Track searches toggle section\n    const trackSearchSection = document.createElement('div');\n    trackSearchSection.className = 'section';\n    trackSearchSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Track searches</h2>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"trackSearchesToggle\">\n          <span class=\"slider\"></span>\n        </label>\n      </div>\n      <p class=\"section-description\">Save searches and typed URLs as memories</p>\n    `;\n    settingsTabContent.appendChild(trackSearchSection);\n\n    // Add user ID input section\n    const userIdSection = document.createElement('div');\n    userIdSection.className = 'section';\n    userIdSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">User ID</h2>\n        <button id=\"userDashboardBtn\" class=\"link-button\" title=\"Open Users Dashboard\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-external-link-icon lucide-external-link\">\n            <path d=\"M15 3h6v6\"/>\n            <path d=\"M10 14 21 3\"/>\n            <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>\n          </svg>\n        </button>\n      </div>\n      <input type=\"text\" id=\"userIdInput\" class=\"settings-input\" placeholder=\"Enter your user ID\" value=\"chrome-extension-user\">\n    `;\n    settingsTabContent.appendChild(userIdSection);\n\n    // Add organization select section\n    const orgSection = document.createElement('div');\n    orgSection.className = 'section';\n    orgSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Organization</h2>\n      </div>\n      <select id=\"orgSelect\" class=\"settings-select\">\n        <option value=\"\">Loading organizations...</option>\n      </select>\n    `;\n    settingsTabContent.appendChild(orgSection);\n\n    // Add project select section\n    const projectSection = document.createElement('div');\n    projectSection.className = 'section';\n    projectSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Project</h2>\n      </div>\n      <select id=\"projectSelect\" class=\"settings-select\">\n        <option value=\"\">Select an organization first</option>\n      </select>\n    `;\n    settingsTabContent.appendChild(projectSection);\n\n    // Add Auto-Inject toggle section\n    const autoInjectSection = document.createElement('div');\n    autoInjectSection.className = 'section';\n    autoInjectSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Enable Auto-Inject</h2>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"autoInjectToggle\" checked>\n          <span class=\"slider\"></span>\n        </label>\n      </div>\n      <p class=\"section-description\">Automatically inject relevant memories into conversations</p>\n    `;\n    // Disabling it for now as auto-inject is not working\n    // settingsTabContent.appendChild(autoInjectSection);\n\n    // Add threshold slider section\n    const thresholdSection = document.createElement('div');\n    thresholdSection.className = 'section';\n    thresholdSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Threshold</h2>\n        <span class=\"threshold-value\">0.3</span>\n      </div>\n      <p class=\"section-description\">Set the minimum similarity score for memory suggestions</p>\n      <div class=\"slider-container\">\n        <input type=\"range\" id=\"thresholdSlider\" class=\"threshold-slider\" min=\"0\" max=\"1\" step=\"0.1\" value=\"0.3\">\n        <div class=\"slider-labels\">\n          <span>0</span>\n          <span>0.5</span>\n          <span>1</span>\n        </div>\n      </div>\n    `;\n    settingsTabContent.appendChild(thresholdSection);\n\n    // Add top k section\n    const topKSection = document.createElement('div');\n    topKSection.className = 'section';\n    topKSection.innerHTML = `\n      <div class=\"section-header\">\n        <h2 class=\"section-title\">Top K</h2>\n      </div>\n      <p class=\"section-description\">Maximum number of memories to suggest</p>\n      <input type=\"number\" id=\"topKInput\" class=\"settings-input\" min=\"1\" max=\"50\" value=\"10\">\n    `;\n    settingsTabContent.appendChild(topKSection);\n\n    // Add save button section\n    const saveSection = document.createElement('div');\n    saveSection.className = 'section';\n    saveSection.innerHTML = `\n      <button id=\"saveSettingsBtn\" class=\"save-button\">\n        <span class=\"save-text\">Save Settings</span>\n        <div class=\"save-loader\" style=\"display: none;\">\n          <div class=\"mini-loader\"></div>\n        </div>\n      </button>\n      <div id=\"saveMessage\" class=\"save-message\" style=\"display: none;\"></div>\n    `;\n    settingsTabContent.appendChild(saveSection);\n\n    contentContainer.appendChild(memoriesTabContent);\n    contentContainer.appendChild(settingsTabContent);\n    sidebarContainer.appendChild(contentContainer);\n\n    // Create footer with shortcut and logout\n    const footerToggle = document.createElement('div');\n    footerToggle.className = 'footer';\n    footerToggle.innerHTML = `\n      <div class=\"shortcut\">Shortcut : ^ + M</div>\n      <button id=\"logoutBtn\" class=\"logout-button\"><span>Logout</span></button>\n    `;\n\n    // Load saved settings\n    chrome.storage.sync.get(\n      [\n        StorageKey.MEMORY_ENABLED,\n        StorageKey.USER_ID,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n        StorageKey.AUTO_INJECT_ENABLED,\n        StorageKey.SIMILARITY_THRESHOLD,\n        StorageKey.TOP_K,\n        StorageKey.TRACK_SEARCHES,\n      ],\n      function (result) {\n        const toggleCheckbox = memoryToggleSection.querySelector('#mem0Toggle') as HTMLInputElement;\n        if (toggleCheckbox) {\n          toggleCheckbox.checked = result[StorageKey.MEMORY_ENABLED] !== false;\n        }\n\n        // Load track searches (default: disabled)\n        const trackSearchesCheckbox = trackSearchSection.querySelector(\n          '#trackSearchesToggle'\n        ) as HTMLInputElement;\n        if (trackSearchesCheckbox) {\n          trackSearchesCheckbox.checked = result[StorageKey.TRACK_SEARCHES] === true;\n        }\n\n        const userIdInput = userIdSection.querySelector('#userIdInput') as HTMLInputElement;\n        // Set saved value or keep default value\n        if (result[StorageKey.USER_ID] && userIdInput) {\n          userIdInput.value = result[StorageKey.USER_ID];\n        }\n        // If no saved value, default is already set in HTML\n\n        // Load auto-inject setting (default: enabled)\n        const autoInjectCheckbox = autoInjectSection.querySelector(\n          '#autoInjectToggle'\n        ) as HTMLInputElement;\n        if (autoInjectCheckbox) {\n          autoInjectCheckbox.checked = result[StorageKey.AUTO_INJECT_ENABLED] !== false;\n        }\n\n        // Load threshold setting (default: 0.1)\n        const thresholdSlider = thresholdSection.querySelector(\n          '#thresholdSlider'\n        ) as HTMLInputElement;\n        const thresholdValue = thresholdSection.querySelector('.threshold-value') as HTMLElement;\n        const threshold =\n          result[StorageKey.SIMILARITY_THRESHOLD] !== undefined\n            ? result[StorageKey.SIMILARITY_THRESHOLD]\n            : 0.1;\n        if (thresholdSlider) {\n          thresholdSlider.value = String(threshold);\n        }\n        if (thresholdValue) {\n          thresholdValue.textContent = Number(threshold).toFixed(1);\n        }\n\n        // Load top k setting (default: 10)\n        const topKInput = topKSection.querySelector('#topKInput') as HTMLInputElement;\n        const topK = result[StorageKey.TOP_K] !== undefined ? result[StorageKey.TOP_K] : 10;\n        if (topKInput) {\n          topKInput.value = String(topK);\n        }\n      }\n    );\n\n    sidebarContainer.appendChild(footerToggle);\n\n    // Add event listeners\n    setupEventListeners(\n      sidebarContainer,\n      memoryToggleSection,\n      userIdSection,\n      orgSection,\n      projectSection,\n      autoInjectSection,\n      thresholdSection,\n      topKSection,\n      saveSection,\n      memoryCountContainer,\n      footerToggle,\n      trackSearchSection\n    );\n\n    document.body.appendChild(sidebarContainer);\n\n    // Slide in the sidebar immediately after creation\n    setTimeout(() => {\n      sidebarContainer.style.right = '0';\n    }, 0);\n\n    // Prevent clicks within the sidebar from closing it\n    sidebarContainer.addEventListener('click', event => {\n      event.stopPropagation();\n    });\n\n    // Add styles\n    addStyles();\n\n    // Fetch organizations and memories\n    fetchOrganizations();\n    fetchMemoriesAndCount();\n  }\n\n  function saveSettings(\n    saveBtn: HTMLButtonElement,\n    saveText: HTMLElement,\n    saveLoader: HTMLElement,\n    saveMessage: HTMLElement,\n    userIdSection: HTMLElement,\n    orgSection: HTMLElement,\n    projectSection: HTMLElement,\n    memoryToggleSection: HTMLElement,\n    autoInjectSection: HTMLElement,\n    thresholdSection: HTMLElement,\n    topKSection: HTMLElement,\n    trackSearchSection: HTMLElement\n  ): void {\n    // Show loading state\n    saveBtn.disabled = true;\n    saveText.style.display = 'none';\n    saveLoader.style.display = 'flex';\n    saveMessage.style.display = 'none';\n\n    // Get all the values\n    const userIdInput = userIdSection.querySelector('#userIdInput') as HTMLInputElement;\n    const orgSelect = orgSection.querySelector('#orgSelect') as HTMLSelectElement;\n    const projectSelect = projectSection.querySelector('#projectSelect') as HTMLSelectElement;\n    const toggleCheckbox = memoryToggleSection.querySelector('#mem0Toggle') as HTMLInputElement;\n    const autoInjectCheckbox = autoInjectSection.querySelector(\n      '#autoInjectToggle'\n    ) as HTMLInputElement;\n    const thresholdSlider = thresholdSection.querySelector('#thresholdSlider') as HTMLInputElement;\n    const topKInput = topKSection.querySelector('#topKInput') as HTMLInputElement;\n    const trackSearchesCheckbox = trackSearchSection.querySelector(\n      '#trackSearchesToggle'\n    ) as HTMLInputElement;\n\n    const userId = (userIdInput?.value || '').trim();\n    const selectedOrgId = orgSelect?.value || '';\n    const selectedOrgName = orgSelect?.options[orgSelect.selectedIndex]?.text || '';\n    const selectedProjectId = projectSelect?.value || '';\n    const selectedProjectName = projectSelect?.options[projectSelect.selectedIndex]?.text || '';\n    const memoryEnabled = Boolean(toggleCheckbox?.checked);\n    const autoInjectEnabled = Boolean(autoInjectCheckbox?.checked);\n    const similarityThreshold = parseFloat(thresholdSlider?.value || '0.3');\n    const topK = parseInt(topKInput?.value || '10', 10);\n\n    // Prepare settings object\n    const settings: SidebarSettings = {\n      user_id: userId || undefined,\n      selected_org: selectedOrgId || undefined,\n      selected_org_name: selectedOrgName || undefined,\n      selected_project: selectedProjectId || undefined,\n      selected_project_name: selectedProjectName || undefined,\n      memory_enabled: memoryEnabled,\n      auto_inject_enabled: autoInjectEnabled,\n      similarity_threshold: similarityThreshold,\n      top_k: topK,\n      track_searches: Boolean(trackSearchesCheckbox?.checked),\n    };\n\n    // Remove undefined values\n    (Object.keys(settings) as Array<keyof SidebarSettings>).forEach(key => {\n      if (settings[key] === undefined) {\n        delete settings[key];\n      }\n    });\n\n    // Save to chrome storage\n    chrome.storage.sync.set(settings, function () {\n      // Send toggle event to API\n      chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], function (data) {\n        const headers = getHeaders(data.apiKey, data.access_token);\n        fetch(`https://api.mem0.ai/v1/extension/`, {\n          method: 'POST',\n          headers: headers,\n          body: JSON.stringify({\n            event_type: 'extension_toggle_button',\n            additional_data: { status: memoryEnabled },\n          }),\n        }).catch(error => {\n          console.error('Error sending toggle event:', error);\n        });\n      });\n\n      // Send message to runtime\n      chrome.runtime.sendMessage({\n        action: SidebarAction.TOGGLE_MEM0,\n        enabled: memoryEnabled,\n      });\n\n      // Show success message\n      setTimeout(() => {\n        saveBtn.disabled = false;\n        saveText.style.display = 'inline';\n        saveLoader.style.display = 'none';\n        saveMessage.style.display = 'block';\n        saveMessage.className = 'save-message success';\n        saveMessage.textContent = 'Settings saved successfully!';\n\n        // Hide message after 3 seconds\n        setTimeout(() => {\n          saveMessage.style.display = 'none';\n        }, 3000);\n\n        // Refresh memories with new settings\n        fetchMemoriesAndCount();\n      }, 500);\n    });\n  }\n\n  function setupEventListeners(\n    sidebarContainer: HTMLElement,\n    memoryToggleSection: HTMLElement,\n    userIdSection: HTMLElement,\n    orgSection: HTMLElement,\n    projectSection: HTMLElement,\n    autoInjectSection: HTMLElement,\n    thresholdSection: HTMLElement,\n    topKSection: HTMLElement,\n    saveSection: HTMLElement,\n    memoryCountContainer: HTMLElement,\n    footerToggle: HTMLElement,\n    trackSearchSection: HTMLElement\n  ): void {\n    // Close button\n    const closeBtn = sidebarContainer.querySelector('#closeBtn') as HTMLButtonElement;\n    closeBtn?.addEventListener('click', toggleSidebar);\n\n    // Tab switching\n    const tabButtons = sidebarContainer.querySelectorAll<HTMLButtonElement>('.tab-button');\n    const tabContents = sidebarContainer.querySelectorAll<HTMLElement>('.tab-content');\n\n    tabButtons.forEach(button => {\n      button.addEventListener('click', function (this: HTMLButtonElement) {\n        const targetTab = this.getAttribute('data-tab');\n\n        // Remove active class from all tabs and contents\n        tabButtons.forEach(btn => btn.classList.remove('active'));\n        tabContents.forEach(content => content.classList.remove('active'));\n\n        // Add active class to clicked tab and corresponding content\n        this.classList.add('active');\n        document.getElementById(`${targetTab}-tab`)?.classList.add('active');\n      });\n    });\n\n    // Dashboard button\n    const openDashboardBtn = memoryCountContainer.querySelector(\n      '#openDashboardBtn'\n    ) as HTMLButtonElement;\n    openDashboardBtn?.addEventListener('click', openDashboard);\n\n    // Logout button\n    const logoutBtn = footerToggle.querySelector('#logoutBtn') as HTMLButtonElement;\n    logoutBtn?.addEventListener('click', logout);\n\n    // Toggle functionality is now handled by the save button\n\n    // Organization select (for loading projects only)\n    const orgSelect = orgSection.querySelector('#orgSelect') as HTMLSelectElement;\n    orgSelect?.addEventListener('change', function (this: HTMLSelectElement) {\n      const selectedOrgId = this.value;\n\n      // Reset project selection\n      const projectSelect = projectSection.querySelector('#projectSelect') as HTMLSelectElement;\n      if (projectSelect) {\n        projectSelect.innerHTML = '<option value=\"\">Loading projects...</option>';\n      }\n\n      // Fetch projects for selected org\n      if (selectedOrgId) {\n        fetchProjects(selectedOrgId, projectSelect);\n      } else {\n        if (projectSelect) {\n          projectSelect.innerHTML = '<option value=\"\">Select an organization first</option>';\n        }\n      }\n    });\n\n    // User dashboard link button\n    const userDashboardBtn = userIdSection.querySelector('#userDashboardBtn') as HTMLButtonElement;\n    userDashboardBtn?.addEventListener('click', function () {\n      chrome.runtime.sendMessage({\n        action: SidebarAction.OPEN_DASHBOARD,\n        url: 'https://app.mem0.ai/dashboard/users',\n      });\n    });\n\n    // Threshold slider event listener\n    const thresholdSlider = thresholdSection.querySelector('#thresholdSlider') as HTMLInputElement;\n    const thresholdValue = thresholdSection.querySelector('.threshold-value') as HTMLElement;\n\n    thresholdSlider?.addEventListener('input', function (this: HTMLInputElement) {\n      if (thresholdValue) {\n        thresholdValue.textContent = parseFloat(this.value).toFixed(1);\n      }\n    });\n\n    // Top K input validation\n    const topKInput = topKSection.querySelector('#topKInput') as HTMLInputElement;\n    topKInput?.addEventListener('input', function (this: HTMLInputElement) {\n      const value = parseInt(this.value, 10);\n      if (value < 1) {\n        this.value = String(1);\n      } else if (value > 50) {\n        this.value = String(50);\n      }\n    });\n\n    // Save button\n    const saveBtn = saveSection.querySelector('#saveSettingsBtn') as HTMLButtonElement;\n    const saveText = saveSection.querySelector('.save-text') as HTMLElement;\n    const saveLoader = saveSection.querySelector('.save-loader') as HTMLElement;\n    const saveMessage = saveSection.querySelector('#saveMessage') as HTMLElement;\n\n    saveBtn?.addEventListener('click', function () {\n      if (!saveBtn || !saveText || !saveLoader || !saveMessage) {\n        return;\n      }\n      saveSettings(\n        saveBtn,\n        saveText,\n        saveLoader,\n        saveMessage,\n        userIdSection,\n        orgSection,\n        projectSection,\n        memoryToggleSection,\n        autoInjectSection,\n        thresholdSection,\n        topKSection,\n        trackSearchSection\n      );\n    });\n  }\n\n  function fetchOrganizations(): void {\n    chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], function (data) {\n      if (data.apiKey || data.access_token) {\n        const headers = getHeaders(data.apiKey, data.access_token);\n        fetch('https://api.mem0.ai/api/v1/orgs/organizations/', {\n          method: 'GET',\n          headers: headers,\n        })\n          .then(response => response.json())\n          .then((orgs: Organization[]) => {\n            const orgSelect = document.getElementById('orgSelect') as HTMLSelectElement;\n            if (orgSelect) {\n              orgSelect.innerHTML = '<option value=\"\">Select an organization</option>';\n            }\n\n            orgs.forEach(org => {\n              const option = document.createElement('option');\n              option.value = org.org_id;\n              option.textContent = org.name;\n              orgSelect?.appendChild(option);\n            });\n\n            // Load saved org selection or select first org by default\n            chrome.storage.sync.get([StorageKey.SELECTED_ORG], function (result) {\n              if (result.selected_org) {\n                if (orgSelect) {\n                  orgSelect.value = String(result.selected_org ?? '');\n                }\n                const projectSelectEl = document.getElementById(\n                  'projectSelect'\n                ) as HTMLSelectElement;\n                const orgIdStr =\n                  typeof result.selected_org === 'string'\n                    ? result.selected_org\n                    : String(result.selected_org || '');\n                fetchProjects(orgIdStr, projectSelectEl);\n              } else if (orgs.length > 0) {\n                // Select first org by default (but don't save until user clicks save)\n                const firstOrg = orgs[0];\n                if (orgSelect) {\n                  orgSelect.value = String(firstOrg?.org_id ?? '');\n                }\n                const projectSelectEl = document.getElementById(\n                  'projectSelect'\n                ) as HTMLSelectElement;\n                fetchProjects(String(firstOrg?.org_id ?? ''), projectSelectEl);\n              }\n            });\n          })\n          .catch(error => {\n            console.error('Error fetching organizations:', error);\n            const orgSelect = document.getElementById('orgSelect') as HTMLSelectElement;\n            if (orgSelect) {\n              orgSelect.innerHTML = '<option value=\"\">Error loading organizations</option>';\n            }\n          });\n      }\n    });\n  }\n\n  function fetchProjects(orgId: string, projectSelect: HTMLSelectElement): void {\n    chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], function (data) {\n      if (data.apiKey || data.access_token) {\n        const headers = getHeaders(data.apiKey, data.access_token);\n        fetch(`https://api.mem0.ai/api/v1/orgs/organizations/${orgId}/projects/`, {\n          method: 'GET',\n          headers: headers,\n        })\n          .then(response => response.json())\n          .then((projects: Project[]) => {\n            if (!projectSelect) {\n              return;\n            }\n            projectSelect.innerHTML = '<option value=\"\">Select a project</option>';\n\n            projects.forEach(project => {\n              const option = document.createElement('option');\n              option.value = project.project_id;\n              option.textContent = project.name;\n              projectSelect.appendChild(option);\n            });\n\n            // Load saved project selection or select first project by default\n            chrome.storage.sync.get([StorageKey.SELECTED_PROJECT], function (result) {\n              if (!projectSelect) {\n                return;\n              }\n              if (result.selected_project) {\n                projectSelect.value = String(result.selected_project ?? '');\n              } else if (projects.length > 0) {\n                // Select first project by default (but don't save until user clicks save)\n                projectSelect.value = String(projects[0]?.project_id ?? '');\n              }\n            });\n          })\n          .catch(error => {\n            console.error('Error fetching projects:', error);\n            if (projectSelect) {\n              projectSelect.innerHTML = '<option value=\"\">Error loading projects</option>';\n            }\n          });\n      }\n    });\n  }\n\n  function fetchMemoriesAndCount(): void {\n    chrome.storage.sync.get(\n      [\n        StorageKey.API_KEY,\n        StorageKey.ACCESS_TOKEN,\n        StorageKey.USER_ID,\n        StorageKey.SELECTED_ORG,\n        StorageKey.SELECTED_PROJECT,\n      ],\n      function (data) {\n        if (data.apiKey || data.access_token) {\n          const headers = getHeaders(data.apiKey, data.access_token);\n\n          // Build query parameters\n          const params = new URLSearchParams();\n          const userId = data.user_id || DEFAULT_USER_ID;\n          params.append('user_id', userId);\n          params.append('page', '1');\n          params.append('page_size', '20');\n\n          if (data.selected_org) {\n            params.append('org_id', data.selected_org);\n          }\n\n          if (data.selected_project) {\n            params.append('project_id', data.selected_project);\n          }\n\n          fetch(`https://api.mem0.ai/v1/memories/?${params.toString()}`, {\n            method: 'GET',\n            headers: headers,\n          })\n            .then(response => response.json())\n            .then((data: MemoriesResponse) => {\n              // Update count and display memories\n              updateMemoryCount(data.count || 0);\n              displayMemories(data.results || []);\n            })\n            .catch(error => {\n              console.error('Error fetching memories:', error);\n              updateMemoryCount('Error');\n              displayErrorMessage();\n            });\n        } else {\n          updateMemoryCount('Login required');\n          displayErrorMessage('Login required to view memories');\n        }\n      }\n    );\n  }\n\n  function updateMemoryCount(count: number | string): void {\n    const countDisplay = document.querySelector('.memory-count') as HTMLElement;\n    if (countDisplay) {\n      countDisplay.classList.remove('loading');\n      countDisplay.textContent =\n        typeof count === 'number' ? new Intl.NumberFormat().format(count) + ' Memories' : count;\n    }\n  }\n\n  function getHeaders(apiKey?: string, accessToken?: string): Record<string, string> {\n    const headers: Record<string, string> = {\n      'Content-Type': 'application/json',\n    };\n    if (apiKey) {\n      headers['Authorization'] = `Token ${apiKey}`;\n    } else if (accessToken) {\n      headers['Authorization'] = `Bearer ${accessToken}`;\n    }\n    return headers;\n  }\n\n  function closeSearchInput(): void {\n    const inputContainer = document.querySelector('.input-container') as HTMLElement;\n    const existingSearchInput = inputContainer?.querySelector('.search-memory');\n    const searchBtn = document.getElementById('searchBtn') as HTMLElement;\n\n    if (existingSearchInput) {\n      existingSearchInput.remove();\n      searchBtn?.classList.remove('active');\n      // Remove filter when search is closed\n      filterMemories('');\n    }\n  }\n\n  function filterMemories(searchTerm: string): void {\n    const memoryItems = document.querySelectorAll<HTMLElement>('.memory-item');\n\n    memoryItems.forEach(item => {\n      const memoryText = item.querySelector('.memory-text')?.textContent?.toLowerCase() || '';\n      if (memoryText.includes(searchTerm)) {\n        item.style.display = 'flex';\n      } else {\n        item.style.display = 'none';\n      }\n    });\n\n    // Add this line to maintain the width of the sidebar\n    const sb = document.getElementById('mem0-sidebar') as HTMLElement;\n    if (sb) {\n      sb.style.width = '400px';\n    }\n  }\n\n  function addStyles() {\n    const style = document.createElement('style');\n    style.textContent = `\n        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');\n        \n        :root {\n          --bg-dark: #18181b;\n          --bg-card: #27272a;\n          --bg-button: #3b3b3f;\n          --bg-button-hover: #4b4b4f;\n          --text-white: #ffffff;\n          --text-gray: #a1a1aa;\n          --purple: #7a5bf7;\n          --border-color: #27272a;\n          --tag-bg: #3b3b3f;\n          --scrollbar-bg: #18181b;\n          --scrollbar-thumb: #3b3b3f;\n          --success-color: #22c55e;\n        }\n        \n        #mem0-sidebar {\n          font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n          position: fixed; \n          top: 60px;\n          right: 50px;\n          width: 400px;\n          height: auto;\n          max-height: 85vh;\n          background: var(--bg-dark);\n          border: 1px solid var(--border-color);\n          border-radius: 12px;\n          box-sizing: border-box;\n          display: flex;\n          flex-direction: column;\n          padding: 0px;\n          color: var(--text-white);\n          z-index: 2147483647;\n          transition: right 0.3s ease-in-out;\n          overflow: hidden;\n          box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);\n        }\n        \n        .fixed-header {\n          box-sizing: border-box;\n          width: 100%;\n          background: var(--bg-dark);\n          border-bottom: 1px solid var(--border-color);\n        }\n        \n        .tabs-container {\n          padding: 0 16px;\n          background: var(--bg-dark);\n          border-bottom: 1px solid var(--border-color);\n        }\n        \n        .tabs {\n          display: flex;\n          gap: 0;\n        }\n        \n        .tab-button {\n          flex: 1;\n          padding: 12px 16px;\n          background: none;\n          border: none;\n          color: var(--text-gray);\n          font-size: 14px;\n          font-weight: 500;\n          cursor: pointer;\n          transition: color 0.2s ease;\n          position: relative;\n          border-bottom: 2px solid transparent;\n        }\n        \n        .tab-button.active {\n          color: var(--text-white);\n          border-bottom-color: var(--purple);\n        }\n        \n        .tab-button:hover {\n          color: var(--text-white);\n        }\n        \n        .tab-content {\n          display: none;\n          flex-direction: column;\n          gap: 24px;\n        }\n        \n        .tab-content.active {\n          display: flex;\n        }\n        \n        .header {\n          display: flex;\n          flex-direction: row;\n          justify-content: space-between;\n          align-items: center;\n          padding: 16px;\n          width: 100%;\n          height: 62px;\n        }\n        \n        .logo-container {\n          display: flex;\n          flex-direction: row;\n          align-items: center;\n          padding: 0px;\n          gap: 8px;\n          height: 24px;\n        }\n        \n        .openmemory-icon {\n          width: 24px;\n          height: 24px;\n        }\n        \n        .openmemory-logo {\n          height: 24px;\n          font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n          font-style: normal;\n          font-weight: 600;\n          font-size: 20px;\n          line-height: 24px;\n          letter-spacing: -0.03em;\n          color: var(--text-white);\n        }\n        \n        .header-buttons {\n          display: flex;\n          flex-direction: row;\n          align-items: center;\n          padding: 0px;\n          gap: 16px;\n          height: 30px;\n        }\n        \n        .close-button {\n          background: none;\n          border: none;\n          width: 24px;\n          height: 24px;\n          cursor: pointer;\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          color: var(--text-gray);\n          font-size: 20px;\n          transition: color 0.2s ease;\n        }\n        \n        .close-button:hover {\n          color: var(--text-white);\n        }\n        \n        /* Custom scrollbar styles */\n        .content {\n          padding: 16px;\n          display: flex;\n          flex-direction: column;\n          gap: 24px;\n          overflow-y: auto;\n          max-height: calc(85vh - 62px - 49px - 60px); /* Subtract header, tab bar, and footer heights */\n          \n          /* Firefox */\n          scrollbar-width: thin;\n          scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-bg);\n        }\n        \n        /* WebKit browsers (Chrome, Safari, Edge) */\n        .content::-webkit-scrollbar {\n          width: 4px;\n        }\n        \n        .content::-webkit-scrollbar-track {\n          background: var(--scrollbar-bg);\n        }\n        \n        .content::-webkit-scrollbar-thumb {\n          background-color: var(--scrollbar-thumb);\n          border-radius: 4px;\n          border: none;\n        }\n        \n        .total-memories {\n          background-color: var(--bg-card);\n          border-radius: 8px;\n          padding: 16px;\n        }\n        \n        .total-memories-content {\n          display: flex;\n          justify-content: space-between;\n          align-items: center;\n        }\n        \n        .total-memories-label {\n          color: var(--text-gray);\n          font-size: 14px;\n          margin-bottom: 4px;\n        }\n        \n        .memory-count {\n          font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n          font-style: normal;\n          font-weight: 500;\n          font-size: 18px;\n          line-height: 140%;\n          letter-spacing: -0.03em;\n          color: var(--text-white);\n        }\n        \n        .memory-count.loading {\n          color: var(--text-gray);\n          font-size: 16px;\n        }\n        \n        .dashboard-button {\n          display: flex;\n          flex-direction: row;\n          align-items: center;\n          padding: 4px 8px;\n          gap: 4px;\n          background: var(--bg-button);\n          background-opacity: 0.5;\n          border-radius: 8px;\n          border: none;\n          cursor: pointer;\n          transition: all 0.2s ease;\n          color: var(--text-white);\n          font-size: 14px;\n        }\n        \n        .dashboard-button:hover {\n          background: var(--bg-button-hover);\n        }\n        \n        .external-link-icon {\n          width: 14px;\n          height: 14px;\n        }\n        \n        .section {\n          display: flex;\n          flex-direction: column;\n          gap: 8px;\n        }\n        \n        .section-header {\n          display: flex;\n          justify-content: space-between;\n          align-items: center;\n          width: 100%;\n        }\n        \n        .section-title {\n          font-size: 18px;\n          font-weight: 500;\n          color: var(--text-white);\n        }\n        \n        .section-description {\n          font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n          font-style: normal;\n          font-weight: 400;\n          font-size: 14px;\n          line-height: 140%;\n          letter-spacing: -0.03em;\n          color: var(--text-gray);\n        }\n\n        .switch {\n          position: relative;\n          display: inline-block;\n          width: 44px;\n          height: 22px;\n        }\n        \n        .switch input {\n          opacity: 0;\n          width: 0;\n          height: 0;\n        }\n        \n        .slider {\n          position: absolute;\n          cursor: pointer;\n          top: 0;\n          left: 0;\n          right: 0;\n          bottom: 0;\n          background-color: var(--bg-card);\n          transition: .4s;\n          border-radius: 34px;\n        }\n        \n        .slider:before {\n          position: absolute;\n          content: \"\";\n          height: 18px;\n          width: 18px;\n          left: 3px;\n          bottom: 2px;\n          background-color: white;\n          transition: .4s;\n          border-radius: 50%;\n        }\n        \n        input:checked + .slider {\n          background-color: var(--purple);\n        }\n        \n        input:focus + .slider {\n          box-shadow: 0 0 1px var(--purple);\n        }\n        \n        input:checked + .slider:before {\n          transform: translateX(20px);\n        }\n        \n        .memory-cards {\n          display: flex;\n          flex-direction: column;\n          gap: 12px;\n        }\n        \n        .memory-card {\n          background-color: var(--bg-card);\n          border-radius: 8px;\n          padding: 12px;\n          display: flex;\n          justify-content: space-between;\n          align-items: flex-start;\n        }\n        \n        .memory-content {\n          flex: 1;\n          padding-right: 8px;\n        }\n        \n        .memory-text {\n          color: var(--text-gray);\n          font-size: 14px;\n          margin: 0 0 8px 0;\n        }\n        \n        .memory-categories {\n          display: flex;\n          flex-wrap: wrap;\n          gap: 4px;\n          margin-top: 4px;\n        }\n        \n        .memory-category {\n          background-color: var(--tag-bg);\n          color: var(--text-white);\n          font-size: 12px;\n          padding: 2px 8px;\n          border-radius: 4px;\n        }\n        \n        .memory-actions {\n          display: flex;\n          gap: 4px;\n          flex-shrink: 0;\n        }\n        \n        .memory-action-button {\n          background: none;\n          border: none;\n          color: var(--text-gray);\n          cursor: pointer;\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          transition: color 0.2s;\n          width: 24px;\n          height: 24px;\n          border-radius: 4px;\n        }\n        \n        .memory-action-button:hover {\n          color: var(--text-white);\n          background-color: var(--bg-button);\n        }\n        \n        .memory-action-button.copied {\n          color: var(--success-color);\n        }\n        \n        .memory-loader {\n          display: flex;\n          justify-content: center;\n          align-items: center;\n          padding: 20px 0;\n        }\n        \n        .no-memories, .memory-error {\n          color: var(--text-gray);\n          text-align: center;\n          font-style: italic;\n          padding: 20px 0;\n        }\n        \n        .footer {\n          display: flex;\n          justify-content: space-between;\n          align-items: center;\n          padding: 16px;\n          border-top: 1px solid var(--border-color);\n        }\n        \n        .shortcut {\n          padding: 6px 12px;\n          background-color: var(--bg-card);\n          color: var(--text-gray);\n          border-radius: 8px;\n          font-size: 14px;\n        }\n        \n        .logout-button {\n          display: flex;\n          flex-direction: row;\n          align-items: center;\n          padding: 6px 16px;\n          background: var(--bg-button);\n          border-radius: 8px;\n          border: none;\n          cursor: pointer;\n          transition: all 0.2s ease;\n          color: var(--text-white);\n          font-size: 14px;\n        }\n        \n        .logout-button:hover {\n          background: var(--bg-button-hover);\n        }\n        \n        .loader {\n          border: 2px solid var(--bg-button);\n          border-top: 2px solid var(--purple);\n          border-radius: 50%;\n          width: 20px;\n          height: 20px;\n          animation: spin 1s linear infinite;\n          margin: 0 auto;\n        }\n        \n        @keyframes spin {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n        \n        .input-container {\n          width: 100%;\n          padding: 0;\n          box-sizing: border-box;\n        }\n        \n        .search-memory {\n          width: 100%;\n          box-sizing: border-box;\n          margin-top: 16px;\n        }\n        \n        .settings-input, .settings-select {\n          width: 100%;\n          padding: 12px 16px;\n          background: var(--bg-card);\n          border: 1px solid var(--border-color);\n          border-radius: 8px;\n          color: var(--text-white);\n          font-size: 14px;\n          font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n          transition: border-color 0.2s ease, background-color 0.2s ease;\n          box-sizing: border-box;\n        }\n        \n        .settings-input:focus, .settings-select:focus {\n          outline: none;\n          border-color: var(--purple);\n          background: var(--bg-button);\n        }\n        \n        .settings-input::placeholder {\n          color: var(--text-gray);\n        }\n        \n        .settings-select option {\n          background: var(--bg-card);\n          color: var(--text-white);\n        }\n        \n        .settings-select:hover {\n          border-color: var(--text-gray);\n        }\n        \n        .link-button {\n          background: none;\n          border: none;\n          color: var(--text-gray);\n          cursor: pointer;\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          width: 24px;\n          height: 24px;\n          border-radius: 4px;\n          transition: all 0.2s ease;\n        }\n        \n        .link-button:hover {\n          color: var(--text-white);\n          background: var(--bg-button);\n        }\n        \n        .save-button {\n          width: 100%;\n          padding: 12px 24px;\n          background: var(--purple);\n          border: none;\n          border-radius: 8px;\n          color: var(--text-white);\n          font-size: 14px;\n          font-weight: 500;\n          cursor: pointer;\n          transition: background-color 0.2s ease;\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          gap: 8px;\n          font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n        }\n        \n        .save-button:hover:not(:disabled) {\n          background: #6d4ed6;\n        }\n        \n        .save-button:disabled {\n          background: var(--bg-button);\n          cursor: not-allowed;\n        }\n        \n        .save-loader {\n          display: none;\n          align-items: center;\n          justify-content: center;\n        }\n        \n        .mini-loader {\n          border: 2px solid var(--bg-button);\n          border-top: 2px solid var(--text-white);\n          border-radius: 50%;\n          width: 16px;\n          height: 16px;\n          animation: spin 1s linear infinite;\n        }\n        \n        .save-message {\n          margin-top: 8px;\n          padding: 8px 12px;\n          border-radius: 6px;\n          font-size: 13px;\n          text-align: center;\n        }\n        \n        .save-message.success {\n          background: rgba(34, 197, 94, 0.1);\n          color: var(--success-color);\n          border: 1px solid rgba(34, 197, 94, 0.3);\n        }\n        \n        .save-message.error {\n          background: rgba(239, 68, 68, 0.1);\n          color: #f87171;\n          border: 1px solid rgba(239, 68, 68, 0.3);\n        }\n        \n        .threshold-value {\n          color: var(--purple);\n          font-weight: 600;\n          font-size: 14px;\n        }\n        \n        .slider-container {\n          display: flex;\n          flex-direction: column;\n          gap: 8px;\n        }\n        \n        .threshold-slider {\n          width: 100%;\n          height: 6px;\n          background: var(--bg-card);\n          border-radius: 3px;\n          outline: none;\n          appearance: none;\n          -webkit-appearance: none;\n          cursor: pointer;\n        }\n        \n        .threshold-slider::-webkit-slider-thumb {\n          appearance: none;\n          width: 20px;\n          height: 20px;\n          border-radius: 50%;\n          background: var(--purple);\n          cursor: pointer;\n          border: 2px solid var(--bg-dark);\n          box-shadow: 0 0 0 1px var(--purple);\n        }\n        \n        .threshold-slider::-moz-range-thumb {\n          width: 20px;\n          height: 20px;\n          border-radius: 50%;\n          background: var(--purple);\n          cursor: pointer;\n          border: 2px solid var(--bg-dark);\n          box-shadow: 0 0 0 1px var(--purple);\n        }\n        \n        .threshold-slider::-webkit-slider-track {\n          width: 100%;\n          height: 6px;\n          background: var(--bg-card);\n          border-radius: 3px;\n        }\n        \n        .threshold-slider::-moz-range-track {\n          width: 100%;\n          height: 6px;\n          background: var(--bg-card);\n          border-radius: 3px;\n          border: none;\n        }\n        \n        .threshold-slider:focus {\n          outline: none;\n        }\n        \n        .threshold-slider:focus::-webkit-slider-thumb {\n          box-shadow: 0 0 0 2px var(--purple);\n        }\n        \n        .threshold-slider:focus::-moz-range-thumb {\n          box-shadow: 0 0 0 2px var(--purple);\n        }\n        \n        .slider-labels {\n          display: flex;\n          justify-content: space-between;\n          font-size: 12px;\n          color: var(--text-gray);\n          margin-top: 4px;\n        }\n        \n        .settings-input[type=\"number\"] {\n          -moz-appearance: textfield;\n        }\n        \n        .settings-input[type=\"number\"]::-webkit-outer-spin-button,\n        .settings-input[type=\"number\"]::-webkit-inner-spin-button {\n          -webkit-appearance: none;\n          margin: 0;\n        }\n    `;\n    document.head.appendChild(style);\n  }\n\n  function logout() {\n    chrome.storage.sync.get([StorageKey.API_KEY, StorageKey.ACCESS_TOKEN], function (data) {\n      const headers = getHeaders(data.apiKey, data.access_token);\n      fetch('https://api.mem0.ai/v1/extension/', {\n        method: 'POST',\n        headers: headers,\n        body: JSON.stringify({\n          event_type: 'extension_logout',\n        }),\n      }).catch(error => {\n        console.error('Error sending logout event:', error);\n      });\n    });\n    chrome.storage.sync.remove(\n      [StorageKey.API_KEY, StorageKey.USER_ID_CAMEL, StorageKey.ACCESS_TOKEN],\n      function () {\n        const sidebar = document.getElementById('mem0-sidebar');\n        if (sidebar) {\n          sidebar.style.right = '-500px';\n        }\n      }\n    );\n  }\n\n  function openDashboard() {\n    chrome.storage.sync.get([StorageKey.USER_ID], function () {\n      chrome.runtime.sendMessage({\n        action: SidebarAction.OPEN_DASHBOARD,\n        url: `https://app.mem0.ai/dashboard/requests`,\n      });\n    });\n  }\n\n  // Add function to display memories\n  function displayMemories(memories: Memory[]): void {\n    const memoryCardsContainer = document.querySelector('.memory-cards') as HTMLElement;\n\n    if (!memoryCardsContainer) {\n      return;\n    }\n\n    // Clear loading indicator\n    memoryCardsContainer.innerHTML = '';\n\n    if (!memories || memories.length === 0) {\n      memoryCardsContainer.innerHTML = '<p class=\"no-memories\">No memories found</p>';\n      return;\n    }\n\n    // Add memory cards\n    memories.forEach(memory => {\n      // Extract memory content from the new format\n      const memoryContent = memory.memory || '';\n\n      // Truncate long text\n      const truncatedContent =\n        memoryContent.length > 120 ? memoryContent.substring(0, 120) + '...' : memoryContent;\n\n      // Get categories if available\n      const categories = memory.categories || [];\n      const categoryTags =\n        categories.length > 0\n          ? `<div class=\"memory-categories\">${categories.map(cat => `<span class=\"memory-category\">${cat}</span>`).join('')}</div>`\n          : '';\n\n      const memoryCard = document.createElement('div');\n      memoryCard.className = 'memory-card';\n      memoryCard.innerHTML = `\n        <div class=\"memory-content\">\n          <p class=\"memory-text\">${truncatedContent}</p>\n          ${categoryTags}\n        </div>\n        <div class=\"memory-actions\">\n          <button class=\"memory-action-button copy-button\" title=\"Copy Memory\" data-content=\"${encodeURIComponent(memoryContent)}\">\n            <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-copy\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg>\n          </button>\n          <button class=\"memory-action-button view-button\" title=\"View Memory\" data-id=\"${memory.id || ''}\">\n            <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-eye\"><path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n          </button>\n        </div>\n      `;\n\n      memoryCardsContainer.appendChild(memoryCard);\n    });\n\n    // Add event listener for the copy button\n    document.querySelectorAll<HTMLButtonElement>('.copy-button').forEach(button => {\n      button.addEventListener('click', function (this: HTMLButtonElement, e) {\n        e.stopPropagation();\n        const content = decodeURIComponent(this.getAttribute('data-content') || '');\n\n        // Copy to clipboard\n        navigator.clipboard\n          .writeText(content)\n          .then(() => {\n            // Visual feedback for copy\n            const originalTitle = this.getAttribute('title') || '';\n            this.setAttribute('title', 'Copied!');\n            this.classList.add('copied');\n\n            // Reset after a short delay\n            setTimeout(() => {\n              this.setAttribute('title', originalTitle);\n              this.classList.remove('copied');\n            }, 2000);\n          })\n          .catch(err => {\n            console.error('Failed to copy: ', err);\n          });\n      });\n    });\n\n    // Add event listener for the view button\n    document.querySelectorAll<HTMLButtonElement>('.view-button').forEach(button => {\n      button.addEventListener('click', function (this: HTMLButtonElement, e) {\n        e.stopPropagation();\n        const memoryId = this.getAttribute('data-id');\n        if (memoryId) {\n          chrome.storage.sync.get([StorageKey.USER_ID], function (data) {\n            const userId = data.user_id || 'chrome-extension-user';\n            chrome.runtime.sendMessage({\n              action: SidebarAction.OPEN_DASHBOARD,\n              url: `https://app.mem0.ai/dashboard/user/${userId}?memoryId=${memoryId}`,\n            });\n          });\n        }\n      });\n    });\n  }\n\n  // Add function to display error message\n  function displayErrorMessage(message = 'Error loading memories') {\n    const memoryCardsContainer = document.querySelector('.memory-cards');\n\n    if (!memoryCardsContainer) {\n      return;\n    }\n\n    memoryCardsContainer.innerHTML = `<p class=\"memory-error\">${message}</p>`;\n  }\n\n  // Initialize the listener when the script loads\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', initializeMem0Sidebar);\n  } else {\n    initializeMem0Sidebar();\n  }\n})();\n"
  },
  {
    "path": "src/types/api.ts",
    "content": "/** message roles (User, Assistant) */\nexport enum MessageRole {\n  User = 'user',\n  Assistant = 'assistant',\n}\n\n/** Message structure with role and content */\nexport type ApiMessage = {\n  role: string;\n  content: string;\n};\n\n/** Request payload for memory API calls */\nexport type ApiMemoryRequest = {\n  messages: ApiMessage[];\n  user_id: string;\n  metadata: {\n    provider: string;\n    category: string;\n    page_url?: string;\n    engine?: string;\n  };\n  source: string;\n  org_id?: string;\n  project_id?: string;\n};\n\n/** Array of memory search results */\nexport type MemorySearchResponse = Array<{\n  id: string;\n  memory: string;\n  text?: string;\n  created_at?: string;\n  user_id?: string;\n  categories?: string[];\n}>;\n\n/** User authentication data structure */\nexport type LoginData = Partial<{\n  apiKey: string;\n  userId: string;\n  user_id: string;\n  access_token: string;\n}>;\n\n/** Default user ID constant */\nexport const DEFAULT_USER_ID = 'chrome-extension-user';\n\n/** Extension source identifier */\nexport const SOURCE = 'OPENMEMORY_CHROME_EXTENSION';\n"
  },
  {
    "path": "src/types/browser.ts",
    "content": "/** Web navigation event details */\nexport type OnCommittedDetails = {\n  tabId: number;\n  url: string;\n  processId: number;\n  frameId: number;\n  parentFrameId: number;\n  transitionType: chrome.webNavigation.TransitionType;\n  transitionQualifiers: chrome.webNavigation.TransitionQualifier[];\n  timeStamp: number;\n  documentId: string;\n  parentDocumentId?: string;\n  documentLifecycle: chrome.extensionTypes.DocumentLifecycle;\n  frameType: chrome.extensionTypes.FrameType;\n};\n\n/** JSON primitive value types */\nexport type JsonPrimitive = string | number | boolean | null;\n/** Recursive JSON value type */\nexport type JsonValue = JsonPrimitive | JsonValue[] | { [k: string]: JsonValue };\n/** JSON object structure */\nexport type JsonObject = { [k: string]: JsonValue };\n\n/** Browser history state data */\nexport type HistoryStateData = JsonObject | null;\n/** Browser history URL type */\nexport type HistoryUrl = string | URL | null;\n"
  },
  {
    "path": "src/types/dom.ts",
    "content": "// Global interfaces for DOM extensions\ndeclare global {\n  interface Element {\n    value?: string;\n    disabled?: boolean;\n    dataset: DOMStringMap;\n    style: CSSStyleDeclaration;\n  }\n  interface CSSStyleDeclaration {\n    msOverflowStyle?: string;\n  }\n  interface Window {\n    mem0Initialized?: boolean;\n    mem0KeyboardListenersAdded?: boolean;\n    mem0ButtonAdded?: boolean;\n  }\n}\n\n/** Extended HTML element with cleanup methods */\nexport type ExtendedHTMLElement = HTMLElement & {\n  _cleanupDragEvents?: () => void;\n};\n\n/** Extended document with mem0-specific properties */\nexport type ExtendedDocument = Document & {\n  __mem0FocusPrimed?: boolean;\n  __mem0EnterCapture?: boolean;\n  __mem0SubmitCapture?: boolean;\n};\n\n/** Extended element with additional properties */\nexport type ExtendedElement = Element & {\n  __mem0Observed?: boolean;\n  nodeType?: number;\n  matches?: (selector: string) => boolean;\n  querySelector?: (selector: string) => Element | null;\n  classList?: DOMTokenList;\n};\n\n/** Modal size and pagination settings */\nexport type ModalDimensions = {\n  width: number;\n  height: number;\n  memoriesPerPage: number;\n};\n\n/** Modal positioning coordinates */\nexport type ModalPosition = {\n  top: number | null;\n  left: number | null;\n};\n\n/** Extended mutation observer with timers */\nexport type MutableMutationObserver = MutationObserver & {\n  memoryStateInterval?: number;\n  debounceTimer?: number;\n};\n"
  },
  {
    "path": "src/types/memory.ts",
    "content": "/** Individual memory structure with id, text, and categories */\nexport type MemoryItem = {\n  id?: string;\n  text: string;\n  memory?: string;\n  categories?: string[];\n  removed?: boolean;\n  created_at?: string;\n  user_id?: string;\n};\n\n/** Simplified memory structure */\nexport type Memory = Partial<{\n  id: string;\n  memory: string;\n  categories: string[];\n}>;\n\n/** Search result item from API */\nexport type MemorySearchItem = { id: string | number; memory: string; categories?: string[] };\n\n/** API response wrapper for memories */\nexport type MemoriesResponse = Partial<{\n  count: number;\n  results: Memory[];\n}>;\n\n/** Prompt templates and regex patterns */\nexport type OpenMemoryPrompts = {\n  memory_header_html_strong: string;\n  memory_header_plain_regex: RegExp;\n  memory_header_html_regex: RegExp;\n};\n\n/** Optional parameters for API calls (org_id, project_id) */\nexport type OptionalApiParams = Partial<{\n  org_id: string;\n  project_id: string;\n}>;\n"
  },
  {
    "path": "src/types/messages.ts",
    "content": "/** Enum for toast notification types (SUCCESS, ERROR) */\nexport enum ToastVariant {\n  SUCCESS = 'success',\n  ERROR = 'error',\n}\n\n/** Enum for different message types */\nexport enum MessageType {\n  GET_SELECTION_CONTEXT = 'mem0:getSelectionContext',\n  SELECTION_CONTEXT = 'mem0:selectionContext',\n  TOAST = 'mem0:toast',\n}\n\n/** Enum for sidebar actions (TOGGLE_SIDEBAR, OPEN_POPUP, etc.) */\nexport enum SidebarAction {\n  TOGGLE_SIDEBAR = 'toggleSidebar',\n  OPEN_POPUP = 'openPopup',\n  TOGGLE_MEM0 = 'toggleMem0',\n  OPEN_DASHBOARD = 'openDashboard',\n  SIDEBAR_SETTINGS = 'toggleSidebarSettings',\n  OPEN_OPTIONS = 'openOptions',\n  SHOW_LOGIN_POPUP = 'showLoginPopup',\n}\n\n/** Payload for selection context messages */\nexport type SelectionContextPayload = Partial<{\n  selection: string;\n  title: string;\n  url: string;\n}>;\n\n/** Response structure for selection context */\nexport type SelectionContextResponse = Partial<{\n  type: string;\n  payload: SelectionContextPayload;\n  error: string;\n}>;\n\n/** Message type for getting selection context */\nexport type GetSelectionContextMessage = {\n  type: MessageType.GET_SELECTION_CONTEXT;\n};\n\n/** Toast notification message structure */\nexport type ToastMessage = {\n  type: MessageType.TOAST;\n  payload: {\n    message?: string;\n    variant?: ToastVariant;\n  };\n};\n\n/** Union type for selection context messages */\nexport type SelectionContextMessage = GetSelectionContextMessage | ToastMessage;\n\n/** Response callback type */\nexport type SendResponse = (response: SelectionContextResponse) => void;\n\nexport type ToggleSidebarMessage = {\n  action: SidebarAction.TOGGLE_SIDEBAR;\n};\n\nexport type OpenPopupMessage = {\n  action: SidebarAction.OPEN_POPUP;\n};\n\nexport type ToggleMem0Message = {\n  action: SidebarAction.TOGGLE_MEM0;\n  enabled: boolean;\n};\n\nexport type OpenDashboardMessage = {\n  action: SidebarAction.OPEN_DASHBOARD;\n  url: string;\n};\n\nexport type SidebarActionMessage =\n  | ToggleSidebarMessage\n  | OpenPopupMessage\n  | ToggleMem0Message\n  | OpenDashboardMessage;\n"
  },
  {
    "path": "src/types/organizations.ts",
    "content": "/** Organization structure with org_id and name */\nexport type Organization = {\n  org_id: string;\n  name: string;\n};\n\n/** Project structure with project_id and name */\nexport type Project = {\n  project_id: string;\n  name: string;\n};\n"
  },
  {
    "path": "src/types/providers.ts",
    "content": "/** Enum for supported AI providers */\nexport enum Provider {\n  ContextMenu = 'ContextMenu',\n  DirectURL = 'DirectURL',\n  SearchTracker = 'SearchTracker',\n  Grok = 'Grok',\n  Claude = 'Claude',\n  ChatGPT = 'ChatGPT',\n  Gemini = 'Gemini',\n  Perplexity = 'Perplexity',\n  DeepSeek = 'DeepSeek',\n  Replit = 'Replit',\n}\n\n/** Enum for memory categories (BOOKMARK, NAVIGATION, SEARCH) */\nexport enum Category {\n  BOOKMARK = 'BOOKMARK',\n  NAVIGATION = 'NAVIGATION',\n  SEARCH = 'SEARCH',\n}\n"
  },
  {
    "path": "src/types/settings.ts",
    "content": "/** User preference structure with API keys, memory settings, and thresholds */\nexport type UserSettings = Partial<{\n  apiKey: string;\n  accessToken: string;\n  userId: string;\n  memoryEnabled: boolean;\n  selectedOrg: string;\n  selectedProject: string;\n  similarityThreshold: number;\n  topK: number;\n}>;\n\n/** Sidebar-specific settings with organization and project info */\nexport type SidebarSettings = {\n  user_id?: string;\n  selected_org?: string;\n  selected_org_name?: string;\n  selected_project?: string;\n  selected_project_name?: string;\n  memory_enabled: boolean;\n  auto_inject_enabled: boolean;\n  similarity_threshold: number;\n  top_k: number;\n  track_searches: boolean;\n};\n\n/** Legacy settings structure for compatibility */\nexport type Settings = {\n  hasCreds: boolean;\n  apiKey: string | null;\n  accessToken: string | null;\n  userId: string;\n  orgId: string | null;\n  projectId: string | null;\n  memoryEnabled: boolean;\n};\n"
  },
  {
    "path": "src/types/storage.ts",
    "content": "/** Enum for all storage keys used in the extension */\nexport enum StorageKey {\n  API_KEY = 'apiKey',\n  ACCESS_TOKEN = 'access_token',\n  USER_ID = 'user_id',\n  USER_ID_CAMEL = 'userId',\n  USER_LOGGED_IN = 'userLoggedIn',\n  SELECTED_ORG = 'selected_org',\n  SELECTED_PROJECT = 'selected_project',\n  MEMORY_ENABLED = 'memory_enabled',\n  AUTO_INJECT_ENABLED = 'auto_inject_enabled',\n  SIMILARITY_THRESHOLD = 'similarity_threshold',\n  TOP_K = 'top_k',\n  TRACK_SEARCHES = 'track_searches',\n}\n\n/** Type mapping for storage values (required fields) */\nexport type StorageItems = {\n  apiKey: string;\n  userId: string;\n  user_id: string;\n  access_token: string;\n  memory_enabled: boolean;\n  selected_org: string;\n  selected_project: string;\n  similarity_threshold: number;\n  top_k: number;\n};\n\n/** Type mapping for storage values (optional fields) */\nexport type StorageData = Partial<{\n  apiKey: string;\n  userId: string;\n  user_id: string;\n  access_token: string;\n  memory_enabled: boolean;\n  selected_org: string;\n  selected_project: string;\n  similarity_threshold: number;\n  top_k: number;\n}>;\n"
  },
  {
    "path": "src/utils/background_search.ts",
    "content": "import type { MemorySearchItem } from '../types/memory';\nimport type { StorageKey } from '../types/storage';\n\nexport type SearchStorage = Partial<{\n  [StorageKey.API_KEY]: string;\n  [StorageKey.USER_ID_CAMEL]: string;\n  [StorageKey.ACCESS_TOKEN]: string;\n  [StorageKey.SELECTED_ORG]: string;\n  [StorageKey.SELECTED_PROJECT]: string;\n  [StorageKey.USER_ID]: string;\n  [StorageKey.SIMILARITY_THRESHOLD]: number;\n  [StorageKey.TOP_K]: number;\n}>;\n\nexport type FetchFn<T> = (query: string, opts: { signal?: AbortSignal }) => Promise<T> | T;\n\nexport interface OrchestratorOptions {\n  fetch: FetchFn<MemorySearchItem[]>;\n  onStart?: (normalizedQuery: string) => void;\n  onSuccess?: (\n    normalizedQuery: string,\n    result: MemorySearchItem[],\n    meta: { fromCache: boolean }\n  ) => void;\n  onError?: (normalizedQuery: string, err: Error) => void;\n  onFinally?: (normalizedQuery: string) => void;\n  minLength?: number;\n  debounceMs?: number;\n  cacheTTL?: number;\n  useCache?: boolean;\n  refreshOnCache?: boolean;\n}\n\nexport interface OrchestratorState {\n  latestText: string;\n  lastCompletedQuery: string;\n  lastResult: MemorySearchItem[] | null;\n  inFlightQuery: string | null;\n  isInFlight: boolean;\n  cacheSize: number;\n}\n\nexport interface Orchestrator {\n  setText(text?: string): void;\n  runImmediate(text?: string | null): void;\n  cancel(): void;\n  getState(): OrchestratorState;\n  setOptions(\n    opts: Partial<\n      Pick<\n        OrchestratorOptions,\n        'minLength' | 'debounceMs' | 'cacheTTL' | 'useCache' | 'refreshOnCache'\n      >\n    >\n  ): void;\n  clearCache(): void;\n}\n\nexport function normalizeQuery(s?: string | number | boolean): string {\n  if (!s) {\n    return '';\n  }\n  return String(s).trim().replace(/\\s+/g, ' ').toLowerCase();\n}\n\nexport function createOrchestrator(options: OrchestratorOptions): Orchestrator {\n  const fetchFn = options?.fetch;\n  if (typeof fetchFn !== 'function') {\n    throw new Error('createOrchestrator requires options.fetch(query, { signal })');\n  }\n\n  const NOOP = (): void => undefined;\n\n  const onStart = options.onStart ?? NOOP;\n  const onSuccess = options.onSuccess ?? NOOP;\n  const onError = options.onError ?? NOOP;\n  const onFinally = options.onFinally ?? NOOP;\n\n  let minLength = typeof options.minLength === 'number' ? options.minLength : 3;\n  let debounceMs = typeof options.debounceMs === 'number' ? options.debounceMs : 75;\n  let cacheTTL = typeof options.cacheTTL === 'number' ? options.cacheTTL : 60_000;\n  let useCache = options.useCache !== false;\n  let refreshOnCache = !!options.refreshOnCache;\n\n  let latestText = '';\n  let lastCompletedQuery = '';\n  let lastResult: MemorySearchItem[] | null = null;\n\n  let inFlightQuery: string | null = null;\n  let abortController: AbortController | null = null;\n\n  let timerId: ReturnType<typeof setTimeout> | null = null;\n  let seq = 0;\n\n  const cache = new Map<string, { ts: number; result: MemorySearchItem[] }>();\n\n  function getState(): OrchestratorState {\n    return {\n      latestText,\n      lastCompletedQuery,\n      lastResult,\n      inFlightQuery,\n      isInFlight: !!inFlightQuery,\n      cacheSize: cache.size,\n    };\n  }\n\n  function setOptions(\n    newOpts: Partial<\n      Pick<\n        OrchestratorOptions,\n        'minLength' | 'debounceMs' | 'cacheTTL' | 'useCache' | 'refreshOnCache'\n      >\n    >\n  ) {\n    if (!newOpts) {\n      return;\n    }\n    if (typeof newOpts.minLength === 'number') {\n      minLength = newOpts.minLength;\n    }\n    if (typeof newOpts.debounceMs === 'number') {\n      debounceMs = newOpts.debounceMs;\n    }\n    if (typeof newOpts.cacheTTL === 'number') {\n      cacheTTL = newOpts.cacheTTL;\n    }\n    if (typeof newOpts.useCache === 'boolean') {\n      useCache = newOpts.useCache;\n    }\n    if (typeof newOpts.refreshOnCache === 'boolean') {\n      refreshOnCache = newOpts.refreshOnCache;\n    }\n  }\n\n  function clearTimer() {\n    if (timerId) {\n      clearTimeout(timerId);\n      timerId = null;\n    }\n  }\n\n  function clearCache() {\n    cache.clear();\n  }\n\n  function getCached(normQuery: string): MemorySearchItem[] | null {\n    if (!useCache) {\n      return null;\n    }\n    const v = cache.get(normQuery);\n    if (!v) {\n      return null;\n    }\n    if (Date.now() - v.ts > cacheTTL) {\n      cache.delete(normQuery);\n      return null;\n    }\n    return v.result;\n  }\n\n  function setCached(normQuery: string, result: MemorySearchItem[]) {\n    cache.set(normQuery, { ts: Date.now(), result });\n  }\n\n  function cancel() {\n    clearTimer();\n    if (abortController) {\n      try {\n        abortController.abort();\n      } catch {\n        /* ignore abort errors */\n      }\n    }\n    inFlightQuery = null;\n    abortController = null;\n  }\n\n  function run(query?: string | null) {\n    const raw = query !== null && query !== undefined ? String(query) : latestText;\n    const norm = normalizeQuery(raw);\n    if (!norm || norm.length < minLength) {\n      return;\n    }\n\n    const cached = getCached(norm);\n    if (cached !== null) {\n      onSuccess(norm, cached, { fromCache: true });\n      if (!refreshOnCache) {\n        return;\n      }\n    }\n\n    if (inFlightQuery && inFlightQuery === norm) {\n      return;\n    }\n    if (inFlightQuery && inFlightQuery !== norm && abortController) {\n      try {\n        abortController.abort();\n      } catch {\n        /* ignore abort errors */\n      }\n    }\n\n    inFlightQuery = norm;\n    abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;\n    const mySeq = ++seq;\n\n    onStart(norm);\n\n    Promise.resolve()\n      .then(() => fetchFn(norm, { signal: abortController ? abortController.signal : undefined }))\n      .then(result => {\n        if (inFlightQuery !== norm || mySeq !== seq) {\n          return;\n        }\n        setCached(norm, result as MemorySearchItem[]);\n        lastCompletedQuery = norm;\n        lastResult = result as MemorySearchItem[];\n        onSuccess(norm, result as MemorySearchItem[], { fromCache: false });\n      })\n      .catch((err: Error) => {\n        const aborted = abortController?.signal.aborted === true || err.name === 'AbortError';\n        if (mySeq !== seq) {\n          return;\n        }\n        if (!aborted) {\n          onError(norm, err);\n        }\n      })\n      .finally(() => {\n        if (mySeq !== seq) {\n          return;\n        }\n        inFlightQuery = null;\n        abortController = null;\n        onFinally(norm);\n      });\n  }\n\n  function schedule() {\n    clearTimer();\n    if (!latestText || normalizeQuery(latestText).length < minLength) {\n      return;\n    }\n    timerId = setTimeout(() => {\n      timerId = null;\n      run(latestText);\n    }, debounceMs);\n  }\n\n  return {\n    setText(text: string | null | undefined) {\n      latestText = text === null || text === undefined ? '' : String(text);\n      schedule();\n    },\n    runImmediate(text?: string | null) {\n      if (text !== null && text !== undefined) {\n        latestText = String(text);\n      }\n      clearTimer();\n      run(latestText);\n    },\n    cancel,\n    getState,\n    setOptions,\n    clearCache,\n  };\n}\n"
  },
  {
    "path": "src/utils/llm_prompts.ts",
    "content": "export const OPENMEMORY_PROMPTS = {\n  rerank_system_prompt: `\nYou are OpenMemory Filterer.\n\nYour tasks:\n1) From the provided candidate memories, select ONLY those that materially help answer the user query.\n2) Never invent or paraphrase memories; pick from provided candidates only.\n3) If none are relevant, return an empty list. DO NOT BE AFRAID TO EXCLUDE MEMORIES.\n\nSelection rules:\n- Prioritize constraints (medical, safety, legal), then strong stable preferences (likes/dislikes, dietary rules), then recent contextual facts.\n- Prefer specific over generic; constraints over trivia; use recency as a tiebreaker.\n- Do NOT select a memory that merely restates the user's present intent (e.g., \"wants to eat a dessert\"). Select enduring preferences instead (e.g., \"likes desserts\").\n\nOutput JSON ONLY, with exactly these keys:\n{\n  \"selected_memory_ids\": [\"id1\", \"id2\", ...]\n}\n`,\n\n  // Shared memory header inserted into prompts in various providers\n  memory_header_text:\n    \"Here is some of my memories to help answer better (don't respond to these memories but use them to assist in the response):\",\n\n  get memory_header_html_strong() {\n    return `<strong>${this.memory_header_text}</strong>`;\n  },\n\n  memory_marker_prefix: 'Here is some of my memories to help answer better',\n\n  // Central regexes for stripping the inserted memory header and its content\n  // Plain text variant (end of prompt) – matches the header and everything after it\n  memory_header_plain_regex:\n    /\\s*Here is some of my memories to help answer better \\(don't respond to these memories but use them to assist in the response\\):[\\s\\S]*$/,\n\n  // HTML variant used in some editors (e.g., Claude ProseMirror)\n  memory_header_html_regex:\n    /<p><strong>Here is some of my memories to help answer better \\(don't respond to these memories but use them to assist in the response\\):<\\/strong><\\/p>([\\s\\S]*?)(?=<p>|$)/,\n};\n"
  },
  {
    "path": "src/utils/site_config.ts",
    "content": "// --- Types ---\ntype InlinePlacement = {\n  strategy: 'inline';\n  where?: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';\n  inlineAlign?: 'start' | 'center' | 'end';\n  inlineClass?: string;\n};\n\ntype DockPlacement = {\n  strategy: 'dock';\n  container?: string | Element;\n  side?: 'top' | 'bottom' | 'left' | 'right';\n  align?: 'start' | 'center' | 'end';\n  gap?: number;\n};\n\ntype FloatPlacement = {\n  strategy: 'float';\n  placement?:\n    | 'top-start'\n    | 'top-center'\n    | 'top-end'\n    | 'right-start'\n    | 'right-center'\n    | 'right-end'\n    | 'bottom-start'\n    | 'bottom-center'\n    | 'bottom-end'\n    | 'left-start'\n    | 'left-center'\n    | 'left-end';\n  gap?: number;\n};\n\ntype Placement = InlinePlacement | DockPlacement | FloatPlacement;\n\ninterface SiteConfig {\n  editorSelector: string;\n  deriveAnchor: (editor: Element) => Element | null;\n  placement: Placement;\n  fallbackAnchors?: string[];\n  adjacentTargets?: string[];\n  autoButtonTextPattern?: RegExp;\n  enableFloatingFallback?: boolean;\n  sendButtonSelector?: string;\n  modelButtonSelector?: string;\n}\n\ninterface SiteConfigs {\n  claude: SiteConfig;\n  chatgpt: SiteConfig;\n  grok: SiteConfig;\n  deepseek: SiteConfig;\n  gemini: SiteConfig;\n  perplexity: SiteConfig;\n  replit: SiteConfig;\n}\n\n// --- Config (no globals) ---\nconst SITE_CONFIG = {\n  claude: {\n    editorSelector:\n      'div[contenteditable=\"true\"], textarea, p[data-placeholder], [contenteditable=\"true\"]',\n    deriveAnchor: editor => editor.closest('form') || editor.parentElement,\n    // Place the icon floating near the editor to avoid container clipping on some layouts\n    placement: { strategy: 'float', placement: 'right-start', gap: 8 },\n    fallbackAnchors: ['#input-tools-menu-trigger', 'button[aria-label*=\"Send\" i]'],\n  },\n  chatgpt: {\n    editorSelector: 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n    deriveAnchor: editor => {\n      const form = editor.closest('form');\n      const toolbar =\n        (form &&\n          (form.querySelector('[data-testid=\"composer-trailing-actions\"]') ||\n            form.querySelector('.composer-trailing-actions') ||\n            form.querySelector('.items-center.gap-1\\\\.5') ||\n            form.querySelector('.items-center.gap-2'))) ||\n        null;\n      return toolbar || form || editor.parentElement;\n    },\n    placement: { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n    adjacentTargets: [\n      'button[aria-label=\"Dictate button\"]',\n      'button[aria-label*=\"mic\" i]',\n      'button[aria-label*=\"voice\" i]',\n    ],\n    fallbackAnchors: [\n      'form [data-testid=\"composer-trailing-actions\"]',\n      'form .composer-trailing-actions',\n      'form textarea',\n      'main form textarea',\n    ],\n    enableFloatingFallback: true,\n  },\n  grok: {\n    editorSelector: 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n    deriveAnchor: editor => {\n      const root = editor.closest('form') || editor.parentElement || document.body;\n      const autoBtn = Array.from(root.querySelectorAll('button,[role=\"button\"]')).find(b =>\n        /\\bAuto\\b/i.test((b.textContent || '').trim())\n      );\n      return autoBtn ? autoBtn.parentElement || root : root;\n    },\n    placement: { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n    autoButtonTextPattern: /\\bAuto\\b/i,\n    fallbackAnchors: [\n      'button[aria-label*=\"Send\" i]',\n      'button[data-testid*=\"send\" i]',\n      'form button[type=\"submit\"]',\n      'textarea',\n      '[role=\"textbox\"]',\n    ],\n  },\n  deepseek: {\n    editorSelector: 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n    deriveAnchor: editor => editor.closest('form') || editor.parentElement,\n    placement: { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n  },\n  gemini: {\n    editorSelector: 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n    deriveAnchor: editor => editor.closest('form') || editor.parentElement,\n    placement: { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n  },\n  perplexity: {\n    editorSelector: 'textarea, [contenteditable], input[type=\"text\"]',\n    deriveAnchor: editor => editor.closest('form') || editor.parentElement,\n    placement: { strategy: 'inline', where: 'beforeend', inlineAlign: 'end' },\n    sendButtonSelector: 'button[aria-label=\"Submit\"]',\n    modelButtonSelector: 'button[aria-label=\"Choose a model\"]',\n  },\n  replit: {\n    editorSelector: 'textarea, [contenteditable=\"true\"], input[type=\"text\"]',\n    deriveAnchor: editor => editor.closest('form') || editor.parentElement || document.body,\n    placement: { strategy: 'float', placement: 'right-center', gap: 12 },\n  },\n} as const satisfies SiteConfigs;\n\n// --- Export exactly as requested ---\nexport { SITE_CONFIG };\n"
  },
  {
    "path": "src/utils/util_functions.ts",
    "content": "import { StorageKey, type StorageData } from '../types/storage';\n\ntype EventType = string;\ntype AdditionalData = Record<string, unknown>;\ntype CallbackFunction = (success: boolean) => void;\n\ntype ExtensionEventPayload = {\n  event_type: EventType;\n  additional_data: {\n    timestamp: string;\n    version: string;\n    user_agent: string;\n    user_id: string;\n    [key: string]: unknown;\n  };\n};\n\ntype BrowserType = 'Edge' | 'Opera' | 'Chrome' | 'Firefox' | 'Safari' | 'Unknown';\n\n/**\n * Utility function to send extension events to PostHog via mem0 API\n * @param eventType - The type of event (e.g., \"extension_install\", \"extension_toggle_button\")\n * @param additionalData - Optional additional data to include with the event\n * @param callback - Optional callback function called after attempt (receives success boolean)\n */\nexport const sendExtensionEvent = (\n  eventType: EventType,\n  additionalData: AdditionalData = {},\n  callback: CallbackFunction | null = null\n): void => {\n  chrome.storage.sync.get(\n    [StorageKey.API_KEY, StorageKey.ACCESS_TOKEN, StorageKey.USER_ID_CAMEL, StorageKey.USER_ID],\n    (data: StorageData) => {\n      if (!data[StorageKey.API_KEY] && !data[StorageKey.ACCESS_TOKEN]) {\n        if (callback) {\n          callback(false);\n        }\n        return;\n      }\n\n      const headers: Record<string, string> = {\n        'Content-Type': 'application/json',\n      };\n\n      if (data[StorageKey.ACCESS_TOKEN]) {\n        headers['Authorization'] = `Bearer ${data[StorageKey.ACCESS_TOKEN]}`;\n      } else if (data[StorageKey.API_KEY]) {\n        headers['Authorization'] = `Token ${data[StorageKey.API_KEY]}`;\n      }\n\n      const payload: ExtensionEventPayload = {\n        event_type: eventType,\n        additional_data: {\n          timestamp: new Date().toISOString(),\n          version: chrome.runtime.getManifest().version,\n          user_agent: navigator.userAgent,\n          user_id:\n            data[StorageKey.USER_ID_CAMEL] || data[StorageKey.USER_ID] || 'chrome-extension-user',\n          ...additionalData,\n        },\n      };\n\n      console.log('eventType', eventType);\n      console.log('payload', payload);\n\n      fetch('https://api.mem0.ai/v1/extension/', {\n        method: 'POST',\n        headers: headers,\n        body: JSON.stringify(payload),\n      })\n        .then(response => {\n          const success = response.ok;\n          if (callback) {\n            callback(success);\n          }\n        })\n        .catch(error => {\n          console.error(`Error sending ${eventType} event:`, error);\n          if (callback) {\n            callback(false);\n          }\n        });\n    }\n  );\n};\n\nexport const getBrowser = (): BrowserType => {\n  const userAgent = navigator.userAgent;\n  if (userAgent.includes('Edg/')) {\n    return 'Edge';\n  }\n  if (userAgent.includes('OPR/') || userAgent.includes('Opera/')) {\n    return 'Opera';\n  }\n  if (userAgent.includes('Chrome/')) {\n    return 'Chrome';\n  }\n  if (userAgent.includes('Firefox/')) {\n    return 'Firefox';\n  }\n  if (userAgent.includes('Safari/') && !userAgent.includes('Chrome/')) {\n    return 'Safari';\n  }\n  return 'Unknown';\n};\n"
  },
  {
    "path": "src/utils/util_positioning.ts",
    "content": "/* eslint-disable no-empty */\ntype AnchorCandidate =\n  | string\n  | {\n      find: () => Element | null;\n    };\n\ntype InlinePlacement = {\n  strategy: 'inline';\n  where?: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';\n  inlineAlign?: 'start' | 'end';\n  inlineClass?: string;\n};\n\ntype DockPlacement = {\n  strategy: 'dock';\n  container?: string | Element;\n  side?: 'top' | 'bottom' | 'left' | 'right';\n  align?: 'start' | 'center' | 'end';\n  gap?: number;\n};\n\ntype FloatPlacement = {\n  strategy: 'float';\n  placement?:\n    | 'top-start'\n    | 'top-center'\n    | 'top-end'\n    | 'right-start'\n    | 'right-center'\n    | 'right-end'\n    | 'bottom-start'\n    | 'bottom-center'\n    | 'bottom-end'\n    | 'left-start'\n    | 'left-center'\n    | 'left-end';\n  gap?: number;\n};\n\nexport type Placement = InlinePlacement | DockPlacement | FloatPlacement;\n\nexport type ApplyPlacementOptions = {\n  container: HTMLElement;\n  anchor: Element | null;\n  placement?: Placement | null;\n};\n\nexport type FindAnchorOptions = {\n  candidates?: AnchorCandidate[];\n  timeoutMs?: number;\n  pollMs?: number;\n};\n\nexport type MountResilientOptions = {\n  anchors?: AnchorCandidate[];\n  timeoutMs?: number;\n  pollMs?: number;\n  placement?: Placement;\n  enableFloatingFallback?: boolean;\n  render?: (shadow: ShadowRoot, host: HTMLElement, anchor: Element | null) => void | (() => void);\n};\n\nexport type MountOnEditorFocusOptions = {\n  editorSelector?: string;\n  deriveAnchor?: (editor: Element) => Element | null;\n  existingHostSelector?: string;\n  placement?: Placement;\n  cacheTtlMs?: number;\n  persistCache?: boolean;\n  fallback?: () => void;\n  learnKey?: string;\n  render?: (shadow: ShadowRoot, host: HTMLElement, anchor: Element | null) => void | (() => void);\n};\n\ntype Stopper = () => void;\n\ntype ShadowHost = {\n  host: HTMLDivElement;\n  shadow: ShadowRoot;\n};\n\ntype CachedAnchorHint = {\n  sel: string;\n  placement: Placement | null;\n  ts: number;\n  ver: string;\n};\n\ndeclare const chrome:\n  | {\n      runtime?: { getManifest: () => { version: string } };\n      storage?: {\n        session?: {\n          get: (key: string, cb: (items: Record<string, unknown>) => void) => void;\n          set: (items: Record<string, unknown>, cb?: () => void) => void;\n        };\n        local?: {\n          get: (key: string, cb: (items: Record<string, unknown>) => void) => void;\n          set: (items: Record<string, unknown>, cb?: () => void) => void;\n          remove: (key: string | string[], cb?: () => void) => void;\n        };\n      };\n    }\n  | undefined;\n\n/* utils */\nconst sleep = (ms: number): Promise<void> => new Promise(r => setTimeout(r, ms));\n\nfunction watchForRemoval(node: Node, onGone: () => void): MutationObserver {\n  const obs = new MutationObserver(() => {\n    if (!document.contains(node)) {\n      try {\n        obs.disconnect();\n      } catch {}\n      onGone();\n    }\n  });\n  obs.observe(document.documentElement, { childList: true, subtree: true });\n  return obs;\n}\n\nfunction watchSpaNavigation(callback: () => void, intervalMs: number = 500): Stopper {\n  const w = globalThis;\n  const loc = w.location;\n  const hist = w.history;\n\n  let href = loc.href;\n  const i = w.setInterval(() => {\n    if (loc.href !== href) {\n      href = loc.href;\n      callback();\n    }\n  }, intervalMs);\n\n  w.addEventListener('popstate', callback);\n\n  const originalPush = hist.pushState.bind(hist) as History['pushState'];\n  const originalReplace = hist.replaceState.bind(hist) as History['replaceState'];\n\n  // Reassign and trigger callback on SPA nav\n  hist.pushState = ((...args: Parameters<History['pushState']>) => {\n    const r = originalPush(...args);\n    callback();\n    return r;\n  }) as History['pushState'];\n\n  hist.replaceState = ((...args: Parameters<History['replaceState']>) => {\n    const r = originalReplace(...args);\n    callback();\n    return r;\n  }) as History['replaceState'];\n\n  return () => {\n    w.clearInterval(i);\n    w.removeEventListener('popstate', callback);\n    hist.pushState = originalPush;\n    hist.replaceState = originalReplace;\n  };\n}\n\n/* public API */\nexport function createShadowRootHost(className: string = 'mem0-root'): ShadowHost {\n  const host = document.createElement('div');\n  host.className = className;\n  const shadow = host.attachShadow({ mode: 'open' });\n  return { host, shadow };\n}\n\nexport async function findAnchor(\n  candidates: AnchorCandidate[] = [],\n  timeoutMs: number = 2000,\n  pollMs: number = 250\n): Promise<Element | null> {\n  const deadline = Date.now() + timeoutMs;\n\n  while (Date.now() < deadline) {\n    for (let i = 0; i < candidates.length; i++) {\n      const cand = candidates[i];\n      let el: Element | null = null;\n      if (typeof cand === 'string') {\n        el = document.querySelector(cand);\n      } else if (cand && typeof cand.find === 'function') {\n        el = cand.find();\n      }\n      if (el) {\n        return el;\n      }\n    }\n    await sleep(pollMs);\n  }\n  return null;\n}\n\nexport function applyPlacement(opts: ApplyPlacementOptions): Stopper {\n  const { container, anchor, placement } = opts;\n\n  if (!anchor) {\n    document.body.appendChild(container);\n    return () => {\n      return;\n    };\n  }\n\n  const p = placement || ({ strategy: 'inline' } as Placement);\n\n  switch (p.strategy) {\n    case 'inline': {\n      const where = p.where || 'beforeend';\n      (anchor as Element).insertAdjacentElement(where, container);\n      if (p.inlineAlign === 'end') {\n        container.style.marginLeft = 'auto';\n      }\n      if (p.inlineClass) {\n        container.classList.add(p.inlineClass);\n      }\n      return () => {\n        return;\n      };\n    }\n\n    case 'dock': {\n      const host = (\n        p.container\n          ? typeof p.container === 'string'\n            ? anchor.closest(p.container) || (anchor as Element)\n            : p.container\n          : anchor\n      ) as HTMLElement;\n\n      if (getComputedStyle(host).position === 'static') {\n        host.style.position = 'relative';\n      }\n      const side = p.side || 'bottom';\n      const align = p.align || 'start';\n      const gap = p.gap ?? 8;\n\n      const cs = container.style;\n      cs.position = 'absolute';\n      cs.zIndex = '2147483647';\n\n      const layout = (): void => {\n        host.getBoundingClientRect(); // force layout\n        if (side === 'top') {\n          cs.top = `${-container.offsetHeight - gap}px`;\n          cs.bottom = '';\n        }\n        if (side === 'bottom') {\n          cs.top = `${host.offsetHeight + gap}px`;\n          cs.bottom = '';\n        }\n        if (side === 'left') {\n          cs.left = `${-container.offsetWidth - gap}px`;\n          cs.right = '';\n          cs.top = '';\n        }\n        if (side === 'right') {\n          cs.left = `${host.offsetWidth + gap}px`;\n          cs.right = '';\n          cs.top = '';\n        }\n\n        const a = { start: '0px', center: '50%', end: '100%' }[align];\n        if (side === 'top' || side === 'bottom') {\n          cs.left = a;\n          cs.transform = align === 'center' ? 'translateX(-50%)' : '';\n        } else {\n          cs.top = a;\n          cs.transform = align === 'center' ? 'translateY(-50%)' : '';\n        }\n      };\n\n      host.appendChild(container);\n      layout();\n\n      const ro = new ResizeObserver(layout);\n      ro.observe(host);\n\n      const on = (): void => layout();\n      globalThis.addEventListener('scroll', on, true);\n      globalThis.addEventListener('resize', on);\n\n      return () => {\n        ro.disconnect();\n        globalThis.removeEventListener('scroll', on, true);\n        globalThis.removeEventListener('resize', on);\n      };\n    }\n\n    case 'float': {\n      const gap = p.gap ?? 8;\n\n      const position = (): void => {\n        const r = (anchor as Element).getBoundingClientRect();\n        const cs = container.style;\n        cs.position = 'fixed';\n        cs.zIndex = '2147483647';\n\n        const parts = (p.placement || 'right-start').split('-');\n        const side = parts[0] as 'top' | 'right' | 'bottom' | 'left';\n        const align = (parts[1] || 'start') as 'start' | 'center' | 'end';\n\n        const set = (x: number, y: number): void => {\n          cs.left = `${x}px`;\n          cs.top = `${y}px`;\n        };\n\n        const y = { start: r.top, center: r.top + r.height / 2, end: r.bottom };\n        const x = { start: r.left, center: r.left + r.width / 2, end: r.right };\n\n        if (side === 'right') {\n          set(r.right + gap, y[align] ?? r.top);\n        }\n        if (side === 'left') {\n          set(r.left - container.offsetWidth - gap, y[align] ?? r.top);\n        }\n        if (side === 'bottom') {\n          set(x[align] ?? r.left, r.bottom + gap);\n        }\n        if (side === 'top') {\n          set(x[align] ?? r.left, r.top - container.offsetHeight - gap);\n        }\n      };\n\n      document.body.appendChild(container);\n      position();\n\n      const on = (): void => position();\n      globalThis.addEventListener('scroll', on, true);\n      globalThis.addEventListener('resize', on);\n      const ro = new ResizeObserver(on);\n      ro.observe(anchor as Element);\n\n      return () => {\n        globalThis.removeEventListener('scroll', on, true);\n        globalThis.removeEventListener('resize', on);\n        ro.disconnect();\n      };\n    }\n\n    default: {\n      (anchor as Element).appendChild(container);\n      return () => {\n        return;\n      };\n    }\n  }\n}\n\n/* private helpers */\nfunction validateCachedAnchor(sel: string | undefined, editor: Element | null) {\n  const el = sel ? (document.querySelector(sel) as Element | null) : null;\n  if (!el || !el.isConnected || !document.contains(el)) {\n    return { ok: false as const, reason: 'missing' as const };\n  }\n  if (editor && !(el === editor || el.contains(editor) || editor.contains(el))) {\n    return { ok: false as const, reason: 'mismatch' as const };\n  }\n  const r = el.getBoundingClientRect();\n  if (r.width === 0 && r.height === 0) {\n    return { ok: false as const, reason: 'invisible' as const };\n  }\n  return { ok: true as const, el };\n}\n\nfunction selectorFor(el: Element | null): string {\n  if (!el) {\n    return '';\n  }\n  if ((el as HTMLElement).id) {\n    return `#${(el as HTMLElement).id}`;\n  }\n  const tag = (el.tagName || '').toLowerCase();\n  const cls =\n    (el as HTMLElement).className && typeof (el as HTMLElement).className === 'string'\n      ? '.' + (el as HTMLElement).className.trim().split(/\\s+/).slice(0, 2).join('.')\n      : '';\n  const s = tag + cls;\n  const p = el.parentElement;\n  if (p && p.id) {\n    return `#${p.id} > ${s}`;\n  }\n  return s;\n}\n\nfunction keyFor(opts: { learnKey?: string }): string {\n  const loc = globalThis.location;\n  return 'mem0_anchor_hint:' + (opts.learnKey || `${loc.host}:${loc.pathname}`);\n}\n\nfunction now(): number {\n  return Date.now();\n}\n\nfunction ver(): string {\n  try {\n    return chrome?.runtime?.getManifest().version ?? '0';\n  } catch {\n    return '0';\n  }\n}\n\n/* chrome storage helpers (safe, promise-based) */\nasync function getSession<T = unknown>(k: string): Promise<T | null> {\n  return new Promise<T | null>(resolve => {\n    try {\n      chrome?.storage?.session?.get(k, (o: Record<string, unknown>) =>\n        resolve((o?.[k] as T) ?? null)\n      );\n    } catch {\n      resolve(null);\n    }\n  });\n}\n\nasync function setSession<T = unknown>(k: string, v: T): Promise<void> {\n  return new Promise<void>(resolve => {\n    try {\n      const o: Record<string, unknown> = {};\n      o[k] = v as unknown;\n      chrome?.storage?.session?.set(o, resolve);\n    } catch {\n      resolve();\n    }\n  });\n}\n\nasync function getLocal<T = unknown>(k: string): Promise<T | null> {\n  return new Promise<T | null>(resolve => {\n    try {\n      chrome?.storage?.local?.get(k, (o: Record<string, unknown>) =>\n        resolve((o?.[k] as T) ?? null)\n      );\n    } catch {\n      resolve(null);\n    }\n  });\n}\n\nasync function setLocal<T = unknown>(k: string, v: T): Promise<void> {\n  return new Promise<void>(resolve => {\n    try {\n      const o: Record<string, unknown> = {};\n      o[k] = v as unknown;\n      chrome?.storage?.local?.set(o, resolve);\n    } catch {\n      resolve();\n    }\n  });\n}\n\nasync function delLocal(k: string | string[]): Promise<void> {\n  return new Promise<void>(resolve => {\n    try {\n      chrome?.storage?.local?.remove(k, resolve);\n    } catch {\n      resolve();\n    }\n  });\n}\n\n/* cache API */\nexport async function resolveCachedAnchor(\n  opts: { learnKey?: string },\n  editor: Element | null,\n  ttlMs: number = 24 * 60 * 60 * 1000\n): Promise<{ el: Element; placement: Placement | null } | null> {\n  const k = keyFor(opts);\n\n  // fast session cache\n  let hint = (await getSession<CachedAnchorHint>(k)) || null;\n  if (hint?.sel) {\n    const v = validateCachedAnchor(hint.sel, editor);\n    if (v.ok) {\n      return { el: v.el, placement: hint.placement };\n    }\n  }\n\n  // persisted cache\n  hint = (await getLocal<CachedAnchorHint>(k)) || null;\n  if (hint?.sel && hint.ver === ver() && now() - hint.ts < ttlMs) {\n    const v2 = validateCachedAnchor(hint.sel, editor);\n    if (v2.ok) {\n      return { el: v2.el, placement: hint.placement };\n    }\n  } else if (hint) {\n    await delLocal(k);\n  }\n\n  return null;\n}\n\nexport async function saveAnchorHint(\n  opts: { learnKey?: string },\n  anchorEl: Element,\n  placement: Placement | null,\n  persist?: boolean\n): Promise<void> {\n  const k = keyFor(opts);\n  const hint: CachedAnchorHint = {\n    sel: selectorFor(anchorEl),\n    placement: placement ?? null,\n    ts: now(),\n    ver: ver(),\n  };\n  await setSession(k, hint);\n  if (persist) {\n    await setLocal(k, hint);\n  }\n}\n\n/* mount helpers */\nexport function mountResilient(opts: MountResilientOptions): Stopper {\n  let cleanup: (() => void) | null = null;\n  let stopSpa: Stopper | null = null;\n  let removalObs: MutationObserver | null = null;\n  let host: HTMLElement | null = null;\n\n  const bootstrap = async (): Promise<void> => {\n    try {\n      if (cleanup) {\n        try {\n          cleanup();\n        } catch {}\n        cleanup = null;\n      }\n      if (removalObs) {\n        try {\n          removalObs.disconnect();\n        } catch {}\n        removalObs = null;\n      }\n      if (host?.isConnected) {\n        host.remove();\n      }\n      host = null;\n\n      const anchor = await findAnchor(\n        opts.anchors || [],\n        opts.timeoutMs ?? 2000,\n        opts.pollMs ?? 200\n      );\n      const { host: h, shadow } = createShadowRootHost('mem0-root');\n      host = h;\n\n      let unplace: Stopper = () => {\n        return;\n      };\n      if (anchor) {\n        unplace = applyPlacement({ container: host, anchor, placement: opts.placement });\n      } else if (opts.enableFloatingFallback) {\n        Object.assign(host.style, {\n          position: 'fixed',\n          right: '16px',\n          bottom: '16px',\n          zIndex: '2147483647',\n        });\n        document.body.appendChild(host);\n      } else {\n        return;\n      }\n\n      const maybeCleanup =\n        typeof opts.render === 'function' ? opts.render(shadow, host, anchor) : null;\n      if (typeof maybeCleanup === 'function') {\n        cleanup = maybeCleanup;\n      }\n\n      const watchNode = anchor || host;\n      removalObs = watchForRemoval(watchNode, () => {\n        try {\n          unplace();\n        } catch {}\n        if (cleanup) {\n          try {\n            cleanup();\n          } catch {}\n        }\n        bootstrap();\n      });\n\n      if (!stopSpa) {\n        stopSpa = watchSpaNavigation(() => bootstrap());\n      }\n    } catch {\n      setTimeout(() => bootstrap(), 1500);\n    }\n  };\n\n  void bootstrap();\n\n  return () => {\n    if (cleanup) {\n      cleanup();\n    }\n    if (stopSpa) {\n      stopSpa();\n    }\n    if (removalObs) {\n      removalObs.disconnect();\n    }\n    if (host?.isConnected) {\n      host.remove();\n    }\n  };\n}\n\nexport function mountOnEditorFocus(opts: MountOnEditorFocusOptions): Stopper {\n  let used = false;\n  const editorSelector =\n    opts.editorSelector || 'textarea, [contenteditable=\"true\"], input[type=\"text\"]';\n  const deriveAnchor =\n    opts.deriveAnchor ||\n    ((editor: Element) => editor.closest('form') || editor.parentElement || null);\n  const guardSelector = opts.existingHostSelector || '#mem0-icon-button, .mem0-root';\n\n  const handleIntent = (e: Event): void => {\n    if (used) {\n      return;\n    }\n    try {\n      if (guardSelector && document.querySelector(guardSelector)) {\n        return;\n      }\n    } catch {}\n    const t = e.target as Element | null;\n    if (!t || !(t.matches && t.matches(editorSelector))) {\n      return;\n    }\n\n    used = true;\n\n    Promise.resolve()\n      .then(() => resolveCachedAnchor(opts, t, opts.cacheTtlMs))\n      .then(async hit => {\n        const anchor = hit?.el ?? deriveAnchor(t);\n        const placement =\n          hit?.placement ?? (opts.placement || ({ strategy: 'inline' } as Placement));\n\n        if (!anchor && typeof opts.fallback === 'function') {\n          used = false;\n          return opts.fallback();\n        }\n        if (!anchor) {\n          used = false;\n          return;\n        }\n\n        const { host, shadow } = createShadowRootHost('mem0-root');\n\n        const unplace = applyPlacement({ container: host, anchor, placement });\n        const cleanup = (\n          opts.render && typeof opts.render === 'function'\n            ? opts.render(shadow, host, anchor)\n            : null\n        ) as null | (() => void);\n\n        if (!hit?.el) {\n          try {\n            await saveAnchorHint(opts, anchor, placement, opts.persistCache);\n          } catch {}\n        }\n\n        const removal = new MutationObserver(() => {\n          if (!document.contains(anchor) || !document.contains(host)) {\n            try {\n              unplace();\n            } catch {}\n            if (cleanup) {\n              try {\n                cleanup();\n              } catch {}\n            }\n            try {\n              removal.disconnect();\n            } catch {}\n            used = false;\n          }\n        });\n        removal.observe(document.documentElement, { childList: true, subtree: true });\n      })\n      .catch(() => {\n        used = false;\n      });\n  };\n\n  globalThis.addEventListener('focusin', handleIntent, true);\n  globalThis.addEventListener('keydown', handleIntent, true);\n  globalThis.addEventListener('pointerdown', handleIntent, true);\n\n  return function stop() {\n    globalThis.removeEventListener('focusin', handleIntent, true);\n    globalThis.removeEventListener('keydown', handleIntent, true);\n    globalThis.removeEventListener('pointerdown', handleIntent, true);\n  };\n}\n\n/* bundled export for convenience */\nexport const OPENMEMORY_UI = {\n  createShadowRootHost,\n  findAnchor,\n  applyPlacement,\n  resolveCachedAnchor,\n  saveAnchorHint,\n  mountResilient,\n  mountOnEditorFocus,\n};\n\nexport default OPENMEMORY_UI;\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2022\", \"DOM\"],\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"Bundler\",\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"useDefineForClassFields\": true,\n    \"skipLibCheck\": true,\n    \"isolatedModules\": true,\n\n    \"allowJs\": false,\n    \"checkJs\": false,\n\n    \"outDir\": \"dist\",\n    \"rootDir\": \".\",\n    \"types\": [\"chrome-types\"]\n  },\n  \"include\": [\"**/*.ts\", \"manifest.json\"],\n  \"exclude\": [\"node_modules\", \"icons/**\", \"**/*.min.js\", \"**/dist/**\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport { crx } from '@crxjs/vite-plugin';\nimport manifest from './manifest.json';\n\nexport default defineConfig({\n  plugins: [crx({ manifest })],\n  build: { outDir: 'dist', emptyOutDir: true }\n});\n"
  }
]