├── .env.example ├── .env.local.example ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── header.png ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── preve.png ├── prisma ├── migrations │ ├── 20240506163629_init │ │ └── migration.sql │ ├── 20240511132913_added_msg │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── public ├── next.svg ├── preve.png └── vercel.svg ├── src ├── app │ ├── actions │ │ └── sendToServer.ts │ ├── api │ │ ├── chat │ │ │ └── route.ts │ │ ├── clerk │ │ │ └── webhooks │ │ │ │ └── route.ts │ │ ├── message │ │ │ └── route.ts │ │ ├── pdf │ │ │ └── route.ts │ │ └── uploadthing │ │ │ ├── core.ts │ │ │ └── route.ts │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ ├── post │ │ └── [id] │ │ │ └── page.tsx │ ├── projects │ │ └── page.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ └── sign-up │ │ └── [[...sign-up]] │ │ └── page.tsx ├── components │ ├── Home.tsx │ ├── Projects.tsx │ └── tools │ │ ├── InputCom.tsx │ │ └── Navbar.tsx ├── middleware.ts └── utils │ ├── db.ts │ └── uploadthing.ts ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=YOUR_POSTGRES_URL 2 | NEXT_PUBLIC_APIKEY=YOUR_GEMINI_API_KEY 3 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | UPLOADTHING_SECRET= 2 | UPLOADTHING_APP_ID= 3 | WEBHOOK_SECRET= 4 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in 5 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up 6 | CLERK_SECRET_KEY= 7 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= 8 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | .env 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ronit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Say Hello to Preve 2 |

3 | header image 4 | 5 | 6 | ### **Key Features:** 7 | 8 | * **Upload PDFs:** Simply drag and drop your PDF files into Preve to get started. 9 | * **Conversational AI:** Ask Preve questions about your document. 10 | * **Information Retrieval:** Get summaries, key points, and specific details highlighted within the PDF. 11 | * **Clarification and Insights:** Preve can clarify confusing passages and offer insights based on the document's content. 12 | 13 | ### **How to Use Preve:** 14 | 15 | 1. **Upload your PDF:** Drag and drop your file or use the upload button. 16 | 2. **Start Chatting:** Type your question in the chat window. 17 | 3. **Get Answers:** Preve will analyze your document and provide relevant information. 18 | 19 | ### **Preve is perfect for:** 20 | 21 | * Students researching for assignments 22 | * Professionals reviewing contracts and reports 23 | * Anyone who needs to quickly understand the content of a PDF 24 | 25 | 26 | ### **Tech Stack:** 27 | 28 | - Nextjs 29 | - Uploadthing 30 | - Postgres 31 | - Prisma 32 | - Tailwind 33 | - Clerk 34 | - Gemini 35 | 36 | ### **Setting up locally** 37 | 38 | ```bash 39 | git clone https://github.com/ronitrajfr/Preve.git 40 | cd preve 41 | npm install 42 | ``` 43 | 44 | Change `.env.example` to `.env` and `.env.local.example` to `.env.local` , then add the PostgreSQL url (you can get one for free from NeonDB) and clerk & uploadthing keys and grab the Gemini api key. 45 | 46 | And then run : 47 | ```bash 48 | npm run dev 49 | ``` 50 | -------------------------------------------------------------------------------- /header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronitrajfr/Preve/471ec0ab2238d23489703bab409cbd1c5ddf61b0/header.png -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | serverComponentsExternalPackages: ["pdf-text-reader"], 5 | }, 6 | }; 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "presolve", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^5.0.6", 13 | "@google/generative-ai": "^0.11.1", 14 | "@prisma/client": "^5.13.0", 15 | "@uploadthing/react": "^6.5.1", 16 | "axios": "^1.6.8", 17 | "lucide-react": "^0.378.0", 18 | "next": "14.2.3", 19 | "next-auth": "^5.0.0-beta.17", 20 | "pdf-parse": "^1.1.1", 21 | "pdf-parse-fork": "^1.2.0", 22 | "pdf-text-reader": "^4.0.1", 23 | "prisma": "^5.13.0", 24 | "react": "^18", 25 | "react-dom": "^18", 26 | "react-markdown": "^9.0.1", 27 | "svix": "^1.23.0", 28 | "uploadthing": "^6.10.1" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20", 32 | "@types/react": "^18", 33 | "@types/react-dom": "^18", 34 | "eslint": "^8", 35 | "eslint-config-next": "14.2.3", 36 | "postcss": "^8", 37 | "tailwindcss": "^3.4.1", 38 | "typescript": "^5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /preve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronitrajfr/Preve/471ec0ab2238d23489703bab409cbd1c5ddf61b0/preve.png -------------------------------------------------------------------------------- /prisma/migrations/20240506163629_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" TEXT NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "firstName" TEXT NOT NULL, 6 | "lastName" TEXT NOT NULL, 7 | "email" TEXT NOT NULL, 8 | 9 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- CreateTable 13 | CREATE TABLE "Pdf" ( 14 | "id" TEXT NOT NULL, 15 | "createdById" TEXT NOT NULL, 16 | "name" TEXT NOT NULL, 17 | "url" TEXT NOT NULL, 18 | "content" TEXT NOT NULL, 19 | 20 | CONSTRAINT "Pdf_pkey" PRIMARY KEY ("id") 21 | ); 22 | 23 | -- CreateIndex 24 | CREATE UNIQUE INDEX "User_userId_key" ON "User"("userId"); 25 | 26 | -- CreateIndex 27 | CREATE UNIQUE INDEX "Pdf_id_key" ON "Pdf"("id"); 28 | 29 | -- AddForeignKey 30 | ALTER TABLE "Pdf" ADD CONSTRAINT "Pdf_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 31 | -------------------------------------------------------------------------------- /prisma/migrations/20240511132913_added_msg/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Message" ( 3 | "id" TEXT NOT NULL, 4 | "content" TEXT NOT NULL, 5 | "createdById" TEXT NOT NULL, 6 | "chatId" TEXT NOT NULL, 7 | 8 | CONSTRAINT "Message_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "Message_id_key" ON "Message"("id"); 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "Message" ADD CONSTRAINT "Message_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "Message" ADD CONSTRAINT "Message_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "Pdf"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /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 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? 5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init 6 | 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | datasource db { 12 | provider = "postgresql" 13 | url = env("DATABASE_URL") 14 | } 15 | 16 | model User { 17 | id String @id @default(cuid()) 18 | userId String @unique 19 | firstName String 20 | lastName String 21 | email String 22 | pdf Pdf[] 23 | message Message[] 24 | //chats Chat[] 25 | } 26 | 27 | model Pdf { 28 | id String @id @unique @default(cuid()) 29 | createdBy User @relation(fields: [createdById], references: [id]) 30 | createdById String 31 | name String 32 | url String 33 | content String 34 | message Message[] 35 | } 36 | 37 | model Message { 38 | id String @id @unique @default(cuid()) 39 | content String 40 | createdBy User @relation(fields: [createdById], references: [id]) 41 | createdById String 42 | chat Pdf @relation(fields: [chatId], references: [id]) 43 | chatId String 44 | } -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/preve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronitrajfr/Preve/471ec0ab2238d23489703bab409cbd1c5ddf61b0/public/preve.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/actions/sendToServer.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { currentUser } from "@clerk/nextjs/server"; 3 | import prisma from "@/utils/db"; 4 | import { revalidatePath } from "next/cache"; 5 | import { FormEvent } from "react"; 6 | 7 | export default async function sendToServer(e: any) { 8 | // TODO: Implement your action here 9 | const user = await currentUser(); 10 | const userId = user?.publicMetadata.userId; 11 | if (!userId) return; 12 | const content = e.get("inputThing"); 13 | console.log(content); 14 | // const newMessage = await prisma.message.create({ 15 | // data: { 16 | // createdById: userId as string, 17 | // content, 18 | // chatId: pdfId, 19 | // }, 20 | // }); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | export async function POST(req: NextRequest) { 4 | const { id, userId } = await req.json(); 5 | return NextResponse.json({ userId, id }); 6 | } 7 | -------------------------------------------------------------------------------- /src/app/api/clerk/webhooks/route.ts: -------------------------------------------------------------------------------- 1 | import { Webhook } from "svix"; 2 | import { headers } from "next/headers"; 3 | import { WebhookEvent, clerkClient } from "@clerk/nextjs/server"; 4 | import { PrismaClient } from "@prisma/client"; 5 | import { NextResponse } from "next/server"; 6 | 7 | const prisma = new PrismaClient(); 8 | 9 | export async function POST(req: Request) { 10 | // You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook 11 | const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; 12 | 13 | if (!WEBHOOK_SECRET) { 14 | throw new Error( 15 | "Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local" 16 | ); 17 | } 18 | 19 | // Get the headers 20 | const headerPayload = headers(); 21 | const svix_id = headerPayload.get("svix-id"); 22 | const svix_timestamp = headerPayload.get("svix-timestamp"); 23 | const svix_signature = headerPayload.get("svix-signature"); 24 | 25 | // If there are no headers, error out 26 | if (!svix_id || !svix_timestamp || !svix_signature) { 27 | return new Response("Error occurred -- no svix headers", { 28 | status: 400, 29 | }); 30 | } 31 | 32 | // Get the body 33 | const payload = await req.json(); 34 | const body = JSON.stringify(payload); 35 | 36 | // Create a new Svix instance with your secret. 37 | const wh = new Webhook(WEBHOOK_SECRET); 38 | 39 | let evt: WebhookEvent; 40 | 41 | // Verify the payload with the headers 42 | try { 43 | evt = wh.verify(body, { 44 | "svix-id": svix_id, 45 | "svix-timestamp": svix_timestamp, 46 | "svix-signature": svix_signature, 47 | }) as WebhookEvent; 48 | } catch (err) { 49 | console.error("Error verifying webhook:", err); 50 | return new Response("Error occured", { 51 | status: 400, 52 | }); 53 | } 54 | 55 | // Get the ID and type 56 | const { id } = evt.data; 57 | const eventType = evt.type; 58 | 59 | if (eventType === "user.created") { 60 | const { id, first_name, last_name, email_addresses } = evt.data; 61 | 62 | if (first_name == null) return; 63 | if (last_name == null) return; 64 | 65 | const newUser = await prisma.user.create({ 66 | data: { 67 | userId: id, 68 | firstName: first_name, 69 | lastName: last_name, 70 | email: email_addresses[0].email_address, 71 | }, 72 | }); 73 | if (newUser) { 74 | await clerkClient.users.updateUserMetadata(id, { 75 | publicMetadata: { 76 | userId: newUser.id, 77 | }, 78 | }); 79 | } 80 | 81 | return NextResponse.json({ message: "OK", user: newUser }); 82 | } 83 | 84 | console.log(`Webhook with and ID of ${id} and type of ${eventType}`); 85 | console.log("Webhook body:", body); 86 | 87 | return new Response("", { status: 200 }); 88 | } 89 | -------------------------------------------------------------------------------- /src/app/api/message/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/utils/db"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function POST(req: NextRequest) { 5 | // const { chatId, userId, content } = await req.json(); 6 | 7 | // try { 8 | // const newMsg = await prisma.message.create({ 9 | // data: { 10 | // createdById: userId, 11 | // chatId, 12 | // content, 13 | // }, 14 | // }); 15 | return NextResponse.json({ newMsg: "lfalsdlaks" }, { status: 200 }); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/api/pdf/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/utils/db"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { readPdfText } from "pdf-text-reader"; 4 | 5 | export async function POST(req: NextRequest) { 6 | const { name, url, userId } = await req.json(); 7 | 8 | try { 9 | const content = await readPdfText({ 10 | url, 11 | }); 12 | console.log(content); 13 | 14 | if (content === "") { 15 | return NextResponse.json({ msg: "Unable to parse the Pdf" }); 16 | } 17 | 18 | const newPdf = await prisma.pdf.create({ 19 | data: { 20 | url, 21 | name, 22 | content, 23 | createdById: userId, 24 | }, 25 | }); 26 | 27 | return NextResponse.json({ newPdf }, { status: 200 }); 28 | } catch (error) { 29 | return NextResponse.json({ error }, { status: 400 }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/api/uploadthing/core.ts: -------------------------------------------------------------------------------- 1 | import { createUploadthing, type FileRouter } from "uploadthing/next"; 2 | import { UploadThingError } from "uploadthing/server"; 3 | 4 | const f = createUploadthing(); 5 | 6 | const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function 7 | 8 | // FileRouter for your app, can contain multiple FileRoutes 9 | export const ourFileRouter = { 10 | // Define as many FileRoutes as you like, each with a unique routeSlug 11 | imageUploader: f({ pdf: { maxFileSize: "8MB" } }) 12 | // Set permissions and file types for this FileRoute 13 | .middleware(async ({ req }) => { 14 | // This code runs on your server before upload 15 | const user = await auth(req); 16 | 17 | // If you throw, the user will not be able to upload 18 | if (!user) throw new UploadThingError("Unauthorized"); 19 | 20 | // Whatever is returned here is accessible in onUploadComplete as `metadata` 21 | return { userId: user.id }; 22 | }) 23 | .onUploadComplete(async ({ metadata, file }) => { 24 | // This code RUNS ON YOUR SERVER after upload 25 | console.log("Upload complete for userId:", metadata.userId); 26 | 27 | console.log("file url", file.url); 28 | 29 | // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback 30 | return { uploadedBy: metadata.userId }; 31 | }), 32 | } satisfies FileRouter; 33 | 34 | 35 | 36 | export type OurFileRouter = typeof ourFileRouter; 37 | -------------------------------------------------------------------------------- /src/app/api/uploadthing/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandler } from "uploadthing/next"; 2 | 3 | import { ourFileRouter } from "./core"; 4 | 5 | // Export routes for Next App Router 6 | export const { GET, POST } = createRouteHandler({ 7 | router: ourFileRouter, 8 | 9 | // Apply an (optional) custom config: 10 | // config: { ... }, 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | height: 16rem; 7 | z-index: -1; 8 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='27' height='27'%3E%3Crect width='20' height='20' fill='%23fff' /%3E%3Crect x='50%' width='0.5' height='100%' fill='rgba(203, 203, 205, 0.5)' /%3E%3Crect y='50%' width='100%' height='0.5' fill='rgba(203, 213, 225, 0.5)' /%3E%3C/svg%3E%0A"); 9 | } 10 | 11 | .user-message { 12 | background-color: red; 13 | color: white; 14 | padding: 10px; 15 | border-radius: 5px; 16 | margin-bottom: 10px; 17 | } 18 | 19 | .ai-message { 20 | background-color: black; 21 | color: white; 22 | padding: 10px; 23 | border-radius: 5px; 24 | margin-bottom: 10px; 25 | } 26 | 27 | .loading { 28 | color: gray; 29 | text-align: center; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "@uploadthing/react/styles.css"; 3 | import { Inter } from "next/font/google"; 4 | import "./globals.css"; 5 | import { ClerkProvider } from "@clerk/nextjs"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata: Metadata = { 10 | title: "Preve", 11 | description: "One stop solution for Students", 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: Readonly<{ 17 | children: React.ReactNode; 18 | }>) { 19 | return ( 20 | 21 | 22 | 27 | 28 | 29 | 33 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {children} 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Home from "@/components/Home"; 2 | export default function Page() { 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/app/post/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import prisma from "@/utils/db"; 2 | import { Navbar } from "@/components/tools/Navbar"; 3 | import { currentUser } from "@clerk/nextjs/server"; 4 | import { notFound, redirect } from "next/navigation"; 5 | import { InputCom } from "@/components/tools/InputCom"; 6 | import sendToServer from "@/app/actions/sendToServer"; 7 | interface Params { 8 | id: string; 9 | } 10 | 11 | async function getContent(id: string) { 12 | const user = await currentUser(); 13 | const userId = user?.publicMetadata.userId; 14 | if (!userId) return; 15 | 16 | const data = await prisma.pdf.findUnique({ 17 | where: { 18 | id, 19 | createdById: userId, 20 | }, 21 | }); 22 | if (!data) { 23 | notFound(); 24 | } 25 | return data; 26 | } 27 | 28 | async function Page({ params }: { params: Params }) { 29 | // Now you can use `postId` in your component logic 30 | const res = await getContent(params.id); 31 | const pdfId = res?.id; 32 | // console.log(res?.content); 33 | if (params.id != res?.id) { 34 | notFound(); 35 | } 36 | // async function serverFuntion(e: any) { 37 | // "use server"; 38 | // const user = await currentUser(); 39 | // const userId = user?.publicMetadata.userId; 40 | // if (!userId) return; 41 | // const content = e.get("inputThing"); 42 | // console.log(content); 43 | // const newMessage = await prisma.message.create({ 44 | // data: { 45 | // content, 46 | // createdById: userId as string, 47 | // chatId: params.id, 48 | // }, 49 | // }); 50 | // console.log(newMessage); 51 | // } 52 | return ( 53 |
54 | 55 | 56 | 57 |
58 | ); 59 | } 60 | 61 | export default Page; 62 | -------------------------------------------------------------------------------- /src/app/projects/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Projects } from "@/components/Projects"; 3 | 4 | const page = () => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | 12 | export default page; 13 | -------------------------------------------------------------------------------- /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/Home.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import axios from "axios"; 3 | import { redirect, useRouter } from "next/navigation"; 4 | import { useUser } from "@clerk/clerk-react"; 5 | import { UploadButton, UploadDropzone } from "@/utils/uploadthing"; 6 | import { Navbar } from "./tools/Navbar"; 7 | 8 | export default function Home() { 9 | const { user } = useUser(); 10 | const router = useRouter(); 11 | return ( 12 |
13 | 14 | { 32 | // Do something with the response 33 | console.log("Files: ", res); 34 | axios 35 | .post("/api/pdf", { 36 | name: "yo", 37 | url: res[0].url, 38 | userId: user?.publicMetadata.userId, 39 | }) 40 | .then((response) => { 41 | console.log(response?.data.newPdf.id); 42 | 43 | if (response.data.msg) { 44 | alert("Unable to parse the PDF"); 45 | } else { 46 | router.push(`/post/${response.data.newPdf.id}`); 47 | alert("PDF uploaded successfully"); 48 | } 49 | }) 50 | .catch((error) => { 51 | console.log(error); 52 | }); 53 | }} 54 | onUploadError={(error: Error) => { 55 | // Do something with the error. 56 | alert(`ERROR! ${error.message}`); 57 | }} 58 | /> 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Projects.tsx: -------------------------------------------------------------------------------- 1 | import prisma from "@/utils/db"; 2 | import { currentUser } from "@clerk/nextjs/server"; 3 | import React from "react"; 4 | import Link from "next/link"; 5 | import { Navbar } from "./tools/Navbar"; 6 | 7 | async function fetchData() { 8 | const user = await currentUser(); 9 | const userId = user?.publicMetadata.userId; 10 | if (!userId) return; 11 | 12 | const getPdfs = await prisma.pdf.findMany({ 13 | where: { 14 | createdById: userId as string, 15 | }, 16 | }); 17 | 18 | return getPdfs; 19 | } 20 | 21 | export async function Projects() { 22 | const res = await fetchData(); 23 | return ( 24 | <> 25 | 26 |
27 | {res && 28 | res.map((data) => { 29 | const truncatedContent = 30 | data.content && data.content.length > 13 31 | ? data.content.substring(0, 75) + "..." 32 | : data.content; 33 | 34 | return ( 35 |
39 |
40 | 47 | 51 | 52 |
53 |
{truncatedContent}
54 |
55 | 60 | Continue 61 | 76 | 77 |
78 |
79 | ); 80 | })} 81 |
82 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/components/tools/InputCom.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState, useRef, useEffect } from "react"; 3 | import { 4 | GoogleGenerativeAI, 5 | HarmCategory, 6 | HarmBlockThreshold, 7 | } from "@google/generative-ai"; 8 | import Markdown from "react-markdown"; 9 | 10 | const genAI = new GoogleGenerativeAI(process.env.NEXT_PUBLIC_APIKEY || ""); 11 | const model = genAI.getGenerativeModel({ model: "gemini-pro" }); 12 | 13 | interface ConversationTurn { 14 | user: string; 15 | ai: string | null; 16 | } 17 | 18 | interface InputComProps { 19 | content: string; 20 | } 21 | 22 | export function InputCom({ content }: InputComProps): JSX.Element { 23 | const [input, setInput] = useState(""); 24 | const [conversation, setConversation] = useState([]); 25 | const conversationEndRef = useRef(null); 26 | 27 | useEffect(() => { 28 | if (conversationEndRef.current) { 29 | conversationEndRef.current.scrollIntoView({ behavior: "smooth" }); 30 | } 31 | }, [conversation]); 32 | 33 | const generationConfig = { 34 | temperature: 0.9, 35 | topK: 1, 36 | topP: 1, 37 | maxOutputTokens: 2048, 38 | }; 39 | 40 | const safetySettings = [ 41 | { 42 | category: HarmCategory.HARM_CATEGORY_HARASSMENT, 43 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 44 | }, 45 | { 46 | category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, 47 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 48 | }, 49 | { 50 | category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, 51 | threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH, 52 | }, 53 | { 54 | category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, 55 | threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, 56 | }, 57 | ]; 58 | 59 | const generateResponse = async (userInput: string): Promise => { 60 | const parts = [ 61 | { text: `act as a genius and give an answer to this ${content}` }, 62 | { text: `input: ${userInput}` }, 63 | { text: "output: your response" }, 64 | ]; 65 | 66 | const result = await model.generateContent({ 67 | contents: [{ role: "user", parts }], 68 | generationConfig, 69 | safetySettings, 70 | }); 71 | 72 | const responseText: string = 73 | result.response?.candidates?.[0]?.content?.parts?.[0]?.text || ""; 74 | return responseText; 75 | }; 76 | 77 | const handleSend = async (): Promise => { 78 | const newConversation: ConversationTurn[] = [ 79 | ...conversation, 80 | { user: input, ai: null }, 81 | ]; // Add a placeholder for the AI response 82 | setConversation(newConversation); 83 | 84 | const response: string = await generateResponse(input); 85 | const updatedConversation: ConversationTurn[] = [...newConversation]; 86 | updatedConversation[newConversation.length - 1].ai = response; 87 | setConversation(updatedConversation); 88 | 89 | setInput(""); // Clear input after sending 90 | }; 91 | async function handleKeyDown(e: any) { 92 | if (e.key === "Enter") { 93 | const newConversation: ConversationTurn[] = [ 94 | ...conversation, 95 | { user: input, ai: null }, 96 | ]; // Add a placeholder for the AI response 97 | setConversation(newConversation); 98 | 99 | const response: string = await generateResponse(input); 100 | const updatedConversation: ConversationTurn[] = [...newConversation]; 101 | updatedConversation[newConversation.length - 1].ai = response; 102 | setConversation(updatedConversation); 103 | 104 | setInput(""); 105 | } 106 | } 107 | return ( 108 |
109 |
110 |
111 | {conversation.map((item, index) => ( 112 |
113 | {(index !== 0 || item.user !== "") && ( 114 |

115 | {" "} 116 | {item.user} 117 |

118 | )} 119 | {item.ai !== null && ( 120 |

121 | {" "} 122 | {item.ai} 123 |

124 | )} 125 | {index === conversation.length - 1 && item.ai === null && ( 126 |

Loading...

127 | )} 128 |
129 | ))} 130 |
131 |
132 |
133 |
134 |
135 | setInput(e.target.value)} 140 | placeholder="Start Chatting..." 141 | className="shadow bg-gray-50 border-2 border-orange-400 text-black text-sm rounded-lg focus:border-orange-500 p-2.5 w-[300px] sm:w-[450px] md:w-[750px]" 142 | style={{ marginBottom: "10px" }} 143 | /> 144 | 150 |
151 |
152 |
153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /src/components/tools/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | import { useUser } from "@clerk/clerk-react"; 5 | import axios from "axios"; 6 | 7 | export const Navbar = () => { 8 | // const { user } = useUser(); 9 | // axios.post("http://localhost:3000/api/chat", { 10 | // id 11 | // }) 12 | return ( 13 |
14 |
15 |
16 | 17 | logo 18 | 19 |
20 | 21 | 51 |
52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; 2 | 3 | const isProtectedRoute = createRouteMatcher(["/"]); 4 | 5 | export default clerkMiddleware((auth, req) => { 6 | if (isProtectedRoute(req)) auth().protect(); 7 | }); 8 | 9 | export const config = { 10 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient(); 5 | }; 6 | 7 | declare const globalThis: { 8 | prismaGlobal: ReturnType; 9 | } & typeof global; 10 | 11 | const prisma = globalThis.prismaGlobal ?? prismaClientSingleton(); 12 | 13 | export default prisma; 14 | 15 | if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma; 16 | -------------------------------------------------------------------------------- /src/utils/uploadthing.ts: -------------------------------------------------------------------------------- 1 | import { 2 | generateUploadButton, 3 | generateUploadDropzone, 4 | } from "@uploadthing/react"; 5 | 6 | import type { OurFileRouter } from "@/app/api/uploadthing/core"; 7 | 8 | export const UploadButton = generateUploadButton(); 9 | export const UploadDropzone = generateUploadDropzone(); 10 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { withUt } from "uploadthing/tw"; 2 | 3 | export default withUt({ 4 | // Your existing Tailwind config 5 | content: ["./src/**/*.{ts,tsx,mdx}"], 6 | }); 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------