fix(voices): 配音素材上传卡「处理中…」修复 #1552
@@ -140,4 +140,8 @@ export interface DirectUploadCompleteResult {
|
||||
storage_key: string
|
||||
ingest_job_id: string
|
||||
url: string
|
||||
/** 同库已存在相同 file_hash 的素材时为 true,ingest_job_id 为空 */
|
||||
duplicated?: boolean
|
||||
/** duplicated 为 true 时返回已存在素材的 id */
|
||||
asset_id?: string
|
||||
}
|
||||
|
||||
+17
-5
@@ -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=true,ingest_job_id 为空;跳过轮询和打标签,
|
||||
// mutationFn 正常 return 即 resolve,useMutation 自动触发 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=true,ingest_job_id 为空,
|
||||
* 不轮询、直接按上传成功处理(onSuccess 分支 toast + 刷新列表)。
|
||||
* 注意:mutationFn 正常 return 即视为 resolve,useMutation 会自动调 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) {
|
||||
|
||||
Reference in New Issue
Block a user