From 5ed0cfff0772261280cb7eb4e673ca264725e4b3 Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sun, 28 Dec 2025 15:15:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8A=9F=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 2 + .../components/BenchmarkForm.vue | 40 ++-- .../kling/hooks/useDigitalHumanGeneration.ts | 3 - .../kling/hooks/useIdentifyFaceController.ts | 175 +++++++++--------- 4 files changed, 115 insertions(+), 105 deletions(-) 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,