# Proposal: 智能材料处理中心

**版本**: v1  
**日期**: 2026-02-17  
**作者**: Agent4 (技术架构师)  
**状态**: 待评审

---

## 一、背景

### 1.1 需求背景

用户在项目管理过程中需要处理大量杂乱的原始材料：
- 庭审录音、会议录音、沟通录音
- 照片、截图、扫描件
- 文档、聊天记录、邮件
- 其他非结构化数据

### 1.2 当前问题

根据截图反馈，问题如下：

| 问题 | 说明 |
|------|------|
| **文件状态丢失** | 上传音频文件后，点击"转写&整理"按钮时，系统未识别到已上传的文件 |
| **交互逻辑错误** | 点击按钮后直接返回"请先上传文件"的提示，而非实际执行转写 |
| **功能未实现** | "会议纪智能整理"页面的后端API尚未完成开发 |

### 1.3 扩展需求

除了解决当前Bug，还需要实现完整功能：

| 需求 | 说明 |
|------|------|
| **材料类型识别** | 自动识别上传的材料类型（音频/图片/文档） |
| **项目归属** | 自动识别项目或手动指定项目 |
| **原始材料保留** | 保留原始上传文件和转写/OCR结果 |
| **去重处理** | 相同文件避免重复转写/OCR |
| **模板转换** | 根据项目模板将材料转化为规范格式 |

---

## 二、系统定位

### 2.1 在PM-Agent中的位置

```
┌─────────────────────────────────────────────────────────────┐
│                    PM-Agent 系统                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────┐  ┌─────────────────┐                 │
│  │  📥 材料输入中心  │  │  📊 项目管理     │                 │
│  │  (本文档)       │  │                 │                 │
│  └─────────────────┘  └─────────────────┘                 │
│                                                             │
│  ┌─────────────────┐  ┌─────────────────┐                 │
│  │  📋 案件中心    │  │  📑 文档中心    │                 │
│  │                 │  │                 │                 │
│  └─────────────────┘  └─────────────────┘                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 2.2 与现有模块的关系

| 模块 | 关系 |
|------|------|
| 客户管理 | 材料可能归属特定客户 |
| 项目管理 | 材料归属特定项目 |
| oc-collab | **独立**，不修改其代码 |

---

## 三、核心功能

### 3.1 功能架构

```
┌─────────────────────────────────────────────────────────────────┐
│                        材料处理流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐               │
│   │ 1.材料输入 │ → │ 2.类型识别│ → │3.项目匹配│               │
│   └──────────┘    └──────────┘    └──────────┘               │
│       │              │               │                         │
│       ▼              ▼               ▼                         │
│   ┌──────────────────────────────────────┐                   │
│   │        4. 原始材料存储                 │                   │
│   │   (data/raw/{project_id}/{id})       │                   │
│   └──────────────────────────────────────┘                   │
│                      │                                          │
│                      ▼                                          │
│   ┌──────────────────────────────────────┐                   │
│   │        5. 内容提取                     │                   │
│   │   (转写/OCR，保留原始结果)            │                   │
│   └──────────────────────────────────────┘                   │
│                      │                                          │
│                      ▼                                          │
│   ┌──────────────────────────────────────┐                   │
│   │        6. 模板转换                    │                   │
│   │   (根据项目模板转化为规范格式)        │                   │
│   └──────────────────────────────────────┘                   │
│                      │                                          │
│                      ▼                                          │
│   ┌──────────────────────────────────────┐                   │
│   │        7. 结果输出                    │                   │
│   │   (原始材料 + 转写 + 规范文档)        │                   │
│   └──────────────────────────────────────┘                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### 3.2 详细功能

#### 3.2.1 材料输入

| 功能 | 说明 |
|------|------|
| **文件上传** | 支持拖拽和点击上传 |
| **批量上传** | 支持一次上传多个文件 |
| **文件类型** | 支持音频、图片、文档等各种格式 |

**支持的格式**：

| 类型 | 格式 |
|------|------|
| 音频 | mp3, wav, m4a, ogg, flac, webm |
| 图片 | png, jpg, jpeg, gif, bmp, webp |
| 文档 | pdf, docx, doc, txt, md |
| 数据 | json, csv, xlsx |

#### 3.2.2 类型识别

| 功能 | 说明 |
|------|------|
| **自动识别** | 根据文件扩展名和内容自动识别材料类型 |
| **手动指定** | 用户也可以手动指定材料类型 |
| **类型映射** | 不同类型触发不同的处理流程 |

**处理流程映射**：

| 材料类型 | 处理方式 |
|----------|----------|
| 音频 | 语音转写（硅基流动API → dossierai → 本地Whisper） |
| 图片 | OCR识别（dossierai → pytesseract） |
| 文档 | 内容提取（dossierai） |
| 其他 | 尝试解析或标记为"待处理" |

#### 3.2.3 项目匹配

| 功能 | 说明 |
|------|------|
| **自动匹配** | 根据材料内容关键词自动匹配项目 |
| **手动指定** | 用户选择归属项目 |
| **新建项目** | 直接创建新材料关联的新项目 |

#### 3.2.4 原始材料存储

| 存储内容 | 路径 |
|----------|------|
| 原始文件 | `data/raw/{project_id}/{material_id}/{filename}` |
| 转写结果 | `data/raw/{project_id}/{material_id}/transcript.txt` |
| OCR结果 | `data/raw/{project_id}/{material_id}/ocr.txt` |
| 元数据 | `data/raw/{project_id}/{material_id}/meta.json` |

#### 3.2.5 去重处理

| 机制 | 说明 |
|------|------|
| **文件哈希** | 根据文件内容生成哈希值 |
| **查重检查** | 上传时检查是否存在相同文件 |
| **结果复用** | 相同文件直接返回已有的转写/OCR结果 |

#### 3.2.6 模板转换

| 功能 | 说明 |
|------|------|
| **项目模板** | 每个项目可以配置专属的转换模板 |
| **模板选择** | 系统自动推荐或用户手动选择 |
| **内容转换** | 将原始材料转化为结构化格式 |

**模板类型示例**：

| 模板类型 | 说明 |
|----------|------|
| 庭审记录 | 提取：当事人、案由、争议焦点、判决结果 |
| 会议纪要 | 提取：参会人员、会议议题、讨论要点、决议事项 |
| 需求文档 | 提取：需求描述、优先级、预期效果 |
| 证据材料 | 提取：证据名称、来源、证明目的 |

---

## 四、页面设计

### 4.1 页面结构

```
┌─────────────────────────────────────────────────────────────┐
│  左侧导航栏         │           右侧主内容区                 │
├─────────────────────┼───────────────────────────────────────┤
│  📱 首页            │  ┌─────────────────────────────────┐  │
│  📁 案件中心        │  │     📥 材料输入中心              │  │
│  ⚖️ 庭审记录        │  ├─────────────────────────────────┤  │
│  📋 会议纪要        │  │                                 │  │
│  📚 法律法规        │  │  项目选择：[请选择项目 ▼]         │  │
│                     │  │                                 │  │
│                     │  │  ┌─────────────────────────┐    │  │
│                     │  │  │   点击或拖拽文件到此处  │    │  │
│                     │  │  │   支持音频/图片/文档    │    │  │
│                     │  │  └─────────────────────────┘    │  │
│                     │  │                                 │  │
│                     │  │  [上传材料]                     │  │
│                     │  │                                 │  │
│                     │  ├─────────────────────────────────┤  │
│                     │  │  📋 材料列表                     │  │
│                     │  │  ┌─────────────────────────┐    │  │
│                     │  │  │ 案件录音.mp3  2026-02-17│    │  │
│                     │  │  │ 合同扫描件.pdf  已转写  │    │  │
│                     │  │  └─────────────────────────┘    │  │
│                     │  │                                 │  │
│                     │  ├─────────────────────────────────┤  │
│                     │  │  输入指令：                     │  │
│                     │  │  ┌─────────────────────────┐    │  │
│                     │  │  │ 请帮我整理这份材料      │    │  │
│                     │  │  └─────────────────────────┘    │  │
│                     │  │                          [处理] │  │
│                     │  └─────────────────────────────────┘  │
└─────────────────────┴───────────────────────────────────────┘
```

### 4.2 材料详情页

```
┌─────────────────────────────────────────────────────────────┐
│  ← 返回                    材料详情                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  文件名：案件录音_20260217.mp3                              │
│  项目：2024-沪74民初721号                                   │
│  类型：音频                                                 │
│  状态：已转写                                              │
│  上传时间：2026-02-17 19:19                                │
│                                                             │
│  ──────────────────────────────────────────────             │
│                                                             │
│  原始材料：                                                 │
│  ┌─────────────────────────────────────────────────────┐  │
│  │                                                     │  │
│  │  [播放按钮]  00:00 / 05:30                         │  │
│  │                                                     │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  ──────────────────────────────────────────────             │
│                                                             │
│  转写结果：                                                 │
│  ┌─────────────────────────────────────────────────────┐  │
│  │ 原告某某公司诉被告某某公司融资租赁合同纠纷一案...    │  │
│  │ 原告主张：被告欠付租金共计人民币50万元...          │  │
│  │ 被告辩称：原告计算有误，实际欠付金额为...           │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  整理结果：                                                 │
│  ┌─────────────────────────────────────────────────────┐  │
│  │ 案由：融资租赁合同纠纷                               │  │
│  │ 原告：某某公司                                     │  │
│  │ 被告：某某公司                                     │  │
│  │ 争议焦点：租金计算金额争议                          │  │
│  │ 判决结果：待判决                                    │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  [删除材料]  [重新转写]  [导出]                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## 五、技术方案

---

## 五、技术方案

### 5.1 技术选型

| 组件 | 推荐方案 | 说明 |
|------|----------|------|
| **前端框架** | Vue 3 + TypeScript | 类型安全、易维护 |
| **UI组件库** | NaiveUI | Vue 3 原生支持 |
| **构建工具** | Vite | 快速开发体验 |
| **后端框架** | FastAPI | Python，与oc-collab统一 |
| **数据库** | SQLite | 轻量、项目数据存储 |
| **部署** | Docker | 便于分发 |

### 5.2 前端项目结构

```
frontend/
├── src/
│   ├── views/                      # 页面组件
│   │   ├── HomeView.vue            # 首页 - 信息输入 (F-012)
│   │   ├── MaterialView.vue       # 材料详情页 (F-013/F-014)
│   │   ├── ProjectsView.vue       # 项目列表 (F-004/F-005)
│   │   ├── TemplatesView.vue       # 项目模板配置 (F-014)
│   │   ├── CustomersView.vue      # 客户管理 (F-003)
│   │   ├── DashboardView.vue      # 进度总览 (F-007)
│   │   ├── IssuesView.vue         # 问题跟踪 (F-008~F-011)
│   │   └── SettingsView.vue       # 系统设置
│   │
│   ├── components/                 # 通用组件
│   │   ├── FileUploader.vue       # 文件上传组件
│   │   ├── MaterialList.vue      # 材料列表组件
│   │   ├── ProjectCard.vue       # 项目卡片组件
│   │   └── TemplateEditor.vue    # 模板编辑器
│   │
│   ├── router/
│   │   └── index.ts               # 路由配置
│   │
│   ├── api/                       # API调用
│   │   └── index.ts               # Axios封装 + 接口
│   │
│   ├── store/                     # 状态管理
│   │   └── index.ts               # Pinia store
│   │
│   ├── types/                     # TypeScript类型定义
│   │   ├── material.ts            # 材料相关类型
│   │   ├── project.ts             # 项目相关类型
│   │   ├── template.ts            # 模板相关类型
│   │   └── common.ts              # 通用类型
│   │
│   ├── utils/                     # 工具函数
│   │   ├── hash.ts                # 文件哈希计算
│   │   └── format.ts              # 格式化工具
│   │
│   ├── App.vue
│   └── main.ts
├── package.json
├── tsconfig.json
└── vite.config.ts
```

### 5.3 前端类型定义

```typescript
// types/material.ts

export type MaterialType = 'audio' | 'image' | 'document' | 'data';

export type MaterialStatus = 'pending' | 'processing' | 'completed' | 'failed';

export type IssueType = 'bug' | 'feature_request' | 'other';

export interface Material {
  id: string;
  projectId: number;
  projectName?: string;
  fileName: string;
  fileType: MaterialType;
  fileHash: string;
  fileSize: number;
  storagePath: string;
  status: MaterialStatus;
  issueType?: IssueType;
  issueId?: string;
  rawContent: string;
  processedContent: string;
  templateId?: string;
  isDuplicate: boolean;
  createdAt: string;
  updatedAt: string;
}

export interface MaterialUploadRequest {
  file: File;
  projectId?: number;
}

export interface MaterialProcessRequest {
  instruction?: string;
  templateId?: string;
}

export interface MaterialConvertRequest {
  templateId: string;
}

export interface MaterialListResponse {
  items: Material[];
  total: number;
  page: number;
  pageSize: number;
}
```

```typescript
// types/project.ts

export type ProjectStatus = 'planning' | 'developing' | 'testing' | 'online' | 'archived';

export interface Project {
  id: number;
  customerId?: number;
  customerName?: string;
  name: string;
  keywords?: string;
  status: ProjectStatus;
  progress: number;
  gitRepo?: string;
  ocCollabEnabled: boolean;
  requirementsCount: number;
  bugsCount: number;
  createdAt: string;
  updatedAt: string;
}

export interface ProjectCreateRequest {
  name: string;
  customerId?: number;
  keywords?: string;
  gitRepo?: string;
  ocCollabEnabled?: boolean;
}
```

```typescript
// types/template.ts

export type TemplateType = 'trial' | 'meeting' | 'requirement' | 'evidence';

export type OutputFormat = 'json' | 'markdown' | 'text';

export interface TemplateField {
  key: string;
  label: string;
  fieldType: 'string' | 'number' | 'date' | 'array' | 'object';
  required: boolean;
}

export interface ProjectTemplate {
  id: string;
  projectId: number;
  name: string;
  templateType: TemplateType;
  promptTemplate: string;
  outputFormat: OutputFormat;
  fields: TemplateField[];
  enabled: boolean;
  createdAt: string;
  updatedAt: string;
}

export interface TemplateCreateRequest {
  name: string;
  projectId: number;
  templateType: TemplateType;
  promptTemplate: string;
  outputFormat: OutputFormat;
  fields: TemplateField[];
}
```

### 5.4 后端项目结构

```
backend/
├── main.py                         # FastAPI 应用入口
├── api/
│   ├── routes/
│   │   ├── upload.py               # F-001 文件上传
│   │   ├── input.py                # F-001 文本输入
│   │   ├── materials.py            # F-013/F-014 材料管理
│   │   ├── templates.py            # F-014 模板管理
│   │   ├── customers.py            # F-003 客户CRUD
│   │   ├── projects.py             # F-004/F-005 项目管理
│   │   ├── dashboard.py            # F-007 进度数据
│   │   ├── issues.py               # F-008~F-011 问题管理
│   │   └── sync.py                # F-006 oc-collab同步
│   └── deps.py                     # 依赖注入
│
├── services/                       # 业务逻辑
│   ├── input_handler.py            # F-001/F-002 输入处理
│   ├── material_service.py          # F-013 材料服务
│   ├── dedupe_service.py            # F-013 去重服务
│   ├── template_service.py          # F-014 模板转换服务
│   ├── customer_service.py          # F-003 客户识别
│   ├── project_service.py           # F-004/F-005 项目匹配
│   ├── classifier_service.py        # F-008 问题分类
│   ├── bug_generator_service.py     # F-009 BUG生成
│   ├── proposal_generator_service.py # F-010 Proposal生成
│   ├── feedback_service.py          # F-011 动态反馈
│   └── git_service.py               # F-006 Git集成
│
├── integrations/                    # 外部集成
│   ├── dossierai_client.py          # dossierai整合
│   └── siliconflow_client.py        # 语音转写
│
├── models/
│   ├── database.py                  # SQLAlchemy模型
│   └── schemas.py                   # Pydantic schemas
│
└── utils/
    ├── file_utils.py                # 文件工具
    └── hash_utils.py                # 哈希工具
```

### 5.5 API设计

| API路径 | 方法 | 功能 | 对应需求 |
|---------|------|------|----------|
| `/api/upload` | POST | 文件上传 + 自动处理 | F-001 |
| `/api/input/text` | POST | 文本输入处理 | F-001 |
| `/api/materials` | GET | 获取材料列表 | F-012 |
| `/api/materials` | POST | 上传新材料 | F-001 |
| `/api/materials/{id}` | GET | 获取材料详情 | F-013 |
| `/api/materials/{id}` | DELETE | 删除材料 | F-013 |
| `/api/materials/{id}/process` | POST | 处理材料（转写/OCR） | F-013 |
| `/api/materials/{id}/convert` | POST | 模板转换 | F-014 |
| `/api/materials/check-duplicate` | POST | 检查重复 | F-013 |
| `/api/templates` | GET/POST | 模板CRUD | F-014 |
| `/api/templates/{id}` | PUT/DELETE | 模板更新/删除 | F-014 |
| `/api/projects` | GET/POST | 项目CRUD | F-004/F-005 |
| `/api/customers` | GET/POST | 客户CRUD | F-003 |
| `/api/dashboard` | GET | 进度数据 | F-007 |
| `/api/issues` | GET | 问题列表 | F-008~F-011 |
| `/api/sync/oc-collab` | POST | 同步到oc-collab | F-006 |

### 5.6 核心服务设计

#### 5.6.1 材料服务 (material_service.py)

```python
# backend/services/material_service.py

import hashlib
import os
from pathlib import Path
from typing import Optional, List
from uuid import uuid4

class MaterialService:
    """材料管理服务"""
    
    def __init__(self, db, storage_path: str = "data/raw"):
        self.db = db
        self.storage_path = Path(storage_path)
    
    async def upload(
        self,
        file_content: bytes,
        filename: str,
        project_id: Optional[int] = None
    ) -> Material:
        """上传材料"""
        # 1. 计算文件哈希
        file_hash = self._calculate_hash(file_content)
        
        # 2. 检查重复
        existing = await self._check_duplicate(file_hash)
        if existing:
            return existing
        
        # 3. 生成存储路径
        material_id = str(uuid4())
        material_dir = self.storage_path / str(project_id or "unassigned") / material_id
        material_dir.mkdir(parents=True, exist_ok=True)
        
        # 4. 保存文件
        file_path = material_dir / filename
        file_path.write_bytes(file_content)
        
        # 5. 创建材料记录
        material = await self._create_material(
            id=material_id,
            filename=filename,
            file_hash=file_hash,
            file_size=len(file_content),
            file_type=self._detect_type(filename),
            storage_path=str(file_path),
            project_id=project_id
        )
        
        return material
    
    def _calculate_hash(self, content: bytes) -> str:
        """计算SHA256哈希"""
        return hashlib.sha256(content).hexdigest()
    
    async def _check_duplicate(self, file_hash: str) -> Optional[Material]:
        """检查重复"""
        # 查询是否存在相同哈希的材料
        return await self.db.get_material_by_hash(file_hash)
    
    async def process(self, material_id: str, instruction: Optional[str] = None) -> dict:
        """处理材料（转写/OCR）"""
        material = await self.db.get_material(material_id)
        
        # 根据类型选择处理方式
        if material.file_type == 'audio':
            return await self._process_audio(material, instruction)
        elif material.file_type == 'image':
            return await self._process_image(material, instruction)
        else:
            return await self._process_document(material, instruction)
    
    async def convert(
        self,
        material_id: str,
        template_id: str,
        template_service: 'TemplateService'
    ) -> str:
        """模板转换"""
        material = await self.db.get_material(material_id)
        template = await template_service.get_template(template_id)
        
        # 调用LLM进行转换
        result = await template_service.convert(
            content=material.raw_content,
            template=template
        )
        
        # 保存转换结果
        await self.db.update_material(
            material_id,
            processed_content=result,
            template_id=template_id
        )
        
        return result
```

#### 5.6.2 去重服务 (dedupe_service.py)

```python
# backend/services/dedupe_service.py

import hashlib
from typing import Optional

class DedupeService:
    """材料去重服务"""
    
    def __init__(self, db):
        self.db = db
    
    async def check_duplicate(self, file_content: bytes) -> Optional[dict]:
        """检查是否重复"""
        file_hash = hashlib.sha256(file_content).hexdigest()
        
        existing = await self.db.get_material_by_hash(file_hash)
        
        if existing:
            return {
                "isDuplicate": True,
                "existingMaterial": {
                    "id": existing.id,
                    "fileName": existing.file_name,
                    "fileHash": existing.file_hash,
                    "createdAt": existing.created_at.isoformat()
                }
            }
        
        return {"isDuplicate": False, "fileHash": file_hash}
    
    async def get_duplicate_count(self, project_id: int) -> int:
        """获取项目的重复材料数量"""
        return await self.db.count_duplicate_materials(project_id)
```

#### 5.6.3 模板服务 (template_service.py)

```python
# backend/services/template_service.py

from typing import List, Optional, Dict, Any
from uuid import uuid4

class TemplateService:
    """模板转换服务"""
    
    def __init__(self, db, llm_client):
        self.db = db
        self.llm = llm_client
    
    async def create_template(self, data: dict) -> ProjectTemplate:
        """创建模板"""
        template = ProjectTemplate(
            id=str(uuid4()),
            project_id=data['project_id'],
            name=data['name'],
            template_type=data['template_type'],
            prompt_template=data['prompt_template'],
            output_format=data['output_format'],
            fields=data['fields'],
            enabled=True
        )
        
        await self.db.save_template(template)
        return template
    
    async def convert(self, content: str, template: ProjectTemplate) -> str:
        """使用模板转换内容"""
        # 构建prompt
        prompt = self._build_prompt(content, template)
        
        # 调用LLM
        result = await self.llm.chat(
            message=prompt,
            system="你是一个专业的文档处理助手，请严格按照要求的格式输出。"
        )
        
        # 根据输出格式处理结果
        if template.output_format == 'json':
            return self._format_json(result)
        elif template.output_format == 'markdown':
            return self._format_markdown(result, template.fields)
        else:
            return result
    
    def _build_prompt(self, content: str, template: ProjectTemplate) -> str:
        """构建转换prompt"""
        fields_desc = "\n".join([
            f"- {f['label']}" for f in template.fields
        ])
        
        return f"""{template.prompt_template}

请从以下内容中提取信息：
{content}

需要提取的字段：
{fields_desc}

请严格按照上述格式输出。"""
```

### 5.7 数据模型

#### 5.7.1 数据库Schema（新增表）

```sql
-- 材料表
CREATE TABLE materials (
    id TEXT PRIMARY KEY,
    project_id INTEGER,
    file_name TEXT NOT NULL,
    file_type TEXT NOT NULL CHECK(file_type IN ('audio', 'image', 'document', 'data')),
    file_hash TEXT NOT NULL,
    file_size INTEGER NOT NULL,
    storage_path TEXT NOT NULL,
    status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'processing', 'completed', 'failed')),
    issue_type TEXT CHECK(issue_type IN ('bug', 'feature_request', 'other')),
    issue_id TEXT,
    raw_content TEXT,
    processed_content TEXT,
    template_id TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL
);

-- 项目模板表
CREATE TABLE project_templates (
    id TEXT PRIMARY KEY,
    project_id INTEGER NOT NULL,
    name TEXT NOT NULL,
    template_type TEXT NOT NULL CHECK(template_type IN ('trial', 'meeting', 'requirement', 'evidence')),
    prompt_template TEXT NOT NULL,
    output_format TEXT DEFAULT 'markdown' CHECK(output_format IN ('json', 'markdown', 'text')),
    fields TEXT NOT NULL,  -- JSON格式存储字段定义
    enabled BOOLEAN DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);

-- 索引
CREATE INDEX idx_materials_project_id ON materials(project_id);
CREATE INDEX idx_materials_file_hash ON materials(file_hash);
CREATE INDEX idx_materials_status ON materials(status);
CREATE INDEX idx_project_templates_project_id ON project_templates(project_id);
```

### 5.8 文件存储结构

```
data/
├── raw/                              # 原始材料
│   ├── {project_id}/                 # 按项目分目录
│   │   ├── {material_id}/           # 按材料ID分目录
│   │   │   ├── original_file         # 原始文件
│   │   │   ├── transcript.txt        # 转写结果
│   │   │   ├── ocr.txt              # OCR结果
│   │   │   └── meta.json             # 元数据
│   │   │
│   │   └── ...
│   │
│   └── unassigned/                  # 未分配项目的材料
│
└── pm_agent.db                      # SQLite数据库
```

### 5.9 处理流程

#### 5.9.1 材料上传流程

```
用户上传文件
       │
       ▼
计算文件哈希 (SHA256)
       │
       ▼
检查重复 ◄────────────┐
       │               │
   ┌───┴───┐           │
   │ 重复  │ 不重复    │
   ▼       ▼           │
返回已有  保存原始文件  │
材料信息  (data/raw/)  │
           │           │
           ▼           │
       创建材料记录    │
       (status=pending)│
           │           │
           └───────────┘
               │
               ▼
        返回上传结果
```

#### 5.9.2 材料处理流程

```
用户点击"处理"/"转写&整理"
       │
       ▼
检查材料状态
   ┌───┴───┐
   │ 已完成 │ 未处理
   ▼       ▼
返回已有  调用InputHandler
结果     处理材料
   │       │
   │   ┌───┴───┐
   │   │音频   │图片   │文档
   │   ▼       ▼       ▼
   │  转写    OCR     解析
   │       │       │
   │       └───────┘
   │           │
   ▼           ▼
保存raw_content
更新status=completed
       │
       ▼
返回处理结果
```

#### 5.9.3 模板转换流程

```
用户选择模板，点击"转换"
       │
       ▼
获取材料raw_content
       │
       ▼
获取模板配置
       │
       ▼
构建LLM prompt
       │
       ▼
调用LLM转换
       │
       ▼
格式化输出
       │
       ▼
保存processed_content
更新template_id
       │
       ▼
返回转换结果
```

### 5.4 处理流程

#### 5.4.1 材料上传流程

```
1. 用户上传文件
         │
         ▼
2. 计算文件哈希
         │
         ▼
3. 检查是否重复
    ┌────┴────┐
    │ 重复    │ 不重复
    ▼         ▼
返回已有结果  继续处理
             │
             ▼
4. 保存原始文件
   (data/raw/{project_id}/{id}/{filename})
             │
             ▼
5. 自动识别文件类型
             │
             ▼
6. 创建材料记录（status=pending）
             │
             ▼
7. 返回材料ID和状态
```

#### 5.4.2 材料处理流程

```
1. 用户点击"处理"/"转写&整理"
         │
         ▼
2. 检查材料状态
    ┌────┴────┐
    │ 已处理  │ 未处理
    ▼         ▼
返回已有结果  继续处理
             │
             ▼
3. 根据文件类型选择处理方式
    ┌────┬────┬────┐
    │音频 │图片 │文档│
    ▼    ▼    ▼
   转写  OCR  解析
             │
             ▼
4. 保存原始提取结果
   (raw_content)
             │
             ▼
5. 如果有指令，进行模板转换
   (processed_content)
             │
             ▼
6. 更新状态为completed
         │
         ▼
7. 返回处理结果
```

---

## 六、与现有系统的关系

### 6.1 复用现有能力

| 现有能力 | 复用方式 |
|----------|----------|
| `siliconflow_client.py` | 语音转写 |
| `dossierai_client.py` | OCR、内容提取、LLM调用 |
| `input_handler.py` | 统一输入处理逻辑 |
| 项目管理模块 | 项目选择和关联 |

### 6.2 独立实现

- **不修改** oc-collab 系统代码
- 前端页面独立于现有的简单HTML页面
- 后端API独立于现有的项目管理API
- 数据存储使用独立的表（material, project_template）

---

## 七、实施计划

### 7.1 Phase 1: 基础功能（1周）

| 任务 | 说明 | 产出 |
|------|------|------|
| 环境搭建 | Vue 3 + TypeScript + Vite + NaiveUI | 可运行的前端项目 |
| 数据库设计 | 创建materials、project_templates表 | SQL迁移脚本 |
| 后端基础API | 材料CRUD接口 | `/api/materials` |
| 前端基础页面 | 首页（信息输入）+ 项目列表 | 可交互页面 |
| 文件存储 | 原始文件存储 + 目录结构 | `data/raw/` |
| 哈希去重 | SHA256计算 + 重复检查 | `/api/materials/check-duplicate` |

### 7.2 Phase 2: 内容提取（1周）

| 任务 | 说明 | 产出 |
|------|------|------|
| 音频转写 | 集成siliconflow_client | 转写服务 |
| 图片OCR | 集成dossierai_client | OCR服务 |
| 文档解析 | 集成dossierai_client | 解析服务 |
| 原始结果存储 | 保存raw_content到数据库 | 材料记录完整 |
| 处理状态管理 | pending→processing→completed | 状态流转正确 |

### 7.3 Phase 3: 模板转换（1周）

| 任务 | 说明 | 产出 |
|------|------|------|
| 模板管理API | CRUD接口 | `/api/templates` |
| 前端模板配置页 | 项目模板管理页面 | UI页面 |
| 指令解析 | 识别用户指令类型 | 智能理解 |
| 内容转换 | 调用LLM进行模板转换 | 格式化输出 |
| 材料详情页 | 展示+转换+重新处理 | 完整功能 |

### 7.4 Phase 4: 与现有系统集成（1周）

| 任务 | 说明 | 产出 |
|------|------|------|
| 客户识别集成 | 复用CustomerService | 自动匹配项目 |
| 问题分类集成 | 复用ClassifierService | 自动分类BUG/需求 |
| 去重展示 | 材料列表显示去重状态 | 用户体验 |
| 问题跟踪集成 | IssuesView页面 | BUG/需求跟踪 |

### 7.5 Phase 5: 完善与优化（0.5周）

| 任务 | 说明 |
|------|------|
| 进度显示 | 处理过程中显示进度 |
| 错误处理 | 完善异常情况处理 |
| 调试修复 | Bug修复和优化 |

---

## 八、预期效果

| 效果 | 说明 |
|------|------|
| **材料统一入口** | 所有类型的原始材料都可以上传 |
| **自动识别** | 自动识别文件类型和处理方式 |
| **去重处理** | 相同文件不会重复处理 |
| **原始保留** | 原始材料和提取结果都完整保留 |
| **模板转换** | 根据项目需求转化为规范格式 |

---

## 九、风险与应对

| 风险 | 应对 |
|------|------|
| API调用失败 | 备用方案：本地Whisper/pytesseract fallback |
| 大文件处理 | 文件大小限制（100MB）+ 进度提示 |
| 转写/OCR不准 | 提供编辑功能，允许用户手动修改 |
| 模板匹配不准 | 支持手动选择和调整模板 |

---

## 十、下一步

1. 评审通过后，由Agent3（产品经理）制定详细开发计划
2. 确认项目模板的具体字段和格式
3. 开始Phase 1开发

---

**文档版本历史**：

| 版本 | 日期 | 变更 |
|------|------|------|
| v1 | 2026-02-17 | 初始版本 |
