create-rag-app 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -53
- package/bin/cli.js +7 -7
- package/package.json +4 -4
- package/src/index.js +65 -65
- package/src/prompts.js +66 -66
- package/src/utils.js +62 -62
- package/templates/nextjs-rag/.env.example +13 -13
- package/templates/nextjs-rag/documents/readme.txt +1 -1
- package/templates/nextjs-rag/jsconfig.json +15 -15
- package/templates/nextjs-rag/next.config.mjs +7 -7
- package/templates/nextjs-rag/package.json +33 -33
- package/templates/nextjs-rag/postcss.config.js +6 -6
- package/templates/nextjs-rag/scripts/ingest.js +25 -25
- package/templates/nextjs-rag/src/app/api/chat/route.js +52 -52
- package/templates/nextjs-rag/src/app/api/ingest/route.js +51 -51
- package/templates/nextjs-rag/src/app/globals.css +43 -43
- package/templates/nextjs-rag/src/app/layout.js +17 -17
- package/templates/nextjs-rag/src/app/page.js +352 -352
- package/templates/nextjs-rag/src/lib/ingest.js +123 -123
- package/templates/nextjs-rag/src/lib/llm.js +54 -54
- package/templates/nextjs-rag/tailwind.config.js +18 -18
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-rag-app",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "next dev",
|
|
7
|
-
"build": "next build",
|
|
8
|
-
"start": "next start",
|
|
9
|
-
"lint": "next lint",
|
|
10
|
-
"ingest": "node scripts/ingest.js"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"next": "15.
|
|
14
|
-
"react": "19.0.0-rc-66855b96-20241106",
|
|
15
|
-
"react-dom": "19.0.0-rc-66855b96-20241106",
|
|
16
|
-
"langchain": "^0.3.5",
|
|
17
|
-
"@langchain/openai": "^0.3.11",
|
|
18
|
-
"@langchain/ollama": "^0.1.2",
|
|
19
|
-
"@langchain/google-genai": "^0.1.3",
|
|
20
|
-
"@langchain/community": "^0.3.11",
|
|
21
|
-
"@langchain/core": "^0.3.15",
|
|
22
|
-
"@langchain/textsplitters": "^0.1.0",
|
|
23
|
-
"chromadb": "^1.9.4",
|
|
24
|
-
"pdf-parse": "1.1.1",
|
|
25
|
-
"openai": "^4.71.1",
|
|
26
|
-
"dotenv": "^16.4.5"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"postcss": "^8.4.47",
|
|
30
|
-
"tailwindcss": "^3.4.14",
|
|
31
|
-
"autoprefixer": "^10.4.20"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "my-rag-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint",
|
|
10
|
+
"ingest": "node scripts/ingest.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"next": "15.5.10",
|
|
14
|
+
"react": "19.0.0-rc-66855b96-20241106",
|
|
15
|
+
"react-dom": "19.0.0-rc-66855b96-20241106",
|
|
16
|
+
"langchain": "^0.3.5",
|
|
17
|
+
"@langchain/openai": "^0.3.11",
|
|
18
|
+
"@langchain/ollama": "^0.1.2",
|
|
19
|
+
"@langchain/google-genai": "^0.1.3",
|
|
20
|
+
"@langchain/community": "^0.3.11",
|
|
21
|
+
"@langchain/core": "^0.3.15",
|
|
22
|
+
"@langchain/textsplitters": "^0.1.0",
|
|
23
|
+
"chromadb": "^1.9.4",
|
|
24
|
+
"pdf-parse": "1.1.1",
|
|
25
|
+
"openai": "^4.71.1",
|
|
26
|
+
"dotenv": "^16.4.5"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"postcss": "^8.4.47",
|
|
30
|
+
"tailwindcss": "^3.4.14",
|
|
31
|
+
"autoprefixer": "^10.4.20"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
plugins: {
|
|
3
|
-
tailwindcss: {},
|
|
4
|
-
autoprefixer: {},
|
|
5
|
-
},
|
|
6
|
-
};
|
|
1
|
+
export default {
|
|
2
|
+
plugins: {
|
|
3
|
+
tailwindcss: {},
|
|
4
|
+
autoprefixer: {},
|
|
5
|
+
},
|
|
6
|
+
};
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import { ingestDocuments } from "../src/lib/ingest.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Script entry point for manual document ingestion.
|
|
6
|
-
* Run this via `npm run ingest` to index all files in the ./documents folder.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* npm run ingest
|
|
10
|
-
*
|
|
11
|
-
* This script ensures that all documents in the local directory are
|
|
12
|
-
* processed, split, and embedded into the ChromaDB vector store.
|
|
13
|
-
*/
|
|
14
|
-
const run = async () => {
|
|
15
|
-
try {
|
|
16
|
-
console.log("🚀 Starting document ingestion process...");
|
|
17
|
-
await ingestDocuments("./documents");
|
|
18
|
-
} catch (error) {
|
|
19
|
-
console.error("❌ Document ingestion failed:");
|
|
20
|
-
console.error(error);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
run();
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { ingestDocuments } from "../src/lib/ingest.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Script entry point for manual document ingestion.
|
|
6
|
+
* Run this via `npm run ingest` to index all files in the ./documents folder.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npm run ingest
|
|
10
|
+
*
|
|
11
|
+
* This script ensures that all documents in the local directory are
|
|
12
|
+
* processed, split, and embedded into the ChromaDB vector store.
|
|
13
|
+
*/
|
|
14
|
+
const run = async () => {
|
|
15
|
+
try {
|
|
16
|
+
console.log("🚀 Starting document ingestion process...");
|
|
17
|
+
await ingestDocuments("./documents");
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("❌ Document ingestion failed:");
|
|
20
|
+
console.error(error);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
run();
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { getLLM, getEmbeddings } from "@/lib/llm";
|
|
2
|
-
import { Chroma } from "@langchain/community/vectorstores/chroma";
|
|
3
|
-
import { PromptTemplate } from "@langchain/core/prompts";
|
|
4
|
-
|
|
5
|
-
export async function POST(req) {
|
|
6
|
-
try {
|
|
7
|
-
const body = await req.json();
|
|
8
|
-
const message = body?.message;
|
|
9
|
-
|
|
10
|
-
if (!message || typeof message !== "string") {
|
|
11
|
-
return Response.json(
|
|
12
|
-
{ error: "Invalid request. 'message' must be a string." },
|
|
13
|
-
{ status: 400 }
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const vectorStore = await Chroma.fromExistingCollection(getEmbeddings(), {
|
|
18
|
-
collectionName: process.env.COLLECTION_NAME || "rag-docs",
|
|
19
|
-
url: process.env.CHROMA_URL || "http://localhost:8000"
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const retriever = vectorStore.asRetriever();
|
|
23
|
-
const llm = getLLM();
|
|
24
|
-
|
|
25
|
-
// 🔥 manually retrieve documents
|
|
26
|
-
const docs = await retriever.invoke(message);
|
|
27
|
-
|
|
28
|
-
const context = docs.map((doc) => doc.pageContent).join("\n\n");
|
|
29
|
-
|
|
30
|
-
const prompt = PromptTemplate.fromTemplate(`
|
|
31
|
-
Answer the question based only on the following context:
|
|
32
|
-
|
|
33
|
-
{context}
|
|
34
|
-
|
|
35
|
-
Question: {question}
|
|
36
|
-
`);
|
|
37
|
-
|
|
38
|
-
const formattedPrompt = await prompt.format({
|
|
39
|
-
context,
|
|
40
|
-
question: message
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const result = await llm.invoke(formattedPrompt);
|
|
44
|
-
|
|
45
|
-
return Response.json({
|
|
46
|
-
response: result.content
|
|
47
|
-
});
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error(error);
|
|
50
|
-
return Response.json({ error: error.message }, { status: 500 });
|
|
51
|
-
}
|
|
52
|
-
}
|
|
1
|
+
import { getLLM, getEmbeddings } from "@/lib/llm";
|
|
2
|
+
import { Chroma } from "@langchain/community/vectorstores/chroma";
|
|
3
|
+
import { PromptTemplate } from "@langchain/core/prompts";
|
|
4
|
+
|
|
5
|
+
export async function POST(req) {
|
|
6
|
+
try {
|
|
7
|
+
const body = await req.json();
|
|
8
|
+
const message = body?.message;
|
|
9
|
+
|
|
10
|
+
if (!message || typeof message !== "string") {
|
|
11
|
+
return Response.json(
|
|
12
|
+
{ error: "Invalid request. 'message' must be a string." },
|
|
13
|
+
{ status: 400 }
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const vectorStore = await Chroma.fromExistingCollection(getEmbeddings(), {
|
|
18
|
+
collectionName: process.env.COLLECTION_NAME || "rag-docs",
|
|
19
|
+
url: process.env.CHROMA_URL || "http://localhost:8000"
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const retriever = vectorStore.asRetriever();
|
|
23
|
+
const llm = getLLM();
|
|
24
|
+
|
|
25
|
+
// 🔥 manually retrieve documents
|
|
26
|
+
const docs = await retriever.invoke(message);
|
|
27
|
+
|
|
28
|
+
const context = docs.map((doc) => doc.pageContent).join("\n\n");
|
|
29
|
+
|
|
30
|
+
const prompt = PromptTemplate.fromTemplate(`
|
|
31
|
+
Answer the question based only on the following context:
|
|
32
|
+
|
|
33
|
+
{context}
|
|
34
|
+
|
|
35
|
+
Question: {question}
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
const formattedPrompt = await prompt.format({
|
|
39
|
+
context,
|
|
40
|
+
question: message
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const result = await llm.invoke(formattedPrompt);
|
|
44
|
+
|
|
45
|
+
return Response.json({
|
|
46
|
+
response: result.content
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(error);
|
|
50
|
+
return Response.json({ error: error.message }, { status: 500 });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { ingestDocuments } from "@/lib/ingest";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
|
-
import path from "path";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Handle document uploads and immediate ingestion into ChromaDB.
|
|
8
|
-
* This route accepts a file upload, saves it locally, and triggers the vector store ingestion.
|
|
9
|
-
*
|
|
10
|
-
* @param {Request} req
|
|
11
|
-
* @returns {NextResponse}
|
|
12
|
-
*/
|
|
13
|
-
export async function POST(req) {
|
|
14
|
-
try {
|
|
15
|
-
const formData = await req.formData();
|
|
16
|
-
const file = formData.get("file");
|
|
17
|
-
|
|
18
|
-
if (!file) {
|
|
19
|
-
return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Validate file type (basic)
|
|
23
|
-
const allowedExtensions = [".pdf", ".txt"];
|
|
24
|
-
const ext = path.extname(file.name).toLowerCase();
|
|
25
|
-
if (!allowedExtensions.includes(ext)) {
|
|
26
|
-
return NextResponse.json({ error: "Unsupported file type. Only PDF and TXT are allowed." }, { status: 400 });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const buffer = Buffer.from(await file.arrayBuffer());
|
|
30
|
-
const uploadDir = path.resolve(process.cwd(), "documents");
|
|
31
|
-
|
|
32
|
-
// Ensure the upload directory exists
|
|
33
|
-
try {
|
|
34
|
-
await fs.mkdir(uploadDir, { recursive: true });
|
|
35
|
-
} catch (e) {
|
|
36
|
-
// Directory likely already exists
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const filePath = path.join(uploadDir, file.name);
|
|
40
|
-
await fs.writeFile(filePath, buffer);
|
|
41
|
-
|
|
42
|
-
// Run ingestion for this specific file
|
|
43
|
-
console.log(`Starting ingestion for: ${file.name}`);
|
|
44
|
-
await ingestDocuments(`documents/${file.name}`, true);
|
|
45
|
-
|
|
46
|
-
return NextResponse.json({ success: true, message: "File uploaded and ingested successfully." });
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error("Upload/Ingest Error:", error);
|
|
49
|
-
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { ingestDocuments } from "@/lib/ingest";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handle document uploads and immediate ingestion into ChromaDB.
|
|
8
|
+
* This route accepts a file upload, saves it locally, and triggers the vector store ingestion.
|
|
9
|
+
*
|
|
10
|
+
* @param {Request} req
|
|
11
|
+
* @returns {NextResponse}
|
|
12
|
+
*/
|
|
13
|
+
export async function POST(req) {
|
|
14
|
+
try {
|
|
15
|
+
const formData = await req.formData();
|
|
16
|
+
const file = formData.get("file");
|
|
17
|
+
|
|
18
|
+
if (!file) {
|
|
19
|
+
return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Validate file type (basic)
|
|
23
|
+
const allowedExtensions = [".pdf", ".txt"];
|
|
24
|
+
const ext = path.extname(file.name).toLowerCase();
|
|
25
|
+
if (!allowedExtensions.includes(ext)) {
|
|
26
|
+
return NextResponse.json({ error: "Unsupported file type. Only PDF and TXT are allowed." }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
30
|
+
const uploadDir = path.resolve(process.cwd(), "documents");
|
|
31
|
+
|
|
32
|
+
// Ensure the upload directory exists
|
|
33
|
+
try {
|
|
34
|
+
await fs.mkdir(uploadDir, { recursive: true });
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Directory likely already exists
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const filePath = path.join(uploadDir, file.name);
|
|
40
|
+
await fs.writeFile(filePath, buffer);
|
|
41
|
+
|
|
42
|
+
// Run ingestion for this specific file
|
|
43
|
+
console.log(`Starting ingestion for: ${file.name}`);
|
|
44
|
+
await ingestDocuments(`documents/${file.name}`, true);
|
|
45
|
+
|
|
46
|
+
return NextResponse.json({ success: true, message: "File uploaded and ingested successfully." });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("Upload/Ingest Error:", error);
|
|
49
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
@layer base {
|
|
6
|
-
body {
|
|
7
|
-
@apply bg-zinc-950 text-zinc-100 antialiased;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
@layer utilities {
|
|
12
|
-
.scrollbar-thin::-webkit-scrollbar {
|
|
13
|
-
width: 6px;
|
|
14
|
-
height: 6px;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.scrollbar-thin::-webkit-scrollbar-track {
|
|
18
|
-
background: transparent;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.scrollbar-thin::-webkit-scrollbar-thumb {
|
|
22
|
-
@apply bg-zinc-800 rounded-full;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
26
|
-
@apply bg-zinc-700;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@keyframes fadeIn {
|
|
31
|
-
from {
|
|
32
|
-
opacity: 0;
|
|
33
|
-
transform: translateY(10px);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
to {
|
|
37
|
-
opacity: 1;
|
|
38
|
-
transform: translateY(0);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.animate-fade-in {
|
|
43
|
-
animation: fadeIn 0.3s ease-out forwards;
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
body {
|
|
7
|
+
@apply bg-zinc-950 text-zinc-100 antialiased;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@layer utilities {
|
|
12
|
+
.scrollbar-thin::-webkit-scrollbar {
|
|
13
|
+
width: 6px;
|
|
14
|
+
height: 6px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.scrollbar-thin::-webkit-scrollbar-track {
|
|
18
|
+
background: transparent;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.scrollbar-thin::-webkit-scrollbar-thumb {
|
|
22
|
+
@apply bg-zinc-800 rounded-full;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
26
|
+
@apply bg-zinc-700;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@keyframes fadeIn {
|
|
31
|
+
from {
|
|
32
|
+
opacity: 0;
|
|
33
|
+
transform: translateY(10px);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
to {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
transform: translateY(0);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.animate-fade-in {
|
|
43
|
+
animation: fadeIn 0.3s ease-out forwards;
|
|
44
44
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { Inter } from "next/font/google";
|
|
2
|
-
import "./globals.css";
|
|
3
|
-
|
|
4
|
-
const inter = Inter({ subsets: ["latin"] });
|
|
5
|
-
|
|
6
|
-
export const metadata = {
|
|
7
|
-
title: "Create RAG App",
|
|
8
|
-
description: "Generated by create-rag-app",
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default function RootLayout({ children }) {
|
|
12
|
-
return (
|
|
13
|
-
<html lang="en">
|
|
14
|
-
<body className={inter.className}>{children}</body>
|
|
15
|
-
</html>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
1
|
+
import { Inter } from "next/font/google";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
|
|
4
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
5
|
+
|
|
6
|
+
export const metadata = {
|
|
7
|
+
title: "Create RAG App",
|
|
8
|
+
description: "Generated by create-rag-app",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function RootLayout({ children }) {
|
|
12
|
+
return (
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<body className={inter.className}>{children}</body>
|
|
15
|
+
</html>
|
|
16
|
+
);
|
|
17
|
+
}
|