feat: add Research Paper Explainer & Quiz Agent#72
feat: add Research Paper Explainer & Quiz Agent#72Mortarion002 wants to merge 2 commits intoLamatic:mainfrom
Conversation
📝 WalkthroughWalkthroughAdds a new "Research Paper Explainer & Quiz Agent" Next.js kit: UI, styling, API routes, Lamatic client/actions, Lamatic flow configs, docs, and project configuration files enabling explanation and quiz generation from research paper text. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Page as app/page.tsx
participant API as /api/explain
participant Orch as orchestrate.ts
participant Lamatic as Lamatic Flow
User->>Page: Provide paperContent + level
Page->>API: POST { paperContent, level }
API->>Orch: explainPaper(paperContent, level)
Orch->>Lamatic: executeFlow(flowId, inputs)
Lamatic-->>Orch: initialResponse (maybe requestId)
Orch->>Lamatic: checkStatus(requestId) [poll]
Lamatic-->>Orch: final generatedResponse
Orch-->>API: { success, data: explanation }
API-->>Page: { explanation }
Page->>User: Render explanation (markdown)
sequenceDiagram
actor User
participant Page as app/page.tsx
participant API as /api/quiz
participant Orch as orchestrate.ts
participant Lamatic as Lamatic Flow
User->>Page: Provide paperContent + numQuestions
Page->>API: POST { paperContent, numQuestions }
API->>Orch: generateQuiz(paperContent, numQuestions)
Orch->>Lamatic: executeFlow(quizFlowId, inputs)
Lamatic-->>Orch: initialResponse (maybe requestId)
Orch->>Lamatic: checkStatus(requestId) [poll]
Lamatic-->>Orch: generatedResponse (string or JSON)
Orch->>Orch: extract/parse JSON if needed
Orch-->>API: { success, data: quizObject }
API-->>Page: quizObject
Page->>User: Render interactive quiz, accept answers, compute score
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can approve the review once all CodeRabbit's comments are resolved.Enable the |
There was a problem hiding this comment.
Pull request overview
Adds a new Next.js-based kit under kits/research-paper-explainer/ that connects to two Lamatic flows to (1) explain pasted research paper text at different difficulty levels and (2) generate a multiple-choice quiz with scoring.
Changes:
- New Next.js 14 + Tailwind UI for paper explanation + quiz-taking.
- New API routes that proxy requests to Lamatic flow endpoints for explain/quiz.
- New kit packaging/docs/config (Lamatic config, env example, build tooling).
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| kits/research-paper-explainer/tsconfig.json | TypeScript config for the kit’s Next.js app. |
| kits/research-paper-explainer/tailwind.config.ts | Tailwind content scanning + theme scaffold. |
| kits/research-paper-explainer/postcss.config.mjs | PostCSS setup for Tailwind + autoprefixer. |
| kits/research-paper-explainer/package.json | Next.js/React deps and scripts for the kit. |
| kits/research-paper-explainer/next.config.mjs | Next.js config placeholder. |
| kits/research-paper-explainer/lamatic-config.json | Declares the two Lamatic flows (explain + quiz). |
| kits/research-paper-explainer/app/page.tsx | Client UI for input, explanation rendering, quiz UX + scoring. |
| kits/research-paper-explainer/app/layout.tsx | Root layout + Google Fonts wiring + metadata. |
| kits/research-paper-explainer/app/globals.css | Global styles, design tokens, and animations. |
| kits/research-paper-explainer/app/api/explain/route.ts | Server route that calls the Lamatic explain flow. |
| kits/research-paper-explainer/app/api/quiz/route.ts | Server route that calls the Lamatic quiz flow + parses JSON. |
| kits/research-paper-explainer/README.md | Kit documentation + setup + expected quiz JSON shape. |
| kits/research-paper-explainer/.gitignore | Kit-local ignores for Next/Vercel/node artifacts. |
| kits/research-paper-explainer/.env.example | Example env vars for Lamatic + flow URLs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| ], | ||
| "env": { | ||
| "required": ["LAMATIC_API_KEY", "LAMATIC_PROJECT_ID"], |
There was a problem hiding this comment.
env.required lists LAMATIC_PROJECT_ID, but this kit's server routes never read it; conversely, the app does require EXPLAIN_FLOW_URL and QUIZ_FLOW_URL (used in the API routes) but they aren’t declared here. Please align this env list with what the kit actually uses (either add the flow URL vars as required and/or remove/justify LAMATIC_PROJECT_ID).
| "required": ["LAMATIC_API_KEY", "LAMATIC_PROJECT_ID"], | |
| "required": ["LAMATIC_API_KEY", "EXPLAIN_FLOW_URL", "QUIZ_FLOW_URL"], |
| @@ -0,0 +1,68 @@ | |||
| { | |||
| "name": "Research Paper Explainer & Quiz Agent", | |||
| "description": "An AI agent that takes any research paper (URL or abstract), explains it in plain language at multiple levels, and generates an interactive quiz to test understanding.", | |||
There was a problem hiding this comment.
The config description claims input can be a research paper URL, but the frontend only accepts raw text input and the backend does not fetch or parse URLs. To avoid misleading users, either add URL support end-to-end or remove the URL claim from the description.
| "description": "An AI agent that takes any research paper (URL or abstract), explains it in plain language at multiple levels, and generates an interactive quiz to test understanding.", | |
| "description": "An AI agent that takes any research paper text (such as an abstract or full paper), explains it in plain language at multiple levels, and generates an interactive quiz to test understanding.", |
| {loading && tab === "explain" ? "Explaining…" : "✦ Explain This Paper"} | ||
| </button> | ||
| <button | ||
| onClick={handleQuiz} | ||
| disabled={loading || paperContent.length < 50} | ||
| className="w-full py-3.5 rounded-xl text-sm font-semibold transition-all duration-200" | ||
| style={{ | ||
| background: "#fff", | ||
| color: "var(--ink)", | ||
| border: "1.5px solid var(--border)", | ||
| cursor: loading || paperContent.length < 50 ? "not-allowed" : "pointer", | ||
| opacity: paperContent.length < 50 ? 0.5 : 1, | ||
| }} | ||
| > | ||
| {loading && tab === "quiz" ? "Generating Quiz…" : "◈ Generate Quiz"} | ||
| </button> |
There was a problem hiding this comment.
loading is global, but the button labels depend on tab (which only changes after the request finishes). If you click “Generate Quiz” while on the Explanation tab, the quiz button won’t show “Generating Quiz…” during the request (and vice-versa for Explain). Track the in-flight action separately (e.g., loadingAction: 'explain' | 'quiz' | null) or set the tab/action before starting the fetch so the correct label is shown.
| setQuiz({ | ||
| questions: data.questions, | ||
| answers: new Array(data.questions.length).fill(null), |
There was a problem hiding this comment.
handleQuiz() assumes the response always contains a valid questions array; if Lamatic returns malformed JSON or an unexpected shape, data.questions.length will throw and break the page. Add runtime validation before using data.questions (and surface a friendly error) to prevent a hard crash.
| setQuiz({ | |
| questions: data.questions, | |
| answers: new Array(data.questions.length).fill(null), | |
| const questions = Array.isArray(data?.questions) ? data.questions : null; | |
| if (!questions || questions.length === 0) { | |
| throw new Error("Received invalid quiz data. Please try again."); | |
| } | |
| const hasValidShape = questions.every( | |
| (q: any) => | |
| q && | |
| typeof q.question === "string" && | |
| Array.isArray(q.options) && | |
| typeof q.correct === "number" && | |
| typeof q.explanation === "string" | |
| ); | |
| if (!hasValidShape) { | |
| throw new Error("Received malformed quiz questions. Please try again."); | |
| } | |
| setQuiz({ | |
| questions, | |
| answers: new Array(questions.length).fill(null), |
| const data = await response.json(); | ||
| const raw = data?.result || data?.output || data; | ||
|
|
||
| // Parse if the LLM returned a JSON string | ||
| let parsed: { questions: QuizQuestion[] }; | ||
| if (typeof raw === "string") { | ||
| const match = raw.match(/\{[\s\S]*\}/); | ||
| parsed = JSON.parse(match ? match[0] : raw); | ||
| } else { | ||
| parsed = raw; | ||
| } | ||
|
|
||
| return NextResponse.json(parsed); |
There was a problem hiding this comment.
The quiz endpoint can return invalid data to the client: parsed is assumed to be { questions: QuizQuestion[] } without validating that raw actually has a questions array (and JSON.parse failures fall through to the generic 500). Please add explicit parsing/validation (e.g., try/catch around JSON.parse, ensure parsed.questions is an array with the expected fields, and return a 502 with a clear error when Lamatic returns malformed output) so the UI doesn’t crash on unexpected responses.
| const { paperContent, numQuestions } = await req.json(); | ||
|
|
||
| if (!paperContent || paperContent.trim().length < 50) { | ||
| return NextResponse.json( | ||
| { error: "Please provide paper content before generating a quiz." }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| const flowUrl = process.env.QUIZ_FLOW_URL; | ||
| const apiKey = process.env.LAMATIC_API_KEY; | ||
|
|
||
| if (!flowUrl || !apiKey) { | ||
| return NextResponse.json( | ||
| { error: "Server misconfiguration: missing QUIZ_FLOW_URL or LAMATIC_API_KEY." }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
|
|
||
| const response = await fetch(flowUrl, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| paperContent, | ||
| numQuestions: numQuestions || 5, | ||
| }), |
There was a problem hiding this comment.
numQuestions is forwarded to the Lamatic flow without type/range validation. Since request JSON can provide a string/float/negative/very large value, consider coercing to a number and clamping to the supported UI range (3–10) before calling the flow (and returning 400 on invalid input).
|
|
||
| > 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. | ||
|
|
||
| [](https://vercel.com/new/clone?repository-url=https://github.com/Mortarion002/AgentKit/tree/main/apps/research-paper-explainer) |
There was a problem hiding this comment.
The Vercel deploy button URL points to a personal fork and to /apps/research-paper-explainer, but this kit lives under kits/research-paper-explainer in the main Lamatic/AgentKit repo. This link will fail or deploy the wrong directory; update it to use repository-url=https://github.com/Lamatic/AgentKit plus root-directory=kits/research-paper-explainer (and ideally include required env vars like other kits do).
| [](https://vercel.com/new/clone?repository-url=https://github.com/Mortarion002/AgentKit/tree/main/apps/research-paper-explainer) | |
| [](https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits/research-paper-explainer) |
| @@ -0,0 +1,155 @@ | |||
| # 📚 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. | |||
There was a problem hiding this comment.
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.
| > 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. |
| 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 { |
There was a problem hiding this comment.
The UI and API enforce a 50-character minimum, but handleExplain() / handleQuiz() only early-return on empty input (not < 50). This creates inconsistent behavior (e.g., programmatic calls still fire a request and then show a server error). Consider checking paperContent.trim().length < 50 on the client too and setting a user-facing error before sending the request.
| 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 flowUrl = process.env.EXPLAIN_FLOW_URL; | ||
| const apiKey = process.env.LAMATIC_API_KEY; | ||
|
|
||
| if (!flowUrl || !apiKey) { | ||
| return NextResponse.json( | ||
| { error: "Server misconfiguration: missing EXPLAIN_FLOW_URL or LAMATIC_API_KEY." }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
|
|
||
| const response = await fetch(flowUrl, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| body: JSON.stringify({ paperContent, level: level || "undergraduate" }), | ||
| }); |
There was a problem hiding this comment.
level is passed through to the Lamatic flow without validation. To avoid unexpected prompts/behavior, validate that level is one of the supported values (high-school/undergraduate/expert) and return 400 (or default safely) when it isn’t.
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (3)
kits/research-paper-explainer/.env.example (1)
5-5: Clarify or removeLAMATIC_PROJECT_IDfrom runtime template.At Line 5, this variable is declared for setup, but
kits/research-paper-explainer/app/api/explain/route.ts(Line 14-15) andkits/research-paper-explainer/app/api/quiz/route.ts(Line 14-15) don’t consume it. Consider documenting why it is required to reduce setup friction.kits/research-paper-explainer/app/api/quiz/route.ts (1)
24-34: Consider adding a timeout to the external fetch call.The fetch to
flowUrlhas no timeout configured. If the Lamatic service is slow or unresponsive, this request will hang indefinitely, potentially causing connection pool exhaustion or poor user experience.Suggested fix using AbortController
+ const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 30000); + const response = await fetch(flowUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ paperContent, numQuestions: numQuestions || 5, }), + signal: controller.signal, }); + + clearTimeout(timeoutId);kits/research-paper-explainer/app/globals.css (1)
61-76: Consider using kebab-case for keyframe names.For consistency with CSS naming conventions, keyframe names like
fadeIncould befade-in. This is a minor style preference flagged by Stylelint.Optional fix
-@keyframes fadeIn { +@keyframes fade-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } .animate-fade-in { - animation: fadeIn 0.45s ease-out forwards; + animation: fade-in 0.45s ease-out forwards; } .animate-fade-in-delay { - animation: fadeIn 0.45s ease-out 0.15s forwards; + animation: fade-in 0.45s ease-out 0.15s forwards; opacity: 0; }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e93065c5-6722-432d-8ec8-6a4562772468
⛔ Files ignored due to path filters (1)
kits/research-paper-explainer/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
kits/research-paper-explainer/.env.examplekits/research-paper-explainer/.gitignorekits/research-paper-explainer/README.mdkits/research-paper-explainer/app/api/explain/route.tskits/research-paper-explainer/app/api/quiz/route.tskits/research-paper-explainer/app/globals.csskits/research-paper-explainer/app/layout.tsxkits/research-paper-explainer/app/page.tsxkits/research-paper-explainer/lamatic-config.jsonkits/research-paper-explainer/next.config.mjskits/research-paper-explainer/package.jsonkits/research-paper-explainer/postcss.config.mjskits/research-paper-explainer/tailwind.config.tskits/research-paper-explainer/tsconfig.json
| QUIZ_FLOW_URL=https://your-project.lamatic.app/api/quiz | ||
|
|
||
| # Optional: Customize the app title | ||
| NEXT_PUBLIC_APP_TITLE=Research Paper Explainer |
There was a problem hiding this comment.
🧩 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' -C1Repository: 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.
| 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)
| 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 } | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
| 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 response = await fetch(flowUrl, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| body: JSON.stringify({ paperContent, level: level || "undergraduate" }), | ||
| }); |
There was a problem hiding this comment.
Add a timeout to the Lamatic upstream call.
At Line 24, the external fetch has no timeout/abort path. A slow upstream can stall this route and degrade availability.
Suggested patch
- const response = await fetch(flowUrl, {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 15000);
+ const response = await fetch(flowUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({ paperContent, level: level || "undergraduate" }),
+ signal: controller.signal,
});
+ clearTimeout(timeout);📝 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.
| const response = await fetch(flowUrl, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify({ paperContent, level: level || "undergraduate" }), | |
| }); | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), 15000); | |
| const response = await fetch(flowUrl, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify({ paperContent, level: level || "undergraduate" }), | |
| signal: controller.signal, | |
| }); | |
| clearTimeout(timeout); |
| // Parse if the LLM returned a JSON string | ||
| let parsed: { questions: QuizQuestion[] }; | ||
| if (typeof raw === "string") { | ||
| const match = raw.match(/\{[\s\S]*\}/); | ||
| parsed = JSON.parse(match ? match[0] : raw); | ||
| } else { | ||
| parsed = raw; | ||
| } |
There was a problem hiding this comment.
Validate parsed quiz structure before returning.
The QuizQuestion interface is defined but never used for runtime validation. If the LLM returns malformed JSON (e.g., missing questions array, wrong types), the client will crash. Consider adding basic structure validation.
Suggested validation
let parsed: { questions: QuizQuestion[] };
if (typeof raw === "string") {
const match = raw.match(/\{[\s\S]*\}/);
parsed = JSON.parse(match ? match[0] : raw);
} else {
parsed = raw;
}
+ if (!Array.isArray(parsed?.questions) || parsed.questions.length === 0) {
+ console.error("Invalid quiz structure:", parsed);
+ return NextResponse.json(
+ { error: "The AI returned an invalid quiz format. Please try again." },
+ { status: 502 }
+ );
+ }
+
return NextResponse.json(parsed);📝 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.
| // Parse if the LLM returned a JSON string | |
| let parsed: { questions: QuizQuestion[] }; | |
| if (typeof raw === "string") { | |
| const match = raw.match(/\{[\s\S]*\}/); | |
| parsed = JSON.parse(match ? match[0] : raw); | |
| } else { | |
| parsed = raw; | |
| } | |
| // Parse if the LLM returned a JSON string | |
| let parsed: { questions: QuizQuestion[] }; | |
| if (typeof raw === "string") { | |
| const match = raw.match(/\{[\s\S]*\}/); | |
| parsed = JSON.parse(match ? match[0] : raw); | |
| } else { | |
| parsed = raw; | |
| } | |
| if (!Array.isArray(parsed?.questions) || parsed.questions.length === 0) { | |
| console.error("Invalid quiz structure:", parsed); | |
| return NextResponse.json( | |
| { error: "The AI returned an invalid quiz format. Please try again." }, | |
| { status: 502 } | |
| ); | |
| } | |
| return NextResponse.json(parsed); |
| 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, | ||
| }); |
There was a problem hiding this comment.
Add defensive validation for quiz API response.
The code assumes data.questions exists and is an array. If the API returns an unexpected shape (e.g., questions is undefined or malformed), calling .length will throw a runtime error.
Suggested fix
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Something went wrong.");
+ if (!Array.isArray(data.questions)) {
+ throw new Error("Invalid quiz response format.");
+ }
setQuiz({
questions: data.questions,
answers: new Array(data.questions.length).fill(null),
submitted: false,
});📝 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.
| 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, | |
| }); | |
| const data = await res.json(); | |
| if (!res.ok) throw new Error(data.error || "Something went wrong."); | |
| if (!Array.isArray(data.questions)) { | |
| throw new Error("Invalid quiz response format."); | |
| } | |
| setQuiz({ | |
| questions: data.questions, | |
| answers: new Array(data.questions.length).fill(null), | |
| submitted: false, | |
| }); |
| ``` | ||
| 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 | ||
| ``` |
There was a problem hiding this comment.
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.
| ``` | |
| 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)
- Built two Lamatic flows: explain-flow and quiz-flow - Configured API Request trigger with paperContent + level schema - Added Generate Text node with Gemini 2.5 Flash (switched from Groq after credential issues) - Fixed API Response output mapping to use LLMNode.output.generatedResponse - Recreated Generate Text node to resolve invalid node reference error - Integrated Lamatic SDK (npm i lamatic) replacing raw GraphQL fetch calls - Fixed env variables to match Lamatic SDK format (LAMATIC_PROJECT_ENDPOINT, LAMATIC_PROJECT_API_KEY) - Implemented async polling via lamaticClient.checkStatus() for flow responses - Added recursive text extraction to handle nested Lamatic response structure - Created actions/orchestrate.ts and lib/lamatic-client.ts following official sample kit pattern - Built Next.js 14 frontend with explain + quiz panels - Added 3-level explanation (Simple / Intermediate / Expert) - Added interactive MCQ quiz with live scoring and answer explanations - Exported flows to flows/ folder - Added config.json with kit metadata - Updated README with c
There was a problem hiding this comment.
Actionable comments posted: 7
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 15648233-3f97-4f9c-8673-2d0045af110b
⛔ Files ignored due to path filters (1)
kits/research-paper-explainer/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (16)
kits/research-paper-explainer/README.mdkits/research-paper-explainer/actions/orchestrate.tskits/research-paper-explainer/app/api/explain/route.tskits/research-paper-explainer/app/api/quiz/route.tskits/research-paper-explainer/flows/explain-flow/README.mdkits/research-paper-explainer/flows/explain-flow/config.jsonkits/research-paper-explainer/flows/explain-flow/inputs.jsonkits/research-paper-explainer/flows/explain-flow/meta.jsonkits/research-paper-explainer/flows/quiz-flow/README.mdkits/research-paper-explainer/flows/quiz-flow/config.jsonkits/research-paper-explainer/flows/quiz-flow/inputs.jsonkits/research-paper-explainer/flows/quiz-flow/meta.jsonkits/research-paper-explainer/lamatic-config.jsonkits/research-paper-explainer/lib/lamatic-client.tskits/research-paper-explainer/next.config.mjskits/research-paper-explainer/package.json
✅ Files skipped from review due to trivial changes (5)
- kits/research-paper-explainer/flows/quiz-flow/meta.json
- kits/research-paper-explainer/flows/explain-flow/inputs.json
- kits/research-paper-explainer/flows/quiz-flow/inputs.json
- kits/research-paper-explainer/package.json
- kits/research-paper-explainer/flows/explain-flow/meta.json
🚧 Files skipped from review as they are similar to previous changes (3)
- kits/research-paper-explainer/app/api/quiz/route.ts
- kits/research-paper-explainer/app/api/explain/route.ts
- kits/research-paper-explainer/lamatic-config.json
| const EXPLAIN_FLOW_ID = process.env.LAMATIC_FLOW_ID ?? "" | ||
| const QUIZ_FLOW_ID = process.env.QUIZ_FLOW_ID ?? "" |
There was a problem hiding this comment.
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
| 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)) |
There was a problem hiding this comment.
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
| 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 } |
There was a problem hiding this comment.
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
| "id": "187c2f4b-c23d-4545-abef-73dc897d6b7d", | ||
| "role": "user", | ||
| "content": "Explain this research paper at {{triggerNode_1.output.level}}level:\n{{triggerNode_1.output.paperContent}}" |
There was a problem hiding this comment.
Fix the missing space in the level prompt.
This renders as Simplelevel / Expertlevel, which weakens the only instruction that selects the explanation depth.
Suggested fix
- "content": "Explain this research paper at {{triggerNode_1.output.level}}level:\n{{triggerNode_1.output.paperContent}}"
+ "content": "Explain this research paper at the {{triggerNode_1.output.level}} level:\n{{triggerNode_1.output.paperContent}}"| export const lamaticClient = new Lamatic({ | ||
| endpoint: process.env.LAMATIC_PROJECT_ENDPOINT ?? "", | ||
| projectId: process.env.LAMATIC_PROJECT_ID ?? "", | ||
| apiKey: process.env.LAMATIC_PROJECT_API_KEY ?? "", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "lamatic-client.ts" | head -20Repository: Lamatic/AgentKit
Length of output: 492
🏁 Script executed:
git ls-files | grep "lamatic-client"Repository: Lamatic/AgentKit
Length of output: 474
🏁 Script executed:
find . -name "*lamatic*" -type f 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 1063
🏁 Script executed:
ls -la kits/ 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 651
🏁 Script executed:
find . -path "*research-paper-explainer*" -type d 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 473
🏁 Script executed:
rg "Lamatic" -l 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 794
🏁 Script executed:
git ls-files | grep -i lamaticRepository: Lamatic/AgentKit
Length of output: 1023
🏁 Script executed:
git ls-files | head -50Repository: Lamatic/AgentKit
Length of output: 2145
🏁 Script executed:
ls -la kits/research-paper-explainer/lib/ 2>/dev/null || echo "Directory not found"Repository: Lamatic/AgentKit
Length of output: 234
🏁 Script executed:
pwd && git rev-parse --show-toplevel 2>/dev/null && ls -laRepository: Lamatic/AgentKit
Length of output: 1090
🏁 Script executed:
rg "LAMATIC_PROJECT" -A 2 -B 2 2>/dev/null | head -50Repository: Lamatic/AgentKit
Length of output: 3016
🏁 Script executed:
rg "process\.env\." -t ts -t tsx | grep -i "??" | head -30Repository: Lamatic/AgentKit
Length of output: 87
🏁 Script executed:
rg "throw new Error" -A 2 -B 2 | grep -A 2 -B 2 -i "env\|config" | head -40Repository: Lamatic/AgentKit
Length of output: 2650
🏁 Script executed:
find . -name ".env*" -o -name "*.config*" -o -name "server.ts" 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 866
🏁 Script executed:
find . -name "package.json" -type f 2>/dev/null | xargs grep -l "lamatic" 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 365
🏁 Script executed:
rg "lamaticClient\|from.*lamatic" --type ts --type tsx 2>/dev/null | head -30Repository: Lamatic/AgentKit
Length of output: 42
🏁 Script executed:
rg "export const.*Client\s*=" --type ts --type tsx 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 42
🏁 Script executed:
rg "lamatic\|LAMATIC" --type ts --type tsx --type js -i 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 42
🌐 Web query:
Lamatic SDK TypeScript constructor requirements endpoint projectId apiKey
💡 Result:
The Lamatic SDK TypeScript constructor requires a configuration object with three mandatory parameters: endpoint (the Lamatic API endpoint), projectId (your project ID), and either apiKey or accessToken (but not both) for authentication. Example: import { Lamatic } from "lamatic"; const lamatic = new Lamatic({ endpoint: "your-endpoint", // Required projectId: "your-project-id", // Required apiKey: "your-api-key", // Required if not using accessToken // OR accessToken: "your-access-token" }); The constructor throws an error if any required field is missing or if neither apiKey nor accessToken is provided.
Citations:
- 1: https://lamatic.ai/docs/sdk
- 2: https://lamatic.ai/docs/sdk/next
- 3: https://lamatic.ai/
- 4: https://building.theatlantic.com/simplifying-lamatic-flow-execution-with-the-lamatic-sdk-ef2d1355aabb
- 5: https://github.com/Lamatic/lamatic-sdk
🏁 Script executed:
rg "if\s*\(!.*process\.env|if\s*\(!.*process\.env.*\)" --type ts --type tsx -A 2 | head -40Repository: Lamatic/AgentKit
Length of output: 87
🏁 Script executed:
rg "Missing.*configuration|required.*environment|throw.*env" --type ts --type tsx --type js 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 42
🏁 Script executed:
find . -path "*agentic*lamatic*" 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 257
🏁 Script executed:
rg "new Lamatic" --type ts --type tsx 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 42
🏁 Script executed:
rg "getRequiredEnv|requireEnv|validateEnv" --type ts --type tsx --type js 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 42
🏁 Script executed:
rg "process\.env\." --type ts --type tsx | grep -v "??" | head -30Repository: Lamatic/AgentKit
Length of output: 87
🏁 Script executed:
git log --oneline -20 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 119
🏁 Script executed:
git ls-files | wc -l && git ls-files | head -100Repository: Lamatic/AgentKit
Length of output: 4810
🏁 Script executed:
rg "endpoint.*projectId\|projectId.*endpoint" --type ts --type tsx --type js 2>/dev/nullRepository: Lamatic/AgentKit
Length of output: 42
🏁 Script executed:
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) 2>/dev/null | wc -lRepository: Lamatic/AgentKit
Length of output: 63
🏁 Script executed:
ls -la 2>/dev/null && echo "---" && ls -la kits 2>/dev/null || trueRepository: Lamatic/AgentKit
Length of output: 1648
🏁 Script executed:
find . -name "*.ts" -type f 2>/dev/null | xargs grep -l "new.*Client\|export const.*client" 2>/dev/null | head -20Repository: Lamatic/AgentKit
Length of output: 42
🌐 Web query:
"fail fast" pattern environment variable validation TypeScript Node.js
💡 Result:
In Node.js/TypeScript, the “fail fast” env-var validation pattern means: load env vars once at process start, validate/coerce them against a schema, and crash immediately (exit code 1 / throw) if anything is missing or invalid—so you never reach “runtime” with a broken configuration.
Typical structure
src/env.ts(orsrc/config.ts) is imported first from your entrypoint (src/index.ts/src/server.ts)- It:
- loads
.env(optional, e.g. viadotenv) - validates + coerces
- exports a typed config object (so the rest of the app never reads
process.envdirectly)
- loads
Option A (common): envalid (built for fail-fast)
cleanEnv() exits (Node) or throws if required vars are missing/invalid by default. [1]
// src/env.ts
import 'dotenv/config';
import { cleanEnv, str, num, bool, url } from 'envalid';
export const env = cleanEnv(process.env, {
NODE_ENV: str({ choices: ['development', 'test', 'production'] }),
PORT: num({ default: 3000 }),
DATABASE_URL: url(),
LOG_JSON: bool({ default: false }),
});// src/index.ts (import first!)
import { env } from './env';
import { startServer } from './server';
startServer({ port: env.PORT });Option B: convict (schema + early validation)
Convict promotes a config schema and validation/early failures when config is wrong. [2]
Option C: presence-only guard with .env.example
If you mainly want “did we define all required keys?”, dotenv-safe loads .env and throws if any variables listed in .env.example are missing. [3]
Sources: envalid (fail-fast default behavior) [1], convict (schema + early validation) [2], dotenv-safe (throws on missing keys vs .env.example) [3].
🏁 Script executed:
cat -n kits/research-paper-explainer/lib/lamatic-client.tsRepository: Lamatic/AgentKit
Length of output: 351
🏁 Script executed:
cat -n kits/agentic/generation/lib/lamatic-client.tsRepository: Lamatic/AgentKit
Length of output: 842
🏁 Script executed:
cat -n kits/embed/sheets/lib/lamatic-client.tsRepository: Lamatic/AgentKit
Length of output: 833
🏁 Script executed:
cat -n kits/sample/content-generation/lib/lamatic-client.tsRepository: Lamatic/AgentKit
Length of output: 842
Fail fast on missing Lamatic configuration.
This file lacks the validation pattern used in other kits. Defaulting endpoint, projectId, and apiKey to "" still creates a client, so a bad deploy fails only during the first flow call instead of at startup. Validate required env vars before constructing the SDK client.
Suggested fix
+const endpoint = process.env.LAMATIC_PROJECT_ENDPOINT;
+const projectId = process.env.LAMATIC_PROJECT_ID;
+const apiKey = process.env.LAMATIC_PROJECT_API_KEY;
+
+if (!endpoint || !projectId || !apiKey) {
+ throw new Error(
+ "Missing Lamatic configuration: LAMATIC_PROJECT_ENDPOINT, LAMATIC_PROJECT_ID, and LAMATIC_PROJECT_API_KEY are required."
+ );
+}
+
export const lamaticClient = new Lamatic({
- endpoint: process.env.LAMATIC_PROJECT_ENDPOINT ?? "",
- projectId: process.env.LAMATIC_PROJECT_ID ?? "",
- apiKey: process.env.LAMATIC_PROJECT_API_KEY ?? "",
+ endpoint,
+ projectId,
+ apiKey,
});| async headers() { | ||
| return [ | ||
| { | ||
| source: '/api/:path*', | ||
| headers: [{ key: 'Connection', value: 'keep-alive' }], | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For Next.js 14.2.5 on Vercel, is adding Connection: keep-alivevianext.config.mjs -> headers()for/api/:path* recommended or invalid under HTTP/2+?
💡 Result:
Adding Connection: keep-alive via next.config.mjs -> headers for /api/:path* is invalid under HTTP/2+, which Vercel uses to serve Next.js deployments. HTTP/2 prohibits connection-specific header fields like Connection (RFC 7540, section 8.1.2.2 and 9.1.2). Servers/clients must not generate them and should treat messages containing them as malformed (though many ignore them). Vercel automatically serves over HTTP/2 (h2 via ALPN/TLS), making the header meaningless and potentially problematic. Keep-alive is persistent by default in HTTP/2 without needing this header. Next.js docs show headers for custom/security headers (e.g., CORS, HSTS) but not Connection. On Vercel, API routes run in serverless/edge functions where connection management is handled by the platform.
Citations:
- 1: https://nextjs.org/docs/14/app/api-reference/next-config-js/headers
- 2: https://nextjs.org/docs/pages/api-reference/config/next-config-js/headers
- 3: https://vercel.com/docs/headers
- 4: https://stackoverflow.com/questions/70718831/how-do-i-convey-keep-alive-metadata-when-the-http-2-protocol-is-used
- 5: Next.js server returns
connection: closeheader for all routes vercel/next.js#51592 - 6: HTTP/2 Support vercel/next.js#10842
- 7: https://community.vercel.com/t/nextjs-vercel-serving-the-entire-app-through-http-1-1/5417
- 8: https://stackoverflow.com/questions/70403584/why-do-http-2-clients-reject-requests-containing-the-connection-header
- 9: https://rfcinfo.com/rfc-7540/9/
Remove Connection: keep-alive header—it violates HTTP/2 and is unnecessary.
HTTP/2 (RFC 7540 section 8.1.2.2) explicitly prohibits connection-specific headers like Connection. Vercel serves Next.js deployments over HTTP/2 by default. Keep-alive is the default connection mode in HTTP/2 and does not need to be set explicitly. This header will either be ignored or cause protocol violations.
Proposed fix
const nextConfig = {
experimental: {
serverActions: {
bodySizeLimit: '2mb',
},
},
- async headers() {
- return [
- {
- source: '/api/:path*',
- headers: [{ key: 'Connection', value: 'keep-alive' }],
- },
- ];
- },
};
export default nextConfig;
Research Paper Explainer & Quiz Agent
Problem Statement
Reading academic research papers is hard for students and learners
outside the domain. Dense jargon and complex methodology make papers
inaccessible to most people.
What this agent does
Tech Stack
Checklist