diff --git a/kits/research-paper-explainer/.env.example b/kits/research-paper-explainer/.env.example new file mode 100644 index 00000000..352e803c --- /dev/null +++ b/kits/research-paper-explainer/.env.example @@ -0,0 +1,12 @@ +# Lamatic.ai Configuration +# Get these from https://studio.lamatic.ai β†’ your project settings + +LAMATIC_API_KEY=your_lamatic_api_key_here +LAMATIC_PROJECT_ID=your_project_id_here + +# Flow endpoint URLs (from your Lamatic Studio deployment) +EXPLAIN_FLOW_URL=https://your-project.lamatic.app/api/explain +QUIZ_FLOW_URL=https://your-project.lamatic.app/api/quiz + +# Optional: Customize the app title +NEXT_PUBLIC_APP_TITLE=Research Paper Explainer diff --git a/kits/research-paper-explainer/.gitignore b/kits/research-paper-explainer/.gitignore new file mode 100644 index 00000000..6a936543 --- /dev/null +++ b/kits/research-paper-explainer/.gitignore @@ -0,0 +1,35 @@ +# 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 +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/kits/research-paper-explainer/README.md b/kits/research-paper-explainer/README.md new file mode 100644 index 00000000..1dfb608d --- /dev/null +++ b/kits/research-paper-explainer/README.md @@ -0,0 +1,199 @@ +# πŸ“š Research Paper Explainer & Quiz Agent + +> A Next.js kit built on [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) that takes any research paper abstract or full text, explains it at your chosen comprehension level, and generates an interactive multiple-choice quiz to test understanding. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Mortarion002/AgentKit/tree/main/kits/research-paper-explainer&root-directory=kits/research-paper-explainer&env=LAMATIC_PROJECT_ENDPOINT,LAMATIC_PROJECT_ID,LAMATIC_PROJECT_API_KEY,LAMATIC_FLOW_ID,QUIZ_FLOW_ID&envDescription=Lamatic%20API%20credentials%20and%20flow%20IDs&envLink=https://studio.lamatic.ai) + +--- + +## 🧠 Problem Statement + +Reading academic research papers is hard. Dense jargon, complex methodologies, and assumed domain knowledge make papers inaccessible to students, curious learners, and even professionals stepping outside their niche. + +**PaperLens** solves this with two AI-powered Lamatic flows: +1. **Explainer Flow** β€” Breaks down any research paper into plain language at three levels: Simple (high school), Intermediate (undergraduate), or Expert. Structured around: Core Problem β†’ Methodology β†’ Key Findings β†’ Real-World Impact. +2. **Quiz Flow** β€” Generates a custom multiple-choice quiz from the paper to reinforce learning and test comprehension, complete with explanations for each answer. + +--- + +## ✨ Features + +- πŸ“„ Paste any research paper abstract or full text +- πŸŽ“ Choose explanation level: Simple / Intermediate / Expert +- 🧩 Generate 3–10 quiz questions with answer feedback & explanations +- πŸ“Š Live score tracking after quiz submission +- ⚑ Two independent Lamatic Flows (explain + quiz) +- 🎨 Clean, distraction-free editorial UI built with Next.js + Tailwind + +--- + +## πŸ—‚ Folder Structure + +``` +kits/research-paper-explainer/ +β”œβ”€β”€ actions/ +β”‚ └── orchestrate.ts # Lamatic flow orchestration (explain + quiz) +β”œβ”€β”€ app/ +β”‚ β”œβ”€β”€ page.tsx # Main UI (input + output panels) +β”‚ β”œβ”€β”€ layout.tsx # Root layout with fonts +β”‚ β”œβ”€β”€ globals.css # Design system & animations +β”‚ └── api/ +β”‚ β”œβ”€β”€ explain/route.ts # API route β†’ explain flow +β”‚ └── quiz/route.ts # API route β†’ quiz flow +β”œβ”€β”€ lib/ +β”‚ └── lamatic-client.ts # Lamatic SDK client +β”œβ”€β”€ flows/ +β”‚ β”œβ”€β”€ explain-flow/ # Exported explain flow from Lamatic Studio +β”‚ └── quiz-flow/ # Exported quiz flow from Lamatic Studio +β”œβ”€β”€ .env.example # Environment variables template +β”œβ”€β”€ config.json # Kit metadata +β”œβ”€β”€ package.json +β”œβ”€β”€ tailwind.config.ts +β”œβ”€β”€ tsconfig.json +└── README.md +``` + +--- + +## πŸš€ Getting Started + +### Prerequisites + +| Tool | Version | +|------|---------| +| Node.js | 18+ | +| npm | 9+ | +| Lamatic Account | [lamatic.ai](https://lamatic.ai) | + +### 1. Clone the Repository + +```bash +git clone https://github.com/Lamatic/AgentKit.git +cd AgentKit/kits/research-paper-explainer +``` + +### 2. Install Dependencies + +```bash +npm install +``` + +### 3. Set Up Lamatic Flows + +1. Sign up at [studio.lamatic.ai](https://studio.lamatic.ai) +2. Create a new project +3. Create two flows using the exported configs in the `flows/` folder: + +#### Explain Flow +- **Trigger:** API Request +- **Schema:** `{ "paperContent": "string", "level": "string" }` +- **LLM Node:** Generate Text with system prompt for structured explanation +- **Output:** `{ "generatedResponse": "{{LLMNode.output.generatedResponse}}" }` + +#### Quiz Flow +- **Trigger:** API Request +- **Schema:** `{ "paperContent": "string", "numQuestions": "string" }` +- **LLM Node:** Generate Text returning JSON quiz format +- **Output:** `{ "generatedResponse": "{{LLMNode.output.generatedResponse}}" }` + +4. Deploy both flows and copy their Flow IDs + +### 4. Configure Environment Variables + +```bash +cp .env.example .env +``` + +Edit `.env` with your actual values: + +```env +LAMATIC_PROJECT_ENDPOINT=https://your-project.lamatic.dev/graphql +LAMATIC_PROJECT_ID=your-project-id +LAMATIC_PROJECT_API_KEY=your-api-key +LAMATIC_FLOW_ID=your-explain-flow-id +QUIZ_FLOW_ID=your-quiz-flow-id +``` + +### 5. Run Locally + +```bash +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000) + +--- + +## 🌐 Deploy to Vercel + +### Option 1 β€” One-click deploy +Click the **Deploy with Vercel** button at the top of this README. + +### Option 2 β€” Manual deploy + +```bash +# Push to GitHub first +git add . +git commit -m "feat: add research-paper-explainer kit" +git push origin main +``` + +Then in Vercel: +1. Import your repository +2. Set **Root Directory** to `kits/research-paper-explainer` +3. Add all environment variables from your `.env` +4. Click **Deploy** + +--- + +## πŸ”‘ Environment Variables + +| Variable | Description | Where to Find | +|---|---|---| +| `LAMATIC_PROJECT_ENDPOINT` | Your Lamatic GraphQL endpoint | Studio β†’ Setup Guide β†’ App Frameworks | +| `LAMATIC_PROJECT_ID` | Your Lamatic project ID | Studio β†’ Setup Guide β†’ App Frameworks | +| `LAMATIC_PROJECT_API_KEY` | Your Lamatic API key | Studio β†’ Get API Key | +| `LAMATIC_FLOW_ID` | Explain flow ID | Studio β†’ Flow β†’ Setup Guide | +| `QUIZ_FLOW_ID` | Quiz flow ID | Studio β†’ Flow β†’ Setup Guide | + +--- + +## πŸ›  Tech Stack + +| Layer | Tech | +|---|---| +| Frontend | Next.js 14, TypeScript, Tailwind CSS | +| AI Orchestration | Lamatic SDK (`lamatic` npm package) | +| LLM | Gemini 2.5 Flash via Lamatic Studio | +| Markdown Rendering | `react-markdown` + `remark-gfm` | +| Deployment | Vercel | + +--- + +## πŸ“– How It Works + +``` +User pastes paper text + ↓ +Next.js app calls Lamatic flow via SDK + ↓ +Lamatic passes text to Gemini 2.5 Flash + ↓ +AI generates explanation or quiz JSON + ↓ +Response polled via checkStatus() + ↓ +Result displayed in the UI +``` + +--- + +## 🀝 Contributing + +Found a bug or want to improve this kit? Open an issue or PR on the [AgentKit repository](https://github.com/Lamatic/AgentKit). + +--- + +## πŸ“„ License + +MIT β€” Built by [Aman Kumar](https://github.com/Mortarion002) as part of the Lamatic AgentKit community. \ No newline at end of file diff --git a/kits/research-paper-explainer/actions/orchestrate.ts b/kits/research-paper-explainer/actions/orchestrate.ts new file mode 100644 index 00000000..e63d5566 --- /dev/null +++ b/kits/research-paper-explainer/actions/orchestrate.ts @@ -0,0 +1,102 @@ +"use server" + +import { lamaticClient } from "@/lib/lamatic-client" + +const EXPLAIN_FLOW_ID = process.env.LAMATIC_FLOW_ID ?? "" +const QUIZ_FLOW_ID = process.env.QUIZ_FLOW_ID ?? "" + +export async function explainPaper( + paperContent: string, + level: string +): Promise<{ success: boolean; data?: string; error?: string }> { + try { + console.log("[explain] Calling flow:", EXPLAIN_FLOW_ID) + + // Step 1 - trigger the flow + const resData = await lamaticClient.executeFlow(EXPLAIN_FLOW_ID, { + paperContent, + level, + }) as any + + console.log("[explain] Initial response:", JSON.stringify(resData)) + + // Step 2 - if async, poll using checkStatus + const requestId = resData?.result?.requestId || resData?.requestId + if (requestId) { + console.log("[explain] Polling for requestId:", requestId) + // poll every 3 seconds, timeout after 60 seconds + const pollData = await lamaticClient.checkStatus(requestId, 3, 60) as any + console.log("[explain] Poll result:", JSON.stringify(pollData)) + + const explanation = + pollData?.data?.output?.result?.generatedResponse || + pollData?.data?.output?.generatedResponse || + pollData?.result?.generatedResponse || + pollData?.generatedResponse || + (typeof pollData === "string" && pollData.length > 10 ? pollData : null) + + if (explanation) return { success: true, data: explanation } + throw new Error("No explanation in poll result: " + JSON.stringify(pollData)) + } + + // Realtime response + const explanation = + resData?.result?.generatedResponse || + resData?.generatedResponse || + (typeof resData?.result === "string" ? resData.result : null) + + if (explanation) return { success: true, data: explanation } + throw new Error("Empty response: " + JSON.stringify(resData)) + + } catch (error) { + console.error("[explain] Error:", error) + return { success: false, error: error instanceof Error ? error.message : "Unknown error" } + } +} + +export async function generateQuiz( + paperContent: string, + numQuestions: number +): Promise<{ success: boolean; data?: any; error?: string }> { + try { + console.log("[quiz] Calling flow:", QUIZ_FLOW_ID) + + const resData = await lamaticClient.executeFlow(QUIZ_FLOW_ID, { + paperContent, + numQuestions: String(numQuestions), + }) as any + + console.log("[quiz] Initial response:", JSON.stringify(resData)) + + const requestId = resData?.result?.requestId || resData?.requestId + if (requestId) { + const pollData = await lamaticClient.checkStatus(requestId, 3, 60) as any + console.log("[quiz] Poll result:", JSON.stringify(pollData)) + + const raw = + pollData?.data?.output?.result?.generatedResponse || + pollData?.data?.output?.generatedResponse || + pollData?.result?.generatedResponse || + pollData?.generatedResponse + + if (raw) { + const match = typeof raw === "string" ? raw.match(/\{[\s\S]*\}/) : null + const parsed = match ? JSON.parse(match[0]) : raw + return { success: true, data: parsed } + } + throw new Error("No quiz in poll result") + } + + const raw = resData?.result?.generatedResponse || resData?.generatedResponse + if (raw) { + const match = typeof raw === "string" ? raw.match(/\{[\s\S]*\}/) : null + const parsed = match ? JSON.parse(match[0]) : raw + return { success: true, data: parsed } + } + + throw new Error("Empty quiz response") + } catch (error) { + console.error("[quiz] Error:", error) + return { success: false, error: error instanceof Error ? error.message : "Unknown error" } + } +} \ No newline at end of file diff --git a/kits/research-paper-explainer/app/api/explain/route.ts b/kits/research-paper-explainer/app/api/explain/route.ts new file mode 100644 index 00000000..cb400eb4 --- /dev/null +++ b/kits/research-paper-explainer/app/api/explain/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from "next/server"; +import { explainPaper } from "@/actions/orchestrate"; + +export async function POST(req: NextRequest) { + try { + const { paperContent, level } = await req.json(); + + if (!paperContent || paperContent.trim().length < 50) { + return NextResponse.json( + { error: "Please provide at least 50 characters of paper content." }, + { status: 400 } + ); + } + + const result = await explainPaper(paperContent, level || "undergraduate"); + + if (!result.success) { + return NextResponse.json({ error: result.error }, { status: 502 }); + } + + return NextResponse.json({ explanation: result.data }); + } catch (err) { + console.error("Explain route error:", err); + return NextResponse.json({ error: "Internal server error." }, { status: 500 }); + } +} \ No newline at end of file diff --git a/kits/research-paper-explainer/app/api/quiz/route.ts b/kits/research-paper-explainer/app/api/quiz/route.ts new file mode 100644 index 00000000..6c799b70 --- /dev/null +++ b/kits/research-paper-explainer/app/api/quiz/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from "next/server"; +import { generateQuiz } from "@/actions/orchestrate"; + +export async function POST(req: NextRequest) { + try { + const { paperContent, numQuestions } = await req.json(); + + if (!paperContent || paperContent.trim().length < 50) { + return NextResponse.json( + { error: "Please provide at least 50 characters of paper content." }, + { status: 400 } + ); + } + + const result = await generateQuiz(paperContent, numQuestions || 5); + + if (!result.success) { + return NextResponse.json({ error: result.error }, { status: 502 }); + } + + return NextResponse.json(result.data); + } catch (err) { + console.error("Quiz route error:", err); + return NextResponse.json({ error: "Internal server error." }, { status: 500 }); + } +} \ No newline at end of file diff --git a/kits/research-paper-explainer/app/globals.css b/kits/research-paper-explainer/app/globals.css new file mode 100644 index 00000000..95ef3b1b --- /dev/null +++ b/kits/research-paper-explainer/app/globals.css @@ -0,0 +1,91 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --ink: #1a1207; + --paper: #f5f0e8; + --cream: #ede7d3; + --amber: #c8860a; + --amber-light: #f0a830; + --amber-pale: #fdf3dc; + --muted: #7a6e5f; + --border: #d6cdb8; + --success: #2d6a4f; + --error: #9b2226; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--paper); + color: var(--ink); + font-family: 'DM Sans', sans-serif; + min-height: 100vh; +} + +.font-serif { + font-family: 'Instrument Serif', serif; +} + +.font-mono { + font-family: 'DM Mono', monospace; +} + +/* Scrollbar */ +::-webkit-scrollbar { width: 6px; } +::-webkit-scrollbar-track { background: var(--cream); } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } + +/* Prose styles for markdown */ +.prose h1, .prose h2, .prose h3 { + font-family: 'Instrument Serif', serif; + color: var(--ink); + margin-top: 1.5em; + margin-bottom: 0.5em; +} +.prose h2 { font-size: 1.25rem; } +.prose h3 { font-size: 1.1rem; } +.prose p { margin-bottom: 0.85em; line-height: 1.75; color: #2e2416; } +.prose strong { color: var(--ink); font-weight: 600; } +.prose ul { padding-left: 1.5em; margin-bottom: 0.85em; } +.prose ul li { margin-bottom: 0.35em; } +.prose ol { padding-left: 1.5em; margin-bottom: 0.85em; } +.prose ol li { margin-bottom: 0.35em; } + +/* Animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} +@keyframes pulse-dot { + 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } + 40% { transform: scale(1); opacity: 1; } +} + +.animate-fade-in { + animation: fadeIn 0.45s ease-out forwards; +} +.animate-fade-in-delay { + animation: fadeIn 0.45s ease-out 0.15s forwards; + opacity: 0; +} + +.loading-dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--amber); + animation: pulse-dot 1.4s ease-in-out infinite; +} +.loading-dot:nth-child(2) { animation-delay: 0.2s; } +.loading-dot:nth-child(3) { animation-delay: 0.4s; } diff --git a/kits/research-paper-explainer/app/layout.tsx b/kits/research-paper-explainer/app/layout.tsx new file mode 100644 index 00000000..794dc00e --- /dev/null +++ b/kits/research-paper-explainer/app/layout.tsx @@ -0,0 +1,27 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: process.env.NEXT_PUBLIC_APP_TITLE || "Research Paper Explainer", + description: "AI-powered research paper explainer and quiz generator built on Lamatic AgentKit", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + + + {children} + + ); +} diff --git a/kits/research-paper-explainer/app/page.tsx b/kits/research-paper-explainer/app/page.tsx new file mode 100644 index 00000000..cc753aa8 --- /dev/null +++ b/kits/research-paper-explainer/app/page.tsx @@ -0,0 +1,427 @@ +"use client"; + +import { useState } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +type Level = "high-school" | "undergraduate" | "expert"; +type Tab = "explain" | "quiz"; + +interface QuizQuestion { + question: string; + options: string[]; + correct: number; + explanation: string; +} + +interface QuizState { + questions: QuizQuestion[]; + answers: (number | null)[]; + submitted: boolean; +} + +const LEVELS: { value: Level; label: string; desc: string }[] = [ + { value: "high-school", label: "Simple", desc: "High school level" }, + { value: "undergraduate", label: "Intermediate", desc: "Undergrad level" }, + { value: "expert", label: "Expert", desc: "Domain specialist" }, +]; + +export default function HomePage() { + const [paperContent, setPaperContent] = useState(""); + const [level, setLevel] = useState("undergraduate"); + const [tab, setTab] = useState("explain"); + const [numQuestions, setNumQuestions] = useState(5); + + const [explanation, setExplanation] = useState(null); + const [quiz, setQuiz] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleExplain() { + if (!paperContent.trim()) return; + setLoading(true); + setError(null); + setExplanation(null); + try { + const res = await fetch("/api/explain", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ paperContent, level }), + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || "Something went wrong."); + setExplanation(typeof data.explanation === "string" ? data.explanation : JSON.stringify(data.explanation, null, 2)); + setTab("explain"); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : "Unknown error"); + } finally { + setLoading(false); + } + } + + async function handleQuiz() { + if (!paperContent.trim()) return; + setLoading(true); + setError(null); + setQuiz(null); + try { + const res = await fetch("/api/quiz", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ paperContent, numQuestions }), + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || "Something went wrong."); + setQuiz({ + questions: data.questions, + answers: new Array(data.questions.length).fill(null), + submitted: false, + }); + setTab("quiz"); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : "Unknown error"); + } finally { + setLoading(false); + } + } + + function handleAnswer(qIdx: number, optIdx: number) { + if (!quiz || quiz.submitted) return; + setQuiz((prev) => { + if (!prev) return prev; + const answers = [...prev.answers]; + answers[qIdx] = optIdx; + return { ...prev, answers }; + }); + } + + function handleSubmitQuiz() { + setQuiz((prev) => prev ? { ...prev, submitted: true } : prev); + } + + const score = quiz?.submitted + ? quiz.questions.filter((q, i) => quiz.answers[i] === q.correct).length + : null; + + return ( +
+ {/* Header */} +
+
+
+

+ PaperLens +

+

+ Research Paper Explainer & Quiz Β· Lamatic AgentKit +

+
+
+ ✦ AI-Powered +
+
+
+ +
+
+ {/* LEFT PANEL β€” Input */} +
+
+ +