diff --git a/CLAUDE.md b/CLAUDE.md
index 1c95ef4857..388f59b295 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -35,6 +35,7 @@ Keep this managed block so 'openspec update' can refresh the instructions.
- TypeScript类型定义规范
- 组件保持小巧专注,逻辑清晰
- 样式优先使用less 示例:`
diff --git a/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts b/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts
index 6375e10738..c88a4946c6 100644
--- a/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts
+++ b/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts
@@ -79,11 +79,8 @@ export function useDigitalHumanGeneration(): UseDigitalHumanGeneration {
videoState.value.videoFile = null
videoState.value.videoSource = 'select'
videoState.value.selectorVisible = false
-
resetIdentifyState()
identifyState.value.videoFileId = video.id
-
- performFaceRecognition()
}
/**
diff --git a/frontend/app/web-gold/src/views/kling/hooks/useIdentifyFaceController.ts b/frontend/app/web-gold/src/views/kling/hooks/useIdentifyFaceController.ts
index d24dc35072..48b69e031f 100644
--- a/frontend/app/web-gold/src/views/kling/hooks/useIdentifyFaceController.ts
+++ b/frontend/app/web-gold/src/views/kling/hooks/useIdentifyFaceController.ts
@@ -22,13 +22,32 @@ import { useDigitalHumanGeneration } from './useDigitalHumanGeneration'
* 内部直接创建和管理两个子 Hook
*/
export function useIdentifyFaceController(): UseIdentifyFaceController {
- // ==================== 创建子 Hooks ====================
+ // ==================== 创建子 Hooks 并解构 ====================
- // 1. 创建语音生成 Hook(独立管理状态)
- const voiceGeneration = useVoiceGeneration()
+ // 1. 语音生成 Hook - 解构响应式变量
+ const {
+ ttsText,
+ speechRate,
+ selectedVoiceMeta,
+ audioState,
+ canGenerateAudio,
+ suggestedMaxChars,
+ generateAudio,
+ resetAudioState,
+ } = useVoiceGeneration()
- // 2. 创建数字人生成 Hook(独立管理状态)
- const digitalHuman = useDigitalHumanGeneration()
+ // 2. 数字人生成 Hook - 解构响应式变量
+ const {
+ videoState,
+ identifyState,
+ faceDuration,
+ performFaceRecognition,
+ handleFileUpload,
+ handleVideoSelect: _handleVideoSelect,
+ getVideoPreviewUrl,
+ resetVideoState,
+ resetIdentifyState,
+ } = useDigitalHumanGeneration()
// 3. Controller 统一管理跨 Hook 的状态
const materialValidation = ref({
@@ -40,15 +59,15 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
// 4. 监听音频状态变化,自动触发素材校验
watch(
- () => voiceGeneration.audioState.value.generated && voiceGeneration.audioState.value.durationMs > 0,
+ () => audioState.value.generated && audioState.value.durationMs > 0,
(newVal, oldVal) => {
if (newVal && !oldVal) {
// 音频生成完成,获取视频时长并校验
- const videoDuration = digitalHuman.faceDuration.value || 0
- const audioDuration = voiceGeneration.audioState.value.durationMs
+ const videoDurationMs = faceDuration.value || 0
+ const audioDurationMs = audioState.value.durationMs
- if (videoDuration > 0) {
- validateMaterialDuration(videoDuration, audioDuration)
+ if (videoDurationMs > 0) {
+ validateMaterialDuration(videoDurationMs, audioDurationMs)
}
}
},
@@ -57,19 +76,19 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
// 5. 监听人脸识别状态变化,更新素材校验的视频时长
watch(
- () => digitalHuman.identifyState.value.identified,
+ () => identifyState.value.identified,
(newVal, oldVal) => {
if (newVal && !oldVal) {
// 人脸识别成功,获取视频时长
- const videoDuration = digitalHuman.faceDuration.value
+ const videoDurationMs = faceDuration.value
// 如果已有音频,则重新校验
- if (voiceGeneration.audioState.value.generated && voiceGeneration.audioState.value.durationMs > 0) {
- const audioDuration = voiceGeneration.audioState.value.durationMs
- validateMaterialDuration(videoDuration, audioDuration)
+ if (audioState.value.generated && audioState.value.durationMs > 0) {
+ const audioDurationMs = audioState.value.durationMs
+ validateMaterialDuration(videoDurationMs, audioDurationMs)
} else {
// 否则只更新视频时长
- materialValidation.value.videoDuration = videoDuration
+ materialValidation.value.videoDuration = videoDurationMs
}
}
},
@@ -81,11 +100,15 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
* 是否可以生成数字人视频(综合检查)
*/
const canGenerate = computed(() => {
- const hasText = voiceGeneration.ttsText.value.trim()
- const hasVoice = voiceGeneration.selectedVoiceMeta.value
- const hasVideo = digitalHuman.videoState.value.uploadedVideo || digitalHuman.videoState.value.selectedVideo
- const audioValidated = voiceGeneration.audioState.value.validationPassed
- const materialValidated = materialValidation.value.isValid
+ const hasText = ttsText.value.trim()
+ const hasVoice = selectedVoiceMeta.value
+ const hasVideo = videoState.value.uploadedVideo || videoState.value.selectedVideo
+
+ // 音频校验:只有生成过音频后才需要校验通过
+ const audioValidated = !audioState.value.generated || audioState.value.validationPassed
+ // 素材校验:只有进行过校验后才需要校验通过
+ const materialValidated = materialValidation.value.videoDuration === 0 || materialValidation.value.isValid
+
return !!(hasText && hasVoice && hasVideo && audioValidated && materialValidated)
})
@@ -93,18 +116,18 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
* 最大的文本长度
*/
const maxTextLength = computed(() => {
- if (!digitalHuman.identifyState.value.identified || digitalHuman.faceDuration.value <= 0) {
+ if (!identifyState.value.identified || faceDuration.value <= 0) {
return 4000
}
- return Math.min(4000, Math.floor(voiceGeneration.suggestedMaxChars.value * 1.2))
+ return Math.min(4000, Math.floor(suggestedMaxChars.value * 1.2))
})
/**
* 文本框占位符
*/
const textareaPlaceholder = computed(() => {
- if (digitalHuman.identifyState.value.identified && digitalHuman.faceDuration.value > 0) {
- return `请输入文案,建议不超过${voiceGeneration.suggestedMaxChars.value}字以确保与视频匹配`
+ if (identifyState.value.identified && faceDuration.value > 0) {
+ return `请输入文案,建议不超过${suggestedMaxChars.value}字以确保与视频匹配`
}
return '请输入你想让角色说话的内容'
})
@@ -117,7 +140,7 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
/**
* 语速显示
*/
- const speechRateDisplay = computed(() => `${voiceGeneration.speechRate.value.toFixed(1)}x`)
+ const speechRateDisplay = computed(() => `${speechRate.value.toFixed(1)}x`)
// ==================== 业务流程方法 ====================
@@ -130,13 +153,13 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
return
}
- const text = voiceGeneration.ttsText.value.trim()
+ const text = ttsText.value.trim()
if (!text) {
message.warning('请输入文案内容')
return
}
- const voice = voiceGeneration.selectedVoiceMeta.value
+ const voice = selectedVoiceMeta.value
if (!voice) {
message.warning('请选择音色')
return
@@ -144,9 +167,9 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
try {
// 如果未识别,先进行人脸识别
- if (!digitalHuman.identifyState.value.identified) {
- const hasUploadFile = digitalHuman.videoState.value.videoFile
- const hasSelectedVideo = digitalHuman.videoState.value.selectedVideo
+ if (!identifyState.value.identified) {
+ const hasUploadFile = videoState.value.videoFile
+ const hasSelectedVideo = videoState.value.selectedVideo
if (!hasUploadFile && !hasSelectedVideo) {
message.warning('请先选择或上传视频')
@@ -154,27 +177,27 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
}
try {
- await digitalHuman.performFaceRecognition()
+ await performFaceRecognition()
message.success('人脸识别完成')
} catch (error) {
return
}
}
- const videoFileId = digitalHuman.identifyState.value.videoFileId
+ const videoFileId = identifyState.value.videoFileId
const taskData: LipSyncTaskData = {
taskName: `数字人任务_${Date.now()}`,
videoFileId: videoFileId!,
- inputText: voiceGeneration.ttsText.value,
- speechRate: voiceGeneration.speechRate.value,
+ inputText: ttsText.value,
+ speechRate: speechRate.value,
volume: 0,
guidanceScale: 1,
seed: 8888,
- kling_session_id: digitalHuman.identifyState.value.sessionId,
- kling_face_id: digitalHuman.identifyState.value.faceId,
- kling_face_start_time: digitalHuman.identifyState.value.faceStartTime,
- kling_face_end_time: digitalHuman.identifyState.value.faceEndTime,
+ kling_session_id: identifyState.value.sessionId,
+ kling_face_id: identifyState.value.faceId,
+ kling_face_start_time: identifyState.value.faceStartTime,
+ kling_face_end_time: identifyState.value.faceEndTime,
ai_provider: 'kling',
voiceConfigId: voice.rawId || extractIdFromString(voice.id),
}
@@ -185,13 +208,13 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
}
// 如果有预生成的音频,添加到任务数据中
- if (voiceGeneration.audioState.value.generated && voiceGeneration.audioState.value.durationMs > 0) {
+ if (audioState.value.generated && audioState.value.durationMs > 0) {
taskData.pre_generated_audio = {
- audioBase64: voiceGeneration.audioState.value.generated.audioBase64,
- format: voiceGeneration.audioState.value.generated.format || 'mp3',
+ audioBase64: audioState.value.generated.audioBase64,
+ format: audioState.value.generated.format || 'mp3',
}
- taskData.sound_end_time = voiceGeneration.audioState.value.durationMs
+ taskData.sound_end_time = audioState.value.durationMs
}
const res = await createLipSyncTask(taskData)
@@ -210,25 +233,25 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
* 更换视频
*/
const replaceVideo = (): void => {
- if (digitalHuman.videoState.value.videoSource === 'upload') {
- digitalHuman.videoState.value.videoFile = null
- digitalHuman.videoState.value.uploadedVideo = ''
+ if (videoState.value.videoSource === 'upload') {
+ videoState.value.videoFile = null
+ videoState.value.uploadedVideo = ''
} else {
- digitalHuman.videoState.value.selectedVideo = null
- digitalHuman.videoState.value.videoFile = null
- digitalHuman.videoState.value.uploadedVideo = ''
+ videoState.value.selectedVideo = null
+ videoState.value.videoFile = null
+ videoState.value.uploadedVideo = ''
}
// 重置所有状态
- digitalHuman.resetVideoState()
- voiceGeneration.resetAudioState()
+ resetVideoState()
+ resetAudioState()
}
/**
* 处理音色选择
*/
const handleVoiceSelect = (voice: any): void => {
- voiceGeneration.selectedVoiceMeta.value = voice
+ selectedVoiceMeta.value = voice
}
/**
@@ -238,7 +261,7 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (file) {
- digitalHuman.handleFileUpload(file)
+ handleFileUpload(file)
}
}
@@ -249,7 +272,7 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
event.preventDefault()
const file = event.dataTransfer?.files[0]
if (file) {
- digitalHuman.handleFileUpload(file)
+ handleFileUpload(file)
}
}
@@ -265,26 +288,26 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
* 选择上传模式
*/
const handleSelectUpload = (): void => {
- digitalHuman.videoState.value.videoSource = 'upload'
- digitalHuman.videoState.value.selectedVideo = null
- digitalHuman.resetIdentifyState()
+ videoState.value.videoSource = 'upload'
+ videoState.value.selectedVideo = null
+ resetIdentifyState()
}
/**
* 从素材库选择
*/
const handleSelectFromLibrary = (): void => {
- digitalHuman.videoState.value.videoSource = 'select'
- digitalHuman.videoState.value.videoFile = null
- digitalHuman.videoState.value.uploadedVideo = ''
- digitalHuman.videoState.value.selectorVisible = true
+ videoState.value.videoSource = 'select'
+ videoState.value.videoFile = null
+ videoState.value.uploadedVideo = ''
+ videoState.value.selectorVisible = true
}
/**
* 处理视频选择器选择
*/
const handleVideoSelect = (video: any): void => {
- digitalHuman.handleVideoSelect(video)
+ _handleVideoSelect(video)
}
/**
@@ -302,7 +325,7 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
* 处理视频加载
*/
const handleVideoLoaded = (videoUrl: string): void => {
- digitalHuman.videoState.value.previewVideoUrl = videoUrl
+ videoState.value.previewVideoUrl = videoUrl
}
// ==================== UI 辅助方法 ====================
@@ -359,32 +382,6 @@ export function useIdentifyFaceController(): UseIdentifyFaceController {
return materialValidation.value.isValid
}
- // ==================== 解构子 Hooks 的响应式变量 ====================
-
- // 语音生成相关
- const {
- ttsText,
- speechRate,
- selectedVoiceMeta,
- audioState,
- canGenerateAudio,
- suggestedMaxChars,
- generateAudio,
- resetAudioState,
- } = voiceGeneration
-
- // 数字人生成相关
- const {
- videoState,
- identifyState,
- faceDuration,
- performFaceRecognition,
- handleFileUpload,
- getVideoPreviewUrl,
- resetVideoState,
- resetIdentifyState,
- } = digitalHuman
-
return {
// ==================== 语音生成相关 ====================
ttsText,