forked from 77media/video-flow
数据结构修改
This commit is contained in:
parent
46bda04605
commit
ce3497003a
@ -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);
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
192
app/service/Interaction/RoleShotService.ts
Normal file
192
app/service/Interaction/RoleShotService.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@ -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]
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
250
app/service/adapter/oldErrAdapter.ts
Normal file
250
app/service/adapter/oldErrAdapter.ts
Normal 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;
|
||||
}
|
||||
@ -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[];
|
||||
|
||||
@ -25,3 +25,6 @@ export function parseScriptBlock(
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 是否正在加载
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user