From 349166fe818c70815a916e76bb0b6c38fb2fe91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E9=BE=99?= Date: Tue, 12 Aug 2025 16:41:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=A7=92=E8=89=B2=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=8F=82=E6=95=B0=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=BA=94=E7=94=A8=E8=A7=92=E8=89=B2=E5=88=B0=E5=88=86?= =?UTF-8?q?=E9=95=9C=E7=9A=84=E8=AF=B7=E6=B1=82=E7=BB=93=E6=9E=84=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=92=E8=89=B2=E8=A7=86=E9=A2=91=E7=89=87?= =?UTF-8?q?=E6=AE=B5=E6=9C=8D=E5=8A=A1=E9=80=BB=E8=BE=91=EF=BC=8C=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=8E=B7=E5=8F=96=E8=A7=92=E8=89=B2=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E7=89=87=E6=AE=B5=E5=88=97=E8=A1=A8=E7=9A=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=EF=BC=8C=E7=A1=AE=E4=BF=9D=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E5=8A=9F=E8=83=BD=E7=9A=84=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=80=A7=E5=92=8C=E5=8F=AF=E7=94=A8=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/video_flow.ts | 52 +++++++-- app/service/Interaction/RoleService.ts | 10 +- app/service/Interaction/RoleShotService.ts | 118 ++++++++++++++------- components/ui/character-tab-content.tsx | 10 +- 4 files changed, 134 insertions(+), 56 deletions(-) diff --git a/api/video_flow.ts b/api/video_flow.ts index 1b71fa7..3121269 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -19,6 +19,20 @@ import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, C import { RoleResponse } from "@/app/service/usecase/RoleEditUseCase"; import { RoleRecognitionResponse } from "@/app/service/usecase/ShotEditUsecase"; +/** + * 角色替换参数接口 + */ +export interface CharacterReplacement { + /** 原始角色名称 */ + original_name: string; + /** 原始角色描述 */ + original_description: string; + /** 新角色名称 */ + new_name: string; + /** 新角色描述 */ + new_description: string; +} + // API 响应类型 interface BaseApiResponse { code: number; @@ -270,12 +284,27 @@ export const regenerateRole = async (request: { * @returns Promise> */ export const applyRoleToShots = async (request: { - /** 角色ID */ - roleId: string; + /** 项目ID */ + project_id: string; /** 分镜ID列表 */ - shotIds: string[]; -}): Promise> => { - return post>("/movie/apply_role_to_shots", request); + shot_id: string; + /** 角色替换参数列表 */ + character_replacements: CharacterReplacement[]; + /** 是否等待完成 */ + wait_for_completion?: boolean; +}): Promise; +}>> => { + return post("/movie/apply_role_to_shots", request); }; /** @@ -919,14 +948,14 @@ export const batchUpdateVideoSegments = async (request: { }; /** - * 获取角色在项目中的场景列表接口 - * @param request - 获取角色场景请求参数 - * @returns Promise> + * 获取角色在项目中的视频片段列表接口 + * @param request - 获取角色视频片段请求参数 + * @returns Promise> */ -export const getCharacterScenes = async (request: { +export const getCharacterShots = async (request: { /** 项目ID */ project_id: string; - /** 角色名称或ID */ + /** 角色名称 */ character_name: string; }): Promise; /** 总数量 */ total_count: number; }>> => { return post("/character/get_character_scenes", request); }; + + diff --git a/app/service/Interaction/RoleService.ts b/app/service/Interaction/RoleService.ts index a755c82..a9206fd 100644 --- a/app/service/Interaction/RoleService.ts +++ b/app/service/Interaction/RoleService.ts @@ -262,13 +262,15 @@ export const useRoleServiceHook = (): UseRoleService => { */ const fetchUserRoleLibrary = useCallback(async () => { try { + let useCase = roleEditUseCase; + // 如果没有初始化RoleEditUseCase,创建一个新的实例 - if (!roleEditUseCase) { - const newRoleEditUseCase = new RoleEditUseCase(); - setRoleEditUseCase(newRoleEditUseCase); + if (!useCase) { + useCase = new RoleEditUseCase(); + setRoleEditUseCase(useCase); } - const roleLibraryList = await roleEditUseCase!.getRoleLibraryList(); + const roleLibraryList = await useCase!.getRoleLibraryList(); setUserRoleLibrary(roleLibraryList); } catch (error) { console.error('获取用户角色库失败:', error); diff --git a/app/service/Interaction/RoleShotService.ts b/app/service/Interaction/RoleShotService.ts index e6c2760..0a54946 100644 --- a/app/service/Interaction/RoleShotService.ts +++ b/app/service/Interaction/RoleShotService.ts @@ -1,7 +1,7 @@ import { useState, useCallback, useMemo } from 'react'; -import { VideoSegmentEntity } from '../domain/Entities'; +import { RoleEntity, VideoSegmentEntity } from '../domain/Entities'; import { RoleEditUseCase } from '../usecase/RoleEditUseCase'; -import { getCharacterScenes } from '@/api/video_flow'; +import { getCharacterShots, applyRoleToShots, CharacterReplacement } from '@/api/video_flow'; /** * 扩展的视频片段实体,包含选择状态 @@ -26,6 +26,8 @@ interface UseRoleShotService { selectedVideoSegmentCount: number; /** 当前选中的角色ID */ selectedRoleId: string | null; + /** 是否正在替换中 */ + isReplacing: boolean; // 操作方法 /** 获取角色出现的分镜列表 */ fetchRoleShots: (roleId: string) => Promise; @@ -34,7 +36,7 @@ interface UseRoleShotService { /** 选择/取消选择单个分镜 */ toggleShotSelection: (shotId: string) => void; /** 应用角色到选中的分镜 */ - applyRoleToSelectedShots: (roleId: string) => Promise; + applyRoleToSelectedShots: (newRole: RoleEntity) => Promise; /** 清空选择列表 */ clearShotSelection: () => void; } @@ -43,13 +45,12 @@ interface UseRoleShotService { * 角色视频片段服务Hook * 提供角色与视频片段交互的所有响应式功能和业务逻辑 */ -export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => { +export const useRoleShotServiceHook = (projectId: string,selectRole:RoleEntity): UseRoleShotService => { // 响应式状态 const [shotSelectionList, setShotSelectionList] = useState([]); const [selectedRoleId, setSelectedRoleId] = useState(null); - - // UseCase实例 - const [roleEditUseCase, setRoleEditUseCase] = useState(null); + const [selectedRole, setSelectedRole] = useState(selectRole); + const [isReplacing, setIsReplacing] = useState(false); // 全局替换状态 // 计算属性 /** @@ -71,16 +72,16 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => /** * 获取角色出现的分镜列表 * @description 获取当前角色应用到的分镜列表,包括已应用状态 - * @param roleId 角色ID + * @param roleName 角色名称 * @throws {Error} 当API调用失败时抛出错误 * @returns {Promise} 获取完成后的Promise */ - const fetchRoleShots = useCallback(async (roleId: string) => { + const fetchRoleShots = useCallback(async (roleName: string) => { try { // 调用新的真实接口获取角色在项目中的场景列表 - const response = await getCharacterScenes({ + const response = await getCharacterShots({ project_id: projectId, - character_name: roleId + character_name: roleName }); if (response.successful) { @@ -92,7 +93,7 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => name: `视频片段_${scene.video_id}`, sketchUrl: "", videoUrl: scene.video_urls, // 保持为string[]类型 - status: 1, // 默认为已完成状态 + status:scene.video_urls.length>0?1:0, // 默认为已完成状态 lens: [], updatedAt: Date.now(), loadingProgress: 100, @@ -102,11 +103,7 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => })); setShotSelectionList(extendedShots); - setSelectedRoleId(roleId); - - // 初始化角色编辑UseCase实例 - const newRoleEditUseCase = new RoleEditUseCase(); - setRoleEditUseCase(newRoleEditUseCase); + setSelectedRoleId(roleName); } else { throw new Error(`获取角色场景列表失败: ${response.message}`); } @@ -145,39 +142,77 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => /** * 应用角色到选中的分镜 * @description 将当前角色应用到选中的分镜,并更新应用状态 - * @param roleId 角色ID * @throws {Error} 当未选择分镜或UseCase未初始化时抛出错误 * @returns {Promise} 应用完成后的Promise */ - const applyRoleToSelectedShots = useCallback(async (roleId: string) => { - if (!roleEditUseCase) { - throw new Error('角色编辑UseCase未初始化'); - } - - const selectedShotIds = shotSelectionList - .filter(shot => shot.selected) - .map(shot => shot.id); - - if (selectedShotIds.length === 0) { - throw new Error('请先选择要应用的分镜'); - } - + const applyRoleToSelectedShots = useCallback(async (newRole: RoleEntity) => { try { - await roleEditUseCase.applyRole(selectedShotIds, roleId); + // 检查是否有选中的分镜 + const selectedShots = shotSelectionList.filter(shot => shot.selected); + if (selectedShots.length === 0) { + throw new Error('请先选择要应用角色的分镜'); + } + + // 检查是否有选中的角色 + if (!selectedRole||!selectedRole.generateText ) { + throw new Error('请先选择要应用的角色'); + } + + // 检查是否正在替换中 + if (isReplacing) { + throw new Error('正在替换中,请等待完成后再试'); + } + + // 设置全局替换状态为true + setIsReplacing(true); + + // 构建角色替换参数 + const characterReplacements: CharacterReplacement[] = [ + { + original_name: selectedRole.name, + original_description: selectedRole.generateText, + new_name: newRole.name, + new_description: newRole.generateText + } + ]; + + // 循环调用接口,为每个选中的分镜单独调用 + selectedShots.forEach(async (shot) => { + try { + // 调用应用角色到分镜接口(不等待完成) + applyRoleToShots({ + project_id: projectId, + shot_id: shot.id, // 单个分镜ID + character_replacements: characterReplacements, + wait_for_completion: false // 不等待完成,异步处理 + }).then(response => { + if (response.successful) { + console.log(`分镜 ${shot.id} 角色替换成功:`, response.data); + } else { + console.error(`分镜 ${shot.id} 角色替换失败:`, response.message); + } + }).catch(error => { + console.error(`分镜 ${shot.id} 角色替换异常:`, error); + }); + } catch (error) { + console.error(`分镜 ${shot.id} 角色替换失败:`, error); + } + }); + + console.log(`开始为 ${selectedShots.length} 个分镜替换角色`); + + // 延迟重置替换状态,给用户一些反馈时间 + setTimeout(() => { + setIsReplacing(false); + }, 2000); - // 更新应用状态 - setShotSelectionList(prev => - prev.map(shot => - selectedShotIds.includes(shot.id) - ? { ...shot, applied: true, selected: false } - : shot - ) - ); } catch (error) { console.error('应用角色到分镜失败:', error); + // 出错时也要重置替换状态 + setIsReplacing(false); throw error; } - }, [roleEditUseCase, shotSelectionList]); + }, [selectedRole, shotSelectionList, projectId, isReplacing]); /** * 清空选择列表 @@ -195,6 +230,7 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => isAllVideoSegmentSelected, selectedVideoSegmentCount, selectedRoleId, + isReplacing, // 操作方法 fetchRoleShots, diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx index bfc05c0..a312560 100644 --- a/components/ui/character-tab-content.tsx +++ b/components/ui/character-tab-content.tsx @@ -9,6 +9,8 @@ import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-chara import { CharacterLibrarySelector } from './character-library-selector'; import HorizontalScroller from './HorizontalScroller'; import { useEditData } from '@/components/pages/work-flow/use-edit-data'; +import { useRoleShotServiceHook } from '@/app/service/Interaction/RoleShotService'; +import { useSearchParams } from 'next/navigation'; interface Appearance { hairStyle: string; @@ -84,11 +86,16 @@ export function CharacterTabContent({ updateRoleText, regenerateRole } = useEditData('role'); + const searchParams = useSearchParams(); + const episodeId = searchParams.get('episodeId'); + + const {fetchRoleShots} = useRoleShotServiceHook(episodeId as string) useEffect(() => { console.log('-==========roleData===========-', roleData); if (roleData.length > 0) { selectRole(roleData[selectRoleIndex].id); + } }, [selectRoleIndex, roleData]); @@ -114,8 +121,9 @@ export function CharacterTabContent({ // 替换角色 setIsReplacePanelOpen(true); + fetchRoleShots("President Alfred King"); }; - + // President Alfred King Samuel Ryan const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => { // 处理替换确认逻辑 console.log('Selected shots:', selectedShots);