3862996045
CI/CD Pipeline / Check if frontend-only change (push) Waiting to run
CI/CD Pipeline / Validate - Code Quality (push) Waiting to run
CI/CD Pipeline / Validate - Type Check (mypy) (push) Waiting to run
CI/CD Pipeline / Validate - Migration (alembic) (push) Waiting to run
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Waiting to run
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Waiting to run
CI/CD Pipeline / PR Build Web Image (push) Waiting to run
CI/CD Pipeline / PR Build Worker Image (push) Waiting to run
CI/CD Pipeline / Build Staging API Image (push) Waiting to run
CI/CD Pipeline / Build Staging Web Image (push) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (push) Waiting to run
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Waiting to run
CI/CD Pipeline / Build Production Web Image (push) Waiting to run
CI/CD Pipeline / Build Production Worker Image (push) Waiting to run
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
/**
|
|
* 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<VoiceClone[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const timerRef = useRef<ReturnType<typeof setInterval>>(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"),
|
|
}
|
|
}
|