fix(voices): 配音素材上传卡「处理中…」修复
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4m1s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 4m5s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 4m30s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m45s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m51s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m54s
AI Code Review / AI Code Review (pull_request) Failing after 5m57s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 411h53m4s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 411h53m21s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 411h53m58s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 411h58m45s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 411h58m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 411h58m47s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 411h58m49s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 411h58m49s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 411h58m51s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 411h58m53s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 411h58m51s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 412h32m52s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 412h32m56s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 412h32m58s

- 轮询成功状态从 'ready' 改为 'completed'('ready' 是 voice-clones 状态,误用)
- 去重命中分支:complete 接口返回 duplicated=true 或 ingest_job_id 为空时,
  不轮询直接按上传成功处理(刷新列表、关闭弹窗)
- 轮询优化:complete 后立即查第一次(后端通常 <1s 处理完),未完成再每 5s 轮询
- 两个上传 hook 同步修改:
  - pages/voices/hooks/useVoiceUpload.ts(旧配音库页)
  - pages/voice-materials/hooks/useVoiceMaterials/actions/useVoiceUpload.ts
    (新配音库页;去重命中时跳过轮询和打标签)
- api/assets/types.ts 的 DirectUploadCompleteResult 补充 duplicated/asset_id 字段
This commit is contained in:
xiaoxia
2026-08-30 11:26:03 +08:00
parent 411595ed90
commit 166028094c
3 changed files with 34 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 的素材时为 true,ingest_job_id 为空 */
duplicated?: boolean
/** duplicated 为 true 时返回已存在素材的 id */
asset_id?: string
}
@@ -48,20 +48,27 @@ 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 为空;跳过轮询和打标签,直接走 onSuccess
if (complete.duplicated === true || !complete.ingest_job_id) {
return
}
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 +79,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,35 @@ 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 + 刷新列表)
*/
if (complete.duplicated === true || !complete.ingest_job_id) {
return
}
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) {