Public Access
feat: implement landing page with file upload, sample loading, and comprehensive JSON schema documentation
This commit is contained in:
1 parent
5c7b0bdb45
commit
b91d5769fe
2 files changed
+361
No files matched your search
@@ -50,6 +50,327 @@ export default function Landing({ onLoadExam, history, onResume, onClearHistory,
|
||||
}
|
||||
}
|
||||
|
||||
const downloadInstructions = () => {
|
||||
const content = `BRAINWAVE EXAM JSON — FORMAT GUIDE
|
||||
====================================
|
||||
Give this document to your preferred LLM (ChatGPT, Claude, Gemini, etc.) along with your exam content,
|
||||
and ask it to produce a valid JSON file you can upload to BrainWave.
|
||||
|
||||
────────────────────────────────────
|
||||
TOP-LEVEL STRUCTURE
|
||||
────────────────────────────────────
|
||||
{
|
||||
"title": "string (REQUIRED) — The name of the exam",
|
||||
"subtitle": "string (optional) — A short description shown under the title",
|
||||
"author": "string (optional) — Who created the exam",
|
||||
"timeLimitMinutes": number | null (optional) — Time limit in minutes, or null/omit for untimed,
|
||||
"feedbackMode": "end" | "immediate" (optional, default: "end")
|
||||
- "end": students see all feedback after submitting the full exam
|
||||
- "immediate": feedback is shown after each question is answered,
|
||||
"passingScore": number (optional) — Passing percentage, e.g. 70 means 70%,
|
||||
"categories": ["string", ...] (optional) — Category labels for organizing questions,
|
||||
"questions": [ ... ] (REQUIRED) — Array of question objects (see below)
|
||||
}
|
||||
|
||||
────────────────────────────────────
|
||||
QUESTION OBJECT (common fields)
|
||||
────────────────────────────────────
|
||||
Every question MUST have these fields:
|
||||
"id": "string" — Unique identifier, e.g. "q1", "q2", etc.
|
||||
"type": "string" — One of the supported types listed below
|
||||
"prompt": "string" — The question text (supports LaTeX with $...$ or $$...$$)
|
||||
|
||||
Optional fields available on ALL question types:
|
||||
"category": "string" — Which category this question belongs to
|
||||
"points": number — Point value (default: 1)
|
||||
"explanation": "string" — Explanation shown after grading (supports LaTeX)
|
||||
"image": { "src": "URL", "alt": "string", "caption": "string" } — Attach an image
|
||||
"audio": { "src": "URL" } — Attach an audio clip
|
||||
"code": { "language": "string", "content": "string" } — Show a code block
|
||||
"table": { "headers": ["Col1", "Col2"], "rows": [["a","b"], ["c","d"]] } — Show a data table
|
||||
"svg": "string" — Raw SVG markup to display
|
||||
|
||||
────────────────────────────────────
|
||||
QUESTION TYPES
|
||||
────────────────────────────────────
|
||||
|
||||
1. MULTIPLE CHOICE ("multiple-choice")
|
||||
Student picks ONE correct answer from a list.
|
||||
|
||||
Required fields:
|
||||
"choices": [{ "id": "a", "text": "Option A" }, { "id": "b", "text": "Option B" }, ...]
|
||||
"answer": "a" ← the id of the correct choice
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q1",
|
||||
"type": "multiple-choice",
|
||||
"prompt": "What is the capital of France?",
|
||||
"choices": [
|
||||
{ "id": "a", "text": "London" },
|
||||
{ "id": "b", "text": "Paris" },
|
||||
{ "id": "c", "text": "Berlin" }
|
||||
],
|
||||
"answer": "b"
|
||||
}
|
||||
|
||||
2. MULTIPLE SELECT ("multiple-select")
|
||||
Student selects ALL correct answers (checkboxes).
|
||||
|
||||
Required fields:
|
||||
"choices": [{ "id": "a", "text": "Option A" }, ...]
|
||||
"answer": ["a", "c"] ← array of correct choice ids
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q2",
|
||||
"type": "multiple-select",
|
||||
"prompt": "Which are prime numbers?",
|
||||
"choices": [
|
||||
{ "id": "a", "text": "2" },
|
||||
{ "id": "b", "text": "4" },
|
||||
{ "id": "c", "text": "7" },
|
||||
{ "id": "d", "text": "9" }
|
||||
],
|
||||
"answer": ["a", "c"]
|
||||
}
|
||||
|
||||
3. SHORT ANSWER ("short-answer")
|
||||
Student types a short text answer.
|
||||
|
||||
Required fields:
|
||||
"answer": "string" — The correct answer
|
||||
|
||||
Optional fields:
|
||||
"acceptedForms": ["alt1", "alt2"] — Other accepted answers
|
||||
"caseSensitive": true|false (default: false)
|
||||
"ignorePunctuation": true|false (default: false)
|
||||
"ignoreAccents": true|false (default: false)
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q3",
|
||||
"type": "short-answer",
|
||||
"prompt": "What element has the chemical symbol 'O'?",
|
||||
"answer": "Oxygen",
|
||||
"caseSensitive": false
|
||||
}
|
||||
|
||||
4. TRANSLATION ("translation")
|
||||
Student translates text between languages.
|
||||
|
||||
Required fields:
|
||||
"answer": "string" — The correct translation
|
||||
"sourceLanguage": "string" — e.g. "English"
|
||||
"targetLanguage": "string" — e.g. "Spanish"
|
||||
|
||||
Optional fields:
|
||||
"acceptedForms": ["alt translation 1", "alt translation 2"]
|
||||
"caseSensitive": true|false
|
||||
"ignorePunctuation": true|false
|
||||
"ignoreAccents": true|false
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q4",
|
||||
"type": "translation",
|
||||
"sourceLanguage": "English",
|
||||
"targetLanguage": "Spanish",
|
||||
"prompt": "Translate: \\"Good morning, how are you?\\"",
|
||||
"answer": "Buenos días, ¿cómo estás?",
|
||||
"acceptedForms": ["Buenos dias, como estas"],
|
||||
"caseSensitive": false,
|
||||
"ignorePunctuation": true,
|
||||
"ignoreAccents": true
|
||||
}
|
||||
|
||||
5. MATH INPUT ("math-input")
|
||||
Student enters a LaTeX math expression.
|
||||
|
||||
Required fields:
|
||||
"answer": "string" — Correct LaTeX expression
|
||||
|
||||
Optional fields:
|
||||
"acceptedForms": ["alt LaTeX 1", "alt LaTeX 2"]
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q5",
|
||||
"type": "math-input",
|
||||
"prompt": "Compute the derivative of $x^3$.",
|
||||
"answer": "3x^2",
|
||||
"acceptedForms": ["3x^{2}", "3 x^2"]
|
||||
}
|
||||
|
||||
6. ESSAY ("essay")
|
||||
Student writes a long-form response. Graded manually (always marked "ungraded").
|
||||
|
||||
Optional fields:
|
||||
"minWords": number — Minimum word count required
|
||||
"rubric": ["criterion 1", "criterion 2"] — Grading rubric shown during review
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q6",
|
||||
"type": "essay",
|
||||
"prompt": "Discuss the causes of World War I.",
|
||||
"points": 10,
|
||||
"minWords": 100,
|
||||
"rubric": [
|
||||
"Mentions alliance systems",
|
||||
"Discusses assassination of Archduke Franz Ferdinand",
|
||||
"Addresses nationalism and imperialism",
|
||||
"Well-structured argument"
|
||||
]
|
||||
}
|
||||
|
||||
7. MATCH ("match")
|
||||
Student matches items from a left column to items in a right column.
|
||||
|
||||
Required fields:
|
||||
"left": [{ "id": "l1", "text": "Item A" }, { "id": "l2", "text": "Item B" }, ...]
|
||||
"right": [{ "id": "r1", "text": "Match 1" }, { "id": "r2", "text": "Match 2" }, ...]
|
||||
"answer": { "l1": "r2", "l2": "r1" } ← maps each left id to its correct right id
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q7",
|
||||
"type": "match",
|
||||
"prompt": "Match the country to its capital.",
|
||||
"left": [
|
||||
{ "id": "l1", "text": "France" },
|
||||
{ "id": "l2", "text": "Japan" },
|
||||
{ "id": "l3", "text": "Brazil" }
|
||||
],
|
||||
"right": [
|
||||
{ "id": "r1", "text": "Brasília" },
|
||||
{ "id": "r2", "text": "Tokyo" },
|
||||
{ "id": "r3", "text": "Paris" }
|
||||
],
|
||||
"answer": { "l1": "r3", "l2": "r2", "l3": "r1" }
|
||||
}
|
||||
|
||||
8. ORDER ("order")
|
||||
Student arranges items in the correct sequence (drag & drop).
|
||||
|
||||
Required fields:
|
||||
"items": [{ "id": "s1", "text": "Step 1" }, { "id": "s2", "text": "Step 2" }, ...]
|
||||
"answer": ["s1", "s2", ...] ← array of item ids in the correct order
|
||||
|
||||
Example:
|
||||
{
|
||||
"id": "q8",
|
||||
"type": "order",
|
||||
"prompt": "Put these historical events in chronological order.",
|
||||
"items": [
|
||||
{ "id": "e1", "text": "Moon landing (1969)" },
|
||||
{ "id": "e2", "text": "Fall of the Berlin Wall (1989)" },
|
||||
{ "id": "e3", "text": "World Wide Web invented (1991)" },
|
||||
{ "id": "e4", "text": "First iPhone released (2007)" }
|
||||
],
|
||||
"answer": ["e1", "e2", "e3", "e4"]
|
||||
}
|
||||
|
||||
────────────────────────────────────
|
||||
LATEX SUPPORT
|
||||
────────────────────────────────────
|
||||
Use LaTeX math in prompts, choices, and explanations:
|
||||
- Inline math: $x^2 + y^2 = z^2$
|
||||
- Display math: $$\\\\int_0^1 x^2 \\\\, dx = \\\\frac{1}{3}$$
|
||||
|
||||
Remember to double-escape backslashes in JSON strings:
|
||||
"prompt": "Evaluate $\\\\frac{d}{dx} x^2$"
|
||||
|
||||
────────────────────────────────────
|
||||
MEDIA ATTACHMENTS
|
||||
────────────────────────────────────
|
||||
You can attach images, audio, code blocks, tables, and SVGs to any question.
|
||||
Use publicly accessible URLs for image and audio sources.
|
||||
|
||||
Image: "image": { "src": "https://example.com/photo.jpg", "alt": "Description", "caption": "Optional caption" }
|
||||
Audio: "audio": { "src": "https://example.com/clip.mp3" }
|
||||
Code: "code": { "language": "python", "content": "def hello():\\n print('hi')" }
|
||||
Table: "table": { "headers": ["Name", "Value"], "rows": [["A", "1"], ["B", "2"]] }
|
||||
|
||||
────────────────────────────────────
|
||||
COMPLETE MINI EXAMPLE
|
||||
────────────────────────────────────
|
||||
{
|
||||
"title": "Biology 101 — Cell Structure Quiz",
|
||||
"subtitle": "Chapters 3-4 review",
|
||||
"author": "Prof. Smith",
|
||||
"timeLimitMinutes": 15,
|
||||
"feedbackMode": "end",
|
||||
"passingScore": 70,
|
||||
"categories": ["Cells", "Organelles"],
|
||||
"questions": [
|
||||
{
|
||||
"id": "q1",
|
||||
"type": "multiple-choice",
|
||||
"category": "Cells",
|
||||
"points": 2,
|
||||
"prompt": "Which organelle is known as the 'powerhouse of the cell'?",
|
||||
"choices": [
|
||||
{ "id": "a", "text": "Nucleus" },
|
||||
{ "id": "b", "text": "Mitochondria" },
|
||||
{ "id": "c", "text": "Ribosome" },
|
||||
{ "id": "d", "text": "Golgi apparatus" }
|
||||
],
|
||||
"answer": "b",
|
||||
"explanation": "Mitochondria produce ATP through cellular respiration."
|
||||
},
|
||||
{
|
||||
"id": "q2",
|
||||
"type": "short-answer",
|
||||
"category": "Organelles",
|
||||
"points": 1,
|
||||
"prompt": "What is the gel-like fluid inside a cell called?",
|
||||
"answer": "cytoplasm",
|
||||
"acceptedForms": ["Cytoplasm", "cytosol"],
|
||||
"caseSensitive": false
|
||||
},
|
||||
{
|
||||
"id": "q3",
|
||||
"type": "order",
|
||||
"category": "Cells",
|
||||
"points": 3,
|
||||
"prompt": "Order these from smallest to largest:",
|
||||
"items": [
|
||||
{ "id": "a", "text": "Atom" },
|
||||
{ "id": "b", "text": "Molecule" },
|
||||
{ "id": "c", "text": "Organelle" },
|
||||
{ "id": "d", "text": "Cell" }
|
||||
],
|
||||
"answer": ["a", "b", "c", "d"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
────────────────────────────────────
|
||||
TIPS FOR LLM PROMPTING
|
||||
────────────────────────────────────
|
||||
When asking your LLM to create an exam, include:
|
||||
1. This document (paste it in full)
|
||||
2. The subject matter or source material
|
||||
3. How many questions you want
|
||||
4. Which question types to use
|
||||
5. Desired difficulty level
|
||||
6. Whether to include a time limit
|
||||
|
||||
Example prompt: "Using the attached format guide, create a 10-question exam on
|
||||
World War II. Use a mix of multiple-choice, short-answer, and ordering questions.
|
||||
Set a 20-minute time limit. Include explanations for each answer."
|
||||
`
|
||||
|
||||
const blob = new Blob([content], { type: 'text/plain' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'brainwave-exam-format-guide.txt'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const resumable = loadState()
|
||||
|
||||
return (
|
||||
@@ -86,6 +407,16 @@ export default function Landing({ onLoadExam, history, onResume, onClearHistory,
|
||||
/>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost download-instructions-btn"
|
||||
onClick={downloadInstructions}
|
||||
id="download-instructions"
|
||||
>
|
||||
<span className="download-instructions-icon">↓</span>
|
||||
Download Instructions — How to create your own exam JSON
|
||||
</button>
|
||||
|
||||
{err && (
|
||||
<div style={{ marginTop: 12, padding: '10px 14px', border: '1px solid var(--wrong)', borderRadius: 8, background: 'var(--wrong-weak)', color: 'var(--wrong)', fontSize: 13 }}>
|
||||
{err}
|
||||
|
||||
Reference in new issue
Block a user