Browse Source

fix(de-ai): 修复去AI味任务提前消失和审查弹窗未弹出

1. 提取中区域显示所有活跃任务(processing/ready/failed),不只是processing
2. 不同状态显示不同图标和文字(处理中/待确认/失败)
3. 审查弹窗渲染条件改为 readyTasks.length===0 && !deAiReviewOpen
4. streamChat 添加兜底:正常返回但未调用 onDone/onError 时用 result 完成
Mochocyang 3 months ago
parent
commit
6c3f67e193
2 changed files with 34 additions and 8 deletions
  1. 19 6
      src/components/layout/knowledge-tree.tsx
  2. 15 2
      src/components/layout/preview-panel.tsx

+ 19 - 6
src/components/layout/knowledge-tree.tsx

@@ -1985,7 +1985,10 @@ export function RawSourcesSection({ onCancelExtraction }: { onCancelExtraction?:
     if (!project) return []
     const projectPath = normalizePath(project.path)
     return deAiTasks
-      .filter((t) => t.projectPath === projectPath && t.status === "processing")
+      .filter((t) =>
+        t.projectPath === projectPath
+        && (t.status === "processing" || t.status === "ready" || t.status === "failed")
+      )
       .sort((a, b) => b.createdAt - a.createdAt)
   }, [project, deAiTasks])
 
@@ -2090,14 +2093,24 @@ export function RawSourcesSection({ onCancelExtraction }: { onCancelExtraction?:
             {projectDeAiTasks.map((task) => (
               <div key={task.id} className="space-y-1 rounded-md bg-muted/30 px-2 py-1.5">
                 <div className="flex items-center gap-1">
-                  <Loader2 className="h-3 w-3 shrink-0 animate-spin text-primary" />
-                  <span className="truncate text-foreground font-medium">
+                  {task.status === "processing" ? (
+                    <Loader2 className="h-3 w-3 shrink-0 animate-spin text-primary" />
+                  ) : task.status === "ready" ? (
+                    <Check className="h-3 w-3 shrink-0 text-emerald-500" />
+                  ) : (
+                    <X className="h-3 w-3 shrink-0 text-destructive" />
+                  )}
+                  <span className={`truncate ${task.status === "processing" ? "text-foreground font-medium" : ""}`}>
                     去AI味:{task.chapterTitle}
+                    {task.status === "ready" ? " · 待确认" : ""}
+                    {task.status === "failed" ? ` · 失败:${task.error ?? ""}` : ""}
                   </span>
                 </div>
-                <div className="text-muted-foreground">
-                  Skill:{task.skillName} · 模型:{task.modelName}
-                </div>
+                {task.status === "processing" && (
+                  <div className="text-muted-foreground">
+                    Skill:{task.skillName} · 模型:{task.modelName}
+                  </div>
+                )}
               </div>
             ))}
             </>

+ 15 - 2
src/components/layout/preview-panel.tsx

@@ -1035,6 +1035,7 @@ export function PreviewPanel() {
       sourceContent: source,
     })
     let result = ""
+    let doneCalled = false
     try {
       await streamChat(
         llmConfig,
@@ -1044,17 +1045,29 @@ export function PreviewPanel() {
             result += token
           },
           onDone: () => {
+            doneCalled = true
             useDeAiTaskStore.getState().finishTask(taskId, result)
           },
           onError: (error) => {
+            doneCalled = true
             console.error("去AI味处理失败:", error)
             useDeAiTaskStore.getState().failTask(taskId, error.message ?? String(error))
           },
         },
       )
+      // 兜底:streamChat 正常返回但未调用 onDone/onError 时,用 result 完成
+      if (!doneCalled) {
+        if (result.trim()) {
+          useDeAiTaskStore.getState().finishTask(taskId, result)
+        } else {
+          useDeAiTaskStore.getState().failTask(taskId, "去AI味未返回内容")
+        }
+      }
     } catch (err) {
       console.error("去AI味处理失败:", err)
-      useDeAiTaskStore.getState().failTask(taskId, String(err))
+      if (!doneCalled) {
+        useDeAiTaskStore.getState().failTask(taskId, String(err))
+      }
     }
   }, [syncDiskBeforeAction, selectedFile, project, chapterHeader, chapterDeAiOptions.currentSkillId])
 
@@ -1666,7 +1679,7 @@ export function PreviewPanel() {
         const readyTasks = deAiTasks.filter(
           (t) => t.status === "ready" || t.status === "confirmed" || t.status === "cancelled"
         )
-        if (readyTasks.length === 0) return null
+        if (readyTasks.length === 0 && !deAiReviewOpen) return null
         const now = Date.now()
         const reviewChapters = readyTasks.map((t, index) => ({
           version: 1 as const,