├── .cursor
└── rules
│ ├── ai-sdk-integration.mdc
│ ├── audio-server.mdc
│ ├── chatbot-integration.mdc
│ ├── file-structure.mdc
│ ├── file-upload-flow.mdc
│ ├── mobile.mdc
│ ├── plugin-development.mdc
│ ├── shared-utilities.mdc
│ └── web-development.mdc
├── .cursorrules
├── .dockerignore
├── .eslintignore
├── .eslintrc
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── build.yml
│ ├── cron.yml
│ └── manual-release.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── ANALYSIS.md
├── CLAUDE.md
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── main.css
├── manifest.json
├── memory
├── 2024-07-29-mobile-image-upload-workflow.md
├── 2024-07-30-sdk-implementation-failure-web-search.md
├── 2024-09-25-expo-run-command-directory.md
└── 2024-09-25-remove-expo-share-intent.md
├── package.json
├── packages
├── audio-server
│ ├── .env
│ ├── .gitignore
│ ├── dist
│ │ ├── server.d.ts
│ │ └── server.js
│ ├── nixpacks.toml
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── server.ts
│ └── tsconfig.json
├── landing
│ ├── .cursorrules
│ ├── .env.example
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ │ ├── (landing)
│ │ │ ├── actions.ts
│ │ │ ├── components
│ │ │ │ ├── beta-request-form.tsx
│ │ │ │ ├── faq-section.tsx
│ │ │ │ ├── grid-pattern.tsx
│ │ │ │ ├── how.tsx
│ │ │ │ ├── integrations-grid.tsx
│ │ │ │ ├── pricing-cards.tsx
│ │ │ │ ├── request-logo.tsx
│ │ │ │ └── waitlist-form.tsx
│ │ │ ├── data
│ │ │ │ └── integrations.ts
│ │ │ ├── demo
│ │ │ │ ├── browser-window.tsx
│ │ │ │ ├── demo.tsx
│ │ │ │ └── loading-animation.tsx
│ │ │ ├── error.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── mobile
│ │ │ │ └── page.tsx
│ │ │ ├── not-found.tsx
│ │ │ ├── opengraph-image.png
│ │ │ └── page.tsx
│ │ ├── PostHogPageView.tsx
│ │ ├── dao
│ │ │ ├── error.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── not-found.tsx
│ │ │ └── page.tsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── opengraph-image.png
│ │ ├── privacy
│ │ │ └── page.tsx
│ │ ├── providers.tsx
│ │ ├── terms-of-service
│ │ │ └── page.tsx
│ │ ├── twitter-image.png
│ │ ├── types.ts
│ │ └── utils
│ │ │ ├── transcription.ts
│ │ │ └── verifyPostal.ts
│ ├── components.json
│ ├── components
│ │ ├── app-page.tsx
│ │ ├── navbar.tsx
│ │ ├── task-summary.tsx
│ │ ├── theme-switcher.tsx
│ │ ├── tutorial
│ │ │ ├── code-block.tsx
│ │ │ ├── connect-supabase-steps.tsx
│ │ │ ├── fetch-data-steps.tsx
│ │ │ ├── sign-up-user-steps.tsx
│ │ │ └── tutorial-step.tsx
│ │ ├── typography
│ │ │ └── inline-code.tsx
│ │ ├── ui
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── use-toast.tsx
│ │ └── un-30.svg
│ ├── hooks
│ │ └── use-toast.ts
│ ├── lib
│ │ └── utils.ts
│ ├── middleware.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ └── _error.js
│ ├── postcss.config.js
│ ├── public
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ ├── after.png
│ │ │ └── before.jpg
│ │ ├── note_companion_logo_bright.jpg
│ │ ├── notecompanion.png
│ │ └── screenpipe-logo.png
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ ├── types
│ │ └── posthog.d.ts
│ └── utils
│ │ ├── cn.ts
│ │ ├── favi.png
│ │ ├── og.png
│ │ └── utils.ts
├── mobile
│ ├── .env
│ ├── .gitignore
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── README.md
│ ├── app.config.ts
│ ├── app.json.description.txt
│ ├── app
│ │ ├── (auth)
│ │ │ ├── _layout.tsx
│ │ │ ├── index.tsx
│ │ │ ├── sign-in.tsx
│ │ │ ├── sign-up.tsx
│ │ │ └── welcome.tsx
│ │ ├── (tabs)
│ │ │ ├── _layout.tsx
│ │ │ ├── camera.tsx
│ │ │ ├── index.tsx
│ │ │ ├── notes.tsx
│ │ │ ├── settings.tsx
│ │ │ └── sync.tsx
│ │ ├── +not-found.tsx
│ │ ├── _layout.tsx
│ │ ├── docs
│ │ │ ├── privacy-policy.tsx
│ │ │ └── terms-of-service.tsx
│ │ ├── file-viewer.tsx
│ │ └── shared.tsx
│ ├── assets
│ │ ├── big-logo.png
│ │ ├── einstein-document.jpg
│ │ ├── fAp56rVs5e5ZsfXD4Mmgki-1000-80.jpg
│ │ ├── fonts
│ │ │ └── SpaceMono-Regular.ttf
│ │ ├── icon.png
│ │ ├── icon_backup.png
│ │ ├── images
│ │ │ ├── adaptive-icon.png
│ │ │ ├── app-demo.png
│ │ │ ├── app-icon.png
│ │ │ ├── favicon.png
│ │ │ ├── icon.png
│ │ │ ├── partial-react-logo.png
│ │ │ ├── react-logo.png
│ │ │ ├── react-logo@2x.png
│ │ │ ├── react-logo@3x.png
│ │ │ ├── splash-icon.png
│ │ │ └── splash.png
│ │ ├── splash-white.png
│ │ └── splash.png
│ ├── babel.config.js
│ ├── components
│ │ ├── Button.tsx
│ │ ├── Collapsible.tsx
│ │ ├── ExternalLink.tsx
│ │ ├── FileCard.tsx
│ │ ├── FileList.tsx
│ │ ├── FilePreview.tsx
│ │ ├── HapticTab.tsx
│ │ ├── HelloWave.tsx
│ │ ├── MarkdownPreview.tsx
│ │ ├── ParallaxScrollView.tsx
│ │ ├── ShareButton.tsx
│ │ ├── SharedFileViewer.tsx
│ │ ├── SignInWithOAuth.tsx
│ │ ├── ThemedText.tsx
│ │ ├── ThemedView.tsx
│ │ ├── __tests__
│ │ │ ├── ThemedText-test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── ThemedText-test.tsx.snap
│ │ ├── file-preview.tsx
│ │ ├── markdown-viewer.tsx
│ │ ├── processing-status.tsx
│ │ ├── text-document-viewer.tsx
│ │ ├── ui
│ │ │ ├── CameraTabButton.tsx
│ │ │ ├── IconSymbol.ios.tsx
│ │ │ ├── IconSymbol.tsx
│ │ │ ├── TabBarBackground.ios.tsx
│ │ │ └── TabBarBackground.tsx
│ │ └── usage-status.tsx
│ ├── constants
│ │ ├── Colors.ts
│ │ └── config.ts
│ ├── converted_icon.png
│ ├── docs
│ │ ├── app-store
│ │ │ └── apple-response.md
│ │ ├── privacy-policy.md
│ │ └── terms-of-service.md
│ ├── eas.json
│ ├── fastlane
│ │ ├── Appfile
│ │ └── Fastfile
│ ├── favicon.ico
│ ├── global.css
│ ├── help.tsx
│ ├── hooks
│ │ ├── useColorScheme.ts
│ │ ├── useColorScheme.web.ts
│ │ ├── useThemeColor.ts
│ │ └── useWarmUpBrowser.ts
│ ├── index.ts
│ ├── metro.config.js
│ ├── nativewind-env.d.ts
│ ├── package.json
│ ├── patches
│ │ ├── react-native-fabric-fix.patch
│ │ ├── react-native@0.76.7.patch
│ │ ├── scripts
│ │ │ └── create-fabric-provider.js
│ │ └── xcode+3.0.1.patch
│ ├── providers
│ │ └── auth.tsx
│ ├── scripts
│ │ └── reset-project.js
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ └── utils
│ │ ├── api.ts
│ │ ├── camera-handler.ts
│ │ ├── file-handler.ts
│ │ ├── image-utils.ts
│ │ ├── share-handler.ts
│ │ └── sharing.ts
├── plugin
│ ├── .gitignore
│ ├── .prettierrc
│ ├── apiUtils.ts
│ ├── components
│ │ ├── ui
│ │ │ ├── alert.tsx
│ │ │ ├── badge copy.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── checkout-button.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── folder-selection.tsx
│ │ │ ├── form.tsx
│ │ │ ├── icons.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── logo.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── utils.tsx
│ │ ├── usage-stats.tsx
│ │ └── utils.ts
│ ├── constants.ts
│ ├── dist
│ │ └── styles.css
│ ├── esbuild.config.mjs
│ ├── fileUtils.ts
│ ├── handlers
│ │ ├── commandHandlers.ts
│ │ └── eventHandlers.ts
│ ├── inbox
│ │ ├── constants.ts
│ │ ├── index.ts
│ │ ├── services
│ │ │ ├── error-service.ts
│ │ │ ├── id-service.ts
│ │ │ ├── queue.ts
│ │ │ ├── record-manager.ts
│ │ │ └── youtube-service.ts
│ │ ├── types.ts
│ │ └── utils
│ │ │ └── file.ts
│ ├── index.ts
│ ├── lib
│ │ └── utils.ts
│ ├── package.json
│ ├── postcss.config.js
│ ├── pre-processor.ts
│ ├── services
│ │ └── logger.ts
│ ├── settings.ts
│ ├── skeleton-loader.tsx
│ ├── someUtils.ts
│ ├── styles.css
│ ├── tailwind.config.js
│ ├── templates
│ │ └── youtube_video.md
│ ├── tsconfig.json
│ ├── utils.ts
│ ├── utils
│ │ └── token-counter.ts
│ ├── version-bump.mjs
│ ├── versions.json
│ └── views
│ │ ├── assistant
│ │ ├── ai-chat
│ │ │ ├── ai-message-renderer.tsx
│ │ │ ├── audio-recorder.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── chat.tsx
│ │ │ ├── components
│ │ │ │ ├── SourcesSection.tsx
│ │ │ │ ├── append-button.tsx
│ │ │ │ ├── attachment-handler.tsx
│ │ │ │ ├── audio-recorder.tsx
│ │ │ │ ├── clear-all-button.tsx
│ │ │ │ ├── context-items.tsx
│ │ │ │ ├── context-limit-indicator.tsx
│ │ │ │ ├── copy-button.tsx
│ │ │ │ ├── example-prompts.tsx
│ │ │ │ ├── message-renderer.tsx
│ │ │ │ ├── model-selector.tsx
│ │ │ │ ├── search-toggle.tsx
│ │ │ │ └── submit-button.tsx
│ │ │ ├── container.tsx
│ │ │ ├── context-limit-indicator.tsx
│ │ │ ├── hooks
│ │ │ │ └── use-current-file.ts
│ │ │ ├── mentions.tsx
│ │ │ ├── message-renderer.tsx
│ │ │ ├── model-selector.tsx
│ │ │ ├── selected-item.tsx
│ │ │ ├── shared
│ │ │ │ └── markdown-renderer.tsx
│ │ │ ├── submit-button.tsx
│ │ │ ├── suggestion.ts
│ │ │ ├── tiptap.tsx
│ │ │ ├── tool-handlers
│ │ │ │ ├── add-text-handler.tsx
│ │ │ │ ├── append-content-handler.tsx
│ │ │ │ ├── date-range-handler.tsx
│ │ │ │ ├── execute-actions-handler.tsx
│ │ │ │ ├── last-modified-handler.tsx
│ │ │ │ ├── modify-text-handler.tsx
│ │ │ │ ├── move-files-handler.tsx
│ │ │ │ ├── onboard-handler.tsx
│ │ │ │ ├── rename-files-handler.tsx
│ │ │ │ ├── screenpipe-handler.tsx
│ │ │ │ ├── screenpipe-utils.ts
│ │ │ │ ├── search-annotation-handler.tsx
│ │ │ │ ├── search-handler.tsx
│ │ │ │ ├── search-rename-handler.tsx
│ │ │ │ ├── settings-update-handler.tsx
│ │ │ │ ├── tool-invocation-handler.tsx
│ │ │ │ ├── types.ts
│ │ │ │ └── youtube-handler.tsx
│ │ │ ├── types.ts
│ │ │ ├── types
│ │ │ │ ├── annotations.ts
│ │ │ │ ├── attachments.ts
│ │ │ │ └── grounding.ts
│ │ │ ├── use-context-items.ts
│ │ │ ├── use-vault-items.ts
│ │ │ ├── user-message-renderer.tsx
│ │ │ └── youtube-transcript.ts
│ │ ├── dashboard
│ │ │ ├── collapsible-section.tsx
│ │ │ ├── floating-action-button.tsx
│ │ │ ├── main-dashboard.tsx
│ │ │ ├── onboarding-wizard.tsx
│ │ │ ├── progress-bar.tsx
│ │ │ └── view.tsx
│ │ ├── inbox-logs.tsx
│ │ ├── organizer
│ │ │ ├── ai-format
│ │ │ │ ├── fabric-templates.tsx
│ │ │ │ ├── templates.tsx
│ │ │ │ └── user-templates.tsx
│ │ │ ├── chunks.tsx
│ │ │ ├── components
│ │ │ │ ├── empty-state.tsx
│ │ │ │ ├── error-box.tsx
│ │ │ │ ├── license-validator.tsx
│ │ │ │ ├── refresh-button.tsx
│ │ │ │ ├── skeleton-loader.tsx
│ │ │ │ └── suggestion-buttons.tsx
│ │ │ ├── folders
│ │ │ │ ├── box.tsx
│ │ │ │ └── components
│ │ │ │ │ ├── error-display.tsx
│ │ │ │ │ └── skeleton-loader.tsx
│ │ │ ├── meetings
│ │ │ │ └── meetings.tsx
│ │ │ ├── organizer.tsx
│ │ │ ├── tags.tsx
│ │ │ ├── titles
│ │ │ │ └── box.tsx
│ │ │ └── transcript.tsx
│ │ ├── provider.tsx
│ │ ├── section-header.tsx
│ │ ├── synchronizer
│ │ │ ├── index.ts
│ │ │ └── sync-tab.tsx
│ │ └── view.tsx
│ │ └── settings
│ │ ├── account-data.tsx
│ │ ├── advanced-tab.tsx
│ │ ├── catalyst-gate.tsx
│ │ ├── customization-tab.tsx
│ │ ├── experiment-tab.tsx
│ │ ├── fabric-prompt-manager.tsx
│ │ ├── file-config-tab.tsx
│ │ ├── general-tab.tsx
│ │ ├── main.tsx
│ │ ├── top-up-credits.tsx
│ │ └── view.tsx
├── release-notes
│ ├── .gitignore
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
└── web
│ ├── .electronignore
│ ├── .env.example
│ ├── .github
│ └── workflows
│ │ └── update.yml
│ ├── .gitignore
│ ├── README.md
│ ├── __mocks__
│ ├── @ai-sdk
│ │ ├── google.ts
│ │ └── openai.ts
│ └── next
│ │ └── server.ts
│ ├── app
│ ├── (app)
│ │ └── dashboard
│ │ │ └── upload-test
│ │ │ └── page.tsx
│ ├── actions.ts
│ ├── api
│ │ ├── (newai)
│ │ │ ├── aiService.ts
│ │ │ ├── aliases
│ │ │ │ └── route.ts
│ │ │ ├── chat
│ │ │ │ ├── route.test.ts
│ │ │ │ ├── route.ts
│ │ │ │ └── tools.ts
│ │ │ ├── chunks
│ │ │ │ └── route.ts
│ │ │ ├── classify-v2
│ │ │ │ └── route.ts
│ │ │ ├── classify1
│ │ │ │ └── route.ts
│ │ │ ├── concepts-and-chunks
│ │ │ │ └── route.ts
│ │ │ ├── concepts
│ │ │ │ └── route.ts
│ │ │ ├── create-folder
│ │ │ │ └── route.ts
│ │ │ ├── fabric-classify
│ │ │ │ └── route.ts
│ │ │ ├── folders
│ │ │ │ ├── embeddings
│ │ │ │ │ ├── bm25.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── existing
│ │ │ │ │ └── route.ts
│ │ │ │ ├── new
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ └── v2
│ │ │ │ │ └── route.ts
│ │ │ ├── format-stream
│ │ │ │ └── route.ts
│ │ │ ├── format
│ │ │ │ ├── route.ts
│ │ │ │ └── v2
│ │ │ │ │ └── route.ts
│ │ │ ├── modify
│ │ │ │ └── route.ts
│ │ │ ├── organize-all
│ │ │ │ └── route.ts
│ │ │ ├── tags
│ │ │ │ ├── existing
│ │ │ │ │ └── route.ts
│ │ │ │ ├── new
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ └── v2
│ │ │ │ │ └── route.ts
│ │ │ ├── title
│ │ │ │ ├── multiple-stream
│ │ │ │ │ └── route.ts
│ │ │ │ ├── multiple
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ └── v2
│ │ │ │ │ └── route.ts
│ │ │ ├── transcribe
│ │ │ │ └── route.ts
│ │ │ └── vision
│ │ │ │ ├── prompt.ts
│ │ │ │ └── route.ts
│ │ ├── (sync)
│ │ │ ├── file-status
│ │ │ │ └── route.ts
│ │ │ ├── files
│ │ │ │ ├── [id]
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ └── upload
│ │ │ │ └── route.ts
│ │ ├── anon.ts
│ │ ├── blob-client-upload
│ │ │ └── route.ts
│ │ ├── check-key
│ │ │ └── route.ts
│ │ ├── check-premium
│ │ │ └── route.ts
│ │ ├── check-tier
│ │ │ └── route.ts
│ │ ├── create-upload-url
│ │ │ └── route.ts
│ │ ├── cron
│ │ │ ├── redeploy
│ │ │ │ └── route.ts
│ │ │ └── reset-tokens
│ │ │ │ ├── route.test.ts
│ │ │ │ └── route.ts
│ │ ├── files
│ │ │ ├── recent
│ │ │ │ └── route.ts
│ │ │ └── upload
│ │ │ │ └── route.ts
│ │ ├── get-upload-status
│ │ │ └── [id]
│ │ │ │ └── route.ts
│ │ ├── health
│ │ │ └── route.ts
│ │ ├── process-file
│ │ │ └── route.ts
│ │ ├── process-pending-uploads
│ │ │ └── route.ts
│ │ ├── public-usage
│ │ │ └── route.ts
│ │ ├── record-upload
│ │ │ └── route.ts
│ │ ├── redeploy
│ │ │ └── route.ts
│ │ ├── sign-in
│ │ │ └── route.ts
│ │ ├── sign-up
│ │ │ └── route.ts
│ │ ├── test-ocr
│ │ │ └── route.ts
│ │ ├── token-usage
│ │ │ └── route.ts
│ │ ├── top-up
│ │ │ └── route.ts
│ │ ├── trigger-processing
│ │ │ └── route.ts
│ │ ├── upload-test
│ │ │ └── route.ts
│ │ ├── usage
│ │ │ └── route.ts
│ │ ├── user
│ │ │ └── subscription-status
│ │ │ │ └── route.ts
│ │ └── webhook
│ │ │ ├── handler-factory.ts
│ │ │ ├── handlers
│ │ │ ├── checkout-complete.ts
│ │ │ ├── invoice-paid.ts
│ │ │ ├── invoice-payment-failed.ts
│ │ │ ├── payment-intent-succeeded.ts
│ │ │ ├── subscription-canceled.ts
│ │ │ └── subscription-updated.ts
│ │ │ ├── route.ts
│ │ │ ├── types.ts
│ │ │ ├── utils.ts
│ │ │ └── verify.ts
│ ├── components
│ │ └── license-form.tsx
│ ├── dashboard
│ │ ├── deployment
│ │ │ ├── _components
│ │ │ │ ├── configuration-form.tsx
│ │ │ │ ├── deployment-status.tsx
│ │ │ │ └── wizard-steps.tsx
│ │ │ ├── actions.ts
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── lifetime
│ │ │ ├── action.ts
│ │ │ ├── automated-setup.tsx
│ │ │ ├── client-component.tsx
│ │ │ ├── components
│ │ │ │ └── plugin-setup.tsx
│ │ │ ├── legacy-setup.tsx
│ │ │ └── page.tsx
│ │ ├── page.tsx
│ │ ├── pricing
│ │ │ ├── actions.ts
│ │ │ └── page.tsx
│ │ ├── self-hosted
│ │ │ └── page.tsx
│ │ ├── settings
│ │ │ └── page.tsx
│ │ ├── subscribers
│ │ │ ├── client-component.tsx
│ │ │ └── page.tsx
│ │ └── sync
│ │ │ ├── _components
│ │ │ ├── FileCard.tsx
│ │ │ ├── FileList.tsx
│ │ │ └── StatusBadge.tsx
│ │ │ ├── actions.ts
│ │ │ └── page.tsx
│ ├── error.tsx
│ ├── favicon.ico
│ ├── global-error.tsx
│ ├── globals.css
│ ├── layout.tsx
│ ├── not-found.tsx
│ ├── onboarding
│ │ └── page.tsx
│ ├── page.tsx
│ ├── posthog-page-view.tsx
│ ├── providers.tsx
│ ├── sign-in
│ │ └── [[...rest]]
│ │ │ └── page.tsx
│ ├── top-up-cancelled
│ │ └── page.tsx
│ ├── top-up-success
│ │ └── page.tsx
│ └── upgrade-from-mobile
│ │ └── page.tsx
│ ├── components.json
│ ├── components
│ ├── auth-layout-wrapper.tsx
│ ├── navigation-bar.tsx
│ ├── pricing-cards.tsx
│ ├── ui
│ │ ├── alert.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── checkout-button.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── folder-selection.tsx
│ │ ├── form.tsx
│ │ ├── icons.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── logo.tsx
│ │ ├── progress.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── switch.tsx
│ │ ├── tabs.tsx
│ │ └── utils.tsx
│ └── user-management.tsx
│ ├── drizzle.config.ts
│ ├── drizzle
│ ├── 0000_thankful_mathemanic.sql
│ ├── 0001_new-hello.sql
│ ├── 0001_numerous_famine.sql
│ ├── 0002_tired_phantom_reporter.sql
│ ├── 0003_first_phantom_reporter.sql
│ ├── 0004_cool_sunspot.sql
│ ├── 0005_stiff_madripoor.sql
│ ├── 0006_smart_brother_voodoo.sql
│ ├── 0007_slow_dracula.sql
│ ├── 0008_woozy_viper.sql
│ ├── 0009_fluffy_electro.sql
│ ├── 0010_certain_groot.sql
│ ├── 0011_lame_green_goblin.sql
│ ├── 0012_same_zodiak.sql
│ ├── 0013_deep_blazing_skull.sql
│ ├── 0014_demonic_flatman.sql
│ ├── 0015_tense_paper_doll.sql
│ ├── 0016_quick_vanisher.sql
│ ├── 0017_large_ser_duncan.sql
│ ├── 0018_previous_catseye.sql
│ ├── 0019_bizarre_night_thrasher.sql
│ ├── 0020_silent_baron_zemo.sql
│ ├── 0021_bouncy_shotgun.sql
│ ├── envConfig.js
│ ├── 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
│ │ ├── 0021_snapshot.json
│ │ └── _journal.json
│ ├── migrations
│ │ └── reset-inactive-users.ts
│ └── schema.ts
│ ├── jest.config.ts
│ ├── jest.env.setup.js
│ ├── jest.setup.ts
│ ├── lib
│ ├── getUrl.ts
│ ├── handleAuthorization.ts
│ ├── incrementAndLogTokenUsage.ts
│ ├── models.ts
│ ├── posthog.ts
│ ├── prompts
│ │ └── chat-prompt.ts
│ ├── services
│ │ ├── clerk.ts
│ │ └── loops.ts
│ ├── subscription.ts
│ ├── utils.ts
│ └── youtubeTranscript.ts
│ ├── middleware.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ └── _error.js
│ ├── postcss.config.mjs
│ ├── public
│ ├── big-logo.png
│ ├── favicon.ico
│ ├── icon.icns
│ ├── lightning-logo.avif
│ ├── logo.icns
│ │ └── 512x512.png
│ ├── next.svg
│ └── vercel.svg
│ ├── scripts
│ ├── migrate.ts
│ └── webhooks.test.ts
│ ├── srm.config.ts
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ ├── types
│ ├── globals.d.ts
│ └── radix-ui.d.ts
│ ├── uploads
│ └── 9e621745-c5de-4a41-8538-e8b63b2e145e-compilation-without-summary.pdf
│ └── vercel-non.json
├── patches
└── xcode@3.0.1.patch
├── pipes
└── file-organizer-notifications
│ ├── .gitignore
│ ├── README.md
│ ├── bun.lockb
│ ├── components.json
│ ├── eslint.config.mjs
│ ├── next.config.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── pipe.json
│ ├── postcss.config.mjs
│ ├── public
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
│ ├── src
│ ├── app
│ │ ├── api
│ │ │ ├── check-folder
│ │ │ │ └── route.ts
│ │ │ ├── intelligence
│ │ │ │ └── route.ts
│ │ │ ├── log
│ │ │ │ └── route.ts
│ │ │ └── settings
│ │ │ │ └── route.ts
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components
│ │ ├── obsidian-settings.tsx
│ │ ├── ollama-models-list.tsx
│ │ └── ui
│ │ │ ├── accordion.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── codeblock.tsx
│ │ │ ├── command.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── icons.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── use-toast.ts
│ ├── hooks
│ │ └── use-toast.ts
│ └── lib
│ │ ├── actions
│ │ └── video-actions.ts
│ │ ├── hooks
│ │ ├── use-copy-to-clipboard.tsx
│ │ ├── use-debounce.tsx
│ │ ├── use-health-check.tsx
│ │ ├── use-settings.tsx
│ │ └── use-sql-autocomplete.tsx
│ │ └── utils.ts
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── promptfooconfig.yaml
├── render.yaml
├── scripts
└── create-fabric-provider.js
├── test-release.js
├── tsconfig.json
├── turbo.json
└── tutorials
├── CommonWorkflows.md
├── ExecuteActionsHandler.md
├── LastModifiedHandler.md
├── ScreenpipeHandler.md
├── assistantSidebar.md
├── bugs.md
├── custom_folder_organization_examples.md
├── env-vars.md
├── faq.md
├── getStarted.md
├── howItWorks.md
├── images
├── assistant_sidebar.png
├── pre_processed_file.png
└── processed_file.png
├── international
├── chinese
├── francais.md
└── russian.md
├── lifetime-setup-v2.md
├── lifetime_faq.md
├── local-llms.md
├── mobile.md
└── troubelshoot.md
/.cursor/rules/audio-server.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: Audio transcription server implementation
3 | globs: packages/audio-server/**/*
4 | ---
5 |
6 | # Audio Server Implementation Guide
7 |
8 | ## Overview
9 | Audio server code should be in `packages/audio-server/`
10 |
11 | ## Components
12 |
13 | ### 1. Transcription Service
14 | - Implement proper queue management
15 | - Handle multiple audio formats
16 | - Implement error recovery
17 |
18 | ### 2. API Integration
19 | - RESTful API endpoints
20 | - Proper authentication
21 | - Rate limiting
22 |
23 | ### 3. File Processing
24 | - Handle large files
25 | - Implement progress tracking
26 | - Proper cleanup procedures
27 |
28 | ## Best Practices
29 | 1. Implement proper audio format validation
30 | 2. Use efficient streaming methods
31 | 3. Implement proper error handling
32 | 4. Cache results when possible
33 | 5. Clean up temporary files
34 |
--------------------------------------------------------------------------------
/.cursor/rules/plugin-development.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: Guidelines for Obsidian plugin development
3 | globs: packages/plugin/**/*
4 | ---
5 |
6 | # Obsidian Plugin Development Guide
7 |
8 | ## Structure
9 | All plugin code should be in `packages/plugin/`
10 |
11 | ## Core Components
12 |
13 | ### 1. File Organization
14 | - Use TypeScript for all plugin code
15 | - Follow Obsidian's plugin architecture
16 | - Implement proper error handling
17 |
18 | ### 2. AI Integration
19 | - Use proper context management
20 | - Implement token usage tracking
21 | - Handle rate limiting
22 |
23 | ### 3. UI Components
24 | - Follow Obsidian's UI guidelines
25 | - Use native Obsidian components
26 | - Implement proper loading states
27 |
28 | ## Best Practices
29 | 1. Test all file operations thoroughly
30 | 2. Handle permissions properly
31 | 3. Implement proper error recovery
32 | 4. Cache results when possible
33 | 5. Follow Obsidian's plugin guidelines
34 |
--------------------------------------------------------------------------------
/.cursor/rules/shared-utilities.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: Shared utilities and types
3 | globs: packages/shared/**/*
4 | ---
5 |
6 | # Shared Utilities Implementation Guide
7 |
8 | ## Overview
9 | Shared code should be in `packages/shared/`
10 |
11 | ## Components
12 |
13 | ### 1. Type Definitions
14 | - Keep types consistent
15 | - Use proper TypeScript features
16 | - Document complex types
17 |
18 | ### 2. Utility Functions
19 | - Keep functions pure
20 | - Implement proper error handling
21 | - Write comprehensive tests
22 |
23 | ### 3. Constants
24 | - Use proper naming conventions
25 | - Document usage requirements
26 | - Keep organized by domain
27 |
28 | ## Best Practices
29 | 1. Document all exports
30 | 2. Write comprehensive tests
31 | 3. Keep dependencies minimal
32 | 4. Use proper versioning
33 | 5. Maintain backwards compatibility
34 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .turbo
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | main.js
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "env": { "node": true },
5 | "plugins": [
6 | "@typescript-eslint"
7 | ],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/eslint-recommended",
11 | "plugin:@typescript-eslint/recommended"
12 | ],
13 | "parserOptions": {
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "no-unused-vars": "warn",
18 | "@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }],
19 | "@typescript-eslint/ban-ts-comment": "warn",
20 | "no-prototype-builtins": "warn",
21 | "@typescript-eslint/no-empty-function": "warn"
22 | }
23 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | custom: ['https://dub.sh/support-fo2k']
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Describe the bug**
4 | A clear and concise description of what the bug is.
5 |
6 | **To Reproduce**
7 | Steps to reproduce the behavior:
8 | 1. Go to '...'
9 | 2. Click on '....'
10 | 3. Scroll down to '....'
11 | 4. See error
12 |
13 | **Current version of Fo2k**
14 |
15 |
16 |
17 | **Customer status: lifetime/cloud**
18 |
19 | Are you lifetime or cloud user?
20 |
21 | **Deployment method (not for cloud)**
22 |
23 | vercel/render/or self-managed
24 |
25 | **Deployment status (not for cloud)**
26 |
27 | depending on if vercel or render try to find out the latest time your service got deployed and share a screenshot.
28 |
29 |
30 |
31 | **Expected behavior**
32 |
33 | A clear and concise description of what you expected to happen.
34 |
35 | **Screenshots**
36 |
37 | A screenshots to help explain your problem.
38 |
39 |
40 | **Additional context**
41 |
42 | Add any other context about the problem here.
43 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build Obsidian Plugin
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'packages/plugin/**'
7 | pull_request:
8 | paths:
9 | - 'packages/plugin/**'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 |
18 | - name: Use Node.js
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: "18.x"
22 |
23 | - name: Install pnpm
24 | uses: pnpm/action-setup@v2
25 | with:
26 | version: 10.8.1
27 |
28 | - name: Install dependencies
29 | run: |
30 | cd packages/plugin
31 | pnpm install
32 |
33 | - name: Build plugin
34 | run: |
35 | cd packages/plugin
36 | GITHUB_ACTIONS=true pnpm build
37 |
38 | - name: Run tests
39 | run: |
40 | cd packages/plugin
41 | pnpm test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # VSCode
2 | .vscode/
3 |
4 | # IntelliJ
5 | *.iml
6 | .idea/
7 |
8 | # Build directories
9 | /dist/
10 | node_modules/
11 |
12 | # Compiled files
13 | main.js
14 |
15 | # Source maps
16 | *.map
17 |
18 | # Obsidian
19 | data.json
20 |
21 | # macOS Finder
22 | .DS_Store
23 |
24 | # Tailwind CSS
25 | /styles.css
26 |
27 | # Turbo build cache
28 | .turbo/
29 | **/.turbo/
30 | .vercel
31 | .env*.local
32 |
33 | # Expo native directories
34 | /android/
35 | /ios/
36 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | node-linker=hoisted
2 | catalog.react=18.3.0
3 | catalog.@types/react=19.0.10
4 | catalog.@types/react-dom=19.0.4
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false
4 | }
5 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Node.js runtime as the base image
2 | FROM node:20
3 |
4 | # Set the working directory in the container
5 | WORKDIR /app
6 |
7 | # Copy the rest of the application code to the working directory
8 | COPY . .
9 |
10 | # install pnpm
11 | RUN npm install -g pnpm
12 |
13 | # Set the working directory to the web package
14 | WORKDIR /app/packages/web
15 |
16 | # Install the application dependencies
17 | RUN pnpm install
18 |
19 | # Build the Next.js application
20 | RUN pnpm run build:self-host
21 |
22 | # Expose the port on which the application will run
23 | EXPOSE 3000
24 |
25 | # Set the command to run the application
26 | CMD ["pnpm", "run", "start"]
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Different AI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | app:
3 | build:
4 | context: .
5 | dockerfile: Dockerfile
6 | ports:
7 | - "3000:3000"
8 | environment:
9 | - OPENAI_API_KEY=${OPENAI_API_KEY}
10 | - SOLO_API_KEY=${SOLO_API_KEY}
11 | - MODEL_VISION=${MODEL_VISION}
12 | - MODEL_TEXT=${MODEL_TEXT}
13 | - MODEL_NAME=${MODEL_NAME}
14 | - MODEL_CLASSIFY=${MODEL_CLASSIFY}
15 | - MODEL_TAGGING=${MODEL_TAGGING}
16 | - MODEL_FOLDERS=${MODEL_FOLDERS}
17 |
18 | - OLLAMA_API_URL=${OLLAMA_API_URL}
19 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "fileorganizer2000",
3 | "name": "Note Companion (prev. File Organizer 2000)",
4 | "version": "2.2.1",
5 | "minAppVersion": "0.15.0",
6 | "description": "An AI assistant to organize and chat with your vault",
7 | "author": "Benjamin Ashgan Shafii",
8 | "isDesktopOnly": true
9 | }
--------------------------------------------------------------------------------
/memory/2024-07-30-sdk-implementation-failure-web-search.md:
--------------------------------------------------------------------------------
1 | # Memory: Use Web Search for Failed API/SDK Implementations
2 |
3 | Date: 2024-07-30
4 |
5 | ## Learning
6 |
7 | When attempting to implement functionality using an external API or SDK (like Clerk's backend client) and encountering repeated type errors or incorrect method usage despite multiple attempts based on assumed knowledge, it indicates a gap in understanding the specific API contract.
8 |
9 | ## Application
10 |
11 | In such situations, instead of looping through slightly different variations based on assumptions or linter errors alone, the next immediate step should be to **perform a targeted web search**.
12 |
13 | Example search queries:
14 | * "[Library/SDK Name] [Specific Task] example [Framework Name]"
15 | * e.g., "Clerk backend API token authentication example Next.js API route"
16 | * "[Library/SDK Name] authenticate request from header token"
17 | * Searching for the specific error message (e.g., "ClerkClient Property 'authenticateRequest' does not exist")
18 |
19 | This helps gather accurate, up-to-date documentation or community examples to correct the implementation, preventing wasted attempts and ensuring the correct API usage is found efficiently.
--------------------------------------------------------------------------------
/memory/2024-09-25-expo-run-command-directory.md:
--------------------------------------------------------------------------------
1 | # Expo Run Command Directory
2 |
3 | **Learned:** Running Expo CLI commands like `expo run:ios` (aliased as `pnpm run ios` or `pnpm run dev` in `packages/mobile/package.json`) must be done from the specific package directory (`packages/mobile`) or by targeting the package from the monorepo root using `pnpm --filter note-companion run ios`.
4 |
5 | **Problem:** Running these commands from subdirectories like `packages/mobile/ios` can cause an `Error: ENOENT: no such file or directory, uv_cwd` because the Expo CLI cannot find the project root correctly.
6 |
7 | **Solution:** Always ensure the current working directory is `packages/mobile` before running `pnpm run ios` or `pnpm run dev`, or use `pnpm --filter note-companion run ...` from the monorepo root.
8 |
9 | **Related:**
10 | - `packages/mobile/package.json` (scripts `ios`, `dev`)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "file-organizer-2000-monorepo",
3 | "private": true,
4 | "devDependencies": {
5 | "@types/node": "^22.13.10",
6 | "node-gyp": "^11.1.0",
7 | "turbo": "^2.4.4",
8 | "typescript": "^5.8.2"
9 | },
10 | "version": "1.0.0",
11 | "packageManager": "pnpm@10.8.1",
12 | "scripts": {
13 | "build": "turbo build",
14 | "dev": "turbo dev",
15 | "lint": "turbo lint",
16 | "test": "turbo test",
17 | "clean": "git clean -xdf node_modules",
18 | "android": "expo run:android",
19 | "ios": "expo run:ios"
20 | },
21 | "dependencies": {
22 | "react-markdown": "^10.1.0",
23 | "remark-gfm": "^4.0.1",
24 | "expo": "~52.0.43",
25 | "react": "18.3.1",
26 | "react-native": "0.76.9"
27 | },
28 | "pnpm": {
29 | "patchedDependencies": {
30 | "xcode@3.0.1": "patches/xcode@3.0.1.patch"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/audio-server/.env:
--------------------------------------------------------------------------------
1 |
2 | PORT=3001
3 |
--------------------------------------------------------------------------------
/packages/audio-server/.gitignore:
--------------------------------------------------------------------------------
1 | uploads/
2 |
3 | # Build artifacts
4 | .turbo
5 | *.tsbuildinfo
6 |
--------------------------------------------------------------------------------
/packages/audio-server/dist/server.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/packages/audio-server/nixpacks.toml:
--------------------------------------------------------------------------------
1 | [phases.setup]
2 | aptPkgs = ["ffmpeg"]
3 |
--------------------------------------------------------------------------------
/packages/audio-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@file-organizer/audio-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/server.js",
6 | "scripts": {
7 | "start": "node dist/server.js",
8 | "build": "tsc",
9 | "dev": "ts-node-dev --respawn --transpile-only src/server.ts"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@deepgram/sdk": "^3.5.0",
16 | "@unkey/api": "^0.23.0",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.4.5",
19 | "express": "^4.19.2",
20 | "morgan": "^1.10.0",
21 | "multer": "^1.4.5-lts.1",
22 | "openai": "^4.52.7"
23 | },
24 | "devDependencies": {
25 | "@types/cors": "^2.8.13",
26 | "@types/express": "^4.17.17",
27 | "@types/morgan": "^1.9.4",
28 | "@types/multer": "^1.4.7",
29 | "@types/node": "^18.15.11",
30 | "ts-node-dev": "^2.0.0",
31 | "typescript": "^5.0.4"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/audio-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es2018",
5 | "module": "commonjs",
6 | "outDir": "./dist",
7 | "rootDir": "./src",
8 | "composite": true
9 | },
10 | "include": ["src/**/*"],
11 | "exclude": ["node_modules"],
12 | "references": [
13 | { "path": "../shared" }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/landing/.env.example:
--------------------------------------------------------------------------------
1 | # Update these with your Supabase details from your project settings > API
2 | # https://app.supabase.com/project/_/settings/api
3 | NEXT_PUBLIC_SUPABASE_URL=your-project-url
4 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
5 |
6 | # Loops API key for newsletter and contact management
7 | LOOPS_API_KEY=your-loops-api-key
8 |
--------------------------------------------------------------------------------
/packages/landing/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/packages/landing/app/(landing)/components/grid-pattern.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/landing/app/(landing)/components/request-logo.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Image from 'next/image';
4 |
5 | export const RequestLogo = ({ className }: { className?: string }) => (
6 |
13 | );
--------------------------------------------------------------------------------
/packages/landing/app/(landing)/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Button } from "@/components/ui/button";
4 | import Link from "next/link";
5 | import { useEffect } from "react";
6 |
7 | export default function Error({
8 | error,
9 | reset,
10 | }: {
11 | error: Error & { digest?: string };
12 | reset: () => void;
13 | }) {
14 | useEffect(() => {
15 | console.error(error);
16 | }, [error]);
17 |
18 | return (
19 |
20 |
Something went wrong
21 |
22 | We apologize for the inconvenience. Please try again or go back to the homepage.
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
--------------------------------------------------------------------------------
/packages/landing/app/(landing)/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Button } from "@/components/ui/button";
4 | import Link from "next/link";
5 |
6 | export default function NotFound() {
7 | return (
8 |
9 |
404 - Page Not Found
10 |
11 | Sorry, we couldn't find the page you're looking for.
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
--------------------------------------------------------------------------------
/packages/landing/app/(landing)/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/app/(landing)/opengraph-image.png
--------------------------------------------------------------------------------
/packages/landing/app/PostHogPageView.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { usePathname, useSearchParams } from "next/navigation"
4 | import { useEffect, Suspense } from "react"
5 | import { usePostHog } from 'posthog-js/react'
6 |
7 | function PostHogPageView() {
8 | const pathname = usePathname()
9 | const searchParams = useSearchParams()
10 | const posthog = usePostHog()
11 |
12 | // Track pageviews
13 | useEffect(() => {
14 | if (pathname && posthog && typeof window !== 'undefined') {
15 | let url = window.origin + pathname
16 | if (searchParams && searchParams.toString()) {
17 | url = url + `?${searchParams.toString()}`
18 | }
19 |
20 | posthog.capture('$pageview', {
21 | '$current_url': url,
22 | path: pathname,
23 | })
24 | }
25 | }, [pathname, searchParams, posthog])
26 |
27 | return null
28 | }
29 |
30 | // Wrap this in Suspense to avoid the `useSearchParams` usage above
31 | // from de-opting the whole app into client-side rendering
32 | export default function SuspendedPostHogPageView() {
33 | return (
34 |
35 |
36 |
37 | )
38 | }
--------------------------------------------------------------------------------
/packages/landing/app/dao/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Button } from "@/components/ui/button";
4 | import Link from "next/link";
5 | import { useEffect } from "react";
6 |
7 | export default function Error({
8 | error,
9 | reset,
10 | }: {
11 | error: Error & { digest?: string };
12 | reset: () => void;
13 | }) {
14 | useEffect(() => {
15 | console.error(error);
16 | }, [error]);
17 |
18 | return (
19 |
20 |
Something went wrong
21 |
22 | We apologize for the inconvenience. Please try again or go back to the homepage.
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
--------------------------------------------------------------------------------
/packages/landing/app/dao/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Button } from "@/components/ui/button";
4 | import Link from "next/link";
5 |
6 | export default function NotFound() {
7 | return (
8 |
9 |
404 - Page Not Found
10 |
11 | Sorry, we couldn't find the page you're looking for.
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
--------------------------------------------------------------------------------
/packages/landing/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 |
6 |
7 | .tiptap p.is-editor-empty:first-child::before {
8 | color: #adb5bd;
9 | content: attr(data-placeholder);
10 | float: left;
11 | height: 0;
12 | pointer-events: none;
13 | }
--------------------------------------------------------------------------------
/packages/landing/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { GeistSans } from "geist/font/sans";
2 | import "./globals.css";
3 | import { Metadata } from "next";
4 | import Providers from "./providers";
5 |
6 | export const metadata: Metadata = {
7 | metadataBase: new URL(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "https://notecompanion.com"),
8 | title: {
9 | default: "Note Companion",
10 | template: "%s | Note Companion",
11 | },
12 | description: "Your AI-powered assistant for Obsidian.",
13 | icons: {
14 | icon: "/favicon.ico",
15 | shortcut: "/favicon.ico",
16 | apple: "/favicon.ico",
17 | },
18 | };
19 |
20 | export default function RootLayout({
21 | children,
22 | }: {
23 | children: React.ReactNode;
24 | }) {
25 | return (
26 |
27 |
28 | {children}
29 |
30 |
31 | );
32 | }
--------------------------------------------------------------------------------
/packages/landing/app/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/app/opengraph-image.png
--------------------------------------------------------------------------------
/packages/landing/app/twitter-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/app/twitter-image.png
--------------------------------------------------------------------------------
/packages/landing/app/types.ts:
--------------------------------------------------------------------------------
1 | export type AttentionPower = 'Squirrel' | 'Caffeinated' | 'Hyperfocus' | 'Time Lord'
2 |
3 | export type Link = {
4 | url: string;
5 | title: string;
6 | }
7 |
8 | export type SubTask = {
9 | id: number;
10 | name: string;
11 | description: string;
12 | completed: boolean;
13 | timeEstimate: number;
14 | }
15 |
16 | export type CreateTask = {
17 | name: string;
18 | description: string;
19 | detailNeeded: boolean;
20 | attentionPower: AttentionPower;
21 | timeEstimate: number;
22 | points: number;
23 | }
24 |
25 |
26 | export interface Task extends CreateTask {
27 | id?: string;
28 | name: string;
29 | description: string;
30 | detailNeeded: boolean;
31 | attentionPower: AttentionPower;
32 | timeEstimate: number;
33 | points: number;
34 | parentTaskId: string | null;
35 | details?: string;
36 | status: 'active' | 'completed' | 'archived' | 'ignored';
37 | importance: string;
38 | rawMessage: string;
39 | links: Link[] | null;
40 | isMainTask: boolean | null;
41 | }
42 |
43 | export type UserStats = {
44 | points: number;
45 | level: number;
46 | streak: number;
47 | }
48 |
49 | export type ProcessedObject = {
50 | tasks: Task[];
51 |
52 | };
53 |
--------------------------------------------------------------------------------
/packages/landing/app/utils/transcription.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | export const transcriptionSchema = z.object({
4 | text: z.string(),
5 | });
6 |
7 | export type TranscriptionResponse = z.infer;
8 |
9 | export const transcribeAudio = async (blob: Blob): Promise => {
10 | const formData = new FormData();
11 | formData.append('file', blob, 'recording.webm');
12 | formData.append('model', 'whisper-1');
13 |
14 | const response = await fetch('/api/transcribe', {
15 | method: 'POST',
16 | body: formData,
17 | });
18 |
19 | const data = await response.json();
20 | const parsed = transcriptionSchema.safeParse(data);
21 |
22 | if (!parsed.success) {
23 | throw new Error('Invalid transcription response');
24 | }
25 |
26 | return parsed.data;
27 | };
--------------------------------------------------------------------------------
/packages/landing/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/landing/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from 'next/link'
4 | import { usePathname } from 'next/navigation'
5 | import { Home, Settings } from 'lucide-react'
6 |
7 | export function Navbar() {
8 | const pathname = usePathname()
9 |
10 | return (
11 |
23 | )
24 | }
--------------------------------------------------------------------------------
/packages/landing/components/tutorial/tutorial-step.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from "../ui/checkbox";
2 |
3 | export function TutorialStep({
4 | title,
5 | children,
6 | }: {
7 | title: string;
8 | children: React.ReactNode;
9 | }) {
10 | return (
11 |
12 |
17 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/packages/landing/components/typography/inline-code.tsx:
--------------------------------------------------------------------------------
1 | export function TypographyInlineCode() {
2 | return (
3 |
4 | @radix-ui/react-alert-dialog
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 | import { cn } from "@/lib/utils"
4 |
5 | const badgeVariants = cva(
6 | "inline-flex items-center rounded-full 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",
7 | {
8 | variants: {
9 | variant: {
10 | default:
11 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
12 | secondary:
13 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14 | destructive:
15 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
16 | outline: "text-foreground",
17 | },
18 | },
19 | defaultVariants: {
20 | variant: "default",
21 | },
22 | }
23 | )
24 |
25 | export interface BadgeProps
26 | extends React.HTMLAttributes,
27 | VariantProps {}
28 |
29 | function Badge({ className, variant, ...props }: BadgeProps) {
30 | return (
31 |
32 | )
33 | }
34 |
35 | export { Badge, badgeVariants }
36 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SwitchPrimitives from "@radix-ui/react-switch";
5 | import { cn } from "../../lib/utils";
6 |
7 | const Switch = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
24 |
25 | ));
26 | Switch.displayName = SwitchPrimitives.Root.displayName;
27 |
28 | export { Switch };
--------------------------------------------------------------------------------
/packages/landing/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/hooks/use-toast"
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | } from "@/components/ui/toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 | import { cn } from "@/utils/cn"
6 |
7 | const TooltipProvider = TooltipPrimitive.Provider as React.FC
8 | const Tooltip = TooltipPrimitive.Root
9 | const TooltipTrigger = TooltipPrimitive.Trigger
10 |
11 | const TooltipContent = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, sideOffset = 4, ...props }, ref) => (
15 |
24 | )) as React.FC>
25 |
26 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
27 |
--------------------------------------------------------------------------------
/packages/landing/components/ui/use-toast.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | type ToastProps,
12 | type ToastActionElement,
13 | } from '../../components/ui/toast';
14 | import { useToast } from '../../hooks/use-toast';
15 |
16 | export function Toaster() {
17 | const { toasts } = useToast();
18 |
19 | return (
20 |
21 | {toasts.map(function ({ id, title, description, action, ...props }) {
22 | return (
23 |
24 |
25 | {title && {title}}
26 | {description && (
27 | {description}
28 | )}
29 |
30 | {action}
31 |
32 |
33 | );
34 | })}
35 |
36 |
37 | );
38 | }
39 |
40 | export {
41 | ToastProvider,
42 | ToastViewport,
43 | Toast,
44 | ToastTitle,
45 | ToastDescription,
46 | ToastClose,
47 | };
48 | export { useToast } from '../../hooks/use-toast';
49 | export type { ToastProps, ToastActionElement };
50 |
--------------------------------------------------------------------------------
/packages/landing/components/un-30.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/packages/landing/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
--------------------------------------------------------------------------------
/packages/landing/middleware.ts:
--------------------------------------------------------------------------------
1 | import { type NextRequest } from "next/server";
2 |
3 | export async function middleware(request: NextRequest) {
4 | }
5 |
6 | export const config = {
7 | matcher: [
8 | /*
9 | * Match all request paths except:
10 | * - _next/static (static files)
11 | * - _next/image (image optimization files)
12 | * - favicon.ico (favicon file)
13 | * - images - .svg, .png, .jpg, .jpeg, .gif, .webp
14 | * Feel free to modify this pattern to include more paths.
15 | */
16 | "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/packages/landing/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | async rewrites() {
4 | return [
5 | {
6 | source: "/ingest/static/:path*",
7 | destination: "https://us-assets.i.posthog.com/static/:path*",
8 | },
9 | {
10 | source: "/ingest/:path*",
11 | destination: "https://us.i.posthog.com/:path*",
12 | },
13 | {
14 | source: "/ingest/decide",
15 | destination: "https://us.i.posthog.com/decide",
16 | },
17 | ];
18 | },
19 | skipTrailingSlashRedirect: true,
20 | images: {
21 | domains: ['framerusercontent.com'],
22 | },
23 | // Skip generating static error pages that are causing issues
24 | typescript: {
25 | // Dangerously allow production builds to successfully complete even if
26 | // your project has type errors.
27 | ignoreBuildErrors: true,
28 | },
29 | eslint: {
30 | // Warning: This allows production builds to successfully complete even if
31 | // your project has ESLint errors.
32 | ignoreDuringBuilds: true,
33 | },
34 | };
35 |
36 | module.exports = nextConfig;
37 |
--------------------------------------------------------------------------------
/packages/landing/pages/_error.js:
--------------------------------------------------------------------------------
1 | function Error({ statusCode }) {
2 | return (
3 |
4 |
5 | {statusCode
6 | ? `An error ${statusCode} occurred on server`
7 | : 'An error occurred on client'}
8 |
9 |
10 | );
11 | }
12 |
13 | Error.getInitialProps = ({ res, err }) => {
14 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
15 | return { statusCode };
16 | };
17 |
18 | export default Error;
--------------------------------------------------------------------------------
/packages/landing/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/packages/landing/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/public/favicon.ico
--------------------------------------------------------------------------------
/packages/landing/public/images/after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/public/images/after.png
--------------------------------------------------------------------------------
/packages/landing/public/images/before.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/public/images/before.jpg
--------------------------------------------------------------------------------
/packages/landing/public/note_companion_logo_bright.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/public/note_companion_logo_bright.jpg
--------------------------------------------------------------------------------
/packages/landing/public/notecompanion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/public/notecompanion.png
--------------------------------------------------------------------------------
/packages/landing/public/screenpipe-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/public/screenpipe-logo.png
--------------------------------------------------------------------------------
/packages/landing/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | },
24 | "typeRoots": ["./types", "./node_modules/@types"]
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "types/**/*.d.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/packages/landing/types/posthog.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'posthog-js' {
2 | const posthog: any;
3 | export default posthog;
4 | }
5 |
6 | declare module 'posthog-js/react' {
7 | import { ReactNode } from 'react';
8 |
9 | export function PostHogProvider({ children, client }: { children: ReactNode; client: any }): JSX.Element;
10 | export function usePostHog(): any;
11 | }
--------------------------------------------------------------------------------
/packages/landing/utils/cn.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 |
--------------------------------------------------------------------------------
/packages/landing/utils/favi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/utils/favi.png
--------------------------------------------------------------------------------
/packages/landing/utils/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/landing/utils/og.png
--------------------------------------------------------------------------------
/packages/landing/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | /**
4 | * Redirects to a specified path with an encoded message as a query parameter.
5 | * @param {('error' | 'success')} type - The type of message, either 'error' or 'success'.
6 | * @param {string} path - The path to redirect to.
7 | * @param {string} message - The message to be encoded and added as a query parameter.
8 | * @returns {never} This function doesn't return as it triggers a redirect.
9 | */
10 | export function encodedRedirect(
11 | type: "error" | "success",
12 | path: string,
13 | message: string,
14 | ) {
15 | return redirect(`${path}?${type}=${encodeURIComponent(message)}`);
16 | }
17 |
--------------------------------------------------------------------------------
/packages/mobile/.env:
--------------------------------------------------------------------------------
1 | EXPO_PUBLIC_API_URL=http://localhost:3010
2 | EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_bWFnbmV0aWMtYnJlYW0tMjQuY2xlcmsuYWNjb3VudHMuZGV2JA
3 | EXPO_PUBLIC_UPGRADE_CHECKOUT_URL=http://localhost:3010/upgrade-from-mobile
--------------------------------------------------------------------------------
/packages/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | ios/
7 | android/
8 |
9 | # Expo
10 | .expo/
11 | dist/
12 | web-build/
13 | expo-env.d.ts
14 | *.ipa
15 |
16 | # Native
17 | *.orig.*
18 | *.jks
19 | *.p8
20 | *.p12
21 | *.key
22 | *.mobileprovision
23 |
24 | # Native folders for CNG/Prebuild
25 | /android/
26 | /ios/
27 |
28 | # Metro
29 | .metro-health-check*
30 |
31 | # debug
32 | npm-debug.*
33 | yarn-debug.*
34 | yarn-error.*
35 |
36 | # macOS
37 | .DS_Store
38 | *.pem
39 |
40 | # local env files
41 | .env*.local
42 |
43 | # typescript
44 | *.tsbuildinfo
45 |
46 | app-example
47 |
--------------------------------------------------------------------------------
/packages/mobile/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
--------------------------------------------------------------------------------
/packages/mobile/app/(tabs)/camera.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 |
4 | // This is a placeholder file to satisfy Expo Router.
5 | // The actual camera functionality is triggered by the custom tab bar button.
6 | export default function CameraScreenPlaceholder() {
7 | return (
8 |
9 | Camera Placeholder
10 |
11 | );
12 | }
--------------------------------------------------------------------------------
/packages/mobile/app/+not-found.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Stack } from 'expo-router';
2 | import { StyleSheet, Text, View } from 'react-native';
3 |
4 | export default function NotFoundScreen() {
5 | return (
6 | <>
7 |
8 |
9 | This screen doesn't exist.
10 |
11 | Go to home screen!
12 |
13 |
14 | >
15 | );
16 | }
17 |
18 | const styles = StyleSheet.create({
19 | container: {
20 | flex: 1,
21 | alignItems: 'center',
22 | justifyContent: 'center',
23 | padding: 20,
24 | backgroundColor: '#fff',
25 | },
26 | title: {
27 | fontSize: 20,
28 | fontWeight: 'bold',
29 | color: '#1a1a1a',
30 | },
31 | link: {
32 | marginTop: 15,
33 | paddingVertical: 15,
34 | },
35 | linkText: {
36 | color: '#007AFF',
37 | fontSize: 16,
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/packages/mobile/app/shared.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useLocalSearchParams, useRouter } from 'expo-router';
3 | import { SharedFileViewer } from '../components/SharedFileViewer';
4 | import { View, StyleSheet } from 'react-native';
5 |
6 | export default function SharedScreen() {
7 | const { url } = useLocalSearchParams();
8 | const router = useRouter();
9 |
10 | useEffect(() => {
11 | if (!url) {
12 | // If no URL is provided, go back to the home screen
13 | router.replace('/');
14 | }
15 | }, [url, router]);
16 |
17 | return (
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | const styles = StyleSheet.create({
25 | container: {
26 | flex: 1,
27 | backgroundColor: '#fff',
28 | },
29 | });
--------------------------------------------------------------------------------
/packages/mobile/assets/big-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/big-logo.png
--------------------------------------------------------------------------------
/packages/mobile/assets/einstein-document.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/einstein-document.jpg
--------------------------------------------------------------------------------
/packages/mobile/assets/fAp56rVs5e5ZsfXD4Mmgki-1000-80.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/fAp56rVs5e5ZsfXD4Mmgki-1000-80.jpg
--------------------------------------------------------------------------------
/packages/mobile/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/packages/mobile/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/icon.png
--------------------------------------------------------------------------------
/packages/mobile/assets/icon_backup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/icon_backup.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/app-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/app-demo.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/app-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/app-icon.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/favicon.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/icon.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/partial-react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/partial-react-logo.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/react-logo.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/react-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/react-logo@2x.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/react-logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/react-logo@3x.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/splash-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/splash-icon.png
--------------------------------------------------------------------------------
/packages/mobile/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/images/splash.png
--------------------------------------------------------------------------------
/packages/mobile/assets/splash-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/splash-white.png
--------------------------------------------------------------------------------
/packages/mobile/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/assets/splash.png
--------------------------------------------------------------------------------
/packages/mobile/babel.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function (api) {
3 | api.cache(true);
4 | return {
5 | presets: [
6 | ["babel-preset-expo", { jsxImportSource: "nativewind" }],
7 | "nativewind/babel",
8 | ],
9 | plugins: [
10 | [
11 | "module-resolver",
12 | {
13 | root: ["."],
14 | alias: {
15 | "@": "./",
16 | },
17 | },
18 | ],
19 | ],
20 | };
21 | };
--------------------------------------------------------------------------------
/packages/mobile/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'expo-router';
2 | import { openBrowserAsync } from 'expo-web-browser';
3 | import { type ComponentProps } from 'react';
4 | import { Platform } from 'react-native';
5 |
6 | type Props = Omit, 'href'> & { href: string };
7 |
8 | export function ExternalLink({ href, ...rest }: Props) {
9 | return (
10 | {
15 | if (Platform.OS !== 'web') {
16 | // Prevent the default behavior of linking to the default browser on native.
17 | event.preventDefault();
18 | // Open the link in an in-app browser.
19 | await openBrowserAsync(href);
20 | }
21 | }}
22 | />
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/packages/mobile/components/HapticTab.tsx:
--------------------------------------------------------------------------------
1 | import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs';
2 | import { Pressable } from 'react-native';
3 | import * as Haptics from 'expo-haptics';
4 | import { Platform } from 'react-native';
5 |
6 | export function HapticTab(props: BottomTabBarButtonProps) {
7 | return (
8 | {
11 | if (Platform.OS === 'ios') {
12 | // Add a soft haptic feedback when pressing down on the tabs.
13 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
14 | }
15 | props.onPressIn?.(ev);
16 | }}
17 | style={[props.style, { flex: 1 }]}
18 | />
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/packages/mobile/components/HelloWave.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { StyleSheet } from 'react-native';
3 | import Animated, {
4 | useSharedValue,
5 | useAnimatedStyle,
6 | withTiming,
7 | withRepeat,
8 | withSequence,
9 | } from 'react-native-reanimated';
10 |
11 | import { ThemedText } from '@/components/ThemedText';
12 |
13 | export function HelloWave() {
14 | const rotationAnimation = useSharedValue(0);
15 |
16 | useEffect(() => {
17 | rotationAnimation.value = withRepeat(
18 | withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
19 | 4 // Run the animation 4 times
20 | );
21 | }, []);
22 |
23 | const animatedStyle = useAnimatedStyle(() => ({
24 | transform: [{ rotate: `${rotationAnimation.value}deg` }],
25 | }));
26 |
27 | return (
28 |
29 | 👋
30 |
31 | );
32 | }
33 |
34 | const styles = StyleSheet.create({
35 | text: {
36 | fontSize: 28,
37 | lineHeight: 32,
38 | marginTop: -6,
39 | },
40 | });
41 |
--------------------------------------------------------------------------------
/packages/mobile/components/SharedFileViewer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, StyleSheet } from 'react-native';
3 |
4 | interface SharedFileViewerProps {
5 | fileUri?: string;
6 | }
7 |
8 | export const SharedFileViewer: React.FC = ({ fileUri }) => {
9 | if (!fileUri) {
10 | return (
11 |
12 | No file shared
13 |
14 | );
15 | }
16 |
17 | return (
18 |
19 | Shared File:
20 | {fileUri}
21 |
22 | );
23 | };
24 |
25 | const styles = StyleSheet.create({
26 | container: {
27 | flex: 1,
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | padding: 20,
31 | },
32 | text: {
33 | fontSize: 18,
34 | marginBottom: 10,
35 | },
36 | fileUri: {
37 | fontSize: 14,
38 | color: '#666',
39 | textAlign: 'center',
40 | },
41 | });
--------------------------------------------------------------------------------
/packages/mobile/components/SignInWithOAuth.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, View, StyleSheet } from "react-native";
3 | import { useOAuth } from "@clerk/clerk-expo";
4 | import * as WebBrowser from "expo-web-browser";
5 |
6 | WebBrowser.maybeCompleteAuthSession();
7 |
8 | export function SignInWithOAuth() {
9 | const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" });
10 |
11 | const onPress = React.useCallback(async () => {
12 | try {
13 | const { createdSessionId, signIn, signUp, setActive } =
14 | await startOAuthFlow();
15 |
16 | if (createdSessionId) {
17 | setActive?.({ session: createdSessionId });
18 | } else {
19 | // Use signIn or signUp for next steps such as MFA
20 | }
21 | } catch (err) {
22 | console.error("OAuth error", err);
23 | }
24 | }, []);
25 |
26 | return (
27 |
28 |
32 |
33 | );
34 | }
35 |
36 | const styles = StyleSheet.create({
37 | container: {
38 | marginTop: 10,
39 | },
40 | });
--------------------------------------------------------------------------------
/packages/mobile/components/__tests__/ThemedText-test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import renderer from 'react-test-renderer';
3 |
4 | import { ThemedText } from '../ThemedText';
5 |
6 | it(`renders correctly`, () => {
7 | const tree = renderer.create(Snapshot test!).toJSON();
8 |
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/packages/mobile/components/__tests__/__snapshots__/ThemedText-test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
22 | Snapshot test!
23 |
24 | `;
25 |
--------------------------------------------------------------------------------
/packages/mobile/components/ui/IconSymbol.ios.tsx:
--------------------------------------------------------------------------------
1 | import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
2 | import { StyleProp, ViewStyle } from 'react-native';
3 |
4 | export function IconSymbol({
5 | name,
6 | size = 24,
7 | color,
8 | style,
9 | weight = 'regular',
10 | }: {
11 | name: SymbolViewProps['name'];
12 | size?: number;
13 | color: string;
14 | style?: StyleProp;
15 | weight?: SymbolWeight;
16 | }) {
17 | return (
18 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/packages/mobile/components/ui/TabBarBackground.ios.tsx:
--------------------------------------------------------------------------------
1 | import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
2 | import { View, StyleSheet } from 'react-native';
3 | import { useSafeAreaInsets } from 'react-native-safe-area-context';
4 | import { useSemanticColor } from '@/hooks/useThemeColor';
5 |
6 | const TabBarBackground = () => {
7 | const backgroundColor = useSemanticColor('tabBar');
8 |
9 | return (
10 |
16 | );
17 | };
18 |
19 | export default TabBarBackground;
20 |
21 | export function useBottomTabOverflow() {
22 | const tabHeight = useBottomTabBarHeight();
23 | const { bottom } = useSafeAreaInsets();
24 | return tabHeight - bottom;
25 | }
26 |
--------------------------------------------------------------------------------
/packages/mobile/components/ui/TabBarBackground.tsx:
--------------------------------------------------------------------------------
1 | import { View } from 'react-native';
2 | import { useSemanticColor } from '@/hooks/useThemeColor';
3 |
4 | // This is a shim for web and Android where the tab bar is generally opaque.
5 | const TabBarBackground = () => {
6 | const backgroundColor = useSemanticColor('tabBar');
7 | return ;
8 | };
9 |
10 | export default TabBarBackground;
11 |
12 | export function useBottomTabOverflow() {
13 | return 0;
14 | }
15 |
--------------------------------------------------------------------------------
/packages/mobile/constants/config.ts:
--------------------------------------------------------------------------------
1 | // Get the API URL from environment variables or use a default for development
2 | export const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:3010';
3 |
4 | // Add retry configuration for API requests
5 | export const API_CONFIG = {
6 | maxRetries: 3,
7 | retryDelay: 1000,
8 | timeout: 60000, // 60 seconds timeout
9 | };
--------------------------------------------------------------------------------
/packages/mobile/converted_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/converted_icon.png
--------------------------------------------------------------------------------
/packages/mobile/eas.json:
--------------------------------------------------------------------------------
1 | {
2 | "cli": {
3 | "version": ">= 7.6.0",
4 | "appVersionSource": "remote"
5 | },
6 | "build": {
7 | "development": {
8 | "developmentClient": true,
9 | "distribution": "internal"
10 | },
11 | "preview": {
12 | "distribution": "internal"
13 | },
14 | "preview-apk": {
15 | "distribution": "internal",
16 | "android": {
17 | "buildType": "apk"
18 | }
19 | },
20 | "production": {
21 | "autoIncrement": true
22 | }
23 | },
24 | "submit": {
25 | "production": {
26 | "ios": {
27 | "ascAppId": "6743101867",
28 | "appleTeamId": "297RER2M96"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/mobile/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app
2 | # apple_id("[[APPLE_ID]]") # Your Apple Developer Portal username
3 |
4 |
5 | # For more information about the Appfile, see:
6 | # https://docs.fastlane.tools/advanced/#appfile
7 |
--------------------------------------------------------------------------------
/packages/mobile/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | # This file contains the fastlane.tools configuration
2 | # You can find the documentation at https://docs.fastlane.tools
3 | #
4 | # For a list of all available actions, check out
5 | #
6 | # https://docs.fastlane.tools/actions
7 | #
8 | # For a list of all available plugins, check out
9 | #
10 | # https://docs.fastlane.tools/plugins/available-plugins
11 | #
12 |
13 | # Uncomment the line if you want fastlane to automatically update itself
14 | # update_fastlane
15 |
16 | default_platform(:ios)
17 |
18 | platform :ios do
19 | desc "Description of what the lane does"
20 | lane :custom_lane do
21 | # add actions here: https://docs.fastlane.tools/actions
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/packages/mobile/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/favicon.ico
--------------------------------------------------------------------------------
/packages/mobile/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/packages/mobile/hooks/useColorScheme.ts:
--------------------------------------------------------------------------------
1 | export { useColorScheme } from 'react-native';
2 |
--------------------------------------------------------------------------------
/packages/mobile/hooks/useColorScheme.web.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useColorScheme as useRNColorScheme } from 'react-native';
3 |
4 | /**
5 | * To support static rendering, this value needs to be re-calculated on the client side for web
6 | */
7 | export function useColorScheme() {
8 | const [hasHydrated, setHasHydrated] = useState(false);
9 |
10 | useEffect(() => {
11 | setHasHydrated(true);
12 | }, []);
13 |
14 | const colorScheme = useRNColorScheme();
15 |
16 | if (hasHydrated) {
17 | return colorScheme;
18 | }
19 |
20 | return 'light';
21 | }
22 |
--------------------------------------------------------------------------------
/packages/mobile/hooks/useWarmUpBrowser.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import * as WebBrowser from "expo-web-browser";
3 |
4 | export const useWarmUpBrowser = () => {
5 | useEffect(() => {
6 | void WebBrowser.warmUpAsync();
7 | return () => {
8 | void WebBrowser.coolDownAsync();
9 | };
10 | }, []);
11 | };
--------------------------------------------------------------------------------
/packages/mobile/index.ts:
--------------------------------------------------------------------------------
1 | import "expo-router/entry";
--------------------------------------------------------------------------------
/packages/mobile/metro.config.js:
--------------------------------------------------------------------------------
1 | const { getDefaultConfig } = require("expo/metro-config");
2 | const { withNativeWind } = require('nativewind/metro');
3 |
4 | const config = getDefaultConfig(__dirname)
5 |
6 | module.exports = withNativeWind(config, { input: './global.css' })
--------------------------------------------------------------------------------
/packages/mobile/nativewind-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
--------------------------------------------------------------------------------
/packages/mobile/patches/react-native-fabric-fix.patch:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Meta Platforms, Inc. and affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "RCTThirdPartyFabricComponentsProvider.h"
9 |
10 | namespace facebook {
11 | namespace react {
12 |
13 | void ThirdPartyFabricComponentsProvider::registerThirdPartyComponentsIfNeeded() {}
14 |
15 | } // namespace react
16 | } // namespace facebook
--------------------------------------------------------------------------------
/packages/mobile/patches/react-native@0.76.7.patch:
--------------------------------------------------------------------------------
1 | diff --git a/scripts/cocoapods/utils.rb b/scripts/cocoapods/utils.rb
2 | index 1c42dd3..2c46dd4 100644
3 | --- a/scripts/cocoapods/utils.rb
4 | +++ b/scripts/cocoapods/utils.rb
5 | @@ -156,7 +156,7 @@ class ReactNativePodsUtils
6 | # ']' and that crashes the pod install. Let's make sure we escape it properly.
7 | flags.gsub!('[', '\\[')
8 | flags.gsub!(']', '\\]')
9 | -
10 | +
11 | return {
12 | :react_native_path => react_native_path,
13 | :fabric_enabled => fabric_enabled,
--------------------------------------------------------------------------------
/packages/mobile/patches/scripts/create-fabric-provider.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/mobile/patches/scripts/create-fabric-provider.js
--------------------------------------------------------------------------------
/packages/mobile/patches/xcode+3.0.1.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/xcode/lib/pbxProject.js b/node_modules/xcode/lib/pbxProject.js
2 | index 068548a..b478056 100644
3 | --- a/node_modules/xcode/lib/pbxProject.js
4 | +++ b/node_modules/xcode/lib/pbxProject.js
5 | @@ -1678,8 +1678,7 @@ function correctForFrameworksPath(file, project) {
6 |
7 | function correctForPath(file, project, group) {
8 | var r_group_dir = new RegExp('^' + group + '[\\\\/]');
9 | -
10 | - if (project.pbxGroupByName(group).path)
11 | + if (project.pbxGroupByName(group)&&project.pbxGroupByName(group).path)
12 | file.path = file.path.replace(r_group_dir, '');
13 |
14 | return file;
--------------------------------------------------------------------------------
/packages/mobile/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | // NOTE: Update this to include the paths to all of your component files.
4 | content: ["./app/**/*.{js,jsx,ts,tsx}"],
5 | presets: [require("nativewind/preset")],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
--------------------------------------------------------------------------------
/packages/mobile/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
5 | "types": ["react", "react-native", "nativewind/types"],
6 | "incremental": true,
7 | "strict": true,
8 | "baseUrl": ".",
9 |
10 | "typeRoots": ["./node_modules/@types"],
11 |
12 | "paths": {
13 | "@/*": ["./*"]
14 | }
15 | },
16 | "exclude": ["node_modules"]
17 | }
--------------------------------------------------------------------------------
/packages/plugin/.gitignore:
--------------------------------------------------------------------------------
1 | # Build output
2 | dist/
3 |
4 | # Development output
5 | main.js
6 | styles.css
7 | data.json
8 |
9 | # Build artifacts
10 | .turbo
11 | *.tsbuildinfo
--------------------------------------------------------------------------------
/packages/plugin/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "arrowParens": "avoid"
5 | }
6 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/checkout-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState } from "react";
3 | import { Button } from "./button";
4 | import { useUser } from "@clerk/nextjs";
5 | import { useRouter } from "next/navigation";
6 |
7 |
8 | export default function CheckoutButton() {
9 | const [loading, setLoading] = useState(false);
10 | const { isLoaded } = useUser();
11 | const router = useRouter();
12 |
13 | const handleCheckout = async () => {
14 | setLoading(true);
15 |
16 | router.push("/dashboard/pricing");
17 |
18 | setLoading(false);
19 | };
20 | if (!isLoaded) {
21 | return ;
22 | }
23 |
24 | return (
25 | // width: max-content;
26 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/icons.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from "react";
2 |
3 | // ... existing code ...
4 |
5 | export function ArrowDownIcon(props: SVGProps) {
6 | return (
7 |
20 | );
21 | }
22 |
23 | export function ArrowRightIcon(props: SVGProps) {
24 | return (
25 |
38 | );
39 | }
40 |
41 | // ... existing code ...
42 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | export type InputProps = React.InputHTMLAttributes;
6 |
7 | const Input = React.forwardRef(
8 | ({ className, type, ...props }, ref) => {
9 | return (
10 |
19 | );
20 | },
21 | );
22 | Input.displayName = "Input";
23 |
24 | export { Input };
25 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { type VariantProps, cva } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef & VariantProps
16 | >(({ className, ...props }, ref) => (
17 |
18 | ));
19 | Label.displayName = LabelPrimitive.Root.displayName;
20 |
21 | export { Label };
22 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/logo.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export default function Logo() {
4 | return (
5 |
6 |
10 |
11 |
12 |
21 |
25 | Note Companion
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "./utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/packages/plugin/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 | import { cn } from "@/lib/utils"
6 |
7 | const Separator = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(
11 | (
12 | { className, orientation = "horizontal", decorative = true, ...props },
13 | ref
14 | ) => (
15 |
26 | )
27 | )
28 | Separator.displayName = SeparatorPrimitive.Root.displayName
29 |
30 | export { Separator }
--------------------------------------------------------------------------------
/packages/plugin/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SwitchPrimitives from "@radix-ui/react-switch";
5 | import { cn } from "@/lib/utils";
6 |
7 | const Switch = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
24 |
25 | ));
26 | Switch.displayName = SwitchPrimitives.Root.displayName;
27 |
28 | export { Switch };
--------------------------------------------------------------------------------
/packages/plugin/components/ui/utils.tsx:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 | import React from 'react';
4 |
5 | export function cn(...inputs: ClassValue[]) {
6 | return twMerge(clsx(inputs));
7 | }
8 |
9 | /**
10 | * Style-isolated container component
11 | *
12 | * This component wraps others with proper class isolation to prevent
13 | * Obsidian styles from affecting our custom components.
14 | */
15 | export function StyledContainer({
16 | children,
17 | className,
18 | ...props
19 | }: React.HTMLAttributes) {
20 | return (
21 |
22 | {children}
23 |
24 | );
25 | }
--------------------------------------------------------------------------------
/packages/plugin/components/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
--------------------------------------------------------------------------------
/packages/plugin/constants.ts:
--------------------------------------------------------------------------------
1 | import { Notice } from "obsidian";
2 |
3 | export const VALID_IMAGE_EXTENSIONS = [
4 | "png",
5 | "jpg",
6 | "jpeg",
7 | "gif",
8 | "svg",
9 | "webp",
10 | ];
11 |
12 | export const VALID_AUDIO_EXTENSIONS = [
13 | "mp3",
14 | "mp4",
15 | "mpeg",
16 | "mpga",
17 | "m4a",
18 | "wav",
19 | "webm",
20 | ];
21 |
22 | export const VALID_MEDIA_EXTENSIONS = [
23 | ...VALID_IMAGE_EXTENSIONS,
24 | ...VALID_AUDIO_EXTENSIONS,
25 | "pdf",
26 | ];
27 |
28 | export const VALID_TEXT_EXTENSIONS = ["md", "txt"];
29 |
30 | export const VALID_EXTENSIONS = [
31 | ...VALID_MEDIA_EXTENSIONS,
32 | ...VALID_TEXT_EXTENSIONS,
33 | "pdf",
34 | ];
35 |
36 | /**
37 | * Validates if a given file extension is supported by FileOrganizer
38 | * @param extension - The file extension to validate (without the dot)
39 | * @returns boolean indicating if the extension is supported
40 | */
41 | export const isValidExtension = (extension: string): boolean => {
42 | const isSupported = VALID_EXTENSIONS.includes(extension);
43 | if (!isSupported) {
44 | new Notice("Sorry, FileOrganizer does not support this file type.");
45 | }
46 | return isSupported;
47 | };
48 |
--------------------------------------------------------------------------------
/packages/plugin/handlers/commandHandlers.ts:
--------------------------------------------------------------------------------
1 | import { WorkspaceLeaf } from "obsidian";
2 | import FileOrganizer from "../index";
3 | import { ORGANIZER_VIEW_TYPE, AssistantViewWrapper } from "../views/assistant/view";
4 | import { App } from "obsidian";
5 |
6 | export function initializeOrganizer(plugin: FileOrganizer) {
7 | plugin.registerView(
8 | ORGANIZER_VIEW_TYPE,
9 | (leaf: WorkspaceLeaf) => new AssistantViewWrapper(leaf, plugin)
10 | );
11 |
12 | plugin.addRibbonIcon("sparkle", "Note Companion", () => {
13 | plugin.ensureAssistantView();
14 | });
15 | }
16 |
17 | export function initializeFileOrganizationCommands(plugin: FileOrganizer) {
18 | plugin.addCommand({
19 | id: "add-to-inbox",
20 | name: "Put in inbox",
21 | callback: async () => {
22 | const activeFile = plugin.app.workspace.getActiveFile();
23 | if (activeFile) {
24 | await plugin.app.vault.rename(activeFile, `${plugin.settings.pathToWatch}/${activeFile.name}`);
25 | }
26 | },
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/packages/plugin/handlers/eventHandlers.ts:
--------------------------------------------------------------------------------
1 | import { Notice, TFile } from "obsidian";
2 | import FileOrganizer from "..";
3 | import { Inbox } from "../inbox";
4 |
5 | export function registerEventHandlers(plugin: FileOrganizer) {
6 | plugin.registerEvent(
7 | plugin.app.vault.on("create", async file => {
8 | // wait 1s
9 | await new Promise(resolve => setTimeout(resolve, 1000));
10 | if (!file.path.includes(plugin.settings.pathToWatch)) return;
11 | if (file instanceof TFile) {
12 | new Notice("Inbox is looking at new file: " + file.basename);
13 | Inbox.getInstance().enqueueFiles([file]);
14 | }
15 | })
16 | );
17 |
18 | plugin.registerEvent(
19 | plugin.app.vault.on("rename", async (file, oldPath) => {
20 | // wait 1s
21 | await new Promise(resolve => setTimeout(resolve, 1000));
22 |
23 | if (!file.path.includes(plugin.settings.pathToWatch)) return;
24 | if (file instanceof TFile) {
25 | new Notice("Inbox is looking at new file: " + file.basename);
26 | Inbox.getInstance().enqueueFiles([file]);
27 | }
28 | })
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/packages/plugin/inbox/constants.ts:
--------------------------------------------------------------------------------
1 | export const VALID_MEDIA_EXTENSIONS = [
2 | "png",
3 | "jpg",
4 | "jpeg",
5 | "gif",
6 | "bmp",
7 | "svg",
8 | "mp3",
9 | "wav",
10 | "mp4",
11 | "mov",
12 | "wmv",
13 | ];
14 |
15 | export const CHUNK_SIZE = 1024 * 1024; // 1MB
16 | export const MAX_CONCURRENT_TASKS = 100;
17 | export const BATCH_DELAY = 100; // ms
18 | export const MAX_BATCH_SIZE = 10;
19 | export const CACHE_TTL = 1000 * 60 * 60 * 24; // 24 hours
20 | export const MAX_LOG_SIZE = 100;
21 | export const ERROR_FOLDER = "_NoteCompanion/Error";
22 |
23 | export const NOTIFICATION_DURATIONS = {
24 | CRITICAL: 10000, // 10 seconds
25 | HIGH: 5000, // 5 seconds
26 | MEDIUM: 3000, // 3 seconds
27 | LOW: 2000, // 2 seconds
28 | };
29 |
30 | export const FILE_PRIORITIES = {
31 | SMALL: 3, // Small files (<100KB)
32 | MARKDOWN: 2, // Markdown files
33 | DEFAULT: 1, // Default priority
34 | };
35 |
36 | export const SIZE_THRESHOLDS = {
37 | SMALL: 1024 * 100, // 100KB
38 | };
--------------------------------------------------------------------------------
/packages/plugin/inbox/services/id-service.ts:
--------------------------------------------------------------------------------
1 | import { TFile } from "obsidian";
2 | import { createHash } from "crypto";
3 |
4 | export class IdService {
5 | private static instance: IdService;
6 |
7 | public static getInstance(): IdService {
8 | if (!IdService.instance) {
9 | IdService.instance = new IdService();
10 | }
11 | return IdService.instance;
12 | }
13 |
14 | public generateFileHash(file: TFile): string {
15 | // Create a unique hash based on file path and last modified time
16 | const content = `${file.path}-${file.stat.mtime}`;
17 | return createHash('sha256').update(content).digest('hex').slice(0, 12);
18 | }
19 |
20 | public generateEventId(fileHash: string, timestamp: number): string {
21 | return `evt-${fileHash}-${timestamp}`;
22 | }
23 |
24 | public generateStepId(fileHash: string, type: string): string {
25 | return `step-${fileHash}-${type}`;
26 | }
27 |
28 | public validateHash(hash: string): boolean {
29 | return typeof hash === 'string' && hash.length === 12;
30 | }
31 | }
--------------------------------------------------------------------------------
/packages/plugin/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 | /**
9 | * A wrapper for className strings that ensures Tailwind classes are properly prefixed
10 | *
11 | * Since we're adding the 'fo-' prefix in tailwind.config.js, we need to use this
12 | * function for any string literals that directly reference Tailwind classes.
13 | *
14 | * For example: className="bg-white p-4" becomes className={tw("bg-white p-4")}
15 | * Behind the scenes, this will be transformed to className="fo-bg-white fo-p-4"
16 | *
17 | * @param classNames The Tailwind class names string or array
18 | * @returns The properly prefixed class string
19 | */
20 | export function tw(classNames: string | string[]): string {
21 | // For compatibility with existing code, return empty string if no classes
22 | if (!classNames) return '';
23 |
24 | const classes = Array.isArray(classNames) ? classNames : classNames.split(' ');
25 |
26 | // Return classes with prefix added if they don't already have it
27 | return classes
28 | .filter(Boolean)
29 | .map(cls => cls.startsWith('fo-') ? cls : `fo-${cls}`)
30 | .join(' ');
31 | }
--------------------------------------------------------------------------------
/packages/plugin/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/plugin/pre-processor.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/plugin/pre-processor.ts
--------------------------------------------------------------------------------
/packages/plugin/services/logger.ts:
--------------------------------------------------------------------------------
1 | class LoggerService {
2 | private isEnabled = false;
3 |
4 | configure(enabled: boolean) {
5 | this.isEnabled = enabled;
6 | }
7 |
8 | info(...messages: any[]) {
9 | if (!this.isEnabled) return;
10 | console.info(...messages);
11 | }
12 |
13 | error(...messages: any[]) {
14 | if (!this.isEnabled) return;
15 | console.error(...messages);
16 | }
17 |
18 | warn(...messages: any[]) {
19 | if (!this.isEnabled) return;
20 | console.warn(...messages);
21 | }
22 |
23 | debug(...messages: any[]) {
24 | if (!this.isEnabled) return;
25 | console.debug(...messages);
26 | }
27 |
28 |
29 | }
30 |
31 | export const logger = new LoggerService();
--------------------------------------------------------------------------------
/packages/plugin/skeleton-loader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const SkeletonLoader: React.FC<{ width?: string; height?: string }> = ({
4 | width = "100%",
5 | height = "1em",
6 | }) => {
7 | return ;
8 | };
9 |
10 | export default SkeletonLoader;
11 |
--------------------------------------------------------------------------------
/packages/plugin/templates/youtube_video.md:
--------------------------------------------------------------------------------
1 | ---
2 | topics: "{{topics}}"
3 | tags: ["YouTube", {{tags}}]
4 | summary: "{{summary}}"
5 | ---
6 |
7 | []({{videoUrl}})
8 |
9 | **Detailed Summary:**
10 | {{summary}}
11 |
12 | **Key Points:**
13 | {{keyPoints}}
14 |
15 | **Additional Notes:**
16 | {{notes}}
--------------------------------------------------------------------------------
/packages/plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "outDir": "./dist",
6 | "rootDir": "./src",
7 | "composite": true,
8 | "paths": {
9 | "@/*": ["*"],
10 | "@/components/*": ["components/*"]
11 | }
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"],
15 | "references": [
16 | { "path": "../shared" }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/plugin/utils.ts:
--------------------------------------------------------------------------------
1 | import { TFile } from "obsidian";
2 |
3 | import { Notice } from "obsidian";
4 | import { isValidExtension } from "./constants";
5 |
6 | export function arrayBufferToBase64(buffer: ArrayBuffer): string {
7 | let binary = "";
8 | const bytes = new Uint8Array(buffer);
9 | const len = bytes.byteLength;
10 | for (let i = 0; i < len; i++) {
11 | binary += String.fromCharCode(bytes[i]);
12 | }
13 | return window.btoa(binary);
14 | }
15 |
16 | export function validateFile(file: TFile): boolean {
17 | if (!file.extension || !isValidExtension(file.extension)) {
18 | new Notice("Unsupported file type. Skipping.", 3000);
19 | return false;
20 | }
21 | return true;
22 | }
23 |
--------------------------------------------------------------------------------
/packages/plugin/utils/token-counter.ts:
--------------------------------------------------------------------------------
1 | import { init, get_encoding } from "tiktoken/init";
2 | import wasmBinary from "tiktoken/tiktoken_bg.wasm";
3 |
4 | let encoding: any = null;
5 | let initPromise: Promise | null = null;
6 |
7 | export function initializeTokenCounter() {
8 | // Return existing promise if initialization is in progress
9 | if (initPromise) return initPromise;
10 |
11 | // Create new initialization promise
12 | initPromise = init((imports) => WebAssembly.instantiate(wasmBinary, imports))
13 | .then(() => {
14 | encoding = get_encoding("cl100k_base");
15 | })
16 | .catch((error) => {
17 | console.error("Error initializing tiktoken:", error);
18 | initPromise = null;
19 | throw error;
20 | });
21 |
22 | return initPromise;
23 | }
24 |
25 | export function getTokenCount(text: string): number {
26 | if (!encoding) {
27 | throw new Error("Token counter not initialized. Call initializeTokenCounter() first.");
28 | }
29 | return encoding.encode(text).length;
30 | }
31 |
32 | export function cleanup() {
33 | if (encoding) {
34 | encoding.free();
35 | encoding = null;
36 | initPromise = null;
37 | }
38 | }
--------------------------------------------------------------------------------
/packages/plugin/version-bump.mjs:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from "fs";
2 |
3 | const targetVersion = process.env.npm_package_version;
4 |
5 | // read minAppVersion from manifest.json and bump version to target version
6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
7 | const { minAppVersion } = manifest;
8 | manifest.version = targetVersion;
9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
10 |
11 | // update versions.json with target version and minAppVersion from manifest.json
12 | let versions = JSON.parse(readFileSync("versions.json", "utf8"));
13 | versions[targetVersion] = minAppVersion;
14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
15 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/avatar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Avatar: React.FC & { role: "user" | "assistant" }> = ({ role, ...props }) => (
4 |
5 | );
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/button.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Button: React.FC> = ({ children, ...props }) => (
4 |
7 | );
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/card.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Card: React.FC> = ({ children, ...props }) => (
4 |
5 | {children}
6 |
7 | );
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/components/append-button.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FileText } from "lucide-react";
3 | import { usePlugin } from "../../provider";
4 |
5 | interface AppendButtonProps {
6 | content: string;
7 | }
8 |
9 | export const AppendButton: React.FC = ({ content }) => {
10 | const plugin = usePlugin();
11 |
12 | const handleAppend = async () => {
13 | const activeFile = plugin.app.workspace.getActiveFile();
14 | if (!activeFile) return;
15 |
16 | const fileContent = await plugin.app.vault.read(activeFile);
17 | await plugin.app.vault.modify(activeFile, fileContent + "\n\n" + content);
18 | };
19 |
20 | return (
21 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/components/copy-button.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Copy } from "lucide-react";
3 |
4 | interface CopyButtonProps {
5 | content: string;
6 | }
7 |
8 | export const CopyButton: React.FC = ({ content }) => {
9 | const handleCopy = async () => {
10 | await navigator.clipboard.writeText(content);
11 | };
12 |
13 | return (
14 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/components/submit-button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button } from '@/components/ui/button';
3 | import { Send } from 'lucide-react';
4 |
5 | interface SubmitButtonProps {
6 | isGenerating: boolean;
7 | }
8 |
9 | export const SubmitButton: React.FC = ({ isGenerating }) => {
10 | return (
11 |
19 | );
20 | };
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/selected-item.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { motion } from "framer-motion";
3 |
4 | interface SelectedItemProps {
5 | item: string;
6 | prefix: string;
7 | onClick: () => void;
8 | onRemove: () => void;
9 | }
10 |
11 | export const SelectedItem: React.FC = ({
12 | item,
13 | prefix,
14 | onClick,
15 | onRemove,
16 | }) => (
17 |
24 |
28 | {prefix}
29 | {item}
30 |
31 |
35 | ×
36 |
37 |
38 | );
39 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/tool-handlers/types.ts:
--------------------------------------------------------------------------------
1 | import { App } from "obsidian";
2 |
3 | export interface ToolInvocation {
4 | toolCallId: string;
5 | toolName: string;
6 | args: Record;
7 | result?: any;
8 | }
9 |
10 | export interface ToolHandlerProps {
11 | toolInvocation: ToolInvocation;
12 | handleAddResult: (result: string) => void;
13 | app: App;
14 | }
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/types.ts:
--------------------------------------------------------------------------------
1 | export type ModelType = "gpt-4.1" | "gpt-4.1-mini" | "gpt-4o" | "gpt-4o-search-preview" | "gpt-4o-mini-search-preview" | "llama3.2" | string;
2 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/types/annotations.ts:
--------------------------------------------------------------------------------
1 | import type { Message } from 'ai';
2 |
3 | export interface SearchResult {
4 | segment: {
5 | text: string;
6 | startIndex: number;
7 | endIndex: number;
8 | };
9 | groundingChunkIndices: number[];
10 | confidenceScores: number[];
11 | }
12 |
13 | export interface WebSource {
14 | web: {
15 | uri: string;
16 | title: string;
17 | };
18 | }
19 |
20 | export interface SearchResultsAnnotation {
21 | type: 'search-results';
22 | groundingMetadata: {
23 | webSearchQueries: string[];
24 | searchEntryPoint: {
25 | renderedContent: string;
26 | };
27 | groundingChunks: WebSource[];
28 | groundingSupports: SearchResult[];
29 | };
30 | }
31 |
32 | export type CustomAnnotation = SearchResultsAnnotation;
33 |
34 | export function isSearchResultsAnnotation(
35 | annotation: any
36 | ): annotation is SearchResultsAnnotation {
37 | return annotation.type === 'search-results';
38 | }
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/types/attachments.ts:
--------------------------------------------------------------------------------
1 | export interface Attachment {
2 | name?: string;
3 | contentType?: string;
4 | url?: string;
5 | }
6 |
7 | export interface LocalAttachment {
8 | id: string;
9 | name: string;
10 | contentType: string;
11 | url: string;
12 | size: number;
13 | }
14 |
15 | export interface AttachmentHandlerProps {
16 | onAttachmentsChange: (attachments: LocalAttachment[]) => void;
17 | maxFileSize?: number; // in bytes, defaults to 4MB
18 | acceptedTypes?: string[]; // e.g. ['image/*', 'application/pdf']
19 | }
20 |
21 | export interface AttachmentPreviewProps {
22 | attachment: LocalAttachment;
23 | onRemove: (id: string) => void;
24 | }
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/types/grounding.ts:
--------------------------------------------------------------------------------
1 | export interface GroundingMetadata {
2 | webSearchQueries?: string[];
3 | searchEntryPoint?: {
4 | renderedContent: string;
5 | };
6 | groundingSupports?: Array<{
7 | segment: {
8 | text: string;
9 | startIndex: number;
10 | endIndex: number;
11 | };
12 | groundingChunkIndices: number[];
13 | confidenceScores: number[];
14 | }>;
15 | }
16 |
17 | export interface DataChunk {
18 | type: 'metadata';
19 | data: {
20 | groundingMetadata: GroundingMetadata;
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/ai-chat/user-message-renderer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { MarkdownContent } from "./shared/markdown-renderer";
3 |
4 | interface UserMarkdownProps {
5 | content: string;
6 | }
7 |
8 | export const UserMarkdown: React.FC = ({ content }) => {
9 | return (
10 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/dashboard/floating-action-button.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { motion } from "framer-motion";
3 | import { Button } from "@/components/ui/button";
4 | import { Sparkles } from "lucide-react";
5 |
6 | interface FloatingActionButtonProps {
7 | label: string;
8 | onClick: () => void;
9 | }
10 |
11 | /**
12 | * Simple FAB in bottom-right corner:
13 | * - 'label' may be dynamic based on current note or plugin context
14 | */
15 | export function FloatingActionButton({
16 | label,
17 | onClick,
18 | }: FloatingActionButtonProps) {
19 | return (
20 |
25 |
33 |
34 | );
35 | }
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/dashboard/progress-bar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Progress } from "@/components/ui/progress";
3 |
4 | interface ProgressBarProps {
5 | value: number; // 0 to 100
6 | }
7 |
8 | /**
9 | * Simple progress indicator
10 | */
11 | export function ProgressBar({ value }: ProgressBarProps) {
12 | return (
13 |
14 | );
15 | }
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/dashboard/view.tsx:
--------------------------------------------------------------------------------
1 | import { ItemView, WorkspaceLeaf } from "obsidian";
2 | import { Root, createRoot } from "react-dom/client";
3 | import { MainDashboard } from "./main-dashboard";
4 | import { AppContext } from "../provider";
5 | import FileOrganizer from "../../../index"; // Your main plugin class
6 |
7 | export const DASHBOARD_VIEW_TYPE = "fo2k.dashboard";
8 |
9 | export class DashboardView extends ItemView {
10 | root: Root | null = null;
11 | plugin: FileOrganizer;
12 |
13 | constructor(leaf: WorkspaceLeaf, plugin: FileOrganizer) {
14 | super(leaf);
15 | this.plugin = plugin;
16 | }
17 |
18 | getViewType(): string {
19 | return DASHBOARD_VIEW_TYPE;
20 | }
21 |
22 | getDisplayText(): string {
23 | return "File Organizer Dashboard";
24 | }
25 |
26 | getIcon(): string {
27 | return "sparkle";
28 | }
29 |
30 | async onOpen(): Promise {
31 | const container = this.containerEl.children[1];
32 | this.root = createRoot(container);
33 |
34 | this.root.render(
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | async onClose(): Promise {
42 | this.root?.unmount();
43 | }
44 | }
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/organizer/components/error-box.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { motion } from "framer-motion";
3 |
4 | interface ErrorBoxProps {
5 | message: string;
6 | description?: string;
7 | actionButton?: React.ReactNode;
8 | }
9 |
10 | export const ErrorBox: React.FC = ({
11 | message,
12 | description,
13 | actionButton,
14 | }) => {
15 | return (
16 |
21 |
22 |
23 |
24 |
25 | {message}
26 |
27 | {description && (
28 |
29 | {description}
30 |
31 | )}
32 |
33 |
34 |
35 | {actionButton && (
36 |
37 | {actionButton}
38 |
39 | )}
40 |
41 |
42 | );
43 | };
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/organizer/folders/components/error-display.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | interface ErrorDisplayProps {
4 | message: string;
5 | onRetry: () => void;
6 | }
7 |
8 | export const ErrorDisplay: React.FC = ({ message, onRetry }) => (
9 |
10 |
Error: {message}
11 |
12 |
13 | );
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/organizer/folders/components/skeleton-loader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | interface SkeletonLoaderProps {
4 | count: number;
5 | rows: number;
6 | width: string;
7 | }
8 |
9 | export const SkeletonLoader: React.FC = ({ count, rows, width }) => (
10 |
11 | {Array.from({ length: count }).map((_, index) => (
12 |
13 | {Array.from({ length: rows }).map((__, idx) => (
14 |
15 | ))}
16 |
17 | ))}
18 |
19 | );
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from "react";
2 | import { Root } from "react-dom/client";
3 | import FileOrganizer from "../..";
4 |
5 | interface AppContextType {
6 | plugin: FileOrganizer;
7 | root: Root;
8 | }
9 |
10 | export const AppContext = createContext(undefined);
11 |
12 | export const useAppContext = (): AppContextType => {
13 | const context = useContext(AppContext);
14 | if (context === undefined) {
15 | throw new Error("useAppContext must be used within an AppProvider");
16 | }
17 | return context;
18 | };
19 |
20 | export const usePlugin = (): FileOrganizer => {
21 | const { plugin } = useAppContext();
22 | return plugin;
23 | };
24 |
25 | export const useRoot = (): Root => {
26 | const { root } = useAppContext();
27 | return root;
28 | };
29 |
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/section-header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | interface SectionHeaderProps {
4 | text: string;
5 | icon?: string;
6 | }
7 |
8 | export const SectionHeader: React.FC = ({ text, icon }) => {
9 | return (
10 |
11 | {icon && {icon}}
12 | {text}
13 |
14 | );
15 | };
--------------------------------------------------------------------------------
/packages/plugin/views/assistant/synchronizer/index.ts:
--------------------------------------------------------------------------------
1 | export { SyncTab } from './sync-tab';
--------------------------------------------------------------------------------
/packages/plugin/views/settings/view.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { App, PluginSettingTab } from 'obsidian';
3 | import FileOrganizer from '../../index';
4 | import { createRoot, Root } from 'react-dom/client';
5 | import { SettingsTabContent } from './main';
6 | import { logMessage } from '../../someUtils';
7 |
8 | export class FileOrganizerSettingTab extends PluginSettingTab {
9 | plugin: FileOrganizer;
10 | private root: Root | null = null;
11 |
12 | constructor(app: App, plugin: FileOrganizer) {
13 | super(app, plugin);
14 | this.plugin = plugin;
15 | }
16 |
17 | display(): void {
18 | const { containerEl } = this;
19 | containerEl.empty();
20 | containerEl.addClass('fo2k-view');
21 |
22 | if (!this.root) {
23 | this.root = createRoot(containerEl);
24 | }
25 |
26 | this.root.render(
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | hide(): void {
34 | logMessage("hide");
35 | if (this.root) {
36 | this.root.unmount();
37 | this.root = null;
38 | }
39 | this.containerEl.removeClass('fo2k-view');
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/packages/release-notes/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/packages/release-notes/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@file-organizer/release-notes",
3 | "version": "1.0.0",
4 | "description": "Release notes generator for Note Companion",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "build": "tsc",
9 | "dev": "tsc -w"
10 | },
11 | "dependencies": {
12 | "@ai-sdk/openai": "latest",
13 | "ai": "latest",
14 | "ignore": "^5.3.0",
15 | "zod": "^3.22.4"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20.0.0",
19 | "typescript": "^5.0.0"
20 | }
21 | }
--------------------------------------------------------------------------------
/packages/release-notes/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "lib": ["ES2020"],
6 | "declaration": true,
7 | "outDir": "./dist",
8 | "rootDir": "./src",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "moduleResolution": "node"
14 | },
15 | "include": ["src/**/*"],
16 | "exclude": ["node_modules", "dist"]
17 | }
--------------------------------------------------------------------------------
/packages/web/.electronignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | test
4 | *.log
5 | *.md
--------------------------------------------------------------------------------
/packages/web/.env.example:
--------------------------------------------------------------------------------
1 | ### The fastest way to get started is just to add your OpenAI API key to the .env file.
2 | OPENAI_API_KEY="sk-xxxx"
3 | OPENAI_API_BASE="" # The base URL for OpenAI API
4 | GROQ_API_KEY="gsk-xxxx"
5 | GROQ_API_BASE="" # The base URL for Groq API
6 | ANTHROPIC_API_KEY="sk-xxxx"
7 | ANTHROPIC_API_BASE="" # The base URL for Anthropic API
8 | GOOGLE_API_KEY="sk-xxxx"
9 | GOOGLE_API_BASE="" # The base URL for Google API
10 | DEEPSEEK_API_KEY="sk-xxxx"
11 | DEEPSEEK_API_BASE="" # The base URL for DeepSeek API
12 | MISTRAL_API_KEY="sk-xxxx" # The API key for Mistral AI OCR
13 |
14 | # Loops API key for newsletter and contact management
15 | LOOPS_API_KEY="your-loops-api-key"
16 |
--------------------------------------------------------------------------------
/packages/web/.github/workflows/update.yml:
--------------------------------------------------------------------------------
1 | name: File Organizer 2000 Updater
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | reason:
7 | description: 'Reason for manual trigger'
8 | required: false
9 | default: 'Manual update'
10 |
11 | jobs:
12 | update-repo:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout current repo
16 | uses: actions/checkout@v3
17 | with:
18 | fetch-depth: 0
19 |
20 | - name: Configure Git
21 | run: |
22 | git config user.name github-actions
23 | git config user.email github-actions@github.com
24 |
25 | - name: Fetch parent repo
26 | run: |
27 | git remote add parent https://github.com/different-ai/file-organizer-2000.git
28 | git fetch parent
29 |
30 | - name: Update current repo
31 | run: |
32 | git checkout main
33 | git read-tree --prefix=web/ -u parent/master:web
34 | git commit -m "Auto-update from parent repo: ${{ github.event.inputs.reason || 'Triggered by repository_dispatch' }}"
35 |
36 | - name: Push changes
37 | run: git push origin main
38 |
--------------------------------------------------------------------------------
/packages/web/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 | .env
3 |
4 | # dependencies
5 | /node_modules
6 | /.pnp
7 | .pnp.js
8 | .yarn/install-state.gz
9 | /package-lock.json
10 |
11 | # testing
12 | /coverage
13 |
14 | # next.js
15 | /.next/
16 | /out/
17 |
18 | # production
19 | /build
20 |
21 | # misc
22 | .DS_Store
23 | *.pem
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
30 | # local env files
31 | .env*.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
--------------------------------------------------------------------------------
/packages/web/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | npm i
3 | npm run build:self-host
4 | OPENAI_API_KEY=<> npm start
5 | ```
6 |
--------------------------------------------------------------------------------
/packages/web/__mocks__/@ai-sdk/google.ts:
--------------------------------------------------------------------------------
1 | export const google = jest.fn((model: string, options: any) => ({
2 | generateText: jest.fn().mockImplementation(async ({ prompt }) => ({
3 | text: "Mocked response",
4 | experimental_providerMetadata: {
5 | google: {
6 | groundingMetadata: {
7 | webSearchQueries: ["test query"],
8 | searchEntryPoint: {
9 | renderedContent: "Test content"
10 | },
11 | groundingSupports: [{
12 | segment: {
13 | text: "Test segment",
14 | startIndex: 0,
15 | endIndex: 11
16 | },
17 | groundingChunkIndices: [0],
18 | confidenceScores: [0.95]
19 | }]
20 | }
21 | }
22 | }
23 | }))
24 | }));
25 |
26 | export type GoogleGenerativeAIProviderMetadata = {
27 | groundingMetadata?: {
28 | webSearchQueries: string[];
29 | searchEntryPoint: {
30 | renderedContent: string;
31 | };
32 | groundingSupports: Array<{
33 | segment: {
34 | text: string;
35 | startIndex: number;
36 | endIndex: number;
37 | };
38 | groundingChunkIndices: number[];
39 | confidenceScores: number[];
40 | }>;
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/packages/web/__mocks__/@ai-sdk/openai.ts:
--------------------------------------------------------------------------------
1 | export const openai = jest.fn((model: string, options: any) => ({
2 | generateText: jest.fn().mockImplementation(async ({ prompt }) => ({
3 | text: "Mocked response",
4 | experimental_providerMetadata: {
5 | openai: {
6 | annotations: [
7 | {
8 | type: "url_citation",
9 | url_citation: {
10 | url: "https://example.com",
11 | title: "Example Page Title",
12 | start_index: 0,
13 | end_index: 15
14 | }
15 | }
16 | ]
17 | }
18 | }
19 | }))
20 | }));
21 |
22 | export type OpenAIProviderMetadata = {
23 | annotations?: Array<{
24 | type: string;
25 | url_citation?: {
26 | url: string;
27 | title: string;
28 | start_index: number;
29 | end_index: number;
30 | };
31 | }>;
32 | };
--------------------------------------------------------------------------------
/packages/web/__mocks__/next/server.ts:
--------------------------------------------------------------------------------
1 | interface NextRequestInit extends RequestInit {
2 | nextConfig?: { i18n?: any };
3 | }
4 |
5 | export class NextRequest extends Request {
6 | private _url: string;
7 | private _body: any;
8 |
9 | constructor(input: RequestInfo | URL, init?: NextRequestInit) {
10 | super(input, init);
11 | Object.setPrototypeOf(this, NextRequest.prototype);
12 | this._url = input instanceof URL ? input.href : input.toString();
13 | try {
14 | this._body = init?.body ? JSON.parse(init.body.toString()) : {};
15 | } catch (e) {
16 | this._body = {};
17 | }
18 | }
19 |
20 | json(): Promise {
21 | return Promise.resolve(this._body);
22 | }
23 |
24 | // Add other required Next.js Request methods
25 | get cookies() {
26 | return new Map();
27 | }
28 |
29 | get url() {
30 | return this._url;
31 | }
32 |
33 | get nextUrl() {
34 | return new URL(this._url);
35 | }
36 | }
37 |
38 | export class NextResponse extends Response {
39 | static json(data: any) {
40 | return new Response(JSON.stringify(data), {
41 | headers: { 'content-type': 'application/json' },
42 | });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/web/app/api/(newai)/chunks/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, NextRequest } from "next/server";
2 | import { fetchChunksForConcept } from "../aiService";
3 | import { getModel } from "@/lib/models";
4 | import { incrementAndLogTokenUsage } from "@/lib/incrementAndLogTokenUsage";
5 | import { handleAuthorization } from "@/lib/handleAuthorization";
6 |
7 | export async function POST(request: NextRequest) {
8 | try {
9 | const { userId } = await handleAuthorization(request);
10 | const { content, concept } = await request.json();
11 | const model = getModel(process.env.MODEL_NAME);
12 | const response = await fetchChunksForConcept(content, concept, model);
13 | console.log("response", response);
14 | const tokens = response.usage.totalTokens;
15 | console.log("incrementing token usage chunks", userId, tokens);
16 | await incrementAndLogTokenUsage(userId, tokens);
17 |
18 | return NextResponse.json({ content: response.object.content });
19 | } catch (error) {
20 | if (error) {
21 | return NextResponse.json(
22 | { error: error.message },
23 | { status: error.status }
24 | );
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/web/app/api/(newai)/classify1/route.ts:
--------------------------------------------------------------------------------
1 | // app/app/api/(ai)/classify/route.ts
2 | import { NextResponse, NextRequest } from "next/server";
3 | import { classifyDocument } from "../aiService";
4 | import { handleAuthorization } from "@/lib/handleAuthorization";
5 | import { incrementAndLogTokenUsage } from "@/lib/incrementAndLogTokenUsage";
6 | import { getModel } from "@/lib/models";
7 |
8 | export async function POST(request: NextRequest) {
9 | try {
10 | const { userId } = await handleAuthorization(request);
11 | const { content, fileName, templateNames } = await request.json();
12 | const model = getModel(process.env.MODEL_NAME);
13 | const response = await classifyDocument(
14 | content,
15 | fileName,
16 | templateNames,
17 | model
18 | );
19 | // increment tokenUsage
20 | const tokens = response.usage.totalTokens;
21 | console.log("incrementing token usage classify", userId, tokens);
22 | await incrementAndLogTokenUsage(userId, tokens);
23 | const documentType = response.object.documentType;
24 | return NextResponse.json({ documentType });
25 | } catch (error) {
26 | if (error) {
27 | return NextResponse.json(
28 | { error: error.message },
29 | { status: error.status }
30 | );
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/web/app/api/(newai)/concepts/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, NextRequest } from "next/server";
2 | import { handleAuthorization } from "@/lib/handleAuthorization";
3 | import { incrementAndLogTokenUsage } from "@/lib/incrementAndLogTokenUsage";
4 | import { getModel } from "@/lib/models";
5 | import { identifyConcepts } from "../aiService";
6 |
7 | export async function POST(request: NextRequest) {
8 | try {
9 | const { userId } = await handleAuthorization(request);
10 | const { document } = await request.json();
11 | const model = getModel(process.env.MODEL_NAME);
12 | const generateConceptsData = await identifyConcepts(document, model);
13 | const tokens = generateConceptsData.usage.totalTokens;
14 | console.log("incrementing token usage concepts", userId, tokens);
15 | const concepts = generateConceptsData.object.concepts;
16 | await incrementAndLogTokenUsage(userId, tokens);
17 | const response = NextResponse.json({ concepts });
18 | response.headers.set("Access-Control-Allow-Origin", "*");
19 |
20 | return response;
21 | } catch (error) {
22 | if (error) {
23 | return NextResponse.json(
24 | { error: error.message },
25 | { status: error.status }
26 | );
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/web/app/api/(newai)/folders/route.ts:
--------------------------------------------------------------------------------
1 | import { guessRelevantFolder } from "../aiService";
2 | import { NextRequest, NextResponse } from "next/server";
3 | import { handleAuthorization } from "@/lib/handleAuthorization";
4 | import { incrementAndLogTokenUsage } from "@/lib/incrementAndLogTokenUsage";
5 | import { getModel } from "@/lib/models";
6 |
7 | export async function POST(request: NextRequest) {
8 | try {
9 | const { userId } = await handleAuthorization(request);
10 | const { content, fileName, folders, customInstructions } = await request.json();
11 | const model = getModel(process.env.MODEL_NAME);
12 | const response = await guessRelevantFolder(
13 | content,
14 | fileName,
15 | folders,
16 | model,
17 | customInstructions
18 | );
19 | // increment tokenUsage
20 | const tokens = response.usage.totalTokens;
21 | console.log("incrementing token usage folders", userId, tokens);
22 | await incrementAndLogTokenUsage(userId, tokens);
23 | return NextResponse.json({
24 | folder: response.object.suggestedFolder,
25 | });
26 | } catch (error) {
27 | if (error) {
28 | return NextResponse.json(
29 | { error: error.message },
30 | { status: error.status }
31 | );
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/web/app/api/(newai)/format/route.ts:
--------------------------------------------------------------------------------
1 | import { formatDocumentContent } from "../aiService";
2 | import { NextResponse, NextRequest } from "next/server";
3 | import { incrementAndLogTokenUsage } from "@/lib/incrementAndLogTokenUsage";
4 | import { handleAuthorization } from "@/lib/handleAuthorization";
5 | import { getModel } from "@/lib/models";
6 |
7 | export const maxDuration = 60; // This function can run for a maximum of 5 seconds
8 |
9 | export async function POST(request: NextRequest) {
10 | try {
11 | const { userId } = await handleAuthorization(request);
12 | const { content, formattingInstruction } = await request.json();
13 | const model = getModel(process.env.MODEL_NAME);
14 | const response = await formatDocumentContent(
15 | content,
16 | formattingInstruction,
17 | model
18 | );
19 | const tokens = response.usage.totalTokens;
20 | console.log("incrementing token usage format", userId, tokens);
21 | await incrementAndLogTokenUsage(userId, tokens);
22 |
23 | return NextResponse.json({ content: response.object.formattedContent });
24 | } catch (error) {
25 | if (error) {
26 | return NextResponse.json(
27 | { error: error.message },
28 | { status: error.status }
29 | );
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/web/app/api/(newai)/tags/route.ts:
--------------------------------------------------------------------------------
1 | import { generateTags } from "../aiService";
2 | import { NextResponse, NextRequest } from "next/server";
3 | import { incrementAndLogTokenUsage } from "@/lib/incrementAndLogTokenUsage";
4 | import { handleAuthorization } from "@/lib/handleAuthorization";
5 | import { getModel } from "@/lib/models";
6 |
7 | export async function POST(request: NextRequest) {
8 | try {
9 | const { userId } = await handleAuthorization(request);
10 | const { content, fileName, tags } = await request.json();
11 | const model = getModel(process.env.MODEL_NAME);
12 | const generateTagsData = await generateTags(content, fileName, tags, model);
13 | const generatedTags = generateTagsData.object.tags ?? [];
14 | // Increment token usage
15 | const tokens = generateTagsData.usage.totalTokens;
16 | console.log("incrementing token usage tags", userId, tokens);
17 | await incrementAndLogTokenUsage(userId, tokens);
18 |
19 | return NextResponse.json({ generatedTags });
20 | } catch (error) {
21 | if (error) {
22 | return NextResponse.json(
23 | { error: error.message },
24 | { status: error.status }
25 | );
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/packages/web/app/api/check-key/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | import { handleAuthorizationV2 } from "@/lib/handleAuthorization";
3 |
4 | export async function POST(request: NextRequest) {
5 | try {
6 | const { userId } = await handleAuthorizationV2(request);
7 |
8 | // If handleAuthorization doesn't throw an error, the key is valid
9 | return NextResponse.json({ message: "Valid key", userId }, { status: 200 });
10 | } catch (error) {
11 | console.log("Error checking key", error);
12 | if (error instanceof Error) {
13 | return NextResponse.json({ error: error.message }, { status: 401 });
14 | }
15 | return NextResponse.json({ error: "Invalid key" }, { status: 401 });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/web/app/api/check-premium/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 |
3 |
4 | export async function GET(request: NextRequest) {
5 | try {
6 | // const { userId } = await handleAuthorizationV2(request);
7 | const hasCatalystAccess = true
8 |
9 | return NextResponse.json({ hasCatalystAccess });
10 | } catch (error) {
11 | return NextResponse.json(
12 | { error: "Failed to check premium status" },
13 | { status: 500 }
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/web/app/api/check-tier/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse, NextRequest } from "next/server";
2 | import { db, UserUsageTable, checkIfUserNeedsUpgrade, checkTokenUsage } from "@/drizzle/schema";
3 | import { handleAuthorizationV2 } from "@/lib/handleAuthorization";
4 |
5 | export async function GET(request: NextRequest) {
6 | try {
7 | const { userId } = await handleAuthorizationV2(request);
8 |
9 | // Check if user needs to upgrade
10 | const needsUpgrade = await checkIfUserNeedsUpgrade(userId);
11 |
12 | // Get token usage information
13 | const tokenUsage = await checkTokenUsage(userId);
14 |
15 | return NextResponse.json({
16 | needsUpgrade,
17 | remainingTokens: tokenUsage.remaining,
18 | usageError: tokenUsage.usageError
19 | });
20 | } catch (error) {
21 | return NextResponse.json(
22 | { error: error.message },
23 | { status: error.status || 500 }
24 | );
25 | }
26 | }
--------------------------------------------------------------------------------
/packages/web/app/api/health/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | export async function GET() {
4 | return NextResponse.json({ status: "ok" }, { status: 200 });
5 | }
--------------------------------------------------------------------------------
/packages/web/app/api/user/subscription-status/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | import { auth } from "@clerk/nextjs/server";
3 | import { getUserSubscriptionStatus } from "@/lib/subscription";
4 |
5 | export async function GET(req: NextRequest) {
6 | try {
7 | // Get the authenticated user ID
8 | const { userId } = await auth();
9 |
10 | // If no user is logged in, return unauthorized
11 | if (!userId) {
12 | return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
13 | }
14 |
15 | // Get the user's subscription status using the shared function
16 | const subscriptionStatus = await getUserSubscriptionStatus(userId);
17 |
18 | // Return the subscription data
19 | return NextResponse.json(subscriptionStatus);
20 |
21 | } catch (error) {
22 | console.error("Error fetching subscription status:", error);
23 | return NextResponse.json(
24 | { error: "Error fetching subscription status" },
25 | { status: 500 }
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/web/app/api/webhook/types.ts:
--------------------------------------------------------------------------------
1 | import Stripe from "stripe";
2 |
3 | export type WebhookEvent = {
4 | id: string;
5 | type: string;
6 | data: {
7 | object: Stripe.Event.Data.Object & {
8 | id: string;
9 | metadata?: {
10 | userId?: string;
11 | type?: string;
12 | plan?: string;
13 | };
14 | };
15 | };
16 | };
17 |
18 | export type WebhookHandlerResponse = {
19 | success: boolean;
20 | message: string;
21 | error?: Error;
22 | };
23 |
24 | export type CustomerData = {
25 | userId: string;
26 | customerId: string;
27 | status: string;
28 | paymentStatus: string;
29 | billingCycle?: "monthly" | "lifetime" | "yearly";
30 | product: string;
31 | plan: string;
32 | lastPayment: Date;
33 | createdAt?: Date;
34 | };
35 |
--------------------------------------------------------------------------------
/packages/web/app/api/webhook/utils.ts:
--------------------------------------------------------------------------------
1 | import { db, UserUsageTable } from "@/drizzle/schema";
2 | import { CustomerData } from "./types";
3 |
4 |
5 | export async function updateUserSubscriptionData(
6 | data: CustomerData
7 | ): Promise {
8 | await db
9 | .insert(UserUsageTable)
10 | .values({
11 | userId: data.userId,
12 | subscriptionStatus: data.status,
13 | paymentStatus: data.paymentStatus,
14 | billingCycle: data.billingCycle,
15 | lastPayment: new Date(),
16 | currentProduct: data.product,
17 | currentPlan: data.plan,
18 | })
19 | .onConflictDoUpdate({
20 | target: [UserUsageTable.userId],
21 | set: {
22 | subscriptionStatus: data.status,
23 | paymentStatus: data.paymentStatus,
24 | billingCycle: data.billingCycle,
25 | lastPayment: new Date(),
26 | currentProduct: data.product,
27 | currentPlan: data.plan,
28 | },
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/packages/web/app/api/webhook/verify.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest } from "next/server";
2 | import Stripe from "stripe";
3 | import { WebhookEvent } from "./types";
4 |
5 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
6 | apiVersion: "2024-06-20",
7 | });
8 |
9 | const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
10 |
11 | export async function verifyStripeWebhook(req: NextRequest): Promise {
12 | const signature = req.headers.get("stripe-signature");
13 |
14 | if (!signature) {
15 | throw new Error("Missing stripe-signature header");
16 | }
17 |
18 | try {
19 | const body = await req.text();
20 | const event = stripe.webhooks.constructEvent(
21 | body,
22 | signature,
23 | webhookSecret
24 | ) as WebhookEvent;
25 |
26 | console.log(`✅ Verified webhook event: ${event.type}`);
27 | return event;
28 | } catch (error) {
29 | console.error("⚠️ Webhook verification failed:", error);
30 | throw new Error(`Webhook verification failed: ${error.message}`);
31 | }
32 | }
--------------------------------------------------------------------------------
/packages/web/app/dashboard/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function DashboardLayout({
2 | children,
3 | }: {
4 | children: React.ReactNode;
5 | }) {
6 | return {children}
;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/web/app/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | export default function Error() {
3 | return Something went wrong!
4 | }
--------------------------------------------------------------------------------
/packages/web/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/app/favicon.ico
--------------------------------------------------------------------------------
/packages/web/app/global-error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'; // Error boundaries must be Client Components
2 |
3 | import { useEffect } from 'react';
4 |
5 | const GlobalError = ({ error, reset }: {
6 | error: Error & { digest?: string }
7 | reset: () => void
8 | }) => {
9 | useEffect(() => {
10 | // Sentry.captureException(error);
11 | }, [error]);
12 |
13 | return (
14 | // global-error must include html and body tags
15 |
16 |
17 | Something went wrong!
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | export default GlobalError;
--------------------------------------------------------------------------------
/packages/web/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 |
4 | export default function NotFound() {
5 | return (
6 |
7 |
Not Found
8 |
Could not find requested resource
9 |
Return Home
10 |
11 | )
12 | }
--------------------------------------------------------------------------------
/packages/web/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 | import { auth } from "@clerk/nextjs/server";
3 | import { getUserBillingCycle } from "./actions";
4 |
5 | export default async function MainPage() {
6 | if (process.env.ENABLE_USER_MANAGEMENT !== "true") {
7 | redirect("/dashboard/self-hosted");
8 | }
9 |
10 | const { userId } = await auth();
11 |
12 | const billingCycle = await getUserBillingCycle(userId);
13 | console.log("Billing cycle:", billingCycle);
14 |
15 | // if billing cycle part of legacy plans
16 | const isSubscription = [
17 | // legacy cycle
18 | "monthly",
19 | "yearly",
20 | // new up to date cycle
21 | "subscription",
22 | ].includes(billingCycle);
23 |
24 | // top-up is not a "PAY ONCE" plan
25 | const isPayOnce = ["pay-once", "lifetime"].includes(billingCycle);
26 |
27 | // Check if the user has any kind of active subscription
28 | const hasSubscription = isSubscription || isPayOnce;
29 |
30 | if (hasSubscription) {
31 | // If user has any kind of subscription, redirect to dashboard
32 | redirect("/dashboard");
33 | } else {
34 | // If user doesn't have a subscription, redirect to the new onboarding page
35 | redirect("/onboarding");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/web/app/posthog-page-view.tsx:
--------------------------------------------------------------------------------
1 | // app/PostHogPageView.tsx
2 | "use client";
3 |
4 | import { usePathname, useSearchParams } from "next/navigation";
5 | import { useEffect, Suspense } from "react";
6 | import { usePostHog } from "posthog-js/react";
7 |
8 | function PostHogPageView(): null {
9 | const pathname = usePathname();
10 | const searchParams = useSearchParams();
11 | const posthog = usePostHog();
12 |
13 | // Track pageviews
14 | useEffect(() => {
15 | if (pathname && posthog) {
16 | let url = window.origin + pathname;
17 | if (searchParams.toString()) {
18 | url = url + `?${searchParams.toString()}`;
19 | }
20 |
21 | posthog.capture("$pageview", { $current_url: url });
22 | }
23 | }, [pathname, searchParams, posthog]);
24 |
25 | return null;
26 | }
27 |
28 | // Wrap this in Suspense to avoid the `useSearchParams` usage above
29 | // from de-opting the whole app into client-side rendering
30 | // See: https://nextjs.org/docs/messages/deopted-into-client-rendering
31 | export default function SuspendedPostHogPageView() {
32 | return (
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/packages/web/app/providers.tsx:
--------------------------------------------------------------------------------
1 | // app/providers.tsx
2 | "use client";
3 | import posthog from "posthog-js";
4 | import { PostHogProvider } from "posthog-js/react";
5 | import PostHogPageView from "./posthog-page-view";
6 |
7 | if (typeof window !== "undefined") {
8 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
9 | api_host: "/ingest",
10 | ui_host: "https://us.posthog.com",
11 | person_profiles: "identified_only",
12 | capture_pageview: false, // Disable automatic pageview capture, as we capture manually
13 | capture_pageleave: true, // Enable pageleave capture
14 | });
15 | }
16 |
17 | export function PHProvider({ children }: { children: React.ReactNode }) {
18 | return (
19 |
20 |
21 | {children}
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/packages/web/app/top-up-cancelled/page.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button";
2 | import { Card } from "@/components/ui/card";
3 | import Link from "next/link";
4 |
5 | export default function TopUpCancelled() {
6 | return (
7 |
8 |
9 |
10 |
Top-up Cancelled
11 |
12 | Your top-up was cancelled. No charges were made to your account.
13 |
14 |
15 |
16 |
21 |
22 |
27 |
28 |
29 |
30 |
31 | );
32 | }
--------------------------------------------------------------------------------
/packages/web/app/top-up-success/page.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button";
2 | import { Card } from "@/components/ui/card";
3 | import Link from "next/link";
4 |
5 | export default function TopUpSuccess() {
6 | return (
7 |
8 |
9 |
10 |
11 | Top-up Successful!
12 |
13 |
14 | Your tokens have been added to your account successfully.
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/packages/web/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/web/components/ui/checkout-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState } from "react";
3 | import { Button } from "./button";
4 | import { useUser } from "@clerk/nextjs";
5 | import { useRouter } from "next/navigation";
6 |
7 |
8 | export default function CheckoutButton() {
9 | const [loading, setLoading] = useState(false);
10 | const { isLoaded } = useUser();
11 | const router = useRouter();
12 |
13 | const handleCheckout = async () => {
14 | setLoading(true);
15 |
16 | router.push("/dashboard/pricing");
17 |
18 | setLoading(false);
19 | };
20 | if (!isLoaded) {
21 | return ;
22 | }
23 |
24 | return (
25 | // width: max-content;
26 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/packages/web/components/ui/icons.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from "react";
2 |
3 | // ... existing code ...
4 |
5 | export function ArrowDownIcon(props: SVGProps) {
6 | return (
7 |
20 | );
21 | }
22 |
23 | export function ArrowRightIcon(props: SVGProps) {
24 | return (
25 |
38 | );
39 | }
40 |
41 | // ... existing code ...
42 |
--------------------------------------------------------------------------------
/packages/web/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | export type InputProps = React.InputHTMLAttributes;
6 |
7 | const Input = React.forwardRef(
8 | ({ className, type, ...props }, ref) => {
9 | return (
10 |
19 | );
20 | },
21 | );
22 | Input.displayName = "Input";
23 |
24 | export { Input };
25 |
--------------------------------------------------------------------------------
/packages/web/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { type VariantProps, cva } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef & VariantProps
16 | >(({ className, ...props }, ref) => (
17 |
18 | ));
19 | Label.displayName = LabelPrimitive.Root.displayName;
20 |
21 | export { Label };
22 |
--------------------------------------------------------------------------------
/packages/web/components/ui/logo.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export default function Logo() {
4 | return (
5 |
6 |
10 |
11 |
12 |
21 |
25 | Note Companion
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/packages/web/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 | import { cn } from "@/lib/utils"
6 |
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/packages/web/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 | import { cn } from "@/lib/utils"
6 |
7 | const Separator = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(
11 | (
12 | { className, orientation = "horizontal", decorative = true, ...props },
13 | ref
14 | ) => (
15 |
26 | )
27 | )
28 | Separator.displayName = SeparatorPrimitive.Root.displayName
29 |
30 | export { Separator }
--------------------------------------------------------------------------------
/packages/web/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SwitchPrimitives from "@radix-ui/react-switch";
5 | import { cn } from "@/lib/utils";
6 |
7 | const Switch = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
24 |
25 | ));
26 | Switch.displayName = SwitchPrimitives.Root.displayName;
27 |
28 | export { Switch };
--------------------------------------------------------------------------------
/packages/web/components/ui/utils.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import { cn } from '@/lib/utils';
5 |
6 | interface StyledContainerProps {
7 | children: React.ReactNode;
8 | className?: string;
9 | }
10 |
11 | export function StyledContainer({ children, className }: StyledContainerProps) {
12 | return (
13 |
14 | {children}
15 |
16 | );
17 | }
--------------------------------------------------------------------------------
/packages/web/components/user-management.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button";
2 | import CheckoutButton from "@/components/ui/checkout-button";
3 | import { auth } from "@clerk/nextjs/server";
4 | import { isPaidUser } from "@/app/actions";
5 |
6 | export default async function ExtraUserSettings() {
7 | const { userId } = await auth();
8 | const isPaid = await isPaidUser(userId);
9 |
10 | return (
11 |
12 |
{!isPaid && }
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/packages/web/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "drizzle-kit";
2 | import { loadEnvConfig } from "@next/env";
3 |
4 | const projectDir = process.cwd();
5 | loadEnvConfig(projectDir);
6 |
7 | export default defineConfig({
8 | schema: "./drizzle/schema.ts",
9 | dialect: "postgresql",
10 | dbCredentials: {
11 | url: process.env.POSTGRES_URL,
12 |
13 | // host: process.env.POSTGRES_HOST,
14 | // user: process.env.POSTGRES_USER,
15 | // password: process.env.POSTGRES_PASSWORD,
16 | // database: process.env.POSTGRES_DATABASE,
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/packages/web/drizzle/0000_thankful_mathemanic.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "user_usage" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "userId" text NOT NULL,
4 | "apiUsage" integer NOT NULL,
5 | "maxUsage" integer NOT NULL,
6 | "createdAt" timestamp DEFAULT now() NOT NULL,
7 | "billingCycle" text NOT NULL,
8 | "tokenUsage" integer DEFAULT 0 NOT NULL,
9 | "maxTokenUsage" integer DEFAULT 1000000 NOT NULL,
10 | CONSTRAINT "user_usage_userId_unique" UNIQUE("userId")
11 | );
12 | --> statement-breakpoint
13 | CREATE UNIQUE INDEX IF NOT EXISTS "unique_user_idx" ON "user_usage" USING btree ("userId");
--------------------------------------------------------------------------------
/packages/web/drizzle/0001_new-hello.sql:
--------------------------------------------------------------------------------
1 | -- Update existing NULL values to the new defaults
2 | UPDATE "user_usage" SET "tokenUsage" = 0 WHERE "tokenUsage" IS NULL;
3 | UPDATE "user_usage" SET "maxTokenUsage" = 1000000 WHERE "maxTokenUsage" IS NULL;
4 |
5 | -- Modify the tokenUsage column with the default value
6 | ALTER TABLE "user_usage"
7 | ALTER COLUMN "tokenUsage" SET DEFAULT 0,
8 | ALTER COLUMN "tokenUsage" SET NOT NULL;
9 |
10 | -- Modify the maxTokenUsage column with the default value
11 | ALTER TABLE "user_usage"
12 | ALTER COLUMN "maxTokenUsage" SET DEFAULT 1000000,
13 | ALTER COLUMN "maxTokenUsage" SET NOT NULL;
14 |
15 |
--------------------------------------------------------------------------------
/packages/web/drizzle/0001_numerous_famine.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ADD COLUMN "dummy" integer DEFAULT 0;
--------------------------------------------------------------------------------
/packages/web/drizzle/0002_tired_phantom_reporter.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" DROP COLUMN IF EXISTS "dummy";
--------------------------------------------------------------------------------
/packages/web/drizzle/0003_first_phantom_reporter.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ADD COLUMN "subscriptionStatus" text DEFAULT 'inactive' NOT NULL;--> statement-breakpoint
2 | ALTER TABLE "user_usage" ADD COLUMN "paymentStatus" text DEFAULT 'unpaid' NOT NULL
--------------------------------------------------------------------------------
/packages/web/drizzle/0004_cool_sunspot.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ALTER COLUMN "maxTokenUsage" SET DEFAULT 5000000;
--------------------------------------------------------------------------------
/packages/web/drizzle/0005_stiff_madripoor.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ADD COLUMN "lastPayment" timestamp;--> statement-breakpoint
2 | ALTER TABLE "user_usage" ADD COLUMN "currentProduct" text;--> statement-breakpoint
3 | ALTER TABLE "user_usage" ADD COLUMN "currentPlan" text;
--------------------------------------------------------------------------------
/packages/web/drizzle/0006_smart_brother_voodoo.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" DROP COLUMN IF EXISTS "apiUsage";--> statement-breakpoint
2 | ALTER TABLE "user_usage" DROP COLUMN IF EXISTS "maxUsage";
--------------------------------------------------------------------------------
/packages/web/drizzle/0007_slow_dracula.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ALTER COLUMN "maxTokenUsage" SET DEFAULT 0;
--------------------------------------------------------------------------------
/packages/web/drizzle/0008_woozy_viper.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "vercel_tokens" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "user_id" text NOT NULL,
4 | "token" text NOT NULL,
5 | "project_id" text,
6 | "deployment_url" text,
7 | "created_at" timestamp DEFAULT now(),
8 | "updated_at" timestamp DEFAULT now()
9 | );
10 |
--------------------------------------------------------------------------------
/packages/web/drizzle/0009_fluffy_electro.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE "vercel_tokens";
--------------------------------------------------------------------------------
/packages/web/drizzle/0010_certain_groot.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "vercel_tokens" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "user_id" text NOT NULL,
4 | "token" text NOT NULL,
5 | "project_id" text,
6 | "deployment_url" text,
7 | "project_url" text,
8 | "created_at" timestamp DEFAULT now(),
9 | "updated_at" timestamp DEFAULT now()
10 | );
11 |
--------------------------------------------------------------------------------
/packages/web/drizzle/0011_lame_green_goblin.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "vercel_tokens" ADD COLUMN "last_deployment" timestamp;--> statement-breakpoint
2 | ALTER TABLE "vercel_tokens" ADD COLUMN "model_provider" text DEFAULT 'openai';--> statement-breakpoint
3 | ALTER TABLE "vercel_tokens" ADD COLUMN "model_name" text DEFAULT 'gpt-4.1-mini';--> statement-breakpoint
4 | ALTER TABLE "vercel_tokens" ADD COLUMN "last_api_key_update" timestamp;--> statement-breakpoint
5 | ALTER TABLE "user_usage" DROP COLUMN IF EXISTS "currentProduct";--> statement-breakpoint
6 | ALTER TABLE "user_usage" DROP COLUMN IF EXISTS "currentPlan";
--------------------------------------------------------------------------------
/packages/web/drizzle/0012_same_zodiak.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ADD COLUMN "currentProduct" text;--> statement-breakpoint
2 | ALTER TABLE "user_usage" ADD COLUMN "currentPlan" text;
--------------------------------------------------------------------------------
/packages/web/drizzle/0013_deep_blazing_skull.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "vercel_tokens" ADD COLUMN "vision_model_name" text DEFAULT 'gpt-4-vision';
--------------------------------------------------------------------------------
/packages/web/drizzle/0014_demonic_flatman.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "vercel_tokens" ALTER COLUMN "vision_model_name" SET DEFAULT 'gpt-4.1-mini';
--------------------------------------------------------------------------------
/packages/web/drizzle/0015_tense_paper_doll.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "user_usage" ADD COLUMN "hasCatalystAccess" boolean DEFAULT false NOT NULL;
2 |
3 | UPDATE "user_usage"
4 | SET "hasCatalystAccess" = true
5 | WHERE "paymentStatus" IN ('paid', 'succeeded');
6 |
--------------------------------------------------------------------------------
/packages/web/drizzle/0016_quick_vanisher.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "christmas_claims" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "user_id" text NOT NULL,
4 | "claimed_at" timestamp DEFAULT now() NOT NULL
5 | );
6 | --> statement-breakpoint
7 | CREATE UNIQUE INDEX IF NOT EXISTS "unique_christmas_claim_idx" ON "christmas_claims" USING btree ("user_id");
--------------------------------------------------------------------------------
/packages/web/drizzle/0017_large_ser_duncan.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "uploaded_files" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "user_id" text NOT NULL,
4 | "blob_url" text NOT NULL,
5 | "file_type" text NOT NULL,
6 | "original_name" text NOT NULL,
7 | "status" text DEFAULT 'pending' NOT NULL,
8 | "text_content" text,
9 | "tokens_used" integer,
10 | "created_at" timestamp DEFAULT now() NOT NULL,
11 | "updated_at" timestamp DEFAULT now() NOT NULL,
12 | "error" text
13 | );
14 |
--------------------------------------------------------------------------------
/packages/web/drizzle/0018_previous_catseye.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "tier_config" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "tier_name" text NOT NULL,
4 | "max_tokens" integer DEFAULT 0 NOT NULL,
5 | "is_active" boolean DEFAULT true NOT NULL,
6 | "created_at" timestamp DEFAULT now() NOT NULL,
7 | "updated_at" timestamp DEFAULT now() NOT NULL,
8 | CONSTRAINT "tier_config_tier_name_unique" UNIQUE("tier_name")
9 | );
10 | --> statement-breakpoint
11 | ALTER TABLE "user_usage" ADD COLUMN "tier" text DEFAULT 'free' NOT NULL;
--------------------------------------------------------------------------------
/packages/web/drizzle/0019_bizarre_night_thrasher.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "uploaded_files" ADD COLUMN IF NOT EXISTS "r2_key" text;
--------------------------------------------------------------------------------
/packages/web/drizzle/0020_silent_baron_zemo.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "uploaded_files" ADD COLUMN "process_type" text DEFAULT 'standard-ocr';
--------------------------------------------------------------------------------
/packages/web/drizzle/0021_bouncy_shotgun.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "uploaded_files" ADD COLUMN "generated_image_url" text;
--------------------------------------------------------------------------------
/packages/web/drizzle/envConfig.js:
--------------------------------------------------------------------------------
1 | import { loadEnvConfig } from "@next/env";
2 |
3 | var projectDir = process.cwd();
4 | loadEnvConfig(projectDir);
5 |
--------------------------------------------------------------------------------
/packages/web/drizzle/migrations/reset-inactive-users.ts:
--------------------------------------------------------------------------------
1 | import { db, UserUsageTable } from "../schema";
2 | import { eq, or, sql } from "drizzle-orm";
3 |
4 | export async function resetInactiveUsers() {
5 | try {
6 | // 1. Reset token usage for inactive users or users with a lifetime billing cycle
7 | await db
8 | .update(UserUsageTable)
9 | .set({
10 | maxTokenUsage: 0,
11 | tokenUsage: 0
12 | })
13 | .where(
14 | or(
15 | eq(UserUsageTable.subscriptionStatus, 'inactive'),
16 | eq(UserUsageTable.billingCycle, 'lifetime')
17 | )
18 | );
19 |
20 | console.log("Successfully reset token usage for inactive users and users with a lifetime billing cycle");
21 |
22 | // 2. Get count of affected users for logging
23 | const affectedUsers = await db
24 | .select({
25 | count: sql`count(*)`,
26 | })
27 | .from(UserUsageTable)
28 | .where(
29 | or(
30 | eq(UserUsageTable.subscriptionStatus, 'inactive'),
31 | eq(UserUsageTable.billingCycle, 'lifetime')
32 | )
33 | );
34 |
35 | console.log(`Reset ${affectedUsers[0].count} users`);
36 |
37 | } catch (error) {
38 | console.error("Error during migration:", error);
39 | throw error;
40 | }
41 | }
--------------------------------------------------------------------------------
/packages/web/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from '@jest/types';
2 |
3 | const config: Config.InitialOptions = {
4 | // Specify the correct root directories for Jest to look for test files
5 | roots: ['/scripts', '/app'],
6 |
7 | // Use TypeScript for Jest
8 | transform: {
9 | '^.+\\.tsx?$': 'ts-jest',
10 | },
11 |
12 | // Mock environment variables
13 | setupFiles: ['/jest.env.setup.js'],
14 |
15 | // Module name mapper for Next.js imports and other aliases
16 | moduleNameMapper: {
17 | '^@/(.*)$': '/__mocks__/@/$1',
18 | '^next/server$': '/__mocks__/next/server.ts'
19 | },
20 |
21 | // Module file extensions for importing
22 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
23 |
24 |
25 | // Test environment
26 | testEnvironment: 'node',
27 | // every file that has .test.ts will be run
28 | testMatch: ['**/**/*.test.ts'],
29 | // use jest.setup.js for global setup
30 | setupFilesAfterEnv: ['/jest.setup.ts'],
31 |
32 | testTimeout: 30000, // 30 seconds
33 | // Other Jest configurations can go here
34 | };
35 |
36 | export default config;
37 |
--------------------------------------------------------------------------------
/packages/web/jest.env.setup.js:
--------------------------------------------------------------------------------
1 | process.env.GOOGLE_API_KEY = 'test-google-api-key';
2 | process.env.OPENAI_API_KEY = 'test-openai-api-key';
3 | process.env.POSTGRES_URL = 'postgresql://test:test@localhost:5432/test';
4 |
--------------------------------------------------------------------------------
/packages/web/jest.setup.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 |
3 | dotenv.config({ path: '.env.local' });
--------------------------------------------------------------------------------
/packages/web/lib/getUrl.ts:
--------------------------------------------------------------------------------
1 | export const getUrl = () => {
2 | let url =
3 | process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
4 | process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
5 | "http://localhost:3000/";
6 | // Make sure to include `https://` when not localhost.
7 | url = url.startsWith("http") ? url : `https://${url}`;
8 | // Make sure to include a trailing `/`.
9 | url = url.endsWith("/") ? url : `${url}/`;
10 | return url;
11 | };
12 |
--------------------------------------------------------------------------------
/packages/web/lib/posthog.ts:
--------------------------------------------------------------------------------
1 | // app/posthog.js
2 | import { PostHog } from 'posthog-node'
3 |
4 | export default function PostHogClient() {
5 | if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
6 | return null
7 | }
8 | const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
9 | host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',
10 | flushAt: 1,
11 | flushInterval: 0
12 | })
13 | return posthogClient
14 | }
15 |
--------------------------------------------------------------------------------
/packages/web/lib/services/clerk.ts:
--------------------------------------------------------------------------------
1 | import { clerkClient } from "@clerk/nextjs/server";
2 | import { CustomerData } from '../../app/api/webhook/types';
3 |
4 | export async function updateClerkMetadata(data: CustomerData) {
5 | try {
6 | const client = await clerkClient();
7 | await client.users.updateUserMetadata(data.userId, {
8 | publicMetadata: {
9 | stripe: {
10 | customerId: data.customerId,
11 | status: data.status,
12 | payment: data.paymentStatus,
13 | product: data.product,
14 | plan: data.plan,
15 | billingCycle: data.billingCycle,
16 | lastPayment: data.lastPayment,
17 | },
18 | },
19 | });
20 |
21 | console.log(`Updated Clerk metadata for user ${data.userId}`);
22 | } catch (error) {
23 | console.error('Error updating Clerk metadata:', error);
24 | throw error; // Re-throw to be handled by the webhook handler
25 | }
26 | }
--------------------------------------------------------------------------------
/packages/web/lib/services/loops.ts:
--------------------------------------------------------------------------------
1 | import { CustomerData } from '@/app/api/webhook/types';
2 |
3 | interface LoopsEvent {
4 | email: string;
5 | firstName?: string;
6 | lastName?: string;
7 | userId: string;
8 | eventName: string;
9 | data?: Record;
10 | }
11 |
12 | export async function trackLoopsEvent({
13 | email,
14 | firstName,
15 | lastName,
16 | userId,
17 | eventName,
18 | data = {}
19 | }: LoopsEvent) {
20 | try {
21 | const response = await fetch('https://app.loops.so/api/v1/events/send', {
22 | method: 'POST',
23 | headers: {
24 | 'Content-Type': 'application/json',
25 | Authorization: `Bearer ${process.env.LOOPS_API_KEY}`,
26 | },
27 | body: JSON.stringify({
28 | email,
29 | eventName,
30 | userId,
31 | firstName,
32 | lastName,
33 | userGroup: "StripeCustomers",
34 | ...data,
35 | }),
36 | });
37 |
38 | if (!response.ok) {
39 | console.error('Loops tracking failed:', await response.text());
40 | }
41 | } catch (error) {
42 | // Log but don't throw to prevent webhook processing from failing
43 | console.error('Error tracking Loops event:', error);
44 | }
45 | }
--------------------------------------------------------------------------------
/packages/web/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function tw(classNames: string) {
9 | // This function ensures all Tailwind classes get the 'fo-' prefix
10 | // to avoid style conflicts with Obsidian
11 | return classNames;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/web/lib/youtubeTranscript.ts:
--------------------------------------------------------------------------------
1 | import { YoutubeTranscript } from 'youtube-transcript';
2 |
3 | export async function getYouTubeTranscript(videoId: string): Promise {
4 | try {
5 | const transcript = await YoutubeTranscript.fetchTranscript(videoId);
6 | // throw new Error('Failed to fetch YouTube transcript');
7 | return transcript.map(entry => entry.text).join(' ');
8 | } catch (error) {
9 | console.error('Error fetching YouTube transcript:', error);
10 | throw new Error('Failed to fetch YouTube transcript');
11 | }
12 | }
--------------------------------------------------------------------------------
/packages/web/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | eslint: {
4 | ignoreDuringBuilds: true,
5 | },
6 | async rewrites() {
7 | return [
8 | {
9 | source: "/ingest/static/:path*",
10 | destination: "https://us-assets.i.posthog.com/static/:path*",
11 | },
12 | {
13 | source: "/ingest/:path*",
14 | destination: "https://us.i.posthog.com/:path*",
15 | },
16 | ];
17 | },
18 | // This is required to support PostHog trailing slash API requests
19 | skipTrailingSlashRedirect: true,
20 |
21 | async headers() {
22 | return [
23 | {
24 | source: "/api/:path*",
25 | headers: [
26 | { key: "Access-Control-Allow-Credentials", value: "true" },
27 | { key: "Access-Control-Allow-Origin", value: "*" },
28 | { key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
29 | { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
30 | ]
31 | }
32 | ]
33 | }
34 | };
35 |
36 | module.exports = nextConfig;
37 |
--------------------------------------------------------------------------------
/packages/web/pages/_error.js:
--------------------------------------------------------------------------------
1 | function Error({ statusCode }) {
2 | return (
3 |
4 | {statusCode
5 | ? `An error ${statusCode} occurred on server`
6 | : 'An error occurred on client'}
7 |
8 | );
9 | }
10 |
11 | Error.getInitialProps = ({ res, err }) => {
12 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
13 | return { statusCode };
14 | };
15 |
16 | export default Error;
--------------------------------------------------------------------------------
/packages/web/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | "@tailwindcss/postcss": {},
4 | autoprefixer: {},
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/packages/web/public/big-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/public/big-logo.png
--------------------------------------------------------------------------------
/packages/web/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/public/favicon.ico
--------------------------------------------------------------------------------
/packages/web/public/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/public/icon.icns
--------------------------------------------------------------------------------
/packages/web/public/lightning-logo.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/public/lightning-logo.avif
--------------------------------------------------------------------------------
/packages/web/public/logo.icns/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/public/logo.icns/512x512.png
--------------------------------------------------------------------------------
/packages/web/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/web/scripts/migrate.ts:
--------------------------------------------------------------------------------
1 | import { resetInactiveUsers } from "../drizzle/migrations/reset-inactive-users";
2 |
3 | async function runMigration() {
4 | try {
5 | console.log("Starting migration...");
6 |
7 | await resetInactiveUsers();
8 |
9 | console.log("Migration completed successfully");
10 | } catch (error) {
11 | console.error("Migration failed:", error);
12 | process.exit(1);
13 | }
14 | }
15 |
16 | runMigration();
--------------------------------------------------------------------------------
/packages/web/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | darkMode: ["class"],
4 | plugins: ["tailwindcss-animate"],
5 | }
6 |
--------------------------------------------------------------------------------
/packages/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ESNext",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": false,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 |
25 | },
26 | "strictNullChecks": false,
27 | "baseUrl": ".",
28 | "composite": true
29 | },
30 | "typeRoots": ["./node_modules/@types"],
31 | "types": ["react", "react-dom"],
32 | "include": [
33 | "next-env.d.ts",
34 | "**/*.ts",
35 | "**/*.tsx",
36 | ".next/types/**/*.ts",
37 | "types/**/*.d.ts"
38 | ],
39 | "exclude": ["node_modules"]
40 | }
41 |
--------------------------------------------------------------------------------
/packages/web/types/globals.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
3 | declare global {
4 | interface CustomJwtSessionClaims {
5 | publicMetadata: {
6 | stripe?: {
7 | status: "incomplete" | "complete";
8 | payment: "unpaid" | "paid";
9 | };
10 | };
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/web/uploads/9e621745-c5de-4a41-8538-e8b63b2e145e-compilation-without-summary.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/packages/web/uploads/9e621745-c5de-4a41-8538-e8b63b2e145e-compilation-without-summary.pdf
--------------------------------------------------------------------------------
/packages/web/vercel-non.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/patches/xcode@3.0.1.patch:
--------------------------------------------------------------------------------
1 | diff --git a/lib/pbxProject.js b/lib/pbxProject.js
2 | index 068548ab89dfd2d39f90d46d881c17dc86f90bf4..8ee4b8b30788ad057cd5f1b1efe41fa51478d4ce 100644
3 | --- a/lib/pbxProject.js
4 | +++ b/lib/pbxProject.js
5 | @@ -1679,7 +1679,7 @@ function correctForFrameworksPath(file, project) {
6 | function correctForPath(file, project, group) {
7 | var r_group_dir = new RegExp('^' + group + '[\\\\/]');
8 |
9 | - if (project.pbxGroupByName(group).path)
10 | + if (project.pbxGroupByName(group)&&project.pbxGroupByName(group).path)
11 | file.path = file.path.replace(r_group_dir, '');
12 |
13 | return file;
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/README.md:
--------------------------------------------------------------------------------
1 |
2 | turn your screen into a living knowledge base with ai
3 |
4 | analyse your screen/mic activity in real time and write logs, to build a CRM, market research, ideas, user personas, etc.
5 |
6 | local LLM first
7 |
8 | https://github.com/user-attachments/assets/e4c115d9-51cf-4870-aec4-4df4743d2d02
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/pipes/file-organizer-notifications/bun.lockb
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | transpilePackages: ["@screenpipe/js"],
5 | webpack: (config, { }) => {
6 | return config;
7 | },
8 | devIndicators: {
9 | buildActivity: false,
10 | appIsrStatus: false
11 | }
12 | };
13 |
14 | export default nextConfig;
15 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/pipe.json:
--------------------------------------------------------------------------------
1 | {
2 | "crons": [
3 | {
4 | "path": "/api/log",
5 | "schedule": "0 */5 * * * *"
6 | },
7 | {
8 | "path": "/api/intelligence",
9 | "schedule": "0 0 */1 * * *"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/app/api/check-folder/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import * as fs from "fs/promises";
3 |
4 | export async function POST(req: Request) {
5 | try {
6 | const { path } = await req.json();
7 |
8 | try {
9 | await fs.access(path);
10 | return NextResponse.json({ exists: true });
11 | } catch {
12 | return NextResponse.json({ exists: false });
13 | }
14 | } catch (error) {
15 | console.error("error checking folder:", error);
16 | return NextResponse.json({ exists: false });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/pipes/file-organizer-notifications/src/app/favicon.ico
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 | import { Toaster } from "@/components/ui/toaster";
5 |
6 | const geistSans = Geist({
7 | variable: "--font-geist-sans",
8 | subsets: ["latin"],
9 | });
10 |
11 | const geistMono = Geist_Mono({
12 | variable: "--font-geist-mono",
13 | subsets: ["latin"],
14 | });
15 |
16 | export const metadata: Metadata = {
17 | title: "Obsidian • Screenpipe",
18 | description: "Turn your screen time into a living knowledge base with AI",
19 | };
20 |
21 | export default function RootLayout({
22 | children,
23 | }: Readonly<{
24 | children: React.ReactNode;
25 | }>) {
26 | return (
27 |
28 |
31 | {children}
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | // "use client";
2 |
3 | import { ObsidianSettings } from "@/components/obsidian-settings";
4 |
5 | export default function Page() {
6 | return (
7 |
8 |
Your knowledge base on autopilot
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/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 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/hooks/use-toast"
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | } from "@/components/ui/toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/lib/hooks/use-copy-to-clipboard.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 |
5 | export interface useCopyToClipboardProps {
6 | timeout?: number;
7 | }
8 |
9 | export function useCopyToClipboard({
10 | timeout = 2000,
11 | }: useCopyToClipboardProps) {
12 | const [isCopied, setIsCopied] = React.useState(false);
13 |
14 | const copyToClipboard = (value: string) => {
15 | if (typeof window === "undefined" || !navigator.clipboard?.writeText) {
16 | return;
17 | }
18 |
19 | if (!value) {
20 | return;
21 | }
22 |
23 | navigator.clipboard.writeText(value).then(() => {
24 | setIsCopied(true);
25 |
26 | setTimeout(() => {
27 | setIsCopied(false);
28 | }, timeout);
29 | });
30 | };
31 |
32 | return { isCopied, copyToClipboard };
33 | }
34 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/src/lib/hooks/use-debounce.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | export function useDebounce(value: T, delay: number): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const handler = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, delay);
10 |
11 | return () => {
12 | clearTimeout(handler);
13 | };
14 | }, [value, delay]);
15 |
16 | return debouncedValue;
17 | }
18 |
--------------------------------------------------------------------------------
/pipes/file-organizer-notifications/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - apps/*
3 | - packages/*
4 | - tooling/*
5 |
6 | catalog:
7 | # Auth
8 | "@auth/core": 0.37.2
9 | "@auth/drizzle-adapter": ~1.7.4
10 | "next-auth": 5.0.0-beta.25
11 |
12 | # Dev tooling
13 | eslint: ^9.19.0
14 | prettier: ^3.4.2
15 | tailwindcss: ^3.4.15
16 | typescript: ^5.7.3
17 |
18 | # Misc
19 | zod: ^3.24.1
20 |
21 | # Tanstack & tRPC
22 | "@tanstack/react-query": ^5.67.1
23 | "@trpc/client": ^11.0.0-rc.824
24 | "@trpc/tanstack-react-query": ^11.0.0-rc.824
25 | "@trpc/server": ^11.0.0-rc.824
26 |
27 | catalogs:
28 | react19:
29 | react: 19.0.0
30 | react-dom: 19.0.0
31 | "@types/react": ^19.0.0
32 | "@types/react-dom": ^19.0.0
33 | react18:
34 | react: 18.3.1
35 | react-dom: 18.3.1
36 | "@types/react": ^18.3.1
37 | "@types/react-dom": ^18.3.1
38 |
--------------------------------------------------------------------------------
/render.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | - type: web
3 | name: File Organizer 2000
4 | runtime: node
5 | rootDir: ./packages/web
6 | buildCommand: pnpm install; pnpm run build:self-host
7 | startCommand: pnpm start
8 | autoDeploy: true
9 | envVars:
10 | - key: NODE_ENV
11 | value: production
12 | - key: OPENAI_API_KEY
13 | sync: false
14 | - key: SOLO_API_KEY
15 | sync: false
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "strict": false,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "jsx": "react-jsx",
10 | "composite": true,
11 | "declaration": true,
12 | "baseUrl": ".",
13 | "paths": {
14 | "@file-organizer/*": [
15 | "packages/*/src"
16 | ],
17 | "@/*": [
18 | "packages/web/*"
19 | ]
20 | }
21 | },
22 | "references": [
23 | {
24 | "path": "packages/shared"
25 | },
26 | {
27 | "path": "packages/plugin"
28 | },
29 | {
30 | "path": "packages/web"
31 | },
32 | {
33 | "path": "packages/audio-server"
34 | },
35 | {
36 | "path": "packages/mobile"
37 | }
38 | ],
39 | "exclude": [
40 | "node_modules"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": [".env", ".env.*"],
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": [
8 | "dist/**",
9 | ".next/**",
10 | "!.next/cache/**",
11 | "main.js",
12 | "styles.css"
13 | ]
14 | },
15 | "test": {
16 | "dependsOn": ["^build"],
17 | "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
18 | },
19 | "lint": {
20 | "outputs": []
21 | },
22 | "dev": {
23 | "cache": false,
24 | "persistent": true,
25 | "env": [
26 | "OPENAI_API_KEY",
27 | "POSTGRES_URL",
28 | "DEEPGRAM_API_KEY",
29 | "PORT",
30 | "GROQ_API_KEY",
31 | "ANTHROPIC_API_KEY",
32 | "GOOGLE_API_KEY",
33 | "DEEPSEEK_API_KEY",
34 | "MISTRAL_API_KEY",
35 | "R2_BUCKET",
36 | "R2_ENDPOINT",
37 | "R2_ACCESS_KEY_ID",
38 | "R2_SECRET_ACCESS_KEY",
39 | "R2_REGION",
40 | "IS_NOTE_COMPANION_TEAM"
41 | ]
42 | },
43 |
44 | "clean": {
45 | "cache": false
46 | },
47 | "db:generate": {
48 | "cache": false
49 | },
50 | "db:migrate": {
51 | "cache": false
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/tutorials/assistantSidebar.md:
--------------------------------------------------------------------------------
1 | # The Assistant Sidebar
2 |
3 | Currently available for [early access users](https://buy.stripe.com/3cs29sdqY7QadeU147) only.
4 |
5 | The assistant sidebar is meant to give you more control in terms of how your file is processed. Instead of moving your file to Inbox, the sidebar will include suggestions for you to choose from.
6 |
7 | 
8 |
9 | But first, you'll need to follow these two steps to enable the feature:
10 |
11 | 1. Go to experimental features in the plugin settings and add your early access code
12 | 2. Go to the Hotkeys section in the main settings and search for `AI Note Companion: Show Assistant`. Then, simply assign a keyboard command.
13 |
14 | Now for the actual functionality all you need to do is:
15 |
16 | 1. In your vault, press the keyboard command you just set up. The assistant sidebar should now appear to the right.
17 | 2. Then click on any of your files and you should see a number of actionable items inside the sidebar. For now, you can add suggested tags, aliases, and move your file to the suggested folder.
18 |
19 | That's it! We are still actively developing this feature and will be happy to hear about any feedback/suggestions.
20 |
--------------------------------------------------------------------------------
/tutorials/bugs.md:
--------------------------------------------------------------------------------
1 | # Debugging Instructions for FO2K
2 |
3 | Before you share bugs please share:
4 | - [ ] your current version of Fo2k
5 |
6 |
7 | - [ ] Customer status: lifetime / cloud (are you using cloud versino or lifetime)
8 | - [ ] Share exact steps so we can reprocue
9 |
10 | ## Lifetime extra steps (disregard if your using our cloud instance)
11 | - [ ] Deployment method (Only for lifetime): Vercel / Render
12 | - [ ] Deployment status: depending on if vercel or render try to find out the latest time your service got deployed and share a screenshot.
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tutorials/getStarted.md:
--------------------------------------------------------------------------------
1 | # Get Started with FileOrganizer 2000
2 |
3 | 1. Install & enable the plugin.
4 | 2. Go to plugin Options and click on the "Login" button.
5 | 3. Sign up.
6 | 4. You'll then be redirected to a screen with button to generate an API key.
7 | 5. Once you have your API key, copy & paste it into the plugin Options in the "Enter your API Key" field.
8 | 6. That's it. Now, access your vault within the Obsidian app, and move a file into the dedicated Inbox folder. See the magic?!
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tutorials/images/assistant_sidebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/tutorials/images/assistant_sidebar.png
--------------------------------------------------------------------------------
/tutorials/images/pre_processed_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/tutorials/images/pre_processed_file.png
--------------------------------------------------------------------------------
/tutorials/images/processed_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/different-ai/note-companion/91bb99d0a9492533b6331164633d73456d6fea5f/tutorials/images/processed_file.png
--------------------------------------------------------------------------------
/tutorials/local-llms.md:
--------------------------------------------------------------------------------
1 | If you want to experiment with local llms.
2 |
3 | Try to check the web/lib/models.ts
4 |
5 | please do not ask for support on this, except if its to submit a pull request.
6 |
7 | we use two env var to decide on the model
8 |
9 | MODEL_NAME
10 | VISON_MODEL
11 |
12 | ---
13 |
14 | we have limited ollama support at the moment.
15 |
16 | what does that mean:
17 | ## Chat
18 | - [x] llama 3.2 support in chat (doesn't require any server change)
19 | - [ ] support for "tool" call
20 |
21 | why no tool call:
22 | we have a bunch of "tool calls that we make to fetch youtube transcripts, filter latest files etc, this is not supported right now) because we couldn't get it to behave well with tool calling.
23 |
24 | ## Organizer
25 |
26 | **No support at all right now**
27 |
28 | not tested
29 |
30 | -----
31 |
32 | ## Possible solutions
33 |
34 | If someone is interest on developing a solution. I would be happy to point you in right direction. I have a couple ideas, but all of them require some effort.
35 |
36 |
--------------------------------------------------------------------------------
/tutorials/mobile.md:
--------------------------------------------------------------------------------
1 | ## iOS shortcut to send Apple Notes and Audios to Obsidian
2 |
3 | The iOS shortcut below makes it easy for you to easily work from your phone with this plugin.
4 |
5 | https://www.icloud.com/shortcuts/06915768862848fb9711f2f19b6405e2
6 |
7 | how to set it up: https://youtu.be/zWJgIRlDWkk?si=HSeOUKaMfJvaLtKI
8 |
9 | Notes:
10 | - It works when your vault is on a cloud drive. I use it with iCloud and works great. Doesn't work with OneDrive last time I tested.
11 | - Currently only works if your iOS is in English. But if you reach out on discord I can help you set it up in your language.
12 |
--------------------------------------------------------------------------------