Nitish kumar
Upload folder using huggingface_hub
c20f20c verified
# PDF 解析系统
提供统一接口支持多种 PDF 解析提供商。
## 支持的提供商
### 1. unpdf (内置)
- **成本**: 免费,内置
- **特性**: 基础文本提取、图片提取
- **要求**: 无
- **使用**: 直接上传 PDF 文件
### 2. MinerU (本地部署)
- **成本**: 免费(需要自己部署)
- **特性**:
- 高级文本提取(保留 Markdown 布局)
- 表格识别
- 公式提取(LaTeX)
- 更好的 OCR 支持
- 多种输出格式(markdown, JSON, docx, html, latex)
- **要求**:
- 部署 MinerU 服务(Docker 或源码)
- 配置服务器地址
- **优势**: 数据隐私、无文件大小限制
## 快速开始
### 部署 MinerU(可选)
```bash
# Docker 部署(推荐)
docker pull opendatalab/mineru:latest
docker run -d --name mineru -p 8080:8080 opendatalab/mineru:latest
# 验证
curl http://localhost:8080/api/health
```
### API 使用
#### 使用 unpdf(文件上传)
```typescript
const formData = new FormData();
formData.append('pdf', pdfFile);
formData.append('providerId', 'unpdf');
const response = await fetch('/api/parse-pdf', {
method: 'POST',
body: formData,
});
const result = await response.json();
// result.data: ParsedPdfContent
```
#### 使用 MinerU(本地服务)
```typescript
const formData = new FormData();
formData.append('pdf', pdfFile);
formData.append('providerId', 'mineru');
formData.append('baseUrl', 'http://localhost:8080');
const response = await fetch('/api/parse-pdf', {
method: 'POST',
body: formData,
});
const result = await response.json();
// result.data: ParsedPdfContent with imageMapping
```
## 响应格式
```typescript
interface ParsedPdfContent {
text: string; // 提取的文本(MinerU 为 Markdown)
images: string[]; // Base64 图片数组
// 扩展特性(MinerU)
tables?: Array<{
page: number;
data: string[][];
caption?: string;
}>;
formulas?: Array<{
page: number;
latex: string;
position?: { x: number; y: number; width: number; height: number };
}>;
layout?: Array<{
page: number;
type: 'title' | 'text' | 'image' | 'table' | 'formula';
content: string;
position?: { x: number; y: number; width: number; height: number };
}>;
metadata?: {
pageCount: number;
parser: 'unpdf' | 'mineru';
fileName?: string;
fileSize?: number;
processingTime?: number;
// 用于内容生成流程(MinerU)
imageMapping?: Record<string, string>; // img_1 -> base64 URL
pdfImages?: Array<{
id: string; // img_1, img_2, etc.
src: string; // base64 data URL
pageNumber: number; // PDF 页码
description?: string; // 图片描述
}>;
};
}
```
## 与内容生成集成
MinerU 解析器与内容生成流程无缝集成:
```typescript
// 1. 解析 PDF
const parseResult = await parsePDF(
{
providerId: 'mineru',
baseUrl: 'http://localhost:8080',
},
buffer,
);
// 2. 提取数据
const pdfText = parseResult.text; // Markdown(含 img_1 引用)
const pdfImages = parseResult.metadata.pdfImages; // 图片数组
const imageMapping = parseResult.metadata.imageMapping; // 图片映射
// 3. 生成场景大纲
await generateSceneOutlinesFromRequirements(
requirements,
pdfText, // Markdown 内容
pdfImages, // 带页码的图片
aiCall,
);
// 4. 生成场景(含图片)
await buildSceneFromOutline(
outline,
aiCall,
stageId,
assignedImages, // 从 pdfImages 筛选
imageMapping, // 用于解析 img_1 到实际 URL
);
```
## 图片处理流程
MinerU 的图片处理:
1. **提取**: PDF → MinerU → Markdown + 图片
2. **转换**: `![alt](images/img_1.png)``![alt](img_1)`
3. **映射**: 创建 `{ "img_1": "data:image/png;base64,..." }`
4. **生成**: AI 使用 `img_1` 引用生成幻灯片
5. **解析**: `resolveImageIds()` 替换为实际 URL
6. **渲染**: 幻灯片显示图片
## 配置
### 全局设置
```typescript
import { useSettingsStore } from '@/lib/store/settings';
useSettingsStore.setState({
pdfProviderId: 'mineru',
pdfProvidersConfig: {
mineru: {
baseUrl: 'http://localhost:8080',
apiKey: 'optional-if-needed',
},
},
});
```
### 请求级配置
```typescript
// 在 API 调用时覆盖全局设置
formData.append('providerId', 'mineru');
formData.append('baseUrl', 'http://your-server:8080');
formData.append('apiKey', 'optional');
```
## 添加新的提供商
### 1. 定义提供商
`lib/pdf/constants.ts`:
```typescript
export const PDF_PROVIDERS = {
myProvider: {
id: 'myProvider',
name: 'My Provider',
requiresApiKey: true,
features: ['text', 'images'],
},
};
```
### 2. 实现解析器
`lib/pdf/pdf-providers.ts`:
```typescript
async function parseWithMyProvider(
config: PDFParserConfig,
pdfBuffer: Buffer
): Promise<ParsedPdfContent> {
// 实现解析逻辑
return {
text: '...',
images: [...],
metadata: {
pageCount: 0,
parser: 'myProvider',
},
};
}
```
### 3. 添加到路由
```typescript
switch (config.providerId) {
case 'unpdf':
result = await parseWithUnpdf(pdfBuffer);
break;
case 'mineru':
result = await parseWithMinerU(config, pdfBuffer);
break;
case 'myProvider':
result = await parseWithMyProvider(config, pdfBuffer);
break;
}
```
## 调试工具
访问 http://localhost:3000/debug/pdf-parser 测试解析功能:
- 切换提供商(unpdf/MinerU)
- 上传 PDF 文件
- 配置服务器地址
- 查看解析结果
- 检查图片映射
## 常见问题
### Q: MinerU 服务无法连接?
**A**: 检查:
```bash
# 服务状态
docker ps | grep mineru
# 网络连通性
curl http://localhost:8080/api/health
# 日志
docker logs mineru
```
### Q: 图片不显示?
**A**: 确保:
1. `imageMapping` 正确传递到 scene-stream API
2. 图片 ID 格式正确(img_1, img_2)
3. Base64 编码完整
### Q: 解析速度慢?
**A**: 优化:
```bash
# 增加 Docker 资源
docker run -d \
--name mineru \
-p 8080:8080 \
--memory=4g \
--cpus=2 \
opendatalab/mineru:latest
```
### Q: unpdf vs MinerU 如何选择?
**A**: 选择建议:
| 场景 | 推荐 |
| ------------------ | ------ |
| 简单 PDF(纯文本) | unpdf |
| 包含表格、公式 | MinerU |
| 需要保留布局 | MinerU |
| 快速测试 | unpdf |
| 生产环境 | MinerU |
| 无法部署服务 | unpdf |
## 性能建议
### MinerU 并发处理
```typescript
const files = [file1, file2, file3];
const results = await Promise.all(
files.map((file) => {
const formData = new FormData();
formData.append('pdf', file);
formData.append('providerId', 'mineru');
return fetch('/api/parse-pdf', {
method: 'POST',
body: formData,
}).then((r) => r.json());
}),
);
```
### 结果缓存
```typescript
// 考虑缓存解析结果
const cacheKey = `pdf_${fileHash}`;
const cached = localStorage.getItem(cacheKey);
if (cached) {
return JSON.parse(cached);
}
```
## 参考资源
- **MinerU GitHub**: https://github.com/opendatalab/MinerU
- **快速开始**: `/MINERU_QUICKSTART.md`
- **变更说明**: `/MINERU_LOCAL_DEPLOYMENT.md`
- **调试工具**: http://localhost:3000/debug/pdf-parser
---
**最后更新**: 2026-02-11
**模式**: 本地自托管
**状态**: 生产就绪