# 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 | `uploaded` → `processing` → `ready` / `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 原始解�� | | 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` ```python # 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...), 不能只给答案 - 选择题要解释为什么对、为什么其他选项错 - 标注常见错误: `
...
` - 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` 的 `` + `` 组件 - 所有页面垂直排列在可滚动容器中 (不是单页切换) - `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` 监听所有 `` 元素, 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` | 知��点/提示/解析 折叠面板 | | `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`, `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`):** ```typescript interface StoredVariant { id: string; // placeholder ID, e.g. "variant-1" sourceQuestionNumber: string; // 原题题号 variant: VariantQuestion; // 生成结果 status: "generating" | "ready"; } ``` **流程:** 1. 用户点击 "Similar Question" → `ActionBar` 调 `onVariantStart(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_book` 或 `mastered` 标记 ### 前端 - 列表展示: 题目信息 + 用户答案 + 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}` | 获���单份试卷信息 | | 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 替代) ```