├── .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 |
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 |
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 |
53 |
{truncatedContent}
54 |
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 |
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 |
--------------------------------------------------------------------------------