数据结构修改

This commit is contained in:
海龙 2025-08-09 17:25:16 +08:00
parent 46bda04605
commit ce3497003a
12 changed files with 842 additions and 390 deletions

View File

@ -14,6 +14,7 @@ import {
LensType,
ScriptSlice,
} from "@/app/service/domain/valueObject";
import { VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter";
// API 响应类型
interface BaseApiResponse<T> {
@ -505,16 +506,18 @@ export const getShotData = async (request: {
* @returns Promise<ApiResponse<重新生成的分镜>>
*/
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<ApiResponse<VideoSegmentEntity>> => {
return post<ApiResponse<any>>("/movie/regenerate_shot", request);
return post<ApiResponse<any>>("/movie/regenerate_shot_video", request);
};
@ -525,9 +528,9 @@ export const regenerateShot = async (request: {
*/
export const getShotList = async (request: {
/** 项目ID */
projectId: string;
}): Promise<ApiResponse<VideoSegmentEntity[]>> => {
return post<ApiResponse<any>>("/movie/get_shot_list", request);
project_id: string;
}): Promise<ApiResponse<VideoSegmentEntityAdapter>> => {
return post<ApiResponse<any>>("/movie_cut/get_task_list", request);
};
/**
@ -742,3 +745,72 @@ export const optimizeShotContent = async (request: {
}): Promise<ApiResponse<LensType[]>> => {
return post<ApiResponse<LensType[]>>("/api/v1/shot/optimize_content", request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<暂停结果>>
*/
export const pauseMovieProjectPlan = async (request: {
/** 项目ID */
project_id: string;
}): Promise<ApiResponse<{
/** 项目ID */
project_id: string;
/** 暂停结果描述 */
message: string;
}>> => {
return post("/movie/pause_movie_project_plan", request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<继续结果>>
*/
export const resumeMovieProjectPlan = async (request: {
/** 项目ID */
project_id: string;
}): Promise<ApiResponse<{
/** 项目ID */
project_id: string;
/** 继续结果描述 */
message: string;
}>> => {
return post("/movie/resume_movie_project_plan", request);
};
/**
* AI优化角色描述
* @param request - AI优化角色描述请求参数
* @returns Promise<ApiResponse<优化后的角色描述>>
*/
export const optimizeRoleDescription = async (request: {
/** 角色ID */
roleId: string;
/** 用户优化建议 */
userSuggestion: string;
/** 角色描述文本 */
roleDescription: string;
}): Promise<ApiResponse<{
/** 优化后的角色描述 */
optimizedDescription: string;
}>> => {
return post("/movie/optimize_role_description", request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<any>>
*/
export const updateShotPrompt = async (request: {
/** 项目ID */
project_id: string;
/** 分镜ID */
shot_id: string;
/** 镜头描述 */
shot_descriptions: LensType[];
}): Promise<ApiResponse<any>> => {
return post("/movie/update_shot_prompt", request);
};

View File

@ -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<void>;
/** 选择角色 */
selectRole: (roleId: string) => void;
/** 初始化当前选中角色的AI文本和标签数据 */
initializeRoleData: () => Promise<void>;
selectRole: (roleId: string) => Promise<void>;
/** 优化AI文本 */
optimizeRoleText: () => Promise<void>;
optimizeRoleText: (userSuggestion: string) => Promise<void>;
/** 修改AI文本 */
updateRoleText: (newContent: string) => Promise<void>;
/** 修改标签内容 */
updateTagContent: (tagId: string, newContent: string | number) => Promise<void>;
/** 重新生成角色 */
regenerateRole: () => Promise<void>;
/** 获取角色出现的分镜列表 */
fetchRoleShots: () => Promise<void>;
/** 切换全选与全不选 */
toggleSelectAllShots: () => void;
/** 选择/取消选择单个分镜 */
toggleShotSelection: (shotId: string) => void;
/** 应用角色到选中的分镜 */
applyRoleToSelectedShots: () => Promise<void>;
/** 获取用户角色库 */
fetchUserRoleLibrary: () => Promise<void>;
/** 替换角色 */
replaceRoleWithLibrary: (replaceRoleId: string) => Promise<void>;
}
/**
@ -82,17 +41,13 @@ interface UseRoleService {
*/
export const useRoleServiceHook = (): UseRoleService => {
// 响应式状态
const [roleList, setRoleList] = useState<RoleItem[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleItem | null>(null);
const [currentRoleText, setCurrentRoleText] = useState<TextItem | null>(null);
const [currentRoleTags, setCurrentRoleTags] = useState<TagItem[]>([]);
const [shotSelectionList, setShotSelectionList] = useState<ShotSelectionItem[]>([]);
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleItem[]>([]);
const [roleList, setRoleList] = useState<RoleEntity[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleEntity | null>(null);
const [currentRoleText, setCurrentRoleText] = useState<string | null>(null);
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
// UseCase实例 - 在角色选择时初始化
const [roleEditUseCase, setRoleEditUseCase] = useState<RoleEditUseCase | null>(null);
const [textEditUseCase, setTextEditUseCase] = useState<TextEditUseCase | null>(null);
const [tagEditUseCases, setTagEditUseCases] = useState<Map<string, TagEditUseCase>>(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<void>} 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<string, TagEditUseCase>();
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<void>} 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<void>} 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<void>} 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<void>} 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<void>} 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<void>} 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,
};
};

View File

@ -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<void>;
/** 切换全选与全不选 */
toggleSelectAllShots: () => void;
/** 选择/取消选择单个分镜 */
toggleShotSelection: (shotId: string) => void;
/** 应用角色到选中的分镜 */
applyRoleToSelectedShots: (roleId: string) => Promise<void>;
/** 清空选择列表 */
clearShotSelection: () => void;
}
/**
* Hook
*
*/
export const useRoleShotServiceHook = (): UseRoleShotService => {
// 响应式状态
const [shotSelectionList, setShotSelectionList] = useState<ExtendedVideoSegmentEntity[]>([]);
const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);
// UseCase实例
const [roleEditUseCase, setRoleEditUseCase] = useState<RoleEditUseCase | null>(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<void>} 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<void>} 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,
};
};

View File

@ -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';
/**

View File

@ -22,8 +22,6 @@ export interface UseShotService {
getVideoSegmentList: (projectId: string) => Promise<void>;
/** 重新生成视频片段 */
regenerateVideoSegment: (
shotPrompt: LensType[],
shotId?: string,
roleReplaceParams?: { oldId: string; newId: string }[],
sceneReplaceParams?: { oldId: string; newId: string }[]
) => Promise<VideoSegmentEntity>;
@ -55,7 +53,7 @@ export const useShotService = (): UseShotService => {
const [loading, setLoading] = useState<boolean>(false);
const [videoSegments, setVideoSegments] = useState<VideoSegmentEntity[]>([]);
const [selectedSegment, setSelectedSegment] = useState<VideoSegmentEntity | null>(null);
const [projectId, setProjectId] = useState<string>("");
// UseCase实例
const [vidoEditUseCase] = useState<VideoSegmentEditUseCase>(
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<VideoSegmentEntity> => {
@ -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]
);
/**

View File

@ -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;
}

View File

@ -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[];

View File

@ -25,3 +25,6 @@ export function parseScriptBlock(
};
}

View File

@ -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<void> {
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<RoleEntity>
*/
async AIgenerateRole(prompt: string, tags: TagValueObject[]): Promise<RoleEntity> {
async AIgenerateRole(prompt: string): Promise<RoleEntity> {
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<string>
*/
async optimizeRoleDescription(userSuggestion: string, roleDescription: string): Promise<string> {
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<string[]>
*/
async parseTagsFromAiText(aiText: string): Promise<string[]> {
// TODO: 未来实现从AI文本中解析标签的逻辑
// 例如:解析文本中的关键词、特征描述等作为标签
return [];
}
}

View File

@ -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<void>
*/
async pauseProject(projectId: string): Promise<void> {
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<void>
*/
async resumeProject(projectId: string): Promise<void> {
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;
}
}
}

View File

@ -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<any>
*/
async saveShotPrompt(
project_id: string,
shot_id: string,
shot_descriptions: LensType[]
): Promise<any> {
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<VideoSegmentEntity>
*/
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<VideoSegmentEntity> {
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

View File

@ -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
};
}
}