├── .editorconfig
├── .env.example
├── .eslintignore
├── .eslintrc.json
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app
├── api
│ ├── chat
│ │ └── route.ts
│ ├── documents
│ │ ├── [id]
│ │ │ └── route.ts
│ │ └── route.ts
│ └── sources
│ │ └── route.ts
├── credentials
│ └── page.tsx
├── documents
│ ├── [id]
│ │ └── page.tsx
│ └── page.tsx
├── layout.tsx
└── page.tsx
├── components
├── documents
│ ├── jumpToPagePlugin.tsx
│ └── table.tsx
├── icons.tsx
├── layout.tsx
├── main-nav.tsx
├── site-header.tsx
├── tailwind-indicator.tsx
├── theme-provider.tsx
├── theme-toggle.tsx
└── ui
│ ├── Spinner.tsx
│ ├── accordion.tsx
│ ├── alert.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── table.tsx
│ ├── toast.tsx
│ └── toaster.tsx
├── config
├── pinecone.ts
└── site.ts
├── context
└── credentials-context.tsx
├── hooks
└── use-toast.ts
├── lib
├── chat.ts
├── fonts.ts
├── langchain
│ ├── chain.ts
│ ├── model.ts
│ ├── schema.ts
│ └── vectorstores
│ │ └── pinecone.ts
├── pdf.ts
├── pinecone.ts
├── prisma.ts
├── supabase.ts
└── utils.ts
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── prettier.config.js
├── prisma
└── schema.prisma
├── public
├── bot-image.png
├── favicon.ico
├── next.svg
├── thirteen.svg
├── usericon.png
└── vercel.svg
├── scripts
└── vc-push.sh
├── styles
├── Home.module.css
├── chrome-bug.css
└── globals.css
├── tailwind.config.js
├── test
└── data
│ ├── 01-valid.pdf
│ ├── 02-valid.pdf
│ ├── 03-invalid.pdf
│ ├── 04-valid.pdf
│ ├── 05-versions-space.pdf
│ └── 05-versions-space.pdf.txt
├── tsconfig.json
├── tsconfig.tsbuildinfo
├── types
└── nav.ts
└── utils
├── cn.ts
└── pinecone-client.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | SUPABASE_KEY=
2 | SUPABASE_URL=
3 | SUPABASE_BUCKET=
4 | DIRECT_URL=
5 | DATABASE_URL=
6 | OPENAI_API_KEY=
7 | PINECONE_API_KEY=
8 | PINECONE_ENVIRONMENT=
9 | PINECONE_INDEX_NAME=
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | .cache
3 | public
4 | node_modules
5 | *.esm.js
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/eslintrc",
3 | "root": true,
4 | "extends": [
5 | "next/core-web-vitals",
6 | "prettier",
7 | "plugin:tailwindcss/recommended"
8 | ],
9 | "plugins": ["tailwindcss"],
10 | "rules": {
11 | "@next/next/no-html-link-for-pages": "off",
12 | "react/jsx-key": "off",
13 | "react-hooks/rules-of-hooks": "off",
14 | "tailwindcss/no-custom-classname": "off"
15 | },
16 | "settings": {
17 | "tailwindcss": {
18 | "callees": ["cn"],
19 | "config": "./tailwind.config.js"
20 | },
21 | "next": {
22 | "rootDir": ["./"]
23 | }
24 | },
25 | "overrides": [
26 | {
27 | "files": ["*.ts", "*.tsx"],
28 | "parser": "@typescript-eslint/parser"
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [anis-marrouchi]
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 | build
15 |
16 | # misc
17 | .DS_Store
18 | *.pem
19 |
20 | # debug
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | .pnpm-debug.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 | .env
32 | # turbo
33 | .turbo
34 |
35 | .contentlayer
36 | .env
37 | .vercel
38 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | strict-peer-dependencies=false
2 | public-hoist-pattern[]=*prisma*
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | cache
2 | .cache
3 | package.json
4 | package-lock.json
5 | public
6 | CHANGELOG.md
7 | .yarn
8 | dist
9 | node_modules
10 | .next
11 | build
12 | .contentlayer
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true,
4 | "WillLuke.nextjs.addTypesOnSave": true,
5 | "WillLuke.nextjs.hasPrompted": true
6 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct for AI Prompt Builder
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open, welcoming, and inclusive environment, we, as contributors and maintainers, pledge to make participation in our project and our community a harassment-free experience for everyone.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contribute to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting or derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies to all project spaces, including the repository, issue tracker, and any other communication channels used by the project. It also applies when representing the project or its community in public spaces or events.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at anismarrouchi@hotmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html.
44 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to AI CONTENT
2 |
3 | Thank you for your interest in contributing to AI Content! We appreciate your efforts and welcome any improvements to the project. This document provides guidelines and instructions for contributing to the project.
4 |
5 | ## Code of Conduct
6 |
7 | Before you start contributing, please read and adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md). We expect all contributors to follow these guidelines to ensure a positive and inclusive environment.
8 |
9 | ## How to Contribute
10 |
11 | 1. **Fork the repository:** Create a fork of the AI Prompt Builder repository on your own GitHub account.
12 |
13 | 2. **Clone the forked repository:** Clone the forked repository to your local machine.
14 |
15 | ```
16 | git clone https://github.com/YOUR_USERNAME/ai-prompt-builder.git
17 | ```
18 |
19 |
20 | 3. **Create a new branch:** Create a new branch for your changes. Use a descriptive name that reflects the changes you're making.
21 |
22 | ```
23 | git checkout -b my-feature-branch
24 | ```
25 |
26 |
27 | 4. **Make your changes:** Implement the changes, enhancements, or bug fixes you want to contribute to the project.
28 |
29 | 5. **Commit your changes:** Use clear and concise commit messages that describe the changes you've made.
30 |
31 | ```
32 | git add .
33 | git commit -m "Add a brief description of your changes"
34 | ```
35 |
36 | 6. **Pull the latest changes from the main repository:** Before submitting a pull request, ensure that your branch is up-to-date with the latest changes in the main repository.
37 |
38 | ```
39 | git pull origin main
40 |
41 | ```
42 |
43 | 7. **Push your changes to your forked repository:** Push your changes to your fork on GitHub.
44 |
45 | ```
46 | git push origin my-feature-branch
47 |
48 | ```
49 |
50 | 8. **Create a pull request:** Open a pull request from your forked repository to the main AI Prompt Builder repository. Provide a clear description of your changes and any additional context that might be helpful for reviewers.
51 |
52 | ## Coding Style
53 |
54 | - Follow the established coding style and best practices for the programming languages and frameworks used in the project.
55 | - Keep the code clean, modular, and well-documented.
56 | - Include comments where necessary to clarify complex or non-intuitive code.
57 |
58 | ## Reporting Bugs and Requesting Features
59 |
60 | Please use the GitHub issue tracker to report bugs or request new features. Provide as much information as possible, including steps to reproduce the issue and any relevant error messages or screenshots.
61 |
62 | ## Questions or Support
63 |
64 | If you have any questions or need support, please reach out to the maintainers via the project's communication channels (e.g., email, Slack, etc.).
65 |
66 | Thank you for your contributions, and we look forward to improving AI Prompt Builder together!
67 |
68 |
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Anis Marrouchi
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 |
2 | # ChatPDF-GPT
3 |
4 | ## Introduction
5 |
6 | ChatPDF-GPT is an innovative project that harnesses the power of the [LangChain framework](https://js.langchain.com/docs/), a transformative tool for developing applications powered by language models. This unique application uses LangChain to offer a chat interface that communicates with PDF documents, driven by the capabilities of [OpenAI's language models](https://platform.openai.com/docs/introduction).
7 |
8 | In this project, the language model is connected to other data sources and allows interaction with its environment, thus embodying the principles of the LangChain framework. Users can upload a PDF document, which is then processed and saved in [Pinecone](https://www.pinecone.io/), a vector database, and [Supabase storage](https://supabase.com/). Users can then chat with the uploaded PDF, with the AI utilizing the content of the document to engage in a meaningful conversation.
9 |
10 | The project relies on the [Next.js](https://nextjs.org/) framework, a leading choice for creating robust, full-stack Web applications. The UI components are beautifully crafted using the [Radix UI library](https://www.radix-ui.com/) and styled with Tailwind CSS, based on the elegant template provided by [shadcn/ui](https://github.com/shadcn/ui).
11 |
12 | ## Features
13 |
14 | 1. **Upload a PDF**: Users can upload a PDF document, which is then stored in Pinecone vector database and Supabase storage.
15 | 2. **Chat with PDF**: The application processes the content of the PDF and allows users to engage in a conversation with the document using OpenAI API.
16 | 3. **PDF Preview**: Users can preview the actual PDF document they are interacting with, using the powerful React component package, [@react-pdf-viewer](https://react-pdf-viewer.dev/).
17 | 4. **List PDFs**: The application provides a list of all uploaded PDF documents stored in the Supabase database.
18 | 5. **Delete a PDF**: Users have the ability to remove a document from the database.
19 | 6. **Cite Sources**: The AI chat interface provides sources from the PDF for every reply, allowing users to navigate directly to the reference in the PDF.
20 |
21 | ## Usage Examples
22 |
23 | ChatPDF-GPT is equipped with examples that illustrate various operations such as:
24 |
25 | 1. Interacting with Pinecone: saving embeddings, deleting a record.
26 | 2. Uploading a file to Supabase storage and also deleting a file.
27 | 3. Listing available documents from Supabase database.
28 | 4. Previewing a PDF using the @react-pdf-viewer.
29 | 5. Navigating directly to the source of an AI reply in the PDF.
30 |
31 | ## Quick Testing Using the Demo
32 |
33 |
34 | To test the functionality of this project using the demo, you will need to provide your own credentials for OpenAI, Supabase, and Pinecone. For Supabase, you can follow the step-by-step guide provided below to setup and retrieve the necessary credentials. For acquiring credentials for OpenAI and Pinecone, please consult the corresponding documentation as a step-by-step guide may not be available. Always ensure you are following the latest instructions provided by the respective services.
35 | ### 1. OpenAI
36 | [OpenAI](https://platform.openai.com/docs/guides/authentication)
37 | ### 2. Supabase
38 | 1. **Creating a New Project in Supabase**:
39 |
40 | - Open your web browser and navigate to [app.supabase.io](https://app.supabase.io/).
41 | - Click on "New project".
42 | - Enter your project details and wait for the new PostgreSQL database to launch. It may take a few minutes for the process to complete.
43 | 2. **Retrieving the Database Connection URL**:
44 |
45 | - Once your database is successfully created, navigate to your project's page.
46 | - Go to the "Settings" section.
47 | - Click the "Database" tab in the sidebar.
48 | - Scroll down to the bottom of the page and look for the "Connection string" section.
49 | - Choose "Nodejs" and copy the URL.
50 |
51 | This connection string will be used for the `DATABASE_URL` environment variable in your application.
52 |
53 | 3. **Retrieving the Connection Pooling Connection String**:
54 | - Still in the "Database" tab of the "Settings" section, scroll to find the "Connection Pooling" section.
55 | - Choose "Nodejs" and copy the Connection Pooling URL.
56 |
57 | This URL will be used for the `DIRECT_URL` environment variable in your application.
58 |
59 | 4. **Retrieving Storage Keys**:
60 | - From your project's page, go to the "Settings" section.
61 | - Click the "API" tab in the sidebar.
62 | - Here, you will find your `SUPABASE_URL` and `SUPABASE_KEY`. Copy these values.
63 |
64 | The `SUPABASE_URL` is the URL for your project, while `SUPABASE_KEY` is the public anonymous key for your project.
65 |
66 | 5. **Setting up the Supabase Bucket**:
67 |
68 | - From your project's page, go to the "Storage" section.
69 | - Here, create a new bucket or use an existing one. The name of the bucket will be used as `SUPABASE_BUCKET` in your application.
70 | 6. **Setting Up Environment Variables in Your Application**:
71 |
72 | - Now, navigate to your project where you are using Supabase.
73 | - Add the following environment variables with the values you copied from the Supabase console:
74 | - `DATABASE_URL`
75 | - `DIRECT_URL`
76 | - `SUPABASE_KEY`
77 | - `SUPABASE_URL`
78 | - `SUPABASE_BUCKET`
79 |
80 | These keys will allow your application to interact with the Supabase services.
81 |
82 |
83 | 7. **Managing Storage Bucket Policies**:
84 | - Navigate to the "Storage" section from your project's page.
85 | - Click on the bucket for which you want to manage policies.
86 | - Click on the "Policies" tab to view the existing policies for the bucket.
87 | - To create a new policy, click the "Create policy" button and fill in the policy details as per your requirement.
88 | - To edit an existing policy, click on the policy in the list and make the necessary changes.
89 |
90 | Please note that while it's possible to set a policy that makes your storage bucket publicly accessible, you should do this with caution. Making your bucket publicly accessible means that anyone with the URL to an object can access it. This might be useful for testing, but for production applications, you should consider more restrictive policies to ensure the security of your data. Always consult the Supabase documentation or a security expert to understand the implications of different policies.
91 |
92 | With this, you should be able to set up Supabase for your project and manage storage policies as per your requirements.
93 |
94 | ### 3. Pincone
95 | [Pinecone](https://www.pinecone.io/docs/)
96 |
97 | ## Setup and Installation
98 |
99 | To set up and run ChatPDF-GPT on your local machine, follow the steps below:
100 |
101 | 1. Clone the project repository:
102 |
103 | ```
104 | git clone https://github.com/anis-marrouchi/chatpdf-gpt.git
105 | ```
106 |
107 | 2. Navigate into the project directory and install the dependencies using [pnpm](https://pnpm.io/):
108 |
109 | ```
110 | cd chatpdf-gpt
111 | pnpm install
112 | ```
113 |
114 | 3. Create a `.env` file in the root directory and fill in your credentials (OpenAI, Pinecone, Supabase) as indicated in the `.env.example` file.
115 |
116 | 4. Create the database schema using Prisma. You must make you have run the prisma generate command `prisma generate`
117 |
118 | ```
119 | npx prisma migrate dev --name init
120 | ```
121 |
122 | 5. Start the server:
123 |
124 | ```
125 | npm run dev
126 | ```
127 |
128 | ## Contribution
129 |
130 | ChatPDF-GPT is an open-source project and we warmly welcome contributions from everyone. Please read our [contributing guide](https://chat.openai.com/CONTRIBUTING.md) for more details on how to get started.
131 |
132 | ## Credits
133 |
134 |
135 | This project stands on the shoulders of giants. Our work would not be possible without the vast array of libraries, frameworks, and tools that the open source community has produced. Specifically, we would like to express our appreciation to:
136 |
137 | 1. The [LangChain](https://js.langchain.com/docs/) team for their groundbreaking framework for applications powered by language models.
138 |
139 | 2. [OpenAI](https://openai.com/) for their state-of-the-art language models, which make the chat functionality possible.
140 |
141 | 3. [Supabase](https://supabase.com/) for their open-source Firebase alternative which we used to build secure and performant backends.
142 |
143 | 4. [Pinecone](https://www.pinecone.io/) for their vector database that allows easy and efficient storage and retrieval of vector embeddings.
144 |
145 | 5. [Next.js](https://nextjs.org/) and [Vercel](https://vercel.com/) for their comprehensive framework which allowed us to build this full-stack Web application with ease.
146 |
147 | 6. [shadcn](https://github.com/shadcn) for their elegant UI components which we built upon to create a beautiful and user-friendly interface.
148 |
149 | 7. [Radix UI](https://www.radix-ui.com/) for their robust, accessible and customizable component library that forms the backbone of our UI.
150 |
151 | 8. [@react-pdf-viewer](https://react-pdf-viewer.dev/) for their powerful React component, which lets users preview the actual PDF document they are interacting with.
152 |
153 |
154 | And all the other dependencies, both listed and not listed, that contributed to the realization of this project. Our contribution is modest in comparison to their collective effort.
155 |
156 | ## License
157 |
158 | ChatPDF-GPT is open-source software licensed under the [MIT license](https://chat.openai.com/LICENSE.md).
--------------------------------------------------------------------------------
/app/api/chat/route.ts:
--------------------------------------------------------------------------------
1 | import { getChain } from "@/lib/langchain/chain"
2 | import { ModelHandler } from "@/lib/langchain/model"
3 | import { getPineconeStore } from "@/lib/langchain/vectorstores/pinecone"
4 | import { NextRequest, NextResponse } from "next/server"
5 |
6 | export const runtime = "edge"
7 |
8 | export async function POST(request: NextRequest) {
9 | const body = await request.json()
10 | // Get credentials from cookies
11 | const credentials = JSON.parse(
12 | request.cookies.get("credentials")?.value || null
13 | )
14 | if (
15 | !credentials ||
16 | !credentials.pineconeIndex ||
17 | !credentials.pineconeEnvironment ||
18 | !credentials.pineconeApiKey
19 | ) {
20 | return NextResponse.redirect("/credentials")
21 | }
22 |
23 | const { prompt, messages: history, id } = body
24 | // OpenAI recommends replacing newlines with spaces for best results
25 | const sanitizedQuestion = `${prompt.trim().replaceAll("\n", " ")}`
26 |
27 | try {
28 | const stream = new TransformStream()
29 | const writer = stream.writable.getWriter()
30 |
31 | const vectorStore = await getPineconeStore(credentials, id)
32 |
33 | const modelHandler = new ModelHandler(writer)
34 | const model = modelHandler.getModel(credentials.openaiApiKey)
35 |
36 | const response = getChain(model, vectorStore, sanitizedQuestion, history)
37 |
38 | return new NextResponse(stream.readable, {
39 | headers: {
40 | "Content-Type": "text/event-stream",
41 | },
42 | })
43 | } catch (error: any) {
44 | console.log("error", error)
45 | return NextResponse.json(
46 | { error: error.message || "Something went wrong" },
47 | { status: 500 }
48 | )
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/api/documents/[id]/route.ts:
--------------------------------------------------------------------------------
1 | // Import the generated Prisma client
2 |
3 | import { NextRequest, NextResponse } from "next/server"
4 | import { initPinecone } from "@/utils/pinecone-client"
5 | import { PrismaClient } from "@prisma/client"
6 |
7 | import { createPrisma } from "@/lib/prisma"
8 | import { supabaseClient } from "@/lib/supabase"
9 |
10 | // @ts-ignore
11 | export async function GET(request: NextRequest, { params: { id } }) {
12 | // Get credentials from cookies
13 | const credentials = JSON.parse(
14 | request.cookies.get("credentials")?.value || null
15 | )
16 | if (!credentials) {
17 | return NextResponse.redirect("/credentials")
18 | }
19 | // refactor this
20 | const { supabaseDatabaseUrl } = credentials
21 | const prisma = new PrismaClient({
22 | datasources: {
23 | db: {
24 | url: supabaseDatabaseUrl,
25 | },
26 | },
27 | })
28 | const data = await prisma.documents.findFirst({
29 | where: {
30 | id,
31 | },
32 | })
33 |
34 | return NextResponse.json({ data })
35 | }
36 |
37 | // delete document and pinecone namespace for document. namespace is the same as the document id
38 | // @ts-ignore
39 | export async function DELETE(request: NextRequest, { params: { id } }) {
40 | // Get credentials from cookies
41 | const credentials = JSON.parse(
42 | request.cookies.get("credentials")?.value || null
43 | )
44 | if (!credentials) {
45 | return NextResponse.redirect("/credentials")
46 | }
47 |
48 | const {
49 | supabaseDatabaseUrl,
50 | pineconeEnvironment,
51 | pineconeApiKey,
52 | pineconeIndex,
53 | supabaseUrl,
54 | supabaseKey,
55 | supabaseBucket,
56 | } = credentials
57 | const prisma = createPrisma({ url: supabaseDatabaseUrl })
58 | const pinecone = await initPinecone(pineconeEnvironment, pineconeApiKey)
59 |
60 | const document = await prisma.documents.delete({
61 | where: {
62 | id,
63 | },
64 | })
65 | console.log("document", document)
66 | // delete pinecone namespace
67 | const index = pinecone.Index(pineconeIndex)
68 | await index.delete1({ deleteAll: true, namespace: id })
69 | // delete supabase storage file
70 | const supabase = supabaseClient(supabaseUrl, supabaseKey)
71 | const { data, error } = await supabase.storage
72 | .from(supabaseBucket)
73 | .remove([document.url])
74 |
75 | if (error) {
76 | console.log(error)
77 | return NextResponse.json(
78 | { error: error.message || "Something went wrong" },
79 | { status: 500 }
80 | )
81 | }
82 | return NextResponse.json({ message: "Document deleted" })
83 | }
84 |
--------------------------------------------------------------------------------
/app/api/documents/route.ts:
--------------------------------------------------------------------------------
1 | // Import the generated Prisma client
2 |
3 | import fs from "fs"
4 | import { NextRequest, NextResponse } from "next/server"
5 | import { initPinecone } from "@/utils/pinecone-client"
6 | import { createClient } from "@supabase/supabase-js"
7 | import axios from "axios"
8 | import { PDFLoader } from "langchain/document_loaders/fs/pdf"
9 | import { OpenAIEmbeddings } from "langchain/embeddings/openai"
10 | import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
11 | import { PineconeStore } from "langchain/vectorstores/pinecone"
12 | import { createPrisma } from "@/lib/prisma"
13 |
14 | export async function POST(request: Request) {
15 | const body = await request.json()
16 | const {
17 | openaiApiKey,
18 | supabaseBucket,
19 | pineconeEnvironment,
20 | supabaseUrl,
21 | supabaseKey,
22 | pineconeIndex,
23 | pineconeApiKey,
24 | supabaseDatabaseUrl,
25 | } = body
26 | const prisma = createPrisma({ url: supabaseDatabaseUrl })
27 | const supabase = createClient(supabaseUrl, supabaseKey)
28 | const pinecone = await initPinecone(pineconeEnvironment, pineconeApiKey)
29 | const data = await prisma.documents.create({
30 | data: {
31 | url: body?.url,
32 | // @ts-ignore
33 | name: body?.name,
34 | },
35 | })
36 | const {
37 | data: { publicUrl },
38 | }: any = supabase.storage.from(supabaseBucket).getPublicUrl(body.url)
39 | const res = await axios.get(publicUrl, { responseType: "arraybuffer" })
40 |
41 | // Write the PDF to a temporary file. This is necessary because the PDFLoader
42 | fs.writeFileSync(`/tmp/${data.id}.pdf`, res.data)
43 | const loader = new PDFLoader(`/tmp/${data.id}.pdf`)
44 |
45 | const rawDocs = await loader.load()
46 | /* Split text into chunks */
47 | const textSplitter = new RecursiveCharacterTextSplitter({
48 | chunkSize: 1536,
49 | chunkOverlap: 200,
50 | })
51 |
52 | const docs = await textSplitter.splitDocuments(rawDocs)
53 |
54 | console.log("creating vector store...")
55 | /*create and store the embeddings in the vectorStore*/
56 | const embeddings = new OpenAIEmbeddings({
57 | openAIApiKey: openaiApiKey,
58 | stripNewLines: true,
59 | verbose: true,
60 | timeout: 60000,
61 | maxConcurrency: 5,
62 | })
63 | const index = pinecone.Index(pineconeIndex) //change to your own index name
64 | // pinecone_name_space is the id of the document
65 | //embed the PDF documents
66 | await PineconeStore.fromDocuments(docs, embeddings, {
67 | pineconeIndex: index,
68 | namespace: data.id,
69 | textKey: "text",
70 | })
71 |
72 | return NextResponse.json({ data })
73 | }
74 |
75 |
76 | export async function GET(request: NextRequest) {
77 | // Get credentials from cookies
78 | const credentials = JSON.parse(request.cookies.get('credentials')?.value || null)
79 | if (!credentials) {
80 | return NextResponse.json({ data: [] });
81 | }
82 |
83 | // refactor this
84 | const {supabaseDatabaseUrl, supabaseDirectUrl} = credentials
85 | const prisma = createPrisma({ url: supabaseDatabaseUrl });
86 | const data = await prisma.documents.findMany({
87 | orderBy: {
88 | created_at: 'desc'
89 | }
90 | })
91 | return NextResponse.json({ data });
92 | }
--------------------------------------------------------------------------------
/app/api/sources/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server"
2 | import { initPinecone } from "@/utils/pinecone-client"
3 |
4 | import { OpenAIEmbeddings } from "langchain/embeddings/openai"
5 |
6 | import { PineconeStore } from "langchain/vectorstores/pinecone"
7 |
8 | import { PINECONE_NAME_SPACE } from "@/config/pinecone"
9 |
10 | export const runtime = "edge"
11 |
12 | export async function POST(req: NextRequest) {
13 | const body = await req.json();
14 | // Get credentials from cookies
15 | const credentials = JSON.parse(
16 | req.cookies.get("credentials")?.value || null
17 | )
18 | if (
19 | !credentials ||
20 | !credentials.pineconeIndex ||
21 | !credentials.pineconeEnvironment ||
22 | !credentials.pineconeApiKey
23 | ) {
24 | return NextResponse.redirect("/credentials")
25 | }
26 |
27 | const { openaiApiKey, pineconeEnvironment, pineconeIndex, pineconeApiKey } =
28 | credentials
29 | const pinecone = await initPinecone(pineconeEnvironment, pineconeApiKey)
30 | const { prompt, messages: history, id } = body
31 |
32 | // OpenAI recommends replacing newlines with spaces for best results
33 | const sanitizedQuestion = `${prompt.trim().replaceAll("\n", " ")}`
34 | try {
35 | const index = pinecone.Index(pineconeIndex)
36 |
37 | /* create vectorstore*/
38 | const vectorStore = await PineconeStore.fromExistingIndex(
39 | new OpenAIEmbeddings({
40 | openAIApiKey: openaiApiKey,
41 | }),
42 | {
43 | pineconeIndex: index,
44 | textKey: "text",
45 | namespace: id || PINECONE_NAME_SPACE, //namespace comes from your config folder
46 | }
47 | )
48 | const response = await vectorStore.similaritySearch(
49 | sanitizedQuestion,
50 | );
51 |
52 | return NextResponse.json(
53 | { sources: response }
54 | )
55 | } catch (error: any) {
56 | console.log("error", error)
57 | return NextResponse.json(
58 | { error: error.message || "Something went wrong" },
59 | { status: 500 }
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/credentials/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import React, { useState } from "react"
3 | import Head from "next/head"
4 | import Link from "next/link"
5 | import { useCredentialsCookie } from "@/context/credentials-context"
6 | import * as DialogPrimitive from "@radix-ui/react-dialog"
7 | import { FileKey } from "lucide-react"
8 |
9 | import { Button } from "@/components/ui/button"
10 | import {
11 | Dialog,
12 | DialogContent,
13 | DialogDescription,
14 | DialogHeader,
15 | DialogTitle,
16 | DialogTrigger,
17 | } from "@/components/ui/dialog"
18 | import { Input } from "@/components/ui/input"
19 | import { Label } from "@/components/ui/label"
20 |
21 | export default function CredentialsPage() {
22 | const { cookieValue, setAndSaveCookieValue } = useCredentialsCookie()
23 | const [openaiApiKey, setOpenaiApiKey] = useState(cookieValue.openaiApiKey)
24 | const [pineconeEnvironment, setPineconeEnvironment] = useState(
25 | cookieValue.pineconeEnvironment
26 | )
27 | const [pineconeIndex, setPineconeIndex] = useState(cookieValue.pineconeIndex)
28 | const [pineconeApiKey, setPineconeApiKey] = useState(
29 | cookieValue.pineconeApiKey
30 | )
31 | const [supabaseKey, setSupabaseKey] = useState(
32 | cookieValue.supabaseKey
33 | )
34 | const [supabaseUrl, setSupabaseUrl] = useState(
35 | cookieValue.supabaseUrl
36 | )
37 | const [supabaseDatabaseUrl, setSupabaseDatabaseUrl] = useState(
38 | cookieValue.supabaseDatabaseUrl
39 | )
40 | const [supabaseDirectUrl, setSupabaseDirectUrl] = useState(
41 | cookieValue.supabaseDirectUrl
42 | )
43 |
44 | const [supabaseBucket, setSupabaseBucket] = useState(
45 | cookieValue.supabaseBucket
46 | )
47 |
48 |
49 | const handleOpenaiApiKeyChange = (e) => {
50 | setOpenaiApiKey(e.target.value)
51 | }
52 | const handlePineconeEnvironmentChange = (e) => {
53 | setPineconeEnvironment(e.target.value)
54 | }
55 | const handlePineconeIndexChange = (e) => {
56 | setPineconeIndex(e.target.value)
57 | }
58 | const handlePineconeApiKeyChange = (e) => {
59 | setPineconeApiKey(e.target.value)
60 | }
61 | const handleSupabaseKeyChange = (e) => {
62 | setSupabaseKey(e.target.value)
63 | }
64 | const handleSupabaseUrlChange = (e) => {
65 | setSupabaseUrl(e.target.value)
66 | }
67 | const handleSupabaseBucketChange = (e) => {
68 | setSupabaseBucket(e.target.value)
69 | }
70 | const handleSupabaseDatabaseUrlChange = (e) => {
71 | setSupabaseDatabaseUrl(e.target.value)
72 | }
73 | const handleSupabaseDirectUrlChange = (e) => {
74 | setSupabaseDirectUrl(e.target.value)
75 | }
76 |
77 |
78 |
79 | const handleSaveCredentials = () => {
80 | setAndSaveCookieValue({
81 | openaiApiKey,
82 | pineconeEnvironment,
83 | pineconeIndex,
84 | pineconeApiKey,
85 | supabaseKey,
86 | supabaseUrl,
87 | supabaseBucket,
88 | supabaseDatabaseUrl,
89 | supabaseDirectUrl,
90 | })
91 | }
92 |
93 | return (
94 | <>
95 |
96 | Credentials
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Manage your API credentials
105 |
106 |
239 |
240 |
241 |
242 | Getting Started
243 |
244 |
245 | This app requires API credentials to work. You can get these
246 | credentials from{" "}
247 |
253 | Open AI
254 | {" "}
255 | ,{" "}
256 |
262 | Pinecone
263 |
264 | {" "}and{" "}
265 |
271 | Supabase
272 |
273 |
274 |
275 | Once you have the credentials, you can add them to this app by
276 | clicking on the "Add Credentials" button above.
277 |
278 |
279 |
280 |
281 |
282 | How do I get my credentials?
283 |
284 |
285 |
286 | 1. Create your {" "}
287 |
293 | OpenAI API key
294 |
295 |
296 |
297 | 2. Create your {" "}
298 |
304 | Pinecone API key
305 |
306 |
307 |
308 | 3. Create a new index in Pinecone. You can name it whatever you
309 | want. For example, "book-gpt".
310 |
311 |
312 | 4. Create your {" "}
313 |
319 | Supabase project
320 |
321 | {" "}and retrieve your API key, URL, Database URL and Direct URL.
322 |