Compare commits

...

2 Commits

Author SHA1 Message Date
xiaoxia f04ddde4d5 fix: AI数字人克隆音色试听传 profile UUID 而非 voice_id
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m27s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m29s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m58s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m8s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m8s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m14s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m25s
AI Code Review / AI Code Review (pull_request) Successful in 6m23s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m12s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 8m5s
根因:后端 /tts/preview 用传入的 voice_id 去查 VoiceCloneProfile,
期望前端传 profile UUID(voice_clone_profile_id 或 clone.id)。
此前 clone 分支传了 voice_id(CosyVoice 模型 ID),
后端查不到 profile 静默返回,用户点播放无反应。

修复:clone 分支 targetId 改用 voice.voice_clone_profile_id || voice.id。
2026-09-09 10:55:27 +08:00
xiaoxia b716d3c34f fix: AI数字人「我的音色」克隆音色试听改用 TTS 合成
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 5s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 25s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m27s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m33s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m45s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m58s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m13s
CI/CD Pipeline / Validate - Security (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
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
配音库克隆音色能听到,数字人页面听不到,根因:
配音库 useAudioPlayer 的 playVoice() 是调 POST /tts/preview
现合成示例文案拿到 audio_url 再播放(clone 的 preview_url/
audio_url 都为空);数字人面板还停留在直接用 preview_url
直链播放,所以 clone 音色静默返回。

修复:
- 克隆音色走 previewTts(与配音库一致)+ 按 voiceId 内存缓存,
  同一音色二次试听不重复合成
- 系统预设音色保持原 preview_url/audio_url 直链播放,不改
- 抽取公共 playAudioUrl,保留原 onended/onerror/play().catch
  错误提示(含「该音色暂无试听音频」兜底)

不动:播放按钮 disabled/title 逻辑、音色列表布局、其他面板。
2026-09-09 10:14:09 +08:00
@@ -5,6 +5,7 @@
import { useEffect, useRef, useState } from "react"
import { message } from "antd"
import { fetchVoices } from "@/api/voices/voices"
import { previewTts } from "@/api/tts"
import type { UnifiedVoiceItem } from "@/api/voices/types"
import {
type VoiceSource,
@@ -44,6 +45,9 @@ export function PanelVoiceSelector({
const [error, setError] = useState<string | null>(null)
const [previewingId, setPreviewingId] = useState<string | null>(null)
const audioRef = useRef<HTMLAudioElement | null>(null)
/** 克隆音色试听合成缓存:voiceId -> url,对齐配音库 useAudioPlayer */
const previewCacheRef = useRef<Map<string, string>>(new Map())
const VOICE_PREVIEW_TEXT = "你好呀,欢迎使用小虾智剪,这是我的配音效果,希望你喜欢。"
/* 切换来源时重新获取音色列表 */
useEffect(() => {
@@ -86,24 +90,15 @@ export function PanelVoiceSelector({
const NO_PREVIEW_TIP = "该音色暂无试听音频,请先用此音色生成一段配音后再试听"
const handlePreview = (voice: UnifiedVoiceItem) => {
const url = voice.preview_url || voice.audio_url
if (!url) {
message.warning(NO_PREVIEW_TIP)
return
}
/* 再次点击当前试听音色 → 停止 */
if (previewingId === voice.id) {
stopPreview()
return
}
/** 用指定 URL 真实播放(抽取公共) */
const playAudioUrl = (voiceId: string, url: string) => {
if (audioRef.current) {
audioRef.current.pause()
audioRef.current = null
}
const audio = new Audio(url)
audioRef.current = audio
setPreviewingId(voice.id)
setPreviewingId(voiceId)
audio.onended = () => {
if (audioRef.current === audio) {
audioRef.current = null
@@ -123,6 +118,52 @@ export function PanelVoiceSelector({
})
}
const handlePreview = async (voice: UnifiedVoiceItem) => {
/* 再次点击当前试听音色 → 停止 */
if (previewingId === voice.id) {
stopPreview()
return
}
/* 克隆音色:preview_url/audio_url 通常为空,需走 POST /tts/preview
* 现合成示例文案再播放,对齐配音库 useAudioPlayer 行为 */
if (voice.type === "clone") {
const cached = previewCacheRef.current.get(voice.voice_clone_profile_id || voice.id)
if (cached) {
playAudioUrl(voice.id, cached)
return
}
const targetId = voice.voice_clone_profile_id || voice.id
setPreviewingId(voice.id)
try {
const res = await previewTts({
text: VOICE_PREVIEW_TEXT,
voice_id: targetId,
speed: 1.0,
})
if (!res.audio_url) {
setPreviewingId(null)
message.error("合成试听失败:未返回音频")
return
}
previewCacheRef.current.set(targetId, res.audio_url)
playAudioUrl(voice.id, res.audio_url)
} catch {
setPreviewingId(null)
// apiClient 拦截器已统一 toast
}
return
}
/* 系统预设音色:沿用 preview_url/audio_url 直链播放 */
const url = voice.preview_url || voice.audio_url
if (!url) {
message.warning(NO_PREVIEW_TIP)
return
}
playAudioUrl(voice.id, url)
}
const handleSpeedChange = (value: string) => {
const parsed = parseFloat(value)
if (Number.isNaN(parsed)) return
@@ -187,9 +228,7 @@ export function PanelVoiceSelector({
<button
type="button"
className="aa-voice-card__preview"
title={
!previewUrl ? NO_PREVIEW_TIP : previewingId === voice.id ? "停止试听" : "试听"
}
title={previewingId === voice.id ? "停止试听" : "试听"}
disabled={!previewUrl}
onClick={(e) => {
e.stopPropagation()