diff --git a/api/video_flow.ts b/api/video_flow.ts index f1661e4..f14f296 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -14,6 +14,7 @@ import { LensType, ScriptSlice, } from "@/app/service/domain/valueObject"; +import { VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter"; // API 响应类型 interface BaseApiResponse { @@ -505,16 +506,18 @@ export const getShotData = async (request: { * @returns Promise> */ export const regenerateShot = async (request: { + /** 项目ID */ + project_id:string; /** 分镜ID */ - shotId?: string; + shot_id?: string; /** 镜头描述 */ - shotPrompt?: LensType[]; + shot_descriptions?: LensType[]; /** 角色ID替换参数,格式为{oldId:string,newId:string}[] */ roleReplaceParams?: { oldId: string; newId: string }[]; /** 场景ID替换参数,格式为{oldId:string,newId:string}[] */ sceneReplaceParams?: { oldId: string; newId: string }[]; }): Promise> => { - return post>("/movie/regenerate_shot", request); + return post>("/movie/regenerate_shot_video", request); }; @@ -525,9 +528,9 @@ export const regenerateShot = async (request: { */ export const getShotList = async (request: { /** 项目ID */ - projectId: string; -}): Promise> => { - return post>("/movie/get_shot_list", request); + project_id: string; +}): Promise> => { + return post>("/movie_cut/get_task_list", request); }; /** @@ -742,3 +745,72 @@ export const optimizeShotContent = async (request: { }): Promise> => { return post>("/api/v1/shot/optimize_content", request); }; + +/** + * 暂停电影项目计划 + * @param request - 暂停项目请求参数 + * @returns Promise> + */ +export const pauseMovieProjectPlan = async (request: { + /** 项目ID */ + project_id: string; +}): Promise> => { + return post("/movie/pause_movie_project_plan", request); +}; + +/** + * 继续电影项目计划 + * @param request - 继续项目请求参数 + * @returns Promise> + */ +export const resumeMovieProjectPlan = async (request: { + /** 项目ID */ + project_id: string; +}): Promise> => { + return post("/movie/resume_movie_project_plan", request); +}; + +/** + * AI优化角色描述 + * @param request - AI优化角色描述请求参数 + * @returns Promise> + */ +export const optimizeRoleDescription = async (request: { + /** 角色ID */ + roleId: string; + /** 用户优化建议 */ + userSuggestion: string; + /** 角色描述文本 */ + roleDescription: string; +}): Promise> => { + return post("/movie/optimize_role_description", request); +}; + +/** + * 更新分镜提示词数据 + * @param request - 更新分镜提示词请求参数 + * @returns Promise> 更新结果 + */ +export const updateShotPrompt = async (request: { + /** 项目ID */ + project_id: string; + /** 分镜ID */ + shot_id: string; + /** 镜头描述 */ + shot_descriptions: LensType[]; +}): Promise> => { + return post("/movie/update_shot_prompt", request); +}; diff --git a/app/service/Interaction/RoleService.ts b/app/service/Interaction/RoleService.ts index 8914963..f5f99a9 100644 --- a/app/service/Interaction/RoleService.ts +++ b/app/service/Interaction/RoleService.ts @@ -1,27 +1,7 @@ import { useState, useCallback, useMemo } from 'react'; -import { RoleEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities'; -import { TagValueObject } from '../domain/valueObject'; -import { RoleItem, TagItem, TextItem } from '../domain/Item'; +import { RoleEntity, AITextEntity } from '../domain/Entities'; import { RoleEditUseCase } from '../usecase/RoleEditUseCase'; -import { TagEditUseCase } from '../usecase/TagEditUseCase'; -import { TextEditUseCase } from '../usecase/TextEditUseCase'; -import { getRoleShots, getRoleData, getRoleList, getUserRoleLibrary, replaceRole } from '@/api/video_flow'; - -/** - * 分镜选择项接口 - */ -interface ShotSelectionItem { - /** 分镜ID */ - id: string; - /** 分镜名称 */ - name: string; - /** 是否已选中 */ - selected: boolean; - /** 是否已应用角色 */ - applied: boolean; - /** 分镜数据 */ - shot: VideoSegmentEntity; -} +import { getRoleData, getRoleList, getUserRoleLibrary, replaceRole } from '@/api/video_flow'; /** * 角色服务Hook返回值接口 @@ -29,51 +9,30 @@ interface ShotSelectionItem { interface UseRoleService { // 响应式数据 /** 角色列表 */ - roleList: RoleItem[]; + roleList: RoleEntity[]; /** 当前选中的角色 */ - selectedRole: RoleItem | null; + selectedRole: RoleEntity | null; /** 当前角色的AI文本 */ - currentRoleText: TextItem | null; - /** 当前角色的标签列表 */ - currentRoleTags: TagItem[]; + currentRoleText: string | null; /** 角色图片URL */ roleImageUrl: string; - /** 分镜选择列表 */ - shotSelectionList: ShotSelectionItem[]; - /** 是否全选分镜 */ - isAllShotsSelected: boolean; - /** 已选中的分镜数量 */ - selectedShotsCount: number; /** 用户角色库 */ - userRoleLibrary: RoleItem[]; + userRoleLibrary: RoleEntity[]; // 操作方法 /** 获取角色列表 */ fetchRoleList: (projectId: string) => Promise; /** 选择角色 */ - selectRole: (roleId: string) => void; - /** 初始化当前选中角色的AI文本和标签数据 */ - initializeRoleData: () => Promise; + selectRole: (roleId: string) => Promise; /** 优化AI文本 */ - optimizeRoleText: () => Promise; + optimizeRoleText: (userSuggestion: string) => Promise; /** 修改AI文本 */ updateRoleText: (newContent: string) => Promise; - /** 修改标签内容 */ - updateTagContent: (tagId: string, newContent: string | number) => Promise; /** 重新生成角色 */ regenerateRole: () => Promise; - /** 获取角色出现的分镜列表 */ - fetchRoleShots: () => Promise; - /** 切换全选与全不选 */ - toggleSelectAllShots: () => void; - /** 选择/取消选择单个分镜 */ - toggleShotSelection: (shotId: string) => void; - /** 应用角色到选中的分镜 */ - applyRoleToSelectedShots: () => Promise; /** 获取用户角色库 */ fetchUserRoleLibrary: () => Promise; /** 替换角色 */ replaceRoleWithLibrary: (replaceRoleId: string) => Promise; - } /** @@ -82,17 +41,13 @@ interface UseRoleService { */ export const useRoleServiceHook = (): UseRoleService => { // 响应式状态 - const [roleList, setRoleList] = useState([]); - const [selectedRole, setSelectedRole] = useState(null); - const [currentRoleText, setCurrentRoleText] = useState(null); - const [currentRoleTags, setCurrentRoleTags] = useState([]); - const [shotSelectionList, setShotSelectionList] = useState([]); - const [userRoleLibrary, setUserRoleLibrary] = useState([]); + const [roleList, setRoleList] = useState([]); + const [selectedRole, setSelectedRole] = useState(null); + const [currentRoleText, setCurrentRoleText] = useState(null); + const [userRoleLibrary, setUserRoleLibrary] = useState([]); // UseCase实例 - 在角色选择时初始化 const [roleEditUseCase, setRoleEditUseCase] = useState(null); - const [textEditUseCase, setTextEditUseCase] = useState(null); - const [tagEditUseCases, setTagEditUseCases] = useState>(new Map()); // 计算属性 /** @@ -100,27 +55,9 @@ export const useRoleServiceHook = (): UseRoleService => { * @description 获取当前选中角色的图片URL */ const roleImageUrl = useMemo(() => { - return selectedRole?.entity.imageUrl || ''; + return selectedRole?.imageUrl || ''; }, [selectedRole]); - /** - * 是否全选分镜 - * @description 判断是否所有分镜都被选中 - */ - const isAllShotsSelected = useMemo(() => { - return shotSelectionList.length > 0 && shotSelectionList.every(shot => shot.selected); - }, [shotSelectionList]); - - /** - * 已选中的分镜数量 - * @description 获取当前选中的分镜数量 - */ - const selectedShotsCount = useMemo(() => { - return shotSelectionList.filter(shot => shot.selected).length; - }, [shotSelectionList]); - - - /** * 获取角色列表 * @description 根据项目ID获取所有角色列表 @@ -130,13 +67,9 @@ export const useRoleServiceHook = (): UseRoleService => { */ const fetchRoleList = useCallback(async (projectId: string) => { try { - const response = await getRoleList({ - projectId: projectId - }); - + const response = await getRoleList({ projectId }); if (response.successful) { - const roleItems = response.data.map(role => new RoleItem(role)); - setRoleList(roleItems); + setRoleList(response.data); } else { throw new Error(`获取角色列表失败: ${response.message}`); } @@ -146,62 +79,19 @@ export const useRoleServiceHook = (): UseRoleService => { } }, []); - /** - * 选择角色 - * @description 根据角色ID选择角色,并初始化相关的UseCase实例 - * @param roleId 角色ID - */ - const selectRole = useCallback(async (roleId: string) => { - const role = roleList.find(r => r.entity.id === roleId); - if (role) { - setSelectedRole(role); - - // 初始化角色编辑UseCase实例 - setRoleEditUseCase(new RoleEditUseCase(role)); - - // 清空文本和标签相关状态 - setTextEditUseCase(null); - setTagEditUseCases(new Map()); - setCurrentRoleText(null); - setCurrentRoleTags([]); - await initializeRoleData(); - } - }, [roleList]); - /** * 初始化角色数据 - * @description 初始化当前选中角色的AI文本和标签数据 + * @description 初始化当前选中角色的AI文本数据 + * @param roleId 角色ID * @throws {Error} 当未选择角色或API调用失败时抛出错误 * @returns {Promise} 初始化完成后的Promise */ - const initializeRoleData = useCallback(async () => { - if (!selectedRole) { - throw new Error('请先选择角色'); - } - + const initializeRoleData = useCallback(async (roleId: string) => { try { - const response = await getRoleData({ - roleId: selectedRole.entity.id - }); - + const response = await getRoleData({ roleId }); if (response.successful) { - const { text, tags } = response.data; - const textItem = new TextItem(text); - const tagItems = tags.map(tag => new TagItem(tag)); - - // 设置当前角色的AI文本和标签 - setCurrentRoleText(textItem); - setCurrentRoleTags(tagItems); - - // 初始化文本UseCase - setTextEditUseCase(new TextEditUseCase(textItem)); - - // 初始化标签UseCase - const newTagEditUseCases = new Map(); - tagItems.forEach(tag => { - newTagEditUseCases.set(tag.entity.id, new TagEditUseCase(tag)); - }); - setTagEditUseCases(newTagEditUseCases); + const { text } = response.data; + setCurrentRoleText(text.content); } else { throw new Error(`获取角色数据失败: ${response.message}`); } @@ -209,29 +99,69 @@ export const useRoleServiceHook = (): UseRoleService => { console.error('初始化角色数据失败:', error); throw error; } - }, [selectedRole]); + }, []); + + /** + * 选择角色 + * @description 根据角色ID选择角色,并初始化相关的UseCase实例 + * @param roleId 角色ID + */ + const selectRole = useCallback(async (roleId: string) => { + const role = roleList.find(r => r.id === roleId); + if (role) { + setSelectedRole(role); + + // 初始化角色编辑UseCase实例 + const newRoleEditUseCase = new RoleEditUseCase(); + newRoleEditUseCase.roleList = roleList; + await newRoleEditUseCase.selectRole(roleId); + setRoleEditUseCase(newRoleEditUseCase); + + // 初始化角色数据 + await initializeRoleData(roleId); + } else { + throw new Error('未找到对应的角色'); + } + }, [initializeRoleData, roleList]); /** * 优化AI文本 - * @description 对当前角色的AI文本进行优化,无文本时不可进行优化 + * @description 对当前角色的AI文本进行优化 + * @param userSuggestion 用户优化建议 * @throws {Error} 当没有可优化的文本内容或UseCase未初始化时抛出错误 * @returns {Promise} 优化完成后的Promise */ - const optimizeRoleText = useCallback(async () => { - if (!textEditUseCase) { - throw new Error('文本编辑UseCase未初始化'); + const optimizeRoleText = useCallback(async (userSuggestion: string) => { + if (!roleEditUseCase) { + throw new Error('角色编辑UseCase未初始化'); } - if (!currentRoleText || !currentRoleText.entity.content) { + if (!currentRoleText) { throw new Error('没有可优化的文本内容'); } - const optimizedContent = await textEditUseCase.getOptimizedContent(); + try { + const optimizedDescription = await roleEditUseCase.optimizeRoleDescription(userSuggestion, currentRoleText); + setCurrentRoleText(optimizedDescription); - // 更新文本内容 - const updatedTextItem = await textEditUseCase.updateText(optimizedContent); - setCurrentRoleText(updatedTextItem); - }, [textEditUseCase, currentRoleText]); + // 更新角色列表中的对应角色描述 + setRoleList(prev => + prev.map(role => + role.id === selectedRole?.id + ? { ...role, generateText: optimizedDescription } + : role + ) + ); + + // 更新当前选中角色 + if (selectedRole) { + setSelectedRole({ ...selectedRole, generateText: optimizedDescription }); + } + } catch (error) { + console.error('优化角色文本失败:', error); + throw error; + } + }, [roleEditUseCase, currentRoleText, selectedRole]); /** * 修改AI文本 @@ -241,47 +171,30 @@ export const useRoleServiceHook = (): UseRoleService => { * @returns {Promise} 修改完成后的Promise */ const updateRoleText = useCallback(async (newContent: string) => { - if (!textEditUseCase) { - throw new Error('文本编辑UseCase未初始化'); + if (!roleEditUseCase) { + throw new Error('角色编辑UseCase未初始化'); } - if (!currentRoleText) { - throw new Error('没有可编辑的文本'); - } + setCurrentRoleText(newContent); - const updatedTextItem = await textEditUseCase.updateText(newContent); - setCurrentRoleText(updatedTextItem); - }, [textEditUseCase, currentRoleText]); - - /** - * 修改标签内容 - * @description 修改指定标签的内容 - * @param tagId 标签ID - * @param newContent 新的标签内容 - * @throws {Error} 当标签不存在或UseCase未初始化时抛出错误 - * @returns {Promise} 修改完成后的Promise - */ - const updateTagContent = useCallback(async (tagId: string, newContent: string | number) => { - const tagEditUseCase = tagEditUseCases.get(tagId); - if (!tagEditUseCase) { - throw new Error(`标签编辑UseCase未初始化,标签ID: ${tagId}`); - } - - const updatedTagItem = await tagEditUseCase.updateTag(newContent); - - // 更新标签列表 - setCurrentRoleTags(prev => - prev.map(tag => - tag.entity.id === tagId - ? updatedTagItem - : tag + // 更新角色列表中的对应角色描述 + setRoleList(prev => + prev.map(role => + role.id === selectedRole?.id + ? { ...role, generateText: newContent } + : role ) ); - }, [tagEditUseCases]); + + // 更新当前选中角色 + if (selectedRole) { + setSelectedRole({ ...selectedRole, generateText: newContent }); + } + }, [roleEditUseCase, selectedRole]); /** * 重新生成角色 - * @description 使用AI文本和标签重新生成角色 + * @description 使用AI文本重新生成角色 * @throws {Error} 当缺少重新生成角色所需的数据或UseCase未初始化时抛出错误 * @returns {Promise} 重新生成完成后的Promise */ @@ -290,121 +203,27 @@ export const useRoleServiceHook = (): UseRoleService => { throw new Error('角色编辑UseCase未初始化'); } - if (!selectedRole || !currentRoleText || currentRoleTags.length === 0) { + if (!selectedRole || !currentRoleText) { throw new Error('缺少重新生成角色所需的数据'); } - const newRoleEntity = await roleEditUseCase.AIgenerateRole(currentRoleText, currentRoleTags); - - // 更新角色 - const newRoleItem = new RoleItem(newRoleEntity); - setSelectedRole(newRoleItem); - - // 更新角色列表 - setRoleList(prev => - prev.map(role => - role.entity.id === newRoleEntity.id ? newRoleItem : role - ) - ); - }, [roleEditUseCase, selectedRole, currentRoleText, currentRoleTags]); - - /** - * 获取角色出现的分镜列表 - * @description 获取当前角色应用到的分镜列表,包括已应用状态 - * @throws {Error} 当未选择角色或API调用失败时抛出错误 - * @returns {Promise} 获取完成后的Promise - */ - const fetchRoleShots = useCallback(async () => { - if (!selectedRole) { - throw new Error('请先选择角色'); - } - try { - const response = await getRoleShots({ - roleId: selectedRole.entity.id - }); + const newRoleEntity = await roleEditUseCase.AIgenerateRole(currentRoleText); + setSelectedRole(newRoleEntity); - if (response.successful) { - const { shots, appliedShotIds } = response.data; - - const shotSelectionItems: ShotSelectionItem[] = shots.map(shot => ({ - id: shot.id, - name: shot.name, - selected: false, - applied: appliedShotIds.includes(shot.id), // 根据API返回的已应用列表判断 - shot - })); - - setShotSelectionList(shotSelectionItems); - } else { - throw new Error(`获取角色分镜列表失败: ${response.message}`); - } + // 更新角色列表 + setRoleList(prev => + prev.map(role => + role.id === newRoleEntity.id + ? newRoleEntity + : role + ) + ); } catch (error) { - console.error('获取角色分镜列表失败:', error); + console.error('重新生成角色失败:', error); throw error; } - }, [selectedRole]); - - /** - * 切换全选与全不选 - * @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 () => { - if (!roleEditUseCase) { - throw new Error('角色编辑UseCase未初始化'); - } - - if (!selectedRole) { - throw new Error('请先选择角色'); - } - - const selectedShotIds = shotSelectionList - .filter(shot => shot.selected) - .map(shot => shot.id); - - if (selectedShotIds.length === 0) { - throw new Error('请先选择要应用的分镜'); - } - - await roleEditUseCase.applyRole(selectedShotIds); - - // 更新分镜列表,标记已应用 - setShotSelectionList(prev => - prev.map(shot => - selectedShotIds.includes(shot.id) - ? { ...shot, applied: true, selected: false } - : shot - ) - ); - }, [roleEditUseCase, selectedRole, shotSelectionList]); + }, [roleEditUseCase, selectedRole, currentRoleText]); /** * 获取用户角色库 @@ -415,10 +234,8 @@ export const useRoleServiceHook = (): UseRoleService => { const fetchUserRoleLibrary = useCallback(async () => { try { const response = await getUserRoleLibrary(); - if (response.successful) { - const roleItems = response.data.map(role => new RoleItem(role)); - setUserRoleLibrary(roleItems); + setUserRoleLibrary(response.data); } else { throw new Error(`获取用户角色库失败: ${response.message}`); } @@ -445,72 +262,51 @@ export const useRoleServiceHook = (): UseRoleService => { } try { - const response = await replaceRole({ - currentRoleId: selectedRole.entity.id, - replaceRoleId: replaceRoleId - }); + await roleEditUseCase.replaceRoleById(selectedRole.id, replaceRoleId); - if (response.successful) { - // 重新获取当前角色的详细数据 - await initializeRoleData(); - } else { - throw new Error(`替换角色失败: ${response.message}`); + // 重新获取角色数据 + await initializeRoleData(selectedRole.id); + + // 更新角色列表 + const libraryRole = userRoleLibrary.find(role => role.id === replaceRoleId); + if (libraryRole) { + const updatedRole = { + ...selectedRole, + name: libraryRole.name, + generateText: libraryRole.generateText, + imageUrl: libraryRole.imageUrl, + }; + + setSelectedRole(updatedRole); + setRoleList(prev => + prev.map(role => + role.id === selectedRole.id + ? updatedRole + : role + ) + ); } } catch (error) { console.error('替换角色失败:', error); throw error; } - }, [selectedRole, roleEditUseCase, initializeRoleData]); - - + }, [selectedRole, roleEditUseCase, userRoleLibrary, initializeRoleData]); return { // 响应式数据 - /** 角色列表 */ roleList, - /** 当前选中的角色 */ selectedRole, - /** 当前角色的AI文本 */ currentRoleText, - /** 当前角色的标签列表 */ - currentRoleTags, - /** 角色图片URL */ roleImageUrl, - /** 分镜选择列表 */ - shotSelectionList, - /** 是否全选分镜 */ - isAllShotsSelected, - /** 已选中的分镜数量 */ - selectedShotsCount, - /** 用户角色库 */ userRoleLibrary, - // 操作方法 - /** 获取角色列表 */ - fetchRoleList, - /** 选择角色 */ - selectRole, - /** 初始化当前选中角色的AI文本和标签数据 */ - initializeRoleData, - /** 优化AI文本 */ - optimizeRoleText, - /** 修改AI文本 */ - updateRoleText, - /** 修改标签内容 */ - updateTagContent, - /** 重新生成角色 */ - regenerateRole, - /** 获取角色出现的分镜列表 */ - fetchRoleShots, - /** 切换全选与全不选 */ - toggleSelectAllShots, - /** 选择/取消选择单个分镜 */ - toggleShotSelection, - /** 应用角色到选中的分镜 */ - applyRoleToSelectedShots, - /** 获取用户角色库 */ - fetchUserRoleLibrary, - /** 替换角色 */ - replaceRoleWithLibrary, + // 操作方法 + fetchRoleList, + selectRole, + optimizeRoleText, + updateRoleText, + regenerateRole, + fetchUserRoleLibrary, + replaceRoleWithLibrary, }; }; diff --git a/app/service/Interaction/RoleShotService.ts b/app/service/Interaction/RoleShotService.ts new file mode 100644 index 0000000..6108e01 --- /dev/null +++ b/app/service/Interaction/RoleShotService.ts @@ -0,0 +1,192 @@ +import { useState, useCallback, useMemo } from 'react'; +import { VideoSegmentEntity } from '../domain/Entities'; +import { RoleEditUseCase } from '../usecase/RoleEditUseCase'; +import { getRoleShots, applyRoleToShots } 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; + // 操作方法 + /** 获取角色出现的分镜列表 */ + fetchRoleShots: (roleId: string) => Promise; + /** 切换全选与全不选 */ + toggleSelectAllShots: () => void; + /** 选择/取消选择单个分镜 */ + toggleShotSelection: (shotId: string) => void; + /** 应用角色到选中的分镜 */ + applyRoleToSelectedShots: (roleId: string) => Promise; + /** 清空选择列表 */ + clearShotSelection: () => void; +} + +/** + * 角色视频片段服务Hook + * 提供角色与视频片段交互的所有响应式功能和业务逻辑 + */ +export const useRoleShotServiceHook = (): UseRoleShotService => { + // 响应式状态 + const [shotSelectionList, setShotSelectionList] = useState([]); + const [selectedRoleId, setSelectedRoleId] = useState(null); + + // UseCase实例 + const [roleEditUseCase, setRoleEditUseCase] = useState(null); + + // 计算属性 + /** + * 是否全选分镜 + * @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 roleId 角色ID + * @throws {Error} 当API调用失败时抛出错误 + * @returns {Promise} 获取完成后的Promise + */ + const fetchRoleShots = useCallback(async (roleId: string) => { + try { + const response = await getRoleShots({ roleId }); + if (response.successful) { + const { shots, appliedShotIds } = response.data; + + const extendedShots: ExtendedVideoSegmentEntity[] = shots.map(shot => ({ + ...shot, + selected: false, + applied: appliedShotIds.includes(shot.id) + })); + + setShotSelectionList(extendedShots); + setSelectedRoleId(roleId); + + // 初始化角色编辑UseCase实例 + const newRoleEditUseCase = new RoleEditUseCase(); + setRoleEditUseCase(newRoleEditUseCase); + } else { + throw new Error(`获取角色分镜列表失败: ${response.message}`); + } + } catch (error) { + console.error('获取角色分镜列表失败:', error); + throw error; + } + }, []); + + /** + * 切换全选与全不选 + * @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 将当前角色应用到选中的分镜,并更新应用状态 + * @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('请先选择要应用的分镜'); + } + + try { + await roleEditUseCase.applyRole(selectedShotIds, roleId); + + // 更新应用状态 + setShotSelectionList(prev => + prev.map(shot => + selectedShotIds.includes(shot.id) + ? { ...shot, applied: true, selected: false } + : shot + ) + ); + } catch (error) { + console.error('应用角色到分镜失败:', error); + throw error; + } + }, [roleEditUseCase, shotSelectionList]); + + /** + * 清空选择列表 + * @description 清空所有分镜的选择状态 + */ + const clearShotSelection = useCallback(() => { + setShotSelectionList(prev => + prev.map(shot => ({ ...shot, selected: false })) + ); + }, []); + + return { + // 响应式数据 + shotSelectionList, + isAllVideoSegmentSelected, + selectedVideoSegmentCount, + selectedRoleId, + + // 操作方法 + fetchRoleShots, + toggleSelectAllShots, + toggleShotSelection, + applyRoleToSelectedShots, + clearShotSelection, + }; +}; diff --git a/app/service/Interaction/SceneService.ts b/app/service/Interaction/SceneService.ts index 71b66d6..1d02113 100644 --- a/app/service/Interaction/SceneService.ts +++ b/app/service/Interaction/SceneService.ts @@ -1,9 +1,8 @@ import { useState, useCallback, useMemo } from 'react'; -import { SceneEntity, TagValueObject, AITextEntity, VideoSegmentEntity } from '../domain/Entities'; +import { SceneEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities'; import { SceneItem, TagItem, TextItem } from '../domain/Item'; import { SceneEditUseCase } from '../usecase/SceneEditUseCase'; import { TagEditUseCase } from '../usecase/TagEditUseCase'; -import { TextEditUseCase } from '../usecase/TextEditUseCase'; import { getSceneShots, getSceneData, getSceneList } from '@/api/video_flow'; /** diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index 6eb393c..46dea61 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -22,8 +22,6 @@ export interface UseShotService { getVideoSegmentList: (projectId: string) => Promise; /** 重新生成视频片段 */ regenerateVideoSegment: ( - shotPrompt: LensType[], - shotId?: string, roleReplaceParams?: { oldId: string; newId: string }[], sceneReplaceParams?: { oldId: string; newId: string }[] ) => Promise; @@ -55,7 +53,7 @@ export const useShotService = (): UseShotService => { const [loading, setLoading] = useState(false); const [videoSegments, setVideoSegments] = useState([]); const [selectedSegment, setSelectedSegment] = useState(null); - + const [projectId, setProjectId] = useState(""); // UseCase实例 const [vidoEditUseCase] = useState( new VideoSegmentEditUseCase() @@ -71,6 +69,9 @@ export const useShotService = (): UseShotService => { setLoading(true); const segments = await vidoEditUseCase.getVideoSegmentList(projectId); + setProjectId(projectId); + console.log('segments', segments); + setVideoSegments(segments); } catch (error) { console.error("获取视频片段列表失败:", error); @@ -91,8 +92,6 @@ export const useShotService = (): UseShotService => { */ const regenerateVideoSegment = useCallback( async ( - shotPrompt: LensType[], - shotId?: string, roleReplaceParams?: { oldId: string; newId: string }[], sceneReplaceParams?: { oldId: string; newId: string }[] ): Promise => { @@ -100,17 +99,18 @@ export const useShotService = (): UseShotService => { setLoading(true); const regeneratedSegment = await vidoEditUseCase.regenerateVideoSegment( - shotPrompt, - shotId, + projectId, + selectedSegment!.lens, + selectedSegment!.id, roleReplaceParams, sceneReplaceParams ); // 如果重新生成的是现有片段,更新列表中的对应项 - if (shotId) { + if (selectedSegment) { setVideoSegments(prev => prev.map(segment => - segment.id === shotId ? regeneratedSegment : segment + segment.id === selectedSegment.id ? regeneratedSegment : segment ) ); } else { @@ -126,7 +126,7 @@ export const useShotService = (): UseShotService => { setLoading(false); } }, - [vidoEditUseCase] + [projectId, selectedSegment, vidoEditUseCase] ); /** diff --git a/app/service/adapter/oldErrAdapter.ts b/app/service/adapter/oldErrAdapter.ts new file mode 100644 index 0000000..3c624a5 --- /dev/null +++ b/app/service/adapter/oldErrAdapter.ts @@ -0,0 +1,250 @@ + + /**============因协同任务开发流程没有明确管理,导致的必要的适配=================**/ + +import { VideoSegmentEntity } from "../domain/Entities"; +import { LensType, ContentItem } from "../domain/valueObject"; + +/** + * @description 视频片段后端结构 + */ +export class VideoSegmentEntityAdapter { + /** 原始文本 */ + original_text: string = ""; + /** 任务状态 */ + task_status: string = ""; + /** 任务结果 */ + task_result: Array<{ + /** 叙事目标 */ + narrative_goal: string; + /** 镜头1描述 */ + shot_1: string; + /** 镜头2描述 */ + shot_2: string; + /** 镜头3描述 */ + shot_3: string; + /** 镜头4描述 */ + shot_4: string; + /** 镜头5描述 */ + shot_5: string; + /** 镜头6描述 */ + shot_6: string; + /** 镜头7描述 */ + shot_7: string; + /** 镜头8描述 */ + shot_8: string; + /** 镜头9描述 */ + shot_9: string; + /** 镜头10描述 */ + shot_10: string; + /** 视频列表 */ + videos: Array<{ + /** 视频地址 */ + video_url: string; + }>; + }> = []; + + /** + * @description 解析shotContent,分离镜头描述和对话内容 + * @param shotContent 原始shot内容 + * @returns {description: string, dialogues: ContentItem[]} 解析后的描述和对话内容 + */ + static parseShotContent(shotContent: string): { description: string; dialogues: ContentItem[] } { + const lines = shotContent.split('\n').map(line => line.trim()).filter(line => line.length > 0); + const dialogues: ContentItem[] = []; + let descriptionLines: string[] = []; + + for (const line of lines) { + // 检查是否是对话行,格式:人物名称[CH-XX]: 对话内容 + const dialogueMatch = line.match(/^(.+?)\s*\[CH-\d+\]:\s*(.+)$/); + if (dialogueMatch) { + const roleName = dialogueMatch[1].trim(); + const content = dialogueMatch[2].trim(); + dialogues.push({ + roleName, + content + }); + } else { + // 如果不是对话行,则认为是描述内容 + descriptionLines.push(line); + } + } + + const description = descriptionLines.join('\n'); + return { description, dialogues }; + } + + /** + * @description 将后端数据结构转换为VideoSegmentEntity数组 + * @param data 后端数据结构 + * @returns VideoSegmentEntity[] 视频片段实体数组 + */ + static toVideoSegmentEntity(data: VideoSegmentEntityAdapter): VideoSegmentEntity[] { + const entities: VideoSegmentEntity[] = []; + + if (data.task_result && data.task_result.length > 0) { + // 遍历task_result中的每一项 + data.task_result.forEach((result, index) => { + // 从task_result中提取镜头信息 + const lens: LensType[] = []; + + // 处理镜头1到镜头10 + for (let i = 1; i <= 10; i++) { + const shotKey = `shot_${i}` as keyof typeof result; + const shotContent = (result as any)[shotKey] as string; + if (shotContent && shotContent.trim()) { + // 解析shotContent,分离镜头描述和对话内容 + const { description, dialogues } = this.parseShotContent(shotContent); + + // 创建镜头项,包含描述和对话内容 + const lensItem = new LensType(`shot_${i}`, description, dialogues); + lens.push(lensItem); + } + } + + // 如果没有任何镜头但有narrative_goal,将其作为镜头1 + if (lens.length === 0 && result.narrative_goal) { + const narrativeLens = new LensType("镜头1", result.narrative_goal, []); + lens.push(narrativeLens); + } + + // 提取视频URL列表 + const videoUrls: string[] = []; + if (result.videos && result.videos.length > 0) { + videoUrls.push(...result.videos.map(video => video.video_url)); + } + + // 根据task_status确定状态 + let status: 0 | 1 | 2 = 1; // 默认为已完成状态 + if (data.task_status === "INIT" || data.task_status === "IN_PROGRESS") { + status = 0; // 视频加载中(INIT和IN_PROGRESS都当成进行中) + } else if (data.task_status === "COMPLETED") { + status = 1; // 任务已完成 + } else if (data.task_status === "FAILED") { + status = 2; // 任务失败 + } + + // 创建VideoSegmentEntity + const entity: VideoSegmentEntity = { + id: `video_mock_${index}`, // 生成临时ID,包含索引 + updatedAt: Date.now(), + loadingProgress: status === 1 ? 100 : status === 0 ? 50 : 0, // 已完成100%,进行中50%,失败0% + disableEdit: false, + name: `视频片段_${index}`, // 生成临时名称,包含索引 + sketchUrl: "", // 后端数据中没有sketchUrl,设为空字符串 + videoUrl: videoUrls, + status: status, + lens: lens + }; + + entities.push(entity); + }); + } + + return entities; + } + + /** + * @description 将VideoSegmentEntity数组转换为后端数据结构 + * @param entities 视频片段实体数组 + * @returns VideoSegmentEntityAdapter 后端数据结构 + */ + static fromVideoSegmentEntity(entities: VideoSegmentEntity[]): VideoSegmentEntityAdapter { + const taskResults: Array<{ + narrative_goal: string; + shot_1: string; + shot_2: string; + shot_3: string; + shot_4: string; + shot_5: string; + shot_6: string; + shot_7: string; + shot_8: string; + shot_9: string; + shot_10: string; + videos: Array<{ video_url: string }>; + }> = []; + + // 遍历每个实体,转换为task_result项 + entities.forEach(entity => { + // 从lens中提取镜头描述(支持镜头1到镜头10) + const shots: { [key: string]: string } = {}; + for (let i = 1; i <= 10; i++) { + const lensItem = entity.lens.find(lens => lens.name === `shot_${i}`); + if (lensItem) { + // 重新组合镜头描述和对话内容 + let fullContent = lensItem.script; + + // 如果有对话内容,添加到镜头描述后面 + if (lensItem.content && lensItem.content.length > 0) { + const dialogueLines = lensItem.content.map(dialogue => + `${dialogue.roleName} [CH-01]: ${dialogue.content}` + ); + fullContent += '\n' + dialogueLines.join('\n'); + } + + shots[`shot_${i}`] = fullContent; + } else { + shots[`shot_${i}`] = ""; + } + } + + // 如果有更多镜头,可以合并到narrative_goal中 + let narrative_goal = ""; + const additionalLenses = entity.lens + .filter(lens => !lens.name.match(/^shot_[1-9]$|^shot_10$/)) + .map(lens => `${lens.name}: ${lens.script}`) + .join("; "); + + if (additionalLenses) { + narrative_goal = additionalLenses; + } + + // 构建videos数组 + const videos = entity.videoUrl.map(url => ({ + video_url: url + })); + + taskResults.push({ + narrative_goal: narrative_goal, + shot_1: shots.shot_1 || "", + shot_2: shots.shot_2 || "", + shot_3: shots.shot_3 || "", + shot_4: shots.shot_4 || "", + shot_5: shots.shot_5 || "", + shot_6: shots.shot_6 || "", + shot_7: shots.shot_7 || "", + shot_8: shots.shot_8 || "", + shot_9: shots.shot_9 || "", + shot_10: shots.shot_10 || "", + videos: videos + }); + }); + + // 根据第一个实体的status确定task_status(如果数组为空,默认为COMPLETED) + let task_status: string = "COMPLETED"; + if (entities.length > 0) { + const firstEntity = entities[0]; + if (firstEntity.status === 0) { + task_status = "IN_PROGRESS"; // 视频加载中映射为IN_PROGRESS + } else if (firstEntity.status === 1) { + task_status = "COMPLETED"; // 任务已完成 + } else if (firstEntity.status === 2) { + task_status = "FAILED"; // 任务失败 + } + } + + // 创建VideoSegmentEntityAdapter + const adapter = new VideoSegmentEntityAdapter(); + adapter.original_text = ""; // 实体中没有original_text,设为空字符串 + adapter.task_status = task_status; + adapter.task_result = taskResults; + + return adapter; + } +} + +/**视频片段基础数据 */ +export interface TaskSketch { + url: string; + video_id: string; +} diff --git a/app/service/domain/Entities.ts b/app/service/domain/Entities.ts index 8d99bf8..ee17ac7 100644 --- a/app/service/domain/Entities.ts +++ b/app/service/domain/Entities.ts @@ -66,7 +66,7 @@ export interface VideoSegmentEntity extends BaseEntity { sketchUrl: string; /**视频片段视频Url */ videoUrl: string[]; - /**视频片段状态 0:草稿加载中 1:视频加载中 2:完成 */ + /**视频片段状态 0:视频加载中 1:任务已完成 2:任务失败 */ status: 0 | 1 | 2; /**镜头项 */ lens: LensType[]; diff --git a/app/service/domain/service.ts b/app/service/domain/service.ts index 8b9a3c4..05f0007 100644 --- a/app/service/domain/service.ts +++ b/app/service/domain/service.ts @@ -25,3 +25,6 @@ export function parseScriptBlock( }; } + + + diff --git a/app/service/usecase/RoleEditUseCase.ts b/app/service/usecase/RoleEditUseCase.ts index cb2898f..3a74d03 100644 --- a/app/service/usecase/RoleEditUseCase.ts +++ b/app/service/usecase/RoleEditUseCase.ts @@ -1,5 +1,4 @@ import { RoleEntity } from '../domain/Entities'; -import { TagValueObject } from '../domain/valueObject'; import { applyRoleToShots, getRoleList, @@ -7,18 +6,20 @@ import { getRoleData, regenerateRole, getRoleShots, - replaceRole + replaceRole, + optimizeRoleDescription } from '@/api/video_flow'; -import { TagEditUseCase } from './TagEditUseCase'; /** * 角色图编辑用例 * 负责角色图内容的初始化、修改和优化 */ export class RoleEditUseCase { + /** 角色列表 */ roleList: RoleEntity[] = []; + /** 当前选中的角色 */ selectedRole: RoleEntity | null = null; - selectedRoleTags:TagEditUseCase = new TagEditUseCase([]); + /** 角色库列表 */ roleLibraryList: RoleEntity[] = []; constructor() { } @@ -59,15 +60,6 @@ export class RoleEditUseCase { } } - /** - * 修改某个标签内容 - * @param tagName 标签名称 - * @param newContent 新内容 - */ - async updateTag(tagName: string, newContent: string | number): Promise { - await this.selectedRoleTags.updateTag(tagName, newContent); - } - /** * 选中某个角色作为当前活跃角色 * @param roleId 角色ID @@ -78,12 +70,10 @@ export class RoleEditUseCase { const roleEntity = this.roleList.find(role => role.id === roleId); if (roleEntity) { this.selectedRole = roleEntity; - // 获取角色数据以获取标签信息 + // 获取角色数据 const response = await getRoleData({ roleId }); - if (response.successful) { - this.selectedRoleTags = new TagEditUseCase(response.data.tags); - } else { - throw new Error(response.message || '获取角色标签数据失败'); + if (!response.successful) { + throw new Error(response.message || '获取角色数据失败'); } } else { throw new Error('未找到对应的角色实体,请先获取角色列表'); @@ -92,21 +82,19 @@ export class RoleEditUseCase { console.error('选择角色失败:', error); throw error; } - } /** * 重新生成角色 * @param prompt 角色提示词 - * @param tags 标签列表 * @returns Promise 重新生成的角色 */ - async AIgenerateRole(prompt: string, tags: TagValueObject[]): Promise { + async AIgenerateRole(prompt: string): Promise { try { // 直接使用当前角色的ID,不做任何处理 const response = await regenerateRole({ prompt, - tagTypes: tags, // 直接传递完整的标签列表给后端,让后端处理 + tagTypes: [], // 标签现在只是文本的一部分,传递空数组 roleId: this.selectedRole?.id }); @@ -188,4 +176,52 @@ export class RoleEditUseCase { } } + /** + * @description: AI优化当前角色描述 + * @param userSuggestion 用户优化建议 + * @param roleDescription 角色描述文本 + * @returns Promise 优化后的角色描述 + */ + async optimizeRoleDescription(userSuggestion: string, roleDescription: string): Promise { + try { + if (!this.selectedRole) { + throw new Error('请先选择角色'); + } + + // 调用AI优化角色描述API + const response = await optimizeRoleDescription({ + roleId: this.selectedRole.id, + userSuggestion, + roleDescription, + }); + + if (!response.successful) { + throw new Error(response.message || 'AI优化角色描述失败'); + } + + const optimizedDescription = response.data.optimizedDescription; + + // 更新角色列表中的对应角色描述 + const roleIndex = this.roleList.findIndex(role => role.id === this.selectedRole?.id); + if (roleIndex !== -1) { + this.roleList[roleIndex].generateText = optimizedDescription; + } + + return optimizedDescription; + } catch (error) { + console.error('AI优化角色描述失败:', error); + throw error; + } + } + + /** + * @description: 从AI文本描述中解析标签信息(预留函数,未来实现) + * @param aiText AI文本描述 + * @returns Promise 解析出的标签列表 + */ + async parseTagsFromAiText(aiText: string): Promise { + // TODO: 未来实现从AI文本中解析标签的逻辑 + // 例如:解析文本中的关键词、特征描述等作为标签 + return []; + } } diff --git a/app/service/usecase/ScriptEditUseCase.ts b/app/service/usecase/ScriptEditUseCase.ts index 88cff54..dc5b5b0 100644 --- a/app/service/usecase/ScriptEditUseCase.ts +++ b/app/service/usecase/ScriptEditUseCase.ts @@ -5,6 +5,8 @@ import { createMovieProjectV1, saveScript, enhanceScriptStream, + pauseMovieProjectPlan, + resumeMovieProjectPlan, } from "@/api/video_flow"; export type ScriptEditKey = 'synopsis' | 'categories' | 'protagonist' | 'incitingIncident' | 'problem' | 'conflict' | 'stakes' | 'characterArc'; @@ -262,4 +264,59 @@ export class ScriptEditUseCase { // 更新剧本 this.scriptValueObject = new ScriptValueObject(newScriptText); } + + /** + * @description: 暂停电影项目计划 + * @param projectId 项目ID + * @returns Promise + */ + async pauseProject(projectId: string): Promise { + try { + this.loading = true; + + // 调用暂停项目API + const response = await pauseMovieProjectPlan({ + project_id: projectId, + }); + + if (!response.successful) { + throw new Error(response.message || "暂停项目失败"); + } + + console.log("项目暂停成功:", response.data.message); + } catch (error) { + console.error("暂停项目失败:", error); + throw error; + } finally { + this.loading = false; + } + } + + /** + * @description: 继续电影项目计划 + * @param projectId 项目ID + * @returns Promise + */ + async resumeProject(projectId: string): Promise { + try { + this.loading = true; + + // 调用继续项目API + const response = await resumeMovieProjectPlan({ + project_id: projectId, + }); + + if (!response.successful) { + throw new Error(response.message || "继续项目失败"); + } + + console.log("项目继续成功:", response.data.message); + } catch (error) { + console.error("继续项目失败:", error); + throw error; + } finally { + this.loading = false; + } + } + } diff --git a/app/service/usecase/ShotEditUsecase.ts b/app/service/usecase/ShotEditUsecase.ts index f604fd1..bf6f12a 100644 --- a/app/service/usecase/ShotEditUsecase.ts +++ b/app/service/usecase/ShotEditUsecase.ts @@ -1,9 +1,11 @@ +import { VideoSegmentEntityAdapter } from "../adapter/oldErrAdapter"; import { VideoSegmentEntity } from "../domain/Entities"; import { LensType } from "../domain/valueObject"; import { getShotList, regenerateShot, optimizeShotContent, + updateShotPrompt, } from "@/api/video_flow"; /** @@ -22,13 +24,13 @@ export class VideoSegmentEditUseCase { try { this.loading = true; - const response = await getShotList({ projectId }); + const response = await getShotList({ project_id: projectId }); if (!response.successful) { throw new Error(response.message || "获取视频片段列表失败"); } - return response.data || []; + return VideoSegmentEntityAdapter.toVideoSegmentEntity(response.data) || []; } catch (error) { console.error("获取视频片段列表失败:", error); throw error; @@ -37,26 +39,68 @@ export class VideoSegmentEditUseCase { } } + /** + * @description 保存分镜提示词数据 + * @param project_id 项目ID + * @param shot_id 分镜ID + * @param shot_descriptions 镜头描述数据 + * @returns Promise 保存结果 + */ + async saveShotPrompt( + project_id: string, + shot_id: string, + shot_descriptions: LensType[] + ): Promise { + try { + this.loading = true; + + const response = await updateShotPrompt({ + project_id, + shot_id, + shot_descriptions, + }); + + if (!response.successful) { + throw new Error(response.message || "保存分镜提示词数据失败"); + } + + return response.data; + } catch (error) { + console.error("保存分镜提示词数据失败:", error); + throw error; + } finally { + this.loading = false; + } + } + /** * @description 通过视频镜头描述数据重新生成视频 - * @param shotPrompt 镜头描述数据 - * @param shotId 视频片段ID(可选,如果重新生成现有片段) + * @param project_id 项目ID + * @param shot_descriptions 镜头描述数据 + * @param shot_id 视频片段ID(可选,如果重新生成现有片段) * @param roleReplaceParams 角色替换参数(可选) * @param sceneReplaceParams 场景替换参数(可选) * @returns Promise 重新生成的视频片段 */ async regenerateVideoSegment( - shotPrompt: LensType[], - shotId?: string, + project_id: string, + shot_descriptions: LensType[], + shot_id?: string, roleReplaceParams?: { oldId: string; newId: string }[], sceneReplaceParams?: { oldId: string; newId: string }[] ): Promise { try { this.loading = true; + // 如果有shot_id,先保存分镜数据 + if (shot_id) { + await this.saveShotPrompt(project_id, shot_id, shot_descriptions); + } + const response = await regenerateShot({ - shotId, - shotPrompt, + project_id, + shot_id, + shot_descriptions, roleReplaceParams, sceneReplaceParams, }); @@ -69,6 +113,8 @@ export class VideoSegmentEditUseCase { } catch (error) { console.error("重新生成视频片段失败:", error); throw error; + } finally { + this.loading = false; } } @@ -107,7 +153,6 @@ export class VideoSegmentEditUseCase { } } - /** * @description 获取加载状态 * @returns boolean 是否正在加载 diff --git a/components/pages/work-flow/use-workflow-data.tsx b/components/pages/work-flow/use-workflow-data.tsx index 7871948..26e99d4 100644 --- a/components/pages/work-flow/use-workflow-data.tsx +++ b/components/pages/work-flow/use-workflow-data.tsx @@ -110,7 +110,7 @@ export function useWorkflowData() { }, [scriptBlocksMemo]); // 监听继续 请求更新数据 useEffect(() => { - + }, [isPauseWorkFlow]); // 自动开始播放一轮 @@ -295,6 +295,7 @@ export function useWorkflowData() { url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null, script: video.description, audio: null, + video_id: video.video_id, }); } setTaskVideos(videoList); @@ -507,6 +508,7 @@ export function useWorkflowData() { url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null, script: video.description, audio: null, + video_id: video.video_id, }); } setTaskVideos(videoList); @@ -534,7 +536,7 @@ export function useWorkflowData() { finalStep = '5.5'; loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...'); } - + if (data.final_video && data.final_video.video) { setFinal({ url: data.final_video.video @@ -617,4 +619,4 @@ export function useWorkflowData() { setAnyAttribute, applyScript }; -} \ No newline at end of file +}