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
12 changes: 12 additions & 0 deletions kits/research-paper-explainer/.env.example
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unquoted env values containing whitespace in .env example files.
rg -nP '^[A-Z0-9_]+=.*\s+.*$' --glob '**/.env.example' -C1

Repository: Lamatic/AgentKit

Length of output: 892


Quote env values that contain spaces.

At line 12, the unquoted value can cause dotenv tooling inconsistencies; quote it for portability and consistency with similar values elsewhere in the codebase.

Suggested patch
-NEXT_PUBLIC_APP_TITLE=Research Paper Explainer
+NEXT_PUBLIC_APP_TITLE="Research Paper Explainer"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NEXT_PUBLIC_APP_TITLE=Research Paper Explainer
NEXT_PUBLIC_APP_TITLE="Research Paper Explainer"
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 12-12: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

35 changes: 35 additions & 0 deletions kits/research-paper-explainer/.gitignore
Original file line number Diff line number Diff line change
@@ -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
199 changes: 199 additions & 0 deletions kits/research-paper-explainer/README.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a live demo link to this file? would allow us to view the functionality better, and allow users to see the kit before forking it. otherwise, LGTM. Thanks!

Original file line number Diff line number Diff line change
@@ -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.
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence says the kit accepts a research paper "(URL or abstract)", but the UI currently only supports pasting text into a textarea (no URL fetching/parsing). Either implement URL ingestion or adjust the docs to match the actual input method.

Suggested change
> 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.
> A Next.js kit built on [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) that lets you paste any research paper abstract or full text into the app, explains it at your chosen comprehension level, and generates an interactive multiple-choice quiz to test understanding.

Copilot uses AI. Check for mistakes.

[![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
```
Comment on lines +32 to +54
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The folder structure code block is missing a language specifier. While this doesn't affect rendering, it helps with accessibility and linting compliance.

Suggested fix
-```
+```text
 research-paper-explainer/
 ├── app/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
research-paper-explainer/
├── app/
│ ├── page.tsx # Main UI (input + output panels)
│ ├── layout.tsx # Root layout with fonts
│ ├── globals.css # Design system & animations
│ └── api/
│ ├── explain/route.ts # Calls the Lamatic explain flow
│ └── quiz/route.ts # Calls the Lamatic quiz flow
├── lamatic-config.json # Flow definitions for Lamatic Studio
├── .env.example # Required environment variables
├── package.json
├── tailwind.config.ts
├── tsconfig.json
└── README.md
```
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 32-32: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


---

## 🚀 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.
102 changes: 102 additions & 0 deletions kits/research-paper-explainer/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -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 ?? ""
Comment on lines +5 to +6
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject missing flow IDs before calling Lamatic.

?? "" lets a misconfigured deployment reach executeFlow(""), which turns a setup problem into an opaque upstream failure on the first request. Short-circuit with a clear configuration error before the SDK call.

Suggested fix
 export async function explainPaper(
   paperContent: string,
   level: string
 ): Promise<{ success: boolean; data?: string; error?: string }> {
   try {
+    if (!EXPLAIN_FLOW_ID) {
+      return { success: false, error: "LAMATIC_FLOW_ID is not configured" };
+    }
+
     console.log("[explain] Calling flow:", EXPLAIN_FLOW_ID)
@@
 export async function generateQuiz(
   paperContent: string,
   numQuestions: number
 ): Promise<{ success: boolean; data?: any; error?: string }> {
   try {
+    if (!QUIZ_FLOW_ID) {
+      return { success: false, error: "QUIZ_FLOW_ID is not configured" };
+    }
+
     console.log("[quiz] Calling flow:", QUIZ_FLOW_ID)

Also applies to: 12-19, 61-67


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))
Comment on lines +21 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact Lamatic payloads from logs and error strings.

These JSON.stringify(...) calls can capture the submitted paper text and generated explanations/quizzes. The explain path also bakes the raw payload into error.message, which is later returned by the API route. Keep request IDs/statuses only.

Suggested fix
-    console.log("[explain] Initial response:", JSON.stringify(resData))
+    console.log("[explain] Initial response received", {
+      requestId: resData?.result?.requestId ?? resData?.requestId ?? null,
+    })
@@
-      console.log("[explain] Poll result:", JSON.stringify(pollData))
+      console.log("[explain] Poll completed", { requestId })
@@
-      throw new Error("No explanation in poll result: " + JSON.stringify(pollData))
+      throw new Error("Explain flow returned no generated response")
@@
-    throw new Error("Empty response: " + JSON.stringify(resData))
+    throw new Error("Explain flow returned no generated response")
@@
-    console.log("[quiz] Initial response:", JSON.stringify(resData))
+    console.log("[quiz] Initial response received", {
+      requestId: resData?.result?.requestId ?? resData?.requestId ?? null,
+    })
@@
-      console.log("[quiz] Poll result:", JSON.stringify(pollData))
+      console.log("[quiz] Poll completed", { requestId })

Also applies to: 39-49, 69-74


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 }
Comment on lines +82 to +85
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't return malformed quiz data as a success.

If the model returns prose or any non-JSON string, match is null and the raw string is returned with success: true. The quiz API then violates its own contract for callers expecting questions[]. Validate the parsed shape before returning.

Suggested fix
-      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 }
-      }
+      if (raw) {
+        const parsed =
+          typeof raw === "string"
+            ? (() => {
+                const match = raw.match(/\{[\s\S]*\}/)
+                if (!match) throw new Error("Quiz response did not contain JSON")
+                return JSON.parse(match[0])
+              })()
+            : raw
+
+        if (!Array.isArray(parsed?.questions)) {
+          throw new Error("Quiz response is missing questions[]")
+        }
+
+        return { success: true, data: parsed }
+      }
@@
-    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 }
-    }
+    if (raw) {
+      const parsed =
+        typeof raw === "string"
+          ? (() => {
+              const match = raw.match(/\{[\s\S]*\}/)
+              if (!match) throw new Error("Quiz response did not contain JSON")
+              return JSON.parse(match[0])
+            })()
+          : raw
+
+      if (!Array.isArray(parsed?.questions)) {
+        throw new Error("Quiz response is missing questions[]")
+      }
+
+      return { success: true, data: parsed }
+    }

Also applies to: 90-94

}
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" }
}
}
26 changes: 26 additions & 0 deletions kits/research-paper-explainer/app/api/explain/route.ts
Original file line number Diff line number Diff line change
@@ -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 }
);
}
Comment on lines +6 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard input types before calling .trim().

At Line 7, non-string paperContent (e.g., object/null) will throw and end up as a 500. Validate type and return 400 for invalid payloads.

Suggested patch
-    const { paperContent, level } = await req.json();
+    const body = await req.json();
+    const paperContent = typeof body?.paperContent === "string" ? body.paperContent : "";
+    const level = typeof body?.level === "string" ? body.level : undefined;
 
-    if (!paperContent || paperContent.trim().length < 50) {
+    if (!paperContent || paperContent.trim().length < 50) {
       return NextResponse.json(
         { error: "Please provide a research paper abstract or content (at least 50 characters)." },
         { status: 400 }
       );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { paperContent, level } = await req.json();
if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide a research paper abstract or content (at least 50 characters)." },
{ status: 400 }
);
}
const body = await req.json();
const paperContent = typeof body?.paperContent === "string" ? body.paperContent : "";
const level = typeof body?.level === "string" ? body.level : undefined;
if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide a research paper abstract or content (at least 50 characters)." },
{ 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 });
}
}
Loading