Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding new examples #68

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/nextjs/faq-bot/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
37 changes: 37 additions & 0 deletions examples/nextjs/faq-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions examples/nextjs/faq-bot/README.md
Original file line number Diff line number Diff line change
@@ -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.
Binary file added examples/nextjs/faq-bot/bun.lockb
Binary file not shown.
4 changes: 4 additions & 0 deletions examples/nextjs/faq-bot/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
33 changes: 33 additions & 0 deletions examples/nextjs/faq-bot/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
8 changes: 8 additions & 0 deletions examples/nextjs/faq-bot/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};

export default config;
Binary file not shown.
30 changes: 30 additions & 0 deletions examples/nextjs/faq-bot/src/app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
};
32 changes: 32 additions & 0 deletions examples/nextjs/faq-bot/src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
};
33 changes: 33 additions & 0 deletions examples/nextjs/faq-bot/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions examples/nextjs/faq-bot/src/app/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions examples/nextjs/faq-bot/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
101 changes: 101 additions & 0 deletions examples/nextjs/faq-bot/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client";
import React, { useState } from "react";

const Home: React.FC = () => {
const [query, setQuery] = useState<string>("");
const [response, setResponse] = useState<string>("");
const [file, setFile] = useState<File | null>(null);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 (
<div className="bg-[#212121] h-screen">
<div className="p-5 max-w-lg mx-auto">
<h1 className="text-xl font-semibold mb-4">PDF-Based FAQ Bot</h1>
<div className="relative mb-2">
<input
type="file"
onChange={handleFileChange}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
<button className="bg-zinc-500 text-white py-2 px-4 w-full rounded hover:bg-zinc-600">
Upload PDF
</button>
</div>

<textarea
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Ask a question..."
rows={2}
className="w-full p-2 border border-gray-300 rounded mb-2 bg-[#212121] text-white"
/>
<button
onClick={handleChat}
className="bg-zinc-500 text-white py-1 px-4 rounded hover:bg-zinc-600"
>
Send
</button>
{response && (
<div className="mt-5 whitespace-pre-wrap">
<h2 className="text-xl font-semibold mb-2">Response:</h2>
<p>{response}</p>
</div>
)}
</div>
</div>
);
};

export default Home;
7 changes: 7 additions & 0 deletions examples/nextjs/faq-bot/src/lib/rag-chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { RAGChat, upstash } from "@upstash/rag-chat";
import { redis } from "./redis";

export const ragChat = new RAGChat({
model: upstash("meta-llama/Meta-Llama-3-8B-Instruct"),
redis: redis,
});
3 changes: 3 additions & 0 deletions examples/nextjs/faq-bot/src/lib/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Redis } from "@upstash/redis";

export const redis = Redis.fromEnv();
16 changes: 16 additions & 0 deletions examples/nextjs/faq-bot/src/utils/AskQuestion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pdfParse from 'pdf-parse';
import fs from 'fs';
import path from 'path';

export const AskQuestion = async (query: string) => {
const pdfPath = path.join(process.cwd(), 'public', 'assets', 'Effective_Ts.pdf');
const dataBuffer = fs.readFileSync(pdfPath);

const pdfData = await pdfParse(dataBuffer);
const text = pdfData.text;

const lines = text.split('\n');
const relevantLines = lines.filter(line => line.includes(query));

return relevantLines.length > 0 ? relevantLines.join('\n') : 'No relevant information found.';
};
Loading