aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/cf-ai-backend/package.json30
-rw-r--r--apps/cf-ai-backend/src/helper.ts380
-rw-r--r--apps/cf-ai-backend/src/index.ts1004
-rw-r--r--apps/cf-ai-backend/src/prompts/prompt1.ts18
-rw-r--r--apps/cf-ai-backend/src/types.ts70
-rw-r--r--apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts102
-rw-r--r--apps/cf-ai-backend/src/utils/chonker.ts68
-rw-r--r--apps/cf-ai-backend/src/utils/seededRandom.ts22
-rw-r--r--apps/cf-ai-backend/tsconfig.json10
-rw-r--r--apps/cf-ai-backend/vite.config.ts2
-rw-r--r--apps/extension/background.ts890
-rw-r--r--apps/extension/components.json32
-rw-r--r--apps/extension/content/ContentApp.tsx820
-rw-r--r--apps/extension/content/base.css8
-rw-r--r--apps/extension/content/content.tsx112
-rw-r--r--apps/extension/content/ui/shadcn/input.tsx28
-rw-r--r--apps/extension/content/ui/shadcn/label.tsx18
-rw-r--r--apps/extension/content/ui/shadcn/popover.tsx42
-rw-r--r--apps/extension/content/ui/shadcn/select.tsx220
-rw-r--r--apps/extension/content/ui/shadcn/textarea.tsx26
-rw-r--r--apps/extension/content/ui/shadcn/toast.tsx158
-rw-r--r--apps/extension/content/ui/shadcn/toaster.tsx52
-rw-r--r--apps/extension/content/ui/shadcn/tooltip.tsx22
-rw-r--r--apps/extension/content/ui/shadcn/use-toast.ts278
-rw-r--r--apps/extension/content/utils.ts2
-rw-r--r--apps/extension/helpers.ts130
-rw-r--r--apps/extension/manifest.json52
-rw-r--r--apps/extension/package.json64
-rw-r--r--apps/extension/postcss.config.js8
-rw-r--r--apps/extension/public/output.css2016
-rw-r--r--apps/extension/tsconfig.json32
-rw-r--r--apps/web/.eslintrc.js12
-rw-r--r--apps/web/app/(auth)/privacy/page.tsx18
-rw-r--r--apps/web/app/(auth)/signin/_components/TextGradient/gradient.module.css138
-rw-r--r--apps/web/app/(auth)/signin/page.tsx200
-rw-r--r--apps/web/app/(auth)/tos/page.tsx18
-rw-r--r--apps/web/app/(canvas)/canvas/[id]/page.tsx22
-rw-r--r--apps/web/app/(canvas)/canvas/page.tsx26
-rw-r--r--apps/web/app/(canvas)/canvas/search&create.tsx62
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPad.tsx466
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPads.tsx42
-rw-r--r--apps/web/app/(canvas)/canvasStyles.css22
-rw-r--r--apps/web/app/(canvas)/layout.tsx38
-rw-r--r--apps/web/app/(dash)/(memories)/content.tsx673
-rw-r--r--apps/web/app/(dash)/(memories)/memories/page.tsx6
-rw-r--r--apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx28
-rw-r--r--apps/web/app/(dash)/chat/CodeBlock.tsx158
-rw-r--r--apps/web/app/(dash)/chat/[chatid]/page.tsx48
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx774
-rw-r--r--apps/web/app/(dash)/chat/markdownRenderHelpers.tsx30
-rw-r--r--apps/web/app/(dash)/header/autoBreadCrumbs.tsx68
-rw-r--r--apps/web/app/(dash)/header/header.tsx96
-rw-r--r--apps/web/app/(dash)/header/newChatButton.tsx26
-rw-r--r--apps/web/app/(dash)/home/homeVariants.ts96
-rw-r--r--apps/web/app/(dash)/home/page.tsx232
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx308
-rw-r--r--apps/web/app/(dash)/layout.tsx42
-rw-r--r--apps/web/app/(dash)/menu.tsx616
-rw-r--r--apps/web/app/(dash)/note/[noteid]/page.tsx30
-rw-r--r--apps/web/app/(landing)/CardPatterns/AnimatedBeam.tsx329
-rw-r--r--apps/web/app/(landing)/CardPatterns/AnimatedBeamWithOutput.tsx961
-rw-r--r--apps/web/app/(landing)/CardPatterns/AnimatedGrid.tsx254
-rw-r--r--apps/web/app/(landing)/CardPatterns/Glare.tsx251
-rw-r--r--apps/web/app/(landing)/Cta.tsx84
-rw-r--r--apps/web/app/(landing)/EmailInput.tsx144
-rw-r--r--apps/web/app/(landing)/FeatureCardContent.tsx191
-rw-r--r--apps/web/app/(landing)/FeatureContent.tsx96
-rw-r--r--apps/web/app/(landing)/Features.tsx108
-rw-r--r--apps/web/app/(landing)/Features/chatbubble.tsx40
-rw-r--r--apps/web/app/(landing)/Features/features.tsx26
-rw-r--r--apps/web/app/(landing)/Features/index.tsx8
-rw-r--r--apps/web/app/(landing)/GridPatterns/PlusGrid.tsx68
-rw-r--r--apps/web/app/(landing)/Headers/Navbar.tsx194
-rw-r--r--apps/web/app/(landing)/Hero.tsx142
-rw-r--r--apps/web/app/(landing)/ImageSliders.tsx324
-rw-r--r--apps/web/app/(landing)/Navbar.tsx84
-rw-r--r--apps/web/app/(landing)/RotatingIcons.tsx174
-rw-r--r--apps/web/app/(landing)/Showcase.tsx446
-rw-r--r--apps/web/app/(landing)/footer.tsx62
-rw-r--r--apps/web/app/(landing)/formSubmitAction.ts74
-rw-r--r--apps/web/app/(landing)/linkArrow.tsx66
-rw-r--r--apps/web/app/(landing)/page.tsx70
-rw-r--r--apps/web/app/(landing)/twitterLink.tsx208
-rw-r--r--apps/web/app/(quicklinks)/extension/route.ts6
-rw-r--r--apps/web/app/actions/doers.ts1275
-rw-r--r--apps/web/app/actions/fetchers.ts601
-rw-r--r--apps/web/app/actions/types.ts14
-rw-r--r--apps/web/app/api/canvas/route.ts10
-rw-r--r--apps/web/app/api/canvasai/route.ts46
-rw-r--r--apps/web/app/api/chat/history/route.ts37
-rw-r--r--apps/web/app/api/chat/route.ts154
-rw-r--r--apps/web/app/api/editorai/route.ts44
-rw-r--r--apps/web/app/api/ensureAuth.ts126
-rw-r--r--apps/web/app/api/getCount/route.ts66
-rw-r--r--apps/web/app/api/hello/route.ts26
-rw-r--r--apps/web/app/api/me/route.ts66
-rw-r--r--apps/web/app/api/memories/route.ts85
-rw-r--r--apps/web/app/api/spaces/route.ts95
-rw-r--r--apps/web/app/api/store/route.ts392
-rw-r--r--apps/web/app/api/telegram/route.ts170
-rw-r--r--apps/web/app/api/unfirlsite/route.ts292
-rw-r--r--apps/web/app/api/upload_image/route.ts92
-rw-r--r--apps/web/app/global-error.tsx26
-rw-r--r--apps/web/app/layout.tsx126
-rw-r--r--apps/web/app/ref/page.tsx202
-rw-r--r--apps/web/cf-env.d.ts36
-rw-r--r--apps/web/components/canvas/canvas.tsx132
-rw-r--r--apps/web/components/canvas/draggableComponent.tsx84
-rw-r--r--apps/web/components/canvas/dropComponent.tsx356
-rw-r--r--apps/web/components/canvas/enabledComp copy.tsx36
-rw-r--r--apps/web/components/canvas/enabledComp.tsx36
-rw-r--r--apps/web/components/canvas/resizableLayout.tsx268
-rw-r--r--apps/web/components/canvas/savesnap.tsx46
-rw-r--r--apps/web/components/canvas/textCard.tsx74
-rw-r--r--apps/web/components/canvas/twitterCard.tsx124
-rw-r--r--apps/web/components/twitter/icons/icons.module.css14
-rw-r--r--apps/web/components/twitter/icons/verified-business.tsx98
-rw-r--r--apps/web/components/twitter/icons/verified-government.tsx28
-rw-r--r--apps/web/components/twitter/icons/verified.tsx20
-rw-r--r--apps/web/components/twitter/render-tweet.tsx196
-rw-r--r--apps/web/components/twitter/tweet-header.module.css106
-rw-r--r--apps/web/components/twitter/verified-badge.module.css8
-rw-r--r--apps/web/components/twitter/verified-badge.tsx46
-rw-r--r--apps/web/drizzle.config.ts16
-rw-r--r--apps/web/env.d.ts8
-rw-r--r--apps/web/instrumentation.ts12
-rw-r--r--apps/web/lib/constants.ts76
-rw-r--r--apps/web/lib/context.ts14
-rw-r--r--apps/web/lib/createAssetUrl.ts158
-rw-r--r--apps/web/lib/createEmbeds.ts418
-rw-r--r--apps/web/lib/get-metadata.ts68
-rw-r--r--apps/web/lib/get-theme-button.tsx10
-rw-r--r--apps/web/lib/handle-errors.ts30
-rw-r--r--apps/web/lib/loadSnap.ts12
-rw-r--r--apps/web/lib/searchParams.ts48
-rw-r--r--apps/web/middleware.ts24
-rw-r--r--apps/web/migrations/meta/0000_snapshot.json1608
-rw-r--r--apps/web/migrations/meta/_journal.json22
-rw-r--r--apps/web/next.config.mjs79
-rw-r--r--apps/web/package.json103
-rw-r--r--apps/web/public/site.webmanifest34
-rw-r--r--apps/web/sentry.client.config.ts28
-rw-r--r--apps/web/sentry.edge.config.ts10
-rw-r--r--apps/web/sentry.server.config.ts14
-rw-r--r--apps/web/server/auth.ts52
-rw-r--r--apps/web/server/db/schema.ts370
-rw-r--r--apps/web/server/encrypt.ts138
-rw-r--r--apps/web/tailwind.config.ts1
-rw-r--r--apps/web/tsconfig.json46
-rw-r--r--apps/web/wrangler.toml3
150 files changed, 12370 insertions, 12106 deletions
diff --git a/apps/cf-ai-backend/package.json b/apps/cf-ai-backend/package.json
index 78353e08..fee0c0d8 100644
--- a/apps/cf-ai-backend/package.json
+++ b/apps/cf-ai-backend/package.json
@@ -1,17 +1,17 @@
{
- "name": "new-cf-ai-backend",
- "private": true,
- "version": "0.0.1",
- "main": "src/index.ts",
- "scripts": {
- "test": "jest --verbose",
- "deploy": "wrangler deploy",
- "dev": "wrangler dev --remote --port 8686",
- "start": "wrangler dev",
- "unsafe-reset-vector-db": "wrangler vectorize delete supermem-vector && wrangler vectorize create --dimensions=1536 supermem-vector-1 --metric=cosine"
- },
- "license": "MIT",
- "dependencies": {
- "@hono/zod-validator": "^0.2.1"
- }
+ "name": "new-cf-ai-backend",
+ "private": true,
+ "version": "0.0.1",
+ "main": "src/index.ts",
+ "scripts": {
+ "test": "jest --verbose",
+ "deploy": "wrangler deploy",
+ "dev": "wrangler dev --remote --port 8686",
+ "start": "wrangler dev",
+ "unsafe-reset-vector-db": "wrangler vectorize delete supermem-vector && wrangler vectorize create --dimensions=1536 supermem-vector-1 --metric=cosine"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "@hono/zod-validator": "^0.2.1"
+ }
}
diff --git a/apps/cf-ai-backend/src/helper.ts b/apps/cf-ai-backend/src/helper.ts
index 8502ca35..c54dde9f 100644
--- a/apps/cf-ai-backend/src/helper.ts
+++ b/apps/cf-ai-backend/src/helper.ts
@@ -9,211 +9,211 @@ import { z } from "zod";
import { seededRandom } from "./utils/seededRandom";
export async function initQuery(
- c: Context<{ Bindings: Env }>,
- model: string = "gpt-4o",
+ c: Context<{ Bindings: Env }>,
+ model: string = "gpt-4o",
) {
- const embeddings = new OpenAIEmbeddings({
- apiKey: c.env.OPENAI_API_KEY,
- modelName: "text-embedding-3-small",
- });
-
- const store = new CloudflareVectorizeStore(embeddings, {
- index: c.env.VECTORIZE_INDEX,
- });
-
- let selectedModel:
- | ReturnType<ReturnType<typeof createOpenAI>>
- | ReturnType<ReturnType<typeof createGoogleGenerativeAI>>
- | ReturnType<ReturnType<typeof createAnthropic>>;
-
- switch (model) {
- case "claude-3-opus":
- const anthropic = createAnthropic({
- apiKey: c.env.ANTHROPIC_API_KEY,
- baseURL:
- "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/anthropic",
- });
- selectedModel = anthropic.chat("claude-3-opus-20240229");
- console.log("Selected model: ", selectedModel);
- break;
- case "gemini-1.5-pro":
- const googleai = createGoogleGenerativeAI({
- apiKey: c.env.GOOGLE_AI_API_KEY,
- baseURL:
- "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/google-vertex-ai",
- });
- selectedModel = googleai.chat("models/gemini-1.5-pro-latest");
- console.log("Selected model: ", selectedModel);
- break;
- case "gpt-4o":
- default:
- const openai = createOpenAI({
- apiKey: c.env.OPENAI_API_KEY,
- baseURL:
- "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/openai",
- compatibility: "strict",
- });
- selectedModel = openai.chat("gpt-4o");
- break;
- }
-
- return { store, model: selectedModel };
+ const embeddings = new OpenAIEmbeddings({
+ apiKey: c.env.OPENAI_API_KEY,
+ modelName: "text-embedding-3-small",
+ });
+
+ const store = new CloudflareVectorizeStore(embeddings, {
+ index: c.env.VECTORIZE_INDEX,
+ });
+
+ let selectedModel:
+ | ReturnType<ReturnType<typeof createOpenAI>>
+ | ReturnType<ReturnType<typeof createGoogleGenerativeAI>>
+ | ReturnType<ReturnType<typeof createAnthropic>>;
+
+ switch (model) {
+ case "claude-3-opus":
+ const anthropic = createAnthropic({
+ apiKey: c.env.ANTHROPIC_API_KEY,
+ baseURL:
+ "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/anthropic",
+ });
+ selectedModel = anthropic.chat("claude-3-opus-20240229");
+ console.log("Selected model: ", selectedModel);
+ break;
+ case "gemini-1.5-pro":
+ const googleai = createGoogleGenerativeAI({
+ apiKey: c.env.GOOGLE_AI_API_KEY,
+ baseURL:
+ "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/google-vertex-ai",
+ });
+ selectedModel = googleai.chat("models/gemini-1.5-pro-latest");
+ console.log("Selected model: ", selectedModel);
+ break;
+ case "gpt-4o":
+ default:
+ const openai = createOpenAI({
+ apiKey: c.env.OPENAI_API_KEY,
+ baseURL:
+ "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/openai",
+ compatibility: "strict",
+ });
+ selectedModel = openai.chat("gpt-4o-mini");
+ break;
+ }
+
+ return { store, model: selectedModel };
}
export async function deleteDocument({
- url,
- user,
- c,
- store,
+ url,
+ user,
+ c,
+ store,
}: {
- url: string;
- user: string;
- c: Context<{ Bindings: Env }>;
- store: CloudflareVectorizeStore;
+ url: string;
+ user: string;
+ c: Context<{ Bindings: Env }>;
+ store: CloudflareVectorizeStore;
}) {
- const toBeDeleted = `${url}#supermemory-web`;
- const random = seededRandom(toBeDeleted);
-
- const uuid =
- random().toString(36).substring(2, 15) +
- random().toString(36).substring(2, 15);
-
- const allIds = await c.env.KV.list({ prefix: uuid });
-
- if (allIds.keys.length > 0) {
- const savedVectorIds = allIds.keys.map((key) => key.name);
- const vectors = await c.env.VECTORIZE_INDEX.getByIds(savedVectorIds);
- // We don't actually delete document directly, we just remove the user from the metadata.
- // If there's no user left, we can delete the document.
- const newVectors = vectors.map((vector) => {
- delete vector.metadata[`user-${user}`];
-
- // Get count of how many users are left
- const userCount = Object.keys(vector.metadata).filter((key) =>
- key.startsWith("user-"),
- ).length;
-
- // If there's no user left, we can delete the document.
- // need to make sure that every chunk is deleted otherwise it would be problematic.
- if (userCount === 0) {
- store.delete({ ids: savedVectorIds });
- void Promise.all(savedVectorIds.map((id) => c.env.KV.delete(id)));
- return null;
- }
-
- return vector;
- });
-
- // If all vectors are null (deleted), we can delete the KV too. Otherwise, we update (upsert) the vectors.
- if (newVectors.every((v) => v === null)) {
- await c.env.KV.delete(uuid);
- } else {
- await c.env.VECTORIZE_INDEX.upsert(newVectors.filter((v) => v !== null));
- }
- }
+ const toBeDeleted = `${url}#supermemory-web`;
+ const random = seededRandom(toBeDeleted);
+
+ const uuid =
+ random().toString(36).substring(2, 15) +
+ random().toString(36).substring(2, 15);
+
+ const allIds = await c.env.KV.list({ prefix: uuid });
+
+ if (allIds.keys.length > 0) {
+ const savedVectorIds = allIds.keys.map((key) => key.name);
+ const vectors = await c.env.VECTORIZE_INDEX.getByIds(savedVectorIds);
+ // We don't actually delete document directly, we just remove the user from the metadata.
+ // If there's no user left, we can delete the document.
+ const newVectors = vectors.map((vector) => {
+ delete vector.metadata[`user-${user}`];
+
+ // Get count of how many users are left
+ const userCount = Object.keys(vector.metadata).filter((key) =>
+ key.startsWith("user-"),
+ ).length;
+
+ // If there's no user left, we can delete the document.
+ // need to make sure that every chunk is deleted otherwise it would be problematic.
+ if (userCount === 0) {
+ store.delete({ ids: savedVectorIds });
+ void Promise.all(savedVectorIds.map((id) => c.env.KV.delete(id)));
+ return null;
+ }
+
+ return vector;
+ });
+
+ // If all vectors are null (deleted), we can delete the KV too. Otherwise, we update (upsert) the vectors.
+ if (newVectors.every((v) => v === null)) {
+ await c.env.KV.delete(uuid);
+ } else {
+ await c.env.VECTORIZE_INDEX.upsert(newVectors.filter((v) => v !== null));
+ }
+ }
}
function sanitizeKey(key: string): string {
- if (!key) throw new Error("Key cannot be empty");
+ if (!key) throw new Error("Key cannot be empty");
- // Remove or replace invalid characters
- let sanitizedKey = key.replace(/[.$"]/g, "_");
+ // Remove or replace invalid characters
+ let sanitizedKey = key.replace(/[.$"]/g, "_");
- // Ensure key does not start with $
- if (sanitizedKey.startsWith("$")) {
- sanitizedKey = sanitizedKey.substring(1);
- }
+ // Ensure key does not start with $
+ if (sanitizedKey.startsWith("$")) {
+ sanitizedKey = sanitizedKey.substring(1);
+ }
- return sanitizedKey;
+ return sanitizedKey;
}
export async function batchCreateChunksAndEmbeddings({
- store,
- body,
- chunks,
- context,
+ store,
+ body,
+ chunks,
+ context,
}: {
- store: CloudflareVectorizeStore;
- body: z.infer<typeof vectorObj>;
- chunks: string[];
- context: Context<{ Bindings: Env }>;
+ store: CloudflareVectorizeStore;
+ body: z.infer<typeof vectorObj>;
+ chunks: string[];
+ context: Context<{ Bindings: Env }>;
}) {
- //! NOTE that we use #supermemory-web to ensure that
- //! If a user saves it through the extension, we don't want other users to be able to see it.
- // Requests from the extension should ALWAYS have a unique ID with the USERiD in it.
- // I cannot stress this enough, important for security.
- const ourID = `${body.url}#supermemory-web`;
- const random = seededRandom(ourID);
- const uuid =
- random().toString(36).substring(2, 15) +
- random().toString(36).substring(2, 15);
-
- const allIds = await context.env.KV.list({ prefix: uuid });
-
- let pageContent = "";
- // If some chunks for that content already exist, we'll just update the metadata to include
- // the user.
- if (allIds.keys.length > 0) {
- const savedVectorIds = allIds.keys.map((key) => key.name);
- const vectors = await context.env.VECTORIZE_INDEX.getByIds(savedVectorIds);
-
- // Now, we'll update all vector metadatas with one more userId and all spaceIds
- const newVectors = vectors.map((vector) => {
- vector.metadata = {
- ...vector.metadata,
- [`user-${body.user}`]: 1,
-
- // For each space in body, add the spaceId to the vector metadata
- ...(body.spaces ?? [])?.reduce((acc, space) => {
- acc[`space-${body.user}-${space}`] = 1;
- return acc;
- }, {}),
- };
- const content =
- vector.metadata.content.toString().split("Content: ")[1] ||
- vector.metadata.content;
- pageContent += `<---chunkId: ${vector.id}\n${content}\n---->`;
- return vector;
- });
-
- await context.env.VECTORIZE_INDEX.upsert(newVectors);
- return pageContent; //Return the page content that goes to d1 db
- }
-
- for (let i = 0; i < chunks.length; i++) {
- const chunk = chunks[i];
- const chunkId = `${uuid}-${i}`;
-
- const newPageContent = `Title: ${body.title}\nDescription: ${body.description}\nURL: ${body.url}\nContent: ${chunk}`;
-
- const docs = await store.addDocuments(
- [
- {
- pageContent: newPageContent,
- metadata: {
- title: body.title?.slice(0, 50) ?? "",
- description: body.description ?? "",
- url: body.url,
- type: body.type ?? "page",
- content: newPageContent,
-
- [sanitizeKey(`user-${body.user}`)]: 1,
- ...body.spaces?.reduce((acc, space) => {
- acc[`space-${body.user}-${space}`] = 1;
- return acc;
- }, {}),
- },
- },
- ],
- {
- ids: [chunkId],
- },
- );
-
- console.log("Docs added: ", docs);
-
- await context.env.KV.put(chunkId, ourID);
- pageContent += `<---chunkId: ${chunkId}\n${chunk}\n---->`;
- }
- return pageContent; // Return the pageContent that goes to the d1 db
+ //! NOTE that we use #supermemory-web to ensure that
+ //! If a user saves it through the extension, we don't want other users to be able to see it.
+ // Requests from the extension should ALWAYS have a unique ID with the USERiD in it.
+ // I cannot stress this enough, important for security.
+ const ourID = `${body.url}#supermemory-web`;
+ const random = seededRandom(ourID);
+ const uuid =
+ random().toString(36).substring(2, 15) +
+ random().toString(36).substring(2, 15);
+
+ const allIds = await context.env.KV.list({ prefix: uuid });
+
+ let pageContent = "";
+ // If some chunks for that content already exist, we'll just update the metadata to include
+ // the user.
+ if (allIds.keys.length > 0) {
+ const savedVectorIds = allIds.keys.map((key) => key.name);
+ const vectors = await context.env.VECTORIZE_INDEX.getByIds(savedVectorIds);
+
+ // Now, we'll update all vector metadatas with one more userId and all spaceIds
+ const newVectors = vectors.map((vector) => {
+ vector.metadata = {
+ ...vector.metadata,
+ [`user-${body.user}`]: 1,
+
+ // For each space in body, add the spaceId to the vector metadata
+ ...(body.spaces ?? [])?.reduce((acc, space) => {
+ acc[`space-${body.user}-${space}`] = 1;
+ return acc;
+ }, {}),
+ };
+ const content =
+ vector.metadata.content.toString().split("Content: ")[1] ||
+ vector.metadata.content;
+ pageContent += `<---chunkId: ${vector.id}\n${content}\n---->`;
+ return vector;
+ });
+
+ await context.env.VECTORIZE_INDEX.upsert(newVectors);
+ return pageContent; //Return the page content that goes to d1 db
+ }
+
+ for (let i = 0; i < chunks.length; i++) {
+ const chunk = chunks[i];
+ const chunkId = `${uuid}-${i}`;
+
+ const newPageContent = `Title: ${body.title}\nDescription: ${body.description}\nURL: ${body.url}\nContent: ${chunk}`;
+
+ const docs = await store.addDocuments(
+ [
+ {
+ pageContent: newPageContent,
+ metadata: {
+ title: body.title?.slice(0, 50) ?? "",
+ description: body.description ?? "",
+ url: body.url,
+ type: body.type ?? "page",
+ content: newPageContent,
+
+ [sanitizeKey(`user-${body.user}`)]: 1,
+ ...body.spaces?.reduce((acc, space) => {
+ acc[`space-${body.user}-${space}`] = 1;
+ return acc;
+ }, {}),
+ },
+ },
+ ],
+ {
+ ids: [chunkId],
+ },
+ );
+
+ console.log("Docs added: ", docs);
+
+ await context.env.KV.put(chunkId, ourID);
+ pageContent += `<---chunkId: ${chunkId}\n${chunk}\n---->`;
+ }
+ return pageContent; // Return the pageContent that goes to the d1 db
}
diff --git a/apps/cf-ai-backend/src/index.ts b/apps/cf-ai-backend/src/index.ts
index 0844c22e..72b4d527 100644
--- a/apps/cf-ai-backend/src/index.ts
+++ b/apps/cf-ai-backend/src/index.ts
@@ -3,9 +3,9 @@ import { Hono } from "hono";
import { CoreMessage, generateText, streamText, tool } from "ai";
import { chatObj, Env, vectorObj } from "./types";
import {
- batchCreateChunksAndEmbeddings,
- deleteDocument,
- initQuery,
+ batchCreateChunksAndEmbeddings,
+ deleteDocument,
+ initQuery,
} from "./helper";
import { timing } from "hono/timing";
import { logger } from "hono/logger";
@@ -19,10 +19,10 @@ import { swaggerUI } from "@hono/swagger-ui";
const app = new Hono<{ Bindings: Env }>();
app.get(
- "/ui",
- swaggerUI({
- url: "/doc",
- }),
+ "/ui",
+ swaggerUI({
+ url: "/doc",
+ }),
);
// ------- MIDDLEWARES -------
@@ -31,345 +31,349 @@ app.use("*", timing());
app.use("*", logger());
app.use("/api/", async (c, next) => {
- if (c.env.NODE_ENV !== "development") {
- const auth = bearerAuth({ token: c.env.SECURITY_KEY });
- return auth(c, next);
- }
- return next();
+ if (c.env.NODE_ENV !== "development") {
+ const auth = bearerAuth({ token: c.env.SECURITY_KEY });
+ return auth(c, next);
+ }
+ return next();
});
// ------- MIDDLEWARES END -------
const fileSchema = z
- .instanceof(File)
- .refine(
- (file) => file.size <= 10 * 1024 * 1024,
- "File size should be less than 10MB",
- ) // Validate file size
- .refine(
- (file) => ["image/jpeg", "image/png", "image/gif"].includes(file.type),
- "Invalid file type",
- ); // Validate file type
+ .instanceof(File)
+ .refine(
+ (file) => file.size <= 10 * 1024 * 1024,
+ "File size should be less than 10MB",
+ ) // Validate file size
+ .refine(
+ (file) => ["image/jpeg", "image/png", "image/gif"].includes(file.type),
+ "Invalid file type",
+ ); // Validate file type
app.get("/", (c) => {
- return c.text("Supermemory backend API is running!");
+ return c.text("Supermemory backend API is running!");
});
app.get("/api/health", (c) => {
- return c.json({ status: "ok" });
+ return c.json({ status: "ok" });
});
app.post("/api/add", zValidator("json", vectorObj), async (c) => {
- const body = c.req.valid("json");
-
- const { store } = await initQuery(c);
-
- console.log(body.spaces);
- const chunks = chunkText(body.pageContent, 1536);
- if (chunks.length > 20) {
- return c.json({ status: "error", message: "We are unable to process documents this size just yet, try something smaller" });
- }
- const chunkedInput = await batchCreateChunksAndEmbeddings({
- store,
- body,
- chunks: chunks,
- context: c,
- });
-
- return c.json({ status: "ok", chunkedInput });
+ const body = c.req.valid("json");
+
+ const { store } = await initQuery(c);
+
+ console.log(body.spaces);
+ const chunks = chunkText(body.pageContent, 1536);
+ if (chunks.length > 20) {
+ return c.json({
+ status: "error",
+ message:
+ "We are unable to process documents this size just yet, try something smaller",
+ });
+ }
+ const chunkedInput = await batchCreateChunksAndEmbeddings({
+ store,
+ body,
+ chunks: chunks,
+ context: c,
+ });
+
+ return c.json({ status: "ok", chunkedInput });
});
app.post(
- "/api/add-with-image",
- zValidator(
- "form",
- z.object({
- images: z
- .array(fileSchema)
- .min(1, "At least one image is required")
- .optional(),
- "images[]": z
- .array(fileSchema)
- .min(1, "At least one image is required")
- .optional(),
- text: z.string().optional(),
- spaces: z.array(z.string()).optional(),
- url: z.string(),
- user: z.string(),
- }),
- (c) => {
- console.log(c);
- },
- ),
- async (c) => {
- const body = c.req.valid("form");
-
- const { store } = await initQuery(c);
-
- if (!(body.images || body["images[]"])) {
- return c.json({ status: "error", message: "No images found" }, 400);
- }
-
- const imagePromises = (body.images ?? body["images[]"]).map(
- async (image) => {
- const buffer = await image.arrayBuffer();
- const input = {
- image: [...new Uint8Array(buffer)],
- prompt:
- "What's in this image? caption everything you see in great detail. If it has text, do an OCR and extract all of it.",
- max_tokens: 1024,
- };
- const response = await c.env.AI.run(
- "@cf/llava-hf/llava-1.5-7b-hf",
- input,
- );
- console.log(response.description);
- return response.description;
- },
- );
-
- const imageDescriptions = await Promise.all(imagePromises);
-
- await batchCreateChunksAndEmbeddings({
- store,
- body: {
- url: body.url,
- user: body.user,
- type: "image",
- description:
- imageDescriptions.length > 1
- ? `A group of ${imageDescriptions.length} images on ${body.url}`
- : imageDescriptions[0],
- spaces: body.spaces,
- pageContent: imageDescriptions.join("\n"),
- title: "Image content from the web",
- },
- chunks: [
- imageDescriptions,
- ...(body.text ? chunkText(body.text, 1536) : []),
- ].flat(),
- context: c,
- });
-
- return c.json({ status: "ok" });
- },
+ "/api/add-with-image",
+ zValidator(
+ "form",
+ z.object({
+ images: z
+ .array(fileSchema)
+ .min(1, "At least one image is required")
+ .optional(),
+ "images[]": z
+ .array(fileSchema)
+ .min(1, "At least one image is required")
+ .optional(),
+ text: z.string().optional(),
+ spaces: z.array(z.string()).optional(),
+ url: z.string(),
+ user: z.string(),
+ }),
+ (c) => {
+ console.log(c);
+ },
+ ),
+ async (c) => {
+ const body = c.req.valid("form");
+
+ const { store } = await initQuery(c);
+
+ if (!(body.images || body["images[]"])) {
+ return c.json({ status: "error", message: "No images found" }, 400);
+ }
+
+ const imagePromises = (body.images ?? body["images[]"]).map(
+ async (image) => {
+ const buffer = await image.arrayBuffer();
+ const input = {
+ image: [...new Uint8Array(buffer)],
+ prompt:
+ "What's in this image? caption everything you see in great detail. If it has text, do an OCR and extract all of it.",
+ max_tokens: 1024,
+ };
+ const response = await c.env.AI.run(
+ "@cf/llava-hf/llava-1.5-7b-hf",
+ input,
+ );
+ console.log(response.description);
+ return response.description;
+ },
+ );
+
+ const imageDescriptions = await Promise.all(imagePromises);
+
+ await batchCreateChunksAndEmbeddings({
+ store,
+ body: {
+ url: body.url,
+ user: body.user,
+ type: "image",
+ description:
+ imageDescriptions.length > 1
+ ? `A group of ${imageDescriptions.length} images on ${body.url}`
+ : imageDescriptions[0],
+ spaces: body.spaces,
+ pageContent: imageDescriptions.join("\n"),
+ title: "Image content from the web",
+ },
+ chunks: [
+ imageDescriptions,
+ ...(body.text ? chunkText(body.text, 1536) : []),
+ ].flat(),
+ context: c,
+ });
+
+ return c.json({ status: "ok" });
+ },
);
app.get(
- "/api/ask",
- zValidator(
- "query",
- z.object({
- query: z.string(),
- }),
- ),
- async (c) => {
- const query = c.req.valid("query");
-
- const { model } = await initQuery(c);
-
- const response = await streamText({ model, prompt: query.query });
- const r = response.toTextStreamResponse();
-
- return r;
- },
+ "/api/ask",
+ zValidator(
+ "query",
+ z.object({
+ query: z.string(),
+ }),
+ ),
+ async (c) => {
+ const query = c.req.valid("query");
+
+ const { model } = await initQuery(c);
+
+ const response = await streamText({ model, prompt: query.query });
+ const r = response.toTextStreamResponse();
+
+ return r;
+ },
);
app.get(
- "/api/search",
- zValidator("query", z.object({ query: z.string(), user: z.string() })),
- async (c) => {
- const { query, user } = c.req.valid("query");
- const filter: VectorizeVectorMetadataFilter = {
- [`user-${user}`]: 1,
- };
-
- const { store } = await initQuery(c);
- const queryAsVector = await store.embeddings.embedQuery(query);
-
- const resp = await c.env.VECTORIZE_INDEX.query(queryAsVector, {
- topK: 5,
- filter,
- returnMetadata: true,
- });
-
- const minScore = Math.min(...resp.matches.map(({ score }) => score));
- const maxScore = Math.max(...resp.matches.map(({ score }) => score));
-
- // This entire chat part is basically just a dumb down version of the /api/chat endpoint.
- const normalizedData = resp.matches.map((data) => ({
- ...data,
- normalizedScore:
- maxScore !== minScore
- ? 1 + ((data.score - minScore) / (maxScore - minScore)) * 98
- : 50,
- }));
-
- const preparedContext = normalizedData.map(
- ({ metadata, score, normalizedScore }) => ({
- context: `Title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
- score,
- normalizedScore,
- }),
- );
-
- return c.json({
- status: "ok",
- response: preparedContext,
- });
- },
+ "/api/search",
+ zValidator("query", z.object({ query: z.string(), user: z.string() })),
+ async (c) => {
+ const { query, user } = c.req.valid("query");
+ const filter: VectorizeVectorMetadataFilter = {
+ [`user-${user}`]: 1,
+ };
+
+ const { store } = await initQuery(c);
+ const queryAsVector = await store.embeddings.embedQuery(query);
+
+ const resp = await c.env.VECTORIZE_INDEX.query(queryAsVector, {
+ topK: 5,
+ filter,
+ returnMetadata: true,
+ });
+
+ const minScore = Math.min(...resp.matches.map(({ score }) => score));
+ const maxScore = Math.max(...resp.matches.map(({ score }) => score));
+
+ // This entire chat part is basically just a dumb down version of the /api/chat endpoint.
+ const normalizedData = resp.matches.map((data) => ({
+ ...data,
+ normalizedScore:
+ maxScore !== minScore
+ ? 1 + ((data.score - minScore) / (maxScore - minScore)) * 98
+ : 50,
+ }));
+
+ const preparedContext = normalizedData.map(
+ ({ metadata, score, normalizedScore }) => ({
+ context: `Title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
+ score,
+ normalizedScore,
+ }),
+ );
+
+ return c.json({
+ status: "ok",
+ response: preparedContext,
+ });
+ },
);
// This is a special endpoint for our "chatbot-only" solutions.
// It does both - adding content AND chatting with it.
app.post(
- "/api/autoChatOrAdd",
- zValidator(
- "query",
- z.object({
- query: z.string(),
- user: z.string(),
- }),
- ),
- zValidator("json", chatObj),
- async (c) => {
- const { query, user } = c.req.valid("query");
- const { chatHistory } = c.req.valid("json");
-
- const { store, model } = await initQuery(c);
-
- let task: "add" | "chat" = "chat";
- let thingToAdd: "page" | "image" | "text" | undefined = undefined;
- let addContent: string | undefined = undefined;
-
- // This is a "router". this finds out if the user wants to add a document, or chat with the AI to get a response.
- const routerQuery = await generateText({
- model: model,
- system: `You are Supermemory chatbot. You can either add a document to the supermemory database, or return a chat response. Based on this query,
+ "/api/autoChatOrAdd",
+ zValidator(
+ "query",
+ z.object({
+ query: z.string(),
+ user: z.string(),
+ }),
+ ),
+ zValidator("json", chatObj),
+ async (c) => {
+ const { query, user } = c.req.valid("query");
+ const { chatHistory } = c.req.valid("json");
+
+ const { store, model } = await initQuery(c);
+
+ let task: "add" | "chat" = "chat";
+ let thingToAdd: "page" | "image" | "text" | undefined = undefined;
+ let addContent: string | undefined = undefined;
+
+ // This is a "router". this finds out if the user wants to add a document, or chat with the AI to get a response.
+ const routerQuery = await generateText({
+ model: model,
+ system: `You are Supermemory chatbot. You can either add a document to the supermemory database, or return a chat response. Based on this query,
You must determine what to do. Basically if it feels like a "question", then you should intiate a chat. If it feels like a "command" or feels like something that could be forwarded to the AI, then you should add a document.
You must also extract the "thing" to add and what type of thing it is.`,
- prompt: `Question from user: ${query}`,
- tools: {
- decideTask: tool({
- description:
- "Decide if the user wants to add a document or chat with the AI",
- parameters: z.object({
- generatedTask: z.enum(["add", "chat"]),
- contentToAdd: z.object({
- thing: z.enum(["page", "image", "text"]),
- content: z.string(),
- }),
- }),
- execute: async ({ generatedTask, contentToAdd }) => {
- task = generatedTask;
- thingToAdd = contentToAdd.thing;
- addContent = contentToAdd.content;
- },
- }),
- },
- });
-
- if ((task as string) === "add") {
- // addString is the plaintext string that the user wants to add to the database
- let addString: string = addContent;
-
- if (thingToAdd === "page") {
- // TODO: Sometimes this query hangs, and errors out. we need to do proper error management here.
- const response = await fetch("https://md.dhr.wtf/?url=" + addContent, {
- headers: {
- Authorization: "Bearer " + c.env.SECURITY_KEY,
- },
- });
-
- addString = await response.text();
- }
-
- // At this point, we can just go ahead and create the embeddings!
- await batchCreateChunksAndEmbeddings({
- store,
- body: {
- url: addContent,
- user,
- type: thingToAdd,
- pageContent: addString,
- title: `${addString.slice(0, 30)}... (Added from chatbot)`,
- },
- chunks: chunkText(addString, 1536),
- context: c,
- });
-
- return c.json({
- status: "ok",
- response:
- "I added the document to your personal second brain! You can now use it to answer questions or chat with me.",
- contentAdded: {
- type: thingToAdd,
- content: addString,
- url:
- thingToAdd === "page"
- ? addContent
- : `https://supermemory.ai/note/${Date.now()}`,
- },
- });
- } else {
- const filter: VectorizeVectorMetadataFilter = {
- [`user-${user}`]: 1,
- };
-
- const queryAsVector = await store.embeddings.embedQuery(query);
-
- const resp = await c.env.VECTORIZE_INDEX.query(queryAsVector, {
- topK: 5,
- filter,
- returnMetadata: true,
- });
-
- const minScore = Math.min(...resp.matches.map(({ score }) => score));
- const maxScore = Math.max(...resp.matches.map(({ score }) => score));
-
- // This entire chat part is basically just a dumb down version of the /api/chat endpoint.
- const normalizedData = resp.matches.map((data) => ({
- ...data,
- normalizedScore:
- maxScore !== minScore
- ? 1 + ((data.score - minScore) / (maxScore - minScore)) * 98
- : 50,
- }));
-
- const preparedContext = normalizedData.map(
- ({ metadata, score, normalizedScore }) => ({
- context: `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
- score,
- normalizedScore,
- }),
- );
-
- const prompt = template({
- contexts: preparedContext,
- question: query,
- });
-
- const initialMessages: CoreMessage[] = [
- {
- role: "system",
- content: `You are an AI chatbot called "Supermemory.ai". When asked a question by a user, you must take all the context provided to you and give a good, small, but helpful response.`,
- },
- { role: "assistant", content: "Hello, how can I help?" },
- ];
-
- const userMessage: CoreMessage = { role: "user", content: prompt };
-
- const response = await generateText({
- model,
- messages: [
- ...initialMessages,
- ...((chatHistory || []) as CoreMessage[]),
- userMessage,
- ],
- });
-
- return c.json({ status: "ok", response: response.text });
- }
- },
+ prompt: `Question from user: ${query}`,
+ tools: {
+ decideTask: tool({
+ description:
+ "Decide if the user wants to add a document or chat with the AI",
+ parameters: z.object({
+ generatedTask: z.enum(["add", "chat"]),
+ contentToAdd: z.object({
+ thing: z.enum(["page", "image", "text"]),
+ content: z.string(),
+ }),
+ }),
+ execute: async ({ generatedTask, contentToAdd }) => {
+ task = generatedTask;
+ thingToAdd = contentToAdd.thing;
+ addContent = contentToAdd.content;
+ },
+ }),
+ },
+ });
+
+ if ((task as string) === "add") {
+ // addString is the plaintext string that the user wants to add to the database
+ let addString: string = addContent;
+
+ if (thingToAdd === "page") {
+ // TODO: Sometimes this query hangs, and errors out. we need to do proper error management here.
+ const response = await fetch("https://md.dhr.wtf/?url=" + addContent, {
+ headers: {
+ Authorization: "Bearer " + c.env.SECURITY_KEY,
+ },
+ });
+
+ addString = await response.text();
+ }
+
+ // At this point, we can just go ahead and create the embeddings!
+ await batchCreateChunksAndEmbeddings({
+ store,
+ body: {
+ url: addContent,
+ user,
+ type: thingToAdd,
+ pageContent: addString,
+ title: `${addString.slice(0, 30)}... (Added from chatbot)`,
+ },
+ chunks: chunkText(addString, 1536),
+ context: c,
+ });
+
+ return c.json({
+ status: "ok",
+ response:
+ "I added the document to your personal second brain! You can now use it to answer questions or chat with me.",
+ contentAdded: {
+ type: thingToAdd,
+ content: addString,
+ url:
+ thingToAdd === "page"
+ ? addContent
+ : `https://supermemory.ai/note/${Date.now()}`,
+ },
+ });
+ } else {
+ const filter: VectorizeVectorMetadataFilter = {
+ [`user-${user}`]: 1,
+ };
+
+ const queryAsVector = await store.embeddings.embedQuery(query);
+
+ const resp = await c.env.VECTORIZE_INDEX.query(queryAsVector, {
+ topK: 5,
+ filter,
+ returnMetadata: true,
+ });
+
+ const minScore = Math.min(...resp.matches.map(({ score }) => score));
+ const maxScore = Math.max(...resp.matches.map(({ score }) => score));
+
+ // This entire chat part is basically just a dumb down version of the /api/chat endpoint.
+ const normalizedData = resp.matches.map((data) => ({
+ ...data,
+ normalizedScore:
+ maxScore !== minScore
+ ? 1 + ((data.score - minScore) / (maxScore - minScore)) * 98
+ : 50,
+ }));
+
+ const preparedContext = normalizedData.map(
+ ({ metadata, score, normalizedScore }) => ({
+ context: `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
+ score,
+ normalizedScore,
+ }),
+ );
+
+ const prompt = template({
+ contexts: preparedContext,
+ question: query,
+ });
+
+ const initialMessages: CoreMessage[] = [
+ {
+ role: "system",
+ content: `You are an AI chatbot called "Supermemory.ai". When asked a question by a user, you must take all the context provided to you and give a good, small, but helpful response.`,
+ },
+ { role: "assistant", content: "Hello, how can I help?" },
+ ];
+
+ const userMessage: CoreMessage = { role: "user", content: prompt };
+
+ const response = await generateText({
+ model,
+ messages: [
+ ...initialMessages,
+ ...((chatHistory || []) as CoreMessage[]),
+ userMessage,
+ ],
+ });
+
+ return c.json({ status: "ok", response: response.text });
+ }
+ },
);
/* TODO: Eventually, we should not have to save each user's content in a seperate vector.
@@ -378,195 +382,195 @@ But, it's not scalable *enough*. How can we store the same vectors for the same
Hard problem to solve, Vectorize doesn't have an OR filter, so we can't just filter by URL and user.
*/
app.post(
- "/api/chat",
- zValidator(
- "query",
- z.object({
- query: z.string(),
- user: z.string(),
- topK: z.number().optional().default(10),
- spaces: z.string().optional(),
- sourcesOnly: z.string().optional().default("false"),
- model: z.string().optional().default("gpt-4o"),
- }),
- ),
- zValidator("json", chatObj),
- async (c) => {
- const query = c.req.valid("query");
- const body = c.req.valid("json");
-
- const sourcesOnly = query.sourcesOnly === "true";
-
- // Return early for dumb requests
- if (sourcesOnly && body.sources) {
- return c.json(body.sources);
- }
-
- const spaces = query.spaces?.split(",") ?? [undefined];
- console.log(spaces);
-
- // Get the AI model maker and vector store
- const { model, store } = await initQuery(c, query.model);
-
- if (!body.sources) {
- const filter: VectorizeVectorMetadataFilter = {
- [`user-${query.user}`]: 1,
- };
- console.log("Spaces", spaces);
-
- // Converting the query to a vector so that we can search for similar vectors
- const queryAsVector = await store.embeddings.embedQuery(query.query);
- const responses: VectorizeMatches = { matches: [], count: 0 };
-
- console.log("hello world", spaces);
-
- // SLICED to 5 to avoid too many queries
- for (const space of spaces.slice(0, 5)) {
- if (space && space.length >= 1) {
- // it's possible for space list to be [undefined] so we only add space filter conditionally
- filter[`space-${query.user}-${space}`] = 1;
- }
-
- // Because there's no OR operator in the filter, we have to make multiple queries
- const resp = await c.env.VECTORIZE_INDEX.query(queryAsVector, {
- topK: query.topK,
- filter,
- returnMetadata: true,
- });
-
- // Basically recreating the response object
- if (resp.count > 0) {
- responses.matches.push(...resp.matches);
- responses.count += resp.count;
- }
- }
-
- const minScore = Math.min(...responses.matches.map(({ score }) => score));
- const maxScore = Math.max(...responses.matches.map(({ score }) => score));
-
- // We are "normalising" the scores - if all of them are on top, we want to make sure that
- // we have a way to filter out the noise.
- const normalizedData = responses.matches.map((data) => ({
- ...data,
- normalizedScore:
- maxScore !== minScore
- ? 1 + ((data.score - minScore) / (maxScore - minScore)) * 98
- : 50, // If all scores are the same, set them to the middle of the scale
- }));
-
- let highScoreData = normalizedData.filter(
- ({ normalizedScore }) => normalizedScore > 50,
- );
-
- // If the normalsation is not done properly, we have a fallback to just get the
- // top 3 scores
- if (highScoreData.length === 0) {
- highScoreData = normalizedData
- .sort((a, b) => b.score - a.score)
- .slice(0, 3);
- }
-
- const sortedHighScoreData = highScoreData.sort(
- (a, b) => b.normalizedScore - a.normalizedScore,
- );
-
- body.sources = {
- normalizedData,
- };
-
- // So this is kinda hacky, but the frontend needs to do 2 calls to get sources and chat.
- // I think this is fine for now, but we can improve this later.
- if (sourcesOnly) {
- const idsAsStrings = sortedHighScoreData.map((dataPoint) =>
- dataPoint.id.toString(),
- );
-
- const storedContent = await Promise.all(
- idsAsStrings.map(async (id) => await c.env.KV.get(id)),
- );
-
- const metadata = normalizedData.map((datapoint) => datapoint.metadata);
-
- return c.json({ ids: storedContent, metadata, normalizedData });
- }
- }
-
- const preparedContext = body.sources.normalizedData.map(
- ({ metadata, score, normalizedScore }) => ({
- context: `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
- score,
- normalizedScore,
- }),
- );
-
- const initialMessages: CoreMessage[] = [
- { role: "user", content: systemPrompt },
- { role: "assistant", content: "Hello, how can I help?" },
- ];
-
- const prompt = template({
- contexts: preparedContext,
- question: query.query,
- });
-
- const userMessage: CoreMessage = { role: "user", content: prompt };
-
- const response = await streamText({
- model: model,
- messages: [
- ...initialMessages,
- ...((body.chatHistory || []) as CoreMessage[]),
- userMessage,
- ],
- // temperature: 0.4,
- });
-
- return response.toTextStreamResponse();
- },
+ "/api/chat",
+ zValidator(
+ "query",
+ z.object({
+ query: z.string(),
+ user: z.string(),
+ topK: z.number().optional().default(10),
+ spaces: z.string().optional(),
+ sourcesOnly: z.string().optional().default("false"),
+ model: z.string().optional().default("gpt-4o"),
+ }),
+ ),
+ zValidator("json", chatObj),
+ async (c) => {
+ const query = c.req.valid("query");
+ const body = c.req.valid("json");
+
+ const sourcesOnly = query.sourcesOnly === "true";
+
+ // Return early for dumb requests
+ if (sourcesOnly && body.sources) {
+ return c.json(body.sources);
+ }
+
+ const spaces = query.spaces?.split(",") ?? [undefined];
+ console.log(spaces);
+
+ // Get the AI model maker and vector store
+ const { model, store } = await initQuery(c, query.model);
+
+ if (!body.sources) {
+ const filter: VectorizeVectorMetadataFilter = {
+ [`user-${query.user}`]: 1,
+ };
+ console.log("Spaces", spaces);
+
+ // Converting the query to a vector so that we can search for similar vectors
+ const queryAsVector = await store.embeddings.embedQuery(query.query);
+ const responses: VectorizeMatches = { matches: [], count: 0 };
+
+ console.log("hello world", spaces);
+
+ // SLICED to 5 to avoid too many queries
+ for (const space of spaces.slice(0, 5)) {
+ if (space && space.length >= 1) {
+ // it's possible for space list to be [undefined] so we only add space filter conditionally
+ filter[`space-${query.user}-${space}`] = 1;
+ }
+
+ // Because there's no OR operator in the filter, we have to make multiple queries
+ const resp = await c.env.VECTORIZE_INDEX.query(queryAsVector, {
+ topK: query.topK,
+ filter,
+ returnMetadata: true,
+ });
+
+ // Basically recreating the response object
+ if (resp.count > 0) {
+ responses.matches.push(...resp.matches);
+ responses.count += resp.count;
+ }
+ }
+
+ const minScore = Math.min(...responses.matches.map(({ score }) => score));
+ const maxScore = Math.max(...responses.matches.map(({ score }) => score));
+
+ // We are "normalising" the scores - if all of them are on top, we want to make sure that
+ // we have a way to filter out the noise.
+ const normalizedData = responses.matches.map((data) => ({
+ ...data,
+ normalizedScore:
+ maxScore !== minScore
+ ? 1 + ((data.score - minScore) / (maxScore - minScore)) * 98
+ : 50, // If all scores are the same, set them to the middle of the scale
+ }));
+
+ let highScoreData = normalizedData.filter(
+ ({ normalizedScore }) => normalizedScore > 50,
+ );
+
+ // If the normalsation is not done properly, we have a fallback to just get the
+ // top 3 scores
+ if (highScoreData.length === 0) {
+ highScoreData = normalizedData
+ .sort((a, b) => b.score - a.score)
+ .slice(0, 3);
+ }
+
+ const sortedHighScoreData = highScoreData.sort(
+ (a, b) => b.normalizedScore - a.normalizedScore,
+ );
+
+ body.sources = {
+ normalizedData,
+ };
+
+ // So this is kinda hacky, but the frontend needs to do 2 calls to get sources and chat.
+ // I think this is fine for now, but we can improve this later.
+ if (sourcesOnly) {
+ const idsAsStrings = sortedHighScoreData.map((dataPoint) =>
+ dataPoint.id.toString(),
+ );
+
+ const storedContent = await Promise.all(
+ idsAsStrings.map(async (id) => await c.env.KV.get(id)),
+ );
+
+ const metadata = normalizedData.map((datapoint) => datapoint.metadata);
+
+ return c.json({ ids: storedContent, metadata, normalizedData });
+ }
+ }
+
+ const preparedContext = body.sources.normalizedData.map(
+ ({ metadata, score, normalizedScore }) => ({
+ context: `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
+ score,
+ normalizedScore,
+ }),
+ );
+
+ const initialMessages: CoreMessage[] = [
+ { role: "user", content: systemPrompt },
+ { role: "assistant", content: "Hello, how can I help?" },
+ ];
+
+ const prompt = template({
+ contexts: preparedContext,
+ question: query.query,
+ });
+
+ const userMessage: CoreMessage = { role: "user", content: prompt };
+
+ const response = await streamText({
+ model: model,
+ messages: [
+ ...initialMessages,
+ ...((body.chatHistory || []) as CoreMessage[]),
+ userMessage,
+ ],
+ // temperature: 0.4,
+ });
+
+ return response.toTextStreamResponse();
+ },
);
app.delete(
- "/api/delete",
- zValidator(
- "query",
- z.object({
- websiteUrl: z.string(),
- user: z.string(),
- }),
- ),
- async (c) => {
- const { websiteUrl, user } = c.req.valid("query");
-
- const { store } = await initQuery(c);
-
- await deleteDocument({ url: websiteUrl, user, c, store });
-
- return c.json({ message: "Document deleted" });
- },
+ "/api/delete",
+ zValidator(
+ "query",
+ z.object({
+ websiteUrl: z.string(),
+ user: z.string(),
+ }),
+ ),
+ async (c) => {
+ const { websiteUrl, user } = c.req.valid("query");
+
+ const { store } = await initQuery(c);
+
+ await deleteDocument({ url: websiteUrl, user, c, store });
+
+ return c.json({ message: "Document deleted" });
+ },
);
// ERROR #1 - this is the api that the editor uses, it is just a scrape off of /api/chat so you may check that out
app.get(
- "/api/editorai",
- zValidator(
- "query",
- z.object({
- context: z.string(),
- request: z.string(),
- }),
- ),
- async (c) => {
- const { context, request } = c.req.valid("query");
- const { model } = await initQuery(c);
-
- const response = await streamText({
- model,
- prompt: `${request}-${context}`,
- maxTokens: 224,
- });
-
- return response.toTextStreamResponse();
- },
+ "/api/editorai",
+ zValidator(
+ "query",
+ z.object({
+ context: z.string(),
+ request: z.string(),
+ }),
+ ),
+ async (c) => {
+ const { context, request } = c.req.valid("query");
+ const { model } = await initQuery(c);
+
+ const response = await streamText({
+ model,
+ prompt: `${request}-${context}`,
+ maxTokens: 224,
+ });
+
+ return response.toTextStreamResponse();
+ },
);
export default app;
diff --git a/apps/cf-ai-backend/src/prompts/prompt1.ts b/apps/cf-ai-backend/src/prompts/prompt1.ts
index e52fe2cd..e311d575 100644
--- a/apps/cf-ai-backend/src/prompts/prompt1.ts
+++ b/apps/cf-ai-backend/src/prompts/prompt1.ts
@@ -15,19 +15,19 @@ To generate your answer:
If no context is provided, introduce yourself and explain that the user can save content which will allow you to answer questions about that content in the future. Do not provide an answer if no context is provided.`;
export const template = ({ contexts, question }) => {
- // Map over contexts to generate the context and score parts
- const contextParts = contexts
- .map(
- ({ context, normalisedScore }) => `
+ // Map over contexts to generate the context and score parts
+ const contextParts = contexts
+ .map(
+ ({ context, normalisedScore }) => `
${context ? `<context> ${context} </context>` : ""}
${normalisedScore ? `<context_score> normalisedScore: ${normalisedScore} </context_score>` : ""}`,
- )
- .join("\n");
+ )
+ .join("\n");
- // Construct the final prompt using a template literal
- const finalPrompt = `
+ // Construct the final prompt using a template literal
+ const finalPrompt = `
Here's the given context and question for the task:
${contextParts}
@@ -38,5 +38,5 @@ export const template = ({ contexts, question }) => {
</question>
`;
- return finalPrompt.trim();
+ return finalPrompt.trim();
};
diff --git a/apps/cf-ai-backend/src/types.ts b/apps/cf-ai-backend/src/types.ts
index dc97777c..4db568a1 100644
--- a/apps/cf-ai-backend/src/types.ts
+++ b/apps/cf-ai-backend/src/types.ts
@@ -2,51 +2,51 @@ import { sourcesZod } from "@repo/shared-types";
import { z } from "zod";
export type Env = {
- VECTORIZE_INDEX: VectorizeIndex;
- AI: Ai;
- SECURITY_KEY: string;
- OPENAI_API_KEY: string;
- GOOGLE_AI_API_KEY: string;
- MY_QUEUE: Queue<TweetData[]>;
- KV: KVNamespace;
- MYBROWSER: unknown;
- ANTHROPIC_API_KEY: string;
- NODE_ENV: string;
+ VECTORIZE_INDEX: VectorizeIndex;
+ AI: Ai;
+ SECURITY_KEY: string;
+ OPENAI_API_KEY: string;
+ GOOGLE_AI_API_KEY: string;
+ MY_QUEUE: Queue<TweetData[]>;
+ KV: KVNamespace;
+ MYBROWSER: unknown;
+ ANTHROPIC_API_KEY: string;
+ NODE_ENV: string;
};
export interface TweetData {
- tweetText: string;
- postUrl: string;
- authorName: string;
- handle: string;
- time: string;
- saveToUser: string;
+ tweetText: string;
+ postUrl: string;
+ authorName: string;
+ handle: string;
+ time: string;
+ saveToUser: string;
}
export const contentObj = z.object({
- role: z.string(),
- parts: z
- .array(
- z.object({
- text: z.string(),
- }),
- )
- .transform((val) => val.map((v) => v.text))
- .optional(),
- content: z.string().optional(),
+ role: z.string(),
+ parts: z
+ .array(
+ z.object({
+ text: z.string(),
+ }),
+ )
+ .transform((val) => val.map((v) => v.text))
+ .optional(),
+ content: z.string().optional(),
});
export const chatObj = z.object({
- chatHistory: z.array(contentObj).optional(),
- sources: sourcesZod.optional(),
+ chatHistory: z.array(contentObj).optional(),
+ sources: sourcesZod.optional(),
});
export const vectorObj = z.object({
- pageContent: z.string(),
- title: z.string().optional(),
- description: z.string().optional(),
- spaces: z.array(z.string()).optional(),
- url: z.string(),
- user: z.string(),
- type: z.string().optional().default("page"),
+ pageContent: z.string(),
+ title: z.string().optional(),
+ description: z.string().optional(),
+ spaces: z.array(z.string()).optional(),
+ url: z.string(),
+ user: z.string(),
+ type: z.string().optional().default("page"),
});
diff --git a/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts b/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts
index 8364ed0d..4a6ff593 100644
--- a/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts
+++ b/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts
@@ -1,58 +1,58 @@
import { z } from "zod";
interface OpenAIEmbeddingsParams {
- apiKey: string;
- modelName: string;
+ apiKey: string;
+ modelName: string;
}
export class OpenAIEmbeddings {
- private apiKey: string;
- private modelName: string;
-
- constructor({ apiKey, modelName }: OpenAIEmbeddingsParams) {
- this.apiKey = apiKey;
- this.modelName = modelName;
- }
-
- async embedDocuments(texts: string[]): Promise<number[][]> {
- const responses = await Promise.all(
- texts.map((text) => this.embedQuery(text)),
- );
- return responses;
- }
-
- async embedQuery(text: string): Promise<number[]> {
- 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,
- }),
- },
- );
-
- const data = await response.json();
-
- const zodTypeExpected = z.object({
- data: z.array(
- z.object({
- embedding: z.array(z.number()),
- }),
- ),
- });
-
- const json = zodTypeExpected.safeParse(data);
-
- if (!json.success) {
- throw new Error("Invalid response from OpenAI: " + json.error.message);
- }
-
- return json.data.data[0].embedding;
- }
+ private apiKey: string;
+ private modelName: string;
+
+ constructor({ apiKey, modelName }: OpenAIEmbeddingsParams) {
+ this.apiKey = apiKey;
+ this.modelName = modelName;
+ }
+
+ async embedDocuments(texts: string[]): Promise<number[][]> {
+ const responses = await Promise.all(
+ texts.map((text) => this.embedQuery(text)),
+ );
+ return responses;
+ }
+
+ async embedQuery(text: string): Promise<number[]> {
+ 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,
+ }),
+ },
+ );
+
+ const data = await response.json();
+
+ const zodTypeExpected = z.object({
+ data: z.array(
+ z.object({
+ embedding: z.array(z.number()),
+ }),
+ ),
+ });
+
+ const json = zodTypeExpected.safeParse(data);
+
+ if (!json.success) {
+ throw new Error("Invalid response from OpenAI: " + json.error.message);
+ }
+
+ return json.data.data[0].embedding;
+ }
}
diff --git a/apps/cf-ai-backend/src/utils/chonker.ts b/apps/cf-ai-backend/src/utils/chonker.ts
index c63020be..18788dab 100644
--- a/apps/cf-ai-backend/src/utils/chonker.ts
+++ b/apps/cf-ai-backend/src/utils/chonker.ts
@@ -4,44 +4,44 @@ import nlp from "compromise";
* Split text into chunks of specified max size with some overlap for continuity.
*/
export default function chunkText(
- text: string,
- maxChunkSize: number,
- overlap: number = 0.2,
+ text: string,
+ maxChunkSize: number,
+ overlap: number = 0.2,
): string[] {
- const sentences = nlp(text).sentences().out("array");
- const chunks = [];
- let currentChunk: string[] = [];
- let currentSize = 0;
+ const sentences = nlp(text).sentences().out("array");
+ const chunks = [];
+ let currentChunk: string[] = [];
+ let currentSize = 0;
- for (let i = 0; i < sentences.length; i++) {
- const sentence = sentences[i];
- currentChunk.push(sentence);
- currentSize += sentence.length;
+ for (let i = 0; i < sentences.length; i++) {
+ const sentence = sentences[i];
+ currentChunk.push(sentence);
+ currentSize += sentence.length;
- if (currentSize >= maxChunkSize) {
- // Calculate overlap
- const overlapSize = Math.floor(currentChunk.length * overlap);
- const chunkText = currentChunk.join(" ");
- chunks.push({
- text: chunkText,
- start: i - currentChunk.length + 1,
- end: i,
- });
+ if (currentSize >= maxChunkSize) {
+ // Calculate overlap
+ const overlapSize = Math.floor(currentChunk.length * overlap);
+ const chunkText = currentChunk.join(" ");
+ chunks.push({
+ text: chunkText,
+ start: i - currentChunk.length + 1,
+ end: i,
+ });
- // Prepare the next chunk with overlap
- currentChunk = currentChunk.slice(-overlapSize);
- currentSize = currentChunk.reduce((sum, s) => sum + s.length, 0);
- }
- }
+ // Prepare the next chunk with overlap
+ currentChunk = currentChunk.slice(-overlapSize);
+ currentSize = currentChunk.reduce((sum, s) => sum + s.length, 0);
+ }
+ }
- if (currentChunk.length > 0) {
- const chunkText = currentChunk.join(" ");
- chunks.push({
- text: chunkText,
- start: sentences.length - currentChunk.length,
- end: sentences.length,
- });
- }
+ if (currentChunk.length > 0) {
+ const chunkText = currentChunk.join(" ");
+ chunks.push({
+ text: chunkText,
+ start: sentences.length - currentChunk.length,
+ end: sentences.length,
+ });
+ }
- return chunks.map((chunk) => chunk.text);
+ return chunks.map((chunk) => chunk.text);
}
diff --git a/apps/cf-ai-backend/src/utils/seededRandom.ts b/apps/cf-ai-backend/src/utils/seededRandom.ts
index 9e315ee8..3e41a53f 100644
--- a/apps/cf-ai-backend/src/utils/seededRandom.ts
+++ b/apps/cf-ai-backend/src/utils/seededRandom.ts
@@ -5,21 +5,21 @@ import { MersenneTwister19937, integer } from "random-js";
* @param {string} seed - The input string to hash.
*/
function hashString(seed: string) {
- let hash = 0;
- for (let i = 0; i < seed.length; i++) {
- const char = seed.charCodeAt(i);
- hash = (hash << 5) - hash + char;
- hash |= 0; // Convert to 32bit integer
- }
- return hash;
+ let hash = 0;
+ for (let i = 0; i < seed.length; i++) {
+ const char = seed.charCodeAt(i);
+ hash = (hash << 5) - hash + char;
+ hash |= 0; // Convert to 32bit integer
+ }
+ return hash;
}
/**
* returns a funtion that generates same sequence of random numbers for a given seed between 0 and 1.
*/
export function seededRandom(seed: string) {
- const seedHash = hashString(seed);
- const engine = MersenneTwister19937.seed(seedHash);
- return () =>
- integer(0, Number.MAX_SAFE_INTEGER)(engine) / Number.MAX_SAFE_INTEGER;
+ const seedHash = hashString(seed);
+ const engine = MersenneTwister19937.seed(seedHash);
+ return () =>
+ integer(0, Number.MAX_SAFE_INTEGER)(engine) / Number.MAX_SAFE_INTEGER;
}
diff --git a/apps/cf-ai-backend/tsconfig.json b/apps/cf-ai-backend/tsconfig.json
index fcdf6914..323a4de4 100644
--- a/apps/cf-ai-backend/tsconfig.json
+++ b/apps/cf-ai-backend/tsconfig.json
@@ -1,7 +1,7 @@
{
- "compilerOptions": {
- "lib": ["ES2020"],
- "types": ["@cloudflare/workers-types"],
- "downlevelIteration": true
- }
+ "compilerOptions": {
+ "lib": ["ES2020"],
+ "types": ["@cloudflare/workers-types"],
+ "downlevelIteration": true
+ }
}
diff --git a/apps/cf-ai-backend/vite.config.ts b/apps/cf-ai-backend/vite.config.ts
index b7647b0d..06136158 100644
--- a/apps/cf-ai-backend/vite.config.ts
+++ b/apps/cf-ai-backend/vite.config.ts
@@ -2,5 +2,5 @@ import { defineConfig } from "vite";
import honox from "honox/vite";
export default defineConfig({
- plugins: [honox()],
+ plugins: [honox()],
});
diff --git a/apps/extension/background.ts b/apps/extension/background.ts
index 406493f1..2641f93d 100644
--- a/apps/extension/background.ts
+++ b/apps/extension/background.ts
@@ -2,7 +2,7 @@ import { Tweet } from "react-tweet/api";
import { features, transformTweetData } from "./helpers";
const tweetToMd = (tweet: Tweet) => {
- return `Tweet from @${tweet.user?.name ?? tweet.user?.screen_name ?? "Unknown"}
+ return `Tweet from @${tweet.user?.name ?? tweet.user?.screen_name ?? "Unknown"}
${tweet.text}
Images: ${tweet.photos ? tweet.photos.map((photo) => photo.url).join(", ") : "none"}
@@ -19,478 +19,478 @@ const BACKEND_URL = "https://supermemory.ai";
let lastTwitterFetch = 0;
const batchImportAll = async (cursor = "", totalImported = 0) => {
- chrome.storage.session.get(["cookie", "csrf", "auth"], (result) => {
- if (!result.cookie || !result.csrf || !result.auth) {
- console.log("cookie, csrf, or auth is missing");
- return;
- }
-
- const myHeaders = new Headers();
- myHeaders.append("Cookie", result.cookie);
- myHeaders.append("X-Csrf-token", result.csrf);
- myHeaders.append("Authorization", result.auth);
-
- const requestOptions: RequestInit = {
- method: "GET",
- headers: myHeaders,
- redirect: "follow",
- };
-
- const variables = {
- count: 100,
- cursor: cursor,
- includePromotedContent: false,
- };
-
- const urlWithCursor = cursor
- ? `${BOOKMARKS_URL}&variables=${encodeURIComponent(JSON.stringify(variables))}`
- : BOOKMARKS_URL;
-
- fetch(urlWithCursor, requestOptions)
- .then((response) => response.json())
- .then((data) => {
- const tweets = getAllTweets(data);
- let importedCount = 0;
-
- for (const tweet of tweets) {
- console.log(tweet);
-
- const tweetMd = tweetToMd(tweet);
- (async () => {
- chrome.storage.local.get(["jwt"], ({ jwt }) => {
- if (!jwt) {
- console.error("No JWT found");
- return;
- }
- fetch(`${BACKEND_URL}/api/store`, {
- method: "POST",
- headers: {
- Authorization: `Bearer ${jwt}`,
- },
- body: JSON.stringify({
- pageContent: tweetMd,
- url: `https://twitter.com/supermemoryai/status/${tweet.id_str}`,
- title: `Tweet by ${tweet.user.name}`,
- description: tweet.text.slice(0, 200),
- type: "tweet",
- }),
- }).then(async (ers) => {
- console.log(ers.status);
- 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,
- });
- }
- },
- );
- });
- });
- })();
- }
-
- console.log("tweets", tweets);
- console.log("data", data);
-
- const instructions =
- data.data?.bookmark_timeline_v2?.timeline?.instructions;
- const lastInstruction = instructions?.[0].entries.pop();
-
- if (lastInstruction?.entryId.startsWith("cursor-bottom-")) {
- let nextCursor = lastInstruction?.content?.value;
-
- if (!nextCursor) {
- for (let i = instructions.length - 1; i >= 0; i--) {
- if (instructions[i].entryId.startsWith("cursor-bottom-")) {
- nextCursor = instructions[i].content.value;
- break;
- }
- }
- }
-
- if (nextCursor) {
- batchImportAll(nextCursor, totalImported); // Recursively call with new cursor
- } else {
- console.log("All bookmarks imported");
-
- 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.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));
- });
+ chrome.storage.session.get(["cookie", "csrf", "auth"], (result) => {
+ if (!result.cookie || !result.csrf || !result.auth) {
+ console.log("cookie, csrf, or auth is missing");
+ return;
+ }
+
+ const myHeaders = new Headers();
+ myHeaders.append("Cookie", result.cookie);
+ myHeaders.append("X-Csrf-token", result.csrf);
+ myHeaders.append("Authorization", result.auth);
+
+ const requestOptions: RequestInit = {
+ method: "GET",
+ headers: myHeaders,
+ redirect: "follow",
+ };
+
+ const variables = {
+ count: 100,
+ cursor: cursor,
+ includePromotedContent: false,
+ };
+
+ const urlWithCursor = cursor
+ ? `${BOOKMARKS_URL}&variables=${encodeURIComponent(JSON.stringify(variables))}`
+ : BOOKMARKS_URL;
+
+ fetch(urlWithCursor, requestOptions)
+ .then((response) => response.json())
+ .then((data) => {
+ const tweets = getAllTweets(data);
+ let importedCount = 0;
+
+ for (const tweet of tweets) {
+ console.log(tweet);
+
+ const tweetMd = tweetToMd(tweet);
+ (async () => {
+ chrome.storage.local.get(["jwt"], ({ jwt }) => {
+ if (!jwt) {
+ console.error("No JWT found");
+ return;
+ }
+ fetch(`${BACKEND_URL}/api/store`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${jwt}`,
+ },
+ body: JSON.stringify({
+ pageContent: tweetMd,
+ url: `https://twitter.com/supermemoryai/status/${tweet.id_str}`,
+ title: `Tweet by ${tweet.user.name}`,
+ description: tweet.text.slice(0, 200),
+ type: "tweet",
+ }),
+ }).then(async (ers) => {
+ console.log(ers.status);
+ 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,
+ });
+ }
+ },
+ );
+ });
+ });
+ })();
+ }
+
+ console.log("tweets", tweets);
+ console.log("data", data);
+
+ const instructions =
+ data.data?.bookmark_timeline_v2?.timeline?.instructions;
+ const lastInstruction = instructions?.[0].entries.pop();
+
+ if (lastInstruction?.entryId.startsWith("cursor-bottom-")) {
+ let nextCursor = lastInstruction?.content?.value;
+
+ if (!nextCursor) {
+ for (let i = instructions.length - 1; i >= 0; i--) {
+ if (instructions[i].entryId.startsWith("cursor-bottom-")) {
+ nextCursor = instructions[i].content.value;
+ break;
+ }
+ }
+ }
+
+ if (nextCursor) {
+ batchImportAll(nextCursor, totalImported); // Recursively call with new cursor
+ } else {
+ console.log("All bookmarks imported");
+
+ 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.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));
+ });
};
chrome.webRequest.onBeforeSendHeaders.addListener(
- (details) => {
- if (
- !(details.url.includes("x.com") || details.url.includes("twitter.com"))
- ) {
- return;
- }
- const authHeader = details.requestHeaders!.find(
- (header) => header.name.toLowerCase() === "authorization",
- );
- const auth = authHeader ? authHeader.value : "";
-
- const cookieHeader = details.requestHeaders!.find(
- (header) => header.name.toLowerCase() === "cookie",
- );
- const cookie = cookieHeader ? cookieHeader.value : "";
-
- const csrfHeader = details.requestHeaders!.find(
- (header) => header.name.toLowerCase() === "x-csrf-token",
- );
- const csrf = csrfHeader ? csrfHeader.value : "";
-
- if (!auth || !cookie || !csrf) {
- console.log("auth, cookie, or csrf is missing");
- return;
- }
- chrome.storage.session.set({ cookie, csrf, auth });
- chrome.storage.local.get(["twitterBookmarks"], (result) => {
- console.log("twitterBookmarks", result.twitterBookmarks);
- if (result.twitterBookmarks !== "true") {
- console.log("twitterBookmarks is NOT true");
- } else {
- if (
- !details.requestHeaders ||
- details.requestHeaders.length === 0 ||
- details.requestHeaders === undefined
- ) {
- return;
- }
-
- // Check cache first
- chrome.storage.local.get(["lastFetch", "cachedData"], (result) => {
- const now = new Date().getTime();
- if (result.lastFetch && now - result.lastFetch < 30 * 60 * 1000) {
- // Cached data is less than 30 minutes old, use it
- console.log("Using cached data");
- console.log(result.cachedData);
- return;
- }
-
- // No valid cache, proceed to fetch
- const authHeader = details.requestHeaders!.find(
- (header) => header.name.toLowerCase() === "authorization",
- );
- const auth = authHeader ? authHeader.value : "";
-
- const cookieHeader = details.requestHeaders!.find(
- (header) => header.name.toLowerCase() === "cookie",
- );
- const cookie = cookieHeader ? cookieHeader.value : "";
-
- const csrfHeader = details.requestHeaders!.find(
- (header) => header.name.toLowerCase() === "x-csrf-token",
- );
- const csrf = csrfHeader ? csrfHeader.value : "";
-
- if (!auth || !cookie || !csrf) {
- console.log("auth, cookie, or csrf is missing");
- return;
- }
- chrome.storage.session.set({ cookie, csrf, auth });
-
- const myHeaders = new Headers();
- myHeaders.append("Cookie", cookie);
- myHeaders.append("X-Csrf-token", csrf);
- myHeaders.append("Authorization", auth);
-
- const requestOptions: RequestInit = {
- method: "GET",
- headers: myHeaders,
- redirect: "follow",
- };
-
- const variables = {
- count: 200,
- includePromotedContent: false,
- };
-
- // only fetch once in 1 minute
- if (now - lastTwitterFetch < 60 * 1000) {
- console.log("Waiting for ratelimits");
- return;
- }
-
- fetch(
- `${BOOKMARKS_URL}&variables=${encodeURIComponent(JSON.stringify(variables))}`,
- requestOptions,
- )
- .then((response) => response.text())
- .then((result) => {
- const tweets = getAllTweets(JSON.parse(result));
-
- console.log("tweets", tweets);
- // Cache the result along with the current timestamp
- chrome.storage.local.set({
- lastFetch: new Date().getTime(),
- cachedData: tweets,
- });
-
- lastTwitterFetch = now;
- })
- .catch((error) => console.error(error));
- });
- return;
- }
- });
- },
- { urls: ["*://x.com/*", "*://twitter.com/*"] },
- ["requestHeaders", "extraHeaders"],
+ (details) => {
+ if (
+ !(details.url.includes("x.com") || details.url.includes("twitter.com"))
+ ) {
+ return;
+ }
+ const authHeader = details.requestHeaders!.find(
+ (header) => header.name.toLowerCase() === "authorization",
+ );
+ const auth = authHeader ? authHeader.value : "";
+
+ const cookieHeader = details.requestHeaders!.find(
+ (header) => header.name.toLowerCase() === "cookie",
+ );
+ const cookie = cookieHeader ? cookieHeader.value : "";
+
+ const csrfHeader = details.requestHeaders!.find(
+ (header) => header.name.toLowerCase() === "x-csrf-token",
+ );
+ const csrf = csrfHeader ? csrfHeader.value : "";
+
+ if (!auth || !cookie || !csrf) {
+ console.log("auth, cookie, or csrf is missing");
+ return;
+ }
+ chrome.storage.session.set({ cookie, csrf, auth });
+ chrome.storage.local.get(["twitterBookmarks"], (result) => {
+ console.log("twitterBookmarks", result.twitterBookmarks);
+ if (result.twitterBookmarks !== "true") {
+ console.log("twitterBookmarks is NOT true");
+ } else {
+ if (
+ !details.requestHeaders ||
+ details.requestHeaders.length === 0 ||
+ details.requestHeaders === undefined
+ ) {
+ return;
+ }
+
+ // Check cache first
+ chrome.storage.local.get(["lastFetch", "cachedData"], (result) => {
+ const now = new Date().getTime();
+ if (result.lastFetch && now - result.lastFetch < 30 * 60 * 1000) {
+ // Cached data is less than 30 minutes old, use it
+ console.log("Using cached data");
+ console.log(result.cachedData);
+ return;
+ }
+
+ // No valid cache, proceed to fetch
+ const authHeader = details.requestHeaders!.find(
+ (header) => header.name.toLowerCase() === "authorization",
+ );
+ const auth = authHeader ? authHeader.value : "";
+
+ const cookieHeader = details.requestHeaders!.find(
+ (header) => header.name.toLowerCase() === "cookie",
+ );
+ const cookie = cookieHeader ? cookieHeader.value : "";
+
+ const csrfHeader = details.requestHeaders!.find(
+ (header) => header.name.toLowerCase() === "x-csrf-token",
+ );
+ const csrf = csrfHeader ? csrfHeader.value : "";
+
+ if (!auth || !cookie || !csrf) {
+ console.log("auth, cookie, or csrf is missing");
+ return;
+ }
+ chrome.storage.session.set({ cookie, csrf, auth });
+
+ const myHeaders = new Headers();
+ myHeaders.append("Cookie", cookie);
+ myHeaders.append("X-Csrf-token", csrf);
+ myHeaders.append("Authorization", auth);
+
+ const requestOptions: RequestInit = {
+ method: "GET",
+ headers: myHeaders,
+ redirect: "follow",
+ };
+
+ const variables = {
+ count: 200,
+ includePromotedContent: false,
+ };
+
+ // only fetch once in 1 minute
+ if (now - lastTwitterFetch < 60 * 1000) {
+ console.log("Waiting for ratelimits");
+ return;
+ }
+
+ fetch(
+ `${BOOKMARKS_URL}&variables=${encodeURIComponent(JSON.stringify(variables))}`,
+ requestOptions,
+ )
+ .then((response) => response.text())
+ .then((result) => {
+ const tweets = getAllTweets(JSON.parse(result));
+
+ console.log("tweets", tweets);
+ // Cache the result along with the current timestamp
+ chrome.storage.local.set({
+ lastFetch: new Date().getTime(),
+ cachedData: tweets,
+ });
+
+ lastTwitterFetch = now;
+ })
+ .catch((error) => console.error(error));
+ });
+ return;
+ }
+ });
+ },
+ { urls: ["*://x.com/*", "*://twitter.com/*"] },
+ ["requestHeaders", "extraHeaders"],
);
const getAllTweets = (rawJson: any): Tweet[] => {
- const entries =
- rawJson?.data?.bookmark_timeline_v2?.timeline?.instructions[0]?.entries;
+ const entries =
+ rawJson?.data?.bookmark_timeline_v2?.timeline?.instructions[0]?.entries;
- console.log("Entries: ", entries);
+ console.log("Entries: ", entries);
- if (!entries) {
- console.error("No entries found");
- return [];
- }
+ if (!entries) {
+ console.error("No entries found");
+ return [];
+ }
- const tweets = entries
- .map((entry: any) => transformTweetData(entry))
- .filter((tweet: Tweet | null) => tweet !== null) as Tweet[];
+ const tweets = entries
+ .map((entry: any) => transformTweetData(entry))
+ .filter((tweet: Tweet | null) => tweet !== null) as Tweet[];
- console.log(tweets);
+ console.log(tweets);
- return tweets;
+ return tweets;
};
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
- console.log(request);
- if (request.type === "getJwt") {
- chrome.storage.local.get(["jwt"], ({ jwt }) => {
- sendResponse({ jwt });
- });
-
- return true;
- } else if (request.type === "urlSave") {
- const content = request.content;
- const url = request.url;
- const title = request.title;
- const description = request.description;
- const ogImage = request.ogImage;
- const favicon = request.favicon;
- console.log(request.content, request.url);
-
- (async () => {
- chrome.storage.local.get(["jwt"], ({ jwt }) => {
- if (!jwt) {
- console.error("No JWT found");
- return;
- }
- fetch(`${BACKEND_URL}/api/store`, {
- method: "POST",
- headers: {
- Authorization: `Bearer ${jwt}`,
- },
- body: JSON.stringify({
- pageContent: content,
- url: url + "#supermemory-user-" + Math.random(),
- title,
- spaces: request.spaces,
- description,
- ogImage,
- image: favicon,
- }),
- }).then((ers) => console.log(ers.status));
- });
- })();
- } else if (request.type === "batchImportAll") {
- batchImportAll();
- return true;
- }
+ console.log(request);
+ if (request.type === "getJwt") {
+ chrome.storage.local.get(["jwt"], ({ jwt }) => {
+ sendResponse({ jwt });
+ });
+
+ return true;
+ } else if (request.type === "urlSave") {
+ const content = request.content;
+ const url = request.url;
+ const title = request.title;
+ const description = request.description;
+ const ogImage = request.ogImage;
+ const favicon = request.favicon;
+ console.log(request.content, request.url);
+
+ (async () => {
+ chrome.storage.local.get(["jwt"], ({ jwt }) => {
+ if (!jwt) {
+ console.error("No JWT found");
+ return;
+ }
+ fetch(`${BACKEND_URL}/api/store`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${jwt}`,
+ },
+ body: JSON.stringify({
+ pageContent: content,
+ url: url + "#supermemory-user-" + Math.random(),
+ title,
+ spaces: request.spaces,
+ description,
+ ogImage,
+ image: favicon,
+ }),
+ }).then((ers) => console.log(ers.status));
+ });
+ })();
+ } else if (request.type === "batchImportAll") {
+ batchImportAll();
+ return true;
+ }
});
chrome.runtime.onInstalled.addListener(function (details) {
- if (details.reason === "install") {
- chrome.tabs.create({
- url: "https://supermemory.ai/signin?extension=true",
- active: true,
- });
- }
+ if (details.reason === "install") {
+ chrome.tabs.create({
+ url: "https://supermemory.ai/signin?extension=true",
+ active: true,
+ });
+ }
});
chrome.runtime.onInstalled.addListener(() => {
- chrome.contextMenus.create({
- id: "saveSelection",
- title: "Save note to Supermemory",
- contexts: ["selection"],
- });
-
- chrome.contextMenus.create({
- id: "savePage",
- title: "Save page to Supermemory",
- contexts: ["page"],
- });
-
- // TODO
- // chrome.contextMenus.create({
- // id: 'saveLink',
- // title: 'Save link to Supermemory',
- // contexts: ['link'],
- // });
+ chrome.contextMenus.create({
+ id: "saveSelection",
+ title: "Save note to Supermemory",
+ contexts: ["selection"],
+ });
+
+ chrome.contextMenus.create({
+ id: "savePage",
+ title: "Save page to Supermemory",
+ contexts: ["page"],
+ });
+
+ // TODO
+ // chrome.contextMenus.create({
+ // id: 'saveLink',
+ // title: 'Save link to Supermemory',
+ // contexts: ['link'],
+ // });
});
interface FetchDataParams {
- content: string;
- url: string;
- title: string;
- description: string;
- ogImage: string;
- favicon: string;
- isExternalContent: boolean; // Indicates if the content is from an external API
+ content: string;
+ url: string;
+ title: string;
+ description: string;
+ ogImage: string;
+ favicon: string;
+ isExternalContent: boolean; // Indicates if the content is from an external API
}
const fetchData = ({
- content,
- url,
- title,
- description,
- ogImage,
- favicon,
- isExternalContent,
+ content,
+ url,
+ title,
+ description,
+ ogImage,
+ favicon,
+ isExternalContent,
}: FetchDataParams) => {
- // Construct the URL
- const finalUrl = isExternalContent
- ? url
- : `${url}#supermemory-stuff-${Math.random()}`;
-
- // Construct the body
- const body = JSON.stringify({
- pageContent: content,
- url: finalUrl,
- title,
- spaces: [],
- description,
- ogImage,
- image: favicon,
- });
-
- // Make the fetch call
- fetch(`${BACKEND_URL}/api/store`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: body,
- })
- .then((response) => {
- console.log("Data saved successfully");
- })
- .catch((error) => {
- console.error("Error saving data:", error);
- });
-
- return Promise.resolve();
+ // Construct the URL
+ const finalUrl = isExternalContent
+ ? url
+ : `${url}#supermemory-stuff-${Math.random()}`;
+
+ // Construct the body
+ const body = JSON.stringify({
+ pageContent: content,
+ url: finalUrl,
+ title,
+ spaces: [],
+ description,
+ ogImage,
+ image: favicon,
+ });
+
+ // Make the fetch call
+ fetch(`${BACKEND_URL}/api/store`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: body,
+ })
+ .then((response) => {
+ console.log("Data saved successfully");
+ })
+ .catch((error) => {
+ console.error("Error saving data:", error);
+ });
+
+ return Promise.resolve();
};
chrome.contextMenus.onClicked.addListener((info, tab) => {
- if (!tab || !tab.id) return;
-
- const tabId = tab.id;
-
- const sendMessageToTab = (message: string) => {
- chrome.tabs.sendMessage(tabId, { message, type: "supermemory-message" });
- };
-
- if (info.menuItemId === "saveSelection" && info.selectionText) {
- sendMessageToTab("Saving selection...");
- fetchData({
- content: info.selectionText || "No content",
- url: info.pageUrl,
- title: tab.title || "Selection Title",
- description: "User-selected content from the page",
- ogImage: "",
- favicon: "",
- isExternalContent: false,
- })
- .then(() => {
- sendMessageToTab("Selection saved successfully.");
- })
- .catch(() => {
- sendMessageToTab("Failed to save selection.");
- });
- } else if (info.menuItemId === "savePage") {
- sendMessageToTab("Saving page...");
- chrome.scripting.executeScript(
- {
- target: { tabId: tabId },
- func: () => document.body.innerText,
- },
- (results) => {
- if (results.length > 0 && results[0].result) {
- fetchData({
- content: results[0].result as string,
- url: info.pageUrl,
- title: tab.title || "Page Title",
- description: "Full page content",
- ogImage: "",
- favicon: "",
- isExternalContent: false,
- })
- .then(() => {
- sendMessageToTab("Page saved successfully.");
- })
- .catch(() => {
- sendMessageToTab("Failed to save page.");
- });
- }
- },
- );
- }
+ if (!tab || !tab.id) return;
+
+ const tabId = tab.id;
+
+ const sendMessageToTab = (message: string) => {
+ chrome.tabs.sendMessage(tabId, { message, type: "supermemory-message" });
+ };
+
+ if (info.menuItemId === "saveSelection" && info.selectionText) {
+ sendMessageToTab("Saving selection...");
+ fetchData({
+ content: info.selectionText || "No content",
+ url: info.pageUrl,
+ title: tab.title || "Selection Title",
+ description: "User-selected content from the page",
+ ogImage: "",
+ favicon: "",
+ isExternalContent: false,
+ })
+ .then(() => {
+ sendMessageToTab("Selection saved successfully.");
+ })
+ .catch(() => {
+ sendMessageToTab("Failed to save selection.");
+ });
+ } else if (info.menuItemId === "savePage") {
+ sendMessageToTab("Saving page...");
+ chrome.scripting.executeScript(
+ {
+ target: { tabId: tabId },
+ func: () => document.body.innerText,
+ },
+ (results) => {
+ if (results.length > 0 && results[0].result) {
+ fetchData({
+ content: results[0].result as string,
+ url: info.pageUrl,
+ title: tab.title || "Page Title",
+ description: "Full page content",
+ ogImage: "",
+ favicon: "",
+ isExternalContent: false,
+ })
+ .then(() => {
+ sendMessageToTab("Page saved successfully.");
+ })
+ .catch(() => {
+ sendMessageToTab("Failed to save page.");
+ });
+ }
+ },
+ );
+ }
});
diff --git a/apps/extension/components.json b/apps/extension/components.json
index d3099362..7d301176 100644
--- a/apps/extension/components.json
+++ b/apps/extension/components.json
@@ -1,18 +1,18 @@
{
- "$schema": "https://ui.shadcn.com/schema.json",
- "style": "default",
- "rsc": true,
- "tsx": true,
- "tailwind": {
- "config": "tailwind.config.ts",
- "css": "../../packages/tailwind-config/globals.css",
- "baseColor": "stone",
- "cssVariables": true,
- "prefix": ""
- },
- "aliases": {
- "components": "content/ui/components",
- "utils": "content/utils",
- "ui": "content/ui/shadcn"
- }
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "../../packages/tailwind-config/globals.css",
+ "baseColor": "stone",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "content/ui/components",
+ "utils": "content/utils",
+ "ui": "content/ui/shadcn"
+ }
}
diff --git a/apps/extension/content/ContentApp.tsx b/apps/extension/content/ContentApp.tsx
index d82857e2..8882e346 100644
--- a/apps/extension/content/ContentApp.tsx
+++ b/apps/extension/content/ContentApp.tsx
@@ -1,21 +1,21 @@
import React, { useEffect, useRef, useState } from "react";
import { Readability } from "@mozilla/readability";
import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
} 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,
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
} from "./ui/shadcn/select";
import { useToast } from "./ui/shadcn/use-toast";
import { Input } from "./ui/shadcn/input";
@@ -25,405 +25,405 @@ import { Textarea } from "./ui/shadcn/textarea";
const BACKEND_URL = "https://supermemory.ai";
export default function ContentApp({
- token,
- shadowRoot,
+ token,
+ shadowRoot,
}: {
- token: string | undefined;
- shadowRoot: ShadowRoot;
+ token: string | undefined;
+ shadowRoot: ShadowRoot;
}) {
- const [hover, setHover] = useState(false);
-
- const { toast } = useToast();
-
- const [loading, setLoading] = useState(false);
-
- const [webNote, setWebNote] = useState<string>("");
-
- const [importedCount, setImportedCount] = useState(0);
- const [isImporting, setIsImporting] = useState(false);
- const [importDone, setImportDone] = useState(false);
-
- const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(
- null,
- );
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
- const [isPopover2Open, setIsPopover2Open] = useState(false);
-
- const [spacesOptions, setSpacesOptions] = useState<
- { id: number; name: string }[]
- >([]);
- const [selectedSpace, setSelectedSpace] = useState<string>();
-
- 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: (
- <div className="flex flex-col gap-2">
- <button
- onClick={() =>
- window.open("https://supermemory.ai/signin", "_blank")
- }
- >
- Login
- </button>
-
- <button
- className="text-xs"
- onClick={async () => {
- await chrome.storage.local.set({
- noLoginWarning: true,
- });
- t.dismiss();
- }}
- >
- Ignore
- </button>
- </div>
- ),
- });
- }
- };
-
- 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);
- }
- });
-
- const getUserData = () => {
- chrome.runtime.sendMessage({ type: "getJwt" });
- };
-
- getUserData();
-
- 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 === "supermemory-message") {
- toast({
- title: request.message,
- });
- }
- });
-
- const portalDiv = document.createElement("div");
- portalDiv.id = "popover-portal";
- 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", () => {});
- };
- }, []);
-
- async function sendUrlToAPI(spaces: string[]) {
- setLoading(true);
-
- setTimeout(() => {
- setLoading(false);
- }, 1500);
-
- // 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 ogImage = document
- .querySelector('meta[property="og:image"]')
- ?.getAttribute("content");
-
- const favicon = (
- document.querySelector('link[rel="icon"]') as HTMLLinkElement
- )?.href;
-
- 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;
- });
- }
- }
-
- if (!shadowRoot || !portalContainer) {
- return null;
- }
-
- return (
- <div className="flex justify-end items-end min-h-screen h-full w-full">
- <Toaster />
-
- <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <PopoverTrigger
- className={`${hover || isPopoverOpen ? "opacity-100" : "opacity-75 pointer-events-none translate-x-3/4"} focus-within:translate-x-0 focus-visible:translate-x-0 size-12 hover:bg-black p-2 rounded-l-2xl transition bg-secondary border-2 border-border opacity-0 absolute flex bottom-20 items-center text-lg`}
- >
- <svg
- className={`w-full h-full size-8 ${loading && "animate-spin"}`}
- width={24}
- height={24}
- viewBox="0 0 42 42"
- fill="currentColor"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M19.0357 8C20.5531 8 21 9.27461 21 10.8438V16.3281H23.5536V14.2212C23.5536 13.1976 23.9468 12.216 24.6467 11.4922L25.0529 11.0721C24.9729 10.8772 24.9286 10.6627 24.9286 10.4375C24.9286 9.54004 25.6321 8.8125 26.5 8.8125C27.3679 8.8125 28.0714 9.54004 28.0714 10.4375C28.0714 11.335 27.3679 12.0625 26.5 12.0625C26.2822 12.0625 26.0748 12.0167 25.8863 11.9339L25.4801 12.354C25.0012 12.8492 24.7321 13.5209 24.7321 14.2212V16.3281H28.9714C29.2045 15.7326 29.7691 15.3125 30.4286 15.3125C31.2964 15.3125 32 16.04 32 16.9375C32 17.835 31.2964 18.5625 30.4286 18.5625C29.7691 18.5625 29.2045 18.1424 28.9714 17.5469H21V21.2031H25.0428C25.2759 20.6076 25.8405 20.1875 26.5 20.1875C27.3679 20.1875 28.0714 20.915 28.0714 21.8125C28.0714 22.71 27.3679 23.4375 26.5 23.4375C25.8405 23.4375 25.2759 23.0174 25.0428 22.4219H21V26.0781H24.4125C25.4023 26.0781 26.3516 26.4847 27.0515 27.2085L29.0292 29.2536C29.2177 29.1708 29.4251 29.125 29.6429 29.125C30.5107 29.125 31.2143 29.8525 31.2143 30.75C31.2143 31.6475 30.5107 32.375 29.6429 32.375C28.775 32.375 28.0714 31.6475 28.0714 30.75C28.0714 30.5248 28.1157 30.3103 28.1958 30.1154L26.2181 28.0703C25.7392 27.5751 25.0897 27.2969 24.4125 27.2969H21V31.1562C21 32.7254 20.5531 34 19.0357 34C17.6165 34 16.4478 32.8879 16.3004 31.4559C16.0451 31.527 15.775 31.5625 15.5 31.5625C13.7665 31.5625 12.3571 30.1051 12.3571 28.3125C12.3571 27.9367 12.421 27.5711 12.5339 27.2359C11.0509 26.657 10 25.1742 10 23.4375C10 21.8176 10.9183 20.416 12.2491 19.766C11.8219 19.2125 11.5714 18.5117 11.5714 17.75C11.5714 16.191 12.6321 14.891 14.0464 14.5711C13.9679 14.2918 13.9286 13.9922 13.9286 13.6875C13.9286 12.1691 14.9402 10.8895 16.3004 10.534C16.4478 9.11211 17.6165 8 19.0357 8Z"
- fill={loading ? "gray" : "#fff"}
- />
- </svg>
- </PopoverTrigger>
- </TooltipTrigger>
- <TooltipContent side="left">
- {userNotLoggedIn ? (
- <>You need to login to use this extension.</>
- ) : (
- <p>Add to supermemory.ai</p>
- )}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- <PopoverContent
- key={userNotLoggedIn ? "login" : "spaces"}
- container={portalContainer}
- >
- {userNotLoggedIn ? (
- <div className="flex flex-col gap-2">
- <button
- onClick={() => {
- window.open("https://supermemory.ai/signin", "_blank");
- }}
- className="bg-slate-700 text-white p-2 rounded-md"
- >
- Login to supermemory.ai
- </button>
- </div>
- ) : (
- <div className="flex flex-col gap-2">
- <Select onValueChange={(value) => setSelectedSpace(value)}>
- <SelectTrigger className="text-white">
- <SelectValue
- className="placeholder:font-semibold placeholder:text-white"
- placeholder="Select a space"
- />
- </SelectTrigger>
- <SelectContent container={portalContainer}>
- <SelectGroup>
- <SelectLabel>Your spaces</SelectLabel>
- {spacesOptions.map((space) => (
- <SelectItem key={space.id} value={`${space.id}`}>
- {space.name}
- </SelectItem>
- ))}
- </SelectGroup>
- </SelectContent>
- </Select>
-
- <Label className="text-slate-400" htmlFor="input-note">
- Add a note
- </Label>
- <Textarea
- value={webNote}
- onChange={(e) => setWebNote(e.target.value)}
- placeholder="Add a note"
- className="text-white"
- id="input-note"
- />
-
- <button
- onClick={async () => {
- await sendUrlToAPI(selectedSpace ? [selectedSpace] : []);
- }}
- className="bg-slate-700 text-white p-2 rounded-md"
- >
- Add to{" "}
- {selectedSpace
- ? spacesOptions.find((s) => s.id === parseInt(selectedSpace))
- ?.name
- : "supermemory.ai"}
- </button>
- </div>
- )}
- </PopoverContent>
- </Popover>
-
- {(window.location.host === "twitter.com" ||
- window.location.host === "x.com") && (
- <Popover open={isPopover2Open} onOpenChange={setIsPopover2Open}>
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <PopoverTrigger
- className={`${hover || isPopover2Open ? "opacity-100" : "opacity-75 pointer-events-none translate-x-3/4"} focus-within:translate-x-0 focus-visible:translate-x-0 size-12 hover:bg-black p-2 rounded-l-2xl transition bg-secondary border-2 border-border opacity-0 absolute flex bottom-6 items-center text-lg`}
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 24 24"
- fill="currentColor"
- className="size-8"
- >
- <path d="M12 1.5a.75.75 0 0 1 .75.75V7.5h-1.5V2.25A.75.75 0 0 1 12 1.5ZM11.25 7.5v5.69l-1.72-1.72a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l3-3a.75.75 0 1 0-1.06-1.06l-1.72 1.72V7.5h3.75a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-9a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3h3.75Z" />
- </svg>
- </PopoverTrigger>
- </TooltipTrigger>
- <TooltipContent side="left">
- {userNotLoggedIn ? (
- <>You need to login to use this extension.</>
- ) : (
- <p>Import all twitter bookmarks</p>
- )}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- <PopoverContent
- key={userNotLoggedIn ? "login" : "spaces"}
- container={portalContainer}
- >
- {userNotLoggedIn ? (
- <div className="flex flex-col gap-2">
- <button
- onClick={() => {
- window.open("https://supermemory.ai/signin", "_blank");
- }}
- className="bg-slate-700 text-white p-2 rounded-md"
- >
- Login to supermemory.ai
- </button>
- </div>
- ) : (
- <div className="flex flex-col gap-2">
- <button
- disabled={isImporting}
- onClick={async () => {
- setIsImporting(true);
- chrome.runtime.sendMessage({ type: "batchImportAll" });
- }}
- className="bg-slate-700 text-white p-2 rounded-md disabled:bg-slate-700/50 disabled:pointer-events-none"
- >
- Import all Twitter bookmarks
- </button>
-
- {isImporting && (
- <div className="flex items-center gap-2">
- <p>Imported {importedCount} bookmarks</p>
- <svg
- className="animate-spin w-6 h-6"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 6v6m0 0v6m0-6h6m-6 0H6"
- />
- </svg>
- </div>
- )}
-
- {importDone && (
- <p className="text-green-500">
- All your twitter bookmarks have been imported!
- </p>
- )}
- </div>
- )}
- </PopoverContent>
- </Popover>
- )}
- </div>
- );
+ const [hover, setHover] = useState(false);
+
+ const { toast } = useToast();
+
+ const [loading, setLoading] = useState(false);
+
+ const [webNote, setWebNote] = useState<string>("");
+
+ const [importedCount, setImportedCount] = useState(0);
+ const [isImporting, setIsImporting] = useState(false);
+ const [importDone, setImportDone] = useState(false);
+
+ const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(
+ null,
+ );
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const [isPopover2Open, setIsPopover2Open] = useState(false);
+
+ const [spacesOptions, setSpacesOptions] = useState<
+ { id: number; name: string }[]
+ >([]);
+ const [selectedSpace, setSelectedSpace] = useState<string>();
+
+ 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: (
+ <div className="flex flex-col gap-2">
+ <button
+ onClick={() =>
+ window.open("https://supermemory.ai/signin", "_blank")
+ }
+ >
+ Login
+ </button>
+
+ <button
+ className="text-xs"
+ onClick={async () => {
+ await chrome.storage.local.set({
+ noLoginWarning: true,
+ });
+ t.dismiss();
+ }}
+ >
+ Ignore
+ </button>
+ </div>
+ ),
+ });
+ }
+ };
+
+ 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);
+ }
+ });
+
+ const getUserData = () => {
+ chrome.runtime.sendMessage({ type: "getJwt" });
+ };
+
+ getUserData();
+
+ 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 === "supermemory-message") {
+ toast({
+ title: request.message,
+ });
+ }
+ });
+
+ const portalDiv = document.createElement("div");
+ portalDiv.id = "popover-portal";
+ 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", () => {});
+ };
+ }, []);
+
+ async function sendUrlToAPI(spaces: string[]) {
+ setLoading(true);
+
+ setTimeout(() => {
+ setLoading(false);
+ }, 1500);
+
+ // 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 ogImage = document
+ .querySelector('meta[property="og:image"]')
+ ?.getAttribute("content");
+
+ const favicon = (
+ document.querySelector('link[rel="icon"]') as HTMLLinkElement
+ )?.href;
+
+ 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;
+ });
+ }
+ }
+
+ if (!shadowRoot || !portalContainer) {
+ return null;
+ }
+
+ return (
+ <div className="flex justify-end items-end min-h-screen h-full w-full">
+ <Toaster />
+
+ <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <PopoverTrigger
+ className={`${hover || isPopoverOpen ? "opacity-100" : "opacity-75 pointer-events-none translate-x-3/4"} focus-within:translate-x-0 focus-visible:translate-x-0 size-12 hover:bg-black p-2 rounded-l-2xl transition bg-secondary border-2 border-border opacity-0 absolute flex bottom-20 items-center text-lg`}
+ >
+ <svg
+ className={`w-full h-full size-8 ${loading && "animate-spin"}`}
+ width={24}
+ height={24}
+ viewBox="0 0 42 42"
+ fill="currentColor"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M19.0357 8C20.5531 8 21 9.27461 21 10.8438V16.3281H23.5536V14.2212C23.5536 13.1976 23.9468 12.216 24.6467 11.4922L25.0529 11.0721C24.9729 10.8772 24.9286 10.6627 24.9286 10.4375C24.9286 9.54004 25.6321 8.8125 26.5 8.8125C27.3679 8.8125 28.0714 9.54004 28.0714 10.4375C28.0714 11.335 27.3679 12.0625 26.5 12.0625C26.2822 12.0625 26.0748 12.0167 25.8863 11.9339L25.4801 12.354C25.0012 12.8492 24.7321 13.5209 24.7321 14.2212V16.3281H28.9714C29.2045 15.7326 29.7691 15.3125 30.4286 15.3125C31.2964 15.3125 32 16.04 32 16.9375C32 17.835 31.2964 18.5625 30.4286 18.5625C29.7691 18.5625 29.2045 18.1424 28.9714 17.5469H21V21.2031H25.0428C25.2759 20.6076 25.8405 20.1875 26.5 20.1875C27.3679 20.1875 28.0714 20.915 28.0714 21.8125C28.0714 22.71 27.3679 23.4375 26.5 23.4375C25.8405 23.4375 25.2759 23.0174 25.0428 22.4219H21V26.0781H24.4125C25.4023 26.0781 26.3516 26.4847 27.0515 27.2085L29.0292 29.2536C29.2177 29.1708 29.4251 29.125 29.6429 29.125C30.5107 29.125 31.2143 29.8525 31.2143 30.75C31.2143 31.6475 30.5107 32.375 29.6429 32.375C28.775 32.375 28.0714 31.6475 28.0714 30.75C28.0714 30.5248 28.1157 30.3103 28.1958 30.1154L26.2181 28.0703C25.7392 27.5751 25.0897 27.2969 24.4125 27.2969H21V31.1562C21 32.7254 20.5531 34 19.0357 34C17.6165 34 16.4478 32.8879 16.3004 31.4559C16.0451 31.527 15.775 31.5625 15.5 31.5625C13.7665 31.5625 12.3571 30.1051 12.3571 28.3125C12.3571 27.9367 12.421 27.5711 12.5339 27.2359C11.0509 26.657 10 25.1742 10 23.4375C10 21.8176 10.9183 20.416 12.2491 19.766C11.8219 19.2125 11.5714 18.5117 11.5714 17.75C11.5714 16.191 12.6321 14.891 14.0464 14.5711C13.9679 14.2918 13.9286 13.9922 13.9286 13.6875C13.9286 12.1691 14.9402 10.8895 16.3004 10.534C16.4478 9.11211 17.6165 8 19.0357 8Z"
+ fill={loading ? "gray" : "#fff"}
+ />
+ </svg>
+ </PopoverTrigger>
+ </TooltipTrigger>
+ <TooltipContent side="left">
+ {userNotLoggedIn ? (
+ <>You need to login to use this extension.</>
+ ) : (
+ <p>Add to supermemory.ai</p>
+ )}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ <PopoverContent
+ key={userNotLoggedIn ? "login" : "spaces"}
+ container={portalContainer}
+ >
+ {userNotLoggedIn ? (
+ <div className="flex flex-col gap-2">
+ <button
+ onClick={() => {
+ window.open("https://supermemory.ai/signin", "_blank");
+ }}
+ className="bg-slate-700 text-white p-2 rounded-md"
+ >
+ Login to supermemory.ai
+ </button>
+ </div>
+ ) : (
+ <div className="flex flex-col gap-2">
+ <Select onValueChange={(value) => setSelectedSpace(value)}>
+ <SelectTrigger className="text-white">
+ <SelectValue
+ className="placeholder:font-semibold placeholder:text-white"
+ placeholder="Select a space"
+ />
+ </SelectTrigger>
+ <SelectContent container={portalContainer}>
+ <SelectGroup>
+ <SelectLabel>Your spaces</SelectLabel>
+ {spacesOptions.map((space) => (
+ <SelectItem key={space.id} value={`${space.id}`}>
+ {space.name}
+ </SelectItem>
+ ))}
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+
+ <Label className="text-slate-400" htmlFor="input-note">
+ Add a note
+ </Label>
+ <Textarea
+ value={webNote}
+ onChange={(e) => setWebNote(e.target.value)}
+ placeholder="Add a note"
+ className="text-white"
+ id="input-note"
+ />
+
+ <button
+ onClick={async () => {
+ await sendUrlToAPI(selectedSpace ? [selectedSpace] : []);
+ }}
+ className="bg-slate-700 text-white p-2 rounded-md"
+ >
+ Add to{" "}
+ {selectedSpace
+ ? spacesOptions.find((s) => s.id === parseInt(selectedSpace))
+ ?.name
+ : "supermemory.ai"}
+ </button>
+ </div>
+ )}
+ </PopoverContent>
+ </Popover>
+
+ {(window.location.host === "twitter.com" ||
+ window.location.host === "x.com") && (
+ <Popover open={isPopover2Open} onOpenChange={setIsPopover2Open}>
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <PopoverTrigger
+ className={`${hover || isPopover2Open ? "opacity-100" : "opacity-75 pointer-events-none translate-x-3/4"} focus-within:translate-x-0 focus-visible:translate-x-0 size-12 hover:bg-black p-2 rounded-l-2xl transition bg-secondary border-2 border-border opacity-0 absolute flex bottom-6 items-center text-lg`}
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="currentColor"
+ className="size-8"
+ >
+ <path d="M12 1.5a.75.75 0 0 1 .75.75V7.5h-1.5V2.25A.75.75 0 0 1 12 1.5ZM11.25 7.5v5.69l-1.72-1.72a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l3-3a.75.75 0 1 0-1.06-1.06l-1.72 1.72V7.5h3.75a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-9a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3h3.75Z" />
+ </svg>
+ </PopoverTrigger>
+ </TooltipTrigger>
+ <TooltipContent side="left">
+ {userNotLoggedIn ? (
+ <>You need to login to use this extension.</>
+ ) : (
+ <p>Import all twitter bookmarks</p>
+ )}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ <PopoverContent
+ key={userNotLoggedIn ? "login" : "spaces"}
+ container={portalContainer}
+ >
+ {userNotLoggedIn ? (
+ <div className="flex flex-col gap-2">
+ <button
+ onClick={() => {
+ window.open("https://supermemory.ai/signin", "_blank");
+ }}
+ className="bg-slate-700 text-white p-2 rounded-md"
+ >
+ Login to supermemory.ai
+ </button>
+ </div>
+ ) : (
+ <div className="flex flex-col gap-2">
+ <button
+ disabled={isImporting}
+ onClick={async () => {
+ setIsImporting(true);
+ chrome.runtime.sendMessage({ type: "batchImportAll" });
+ }}
+ className="bg-slate-700 text-white p-2 rounded-md disabled:bg-slate-700/50 disabled:pointer-events-none"
+ >
+ Import all Twitter bookmarks
+ </button>
+
+ {isImporting && (
+ <div className="flex items-center gap-2">
+ <p>Imported {importedCount} bookmarks</p>
+ <svg
+ className="animate-spin w-6 h-6"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={2}
+ d="M12 6v6m0 0v6m0-6h6m-6 0H6"
+ />
+ </svg>
+ </div>
+ )}
+
+ {importDone && (
+ <p className="text-green-500">
+ All your twitter bookmarks have been imported!
+ </p>
+ )}
+ </div>
+ )}
+ </PopoverContent>
+ </Popover>
+ )}
+ </div>
+ );
}
diff --git a/apps/extension/content/base.css b/apps/extension/content/base.css
index cd7633f5..3e9995fa 100644
--- a/apps/extension/content/base.css
+++ b/apps/extension/content/base.css
@@ -1,6 +1,6 @@
#supermemory-extension-host {
- position: fixed;
- bottom: 0;
- right: 0;
- z-index: 99999;
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ z-index: 99999;
}
diff --git a/apps/extension/content/content.tsx b/apps/extension/content/content.tsx
index 9accca62..e97c06e6 100644
--- a/apps/extension/content/content.tsx
+++ b/apps/extension/content/content.tsx
@@ -5,79 +5,79 @@ import("./base.css");
setTimeout(initial, 1000);
const TAILWIND_URL =
- "https://cdn.jsdelivr.net/npm/tailwindcss@^2.0/dist/tailwind.min.css";
+ "https://cdn.jsdelivr.net/npm/tailwindcss@^2.0/dist/tailwind.min.css";
const appendTailwindStyleData = (shadowRoot: ShadowRoot) => {
- const styleSheet = document.createElement("style");
+ const styleSheet = document.createElement("style");
- const path = chrome.runtime.getURL("../public/output.css");
+ const path = chrome.runtime.getURL("../public/output.css");
- fetch(path)
- .then((response) => response.text())
- .then((css) => {
- styleSheet.textContent = css;
- shadowRoot.appendChild(styleSheet);
- });
+ fetch(path)
+ .then((response) => response.text())
+ .then((css) => {
+ styleSheet.textContent = css;
+ shadowRoot.appendChild(styleSheet);
+ });
};
const appendTailwindStyleLink = (shadowRoot: ShadowRoot) => {
- // Import Tailwind CSS and inject it into the shadow DOM
- const styleSheet = document.createElement("link");
- styleSheet.rel = "stylesheet";
- styleSheet.href = TAILWIND_URL;
- shadowRoot.appendChild(styleSheet);
+ // Import Tailwind CSS and inject it into the shadow DOM
+ const styleSheet = document.createElement("link");
+ styleSheet.rel = "stylesheet";
+ styleSheet.href = TAILWIND_URL;
+ shadowRoot.appendChild(styleSheet);
};
function initial() {
- // Create a new div element to host the shadow root.
- // Styles for this div is in `content/content.css`
- const hostDiv = document.createElement("div");
- hostDiv.id = "supermemory-extension-host";
- document.body.appendChild(hostDiv);
+ // Create a new div element to host the shadow root.
+ // Styles for this div is in `content/content.css`
+ const hostDiv = document.createElement("div");
+ hostDiv.id = "supermemory-extension-host";
+ document.body.appendChild(hostDiv);
- // Attach the shadow DOM to the hostDiv and set the mode to
- // 'open' for accessibility from JavaScript.
- // Ref https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
- const shadowRoot = hostDiv.attachShadow({ mode: "open" });
+ // Attach the shadow DOM to the hostDiv and set the mode to
+ // 'open' for accessibility from JavaScript.
+ // Ref https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
+ const shadowRoot = hostDiv.attachShadow({ mode: "open" });
- // Create a new div element that will be the root container for the React app
- const rootDiv = document.createElement("div");
- rootDiv.id = "supermemory-extension-root";
- shadowRoot.appendChild(rootDiv);
+ // Create a new div element that will be the root container for the React app
+ const rootDiv = document.createElement("div");
+ rootDiv.id = "supermemory-extension-root";
+ shadowRoot.appendChild(rootDiv);
- appendTailwindStyleData(shadowRoot);
+ appendTailwindStyleData(shadowRoot);
- const root = ReactDOM.createRoot(rootDiv);
+ const root = ReactDOM.createRoot(rootDiv);
- const jwt = chrome.storage.local.get("jwt").then((data) => {
- return data.jwt;
- }) as Promise<string | undefined>;
+ const jwt = chrome.storage.local.get("jwt").then((data) => {
+ return data.jwt;
+ }) as Promise<string | undefined>;
- jwt.then((token) =>
- root.render(<ContentApp shadowRoot={shadowRoot} token={token} />),
- );
+ jwt.then((token) =>
+ root.render(<ContentApp shadowRoot={shadowRoot} token={token} />),
+ );
}
window.addEventListener("message", (event) => {
- if (event.source !== window) {
- return;
- }
- const jwt = event.data.token;
-
- if (jwt) {
- if (
- !(
- window.location.hostname === "localhost" ||
- window.location.hostname === "supermemory.ai" ||
- window.location.hostname === "beta.supermemory.ai"
- )
- ) {
- console.log(
- "JWT is only allowed to be used on localhost or supermemory.ai",
- );
- return;
- }
-
- chrome.storage.local.set({ jwt }, () => {});
- }
+ if (event.source !== window) {
+ return;
+ }
+ const jwt = event.data.token;
+
+ if (jwt) {
+ if (
+ !(
+ window.location.hostname === "localhost" ||
+ window.location.hostname === "supermemory.ai" ||
+ window.location.hostname === "beta.supermemory.ai"
+ )
+ ) {
+ console.log(
+ "JWT is only allowed to be used on localhost or supermemory.ai",
+ );
+ return;
+ }
+
+ chrome.storage.local.set({ jwt }, () => {});
+ }
});
diff --git a/apps/extension/content/ui/shadcn/input.tsx b/apps/extension/content/ui/shadcn/input.tsx
index 96d600b7..f50a1692 100644
--- a/apps/extension/content/ui/shadcn/input.tsx
+++ b/apps/extension/content/ui/shadcn/input.tsx
@@ -3,22 +3,22 @@ import * as React from "react";
import { cn } from "../../utils";
export interface InputProps
- extends React.InputHTMLAttributes<HTMLInputElement> {}
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
- ({ className, type, ...props }, ref) => {
- return (
- <input
- type={type}
- className={cn(
- "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- ref={ref}
- {...props}
- />
- );
- },
+ ({ className, type, ...props }, ref) => {
+ return (
+ <input
+ type={type}
+ className={cn(
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ className,
+ )}
+ ref={ref}
+ {...props}
+ />
+ );
+ },
);
Input.displayName = "Input";
diff --git a/apps/extension/content/ui/shadcn/label.tsx b/apps/extension/content/ui/shadcn/label.tsx
index 25199133..3201d1eb 100644
--- a/apps/extension/content/ui/shadcn/label.tsx
+++ b/apps/extension/content/ui/shadcn/label.tsx
@@ -7,19 +7,19 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../utils";
const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef<
- React.ElementRef<typeof LabelPrimitive.Root>,
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
- VariantProps<typeof labelVariants>
+ React.ElementRef<typeof LabelPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
+ VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
- <LabelPrimitive.Root
- ref={ref}
- className={cn(labelVariants(), className)}
- {...props}
- />
+ <LabelPrimitive.Root
+ ref={ref}
+ className={cn(labelVariants(), className)}
+ {...props}
+ />
));
Label.displayName = LabelPrimitive.Root.displayName;
diff --git a/apps/extension/content/ui/shadcn/popover.tsx b/apps/extension/content/ui/shadcn/popover.tsx
index f84f8e46..43f19247 100644
--- a/apps/extension/content/ui/shadcn/popover.tsx
+++ b/apps/extension/content/ui/shadcn/popover.tsx
@@ -10,28 +10,28 @@ const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
- React.ElementRef<typeof PopoverPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
- container?: Element;
- }
+ React.ElementRef<typeof PopoverPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
+ container?: Element;
+ }
>(
- (
- { className, container, align = "center", sideOffset = 4, ...props },
- ref,
- ) => (
- <PopoverPrimitive.Portal container={container}>
- <PopoverPrimitive.Content
- ref={ref}
- align={align}
- sideOffset={sideOffset}
- className={cn(
- "z-50 w-72 mx-4 my-2 rounded-md border border-border bg-secondary p-4 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className,
- )}
- {...props}
- />
- </PopoverPrimitive.Portal>
- ),
+ (
+ { className, container, align = "center", sideOffset = 4, ...props },
+ ref,
+ ) => (
+ <PopoverPrimitive.Portal container={container}>
+ <PopoverPrimitive.Content
+ ref={ref}
+ align={align}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 w-72 mx-4 my-2 rounded-md border border-border bg-secondary p-4 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className,
+ )}
+ {...props}
+ />
+ </PopoverPrimitive.Portal>
+ ),
);
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
diff --git a/apps/extension/content/ui/shadcn/select.tsx b/apps/extension/content/ui/shadcn/select.tsx
index f90195bd..77a91dcc 100644
--- a/apps/extension/content/ui/shadcn/select.tsx
+++ b/apps/extension/content/ui/shadcn/select.tsx
@@ -13,150 +13,150 @@ const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.Trigger>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
- <SelectPrimitive.Trigger
- ref={ref}
- className={cn(
- "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
- className,
- )}
- {...props}
- >
- {children}
- <SelectPrimitive.Icon asChild>
- <ChevronDown className="h-4 w-4 opacity-50" />
- </SelectPrimitive.Icon>
- </SelectPrimitive.Trigger>
+ <SelectPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+ <SelectPrimitive.Icon asChild>
+ <ChevronDown className="h-4 w-4 opacity-50" />
+ </SelectPrimitive.Icon>
+ </SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
- <SelectPrimitive.ScrollUpButton
- ref={ref}
- className={cn(
- "flex cursor-default items-center justify-center py-1",
- className,
- )}
- {...props}
- >
- <ChevronUp className="h-4 w-4" />
- </SelectPrimitive.ScrollUpButton>
+ <SelectPrimitive.ScrollUpButton
+ ref={ref}
+ className={cn(
+ "flex cursor-default items-center justify-center py-1",
+ className,
+ )}
+ {...props}
+ >
+ <ChevronUp className="h-4 w-4" />
+ </SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
- <SelectPrimitive.ScrollDownButton
- ref={ref}
- className={cn(
- "flex cursor-default items-center justify-center py-1",
- className,
- )}
- {...props}
- >
- <ChevronDown className="h-4 w-4" />
- </SelectPrimitive.ScrollDownButton>
+ <SelectPrimitive.ScrollDownButton
+ ref={ref}
+ className={cn(
+ "flex cursor-default items-center justify-center py-1",
+ className,
+ )}
+ {...props}
+ >
+ <ChevronDown className="h-4 w-4" />
+ </SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
- SelectPrimitive.ScrollDownButton.displayName;
+ SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> & {
- container?: Element;
- }
+ React.ElementRef<typeof SelectPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> & {
+ container?: Element;
+ }
>(({ className, children, position = "popper", container, ...props }, ref) => (
- <SelectPrimitive.Portal container={container}>
- <SelectPrimitive.Content
- ref={ref}
- className={cn(
- "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- position === "popper" &&
- "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
- className,
- )}
- position={position}
- {...props}
- >
- <SelectScrollUpButton />
- <SelectPrimitive.Viewport
- className={cn(
- "p-1",
- position === "popper" &&
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
- )}
- >
- {children}
- </SelectPrimitive.Viewport>
- <SelectScrollDownButton />
- </SelectPrimitive.Content>
- </SelectPrimitive.Portal>
+ <SelectPrimitive.Portal container={container}>
+ <SelectPrimitive.Content
+ ref={ref}
+ className={cn(
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ position === "popper" &&
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+ className,
+ )}
+ position={position}
+ {...props}
+ >
+ <SelectScrollUpButton />
+ <SelectPrimitive.Viewport
+ className={cn(
+ "p-1",
+ position === "popper" &&
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
+ )}
+ >
+ {children}
+ </SelectPrimitive.Viewport>
+ <SelectScrollDownButton />
+ </SelectPrimitive.Content>
+ </SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.Label>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
+ React.ElementRef<typeof SelectPrimitive.Label>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
- <SelectPrimitive.Label
- ref={ref}
- className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
- {...props}
- />
+ <SelectPrimitive.Label
+ ref={ref}
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
+ {...props}
+ />
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.Item>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
+ React.ElementRef<typeof SelectPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
- <SelectPrimitive.Item
- ref={ref}
- className={cn(
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className,
- )}
- {...props}
- >
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
- <SelectPrimitive.ItemIndicator>
- <Check className="h-4 w-4" />
- </SelectPrimitive.ItemIndicator>
- </span>
+ <SelectPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className,
+ )}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <SelectPrimitive.ItemIndicator>
+ <Check className="h-4 w-4" />
+ </SelectPrimitive.ItemIndicator>
+ </span>
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
- </SelectPrimitive.Item>
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
+ </SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
- React.ElementRef<typeof SelectPrimitive.Separator>,
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
+ React.ElementRef<typeof SelectPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
- <SelectPrimitive.Separator
- ref={ref}
- className={cn("-mx-1 my-1 h-px bg-muted", className)}
- {...props}
- />
+ <SelectPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
+ {...props}
+ />
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
- Select,
- SelectGroup,
- SelectValue,
- SelectTrigger,
- SelectContent,
- SelectLabel,
- SelectItem,
- SelectSeparator,
- SelectScrollUpButton,
- SelectScrollDownButton,
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
};
diff --git a/apps/extension/content/ui/shadcn/textarea.tsx b/apps/extension/content/ui/shadcn/textarea.tsx
index 6ff704d8..65202281 100644
--- a/apps/extension/content/ui/shadcn/textarea.tsx
+++ b/apps/extension/content/ui/shadcn/textarea.tsx
@@ -3,21 +3,21 @@ import * as React from "react";
import { cn } from "../../utils";
export interface TextareaProps
- extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
- ({ className, ...props }, ref) => {
- return (
- <textarea
- className={cn(
- "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- ref={ref}
- {...props}
- />
- );
- },
+ ({ className, ...props }, ref) => {
+ return (
+ <textarea
+ className={cn(
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ className,
+ )}
+ ref={ref}
+ {...props}
+ />
+ );
+ },
);
Textarea.displayName = "Textarea";
diff --git a/apps/extension/content/ui/shadcn/toast.tsx b/apps/extension/content/ui/shadcn/toast.tsx
index b0f93ac4..e9db7e46 100644
--- a/apps/extension/content/ui/shadcn/toast.tsx
+++ b/apps/extension/content/ui/shadcn/toast.tsx
@@ -10,105 +10,105 @@ import { cn } from "../../utils";
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Viewport>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
- <ToastPrimitives.Viewport
- ref={ref}
- className={cn(
- "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
- className,
- )}
- {...props}
- />
+ <ToastPrimitives.Viewport
+ ref={ref}
+ className={cn(
+ "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
+ className,
+ )}
+ {...props}
+ />
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
- "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
- {
- variants: {
- variant: {
- default: "border bg-background text-foreground",
- destructive:
- "destructive group border-destructive bg-destructive text-destructive-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
);
const Toast = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Root>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
- VariantProps<typeof toastVariants>
+ React.ElementRef<typeof ToastPrimitives.Root>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
+ VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
- return (
- <ToastPrimitives.Root
- ref={ref}
- className={cn(toastVariants({ variant }), className)}
- {...props}
- />
- );
+ return (
+ <ToastPrimitives.Root
+ ref={ref}
+ className={cn(toastVariants({ variant }), className)}
+ {...props}
+ />
+ );
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Action>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
+ React.ElementRef<typeof ToastPrimitives.Action>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
- <ToastPrimitives.Action
- ref={ref}
- className={cn(
- "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
- className,
- )}
- {...props}
- />
+ <ToastPrimitives.Action
+ ref={ref}
+ className={cn(
+ "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
+ className,
+ )}
+ {...props}
+ />
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Close>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
+ React.ElementRef<typeof ToastPrimitives.Close>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
- <ToastPrimitives.Close
- ref={ref}
- className={cn(
- "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
- className,
- )}
- toast-close=""
- {...props}
- >
- <X className="h-4 w-4" />
- </ToastPrimitives.Close>
+ <ToastPrimitives.Close
+ ref={ref}
+ className={cn(
+ "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
+ className,
+ )}
+ toast-close=""
+ {...props}
+ >
+ <X className="h-4 w-4" />
+ </ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Title>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
+ React.ElementRef<typeof ToastPrimitives.Title>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
- <ToastPrimitives.Title
- ref={ref}
- className={cn("text-sm font-semibold", className)}
- {...props}
- />
+ <ToastPrimitives.Title
+ ref={ref}
+ className={cn("text-sm font-semibold", className)}
+ {...props}
+ />
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Description>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
+ React.ElementRef<typeof ToastPrimitives.Description>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
- <ToastPrimitives.Description
- ref={ref}
- className={cn("text-sm opacity-90", className)}
- {...props}
- />
+ <ToastPrimitives.Description
+ ref={ref}
+ className={cn("text-sm opacity-90", className)}
+ {...props}
+ />
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
@@ -117,13 +117,13 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
- type ToastProps,
- type ToastActionElement,
- ToastProvider,
- ToastViewport,
- Toast,
- ToastTitle,
- ToastDescription,
- ToastClose,
- ToastAction,
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
};
diff --git a/apps/extension/content/ui/shadcn/toaster.tsx b/apps/extension/content/ui/shadcn/toaster.tsx
index 9b494bfd..1e896d64 100644
--- a/apps/extension/content/ui/shadcn/toaster.tsx
+++ b/apps/extension/content/ui/shadcn/toaster.tsx
@@ -1,35 +1,35 @@
"use client";
import {
- Toast,
- ToastClose,
- ToastDescription,
- ToastProvider,
- ToastTitle,
- ToastViewport,
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
} from "../shadcn/toast";
import { useToast } from "../shadcn/use-toast";
export function Toaster() {
- const { toasts } = useToast();
+ const { toasts } = useToast();
- return (
- <ToastProvider>
- {toasts.map(function ({ id, title, description, action, ...props }) {
- return (
- <Toast key={id} {...props}>
- <div className="grid gap-1">
- {title && <ToastTitle>{title}</ToastTitle>}
- {description && (
- <ToastDescription>{description}</ToastDescription>
- )}
- </div>
- {action}
- <ToastClose />
- </Toast>
- );
- })}
- <ToastViewport />
- </ToastProvider>
- );
+ return (
+ <ToastProvider>
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+ <Toast key={id} {...props}>
+ <div className="grid gap-1">
+ {title && <ToastTitle>{title}</ToastTitle>}
+ {description && (
+ <ToastDescription>{description}</ToastDescription>
+ )}
+ </div>
+ {action}
+ <ToastClose />
+ </Toast>
+ );
+ })}
+ <ToastViewport />
+ </ToastProvider>
+ );
}
diff --git a/apps/extension/content/ui/shadcn/tooltip.tsx b/apps/extension/content/ui/shadcn/tooltip.tsx
index 35b9ffcc..880a15dc 100644
--- a/apps/extension/content/ui/shadcn/tooltip.tsx
+++ b/apps/extension/content/ui/shadcn/tooltip.tsx
@@ -12,18 +12,18 @@ const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
- React.ElementRef<typeof TooltipPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
+ React.ElementRef<typeof TooltipPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
- <TooltipPrimitive.Content
- ref={ref}
- sideOffset={sideOffset}
- className={cn(
- "z-50 overflow-hidden rounded-md border border-white/30 text-white bg-black px-3 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className,
- )}
- {...props}
- />
+ <TooltipPrimitive.Content
+ ref={ref}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 overflow-hidden rounded-md border border-white/30 text-white bg-black px-3 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className,
+ )}
+ {...props}
+ />
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
diff --git a/apps/extension/content/ui/shadcn/use-toast.ts b/apps/extension/content/ui/shadcn/use-toast.ts
index 4a64ceb6..8fd6c037 100644
--- a/apps/extension/content/ui/shadcn/use-toast.ts
+++ b/apps/extension/content/ui/shadcn/use-toast.ts
@@ -9,121 +9,121 @@ const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
- id: string;
- title?: React.ReactNode;
- description?: React.ReactNode;
- action?: ToastActionElement;
+ id: string;
+ title?: React.ReactNode;
+ description?: React.ReactNode;
+ action?: ToastActionElement;
};
const actionTypes = {
- ADD_TOAST: "ADD_TOAST",
- UPDATE_TOAST: "UPDATE_TOAST",
- DISMISS_TOAST: "DISMISS_TOAST",
- REMOVE_TOAST: "REMOVE_TOAST",
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
} as const;
let count = 0;
function genId() {
- count = (count + 1) % Number.MAX_SAFE_INTEGER;
- return count.toString();
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
+ return count.toString();
}
type ActionType = typeof actionTypes;
type Action =
- | {
- type: ActionType["ADD_TOAST"];
- toast: ToasterToast;
- }
- | {
- type: ActionType["UPDATE_TOAST"];
- toast: Partial<ToasterToast>;
- }
- | {
- type: ActionType["DISMISS_TOAST"];
- toastId?: ToasterToast["id"];
- }
- | {
- type: ActionType["REMOVE_TOAST"];
- toastId?: ToasterToast["id"];
- };
+ | {
+ type: ActionType["ADD_TOAST"];
+ toast: ToasterToast;
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"];
+ toast: Partial<ToasterToast>;
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"];
+ toastId?: ToasterToast["id"];
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"];
+ toastId?: ToasterToast["id"];
+ };
interface State {
- toasts: ToasterToast[];
+ toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
- if (toastTimeouts.has(toastId)) {
- return;
- }
-
- const timeout = setTimeout(() => {
- toastTimeouts.delete(toastId);
- dispatch({
- type: "REMOVE_TOAST",
- toastId: toastId,
- });
- }, TOAST_REMOVE_DELAY);
-
- toastTimeouts.set(toastId, timeout);
+ if (toastTimeouts.has(toastId)) {
+ return;
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId);
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ });
+ }, TOAST_REMOVE_DELAY);
+
+ toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
- switch (action.type) {
- case "ADD_TOAST":
- return {
- ...state,
- toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
- };
-
- case "UPDATE_TOAST":
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === action.toast.id ? { ...t, ...action.toast } : t,
- ),
- };
-
- case "DISMISS_TOAST": {
- const { toastId } = action;
-
- // ! Side effects ! - This could be extracted into a dismissToast() action,
- // but I'll keep it here for simplicity
- if (toastId) {
- addToRemoveQueue(toastId);
- } else {
- state.toasts.forEach((toast) => {
- addToRemoveQueue(toast.id);
- });
- }
-
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === toastId || toastId === undefined
- ? {
- ...t,
- open: false,
- }
- : t,
- ),
- };
- }
- case "REMOVE_TOAST":
- if (action.toastId === undefined) {
- return {
- ...state,
- toasts: [],
- };
- }
- return {
- ...state,
- toasts: state.toasts.filter((t) => t.id !== action.toastId),
- };
- }
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ };
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t,
+ ),
+ };
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action;
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId);
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id);
+ });
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t,
+ ),
+ };
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ };
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ };
+ }
};
const listeners: Array<(state: State) => void> = [];
@@ -131,61 +131,61 @@ const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
- memoryState = reducer(memoryState, action);
- listeners.forEach((listener) => {
- listener(memoryState);
- });
+ memoryState = reducer(memoryState, action);
+ listeners.forEach((listener) => {
+ listener(memoryState);
+ });
}
type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) {
- const id = genId();
-
- const update = (props: ToasterToast) =>
- dispatch({
- type: "UPDATE_TOAST",
- toast: { ...props, id },
- });
- const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
-
- dispatch({
- type: "ADD_TOAST",
- toast: {
- ...props,
- id,
- open: true,
- onOpenChange: (open) => {
- if (!open) dismiss();
- },
- },
- });
-
- return {
- id: id,
- dismiss,
- update,
- };
+ const id = genId();
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ });
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss();
+ },
+ },
+ });
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ };
}
function useToast() {
- const [state, setState] = React.useState<State>(memoryState);
-
- React.useEffect(() => {
- listeners.push(setState);
- return () => {
- const index = listeners.indexOf(setState);
- if (index > -1) {
- listeners.splice(index, 1);
- }
- };
- }, [state]);
-
- return {
- ...state,
- toast,
- dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
- };
+ const [state, setState] = React.useState<State>(memoryState);
+
+ React.useEffect(() => {
+ listeners.push(setState);
+ return () => {
+ const index = listeners.indexOf(setState);
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ };
+ }, [state]);
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ };
}
export { useToast, toast };
diff --git a/apps/extension/content/utils.ts b/apps/extension/content/utils.ts
index 365058ce..ac680b30 100644
--- a/apps/extension/content/utils.ts
+++ b/apps/extension/content/utils.ts
@@ -2,5 +2,5 @@ import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs));
+ return twMerge(clsx(inputs));
}
diff --git a/apps/extension/helpers.ts b/apps/extension/helpers.ts
index 8554237e..9e95f963 100644
--- a/apps/extension/helpers.ts
+++ b/apps/extension/helpers.ts
@@ -1,75 +1,75 @@
import { Tweet } from "react-tweet/api";
export const features = {
- graphql_timeline_v2_bookmark_timeline: true,
- rweb_tipjar_consumption_enabled: true,
- responsive_web_graphql_exclude_directive_enabled: true,
- verified_phone_label_enabled: false,
- creator_subscriptions_tweet_preview_api_enabled: true,
- responsive_web_graphql_timeline_navigation_enabled: true,
- responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
- communities_web_enable_tweet_community_results_fetch: true,
- c9s_tweet_anatomy_moderator_badge_enabled: true,
- articles_preview_enabled: true,
- tweetypie_unmention_optimization_enabled: true,
- responsive_web_edit_tweet_api_enabled: true,
- graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
- view_counts_everywhere_api_enabled: true,
- longform_notetweets_consumption_enabled: true,
- responsive_web_twitter_article_tweet_consumption_enabled: true,
- tweet_awards_web_tipping_enabled: false,
- creator_subscriptions_quote_tweet_preview_enabled: false,
- freedom_of_speech_not_reach_fetch_enabled: true,
- standardized_nudges_misinfo: true,
- tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
- rweb_video_timestamps_enabled: true,
- longform_notetweets_rich_text_read_enabled: true,
- longform_notetweets_inline_media_enabled: true,
- responsive_web_enhance_cards_enabled: false,
+ graphql_timeline_v2_bookmark_timeline: true,
+ rweb_tipjar_consumption_enabled: true,
+ responsive_web_graphql_exclude_directive_enabled: true,
+ verified_phone_label_enabled: false,
+ creator_subscriptions_tweet_preview_api_enabled: true,
+ responsive_web_graphql_timeline_navigation_enabled: true,
+ responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
+ communities_web_enable_tweet_community_results_fetch: true,
+ c9s_tweet_anatomy_moderator_badge_enabled: true,
+ articles_preview_enabled: true,
+ tweetypie_unmention_optimization_enabled: true,
+ responsive_web_edit_tweet_api_enabled: true,
+ graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
+ view_counts_everywhere_api_enabled: true,
+ longform_notetweets_consumption_enabled: true,
+ responsive_web_twitter_article_tweet_consumption_enabled: true,
+ tweet_awards_web_tipping_enabled: false,
+ creator_subscriptions_quote_tweet_preview_enabled: false,
+ freedom_of_speech_not_reach_fetch_enabled: true,
+ standardized_nudges_misinfo: true,
+ tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
+ rweb_video_timestamps_enabled: true,
+ longform_notetweets_rich_text_read_enabled: true,
+ longform_notetweets_inline_media_enabled: true,
+ responsive_web_enhance_cards_enabled: false,
};
export function transformTweetData(input: any): Tweet | null {
- const tweet = input.content?.itemContent?.tweet_results?.result;
+ const tweet = input.content?.itemContent?.tweet_results?.result;
- if (!tweet || tweet.legacy === undefined) {
- return null;
- }
+ if (!tweet || tweet.legacy === undefined) {
+ return null;
+ }
- const transformed: Tweet = {
- __typename: tweet.__typename,
- lang: tweet.legacy?.lang,
- favorite_count: tweet.legacy.favorite_count,
- created_at: new Date(tweet.legacy.created_at).toISOString(),
- display_text_range: tweet.legacy.display_text_range,
- entities: {
- hashtags: tweet.legacy.entities.hashtags,
- urls: tweet.legacy.entities.urls,
- user_mentions: tweet.legacy.entities.user_mentions,
- symbols: tweet.legacy.entities.symbols,
- },
- id_str: tweet.legacy.id_str,
- text: tweet.legacy.full_text,
- user: {
- id_str: tweet.core.user_results.result.legacy.id_str,
- name: tweet.core.user_results.result.legacy.name,
- profile_image_url_https:
- tweet.core.user_results.result.legacy.profile_image_url_https,
- screen_name: tweet.core.user_results.result.legacy.screen_name,
- verified: tweet.core.user_results.result.legacy.verified,
- is_blue_verified: tweet.core.user_results.result.is_blue_verified,
- profile_image_shape: tweet.core.user_results.result.profile_image_shape,
- },
- edit_control: {
- edit_tweet_ids: tweet.edit_control.edit_tweet_ids,
- editable_until_msecs: tweet.edit_control.editable_until_msecs,
- is_edit_eligible: tweet.edit_control.is_edit_eligible,
- edits_remaining: tweet.edit_control.edits_remaining,
- },
- conversation_count: tweet.legacy.reply_count,
- news_action_type: "conversation",
- isEdited: tweet.edit_control.is_edit_eligible,
- isStaleEdit: false, // This value is derived from the context, adjust as needed
- };
+ const transformed: Tweet = {
+ __typename: tweet.__typename,
+ lang: tweet.legacy?.lang,
+ favorite_count: tweet.legacy.favorite_count,
+ created_at: new Date(tweet.legacy.created_at).toISOString(),
+ display_text_range: tweet.legacy.display_text_range,
+ entities: {
+ hashtags: tweet.legacy.entities.hashtags,
+ urls: tweet.legacy.entities.urls,
+ user_mentions: tweet.legacy.entities.user_mentions,
+ symbols: tweet.legacy.entities.symbols,
+ },
+ id_str: tweet.legacy.id_str,
+ text: tweet.legacy.full_text,
+ user: {
+ id_str: tweet.core.user_results.result.legacy.id_str,
+ name: tweet.core.user_results.result.legacy.name,
+ profile_image_url_https:
+ tweet.core.user_results.result.legacy.profile_image_url_https,
+ screen_name: tweet.core.user_results.result.legacy.screen_name,
+ verified: tweet.core.user_results.result.legacy.verified,
+ is_blue_verified: tweet.core.user_results.result.is_blue_verified,
+ profile_image_shape: tweet.core.user_results.result.profile_image_shape,
+ },
+ edit_control: {
+ edit_tweet_ids: tweet.edit_control.edit_tweet_ids,
+ editable_until_msecs: tweet.edit_control.editable_until_msecs,
+ is_edit_eligible: tweet.edit_control.is_edit_eligible,
+ edits_remaining: tweet.edit_control.edits_remaining,
+ },
+ conversation_count: tweet.legacy.reply_count,
+ news_action_type: "conversation",
+ isEdited: tweet.edit_control.is_edit_eligible,
+ isStaleEdit: false, // This value is derived from the context, adjust as needed
+ };
- return transformed;
+ return transformed;
}
diff --git a/apps/extension/manifest.json b/apps/extension/manifest.json
index 006108ac..0edcd320 100644
--- a/apps/extension/manifest.json
+++ b/apps/extension/manifest.json
@@ -1,28 +1,28 @@
{
- "manifest_version": 3,
- "$schema": "https://json.schemastore.org/chrome-manifest",
- "version": "2.63",
- "name": "supermemory",
- "description": "An extension for https://supermemory.ai - an AI hub for all your bookmarks.",
- "background": {
- "service_worker": "./background.ts"
- },
- "content_scripts": [
- {
- "matches": ["<all_urls>"],
- "js": ["./content/content.tsx"]
- }
- ],
- "icons": {
- "16": "public/icon/icon_16.png",
- "48": "public/icon/icon_48.png"
- },
- "web_accessible_resources": [
- {
- "resources": ["public/*"],
- "matches": ["<all_urls>"]
- }
- ],
- "permissions": ["webRequest", "storage", "contextMenus"],
- "host_permissions": ["<all_urls>"]
+ "manifest_version": 3,
+ "$schema": "https://json.schemastore.org/chrome-manifest",
+ "version": "2.63",
+ "name": "supermemory",
+ "description": "An extension for https://supermemory.ai - an AI hub for all your bookmarks.",
+ "background": {
+ "service_worker": "./background.ts"
+ },
+ "content_scripts": [
+ {
+ "matches": ["<all_urls>"],
+ "js": ["./content/content.tsx"]
+ }
+ ],
+ "icons": {
+ "16": "public/icon/icon_16.png",
+ "48": "public/icon/icon_48.png"
+ },
+ "web_accessible_resources": [
+ {
+ "resources": ["public/*"],
+ "matches": ["<all_urls>"]
+ }
+ ],
+ "permissions": ["webRequest", "storage", "contextMenus"],
+ "host_permissions": ["<all_urls>"]
}
diff --git a/apps/extension/package.json b/apps/extension/package.json
index a24d8355..e028d680 100644
--- a/apps/extension/package.json
+++ b/apps/extension/package.json
@@ -1,34 +1,34 @@
{
- "devDependencies": {
- "@types/react": "^18.0.9",
- "@types/react-dom": "^18.0.5",
- "@webpack-cli/generators": "^3.0.7",
- "extension": "latest",
- "react": "^18.1.0",
- "react-dom": "^18.1.0",
- "tailwindcss": "^3.4.1",
- "typescript": "5.3.3",
- "webpack-cli": "^5.1.4"
- },
- "scripts": {
- "dev": "extension dev",
- "start": "extension start",
- "build": "extension build",
- "style": "tailwindcss -i ../../packages/tailwind-config/globals.css -o ./public/output.css --watch"
- },
- "name": "supermemory-extension",
- "private": true,
- "version": "0.0.0",
- "dependencies": {
- "@radix-ui/react-label": "^2.1.0",
- "@radix-ui/react-popover": "^1.1.1",
- "@radix-ui/react-select": "^2.1.1",
- "@radix-ui/react-toast": "^1.2.1",
- "@radix-ui/react-tooltip": "^1.1.2",
- "class-variance-authority": "^0.7.0",
- "clsx": "^2.1.1",
- "lucide-react": "^0.400.0",
- "tailwind-merge": "^2.3.0",
- "tailwindcss-animate": "^1.0.7"
- }
+ "devDependencies": {
+ "@types/react": "^18.0.9",
+ "@types/react-dom": "^18.0.5",
+ "@webpack-cli/generators": "^3.0.7",
+ "extension": "latest",
+ "react": "^18.1.0",
+ "react-dom": "^18.1.0",
+ "tailwindcss": "^3.4.1",
+ "typescript": "5.3.3",
+ "webpack-cli": "^5.1.4"
+ },
+ "scripts": {
+ "dev": "extension dev",
+ "start": "extension start",
+ "build": "extension build",
+ "style": "tailwindcss -i ../../packages/tailwind-config/globals.css -o ./public/output.css --watch"
+ },
+ "name": "supermemory-extension",
+ "private": true,
+ "version": "0.0.0",
+ "dependencies": {
+ "@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-popover": "^1.1.1",
+ "@radix-ui/react-select": "^2.1.1",
+ "@radix-ui/react-toast": "^1.2.1",
+ "@radix-ui/react-tooltip": "^1.1.2",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.400.0",
+ "tailwind-merge": "^2.3.0",
+ "tailwindcss-animate": "^1.0.7"
+ }
}
diff --git a/apps/extension/postcss.config.js b/apps/extension/postcss.config.js
index 12a703d9..e873f1a4 100644
--- a/apps/extension/postcss.config.js
+++ b/apps/extension/postcss.config.js
@@ -1,6 +1,6 @@
module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
};
diff --git a/apps/extension/public/output.css b/apps/extension/public/output.css
index f9e1d1d2..27771b4d 100644
--- a/apps/extension/public/output.css
+++ b/apps/extension/public/output.css
@@ -10,19 +10,19 @@
*,
::before,
::after {
- box-sizing: border-box;
- /* 1 */
- border-width: 0;
- /* 2 */
- border-style: solid;
- /* 2 */
- border-color: #e5e7eb;
- /* 2 */
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
}
::before,
::after {
- --tw-content: "";
+ --tw-content: "";
}
/*
@@ -37,24 +37,24 @@
html,
:host {
- line-height: 1.5;
- /* 1 */
- -webkit-text-size-adjust: 100%;
- /* 2 */
- -moz-tab-size: 4;
- /* 3 */
- -o-tab-size: 4;
- tab-size: 4;
- /* 3 */
- font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
- "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- /* 4 */
- font-feature-settings: normal;
- /* 5 */
- font-variation-settings: normal;
- /* 6 */
- -webkit-tap-highlight-color: transparent;
- /* 7 */
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
+ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 4 */
+ font-feature-settings: normal;
+ /* 5 */
+ font-variation-settings: normal;
+ /* 6 */
+ -webkit-tap-highlight-color: transparent;
+ /* 7 */
}
/*
@@ -63,10 +63,10 @@ html,
*/
body {
- margin: 0;
- /* 1 */
- line-height: inherit;
- /* 2 */
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
}
/*
@@ -76,12 +76,12 @@ body {
*/
hr {
- height: 0;
- /* 1 */
- color: inherit;
- /* 2 */
- border-top-width: 1px;
- /* 3 */
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
}
/*
@@ -89,8 +89,8 @@ Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
- -webkit-text-decoration: underline dotted;
- text-decoration: underline dotted;
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
}
/*
@@ -103,8 +103,8 @@ h3,
h4,
h5,
h6 {
- font-size: inherit;
- font-weight: inherit;
+ font-size: inherit;
+ font-weight: inherit;
}
/*
@@ -112,8 +112,8 @@ Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
- color: inherit;
- text-decoration: inherit;
+ color: inherit;
+ text-decoration: inherit;
}
/*
@@ -122,7 +122,7 @@ Add the correct font weight in Edge and Safari.
b,
strong {
- font-weight: bolder;
+ font-weight: bolder;
}
/*
@@ -136,15 +136,15 @@ code,
kbd,
samp,
pre {
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
- "Liberation Mono", "Courier New", monospace;
- /* 1 */
- font-feature-settings: normal;
- /* 2 */
- font-variation-settings: normal;
- /* 3 */
- font-size: 1em;
- /* 4 */
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
+ "Liberation Mono", "Courier New", monospace;
+ /* 1 */
+ font-feature-settings: normal;
+ /* 2 */
+ font-variation-settings: normal;
+ /* 3 */
+ font-size: 1em;
+ /* 4 */
}
/*
@@ -152,7 +152,7 @@ Add the correct font size in all browsers.
*/
small {
- font-size: 80%;
+ font-size: 80%;
}
/*
@@ -161,18 +161,18 @@ Prevent `sub` and `sup` elements from affecting the line height in all browsers.
sub,
sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
}
sub {
- bottom: -0.25em;
+ bottom: -0.25em;
}
sup {
- top: -0.5em;
+ top: -0.5em;
}
/*
@@ -182,12 +182,12 @@ sup {
*/
table {
- text-indent: 0;
- /* 1 */
- border-color: inherit;
- /* 2 */
- border-collapse: collapse;
- /* 3 */
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
}
/*
@@ -201,26 +201,26 @@ input,
optgroup,
select,
textarea {
- font-family: inherit;
- /* 1 */
- font-feature-settings: inherit;
- /* 1 */
- font-variation-settings: inherit;
- /* 1 */
- font-size: 100%;
- /* 1 */
- font-weight: inherit;
- /* 1 */
- line-height: inherit;
- /* 1 */
- letter-spacing: inherit;
- /* 1 */
- color: inherit;
- /* 1 */
- margin: 0;
- /* 2 */
- padding: 0;
- /* 3 */
+ font-family: inherit;
+ /* 1 */
+ font-feature-settings: inherit;
+ /* 1 */
+ font-variation-settings: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ font-weight: inherit;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ letter-spacing: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
}
/*
@@ -229,7 +229,7 @@ Remove the inheritance of text transform in Edge and Firefox.
button,
select {
- text-transform: none;
+ text-transform: none;
}
/*
@@ -241,12 +241,12 @@ button,
input:where([type="button"]),
input:where([type="reset"]),
input:where([type="submit"]) {
- -webkit-appearance: button;
- /* 1 */
- background-color: transparent;
- /* 2 */
- background-image: none;
- /* 2 */
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
}
/*
@@ -254,7 +254,7 @@ Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
- outline: auto;
+ outline: auto;
}
/*
@@ -262,7 +262,7 @@ Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/
*/
:-moz-ui-invalid {
- box-shadow: none;
+ box-shadow: none;
}
/*
@@ -270,7 +270,7 @@ Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
- vertical-align: baseline;
+ vertical-align: baseline;
}
/*
@@ -279,7 +279,7 @@ Correct the cursor style of increment and decrement buttons in Safari.
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
- height: auto;
+ height: auto;
}
/*
@@ -288,10 +288,10 @@ Correct the cursor style of increment and decrement buttons in Safari.
*/
[type="search"] {
- -webkit-appearance: textfield;
- /* 1 */
- outline-offset: -2px;
- /* 2 */
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
}
/*
@@ -299,7 +299,7 @@ Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
- -webkit-appearance: none;
+ -webkit-appearance: none;
}
/*
@@ -308,10 +308,10 @@ Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-file-upload-button {
- -webkit-appearance: button;
- /* 1 */
- font: inherit;
- /* 2 */
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
}
/*
@@ -319,7 +319,7 @@ Add the correct display in Chrome and Safari.
*/
summary {
- display: list-item;
+ display: list-item;
}
/*
@@ -339,24 +339,24 @@ hr,
figure,
p,
pre {
- margin: 0;
+ margin: 0;
}
fieldset {
- margin: 0;
- padding: 0;
+ margin: 0;
+ padding: 0;
}
legend {
- padding: 0;
+ padding: 0;
}
ol,
ul,
menu {
- list-style: none;
- margin: 0;
- padding: 0;
+ list-style: none;
+ margin: 0;
+ padding: 0;
}
/*
@@ -364,7 +364,7 @@ Reset default styling for dialogs.
*/
dialog {
- padding: 0;
+ padding: 0;
}
/*
@@ -372,7 +372,7 @@ Prevent resizing textareas horizontally by default.
*/
textarea {
- resize: vertical;
+ resize: vertical;
}
/*
@@ -382,18 +382,18 @@ textarea {
input::-moz-placeholder,
textarea::-moz-placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
}
input::placeholder,
textarea::placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
}
/*
@@ -402,7 +402,7 @@ Set the default cursor for buttons.
button,
[role="button"] {
- cursor: pointer;
+ cursor: pointer;
}
/*
@@ -410,7 +410,7 @@ Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
- cursor: default;
+ cursor: default;
}
/*
@@ -427,10 +427,10 @@ audio,
iframe,
embed,
object {
- display: block;
- /* 1 */
- vertical-align: middle;
- /* 2 */
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
}
/*
@@ -439,1367 +439,1367 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
img,
video {
- max-width: 100%;
- height: auto;
+ max-width: 100%;
+ height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
- display: none;
+ display: none;
}
* {
- scrollbar-color: initial;
- scrollbar-width: initial;
+ scrollbar-color: initial;
+ scrollbar-width: initial;
}
*,
::before,
::after {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
}
::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
}
.container {
- width: 100%;
- margin-right: auto;
- margin-left: auto;
- padding-right: 2rem;
- padding-left: 2rem;
+ width: 100%;
+ margin-right: auto;
+ margin-left: auto;
+ padding-right: 2rem;
+ padding-left: 2rem;
}
@media (min-width: 1400px) {
- .container {
- max-width: 1400px;
- }
+ .container {
+ max-width: 1400px;
+ }
}
.sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
}
.pointer-events-none {
- pointer-events: none;
+ pointer-events: none;
}
.pointer-events-auto {
- pointer-events: auto;
+ pointer-events: auto;
}
.fixed {
- position: fixed;
+ position: fixed;
}
.absolute {
- position: absolute;
+ position: absolute;
}
.relative {
- position: relative;
+ position: relative;
}
.inset-0 {
- inset: 0px;
+ inset: 0px;
}
.inset-x-0 {
- left: 0px;
- right: 0px;
+ left: 0px;
+ right: 0px;
}
.bottom-0 {
- bottom: 0px;
+ bottom: 0px;
}
.bottom-20 {
- bottom: 5rem;
+ bottom: 5rem;
}
.bottom-6 {
- bottom: 1.5rem;
+ bottom: 1.5rem;
}
.left-2 {
- left: 0.5rem;
+ left: 0.5rem;
}
.left-\[50\%\] {
- left: 50%;
+ left: 50%;
}
.right-2 {
- right: 0.5rem;
+ right: 0.5rem;
}
.right-4 {
- right: 1rem;
+ right: 1rem;
}
.top-0 {
- top: 0px;
+ top: 0px;
}
.top-2 {
- top: 0.5rem;
+ top: 0.5rem;
}
.top-4 {
- top: 1rem;
+ top: 1rem;
}
.top-\[50\%\] {
- top: 50%;
+ top: 50%;
}
.-z-\[1\] {
- z-index: -1;
+ z-index: -1;
}
.z-10 {
- z-index: 10;
+ z-index: 10;
}
.z-30 {
- z-index: 30;
+ z-index: 30;
}
.z-50 {
- z-index: 50;
+ z-index: 50;
}
.z-\[100\] {
- z-index: 100;
+ z-index: 100;
}
.-mx-1 {
- margin-left: -0.25rem;
- margin-right: -0.25rem;
+ margin-left: -0.25rem;
+ margin-right: -0.25rem;
}
.mx-4 {
- margin-left: 1rem;
- margin-right: 1rem;
+ margin-left: 1rem;
+ margin-right: 1rem;
}
.mx-auto {
- margin-left: auto;
- margin-right: auto;
+ margin-left: auto;
+ margin-right: auto;
}
.my-1 {
- margin-top: 0.25rem;
- margin-bottom: 0.25rem;
+ margin-top: 0.25rem;
+ margin-bottom: 0.25rem;
}
.my-2 {
- margin-top: 0.5rem;
- margin-bottom: 0.5rem;
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
}
.mb-1 {
- margin-bottom: 0.25rem;
+ margin-bottom: 0.25rem;
}
.ml-auto {
- margin-left: auto;
+ margin-left: auto;
}
.mr-2 {
- margin-right: 0.5rem;
+ margin-right: 0.5rem;
}
.mt-1 {
- margin-top: 0.25rem;
+ margin-top: 0.25rem;
}
.mt-2 {
- margin-top: 0.5rem;
+ margin-top: 0.5rem;
}
.mt-24 {
- margin-top: 6rem;
+ margin-top: 6rem;
}
.mt-4 {
- margin-top: 1rem;
+ margin-top: 1rem;
}
.mt-auto {
- margin-top: auto;
+ margin-top: auto;
}
.block {
- display: block;
+ display: block;
}
.flex {
- display: flex;
+ display: flex;
}
.inline-flex {
- display: inline-flex;
+ display: inline-flex;
}
.grid {
- display: grid;
+ display: grid;
}
.hidden {
- display: none;
+ display: none;
}
.size-12 {
- width: 3rem;
- height: 3rem;
+ width: 3rem;
+ height: 3rem;
}
.size-4 {
- width: 1rem;
- height: 1rem;
+ width: 1rem;
+ height: 1rem;
}
.size-8 {
- width: 2rem;
- height: 2rem;
+ width: 2rem;
+ height: 2rem;
}
.h-10 {
- height: 2.5rem;
+ height: 2.5rem;
}
.h-11 {
- height: 2.75rem;
+ height: 2.75rem;
}
.h-2 {
- height: 0.5rem;
+ height: 0.5rem;
}
.h-2\.5 {
- height: 0.625rem;
+ height: 0.625rem;
}
.h-3 {
- height: 0.75rem;
+ height: 0.75rem;
}
.h-3\.5 {
- height: 0.875rem;
+ height: 0.875rem;
}
.h-4 {
- height: 1rem;
+ height: 1rem;
}
.h-5 {
- height: 1.25rem;
+ height: 1.25rem;
}
.h-6 {
- height: 1.5rem;
+ height: 1.5rem;
}
.h-8 {
- height: 2rem;
+ height: 2rem;
}
.h-9 {
- height: 2.25rem;
+ height: 2.25rem;
}
.h-\[1px\] {
- height: 1px;
+ height: 1px;
}
.h-\[var\(--radix-select-trigger-height\)\] {
- height: var(--radix-select-trigger-height);
+ height: var(--radix-select-trigger-height);
}
.h-auto {
- height: auto;
+ height: auto;
}
.h-full {
- height: 100%;
+ height: 100%;
}
.h-px {
- height: 1px;
+ height: 1px;
}
.max-h-96 {
- max-height: 24rem;
+ max-height: 24rem;
}
.max-h-\[300px\] {
- max-height: 300px;
+ max-height: 300px;
}
.max-h-screen {
- max-height: 100vh;
+ max-height: 100vh;
}
.min-h-\[80px\] {
- min-height: 80px;
+ min-height: 80px;
}
.min-h-screen {
- min-height: 100vh;
+ min-height: 100vh;
}
.w-10 {
- width: 2.5rem;
+ width: 2.5rem;
}
.w-11 {
- width: 2.75rem;
+ width: 2.75rem;
}
.w-2 {
- width: 0.5rem;
+ width: 0.5rem;
}
.w-2\.5 {
- width: 0.625rem;
+ width: 0.625rem;
}
.w-3 {
- width: 0.75rem;
+ width: 0.75rem;
}
.w-3\.5 {
- width: 0.875rem;
+ width: 0.875rem;
}
.w-4 {
- width: 1rem;
+ width: 1rem;
}
.w-5 {
- width: 1.25rem;
+ width: 1.25rem;
}
.w-6 {
- width: 1.5rem;
+ width: 1.5rem;
}
.w-72 {
- width: 18rem;
+ width: 18rem;
}
.w-\[100px\] {
- width: 100px;
+ width: 100px;
}
.w-\[1px\] {
- width: 1px;
+ width: 1px;
}
.w-full {
- width: 100%;
+ width: 100%;
}
.min-w-\[8rem\] {
- min-width: 8rem;
+ min-width: 8rem;
}
.min-w-\[var\(--radix-select-trigger-width\)\] {
- min-width: var(--radix-select-trigger-width);
+ min-width: var(--radix-select-trigger-width);
}
.max-w-64 {
- max-width: 16rem;
+ max-width: 16rem;
}
.max-w-lg {
- max-width: 32rem;
+ max-width: 32rem;
}
.flex-1 {
- flex: 1 1 0%;
+ flex: 1 1 0%;
}
.shrink-0 {
- flex-shrink: 0;
+ flex-shrink: 0;
}
.translate-x-3\/4 {
- --tw-translate-x: 75%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 75%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-x-5 {
- --tw-translate-x: 1.25rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 1.25rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-x-\[-50\%\] {
- --tw-translate-x: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-y-12 {
- --tw-translate-y: 3rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-y: 3rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-y-\[-50\%\] {
- --tw-translate-y: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@keyframes spin {
- to {
- transform: rotate(360deg);
- }
+ to {
+ transform: rotate(360deg);
+ }
}
.animate-spin {
- animation: spin 1s linear infinite;
+ animation: spin 1s linear infinite;
}
.cursor-default {
- cursor: default;
+ cursor: default;
}
.cursor-pointer {
- cursor: pointer;
+ cursor: pointer;
}
.touch-none {
- touch-action: none;
+ touch-action: none;
}
.select-none {
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
}
.flex-col {
- flex-direction: column;
+ flex-direction: column;
}
.flex-col-reverse {
- flex-direction: column-reverse;
+ flex-direction: column-reverse;
}
.items-end {
- align-items: flex-end;
+ align-items: flex-end;
}
.items-center {
- align-items: center;
+ align-items: center;
}
.justify-end {
- justify-content: flex-end;
+ justify-content: flex-end;
}
.justify-center {
- justify-content: center;
+ justify-content: center;
}
.justify-between {
- justify-content: space-between;
+ justify-content: space-between;
}
.gap-1 {
- gap: 0.25rem;
+ gap: 0.25rem;
}
.gap-1\.5 {
- gap: 0.375rem;
+ gap: 0.375rem;
}
.gap-2 {
- gap: 0.5rem;
+ gap: 0.5rem;
}
.gap-4 {
- gap: 1rem;
+ gap: 1rem;
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-right: calc(1rem * var(--tw-space-x-reverse));
- margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+ --tw-space-x-reverse: 0;
+ margin-right: calc(1rem * var(--tw-space-x-reverse));
+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-1 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
}
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
}
.overflow-hidden {
- overflow: hidden;
+ overflow: hidden;
}
.overflow-y-auto {
- overflow-y: auto;
+ overflow-y: auto;
}
.overflow-x-hidden {
- overflow-x: hidden;
+ overflow-x: hidden;
}
.whitespace-nowrap {
- white-space: nowrap;
+ white-space: nowrap;
}
.rounded-2xl {
- border-radius: 1rem;
+ border-radius: 1rem;
}
.rounded-3xl {
- border-radius: 1.5rem;
+ border-radius: 1.5rem;
}
.rounded-\[inherit\] {
- border-radius: inherit;
+ border-radius: inherit;
}
.rounded-full {
- border-radius: 9999px;
+ border-radius: 9999px;
}
.rounded-lg {
- border-radius: var(--radius);
+ border-radius: var(--radius);
}
.rounded-md {
- border-radius: calc(var(--radius) - 2px);
+ border-radius: calc(var(--radius) - 2px);
}
.rounded-sm {
- border-radius: calc(var(--radius) - 4px);
+ border-radius: calc(var(--radius) - 4px);
}
.rounded-b-xl {
- border-bottom-right-radius: 0.75rem;
- border-bottom-left-radius: 0.75rem;
+ border-bottom-right-radius: 0.75rem;
+ border-bottom-left-radius: 0.75rem;
}
.rounded-l-2xl {
- border-top-left-radius: 1rem;
- border-bottom-left-radius: 1rem;
+ border-top-left-radius: 1rem;
+ border-bottom-left-radius: 1rem;
}
.rounded-t-\[10px\] {
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
}
.border {
- border-width: 1px;
+ border-width: 1px;
}
.border-2 {
- border-width: 2px;
+ border-width: 2px;
}
.border-l {
- border-left-width: 1px;
+ border-left-width: 1px;
}
.border-t {
- border-top-width: 1px;
+ border-top-width: 1px;
}
.border-border {
- border-color: var(--border);
+ border-color: var(--border);
}
.border-destructive {
- border-color: hsl(var(--destructive));
+ border-color: hsl(var(--destructive));
}
.border-input {
- border-color: hsl(var(--input));
+ border-color: hsl(var(--input));
}
.border-transparent {
- border-color: transparent;
+ border-color: transparent;
}
.border-white\/10 {
- border-color: rgb(255 255 255 / 0.1);
+ border-color: rgb(255 255 255 / 0.1);
}
.border-white\/30 {
- border-color: rgb(255 255 255 / 0.3);
+ border-color: rgb(255 255 255 / 0.3);
}
.border-l-transparent {
- border-left-color: transparent;
+ border-left-color: transparent;
}
.border-t-transparent {
- border-top-color: transparent;
+ border-top-color: transparent;
}
.bg-\[\#2D343A\] {
- --tw-bg-opacity: 1;
- background-color: rgb(45 52 58 / var(--tw-bg-opacity));
+ --tw-bg-opacity: 1;
+ background-color: rgb(45 52 58 / var(--tw-bg-opacity));
}
.bg-background {
- background-color: var(--background);
+ background-color: var(--background);
}
.bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
.bg-black\/20 {
- background-color: rgb(0 0 0 / 0.2);
+ background-color: rgb(0 0 0 / 0.2);
}
.bg-black\/80 {
- background-color: rgb(0 0 0 / 0.8);
+ background-color: rgb(0 0 0 / 0.8);
}
.bg-border {
- background-color: var(--border);
+ background-color: var(--border);
}
.bg-card {
- background-color: hsl(var(--card));
+ background-color: hsl(var(--card));
}
.bg-destructive {
- background-color: hsl(var(--destructive));
+ background-color: hsl(var(--destructive));
}
.bg-muted {
- background-color: hsl(var(--muted));
+ background-color: hsl(var(--muted));
}
.bg-popover {
- background-color: hsl(var(--popover));
+ background-color: hsl(var(--popover));
}
.bg-primary {
- background-color: var(--primary);
+ background-color: var(--primary);
}
.bg-secondary {
- background-color: var(--secondary);
+ background-color: var(--secondary);
}
.bg-slate-700 {
- --tw-bg-opacity: 1;
- background-color: rgb(51 65 85 / var(--tw-bg-opacity));
+ --tw-bg-opacity: 1;
+ background-color: rgb(51 65 85 / var(--tw-bg-opacity));
}
.bg-transparent {
- background-color: transparent;
+ background-color: transparent;
}
.bg-page-gradient {
- background-image: radial-gradient(
- ellipse 80% 50% at 50% -20%,
- rgba(120, 119, 198, 0.3),
- transparent
- );
+ background-image: radial-gradient(
+ ellipse 80% 50% at 50% -20%,
+ rgba(120, 119, 198, 0.3),
+ transparent
+ );
}
.fill-current {
- fill: currentColor;
+ fill: currentColor;
}
.stroke-2 {
- stroke-width: 2;
+ stroke-width: 2;
}
.p-0 {
- padding: 0px;
+ padding: 0px;
}
.p-1 {
- padding: 0.25rem;
+ padding: 0.25rem;
}
.p-2 {
- padding: 0.5rem;
+ padding: 0.5rem;
}
.p-4 {
- padding: 1rem;
+ padding: 1rem;
}
.p-6 {
- padding: 1.5rem;
+ padding: 1.5rem;
}
.p-\[1px\] {
- padding: 1px;
+ padding: 1px;
}
.px-2 {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
}
.px-2\.5 {
- padding-left: 0.625rem;
- padding-right: 0.625rem;
+ padding-left: 0.625rem;
+ padding-right: 0.625rem;
}
.px-3 {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
}
.px-4 {
- padding-left: 1rem;
- padding-right: 1rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
}
.px-6 {
- padding-left: 1.5rem;
- padding-right: 1.5rem;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
}
.px-8 {
- padding-left: 2rem;
- padding-right: 2rem;
+ padding-left: 2rem;
+ padding-right: 2rem;
}
.py-0 {
- padding-top: 0px;
- padding-bottom: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
}
.py-0\.5 {
- padding-top: 0.125rem;
- padding-bottom: 0.125rem;
+ padding-top: 0.125rem;
+ padding-bottom: 0.125rem;
}
.py-1 {
- padding-top: 0.25rem;
- padding-bottom: 0.25rem;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
}
.py-1\.5 {
- padding-top: 0.375rem;
- padding-bottom: 0.375rem;
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
}
.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
}
.py-3 {
- padding-top: 0.75rem;
- padding-bottom: 0.75rem;
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
}
.py-4 {
- padding-top: 1rem;
- padding-bottom: 1rem;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
}
.py-6 {
- padding-top: 1.5rem;
- padding-bottom: 1.5rem;
+ padding-top: 1.5rem;
+ padding-bottom: 1.5rem;
}
.pb-4 {
- padding-bottom: 1rem;
+ padding-bottom: 1rem;
}
.pl-8 {
- padding-left: 2rem;
+ padding-left: 2rem;
}
.pr-2 {
- padding-right: 0.5rem;
+ padding-right: 0.5rem;
}
.pr-8 {
- padding-right: 2rem;
+ padding-right: 2rem;
}
.pt-0 {
- padding-top: 0px;
+ padding-top: 0px;
}
.text-left {
- text-align: left;
+ text-align: left;
}
.text-center {
- text-align: center;
+ text-align: center;
}
.text-2xl {
- font-size: 1.5rem;
- line-height: 2rem;
+ font-size: 1.5rem;
+ line-height: 2rem;
}
.text-lg {
- font-size: 1.125rem;
- line-height: 1.75rem;
+ font-size: 1.125rem;
+ line-height: 1.75rem;
}
.text-sm {
- font-size: 0.875rem;
- line-height: 1.25rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
}
.text-xs {
- font-size: 0.75rem;
- line-height: 1rem;
+ font-size: 0.75rem;
+ line-height: 1rem;
}
.font-medium {
- font-weight: 500;
+ font-weight: 500;
}
.font-semibold {
- font-weight: 600;
+ font-weight: 600;
}
.leading-none {
- line-height: 1;
+ line-height: 1;
}
.tracking-tight {
- letter-spacing: -0.025em;
+ letter-spacing: -0.025em;
}
.tracking-widest {
- letter-spacing: 0.1em;
+ letter-spacing: 0.1em;
}
.text-card-foreground {
- color: hsl(var(--card-foreground));
+ color: hsl(var(--card-foreground));
}
.text-destructive {
- color: hsl(var(--destructive));
+ color: hsl(var(--destructive));
}
.text-destructive-foreground {
- color: hsl(var(--destructive-foreground));
+ color: hsl(var(--destructive-foreground));
}
.text-foreground {
- color: var(--foreground);
+ color: var(--foreground);
}
.text-green-500 {
- --tw-text-opacity: 1;
- color: rgb(34 197 94 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(34 197 94 / var(--tw-text-opacity));
}
.text-muted-foreground {
- color: hsl(var(--muted-foreground));
+ color: hsl(var(--muted-foreground));
}
.text-popover-foreground {
- color: hsl(var(--popover-foreground));
+ color: hsl(var(--popover-foreground));
}
.text-primary {
- color: var(--primary);
+ color: var(--primary);
}
.text-slate-400 {
- --tw-text-opacity: 1;
- color: rgb(148 163 184 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(148 163 184 / var(--tw-text-opacity));
}
.text-white {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-zinc-200 {
- --tw-text-opacity: 1;
- color: rgb(228 228 231 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(228 228 231 / var(--tw-text-opacity));
}
.text-zinc-500 {
- --tw-text-opacity: 1;
- color: rgb(113 113 122 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(113 113 122 / var(--tw-text-opacity));
}
.underline-offset-4 {
- text-underline-offset: 4px;
+ text-underline-offset: 4px;
}
.opacity-0 {
- opacity: 0;
+ opacity: 0;
}
.opacity-100 {
- opacity: 1;
+ opacity: 1;
}
.opacity-50 {
- opacity: 0.5;
+ opacity: 0.5;
}
.opacity-60 {
- opacity: 0.6;
+ opacity: 0.6;
}
.opacity-70 {
- opacity: 0.7;
+ opacity: 0.7;
}
.opacity-75 {
- opacity: 0.75;
+ opacity: 0.75;
}
.opacity-90 {
- opacity: 0.9;
+ opacity: 0.9;
}
.shadow {
- --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
- var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
+ var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-lg {
- --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
- var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
+ 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
+ var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-md {
- --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
- var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
+ 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
+ var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-sm {
- --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
- var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
+ var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.outline-none {
- outline: 2px solid transparent;
- outline-offset: 2px;
+ outline: 2px solid transparent;
+ outline-offset: 2px;
}
.outline {
- outline-style: solid;
+ outline-style: solid;
}
.ring-0 {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
- var(--tw-ring-offset-width) var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
- calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
- var(--tw-shadow, 0 0 #0000);
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
+ var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
+ calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
}
.ring-offset-background {
- --tw-ring-offset-color: var(--background);
+ --tw-ring-offset-color: var(--background);
}
.filter {
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast)
- var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate)
- var(--tw-sepia) var(--tw-drop-shadow);
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast)
+ var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate)
+ var(--tw-sepia) var(--tw-drop-shadow);
}
.backdrop-blur-sm {
- --tw-backdrop-blur: blur(4px);
- -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
- var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
- var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
- var(--tw-backdrop-sepia);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
- var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
- var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
- var(--tw-backdrop-sepia);
+ --tw-backdrop-blur: blur(4px);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
+ var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
+ var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
+ var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
+ var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
}
.transition {
- transition-property:
- color,
- background-color,
- border-color,
- text-decoration-color,
- fill,
- stroke,
- opacity,
- box-shadow,
- transform,
- filter,
- -webkit-backdrop-filter;
- transition-property: color, background-color, border-color,
- text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter,
- backdrop-filter;
- transition-property:
- color,
- background-color,
- border-color,
- text-decoration-color,
- fill,
- stroke,
- opacity,
- box-shadow,
- transform,
- filter,
- backdrop-filter,
- -webkit-backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
+ transition-property:
+ color,
+ background-color,
+ border-color,
+ text-decoration-color,
+ fill,
+ stroke,
+ opacity,
+ box-shadow,
+ transform,
+ filter,
+ -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color,
+ text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter,
+ backdrop-filter;
+ transition-property:
+ color,
+ background-color,
+ border-color,
+ text-decoration-color,
+ fill,
+ stroke,
+ opacity,
+ box-shadow,
+ transform,
+ filter,
+ backdrop-filter,
+ -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
}
.transition-all {
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
}
.transition-colors {
- transition-property: color, background-color, border-color,
- text-decoration-color, fill, stroke;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
+ transition-property: color, background-color, border-color,
+ text-decoration-color, fill, stroke;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
}
.transition-opacity {
- transition-property: opacity;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
+ transition-property: opacity;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
}
.transition-transform {
- transition-property: transform;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
+ transition-property: transform;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
}
.duration-200 {
- transition-duration: 200ms;
+ transition-duration: 200ms;
}
@keyframes enter {
- from {
- opacity: var(--tw-enter-opacity, 1);
- transform: translate3d(
- var(--tw-enter-translate-x, 0),
- var(--tw-enter-translate-y, 0),
- 0
- )
- scale3d(
- var(--tw-enter-scale, 1),
- var(--tw-enter-scale, 1),
- var(--tw-enter-scale, 1)
- )
- rotate(var(--tw-enter-rotate, 0));
- }
+ from {
+ opacity: var(--tw-enter-opacity, 1);
+ transform: translate3d(
+ var(--tw-enter-translate-x, 0),
+ var(--tw-enter-translate-y, 0),
+ 0
+ )
+ scale3d(
+ var(--tw-enter-scale, 1),
+ var(--tw-enter-scale, 1),
+ var(--tw-enter-scale, 1)
+ )
+ rotate(var(--tw-enter-rotate, 0));
+ }
}
@keyframes exit {
- to {
- opacity: var(--tw-exit-opacity, 1);
- transform: translate3d(
- var(--tw-exit-translate-x, 0),
- var(--tw-exit-translate-y, 0),
- 0
- )
- scale3d(
- var(--tw-exit-scale, 1),
- var(--tw-exit-scale, 1),
- var(--tw-exit-scale, 1)
- )
- rotate(var(--tw-exit-rotate, 0));
- }
+ to {
+ opacity: var(--tw-exit-opacity, 1);
+ transform: translate3d(
+ var(--tw-exit-translate-x, 0),
+ var(--tw-exit-translate-y, 0),
+ 0
+ )
+ scale3d(
+ var(--tw-exit-scale, 1),
+ var(--tw-exit-scale, 1),
+ var(--tw-exit-scale, 1)
+ )
+ rotate(var(--tw-exit-rotate, 0));
+ }
}
.animate-in {
- animation-name: enter;
- animation-duration: 150ms;
- --tw-enter-opacity: initial;
- --tw-enter-scale: initial;
- --tw-enter-rotate: initial;
- --tw-enter-translate-x: initial;
- --tw-enter-translate-y: initial;
+ animation-name: enter;
+ animation-duration: 150ms;
+ --tw-enter-opacity: initial;
+ --tw-enter-scale: initial;
+ --tw-enter-rotate: initial;
+ --tw-enter-translate-x: initial;
+ --tw-enter-translate-y: initial;
}
.fade-in-0 {
- --tw-enter-opacity: 0;
+ --tw-enter-opacity: 0;
}
.zoom-in-95 {
- --tw-enter-scale: 0.95;
+ --tw-enter-scale: 0.95;
}
.duration-200 {
- animation-duration: 200ms;
+ animation-duration: 200ms;
}
.\[background\:linear-gradient\(\#2E2E32\2c
- \#2E2E32\)\2c
- linear-gradient\(120deg\2c
- theme\(colors\.zinc\.700\)\2c
- theme\(colors\.zinc\.700\/0\)\2c
- theme\(colors\.zinc\.700\)\)\] {
- background: linear-gradient(#2e2e32, #2e2e32),
- linear-gradient(120deg, #3f3f46, rgb(63 63 70 / 0), #3f3f46);
+ \#2E2E32\)\2c
+ linear-gradient\(120deg\2c
+ theme\(colors\.zinc\.700\)\2c
+ theme\(colors\.zinc\.700\/0\)\2c
+ theme\(colors\.zinc\.700\)\)\] {
+ background: linear-gradient(#2e2e32, #2e2e32),
+ linear-gradient(120deg, #3f3f46, rgb(63 63 70 / 0), #3f3f46);
}
:root {
- --foreground: rgba(179, 188, 197, 1);
- --foreground-menu: rgba(106, 115, 125, 1);
- --background: rgba(31, 36, 40, 1);
- --secondary: rgb(45, 51, 58);
- --primary: rgba(54, 157, 253, 1);
- --border: rgba(62, 68, 73, 1);
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary-foreground: 0 0% 98%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --radius: 0.5rem;
+ --foreground: rgba(179, 188, 197, 1);
+ --foreground-menu: rgba(106, 115, 125, 1);
+ --background: rgba(31, 36, 40, 1);
+ --secondary: rgb(45, 51, 58);
+ --primary: rgba(54, 157, 253, 1);
+ --border: rgba(62, 68, 73, 1);
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --radius: 0.5rem;
}
:host {
- --foreground: rgba(179, 188, 197, 1);
- --foreground-menu: rgba(106, 115, 125, 1);
- --background: rgba(31, 36, 40, 1);
- --secondary: rgb(45, 51, 58);
- --primary: rgba(54, 157, 253, 1);
- --border: rgba(62, 68, 73, 1);
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary-foreground: 0 0% 98%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --radius: 0.5rem;
+ --foreground: rgba(179, 188, 197, 1);
+ --foreground-menu: rgba(106, 115, 125, 1);
+ --background: rgba(31, 36, 40, 1);
+ --secondary: rgb(45, 51, 58);
+ --primary: rgba(54, 157, 253, 1);
+ --border: rgba(62, 68, 73, 1);
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --radius: 0.5rem;
}
body {
- color: var(--foreground);
- background: var(--background);
- font-size: 14px;
+ color: var(--foreground);
+ background: var(--background);
+ font-size: 14px;
}
.gradient-background {
- background: linear-gradient(
- 150deg,
- rgba(255, 255, 255, 0.1) 0%,
- rgba(255, 255, 255, 0)
- );
+ background: linear-gradient(
+ 150deg,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0)
+ );
}
.no-scrollbar {
- /* For WebKit (Safari, Chrome, etc.) */
- &::-webkit-scrollbar {
- display: none;
- }
- /* For Firefox */
- scrollbar-width: none;
- /* For IE and Edge */
- -ms-overflow-style: none;
+ /* For WebKit (Safari, Chrome, etc.) */
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ /* For Firefox */
+ scrollbar-width: none;
+ /* For IE and Edge */
+ -ms-overflow-style: none;
}
:not(pre) > code.hljs,
:not(pre) > code[class*="language-"] {
- border-radius: 0.3em;
- white-space: normal;
+ border-radius: 0.3em;
+ white-space: normal;
}
.hljs-comment {
- color: hsla(0, 0%, 100%, 0.5);
+ color: hsla(0, 0%, 100%, 0.5);
}
.hljs-meta {
- color: hsla(0, 0%, 100%, 0.6);
+ color: hsla(0, 0%, 100%, 0.6);
}
.hljs-built_in,
.hljs-class .hljs-title {
- color: #e9950c;
+ color: #e9950c;
}
.hljs-doctag,
.hljs-formula,
.hljs-keyword,
.hljs-literal {
- color: #2e95d3;
+ color: #2e95d3;
}
.hljs-addition,
@@ -1807,7 +1807,7 @@ body {
.hljs-meta-string,
.hljs-regexp,
.hljs-string {
- color: #00a67d;
+ color: #00a67d;
}
.hljs-attr,
@@ -1818,7 +1818,7 @@ body {
.hljs-template-variable,
.hljs-type,
.hljs-variable {
- color: #df3079;
+ color: #df3079;
}
.hljs-bullet,
@@ -1826,686 +1826,686 @@ body {
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
- color: #f22c3d;
+ color: #f22c3d;
}
/* no scrollbar visibility */
.no-scrollbar::-webkit-scrollbar {
- width: 0px;
+ width: 0px;
}
::-moz-selection {
- /* Code for Firefox */
- color: #369dfd;
- background: #21303d;
+ /* Code for Firefox */
+ color: #369dfd;
+ background: #21303d;
}
::selection {
- color: #369dfd;
- background: #21303d;
+ color: #369dfd;
+ background: #21303d;
}
.file\:border-0::file-selector-button {
- border-width: 0px;
+ border-width: 0px;
}
.file\:bg-transparent::file-selector-button {
- background-color: transparent;
+ background-color: transparent;
}
.file\:text-sm::file-selector-button {
- font-size: 0.875rem;
- line-height: 1.25rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
}
.file\:font-medium::file-selector-button {
- font-weight: 500;
+ font-weight: 500;
}
.placeholder\:font-semibold::-moz-placeholder {
- font-weight: 600;
+ font-weight: 600;
}
.placeholder\:font-semibold::placeholder {
- font-weight: 600;
+ font-weight: 600;
}
.placeholder\:text-muted::-moz-placeholder {
- color: hsl(var(--muted));
+ color: hsl(var(--muted));
}
.placeholder\:text-muted::placeholder {
- color: hsl(var(--muted));
+ color: hsl(var(--muted));
}
.placeholder\:text-muted-foreground::-moz-placeholder {
- color: hsl(var(--muted-foreground));
+ color: hsl(var(--muted-foreground));
}
.placeholder\:text-muted-foreground::placeholder {
- color: hsl(var(--muted-foreground));
+ color: hsl(var(--muted-foreground));
}
.placeholder\:text-white::-moz-placeholder {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
}
.placeholder\:text-white::placeholder {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
}
.focus-within\:translate-x-0:focus-within {
- --tw-translate-x: 0px;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.group:focus-within .group-focus-within\:block {
- display: block;
+ display: block;
}
.group:focus-within .group-focus-within\:opacity-100 {
- opacity: 1;
+ opacity: 1;
}
.group:hover .group-hover\:opacity-100 {
- opacity: 1;
+ opacity: 1;
}
.group.destructive .group-\[\.destructive\]\:border-muted\/40 {
- border-color: hsl(var(--muted) / 0.4);
+ border-color: hsl(var(--muted) / 0.4);
}
.group.toaster .group-\[\.toaster\]\:border-border {
- border-color: var(--border);
+ border-color: var(--border);
}
.group.toast .group-\[\.toast\]\:bg-muted {
- background-color: hsl(var(--muted));
+ background-color: hsl(var(--muted));
}
.group.toast .group-\[\.toast\]\:bg-primary {
- background-color: var(--primary);
+ background-color: var(--primary);
}
.group.toaster .group-\[\.toaster\]\:bg-background {
- background-color: var(--background);
+ background-color: var(--background);
}
.group.destructive .group-\[\.destructive\]\:text-red-300 {
- --tw-text-opacity: 1;
- color: rgb(252 165 165 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(252 165 165 / var(--tw-text-opacity));
}
.group.toast .group-\[\.toast\]\:text-muted-foreground {
- color: hsl(var(--muted-foreground));
+ color: hsl(var(--muted-foreground));
}
.group.toaster .group-\[\.toaster\]\:text-foreground {
- color: var(--foreground);
+ color: var(--foreground);
}
.group.toaster .group-\[\.toaster\]\:shadow-lg {
- --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
- var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
+ 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
+ var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.peer:disabled ~ .peer-disabled\:cursor-not-allowed {
- cursor: not-allowed;
+ cursor: not-allowed;
}
.peer:disabled ~ .peer-disabled\:opacity-70 {
- opacity: 0.7;
+ opacity: 0.7;
}
.aria-selected\:bg-accent[aria-selected="true"] {
- background-color: hsl(var(--accent));
+ background-color: hsl(var(--accent));
}
.aria-selected\:text-accent-foreground[aria-selected="true"] {
- color: hsl(var(--accent-foreground));
+ color: hsl(var(--accent-foreground));
}
.data-\[disabled\=true\]\:pointer-events-none[data-disabled="true"] {
- pointer-events: none;
+ pointer-events: none;
}
.data-\[disabled\]\:pointer-events-none[data-disabled] {
- pointer-events: none;
+ pointer-events: none;
}
.data-\[side\=bottom\]\:translate-y-1[data-side="bottom"] {
- --tw-translate-y: 0.25rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-y: 0.25rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[side\=left\]\:-translate-x-1[data-side="left"] {
- --tw-translate-x: -0.25rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: -0.25rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[side\=right\]\:translate-x-1[data-side="right"] {
- --tw-translate-x: 0.25rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 0.25rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[side\=top\]\:-translate-y-1[data-side="top"] {
- --tw-translate-y: -0.25rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-y: -0.25rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[state\=checked\]\:translate-x-5[data-state="checked"] {
- --tw-translate-x: 1.25rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 1.25rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[state\=unchecked\]\:translate-x-0[data-state="unchecked"] {
- --tw-translate-x: 0px;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[swipe\=cancel\]\:translate-x-0[data-swipe="cancel"] {
- --tw-translate-x: 0px;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe="end"] {
- --tw-translate-x: var(--radix-toast-swipe-end-x);
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: var(--radix-toast-swipe-end-x);
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe="move"] {
- --tw-translate-x: var(--radix-toast-swipe-move-x);
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: var(--radix-toast-swipe-move-x);
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@keyframes accordion-up {
- from {
- height: var(--radix-accordion-content-height);
- }
+ from {
+ height: var(--radix-accordion-content-height);
+ }
- to {
- height: 0;
- }
+ to {
+ height: 0;
+ }
}
.data-\[state\=closed\]\:animate-accordion-up[data-state="closed"] {
- animation: accordion-up 0.2s ease-out;
+ animation: accordion-up 0.2s ease-out;
}
@keyframes accordion-down {
- from {
- height: 0;
- }
+ from {
+ height: 0;
+ }
- to {
- height: var(--radix-accordion-content-height);
- }
+ to {
+ height: var(--radix-accordion-content-height);
+ }
}
.data-\[state\=open\]\:animate-accordion-down[data-state="open"] {
- animation: accordion-down 0.2s ease-out;
+ animation: accordion-down 0.2s ease-out;
}
.data-\[state\=active\]\:bg-background[data-state="active"] {
- background-color: var(--background);
+ background-color: var(--background);
}
.data-\[state\=checked\]\:bg-primary[data-state="checked"] {
- background-color: var(--primary);
+ background-color: var(--primary);
}
.data-\[state\=open\]\:bg-accent[data-state="open"] {
- background-color: hsl(var(--accent));
+ background-color: hsl(var(--accent));
}
.data-\[state\=open\]\:bg-secondary[data-state="open"] {
- background-color: var(--secondary);
+ background-color: var(--secondary);
}
.data-\[state\=unchecked\]\:bg-input[data-state="unchecked"] {
- background-color: hsl(var(--input));
+ background-color: hsl(var(--input));
}
.data-\[state\=active\]\:text-foreground[data-state="active"] {
- color: var(--foreground);
+ color: var(--foreground);
}
.data-\[state\=open\]\:text-muted-foreground[data-state="open"] {
- color: hsl(var(--muted-foreground));
+ color: hsl(var(--muted-foreground));
}
.data-\[disabled\=true\]\:opacity-50[data-disabled="true"] {
- opacity: 0.5;
+ opacity: 0.5;
}
.data-\[disabled\]\:opacity-50[data-disabled] {
- opacity: 0.5;
+ opacity: 0.5;
}
.data-\[state\=active\]\:shadow-sm[data-state="active"] {
- --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
- var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
+ var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.data-\[swipe\=move\]\:transition-none[data-swipe="move"] {
- transition-property: none;
+ transition-property: none;
}
.data-\[state\=open\]\:animate-in[data-state="open"] {
- animation-name: enter;
- animation-duration: 150ms;
- --tw-enter-opacity: initial;
- --tw-enter-scale: initial;
- --tw-enter-rotate: initial;
- --tw-enter-translate-x: initial;
- --tw-enter-translate-y: initial;
+ animation-name: enter;
+ animation-duration: 150ms;
+ --tw-enter-opacity: initial;
+ --tw-enter-scale: initial;
+ --tw-enter-rotate: initial;
+ --tw-enter-translate-x: initial;
+ --tw-enter-translate-y: initial;
}
.data-\[state\=closed\]\:animate-out[data-state="closed"] {
- animation-name: exit;
- animation-duration: 150ms;
- --tw-exit-opacity: initial;
- --tw-exit-scale: initial;
- --tw-exit-rotate: initial;
- --tw-exit-translate-x: initial;
- --tw-exit-translate-y: initial;
+ animation-name: exit;
+ animation-duration: 150ms;
+ --tw-exit-opacity: initial;
+ --tw-exit-scale: initial;
+ --tw-exit-rotate: initial;
+ --tw-exit-translate-x: initial;
+ --tw-exit-translate-y: initial;
}
.data-\[swipe\=end\]\:animate-out[data-swipe="end"] {
- animation-name: exit;
- animation-duration: 150ms;
- --tw-exit-opacity: initial;
- --tw-exit-scale: initial;
- --tw-exit-rotate: initial;
- --tw-exit-translate-x: initial;
- --tw-exit-translate-y: initial;
+ animation-name: exit;
+ animation-duration: 150ms;
+ --tw-exit-opacity: initial;
+ --tw-exit-scale: initial;
+ --tw-exit-rotate: initial;
+ --tw-exit-translate-x: initial;
+ --tw-exit-translate-y: initial;
}
.data-\[state\=closed\]\:fade-out-0[data-state="closed"] {
- --tw-exit-opacity: 0;
+ --tw-exit-opacity: 0;
}
.data-\[state\=closed\]\:fade-out-80[data-state="closed"] {
- --tw-exit-opacity: 0.8;
+ --tw-exit-opacity: 0.8;
}
.data-\[state\=open\]\:fade-in-0[data-state="open"] {
- --tw-enter-opacity: 0;
+ --tw-enter-opacity: 0;
}
.data-\[state\=closed\]\:zoom-out-95[data-state="closed"] {
- --tw-exit-scale: 0.95;
+ --tw-exit-scale: 0.95;
}
.data-\[state\=open\]\:zoom-in-95[data-state="open"] {
- --tw-enter-scale: 0.95;
+ --tw-enter-scale: 0.95;
}
.data-\[side\=bottom\]\:slide-in-from-top-2[data-side="bottom"] {
- --tw-enter-translate-y: -0.5rem;
+ --tw-enter-translate-y: -0.5rem;
}
.data-\[side\=left\]\:slide-in-from-right-2[data-side="left"] {
- --tw-enter-translate-x: 0.5rem;
+ --tw-enter-translate-x: 0.5rem;
}
.data-\[side\=right\]\:slide-in-from-left-2[data-side="right"] {
- --tw-enter-translate-x: -0.5rem;
+ --tw-enter-translate-x: -0.5rem;
}
.data-\[side\=top\]\:slide-in-from-bottom-2[data-side="top"] {
- --tw-enter-translate-y: 0.5rem;
+ --tw-enter-translate-y: 0.5rem;
}
.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state="closed"] {
- --tw-exit-translate-x: -50%;
+ --tw-exit-translate-x: -50%;
}
.data-\[state\=closed\]\:slide-out-to-right-full[data-state="closed"] {
- --tw-exit-translate-x: 100%;
+ --tw-exit-translate-x: 100%;
}
.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state="closed"] {
- --tw-exit-translate-y: -48%;
+ --tw-exit-translate-y: -48%;
}
.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state="open"] {
- --tw-enter-translate-x: -50%;
+ --tw-enter-translate-x: -50%;
}
.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state="open"] {
- --tw-enter-translate-y: -48%;
+ --tw-enter-translate-y: -48%;
}
.data-\[state\=open\]\:slide-in-from-top-full[data-state="open"] {
- --tw-enter-translate-y: -100%;
+ --tw-enter-translate-y: -100%;
}
.hover\:border:hover {
- border-width: 1px;
+ border-width: 1px;
}
.hover\:border-white\/20:hover {
- border-color: rgb(255 255 255 / 0.2);
+ border-color: rgb(255 255 255 / 0.2);
}
.hover\:bg-accent:hover {
- background-color: hsl(var(--accent));
+ background-color: hsl(var(--accent));
}
.hover\:bg-black:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
.hover\:bg-destructive\/80:hover {
- background-color: hsl(var(--destructive) / 0.8);
+ background-color: hsl(var(--destructive) / 0.8);
}
.hover\:bg-destructive\/90:hover {
- background-color: hsl(var(--destructive) / 0.9);
+ background-color: hsl(var(--destructive) / 0.9);
}
.hover\:bg-secondary:hover {
- background-color: var(--secondary);
+ background-color: var(--secondary);
}
.hover\:bg-page-gradient:hover {
- background-image: radial-gradient(
- ellipse 80% 50% at 50% -20%,
- rgba(120, 119, 198, 0.3),
- transparent
- );
+ background-image: radial-gradient(
+ ellipse 80% 50% at 50% -20%,
+ rgba(120, 119, 198, 0.3),
+ transparent
+ );
}
.hover\:text-accent-foreground:hover {
- color: hsl(var(--accent-foreground));
+ color: hsl(var(--accent-foreground));
}
.hover\:text-foreground:hover {
- color: var(--foreground);
+ color: var(--foreground);
}
.hover\:underline:hover {
- text-decoration-line: underline;
+ text-decoration-line: underline;
}
.hover\:opacity-100:hover {
- opacity: 1;
+ opacity: 1;
}
.group.destructive
- .group-\[\.destructive\]\:hover\:border-destructive\/30:hover {
- border-color: hsl(var(--destructive) / 0.3);
+ .group-\[\.destructive\]\:hover\:border-destructive\/30:hover {
+ border-color: hsl(var(--destructive) / 0.3);
}
.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover {
- background-color: hsl(var(--destructive));
+ background-color: hsl(var(--destructive));
}
.group.destructive
- .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover {
- color: hsl(var(--destructive-foreground));
+ .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover {
+ color: hsl(var(--destructive-foreground));
}
.group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover {
- --tw-text-opacity: 1;
- color: rgb(254 242 242 / var(--tw-text-opacity));
+ --tw-text-opacity: 1;
+ color: rgb(254 242 242 / var(--tw-text-opacity));
}
.focus\:bg-accent:focus {
- background-color: hsl(var(--accent));
+ background-color: hsl(var(--accent));
}
.focus\:bg-foreground-menu:focus {
- background-color: var(--foreground-menu);
+ background-color: var(--foreground-menu);
}
.focus\:bg-secondary:focus {
- background-color: var(--secondary);
+ background-color: var(--secondary);
}
.focus\:text-accent-foreground:focus {
- color: hsl(var(--accent-foreground));
+ color: hsl(var(--accent-foreground));
}
.focus\:opacity-100:focus {
- opacity: 1;
+ opacity: 1;
}
.focus\:outline-none:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
+ outline: 2px solid transparent;
+ outline-offset: 2px;
}
.focus\:ring-2:focus {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
- var(--tw-ring-offset-width) var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
- calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
- var(--tw-shadow, 0 0 #0000);
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
+ var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
+ calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-ring:focus {
- --tw-ring-color: hsl(var(--ring));
+ --tw-ring-color: hsl(var(--ring));
}
.focus\:ring-offset-2:focus {
- --tw-ring-offset-width: 2px;
+ --tw-ring-offset-width: 2px;
}
.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus {
- --tw-ring-color: hsl(var(--destructive));
+ --tw-ring-color: hsl(var(--destructive));
}
.group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity));
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity));
}
.group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus {
- --tw-ring-offset-color: #dc2626;
+ --tw-ring-offset-color: #dc2626;
}
.focus-visible\:translate-x-0:focus-visible {
- --tw-translate-x: 0px;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.focus-visible\:outline-none:focus-visible {
- outline: 2px solid transparent;
- outline-offset: 2px;
+ outline: 2px solid transparent;
+ outline-offset: 2px;
}
.focus-visible\:ring-2:focus-visible {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
- var(--tw-ring-offset-width) var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
- calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
- var(--tw-shadow, 0 0 #0000);
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
+ var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
+ calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
}
.focus-visible\:ring-ring:focus-visible {
- --tw-ring-color: hsl(var(--ring));
+ --tw-ring-color: hsl(var(--ring));
}
.focus-visible\:ring-offset-2:focus-visible {
- --tw-ring-offset-width: 2px;
+ --tw-ring-offset-width: 2px;
}
.focus-visible\:ring-offset-background:focus-visible {
- --tw-ring-offset-color: var(--background);
+ --tw-ring-offset-color: var(--background);
}
.disabled\:pointer-events-none:disabled {
- pointer-events: none;
+ pointer-events: none;
}
.disabled\:cursor-not-allowed:disabled {
- cursor: not-allowed;
+ cursor: not-allowed;
}
.disabled\:bg-slate-700\/50:disabled {
- background-color: rgb(51 65 85 / 0.5);
+ background-color: rgb(51 65 85 / 0.5);
}
.disabled\:opacity-50:disabled {
- opacity: 0.5;
+ opacity: 0.5;
}
@media (min-width: 640px) {
- .sm\:bottom-0 {
- bottom: 0px;
- }
+ .sm\:bottom-0 {
+ bottom: 0px;
+ }
- .sm\:right-0 {
- right: 0px;
- }
+ .sm\:right-0 {
+ right: 0px;
+ }
- .sm\:top-auto {
- top: auto;
- }
+ .sm\:top-auto {
+ top: auto;
+ }
- .sm\:flex-row {
- flex-direction: row;
- }
+ .sm\:flex-row {
+ flex-direction: row;
+ }
- .sm\:flex-col {
- flex-direction: column;
- }
+ .sm\:flex-col {
+ flex-direction: column;
+ }
- .sm\:justify-end {
- justify-content: flex-end;
- }
+ .sm\:justify-end {
+ justify-content: flex-end;
+ }
- .sm\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-right: calc(0.5rem * var(--tw-space-x-reverse));
- margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
- }
+ .sm\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+ }
- .sm\:text-left {
- text-align: left;
- }
+ .sm\:text-left {
+ text-align: left;
+ }
- .data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state="open"] {
- --tw-enter-translate-y: 100%;
- }
+ .data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state="open"] {
+ --tw-enter-translate-y: 100%;
+ }
}
@media (min-width: 768px) {
- .md\:max-w-\[420px\] {
- max-width: 420px;
- }
+ .md\:max-w-\[420px\] {
+ max-width: 420px;
+ }
}
.\[\&\>span\]\:line-clamp-1 > span {
- overflow: hidden;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 1;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
}
.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state="open"] > svg {
- --tw-rotate: 180deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y))
- rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
- scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ --tw-rotate: 180deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading] {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
}
.\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading] {
- padding-top: 0.375rem;
- padding-bottom: 0.375rem;
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
}
.\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading] {
- font-size: 0.75rem;
- line-height: 1rem;
+ font-size: 0.75rem;
+ line-height: 1rem;
}
.\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading] {
- font-weight: 500;
+ font-weight: 500;
}
.\[\&_\[cmdk-group-heading\]\]\:text-muted-foreground [cmdk-group-heading] {
- color: hsl(var(--muted-foreground));
+ color: hsl(var(--muted-foreground));
}
.\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0
- [cmdk-group]:not([hidden])
- ~ [cmdk-group] {
- padding-top: 0px;
+ [cmdk-group]:not([hidden])
+ ~ [cmdk-group] {
+ padding-top: 0px;
}
.\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group] {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
}
.\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg {
- height: 1.25rem;
+ height: 1.25rem;
}
.\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg {
- width: 1.25rem;
+ width: 1.25rem;
}
.\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input] {
- height: 3rem;
+ height: 3rem;
}
.\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item] {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
}
.\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item] {
- padding-top: 0.75rem;
- padding-bottom: 0.75rem;
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
}
.\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg {
- height: 1.25rem;
+ height: 1.25rem;
}
.\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg {
- width: 1.25rem;
+ width: 1.25rem;
}
diff --git a/apps/extension/tsconfig.json b/apps/extension/tsconfig.json
index 520f3602..dc1f8aa6 100644
--- a/apps/extension/tsconfig.json
+++ b/apps/extension/tsconfig.json
@@ -1,18 +1,18 @@
{
- "compilerOptions": {
- "allowJs": true,
- "allowSyntheticDefaultImports": true,
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "isolatedModules": false,
- "jsx": "react-jsx",
- "lib": ["dom", "dom.iterable", "esnext"],
- "moduleResolution": "node",
- "module": "esnext",
- "resolveJsonModule": true,
- "strict": true,
- "target": "esnext"
- },
- "include": ["./"],
- "exclude": ["node_modules", "dist"]
+ "compilerOptions": {
+ "allowJs": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": false,
+ "jsx": "react-jsx",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "moduleResolution": "node",
+ "module": "esnext",
+ "resolveJsonModule": true,
+ "strict": true,
+ "target": "esnext"
+ },
+ "include": ["./"],
+ "exclude": ["node_modules", "dist"]
}
diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js
index 7d644a4c..83d36d43 100644
--- a/apps/web/.eslintrc.js
+++ b/apps/web/.eslintrc.js
@@ -1,9 +1,9 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
- root: true,
- extends: ["@repo/eslint-config/next.js"],
- parser: "@typescript-eslint/parser",
- parserOptions: {
- project: true,
- },
+ root: true,
+ extends: ["@repo/eslint-config/next.js"],
+ parser: "@typescript-eslint/parser",
+ parserOptions: {
+ project: true,
+ },
};
diff --git a/apps/web/app/(auth)/privacy/page.tsx b/apps/web/app/(auth)/privacy/page.tsx
index 2712dbb2..d9ac8c14 100644
--- a/apps/web/app/(auth)/privacy/page.tsx
+++ b/apps/web/app/(auth)/privacy/page.tsx
@@ -2,11 +2,11 @@ import React from "react";
import Markdown from "react-markdown";
function Page() {
- return (
- <div className="flex flex-col items-center justify-center mt-8">
- <div className="max-w-3xl prose prose-invert">
- <Markdown>
- {`
+ return (
+ <div className="flex flex-col items-center justify-center mt-8">
+ <div className="max-w-3xl prose prose-invert">
+ <Markdown>
+ {`
Privacy Policy for Supermemory.ai
# Introduction
This Privacy Policy provides detailed information on the handling, storage, and protection of your personal information by Supermemory.ai - A web app and a browser extension developed and owned by Dhravya Shah and Supermemory team in 2024. The extension is designed to enhance your browsing experience by providing contextual information based on the content of the web pages you visit. This policy outlines the types of data collected by Supermemory.ai, how it is used, and the measures we take to protect your privacy.
@@ -63,10 +63,10 @@ Email: [email protected]
This document was last updated on July 4, 2024.
`}
- </Markdown>
- </div>
- </div>
- );
+ </Markdown>
+ </div>
+ </div>
+ );
}
export default Page;
diff --git a/apps/web/app/(auth)/signin/_components/TextGradient/gradient.module.css b/apps/web/app/(auth)/signin/_components/TextGradient/gradient.module.css
index b9afc7d1..c645572d 100644
--- a/apps/web/app/(auth)/signin/_components/TextGradient/gradient.module.css
+++ b/apps/web/app/(auth)/signin/_components/TextGradient/gradient.module.css
@@ -1,70 +1,70 @@
@keyframes background-pan {
- from {
- background-position: 0% center;
- }
-
- to {
- background-position: -200% center;
- }
- }
-
- @keyframes scale {
- from,
- to {
- transform: scale(0);
- }
-
- 50% {
- transform: scale(1);
- }
- }
-
- @keyframes rotate {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(180deg);
- }
- }
-
- .star {
- --size: clamp(20px, 1.5vw, 30px);
-
- animation: scale 700ms ease forwards;
- display: block;
- height: var(--size);
- left: var(--star-left);
- position: absolute;
- top: var(--star-top);
- width: var(--size);
- }
-
- .star > svg {
- animation: rotate 1000ms linear infinite;
- display: block;
- opacity: 0.7;
- }
-
- .star > svg > path {
- fill: #7b3907;
- }
-
- .magicText {
- --purple: rgba(72, 130, 244, 0.868);
- --violet: #9a80f7;
- --pink: #f7f7f7;
- animation: background-pan 3s linear infinite;
- background: linear-gradient(
- to right,
- var(--purple),
- var(--violet),
- var(--pink),
- var(--purple)
- );
- background-size: 200%;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- white-space: nowrap;
- } \ No newline at end of file
+ from {
+ background-position: 0% center;
+ }
+
+ to {
+ background-position: -200% center;
+ }
+}
+
+@keyframes scale {
+ from,
+ to {
+ transform: scale(0);
+ }
+
+ 50% {
+ transform: scale(1);
+ }
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(180deg);
+ }
+}
+
+.star {
+ --size: clamp(20px, 1.5vw, 30px);
+
+ animation: scale 700ms ease forwards;
+ display: block;
+ height: var(--size);
+ left: var(--star-left);
+ position: absolute;
+ top: var(--star-top);
+ width: var(--size);
+}
+
+.star > svg {
+ animation: rotate 1000ms linear infinite;
+ display: block;
+ opacity: 0.7;
+}
+
+.star > svg > path {
+ fill: #7b3907;
+}
+
+.magicText {
+ --purple: rgba(72, 130, 244, 0.868);
+ --violet: #9a80f7;
+ --pink: #f7f7f7;
+ animation: background-pan 3s linear infinite;
+ background: linear-gradient(
+ to right,
+ var(--purple),
+ var(--violet),
+ var(--pink),
+ var(--purple)
+ );
+ background-size: 200%;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ white-space: nowrap;
+}
diff --git a/apps/web/app/(auth)/signin/page.tsx b/apps/web/app/(auth)/signin/page.tsx
index 836b72db..3db512ae 100644
--- a/apps/web/app/(auth)/signin/page.tsx
+++ b/apps/web/app/(auth)/signin/page.tsx
@@ -11,112 +11,112 @@ import { toast } from "sonner";
export const runtime = "edge";
async function Signin({
- searchParams,
+ searchParams,
}: {
- searchParams: Record<string, string>;
+ searchParams: Record<string, string>;
}) {
- const user = await auth();
+ const user = await auth();
- if (user) {
- await redirect("/home");
- }
+ if (user) {
+ await redirect("/home");
+ }
- return (
- <div className="flex relative font-geistSans overflow-hidden items-center justify-between min-h-screen">
- <div className="relative w-full lg:w-1/2 flex items-center min-h-screen bg-page-gradient p-8 border-r-[1px] border-white/5">
- <div className="absolute top-0 left-0 p-8 text-white inline-flex gap-2 items-center">
- <Image
- src={Logo}
- alt="SuperMemory logo"
- className="hover:brightness-125 duration-200"
- />
- <span className="text-xl">supermemory.ai</span>
- </div>
- <div className="absolute inset-0 opacity-5 w-full bg-transparent bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]"></div>
- <img
- className="absolute inset-x-0 -top-20 opacity-20"
- src={"/images/landing-hero-left.png"}
- width={1000}
- height={1000}
- alt="back bg"
- />
+ return (
+ <div className="flex relative font-geistSans overflow-hidden items-center justify-between min-h-screen">
+ <div className="relative w-full lg:w-1/2 flex items-center min-h-screen bg-page-gradient p-8 border-r-[1px] border-white/5">
+ <div className="absolute top-0 left-0 p-8 text-white inline-flex gap-2 items-center">
+ <Image
+ src={Logo}
+ alt="SuperMemory logo"
+ className="hover:brightness-125 duration-200"
+ />
+ <span className="text-xl">supermemory.ai</span>
+ </div>
+ <div className="absolute inset-0 opacity-5 w-full bg-transparent bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]"></div>
+ <img
+ className="absolute inset-x-0 -top-20 opacity-20"
+ src={"/images/landing-hero-left.png"}
+ width={1000}
+ height={1000}
+ alt="back bg"
+ />
- <div className="pl-4 z-20">
- <h1 className="text-5xl text-white mb-8 tracking-tighter">
- Hello,{" "}
- <span
- className={cn(
- "bg-gradient-to-tr from-zinc-100 via-zinc-200/50 to-zinc-200/90 text-transparent bg-clip-text animate-gradient",
- gradientStyle.magicText,
- )}
- >
- human
- </span>{" "}
- </h1>
- <p className="text-white mb-8 text-lg tracking-tighter">
- Write, ideate, and learn with all the wisdom of your bookmarks.
- </p>
- <div className="flex items-center gap-4">
- <div
- className={`relative cursor-pointer transition-width z-20 rounded-2xl bg-hero-gradient p-[0.7px] duration-500 ease-in-out fit dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset]`}
- >
- <form
- action={async () => {
- "use server";
- await signIn("google", {
- redirectTo: "/home?firstTime=true",
- });
- }}
- >
- <button
- type="submit"
- className={`relative text-white transition-width flex gap-3 justify-center w-full items-center rounded-2xl bg-page-gradient hover:opacity-70 duration-500 px-6 py-4 outline-none duration- focus:outline-none `}
- >
- <Google />
- <span className="relative w-full">Continue with Google</span>
- </button>
- </form>
- </div>
- </div>
- <div className="text-slate-500 mt-16 z-20">
- By continuing, you agree to the
- <Link href="/tos" className="text-slate-200">
- {" "}
- Terms of Service
- </Link>{" "}
- and
- <Link href="/privacy" className="text-slate-200">
- {" "}
- Privacy Policy
- </Link>
- </div>
- </div>
- </div>
- <div className="relative hidden w-0 lg:flex lg:w-1/2 flex-col items-center justify-center min-h-screen bg-page-gradient overflow-hidden">
- <img
- className="absolute inset-x-0 -top-20 opacity-15 "
- src={"/images/landing-hero.jpeg"}
- width={1000}
- height={1000}
- alt="back bg"
- />
- <span className="text-3xl leading-relaxed tracking-tighter mb-8">
- Ready for your{" "}
- <span className="text-white font-bold">Second brain</span>?
- </span>
+ <div className="pl-4 z-20">
+ <h1 className="text-5xl text-white mb-8 tracking-tighter">
+ Hello,{" "}
+ <span
+ className={cn(
+ "bg-gradient-to-tr from-zinc-100 via-zinc-200/50 to-zinc-200/90 text-transparent bg-clip-text animate-gradient",
+ gradientStyle.magicText,
+ )}
+ >
+ human
+ </span>{" "}
+ </h1>
+ <p className="text-white mb-8 text-lg tracking-tighter">
+ Write, ideate, and learn with all the wisdom of your bookmarks.
+ </p>
+ <div className="flex items-center gap-4">
+ <div
+ className={`relative cursor-pointer transition-width z-20 rounded-2xl bg-hero-gradient p-[0.7px] duration-500 ease-in-out fit dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset]`}
+ >
+ <form
+ action={async () => {
+ "use server";
+ await signIn("google", {
+ redirectTo: "/home?firstTime=true",
+ });
+ }}
+ >
+ <button
+ type="submit"
+ className={`relative text-white transition-width flex gap-3 justify-center w-full items-center rounded-2xl bg-page-gradient hover:opacity-70 duration-500 px-6 py-4 outline-none duration- focus:outline-none `}
+ >
+ <Google />
+ <span className="relative w-full">Continue with Google</span>
+ </button>
+ </form>
+ </div>
+ </div>
+ <div className="text-slate-500 mt-16 z-20">
+ By continuing, you agree to the
+ <Link href="/tos" className="text-slate-200">
+ {" "}
+ Terms of Service
+ </Link>{" "}
+ and
+ <Link href="/privacy" className="text-slate-200">
+ {" "}
+ Privacy Policy
+ </Link>
+ </div>
+ </div>
+ </div>
+ <div className="relative hidden w-0 lg:flex lg:w-1/2 flex-col items-center justify-center min-h-screen bg-page-gradient overflow-hidden">
+ <img
+ className="absolute inset-x-0 -top-20 opacity-15 "
+ src={"/images/landing-hero.jpeg"}
+ width={1000}
+ height={1000}
+ alt="back bg"
+ />
+ <span className="text-3xl leading-relaxed tracking-tighter mb-8">
+ Ready for your{" "}
+ <span className="text-white font-bold">Second brain</span>?
+ </span>
- <div>
- <Image
- className="mx-auto rounded-lg shadow-2xl w-[calc(100%-100px)] md:px-14 lg:px-0 lg:max-w-none"
- src={"/images/memory.svg"}
- width={700}
- height={520}
- alt="Carousel 01"
- />
- </div>
- </div>
- </div>
- );
+ <div>
+ <Image
+ className="mx-auto rounded-lg shadow-2xl w-[calc(100%-100px)] md:px-14 lg:px-0 lg:max-w-none"
+ src={"/images/memory.svg"}
+ width={700}
+ height={520}
+ alt="Carousel 01"
+ />
+ </div>
+ </div>
+ </div>
+ );
}
export default Signin;
diff --git a/apps/web/app/(auth)/tos/page.tsx b/apps/web/app/(auth)/tos/page.tsx
index 4f74dbfb..eb506ac9 100644
--- a/apps/web/app/(auth)/tos/page.tsx
+++ b/apps/web/app/(auth)/tos/page.tsx
@@ -2,11 +2,11 @@ import React from "react";
import Markdown from "react-markdown";
function Page() {
- return (
- <div className="flex flex-col items-center justify-center mt-8">
- <div className="max-w-3xl prose prose-invert">
- <Markdown>
- {`
+ return (
+ <div className="flex flex-col items-center justify-center mt-8">
+ <div className="max-w-3xl prose prose-invert">
+ <Markdown>
+ {`
Terms of Service for Supermemory.ai
**Effective Date:** July 4, 2024
@@ -48,10 +48,10 @@ If you have any questions about these Terms of Service, please contact us at dhr
**By using Supermemory, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.**
`}
- </Markdown>
- </div>
- </div>
- );
+ </Markdown>
+ </div>
+ </div>
+ );
}
export default Page;
diff --git a/apps/web/app/(canvas)/canvas/[id]/page.tsx b/apps/web/app/(canvas)/canvas/[id]/page.tsx
index cad6143e..bddd3675 100644
--- a/apps/web/app/(canvas)/canvas/[id]/page.tsx
+++ b/apps/web/app/(canvas)/canvas/[id]/page.tsx
@@ -1,17 +1,17 @@
import { userHasCanvas } from "@/app/actions/fetchers";
import {
- RectProvider,
- ResizaleLayout,
+ RectProvider,
+ ResizaleLayout,
} from "@/components/canvas/resizableLayout";
import { redirect } from "next/navigation";
export default async function page({ params }: any) {
- const canvasExists = await userHasCanvas(params.id);
- if (!canvasExists.success) {
- redirect("/canvas");
- }
- return (
- <RectProvider id={params.id}>
- <ResizaleLayout />
- </RectProvider>
- );
+ const canvasExists = await userHasCanvas(params.id);
+ if (!canvasExists.success) {
+ redirect("/canvas");
+ }
+ return (
+ <RectProvider id={params.id}>
+ <ResizaleLayout />
+ </RectProvider>
+ );
}
diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx
index d0824a20..0bf30d89 100644
--- a/apps/web/app/(canvas)/canvas/page.tsx
+++ b/apps/web/app/(canvas)/canvas/page.tsx
@@ -4,19 +4,19 @@ import SearchandCreate from "./search&create";
import ThinkPads from "./thinkPads";
async function page() {
- const canvas = await getCanvas();
- return (
- <div className="h-screen w-full py-32 text-[#FFFFFF] ">
- <div className="flex w-full flex-col items-center gap-8">
- <h1 className="text-4xl font-medium">Your thinkpads</h1>
- <SearchandCreate />
- {
- // @ts-ignore
- canvas.success && <ThinkPads data={canvas.data} />
- }
- </div>
- </div>
- );
+ const canvas = await getCanvas();
+ return (
+ <div className="h-screen w-full py-32 text-[#FFFFFF] ">
+ <div className="flex w-full flex-col items-center gap-8">
+ <h1 className="text-4xl font-medium">Your thinkpads</h1>
+ <SearchandCreate />
+ {
+ // @ts-ignore
+ canvas.success && <ThinkPads data={canvas.data} />
+ }
+ </div>
+ </div>
+ );
}
export default page;
diff --git a/apps/web/app/(canvas)/canvas/search&create.tsx b/apps/web/app/(canvas)/canvas/search&create.tsx
index e73ad76f..ad64729e 100644
--- a/apps/web/app/(canvas)/canvas/search&create.tsx
+++ b/apps/web/app/(canvas)/canvas/search&create.tsx
@@ -7,39 +7,39 @@ import { createCanvas } from "@/app/actions/doers";
import { toast } from "sonner";
export default function SearchandCreate() {
- return (
- <div className="flex w-[90%] max-w-2xl gap-2">
- <div className="flex flex-grow items-center overflow-hidden rounded-xl bg-[#1F2428]">
- <input
- placeholder="search here..."
- className="flex-grow bg-[#1F2428] px-5 py-3 text-xl focus:border-none focus:outline-none"
- />
- <button className="h-full border-l-2 border-[#384149] px-2 pl-2">
- <Image src={SearchIcon} alt="search" />
- </button>
- </div>
+ return (
+ <div className="flex w-[90%] max-w-2xl gap-2">
+ <div className="flex flex-grow items-center overflow-hidden rounded-xl bg-[#1F2428]">
+ <input
+ placeholder="search here..."
+ className="flex-grow bg-[#1F2428] px-5 py-3 text-xl focus:border-none focus:outline-none"
+ />
+ <button className="h-full border-l-2 border-[#384149] px-2 pl-2">
+ <Image src={SearchIcon} alt="search" />
+ </button>
+ </div>
- <form
- action={async () => {
- const res = await createCanvas();
- if (!res.success) {
- toast.warning(res.message, {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- }
- }}
- >
- <Button />
- </form>
- </div>
- );
+ <form
+ action={async () => {
+ const res = await createCanvas();
+ if (!res.success) {
+ toast.warning(res.message, {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ >
+ <Button />
+ </form>
+ </div>
+ );
}
function Button() {
- const { pending } = useFormStatus();
- return (
- <button className="rounded-xl bg-[#1F2428] px-5 py-3 text-xl text-[#B8C4C6]">
- {pending ? "Creating.." : "Create New"}
- </button>
- );
+ const { pending } = useFormStatus();
+ return (
+ <button className="rounded-xl bg-[#1F2428] px-5 py-3 text-xl text-[#B8C4C6]">
+ {pending ? "Creating.." : "Create New"}
+ </button>
+ );
}
diff --git a/apps/web/app/(canvas)/canvas/thinkPad.tsx b/apps/web/app/(canvas)/canvas/thinkPad.tsx
index fff30f26..4d31107d 100644
--- a/apps/web/app/(canvas)/canvas/thinkPad.tsx
+++ b/apps/web/app/(canvas)/canvas/thinkPad.tsx
@@ -2,229 +2,229 @@ import { getCanvasData } from "@/app/actions/fetchers";
import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
import {
- EllipsisHorizontalCircleIcon,
- TrashIcon,
- PencilSquareIcon,
+ EllipsisHorizontalCircleIcon,
+ TrashIcon,
+ PencilSquareIcon,
} from "@heroicons/react/24/outline";
import { toast } from "sonner";
import { Label } from "@repo/ui/shadcn/label";
const childVariants = {
- hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
- visible: { opacity: 1, y: 0, filter: "blur(0px)" },
+ hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
+ visible: { opacity: 1, y: 0, filter: "blur(0px)" },
};
export default function ThinkPad({
- title,
- description,
- image,
- id,
+ title,
+ description,
+ image,
+ id,
}: {
- title: string;
- description: string;
- image: string;
- id: string;
+ title: string;
+ description: string;
+ image: string;
+ id: string;
}) {
- const [deleted, setDeleted] = useState(false);
- const [info, setInfo] = useState({ title, description });
- return (
- <AnimatePresence mode="sync">
- {!deleted && (
- <motion.div
- layout
- exit={{ opacity: 0, scaleY: 0 }}
- variants={childVariants}
- className="flex h-48 origin-top relative gap-4 rounded-2xl bg-[#1F2428] p-2"
- >
- <Link
- className="h-full select-none min-w-[40%] bg-[#363f46] rounded-xl overflow-hidden"
- href={`/canvas/${id}`}
- >
- <Suspense
- fallback={
- <div className=" h-full w-full flex justify-center items-center">
- Loading...
- </div>
- }
- >
- <ImageComponent id={id} />
- </Suspense>
- </Link>
- <div className="flex flex-col gap-2">
- <motion.h2
- initial={{ opacity: 0, filter: "blur(3px)" }}
- animate={{ opacity: 1, filter: "blur(0px)" }}
- key={info.title}
- >
- {info.title}
- </motion.h2>
- <motion.h3
- key={info.description}
- initial={{ opacity: 0, filter: "blur(3px)" }}
- animate={{ opacity: 1, filter: "blur(0px)" }}
- className="overflow-hidden text-ellipsis text-[#B8C4C6]"
- >
- {info.description}
- </motion.h3>
- </div>
- <Menu
- info={info}
- id={id}
- setDeleted={() => setDeleted(true)}
- setInfo={(e) => setInfo(e)}
- />
- </motion.div>
- )}
- </AnimatePresence>
- );
+ const [deleted, setDeleted] = useState(false);
+ const [info, setInfo] = useState({ title, description });
+ return (
+ <AnimatePresence mode="sync">
+ {!deleted && (
+ <motion.div
+ layout
+ exit={{ opacity: 0, scaleY: 0 }}
+ variants={childVariants}
+ className="flex h-48 origin-top relative gap-4 rounded-2xl bg-[#1F2428] p-2"
+ >
+ <Link
+ className="h-full select-none min-w-[40%] bg-[#363f46] rounded-xl overflow-hidden"
+ href={`/canvas/${id}`}
+ >
+ <Suspense
+ fallback={
+ <div className=" h-full w-full flex justify-center items-center">
+ Loading...
+ </div>
+ }
+ >
+ <ImageComponent id={id} />
+ </Suspense>
+ </Link>
+ <div className="flex flex-col gap-2">
+ <motion.h2
+ initial={{ opacity: 0, filter: "blur(3px)" }}
+ animate={{ opacity: 1, filter: "blur(0px)" }}
+ key={info.title}
+ >
+ {info.title}
+ </motion.h2>
+ <motion.h3
+ key={info.description}
+ initial={{ opacity: 0, filter: "blur(3px)" }}
+ animate={{ opacity: 1, filter: "blur(0px)" }}
+ className="overflow-hidden text-ellipsis text-[#B8C4C6]"
+ >
+ {info.description}
+ </motion.h3>
+ </div>
+ <Menu
+ info={info}
+ id={id}
+ setDeleted={() => setDeleted(true)}
+ setInfo={(e) => setInfo(e)}
+ />
+ </motion.div>
+ )}
+ </AnimatePresence>
+ );
}
import {
- Popover,
- PopoverContent,
- PopoverTrigger,
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
} from "@repo/ui/shadcn/popover";
function Menu({
- info,
- id,
- setDeleted,
- setInfo,
+ info,
+ id,
+ setDeleted,
+ setInfo,
}: {
- info: { title: string; description: string };
- id: string;
- setDeleted: () => void;
- setInfo: ({
- title,
- description,
- }: {
- title: string;
- description: string;
- }) => void;
+ info: { title: string; description: string };
+ id: string;
+ setDeleted: () => void;
+ setInfo: ({
+ title,
+ description,
+ }: {
+ title: string;
+ description: string;
+ }) => void;
}) {
- return (
- <Popover>
- <PopoverTrigger className="absolute z-20 top-0 right-0" asChild>
- <Button variant="secondary">
- <EllipsisHorizontalCircleIcon className="size-5 stroke-2 stroke-[#B8C4C6]" />
- </Button>
- </PopoverTrigger>
- <PopoverContent
- align="start"
- className="w-32 px-2 py-2 bg-[#161f2a]/30 text-[#B8C4C6] border-border flex flex-col gap-3"
- >
- <EditToolbar info={info} id={id} setInfo={setInfo} />
- <Button
- onClick={async () => {
- const res = await deleteCanvas(id);
- if (res.success) {
- toast.success("Thinkpad removed.", {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- setDeleted();
- } else {
- toast.warning("Something went wrong.", {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- }
- }}
- className="flex gap-2 border-border"
- variant="outline"
- >
- <TrashIcon className="size-8 stroke-1" /> Delete
- </Button>
- </PopoverContent>
- </Popover>
- );
+ return (
+ <Popover>
+ <PopoverTrigger className="absolute z-20 top-0 right-0" asChild>
+ <Button variant="secondary">
+ <EllipsisHorizontalCircleIcon className="size-5 stroke-2 stroke-[#B8C4C6]" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent
+ align="start"
+ className="w-32 px-2 py-2 bg-[#161f2a]/30 text-[#B8C4C6] border-border flex flex-col gap-3"
+ >
+ <EditToolbar info={info} id={id} setInfo={setInfo} />
+ <Button
+ onClick={async () => {
+ const res = await deleteCanvas(id);
+ if (res.success) {
+ toast.success("Thinkpad removed.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ setDeleted();
+ } else {
+ toast.warning("Something went wrong.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ className="flex gap-2 border-border"
+ variant="outline"
+ >
+ <TrashIcon className="size-8 stroke-1" /> Delete
+ </Button>
+ </PopoverContent>
+ </Popover>
+ );
}
function EditToolbar({
- id,
- setInfo,
- info,
+ id,
+ setInfo,
+ info,
}: {
- id: string;
- setInfo: ({
- title,
- description,
- }: {
- title: string;
- description: string;
- }) => void;
- info: {
- title: string;
- description: string;
- };
+ id: string;
+ setInfo: ({
+ title,
+ description,
+ }: {
+ title: string;
+ description: string;
+ }) => void;
+ info: {
+ title: string;
+ description: string;
+ };
}) {
- const [open, setOpen] = useState(false);
- return (
- <Dialog open={open} onOpenChange={setOpen}>
- <DialogTrigger asChild>
- <Button className="flex gap-2 border-border" variant="outline">
- <PencilSquareIcon className="size-8 stroke-1" /> Edit
- </Button>
- </DialogTrigger>
- <DialogContent className="sm:max-w-[425px] bg-[#161f2a]/30 border-0">
- <form
- action={async (FormData) => {
- const data = {
- title: FormData.get("title") as string,
- description: FormData.get("description") as string,
- };
- const res = await AddCanvasInfo({ id, ...data });
- if (res.success) {
- setOpen(false);
- setInfo(data);
- } else {
- setOpen(false);
- toast.error("Something went wrong.", {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- }
- }}
- >
- <DialogHeader>
- <DialogTitle>Edit Canvas</DialogTitle>
- <DialogDescription>
- Add Description to your canvas. Pro tip: Let AI do the job, as you
- add your content into canvas, we will autogenerate your
- description.
- </DialogDescription>
- </DialogHeader>
- <div className="grid gap-4 py-4">
- <div className="grid grid-cols-4 items-center gap-4">
- <Label htmlFor="title" className="text-right">
- Title
- </Label>
- <Input
- defaultValue={info.title}
- name="title"
- id="title"
- placeholder="life planning..."
- className="col-span-3 border-0"
- />
- </div>
- <div className="grid grid-cols-4 items-center gap-4">
- <Label htmlFor="description" className="text-right">
- Description
- </Label>
- <Textarea
- defaultValue={info.description}
- rows={6}
- id="description"
- name="description"
- placeholder="contains information about..."
- className="col-span-3 border-0 resize-none"
- />
- </div>
- </div>
- <DialogFooter>
- <Button type="submit">Save changes</Button>
- </DialogFooter>
- </form>
- </DialogContent>
- </Dialog>
- );
+ const [open, setOpen] = useState(false);
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button className="flex gap-2 border-border" variant="outline">
+ <PencilSquareIcon className="size-8 stroke-1" /> Edit
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="sm:max-w-[425px] bg-[#161f2a]/30 border-0">
+ <form
+ action={async (FormData) => {
+ const data = {
+ title: FormData.get("title") as string,
+ description: FormData.get("description") as string,
+ };
+ const res = await AddCanvasInfo({ id, ...data });
+ if (res.success) {
+ setOpen(false);
+ setInfo(data);
+ } else {
+ setOpen(false);
+ toast.error("Something went wrong.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ >
+ <DialogHeader>
+ <DialogTitle>Edit Canvas</DialogTitle>
+ <DialogDescription>
+ Add Description to your canvas. Pro tip: Let AI do the job, as you
+ add your content into canvas, we will autogenerate your
+ description.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="title" className="text-right">
+ Title
+ </Label>
+ <Input
+ defaultValue={info.title}
+ name="title"
+ id="title"
+ placeholder="life planning..."
+ className="col-span-3 border-0"
+ />
+ </div>
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="description" className="text-right">
+ Description
+ </Label>
+ <Textarea
+ defaultValue={info.description}
+ rows={6}
+ id="description"
+ name="description"
+ placeholder="contains information about..."
+ className="col-span-3 border-0 resize-none"
+ />
+ </div>
+ </div>
+ <DialogFooter>
+ <Button type="submit">Save changes</Button>
+ </DialogFooter>
+ </form>
+ </DialogContent>
+ </Dialog>
+ );
}
import { Suspense, memo, use, useState } from "react";
@@ -232,13 +232,13 @@ import { Box, TldrawImage } from "tldraw";
import { Button } from "@repo/ui/shadcn/button";
import { AddCanvasInfo, deleteCanvas } from "@/app/actions/doers";
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
} from "@repo/ui/shadcn/dialog";
import { Input } from "@repo/ui/shadcn/input";
import { Textarea } from "@repo/ui/shadcn/textarea";
@@ -246,31 +246,31 @@ import { textCardUtil } from "@/components/canvas/textCard";
import { twitterCardUtil } from "@/components/canvas/twitterCard";
const ImageComponent = memo(({ id }: { id: string }) => {
- const snapshot = use(getCanvasData(id));
- if (snapshot.bounds) {
- const pageBounds = new Box(
- snapshot.bounds.x,
- snapshot.bounds.y,
- snapshot.bounds.w,
- snapshot.bounds.h,
- );
+ const snapshot = use(getCanvasData(id));
+ if (snapshot.bounds) {
+ const pageBounds = new Box(
+ snapshot.bounds.x,
+ snapshot.bounds.y,
+ snapshot.bounds.w,
+ snapshot.bounds.h,
+ );
- return (
- <TldrawImage
- shapeUtils={[twitterCardUtil, textCardUtil]}
- snapshot={snapshot.snapshot}
- background={false}
- darkMode={true}
- bounds={pageBounds}
- padding={0}
- scale={1}
- format="png"
- />
- );
- }
- return (
- <div className=" h-full w-full flex justify-center items-center">
- Drew things to seee here
- </div>
- );
+ return (
+ <TldrawImage
+ shapeUtils={[twitterCardUtil, textCardUtil]}
+ snapshot={snapshot.snapshot}
+ background={false}
+ darkMode={true}
+ bounds={pageBounds}
+ padding={0}
+ scale={1}
+ format="png"
+ />
+ );
+ }
+ return (
+ <div className=" h-full w-full flex justify-center items-center">
+ Drew things to seee here
+ </div>
+ );
});
diff --git a/apps/web/app/(canvas)/canvas/thinkPads.tsx b/apps/web/app/(canvas)/canvas/thinkPads.tsx
index 3e8d7550..c5e92370 100644
--- a/apps/web/app/(canvas)/canvas/thinkPads.tsx
+++ b/apps/web/app/(canvas)/canvas/thinkPads.tsx
@@ -3,30 +3,30 @@ import { motion } from "framer-motion";
import ThinkPad from "./thinkPad";
const containerVariants = {
- hidden: { opacity: 0 },
- visible: {
- opacity: 1,
- transition: {
- staggerChildren: 0.1,
- },
- },
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.1,
+ },
+ },
};
export default function ThinkPads({
- data,
+ data,
}: {
- data: { image: string; title: string; description: string; id: string }[];
+ data: { image: string; title: string; description: string; id: string }[];
}) {
- return (
- <motion.div
- variants={containerVariants}
- initial="hidden"
- animate="visible"
- className="w-[90%] max-w-2xl space-y-6"
- >
- {data.map((item) => {
- return <ThinkPad {...item} />;
- })}
- </motion.div>
- );
+ return (
+ <motion.div
+ variants={containerVariants}
+ initial="hidden"
+ animate="visible"
+ className="w-[90%] max-w-2xl space-y-6"
+ >
+ {data.map((item) => {
+ return <ThinkPad {...item} />;
+ })}
+ </motion.div>
+ );
}
diff --git a/apps/web/app/(canvas)/canvasStyles.css b/apps/web/app/(canvas)/canvasStyles.css
index af4740e1..1664e819 100644
--- a/apps/web/app/(canvas)/canvasStyles.css
+++ b/apps/web/app/(canvas)/canvasStyles.css
@@ -1,5 +1,5 @@
.tl-background {
- background: #1f2428 !important;
+ background: #1f2428 !important;
}
.tlui-style-panel.tlui-style-panel__wrapper,
@@ -11,26 +11,26 @@
.tlui-button__help,
.tlui-help-menu,
.tlui-dialog__content {
- background: #2c3840 !important;
- border-top: #2c3840 !important;
- border-right: #2c3840 !important;
- border-bottom: #2c3840 !important;
- border-left: #2c3840 !important;
+ background: #2c3840 !important;
+ border-top: #2c3840 !important;
+ border-right: #2c3840 !important;
+ border-bottom: #2c3840 !important;
+ border-left: #2c3840 !important;
}
.tlui-navigation-panel::before {
- border-top: #2c3840 !important;
- border-right: #2c3840 !important;
+ border-top: #2c3840 !important;
+ border-right: #2c3840 !important;
}
.tlui-minimap {
- background: #2c3840 !important;
+ background: #2c3840 !important;
}
.tlui-minimap__canvas {
- background: #1f2428 !important;
+ background: #1f2428 !important;
}
.tlui-dialog__overlay {
- position: fixed;
+ position: fixed;
}
diff --git a/apps/web/app/(canvas)/layout.tsx b/apps/web/app/(canvas)/layout.tsx
index 33e0adfb..e9d38968 100644
--- a/apps/web/app/(canvas)/layout.tsx
+++ b/apps/web/app/(canvas)/layout.tsx
@@ -5,26 +5,26 @@ import BackgroundPlus from "../(landing)/GridPatterns/PlusGrid";
import { Toaster } from "@repo/ui/shadcn/sonner";
export default async function RootLayout({
- children,
+ children,
}: {
- children: React.ReactNode;
+ children: React.ReactNode;
}) {
- const info = await auth();
+ const info = await auth();
- if (!info) {
- return redirect("/signin");
- }
- return (
- <>
- <div className="relative flex justify-center z-40 pointer-events-none">
- <div
- className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
- <div>{children}</div>
- <Toaster />
- </>
- );
+ if (!info) {
+ return redirect("/signin");
+ }
+ return (
+ <>
+ <div className="relative flex justify-center z-40 pointer-events-none">
+ <div
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
+ <div>{children}</div>
+ <Toaster />
+ </>
+ );
}
diff --git a/apps/web/app/(dash)/(memories)/content.tsx b/apps/web/app/(dash)/(memories)/content.tsx
index 879c0502..fea4477a 100644
--- a/apps/web/app/(dash)/(memories)/content.tsx
+++ b/apps/web/app/(dash)/(memories)/content.tsx
@@ -1,15 +1,14 @@
"use client";
-import { getAllUserMemoriesAndSpaces } from "@/app/actions/fetchers";
import { Content, StoredSpace } from "@/server/db/schema";
import { MemoriesIcon, NextIcon, SearchIcon, UrlIcon } from "@repo/ui/icons";
import {
- ArrowLeftIcon,
- MenuIcon,
- MoveIcon,
- NotebookIcon,
- PaperclipIcon,
- TrashIcon,
+ ArrowLeftIcon,
+ MenuIcon,
+ MoveIcon,
+ NotebookIcon,
+ PaperclipIcon,
+ TrashIcon,
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
@@ -18,16 +17,16 @@ import Masonry from "react-layout-masonry";
import { getRawTweet } from "@repo/shared-types/utils";
import { MyTweet } from "../../../components/twitter/render-tweet";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuPortal,
- DropdownMenuSeparator,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuTrigger,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuSeparator,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
} from "@repo/ui/shadcn/dropdown-menu";
import { Button } from "@repo/ui/shadcn/button";
import { addUserToSpace, deleteItem, moveItem } from "@/app/actions/doers";
@@ -36,360 +35,360 @@ import { Input } from "@repo/ui/shadcn/input";
import { motion } from "framer-motion";
export function MemoriesPage({
- memoriesAndSpaces,
- title = "Your Memories",
- currentSpace,
- usersWithAccess,
+ memoriesAndSpaces,
+ title = "Your Memories",
+ currentSpace,
+ usersWithAccess,
}: {
- memoriesAndSpaces: { memories: Content[]; spaces: StoredSpace[] };
- title?: string;
- currentSpace?: StoredSpace;
- usersWithAccess?: string[];
+ memoriesAndSpaces: { memories: Content[]; spaces: StoredSpace[] };
+ title?: string;
+ currentSpace?: StoredSpace;
+ usersWithAccess?: string[];
}) {
- const [filter, setFilter] = useState("All");
+ const [filter, setFilter] = useState("All");
- // Sort Both memories and spaces by their savedAt and createdAt dates respectfully.
- // The output should be just one single list of items
- // And it will look something like { item: "memory" | "space", date: Date, data: Content | StoredSpace }
- const sortedItems = useMemo(() => {
- // Merge the lists
- const unifiedItems = [
- ...memoriesAndSpaces.memories.map((memory) => ({
- item: "memory",
- date: new Date(memory.savedAt), // Assuming savedAt is a string date
- data: memory,
- })),
- ...memoriesAndSpaces.spaces.map((space) => ({
- item: "space",
- date: new Date(space.createdAt), // Assuming createdAt is a string date
- data: space,
- })),
- ].map((item) => ({
- ...item,
- date: Number(item.date), // Convert the date to a number
- }));
+ // Sort Both memories and spaces by their savedAt and createdAt dates respectfully.
+ // The output should be just one single list of items
+ // And it will look something like { item: "memory" | "space", date: Date, data: Content | StoredSpace }
+ const sortedItems = useMemo(() => {
+ // Merge the lists
+ const unifiedItems = [
+ ...memoriesAndSpaces.memories.map((memory) => ({
+ item: "memory",
+ date: new Date(memory.savedAt), // Assuming savedAt is a string date
+ data: memory,
+ })),
+ ...memoriesAndSpaces.spaces.map((space) => ({
+ item: "space",
+ date: new Date(space.createdAt), // Assuming createdAt is a string date
+ data: space,
+ })),
+ ].map((item) => ({
+ ...item,
+ date: Number(item.date), // Convert the date to a number
+ }));
- // Sort the merged list
- return unifiedItems
- .filter((item) => {
- if (filter === "All") return true;
- if (filter === "Spaces" && item.item === "space") {
- return true;
- }
- if (filter === "Pages")
- return (
- item.item === "memory" && (item.data as Content).type === "page"
- );
- if (filter === "Notes")
- return (
- item.item === "memory" && (item.data as Content).type === "note"
- );
- if (filter === "Tweet")
- return (
- item.item === "memory" && (item.data as Content).type === "tweet"
- );
- return false;
- })
- .sort((a, b) => b.date - a.date);
- }, [memoriesAndSpaces.memories, memoriesAndSpaces.spaces, filter]);
+ // Sort the merged list
+ return unifiedItems
+ .filter((item) => {
+ if (filter === "All") return true;
+ if (filter === "Spaces" && item.item === "space") {
+ return true;
+ }
+ if (filter === "Pages")
+ return (
+ item.item === "memory" && (item.data as Content).type === "page"
+ );
+ if (filter === "Notes")
+ return (
+ item.item === "memory" && (item.data as Content).type === "note"
+ );
+ if (filter === "Tweet")
+ return (
+ item.item === "memory" && (item.data as Content).type === "tweet"
+ );
+ return false;
+ })
+ .sort((a, b) => b.date - a.date);
+ }, [memoriesAndSpaces.memories, memoriesAndSpaces.spaces, filter]);
- return (
- <div
- key={`${memoriesAndSpaces.memories.length + memoriesAndSpaces.spaces.length}`}
- className="px-2 md:px-32 py-36 h-full flex mx-auto w-full flex-col gap-6"
- >
- {currentSpace && (
- <Link href={"/memories"} className="flex gap-2 items-center">
- <ArrowLeftIcon className="w-3 h-3" /> Back to all memories
- </Link>
- )}
+ return (
+ <div
+ key={`${memoriesAndSpaces.memories.length + memoriesAndSpaces.spaces.length}`}
+ className="px-2 md:px-32 py-36 h-full flex mx-auto w-full flex-col gap-6"
+ >
+ {currentSpace && (
+ <Link href={"/memories"} className="flex gap-2 items-center">
+ <ArrowLeftIcon className="w-3 h-3" /> Back to all memories
+ </Link>
+ )}
- <h2 className="text-white w-full text-3xl text-left font-semibold">
- {title}
- </h2>
- {currentSpace && (
- <div className="flex flex-col gap-2">
- <div className="flex gap-4 items-center">
- Space
- <div className="flex items-center gap-2 bg-secondary p-2 rounded-xl">
- <Image src={MemoriesIcon} alt="Spaces icon" className="w-3 h-3" />
- <span className="text-[#fff]">{currentSpace.name}</span>
- </div>
- </div>
+ <h2 className="text-white w-full text-3xl text-left font-semibold">
+ {title}
+ </h2>
+ {currentSpace && (
+ <div className="flex flex-col gap-2">
+ <div className="flex gap-4 items-center">
+ Space
+ <div className="flex items-center gap-2 bg-secondary p-2 rounded-xl">
+ <Image src={MemoriesIcon} alt="Spaces icon" className="w-3 h-3" />
+ <span className="text-[#fff]">{currentSpace.name}</span>
+ </div>
+ </div>
- {usersWithAccess && usersWithAccess.length > 0 && (
- <div className="flex gap-4 items-center">
- Users with access
- <div className="flex gap-2">
- {usersWithAccess.map((user) => (
- <div className="flex items-center gap-2 bg-secondary p-2 rounded-xl">
- <Image
- src={UrlIcon}
- alt="Spaces icon"
- className="w-3 h-3"
- />
- <span className="text-[#fff]">{user}</span>
- </div>
- ))}
- </div>
- </div>
- )}
+ {usersWithAccess && usersWithAccess.length > 0 && (
+ <div className="flex gap-4 items-center">
+ Users with access
+ <div className="flex gap-2">
+ {usersWithAccess.map((user) => (
+ <div className="flex items-center gap-2 bg-secondary p-2 rounded-xl">
+ <Image
+ src={UrlIcon}
+ alt="Spaces icon"
+ className="w-3 h-3"
+ />
+ <span className="text-[#fff]">{user}</span>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
- <form
- action={async (e: FormData) => {
- const email = e.get("email")?.toString();
+ <form
+ action={async (e: FormData) => {
+ const email = e.get("email")?.toString();
- if (!email) {
- toast.error("Please enter an email");
- return;
- }
+ if (!email) {
+ toast.error("Please enter an email");
+ return;
+ }
- const resp = await addUserToSpace(email, currentSpace.id);
+ const resp = await addUserToSpace(email, currentSpace.id);
- if (resp.success) {
- toast.success("User added to space");
- } else {
- toast.error("Failed to add user to space");
- }
- }}
- className="flex gap-2 max-w-xl mt-2"
- >
- <Input name="email" placeholder="Add user by email" />
- <Button variant="secondary">Add</Button>
- </form>
- </div>
- )}
+ if (resp.success) {
+ toast.success("User added to space");
+ } else {
+ toast.error("Failed to add user to space");
+ }
+ }}
+ className="flex gap-2 max-w-xl mt-2"
+ >
+ <Input name="email" placeholder="Add user by email" />
+ <Button variant="secondary">Add</Button>
+ </form>
+ </div>
+ )}
- <Filters
- setFilter={setFilter}
- filter={filter}
- filterMethods={
- currentSpace ? SpaceFilterMethods : MemoriesFilterMethods
- }
- />
+ <Filters
+ setFilter={setFilter}
+ filter={filter}
+ filterMethods={
+ currentSpace ? SpaceFilterMethods : MemoriesFilterMethods
+ }
+ />
- <Masonry
- className="mt-6 relative"
- columns={{ 640: 1, 768: 2, 1024: 3 }}
- gap={16}
- columnProps={{
- className: "min-w-[calc(33.3333%-16px)] w-full",
- }}
- >
- {sortedItems.map((item) => {
- if (item.item === "memory") {
- return (
- <LinkComponent
- type={(item.data as Content).type ?? "note"}
- content={(item.data as Content).content}
- title={(item.data as Content).title ?? "Untitled"}
- url={
- (item.data as Content).baseUrl ?? (item.data as Content).url
- }
- image={
- (item.data as Content).ogImage ??
- (item.data as Content).image ??
- "/placeholder-image.svg" // TODO: add this placeholder
- }
- description={(item.data as Content).description ?? ""}
- spaces={memoriesAndSpaces.spaces}
- id={(item.data as Content).id}
- />
- );
- }
+ <Masonry
+ className="mt-6 relative"
+ columns={{ 640: 1, 768: 2, 1024: 3 }}
+ gap={16}
+ columnProps={{
+ className: "min-w-[calc(33.3333%-16px)] w-full",
+ }}
+ >
+ {sortedItems.map((item) => {
+ if (item.item === "memory") {
+ return (
+ <LinkComponent
+ type={(item.data as Content).type ?? "note"}
+ content={(item.data as Content).content}
+ title={(item.data as Content).title ?? "Untitled"}
+ url={
+ (item.data as Content).baseUrl ?? (item.data as Content).url
+ }
+ image={
+ (item.data as Content).ogImage ??
+ (item.data as Content).image ??
+ "/placeholder-image.svg" // TODO: add this placeholder
+ }
+ description={(item.data as Content).description ?? ""}
+ spaces={memoriesAndSpaces.spaces}
+ id={(item.data as Content).id}
+ />
+ );
+ }
- if (item.item === "space") {
- return (
- <TabComponent
- title={(item.data as StoredSpace).name}
- description={`${(item.data as StoredSpace).numItems} memories`}
- id={(item.data as StoredSpace).id}
- />
- );
- }
+ if (item.item === "space") {
+ return (
+ <TabComponent
+ title={(item.data as StoredSpace).name}
+ description={`${(item.data as StoredSpace).numItems} memories`}
+ id={(item.data as StoredSpace).id}
+ />
+ );
+ }
- return null;
- })}
- </Masonry>
- </div>
- );
+ return null;
+ })}
+ </Masonry>
+ </div>
+ );
}
function TabComponent({
- title,
- description,
- id,
+ title,
+ description,
+ id,
}: {
- title: string;
- description: string;
- id: number;
+ title: string;
+ description: string;
+ id: number;
}) {
- return (
- <Link
- href={`/space/${id}`}
- className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4"
- >
- <div className="flex items-center gap-2 text-xs">
- <Image alt="Spaces icon" src={MemoriesIcon} className="size-3" /> Space
- </div>
- <div className="flex items-center">
- <div>
- <div className="h-12 w-12 flex justify-center items-center rounded-md">
- {title.slice(0, 2).toUpperCase()} {id}
- </div>
- </div>
- <div className="grow px-4">
- <div className="text-lg text-[#fff] line-clamp-2">{title}</div>
- <div>{description}</div>
- </div>
- <div>
- <Image src={NextIcon} alt="Search icon" />
- </div>
- </div>
- </Link>
- );
+ return (
+ <Link
+ href={`/space/${id}`}
+ className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4"
+ >
+ <div className="flex items-center gap-2 text-xs">
+ <Image alt="Spaces icon" src={MemoriesIcon} className="size-3" /> Space
+ </div>
+ <div className="flex items-center">
+ <div>
+ <div className="h-12 w-12 flex justify-center items-center rounded-md">
+ {title.slice(0, 2).toUpperCase()} {id}
+ </div>
+ </div>
+ <div className="grow px-4">
+ <div className="text-lg text-[#fff] line-clamp-2">{title}</div>
+ <div>{description}</div>
+ </div>
+ <div>
+ <Image src={NextIcon} alt="Search icon" />
+ </div>
+ </div>
+ </Link>
+ );
}
function LinkComponent({
- type,
- content,
- title,
- url,
- image,
- description,
- spaces,
- id,
+ type,
+ content,
+ title,
+ url,
+ image,
+ description,
+ spaces,
+ id,
}: {
- type: string;
- content: string;
- title: string;
- url: string;
- image?: string;
- description: string;
- spaces: StoredSpace[];
- id: number;
+ type: string;
+ content: string;
+ title: string;
+ url: string;
+ image?: string;
+ description: string;
+ spaces: StoredSpace[];
+ id: number;
}) {
- return (
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- className={`bg-secondary group relative border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`}
- >
- <Link
- href={url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
- target="_blank"
- >
- {type === "page" ? (
- <>
- <div className="flex items-center gap-2 text-xs">
- <PaperclipIcon className="w-3 h-3" /> Page
- </div>
- {/* remove `<---chunkId: ${vector.id}\n${content}\n---->` pattern from title */}
- <div className="text-lg text-[#fff] mt-4 line-clamp-2">
- {title.replace(/(<---chunkId: .*?\n.*?\n---->)/g, "")}
- </div>
- <div>
- {url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
- </div>
- </>
- ) : type === "note" ? (
- <>
- <div className="flex items-center gap-2 text-xs">
- <NotebookIcon className="w-3 h-3" /> Note
- </div>
- <div className="text-lg text-[#fff] mt-4 line-clamp-2">
- {title.replace(/(<---chunkId: .*?\n.*?\n---->)/g, "")}
- </div>
- <div className="line-clamp-3 mt-2">
- {content.replace(title, "")}
- </div>
- </>
- ) : type === "tweet" ? (
- <MyTweet tweet={JSON.parse(getRawTweet(content) ?? "{}")} />
- ) : null}
- </Link>
- <DropdownMenu modal={false}>
- <DropdownMenuTrigger className="top-5 right-5 absolute opacity-0 group-focus:opacity-100 group-hover:opacity-100 transition duration-200">
- <MenuIcon />
- </DropdownMenuTrigger>
- <DropdownMenuContent>
- {spaces.length > 0 && (
- <DropdownMenuSub>
- <DropdownMenuSubTrigger>
- <MoveIcon className="mr-2 h-4 w-4" />
- <span>Add to space</span>
- </DropdownMenuSubTrigger>
- <DropdownMenuPortal>
- <DropdownMenuSubContent>
- {spaces.map((space) => (
- <DropdownMenuItem>
- <button
- className="w-full h-full"
- onClick={async () => {
- toast.info("Adding to space...");
+ return (
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ className={`bg-secondary group relative border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`}
+ >
+ <Link
+ href={url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
+ target="_blank"
+ >
+ {type === "page" ? (
+ <>
+ <div className="flex items-center gap-2 text-xs">
+ <PaperclipIcon className="w-3 h-3" /> Page
+ </div>
+ {/* remove `<---chunkId: ${vector.id}\n${content}\n---->` pattern from title */}
+ <div className="text-lg text-[#fff] mt-4 line-clamp-2">
+ {title.replace(/(<---chunkId: .*?\n.*?\n---->)/g, "")}
+ </div>
+ <div>
+ {url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
+ </div>
+ </>
+ ) : type === "note" ? (
+ <>
+ <div className="flex items-center gap-2 text-xs">
+ <NotebookIcon className="w-3 h-3" /> Note
+ </div>
+ <div className="text-lg text-[#fff] mt-4 line-clamp-2">
+ {title.replace(/(<---chunkId: .*?\n.*?\n---->)/g, "")}
+ </div>
+ <div className="line-clamp-3 mt-2">
+ {content.replace(title, "")}
+ </div>
+ </>
+ ) : type === "tweet" ? (
+ <MyTweet tweet={JSON.parse(getRawTweet(content) ?? "{}")} />
+ ) : null}
+ </Link>
+ <DropdownMenu modal={false}>
+ <DropdownMenuTrigger className="top-5 right-5 absolute opacity-0 group-focus:opacity-100 group-hover:opacity-100 transition duration-200">
+ <MenuIcon />
+ </DropdownMenuTrigger>
+ <DropdownMenuContent>
+ {spaces.length > 0 && (
+ <DropdownMenuSub>
+ <DropdownMenuSubTrigger>
+ <MoveIcon className="mr-2 h-4 w-4" />
+ <span>Add to space</span>
+ </DropdownMenuSubTrigger>
+ <DropdownMenuPortal>
+ <DropdownMenuSubContent>
+ {spaces.map((space) => (
+ <DropdownMenuItem>
+ <button
+ className="w-full h-full"
+ onClick={async () => {
+ toast.info("Adding to space...");
- const response = await moveItem(id, [space.id]);
+ const response = await moveItem(id, [space.id]);
- if (response.success) {
- toast.success("Moved to space");
- console.log("Moved to space");
- } else {
- toast.error("Failed to move to space");
- console.error("Failed to move to space");
- }
- }}
- >
- {space.name}
- </button>
- </DropdownMenuItem>
- ))}
- </DropdownMenuSubContent>
- </DropdownMenuPortal>
- </DropdownMenuSub>
- )}
- <DropdownMenuItem asChild>
- <Button
- onClick={async () => {
- await deleteItem(id);
- }}
- variant="destructive"
- className="w-full"
- >
- <TrashIcon className="mr-2 h-4 w-4" />
- Delete
- </Button>
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- </motion.div>
- );
+ if (response.success) {
+ toast.success("Moved to space");
+ console.log("Moved to space");
+ } else {
+ toast.error("Failed to move to space");
+ console.error("Failed to move to space");
+ }
+ }}
+ >
+ {space.name}
+ </button>
+ </DropdownMenuItem>
+ ))}
+ </DropdownMenuSubContent>
+ </DropdownMenuPortal>
+ </DropdownMenuSub>
+ )}
+ <DropdownMenuItem asChild>
+ <Button
+ onClick={async () => {
+ await deleteItem(id);
+ }}
+ variant="destructive"
+ className="w-full"
+ >
+ <TrashIcon className="mr-2 h-4 w-4" />
+ Delete
+ </Button>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </motion.div>
+ );
}
const MemoriesFilterMethods = ["All", "Spaces", "Pages", "Notes", "Tweet"];
const SpaceFilterMethods = ["All", "Pages", "Notes", "Tweet"];
function Filters({
- setFilter,
- filter,
- filterMethods,
+ setFilter,
+ filter,
+ filterMethods,
}: {
- setFilter: (i: string) => void;
- filter: string;
- filterMethods: string[];
+ setFilter: (i: string) => void;
+ filter: string;
+ filterMethods: string[];
}) {
- return (
- <div className="flex gap-4 flex-wrap">
- {filterMethods.map((i) => {
- return (
- <button
- onClick={() => setFilter(i)}
- className={`transition px-6 py-2 rounded-xl bg-border ${i === filter ? " text-[#369DFD]" : "text-[#B3BCC5] bg-secondary hover:bg-secondary hover:text-[#76a3cc]"}`}
- >
- {i}
- </button>
- );
- })}
- </div>
- );
+ return (
+ <div className="flex gap-4 flex-wrap">
+ {filterMethods.map((i) => {
+ return (
+ <button
+ onClick={() => setFilter(i)}
+ className={`transition px-6 py-2 rounded-xl bg-border ${i === filter ? " text-[#369DFD]" : "text-[#B3BCC5] bg-secondary hover:bg-secondary hover:text-[#76a3cc]"}`}
+ >
+ {i}
+ </button>
+ );
+ })}
+ </div>
+ );
}
export default MemoriesPage;
diff --git a/apps/web/app/(dash)/(memories)/memories/page.tsx b/apps/web/app/(dash)/(memories)/memories/page.tsx
index d1aa999a..d0f555f2 100644
--- a/apps/web/app/(dash)/(memories)/memories/page.tsx
+++ b/apps/web/app/(dash)/(memories)/memories/page.tsx
@@ -3,9 +3,9 @@ import { redirect } from "next/navigation";
import MemoriesPage from "../content";
async function Page() {
- const { success, data } = await getAllUserMemoriesAndSpaces();
- if (!success ?? !data) return redirect("/home");
- return <MemoriesPage memoriesAndSpaces={data} />;
+ const { success, data } = await getAllUserMemoriesAndSpaces();
+ if (!success ?? !data) return redirect("/home");
+ return <MemoriesPage memoriesAndSpaces={data} />;
}
export default Page;
diff --git a/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx b/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx
index 0bf33896..ed1ea1cc 100644
--- a/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx
+++ b/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx
@@ -7,23 +7,23 @@ import { spacesAccess } from "@/server/db/schema";
import { auth } from "@/server/auth";
async function Page({ params: { spaceid } }: { params: { spaceid: number } }) {
- const user = await auth();
+ const user = await auth();
- const { success, data } = await getMemoriesInsideSpace(spaceid);
- if (!success ?? !data) return redirect("/home");
+ const { success, data } = await getMemoriesInsideSpace(spaceid);
+ if (!success ?? !data) return redirect("/home");
- const hasAccess = await db.query.spacesAccess.findMany({
- where: and(eq(spacesAccess.spaceId, spaceid)),
- });
+ const hasAccess = await db.query.spacesAccess.findMany({
+ where: and(eq(spacesAccess.spaceId, spaceid)),
+ });
- return (
- <MemoriesPage
- memoriesAndSpaces={{ memories: data.memories, spaces: [] }}
- title={data.spaces[0]?.name}
- currentSpace={data.spaces[0]}
- usersWithAccess={hasAccess.map((x) => x.userEmail) ?? []}
- />
- );
+ return (
+ <MemoriesPage
+ memoriesAndSpaces={{ memories: data.memories, spaces: [] }}
+ title={data.spaces[0]?.name}
+ currentSpace={data.spaces[0]}
+ usersWithAccess={hasAccess.map((x) => x.userEmail) ?? []}
+ />
+ );
}
export default Page;
diff --git a/apps/web/app/(dash)/chat/CodeBlock.tsx b/apps/web/app/(dash)/chat/CodeBlock.tsx
index 0bb6a19d..22b2570a 100644
--- a/apps/web/app/(dash)/chat/CodeBlock.tsx
+++ b/apps/web/app/(dash)/chat/CodeBlock.tsx
@@ -1,90 +1,90 @@
import React, { useRef, useState } from "react";
const CodeBlock = ({
- lang,
- codeChildren,
+ lang,
+ codeChildren,
}: {
- lang: string;
- codeChildren: React.ReactNode & React.ReactNode[];
+ lang: string;
+ codeChildren: React.ReactNode & React.ReactNode[];
}) => {
- const codeRef = useRef<HTMLElement>(null);
+ const codeRef = useRef<HTMLElement>(null);
- return (
- <div className="bg-black rounded-md">
- <CodeBar lang={lang} codeRef={codeRef} />
- <div className="p-4 overflow-y-auto">
- <code ref={codeRef} className={`!whitespace-pre hljs language-${lang}`}>
- {codeChildren}
- </code>
- </div>
- </div>
- );
+ return (
+ <div className="bg-black rounded-md">
+ <CodeBar lang={lang} codeRef={codeRef} />
+ <div className="p-4 overflow-y-auto">
+ <code ref={codeRef} className={`!whitespace-pre hljs language-${lang}`}>
+ {codeChildren}
+ </code>
+ </div>
+ </div>
+ );
};
const CodeBar = React.memo(
- ({
- lang,
- codeRef,
- }: {
- lang: string;
- codeRef: React.RefObject<HTMLElement>;
- }) => {
- const [isCopied, setIsCopied] = useState<boolean>(false);
- return (
- <div className="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans">
- <span className="">{lang}</span>
- <button
- className="flex ml-auto gap-2"
- aria-label="copy codeblock"
- onClick={async () => {
- const codeString = codeRef.current?.textContent;
- if (codeString)
- navigator.clipboard.writeText(codeString).then(() => {
- setIsCopied(true);
- setTimeout(() => setIsCopied(false), 3000);
- });
- }}
- >
- {isCopied ? (
- <>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="size-4"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"
- />
- </svg>
- Copied!
- </>
- ) : (
- <>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="size-4"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
- />
- </svg>
- Copy code
- </>
- )}
- </button>
- </div>
- );
- },
+ ({
+ lang,
+ codeRef,
+ }: {
+ lang: string;
+ codeRef: React.RefObject<HTMLElement>;
+ }) => {
+ const [isCopied, setIsCopied] = useState<boolean>(false);
+ return (
+ <div className="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans">
+ <span className="">{lang}</span>
+ <button
+ className="flex ml-auto gap-2"
+ aria-label="copy codeblock"
+ onClick={async () => {
+ const codeString = codeRef.current?.textContent;
+ if (codeString)
+ navigator.clipboard.writeText(codeString).then(() => {
+ setIsCopied(true);
+ setTimeout(() => setIsCopied(false), 3000);
+ });
+ }}
+ >
+ {isCopied ? (
+ <>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ className="size-4"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"
+ />
+ </svg>
+ Copied!
+ </>
+ ) : (
+ <>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ className="size-4"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
+ />
+ </svg>
+ Copy code
+ </>
+ )}
+ </button>
+ </div>
+ );
+ },
);
export default CodeBlock;
diff --git a/apps/web/app/(dash)/chat/[chatid]/page.tsx b/apps/web/app/(dash)/chat/[chatid]/page.tsx
index 96e96020..87fd0b19 100644
--- a/apps/web/app/(dash)/chat/[chatid]/page.tsx
+++ b/apps/web/app/(dash)/chat/[chatid]/page.tsx
@@ -3,36 +3,36 @@ import { chatSearchParamsCache } from "@/lib/searchParams";
import ChatWindow from "../chatWindow";
async function Page({
- params,
- searchParams,
+ params,
+ searchParams,
}: {
- params: { chatid: string };
- searchParams: Record<string, string | string[] | undefined>;
+ params: { chatid: string };
+ searchParams: Record<string, string | string[] | undefined>;
}) {
- const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams);
+ const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams);
- let chat: Awaited<ReturnType<typeof getFullChatThread>>;
+ let chat: Awaited<ReturnType<typeof getFullChatThread>>;
- try {
- chat = await getFullChatThread(params.chatid);
- } catch (e) {
- const error = e as Error;
- return <div>This page errored out: {error.message}</div>;
- }
+ try {
+ chat = await getFullChatThread(params.chatid);
+ } catch (e) {
+ const error = e as Error;
+ return <div>This page errored out: {error.message}</div>;
+ }
- if (!chat.success || !chat.data) {
- console.error(chat.error);
- return <div>Chat not found. Check the console for more details.</div>;
- }
+ if (!chat.success || !chat.data) {
+ console.error(chat.error);
+ return <div>Chat not found. Check the console for more details.</div>;
+ }
- return (
- <ChatWindow
- q={q}
- spaces={spaces ?? []}
- initialChat={chat.data.length > 0 ? chat.data : undefined}
- threadId={params.chatid}
- />
- );
+ return (
+ <ChatWindow
+ q={q}
+ spaces={spaces ?? []}
+ initialChat={chat.data.length > 0 ? chat.data : undefined}
+ threadId={params.chatid}
+ />
+ );
}
export default Page;
diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx
index e610057d..3bc9fec6 100644
--- a/apps/web/app/(dash)/chat/chatWindow.tsx
+++ b/apps/web/app/(dash)/chat/chatWindow.tsx
@@ -8,10 +8,10 @@ import { motion } from "framer-motion";
import { useRouter } from "next/navigation";
import { ChatHistory, sourcesZod } from "@repo/shared-types";
import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
} from "@repo/ui/shadcn/accordion";
import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
@@ -27,417 +27,417 @@ import { ClipboardIcon } from "@heroicons/react/24/outline";
import { SendIcon } from "lucide-react";
function ChatWindow({
- q,
- spaces,
- initialChat = [
- {
- question: q,
- answer: {
- parts: [],
- sources: [],
- },
- },
- ],
- threadId,
+ q,
+ spaces,
+ initialChat = [
+ {
+ question: q,
+ answer: {
+ parts: [],
+ sources: [],
+ },
+ },
+ ],
+ threadId,
}: {
- q: string;
- spaces: { id: number; name: string }[];
- initialChat?: ChatHistory[];
- threadId: string;
+ q: string;
+ spaces: { id: number; name: string }[];
+ initialChat?: ChatHistory[];
+ threadId: string;
}) {
- const [layout, setLayout] = useState<"chat" | "initial">(
- initialChat.length > 1 ? "chat" : "initial",
- );
- const [chatHistory, setChatHistory] = useState<ChatHistory[]>(initialChat);
+ const [layout, setLayout] = useState<"chat" | "initial">(
+ initialChat.length > 1 ? "chat" : "initial",
+ );
+ const [chatHistory, setChatHistory] = useState<ChatHistory[]>(initialChat);
- const removeJustificationFromText = (text: string) => {
- // remove everything after the first "<justification>" word
- const justificationLine = text.indexOf("<justification>");
- if (justificationLine !== -1) {
- // Add that justification to the last chat message
- const lastChatMessage = chatHistory[chatHistory.length - 1];
- if (lastChatMessage) {
- lastChatMessage.answer.justification = text.slice(justificationLine);
- }
- return text.slice(0, justificationLine);
- }
- return text;
- };
+ const removeJustificationFromText = (text: string) => {
+ // remove everything after the first "<justification>" word
+ const justificationLine = text.indexOf("<justification>");
+ if (justificationLine !== -1) {
+ // Add that justification to the last chat message
+ const lastChatMessage = chatHistory[chatHistory.length - 1];
+ if (lastChatMessage) {
+ lastChatMessage.answer.justification = text.slice(justificationLine);
+ }
+ return text.slice(0, justificationLine);
+ }
+ return text;
+ };
- const router = useRouter();
+ const router = useRouter();
- const getAnswer = async (query: string, spaces: string[]) => {
- const sourcesFetch = await fetch(
- `/api/chat?q=${query}&spaces=${spaces}&sourcesOnly=true&threadId=${threadId}`,
- {
- method: "POST",
- body: JSON.stringify({ chatHistory }),
- },
- );
+ const getAnswer = async (query: string, spaces: string[]) => {
+ const sourcesFetch = await fetch(
+ `/api/chat?q=${query}&spaces=${spaces}&sourcesOnly=true&threadId=${threadId}`,
+ {
+ method: "POST",
+ body: JSON.stringify({ chatHistory }),
+ },
+ );
- // TODO: handle this properly
- const sources = await sourcesFetch.json();
+ // TODO: handle this properly
+ const sources = await sourcesFetch.json();
- const sourcesParsed = sourcesZod.safeParse(sources);
+ const sourcesParsed = sourcesZod.safeParse(sources);
- if (!sourcesParsed.success) {
- console.error(sourcesParsed.error);
- toast.error("Something went wrong while getting the sources");
- return;
- }
- window.scrollTo({
- top: document.documentElement.scrollHeight,
- behavior: "smooth",
- });
+ if (!sourcesParsed.success) {
+ console.error(sourcesParsed.error);
+ toast.error("Something went wrong while getting the sources");
+ return;
+ }
+ window.scrollTo({
+ top: document.documentElement.scrollHeight,
+ behavior: "smooth",
+ });
- const updateChatHistoryAndFetch = async () => {
- // Step 1: Update chat history with the assistant's response
- await new Promise((resolve) => {
- setChatHistory((prevChatHistory) => {
- const newChatHistory = [...prevChatHistory];
- const lastAnswer = newChatHistory[newChatHistory.length - 1];
- if (!lastAnswer) {
- resolve(undefined);
- return prevChatHistory;
- }
+ const updateChatHistoryAndFetch = async () => {
+ // Step 1: Update chat history with the assistant's response
+ await new Promise((resolve) => {
+ setChatHistory((prevChatHistory) => {
+ const newChatHistory = [...prevChatHistory];
+ const lastAnswer = newChatHistory[newChatHistory.length - 1];
+ if (!lastAnswer) {
+ resolve(undefined);
+ return prevChatHistory;
+ }
- const filteredSourceUrls = new Set(
- sourcesParsed.data.metadata.map((source) => source.url),
- );
- const uniqueSources = sourcesParsed.data.metadata.filter((source) => {
- if (filteredSourceUrls.has(source.url)) {
- filteredSourceUrls.delete(source.url);
- return true;
- }
- return false;
- });
+ const filteredSourceUrls = new Set(
+ sourcesParsed.data.metadata.map((source) => source.url),
+ );
+ const uniqueSources = sourcesParsed.data.metadata.filter((source) => {
+ if (filteredSourceUrls.has(source.url)) {
+ filteredSourceUrls.delete(source.url);
+ return true;
+ }
+ return false;
+ });
- lastAnswer.answer.sources = uniqueSources.map((source) => ({
- title: source.title ?? "Untitled",
- type: source.type ?? "page",
- source: source.url ?? "https://supermemory.ai",
- content: source.description ?? "No content available",
- numChunks: sourcesParsed.data.metadata.filter(
- (f) => f.url === source.url,
- ).length,
- }));
+ lastAnswer.answer.sources = uniqueSources.map((source) => ({
+ title: source.title ?? "Untitled",
+ type: source.type ?? "page",
+ source: source.url ?? "https://supermemory.ai",
+ content: source.description ?? "No content available",
+ numChunks: sourcesParsed.data.metadata.filter(
+ (f) => f.url === source.url,
+ ).length,
+ }));
- resolve(newChatHistory);
- return newChatHistory;
- });
- });
+ resolve(newChatHistory);
+ return newChatHistory;
+ });
+ });
- // Step 2: Fetch data from the API
- const resp = await fetch(
- `/api/chat?q=${query}&spaces=${spaces}&threadId=${threadId}`,
- {
- method: "POST",
- body: JSON.stringify({ chatHistory, sources: sourcesParsed.data }),
- },
- );
+ // Step 2: Fetch data from the API
+ const resp = await fetch(
+ `/api/chat?q=${query}&spaces=${spaces}&threadId=${threadId}`,
+ {
+ method: "POST",
+ body: JSON.stringify({ chatHistory, sources: sourcesParsed.data }),
+ },
+ );
- // Step 3: Read the response stream and update the chat history
- const reader = resp.body?.getReader();
- let done = false;
- while (!done && reader) {
- const { value, done: d } = await reader.read();
- if (d) {
- setChatHistory((prevChatHistory) => {
- createChatObject(threadId, prevChatHistory);
- return prevChatHistory;
- });
- }
- done = d;
+ // Step 3: Read the response stream and update the chat history
+ const reader = resp.body?.getReader();
+ let done = false;
+ while (!done && reader) {
+ const { value, done: d } = await reader.read();
+ if (d) {
+ setChatHistory((prevChatHistory) => {
+ createChatObject(threadId, prevChatHistory);
+ return prevChatHistory;
+ });
+ }
+ done = d;
- const txt = new TextDecoder().decode(value);
- setChatHistory((prevChatHistory) => {
- const newChatHistory = [...prevChatHistory];
- const lastAnswer = newChatHistory[newChatHistory.length - 1];
- if (!lastAnswer) return prevChatHistory;
+ const txt = new TextDecoder().decode(value);
+ setChatHistory((prevChatHistory) => {
+ const newChatHistory = [...prevChatHistory];
+ const lastAnswer = newChatHistory[newChatHistory.length - 1];
+ if (!lastAnswer) return prevChatHistory;
- window.scrollTo({
- top: document.documentElement.scrollHeight,
- behavior: "smooth",
- });
+ window.scrollTo({
+ top: document.documentElement.scrollHeight,
+ behavior: "smooth",
+ });
- lastAnswer.answer.parts.push({ text: txt });
- return newChatHistory;
- });
- }
- };
+ lastAnswer.answer.parts.push({ text: txt });
+ return newChatHistory;
+ });
+ }
+ };
- updateChatHistoryAndFetch();
- };
+ updateChatHistoryAndFetch();
+ };
- useEffect(() => {
- if (q.trim().length > 0 || chatHistory.length > 0) {
- setLayout("chat");
- const lastChat = chatHistory.length > 0 ? chatHistory.length - 1 : 0;
- const startGenerating = chatHistory[lastChat]?.answer.parts[0]?.text
- ? false
- : true;
- if (startGenerating) {
- getAnswer(
- q,
- spaces.map((s) => `${s.id}`),
- );
- }
- } else {
- router.push("/home");
- }
- }, []);
+ useEffect(() => {
+ if (q.trim().length > 0 || chatHistory.length > 0) {
+ setLayout("chat");
+ const lastChat = chatHistory.length > 0 ? chatHistory.length - 1 : 0;
+ const startGenerating = chatHistory[lastChat]?.answer.parts[0]?.text
+ ? false
+ : true;
+ if (startGenerating) {
+ getAnswer(
+ q,
+ spaces.map((s) => `${s.id}`),
+ );
+ }
+ } else {
+ router.push("/home");
+ }
+ }, []);
- return (
- <div className="h-full">
- <AnimatePresence mode="popLayout">
- {layout === "initial" ? (
- <motion.div
- exit={{ opacity: 0 }}
- key="initial"
- className="max-w-3xl h-full justify-center items-center flex mx-auto w-full flex-col"
- >
- <div className="w-full h-96">
- <QueryInput
- handleSubmit={() => {}}
- initialQuery={q}
- initialSpaces={[]}
- disabled
- />
- </div>
- </motion.div>
- ) : (
- <div
- className="max-w-3xl z-10 mx-auto relative h-full overflow-y-auto scrollbar-none"
- key="chat"
- >
- <div className="w-full pt-24 mb-40 px-4 md:px-0">
- {chatHistory.map((chat, idx) => (
- <div key={idx} className="space-y-16">
- <div
- className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b border-b-gray-400" : ""}`}
- >
- <h2
- className={cn(
- "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-xl",
- )}
- >
- {chat.question}
- </h2>
+ return (
+ <div className="h-full">
+ <AnimatePresence mode="popLayout">
+ {layout === "initial" ? (
+ <motion.div
+ exit={{ opacity: 0 }}
+ key="initial"
+ className="max-w-3xl h-full justify-center items-center flex mx-auto w-full flex-col"
+ >
+ <div className="w-full h-96">
+ <QueryInput
+ handleSubmit={() => {}}
+ initialQuery={q}
+ initialSpaces={[]}
+ disabled
+ />
+ </div>
+ </motion.div>
+ ) : (
+ <div
+ className="max-w-3xl z-10 mx-auto relative h-full overflow-y-auto scrollbar-none"
+ key="chat"
+ >
+ <div className="w-full pt-24 mb-40 px-4 md:px-0">
+ {chatHistory.map((chat, idx) => (
+ <div key={idx} className="space-y-16">
+ <div
+ className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b border-b-gray-400" : ""}`}
+ >
+ <h2
+ className={cn(
+ "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-xl",
+ )}
+ >
+ {chat.question}
+ </h2>
- <div className="flex flex-col">
- {/* Related memories */}
- <div
- className={`space-y-4 ${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`}
- >
- <Accordion
- defaultValue={
- idx === chatHistory.length - 1 ? "memories" : ""
- }
- type="single"
- collapsible
- >
- <AccordionItem value="memories">
- <AccordionTrigger className="text-foreground-menu">
- Related Memories
- </AccordionTrigger>
- {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */}
- <AccordionContent
- className="flex items-center no-scrollbar overflow-auto gap-4 relative max-w-3xl no-scrollbar"
- defaultChecked
- >
- {/* Loading state */}
- {chat.answer.sources.length > 0 ||
- (chat.answer.parts.length === 0 && (
- <>
- {[1, 2, 3, 4].map((_, idx) => (
- <div
- key={`loadingState-${idx}`}
- className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary animate-pulse"
- >
- <div className="bg-slate-700 h-2 rounded-full w-1/2"></div>
- <div className="bg-slate-700 h-2 rounded-full w-full"></div>
- </div>
- ))}
- </>
- ))}
- {chat.answer.sources.map((source, idx) => (
- <Link
- href={source.source}
- key={idx}
- className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary"
- >
- <div className="flex justify-between text-foreground-menu text-sm">
- <span>{source.type}</span>
+ <div className="flex flex-col">
+ {/* Related memories */}
+ <div
+ className={`space-y-4 ${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`}
+ >
+ <Accordion
+ defaultValue={
+ idx === chatHistory.length - 1 ? "memories" : ""
+ }
+ type="single"
+ collapsible
+ >
+ <AccordionItem value="memories">
+ <AccordionTrigger className="text-foreground-menu">
+ Related Memories
+ </AccordionTrigger>
+ {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */}
+ <AccordionContent
+ className="flex items-center no-scrollbar overflow-auto gap-4 relative max-w-3xl no-scrollbar"
+ defaultChecked
+ >
+ {/* Loading state */}
+ {chat.answer.sources.length > 0 ||
+ (chat.answer.parts.length === 0 && (
+ <>
+ {[1, 2, 3, 4].map((_, idx) => (
+ <div
+ key={`loadingState-${idx}`}
+ className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary animate-pulse"
+ >
+ <div className="bg-slate-700 h-2 rounded-full w-1/2"></div>
+ <div className="bg-slate-700 h-2 rounded-full w-full"></div>
+ </div>
+ ))}
+ </>
+ ))}
+ {chat.answer.sources.map((source, idx) => (
+ <Link
+ href={source.source}
+ key={idx}
+ className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary"
+ >
+ <div className="flex justify-between text-foreground-menu text-sm">
+ <span>{source.type}</span>
- {source.numChunks > 1 && (
- <span>{source.numChunks} chunks</span>
- )}
- </div>
- <div className="text-base">
- {source.title}
- </div>
- <div className="text-xs line-clamp-2">
- {source.content.length > 100
- ? source.content.slice(0, 100) + "..."
- : source.content}
- </div>
- </Link>
- ))}
- </AccordionContent>
- </AccordionItem>
- </Accordion>
- </div>
+ {source.numChunks > 1 && (
+ <span>{source.numChunks} chunks</span>
+ )}
+ </div>
+ <div className="text-base">
+ {source.title}
+ </div>
+ <div className="text-xs line-clamp-2">
+ {source.content.length > 100
+ ? source.content.slice(0, 100) + "..."
+ : source.content}
+ </div>
+ </Link>
+ ))}
+ </AccordionContent>
+ </AccordionItem>
+ </Accordion>
+ </div>
- {/* Summary */}
- <div>
- <div className="text-foreground-menu py-2">Summary</div>
- <div className="text-base">
- {/* Loading state */}
- {(chat.answer.parts.length === 0 ||
- chat.answer.parts.join("").length === 0) && (
- <div className="animate-pulse flex space-x-4">
- <div className="flex-1 space-y-3 py-1">
- <div className="h-2 bg-slate-700 rounded"></div>
- <div className="h-2 bg-slate-700 rounded"></div>
- </div>
- </div>
- )}
+ {/* Summary */}
+ <div>
+ <div className="text-foreground-menu py-2">Summary</div>
+ <div className="text-base">
+ {/* Loading state */}
+ {(chat.answer.parts.length === 0 ||
+ chat.answer.parts.join("").length === 0) && (
+ <div className="animate-pulse flex space-x-4">
+ <div className="flex-1 space-y-3 py-1">
+ <div className="h-2 bg-slate-700 rounded"></div>
+ <div className="h-2 bg-slate-700 rounded"></div>
+ </div>
+ </div>
+ )}
- <Markdown
- remarkPlugins={[remarkGfm, [remarkMath]]}
- rehypePlugins={[
- rehypeKatex,
- [
- rehypeHighlight,
- {
- detect: true,
- ignoreMissing: true,
- subset: codeLanguageSubset,
- },
- ],
- ]}
- components={{
- code: code as any,
- p: p as any,
- }}
- className="flex flex-col gap-2 text-base"
- >
- {removeJustificationFromText(
- chat.answer.parts
- .map((part) => part.text)
- .join(""),
- )}
- </Markdown>
+ <Markdown
+ remarkPlugins={[remarkGfm, [remarkMath]]}
+ rehypePlugins={[
+ rehypeKatex,
+ [
+ rehypeHighlight,
+ {
+ detect: true,
+ ignoreMissing: true,
+ subset: codeLanguageSubset,
+ },
+ ],
+ ]}
+ components={{
+ code: code as any,
+ p: p as any,
+ }}
+ className="flex flex-col gap-2 text-base"
+ >
+ {removeJustificationFromText(
+ chat.answer.parts
+ .map((part) => part.text)
+ .join(""),
+ )}
+ </Markdown>
- <div className="mt-3 relative -left-2 flex items-center gap-1">
- {/* TODO: speak response */}
- {/* <button className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200">
+ <div className="mt-3 relative -left-2 flex items-center gap-1">
+ {/* TODO: speak response */}
+ {/* <button className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200">
<SpeakerWaveIcon className="size-[18px] group-hover:text-primary" />
</button> */}
- {/* copy response */}
- <button
- onClick={() =>
- navigator.clipboard.writeText(
- chat.answer.parts
- .map((part) => part.text)
- .join(""),
- )
- }
- className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200"
- >
- <ClipboardIcon className="size-[18px] group-hover:text-primary" />
- </button>
- <button
- onClick={async () => {
- const isWebShareSupported =
- navigator.share !== undefined;
- if (isWebShareSupported) {
- try {
- await navigator.share({
- title: "Your Share Title",
- text: "Your share text or description",
- url: "https://your-url-to-share.com",
- });
- } catch (e) {
- console.error("Error sharing:", e);
- }
- } else {
- console.error("web share is not supported!");
- }
- }}
- className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200"
- >
- <SendIcon className="size-[18px] group-hover:text-primary" />
- </button>
- </div>
- </div>
- </div>
- {/* Justification */}
- {chat.answer.justification &&
- chat.answer.justification.length && (
- <div
- className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`}
- >
- <Accordion
- defaultValue={""}
- type="single"
- collapsible
- >
- <AccordionItem value="justification">
- <AccordionTrigger className="text-foreground-menu">
- Justification
- </AccordionTrigger>
- <AccordionContent
- className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
- defaultChecked
- >
- {chat.answer.justification.length > 0
- ? chat.answer.justification
- .replaceAll("<justification>", "")
- .replaceAll("</justification>", "")
- : "No justification provided."}
- </AccordionContent>
- </AccordionItem>
- </Accordion>
- </div>
- )}
- </div>
- </div>
- </div>
- ))}
- </div>
+ {/* copy response */}
+ <button
+ onClick={() =>
+ navigator.clipboard.writeText(
+ chat.answer.parts
+ .map((part) => part.text)
+ .join(""),
+ )
+ }
+ className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200"
+ >
+ <ClipboardIcon className="size-[18px] group-hover:text-primary" />
+ </button>
+ <button
+ onClick={async () => {
+ const isWebShareSupported =
+ navigator.share !== undefined;
+ if (isWebShareSupported) {
+ try {
+ await navigator.share({
+ title: "Your Share Title",
+ text: "Your share text or description",
+ url: "https://your-url-to-share.com",
+ });
+ } catch (e) {
+ console.error("Error sharing:", e);
+ }
+ } else {
+ console.error("web share is not supported!");
+ }
+ }}
+ className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200"
+ >
+ <SendIcon className="size-[18px] group-hover:text-primary" />
+ </button>
+ </div>
+ </div>
+ </div>
+ {/* Justification */}
+ {chat.answer.justification &&
+ chat.answer.justification.length && (
+ <div
+ className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`}
+ >
+ <Accordion
+ defaultValue={""}
+ type="single"
+ collapsible
+ >
+ <AccordionItem value="justification">
+ <AccordionTrigger className="text-foreground-menu">
+ Justification
+ </AccordionTrigger>
+ <AccordionContent
+ className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
+ defaultChecked
+ >
+ {chat.answer.justification.length > 0
+ ? chat.answer.justification
+ .replaceAll("<justification>", "")
+ .replaceAll("</justification>", "")
+ : "No justification provided."}
+ </AccordionContent>
+ </AccordionItem>
+ </Accordion>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
- <div className="fixed bottom-24 md:bottom-4 w-full max-w-3xl">
- <QueryInput
- mini
- className="w-full shadow-md"
- initialQuery={""}
- initialSpaces={spaces}
- handleSubmit={async (q, spaces) => {
- setChatHistory((prevChatHistory) => {
- return [
- ...prevChatHistory,
- {
- question: q,
- answer: {
- parts: [],
- sources: [],
- },
- },
- ];
- });
- await getAnswer(
- q,
- spaces.map((s) => `${s.id}`),
- );
- }}
- />
- </div>
- </div>
- )}
- </AnimatePresence>
- </div>
- );
+ <div className="fixed bottom-24 md:bottom-4 w-full max-w-3xl">
+ <QueryInput
+ mini
+ className="w-full shadow-md"
+ initialQuery={""}
+ initialSpaces={spaces}
+ handleSubmit={async (q, spaces) => {
+ setChatHistory((prevChatHistory) => {
+ return [
+ ...prevChatHistory,
+ {
+ question: q,
+ answer: {
+ parts: [],
+ sources: [],
+ },
+ },
+ ];
+ });
+ await getAnswer(
+ q,
+ spaces.map((s) => `${s.id}`),
+ );
+ }}
+ />
+ </div>
+ </div>
+ )}
+ </AnimatePresence>
+ </div>
+ );
}
export default ChatWindow;
diff --git a/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx b/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
index 747d4fca..71d3b889 100644
--- a/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
+++ b/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
@@ -3,23 +3,23 @@ import { ExtraProps } from "react-markdown";
import CodeBlock from "./CodeBlock";
export const code = memo((props: JSX.IntrinsicElements["code"]) => {
- const { className, children } = props;
- const match = /language-(\w+)/.exec(className || "");
- const lang = match && match[1];
+ const { className, children } = props;
+ const match = /language-(\w+)/.exec(className || "");
+ const lang = match && match[1];
- return <CodeBlock lang={lang || "text"} codeChildren={children as any} />;
+ return <CodeBlock lang={lang || "text"} codeChildren={children as any} />;
});
export const p = memo(
- (
- props?: Omit<
- DetailedHTMLProps<
- HTMLAttributes<HTMLParagraphElement>,
- HTMLParagraphElement
- >,
- "ref"
- >,
- ) => {
- return <p className="whitespace-pre-wrap">{props?.children}</p>;
- },
+ (
+ props?: Omit<
+ DetailedHTMLProps<
+ HTMLAttributes<HTMLParagraphElement>,
+ HTMLParagraphElement
+ >,
+ "ref"
+ >,
+ ) => {
+ return <p className="whitespace-pre-wrap">{props?.children}</p>;
+ },
);
diff --git a/apps/web/app/(dash)/header/autoBreadCrumbs.tsx b/apps/web/app/(dash)/header/autoBreadCrumbs.tsx
index 5767ca6f..a823671c 100644
--- a/apps/web/app/(dash)/header/autoBreadCrumbs.tsx
+++ b/apps/web/app/(dash)/header/autoBreadCrumbs.tsx
@@ -1,47 +1,47 @@
"use client";
import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbSeparator,
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbList,
+ BreadcrumbSeparator,
} from "@repo/ui/shadcn/breadcrumb";
import { usePathname } from "next/navigation";
import React from "react";
function AutoBreadCrumbs() {
- const pathname = usePathname();
+ const pathname = usePathname();
- console.log(pathname.split("/").filter(Boolean));
+ console.log(pathname.split("/").filter(Boolean));
- return (
- <Breadcrumb className="hidden md:block">
- <BreadcrumbList>
- {!pathname.startsWith("/home") && (
- <>
- <BreadcrumbItem>
- <BreadcrumbLink href="/">Home</BreadcrumbLink>
- </BreadcrumbItem>
- <BreadcrumbSeparator hidden={pathname.split("/").length === 1} />
- </>
- )}
- {pathname
- .split("/")
- .filter(Boolean)
- .map((path, idx, paths) => (
- <>
- <BreadcrumbItem key={path}>
- <BreadcrumbLink href={`/${paths.slice(0, idx + 1).join("/")}`}>
- {path.charAt(0).toUpperCase() + path.slice(1)}
- </BreadcrumbLink>
- </BreadcrumbItem>
- <BreadcrumbSeparator hidden={idx === paths.length - 1} />
- </>
- ))}
- </BreadcrumbList>
- </Breadcrumb>
- );
+ return (
+ <Breadcrumb className="hidden md:block">
+ <BreadcrumbList>
+ {!pathname.startsWith("/home") && (
+ <>
+ <BreadcrumbItem>
+ <BreadcrumbLink href="/">Home</BreadcrumbLink>
+ </BreadcrumbItem>
+ <BreadcrumbSeparator hidden={pathname.split("/").length === 1} />
+ </>
+ )}
+ {pathname
+ .split("/")
+ .filter(Boolean)
+ .map((path, idx, paths) => (
+ <>
+ <BreadcrumbItem key={path}>
+ <BreadcrumbLink href={`/${paths.slice(0, idx + 1).join("/")}`}>
+ {path.charAt(0).toUpperCase() + path.slice(1)}
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+ <BreadcrumbSeparator hidden={idx === paths.length - 1} />
+ </>
+ ))}
+ </BreadcrumbList>
+ </Breadcrumb>
+ );
}
export default AutoBreadCrumbs;
diff --git a/apps/web/app/(dash)/header/header.tsx b/apps/web/app/(dash)/header/header.tsx
index 0fc28227..b9d400c9 100644
--- a/apps/web/app/(dash)/header/header.tsx
+++ b/apps/web/app/(dash)/header/header.tsx
@@ -8,54 +8,54 @@ import NewChatButton from "./newChatButton";
import AutoBreadCrumbs from "./autoBreadCrumbs";
async function Header() {
- const chatThreads = await getChatHistory();
-
- if (!chatThreads.success || !chatThreads.data) {
- return <div>Error fetching chat threads</div>;
- }
-
- return (
- <div className="p-4 relative z-30 h-16 flex items-center">
- <div className="w-full flex items-center justify-between">
- <div className="flex items-center gap-4">
- <Link className="" href="/home">
- <Image
- src={Logo}
- alt="SuperMemory logo"
- className="hover:brightness-125 duration-200 w-full h-full"
- />
- </Link>
-
- <AutoBreadCrumbs />
- </div>
-
- <div className="flex items-center gap-2">
- <NewChatButton />
-
- <div className="relative group">
- <button className="flex duration-200 items-center text-[#7D8994] hover:bg-[#1F2429] text-[13px] gap-2 px-3 py-2 rounded-xl">
- History
- </button>
-
- <div className="absolute p-4 hidden group-hover:block right-0 w-full md:w-[400px] max-h-[70vh] overflow-auto">
- <div className="bg-[#1F2429] rounded-xl p-2 flex flex-col shadow-lg">
- {chatThreads.data.map((thread) => (
- <Link
- prefetch={false}
- href={`/chat/${thread.id}`}
- key={thread.id}
- className="p-2 rounded-md hover:bg-secondary"
- >
- {thread.firstMessage}
- </Link>
- ))}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
+ const chatThreads = await getChatHistory();
+
+ if (!chatThreads.success || !chatThreads.data) {
+ return <div>Error fetching chat threads</div>;
+ }
+
+ return (
+ <div className="p-4 relative z-30 h-16 flex items-center">
+ <div className="w-full flex items-center justify-between">
+ <div className="flex items-center gap-4">
+ <Link className="" href="/home">
+ <Image
+ src={Logo}
+ alt="SuperMemory logo"
+ className="hover:brightness-125 duration-200 w-full h-full"
+ />
+ </Link>
+
+ <AutoBreadCrumbs />
+ </div>
+
+ <div className="flex items-center gap-2">
+ <NewChatButton />
+
+ <div className="relative group">
+ <button className="flex duration-200 items-center text-[#7D8994] hover:bg-[#1F2429] text-[13px] gap-2 px-3 py-2 rounded-xl">
+ History
+ </button>
+
+ <div className="absolute p-4 hidden group-hover:block right-0 w-full md:w-[400px] max-h-[70vh] overflow-auto">
+ <div className="bg-[#1F2429] rounded-xl p-2 flex flex-col shadow-lg">
+ {chatThreads.data.map((thread) => (
+ <Link
+ prefetch={false}
+ href={`/chat/${thread.id}`}
+ key={thread.id}
+ className="p-2 rounded-md hover:bg-secondary"
+ >
+ {thread.firstMessage}
+ </Link>
+ ))}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
}
export default Header;
diff --git a/apps/web/app/(dash)/header/newChatButton.tsx b/apps/web/app/(dash)/header/newChatButton.tsx
index 0e9e1c5a..a634ab41 100644
--- a/apps/web/app/(dash)/header/newChatButton.tsx
+++ b/apps/web/app/(dash)/header/newChatButton.tsx
@@ -7,21 +7,21 @@ import { usePathname } from "next/navigation";
import React from "react";
function NewChatButton() {
- const path = usePathname();
+ const path = usePathname();
- if (path.startsWith("/chat")) {
- return (
- <Link
- href="/home"
- className="flex duration-200 items-center text-[#7D8994] hover:bg-[#1F2429] text-[13px] gap-2 px-3 py-2 rounded-xl"
- >
- <Image src={ChatIcon} alt="Chat icon" className="w-5" />
- Start new chat
- </Link>
- );
- }
+ if (path.startsWith("/chat")) {
+ return (
+ <Link
+ href="/home"
+ className="flex duration-200 items-center text-[#7D8994] hover:bg-[#1F2429] text-[13px] gap-2 px-3 py-2 rounded-xl"
+ >
+ <Image src={ChatIcon} alt="Chat icon" className="w-5" />
+ Start new chat
+ </Link>
+ );
+ }
- return null;
+ return null;
}
export default NewChatButton;
diff --git a/apps/web/app/(dash)/home/homeVariants.ts b/apps/web/app/(dash)/home/homeVariants.ts
index cc533fc4..1b44bab9 100644
--- a/apps/web/app/(dash)/home/homeVariants.ts
+++ b/apps/web/app/(dash)/home/homeVariants.ts
@@ -1,50 +1,50 @@
export const variants = [
- [
- {
- type: "text",
- content: "Unlock your",
- },
- {
- type: "highlighted",
- content: " digital brain",
- },
- ],
- [
- {
- type: "text",
- content: "Save",
- },
- {
- type: "highlighted",
- content: " everything.",
- },
- {
- type: "text",
- content: " Connect",
- },
- {
- type: "highlighted",
- content: " anything.",
- },
- ],
- [
- {
- type: "text",
- content: "Turn your bookmarks into",
- },
- {
- type: "highlighted",
- content: " insights.",
- },
- ],
- [
- {
- type: "text",
- content: "The smart way to use your",
- },
- {
- type: "highlighted",
- content: " digital treasure.",
- },
- ],
+ [
+ {
+ type: "text",
+ content: "Unlock your",
+ },
+ {
+ type: "highlighted",
+ content: " digital brain",
+ },
+ ],
+ [
+ {
+ type: "text",
+ content: "Save",
+ },
+ {
+ type: "highlighted",
+ content: " everything.",
+ },
+ {
+ type: "text",
+ content: " Connect",
+ },
+ {
+ type: "highlighted",
+ content: " anything.",
+ },
+ ],
+ [
+ {
+ type: "text",
+ content: "Turn your bookmarks into",
+ },
+ {
+ type: "highlighted",
+ content: " insights.",
+ },
+ ],
+ [
+ {
+ type: "text",
+ content: "The smart way to use your",
+ },
+ {
+ type: "highlighted",
+ content: " digital treasure.",
+ },
+ ],
];
diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx
index a3a3b946..7ef8e65d 100644
--- a/apps/web/app/(dash)/home/page.tsx
+++ b/apps/web/app/(dash)/home/page.tsx
@@ -10,127 +10,127 @@ import { motion } from "framer-motion";
import { variants } from "./homeVariants";
const slap = {
- initial: {
- opacity: 0,
- scale: 1.1,
- },
- whileInView: { opacity: 1, scale: 1 },
- transition: {
- duration: 0.5,
- ease: "easeInOut",
- },
- viewport: { once: true },
+ initial: {
+ opacity: 0,
+ scale: 1.1,
+ },
+ whileInView: { opacity: 1, scale: 1 },
+ transition: {
+ duration: 0.5,
+ ease: "easeInOut",
+ },
+ viewport: { once: true },
};
function Page({
- searchParams,
+ searchParams,
}: {
- searchParams: Record<string, string | string[] | undefined>;
+ searchParams: Record<string, string | string[] | undefined>;
}) {
- // TODO: use this to show a welcome page/modal
- // const { firstTime } = homeSearchParamsCache.parse(searchParams);
-
- const [telegramUser, setTelegramUser] = useState<string | undefined>(
- searchParams.telegramUser as string,
- );
- const [extensionInstalled, setExtensionInstalled] = useState<
- string | undefined
- >(searchParams.extension as string);
-
- const { push } = useRouter();
-
- const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]);
-
- const [showVariant, setShowVariant] = useState<number>(0);
-
- useEffect(() => {
- if (telegramUser) {
- const linkTelegram = async () => {
- const response = await linkTelegramToUser(telegramUser);
-
- if (response.success) {
- toast.success("Your telegram has been linked successfully.");
- } else {
- toast.error("Failed to link telegram. Please try again.");
- }
- };
-
- linkTelegram();
- }
-
- if (extensionInstalled) {
- toast.success("Extension installed successfully");
- }
-
- getSpaces().then((res) => {
- if (res.success && res.data) {
- setSpaces(res.data);
- return;
- }
- // TODO: HANDLE ERROR
- });
-
- setShowVariant(Math.floor(Math.random() * variants.length));
-
- getSessionAuthToken().then((token) => {
- if (typeof window === "undefined") return;
- window.postMessage({ token: token.data }, "*");
- });
- }, [telegramUser]);
-
- return (
- <div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col px-2 md:px-0">
- {/* all content goes here */}
- {/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
-
- <motion.h1
- {...{
- ...slap,
- transition: { ...slap.transition, delay: 0.2 },
- }}
- className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl"
- >
- {variants[showVariant]!.map((v, i) => {
- return (
- <span
- key={i}
- className={
- v.type === "highlighted"
- ? "bg-gradient-to-r to-blue-200 from-zinc-300 text-transparent bg-clip-text"
- : ""
- }
- >
- {v.content}
- </span>
- );
- })}
- </motion.h1>
-
- <div className="w-full pb-20 mt-12">
- <QueryInput
- handleSubmit={async (q, spaces) => {
- if (q.length === 0) {
- toast.error("Query is required");
- return;
- }
-
- const threadid = await createChatThread(q);
-
- if (!threadid.success || !threadid.data) {
- toast.error("Failed to create chat thread");
- return;
- }
-
- push(
- `/chat/${threadid.data}?spaces=${JSON.stringify(spaces)}&q=${q}`,
- );
- }}
- initialSpaces={spaces}
- setInitialSpaces={setSpaces}
- />
- </div>
- </div>
- );
+ // TODO: use this to show a welcome page/modal
+ // const { firstTime } = homeSearchParamsCache.parse(searchParams);
+
+ const [telegramUser, setTelegramUser] = useState<string | undefined>(
+ searchParams.telegramUser as string,
+ );
+ const [extensionInstalled, setExtensionInstalled] = useState<
+ string | undefined
+ >(searchParams.extension as string);
+
+ const { push } = useRouter();
+
+ const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]);
+
+ const [showVariant, setShowVariant] = useState<number>(0);
+
+ useEffect(() => {
+ if (telegramUser) {
+ const linkTelegram = async () => {
+ const response = await linkTelegramToUser(telegramUser);
+
+ if (response.success) {
+ toast.success("Your telegram has been linked successfully.");
+ } else {
+ toast.error("Failed to link telegram. Please try again.");
+ }
+ };
+
+ linkTelegram();
+ }
+
+ if (extensionInstalled) {
+ toast.success("Extension installed successfully");
+ }
+
+ getSpaces().then((res) => {
+ if (res.success && res.data) {
+ setSpaces(res.data);
+ return;
+ }
+ // TODO: HANDLE ERROR
+ });
+
+ setShowVariant(Math.floor(Math.random() * variants.length));
+
+ getSessionAuthToken().then((token) => {
+ if (typeof window === "undefined") return;
+ window.postMessage({ token: token.data }, "*");
+ });
+ }, [telegramUser]);
+
+ return (
+ <div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col px-2 md:px-0">
+ {/* all content goes here */}
+ {/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
+
+ <motion.h1
+ {...{
+ ...slap,
+ transition: { ...slap.transition, delay: 0.2 },
+ }}
+ className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl"
+ >
+ {variants[showVariant]!.map((v, i) => {
+ return (
+ <span
+ key={i}
+ className={
+ v.type === "highlighted"
+ ? "bg-gradient-to-r to-blue-200 from-zinc-300 text-transparent bg-clip-text"
+ : ""
+ }
+ >
+ {v.content}
+ </span>
+ );
+ })}
+ </motion.h1>
+
+ <div className="w-full pb-20 mt-12">
+ <QueryInput
+ handleSubmit={async (q, spaces) => {
+ if (q.length === 0) {
+ toast.error("Query is required");
+ return;
+ }
+
+ const threadid = await createChatThread(q);
+
+ if (!threadid.success || !threadid.data) {
+ toast.error("Failed to create chat thread");
+ return;
+ }
+
+ push(
+ `/chat/${threadid.data}?spaces=${JSON.stringify(spaces)}&q=${q}`,
+ );
+ }}
+ initialSpaces={spaces}
+ setInitialSpaces={setSpaces}
+ />
+ </div>
+ </div>
+ );
}
export default Page;
diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx
index d622b8b0..c7267298 100644
--- a/apps/web/app/(dash)/home/queryinput.tsx
+++ b/apps/web/app/(dash)/home/queryinput.tsx
@@ -12,170 +12,170 @@ import { toast } from "sonner";
import { createSpace } from "@/app/actions/doers";
function QueryInput({
- initialQuery = "",
- initialSpaces = [],
- disabled = false,
- className,
- mini = false,
- handleSubmit,
- setInitialSpaces,
+ initialQuery = "",
+ initialSpaces = [],
+ disabled = false,
+ className,
+ mini = false,
+ handleSubmit,
+ setInitialSpaces,
}: {
- initialQuery?: string;
- initialSpaces?: {
- id: number;
- name: string;
- }[];
- disabled?: boolean;
- className?: string;
- mini?: boolean;
- handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void;
- setInitialSpaces?: React.Dispatch<
- React.SetStateAction<{ id: number; name: string }[]>
- >;
+ initialQuery?: string;
+ initialSpaces?: {
+ id: number;
+ name: string;
+ }[];
+ disabled?: boolean;
+ className?: string;
+ mini?: boolean;
+ handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void;
+ setInitialSpaces?: React.Dispatch<
+ React.SetStateAction<{ id: number; name: string }[]>
+ >;
}) {
- const [q, setQ] = useState(initialQuery);
+ const [q, setQ] = useState(initialQuery);
- const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
+ const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
- const options = useMemo(
- () =>
- initialSpaces.map((x) => ({
- label: x.name,
- value: x.id.toString(),
- })),
- [initialSpaces],
- );
+ const options = useMemo(
+ () =>
+ initialSpaces.map((x) => ({
+ label: x.name,
+ value: x.id.toString(),
+ })),
+ [initialSpaces],
+ );
- const preparedSpaces = useMemo(
- () =>
- initialSpaces
- .filter((x) => selectedSpaces.includes(x.id))
- .map((x) => {
- return {
- id: x.id,
- name: x.name,
- };
- }),
- [selectedSpaces, initialSpaces],
- );
+ const preparedSpaces = useMemo(
+ () =>
+ initialSpaces
+ .filter((x) => selectedSpaces.includes(x.id))
+ .map((x) => {
+ return {
+ id: x.id,
+ name: x.name,
+ };
+ }),
+ [selectedSpaces, initialSpaces],
+ );
- return (
- <div className={`${className}`}>
- <div
- className={`bg-secondary border-2 border-b-0 border-border ${!mini ? "rounded-t-3xl" : "rounded-3xl"}`}
- >
- {/* input and action button */}
- <form
- action={async () => {
- handleSubmit(q, preparedSpaces);
- setQ("");
- }}
- className="flex gap-4 p-3"
- >
- <textarea
- autoFocus
- name="q"
- cols={30}
- rows={mini ? 2 : 4}
- className="bg-transparent pt-2.5 text-base placeholder:text-[#9B9B9B] focus:text-gray-200 duration-200 tracking-[3%] outline-none resize-none w-full p-4"
- placeholder="Ask your second brain..."
- onKeyDown={(e) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- if (q.trim().length === 0) {
- return;
- }
- handleSubmit(q, preparedSpaces);
- setQ("");
- }
- }}
- onChange={(e) => setQ(e.target.value)}
- value={q}
- disabled={disabled}
- />
+ return (
+ <div className={`${className}`}>
+ <div
+ className={`bg-secondary border-2 border-b-0 border-border ${!mini ? "rounded-t-3xl" : "rounded-3xl"}`}
+ >
+ {/* input and action button */}
+ <form
+ action={async () => {
+ handleSubmit(q, preparedSpaces);
+ setQ("");
+ }}
+ className="flex gap-4 p-3"
+ >
+ <textarea
+ autoFocus
+ name="q"
+ cols={30}
+ rows={mini ? 2 : 4}
+ className="bg-transparent pt-2.5 text-base placeholder:text-[#9B9B9B] focus:text-gray-200 duration-200 tracking-[3%] outline-none resize-none w-full p-4"
+ placeholder="Ask your second brain..."
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ if (q.trim().length === 0) {
+ return;
+ }
+ handleSubmit(q, preparedSpaces);
+ setQ("");
+ }
+ }}
+ onChange={(e) => setQ(e.target.value)}
+ value={q}
+ disabled={disabled}
+ />
- <button
- type="submit"
- onClick={(e) => {
- e.preventDefault();
- if (q.trim().length === 0) {
- return;
- }
- handleSubmit(q, preparedSpaces);
- }}
- disabled={disabled}
- className="h-12 w-12 rounded-[14px] bg-border all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90"
- >
- <Image src={ArrowRightIcon} alt="Right arrow icon" />
- </button>
- </form>
- </div>
- {/* selected sources */}
- {!mini && (
- <>
- <Divider />
- <div className="flex justify-between items-center gap-6 h-auto bg-secondary rounded-b-3xl border-2 border-border">
- <Combobox
- options={options}
- className="rounded-bl-3xl bg-[#3C464D] w-44"
- onSelect={(v) =>
- setSelectedSpaces((prev) => {
- if (v === "") {
- return [];
- }
- return [...prev, parseInt(v)];
- })
- }
- onSubmit={async (spaceName) => {
- const space = options.find((x) => x.label === spaceName);
- toast.info("Creating space...");
+ <button
+ type="submit"
+ onClick={(e) => {
+ e.preventDefault();
+ if (q.trim().length === 0) {
+ return;
+ }
+ handleSubmit(q, preparedSpaces);
+ }}
+ disabled={disabled}
+ className="h-12 w-12 rounded-[14px] bg-border all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90"
+ >
+ <Image src={ArrowRightIcon} alt="Right arrow icon" />
+ </button>
+ </form>
+ </div>
+ {/* selected sources */}
+ {!mini && (
+ <>
+ <Divider />
+ <div className="flex justify-between items-center gap-6 h-auto bg-secondary rounded-b-3xl border-2 border-border">
+ <Combobox
+ options={options}
+ className="rounded-bl-3xl bg-[#3C464D] w-44"
+ onSelect={(v) =>
+ setSelectedSpaces((prev) => {
+ if (v === "") {
+ return [];
+ }
+ return [...prev, parseInt(v)];
+ })
+ }
+ onSubmit={async (spaceName) => {
+ const space = options.find((x) => x.label === spaceName);
+ toast.info("Creating space...");
- if (space) {
- toast.error("A space with that name already exists.");
- }
+ if (space) {
+ toast.error("A space with that name already exists.");
+ }
- const creationTask = await createSpace(spaceName);
- if (creationTask.success && creationTask.data) {
- toast.success("Space created " + creationTask.data);
- setInitialSpaces?.((prev) => [
- ...prev,
- {
- name: spaceName,
- id: creationTask.data!,
- },
- ]);
- setSelectedSpaces((prev) => [...prev, creationTask.data!]);
- } else {
- toast.error(
- "Space creation failed: " + creationTask.error ??
- "Unknown error",
- );
- }
- }}
- placeholder="Chat with a space..."
- />
+ const creationTask = await createSpace(spaceName);
+ if (creationTask.success && creationTask.data) {
+ toast.success("Space created " + creationTask.data);
+ setInitialSpaces?.((prev) => [
+ ...prev,
+ {
+ name: spaceName,
+ id: creationTask.data!,
+ },
+ ]);
+ setSelectedSpaces((prev) => [...prev, creationTask.data!]);
+ } else {
+ toast.error(
+ "Space creation failed: " + creationTask.error ??
+ "Unknown error",
+ );
+ }
+ }}
+ placeholder="Chat with a space..."
+ />
- <div className="flex flex-row gap-0.5 h-full">
- {preparedSpaces.map((x, idx) => (
- <button
- key={x.id}
- onClick={() =>
- setSelectedSpaces((prev) => prev.filter((y) => y !== x.id))
- }
- className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${idx === preparedSpaces.length - 1 ? "rounded-br-xl" : ""}`}
- >
- <p className="line-clamp-1">{x.name}</p>
- <div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
- <MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
- </div>
- </button>
- ))}
- </div>
- </div>
- </>
- )}
- </div>
- );
+ <div className="flex flex-row gap-0.5 h-full">
+ {preparedSpaces.map((x, idx) => (
+ <button
+ key={x.id}
+ onClick={() =>
+ setSelectedSpaces((prev) => prev.filter((y) => y !== x.id))
+ }
+ className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${idx === preparedSpaces.length - 1 ? "rounded-br-xl" : ""}`}
+ >
+ <p className="line-clamp-1">{x.name}</p>
+ <div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
+ <MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
+ </div>
+ </button>
+ ))}
+ </div>
+ </div>
+ </>
+ )}
+ </div>
+ );
}
export default QueryInput;
diff --git a/apps/web/app/(dash)/layout.tsx b/apps/web/app/(dash)/layout.tsx
index e161992f..b2b27a4f 100644
--- a/apps/web/app/(dash)/layout.tsx
+++ b/apps/web/app/(dash)/layout.tsx
@@ -6,33 +6,33 @@ import { Toaster } from "@repo/ui/shadcn/sonner";
import BackgroundPlus from "../(landing)/GridPatterns/PlusGrid";
async function Layout({ children }: { children: React.ReactNode }) {
- const info = await auth();
+ const info = await auth();
- if (!info) {
- return redirect("/signin");
- }
+ if (!info) {
+ return redirect("/signin");
+ }
- return (
- <main className="h-screen flex flex-col">
- <div className="fixed top-0 left-0 w-full z-40">
- <Header />
- </div>
+ return (
+ <main className="h-screen flex flex-col">
+ <div className="fixed top-0 left-0 w-full z-40">
+ <Header />
+ </div>
- <div className="relative flex justify-center z-40 pointer-events-none">
- <div
- className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
+ <div className="relative flex justify-center z-40 pointer-events-none">
+ <div
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
- <Menu />
+ <Menu />
- <div className="w-full h-full">{children}</div>
+ <div className="w-full h-full">{children}</div>
- <Toaster />
- </main>
- );
+ <Toaster />
+ </main>
+ );
}
export default Layout;
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index 5e05d358..711b081c 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -7,13 +7,13 @@ import { MemoriesIcon, ExploreIcon, CanvasIcon, AddIcon } from "@repo/ui/icons";
import { Button } from "@repo/ui/shadcn/button";
import { MinusIcon, PlusCircleIcon } from "lucide-react";
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
} from "@repo/ui/shadcn/dialog";
import { Label } from "@repo/ui/shadcn/label";
import { Textarea } from "@repo/ui/shadcn/textarea";
@@ -26,334 +26,334 @@ import { StoredSpace } from "@/server/db/schema";
import useMeasure from "react-use-measure";
function Menu() {
- const [spaces, setSpaces] = useState<StoredSpace[]>([]);
+ const [spaces, setSpaces] = useState<StoredSpace[]>([]);
- useEffect(() => {
- (async () => {
- let spaces = await getSpaces();
+ useEffect(() => {
+ (async () => {
+ let spaces = await getSpaces();
- if (!spaces.success || !spaces.data) {
- toast.warning("Unable to get spaces", {
- richColors: true,
- });
- setSpaces([]);
- return;
- }
- setSpaces(spaces.data);
- })();
- }, []);
+ if (!spaces.success || !spaces.data) {
+ toast.warning("Unable to get spaces", {
+ richColors: true,
+ });
+ setSpaces([]);
+ return;
+ }
+ setSpaces(spaces.data);
+ })();
+ }, []);
- const menuItems = [
- {
- icon: MemoriesIcon,
- text: "Memories",
- url: "/memories",
- disabled: false,
- },
- {
- icon: CanvasIcon,
- text: "Canvas",
- url: "/canvas",
- disabled: true,
- },
- ];
+ const menuItems = [
+ {
+ icon: MemoriesIcon,
+ text: "Memories",
+ url: "/memories",
+ disabled: false,
+ },
+ {
+ icon: CanvasIcon,
+ text: "Canvas",
+ url: "/canvas",
+ disabled: true,
+ },
+ ];
- const [content, setContent] = useState("");
- const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
+ const [content, setContent] = useState("");
+ const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
- const autoDetectedType = useMemo(() => {
- if (content.length === 0) {
- return "none";
- }
+ const autoDetectedType = useMemo(() => {
+ if (content.length === 0) {
+ return "none";
+ }
- if (
- content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)
- ) {
- return "tweet";
- } else if (content.match(/https?:\/\/[\w\.]+/)) {
- return "page";
- } else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
- return "page";
- } else {
- return "note";
- }
- }, [content]);
+ if (
+ content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)
+ ) {
+ return "tweet";
+ } else if (content.match(/https?:\/\/[\w\.]+/)) {
+ return "page";
+ } else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
+ return "page";
+ } else {
+ return "note";
+ }
+ }, [content]);
- const [dialogOpen, setDialogOpen] = useState(false);
+ const [dialogOpen, setDialogOpen] = useState(false);
- const options = useMemo(
- () =>
- spaces.map((x) => ({
- label: x.name,
- value: x.id.toString(),
- })),
- [spaces],
- );
+ const options = useMemo(
+ () =>
+ spaces.map((x) => ({
+ label: x.name,
+ value: x.id.toString(),
+ })),
+ [spaces],
+ );
- const handleSubmit = async (content?: string, spaces?: number[]) => {
- setDialogOpen(false);
+ const handleSubmit = async (content?: string, spaces?: number[]) => {
+ setDialogOpen(false);
- toast.info("Creating memory...", {
- icon: <PlusCircleIcon className="w-4 h-4 text-white animate-spin" />,
- duration: 7500,
- });
+ toast.info("Creating memory...", {
+ icon: <PlusCircleIcon className="w-4 h-4 text-white animate-spin" />,
+ duration: 7500,
+ });
- if (!content || content.length === 0) {
- toast.error("Content is required");
- return;
- }
+ if (!content || content.length === 0) {
+ toast.error("Content is required");
+ return;
+ }
- console.log(spaces);
+ console.log(spaces);
- const cont = await createMemory({
- content: content,
- spaces: spaces ?? undefined,
- });
+ const cont = await createMemory({
+ content: content,
+ spaces: spaces ?? undefined,
+ });
- setContent("");
- setSelectedSpaces([]);
+ setContent("");
+ setSelectedSpaces([]);
- if (cont.success) {
- toast.success("Memory created", {
- richColors: true,
- });
- } else {
- toast.error(`Memory creation failed: ${cont.error}`);
- }
- };
+ if (cont.success) {
+ toast.success("Memory created", {
+ richColors: true,
+ });
+ } else {
+ toast.error(`Memory creation failed: ${cont.error}`);
+ }
+ };
- return (
- <>
- {/* Desktop Menu */}
- <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
- <div className="hidden lg:flex fixed h-screen pb-20 w-full p-4 items-center justify-start top-0 left-0 pointer-events-none z-[39]">
- <div className="pointer-events-auto group flex w-14 text-foreground-menu text-[15px] font-medium flex-col items-start gap-6 overflow-hidden rounded-[28px] border-2 border-border bg-secondary px-3 py-4 duration-200 hover:w-40 z-[99999]">
- <div className="border-b border-border pb-4 w-full">
- <DialogTrigger
- className={`flex w-full text-white brightness-75 hover:brightness-125 focus:brightness-125 cursor-pointer items-center gap-3 px-1 duration-200 justify-start`}
- >
- <Image
- src={AddIcon}
- alt="Logo"
- width={24}
- height={24}
- className="hover:brightness-125 focus:brightness-125 duration-200 text-white"
- />
- <p className="opacity-0 duration-200 group-hover:opacity-100">
- Add
- </p>
- </DialogTrigger>
- </div>
- {menuItems.map((item) => (
- <Link
- aria-disabled={item.disabled}
- href={item.disabled ? "#" : item.url}
- key={item.url}
- className={`flex w-full ${
- item.disabled
- ? "cursor-not-allowed opacity-30"
- : "text-white brightness-75 hover:brightness-125 cursor-pointer"
- } items-center gap-3 px-1 duration-200 hover:scale-105 active:scale-90 justify-start`}
- >
- <Image
- src={item.icon}
- alt={`${item.text} icon`}
- width={24}
- height={24}
- className="hover:brightness-125 duration-200"
- />
- <p className="opacity-0 duration-200 group-hover:opacity-100">
- {item.text}
- </p>
- </Link>
- ))}
- </div>
- </div>
+ return (
+ <>
+ {/* Desktop Menu */}
+ <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
+ <div className="hidden lg:flex fixed h-screen pb-20 w-full p-4 items-center justify-start top-0 left-0 pointer-events-none z-[39]">
+ <div className="pointer-events-auto group flex w-14 text-foreground-menu text-[15px] font-medium flex-col items-start gap-6 overflow-hidden rounded-[28px] border-2 border-border bg-secondary px-3 py-4 duration-200 hover:w-40 z-[99999]">
+ <div className="border-b border-border pb-4 w-full">
+ <DialogTrigger
+ className={`flex w-full text-white brightness-75 hover:brightness-125 focus:brightness-125 cursor-pointer items-center gap-3 px-1 duration-200 justify-start`}
+ >
+ <Image
+ src={AddIcon}
+ alt="Logo"
+ width={24}
+ height={24}
+ className="hover:brightness-125 focus:brightness-125 duration-200 text-white"
+ />
+ <p className="opacity-0 duration-200 group-hover:opacity-100">
+ Add
+ </p>
+ </DialogTrigger>
+ </div>
+ {menuItems.map((item) => (
+ <Link
+ aria-disabled={item.disabled}
+ href={item.disabled ? "#" : item.url}
+ key={item.url}
+ className={`flex w-full ${
+ item.disabled
+ ? "cursor-not-allowed opacity-30"
+ : "text-white brightness-75 hover:brightness-125 cursor-pointer"
+ } items-center gap-3 px-1 duration-200 hover:scale-105 active:scale-90 justify-start`}
+ >
+ <Image
+ src={item.icon}
+ alt={`${item.text} icon`}
+ width={24}
+ height={24}
+ className="hover:brightness-125 duration-200"
+ />
+ <p className="opacity-0 duration-200 group-hover:opacity-100">
+ {item.text}
+ </p>
+ </Link>
+ ))}
+ </div>
+ </div>
- <DialogContent className="sm:max-w-[475px] text-[#F2F3F5] rounded-2xl bg-background z-[39] backdrop-blur-md">
- <form
- action={async (e: FormData) => {
- const content = e.get("content")?.toString();
+ <DialogContent className="sm:max-w-[475px] text-[#F2F3F5] rounded-2xl bg-background z-[39] backdrop-blur-md">
+ <form
+ action={async (e: FormData) => {
+ const content = e.get("content")?.toString();
- await handleSubmit(content, selectedSpaces);
- }}
- className="flex flex-col gap-4 "
- >
- <DialogHeader>
- <DialogTitle>Add memory</DialogTitle>
- <DialogDescription className="text-[#F2F3F5]">
- A "Memory" is a bookmark, something you want to remember.
- </DialogDescription>
- </DialogHeader>
+ await handleSubmit(content, selectedSpaces);
+ }}
+ className="flex flex-col gap-4 "
+ >
+ <DialogHeader>
+ <DialogTitle>Add memory</DialogTitle>
+ <DialogDescription className="text-[#F2F3F5]">
+ A "Memory" is a bookmark, something you want to remember.
+ </DialogDescription>
+ </DialogHeader>
- <div>
- <Label htmlFor="name">Resource (URL or content)</Label>
- <Textarea
- className={`bg-[#2F353C] text-[#DBDEE1] max-h-[35vh] overflow-auto focus-visible:ring-0 border-none focus-visible:ring-offset-0 mt-2 ${/^https?:\/\/\S+$/i.test(content) && "text-[#1D9BF0] underline underline-offset-2"}`}
- id="content"
- name="content"
- rows={8}
- placeholder="Start typing a note or paste a URL here. I'll remember it."
- value={content}
- onChange={(e) => setContent(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- handleSubmit(content, selectedSpaces);
- }
- }}
- />
- </div>
+ <div>
+ <Label htmlFor="name">Resource (URL or content)</Label>
+ <Textarea
+ className={`bg-[#2F353C] text-[#DBDEE1] max-h-[35vh] overflow-auto focus-visible:ring-0 border-none focus-visible:ring-offset-0 mt-2 ${/^https?:\/\/\S+$/i.test(content) && "text-[#1D9BF0] underline underline-offset-2"}`}
+ id="content"
+ name="content"
+ rows={8}
+ placeholder="Start typing a note or paste a URL here. I'll remember it."
+ value={content}
+ onChange={(e) => setContent(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ handleSubmit(content, selectedSpaces);
+ }
+ }}
+ />
+ </div>
- <div>
- <Label className="space-y-1" htmlFor="space">
- <h3 className="font-semibold text-lg tracking-tight">
- Spaces (Optional)
- </h3>
- <p className="leading-normal text-[#F2F3F5] text-sm">
- A space is a collection of memories. It's a way to organise
- your memories.
- </p>
- </Label>
+ <div>
+ <Label className="space-y-1" htmlFor="space">
+ <h3 className="font-semibold text-lg tracking-tight">
+ Spaces (Optional)
+ </h3>
+ <p className="leading-normal text-[#F2F3F5] text-sm">
+ A space is a collection of memories. It's a way to organise
+ your memories.
+ </p>
+ </Label>
- <ComboboxWithCreate
- options={spaces.map((x) => ({
- label: x.name,
- value: x.id.toString(),
- }))}
- onSelect={(v) =>
- setSelectedSpaces((prev) => {
- if (v === "") {
- return [];
- }
- return [...prev, parseInt(v)];
- })
- }
- onSubmit={async (spaceName) => {
- const space = options.find((x) => x.label === spaceName);
- toast.info("Creating space...");
+ <ComboboxWithCreate
+ options={spaces.map((x) => ({
+ label: x.name,
+ value: x.id.toString(),
+ }))}
+ onSelect={(v) =>
+ setSelectedSpaces((prev) => {
+ if (v === "") {
+ return [];
+ }
+ return [...prev, parseInt(v)];
+ })
+ }
+ onSubmit={async (spaceName) => {
+ const space = options.find((x) => x.label === spaceName);
+ toast.info("Creating space...");
- if (space) {
- toast.error("A space with that name already exists.");
- }
+ if (space) {
+ toast.error("A space with that name already exists.");
+ }
- const creationTask = await createSpace(spaceName);
- if (creationTask.success && creationTask.data) {
- toast.success("Space created " + creationTask.data);
- setSpaces((prev) => [
- ...prev,
- {
- name: spaceName,
- id: creationTask.data!,
- createdAt: new Date(),
- user: null,
- numItems: 0,
- },
- ]);
- setSelectedSpaces((prev) => [...prev, creationTask.data!]);
- } else {
- toast.error(
- "Space creation failed: " + creationTask.error ??
- "Unknown error",
- );
- }
- }}
- placeholder="Select or create a new space."
- className="bg-[#2F353C] h-min rounded-md mt-4 mb-4"
- />
+ const creationTask = await createSpace(spaceName);
+ if (creationTask.success && creationTask.data) {
+ toast.success("Space created " + creationTask.data);
+ setSpaces((prev) => [
+ ...prev,
+ {
+ name: spaceName,
+ id: creationTask.data!,
+ createdAt: new Date(),
+ user: null,
+ numItems: 0,
+ },
+ ]);
+ setSelectedSpaces((prev) => [...prev, creationTask.data!]);
+ } else {
+ toast.error(
+ "Space creation failed: " + creationTask.error ??
+ "Unknown error",
+ );
+ }
+ }}
+ placeholder="Select or create a new space."
+ className="bg-[#2F353C] h-min rounded-md mt-4 mb-4"
+ />
- <div>
- {selectedSpaces.length > 0 && (
- <div className="flex flex-row flex-wrap gap-0.5 h-min">
- {[...new Set(selectedSpaces)].map((x, idx) => (
- <button
- key={x}
- type="button"
- onClick={() =>
- setSelectedSpaces((prev) =>
- prev.filter((y) => y !== x),
- )
- }
- className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${
- idx === selectedSpaces.length - 1
- ? "rounded-br-xl"
- : ""
- }`}
- >
- <p className="line-clamp-1">
- {spaces.find((y) => y.id === x)?.name}
- </p>
- <div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
- <MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
- </div>
- </button>
- ))}
- </div>
- )}
- </div>
- </div>
+ <div>
+ {selectedSpaces.length > 0 && (
+ <div className="flex flex-row flex-wrap gap-0.5 h-min">
+ {[...new Set(selectedSpaces)].map((x, idx) => (
+ <button
+ key={x}
+ type="button"
+ onClick={() =>
+ setSelectedSpaces((prev) =>
+ prev.filter((y) => y !== x),
+ )
+ }
+ className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${
+ idx === selectedSpaces.length - 1
+ ? "rounded-br-xl"
+ : ""
+ }`}
+ >
+ <p className="line-clamp-1">
+ {spaces.find((y) => y.id === x)?.name}
+ </p>
+ <div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
+ <MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
+ </div>
+ </button>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
- <DialogFooter>
- <Button
- disabled={autoDetectedType === "none"}
- variant={"secondary"}
- type="submit"
- >
- Save {autoDetectedType != "none" && autoDetectedType}
- </Button>
- </DialogFooter>
- </form>
- </DialogContent>
+ <DialogFooter>
+ <Button
+ disabled={autoDetectedType === "none"}
+ variant={"secondary"}
+ type="submit"
+ >
+ Save {autoDetectedType != "none" && autoDetectedType}
+ </Button>
+ </DialogFooter>
+ </form>
+ </DialogContent>
- {/* Mobile Menu */}
- <div className="lg:hidden fixed bottom-0 left-0 w-full p-4 bg-secondary z-50 border-t-2 border-border">
- <div className="flex justify-around items-center">
- <Link
- href={"/"}
- className={`flex flex-col items-center text-white ${"cursor-pointer"}`}
- >
- <HomeIcon width={24} height={24} />
- <p className="text-xs text-foreground-menu mt-2">Home</p>
- </Link>
+ {/* Mobile Menu */}
+ <div className="lg:hidden fixed bottom-0 left-0 w-full p-4 bg-secondary z-50 border-t-2 border-border">
+ <div className="flex justify-around items-center">
+ <Link
+ href={"/"}
+ className={`flex flex-col items-center text-white ${"cursor-pointer"}`}
+ >
+ <HomeIcon width={24} height={24} />
+ <p className="text-xs text-foreground-menu mt-2">Home</p>
+ </Link>
- <DialogTrigger
- className={`flex flex-col items-center cursor-pointer text-white`}
- >
- <Image
- src={AddIcon}
- alt="Logo"
- width={24}
- height={24}
- className="hover:brightness-125 focus:brightness-125 duration-200 stroke-white"
- />
- <p className="text-xs text-foreground-menu mt-2">Add</p>
- </DialogTrigger>
- {menuItems.map((item) => (
- <Link
- aria-disabled={item.disabled}
- href={item.disabled ? "#" : item.url}
- key={item.url}
- className={`flex flex-col items-center ${
- item.disabled
- ? "opacity-50 cursor-not-allowed"
- : "cursor-pointer"
- }`}
- onClick={(e) => item.disabled && e.preventDefault()}
- >
- <Image
- src={item.icon}
- alt={`${item.text} icon`}
- width={24}
- height={24}
- />
- <p className="text-xs text-foreground-menu mt-2">{item.text}</p>
- </Link>
- ))}
- </div>
- </div>
- </Dialog>
- </>
- );
+ <DialogTrigger
+ className={`flex flex-col items-center cursor-pointer text-white`}
+ >
+ <Image
+ src={AddIcon}
+ alt="Logo"
+ width={24}
+ height={24}
+ className="hover:brightness-125 focus:brightness-125 duration-200 stroke-white"
+ />
+ <p className="text-xs text-foreground-menu mt-2">Add</p>
+ </DialogTrigger>
+ {menuItems.map((item) => (
+ <Link
+ aria-disabled={item.disabled}
+ href={item.disabled ? "#" : item.url}
+ key={item.url}
+ className={`flex flex-col items-center ${
+ item.disabled
+ ? "opacity-50 cursor-not-allowed"
+ : "cursor-pointer"
+ }`}
+ onClick={(e) => item.disabled && e.preventDefault()}
+ >
+ <Image
+ src={item.icon}
+ alt={`${item.text} icon`}
+ width={24}
+ height={24}
+ />
+ <p className="text-xs text-foreground-menu mt-2">{item.text}</p>
+ </Link>
+ ))}
+ </div>
+ </div>
+ </Dialog>
+ </>
+ );
}
export default Menu;
diff --git a/apps/web/app/(dash)/note/[noteid]/page.tsx b/apps/web/app/(dash)/note/[noteid]/page.tsx
index 76fed275..40fe6a9d 100644
--- a/apps/web/app/(dash)/note/[noteid]/page.tsx
+++ b/apps/web/app/(dash)/note/[noteid]/page.tsx
@@ -2,23 +2,23 @@ import { getNoteFromId } from "@/app/actions/fetchers";
import { NotebookIcon } from "lucide-react";
async function Page({ params }: { params: { noteid: string } }) {
- const note = await getNoteFromId(params.noteid as string);
+ const note = await getNoteFromId(params.noteid as string);
- if (!note.success) {
- return <div>Failed to load note</div>;
- }
+ if (!note.success) {
+ return <div>Failed to load note</div>;
+ }
- return (
- <div className="max-w-3xl mt-16 md:mt-32 flex mx-auto w-full flex-col">
- <div className="flex items-center gap-2 text-xs">
- <NotebookIcon className="w-3 h-3" /> Note
- </div>
- <h1 className="text-white w-full font-medium text-2xl text-left mt-2">
- {note.data?.title}
- </h1>
- <div className="w-full pb-20 mt-12">{note.data?.content}</div>
- </div>
- );
+ return (
+ <div className="max-w-3xl mt-16 md:mt-32 flex mx-auto w-full flex-col">
+ <div className="flex items-center gap-2 text-xs">
+ <NotebookIcon className="w-3 h-3" /> Note
+ </div>
+ <h1 className="text-white w-full font-medium text-2xl text-left mt-2">
+ {note.data?.title}
+ </h1>
+ <div className="w-full pb-20 mt-12">{note.data?.content}</div>
+ </div>
+ );
}
export default Page;
diff --git a/apps/web/app/(landing)/CardPatterns/AnimatedBeam.tsx b/apps/web/app/(landing)/CardPatterns/AnimatedBeam.tsx
index 59e1e002..647672f8 100644
--- a/apps/web/app/(landing)/CardPatterns/AnimatedBeam.tsx
+++ b/apps/web/app/(landing)/CardPatterns/AnimatedBeam.tsx
@@ -5,182 +5,183 @@ import { motion } from "framer-motion";
import { RefObject, useEffect, useId, useState } from "react";
export interface AnimatedBeamProps {
- className?: string;
- containerRef: RefObject<HTMLElement>; // Container ref
- fromRef: RefObject<HTMLElement>;
- toRef: RefObject<HTMLElement>;
- curvature?: number;
- reverse?: boolean;
- pathColor?: string;
- pathWidth?: number;
- pathOpacity?: number;
- gradientStartColor?: string;
- gradientStopColor?: string;
- delay?: number;
- duration?: number;
- startXOffset?: number;
- startYOffset?: number;
- endXOffset?: number;
- endYOffset?: number;
+ className?: string;
+ containerRef: RefObject<HTMLElement>; // Container ref
+ fromRef: RefObject<HTMLElement>;
+ toRef: RefObject<HTMLElement>;
+ curvature?: number;
+ reverse?: boolean;
+ pathColor?: string;
+ pathWidth?: number;
+ pathOpacity?: number;
+ gradientStartColor?: string;
+ gradientStopColor?: string;
+ delay?: number;
+ duration?: number;
+ startXOffset?: number;
+ startYOffset?: number;
+ endXOffset?: number;
+ endYOffset?: number;
}
export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
- className,
- containerRef,
- fromRef,
- toRef,
- curvature = 0,
- reverse = false, // Include the reverse prop
- duration = Math.random() * 3 + 4,
- delay = 0,
- pathColor = "gray",
- pathWidth = 2,
- pathOpacity = 0.2,
- gradientStartColor = "#ffaa40",
- gradientStopColor = "#9c40ff",
- startXOffset = 0,
- startYOffset = 0,
- endXOffset = 0,
- endYOffset = 0,
+ className,
+ containerRef,
+ fromRef,
+ toRef,
+ curvature = 0,
+ reverse = false, // Include the reverse prop
+ duration = Math.random() * 3 + 4,
+ delay = 0,
+ pathColor = "gray",
+ pathWidth = 2,
+ pathOpacity = 0.2,
+ gradientStartColor = "#ffaa40",
+ gradientStopColor = "#9c40ff",
+ startXOffset = 0,
+ startYOffset = 0,
+ endXOffset = 0,
+ endYOffset = 0,
}) => {
- const id = useId();
- const [pathD, setPathD] = useState("");
- const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
+ const id = useId();
+ const [pathD, setPathD] = useState("");
+ const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
- // Calculate the gradient coordinates based on the reverse prop
- const gradientCoordinates = reverse
- ? {
- x1: ["90%", "-10%"],
- x2: ["100%", "0%"],
- y1: ["0%", "0%"],
- y2: ["0%", "0%"],
- }
- : {
- x1: ["10%", "110%"],
- x2: ["0%", "100%"],
- y1: ["0%", "0%"],
- y2: ["0%", "0%"],
- };
+ // Calculate the gradient coordinates based on the reverse prop
+ const gradientCoordinates = reverse
+ ? {
+ x1: ["90%", "-10%"],
+ x2: ["100%", "0%"],
+ y1: ["0%", "0%"],
+ y2: ["0%", "0%"],
+ }
+ : {
+ x1: ["10%", "110%"],
+ x2: ["0%", "100%"],
+ y1: ["0%", "0%"],
+ y2: ["0%", "0%"],
+ };
- useEffect(() => {
- const updatePath = () => {
- if (containerRef.current && fromRef.current && toRef.current) {
- const containerRect = containerRef.current.getBoundingClientRect();
- const rectA = fromRef.current.getBoundingClientRect();
- const rectB = toRef.current.getBoundingClientRect();
+ useEffect(() => {
+ const updatePath = () => {
+ if (containerRef.current && fromRef.current && toRef.current) {
+ const containerRect = containerRef.current.getBoundingClientRect();
+ const rectA = fromRef.current.getBoundingClientRect();
+ const rectB = toRef.current.getBoundingClientRect();
- const svgWidth = containerRect.width;
- const svgHeight = containerRect.height;
- setSvgDimensions({ width: svgWidth, height: svgHeight });
+ const svgWidth = containerRect.width;
+ const svgHeight = containerRect.height;
+ setSvgDimensions({ width: svgWidth, height: svgHeight });
- const startX =
- rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
- const startY =
- rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
- const endX =
- rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
- const endY =
- rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
+ const startX =
+ rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
+ const startY =
+ rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
+ const endX =
+ rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
+ const endY =
+ rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
- const controlY = startY - curvature;
- const d = `M ${startX},${startY} Q ${(startX + endX) / 2
- },${controlY} ${endX},${endY}`;
- setPathD(d);
- }
- };
+ const controlY = startY - curvature;
+ const d = `M ${startX},${startY} Q ${
+ (startX + endX) / 2
+ },${controlY} ${endX},${endY}`;
+ setPathD(d);
+ }
+ };
- // Initialize ResizeObserver
- const resizeObserver = new ResizeObserver((entries) => {
- // For all entries, recalculate the path
- for (let entry of entries) {
- updatePath();
- }
- });
+ // Initialize ResizeObserver
+ const resizeObserver = new ResizeObserver((entries) => {
+ // For all entries, recalculate the path
+ for (let entry of entries) {
+ updatePath();
+ }
+ });
- // Observe the container element
- if (containerRef.current) {
- resizeObserver.observe(containerRef.current);
- }
+ // Observe the container element
+ if (containerRef.current) {
+ resizeObserver.observe(containerRef.current);
+ }
- // Call the updatePath initially to set the initial path
- updatePath();
+ // Call the updatePath initially to set the initial path
+ updatePath();
- // Clean up the observer on component unmount
- return () => {
- resizeObserver.disconnect();
- };
- }, [
- containerRef,
- fromRef,
- toRef,
- curvature,
- startXOffset,
- startYOffset,
- endXOffset,
- endYOffset,
- ]);
+ // Clean up the observer on component unmount
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, [
+ containerRef,
+ fromRef,
+ toRef,
+ curvature,
+ startXOffset,
+ startYOffset,
+ endXOffset,
+ endYOffset,
+ ]);
- return (
- <svg
- fill="none"
- width={svgDimensions.width}
- height={svgDimensions.height}
- xmlns="http://www.w3.org/2000/svg"
- className={cn(
- "pointer-events-none absolute left-0 top-0 transform-gpu stroke-2",
- className,
- )}
- viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
- >
- <path
- d={pathD}
- stroke={pathColor}
- strokeWidth={pathWidth}
- strokeOpacity={pathOpacity}
- strokeLinecap="round"
- />
- <path
- d={pathD}
- strokeWidth={pathWidth}
- stroke={`url(#${id})`}
- strokeOpacity="1"
- strokeLinecap="round"
- />
- <defs>
- <motion.linearGradient
- className="transform-gpu"
- id={id}
- gradientUnits={"userSpaceOnUse"}
- initial={{
- x1: "0%",
- x2: "0%",
- y1: "0%",
- y2: "0%",
- }}
- animate={{
- x1: gradientCoordinates.x1,
- x2: gradientCoordinates.x2,
- y1: gradientCoordinates.y1,
- y2: gradientCoordinates.y2,
- }}
- transition={{
- delay,
- duration,
- ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
- repeat: Infinity,
- repeatDelay: 0,
- }}
- >
- <stop stopColor={gradientStartColor} stopOpacity="0"></stop>
- <stop stopColor={gradientStartColor}></stop>
- <stop offset="32.5%" stopColor={gradientStopColor}></stop>
- <stop
- offset="100%"
- stopColor={gradientStopColor}
- stopOpacity="0"
- ></stop>
- </motion.linearGradient>
- </defs>
- </svg>
- );
+ return (
+ <svg
+ fill="none"
+ width={svgDimensions.width}
+ height={svgDimensions.height}
+ xmlns="http://www.w3.org/2000/svg"
+ className={cn(
+ "pointer-events-none absolute left-0 top-0 transform-gpu stroke-2",
+ className,
+ )}
+ viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
+ >
+ <path
+ d={pathD}
+ stroke={pathColor}
+ strokeWidth={pathWidth}
+ strokeOpacity={pathOpacity}
+ strokeLinecap="round"
+ />
+ <path
+ d={pathD}
+ strokeWidth={pathWidth}
+ stroke={`url(#${id})`}
+ strokeOpacity="1"
+ strokeLinecap="round"
+ />
+ <defs>
+ <motion.linearGradient
+ className="transform-gpu"
+ id={id}
+ gradientUnits={"userSpaceOnUse"}
+ initial={{
+ x1: "0%",
+ x2: "0%",
+ y1: "0%",
+ y2: "0%",
+ }}
+ animate={{
+ x1: gradientCoordinates.x1,
+ x2: gradientCoordinates.x2,
+ y1: gradientCoordinates.y1,
+ y2: gradientCoordinates.y2,
+ }}
+ transition={{
+ delay,
+ duration,
+ ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
+ repeat: Infinity,
+ repeatDelay: 0,
+ }}
+ >
+ <stop stopColor={gradientStartColor} stopOpacity="0"></stop>
+ <stop stopColor={gradientStartColor}></stop>
+ <stop offset="32.5%" stopColor={gradientStopColor}></stop>
+ <stop
+ offset="100%"
+ stopColor={gradientStopColor}
+ stopOpacity="0"
+ ></stop>
+ </motion.linearGradient>
+ </defs>
+ </svg>
+ );
};
diff --git a/apps/web/app/(landing)/CardPatterns/AnimatedBeamWithOutput.tsx b/apps/web/app/(landing)/CardPatterns/AnimatedBeamWithOutput.tsx
index 94774ec2..cb5935ee 100644
--- a/apps/web/app/(landing)/CardPatterns/AnimatedBeamWithOutput.tsx
+++ b/apps/web/app/(landing)/CardPatterns/AnimatedBeamWithOutput.tsx
@@ -5,490 +5,497 @@ import React, { forwardRef, useRef } from "react";
import Logo from "../../../public/logo.svg";
import Image from "next/image";
const Circle = forwardRef<
- HTMLDivElement,
- { className?: string; children?: React.ReactNode }
+ HTMLDivElement,
+ { className?: string; children?: React.ReactNode }
>(({ className, children }, ref) => {
- return (
- <div
- ref={ref}
- className={cn(
- "z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]",
- className,
- )}
- >
- {children}
- </div>
- );
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ "z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]",
+ className,
+ )}
+ >
+ {children}
+ </div>
+ );
});
export function AnimatedBeamShow() {
- const containerRef = useRef<HTMLDivElement>(null);
- const div1Ref = useRef<HTMLDivElement>(null);
- const div2Ref = useRef<HTMLDivElement>(null);
- const div3Ref = useRef<HTMLDivElement>(null);
- const div4Ref = useRef<HTMLDivElement>(null);
- const div5Ref = useRef<HTMLDivElement>(null);
- const div6Ref = useRef<HTMLDivElement>(null);
- const div7Ref = useRef<HTMLDivElement>(null);
+ const containerRef = useRef<HTMLDivElement>(null);
+ const div1Ref = useRef<HTMLDivElement>(null);
+ const div2Ref = useRef<HTMLDivElement>(null);
+ const div3Ref = useRef<HTMLDivElement>(null);
+ const div4Ref = useRef<HTMLDivElement>(null);
+ const div5Ref = useRef<HTMLDivElement>(null);
+ const div6Ref = useRef<HTMLDivElement>(null);
+ const div7Ref = useRef<HTMLDivElement>(null);
- return (
- <div
- className="flex overflow-hidden relative justify-center items-center p-10 px-10 w-full max-w-full bg-transparent"
- ref={containerRef}
- >
- <div className="flex flex-col gap-10 justify-between items-stretch w-full h-full">
- <div className="flex flex-row justify-between items-center">
- <Circle ref={div1Ref}>
- <Icons.googleDrive />
- </Circle>
- <Circle ref={div5Ref}>
- <Icons.googleDocs />
- </Circle>
- </div>
- <div className="flex flex-row justify-between items-center">
- <Circle ref={div2Ref}>
- <Icons.notion />
- </Circle>
- <Circle ref={div4Ref} className="w-16 h-16 bg-page-gradient bg-slate-900 border border-white/20 [box-shadow:0_-20px_80px_-20px_#8686f01f_inset]">
- <Image src={Logo} className="text-black w-full h-full" alt="logo image" />
- </Circle>
- <Circle ref={div6Ref}>
- <Icons.zapier />
- </Circle>
- </div>
- <div className="flex flex-row justify-between items-center">
- <Circle ref={div3Ref}>
- <Icons.whatsapp />
- </Circle>
- <Circle ref={div7Ref}>
- <Icons.messenger />
- </Circle>
- </div>
- </div>
+ return (
+ <div
+ className="flex overflow-hidden relative justify-center items-center p-10 px-10 w-full max-w-full bg-transparent"
+ ref={containerRef}
+ >
+ <div className="flex flex-col gap-10 justify-between items-stretch w-full h-full">
+ <div className="flex flex-row justify-between items-center">
+ <Circle ref={div1Ref}>
+ <Icons.googleDrive />
+ </Circle>
+ <Circle ref={div5Ref}>
+ <Icons.googleDocs />
+ </Circle>
+ </div>
+ <div className="flex flex-row justify-between items-center">
+ <Circle ref={div2Ref}>
+ <Icons.notion />
+ </Circle>
+ <Circle
+ ref={div4Ref}
+ className="w-16 h-16 bg-page-gradient bg-slate-900 border border-white/20 [box-shadow:0_-20px_80px_-20px_#8686f01f_inset]"
+ >
+ <Image
+ src={Logo}
+ className="text-black w-full h-full"
+ alt="logo image"
+ />
+ </Circle>
+ <Circle ref={div6Ref}>
+ <Icons.zapier />
+ </Circle>
+ </div>
+ <div className="flex flex-row justify-between items-center">
+ <Circle ref={div3Ref}>
+ <Icons.whatsapp />
+ </Circle>
+ <Circle ref={div7Ref}>
+ <Icons.messenger />
+ </Circle>
+ </div>
+ </div>
- <AnimatedBeam
- containerRef={containerRef}
- fromRef={div1Ref}
- toRef={div4Ref}
- curvature={-75}
- endYOffset={-10}
- />
- <AnimatedBeam
- containerRef={containerRef}
- fromRef={div2Ref}
- toRef={div4Ref}
- />
- <AnimatedBeam
- containerRef={containerRef}
- fromRef={div3Ref}
- toRef={div4Ref}
- curvature={75}
- endYOffset={10}
- />
- <AnimatedBeam
- containerRef={containerRef}
- fromRef={div5Ref}
- toRef={div4Ref}
- curvature={-75}
- endYOffset={-10}
- reverse
- />
- <AnimatedBeam
- containerRef={containerRef}
- fromRef={div6Ref}
- toRef={div4Ref}
- reverse
- />
- <AnimatedBeam
- containerRef={containerRef}
- fromRef={div7Ref}
- toRef={div4Ref}
- curvature={75}
- endYOffset={10}
- reverse
- />
- </div>
- );
+ <AnimatedBeam
+ containerRef={containerRef}
+ fromRef={div1Ref}
+ toRef={div4Ref}
+ curvature={-75}
+ endYOffset={-10}
+ />
+ <AnimatedBeam
+ containerRef={containerRef}
+ fromRef={div2Ref}
+ toRef={div4Ref}
+ />
+ <AnimatedBeam
+ containerRef={containerRef}
+ fromRef={div3Ref}
+ toRef={div4Ref}
+ curvature={75}
+ endYOffset={10}
+ />
+ <AnimatedBeam
+ containerRef={containerRef}
+ fromRef={div5Ref}
+ toRef={div4Ref}
+ curvature={-75}
+ endYOffset={-10}
+ reverse
+ />
+ <AnimatedBeam
+ containerRef={containerRef}
+ fromRef={div6Ref}
+ toRef={div4Ref}
+ reverse
+ />
+ <AnimatedBeam
+ containerRef={containerRef}
+ fromRef={div7Ref}
+ toRef={div4Ref}
+ curvature={75}
+ endYOffset={10}
+ reverse
+ />
+ </div>
+ );
}
const Icons = {
- notion: () => (
- <svg
- width="100"
- height="100"
- viewBox="0 0 100 100"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z"
- fill="#ffffff"
- />
- <path
- d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z"
- fill="#000000"
- fillRule="evenodd"
- clipRule="evenodd"
- />
- </svg>
- ),
- openai: () => (
- <svg
- width="100"
- height="100"
- viewBox="0 0 24 24"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
- </svg>
- ),
- googleDrive: () => (
- <svg
- width="100"
- height="100"
- viewBox="0 0 87.3 78"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z"
- fill="#0066da"
- />
- <path
- d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z"
- fill="#00ac47"
- />
- <path
- d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z"
- fill="#ea4335"
- />
- <path
- d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z"
- fill="#00832d"
- />
- <path
- d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z"
- fill="#2684fc"
- />
- <path
- d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z"
- fill="#ffba00"
- />
- </svg>
- ),
- whatsapp: () => (
- <svg
- width="100"
- height="100"
- viewBox="0 0 175.216 175.552"
- xmlns="http://www.w3.org/2000/svg"
- >
- <defs>
- <linearGradient
- id="b"
- x1="85.915"
- x2="86.535"
- y1="32.567"
- y2="137.092"
- gradientUnits="userSpaceOnUse"
- >
- <stop offset="0" stopColor="#57d163" />
- <stop offset="1" stopColor="#23b33a" />
- </linearGradient>
- <filter
- id="a"
- width="1.115"
- height="1.114"
- x="-.057"
- y="-.057"
- colorInterpolationFilters="sRGB"
- >
- <feGaussianBlur stdDeviation="3.531" />
- </filter>
- </defs>
- <path
- d="m54.532 138.45 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.523h.023c33.707 0 61.139-27.426 61.153-61.135.006-16.335-6.349-31.696-17.895-43.251A60.75 60.75 0 0 0 87.94 25.983c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.558zm-40.811 23.544L24.16 123.88c-6.438-11.154-9.825-23.808-9.821-36.772.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954zm0 0"
- fill="#b3b3b3"
- filter="url(#a)"
- />
- <path
- d="m12.966 161.238 10.439-38.114a73.42 73.42 0 0 1-9.821-36.772c.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954z"
- fill="#ffffff"
- />
- <path
- d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.559 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.524h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.929z"
- fill="url(#linearGradient1780)"
- />
- <path
- d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.313-6.179 22.558 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.517 31.126 8.523h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.928z"
- fill="url(#b)"
- />
- <path
- d="M68.772 55.603c-1.378-3.061-2.828-3.123-4.137-3.176l-3.524-.043c-1.226 0-3.218.46-4.902 2.3s-6.435 6.287-6.435 15.332 6.588 17.785 7.506 19.013 12.718 20.381 31.405 27.75c15.529 6.124 18.689 4.906 22.061 4.6s10.877-4.447 12.408-8.74 1.532-7.971 1.073-8.74-1.685-1.226-3.525-2.146-10.877-5.367-12.562-5.981-2.91-.919-4.137.921-4.746 5.979-5.819 7.206-2.144 1.381-3.984.462-7.76-2.861-14.784-9.124c-5.465-4.873-9.154-10.891-10.228-12.73s-.114-2.835.808-3.751c.825-.824 1.838-2.147 2.759-3.22s1.224-1.84 1.836-3.065.307-2.301-.153-3.22-4.032-10.011-5.666-13.647"
- fill="#ffffff"
- fillRule="evenodd"
- />
- </svg>
- ),
- googleDocs: () => (
- <svg
- width="47px"
- height="65px"
- viewBox="0 0 47 65"
- xmlns="http://www.w3.org/2000/svg"
- >
- <defs>
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-1"
- />
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-3"
- />
- <linearGradient
- x1="50.0053945%"
- y1="8.58610612%"
- x2="50.0053945%"
- y2="100.013939%"
- id="linearGradient-5"
- >
- <stop stopColor="#1A237E" stopOpacity="0.2" offset="0%" />
- <stop stopColor="#1A237E" stopOpacity="0.02" offset="100%" />
- </linearGradient>
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-6"
- />
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-8"
- />
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-10"
- />
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-12"
- />
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="path-14"
- />
- <radialGradient
- cx="3.16804688%"
- cy="2.71744318%"
- fx="3.16804688%"
- fy="2.71744318%"
- r="161.248516%"
- gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.723077),translate(-0.031680,-0.027174)"
- id="radialGradient-16"
- >
- <stop stopColor="#FFFFFF" stopOpacity="0.1" offset="0%" />
- <stop stopColor="#FFFFFF" stopOpacity="0" offset="100%" />
- </radialGradient>
- </defs>
- <g
- id="Page-1"
- stroke="none"
- strokeWidth="1"
- fill="none"
- fillRule="evenodd"
- >
- <g transform="translate(-451.000000, -463.000000)">
- <g id="Hero" transform="translate(0.000000, 63.000000)">
- <g id="Personal" transform="translate(277.000000, 309.000000)">
- <g id="Docs-icon" transform="translate(174.000000, 91.000000)">
- <g id="Group">
- <g id="Clipped">
- <mask id="mask-2" fill="white">
- <use xlinkHref="#path-1" />
- </mask>
- <g id="SVGID_1_" />
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L36.71875,10.3409091 L29.375,0 Z"
- id="Path"
- fill="#4285F4"
- fillRule="nonzero"
- mask="url(#mask-2)"
- />
- </g>
- <g id="Clipped">
- <mask id="mask-4" fill="white">
- <use xlinkHref="#path-3" />
- </mask>
- <g id="SVGID_1_" />
- <polygon
- id="Path"
- fill="url(#linearGradient-5)"
- fillRule="nonzero"
- mask="url(#mask-4)"
- points="30.6638281 16.4309659 47 32.8582386 47 17.7272727"
- ></polygon>
- </g>
- <g id="Clipped">
- <mask id="mask-7" fill="white">
- <use xlinkHref="#path-6" />
- </mask>
- <g id="SVGID_1_" />
- <path
- d="M11.75,47.2727273 L35.25,47.2727273 L35.25,44.3181818 L11.75,44.3181818 L11.75,47.2727273 Z M11.75,53.1818182 L29.375,53.1818182 L29.375,50.2272727 L11.75,50.2272727 L11.75,53.1818182 Z M11.75,32.5 L11.75,35.4545455 L35.25,35.4545455 L35.25,32.5 L11.75,32.5 Z M11.75,41.3636364 L35.25,41.3636364 L35.25,38.4090909 L11.75,38.4090909 L11.75,41.3636364 Z"
- id="Shape"
- fill="#F1F1F1"
- fillRule="nonzero"
- mask="url(#mask-7)"
- />
- </g>
- <g id="Clipped">
- <mask id="mask-9" fill="white">
- <use xlinkHref="#path-8" />
- </mask>
- <g id="SVGID_1_" />
- <g id="Group" mask="url(#mask-9)">
- <g transform="translate(26.437500, -2.954545)">
- <path
- d="M2.9375,2.95454545 L2.9375,16.25 C2.9375,18.6985795 4.90929688,20.6818182 7.34375,20.6818182 L20.5625,20.6818182 L2.9375,2.95454545 Z"
- id="Path"
- fill="#A1C2FA"
- fillRule="nonzero"
- />
- </g>
- </g>
- </g>
- <g id="Clipped">
- <mask id="mask-11" fill="white">
- <use xlinkHref="#path-10" />
- </mask>
- <g id="SVGID_1_" />
- <path
- d="M4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,4.80113636 C0,2.36363636 1.9828125,0.369318182 4.40625,0.369318182 L29.375,0.369318182 L29.375,0 L4.40625,0 Z"
- id="Path"
- fillOpacity="0.2"
- fill="#FFFFFF"
- fillRule="nonzero"
- mask="url(#mask-11)"
- />
- </g>
- <g id="Clipped">
- <mask id="mask-13" fill="white">
- <use xlinkHref="#path-12" />
- </mask>
- <g id="SVGID_1_" />
- <path
- d="M42.59375,64.6306818 L4.40625,64.6306818 C1.9828125,64.6306818 0,62.6363636 0,60.1988636 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,60.1988636 C47,62.6363636 45.0171875,64.6306818 42.59375,64.6306818 Z"
- id="Path"
- fillOpacity="0.2"
- fill="#1A237E"
- fillRule="nonzero"
- mask="url(#mask-13)"
- />
- </g>
- <g id="Clipped">
- <mask id="mask-15" fill="white">
- <use xlinkHref="#path-14" />
- </mask>
- <g id="SVGID_1_" />
- <path
- d="M33.78125,17.7272727 C31.3467969,17.7272727 29.375,15.7440341 29.375,13.2954545 L29.375,13.6647727 C29.375,16.1133523 31.3467969,18.0965909 33.78125,18.0965909 L47,18.0965909 L47,17.7272727 L33.78125,17.7272727 Z"
- id="Path"
- fillOpacity="0.1"
- fill="#1A237E"
- fillRule="nonzero"
- mask="url(#mask-15)"
- />
- </g>
- </g>
- <path
- d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
- id="Path"
- fill="url(#radialGradient-16)"
- fillRule="nonzero"
- />
- </g>
- </g>
- </g>
- </g>
- </g>
- </svg>
- ),
- zapier: () => (
- <svg
- width="105"
- height="28"
- viewBox="0 0 244 66"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M57.1877 45.2253L57.1534 45.1166L78.809 25.2914V15.7391H44.0663V25.2914H64.8181L64.8524 25.3829L43.4084 45.2253V54.7775H79.1579V45.2253H57.1877Z"
- fill="#201515"
- />
- <path
- d="M100.487 14.8297C96.4797 14.8297 93.2136 15.434 90.6892 16.6429C88.3376 17.6963 86.3568 19.4321 85.0036 21.6249C83.7091 23.8321 82.8962 26.2883 82.6184 28.832L93.1602 30.3135C93.5415 28.0674 94.3042 26.4754 95.4482 25.5373C96.7486 24.5562 98.3511 24.0605 99.9783 24.136C102.118 24.136 103.67 24.7079 104.634 25.8519C105.59 26.9959 106.076 28.5803 106.076 30.6681V31.7091H95.9401C90.7807 31.7091 87.0742 32.8531 84.8206 35.1411C82.5669 37.429 81.442 40.4492 81.4458 44.2014C81.4458 48.0452 82.5707 50.9052 84.8206 52.7813C87.0704 54.6574 89.8999 55.5897 93.3089 55.5783C97.5379 55.5783 100.791 54.1235 103.067 51.214C104.412 49.426 105.372 47.3793 105.887 45.2024H106.27L107.723 54.7546H117.275V30.5651C117.275 25.5659 115.958 21.6936 113.323 18.948C110.688 16.2024 106.409 14.8297 100.487 14.8297ZM103.828 44.6475C102.312 45.9116 100.327 46.5408 97.8562 46.5408C95.8199 46.5408 94.4052 46.1843 93.6121 45.4712C93.2256 45.1338 92.9182 44.7155 92.7116 44.246C92.505 43.7764 92.4043 43.2671 92.4166 42.7543C92.3941 42.2706 92.4702 41.7874 92.6403 41.3341C92.8104 40.8808 93.071 40.4668 93.4062 40.1174C93.7687 39.7774 94.1964 39.5145 94.6633 39.3444C95.1303 39.1743 95.6269 39.1006 96.1231 39.1278H106.093V39.7856C106.113 40.7154 105.919 41.6374 105.527 42.4804C105.134 43.3234 104.553 44.0649 103.828 44.6475Z"
- fill="#201515"
- />
- <path
- d="M175.035 15.7391H163.75V54.7833H175.035V15.7391Z"
- fill="#201515"
- />
- <path
- d="M241.666 15.7391C238.478 15.7391 235.965 16.864 234.127 19.1139C232.808 20.7307 231.805 23.1197 231.119 26.2809H230.787L229.311 15.7391H219.673V54.7775H230.959V34.7578C230.959 32.2335 231.55 30.2982 232.732 28.9521C233.914 27.606 236.095 26.933 239.275 26.933H243.559V15.7391H241.666Z"
- fill="#201515"
- />
- <path
- d="M208.473 17.0147C205.839 15.4474 202.515 14.6657 198.504 14.6695C192.189 14.6695 187.247 16.4675 183.678 20.0634C180.108 23.6593 178.324 28.6166 178.324 34.9352C178.233 38.7553 179.067 42.5407 180.755 45.9689C182.3 49.0238 184.706 51.5592 187.676 53.2618C190.665 54.9892 194.221 55.8548 198.344 55.8586C201.909 55.8586 204.887 55.3095 207.278 54.2113C209.526 53.225 211.483 51.6791 212.964 49.7211C214.373 47.7991 215.42 45.6359 216.052 43.3377L206.329 40.615C205.919 42.1094 205.131 43.4728 204.041 44.5732C202.942 45.6714 201.102 46.2206 198.521 46.2206C195.451 46.2206 193.163 45.3416 191.657 43.5837C190.564 42.3139 189.878 40.5006 189.575 38.1498H216.201C216.31 37.0515 216.367 36.1306 216.367 35.387V32.9561C216.431 29.6903 215.757 26.4522 214.394 23.4839C213.118 20.7799 211.054 18.5248 208.473 17.0147ZM198.178 23.9758C202.754 23.9758 205.348 26.2275 205.962 30.731H189.775C190.032 29.2284 190.655 27.8121 191.588 26.607C193.072 24.8491 195.268 23.972 198.178 23.9758Z"
- fill="#201515"
- />
- <path
- d="M169.515 0.00366253C168.666 -0.0252113 167.82 0.116874 167.027 0.421484C166.234 0.726094 165.511 1.187 164.899 1.77682C164.297 2.3723 163.824 3.08658 163.512 3.87431C163.2 4.66204 163.055 5.50601 163.086 6.35275C163.056 7.20497 163.201 8.05433 163.514 8.84781C163.826 9.64129 164.299 10.3619 164.902 10.9646C165.505 11.5673 166.226 12.0392 167.02 12.3509C167.814 12.6626 168.663 12.8074 169.515 12.7762C170.362 12.8082 171.206 12.6635 171.994 12.3514C172.782 12.0392 173.496 11.5664 174.091 10.963C174.682 10.3534 175.142 9.63077 175.446 8.83849C175.75 8.04621 175.89 7.20067 175.859 6.35275C175.898 5.50985 175.761 4.66806 175.456 3.88115C175.151 3.09424 174.686 2.37951 174.09 1.78258C173.493 1.18565 172.779 0.719644 171.992 0.414327C171.206 0.109011 170.364 -0.0288946 169.521 0.00938803L169.515 0.00366253Z"
- fill="#201515"
- />
- <path
- d="M146.201 14.6695C142.357 14.6695 139.268 15.8764 136.935 18.2902C135.207 20.0786 133.939 22.7479 133.131 26.2981H132.771L131.295 15.7563H121.657V66H132.942V45.3054H133.354C133.698 46.6852 134.181 48.0267 134.795 49.3093C135.75 51.3986 137.316 53.1496 139.286 54.3314C141.328 55.446 143.629 56.0005 145.955 55.9387C150.68 55.9387 154.277 54.0988 156.748 50.419C159.219 46.7392 160.455 41.6046 160.455 35.0153C160.455 28.6509 159.259 23.6689 156.869 20.0691C154.478 16.4694 150.922 14.6695 146.201 14.6695ZM147.345 42.9602C146.029 44.8668 143.97 45.8201 141.167 45.8201C140.012 45.8735 138.86 45.6507 137.808 45.1703C136.755 44.6898 135.832 43.9656 135.116 43.0574C133.655 41.2233 132.927 38.7122 132.931 35.5243V34.7807C132.931 31.5432 133.659 29.0646 135.116 27.3448C136.572 25.625 138.59 24.7747 141.167 24.7937C144.02 24.7937 146.092 25.6994 147.385 27.5107C148.678 29.322 149.324 31.8483 149.324 35.0896C149.332 38.4414 148.676 41.065 147.356 42.9602H147.345Z"
- fill="#201515"
- />
- <path d="M39.0441 45.2253H0V54.789H39.0441V45.2253Z" fill="#FF4F00" />
- </svg>
- ),
- messenger: () => (
- <svg
- width="100"
- height="100"
- viewBox="0 0 48 48"
- xmlns="http://www.w3.org/2000/svg"
- >
- <radialGradient
- id="8O3wK6b5ASW2Wn6hRCB5xa_YFbzdUk7Q3F8_gr1"
- cx="11.087"
- cy="7.022"
- r="47.612"
- gradientTransform="matrix(1 0 0 -1 0 50)"
- gradientUnits="userSpaceOnUse"
- >
- <stop offset="0" stopColor="#1292ff"></stop>
- <stop offset=".079" stopColor="#2982ff"></stop>
- <stop offset=".23" stopColor="#4e69ff"></stop>
- <stop offset=".351" stopColor="#6559ff"></stop>
- <stop offset=".428" stopColor="#6d53ff"></stop>
- <stop offset=".754" stopColor="#df47aa"></stop>
- <stop offset=".946" stopColor="#ff6257"></stop>
- </radialGradient>
- <path
- fill="url(#8O3wK6b5ASW2Wn6hRCB5xa_YFbzdUk7Q3F8_gr1)"
- d="M44,23.5C44,34.27,35.05,43,24,43c-1.651,0-3.25-0.194-4.784-0.564 c-0.465-0.112-0.951-0.069-1.379,0.145L13.46,44.77C12.33,45.335,11,44.513,11,43.249v-4.025c0-0.575-0.257-1.111-0.681-1.499 C6.425,34.165,4,29.11,4,23.5C4,12.73,12.95,4,24,4S44,12.73,44,23.5z"
- />
- <path
- d="M34.992,17.292c-0.428,0-0.843,0.142-1.2,0.411l-5.694,4.215 c-0.133,0.1-0.28,0.15-0.435,0.15c-0.15,0-0.291-0.047-0.41-0.136l-3.972-2.99c-0.808-0.601-1.76-0.918-2.757-0.918 c-1.576,0-3.025,0.791-3.876,2.116l-1.211,1.891l-4.12,6.695c-0.392,0.614-0.422,1.372-0.071,2.014 c0.358,0.654,1.034,1.06,1.764,1.06c0.428,0,0.843-0.142,1.2-0.411l5.694-4.215c0.133-0.1,0.28-0.15,0.435-0.15 c0.15,0,0.291,0.047,0.41,0.136l3.972,2.99c0.809,0.602,1.76,0.918,2.757,0.918c1.576,0,3.025-0.791,3.876-2.116l1.211-1.891 l4.12-6.695c0.392-0.614,0.422-1.372,0.071-2.014C36.398,17.698,35.722,17.292,34.992,17.292L34.992,17.292z"
- opacity=".05"
- />
- <path
- d="M34.992,17.792c-0.319,0-0.63,0.107-0.899,0.31l-5.697,4.218 c-0.216,0.163-0.468,0.248-0.732,0.248c-0.259,0-0.504-0.082-0.71-0.236l-3.973-2.991c-0.719-0.535-1.568-0.817-2.457-0.817 c-1.405,0-2.696,0.705-3.455,1.887l-1.21,1.891l-4.115,6.688c-0.297,0.465-0.32,1.033-0.058,1.511c0.266,0.486,0.787,0.8,1.325,0.8 c0.319,0,0.63-0.107,0.899-0.31l5.697-4.218c0.216-0.163,0.468-0.248,0.732-0.248c0.259,0,0.504,0.082,0.71,0.236l3.973,2.991 c0.719,0.535,1.568,0.817,2.457,0.817c1.405,0,2.696-0.705,3.455-1.887l1.21-1.891l4.115-6.688c0.297-0.465,0.32-1.033,0.058-1.511 C36.051,18.106,35.531,17.792,34.992,17.792L34.992,17.792z"
- opacity=".07"
- />
- <path
- fill="#ffffff"
- d="M34.394,18.501l-5.7,4.22c-0.61,0.46-1.44,0.46-2.04,0.01L22.68,19.74 c-1.68-1.25-4.06-0.82-5.19,0.94l-1.21,1.89l-4.11,6.68c-0.6,0.94,0.55,2.01,1.44,1.34l5.7-4.22c0.61-0.46,1.44-0.46,2.04-0.01 l3.974,2.991c1.68,1.25,4.06,0.82,5.19-0.94l1.21-1.89l4.11-6.68C36.434,18.901,35.284,17.831,34.394,18.501z"
- />
- </svg>
- ),
+ notion: () => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 100 100"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z"
+ fill="#ffffff"
+ />
+ <path
+ d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z"
+ fill="#000000"
+ fillRule="evenodd"
+ clipRule="evenodd"
+ />
+ </svg>
+ ),
+ openai: () => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 24 24"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
+ </svg>
+ ),
+ googleDrive: () => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 87.3 78"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z"
+ fill="#0066da"
+ />
+ <path
+ d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z"
+ fill="#00ac47"
+ />
+ <path
+ d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z"
+ fill="#ea4335"
+ />
+ <path
+ d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z"
+ fill="#00832d"
+ />
+ <path
+ d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z"
+ fill="#2684fc"
+ />
+ <path
+ d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z"
+ fill="#ffba00"
+ />
+ </svg>
+ ),
+ whatsapp: () => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 175.216 175.552"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <defs>
+ <linearGradient
+ id="b"
+ x1="85.915"
+ x2="86.535"
+ y1="32.567"
+ y2="137.092"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop offset="0" stopColor="#57d163" />
+ <stop offset="1" stopColor="#23b33a" />
+ </linearGradient>
+ <filter
+ id="a"
+ width="1.115"
+ height="1.114"
+ x="-.057"
+ y="-.057"
+ colorInterpolationFilters="sRGB"
+ >
+ <feGaussianBlur stdDeviation="3.531" />
+ </filter>
+ </defs>
+ <path
+ d="m54.532 138.45 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.523h.023c33.707 0 61.139-27.426 61.153-61.135.006-16.335-6.349-31.696-17.895-43.251A60.75 60.75 0 0 0 87.94 25.983c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.558zm-40.811 23.544L24.16 123.88c-6.438-11.154-9.825-23.808-9.821-36.772.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954zm0 0"
+ fill="#b3b3b3"
+ filter="url(#a)"
+ />
+ <path
+ d="m12.966 161.238 10.439-38.114a73.42 73.42 0 0 1-9.821-36.772c.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954z"
+ fill="#ffffff"
+ />
+ <path
+ d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.559 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.524h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.929z"
+ fill="url(#linearGradient1780)"
+ />
+ <path
+ d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.313-6.179 22.558 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.517 31.126 8.523h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.928z"
+ fill="url(#b)"
+ />
+ <path
+ d="M68.772 55.603c-1.378-3.061-2.828-3.123-4.137-3.176l-3.524-.043c-1.226 0-3.218.46-4.902 2.3s-6.435 6.287-6.435 15.332 6.588 17.785 7.506 19.013 12.718 20.381 31.405 27.75c15.529 6.124 18.689 4.906 22.061 4.6s10.877-4.447 12.408-8.74 1.532-7.971 1.073-8.74-1.685-1.226-3.525-2.146-10.877-5.367-12.562-5.981-2.91-.919-4.137.921-4.746 5.979-5.819 7.206-2.144 1.381-3.984.462-7.76-2.861-14.784-9.124c-5.465-4.873-9.154-10.891-10.228-12.73s-.114-2.835.808-3.751c.825-.824 1.838-2.147 2.759-3.22s1.224-1.84 1.836-3.065.307-2.301-.153-3.22-4.032-10.011-5.666-13.647"
+ fill="#ffffff"
+ fillRule="evenodd"
+ />
+ </svg>
+ ),
+ googleDocs: () => (
+ <svg
+ width="47px"
+ height="65px"
+ viewBox="0 0 47 65"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <defs>
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-1"
+ />
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-3"
+ />
+ <linearGradient
+ x1="50.0053945%"
+ y1="8.58610612%"
+ x2="50.0053945%"
+ y2="100.013939%"
+ id="linearGradient-5"
+ >
+ <stop stopColor="#1A237E" stopOpacity="0.2" offset="0%" />
+ <stop stopColor="#1A237E" stopOpacity="0.02" offset="100%" />
+ </linearGradient>
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-6"
+ />
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-8"
+ />
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-10"
+ />
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-12"
+ />
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="path-14"
+ />
+ <radialGradient
+ cx="3.16804688%"
+ cy="2.71744318%"
+ fx="3.16804688%"
+ fy="2.71744318%"
+ r="161.248516%"
+ gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.723077),translate(-0.031680,-0.027174)"
+ id="radialGradient-16"
+ >
+ <stop stopColor="#FFFFFF" stopOpacity="0.1" offset="0%" />
+ <stop stopColor="#FFFFFF" stopOpacity="0" offset="100%" />
+ </radialGradient>
+ </defs>
+ <g
+ id="Page-1"
+ stroke="none"
+ strokeWidth="1"
+ fill="none"
+ fillRule="evenodd"
+ >
+ <g transform="translate(-451.000000, -463.000000)">
+ <g id="Hero" transform="translate(0.000000, 63.000000)">
+ <g id="Personal" transform="translate(277.000000, 309.000000)">
+ <g id="Docs-icon" transform="translate(174.000000, 91.000000)">
+ <g id="Group">
+ <g id="Clipped">
+ <mask id="mask-2" fill="white">
+ <use xlinkHref="#path-1" />
+ </mask>
+ <g id="SVGID_1_" />
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L36.71875,10.3409091 L29.375,0 Z"
+ id="Path"
+ fill="#4285F4"
+ fillRule="nonzero"
+ mask="url(#mask-2)"
+ />
+ </g>
+ <g id="Clipped">
+ <mask id="mask-4" fill="white">
+ <use xlinkHref="#path-3" />
+ </mask>
+ <g id="SVGID_1_" />
+ <polygon
+ id="Path"
+ fill="url(#linearGradient-5)"
+ fillRule="nonzero"
+ mask="url(#mask-4)"
+ points="30.6638281 16.4309659 47 32.8582386 47 17.7272727"
+ ></polygon>
+ </g>
+ <g id="Clipped">
+ <mask id="mask-7" fill="white">
+ <use xlinkHref="#path-6" />
+ </mask>
+ <g id="SVGID_1_" />
+ <path
+ d="M11.75,47.2727273 L35.25,47.2727273 L35.25,44.3181818 L11.75,44.3181818 L11.75,47.2727273 Z M11.75,53.1818182 L29.375,53.1818182 L29.375,50.2272727 L11.75,50.2272727 L11.75,53.1818182 Z M11.75,32.5 L11.75,35.4545455 L35.25,35.4545455 L35.25,32.5 L11.75,32.5 Z M11.75,41.3636364 L35.25,41.3636364 L35.25,38.4090909 L11.75,38.4090909 L11.75,41.3636364 Z"
+ id="Shape"
+ fill="#F1F1F1"
+ fillRule="nonzero"
+ mask="url(#mask-7)"
+ />
+ </g>
+ <g id="Clipped">
+ <mask id="mask-9" fill="white">
+ <use xlinkHref="#path-8" />
+ </mask>
+ <g id="SVGID_1_" />
+ <g id="Group" mask="url(#mask-9)">
+ <g transform="translate(26.437500, -2.954545)">
+ <path
+ d="M2.9375,2.95454545 L2.9375,16.25 C2.9375,18.6985795 4.90929688,20.6818182 7.34375,20.6818182 L20.5625,20.6818182 L2.9375,2.95454545 Z"
+ id="Path"
+ fill="#A1C2FA"
+ fillRule="nonzero"
+ />
+ </g>
+ </g>
+ </g>
+ <g id="Clipped">
+ <mask id="mask-11" fill="white">
+ <use xlinkHref="#path-10" />
+ </mask>
+ <g id="SVGID_1_" />
+ <path
+ d="M4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,4.80113636 C0,2.36363636 1.9828125,0.369318182 4.40625,0.369318182 L29.375,0.369318182 L29.375,0 L4.40625,0 Z"
+ id="Path"
+ fillOpacity="0.2"
+ fill="#FFFFFF"
+ fillRule="nonzero"
+ mask="url(#mask-11)"
+ />
+ </g>
+ <g id="Clipped">
+ <mask id="mask-13" fill="white">
+ <use xlinkHref="#path-12" />
+ </mask>
+ <g id="SVGID_1_" />
+ <path
+ d="M42.59375,64.6306818 L4.40625,64.6306818 C1.9828125,64.6306818 0,62.6363636 0,60.1988636 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,60.1988636 C47,62.6363636 45.0171875,64.6306818 42.59375,64.6306818 Z"
+ id="Path"
+ fillOpacity="0.2"
+ fill="#1A237E"
+ fillRule="nonzero"
+ mask="url(#mask-13)"
+ />
+ </g>
+ <g id="Clipped">
+ <mask id="mask-15" fill="white">
+ <use xlinkHref="#path-14" />
+ </mask>
+ <g id="SVGID_1_" />
+ <path
+ d="M33.78125,17.7272727 C31.3467969,17.7272727 29.375,15.7440341 29.375,13.2954545 L29.375,13.6647727 C29.375,16.1133523 31.3467969,18.0965909 33.78125,18.0965909 L47,18.0965909 L47,17.7272727 L33.78125,17.7272727 Z"
+ id="Path"
+ fillOpacity="0.1"
+ fill="#1A237E"
+ fillRule="nonzero"
+ mask="url(#mask-15)"
+ />
+ </g>
+ </g>
+ <path
+ d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z"
+ id="Path"
+ fill="url(#radialGradient-16)"
+ fillRule="nonzero"
+ />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </svg>
+ ),
+ zapier: () => (
+ <svg
+ width="105"
+ height="28"
+ viewBox="0 0 244 66"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M57.1877 45.2253L57.1534 45.1166L78.809 25.2914V15.7391H44.0663V25.2914H64.8181L64.8524 25.3829L43.4084 45.2253V54.7775H79.1579V45.2253H57.1877Z"
+ fill="#201515"
+ />
+ <path
+ d="M100.487 14.8297C96.4797 14.8297 93.2136 15.434 90.6892 16.6429C88.3376 17.6963 86.3568 19.4321 85.0036 21.6249C83.7091 23.8321 82.8962 26.2883 82.6184 28.832L93.1602 30.3135C93.5415 28.0674 94.3042 26.4754 95.4482 25.5373C96.7486 24.5562 98.3511 24.0605 99.9783 24.136C102.118 24.136 103.67 24.7079 104.634 25.8519C105.59 26.9959 106.076 28.5803 106.076 30.6681V31.7091H95.9401C90.7807 31.7091 87.0742 32.8531 84.8206 35.1411C82.5669 37.429 81.442 40.4492 81.4458 44.2014C81.4458 48.0452 82.5707 50.9052 84.8206 52.7813C87.0704 54.6574 89.8999 55.5897 93.3089 55.5783C97.5379 55.5783 100.791 54.1235 103.067 51.214C104.412 49.426 105.372 47.3793 105.887 45.2024H106.27L107.723 54.7546H117.275V30.5651C117.275 25.5659 115.958 21.6936 113.323 18.948C110.688 16.2024 106.409 14.8297 100.487 14.8297ZM103.828 44.6475C102.312 45.9116 100.327 46.5408 97.8562 46.5408C95.8199 46.5408 94.4052 46.1843 93.6121 45.4712C93.2256 45.1338 92.9182 44.7155 92.7116 44.246C92.505 43.7764 92.4043 43.2671 92.4166 42.7543C92.3941 42.2706 92.4702 41.7874 92.6403 41.3341C92.8104 40.8808 93.071 40.4668 93.4062 40.1174C93.7687 39.7774 94.1964 39.5145 94.6633 39.3444C95.1303 39.1743 95.6269 39.1006 96.1231 39.1278H106.093V39.7856C106.113 40.7154 105.919 41.6374 105.527 42.4804C105.134 43.3234 104.553 44.0649 103.828 44.6475Z"
+ fill="#201515"
+ />
+ <path
+ d="M175.035 15.7391H163.75V54.7833H175.035V15.7391Z"
+ fill="#201515"
+ />
+ <path
+ d="M241.666 15.7391C238.478 15.7391 235.965 16.864 234.127 19.1139C232.808 20.7307 231.805 23.1197 231.119 26.2809H230.787L229.311 15.7391H219.673V54.7775H230.959V34.7578C230.959 32.2335 231.55 30.2982 232.732 28.9521C233.914 27.606 236.095 26.933 239.275 26.933H243.559V15.7391H241.666Z"
+ fill="#201515"
+ />
+ <path
+ d="M208.473 17.0147C205.839 15.4474 202.515 14.6657 198.504 14.6695C192.189 14.6695 187.247 16.4675 183.678 20.0634C180.108 23.6593 178.324 28.6166 178.324 34.9352C178.233 38.7553 179.067 42.5407 180.755 45.9689C182.3 49.0238 184.706 51.5592 187.676 53.2618C190.665 54.9892 194.221 55.8548 198.344 55.8586C201.909 55.8586 204.887 55.3095 207.278 54.2113C209.526 53.225 211.483 51.6791 212.964 49.7211C214.373 47.7991 215.42 45.6359 216.052 43.3377L206.329 40.615C205.919 42.1094 205.131 43.4728 204.041 44.5732C202.942 45.6714 201.102 46.2206 198.521 46.2206C195.451 46.2206 193.163 45.3416 191.657 43.5837C190.564 42.3139 189.878 40.5006 189.575 38.1498H216.201C216.31 37.0515 216.367 36.1306 216.367 35.387V32.9561C216.431 29.6903 215.757 26.4522 214.394 23.4839C213.118 20.7799 211.054 18.5248 208.473 17.0147ZM198.178 23.9758C202.754 23.9758 205.348 26.2275 205.962 30.731H189.775C190.032 29.2284 190.655 27.8121 191.588 26.607C193.072 24.8491 195.268 23.972 198.178 23.9758Z"
+ fill="#201515"
+ />
+ <path
+ d="M169.515 0.00366253C168.666 -0.0252113 167.82 0.116874 167.027 0.421484C166.234 0.726094 165.511 1.187 164.899 1.77682C164.297 2.3723 163.824 3.08658 163.512 3.87431C163.2 4.66204 163.055 5.50601 163.086 6.35275C163.056 7.20497 163.201 8.05433 163.514 8.84781C163.826 9.64129 164.299 10.3619 164.902 10.9646C165.505 11.5673 166.226 12.0392 167.02 12.3509C167.814 12.6626 168.663 12.8074 169.515 12.7762C170.362 12.8082 171.206 12.6635 171.994 12.3514C172.782 12.0392 173.496 11.5664 174.091 10.963C174.682 10.3534 175.142 9.63077 175.446 8.83849C175.75 8.04621 175.89 7.20067 175.859 6.35275C175.898 5.50985 175.761 4.66806 175.456 3.88115C175.151 3.09424 174.686 2.37951 174.09 1.78258C173.493 1.18565 172.779 0.719644 171.992 0.414327C171.206 0.109011 170.364 -0.0288946 169.521 0.00938803L169.515 0.00366253Z"
+ fill="#201515"
+ />
+ <path
+ d="M146.201 14.6695C142.357 14.6695 139.268 15.8764 136.935 18.2902C135.207 20.0786 133.939 22.7479 133.131 26.2981H132.771L131.295 15.7563H121.657V66H132.942V45.3054H133.354C133.698 46.6852 134.181 48.0267 134.795 49.3093C135.75 51.3986 137.316 53.1496 139.286 54.3314C141.328 55.446 143.629 56.0005 145.955 55.9387C150.68 55.9387 154.277 54.0988 156.748 50.419C159.219 46.7392 160.455 41.6046 160.455 35.0153C160.455 28.6509 159.259 23.6689 156.869 20.0691C154.478 16.4694 150.922 14.6695 146.201 14.6695ZM147.345 42.9602C146.029 44.8668 143.97 45.8201 141.167 45.8201C140.012 45.8735 138.86 45.6507 137.808 45.1703C136.755 44.6898 135.832 43.9656 135.116 43.0574C133.655 41.2233 132.927 38.7122 132.931 35.5243V34.7807C132.931 31.5432 133.659 29.0646 135.116 27.3448C136.572 25.625 138.59 24.7747 141.167 24.7937C144.02 24.7937 146.092 25.6994 147.385 27.5107C148.678 29.322 149.324 31.8483 149.324 35.0896C149.332 38.4414 148.676 41.065 147.356 42.9602H147.345Z"
+ fill="#201515"
+ />
+ <path d="M39.0441 45.2253H0V54.789H39.0441V45.2253Z" fill="#FF4F00" />
+ </svg>
+ ),
+ messenger: () => (
+ <svg
+ width="100"
+ height="100"
+ viewBox="0 0 48 48"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <radialGradient
+ id="8O3wK6b5ASW2Wn6hRCB5xa_YFbzdUk7Q3F8_gr1"
+ cx="11.087"
+ cy="7.022"
+ r="47.612"
+ gradientTransform="matrix(1 0 0 -1 0 50)"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop offset="0" stopColor="#1292ff"></stop>
+ <stop offset=".079" stopColor="#2982ff"></stop>
+ <stop offset=".23" stopColor="#4e69ff"></stop>
+ <stop offset=".351" stopColor="#6559ff"></stop>
+ <stop offset=".428" stopColor="#6d53ff"></stop>
+ <stop offset=".754" stopColor="#df47aa"></stop>
+ <stop offset=".946" stopColor="#ff6257"></stop>
+ </radialGradient>
+ <path
+ fill="url(#8O3wK6b5ASW2Wn6hRCB5xa_YFbzdUk7Q3F8_gr1)"
+ d="M44,23.5C44,34.27,35.05,43,24,43c-1.651,0-3.25-0.194-4.784-0.564 c-0.465-0.112-0.951-0.069-1.379,0.145L13.46,44.77C12.33,45.335,11,44.513,11,43.249v-4.025c0-0.575-0.257-1.111-0.681-1.499 C6.425,34.165,4,29.11,4,23.5C4,12.73,12.95,4,24,4S44,12.73,44,23.5z"
+ />
+ <path
+ d="M34.992,17.292c-0.428,0-0.843,0.142-1.2,0.411l-5.694,4.215 c-0.133,0.1-0.28,0.15-0.435,0.15c-0.15,0-0.291-0.047-0.41-0.136l-3.972-2.99c-0.808-0.601-1.76-0.918-2.757-0.918 c-1.576,0-3.025,0.791-3.876,2.116l-1.211,1.891l-4.12,6.695c-0.392,0.614-0.422,1.372-0.071,2.014 c0.358,0.654,1.034,1.06,1.764,1.06c0.428,0,0.843-0.142,1.2-0.411l5.694-4.215c0.133-0.1,0.28-0.15,0.435-0.15 c0.15,0,0.291,0.047,0.41,0.136l3.972,2.99c0.809,0.602,1.76,0.918,2.757,0.918c1.576,0,3.025-0.791,3.876-2.116l1.211-1.891 l4.12-6.695c0.392-0.614,0.422-1.372,0.071-2.014C36.398,17.698,35.722,17.292,34.992,17.292L34.992,17.292z"
+ opacity=".05"
+ />
+ <path
+ d="M34.992,17.792c-0.319,0-0.63,0.107-0.899,0.31l-5.697,4.218 c-0.216,0.163-0.468,0.248-0.732,0.248c-0.259,0-0.504-0.082-0.71-0.236l-3.973-2.991c-0.719-0.535-1.568-0.817-2.457-0.817 c-1.405,0-2.696,0.705-3.455,1.887l-1.21,1.891l-4.115,6.688c-0.297,0.465-0.32,1.033-0.058,1.511c0.266,0.486,0.787,0.8,1.325,0.8 c0.319,0,0.63-0.107,0.899-0.31l5.697-4.218c0.216-0.163,0.468-0.248,0.732-0.248c0.259,0,0.504,0.082,0.71,0.236l3.973,2.991 c0.719,0.535,1.568,0.817,2.457,0.817c1.405,0,2.696-0.705,3.455-1.887l1.21-1.891l4.115-6.688c0.297-0.465,0.32-1.033,0.058-1.511 C36.051,18.106,35.531,17.792,34.992,17.792L34.992,17.792z"
+ opacity=".07"
+ />
+ <path
+ fill="#ffffff"
+ d="M34.394,18.501l-5.7,4.22c-0.61,0.46-1.44,0.46-2.04,0.01L22.68,19.74 c-1.68-1.25-4.06-0.82-5.19,0.94l-1.21,1.89l-4.11,6.68c-0.6,0.94,0.55,2.01,1.44,1.34l5.7-4.22c0.61-0.46,1.44-0.46,2.04-0.01 l3.974,2.991c1.68,1.25,4.06,0.82,5.19-0.94l1.21-1.89l4.11-6.68C36.434,18.901,35.284,17.831,34.394,18.501z"
+ />
+ </svg>
+ ),
};
diff --git a/apps/web/app/(landing)/CardPatterns/AnimatedGrid.tsx b/apps/web/app/(landing)/CardPatterns/AnimatedGrid.tsx
index 31f9e52c..0fe48ca3 100644
--- a/apps/web/app/(landing)/CardPatterns/AnimatedGrid.tsx
+++ b/apps/web/app/(landing)/CardPatterns/AnimatedGrid.tsx
@@ -5,145 +5,145 @@ import { motion } from "framer-motion";
import { useEffect, useId, useRef, useState } from "react";
interface GridPatternProps {
- width?: number;
- height?: number;
- x?: number;
- y?: number;
- strokeDasharray?: any;
- numSquares?: number;
- className?: string;
- maxOpacity?: number;
- duration?: number;
- repeatDelay?: number;
+ width?: number;
+ height?: number;
+ x?: number;
+ y?: number;
+ strokeDasharray?: any;
+ numSquares?: number;
+ className?: string;
+ maxOpacity?: number;
+ duration?: number;
+ repeatDelay?: number;
}
export function GridPattern({
- width = 40,
- height = 40,
- x = -1,
- y = -1,
- strokeDasharray = 0,
- numSquares = 50,
- className,
- maxOpacity = 0.5,
- duration = 4,
- repeatDelay = 0.5,
- ...props
+ width = 40,
+ height = 40,
+ x = -1,
+ y = -1,
+ strokeDasharray = 0,
+ numSquares = 50,
+ className,
+ maxOpacity = 0.5,
+ duration = 4,
+ repeatDelay = 0.5,
+ ...props
}: GridPatternProps) {
- const id = useId();
- const containerRef = useRef(null);
- const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
- const [squares, setSquares] = useState(() => generateSquares(numSquares));
+ const id = useId();
+ const containerRef = useRef(null);
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
+ const [squares, setSquares] = useState(() => generateSquares(numSquares));
- function getPos() {
- return [
- Math.floor((Math.random() * dimensions.width) / width),
- Math.floor((Math.random() * dimensions.height) / height),
- ];
- }
+ function getPos() {
+ return [
+ Math.floor((Math.random() * dimensions.width) / width),
+ Math.floor((Math.random() * dimensions.height) / height),
+ ];
+ }
- // Adjust the generateSquares function to return objects with an id, x, and y
- function generateSquares(count: number) {
- return Array.from({ length: count }, (_, i) => ({
- id: i,
- pos: getPos(),
- }));
- }
+ // Adjust the generateSquares function to return objects with an id, x, and y
+ function generateSquares(count: number) {
+ return Array.from({ length: count }, (_, i) => ({
+ id: i,
+ pos: getPos(),
+ }));
+ }
- // Function to update a single square's position
- const updateSquarePosition = (id: number) => {
- setSquares((currentSquares) =>
- currentSquares.map((sq) =>
- sq.id === id
- ? {
- ...sq,
- pos: getPos(),
- }
- : sq,
- ),
- );
- };
+ // Function to update a single square's position
+ const updateSquarePosition = (id: number) => {
+ setSquares((currentSquares) =>
+ currentSquares.map((sq) =>
+ sq.id === id
+ ? {
+ ...sq,
+ pos: getPos(),
+ }
+ : sq,
+ ),
+ );
+ };
- // Update squares to animate in
- useEffect(() => {
- if (dimensions.width && dimensions.height) {
- setSquares(generateSquares(numSquares));
- }
- }, [dimensions, numSquares]);
+ // Update squares to animate in
+ useEffect(() => {
+ if (dimensions.width && dimensions.height) {
+ setSquares(generateSquares(numSquares));
+ }
+ }, [dimensions, numSquares]);
- // Resize observer to update container dimensions
- useEffect(() => {
- const resizeObserver = new ResizeObserver((entries) => {
- for (let entry of entries) {
- setDimensions({
- width: entry.contentRect.width,
- height: entry.contentRect.height,
- });
- }
- });
+ // Resize observer to update container dimensions
+ useEffect(() => {
+ const resizeObserver = new ResizeObserver((entries) => {
+ for (let entry of entries) {
+ setDimensions({
+ width: entry.contentRect.width,
+ height: entry.contentRect.height,
+ });
+ }
+ });
- if (containerRef.current) {
- resizeObserver.observe(containerRef.current);
- }
+ if (containerRef.current) {
+ resizeObserver.observe(containerRef.current);
+ }
- return () => {
- if (containerRef.current) {
- resizeObserver.unobserve(containerRef.current);
- }
- };
- }, [containerRef]);
+ return () => {
+ if (containerRef.current) {
+ resizeObserver.unobserve(containerRef.current);
+ }
+ };
+ }, [containerRef]);
- return (
- <svg
- ref={containerRef}
- aria-hidden="true"
- className={cn(
- "pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
- className,
- )}
- {...props}
- >
- <defs>
- <pattern
- id={id}
- width={width}
- height={height}
- patternUnits="userSpaceOnUse"
- x={x}
- y={y}
- >
- <path
- d={`M.5 ${height}V.5H${width}`}
- fill="none"
- strokeDasharray={strokeDasharray}
- />
- </pattern>
- </defs>
- <rect width="100%" height="100%" fill={`url(#${id})`} />
- <svg x={x} y={y} className="overflow-visible">
- {squares.map(({ pos: [x, y], id }, index) => (
- <motion.rect
- initial={{ opacity: 0 }}
- animate={{ opacity: maxOpacity }}
- transition={{
- duration,
- repeat: 1,
- delay: index * 0.1,
- repeatType: "reverse",
- }}
- onAnimationComplete={() => updateSquarePosition(id)}
- key={`${x}-${y}-${index}`}
- width={width - 1}
- height={height - 1}
- x={x ?? 1 * width + 1}
- y={y ?? 1 * height + 1}
- fill="currentColor"
- strokeWidth="0"
- />
- ))}
- </svg>
- </svg>
- );
+ return (
+ <svg
+ ref={containerRef}
+ aria-hidden="true"
+ className={cn(
+ "pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
+ className,
+ )}
+ {...props}
+ >
+ <defs>
+ <pattern
+ id={id}
+ width={width}
+ height={height}
+ patternUnits="userSpaceOnUse"
+ x={x}
+ y={y}
+ >
+ <path
+ d={`M.5 ${height}V.5H${width}`}
+ fill="none"
+ strokeDasharray={strokeDasharray}
+ />
+ </pattern>
+ </defs>
+ <rect width="100%" height="100%" fill={`url(#${id})`} />
+ <svg x={x} y={y} className="overflow-visible">
+ {squares.map(({ pos: [x, y], id }, index) => (
+ <motion.rect
+ initial={{ opacity: 0 }}
+ animate={{ opacity: maxOpacity }}
+ transition={{
+ duration,
+ repeat: 1,
+ delay: index * 0.1,
+ repeatType: "reverse",
+ }}
+ onAnimationComplete={() => updateSquarePosition(id)}
+ key={`${x}-${y}-${index}`}
+ width={width - 1}
+ height={height - 1}
+ x={x ?? 1 * width + 1}
+ y={y ?? 1 * height + 1}
+ fill="currentColor"
+ strokeWidth="0"
+ />
+ ))}
+ </svg>
+ </svg>
+ );
}
export default GridPattern;
diff --git a/apps/web/app/(landing)/CardPatterns/Glare.tsx b/apps/web/app/(landing)/CardPatterns/Glare.tsx
index 53550ecc..7f6a2621 100644
--- a/apps/web/app/(landing)/CardPatterns/Glare.tsx
+++ b/apps/web/app/(landing)/CardPatterns/Glare.tsx
@@ -4,136 +4,135 @@ import { cn } from "@repo/ui/lib/utils";
import { useRef } from "react";
export const GlareCard = ({
- children,
- className,
+ children,
+ className,
}: {
- children: React.ReactNode;
- className?: string;
+ children: React.ReactNode;
+ className?: string;
}) => {
- const isPointerInside = useRef(false);
- const refElement = useRef<HTMLDivElement>(null);
- const state = useRef({
- glare: {
- x: 50,
- y: 50,
- },
- background: {
- x: 50,
- y: 50,
- },
- rotate: {
- x: 0,
- y: 0,
- },
- });
- const containerStyle = {
- "--m-x": "50%",
- "--m-y": "50%",
- "--r-x": "0deg",
- "--r-y": "0deg",
- "--bg-x": "50%",
- "--bg-y": "50%",
- "--duration": "300ms",
- "--foil-size": "100%",
- "--opacity": "0",
- "--radius": "23px",
- "--easing": "ease",
- "--transition": "var(--duration) var(--easing)",
- } as any;
+ const isPointerInside = useRef(false);
+ const refElement = useRef<HTMLDivElement>(null);
+ const state = useRef({
+ glare: {
+ x: 50,
+ y: 50,
+ },
+ background: {
+ x: 50,
+ y: 50,
+ },
+ rotate: {
+ x: 0,
+ y: 0,
+ },
+ });
+ const containerStyle = {
+ "--m-x": "50%",
+ "--m-y": "50%",
+ "--r-x": "0deg",
+ "--r-y": "0deg",
+ "--bg-x": "50%",
+ "--bg-y": "50%",
+ "--duration": "300ms",
+ "--foil-size": "100%",
+ "--opacity": "0",
+ "--radius": "23px",
+ "--easing": "ease",
+ "--transition": "var(--duration) var(--easing)",
+ } as any;
- const backgroundStyle = {
- "--step": "5%",
- "--foil-svg": `url("data:image/svg+xml,%3Csvg width='26' height='26' viewBox='0 0 26 26' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.99994 3.419C2.99994 3.419 21.6142 7.43646 22.7921 12.153C23.97 16.8695 3.41838 23.0306 3.41838 23.0306' stroke='white' stroke-width='5' stroke-miterlimit='3.86874' stroke-linecap='round' style='mix-blend-mode:darken'/%3E%3C/svg%3E")`,
- "--pattern": "var(--foil-svg) center/100% no-repeat",
- "--rainbow":
- "repeating-linear-gradient( 0deg,rgb(255,119,115) calc(var(--step) * 1),rgba(255,237,95,1) calc(var(--step) * 2),rgba(168,255,95,1) calc(var(--step) * 3),rgba(131,255,247,1) calc(var(--step) * 4),rgba(120,148,255,1) calc(var(--step) * 5),rgb(216,117,255) calc(var(--step) * 6),rgb(255,119,115) calc(var(--step) * 7) ) 0% var(--bg-y)/200% 700% no-repeat",
- "--diagonal":
- "repeating-linear-gradient( 128deg,#0e152e 0%,hsl(180,10%,60%) 3.8%,hsl(180,10%,60%) 4.5%,hsl(180,10%,60%) 5.2%,#0e152e 10%,#0e152e 12% ) var(--bg-x) var(--bg-y)/300% no-repeat",
- "--shade":
- "radial-gradient( farthest-corner circle at var(--m-x) var(--m-y),rgba(255,255,255,0.1) 12%,rgba(255,255,255,0.15) 20%,rgba(255,255,255,0.25) 120% ) var(--bg-x) var(--bg-y)/300% no-repeat",
- "--hero-gradient":
- 'radial-gradient(ellipse 50% 80% at 20% 40%, rgba(93, 52, 221, 0.1), transparent), radial-gradient(ellipse 50% 80% at 80% 50%, rgba(120, 119, 198, 0.15),transparent)',
+ const backgroundStyle = {
+ "--step": "5%",
+ "--foil-svg": `url("data:image/svg+xml,%3Csvg width='26' height='26' viewBox='0 0 26 26' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.99994 3.419C2.99994 3.419 21.6142 7.43646 22.7921 12.153C23.97 16.8695 3.41838 23.0306 3.41838 23.0306' stroke='white' stroke-width='5' stroke-miterlimit='3.86874' stroke-linecap='round' style='mix-blend-mode:darken'/%3E%3C/svg%3E")`,
+ "--pattern": "var(--foil-svg) center/100% no-repeat",
+ "--rainbow":
+ "repeating-linear-gradient( 0deg,rgb(255,119,115) calc(var(--step) * 1),rgba(255,237,95,1) calc(var(--step) * 2),rgba(168,255,95,1) calc(var(--step) * 3),rgba(131,255,247,1) calc(var(--step) * 4),rgba(120,148,255,1) calc(var(--step) * 5),rgb(216,117,255) calc(var(--step) * 6),rgb(255,119,115) calc(var(--step) * 7) ) 0% var(--bg-y)/200% 700% no-repeat",
+ "--diagonal":
+ "repeating-linear-gradient( 128deg,#0e152e 0%,hsl(180,10%,60%) 3.8%,hsl(180,10%,60%) 4.5%,hsl(180,10%,60%) 5.2%,#0e152e 10%,#0e152e 12% ) var(--bg-x) var(--bg-y)/300% no-repeat",
+ "--shade":
+ "radial-gradient( farthest-corner circle at var(--m-x) var(--m-y),rgba(255,255,255,0.1) 12%,rgba(255,255,255,0.15) 20%,rgba(255,255,255,0.25) 120% ) var(--bg-x) var(--bg-y)/300% no-repeat",
+ "--hero-gradient":
+ "radial-gradient(ellipse 50% 80% at 20% 40%, rgba(93, 52, 221, 0.1), transparent), radial-gradient(ellipse 50% 80% at 80% 50%, rgba(120, 119, 198, 0.15),transparent)",
- backgroundBlendMode: "hue, hue, hue, overlay",
- };
+ backgroundBlendMode: "hue, hue, hue, overlay",
+ };
- const updateStyles = () => {
- if (refElement.current) {
- console.log(state.current);
- const { background, rotate, glare } = state.current;
- refElement.current?.style.setProperty("--m-x", `${glare.x}%`);
- refElement.current?.style.setProperty("--m-y", `${glare.y}%`);
- refElement.current?.style.setProperty("--r-x", `${rotate.x}deg`);
- refElement.current?.style.setProperty("--r-y", `${rotate.y}deg`);
- refElement.current?.style.setProperty("--bg-x", `${background.x}%`);
- refElement.current?.style.setProperty("--bg-y", `${background.y}%`);
- }
- };
- return (
- <div
- style={containerStyle}
- className="relative isolate [contain:layout_style] [perspective:600px] transition-transform duration-[var(--duration)] ease-[var(--easing)] delay-[var(--delay)] will-change-transform w-full h-full"
- ref={refElement}
- onPointerMove={(event) => {
- const rotateFactor = 0.4;
- const rect = event.currentTarget.getBoundingClientRect();
- const position = {
- x: event.clientX - rect.left,
- y: event.clientY - rect.top,
- };
- const percentage = {
- x: (100 / rect.width) * position.x,
- y: (100 / rect.height) * position.y,
- };
- const delta = {
- x: percentage.x - 50,
- y: percentage.y - 50,
- };
+ const updateStyles = () => {
+ if (refElement.current) {
+ console.log(state.current);
+ const { background, rotate, glare } = state.current;
+ refElement.current?.style.setProperty("--m-x", `${glare.x}%`);
+ refElement.current?.style.setProperty("--m-y", `${glare.y}%`);
+ refElement.current?.style.setProperty("--r-x", `${rotate.x}deg`);
+ refElement.current?.style.setProperty("--r-y", `${rotate.y}deg`);
+ refElement.current?.style.setProperty("--bg-x", `${background.x}%`);
+ refElement.current?.style.setProperty("--bg-y", `${background.y}%`);
+ }
+ };
+ return (
+ <div
+ style={containerStyle}
+ className="relative isolate [contain:layout_style] [perspective:600px] transition-transform duration-[var(--duration)] ease-[var(--easing)] delay-[var(--delay)] will-change-transform w-full h-full"
+ ref={refElement}
+ onPointerMove={(event) => {
+ const rotateFactor = 0.4;
+ const rect = event.currentTarget.getBoundingClientRect();
+ const position = {
+ x: event.clientX - rect.left,
+ y: event.clientY - rect.top,
+ };
+ const percentage = {
+ x: (100 / rect.width) * position.x,
+ y: (100 / rect.height) * position.y,
+ };
+ const delta = {
+ x: percentage.x - 50,
+ y: percentage.y - 50,
+ };
- const { background, rotate, glare } = state.current;
- background.x = 50 + percentage.x / 4 - 12.5;
- background.y = 50 + percentage.y / 3 - 16.67;
- rotate.x = -(delta.x / 3.5);
- rotate.y = delta.y / 2;
- rotate.x *= rotateFactor;
- rotate.y *= rotateFactor;
- glare.x = percentage.x;
- glare.y = percentage.y;
+ const { background, rotate, glare } = state.current;
+ background.x = 50 + percentage.x / 4 - 12.5;
+ background.y = 50 + percentage.y / 3 - 16.67;
+ rotate.x = -(delta.x / 3.5);
+ rotate.y = delta.y / 2;
+ rotate.x *= rotateFactor;
+ rotate.y *= rotateFactor;
+ glare.x = percentage.x;
+ glare.y = percentage.y;
- updateStyles();
- }}
- onPointerEnter={() => {
- isPointerInside.current = true;
- if (refElement.current) {
- setTimeout(() => {
- if (isPointerInside.current) {
- refElement.current?.style.setProperty("--duration", "0s");
- }
- }, 300);
- }
- }}
- onPointerLeave={() => {
- isPointerInside.current = false;
- if (refElement.current) {
- refElement.current.style.removeProperty("--duration");
- refElement.current?.style.setProperty("--r-x", `0deg`);
- refElement.current?.style.setProperty("--r-y", `0deg`);
- }
- }}
- >
- <div className="h-full Bg-transparent border-[0.1px] border-white/20 dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset] grid will-change-transform origin-center transition-transform duration-[var(--duration)] ease-[var(--easing)] delay-[var(--delay)] [transform:rotateY(var(--r-x))_rotateX(var(--r-y))] rounded-[var(--radius)] hover:[--opacity:0.12] hover:[--duration:200ms] hover:[--easing:linear] hover:filter-none overflow-hidden">
- <div className="w-full h-full grid [grid-area:1/1] mix-blend-soft-light [clip-path:inset(0_0_0_0_round_var(--radius))]">
- <div className={cn("h-full w-full bg-hero-gradient", className)}>
- {children}
- </div>
- </div>
- <div className="w-full pb-10 bg-glass-gradient h-full grid [grid-area:1/1] mix-blend-soft-light [clip-path:inset(0_0_1px_0_round_var(--radius))] opacity-[var(--opacity)] transition-opacity transition-background duration-[var(--duration)] ease-[var(--easing)] delay-[var(--delay)] will-change-background [background:radial-gradient(farthest-corner_circle_at_var(--m-x)_var(--m-y),_rgba(255,255,255,0.8)_10%,_rgba(255,255,255,0.65)_20%,_rgba(255,255,255,0)_90%)]" />
- <div
- className="w-full h-full grid [grid-area:1/1] mix-blend-color-dodge opacity-[var(--opacity)] will-change-background transition-opacity [clip-path:inset(0_0_1px_0_round_var(--radius))] [background-blend-mode:hue_hue_hue_overlay] [background:var(--hero-gradient),_var(--rainbow),var(--diagonal),_var(--shade)] relative after:content-[''] after:grid-area-[inherit] after:bg-repeat-[inherit] after:bg-attachment-[inherit] after:bg-origin-[inherit] after:bg-clip-[inherit] after:bg-[inherit] after:mix-blend-exclusion after:[background-size:var(--foil-size),_200%_400%,_800%,_200%] after:[background-position:center,_0%_var(--bg-y),_calc(var(--bg-x)*_-1)_calc(var(--bg-y)*_-1),_var(--bg-x)_var(--bg-y)] after:[background-blend-mode:soft-light,_hue,_hard-light]"
- style={{ ...backgroundStyle , 'opacity':"10%"}}
- />
-
- </div>
- </div>
- );
-}; \ No newline at end of file
+ updateStyles();
+ }}
+ onPointerEnter={() => {
+ isPointerInside.current = true;
+ if (refElement.current) {
+ setTimeout(() => {
+ if (isPointerInside.current) {
+ refElement.current?.style.setProperty("--duration", "0s");
+ }
+ }, 300);
+ }
+ }}
+ onPointerLeave={() => {
+ isPointerInside.current = false;
+ if (refElement.current) {
+ refElement.current.style.removeProperty("--duration");
+ refElement.current?.style.setProperty("--r-x", `0deg`);
+ refElement.current?.style.setProperty("--r-y", `0deg`);
+ }
+ }}
+ >
+ <div className="h-full Bg-transparent border-[0.1px] border-white/20 dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset] grid will-change-transform origin-center transition-transform duration-[var(--duration)] ease-[var(--easing)] delay-[var(--delay)] [transform:rotateY(var(--r-x))_rotateX(var(--r-y))] rounded-[var(--radius)] hover:[--opacity:0.12] hover:[--duration:200ms] hover:[--easing:linear] hover:filter-none overflow-hidden">
+ <div className="w-full h-full grid [grid-area:1/1] mix-blend-soft-light [clip-path:inset(0_0_0_0_round_var(--radius))]">
+ <div className={cn("h-full w-full bg-hero-gradient", className)}>
+ {children}
+ </div>
+ </div>
+ <div className="w-full pb-10 bg-glass-gradient h-full grid [grid-area:1/1] mix-blend-soft-light [clip-path:inset(0_0_1px_0_round_var(--radius))] opacity-[var(--opacity)] transition-opacity transition-background duration-[var(--duration)] ease-[var(--easing)] delay-[var(--delay)] will-change-background [background:radial-gradient(farthest-corner_circle_at_var(--m-x)_var(--m-y),_rgba(255,255,255,0.8)_10%,_rgba(255,255,255,0.65)_20%,_rgba(255,255,255,0)_90%)]" />
+ <div
+ className="w-full h-full grid [grid-area:1/1] mix-blend-color-dodge opacity-[var(--opacity)] will-change-background transition-opacity [clip-path:inset(0_0_1px_0_round_var(--radius))] [background-blend-mode:hue_hue_hue_overlay] [background:var(--hero-gradient),_var(--rainbow),var(--diagonal),_var(--shade)] relative after:content-[''] after:grid-area-[inherit] after:bg-repeat-[inherit] after:bg-attachment-[inherit] after:bg-origin-[inherit] after:bg-clip-[inherit] after:bg-[inherit] after:mix-blend-exclusion after:[background-size:var(--foil-size),_200%_400%,_800%,_200%] after:[background-position:center,_0%_var(--bg-y),_calc(var(--bg-x)*_-1)_calc(var(--bg-y)*_-1),_var(--bg-x)_var(--bg-y)] after:[background-blend-mode:soft-light,_hue,_hard-light]"
+ style={{ ...backgroundStyle, opacity: "10%" }}
+ />
+ </div>
+ </div>
+ );
+};
diff --git a/apps/web/app/(landing)/Cta.tsx b/apps/web/app/(landing)/Cta.tsx
index 67a07e86..e7888c05 100644
--- a/apps/web/app/(landing)/Cta.tsx
+++ b/apps/web/app/(landing)/Cta.tsx
@@ -4,49 +4,49 @@ import { ArrowUpRight, ChevronRight } from "lucide-react";
import Link from "next/link";
function Cta() {
- return (
- <section
- id="waitlist"
- className="relative bg-page-gradient dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset] min-h-[600px] border-[1px] border-white/20 flex flex-col gap-8 justify-center items-center mt-32 mb-28 w-full md:w-3/4 rounded-3xl py-10 px-3 md:px-8 mx-auto"
- >
- <div className="absolute -z-1 inset-0 rounded-3xl opacity-5 h-[600px] w-full bg-transparent bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]"></div>
- <div className="absolute top-0 z-0 w-screen right-0 mx-auto h-[500px] overflow-hidden bg-inherit bg-[radial-gradient(ellipse_20%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"></div>
+ return (
+ <section
+ id="waitlist"
+ className="relative bg-page-gradient dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset] min-h-[600px] border-[1px] border-white/20 flex flex-col gap-8 justify-center items-center mt-32 mb-28 w-full md:w-3/4 rounded-3xl py-10 px-3 md:px-8 mx-auto"
+ >
+ <div className="absolute -z-1 inset-0 rounded-3xl opacity-5 h-[600px] w-full bg-transparent bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]"></div>
+ <div className="absolute top-0 z-0 w-screen right-0 mx-auto h-[500px] overflow-hidden bg-inherit bg-[radial-gradient(ellipse_20%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"></div>
- <div className="absolute left-0 w-full h-full z-[-1]">
- {/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/}
- <div className="overflow-hidden">
- <div
- className="absolute left-[20%] top-[-165%] h-32 w-full overflow-hidden bg-[#369DFD] bg-opacity-70 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- </div>
- <h1 className="text-sm z-20 text-gray-400 group font-geist mx-auto px-5 py-2 bg-gradient-to-tr from-zinc-300/5 via-gray-400/5 to-transparent border-[2px] border-white/5 rounded-3xl w-fit">
- <p className="tracking-tight uppercase">
- Launching July 1st, 2024
- <ChevronRight className="inline w-4 h-4 ml-2 group-hover:translate-x-1 duration-300" />
- </p>
- </h1>
- <h1 className="z-20 mx-auto mt-0 max-w-xl font-normal tracking-tighter text-4xl sm:text-5xl md:text-6xl lg:text-7xl text-center text-transparent bg-clip-text bg-gradient-to-tr from-zinc-400/50 to-white/60 via-white">
- Your bookmarks are collecting dust.
- </h1>
- <p className="z-20 text-center text-md md:text-lg">
- Sign up for the waitlist and be the first to try Supermemory
- </p>
- <div className="w-fit mx-auto">
- <Link
- href="/signin"
- className="flex mx-auto w-fit gap-x-2 justify-center items-center py-3 px-5 ml-3 rounded-3xl border duration-200 group bg-page-gradient border-white/30 text-md font-geistSans hover:border-zinc-600 hover:bg-transparent/10 hover:text-zinc-100 text-white z-[1] relative"
- >
- Sign in
- <div className="flex overflow-hidden relative justify-center items-center ml-1 w-5 h-5">
- <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
- <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
- </div>
- </Link>
- </div>
- </section>
- );
+ <div className="absolute left-0 w-full h-full z-[-1]">
+ {/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/}
+ <div className="overflow-hidden">
+ <div
+ className="absolute left-[20%] top-[-165%] h-32 w-full overflow-hidden bg-[#369DFD] bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ </div>
+ <h1 className="text-sm z-20 text-gray-400 group font-geist mx-auto px-5 py-2 bg-gradient-to-tr from-zinc-300/5 via-gray-400/5 to-transparent border-[2px] border-white/5 rounded-3xl w-fit">
+ <p className="tracking-tight uppercase">
+ Launching July 1st, 2024
+ <ChevronRight className="inline w-4 h-4 ml-2 group-hover:translate-x-1 duration-300" />
+ </p>
+ </h1>
+ <h1 className="z-20 mx-auto mt-0 max-w-xl font-normal tracking-tighter text-4xl sm:text-5xl md:text-6xl lg:text-7xl text-center text-transparent bg-clip-text bg-gradient-to-tr from-zinc-400/50 to-white/60 via-white">
+ Your bookmarks are collecting dust.
+ </h1>
+ <p className="z-20 text-center text-md md:text-lg">
+ Sign up for the waitlist and be the first to try Supermemory
+ </p>
+ <div className="w-fit mx-auto">
+ <Link
+ href="/signin"
+ className="flex mx-auto w-fit gap-x-2 justify-center items-center py-3 px-5 ml-3 rounded-3xl border duration-200 group bg-page-gradient border-white/30 text-md font-geistSans hover:border-zinc-600 hover:bg-transparent/10 hover:text-zinc-100 text-white z-[1] relative"
+ >
+ Sign in
+ <div className="flex overflow-hidden relative justify-center items-center ml-1 w-5 h-5">
+ <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
+ <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
+ </div>
+ </Link>
+ </div>
+ </section>
+ );
}
export default Cta;
diff --git a/apps/web/app/(landing)/EmailInput.tsx b/apps/web/app/(landing)/EmailInput.tsx
index 936b8835..71ec4801 100644
--- a/apps/web/app/(landing)/EmailInput.tsx
+++ b/apps/web/app/(landing)/EmailInput.tsx
@@ -6,80 +6,80 @@ import { useToast } from "@repo/ui/shadcn/use-toast";
import { Loader } from "lucide-react";
function EmailInput() {
- const [email, setEmail] = useState("");
- const { toast } = useToast();
- const [loading, setLoading] = useState(false);
+ const [email, setEmail] = useState("");
+ const { toast } = useToast();
+ const [loading, setLoading] = useState(false);
- return (
- <form
- onSubmit={async (e: FormEvent<HTMLFormElement>) => {
- e.preventDefault();
- setLoading(true);
- const value = await formSubmitAction(email, "token" as string);
+ return (
+ <form
+ onSubmit={async (e: FormEvent<HTMLFormElement>) => {
+ e.preventDefault();
+ setLoading(true);
+ const value = await formSubmitAction(email, "token" as string);
- if (value.success) {
- setEmail("");
- toast({
- title: "You are now on the waitlist! 🎉",
- description:
- "We will notify you when we launch. Check your inbox and spam folder for a surprise! 🎁",
- });
- } else {
- console.error("email submission failed: ", value.value);
- toast({
- variant: "destructive",
- title: "Uh oh! Something went wrong.",
- description: `${value.value}`,
- });
- }
- setLoading(false);
- }}
- className="flex w-full items-center justify-center gap-2"
- >
- <div
- className={`transition-width z-20 rounded-2xl bg-glass-gradient backdrop-blur-sm dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset] border-[1px] border-white/5 from-gray-200/70 to-transparent p-[0] duration-300 ease-in-out ${email ? "w-[90%] md:w-[42%]" : "w-full md:w-1/2"}`}
- >
- <input
- type="email"
- name="email"
- className={`transition-width py-4 bg-page-gradient flex w-full items-center rounded-xl border-white/5 bg-transparent px-4 outline-none duration-300 focus:outline-none`}
- placeholder="Enter your email"
- value={email}
- required
- onChange={(e) => setEmail(e.target.value)}
- />
- </div>
- <div
- className="cf-turnstile"
- data-sitekey="0x4AAAAAAAakohhUeXc99J7E"
- ></div>
- {email && (
- <button
- type="submit"
- className="transition-width rounded-xl z-20 bg-gray-700 p-4 text-white duration-300"
- >
- {loading ? (
- <Loader className="h-6 w-6 animate-spin" />
- ) : (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="h-6 w-6"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
- />
- </svg>
- )}
- </button>
- )}
- </form>
- );
+ if (value.success) {
+ setEmail("");
+ toast({
+ title: "You are now on the waitlist! 🎉",
+ description:
+ "We will notify you when we launch. Check your inbox and spam folder for a surprise! 🎁",
+ });
+ } else {
+ console.error("email submission failed: ", value.value);
+ toast({
+ variant: "destructive",
+ title: "Uh oh! Something went wrong.",
+ description: `${value.value}`,
+ });
+ }
+ setLoading(false);
+ }}
+ className="flex w-full items-center justify-center gap-2"
+ >
+ <div
+ className={`transition-width z-20 rounded-2xl bg-glass-gradient backdrop-blur-sm dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset] border-[1px] border-white/5 from-gray-200/70 to-transparent p-[0] duration-300 ease-in-out ${email ? "w-[90%] md:w-[42%]" : "w-full md:w-1/2"}`}
+ >
+ <input
+ type="email"
+ name="email"
+ className={`transition-width py-4 bg-page-gradient flex w-full items-center rounded-xl border-white/5 bg-transparent px-4 outline-none duration-300 focus:outline-none`}
+ placeholder="Enter your email"
+ value={email}
+ required
+ onChange={(e) => setEmail(e.target.value)}
+ />
+ </div>
+ <div
+ className="cf-turnstile"
+ data-sitekey="0x4AAAAAAAakohhUeXc99J7E"
+ ></div>
+ {email && (
+ <button
+ type="submit"
+ className="transition-width rounded-xl z-20 bg-gray-700 p-4 text-white duration-300"
+ >
+ {loading ? (
+ <Loader className="h-6 w-6 animate-spin" />
+ ) : (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ className="h-6 w-6"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
+ />
+ </svg>
+ )}
+ </button>
+ )}
+ </form>
+ );
}
export default EmailInput;
diff --git a/apps/web/app/(landing)/FeatureCardContent.tsx b/apps/web/app/(landing)/FeatureCardContent.tsx
index 26772141..95903193 100644
--- a/apps/web/app/(landing)/FeatureCardContent.tsx
+++ b/apps/web/app/(landing)/FeatureCardContent.tsx
@@ -4,99 +4,102 @@ import React from "react";
import { MessageCircle, Search } from "lucide-react";
export default function FUIFeatureSectionWithCards() {
- const features = [
- {
- icon: <Twitter className="w-5 h-5" />,
- title: "Twitter Bookmarks",
- desc: "Use all the knowledge you've saved on Twitter to train your own supermemory..",
- },
- {
- icon: <Search className="w-5 h-5" />,
- title: "Powerful search",
- desc: "Look up anything you've saved in your supermemory, and get the information you need in seconds.",
- },
- {
- icon: <MessageCircle className="w-5 h-5" />,
- title: "Chat with collections",
- desc: "Use collections to talk to specific knowledgebases like 'My twitter bookmarks', or 'Learning web development'",
- },
- {
- icon: (
- <svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="14" height="16">
- <path d="M13 0H1C.4 0 0 .4 0 1v14c0 .6.4 1 1 1h8l5-5V1c0-.6-.4-1-1-1ZM2 2h10v8H8v4H2V2Z" />
- </svg>
- ),
- title: "Knowledge canvas",
- desc: " Arrange your saved information in a way that makes sense to youin a 2d canvas.",
- },
- {
- icon: (
- <svg
- fill="currentColor"
- xmlns="http://www.w3.org/2000/svg"
- width="16"
- height="16"
- >
- <path d="M14.6.085 8 2.885 1.4.085c-.5-.2-1.4-.1-1.4.9v11c0 .4.2.8.6.9l7 3c.3.1.5.1.8 0l7-3c.4-.2.6-.5.6-.9v-11c0-1-.9-1.1-1.4-.9ZM2 2.485l5 2.1v8.8l-5-2.1v-8.8Zm12 8.8-5 2.1v-8.7l5-2.1v8.7Z" />
- </svg>
- ),
- title: "Just... bookmarks",
- desc: "AI is cool, but sometimes you just need a place to save your stuff. Supermemory is that place.",
- },
- {
- icon: (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- className="w-5 h-"
- >
- <path d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z" />
- </svg>
- ),
- title: "Writing assistant",
- desc: " Use our markdown editor to write content based on your saved data, with the help of AI.",
- },
- ];
+ const features = [
+ {
+ icon: <Twitter className="w-5 h-5" />,
+ title: "Twitter Bookmarks",
+ desc: "Use all the knowledge you've saved on Twitter to train your own supermemory..",
+ },
+ {
+ icon: <Search className="w-5 h-5" />,
+ title: "Powerful search",
+ desc: "Look up anything you've saved in your supermemory, and get the information you need in seconds.",
+ },
+ {
+ icon: <MessageCircle className="w-5 h-5" />,
+ title: "Chat with collections",
+ desc: "Use collections to talk to specific knowledgebases like 'My twitter bookmarks', or 'Learning web development'",
+ },
+ {
+ icon: (
+ <svg
+ fill="currentColor"
+ xmlns="http://www.w3.org/2000/svg"
+ width="14"
+ height="16"
+ >
+ <path d="M13 0H1C.4 0 0 .4 0 1v14c0 .6.4 1 1 1h8l5-5V1c0-.6-.4-1-1-1ZM2 2h10v8H8v4H2V2Z" />
+ </svg>
+ ),
+ title: "Knowledge canvas",
+ desc: " Arrange your saved information in a way that makes sense to youin a 2d canvas.",
+ },
+ {
+ icon: (
+ <svg
+ fill="currentColor"
+ xmlns="http://www.w3.org/2000/svg"
+ width="16"
+ height="16"
+ >
+ <path d="M14.6.085 8 2.885 1.4.085c-.5-.2-1.4-.1-1.4.9v11c0 .4.2.8.6.9l7 3c.3.1.5.1.8 0l7-3c.4-.2.6-.5.6-.9v-11c0-1-.9-1.1-1.4-.9ZM2 2.485l5 2.1v8.8l-5-2.1v-8.8Zm12 8.8-5 2.1v-8.7l5-2.1v8.7Z" />
+ </svg>
+ ),
+ title: "Just... bookmarks",
+ desc: "AI is cool, but sometimes you just need a place to save your stuff. Supermemory is that place.",
+ },
+ {
+ icon: (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ className="w-5 h-"
+ >
+ <path d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z" />
+ </svg>
+ ),
+ title: "Writing assistant",
+ desc: " Use our markdown editor to write content based on your saved data, with the help of AI.",
+ },
+ ];
- return (
- <section className="relative z-20 pb-14">
- <div className="px-4 mx-auto max-w-screen-xl text-gray-400 md:px-8 lg:px-0">
- <div className="relative mx-auto max-w-2xl sm:text-center">
- <div className="relative z-10">
- <h3 className="mt-4 text-3xl font-normal tracking-tighter text-gray-200 sm:text-4xl md:text-5xl font-geist">
- </h3>
-
- </div>
- <div
- className="absolute inset-0 mx-auto max-w-xs h-44 blur-[118px]"
- style={{
- background:
- "linear-gradient(152.92deg, rgba(192, 132, 252, 0.2) 4.54%, rgba(232, 121, 249, 0.26) 34.2%, rgba(192, 132, 252, 0.1) 77.55%)",
- }}
- ></div>
- </div>
- <div className="relative z-20 mt-[4rem]">
- <ul className="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
- {features.map((item, idx) => (
- <GlareCard key={`l-${idx}`}>
- <li
- key={idx}
- className="z-20 transform-gpu space-y-3 rounded-xl border border-white/10 bg-transparent/20 p-4 [border:1px_solid_rgba(255,255,255,.1)] [box-shadow:0_-20px_80px_-20px_#8686f01f_inset]"
- >
- <div className="w-fit transform-gpu rounded-full p-4 text-purple-600 dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset]">
- {item.icon}
- </div>
- <h4 className="text-lg font-bold tracking-tighter text-gray-300 font-geist">
- {item.title}
- </h4>
- <p className="text-gray-500">{item.desc}</p>
- </li>
- </GlareCard>
- ))}
- </ul>
- </div>
- </div>
- </section>
- );
+ return (
+ <section className="relative z-20 pb-14">
+ <div className="px-4 mx-auto max-w-screen-xl text-gray-400 md:px-8 lg:px-0">
+ <div className="relative mx-auto max-w-2xl sm:text-center">
+ <div className="relative z-10">
+ <h3 className="mt-4 text-3xl font-normal tracking-tighter text-gray-200 sm:text-4xl md:text-5xl font-geist"></h3>
+ </div>
+ <div
+ className="absolute inset-0 mx-auto max-w-xs h-44 blur-[118px]"
+ style={{
+ background:
+ "linear-gradient(152.92deg, rgba(192, 132, 252, 0.2) 4.54%, rgba(232, 121, 249, 0.26) 34.2%, rgba(192, 132, 252, 0.1) 77.55%)",
+ }}
+ ></div>
+ </div>
+ <div className="relative z-20 mt-[4rem]">
+ <ul className="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
+ {features.map((item, idx) => (
+ <GlareCard key={`l-${idx}`}>
+ <li
+ key={idx}
+ className="z-20 transform-gpu space-y-3 rounded-xl border border-white/10 bg-transparent/20 p-4 [border:1px_solid_rgba(255,255,255,.1)] [box-shadow:0_-20px_80px_-20px_#8686f01f_inset]"
+ >
+ <div className="w-fit transform-gpu rounded-full p-4 text-purple-600 dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#8686f01f_inset]">
+ {item.icon}
+ </div>
+ <h4 className="text-lg font-bold tracking-tighter text-gray-300 font-geist">
+ {item.title}
+ </h4>
+ <p className="text-gray-500">{item.desc}</p>
+ </li>
+ </GlareCard>
+ ))}
+ </ul>
+ </div>
+ </div>
+ </section>
+ );
}
diff --git a/apps/web/app/(landing)/FeatureContent.tsx b/apps/web/app/(landing)/FeatureContent.tsx
index 7de64d53..3b6db3e0 100644
--- a/apps/web/app/(landing)/FeatureContent.tsx
+++ b/apps/web/app/(landing)/FeatureContent.tsx
@@ -1,59 +1,59 @@
export const features = [
- {
- title: "For Researchers",
- description:
- "Add content to collections and use it as a knowledge base for your research, link multiple sources together to get a better understanding of the topic.",
- svg: <ResearchSvg />,
- },
- {
- title: "For Content writers",
- description:
- "Save time and use the writing assistant to generate content based on your own saved collections and sources.",
- svg: <ContentSvg />,
- },
- {
- title: "For Developers",
- description:
- "Talk to documentation websites, code snippets, etc. so you never have to google the same thing a hundred times.",
- svg: <DeveloperSvg />,
- },
+ {
+ title: "For Researchers",
+ description:
+ "Add content to collections and use it as a knowledge base for your research, link multiple sources together to get a better understanding of the topic.",
+ svg: <ResearchSvg />,
+ },
+ {
+ title: "For Content writers",
+ description:
+ "Save time and use the writing assistant to generate content based on your own saved collections and sources.",
+ svg: <ContentSvg />,
+ },
+ {
+ title: "For Developers",
+ description:
+ "Talk to documentation websites, code snippets, etc. so you never have to google the same thing a hundred times.",
+ svg: <DeveloperSvg />,
+ },
];
function ResearchSvg() {
- return (
- <svg
- className="mr-3 shrink-0 fill-zinc-400"
- xmlns="http://www.w3.org/2000/svg"
- width="24"
- height="24"
- >
- <path d="m7.951 14.537 6.296-7.196 1.506 1.318-7.704 8.804-3.756-3.756 1.414-1.414 2.244 2.244Zm11.296-7.196 1.506 1.318-7.704 8.804-1.756-1.756 1.414-1.414.244.244 6.296-7.196Z" />
- </svg>
- );
+ return (
+ <svg
+ className="mr-3 shrink-0 fill-zinc-400"
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ >
+ <path d="m7.951 14.537 6.296-7.196 1.506 1.318-7.704 8.804-3.756-3.756 1.414-1.414 2.244 2.244Zm11.296-7.196 1.506 1.318-7.704 8.804-1.756-1.756 1.414-1.414.244.244 6.296-7.196Z" />
+ </svg>
+ );
}
function ContentSvg() {
- return (
- <svg
- className="mr-3 shrink-0 fill-zinc-400"
- xmlns="http://www.w3.org/2000/svg"
- width="24"
- height="24"
- >
- <path d="m16.997 19.056-1.78-.912A13.91 13.91 0 0 0 16.75 11.8c0-2.206-.526-4.38-1.533-6.344l1.78-.912A15.91 15.91 0 0 1 18.75 11.8c0 2.524-.602 5.01-1.753 7.256Zm-3.616-1.701-1.77-.93A9.944 9.944 0 0 0 12.75 11.8c0-1.611-.39-3.199-1.14-4.625l1.771-.93c.9 1.714 1.37 3.62 1.369 5.555 0 1.935-.47 3.841-1.369 5.555Zm-3.626-1.693-1.75-.968c.49-.885.746-1.881.745-2.895a5.97 5.97 0 0 0-.745-2.893l1.75-.968a7.968 7.968 0 0 1 .995 3.861 7.97 7.97 0 0 1-.995 3.863Zm-3.673-1.65-1.664-1.11c.217-.325.333-.709.332-1.103 0-.392-.115-.776-.332-1.102L6.082 9.59c.437.655.67 1.425.668 2.21a3.981 3.981 0 0 1-.668 2.212Z" />
- </svg>
- );
+ return (
+ <svg
+ className="mr-3 shrink-0 fill-zinc-400"
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ >
+ <path d="m16.997 19.056-1.78-.912A13.91 13.91 0 0 0 16.75 11.8c0-2.206-.526-4.38-1.533-6.344l1.78-.912A15.91 15.91 0 0 1 18.75 11.8c0 2.524-.602 5.01-1.753 7.256Zm-3.616-1.701-1.77-.93A9.944 9.944 0 0 0 12.75 11.8c0-1.611-.39-3.199-1.14-4.625l1.771-.93c.9 1.714 1.37 3.62 1.369 5.555 0 1.935-.47 3.841-1.369 5.555Zm-3.626-1.693-1.75-.968c.49-.885.746-1.881.745-2.895a5.97 5.97 0 0 0-.745-2.893l1.75-.968a7.968 7.968 0 0 1 .995 3.861 7.97 7.97 0 0 1-.995 3.863Zm-3.673-1.65-1.664-1.11c.217-.325.333-.709.332-1.103 0-.392-.115-.776-.332-1.102L6.082 9.59c.437.655.67 1.425.668 2.21a3.981 3.981 0 0 1-.668 2.212Z" />
+ </svg>
+ );
}
function DeveloperSvg() {
- return (
- <svg
- className="mr-3 shrink-0 fill-zinc-400"
- xmlns="http://www.w3.org/2000/svg"
- width="24"
- height="24"
- >
- <path d="m11.293 5.293 1.414 1.414-8 8-1.414-1.414 8-8Zm7-1 1.414 1.414-8 8-1.414-1.414 8-8Zm0 6 1.414 1.414-8 8-1.414-1.414 8-8Z" />
- </svg>
- );
+ return (
+ <svg
+ className="mr-3 shrink-0 fill-zinc-400"
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ >
+ <path d="m11.293 5.293 1.414 1.414-8 8-1.414-1.414 8-8Zm7-1 1.414 1.414-8 8-1.414-1.414 8-8Zm0 6 1.414 1.414-8 8-1.414-1.414 8-8Z" />
+ </svg>
+ );
}
diff --git a/apps/web/app/(landing)/Features.tsx b/apps/web/app/(landing)/Features.tsx
index 55634d0c..d2ad33c2 100644
--- a/apps/web/app/(landing)/Features.tsx
+++ b/apps/web/app/(landing)/Features.tsx
@@ -5,64 +5,64 @@ import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
export default function Features() {
- const [tab, setTab] = useState<number>(0);
+ const [tab, setTab] = useState<number>(0);
- const tabs = useRef<HTMLDivElement>(null);
+ const tabs = useRef<HTMLDivElement>(null);
- const heightFix = () => {
- if (tabs.current && tabs.current.parentElement)
- tabs.current.parentElement.style.height = `${tabs.current.clientHeight}px`;
- };
+ const heightFix = () => {
+ if (tabs.current && tabs.current.parentElement)
+ tabs.current.parentElement.style.height = `${tabs.current.clientHeight}px`;
+ };
- function handleClickIndex(tab: number) {
- setTab(tab);
- }
+ function handleClickIndex(tab: number) {
+ setTab(tab);
+ }
- useEffect(() => {
- heightFix();
- }, []);
+ useEffect(() => {
+ heightFix();
+ }, []);
- return (
- <section
- id="features"
- className="overflow-hidden relative w-full max-lg:after:hidden mt-10"
- >
- <img
- src="/images/tailwind-bg-gradient.avif"
- className="absolute -top-0 left-10 opacity-40 z-2"
- />
- <div className="relative ">
- <div className="flex relative flex-col px-4 mx-auto max-w-screen-xl md:px-0">
- <div className="relative mx-auto mb-5 space-y-4 max-w-3xl text-center">
- <h2 className="pt-16 text-4xl tracking-tighter text-transparent bg-clip-text bg-gradient-to-tr via-white md:text-5xl lg:text-6xl font-nomral font-geist from-zinc-400/50 to-white/60">
- A "Second brain" made for you... or your team
- </h2>
+ return (
+ <section
+ id="features"
+ className="overflow-hidden relative w-full max-lg:after:hidden mt-10"
+ >
+ <img
+ src="/images/tailwind-bg-gradient.avif"
+ className="absolute -top-0 left-10 opacity-40 z-2"
+ />
+ <div className="relative ">
+ <div className="flex relative flex-col px-4 mx-auto max-w-screen-xl md:px-0">
+ <div className="relative mx-auto mb-5 space-y-4 max-w-3xl text-center">
+ <h2 className="pt-16 text-4xl tracking-tighter text-transparent bg-clip-text bg-gradient-to-tr via-white md:text-5xl lg:text-6xl font-nomral font-geist from-zinc-400/50 to-white/60">
+ A "Second brain" made for you... or your team
+ </h2>
- <p className="text-zinc-400">
- Supermemory offers all the vital building blocks you need to
- transform your gold mine of content into a powerful knowledgebase
- for yourself, your team or even a group of friends!
- </p>
- <Link
- href="/signin"
- className="mx-auto flex gap-2 justify-center items-center py-2 px-10 mt-4 text-lg tracking-tighter text-center bg-gradient-to-br rounded-md ring-2 ring-offset-1 transition-all hover:ring-transparent group w-fit font-geist bg-page-gradient text-md from-zinc-400 to-zinc-700 text-zinc-50 ring-zinc-500/50 ring-offset-zinc-950/5 hover:scale-[1.02] active:scale-[0.98] active:ring-zinc-500/70"
- >
- Get Started
- <div className="overflow-hidden relative ml-1 w-5 h-5">
- <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
- <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
- </div>
- </Link>
- </div>
- <FUIFeatureSectionWithCards />
- <div className="overflow-x-hidden overflow-y-hidden">
- <div
- className="absolute left-0 top-[60%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-20 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- </div>
- </div>
- </section>
- );
+ <p className="text-zinc-400">
+ Supermemory offers all the vital building blocks you need to
+ transform your gold mine of content into a powerful knowledgebase
+ for yourself, your team or even a group of friends!
+ </p>
+ <Link
+ href="/signin"
+ className="mx-auto flex gap-2 justify-center items-center py-2 px-10 mt-4 text-lg tracking-tighter text-center bg-gradient-to-br rounded-md ring-2 ring-offset-1 transition-all hover:ring-transparent group w-fit font-geist bg-page-gradient text-md from-zinc-400 to-zinc-700 text-zinc-50 ring-zinc-500/50 ring-offset-zinc-950/5 hover:scale-[1.02] active:scale-[0.98] active:ring-zinc-500/70"
+ >
+ Get Started
+ <div className="overflow-hidden relative ml-1 w-5 h-5">
+ <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
+ <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
+ </div>
+ </Link>
+ </div>
+ <FUIFeatureSectionWithCards />
+ <div className="overflow-x-hidden overflow-y-hidden">
+ <div
+ className="absolute left-0 top-[60%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-20 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ </div>
+ </div>
+ </section>
+ );
}
diff --git a/apps/web/app/(landing)/Features/chatbubble.tsx b/apps/web/app/(landing)/Features/chatbubble.tsx
index 96a3f9b7..ab9b0a07 100644
--- a/apps/web/app/(landing)/Features/chatbubble.tsx
+++ b/apps/web/app/(landing)/Features/chatbubble.tsx
@@ -1,17 +1,23 @@
-const ChatBubbleWing = ({ className, pathClassName }: {className?:string , pathClassName?:string}) => {
- return (
- <svg
- className={`${className || ""}`}
- xmlns="http://www.w3.org/2000/svg"
- width="26"
- height="37"
- >
- <path
- className={`${pathClassName || ""}`}
- d="M21.843 37.001c3.564 0 5.348-4.309 2.829-6.828L3.515 9.015A12 12 0 0 1 0 .53v36.471h21.843z"
- />
- </svg>
- );
- };
-
- export default ChatBubbleWing; \ No newline at end of file
+const ChatBubbleWing = ({
+ className,
+ pathClassName,
+}: {
+ className?: string;
+ pathClassName?: string;
+}) => {
+ return (
+ <svg
+ className={`${className || ""}`}
+ xmlns="http://www.w3.org/2000/svg"
+ width="26"
+ height="37"
+ >
+ <path
+ className={`${pathClassName || ""}`}
+ d="M21.843 37.001c3.564 0 5.348-4.309 2.829-6.828L3.515 9.015A12 12 0 0 1 0 .53v36.471h21.843z"
+ />
+ </svg>
+ );
+};
+
+export default ChatBubbleWing;
diff --git a/apps/web/app/(landing)/Features/features.tsx b/apps/web/app/(landing)/Features/features.tsx
index f62ce3b9..b48209da 100644
--- a/apps/web/app/(landing)/Features/features.tsx
+++ b/apps/web/app/(landing)/Features/features.tsx
@@ -1,17 +1,17 @@
import { cn } from "@repo/ui/lib/utils";
export const Gradient = ({ opacity = 50 }: { opacity?: number }) => {
- return (
- <div
- className={cn(
- "absolute top-0 -left-[10rem] w-[56.625rem] h-[56.625rem] mix-blend-color-dodge",
- `opacity-${opacity}`,
- )}
- >
- <div
- className="top-0 -left-[10rem] w-[56.625rem] h-[56.625rem] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-40 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- );
+ return (
+ <div
+ className={cn(
+ "absolute top-0 -left-[10rem] w-[56.625rem] h-[56.625rem] mix-blend-color-dodge",
+ `opacity-${opacity}`,
+ )}
+ >
+ <div
+ className="top-0 -left-[10rem] w-[56.625rem] h-[56.625rem] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-40 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ );
};
diff --git a/apps/web/app/(landing)/Features/index.tsx b/apps/web/app/(landing)/Features/index.tsx
index d7c606e1..7f1a4ccb 100644
--- a/apps/web/app/(landing)/Features/index.tsx
+++ b/apps/web/app/(landing)/Features/index.tsx
@@ -222,8 +222,8 @@ const Services = () => {
export default Services;
const supermemoryPoints = [
- "Privacy focused",
- "Works everywhere you are",
- "Self hostable",
- "Super affordable, with a generous free tier",
+ "Privacy focused",
+ "Works everywhere you are",
+ "Self hostable",
+ "Super affordable, with a generous free tier",
];
diff --git a/apps/web/app/(landing)/GridPatterns/PlusGrid.tsx b/apps/web/app/(landing)/GridPatterns/PlusGrid.tsx
index 937ac904..5517e5b3 100644
--- a/apps/web/app/(landing)/GridPatterns/PlusGrid.tsx
+++ b/apps/web/app/(landing)/GridPatterns/PlusGrid.tsx
@@ -1,45 +1,45 @@
interface PlusPatternBackgroundProps {
- plusSize?: number;
- plusColor?: string;
- backgroundColor?: string;
- className?: string;
- style?: React.CSSProperties;
- fade?: boolean;
- [key: string]: any;
+ plusSize?: number;
+ plusColor?: string;
+ backgroundColor?: string;
+ className?: string;
+ style?: React.CSSProperties;
+ fade?: boolean;
+ [key: string]: any;
}
export const BackgroundPlus: React.FC<PlusPatternBackgroundProps> = ({
- plusColor = "#6b6b6b",
- backgroundColor = "transparent",
- plusSize = 60,
- className,
- fade = true,
- style,
- ...props
+ plusColor = "#6b6b6b",
+ backgroundColor = "transparent",
+ plusSize = 60,
+ className,
+ fade = true,
+ style,
+ ...props
}) => {
- const encodedPlusColor = encodeURIComponent(plusColor);
+ const encodedPlusColor = encodeURIComponent(plusColor);
- const maskStyle: React.CSSProperties = fade
- ? {
- maskImage: "radial-gradient(circle, white 10%, transparent 90%)",
- WebkitMaskImage: "radial-gradient(circle, white 10%, transparent 90%)",
- }
- : {};
+ const maskStyle: React.CSSProperties = fade
+ ? {
+ maskImage: "radial-gradient(circle, white 10%, transparent 90%)",
+ WebkitMaskImage: "radial-gradient(circle, white 10%, transparent 90%)",
+ }
+ : {};
- const backgroundStyle: React.CSSProperties = {
- backgroundColor,
- backgroundImage: `url("data:image/svg+xml,%3Csvg width='${plusSize}' height='${plusSize}' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='${encodedPlusColor}' fill-opacity='0.2'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
- ...maskStyle,
- ...style,
- };
+ const backgroundStyle: React.CSSProperties = {
+ backgroundColor,
+ backgroundImage: `url("data:image/svg+xml,%3Csvg width='${plusSize}' height='${plusSize}' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='${encodedPlusColor}' fill-opacity='0.2'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
+ ...maskStyle,
+ ...style,
+ };
- return (
- <div
- className={`absolute inset-0 h-full w-full ${className}`}
- style={backgroundStyle}
- {...props}
- />
- );
+ return (
+ <div
+ className={`absolute inset-0 h-full w-full ${className}`}
+ style={backgroundStyle}
+ {...props}
+ />
+ );
};
export default BackgroundPlus;
diff --git a/apps/web/app/(landing)/Headers/Navbar.tsx b/apps/web/app/(landing)/Headers/Navbar.tsx
index 0225ea0e..d783244a 100644
--- a/apps/web/app/(landing)/Headers/Navbar.tsx
+++ b/apps/web/app/(landing)/Headers/Navbar.tsx
@@ -5,116 +5,116 @@ import Image from "next/image";
import Link from "next/link";
import { ArrowUpRight } from "lucide-react";
export const SlideNavTabs = () => {
- return (
- <div className="fixed right-0 left-0 top-5 z-30 mx-auto text-white bg-transparent">
- <SlideTabs />
- </div>
- );
+ return (
+ <div className="fixed right-0 left-0 top-5 z-30 mx-auto text-white bg-transparent">
+ <SlideTabs />
+ </div>
+ );
};
const SlideTabs = () => {
- const [position, setPosition] = useState({
- left: 0,
- width: 0,
- opacity: 0,
- });
+ const [position, setPosition] = useState({
+ left: 0,
+ width: 0,
+ opacity: 0,
+ });
- return (
- <ul
- onMouseLeave={() => {
- setPosition((pv) => ({
- ...pv,
- opacity: 0,
- }));
- }}
- className="flex relative items-center py-3 px-5 mx-auto text-sm text-gray-200 bg-gradient-to-tr to-transparent rounded-full border-2 w-fit border-white/5 from-zinc-300/5 via-gray-400/5 shadow-[0px_2px_3px_-1px_rgba(0,0,0,0.1),0px_1px_0px_0px_rgba(25,28,33,0.02),0px_0px_0px_1px_rgba(25,28,33,0.08)] backdrop-blur-lg backdrop-filter"
- >
- <Link href={"/"} className="flex items-start mr-4 opacity-50 h-fit">
- <Image src={Logo} alt="Supermemory logo" width={40} height={40} />
- </Link>
+ return (
+ <ul
+ onMouseLeave={() => {
+ setPosition((pv) => ({
+ ...pv,
+ opacity: 0,
+ }));
+ }}
+ className="flex relative items-center py-3 px-5 mx-auto text-sm text-gray-200 bg-gradient-to-tr to-transparent rounded-full border-2 w-fit border-white/5 from-zinc-300/5 via-gray-400/5 shadow-[0px_2px_3px_-1px_rgba(0,0,0,0.1),0px_1px_0px_0px_rgba(25,28,33,0.02),0px_0px_0px_1px_rgba(25,28,33,0.08)] backdrop-blur-lg backdrop-filter"
+ >
+ <Link href={"/"} className="flex items-start mr-4 opacity-50 h-fit">
+ <Image src={Logo} alt="Supermemory logo" width={40} height={40} />
+ </Link>
- <Tab key={0} setPosition={setPosition}>
- <Link className="w-full h-full" href={"/home"}>
- Home
- </Link>
- </Tab>
- <Tab setPosition={setPosition}>
- <Link className="w-full h-full" href={"/#use-cases"}>
- Use Cases
- </Link>
- </Tab>
- <Tab setPosition={setPosition}>
- <Link className="w-full h-full" href={"/#features"}>
- Features
- </Link>
- </Tab>
- <Tab setPosition={setPosition}>
- <Link
- className="w-full h-full"
- href={"https://github.com/Dhravya/supermemory/graphs/contributors"}
- >
- Team
- </Link>
- </Tab>
+ <Tab key={0} setPosition={setPosition}>
+ <Link className="w-full h-full" href={"/home"}>
+ Home
+ </Link>
+ </Tab>
+ <Tab setPosition={setPosition}>
+ <Link className="w-full h-full" href={"/#use-cases"}>
+ Use Cases
+ </Link>
+ </Tab>
+ <Tab setPosition={setPosition}>
+ <Link className="w-full h-full" href={"/#features"}>
+ Features
+ </Link>
+ </Tab>
+ <Tab setPosition={setPosition}>
+ <Link
+ className="w-full h-full"
+ href={"https://github.com/Dhravya/supermemory/graphs/contributors"}
+ >
+ Team
+ </Link>
+ </Tab>
- <Link
- href="https://git.new/memory"
- className="inline-flex gap-x-2 justify-start items-start py-3 px-5 ml-3 w-full rounded-3xl border duration-200 sm:w-auto group bg-page-gradient border-white/30 text-md font-geistSans hover:border-zinc-600 hover:bg-transparent/10 hover:text-zinc-100"
- >
- Github
- <div className="flex overflow-hidden relative justify-center items-center ml-1 w-5 h-5">
- <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
- <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
- </div>
- </Link>
+ <Link
+ href="https://git.new/memory"
+ className="inline-flex gap-x-2 justify-start items-start py-3 px-5 ml-3 w-full rounded-3xl border duration-200 sm:w-auto group bg-page-gradient border-white/30 text-md font-geistSans hover:border-zinc-600 hover:bg-transparent/10 hover:text-zinc-100"
+ >
+ Github
+ <div className="flex overflow-hidden relative justify-center items-center ml-1 w-5 h-5">
+ <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
+ <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
+ </div>
+ </Link>
- <Cursor position={position} />
- </ul>
- );
+ <Cursor position={position} />
+ </ul>
+ );
};
const Tab = ({
- children,
- setPosition,
+ children,
+ setPosition,
}: {
- children: React.ReactNode;
- setPosition: ({
- left,
- width,
- opacity,
- }: {
- left: number;
- width: number;
- opacity: number;
- }) => void;
+ children: React.ReactNode;
+ setPosition: ({
+ left,
+ width,
+ opacity,
+ }: {
+ left: number;
+ width: number;
+ opacity: number;
+ }) => void;
}) => {
- const ref = useRef<HTMLLIElement>(null);
+ const ref = useRef<HTMLLIElement>(null);
- return (
- <li
- ref={ref}
- onMouseEnter={() => {
- if (!ref?.current) return;
- const { width } = ref.current.getBoundingClientRect();
- setPosition({
- left: ref.current.offsetLeft,
- width,
- opacity: 1,
- });
- }}
- className="block relative z-10 py-2.5 px-3 text-xs text-white cursor-pointer md:py-2 md:px-5 md:text-base mix-blend-difference"
- >
- {children}
- </li>
- );
+ return (
+ <li
+ ref={ref}
+ onMouseEnter={() => {
+ if (!ref?.current) return;
+ const { width } = ref.current.getBoundingClientRect();
+ setPosition({
+ left: ref.current.offsetLeft,
+ width,
+ opacity: 1,
+ });
+ }}
+ className="block relative z-10 py-2.5 px-3 text-xs text-white cursor-pointer md:py-2 md:px-5 md:text-base mix-blend-difference"
+ >
+ {children}
+ </li>
+ );
};
// @ts-ignore
const Cursor = ({ position }) => {
- return (
- <motion.li
- animate={{
- ...position,
- }}
- className="absolute z-0 h-7 bg-glass-gradient bg-transparent rounded-full md:h-10 shadow-[0px_2px_3px_-1px_rgba(0,0,0,0.1),0px_1px_0px_0px_rgba(25,28,33,0.02),0px_0px_0px_1px_rgba(25,28,33,0.08)] backdrop-blur-lg backdrop-filter"
- />
- );
+ return (
+ <motion.li
+ animate={{
+ ...position,
+ }}
+ className="absolute z-0 h-7 bg-glass-gradient bg-transparent rounded-full md:h-10 shadow-[0px_2px_3px_-1px_rgba(0,0,0,0.1),0px_1px_0px_0px_rgba(25,28,33,0.02),0px_0px_0px_1px_rgba(25,28,33,0.08)] backdrop-blur-lg backdrop-filter"
+ />
+ );
};
diff --git a/apps/web/app/(landing)/Hero.tsx b/apps/web/app/(landing)/Hero.tsx
index 4a51373a..4a9a8e04 100644
--- a/apps/web/app/(landing)/Hero.tsx
+++ b/apps/web/app/(landing)/Hero.tsx
@@ -10,81 +10,81 @@ import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
const slap = {
- initial: {
- opacity: 0,
- scale: 1.1,
- },
- whileInView: { opacity: 1, scale: 1 },
- transition: {
- duration: 0.5,
- ease: "easeInOut",
- },
- viewport: { once: true },
+ initial: {
+ opacity: 0,
+ scale: 1.1,
+ },
+ whileInView: { opacity: 1, scale: 1 },
+ transition: {
+ duration: 0.5,
+ ease: "easeInOut",
+ },
+ viewport: { once: true },
};
function Hero() {
- return (
- <>
- <section className="flex relative flex-col gap-5 justify-center items-center mt-24 max-w-xl md:mt-32 md:max-w-2xl lg:max-w-3xl">
- <TwitterBorder />
- <motion.h1
- {...{
- ...slap,
- transition: { ...slap.transition, delay: 0.2 },
- }}
- className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter sm:text-5xl text-transparent md:text-6xl lg:text-7xl"
- >
- Unlock your{" "}
- <span className="text-transparent bg-clip-text bg-gradient-to-r to-blue-200 from-zinc-300">
- digital brain
- </span>{" "}
- with supermemory
- </motion.h1>
- <motion.p
- {...{
- ...slap,
- transition: { ...slap.transition, delay: 0.3 },
- }}
- className="text-lg text-center text-soft-foreground-text"
- >
- Supermemory is your ultimate hub for organizing, searching, and
- utilizing saved information with powerful tools like a search engine,
- writing assistant, and canvas.
- </motion.p>
- <Link
- href="/signin"
- className="inline-flex text-lg gap-x-2 mt-2 backdrop-blur-md text-white justify-center items-center py-3 px-5 ml-3 w-fit rounded-3xl border duration-200 group bg-page-gradient border-white/30 text-md font-geistSans hover:border-zinc-600 hover:bg-transparent/10 hover:text-zinc-100"
- >
- It's free. Sign up now
- <div className="flex overflow-hidden relative justify-center items-center ml-1 w-5 h-5">
- <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
- <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
- </div>
- </Link>
- </section>
+ return (
+ <>
+ <section className="flex relative flex-col gap-5 justify-center items-center mt-24 max-w-xl md:mt-32 md:max-w-2xl lg:max-w-3xl">
+ <TwitterBorder />
+ <motion.h1
+ {...{
+ ...slap,
+ transition: { ...slap.transition, delay: 0.2 },
+ }}
+ className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter sm:text-5xl text-transparent md:text-6xl lg:text-7xl"
+ >
+ Unlock your{" "}
+ <span className="text-transparent bg-clip-text bg-gradient-to-r to-blue-200 from-zinc-300">
+ digital brain
+ </span>{" "}
+ with supermemory
+ </motion.h1>
+ <motion.p
+ {...{
+ ...slap,
+ transition: { ...slap.transition, delay: 0.3 },
+ }}
+ className="text-lg text-center text-soft-foreground-text"
+ >
+ Supermemory is your ultimate hub for organizing, searching, and
+ utilizing saved information with powerful tools like a search engine,
+ writing assistant, and canvas.
+ </motion.p>
+ <Link
+ href="/signin"
+ className="inline-flex text-lg gap-x-2 mt-2 backdrop-blur-md text-white justify-center items-center py-3 px-5 ml-3 w-fit rounded-3xl border duration-200 group bg-page-gradient border-white/30 text-md font-geistSans hover:border-zinc-600 hover:bg-transparent/10 hover:text-zinc-100"
+ >
+ It's free. Sign up now
+ <div className="flex overflow-hidden relative justify-center items-center ml-1 w-5 h-5">
+ <ArrowUpRight className="absolute transition-all duration-500 group-hover:translate-x-4 group-hover:-translate-y-5" />
+ <ArrowUpRight className="absolute transition-all duration-500 -translate-x-4 -translate-y-5 group-hover:translate-x-0 group-hover:translate-y-0" />
+ </div>
+ </Link>
+ </section>
- <AnimatedLogoCloud />
- <div className="relative z-50">
- <motion.img
- {...{
- ...slap,
- transition: { ...slap.transition, delay: 0.35 },
- }}
- src="/images/landing-hero.jpeg"
- alt="Landing page background"
- draggable="false"
- className="z-40 md:mt-[-40px] hidden sm:block h-full max-w-[70vw] mx-auto md:w-full select-none px-5 !rounded-2xl"
- style={{
- borderRadius: "20px",
- }}
- />
- <div
- className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- </>
- );
+ <AnimatedLogoCloud />
+ <div className="relative z-50">
+ <motion.img
+ {...{
+ ...slap,
+ transition: { ...slap.transition, delay: 0.35 },
+ }}
+ src="/images/landing-hero.jpeg"
+ alt="Landing page background"
+ draggable="false"
+ className="z-40 md:mt-[-40px] hidden sm:block h-full max-w-[70vw] mx-auto md:w-full select-none px-5 !rounded-2xl"
+ style={{
+ borderRadius: "20px",
+ }}
+ />
+ <div
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ </>
+ );
}
export default Hero;
diff --git a/apps/web/app/(landing)/ImageSliders.tsx b/apps/web/app/(landing)/ImageSliders.tsx
index 8f1dd54d..637cd6f2 100644
--- a/apps/web/app/(landing)/ImageSliders.tsx
+++ b/apps/web/app/(landing)/ImageSliders.tsx
@@ -1,89 +1,89 @@
import {
- // Github,
- Medium,
- Notion,
- Reddit,
- Twitter,
+ // Github,
+ Medium,
+ Notion,
+ Reddit,
+ Twitter,
} from "@repo/ui/components/icons";
import Image from "next/image";
const Github = ({ className }: { className?: string }) => {
- return (
- <svg
- fill="#ffffff"
- className={className}
- version="1.1"
- id="svg2"
- xmlns="http://www.w3.org/2000/svg"
- x="0"
- y="0"
- viewBox="0 0 2500 676.1"
- xmlSpace="preserve"
- >
- <g id="layer1" transform="translate(-85.59 -376.905)">
- <g id="g3012" transform="matrix(1.25 0 0 -1.25 85.59 539.248)">
- <g id="g3014" transform="scale(.1)">
- <g id="g3016">
- <g id="g3018">
- <path
- id="path3024"
- className="st0"
- d="M3852.1-1016.2H2171.4c-43.4 0-78.5-35.2-78.5-78.6v-821.7c0-43.4 35.2-78.7 78.5-78.7H2827V-3016s-147.2-50.2-554.2-50.2c-480.2 0-1150.9 175.5-1150.9 1650.5 0 1475.3 698.5 1669.4 1354.2 1669.4 567.6 0 812.2-99.9 967.8-148.1 48.9-15 94.1 33.7 94.1 77.1l187.5 793.9c0 20.3-6.9 44.7-30 61.3-63.2 45.1-448.7 260.8-1422.6 260.8C1150.9 1298.8 0 821.4 0-1473.3S1317.6-4110 2428-4110c919.4 0 1477.1 392.9 1477.1 392.9 23 12.7 25.5 44.8 25.5 59.6v2562.7c0 43.4-35.2 78.6-78.5 78.6"
- />
- <path
- id="path3026"
- className="st0"
- d="M12513.4 1023.8c0 43.7-34.6 79-78 79h-946.3c-43.2 0-78.4-35.3-78.4-79l.3-1828.8H9936v1828.8c0 43.7-34.8 79-78.1 79h-946.3c-43.1 0-78.2-35.3-78.2-79V-3928c0-43.7 35.1-79.2 78.2-79.2h946.3c43.3 0 78.1 35.6 78.1 79.2v2118h1475l-2.6-2118c0-43.7 35.1-79.2 78.4-79.2h948.6c43.4 0 78 35.6 78.1 79.2v4951.8"
- />
- <path
- id="path3028"
- className="st0"
- d="M5637.8 374c0 340.7-273.2 616.1-610.2 616.1-336.7 0-610.1-275.4-610.1-616.1 0-340.4 273.4-616.5 610.1-616.5 337 0 610.2 276.1 610.2 616.5"
- />
- <path
- id="path3030"
- className="st0"
- d="M5570.1-2883.4v2285.8c0 43.4-35 78.9-78.3 78.9h-943.3c-43.3 0-82-44.6-82-88v-3274.8c0-96.3 60-124.9 137.6-124.9H5454c93.2 0 116.1 45.8 116.1 126.4v996.6"
- />
- <path
- id="path3032"
- className="st0"
- d="M16109.8-526.2h-939.1c-43.1 0-78.1-35.5-78.1-79.2v-2428s-238.5-174.5-577.2-174.5c-338.6 0-428.4 153.6-428.4 485.2v2117.4c0 43.7-35 79.2-78.1 79.2h-953.1c-43 0-78.3-35.5-78.3-79.2V-2883c0-984.8 548.8-1225.7 1303.9-1225.7 619.4 0 1118.8 342.2 1118.8 342.2s23.8-180.3 34.5-201.7c10.8-21.3 38.8-42.9 69.1-42.9l606.3 2.7c43 0 78.3 35.6 78.3 79l-.3 3324.1c.1 43.6-35 79.1-78.3 79.1"
- />
- <path
- id="path3034"
- className="st0"
- d="M18306-3205c-325.7 9.9-546.6 157.7-546.6 157.7v1568.1s218 133.6 485.4 157.5c338.2 30.3 664-71.9 664-878.5-.1-850.6-147.1-1018.5-602.8-1004.8zm370.4 2790c-533.4 0-896.1-238-896.1-238v1676.8c0 43.7-34.9 79-78.1 79h-949c-43.1 0-78.2-35.3-78.2-79V-3928c0-43.7 35.1-79.2 78.3-79.2h658.4c29.7 0 52.1 15.3 68.7 42.1 16.4 26.6 40 228.3 40 228.3s388.1-367.7 1122.6-367.7c862.4 0 1357 437.5 1357 1963.8 0 1526.2-789.9 1725.7-1323.6 1725.7"
- />
- <path
- id="path3036"
- className="st0"
- d="M8289.9-518.3H7580l-1.1 937.8c0 35.5-18.3 53.2-59.3 53.2h-967.3c-37.6 0-57.8-16.5-57.8-52.7v-969.1s-484.8-117-517.5-126.5c-32.6-9.5-56.7-39.6-56.7-75.5v-609c0-43.8 35-79.1 78.3-79.1h496v-1465c0-1088.2 763.3-1195.1 1278.4-1195.1 235.3 0 516.9 75.6 563.3 92.8 28.1 10.3 44.4 39.4 44.4 71l.8 669.9c0 43.7-36.9 79-78.5 79-41.4 0-147.2-16.8-256.1-16.8-348.7 0-466.8 162.1-466.8 372v1392.2H8290c43.3 0 78.3 35.3 78.3 79.1v762.8c-.1 43.8-35.2 79-78.4 79"
- />
- </g>
- </g>
- </g>
- </g>
- </g>
- </svg>
- );
+ return (
+ <svg
+ fill="#ffffff"
+ className={className}
+ version="1.1"
+ id="svg2"
+ xmlns="http://www.w3.org/2000/svg"
+ x="0"
+ y="0"
+ viewBox="0 0 2500 676.1"
+ xmlSpace="preserve"
+ >
+ <g id="layer1" transform="translate(-85.59 -376.905)">
+ <g id="g3012" transform="matrix(1.25 0 0 -1.25 85.59 539.248)">
+ <g id="g3014" transform="scale(.1)">
+ <g id="g3016">
+ <g id="g3018">
+ <path
+ id="path3024"
+ className="st0"
+ d="M3852.1-1016.2H2171.4c-43.4 0-78.5-35.2-78.5-78.6v-821.7c0-43.4 35.2-78.7 78.5-78.7H2827V-3016s-147.2-50.2-554.2-50.2c-480.2 0-1150.9 175.5-1150.9 1650.5 0 1475.3 698.5 1669.4 1354.2 1669.4 567.6 0 812.2-99.9 967.8-148.1 48.9-15 94.1 33.7 94.1 77.1l187.5 793.9c0 20.3-6.9 44.7-30 61.3-63.2 45.1-448.7 260.8-1422.6 260.8C1150.9 1298.8 0 821.4 0-1473.3S1317.6-4110 2428-4110c919.4 0 1477.1 392.9 1477.1 392.9 23 12.7 25.5 44.8 25.5 59.6v2562.7c0 43.4-35.2 78.6-78.5 78.6"
+ />
+ <path
+ id="path3026"
+ className="st0"
+ d="M12513.4 1023.8c0 43.7-34.6 79-78 79h-946.3c-43.2 0-78.4-35.3-78.4-79l.3-1828.8H9936v1828.8c0 43.7-34.8 79-78.1 79h-946.3c-43.1 0-78.2-35.3-78.2-79V-3928c0-43.7 35.1-79.2 78.2-79.2h946.3c43.3 0 78.1 35.6 78.1 79.2v2118h1475l-2.6-2118c0-43.7 35.1-79.2 78.4-79.2h948.6c43.4 0 78 35.6 78.1 79.2v4951.8"
+ />
+ <path
+ id="path3028"
+ className="st0"
+ d="M5637.8 374c0 340.7-273.2 616.1-610.2 616.1-336.7 0-610.1-275.4-610.1-616.1 0-340.4 273.4-616.5 610.1-616.5 337 0 610.2 276.1 610.2 616.5"
+ />
+ <path
+ id="path3030"
+ className="st0"
+ d="M5570.1-2883.4v2285.8c0 43.4-35 78.9-78.3 78.9h-943.3c-43.3 0-82-44.6-82-88v-3274.8c0-96.3 60-124.9 137.6-124.9H5454c93.2 0 116.1 45.8 116.1 126.4v996.6"
+ />
+ <path
+ id="path3032"
+ className="st0"
+ d="M16109.8-526.2h-939.1c-43.1 0-78.1-35.5-78.1-79.2v-2428s-238.5-174.5-577.2-174.5c-338.6 0-428.4 153.6-428.4 485.2v2117.4c0 43.7-35 79.2-78.1 79.2h-953.1c-43 0-78.3-35.5-78.3-79.2V-2883c0-984.8 548.8-1225.7 1303.9-1225.7 619.4 0 1118.8 342.2 1118.8 342.2s23.8-180.3 34.5-201.7c10.8-21.3 38.8-42.9 69.1-42.9l606.3 2.7c43 0 78.3 35.6 78.3 79l-.3 3324.1c.1 43.6-35 79.1-78.3 79.1"
+ />
+ <path
+ id="path3034"
+ className="st0"
+ d="M18306-3205c-325.7 9.9-546.6 157.7-546.6 157.7v1568.1s218 133.6 485.4 157.5c338.2 30.3 664-71.9 664-878.5-.1-850.6-147.1-1018.5-602.8-1004.8zm370.4 2790c-533.4 0-896.1-238-896.1-238v1676.8c0 43.7-34.9 79-78.1 79h-949c-43.1 0-78.2-35.3-78.2-79V-3928c0-43.7 35.1-79.2 78.3-79.2h658.4c29.7 0 52.1 15.3 68.7 42.1 16.4 26.6 40 228.3 40 228.3s388.1-367.7 1122.6-367.7c862.4 0 1357 437.5 1357 1963.8 0 1526.2-789.9 1725.7-1323.6 1725.7"
+ />
+ <path
+ id="path3036"
+ className="st0"
+ d="M8289.9-518.3H7580l-1.1 937.8c0 35.5-18.3 53.2-59.3 53.2h-967.3c-37.6 0-57.8-16.5-57.8-52.7v-969.1s-484.8-117-517.5-126.5c-32.6-9.5-56.7-39.6-56.7-75.5v-609c0-43.8 35-79.1 78.3-79.1h496v-1465c0-1088.2 763.3-1195.1 1278.4-1195.1 235.3 0 516.9 75.6 563.3 92.8 28.1 10.3 44.4 39.4 44.4 71l.8 669.9c0 43.7-36.9 79-78.5 79-41.4 0-147.2-16.8-256.1-16.8-348.7 0-466.8 162.1-466.8 372v1392.2H8290c43.3 0 78.3 35.3 78.3 79.1v762.8c-.1 43.8-35.2 79-78.4 79"
+ />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </svg>
+ );
};
const NotionText = ({ className }: { className?: string }) => {
- return (
- <svg
- className={className}
- version="1.0"
- fill="#ffffff"
- id="katman_1"
- xmlns="http://www.w3.org/2000/svg"
- xmlnsXlink="http://www.w3.org/1999/xlink"
- x="0px"
- y="0px"
- viewBox="0 0 600 400"
- xmlSpace="preserve"
- >
- <style type="text/css"></style>
- <path
- d="M283.2,228.4v-39.7h0.7l28.6,39.7h9v-58.3h-10v39.6h-0.7l-28.6-39.6h-9v58.3L283.2,228.4L283.2,228.4z M349.8,229.3
+ return (
+ <svg
+ className={className}
+ version="1.0"
+ fill="#ffffff"
+ id="katman_1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 400"
+ xmlSpace="preserve"
+ >
+ <style type="text/css"></style>
+ <path
+ d="M283.2,228.4v-39.7h0.7l28.6,39.7h9v-58.3h-10v39.6h-0.7l-28.6-39.6h-9v58.3L283.2,228.4L283.2,228.4z M349.8,229.3
c13.2,0,21.3-8.6,21.3-23c0-14.3-8.1-23-21.3-23c-13.1,0-21.3,8.7-21.3,23C328.6,220.7,336.6,229.3,349.8,229.3z M349.8,220.9
c-7,0-11-5.3-11-14.6c0-9.2,4-14.6,11-14.6c7,0,11,5.4,11,14.6C360.8,215.6,356.8,220.9,349.8,220.9z M380.2,173.4v11.1h-7v8h7v24.1
c0,8.6,4,12,14.2,12c1.9,0,3.8-0.2,5.3-0.5v-7.8c-1.2,0.1-2,0.2-3.4,0.2c-4.2,0-6.1-1.9-6.1-6.3v-21.7h9.5v-8h-9.5v-11.1
@@ -92,38 +92,38 @@ const NotionText = ({ className }: { className?: string }) => {
c-13.1,0-21.3,8.7-21.3,23C421.8,220.7,429.8,229.3,443.1,229.3z M443.1,220.9c-7,0-11-5.3-11-14.6c0-9.2,4-14.6,11-14.6
c6.9,0,11,5.4,11,14.6C454,215.6,450,220.9,443.1,220.9z M470.3,228.4h10v-25.7c0-6.5,3.8-10.6,9.7-10.6c6.1,0,8.9,3.4,8.9,10.1
v26.2h10v-28.6c0-10.6-5.4-16.5-15.2-16.5c-6.6,0-11,3-13.1,8h-0.7v-7.1h-9.7C470.3,184.2,470.3,228.4,470.3,228.4z"
- />
- <g>
- <path
- className="st0"
- d="M120,152.1c4.7,3.8,6.4,3.5,15.2,2.9l82.9-5c1.8,0,0.3-1.8-0.3-2l-13.8-9.9c-2.6-2-6.2-4.4-12.9-3.8l-80.2,5.9
+ />
+ <g>
+ <path
+ className="st0"
+ d="M120,152.1c4.7,3.8,6.4,3.5,15.2,2.9l82.9-5c1.8,0,0.3-1.8-0.3-2l-13.8-9.9c-2.6-2-6.2-4.4-12.9-3.8l-80.2,5.9
c-2.9,0.3-3.5,1.8-2.3,2.9L120,152.1z M125,171.4v87.2c0,4.7,2.3,6.4,7.6,6.1l91.1-5.3c5.3-0.3,5.9-3.5,5.9-7.3v-86.6
c0-3.8-1.5-5.9-4.7-5.6l-95.2,5.6C126.2,165.8,125,167.6,125,171.4L125,171.4z M214.9,176.1c0.6,2.6,0,5.3-2.6,5.6l-4.4,0.9v64.4
c-3.8,2-7.3,3.2-10.3,3.2c-4.7,0-5.9-1.5-9.4-5.9l-28.7-45.1v43.6l9.1,2c0,0,0,5.3-7.3,5.3l-20.2,1.2c-0.6-1.2,0-4.1,2-4.7l5.3-1.5
v-57.6l-7.3-0.6c-0.6-2.6,0.9-6.4,5-6.7l21.7-1.5l29.9,45.6V184l-7.6-0.9c-0.6-3.2,1.8-5.6,4.7-5.9L214.9,176.1z M104.2,132.2
l83.5-6.1c10.2-0.9,12.9-0.3,19.3,4.4l26.6,18.7c4.4,3.2,5.9,4.1,5.9,7.6v102.7c0,6.4-2.3,10.2-10.5,10.8l-96.9,5.9
c-6.2,0.3-9.1-0.6-12.3-4.7L100.1,246c-3.5-4.7-5-8.2-5-12.3v-91.3C95.1,137.1,97.5,132.8,104.2,132.2z"
- />
- </g>
- </svg>
- );
+ />
+ </g>
+ </svg>
+ );
};
const MediumText = ({ className }: { className?: string }) => {
- return (
- <svg
- className={className}
- fill="#ffffff"
- version="1.1"
- id="layer"
- xmlns="http://www.w3.org/2000/svg"
- xmlnsXlink="http://www.w3.org/1999/xlink"
- x="0px"
- y="0px"
- viewBox="0 0 652 652"
- xmlSpace="preserve"
- >
- <path
- d="M112,321.5c0,25-20.1,45.3-45,45.3s-45-20.3-45-45.3s20.1-45.3,45-45.3S112,296.5,112,321.5 M161.3,321.5
+ return (
+ <svg
+ className={className}
+ fill="#ffffff"
+ version="1.1"
+ id="layer"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ x="0px"
+ y="0px"
+ viewBox="0 0 652 652"
+ xmlSpace="preserve"
+ >
+ <path
+ d="M112,321.5c0,25-20.1,45.3-45,45.3s-45-20.3-45-45.3s20.1-45.3,45-45.3S112,296.5,112,321.5 M161.3,321.5
c0,23.5-10.1,42.6-22.5,42.6c-12.4,0-22.5-19.1-22.5-42.6s10.1-42.6,22.5-42.6C151.2,278.9,161.3,297.9,161.3,321.5 M181.5,321.5
c0,21.1-3.5,38.2-7.9,38.2c-4.4,0-7.9-17.1-7.9-38.2s3.5-38.2,7.9-38.2S181.5,300.4,181.5,321.5 M305.6,280.1l0.1,0v-1h-25.6
L256.4,335l-23.8-55.8h-27.6v1l0.1,0c4.7,1.1,7,2.6,7,8.3v66.4c0,5.7-2.4,7.2-7,8.3l-0.1,0v1h18.7v-1l-0.1,0c-4.7-1.1-7-2.6-7-8.3
@@ -143,70 +143,70 @@ const MediumText = ({ className }: { className?: string }) => {
c0-11.6-6.5-18.5-17.4-18.5c-7.9,0-14.6,4.6-17.2,11.7c-2-7.6-7.9-11.7-16.7-11.7c-7.7,0-13.7,4-16.2,10.9v-10.7l-25.5,7v1l0.2,0
c5.4,0.5,7,2.4,7,8.7v47.6h23.8v-1l-0.1,0c-4-0.9-5.3-2.7-5.3-7.1v-42.6c1.1-2.5,3.2-5.5,7.5-5.5c5.3,0,8,3.7,8,10.9v45.3h23.8v-1
l-0.1,0c-4-0.9-5.3-2.7-5.3-7.1v-37.9c0-1.4-0.1-2.8-0.3-4.2c1.1-2.7,3.4-5.9,7.8-5.9c5.4,0,8,3.6,8,10.9v45.3H622z"
- />
- </svg>
- );
+ />
+ </svg>
+ );
};
const logos = [
- {
- name: "Github",
- url: <Github className="w-32 brightness-100 invert-1 mx-10" />,
- },
- {
- name: "Medium",
- url: <MediumText className="w-52 h brightness-100 invert-1 mx-10" />,
- },
- {
- name: "Notion",
- url: <NotionText className="w-52 brightness-100 invert-1 mx-10" />,
- },
- {
- name: "Reddit",
- url: (
- <Reddit className="w-16 h-16 brightness-100 invert-1 mx-10 grayscale" />
- ),
- },
- {
- name: "Twitter",
- url: <Twitter className="w-16 h-16 brightness-100 invert-1 mx-10" />,
- },
+ {
+ name: "Github",
+ url: <Github className="w-32 brightness-100 invert-1 mx-10" />,
+ },
+ {
+ name: "Medium",
+ url: <MediumText className="w-52 h brightness-100 invert-1 mx-10" />,
+ },
+ {
+ name: "Notion",
+ url: <NotionText className="w-52 brightness-100 invert-1 mx-10" />,
+ },
+ {
+ name: "Reddit",
+ url: (
+ <Reddit className="w-16 h-16 brightness-100 invert-1 mx-10 grayscale" />
+ ),
+ },
+ {
+ name: "Twitter",
+ url: <Twitter className="w-16 h-16 brightness-100 invert-1 mx-10" />,
+ },
];
const AnimatedLogoCloud = () => {
- return (
- <div className="py-2 max-w-4xl scale-75">
- <p className="font-normal tracking-tighter text-base text-gray-100 bg-gradient-to-br from-zinc-400 via-zinc-300 to-zinc-700 bg-clip-text text-transparent text-center mt-4">
- Works perfectly with the apps you love.
- </p>
- {/* <hr className="h-[0.1px] relative bg-white/10" /> */}
+ return (
+ <div className="py-2 max-w-4xl scale-75">
+ <p className="font-normal tracking-tighter text-base text-gray-100 bg-gradient-to-br from-zinc-400 via-zinc-300 to-zinc-700 bg-clip-text text-transparent text-center mt-4">
+ Works perfectly with the apps you love.
+ </p>
+ {/* <hr className="h-[0.1px] relative bg-white/10" /> */}
- <div className="relative bg-page-gradient h-full mx-auto max-w-full">
- <div className="absolute z-40 mx-auto h-screen overflow-hidden bg-inherit bg-[radial-gradient(ellipse_20%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"></div>
- </div>
- <div className="px-4 mx-auto w-full md:px-8 relative ">
- <div
- className="flex overflow-hidden relative gap-6 p-2 mt-[-40px] group"
- style={{
- maskImage:
- "linear-gradient(to left, transparent 0%, black 20%, black 80%, transparent 95%)",
- }}
- >
- {Array(5)
- .fill(null)
- .map((index) => (
- <div
- key={`animated-logo-cloud-${index}`}
- className="flex flex-row gap-5 justify-around items-center animate-logo-cloud shrink-0"
- >
- {logos.map((logo, key) => (
- <>{logo.url}</>
- ))}
- </div>
- ))}
- </div>
- </div>
- </div>
- );
+ <div className="relative bg-page-gradient h-full mx-auto max-w-full">
+ <div className="absolute z-40 mx-auto h-screen overflow-hidden bg-inherit bg-[radial-gradient(ellipse_20%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"></div>
+ </div>
+ <div className="px-4 mx-auto w-full md:px-8 relative ">
+ <div
+ className="flex overflow-hidden relative gap-6 p-2 mt-[-40px] group"
+ style={{
+ maskImage:
+ "linear-gradient(to left, transparent 0%, black 20%, black 80%, transparent 95%)",
+ }}
+ >
+ {Array(5)
+ .fill(null)
+ .map((index) => (
+ <div
+ key={`animated-logo-cloud-${index}`}
+ className="flex flex-row gap-5 justify-around items-center animate-logo-cloud shrink-0"
+ >
+ {logos.map((logo, key) => (
+ <>{logo.url}</>
+ ))}
+ </div>
+ ))}
+ </div>
+ </div>
+ </div>
+ );
};
export default AnimatedLogoCloud;
diff --git a/apps/web/app/(landing)/Navbar.tsx b/apps/web/app/(landing)/Navbar.tsx
index 18dcc2d1..2ec3c2d7 100644
--- a/apps/web/app/(landing)/Navbar.tsx
+++ b/apps/web/app/(landing)/Navbar.tsx
@@ -7,56 +7,56 @@ import Link from "next/link";
import React from "react";
import { useState } from "react";
import {
- motion,
- AnimatePresence,
- useScroll,
- useMotionValueEvent,
+ motion,
+ AnimatePresence,
+ useScroll,
+ useMotionValueEvent,
} from "framer-motion";
import { SlideNavTabs } from "./Headers/Navbar";
function NavbarContent() {
- return (
- <div className="">
- <SlideNavTabs />
- </div>
- );
+ return (
+ <div className="">
+ <SlideNavTabs />
+ </div>
+ );
}
export const Navbar = () => {
- const { scrollYProgress } = useScroll();
+ const { scrollYProgress } = useScroll();
- const [visible, setVisible] = useState(true);
+ const [visible, setVisible] = useState(true);
- useMotionValueEvent(scrollYProgress, "change", (current) => {
- // Check if current is not undefined and is a number
- if (typeof current === "number") {
- let direction = current! - scrollYProgress.getPrevious()!;
- if (direction < 0) {
- setVisible(true);
- } else {
- setVisible(false);
- }
- }
- });
+ useMotionValueEvent(scrollYProgress, "change", (current) => {
+ // Check if current is not undefined and is a number
+ if (typeof current === "number") {
+ let direction = current! - scrollYProgress.getPrevious()!;
+ if (direction < 0) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ }
+ });
- return (
- <AnimatePresence mode="wait">
- <motion.nav
- initial={{
- y: -130,
- opacity: 1,
- }}
- animate={{
- y: visible ? 0 : -100,
- opacity: visible ? 1 : 0,
- }}
- transition={{
- duration: 0.2,
- }}
- className="fixed top-0 z-[99999] mt-12 hidden w-full px-24 text-sm md:flex"
- >
- <NavbarContent />
- </motion.nav>
- </AnimatePresence>
- );
+ return (
+ <AnimatePresence mode="wait">
+ <motion.nav
+ initial={{
+ y: -130,
+ opacity: 1,
+ }}
+ animate={{
+ y: visible ? 0 : -100,
+ opacity: visible ? 1 : 0,
+ }}
+ transition={{
+ duration: 0.2,
+ }}
+ className="fixed top-0 z-[99999] mt-12 hidden w-full px-24 text-sm md:flex"
+ >
+ <NavbarContent />
+ </motion.nav>
+ </AnimatePresence>
+ );
};
diff --git a/apps/web/app/(landing)/RotatingIcons.tsx b/apps/web/app/(landing)/RotatingIcons.tsx
index d8fd1035..4b635fab 100644
--- a/apps/web/app/(landing)/RotatingIcons.tsx
+++ b/apps/web/app/(landing)/RotatingIcons.tsx
@@ -2,100 +2,100 @@
import { motion } from "framer-motion";
import {
- Github,
- Medium,
- Notion,
- Reddit,
- Twitter,
+ Github,
+ Medium,
+ Notion,
+ Reddit,
+ Twitter,
} from "@repo/ui/components/icons";
import Image from "next/image";
const icons = [
- <div className="rounded-full bg-purple-600/20 p-4">
- <Github className="h-8 w-8 text-purple-500" />
- </div>,
- <div className="rounded-full bg-blue-800/20 p-4">
- <Twitter className="h-8 w-8 text-blue-500" />
- </div>,
- <div className="rounded-full bg-green-800/20 p-4">
- <Medium className="h-8 w-8 text-green-500" />
- </div>,
- <div className="rounded-full bg-red-800/20 p-4">
- <Reddit className="h-8 w-8 text-red-500" />
- </div>,
- <div className="rounded-full bg-white/20 p-4">
- <Notion className="h-8 w-8 text-white" />
- </div>,
+ <div className="rounded-full bg-purple-600/20 p-4">
+ <Github className="h-8 w-8 text-purple-500" />
+ </div>,
+ <div className="rounded-full bg-blue-800/20 p-4">
+ <Twitter className="h-8 w-8 text-blue-500" />
+ </div>,
+ <div className="rounded-full bg-green-800/20 p-4">
+ <Medium className="h-8 w-8 text-green-500" />
+ </div>,
+ <div className="rounded-full bg-red-800/20 p-4">
+ <Reddit className="h-8 w-8 text-red-500" />
+ </div>,
+ <div className="rounded-full bg-white/20 p-4">
+ <Notion className="h-8 w-8 text-white" />
+ </div>,
];
const RotatingIcons: React.FC = () => {
- return (
- <div className="relative flex w-full flex-col items-center justify-center gap-8 px-4 sm:px-6 mt-10">
- <motion.h1
- {...{
- transition: { delay: 0.2 },
- }}
- className="text-center max-w-2xl mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl lg:text-6xl"
- >
- Collect data from{" "}
- <span className="bg-gradient-to-r from-zinc-300 to-blue-200 bg-clip-text text-transparent">
- any website on the internet
- </span>
- </motion.h1>
- <div className="flex items-center justify-center">
- <div className="relative m-2 mx-auto h-96 w-96 scale-[70%] md:scale-100 ">
- <div className="relative h-full w-full rounded-full border border-gray-800">
- {icons.map((icon, index) => (
- <motion.div
- key={index}
- className="absolute top-1/2 -translate-x-10 transform"
- style={{
- originX: "200px",
- originY: "-8px",
- }}
- animate={{
- rotate: [0, 360],
- }}
- transition={{
- repeat: Infinity,
- duration: 5,
- ease: "linear",
- delay: index,
- }}
- >
- <motion.div
- style={{
- rotate: index * 72,
- }}
- animate={{
- rotate: [0, -360],
- }}
- transition={{
- repeat: Infinity,
- duration: 5,
- ease: "linear",
- delay: index,
- }}
- >
- {icon}
- </motion.div>
- </motion.div>
- ))}
- <Image
- src="/logo.svg"
- alt="Supermemory logo"
- width={80}
- height={80}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform text-white"
- />
- </div>
- </div>
- </div>
- <p className="text-center text-sm text-zinc-500">
- ... and bring it into your second brain
- </p>
- </div>
- );
+ return (
+ <div className="relative flex w-full flex-col items-center justify-center gap-8 px-4 sm:px-6 mt-10">
+ <motion.h1
+ {...{
+ transition: { delay: 0.2 },
+ }}
+ className="text-center max-w-2xl mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl lg:text-6xl"
+ >
+ Collect data from{" "}
+ <span className="bg-gradient-to-r from-zinc-300 to-blue-200 bg-clip-text text-transparent">
+ any website on the internet
+ </span>
+ </motion.h1>
+ <div className="flex items-center justify-center">
+ <div className="relative m-2 mx-auto h-96 w-96 scale-[70%] md:scale-100 ">
+ <div className="relative h-full w-full rounded-full border border-gray-800">
+ {icons.map((icon, index) => (
+ <motion.div
+ key={index}
+ className="absolute top-1/2 -translate-x-10 transform"
+ style={{
+ originX: "200px",
+ originY: "-8px",
+ }}
+ animate={{
+ rotate: [0, 360],
+ }}
+ transition={{
+ repeat: Infinity,
+ duration: 5,
+ ease: "linear",
+ delay: index,
+ }}
+ >
+ <motion.div
+ style={{
+ rotate: index * 72,
+ }}
+ animate={{
+ rotate: [0, -360],
+ }}
+ transition={{
+ repeat: Infinity,
+ duration: 5,
+ ease: "linear",
+ delay: index,
+ }}
+ >
+ {icon}
+ </motion.div>
+ </motion.div>
+ ))}
+ <Image
+ src="/logo.svg"
+ alt="Supermemory logo"
+ width={80}
+ height={80}
+ className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform text-white"
+ />
+ </div>
+ </div>
+ </div>
+ <p className="text-center text-sm text-zinc-500">
+ ... and bring it into your second brain
+ </p>
+ </div>
+ );
};
export default RotatingIcons;
diff --git a/apps/web/app/(landing)/Showcase.tsx b/apps/web/app/(landing)/Showcase.tsx
index c0650742..36cbf0c3 100644
--- a/apps/web/app/(landing)/Showcase.tsx
+++ b/apps/web/app/(landing)/Showcase.tsx
@@ -8,143 +8,143 @@ import TestImg from "../../public/images/carousel-illustration-01.png";
import Search from "../../public/images/search.svg";
import Memroy from "../../public/images/memory.svg";
interface Feature {
- name: React.ReactNode;
- summary: string;
- description: string;
- image: ImageProps["src"];
- icon: React.ComponentType;
+ name: React.ReactNode;
+ summary: string;
+ description: string;
+ image: ImageProps["src"];
+ icon: React.ComponentType;
}
// TODO: This features section will be more for "use-cases"
const features: Array<Feature> = [
- {
- name: "Ideation",
- summary:
- "Never lose a great idea again - instead of saving it in your head, save it in supermemory.",
- description:
- "The internet is full of great ideas, but there's a problem. They are ephemeral. They come and go. ",
- image: "asking_questions.png",
- icon: function ReportingIcon() {
- let id = useId();
- return (
- <>
- <defs>
- <linearGradient
- id={id}
- x1="11.5"
- y1={18}
- x2={36}
- y2="15.5"
- gradientUnits="userSpaceOnUse"
- >
- <stop offset=".194" stopColor="#fff" />
- <stop offset={1} stopColor="#6692F1" />
- </linearGradient>
- </defs>
- <path
- d="m30 15-4 5-4-11-4 18-4-11-4 7-4-5"
- stroke={`url(#${id})`}
- strokeWidth={2}
- strokeLinecap="round"
- strokeLinejoin="round"
- />
- </>
- );
- },
- },
- {
- name: "Bookmarks",
- summary: "Simply great bookmarking tool.",
- description:
- "Good bookmarking tools have ways to import and organise your bookmarks. Great bookmarking tool resurfaces them when you need them.",
- image: "memory.svg",
- icon: function InventoryIcon() {
- return (
- <>
- <path
- opacity=".5"
- d="M8 17a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2Z"
- fill="#fff"
- />
- <path
- opacity=".3"
- d="M8 24a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2Z"
- fill="#fff"
- />
- <path
- d="M8 10a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2Z"
- fill="#fff"
- />
- </>
- );
- },
- },
- {
- name: "Contacts",
- summary: "Life is all about the people you know.",
- description:
- "Tell supermemory about people you know, and when you forget, you know where to look.",
- image: "search.svg",
- icon: function ContactsIcon() {
- return (
- <>
- <path
- opacity=".5"
- d="M25.778 25.778c.39.39 1.027.393 1.384-.028A11.952 11.952 0 0 0 30 18c0-6.627-5.373-12-12-12S6 11.373 6 18c0 2.954 1.067 5.659 2.838 7.75.357.421.993.419 1.384.028.39-.39.386-1.02.036-1.448A9.959 9.959 0 0 1 8 18c0-5.523 4.477-10 10-10s10 4.477 10 10a9.959 9.959 0 0 1-2.258 6.33c-.35.427-.354 1.058.036 1.448Z"
- fill="#fff"
- />
- <path
- d="M12 28.395V28a6 6 0 0 1 12 0v.395A11.945 11.945 0 0 1 18 30c-2.186 0-4.235-.584-6-1.605ZM21 16.5c0-1.933-.5-3.5-3-3.5s-3 1.567-3 3.5 1.343 3.5 3 3.5 3-1.567 3-3.5Z"
- fill="#fff"
- />
- </>
- );
- },
- },
+ {
+ name: "Ideation",
+ summary:
+ "Never lose a great idea again - instead of saving it in your head, save it in supermemory.",
+ description:
+ "The internet is full of great ideas, but there's a problem. They are ephemeral. They come and go. ",
+ image: "asking_questions.png",
+ icon: function ReportingIcon() {
+ let id = useId();
+ return (
+ <>
+ <defs>
+ <linearGradient
+ id={id}
+ x1="11.5"
+ y1={18}
+ x2={36}
+ y2="15.5"
+ gradientUnits="userSpaceOnUse"
+ >
+ <stop offset=".194" stopColor="#fff" />
+ <stop offset={1} stopColor="#6692F1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="m30 15-4 5-4-11-4 18-4-11-4 7-4-5"
+ stroke={`url(#${id})`}
+ strokeWidth={2}
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ />
+ </>
+ );
+ },
+ },
+ {
+ name: "Bookmarks",
+ summary: "Simply great bookmarking tool.",
+ description:
+ "Good bookmarking tools have ways to import and organise your bookmarks. Great bookmarking tool resurfaces them when you need them.",
+ image: "memory.svg",
+ icon: function InventoryIcon() {
+ return (
+ <>
+ <path
+ opacity=".5"
+ d="M8 17a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2Z"
+ fill="#fff"
+ />
+ <path
+ opacity=".3"
+ d="M8 24a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2Z"
+ fill="#fff"
+ />
+ <path
+ d="M8 10a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2Z"
+ fill="#fff"
+ />
+ </>
+ );
+ },
+ },
+ {
+ name: "Contacts",
+ summary: "Life is all about the people you know.",
+ description:
+ "Tell supermemory about people you know, and when you forget, you know where to look.",
+ image: "search.svg",
+ icon: function ContactsIcon() {
+ return (
+ <>
+ <path
+ opacity=".5"
+ d="M25.778 25.778c.39.39 1.027.393 1.384-.028A11.952 11.952 0 0 0 30 18c0-6.627-5.373-12-12-12S6 11.373 6 18c0 2.954 1.067 5.659 2.838 7.75.357.421.993.419 1.384.028.39-.39.386-1.02.036-1.448A9.959 9.959 0 0 1 8 18c0-5.523 4.477-10 10-10s10 4.477 10 10a9.959 9.959 0 0 1-2.258 6.33c-.35.427-.354 1.058.036 1.448Z"
+ fill="#fff"
+ />
+ <path
+ d="M12 28.395V28a6 6 0 0 1 12 0v.395A11.945 11.945 0 0 1 18 30c-2.186 0-4.235-.584-6-1.605ZM21 16.5c0-1.933-.5-3.5-3-3.5s-3 1.567-3 3.5 1.343 3.5 3 3.5 3-1.567 3-3.5Z"
+ fill="#fff"
+ />
+ </>
+ );
+ },
+ },
];
function Feature({
- feature,
- isActive,
- className,
- ...props
+ feature,
+ isActive,
+ className,
+ ...props
}: React.ComponentPropsWithoutRef<"div"> & {
- feature: Feature;
- isActive: boolean;
+ feature: Feature;
+ isActive: boolean;
}) {
- return (
- <div
- className={clsx(
- className,
- "focus:outline-none",
- !isActive && "opacity-75 hover:opacity-100",
- )}
- {...props}
- >
- <div
- className={clsx(
- "w-9 rounded-lg",
- isActive ? `bg-indigo-500` : "bg-slate-500",
- )}
- >
- <svg aria-hidden="true" className="h-9 w-9" fill="none">
- <feature.icon />
- </svg>
- </div>
- <h3
- className={clsx(
- "mt-4 text-sm font-medium",
- isActive ? `text-indigo-300` : "text-gray-300",
- )}
- >
- {feature.name}
- </h3>
- <p className="mt-2 font-display text-xl text-gray-200">
- {feature.summary}
- </p>
- <p className="mt-2 text-sm text-gray-300">{feature.description}</p>
- </div>
- );
+ return (
+ <div
+ className={clsx(
+ className,
+ "focus:outline-none",
+ !isActive && "opacity-75 hover:opacity-100",
+ )}
+ {...props}
+ >
+ <div
+ className={clsx(
+ "w-9 rounded-lg",
+ isActive ? `bg-indigo-500` : "bg-slate-500",
+ )}
+ >
+ <svg aria-hidden="true" className="h-9 w-9" fill="none">
+ <feature.icon />
+ </svg>
+ </div>
+ <h3
+ className={clsx(
+ "mt-4 text-sm font-medium",
+ isActive ? `text-indigo-300` : "text-gray-300",
+ )}
+ >
+ {feature.name}
+ </h3>
+ <p className="mt-2 font-display text-xl text-gray-200">
+ {feature.summary}
+ </p>
+ <p className="mt-2 text-sm text-gray-300">{feature.description}</p>
+ </div>
+ );
}
function FeaturesMobile() {
@@ -171,76 +171,76 @@ function FeaturesMobile() {
}
function FeaturesDesktop() {
- return (
- <TabGroup className="hidden lg:mt-20 lg:block">
- {({ selectedIndex }) => (
- <>
- <TabList className="grid grid-cols-3 gap-x-8">
- {features.map((feature, featureIndex) => (
- <Feature
- key={feature.summary}
- feature={{
- ...feature,
- name: (
- <Tab className="ui-not-focus-visible:outline-none">
- <span className="absolute inset-0" />
- {feature.name}
- </Tab>
- ),
- }}
- isActive={featureIndex === selectedIndex}
- className="relative"
- />
- ))}
- </TabList>
- <TabPanels className="relative mt-20 overflow-hidden rounded-3xl bg-page-gradient px-14 py-16 xl:px-16">
- <div className="-mx-5 flex">
- {features.map((feature, featureIndex) => (
- <TabPanel
- static
- key={feature.summary}
- className={clsx(
- "px-5 transition duration-500 ease-in-out ui-not-focus-visible:outline-none",
- featureIndex !== selectedIndex && "opacity-60",
- )}
- style={{ transform: `translateX(-${selectedIndex * 100}%)` }}
- aria-hidden={featureIndex !== selectedIndex}
- >
- <div className="w-[52.75rem] overflow-hidden rounded-xl bg-page-gradient shadow-lg shadow-gray-200/5 ring-1 ring-slate-500/10">
- <img
- className="max-w-full"
- src={`/images/${feature.image}`}
- alt=""
- sizes="52.75rem"
- />
- </div>
- </TabPanel>
- ))}
- </div>
- <div className="absolute inset-0 rounded-4xl ring-1 ring-inset ring-gray-200/10" />
- </TabPanels>
- </>
- )}
- </TabGroup>
- );
+ return (
+ <TabGroup className="hidden lg:mt-20 lg:block">
+ {({ selectedIndex }) => (
+ <>
+ <TabList className="grid grid-cols-3 gap-x-8">
+ {features.map((feature, featureIndex) => (
+ <Feature
+ key={feature.summary}
+ feature={{
+ ...feature,
+ name: (
+ <Tab className="ui-not-focus-visible:outline-none">
+ <span className="absolute inset-0" />
+ {feature.name}
+ </Tab>
+ ),
+ }}
+ isActive={featureIndex === selectedIndex}
+ className="relative"
+ />
+ ))}
+ </TabList>
+ <TabPanels className="relative mt-20 overflow-hidden rounded-3xl bg-page-gradient px-14 py-16 xl:px-16">
+ <div className="-mx-5 flex">
+ {features.map((feature, featureIndex) => (
+ <TabPanel
+ static
+ key={feature.summary}
+ className={clsx(
+ "px-5 transition duration-500 ease-in-out ui-not-focus-visible:outline-none",
+ featureIndex !== selectedIndex && "opacity-60",
+ )}
+ style={{ transform: `translateX(-${selectedIndex * 100}%)` }}
+ aria-hidden={featureIndex !== selectedIndex}
+ >
+ <div className="w-[52.75rem] overflow-hidden rounded-xl bg-page-gradient shadow-lg shadow-gray-200/5 ring-1 ring-slate-500/10">
+ <img
+ className="max-w-full"
+ src={`/images/${feature.image}`}
+ alt=""
+ sizes="52.75rem"
+ />
+ </div>
+ </TabPanel>
+ ))}
+ </div>
+ <div className="absolute inset-0 rounded-4xl ring-1 ring-inset ring-gray-200/10" />
+ </TabPanels>
+ </>
+ )}
+ </TabGroup>
+ );
}
export function Showcases() {
- return (
- <section
- id="use-cases"
- className=" bg-page-gradient bg-opacity-0 relative pb-14 pt-20 sm:pb-20 sm:pt-32 lg:pb-32 w-full mt-10"
- >
- <Container>
- <div className="-z-1 absolute inset-x-0 -top-0 h-[600px] w-full bg-transparent bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem] opacity-10 [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]"></div>
- <div
- className="h-fukl absolute inset-0 rotate-180 blur-xl"
- // style={{
- // background:
- // "linear-gradient(143.6deg, rgba(52, 103, 235, 0) 20.79%, rgba(120,119,198, 0.26) 40.92%, rgba(120,119,198, 0) 70.35%)",
- // }}
- ></div>
- {/* <img
+ return (
+ <section
+ id="use-cases"
+ className=" bg-page-gradient bg-opacity-0 relative pb-14 pt-20 sm:pb-20 sm:pt-32 lg:pb-32 w-full mt-10"
+ >
+ <Container>
+ <div className="-z-1 absolute inset-x-0 -top-0 h-[600px] w-full bg-transparent bg-[linear-gradient(to_right,#f0f0f0_1px,transparent_1px),linear-gradient(to_bottom,#f0f0f0_1px,transparent_1px)] bg-[size:6rem_4rem] opacity-10 [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]"></div>
+ <div
+ className="h-fukl absolute inset-0 rotate-180 blur-xl"
+ // style={{
+ // background:
+ // "linear-gradient(143.6deg, rgba(52, 103, 235, 0) 20.79%, rgba(120,119,198, 0.26) 40.92%, rgba(120,119,198, 0) 70.35%)",
+ // }}
+ ></div>
+ {/* <img
className="absolute inset-x-0 -top-0 opacity-75 "
src={
"/images/landing-hero-left.webp"
@@ -249,36 +249,36 @@ export function Showcases() {
height={1000}
alt="back bg"
/> */}
- <div className="mr-auto relative max-w-3xl md:text-start">
- <h2 className="font-display text-4xl tracking-tight text-gray-200 sm:text-7xl">
- <span className="bg-gradient-to-br from-indigo-400 via-indigo-300 to-indigo-700 bg-clip-text text-transparent">
- Supermemory <br />
- </span>{" "}
- remembers everything.
- </h2>
- <p className="mt-4 text-lg tracking-tight text-gray-100">
- ... so you don't have to. Whether you're a student, a professional,
- or just a person on the the internet. we got you covered.
- </p>
- <div className="overflow-x-hidden overflow-y-hidden">
- <div className="absolute right-0 z-20 top-[0%] h-40 w-[17%] overflow-x-hidden bg-[#369DFD] bg-opacity-20 blur-[110px]" />
- </div>
- </div>
- <FeaturesMobile />
- <FeaturesDesktop />
- </Container>
- </section>
- );
+ <div className="mr-auto relative max-w-3xl md:text-start">
+ <h2 className="font-display text-4xl tracking-tight text-gray-200 sm:text-7xl">
+ <span className="bg-gradient-to-br from-indigo-400 via-indigo-300 to-indigo-700 bg-clip-text text-transparent">
+ Supermemory <br />
+ </span>{" "}
+ remembers everything.
+ </h2>
+ <p className="mt-4 text-lg tracking-tight text-gray-100">
+ ... so you don't have to. Whether you're a student, a professional,
+ or just a person on the the internet. we got you covered.
+ </p>
+ <div className="overflow-x-hidden overflow-y-hidden">
+ <div className="absolute right-0 z-20 top-[0%] h-40 w-[17%] overflow-x-hidden bg-[#369DFD] bg-opacity-20 blur-[110px]" />
+ </div>
+ </div>
+ <FeaturesMobile />
+ <FeaturesDesktop />
+ </Container>
+ </section>
+ );
}
export function Container({
- className,
- ...props
+ className,
+ ...props
}: React.ComponentPropsWithoutRef<"div">) {
- return (
- <div
- className={clsx("mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", className)}
- {...props}
- />
- );
+ return (
+ <div
+ className={clsx("mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", className)}
+ {...props}
+ />
+ );
}
diff --git a/apps/web/app/(landing)/footer.tsx b/apps/web/app/(landing)/footer.tsx
index 5ec29f1a..15003fb9 100644
--- a/apps/web/app/(landing)/footer.tsx
+++ b/apps/web/app/(landing)/footer.tsx
@@ -2,37 +2,37 @@ import React from "react";
import LinkArrow from "./linkArrow";
function Footer() {
- return (
- <footer className="mt-20 w-full md:flex overflow-y-hidden items-center justify-between gap-4 px-8 py-8 text-sm text-zinc-500 overflow-hidden text-center">
- <p>© 2024 Supermemory.ai</p>
- <div className="flex gap-5 justify-around my-2">
- <a
- className="group/mail flex items-center"
- target="_blank"
- href="mailto:[email protected]"
- >
- Contact
- <LinkArrow classname="group-hover/mail:opacity-100 opacity-0 transition hidden md:block" />
- </a>
- <a
- className="group/twit flex items-center"
- target="_blank"
- href="https://twitter.com/supermemoryai"
- >
- Twitter{" "}
- <LinkArrow classname="group-hover/twit:opacity-100 opacity-0 transition hidden md:block" />
- </a>
- <a
- className="group/git flex items-center"
- target="_blank"
- href="https://github.com/dhravya/supermemory"
- >
- Github{" "}
- <LinkArrow classname="group-hover/git:opacity-100 opacity-0 transition hidden md:block" />
- </a>
- </div>
- </footer>
- );
+ return (
+ <footer className="mt-20 w-full md:flex overflow-y-hidden items-center justify-between gap-4 px-8 py-8 text-sm text-zinc-500 overflow-hidden text-center">
+ <p>© 2024 Supermemory.ai</p>
+ <div className="flex gap-5 justify-around my-2">
+ <a
+ className="group/mail flex items-center"
+ target="_blank"
+ href="mailto:[email protected]"
+ >
+ Contact
+ <LinkArrow classname="group-hover/mail:opacity-100 opacity-0 transition hidden md:block" />
+ </a>
+ <a
+ className="group/twit flex items-center"
+ target="_blank"
+ href="https://twitter.com/supermemoryai"
+ >
+ Twitter{" "}
+ <LinkArrow classname="group-hover/twit:opacity-100 opacity-0 transition hidden md:block" />
+ </a>
+ <a
+ className="group/git flex items-center"
+ target="_blank"
+ href="https://github.com/dhravya/supermemory"
+ >
+ Github{" "}
+ <LinkArrow classname="group-hover/git:opacity-100 opacity-0 transition hidden md:block" />
+ </a>
+ </div>
+ </footer>
+ );
}
export default Footer;
diff --git a/apps/web/app/(landing)/formSubmitAction.ts b/apps/web/app/(landing)/formSubmitAction.ts
index 9c2eefff..64a4c6d1 100644
--- a/apps/web/app/(landing)/formSubmitAction.ts
+++ b/apps/web/app/(landing)/formSubmitAction.ts
@@ -2,47 +2,47 @@
import { headers } from "next/headers";
const formSubmitAction = async (email: string, token: string) => {
- console.log("email submitted:", email);
- const formBody = `email=${encodeURIComponent(email)}`;
- const h = await headers();
- const ip = h.get("cf-connecting-ip");
+ console.log("email submitted:", email);
+ const formBody = `email=${encodeURIComponent(email)}`;
+ const h = await headers();
+ const ip = h.get("cf-connecting-ip");
- if (ip) {
- if (process.env.RATELIMITER) {
- // @ts-ignore
- const { success } = await process.env.RATELIMITER.limit({
- key: `waitlist-${ip}`,
- });
+ if (ip) {
+ if (process.env.RATELIMITER) {
+ // @ts-ignore
+ const { success } = await process.env.RATELIMITER.limit({
+ key: `waitlist-${ip}`,
+ });
- if (!success) {
- console.error("rate limit exceeded");
- return { value: "Rate limit exceeded", success: false };
- }
- } else {
- console.info("RATELIMITER not found in env");
- }
- } else {
- console.info("cf-connecting-ip not found in headers");
- }
+ if (!success) {
+ console.error("rate limit exceeded");
+ return { value: "Rate limit exceeded", success: false };
+ }
+ } else {
+ console.info("RATELIMITER not found in env");
+ }
+ } else {
+ console.info("cf-connecting-ip not found in headers");
+ }
- const resp = await fetch(
- "https://app.loops.so/api/newsletter-form/clwcn8dde0059m6hobbdw2rwe",
- {
- method: "POST",
- body: formBody,
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- },
- );
+ const resp = await fetch(
+ "https://app.loops.so/api/newsletter-form/clwcn8dde0059m6hobbdw2rwe",
+ {
+ method: "POST",
+ body: formBody,
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ },
+ );
- if (resp.ok) {
- console.log("email submitted successfully");
- return { value: await resp.json(), success: true };
- } else {
- console.error("email submission failed");
- return { value: await resp.text(), success: false };
- }
+ if (resp.ok) {
+ console.log("email submitted successfully");
+ return { value: await resp.json(), success: true };
+ } else {
+ console.error("email submission failed");
+ return { value: await resp.text(), success: false };
+ }
};
export default formSubmitAction;
diff --git a/apps/web/app/(landing)/linkArrow.tsx b/apps/web/app/(landing)/linkArrow.tsx
index a696ca25..7452d6ad 100644
--- a/apps/web/app/(landing)/linkArrow.tsx
+++ b/apps/web/app/(landing)/linkArrow.tsx
@@ -1,41 +1,41 @@
import React from "react";
function LinkArrow({
- classname,
- stroke,
+ classname,
+ stroke,
}: {
- classname?: string;
- stroke?: string;
+ classname?: string;
+ stroke?: string;
}) {
- return (
- <svg
- className={classname}
- stroke={stroke}
- width="24px"
- height="24px"
- viewBox="-2.4 -2.4 28.80 28.80"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"
- >
- <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
- <g
- id="SVGRepo_tracerCarrier"
- strokeLinecap="round"
- strokeLinejoin="round"
- ></g>
- <g id="SVGRepo_iconCarrier">
- {" "}
- <path
- d="M7 17L17 7M17 7H8M17 7V16"
- stroke="currentColor"
- strokeWidth="0.792"
- strokeLinecap="round"
- strokeLinejoin="round"
- ></path>{" "}
- </g>
- </svg>
- );
+ return (
+ <svg
+ className={classname}
+ stroke={stroke}
+ width="24px"
+ height="24px"
+ viewBox="-2.4 -2.4 28.80 28.80"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"
+ >
+ <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
+ <g
+ id="SVGRepo_tracerCarrier"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ ></g>
+ <g id="SVGRepo_iconCarrier">
+ {" "}
+ <path
+ d="M7 17L17 7M17 7H8M17 7V16"
+ stroke="currentColor"
+ strokeWidth="0.792"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ ></path>{" "}
+ </g>
+ </svg>
+ );
}
export default LinkArrow;
diff --git a/apps/web/app/(landing)/page.tsx b/apps/web/app/(landing)/page.tsx
index 2b36df2a..b2b86818 100644
--- a/apps/web/app/(landing)/page.tsx
+++ b/apps/web/app/(landing)/page.tsx
@@ -14,56 +14,56 @@ import { redirect } from "next/navigation";
export const runtime = "edge";
export default async function Home() {
- const user = await auth();
+ const user = await auth();
- if (user) {
- await redirect("/home");
- }
+ if (user) {
+ await redirect("/home");
+ }
- return (
- <>
- <BackgroundPlus />
- <main className="flex overflow-x-hidden relative flex-col items-center px-2 min-h-screen md:px-0 font-geistSans bg-hero-gradient">
- <div className="absolute top-0 -z-10 min-h-screen w-screen overflow-hidden bg-inherit bg-[radial-gradient(ellipse_20%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"></div>
+ return (
+ <>
+ <BackgroundPlus />
+ <main className="flex overflow-x-hidden relative flex-col items-center px-2 min-h-screen md:px-0 font-geistSans bg-hero-gradient">
+ <div className="absolute top-0 -z-10 min-h-screen w-screen overflow-hidden bg-inherit bg-[radial-gradient(ellipse_20%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"></div>
- <Navbar />
+ <Navbar />
- {/* Background gradients */}
- <div className="absolute top-0 left-0 w-full h-full z-[-1]">
- <div className="overflow-x-hidden">
- <div
- className="absolute left-0 h-32 w-[95%] overflow-x-hidden bg-[#369DFD] bg-opacity-70 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
+ {/* Background gradients */}
+ <div className="absolute top-0 left-0 w-full h-full z-[-1]">
+ <div className="overflow-x-hidden">
+ <div
+ className="absolute left-0 h-32 w-[95%] overflow-x-hidden bg-[#369DFD] bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
- {/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/}
- {/* <div className="overflow-x-hidden overflow-y-hidden">
+ {/* a blue gradient line that's slightly tilted with blur (a lotof blur)*/}
+ {/* <div className="overflow-x-hidden overflow-y-hidden">
<div
className="absolute left-0 top-[100%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-40 blur-[337.4px]"
style={{ transform: "rotate(-30deg)" }}
/>
</div> */}
- {/* <div className="overflow-x-hidden overflow-y-hidden">
+ {/* <div className="overflow-x-hidden overflow-y-hidden">
<div className="absolute right-0 top-[145%] h-40 w-[17%] overflow-x-hidden bg-[#369DFD] bg-opacity-20 blur-[110px]" />
</div> */}
- </div>
+ </div>
- {/* Hero section */}
- <Hero />
- <Showcases />
- <Services />
+ {/* Hero section */}
+ <Hero />
+ <Showcases />
+ <Services />
- {/* Features section */}
- <Features />
+ {/* Features section */}
+ <Features />
- <RotatingIcons />
- <Cta />
+ <RotatingIcons />
+ <Cta />
- <Toaster />
- <Footer />
- </main>
- </>
- );
+ <Toaster />
+ <Footer />
+ </main>
+ </>
+ );
}
diff --git a/apps/web/app/(landing)/twitterLink.tsx b/apps/web/app/(landing)/twitterLink.tsx
index a69fdee4..61c17cfb 100644
--- a/apps/web/app/(landing)/twitterLink.tsx
+++ b/apps/web/app/(landing)/twitterLink.tsx
@@ -7,118 +7,118 @@ import { Twitter } from "@repo/ui/components/icons";
import LinkArrow from "./linkArrow";
export function TwitterBorder() {
- return (
- <a href="https://x.com/supermemoryai" target="_blank">
- <HoverBorderGradient
- containerClassName="rounded-full"
- className="group/anchor z-20 pl-10 pr- flex items-center justify-center gap-4 rounded-full text-white/80 bg-white/5 py-2 text-sm"
- >
- <Twitter className="h-4 w-4" />
- <div className="flex items-center justify-center">
- {" "}
- Follow us on Twitter{" "}
- <LinkArrow
- classname="group-hover/anchor:opacity-100 opacity-0 transition"
- stroke="#ffffff"
- />
- </div>
- </HoverBorderGradient>
- </a>
- );
+ return (
+ <a href="https://x.com/supermemoryai" target="_blank">
+ <HoverBorderGradient
+ containerClassName="rounded-full"
+ className="group/anchor z-20 pl-10 pr- flex items-center justify-center gap-4 rounded-full text-white/80 bg-white/5 py-2 text-sm"
+ >
+ <Twitter className="h-4 w-4" />
+ <div className="flex items-center justify-center">
+ {" "}
+ Follow us on Twitter{" "}
+ <LinkArrow
+ classname="group-hover/anchor:opacity-100 opacity-0 transition"
+ stroke="#ffffff"
+ />
+ </div>
+ </HoverBorderGradient>
+ </a>
+ );
}
type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT";
export function HoverBorderGradient({
- children,
- containerClassName,
- className,
- as: Tag = "button",
- duration = 1,
- clockwise = true,
- ...props
+ children,
+ containerClassName,
+ className,
+ as: Tag = "button",
+ duration = 1,
+ clockwise = true,
+ ...props
}: React.PropsWithChildren<
- {
- as?: React.ElementType;
- containerClassName?: string;
- className?: string;
- duration?: number;
- clockwise?: boolean;
- } & React.HTMLAttributes<HTMLElement>
+ {
+ as?: React.ElementType;
+ containerClassName?: string;
+ className?: string;
+ duration?: number;
+ clockwise?: boolean;
+ } & React.HTMLAttributes<HTMLElement>
>) {
- const [hovered, setHovered] = useState<boolean>(false);
- const [direction, setDirection] = useState<Direction>("TOP");
+ const [hovered, setHovered] = useState<boolean>(false);
+ const [direction, setDirection] = useState<Direction>("TOP");
- const rotateDirection = (currentDirection: Direction): Direction => {
- const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"];
- const currentIndex = directions.indexOf(currentDirection);
- const nextIndex = clockwise
- ? (currentIndex - 1 + directions.length) % directions.length
- : (currentIndex + 1) % directions.length;
- if (!directions[nextIndex]) {
- return directions[0]!;
- }
- return directions[nextIndex]!;
- };
+ const rotateDirection = (currentDirection: Direction): Direction => {
+ const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"];
+ const currentIndex = directions.indexOf(currentDirection);
+ const nextIndex = clockwise
+ ? (currentIndex - 1 + directions.length) % directions.length
+ : (currentIndex + 1) % directions.length;
+ if (!directions[nextIndex]) {
+ return directions[0]!;
+ }
+ return directions[nextIndex]!;
+ };
- const movingMap: Record<Direction, string> = {
- TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
- LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
- BOTTOM:
- "radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
- RIGHT:
- "radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
- };
+ const movingMap: Record<Direction, string> = {
+ TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ BOTTOM:
+ "radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ RIGHT:
+ "radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ };
- const highlight =
- "radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)";
- useEffect(() => {
- if (!hovered) {
- const interval = setInterval(() => {
- setDirection((prevState) => rotateDirection(prevState));
- }, duration * 1000);
- return () => clearInterval(interval);
- }
- }, [hovered]);
- return (
- <Tag
- onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
- setHovered(true);
- }}
- onMouseLeave={() => setHovered(false)}
- className={cn(
- "relative h-min w-fit mt-10 transition duration-500 group/anchor flex items-center justify-center gap-4 rounded-full text-white/80 bg-white/5 text-sm",
- containerClassName,
- )}
- {...props}
- >
- <div
- className={cn(
- "z-10 w-auto rounded-[inherit] bg-white/5 px-4 py-2 text-white",
- className,
- )}
- >
- {children}
- </div>
- <motion.div
- className={cn(
- "absolute inset-0 z-0 flex-none overflow-hidden rounded-[inherit]",
- )}
- style={{
- filter: "blur(2px)",
- position: "absolute",
- width: "100%",
- height: "100%",
- }}
- initial={{ background: movingMap[direction] }}
- animate={{
- background: hovered
- ? [movingMap[direction], highlight]
- : movingMap[direction],
- }}
- transition={{ ease: "linear", duration: duration ?? 1 }}
- />
- <div className="z-1 absolute inset-[2px] flex-none rounded-[100px] bg-[#121E2C]" />
- </Tag>
- );
+ const highlight =
+ "radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)";
+ useEffect(() => {
+ if (!hovered) {
+ const interval = setInterval(() => {
+ setDirection((prevState) => rotateDirection(prevState));
+ }, duration * 1000);
+ return () => clearInterval(interval);
+ }
+ }, [hovered]);
+ return (
+ <Tag
+ onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
+ setHovered(true);
+ }}
+ onMouseLeave={() => setHovered(false)}
+ className={cn(
+ "relative h-min w-fit mt-10 transition duration-500 group/anchor flex items-center justify-center gap-4 rounded-full text-white/80 bg-white/5 text-sm",
+ containerClassName,
+ )}
+ {...props}
+ >
+ <div
+ className={cn(
+ "z-10 w-auto rounded-[inherit] bg-white/5 px-4 py-2 text-white",
+ className,
+ )}
+ >
+ {children}
+ </div>
+ <motion.div
+ className={cn(
+ "absolute inset-0 z-0 flex-none overflow-hidden rounded-[inherit]",
+ )}
+ style={{
+ filter: "blur(2px)",
+ position: "absolute",
+ width: "100%",
+ height: "100%",
+ }}
+ initial={{ background: movingMap[direction] }}
+ animate={{
+ background: hovered
+ ? [movingMap[direction], highlight]
+ : movingMap[direction],
+ }}
+ transition={{ ease: "linear", duration: duration ?? 1 }}
+ />
+ <div className="z-1 absolute inset-[2px] flex-none rounded-[100px] bg-[#121E2C]" />
+ </Tag>
+ );
}
diff --git a/apps/web/app/(quicklinks)/extension/route.ts b/apps/web/app/(quicklinks)/extension/route.ts
index 4811d808..6b017306 100644
--- a/apps/web/app/(quicklinks)/extension/route.ts
+++ b/apps/web/app/(quicklinks)/extension/route.ts
@@ -1,7 +1,7 @@
import { redirect } from "next/navigation";
export async function GET() {
- return redirect(
- "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc?authuser=0&hl=en-GB",
- );
+ return redirect(
+ "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc?authuser=0&hl=en-GB",
+ );
}
diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts
index 16dcf169..a2cdb4f5 100644
--- a/apps/web/app/actions/doers.ts
+++ b/apps/web/app/actions/doers.ts
@@ -3,14 +3,14 @@
import { revalidatePath } from "next/cache";
import { db } from "../../server/db";
import {
- canvas,
- chatHistory,
- chatThreads,
- contentToSpace,
- space,
- spacesAccess,
- storedContent,
- users,
+ canvas,
+ chatHistory,
+ chatThreads,
+ contentToSpace,
+ space,
+ spacesAccess,
+ storedContent,
+ users,
} from "../../server/db/schema";
import { ServerActionReturnType } from "./types";
import { auth } from "../../server/auth";
@@ -24,688 +24,687 @@ import { redirect } from "next/navigation";
import { tweetToMd } from "@repo/shared-types/utils";
export const createSpace = async (
- input: string | FormData,
+ input: string | FormData,
): ServerActionReturnType<number> => {
- const data = await auth();
-
- if (!data || !data.user) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- if (typeof input === "object") {
- input = (input as FormData).get("name") as string;
- }
-
- try {
- const resp = await db
- .insert(space)
- .values({ name: input, user: data.user.id, createdAt: new Date() });
-
- revalidatePath("/home");
- return { success: true, data: resp.meta.last_row_id };
- } catch (e: unknown) {
- const error = e as Error;
- if (
- error.message.includes("D1_ERROR: UNIQUE constraint failed: space.name")
- ) {
- return { success: false, data: 0, error: "Space already exists" };
- } else {
- return {
- success: false,
- data: 0,
- error: "Failed to create space with error: " + error.message,
- };
- }
- }
+ const data = await auth();
+
+ if (!data || !data.user) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ if (typeof input === "object") {
+ input = (input as FormData).get("name") as string;
+ }
+
+ try {
+ const resp = await db
+ .insert(space)
+ .values({ name: input, user: data.user.id, createdAt: new Date() });
+
+ revalidatePath("/home");
+ return { success: true, data: resp.meta.last_row_id };
+ } catch (e: unknown) {
+ const error = e as Error;
+ if (
+ error.message.includes("D1_ERROR: UNIQUE constraint failed: space.name")
+ ) {
+ return { success: false, data: 0, error: "Space already exists" };
+ } else {
+ return {
+ success: false,
+ data: 0,
+ error: "Failed to create space with error: " + error.message,
+ };
+ }
+ }
};
const typeDecider = (content: string) => {
- // if the content is a URL, then it's a page. if its a URL with https://x.com/user/status/123, then it's a tweet. else, it's a note.
- // do strict checking with regex
- if (content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)) {
- return "tweet";
- } else if (content.match(/https?:\/\/[\w\.]+/)) {
- return "page";
- } else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
- return "page";
- } else {
- return "note";
- }
+ // if the content is a URL, then it's a page. if its a URL with https://x.com/user/status/123, then it's a tweet. else, it's a note.
+ // do strict checking with regex
+ if (content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)) {
+ return "tweet";
+ } else if (content.match(/https?:\/\/[\w\.]+/)) {
+ return "page";
+ } else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
+ return "page";
+ } else {
+ return "note";
+ }
};
export const limit = async (
- userId: string,
- type = "page",
- items: number = 1,
+ userId: string,
+ type = "page",
+ items: number = 1,
) => {
- const countResult = await db
- .select({
- count: sql<number>`count(*)`.mapWith(Number),
- })
- .from(storedContent)
- .where(and(eq(storedContent.userId, userId), eq(storedContent.type, type)));
-
- const currentCount = countResult[0]?.count || 0;
- const totalLimit = LIMITS[type as keyof typeof LIMITS];
- const remainingLimit = totalLimit - currentCount;
-
- return items <= remainingLimit;
+ const countResult = await db
+ .select({
+ count: sql<number>`count(*)`.mapWith(Number),
+ })
+ .from(storedContent)
+ .where(and(eq(storedContent.userId, userId), eq(storedContent.type, type)));
+
+ const currentCount = countResult[0]?.count || 0;
+ const totalLimit = LIMITS[type as keyof typeof LIMITS];
+ const remainingLimit = totalLimit - currentCount;
+
+ return items <= remainingLimit;
};
export const addUserToSpace = async (userEmail: string, spaceId: number) => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- // We need to make sure that the user owns the space
- const spaceData = await db
- .select()
- .from(space)
- .where(and(eq(space.id, spaceId), eq(space.user, data.user.id)))
- .all();
-
- if (spaceData.length === 0) {
- return {
- success: false,
- error: "You do not own this space",
- };
- }
-
- try {
- await db.insert(spacesAccess).values({
- spaceId: spaceId,
- userEmail: userEmail,
- });
-
- revalidatePath("/space/" + spaceId);
-
- return {
- success: true,
- };
- } catch (e) {
- return {
- success: false,
- error: (e as Error).message,
- };
- }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ // We need to make sure that the user owns the space
+ const spaceData = await db
+ .select()
+ .from(space)
+ .where(and(eq(space.id, spaceId), eq(space.user, data.user.id)))
+ .all();
+
+ if (spaceData.length === 0) {
+ return {
+ success: false,
+ error: "You do not own this space",
+ };
+ }
+
+ try {
+ await db.insert(spacesAccess).values({
+ spaceId: spaceId,
+ userEmail: userEmail,
+ });
+
+ revalidatePath("/space/" + spaceId);
+
+ return {
+ success: true,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ error: (e as Error).message,
+ };
+ }
};
const getTweetData = async (tweetID: string) => {
- const url = `https://cdn.syndication.twimg.com/tweet-result?id=${tweetID}&lang=en&features=tfw_timeline_list%3A%3Btfw_follower_count_sunset%3Atrue%3Btfw_tweet_edit_backend%3Aon%3Btfw_refsrc_session%3Aon%3Btfw_fosnr_soft_interventions_enabled%3Aon%3Btfw_show_birdwatch_pivots_enabled%3Aon%3Btfw_show_business_verified_badge%3Aon%3Btfw_duplicate_scribes_to_settings%3Aon%3Btfw_use_profile_image_shape_enabled%3Aon%3Btfw_show_blue_verified_badge%3Aon%3Btfw_legacy_timeline_sunset%3Atrue%3Btfw_show_gov_verified_badge%3Aon%3Btfw_show_business_affiliate_badge%3Aon%3Btfw_tweet_edit_frontend%3Aon&token=4c2mmul6mnh`;
-
- const resp = await fetch(url, {
- headers: {
- "User-Agent":
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
- Accept: "application/json",
- "Accept-Language": "en-US,en;q=0.5",
- "Accept-Encoding": "gzip, deflate, br",
- Connection: "keep-alive",
- "Upgrade-Insecure-Requests": "1",
- "Cache-Control": "max-age=0",
- TE: "Trailers",
- },
- });
- console.log(resp.status);
- const data = (await resp.json()) as Tweet;
-
- return data;
+ const url = `https://cdn.syndication.twimg.com/tweet-result?id=${tweetID}&lang=en&features=tfw_timeline_list%3A%3Btfw_follower_count_sunset%3Atrue%3Btfw_tweet_edit_backend%3Aon%3Btfw_refsrc_session%3Aon%3Btfw_fosnr_soft_interventions_enabled%3Aon%3Btfw_show_birdwatch_pivots_enabled%3Aon%3Btfw_show_business_verified_badge%3Aon%3Btfw_duplicate_scribes_to_settings%3Aon%3Btfw_use_profile_image_shape_enabled%3Aon%3Btfw_show_blue_verified_badge%3Aon%3Btfw_legacy_timeline_sunset%3Atrue%3Btfw_show_gov_verified_badge%3Aon%3Btfw_show_business_affiliate_badge%3Aon%3Btfw_tweet_edit_frontend%3Aon&token=4c2mmul6mnh`;
+
+ const resp = await fetch(url, {
+ headers: {
+ "User-Agent":
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
+ Accept: "application/json",
+ "Accept-Language": "en-US,en;q=0.5",
+ "Accept-Encoding": "gzip, deflate, br",
+ Connection: "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "Cache-Control": "max-age=0",
+ TE: "Trailers",
+ },
+ });
+ console.log(resp.status);
+ const data = (await resp.json()) as Tweet;
+
+ return data;
};
export const createMemory = async (input: {
- content: string;
- spaces?: number[];
+ content: string;
+ spaces?: number[];
}): ServerActionReturnType<number> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const type = typeDecider(input.content);
-
- let pageContent = input.content;
- let metadata: Awaited<ReturnType<typeof getMetaData>>;
-
- if (!(await limit(data.user.id, type))) {
- return {
- success: false,
- data: 0,
- error: `You have exceeded the limit of ${LIMITS[type as keyof typeof LIMITS]} ${type}s.`,
- };
- }
-
- let noteId = 0;
-
- if (type === "page") {
- const response = await fetch("https://md.dhr.wtf/?url=" + input.content, {
- headers: {
- Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
- },
- });
- pageContent = await response.text();
-
- try {
- metadata = await getMetaData(input.content);
- } catch (e) {
- return {
- success: false,
- error: "Failed to fetch metadata for the page. Please try again later.",
- };
- }
- } else if (type === "tweet") {
- const tweet = await getTweetData(input.content.split("/").pop() as string);
- pageContent = tweetToMd(tweet);
- metadata = {
- baseUrl: input.content,
- description: tweet.text.slice(0, 200),
- image: tweet.user.profile_image_url_https,
- title: `Tweet by ${tweet.user.name}`,
- };
- } else if (type === "note") {
- pageContent = input.content;
- noteId = new Date().getTime();
- metadata = {
- baseUrl: `https://supermemory.ai/note/${noteId}`,
- description: `Note created at ${new Date().toLocaleString()}`,
- image: "https://supermemory.ai/logo.png",
- title: `${pageContent.slice(0, 20)} ${pageContent.length > 20 ? "..." : ""}`,
- };
- } else {
- return {
- success: false,
- data: 0,
- error: "Invalid type",
- };
- }
-
- let storeToSpaces = input.spaces;
-
- if (!storeToSpaces) {
- storeToSpaces = [];
- }
-
- const vectorSaveResponse = await fetch(
- `${process.env.BACKEND_BASE_URL}/api/add`,
- {
- method: "POST",
- body: JSON.stringify({
- pageContent,
- title: metadata.title,
- description: metadata.description,
- url: metadata.baseUrl,
- spaces: storeToSpaces.map((spaceId) => spaceId.toString()),
- user: data.user.id,
- type,
- }),
- headers: {
- "Content-Type": "application/json",
- Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
- },
- },
- );
-
- if (!vectorSaveResponse.ok) {
- const errorData = await vectorSaveResponse.text();
- console.error(errorData);
- return {
- success: false,
- data: 0,
- error: `Failed to save to vector store. Backend returned error: ${errorData}`,
- };
- }
-
- let contentId: number | undefined;
-
- const response = (await vectorSaveResponse.json()) as {
- status: string;
- chunkedInput: string;
- message?: string;
- };
-
- try {
- if (response.status !== "ok") {
- if (response.status === "error") {
- return {
- success: false,
- data: 0,
- error: response.message,
- };
- } else {
- return {
- success: false,
- data: 0,
- error: `Failed to save to vector store. Backend returned error: ${response.message}`,
- };
- }
- }
- } catch (e) {
- return {
- success: false,
- data: 0,
- error: `Failed to save to vector store. Backend returned error: ${e}`,
- };
- }
-
- const saveToDbUrl =
- (metadata.baseUrl.split("#supermemory-user-")[0] ?? metadata.baseUrl) +
- "#supermemory-user-" +
- data.user.id;
-
- // Insert into database
- try {
- const insertResponse = await db
- .insert(storedContent)
- .values({
- content: pageContent,
- title: metadata.title,
- description: metadata.description,
- url: saveToDbUrl,
- baseUrl: saveToDbUrl,
- image: metadata.image,
- savedAt: new Date(),
- userId: data.user.id,
- type,
- noteId,
- })
- .returning({ id: storedContent.id });
- revalidatePath("/memories");
- revalidatePath("/home");
-
- contentId = insertResponse[0]?.id;
- } catch (e) {
- const error = e as Error;
- console.log("Error: ", error.message);
-
- if (
- error.message.includes(
- "D1_ERROR: UNIQUE constraint failed: storedContent.baseUrl",
- )
- ) {
- return {
- success: false,
- data: 0,
- error: "Content already exists",
- };
- }
-
- return {
- success: false,
- data: 0,
- error: "Failed to save to database with error: " + error.message,
- };
- }
-
- if (!contentId) {
- return {
- success: false,
- data: 0,
- error: "Something went wrong while saving the document to the database",
- };
- }
-
- if (storeToSpaces.length > 0) {
- // Adding the many-to-many relationship between content and spaces
- const spaceData = await db
- .select()
- .from(space)
- .where(
- and(inArray(space.id, storeToSpaces), eq(space.user, data.user.id)),
- )
- .all();
-
- await Promise.all(
- spaceData.map(async (s) => {
- await db
- .insert(contentToSpace)
- .values({ contentId: contentId, spaceId: s.id });
-
- await db.update(space).set({ numItems: s.numItems + 1 });
- }),
- );
- }
-
- return {
- success: true,
- data: 1,
- };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const type = typeDecider(input.content);
+
+ let pageContent = input.content;
+ let metadata: Awaited<ReturnType<typeof getMetaData>>;
+
+ if (!(await limit(data.user.id, type))) {
+ return {
+ success: false,
+ data: 0,
+ error: `You have exceeded the limit of ${LIMITS[type as keyof typeof LIMITS]} ${type}s.`,
+ };
+ }
+
+ let noteId = 0;
+
+ if (type === "page") {
+ const response = await fetch("https://md.dhr.wtf/?url=" + input.content, {
+ headers: {
+ Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
+ },
+ });
+ pageContent = await response.text();
+
+ try {
+ metadata = await getMetaData(input.content);
+ } catch (e) {
+ return {
+ success: false,
+ error: "Failed to fetch metadata for the page. Please try again later.",
+ };
+ }
+ } else if (type === "tweet") {
+ const tweet = await getTweetData(input.content.split("/").pop() as string);
+ pageContent = tweetToMd(tweet);
+ metadata = {
+ baseUrl: input.content,
+ description: tweet.text.slice(0, 200),
+ image: tweet.user.profile_image_url_https,
+ title: `Tweet by ${tweet.user.name}`,
+ };
+ } else if (type === "note") {
+ pageContent = input.content;
+ noteId = new Date().getTime();
+ metadata = {
+ baseUrl: `https://supermemory.ai/note/${noteId}`,
+ description: `Note created at ${new Date().toLocaleString()}`,
+ image: "https://supermemory.ai/logo.png",
+ title: `${pageContent.slice(0, 20)} ${pageContent.length > 20 ? "..." : ""}`,
+ };
+ } else {
+ return {
+ success: false,
+ data: 0,
+ error: "Invalid type",
+ };
+ }
+
+ let storeToSpaces = input.spaces;
+
+ if (!storeToSpaces) {
+ storeToSpaces = [];
+ }
+
+ const vectorSaveResponse = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/add`,
+ {
+ method: "POST",
+ body: JSON.stringify({
+ pageContent,
+ title: metadata.title,
+ description: metadata.description,
+ url: metadata.baseUrl,
+ spaces: storeToSpaces.map((spaceId) => spaceId.toString()),
+ user: data.user.id,
+ type,
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
+ },
+ },
+ );
+
+ if (!vectorSaveResponse.ok) {
+ const errorData = await vectorSaveResponse.text();
+ console.error(errorData);
+ return {
+ success: false,
+ data: 0,
+ error: `Failed to save to vector store. Backend returned error: ${errorData}`,
+ };
+ }
+
+ let contentId: number | undefined;
+
+ const response = (await vectorSaveResponse.json()) as {
+ status: string;
+ chunkedInput: string;
+ message?: string;
+ };
+
+ try {
+ if (response.status !== "ok") {
+ if (response.status === "error") {
+ return {
+ success: false,
+ data: 0,
+ error: response.message,
+ };
+ } else {
+ return {
+ success: false,
+ data: 0,
+ error: `Failed to save to vector store. Backend returned error: ${response.message}`,
+ };
+ }
+ }
+ } catch (e) {
+ return {
+ success: false,
+ data: 0,
+ error: `Failed to save to vector store. Backend returned error: ${e}`,
+ };
+ }
+
+ const saveToDbUrl =
+ (metadata.baseUrl.split("#supermemory-user-")[0] ?? metadata.baseUrl) +
+ "#supermemory-user-" +
+ data.user.id;
+
+ // Insert into database
+ try {
+ const insertResponse = await db
+ .insert(storedContent)
+ .values({
+ content: pageContent,
+ title: metadata.title,
+ description: metadata.description,
+ url: saveToDbUrl,
+ baseUrl: saveToDbUrl,
+ image: metadata.image,
+ savedAt: new Date(),
+ userId: data.user.id,
+ type,
+ noteId,
+ })
+ .returning({ id: storedContent.id });
+ revalidatePath("/memories");
+ revalidatePath("/home");
+
+ contentId = insertResponse[0]?.id;
+ } catch (e) {
+ const error = e as Error;
+ console.log("Error: ", error.message);
+
+ if (
+ error.message.includes(
+ "D1_ERROR: UNIQUE constraint failed: storedContent.baseUrl",
+ )
+ ) {
+ return {
+ success: false,
+ data: 0,
+ error: "Content already exists",
+ };
+ }
+
+ return {
+ success: false,
+ data: 0,
+ error: "Failed to save to database with error: " + error.message,
+ };
+ }
+
+ if (!contentId) {
+ return {
+ success: false,
+ data: 0,
+ error: "Something went wrong while saving the document to the database",
+ };
+ }
+
+ if (storeToSpaces.length > 0) {
+ // Adding the many-to-many relationship between content and spaces
+ const spaceData = await db
+ .select()
+ .from(space)
+ .where(
+ and(inArray(space.id, storeToSpaces), eq(space.user, data.user.id)),
+ )
+ .all();
+
+ await Promise.all(
+ spaceData.map(async (s) => {
+ await db
+ .insert(contentToSpace)
+ .values({ contentId: contentId, spaceId: s.id });
+
+ await db.update(space).set({ numItems: s.numItems + 1 });
+ }),
+ );
+ }
+
+ return {
+ success: true,
+ data: 1,
+ };
};
export const createChatThread = async (
- firstMessage: string,
+ firstMessage: string,
): ServerActionReturnType<string> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const thread = await db
- .insert(chatThreads)
- .values({
- firstMessage,
- userId: data.user.id,
- })
- .returning({ id: chatThreads.id })
- .execute();
-
- console.log(thread);
-
- if (!thread[0]) {
- return {
- success: false,
- error: "Failed to create chat thread",
- };
- }
-
- return { success: true, data: thread[0].id };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const thread = await db
+ .insert(chatThreads)
+ .values({
+ firstMessage,
+ userId: data.user.id,
+ })
+ .returning({ id: chatThreads.id })
+ .execute();
+
+ console.log(thread);
+
+ if (!thread[0]) {
+ return {
+ success: false,
+ error: "Failed to create chat thread",
+ };
+ }
+
+ return { success: true, data: thread[0].id };
};
export const createChatObject = async (
- threadId: string,
- chatHistorySoFar: ChatHistory[],
+ threadId: string,
+ chatHistorySoFar: ChatHistory[],
): ServerActionReturnType<boolean> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const lastChat = chatHistorySoFar[chatHistorySoFar.length - 1];
- if (!lastChat) {
- return {
- success: false,
- data: false,
- error: "No chat object found",
- };
- }
- console.log("sources: ", lastChat.answer.sources);
-
- const saved = await db.insert(chatHistory).values({
- question: lastChat.question,
- answer: lastChat.answer.parts.map((part) => part.text).join(""),
- answerSources: JSON.stringify(lastChat.answer.sources),
- threadId,
- });
-
- if (!saved) {
- return {
- success: false,
- data: false,
- error: "Failed to save chat object",
- };
- }
-
- return {
- success: true,
- data: true,
- };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const lastChat = chatHistorySoFar[chatHistorySoFar.length - 1];
+ if (!lastChat) {
+ return {
+ success: false,
+ data: false,
+ error: "No chat object found",
+ };
+ }
+
+ const saved = await db.insert(chatHistory).values({
+ question: lastChat.question,
+ answer: lastChat.answer.parts.map((part) => part.text).join(""),
+ answerSources: JSON.stringify(lastChat.answer.sources),
+ threadId,
+ });
+
+ if (!saved) {
+ return {
+ success: false,
+ data: false,
+ error: "Failed to save chat object",
+ };
+ }
+
+ return {
+ success: true,
+ data: true,
+ };
};
export const linkTelegramToUser = async (
- telegramUser: string,
+ telegramUser: string,
): ServerActionReturnType<boolean> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const user = await db
- .update(users)
- .set({ telegramId: decipher(telegramUser) })
- .where(eq(users.id, data.user.id))
- .execute();
-
- if (!user) {
- return {
- success: false,
- data: false,
- error: "Failed to link telegram to user",
- };
- }
-
- return {
- success: true,
- data: true,
- };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const user = await db
+ .update(users)
+ .set({ telegramId: decipher(telegramUser) })
+ .where(eq(users.id, data.user.id))
+ .execute();
+
+ if (!user) {
+ return {
+ success: false,
+ data: false,
+ error: "Failed to link telegram to user",
+ };
+ }
+
+ return {
+ success: true,
+ data: true,
+ };
};
export const deleteItem = async (id: number) => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- try {
- const deletedItem = await db
- .delete(storedContent)
- .where(eq(storedContent.id, id))
- .returning();
-
- if (!deletedItem) {
- return {
- success: false,
- error: "Failed to delete item",
- };
- }
-
- const actualUrl = deletedItem[0]?.url.split("#supermemory-user-")[0];
-
- console.log(
- "ACTUAL URL BADBAL;KFJDLKASJFLKDSJFLKDSJFKD LSFJSLKDJF :",
- actualUrl,
- );
-
- await fetch(`${process.env.BACKEND_BASE_URL}/api/delete`, {
- method: "POST",
- body: JSON.stringify({
- websiteUrl: actualUrl,
- user: data.user.id,
- }),
- headers: {
- "Content-Type": "application/json",
- Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
- },
- });
-
- revalidatePath("/memories");
-
- return {
- success: true,
- message: "in-sync",
- };
- } catch (error) {
- return {
- success: false,
- error,
- message: "An error occured while saving your canvas",
- };
- }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ try {
+ const deletedItem = await db
+ .delete(storedContent)
+ .where(eq(storedContent.id, id))
+ .returning();
+
+ if (!deletedItem) {
+ return {
+ success: false,
+ error: "Failed to delete item",
+ };
+ }
+
+ const actualUrl = deletedItem[0]?.url.split("#supermemory-user-")[0];
+
+ console.log(
+ "ACTUAL URL BADBAL;KFJDLKASJFLKDSJFLKDSJFKD LSFJSLKDJF :",
+ actualUrl,
+ );
+
+ await fetch(`${process.env.BACKEND_BASE_URL}/api/delete`, {
+ method: "POST",
+ body: JSON.stringify({
+ websiteUrl: actualUrl,
+ user: data.user.id,
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
+ },
+ });
+
+ revalidatePath("/memories");
+
+ return {
+ success: true,
+ message: "in-sync",
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error,
+ message: "An error occured while saving your canvas",
+ };
+ }
};
// TODO: also move in vectorize
export const moveItem = async (
- id: number,
- spaces: number[],
- fromSpace?: number | undefined,
+ id: number,
+ spaces: number[],
+ fromSpace?: number | undefined,
): ServerActionReturnType<boolean> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- try {
- if (fromSpace) {
- await db
- .delete(contentToSpace)
- .where(
- and(
- eq(contentToSpace.contentId, id),
- eq(contentToSpace.spaceId, fromSpace),
- ),
- );
- }
-
- const addedItem = await db
- .insert(contentToSpace)
- .values(spaces.map((spaceId) => ({ contentId: id, spaceId })))
- .returning();
-
- if (!(addedItem.length > 0)) {
- return {
- success: false,
- error: "Failed to move item",
- };
- }
-
- await db
- .update(space)
- .set({ numItems: sql<number>`numItems + 1` })
- .where(eq(space.id, addedItem[0]?.spaceId!));
-
- if (!addedItem) {
- return {
- success: false,
- error: "Failed to move item",
- };
- }
-
- revalidatePath("/memories");
-
- return {
- success: true,
- data: true,
- };
- } catch (error) {
- return {
- success: false,
- error: (error as Error).message,
- };
- }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ try {
+ if (fromSpace) {
+ await db
+ .delete(contentToSpace)
+ .where(
+ and(
+ eq(contentToSpace.contentId, id),
+ eq(contentToSpace.spaceId, fromSpace),
+ ),
+ );
+ }
+
+ const addedItem = await db
+ .insert(contentToSpace)
+ .values(spaces.map((spaceId) => ({ contentId: id, spaceId })))
+ .returning();
+
+ if (!(addedItem.length > 0)) {
+ return {
+ success: false,
+ error: "Failed to move item",
+ };
+ }
+
+ await db
+ .update(space)
+ .set({ numItems: sql<number>`numItems + 1` })
+ .where(eq(space.id, addedItem[0]?.spaceId!));
+
+ if (!addedItem) {
+ return {
+ success: false,
+ error: "Failed to move item",
+ };
+ }
+
+ revalidatePath("/memories");
+
+ return {
+ success: true,
+ data: true,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: (error as Error).message,
+ };
+ }
};
export const createCanvas = async () => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const canvases = await db
- .select()
- .from(canvas)
- .where(eq(canvas.userId, data.user.id));
-
- if (canvases.length >= 5) {
- return {
- success: false,
- message: "A user currently can only have 5 canvases",
- };
- }
-
- const resp = await db
- .insert(canvas)
- .values({ userId: data.user.id })
- .returning({ id: canvas.id });
- redirect(`/canvas/${resp[0]!.id}`);
- // TODO INVESTIGATE: NO REDIRECT INSIDE TRY CATCH BLOCK
- // try {
- // const resp = await db
- // .insert(canvas)
- // .values({ userId: data.user.id }).returning({id: canvas.id});
- // return redirect(`/canvas/${resp[0]!.id}`);
- // } catch (e: unknown) {
- // const error = e as Error;
- // if (
- // error.message.includes("D1_ERROR: UNIQUE constraint failed: space.name")
- // ) {
- // return { success: false, data: 0, error: "Space already exists" };
- // } else {
- // return {
- // success: false,
- // data: 0,
- // error: "Failed to create space with error: " + error.message,
- // };
- // }
- // }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const canvases = await db
+ .select()
+ .from(canvas)
+ .where(eq(canvas.userId, data.user.id));
+
+ if (canvases.length >= 5) {
+ return {
+ success: false,
+ message: "A user currently can only have 5 canvases",
+ };
+ }
+
+ const resp = await db
+ .insert(canvas)
+ .values({ userId: data.user.id })
+ .returning({ id: canvas.id });
+ redirect(`/canvas/${resp[0]!.id}`);
+ // TODO INVESTIGATE: NO REDIRECT INSIDE TRY CATCH BLOCK
+ // try {
+ // const resp = await db
+ // .insert(canvas)
+ // .values({ userId: data.user.id }).returning({id: canvas.id});
+ // return redirect(`/canvas/${resp[0]!.id}`);
+ // } catch (e: unknown) {
+ // const error = e as Error;
+ // if (
+ // error.message.includes("D1_ERROR: UNIQUE constraint failed: space.name")
+ // ) {
+ // return { success: false, data: 0, error: "Space already exists" };
+ // } else {
+ // return {
+ // success: false,
+ // data: 0,
+ // error: "Failed to create space with error: " + error.message,
+ // };
+ // }
+ // }
};
export const SaveCanvas = async ({
- id,
- data,
+ id,
+ data,
}: {
- id: string;
- data: string;
+ id: string;
+ data: string;
}) => {
- console.log({ id, data });
- try {
- await process.env.CANVAS_SNAPS.put(id, data);
- return {
- success: true,
- message: "in-sync",
- };
- } catch (error) {
- return {
- success: false,
- error,
- message: "An error occured while saving your canvas",
- };
- }
+ console.log({ id, data });
+ try {
+ await process.env.CANVAS_SNAPS.put(id, data);
+ return {
+ success: true,
+ message: "in-sync",
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error,
+ message: "An error occured while saving your canvas",
+ };
+ }
};
export const deleteCanvas = async (id: string) => {
- try {
- await process.env.CANVAS_SNAPS.delete(id);
- await db.delete(canvas).where(eq(canvas.id, id));
- return {
- success: true,
- message: "in-sync",
- };
- } catch (error) {
- return {
- success: false,
- error,
- message: "An error occured while saving your canvas",
- };
- }
+ try {
+ await process.env.CANVAS_SNAPS.delete(id);
+ await db.delete(canvas).where(eq(canvas.id, id));
+ return {
+ success: true,
+ message: "in-sync",
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error,
+ message: "An error occured while saving your canvas",
+ };
+ }
};
export async function AddCanvasInfo({
- id,
- title,
- description,
+ id,
+ title,
+ description,
}: {
- id: string;
- title: string;
- description: string;
+ id: string;
+ title: string;
+ description: string;
}) {
- try {
- await db
- .update(canvas)
- .set({ description, title })
- .where(eq(canvas.id, id));
- return {
- success: true,
- message: "info updated successfully",
- };
- } catch (error) {
- return {
- success: false,
- message: "something went wrong :/",
- };
- }
+ try {
+ await db
+ .update(canvas)
+ .set({ description, title })
+ .where(eq(canvas.id, id));
+ return {
+ success: true,
+ message: "info updated successfully",
+ };
+ } catch (error) {
+ return {
+ success: false,
+ message: "something went wrong :/",
+ };
+ }
}
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
index f21a942d..1838ee1c 100644
--- a/apps/web/app/actions/fetchers.ts
+++ b/apps/web/app/actions/fetchers.ts
@@ -3,359 +3,358 @@
import { and, asc, eq, exists, inArray, not, or, sql } from "drizzle-orm";
import { db } from "../../server/db";
import {
- canvas,
- chatHistory,
- ChatThread,
- chatThreads,
- Content,
- contentToSpace,
- space,
- spacesAccess,
- storedContent,
- StoredSpace,
- users,
+ canvas,
+ chatHistory,
+ ChatThread,
+ chatThreads,
+ Content,
+ contentToSpace,
+ space,
+ spacesAccess,
+ storedContent,
+ StoredSpace,
+ users,
} from "../../server/db/schema";
import { ServerActionReturnType, Space } from "./types";
import { auth } from "../../server/auth";
import { ChatHistory, SourceZod } from "@repo/shared-types";
-import { ChatHistory as ChatHistoryType } from "../../server/db/schema";
import { z } from "zod";
import { redirect } from "next/navigation";
import { cookies, headers } from "next/headers";
export const getSpaces = async (): ServerActionReturnType<StoredSpace[]> => {
- const data = await auth();
+ const data = await auth();
- if (!data || !data.user) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
+ if (!data || !data.user) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
- const spaces = await db.query.space.findMany({
- where: eq(users, data.user.id),
- });
+ const spaces = await db.query.space.findMany({
+ where: eq(users, data.user.id),
+ });
- return { success: true, data: spaces };
+ return { success: true, data: spaces };
};
export const getAllMemories = async (
- freeMemoriesOnly: boolean = false,
+ freeMemoriesOnly: boolean = false,
): ServerActionReturnType<Content[]> => {
- const data = await auth();
-
- if (!data || !data.user) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- if (!freeMemoriesOnly) {
- // Returns all memories, no matter the space.
- const memories = await db.query.storedContent.findMany({
- where: eq(users, data.user.id),
- });
-
- return { success: true, data: memories };
- }
-
- // This only returns memories that are not a part of any space.
- // This is useful for home page where we want to show a list of spaces and memories.
- const contentNotInAnySpace = await db
- .select()
- .from(storedContent)
- .where(
- not(
- eq(
- storedContent.id,
- db
- .select({ contentId: contentToSpace.contentId })
- .from(contentToSpace),
- ),
- ),
- )
- .execute();
-
- return { success: true, data: contentNotInAnySpace };
+ const data = await auth();
+
+ if (!data || !data.user) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ if (!freeMemoriesOnly) {
+ // Returns all memories, no matter the space.
+ const memories = await db.query.storedContent.findMany({
+ where: eq(users, data.user.id),
+ });
+
+ return { success: true, data: memories };
+ }
+
+ // This only returns memories that are not a part of any space.
+ // This is useful for home page where we want to show a list of spaces and memories.
+ const contentNotInAnySpace = await db
+ .select()
+ .from(storedContent)
+ .where(
+ not(
+ eq(
+ storedContent.id,
+ db
+ .select({ contentId: contentToSpace.contentId })
+ .from(contentToSpace),
+ ),
+ ),
+ )
+ .execute();
+
+ return { success: true, data: contentNotInAnySpace };
};
export const getMemoriesInsideSpace = async (
- spaceId: number,
+ spaceId: number,
): ServerActionReturnType<{ memories: Content[]; spaces: StoredSpace[] }> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.email) {
- return { error: "Not authenticated", success: false };
- }
-
- const spaces = await db
- .select()
- .from(space)
- .where(
- and(
- eq(space.id, spaceId),
- or(
- eq(space.user, data.user.id!),
- exists(
- db
- .select()
- .from(spacesAccess)
- .where(
- and(
- eq(spacesAccess.spaceId, space.id),
- eq(spacesAccess.userEmail, data.user.email),
- ),
- ),
- ),
- ),
- ),
- )
- .limit(1);
-
- const memories = await db
- .select({
- id: storedContent.id,
- content: storedContent.content,
- title: storedContent.title,
- description: storedContent.description,
- url: storedContent.url,
- savedAt: storedContent.savedAt,
- baseUrl: storedContent.baseUrl,
- ogImage: storedContent.ogImage,
- type: storedContent.type,
- image: storedContent.image,
- userId: storedContent.userId,
- noteId: storedContent.noteId,
- })
- .from(storedContent)
- .innerJoin(contentToSpace, eq(storedContent.id, contentToSpace.contentId))
- .where(eq(contentToSpace.spaceId, spaceId));
-
- if (spaces.length === 0) {
- return { error: "Not authorized", success: false };
- }
-
- return {
- success: true,
- data: {
- memories: memories,
- spaces: spaces,
- },
- };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.email) {
+ return { error: "Not authenticated", success: false };
+ }
+
+ const spaces = await db
+ .select()
+ .from(space)
+ .where(
+ and(
+ eq(space.id, spaceId),
+ or(
+ eq(space.user, data.user.id!),
+ exists(
+ db
+ .select()
+ .from(spacesAccess)
+ .where(
+ and(
+ eq(spacesAccess.spaceId, space.id),
+ eq(spacesAccess.userEmail, data.user.email),
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ .limit(1);
+
+ const memories = await db
+ .select({
+ id: storedContent.id,
+ content: storedContent.content,
+ title: storedContent.title,
+ description: storedContent.description,
+ url: storedContent.url,
+ savedAt: storedContent.savedAt,
+ baseUrl: storedContent.baseUrl,
+ ogImage: storedContent.ogImage,
+ type: storedContent.type,
+ image: storedContent.image,
+ userId: storedContent.userId,
+ noteId: storedContent.noteId,
+ })
+ .from(storedContent)
+ .innerJoin(contentToSpace, eq(storedContent.id, contentToSpace.contentId))
+ .where(eq(contentToSpace.spaceId, spaceId));
+
+ if (spaces.length === 0) {
+ return { error: "Not authorized", success: false };
+ }
+
+ return {
+ success: true,
+ data: {
+ memories: memories,
+ spaces: spaces,
+ },
+ };
};
export const getAllUserMemoriesAndSpaces = async (): ServerActionReturnType<{
- spaces: StoredSpace[];
- memories: Content[];
+ spaces: StoredSpace[];
+ memories: Content[];
}> => {
- const data = await auth();
+ const data = await auth();
- if (!data || !data.user) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
+ if (!data || !data.user) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
- const spaces = await db.query.space.findMany({
- where: eq(users, data.user.id),
- });
+ const spaces = await db.query.space.findMany({
+ where: eq(users, data.user.id),
+ });
- const memories = await db.query.storedContent.findMany({
- where: eq(users, data.user.id),
- });
+ const memories = await db.query.storedContent.findMany({
+ where: eq(users, data.user.id),
+ });
- return {
- success: true,
- data: { spaces: spaces, memories: memories },
- };
+ return {
+ success: true,
+ data: { spaces: spaces, memories: memories },
+ };
};
export const getFullChatThread = async (
- threadId: string,
+ threadId: string,
): ServerActionReturnType<ChatHistory[]> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const thread = await db.query.chatThreads.findFirst({
- where: and(
- eq(chatThreads.id, threadId),
- eq(chatThreads.userId, data.user.id),
- ),
- });
-
- if (!thread) {
- return { error: "Thread not found", success: false };
- }
-
- const allChatsInThisThread = await db.query.chatHistory
- .findMany({
- where: and(eq(chatHistory.threadId, threadId)),
- orderBy: asc(chatHistory.id),
- })
- .execute();
-
- const accumulatedChatHistory: ChatHistory[] = allChatsInThisThread.map(
- (chat) => {
- console.log("answer sources", chat.answerSources);
- const sourceCheck = z
- .array(SourceZod)
- .safeParse(JSON.parse(chat.answerSources ?? "[]"));
-
- if (!sourceCheck.success || !sourceCheck.data) {
- console.error("sourceCheck.error", sourceCheck.error);
- throw new Error("Invalid source data");
- }
-
- const sources = sourceCheck.data;
-
- return {
- question: chat.question,
- answer: {
- parts: [
- {
- text: chat.answer ?? undefined,
- },
- ],
- sources: sources ?? [],
- },
- };
- },
- );
-
- return {
- success: true,
- data: accumulatedChatHistory,
- };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const thread = await db.query.chatThreads.findFirst({
+ where: and(
+ eq(chatThreads.id, threadId),
+ eq(chatThreads.userId, data.user.id),
+ ),
+ });
+
+ if (!thread) {
+ return { error: "Thread not found", success: false };
+ }
+
+ const allChatsInThisThread = await db.query.chatHistory
+ .findMany({
+ where: and(eq(chatHistory.threadId, threadId)),
+ orderBy: asc(chatHistory.id),
+ })
+ .execute();
+
+ const accumulatedChatHistory: ChatHistory[] = allChatsInThisThread.map(
+ (chat) => {
+ console.log("answer sources", chat.answerSources);
+ const sourceCheck = z
+ .array(SourceZod)
+ .safeParse(JSON.parse(chat.answerSources ?? "[]"));
+
+ if (!sourceCheck.success || !sourceCheck.data) {
+ console.error("sourceCheck.error", sourceCheck.error);
+ throw new Error("Invalid source data");
+ }
+
+ const sources = sourceCheck.data;
+
+ return {
+ question: chat.question,
+ answer: {
+ parts: [
+ {
+ text: chat.answer ?? undefined,
+ },
+ ],
+ sources: sources ?? [],
+ },
+ };
+ },
+ );
+
+ return {
+ success: true,
+ data: accumulatedChatHistory,
+ };
};
export const getChatHistory = async (): ServerActionReturnType<
- ChatThread[]
+ ChatThread[]
> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- try {
- const chatHistorys = await db.query.chatThreads.findMany({
- where: eq(chatThreads.userId, data.user.id),
- });
-
- return {
- success: true,
- data: chatHistorys,
- };
- } catch (e) {
- return {
- success: false,
- error: (e as Error).message,
- };
- }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ try {
+ const chatHistorys = await db.query.chatThreads.findMany({
+ where: eq(chatThreads.userId, data.user.id),
+ });
+
+ return {
+ success: true,
+ data: chatHistorys,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ error: (e as Error).message,
+ };
+ }
};
export const getSessionAuthToken = async (): ServerActionReturnType<string> => {
- const token =
- cookies().get("next-auth.session-token")?.value ??
- cookies().get("__Secure-authjs.session-token")?.value ??
- cookies().get("authjs.session-token")?.value ??
- headers().get("Authorization")?.replace("Bearer ", "");
-
- return {
- success: true,
- data: token,
- };
+ const token =
+ cookies().get("next-auth.session-token")?.value ??
+ cookies().get("__Secure-authjs.session-token")?.value ??
+ cookies().get("authjs.session-token")?.value ??
+ headers().get("Authorization")?.replace("Bearer ", "");
+
+ return {
+ success: true,
+ data: token,
+ };
};
export const getNoteFromId = async (
- noteId: string,
+ noteId: string,
): ServerActionReturnType<Content> => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- const note = await db.query.storedContent.findFirst({
- where: and(
- eq(storedContent.noteId, parseInt(noteId)),
- eq(users, data.user.id),
- ),
- });
-
- return {
- success: true,
- data: note,
- };
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ const note = await db.query.storedContent.findFirst({
+ where: and(
+ eq(storedContent.noteId, parseInt(noteId)),
+ eq(users, data.user.id),
+ ),
+ });
+
+ return {
+ success: true,
+ data: note,
+ };
};
export const getCanvas = async () => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- try {
- const canvases = await db
- .select()
- .from(canvas)
- .where(eq(canvas.userId, data.user.id));
-
- return {
- success: true,
- data: canvases.map(({ userId, ...rest }) => rest),
- };
- } catch (e) {
- return {
- success: false,
- error: (e as Error).message,
- };
- }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ try {
+ const canvases = await db
+ .select()
+ .from(canvas)
+ .where(eq(canvas.userId, data.user.id));
+
+ return {
+ success: true,
+ data: canvases.map(({ userId, ...rest }) => rest),
+ };
+ } catch (e) {
+ return {
+ success: false,
+ error: (e as Error).message,
+ };
+ }
};
export const userHasCanvas = async (canvasId: string) => {
- const data = await auth();
-
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
-
- try {
- const canvases = await db
- .select()
- .from(canvas)
- .where(eq(canvas.userId, data.user.id));
- const exists = !!canvases.find((canvas) => canvas.id === canvasId);
- return {
- success: exists,
- };
- } catch (e) {
- return {
- success: false,
- error: (e as Error).message,
- };
- }
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ try {
+ const canvases = await db
+ .select()
+ .from(canvas)
+ .where(eq(canvas.userId, data.user.id));
+ const exists = !!canvases.find((canvas) => canvas.id === canvasId);
+ return {
+ success: exists,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ error: (e as Error).message,
+ };
+ }
};
export const getCanvasData = async (canvasId: string) => {
- const data = await auth();
+ const data = await auth();
- if (!data || !data.user || !data.user.id) {
- redirect("/signin");
- return { error: "Not authenticated", success: false };
- }
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
- const canvas = await process.env.CANVAS_SNAPS.get(canvasId);
+ const canvas = await process.env.CANVAS_SNAPS.get(canvasId);
- console.log({ canvas, canvasId });
- if (canvas) {
- return JSON.parse(canvas);
- } else {
- return { snapshot: {} };
- }
+ console.log({ canvas, canvasId });
+ if (canvas) {
+ return JSON.parse(canvas);
+ } else {
+ return { snapshot: {} };
+ }
};
diff --git a/apps/web/app/actions/types.ts b/apps/web/app/actions/types.ts
index 0b1aaece..bf27c12f 100644
--- a/apps/web/app/actions/types.ts
+++ b/apps/web/app/actions/types.ts
@@ -1,12 +1,12 @@
export type Space = {
- id: number;
- name: string;
- numberOfMemories?: number;
- createdAt?: string;
+ id: number;
+ name: string;
+ numberOfMemories?: number;
+ createdAt?: string;
};
export type ServerActionReturnType<T> = Promise<{
- error?: string;
- success: boolean;
- data?: T;
+ error?: string;
+ success: boolean;
+ data?: T;
}>;
diff --git a/apps/web/app/api/canvas/route.ts b/apps/web/app/api/canvas/route.ts
index 70abace4..151478dd 100644
--- a/apps/web/app/api/canvas/route.ts
+++ b/apps/web/app/api/canvas/route.ts
@@ -1,10 +1,10 @@
export function GET(req: Request) {
- const id = new URL(req.url).searchParams.get("id");
- return new Response(JSON.stringify(id));
+ const id = new URL(req.url).searchParams.get("id");
+ return new Response(JSON.stringify(id));
}
export async function POST(req: Request) {
- const body = await req.json();
- const id = new URL(req.url).searchParams.get("id");
- return new Response(JSON.stringify({ body, id }));
+ const body = await req.json();
+ const id = new URL(req.url).searchParams.get("id");
+ return new Response(JSON.stringify({ body, id }));
}
diff --git a/apps/web/app/api/canvasai/route.ts b/apps/web/app/api/canvasai/route.ts
index 7e31f5b3..ac1baf03 100644
--- a/apps/web/app/api/canvasai/route.ts
+++ b/apps/web/app/api/canvasai/route.ts
@@ -4,28 +4,28 @@ import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function POST(request: NextRequest) {
- const session = await ensureAuth(request);
- if (!session) {
- return new Response("Unauthorized", { status: 401 });
- }
- const res: { query: string } = await request.json();
+ const session = await ensureAuth(request);
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+ const res: { query: string } = await request.json();
- try {
- const resp = await fetch(
- `${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`,
- );
- if (resp.status !== 200 || !resp.ok) {
- const errorData = await resp.text();
- console.log(errorData);
- return new Response(
- JSON.stringify({ message: "Error in CF function", error: errorData }),
- { status: resp.status },
- );
- }
- return new Response(
- JSON.stringify({ response: await resp.json(), status: 200 }),
- );
- } catch (error) {
- return new Response(`Error, ${error}`);
- }
+ try {
+ const resp = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`,
+ );
+ if (resp.status !== 200 || !resp.ok) {
+ const errorData = await resp.text();
+ console.log(errorData);
+ return new Response(
+ JSON.stringify({ message: "Error in CF function", error: errorData }),
+ { status: resp.status },
+ );
+ }
+ return new Response(
+ JSON.stringify({ response: await resp.json(), status: 200 }),
+ );
+ } catch (error) {
+ return new Response(`Error, ${error}`);
+ }
}
diff --git a/apps/web/app/api/chat/history/route.ts b/apps/web/app/api/chat/history/route.ts
new file mode 100644
index 00000000..98b66064
--- /dev/null
+++ b/apps/web/app/api/chat/history/route.ts
@@ -0,0 +1,37 @@
+import { NextRequest } from "next/server";
+import { ensureAuth } from "../../ensureAuth";
+import { db } from "@/server/db";
+import { eq } from "drizzle-orm";
+import { chatThreads } from "@/server/db/schema";
+
+export const runtime = "edge";
+
+export async function GET(req: NextRequest) {
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ if (!process.env.BACKEND_SECURITY_KEY) {
+ return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
+ }
+
+ try {
+ const chatHistorys = await db.query.chatThreads.findMany({
+ where: eq(chatThreads.userId, session.user.id),
+ });
+
+ return new Response(JSON.stringify({ success: true, data: chatHistorys }), {
+ status: 200,
+ });
+ } catch (e) {
+ return new Response(
+ JSON.stringify({
+ success: false,
+ error: (e as Error).message,
+ }),
+ { status: 400 },
+ );
+ }
+}
diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts
index 693b1be5..004bfd3b 100644
--- a/apps/web/app/api/chat/route.ts
+++ b/apps/web/app/api/chat/route.ts
@@ -1,9 +1,9 @@
import { type NextRequest } from "next/server";
import {
- ChatHistory,
- ChatHistoryZod,
- convertChatHistoryList,
- SourcesFromApi,
+ ChatHistory,
+ ChatHistoryZod,
+ convertChatHistoryList,
+ SourcesFromApi,
} from "@repo/shared-types";
import { ensureAuth } from "../ensureAuth";
import { z } from "zod";
@@ -11,77 +11,77 @@ import { z } from "zod";
export const runtime = "edge";
export async function POST(req: NextRequest) {
- const session = await ensureAuth(req);
-
- if (!session) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- if (!process.env.BACKEND_SECURITY_KEY) {
- return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
- }
-
- const url = new URL(req.url);
-
- const query = url.searchParams.get("q");
- const spaces = url.searchParams.get("spaces");
-
- const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false";
-
- const jsonRequest = (await req.json()) as {
- chatHistory: ChatHistory[];
- sources: SourcesFromApi[] | undefined;
- };
- const { chatHistory, sources } = jsonRequest;
-
- if (!query || query.trim.length < 0) {
- return new Response(JSON.stringify({ message: "Invalid query" }), {
- status: 400,
- });
- }
-
- const validated = z.array(ChatHistoryZod).safeParse(chatHistory ?? []);
-
- if (!validated.success) {
- return new Response(
- JSON.stringify({
- message: "Invalid chat history",
- error: validated.error,
- }),
- { status: 400 },
- );
- }
-
- const modelCompatible = await convertChatHistoryList(validated.data);
-
- const resp = await fetch(
- `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.id}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`,
- {
- headers: {
- Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`,
- "Content-Type": "application/json",
- },
- method: "POST",
- body: JSON.stringify({
- chatHistory: modelCompatible,
- sources,
- }),
- },
- );
-
- if (sourcesOnly == "true") {
- const data = (await resp.json()) as SourcesFromApi;
- return new Response(JSON.stringify(data), { status: 200 });
- }
-
- if (resp.status !== 200 || !resp.ok) {
- const errorData = await resp.text();
- console.log(errorData);
- return new Response(
- JSON.stringify({ message: "Error in CF function", error: errorData }),
- { status: resp.status },
- );
- }
-
- return resp
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ if (!process.env.BACKEND_SECURITY_KEY) {
+ return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
+ }
+
+ const url = new URL(req.url);
+
+ const query = url.searchParams.get("q");
+ const spaces = url.searchParams.get("spaces");
+
+ const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false";
+
+ const jsonRequest = (await req.json()) as {
+ chatHistory: ChatHistory[];
+ sources: SourcesFromApi[] | undefined;
+ };
+ const { chatHistory, sources } = jsonRequest;
+
+ if (!query || query.trim.length < 0) {
+ return new Response(JSON.stringify({ message: "Invalid query" }), {
+ status: 400,
+ });
+ }
+
+ const validated = z.array(ChatHistoryZod).safeParse(chatHistory ?? []);
+
+ if (!validated.success) {
+ return new Response(
+ JSON.stringify({
+ message: "Invalid chat history",
+ error: validated.error,
+ }),
+ { status: 400 },
+ );
+ }
+
+ const modelCompatible = await convertChatHistoryList(validated.data);
+
+ const resp = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.id}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`,
+ {
+ headers: {
+ Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`,
+ "Content-Type": "application/json",
+ },
+ method: "POST",
+ body: JSON.stringify({
+ chatHistory: modelCompatible,
+ sources,
+ }),
+ },
+ );
+
+ if (sourcesOnly == "true") {
+ const data = (await resp.json()) as SourcesFromApi;
+ return new Response(JSON.stringify(data), { status: 200 });
+ }
+
+ if (resp.status !== 200 || !resp.ok) {
+ const errorData = await resp.text();
+ console.log(errorData);
+ return new Response(
+ JSON.stringify({ message: "Error in CF function", error: errorData }),
+ { status: resp.status },
+ );
+ }
+
+ return resp;
}
diff --git a/apps/web/app/api/editorai/route.ts b/apps/web/app/api/editorai/route.ts
index 5e1fbf0c..ea580b3d 100644
--- a/apps/web/app/api/editorai/route.ts
+++ b/apps/web/app/api/editorai/route.ts
@@ -6,25 +6,27 @@ export const runtime = "edge";
// ERROR #2 - This the the next function that calls the backend, I sometimes think this is redundency, but whatever
// I have commented the auth code, It should not work in development, but it still does sometimes
export async function POST(request: NextRequest) {
- // const d = await ensureAuth(request);
- // if (!d) {
- // return new Response("Unauthorized", { status: 401 });
- // }
- const res : {context: string, request: string} = await request.json()
+ // const d = await ensureAuth(request);
+ // if (!d) {
+ // return new Response("Unauthorized", { status: 401 });
+ // }
+ const res: { context: string; request: string } = await request.json();
- try {
- const resp = await fetch(`${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`);
- // this just checks if there are erros I am keeping it commented for you to better understand the important pieces
- // if (resp.status !== 200 || !resp.ok) {
- // const errorData = await resp.text();
- // console.log(errorData);
- // return new Response(
- // JSON.stringify({ message: "Error in CF function", error: errorData }),
- // { status: resp.status },
- // );
- // }
- return new Response(resp.body, { status: 200 });
- } catch (error) {
- return new Response(`Error, ${error}`)
- }
-} \ No newline at end of file
+ try {
+ const resp = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`,
+ );
+ // this just checks if there are erros I am keeping it commented for you to better understand the important pieces
+ // if (resp.status !== 200 || !resp.ok) {
+ // const errorData = await resp.text();
+ // console.log(errorData);
+ // return new Response(
+ // JSON.stringify({ message: "Error in CF function", error: errorData }),
+ // { status: resp.status },
+ // );
+ // }
+ return new Response(resp.body, { status: 200 });
+ } catch (error) {
+ return new Response(`Error, ${error}`);
+ }
+}
diff --git a/apps/web/app/api/ensureAuth.ts b/apps/web/app/api/ensureAuth.ts
index d2c14b3b..1fcd2914 100644
--- a/apps/web/app/api/ensureAuth.ts
+++ b/apps/web/app/api/ensureAuth.ts
@@ -1,33 +1,105 @@
import { NextRequest } from "next/server";
import { db } from "../../server/db";
-import { sessions, users } from "../../server/db/schema";
+import { accounts, sessions, users } from "../../server/db/schema";
import { eq } from "drizzle-orm";
export async function ensureAuth(req: NextRequest) {
- // A helper function to protect routes
-
- const token =
- req.cookies.get("next-auth.session-token")?.value ??
- req.cookies.get("__Secure-authjs.session-token")?.value ??
- req.cookies.get("authjs.session-token")?.value ??
- req.headers.get("Authorization")?.replace("Bearer ", "");
-
- if (!token) {
- return undefined;
- }
-
- const sessionData = await db
- .select()
- .from(sessions)
- .innerJoin(users, eq(users.id, sessions.userId))
- .where(eq(sessions.sessionToken, token!));
-
- if (!sessionData || sessionData.length === 0) {
- return undefined;
- }
-
- return {
- user: sessionData[0]!.user,
- session: sessionData[0]!,
- };
+ // A helper function to protect routes
+
+ const token =
+ req.cookies.get("next-auth.session-token")?.value ??
+ req.cookies.get("__Secure-authjs.session-token")?.value ??
+ req.cookies.get("authjs.session-token")?.value ??
+ req.headers.get("Authorization")?.replace("Bearer ", "");
+
+ if (!token) {
+ return undefined;
+ }
+
+ let sessionData = await db
+ .select()
+ .from(sessions)
+ .innerJoin(users, eq(users.id, sessions.userId))
+ .where(eq(sessions.sessionToken, token!));
+
+ const isMobile =
+ token.split("?") && token.split("?")[1] === `source="mobile"`;
+
+ if (isMobile) {
+ // remove everything after ? in token
+ const newToken = token.split("?").slice(0, -1).join("?");
+
+ console.log(token, newToken);
+
+ const authUserFetch = await fetch(
+ `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${newToken}`,
+ );
+
+ if (!authUserFetch.ok) {
+ console.error(
+ "Error fetching Google user,",
+ authUserFetch.statusText,
+ await authUserFetch.text(),
+ );
+ console.log("Google user not found or error.");
+ return undefined;
+ }
+
+ const authUserData = (await authUserFetch.json()) as {
+ email: string;
+ audience: string;
+ issued_to: string;
+ };
+
+ console.log(authUserData);
+
+ if (
+ !(
+ authUserData.audience.split("-")[0] ===
+ process.env.GOOGLE_CLIENT_ID.split("-")[0] &&
+ authUserData.issued_to.split("-")[0] ===
+ process.env.GOOGLE_CLIENT_ID.split("-")[0]
+ )
+ ) {
+ console.log(
+ "Google user not authorized because of audience or issued_to mismatch",
+ );
+ return undefined;
+ }
+
+ const authUserEmail = authUserData.email;
+
+ let user = await db
+ .select()
+ .from(users)
+ .where(eq(users.email, authUserEmail))
+ .limit(1);
+
+ if (!user || user.length === 0) {
+ // create the user
+ user = await db
+ .insert(users)
+ .values({
+ email: authUserEmail,
+ name: authUserEmail.split("@")[0],
+ })
+ .returning();
+ }
+
+ sessionData = [
+ {
+ ...sessionData[0]!,
+ user: user[0]!,
+ },
+ ];
+ }
+
+ if (!sessionData || sessionData.length === 0) {
+ return undefined;
+ }
+
+ return {
+ user: sessionData[0]!.user,
+ session: sessionData[0]!,
+ };
}
diff --git a/apps/web/app/api/getCount/route.ts b/apps/web/app/api/getCount/route.ts
index 7cd2a2d3..f91b7b94 100644
--- a/apps/web/app/api/getCount/route.ts
+++ b/apps/web/app/api/getCount/route.ts
@@ -7,41 +7,41 @@ import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function GET(req: NextRequest) {
- const session = await ensureAuth(req);
+ const session = await ensureAuth(req);
- if (!session) {
- return new Response("Unauthorized", { status: 401 });
- }
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
- const tweetsCount = await db
- .select({
- count: sql<number>`count(*)`.mapWith(Number),
- })
- .from(storedContent)
- .where(
- and(
- eq(storedContent.userId, session.user.id),
- eq(storedContent.type, "twitter-bookmark"),
- ),
- );
+ const tweetsCount = await db
+ .select({
+ count: sql<number>`count(*)`.mapWith(Number),
+ })
+ .from(storedContent)
+ .where(
+ and(
+ eq(storedContent.userId, session.user.id),
+ eq(storedContent.type, "twitter-bookmark"),
+ ),
+ );
- const pageCount = await db
- .select({
- count: sql<number>`count(*)`.mapWith(Number),
- })
- .from(storedContent)
- .where(
- and(
- eq(storedContent.userId, session.user.id),
- ne(storedContent.type, "twitter-bookmark"),
- ),
- );
+ const pageCount = await db
+ .select({
+ count: sql<number>`count(*)`.mapWith(Number),
+ })
+ .from(storedContent)
+ .where(
+ and(
+ eq(storedContent.userId, session.user.id),
+ ne(storedContent.type, "twitter-bookmark"),
+ ),
+ );
- return NextResponse.json({
- tweetsCount: tweetsCount[0]!.count,
- tweetsLimit: 1000,
- pageCount: pageCount[0]!.count,
- pageLimit: 100,
- user: session.user.email,
- });
+ return NextResponse.json({
+ tweetsCount: tweetsCount[0]!.count,
+ tweetsLimit: 1000,
+ pageCount: pageCount[0]!.count,
+ pageLimit: 100,
+ user: session.user.email,
+ });
}
diff --git a/apps/web/app/api/hello/route.ts b/apps/web/app/api/hello/route.ts
index 363d0704..259d34fc 100644
--- a/apps/web/app/api/hello/route.ts
+++ b/apps/web/app/api/hello/route.ts
@@ -4,19 +4,19 @@ import { getRequestContext } from "@cloudflare/next-on-pages";
export const runtime = "edge";
export async function GET(request: NextRequest) {
- let responseText = "Hello World";
+ let responseText = "Hello World";
- // In the edge runtime you can use Bindings that are available in your application
- // (for more details see:
- // - https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application
- // - https://developers.cloudflare.com/pages/functions/bindings/
- // )
- //
- // KV Example:
- // const myKv = getRequestContext().env.MY_KV_NAMESPACE
- // await myKv.put('suffix', ' from a KV store!')
- // const suffix = await myKv.get('suffix')
- // responseText += suffix
+ // In the edge runtime you can use Bindings that are available in your application
+ // (for more details see:
+ // - https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application
+ // - https://developers.cloudflare.com/pages/functions/bindings/
+ // )
+ //
+ // KV Example:
+ // const myKv = getRequestContext().env.MY_KV_NAMESPACE
+ // await myKv.put('suffix', ' from a KV store!')
+ // const suffix = await myKv.get('suffix')
+ // responseText += suffix
- return new Response(responseText);
+ return new Response(responseText);
}
diff --git a/apps/web/app/api/me/route.ts b/apps/web/app/api/me/route.ts
index 621dcbfe..ab408f3e 100644
--- a/apps/web/app/api/me/route.ts
+++ b/apps/web/app/api/me/route.ts
@@ -6,42 +6,42 @@ import { type NextRequest, NextResponse } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
- const token =
- req.cookies.get("next-auth.session-token")?.value ??
- req.cookies.get("__Secure-authjs.session-token")?.value ??
- req.cookies.get("authjs.session-token")?.value ??
- req.headers.get("Authorization")?.replace("Bearer ", "");
+ const token =
+ req.cookies.get("next-auth.session-token")?.value ??
+ req.cookies.get("__Secure-authjs.session-token")?.value ??
+ req.cookies.get("authjs.session-token")?.value ??
+ req.headers.get("Authorization")?.replace("Bearer ", "");
- const session = await db
- .select()
- .from(sessions)
- .where(eq(sessions.sessionToken, token!));
+ const session = await db
+ .select()
+ .from(sessions)
+ .where(eq(sessions.sessionToken, token!));
- if (!session || session.length === 0) {
- return new Response(
- JSON.stringify({ message: "Invalid Key, session not found." }),
- { status: 404 },
- );
- }
+ if (!session || session.length === 0) {
+ return new Response(
+ JSON.stringify({ message: "Invalid Key, session not found." }),
+ { status: 404 },
+ );
+ }
- const user = await db
- .select()
- .from(users)
- .where(eq(users.id, session[0]!.userId))
- .limit(1);
+ const user = await db
+ .select()
+ .from(users)
+ .where(eq(users.id, session[0]!.userId))
+ .limit(1);
- if (!user || user.length === 0) {
- return NextResponse.json(
- { message: "Invalid Key, session not found." },
- { status: 404 },
- );
- }
+ if (!user || user.length === 0) {
+ return NextResponse.json(
+ { message: "Invalid Key, session not found." },
+ { status: 404 },
+ );
+ }
- return new Response(
- JSON.stringify({
- message: "OK",
- data: { session: session[0], user: user[0] },
- }),
- { status: 200 },
- );
+ return new Response(
+ JSON.stringify({
+ message: "OK",
+ data: { session: session[0], user: user[0] },
+ }),
+ { status: 200 },
+ );
}
diff --git a/apps/web/app/api/memories/route.ts b/apps/web/app/api/memories/route.ts
new file mode 100644
index 00000000..acb43b5d
--- /dev/null
+++ b/apps/web/app/api/memories/route.ts
@@ -0,0 +1,85 @@
+import { NextRequest } from "next/server";
+import { db } from "@/server/db";
+import { eq } from "drizzle-orm";
+import {
+ chatThreads,
+ contentToSpace,
+ storedContent,
+ users,
+} from "@/server/db/schema";
+import { ensureAuth } from "../ensureAuth";
+
+export const runtime = "edge";
+
+export async function GET(req: NextRequest) {
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ if (!process.env.BACKEND_SECURITY_KEY) {
+ return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
+ }
+
+ const url = new URL(req.url);
+
+ const spaceId = url.searchParams.get("space");
+
+ try {
+ if (spaceId) {
+ const memories = await db
+ .select({
+ id: storedContent.id,
+ content: storedContent.content,
+ title: storedContent.title,
+ description: storedContent.description,
+ url: storedContent.url,
+ savedAt: storedContent.savedAt,
+ baseUrl: storedContent.baseUrl,
+ ogImage: storedContent.ogImage,
+ type: storedContent.type,
+ image: storedContent.image,
+ userId: storedContent.userId,
+ noteId: storedContent.noteId,
+ })
+ .from(storedContent)
+ .innerJoin(
+ contentToSpace,
+ eq(storedContent.id, contentToSpace.contentId),
+ )
+ .where(eq(contentToSpace.spaceId, parseInt(spaceId)));
+
+ return new Response(
+ JSON.stringify({
+ success: true,
+ data: { memories: memories },
+ }),
+ { status: 200 },
+ );
+ }
+
+ const spaces = await db.query.space.findMany({
+ where: eq(users, session.user.id),
+ });
+
+ const memories = await db.query.storedContent.findMany({
+ where: eq(users, session.user.id),
+ });
+
+ return new Response(
+ JSON.stringify({
+ success: true,
+ data: { spaces: spaces, memories: memories },
+ }),
+ );
+ } catch (e) {
+ return new Response(
+ JSON.stringify({
+ success: false,
+ error: (e as Error).message,
+ }),
+ { status: 400 },
+ );
+ }
+}
diff --git a/apps/web/app/api/spaces/route.ts b/apps/web/app/api/spaces/route.ts
index cbed547d..e85e07ed 100644
--- a/apps/web/app/api/spaces/route.ts
+++ b/apps/web/app/api/spaces/route.ts
@@ -1,29 +1,84 @@
import { db } from "@/server/db";
-import { sessions, space, users } from "@/server/db/schema";
+import { space } from "@/server/db/schema";
import { eq } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
import { ensureAuth } from "../ensureAuth";
+import { z } from "zod";
export const runtime = "edge";
export async function GET(req: NextRequest) {
- const session = await ensureAuth(req);
-
- if (!session) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- const spaces = await db
- .select()
- .from(space)
- .where(eq(space.user, session.user.id))
- .all();
-
- return NextResponse.json(
- {
- message: "OK",
- data: spaces,
- },
- { status: 200 },
- );
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ const spaces = await db
+ .select()
+ .from(space)
+ .where(eq(space.user, session.user.id))
+ .all();
+
+ // We want to slowly move away from this pattern and return `new Response` directly
+ return NextResponse.json(
+ {
+ message: "OK",
+ data: spaces,
+ },
+ { status: 200 },
+ );
}
+
+export async function POST(req: NextRequest) {
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ const expectedInput = z.object({
+ name: z.string(),
+ });
+
+ const jsonRequest = await req.json();
+
+ const validated = expectedInput.safeParse(jsonRequest);
+ if (!validated.success) {
+ return new Response(
+ JSON.stringify({ success: false, error: validated.error }),
+ {
+ status: 400,
+ },
+ );
+ }
+
+ const newSpace = await db.insert(space).values({
+ name: validated.data.name,
+ createdAt: new Date(),
+ user: session.user.id,
+ });
+
+ return new Response(JSON.stringify({ success: true, data: newSpace }), {
+ status: 200,
+ });
+}
+
+export const DELETE = async (req: NextRequest) => {
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ const url = new URL(req.url);
+ const spaceId = url.searchParams.get("space");
+
+ if (!spaceId) {
+ return new Response("Invalid space id", { status: 400 });
+ }
+
+ await db.delete(space).where(eq(space.id, parseInt(spaceId)));
+
+ return new Response(JSON.stringify({ success: true }), { status: 200 });
+};
diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts
index 26636c5c..f9ab7c01 100644
--- a/apps/web/app/api/store/route.ts
+++ b/apps/web/app/api/store/route.ts
@@ -11,204 +11,204 @@ import { limit } from "@/app/actions/doers";
export const runtime = "edge";
const createMemoryFromAPI = async (input: {
- data: AddFromAPIType;
- userId: string;
+ data: AddFromAPIType;
+ userId: string;
}) => {
- if (!(await limit(input.userId, input.data.type))) {
- return {
- success: false,
- data: 0,
- error: `You have exceeded the limit of ${LIMITS[input.data.type as keyof typeof LIMITS]} ${input.data.type}s.`,
- };
- }
-
- const vectorSaveResponse = await fetch(
- `${process.env.BACKEND_BASE_URL}/api/add`,
- {
- method: "POST",
- body: JSON.stringify({
- pageContent: input.data.pageContent,
- title: input.data.title,
- description: input.data.description,
- url: input.data.url,
- spaces: input.data.spaces,
- user: input.userId,
- type: input.data.type,
- }),
- headers: {
- "Content-Type": "application/json",
- Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
- },
- },
- );
-
- if (!vectorSaveResponse.ok) {
- const errorData = await vectorSaveResponse.text();
- console.error(errorData);
- return {
- success: false,
- data: 0,
- error: `Failed to save to vector store. Backend returned error: ${errorData}`,
- };
- }
-
- let contentId: number | undefined;
-
- const saveToDbUrl =
- (input.data.url.split("#supermemory-user-")[0] ?? input.data.url) +
- "#supermemory-user-" +
- input.userId;
-
- const noteId = new Date().getTime();
-
- // Insert into database
- try {
- const insertResponse = await db
- .insert(storedContent)
- .values({
- content: input.data.pageContent,
- title: input.data.title,
- description: input.data.description,
- url: saveToDbUrl,
- baseUrl: saveToDbUrl,
- image: input.data.image,
- savedAt: new Date(),
- userId: input.userId,
- type: input.data.type,
- noteId,
- })
- .returning({ id: storedContent.id });
-
- contentId = insertResponse[0]?.id;
- } catch (e) {
- const error = e as Error;
- console.log("Error: ", error.message);
-
- if (error.message.includes("D1_ERROR: UNIQUE constraint failed:")) {
- return {
- success: false,
- data: 0,
- error: "Content already exists",
- };
- }
-
- return {
- success: false,
- data: 0,
- error: "Failed to save to database with error: " + error.message,
- };
- }
-
- if (!contentId) {
- return {
- success: false,
- data: 0,
- error: "Failed to save to database",
- };
- }
-
- if (input.data.spaces.length > 0) {
- // Adding the many-to-many relationship between content and spaces
- const spaceData = await db
- .select()
- .from(space)
- .where(
- and(
- inArray(
- space.id,
- input.data.spaces.map((s) => parseInt(s)),
- ),
- eq(space.user, input.userId),
- ),
- )
- .all();
-
- await Promise.all(
- spaceData.map(async (s) => {
- await db
- .insert(contentToSpace)
- .values({ contentId: contentId, spaceId: s.id });
-
- await db.update(space).set({ numItems: s.numItems + 1 });
- }),
- );
- }
-
- try {
- const response = await vectorSaveResponse.json();
-
- const expectedResponse = z.object({ status: z.literal("ok") });
-
- const parsedResponse = expectedResponse.safeParse(response);
-
- if (!parsedResponse.success) {
- return {
- success: false,
- data: 0,
- error: `Failed to save to vector store. Backend returned error: ${parsedResponse.error.message}`,
- };
- }
-
- return {
- success: true,
- data: 1,
- };
- } catch (e) {
- return {
- success: false,
- data: 0,
- error: `Failed to save to vector store. Backend returned error: ${e}`,
- };
- }
+ if (!(await limit(input.userId, input.data.type))) {
+ return {
+ success: false,
+ data: 0,
+ error: `You have exceeded the limit of ${LIMITS[input.data.type as keyof typeof LIMITS]} ${input.data.type}s.`,
+ };
+ }
+
+ const vectorSaveResponse = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/add`,
+ {
+ method: "POST",
+ body: JSON.stringify({
+ pageContent: input.data.pageContent,
+ title: input.data.title,
+ description: input.data.description,
+ url: input.data.url,
+ spaces: input.data.spaces,
+ user: input.userId,
+ type: input.data.type,
+ }),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
+ },
+ },
+ );
+
+ if (!vectorSaveResponse.ok) {
+ const errorData = await vectorSaveResponse.text();
+ console.error(errorData);
+ return {
+ success: false,
+ data: 0,
+ error: `Failed to save to vector store. Backend returned error: ${errorData}`,
+ };
+ }
+
+ let contentId: number | undefined;
+
+ const saveToDbUrl =
+ (input.data.url.split("#supermemory-user-")[0] ?? input.data.url) +
+ "#supermemory-user-" +
+ input.userId;
+
+ const noteId = new Date().getTime();
+
+ // Insert into database
+ try {
+ const insertResponse = await db
+ .insert(storedContent)
+ .values({
+ content: input.data.pageContent,
+ title: input.data.title,
+ description: input.data.description,
+ url: saveToDbUrl,
+ baseUrl: saveToDbUrl,
+ image: input.data.image,
+ savedAt: new Date(),
+ userId: input.userId,
+ type: input.data.type,
+ noteId,
+ })
+ .returning({ id: storedContent.id });
+
+ contentId = insertResponse[0]?.id;
+ } catch (e) {
+ const error = e as Error;
+ console.log("Error: ", error.message);
+
+ if (error.message.includes("D1_ERROR: UNIQUE constraint failed:")) {
+ return {
+ success: false,
+ data: 0,
+ error: "Content already exists",
+ };
+ }
+
+ return {
+ success: false,
+ data: 0,
+ error: "Failed to save to database with error: " + error.message,
+ };
+ }
+
+ if (!contentId) {
+ return {
+ success: false,
+ data: 0,
+ error: "Failed to save to database",
+ };
+ }
+
+ if (input.data.spaces.length > 0) {
+ // Adding the many-to-many relationship between content and spaces
+ const spaceData = await db
+ .select()
+ .from(space)
+ .where(
+ and(
+ inArray(
+ space.id,
+ input.data.spaces.map((s) => parseInt(s)),
+ ),
+ eq(space.user, input.userId),
+ ),
+ )
+ .all();
+
+ await Promise.all(
+ spaceData.map(async (s) => {
+ await db
+ .insert(contentToSpace)
+ .values({ contentId: contentId, spaceId: s.id });
+
+ await db.update(space).set({ numItems: s.numItems + 1 });
+ }),
+ );
+ }
+
+ try {
+ const response = await vectorSaveResponse.json();
+
+ const expectedResponse = z.object({ status: z.literal("ok") });
+
+ const parsedResponse = expectedResponse.safeParse(response);
+
+ if (!parsedResponse.success) {
+ return {
+ success: false,
+ data: 0,
+ error: `Failed to save to vector store. Backend returned error: ${parsedResponse.error.message}`,
+ };
+ }
+
+ return {
+ success: true,
+ data: 1,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ data: 0,
+ error: `Failed to save to vector store. Backend returned error: ${e}`,
+ };
+ }
};
export async function POST(req: NextRequest) {
- const session = await ensureAuth(req);
-
- if (!session) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- if (!process.env.BACKEND_SECURITY_KEY) {
- return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
- }
-
- const body = await req.json();
-
- const validated = addFromAPIType.safeParse(body);
-
- if (!validated.success) {
- return new Response(
- JSON.stringify({
- message: "Invalid request",
- error: validated.error,
- }),
- { status: 400 },
- );
- }
-
- const data = validated.data;
-
- const result = await createMemoryFromAPI({
- data,
- userId: session.user.id,
- });
-
- if (!result.success) {
- return new Response(
- JSON.stringify({
- message: "Failed to save document",
- error: result.error,
- }),
- { status: 501 },
- );
- }
-
- return new Response(
- JSON.stringify({
- message: "Document saved",
- data: result.data,
- }),
- { status: 200 },
- );
+ const session = await ensureAuth(req);
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ if (!process.env.BACKEND_SECURITY_KEY) {
+ return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
+ }
+
+ const body = await req.json();
+
+ const validated = addFromAPIType.safeParse(body);
+
+ if (!validated.success) {
+ return new Response(
+ JSON.stringify({
+ message: "Invalid request",
+ error: validated.error,
+ }),
+ { status: 400 },
+ );
+ }
+
+ const data = validated.data;
+
+ const result = await createMemoryFromAPI({
+ data,
+ userId: session.user.id,
+ });
+
+ if (!result.success) {
+ return new Response(
+ JSON.stringify({
+ message: "Failed to save document",
+ error: result.error,
+ }),
+ { status: 501 },
+ );
+ }
+
+ return new Response(
+ JSON.stringify({
+ message: "Document saved",
+ data: result.data,
+ }),
+ { status: 200 },
+ );
}
diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts
index 5f824b79..78837e5f 100644
--- a/apps/web/app/api/telegram/route.ts
+++ b/apps/web/app/api/telegram/route.ts
@@ -8,7 +8,7 @@ import { User } from "grammy/types";
export const runtime = "edge";
if (!process.env.TELEGRAM_BOT_TOKEN) {
- throw new Error("TELEGRAM_BOT_TOKEN is not defined");
+ throw new Error("TELEGRAM_BOT_TOKEN is not defined");
}
console.log("Telegram bot activated");
@@ -17,97 +17,97 @@ const token = process.env.TELEGRAM_BOT_TOKEN;
const bot = new Bot(token);
bot.command("start", async (ctx) => {
- const user: User = (await ctx.getAuthor()).user;
+ const user: User = (await ctx.getAuthor()).user;
- const cipherd = cipher(user.id.toString());
- await ctx.reply(
- `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`,
- );
+ const cipherd = cipher(user.id.toString());
+ await ctx.reply(
+ `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`,
+ );
});
bot.on("message", async (ctx) => {
- const user: User = (await ctx.getAuthor()).user;
-
- const cipherd = cipher(user.id.toString());
-
- const dbUser = await db.query.users
- .findFirst({
- where: eq(users.telegramId, user.id.toString()),
- })
- .execute();
-
- if (!dbUser) {
- await ctx.reply(
- `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`,
- );
-
- return;
- }
-
- const message = await ctx.reply("I'm thinking...");
-
- const response = await fetch(
- `${process.env.BACKEND_BASE_URL}/api/autoChatOrAdd?query=${ctx.message.text}&user=${dbUser.id}`,
- {
- method: "POST",
- headers: {
- Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- // TODO: we can use the conversations API to get the last 5 messages
- // get chatHistory from this conversation.
- // Basically the last 5 messages between the user and the assistant.
- // In ths form of [{role: 'user' | 'assistant', content: string}]
- // https://grammy.dev/plugins/conversations
- chatHistory: [],
- }),
- },
- );
-
- if (response.status !== 200) {
- console.log("Failed to get response from backend");
- console.log(response.status);
- console.log(await response.text());
- await ctx.reply(
- "Sorry, I am not able to process your request at the moment.",
- );
- return;
- }
-
- const data = (await response.json()) as {
- status: string;
- response: string;
- contentAdded: {
- type: string;
- content: string;
- url: string;
- };
- };
-
- // TODO: we might want to enrich this data with more information
- if (data.contentAdded) {
- await db
- .insert(storedContent)
- .values({
- content: data.contentAdded.content,
- title: `${data.contentAdded.content.slice(0, 30)}... (Added from chatbot)`,
- description: "",
- url: data.contentAdded.url,
- baseUrl: data.contentAdded.url,
- image: "",
- savedAt: new Date(),
- userId: dbUser.id,
- type: data.contentAdded.type,
- })
- .returning({ id: storedContent.id });
- }
-
- await ctx.api.editMessageText(ctx.chat.id, message.message_id, data.response);
+ const user: User = (await ctx.getAuthor()).user;
+
+ const cipherd = cipher(user.id.toString());
+
+ const dbUser = await db.query.users
+ .findFirst({
+ where: eq(users.telegramId, user.id.toString()),
+ })
+ .execute();
+
+ if (!dbUser) {
+ await ctx.reply(
+ `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`,
+ );
+
+ return;
+ }
+
+ const message = await ctx.reply("I'm thinking...");
+
+ const response = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/autoChatOrAdd?query=${ctx.message.text}&user=${dbUser.id}`,
+ {
+ method: "POST",
+ headers: {
+ Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ // TODO: we can use the conversations API to get the last 5 messages
+ // get chatHistory from this conversation.
+ // Basically the last 5 messages between the user and the assistant.
+ // In ths form of [{role: 'user' | 'assistant', content: string}]
+ // https://grammy.dev/plugins/conversations
+ chatHistory: [],
+ }),
+ },
+ );
+
+ if (response.status !== 200) {
+ console.log("Failed to get response from backend");
+ console.log(response.status);
+ console.log(await response.text());
+ await ctx.reply(
+ "Sorry, I am not able to process your request at the moment.",
+ );
+ return;
+ }
+
+ const data = (await response.json()) as {
+ status: string;
+ response: string;
+ contentAdded: {
+ type: string;
+ content: string;
+ url: string;
+ };
+ };
+
+ // TODO: we might want to enrich this data with more information
+ if (data.contentAdded) {
+ await db
+ .insert(storedContent)
+ .values({
+ content: data.contentAdded.content,
+ title: `${data.contentAdded.content.slice(0, 30)}... (Added from chatbot)`,
+ description: "",
+ url: data.contentAdded.url,
+ baseUrl: data.contentAdded.url,
+ image: "",
+ savedAt: new Date(),
+ userId: dbUser.id,
+ type: data.contentAdded.type,
+ })
+ .returning({ id: storedContent.id });
+ }
+
+ await ctx.api.editMessageText(ctx.chat.id, message.message_id, data.response);
});
export const POST = webhookCallback(bot, "std/http");
export const GET = async () => {
- return new Response("OK", { status: 200 });
+ return new Response("OK", { status: 200 });
};
diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts
index 36e47987..78b61ca9 100644
--- a/apps/web/app/api/unfirlsite/route.ts
+++ b/apps/web/app/api/unfirlsite/route.ts
@@ -7,150 +7,150 @@ import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function POST(request: NextRequest) {
- const r2 = new AwsClient({
- accessKeyId: process.env.R2_ACCESS_KEY_ID,
- secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
- });
-
- async function unfurl(url: string) {
- const response = await fetch(url);
- if (response.status >= 400) {
- throw new Error(`Error fetching url: ${response.status}`);
- }
- const contentType = response.headers.get("content-type");
- if (!contentType?.includes("text/html")) {
- throw new Error(`Content-type not right: ${contentType}`);
- }
-
- const content = await response.text();
- const $ = load(content);
-
- const og: { [key: string]: string | undefined } = {};
- const twitter: { [key: string]: string | undefined } = {};
-
- $("meta[property^=og:]").each(
- // @ts-ignore, it just works so why care of type safety if someone has better way go ahead
- (_, el) => (og[$(el).attr("property")!] = $(el).attr("content")),
- );
- $("meta[name^=twitter:]").each(
- // @ts-ignore
- (_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")),
- );
-
- const title =
- og["og:title"] ??
- twitter["twitter:title"] ??
- $("title").text() ??
- undefined;
- const description =
- og["og:description"] ??
- twitter["twitter:description"] ??
- $('meta[name="description"]').attr("content") ??
- undefined;
- const image =
- og["og:image:secure_url"] ??
- og["og:image"] ??
- twitter["twitter:image"] ??
- undefined;
-
- return {
- title,
- description,
- image,
- };
- }
-
- const d = await ensureAuth(request);
- if (!d) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- if (
- !process.env.R2_ACCESS_KEY_ID ||
- !process.env.R2_ACCOUNT_ID ||
- !process.env.R2_SECRET_ACCESS_KEY ||
- !process.env.R2_BUCKET_NAME
- ) {
- return new Response(
- "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
- { status: 500 },
- );
- }
-
- const website = new URL(request.url).searchParams.get("website");
-
- if (!website) {
- return new Response("Missing website", { status: 400 });
- }
-
- const salt = () => Math.floor(Math.random() * 11);
- const encodeWebsite = `${encodeURIComponent(website)}${salt()}`;
-
- try {
- // this returns the og image, description and title of website
- const response = await unfurl(website);
-
- if (!response.image) {
- return new Response(JSON.stringify(response));
- }
-
- if (!process.env.DEV_IMAGES) {
- return new Response("Missing DEV_IMAGES namespace.", { status: 500 });
- }
-
- const imageUrl = await process.env.DEV_IMAGES!.get(encodeWebsite);
- if (imageUrl) {
- return new Response(
- JSON.stringify({
- image: imageUrl,
- title: response.title,
- description: response.description,
- }),
- );
- }
-
- const res = await fetch(`${response.image}`);
- const image = await res.blob();
-
- const url = new URL(
- `https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
- );
-
- url.pathname = encodeWebsite;
- url.searchParams.set("X-Amz-Expires", "3600");
-
- const signedPuturl = await r2.sign(
- new Request(url, {
- method: "PUT",
- }),
- {
- aws: { signQuery: true },
- },
- );
- await fetch(signedPuturl.url, {
- method: "PUT",
- body: image,
- });
-
- await process.env.DEV_IMAGES.put(
- encodeWebsite,
- `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
- );
-
- return new Response(
- JSON.stringify({
- image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
- title: response.title,
- description: response.description,
- }),
- );
- } catch (error) {
- console.log(error);
- return new Response(
- JSON.stringify({
- status: 500,
- error: error,
- }),
- );
- }
+ const r2 = new AwsClient({
+ accessKeyId: process.env.R2_ACCESS_KEY_ID,
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
+ });
+
+ async function unfurl(url: string) {
+ const response = await fetch(url);
+ if (response.status >= 400) {
+ throw new Error(`Error fetching url: ${response.status}`);
+ }
+ const contentType = response.headers.get("content-type");
+ if (!contentType?.includes("text/html")) {
+ throw new Error(`Content-type not right: ${contentType}`);
+ }
+
+ const content = await response.text();
+ const $ = load(content);
+
+ const og: { [key: string]: string | undefined } = {};
+ const twitter: { [key: string]: string | undefined } = {};
+
+ $("meta[property^=og:]").each(
+ // @ts-ignore, it just works so why care of type safety if someone has better way go ahead
+ (_, el) => (og[$(el).attr("property")!] = $(el).attr("content")),
+ );
+ $("meta[name^=twitter:]").each(
+ // @ts-ignore
+ (_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")),
+ );
+
+ const title =
+ og["og:title"] ??
+ twitter["twitter:title"] ??
+ $("title").text() ??
+ undefined;
+ const description =
+ og["og:description"] ??
+ twitter["twitter:description"] ??
+ $('meta[name="description"]').attr("content") ??
+ undefined;
+ const image =
+ og["og:image:secure_url"] ??
+ og["og:image"] ??
+ twitter["twitter:image"] ??
+ undefined;
+
+ return {
+ title,
+ description,
+ image,
+ };
+ }
+
+ const d = await ensureAuth(request);
+ if (!d) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ if (
+ !process.env.R2_ACCESS_KEY_ID ||
+ !process.env.R2_ACCOUNT_ID ||
+ !process.env.R2_SECRET_ACCESS_KEY ||
+ !process.env.R2_BUCKET_NAME
+ ) {
+ return new Response(
+ "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
+ { status: 500 },
+ );
+ }
+
+ const website = new URL(request.url).searchParams.get("website");
+
+ if (!website) {
+ return new Response("Missing website", { status: 400 });
+ }
+
+ const salt = () => Math.floor(Math.random() * 11);
+ const encodeWebsite = `${encodeURIComponent(website)}${salt()}`;
+
+ try {
+ // this returns the og image, description and title of website
+ const response = await unfurl(website);
+
+ if (!response.image) {
+ return new Response(JSON.stringify(response));
+ }
+
+ if (!process.env.DEV_IMAGES) {
+ return new Response("Missing DEV_IMAGES namespace.", { status: 500 });
+ }
+
+ const imageUrl = await process.env.DEV_IMAGES!.get(encodeWebsite);
+ if (imageUrl) {
+ return new Response(
+ JSON.stringify({
+ image: imageUrl,
+ title: response.title,
+ description: response.description,
+ }),
+ );
+ }
+
+ const res = await fetch(`${response.image}`);
+ const image = await res.blob();
+
+ const url = new URL(
+ `https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
+ );
+
+ url.pathname = encodeWebsite;
+ url.searchParams.set("X-Amz-Expires", "3600");
+
+ const signedPuturl = await r2.sign(
+ new Request(url, {
+ method: "PUT",
+ }),
+ {
+ aws: { signQuery: true },
+ },
+ );
+ await fetch(signedPuturl.url, {
+ method: "PUT",
+ body: image,
+ });
+
+ await process.env.DEV_IMAGES.put(
+ encodeWebsite,
+ `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
+ );
+
+ return new Response(
+ JSON.stringify({
+ image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
+ title: response.title,
+ description: response.description,
+ }),
+ );
+ } catch (error) {
+ console.log(error);
+ return new Response(
+ JSON.stringify({
+ status: 500,
+ error: error,
+ }),
+ );
+ }
}
diff --git a/apps/web/app/api/upload_image/route.ts b/apps/web/app/api/upload_image/route.ts
index 0d93c5b0..7ba75576 100644
--- a/apps/web/app/api/upload_image/route.ts
+++ b/apps/web/app/api/upload_image/route.ts
@@ -7,50 +7,50 @@ import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function PUT(request: NextRequest) {
- const d = await ensureAuth(request);
-
- if (!d) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- const reqUrl = new URL(request.url);
- const filename = reqUrl.searchParams.get("filename");
-
- if (!filename) {
- return new Response("Missing filename", { status: 400 });
- }
-
- if (
- !process.env.R2_ENDPOINT ||
- !process.env.R2_ACCESS_ID ||
- !process.env.R2_SECRET_KEY ||
- !process.env.R2_BUCKET_NAME
- ) {
- return new Response(
- "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
- { status: 500 },
- );
- }
-
- const s3 = new S3Client({
- region: "auto",
- endpoint: process.env.R2_ENDPOINT,
- credentials: {
- accessKeyId: process.env.R2_ACCESS_ID,
- secretAccessKey: process.env.R2_SECRET_KEY,
- },
- });
-
- const url = await getSignedUrl(
- s3,
- new PutObjectCommand({ Bucket: process.env.R2_BUCKET_NAME, Key: filename }),
- { expiresIn: 3600 },
- );
-
- return new Response(JSON.stringify({ url }), {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- },
- });
+ const d = await ensureAuth(request);
+
+ if (!d) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ const reqUrl = new URL(request.url);
+ const filename = reqUrl.searchParams.get("filename");
+
+ if (!filename) {
+ return new Response("Missing filename", { status: 400 });
+ }
+
+ if (
+ !process.env.R2_ENDPOINT ||
+ !process.env.R2_ACCESS_ID ||
+ !process.env.R2_SECRET_KEY ||
+ !process.env.R2_BUCKET_NAME
+ ) {
+ return new Response(
+ "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
+ { status: 500 },
+ );
+ }
+
+ const s3 = new S3Client({
+ region: "auto",
+ endpoint: process.env.R2_ENDPOINT,
+ credentials: {
+ accessKeyId: process.env.R2_ACCESS_ID,
+ secretAccessKey: process.env.R2_SECRET_KEY,
+ },
+ });
+
+ const url = await getSignedUrl(
+ s3,
+ new PutObjectCommand({ Bucket: process.env.R2_BUCKET_NAME, Key: filename }),
+ { expiresIn: 3600 },
+ );
+
+ return new Response(JSON.stringify({ url }), {
+ status: 200,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
}
diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx
index 9388e06e..786d6682 100644
--- a/apps/web/app/global-error.tsx
+++ b/apps/web/app/global-error.tsx
@@ -5,23 +5,23 @@ import NextError from "next/error";
import { useEffect } from "react";
export default function GlobalError({
- error,
+ error,
}: {
- error: Error & { digest?: string };
+ error: Error & { digest?: string };
}) {
- useEffect(() => {
- Sentry.captureException(error);
- }, [error]);
+ useEffect(() => {
+ Sentry.captureException(error);
+ }, [error]);
- return (
- <html>
- <body>
- {/* `NextError` is the default Next.js error page component. Its type
+ return (
+ <html>
+ <body>
+ {/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router
does not expose status codes for errors, we simply pass 0 to render a
generic error message. */}
- <NextError statusCode={0} />
- </body>
- </html>
- );
+ <NextError statusCode={0} />
+ </body>
+ </html>
+ );
}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index affe0cfb..cf6e9b0f 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -12,74 +12,74 @@ const inter = Inter({ subsets: ["latin"] });
export const runtime = "edge";
export const metadata: Metadata = {
- title: "Supermemory - Your personal second brain.",
- description:
- "Bring saved information from all over the internet into one place where you can connect it, and ask AI about it",
- openGraph: {
- images: [
- {
- url: "https://supermemory.ai/og-image.png",
- width: 1200,
- height: 627,
- alt: "Supermemory - Your personal second brain.",
- },
- ],
- },
- metadataBase: {
- host: "https://supermemory.ai",
- href: "/",
- origin: "https://supermemory.ai",
- password: "supermemory",
- hash: "supermemory",
- pathname: "/",
- search: "",
- username: "supermemoryai",
- hostname: "supermemory.ai",
- port: "",
- protocol: "https:",
- searchParams: new URLSearchParams(""),
- toString: () => "https://supermemory.ai/",
- toJSON: () => "https://supermemory.ai/",
- },
- twitter: {
- card: "summary_large_image",
- site: "https://supermemory.ai",
- creator: "https://supermemory.ai",
- title: "Supermemory - Your personal second brain.",
- description:
- "Bring saved information from all over the internet into one place where you can connect it, and ask AI about it",
- images: [
- {
- url: "https://supermemory.ai/og-image.png",
- width: 1200,
- height: 627,
- alt: "Supermemory - Your personal second brain.",
- },
- ],
- },
+ title: "Supermemory - Your personal second brain.",
+ description:
+ "Bring saved information from all over the internet into one place where you can connect it, and ask AI about it",
+ openGraph: {
+ images: [
+ {
+ url: "https://supermemory.ai/og-image.png",
+ width: 1200,
+ height: 627,
+ alt: "Supermemory - Your personal second brain.",
+ },
+ ],
+ },
+ metadataBase: {
+ host: "https://supermemory.ai",
+ href: "/",
+ origin: "https://supermemory.ai",
+ password: "supermemory",
+ hash: "supermemory",
+ pathname: "/",
+ search: "",
+ username: "supermemoryai",
+ hostname: "supermemory.ai",
+ port: "",
+ protocol: "https:",
+ searchParams: new URLSearchParams(""),
+ toString: () => "https://supermemory.ai/",
+ toJSON: () => "https://supermemory.ai/",
+ },
+ twitter: {
+ card: "summary_large_image",
+ site: "https://supermemory.ai",
+ creator: "https://supermemory.ai",
+ title: "Supermemory - Your personal second brain.",
+ description:
+ "Bring saved information from all over the internet into one place where you can connect it, and ask AI about it",
+ images: [
+ {
+ url: "https://supermemory.ai/og-image.png",
+ width: 1200,
+ height: 627,
+ alt: "Supermemory - Your personal second brain.",
+ },
+ ],
+ },
};
export default function RootLayout({
- children,
+ children,
}: {
- children: React.ReactNode;
+ children: React.ReactNode;
}): JSX.Element {
- return (
- <html lang="en" className="overflow-x-hidden" suppressHydrationWarning>
- {/* <head>
+ return (
+ <html lang="en" className="overflow-x-hidden" suppressHydrationWarning>
+ {/* <head>
<ThemeScript />
</head> */}
- {/* TODO: when lightmode support is added, remove the 'dark' class from the body tag */}
- <body
- className={cn(
- `${inter.className} dark`,
- GeistMono.variable,
- GeistSans.variable,
- )}
- >
- {children}
- <Toaster />
- </body>
- </html>
- );
+ {/* TODO: when lightmode support is added, remove the 'dark' class from the body tag */}
+ <body
+ className={cn(
+ `${inter.className} dark`,
+ GeistMono.variable,
+ GeistSans.variable,
+ )}
+ >
+ {children}
+ <Toaster />
+ </body>
+ </html>
+ );
}
diff --git a/apps/web/app/ref/page.tsx b/apps/web/app/ref/page.tsx
index b51a16bb..f61e9616 100644
--- a/apps/web/app/ref/page.tsx
+++ b/apps/web/app/ref/page.tsx
@@ -8,111 +8,111 @@ import { getThemeToggler } from "../../lib/get-theme-button";
export const runtime = "edge";
export default async function Page() {
- const usr = await auth();
+ const usr = await auth();
- const userCount = await db
- .select({
- count: sql<number>`count(*)`.mapWith(Number),
- })
- .from(users);
+ const userCount = await db
+ .select({
+ count: sql<number>`count(*)`.mapWith(Number),
+ })
+ .from(users);
- const SetThemeButton = getThemeToggler();
+ const SetThemeButton = getThemeToggler();
- return (
- <main className="flex flex-col items-center justify-center min-h-screen">
- <div className="flex max-w-2xl justify-between w-full">
- <SetThemeButton />
+ return (
+ <main className="flex flex-col items-center justify-center min-h-screen">
+ <div className="flex max-w-2xl justify-between w-full">
+ <SetThemeButton />
- <div className="flex gap-2 items-center justify-center">
- {" "}
- <svg
- viewBox="0 0 256 116"
- xmlns="http://www.w3.org/2000/svg"
- width="45px"
- height="45px"
- preserveAspectRatio="xMidYMid"
- >
- <path
- fill="#FFF"
- d="m202.357 49.394-5.311-2.124C172.085 103.434 72.786 69.289 66.81 85.997c-.996 11.286 54.227 2.146 93.706 4.059 12.039.583 18.076 9.671 12.964 24.484l10.069.031c11.615-36.209 48.683-17.73 50.232-29.68-2.545-7.857-42.601 0-31.425-35.497Z"
- />
- <path
- fill="#F4811F"
- d="M176.332 108.348c1.593-5.31 1.062-10.622-1.593-13.809-2.656-3.187-6.374-5.31-11.154-5.842L71.17 87.634c-.531 0-1.062-.53-1.593-.53-.531-.532-.531-1.063 0-1.594.531-1.062 1.062-1.594 2.124-1.594l92.946-1.062c11.154-.53 22.839-9.56 27.087-20.182l5.312-13.809c0-.532.531-1.063 0-1.594C191.203 20.182 166.772 0 138.091 0 111.535 0 88.697 16.995 80.73 40.896c-5.311-3.718-11.684-5.843-19.12-5.31-12.747 1.061-22.838 11.683-24.432 24.43-.531 3.187 0 6.374.532 9.56C16.996 70.107 0 87.103 0 108.348c0 2.124 0 3.718.531 5.842 0 1.063 1.062 1.594 1.594 1.594h170.489c1.062 0 2.125-.53 2.125-1.594l1.593-5.842Z"
- />
- <path
- fill="#FAAD3F"
- d="M205.544 48.863h-2.656c-.531 0-1.062.53-1.593 1.062l-3.718 12.747c-1.593 5.31-1.062 10.623 1.594 13.809 2.655 3.187 6.373 5.31 11.153 5.843l19.652 1.062c.53 0 1.062.53 1.593.53.53.532.53 1.063 0 1.594-.531 1.063-1.062 1.594-2.125 1.594l-20.182 1.062c-11.154.53-22.838 9.56-27.087 20.182l-1.063 4.78c-.531.532 0 1.594 1.063 1.594h70.108c1.062 0 1.593-.531 1.593-1.593 1.062-4.25 2.124-9.03 2.124-13.81 0-27.618-22.838-50.456-50.456-50.456"
- />
- </svg>
- <span className="italic">Cloudflare Next Saas Starter</span>
- </div>
+ <div className="flex gap-2 items-center justify-center">
+ {" "}
+ <svg
+ viewBox="0 0 256 116"
+ xmlns="http://www.w3.org/2000/svg"
+ width="45px"
+ height="45px"
+ preserveAspectRatio="xMidYMid"
+ >
+ <path
+ fill="#FFF"
+ d="m202.357 49.394-5.311-2.124C172.085 103.434 72.786 69.289 66.81 85.997c-.996 11.286 54.227 2.146 93.706 4.059 12.039.583 18.076 9.671 12.964 24.484l10.069.031c11.615-36.209 48.683-17.73 50.232-29.68-2.545-7.857-42.601 0-31.425-35.497Z"
+ />
+ <path
+ fill="#F4811F"
+ d="M176.332 108.348c1.593-5.31 1.062-10.622-1.593-13.809-2.656-3.187-6.374-5.31-11.154-5.842L71.17 87.634c-.531 0-1.062-.53-1.593-.53-.531-.532-.531-1.063 0-1.594.531-1.062 1.062-1.594 2.124-1.594l92.946-1.062c11.154-.53 22.839-9.56 27.087-20.182l5.312-13.809c0-.532.531-1.063 0-1.594C191.203 20.182 166.772 0 138.091 0 111.535 0 88.697 16.995 80.73 40.896c-5.311-3.718-11.684-5.843-19.12-5.31-12.747 1.061-22.838 11.683-24.432 24.43-.531 3.187 0 6.374.532 9.56C16.996 70.107 0 87.103 0 108.348c0 2.124 0 3.718.531 5.842 0 1.063 1.062 1.594 1.594 1.594h170.489c1.062 0 2.125-.53 2.125-1.594l1.593-5.842Z"
+ />
+ <path
+ fill="#FAAD3F"
+ d="M205.544 48.863h-2.656c-.531 0-1.062.53-1.593 1.062l-3.718 12.747c-1.593 5.31-1.062 10.623 1.594 13.809 2.655 3.187 6.373 5.31 11.153 5.843l19.652 1.062c.53 0 1.062.53 1.593.53.53.532.53 1.063 0 1.594-.531 1.063-1.062 1.594-2.125 1.594l-20.182 1.062c-11.154.53-22.838 9.56-27.087 20.182l-1.063 4.78c-.531.532 0 1.594 1.063 1.594h70.108c1.062 0 1.593-.531 1.593-1.593 1.062-4.25 2.124-9.03 2.124-13.81 0-27.618-22.838-50.456-50.456-50.456"
+ />
+ </svg>
+ <span className="italic">Cloudflare Next Saas Starter</span>
+ </div>
- <div className="border border-black dark:border-white rounded-2xl p-2 flex items-center">
- Start by editing apps/web/page.tsx
- </div>
- </div>
+ <div className="border border-black dark:border-white rounded-2xl p-2 flex items-center">
+ Start by editing apps/web/page.tsx
+ </div>
+ </div>
- <div className="max-w-2xl text-start w-full mt-16">
- Welcome to Cloudflare Next Saas Starter. <br /> Built a full stack app
- using production-ready tools and frameworks, host on Cloudflare
- instantly.
- <br />
- An opinionated, batteries-included framework with{" "}
- <a
- className="text-transparent bg-clip-text bg-gradient-to-r from-[#a93d64] to-[#275ba9]"
- href="https://turbo.build"
- >
- Turborepo
- </a>{" "}
- and Nextjs. Fully Typesafe. Best practices followed by default.
- <br /> <br />
- Here's what the stack includes:
- <ul className="list-disc mt-4 prose dark:prose-invert">
- <li>
- Authentication with <code>next-auth</code>
- </li>
- <li>Database using Cloudflare's D1 serverless databases</li>
- <li>Drizzle ORM, already connected to your database and auth ⚡</li>
- <li>Light/darkmode theming that works with server components (!)</li>
- <li>Styling using TailwindCSS and ShadcnUI</li>
- <li>Turborepo with a landing page and shared components</li>
- <li>Cloudflare wrangler for quick functions on the edge</li>
- <li>
- ... best part: everything's already set up for you. Just code!
- </li>
- </ul>
- <div className="mt-4 flex flex-col gap-2">
- <span>Number of users in database: {userCount[0]!.count}</span>
- </div>
- {usr?.user?.email ? (
- <>
- <div className="mt-4 flex flex-col gap-2">
- <span>Hello {usr.user.name} 👋</span>
- <span>{usr.user.email}</span>
- </div>
- <form
- action={async () => {
- "use server";
- await signOut();
- }}
- >
- <Button variant={"destructive"} className="mt-4">
- Sign out
- </Button>
- </form>
- </>
- ) : (
- <form
- action={async () => {
- "use server";
- await signIn("google");
- }}
- >
- <Button className="mt-4">Login with Google</Button>
- </form>
- )}
- </div>
- </main>
- );
+ <div className="max-w-2xl text-start w-full mt-16">
+ Welcome to Cloudflare Next Saas Starter. <br /> Built a full stack app
+ using production-ready tools and frameworks, host on Cloudflare
+ instantly.
+ <br />
+ An opinionated, batteries-included framework with{" "}
+ <a
+ className="text-transparent bg-clip-text bg-gradient-to-r from-[#a93d64] to-[#275ba9]"
+ href="https://turbo.build"
+ >
+ Turborepo
+ </a>{" "}
+ and Nextjs. Fully Typesafe. Best practices followed by default.
+ <br /> <br />
+ Here's what the stack includes:
+ <ul className="list-disc mt-4 prose dark:prose-invert">
+ <li>
+ Authentication with <code>next-auth</code>
+ </li>
+ <li>Database using Cloudflare's D1 serverless databases</li>
+ <li>Drizzle ORM, already connected to your database and auth ⚡</li>
+ <li>Light/darkmode theming that works with server components (!)</li>
+ <li>Styling using TailwindCSS and ShadcnUI</li>
+ <li>Turborepo with a landing page and shared components</li>
+ <li>Cloudflare wrangler for quick functions on the edge</li>
+ <li>
+ ... best part: everything's already set up for you. Just code!
+ </li>
+ </ul>
+ <div className="mt-4 flex flex-col gap-2">
+ <span>Number of users in database: {userCount[0]!.count}</span>
+ </div>
+ {usr?.user?.email ? (
+ <>
+ <div className="mt-4 flex flex-col gap-2">
+ <span>Hello {usr.user.name} 👋</span>
+ <span>{usr.user.email}</span>
+ </div>
+ <form
+ action={async () => {
+ "use server";
+ await signOut();
+ }}
+ >
+ <Button variant={"destructive"} className="mt-4">
+ Sign out
+ </Button>
+ </form>
+ </>
+ ) : (
+ <form
+ action={async () => {
+ "use server";
+ await signIn("google");
+ }}
+ >
+ <Button className="mt-4">Login with Google</Button>
+ </form>
+ )}
+ </div>
+ </main>
+ );
}
diff --git a/apps/web/cf-env.d.ts b/apps/web/cf-env.d.ts
index 72bd946b..7381d63e 100644
--- a/apps/web/cf-env.d.ts
+++ b/apps/web/cf-env.d.ts
@@ -1,24 +1,26 @@
declare global {
- namespace NodeJS {
- interface ProcessEnv extends CloudflareEnv {
- GOOGLE_CLIENT_ID: string;
- GOOGLE_CLIENT_SECRET: string;
- AUTH_SECRET: string;
+ namespace NodeJS {
+ interface ProcessEnv extends CloudflareEnv {
+ GOOGLE_CLIENT_ID: string;
+ GOOGLE_CLIENT_SECRET: string;
+ AUTH_SECRET: string;
- R2_ENDPOINT: string;
- R2_ACCESS_KEY_ID: string;
- R2_SECRET_ACCESS_KEY: string;
- R2_PUBLIC_BUCKET_ADDRESS: string;
- R2_BUCKET_NAME: string;
+ R2_ENDPOINT: string;
+ R2_ACCESS_KEY_ID: string;
+ R2_SECRET_ACCESS_KEY: string;
+ R2_PUBLIC_BUCKET_ADDRESS: string;
+ R2_BUCKET_NAME: string;
- BACKEND_SECURITY_KEY: string;
- BACKEND_BASE_URL: string;
+ BACKEND_SECURITY_KEY: string;
+ BACKEND_BASE_URL: string;
- CLOUDFLARE_ACCOUNT_ID: string;
- CLOUDFLARE_DATABASE_ID: string;
- CLOUDFLARE_D1_TOKEN: string;
- }
- }
+ CLOUDFLARE_ACCOUNT_ID: string;
+ CLOUDFLARE_DATABASE_ID: string;
+ CLOUDFLARE_D1_TOKEN: string;
+
+ MOBILE_TRUST_TOKEN: string;
+ }
+ }
}
export {};
diff --git a/apps/web/components/canvas/canvas.tsx b/apps/web/components/canvas/canvas.tsx
index 1fbff4b8..904cad3a 100644
--- a/apps/web/components/canvas/canvas.tsx
+++ b/apps/web/components/canvas/canvas.tsx
@@ -16,81 +16,81 @@ import { useRect } from "./resizableLayout";
// import "./canvas.css";
export const Canvas = memo(() => {
- const [isDraggingOver, setIsDraggingOver] = useState<boolean>(false);
- const Dragref = useRef<HTMLDivElement | null>(null);
+ const [isDraggingOver, setIsDraggingOver] = useState<boolean>(false);
+ const Dragref = useRef<HTMLDivElement | null>(null);
- const handleDragOver = (event: any) => {
- event.preventDefault();
- setIsDraggingOver(true);
- console.log("entere");
- };
+ const handleDragOver = (event: any) => {
+ event.preventDefault();
+ setIsDraggingOver(true);
+ console.log("entere");
+ };
- useEffect(() => {
- const divElement = Dragref.current;
- if (divElement) {
- divElement.addEventListener("dragover", handleDragOver);
- }
- return () => {
- if (divElement) {
- divElement.removeEventListener("dragover", handleDragOver);
- }
- };
- }, []);
+ useEffect(() => {
+ const divElement = Dragref.current;
+ if (divElement) {
+ divElement.addEventListener("dragover", handleDragOver);
+ }
+ return () => {
+ if (divElement) {
+ divElement.removeEventListener("dragover", handleDragOver);
+ }
+ };
+ }, []);
- return (
- <DragContext.Provider value={{ isDraggingOver, setIsDraggingOver }}>
- <div ref={Dragref} className="w-full h-full">
- <TldrawComponent />
- </div>
- </DragContext.Provider>
- );
+ return (
+ <DragContext.Provider value={{ isDraggingOver, setIsDraggingOver }}>
+ <div ref={Dragref} className="w-full h-full">
+ <TldrawComponent />
+ </div>
+ </DragContext.Provider>
+ );
});
const TldrawComponent = memo(() => {
- const { id } = useRect();
- const [storeWithStatus, setStoreWithStatus] = useState<TLStoreWithStatus>({
- status: "loading",
- });
- useEffect(() => {
- const fetchStore = async () => {
- const store = await loadRemoteSnapshot(id);
+ const { id } = useRect();
+ const [storeWithStatus, setStoreWithStatus] = useState<TLStoreWithStatus>({
+ status: "loading",
+ });
+ useEffect(() => {
+ const fetchStore = async () => {
+ const store = await loadRemoteSnapshot(id);
- setStoreWithStatus({
- store: store,
- status: "not-synced",
- });
- };
+ setStoreWithStatus({
+ store: store,
+ status: "not-synced",
+ });
+ };
- fetchStore();
- }, []);
+ fetchStore();
+ }, []);
- const handleMount = useCallback((editor: Editor) => {
- (window as any).app = editor;
- (window as any).editor = editor;
- editor.registerExternalAssetHandler("url", createAssetFromUrl);
- editor.registerExternalContentHandler("url", ({ url, point, sources }) => {
- createEmbedsFromUrl({ url, point, sources, editor });
- });
- }, []);
+ const handleMount = useCallback((editor: Editor) => {
+ (window as any).app = editor;
+ (window as any).editor = editor;
+ editor.registerExternalAssetHandler("url", createAssetFromUrl);
+ editor.registerExternalContentHandler("url", ({ url, point, sources }) => {
+ createEmbedsFromUrl({ url, point, sources, editor });
+ });
+ }, []);
- setUserPreferences({ id: "supermemory", colorScheme: "dark" });
+ setUserPreferences({ id: "supermemory", colorScheme: "dark" });
- const assetUrls = getAssetUrls();
- return (
- <div className="w-full h-full">
- <Tldraw
- className="relative"
- assetUrls={assetUrls}
- components={components}
- store={storeWithStatus}
- shapeUtils={[twitterCardUtil, textCardUtil]}
- onMount={handleMount}
- >
- <div className="absolute left-1/2 top-0 z-[1000000] flex -translate-x-1/2 gap-2 bg-[#2C3439] text-[#B3BCC5]">
- <SaveStatus id={id} />
- </div>
- <DropZone />
- </Tldraw>
- </div>
- );
+ const assetUrls = getAssetUrls();
+ return (
+ <div className="w-full h-full">
+ <Tldraw
+ className="relative"
+ assetUrls={assetUrls}
+ components={components}
+ store={storeWithStatus}
+ shapeUtils={[twitterCardUtil, textCardUtil]}
+ onMount={handleMount}
+ >
+ <div className="absolute left-1/2 top-0 z-[1000000] flex -translate-x-1/2 gap-2 bg-[#2C3439] text-[#B3BCC5]">
+ <SaveStatus id={id} />
+ </div>
+ <DropZone />
+ </Tldraw>
+ </div>
+ );
});
diff --git a/apps/web/components/canvas/draggableComponent.tsx b/apps/web/components/canvas/draggableComponent.tsx
index 36461163..4d72995b 100644
--- a/apps/web/components/canvas/draggableComponent.tsx
+++ b/apps/web/components/canvas/draggableComponent.tsx
@@ -3,54 +3,54 @@ import { useRef, useState } from "react";
import { motion } from "framer-motion";
export default function DraggableComponentsContainer({
- content,
+ content,
}: {
- content: { context: string }[] | undefined;
+ content: { context: string }[] | undefined;
}) {
- if (content === undefined) return null;
- return (
- <div className="flex flex-col gap-10">
- {content.map((i) => {
- return <DraggableComponents content={i.context} />;
- })}
- </div>
- );
+ if (content === undefined) return null;
+ return (
+ <div className="flex flex-col gap-10">
+ {content.map((i) => {
+ return <DraggableComponents content={i.context} />;
+ })}
+ </div>
+ );
}
function DraggableComponents({ content }: { content: string }) {
- const [isDragging, setIsDragging] = useState(false);
- const containerRef = useRef<HTMLDivElement>(null);
+ const [isDragging, setIsDragging] = useState(false);
+ const containerRef = useRef<HTMLDivElement>(null);
- const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
- setIsDragging(true);
- if (containerRef.current) {
- // Serialize the children as a string for dataTransfer
- const childrenHtml = containerRef.current.innerHTML;
- event.dataTransfer.setData("text/html", childrenHtml);
- }
- };
+ const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
+ setIsDragging(true);
+ if (containerRef.current) {
+ // Serialize the children as a string for dataTransfer
+ const childrenHtml = containerRef.current.innerHTML;
+ event.dataTransfer.setData("text/html", childrenHtml);
+ }
+ };
- const handleDragEnd = () => {
- setIsDragging(false);
- };
+ const handleDragEnd = () => {
+ setIsDragging(false);
+ };
- return (
- <motion.div
- initial={{ opacity: 0, y: 5 }}
- animate={{ opacity: 1, y: 0 }}
- ref={containerRef}
- onDragEnd={handleDragEnd}
- // @ts-expect-error TODO: fix this
- onDragStart={handleDragStart}
- draggable
- className={`flex gap-4 px-3 overflow-hidden rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
- >
- <div className="flex flex-col gap-2">
- <div>
- <h1 className="line-clamp-3">{content}</h1>
- </div>
- {/* <p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p> */}
- </div>
- </motion.div>
- );
+ return (
+ <motion.div
+ initial={{ opacity: 0, y: 5 }}
+ animate={{ opacity: 1, y: 0 }}
+ ref={containerRef}
+ onDragEnd={handleDragEnd}
+ // @ts-expect-error TODO: fix this
+ onDragStart={handleDragStart}
+ draggable
+ className={`flex gap-4 px-3 overflow-hidden rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
+ >
+ <div className="flex flex-col gap-2">
+ <div>
+ <h1 className="line-clamp-3">{content}</h1>
+ </div>
+ {/* <p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p> */}
+ </div>
+ </motion.div>
+ );
}
diff --git a/apps/web/components/canvas/dropComponent.tsx b/apps/web/components/canvas/dropComponent.tsx
index 5ea383a1..df7a55ad 100644
--- a/apps/web/components/canvas/dropComponent.tsx
+++ b/apps/web/components/canvas/dropComponent.tsx
@@ -1,206 +1,206 @@
import React, { useRef, useCallback, useEffect, useContext } from "react";
import { useEditor } from "tldraw";
import DragContext, {
- DragContextType,
- useDragContext,
+ DragContextType,
+ useDragContext,
} from "../../lib/context";
import { handleExternalDroppedContent } from "../../lib/createEmbeds";
const stripHtmlTags = (html: string): string => {
- const div = document.createElement("div");
- div.innerHTML = html;
- return div.textContent || div.innerText || "";
+ const div = document.createElement("div");
+ div.innerHTML = html;
+ return div.textContent || div.innerText || "";
};
function formatTextToRatio(text: string) {
- const totalWidth = text.length;
- const maxLineWidth = Math.floor(totalWidth / 4);
-
- const words = text.split(" ");
- let lines = [];
- let currentLine = "";
-
- words.forEach((word) => {
- // Check if adding the next word exceeds the maximum line width
- if ((currentLine + word).length <= maxLineWidth) {
- currentLine += (currentLine ? " " : "") + word;
- } else {
- // If the current line is full, push it to new line
- lines.push(currentLine);
- currentLine = word;
- }
- });
- if (currentLine) {
- lines.push(currentLine);
- }
- return lines.join("\n");
+ const totalWidth = text.length;
+ const maxLineWidth = Math.floor(totalWidth / 4);
+
+ const words = text.split(" ");
+ let lines = [];
+ let currentLine = "";
+
+ words.forEach((word) => {
+ // Check if adding the next word exceeds the maximum line width
+ if ((currentLine + word).length <= maxLineWidth) {
+ currentLine += (currentLine ? " " : "") + word;
+ } else {
+ // If the current line is full, push it to new line
+ lines.push(currentLine);
+ currentLine = word;
+ }
+ });
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+ return lines.join("\n");
}
function DropZone() {
- const dropRef = useRef<HTMLDivElement | null>(null);
- const { isDraggingOver, setIsDraggingOver } = useDragContext();
-
- const editor = useEditor();
-
- const handleDragLeave = () => {
- setIsDraggingOver(false);
- console.log("leaver");
- };
-
- useEffect(() => {
- setInterval(() => {
- editor.selectAll();
- const shapes = editor.getSelectedShapes();
- const text = shapes.filter((s) => s.type === "text");
- console.log("hrhh", text);
- }, 5000);
- }, []);
-
- const handleDrop = useCallback((event: DragEvent) => {
- event.preventDefault();
- setIsDraggingOver(false);
- const dt = event.dataTransfer;
- if (!dt) {
- return;
- }
- const items = dt.items;
-
- for (let i = 0; i < items.length; i++) {
- if (items[i]!.kind === "file" && items[i]!.type.startsWith("image/")) {
- const file = items[i]!.getAsFile();
- if (file) {
- const reader = new FileReader();
- reader.onload = (e) => {
- if (e.target) {
- // setDroppedImage(e.target.result as string);
- }
- };
- reader.readAsDataURL(file);
- }
- } else if (items[i]!.kind === "string") {
- items[i]!.getAsString((data) => {
- const cleanText = stripHtmlTags(data);
- const onethree = formatTextToRatio(cleanText);
- handleExternalDroppedContent({ editor, text: onethree });
- });
- }
- }
- }, []);
-
- useEffect(() => {
- const divElement = dropRef.current;
- if (divElement) {
- divElement.addEventListener("drop", handleDrop);
- divElement.addEventListener("dragleave", handleDragLeave);
- }
- return () => {
- if (divElement) {
- divElement.removeEventListener("drop", handleDrop);
- divElement.addEventListener("dragleave", handleDragLeave);
- }
- };
- }, []);
-
- return (
- <div
- className={`h-full flex justify-center items-center w-full absolute top-0 left-0 z-[100000] pointer-events-none ${isDraggingOver && "bg-[#2c3439ad] pointer-events-auto"}`}
- ref={dropRef}
- >
- {isDraggingOver && (
- <>
- <div className="absolute top-4 left-8">
- <TopRight />
- </div>
- <div className="absolute top-4 right-8">
- <TopLeft />
- </div>
- <div className="absolute bottom-4 left-8">
- <BottomLeft />
- </div>
- <div className="absolute bottom-4 right-8">
- <BottomRight />
- </div>
- <h2 className="text-2xl">Drop here to add Content on Canvas</h2>
- </>
- )}
- </div>
- );
+ const dropRef = useRef<HTMLDivElement | null>(null);
+ const { isDraggingOver, setIsDraggingOver } = useDragContext();
+
+ const editor = useEditor();
+
+ const handleDragLeave = () => {
+ setIsDraggingOver(false);
+ console.log("leaver");
+ };
+
+ useEffect(() => {
+ setInterval(() => {
+ editor.selectAll();
+ const shapes = editor.getSelectedShapes();
+ const text = shapes.filter((s) => s.type === "text");
+ console.log("hrhh", text);
+ }, 5000);
+ }, []);
+
+ const handleDrop = useCallback((event: DragEvent) => {
+ event.preventDefault();
+ setIsDraggingOver(false);
+ const dt = event.dataTransfer;
+ if (!dt) {
+ return;
+ }
+ const items = dt.items;
+
+ for (let i = 0; i < items.length; i++) {
+ if (items[i]!.kind === "file" && items[i]!.type.startsWith("image/")) {
+ const file = items[i]!.getAsFile();
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ if (e.target) {
+ // setDroppedImage(e.target.result as string);
+ }
+ };
+ reader.readAsDataURL(file);
+ }
+ } else if (items[i]!.kind === "string") {
+ items[i]!.getAsString((data) => {
+ const cleanText = stripHtmlTags(data);
+ const onethree = formatTextToRatio(cleanText);
+ handleExternalDroppedContent({ editor, text: onethree });
+ });
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ const divElement = dropRef.current;
+ if (divElement) {
+ divElement.addEventListener("drop", handleDrop);
+ divElement.addEventListener("dragleave", handleDragLeave);
+ }
+ return () => {
+ if (divElement) {
+ divElement.removeEventListener("drop", handleDrop);
+ divElement.addEventListener("dragleave", handleDragLeave);
+ }
+ };
+ }, []);
+
+ return (
+ <div
+ className={`h-full flex justify-center items-center w-full absolute top-0 left-0 z-[100000] pointer-events-none ${isDraggingOver && "bg-[#2c3439ad] pointer-events-auto"}`}
+ ref={dropRef}
+ >
+ {isDraggingOver && (
+ <>
+ <div className="absolute top-4 left-8">
+ <TopRight />
+ </div>
+ <div className="absolute top-4 right-8">
+ <TopLeft />
+ </div>
+ <div className="absolute bottom-4 left-8">
+ <BottomLeft />
+ </div>
+ <div className="absolute bottom-4 right-8">
+ <BottomRight />
+ </div>
+ <h2 className="text-2xl">Drop here to add Content on Canvas</h2>
+ </>
+ )}
+ </div>
+ );
}
function TopRight() {
- return (
- <svg
- width="48"
- height="48"
- viewBox="0 0 48 48"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M44 4H12C7.58172 4 4 7.58172 4 12V44"
- stroke="white"
- stroke-width="8"
- stroke-linecap="round"
- />
- </svg>
- );
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M44 4H12C7.58172 4 4 7.58172 4 12V44"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
}
function TopLeft() {
- return (
- <svg
- width="48"
- height="48"
- viewBox="0 0 48 48"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M4 4H36C40.4183 4 44 7.58172 44 12V44"
- stroke="white"
- stroke-width="8"
- stroke-linecap="round"
- />
- </svg>
- );
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M4 4H36C40.4183 4 44 7.58172 44 12V44"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
}
function BottomLeft() {
- return (
- <svg
- width="48"
- height="48"
- viewBox="0 0 48 48"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M44 44H12C7.58172 44 4 40.4183 4 36V4"
- stroke="white"
- stroke-width="8"
- stroke-linecap="round"
- />
- </svg>
- );
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M44 44H12C7.58172 44 4 40.4183 4 36V4"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
}
function BottomRight() {
- return (
- <svg
- width="48"
- height="48"
- viewBox="0 0 48 48"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M4 44H36C40.4183 44 44 40.4183 44 36V4"
- stroke="white"
- stroke-width="8"
- stroke-linecap="round"
- />
- </svg>
- );
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M4 44H36C40.4183 44 44 40.4183 44 36V4"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
}
export default DropZone;
diff --git a/apps/web/components/canvas/enabledComp copy.tsx b/apps/web/components/canvas/enabledComp copy.tsx
index b87ef227..e8ed403d 100644
--- a/apps/web/components/canvas/enabledComp copy.tsx
+++ b/apps/web/components/canvas/enabledComp copy.tsx
@@ -1,22 +1,22 @@
import { TLUiComponents } from "tldraw";
export const components: Partial<TLUiComponents> = {
- ActionsMenu: null,
- MainMenu: null,
- QuickActions: null,
- TopPanel: null,
- DebugPanel: null,
- DebugMenu: null,
- PageMenu: null,
- // Minimap: null,
- // ContextMenu: null,
- // HelpMenu: null,
- // ZoomMenu: null,
- // StylePanel: null,
- // NavigationPanel: null,
- // Toolbar: null,
- // KeyboardShortcutsDialog: null,
- // HelperButtons: null,
- // SharePanel: null,
- // MenuPanel: null,
+ ActionsMenu: null,
+ MainMenu: null,
+ QuickActions: null,
+ TopPanel: null,
+ DebugPanel: null,
+ DebugMenu: null,
+ PageMenu: null,
+ // Minimap: null,
+ // ContextMenu: null,
+ // HelpMenu: null,
+ // ZoomMenu: null,
+ // StylePanel: null,
+ // NavigationPanel: null,
+ // Toolbar: null,
+ // KeyboardShortcutsDialog: null,
+ // HelperButtons: null,
+ // SharePanel: null,
+ // MenuPanel: null,
};
diff --git a/apps/web/components/canvas/enabledComp.tsx b/apps/web/components/canvas/enabledComp.tsx
index b87ef227..e8ed403d 100644
--- a/apps/web/components/canvas/enabledComp.tsx
+++ b/apps/web/components/canvas/enabledComp.tsx
@@ -1,22 +1,22 @@
import { TLUiComponents } from "tldraw";
export const components: Partial<TLUiComponents> = {
- ActionsMenu: null,
- MainMenu: null,
- QuickActions: null,
- TopPanel: null,
- DebugPanel: null,
- DebugMenu: null,
- PageMenu: null,
- // Minimap: null,
- // ContextMenu: null,
- // HelpMenu: null,
- // ZoomMenu: null,
- // StylePanel: null,
- // NavigationPanel: null,
- // Toolbar: null,
- // KeyboardShortcutsDialog: null,
- // HelperButtons: null,
- // SharePanel: null,
- // MenuPanel: null,
+ ActionsMenu: null,
+ MainMenu: null,
+ QuickActions: null,
+ TopPanel: null,
+ DebugPanel: null,
+ DebugMenu: null,
+ PageMenu: null,
+ // Minimap: null,
+ // ContextMenu: null,
+ // HelpMenu: null,
+ // ZoomMenu: null,
+ // StylePanel: null,
+ // NavigationPanel: null,
+ // Toolbar: null,
+ // KeyboardShortcutsDialog: null,
+ // HelperButtons: null,
+ // SharePanel: null,
+ // MenuPanel: null,
};
diff --git a/apps/web/components/canvas/resizableLayout.tsx b/apps/web/components/canvas/resizableLayout.tsx
index 04e407fa..de4d124c 100644
--- a/apps/web/components/canvas/resizableLayout.tsx
+++ b/apps/web/components/canvas/resizableLayout.tsx
@@ -9,169 +9,169 @@ import Image from "next/image";
import { Label } from "@repo/ui/shadcn/label";
interface RectContextType {
- fullScreen: boolean;
- setFullScreen: React.Dispatch<React.SetStateAction<boolean>>;
- visible: boolean;
- setVisible: React.Dispatch<React.SetStateAction<boolean>>;
- id: string;
+ fullScreen: boolean;
+ setFullScreen: React.Dispatch<React.SetStateAction<boolean>>;
+ visible: boolean;
+ setVisible: React.Dispatch<React.SetStateAction<boolean>>;
+ id: string;
}
const RectContext = createContext<RectContextType | undefined>(undefined);
export const RectProvider = ({
- id,
- children,
+ id,
+ children,
}: {
- id: string;
- children: React.ReactNode;
+ id: string;
+ children: React.ReactNode;
}) => {
- const [fullScreen, setFullScreen] = useState(false);
- const [visible, setVisible] = useState(true);
+ const [fullScreen, setFullScreen] = useState(false);
+ const [visible, setVisible] = useState(true);
- const value = {
- id,
- fullScreen,
- setFullScreen,
- visible,
- setVisible,
- };
+ const value = {
+ id,
+ fullScreen,
+ setFullScreen,
+ visible,
+ setVisible,
+ };
- return <RectContext.Provider value={value}>{children}</RectContext.Provider>;
+ return <RectContext.Provider value={value}>{children}</RectContext.Provider>;
};
export const useRect = () => {
- const context = useContext(RectContext);
- if (context === undefined) {
- throw new Error("useRect must be used within a RectProvider");
- }
- return context;
+ const context = useContext(RectContext);
+ if (context === undefined) {
+ throw new Error("useRect must be used within a RectProvider");
+ }
+ return context;
};
export function ResizaleLayout() {
- const { setVisible, fullScreen, setFullScreen } = useRect();
+ const { setVisible, fullScreen, setFullScreen } = useRect();
- return (
- <div
- className={`h-screen w-full ${!fullScreen ? "px-4 py-6" : "bg-[#1F2428]"} transition-all`}
- >
- <PanelGroup
- onLayout={(l) => {
- l[0]! < 20 ? setVisible(false) : setVisible(true);
- }}
- className={` ${fullScreen ? "w-[calc(100vw-2rem)]" : "w-screen"} transition-all`}
- direction="horizontal"
- >
- <Panel
- onExpand={() => {
- setTimeout(() => setFullScreen(false), 50);
- }}
- onCollapse={() => {
- setTimeout(() => setFullScreen(true), 50);
- }}
- defaultSize={30}
- collapsible={true}
- >
- <SidePanelContainer />
- </Panel>
- <PanelResizeHandle
- className={`relative flex items-center transition-all justify-center ${!fullScreen && "px-1"}`}
- >
- <DragIconContainer />
- </PanelResizeHandle>
- <Panel className="relative" defaultSize={70} minSize={60}>
- <CanvasContainer />
- </Panel>
- </PanelGroup>
- </div>
- );
+ return (
+ <div
+ className={`h-screen w-full ${!fullScreen ? "px-4 py-6" : "bg-[#1F2428]"} transition-all`}
+ >
+ <PanelGroup
+ onLayout={(l) => {
+ l[0]! < 20 ? setVisible(false) : setVisible(true);
+ }}
+ className={` ${fullScreen ? "w-[calc(100vw-2rem)]" : "w-screen"} transition-all`}
+ direction="horizontal"
+ >
+ <Panel
+ onExpand={() => {
+ setTimeout(() => setFullScreen(false), 50);
+ }}
+ onCollapse={() => {
+ setTimeout(() => setFullScreen(true), 50);
+ }}
+ defaultSize={30}
+ collapsible={true}
+ >
+ <SidePanelContainer />
+ </Panel>
+ <PanelResizeHandle
+ className={`relative flex items-center transition-all justify-center ${!fullScreen && "px-1"}`}
+ >
+ <DragIconContainer />
+ </PanelResizeHandle>
+ <Panel className="relative" defaultSize={70} minSize={60}>
+ <CanvasContainer />
+ </Panel>
+ </PanelGroup>
+ </div>
+ );
}
function DragIconContainer() {
- const { fullScreen } = useRect();
- return (
- <div
- className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
- >
- <Image src={DragIcon} alt="drag-icon" />
- </div>
- );
+ const { fullScreen } = useRect();
+ return (
+ <div
+ className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
+ >
+ <Image src={DragIcon} alt="drag-icon" />
+ </div>
+ );
}
function CanvasContainer() {
- const { fullScreen } = useRect();
- return (
- <div
- className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
- >
- <Canvas />
- </div>
- );
+ const { fullScreen } = useRect();
+ return (
+ <div
+ className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
+ >
+ <Canvas />
+ </div>
+ );
}
function SidePanelContainer() {
- const { fullScreen, visible } = useRect();
- return (
- <div
- className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen" : "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}
- >
- <div className="flex items-center justify-between bg-[#2C3439] px-4 py-2 text-lg font-medium text-[#989EA4]">
- Change Filters
- <Image src={SettingsIcon} alt="setting-icon" />
- </div>
- {visible ? (
- <SidePanel />
- ) : (
- <h1 className="text-center py-10 text-xl">Need more space to show!</h1>
- )}
- </div>
- );
+ const { fullScreen, visible } = useRect();
+ return (
+ <div
+ className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen" : "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}
+ >
+ <div className="flex items-center justify-between bg-[#2C3439] px-4 py-2 text-lg font-medium text-[#989EA4]">
+ Change Filters
+ <Image src={SettingsIcon} alt="setting-icon" />
+ </div>
+ {visible ? (
+ <SidePanel />
+ ) : (
+ <h1 className="text-center py-10 text-xl">Need more space to show!</h1>
+ )}
+ </div>
+ );
}
function SidePanel() {
- const [content, setContent] = useState<{ context: string }[]>();
- return (
- <>
- <div className="px-3 py-5">
- <form
- action={async (FormData) => {
- const search = FormData.get("search");
- console.log(search);
- const res = await fetch("/api/canvasai", {
- method: "POST",
- body: JSON.stringify({ query: search }),
- });
- const t = await res.json();
- // @ts-expect-error TODO: fix this
- console.log(t.response.response);
- // @ts-expect-error TODO: fix this
- setContent(t.response.response);
- }}
- >
- <input
- placeholder="search..."
- name="search"
- className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
- />
- </form>
- </div>
- <DraggableComponentsContainer content={content} />
- </>
- );
+ const [content, setContent] = useState<{ context: string }[]>();
+ return (
+ <>
+ <div className="px-3 py-5">
+ <form
+ action={async (FormData) => {
+ const search = FormData.get("search");
+ console.log(search);
+ const res = await fetch("/api/canvasai", {
+ method: "POST",
+ body: JSON.stringify({ query: search }),
+ });
+ const t = await res.json();
+ // @ts-expect-error TODO: fix this
+ console.log(t.response.response);
+ // @ts-expect-error TODO: fix this
+ setContent(t.response.response);
+ }}
+ >
+ <input
+ placeholder="search..."
+ name="search"
+ className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
+ />
+ </form>
+ </div>
+ <DraggableComponentsContainer content={content} />
+ </>
+ );
}
const content = [
- {
- content:
- "Regional growth patterns diverge, with strong performance in the United States and several emerging markets, contrasted by weaker prospects in many advanced economies, particularly in Europe (World Economic Forum) (OECD). The rapid adoption of artificial intelligence (AI) is expected to drive productivity growth, especially in advanced economies, potentially mitigating labor shortages and boosting income levels in emerging markets (World Economic Forum) (OECD). However, ongoing geopolitical tensions and economic fragmentation are likely to maintain a level of uncertainty and volatility in the global economy (World Economic Forum.",
- iconAlt: "Autocomplete",
- extraInfo:
- "Page Url: https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
- },
- {
- content:
- "As of mid-2024, the global economy is experiencing modest growth with significant regional disparities. Global GDP growth is projected to be around 3.1% in 2024, rising slightly to 3.2% in 2025. This performance, although below the pre-pandemic average, reflects resilience despite various economic pressures, including tight monetary conditions and geopolitical tensions (IMF)(OECD) Inflation is moderating faster than expected, with global headline inflation projected to fall to 5.8% in 2024 and 4.4% in 2025, contributing to improving real incomes and positive trade growth (IMF) (OECD)",
- iconAlt: "Autocomplete",
- extraInfo:
- "Page Url: https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia",
- },
+ {
+ content:
+ "Regional growth patterns diverge, with strong performance in the United States and several emerging markets, contrasted by weaker prospects in many advanced economies, particularly in Europe (World Economic Forum) (OECD). The rapid adoption of artificial intelligence (AI) is expected to drive productivity growth, especially in advanced economies, potentially mitigating labor shortages and boosting income levels in emerging markets (World Economic Forum) (OECD). However, ongoing geopolitical tensions and economic fragmentation are likely to maintain a level of uncertainty and volatility in the global economy (World Economic Forum.",
+ iconAlt: "Autocomplete",
+ extraInfo:
+ "Page Url: https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
+ },
+ {
+ content:
+ "As of mid-2024, the global economy is experiencing modest growth with significant regional disparities. Global GDP growth is projected to be around 3.1% in 2024, rising slightly to 3.2% in 2025. This performance, although below the pre-pandemic average, reflects resilience despite various economic pressures, including tight monetary conditions and geopolitical tensions (IMF)(OECD) Inflation is moderating faster than expected, with global headline inflation projected to fall to 5.8% in 2024 and 4.4% in 2025, contributing to improving real incomes and positive trade growth (IMF) (OECD)",
+ iconAlt: "Autocomplete",
+ extraInfo:
+ "Page Url: https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia",
+ },
];
diff --git a/apps/web/components/canvas/savesnap.tsx b/apps/web/components/canvas/savesnap.tsx
index a8cacd3e..fd04b3df 100644
--- a/apps/web/components/canvas/savesnap.tsx
+++ b/apps/web/components/canvas/savesnap.tsx
@@ -3,33 +3,33 @@ import { debounce, getSnapshot, useEditor } from "tldraw";
import { SaveCanvas } from "@/app/actions/doers";
export function SaveStatus({ id }: { id: string }) {
- const [save, setSave] = useState("saved!");
- const editor = useEditor();
+ const [save, setSave] = useState("saved!");
+ const editor = useEditor();
- const debouncedSave = useCallback(
- debounce(async () => {
- const snapshot = getSnapshot(editor.store);
- const bounds = editor.getViewportPageBounds();
- console.log(bounds);
+ const debouncedSave = useCallback(
+ debounce(async () => {
+ const snapshot = getSnapshot(editor.store);
+ const bounds = editor.getViewportPageBounds();
+ console.log(bounds);
- SaveCanvas({ id, data: JSON.stringify({ snapshot, bounds }) });
+ SaveCanvas({ id, data: JSON.stringify({ snapshot, bounds }) });
- setSave("saved!");
- }, 3000),
- [editor], // Dependency array ensures the function is not recreated on every render
- );
+ setSave("saved!");
+ }, 3000),
+ [editor], // Dependency array ensures the function is not recreated on every render
+ );
- useEffect(() => {
- const unsubscribe = editor.store.listen(
- () => {
- setSave("saving...");
- debouncedSave();
- },
- { scope: "document", source: "user" },
- );
+ useEffect(() => {
+ const unsubscribe = editor.store.listen(
+ () => {
+ setSave("saving...");
+ debouncedSave();
+ },
+ { scope: "document", source: "user" },
+ );
- return () => unsubscribe(); // Cleanup on unmount
- }, [editor, debouncedSave]);
+ return () => unsubscribe(); // Cleanup on unmount
+ }, [editor, debouncedSave]);
- return <button>{save}</button>;
+ return <button>{save}</button>;
}
diff --git a/apps/web/components/canvas/textCard.tsx b/apps/web/components/canvas/textCard.tsx
index 600dc1a5..5f649135 100644
--- a/apps/web/components/canvas/textCard.tsx
+++ b/apps/web/components/canvas/textCard.tsx
@@ -1,47 +1,47 @@
import { BaseBoxShapeUtil, HTMLContainer, TLBaseShape } from "tldraw";
type ITextCardShape = TLBaseShape<
- "Textcard",
- { w: number; h: number; content: string; extrainfo: string }
+ "Textcard",
+ { w: number; h: number; content: string; extrainfo: string }
>;
export class textCardUtil extends BaseBoxShapeUtil<ITextCardShape> {
- static override type = "Textcard" as const;
+ static override type = "Textcard" as const;
- getDefaultProps(): ITextCardShape["props"] {
- return {
- w: 100,
- h: 50,
- content: "",
- extrainfo: "",
- };
- }
+ getDefaultProps(): ITextCardShape["props"] {
+ return {
+ w: 100,
+ h: 50,
+ content: "",
+ extrainfo: "",
+ };
+ }
- component(s: ITextCardShape) {
- return (
- <HTMLContainer className="flex h-full w-full items-center justify-center">
- <div
- style={{
- height: s.props.h,
- width: s.props.w,
- pointerEvents: "all",
- background: "#2E3C4C",
- borderRadius: "16px",
- border: "2px solid #3e4449",
- padding: "8px 14px",
- overflow: "auto",
- }}
- >
- <h1 style={{ fontSize: "15px" }}>{s.props.content}</h1>
- <p style={{ fontSize: "14px", color: "#369DFD" }}>
- {s.props.extrainfo}
- </p>
- </div>
- </HTMLContainer>
- );
- }
+ component(s: ITextCardShape) {
+ return (
+ <HTMLContainer className="flex h-full w-full items-center justify-center">
+ <div
+ style={{
+ height: s.props.h,
+ width: s.props.w,
+ pointerEvents: "all",
+ background: "#2E3C4C",
+ borderRadius: "16px",
+ border: "2px solid #3e4449",
+ padding: "8px 14px",
+ overflow: "auto",
+ }}
+ >
+ <h1 style={{ fontSize: "15px" }}>{s.props.content}</h1>
+ <p style={{ fontSize: "14px", color: "#369DFD" }}>
+ {s.props.extrainfo}
+ </p>
+ </div>
+ </HTMLContainer>
+ );
+ }
- indicator(shape: ITextCardShape) {
- return <rect width={shape.props.w} height={shape.props.h} />;
- }
+ indicator(shape: ITextCardShape) {
+ return <rect width={shape.props.w} height={shape.props.h} />;
+ }
}
diff --git a/apps/web/components/canvas/twitterCard.tsx b/apps/web/components/canvas/twitterCard.tsx
index 8cf8e576..6aebf5ff 100644
--- a/apps/web/components/canvas/twitterCard.tsx
+++ b/apps/web/components/canvas/twitterCard.tsx
@@ -1,79 +1,79 @@
import {
- BaseBoxShapeUtil,
- HTMLContainer,
- TLBaseShape,
- toDomPrecision,
+ BaseBoxShapeUtil,
+ HTMLContainer,
+ TLBaseShape,
+ toDomPrecision,
} from "tldraw";
type ITwitterCardShape = TLBaseShape<
- "Twittercard",
- { w: number; h: number; url: string }
+ "Twittercard",
+ { w: number; h: number; url: string }
>;
export class twitterCardUtil extends BaseBoxShapeUtil<ITwitterCardShape> {
- static override type = "Twittercard" as const;
+ static override type = "Twittercard" as const;
- getDefaultProps(): ITwitterCardShape["props"] {
- return {
- w: 500,
- h: 550,
- url: "",
- };
- }
+ getDefaultProps(): ITwitterCardShape["props"] {
+ return {
+ w: 500,
+ h: 550,
+ url: "",
+ };
+ }
- component(s: ITwitterCardShape) {
- return (
- <HTMLContainer className="flex h-full w-full items-center justify-center">
- <TwitterPost
- url={s.props.url}
- width={s.props.w}
- isInteractive={false}
- height={s.props.h}
- />
- </HTMLContainer>
- );
- }
+ component(s: ITwitterCardShape) {
+ return (
+ <HTMLContainer className="flex h-full w-full items-center justify-center">
+ <TwitterPost
+ url={s.props.url}
+ width={s.props.w}
+ isInteractive={false}
+ height={s.props.h}
+ />
+ </HTMLContainer>
+ );
+ }
- indicator(shape: ITwitterCardShape) {
- return <rect width={shape.props.w} height={shape.props.h} />;
- }
+ indicator(shape: ITwitterCardShape) {
+ return <rect width={shape.props.w} height={shape.props.h} />;
+ }
}
function TwitterPost({
- isInteractive,
- width,
- height,
- url,
+ isInteractive,
+ width,
+ height,
+ url,
}: {
- isInteractive: boolean;
- width: number;
- height: number;
- url: string;
+ isInteractive: boolean;
+ width: number;
+ height: number;
+ url: string;
}) {
- const link = (() => {
- try {
- const urlObj = new URL(url);
- const path = urlObj.pathname;
- return path;
- } catch (error) {
- console.error("Invalid URL", error);
- return null;
- }
- })();
+ const link = (() => {
+ try {
+ const urlObj = new URL(url);
+ const path = urlObj.pathname;
+ return path;
+ } catch (error) {
+ console.error("Invalid URL", error);
+ return null;
+ }
+ })();
- return (
- <iframe
- className="tl-embed"
- draggable={false}
- width={toDomPrecision(width)}
- height={toDomPrecision(height)}
- seamless
- referrerPolicy="no-referrer-when-downgrade"
- style={{
- pointerEvents: isInteractive ? "all" : "none",
- zIndex: isInteractive ? "" : "-1",
- }}
- srcDoc={`
+ return (
+ <iframe
+ className="tl-embed"
+ draggable={false}
+ width={toDomPrecision(width)}
+ height={toDomPrecision(height)}
+ seamless
+ referrerPolicy="no-referrer-when-downgrade"
+ style={{
+ pointerEvents: isInteractive ? "all" : "none",
+ zIndex: isInteractive ? "" : "-1",
+ }}
+ srcDoc={`
<html lang="en">
<head>
<meta charset="UTF-8">
@@ -84,6 +84,6 @@ function TwitterPost({
<blockquote data-theme="dark" class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</body>
</html>`}
- />
- );
+ />
+ );
}
diff --git a/apps/web/components/twitter/icons/icons.module.css b/apps/web/components/twitter/icons/icons.module.css
index aca493e6..ff9c07f8 100644
--- a/apps/web/components/twitter/icons/icons.module.css
+++ b/apps/web/components/twitter/icons/icons.module.css
@@ -1,9 +1,9 @@
.verified {
- margin-left: 0.125rem;
- max-width: 20px;
- max-height: 20px;
- height: 1.25em;
- fill: currentColor;
- user-select: none;
- vertical-align: text-bottom;
+ margin-left: 0.125rem;
+ max-width: 20px;
+ max-height: 20px;
+ height: 1.25em;
+ fill: currentColor;
+ user-select: none;
+ vertical-align: text-bottom;
}
diff --git a/apps/web/components/twitter/icons/verified-business.tsx b/apps/web/components/twitter/icons/verified-business.tsx
index 06d574bd..c93829c6 100644
--- a/apps/web/components/twitter/icons/verified-business.tsx
+++ b/apps/web/components/twitter/icons/verified-business.tsx
@@ -1,53 +1,53 @@
import s from "./icons.module.css";
export const VerifiedBusiness = () => (
- <svg
- viewBox="0 0 22 22"
- aria-label="Verified account"
- role="img"
- className={s.verified}
- >
- <g>
- <linearGradient
- gradientUnits="userSpaceOnUse"
- id="0-a"
- x1="4.411"
- x2="18.083"
- y1="2.495"
- y2="21.508"
- >
- <stop offset="0" stopColor="#f4e72a"></stop>
- <stop offset=".539" stopColor="#cd8105"></stop>
- <stop offset=".68" stopColor="#cb7b00"></stop>
- <stop offset="1" stopColor="#f4ec26"></stop>
- <stop offset="1" stopColor="#f4e72a"></stop>
- </linearGradient>
- <linearGradient
- gradientUnits="userSpaceOnUse"
- id="0-b"
- x1="5.355"
- x2="16.361"
- y1="3.395"
- y2="19.133"
- >
- <stop offset="0" stopColor="#f9e87f"></stop>
- <stop offset=".406" stopColor="#e2b719"></stop>
- <stop offset=".989" stopColor="#e2b719"></stop>
- </linearGradient>
- <g clipRule="evenodd" fillRule="evenodd">
- <path
- d="M13.324 3.848L11 1.6 8.676 3.848l-3.201-.453-.559 3.184L2.06 8.095 3.48 11l-1.42 2.904 2.856 1.516.559 3.184 3.201-.452L11 20.4l2.324-2.248 3.201.452.559-3.184 2.856-1.516L18.52 11l1.42-2.905-2.856-1.516-.559-3.184zm-7.09 7.575l3.428 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
- fill="url(#0-a)"
- ></path>
- <path
- d="M13.101 4.533L11 2.5 8.899 4.533l-2.895-.41-.505 2.88-2.583 1.37L4.2 11l-1.284 2.627 2.583 1.37.505 2.88 2.895-.41L11 19.5l2.101-2.033 2.895.41.505-2.88 2.583-1.37L17.8 11l1.284-2.627-2.583-1.37-.505-2.88zm-6.868 6.89l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
- fill="url(#0-b)"
- ></path>
- <path
- d="M6.233 11.423l3.429 3.428 5.65-6.17.038-.033-.005 1.398-5.683 6.206-3.429-3.429-.003-1.405.005.003z"
- fill="#d18800"
- ></path>
- </g>
- </g>
- </svg>
+ <svg
+ viewBox="0 0 22 22"
+ aria-label="Verified account"
+ role="img"
+ className={s.verified}
+ >
+ <g>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="0-a"
+ x1="4.411"
+ x2="18.083"
+ y1="2.495"
+ y2="21.508"
+ >
+ <stop offset="0" stopColor="#f4e72a"></stop>
+ <stop offset=".539" stopColor="#cd8105"></stop>
+ <stop offset=".68" stopColor="#cb7b00"></stop>
+ <stop offset="1" stopColor="#f4ec26"></stop>
+ <stop offset="1" stopColor="#f4e72a"></stop>
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="0-b"
+ x1="5.355"
+ x2="16.361"
+ y1="3.395"
+ y2="19.133"
+ >
+ <stop offset="0" stopColor="#f9e87f"></stop>
+ <stop offset=".406" stopColor="#e2b719"></stop>
+ <stop offset=".989" stopColor="#e2b719"></stop>
+ </linearGradient>
+ <g clipRule="evenodd" fillRule="evenodd">
+ <path
+ d="M13.324 3.848L11 1.6 8.676 3.848l-3.201-.453-.559 3.184L2.06 8.095 3.48 11l-1.42 2.904 2.856 1.516.559 3.184 3.201-.452L11 20.4l2.324-2.248 3.201.452.559-3.184 2.856-1.516L18.52 11l1.42-2.905-2.856-1.516-.559-3.184zm-7.09 7.575l3.428 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
+ fill="url(#0-a)"
+ ></path>
+ <path
+ d="M13.101 4.533L11 2.5 8.899 4.533l-2.895-.41-.505 2.88-2.583 1.37L4.2 11l-1.284 2.627 2.583 1.37.505 2.88 2.895-.41L11 19.5l2.101-2.033 2.895.41.505-2.88 2.583-1.37L17.8 11l1.284-2.627-2.583-1.37-.505-2.88zm-6.868 6.89l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
+ fill="url(#0-b)"
+ ></path>
+ <path
+ d="M6.233 11.423l3.429 3.428 5.65-6.17.038-.033-.005 1.398-5.683 6.206-3.429-3.429-.003-1.405.005.003z"
+ fill="#d18800"
+ ></path>
+ </g>
+ </g>
+ </svg>
);
diff --git a/apps/web/components/twitter/icons/verified-government.tsx b/apps/web/components/twitter/icons/verified-government.tsx
index 601a6910..2673198a 100644
--- a/apps/web/components/twitter/icons/verified-government.tsx
+++ b/apps/web/components/twitter/icons/verified-government.tsx
@@ -1,18 +1,18 @@
import s from "./icons.module.css";
export const VerifiedGovernment = () => (
- <svg
- viewBox="0 0 22 22"
- aria-label="Verified account"
- role="img"
- className={s.verified}
- >
- <g>
- <path
- clipRule="evenodd"
- d="M12.05 2.056c-.568-.608-1.532-.608-2.1 0l-1.393 1.49c-.284.303-.685.47-1.1.455L5.42 3.932c-.832-.028-1.514.654-1.486 1.486l.069 2.039c.014.415-.152.816-.456 1.1l-1.49 1.392c-.608.568-.608 1.533 0 2.101l1.49 1.393c.304.284.47.684.456 1.1l-.07 2.038c-.027.832.655 1.514 1.487 1.486l2.038-.069c.415-.014.816.152 1.1.455l1.392 1.49c.569.609 1.533.609 2.102 0l1.393-1.49c.283-.303.684-.47 1.099-.455l2.038.069c.832.028 1.515-.654 1.486-1.486L18 14.542c-.015-.415.152-.815.455-1.099l1.49-1.393c.608-.568.608-1.533 0-2.101l-1.49-1.393c-.303-.283-.47-.684-.455-1.1l.068-2.038c.029-.832-.654-1.514-1.486-1.486l-2.038.07c-.415.013-.816-.153-1.1-.456zm-5.817 9.367l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
- fillRule="evenodd"
- ></path>
- </g>
- </svg>
+ <svg
+ viewBox="0 0 22 22"
+ aria-label="Verified account"
+ role="img"
+ className={s.verified}
+ >
+ <g>
+ <path
+ clipRule="evenodd"
+ d="M12.05 2.056c-.568-.608-1.532-.608-2.1 0l-1.393 1.49c-.284.303-.685.47-1.1.455L5.42 3.932c-.832-.028-1.514.654-1.486 1.486l.069 2.039c.014.415-.152.816-.456 1.1l-1.49 1.392c-.608.568-.608 1.533 0 2.101l1.49 1.393c.304.284.47.684.456 1.1l-.07 2.038c-.027.832.655 1.514 1.487 1.486l2.038-.069c.415-.014.816.152 1.1.455l1.392 1.49c.569.609 1.533.609 2.102 0l1.393-1.49c.283-.303.684-.47 1.099-.455l2.038.069c.832.028 1.515-.654 1.486-1.486L18 14.542c-.015-.415.152-.815.455-1.099l1.49-1.393c.608-.568.608-1.533 0-2.101l-1.49-1.393c-.303-.283-.47-.684-.455-1.1l.068-2.038c.029-.832-.654-1.514-1.486-1.486l-2.038.07c-.415.013-.816-.153-1.1-.456zm-5.817 9.367l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
+ fillRule="evenodd"
+ ></path>
+ </g>
+ </svg>
);
diff --git a/apps/web/components/twitter/icons/verified.tsx b/apps/web/components/twitter/icons/verified.tsx
index 81c9fc25..7490017a 100644
--- a/apps/web/components/twitter/icons/verified.tsx
+++ b/apps/web/components/twitter/icons/verified.tsx
@@ -1,14 +1,14 @@
import s from "./icons.module.css";
export const Verified = () => (
- <svg
- viewBox="0 0 24 24"
- aria-label="Verified account"
- role="img"
- className={s.verified}
- >
- <g>
- <path d="M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.33 2.19c-1.4-.46-2.91-.2-3.92.81s-1.26 2.52-.8 3.91c-1.31.67-2.2 1.91-2.2 3.34s.89 2.67 2.2 3.34c-.46 1.39-.21 2.9.8 3.91s2.52 1.26 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.68-.88 3.34-2.19c1.39.45 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z"></path>
- </g>
- </svg>
+ <svg
+ viewBox="0 0 24 24"
+ aria-label="Verified account"
+ role="img"
+ className={s.verified}
+ >
+ <g>
+ <path d="M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.33 2.19c-1.4-.46-2.91-.2-3.92.81s-1.26 2.52-.8 3.91c-1.31.67-2.2 1.91-2.2 3.34s.89 2.67 2.2 3.34c-.46 1.39-.21 2.9.8 3.91s2.52 1.26 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.68-.88 3.34-2.19c1.39.45 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z"></path>
+ </g>
+ </svg>
);
diff --git a/apps/web/components/twitter/render-tweet.tsx b/apps/web/components/twitter/render-tweet.tsx
index 889f1977..0e7f2c1e 100644
--- a/apps/web/components/twitter/render-tweet.tsx
+++ b/apps/web/components/twitter/render-tweet.tsx
@@ -1,121 +1,121 @@
import type { Tweet } from "react-tweet/api";
import {
- type TwitterComponents,
- TweetContainer,
- TweetInReplyTo,
- TweetBody,
- TweetMedia,
- TweetInfo,
- QuotedTweet,
- enrichTweet,
- EnrichedTweet,
+ type TwitterComponents,
+ TweetContainer,
+ TweetInReplyTo,
+ TweetBody,
+ TweetMedia,
+ TweetInfo,
+ QuotedTweet,
+ enrichTweet,
+ EnrichedTweet,
} from "react-tweet";
import clsx from "clsx";
import s from "./tweet-header.module.css";
import { VerifiedBadge } from "./verified-badge";
type Props = {
- tweet: Tweet | { error: string };
- components?: TwitterComponents;
+ tweet: Tweet | { error: string };
+ components?: TwitterComponents;
};
type AvatarImgProps = {
- src: string;
- alt: string;
- width: number;
- height: number;
+ src: string;
+ alt: string;
+ width: number;
+ height: number;
};
const AvatarImg = (props: AvatarImgProps) => <img {...props} />;
const TweetHeader = ({
- tweet,
- components,
+ tweet,
+ components,
}: {
- tweet: EnrichedTweet;
- components?: TwitterComponents;
+ tweet: EnrichedTweet;
+ components?: TwitterComponents;
}) => {
- const Img = components?.AvatarImg ?? AvatarImg;
- const { user } = tweet;
+ const Img = components?.AvatarImg ?? AvatarImg;
+ const { user } = tweet;
- return (
- <div className={s.header}>
- <a
- href={tweet.url}
- className={s.avatar}
- target="_blank"
- rel="noopener noreferrer"
- >
- <div
- className={clsx(
- s.avatarOverflow,
- user.profile_image_shape === "Square" && s.avatarSquare,
- )}
- >
- <Img
- src={user.profile_image_url_https}
- alt={user.name}
- width={48}
- height={48}
- />
- </div>
- <div className={s.avatarOverflow}>
- <div className={s.avatarShadow}></div>
- </div>
- </a>
- <div className={s.author}>
- <a
- href={tweet.url}
- className={s.authorLink}
- target="_blank"
- rel="noopener noreferrer"
- >
- <div className={s.authorLinkText}>
- <span title={user.name}>{user.name}</span>
- </div>
- <VerifiedBadge user={user} className={s.authorVerified} />
- </a>
- <div className={s.authorMeta}>
- <a
- href={tweet.url}
- className={s.username}
- target="_blank"
- rel="noopener noreferrer"
- >
- <span title={`@${user.screen_name}`}>@{user.screen_name}</span>
- </a>
- <div className={s.authorFollow}>
- <span className={s.separator}>·</span>
- <a
- href={user.follow_url}
- className={s.follow}
- target="_blank"
- rel="noopener noreferrer"
- >
- Follow
- </a>
- </div>
- </div>
- </div>
- </div>
- );
+ return (
+ <div className={s.header}>
+ <a
+ href={tweet.url}
+ className={s.avatar}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <div
+ className={clsx(
+ s.avatarOverflow,
+ user.profile_image_shape === "Square" && s.avatarSquare,
+ )}
+ >
+ <Img
+ src={user.profile_image_url_https}
+ alt={user.name}
+ width={48}
+ height={48}
+ />
+ </div>
+ <div className={s.avatarOverflow}>
+ <div className={s.avatarShadow}></div>
+ </div>
+ </a>
+ <div className={s.author}>
+ <a
+ href={tweet.url}
+ className={s.authorLink}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <div className={s.authorLinkText}>
+ <span title={user.name}>{user.name}</span>
+ </div>
+ <VerifiedBadge user={user} className={s.authorVerified} />
+ </a>
+ <div className={s.authorMeta}>
+ <a
+ href={tweet.url}
+ className={s.username}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <span title={`@${user.screen_name}`}>@{user.screen_name}</span>
+ </a>
+ <div className={s.authorFollow}>
+ <span className={s.separator}>·</span>
+ <a
+ href={user.follow_url}
+ className={s.follow}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ Follow
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
};
export const MyTweet = ({ tweet: t, components }: Props) => {
- if ("error" in t) {
- return <div>{t.error}</div>;
- }
+ if ("error" in t) {
+ return <div>{t.error}</div>;
+ }
- const tweet = enrichTweet(t);
- return (
- <TweetContainer className="bg-transparent !m-0 !p-0 !z-0">
- <TweetHeader tweet={tweet} components={components} />
- {tweet.in_reply_to_status_id_str && <TweetInReplyTo tweet={tweet} />}
- <TweetBody tweet={tweet} />
- {tweet.mediaDetails?.length ? (
- <TweetMedia tweet={tweet} components={components} />
- ) : null}
- {tweet.quoted_tweet && <QuotedTweet tweet={tweet.quoted_tweet} />}
- <TweetInfo tweet={tweet} />
- </TweetContainer>
- );
+ const tweet = enrichTweet(t);
+ return (
+ <TweetContainer className="bg-transparent !m-0 !p-0 !z-0">
+ <TweetHeader tweet={tweet} components={components} />
+ {tweet.in_reply_to_status_id_str && <TweetInReplyTo tweet={tweet} />}
+ <TweetBody tweet={tweet} />
+ {tweet.mediaDetails?.length ? (
+ <TweetMedia tweet={tweet} components={components} />
+ ) : null}
+ {tweet.quoted_tweet && <QuotedTweet tweet={tweet.quoted_tweet} />}
+ <TweetInfo tweet={tweet} />
+ </TweetContainer>
+ );
};
diff --git a/apps/web/components/twitter/tweet-header.module.css b/apps/web/components/twitter/tweet-header.module.css
index 2ce994e2..38a053dd 100644
--- a/apps/web/components/twitter/tweet-header.module.css
+++ b/apps/web/components/twitter/tweet-header.module.css
@@ -1,96 +1,96 @@
.header {
- display: flex;
- padding-bottom: 0.75rem;
- line-height: var(--tweet-header-line-height);
- font-size: var(--tweet-header-font-size);
- white-space: nowrap;
- overflow-wrap: break-word;
- overflow: hidden;
+ display: flex;
+ padding-bottom: 0.75rem;
+ line-height: var(--tweet-header-line-height);
+ font-size: var(--tweet-header-font-size);
+ white-space: nowrap;
+ overflow-wrap: break-word;
+ overflow: hidden;
}
.avatar {
- position: relative;
- height: 48px;
- width: 48px;
+ position: relative;
+ height: 48px;
+ width: 48px;
}
.avatarOverflow {
- height: 100%;
- width: 100%;
- position: absolute;
- overflow: hidden;
- border-radius: 9999px;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ overflow: hidden;
+ border-radius: 9999px;
}
.avatarSquare {
- border-radius: 4px;
+ border-radius: 4px;
}
.avatarShadow {
- height: 100%;
- width: 100%;
- transition-property: background-color;
- transition-duration: 0.2s;
- box-shadow: rgb(0 0 0 / 3%) 0px 0px 2px inset;
+ height: 100%;
+ width: 100%;
+ transition-property: background-color;
+ transition-duration: 0.2s;
+ box-shadow: rgb(0 0 0 / 3%) 0px 0px 2px inset;
}
.avatarShadow:hover {
- background-color: rgba(26, 26, 26, 0.15);
+ background-color: rgba(26, 26, 26, 0.15);
}
.author {
- max-width: calc(100% - 84px);
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin: 0 0.5rem;
+ max-width: calc(100% - 84px);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin: 0 0.5rem;
}
.authorLink {
- text-decoration: none;
- color: inherit;
- display: flex;
- align-items: center;
+ text-decoration: none;
+ color: inherit;
+ display: flex;
+ align-items: center;
}
.authorLink:hover {
- text-decoration-line: underline;
+ text-decoration-line: underline;
}
.authorVerified {
- display: inline-flex;
+ display: inline-flex;
}
.authorLinkText {
- font-weight: 700;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
+ font-weight: 700;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
}
.authorMeta {
- display: flex;
+ display: flex;
}
.authorFollow {
- display: flex;
+ display: flex;
}
.username {
- color: var(--tweet-font-color-secondary);
- text-decoration: none;
- text-overflow: ellipsis;
+ color: var(--tweet-font-color-secondary);
+ text-decoration: none;
+ text-overflow: ellipsis;
}
.follow {
- color: var(--tweet-color-blue-secondary);
- text-decoration: none;
- font-weight: 700;
+ color: var(--tweet-color-blue-secondary);
+ text-decoration: none;
+ font-weight: 700;
}
.follow:hover {
- text-decoration-line: underline;
+ text-decoration-line: underline;
}
.separator {
- padding: 0 0.25rem;
+ padding: 0 0.25rem;
}
.brand {
- margin-inline-start: auto;
+ margin-inline-start: auto;
}
.twitterIcon {
- width: 23.75px;
- height: 23.75px;
- color: var(--tweet-twitter-icon-color);
- fill: currentColor;
- user-select: none;
+ width: 23.75px;
+ height: 23.75px;
+ color: var(--tweet-twitter-icon-color);
+ fill: currentColor;
+ user-select: none;
}
diff --git a/apps/web/components/twitter/verified-badge.module.css b/apps/web/components/twitter/verified-badge.module.css
index c16e77ec..55537b1c 100644
--- a/apps/web/components/twitter/verified-badge.module.css
+++ b/apps/web/components/twitter/verified-badge.module.css
@@ -1,10 +1,10 @@
.verifiedOld {
- color: var(--tweet-verified-old-color);
+ color: var(--tweet-verified-old-color);
}
.verifiedBlue {
- color: var(--tweet-verified-blue-color);
+ color: var(--tweet-verified-blue-color);
}
.verifiedGovernment {
- /* color: var(--tweet-verified-government-color); */
- color: rgb(130, 154, 171);
+ /* color: var(--tweet-verified-government-color); */
+ color: rgb(130, 154, 171);
}
diff --git a/apps/web/components/twitter/verified-badge.tsx b/apps/web/components/twitter/verified-badge.tsx
index daa11852..e0142a48 100644
--- a/apps/web/components/twitter/verified-badge.tsx
+++ b/apps/web/components/twitter/verified-badge.tsx
@@ -3,32 +3,32 @@ import { Verified, VerifiedBusiness, VerifiedGovernment } from "./icons/index";
import s from "./verified-badge.module.css";
type Props = {
- user: any;
- className?: string;
+ user: any;
+ className?: string;
};
export const VerifiedBadge = ({ user, className }: Props) => {
- const verified = user.verified || user.is_blue_verified || user.verified_type;
- let icon = <Verified />;
- let iconClassName: string | null = s.verifiedBlue ?? null;
+ const verified = user.verified || user.is_blue_verified || user.verified_type;
+ let icon = <Verified />;
+ let iconClassName: string | null = s.verifiedBlue ?? null;
- if (verified) {
- if (!user.is_blue_verified) {
- iconClassName = s.verifiedOld!;
- }
- switch (user.verified_type) {
- case "Government":
- icon = <VerifiedGovernment />;
- iconClassName = s.verifiedGovernment!;
- break;
- case "Business":
- icon = <VerifiedBusiness />;
- iconClassName = null;
- break;
- }
- }
+ if (verified) {
+ if (!user.is_blue_verified) {
+ iconClassName = s.verifiedOld!;
+ }
+ switch (user.verified_type) {
+ case "Government":
+ icon = <VerifiedGovernment />;
+ iconClassName = s.verifiedGovernment!;
+ break;
+ case "Business":
+ icon = <VerifiedBusiness />;
+ iconClassName = null;
+ break;
+ }
+ }
- return verified ? (
- <div className={clsx(className, iconClassName)}>{icon}</div>
- ) : null;
+ return verified ? (
+ <div className={clsx(className, iconClassName)}>{icon}</div>
+ ) : null;
};
diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts
index 521e1fcb..58116123 100644
--- a/apps/web/drizzle.config.ts
+++ b/apps/web/drizzle.config.ts
@@ -1,12 +1,12 @@
import { type Config } from "drizzle-kit";
export default {
- schema: "./server/db/schema.ts",
- dialect: "sqlite",
- driver: "d1",
- dbCredentials: {
- wranglerConfigPath: "./wrangler.toml",
- dbName: "",
- },
- out: "migrations",
+ schema: "./server/db/schema.ts",
+ dialect: "sqlite",
+ driver: "d1",
+ dbCredentials: {
+ wranglerConfigPath: "./wrangler.toml",
+ dbName: "",
+ },
+ out: "migrations",
} satisfies Config;
diff --git a/apps/web/env.d.ts b/apps/web/env.d.ts
index d52ab7b5..b6a410f9 100644
--- a/apps/web/env.d.ts
+++ b/apps/web/env.d.ts
@@ -2,8 +2,8 @@
// by running `wrangler types --env-interface CloudflareEnv env.d.ts`
interface CloudflareEnv {
- STORAGE: R2Bucket;
- DATABASE: D1Database;
- DEV_IMAGES: R2Bucket;
- CANVAS_SNAPS: KVNamespace;
+ STORAGE: R2Bucket;
+ DATABASE: D1Database;
+ DEV_IMAGES: R2Bucket;
+ CANVAS_SNAPS: KVNamespace;
}
diff --git a/apps/web/instrumentation.ts b/apps/web/instrumentation.ts
index f8a929ba..60327ca5 100644
--- a/apps/web/instrumentation.ts
+++ b/apps/web/instrumentation.ts
@@ -1,9 +1,9 @@
export async function register() {
- if (process.env.NEXT_RUNTIME === "nodejs") {
- await import("./sentry.server.config");
- }
+ if (process.env.NEXT_RUNTIME === "nodejs") {
+ await import("./sentry.server.config");
+ }
- if (process.env.NEXT_RUNTIME === "edge") {
- await import("./sentry.edge.config");
- }
+ if (process.env.NEXT_RUNTIME === "edge") {
+ await import("./sentry.edge.config");
+ }
}
diff --git a/apps/web/lib/constants.ts b/apps/web/lib/constants.ts
index 7a9485cf..241a6a1d 100644
--- a/apps/web/lib/constants.ts
+++ b/apps/web/lib/constants.ts
@@ -1,43 +1,43 @@
export const LIMITS = {
- page: 100,
- tweet: 1000,
- note: 1000,
+ page: 100,
+ tweet: 1000,
+ note: 1000,
};
export const codeLanguageSubset = [
- "python",
- "javascript",
- "java",
- "go",
- "bash",
- "c",
- "cpp",
- "csharp",
- "css",
- "diff",
- "graphql",
- "json",
- "kotlin",
- "less",
- "lua",
- "makefile",
- "markdown",
- "objectivec",
- "perl",
- "php",
- "php-template",
- "plaintext",
- "python-repl",
- "r",
- "ruby",
- "rust",
- "scss",
- "shell",
- "sql",
- "swift",
- "typescript",
- "vbnet",
- "wasm",
- "xml",
- "yaml",
+ "python",
+ "javascript",
+ "java",
+ "go",
+ "bash",
+ "c",
+ "cpp",
+ "csharp",
+ "css",
+ "diff",
+ "graphql",
+ "json",
+ "kotlin",
+ "less",
+ "lua",
+ "makefile",
+ "markdown",
+ "objectivec",
+ "perl",
+ "php",
+ "php-template",
+ "plaintext",
+ "python-repl",
+ "r",
+ "ruby",
+ "rust",
+ "scss",
+ "shell",
+ "sql",
+ "swift",
+ "typescript",
+ "vbnet",
+ "wasm",
+ "xml",
+ "yaml",
];
diff --git a/apps/web/lib/context.ts b/apps/web/lib/context.ts
index 840c0d31..6c6bfa1b 100644
--- a/apps/web/lib/context.ts
+++ b/apps/web/lib/context.ts
@@ -1,18 +1,18 @@
import { createContext, useContext } from "react";
export interface DragContextType {
- isDraggingOver: boolean;
- setIsDraggingOver: React.Dispatch<React.SetStateAction<boolean>>;
+ isDraggingOver: boolean;
+ setIsDraggingOver: React.Dispatch<React.SetStateAction<boolean>>;
}
const DragContext = createContext<DragContextType | undefined>(undefined);
export const useDragContext = () => {
- const context = useContext(DragContext);
- if (context === undefined) {
- throw new Error("useAppContext must be used within an AppProvider");
- }
- return context;
+ const context = useContext(DragContext);
+ if (context === undefined) {
+ throw new Error("useAppContext must be used within an AppProvider");
+ }
+ return context;
};
export default DragContext;
diff --git a/apps/web/lib/createAssetUrl.ts b/apps/web/lib/createAssetUrl.ts
index 05c2baea..cdebb919 100644
--- a/apps/web/lib/createAssetUrl.ts
+++ b/apps/web/lib/createAssetUrl.ts
@@ -1,94 +1,94 @@
import {
- AssetRecordType,
- TLAsset,
- getHashForString,
- truncateStringWithEllipsis,
+ AssetRecordType,
+ TLAsset,
+ getHashForString,
+ truncateStringWithEllipsis,
} from "tldraw";
// import { BOOKMARK_ENDPOINT } from './config'
interface ResponseBody {
- title?: string;
- description?: string;
- image?: string;
+ title?: string;
+ description?: string;
+ image?: string;
}
export async function createAssetFromUrl({
- url,
+ url,
}: {
- type: "url";
- url: string;
+ type: "url";
+ url: string;
}): Promise<TLAsset> {
- // try {
- // // First, try to get the meta data from our endpoint
- // const meta = (await (
- // await fetch(BOOKMARK_ENDPOINT, {
- // method: 'POST',
- // headers: {
- // 'Content-Type': 'application/json',
- // },
- // body: JSON.stringify({
- // url,
- // }),
- // })
- // ).json()) as ResponseBody
+ // try {
+ // // First, try to get the meta data from our endpoint
+ // const meta = (await (
+ // await fetch(BOOKMARK_ENDPOINT, {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify({
+ // url,
+ // }),
+ // })
+ // ).json()) as ResponseBody
- // return {
- // id: AssetRecordType.createId(getHashForString(url)),
- // typeName: 'asset',
- // type: 'bookmark',
- // props: {
- // src: url,
- // description: meta.description ?? '',
- // image: meta.image ?? '',
- // title: meta.title ?? truncateStringWithEllipsis(url, 32),
- // },
- // meta: {},
- // }
- // } catch (error) {
- // Otherwise, fallback to fetching data from the url
+ // return {
+ // id: AssetRecordType.createId(getHashForString(url)),
+ // typeName: 'asset',
+ // type: 'bookmark',
+ // props: {
+ // src: url,
+ // description: meta.description ?? '',
+ // image: meta.image ?? '',
+ // title: meta.title ?? truncateStringWithEllipsis(url, 32),
+ // },
+ // meta: {},
+ // }
+ // } catch (error) {
+ // Otherwise, fallback to fetching data from the url
- let meta: { image: string; title: string; description: string };
+ let meta: { image: string; title: string; description: string };
- try {
- const resp = await fetch(url, { method: "GET", mode: "no-cors" });
- const html = await resp.text();
- const doc = new DOMParser().parseFromString(html, "text/html");
- meta = {
- image:
- doc.head
- .querySelector('meta[property="og:image"]')
- ?.getAttribute("content") ?? "",
- title:
- doc.head
- .querySelector('meta[property="og:title"]')
- ?.getAttribute("content") ?? truncateStringWithEllipsis(url, 32),
- description:
- doc.head
- .querySelector('meta[property="og:description"]')
- ?.getAttribute("content") ?? "",
- };
- } catch (error) {
- console.error(error);
- meta = {
- image: "",
- title: truncateStringWithEllipsis(url, 32),
- description: "",
- };
- }
+ try {
+ const resp = await fetch(url, { method: "GET", mode: "no-cors" });
+ const html = await resp.text();
+ const doc = new DOMParser().parseFromString(html, "text/html");
+ meta = {
+ image:
+ doc.head
+ .querySelector('meta[property="og:image"]')
+ ?.getAttribute("content") ?? "",
+ title:
+ doc.head
+ .querySelector('meta[property="og:title"]')
+ ?.getAttribute("content") ?? truncateStringWithEllipsis(url, 32),
+ description:
+ doc.head
+ .querySelector('meta[property="og:description"]')
+ ?.getAttribute("content") ?? "",
+ };
+ } catch (error) {
+ console.error(error);
+ meta = {
+ image: "",
+ title: truncateStringWithEllipsis(url, 32),
+ description: "",
+ };
+ }
- // Create the bookmark asset from the meta
- return {
- id: AssetRecordType.createId(getHashForString(url)),
- typeName: "asset",
- type: "bookmark",
- props: {
- src: url,
- image: meta.image,
- title: meta.title,
- description: meta.description,
- favicon: meta.image,
- },
- meta: {},
- };
- // }
+ // Create the bookmark asset from the meta
+ return {
+ id: AssetRecordType.createId(getHashForString(url)),
+ typeName: "asset",
+ type: "bookmark",
+ props: {
+ src: url,
+ image: meta.image,
+ title: meta.title,
+ description: meta.description,
+ favicon: meta.image,
+ },
+ meta: {},
+ };
+ // }
}
diff --git a/apps/web/lib/createEmbeds.ts b/apps/web/lib/createEmbeds.ts
index b3a7fb52..75347d31 100644
--- a/apps/web/lib/createEmbeds.ts
+++ b/apps/web/lib/createEmbeds.ts
@@ -1,236 +1,236 @@
// @ts-nocheck TODO: A LOT OF TS ERRORS HERE
import {
- AssetRecordType,
- Editor,
- TLAsset,
- TLAssetId,
- TLBookmarkShape,
- TLExternalContentSource,
- TLShapePartial,
- Vec,
- VecLike,
- createShapeId,
- getEmbedInfo,
- getHashForString,
+ AssetRecordType,
+ Editor,
+ TLAsset,
+ TLAssetId,
+ TLBookmarkShape,
+ TLExternalContentSource,
+ TLShapePartial,
+ Vec,
+ VecLike,
+ createShapeId,
+ getEmbedInfo,
+ getHashForString,
} from "tldraw";
export default async function createEmbedsFromUrl({
- url,
- point,
- sources,
- editor,
+ url,
+ point,
+ sources,
+ editor,
}: {
- url: string;
- point?: VecLike | undefined;
- sources?: TLExternalContentSource[] | undefined;
- editor: Editor;
+ url: string;
+ point?: VecLike | undefined;
+ sources?: TLExternalContentSource[] | undefined;
+ editor: Editor;
}) {
- const position =
- point ??
- (editor.inputs.shiftKey
- ? editor.inputs.currentPagePoint
- : editor.getViewportPageBounds().center);
-
- if (url?.includes("x.com") || url?.includes("twitter.com")) {
- return editor.createShape({
- type: "Twittercard",
- x: position.x - 250,
- y: position.y - 150,
- props: { url: url },
- });
- }
-
- // try to paste as an embed first
- const embedInfo = getEmbedInfo(url);
-
- if (embedInfo) {
- return editor.putExternalContent({
- type: "embed",
- url: embedInfo.url,
- point,
- embed: embedInfo.definition,
- });
- }
-
- const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url));
- const shape = createEmptyBookmarkShape(editor, url, position);
-
- // Use an existing asset if we have one, or else else create a new one
- let asset = editor.getAsset(assetId) as TLAsset;
- let shouldAlsoCreateAsset = false;
- if (!asset) {
- shouldAlsoCreateAsset = true;
- try {
- const bookmarkAsset = await editor.getAssetForExternalContent({
- type: "url",
- url,
- });
- const fetchWebsite: {
- title?: string;
- image?: string;
- description?: string;
- } = await (
- await fetch(`/api/unfirlsite?website=${url}`, {
- method: "POST",
- })
- ).json();
- if (bookmarkAsset) {
- if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title;
- if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image;
- if (fetchWebsite.description)
- bookmarkAsset.props.description = fetchWebsite.description;
- }
- if (!bookmarkAsset) throw Error("Could not create an asset");
- asset = bookmarkAsset;
- } catch (e) {
- console.log(e);
- return;
- }
- }
-
- editor.batch(() => {
- if (shouldAlsoCreateAsset) {
- editor.createAssets([asset]);
- }
-
- editor.updateShapes([
- {
- id: shape.id,
- type: shape.type,
- props: {
- assetId: asset.id,
- },
- },
- ]);
- });
+ const position =
+ point ??
+ (editor.inputs.shiftKey
+ ? editor.inputs.currentPagePoint
+ : editor.getViewportPageBounds().center);
+
+ if (url?.includes("x.com") || url?.includes("twitter.com")) {
+ return editor.createShape({
+ type: "Twittercard",
+ x: position.x - 250,
+ y: position.y - 150,
+ props: { url: url },
+ });
+ }
+
+ // try to paste as an embed first
+ const embedInfo = getEmbedInfo(url);
+
+ if (embedInfo) {
+ return editor.putExternalContent({
+ type: "embed",
+ url: embedInfo.url,
+ point,
+ embed: embedInfo.definition,
+ });
+ }
+
+ const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url));
+ const shape = createEmptyBookmarkShape(editor, url, position);
+
+ // Use an existing asset if we have one, or else else create a new one
+ let asset = editor.getAsset(assetId) as TLAsset;
+ let shouldAlsoCreateAsset = false;
+ if (!asset) {
+ shouldAlsoCreateAsset = true;
+ try {
+ const bookmarkAsset = await editor.getAssetForExternalContent({
+ type: "url",
+ url,
+ });
+ const fetchWebsite: {
+ title?: string;
+ image?: string;
+ description?: string;
+ } = await (
+ await fetch(`/api/unfirlsite?website=${url}`, {
+ method: "POST",
+ })
+ ).json();
+ if (bookmarkAsset) {
+ if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title;
+ if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image;
+ if (fetchWebsite.description)
+ bookmarkAsset.props.description = fetchWebsite.description;
+ }
+ if (!bookmarkAsset) throw Error("Could not create an asset");
+ asset = bookmarkAsset;
+ } catch (e) {
+ console.log(e);
+ return;
+ }
+ }
+
+ editor.batch(() => {
+ if (shouldAlsoCreateAsset) {
+ editor.createAssets([asset]);
+ }
+
+ editor.updateShapes([
+ {
+ id: shape.id,
+ type: shape.type,
+ props: {
+ assetId: asset.id,
+ },
+ },
+ ]);
+ });
}
function isURL(str: string) {
- try {
- new URL(str);
- return true;
- } catch {
- return false;
- }
+ try {
+ new URL(str);
+ return true;
+ } catch {
+ return false;
+ }
}
function formatTextToRatio(text: string) {
- const totalWidth = text.length;
- const maxLineWidth = Math.floor(totalWidth / 10);
-
- const words = text.split(" ");
- let lines = [];
- let currentLine = "";
-
- words.forEach((word) => {
- if ((currentLine + word).length <= maxLineWidth) {
- currentLine += (currentLine ? " " : "") + word;
- } else {
- lines.push(currentLine);
- currentLine = word;
- }
- });
- if (currentLine) {
- lines.push(currentLine);
- }
- return { height: (lines.length + 1) * 18, width: maxLineWidth * 10 };
+ const totalWidth = text.length;
+ const maxLineWidth = Math.floor(totalWidth / 10);
+
+ const words = text.split(" ");
+ let lines = [];
+ let currentLine = "";
+
+ words.forEach((word) => {
+ if ((currentLine + word).length <= maxLineWidth) {
+ currentLine += (currentLine ? " " : "") + word;
+ } else {
+ lines.push(currentLine);
+ currentLine = word;
+ }
+ });
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+ return { height: (lines.length + 1) * 18, width: maxLineWidth * 10 };
}
export function handleExternalDroppedContent({
- text,
- editor,
+ text,
+ editor,
}: {
- text: string;
- editor: Editor;
+ text: string;
+ editor: Editor;
}) {
- const position = editor.inputs.shiftKey
- ? editor.inputs.currentPagePoint
- : editor.getViewportPageBounds().center;
-
- if (isURL(text)) {
- createEmbedsFromUrl({ editor, url: text });
- } else {
- // editor.createShape({
- // type: "text",
- // x: position.x - 75,
- // y: position.y - 75,
- // props: {
- // text: text,
- // size: "s",
- // textAlign: "start",
- // },
- // });
- const { height, width } = formatTextToRatio(text);
- editor.createShape({
- type: "Textcard",
- x: position.x - width / 2,
- y: position.y - height / 2,
- props: {
- content: text,
- extrainfo: "https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
- w: width,
- h: height,
- },
- });
- }
+ const position = editor.inputs.shiftKey
+ ? editor.inputs.currentPagePoint
+ : editor.getViewportPageBounds().center;
+
+ if (isURL(text)) {
+ createEmbedsFromUrl({ editor, url: text });
+ } else {
+ // editor.createShape({
+ // type: "text",
+ // x: position.x - 75,
+ // y: position.y - 75,
+ // props: {
+ // text: text,
+ // size: "s",
+ // textAlign: "start",
+ // },
+ // });
+ const { height, width } = formatTextToRatio(text);
+ editor.createShape({
+ type: "Textcard",
+ x: position.x - width / 2,
+ y: position.y - height / 2,
+ props: {
+ content: text,
+ extrainfo: "https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
+ w: width,
+ h: height,
+ },
+ });
+ }
}
function centerSelectionAroundPoint(editor: Editor, position: VecLike) {
- // Re-position shapes so that the center of the group is at the provided point
- const viewportPageBounds = editor.getViewportPageBounds();
- let selectionPageBounds = editor.getSelectionPageBounds();
-
- if (selectionPageBounds) {
- const offset = selectionPageBounds!.center.sub(position);
-
- editor.updateShapes(
- editor.getSelectedShapes().map((shape) => {
- const localRotation = editor
- .getShapeParentTransform(shape)
- .decompose().rotation;
- const localDelta = Vec.Rot(offset, -localRotation);
- return {
- id: shape.id,
- type: shape.type,
- x: shape.x! - localDelta.x,
- y: shape.y! - localDelta.y,
- };
- }),
- );
- }
-
- // Zoom out to fit the shapes, if necessary
- selectionPageBounds = editor.getSelectionPageBounds();
- if (
- selectionPageBounds &&
- !viewportPageBounds.contains(selectionPageBounds)
- ) {
- editor.zoomToSelection();
- }
+ // Re-position shapes so that the center of the group is at the provided point
+ const viewportPageBounds = editor.getViewportPageBounds();
+ let selectionPageBounds = editor.getSelectionPageBounds();
+
+ if (selectionPageBounds) {
+ const offset = selectionPageBounds!.center.sub(position);
+
+ editor.updateShapes(
+ editor.getSelectedShapes().map((shape) => {
+ const localRotation = editor
+ .getShapeParentTransform(shape)
+ .decompose().rotation;
+ const localDelta = Vec.Rot(offset, -localRotation);
+ return {
+ id: shape.id,
+ type: shape.type,
+ x: shape.x! - localDelta.x,
+ y: shape.y! - localDelta.y,
+ };
+ }),
+ );
+ }
+
+ // Zoom out to fit the shapes, if necessary
+ selectionPageBounds = editor.getSelectionPageBounds();
+ if (
+ selectionPageBounds &&
+ !viewportPageBounds.contains(selectionPageBounds)
+ ) {
+ editor.zoomToSelection();
+ }
}
export function createEmptyBookmarkShape(
- editor: Editor,
- url: string,
- position: VecLike,
+ editor: Editor,
+ url: string,
+ position: VecLike,
): TLBookmarkShape {
- const partial: TLShapePartial = {
- id: createShapeId(),
- type: "bookmark",
- x: position.x - 150,
- y: position.y - 160,
- opacity: 1,
- props: {
- assetId: null,
- url,
- },
- };
-
- editor.batch(() => {
- editor.createShapes([partial]).select(partial.id);
- centerSelectionAroundPoint(editor, position);
- });
-
- return editor.getShape(partial.id) as TLBookmarkShape;
+ const partial: TLShapePartial = {
+ id: createShapeId(),
+ type: "bookmark",
+ x: position.x - 150,
+ y: position.y - 160,
+ opacity: 1,
+ props: {
+ assetId: null,
+ url,
+ },
+ };
+
+ editor.batch(() => {
+ editor.createShapes([partial]).select(partial.id);
+ centerSelectionAroundPoint(editor, position);
+ });
+
+ return editor.getShape(partial.id) as TLBookmarkShape;
}
diff --git a/apps/web/lib/get-metadata.ts b/apps/web/lib/get-metadata.ts
index 81dab2ba..c81397ff 100644
--- a/apps/web/lib/get-metadata.ts
+++ b/apps/web/lib/get-metadata.ts
@@ -3,38 +3,38 @@ import * as cheerio from "cheerio";
// TODO: THIS SHOULD PROBABLY ALSO FETCH THE OG-IMAGE
export async function getMetaData(url: string) {
- const response = await fetch(url);
- const html = await response.text();
-
- const $ = cheerio.load(html);
-
- // Extract the base URL
- const baseUrl = url
-
- // Extract title
- const title = $("title").text().trim();
-
- const description = $("meta[name=description]").attr("content") ?? "";
-
- const _favicon =
- $("link[rel=icon]").attr("href") ?? "https://supermemory.dhr.wtf/web.svg";
-
- let favicon =
- _favicon.trim().length > 0
- ? _favicon.trim()
- : "https://supermemory.dhr.wtf/web.svg";
- if (favicon.startsWith("/")) {
- favicon = baseUrl + favicon;
- } else if (favicon.startsWith("./")) {
- favicon = baseUrl + favicon.slice(1);
- }
-
- // Prepare the metadata object
- const metadata = {
- title,
- description,
- image: favicon,
- baseUrl,
- };
- return metadata;
+ const response = await fetch(url);
+ const html = await response.text();
+
+ const $ = cheerio.load(html);
+
+ // Extract the base URL
+ const baseUrl = url;
+
+ // Extract title
+ const title = $("title").text().trim();
+
+ const description = $("meta[name=description]").attr("content") ?? "";
+
+ const _favicon =
+ $("link[rel=icon]").attr("href") ?? "https://supermemory.dhr.wtf/web.svg";
+
+ let favicon =
+ _favicon.trim().length > 0
+ ? _favicon.trim()
+ : "https://supermemory.dhr.wtf/web.svg";
+ if (favicon.startsWith("/")) {
+ favicon = baseUrl + favicon;
+ } else if (favicon.startsWith("./")) {
+ favicon = baseUrl + favicon.slice(1);
+ }
+
+ // Prepare the metadata object
+ const metadata = {
+ title,
+ description,
+ image: favicon,
+ baseUrl,
+ };
+ return metadata;
}
diff --git a/apps/web/lib/get-theme-button.tsx b/apps/web/lib/get-theme-button.tsx
index 020cc976..e504e996 100644
--- a/apps/web/lib/get-theme-button.tsx
+++ b/apps/web/lib/get-theme-button.tsx
@@ -4,8 +4,8 @@ import dynamic from "next/dynamic";
// Don't SSR the toggle since the value on the server will be different than the client
export const getThemeToggler = () =>
- dynamic(() => import("@repo/ui/shadcn/theme-toggle"), {
- ssr: false,
- // Make sure to code a placeholder so the UI doesn't jump when the component loads
- loading: () => <div className="w-6 h-6" />,
- });
+ dynamic(() => import("@repo/ui/shadcn/theme-toggle"), {
+ ssr: false,
+ // Make sure to code a placeholder so the UI doesn't jump when the component loads
+ loading: () => <div className="w-6 h-6" />,
+ });
diff --git a/apps/web/lib/handle-errors.ts b/apps/web/lib/handle-errors.ts
index 42cae589..2e207178 100644
--- a/apps/web/lib/handle-errors.ts
+++ b/apps/web/lib/handle-errors.ts
@@ -3,23 +3,23 @@ import { toast } from "sonner";
import { z } from "zod";
export function getErrorMessage(err: unknown) {
- const unknownError = "Something went wrong, please try again later.";
+ const unknownError = "Something went wrong, please try again later.";
- if (err instanceof z.ZodError) {
- const errors = err.issues.map((issue) => {
- return issue.message;
- });
- return errors.join("\n");
- } else if (err instanceof Error) {
- return err.message;
- } else if (isRedirectError(err)) {
- throw err;
- } else {
- return unknownError;
- }
+ if (err instanceof z.ZodError) {
+ const errors = err.issues.map((issue) => {
+ return issue.message;
+ });
+ return errors.join("\n");
+ } else if (err instanceof Error) {
+ return err.message;
+ } else if (isRedirectError(err)) {
+ throw err;
+ } else {
+ return unknownError;
+ }
}
export function showErrorToast(err: unknown) {
- const errorMessage = getErrorMessage(err);
- return toast.error(errorMessage);
+ const errorMessage = getErrorMessage(err);
+ return toast.error(errorMessage);
}
diff --git a/apps/web/lib/loadSnap.ts b/apps/web/lib/loadSnap.ts
index 083603eb..bcf81eca 100644
--- a/apps/web/lib/loadSnap.ts
+++ b/apps/web/lib/loadSnap.ts
@@ -4,11 +4,11 @@ import { twitterCardUtil } from "../components/canvas/twitterCard";
import { textCardUtil } from "../components/canvas/textCard";
export async function loadRemoteSnapshot(id: string) {
- const snapshot = await getCanvasData(id);
+ const snapshot = await getCanvasData(id);
- const newStore = createTLStore({
- shapeUtils: [...defaultShapeUtils, twitterCardUtil, textCardUtil],
- });
- loadSnapshot(newStore, snapshot.snapshot);
- return newStore;
+ const newStore = createTLStore({
+ shapeUtils: [...defaultShapeUtils, twitterCardUtil, textCardUtil],
+ });
+ loadSnapshot(newStore, snapshot.snapshot);
+ return newStore;
}
diff --git a/apps/web/lib/searchParams.ts b/apps/web/lib/searchParams.ts
index f3188a6f..2e8b1633 100644
--- a/apps/web/lib/searchParams.ts
+++ b/apps/web/lib/searchParams.ts
@@ -1,35 +1,35 @@
import {
- createSearchParamsCache,
- parseAsInteger,
- parseAsString,
- parseAsBoolean,
- parseAsArrayOf,
- parseAsJson,
+ createSearchParamsCache,
+ parseAsInteger,
+ parseAsString,
+ parseAsBoolean,
+ parseAsArrayOf,
+ parseAsJson,
} from "nuqs/server";
import { z } from "zod";
export const homeSearchParamsCache = createSearchParamsCache({
- firstTime: parseAsBoolean.withDefault(false),
+ firstTime: parseAsBoolean.withDefault(false),
});
export const chatSearchParamsCache = createSearchParamsCache({
- firstTime: parseAsBoolean.withDefault(false),
- q: parseAsString.withDefault(""),
- spaces: parseAsJson((c) => {
- const valid = z
- .array(
- z.object({
- id: z.number(),
- name: z.string(),
- }),
- )
- .safeParse(c);
+ firstTime: parseAsBoolean.withDefault(false),
+ q: parseAsString.withDefault(""),
+ spaces: parseAsJson((c) => {
+ const valid = z
+ .array(
+ z.object({
+ id: z.number(),
+ name: z.string(),
+ }),
+ )
+ .safeParse(c);
- if (!valid.success) {
- console.log("invalid spaces", valid.error);
- return null;
- }
+ if (!valid.success) {
+ console.log("invalid spaces", valid.error);
+ return null;
+ }
- return valid.data;
- }),
+ return valid.data;
+ }),
});
diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts
index 58ac535c..9e1e335f 100644
--- a/apps/web/middleware.ts
+++ b/apps/web/middleware.ts
@@ -1,24 +1,24 @@
import { NextRequest, NextResponse } from "next/server";
const corsHeaders = {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export function middleware(request: NextRequest) {
- if (request.method === "OPTIONS") {
- return new NextResponse(null, { headers: corsHeaders });
- }
+ if (request.method === "OPTIONS") {
+ return new NextResponse(null, { headers: corsHeaders });
+ }
- const response = NextResponse.next();
- Object.entries(corsHeaders).forEach(([key, value]) => {
- response.headers.set(key, value);
- });
+ const response = NextResponse.next();
+ Object.entries(corsHeaders).forEach(([key, value]) => {
+ response.headers.set(key, value);
+ });
- return response;
+ return response;
}
export const config = {
- matcher: "/api/:path*",
+ matcher: "/api/:path*",
};
diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json
index 28df2f2f..0639eb77 100644
--- a/apps/web/migrations/meta/0000_snapshot.json
+++ b/apps/web/migrations/meta/0000_snapshot.json
@@ -1,806 +1,806 @@
{
- "version": "6",
- "dialect": "sqlite",
- "id": "ab91d972-05ff-4916-84b7-1cfaab4c3879",
- "prevId": "00000000-0000-0000-0000-000000000000",
- "tables": {
- "account": {
- "name": "account",
- "columns": {
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "provider": {
- "name": "provider",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "providerAccountId": {
- "name": "providerAccountId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "refresh_token": {
- "name": "refresh_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "access_token": {
- "name": "access_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "expires_at": {
- "name": "expires_at",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "token_type": {
- "name": "token_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "scope": {
- "name": "scope",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "id_token": {
- "name": "id_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "session_state": {
- "name": "session_state",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "account_userId_user_id_fk": {
- "name": "account_userId_user_id_fk",
- "tableFrom": "account",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "account_provider_providerAccountId_pk": {
- "columns": ["provider", "providerAccountId"],
- "name": "account_provider_providerAccountId_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "authenticator": {
- "name": "authenticator",
- "columns": {
- "credentialID": {
- "name": "credentialID",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "providerAccountId": {
- "name": "providerAccountId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "credentialPublicKey": {
- "name": "credentialPublicKey",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "counter": {
- "name": "counter",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "credentialDeviceType": {
- "name": "credentialDeviceType",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "credentialBackedUp": {
- "name": "credentialBackedUp",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "transports": {
- "name": "transports",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "authenticator_credentialID_unique": {
- "name": "authenticator_credentialID_unique",
- "columns": ["credentialID"],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "authenticator_userId_user_id_fk": {
- "name": "authenticator_userId_user_id_fk",
- "tableFrom": "authenticator",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "authenticator_userId_credentialID_pk": {
- "columns": ["credentialID", "userId"],
- "name": "authenticator_userId_credentialID_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "canvas": {
- "name": "canvas",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'Untitled'"
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'Untitled'"
- },
- "url": {
- "name": "url",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "''"
- },
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "canvas_user_userId": {
- "name": "canvas_user_userId",
- "columns": ["userId"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "canvas_userId_user_id_fk": {
- "name": "canvas_userId_user_id_fk",
- "tableFrom": "canvas",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "chatHistory": {
- "name": "chatHistory",
- "columns": {
- "id": {
- "name": "id",
- "type": "integer",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": true
- },
- "threadId": {
- "name": "threadId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "question": {
- "name": "question",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "answerParts": {
- "name": "answerParts",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "answerSources": {
- "name": "answerSources",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "answerJustification": {
- "name": "answerJustification",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "chatHistory_thread_idx": {
- "name": "chatHistory_thread_idx",
- "columns": ["threadId"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "chatHistory_threadId_chatThread_id_fk": {
- "name": "chatHistory_threadId_chatThread_id_fk",
- "tableFrom": "chatHistory",
- "tableTo": "chatThread",
- "columnsFrom": ["threadId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "chatThread": {
- "name": "chatThread",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "firstMessage": {
- "name": "firstMessage",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "chatThread_user_idx": {
- "name": "chatThread_user_idx",
- "columns": ["userId"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "chatThread_userId_user_id_fk": {
- "name": "chatThread_userId_user_id_fk",
- "tableFrom": "chatThread",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "contentToSpace": {
- "name": "contentToSpace",
- "columns": {
- "contentId": {
- "name": "contentId",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "spaceId": {
- "name": "spaceId",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "contentToSpace_contentId_storedContent_id_fk": {
- "name": "contentToSpace_contentId_storedContent_id_fk",
- "tableFrom": "contentToSpace",
- "tableTo": "storedContent",
- "columnsFrom": ["contentId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "contentToSpace_spaceId_space_id_fk": {
- "name": "contentToSpace_spaceId_space_id_fk",
- "tableFrom": "contentToSpace",
- "tableTo": "space",
- "columnsFrom": ["spaceId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "contentToSpace_contentId_spaceId_pk": {
- "columns": ["contentId", "spaceId"],
- "name": "contentToSpace_contentId_spaceId_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "session": {
- "name": "session",
- "columns": {
- "sessionToken": {
- "name": "sessionToken",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "expires": {
- "name": "expires",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "session_userId_user_id_fk": {
- "name": "session_userId_user_id_fk",
- "tableFrom": "session",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "space": {
- "name": "space",
- "columns": {
- "id": {
- "name": "id",
- "type": "integer",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'none'"
- },
- "user": {
- "name": "user",
- "type": "text(255)",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "createdAt": {
- "name": "createdAt",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "numItems": {
- "name": "numItems",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- }
- },
- "indexes": {
- "space_name_unique": {
- "name": "space_name_unique",
- "columns": ["name"],
- "isUnique": true
- },
- "spaces_name_idx": {
- "name": "spaces_name_idx",
- "columns": ["name"],
- "isUnique": false
- },
- "spaces_user_idx": {
- "name": "spaces_user_idx",
- "columns": ["user"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "space_user_user_id_fk": {
- "name": "space_user_user_id_fk",
- "tableFrom": "space",
- "tableTo": "user",
- "columnsFrom": ["user"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "spacesAccess": {
- "name": "spacesAccess",
- "columns": {
- "spaceId": {
- "name": "spaceId",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "userEmail": {
- "name": "userEmail",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "spacesAccess_spaceId_space_id_fk": {
- "name": "spacesAccess_spaceId_space_id_fk",
- "tableFrom": "spacesAccess",
- "tableTo": "space",
- "columnsFrom": ["spaceId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "spacesAccess_spaceId_userEmail_pk": {
- "columns": ["spaceId", "userEmail"],
- "name": "spacesAccess_spaceId_userEmail_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "storedContent": {
- "name": "storedContent",
- "columns": {
- "id": {
- "name": "id",
- "type": "integer",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": true
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "title": {
- "name": "title",
- "type": "text(255)",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "description": {
- "name": "description",
- "type": "text(255)",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "url": {
- "name": "url",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "savedAt": {
- "name": "savedAt",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "baseUrl": {
- "name": "baseUrl",
- "type": "text(255)",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "ogImage": {
- "name": "ogImage",
- "type": "text(255)",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false,
- "default": "'page'"
- },
- "image": {
- "name": "image",
- "type": "text(255)",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "user": {
- "name": "user",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "noteId": {
- "name": "noteId",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "storedContent_baseUrl_unique": {
- "name": "storedContent_baseUrl_unique",
- "columns": ["baseUrl"],
- "isUnique": true
- },
- "storedContent_url_idx": {
- "name": "storedContent_url_idx",
- "columns": ["url"],
- "isUnique": false
- },
- "storedContent_savedAt_idx": {
- "name": "storedContent_savedAt_idx",
- "columns": ["savedAt"],
- "isUnique": false
- },
- "storedContent_title_idx": {
- "name": "storedContent_title_idx",
- "columns": ["title"],
- "isUnique": false
- },
- "storedContent_user_idx": {
- "name": "storedContent_user_idx",
- "columns": ["user"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "storedContent_user_user_id_fk": {
- "name": "storedContent_user_user_id_fk",
- "tableFrom": "storedContent",
- "tableTo": "user",
- "columnsFrom": ["user"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "user": {
- "name": "user",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "emailVerified": {
- "name": "emailVerified",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "image": {
- "name": "image",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "telegramId": {
- "name": "telegramId",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "users_email_idx": {
- "name": "users_email_idx",
- "columns": ["email"],
- "isUnique": false
- },
- "users_telegram_idx": {
- "name": "users_telegram_idx",
- "columns": ["telegramId"],
- "isUnique": false
- },
- "users_id_idx": {
- "name": "users_id_idx",
- "columns": ["id"],
- "isUnique": false
- }
- },
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "verificationToken": {
- "name": "verificationToken",
- "columns": {
- "identifier": {
- "name": "identifier",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "token": {
- "name": "token",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "expires": {
- "name": "expires",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {
- "verificationToken_identifier_token_pk": {
- "columns": ["identifier", "token"],
- "name": "verificationToken_identifier_token_pk"
- }
- },
- "uniqueConstraints": {}
- }
- },
- "enums": {},
- "_meta": {
- "schemas": {},
- "tables": {},
- "columns": {}
- }
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "ab91d972-05ff-4916-84b7-1cfaab4c3879",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": ["provider", "providerAccountId"],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "authenticator": {
+ "name": "authenticator",
+ "columns": {
+ "credentialID": {
+ "name": "credentialID",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credentialPublicKey": {
+ "name": "credentialPublicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credentialDeviceType": {
+ "name": "credentialDeviceType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credentialBackedUp": {
+ "name": "credentialBackedUp",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "authenticator_credentialID_unique": {
+ "name": "authenticator_credentialID_unique",
+ "columns": ["credentialID"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "authenticator_userId_user_id_fk": {
+ "name": "authenticator_userId_user_id_fk",
+ "tableFrom": "authenticator",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "authenticator_userId_credentialID_pk": {
+ "columns": ["credentialID", "userId"],
+ "name": "authenticator_userId_credentialID_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "canvas": {
+ "name": "canvas",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'Untitled'"
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'Untitled'"
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "canvas_user_userId": {
+ "name": "canvas_user_userId",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "canvas_userId_user_id_fk": {
+ "name": "canvas_userId_user_id_fk",
+ "tableFrom": "canvas",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "chatHistory": {
+ "name": "chatHistory",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "threadId": {
+ "name": "threadId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "question": {
+ "name": "question",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "answerParts": {
+ "name": "answerParts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "answerSources": {
+ "name": "answerSources",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "answerJustification": {
+ "name": "answerJustification",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "chatHistory_thread_idx": {
+ "name": "chatHistory_thread_idx",
+ "columns": ["threadId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "chatHistory_threadId_chatThread_id_fk": {
+ "name": "chatHistory_threadId_chatThread_id_fk",
+ "tableFrom": "chatHistory",
+ "tableTo": "chatThread",
+ "columnsFrom": ["threadId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "chatThread": {
+ "name": "chatThread",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "firstMessage": {
+ "name": "firstMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "chatThread_user_idx": {
+ "name": "chatThread_user_idx",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "chatThread_userId_user_id_fk": {
+ "name": "chatThread_userId_user_id_fk",
+ "tableFrom": "chatThread",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "contentToSpace": {
+ "name": "contentToSpace",
+ "columns": {
+ "contentId": {
+ "name": "contentId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "spaceId": {
+ "name": "spaceId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "contentToSpace_contentId_storedContent_id_fk": {
+ "name": "contentToSpace_contentId_storedContent_id_fk",
+ "tableFrom": "contentToSpace",
+ "tableTo": "storedContent",
+ "columnsFrom": ["contentId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "contentToSpace_spaceId_space_id_fk": {
+ "name": "contentToSpace_spaceId_space_id_fk",
+ "tableFrom": "contentToSpace",
+ "tableTo": "space",
+ "columnsFrom": ["spaceId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "contentToSpace_contentId_spaceId_pk": {
+ "columns": ["contentId", "spaceId"],
+ "name": "contentToSpace_contentId_spaceId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "space": {
+ "name": "space",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'none'"
+ },
+ "user": {
+ "name": "user",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "numItems": {
+ "name": "numItems",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "space_name_unique": {
+ "name": "space_name_unique",
+ "columns": ["name"],
+ "isUnique": true
+ },
+ "spaces_name_idx": {
+ "name": "spaces_name_idx",
+ "columns": ["name"],
+ "isUnique": false
+ },
+ "spaces_user_idx": {
+ "name": "spaces_user_idx",
+ "columns": ["user"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "space_user_user_id_fk": {
+ "name": "space_user_user_id_fk",
+ "tableFrom": "space",
+ "tableTo": "user",
+ "columnsFrom": ["user"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "spacesAccess": {
+ "name": "spacesAccess",
+ "columns": {
+ "spaceId": {
+ "name": "spaceId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userEmail": {
+ "name": "userEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "spacesAccess_spaceId_space_id_fk": {
+ "name": "spacesAccess_spaceId_space_id_fk",
+ "tableFrom": "spacesAccess",
+ "tableTo": "space",
+ "columnsFrom": ["spaceId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "spacesAccess_spaceId_userEmail_pk": {
+ "columns": ["spaceId", "userEmail"],
+ "name": "spacesAccess_spaceId_userEmail_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "storedContent": {
+ "name": "storedContent",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "savedAt": {
+ "name": "savedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "baseUrl": {
+ "name": "baseUrl",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ogImage": {
+ "name": "ogImage",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'page'"
+ },
+ "image": {
+ "name": "image",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user": {
+ "name": "user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "noteId": {
+ "name": "noteId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "storedContent_baseUrl_unique": {
+ "name": "storedContent_baseUrl_unique",
+ "columns": ["baseUrl"],
+ "isUnique": true
+ },
+ "storedContent_url_idx": {
+ "name": "storedContent_url_idx",
+ "columns": ["url"],
+ "isUnique": false
+ },
+ "storedContent_savedAt_idx": {
+ "name": "storedContent_savedAt_idx",
+ "columns": ["savedAt"],
+ "isUnique": false
+ },
+ "storedContent_title_idx": {
+ "name": "storedContent_title_idx",
+ "columns": ["title"],
+ "isUnique": false
+ },
+ "storedContent_user_idx": {
+ "name": "storedContent_user_idx",
+ "columns": ["user"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "storedContent_user_user_id_fk": {
+ "name": "storedContent_user_user_id_fk",
+ "tableFrom": "storedContent",
+ "tableTo": "user",
+ "columnsFrom": ["user"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "users_email_idx": {
+ "name": "users_email_idx",
+ "columns": ["email"],
+ "isUnique": false
+ },
+ "users_telegram_idx": {
+ "name": "users_telegram_idx",
+ "columns": ["telegramId"],
+ "isUnique": false
+ },
+ "users_id_idx": {
+ "name": "users_id_idx",
+ "columns": ["id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": ["identifier", "token"],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
}
diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json
index f92f7aad..c7ab51e1 100644
--- a/apps/web/migrations/meta/_journal.json
+++ b/apps/web/migrations/meta/_journal.json
@@ -1,13 +1,13 @@
{
- "version": "6",
- "dialect": "sqlite",
- "entries": [
- {
- "idx": 0,
- "version": "6",
- "when": 1720360287793,
- "tag": "0000_exotic_sway",
- "breakpoints": true
- }
- ]
+ "version": "6",
+ "dialect": "sqlite",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "6",
+ "when": 1720360287793,
+ "tag": "0000_exotic_sway",
+ "breakpoints": true
+ }
+ ]
}
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 12929a8c..c0001fa5 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -4,60 +4,63 @@ import { withSentryConfig } from "@sentry/nextjs";
/** @type {import('next').NextConfig} */
const baseNextConfig = {
- transpilePackages: ["@repo/ui"],
- reactStrictMode: false,
- env: {
- TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
- },
+ transpilePackages: ["@repo/ui"],
+ reactStrictMode: false,
+ env: {
+ TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
+ },
};
let selectedCofig = baseNextConfig;
if (process.env.NODE_ENV === "development") {
- selectedCofig = MillionLint.next({
- rsc: true,
- })(baseNextConfig);
+ selectedCofig = MillionLint.next({
+ rsc: true,
+ })(baseNextConfig);
}
-export default withSentryConfig(selectedCofig, {
- // For all available options, see:
- // https://github.com/getsentry/sentry-webpack-plugin#options
+export default selectedCofig;
- org: "none-h00",
- project: "javascript-nextjs",
- // Only print logs for uploading source maps in CI
- silent: !process.env.CI,
+//! Disabled sentry for now because of unreasonably large bundle size
+// export default withSentryConfig(selectedCofig, {
+// // For all available options, see:
+// // https://github.com/getsentry/sentry-webpack-plugin#options
- // For all available options, see:
- // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
+// org: "none-h00",
+// project: "javascript-nextjs",
+// // Only print logs for uploading source maps in CI
+// silent: !process.env.CI,
- // Upload a larger set of source maps for prettier stack traces (increases build time)
- widenClientFileUpload: true,
+// // For all available options, see:
+// // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
- // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
- // This can increase your server load as well as your hosting bill.
- // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
- // side errors will fail.
- tunnelRoute: "/monitoring",
+// // Upload a larger set of source maps for prettier stack traces (increases build time)
+// widenClientFileUpload: true,
- // Hides source maps from generated client bundles
- hideSourceMaps: true,
+// // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
+// // This can increase your server load as well as your hosting bill.
+// // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
+// // side errors will fail.
+// tunnelRoute: "/monitoring",
- // Automatically tree-shake Sentry logger statements to reduce bundle size
- disableLogger: true,
+// // Hides source maps from generated client bundles
+// hideSourceMaps: true,
- // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
- // See the following for more information:
- // https://docs.sentry.io/product/crons/
- // https://vercel.com/docs/cron-jobs
- automaticVercelMonitors: true,
-});
+// // Automatically tree-shake Sentry logger statements to reduce bundle size
+// disableLogger: true,
+
+// // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
+// // See the following for more information:
+// // https://docs.sentry.io/product/crons/
+// // https://vercel.com/docs/cron-jobs
+// automaticVercelMonitors: true,
+// });
// we only need to use the utility during development so we can check NODE_ENV
// (note: this check is recommended but completely optional)
if (process.env.NODE_ENV === "development") {
- // `await`ing the call is not necessary but it helps making sure that the setup has succeeded.
- // If you cannot use top level awaits you could use the following to avoid an unhandled rejection:
- // `setupDevPlatform().catch(e => console.error(e));`
- await setupDevPlatform();
+ // `await`ing the call is not necessary but it helps making sure that the setup has succeeded.
+ // If you cannot use top level awaits you could use the following to avoid an unhandled rejection:
+ // `setupDevPlatform().catch(e => console.error(e));`
+ await setupDevPlatform();
}
diff --git a/apps/web/package.json b/apps/web/package.json
index 3a5771d8..7d97fe7c 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,54 +1,53 @@
{
- "name": "@repo/web",
- "version": "1.0.0",
- "private": true,
- "packageManager": "[email protected]",
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "eslint . --max-warnings 0",
- "cf-typegen": "wrangler types --env-interface CloudflareEnv env.d.ts",
- "pages:build": "npx @cloudflare/next-on-pages",
- "preview": "npm run pages:build && wrangler pages dev",
- "deploy": "npm run pages:build && wrangler pages deploy --branch main",
- "schema-update": "bunx drizzle-kit generate sqlite",
- "update-local-db": "bunx wrangler d1 execute dev-d1-anycontext --local",
- "update-prod-db": "bunx wrangler d1 execute prod-d1-supermemory --remote"
- },
- "dependencies": {
- "@million/lint": "^1.0.0-rc.11",
- "@radix-ui/react-dialog": "^1.0.5",
- "@radix-ui/react-popover": "^1.0.7",
- "@radix-ui/react-slot": "^1.1.0",
- "@sentry/nextjs": "^8",
- "cmdk": "^1.0.0",
- "lowlight": "^3.1.0",
- "million": "^3.1.6",
- "next": "^14.1.1",
- "novel": "^0.4.2",
- "nuqs": "^1.17.4",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-resizable-panels": "^2.0.19",
- "use-debounce": "^10.0.1"
- },
- "devDependencies": {
- "@next/eslint-plugin-next": "^14.1.1",
- "@repo/eslint-config": "*",
- "@repo/typescript-config": "*",
- "@repo/tailwind-config": "*",
- "@repo/shared-types": "*",
- "@types/eslint": "^8.56.5",
- "@types/node": "^20.11.24",
- "@types/react": "^18.2.61",
- "@types/react-dom": "^18.2.19",
- "eslint": "^8.57.0",
- "typescript": "^5.3.3"
- },
- "trustedDependencies": [
- "esbuild",
- "workerd",
- "xycolors"
- ]
+ "name": "@repo/web",
+ "version": "1.0.0",
+ "private": true,
+ "packageManager": "[email protected]",
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint . --max-warnings 0",
+ "cf-typegen": "wrangler types --env-interface CloudflareEnv env.d.ts",
+ "pages:build": "npx @cloudflare/next-on-pages",
+ "preview": "npm run pages:build && wrangler pages dev",
+ "deploy": "npm run pages:build && wrangler pages deploy --branch main",
+ "schema-update": "bunx drizzle-kit generate sqlite",
+ "update-local-db": "bunx wrangler d1 execute dev-d1-anycontext --local",
+ "update-prod-db": "bunx wrangler d1 execute prod-d1-supermemory --remote"
+ },
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.0.5",
+ "@radix-ui/react-popover": "^1.0.7",
+ "@radix-ui/react-slot": "^1.1.0",
+ "@sentry/nextjs": "^8",
+ "cmdk": "^1.0.0",
+ "lowlight": "^3.1.0",
+ "million": "^3.1.6",
+ "next": "^14.1.1",
+ "novel": "^0.4.2",
+ "nuqs": "^1.17.4",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-resizable-panels": "^2.0.19",
+ "use-debounce": "^10.0.1"
+ },
+ "devDependencies": {
+ "@next/eslint-plugin-next": "^14.1.1",
+ "@repo/eslint-config": "*",
+ "@repo/typescript-config": "*",
+ "@repo/tailwind-config": "*",
+ "@repo/shared-types": "*",
+ "@types/eslint": "^8.56.5",
+ "@types/node": "^20.11.24",
+ "@types/react": "^18.2.61",
+ "@types/react-dom": "^18.2.19",
+ "eslint": "^8.57.0",
+ "typescript": "^5.3.3"
+ },
+ "trustedDependencies": [
+ "esbuild",
+ "workerd",
+ "xycolors"
+ ]
}
diff --git a/apps/web/public/site.webmanifest b/apps/web/public/site.webmanifest
index c903e516..4b972c48 100644
--- a/apps/web/public/site.webmanifest
+++ b/apps/web/public/site.webmanifest
@@ -1,19 +1,19 @@
{
- "name": "Supermemory - your second brain.",
- "short_name": "Supermemory",
- "icons": [
- {
- "src": "/icons/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "/icons/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- }
- ],
- "theme_color": "#ffffff",
- "background_color": "#ffffff",
- "display": "standalone"
+ "name": "Supermemory - your second brain.",
+ "short_name": "Supermemory",
+ "icons": [
+ {
+ "src": "/icons/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
}
diff --git a/apps/web/sentry.client.config.ts b/apps/web/sentry.client.config.ts
index 26a9f54c..8ae9937e 100644
--- a/apps/web/sentry.client.config.ts
+++ b/apps/web/sentry.client.config.ts
@@ -5,26 +5,20 @@
import * as Sentry from "@sentry/nextjs";
Sentry.init({
- dsn: "https://4056089e6a604362a3bb5cbcbcb557b3@o4503929862946816.ingest.us.sentry.io/4503929866354688",
+ dsn: "https://4056089e6a604362a3bb5cbcbcb557b3@o4503929862946816.ingest.us.sentry.io/4503929866354688",
- // Adjust this value in production, or use tracesSampler for greater control
- tracesSampleRate: 1,
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1,
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
- replaysOnErrorSampleRate: 1.0,
+ replaysOnErrorSampleRate: 1.0,
- // This sets the sample rate to be 10%. You may want this to be 100% while
- // in development and sample at a lower rate in production
- replaysSessionSampleRate: 0.1,
+ // This sets the sample rate to be 10%. You may want this to be 100% while
+ // in development and sample at a lower rate in production
+ replaysSessionSampleRate: 0.1,
- // You can remove this option if you're not planning to use the Sentry Session Replay feature:
- integrations: [
- Sentry.replayIntegration({
- // Additional Replay configuration goes in here, for example:
- maskAllText: true,
- blockAllMedia: true,
- }),
- ],
+ // You can remove this option if you're not planning to use the Sentry Session Replay feature:
+ integrations: [],
});
diff --git a/apps/web/sentry.edge.config.ts b/apps/web/sentry.edge.config.ts
index b6be0719..5cf7b5a9 100644
--- a/apps/web/sentry.edge.config.ts
+++ b/apps/web/sentry.edge.config.ts
@@ -6,11 +6,11 @@
import * as Sentry from "@sentry/nextjs";
Sentry.init({
- dsn: "https://4056089e6a604362a3bb5cbcbcb557b3@o4503929862946816.ingest.us.sentry.io/4503929866354688",
+ dsn: "https://4056089e6a604362a3bb5cbcbcb557b3@o4503929862946816.ingest.us.sentry.io/4503929866354688",
- // Adjust this value in production, or use tracesSampler for greater control
- tracesSampleRate: 1,
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1,
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
});
diff --git a/apps/web/sentry.server.config.ts b/apps/web/sentry.server.config.ts
index 60d0a6a6..a168ea0a 100644
--- a/apps/web/sentry.server.config.ts
+++ b/apps/web/sentry.server.config.ts
@@ -5,14 +5,14 @@
import * as Sentry from "@sentry/nextjs";
Sentry.init({
- dsn: "https://4056089e6a604362a3bb5cbcbcb557b3@o4503929862946816.ingest.us.sentry.io/4503929866354688",
+ dsn: "https://4056089e6a604362a3bb5cbcbcb557b3@o4503929862946816.ingest.us.sentry.io/4503929866354688",
- // Adjust this value in production, or use tracesSampler for greater control
- tracesSampleRate: 1,
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1,
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
- // Uncomment the line below to enable Spotlight (https://spotlightjs.com)
- // spotlight: process.env.NODE_ENV === 'development',
+ // Uncomment the line below to enable Spotlight (https://spotlightjs.com)
+ // spotlight: process.env.NODE_ENV === 'development',
});
diff --git a/apps/web/server/auth.ts b/apps/web/server/auth.ts
index d84934ea..78671551 100644
--- a/apps/web/server/auth.ts
+++ b/apps/web/server/auth.ts
@@ -5,31 +5,31 @@ import { db } from "./db";
import { accounts, sessions, users, verificationTokens } from "./db/schema";
export const {
- handlers: { GET, POST },
- signIn,
- signOut,
- auth,
+ handlers: { GET, POST },
+ signIn,
+ signOut,
+ auth,
} = NextAuth({
- secret: process.env.BACKEND_SECURITY_KEY,
- trustHost: true,
- // callbacks: {
- // session: ({ session, token, user }) => ({
- // ...session,
- // user: {
- // ...session.user,
- // },
- // }),
- // },
- adapter: DrizzleAdapter(db, {
- usersTable: users,
- accountsTable: accounts,
- sessionsTable: sessions,
- verificationTokensTable: verificationTokens,
- }),
- providers: [
- Google({
- clientId: process.env.GOOGLE_CLIENT_ID,
- clientSecret: process.env.GOOGLE_CLIENT_SECRET,
- }),
- ],
+ secret: process.env.BACKEND_SECURITY_KEY,
+ trustHost: true,
+ // callbacks: {
+ // session: ({ session, token, user }) => ({
+ // ...session,
+ // user: {
+ // ...session.user,
+ // },
+ // }),
+ // },
+ adapter: DrizzleAdapter(db, {
+ usersTable: users,
+ accountsTable: accounts,
+ sessionsTable: sessions,
+ verificationTokensTable: verificationTokens,
+ }),
+ providers: [
+ Google({
+ clientId: process.env.GOOGLE_CLIENT_ID,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
+ }),
+ ],
});
diff --git a/apps/web/server/db/schema.ts b/apps/web/server/db/schema.ts
index 979b5152..ae293a91 100644
--- a/apps/web/server/db/schema.ts
+++ b/apps/web/server/db/schema.ts
@@ -1,238 +1,238 @@
import { relations, sql } from "drizzle-orm";
import {
- index,
- int,
- primaryKey,
- sqliteTableCreator,
- text,
- integer,
+ index,
+ int,
+ primaryKey,
+ sqliteTableCreator,
+ text,
+ integer,
} from "drizzle-orm/sqlite-core";
import type { AdapterAccountType } from "next-auth/adapters";
export const createTable = sqliteTableCreator((name) => `${name}`);
export const users = createTable(
- "user",
- {
- id: text("id")
- .primaryKey()
- .$defaultFn(() => crypto.randomUUID()),
- name: text("name"),
- email: text("email").notNull(),
- emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
- image: text("image"),
- telegramId: text("telegramId"),
- },
- (user) => ({
- emailIdx: index("users_email_idx").on(user.email),
- telegramIdx: index("users_telegram_idx").on(user.telegramId),
- idIdx: index("users_id_idx").on(user.id),
- }),
+ "user",
+ {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => crypto.randomUUID()),
+ name: text("name"),
+ email: text("email").notNull(),
+ emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
+ image: text("image"),
+ telegramId: text("telegramId"),
+ },
+ (user) => ({
+ emailIdx: index("users_email_idx").on(user.email),
+ telegramIdx: index("users_telegram_idx").on(user.telegramId),
+ idIdx: index("users_id_idx").on(user.id),
+ }),
);
export type User = typeof users.$inferSelect;
export const accounts = createTable(
- "account",
- {
- userId: text("userId")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- type: text("type").$type<AdapterAccountType>().notNull(),
- provider: text("provider").notNull(),
- providerAccountId: text("providerAccountId").notNull(),
- refresh_token: text("refresh_token"),
- access_token: text("access_token"),
- expires_at: integer("expires_at"),
- token_type: text("token_type"),
- scope: text("scope"),
- id_token: text("id_token"),
- session_state: text("session_state"),
- },
- (account) => ({
- compoundKey: primaryKey({
- columns: [account.provider, account.providerAccountId],
- }),
- }),
+ "account",
+ {
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ type: text("type").$type<AdapterAccountType>().notNull(),
+ provider: text("provider").notNull(),
+ providerAccountId: text("providerAccountId").notNull(),
+ refresh_token: text("refresh_token"),
+ access_token: text("access_token"),
+ expires_at: integer("expires_at"),
+ token_type: text("token_type"),
+ scope: text("scope"),
+ id_token: text("id_token"),
+ session_state: text("session_state"),
+ },
+ (account) => ({
+ compoundKey: primaryKey({
+ columns: [account.provider, account.providerAccountId],
+ }),
+ }),
);
export const sessions = createTable("session", {
- sessionToken: text("sessionToken").primaryKey(),
- userId: text("userId")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
+ sessionToken: text("sessionToken").primaryKey(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
});
export const verificationTokens = createTable(
- "verificationToken",
- {
- identifier: text("identifier").notNull(),
- token: text("token").notNull(),
- expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
- },
- (verificationToken) => ({
- compositePk: primaryKey({
- columns: [verificationToken.identifier, verificationToken.token],
- }),
- }),
+ "verificationToken",
+ {
+ identifier: text("identifier").notNull(),
+ token: text("token").notNull(),
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
+ },
+ (verificationToken) => ({
+ compositePk: primaryKey({
+ columns: [verificationToken.identifier, verificationToken.token],
+ }),
+ }),
);
export const authenticators = createTable(
- "authenticator",
- {
- credentialID: text("credentialID").notNull().unique(),
- userId: text("userId")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- providerAccountId: text("providerAccountId").notNull(),
- credentialPublicKey: text("credentialPublicKey").notNull(),
- counter: integer("counter").notNull(),
- credentialDeviceType: text("credentialDeviceType").notNull(),
- credentialBackedUp: integer("credentialBackedUp", {
- mode: "boolean",
- }).notNull(),
- transports: text("transports"),
- },
- (authenticator) => ({
- compositePK: primaryKey({
- columns: [authenticator.userId, authenticator.credentialID],
- }),
- }),
+ "authenticator",
+ {
+ credentialID: text("credentialID").notNull().unique(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ providerAccountId: text("providerAccountId").notNull(),
+ credentialPublicKey: text("credentialPublicKey").notNull(),
+ counter: integer("counter").notNull(),
+ credentialDeviceType: text("credentialDeviceType").notNull(),
+ credentialBackedUp: integer("credentialBackedUp", {
+ mode: "boolean",
+ }).notNull(),
+ transports: text("transports"),
+ },
+ (authenticator) => ({
+ compositePK: primaryKey({
+ columns: [authenticator.userId, authenticator.credentialID],
+ }),
+ }),
);
export const storedContent = createTable(
- "storedContent",
- {
- id: integer("id").notNull().primaryKey({ autoIncrement: true }),
- content: text("content").notNull(),
- title: text("title", { length: 255 }),
- description: text("description", { length: 255 }),
- url: text("url").notNull(),
- savedAt: int("savedAt", { mode: "timestamp" }).notNull(),
- baseUrl: text("baseUrl", { length: 255 }).unique(),
- ogImage: text("ogImage", { length: 255 }),
- type: text("type").default("page"),
- image: text("image", { length: 255 }),
- userId: text("user").references(() => users.id, {
- onDelete: "cascade",
- }),
- noteId: integer("noteId"),
- },
- (sc) => ({
- urlIdx: index("storedContent_url_idx").on(sc.url),
- savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt),
- titleInx: index("storedContent_title_idx").on(sc.title),
- userIdx: index("storedContent_user_idx").on(sc.userId),
- }),
+ "storedContent",
+ {
+ id: integer("id").notNull().primaryKey({ autoIncrement: true }),
+ content: text("content").notNull(),
+ title: text("title", { length: 255 }),
+ description: text("description", { length: 255 }),
+ url: text("url").notNull(),
+ savedAt: int("savedAt", { mode: "timestamp" }).notNull(),
+ baseUrl: text("baseUrl", { length: 255 }).unique(),
+ ogImage: text("ogImage", { length: 255 }),
+ type: text("type").default("page"),
+ image: text("image", { length: 255 }),
+ userId: text("user").references(() => users.id, {
+ onDelete: "cascade",
+ }),
+ noteId: integer("noteId"),
+ },
+ (sc) => ({
+ urlIdx: index("storedContent_url_idx").on(sc.url),
+ savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt),
+ titleInx: index("storedContent_title_idx").on(sc.title),
+ userIdx: index("storedContent_user_idx").on(sc.userId),
+ }),
);
export type Content = typeof storedContent.$inferSelect;
export const contentToSpace = createTable(
- "contentToSpace",
- {
- contentId: integer("contentId")
- .notNull()
- .references(() => storedContent.id, { onDelete: "cascade" }),
- spaceId: integer("spaceId")
- .notNull()
- .references(() => space.id, { onDelete: "cascade" }),
- },
- (cts) => ({
- compoundKey: primaryKey({ columns: [cts.contentId, cts.spaceId] }),
- }),
+ "contentToSpace",
+ {
+ contentId: integer("contentId")
+ .notNull()
+ .references(() => storedContent.id, { onDelete: "cascade" }),
+ spaceId: integer("spaceId")
+ .notNull()
+ .references(() => space.id, { onDelete: "cascade" }),
+ },
+ (cts) => ({
+ compoundKey: primaryKey({ columns: [cts.contentId, cts.spaceId] }),
+ }),
);
export const space = createTable(
- "space",
- {
- id: integer("id").notNull().primaryKey({ autoIncrement: true }),
- name: text("name").notNull().unique().default("none"),
- user: text("user", { length: 255 }).references(() => users.id, {
- onDelete: "cascade",
- }),
- createdAt: int("createdAt", { mode: "timestamp" }).notNull(),
- numItems: integer("numItems").notNull().default(0),
- },
- (space) => ({
- nameIdx: index("spaces_name_idx").on(space.name),
- userIdx: index("spaces_user_idx").on(space.user),
- }),
+ "space",
+ {
+ id: integer("id").notNull().primaryKey({ autoIncrement: true }),
+ name: text("name").notNull().unique().default("none"),
+ user: text("user", { length: 255 }).references(() => users.id, {
+ onDelete: "cascade",
+ }),
+ createdAt: int("createdAt", { mode: "timestamp" }).notNull(),
+ numItems: integer("numItems").notNull().default(0),
+ },
+ (space) => ({
+ nameIdx: index("spaces_name_idx").on(space.name),
+ userIdx: index("spaces_user_idx").on(space.user),
+ }),
);
export const spacesAccess = createTable(
- "spacesAccess",
- {
- spaceId: integer("spaceId")
- .notNull()
- .references(() => space.id, { onDelete: "cascade" }),
- userEmail: text("userEmail").notNull(),
- },
- (spaceAccess) => ({
- compoundKey: primaryKey({
- columns: [spaceAccess.spaceId, spaceAccess.userEmail],
- }),
- }),
+ "spacesAccess",
+ {
+ spaceId: integer("spaceId")
+ .notNull()
+ .references(() => space.id, { onDelete: "cascade" }),
+ userEmail: text("userEmail").notNull(),
+ },
+ (spaceAccess) => ({
+ compoundKey: primaryKey({
+ columns: [spaceAccess.spaceId, spaceAccess.userEmail],
+ }),
+ }),
);
export type StoredContent = Omit<typeof storedContent.$inferSelect, "user">;
export type StoredSpace = typeof space.$inferSelect;
export type ChachedSpaceContent = StoredContent & {
- space: number;
+ space: number;
};
export const chatThreads = createTable(
- "chatThread",
- {
- id: text("id")
- .notNull()
- .primaryKey()
- .$defaultFn(() => crypto.randomUUID()),
- firstMessage: text("firstMessage").notNull(),
- userId: text("userId")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- },
- (thread) => ({
- userIdx: index("chatThread_user_idx").on(thread.userId),
- }),
+ "chatThread",
+ {
+ id: text("id")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => crypto.randomUUID()),
+ firstMessage: text("firstMessage").notNull(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ },
+ (thread) => ({
+ userIdx: index("chatThread_user_idx").on(thread.userId),
+ }),
);
export const chatHistory = createTable(
- "chatHistory",
- {
- id: integer("id").notNull().primaryKey({ autoIncrement: true }),
- threadId: text("threadId")
- .notNull()
- .references(() => chatThreads.id, { onDelete: "cascade" }),
- question: text("question").notNull(),
- answer: text("answerParts"), // Single answer part as string
- answerSources: text("answerSources"), // JSON stringified array of objects
- answerJustification: text("answerJustification"),
- },
- (history) => ({
- threadIdx: index("chatHistory_thread_idx").on(history.threadId),
- }),
+ "chatHistory",
+ {
+ id: integer("id").notNull().primaryKey({ autoIncrement: true }),
+ threadId: text("threadId")
+ .notNull()
+ .references(() => chatThreads.id, { onDelete: "cascade" }),
+ question: text("question").notNull(),
+ answer: text("answerParts"), // Single answer part as string
+ answerSources: text("answerSources"), // JSON stringified array of objects
+ answerJustification: text("answerJustification"),
+ },
+ (history) => ({
+ threadIdx: index("chatHistory_thread_idx").on(history.threadId),
+ }),
);
export const canvas = createTable(
- "canvas",
- {
- id: text("id")
- .notNull()
- .primaryKey()
- .$defaultFn(() => crypto.randomUUID()),
- title: text("title").default("Untitled").notNull(),
- description: text("description").default("Untitled").notNull(),
- imageUrl: text("url").default("").notNull(),
- userId: text("userId")
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- },
- (canvas) => ({
- userIdx: index("canvas_user_userId").on(canvas.userId),
- }),
+ "canvas",
+ {
+ id: text("id")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => crypto.randomUUID()),
+ title: text("title").default("Untitled").notNull(),
+ description: text("description").default("Untitled").notNull(),
+ imageUrl: text("url").default("").notNull(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ },
+ (canvas) => ({
+ userIdx: index("canvas_user_userId").on(canvas.userId),
+ }),
);
export type ChatThread = typeof chatThreads.$inferSelect;
diff --git a/apps/web/server/encrypt.ts b/apps/web/server/encrypt.ts
index bb2f5050..d2bbf4d3 100644
--- a/apps/web/server/encrypt.ts
+++ b/apps/web/server/encrypt.ts
@@ -1,95 +1,95 @@
function convertStringToFixedNumber(input: string): number {
- let hash = 0;
- for (let i = 0; i < input.length; i++) {
- const char = input.charCodeAt(i);
- hash = (hash * 31 + char) % 1000000007; // Hashing by a large prime number
- }
- return hash;
+ let hash = 0;
+ for (let i = 0; i < input.length; i++) {
+ const char = input.charCodeAt(i);
+ hash = (hash * 31 + char) % 1000000007; // Hashing by a large prime number
+ }
+ return hash;
}
const chars = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-1234567890`;
const shuffled = shuffle(
- chars.split(""),
- convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY),
+ chars.split(""),
+ convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY),
);
function random(seed: number) {
- const x = Math.sin(seed++) * 10000;
- return x - Math.floor(x);
+ const x = Math.sin(seed++) * 10000;
+ return x - Math.floor(x);
}
function shuffle(array: string[], seed: number) {
- let m = array.length,
- t,
- i;
+ let m = array.length,
+ t,
+ i;
- while (m) {
- i = Math.floor(random(seed) * m--);
+ while (m) {
+ i = Math.floor(random(seed) * m--);
- t = array[m];
- array[m] = array[i]!;
- array[i] = t!;
- ++seed;
- }
+ t = array[m];
+ array[m] = array[i]!;
+ array[i] = t!;
+ ++seed;
+ }
- return array;
+ return array;
}
export const cipher = (text: string) => {
- let returned_text = "";
+ let returned_text = "";
- for (let i = 0; i < text.length; i++) {
- returned_text += shuffled[chars.indexOf(text[i]!)];
- }
+ for (let i = 0; i < text.length; i++) {
+ returned_text += shuffled[chars.indexOf(text[i]!)];
+ }
- return extend(returned_text);
+ return extend(returned_text);
};
export const decipher = (text: string) => {
- let returned_text = "";
- const index = Math.floor(
- random(convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY)) *
- (text.length / 2),
- );
-
- for (let i = 0; i < text.length; i++) {
- returned_text += chars[shuffled.indexOf(text[i]!)];
- }
- const total = parseInt(text[index]!);
- const str = parseInt(text.slice(index + 1, index + total + 1));
- return returned_text.slice(text.length - str);
+ let returned_text = "";
+ const index = Math.floor(
+ random(convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY)) *
+ (text.length / 2),
+ );
+
+ for (let i = 0; i < text.length; i++) {
+ returned_text += chars[shuffled.indexOf(text[i]!)];
+ }
+ const total = parseInt(text[index]!);
+ const str = parseInt(text.slice(index + 1, index + total + 1));
+ return returned_text.slice(text.length - str);
};
const extend = (text: string, length = 60) => {
- const extra = length - text.length;
-
- if (extra < 0) {
- return text;
- }
-
- // Random index to store the length of the string
- const index = Math.floor(
- random(convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY)) *
- (length / 2),
- );
-
- const storage_string =
- text.length.toString().length.toString() + text.length.toString();
- let returned = "";
- let total = storage_string.length + text.length;
-
- for (let i = 0; i < extra; i++) {
- if (i == index) {
- returned += storage_string;
- } else {
- if (total >= length) {
- break;
- }
- // Add a random character
- returned += shuffled[Math.floor(random(Math.random()) * shuffled.length)];
- total++;
- }
- }
- returned += text;
- return returned;
+ const extra = length - text.length;
+
+ if (extra < 0) {
+ return text;
+ }
+
+ // Random index to store the length of the string
+ const index = Math.floor(
+ random(convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY)) *
+ (length / 2),
+ );
+
+ const storage_string =
+ text.length.toString().length.toString() + text.length.toString();
+ let returned = "";
+ let total = storage_string.length + text.length;
+
+ for (let i = 0; i < extra; i++) {
+ if (i == index) {
+ returned += storage_string;
+ } else {
+ if (total >= length) {
+ break;
+ }
+ // Add a random character
+ returned += shuffled[Math.floor(random(Math.random()) * shuffled.length)];
+ total++;
+ }
+ }
+ returned += text;
+ return returned;
};
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index 88fe2e7a..cf1434cf 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -1,2 +1 @@
module.exports = require("@repo/tailwind-config/tailwind.config");
- \ No newline at end of file
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index e5432796..7072be89 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -1,25 +1,25 @@
{
- "extends": "@repo/typescript-config/nextjs.json",
- "compilerOptions": {
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": ["./*"]
- }
- },
- "include": [
- "cf-env.d.ts",
- "env.d.ts",
- "next.config.mjs",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts",
- "../../packages/ui/",
- "../../packages/shared-types",
- "./components"
- ],
- "exclude": ["node_modules/"]
+ "extends": "@repo/typescript-config/nextjs.json",
+ "compilerOptions": {
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": [
+ "cf-env.d.ts",
+ "env.d.ts",
+ "next.config.mjs",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ "../../packages/ui/",
+ "../../packages/shared-types",
+ "./components"
+ ],
+ "exclude": ["node_modules/"]
}
diff --git a/apps/web/wrangler.toml b/apps/web/wrangler.toml
index a5f9b461..ce38285b 100644
--- a/apps/web/wrangler.toml
+++ b/apps/web/wrangler.toml
@@ -1,9 +1,8 @@
-name = "supermemory-ai"
+name = "supermemory"
compatibility_date = "2024-03-29"
compatibility_flags = [ "nodejs_compat" ]
pages_build_output_dir = ".vercel/output/static"
-
[placement]
mode = "smart"