diff --git a/examples/nextjs/faq-bot/.eslintrc.json b/examples/nextjs/faq-bot/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/examples/nextjs/faq-bot/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/examples/nextjs/faq-bot/.gitignore b/examples/nextjs/faq-bot/.gitignore new file mode 100644 index 0000000..00bba9b --- /dev/null +++ b/examples/nextjs/faq-bot/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/nextjs/faq-bot/README.md b/examples/nextjs/faq-bot/README.md new file mode 100644 index 0000000..dc57247 --- /dev/null +++ b/examples/nextjs/faq-bot/README.md @@ -0,0 +1,34 @@ +# RAGChat with Next.js Example + +This project demonstrates how to implement RAGChat (Retrieval-Augmented Generation Chat) using Next.js route handlers. For PDF Based FAQ Bot, we use the Vercel AI-SDK to manage chat messages and interactions. This is fully optional and simplifies our example. + +## Getting started + +### 1. Install packages + +```bash +bun install +``` + +### 2. Start the app + +```bash +bun run dev +``` + +The app will start running on `http://localhost:3000`. + +### How It Works + +1. Server actions: The server actions for adding data as well as chatting are located in `actions.ts`: + + - `server_chat`: Handles chat requests, retrieves relevant information using RAG, and streams responses back to the client. + - `server_add_data`: Adds new data to the knowledge base for future retrieval. + +2. RAG Implementation: The RAGChat class from @upstash/rag-chat is used to integrate the vector database and language model. +3. Vector Database: Upstash Vector is used to store and retrieve context-relevant information. +4. Language Model: The example uses Meta's Llama 3 8B Instruct model via QStash, but can be easily switched to OpenAI's GPT models. + +### Customization + +Upstash RAGChat is highly customizable to suit your needs. For all available options, please refer to our RAGChat documentation. diff --git a/examples/nextjs/faq-bot/bun.lockb b/examples/nextjs/faq-bot/bun.lockb new file mode 100644 index 0000000..bb100d1 Binary files /dev/null and b/examples/nextjs/faq-bot/bun.lockb differ diff --git a/examples/nextjs/faq-bot/next.config.mjs b/examples/nextjs/faq-bot/next.config.mjs new file mode 100644 index 0000000..4678774 --- /dev/null +++ b/examples/nextjs/faq-bot/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/examples/nextjs/faq-bot/package.json b/examples/nextjs/faq-bot/package.json new file mode 100644 index 0000000..dd7e466 --- /dev/null +++ b/examples/nextjs/faq-bot/package.json @@ -0,0 +1,33 @@ +{ + "name": "faq-bot", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@nextui-org/react": "^2.4.6", + "@types/formidable": "^3.4.5", + "@types/pdf-parse": "^1.1.4", + "@upstash/rag-chat": "^1.5.0", + "formidable": "^3.5.1", + "lucide-react": "^0.437.0", + "next": "14.2.5", + "pdf-parse": "^1.1.1", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "typescript": "^5", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "eslint": "^8", + "eslint-config-next": "14.2.5" + } +} diff --git a/examples/nextjs/faq-bot/postcss.config.mjs b/examples/nextjs/faq-bot/postcss.config.mjs new file mode 100644 index 0000000..1a69fd2 --- /dev/null +++ b/examples/nextjs/faq-bot/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/examples/nextjs/faq-bot/public/assets/Effective_Ts.pdf b/examples/nextjs/faq-bot/public/assets/Effective_Ts.pdf new file mode 100644 index 0000000..bd7c913 Binary files /dev/null and b/examples/nextjs/faq-bot/public/assets/Effective_Ts.pdf differ diff --git a/examples/nextjs/faq-bot/src/app/api/chat/route.ts b/examples/nextjs/faq-bot/src/app/api/chat/route.ts new file mode 100644 index 0000000..39e3606 --- /dev/null +++ b/examples/nextjs/faq-bot/src/app/api/chat/route.ts @@ -0,0 +1,30 @@ +import { ragChat } from "@/lib/rag-chat"; +import { aiUseChatAdapter } from "@upstash/rag-chat/nextjs"; +import { NextRequest, NextResponse } from "next/server"; + +export const POST = async (req: NextRequest) => { + try { + const body = await req.json(); + console.log('Request body:', body); + + const { messages, sessionId } = body; + + if (!messages || !Array.isArray(messages) || messages.length === 0) { + return NextResponse.json({ error: 'Invalid messages array' }, { status: 400 }); + } + + const lastMessage = messages[messages.length - 1].content; + + if (typeof lastMessage !== 'string') { + return NextResponse.json({ error: 'Invalid message content' }, { status: 400 }); + } + + const response = await ragChat.chat(lastMessage, { streaming: true, sessionId }); + + console.log('Response from ragChat:', response); + return aiUseChatAdapter(response); + } catch (error) { + console.error('Error in POST /api/chat:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +}; diff --git a/examples/nextjs/faq-bot/src/app/api/upload/route.ts b/examples/nextjs/faq-bot/src/app/api/upload/route.ts new file mode 100644 index 0000000..e80f89b --- /dev/null +++ b/examples/nextjs/faq-bot/src/app/api/upload/route.ts @@ -0,0 +1,32 @@ +import { NextResponse } from 'next/server'; +import path from 'path'; +import { mkdir, writeFile } from 'fs/promises'; +import { existsSync } from 'fs'; + +export const POST = async (req: Request) => { + try { + const formData = await req.formData(); + const file = formData.get('file') as File | null; + + if (!file) { + return NextResponse.json({ error: 'No file provided' }, { status: 400 }); + } + + const buffer = Buffer.from(await file.arrayBuffer()); + const filename = file.name.replace(/ /g, '_'); + const dirPath = path.join(process.cwd(), 'public', 'assets'); + const filePath = path.join(dirPath, filename); + + // Ensure the directory exists + if (!existsSync(dirPath)) { + await mkdir(dirPath, { recursive: true }); + } + + // Write the file to the specified directory + await writeFile(filePath, buffer); + return NextResponse.json({ message: 'File uploaded successfully' }, { status: 201 }); + } catch (error) { + console.error('Error occurred during file upload:', error); + return NextResponse.json({ message: 'Failed to upload file', error }, { status: 500 }); + } +}; diff --git a/examples/nextjs/faq-bot/src/app/globals.css b/examples/nextjs/faq-bot/src/app/globals.css new file mode 100644 index 0000000..875c01e --- /dev/null +++ b/examples/nextjs/faq-bot/src/app/globals.css @@ -0,0 +1,33 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} diff --git a/examples/nextjs/faq-bot/src/app/icon.svg b/examples/nextjs/faq-bot/src/app/icon.svg new file mode 100644 index 0000000..85d45ab --- /dev/null +++ b/examples/nextjs/faq-bot/src/app/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/faq-bot/src/app/layout.tsx b/examples/nextjs/faq-bot/src/app/layout.tsx new file mode 100644 index 0000000..e7d144a --- /dev/null +++ b/examples/nextjs/faq-bot/src/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "FAQ Bot", + description: "Developed by Abhishek Kushwaha", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/examples/nextjs/faq-bot/src/app/page.tsx b/examples/nextjs/faq-bot/src/app/page.tsx new file mode 100644 index 0000000..e76ba93 --- /dev/null +++ b/examples/nextjs/faq-bot/src/app/page.tsx @@ -0,0 +1,101 @@ +"use client"; +import React, { useState } from "react"; + +const Home: React.FC = () => { + const [query, setQuery] = useState(""); + const [response, setResponse] = useState(""); + const [file, setFile] = useState(null); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + const selectedFile = e.target.files[0]; + setFile(selectedFile); + handleUpload(selectedFile); + } + }; + + const handleUpload = async (file: File) => { + try { + const formData = new FormData(); + formData.append("file", file); + + const res = await fetch("/api/upload", { + method: "POST", + body: formData, + }); + + if (!res.ok) throw new Error("Error during file upload"); + + const data = await res.json(); + console.log(data); + alert("File uploaded successfully"); + } catch (error) { + console.error(error); + alert("An error occurred during file upload."); + } + }; + + const handleChat = async () => { + try { + const res = await fetch("/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + messages: [{ content: query }], + sessionId: "optional-session-id" + }), + }); + + if (!res.ok) throw new Error("Error during chat request"); + + const data = await res.json(); + setResponse(data.response); + } catch (error) { + console.error(error); + setResponse("An error occurred while fetching the response."); + } + }; + + + return ( +
+
+

PDF-Based FAQ Bot

+
+ + +
+ +