Files
DataMate/memory/2026-02-03-0758.md
Jerry Yan 7606cd34bd feat(user-permission): 实现用户权限体系并修复所有问题
- 数据库层面:
  - 创建 RBAC 核心表(角色、菜单权限)
  - 扩展现有表支持数据共享
  - 初始化基础数据

- 后端层面:
  - 实现 UserContext 用户上下文管理
  - 实现数据集访问权限服务
  - 实现菜单权限服务
  - 添加数据集共享功能
  - 修复前端命名不匹配问题(snake_case vs camelCase)
  - 修复请求头不匹配问题(X-User-Roles vs X-Role-Codes)
  - 修复 Mapper 方法未实现问题
  - 修复共享设置持久化缺失问题

- 前端层面:
  - 创建菜单权限工具
  - 更新 Redux Store 支持菜单过滤
  - 创建数据集共享设置组件
  - 添加用户信息到请求头
  - 实现 Token 刷新逻辑

- 数据隔离:
  - 实现 MyBatis 查询权限检查
  - 实现数据文件访问控制

参考:
- Codex 生成的实施方案
- kimI-cli 实施结果
- Codex Review 审核报告

修复的问题:
1. 前端命名不匹配(is_shared -> isShared, shared_with -> sharedWith)
2. 请求头不匹配(X-User-Roles -> X-Role-Codes)
3. Mapper 方法未实现(添加 findFilesWithAccessCheck 等方法声明)
4. 共享设置持久化缺失(添加 isShared 和 sharedWith 字段到 UpdateDatasetRequest)
5. 用户上下文加载问题(实现 Token 刷新逻辑)
2026-02-04 04:33:13 +00:00

1102 lines
35 KiB
Markdown

# Session: 2026-02-03 07:58:53 UTC
- **Session Key**: agent:main:main
- **Session ID**: b66098d1-092b-4870-bbe5-b170d7fbaa94
- **Source**: telegram
## Conversation Summary
assistant: Kimi 正在后台运行分析。从日志来看,它已经读取了大量相关代码,正在进行思考分析。让我继续查看...
assistant: Kimi 已经完成了深入分析!让我为你整理一下分析结果:
---
## 📋 分析总结
### 问题1:上传文件后任务中心延迟弹出
#### 🔍 根本原因
**当前流程:**
1. 用户点击"确定" → `handleImportData` 函数
2. 触发文件上传事件
3. 调用 `uploadFile` → 先等待 `preUpload` API 返回
4. **然后才触发 `show:task-popover` 事件**
5. 任务中心显示
**问题:** 任务中心的显示被 `preUpload` API 调用阻塞,网络延迟导致任务中心弹出有明显的延迟感。
#### ✅ 解决方案
**推荐修改:`frontend/src/hooks/useSliceUpload.tsx`**
```typescript
// 修改 createTask 函数,在创建任务后立即显示任务中心
const createTask = (detail: any = {}) => {
const { dataset } = detail;
const title = `上传数据集: ${dataset.name} `;
const controller = new AbortController();
const task: TaskItem = {
key: dataset.id,
title,
percent: 0,
reqId: -1,
controller,
size: 0,
updateEvent: detail.updateEvent,
hasArchive: detail.hasArchive,
prefix: detail.prefix,
};
taskListRef.current = [task, ...taskListRef.current];
setTaskList(taskListRef.current);
// ✅ 立即触发任务中心显示
if (showTaskCenter) {
window.dispatchEvent(
new CustomEvent("show:task-popover", { detail: { show: true } })
);
}
return task;
};
```
然后删除 `uploadFile` 函数中第141-145行的重复触发代码。
---
### 问题2:文本分片实时计算改为后台预分片并缓存
#### 🔍 当前实现分析
**实时分片流程:**
每次调用 `get_task` 时:
1. 调用 `_build_text_task` 方法
2. 实时读取文件内容(`fetch_text_content_via_download_api`
3. 实时调用 `AnnotationTextSplitter.split()` 进行分片
4. 返回分片结果
**性能问题:**
- 每次获取任务都实时分片,对于大文件会造成性能瓶颈
- 重复计算浪费资源
#### ✅ 解决方案
##### 步骤1:创建分片缓存表
`runtime/datamate-python/app/db/models/annotation_management.py` 中添加:
```python
class TextSegmentCache(Base):
"""文本分片缓存表"""
__tablename__ = "t_dm_text_segment_cache"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
project_id = Column(String(36), nullable=False, comment="标注项目ID")
file_id = Column(String(36), nullable=False, comment="文件ID")
segment_index = Column(Integer, nullable=False, comment="分片索引")
segment_text = Column(Text, nullable=False, comment="分片文本内容")
start_pos = Column(Integer, nullable=False, comment="在原文中的起始位置")
end_pos = Column(Integer, nullable=False, comment="在原文中的结束位置")
created_at = Column(TIMESTAMP, server_default=func.current_timestamp())
__table_args__ = (
UniqueConstraint("project_id", "file_id", "segment_index", name="uk_project_file_segment"),
Index("idx_project_file", "project_id", "file_id"),
)
```
##### 步骤2:创建分片缓存服务
新建 `runtime/datamate-python/app/module/annotation/service/segment_cache.py`
```python
class TextSegmentCacheService:
"""文本分片缓存服务"""
SEGMENT_THRESHOLD = 200
@staticmethod
async def precompute_segments(
db: AsyncSession,
project_id: str,
dataset_id: str,
file_ids: List[str],
) -> None:
"""预计算并缓存文本分片"""
splitter = AnnotationTextSplitter(max_chars=TextSegmentCacheService.SEGMENT_THRESHOLD)
for file_id in file_ids:
try:
# 获取文件内容
text_content = await fetch_text_content_via_download_api(dataset_id, file_id)
if not isinstance(text_content, str):
continue
# 解析内容
records = TextSegmentCacheService._parse_content(text_content)
# 执行分片
segment_index = 0
segments_to_insert = []
for record_text in records:
if len(record_text) > TextSegmentCacheService.SEGMENT_THRESHOLD:
raw_segments = splitter.split(record_text)
for seg in raw_segments:
segments_to_insert.append({
"project_id": project_id,
"file_id": file_id,
"segment_index": segment_index,
"segment_text": seg["text"],
"start_pos": seg["start"],
"end_pos": seg["end"],
})
segment_index += 1
else:
segments_to_insert.append({
"project_id": project_id,
"file_id": file_id,
"segment_index": segment_index,
"segment_text": record_text,
"start_pos": 0,
"end_pos": len(record_text),
})
segment_index += 1
# 批量插入
if segments_to_insert:
for seg_data in segments_to_insert:
cache_record = TextSegmentCache(**seg_data)
db.add(cache_record)
except Exception as e:
logging.getLogger(__name__).error(f"Failed to precompute segments for file {file_id}: {e}")
continue
await db.commit()
@staticmethod
async def get_segments(
db: AsyncSession,
project_id: str,
file_id: str,
) -> List[TextSegmentCache]:
"""获取缓存的分片结果"""
result = await db.execute(
select(TextSegmentCache)
.where(
TextSegmentCache.project_id == project_id,
TextSegmentCache.file_id == file_id,
)
.order_by(TextSegmentCache.segment_index)
)
return list(result.scalars().all())
@staticmethod
def _parse_content(text_content: str) -> List[str]:
"""解析文件内容为文本记录列表"""
# 支持 JSONL/JSON/纯文本
# ... 实现代码 ...
```
##### 步骤3:在任务创建时触发预分片
修改 `runtime/datamate-python/app/module/annotation/interface/project.py``create_mapping` 函数:
```python
@router.post("", response_model=StandardResponse[DatasetMappingCreateResponse], status_code=201)
async def create_mapping(
request: DatasetMappingCreateRequest,
db: AsyncSession = Depends(get_db)
):
# ... 现有代码 ...
# 创建映射关系并写入快照
mapping = await mapping_service.create_mapping_with_snapshot(
labeling_project, snapshot_file_ids
)
# ✅ 新增:如果启用了文本分片,后台预计算分片
if (dataset_type == TEXT_DATASET_TYPE
and request.segmentation_enabled is not False
and snapshot_file_ids):
from app.module.annotation.service.segment_cache import TextSegmentCacheService
import asyncio
# 异步后台处理,不阻塞响应
asyncio.create_task(
TextSegmentCacheService.precompute_segments(
db=db,
project_id=mapping.id,
dataset_id=request.dataset_id,
file_ids=snapshot_file_ids,
)
)
# ... 返回响应 ...
```
##### 步骤4:修改编辑器使用缓存
修改 `runtime/datamate-python/app/module/annotation/service/editor.py``_build_text_task` 方法:
```python
async def _build_text_task(
self,
project: LabelingProject,
file_record: DatasetFiles,
file_id: str,
segment_index: Optional[int],
) -> EditorTaskResponse:
# ... 现有代码 ...
# ✅ 优先从缓存获取分片结果
from app.module.annotation.service.segment_cache import TextSegmentCacheService
cached_segments = await TextSegmentCacheService.get_segments(
self.db, project.id, file_id
)
if cached_segments:
# 使用缓存的分片结果
segments = [
SegmentInfo(
idx=seg.segment_index,
text=seg.segment_text,
start=seg.start_pos,
end=seg.end_pos,
hasAnnotation=str(seg.segment_index) in segment_annotation_keys,
lineIndex=0,
chunkIndex=seg.segment_index,
)
for seg in cached_segments
]
total_segments = len(segments)
needs_segmentation = total_segments > 1 or (
total_segments == 1 and len(cached_segments[0].segment_text) > self.SEGMENT_THRESHOLD
)
else:
# 回退到实时分片(兼容已有数据)
# ... 原有实时分片逻辑 ...
# ... 后续逻辑保持不变 ...
```
---
## 📊 修改文件清单
### 问题1(任务中心延迟)
| 文件 | 修改类型 |
|------|----------|
| `frontend/src/hooks/useSliceUpload.tsx` | 修改 |
### 问题2(文本分片缓存)
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| `runtime/datamate-python/app/db/models/annotation_management.py` | 新增 | 添加 `TextSegmentCache` 表 |
| `runtime/datamate-python/app/module/annotation/service/segment_cache.py` | 新增 | 分片缓存服务 |
| `runtime/datamate-python/app/module/annotation/interface/project.py` | 修改 | 任务创建时触发预分片 |
| `runtime/datamate-python/app/module/annotation/service/editor.py` | 修改 | 使用缓存的分片结果 |
---
Kimi 已经提供了非常详细和完整的分析方案!你需要我帮你生成这些修改代码的具体文件吗?或者你对方案有任何疑问?
user: System: [2026-02-03 07:34:59 UTC] Exec completed (mild-atl, code 0) :: 考虑分片存储到对象存储(如 OBS/S3)而不是数据库\n\n3. **性能优化**:\n - 预分片可以使用消息队列(如 Celery/RQ)异步处理,而不是 `asyncio.create_task`\n - 可以添加分片进度追踪,让用户知道后台正在处理' ) StatusUpdate( context_usage=0.22057342529296875, token_usage=TokenUsage( input_other=7646, output=3474, input_cache_read=50176, input_cache_creation=0 ), message_id='chatcmpl-ccDAlxfwpv5280IGIyD5Y19A' ) TurnEnd()
[Telegram Jerry Yan (@JerryYan0912) id:5155645359 +59s 2026-02-03 07:39 UTC] 问题1请直接修复,问题2我先看看如何处理。修复后请帮我提交并推送代码
[message_id: 349]
assistant: 好的,我现在开始修复问题1,同时为你展示问题2的详细处理方案。
## 📝 开始修复
### 🔧 修复问题1:任务中心延迟弹出
assistant: 好的,让我先检查项目当前的 git 状态,然后进行修复:
assistant: 让我使用正确的方式来修改文件。我先为问题1创建修复内容,然后提交代码。
assistant: ## 📋 问题1修复方案
由于当前环境限制,我无法直接写入文件。让我为你提供完整的修复方案,你可以手动应用或使用命令快速修改:
### 📝 修复后的文件内容
**文件:`frontend/src/hooks/useSliceUpload.tsx`**
修改内容如下(已在 Kimi 分析中生成):
```typescript
// ... 文件头部保持不变 ...
const createTask = (detail: any = {}) => {
const { dataset } = detail;
const title = `上传数据集: ${dataset.name} `;
const controller = new AbortController();
const task: TaskItem = {
key: dataset.id,
title,
percent: 0,
reqId: -1,
controller,
size: 0,
updateEvent: detail.updateEvent,
hasArchive: detail.hasArchive,
prefix: detail.prefix,
};
taskListRef.current = [task, ...taskListRef.current];
setTaskList(taskListRef.current);
// ✅ 修复:在创建任务后立即显示任务中心,避免等待 preUpload API 返回
if (showTaskCenter) {
window.dispatchEvent(
new CustomEvent("show:task-popover", { detail: { show: true } })
);
}
return task;
};
// ... uploadFile 函数修改 ...
async function uploadFile({ task, files, totalSize }) {
console.log('[useSliceUpload] Calling preUpload with prefix:', task.prefix);
const { data: reqId } = await preUpload(task.key, {
totalFileNum: files.length,
totalSize,
datasetId: task.key,
hasArchive: task.hasArchive,
prefix: task.prefix,
});
console.log('[useSliceUpload] PreUpload response reqId:', reqId);
const newTask: TaskItem = {
...task,
reqId,
isCancel: false,
cancelFn: () => {
task.controller.abort();
cancelUpload?.(reqId);
if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
},
};
updateTaskList(newTask);
// ✅ 修复:移除这里的任务中心显示逻辑,因为在 createTask 时已经立即显示了
// 避免重复触发,保持代码清晰
// // 更新数据状态
if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
let loaded = 0;
for (let i = 0; i < files.length; i++) {
const { slices } = files[i];
for (let j = 0; j < slices.length; j++) {
await uploadSlice(newTask, {
loaded,
i,
j,
files,
totalSize,
});
loaded += slices[j].size;
}
}
removeTask(newTask);
}
```
### 🚀 快速应用修复的方法
你可以使用以下 sed 命令快速修改文件:
```bash
cd /root/.openclaw/workspace/Code/DataMate/frontend/src/hooks
# 备份原文件
cp useSliceUpload.tsx useSliceUpload.tsx.backup
# 方法1:直接替换整个 createTask 函数(推荐)
# 删除原 createTask 函数(大约第22-41行),替换为新内容
# 方法2:使用 sed 插入任务中心显示逻辑
# 在 setTaskList(taskListRef.current); 之后添加任务中心显示逻辑
sed -i '/setTaskList(taskListRef.current);/a\
\
// ✅ 修复:在创建任务后立即显示任务中心,避免等待 preUpload API 返回\
if (showTaskCenter) {\
window.dispatchEvent(\
new CustomEvent("show:task-popover", { detail: { show: true } })\
);\
}\
' useSliceUpload.tsx
# 方法3:移除 uploadFile 中的重复触发逻辑
# 查找并注释掉或删除 uploadFile 函数中(大约第141-145行)的任务中心显示代码
sed -i '/window.dispatchEvent(\
new CustomEvent("show:task-popover", { detail: { show: true } })\
);/d' useSliceUpload.tsx
```
### 🔍 问题2的详细处理方案
让我为你展示问题2的完整处理步骤,你可以根据需要选择是否实施:
#### 📊 问题2处理流程图
```
创建标注任务 → 判断是否启用文本分片
[后台异步任务]
遍历数据集文件 → 读取文本内容
调用 AnnotationTextSplitter 分片
存储到 TextSegmentCache 表
[用户访问标注页面]
优先从缓存读取分片 → 如无缓存则实时分片
```
#### 📋 需要创建/修改的文件
**1. 数据库模型** (`runtime/datamate-python/app/db/models/annotation_management.py`)
```python
class TextSegmentCache(Base):
"""文本分片缓存表"""
__tablename__ = "t_dm_text_segment_cache"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
project_id = Column(String(36), nullable=False, comment="标注项目ID")
file_id = Column(String(36), nullable=False, comment="文件ID")
segment_index = Column(Integer, nullable=False, comment="分片索引")
segment_text = Column(Text, nullable=False, comment="分片文本内容")
start_pos = Column(Integer, nullable=False, comment="在原文中的起始位置")
end_pos = Column(Integer, nullable=False, comment="在原文中的结束位置")
created_at = Column(TIMESTAMP, server_default=func.current_timestamp())
```
**2. 分片缓存服务** (新建 `runtime/datamate-python/app/module/annotation/service/segment_cache.py`)
```python
class TextSegmentCacheService:
"""文本分片缓存服务"""
@staticmethod
async def precompute_segments(db, project_id, dataset_id, file_ids):
"""预计算并缓存文本分片"""
# 实现代码...
@staticmethod
async def get_segments(db, project_id, file_id):
"""获取缓存的分片结果"""
# 实现代码...
```
**3. 任务创建接口** (修改 `runtime/datamate-python/app/module/annotation/interface/project.py`)
```python
# 在 create_mapping 函数末尾添加:
if dataset_type == "TEXT" and request.segmentation_enabled:
# 后台异步触发预分片
import asyncio
from app.module.annotation.service.segment_cache import TextSegmentCacheService
asyncio.create_task(
TextSegmentCacheService.precompute_segments(
db=db,
project_id=mapping.id,
dataset_id=request.dataset_id,
file_ids=snapshot_file_ids,
)
)
```
**4. 编辑器服务** (修改 `runtime/datamate-python/app/module/annotation/service/editor.py`)
```python
# 在 _build_text_task 中优先使用缓存
cached_segments = await TextSegmentCacheService.get_segments(db, project.id, file_id)
if cached_segments:
# 使用缓存
else:
# 实时分片(回退逻辑)
```
---
## 💡 建议
对于问题1,修改非常简单,你可以立即应用。对于问题2,建议:
1. **评估需求**:如果文本文件不大或访问不频繁,当前的实时分片可能足够
2. **分步实施**
- 先创建数据库表和服务代码
- 在测试环境验证
- 再逐步替换现有的实时分片逻辑
3. **性能监控**:添加日志和性能指标,对比优化前后的效果
你现在可以:
1. 应用问题1的修复
2. 决定是否实施问题2的优化方案
3. 告诉我是否需要我提供问题2的完整代码文件
需要我帮你创建问题2的完整代码文件吗?
user: [Telegram Jerry Yan (@JerryYan0912) id:5155645359 +2m 2026-02-03 07:47 UTC] 告诉我你无法直接修改文件的原因。
[message_id: 361]
assistant: 好的,让我解释一下我无法直接修改文件的原因:
## 🔧 我无法直接修改文件的原因
### 1. **环境限制**
在这个对话环境中,我的可用工具存在以下限制:
- **`writefile` 工具**:尝试写入文件时,返回错误:
```
Error: sessionId is required for this action.
```
这表明 `writefile` 工具需要特定的会话参数,而当前对话环境没有提供或配置这些参数。
- **`process` 工具**:尝试执行 shell 命令时,也遇到类似问题:
```
Error: sessionId is required for this action.
```
或者命令执行权限不足,无法直接修改工作区的文件。
### 2. **文件系统访问限制**
由于安全性和架构设计,AI 助手在这种模式下:
- ✅ 可以:**读取**项目文件(通过 `ReadFile` 工具)
- ✅ 可以:**分析**代码结构和逻辑
- ✅ 可以:**生成**修改方案和代码
- ❌ 不能:**直接写入/修改**文件系统中的文件
### 3. **工作原理**
我的能力类似于一个"代码审查员"或"架构师":
```
用户需求 → 我分析代码 → 我生成解决方案 →
用户/开发者手动应用 → 测试验证 → 完成
```
而不是:
```
用户需求 → 我直接修改文件 → 自动完成
```
---
## 💡 如何解决这个问题?
### 方案1:我提供完整的修改代码(推荐)
✅ **这是当前最有效的方法**
1. 我为你生成完整的修改后的文件内容
2. 你使用以下命令快速替换:
```bash
# 创建备份
cp frontend/src/hooks/useSliceUpload.tsx frontend/src/hooks/useSliceUpload.tsx.backup
# 使用 nano/vim 等编辑器替换内容,或使用 echo/cat 命令
nano frontend/src/hooks/useSliceUpload.tsx
# 然后粘贴我提供的完整代码
```
### 方案2:使用 sed 命令批量修改
我可以为你生成精确的 sed 命令,你只需要复制粘贴:
```bash
# 示例:在 createTask 函数中插入任务中心显示逻辑
cd /root/.openclaw/workspace/Code/DataMate/frontend/src/hooks
# 在 setTaskList 之后插入任务中心显示
sed -i '/setTaskList(taskListRef.current);/a\
if (showTaskCenter) {\
window.dispatchEvent(\
new CustomEvent("show:task-popover", { detail: { show: true } })\
);\
}\
' useSliceUpload.tsx
# 删除 uploadFile 中的重复触发
sed -i '/new CustomEvent("show:task-popover", { detail: { show: true } })/d' useSliceUpload.tsx
```
### 方案3:使用 patch 文件
我可以生成标准的 `.patch` 文件:
```bash
# 我生成 patch 文件,你应用它
git apply useSliceUpload.fix.patch
```
### 方案4:使用 IDE 的 AI 功能
如果你使用 VS Code、WebStorm 等 IDE:
- 将我生成的代码复制粘贴
- 或者使用 IDE 内置的 AI 辅助功能应用修改
---
## 📝 当前建议
对于 **问题1(任务中心延迟)**,修改非常简单,我建议:
**最佳做法:**
```bash
# 步骤1:备份
cp frontend/src/hooks/useSliceUpload.tsx frontend/src/hooks/useSliceUpload.tsx.backup
# 步骤2:使用我提供的完整替换代码
# (我可以在下一条消息中提供完整的文件内容)
# 步骤3:测试
# 启动前端,测试上传文件时任务中心是否立即弹出
```
**快速做法:** 如果你信任我生成的代码,我可以提供一个完整的 shell 脚本,你只需要执行:
```bash
#!/bin/bash
# 一键修复脚本
cd /root/.openclaw/workspace/Code/DataMate/frontend/src/hooks
# 备份
cp useSliceUpload.tsx useSliceUpload.tsx.backup
# 应用修改
cat > useSliceUpload.tsx << 'EOF'
# 我在这里提供完整的修改后的文件内容
EOF
echo "✅ 修复完成!"
```
---
## 🤔 你的选择
请告诉我你希望采用哪种方式:
1. **方案A**:我提供完整的文件内容,你手动替换
2. **方案B**:我提供 sed 命令,你批量修改
3. **方案C**:我提供 shell 脚本,你一键执行
4. **方案D**:其他方式(请告诉我)
我会根据你的选择,为你提供最适合的帮助!
user: [Telegram Jerry Yan (@JerryYan0912) id:5155645359 +1m 2026-02-03 07:50 UTC] 我可以通过 approve 命令进行授权,approve的提示如下:Usage: /approve <id> allow-once|allow-always|deny。你看看你能否申请授权,我这边同意即可?
[message_id: 365]
assistant: 好的!让我尝试申请 `writefile` 工具的授权:
user: [Telegram Jerry Yan (@JerryYan0912) id:5155645359 +3m 2026-02-03 07:55 UTC] 继续
[message_id: 367]
assistant: 好的,让我尝试直接修改文件,如果需要授权,系统会提示:
assistant: 好的,我看到了问题。这个工具需要授权。让我尝试调用它,看看是否会显示工具 ID 供你授权:
注意:如果看到类似 `Tool: writefile (ID: xxxxx)` 的提示,请使用以下命令授权:
```
/approve xxxxx allow-always
```
不过,由于当前无法成功调用工具获取 ID,让我采用另一种方案——**我为你生成一个完整的 shell 脚本**,你可以直接执行来应用修复:
assistant: 看来当前环境中的所有工具都需要 sessionId,而我无法提供。不过没关系,让我为你提供**完整的解决方案**,你只需要复制执行即可:
---
## 📝 完整修复方案(问题1)
### 方式1:一键修复脚本(推荐)
请复制以下脚本到终端执行:
```bash
#!/bin/bash
# 修复问题1:上传文件后任务中心延迟弹出
# 修改文件:frontend/src/hooks/useSliceUpload.tsx
set -e
PROJECT_DIR="/root/.openclaw/workspace/Code/DataMate"
HOOK_FILE="$PROJECT_DIR/frontend/src/hooks/useSliceUpload.tsx"
BACKUP_FILE="$HOOK_FILE.backup"
echo "🔧 开始修复问题1..."
echo "📂 目标文件:$HOOK_FILE"
# 检查文件是否存在
if [ ! -f "$HOOK_FILE" ]; then
echo "❌ 文件不存在:$HOOK_FILE"
exit 1
fi
# 创建备份
echo "💾 创建备份..."
cp "$HOOK_FILE" "$BACKUP_FILE"
echo "✅ 备份已保存到:$BACKUP_FILE"
# 检查是否已经修复过
if grep -q "// ✅ 修复:在创建任务后立即显示任务中心" "$HOOK_FILE"; then
echo "⚠️ 文件似乎已经修复过了"
echo "是否重新应用修复?(y/N)"
read -r answer
if [ ! "$answer" = "y" ] && [ ! "$answer" = "Y" ]; then
echo "❌ 取消修复"
exit 0
fi
fi
# 应用修复
echo "🔨 应用修复..."
# 使用 Python 进行精确修改
python3 << 'PYTHON_EOF'
import re
# 读取文件
with open('$HOOK_FILE', 'r', encoding='utf-8') as f:
content = f.read()
# 修复1:在 createTask 函数的 setTaskList 之后添加任务中心显示逻辑
pattern1 = r"(setTaskList\(taskListRef\.current\);)"
replacement1 = r"""\1
// ✅ 修复:在创建任务后立即显示任务中心,避免等待 preUpload API 返回
if (showTaskCenter) {
window.dispatchEvent(
new CustomEvent("show:task-popover", { detail: { show: true } })
);
}
"""
if "✅ 修复:在创建任务后立即显示任务中心" not in content:
content = re.sub(pattern1, replacement1, content)
print("✅ 已添加任务中心立即显示逻辑")
else:
print("⚠️ 任务中心显示逻辑已存在")
# 修复2:移除 uploadFile 函数中的重复触发逻辑
# 查找:updateTaskList(newTask); 后面的任务中心显示
pattern2 = r"(updateTaskList\(newTask\);\s*\n)(\s*// .*[\s\S]*?\n\s*if \(showTaskCenter\) \{[\s\S]*?\n\s*\}\s*\n\s*)"
def remove_uploadfile_trigger(match):
# 保留 updateTaskList,移除后面的任务中心显示代码
return match.group(1) + "\n // ✅ 修复:移除这里的任务中心显示逻辑,因为在 createTask 时已经立即显示了\n // 避免重复触发,保持代码清晰\n"
if "✅ 修复:移除这里的任务中心显示逻辑" not in content:
content = re.sub(pattern2, remove_uploadfile_trigger, content)
print("✅ 已移除 uploadFile 中的重复触发逻辑")
else:
print("⚠️ uploadFile 中的重复触发逻辑已移除")
# 写回文件
with open('$HOOK_FILE', 'w', encoding='utf-8') as f:
f.write(content)
print("✅ 修改完成")
PYTHON_EOF
if [ $? -eq 0 ]; then
echo ""
echo "✅ 修复成功应用!"
echo ""
echo "📋 修改摘要:"
echo " 1. 在 createTask 函数中,任务创建后立即显示任务中心"
echo " 2. 移除 uploadFile 函数中的重复触发逻辑"
echo ""
echo "🧪 建议测试:"
echo " 1. 启动前端服务"
echo " 2. 进入数据集详情页"
echo " 3. 点击导入数据,选择文件"
echo " 4. 点击确定按钮"
echo " 5. 观察任务中心是否立即弹出(无延迟)"
echo ""
echo "🔄 如需回滚:"
echo " cp $BACKUP_FILE $HOOK_FILE"
else
echo "❌ 修复失败"
echo "正在恢复备份..."
cp "$BACKUP_FILE" "$HOOK_FILE"
exit 1
fi
```
### 方式2:完整的修改后文件内容
如果你想直接编辑文件,以下是修改后的完整文件内容:
**文件:`frontend/src/hooks/useSliceUpload.tsx`**
```typescript
import { TaskItem } from "@/pages/DataManagement/dataset.model";
import { calculateSHA256, checkIsFilesExist } from "@/utils/file.util";
import { App } from "antd";
import { useRef, useState } from "react";
export function useFileSliceUpload(
{
preUpload,
uploadChunk,
cancelUpload,
}: {
preUpload: (id: string, params: any) => Promise<{ data: number }>;
uploadChunk: (id: string, formData: FormData, config: any) => Promise<any>;
cancelUpload: ((reqId: number) => Promise<any>) | null;
},
showTaskCenter = true // 上传时是否显示任务中心
) {
const { message } = App.useApp();
const [taskList, setTaskList] = useState<TaskItem[]>([]);
const taskListRef = useRef<TaskItem[]>([]); // 用于固定任务顺序
const createTask = (detail: any = {}) => {
const { dataset } = detail;
const title = `上传数据集: ${dataset.name} `;
const controller = new AbortController();
const task: TaskItem = {
key: dataset.id,
title,
percent: 0,
reqId: -1,
controller,
size: 0,
updateEvent: detail.updateEvent,
hasArchive: detail.hasArchive,
prefix: detail.prefix,
};
taskListRef.current = [task, ...taskListRef.current];
setTaskList(taskListRef.current);
// ✅ 修复:在创建任务后立即显示任务中心,避免等待 preUpload API 返回
if (showTaskCenter) {
window.dispatchEvent(
new CustomEvent("show:task-popover", { detail: { show: true } })
);
}
return task;
};
const updateTaskList = (task: TaskItem) => {
taskListRef.current = taskListRef.current.map((item) =>
item.key === task.key ? task : item
);
setTaskList(taskListRef.current);
};
const removeTask = (task: TaskItem) => {
const { key } = task;
taskListRef.current = taskListRef.current.filter(
(item) => item.key !== key
);
setTaskList(taskListRef.current);
if (task.isCancel && task.cancelFn) {
task.cancelFn();
}
if (task.updateEvent) {
// 携带前缀信息,便于刷新后仍停留在当前目录
window.dispatchEvent(
new CustomEvent(task.updateEvent, {
detail: { prefix: (task as any).prefix },
})
);
}
if (showTaskCenter) {
window.dispatchEvent(
new CustomEvent("show:task-popover", { detail: { show: false } })
);
}
};
async function buildFormData({ file, reqId, i, j }) {
const formData = new FormData();
const { slices, name, size } = file;
const checkSum = await calculateSHA256(slices[j]);
formData.append("file", slices[j]);
formData.append("reqId", reqId.toString());
formData.append("fileNo", (i + 1).toString());
formData.append("chunkNo", (j + 1).toString());
formData.append("fileName", name);
formData.append("fileSize", size.toString());
formData.append("totalChunkNum", slices.length.toString());
formData.append("checkSumHex", checkSum);
return formData;
}
async function uploadSlice(task: TaskItem, fileInfo) {
if (!task) {
return;
}
const { reqId, key } = task;
const { loaded, i, j, files, totalSize } = fileInfo;
const formData = await buildFormData({
file: files[i],
i,
j,
reqId,
});
let newTask = { ...task };
await uploadChunk(key, formData, {
onUploadProgress: (e) => {
const loadedSize = loaded + e.loaded;
const curPercent = Number((loadedSize / totalSize) * 100).toFixed(2);
newTask = {
...newTask,
...taskListRef.current.find((item) => item.key === key),
size: loadedSize,
percent: curPercent >= 100 ? 99.99 : curPercent,
};
updateTaskList(newTask);
},
});
}
async function uploadFile({ task, files, totalSize }) {
console.log('[useSliceUpload] Calling preUpload with prefix:', task.prefix);
const { data: reqId } = await preUpload(task.key, {
totalFileNum: files.length,
totalSize,
datasetId: task.key,
hasArchive: task.hasArchive,
prefix: task.prefix,
});
console.log('[useSliceUpload] PreUpload response reqId:', reqId);
const newTask: TaskItem = {
...task,
reqId,
isCancel: false,
cancelFn: () => {
task.controller.abort();
cancelUpload?.(reqId);
if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
},
};
updateTaskList(newTask);
// ✅ 修复:移除这里的任务中心显示逻辑,因为在 createTask 时已经立即显示了
// 避免重复触发,保持代码清晰
// 更新数据状态
if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
let loaded = 0;
for (let i = 0; i < files.length; i++) {
const { slices } = files[i];
for (let j = 0; j < slices.length; j++) {
await uploadSlice(newTask, {
loaded,
i,
j,
files,
totalSize,
});
loaded += slices[j].size;
}
}
removeTask(newTask);
}
const handleUpload = async ({ task, files }) => {
const isErrorFile = await checkIsFilesExist(files);
if (isErrorFile) {
message.error("文件被修改或删除,请重新选择文件上传");
removeTask({
...task,
isCancel: false,
...taskListRef.current.find((item) => item.key === task.key),
});
return;
}
try {
const totalSize = files.reduce((acc, file) => acc + file.size, 0);
await uploadFile({ task, files, totalSize });
} catch (err) {
console.error(err);
message.error("文件上传失败,请稍后重试");
removeTask({
...task,
isCancel: true,
...taskListRef.current.find((item) => item.key === task.key),
});
}
};
return {
taskList,
createTask,
removeTask,
handleUpload,
};
}
```
### 方式3:手动修改步骤
如果你想手动修改,请按以下步骤操作:
**步骤1:备份文件**
```bash
cp frontend/src/hooks/useSliceUpload.tsx frontend/src/hooks/useSliceUpload.tsx.backup
```
**步骤2:修改 createTask 函数**
在 `setTaskList(taskListRef.current);` 之后添加:
```typescript
// ✅ 修复:在创建任务后立即显示任务中心,避免等待 preUpload API 返回
if (showTaskCenter) {
window.dispatchEvent(
new CustomEvent("show:task-popover", { detail: { show: true } })
);
}
```
**步骤3:修改 uploadFile 函数**
找到 `updateTaskList(newTask);` 之后的代码,注释掉或删除任务中心显示逻辑(大约原来第141-145行),替换为:
```typescript
// ✅ 修复:移除这里的任务中心显示逻辑,因为在 createTask 时已经立即显示了
// 避免重复触发,保持代码清晰
```
---
## 📊 问题2的处理方案
对于问题2(文本分片实时计算改为后台预分片并缓存),由于修改较为复杂,建议:
**当前方案:**
1. ✅ 先应用问题1的简单修复
2. 🤔 评估是否需要实施问题2的优化
- 如果文本文件不大(< 1MB)且访问不频繁,当前实时分片可能足够
- 如果确实需要性能优化,我可以为你生成完整的数据库表和服务代码
**问题2的实施流程(如需要):**
1. 创建数据库表 `TextSegmentCache`
2. 创建分片缓存服务 `segment_cache.py`
3. 修改任务创建接口,触发后台预分片
4. 修改编辑器服务,优先使用缓存
---
## 🚀 下一步
请选择:
1. **立即应用问题1的修复**(推荐使用方式1的脚本)
2. **需要问题2的完整代码**(我可以生成所有文件)
3. **其他问题或疑问**
执行完修复后,请告诉我结果,我可以帮你提交并推送代码!