mirror of
https://github.com/zhouxiaoka/autoclip.git
synced 2026-09-03 06:24:14 +08:00
fix: kill the "已开始重试处理项目" success-toast loop
The previous fix stopped the failure-toast spam but exposed a success-toast loop. handleRetryProject calls loadProjects, which sets loading=true and swaps the project list for HomePage's "正在加载项目列表" spinner — unmounting every ProjectCard. That reset the useRef auto-start guard, so on remount auto-start fired again → onRetry → handleRetryProject → loadProjects → unmount → ... an infinite loop, one "已开始重试处理项目" toast per cycle (and the flickering spinner in the report). Three cuts so the loop can't form: 1. Auto-start guard moved from useRef to a module-level Set<projectId>, which survives the remount. Project ids are unique per import → once per session. 2. Silent auto-start no longer calls onRetry — it must never drive the parent's toast/reload path. Only user-clicked retries notify the parent. 3. handleRetryProject no longer re-issues retryProcessing (the card already sent the request); it just toasts once and refreshes. Frontend typecheck clean; production build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Card, Tag, Button, Space, Typography, Popconfirm, message, Tooltip } from 'antd'
|
||||
import { PlayCircleOutlined, DeleteOutlined, DownloadOutlined, ReloadOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
@@ -49,6 +49,13 @@ if (typeof document !== 'undefined') {
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
// Tracks which project ids have already had a best-effort auto-start, surviving
|
||||
// component remounts (the list briefly unmounts while HomePage shows its
|
||||
// loading spinner). A useRef would reset on every remount and let auto-start
|
||||
// fire again, which created an infinite onRetry→loadProjects→remount loop.
|
||||
// Project ids are unique per import, so once-per-session is exactly right.
|
||||
const autoStartedProjectIds = new Set<string>()
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: Project
|
||||
onDelete: (id: string) => void
|
||||
@@ -244,20 +251,18 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project, onDelete, onRetry, o
|
||||
// 导致 effect 反复触发 → 对一个还没下载完的 B站项目疯狂 POST /process(返回
|
||||
// 400 "Video file not found")→ 满屏「重试失败」。下载完成后后端会自动启动
|
||||
// 流水线,所以这里只需做一次「尽力而为」的启动即可。
|
||||
const autoStartAttempted = useRef(false)
|
||||
useEffect(() => {
|
||||
autoStartAttempted.current = false
|
||||
}, [project.id])
|
||||
useEffect(() => {
|
||||
if (
|
||||
project.status === 'pending' &&
|
||||
!isDownloading &&
|
||||
!autoStartAttempted.current
|
||||
!autoStartedProjectIds.has(project.id)
|
||||
) {
|
||||
autoStartAttempted.current = true
|
||||
// Best-effort, one-shot. Uploads (file already present) start processing;
|
||||
// B站 imports whose download isn't done yet return 400 here — that's fine,
|
||||
// the backend auto-starts the pipeline when the download completes.
|
||||
autoStartedProjectIds.add(project.id)
|
||||
// Best-effort, one-shot per project. Uploads (file already present) start
|
||||
// processing; B站 imports whose download isn't done yet return 400 here —
|
||||
// that's fine, the backend auto-starts the pipeline when the download
|
||||
// completes. Silent + no onRetry so this never drives the parent's
|
||||
// toast/reload path.
|
||||
handleRetry({ silent: true })
|
||||
}
|
||||
}, [project.status, project.id, isDownloading])
|
||||
@@ -282,8 +287,10 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project, onDelete, onRetry, o
|
||||
} else {
|
||||
await projectApi.retryProcessing(project.id)
|
||||
}
|
||||
// 移除重复的toast显示,让父组件统一处理
|
||||
if (onRetry) {
|
||||
// 让父组件统一处理 toast / 刷新。但「静默自动启动」绝不能触发父组件,
|
||||
// 否则会走 handleRetryProject → loadProjects → 列表重挂载 → 再次自动启动
|
||||
// 的死循环。只有用户手动点重试才通知父组件。
|
||||
if (onRetry && !opts?.silent) {
|
||||
onRetry(project.id)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -89,23 +89,16 @@ const HomePage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleRetryProject = async (projectId: string) => {
|
||||
// 由 ProjectCard 在「用户手动点重试」且重试请求已成功后调用。
|
||||
// ProjectCard.handleRetry 已经发过 start/retryProcessing 请求,这里只负责
|
||||
// 提示 + 刷新列表,绝不能再发一次重试请求(会和卡片自身的请求叠加,并制造
|
||||
// loadProjects→重挂载→自动启动 的循环)。
|
||||
const handleRetryProject = async () => {
|
||||
message.success('已开始重试处理项目')
|
||||
try {
|
||||
// 查找项目状态
|
||||
const project = projects.find(p => p.id === projectId)
|
||||
if (!project) {
|
||||
message.error('项目不存在')
|
||||
return
|
||||
}
|
||||
|
||||
// 统一使用retryProcessing API,它会自动处理视频文件不存在的情况
|
||||
await projectApi.retryProcessing(projectId)
|
||||
message.success('已开始重试处理项目')
|
||||
|
||||
await loadProjects()
|
||||
} catch (error) {
|
||||
message.error('重试失败,请稍后再试')
|
||||
console.error('Retry project error:', error)
|
||||
console.error('Refresh after retry error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +358,7 @@ const HomePage: React.FC = () => {
|
||||
<ProjectCard
|
||||
project={project}
|
||||
onDelete={handleDeleteProject}
|
||||
onRetry={() => handleRetryProject(project.id)}
|
||||
onRetry={() => handleRetryProject()}
|
||||
onClick={() => handleProjectCardClick(project)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user