/** * useCloneProgress — 克隆进度轮询 Hook * * 当存在 processing 状态的克隆条目时,每 3 秒自动拉取最新列表; * 全部完成(ready / failed)后停止轮询。 */ import { useState, useEffect, useCallback, useRef } from "react"; import { getVoiceClones } from "@/api/voiceClone"; import type { VoiceClone } from "@/api/voiceClone"; 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"), }; };