├── .env-example ├── .eslintrc.json ├── .gitignore ├── CodeSnap └── code.png ├── README.md ├── components.json ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── prisma └── schema.prisma ├── src ├── app │ ├── ThemeProvider.tsx │ ├── api │ │ ├── chat │ │ │ └── route.ts │ │ └── notes │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── notes │ │ ├── NavBar.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── page.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ └── sign-up │ │ └── [[...sign-up]] │ │ └── page.tsx ├── assets │ ├── logo.png │ ├── logo1.png │ └── logo2.png ├── components │ ├── AIChatBox.tsx │ ├── AIChatButton.tsx │ ├── AddEditNoteDialog.tsx │ ├── Note.tsx │ ├── ThemeToggleButton.tsx │ └── ui │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dialog.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── loading-button.tsx │ │ └── textarea.tsx ├── lib │ ├── db │ │ ├── pinecone.ts │ │ └── prisma.ts │ ├── openai.ts │ ├── utils.ts │ └── validation │ │ └── note.ts └── middleware.ts ├── tailwind.config.js └── tsconfig.json /.env-example: -------------------------------------------------------------------------------- 1 | # This was inserted by `prisma init`: 2 | # Environment variables declared in this file are automatically made available to Prisma. 3 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 4 | 5 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. 6 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 7 | 8 | DATABASE_URL= "your dabase url" --> change password and add database name 9 | 10 | 11 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= your clerk publishable key 12 | CLERK_SECRET_KEY= your clerk secret key 13 | 14 | 15 | NEXT_PUBLIC_CLERK_SIGN_IN_URL =/sign-in 16 | 17 | NEXT_PUBLIC_CLERK_SIGN_OUT_URL =/sign-out 18 | 19 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL =/notes 20 | 21 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL =/notes 22 | 23 | OPENAI_API_KEY= your openai api key 24 | 25 | PINECONE_API_KEY= your pincode api key -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 38 | .env 39 | 40 | -------------------------------------------------------------------------------- /CodeSnap/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekHegde2000/ai-note-app/6e91e67cec46acd6d51a774c5483c431433a2fd2/CodeSnap/code.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AI Note-Taking App 3 | 4 | ## Introduction 5 | 6 | Introducing a revolutionary AI-powered note-taking app that elevates note-taking to a whole new level. This innovative app utilizes the power of artificial intelligence to analyze your notes and provide comprehensive answers to your queries. Say goodbye to the days of manually searching through piles of notes – our AI assistant will do the work for you, saving you time and effort. 7 | 8 | ## Project Demo 9 | 10 | https://github.com/abhishekHegde2000/ai-note-app/assets/88794340/fa709d86-efca-421b-9251-421db9f810b0 11 | 12 | ## Features 13 | 14 | * **AI-Powered Chatbot:** Engage in real-time conversations with our AI chatbot, ask questions about your notes, and receive insightful answers. 15 | 16 | * **Intelligent Document Retrieval:** Seamlessly retrieve relevant documents and notes using ChatGPT vector embeddings and Pinecone. 17 | 18 | * **Personalized User Experience:** Switch between light and dark themes for optimal viewing comfort with our next-themes integration. 19 | 20 | * **Secure User Authentication:** Protect your notes with Clerk's robust authentication system, ensuring only authorized users can access your information. 21 | 22 | * **CRUD Operations for Notes:** Create, update, and delete notes effortlessly using Prisma and MongoDB Atlas, maintaining a well-organized note-taking system. 23 | 24 | * **Organized Layouts:** Navigate through structured and intuitive layouts, thanks to our nested layouts implementation. 25 | 26 | * **Comprehensive Form Validation:** Validate form inputs both client-side and server-side with Zod, React-Hook-Form, and Shadcn UI Form, ensuring data integrity. 27 | 28 | * **Fully Responsive Design:** Experience a seamless user experience across all devices with our TailwindCSS modifiers, ensuring the app adapts perfectly to any screen size. 29 | 30 | ## Technology Stack 31 | 32 | * Next.js 14: A modern React framework for building server-side rendered applications. 33 | 34 | * ChatGPT API: Leverage the power of ChatGPT to generate human-quality text, translate languages, write different kinds of creative content, and answer your questions in an informative way. 35 | 36 | * Vector Embeddings: Represent text documents as vectors in a high-dimensional space, enabling efficient similarity searches and document retrieval. 37 | 38 | * Pinecone: A scalable vector similarity search engine for rapid and accurate document retrieval. 39 | 40 | * TailwindCSS: A utility-first CSS framework for building responsive and customizable user interfaces. 41 | 42 | * Shadcn UI: A collection of React components for building beautiful and accessible user interfaces. 43 | 44 | * TypeScript: A superset of JavaScript that adds type annotations, ensuring code reliability and maintainability. 45 | 46 | * Vercel AI SDK: A suite of tools for integrating AI capabilities into your Next.js applications. 47 | 48 | * API Route Handlers: Create and handle API routes to communicate with your application's backend. 49 | 50 | * Clerk: A user authentication and authorization platform for securing your application. 51 | 52 | * Prisma: A powerful data modeling and manipulation tool for interacting with your database. 53 | 54 | * MongoDB Atlas: A cloud-based NoSQL database for storing your notes and application data. 55 | 56 | ## Installation and Usage 57 | 58 | 1. Clone the project repository from GitHub: 59 | 60 | ```bash 61 | git clone https://github.com/abhishekHegde2000/ai-note-app.git 62 | ``` 63 | 64 | 2. Install the project dependencies: 65 | 66 | ```bash 67 | cd ai-note-taking-app 68 | npm install 69 | ``` 70 | 71 | 3. Set up environment variables for your database connection and ChatGPT API key. 72 | 73 | 4. Start the development server: 74 | 75 | ```bash 76 | npm run dev 77 | ``` 78 | 79 | 5. Open the application in your web browser: 80 | 81 | ```bash 82 | http://localhost:3000 83 | ``` 84 | 85 | ## Contribution 86 | 87 | I welcome contributions to this project. Please feel free to submit issues, pull requests, or suggestions for improvement. 88 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | hostname: "img.clerk.com", 7 | }, 8 | ], 9 | }, 10 | }; 11 | 12 | module.exports = nextConfig; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-ai-note-app", 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 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@clerk/nextjs": "^4.26.1", 14 | "@clerk/themes": "^1.7.9", 15 | "@hookform/resolvers": "^3.3.2", 16 | "@pinecone-database/pinecone": "^1.1.2", 17 | "@prisma/client": "^5.5.2", 18 | "@radix-ui/react-dialog": "^1.0.5", 19 | "@radix-ui/react-label": "^2.0.2", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "ai": "^2.2.20", 22 | "class-variance-authority": "^0.7.0", 23 | "clsx": "^2.0.0", 24 | "eslint-config-prettier": "^9.0.0", 25 | "lucide-react": "^0.290.0", 26 | "mongodb": "^6.3.0", 27 | "next": "14.0.0", 28 | "next-themes": "^0.2.1", 29 | "openai": "^4.14.1", 30 | "prettier": "^3.0.3", 31 | "prettier-plugin-tailwindcss": "^0.5.6", 32 | "react": "^18", 33 | "react-dom": "^18", 34 | "react-hook-form": "^7.47.0", 35 | "tailwind-merge": "^1.14.0", 36 | "tailwindcss-animate": "^1.0.7", 37 | "zod": "^3.22.4" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^20", 41 | "@types/react": "^18", 42 | "@types/react-dom": "^18", 43 | "autoprefixer": "^10", 44 | "eslint": "^8", 45 | "eslint-config-next": "14.0.0", 46 | "postcss": "^8", 47 | "tailwindcss": "^3", 48 | "typescript": "^5" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["prettier-plugin-tailwindcss"], 3 | }; 4 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "mongodb" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model Note { 14 | id String @id @default(auto()) @map("_id") @db.ObjectId 15 | title String 16 | content String? 17 | userId String 18 | createdAt DateTime @default(now()) 19 | updatedAt DateTime @updatedAt 20 | 21 | @@map("notes") 22 | } 23 | -------------------------------------------------------------------------------- /src/app/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export { ThemeProvider } from "next-themes"; 4 | -------------------------------------------------------------------------------- /src/app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { notesIndex } from "@/lib/db/pinecone"; 2 | import prisma from "@/lib/db/prisma"; 3 | import openai, { getEmbedding } from "@/lib/openai"; 4 | import { auth } from "@clerk/nextjs"; 5 | import { OpenAIStream, StreamingTextResponse } from "ai"; 6 | import { ChatCompletionMessage } from "openai/resources/index.mjs"; 7 | 8 | export async function POST(req: Request) { 9 | try { 10 | const body = await req.json(); 11 | const messages: ChatCompletionMessage[] = body.messages; 12 | 13 | const messagesTruncated = messages.slice(-6); 14 | 15 | const embedding = await getEmbedding( 16 | messagesTruncated.map((message) => message.content).join("\n"), 17 | ); 18 | 19 | const { userId } = auth(); 20 | 21 | const vectorQueryResponse = await notesIndex.query({ 22 | vector: embedding, 23 | topK: 4, 24 | filter: { userId }, 25 | }); 26 | 27 | const relevantNotes = await prisma.note.findMany({ 28 | where: { 29 | id: { 30 | in: vectorQueryResponse.matches.map((match) => match.id), 31 | }, 32 | }, 33 | }); 34 | 35 | console.log("Relevant notes found: ", relevantNotes); 36 | 37 | const systemMessage: ChatCompletionMessage = { 38 | role: "system", 39 | content: 40 | "You are an intelligent note-taking app. You answer the user's question based on their existing notes. " + 41 | "The relevant notes for this query are:\n" + 42 | relevantNotes 43 | .map((note) => `Title: ${note.title}\n\nContent:\n${note.content}`) 44 | .join("\n\n"), 45 | }; 46 | 47 | const response = await openai.chat.completions.create({ 48 | model: "gpt-3.5-turbo", 49 | stream: true, 50 | messages: [systemMessage, ...messagesTruncated], 51 | }); 52 | 53 | const stream = OpenAIStream(response); 54 | return new StreamingTextResponse(stream); 55 | } catch (error) { 56 | console.error(error); 57 | return Response.json({ error: "Internal server error" }, { status: 500 }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/app/api/notes/route.ts: -------------------------------------------------------------------------------- 1 | import { notesIndex } from "@/lib/db/pinecone"; 2 | import prisma from "@/lib/db/prisma"; 3 | import { getEmbedding } from "@/lib/openai"; 4 | import { 5 | createNoteSchema, 6 | deleteNoteSchema, 7 | updateNoteSchema, 8 | } from "@/lib/validation/note"; 9 | import { auth } from "@clerk/nextjs"; 10 | 11 | export async function POST(req: Request) { 12 | try { 13 | const body = await req.json(); 14 | 15 | const parseResult = createNoteSchema.safeParse(body); 16 | 17 | if (!parseResult.success) { 18 | console.error(parseResult.error); 19 | return Response.json({ error: "Invalid input" }, { status: 400 }); 20 | } 21 | 22 | const { title, content } = parseResult.data; 23 | 24 | const { userId } = auth(); 25 | 26 | if (!userId) { 27 | return Response.json({ error: "Unauthorized" }, { status: 401 }); 28 | } 29 | 30 | const embedding = await getEmbeddingForNote(title, content); 31 | 32 | const note = await prisma.$transaction(async (tx) => { 33 | const note = await tx.note.create({ 34 | data: { 35 | title, 36 | content, 37 | userId, 38 | }, 39 | }); 40 | 41 | await notesIndex.upsert([ 42 | { 43 | id: note.id, 44 | values: embedding, 45 | metadata: { userId }, 46 | }, 47 | ]); 48 | 49 | return note; 50 | }); 51 | 52 | return Response.json({ note }, { status: 201 }); 53 | } catch (error) { 54 | console.error(error); 55 | return Response.json({ error: "Internal server error" }, { status: 500 }); 56 | } 57 | } 58 | 59 | export async function PUT(req: Request) { 60 | try { 61 | const body = await req.json(); 62 | 63 | const parseResult = updateNoteSchema.safeParse(body); 64 | 65 | if (!parseResult.success) { 66 | console.error(parseResult.error); 67 | return Response.json({ error: "Invalid input" }, { status: 400 }); 68 | } 69 | 70 | const { id, title, content } = parseResult.data; 71 | 72 | const note = await prisma.note.findUnique({ where: { id } }); 73 | 74 | if (!note) { 75 | return Response.json({ error: "Note not found" }, { status: 404 }); 76 | } 77 | 78 | const { userId } = auth(); 79 | 80 | if (!userId || userId !== note.userId) { 81 | return Response.json({ error: "Unauthorized" }, { status: 401 }); 82 | } 83 | 84 | const embedding = await getEmbeddingForNote(title, content); 85 | 86 | const updatedNote = await prisma.$transaction(async (tx) => { 87 | const updatedNote = await tx.note.update({ 88 | where: { id }, 89 | data: { 90 | title, 91 | content, 92 | }, 93 | }); 94 | 95 | await notesIndex.upsert([ 96 | { 97 | id, 98 | values: embedding, 99 | metadata: { userId }, 100 | }, 101 | ]); 102 | 103 | return updatedNote; 104 | }); 105 | 106 | return Response.json({ updatedNote }, { status: 200 }); 107 | } catch (error) { 108 | console.error(error); 109 | return Response.json({ error: "Internal server error" }, { status: 500 }); 110 | } 111 | } 112 | 113 | export async function DELETE(req: Request) { 114 | try { 115 | const body = await req.json(); 116 | 117 | const parseResult = deleteNoteSchema.safeParse(body); 118 | 119 | if (!parseResult.success) { 120 | console.error(parseResult.error); 121 | return Response.json({ error: "Invalid input" }, { status: 400 }); 122 | } 123 | 124 | const { id } = parseResult.data; 125 | 126 | const note = await prisma.note.findUnique({ where: { id } }); 127 | 128 | if (!note) { 129 | return Response.json({ error: "Note not found" }, { status: 404 }); 130 | } 131 | 132 | const { userId } = auth(); 133 | 134 | if (!userId || userId !== note.userId) { 135 | return Response.json({ error: "Unauthorized" }, { status: 401 }); 136 | } 137 | 138 | await prisma.$transaction(async (tx) => { 139 | await tx.note.delete({ where: { id } }); 140 | await notesIndex.deleteOne(id); 141 | }); 142 | 143 | return Response.json({ message: "Note deleted" }, { status: 200 }); 144 | } catch (error) { 145 | console.error(error); 146 | return Response.json({ error: "Internal server error" }, { status: 500 }); 147 | } 148 | } 149 | 150 | async function getEmbeddingForNote(title: string, content: string | undefined) { 151 | return getEmbedding(title + "\n\n" + content ?? ""); 152 | } 153 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekHegde2000/ai-note-app/6e91e67cec46acd6d51a774c5483c431433a2fd2/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ClerkProvider } from "@clerk/nextjs"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import { ThemeProvider } from "./ThemeProvider"; 5 | import "./globals.css"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata: Metadata = { 10 | title: "IntelliNote", 11 | description: "The intelligent note-taking app", 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/notes/NavBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import logo from "@/assets/logo.png"; 4 | import AIChatButton from "@/components/AIChatButton"; 5 | import AddEditNoteDialog from "@/components/AddEditNoteDialog"; 6 | import ThemeToggleButton from "@/components/ThemeToggleButton"; 7 | import { Button } from "@/components/ui/button"; 8 | import { UserButton } from "@clerk/nextjs"; 9 | import { dark } from "@clerk/themes"; 10 | import { Plus } from "lucide-react"; 11 | import { useTheme } from "next-themes"; 12 | import Image from "next/image"; 13 | import Link from "next/link"; 14 | import { useState } from "react"; 15 | 16 | export default function NavBar() { 17 | const { theme } = useTheme(); 18 | 19 | const [showAddEditNoteDialog, setShowAddEditNoteDialog] = useState(false); 20 | 21 | return ( 22 | <> 23 |
24 |
25 | 26 | IntelliNote logo 27 | IntelliNote 28 | 29 |
30 | 37 | 38 | 42 | 43 |
44 |
45 |
46 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/app/notes/layout.tsx: -------------------------------------------------------------------------------- 1 | import NavBar from "./NavBar"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 | 7 |
{children}
8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/notes/page.tsx: -------------------------------------------------------------------------------- 1 | import Note from "@/components/Note"; 2 | import prisma from "@/lib/db/prisma"; 3 | import { auth } from "@clerk/nextjs"; 4 | import { Metadata } from "next"; 5 | 6 | export const metadata: Metadata = { 7 | title: "IntelliNote - Notes", 8 | }; 9 | 10 | export default async function NotesPage() { 11 | const { userId } = auth(); 12 | 13 | if (!userId) throw Error("userId undefined"); 14 | 15 | const allNotes = await prisma.note.findMany({ where: { userId } }); 16 | 17 | return ( 18 |
19 | {allNotes.map((note) => ( 20 | 21 | ))} 22 | {allNotes.length === 0 && ( 23 |
24 | {"You don't have any notes yet. Why don't you create one?"} 25 |
26 | )} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import logo from "@/assets/logo.png"; 2 | import { Button } from "@/components/ui/button"; 3 | import { auth } from "@clerk/nextjs"; 4 | import Image from "next/image"; 5 | import Link from "next/link"; 6 | import { redirect } from "next/navigation"; 7 | 8 | export default function Home() { 9 | const { userId } = auth(); 10 | 11 | if (userId) redirect("/notes"); 12 | 13 | return ( 14 |
15 |
16 | IntelliNote logo 17 | 18 | IntelliNote 19 | 20 |
21 |

22 | An intelligent note-taking app with AI integration, built with OpenAI, 23 | Pinecone, Next.js, Shadcn UI, Clerk, and more. 24 |

25 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | import { Metadata } from "next"; 3 | 4 | export const metadata: Metadata = { 5 | title: "IntelliNote - Sign In", 6 | }; 7 | 8 | export default function SignInPage() { 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | import { Metadata } from "next"; 3 | 4 | export const metadata: Metadata = { 5 | title: "IntelliNote - Sign Up", 6 | }; 7 | 8 | export default function SignUpPage() { 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekHegde2000/ai-note-app/6e91e67cec46acd6d51a774c5483c431433a2fd2/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekHegde2000/ai-note-app/6e91e67cec46acd6d51a774c5483c431433a2fd2/src/assets/logo1.png -------------------------------------------------------------------------------- /src/assets/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekHegde2000/ai-note-app/6e91e67cec46acd6d51a774c5483c431433a2fd2/src/assets/logo2.png -------------------------------------------------------------------------------- /src/components/AIChatBox.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { useUser } from "@clerk/nextjs"; 3 | import { Message } from "ai"; 4 | import { useChat } from "ai/react"; 5 | import { Bot, Trash, XCircle } from "lucide-react"; 6 | import Image from "next/image"; 7 | import { useEffect, useRef } from "react"; 8 | import { Button } from "./ui/button"; 9 | import { Input } from "./ui/input"; 10 | 11 | interface AIChatBoxProps { 12 | open: boolean; 13 | onClose: () => void; 14 | } 15 | 16 | export default function AIChatBox({ open, onClose }: AIChatBoxProps) { 17 | const { 18 | messages, 19 | input, 20 | handleInputChange, 21 | handleSubmit, 22 | setMessages, 23 | isLoading, 24 | error, 25 | } = useChat(); 26 | 27 | const inputRef = useRef(null); 28 | const scrollRef = useRef(null); 29 | 30 | useEffect(() => { 31 | if (scrollRef.current) { 32 | scrollRef.current.scrollTop = scrollRef.current.scrollHeight; 33 | } 34 | }, [messages]); 35 | 36 | useEffect(() => { 37 | if (open) { 38 | inputRef.current?.focus(); 39 | } 40 | }, [open]); 41 | 42 | const lastMessageIsUser = messages[messages.length - 1]?.role === "user"; 43 | 44 | return ( 45 |
51 | 54 |
55 |
56 | {messages.map((message) => ( 57 | 58 | ))} 59 | {isLoading && lastMessageIsUser && ( 60 | 66 | )} 67 | {error && ( 68 | 74 | )} 75 | {!error && messages.length === 0 && ( 76 |
77 | 78 | Ask the AI a question about your notes 79 |
80 | )} 81 |
82 |
83 | 93 | 99 | 100 |
101 |
102 |
103 | ); 104 | } 105 | 106 | function ChatMessage({ 107 | message: { role, content }, 108 | }: { 109 | message: Pick; 110 | }) { 111 | const { user } = useUser(); 112 | 113 | const isAiMessage = role === "assistant"; 114 | 115 | return ( 116 |
122 | {isAiMessage && } 123 |

129 | {content} 130 |

131 | {!isAiMessage && user?.imageUrl && ( 132 | User image 139 | )} 140 |
141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /src/components/AIChatButton.tsx: -------------------------------------------------------------------------------- 1 | import { Bot } from "lucide-react"; 2 | import { useState } from "react"; 3 | import AIChatBox from "./AIChatBox"; 4 | import { Button } from "./ui/button"; 5 | 6 | export default function AIChatButton() { 7 | const [chatBoxOpen, setChatBoxOpen] = useState(false); 8 | 9 | return ( 10 | <> 11 | 15 | setChatBoxOpen(false)} /> 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/AddEditNoteDialog.tsx: -------------------------------------------------------------------------------- 1 | import { CreateNoteSchema, createNoteSchema } from "@/lib/validation/note"; 2 | import { zodResolver } from "@hookform/resolvers/zod"; 3 | import { Note } from "@prisma/client"; 4 | import { useRouter } from "next/navigation"; 5 | import { useState } from "react"; 6 | import { useForm } from "react-hook-form"; 7 | import { 8 | Dialog, 9 | DialogContent, 10 | DialogFooter, 11 | DialogHeader, 12 | DialogTitle, 13 | } from "./ui/dialog"; 14 | import { 15 | Form, 16 | FormControl, 17 | FormField, 18 | FormItem, 19 | FormLabel, 20 | FormMessage, 21 | } from "./ui/form"; 22 | import { Input } from "./ui/input"; 23 | import LoadingButton from "./ui/loading-button"; 24 | import { Textarea } from "./ui/textarea"; 25 | 26 | interface AddEditNoteDialogProps { 27 | open: boolean; 28 | setOpen: (open: boolean) => void; 29 | noteToEdit?: Note; 30 | } 31 | 32 | export default function AddEditNoteDialog({ 33 | open, 34 | setOpen, 35 | noteToEdit, 36 | }: AddEditNoteDialogProps) { 37 | const [deleteInProgress, setDeleteInProgress] = useState(false); 38 | 39 | const router = useRouter(); 40 | 41 | const form = useForm({ 42 | resolver: zodResolver(createNoteSchema), 43 | defaultValues: { 44 | title: noteToEdit?.title || "", 45 | content: noteToEdit?.content || "", 46 | }, 47 | }); 48 | 49 | async function onSubmit(input: CreateNoteSchema) { 50 | try { 51 | if (noteToEdit) { 52 | const response = await fetch("/api/notes", { 53 | method: "PUT", 54 | body: JSON.stringify({ 55 | id: noteToEdit.id, 56 | ...input, 57 | }), 58 | }); 59 | if (!response.ok) throw Error("Status code: " + response.status); 60 | } else { 61 | const response = await fetch("/api/notes", { 62 | method: "POST", 63 | body: JSON.stringify(input), 64 | }); 65 | if (!response.ok) throw Error("Status code: " + response.status); 66 | form.reset(); 67 | } 68 | router.refresh(); 69 | setOpen(false); 70 | } catch (error) { 71 | console.error(error); 72 | alert("Something went wrong. Please try again."); 73 | } 74 | } 75 | 76 | async function deleteNote() { 77 | if (!noteToEdit) return; 78 | setDeleteInProgress(true); 79 | try { 80 | const response = await fetch("/api/notes", { 81 | method: "DELETE", 82 | body: JSON.stringify({ 83 | id: noteToEdit.id, 84 | }), 85 | }); 86 | if (!response.ok) throw Error("Status code: " + response.status); 87 | router.refresh(); 88 | setOpen(false); 89 | } catch (error) { 90 | console.error(error); 91 | alert("Something went wrong. Please try again."); 92 | } finally { 93 | setDeleteInProgress(false); 94 | } 95 | } 96 | 97 | return ( 98 | 99 | 100 | 101 | {noteToEdit ? "Edit Note" : "Add Note"} 102 | 103 |
104 | 105 | ( 109 | 110 | Note title 111 | 112 | 113 | 114 | 115 | 116 | )} 117 | /> 118 | ( 122 | 123 | Note content 124 | 125 |