Add project documentation and specs
This commit is contained in:
307
docs/superpowers/specs/2026-03-21-knowledge-folder-design.md
Normal file
307
docs/superpowers/specs/2026-03-21-knowledge-folder-design.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# 知识库文件夹分层设计
|
||||
|
||||
> **Goal:** 为知识库添加文件夹分层组织功能,支持多层嵌套、CRUD 操作,支持知识大脑汇聚各类内容。
|
||||
|
||||
## 1. 概念与愿景
|
||||
|
||||
知识库是用户的**资料中枢**,文件夹分层让知识更有序。用户可以按主题/项目/类型建立文件夹层级,如 `技术文档/Python/入门.pdf`。
|
||||
|
||||
知识大脑会汇聚来自知识库、待办、看板、论坛、对话的内容,形成完整的用户知识画像。文件夹是知识的入口分类,而非知识图谱的一部分。
|
||||
|
||||
## 2. 数据模型
|
||||
|
||||
### 2.1 Folder 表(邻接表模式)
|
||||
|
||||
```python
|
||||
class Folder(BaseModel):
|
||||
__tablename__ = "folders"
|
||||
|
||||
user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
parent_id = Column(String(36), ForeignKey("folders.id"), nullable=True) # NULL=根目录
|
||||
# 注意: id, created_at, updated_at 继承自 BaseModel
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- 邻接表模式:通过 `parent_id` 指向父文件夹
|
||||
- 根目录文件夹的 `parent_id = NULL`
|
||||
- 查询完整树结构使用递归 CTE
|
||||
- **唯一约束**:`user_id + parent_id + name` 组合唯一,防止同级重名
|
||||
|
||||
**验证规则:**
|
||||
- 文件夹名称不能为空,最大 255 字符
|
||||
- 不允许包含字符:`/ \ * ? :`
|
||||
- 最大嵌套深度:10 层(防止 UI/性能问题)
|
||||
|
||||
### 2.2 Document 表变更
|
||||
|
||||
```python
|
||||
class Document(BaseModel):
|
||||
# ...现有字段...
|
||||
folder_id = Column(String(36), ForeignKey("folders.id"), nullable=True) # 新增
|
||||
```
|
||||
|
||||
**约定:**
|
||||
- `folder_id = NULL` 表示文档在根目录(未分类)
|
||||
- 删除文件夹时,级联删除该文件夹及其所有子文件夹中的文档
|
||||
|
||||
### 2.3 ChromaDB Metadata
|
||||
|
||||
```python
|
||||
{
|
||||
"document_id": "xxx",
|
||||
"document_title": "入门.pdf",
|
||||
"folder_path": "/技术文档/Python", # 完整路径,用于检索过滤
|
||||
"file_type": "pdf",
|
||||
"chunk_index": 0,
|
||||
}
|
||||
```
|
||||
|
||||
## 3. API 接口
|
||||
|
||||
### 3.1 文件夹管理
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/folders` | 获取用户的完整文件夹树 |
|
||||
| POST | `/api/folders` | 创建文件夹 `{ name, parent_id? }` |
|
||||
| PUT | `/api/folders/{id}` | 重命名文件夹 `{ name }` |
|
||||
| DELETE | `/api/folders/{id}` | 删除文件夹(级联删除文档) |
|
||||
|
||||
**GET /api/folders 响应:**
|
||||
```json
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"id": "xxx",
|
||||
"name": "技术文档",
|
||||
"parent_id": null,
|
||||
"children": [
|
||||
{
|
||||
"id": "yyy",
|
||||
"name": "Python",
|
||||
"parent_id": "xxx",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 文档管理变更
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/documents?folder_id=` | 按文件夹查询文档 |
|
||||
| POST | `/api/documents` | 上传文档时指定 `folder_id` |
|
||||
| DELETE | `/api/documents/{id}` | 删除文档 |
|
||||
|
||||
**POST /api/documents 请求增加可选字段:**
|
||||
```json
|
||||
{
|
||||
"file": "<binary>",
|
||||
"folder_id": "yyy" // 可选,不传表示根目录
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 安全与权限
|
||||
|
||||
**所有权验证:**
|
||||
- 所有文件夹操作必须验证 `folder.user_id == current_user.id`
|
||||
- 文档操作时验证 `document.user_id == current_user.id`
|
||||
- `folder_id` 参数需要验证归属,防止跨用户访问
|
||||
|
||||
**示例中间件:**
|
||||
```python
|
||||
async def verify_folder_access(folder_id: str, user_id: str, db: AsyncSession):
|
||||
result = await db.execute(
|
||||
select(Folder).where(Folder.id == folder_id, Folder.user_id == user_id)
|
||||
)
|
||||
if not result.scalar_one_or_none():
|
||||
raise HTTPException(status_code=403, detail="无权访问此文件夹")
|
||||
```
|
||||
|
||||
### 3.4 向量检索变更
|
||||
|
||||
`KnowledgeService.retrieve()` 增加可选参数 `folder_id`:
|
||||
|
||||
```python
|
||||
async def retrieve(
|
||||
self,
|
||||
query: str,
|
||||
user_id: str,
|
||||
folder_id: str | None = None, # 新增
|
||||
top_k: int = 5,
|
||||
):
|
||||
# 如果指定 folder_id,构建 path 前缀过滤
|
||||
folder_path = await self._get_folder_path(folder_id)
|
||||
where = {"folder_path": {"$starts_with": folder_path}} if folder_path else None
|
||||
```
|
||||
|
||||
### 3.5 ChromaDB 同步策略
|
||||
|
||||
**文件夹重命名/移动时的同步:**
|
||||
|
||||
由于 ChromaDB metadata 中存储了 `folder_path`,当文件夹路径变化时需要同步更新:
|
||||
|
||||
```python
|
||||
async def update_folder_paths(folder_id: str, old_path: str, new_path: str):
|
||||
"""更新所有子文件夹和文档的路径"""
|
||||
# 1. 更新所有子文件夹的 path
|
||||
children = await db.execute(
|
||||
select(Folder).where(Folder.parent_id == folder_id)
|
||||
)
|
||||
for child in children.scalars():
|
||||
child_new_path = new_path + "/" + child.name
|
||||
await update_folder_paths(child.id, old_path + "/" + child.name, child_new_path)
|
||||
|
||||
# 2. 更新该文件夹下所有文档的 ChromaDB metadata
|
||||
docs = await db.execute(
|
||||
select(Document).where(Document.folder_id == folder_id)
|
||||
)
|
||||
for doc in docs.scalars():
|
||||
collection.update(
|
||||
where={"document_id": doc.id},
|
||||
set={"folder_path": new_path}
|
||||
)
|
||||
```
|
||||
|
||||
**删除文件夹时的清理:**
|
||||
|
||||
```python
|
||||
async def delete_folder_cascade(folder_id: str):
|
||||
"""级联删除:先删子文件夹,再删文档,最后删自己"""
|
||||
# 1. 递归删除所有子文件夹
|
||||
children = await db.execute(
|
||||
select(Folder).where(Folder.parent_id == folder_id)
|
||||
)
|
||||
for child in children.scalars():
|
||||
await delete_folder_cascade(child.id)
|
||||
|
||||
# 2. 删除该文件夹下所有文档(从 ChromaDB 和数据库)
|
||||
docs = await db.execute(
|
||||
select(Document).where(Document.folder_id == folder_id)
|
||||
)
|
||||
for doc in docs.scalars():
|
||||
await knowledge_service.delete_from_vectorstore(user_id, doc.id)
|
||||
await db.delete(doc)
|
||||
|
||||
# 3. 删除文件夹本身
|
||||
folder = await db.get(Folder, folder_id)
|
||||
await db.delete(folder)
|
||||
```
|
||||
|
||||
## 4. 前端设计
|
||||
|
||||
### 4.1 布局结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ KNOWLEDGE BASE [+新建文件夹] [+上传] │
|
||||
├──────────────┬──────────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ 📁 技术文档 │ 搜索栏 [🔍 搜索...] [混合▼] │
|
||||
│ 📁 Python │ │
|
||||
│ 📄 入门 │ ┌─────────────────────────────────┐ │
|
||||
│ 📄 进阶 │ │ 文档标题 │ │
|
||||
│ 📁 Vue │ │ 类型 · 大小 · 状态 │ │
|
||||
│ 📁 产品 │ └─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ 📁 临时文件 │ ┌─────────────────────────────────┐ │
|
||||
│ │ │ ... │ │
|
||||
│ │ └─────────────────────────────────┘ │
|
||||
└──────────────┴──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 组件结构
|
||||
|
||||
```
|
||||
KnowledgeView
|
||||
├── Header (标题 + 操作按钮)
|
||||
├── MainLayout (flexbox: sidebar + content)
|
||||
│ ├── FolderTree (左侧边栏)
|
||||
│ │ ├── FolderItem (递归组件)
|
||||
│ │ │ ├── folder icon + name
|
||||
│ │ │ ├── children (递归)
|
||||
│ │ │ └── context menu (右键: 重命名/删除)
|
||||
│ │ └── AddFolderButton
|
||||
│ │
|
||||
│ └── ContentArea (右侧主区域)
|
||||
│ ├── SearchBar
|
||||
│ ├── UploadZone
|
||||
│ ├── DocumentList
|
||||
│ └── SearchResults
|
||||
```
|
||||
|
||||
### 4.3 交互细节
|
||||
|
||||
| 操作 | 行为 |
|
||||
|------|------|
|
||||
| 点击文件夹 | 高亮选中,显示该文件夹下文档 |
|
||||
| 右键文件夹 | 弹出菜单:重命名 / 删除 |
|
||||
| 双击文件夹名 | 进入编辑状态 |
|
||||
| 新建文件夹 | 弹出输入框,默认在当前选中位置创建 |
|
||||
| 上传文档 | 需先选择目标文件夹,否则默认根目录 |
|
||||
| 搜索 | 可选限定在当前文件夹内搜索 |
|
||||
|
||||
### 4.4 UI 风格
|
||||
|
||||
保持一致的 sci-fi holographic 风格:
|
||||
- 主色调:青色 `#00f5d4` + 深色背景
|
||||
- 文件夹图标:使用 Folder/FolderOpen 图标
|
||||
- 悬停/选中状态:边框高亮 + 背景色变化
|
||||
- 动画:展开/折叠动画 200ms ease
|
||||
|
||||
## 5. 实施步骤
|
||||
|
||||
### Phase 1: 数据层
|
||||
1. 创建 `Folder` 模型和表
|
||||
2. 修改 `Document` 模型,增加 `folder_id` 外键
|
||||
3. 添加数据库迁移
|
||||
|
||||
### Phase 2: 后端 API
|
||||
1. 实现文件夹 CRUD 接口
|
||||
2. 修改文档上传接口,支持 `folder_id`
|
||||
3. 修改文档列表接口,支持 `folder_id` 过滤
|
||||
4. 修改向量检索,支持 `folder_id` 范围限定
|
||||
5. 实现递归 CTE 查询文件夹树
|
||||
|
||||
**递归 CTE 示例(获取完整文件夹树):**
|
||||
```sql
|
||||
WITH RECURSIVE folder_tree AS (
|
||||
-- 基础查询:根文件夹
|
||||
SELECT id, name, parent_id, 0 as depth
|
||||
FROM folders
|
||||
WHERE user_id = :user_id AND parent_id IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 递归查询:子文件夹
|
||||
SELECT f.id, f.name, f.parent_id, ft.depth + 1
|
||||
FROM folders f
|
||||
INNER JOIN folder_tree ft ON ft.id = f.parent_id
|
||||
WHERE f.user_id = :user_id
|
||||
)
|
||||
SELECT * FROM folder_tree ORDER BY depth, name;
|
||||
```
|
||||
|
||||
### Phase 3: 前端
|
||||
1. 创建 `FolderTree` 组件
|
||||
2. 改造 `KnowledgeView` 布局
|
||||
3. 实现文件夹右键菜单(重命名/删除)
|
||||
4. 实现新建文件夹弹窗
|
||||
5. 上传时强制选择文件夹
|
||||
|
||||
### Phase 4: 测试
|
||||
1. 文件夹 CRUD 测试
|
||||
2. 级联删除测试(删除文件夹 + 文档)
|
||||
3. 向量检索按文件夹过滤测试
|
||||
4. 前端交互测试
|
||||
|
||||
## 6. 技术约束
|
||||
|
||||
- SQLite 的递归 CTE 查询文件夹树
|
||||
- 删除文件夹时先删除子文件夹(递归),再删除文档
|
||||
- ChromaDB 的 `where` 过滤使用 `$starts_with` 做路径前缀匹配
|
||||
- 前端递归组件注意防止无限循环
|
||||
Reference in New Issue
Block a user