Skip to content
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
Binary file added Page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions app/api/documents/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NextRequest } from "next/server";
import { createClient } from "@supabase/supabase-js";
import { supabaseUrl, supabaseKey } from "../../../utils/env";
import { getEmbedding } from "../../../utils/openai";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
try {
const { query } = await req.json();
if (!query) {
return new Response(JSON.stringify({ error: "Missing query" }), { status: 400 });
}
const embedding = await getEmbedding(query);
const supabase = createClient(supabaseUrl, supabaseKey);
// Call the match_pages function
const { data, error } = await supabase.rpc("match_pages", {
query_embedding: embedding,
match_threshold: 0.7,
match_count: 10,
});
if (error) {
return new Response(JSON.stringify({ error: error.message }), { status: 500 });
}
return new Response(JSON.stringify({ results: data }), { status: 200 });
} catch (e: any) {
return new Response(JSON.stringify({ error: e.message }), { status: 500 });
}
}
81 changes: 81 additions & 0 deletions app/api/documents/upload/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { NextRequest } from "next/server";
import { createClient } from "@supabase/supabase-js";
import { supabaseUrl, supabaseKey } from "../../../utils/env";
import { getEmbedding } from "../../../utils/openai";
import * as pdfjs from "pdfjs-dist/legacy/build/pdf";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
try {
const formData = await req.formData();
const fileBlob = formData.get("file");
const title = formData.get("title") as string;
if (!fileBlob || !title) {
console.error("Missing file or title", { fileBlob, title });
return new Response(JSON.stringify({ error: "Missing file or title" }), { status: 400 });
}

// Try to get a name for the file, fallback if not present
const fileName = (fileBlob as any).name || `upload-${Date.now()}.pdf`;

let arrayBuffer, buffer;
try {
arrayBuffer = await fileBlob.arrayBuffer();
buffer = Buffer.from(arrayBuffer);
} catch (err) {
console.error("Error converting fileBlob to buffer", err);
return new Response(JSON.stringify({ error: "Failed to process file upload" }), { status: 500 });
}

// Upload PDF to Supabase Storage
const supabase = createClient(supabaseUrl, supabaseKey);
const storagePath = `pdfs/${Date.now()}-${fileName}`;
const { error: uploadError } = await supabase.storage.from("pdfs").upload(storagePath, buffer, { upsert: true });
if (uploadError) {
console.error("Supabase upload error", uploadError);
return new Response(JSON.stringify({ error: uploadError.message }), { status: 500 });
}

// Extract per-page text using pdfjs
let pdf;
try {
pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
} catch (err) {
console.error("PDF.js failed to parse document", err);
return new Response(JSON.stringify({ error: "Failed to parse PDF document" }), { status: 500 });
}
const numPages = pdf.numPages;
const pages = [];
for (let i = 1; i <= numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const text = textContent.items.map((item: any) => item.str).join(" ");
let embedding = null;
if (text.trim().length > 0) {
try {
embedding = await getEmbedding(text);
} catch (err) {
console.error(`OpenAI embedding failed for page ${i}`, err);
}
}
pages.push({ page_number: i, text, embedding });
}

// Insert document record into Supabase
const { error: insertError } = await supabase.from("documents").insert({
title,
storage_path: storagePath,
pages,
});
if (insertError) {
console.error("Supabase insert error", insertError);
return new Response(JSON.stringify({ error: insertError.message }), { status: 500 });
}

return new Response(JSON.stringify({ success: true }), { status: 200 });
} catch (e: any) {
console.error("/api/documents/upload uncaught error", e);
return new Response(JSON.stringify({ error: e.message, stack: e.stack }), { status: 500 });
}
}
1 change: 1 addition & 0 deletions app/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET as string;
export const storageMethod = process.env.STORAGE_METHOD
? (process.env.STORAGE_METHOD as "supabase" | "sqlite")
: "sqlite";
export const OPENAI_API_KEY = process.env.OPENAI_API_KEY as string;
20 changes: 20 additions & 0 deletions app/utils/openai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { OPENAI_API_KEY } from "./env";

export async function getEmbedding(text: string): Promise<number[]> {
const response = await fetch("https://api.openai.com/v1/embeddings", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
input: text,
model: "text-embedding-ada-002",
}),
});
if (!response.ok) {
throw new Error(`OpenAI API error: ${response.statusText}`);
}
const data = await response.json();
return data.data[0].embedding;
}
Binary file added data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading