├── .env.example
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode
└── settings.json
├── README.md
├── app
├── (auth)
│ ├── layout.tsx
│ ├── sign-in
│ │ └── [[...sign-in]]
│ │ │ └── page.tsx
│ └── sign-up
│ │ └── [[...sign-up]]
│ │ └── page.tsx
├── (dashboard)
│ ├── action-items
│ │ ├── action-items-wrapper.tsx
│ │ └── page.tsx
│ ├── record
│ │ └── page.tsx
│ └── recordings
│ │ ├── [recordingId]
│ │ ├── _components
│ │ │ ├── action-form.tsx
│ │ │ └── note-card.tsx
│ │ └── page.tsx
│ │ ├── _components
│ │ ├── columns.tsx
│ │ ├── data-table-column-header.tsx
│ │ ├── data-table-pagination.tsx
│ │ ├── data-table-row-actions.tsx
│ │ ├── data-table-toolbar.tsx
│ │ ├── data-table-view-options.tsx
│ │ ├── data-table.tsx
│ │ └── notes-wrapper.tsx
│ │ └── page.tsx
├── (marketing)
│ ├── _component
│ │ ├── bento-grid.tsx
│ │ ├── bentogrid.tsx
│ │ ├── hero-highlight-section.tsx
│ │ ├── hero-highlight.tsx
│ │ └── marketing-bentogrid.tsx
│ └── page.tsx
├── (profile)
│ ├── layout.tsx
│ └── user-profile
│ │ └── [[...user-profile]]
│ │ └── page.tsx
├── (public)
│ └── share
│ │ └── [noteId]
│ │ └── page.tsx
├── error.tsx
├── globals.css
├── layout.tsx
├── loading.tsx
└── not-found.tsx
├── bun.lockb
├── components.json
├── components
├── delete-model.tsx
├── footer.tsx
├── navbar.tsx
├── providers
│ └── convex-provider.tsx
├── sharechat-model.tsx
├── skeltons.tsx
├── theme-provider.tsx
├── theme-toggle.tsx
├── ui
│ ├── alert-dialog.tsx
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── checkbox.tsx
│ ├── command.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── popover.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── skeleton.tsx
│ ├── table.tsx
│ └── tabs.tsx
└── user-nav.tsx
├── constants
└── index.ts
├── convex
├── README.md
├── _generated
│ ├── api.d.ts
│ ├── api.js
│ ├── dataModel.d.ts
│ ├── server.d.ts
│ └── server.js
├── assembly.ts
├── auth.config.ts
├── gemini.ts
├── notes.ts
├── schema.ts
└── tsconfig.json
├── hooks
└── use-origin.tsx
├── lib
└── utils.ts
├── license.md
├── middleware.ts
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── public
├── desktop.png
├── favicon.ico
├── mobile.png
├── next.svg
├── nonrecording_mic.svg
├── recording_mic.svg
├── thumbnail.png
└── vercel.svg
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | # make account here for keys: https://www.convex.dev
2 | CONVEX_DEPLOYMENT=
3 | NEXT_PUBLIC_CONVEX_URL=
4 |
5 | # make account here for keys: https://clerk.com
6 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
7 | CLERK_SECRET_KEY=
8 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
9 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
10 | NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL=/record
11 | NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/record
12 |
13 | # make account here for keys: https://www.assemblyai.com
14 | ASSEMBLY_API_KEY=
15 |
16 | # make account here for keys: https://ai.google.dev/gemini-api/docs/api-key
17 | GOOGLE_API_KEY=
18 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 | dist
5 | .next
6 | out
7 | node_modules
8 | public
9 |
10 | # Ignore specific config files
11 | .env
12 | .env.local
13 |
14 | # Ignore package manager lock files
15 | bun.lockb
16 |
17 | # Ignore Next.js generated files
18 | .next/
19 |
20 | # Ignore the build output directory
21 | out/
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "project": "./tsconfig.json"
5 | },
6 | "plugins": ["@typescript-eslint"],
7 | "extends": [
8 | "next/core-web-vitals",
9 | "plugin:@typescript-eslint/recommended-type-checked",
10 | "plugin:@typescript-eslint/stylistic-type-checked"
11 | ],
12 | "rules": {
13 | "@typescript-eslint/array-type": "off",
14 | "@typescript-eslint/no-unsafe-assignment": "off",
15 | "@typescript-eslint/no-unsafe-member-access": "off",
16 | "@typescript-eslint/consistent-type-definitions": "off",
17 | "@typescript-eslint/consistent-type-imports": [
18 | "warn",
19 | {
20 | "prefer": "type-imports",
21 | "fixStyle": "inline-type-imports"
22 | }
23 | ],
24 | "@typescript-eslint/no-unused-vars": [
25 | "warn",
26 | {
27 | "argsIgnorePattern": "^_"
28 | }
29 | ],
30 | "@typescript-eslint/require-await": "off",
31 | "@typescript-eslint/no-misused-promises": [
32 | "error",
33 | {
34 | "checksVoidReturn": {
35 | "attributes": false
36 | }
37 | }
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.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 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 | dist
5 | .next
6 | out
7 | node_modules
8 | public
9 |
10 | # Ignore specific config files
11 | .env
12 | .env.local
13 |
14 | # Ignore package manager lock files
15 | bun.lockb
16 |
17 | # Ignore Next.js generated files
18 | .next/
19 |
20 | # Ignore the build output directory
21 | out/
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/prettierrc",
3 | "plugins": ["prettier-plugin-tailwindcss"],
4 | "singleQuote": true,
5 | "semi": false,
6 | "trailingComma": "es5",
7 | "printWidth": 80,
8 | "tabWidth": 2,
9 | "useTabs": false,
10 | "bracketSpacing": true,
11 | "arrowParens": "always",
12 | "endOfLine": "lf"
13 | }
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // Formatting using Prettier by default for all languages
3 | "editor.formatOnPaste": true,
4 | "editor.formatOnSave": true,
5 | "editor.defaultFormatter": "esbenp.prettier-vscode",
6 | "editor.codeActionsOnSave": {
7 | "source.fixAll": "explicit"
8 | },
9 | // Formatting using Prettier for JavaScript, overrides VSCode default.
10 | "[javascript]": {
11 | "editor.defaultFormatter": "esbenp.prettier-vscode"
12 | },
13 | // Linting using ESLint.
14 | "eslint.validate": [
15 | "javascript",
16 | "javascriptreact",
17 | "typescript",
18 | "typescriptreact"
19 | ],
20 | // Disable TypeScript surveys.
21 | "typescript.surveys.enabled": false,
22 | "typescript.tsdk": "node_modules/typescript/lib"
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
NotesGPT
3 | NotesGPT seamlessly converts your voice notes into organized summaries and clear action items using AI.
4 |
5 |
6 |
9 |
10 |
11 | 
12 |
13 | Key Features:
14 |
15 | - Landing page 🛬
16 | - Light and Dark mode 🌓
17 | - Authentication 🔐
18 | - Real-time voice-to-text conversion 🗣️➡️📝
19 | - Organized summaries generation 📑
20 | - Generate transcript using AI 📑
21 | - Use google gemini ai for title, summary generation 🚀
22 | - Use assembly ai for generating transcript of voice 🔊
23 | - Ability to add action items ✅
24 | - Ability to search through action items 🔎
25 | - Share note functionality 🌍
26 |
27 | ### Prerequisites
28 |
29 | **You should have Nodejs and Bun installed on your system**
30 |
31 | ### Cloning the repository
32 |
33 | ```shell
34 | git clone https://github.com/abdtriedcoding/notesGPT.git
35 | ```
36 |
37 | ### Install packages
38 |
39 | ```shell
40 | bun i
41 | ```
42 |
43 | ### Setup .env file taking refrence from .env.example file
44 |
45 | ### Setup Convex
46 |
47 | ```shell
48 | bunx convex dev
49 |
50 | ```
51 |
52 | ### Start the app
53 |
54 | ```shell
55 | bun run dev
56 | ```
57 |
--------------------------------------------------------------------------------
/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function AuthLayout({
2 | children,
3 | }: {
4 | children: React.ReactNode
5 | }) {
6 | return (
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/app/(auth)/sign-in/[[...sign-in]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignIn } from '@clerk/nextjs'
2 |
3 | export default function Signin() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/app/(auth)/sign-up/[[...sign-up]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignUp } from '@clerk/nextjs'
2 |
3 | export default function Signup() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/app/(dashboard)/action-items/action-items-wrapper.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState } from 'react'
4 | import { Input } from '@/components/ui/input'
5 | import { type api } from '@/convex/_generated/api'
6 | import { EmptyState } from '@/components/skeltons'
7 | import { type Preloaded, usePreloadedQuery } from 'convex/react'
8 | import NoteCard from '../recordings/[recordingId]/_components/note-card'
9 |
10 | export default function ActionItemsWrapper(props: {
11 | preloadedActionItems: Preloaded
12 | }) {
13 | const [search, setSearch] = useState('')
14 | const userActionItems = usePreloadedQuery(props.preloadedActionItems)
15 |
16 | if (userActionItems.length === 0) return
17 |
18 | const actionItems = userActionItems.filter(
19 | (e) =>
20 | e.action.toLocaleLowerCase().includes(search.toLocaleLowerCase()) ||
21 | e.title?.toLocaleLowerCase().includes(search.toLocaleLowerCase())
22 | )
23 |
24 | return (
25 |
26 |
Action Items
27 |
28 | {userActionItems?.length ?? 0} tasks
29 |
30 |
setSearch(e.target.value)}
33 | placeholder="Search through action items..."
34 | />
35 | {actionItems.length === 0 ? (
36 |
No Result Found
37 | ) : (
38 | actionItems.map((item) =>
)
39 | )}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/app/(dashboard)/action-items/page.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthToken } from '@/lib/utils'
2 | import { preloadQuery } from 'convex/nextjs'
3 | import { api } from '@/convex/_generated/api'
4 | import ActionItemsWrapper from './action-items-wrapper'
5 |
6 | export default async function ActionItemsPage() {
7 | const token = await getAuthToken()
8 | const preloadedActionItems = await preloadQuery(
9 | api.notes.getActionItems,
10 | {},
11 | { token }
12 | )
13 |
14 | return
15 | }
16 |
--------------------------------------------------------------------------------
/app/(dashboard)/record/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Image from 'next/image'
4 | import { useEffect, useState } from 'react'
5 | import { useRouter } from 'next/navigation'
6 | import { formatTime, getCurrentFormattedDate } from '@/lib/utils'
7 |
8 | import { useMutation } from 'convex/react'
9 | import { api } from '@/convex/_generated/api'
10 |
11 | export default function RecordPage() {
12 | const router = useRouter()
13 | const [isRunning, setIsRunning] = useState(false)
14 | const [totalSeconds, setTotalSeconds] = useState(0)
15 | const [mediaRecorder, setMediaRecorder] = useState(null)
16 | const [title, setTitle] = useState('Record your voice note')
17 |
18 | const generateUploadUrl = useMutation(api.notes.generateUploadUrl)
19 | const createNote = useMutation(api.notes.createNote)
20 |
21 | useEffect(() => {
22 | let interval: NodeJS.Timeout
23 |
24 | if (isRunning) {
25 | interval = setInterval(() => {
26 | setTotalSeconds((prevTotalSeconds) => prevTotalSeconds + 1)
27 | }, 1000)
28 | }
29 |
30 | return () => clearInterval(interval)
31 | }, [isRunning])
32 |
33 | async function startRecording() {
34 | setIsRunning(true)
35 | const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
36 | const recorder = new MediaRecorder(stream)
37 | const audioChunks: Blob[] = []
38 |
39 | recorder.ondataavailable = (e) => {
40 | audioChunks.push(e.data)
41 | }
42 |
43 | recorder.onstop = async () => {
44 | const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' })
45 | const postUrl = await generateUploadUrl()
46 | const result = await fetch(postUrl, {
47 | method: 'POST',
48 | headers: { 'Content-Type': 'audio/mp3' },
49 | body: audioBlob,
50 | })
51 | const { storageId } = await result.json()
52 | const noteId = await createNote({ storageId })
53 | router.push(`/recordings/${noteId}`)
54 | }
55 | setMediaRecorder(recorder)
56 | recorder.start()
57 | }
58 |
59 | function stopRecording() {
60 | mediaRecorder?.stop()
61 | setIsRunning(false)
62 | }
63 |
64 | const handleRecordClick = async () => {
65 | if (title === 'Record your voice note') {
66 | setTitle('Recording...')
67 | await startRecording()
68 | } else if (title === 'Recording...') {
69 | setTitle('Processing...')
70 | stopRecording()
71 | }
72 | }
73 |
74 | return (
75 |
76 |
{title}
77 |
{getCurrentFormattedDate()}
78 |
79 |
80 |
90 |
91 |
92 | {formatTime(Math.floor(totalSeconds / 60))}:
93 | {formatTime(totalSeconds % 60)}
94 |
95 |
96 |
97 |
98 |
117 |
118 | )
119 | }
120 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/[recordingId]/_components/action-form.tsx:
--------------------------------------------------------------------------------
1 | import { toast } from 'sonner'
2 | import { useState } from 'react'
3 | import { useMutation } from 'convex/react'
4 | import { api } from '@/convex/_generated/api'
5 | import { type Id } from '@/convex/_generated/dataModel'
6 |
7 | import { Input } from '@/components/ui/input'
8 | import { Button } from '@/components/ui/button'
9 |
10 | export default function ActionForm({ id }: { id: Id<'notes'> }) {
11 | const [input, setInput] = useState('')
12 | const createActionItem = useMutation(api.notes.createActionItem)
13 |
14 | const handleCreateAction = () => {
15 | if (!input.trim()) {
16 | return toast.error('Input is empty')
17 | }
18 |
19 | const promise = createActionItem({
20 | noteId: id,
21 | action: input.trim(),
22 | })
23 | toast.promise(promise, {
24 | loading: 'Creating action item...',
25 | success: 'Action item created',
26 | error: 'Failed to create action item',
27 | })
28 | setInput('')
29 | }
30 |
31 | return (
32 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/[recordingId]/_components/note-card.tsx:
--------------------------------------------------------------------------------
1 | import { toast } from 'sonner'
2 | import { formatDate } from '@/lib/utils'
3 | import { useMutation } from 'convex/react'
4 | import { api } from '@/convex/_generated/api'
5 | import { Checkbox } from '@/components/ui/checkbox'
6 | import { type Id } from '@/convex/_generated/dataModel'
7 | import { Card, CardContent } from '@/components/ui/card'
8 |
9 | interface ActionItemProps {
10 | _id: Id<'actionItems'>
11 | _creationTime: number
12 | userId: string
13 | noteId: Id<'notes'>
14 | action: string
15 | title?: string
16 | preview?: boolean
17 | }
18 |
19 | export default function NoteCard({
20 | _creationTime,
21 | action,
22 | title,
23 | _id,
24 | preview,
25 | }: ActionItemProps) {
26 | const removeActionItem = useMutation(api.notes.removeActionItem)
27 |
28 | const handleremoveActionItem = () => {
29 | const promise = removeActionItem({
30 | id: _id,
31 | })
32 | toast.promise(promise, {
33 | loading: 'Deleting action item...',
34 | success: 'Action item completed',
35 | error: ' Failed to delete action item',
36 | })
37 | }
38 |
39 | return (
40 |
41 |
42 |
43 | {!preview &&
}
44 |
{action}
45 |
46 |
47 | From: {title ?? 'No Title'}
48 |
49 |
50 | {formatDate(_creationTime)}
51 |
52 |
53 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/[recordingId]/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useQuery } from 'convex/react'
4 | import { useParams } from 'next/navigation'
5 | import { api } from '@/convex/_generated/api'
6 | import { type Doc, type Id } from '@/convex/_generated/dataModel'
7 |
8 | import NoteCard from './_components/note-card'
9 | import ActionForm from './_components/action-form'
10 | import { ActionItemSkelton } from '@/components/skeltons'
11 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
12 |
13 | interface NoteWithActionItem {
14 | note: Doc<'notes'>
15 | actionItems: Doc<'actionItems'>[]
16 | }
17 |
18 | export default function RecordingIdPage() {
19 | const params = useParams()
20 | const { recordingId } = params
21 |
22 | const noteWithActionItems = useQuery(api.notes.getNoteById, {
23 | id: recordingId as Id<'notes'>,
24 | })
25 |
26 | if (noteWithActionItems === undefined) {
27 | return
28 | }
29 |
30 | const { note, actionItems }: NoteWithActionItem = noteWithActionItems
31 |
32 | return (
33 | <>
34 |
35 |
36 | Transcript
37 | Summary
38 | Action Items
39 |
40 | {note?.transcription}
41 | {note?.summary}
42 |
43 |
44 | {actionItems.map((item) => (
45 |
46 | ))}
47 |
48 |
49 | >
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/columns.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { formatDate } from '@/lib/utils'
4 | import { type ColumnDef } from '@tanstack/react-table'
5 | import { type Id } from '@/convex/_generated/dataModel'
6 | import { DataTableRowActions } from './data-table-row-actions'
7 | import { DataTableColumnHeader } from './data-table-column-header'
8 |
9 | interface Note {
10 | _creationTime: number
11 | _id: Id<'notes'>
12 | audioFileId: string
13 | audioFileUrl: string
14 | summary?: string
15 | title?: string
16 | transcription?: string
17 | userId: string
18 | }
19 |
20 | export const columns: ColumnDef[] = [
21 | {
22 | accessorKey: 'title',
23 | header: ({ column }) => (
24 |
25 | ),
26 | cell: ({ row }) => {
27 | return (
28 |
29 | {row.getValue('title')}
30 |
31 | )
32 | },
33 | },
34 | {
35 | accessorKey: '_creationTime',
36 | header: ({ column }) => (
37 |
38 | ),
39 | cell: ({ row }) => {
40 | return (
41 |
42 | {formatDate(row.getValue('_creationTime'))}
43 |
44 | )
45 | },
46 | },
47 | {
48 | id: 'actions',
49 | cell: ({ row }) => {
50 | const { _id } = row.original
51 | return
52 | },
53 | },
54 | ]
55 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/data-table-column-header.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 | import { type Column } from '@tanstack/react-table'
3 | import { Button } from '@/components/ui/button'
4 | import {
5 | ArrowDownIcon,
6 | ArrowUpIcon,
7 | ChevronsUpDown,
8 | EyeOff,
9 | } from 'lucide-react'
10 | import {
11 | DropdownMenu,
12 | DropdownMenuContent,
13 | DropdownMenuItem,
14 | DropdownMenuSeparator,
15 | DropdownMenuTrigger,
16 | } from '@/components/ui/dropdown-menu'
17 |
18 | interface DataTableColumnHeaderProps
19 | extends React.HTMLAttributes {
20 | column: Column
21 | title: string
22 | }
23 |
24 | export function DataTableColumnHeader({
25 | column,
26 | title,
27 | className,
28 | }: DataTableColumnHeaderProps) {
29 | if (!column.getCanSort()) {
30 | return {title}
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 |
51 |
52 |
53 | column.toggleSorting(false)}>
54 |
55 | Asc
56 |
57 | column.toggleSorting(true)}>
58 |
59 | Desc
60 |
61 |
62 | column.toggleVisibility(false)}>
63 |
64 | Hide
65 |
66 |
67 |
68 |
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/data-table-pagination.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Select,
3 | SelectContent,
4 | SelectItem,
5 | SelectTrigger,
6 | SelectValue,
7 | } from '@/components/ui/select'
8 | import {
9 | ArrowLeft,
10 | ArrowRightIcon,
11 | ChevronLeftIcon,
12 | ChevronRightIcon,
13 | } from 'lucide-react'
14 | import { Button } from '@/components/ui/button'
15 | import { type Table } from '@tanstack/react-table'
16 |
17 | interface DataTablePaginationProps {
18 | table: Table
19 | }
20 |
21 | export function DataTablePagination({
22 | table,
23 | }: DataTablePaginationProps) {
24 | return (
25 |
26 |
27 |
Rows per page
28 |
45 |
46 |
47 | Page {table.getState().pagination.pageIndex + 1} of{' '}
48 | {table.getPageCount()}
49 |
50 |
51 |
60 |
69 |
78 |
87 |
88 |
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/data-table-row-actions.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuTrigger,
7 | } from '@/components/ui/dropdown-menu'
8 | import { Ellipsis } from 'lucide-react'
9 | import { Button } from '@/components/ui/button'
10 |
11 | import { toast } from 'sonner'
12 | import { useMutation } from 'convex/react'
13 | import { useRouter } from 'next/navigation'
14 | import { api } from '@/convex/_generated/api'
15 | import { type Id } from '@/convex/_generated/dataModel'
16 | import { DeleteModel } from '@/components/delete-model'
17 | import { ShareChatModel } from '@/components/sharechat-model'
18 |
19 | export function DataTableRowActions({ id }: { id: Id<'notes'> }) {
20 | const router = useRouter()
21 | const removeNote = useMutation(api.notes.removeNote)
22 |
23 | const handelRemoveNote = () => {
24 | const promise = removeNote({
25 | id,
26 | })
27 | toast.promise(promise, {
28 | loading: 'Deleting Note...',
29 | success: 'Note Deleted',
30 | error: ' Failed to delete note.',
31 | })
32 | }
33 |
34 | return (
35 |
36 |
37 |
44 |
45 |
46 |
53 |
54 |
60 |
61 |
62 |
68 |
69 |
70 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/data-table-toolbar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Input } from '@/components/ui/input'
4 | import { type Table } from '@tanstack/react-table'
5 | import { DataTableViewOptions } from './data-table-view-options'
6 |
7 | interface DataTableToolbarProps {
8 | table: Table
9 | }
10 |
11 | export function DataTableToolbar({
12 | table,
13 | }: DataTableToolbarProps) {
14 | return (
15 |
16 |
17 |
21 | table.getColumn('title')?.setFilterValue(event.target.value)
22 | }
23 | className="h-8 w-[250px] lg:w-[350px]"
24 | />
25 |
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/data-table-view-options.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { type Table } from '@tanstack/react-table'
4 | import {
5 | DropdownMenu,
6 | DropdownMenuCheckboxItem,
7 | DropdownMenuContent,
8 | DropdownMenuLabel,
9 | DropdownMenuSeparator,
10 | DropdownMenuTrigger,
11 | } from '@/components/ui/dropdown-menu'
12 | import { Button } from '@/components/ui/button'
13 | import { MoreHorizontalIcon } from 'lucide-react'
14 |
15 | interface DataTableViewOptionsProps {
16 | table: Table
17 | }
18 |
19 | export function DataTableViewOptions({
20 | table,
21 | }: DataTableViewOptionsProps) {
22 | return (
23 |
24 |
25 |
33 |
34 |
35 | Toggle columns
36 |
37 | {table
38 | .getAllColumns()
39 | .filter(
40 | (column) =>
41 | typeof column.accessorFn !== 'undefined' && column.getCanHide()
42 | )
43 | .map((column) => {
44 | return (
45 | column.toggleVisibility(!!value)}
50 | >
51 | {column.id}
52 |
53 | )
54 | })}
55 |
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/data-table.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import {
5 | type ColumnDef,
6 | type ColumnFiltersState,
7 | type SortingState,
8 | type VisibilityState,
9 | flexRender,
10 | getCoreRowModel,
11 | getFacetedRowModel,
12 | getFacetedUniqueValues,
13 | getFilteredRowModel,
14 | getPaginationRowModel,
15 | getSortedRowModel,
16 | useReactTable,
17 | } from '@tanstack/react-table'
18 |
19 | import {
20 | Table,
21 | TableBody,
22 | TableCell,
23 | TableHead,
24 | TableHeader,
25 | TableRow,
26 | } from '@/components/ui/table'
27 |
28 | import { DataTablePagination } from './data-table-pagination'
29 | import { DataTableToolbar } from './data-table-toolbar'
30 |
31 | interface DataTableProps {
32 | columns: ColumnDef[]
33 | data: TData[]
34 | }
35 |
36 | export function DataTable({
37 | columns,
38 | data,
39 | }: DataTableProps) {
40 | const [rowSelection, setRowSelection] = React.useState({})
41 | const [columnVisibility, setColumnVisibility] =
42 | React.useState({})
43 | const [columnFilters, setColumnFilters] = React.useState(
44 | []
45 | )
46 | const [sorting, setSorting] = React.useState([])
47 |
48 | const table = useReactTable({
49 | data,
50 | columns,
51 | state: {
52 | sorting,
53 | columnVisibility,
54 | rowSelection,
55 | columnFilters,
56 | },
57 | enableRowSelection: true,
58 | onRowSelectionChange: setRowSelection,
59 | onSortingChange: setSorting,
60 | onColumnFiltersChange: setColumnFilters,
61 | onColumnVisibilityChange: setColumnVisibility,
62 | getCoreRowModel: getCoreRowModel(),
63 | getFilteredRowModel: getFilteredRowModel(),
64 | getPaginationRowModel: getPaginationRowModel(),
65 | getSortedRowModel: getSortedRowModel(),
66 | getFacetedRowModel: getFacetedRowModel(),
67 | getFacetedUniqueValues: getFacetedUniqueValues(),
68 | })
69 |
70 | return (
71 |
72 |
73 |
74 |
75 |
76 | {table.getHeaderGroups().map((headerGroup) => (
77 |
78 | {headerGroup.headers.map((header) => {
79 | return (
80 |
81 | {header.isPlaceholder
82 | ? null
83 | : flexRender(
84 | header.column.columnDef.header,
85 | header.getContext()
86 | )}
87 |
88 | )
89 | })}
90 |
91 | ))}
92 |
93 |
94 | {table.getRowModel().rows?.length ? (
95 | table.getRowModel().rows.map((row) => (
96 |
100 | {row.getVisibleCells().map((cell) => (
101 |
102 | {flexRender(
103 | cell.column.columnDef.cell,
104 | cell.getContext()
105 | )}
106 |
107 | ))}
108 |
109 | ))
110 | ) : (
111 |
112 |
116 | No results.
117 |
118 |
119 | )}
120 |
121 |
122 |
123 |
124 |
125 | )
126 | }
127 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/_components/notes-wrapper.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from 'next/link'
4 | import { columns } from './columns'
5 | import { DataTable } from './data-table'
6 | import { type api } from '@/convex/_generated/api'
7 | import { Mic, PencilLine } from 'lucide-react'
8 | import { buttonVariants } from '@/components/ui/button'
9 | import { type Preloaded, usePreloadedQuery } from 'convex/react'
10 |
11 | export function NotesWrapper(props: {
12 | preloadedNotes: Preloaded
13 | }) {
14 | const userNotes = usePreloadedQuery(props.preloadedNotes)
15 |
16 | return (
17 | <>
18 |
19 |
Welcome back!
20 |
21 | Here's a list of all your notes!
22 |
23 |
24 | {userNotes.length === 0 ? (
25 |
26 | ) : (
27 |
28 | )}
29 | >
30 | )
31 | }
32 |
33 | function EmptyState() {
34 | return (
35 |
36 |
37 |
41 |
Record a New Voice Note
42 |
43 |
44 |
48 |
View Action Items
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/app/(dashboard)/recordings/page.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthToken } from '@/lib/utils'
2 | import { preloadQuery } from 'convex/nextjs'
3 | import { api } from '@/convex/_generated/api'
4 | import { NotesWrapper } from './_components/notes-wrapper'
5 |
6 | export default async function RecordingPage() {
7 | const token = await getAuthToken()
8 | const preloadedNotes = await preloadQuery(
9 | api.notes.getUserNotes,
10 | {},
11 | { token }
12 | )
13 |
14 | return
15 | }
16 |
--------------------------------------------------------------------------------
/app/(marketing)/_component/bento-grid.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 |
3 | export const BentoGrid = ({
4 | className,
5 | children,
6 | }: {
7 | className?: string
8 | children?: React.ReactNode
9 | }) => {
10 | return (
11 |
17 | {children}
18 |
19 | )
20 | }
21 |
22 | export const BentoGridItem = ({
23 | className,
24 | title,
25 | description,
26 | header,
27 | icon,
28 | }: {
29 | className?: string
30 | title?: string | React.ReactNode
31 | description?: string | React.ReactNode
32 | header?: React.ReactNode
33 | icon?: React.ReactNode
34 | }) => {
35 | return (
36 |
42 | {header}
43 |
44 | {icon}
45 |
46 | {title}
47 |
48 |
49 | {description}
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/app/(marketing)/_component/bentogrid.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React from 'react'
4 | import { cn } from '@/lib/utils'
5 | import { motion } from 'framer-motion'
6 | import { BentoGrid, BentoGridItem } from './bento-grid'
7 | import {
8 | Box,
9 | CheckCircle,
10 | File,
11 | Frown,
12 | Goal,
13 | Lightbulb,
14 | Mic,
15 | User,
16 | XCircle,
17 | } from 'lucide-react'
18 |
19 | export function BentoGridTemplate() {
20 | return (
21 |
22 | {items.map((item, i) => (
23 | p:text-lg]', item.className)}
29 | icon={item.icon}
30 | />
31 | ))}
32 |
33 | )
34 | }
35 |
36 | const SkeletonOne = () => {
37 | const variants = {
38 | initial: {
39 | x: 0,
40 | },
41 | animate: {
42 | x: 10,
43 | rotate: 5,
44 | transition: {
45 | duration: 0.2,
46 | },
47 | },
48 | }
49 | const variantsSecond = {
50 | initial: {
51 | x: 0,
52 | },
53 | animate: {
54 | x: -10,
55 | rotate: -5,
56 | transition: {
57 | duration: 0.2,
58 | },
59 | },
60 | }
61 |
62 | return (
63 |
68 |
72 |
73 |
74 |
75 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
89 |
90 | )
91 | }
92 | const SkeletonTwo = () => {
93 | const variants = {
94 | initial: {
95 | width: 0,
96 | },
97 | animate: {
98 | width: '100%',
99 | transition: {
100 | duration: 0.2,
101 | },
102 | },
103 | hover: {
104 | width: ['0%', '100%'],
105 | transition: {
106 | duration: 2,
107 | },
108 | },
109 | }
110 | const arr = new Array(6).fill(0)
111 | return (
112 |
118 | {arr.map((_, i) => (
119 |
127 | ))}
128 |
129 | )
130 | }
131 | const SkeletonThree = () => {
132 | const variants = {
133 | initial: {
134 | backgroundPosition: '0 50%',
135 | },
136 | animate: {
137 | backgroundPosition: ['0, 50%', '100% 50%', '0 50%'],
138 | },
139 | }
140 | return (
141 |
157 |
158 |
159 | )
160 | }
161 | const SkeletonFour = () => {
162 | const first = {
163 | initial: {
164 | x: 20,
165 | rotate: -5,
166 | },
167 | hover: {
168 | x: 0,
169 | rotate: 0,
170 | },
171 | }
172 | const second = {
173 | initial: {
174 | x: -20,
175 | rotate: 5,
176 | },
177 | hover: {
178 | x: 0,
179 | rotate: 0,
180 | },
181 | }
182 | return (
183 |
189 |
193 |
194 |
195 | You are not going to able to organize notes easily ?
196 |
197 |
198 | Take Action
199 |
200 |
201 |
202 |
203 |
204 | On your way to meet your goal
205 |
206 |
207 | On Track
208 |
209 |
210 |
214 |
215 |
216 | Your going to miss your goal this month
217 |
218 |
219 | Helpless
220 |
221 |
222 |
223 | )
224 | }
225 | const SkeletonFive = () => {
226 | const variants = {
227 | initial: {
228 | x: 0,
229 | },
230 | animate: {
231 | x: 10,
232 | rotate: 5,
233 | transition: {
234 | duration: 0.2,
235 | },
236 | },
237 | }
238 | const variantsSecond = {
239 | initial: {
240 | x: 0,
241 | },
242 | animate: {
243 | x: -10,
244 | rotate: -5,
245 | transition: {
246 | duration: 0.2,
247 | },
248 | },
249 | }
250 |
251 | return (
252 |
257 |
261 |
262 |
263 | How can i manage my notes or daily notes efficiently ?
264 |
265 |
266 |
270 | Use NotesGPT.
271 |
272 |
273 |
274 | )
275 | }
276 | const items = [
277 | {
278 | title: 'Voice-to-Notes Transformation',
279 | description: (
280 |
281 | Convert your voice memos into actionable insights effortlessly.
282 |
283 | ),
284 | header: ,
285 | className: 'md:col-span-1',
286 | icon: ,
287 | },
288 | {
289 | title: 'Streamlined Organization',
290 | description: (
291 |
292 | Effortlessly summarize and organize your voice notes into clear action
293 | items.
294 |
295 | ),
296 | header: ,
297 | className: 'md:col-span-1',
298 | icon: ,
299 | },
300 | {
301 | title: 'Instant Note Generation',
302 | description: (
303 |
304 | Say it, summarize it, do it. Let NotesGPT handle the rest.
305 |
306 | ),
307 | header: ,
308 | className: 'md:col-span-1',
309 | icon: ,
310 | },
311 | {
312 | title: 'Add task Setting and Tracking',
313 | description: (
314 |
315 | Add task and track them achieve them faster. Let NotesGPT help you stay
316 | on track.
317 |
318 | ),
319 | header: ,
320 | className: 'md:col-span-2',
321 | icon: ,
322 | },
323 |
324 | {
325 | title: 'Text Summarization',
326 | description: (
327 | Get summarize notes with AI technology.
328 | ),
329 | header: ,
330 | className: 'md:col-span-1',
331 | icon: ,
332 | },
333 | ]
334 |
--------------------------------------------------------------------------------
/app/(marketing)/_component/hero-highlight-section.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { motion } from 'framer-motion'
4 | import { HeroHighlight, Highlight } from './hero-highlight'
5 |
6 | export function HeroHighlightSection() {
7 | return (
8 |
9 |
24 | With NotesGPT, you can easliy organize your notes or task for every{' '}
25 |
26 | day, week, month
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/app/(marketing)/_component/hero-highlight.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React from 'react'
4 | import { cn } from '@/lib/utils'
5 | import { useMotionValue, motion, useMotionTemplate } from 'framer-motion'
6 |
7 | export const HeroHighlight = ({
8 | children,
9 | className,
10 | containerClassName,
11 | }: {
12 | children: React.ReactNode
13 | className?: string
14 | containerClassName?: string
15 | }) => {
16 | const mouseX = useMotionValue(0)
17 | const mouseY = useMotionValue(0)
18 |
19 | function handleMouseMove({
20 | currentTarget,
21 | clientX,
22 | clientY,
23 | }: React.MouseEvent) {
24 | if (!currentTarget) return
25 | const { left, top } = currentTarget.getBoundingClientRect()
26 |
27 | mouseX.set(clientX - left)
28 | mouseY.set(clientY - top)
29 | }
30 | return (
31 |
38 |
39 |
58 |
59 |
{children}
60 |
61 | )
62 | }
63 |
64 | export const Highlight = ({
65 | children,
66 | className,
67 | }: {
68 | children: React.ReactNode
69 | className?: string
70 | }) => {
71 | return (
72 |
94 | {children}
95 |
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/app/(marketing)/_component/marketing-bentogrid.tsx:
--------------------------------------------------------------------------------
1 | import { Balancer } from 'react-wrap-balancer'
2 | import { BentoGridTemplate } from './bentogrid'
3 |
4 | export default function MarketingBentoGrid() {
5 | return (
6 |
7 |
8 |
9 |
13 | The new
14 |
15 |
16 |
20 | Golden Standard
21 |
22 |
23 |
27 |
28 | NotesGPT seamlessly converts your voice notes into organized
29 | summaries and clear action items using AI.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/app/(marketing)/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import Image from 'next/image'
3 | import { cn } from '@/lib/utils'
4 | import { Twitter } from 'lucide-react'
5 | import Balancer from 'react-wrap-balancer'
6 | import { Button, buttonVariants } from '@/components/ui/button'
7 | import MarketingBentoGrid from './_component/marketing-bentogrid'
8 | import { HeroHighlightSection } from './_component/hero-highlight-section'
9 |
10 | export default function MarketingPage() {
11 | return (
12 |
13 |
22 | Introducing on
23 |
24 |
28 |
29 | AI-Powered{' '}
30 |
31 | Voice Notes{' '}
32 |
33 | Management
34 |
35 |
36 |
40 |
41 | NotesGPT seamlessly converts your voice notes into organized summaries
42 | and clear action items using AI.
43 |
44 |
45 |
53 |
57 |
58 |
68 |
69 |
70 |
80 |
81 |
82 |
83 |
84 |
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/app/(profile)/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function UserProfileLayout({
2 | children,
3 | }: {
4 | children: React.ReactNode
5 | }) {
6 | return (
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/app/(profile)/user-profile/[[...user-profile]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { UserProfile } from '@clerk/nextjs'
2 |
3 | const UserProfilePage = () =>
4 |
5 | export default UserProfilePage
6 |
--------------------------------------------------------------------------------
/app/(public)/share/[noteId]/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useQuery } from 'convex/react'
4 | import { useParams } from 'next/navigation'
5 | import { api } from '@/convex/_generated/api'
6 | import { type Doc, type Id } from '@/convex/_generated/dataModel'
7 |
8 | import { ActionItemSkelton, EmptyState } from '@/components/skeltons'
9 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
10 | import NoteCard from '@/app/(dashboard)/recordings/[recordingId]/_components/note-card'
11 |
12 | interface NoteWithActionItem {
13 | note: Doc<'notes'>
14 | actionItems: Doc<'actionItems'>[]
15 | }
16 |
17 | export default function SharePage() {
18 | const params = useParams()
19 | const { noteId } = params
20 |
21 | const noteWithActionItems = useQuery(api.notes.getNoteById, {
22 | id: noteId as Id<'notes'>,
23 | })
24 |
25 | if (noteWithActionItems === undefined) {
26 | return
27 | }
28 |
29 | const { note, actionItems }: NoteWithActionItem = noteWithActionItems
30 | return (
31 |
32 |
33 | Transcript
34 | Summary
35 | Action Items
36 |
37 | {note?.transcription}
38 | {note?.summary}
39 |
40 | {actionItems.length > 0 ? (
41 | actionItems.map((item) => (
42 |
43 | ))
44 | ) : (
45 |
46 | )}
47 |
48 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/app/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Button } from '@/components/ui/button'
4 |
5 | export default function Error({
6 | reset,
7 | }: {
8 | error: Error & { digest?: string }
9 | reset: () => void
10 | }) {
11 | return (
12 |
13 |
Something went wrong!
14 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .recording-box {
6 | background: linear-gradient(
7 | 143deg,
8 | #e648f4 8.37%,
9 | rgba(73, 80, 240, 0.62) 71.62%
10 | );
11 | filter: blur(13.100000381469727px);
12 | }
13 |
14 | .record-animation {
15 | animation: rotate360 infinite 5s linear;
16 | }
17 |
18 | @keyframes rotate360 {
19 | 0% {
20 | transform: rotate(0deg);
21 | }
22 |
23 | 100% {
24 | transform: rotate(360deg);
25 | }
26 | }
27 |
28 | @layer base {
29 | :root {
30 | --background: 0 0% 100%;
31 | --foreground: 240 10% 3.9%;
32 |
33 | --card: 0 0% 100%;
34 | --card-foreground: 240 10% 3.9%;
35 |
36 | --popover: 0 0% 100%;
37 | --popover-foreground: 240 10% 3.9%;
38 |
39 | --primary: 240 5.9% 10%;
40 | --primary-foreground: 0 0% 98%;
41 |
42 | --secondary: 240 4.8% 95.9%;
43 | --secondary-foreground: 240 5.9% 10%;
44 |
45 | --muted: 240 4.8% 95.9%;
46 | --muted-foreground: 240 3.8% 46.1%;
47 |
48 | --accent: 240 4.8% 95.9%;
49 | --accent-foreground: 240 5.9% 10%;
50 |
51 | --destructive: 0 84.2% 60.2%;
52 | --destructive-foreground: 0 0% 98%;
53 |
54 | --border: 240 5.9% 90%;
55 | --input: 240 5.9% 90%;
56 | --ring: 240 10% 3.9%;
57 |
58 | --radius: 0.5rem;
59 | }
60 |
61 | .dark {
62 | --background: 240 10% 3.9%;
63 | --foreground: 0 0% 98%;
64 |
65 | --card: 240 10% 3.9%;
66 | --card-foreground: 0 0% 98%;
67 |
68 | --popover: 240 10% 3.9%;
69 | --popover-foreground: 0 0% 98%;
70 |
71 | --primary: 0 0% 98%;
72 | --primary-foreground: 240 5.9% 10%;
73 |
74 | --secondary: 240 3.7% 15.9%;
75 | --secondary-foreground: 0 0% 98%;
76 |
77 | --muted: 240 3.7% 15.9%;
78 | --muted-foreground: 240 5% 64.9%;
79 |
80 | --accent: 240 3.7% 15.9%;
81 | --accent-foreground: 0 0% 98%;
82 |
83 | --destructive: 0 62.8% 30.6%;
84 | --destructive-foreground: 0 0% 98%;
85 |
86 | --border: 240 3.7% 15.9%;
87 | --input: 240 3.7% 15.9%;
88 | --ring: 240 4.9% 83.9%;
89 | }
90 | }
91 |
92 | @layer base {
93 | * {
94 | @apply border-border;
95 | }
96 | body {
97 | @apply bg-background text-foreground;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css'
2 | import { Toaster } from 'sonner'
3 | import { type Metadata } from 'next'
4 | import { Poppins } from 'next/font/google'
5 | import { Analytics } from '@vercel/analytics/react'
6 | import { ThemeProvider } from '@/components/theme-provider'
7 | import { ConvexProvider } from '@/components/providers/convex-provider'
8 |
9 | import Navbar from '@/components/navbar'
10 | import Footer from '@/components/footer'
11 |
12 | const font = Poppins({ subsets: ['latin'], weight: ['500'] })
13 |
14 | export const metadata: Metadata = {
15 | title: {
16 | default: 'NotesGPT',
17 | template: `%s - "NotesGPT`,
18 | },
19 | metadataBase: new URL('https://notessgpt.vercel.app'),
20 | description:
21 | 'NotesGPT seamlessly converts your voice notes into organized summaries and clear action items using AI.',
22 | keywords: [
23 | 'Voice Note Conversion',
24 | 'AI Note Making',
25 | 'Automated Summaries',
26 | 'Task Management',
27 | 'Project Management',
28 | 'Voice to Text',
29 | ],
30 | authors: [
31 | {
32 | name: 'abdtriedcoding',
33 | url: 'https://abdullahsidd.vercel.app',
34 | },
35 | ],
36 | creator: 'abdtriedcoding',
37 | openGraph: {
38 | type: 'website',
39 | locale: 'en_US',
40 | url: 'https://notessgpt.vercel.app',
41 | title: 'NotesGPT',
42 | description:
43 | 'NotesGPT seamlessly converts your voice notes into organized summaries and clear action items using AI.',
44 | siteName: 'NotesGPT',
45 | images: [
46 | {
47 | url: '/thumbnail.png',
48 | width: 1200,
49 | height: 630,
50 | alt: 'NotesGPT',
51 | },
52 | ],
53 | },
54 | twitter: {
55 | card: 'summary_large_image',
56 | title: 'NotesGPT',
57 | description:
58 | 'NotesGPT seamlessly converts your voice notes into organized summaries and clear action items using AI.',
59 | images: ['/thumbnail.png'],
60 | creator: '@abdtriedcoding',
61 | },
62 | icons: {
63 | icon: '/favicon.ico',
64 | },
65 | }
66 |
67 | export default function RootLayout({
68 | children,
69 | }: Readonly<{
70 | children: React.ReactNode
71 | }>) {
72 | return (
73 |
74 |
75 |
76 |
82 |
83 |
84 |
85 | {children}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | )
94 | }
95 |
--------------------------------------------------------------------------------
/app/loading.tsx:
--------------------------------------------------------------------------------
1 | import { Loader } from 'lucide-react'
2 |
3 | export default function Loading() {
4 | // You can add any UI inside Loading, including a Skeleton.
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { buttonVariants } from '@/components/ui/button'
3 |
4 | export default function NotFound() {
5 | return (
6 |
7 |
404
8 |
9 | Go back home
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdtriedcoding/notesGPT/3cbba2f0021bd7317ed0631649da31e02ee66784/bun.lockb
--------------------------------------------------------------------------------
/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": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/delete-model.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | AlertDialog,
3 | AlertDialogAction,
4 | AlertDialogCancel,
5 | AlertDialogContent,
6 | AlertDialogDescription,
7 | AlertDialogFooter,
8 | AlertDialogHeader,
9 | AlertDialogTitle,
10 | AlertDialogTrigger,
11 | } from '@/components/ui/alert-dialog'
12 |
13 | interface DeleteModelProp {
14 | children: React.ReactNode
15 | onConfirm: () => void
16 | }
17 |
18 | export function DeleteModel({ children, onConfirm }: DeleteModelProp) {
19 | return (
20 |
21 | {children}
22 |
23 |
24 | Are you absolutely sure?
25 |
26 | This action cannot be undone.
27 |
28 |
29 |
30 | Cancel
31 | Delete
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import { StickyNote } from 'lucide-react'
2 |
3 | export default function Footer() {
4 | return (
5 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from 'next/link'
4 | import UserNav from './user-nav'
5 | import { navItems } from '@/constants'
6 | import { usePathname } from 'next/navigation'
7 | import { Button } from '@/components/ui/button'
8 | import { Loader, StickyNote } from 'lucide-react'
9 | import { ThemeToggle } from '@/components/theme-toggle'
10 | import { ClerkLoaded, ClerkLoading, SignInButton, useUser } from '@clerk/nextjs'
11 |
12 | export default function Navbar() {
13 | const pathname = usePathname()
14 | const { user } = useUser()
15 |
16 | return (
17 | <>
18 | {!pathname.startsWith('/share') && (
19 |
50 | )}
51 | >
52 | )
53 | }
54 |
55 | function Logo() {
56 | return (
57 |
58 |
59 | NotesGPT
60 |
61 | )
62 | }
63 |
64 | function NavItem({ title, href }: { title: string; href: string }) {
65 | return (
66 |
70 | {title}
71 |
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/components/providers/convex-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { type ReactNode } from 'react'
4 | import { ConvexReactClient } from 'convex/react'
5 | import { ClerkProvider, useAuth } from '@clerk/nextjs'
6 | import { ConvexProviderWithClerk } from 'convex/react-clerk'
7 |
8 | const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!)
9 |
10 | export const ConvexProvider = ({ children }: { children: ReactNode }) => {
11 | return (
12 |
15 |
16 | {children}
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/components/sharechat-model.tsx:
--------------------------------------------------------------------------------
1 | import { toast } from 'sonner'
2 | import { useState } from 'react'
3 | import { Check, Copy } from 'lucide-react'
4 | import { Input } from '@/components/ui/input'
5 | import { useOrigin } from '@/hooks/use-origin'
6 | import { Button } from '@/components/ui/button'
7 | import {
8 | Dialog,
9 | DialogContent,
10 | DialogHeader,
11 | DialogTitle,
12 | DialogTrigger,
13 | } from '@/components/ui/dialog'
14 |
15 | interface ShareChatModelProp {
16 | children: React.ReactNode
17 | id: string
18 | }
19 |
20 | export function ShareChatModel({ children, id }: ShareChatModelProp) {
21 | const origin = useOrigin()
22 | const [copied, setCopied] = useState(false)
23 |
24 | const url = `${origin}/share/${id}`
25 |
26 | const onCopy = async () => {
27 | await navigator.clipboard.writeText(url)
28 | setCopied(true)
29 | toast.success('Url Copied')
30 |
31 | setTimeout(() => {
32 | setCopied(false)
33 | }, 1000)
34 | }
35 |
36 | return (
37 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/components/skeltons.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { Button } from './ui/button'
3 | import { Skeleton } from '@/components/ui/skeleton'
4 |
5 | export function ActionItemSkelton() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export function EmptyState() {
22 | return (
23 |
24 |
You currently have no action items.
25 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { ThemeProvider as NextThemesProvider } from 'next-themes'
5 | import { type ThemeProviderProps } from 'next-themes/dist/types'
6 | import { Provider as BalancerProvider } from 'react-wrap-balancer'
7 |
8 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
9 | return (
10 |
11 | {children}
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/components/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { Laptop, Moon, MoonIcon, Sun, SunIcon } from 'lucide-react'
5 | import { useTheme } from 'next-themes'
6 |
7 | import { Button } from '@/components/ui/button'
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuTrigger,
13 | } from '@/components/ui/dropdown-menu'
14 |
15 | export function ThemeToggle() {
16 | const { setTheme } = useTheme()
17 |
18 | return (
19 |
20 |
21 |
26 |
27 |
28 | setTheme('light')}>
29 |
30 | Light
31 |
32 | setTheme('dark')}>
33 |
34 | Dark
35 |
36 | setTheme('system')}>
37 |
38 | System
39 |
40 |
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { buttonVariants } from "@/components/ui/button"
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ))
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47 |
48 | const AlertDialogHeader = ({
49 | className,
50 | ...props
51 | }: React.HTMLAttributes) => (
52 |
59 | )
60 | AlertDialogHeader.displayName = "AlertDialogHeader"
61 |
62 | const AlertDialogFooter = ({
63 | className,
64 | ...props
65 | }: React.HTMLAttributes) => (
66 |
73 | )
74 | AlertDialogFooter.displayName = "AlertDialogFooter"
75 |
76 | const AlertDialogTitle = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef
79 | >(({ className, ...props }, ref) => (
80 |
85 | ))
86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87 |
88 | const AlertDialogDescription = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
97 | ))
98 | AlertDialogDescription.displayName =
99 | AlertDialogPrimitive.Description.displayName
100 |
101 | const AlertDialogAction = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112 |
113 | const AlertDialogCancel = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
126 | ))
127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128 |
129 | export {
130 | AlertDialog,
131 | AlertDialogPortal,
132 | AlertDialogOverlay,
133 | AlertDialogTrigger,
134 | AlertDialogContent,
135 | AlertDialogHeader,
136 | AlertDialogFooter,
137 | AlertDialogTitle,
138 | AlertDialogDescription,
139 | AlertDialogAction,
140 | AlertDialogCancel,
141 | }
142 |
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ));
21 | Avatar.displayName = AvatarPrimitive.Root.displayName;
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ));
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ));
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
49 |
50 | export { Avatar, AvatarImage, AvatarFallback };
51 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-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",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ));
45 | CardTitle.displayName = "CardTitle";
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ));
57 | CardDescription.displayName = "CardDescription";
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ));
65 | CardContent.displayName = "CardContent";
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ));
77 | CardFooter.displayName = "CardFooter";
78 |
79 | export {
80 | Card,
81 | CardHeader,
82 | CardFooter,
83 | CardTitle,
84 | CardDescription,
85 | CardContent,
86 | };
87 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { type DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { Search } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 | import { Dialog, DialogContent } from "@/components/ui/dialog"
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | type CommandDialogProps = DialogProps
27 |
28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
29 | return (
30 |
37 | )
38 | }
39 |
40 | const CommandInput = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
45 |
46 |
54 |
55 | ))
56 |
57 | CommandInput.displayName = CommandPrimitive.Input.displayName
58 |
59 | const CommandList = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, ...props }, ref) => (
63 |
68 | ))
69 |
70 | CommandList.displayName = CommandPrimitive.List.displayName
71 |
72 | const CommandEmpty = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >((props, ref) => (
76 |
81 | ))
82 |
83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
84 |
85 | const CommandGroup = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >(({ className, ...props }, ref) => (
89 |
97 | ))
98 |
99 | CommandGroup.displayName = CommandPrimitive.Group.displayName
100 |
101 | const CommandSeparator = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
112 |
113 | const CommandItem = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
125 | ))
126 |
127 | CommandItem.displayName = CommandPrimitive.Item.displayName
128 |
129 | const CommandShortcut = ({
130 | className,
131 | ...props
132 | }: React.HTMLAttributes) => {
133 | return (
134 |
141 | )
142 | }
143 | CommandShortcut.displayName = "CommandShortcut"
144 |
145 | export {
146 | Command,
147 | CommandDialog,
148 | CommandInput,
149 | CommandList,
150 | CommandEmpty,
151 | CommandGroup,
152 | CommandItem,
153 | CommandShortcut,
154 | CommandSeparator,
155 | }
156 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogClose,
116 | DialogTrigger,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown, ChevronUp } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ))
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ))
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = "popper", ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ))
100 | SelectContent.displayName = SelectPrimitive.Content.displayName
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ))
135 | SelectItem.displayName = SelectPrimitive.Item.displayName
136 |
137 | const SelectSeparator = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef
140 | >(({ className, ...props }, ref) => (
141 |
146 | ))
147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148 |
149 | export {
150 | Select,
151 | SelectGroup,
152 | SelectValue,
153 | SelectTrigger,
154 | SelectContent,
155 | SelectLabel,
156 | SelectItem,
157 | SelectSeparator,
158 | SelectScrollUpButton,
159 | SelectScrollDownButton,
160 | }
161 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = "Table"
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = "TableHeader"
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = "TableBody"
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0",
47 | className
48 | )}
49 | {...props}
50 | />
51 | ))
52 | TableFooter.displayName = "TableFooter"
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ))
67 | TableRow.displayName = "TableRow"
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ))
82 | TableHead.displayName = "TableHead"
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.TdHTMLAttributes
87 | >(({ className, ...props }, ref) => (
88 | |
93 | ))
94 | TableCell.displayName = "TableCell"
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = "TableCaption"
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/components/user-nav.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuSeparator,
8 | DropdownMenuTrigger,
9 | } from '@/components/ui/dropdown-menu'
10 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
11 | import { Loader, LogOut, Mic, PencilLine, User, VideoIcon } from 'lucide-react'
12 |
13 | import { useUser } from '@clerk/nextjs'
14 | import { useRouter } from 'next/navigation'
15 | import { useClerk } from '@clerk/clerk-react'
16 |
17 | export default function UserNav() {
18 | const router = useRouter()
19 | const { isLoaded, isSignedIn, user } = useUser()
20 | const { signOut } = useClerk()
21 |
22 | if (!isLoaded || !isSignedIn) {
23 | return
24 | }
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | CN
32 |
33 |
34 |
35 | router.push('/user-profile')}>
36 |
37 | Profile
38 |
39 | router.push('/recordings')}
42 | >
43 |
44 | Recordings
45 |
46 | router.push('/record')}
49 | >
50 |
51 | Record
52 |
53 | router.push('/action-items')}
56 | >
57 |
58 | Action Items
59 |
60 |
61 | signOut(() => router.push('/'))}>
62 |
63 | Log out
64 |
65 |
66 |
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/constants/index.ts:
--------------------------------------------------------------------------------
1 | export const navItems = [
2 | {
3 | title: 'Recordings',
4 | href: '/recordings',
5 | },
6 | {
7 | title: 'Record',
8 | href: '/record',
9 | },
10 | {
11 | title: 'Action Items',
12 | href: '/action-items',
13 | },
14 | ]
15 |
--------------------------------------------------------------------------------
/convex/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your Convex functions directory!
2 |
3 | Write your Convex functions here.
4 | See https://docs.convex.dev/functions for more.
5 |
6 | A query function that takes two arguments looks like:
7 |
8 | ```ts
9 | // functions.js
10 | import { query } from "./_generated/server";
11 | import { v } from "convex/values";
12 |
13 | export const myQueryFunction = query({
14 | // Validators for arguments.
15 | args: {
16 | first: v.number(),
17 | second: v.string(),
18 | },
19 |
20 | // Function implementation.
21 | handler: async (ctx, args) => {
22 | // Read the database as many times as you need here.
23 | // See https://docs.convex.dev/database/reading-data.
24 | const documents = await ctx.db.query("tablename").collect();
25 |
26 | // Arguments passed from the client are properties of the args object.
27 | console.log(args.first, args.second);
28 |
29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data,
30 | // remove non-public properties, or create new objects.
31 | return documents;
32 | },
33 | });
34 | ```
35 |
36 | Using this query function in a React component looks like:
37 |
38 | ```ts
39 | const data = useQuery(api.functions.myQueryFunction, {
40 | first: 10,
41 | second: "hello",
42 | });
43 | ```
44 |
45 | A mutation function looks like:
46 |
47 | ```ts
48 | // functions.js
49 | import { mutation } from "./_generated/server";
50 | import { v } from "convex/values";
51 |
52 | export const myMutationFunction = mutation({
53 | // Validators for arguments.
54 | args: {
55 | first: v.string(),
56 | second: v.string(),
57 | },
58 |
59 | // Function implementation.
60 | handler: async (ctx, args) => {
61 | // Insert or modify documents in the database here.
62 | // Mutations can also read from the database like queries.
63 | // See https://docs.convex.dev/database/writing-data.
64 | const message = { body: args.first, author: args.second };
65 | const id = await ctx.db.insert("messages", message);
66 |
67 | // Optionally, return a value from your mutation.
68 | return await ctx.db.get(id);
69 | },
70 | });
71 | ```
72 |
73 | Using this mutation function in a React component looks like:
74 |
75 | ```ts
76 | const mutation = useMutation(api.functions.myMutationFunction);
77 | function handleButtonPress() {
78 | // fire and forget, the most common way to use mutations
79 | mutation({ first: "Hello!", second: "me" });
80 | // OR
81 | // use the result once the mutation has completed
82 | mutation({ first: "Hello!", second: "me" }).then((result) =>
83 | console.log(result),
84 | );
85 | }
86 | ```
87 |
88 | Use the Convex CLI to push your functions to a deployment. See everything
89 | the Convex CLI can do by running `npx convex -h` in your project root
90 | directory. To learn more, launch the docs with `npx convex docs`.
91 |
--------------------------------------------------------------------------------
/convex/_generated/api.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated `api` utility.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * Generated by convex@1.11.2.
8 | * To regenerate, run `npx convex dev`.
9 | * @module
10 | */
11 |
12 | import type {
13 | ApiFromModules,
14 | FilterApi,
15 | FunctionReference,
16 | } from "convex/server";
17 | import type * as assembly from "../assembly.js";
18 | import type * as gemini from "../gemini.js";
19 | import type * as notes from "../notes.js";
20 |
21 | /**
22 | * A utility for referencing Convex functions in your app's API.
23 | *
24 | * Usage:
25 | * ```js
26 | * const myFunctionReference = api.myModule.myFunction;
27 | * ```
28 | */
29 | declare const fullApi: ApiFromModules<{
30 | assembly: typeof assembly;
31 | gemini: typeof gemini;
32 | notes: typeof notes;
33 | }>;
34 | export declare const api: FilterApi<
35 | typeof fullApi,
36 | FunctionReference
37 | >;
38 | export declare const internal: FilterApi<
39 | typeof fullApi,
40 | FunctionReference
41 | >;
42 |
--------------------------------------------------------------------------------
/convex/_generated/api.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated `api` utility.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * Generated by convex@1.11.2.
8 | * To regenerate, run `npx convex dev`.
9 | * @module
10 | */
11 |
12 | import { anyApi } from "convex/server";
13 |
14 | /**
15 | * A utility for referencing Convex functions in your app's API.
16 | *
17 | * Usage:
18 | * ```js
19 | * const myFunctionReference = api.myModule.myFunction;
20 | * ```
21 | */
22 | export const api = anyApi;
23 | export const internal = anyApi;
24 |
--------------------------------------------------------------------------------
/convex/_generated/dataModel.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated data model types.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * Generated by convex@1.11.2.
8 | * To regenerate, run `npx convex dev`.
9 | * @module
10 | */
11 |
12 | import type {
13 | DataModelFromSchemaDefinition,
14 | DocumentByName,
15 | TableNamesInDataModel,
16 | SystemTableNames,
17 | } from "convex/server";
18 | import type { GenericId } from "convex/values";
19 | import schema from "../schema.js";
20 |
21 | /**
22 | * The names of all of your Convex tables.
23 | */
24 | export type TableNames = TableNamesInDataModel;
25 |
26 | /**
27 | * The type of a document stored in Convex.
28 | *
29 | * @typeParam TableName - A string literal type of the table name (like "users").
30 | */
31 | export type Doc = DocumentByName<
32 | DataModel,
33 | TableName
34 | >;
35 |
36 | /**
37 | * An identifier for a document in Convex.
38 | *
39 | * Convex documents are uniquely identified by their `Id`, which is accessible
40 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
41 | *
42 | * Documents can be loaded using `db.get(id)` in query and mutation functions.
43 | *
44 | * IDs are just strings at runtime, but this type can be used to distinguish them from other
45 | * strings when type checking.
46 | *
47 | * @typeParam TableName - A string literal type of the table name (like "users").
48 | */
49 | export type Id =
50 | GenericId;
51 |
52 | /**
53 | * A type describing your Convex data model.
54 | *
55 | * This type includes information about what tables you have, the type of
56 | * documents stored in those tables, and the indexes defined on them.
57 | *
58 | * This type is used to parameterize methods like `queryGeneric` and
59 | * `mutationGeneric` to make them type-safe.
60 | */
61 | export type DataModel = DataModelFromSchemaDefinition;
62 |
--------------------------------------------------------------------------------
/convex/_generated/server.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated utilities for implementing server-side Convex query and mutation functions.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * Generated by convex@1.11.2.
8 | * To regenerate, run `npx convex dev`.
9 | * @module
10 | */
11 |
12 | import {
13 | ActionBuilder,
14 | HttpActionBuilder,
15 | MutationBuilder,
16 | QueryBuilder,
17 | GenericActionCtx,
18 | GenericMutationCtx,
19 | GenericQueryCtx,
20 | GenericDatabaseReader,
21 | GenericDatabaseWriter,
22 | } from "convex/server";
23 | import type { DataModel } from "./dataModel.js";
24 |
25 | /**
26 | * Define a query in this Convex app's public API.
27 | *
28 | * This function will be allowed to read your Convex database and will be accessible from the client.
29 | *
30 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
31 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
32 | */
33 | export declare const query: QueryBuilder;
34 |
35 | /**
36 | * Define a query that is only accessible from other Convex functions (but not from the client).
37 | *
38 | * This function will be allowed to read from your Convex database. It will not be accessible from the client.
39 | *
40 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
41 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
42 | */
43 | export declare const internalQuery: QueryBuilder;
44 |
45 | /**
46 | * Define a mutation in this Convex app's public API.
47 | *
48 | * This function will be allowed to modify your Convex database and will be accessible from the client.
49 | *
50 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
51 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
52 | */
53 | export declare const mutation: MutationBuilder;
54 |
55 | /**
56 | * Define a mutation that is only accessible from other Convex functions (but not from the client).
57 | *
58 | * This function will be allowed to modify your Convex database. It will not be accessible from the client.
59 | *
60 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
61 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
62 | */
63 | export declare const internalMutation: MutationBuilder;
64 |
65 | /**
66 | * Define an action in this Convex app's public API.
67 | *
68 | * An action is a function which can execute any JavaScript code, including non-deterministic
69 | * code and code with side-effects, like calling third-party services.
70 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
71 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
72 | *
73 | * @param func - The action. It receives an {@link ActionCtx} as its first argument.
74 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
75 | */
76 | export declare const action: ActionBuilder;
77 |
78 | /**
79 | * Define an action that is only accessible from other Convex functions (but not from the client).
80 | *
81 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
82 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
83 | */
84 | export declare const internalAction: ActionBuilder;
85 |
86 | /**
87 | * Define an HTTP action.
88 | *
89 | * This function will be used to respond to HTTP requests received by a Convex
90 | * deployment if the requests matches the path and method where this action
91 | * is routed. Be sure to route your action in `convex/http.js`.
92 | *
93 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
94 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
95 | */
96 | export declare const httpAction: HttpActionBuilder;
97 |
98 | /**
99 | * A set of services for use within Convex query functions.
100 | *
101 | * The query context is passed as the first argument to any Convex query
102 | * function run on the server.
103 | *
104 | * This differs from the {@link MutationCtx} because all of the services are
105 | * read-only.
106 | */
107 | export type QueryCtx = GenericQueryCtx;
108 |
109 | /**
110 | * A set of services for use within Convex mutation functions.
111 | *
112 | * The mutation context is passed as the first argument to any Convex mutation
113 | * function run on the server.
114 | */
115 | export type MutationCtx = GenericMutationCtx;
116 |
117 | /**
118 | * A set of services for use within Convex action functions.
119 | *
120 | * The action context is passed as the first argument to any Convex action
121 | * function run on the server.
122 | */
123 | export type ActionCtx = GenericActionCtx;
124 |
125 | /**
126 | * An interface to read from the database within Convex query functions.
127 | *
128 | * The two entry points are {@link DatabaseReader.get}, which fetches a single
129 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts
130 | * building a query.
131 | */
132 | export type DatabaseReader = GenericDatabaseReader;
133 |
134 | /**
135 | * An interface to read from and write to the database within Convex mutation
136 | * functions.
137 | *
138 | * Convex guarantees that all writes within a single mutation are
139 | * executed atomically, so you never have to worry about partial writes leaving
140 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
141 | * for the guarantees Convex provides your functions.
142 | */
143 | export type DatabaseWriter = GenericDatabaseWriter;
144 |
--------------------------------------------------------------------------------
/convex/_generated/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated utilities for implementing server-side Convex query and mutation functions.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * Generated by convex@1.11.2.
8 | * To regenerate, run `npx convex dev`.
9 | * @module
10 | */
11 |
12 | import {
13 | actionGeneric,
14 | httpActionGeneric,
15 | queryGeneric,
16 | mutationGeneric,
17 | internalActionGeneric,
18 | internalMutationGeneric,
19 | internalQueryGeneric,
20 | } from "convex/server";
21 |
22 | /**
23 | * Define a query in this Convex app's public API.
24 | *
25 | * This function will be allowed to read your Convex database and will be accessible from the client.
26 | *
27 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
28 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
29 | */
30 | export const query = queryGeneric;
31 |
32 | /**
33 | * Define a query that is only accessible from other Convex functions (but not from the client).
34 | *
35 | * This function will be allowed to read from your Convex database. It will not be accessible from the client.
36 | *
37 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
38 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
39 | */
40 | export const internalQuery = internalQueryGeneric;
41 |
42 | /**
43 | * Define a mutation in this Convex app's public API.
44 | *
45 | * This function will be allowed to modify your Convex database and will be accessible from the client.
46 | *
47 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
48 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
49 | */
50 | export const mutation = mutationGeneric;
51 |
52 | /**
53 | * Define a mutation that is only accessible from other Convex functions (but not from the client).
54 | *
55 | * This function will be allowed to modify your Convex database. It will not be accessible from the client.
56 | *
57 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
58 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
59 | */
60 | export const internalMutation = internalMutationGeneric;
61 |
62 | /**
63 | * Define an action in this Convex app's public API.
64 | *
65 | * An action is a function which can execute any JavaScript code, including non-deterministic
66 | * code and code with side-effects, like calling third-party services.
67 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
68 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
69 | *
70 | * @param func - The action. It receives an {@link ActionCtx} as its first argument.
71 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
72 | */
73 | export const action = actionGeneric;
74 |
75 | /**
76 | * Define an action that is only accessible from other Convex functions (but not from the client).
77 | *
78 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
79 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
80 | */
81 | export const internalAction = internalActionGeneric;
82 |
83 | /**
84 | * Define a Convex HTTP action.
85 | *
86 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
87 | * as its second.
88 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
89 | */
90 | export const httpAction = httpActionGeneric;
91 |
--------------------------------------------------------------------------------
/convex/assembly.ts:
--------------------------------------------------------------------------------
1 | ;('use node')
2 |
3 | import { v } from 'convex/values'
4 | import { AssemblyAI } from 'assemblyai'
5 | import { internal } from './_generated/api'
6 | import { internalAction, internalMutation } from './_generated/server'
7 |
8 | const client = new AssemblyAI({
9 | apiKey: process.env.ASSEMBLY_API_KEY!,
10 | })
11 |
12 | export const doTranscribe = internalAction({
13 | args: {
14 | fileUrl: v.string(),
15 | noteId: v.id('notes'),
16 | },
17 | handler: async (ctx, args) => {
18 | const { fileUrl, noteId } = args
19 | const data = {
20 | audio_url: fileUrl,
21 | }
22 |
23 | const responce = await client.transcripts.transcribe(data)
24 | const transcript = responce.text || 'error'
25 | await ctx.runMutation(internal.assembly.saveTranscript, {
26 | noteId,
27 | transcript,
28 | })
29 | },
30 | })
31 |
32 | export const saveTranscript = internalMutation({
33 | args: {
34 | noteId: v.id('notes'),
35 | transcript: v.string(),
36 | },
37 | handler: async (ctx, args) => {
38 | const { noteId, transcript } = args
39 |
40 | await ctx.db.patch(noteId, {
41 | transcription: transcript,
42 | })
43 |
44 | await ctx.scheduler.runAfter(0, internal.gemini.chat, {
45 | noteId,
46 | transcript,
47 | })
48 | },
49 | })
50 |
--------------------------------------------------------------------------------
/convex/auth.config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | providers: [
3 | {
4 | domain: 'https://summary-krill-98.clerk.accounts.dev',
5 | applicationID: 'convex',
6 | },
7 | ],
8 | }
9 |
--------------------------------------------------------------------------------
/convex/gemini.ts:
--------------------------------------------------------------------------------
1 | ;('use node')
2 |
3 | import { v } from 'convex/values'
4 | import { internal } from './_generated/api'
5 | import { GoogleGenerativeAI } from '@google/generative-ai'
6 | import { internalAction, internalMutation } from './_generated/server'
7 |
8 | interface TranscriptSummary {
9 | title: string
10 | summary: string
11 | }
12 |
13 | const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!)
14 |
15 | const model = genAI.getGenerativeModel({ model: 'gemini-pro' })
16 |
17 | export const chat = internalAction({
18 | args: {
19 | noteId: v.id('notes'),
20 | transcript: v.string(),
21 | },
22 | handler: async (ctx, args) => {
23 | const { noteId, transcript } = args
24 |
25 | const prompt = `The following is a transcript of a voice message. Extract a title, summary from it and answer in JSON so that i can parse later, This is format: {"title": "string", "summary": "string"} Dont add extra information like starting with "json" just return in this {"title": "string", "summary": "string"} specified format. Use this example as refrence and give me output in exact that responce, const text = '{ "title": "Name and Current Status", "summary": "The speaker introduces themselves with their name and mentions that they\'re currently running, leaving the details about who they\'re running from or for unspecified." }'.
26 | Here is the transcript ${transcript}`
27 |
28 | const result = await model.generateContent(prompt)
29 | const response = result.response
30 | const text = response.text()
31 | const data: TranscriptSummary = JSON.parse(text)
32 | const { summary, title } = data
33 | await ctx.runMutation(internal.gemini.saveSummary, {
34 | noteId,
35 | summary,
36 | title,
37 | })
38 | },
39 | })
40 |
41 | export const saveSummary = internalMutation({
42 | args: {
43 | noteId: v.id('notes'),
44 | summary: v.string(),
45 | title: v.string(),
46 | },
47 | handler: async (ctx, args) => {
48 | const { noteId, summary, title } = args
49 | await ctx.db.patch(noteId, {
50 | summary,
51 | title,
52 | })
53 | },
54 | })
55 |
--------------------------------------------------------------------------------
/convex/notes.ts:
--------------------------------------------------------------------------------
1 | import { v } from 'convex/values'
2 | import { internal } from './_generated/api'
3 | import { mutation, query } from './_generated/server'
4 |
5 | export const generateUploadUrl = mutation(async (ctx) => {
6 | return await ctx.storage.generateUploadUrl()
7 | })
8 |
9 | export const createNote = mutation({
10 | args: {
11 | storageId: v.id('_storage'),
12 | },
13 | handler: async (ctx, { storageId }) => {
14 | const identity = await ctx.auth.getUserIdentity()
15 | if (!identity) {
16 | throw new Error('Not authenticated')
17 | }
18 |
19 | const userId = identity.subject
20 |
21 | const fileUrl = (await ctx.storage.getUrl(storageId))!
22 |
23 | const noteId = await ctx.db.insert('notes', {
24 | userId,
25 | audioFileId: storageId,
26 | audioFileUrl: fileUrl,
27 | })
28 |
29 | await ctx.scheduler.runAfter(0, internal.assembly.doTranscribe, {
30 | fileUrl,
31 | noteId,
32 | })
33 |
34 | return noteId
35 | },
36 | })
37 |
38 | export const getUserNotes = query({
39 | handler: async (ctx) => {
40 | const identity = await ctx.auth.getUserIdentity()
41 | if (!identity) {
42 | throw new Error('Not authenticated')
43 | }
44 |
45 | const userId = identity.subject
46 |
47 | const notes = await ctx.db
48 | .query('notes')
49 | .withIndex('by_userId', (q) => q.eq('userId', userId))
50 | .order('desc')
51 | .collect()
52 |
53 | return notes
54 | },
55 | })
56 |
57 | // Currently not a good solution its just a working prototype query. Need to make seprate query for getting particular note from id and getting share note page. Need to add confirm user identity and other edge cases.
58 | export const getNoteById = query({
59 | args: {
60 | id: v.id('notes'),
61 | },
62 | handler: async (ctx, args) => {
63 | const { id } = args
64 | // const identity = await ctx.auth.getUserIdentity()
65 | // if (!identity) {
66 | // throw new Error('Not authenticated')
67 | // }
68 |
69 | // const userId = identity.subject
70 |
71 | // if (!userId) {
72 | // throw new Error('Not authenticated')
73 | // }
74 |
75 | const note = await ctx.db.get(id)
76 | if (!note) {
77 | throw new Error('Not found')
78 | }
79 |
80 | // if (note.userId !== userId) {
81 | // throw new Error('Unauthorized')
82 | // }
83 |
84 | const actionItems = await ctx.db
85 | .query('actionItems')
86 | .withIndex('by_noteId', (q) => q.eq('noteId', note._id))
87 | .order('desc')
88 | .collect()
89 |
90 | return { note, actionItems }
91 | },
92 | })
93 |
94 | export const removeNote = mutation({
95 | args: {
96 | id: v.id('notes'),
97 | },
98 | handler: async (ctx, args) => {
99 | const { id } = args
100 | const identity = await ctx.auth.getUserIdentity()
101 | if (!identity) {
102 | throw new Error('Not authenticated')
103 | }
104 |
105 | const note = await ctx.db.get(id)
106 |
107 | if (!note) {
108 | throw new Error('Note not found')
109 | }
110 |
111 | const userId = identity.subject
112 |
113 | if (note.userId !== userId) {
114 | throw new Error('Not your note')
115 | }
116 |
117 | const actionItems = await ctx.db
118 | .query('actionItems')
119 | .withIndex('by_noteId', (q) => q.eq('noteId', id))
120 | .collect()
121 |
122 | await Promise.all(actionItems.map((item) => ctx.db.delete(item._id)))
123 |
124 | const promise = await ctx.db.delete(id)
125 |
126 | return promise
127 | },
128 | })
129 |
130 | export const createActionItem = mutation({
131 | args: {
132 | noteId: v.id('notes'),
133 | action: v.string(),
134 | },
135 | handler: async (ctx, { noteId, action }) => {
136 | const identity = await ctx.auth.getUserIdentity()
137 | if (!identity) {
138 | throw new Error('Not authenticated')
139 | }
140 |
141 | const userId = identity.subject
142 |
143 | const note = await ctx.db.get(noteId)
144 | if (!note) {
145 | throw new Error('Not found')
146 | }
147 |
148 | if (note.userId !== userId) {
149 | throw new Error('Unauthorized')
150 | }
151 |
152 | const promise = await ctx.db.insert('actionItems', {
153 | userId,
154 | noteId,
155 | action,
156 | })
157 | return promise
158 | },
159 | })
160 |
161 | export const getActionItems = query({
162 | handler: async (ctx) => {
163 | const identity = await ctx.auth.getUserIdentity()
164 | if (!identity) {
165 | throw new Error('Not authenticated')
166 | }
167 |
168 | const userId = identity.subject
169 |
170 | const actionItems = await ctx.db
171 | .query('actionItems')
172 | .withIndex('by_userId', (q) => q.eq('userId', userId))
173 | .order('desc')
174 | .collect()
175 |
176 | let modifiedActionItems = []
177 | for (let item of actionItems) {
178 | const note = await ctx.db.get(item.noteId)
179 | if (!note) continue
180 | modifiedActionItems.push({
181 | ...item,
182 | title: note.title,
183 | })
184 | }
185 |
186 | return modifiedActionItems
187 | },
188 | })
189 |
190 | export const removeActionItem = mutation({
191 | args: {
192 | id: v.id('actionItems'),
193 | },
194 | handler: async (ctx, args) => {
195 | const { id } = args
196 | const identity = await ctx.auth.getUserIdentity()
197 | if (!identity) {
198 | throw new Error('Not authenticated')
199 | }
200 |
201 | const actionItem = await ctx.db.get(id)
202 |
203 | if (!actionItem) {
204 | throw new Error('Action Item not found')
205 | }
206 |
207 | const userId = identity.subject
208 |
209 | if (actionItem.userId !== userId) {
210 | throw new Error('Not your action item')
211 | }
212 |
213 | const promise = await ctx.db.delete(id)
214 | return promise
215 | },
216 | })
217 |
--------------------------------------------------------------------------------
/convex/schema.ts:
--------------------------------------------------------------------------------
1 | import { v } from 'convex/values'
2 | import { defineSchema, defineTable } from 'convex/server'
3 |
4 | export default defineSchema({
5 | notes: defineTable({
6 | userId: v.string(),
7 | audioFileId: v.string(),
8 | audioFileUrl: v.string(),
9 | title: v.optional(v.string()),
10 | transcription: v.optional(v.string()),
11 | summary: v.optional(v.string()),
12 | }).index('by_userId', ['userId']),
13 | actionItems: defineTable({
14 | noteId: v.id('notes'),
15 | userId: v.string(),
16 | action: v.string(),
17 | })
18 | .index('by_noteId', ['noteId'])
19 | .index('by_userId', ['userId']),
20 | })
21 |
--------------------------------------------------------------------------------
/convex/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | /* This TypeScript project config describes the environment that
3 | * Convex functions run in and is used to typecheck them.
4 | * You can modify it, but some settings required to use Convex.
5 | */
6 | "compilerOptions": {
7 | /* These settings are not required by Convex and can be modified. */
8 | "allowJs": true,
9 | "strict": true,
10 |
11 | /* These compiler options are required by Convex */
12 | "target": "ESNext",
13 | "lib": ["ES2021", "dom"],
14 | "forceConsistentCasingInFileNames": true,
15 | "allowSyntheticDefaultImports": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "isolatedModules": true,
19 | "skipLibCheck": true,
20 | "noEmit": true
21 | },
22 | "include": ["./**/*"],
23 | "exclude": ["./_generated"]
24 | }
25 |
--------------------------------------------------------------------------------
/hooks/use-origin.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export const useOrigin = () => {
4 | const [mounted, setMounted] = useState(false)
5 | const origin =
6 | typeof window !== 'undefined' && window.location.origin
7 | ? window.location.origin
8 | : ''
9 |
10 | useEffect(() => {
11 | setMounted(true)
12 | }, [])
13 |
14 | if (!mounted) {
15 | return ''
16 | }
17 |
18 | return origin
19 | }
20 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { twMerge } from 'tailwind-merge'
2 | import { auth } from '@clerk/nextjs/server'
3 | import { type ClassValue, clsx } from 'clsx'
4 |
5 | export function cn(...inputs: ClassValue[]) {
6 | return twMerge(clsx(inputs))
7 | }
8 |
9 | export function getCurrentFormattedDate(): string {
10 | const currentDate = new Date()
11 | const options: Intl.DateTimeFormatOptions = {
12 | year: 'numeric',
13 | month: 'long',
14 | day: 'numeric',
15 | hour: 'numeric',
16 | minute: 'numeric',
17 | hour12: true,
18 | }
19 | return new Intl.DateTimeFormat('en-US', options).format(currentDate)
20 | }
21 |
22 | export function formatTime(time: number): string {
23 | return time < 10 ? `0${time}` : `${time}`
24 | }
25 |
26 | export async function getAuthToken() {
27 | return (await auth().getToken({ template: 'convex' })) ?? undefined
28 | }
29 |
30 | export function formatDate(timestamp: number) {
31 | const date = new Date(timestamp)
32 | const options: Intl.DateTimeFormatOptions = {
33 | weekday: 'short',
34 | month: 'short',
35 | day: '2-digit',
36 | year: 'numeric',
37 | }
38 | return date.toLocaleDateString('en-US', options)
39 | }
40 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Abdullah Sidd
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 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
2 |
3 | const isProtectedRoute = createRouteMatcher([
4 | '/recordings(.*)',
5 | '/record(.*)',
6 | '/action-items(.*)',
7 | ])
8 |
9 | export default clerkMiddleware((auth, req) => {
10 | if (isProtectedRoute(req)) auth().protect()
11 | })
12 |
13 | export const config = {
14 | matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
15 | }
16 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | const nextConfig = {
2 | images: {
3 | remotePatterns: [
4 | {
5 | protocol: 'https',
6 | hostname: 'pbs.twimg.com',
7 | pathname: '**',
8 | },
9 | ],
10 | },
11 | experimental: {
12 | reactCompiler: true,
13 | },
14 | }
15 |
16 | export default nextConfig
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notesgpt",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@clerk/nextjs": "^5.0.2",
13 | "@google/generative-ai": "^0.8.0",
14 | "@radix-ui/react-alert-dialog": "^1.0.5",
15 | "@radix-ui/react-avatar": "^1.0.4",
16 | "@radix-ui/react-checkbox": "^1.0.4",
17 | "@radix-ui/react-dialog": "^1.0.5",
18 | "@radix-ui/react-dropdown-menu": "^2.0.6",
19 | "@radix-ui/react-popover": "^1.0.7",
20 | "@radix-ui/react-select": "^2.0.0",
21 | "@radix-ui/react-separator": "^1.0.3",
22 | "@radix-ui/react-slot": "^1.0.2",
23 | "@radix-ui/react-tabs": "^1.0.4",
24 | "@tanstack/react-table": "^8.16.0",
25 | "@vercel/analytics": "^1.2.2",
26 | "assemblyai": "^4.4.1",
27 | "babel-plugin-react-compiler": "^0.0.0-experimental-696af53-20240625",
28 | "class-variance-authority": "^0.7.0",
29 | "clsx": "^2.1.1",
30 | "cmdk": "^1.0.0",
31 | "convex": "^1.11.2",
32 | "framer-motion": "^11.1.7",
33 | "lucide-react": "^0.372.0",
34 | "next": "^15.0.0-rc.0",
35 | "next-themes": "^0.3.0",
36 | "react": "^19.0.0-rc-c3cdbec0a7-20240708",
37 | "react-dom": "^19.0.0-rc-c3cdbec0a7-20240708",
38 | "react-wrap-balancer": "^1.1.0",
39 | "sonner": "^1.4.41",
40 | "tailwind-merge": "^2.3.0",
41 | "tailwindcss-animate": "^1.0.7"
42 | },
43 | "devDependencies": {
44 | "@types/eslint": "^8.56.10",
45 | "@types/node": "^20",
46 | "@types/react": "^18",
47 | "@types/react-dom": "^18",
48 | "@typescript-eslint/eslint-plugin": "^7.16.0",
49 | "@typescript-eslint/parser": "^7.16.0",
50 | "eslint": "^8",
51 | "eslint-config-next": "14.2.2",
52 | "postcss": "^8",
53 | "prettier": "^3.3.2",
54 | "prettier-plugin-tailwindcss": "^0.6.5",
55 | "tailwindcss": "^3.4.1",
56 | "typescript": "^5"
57 | },
58 | "trustedDependencies": [
59 | "@clerk/shared",
60 | "esbuild",
61 | "sharp"
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdtriedcoding/notesGPT/3cbba2f0021bd7317ed0631649da31e02ee66784/public/desktop.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdtriedcoding/notesGPT/3cbba2f0021bd7317ed0631649da31e02ee66784/public/favicon.ico
--------------------------------------------------------------------------------
/public/mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdtriedcoding/notesGPT/3cbba2f0021bd7317ed0631649da31e02ee66784/public/mobile.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/nonrecording_mic.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/recording_mic.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdtriedcoding/notesGPT/3cbba2f0021bd7317ed0631649da31e02ee66784/public/thumbnail.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./pages/**/*.{ts,tsx}",
7 | "./components/**/*.{ts,tsx}",
8 | "./app/**/*.{ts,tsx}",
9 | "./src/**/*.{ts,tsx}",
10 | ],
11 | prefix: "",
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: "2rem",
16 | screens: {
17 | "2xl": "1400px",
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: "hsl(var(--border))",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | },
56 | borderRadius: {
57 | lg: "var(--radius)",
58 | md: "calc(var(--radius) - 2px)",
59 | sm: "calc(var(--radius) - 4px)",
60 | },
61 | keyframes: {
62 | "accordion-down": {
63 | from: { height: "0" },
64 | to: { height: "var(--radix-accordion-content-height)" },
65 | },
66 | "accordion-up": {
67 | from: { height: "var(--radix-accordion-content-height)" },
68 | to: { height: "0" },
69 | },
70 | // Fade up and down
71 | "fade-up": {
72 | "0%": {
73 | opacity: "0",
74 | transform: "translateY(10px)",
75 | },
76 | "80%": {
77 | opacity: "0.7",
78 | },
79 | "100%": {
80 | opacity: "1",
81 | transform: "translateY(0px)",
82 | },
83 | },
84 | "fade-down": {
85 | "0%": {
86 | opacity: "0",
87 | transform: "translateY(-10px)",
88 | },
89 | "80%": {
90 | opacity: "0.6",
91 | },
92 | "100%": {
93 | opacity: "1",
94 | transform: "translateY(0px)",
95 | },
96 | },
97 | // Fade in and out
98 | "fade-in": {
99 | "0%": {
100 | opacity: "0",
101 | },
102 | "50%": {
103 | opacity: "0.6",
104 | },
105 | "100%": {
106 | opacity: "1",
107 | },
108 | },
109 | "fade-out": {
110 | "0%": {
111 | opacity: "0",
112 | },
113 | "50%": {
114 | opacity: "0.6",
115 | },
116 | "100%": {
117 | opacity: "1",
118 | },
119 | },
120 | },
121 | animation: {
122 | "accordion-down": "accordion-down 0.2s ease-out",
123 | "accordion-up": "accordion-up 0.2s ease-out",
124 | // Fade up and down
125 | "fade-up": "fade-up 0.5s",
126 | "fade-down": "fade-down 0.5s",
127 |
128 | // Fade in and out
129 | "fade-in": "fade-in 0.4s",
130 | "fade-out": "fade-out 0.4s",
131 | },
132 | },
133 | },
134 | plugins: [require("tailwindcss-animate")],
135 | } satisfies Config;
136 |
137 | export default config;
138 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Base Options: */
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2022",
7 | "allowJs": true,
8 | "resolveJsonModule": true,
9 | "moduleDetection": "force",
10 | "isolatedModules": true,
11 |
12 | /* Strictness */
13 | "strict": true,
14 | "noUncheckedIndexedAccess": true,
15 | "checkJs": true,
16 |
17 | /* Bundled projects */
18 | "lib": ["dom", "dom.iterable", "ES2022"],
19 | "noEmit": true,
20 | "module": "ESNext",
21 | "moduleResolution": "Bundler",
22 | "jsx": "preserve",
23 | "plugins": [{ "name": "next" }],
24 | "incremental": true,
25 |
26 | /* Path Aliases */
27 | "baseUrl": ".",
28 | "paths": {
29 | "@/*": ["./*"]
30 | }
31 | },
32 | "include": [
33 | ".eslintrc.cjs",
34 | "next-env.d.ts",
35 | "**/*.ts",
36 | "**/*.tsx",
37 | "**/*.cjs",
38 | "**/*.js",
39 | ".next/types/**/*.ts",
40 | "next.config.mjs"
41 | ],
42 | "exclude": ["node_modules"]
43 | }
44 |
--------------------------------------------------------------------------------