fix(voices): 配音素材上传卡「处理中…」修复 #1552

Merged
auto-approve-bot merged 2 commits from fix/voice-upload-polling-dedup into develop 2026-08-30 11:46:05 +08:00
3 changed files with 43 additions and 12 deletions
+4
View File
@@ -140,4 +140,8 @@ export interface DirectUploadCompleteResult {
storage_key: string
ingest_job_id: string
url: string
/** 同库已存在相同 file_hash 的素材时为 trueingest_job_id 为空 */
duplicated?: boolean
/** duplicated 为 true 时返回已存在素材的 id */
asset_id?: string
}
@@ -48,20 +48,32 @@ export function useVoiceUpload({ voiceLibrary, createLibMutation }: UseVoiceUplo
}
// 2. 上传文件(带进度,后端自动创建 ingest job)
const { ingest_job_id } = await uploadAssetDirect({
const complete = await uploadAssetDirect({
file: data.file,
library_id: lib.id,
onProgress: (p) => setUploadProgress(p),
})
// 3. 轮询 ingest job 状态
// 去重命中(同库已存在相同 file_hash 素材):
// 后端返回 duplicated=trueingest_job_id 为空;跳过轮询和打标签,
// mutationFn 正常 return 即 resolveuseMutation 自动触发 onSuccess 刷新列表。
// 已存在素材复用上次标签,无需重新打标。
if (complete.duplicated === true) {
return
}
if (!complete.ingest_job_id) {
throw new Error("上传完成但未返回处理任务 ID,请重试")
}
const { ingest_job_id } = complete
// 3. 轮询 ingest job 状态(complete 后先立即查一次,未完成再每 5s 轮询)
let job: Awaited<ReturnType<typeof getIngestJob>> | null = null
let retries = 0
const maxRetries = 60 // 最多等待 5 分钟
while (retries < maxRetries) {
job = await getIngestJob(ingest_job_id)
while (job.status !== "completed" && job.status !== "failed" && retries < maxRetries) {
await new Promise((r) => setTimeout(r, 5000))
job = await getIngestJob(ingest_job_id)
if (job.status === "completed" || job.status === "failed") break
retries++
}
@@ -72,7 +84,7 @@ export function useVoiceUpload({ voiceLibrary, createLibMutation }: UseVoiceUplo
throw new Error("音频处理超时,请稍后在素材库查看")
}
// 4. 打标签(标签走独立 API)
// 4. 打标签(标签走独立 API;去重命中时已提前 return,这里只对新创建的素材执行
if (data.tagIds.length > 0 && job.result_asset_id) {
await tagAsset(job.result_asset_id, data.tagIds)
}
@@ -32,24 +32,39 @@ export function useVoiceUpload({ showToast }: UseVoiceUploadProps) {
if (!lib) throw new Error("配音库不存在,请先在配音库页面创建")
/* 直传文件(后端会自动创建 ingest job */
const { ingest_job_id } = await uploadAssetDirect({
const complete = await uploadAssetDirect({
file: data.file,
library_id: lib.id,
onProgress: (p) => setUploadProgress(p),
})
/* 轮询 ingest job 状态,等待 Worker 处理完成 */
let jobStatus = ""
/* 去重命中(同库已存在相同 file_hash 素材):
* 后端返回 duplicated=trueingest_job_id 为空,
* 不轮询、直接按上传成功处理(onSuccess 分支 toast + 刷新列表)。
* 注意:mutationFn 正常 return 即视为 resolveuseMutation 会自动调 onSuccess。
*/
if (complete.duplicated === true) {
return
}
if (!complete.ingest_job_id) {
throw new Error("上传完成但未返回处理任务 ID,请重试")
}
const { ingest_job_id } = complete
/* 轮询 ingest job 状态,等待 Worker 处理完成;
* complete 后先立即查一次(后端通常不到 1s 处理完),未完成再每 5s 轮询。
* 成功状态为 IngestJobStatus.completed"ready" 是 voice-clones 的状态,此处误用需避免。
*/
let job = await getIngestJob(ingest_job_id)
let retries = 0
const maxRetries = 60 // 最多等待 5 分钟(60 * 5秒)
while (jobStatus !== "ready" && jobStatus !== "failed" && retries < maxRetries) {
while (job.status !== "completed" && job.status !== "failed" && retries < maxRetries) {
await new Promise((r) => setTimeout(r, 5000))
const job = await getIngestJob(ingest_job_id)
jobStatus = job.status
job = await getIngestJob(ingest_job_id)
retries++
}
if (jobStatus === "failed") {
if (job.status === "failed") {
throw new Error("音频处理失败,请重试")
}
if (retries >= maxRetries) {