├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── drizzle.config.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── app │ ├── api │ │ ├── chat │ │ │ └── route.ts │ │ ├── create-chat │ │ │ └── route.ts │ │ ├── get-messages │ │ │ └── route.ts │ │ └── uploadthing │ │ │ ├── core.ts │ │ │ └── route.ts │ ├── chat │ │ └── [chatId] │ │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ └── sign-up │ │ └── [[...sign-up]] │ │ └── page.tsx ├── components │ ├── ChatComponent.tsx │ ├── ChatSideBar.tsx │ ├── FileUpload.tsx │ ├── MessageList.tsx │ ├── PDFViewer.tsx │ ├── Providers.tsx │ └── ui │ │ ├── Sheet.tsx │ │ ├── button.tsx │ │ ├── input.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── use-toast.ts ├── db │ ├── dbConfig.ts │ ├── helper │ │ └── index.ts │ └── models │ │ ├── chatModel.ts │ │ └── messageModel.ts ├── lib │ ├── context.ts │ ├── db │ │ ├── index.ts │ │ └── schema.ts │ ├── downloadFile.ts │ ├── embeddings.ts │ ├── huggingEmbedding.ts │ ├── pinecone.ts │ ├── transformers.ts │ ├── uploadThing.ts │ └── utils.ts └── middleware.ts ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | #playing with models 16 | openai challenges => cant perform embedding because of free tier limit 3/min. to tackle this im gonna make the chunksize high and limit to 3 not good at all but to test its enough. 17 | 18 | chatgpt model using cosine and transformer im trying dot product and cosine for similarites. 19 | vector dimension of text-ada open ai model is 1536, so need to update pinecone db; 20 | 21 | By using transformers i can use the "Xenova/e5-large-v2" model and its dimensions is 1024 so need to change the pinecone config. 22 | 23 | pinecone migrated from 0 to 1v so things changes including namespace not available for free tier so need to use some filter across the meta data. 24 | 25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 26 | 27 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 28 | 29 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 30 | 31 | ## Learn More 32 | 33 | To learn more about Next.js, take a look at the following resources: 34 | 35 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 36 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 37 | 38 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 39 | 40 | ## Deploy on Vercel 41 | 42 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 43 | 44 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 45 | -------------------------------------------------------------------------------- /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/global.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "drizzle-kit"; 2 | import * as dotenv from "dotenv"; 3 | dotenv.config({ path: ".env.local" }); 4 | 5 | export default { 6 | driver: "pg", 7 | schema: "./src/lib/db/schema.ts", 8 | dbCredentials: { 9 | connectionString: process.env.NEON_DATABASE_URL!, 10 | }, 11 | } satisfies Config; 12 | 13 | // npx drizzle-kit push:pg 14 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: "standalone", 4 | experimental: { 5 | serverComponentsExternalPackages: ["sharp", "onnxruntime-node"], 6 | }, 7 | }; 8 | 9 | module.exports = nextConfig; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-with-pdf", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "set NODE_OPTIONS=--max-old-space-size=8192 && next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^4.23.5", 13 | "@neondatabase/serverless": "^0.6.0", 14 | "@pinecone-database/doc-splitter": "^0.0.1", 15 | "@pinecone-database/pinecone": "0.1.6", 16 | "@radix-ui/react-dialog": "^1.0.4", 17 | "@radix-ui/react-slot": "^1.0.2", 18 | "@radix-ui/react-toast": "^1.1.4", 19 | "@tanstack/react-query": "^4.35.0", 20 | "@types/md5": "^2.3.2", 21 | "@types/node": "20.6.0", 22 | "@types/react": "18.2.21", 23 | "@types/react-dom": "18.2.7", 24 | "@uploadthing/react": "^5.5.0", 25 | "@xenova/transformers": "^2.6.0", 26 | "ai": "^2.2.12", 27 | "autoprefixer": "10.4.15", 28 | "axios": "^1.5.0", 29 | "class-variance-authority": "^0.7.0", 30 | "clsx": "^2.0.0", 31 | "dotenv": "^16.3.1", 32 | "drizzle-kit": "^0.19.13", 33 | "drizzle-orm": "^0.28.6", 34 | "encoding": "^0.1.13", 35 | "eslint": "8.49.0", 36 | "eslint-config-next": "13.4.19", 37 | "langchain": "^0.0.146", 38 | "lucide-react": "^0.276.0", 39 | "md5": "^2.3.0", 40 | "mongoose": "^7.5.0", 41 | "next": "13.4.19", 42 | "openai-edge": "^1.2.2", 43 | "pdf-parse": "^1.1.1", 44 | "pg": "^8.11.3", 45 | "postcss": "8.4.29", 46 | "react": "18.2.0", 47 | "react-dom": "18.2.0", 48 | "react-dropzone": "^14.2.3", 49 | "tailwind-merge": "^1.14.0", 50 | "tailwind-scrollbar": "^3.0.5", 51 | "tailwindcss": "3.3.3", 52 | "tailwindcss-animate": "^1.0.7", 53 | "typescript": "5.2.2", 54 | "uploadthing": "^5.5.3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { Configuration, OpenAIApi } from "openai-edge"; 2 | import { OpenAIStream, StreamingTextResponse, Message } from "ai"; 3 | import { getContext } from "@/lib/context"; 4 | import { db } from "@/lib/db"; 5 | import { chats, messages as _messages } from "@/lib/db/schema"; 6 | import { eq } from "drizzle-orm"; 7 | import { NextResponse } from "next/server"; 8 | import _message from "@/db/models/messageModel"; 9 | 10 | export const runtime = "edge"; 11 | 12 | const config = new Configuration({ 13 | apiKey: process.env.OPENAI_API_KEY, 14 | }); 15 | const openai = new OpenAIApi(config); 16 | 17 | export async function POST(req: Request) { 18 | try { 19 | const { messages, chatId } = await req.json(); 20 | 21 | const _chats = await db.select().from(chats).where(eq(chats.id, chatId)); 22 | if (_chats.length != 1) { 23 | return NextResponse.json({ error: "chat not found" }, { status: 404 }); 24 | } 25 | const fileKey = _chats[0].fileKey; 26 | const lastMessage = messages[messages.length - 1]; 27 | const context = await getContext(lastMessage.content, fileKey); 28 | console.log("lol context", context); 29 | const prompt = { 30 | role: "system", 31 | content: `AI assistant is a brand new, powerful, human-like artificial intelligence. 32 | The traits of AI include expert knowledge, helpfulness, cleverness, and articulateness. 33 | AI is a well-behaved and well-mannered individual. 34 | AI is always friendly, kind, and inspiring, and he is eager to provide vivid and thoughtful responses to the user. 35 | AI has the sum of all knowledge in their brain, and is able to accurately answer nearly any question about any topic in conversation. 36 | AI assistant is a big fan of Pinecone and Vercel. 37 | START CONTEXT BLOCK 38 | ${context} 39 | END OF CONTEXT BLOCK 40 | AI assistant will take into account any CONTEXT BLOCK that is provided in a conversation. 41 | If the context does not provide the answer to question, the AI assistant will say, "I'm sorry, but I don't know the answer to that question". 42 | AI assistant will not apologize for previous responses, but instead will indicated new information was gained. 43 | AI assistant will not invent anything that is not drawn directly from the context. 44 | `, 45 | }; 46 | 47 | const response = await openai.createChatCompletion({ 48 | model: "gpt-3.5-turbo", 49 | messages: [ 50 | prompt, 51 | ...messages.filter((message: Message) => message.role === "user"), 52 | ], 53 | stream: true, 54 | }); 55 | const stream = OpenAIStream(response, { 56 | onStart: async () => { 57 | // save user message into db 58 | await db.insert(_messages).values({ 59 | chatId, 60 | content: lastMessage.content, 61 | role: "user", 62 | }); 63 | }, 64 | onCompletion: async (completion) => { 65 | // save ai message into db 66 | await db.insert(_messages).values({ 67 | chatId, 68 | content: completion, 69 | role: "system", 70 | }); 71 | }, 72 | }); 73 | return new StreamingTextResponse(stream); 74 | } catch (error) { 75 | console.log("some error happended in chatCompletion", error); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/api/create-chat/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { chats } from "@/lib/db/schema"; 3 | import { loadPdfIntoPinecone } from "@/lib/pinecone"; 4 | // import { loadS3IntoPinecone } from "@/lib/pinecone"; 5 | // import { getS3Url } from "@/lib/s3"; 6 | import { auth } from "@clerk/nextjs"; 7 | import { NextResponse } from "next/server"; 8 | import { utapi } from "uploadthing/server"; 9 | 10 | // /api/create-chat 11 | export async function POST(req: Request, res: Response) { 12 | const { userId } = await auth(); 13 | console.log("userID from server", userId); 14 | if (!userId) { 15 | return NextResponse.json({ error: "unauthorized" }, { status: 401 }); 16 | } 17 | try { 18 | const body = await req.json(); 19 | const { file_key, file_name } = body; 20 | console.log(file_key, file_name); 21 | const pdf = await utapi.getFileUrls(file_key); 22 | console.log("pdf from server==>", pdf); 23 | const file_url = pdf[0].url; 24 | let doc = await loadPdfIntoPinecone(file_key, file_url); 25 | console.log("doc===>", doc); 26 | const chat_id = await db 27 | .insert(chats) 28 | .values({ 29 | fileKey: file_key, 30 | pdfName: file_name, 31 | pdfUrl: file_url, 32 | userId, 33 | }) 34 | .returning({ 35 | insertedId: chats?.id, 36 | }); 37 | console.log("chat==?", chat_id); 38 | return NextResponse.json( 39 | { 40 | chat_id: chat_id[0]?.insertedId, 41 | chat_name: file_name, 42 | }, 43 | { status: 200 } 44 | ); 45 | } catch (error) { 46 | console.error(error); 47 | return NextResponse.json( 48 | { error: "internal server error", msg: error }, 49 | { status: 500 } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/api/get-messages/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { messages } from "@/lib/db/schema"; 3 | import { eq } from "drizzle-orm"; 4 | import { NextResponse } from "next/server"; 5 | 6 | export const runtime = "edge"; 7 | 8 | export const POST = async (req: Request) => { 9 | const { chatId } = await req.json(); 10 | const _messages = await db 11 | .select() 12 | .from(messages) 13 | .where(eq(messages.chatId, chatId)); 14 | return NextResponse.json(_messages); 15 | }; 16 | -------------------------------------------------------------------------------- /src/app/api/uploadthing/core.ts: -------------------------------------------------------------------------------- 1 | import { createUploadthing, type FileRouter } from "uploadthing/next"; 2 | import { currentUser } from "@clerk/nextjs"; 3 | 4 | const f = createUploadthing(); 5 | 6 | // const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function 7 | const getUser = async () => await currentUser(); 8 | 9 | // FileRouter for your app, can contain multiple FileRoutes 10 | export const ourFileRouter = { 11 | // Define as many FileRoutes as you like, each with a unique routeSlug 12 | pdfUploader: f({ 13 | pdf: { maxFileCount: 1, maxFileSize: "8MB" }, 14 | }) 15 | // Set permissions and file types for this FileRoute 16 | .middleware(async ({ req }) => { 17 | // This code runs on your server before upload 18 | const user = await getUser(); 19 | 20 | // If you throw, the user will not be able to upload 21 | if (!user) throw new Error("Unauthorized"); 22 | 23 | // Whatever is returned here is accessible in onUploadComplete as `metadata` 24 | return { userId: user.id }; 25 | }) 26 | .onUploadComplete(async ({ metadata, file }) => { 27 | // This code RUNS ON YOUR SERVER after upload 28 | console.log("Upload complete for userId:", metadata.userId); 29 | 30 | console.log("file url", file.url); 31 | }), 32 | } satisfies FileRouter; 33 | 34 | export type OurFileRouter = typeof ourFileRouter; 35 | -------------------------------------------------------------------------------- /src/app/api/uploadthing/route.ts: -------------------------------------------------------------------------------- 1 | import { createNextRouteHandler } from "uploadthing/next"; 2 | 3 | import { ourFileRouter } from "./core"; 4 | 5 | // Export routes for Next App Router 6 | export const { GET, POST } = createNextRouteHandler({ 7 | router: ourFileRouter, 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/chat/[chatId]/page.tsx: -------------------------------------------------------------------------------- 1 | import ChatComponent from "@/components/ChatComponent"; 2 | import ChatSideBar from "@/components/ChatSideBar"; 3 | import { PanelRightOpen } from "lucide-react"; 4 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/Sheet"; 5 | import { Button } from "@/components/ui/button"; 6 | import PDFViewer from "@/components/PDFViewer"; 7 | import { eq } from "drizzle-orm"; 8 | import { db } from "@/lib/db"; 9 | import { chats } from "@/lib/db/schema"; 10 | // import { checkSubscription } from "@/lib/subscription"; 11 | import { auth } from "@clerk/nextjs"; 12 | // import { eq } from "drizzle-orm"; 13 | import { redirect } from "next/navigation"; 14 | import React from "react"; 15 | 16 | type Props = { 17 | params: { 18 | chatId: string; 19 | }; 20 | }; 21 | 22 | const ChatPage = async ({ params: { chatId } }: Props) => { 23 | const { userId } = await auth(); 24 | if (!userId) { 25 | return redirect("/sign-in"); 26 | } 27 | 28 | const _chats = await db.select().from(chats).where(eq(chats.userId, userId)); 29 | if (!_chats) { 30 | return redirect("/"); 31 | } 32 | if (!_chats.find((chat) => chat.id === parseInt(chatId))) { 33 | return redirect("/"); 34 | } 35 | 36 | const currentChat = _chats.find((chat) => chat.id === parseInt(chatId)); 37 | 38 | // if (!_chats.find((chat) => chat._id === chatId)) { 39 | // return redirect("/"); 40 | // } 41 | 42 | return ( 43 |
44 |
45 | {/* chat sidebar */} 46 |
47 | 48 |
49 | 50 |
51 | 52 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 |

Chat

69 |

70 | {currentChat?.pdfName} 71 |

72 |
73 |
74 | 75 | {/* pdf viewer */} 76 |
77 | 78 |
79 | {/* chat component */} 80 |
81 | 82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | export default ChatPage; 89 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiranism/chat-with-pdf/624664c211d4b6025f872ca8f36c7111e69ad44b/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 | } 77 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ClerkProvider } from "@clerk/nextjs"; 2 | import "./globals.css"; 3 | import type { Metadata } from "next"; 4 | import { Inter } from "next/font/google"; 5 | import "@uploadthing/react/styles.css"; 6 | import Providers from "@/components/Providers"; 7 | import { Toaster } from "@/components/ui/toaster"; 8 | const inter = Inter({ subsets: ["latin"] }); 9 | 10 | export const metadata: Metadata = { 11 | title: "Create Next App", 12 | description: "Generated by create next app", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | return ( 21 | 22 | 23 | 24 | 25 | {children} 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { FileUpload } from "@/components/FileUpload"; 2 | import { Button } from "@/components/ui/button"; 3 | import { utapi } from "uploadthing/server"; 4 | import { UserButton, auth } from "@clerk/nextjs"; 5 | import { ArrowRight, LogIn } from "lucide-react"; 6 | import Link from "next/link"; 7 | import { db } from "@/lib/db"; 8 | import { chats } from "@/lib/db/schema"; 9 | import { eq } from "drizzle-orm"; 10 | 11 | export default async function Home() { 12 | const { userId } = await auth(); 13 | const isAuth = !!userId; 14 | 15 | let firstChat; 16 | if (userId) { 17 | firstChat = await db.select().from(chats).where(eq(chats.userId, userId)); 18 | if (firstChat) { 19 | firstChat = firstChat[0]; 20 | } 21 | } 22 | 23 | return ( 24 |
25 |
26 |
27 |
28 |

29 | Chat With PDF 30 |

31 | 32 |
33 |
34 | {isAuth && firstChat && ( 35 | 36 | 39 | 40 | )} 41 |
42 |

43 | Chat with any PDF. Join millions of students, researchers and 44 | professionals to instantly answer questions and understand research 45 | with AI. 46 |

47 |
48 | {isAuth ? ( 49 | <> 50 | 51 | 52 | ) : ( 53 | 54 | 58 | 59 | )} 60 |
61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ChatComponent.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useRef } from "react"; 3 | import { Input } from "./ui/input"; 4 | import { useChat } from "ai/react"; 5 | import { Button } from "./ui/button"; 6 | import { Send } from "lucide-react"; 7 | import MessageList from "./MessageList"; 8 | import { useQuery } from "@tanstack/react-query"; 9 | import axios from "axios"; 10 | import { Message } from "ai"; 11 | 12 | type Props = { chatId: number }; 13 | 14 | const ChatComponent = ({ chatId }: Props) => { 15 | const { data, isLoading } = useQuery({ 16 | queryKey: ["chat", chatId], 17 | queryFn: async () => { 18 | const response = await axios.post("/api/get-messages", { 19 | chatId, 20 | }); 21 | return response.data; 22 | }, 23 | }); 24 | 25 | const { input, handleInputChange, handleSubmit, messages } = useChat({ 26 | api: "/api/chat", 27 | body: { 28 | chatId, 29 | }, 30 | initialMessages: data || [], 31 | }); 32 | const messageRef = React.useRef(null); 33 | React.useEffect(() => { 34 | if (messageRef.current) { 35 | messageRef.current.scrollTop = messageRef.current.scrollHeight; 36 | } 37 | }, [messages]); 38 | 39 | return ( 40 |
41 | {/* header */} 42 |
43 |

Chat

44 |
45 | 46 | {/* message list */} 47 | 52 |
53 |
57 |
58 | 64 | 67 |
68 |
69 |
70 | ); 71 | }; 72 | 73 | export default ChatComponent; 74 | -------------------------------------------------------------------------------- /src/components/ChatSideBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Link from "next/link"; 3 | import React from "react"; 4 | import { Button } from "./ui/button"; 5 | import { MessageCircle, PlusCircle } from "lucide-react"; 6 | import { cn } from "@/lib/utils"; 7 | import axios from "axios"; 8 | import { DrizzleChat } from "@/lib/db/schema"; 9 | // import SubscriptionButton from "./SubscriptionButton"; 10 | 11 | type Props = { 12 | chats: DrizzleChat[]; 13 | chatId: number; 14 | isPro?: boolean; 15 | }; 16 | 17 | const ChatSideBar = ({ chats, chatId, isPro }: Props) => { 18 | const [loading, setLoading] = React.useState(false); 19 | 20 | return ( 21 |
22 | 23 | 27 | 28 | 29 |
30 | {chats.map((chat) => ( 31 | 32 |
38 | 39 |

40 | {chat.pdfName} 41 |

42 |
43 | 44 | ))} 45 |
46 | 47 | {/*
48 |
49 | Home 50 | Source 51 |
52 | 53 |
*/} 54 |
55 | ); 56 | }; 57 | 58 | export default ChatSideBar; 59 | -------------------------------------------------------------------------------- /src/components/FileUpload.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { UploadDropzone } from "@uploadthing/react"; 3 | 4 | import { OurFileRouter } from "@/app/api/uploadthing/core"; 5 | import axios from "axios"; 6 | import { useRouter } from "next/navigation"; 7 | import { useState } from "react"; 8 | import { useMutation } from "@tanstack/react-query"; 9 | import { useToast } from "./ui/use-toast"; 10 | import { Loader2 } from "lucide-react"; 11 | export const FileUpload = () => { 12 | const router = useRouter(); 13 | const { toast } = useToast(); 14 | const [uploading, setUploading] = useState(false); 15 | const { mutate, isLoading } = useMutation({ 16 | mutationFn: async ({ 17 | file_key, 18 | file_name, 19 | }: { 20 | file_key: string; 21 | file_name: string; 22 | }) => { 23 | const response = await axios.post("/api/create-chat", { 24 | file_key, 25 | file_name, 26 | }); 27 | return response.data; 28 | }, 29 | }); 30 | 31 | return ( 32 | 33 | className="bg-white ut-label:text-lg ut-allowed-content:ut-uploading:text-red-300" 34 | endpoint="pdfUploader" 35 | content={{ 36 | allowedContent({ isUploading }) { 37 | if (isUploading) 38 | return ( 39 | <> 40 |

41 | Spilling Tea to GPT... 42 |

43 | 44 | ); 45 | 46 | if (!isUploading && isLoading) 47 | return ( 48 | <> 49 |

50 | Loading... Almost there! 51 |

52 | 53 | ); 54 | }, 55 | }} 56 | onClientUploadComplete={(res) => { 57 | // Do something with the response 58 | console.log("Files: ", res); 59 | const data = { 60 | file_key: res![0].key, 61 | file_name: res![0].name, 62 | }; 63 | mutate(data, { 64 | onSuccess: ({ chat_id, chat_name }) => { 65 | toast({ 66 | title: "Chat created!", 67 | description: `Chat session created for ${chat_name}`, 68 | }); 69 | router.push(`/chat/${chat_id}`); 70 | }, 71 | onError: (err) => { 72 | toast({ 73 | variant: "destructive", 74 | title: "Error creating chat!", 75 | }); 76 | console.error(err); 77 | }, 78 | }); 79 | }} 80 | onUploadError={(error: Error) => { 81 | toast({ 82 | variant: "destructive", 83 | title: `ERROR! ${error.message}`, 84 | }); 85 | }} 86 | onUploadBegin={(name) => { 87 | // Do something once upload begins 88 | console.log("Uploading: ", name); 89 | }} 90 | /> 91 | ); 92 | }; 93 | -------------------------------------------------------------------------------- /src/components/MessageList.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { Message } from "ai/react"; 3 | import { Loader2 } from "lucide-react"; 4 | import React, { RefObject } from "react"; 5 | 6 | type Props = { 7 | isLoading: boolean; 8 | messages: Message[]; 9 | messageRef: RefObject; 10 | }; 11 | 12 | const MessageList = ({ messages, isLoading, messageRef }: Props) => { 13 | if (isLoading) { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | if (!messages) return <>; 21 | return ( 22 |
26 | {messages.length > 0 && 27 | messages.map((message) => { 28 | return ( 29 |
36 |
44 |

{message.content}

45 |
46 |
47 | ); 48 | })} 49 |
50 | ); 51 | }; 52 | 53 | export default MessageList; 54 | -------------------------------------------------------------------------------- /src/components/PDFViewer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { file_url: string | "" }; 4 | 5 | const PDFViewer = ({ file_url }: Props) => { 6 | console.log("fileUrl", file_url); 7 | return ( 8 | <> 9 | {file_url && ( 10 | 14 | )} 15 | 16 | ); 17 | }; 18 | 19 | export default PDFViewer; 20 | -------------------------------------------------------------------------------- /src/components/Providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; 4 | 5 | type Props = { 6 | children: React.ReactNode; 7 | }; 8 | 9 | const queryClient = new QueryClient(); 10 | 11 | const Providers = ({ children }: Props) => { 12 | return ( 13 | {children} 14 | ); 15 | }; 16 | 17 | export default Providers; 18 | -------------------------------------------------------------------------------- /src/components/ui/Sheet.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SheetPrimitive from "@radix-ui/react-dialog"; 5 | import { cva, type VariantProps } from "class-variance-authority"; 6 | import { X } from "lucide-react"; 7 | 8 | import { cn } from "@/lib/utils"; 9 | 10 | const Sheet = SheetPrimitive.Root; 11 | 12 | const SheetTrigger = SheetPrimitive.Trigger; 13 | 14 | const SheetClose = SheetPrimitive.Close; 15 | 16 | const SheetPortal = ({ 17 | className, 18 | ...props 19 | }: SheetPrimitive.DialogPortalProps) => ( 20 | 21 | ); 22 | SheetPortal.displayName = SheetPrimitive.Portal.displayName; 23 | 24 | const SheetOverlay = React.forwardRef< 25 | React.ElementRef, 26 | React.ComponentPropsWithoutRef 27 | >(({ className, ...props }, ref) => ( 28 | 36 | )); 37 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; 38 | 39 | const sheetVariants = cva( 40 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 41 | { 42 | variants: { 43 | side: { 44 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", 45 | bottom: 46 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", 47 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", 48 | right: 49 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", 50 | }, 51 | }, 52 | defaultVariants: { 53 | side: "right", 54 | }, 55 | } 56 | ); 57 | 58 | interface SheetContentProps 59 | extends React.ComponentPropsWithoutRef, 60 | VariantProps {} 61 | 62 | const SheetContent = React.forwardRef< 63 | React.ElementRef, 64 | SheetContentProps 65 | >(({ side = "right", className, children, ...props }, ref) => ( 66 | 67 | 68 | 73 | {children} 74 | 75 | 76 | Close 77 | 78 | 79 | 80 | )); 81 | SheetContent.displayName = SheetPrimitive.Content.displayName; 82 | 83 | const SheetHeader = ({ 84 | className, 85 | ...props 86 | }: React.HTMLAttributes) => ( 87 |
94 | ); 95 | SheetHeader.displayName = "SheetHeader"; 96 | 97 | const SheetFooter = ({ 98 | className, 99 | ...props 100 | }: React.HTMLAttributes) => ( 101 |
108 | ); 109 | SheetFooter.displayName = "SheetFooter"; 110 | 111 | const SheetTitle = React.forwardRef< 112 | React.ElementRef, 113 | React.ComponentPropsWithoutRef 114 | >(({ className, ...props }, ref) => ( 115 | 120 | )); 121 | SheetTitle.displayName = SheetPrimitive.Title.displayName; 122 | 123 | const SheetDescription = React.forwardRef< 124 | React.ElementRef, 125 | React.ComponentPropsWithoutRef 126 | >(({ className, ...props }, ref) => ( 127 | 132 | )); 133 | SheetDescription.displayName = SheetPrimitive.Description.displayName; 134 | 135 | export { 136 | Sheet, 137 | SheetTrigger, 138 | SheetClose, 139 | SheetContent, 140 | SheetHeader, 141 | SheetFooter, 142 | SheetTitle, 143 | SheetDescription, 144 | }; 145 | -------------------------------------------------------------------------------- /src/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 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 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | } 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /src/components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ToastPrimitives from "@radix-ui/react-toast"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | import { X } from "lucide-react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const ToastProvider = ToastPrimitives.Provider; 9 | 10 | const ToastViewport = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )); 23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName; 24 | 25 | const toastVariants = cva( 26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 27 | { 28 | variants: { 29 | variant: { 30 | default: "border bg-background text-foreground", 31 | destructive: 32 | "destructive group border-destructive bg-destructive text-destructive-foreground", 33 | }, 34 | }, 35 | defaultVariants: { 36 | variant: "default", 37 | }, 38 | } 39 | ); 40 | 41 | const Toast = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef & 44 | VariantProps 45 | >(({ className, variant, ...props }, ref) => { 46 | return ( 47 | 52 | ); 53 | }); 54 | Toast.displayName = ToastPrimitives.Root.displayName; 55 | 56 | const ToastAction = React.forwardRef< 57 | React.ElementRef, 58 | React.ComponentPropsWithoutRef 59 | >(({ className, ...props }, ref) => ( 60 | 68 | )); 69 | ToastAction.displayName = ToastPrimitives.Action.displayName; 70 | 71 | const ToastClose = React.forwardRef< 72 | React.ElementRef, 73 | React.ComponentPropsWithoutRef 74 | >(({ className, ...props }, ref) => ( 75 | 84 | 85 | 86 | )); 87 | ToastClose.displayName = ToastPrimitives.Close.displayName; 88 | 89 | const ToastTitle = React.forwardRef< 90 | React.ElementRef, 91 | React.ComponentPropsWithoutRef 92 | >(({ className, ...props }, ref) => ( 93 | 98 | )); 99 | ToastTitle.displayName = ToastPrimitives.Title.displayName; 100 | 101 | const ToastDescription = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )); 111 | ToastDescription.displayName = ToastPrimitives.Description.displayName; 112 | 113 | type ToastProps = React.ComponentPropsWithoutRef; 114 | 115 | type ToastActionElement = React.ReactElement; 116 | 117 | export { 118 | type ToastProps, 119 | type ToastActionElement, 120 | ToastProvider, 121 | ToastViewport, 122 | Toast, 123 | ToastTitle, 124 | ToastDescription, 125 | ToastClose, 126 | ToastAction, 127 | }; 128 | -------------------------------------------------------------------------------- /src/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/components/ui/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/ui/use-toast.ts: -------------------------------------------------------------------------------- 1 | // Inspired by react-hot-toast library 2 | import * as React from "react" 3 | 4 | import type { 5 | ToastActionElement, 6 | ToastProps, 7 | } from "@/components/ui/toast" 8 | 9 | const TOAST_LIMIT = 1 10 | const TOAST_REMOVE_DELAY = 1000000 11 | 12 | type ToasterToast = ToastProps & { 13 | id: string 14 | title?: React.ReactNode 15 | description?: React.ReactNode 16 | action?: ToastActionElement 17 | } 18 | 19 | const actionTypes = { 20 | ADD_TOAST: "ADD_TOAST", 21 | UPDATE_TOAST: "UPDATE_TOAST", 22 | DISMISS_TOAST: "DISMISS_TOAST", 23 | REMOVE_TOAST: "REMOVE_TOAST", 24 | } as const 25 | 26 | let count = 0 27 | 28 | function genId() { 29 | count = (count + 1) % Number.MAX_VALUE 30 | return count.toString() 31 | } 32 | 33 | type ActionType = typeof actionTypes 34 | 35 | type Action = 36 | | { 37 | type: ActionType["ADD_TOAST"] 38 | toast: ToasterToast 39 | } 40 | | { 41 | type: ActionType["UPDATE_TOAST"] 42 | toast: Partial 43 | } 44 | | { 45 | type: ActionType["DISMISS_TOAST"] 46 | toastId?: ToasterToast["id"] 47 | } 48 | | { 49 | type: ActionType["REMOVE_TOAST"] 50 | toastId?: ToasterToast["id"] 51 | } 52 | 53 | interface State { 54 | toasts: ToasterToast[] 55 | } 56 | 57 | const toastTimeouts = new Map>() 58 | 59 | const addToRemoveQueue = (toastId: string) => { 60 | if (toastTimeouts.has(toastId)) { 61 | return 62 | } 63 | 64 | const timeout = setTimeout(() => { 65 | toastTimeouts.delete(toastId) 66 | dispatch({ 67 | type: "REMOVE_TOAST", 68 | toastId: toastId, 69 | }) 70 | }, TOAST_REMOVE_DELAY) 71 | 72 | toastTimeouts.set(toastId, timeout) 73 | } 74 | 75 | export const reducer = (state: State, action: Action): State => { 76 | switch (action.type) { 77 | case "ADD_TOAST": 78 | return { 79 | ...state, 80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 81 | } 82 | 83 | case "UPDATE_TOAST": 84 | return { 85 | ...state, 86 | toasts: state.toasts.map((t) => 87 | t.id === action.toast.id ? { ...t, ...action.toast } : t 88 | ), 89 | } 90 | 91 | case "DISMISS_TOAST": { 92 | const { toastId } = action 93 | 94 | // ! Side effects ! - This could be extracted into a dismissToast() action, 95 | // but I'll keep it here for simplicity 96 | if (toastId) { 97 | addToRemoveQueue(toastId) 98 | } else { 99 | state.toasts.forEach((toast) => { 100 | addToRemoveQueue(toast.id) 101 | }) 102 | } 103 | 104 | return { 105 | ...state, 106 | toasts: state.toasts.map((t) => 107 | t.id === toastId || toastId === undefined 108 | ? { 109 | ...t, 110 | open: false, 111 | } 112 | : t 113 | ), 114 | } 115 | } 116 | case "REMOVE_TOAST": 117 | if (action.toastId === undefined) { 118 | return { 119 | ...state, 120 | toasts: [], 121 | } 122 | } 123 | return { 124 | ...state, 125 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 126 | } 127 | } 128 | } 129 | 130 | const listeners: Array<(state: State) => void> = [] 131 | 132 | let memoryState: State = { toasts: [] } 133 | 134 | function dispatch(action: Action) { 135 | memoryState = reducer(memoryState, action) 136 | listeners.forEach((listener) => { 137 | listener(memoryState) 138 | }) 139 | } 140 | 141 | type Toast = Omit 142 | 143 | function toast({ ...props }: Toast) { 144 | const id = genId() 145 | 146 | const update = (props: ToasterToast) => 147 | dispatch({ 148 | type: "UPDATE_TOAST", 149 | toast: { ...props, id }, 150 | }) 151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) 152 | 153 | dispatch({ 154 | type: "ADD_TOAST", 155 | toast: { 156 | ...props, 157 | id, 158 | open: true, 159 | onOpenChange: (open) => { 160 | if (!open) dismiss() 161 | }, 162 | }, 163 | }) 164 | 165 | return { 166 | id: id, 167 | dismiss, 168 | update, 169 | } 170 | } 171 | 172 | function useToast() { 173 | const [state, setState] = React.useState(memoryState) 174 | 175 | React.useEffect(() => { 176 | listeners.push(setState) 177 | return () => { 178 | const index = listeners.indexOf(setState) 179 | if (index > -1) { 180 | listeners.splice(index, 1) 181 | } 182 | } 183 | }, [state]) 184 | 185 | return { 186 | ...state, 187 | toast, 188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 189 | } 190 | } 191 | 192 | export { useToast, toast } 193 | -------------------------------------------------------------------------------- /src/db/dbConfig.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | export async function connect() { 4 | try { 5 | mongoose.connect(process.env.MONGO_URI!); 6 | const connection = mongoose.connection; 7 | 8 | connection.on("connected", () => { 9 | console.log("MongoDB connected successfully"); 10 | }); 11 | 12 | connection.on("error", (err) => { 13 | console.log( 14 | "MongoDB connection error. Please make sure MongoDB is running. " + err 15 | ); 16 | process.exit(); 17 | }); 18 | } catch (error) { 19 | console.log("Something goes wrong!"); 20 | console.log(error); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/db/helper/index.ts: -------------------------------------------------------------------------------- 1 | import Chat from "../models/chatModel"; 2 | export interface ChatData { 3 | file_key: string; 4 | file_name: string; 5 | file_url: string; 6 | userId: string; 7 | _id?: string | undefined; 8 | } 9 | export const createChat = async (data: ChatData) => { 10 | return await Chat.create(data); 11 | }; 12 | -------------------------------------------------------------------------------- /src/db/models/chatModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSystemEnumValues = ["system", "user"]; 4 | 5 | const chatSchema = new mongoose.Schema({ 6 | file_name: { 7 | type: String, 8 | required: true, 9 | }, 10 | file_url: { 11 | type: String, 12 | required: true, 13 | }, 14 | createdAt: { 15 | type: Date, 16 | required: true, 17 | default: Date.now, 18 | }, 19 | userId: { 20 | type: String, 21 | required: true, 22 | }, 23 | file_key: { 24 | type: String, 25 | required: true, 26 | }, 27 | }); 28 | 29 | const userSubscriptionSchema = new mongoose.Schema({ 30 | userId: { 31 | type: String, 32 | required: true, 33 | unique: true, 34 | }, 35 | stripeCustomerId: { 36 | type: String, 37 | required: true, 38 | unique: true, 39 | }, 40 | stripeSubscriptionId: { 41 | type: String, 42 | unique: true, 43 | }, 44 | stripePriceId: { 45 | type: String, 46 | }, 47 | stripeCurrentPeriodEnd: { 48 | type: Date, 49 | }, 50 | }); 51 | 52 | 53 | const Chat = mongoose.models.chats || mongoose.model("chats", chatSchema); 54 | 55 | export default Chat; 56 | -------------------------------------------------------------------------------- /src/db/models/messageModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | const userSystemEnumValues = ["system", "user"]; 3 | 4 | const messageSchema = new mongoose.Schema({ 5 | chatId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: "Chat", 8 | required: true, 9 | }, 10 | content: { 11 | type: String, 12 | required: true, 13 | }, 14 | createdAt: { 15 | type: Date, 16 | required: true, 17 | default: Date.now, 18 | }, 19 | role: { 20 | type: String, 21 | enum: userSystemEnumValues, 22 | required: true, 23 | }, 24 | }); 25 | 26 | const Message = 27 | mongoose.models.messages || mongoose.model("messages", messageSchema); 28 | 29 | export default Message; 30 | -------------------------------------------------------------------------------- /src/lib/context.ts: -------------------------------------------------------------------------------- 1 | import { PineconeClient } from "@pinecone-database/pinecone"; 2 | import { convertToAscii } from "./utils"; 3 | import { getEmbeddings } from "./embeddings"; 4 | // import { embeddingTransformer } from "./transformers"; 5 | 6 | export async function getMatchesFromEmbeddings( 7 | embeddings: number[], 8 | fileKey: string 9 | ) { 10 | const pinecone = new PineconeClient(); 11 | await pinecone.init({ 12 | apiKey: process.env.PINECONE_API_KEY!, 13 | environment: process.env.PINECONE_ENVIRONMENT!, 14 | }); 15 | const index = await pinecone.Index("chat-with-pdf"); 16 | try { 17 | const fileKeyWithoutAsci = convertToAscii(fileKey); 18 | // const namespace = index.namespace(namespaceWithoutAscii); 19 | const namespace = convertToAscii(fileKey); 20 | const filter = { fileKey: { $eq: fileKeyWithoutAsci } }; 21 | // const queryResult = await index.query({ 22 | // topK: 5, 23 | // vector: embeddings, 24 | // filter: filter, 25 | // includeMetadata: true, 26 | // }); 27 | 28 | const queryResult = await index.query({ 29 | queryRequest: { 30 | topK: 5, 31 | vector: embeddings, 32 | filter: filter, 33 | includeMetadata: true, 34 | }, 35 | }); 36 | console.log("queryRes=>", queryResult); 37 | return queryResult.matches || []; 38 | } catch (error) { 39 | console.log("error querying embeddings", error); 40 | throw error; 41 | } 42 | } 43 | 44 | export async function getContext(query: string, fileKey: string) { 45 | const queryEmbeddings = await getEmbeddings(query); 46 | console.log("queryEmbeddings", queryEmbeddings); 47 | const matches = await getMatchesFromEmbeddings(queryEmbeddings, fileKey); 48 | console.log("matched", matches); 49 | const qualifyingDocs = matches.filter( 50 | (match) => match.score && match.score > 0.7 51 | ); 52 | 53 | type Metadata = { 54 | text: string; 55 | pageNumber: number; 56 | }; 57 | let docs = qualifyingDocs.map((match) => (match.metadata as Metadata).text); 58 | 59 | console.log("matching docs=>", docs); 60 | return docs.join("\n").substring(0, 3000); 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/db/index.ts: -------------------------------------------------------------------------------- 1 | import { neon, neonConfig } from "@neondatabase/serverless"; 2 | import { drizzle } from "drizzle-orm/neon-http"; 3 | 4 | neonConfig.fetchConnectionCache = true; 5 | 6 | if (!process.env.NEON_DATABASE_URL) { 7 | throw new Error("database url not found"); 8 | } 9 | 10 | const sql = neon(process.env.NEON_DATABASE_URL); 11 | 12 | export const db = drizzle(sql); 13 | -------------------------------------------------------------------------------- /src/lib/db/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | integer, 3 | pgEnum, 4 | pgTable, 5 | serial, 6 | text, 7 | timestamp, 8 | varchar, 9 | } from "drizzle-orm/pg-core"; 10 | 11 | export const userSystemEnum = pgEnum("user_system_enum", ["system", "user"]); 12 | 13 | export const chats = pgTable("chats", { 14 | id: serial("id").primaryKey(), 15 | pdfName: text("pdf_name").notNull(), 16 | pdfUrl: text("pdf_url").notNull(), 17 | createdAt: timestamp("created_at").notNull().defaultNow(), 18 | userId: varchar("user_id", { length: 256 }).notNull(), 19 | fileKey: text("file_key").notNull(), 20 | }); 21 | 22 | export type DrizzleChat = typeof chats.$inferSelect; 23 | 24 | export const messages = pgTable("messages", { 25 | id: serial("id").primaryKey(), 26 | chatId: integer("chat_id") 27 | .references(() => chats.id) 28 | .notNull(), 29 | content: text("content").notNull(), 30 | createdAt: timestamp("created_at").notNull().defaultNow(), 31 | role: userSystemEnum("role").notNull(), 32 | }); 33 | 34 | export const userSubscriptions = pgTable("user_subscriptions", { 35 | id: serial("id").primaryKey(), 36 | userId: varchar("user_id", { length: 256 }).notNull().unique(), 37 | stripeCustomerId: varchar("stripe_customer_id", { length: 256 }) 38 | .notNull() 39 | .unique(), 40 | stripeSubscriptionId: varchar("stripe_subscription_id", { 41 | length: 256, 42 | }).unique(), 43 | stripePriceId: varchar("stripe_price_id", { length: 256 }), 44 | stripeCurrentPeriodEnd: timestamp("stripe_current_period_end"), 45 | }); 46 | 47 | // drizzle-orm 48 | // drizzle-kit 49 | -------------------------------------------------------------------------------- /src/lib/downloadFile.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import fs from "fs"; 3 | import os from "os"; 4 | import path from "path"; 5 | 6 | export async function downloadFromURL(file_url: string) { 7 | try { 8 | // Use axios to download the PDF content 9 | const response = await axios.get(file_url, { 10 | responseType: "arraybuffer", // Ensure it's treated as an array buffer 11 | }); 12 | 13 | // The PDF content is stored in response.data as an array buffer 14 | const pdfContent = response.data; 15 | 16 | // Create a temporary directory using the OS's tmpdir and store the PDF there 17 | // const tempDirectory = process.env.TEMP! || process.env.TMP!; 18 | const tempDirectory = os.tmpdir(); 19 | 20 | const file_name = path.join(tempDirectory, `pdf-${Date.now()}.pdf`); 21 | // const file_name = `/temp/pdf-${Date.now()}.pdf`; 22 | // fs.writeFileSync(file_name, obj.Body as Buffer); 23 | fs.writeFileSync(file_name, pdfContent); 24 | console.log( 25 | "File successfully written to the temporary directory:", 26 | file_name 27 | ); 28 | return file_name; 29 | } catch (error) { 30 | console.error(error); 31 | return null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/embeddings.ts: -------------------------------------------------------------------------------- 1 | import { OpenAIApi, Configuration } from "openai-edge"; 2 | 3 | const config = new Configuration({ 4 | apiKey: process.env.OPENAI_API_KEY, 5 | }); 6 | 7 | const openai = new OpenAIApi(config); 8 | 9 | export async function getEmbeddings(text: string) { 10 | try { 11 | const response = await openai.createEmbedding({ 12 | model: "text-embedding-ada-002", 13 | input: text.replace(/\n/g, " "), 14 | }); 15 | const result = await response.json(); 16 | console.log("getEmb result-=>", result); 17 | return result?.data[0]?.embedding as number[]; 18 | } catch (error) { 19 | console.log("error calling openai embeddings ai", error); 20 | throw error; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/huggingEmbedding.ts: -------------------------------------------------------------------------------- 1 | // import { HfInference } from "@huggingface/inference"; 2 | 3 | // const hf = new HfInference(process.env.HF_TOKEN); 4 | 5 | // export async function hfEmbedding(text: string) { 6 | // try { 7 | // const response = await hf.featureExtraction({ 8 | // model: "BAAI/bge-base-en", 9 | // inputs: { 10 | // inputs: text.replace(/\n/g, " "), 11 | // }, 12 | // }); 13 | // console.log("getEmb result-=>", response); 14 | // return response; 15 | // } catch (error) { 16 | // console.log("error calling openai embeddings ai", error); 17 | // throw error; 18 | // } 19 | // } 20 | -------------------------------------------------------------------------------- /src/lib/pinecone.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PineconeClient, 3 | Vector, 4 | utils as PineconeUtils, 5 | } from "@pinecone-database/pinecone"; 6 | import { PDFLoader } from "langchain/document_loaders/fs/pdf"; 7 | import { 8 | Document, 9 | RecursiveCharacterTextSplitter, 10 | } from "@pinecone-database/doc-splitter"; 11 | import { downloadFromURL } from "./downloadFile"; 12 | import { getEmbeddings } from "./embeddings"; 13 | import md5 from "md5"; 14 | import { convertToAscii } from "./utils"; 15 | // import { embeddingTransformer } from "./transformers"; 16 | 17 | let pinecone: PineconeClient | null = null; 18 | 19 | export const getPineconeClient = async () => { 20 | if (!pinecone) { 21 | pinecone = new PineconeClient(); 22 | await pinecone.init({ 23 | environment: process.env.PINECONE_ENVIRONMENT!, 24 | apiKey: process.env.PINECONE_API_KEY!, 25 | }); 26 | } 27 | return pinecone; 28 | }; 29 | 30 | type PDFPage = { 31 | pageContent: string; 32 | metadata: { 33 | loc: { pageNumber: number }; 34 | }; 35 | }; 36 | 37 | export async function loadPdfIntoPinecone(file_key: string, file_url: string) { 38 | console.log("downloading file from uploadthing into filesystem"); 39 | const file_name = await downloadFromURL(file_url); 40 | if (!file_name) { 41 | throw new Error("could not download from uploadthing"); 42 | } 43 | const loader = new PDFLoader(file_name); 44 | const pages = (await loader.load()) as PDFPage[]; 45 | console.log("pagees=>", pages); 46 | // split and segment the pdf 47 | const documents = await Promise.all(pages.map(prepareDoc)); 48 | console.log("documents", documents); 49 | const fileKeyWithoutAsci = convertToAscii(file_key); 50 | // vectorise and embed individual docs 51 | const vectors = await Promise.all( 52 | documents 53 | .flat() 54 | .slice(0, 3) 55 | .map((doc) => embedDocument(doc, fileKeyWithoutAsci)) 56 | ); 57 | console.log("vectors", vectors); 58 | 59 | // upload the vector to pinecone 60 | const client = await getPineconeClient(); 61 | const pineconeIndex = client.Index("chat-with-pdf"); 62 | 63 | // const namespace = pineconeIndex.namespace(namespaceWithoutAsci); 64 | console.log("inserting vectors into pinecone"); 65 | const namespace = convertToAscii(file_key); 66 | await pineconeIndex.upsert({ 67 | upsertRequest: { 68 | vectors: vectors, 69 | }, 70 | }); 71 | // PineconeUtils.chunkedUpsert(pineconeIndex, vectors, namespace, 10); 72 | // let res = await pineconeIndex.upsert(vectors); 73 | console.log("res from pine==>"); 74 | return documents[0]; 75 | } 76 | 77 | async function embedDocument(doc: Document, file_key: string) { 78 | try { 79 | const embeddings = await getEmbeddings(doc.pageContent); 80 | console.log("embeddings=>", embeddings); 81 | const hash = md5(doc.pageContent); 82 | return { 83 | id: hash, 84 | values: embeddings, 85 | metadata: { 86 | text: doc.metadata.text, 87 | fileKey: file_key, 88 | pageNumber: doc.metadata.pageNumber, 89 | }, 90 | } as Vector; 91 | } catch (error) { 92 | console.log("error embedding document", error); 93 | throw error; 94 | } 95 | } 96 | 97 | export const truncateStringByBytes = (str: string, bytes: number) => { 98 | const enc = new TextEncoder(); 99 | return new TextDecoder("utf-8").decode(enc.encode(str).slice(0, bytes)); 100 | }; 101 | 102 | async function prepareDoc(page: PDFPage) { 103 | let { metadata, pageContent } = page; 104 | pageContent = pageContent.replace(/\n/g, ""); 105 | // split the docs 106 | const splitter = new RecursiveCharacterTextSplitter({ 107 | chunkSize: 36000, 108 | chunkOverlap: 0, 109 | }); 110 | const docs = await splitter.splitDocuments([ 111 | new Document({ 112 | pageContent, 113 | metadata: { 114 | pageNumber: metadata.loc.pageNumber, 115 | text: truncateStringByBytes(pageContent, 36000), 116 | }, 117 | }), 118 | ]); 119 | return docs; 120 | } 121 | 122 | /* 123 | [ 124 | [ 125 | Document { 126 | pageContent: 127 | 'Kiran.SFRONTEND DEVELOPER · BSC COMPUTER SCIENCE · UNIVERSITY OF CALICUT7306405336|imkir4n@gmail.com|kiranfolio.vercel.app|Kiranism|kiranismTechnical SkillsFrontend DevelopmentReact, Next.js, Javascript (ES6+), Typescript, HTML, CSS, Redux, React Router, Material UI, Tailwind CSSBuild Tools and othersJest, Vite, Webpack, Babel, Node, NPM, Express, MongoDB, Git, Github, RESTful APIsWork ExperienceSTACKROOTS TECHNOLOGY SOLUTIONSCyberpark Kozhikode, KeralaFULLSTACK DEVELOPER - MERN [1 YR 3 MOS]Jan. 2022 - Apr. 2023•Collaborated with a team of developers and designers to design, develop, and launch dynamic web applications usingReact js, Nextj s,Node js, meeting project requirements.•Successfullydeployed 3+ projectsto the cloud using Git, ensuringscalability and maintainabilityof the codebase.•Led comprehensive code reviews with the team lead, yielding20% fewer bugsand elevating application stability.•Enhanced user experience with dynamic JavaScript and CSS animations, powered', 128 | metadata: { 129 | pageNumber: 1, 130 | text: 131 | 'Kiran.SFRONTEND DEVELOPER · BSC COMPUTER SCIENCE · UNIVERSITY OF CALICUT7306405336|imkir4n@gmail.com|kiranfolio.vercel.app|Kiranism|kiranismTechnical SkillsFrontend DevelopmentReact, Next.js, Javascript (ES6+), Typescript, HTML, CSS, Redux, React Router, Material UI, Tailwind CSSBuild Tools and othersJest, Vite, Webpack, Babel, Node, NPM, Express, MongoDB, Git, Github, RESTful APIsWork ExperienceSTACKROOTS TECHNOLOGY SOLUTIONSCyberpark Kozhikode, KeralaFULLSTACK DEVELOPER - MERN [1 YR 3 MOS]Jan. 2022 - Apr. 2023•Collaborated with a team of developers and designers to design, develop, and launch dynamic web applications usingReact js, Nextj s,Node js, meeting project requirements.•Successfullydeployed 3+ projectsto the cloud using Git, ensuringscalability and maintainabilityof the codebase.•Led comprehensive code reviews with the team lead, yielding20% fewer bugsand elevating application stability.•Enhanced user experience with dynamic JavaScript and CSS animations, powered byFramer Motion, driving a40% increase in user inter-actionand heightened application engagement.ProjectsXarchView ProjectSAAS-BASED ARCHITECTURAL PROJECT MANAGEMENT ERP SOLUTION, STREAMLINING PROJECT TASKS AND COMMUNICATION.•Implemented secure role-based authenticationfor controlled access, along withConditional UI Renderingbased on user roles.•UtilizedFormik and Yup for seamless form handling, validation, and included file upload usingReact FilePond. Increased form submissionefficiency by20%.•Managed application state efficiently usingRedux Toolkit. Resulting inoptimized rendering and enhanced data flow.•Enabledlive chat functionalityusingSocket.IOfor real-time user interaction. Boosted user engagement by30%.•Applied advancedReact optimization techniques, includingcode splitting, debouncing, for enhanced performance.REACT.JSREDUX TOOLKITMUIREST APIREACT ROUTERNODEEXPRESSGITE-commerceView ProjectONLINE MERCHANDISE STORE•Optimized performance by incorporating lazy loading of product images and content, resulting in a significant33% reduction in initial pageload time.•Leveraged React, Redux, Zustand, and Material UI to architect dynamic frontend components, achieving over70% component reusabilitythrough strategicutilization of MUI components.•ImplementedRazorpay for secure online payments, offering seamless UPI transactions for effortless and convenient user experience.•Maximized the potential of Formik byimplementing comprehensive form validations, resulting inerror-free data submission.REACT.JSREDUXZUSTANDMUIREST APIRAZORPAYNODEEXPRESSGITWallpaperNextView ProjectA WALLPAPER WEBSITE USING NEXT.JS 12 AS A PERSONAL PROJECT•Prepared GROQ queries to retrieve and display data fromSanity.io CMSin web application.•RevampedSEO techniquesand applied keyword optimization strategies, resulting in a 40% increase in website visibility and a 60% boost inorganic traffic within 6 months.•Enhanced website performance through the application ofoptimization techniques, resulting in significantly improvedPageSpeed scoresand faster page load times.NEXT.JSSANITY CMSGROQTAILWIND CSSDAISY UIGITEducationUniversity of CalicutKozhikode,KeralaBSC IN COMPUTER SCIENCE2018 - 2021Certifications2021Certificate of Achievement,Completed Full stack Mern BootcampLearnCodeOnlineAUGUST 9, 2023KIRAN · RÉSUMÉ1', 132 | loc: { lines: { from: 1, to: 1 } } 133 | } 134 | }, 135 | Document { 136 | pageContent: 137 | 'byFramer Motion, driving a40% increase in user inter-actionand heightened application engagement.ProjectsXarchView ProjectSAAS-BASED ARCHITECTURAL PROJECT MANAGEMENT ERP SOLUTION, STREAMLINING PROJECT TASKS AND COMMUNICATION.•Implemented secure role-based authenticationfor controlled access, along withConditional UI Renderingbased on user roles.•UtilizedFormik and Yup for seamless form handling, validation, and included file upload usingReact FilePond. Increased form submissionefficiency by20%.•Managed application state efficiently usingRedux Toolkit. Resulting inoptimized rendering and enhanced data flow.•Enabledlive chat functionalityusingSocket.IOfor real-time user interaction. Boosted user engagement by30%.•Applied advancedReact optimization techniques, includingcode splitting, debouncing, for enhanced performance.REACT.JSREDUX TOOLKITMUIREST APIREACT ROUTERNODEEXPRESSGITE-commerceView ProjectONLINE MERCHANDISE STORE•Optimized performance by incorporating lazy loading of product', 138 | metadata: { 139 | pageNumber: 1, 140 | text: 141 | 'Kiran.SFRONTEND DEVELOPER · BSC COMPUTER SCIENCE · UNIVERSITY OF CALICUT7306405336|imkir4n@gmail.com|kiranfolio.vercel.app|Kiranism|kiranismTechnical SkillsFrontend DevelopmentReact, Next.js, Javascript (ES6+), Typescript, HTML, CSS, Redux, React Router, Material UI, Tailwind CSSBuild Tools and othersJest, Vite, Webpack, Babel, Node, NPM, Express, MongoDB, Git, Github, RESTful APIsWork ExperienceSTACKROOTS TECHNOLOGY SOLUTIONSCyberpark Kozhikode, KeralaFULLSTACK DEVELOPER - MERN [1 YR 3 MOS]Jan. 2022 - Apr. 2023•Collaborated with a team of developers and designers to design, develop, and launch dynamic web applications usingReact js, Nextj s,Node js, meeting project requirements.•Successfullydeployed 3+ projectsto the cloud using Git, ensuringscalability and maintainabilityof the codebase.•Led comprehensive code reviews with the team lead, yielding20% fewer bugsand elevating application stability.•Enhanced user experience with dynamic JavaScript and CSS animations, powered byFramer Motion, driving a40% increase in user inter-actionand heightened application engagement.ProjectsXarchView ProjectSAAS-BASED ARCHITECTURAL PROJECT MANAGEMENT ERP SOLUTION, STREAMLINING PROJECT TASKS AND COMMUNICATION.•Implemented secure role-based authenticationfor controlled access, along withConditional UI Renderingbased on user roles.•UtilizedFormik and Yup for seamless form handling, validation, and included file upload usingReact FilePond. Increased form submissionefficiency by20%.•Managed application state efficiently usingRedux Toolkit. Resulting inoptimized rendering and enhanced data flow.•Enabledlive chat functionalityusingSocket.IOfor real-time user interaction. Boosted user engagement by30%.•Applied advancedReact optimization techniques, includingcode splitting, debouncing, for enhanced performance.REACT.JSREDUX TOOLKITMUIREST APIREACT ROUTERNODEEXPRESSGITE-commerceView ProjectONLINE MERCHANDISE STORE•Optimized performance by incorporating lazy loading of product images and content, resulting in a significant33% reduction in initial pageload time.•Leveraged React, Redux, Zustand, and Material UI to architect dynamic frontend components, achieving over70% component reusabilitythrough strategicutilization of MUI components.•ImplementedRazorpay for secure online payments, offering seamless UPI transactions for effortless and convenient user experience.•Maximized the potential of Formik byimplementing comprehensive form validations, resulting inerror-free data submission.REACT.JSREDUXZUSTANDMUIREST APIRAZORPAYNODEEXPRESSGITWallpaperNextView ProjectA WALLPAPER WEBSITE USING NEXT.JS 12 AS A PERSONAL PROJECT•Prepared GROQ queries to retrieve and display data fromSanity.io CMSin web application.•RevampedSEO techniquesand applied keyword optimization strategies, resulting in a 40% increase in website visibility and a 60% boost inorganic traffic within 6 months.•Enhanced website performance through the application ofoptimization techniques, resulting in significantly improvedPageSpeed scoresand faster page load times.NEXT.JSSANITY CMSGROQTAILWIND CSSDAISY UIGITEducationUniversity of CalicutKozhikode,KeralaBSC IN COMPUTER SCIENCE2018 - 2021Certifications2021Certificate of Achievement,Completed Full stack Mern BootcampLearnCodeOnlineAUGUST 9, 2023KIRAN · RÉSUMÉ1', 142 | loc: { lines: { from: 1, to: 1 } } 143 | } 144 | }, 145 | Document { 146 | pageContent: 147 | 'images and content, resulting in a significant33% reduction in initial pageload time.•Leveraged React, Redux, Zustand, and Material UI to architect dynamic frontend components, achieving over70% component reusabilitythrough strategicutilization of MUI components.•ImplementedRazorpay for secure online payments, offering seamless UPI transactions for effortless and convenient user experience.•Maximized the potential of Formik byimplementing comprehensive form validations, resulting inerror-free data submission.REACT.JSREDUXZUSTANDMUIREST APIRAZORPAYNODEEXPRESSGITWallpaperNextView ProjectA WALLPAPER WEBSITE USING NEXT.JS 12 AS A PERSONAL PROJECT•Prepared GROQ queries to retrieve and display data fromSanity.io CMSin web application.•RevampedSEO techniquesand applied keyword optimization strategies, resulting in a 40% increase in website visibility and a 60% boost inorganic traffic within 6 months.•Enhanced website performance through the application ofoptimization techniques, resulting in', 148 | metadata: { 149 | pageNumber: 1, 150 | text: 151 | 'Kiran.SFRONTEND DEVELOPER · BSC COMPUTER SCIENCE · UNIVERSITY OF CALICUT7306405336|imkir4n@gmail.com|kiranfolio.vercel.app|Kiranism|kiranismTechnical SkillsFrontend DevelopmentReact, Next.js, Javascript (ES6+), Typescript, HTML, CSS, Redux, React Router, Material UI, Tailwind CSSBuild Tools and othersJest, Vite, Webpack, Babel, Node, NPM, Express, MongoDB, Git, Github, RESTful APIsWork ExperienceSTACKROOTS TECHNOLOGY SOLUTIONSCyberpark Kozhikode, KeralaFULLSTACK DEVELOPER - MERN [1 YR 3 MOS]Jan. 2022 - Apr. 2023•Collaborated with a team of developers and designers to design, develop, and launch dynamic web applications usingReact js, Nextj s,Node js, meeting project requirements.•Successfullydeployed 3+ projectsto the cloud using Git, ensuringscalability and maintainabilityof the codebase.•Led comprehensive code reviews with the team lead, yielding20% fewer bugsand elevating application stability.•Enhanced user experience with dynamic JavaScript and CSS animations, powered byFramer Motion, driving a40% increase in user inter-actionand heightened application engagement.ProjectsXarchView ProjectSAAS-BASED ARCHITECTURAL PROJECT MANAGEMENT ERP SOLUTION, STREAMLINING PROJECT TASKS AND COMMUNICATION.•Implemented secure role-based authenticationfor controlled access, along withConditional UI Renderingbased on user roles.•UtilizedFormik and Yup for seamless form handling, validation, and included file upload usingReact FilePond. Increased form submissionefficiency by20%.•Managed application state efficiently usingRedux Toolkit. Resulting inoptimized rendering and enhanced data flow.•Enabledlive chat functionalityusingSocket.IOfor real-time user interaction. Boosted user engagement by30%.•Applied advancedReact optimization techniques, includingcode splitting, debouncing, for enhanced performance.REACT.JSREDUX TOOLKITMUIREST APIREACT ROUTERNODEEXPRESSGITE-commerceView ProjectONLINE MERCHANDISE STORE•Optimized performance by incorporating lazy loading of product images and content, resulting in a significant33% reduction in initial pageload time.•Leveraged React, Redux, Zustand, and Material UI to architect dynamic frontend components, achieving over70% component reusabilitythrough strategicutilization of MUI components.•ImplementedRazorpay for secure online payments, offering seamless UPI transactions for effortless and convenient user experience.•Maximized the potential of Formik byimplementing comprehensive form validations, resulting inerror-free data submission.REACT.JSREDUXZUSTANDMUIREST APIRAZORPAYNODEEXPRESSGITWallpaperNextView ProjectA WALLPAPER WEBSITE USING NEXT.JS 12 AS A PERSONAL PROJECT•Prepared GROQ queries to retrieve and display data fromSanity.io CMSin web application.•RevampedSEO techniquesand applied keyword optimization strategies, resulting in a 40% increase in website visibility and a 60% boost inorganic traffic within 6 months.•Enhanced website performance through the application ofoptimization techniques, resulting in significantly improvedPageSpeed scoresand faster page load times.NEXT.JSSANITY CMSGROQTAILWIND CSSDAISY UIGITEducationUniversity of CalicutKozhikode,KeralaBSC IN COMPUTER SCIENCE2018 - 2021Certifications2021Certificate of Achievement,Completed Full stack Mern BootcampLearnCodeOnlineAUGUST 9, 2023KIRAN · RÉSUMÉ1', 152 | loc: { lines: { from: 1, to: 1 } } 153 | } 154 | }, 155 | Document { 156 | pageContent: 157 | 'significantly improvedPageSpeed scoresand faster page load times.NEXT.JSSANITY CMSGROQTAILWIND CSSDAISY UIGITEducationUniversity of CalicutKozhikode,KeralaBSC IN COMPUTER SCIENCE2018 - 2021Certifications2021Certificate of Achievement,Completed Full stack Mern BootcampLearnCodeOnlineAUGUST 9, 2023KIRAN · RÉSUMÉ1', 158 | metadata: { 159 | pageNumber: 1, 160 | text: 161 | 'Kiran.SFRONTEND DEVELOPER · BSC COMPUTER SCIENCE · UNIVERSITY OF CALICUT7306405336|imkir4n@gmail.com|kiranfolio.vercel.app|Kiranism|kiranismTechnical SkillsFrontend DevelopmentReact, Next.js, Javascript (ES6+), Typescript, HTML, CSS, Redux, React Router, Material UI, Tailwind CSSBuild Tools and othersJest, Vite, Webpack, Babel, Node, NPM, Express, MongoDB, Git, Github, RESTful APIsWork ExperienceSTACKROOTS TECHNOLOGY SOLUTIONSCyberpark Kozhikode, KeralaFULLSTACK DEVELOPER - MERN [1 YR 3 MOS]Jan. 2022 - Apr. 2023•Collaborated with a team of developers and designers to design, develop, and launch dynamic web applications usingReact js, Nextj s,Node js, meeting project requirements.•Successfullydeployed 3+ projectsto the cloud using Git, ensuringscalability and maintainabilityof the codebase.•Led comprehensive code reviews with the team lead, yielding20% fewer bugsand elevating application stability.•Enhanced user experience with dynamic JavaScript and CSS animations, powered byFramer Motion, driving a40% increase in user inter-actionand heightened application engagement.ProjectsXarchView ProjectSAAS-BASED ARCHITECTURAL PROJECT MANAGEMENT ERP SOLUTION, STREAMLINING PROJECT TASKS AND COMMUNICATION.•Implemented secure role-based authenticationfor controlled access, along withConditional UI Renderingbased on user roles.•UtilizedFormik and Yup for seamless form handling, validation, and included file upload usingReact FilePond. Increased form submissionefficiency by20%.•Managed application state efficiently usingRedux Toolkit. Resulting inoptimized rendering and enhanced data flow.•Enabledlive chat functionalityusingSocket.IOfor real-time user interaction. Boosted user engagement by30%.•Applied advancedReact optimization techniques, includingcode splitting, debouncing, for enhanced performance.REACT.JSREDUX TOOLKITMUIREST APIREACT ROUTERNODEEXPRESSGITE-commerceView ProjectONLINE MERCHANDISE STORE•Optimized performance by incorporating lazy loading of product images and content, resulting in a significant33% reduction in initial pageload time.•Leveraged React, Redux, Zustand, and Material UI to architect dynamic frontend components, achieving over70% component reusabilitythrough strategicutilization of MUI components.•ImplementedRazorpay for secure online payments, offering seamless UPI transactions for effortless and convenient user experience.•Maximized the potential of Formik byimplementing comprehensive form validations, resulting inerror-free data submission.REACT.JSREDUXZUSTANDMUIREST APIRAZORPAYNODEEXPRESSGITWallpaperNextView ProjectA WALLPAPER WEBSITE USING NEXT.JS 12 AS A PERSONAL PROJECT•Prepared GROQ queries to retrieve and display data fromSanity.io CMSin web application.•RevampedSEO techniquesand applied keyword optimization strategies, resulting in a 40% increase in website visibility and a 60% boost inorganic traffic within 6 months.•Enhanced website performance through the application ofoptimization techniques, resulting in significantly improvedPageSpeed scoresand faster page load times.NEXT.JSSANITY CMSGROQTAILWIND CSSDAISY UIGITEducationUniversity of CalicutKozhikode,KeralaBSC IN COMPUTER SCIENCE2018 - 2021Certifications2021Certificate of Achievement,Completed Full stack Mern BootcampLearnCodeOnlineAUGUST 9, 2023KIRAN · RÉSUMÉ1', 162 | loc: { lines: { from: 1, to: 1 } } 163 | } 164 | } 165 | ] 166 | ] */ 167 | -------------------------------------------------------------------------------- /src/lib/transformers.ts: -------------------------------------------------------------------------------- 1 | import { pipeline } from "@xenova/transformers"; 2 | 3 | export async function embeddingTransformer(text: string) { 4 | try { 5 | console.log("transformer initialized") 6 | const generateEmbeddings = await pipeline( 7 | "feature-extraction", 8 | "Xenova/e5-large-v2" 9 | ); 10 | const response = await generateEmbeddings(text.replace(/\n/g, " "), { 11 | pooling: "mean", 12 | normalize: true, 13 | }); 14 | console.log("getEmb result-=>", response); 15 | return Array.from(response?.data) as number[]; 16 | } catch (error) { 17 | console.log("error calling transformer for embeddings ai", error); 18 | throw error; 19 | } 20 | } 21 | 22 | // railway 23 | -------------------------------------------------------------------------------- /src/lib/uploadThing.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { generateReactHelpers } from "@uploadthing/react/hooks"; 3 | 4 | import type { OurFileRouter } from "@/app/api/uploadthing/core"; 5 | 6 | export const { useUploadThing, uploadFiles } = 7 | generateReactHelpers(); 8 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | export function convertToAscii(inputString: string) { 9 | // remove non ascii characters 10 | const asciiString = inputString.replace(/[^\x00-\x7F]+/g, ""); 11 | return asciiString; 12 | } 13 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware } from "@clerk/nextjs"; 2 | 3 | // This example protects all routes including api/trpc routes 4 | // Please edit this to allow other routes to be public as needed. 5 | // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your middleware 6 | export default authMiddleware({ 7 | publicRoutes: ["/", "/api/uploadthing", "/api/welcome"], 8 | }); 9 | 10 | export const config = { 11 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], 12 | }; 13 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./pages/**/*.{ts,tsx}", 6 | "./components/**/*.{ts,tsx}", 7 | "./app/**/*.{ts,tsx}", 8 | "./src/**/*.{ts,tsx}", 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | sm: "640px", 17 | lg: "1024px", 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 | }, 71 | animation: { 72 | "accordion-down": "accordion-down 0.2s ease-out", 73 | "accordion-up": "accordion-up 0.2s ease-out", 74 | }, 75 | }, 76 | }, 77 | plugins: [require("tailwindcss-animate"), require("tailwind-scrollbar")], 78 | }; 79 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------