From 6ef746da542b8e5da3ef638fb7f46f5aa10a3a02 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Thu, 4 Jul 2024 19:17:51 -0500 Subject: [PATCH] revamped extention --- apps/cf-ai-backend/src/helper.ts | 1 + apps/cf-ai-backend/src/index.ts | 2 +- .../cf-ai-backend/src/utils/OpenAIEmbedder.ts | 23 +- apps/extension/background.ts | 72 ++- apps/extension/content/ContentApp.tsx | 365 +++++++++++--- apps/extension/content/content.tsx | 2 +- apps/extension/content/ui/shadcn/input.tsx | 25 + apps/extension/content/ui/shadcn/label.tsx | 26 + apps/extension/content/ui/shadcn/popover.tsx | 2 +- apps/extension/content/ui/shadcn/select.tsx | 162 ++++++ apps/extension/content/ui/shadcn/textarea.tsx | 24 + apps/extension/content/ui/shadcn/tooltip.tsx | 2 +- apps/extension/content/ui/shadcn/use-toast.ts | 2 +- apps/extension/package.json | 2 + apps/extension/public/output.css | 466 ++++++++---------- apps/web/app/api/ensureAuth.ts | 2 +- apps/web/app/api/store/route.ts | 16 +- apps/web/middleware.ts | 27 +- 18 files changed, 840 insertions(+), 381 deletions(-) create mode 100644 apps/extension/content/ui/shadcn/input.tsx create mode 100644 apps/extension/content/ui/shadcn/label.tsx create mode 100644 apps/extension/content/ui/shadcn/select.tsx create mode 100644 apps/extension/content/ui/shadcn/textarea.tsx diff --git a/apps/cf-ai-backend/src/helper.ts b/apps/cf-ai-backend/src/helper.ts index db3ab2d3..98e38ce8 100644 --- a/apps/cf-ai-backend/src/helper.ts +++ b/apps/cf-ai-backend/src/helper.ts @@ -51,6 +51,7 @@ export async function initQuery( apiKey: c.env.OPENAI_API_KEY, baseURL: "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/openai", + compatibility: "strict", }); selectedModel = openai.chat("gpt-4o"); break; diff --git a/apps/cf-ai-backend/src/index.ts b/apps/cf-ai-backend/src/index.ts index 31ee520f..04f80f0d 100644 --- a/apps/cf-ai-backend/src/index.ts +++ b/apps/cf-ai-backend/src/index.ts @@ -509,7 +509,7 @@ app.post( const userMessage: CoreMessage = { role: "user", content: prompt }; const response = await streamText({ - model, + model: model, messages: [ ...initialMessages, ...((body.chatHistory || []) as CoreMessage[]), diff --git a/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts b/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts index be5839b1..8364ed0d 100644 --- a/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts +++ b/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts @@ -22,17 +22,20 @@ export class OpenAIEmbeddings { } async embedQuery(text: string): Promise { - const response = await fetch("https://api.openai.com/v1/embeddings", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.apiKey}`, + const response = await fetch( + "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/openai/embeddings", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + body: JSON.stringify({ + input: text, + model: this.modelName, + }), }, - body: JSON.stringify({ - input: text, - model: this.modelName, - }), - }); + ); const data = await response.json(); diff --git a/apps/extension/background.ts b/apps/extension/background.ts index 97bb341c..df4d0375 100644 --- a/apps/extension/background.ts +++ b/apps/extension/background.ts @@ -74,15 +74,28 @@ const batchImportAll = async (cursor = "", totalImported = 0) => { description: tweet.text.slice(0, 200), type: "tweet", }), - }).then((ers) => { + }).then(async (ers) => { console.log(ers.status); importedCount++; totalImported++; - // Send an update message to the content script - chrome.runtime.sendMessage({ - type: "import-update", - importedCount: totalImported, - }); + console.log(totalImported); + chrome.tabs.query( + { active: true, currentWindow: true }, + async function (tabs) { + if (tabs.length > 0) { + let currentTabId = tabs[0].id; + + if (!currentTabId) { + return; + } + + await chrome.tabs.sendMessage(currentTabId, { + type: "import-update", + importedCount: totalImported, + }); + } + }, + ); }); }); })(); @@ -111,19 +124,45 @@ const batchImportAll = async (cursor = "", totalImported = 0) => { batchImportAll(nextCursor, totalImported); // Recursively call with new cursor } else { console.log("All bookmarks imported"); - // Send a "done" message to the content script - chrome.runtime.sendMessage({ - type: "import-done", - importedCount: totalImported, - }); + + chrome.tabs.query( + { active: true, currentWindow: true }, + async function (tabs) { + if (tabs.length > 0) { + let currentTabId = tabs[0].id; + + if (!currentTabId) { + return; + } + + await chrome.runtime.sendMessage({ + type: "import-done", + importedCount: totalImported, + }); + } + }, + ); } } else { console.log("All bookmarks imported"); // Send a "done" message to the content script - chrome.runtime.sendMessage({ - type: "import-done", - importedCount: totalImported, - }); + chrome.tabs.query( + { active: true, currentWindow: true }, + async function (tabs) { + if (tabs.length > 0) { + let currentTabId = tabs[0].id; + + if (!currentTabId) { + return; + } + + await chrome.runtime.sendMessage({ + type: "import-done", + importedCount: totalImported, + }); + } + }, + ); } }) .catch((error) => console.error(error)); @@ -311,10 +350,9 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { }).then((ers) => console.log(ers.status)); }); })(); - - return true; } else if (request.type === "batchImportAll") { batchImportAll(); + return true; } }); diff --git a/apps/extension/content/ContentApp.tsx b/apps/extension/content/ContentApp.tsx index c1af1dfc..1dea7645 100644 --- a/apps/extension/content/ContentApp.tsx +++ b/apps/extension/content/ContentApp.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; import { Readability } from "@mozilla/readability"; -import tailwindBg from "../public/tailwind_bg.png"; import { Tooltip, TooltipContent, @@ -9,7 +8,19 @@ import { } from "./ui/shadcn/tooltip"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/shadcn/popover"; import { Toaster } from "./ui/shadcn/toaster"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "./ui/shadcn/select"; import { useToast } from "./ui/shadcn/use-toast"; +import { Input } from "./ui/shadcn/input"; +import { Label } from "./ui/shadcn/label"; +import { Textarea } from "./ui/shadcn/textarea"; const BACKEND_URL = "https://supermemory.ai"; @@ -20,19 +31,70 @@ export default function ContentApp({ token: string | undefined; shadowRoot: ShadowRoot; }) { - const [text, setText] = useState(""); const [hover, setHover] = useState(false); + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + const [webNote, setWebNote] = useState(""); + const [importedCount, setImportedCount] = useState(0); const [isImporting, setIsImporting] = useState(false); + const [importDone, setImportDone] = useState(false); const [portalContainer, setPortalContainer] = useState( null, ); const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const { toast } = useToast(); + const [isPopover2Open, setIsPopover2Open] = useState(false); + + const [spacesOptions, setSpacesOptions] = useState< + { id: number; name: string }[] + >([]); + const [selectedSpace, setSelectedSpace] = useState(); + + const [userNotLoggedIn, setUserNotLoggedIn] = useState(false); + + const showLoginToast = async () => { + setUserNotLoggedIn(true); + + const NOSHOW_TOAST = ["accounts.google.com", "supermemory.ai"]; + + const noLoginWarning = await chrome.storage.local.get("noLoginWarning"); + if (Object.keys(noLoginWarning).length > 0) { + return; + } + + if (!NOSHOW_TOAST.includes(window.location.host)) { + const t = toast({ + title: "Please login to supermemory.ai to use this extension.", + action: ( +
+ + + +
+ ), + }); + } + }; useEffect(() => { document.addEventListener("mousemove", (e) => { @@ -47,24 +109,21 @@ export default function ContentApp({ }); const getUserData = () => { - chrome.runtime.sendMessage({ type: "getJwt" }, (response) => { - console.log("jwt", response.jwt); - }); + chrome.runtime.sendMessage({ type: "getJwt" }); }; getUserData(); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - console.log("request", request); - console.log("sender", sender); - console.log("sendResponse", sendResponse); if (request.type === "import-update") { + console.log(request); setIsImporting(true); setImportedCount(request.importedCount); } if (request.type === "import-done") { setIsImporting(false); + setImportDone(true); } }); @@ -73,12 +132,36 @@ export default function ContentApp({ shadowRoot.appendChild(portalDiv); setPortalContainer(portalDiv); + const getSpaces = async () => { + const response = await fetch(`${BACKEND_URL}/api/spaces`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response.status === 401) { + showLoginToast(); + return; + } + + try { + const data = await response.json(); + setSpacesOptions(data.data); + } catch (e) { + console.error( + `Error in supermemory.ai extension: ${e}. Please contact the developer https://x.com/dhravyashah`, + ); + } + }; + + getSpaces(); + return () => { document.removeEventListener("mousemove", () => {}); }; - }, [document.getElementById("supermemory-extension-host")]); + }, []); - function sendUrlToAPI(spaces: number[]) { + async function sendUrlToAPI(spaces: string[]) { setLoading(true); setTimeout(() => { @@ -91,7 +174,6 @@ export default function ContentApp({ const blacklist: string[] = []; // check if the URL is blacklisted if (blacklist.some((blacklisted) => url.includes(blacklisted))) { - console.log("URL is blacklisted"); return; } else { const clone = document.cloneNode(true) as Document; @@ -105,16 +187,45 @@ export default function ContentApp({ document.querySelector('link[rel="icon"]') as HTMLLinkElement )?.href; - console.log("article", article); - chrome.runtime.sendMessage({ - type: "urlSave", - content: article?.textContent, - url, - spaces, - title: article?.title, - description: article?.excerpt, - ogImage: ogImage, - favicon: favicon, + setLoading(true); + + setIsPopoverOpen(false); + + await fetch(`${BACKEND_URL}/api/store`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + pageContent: + (webNote ? `Note about this website: ${webNote}\n\n` : "") + + article?.textContent, + url: url + "#supermemory-user-" + Math.random(), + title: article?.title.slice(0, 500), + spaces: spaces, + description: article?.excerpt.slice(0, 250), + ogImage: ogImage?.slice(0, 1000), + image: favicon, + }), + }).then(async (rep) => { + if (rep.status === 401) { + showLoginToast(); + return; + } + + const d = await rep.json(); + + if (rep.status === 200) { + toast({ + title: "Saved to supermemory.ai", + }); + } else { + toast({ + title: `Failed to save to supermemory.ai: ${d.error ?? "Unknown error"}`, + }); + } + setLoading(false); + return rep; }); } } @@ -134,69 +245,179 @@ export default function ContentApp({ - {loading ? ( -
Saving...
- ) : ( - - - - )} + + +
-

Add to supermemory.ai

+ {userNotLoggedIn ? ( + <>You need to login to use this extension. + ) : ( +

Add to supermemory.ai

+ )}
- -
-

Select a space

+ + {userNotLoggedIn ? ( +
+ +
+ ) : ( +
+ - -
+ +