diff --git a/.husky/_/pre-commit b/.husky/_/pre-commit index 921078d7..7f8135c8 100755 --- a/.husky/_/pre-commit +++ b/.husky/_/pre-commit @@ -1,3 +1,3 @@ #!/bin/sh . "$(dirname "$0")/husky.sh" -bun run lint-staged \ No newline at end of file +pnpm run lint-staged \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..361b1968 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://registry.npmjs.org/ +link-workspace-packages=true diff --git a/SETUP-GUIDE.md b/SETUP-GUIDE.md index 72052931..46a2e1da 100644 --- a/SETUP-GUIDE.md +++ b/SETUP-GUIDE.md @@ -4,17 +4,16 @@ This guide will help you set up your own instance of Supermemory. This is necces ## Prerequisites -- [bun](https://bun.sh/) +- [pnpm](https://pnpm.io/installation): pnpm is used as a package manager. You can enable pnpm by running `corepack enable pnpm` in your terminal. - [turbo](https://turbo.build/repo/docs/installing) - [wrangler](https://developers.cloudflare.com/workers/cli-wrangler/install-update) -- [yarn](https://yarnpkg.com/getting-started/install): yarn is required to run scripts using turborepo. bun is not supported by turborepo yet vercel/turbo#4762 - [Cloudflare Workers](https://developers.cloudflare.com/workers/platform/pricing/): You also need to have a paid Workers plan to use the vectorize feature which is needed run the AI backend. It is currently $5/mo + usage costs. - [Cloudflare R2](https://developers.cloudflare.com/r2/): You need to enable R2 in the Cloudflare Dashboard for use in the web app. ## Steps 1. Clone the repo -2. Run `bun install` in the root directory +2. Run `pnpm install` in the root directory ### web @@ -37,11 +36,11 @@ BACKEND_BASE_URL="http://localhost:8686" 3. KV Namespaces ```bash -bunx wrangler kv namespace create canvas-snaps +pnpx wrangler kv namespace create canvas-snaps ``` ```bash -bunx wrangler kv namespace create recommendations +pnpx wrangler kv namespace create recommendations ``` Do not change the binding value in the `wrangler.toml` but update the id for the namespaces with the values you get from the above commands. @@ -49,7 +48,7 @@ Do not change the binding value in the `wrangler.toml` but update the id for the 4. R2 Storage ```bash -bunx wrangler r2 bucket create supermemory-r2 +pnpx wrangler r2 bucket create supermemory-r2 ``` Update bucket_name in the `wrangler.toml` file in `apps/web` to `supermemory-r2` @@ -57,13 +56,13 @@ Update bucket_name in the `wrangler.toml` file in `apps/web` to `supermemory-r2` 5. D1 Database ```bash -bunx wrangler d1 create supermemory-db-prod +pnpx wrangler d1 create supermemory-db-prod ``` Update the database_name and database_id in `[[env.production.d1_databases]]` with the values you get from the above command. ```bash -bunx wrangler d1 create supermemory-db-preview +pnpx wrangler d1 create supermemory-db-preview ``` Update the database_name and database_id in `[[d1_databases]]` and `[[env.preview.d1_databases]]` with the values you get from the above command. @@ -81,12 +80,12 @@ database_id = "YOUR_DB_ID" Simply run this command in `apps/web` ```bash -bunx wrangler d1 migrations apply supermemory-db-preview +pnpx wrangler d1 migrations apply supermemory-db-preview ``` If it runs, you can set up the cloud database as well by add the `--remote` flag, -if you just want to contribute to frontend then just run `bun run dev` in the root of the project and done! (you won't be able to try ai stuff), otherwise continue... +if you just want to contribute to frontend then just run `pnpm run dev` in the root of the project and done! (you won't be able to try ai stuff), otherwise continue... ### cf-ai-backend @@ -104,7 +103,7 @@ OPENAI_API_KEY="sk-" > Note: You need to use the workers paid plan to use vectorize for now. ```bash -bunx wrangler vectorize create --dimensions=1536 supermemory --metric=cosine +pnpx wrangler vectorize create --dimensions=1536 supermemory --metric=cosine ``` Update the index_name for `[[vectorize]]` in `wrangler.toml` file in `apps/cf-ai-backend` with the `supermemory` or the name you used in the above command. @@ -112,27 +111,27 @@ Update the index_name for `[[vectorize]]` in `wrangler.toml` file in `apps/cf-ai 3. Create KV namespaces for the `cf-ai-backend` module ```bash -bunx wrangler kv namespace create prod +pnpx wrangler kv namespace create prod ``` Update the id in `[[kv_namespaces]]` in the `wrangler.toml` file in `apps/cf-ai-backend` with the value you get from the above command. ```bash -bunx wrangler kv namespace create preview +pnpx wrangler kv namespace create preview ``` Update the preview_id in `[[kv_namespaces]]` in the `wrangler.toml` file in `apps/cf-ai-backend` with the value you get from the above command. ## Local Development -- Run `bun dev` in the root directory and Voila! You have your own supermemory instance running! +- Run `pnpm dev` in the root directory and Voila! You have your own supermemory instance running! > [!NOTE] -> It sometimes takes multiple tries to successfully run the `bun dev` command. If you encounter any issues, try running the command again. +> It sometimes takes multiple tries to successfully run the `pnpm dev` command. If you encounter any issues, try running the command again. ## Deploying -To deploy the web app, run `bun deploy` in the `apps/web` directory. +To deploy the web app, run `pnpm run deploy` in the `apps/web` directory. To deploy the cf-ai-backend module, run `wrangler publish` in the `apps/cf-ai-backend` directory. -To get the extension running, you need to build it first. Run `bun build` in the `apps/extension` directory and then load the extension in chrome. +To get the extension running, you need to build it first. Run `pnpm build` in the `apps/extension` directory and then load the extension in chrome. diff --git a/apps/cf-ai-backend/package.json b/apps/cf-ai-backend/package.json index fee0c0d8..2b83cc93 100644 --- a/apps/cf-ai-backend/package.json +++ b/apps/cf-ai-backend/package.json @@ -12,6 +12,10 @@ }, "license": "MIT", "dependencies": { - "@hono/zod-validator": "^0.2.1" - } + "@hono/zod-validator": "^0.2.1", + "hono": "^4.5.1" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0" + } } diff --git a/apps/extension/content/ContentApp.tsx b/apps/extension/content/ContentApp.tsx index 89a3a635..c0339897 100644 --- a/apps/extension/content/ContentApp.tsx +++ b/apps/extension/content/ContentApp.tsx @@ -31,400 +31,400 @@ export default function ContentApp({ token: string | undefined; shadowRoot: ShadowRoot; }) { - const [hover, setHover] = useState(false); + const [hover, setHover] = useState(false); - const { toast } = useToast(); + const { toast } = useToast(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - const [webNote, setWebNote] = useState(""); + const [webNote, setWebNote] = useState(""); - const [importedCount, setImportedCount] = useState(0); - const [isImporting, setIsImporting] = useState(false); - const [importDone, setImportDone] = useState(false); + 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 [isPopover2Open, setIsPopover2Open] = useState(false); + const [portalContainer, setPortalContainer] = useState( + null, + ); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [isPopover2Open, setIsPopover2Open] = useState(false); - const [spacesOptions, setSpacesOptions] = useState< - { id: number; name: string }[] - >([]); - const [selectedSpace, setSelectedSpace] = useState(); + const [spacesOptions, setSpacesOptions] = useState< + { id: number; name: string }[] + >([]); + const [selectedSpace, setSelectedSpace] = useState(); - const [userNotLoggedIn, setUserNotLoggedIn] = useState(false); + const [userNotLoggedIn, setUserNotLoggedIn] = useState(false); - const showLoginToast = async () => { - setUserNotLoggedIn(true); + const showLoginToast = async () => { + setUserNotLoggedIn(true); - const NOSHOW_TOAST = ["accounts.google.com", "supermemory.ai"]; + const NOSHOW_TOAST = ["accounts.google.com", "supermemory.ai"]; - const noLoginWarning = await chrome.storage.local.get("noLoginWarning"); - if (Object.keys(noLoginWarning).length > 0) { - return; - } + 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: ( -
- + 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) => { - const percentageX = (e.clientX / window.innerWidth) * 100; - const percentageY = (e.clientY / window.innerHeight) * 100; + useEffect(() => { + document.addEventListener("mousemove", (e) => { + const percentageX = (e.clientX / window.innerWidth) * 100; + const percentageY = (e.clientY / window.innerHeight) * 100; - if (percentageX > 75 && percentageY > 75) { - setHover(true); - } else { - setHover(false); - } - }); + if (percentageX > 75 && percentageY > 75) { + setHover(true); + } else { + setHover(false); + } + }); - const getUserData = () => { - chrome.runtime.sendMessage({ type: "getJwt" }); - }; + const getUserData = () => { + chrome.runtime.sendMessage({ type: "getJwt" }); + }; - getUserData(); + getUserData(); - chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request.type === "import-update") { - setIsImporting(true); - setImportedCount(request.importedCount); - } + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.type === "import-update") { + setIsImporting(true); + setImportedCount(request.importedCount); + } - if (request.type === "import-done") { - setIsImporting(false); - setImportDone(true); - } + if (request.type === "import-done") { + setIsImporting(false); + setImportDone(true); + } - if (request.type === "supermemory-message") { - toast({ - title: request.message, - }); - } - }); + if (request.type === "supermemory-message") { + toast({ + title: request.message, + }); + } + }); - const portalDiv = document.createElement("div"); - portalDiv.id = "popover-portal"; - shadowRoot.appendChild(portalDiv); - setPortalContainer(portalDiv); + const portalDiv = document.createElement("div"); + portalDiv.id = "popover-portal"; + shadowRoot.appendChild(portalDiv); + setPortalContainer(portalDiv); - return () => { - document.removeEventListener("mousemove", () => {}); - }; - }, []); + return () => { + document.removeEventListener("mousemove", () => {}); + }; + }, []); - const getSpaces = async () => { - const response = await fetch(`${BACKEND_URL}/api/spaces`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const getSpaces = async () => { + const response = await fetch(`${BACKEND_URL}/api/spaces`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); - if (response.status === 401) { - showLoginToast(); - return; - } + 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`, - ); - } - }; + 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`, + ); + } + }; - async function sendUrlToAPI(spaces: string[]) { - setLoading(true); + async function sendUrlToAPI(spaces: string[]) { + setLoading(true); - setTimeout(() => { - setLoading(false); - }, 1500); + setTimeout(() => { + setLoading(false); + }, 1500); - // get the current URL - const url = window.location.href; + // get the current URL + const url = window.location.href; - const blacklist: string[] = []; - // check if the URL is blacklisted - if (blacklist.some((blacklisted) => url.includes(blacklisted))) { - return; - } else { - const clone = document.cloneNode(true) as Document; - const article = new Readability(clone).parse(); + const blacklist: string[] = []; + // check if the URL is blacklisted + if (blacklist.some((blacklisted) => url.includes(blacklisted))) { + return; + } else { + const clone = document.cloneNode(true) as Document; + const article = new Readability(clone).parse(); - const ogImage = document - .querySelector('meta[property="og:image"]') - ?.getAttribute("content"); + const ogImage = document + .querySelector('meta[property="og:image"]') + ?.getAttribute("content"); - const favicon = ( - document.querySelector('link[rel="icon"]') as HTMLLinkElement - )?.href; + const favicon = ( + document.querySelector('link[rel="icon"]') as HTMLLinkElement + )?.href; - setLoading(true); + setLoading(true); - setIsPopoverOpen(false); + 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; - } + 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(); + 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; - }); - } - } + 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; + }); + } + } - if (!shadowRoot || !portalContainer) { - return null; - } + if (!shadowRoot || !portalContainer) { + return null; + } - return ( -
- + return ( +
+ - setIsPopoverOpen(!isPopoverOpen)} - > - - - await getSpaces()} asChild> - - - - - - - - {userNotLoggedIn ? ( - <>You need to login to use this extension. - ) : ( -

Add to supermemory.ai

- )} -
-
-
- - {userNotLoggedIn ? ( -
- -
- ) : ( -
- + setIsPopoverOpen(!isPopoverOpen)} + > + + + await getSpaces()} asChild> + + + + + + + + {userNotLoggedIn ? ( + <>You need to login to use this extension. + ) : ( +

Add to supermemory.ai

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