├── .cursorrules ├── .gitignore ├── .gitmodules ├── .npmrc ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── SELF-HOSTING-GUIDE.md ├── apps ├── backend │ ├── .gitignore │ ├── README.md │ ├── drizzle.config.prod.ts │ ├── drizzle.config.ts │ ├── drizzle │ │ ├── 0000_odd_impossible_man.sql │ │ ├── 0001_seed-types.sql │ │ ├── 0002_skinny_princess_powerful.sql │ │ ├── 0003_luxuriant_annihilus.sql │ │ ├── 0004_early_rick_jones.sql │ │ ├── 0005_create-access-types.sql │ │ ├── 0006_wandering_grandmaster.sql │ │ ├── 0007_fantastic_serpent_society.sql │ │ ├── 0008_add-notion.sql │ │ ├── 0009_milky_sleepwalker.sql │ │ ├── 0010_heavy_preak.sql │ │ ├── 0011_new_liz_osborn.sql │ │ ├── 0012_small_mystique.sql │ │ ├── 0013_sharp_hemingway.sql │ │ ├── 0014_mighty_the_captain.sql │ │ ├── 0015_perpetual_mauler.sql │ │ ├── 0016_good_deathbird.sql │ │ ├── 0018_past_inertia.sql │ │ ├── 0019_vengeful_marten_broadcloak.sql │ │ ├── 0020_opposite_steel_serpent.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ ├── 0001_snapshot.json │ │ │ ├── 0002_snapshot.json │ │ │ ├── 0003_snapshot.json │ │ │ ├── 0004_snapshot.json │ │ │ ├── 0005_snapshot.json │ │ │ ├── 0006_snapshot.json │ │ │ ├── 0007_snapshot.json │ │ │ ├── 0008_snapshot.json │ │ │ ├── 0009_snapshot.json │ │ │ ├── 0010_snapshot.json │ │ │ ├── 0011_snapshot.json │ │ │ ├── 0012_snapshot.json │ │ │ ├── 0013_snapshot.json │ │ │ ├── 0014_snapshot.json │ │ │ ├── 0015_snapshot.json │ │ │ ├── 0016_snapshot.json │ │ │ ├── 0017_snapshot.json │ │ │ ├── 0018_snapshot.json │ │ │ ├── 0019_snapshot.json │ │ │ ├── 0020_snapshot.json │ │ │ └── _journal.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── output.css │ ├── scripts │ │ └── migrate.ts │ ├── src │ │ ├── auth.ts │ │ ├── components │ │ │ └── landing.tsx │ │ ├── errors │ │ │ ├── baseError.ts │ │ │ └── results.ts │ │ ├── globals.css │ │ ├── index.tsx │ │ ├── providers.ts │ │ ├── routes │ │ │ ├── actions.ts │ │ │ ├── integrations.ts │ │ │ ├── memories.ts │ │ │ ├── spaces.ts │ │ │ └── user.ts │ │ ├── types.ts │ │ ├── utils │ │ │ ├── chunkers.ts │ │ │ ├── cipher.ts │ │ │ ├── extractDocumentContent.ts │ │ │ ├── extractor.ts │ │ │ ├── fetchers.ts │ │ │ ├── notion.ts │ │ │ ├── tweetsToThreads.ts │ │ │ └── typeDecider.ts │ │ └── workflow │ │ │ └── index.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.d.ts │ └── wrangler.toml ├── docs │ ├── README.md │ ├── api-reference │ │ └── endpoints │ │ │ ├── connect │ │ │ ├── connect-app.mdx │ │ │ └── connection-information.mdx │ │ │ ├── memory-management │ │ │ ├── delete-delete.mdx │ │ │ ├── post-add.mdx │ │ │ └── put-update.mdx │ │ │ ├── search │ │ │ ├── get-fastsearch.mdx │ │ │ └── post-search.mdx │ │ │ └── settings.mdx │ ├── changelog │ │ └── overview.mdx │ ├── essentials │ │ ├── metadata-filtering.mdx │ │ └── pricing.mdx │ ├── favicon.png │ ├── image.png │ ├── images │ │ ├── checks-passed.png │ │ ├── hero-dark.svg │ │ ├── hero-light.svg │ │ └── setup │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ ├── introduction.mdx │ ├── logo │ │ ├── dark.svg │ │ └── light.svg │ ├── mint.json │ ├── openapi.json │ ├── quickstart.mdx │ ├── self-hosting.mdx │ └── snippets │ │ └── snippet-intro.mdx ├── extension │ ├── .gitignore │ ├── README.md │ ├── components.json │ ├── css │ │ └── globals.css │ ├── extension-env.d.ts │ ├── images │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── favicon.ico │ ├── lib │ │ └── utils.ts │ ├── manifest.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── globals.css │ │ └── logo.svg │ ├── scripts │ │ ├── content.tsx │ │ └── css.tsx │ ├── src │ │ ├── background.ts │ │ ├── content.tsx │ │ ├── helpers.ts │ │ ├── twitter.constants.ts │ │ └── twitter.ts │ ├── tailwind.config.cjs │ ├── tsconfig.json │ └── ui │ │ ├── hooks │ │ └── use-spaces.tsx │ │ ├── shadcn │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ └── popover.tsx │ │ └── spaces-selector.tsx └── web │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── app │ ├── components │ │ ├── Chat.tsx │ │ ├── ChatInputForm.tsx │ │ ├── Histories.tsx │ │ ├── Landing │ │ │ ├── Feature.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Hero.tsx │ │ │ ├── Note.tsx │ │ │ ├── Private.tsx │ │ │ ├── index.tsx │ │ │ └── plus-grid.tsx │ │ ├── Navbar.tsx │ │ ├── Reminders.tsx │ │ ├── Suggestions.tsx │ │ ├── editor │ │ │ ├── plate-editor.tsx │ │ │ ├── plate-types.ts │ │ │ ├── plugins │ │ │ │ ├── ai-plugins.tsx │ │ │ │ ├── align-plugin.ts │ │ │ │ ├── autoformat-plugin.ts │ │ │ │ ├── basic-nodes-plugins.tsx │ │ │ │ ├── block-menu-plugins.ts │ │ │ │ ├── block-selection-plugins.ts │ │ │ │ ├── comments-plugin.tsx │ │ │ │ ├── copilot-plugins.ts │ │ │ │ ├── delete-plugins.ts │ │ │ │ ├── dnd-plugins.tsx │ │ │ │ ├── editor-plugins.tsx │ │ │ │ ├── exit-break-plugin.ts │ │ │ │ ├── fixed-toolbar-plugin.tsx │ │ │ │ ├── floating-toolbar-plugin.tsx │ │ │ │ ├── indent-list-plugins.ts │ │ │ │ ├── line-height-plugin.ts │ │ │ │ ├── link-plugin.tsx │ │ │ │ ├── media-plugins.tsx │ │ │ │ ├── mention-plugin.ts │ │ │ │ ├── reset-block-type-plugin.ts │ │ │ │ ├── soft-break-plugin.ts │ │ │ │ ├── table-plugin.ts │ │ │ │ └── toc-plugin.ts │ │ │ ├── transforms.ts │ │ │ ├── use-chat.tsx │ │ │ ├── use-create-editor.tsx │ │ │ └── writing-playground.tsx │ │ ├── gradients │ │ │ └── gradient1.png │ │ ├── icons │ │ │ ├── IntegrationIcons.tsx │ │ │ └── Logo.tsx │ │ ├── markdown │ │ │ ├── codeblock.tsx │ │ │ └── renderer.tsx │ │ ├── memories │ │ │ ├── AddMemory.tsx │ │ │ ├── CSVUploadModal.tsx │ │ │ ├── Integrations.tsx │ │ │ ├── MarkdownUploadModal.tsx │ │ │ ├── MemoriesPage.tsx │ │ │ ├── SharedCard.tsx │ │ │ └── SpacesSelector.tsx │ │ ├── plate-ui │ │ │ ├── ai-chat-editor.tsx │ │ │ ├── ai-leaf.tsx │ │ │ ├── ai-menu-items.tsx │ │ │ ├── ai-menu.tsx │ │ │ ├── ai-toolbar-button.tsx │ │ │ ├── align-dropdown-menu.tsx │ │ │ ├── avatar.tsx │ │ │ ├── block-context-menu.tsx │ │ │ ├── block-selection.tsx │ │ │ ├── blockquote-element.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── caption.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── code-block-combobox.tsx │ │ │ ├── code-block-element.css │ │ │ ├── code-block-element.tsx │ │ │ ├── code-leaf.tsx │ │ │ ├── code-line-element.tsx │ │ │ ├── code-syntax-leaf.tsx │ │ │ ├── color-constants.ts │ │ │ ├── color-dropdown-menu-items.tsx │ │ │ ├── color-dropdown-menu.tsx │ │ │ ├── color-input.tsx │ │ │ ├── color-picker.tsx │ │ │ ├── colors-custom.tsx │ │ │ ├── column-element.tsx │ │ │ ├── column-group-element.tsx │ │ │ ├── command.tsx │ │ │ ├── comment-avatar.tsx │ │ │ ├── comment-create-form.tsx │ │ │ ├── comment-item.tsx │ │ │ ├── comment-leaf.tsx │ │ │ ├── comment-more-dropdown.tsx │ │ │ ├── comment-reply-items.tsx │ │ │ ├── comment-resolve-button.tsx │ │ │ ├── comment-toolbar-button.tsx │ │ │ ├── comment-value.tsx │ │ │ ├── comments-popover.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── cursor-overlay.tsx │ │ │ ├── date-element.tsx │ │ │ ├── dialog.tsx │ │ │ ├── draggable.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── editor.tsx │ │ │ ├── emoji-picker-navigation.tsx │ │ │ ├── emoji-picker-preview.tsx │ │ │ ├── emoji-picker-search-and-clear.tsx │ │ │ ├── emoji-picker-search-bar.tsx │ │ │ ├── emoji-picker.tsx │ │ │ ├── emoji-toolbar-dropdown.tsx │ │ │ ├── excalidraw-element.tsx │ │ │ ├── fixed-toolbar-buttons.tsx │ │ │ ├── fixed-toolbar.tsx │ │ │ ├── floating-toolbar-buttons.tsx │ │ │ ├── floating-toolbar.tsx │ │ │ ├── ghost-text.tsx │ │ │ ├── heading-element.tsx │ │ │ ├── highlight-leaf.tsx │ │ │ ├── hr-element.tsx │ │ │ ├── image-element.tsx │ │ │ ├── image-preview.tsx │ │ │ ├── indent-fire-marker.tsx │ │ │ ├── indent-list-toolbar-button.tsx │ │ │ ├── indent-todo-marker.tsx │ │ │ ├── indent-todo-toolbar-button.tsx │ │ │ ├── indent-toolbar-button.tsx │ │ │ ├── inline-combobox.tsx │ │ │ ├── input.tsx │ │ │ ├── insert-dropdown-menu.tsx │ │ │ ├── kbd-leaf.tsx │ │ │ ├── line-height-dropdown-menu.tsx │ │ │ ├── link-element.tsx │ │ │ ├── link-floating-toolbar.tsx │ │ │ ├── link-toolbar-button.tsx │ │ │ ├── mark-toolbar-button.tsx │ │ │ ├── media-embed-element.tsx │ │ │ ├── media-popover.tsx │ │ │ ├── media-toolbar-button.tsx │ │ │ ├── mention-element.tsx │ │ │ ├── mention-input-element.tsx │ │ │ ├── mode-dropdown-menu.tsx │ │ │ ├── more-dropdown-menu.tsx │ │ │ ├── outdent-toolbar-button.tsx │ │ │ ├── paragraph-element.tsx │ │ │ ├── placeholder.tsx │ │ │ ├── plate-element.tsx │ │ │ ├── popover.tsx │ │ │ ├── resizable.tsx │ │ │ ├── separator.tsx │ │ │ ├── slash-input-element.tsx │ │ │ ├── table-cell-element.tsx │ │ │ ├── table-dropdown-menu.tsx │ │ │ ├── table-element.tsx │ │ │ ├── table-row-element.tsx │ │ │ ├── toc-element.tsx │ │ │ ├── toggle-element.tsx │ │ │ ├── toggle-toolbar-button.tsx │ │ │ ├── toolbar.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── turn-into-dropdown-menu.tsx │ │ │ └── with-draggables.tsx │ │ ├── skeletons │ │ │ ├── HistoriesSkeleton.tsx │ │ │ └── SuggestionsSkeleton.tsx │ │ ├── theme-button.tsx │ │ ├── twitter │ │ │ ├── avatar-img.tsx │ │ │ ├── icons │ │ │ │ ├── icons.module.css │ │ │ │ ├── index.ts │ │ │ │ ├── verified-business.tsx │ │ │ │ ├── verified-government.tsx │ │ │ │ └── verified.tsx │ │ │ ├── quoted-tweet │ │ │ │ ├── quoted-tweet-body.module.css │ │ │ │ ├── quoted-tweet-body.tsx │ │ │ │ ├── quoted-tweet-container.module.css │ │ │ │ ├── quoted-tweet-container.tsx │ │ │ │ ├── quoted-tweet-header.module.css │ │ │ │ ├── quoted-tweet-header.tsx │ │ │ │ └── quoted-tweet.tsx │ │ │ ├── render-tweet.tsx │ │ │ ├── tweet-body.module.css │ │ │ ├── tweet-container.module.css │ │ │ ├── tweet-header.module.css │ │ │ ├── tweet-in-reply-to.module.css │ │ │ ├── tweet-link.module.css │ │ │ ├── tweet-media-video.module.css │ │ │ ├── tweet-media-video.tsx │ │ │ ├── tweet-media.module.css │ │ │ ├── tweet-media.tsx │ │ │ ├── verified-badge.module.css │ │ │ └── verified-badge.tsx │ │ └── ui │ │ │ ├── Loader.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── combobox.tsx │ │ │ ├── command.tsx │ │ │ ├── credenza.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── select.tsx │ │ │ ├── switch.tsx │ │ │ ├── tabs.tsx │ │ │ └── textarea.tsx │ ├── config │ │ ├── integrations.tsx │ │ └── util.ts │ ├── entry.client.tsx │ ├── entry.server.tsx │ ├── lib │ │ ├── auth │ │ │ └── authMiddleware.ts │ │ ├── constants │ │ │ ├── pastelColors.tsx │ │ │ └── typeIcons.tsx │ │ ├── env.server.ts │ │ ├── environment.ts │ │ ├── hooks │ │ │ ├── use-auto-scroll.ts │ │ │ ├── use-chat-stream.ts │ │ │ ├── use-debounce.ts │ │ │ ├── use-fetcher-with-promise.tsx │ │ │ ├── use-is-touch-device.ts │ │ │ ├── use-keyboard.tsx │ │ │ ├── use-live-transcript.tsx │ │ │ ├── use-media-query.tsx │ │ │ ├── use-memories.tsx │ │ │ ├── use-mounted.ts │ │ │ ├── use-spaces.tsx │ │ │ ├── use-text-overflow.ts │ │ │ └── use-upload-file.ts │ │ ├── misc.ts │ │ ├── stripe.constants.ts │ │ ├── stripe.ts │ │ ├── theme-provider.tsx │ │ ├── theme.server.ts │ │ ├── types │ │ │ ├── memory.ts │ │ │ └── safety.ts │ │ ├── utils.ts │ │ └── utils │ │ │ ├── metadata.ts │ │ │ └── tweet.ts │ ├── root.tsx │ ├── routes │ │ ├── _index.tsx │ │ ├── action.chat.tsx │ │ ├── action.set-theme.tsx │ │ ├── action.sign-out.tsx │ │ ├── action.upload.tsx │ │ ├── callback.ts │ │ ├── chat.$chatId.tsx │ │ ├── chat.new.tsx │ │ ├── content.$contentid.tsx │ │ ├── editor.tsx │ │ ├── extension.ts │ │ ├── home.tsx │ │ ├── onboarding.add.tsx │ │ ├── onboarding.import.tsx │ │ ├── onboarding.index.tsx │ │ ├── onboarding.privacy.tsx │ │ ├── pay.stripe.callback.tsx │ │ ├── pay.stripe.tsx │ │ ├── pay.stripe.webhook.tsx │ │ ├── pitch.index.tsx │ │ ├── pricing.index.tsx │ │ ├── privacy.tsx │ │ ├── ref.tsx │ │ ├── signin.tsx │ │ ├── space.$spaceId.tsx │ │ ├── space.($spaceId).invitation.tsx │ │ └── tos.tsx │ ├── sonner.css │ ├── tailwind.css │ └── types │ │ ├── css.d.ts │ │ └── stripe.d.ts │ ├── assets │ └── logo.svg │ ├── components.json │ ├── env.d.ts │ ├── eslint.config.mjs │ ├── functions │ ├── [[path]].ts │ └── notion.ts │ ├── load-context.ts │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── _headers │ ├── _routes.json │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── og-image.png │ ├── product-of-the-day.png │ ├── siri.webp │ └── site.webmanifest │ ├── server │ ├── index.ts │ └── proxy.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── vite.config.ts │ ├── worker-configuration.d.ts │ └── wrangler.toml ├── docker-compose.yml ├── logo.svg ├── package.json ├── packages ├── db │ ├── index.ts │ ├── package.json │ └── schema.ts └── shared │ ├── icons.tsx │ ├── package.json │ ├── tsconfig.json │ ├── types.ts │ └── utils.ts ├── patches ├── @udecode%2Fplate-emoji@40.0.0.patch └── raf@3.4.1.patch └── turbo.json /.cursorrules: -------------------------------------------------------------------------------- 1 | i am building an app called "supermemory" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | bun.lockb 3 | tests/ 4 | new-api/ 5 | 6 | packages/scripts/* 7 | # Dependencies 8 | node_modules/ 9 | /.pnp 10 | .pnp.js 11 | 12 | # Local env files 13 | .env 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | .env*.local 19 | 20 | # Testing 21 | /coverage 22 | 23 | # Turbo 24 | .turbo 25 | 26 | # Vercel 27 | .vercel 28 | 29 | # Build Outputs 30 | /.next 31 | out/ 32 | build 33 | /dist 34 | 35 | # Debug 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | # Misc 41 | .DS_Store 42 | *.pem 43 | 44 | # IDE specific files 45 | .idea/ 46 | .vscode/ 47 | *.swp 48 | *.swo 49 | 50 | # Sensitive data 51 | *.key 52 | **/credentials.* 53 | **/secrets.* 54 | config.private.* 55 | 56 | # Cache 57 | .cache/ 58 | .npm 59 | .eslintcache 60 | 61 | # Local database files 62 | *.sqlite 63 | *.db 64 | 65 | # Personal notes/todos 66 | TODO.md 67 | NOTES.md 68 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/.gitmodules -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | engine-strict=true 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "always" 6 | }, 7 | "sqltools.connections": [ 8 | { 9 | "previewLimit": 50, 10 | "server": "localhost", 11 | "driver": "PostgreSQL", 12 | "name": "SupermemoryDB", 13 | "connectString": "postgresql://dhravya:DPOSTGRES99%40c22%23hLab27postgres@168.119.111.189/supermemory" 14 | } 15 | ], 16 | "millionLint.disableOutdatedVersionMessage": true 17 | } 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:17 2 | 3 | # Install build dependencies 4 | RUN apt-get update && apt-get install -y \ 5 | build-essential \ 6 | git \ 7 | curl \ 8 | pkg-config \ 9 | libssl-dev \ 10 | libclang-dev \ 11 | llvm-dev \ 12 | postgresql-server-dev-all \ 13 | && rm -rf /var/lib/apt/lists/* 14 | 15 | # Install pgvector 16 | RUN git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git \ 17 | && cd pgvector \ 18 | && make \ 19 | && make install 20 | 21 | # Install Rust 22 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 23 | ENV PATH="/root/.cargo/bin:${PATH}" 24 | 25 | # Install and initialize pgrx 26 | RUN cargo install cargo-pgrx --version 0.12.5 --locked && \ 27 | cargo pgrx init --pg17 pg_config 28 | 29 | # Clone and install pgvectorscale 30 | RUN cd /tmp && \ 31 | git clone --branch 0.5.0 https://github.com/timescale/pgvectorscale && \ 32 | cd pgvectorscale/pgvectorscale && \ 33 | cargo pgrx install --release 34 | 35 | # Create initialization script to enable both extensions 36 | RUN echo 'CREATE EXTENSION IF NOT EXISTS vector;' > /docker-entrypoint-initdb.d/01-init-vector.sql && \ 37 | echo 'CREATE EXTENSION IF NOT EXISTS vectorscale CASCADE;' > /docker-entrypoint-initdb.d/02-init-vectorscale.sql -------------------------------------------------------------------------------- /apps/backend/.gitignore: -------------------------------------------------------------------------------- 1 | # prod 2 | dist/ 3 | .dev.vars 4 | *.vars 5 | 6 | # dev 7 | .yarn/ 8 | !.yarn/releases 9 | .vscode/* 10 | !.vscode/launch.json 11 | !.vscode/*.code-snippets 12 | .idea/workspace.xml 13 | .idea/usage.statistics.xml 14 | .idea/shelf 15 | 16 | # deps 17 | node_modules/ 18 | .wrangler 19 | 20 | # env 21 | .env 22 | .env.production 23 | .dev.vars 24 | 25 | # logs 26 | logs/ 27 | *.log 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | pnpm-debug.log* 32 | lerna-debug.log* 33 | 34 | # misc 35 | .DS_Store 36 | -------------------------------------------------------------------------------- /apps/backend/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | npm run deploy 8 | ``` 9 | -------------------------------------------------------------------------------- /apps/backend/drizzle.config.prod.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | 3 | export default defineConfig({ 4 | dialect: "postgresql", 5 | schema: "../../packages/db", 6 | out: "./drizzle", 7 | dbCredentials: { 8 | url: process.env.PROD_DATABASE_URL!, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /apps/backend/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | import { defineConfig } from "drizzle-kit"; 3 | import process from "process"; 4 | 5 | config(); 6 | 7 | if (process.env.NODE_ENV !== "production" && !process.env.DATABASE_URL) { 8 | throw new Error("DATABASE_URL is not set"); 9 | } 10 | 11 | export default defineConfig({ 12 | dialect: "postgresql", 13 | schema: "../../packages/db", 14 | out: "./drizzle", 15 | dbCredentials: { 16 | url: process.env.DATABASE_URL!, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /apps/backend/drizzle/0001_seed-types.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put you code below! -- 2 | INSERT INTO "document_type" (type) VALUES ('tweet'), ('page'), ('note') ON CONFLICT DO NOTHING; 3 | INSERT INTO "document_type" (type) VALUES ('document') ON CONFLICT DO NOTHING; -------------------------------------------------------------------------------- /apps/backend/drizzle/0002_skinny_princess_powerful.sql: -------------------------------------------------------------------------------- 1 | -- Active: 1732249624784@@_@5432@supermemorymain 2 | ALTER TABLE "documents" ADD COLUMN "is_successfully_processed" boolean DEFAULT false; -------------------------------------------------------------------------------- /apps/backend/drizzle/0003_luxuriant_annihilus.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "documents" ALTER COLUMN "is_successfully_processed" DROP NOT NULL;--> statement-breakpoint 2 | ALTER TABLE "space_access" ADD COLUMN "access_type" text DEFAULT 'read' NOT NULL;--> statement-breakpoint 3 | CREATE UNIQUE INDEX IF NOT EXISTS "document_raw_user_idx" ON "documents" USING btree ("raw","user_id");--> statement-breakpoint -------------------------------------------------------------------------------- /apps/backend/drizzle/0004_early_rick_jones.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "saved_spaces" ( 2 | "user_id" integer NOT NULL, 3 | "space_id" integer NOT NULL, 4 | "saved_at" timestamp DEFAULT now() NOT NULL 5 | ); 6 | --> statement-breakpoint 7 | DO $$ BEGIN 8 | ALTER TABLE "saved_spaces" ADD CONSTRAINT "saved_spaces_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; 9 | EXCEPTION 10 | WHEN duplicate_object THEN null; 11 | END $$; 12 | --> statement-breakpoint 13 | DO $$ BEGIN 14 | ALTER TABLE "saved_spaces" ADD CONSTRAINT "saved_spaces_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action; 15 | EXCEPTION 16 | WHEN duplicate_object THEN null; 17 | END $$; 18 | --> statement-breakpoint 19 | CREATE UNIQUE INDEX IF NOT EXISTS "saved_spaces_user_space_idx" ON "saved_spaces" USING btree ("user_id","space_id"); -------------------------------------------------------------------------------- /apps/backend/drizzle/0005_create-access-types.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put you code below! -- 2 | INSERT INTO "space_access_status" (status) VALUES ('pending'), ('accepted'), ('rejected') ON CONFLICT DO NOTHING; -------------------------------------------------------------------------------- /apps/backend/drizzle/0006_wandering_grandmaster.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "waitlist" ( 2 | "email" varchar(512) PRIMARY KEY NOT NULL 3 | ); 4 | -------------------------------------------------------------------------------- /apps/backend/drizzle/0007_fantastic_serpent_society.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS "document_url_user_id_idx"; -------------------------------------------------------------------------------- /apps/backend/drizzle/0008_add-notion.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put you code below! -- 2 | INSERT INTO "document_type" (type) VALUES ('notion') ON CONFLICT DO NOTHING; -------------------------------------------------------------------------------- /apps/backend/drizzle/0009_milky_sleepwalker.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "waitlist" ADD COLUMN "created_at" timestamp with time zone DEFAULT now() NOT NULL; -------------------------------------------------------------------------------- /apps/backend/drizzle/0010_heavy_preak.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "documents" DROP CONSTRAINT "documents_user_id_users_id_fk"; 2 | --> statement-breakpoint 3 | DO $$ BEGIN 4 | ALTER TABLE "documents" ADD CONSTRAINT "documents_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; 5 | EXCEPTION 6 | WHEN duplicate_object THEN null; 7 | END $$; 8 | -------------------------------------------------------------------------------- /apps/backend/drizzle/0011_new_liz_osborn.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "documents" ADD COLUMN "error_message" text; -------------------------------------------------------------------------------- /apps/backend/drizzle/0012_small_mystique.sql: -------------------------------------------------------------------------------- 1 | -- Active: 1732308352274@@127.0.0.1@5432@supermemorydhravya 2 | ALTER TABLE "users" ADD COLUMN "last_api_key_generated_at" timestamp DEFAULT now(); -------------------------------------------------------------------------------- /apps/backend/drizzle/0013_sharp_hemingway.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "documents" ADD COLUMN "content_hash" text; -------------------------------------------------------------------------------- /apps/backend/drizzle/0014_mighty_the_captain.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "users" ADD COLUMN "stripe_customer_id" text;--> statement-breakpoint 2 | ALTER TABLE "users" ADD COLUMN "tier" text DEFAULT 'free' NOT NULL; -------------------------------------------------------------------------------- /apps/backend/drizzle/0015_perpetual_mauler.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "chunks" ALTER COLUMN "embeddings" SET DATA TYPE vector(768); -------------------------------------------------------------------------------- /apps/backend/drizzle/0016_good_deathbird.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "chunks" ALTER COLUMN "embeddings" SET DATA TYPE vector(768);--> statement-breakpoint 2 | CREATE INDEX IF NOT EXISTS "documents_search_idx" ON "documents" USING gin (( 3 | setweight(to_tsvector('english', coalesce("content", '')),'A') || 4 | setweight(to_tsvector('english', coalesce("title", '')),'B') || 5 | setweight(to_tsvector('english', coalesce("description", '')),'C') || 6 | setweight(to_tsvector('english', coalesce("url", '')),'D') 7 | )); -------------------------------------------------------------------------------- /apps/backend/drizzle/0018_past_inertia.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS "documents_search_idx";--> statement-breakpoint 2 | CREATE INDEX IF NOT EXISTS "documents_search_idx" ON "documents" USING gin (( 3 | setweight(to_tsvector('english', coalesce("content", '')),'A') || 4 | setweight(to_tsvector('english', coalesce("title", '')),'B') || 5 | setweight(to_tsvector('english', coalesce("description", '')),'C') || 6 | setweight(to_tsvector('english', coalesce("url", '')),'D') 7 | )); -------------------------------------------------------------------------------- /apps/backend/drizzle/0019_vengeful_marten_broadcloak.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "documents" ADD COLUMN "metadata" jsonb; -------------------------------------------------------------------------------- /apps/backend/drizzle/0020_opposite_steel_serpent.sql: -------------------------------------------------------------------------------- 1 | -- Active: 1732308352274@@127.0.0.1@5432@supermemorydhravya 2 | DROP TABLE "job"; -------------------------------------------------------------------------------- /apps/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supermemory-backend", 3 | "scripts": { 4 | "dev": "bunx wrangler -v && wrangler dev", 5 | "deploy": "bunx wrangler deploy --minify", 6 | "generate-migration": "dotenv -- npx drizzle-kit generate", 7 | "migrate:local": "bun run ./scripts/migrate.ts", 8 | "migrate:prod": "NODE_ENV=production bun run ./scripts/migrate.ts", 9 | "tail": "bunx wrangler tail" 10 | }, 11 | "dependencies": { 12 | "@ai-sdk/google": "^0.0.51", 13 | "@ai-sdk/openai": "^0.0.70", 14 | "@hono/swagger-ui": "^0.5.0", 15 | "@hono/zod-openapi": "^0.18.3", 16 | "@hono/zod-validator": "^0.4.1", 17 | "@supermemory/db": "workspace:*", 18 | "ai": "4.0.16", 19 | "compromise": "^14.14.2", 20 | "dotenv": "^16.4.5", 21 | "drizzle-kit": "^0.25.0", 22 | "drizzle-orm": "^0.34.1", 23 | "hono": "^4.6.4", 24 | "openai": "^4.68.4", 25 | "postgres": "^3.4.4", 26 | "uuid": "^11.0.1", 27 | "wrangler": "^3.111.0", 28 | "zod": "^3.23.8" 29 | }, 30 | "devDependencies": { 31 | "@cloudflare/workers-types": "^4.20250124.3" 32 | }, 33 | "overrides": { 34 | "iron-webcrypto": "^1.2.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/backend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/backend/public/favicon.ico -------------------------------------------------------------------------------- /apps/backend/scripts/migrate.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | import { drizzle } from "drizzle-orm/postgres-js"; 3 | import { migrate } from "drizzle-orm/postgres-js/migrator"; 4 | import process from "node:process"; 5 | import postgres from "postgres"; 6 | 7 | config(); 8 | 9 | const isProd = process.env.NODE_ENV === "production"; 10 | const connectionString = isProd ? process.env.PROD_DATABASE_URL : process.env.DATABASE_URL; 11 | 12 | if (!connectionString) { 13 | throw new Error(`${isProd ? "PROD_DATABASE_URL" : "DATABASE_URL"} is not set`); 14 | } 15 | 16 | console.log("Connecting to:", connectionString.replace(/:[^:@]+@/, ":****@")); // Log sanitized connection string 17 | 18 | const migrationClient = postgres(connectionString, { max: 1 }); 19 | 20 | async function main() { 21 | console.log("Running migrations..."); 22 | 23 | try { 24 | const db = drizzle(migrationClient); 25 | await migrate(db, { migrationsFolder: "./drizzle" }); 26 | console.log("Migrations completed!"); 27 | } catch (error) { 28 | console.error("Migration failed:", error); 29 | } finally { 30 | await migrationClient.end(); 31 | } 32 | } 33 | 34 | main().catch((err) => { 35 | console.error("Unexpected error:", err); 36 | process.exit(1); 37 | }); 38 | -------------------------------------------------------------------------------- /apps/backend/src/errors/baseError.ts: -------------------------------------------------------------------------------- 1 | export class BaseHttpError extends Error { 2 | public status: number; 3 | public message: string; 4 | 5 | constructor(status: number, message: string) { 6 | super(message); 7 | this.status = status; 8 | this.message = message; 9 | Object.setPrototypeOf(this, new.target.prototype); // Restore prototype chain 10 | } 11 | } 12 | 13 | 14 | export class BaseError extends Error { 15 | type: string; 16 | message: string; 17 | source: string; 18 | ignoreLog: boolean; 19 | 20 | constructor( 21 | type: string, 22 | message?: string, 23 | source?: string, 24 | ignoreLog = false 25 | ) { 26 | super(); 27 | 28 | Object.setPrototypeOf(this, new.target.prototype); 29 | 30 | this.type = type; 31 | this.message = 32 | message ?? 33 | "An unknown error occurred. If this persists, please contact us."; 34 | this.source = source ?? "unspecified"; 35 | this.ignoreLog = ignoreLog; 36 | } 37 | 38 | toJSON(): Record { 39 | return { 40 | type: this.type, 41 | message: this.message, 42 | source: this.source, 43 | }; 44 | } 45 | } -------------------------------------------------------------------------------- /apps/backend/src/errors/results.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from "./baseError"; 2 | 3 | export type Result = 4 | | { ok: true; value: T } 5 | | { ok: false; error: E }; 6 | 7 | export const Ok = (data: T): Result => { 8 | return { ok: true, value: data }; 9 | }; 10 | 11 | export const Err = (error: E): Result => { 12 | return { ok: false, error }; 13 | }; 14 | 15 | export async function wrap( 16 | p: Promise, 17 | errorFactory: (err: Error, source: string) => E, 18 | source: string = "unspecified" 19 | ): Promise> { 20 | try { 21 | return Ok(await p); 22 | } catch (e) { 23 | return Err(errorFactory(e as Error, source)); 24 | } 25 | } 26 | 27 | export function isErr( 28 | result: Result, 29 | ): result is { ok: false; error: E } { 30 | return !result.ok; 31 | } -------------------------------------------------------------------------------- /apps/backend/src/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/backend/src/providers.ts: -------------------------------------------------------------------------------- 1 | import { createOpenAI, OpenAIProvider } from "@ai-sdk/openai"; 2 | import { createGoogleGenerativeAI } from "@ai-sdk/google"; 3 | import { Env } from "./types"; 4 | 5 | export function openai( 6 | env: Env, 7 | apiKey?: string 8 | ): ReturnType { 9 | return createOpenAI({ 10 | apiKey: apiKey || env.OPEN_AI_API_KEY, 11 | baseURL: "https://gateway.ai.cloudflare.com/v1/47c2b4d598af9d423c06fc9f936226d5/supermemory/openai" 12 | }); 13 | } 14 | 15 | export function google(securityKey: string) { 16 | return createGoogleGenerativeAI({ 17 | apiKey: securityKey, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /apps/backend/src/utils/extractor.ts: -------------------------------------------------------------------------------- 1 | import { Env } from "../types"; 2 | 3 | export const extractPageContent = async (content: string, env: Env) => { 4 | const resp = await fetch(`https://r.jina.ai/${content}`); 5 | 6 | if (!resp.ok) { 7 | throw new Error( 8 | `Failed to fetch ${content}: ${resp.statusText}` + (await resp.text()) 9 | ); 10 | } 11 | 12 | const metadataResp = await fetch(`https://md.dhr.wtf/metadata?url=${content}`); 13 | 14 | if (!metadataResp.ok) { 15 | throw new Error( 16 | `Failed to fetch metadata for ${content}: ${metadataResp.statusText}` + 17 | (await metadataResp.text()) 18 | ); 19 | } 20 | 21 | const metadata = await metadataResp.json() as { 22 | title?: string; 23 | description?: string; 24 | image?: string; 25 | favicon?: string; 26 | }; 27 | 28 | const responseText = await resp.text(); 29 | 30 | try { 31 | const json: { 32 | contentToVectorize: string; 33 | contentToSave: string; 34 | title?: string; 35 | description?: string; 36 | image?: string; 37 | favicon?: string; 38 | } = { 39 | contentToSave: responseText, 40 | contentToVectorize: responseText, 41 | title: metadata.title, 42 | description: metadata.description, 43 | image: metadata.image, 44 | favicon: metadata.favicon, 45 | }; 46 | return json; 47 | } catch (e) { 48 | throw new Error(`Failed to parse JSON from ${content}: ${e}`); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /apps/backend/src/utils/typeDecider.ts: -------------------------------------------------------------------------------- 1 | import { Result, Ok, Err } from "../errors/results"; 2 | import { BaseError } from "../errors/baseError"; 3 | 4 | export type contentType = "page" | "tweet" | "note" | "document" | "notion"; 5 | 6 | class GetTypeError extends BaseError { 7 | constructor(message?: string, source?: string) { 8 | super("[Decide Type Error]", message, source); 9 | } 10 | } 11 | export const typeDecider = ( 12 | content: string 13 | ): Result => { 14 | try { 15 | // 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. 16 | // if it ends with .pdf etc then it's a document. else, it's a note. 17 | // do strict checking with regex 18 | if ( 19 | content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/) 20 | ) { 21 | return Ok("tweet"); 22 | } else if (content.match(/\.(pdf|doc|docx|txt|rtf|odt|md)/i)) { 23 | return Ok("document"); 24 | } else if ( 25 | content.match(/https?:\/\/(www\.)?notion\.so\/.*/) 26 | ) { 27 | return Ok("notion"); 28 | } else if ( 29 | content.match( 30 | /^(https?:\/\/)?(www\.)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(\/.*)?$/i 31 | ) 32 | ) { 33 | return Ok("page"); 34 | } else { 35 | return Ok("note"); 36 | } 37 | } catch (e) { 38 | console.error("[Decide Type Error]", e); 39 | return Err(new GetTypeError((e as Error).message, "typeDecider")); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /apps/backend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | 10 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": ["ESNext"], 9 | "types": [ 10 | "@cloudflare/workers-types/experimental", 11 | "@cloudflare/workers-types" 12 | ], 13 | "jsx": "react-jsx", 14 | "jsxImportSource": "hono/jsx" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/backend/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@mixmark-io/domino" { 2 | export function createDocument(html: string): Document; 3 | } -------------------------------------------------------------------------------- /apps/backend/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "supermemory-backend" 2 | main = "src/index.tsx" 3 | compatibility_date = "2024-10-11" 4 | compatibility_flags = [ "nodejs_compat" ] 5 | 6 | [assets] 7 | directory = "./public/" 8 | binding = "ASSETS" 9 | 10 | 11 | [observability] 12 | enabled = true 13 | 14 | [placement] 15 | mode = "smart" 16 | 17 | [ai] 18 | binding = "AI" 19 | 20 | [[workflows]] 21 | name = "content-workflow-supermemory" 22 | binding = "CONTENT_WORKFLOW" 23 | class_name = "ContentWorkflow" 24 | 25 | 26 | [[kv_namespaces]] 27 | binding= "MD_CACHE" 28 | id = "3186489f943d409a9b772d876a58a73e" 29 | preview_id = "3186489f943d409a9b772d876a58a73e" 30 | 31 | [[kv_namespaces]] 32 | binding = "ENCRYPTED_TOKENS" 33 | id = "a1f048ee14644468ad63b817b5648a31" 34 | preview_id = "a1f048ee14644468ad63b817b5648a31" 35 | 36 | [[hyperdrive]] 37 | binding = "HYPERDRIVE" 38 | id = "3a377d1b9c084e698ee201f10dfa8131" 39 | localConnectionString = "postgres://postgres:postgres@localhost:5432/supermemorylocal?sslmode=require" 40 | 41 | [[unsafe.bindings]] 42 | name = "EMAIL_LIMITER" 43 | type = "ratelimit" 44 | namespace_id = "2114284" 45 | simple = { limit = 1, period = 60 } 46 | 47 | tail_consumers = [{service = "supermemory-backend-tail"}] 48 | 49 | [[durable_objects.bindings]] 50 | name = "RATE_LIMITER" 51 | class_name = "DurableObjectRateLimiter" 52 | 53 | [[migrations]] 54 | tag = "v1" 55 | new_classes = ["DurableObjectRateLimiter"] -------------------------------------------------------------------------------- /apps/docs/README.md: -------------------------------------------------------------------------------- 1 | # Mintlify Starter Kit 2 | 3 | Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including 4 | 5 | - Guide pages 6 | - Navigation 7 | - Customizations 8 | - API Reference pages 9 | - Use of popular components 10 | 11 | ### Development 12 | 13 | Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command 14 | 15 | ``` 16 | npm i -g mintlify 17 | ``` 18 | 19 | Run the following command at the root of your documentation (where mint.json is) 20 | 21 | ``` 22 | mintlify dev 23 | ``` 24 | 25 | ### Publishing Changes 26 | 27 | Install our Github App to auto propagate changes from your repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard. 28 | 29 | #### Troubleshooting 30 | 31 | - Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. 32 | - Page loads as a 404 - Make sure you are running in a folder with `mint.json` 33 | -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/connect/connect-app.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /connect/{app} 3 | --- 4 | 5 | You may connect supermemory to other apps. 6 | when you send a GET request to the \/connect:APP?id= endpoint, you will get a redirectURL. This is a safe URL that your users can click and select the appropriate files with. Once this is done, supermemory will periodically re-fetch and make sure that the data is always fresh. 7 | 8 | As of right now, these apps are supported: 9 | - Notion 10 | 11 | -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/connect/connection-information.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /connections/{connectionId} 3 | --- 4 | 5 | Get the connection details using this endpoint. -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/memory-management/delete-delete.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: delete /delete/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/memory-management/post-add.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /add 3 | --- 4 | 5 | Add a new memory with content and metadata. 6 | 7 | Fields: 8 | 9 | `content`: string 10 | 11 | `id`: string 12 | 13 | `metadata`: Record 14 | 15 | The `content` can be of the following types: 16 | 17 | - note \/ Markdown 18 | 19 | - If it is a markdown, all the images inside `![]` image tags will automatically be parsed. 20 | 21 | - pdf 22 | 23 | - tweet 24 | 25 | - google_doc 26 | 27 | - notion_doc 28 | 29 | - webpage URL 30 | 31 | - Images and other content is also intelligently parsed in case of a webpage. 32 | 33 | 34 | The metadata provided is a JSON object. 35 | 36 | for eg. 37 | 38 | ``` json 39 | { 40 | "classId": "21412", 41 | "year": "fifth" 42 | } 43 | 44 | ``` 45 | 46 | If you wish to do exact searches, please use strings. But if you want to search in a range (time, numbers, prices), you can use numbers too. 47 | 48 | ``` json 49 | { 50 | "price": 1250 51 | } 52 | 53 | ``` 54 | 55 | More about \[metadata filtering here\]([https://docs.supermemory.ai/essentials/metadata-filtering](https://docs.supermemory.ai/essentials/metadata-filtering)) 56 | 57 | The `id` is optional. If provided, supermemory will store the same ID as your internal database. This can help for retrieval purposes. 58 | 59 | If the `id` already exists, supermemory will update it instead. -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/memory-management/put-update.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: put /update/{id} 3 | --- 4 | 5 | Update an existing memory. 6 | Please note that all existing metadata will be replaced with the new ones. 7 | You can also use the \/add endpoint along with the ID specified. -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/search/get-fastsearch.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /fastsearch 3 | --- 4 | 5 | Fast, lossy search using quantized embeddings. This can be used in case your app has text completions, or when searching fast is absolutely necessary. -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/search/post-search.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /search 3 | --- 4 | 5 | Search through documents with metadata filtering. 6 | 7 | Body: 8 | `q`: Your search query 9 | 10 | `limit`: Number of documents you want to get 11 | 12 | `filters`: Filters can be applied as `AND, OR, negate, numeric` types. You can read more about it here - \[metadata filtering here\]([https://docs.supermemory.ai/essentials/metadata-filtering](https://docs.supermemory.ai/essentials/metadata-filtering)) 13 | -------------------------------------------------------------------------------- /apps/docs/api-reference/endpoints/settings.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: put /settings 3 | --- -------------------------------------------------------------------------------- /apps/docs/changelog/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Product Updates" 3 | description: "New updates and improvements" 4 | mode: "center" 5 | --- 6 | 7 | 8 | - You can now search for memories in multiple spaces at once. 9 | - All endpoints have been updated to `/v1` for better versioning 10 | - Improved documentation and examples 11 | - Interactive [API Playground](https://docs.supermemory.ai/api-reference) 12 | -------------------------------------------------------------------------------- /apps/docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/docs/favicon.png -------------------------------------------------------------------------------- /apps/docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/docs/image.png -------------------------------------------------------------------------------- /apps/docs/images/checks-passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/docs/images/checks-passed.png -------------------------------------------------------------------------------- /apps/docs/images/setup/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/docs/images/setup/1.png -------------------------------------------------------------------------------- /apps/docs/images/setup/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/docs/images/setup/2.png -------------------------------------------------------------------------------- /apps/docs/images/setup/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/docs/images/setup/3.png -------------------------------------------------------------------------------- /apps/docs/logo/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/docs/logo/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/docs/mint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://mintlify.com/schema.json", 3 | "name": "Supermemory | Memory API for the AI era", 4 | "logo": { 5 | "dark": "/logo/dark.svg", 6 | "light": "/logo/light.svg" 7 | }, 8 | "favicon": "/favicon.png", 9 | "colors": { 10 | "primary": "#1E3A8A", 11 | "light": "#3B82F6", 12 | "dark": "#1E3A8A", 13 | "anchors": { 14 | "from": "#1E3A8A", 15 | "to": "#3B82F6" 16 | } 17 | }, 18 | "topbarLinks": [ 19 | { 20 | "name": "Support", 21 | "url": "mailto:dhravya@supermemory.com" 22 | } 23 | ], 24 | "topbarCtaButton": { 25 | "name": "Dashboard", 26 | "url": "https://supermemory.ai" 27 | }, 28 | "tabs": [ 29 | { 30 | "name": "API Reference", 31 | "url": "api-reference/endpoints", 32 | "openapi": "/openapi.json" 33 | }, 34 | { 35 | "name": "Changelog", 36 | "url": "changelog/overview" 37 | } 38 | ], 39 | "anchors": [ 40 | { 41 | "name": "Github", 42 | "icon": "github", 43 | "url": "https://git.new/memory" 44 | }, 45 | { 46 | "name": "Ask the founder", 47 | "icon": "mail", 48 | "url": "mailto:dhravya@supermemory.com" 49 | }, 50 | { 51 | "name": "X (Twitter)", 52 | "icon": "twitter", 53 | "url": "https://x.com/supermemoryai" 54 | } 55 | ], 56 | "navigation": [ 57 | { 58 | "group": "Get Started (5 mins)", 59 | "pages": ["introduction", "quickstart", "self-hosting"] 60 | }, 61 | { 62 | "group": "Essentials", 63 | "pages": ["essentials/metadata-filtering", "essentials/pricing"] 64 | }, 65 | { 66 | "group": "Changelog", 67 | "pages": ["changelog/overview"] 68 | } 69 | ], 70 | "footerSocials": { 71 | "x": "https://x.com/supermemoryai", 72 | "github": "https://github.com/supermemoryai", 73 | "linkedin": "https://linkedin.com/company/supermemoryai" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /apps/docs/snippets/snippet-intro.mdx: -------------------------------------------------------------------------------- 1 | hi -------------------------------------------------------------------------------- /apps/extension/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | dist 11 | 12 | # misc 13 | .DS_Store 14 | 15 | # local env files 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | # debug files 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # extension.js 27 | extension-env.d.ts -------------------------------------------------------------------------------- /apps/extension/README.md: -------------------------------------------------------------------------------- 1 | Extension for Supermemory 2 | 3 | helps in importing twitter bookmarks / chrome bookmarks / current tab content into Supermemory -------------------------------------------------------------------------------- /apps/extension/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "css/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/ui", 15 | "utils": "@/lib/utils", 16 | "ui": "@/ui/shadcn", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /apps/extension/css/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 47.4% 11.2%; 9 | --muted: 210 40% 96.1%; 10 | --muted-foreground: 215.4 16.3% 46.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 222.2 47.4% 11.2%; 13 | --border: 214.3 31.8% 91.4%; 14 | --input: 214.3 31.8% 91.4%; 15 | --card: 0 0% 100%; 16 | --card-foreground: 222.2 47.4% 11.2%; 17 | --primary: 222.2 47.4% 11.2%; 18 | --primary-foreground: 210 40% 98%; 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | --accent: 210 40% 96.1%; 22 | --accent-foreground: 222.2 47.4% 11.2%; 23 | --destructive: 0 100% 50%; 24 | --destructive-foreground: 210 40% 98%; 25 | --ring: 215 20.2% 65.1%; 26 | --radius: 0.5rem; 27 | } 28 | 29 | .dark { 30 | --background: 224 71% 4%; 31 | --foreground: 213 31% 91%; 32 | --muted: 223 47% 11%; 33 | --muted-foreground: 215.4 16.3% 56.9%; 34 | --accent: 216 34% 17%; 35 | --accent-foreground: 210 40% 98%; 36 | --popover: 224 71% 4%; 37 | --popover-foreground: 215 20.2% 65.1%; 38 | --border: 216 34% 17%; 39 | --input: 216 34% 17%; 40 | --card: 224 71% 4%; 41 | --card-foreground: 213 31% 91%; 42 | --primary: 210 40% 98%; 43 | --primary-foreground: 222.2 47.4% 1.2%; 44 | --secondary: 222.2 47.4% 11.2%; 45 | --secondary-foreground: 210 40% 98%; 46 | --destructive: 0 63% 31%; 47 | --destructive-foreground: 210 40% 98%; 48 | --ring: 216 34% 17%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | body { 57 | @apply font-sans antialiased bg-background text-foreground; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /apps/extension/extension-env.d.ts: -------------------------------------------------------------------------------- 1 | // Required Extension.js types for TypeScript projects. 2 | // This file is auto-generated and should not be excluded. 3 | // If you need additional types, consider creating a new *.d.ts file and 4 | // referencing it in the "include" array of your tsconfig.json file. 5 | // See https://www.typescriptlang.org/tsconfig#include for more information. 6 | /// 7 | 8 | // Polyfill types for browser.* APIs. 9 | /// 10 | -------------------------------------------------------------------------------- /apps/extension/images/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/extension/images/icon/128.png -------------------------------------------------------------------------------- /apps/extension/images/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/extension/images/icon/16.png -------------------------------------------------------------------------------- /apps/extension/images/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/extension/images/icon/32.png -------------------------------------------------------------------------------- /apps/extension/images/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/extension/images/icon/48.png -------------------------------------------------------------------------------- /apps/extension/images/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/extension/images/icon/favicon.ico -------------------------------------------------------------------------------- /apps/extension/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | // getBaseURL function that checks if we are in dev or prod and returns the correct baseURL 9 | export async function getBaseURL() { 10 | if (typeof chrome === "undefined") { 11 | console.error("chrome is undefined: only run in background script"); 12 | throw new Error("chrome is undefined: only run in background script"); 13 | } 14 | const extensionInfo = await chrome.management.getSelf(); 15 | console.info(`Running in ${extensionInfo.installType} mode`); 16 | // If we're in development mode, the id will contain 'development' 17 | return extensionInfo.installType.includes("development") 18 | ? "http://localhost:3000" 19 | : "https://supermemory.ai"; 20 | } 21 | -------------------------------------------------------------------------------- /apps/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/chrome-manifest.json", 3 | "manifest_version": 3, 4 | "version": "5.0.200", 5 | "homepage_url": "https://supermemory.ai", 6 | "name": "Supermemory", 7 | "description": "An extension for https://supermemory.ai - an AI hub for all your knowledge.", 8 | "author": "Dhravya Shah", 9 | "permissions": [ 10 | "activeTab", 11 | "scripting", 12 | "tabs", 13 | "management", 14 | "webRequest", 15 | "storage", 16 | "bookmarks" 17 | ], 18 | "host_permissions": ["*://*/*"], 19 | "background": { 20 | "service_worker": "src/background.ts" 21 | }, 22 | "action": {}, 23 | "externally_connectable": { 24 | "matches": [ 25 | "http://localhost:3000/*", 26 | "https://supermemory.ai/*", 27 | "https://beta.supermemory.ai/*", 28 | "http://supermemory.com/*" 29 | ] 30 | }, 31 | "icons": { 32 | "16": "images/icon/16.png", 33 | "32": "images/icon/32.png", 34 | "48": "images/icon/48.png", 35 | "128": "images/icon/128.png" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supermemory-extension", 3 | "version": "1.0.0", 4 | "description": "A memory enhancement extension", 5 | "type": "module", 6 | "scripts": { 7 | "dev:css": "npx tailwindcss -i css/globals.css -o public/globals.css --watch", 8 | "dev:extension": "extension dev", 9 | "dev": "concurrently \"npm:dev:css\" \"npm:dev:extension\"", 10 | "build:css": "npx tailwindcss -i css/globals.css -o public/globals.css", 11 | "build:extension": "extension build", 12 | "build": "npm run build:css && npm run build:extension", 13 | "start": "npm run build && extension start", 14 | "zip": "zip -r supermemory-extension.zip ./dist/chrome/*" 15 | }, 16 | "dependencies": { 17 | "@mozilla/readability": "^0.5.0", 18 | "@radix-ui/react-dialog": "^1.1.2", 19 | "@radix-ui/react-popover": "^1.1.2", 20 | "@radix-ui/react-slot": "^1.1.0", 21 | "@radix-ui/react-toast": "^1.2.2", 22 | "@types/react": "^18.3.1", 23 | "@types/react-dom": "^18.3.1", 24 | "class-variance-authority": "^0.7.1", 25 | "clsx": "^2.1.1", 26 | "cmdk": "1.0.0", 27 | "lucide-react": "^0.462.0", 28 | "react": "^18.3.1", 29 | "react-dom": "^18.3.1", 30 | "tailwind-merge": "^2.5.5", 31 | "tailwindcss-animate": "^1.0.7" 32 | }, 33 | "devDependencies": { 34 | "@types/chrome": "^0.0.260", 35 | "extension": "latest", 36 | "typescript": "^5.3.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/extension/public/logo.svg: -------------------------------------------------------------------------------- 1 | 7 | 11 | -------------------------------------------------------------------------------- /apps/extension/src/twitter.constants.ts: -------------------------------------------------------------------------------- 1 | export const features = { 2 | graphql_timeline_v2_bookmark_timeline: true, 3 | rweb_tipjar_consumption_enabled: true, 4 | responsive_web_graphql_exclude_directive_enabled: true, 5 | verified_phone_label_enabled: false, 6 | creator_subscriptions_tweet_preview_api_enabled: true, 7 | responsive_web_graphql_timeline_navigation_enabled: true, 8 | responsive_web_graphql_skip_user_profile_image_extensions_enabled: false, 9 | communities_web_enable_tweet_community_results_fetch: true, 10 | c9s_tweet_anatomy_moderator_badge_enabled: true, 11 | articles_preview_enabled: true, 12 | tweetypie_unmention_optimization_enabled: true, 13 | responsive_web_edit_tweet_api_enabled: true, 14 | graphql_is_translatable_rweb_tweet_is_translatable_enabled: true, 15 | view_counts_everywhere_api_enabled: true, 16 | longform_notetweets_consumption_enabled: true, 17 | responsive_web_twitter_article_tweet_consumption_enabled: true, 18 | tweet_awards_web_tipping_enabled: false, 19 | creator_subscriptions_quote_tweet_preview_enabled: false, 20 | freedom_of_speech_not_reach_fetch_enabled: true, 21 | standardized_nudges_misinfo: true, 22 | tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true, 23 | rweb_video_timestamps_enabled: true, 24 | longform_notetweets_rich_text_read_enabled: true, 25 | longform_notetweets_inline_media_enabled: true, 26 | responsive_web_enhance_cards_enabled: false, 27 | profile_label_improvements_pcf_label_in_post_enabled: false, 28 | premium_content_api_read_enabled: true, 29 | responsive_web_grok_analyze_button_fetch_trends_enabled: true, 30 | responsive_web_grok_analyze_post_followups_enabled: true, 31 | responsive_web_grok_share_attachment_enabled: true, 32 | }; -------------------------------------------------------------------------------- /apps/extension/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | darkMode: ["class"], 4 | content: ["./src/**/*.{ts,tsx}", "./ui/**/*.{ts,tsx}"], 5 | theme: { 6 | extend: { 7 | colors: { 8 | border: "hsl(var(--border))", 9 | input: "hsl(var(--input))", 10 | ring: "hsl(var(--ring))", 11 | background: "hsl(var(--background))", 12 | foreground: "hsl(var(--foreground))", 13 | primary: { 14 | DEFAULT: "hsl(var(--primary))", 15 | foreground: "hsl(var(--primary-foreground))", 16 | }, 17 | secondary: { 18 | DEFAULT: "hsl(var(--secondary))", 19 | foreground: "hsl(var(--secondary-foreground))", 20 | }, 21 | destructive: { 22 | DEFAULT: "hsl(var(--destructive))", 23 | foreground: "hsl(var(--destructive-foreground))", 24 | }, 25 | muted: { 26 | DEFAULT: "hsl(var(--muted))", 27 | foreground: "hsl(var(--muted-foreground))", 28 | }, 29 | accent: { 30 | DEFAULT: "hsl(var(--accent))", 31 | foreground: "hsl(var(--accent-foreground))", 32 | }, 33 | popover: { 34 | DEFAULT: "hsl(var(--popover))", 35 | foreground: "hsl(var(--popover-foreground))", 36 | }, 37 | card: { 38 | DEFAULT: "hsl(var(--card))", 39 | foreground: "hsl(var(--card-foreground))", 40 | }, 41 | }, 42 | borderRadius: { 43 | lg: `var(--radius)`, 44 | md: `calc(var(--radius) - 2px)`, 45 | sm: "calc(var(--radius) - 4px)", 46 | }, 47 | }, 48 | }, 49 | plugins: [import("tailwindcss-animate")], 50 | } 51 | -------------------------------------------------------------------------------- /apps/extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "preserve", 14 | "types": ["chrome"], 15 | "baseUrl": ".", 16 | "paths": { 17 | "@/*": ["./*"] 18 | } 19 | }, 20 | "include": ["src/**/*.ts", "src/**/*.tsx", "ui/**/*.tsx", "src/content.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /apps/extension/ui/shadcn/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /apps/extension/ui/shadcn/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as PopoverPrimitive from "@radix-ui/react-popover" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Popover = PopoverPrimitive.Root 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger 9 | 10 | const PopoverAnchor = PopoverPrimitive.Anchor 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )) 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 30 | 31 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } 32 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | .dev.vars 7 | 8 | .wrangler 9 | 10 | .env.* 11 | .env 12 | .dev.vars 13 | *.vars 14 | bun.lockb 15 | 16 | # Million Lint 17 | .million 18 | database.sql 19 | -------------------------------------------------------------------------------- /apps/web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": false, 4 | "semi": true, 5 | "useTabs": true, 6 | "plugins": ["prettier-plugin-tailwindcss", "@trivago/prettier-plugin-sort-imports"], 7 | "importOrder": [ 8 | "^react", 9 | "^@remix-run/(.*)$", 10 | "^@ui/(.*)$", 11 | "^components/(.*)$", 12 | "^lib/(.*)$", 13 | "^[./]", 14 | "" 15 | ], 16 | "importOrderSeparation": true, 17 | "importOrderSortSpecifiers": true, 18 | "importOrderGroupNamespaceSpecifiers": true 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix + Cloudflare! 2 | 3 | - 📖 [Remix docs](https://remix.run/docs) 4 | - 📖 [Remix Cloudflare docs](https://remix.run/guides/vite#cloudflare) 5 | 6 | ## Development 7 | 8 | Run the dev server: 9 | 10 | ```sh 11 | npm run dev 12 | ``` 13 | 14 | To run Wrangler: 15 | 16 | ```sh 17 | npm run build 18 | npm run start 19 | ``` 20 | 21 | ## Typegen 22 | 23 | Generate types for your Cloudflare bindings in `wrangler.toml`: 24 | 25 | ```sh 26 | npm run typegen 27 | ``` 28 | 29 | You will need to rerun typegen whenever you make changes to `wrangler.toml`. 30 | 31 | ## Deployment 32 | 33 | First, build your app for production: 34 | 35 | ```sh 36 | npm run build 37 | ``` 38 | 39 | Then, deploy your app to Cloudflare Pages: 40 | 41 | ```sh 42 | npm run deploy 43 | ``` 44 | 45 | ## Styling 46 | 47 | This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information. 48 | -------------------------------------------------------------------------------- /apps/web/app/components/Histories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Clock } from "lucide-react"; 4 | 5 | function Histories({ 6 | historyMessages, 7 | }: { 8 | historyMessages: { chatId: string; firstMessage: string }[]; 9 | }) { 10 | return ( 11 |
12 |
13 | 14 | Recently Asked 15 |
16 |
17 | {historyMessages.map((history) => ( 18 | 23 | 31 | 32 | 33 | 34 | {history.firstMessage} 35 | 36 | ))} 37 |
38 |
39 | ); 40 | } 41 | 42 | export default Histories; 43 | -------------------------------------------------------------------------------- /apps/web/app/components/Landing/index.tsx: -------------------------------------------------------------------------------- 1 | import Feature2 from "./Feature"; 2 | import Footer from "./Footer"; 3 | import Hero from "./Hero"; 4 | import Note from "./Note"; 5 | import Private from "./Private"; 6 | 7 | export default function Landing() { 8 | return ( 9 |
10 | 11 | 12 | 13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/app/components/Suggestions.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { cn } from "~/lib/utils"; 3 | import { Button } from "~/components/ui/button"; 4 | import { CircleHelp } from "lucide-react"; 5 | 6 | interface SuggestionProps { 7 | text: string; 8 | onClick: () => void; 9 | } 10 | 11 | const SingleSuggestion: React.FC = ({ text, onClick }) => { 12 | return ( 13 | 21 | ); 22 | }; 23 | 24 | interface SuggestionsProps { 25 | items: string[]; 26 | onSelect: (item: string) => void; 27 | } 28 | 29 | const Suggestions: React.FC = ({ items, onSelect }) => { 30 | return ( 31 |
32 | {items?.map((item, index) => ( 33 | onSelect(item)} 37 | /> 38 | ))} 39 |
40 | ); 41 | }; 42 | 43 | export default Suggestions; 44 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plate-editor.tsx: -------------------------------------------------------------------------------- 1 | import { DndProvider } from "react-dnd"; 2 | import { HTML5Backend } from "react-dnd-html5-backend"; 3 | 4 | import { Plate } from "@udecode/plate-common/react"; 5 | import { useCreateEditor } from "~/components/editor/use-create-editor"; 6 | import { Editor, EditorContainer } from "~/components/plate-ui/editor"; 7 | 8 | export function PlateEditor() { 9 | const editor = useCreateEditor(); 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/align-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { AlignPlugin } from '@udecode/plate-alignment/react'; 4 | import { ParagraphPlugin } from '@udecode/plate-common/react'; 5 | import { HEADING_LEVELS } from '@udecode/plate-heading'; 6 | import { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react'; 7 | 8 | export const alignPlugin = AlignPlugin.extend({ 9 | inject: { 10 | targetPlugins: [ 11 | ParagraphPlugin.key, 12 | ...HEADING_LEVELS, 13 | MediaEmbedPlugin.key, 14 | ImagePlugin.key, 15 | ], 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/basic-nodes-plugins.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { BasicMarksPlugin } from '@udecode/plate-basic-marks/react'; 4 | import { BlockquotePlugin } from '@udecode/plate-block-quote/react'; 5 | import { CodeBlockPlugin } from '@udecode/plate-code-block/react'; 6 | import { HeadingPlugin } from '@udecode/plate-heading/react'; 7 | import Prism from 'prismjs'; 8 | 9 | export const basicNodesPlugins = [ 10 | HeadingPlugin.configure({ options: { levels: 3 } }), 11 | BlockquotePlugin, 12 | CodeBlockPlugin.configure({ options: { prism: Prism } }), 13 | BasicMarksPlugin, 14 | ] as const; 15 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/block-menu-plugins.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { BlockMenuPlugin } from '@udecode/plate-selection/react'; 4 | 5 | import { BlockContextMenu } from '~/components/plate-ui/block-context-menu'; 6 | 7 | import { blockSelectionPlugins } from './block-selection-plugins'; 8 | 9 | export const blockMenuPlugins = [ 10 | ...blockSelectionPlugins, 11 | BlockMenuPlugin.configure({ 12 | render: { aboveEditable: BlockContextMenu }, 13 | }), 14 | ] as const; 15 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/block-selection-plugins.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { BlockSelectionPlugin } from '@udecode/plate-selection/react'; 4 | 5 | export const blockSelectionPlugins = [ 6 | BlockSelectionPlugin.configure({ 7 | inject: { 8 | excludeBelowPlugins: ['tr'], 9 | excludePlugins: ['table', 'code_line', 'column_group', 'column'], 10 | }, 11 | options: { 12 | areaOptions: { 13 | behaviour: { 14 | scrolling: { 15 | speedDivider: 1.5, 16 | }, 17 | startThreshold: 4, 18 | }, 19 | }, 20 | enableContextMenu: true, 21 | }, 22 | }), 23 | ] as const; 24 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/comments-plugin.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { CommentsPlugin } from '@udecode/plate-comments/react'; 4 | 5 | import { CommentsPopover } from '~/components/plate-ui/comments-popover'; 6 | 7 | export const commentsPlugin = CommentsPlugin.configure({ 8 | options: { 9 | myUserId: '1', 10 | users: { 11 | 1: { 12 | id: '1', 13 | avatarUrl: 'https://avatars.githubusercontent.com/u/19695832?s=96&v=4', 14 | name: 'zbeyens', 15 | }, 16 | 2: { 17 | id: '2', 18 | avatarUrl: 'https://avatars.githubusercontent.com/u/4272090?v=4', 19 | name: '12joan', 20 | }, 21 | }, 22 | }, 23 | render: { afterEditable: () => }, 24 | }); 25 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/delete-plugins.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react'; 4 | import { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react'; 5 | import { DeletePlugin, SelectOnBackspacePlugin } from '@udecode/plate-select'; 6 | 7 | export const deletePlugins = [ 8 | SelectOnBackspacePlugin.configure({ 9 | options: { 10 | query: { 11 | allow: [ 12 | ImagePlugin.key, 13 | MediaEmbedPlugin.key, 14 | HorizontalRulePlugin.key, 15 | ], 16 | }, 17 | }, 18 | }), 19 | DeletePlugin, 20 | ] as const; 21 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/dnd-plugins.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { DndPlugin } from '@udecode/plate-dnd'; 4 | import { ImagePlugin } from '@udecode/plate-media/react'; 5 | import { NodeIdPlugin } from '@udecode/plate-node-id'; 6 | 7 | export const dndPlugins = [ 8 | NodeIdPlugin, 9 | DndPlugin.configure({ 10 | options: { 11 | enableScroller: true, 12 | onDropFiles: ({ dragItem, editor, target }) => { 13 | editor 14 | .getTransforms(ImagePlugin) 15 | .insert.imageFromFiles(dragItem.files, { 16 | at: target, 17 | nextBlock: false, 18 | }); 19 | }, 20 | }, 21 | }), 22 | ] as const; 23 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/exit-break-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ExitBreakPlugin } from '@udecode/plate-break/react'; 4 | import { HEADING_LEVELS } from '@udecode/plate-heading'; 5 | 6 | export const exitBreakPlugin = ExitBreakPlugin.configure({ 7 | options: { 8 | rules: [ 9 | { 10 | hotkey: 'mod+enter', 11 | }, 12 | { 13 | before: true, 14 | hotkey: 'mod+shift+enter', 15 | }, 16 | { 17 | hotkey: 'enter', 18 | level: 1, 19 | query: { 20 | allow: HEADING_LEVELS, 21 | end: true, 22 | start: true, 23 | }, 24 | relative: true, 25 | }, 26 | ], 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/fixed-toolbar-plugin.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { createPlatePlugin } from '@udecode/plate-common/react'; 4 | 5 | import { FixedToolbar } from '~/components/plate-ui/fixed-toolbar'; 6 | import { FixedToolbarButtons } from '~/components/plate-ui/fixed-toolbar-buttons'; 7 | 8 | export const FixedToolbarPlugin = createPlatePlugin({ 9 | key: 'fixed-toolbar', 10 | render: { 11 | beforeEditable: () => ( 12 | 13 | 14 | 15 | ), 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/floating-toolbar-plugin.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { createPlatePlugin } from '@udecode/plate-common/react'; 4 | 5 | import { FloatingToolbar } from '~/components/plate-ui/floating-toolbar'; 6 | import { FloatingToolbarButtons } from '~/components/plate-ui/floating-toolbar-buttons'; 7 | 8 | export const FloatingToolbarPlugin = createPlatePlugin({ 9 | key: 'floating-toolbar', 10 | render: { 11 | afterEditable: () => ( 12 | 13 | 14 | 15 | ), 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/indent-list-plugins.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { BlockquotePlugin } from '@udecode/plate-block-quote/react'; 4 | import { CodeBlockPlugin } from '@udecode/plate-code-block/react'; 5 | import { ParagraphPlugin } from '@udecode/plate-common/react'; 6 | import { HEADING_LEVELS } from '@udecode/plate-heading'; 7 | import { IndentPlugin } from '@udecode/plate-indent/react'; 8 | import { IndentListPlugin } from '@udecode/plate-indent-list/react'; 9 | import { TogglePlugin } from '@udecode/plate-toggle/react'; 10 | 11 | import { 12 | FireLiComponent, 13 | FireMarker, 14 | } from '~/components/plate-ui/indent-fire-marker'; 15 | import { 16 | TodoLi, 17 | TodoMarker, 18 | } from '~/components/plate-ui/indent-todo-marker'; 19 | 20 | export const indentListPlugins = [ 21 | IndentPlugin.extend({ 22 | inject: { 23 | targetPlugins: [ 24 | ParagraphPlugin.key, 25 | ...HEADING_LEVELS, 26 | BlockquotePlugin.key, 27 | CodeBlockPlugin.key, 28 | TogglePlugin.key, 29 | ], 30 | }, 31 | }), 32 | IndentListPlugin.extend({ 33 | inject: { 34 | targetPlugins: [ 35 | ParagraphPlugin.key, 36 | ...HEADING_LEVELS, 37 | BlockquotePlugin.key, 38 | CodeBlockPlugin.key, 39 | TogglePlugin.key, 40 | ], 41 | }, 42 | options: { 43 | listStyleTypes: { 44 | fire: { 45 | liComponent: FireLiComponent, 46 | markerComponent: FireMarker, 47 | type: 'fire', 48 | }, 49 | todo: { 50 | liComponent: TodoLi, 51 | markerComponent: TodoMarker, 52 | type: 'todo', 53 | }, 54 | }, 55 | }, 56 | }), 57 | ]; 58 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/line-height-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ParagraphPlugin } from '@udecode/plate-common/react'; 4 | import { HEADING_LEVELS } from '@udecode/plate-heading'; 5 | import { LineHeightPlugin } from '@udecode/plate-line-height/react'; 6 | 7 | export const lineHeightPlugin = LineHeightPlugin.configure({ 8 | inject: { 9 | nodeProps: { 10 | defaultNodeValue: 1.5, 11 | validNodeValues: [1, 1.2, 1.5, 2, 3], 12 | }, 13 | targetPlugins: [ParagraphPlugin.key, ...HEADING_LEVELS], 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/link-plugin.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { LinkPlugin } from '@udecode/plate-link/react'; 4 | 5 | import { LinkFloatingToolbar } from '~/components/plate-ui/link-floating-toolbar'; 6 | 7 | export const linkPlugin = LinkPlugin.extend({ 8 | render: { afterEditable: () => }, 9 | }); 10 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/media-plugins.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { CaptionPlugin } from '@udecode/plate-caption/react'; 4 | import { 5 | AudioPlugin, 6 | FilePlugin, 7 | ImagePlugin, 8 | MediaEmbedPlugin, 9 | PlaceholderPlugin, 10 | VideoPlugin, 11 | } from '@udecode/plate-media/react'; 12 | 13 | import { ImagePreview } from '~/components/plate-ui/image-preview'; 14 | 15 | export const mediaPlugins = [ 16 | PlaceholderPlugin, 17 | ImagePlugin.extend({ 18 | options: { 19 | disableUploadInsert: true, 20 | }, 21 | render: { afterEditable: ImagePreview }, 22 | }), 23 | MediaEmbedPlugin, 24 | VideoPlugin, 25 | AudioPlugin, 26 | FilePlugin, 27 | CaptionPlugin.configure({ 28 | options: { plugins: [ImagePlugin, MediaEmbedPlugin] }, 29 | }), 30 | ] as const; 31 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/mention-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { MentionPlugin } from '@udecode/plate-mention/react'; 4 | 5 | export const mentionPlugin = MentionPlugin.configure({ 6 | options: { triggerPreviousCharPattern: /^$|^[\s"']$/ }, 7 | }); 8 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/reset-block-type-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { BlockquotePlugin } from '@udecode/plate-block-quote/react'; 4 | import { CalloutPlugin } from '@udecode/plate-callout/react'; 5 | import { 6 | isCodeBlockEmpty, 7 | isSelectionAtCodeBlockStart, 8 | unwrapCodeBlock, 9 | } from '@udecode/plate-code-block'; 10 | import { CodeBlockPlugin } from '@udecode/plate-code-block/react'; 11 | import { 12 | isBlockAboveEmpty, 13 | isSelectionAtBlockStart, 14 | } from '@udecode/plate-common'; 15 | import { ParagraphPlugin } from '@udecode/plate-common/react'; 16 | import { HEADING_LEVELS } from '@udecode/plate-heading'; 17 | import { INDENT_LIST_KEYS, ListStyleType } from '@udecode/plate-indent-list'; 18 | import { ResetNodePlugin } from '@udecode/plate-reset-node/react'; 19 | 20 | const resetBlockTypesCommonRule = { 21 | defaultType: ParagraphPlugin.key, 22 | types: [ 23 | ...HEADING_LEVELS, 24 | BlockquotePlugin.key, 25 | INDENT_LIST_KEYS.todo, 26 | ListStyleType.Disc, 27 | ListStyleType.Decimal, 28 | CalloutPlugin.key, 29 | ], 30 | }; 31 | 32 | const resetBlockTypesCodeBlockRule = { 33 | defaultType: ParagraphPlugin.key, 34 | types: [CodeBlockPlugin.key], 35 | onReset: unwrapCodeBlock, 36 | }; 37 | 38 | export const resetBlockTypePlugin = ResetNodePlugin.configure({ 39 | options: { 40 | rules: [ 41 | { 42 | ...resetBlockTypesCommonRule, 43 | hotkey: 'Enter', 44 | predicate: isBlockAboveEmpty, 45 | }, 46 | { 47 | ...resetBlockTypesCommonRule, 48 | hotkey: 'Backspace', 49 | predicate: isSelectionAtBlockStart, 50 | }, 51 | { 52 | ...resetBlockTypesCodeBlockRule, 53 | hotkey: 'Enter', 54 | predicate: isCodeBlockEmpty, 55 | }, 56 | { 57 | ...resetBlockTypesCodeBlockRule, 58 | hotkey: 'Backspace', 59 | predicate: isSelectionAtCodeBlockStart, 60 | }, 61 | ], 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/soft-break-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { BlockquotePlugin } from '@udecode/plate-block-quote/react'; 4 | import { SoftBreakPlugin } from '@udecode/plate-break/react'; 5 | import { CalloutPlugin } from '@udecode/plate-callout/react'; 6 | import { CodeBlockPlugin } from '@udecode/plate-code-block/react'; 7 | import { 8 | TableCellHeaderPlugin, 9 | TableCellPlugin, 10 | } from '@udecode/plate-table/react'; 11 | 12 | export const softBreakPlugin = SoftBreakPlugin.configure({ 13 | options: { 14 | rules: [ 15 | { hotkey: 'shift+enter' }, 16 | { 17 | hotkey: 'enter', 18 | query: { 19 | allow: [ 20 | CodeBlockPlugin.key, 21 | BlockquotePlugin.key, 22 | TableCellPlugin.key, 23 | TableCellHeaderPlugin.key, 24 | CalloutPlugin.key, 25 | ], 26 | }, 27 | }, 28 | ], 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/table-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { TablePlugin } from '@udecode/plate-table/react'; 4 | 5 | export const tablePlugin = TablePlugin.configure({ 6 | options: { 7 | enableMerging: true, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /apps/web/app/components/editor/plugins/toc-plugin.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { TocPlugin } from '@udecode/plate-heading/react'; 4 | 5 | export const tocPlugin = TocPlugin.configure({ 6 | options: { 7 | // isScroll: true, 8 | topOffset: 80, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /apps/web/app/components/gradients/gradient1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supermemoryai/supermemory/5b35e3ea7895a2b6bc95d0031c078707bdca20fa/apps/web/app/components/gradients/gradient1.png -------------------------------------------------------------------------------- /apps/web/app/components/markdown/codeblock.tsx: -------------------------------------------------------------------------------- 1 | import type { CodeToHtmlOptions } from "@llm-ui/code"; 2 | import { allLangs, allLangsAlias, loadHighlighter, useCodeBlockToHtml } from "@llm-ui/code"; 3 | import { type LLMOutputComponent } from "@llm-ui/react"; 4 | import parseHtml from "html-react-parser"; 5 | import { getHighlighterCore } from "shiki/core"; 6 | import { bundledLanguagesInfo } from "shiki/langs"; 7 | // WARNING: Importing bundledThemes increases your bundle size 8 | // see: https://llm-ui.com/docs/blocks/code#bundle-size 9 | import { bundledThemes } from "shiki/themes"; 10 | import getWasm from "shiki/wasm"; 11 | 12 | const highlighter = loadHighlighter( 13 | getHighlighterCore({ 14 | langs: allLangs(bundledLanguagesInfo), 15 | langAlias: allLangsAlias(bundledLanguagesInfo), 16 | themes: Object.values(bundledThemes), 17 | loadWasm: getWasm, 18 | }), 19 | ); 20 | 21 | const codeToHtmlOptions: CodeToHtmlOptions = { 22 | theme: "github-dark", 23 | }; 24 | 25 | export const CodeBlock: LLMOutputComponent = ({ blockMatch }) => { 26 | const { html, code } = useCodeBlockToHtml({ 27 | markdownCodeBlock: blockMatch.output, 28 | highlighter, 29 | codeToHtmlOptions, 30 | }); 31 | if (!html) { 32 | // fallback to
 if Shiki is not loaded yet
33 | 		return (
34 | 			
35 | 				{code}
36 | 			
37 | ); 38 | } 39 | return <>{parseHtml(html)}; 40 | }; 41 | -------------------------------------------------------------------------------- /apps/web/app/components/markdown/renderer.tsx: -------------------------------------------------------------------------------- 1 | import ReactMarkdown from "react-markdown"; 2 | import remarkGfm from "remark-gfm"; 3 | import { type LLMOutputComponent } from "@llm-ui/react"; 4 | 5 | 6 | // Customize this component with your own styling 7 | export const MarkdownComponent: LLMOutputComponent = ({ blockMatch }) => { 8 | const markdown = blockMatch.output; 9 | return {markdown}; 10 | }; -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/ai-chat-editor.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { memo } from 'react'; 4 | 5 | import { AIChatPlugin, useLastAssistantMessage } from '@udecode/plate-ai/react'; 6 | import { 7 | type PlateEditor, 8 | Plate, 9 | useEditorPlugin, 10 | } from '@udecode/plate-common/react'; 11 | import { deserializeMd } from '@udecode/plate-markdown'; 12 | 13 | import { Editor } from './editor'; 14 | 15 | export const AIChatEditor = memo( 16 | ({ 17 | aiEditorRef, 18 | }: { 19 | aiEditorRef: React.MutableRefObject; 20 | }) => { 21 | const { getOptions } = useEditorPlugin(AIChatPlugin); 22 | const lastAssistantMessage = useLastAssistantMessage(); 23 | const content = lastAssistantMessage?.content ?? ''; 24 | 25 | const aiEditor = React.useMemo(() => { 26 | const editor = getOptions().createAIEditor(); 27 | 28 | const fragment = deserializeMd(editor, content); 29 | editor.children = 30 | fragment.length > 0 ? fragment : editor.api.create.value(); 31 | 32 | return editor; 33 | // eslint-disable-next-line react-hooks/exhaustive-deps 34 | }, []); 35 | 36 | React.useEffect(() => { 37 | if (aiEditor && content) { 38 | aiEditorRef.current = aiEditor; 39 | 40 | setTimeout(() => { 41 | aiEditor.tf.setValue(deserializeMd(aiEditor, content)); 42 | }, 0); 43 | } 44 | }, [aiEditor, aiEditorRef, content]); 45 | 46 | if (!content) return null; 47 | 48 | return ( 49 | 50 | 51 | 52 | ); 53 | } 54 | ); 55 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/ai-leaf.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { PlateLeaf } from '@udecode/plate-common/react'; 7 | 8 | export const AILeaf = withRef( 9 | ({ children, className, ...props }, ref) => { 10 | return ( 11 | 20 | {children} 21 | 22 | ); 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/ai-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { AIChatPlugin } from '@udecode/plate-ai/react'; 7 | import { useEditorPlugin } from '@udecode/plate-common/react'; 8 | 9 | import { ToolbarButton } from './toolbar'; 10 | 11 | export const AIToolbarButton = withRef( 12 | ({ children, ...rest }, ref) => { 13 | const { api } = useEditorPlugin(AIChatPlugin); 14 | 15 | return ( 16 | { 20 | api.aiChat.show(); 21 | }} 22 | onMouseDown={(e) => { 23 | e.preventDefault(); 24 | }} 25 | > 26 | {children} 27 | 28 | ); 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as AvatarPrimitive from '@radix-ui/react-avatar'; 4 | import { withCn } from '@udecode/cn'; 5 | 6 | export const Avatar = withCn( 7 | AvatarPrimitive.Root, 8 | 'relative flex size-10 shrink-0 overflow-hidden rounded-full' 9 | ); 10 | 11 | export const AvatarImage = withCn( 12 | AvatarPrimitive.Image, 13 | 'aspect-square size-full' 14 | ); 15 | 16 | export const AvatarFallback = withCn( 17 | AvatarPrimitive.Fallback, 18 | 'flex size-full items-center justify-center rounded-full bg-muted' 19 | ); 20 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/block-selection.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { cn } from '@udecode/cn'; 4 | import { useBlockSelected } from '@udecode/plate-selection/react'; 5 | import { type VariantProps, cva } from 'class-variance-authority'; 6 | 7 | export const blockSelectionVariants = cva( 8 | 'pointer-events-none absolute inset-0 z-[1] bg-brand/[.13] transition-opacity', 9 | { 10 | defaultVariants: { 11 | active: true, 12 | }, 13 | variants: { 14 | active: { 15 | false: 'opacity-0', 16 | true: 'opacity-100', 17 | }, 18 | }, 19 | } 20 | ); 21 | 22 | export function BlockSelection({ 23 | className, 24 | ...props 25 | }: React.HTMLAttributes & 26 | VariantProps) { 27 | const isBlockSelected = useBlockSelected(); 28 | 29 | if (!isBlockSelected) return null; 30 | 31 | return ( 32 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/blockquote-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | 7 | import { PlateElement } from './plate-element'; 8 | 9 | export const BlockquoteElement = withRef( 10 | ({ children, className, ...props }, ref) => { 11 | return ( 12 | 18 | {children} 19 | 20 | ); 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/caption.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | cn, 5 | createPrimitiveComponent, 6 | withCn, 7 | withVariants, 8 | } from '@udecode/cn'; 9 | import { 10 | Caption as CaptionPrimitive, 11 | CaptionTextarea as CaptionTextareaPrimitive, 12 | useCaptionButton, 13 | useCaptionButtonState, 14 | } from '@udecode/plate-caption/react'; 15 | import { cva } from 'class-variance-authority'; 16 | 17 | import { Button } from './button'; 18 | 19 | const captionVariants = cva('max-w-full', { 20 | defaultVariants: { 21 | align: 'center', 22 | }, 23 | variants: { 24 | align: { 25 | center: 'mx-auto', 26 | left: 'mr-auto', 27 | right: 'ml-auto', 28 | }, 29 | }, 30 | }); 31 | 32 | export const Caption = withVariants(CaptionPrimitive, captionVariants, [ 33 | 'align', 34 | ]); 35 | 36 | export const CaptionTextarea = withCn( 37 | CaptionTextareaPrimitive, 38 | cn( 39 | 'mt-2 w-full resize-none border-none bg-inherit p-0 font-[inherit] text-inherit', 40 | 'focus:outline-none focus:[&::placeholder]:opacity-0', 41 | 'text-center print:placeholder:text-transparent' 42 | ) 43 | ); 44 | 45 | export const CaptionButton = createPrimitiveComponent(Button)({ 46 | propsHook: useCaptionButton, 47 | stateHook: useCaptionButtonState, 48 | }); 49 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | 5 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; 6 | import { cn, withRef } from '@udecode/cn'; 7 | import { Check } from 'lucide-react'; 8 | 9 | export const Checkbox = withRef( 10 | ({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | ) 26 | ); 27 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/code-block-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { useCodeBlockElementState } from '@udecode/plate-code-block/react'; 7 | 8 | import { CodeBlockCombobox } from './code-block-combobox'; 9 | import { PlateElement } from './plate-element'; 10 | 11 | import './code-block-element.css'; 12 | 13 | export const CodeBlockElement = withRef( 14 | ({ children, className, ...props }, ref) => { 15 | const { element } = props; 16 | const state = useCodeBlockElementState({ element }); 17 | 18 | return ( 19 | 24 |
25 |           {children}
26 |         
27 | 28 | {state.syntax && ( 29 |
33 | 34 |
35 | )} 36 |
37 | ); 38 | } 39 | ); 40 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/code-leaf.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { PlateLeaf } from '@udecode/plate-common/react'; 7 | 8 | export const CodeLeaf = withRef( 9 | ({ children, className, ...props }, ref) => { 10 | return ( 11 | 20 | {children} 21 | 22 | ); 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/code-line-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | 4 | import { withRef } from '@udecode/cn'; 5 | 6 | import { PlateElement } from './plate-element'; 7 | 8 | export const CodeLineElement = withRef((props, ref) => ( 9 | 10 | )); 11 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/code-syntax-leaf.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | 4 | import { withRef } from '@udecode/cn'; 5 | import { useCodeSyntaxLeaf } from '@udecode/plate-code-block/react'; 6 | import { PlateLeaf } from '@udecode/plate-common/react'; 7 | 8 | export const CodeSyntaxLeaf = withRef( 9 | ({ children, ...props }, ref) => { 10 | const { leaf } = props; 11 | 12 | const { tokenProps } = useCodeSyntaxLeaf({ leaf }); 13 | 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | ); 21 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/color-dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; 4 | 5 | import { 6 | useColorDropdownMenu, 7 | useColorDropdownMenuState, 8 | } from '@udecode/plate-font/react'; 9 | 10 | import { DEFAULT_COLORS, DEFAULT_CUSTOM_COLORS } from './color-constants'; 11 | import { ColorPicker } from './color-picker'; 12 | import { 13 | DropdownMenu, 14 | DropdownMenuContent, 15 | DropdownMenuTrigger, 16 | } from './dropdown-menu'; 17 | import { ToolbarButton } from './toolbar'; 18 | 19 | type ColorDropdownMenuProps = { 20 | nodeType: string; 21 | tooltip?: string; 22 | } & DropdownMenuProps; 23 | 24 | export function ColorDropdownMenu({ 25 | children, 26 | nodeType, 27 | tooltip, 28 | }: ColorDropdownMenuProps) { 29 | const state = useColorDropdownMenuState({ 30 | closeOnSelect: true, 31 | colors: DEFAULT_COLORS, 32 | customColors: DEFAULT_CUSTOM_COLORS, 33 | nodeType, 34 | }); 35 | 36 | const { buttonProps, menuProps } = useColorDropdownMenu(state); 37 | 38 | return ( 39 | 40 | 41 | 42 | {children} 43 | 44 | 45 | 46 | 47 | 55 | 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/color-input.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { useComposedRef } from '@udecode/plate-common/react'; 7 | import { useColorInput } from '@udecode/plate-font/react'; 8 | 9 | export const ColorInput = withRef<'input'>( 10 | ({ children, className, value = '#000000', ...props }, ref) => { 11 | const { childProps, inputRef } = useColorInput(); 12 | 13 | return ( 14 |
15 | {React.Children.map(children, (child) => { 16 | if (!child) return child; 17 | 18 | return React.cloneElement(child as React.ReactElement, childProps); 19 | })} 20 | 21 | 28 |
29 | ); 30 | } 31 | ); 32 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/column-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import type { TColumnElement } from '@udecode/plate-layout'; 6 | 7 | import { cn, withRef } from '@udecode/cn'; 8 | import { useElement, withHOC } from '@udecode/plate-common/react'; 9 | import { ResizableProvider } from '@udecode/plate-resizable'; 10 | import { useReadOnly } from 'slate-react'; 11 | 12 | import { PlateElement } from './plate-element'; 13 | 14 | export const ColumnElement = withHOC( 15 | ResizableProvider, 16 | withRef(({ children, className, ...props }, ref) => { 17 | const readOnly = useReadOnly(); 18 | const { width } = useElement(); 19 | 20 | return ( 21 | 30 | {children} 31 | 32 | ); 33 | }) 34 | ); 35 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { CommentsPlugin } from '@udecode/plate-comments/react'; 6 | import { useEditorPlugin } from '@udecode/plate-common/react'; 7 | 8 | import { Avatar, AvatarFallback, AvatarImage } from './avatar'; 9 | 10 | export function CommentAvatar({ userId }: { userId: string | null }) { 11 | const { useOption } = useEditorPlugin(CommentsPlugin); 12 | const user = useOption('userById', userId); 13 | 14 | if (!user) return null; 15 | 16 | return ( 17 | 18 | 19 | {user.name?.[0]} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-create-form.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { 7 | CommentNewSubmitButton, 8 | CommentNewTextarea, 9 | CommentsPlugin, 10 | } from '@udecode/plate-comments/react'; 11 | import { useEditorPlugin } from '@udecode/plate-common/react'; 12 | 13 | import { buttonVariants } from './button'; 14 | import { CommentAvatar } from './comment-avatar'; 15 | import { inputVariants } from './input'; 16 | 17 | export function CommentCreateForm() { 18 | const { useOption } = useEditorPlugin(CommentsPlugin); 19 | 20 | const myUserId = useOption('myUserId'); 21 | 22 | return ( 23 |
24 | 25 | 26 |
27 | 28 | 29 | 32 | Comment 33 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-leaf.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import type { TCommentText } from '@udecode/plate-comments'; 6 | 7 | import { cn } from '@udecode/cn'; 8 | import { 9 | useCommentLeaf, 10 | useCommentLeafState, 11 | } from '@udecode/plate-comments/react'; 12 | import { type PlateLeafProps, PlateLeaf } from '@udecode/plate-common/react'; 13 | 14 | export function CommentLeaf({ 15 | className, 16 | ...props 17 | }: PlateLeafProps) { 18 | const { children, leaf, nodeProps } = props; 19 | 20 | const state = useCommentLeafState({ leaf }); 21 | const { props: rootProps } = useCommentLeaf(state); 22 | 23 | if (!state.commentCount) return <>{children}; 24 | 25 | let aboveChildren = <>{children}; 26 | 27 | if (!state.isActive) { 28 | for (let i = 1; i < state.commentCount; i++) { 29 | aboveChildren = {aboveChildren}; 30 | } 31 | } 32 | 33 | return ( 34 | 46 | {aboveChildren} 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-more-dropdown.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { 7 | useCommentDeleteButton, 8 | useCommentDeleteButtonState, 9 | useCommentEditButton, 10 | useCommentEditButtonState, 11 | } from '@udecode/plate-comments/react'; 12 | import { MoreHorizontal } from 'lucide-react'; 13 | 14 | import { Button } from './button'; 15 | import { 16 | DropdownMenu, 17 | DropdownMenuContent, 18 | DropdownMenuGroup, 19 | DropdownMenuItem, 20 | DropdownMenuTrigger, 21 | } from './dropdown-menu'; 22 | 23 | export function CommentMoreDropdown() { 24 | const editButtonState = useCommentEditButtonState(); 25 | const { props: editProps } = useCommentEditButton(editButtonState); 26 | const deleteButtonState = useCommentDeleteButtonState(); 27 | const { props: deleteProps } = useCommentDeleteButton(deleteButtonState); 28 | 29 | return ( 30 | 31 | 32 | 35 | 36 | 37 | 38 | Edit comment 39 | Delete comment 40 | 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-reply-items.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { 6 | SCOPE_ACTIVE_COMMENT, 7 | useCommentReplies, 8 | } from '@udecode/plate-comments/react'; 9 | 10 | import { CommentItem } from './comment-item'; 11 | 12 | export function CommentReplyItems() { 13 | const commentReplies = useCommentReplies(SCOPE_ACTIVE_COMMENT); 14 | 15 | return ( 16 | <> 17 | {Object.keys(commentReplies).map((id) => ( 18 | 19 | ))} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-resolve-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { 7 | CommentResolveButton as CommentResolveButtonPrimitive, 8 | useComment, 9 | } from '@udecode/plate-comments/react'; 10 | import { Check, RotateCcw } from 'lucide-react'; 11 | 12 | import { buttonVariants } from './button'; 13 | 14 | export function CommentResolveButton() { 15 | const comment = useComment()!; 16 | 17 | return ( 18 | 24 | {comment.isResolved ? ( 25 | 26 | ) : ( 27 | 28 | )} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { useCommentAddButton } from '@udecode/plate-comments/react'; 6 | import { MessageSquarePlus } from 'lucide-react'; 7 | 8 | import { ToolbarButton } from './toolbar'; 9 | 10 | export function CommentToolbarButton() { 11 | const { hidden, props } = useCommentAddButton(); 12 | 13 | if (hidden) return null; 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comment-value.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { 7 | CommentEditActions, 8 | CommentEditTextarea, 9 | } from '@udecode/plate-comments/react'; 10 | 11 | import { buttonVariants } from './button'; 12 | import { inputVariants } from './input'; 13 | 14 | export function CommentValue() { 15 | return ( 16 |
17 | 18 | 19 |
20 | 23 | Cancel 24 | 25 | 26 | 29 | Save 30 | 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/comments-popover.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { 7 | CommentProvider, 8 | CommentsPositioner, 9 | SCOPE_ACTIVE_COMMENT, 10 | useFloatingCommentsContentState, 11 | useFloatingCommentsState, 12 | } from '@udecode/plate-comments/react'; 13 | import { PortalBody } from '@udecode/plate-common/react'; 14 | 15 | import { CommentCreateForm } from './comment-create-form'; 16 | import { CommentItem } from './comment-item'; 17 | import { CommentReplyItems } from './comment-reply-items'; 18 | import { popoverVariants } from './popover'; 19 | 20 | export type FloatingCommentsContentProps = { 21 | disableForm?: boolean; 22 | }; 23 | 24 | export function CommentsPopoverContent(props: FloatingCommentsContentProps) { 25 | const { disableForm } = props; 26 | 27 | const { activeCommentId, hasNoComment, myUserId, ref } = 28 | useFloatingCommentsContentState(); 29 | 30 | return ( 31 | 36 |
37 | {!hasNoComment && ( 38 | <> 39 | 40 | 41 | 42 | 43 | )} 44 | 45 | {!!myUserId && !disableForm && } 46 |
47 |
48 | ); 49 | } 50 | 51 | export function CommentsPopover() { 52 | const { activeCommentId, loaded } = useFloatingCommentsState(); 53 | 54 | if (!loaded || !activeCommentId) return null; 55 | 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/cursor-overlay.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { isCollapsed } from '@udecode/plate-common'; 7 | import { 8 | type CursorData, 9 | type CursorOverlayState, 10 | useCursorOverlay, 11 | } from '@udecode/plate-selection/react'; 12 | 13 | export function Cursor({ 14 | id, 15 | caretPosition, 16 | data, 17 | selection, 18 | selectionRects, 19 | }: CursorOverlayState) { 20 | const { style, selectionStyle = style } = data ?? ({} as CursorData); 21 | const isCursor = isCollapsed(selection); 22 | 23 | return ( 24 | <> 25 | {selectionRects.map((position, i) => { 26 | return ( 27 |
39 | ); 40 | })} 41 | {caretPosition && ( 42 |
49 | )} 50 | 51 | ); 52 | } 53 | 54 | export function CursorOverlay() { 55 | const { cursors } = useCursorOverlay(); 56 | 57 | return ( 58 | <> 59 | {cursors.map((cursor) => ( 60 | 61 | ))} 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/emoji-picker-search-and-clear.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { UseEmojiPickerType } from '@udecode/plate-emoji/react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | 7 | import { Button } from './button'; 8 | import { emojiSearchIcons } from './emoji-icons'; 9 | 10 | export type EmojiPickerSearchAndClearProps = Pick< 11 | UseEmojiPickerType, 12 | 'clearSearch' | 'i18n' | 'searchValue' 13 | >; 14 | 15 | export function EmojiPickerSearchAndClear({ 16 | clearSearch, 17 | i18n, 18 | searchValue, 19 | }: EmojiPickerSearchAndClearProps) { 20 | return ( 21 |
22 |
27 | {emojiSearchIcons.loupe} 28 |
29 | {searchValue && ( 30 | 43 | )} 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/emoji-picker-search-bar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { ReactNode } from 'react'; 4 | 5 | import type { UseEmojiPickerType } from '@udecode/plate-emoji/react'; 6 | 7 | export type EmojiPickerSearchBarProps = { 8 | children: ReactNode; 9 | } & Pick; 10 | 11 | export function EmojiPickerSearchBar({ 12 | children, 13 | i18n, 14 | searchValue, 15 | setSearch, 16 | }: EmojiPickerSearchBarProps) { 17 | return ( 18 |
19 |
20 | setSearch(event.target.value)} 24 | placeholder={i18n.search} 25 | aria-label="Search" 26 | autoComplete="off" 27 | type="text" 28 | /> 29 | {children} 30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/emoji-toolbar-dropdown.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { type ReactNode } from 'react'; 4 | 5 | import * as Popover from '@radix-ui/react-popover'; 6 | 7 | type EmojiToolbarDropdownProps = { 8 | children: ReactNode; 9 | control: ReactNode; 10 | isOpen: boolean; 11 | setIsOpen: (open: boolean) => void; 12 | }; 13 | 14 | export function EmojiToolbarDropdown({ 15 | children, 16 | control, 17 | isOpen, 18 | setIsOpen, 19 | }: EmojiToolbarDropdownProps) { 20 | return ( 21 | 22 | {control} 23 | 24 | 25 | {children} 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/excalidraw-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | 4 | import { withRef } from '@udecode/cn'; 5 | import { useExcalidrawElement } from '@udecode/plate-excalidraw/react'; 6 | 7 | import { PlateElement } from './plate-element'; 8 | 9 | export const ExcalidrawElement = withRef( 10 | ({ nodeProps, ...props }, ref) => { 11 | const { children, element } = props; 12 | 13 | const { Excalidraw, excalidrawProps } = useExcalidrawElement({ 14 | element, 15 | }); 16 | 17 | return ( 18 | 19 |
20 |
21 | {Excalidraw && ( 22 | 23 | )} 24 |
25 |
26 | {children} 27 |
28 | ); 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/fixed-toolbar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { withCn } from '@udecode/cn'; 4 | 5 | import { Toolbar } from './toolbar'; 6 | 7 | export const FixedToolbar = withCn( 8 | Toolbar, 9 | 'supports-backdrop-blur:bg-background/60 sticky left-0 top-0 z-50 w-full justify-between overflow-x-auto rounded-t-lg border-b border-b-border bg-background/95 p-1 backdrop-blur scrollbar-hide' 10 | ); 11 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/ghost-text.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import type { CopilotPluginConfig } from '@udecode/plate-ai/react'; 6 | 7 | import { useEditorPlugin, useElement } from '@udecode/plate-common/react'; 8 | 9 | export const GhostText = () => { 10 | const { useOption } = useEditorPlugin({ 11 | key: 'copilot', 12 | }); 13 | const element = useElement(); 14 | 15 | const isSuggested = useOption('isSuggested', element.id as string); 16 | 17 | if (!isSuggested) return null; 18 | 19 | return ; 20 | }; 21 | 22 | export function GhostTextContent() { 23 | const { useOption } = useEditorPlugin({ 24 | key: 'copilot', 25 | }); 26 | 27 | const suggestionText = useOption('suggestionText'); 28 | 29 | return ( 30 | 34 | {suggestionText && suggestionText} 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/heading-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef, withVariants } from '@udecode/cn'; 6 | import { cva } from 'class-variance-authority'; 7 | 8 | import { PlateElement } from './plate-element'; 9 | 10 | const headingVariants = cva('relative mb-1', { 11 | variants: { 12 | variant: { 13 | h1: 'mt-[1.6em] pb-1 font-heading text-4xl font-bold', 14 | h2: 'mt-[1.4em] pb-px font-heading text-2xl font-semibold tracking-tight', 15 | h3: 'mt-[1em] pb-px font-heading text-xl font-semibold tracking-tight', 16 | h4: 'mt-[0.75em] font-heading text-lg font-semibold tracking-tight', 17 | h5: 'mt-[0.75em] text-lg font-semibold tracking-tight', 18 | h6: 'mt-[0.75em] text-base font-semibold tracking-tight', 19 | }, 20 | }, 21 | }); 22 | 23 | const HeadingElementVariants = withVariants(PlateElement, headingVariants, [ 24 | 'variant', 25 | ]); 26 | 27 | export const HeadingElement = withRef( 28 | ({ children, variant = 'h1', ...props }, ref) => { 29 | return ( 30 | 36 | {children} 37 | 38 | ); 39 | } 40 | ); 41 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/highlight-leaf.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { PlateLeaf } from '@udecode/plate-common/react'; 7 | 8 | export const HighlightLeaf = withRef( 9 | ({ children, className, ...props }, ref) => ( 10 | 16 | {children} 17 | 18 | ) 19 | ); 20 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/hr-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { useFocused, useSelected } from 'slate-react'; 7 | 8 | import { PlateElement } from './plate-element'; 9 | 10 | export const HrElement = withRef( 11 | ({ className, nodeProps, ...props }, ref) => { 12 | const { children } = props; 13 | 14 | const selected = useSelected(); 15 | const focused = useFocused(); 16 | 17 | return ( 18 | 19 |
20 |
27 |
28 | {children} 29 |
30 | ); 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/indent-fire-marker.tsx: -------------------------------------------------------------------------------- 1 | import type { PlateRenderElementProps } from '@udecode/plate-common/react'; 2 | import type { TIndentElement } from '@udecode/plate-indent'; 3 | 4 | export const FireMarker = ( 5 | props: Omit 6 | ) => { 7 | const { element } = props; 8 | 9 | return ( 10 |
11 | 12 | {(element as TIndentElement).indent % 2 === 0 ? '🔥' : '🚀'} 13 | 14 |
15 | ); 16 | }; 17 | 18 | export const FireLiComponent = (props: PlateRenderElementProps) => { 19 | const { children } = props; 20 | 21 | return {children}; 22 | }; 23 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/indent-list-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { ListStyleType } from '@udecode/plate-indent-list'; 7 | import { 8 | useIndentListToolbarButton, 9 | useIndentListToolbarButtonState, 10 | } from '@udecode/plate-indent-list/react'; 11 | import { List, ListOrdered } from 'lucide-react'; 12 | 13 | import { ToolbarButton } from './toolbar'; 14 | 15 | export const IndentListToolbarButton = withRef< 16 | typeof ToolbarButton, 17 | { 18 | nodeType?: ListStyleType; 19 | } 20 | >(({ nodeType = ListStyleType.Disc }, ref) => { 21 | const state = useIndentListToolbarButtonState({ nodeType }); 22 | const { props } = useIndentListToolbarButton(state); 23 | 24 | return ( 25 | 32 | {nodeType === ListStyleType.Disc ? : } 33 | 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/indent-todo-marker.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { PlateRenderElementProps } from '@udecode/plate-common/react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { 7 | useIndentTodoListElement, 8 | useIndentTodoListElementState, 9 | } from '@udecode/plate-indent-list/react'; 10 | 11 | import { Checkbox } from './checkbox'; 12 | 13 | export const TodoMarker = ({ 14 | element, 15 | }: Omit) => { 16 | const state = useIndentTodoListElementState({ element }); 17 | const { checkboxProps } = useIndentTodoListElement(state); 18 | 19 | return ( 20 |
21 | 25 |
26 | ); 27 | }; 28 | 29 | export const TodoLi = (props: PlateRenderElementProps) => { 30 | const { children, element } = props; 31 | 32 | return ( 33 | 38 | {children} 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/indent-todo-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { withRef } from '@udecode/cn'; 4 | import { 5 | useIndentTodoToolBarButton, 6 | useIndentTodoToolBarButtonState, 7 | } from '@udecode/plate-indent-list/react'; 8 | import { Square } from 'lucide-react'; 9 | 10 | import { ToolbarButton } from './toolbar'; 11 | 12 | export const IndentTodoToolbarButton = withRef( 13 | (rest, ref) => { 14 | const state = useIndentTodoToolBarButtonState({ nodeType: 'todo' }); 15 | const { props } = useIndentTodoToolBarButton(state); 16 | 17 | return ( 18 | 19 | 20 | 21 | ); 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/indent-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { useIndentButton } from '@udecode/plate-indent/react'; 7 | import { Indent } from 'lucide-react'; 8 | 9 | import { ToolbarButton } from './toolbar'; 10 | 11 | export const IndentToolbarButton = withRef( 12 | (rest, ref) => { 13 | const { props } = useIndentButton(); 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/input.tsx: -------------------------------------------------------------------------------- 1 | import { withVariants } from '@udecode/cn'; 2 | import { cva } from 'class-variance-authority'; 3 | 4 | export const inputVariants = cva( 5 | 'flex w-full rounded-md bg-transparent text-sm file:border-0 file:bg-background file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50', 6 | { 7 | defaultVariants: { 8 | h: 'md', 9 | variant: 'default', 10 | }, 11 | variants: { 12 | h: { 13 | md: 'h-10 px-3 py-2', 14 | sm: 'h-[28px] px-1.5 py-1', 15 | }, 16 | variant: { 17 | default: 18 | 'border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', 19 | ghost: 'border-none focus-visible:ring-transparent', 20 | }, 21 | }, 22 | } 23 | ); 24 | 25 | export const Input = withVariants('input', inputVariants, ['variant', 'h']); 26 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/kbd-leaf.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { PlateLeaf } from '@udecode/plate-common/react'; 7 | 8 | export const KbdLeaf = withRef( 9 | ({ children, className, ...props }, ref) => ( 10 | 19 | {children} 20 | 21 | ) 22 | ); 23 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/line-height-dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; 6 | 7 | import { 8 | useLineHeightDropdownMenu, 9 | useLineHeightDropdownMenuState, 10 | } from '@udecode/plate-line-height/react'; 11 | import { WrapText } from 'lucide-react'; 12 | 13 | import { 14 | DropdownMenu, 15 | DropdownMenuContent, 16 | DropdownMenuRadioGroup, 17 | DropdownMenuRadioItem, 18 | DropdownMenuTrigger, 19 | useOpenState, 20 | } from './dropdown-menu'; 21 | import { ToolbarButton } from './toolbar'; 22 | 23 | export function LineHeightDropdownMenu({ ...props }: DropdownMenuProps) { 24 | const openState = useOpenState(); 25 | const state = useLineHeightDropdownMenuState(); 26 | const { radioGroupProps } = useLineHeightDropdownMenu(state); 27 | 28 | return ( 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {state.values.map((_value) => ( 43 | 48 | {_value} 49 | 50 | ))} 51 | 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/link-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import type { TLinkElement } from '@udecode/plate-link'; 6 | 7 | import { cn, withRef } from '@udecode/cn'; 8 | import { useElement } from '@udecode/plate-common/react'; 9 | import { useLink } from '@udecode/plate-link/react'; 10 | 11 | import { PlateElement } from './plate-element'; 12 | 13 | export const LinkElement = withRef( 14 | ({ children, className, ...props }, ref) => { 15 | const element = useElement(); 16 | const { props: linkProps } = useLink({ element }); 17 | 18 | return ( 19 | 29 | {children} 30 | 31 | ); 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/link-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { 7 | useLinkToolbarButton, 8 | useLinkToolbarButtonState, 9 | } from '@udecode/plate-link/react'; 10 | import { Link } from 'lucide-react'; 11 | 12 | import { ToolbarButton } from './toolbar'; 13 | 14 | export const LinkToolbarButton = withRef((rest, ref) => { 15 | const state = useLinkToolbarButtonState(); 16 | const { props } = useLinkToolbarButton(state); 17 | 18 | return ( 19 | 26 | 27 | 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/mark-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { 7 | useMarkToolbarButton, 8 | useMarkToolbarButtonState, 9 | } from '@udecode/plate-common/react'; 10 | 11 | import { ToolbarButton } from './toolbar'; 12 | 13 | export const MarkToolbarButton = withRef< 14 | typeof ToolbarButton, 15 | { 16 | nodeType: string; 17 | clear?: string[] | string; 18 | } 19 | >(({ clear, nodeType, ...rest }, ref) => { 20 | const state = useMarkToolbarButtonState({ clear, nodeType }); 21 | const { props } = useMarkToolbarButton(state); 22 | 23 | return ; 24 | }); 25 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/media-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { 7 | type ImagePlugin, 8 | type MediaEmbedPlugin, 9 | useMediaToolbarButton, 10 | } from '@udecode/plate-media/react'; 11 | import { ImageIcon } from 'lucide-react'; 12 | 13 | import { ToolbarButton } from './toolbar'; 14 | 15 | export const MediaToolbarButton = withRef< 16 | typeof ToolbarButton, 17 | { 18 | nodeType?: typeof ImagePlugin.key | typeof MediaEmbedPlugin.key; 19 | } 20 | >(({ nodeType, ...rest }, ref) => { 21 | const { props } = useMediaToolbarButton({ nodeType }); 22 | 23 | return ( 24 | 25 | 26 | 27 | ); 28 | }); 29 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/outdent-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { useOutdentButton } from '@udecode/plate-indent/react'; 7 | import { Outdent } from 'lucide-react'; 8 | 9 | import { ToolbarButton } from './toolbar'; 10 | 11 | export const OutdentToolbarButton = withRef( 12 | (rest, ref) => { 13 | const { props } = useOutdentButton(); 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/paragraph-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { withRef } from '@udecode/plate-common/react'; 7 | 8 | import { PlateElement } from './plate-element'; 9 | 10 | export const ParagraphElement = withRef( 11 | ({ children, className, ...props }, ref) => { 12 | return ( 13 | 18 | {children} 19 | 20 | ); 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/placeholder.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn } from '@udecode/cn'; 6 | import { ParagraphPlugin } from '@udecode/plate-common/react'; 7 | import { 8 | type PlaceholderProps, 9 | createNodeHOC, 10 | createNodesHOC, 11 | usePlaceholderState, 12 | } from '@udecode/plate-common/react'; 13 | import { HEADING_KEYS } from '@udecode/plate-heading'; 14 | 15 | export const Placeholder = (props: PlaceholderProps) => { 16 | const { children, nodeProps, placeholder } = props; 17 | 18 | const { enabled } = usePlaceholderState(props); 19 | 20 | return React.Children.map(children, (child) => { 21 | return React.cloneElement(child, { 22 | className: child.props.className, 23 | nodeProps: { 24 | ...nodeProps, 25 | className: cn( 26 | enabled && 27 | 'before:absolute before:cursor-text before:opacity-30 before:content-[attr(placeholder)]' 28 | ), 29 | placeholder, 30 | }, 31 | }); 32 | }); 33 | }; 34 | 35 | export const withPlaceholder = createNodeHOC(Placeholder); 36 | 37 | export const withPlaceholdersPrimitive = createNodesHOC(Placeholder); 38 | 39 | export const withPlaceholders = (components: any) => 40 | withPlaceholdersPrimitive(components, [ 41 | { 42 | key: ParagraphPlugin.key, 43 | hideOnBlur: true, 44 | placeholder: 'Type a paragraph', 45 | query: { 46 | maxLevel: 1, 47 | }, 48 | }, 49 | { 50 | key: HEADING_KEYS.h1, 51 | hideOnBlur: false, 52 | placeholder: 'Untitled', 53 | }, 54 | ]); 55 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/plate-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import type { PlateElementProps } from '@udecode/plate-common/react'; 6 | 7 | import { cn } from '@udecode/cn'; 8 | import { PlateElement as PlateElementPrimitive } from '@udecode/plate-common/react'; 9 | 10 | import { BlockSelection } from './block-selection'; 11 | 12 | export const PlateElement = React.forwardRef( 13 | ({ children, className, ...props }: PlateElementProps, ref) => { 14 | return ( 15 | 20 | {children} 21 | 22 | {className?.includes('slate-selectable') && } 23 | 24 | ); 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/popover.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | 5 | import * as PopoverPrimitive from '@radix-ui/react-popover'; 6 | import { cn, withRef } from '@udecode/cn'; 7 | import { cva } from 'class-variance-authority'; 8 | 9 | export const Popover = PopoverPrimitive.Root; 10 | 11 | export const PopoverTrigger = PopoverPrimitive.Trigger; 12 | 13 | export const PopoverAnchor = PopoverPrimitive.Anchor; 14 | 15 | export const popoverVariants = cva( 16 | 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground 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 print:hidden' 17 | ); 18 | 19 | export const PopoverContent = withRef( 20 | ({ align = 'center', className, sideOffset = 4, ...props }, ref) => ( 21 | 22 | 29 | 30 | ) 31 | ); 32 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef, withVariants } from '@udecode/cn'; 6 | import { 7 | Resizable as ResizablePrimitive, 8 | ResizeHandle as ResizeHandlePrimitive, 9 | } from '@udecode/plate-resizable'; 10 | import { cva } from 'class-variance-authority'; 11 | 12 | export const mediaResizeHandleVariants = cva( 13 | cn( 14 | 'top-0 flex w-6 select-none flex-col justify-center', 15 | "after:flex after:h-16 after:w-[3px] after:rounded-[6px] after:bg-ring after:opacity-0 after:content-['_'] group-hover:after:opacity-100" 16 | ), 17 | { 18 | variants: { 19 | direction: { 20 | left: '-left-3 -ml-3 pl-3', 21 | right: '-right-3 -mr-3 items-end pr-3', 22 | }, 23 | }, 24 | } 25 | ); 26 | 27 | const resizeHandleVariants = cva(cn('absolute z-40'), { 28 | variants: { 29 | direction: { 30 | bottom: 'w-full cursor-row-resize', 31 | left: 'h-full cursor-col-resize', 32 | right: 'h-full cursor-col-resize', 33 | top: 'w-full cursor-row-resize', 34 | }, 35 | }, 36 | }); 37 | 38 | const ResizeHandleVariants = withVariants( 39 | ResizeHandlePrimitive, 40 | resizeHandleVariants, 41 | ['direction'] 42 | ); 43 | 44 | export const ResizeHandle = withRef( 45 | (props, ref) => ( 46 | 51 | ) 52 | ); 53 | 54 | const resizableVariants = cva('', { 55 | variants: { 56 | align: { 57 | center: 'mx-auto', 58 | left: 'mr-auto', 59 | right: 'ml-auto', 60 | }, 61 | }, 62 | }); 63 | 64 | export const Resizable = withVariants(ResizablePrimitive, resizableVariants, [ 65 | 'align', 66 | ]); 67 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/separator.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as SeparatorPrimitive from '@radix-ui/react-separator'; 4 | import { withProps, withVariants } from '@udecode/cn'; 5 | import { cva } from 'class-variance-authority'; 6 | 7 | const separatorVariants = cva('shrink-0 bg-border', { 8 | defaultVariants: { 9 | orientation: 'horizontal', 10 | }, 11 | variants: { 12 | orientation: { 13 | horizontal: 'h-px w-full', 14 | vertical: 'h-full w-px', 15 | }, 16 | }, 17 | }); 18 | 19 | export const Separator = withVariants( 20 | withProps(SeparatorPrimitive.Root, { 21 | decorative: true, 22 | orientation: 'horizontal', 23 | }), 24 | separatorVariants 25 | ); 26 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/table-row-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { cn, withRef } from '@udecode/cn'; 6 | import { PlateElement } from '@udecode/plate-common/react'; 7 | 8 | export const TableRowElement = withRef< 9 | typeof PlateElement, 10 | { 11 | hideBorder?: boolean; 12 | } 13 | >(({ children, hideBorder, ...props }, ref) => { 14 | return ( 15 | 21 | {children} 22 | 23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/toggle-element.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { cn, withRef } from '@udecode/cn'; 4 | import { useElement } from '@udecode/plate-common/react'; 5 | import { 6 | useToggleButton, 7 | useToggleButtonState, 8 | } from '@udecode/plate-toggle/react'; 9 | import { ChevronRight } from 'lucide-react'; 10 | 11 | import { Button } from './button'; 12 | import { PlateElement } from './plate-element'; 13 | 14 | export const ToggleElement = withRef( 15 | ({ children, className, ...props }, ref) => { 16 | const element = useElement(); 17 | const state = useToggleButtonState(element.id as string); 18 | const { buttonProps, open } = useToggleButton(state); 19 | 20 | return ( 21 | 26 | 40 | {children} 41 | 42 | ); 43 | } 44 | ); 45 | -------------------------------------------------------------------------------- /apps/web/app/components/plate-ui/toggle-toolbar-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { withRef } from '@udecode/cn'; 6 | import { 7 | useToggleToolbarButton, 8 | useToggleToolbarButtonState, 9 | } from '@udecode/plate-toggle/react'; 10 | import { ChevronRightIcon } from 'lucide-react'; 11 | 12 | import { ToolbarButton } from './toolbar'; 13 | 14 | export const ToggleToolbarButton = withRef( 15 | (rest, ref) => { 16 | const state = useToggleToolbarButtonState(); 17 | const { props } = useToggleToolbarButton(state); 18 | 19 | return ( 20 | 21 | 22 | 23 | ); 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /apps/web/app/components/skeletons/HistoriesSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { cn } from "~/lib/utils"; 3 | 4 | const HistoriesSkeleton: React.FC = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 | {Array.from({ length: 3 }).map((_, index) => ( 13 |
17 |
18 |
19 |
20 | ))} 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default HistoriesSkeleton; -------------------------------------------------------------------------------- /apps/web/app/components/skeletons/SuggestionsSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { cn } from "~/lib/utils"; 3 | 4 | const SuggestionsSkeleton: React.FC = () => { 5 | return ( 6 |
7 | {Array.from({ length: 3 }).map((_, index) => ( 8 |
12 | ))} 13 |
14 | ); 15 | }; 16 | 17 | export default SuggestionsSkeleton; -------------------------------------------------------------------------------- /apps/web/app/components/theme-button.tsx: -------------------------------------------------------------------------------- 1 | import { Theme, useTheme } from "~/lib/theme-provider"; 2 | 3 | function ThemeButton() { 4 | const [theme, setTheme] = useTheme(); 5 | return ( 6 | 12 | ); 13 | } 14 | 15 | export default ThemeButton; 16 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/avatar-img.tsx: -------------------------------------------------------------------------------- 1 | type AvatarImgProps = { 2 | src: string; 3 | alt: string; 4 | width: number; 5 | height: number; 6 | }; 7 | 8 | // eslint-disable-next-line jsx-a11y/alt-text -- The alt text is part of `...props` 9 | export const AvatarImg = (props: AvatarImgProps) => ; 10 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/icons/icons.module.css: -------------------------------------------------------------------------------- 1 | .verified { 2 | margin-left: 0.125rem; 3 | max-width: 20px; 4 | max-height: 20px; 5 | height: 1.25em; 6 | fill: currentColor; 7 | user-select: none; 8 | vertical-align: text-bottom; 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./verified"; 2 | export * from "./verified-business"; 3 | export * from "./verified-government"; 4 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/icons/verified-government.tsx: -------------------------------------------------------------------------------- 1 | import s from "./icons.module.css"; 2 | 3 | export const VerifiedGovernment = () => ( 4 | 5 | 6 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/icons/verified.tsx: -------------------------------------------------------------------------------- 1 | import s from "./icons.module.css"; 2 | 3 | export const Verified = () => ( 4 | 5 | 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet-body.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-size: var(--tweet-quoted-body-font-size); 3 | font-weight: var(--tweet-quoted-body-font-weight); 4 | line-height: var(--tweet-quoted-body-line-height); 5 | margin: var(--tweet-quoted-body-margin); 6 | overflow-wrap: break-word; 7 | white-space: pre-wrap; 8 | padding: 0 0.75rem; 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet-body.tsx: -------------------------------------------------------------------------------- 1 | import type { EnrichedQuotedTweet } from "react-tweet"; 2 | 3 | import s from "./quoted-tweet-body.module.css"; 4 | 5 | type Props = { tweet: EnrichedQuotedTweet }; 6 | 7 | export const QuotedTweetBody = ({ tweet }: Props) => ( 8 |

9 | {tweet.entities.map((item, i) => ( 10 | 11 | ))} 12 |

13 | ); 14 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet-container.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | width: 100%; 3 | overflow: hidden; 4 | border: var(--tweet-border); 5 | border-radius: 12px; 6 | margin: var(--tweet-quoted-container-margin); 7 | transition-property: background-color, box-shadow; 8 | transition-duration: 0.2s; 9 | cursor: pointer; 10 | } 11 | 12 | .root:hover { 13 | background-color: var(--tweet-quoted-bg-color-hover); 14 | } 15 | 16 | .article { 17 | position: relative; 18 | box-sizing: inherit; 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet-container.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | import type { EnrichedQuotedTweet } from "react-tweet"; 3 | 4 | import s from "./quoted-tweet-container.module.css"; 5 | 6 | type Props = { tweet: EnrichedQuotedTweet; children: ReactNode }; 7 | 8 | export const QuotedTweetContainer = ({ tweet, children }: Props) => ( 9 |
{ 12 | e.preventDefault(); 13 | window.open(tweet.url, "_blank"); 14 | }} 15 | > 16 |
{children}
17 |
18 | ); 19 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet-header.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | padding: 0.75rem 0.75rem 0 0.75rem; 4 | line-height: var(--tweet-header-line-height); 5 | font-size: var(--tweet-header-font-size); 6 | white-space: nowrap; 7 | overflow-wrap: break-word; 8 | overflow: hidden; 9 | } 10 | 11 | .avatar { 12 | position: relative; 13 | height: 20px; 14 | width: 20px; 15 | } 16 | 17 | .avatarSquare { 18 | border-radius: 4px; 19 | } 20 | 21 | .author { 22 | display: flex; 23 | margin: 0 0.5rem; 24 | } 25 | 26 | .authorText { 27 | font-weight: 700; 28 | text-overflow: ellipsis; 29 | overflow: hidden; 30 | white-space: nowrap; 31 | } 32 | 33 | .username { 34 | color: var(--tweet-font-color-secondary); 35 | text-decoration: none; 36 | text-overflow: ellipsis; 37 | margin-left: 0.125rem; 38 | } 39 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet-header.tsx: -------------------------------------------------------------------------------- 1 | import type { EnrichedQuotedTweet } from "react-tweet"; 2 | 3 | import { AvatarImg } from "../avatar-img"; 4 | import { VerifiedBadge } from "../verified-badge.js"; 5 | import s from "./quoted-tweet-header.module.css"; 6 | 7 | import clsx from "clsx"; 8 | 9 | type Props = { tweet: EnrichedQuotedTweet }; 10 | 11 | export const QuotedTweetHeader = ({ tweet }: Props) => { 12 | const { user } = tweet; 13 | 14 | return ( 15 |
16 | 17 |
23 | 24 |
25 |
26 |
27 |
28 | {user.name} 29 |
30 | 31 |
32 | @{user.screen_name} 33 |
34 |
35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/quoted-tweet/quoted-tweet.tsx: -------------------------------------------------------------------------------- 1 | import type { EnrichedQuotedTweet } from "react-tweet"; 2 | 3 | import { TweetMedia } from "../tweet-media"; 4 | import { QuotedTweetBody } from "./quoted-tweet-body"; 5 | import { QuotedTweetContainer } from "./quoted-tweet-container"; 6 | import { QuotedTweetHeader } from "./quoted-tweet-header"; 7 | 8 | type Props = { tweet: EnrichedQuotedTweet }; 9 | 10 | export const QuotedTweet = ({ tweet }: Props) => ( 11 | 12 | 13 | 14 | {tweet.mediaDetails?.length ? : null} 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/tweet-body.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-size: var(--tweet-body-font-size); 3 | font-weight: var(--tweet-body-font-weight); 4 | line-height: var(--tweet-body-line-height); 5 | margin: var(--tweet-body-margin); 6 | overflow-wrap: break-word; 7 | white-space: pre-wrap; 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/tweet-container.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | width: 100%; 3 | min-width: 250px; 4 | max-width: 550px; 5 | overflow: hidden; 6 | /* Base font styles */ 7 | color: var(--tweet-font-color); 8 | font-family: var(--tweet-font-family); 9 | font-weight: 400; 10 | box-sizing: border-box; 11 | border: var(--tweet-border); 12 | border-radius: 24px; 13 | margin: var(--tweet-container-margin); 14 | background-color: var(--tweet-bg-color); 15 | transition-property: background-color, box-shadow; 16 | transition-duration: 0.2s; 17 | } 18 | .root:hover { 19 | background-color: var(--tweet-bg-color-hover); 20 | } 21 | .article { 22 | position: relative; 23 | box-sizing: inherit; 24 | padding: 0.75rem 1rem; 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/tweet-in-reply-to.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | text-decoration: none; 3 | color: var(--tweet-font-color-secondary); 4 | font-size: 0.9375rem; 5 | line-height: 1.25rem; 6 | margin-bottom: 0.25rem; 7 | overflow-wrap: break-word; 8 | white-space: pre-wrap; 9 | } 10 | .root:hover { 11 | text-decoration-thickness: 1px; 12 | text-decoration-line: underline; 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/tweet-link.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-weight: inherit; 3 | color: var(--tweet-color-blue-secondary); 4 | text-decoration: none; 5 | cursor: pointer; 6 | } 7 | .root:hover { 8 | text-decoration-thickness: 1px; 9 | text-decoration-line: underline; 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/tweet-media-video.module.css: -------------------------------------------------------------------------------- 1 | .anchor { 2 | display: flex; 3 | align-items: center; 4 | color: white; 5 | padding: 0 1rem; 6 | border: 1px solid transparent; 7 | border-radius: 9999px; 8 | font-weight: 700; 9 | transition: background-color 0.2s; 10 | cursor: pointer; 11 | user-select: none; 12 | outline-style: none; 13 | text-decoration: none; 14 | text-overflow: ellipsis; 15 | white-space: nowrap; 16 | } 17 | .videoButton { 18 | position: relative; 19 | height: 67px; 20 | width: 67px; 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | background-color: var(--tweet-color-blue-primary); 25 | transition-property: background-color; 26 | transition-duration: 0.2s; 27 | border: 4px solid #fff; 28 | border-radius: 9999px; 29 | cursor: pointer; 30 | } 31 | .videoButton:hover, 32 | .videoButton:focus-visible { 33 | background-color: var(--tweet-color-blue-primary-hover); 34 | } 35 | .videoButtonIcon { 36 | margin-left: 3px; 37 | width: calc(50% + 4px); 38 | height: calc(50% + 4px); 39 | max-width: 100%; 40 | color: #fff; 41 | fill: currentColor; 42 | user-select: none; 43 | } 44 | .watchOnTwitter { 45 | position: absolute; 46 | top: 12px; 47 | right: 8px; 48 | } 49 | .watchOnTwitter > a { 50 | min-width: 2rem; 51 | min-height: 2rem; 52 | font-size: 0.875rem; 53 | line-height: 1rem; 54 | backdrop-filter: blur(4px); 55 | background-color: rgba(15, 20, 25, 0.75); 56 | } 57 | .watchOnTwitter > a:hover { 58 | background-color: rgba(39, 44, 48, 0.75); 59 | } 60 | .viewReplies { 61 | position: relative; 62 | min-height: 2rem; 63 | background-color: var(--tweet-color-blue-primary); 64 | border-color: var(--tweet-color-blue-primary); 65 | font-size: 0.9375rem; 66 | line-height: 1.25rem; 67 | } 68 | .viewReplies:hover { 69 | background-color: var(--tweet-color-blue-primary-hover); 70 | } 71 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/tweet-media.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | margin-top: 0.75rem; 3 | overflow: hidden; 4 | position: relative; 5 | } 6 | .rounded { 7 | border: var(--tweet-border); 8 | border-radius: 12px; 9 | } 10 | .mediaWrapper { 11 | display: grid; 12 | grid-auto-rows: 1fr; 13 | gap: 2px; 14 | height: 100%; 15 | width: 100%; 16 | } 17 | .grid2Columns { 18 | grid-template-columns: repeat(2, 1fr); 19 | } 20 | .grid3 > a:first-child { 21 | grid-row: span 2; 22 | } 23 | .grid2x2 { 24 | grid-template-rows: repeat(2, 1fr); 25 | } 26 | .mediaContainer { 27 | position: relative; 28 | height: 100%; 29 | width: 100%; 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | } 34 | .mediaLink { 35 | text-decoration: none; 36 | outline-style: none; 37 | } 38 | .skeleton { 39 | padding-bottom: 56.25%; 40 | width: 100%; 41 | display: block; 42 | } 43 | .image { 44 | position: absolute; 45 | top: 0px; 46 | left: 0px; 47 | bottom: 0px; 48 | height: 100%; 49 | width: 100%; 50 | margin: 0; 51 | object-fit: cover; 52 | object-position: center; 53 | } 54 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/verified-badge.module.css: -------------------------------------------------------------------------------- 1 | .verifiedOld { 2 | color: var(--tweet-verified-old-color); 3 | } 4 | .verifiedBlue { 5 | color: var(--tweet-verified-blue-color); 6 | } 7 | .verifiedGovernment { 8 | /* color: var(--tweet-verified-government-color); */ 9 | color: rgb(130, 154, 171); 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/app/components/twitter/verified-badge.tsx: -------------------------------------------------------------------------------- 1 | import { Verified, VerifiedBusiness, VerifiedGovernment } from "./icons/index"; 2 | import s from "./verified-badge.module.css"; 3 | 4 | import clsx from "clsx"; 5 | 6 | type Props = { 7 | user: any; 8 | className?: string; 9 | }; 10 | 11 | export const VerifiedBadge = ({ user, className }: Props) => { 12 | const verified = user.verified || user.is_blue_verified || user.verified_type; 13 | let icon = ; 14 | let iconClassName: string | null = s.verifiedBlue ?? null; 15 | 16 | if (verified) { 17 | if (!user.is_blue_verified) { 18 | iconClassName = s.verifiedOld!; 19 | } 20 | switch (user.verified_type) { 21 | case "Government": 22 | icon = ; 23 | iconClassName = s.verifiedGovernment!; 24 | break; 25 | case "Business": 26 | icon = ; 27 | iconClassName = null; 28 | break; 29 | } 30 | } 31 | 32 | return verified ?
{icon}
: null; 33 | }; 34 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Loader() { 4 | return ( 5 |
6 | {/* Example Loader: Spinner */} 7 | 8 | 9 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default Loader; 20 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 3 | 4 | import { cn } from "~/lib/utils" 5 | 6 | const Avatar = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | )) 19 | Avatar.displayName = AvatarPrimitive.Root.displayName 20 | 21 | const AvatarImage = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 30 | )) 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 32 | 33 | const AvatarFallback = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | )) 46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 47 | 48 | export { Avatar, AvatarImage, AvatarFallback } 49 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "~/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 6 | import { Check } from "lucide-react"; 7 | import { cn } from "~/lib/utils"; 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 22 | 23 | 24 | 25 | )); 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 27 | 28 | export { Checkbox }; 29 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "~/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "~/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 4 | import { cn } from "~/lib/utils"; 5 | 6 | const Popover = PopoverPrimitive.Root; 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger; 9 | 10 | const PopoverAnchor = PopoverPrimitive.Anchor; 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )); 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 30 | 31 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; 32 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SwitchPrimitives from "@radix-ui/react-switch" 3 | 4 | import { cn } from "~/lib/utils" 5 | 6 | const Switch = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | 23 | 24 | )) 25 | Switch.displayName = SwitchPrimitives.Root.displayName 26 | 27 | export { Switch } 28 | -------------------------------------------------------------------------------- /apps/web/app/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "~/lib/utils"; 4 | 5 | export interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |