# 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 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; cancelUpload: ((reqId: number) => Promise) | null; }, showTaskCenter = true // 上传时是否显示任务中心 ) { const { message } = App.useApp(); const [taskList, setTaskList] = useState([]); const taskListRef = useRef([]); // 用于固定任务顺序 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. **其他问题或疑问** 执行完修复后,请告诉我结果,我可以帮你提交并推送代码!