import { useState, useCallback, useMemo } from 'react'; import { RoleEntity, VideoSegmentEntity } from '../domain/Entities'; import { RoleEditUseCase } from '../usecase/RoleEditUseCase'; import { getCharacterShots, applyRoleToShots, CharacterReplacement } from '@/api/video_flow'; /** * 扩展的视频片段实体,包含选择状态 */ interface ExtendedVideoSegmentEntity extends VideoSegmentEntity { /** 是否已选中 */ selected?: boolean; /** 是否已应用 */ applied?: boolean; } /** * 角色视频片段服务Hook返回值接口 */ interface UseRoleShotService { // 响应式数据 /** 分镜选择列表 */ shotSelectionList: ExtendedVideoSegmentEntity[]; /** 是否全选分镜 */ isAllVideoSegmentSelected: boolean; /** 已选中的分镜数量 */ selectedVideoSegmentCount: number; /** 当前选中的角色ID */ selectedRoleId: string | null; /** 是否正在替换中 */ isReplacing: boolean; // 操作方法 /** 获取角色出现的分镜列表 */ fetchRoleShots: (roleId: string) => Promise; /** 切换全选与全不选 */ toggleSelectAllShots: () => void; /** 选择/取消选择单个分镜 */ toggleShotSelection: (shotId: string) => void; /** 应用角色到选中的分镜 */ applyRoleToSelectedShots: (newRole: RoleEntity) => Promise; /** 清空选择列表 */ clearShotSelection: () => void; } /** * 角色视频片段服务Hook * 提供角色与视频片段交互的所有响应式功能和业务逻辑 */ export const useRoleShotServiceHook = (projectId: string,selectRole:RoleEntity): UseRoleShotService => { // 响应式状态 const [shotSelectionList, setShotSelectionList] = useState([]); const [selectedRoleId, setSelectedRoleId] = useState(null); const [selectedRole, setSelectedRole] = useState(selectRole); const [isReplacing, setIsReplacing] = useState(false); // 全局替换状态 // 计算属性 /** * 是否全选分镜 * @description 判断是否所有分镜都被选中 */ const isAllVideoSegmentSelected = useMemo(() => { return shotSelectionList.length > 0 && shotSelectionList.every(shot => shot.selected); }, [shotSelectionList]); /** * 已选中的分镜数量 * @description 获取当前选中的分镜数量 */ const selectedVideoSegmentCount = useMemo(() => { return shotSelectionList.filter(shot => shot.selected).length; }, [shotSelectionList]); /** * 获取角色出现的分镜列表 * @description 获取当前角色应用到的分镜列表,包括已应用状态 * @param roleName 角色名称 * @throws {Error} 当API调用失败时抛出错误 * @returns {Promise} 获取完成后的Promise */ const fetchRoleShots = useCallback(async (roleName: string) => { try { // 调用新的真实接口获取角色在项目中的场景列表 const response = await getCharacterShots({ project_id: projectId, character_name: roleName }); if (response.successful) { const { scenes, total_count } = response.data; // 将scenes数据转换为ExtendedVideoSegmentEntity[]数据结构 const extendedShots: ExtendedVideoSegmentEntity[] = scenes.map(scene => ({ id: scene.video_id, name: `视频片段_${scene.video_id}`, sketchUrl: "", videoUrl: scene.video_urls, // 保持为string[]类型 status:scene.video_urls.length>0?1:0, // 默认为已完成状态 lens: [], updatedAt: Date.now(), loadingProgress: 100, disableEdit: false, selected: false, applied: true // 由于是通过角色查询到的,所以都是已应用的 })); setShotSelectionList(extendedShots); setSelectedRoleId(roleName); } else { throw new Error(`获取角色场景列表失败: ${response.message}`); } } catch (error) { console.error('获取角色场景列表失败:', error); throw error; } }, [projectId]); /** * 切换全选与全不选 * @description 如果当前是全选状态则全不选,否则全选 */ const toggleSelectAllShots = useCallback(() => { setShotSelectionList(prev => { const isAllSelected = prev.length > 0 && prev.every(shot => shot.selected); return prev.map(shot => ({ ...shot, selected: !isAllSelected })); }); }, []); /** * 选择/取消选择单个分镜 * @description 切换指定分镜的选择状态 * @param shotId 分镜ID */ const toggleShotSelection = useCallback((shotId: string) => { setShotSelectionList(prev => prev.map(shot => shot.id === shotId ? { ...shot, selected: !shot.selected } : shot ) ); }, []); /** * 应用角色到选中的分镜 * @description 将当前角色应用到选中的分镜,并更新应用状态 * @throws {Error} 当未选择分镜或UseCase未初始化时抛出错误 * @returns {Promise} 应用完成后的Promise */ const applyRoleToSelectedShots = useCallback(async (newRole: RoleEntity) => { try { // 检查是否有选中的分镜 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); } catch (error) { console.error('应用角色到分镜失败:', error); // 出错时也要重置替换状态 setIsReplacing(false); throw error; } }, [selectedRole, shotSelectionList, projectId, isReplacing]); /** * 清空选择列表 * @description 清空所有分镜的选择状态 */ const clearShotSelection = useCallback(() => { setShotSelectionList(prev => prev.map(shot => ({ ...shot, selected: false })) ); }, []); return { // 响应式数据 shotSelectionList, isAllVideoSegmentSelected, selectedVideoSegmentCount, selectedRoleId, isReplacing, // 操作方法 fetchRoleShots, toggleSelectAllShots, toggleShotSelection, applyRoleToSelectedShots, clearShotSelection, }; };