diff --git a/README.md b/README.md
index c791742..b01adbe 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ yarn install
```env
GOOGLE_GENERATIVE_AI_API_KEY=your_api_key_here
+FIRECRAWL_API_KEY=
```
4. Run the development server:
diff --git a/app/api/generate-quiz/route.ts b/app/api/generate-quiz/route.ts
index 8093bf4..487a27c 100644
--- a/app/api/generate-quiz/route.ts
+++ b/app/api/generate-quiz/route.ts
@@ -1,12 +1,33 @@
import { pdfExtractSchema } from "@/lib/schemas";
import { google } from "@ai-sdk/google";
import { streamObject } from "ai";
+import FirecrawlApp from '@mendable/firecrawl-js';
export const maxDuration = 60;
export async function POST(req: Request) {
- const { files } = await req.json();
- const firstFile = files[0].data;
+ const { files, url } = await req.json();
+
+ let content = '';
+
+ if (url) {
+ const app = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY! });
+
+ const scrapeResponse = await app.scrapeUrl(url, {
+ formats: ['markdown'],
+ });
+
+ if (!scrapeResponse.success) {
+ throw new Error(`Failed to scrape: ${scrapeResponse.error}`);
+ }
+
+ content = scrapeResponse.markdown ?? '';
+ console.log(content);
+ } else if (files && files.length > 0) {
+ content = files[0]?.data ?? '';
+ } else {
+ throw new Error('No content provided');
+ }
const result = await streamObject({
model: google("gemini-1.5-pro-latest"),
@@ -14,19 +35,20 @@ export async function POST(req: Request) {
{
role: "system",
content:
- "You are a document analyzer. Extract the most important points from the provided PDF document. Focus on key information, main ideas, and significant details.",
+ "You are a document analyzer. Extract the most important points from the provided document. Focus on key information, main ideas, and significant details.",
},
{
role: "user",
content: [
{
type: "text",
- text: "Please read this PDF and extract the key points. Include relevant context where helpful.",
+ text: url
+ ? "Please analyze this webpage content and extract the key points. Include relevant context where helpful."
+ : "Please read this PDF and extract the key points. Include relevant context where helpful.",
},
{
- type: "file",
- data: firstFile,
- mimeType: "application/pdf",
+ type: "text",
+ text: content,
},
],
},
diff --git a/components/MindMap.tsx b/components/MindMap.tsx
index 6f79851..ae671d9 100644
--- a/components/MindMap.tsx
+++ b/components/MindMap.tsx
@@ -115,14 +115,9 @@ export default function MindMap({ data, onNodeClick }: MindMapProps) {
No Mind Map Yet
- Upload a PDF to generate an interactive mind map of its key concepts.
+ Upload a PDF or Enter a URL to generate an interactive mind map of its key concepts.
-
+
)
diff --git a/components/PDFExtractor.tsx b/components/PDFExtractor.tsx
index 504562b..e85c22d 100644
--- a/components/PDFExtractor.tsx
+++ b/components/PDFExtractor.tsx
@@ -1,83 +1,121 @@
-'use client'
+"use client";
-import { useState } from "react"
-import { experimental_useObject } from "ai/react"
-import { toast } from "sonner"
-import { FileUp, Loader2 } from "lucide-react"
-import { Button } from "@/components/ui/button"
-import { Card, CardContent, CardFooter, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
-import { Link } from "@/components/ui/link"
-import { pdfExtractSchema, type PDFExtract } from "@/lib/schemas"
+import { useState } from "react";
+import { experimental_useObject } from "ai/react";
+import { toast } from "sonner";
+import { FileUp, Loader2 } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+} from "@/components/ui/card";
+import { Link } from "@/components/ui/link";
+import { pdfExtractSchema, type PDFExtract } from "@/lib/schemas";
interface PDFExtractorProps {
onExtractComplete?: (content: PDFExtract) => void;
onPartialContent?: (content: Partial) => void;
}
-export default function PDFExtractor({ onExtractComplete, onPartialContent }: PDFExtractorProps) {
- const [files, setFiles] = useState([])
- const [isDragging, setIsDragging] = useState(false)
+export default function PDFExtractor({
+ onExtractComplete,
+ onPartialContent,
+}: PDFExtractorProps) {
+ const [files, setFiles] = useState([]);
+ const [url, setUrl] = useState("");
+ const [isDragging, setIsDragging] = useState(false);
const {
submit,
object: extractedContent,
- isLoading
+ isLoading,
} = experimental_useObject({
api: "/api/generate-quiz",
schema: pdfExtractSchema,
initialValue: undefined,
onError: (error) => {
- toast.error("Failed to analyze PDF. Please try again.")
- setFiles([])
+ toast.error("Failed to analyze PDF. Please try again.");
+ setFiles([]);
+ setUrl("");
},
onFinish: ({ object }) => {
if (object) {
- onExtractComplete?.(object)
- onPartialContent?.(object)
+ onExtractComplete?.(object);
+ onPartialContent?.(object);
}
- }
- })
+ },
+ });
const handleFileChange = (e: React.ChangeEvent) => {
- const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari && isDragging) {
- toast.error("Safari does not support drag & drop. Please use the file picker.")
- return
+ toast.error(
+ "Safari does not support drag & drop. Please use the file picker."
+ );
+ return;
}
- const selectedFiles = Array.from(e.target.files || [])
+ const selectedFiles = Array.from(e.target.files || []);
const validFiles = selectedFiles.filter(
- (file) => file.type === "application/pdf" && file.size <= 5 * 1024 * 1024,
- )
+ (file) => file.type === "application/pdf" && file.size <= 5 * 1024 * 1024
+ );
if (validFiles.length !== selectedFiles.length) {
- toast.error("Only PDF files under 5MB are allowed.")
+ toast.error("Only PDF files under 5MB are allowed.");
}
- setFiles(validFiles)
- }
+ setFiles(validFiles);
+ setUrl("");
+ };
const encodeFileAsBase64 = (file: File): Promise => {
return new Promise((resolve, reject) => {
- const reader = new FileReader()
- reader.readAsDataURL(file)
- reader.onload = () => resolve(reader.result as string)
- reader.onerror = (error) => reject(error)
- })
- }
-
- const handleSubmitWithFiles = async (e: React.FormEvent) => {
- e.preventDefault()
- const encodedFiles = await Promise.all(
- files.map(async (file) => ({
- name: file.name,
- type: file.type,
- data: await encodeFileAsBase64(file),
- })),
- )
- submit({ files: encodedFiles })
- }
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => resolve(reader.result as string);
+ reader.onerror = (error) => reject(error);
+ });
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ try {
+ if (files.length > 0) {
+ const encodedFiles = await Promise.all(
+ files.map(async (file) => ({
+ name: file.name,
+ type: file.type,
+ data: await encodeFileAsBase64(file),
+ }))
+ );
+ submit({ files: encodedFiles });
+ } else if (url) {
+ try {
+ new URL(url);
+ submit({ url });
+ } catch {
+ toast.error("Please enter a valid URL");
+ return;
+ }
+ } else {
+ toast.error("Please upload a PDF or enter a URL.");
+ }
+ } catch (error) {
+ toast.error("An error occurred. Please try again.");
+ }
+ };
+
+ const handleUrlChange = (e: React.ChangeEvent) => {
+ setUrl(e.target.value);
+ if (e.target.value) {
+ setFiles([]);
+ }
+ };
return (
@@ -87,7 +125,7 @@ export default function PDFExtractor({ onExtractComplete, onPartialContent }: PD
Mind Map Maker
- Upload a PDF to generate a mind map using{" "}
+ Upload a PDF or enter a URL to generate a mind map using{" "}
Google's Gemini Pro
@@ -97,11 +135,14 @@ export default function PDFExtractor({ onExtractComplete, onPartialContent }: PD
-
-
+
{extractedContent && extractedContent.keyPoints && (
-