# 实现完成报告(完整版 v3) ## 执行概览 ✅ **状态**:Streaming (P1) + Upload/Task Pipeline (P2/P3) + MinerU 真实接入 + 任务管理面板 + Markdown ZIP 打包 已完成 **最近更新**:2026-06-04 **范围**:MinerU 真实 markdown 处理、任务管理面板(手机端)、ZIP 打包下载、HF_HUB_OFFLINE 离线模式 --- ## 已完成的工作 ### P1:实时网络图传 ✅ #### 网络基础设施 | 文件 | 说明 | |------|------| | `network/ServerEndpoint.kt` | 服务端点模型(host/port/url/wsUrl) | | `network/NetworkInfoProvider.kt` | 本地 IP 获取 | | `network/stream/StreamState.kt` | 图传状态模型(Disconnected/Connecting/Connected/Error) | | `network/stream/StreamQualityPreset.kt` | 质量预设 ↔ StreamQuality 映射 | | `network/stream/FrameCompressor.kt` | JPEG 压缩 + 缩放 | | `network/stream/FrameDropController.kt` | 丢帧控制(AtomicBoolean + 时间间隔) | | `network/stream/OkHttpStreamClient.kt` | WebSocket 图传客户端(包含 StreamClient 接口) | #### 相机页集成 - `CameraViewModel`:添加 streamState、streamTargetHost、toggleStreaming()、sendStreamFrame() - `liveAnalysis()` 中嵌入图传帧发送(fire-and-forget,不影响 ML 分析) - `CameraScreen`:添加 StreamToggleButton(Cast 图标 + 状态颜色 + 主机显示) - 图传断连不影响正式扫描 #### PC 服务器 - `pc-server/main.py`:FastAPI 服务,含 `/health`、`WS /stream`、Web 预览页面 - 支持帧广播:接收手机帧并转发给浏览器客户端 - 帧率统计和日志 #### 帧率控制 - 添加 `StreamFrameRate` 枚举(UNLIMITED / FPS_15 / FPS_10 / FPS_5) - 设置页 RadioButton 选择 - 无限制模式:minIntervalMs <= 0,仅以 isSending 状态控制 --- ### P2/P3:PDF 上传与任务处理流水线 ✅ #### 上传与处理分离(最新重构) 遵循 `pc-api-spec.md` 接口规范,将上传和处理解耦为独立步骤: **上传(纯传输)**: - `POST /upload/pdf` → 返回 `fileId`(201 Created) - 仅保存 PDF 到 `./uploads/`,不触发任何处理 - PC 服务端使用独立 `files_db` 字典存储文件记录 **处理(任务创建)**: - `POST /tasks/process` → 基于 `fileId` + `processType` 创建任务(202 Accepted) - `processType` 可选值:`ocrpdf`、`markdown` - 异步模拟处理:queued → processing(10%→50%→90%) → completed - OCR PDF 模式:复制原始 PDF 作为处理结果(修复了之前空白 PDF 的问题) - Markdown 模式:生成示例 `.md` 文件 #### Android 端网络客户端 | 文件 | 说明 | |------|------| | `network/upload/PdfUploadClient.kt` | HTTP multipart POST 上传 PDF,返回 `(fileId, fileName, sizeBytes)` | | `network/tasks/TaskModels.kt` | 任务数据模型(TaskStatus / ArtifactInfo / ProcessTaskResult) | | `network/tasks/TaskClient.kt` | REST 客户端:`processPdf(fileId, processType)`、查询状态、产物列表、下载 | #### 导出页三按钮 UI Android 导出页新增三个独立操作按钮: 1. **仅传输到电脑** — `uploadPdfToServer()`:纯上传,设置 `Uploaded(fileId, taskId=null)` 2. **上传并处理 (OCR PDF)** — `uploadAndProcess("ocrpdf")`:上传 + 创建 OCR 任务 3. **上传并处理 (Markdown)** — `uploadAndProcess("markdown")`:上传 + 创建 Markdown 任务 `UploadState` 状态模型: - `Idle` — 未操作 - `Uploading(progress)` — 上传中 - `Uploaded(fileId, taskId?)` — 上传成功,taskId 为 null 表示纯传输 - `Error(message)` — 上传失败 --- ### PC 管理面板 ✅ 浏览器访问 `/dashboard`,包含: #### 统计卡片 - 已上传文件数 - 处理任务数(排队中/处理中/已完成) #### 文件列表 - 显示所有已上传的原始 PDF - 列:文件名、文件 ID、大小、时间、操作 - 操作列提供 ⬇ 下载按钮(`/files/{fileId}/download`) #### 任务列表 - 显示所有处理任务及其状态 - 列:文件名、任务 ID、状态(带 badge)、进度条、处理类型、时间、操作 - 已完成任务的操作列提供 ⬇ 下载产物按钮 #### 导航 - 顶部导航栏可在图传预览页(/)和管理面板(/dashboard)间切换 - 自动刷新(每 2 秒) --- --- ### MinerU 真实接入 ✅ 替换了之前的模拟 markdown 处理,使用真实 MinerU pipeline 后端: - 使用 `aio_do_parse()` 异步接口,不阻塞 FastAPI 事件循环 - Pipeline 后端配置:`backend="pipeline"`, `parse_method="auto"` - 环境:Conda 环境 `MinerU`(Python 3.10.20, PyTorch 2.6.0+cu124, CUDA 12.4) - GPU: NVIDIA RTX 4060 Laptop (8 GB VRAM) - 模型缓存路径:`C:/Users/32892/.cache/huggingface/hub/` - `HF_HUB_OFFLINE=1` 强制使用本地缓存,绕过国内网络不可达 huggingface.co 的问题 **MinerU markdown 输出**: - `{name}.md` — markdown 产物 - `images/` — 提取的图片资源 - `{name}_result.zip` — `.md + images/` 的完整打包(新增,便于手机端下载后直接使用) **MinerU ocrpdf 输出**: - `{name}_layout.pdf` — 带布局框的 PDF(当前模式) - 注:此处不是真正的 OCRmyPDF 双层 PDF,详见 NEXT_STEPS ### Markdown ZIP 打包 ✅ PC 服务器 markdown 处理完成后,自动检查 `images/` 目录: - 有图片 → 打包 `{name}.md` + `images/` 为 `{name}_result.zip` - 无图片 → 仅保留 `.md` 产物 - 两种产物(`.md` 和 `.zip`)均注册为独立 artifact,客户端可按需下载 - `download_artifact` 支持 `application/zip` MIME 类型 ### 手机端任务管理面板 ✅ 在导出页底部新增 `TaskPanelSection` UI 组件: - **任务状态显示**:排队中 / 处理中(进度条) / 已完成 / 失败 - **后台轮询**:2 秒间隔轮询 PC 任务状态,自动更新 UI,完成后自动停止 - **下载到指定目录**:用户点击"选择目录" → SAF 文件夹选择器 → 点击"下载" → 保存到指定目录 - **产物优选**:markdown 任务默认下载 ZIP;ocrpdf 任务默认下载 PDF - **下载进度**:实时显示下载进度条 - **已下载状态**:显示"已下载 — 打开"按钮,可打开文件 涉及文件: - `ExportUiState.kt`:新增 `RemoteTask`、`TaskPanelState`、`DownloadState` - `ExportViewModel.kt`:新增 `_taskPanelState`、轮询逻辑、`downloadResult()` - `ExportScreen.kt`:新增 `TaskPanelSection`、`TaskRow` UI 组件 - `ExportActions`:新增 `downloadResult`、`resetDownloadState` 回调 ### Bug 修复 | 问题 | 原因 | 修复 | |------|------|------| | MinerU 无法处理(SSL 错误) | `huggingface_hub` 启动时在线校验 revision | `main.py` 顶部设置 `HF_HUB_OFFLINE=1` | | `main.py` 重复 `@Composable` 编译错误 | 编辑失误 | 移除重复注解 | | `ButtonDefaults.TextButtonContentPadding.copy()` 不存在 | Material3 API 差异 | 改用 `PaddingValues()` 直接构造 | | `DownloadState.Error` 不含 taskId | 无法区分哪个任务的错误 | 添加 `taskId` 参数 | | WebSocket.send(ByteArray) 编译错误 | OkHttp WebSocket.send 需要 ByteString | 使用 `toByteString()` 扩展 | | 网络权限未申请 | 旧 `tools:node="remove"` 删除声明 | 移除冲突行 | | 明文通信被禁止 | `` 不支持 CIDR | 改用 `` | | 帧未显示在浏览器 | 服务器未广播帧到浏览器客户端 | 添加 broadcast 循环 | | 端口输入框"删除不干净" | `toIntOrNull()` 返回 null 后未更新 | 用 `remember` + `LaunchedEffect` | | 下载的 PDF 为空白页 | `_create_minimal_pdf()` 缺少内容流 | 改为复制原始上传文件 | | 上传进度卡在 0% | `upload_pdf` 未启动 `simulate_processing` | 添加 `asyncio.create_task`(后因分离重构移除) | | Preview 函数编译错误 | 缺少 `onUploadAndProcess` 参数 | 添加 `onUploadAndProcess = {}` | --- ## 架构总结 ### 完整数据流 ``` 相机预览 → liveAnalysis() ├── → ML 分析(不变) → 文档页面 │ ├── → Streaming(图传开启时) │ FrameCompressor → FrameDropController → OkHttpStreamClient → PC WS /stream → Browser │ └── → 拍照 → 处理 → PDF 生成 ExportViewModel ├── uploadPdfToServer() │ → PdfUploadClient.uploadPdf() → PC POST /upload/pdf │ → 返回 fileId → Uploaded(fileId, taskId=null) │ └── uploadAndProcess(processType) → PdfUploadClient.uploadPdf() → PC POST /upload/pdf → fileId → TaskClient.processPdf(fileId, processType) → PC POST /tasks/process → taskId → Uploaded(fileId, taskId) ``` ### PC 服务端架构 ``` files_db (dict): fileId → {fileId, fileName, sizeBytes, uploadPath, createdAt} tasks_db (dict): taskId → {taskId, fileId, status, progress, processType, ...} artifacts_db (dict): taskId → [{artifactId, fileName, ...}] artifacts_map (dict): artifactId → {artifactId, fileName, filePath, ...} ``` ### AppContainer 新增注入 ``` - networkInfoProvider - okHttpClient - streamClient: StreamClient - pdfUploadClient: PdfUploadClient - taskClient: TaskClient ``` --- ## PC 端端点总览 | 端点 | 方法 | 功能 | |------|------|------| | `/health` | GET | 健康检查 | | `/` | GET | 图传预览页面 | | `/stream` | WS | 接收 JPEG 帧 | | `/dashboard` | GET | 管理面板页面 | | `/api/dashboard` | GET | 管理面板 JSON 数据 | | `/upload/pdf` | POST | 上传 PDF(纯上传,201) | | `/tasks/process` | POST | 创建处理任务(202) | | `/tasks/{taskId}` | GET | 查询任务状态 | | `/tasks/{taskId}/artifacts` | GET | 查询任务产物列表 | | `/artifacts/{artifactId}/download` | GET | 下载处理产物 | | `/files/{fileId}/download` | GET | 下载已上传的原始文件 | --- ## 文件清单 ### 新增文件(Android 网络层) 1. `network/ServerEndpoint.kt` 2. `network/NetworkInfoProvider.kt` 3. `network/stream/StreamState.kt` 4. `network/stream/StreamQualityPreset.kt` 5. `network/stream/FrameCompressor.kt` 6. `network/stream/FrameDropController.kt` 7. `network/stream/OkHttpStreamClient.kt` 8. `network/upload/PdfUploadClient.kt` 9. `network/tasks/TaskModels.kt` 10. `network/tasks/TaskClient.kt` 11. `res/xml/network_security_config.xml` 12. `network/discovery/DiscoveredHost.kt`(占位,待 P0 实现) 13. `network/discovery/DiscoveryState.kt`(占位,待 P0 实现) 14. `network/discovery/LanServiceDiscovery.kt`(占位,待 P0 实现) ### 新增文件(PC) 15. `pc-server/main.py` ### 修改文件 | 文件 | 修改内容 | |------|---------| | `gradle/libs.versions.toml` | 添加 OkHttp 4.12.0 | | `app/build.gradle.kts` | 添加 OkHttp 依赖 | | `AndroidManifest.xml` | 添加网络权限、网络安全配置 | | `FairScanApp.kt` | 添加 okHttpClient、streamClient、pdfUploadClient、taskClient | | `CameraViewModel.kt` | 添加图传字段和方法、帧率控制 | | `CameraScreen.kt` | 添加 StreamToggleButton | | `SettingsRepository.kt` | 添加 StreamFrameRate、ServerHost、ServerPort 等 | | `SettingsViewModel.kt` | 添加 streamFrameRate、serverHost 等字段 | | `SettingsScreen.kt` | 添加帧率选择、网络配置 UI | | `MainActivity.kt` | 添加上传回调、taskPanelState 收集、downloadResult 回调 | | `ExportViewModel.kt` | 添加 uploadPdfToServer()、uploadAndProcess()、downloadResult()、startPolling() | | `ExportUiState.kt` | 添加 UploadState、RemoteTask、TaskPanelState、DownloadState | | `ExportScreen.kt` | 添加上传按钮、TaskPanelSection、TaskRow UI 组件 | | `pc-server/main.py` | 添加 MinerU 真实接入、ZIP 打包、HF_HUB_OFFLINE | --- ## 待实现 | 项目 | 状态 | |------|------| | **OCRmyPDF 真实接入** | **📌 下一步**(当前 ocrpdf 用 MinerU 生成 layout PDF,非真正双层可搜索 PDF) | | NSD 局域网自动发现 | 📌 占位(接口已定义) | | 设置页"扫描主机"/"测试连接"按钮功能 | 📌 待实现 | | 图传延迟/帧率实时显示 | 🔜 可优化 | --- **修改人**:Claude Code **最后更新**:2026-06-04 **修改类型**:Feature - Streaming + Upload/Process Pipeline + Dashboard + Real MinerU + Task Panel + ZIP