aboutsummaryrefslogtreecommitdiff
path: root/apps/browser-extension/utils
diff options
context:
space:
mode:
authorMaheshtheDev <[email protected]>2025-09-08 05:19:10 +0000
committerMaheshtheDev <[email protected]>2025-09-08 05:19:10 +0000
commit3e5ea2fd9ed210644ae29b013b579703e30986de (patch)
tree7de64cf7bddca72cf56e89ee4320005dad9f2ac5 /apps/browser-extension/utils
parentfix: billing page (#416) (diff)
downloadsupermemory-3e5ea2fd9ed210644ae29b013b579703e30986de.tar.xz
supermemory-3e5ea2fd9ed210644ae29b013b579703e30986de.zip
extension: updated telemetry and batch upload (#415)
Diffstat (limited to 'apps/browser-extension/utils')
-rw-r--r--apps/browser-extension/utils/api.ts92
-rw-r--r--apps/browser-extension/utils/constants.ts15
-rw-r--r--apps/browser-extension/utils/posthog.ts74
-rw-r--r--apps/browser-extension/utils/twitter-import.ts77
-rw-r--r--apps/browser-extension/utils/types.ts4
-rw-r--r--apps/browser-extension/utils/ui-components.ts149
6 files changed, 220 insertions, 191 deletions
diff --git a/apps/browser-extension/utils/api.ts b/apps/browser-extension/utils/api.ts
index 2b95c6e2..7e4de310 100644
--- a/apps/browser-extension/utils/api.ts
+++ b/apps/browser-extension/utils/api.ts
@@ -1,27 +1,27 @@
/**
* API service for supermemory browser extension
*/
-import { API_ENDPOINTS, STORAGE_KEYS } from "./constants";
+import { API_ENDPOINTS, STORAGE_KEYS } from "./constants"
import {
AuthenticationError,
type MemoryPayload,
type Project,
type ProjectsResponse,
SupermemoryAPIError,
-} from "./types";
+} from "./types"
/**
* Get bearer token from storage
*/
async function getBearerToken(): Promise<string> {
- const result = await chrome.storage.local.get([STORAGE_KEYS.BEARER_TOKEN]);
- const token = result[STORAGE_KEYS.BEARER_TOKEN];
+ const result = await chrome.storage.local.get([STORAGE_KEYS.BEARER_TOKEN])
+ const token = result[STORAGE_KEYS.BEARER_TOKEN]
if (!token) {
- throw new AuthenticationError("Bearer token not found");
+ throw new AuthenticationError("Bearer token not found")
}
- return token;
+ return token
}
/**
@@ -31,7 +31,7 @@ async function makeAuthenticatedRequest<T>(
endpoint: string,
options: RequestInit = {},
): Promise<T> {
- const token = await getBearerToken();
+ const token = await getBearerToken()
const response = await fetch(`${API_ENDPOINTS.SUPERMEMORY_API}${endpoint}`, {
...options,
@@ -41,19 +41,19 @@ async function makeAuthenticatedRequest<T>(
"Content-Type": "application/json",
...options.headers,
},
- });
+ })
if (!response.ok) {
if (response.status === 401) {
- throw new AuthenticationError("Invalid or expired token");
+ throw new AuthenticationError("Invalid or expired token")
}
throw new SupermemoryAPIError(
`API request failed: ${response.statusText}`,
response.status,
- );
+ )
}
- return response.json();
+ return response.json()
}
/**
@@ -62,11 +62,11 @@ async function makeAuthenticatedRequest<T>(
export async function fetchProjects(): Promise<Project[]> {
try {
const response =
- await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects");
- return response.projects;
+ await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects")
+ return response.projects
} catch (error) {
- console.error("Failed to fetch projects:", error);
- throw error;
+ console.error("Failed to fetch projects:", error)
+ throw error
}
}
@@ -77,11 +77,11 @@ export async function getDefaultProject(): Promise<Project | null> {
try {
const result = await chrome.storage.local.get([
STORAGE_KEYS.DEFAULT_PROJECT,
- ]);
- return result[STORAGE_KEYS.DEFAULT_PROJECT] || null;
+ ])
+ return result[STORAGE_KEYS.DEFAULT_PROJECT] || null
} catch (error) {
- console.error("Failed to get default project:", error);
- return null;
+ console.error("Failed to get default project:", error)
+ return null
}
}
@@ -92,10 +92,10 @@ export async function setDefaultProject(project: Project): Promise<void> {
try {
await chrome.storage.local.set({
[STORAGE_KEYS.DEFAULT_PROJECT]: project,
- });
+ })
} catch (error) {
- console.error("Failed to set default project:", error);
- throw error;
+ console.error("Failed to set default project:", error)
+ throw error
}
}
@@ -107,11 +107,11 @@ export async function saveMemory(payload: MemoryPayload): Promise<unknown> {
const response = await makeAuthenticatedRequest<unknown>("/v3/memories", {
method: "POST",
body: JSON.stringify(payload),
- });
- return response;
+ })
+ return response
} catch (error) {
- console.error("Failed to save memory:", error);
- throw error;
+ console.error("Failed to save memory:", error)
+ throw error
}
}
@@ -123,34 +123,40 @@ export async function searchMemories(query: string): Promise<unknown> {
const response = await makeAuthenticatedRequest<unknown>("/v4/search", {
method: "POST",
body: JSON.stringify({ q: query, include: { relatedMemories: true } }),
- });
- return response;
+ })
+ return response
} catch (error) {
- console.error("Failed to search memories:", error);
- throw error;
+ console.error("Failed to search memories:", error)
+ throw error
}
}
/**
* Save tweet to Supermemory API (specific for Twitter imports)
*/
-export async function saveTweet(
- content: string,
- metadata: { sm_source: string; [key: string]: unknown },
- containerTag = "sm_project_twitter_bookmarks",
-): Promise<void> {
+export async function saveAllTweets(
+ documents: MemoryPayload[],
+): Promise<unknown> {
try {
- const payload: MemoryPayload = {
- containerTags: [containerTag],
- content,
- metadata,
- };
- await saveMemory(payload);
+ const response = await makeAuthenticatedRequest<unknown>(
+ "/v3/memories/batch",
+ {
+ method: "POST",
+ body: JSON.stringify({
+ documents,
+ metadata: {
+ sm_source: "consumer",
+ sm_internal_group_id: "twitter_bookmarks",
+ },
+ }),
+ },
+ )
+ return response
} catch (error) {
if (error instanceof SupermemoryAPIError && error.statusCode === 409) {
// Skip if already exists (409 Conflict)
- return;
+ return
}
- throw error;
+ throw error
}
}
diff --git a/apps/browser-extension/utils/constants.ts b/apps/browser-extension/utils/constants.ts
index 7634759b..5ebd76d1 100644
--- a/apps/browser-extension/utils/constants.ts
+++ b/apps/browser-extension/utils/constants.ts
@@ -15,6 +15,7 @@ export const API_ENDPOINTS = {
*/
export const STORAGE_KEYS = {
BEARER_TOKEN: "bearer-token",
+ USER_DATA: "user-data",
TOKENS_LOGGED: "tokens-logged",
TWITTER_COOKIE: "twitter-cookie",
TWITTER_CSRF: "twitter-csrf",
@@ -27,10 +28,6 @@ export const STORAGE_KEYS = {
*/
export const ELEMENT_IDS = {
TWITTER_IMPORT_BUTTON: "sm-twitter-import-button",
- TWITTER_IMPORT_STATUS: "sm-twitter-import-status",
- TWITTER_CLOSE_BTN: "sm-twitter-close-btn",
- TWITTER_IMPORT_BTN: "sm-twitter-import-btn",
- TWITTER_SIGNIN_BTN: "sm-twitter-signin-btn",
SUPERMEMORY_TOAST: "sm-toast",
SUPERMEMORY_SAVE_BUTTON: "sm-save-button",
SAVE_TWEET_ELEMENT: "sm-save-tweet-element",
@@ -83,3 +80,13 @@ export const MESSAGE_TYPES = {
export const CONTEXT_MENU_IDS = {
SAVE_TO_SUPERMEMORY: "sm-save-to-supermemory",
} as const
+
+export const POSTHOG_EVENT_KEY = {
+ TWITTER_IMPORT_STARTED: "twitter_import_started",
+ SAVE_MEMORY_ATTEMPTED: "save_memory_attempted",
+ SAVE_MEMORY_ATTEMPT_FAILED: "save_memory_attempt_failed",
+ SOURCE: "extension",
+ T3_CHAT_MEMORIES_SEARCHED: "t3_chat_memories_searched",
+ CLAUDE_CHAT_MEMORIES_SEARCHED: "claude_chat_memories_searched",
+ CHATGPT_CHAT_MEMORIES_SEARCHED: "chatgpt_chat_memories_searched",
+} as const
diff --git a/apps/browser-extension/utils/posthog.ts b/apps/browser-extension/utils/posthog.ts
new file mode 100644
index 00000000..cdcdbc4e
--- /dev/null
+++ b/apps/browser-extension/utils/posthog.ts
@@ -0,0 +1,74 @@
+import { PostHog } from "posthog-js/dist/module.no-external"
+import { STORAGE_KEYS } from "./constants"
+
+export async function identifyUser(posthog: PostHog): Promise<void> {
+ const stored = await chrome.storage.local.get([STORAGE_KEYS.USER_DATA])
+ const userData = stored[STORAGE_KEYS.USER_DATA]
+
+ if (userData?.userId) {
+ posthog.identify(userData.userId, {
+ email: userData.email,
+ name: userData.name,
+ userId: userData.userId,
+ })
+ }
+}
+
+let posthogInstance: PostHog | null = null
+let initializationPromise: Promise<PostHog> | null = null
+
+export const POSTHOG_CONFIG = {
+ api_host: "https://api.supermemory.ai/orange",
+ person_profiles: "identified_only",
+ disable_external_dependency_loading: true,
+ persistence: "localStorage",
+ capture_pageview: false,
+ autocapture: false,
+} as const
+
+export async function getPostHogInstance(): Promise<PostHog> {
+ if (posthogInstance) {
+ return posthogInstance
+ }
+
+ if (initializationPromise) {
+ return initializationPromise
+ }
+
+ initializationPromise = initializePostHog()
+ return initializationPromise
+}
+
+async function initializePostHog(): Promise<PostHog> {
+ try {
+ const posthog = new PostHog()
+
+ if (!import.meta.env.WXT_POSTHOG_API_KEY) {
+ console.error("PostHog API key not configured")
+ throw new Error("PostHog API key not configured")
+ }
+
+ posthog.init(import.meta.env.WXT_POSTHOG_API_KEY || "", POSTHOG_CONFIG)
+
+ await identifyUser(posthog)
+
+ posthogInstance = posthog
+ return posthog
+ } catch (error) {
+ console.error("Failed to initialize PostHog:", error)
+ initializationPromise = null
+ throw error
+ }
+}
+
+export async function trackEvent(
+ eventName: string,
+ properties?: Record<string, unknown>,
+): Promise<void> {
+ try {
+ const posthog = await getPostHogInstance()
+ posthog.capture(eventName, properties)
+ } catch (error) {
+ console.error(`Failed to track event ${eventName}:`, error)
+ }
+}
diff --git a/apps/browser-extension/utils/twitter-import.ts b/apps/browser-extension/utils/twitter-import.ts
index c516e094..e68d6dbf 100644
--- a/apps/browser-extension/utils/twitter-import.ts
+++ b/apps/browser-extension/utils/twitter-import.ts
@@ -3,16 +3,14 @@
* Handles the import process for Twitter bookmarks
*/
-import { saveTweet } from "./api"
+import { saveAllTweets } from "./api"
import { createTwitterAPIHeaders, getTwitterTokens } from "./twitter-auth"
import {
BOOKMARKS_URL,
buildRequestVariables,
extractNextCursor,
getAllTweets,
- type Tweet,
type TwitterAPIResponse,
- tweetToMarkdown,
} from "./twitter-utils"
export type ImportProgressCallback = (message: string) => Promise<void>
@@ -48,31 +46,6 @@ class RateLimiter {
}
/**
- * Imports a single tweet to Supermemory
- * @param tweetMd - Tweet content in markdown format
- * @param tweet - Original tweet object with metadata
- * @returns Promise that resolves when tweet is imported
- */
-async function importTweet(tweetMd: string, tweet: Tweet): Promise<void> {
- const metadata = {
- sm_source: "consumer",
- tweet_id: tweet.id_str,
- author: tweet.user.screen_name,
- created_at: tweet.created_at,
- likes: tweet.favorite_count,
- retweets: tweet.retweet_count || 0,
- }
-
- try {
- await saveTweet(tweetMd, metadata)
- } catch (error) {
- throw new Error(
- `Failed to save tweet: ${error instanceof Error ? error.message : "Unknown error"}`,
- )
- }
-}
-
-/**
* Main class for handling Twitter bookmarks import
*/
export class TwitterImporter {
@@ -91,9 +64,10 @@ export class TwitterImporter {
}
this.importInProgress = true
+ const uniqueGroupId = crypto.randomUUID()
try {
- await this.batchImportAll("", 0)
+ await this.batchImportAll("", 0, uniqueGroupId)
this.rateLimiter.reset()
} catch (error) {
await this.config.onError(error as Error)
@@ -107,7 +81,7 @@ export class TwitterImporter {
* @param cursor - Pagination cursor for Twitter API
* @param totalImported - Number of tweets imported so far
*/
- private async batchImportAll(cursor = "", totalImported = 0): Promise<void> {
+ private async batchImportAll(cursor = "", totalImported = 0, uniqueGroupId = "twitter_bookmarks"): Promise<void> {
try {
// Use a local variable to track imported count
let importedCount = totalImported
@@ -130,9 +104,6 @@ export class TwitterImporter {
? `${BOOKMARKS_URL}&variables=${encodeURIComponent(JSON.stringify(variables))}`
: BOOKMARKS_URL
- console.log("Making Twitter API request to:", urlWithCursor)
- console.log("Request headers:", Object.fromEntries(headers.entries()))
-
const response = await fetch(urlWithCursor, {
method: "GET",
headers,
@@ -145,7 +116,7 @@ export class TwitterImporter {
if (response.status === 429) {
await this.rateLimiter.handleRateLimit(this.config.onProgress)
- return this.batchImportAll(cursor, totalImported)
+ return this.batchImportAll(cursor, totalImported, uniqueGroupId)
}
throw new Error(
`Failed to fetch data: ${response.status} - ${errorText}`,
@@ -155,21 +126,45 @@ export class TwitterImporter {
const data: TwitterAPIResponse = await response.json()
const tweets = getAllTweets(data)
- console.log("Tweets:", tweets)
+ const documents: MemoryPayload[] = []
- // Process each tweet
+ // Convert tweets to MemoryPayload
for (const tweet of tweets) {
try {
- const tweetMd = tweetToMarkdown(tweet)
- await importTweet(tweetMd, tweet)
+ const metadata = {
+ sm_source: "consumer",
+ tweet_id: tweet.id_str,
+ author: tweet.user.screen_name,
+ created_at: tweet.created_at,
+ likes: tweet.favorite_count,
+ retweets: tweet.retweet_count || 0,
+ sm_internal_group_id: uniqueGroupId,
+ }
+ documents.push({
+ containerTags: ["sm_project_twitter_bookmarks"],
+ content: `https://x.com/${tweet.user.screen_name}/status/${tweet.id_str}`,
+ metadata,
+ customId: tweet.id_str,
+ })
importedCount++
- await this.config.onProgress(`Imported ${importedCount} tweets`)
+ await this.config.onProgress(`Imported ${importedCount} tweets, so far...`)
} catch (error) {
console.error("Error importing tweet:", error)
- // Continue with next tweet
}
}
+ try {
+ if (documents.length > 0) {
+ await saveAllTweets(documents)
+ }
+ console.log("Tweets saved")
+ console.log("Documents:", documents)
+ } catch (error) {
+ console.error("Error saving tweets batch:", error)
+ await this.config.onError(error as Error)
+ return
+ }
+
// Handle pagination
const instructions =
data.data?.bookmark_timeline_v2?.timeline?.instructions
@@ -180,7 +175,7 @@ export class TwitterImporter {
if (nextCursor && tweets.length > 0) {
await new Promise((resolve) => setTimeout(resolve, 1000)) // Rate limiting
- await this.batchImportAll(nextCursor, importedCount)
+ await this.batchImportAll(nextCursor, importedCount, uniqueGroupId)
} else {
await this.config.onComplete(importedCount)
}
diff --git a/apps/browser-extension/utils/types.ts b/apps/browser-extension/utils/types.ts
index 2d0981c8..d20f899e 100644
--- a/apps/browser-extension/utils/types.ts
+++ b/apps/browser-extension/utils/types.ts
@@ -17,6 +17,7 @@ export interface ExtensionMessage {
state?: ToastState
importedMessage?: string
totalImported?: number
+ actionSource?: string
}
/**
@@ -32,12 +33,13 @@ export interface MemoryData {
* Supermemory API payload for storing memories
*/
export interface MemoryPayload {
- containerTags: string[]
+ containerTags?: string[]
content: string
metadata: {
sm_source: string
[key: string]: unknown
}
+ customId?: string
}
/**
diff --git a/apps/browser-extension/utils/ui-components.ts b/apps/browser-extension/utils/ui-components.ts
index 9c060017..29388656 100644
--- a/apps/browser-extension/utils/ui-components.ts
+++ b/apps/browser-extension/utils/ui-components.ts
@@ -3,7 +3,7 @@
* Reusable UI components for the browser extension
*/
-import { API_ENDPOINTS, ELEMENT_IDS, UI_CONFIG } from "./constants"
+import { ELEMENT_IDS, UI_CONFIG } from "./constants"
import type { ToastState } from "./types"
/**
@@ -158,7 +158,7 @@ export function createTwitterImportButton(onClick: () => void): HTMLElement {
color: black;
border: none;
border-radius: 50px;
- padding: 12px 16px;
+ padding: 10px 12px;
cursor: pointer;
display: flex;
align-items: center;
@@ -169,15 +169,16 @@ export function createTwitterImportButton(onClick: () => void): HTMLElement {
const iconUrl = browser.runtime.getURL("/icon-16.png")
button.innerHTML = `
<img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" />
+ <span style="font-weight: 500; font-size: 12px;">Import Bookmarks</span>
`
button.addEventListener("mouseenter", () => {
- button.style.transform = "scale(1.05)"
+ button.style.opacity = "0.8"
button.style.boxShadow = "0 4px 12px rgba(29, 155, 240, 0.4)"
})
button.addEventListener("mouseleave", () => {
- button.style.transform = "scale(1)"
+ button.style.opacity = "1"
button.style.boxShadow = "0 2px 8px rgba(29, 155, 240, 0.3)"
})
@@ -187,103 +188,6 @@ export function createTwitterImportButton(onClick: () => void): HTMLElement {
}
/**
- * Creates the Twitter import UI dialog
- * @param onClose - Close handler
- * @param onImport - Import handler
- * @param isAuthenticated - Whether user is authenticated
- * @returns HTMLElement - The dialog element
- */
-export function createTwitterImportUI(
- onClose: () => void,
- onImport: () => void,
- isAuthenticated: boolean,
-): HTMLElement {
- const container = document.createElement("div")
- container.style.cssText = `
- position: fixed;
- top: 20px;
- right: 20px;
- z-index: 2147483647;
- background: #ffffff;
- border-radius: 12px;
- padding: 16px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- min-width: 280px;
- max-width: 400px;
- border: 1px solid #e1e5e9;
- font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
- `
-
- container.innerHTML = `
- <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
- <div style="display: flex; align-items: center; gap: 8px;">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="#1d9bf0">
- <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
- </svg>
- <h3 style="margin: 0; font-size: 16px; font-weight: 600; color: #0f1419;">
- Import Twitter Bookmarks
- </h3>
- </div>
- <button id="${ELEMENT_IDS.TWITTER_CLOSE_BTN}" style="background: none; border: none; cursor: pointer; padding: 4px; border-radius: 4px; color: #536471;">
- ✕
- </button>
- </div>
-
- ${
- isAuthenticated
- ? `
- <div>
- <p style="color: #536471; font-size: 14px; margin: 0 0 12px 0; line-height: 1.4;">
- This will import all your Twitter bookmarks to Supermemory
- </p>
-
- <button id="${ELEMENT_IDS.TWITTER_IMPORT_BTN}" style="width: 100%; background: #1d9bf0; color: white; border: none; border-radius: 20px; padding: 12px 16px; cursor: pointer; font-size: 14px; font-weight: 500; margin-bottom: 12px;">
- Import All Bookmarks
- </button>
-
- <div id="${ELEMENT_IDS.TWITTER_IMPORT_STATUS}"></div>
- </div>
- `
- : `
- <div style="text-align: center;">
- <p style="color: #536471; font-size: 14px; margin: 0 0 12px 0;">
- Please sign in to supermemory first
- </p>
- <button id="${ELEMENT_IDS.TWITTER_SIGNIN_BTN}" style="background: #1d9bf0; color: white; border: none; border-radius: 20px; padding: 8px 16px; cursor: pointer; font-size: 14px; font-weight: 500;">
- Sign In
- </button>
- </div>
- `
- }
-
- <style>
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- </style>
- `
-
- // Add event listeners
- const closeBtn = container.querySelector(`#${ELEMENT_IDS.TWITTER_CLOSE_BTN}`)
- closeBtn?.addEventListener("click", onClose)
-
- const importBtn = container.querySelector(
- `#${ELEMENT_IDS.TWITTER_IMPORT_BTN}`,
- )
- importBtn?.addEventListener("click", onImport)
-
- const signinBtn = container.querySelector(
- `#${ELEMENT_IDS.TWITTER_SIGNIN_BTN}`,
- )
- signinBtn?.addEventListener("click", () => {
- browser.tabs.create({ url: `${API_ENDPOINTS.SUPERMEMORY_WEB}/login` })
- })
-
- return container
-}
-
-/**
* Creates a save tweet element button for Twitter/X
* @param onClick - Click handler for the button
* @returns HTMLElement - The save button element
@@ -510,7 +414,48 @@ export const DOMUtils = {
state: ToastState,
duration: number = UI_CONFIG.TOAST_DURATION,
): HTMLElement {
- // Remove all existing toasts more aggressively
+ const existingToast = document.getElementById(ELEMENT_IDS.SUPERMEMORY_TOAST)
+
+ if ((state === "success" || state === "error") && existingToast) {
+ const icon = existingToast.querySelector("div")
+ const text = existingToast.querySelector("span")
+
+ if (icon && text) {
+ // Update based on new state
+ if (state === "success") {
+ const iconUrl = browser.runtime.getURL("/icon-16.png")
+ icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />`
+ icon.style.animation = ""
+ text.textContent = "Added to Memory"
+ } else if (state === "error") {
+ icon.innerHTML = `
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="12" cy="12" r="10" fill="#ef4444"/>
+ <path d="M15 9L9 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M9 9L15 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>
+ `
+ icon.style.animation = ""
+ text.textContent =
+ "Failed to save memory / Make sure you are logged in"
+ }
+
+ // Auto-dismiss
+ setTimeout(() => {
+ if (document.body.contains(existingToast)) {
+ existingToast.style.animation = "fadeOut 0.3s ease-out"
+ setTimeout(() => {
+ if (document.body.contains(existingToast)) {
+ existingToast.remove()
+ }
+ }, 300)
+ }
+ }, duration)
+
+ return existingToast
+ }
+ }
+
const existingToasts = document.querySelectorAll(
`#${ELEMENT_IDS.SUPERMEMORY_TOAST}`,
)