feat: expandable previews, KaTeX rendering, variant speedup, batch import

- Analytics/Similar: expandable question preview with KaTeX rendering
- KaTeXRenderer: auto markdown-to-HTML (code blocks, tables, bold), auto Unicode→LaTeX
- ErrorBook: full question text rendering instead of truncated preview
- Variant: remove hint/solution from generation (faster), async, fix null crash
- Grading: add max_tokens limit
- JSON parser: robust multi-layer repair + JSONDecodeError retry
- Extraction prompt: enforce LaTeX notation for math
- Upload: redirect to home instead of blank paper page
- ProcessingBanner: add ETA time estimate + percentage
- Batch import script + handoff guide for team

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zhao
2026-04-24 22:41:57 +09:00
parent 10fa2b74ef
commit 9c09944c96
16 changed files with 990 additions and 127 deletions

View File

@@ -68,9 +68,7 @@ Return JSON:
"question_text": "HTML formatted variant question",
"question_type": "{question_type}",
"options": [MC only, format {{"label":"A","text":"..."}}, ...] or null,
"correct_answer": "Correct answer (plain text)",
"ai_hint": "HTML formatted hint that guides thinking WITHOUT giving the answer",
"solution": "HTML formatted complete step-by-step solution"
"correct_answer": "Correct answer (plain text)"
}}"""
@@ -90,7 +88,7 @@ def ocr_photo(photo_bytes: bytes) -> str:
]},
],
temperature=0,
max_tokens=2000,
max_tokens=1500,
)
return resp.choices[0].message.content or ""
@@ -114,13 +112,15 @@ def grade_answer(question: dict, student_answer: str) -> dict:
)},
],
temperature=0.2,
max_tokens=2048,
response_format={"type": "json_object"},
)
return json.loads(resp.choices[0].message.content)
def generate_variant(question: dict) -> dict:
"""Gemini generates a variant question"""
async def generate_variant(question: dict) -> dict:
"""DeepSeek generates a variant question (async)"""
import asyncio
answer = (
question.get("correct_option")
or question.get("correct_answer")
@@ -129,18 +129,20 @@ def generate_variant(question: dict) -> dict:
)
ds = get_deepseek_client()
resp = ds.chat.completions.create(
prompt = VARIANT_PROMPT.format(
question_type=question["question_type"],
question_text=question["question_text"],
topics=", ".join(question.get("topics", [])),
difficulty=question.get("difficulty", "medium"),
answer=answer,
)
resp = await asyncio.to_thread(
ds.chat.completions.create,
model="deepseek-chat",
messages=[
{"role": "system", "content": VARIANT_PROMPT.format(
question_type=question["question_type"],
question_text=question["question_text"],
topics=", ".join(question.get("topics", [])),
difficulty=question.get("difficulty", "medium"),
answer=answer,
)},
],
messages=[{"role": "system", "content": prompt}],
temperature=0.5,
max_tokens=2048,
response_format={"type": "json_object"},
)
return json.loads(resp.choices[0].message.content)