517 lines
20 KiB
Markdown
517 lines
20 KiB
Markdown
# 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 原始解<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`
|
||
|
||
```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...), 不能只给答案
|
||
- 选择题要解释为什么对、为什么其他选项错
|
||
- 标注常见错误: `<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` | 知<><E79FA5>点/提示/解析 折叠面板 |
|
||
| `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`):**
|
||
```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}` | 获<><E88EB7><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 替代)
|
||
```
|