Files
PastpaperMaster/TECHNICAL.md
Zhao 7a09167261 Initial commit: PastPaper Master full stack
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 12:27:47 +07:00

20 KiB
Raw Permalink Blame History

PastPaper Master — 技术文档

系统架构总览

┌─────────────────────────────────────────────────────────────────┐
│                      Frontend (React 19 + Vite 7)               │
│  Pages: Home / Upload / Workbench / ErrorBook                   │
│  PDF: react-pdf v10 | Math: KaTeX 0.16 | Style: Tailwind v4    │
└────────────────────────────┬────────────────────────────────────┘
                             │ /api (Vite proxy → :8000)
┌────────────────────────────▼────────────────────────────────────┐
│                      Backend (FastAPI + Python)                  │
│  Routers: papers / attempts / questions                         │
│  Services: paper_processor / grader / llm_clients / text_extractor│
└────────┬───────────────────┬──────────────────┬─────────────────┘
         │                   │                  │
   ┌─────▼─────┐   ┌────────▼───────┐  ┌───────▼──────┐
   │ Supabase   │   │ GPT-4o         │  │ Qwen-plus    │
   │ PostgreSQL │   │ (laozhang API) │  │ (DashScope)  │
   │ + Storage  │   │ 结构化/OCR/变体 │  │ AI三件套/判分 │
   └───────────┘   └────────────────┘  └──────────────┘

技术栈一览:

  • Frontend: React 19, TypeScript, Vite 7, Tailwind CSS v4, react-pdf v10, KaTeX 0.16
  • Backend: FastAPI, Python 3.12, uv (包管理)
  • Database: Supabase (PostgreSQL + Row Level Security)
  • Storage: Supabase Storage (buckets: papers, attempt-photos)
  • LLM: GPT-4o (laozhang API 代理), Qwen-plus (阿里 DashScope)

数据库 Schema

文件: supabase/migrations/001_init_schema.sql

Table: papers — 试卷

字段 类型 说明
id UUID PK 自动生成
user_id UUID FK → auth.users 上传者
course_code TEXT 课程代码, e.g. "COMP2011"
year / term / exam_type INT/TEXT/TEXT 元信息
paper_file_url TEXT 试卷 PDF (Supabase Storage)
answer_file_url TEXT? 答案 PDF (可选)
status TEXT uploadedprocessingready / error
paper_extracted_text TEXT PyMuPDF 提取的原始文本 (缓存)
total_score / question_count INT AI 提取的整卷概览
topics_summary JSONB {"Linked List": 40, "Recursion": 30}
difficulty_level TEXT easy / medium / hard

Table: paper_questions — 逐题数据

字段 类型 说明
id UUID PK
paper_id UUID FK → papers
question_number TEXT "1", "1a", "2b"
parent_question TEXT? 子题父题号: "1a" → "1"
display_order INT 排序
question_type TEXT mc / true_false / fill_blank / long_question
question_text TEXT 题目原文
score / page_number INT 分值, PDF 页码 (PDF-题目联动用)
options JSONB MC 选项: [{"label":"A","text":"..."}]
correct_option TEXT MC 正确选项
correct_answer TEXT 填空题正确答案
raw_answer_text TEXT 答案 PDF 原始解<E5A78B><E8A7A3>
topics TEXT[] 知识点标签
difficulty TEXT easy / medium / hard
knowledge_reminder TEXT AI 知识点提醒 (HTML+KaTeX)
ai_hint TEXT AI 思路提示 (HTML+KaTeX)
solution TEXT AI 完整解题过程 (HTML+KaTeX)

Table: user_attempts — 用户答题记录

字段 类型 说明
id UUID PK
user_id / question_id UUID FK
attempt_type TEXT select / input / photo
user_answer TEXT 用户的选项或输入
photo_url / photo_ocr_text TEXT 拍照上传的图片和 OCR 结果
is_correct BOOL AI 判定
feedback TEXT HTML 逐步错误分析
error_at_step INT 第几步出错
in_error_book / mastered BOOL 错题本状态

核心功能一:试卷分析管线

流程概述

用户上传 PDF → 后台 BackgroundTask → 5 步管线 → 状态变 ready

文件

文件 作用
backend/app/routers/papers.py 上传接口, 触发后台处理
backend/app/services/paper_processor.py 核心管线, 5 步处理逻辑
backend/app/services/text_extractor.py PDF → 文本提取 (PyMuPDF)
backend/app/services/llm_clients.py GPT-4o / Qwen 客户端单例

管线 5 步 (paper_processor.py: process_paper())

Step 1 — PDF 文本提取

  • 使用 PyMuPDF (fitz) 逐页提取文本
  • 如果某页文本 < 50 字符 (可能是扫描件), 额外保存该页为 base64 图片备用
  • 提取结果缓存到 papers.paper_extracted_text
# text_extractor.py
extract_pdf(file_bytes)  ExtractedContent(pages_text, page_images, total_pages, has_images)
get_full_text(extracted)  "--- Page 1 ---\n{text}\n\n--- Page 2 ---\n..."

Step 2 — GPT-4o 结构化拆题

  • Model: gpt-4o, temperature=0, response_format=json_object
  • 输入: 整卷文本
  • 输出: JSON 包含 total_score, difficulty_level, topics_summary, questions[]
  • 每题提取: question_number, parent_question, question_type, question_text, score, page_number, options, topics, difficulty
  • 更新 papers 表的概览字段 (total_score, question_count, topics_summary, difficulty_level)

Step 3 — 答案匹配 (如果有答案 PDF)

  • Model: gpt-4o, temperature=0
  • 输入: 题目结构 JSON + 答案文本
  • 输出: 逐题匹配 — correct_option / correct_answer / raw_answer_text
  • 选择题 → correct_option, 填空题 → correct_answer, 大题 → raw_answer_text

Step 4 — Qwen 生成 AI 三件套 (逐题)

  • Model: qwen-plus, temperature=0.3
  • 逐题调用, 输入题目信息 + 标准答案
  • 输出 JSON 三件套:
    • knowledge_reminder: 前置知识要点 (HTML+KaTeX)
    • ai_hint: 不给答案的思路引导 (HTML+KaTeX)
    • solution: 完整逐步解题过程 (HTML+KaTeX)
  • 写入 paper_questions

Step 5 — 标记完成

  • papers.status 更新为 ready
  • 如果任何步骤抛异常, status 设为 error, 错误信息写入 error_message

关键 Prompt 设计

STRUCTURE_PROMPT — 结构化拆题

  • 限定 question_type 只能是 mc / true_false / fill_blank / long_question
  • 判断题 (True/False) 用 true_false 类型options 为 [{label:"True",text:"True"},{label:"False",text:"False"}]
  • 选择题必须提取 options 数组
  • 子题通过 parent_question 关联 (e.g. "1a" parent 是 "1")
  • 要求推断 page_number, topics, difficulty

ANSWER_MATCH_PROMPT — 答案匹配

  • 输入包含 questions_json (题号+题型) 和 answer_text
  • 按题型输出不同字段: MC → correct_option, fill → correct_answer, 大题 → raw_answer_text

ANALYSIS_PROMPT — AI 三件套

  • Solution 要求带完整过程 (Step 1, 2, 3...), 不能只给答案
  • 选择题要解释为什么对、为什么其他选项错
  • 标注常见错误: <div class="common-error">...</div>
  • KaTeX 规则: 块级 $$...$$, 行内 $...$

核心功能二PDF 滚动 + 题目联动

文件

文件 作用
frontend/src/components/workbench/PdfViewer.tsx PDF 连续滚动渲染 + 可见页检测
frontend/src/components/workbench/QuestionNav.tsx 题目水平导航栏
frontend/src/pages/WorkbenchPage.tsx 双向联动调度中枢

实现方案

布局: 左侧 60% PDF, 右侧 40% 题目面板

PDF 连续滚动 (PdfViewer.tsx)

  • 使用 react-pdf<Document> + <Page> 组件
  • 所有页面垂直排列在可滚动容器中 (不是单页切换)
  • ResizeObserver 监听容器宽度, 动态设置 Page width
  • 手动跳转: 输入页码 → scrollIntoView

双向联动:

  1. 题目 → PDF (点击题目, PDF 滚动到对应页)

    • QuestionNav 点击 → handleQuestionSelect(index) → 记录 lastUserSelectTime = Date.now() + setCurrentIndex
    • PdfViewer 收到 currentPage prop 变化 → useEffect 触发 el.scrollIntoView({ behavior: "smooth" })
    • 设置 programmaticScroll.current = true, 2s 后重置
  2. PDF → 题目 (滚动 PDF, 右侧自动切换到当前题)

    • IntersectionObserver 监听所有 <Page> 元素, threshold: [0, 0.25, 0.5, 0.75, 1]
    • 追踪每页的 intersectionRatio, 选出可见占比最高的页码
    • 如果 programmaticScroll.current === true, 跳过回调
    • 触发 onPageChange(bestPage) → WorkbenchPage handlePdfPageChange
    • handlePdfPageChange: 找到 page_number <= currentPage 的最后一题, 更新 currentIndex

防止跳转抢夺 (双层保护):

  • WorkbenchPage 层 (核心): lastUserSelectTime ref — 用户点击题目后 2 秒内, handlePdfPageChange 直接 return, 不响应任何 Observer 回调。解决长文档 smooth scroll 经过中间页触发 Observer 导致题目被切走的问题
  • PdfViewer 层 (辅助): programmaticScroll ref — scrollIntoView 期间 Observer 回调跳过, 2s 后重置

核心功能三:做题交互 (MC / 填空)

文件

文件 作用
frontend/src/components/workbench/QuestionDetail.tsx 题目展示 + 答题交互
frontend/src/components/workbench/AiTrioPanel.tsx <EFBFBD><EFBFBD>点/提示/解析 折叠面板
frontend/src/components/shared/CollapsibleSection.tsx 可折叠区域组件
frontend/src/components/shared/KaTeXRenderer.tsx HTML+KaTeX 渲染器

QuestionDetail 交互逻辑

选择题 (MC):

  • 状态: selectedOption, checked
  • 点击选项 → 高亮蓝色 (未检查时)
  • 点击 "Check Answer" → checked=true
  • 正确: 选项变绿 + "Correct!" / 错误: 选中项变红, 正确项变绿 + 显示正确答案
  • 切换题目时自动重置状态 (useEffect on question.id)

判断题 (True/False):

  • 状态: tfAnswers: Record<string, "True" | "False">, tfChecked
  • 每个 statement 右侧有 T / F 两个按钮, 独立切换
  • 选中高亮蓝色, 全部选完后可点 "Submit Answers"
  • 提交后提示查看 solution 对答案 (因为逐条正确答案暂未单独存储)

填空题 (Fill Blank):

  • 文本输入框 + "Check" 按钮
  • Enter 键可直接检查
  • 大小写不敏感比较 (toLowerCase())
  • 检查后输入框变色: 绿色 (对) / 红色 (错)

回调: onAnswerResult(isCorrect, userAnswer) → WorkbenchPage → recordAttempt API

AiTrioPanel

  • 三个 CollapsibleSection: Knowledge Reminder (蓝, 默认展开), AI Hint (琥珀), Solution (绿)
  • CollapsibleSection 使用 CSS grid-template-rows: 0fr → 1fr 动画平滑展开收起
  • 内容通过 KaTeXRenderer 渲染 (HTML + KaTeX 公式)

核心功能四:变体题生成 (Similar Question)

文件

文件 作用
backend/app/routers/questions.py POST /{question_id}/variant 端点
backend/app/services/grader.py generate_variant() — GPT-4o 生成变体
frontend/src/components/workbench/ActionBar.tsx "Similar Question" 按钮, 异步触发
frontend/src/pages/WorkbenchPage.tsx Variants Tab 状态管理
frontend/src/components/workbench/VariantDetail.tsx 变体题作答界面

后端

  • POST /api/questions/{question_id}/variant
  • 从 DB 查原题 → 调 generate_variant(question) → 附上原题的 knowledge_reminder → 返回
  • Model: gpt-4o, temperature=0.5, response_format=json_object
  • VARIANT_PROMPT 要求: 同知识点, 相似难度, 不同数据/场景, 输出 HTML 格式 (非 markdown)
  • 输出字段: question_text, question_type, options (if MC), correct_answer, ai_hint, solution

前端交互 (Tab-based 异步流程)

状态管理 (WorkbenchPage.tsx):

interface StoredVariant {
  id: string;                    // placeholder ID, e.g. "variant-1"
  sourceQuestionNumber: string;  // 原题题号
  variant: VariantQuestion;      // 生成结果
  status: "generating" | "ready";
}

流程:

  1. 用户点击 "Similar Question" → ActionBaronVariantStart(placeholderId, questionNumber)
  2. WorkbenchPage 创建 status: "generating" 的占位项, 用户可继续做题不受阻塞
  3. API 返回后 → onVariantReady(placeholderId, variant) → 状态更新为 ready
  4. 失败 → onVariantFailed(placeholderId) → 删除占位项

右侧面板三种视图:

  • Questions Tab: 题目导航 + QuestionDetail + AiTrioPanel + ActionBar
  • Variants Tab: 变体列表 (Generating.../Ready), 每项显示题号和预览文本
  • Variant Detail: 点击 "Start" 后整个右侧替换为 VariantDetail 组件 + "Back" 按钮

VariantDetail 组件: 紫色主题, 包含完整 MC/填空交互 + AI 三件套 (CollapsibleSection)


核心功能五:拍照批改

文件

文件 作用
backend/app/routers/attempts.py POST /photo — 上传+OCR+批改
backend/app/services/grader.py ocr_photo() + grade_answer()
frontend/src/components/workbench/PhotoUpload.tsx 拍照上传 Modal
frontend/src/components/workbench/ActionBar.tsx "Upload handwritten answer" 按钮

后端流程

  1. 接收图片 → 上传到 Supabase Storage attempt-photos bucket
  2. ocr_photo(photo_bytes) — GPT-4o Vision 识别手写内容
    • 输入: base64 图片
    • 输出: 学生答案文本 (含 LaTeX 公式)
  3. grade_answer(question, student_answer) — Qwen-plus 批改
    • 输入: 题目信息 + 标准答案 + 学生答案
    • 输出: { is_correct, score_given, feedback (HTML), error_at_step }
  4. 写入 user_attempts 表 (含 photo_url, photo_ocr_text, feedback, is_correct)
  5. 答错自动 in_error_book = true

前端

  • PhotoUpload: Modal 弹窗, 支持拖拽/点击选择图片
  • 预览 → 提交 → 显示 OCR 识别结果 + AI 批改反馈
  • 所有题型均可使用 (MC / 填空 / 大题)

核心功能六:错题本

文件

文件 作用
backend/app/routers/attempts.py GET /error-book + PATCH /{attempt_id}
frontend/src/pages/ErrorBookPage.tsx 错题本页面
frontend/src/lib/api.ts getErrorBook() + updateAttempt()

后端

  • GET /api/attempts/error-book?user_id=xxx
    • 查询 in_error_book=true AND mastered=false
    • JOIN paper_questions 返回完整题目信息
  • PATCH /api/attempts/{attempt_id}
    • 更新 in_error_bookmastered 标记

前端

  • 列表展示: 题目信息 + 用户答案 + AI 反馈
  • 操作: "Review in Workbench" (跳转) / "Mastered" (标记掌握) / "Remove" (移出错题本)

核心功能七:答题记录

文件

文件 作用
backend/app/routers/attempts.py POST / — 记录答题
frontend/src/components/workbench/ActionBar.tsx "Got it right" / "Got it wrong" 按钮

流程

  • "Got it right" → POST /api/attempts/ with attempt_type: "select", is_correct: true
  • "Got it wrong" → POST /api/attempts/ with attempt_type: "select", is_correct: false
    • 后端自动 in_error_book = true
  • Toast 提示操作结果

API 接口汇总

Papers Router (/api/papers)

Method Path 说明
GET / 列出所有试卷 (可按 user_id 过滤)
POST /upload 上传试卷 PDF + 可选答案 PDF
GET /{paper_id} <EFBFBD><EFBFBD><EFBFBD>单份试卷信息
GET /{paper_id}/questions 获取试卷所有题目

Attempts Router (/api/attempts)

Method Path 说明
POST / 记录一次答题
POST /photo 拍照上传 + OCR + AI 批改
GET /error-book?user_id= 获取错题本
PATCH /{attempt_id} 更新错题本/掌握状态

Questions Router (/api/questions)

Method Path 说明
POST /{question_id}/variant 生成变体题

前端路由

路径 页面 文件
/ 首页 — 试卷列表 src/pages/HomePage.tsx
/upload 上传试卷 src/pages/UploadPage.tsx
/paper/:id 做题工作台 src/pages/WorkbenchPage.tsx
/error-book 错题本 src/pages/ErrorBookPage.tsx

前端组件树 (Workbench)

WorkbenchPage
├── Header                          # 顶部导航 (课程+试卷标题)
├── PdfViewer                       # 左侧 60% — PDF 连续滚动
└── Right Panel (40%)
    ├── [Questions Tab]
    │   ├── QuestionNav             # 题目水平导航 Q1 Q2 Q3...
    │   ├── QuestionDetail          # 题目展示 + MC/填空交互
    │   ├── AiTrioPanel             # 知识点/提示/解析 (3x CollapsibleSection)
    │   └── ActionBar               # 底部按钮 (对/错/变体/拍照)
    ├── [Variants Tab]
    │   └── Variant Cards           # 变体列表 (Generating.../Ready)
    └── [Variant Detail View]       # 替换整个右侧
        ├── Back Button
        └── VariantDetail           # 变体题作答 + AI 三件套

LLM 调用模型分工

任务 模型 Provider 文件
结构化拆题 gpt-4o laozhang API paper_processor.py
答案匹配 gpt-4o laozhang API paper_processor.py
AI 三件套 (knowledge/hint/solution) qwen-plus DashScope paper_processor.py
手写 OCR gpt-4o (Vision) laozhang API grader.py
答案批改 qwen-plus DashScope grader.py
变体题生成 gpt-4o laozhang API grader.py

配置与环境变量

文件: backend/app/config.py, .env

变量 说明
SUPABASE_URL Supabase 项目 URL
SUPABASE_ANON_KEY 前端用匿名 Key
SUPABASE_SERVICE_ROLE_KEY 后端用 Service Role Key (绕过 RLS)
LAOZHANG_BASE_URL GPT-4o 代理 API 地址
LAOZHANG_API_KEY GPT-4o 代理 API Key
DASHSCOPE_BASE_URL 阿里 DashScope API
DASHSCOPE_API_KEY DashScope API Key

文件完整索引

Backend (backend/app/)

main.py                        # FastAPI 入口, CORS, 路由注册
config.py                      # Pydantic Settings, 环境变量
routers/
  papers.py                    # 试卷 CRUD + 上传触发处理
  attempts.py                  # 答题记录 + 拍照OCR批改 + 错题本
  questions.py                 # 变体题生成
services/
  paper_processor.py           # 核心5步管线: PDF→结构化→答案匹配→AI三件套
  text_extractor.py            # PyMuPDF 文本提取
  grader.py                    # OCR + 批改 + 变体生成 (Prompt + LLM 调用)
  llm_clients.py               # GPT-4o / Qwen 客户端单例
  supabase_client.py           # Supabase 客户端

Frontend (frontend/src/)

App.tsx                        # React Router 路由定义
main.tsx                       # ReactDOM 入口
lib/
  api.ts                       # 所有 API 调用封装 (9 个函数)
types/
  api.ts                       # TypeScript 类型定义
hooks/
  usePaper.ts                  # 轮询获取试卷状态 (3s interval)
  useQuestions.ts              # 获取题目列表
pages/
  HomePage.tsx                 # 首页 — 试卷列表
  UploadPage.tsx               # 上传页
  WorkbenchPage.tsx            # 做题工作台 — 核心调度组件
  ErrorBookPage.tsx            # 错题本
components/
  layout/
    Header.tsx                 # 顶部导航栏
  shared/
    KaTeXRenderer.tsx          # HTML+KaTeX 公式渲染
    CollapsibleSection.tsx     # 折叠面板 (grid动画)
    StatusBadge.tsx            # 状态标签
  upload/
    UploadForm.tsx             # 上传表单
    FilePickerField.tsx        # 文件选择器
  workbench/
    PdfViewer.tsx              # PDF 连续滚动 + IntersectionObserver
    QuestionNav.tsx            # 题目导航栏
    QuestionDetail.tsx         # 题目展示 + MC/填空交互
    AiTrioPanel.tsx            # AI 三件套面板
    ActionBar.tsx              # 底部操作按钮
    PhotoUpload.tsx            # 拍照上传 Modal
    VariantDetail.tsx          # 变体题内联作答
    VariantModal.tsx           # (已废弃, 被 VariantDetail 替代)