/** * useCloneProgress — 克隆进度轮询 Hook * * 当存在 processing 状态的克隆条目时,每 3 秒自动拉取最新列表; * 全部完成(ready / failed)后停止轮询。 */ import { useState, useEffect, useCallback, useRef } from "react" import { getVoiceClones } from "@/api/voice-clone" import type { VoiceClone } from "@/api/voice-clone" const POLL_INTERVAL = 3000 // 3 秒 export const useCloneProgress = () => { const [clones, setClones] = useState([]) const [loading, setLoading] = useState(false) const timerRef = useRef>(undefined) const fetchClones = useCallback(async (silent = false) => { if (!silent) setLoading(true) try { const data = await getVoiceClones() setClones(data) } catch { // 静默失败,下次轮询重试 } finally { if (!silent) setLoading(false) } }, []) /* 初始加载 */ useEffect(() => { fetchClones() }, [fetchClones]) /* 轮询:有 processing 条目时启动,全部结束时停止 */ useEffect(() => { const hasProcessing = clones.some((c) => c.status === "processing") if (hasProcessing) { if (!timerRef.current) { timerRef.current = setInterval(() => { fetchClones(true) }, POLL_INTERVAL) } } else { if (timerRef.current) { clearInterval(timerRef.current) timerRef.current = undefined } } return () => { if (timerRef.current) { clearInterval(timerRef.current) timerRef.current = undefined } } }, [clones, fetchClones]) /** 手动刷新 */ const refresh = useCallback(() => fetchClones(), [fetchClones]) /** 克隆成功后追加到列表 */ const addClone = useCallback((voice: VoiceClone) => { setClones((prev) => [voice, ...prev]) }, []) /** 删除后从列表移除 */ const removeClone = useCallback((id: string) => { setClones((prev) => prev.filter((c) => c.id !== id)) }, []) /** 更新某条克隆(如改名) */ const updateClone = useCallback((updated: VoiceClone) => { setClones((prev) => prev.map((c) => (c.id === updated.id ? updated : c))) }, []) return { clones, loading, refresh, addClone, removeClone, updateClone, /** 是否有正在处理中的克隆 */ hasProcessing: clones.some((c) => c.status === "processing"), } }