更新角色和场景服务Hook,新增获取角色列表、角色数据、场景列表和场景数据的API接口;实现角色替换功能;优化相关状态管理和数据初始化逻辑;删除不再使用的RoleService.puml文件。

This commit is contained in:
海龙 2025-07-30 15:54:08 +08:00
parent 70b6ce5d4c
commit f9e05b3a23
7 changed files with 3736 additions and 263 deletions

View File

@ -322,6 +322,64 @@ export const getRoleShots = async (request: {
return post<ApiResponse<any>>('/movie/get_role_shots', request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<角色实体列表>>
*/
export const getRoleList = async (request: {
/** 项目ID */
projectId: string;
}): Promise<ApiResponse<RoleEntity[]>> => {
return post<ApiResponse<any>>('/movie/get_role_list', request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<{ AI文本数据, 标签列表 }>>
*/
export const getRoleData = async (request: {
/** 角色ID */
roleId: string;
}): Promise<ApiResponse<{
/** AI文本数据 */
text: AITextEntity;
/** 标签列表 */
tags: TagEntity[];
}>> => {
return post<ApiResponse<any>>('/movie/get_role_data', request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<{ AI文本数据, 标签列表 }>>
*/
export const getSceneData = async (request: {
/** 场景ID */
sceneId: string;
}): Promise<ApiResponse<{
/** AI文本数据 */
text: AITextEntity;
/** 标签列表 */
tags: TagEntity[];
}>> => {
return post<ApiResponse<any>>('/movie/get_scene_data', request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<场景实体列表>>
*/
export const getSceneList = async (request: {
/** 项目ID */
projectId: string;
}): Promise<ApiResponse<SceneEntity[]>> => {
return post<ApiResponse<any>>('/movie/get_scene_list', request);
};
/**
*
* @param request -
@ -339,3 +397,25 @@ export const getSceneShots = async (request: {
return post<ApiResponse<any>>('/movie/get_scene_shots', request);
};
/**
*
* @returns Promise<ApiResponse<角色实体列表>>
*/
export const getUserRoleLibrary = async (): Promise<ApiResponse<RoleEntity[]>> => {
return post<ApiResponse<any>>('/movie/get_user_role_library', {});
};
/**
*
* @param request -
* @returns Promise<ApiResponse<替换结果>>
*/
export const replaceRole = async (request: {
/** 当前角色ID */
currentRoleId: string;
/** 替换的角色ID */
replaceRoleId: string;
}): Promise<ApiResponse<any>> => {
return post<ApiResponse<any>>('/movie/replace_role', request);
};

View File

@ -1,151 +0,0 @@
@startuml RoleService Hook 架构图
!theme plain
skinparam backgroundColor #FFFFFF
skinparam componentStyle rectangle
' 主要模块
package "RoleService Hook" as RoleServiceHook {
component "响应式状态管理" as StateManagement
component "计算属性" as ComputedProps
component "角色操作方法" as RoleOperations
component "文本操作方法" as TextOperations
component "标签操作方法" as TagOperations
component "分镜操作方法" as ShotOperations
}
' API层
package "API接口层" as APILayer {
component "角色相关API" as RoleAPI {
[regenerateRole()]
[applyRoleToShots()]
[getRoleShots()]
}
component "文本相关API" as TextAPI {
[updateText()]
}
component "标签相关API" as TagAPI {
[updateTag()]
}
}
' UseCase层
package "UseCase层" as UseCaseLayer {
component "RoleEditUseCase" as RoleEditUseCase {
[AIgenerateRole(prompt, tags)]
[applyRole(shotIds)]
}
component "TextEditUseCase" as TextEditUseCase {
[updateText(content)]
[getOptimizedContent()]
}
component "TagEditUseCase" as TagEditUseCase {
[updateTag(content)]
}
}
' Domain层
package "Domain层" as DomainLayer {
component "实体定义" as Entities {
[RoleEntity]
[AITextEntity]
[TagEntity]
[ShotEntity]
}
component "可编辑项" as Items {
[RoleItem]
[TextItem]
[TagItem]
[ShotItem]
}
}
' React Hook
package "React Hook" as ReactHook {
component "useState" as UseState
component "useCallback" as UseCallback
component "useMemo" as UseMemo
}
' 依赖关系
' Hook内部依赖
RoleServiceHook --> StateManagement : 管理状态
RoleServiceHook --> ComputedProps : 计算属性
RoleServiceHook --> RoleOperations : 角色操作
RoleServiceHook --> TextOperations : 文本操作
RoleServiceHook --> TagOperations : 标签操作
RoleServiceHook --> ShotOperations : 分镜操作
' 操作方法依赖UseCase
RoleOperations --> RoleEditUseCase : 调用
TextOperations --> TextEditUseCase : 调用
TagOperations --> TagEditUseCase : 调用
ShotOperations --> RoleEditUseCase : 调用
' UseCase依赖API
RoleEditUseCase --> RoleAPI : 调用
TextEditUseCase --> TextAPI : 调用
TagEditUseCase --> TagAPI : 调用
' 状态管理依赖Domain
StateManagement --> Items : 使用
ComputedProps --> Entities : 计算
RoleOperations --> Items : 操作
TextOperations --> Items : 操作
TagOperations --> Items : 操作
ShotOperations --> Items : 操作
' React Hook依赖
RoleServiceHook --> UseState : 状态管理
RoleServiceHook --> UseCallback : 方法优化
RoleServiceHook --> UseMemo : 计算优化
' 数据流
note right of StateManagement
响应式状态:
- roleList: 角色列表
- selectedRole: 当前选中角色
- currentRoleText: 当前AI文本
- currentRoleTags: 当前标签列表
- shotSelectionList: 分镜选择列表
end note
note right of ComputedProps
计算属性:
- roleImageUrl: 角色图片URL
- isAllShotsSelected: 是否全选
- selectedShotsCount: 选中数量
end note
note right of RoleOperations
角色操作:
- selectRole: 选择角色
- regenerateRole: 重新生成
end note
note right of TextOperations
文本操作:
- optimizeRoleText: 优化文本
- updateRoleText: 修改文本
end note
note right of TagOperations
标签操作:
- updateTagContent: 修改标签
end note
note right of ShotOperations
分镜操作:
- fetchRoleShots: 获取分镜列表
- selectAllShots: 全选
- invertShotSelection: 反选
- toggleShotSelection: 切换选择
- applyRoleToSelectedShots: 应用角色
end note
@enduml

View File

@ -4,7 +4,7 @@ import { RoleItem, TagItem, TextItem, ShotItem } from '../domain/Item';
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
import { TagEditUseCase } from '../usecase/TagEditUseCase';
import { TextEditUseCase } from '../usecase/TextEditUseCase';
import { getRoleShots } from '@/api/video_flow';
import { getRoleShots, getRoleData, getRoleList, getUserRoleLibrary, replaceRole } from '@/api/video_flow';
/**
*
@ -43,12 +43,15 @@ interface UseRoleService {
isAllShotsSelected: boolean;
/** 已选中的分镜数量 */
selectedShotsCount: number;
/** 用户角色库 */
userRoleLibrary: RoleItem[];
// 操作方法
/** 获取角色列表 */
fetchRoleList: (projectId: string) => Promise<void>;
/** 选择角色 */
selectRole: (roleId: string) => void;
/** 设置当前角色的AI文本和标签 */
setCurrentRoleData: (text: TextItem, tags: TagItem[]) => void;
/** 初始化当前选中角色的AI文本和标签数据 */
initializeRoleData: () => Promise<void>;
/** 优化AI文本 */
optimizeRoleText: () => Promise<void>;
/** 修改AI文本 */
@ -65,6 +68,11 @@ interface UseRoleService {
toggleShotSelection: (shotId: string) => void;
/** 应用角色到选中的分镜 */
applyRoleToSelectedShots: () => Promise<void>;
/** 获取用户角色库 */
fetchUserRoleLibrary: () => Promise<void>;
/** 替换角色 */
replaceRoleWithLibrary: (replaceRoleId: string) => Promise<void>;
}
/**
@ -78,6 +86,7 @@ export const useRoleServiceHook = (): UseRoleService => {
const [currentRoleText, setCurrentRoleText] = useState<TextItem | null>(null);
const [currentRoleTags, setCurrentRoleTags] = useState<TagItem[]>([]);
const [shotSelectionList, setShotSelectionList] = useState<ShotSelectionItem[]>([]);
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleItem[]>([]);
// UseCase实例 - 在角色选择时初始化
const [roleEditUseCase, setRoleEditUseCase] = useState<RoleEditUseCase | null>(null);
@ -109,26 +118,98 @@ export const useRoleServiceHook = (): UseRoleService => {
return shotSelectionList.filter(shot => shot.selected).length;
}, [shotSelectionList]);
/**
*
* @description ID获取所有角色列表
* @param projectId ID
* @throws {Error} API调用失败时抛出错误
* @returns {Promise<void>} Promise
*/
const fetchRoleList = useCallback(async (projectId: string) => {
try {
const response = await getRoleList({
projectId: projectId
});
if (response.successful) {
const roleItems = response.data.map(role => new RoleItem(role));
setRoleList(roleItems);
} else {
throw new Error(`获取角色列表失败: ${response.message}`);
}
} catch (error) {
console.error('获取角色列表失败:', error);
throw error;
}
}, []);
/**
*
* @description ID选择角色UseCase实例
* @param roleId ID
*/
const selectRole = useCallback((roleId: string) => {
const selectRole = useCallback(async (roleId: string) => {
const role = roleList.find(r => r.entity.id === roleId);
if (role) {
setSelectedRole(role);
// 初始化UseCase实例
// 初始化角色编辑UseCase实例
setRoleEditUseCase(new RoleEditUseCase(role));
setTextEditUseCase(null); // 文本UseCase在获取到文本后初始化
setTagEditUseCases(new Map()); // 标签UseCase在获取到标签后初始化
// 清空文本和标签相关状态
setTextEditUseCase(null);
setTagEditUseCases(new Map());
setCurrentRoleText(null);
setCurrentRoleTags([]);
await initializeRoleData();
}
}, [roleList]);
/**
*
* @description AI文本和标签数据
* @throws {Error} API调用失败时抛出错误
* @returns {Promise<void>} Promise
*/
const initializeRoleData = useCallback(async () => {
if (!selectedRole) {
throw new Error('请先选择角色');
}
try {
const response = await getRoleData({
roleId: selectedRole.entity.id
});
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);
} else {
throw new Error(`获取角色数据失败: ${response.message}`);
}
} catch (error) {
console.error('初始化角色数据失败:', error);
throw error;
}
}, [selectedRole]);
/**
* AI文本
* @description AI文本进行优化
@ -325,27 +406,62 @@ export const useRoleServiceHook = (): UseRoleService => {
}, [roleEditUseCase, selectedRole, shotSelectionList]);
/**
* AI文本和标签
* @description AI文本和标签UseCase
* @param text AI文本项
* @param tags
*
* @description
* @throws {Error} API调用失败时抛出错误
* @returns {Promise<void>} Promise
*/
const setCurrentRoleData = useCallback((text: TextItem, tags: TagItem[]) => {
setCurrentRoleText(text);
setCurrentRoleTags(tags);
const fetchUserRoleLibrary = useCallback(async () => {
try {
const response = await getUserRoleLibrary();
// 初始化文本UseCase
if (text) {
setTextEditUseCase(new TextEditUseCase(text));
if (response.successful) {
const roleItems = response.data.map(role => new RoleItem(role));
setUserRoleLibrary(roleItems);
} else {
throw new Error(`获取用户角色库失败: ${response.message}`);
}
} catch (error) {
console.error('获取用户角色库失败:', error);
throw error;
}
}, []);
/**
*
* @description 使
* @param replaceRoleId ID
* @throws {Error} API调用失败或UseCase未初始化时抛出错误
* @returns {Promise<void>} Promise
*/
const replaceRoleWithLibrary = useCallback(async (replaceRoleId: string) => {
if (!selectedRole) {
throw new Error('请先选择角色');
}
// 初始化标签UseCase
const newTagEditUseCases = new Map<string, TagEditUseCase>();
tags.forEach(tag => {
newTagEditUseCases.set(tag.entity.id, new TagEditUseCase(tag));
});
setTagEditUseCases(newTagEditUseCases);
}, []);
if (!roleEditUseCase) {
throw new Error('角色编辑UseCase未初始化');
}
try {
const response = await replaceRole({
currentRoleId: selectedRole.entity.id,
replaceRoleId: replaceRoleId
});
if (response.successful) {
// 重新获取当前角色的详细数据
await initializeRoleData();
} else {
throw new Error(`替换角色失败: ${response.message}`);
}
} catch (error) {
console.error('替换角色失败:', error);
throw error;
}
}, [selectedRole, roleEditUseCase, initializeRoleData]);
return {
// 响应式数据
@ -365,12 +481,15 @@ export const useRoleServiceHook = (): UseRoleService => {
isAllShotsSelected,
/** 已选中的分镜数量 */
selectedShotsCount,
/** 用户角色库 */
userRoleLibrary,
// 操作方法
/** 获取角色列表 */
fetchRoleList,
/** 选择角色 */
selectRole,
/** 设置当前角色的AI文本和标签 */
setCurrentRoleData,
/** 初始化当前选中角色的AI文本和标签数据 */
initializeRoleData,
/** 优化AI文本 */
optimizeRoleText,
/** 修改AI文本 */
@ -386,6 +505,11 @@ export const useRoleServiceHook = (): UseRoleService => {
/** 选择/取消选择单个分镜 */
toggleShotSelection,
/** 应用角色到选中的分镜 */
applyRoleToSelectedShots
applyRoleToSelectedShots,
/** 获取用户角色库 */
fetchUserRoleLibrary,
/** 替换角色 */
replaceRoleWithLibrary,
};
};

View File

@ -4,7 +4,7 @@ import { SceneItem, TagItem, TextItem, ShotItem } from '../domain/Item';
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
import { TagEditUseCase } from '../usecase/TagEditUseCase';
import { TextEditUseCase } from '../usecase/TextEditUseCase';
import { getSceneShots } from '@/api/video_flow';
import { getSceneShots, getSceneData, getSceneList } from '@/api/video_flow';
/**
*
@ -45,10 +45,12 @@ interface UseSceneService {
selectedShotsCount: number;
// 操作方法
/** 获取场景列表 */
fetchSceneList: (projectId: string) => Promise<void>;
/** 选择场景 */
selectScene: (sceneId: string) => void;
/** 设置当前场景的AI文本和标签 */
setCurrentSceneData: (text: TextItem, tags: TagItem[]) => void;
/** 初始化当前选中场景的AI文本和标签数据 */
initializeSceneData: () => Promise<void>;
/** 优化AI文本 */
optimizeSceneText: () => Promise<void>;
/** 修改AI文本 */
@ -85,48 +87,126 @@ export const useSceneServiceHook = (): UseSceneService => {
const [tagEditUseCases, setTagEditUseCases] = useState<Map<string, TagEditUseCase>>(new Map());
// 计算属性
/**
* URL
* @description URL
*/
const sceneImageUrl = useMemo(() => {
return selectedScene?.entity.imageUrl || '';
}, [selectedScene]);
/**
*
* @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]);
// 选择场景
const selectScene = useCallback((sceneId: string) => {
/**
*
* @description ID获取所有场景列表
* @param projectId ID
* @throws {Error} API调用失败时抛出错误
* @returns {Promise<void>} Promise
*/
const fetchSceneList = useCallback(async (projectId: string) => {
try {
const response = await getSceneList({
projectId: projectId
});
if (response.successful) {
const sceneItems = response.data.map(scene => new SceneItem(scene));
setSceneList(sceneItems);
} else {
throw new Error(`获取场景列表失败: ${response.message}`);
}
} catch (error) {
console.error('获取场景列表失败:', error);
throw error;
}
}, []);
/**
*
* @description ID选择场景UseCase实例
* @param sceneId ID
*/
const selectScene = useCallback(async (sceneId: string) => {
const scene = sceneList.find(s => s.entity.id === sceneId);
if (scene) {
setSelectedScene(scene);
// 初始化场景编辑UseCase实例
setSceneEditUseCase(new SceneEditUseCase(scene));
// 清空文本和标签相关状态
setTextEditUseCase(null);
setTagEditUseCases(new Map());
setCurrentSceneText(null);
setCurrentSceneTags([]);
await initializeSceneData();
}
}, [sceneList]);
// 设置当前场景数据
const setCurrentSceneData = useCallback((text: TextItem, tags: TagItem[]) => {
setCurrentSceneText(text);
setCurrentSceneTags(tags);
if (text) {
setTextEditUseCase(new TextEditUseCase(text));
/**
*
* @description AI文本和标签数据
* @throws {Error} API调用失败时抛出错误
* @returns {Promise<void>} Promise
*/
const initializeSceneData = useCallback(async () => {
if (!selectedScene) {
throw new Error('请先选择场景');
}
const newTagEditUseCases = new Map<string, TagEditUseCase>();
tags.forEach(tag => {
newTagEditUseCases.set(tag.entity.id, new TagEditUseCase(tag));
});
setTagEditUseCases(newTagEditUseCases);
}, []);
try {
const response = await getSceneData({
sceneId: selectedScene.entity.id
});
// 优化AI文本
if (response.successful) {
const { text, tags } = response.data;
const textItem = new TextItem(text);
const tagItems = tags.map(tag => new TagItem(tag));
// 设置当前场景的AI文本和标签
setCurrentSceneText(textItem);
setCurrentSceneTags(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);
} else {
throw new Error(`获取场景数据失败: ${response.message}`);
}
} catch (error) {
console.error('初始化场景数据失败:', error);
throw error;
}
}, [selectedScene]);
/**
* AI文本
* @description AI文本进行优化
* @throws {Error} UseCase未初始化时抛出错误
* @returns {Promise<void>} Promise
*/
const optimizeSceneText = useCallback(async () => {
if (!textEditUseCase) {
throw new Error('文本编辑UseCase未初始化');
@ -141,7 +221,13 @@ export const useSceneServiceHook = (): UseSceneService => {
setCurrentSceneText(updatedTextItem);
}, [textEditUseCase, currentSceneText]);
// 修改AI文本
/**
* AI文本
* @description AI文本内容
* @param newContent
* @throws {Error} UseCase未初始化时抛出错误
* @returns {Promise<void>} Promise
*/
const updateSceneText = useCallback(async (newContent: string) => {
if (!textEditUseCase) {
throw new Error('文本编辑UseCase未初始化');
@ -155,7 +241,14 @@ export const useSceneServiceHook = (): UseSceneService => {
setCurrentSceneText(updatedTextItem);
}, [textEditUseCase, currentSceneText]);
// 修改标签内容
/**
*
* @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) {
@ -173,7 +266,12 @@ export const useSceneServiceHook = (): UseSceneService => {
);
}, [tagEditUseCases]);
// 重新生成场景
/**
*
* @description 使AI文本和标签重新生成场景
* @throws {Error} UseCase未初始化时抛出错误
* @returns {Promise<void>} Promise
*/
const regenerateScene = useCallback(async () => {
if (!sceneEditUseCase) {
throw new Error('场景编辑UseCase未初始化');
@ -195,7 +293,12 @@ export const useSceneServiceHook = (): UseSceneService => {
);
}, [sceneEditUseCase, selectedScene, currentSceneText, currentSceneTags]);
// 获取场景分镜列表
/**
*
* @description
* @throws {Error} API调用失败时抛出错误
* @returns {Promise<void>} Promise
*/
const fetchSceneShots = useCallback(async () => {
if (!selectedScene) {
throw new Error('请先选择场景');
@ -227,7 +330,10 @@ export const useSceneServiceHook = (): UseSceneService => {
}
}, [selectedScene]);
// 切换全选与全不选
/**
*
* @description
*/
const toggleSelectAllShots = useCallback(() => {
setShotSelectionList(prev => {
const isAllSelected = prev.length > 0 && prev.every(shot => shot.selected);
@ -235,7 +341,11 @@ export const useSceneServiceHook = (): UseSceneService => {
});
}, []);
// 选择/取消选择单个分镜
/**
* /
* @description
* @param shotId ID
*/
const toggleShotSelection = useCallback((shotId: string) => {
setShotSelectionList(prev =>
prev.map(shot =>
@ -246,7 +356,12 @@ export const useSceneServiceHook = (): UseSceneService => {
);
}, []);
// 应用场景到选中的分镜
/**
*
* @description
* @throws {Error} UseCase未初始化时抛出错误
* @returns {Promise<void>} Promise
*/
const applySceneToSelectedShots = useCallback(async () => {
if (!sceneEditUseCase) {
throw new Error('场景编辑UseCase未初始化');
@ -287,15 +402,27 @@ export const useSceneServiceHook = (): UseSceneService => {
selectedShotsCount,
// 操作方法
/** 获取场景列表 */
fetchSceneList,
/** 选择场景 */
selectScene,
setCurrentSceneData,
/** 初始化当前选中场景的AI文本和标签数据 */
initializeSceneData,
/** 优化AI文本 */
optimizeSceneText,
/** 修改AI文本 */
updateSceneText,
/** 修改标签内容 */
updateTagContent,
/** 重新生成场景 */
regenerateScene,
/** 获取场景出现的分镜列表 */
fetchSceneShots,
/** 切换全选与全不选 */
toggleSelectAllShots,
/** 选择/取消选择单个分镜 */
toggleShotSelection,
/** 应用场景到选中的分镜 */
applySceneToSelectedShots
};
};

View File

@ -16,7 +16,6 @@ export interface BaseEntity {
loadingProgress: number;
/** 禁止编辑 */
disableEdit: boolean;
}
/**
@ -27,7 +26,6 @@ export interface AITextEntity extends BaseEntity {
content: string;
}
/**
*
*/
@ -40,6 +38,8 @@ export interface RoleEntity extends BaseEntity {
tagIds: string[];
/** 角色图片URL */
imageUrl: string;
/** 角色是否已存储 */
isStored: boolean;
}
/**
@ -66,8 +66,6 @@ export interface SceneEntity extends BaseEntity {
generateTextId: string;
}
interface RoleMap {
/** 角色ID */
roleId: string;

3394
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -106,7 +106,10 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.19",
"@types/react-grid-layout": "^1.3.5"
"@types/react-grid-layout": "^1.3.5",
"jest": "^30.0.5",
"ts-jest": "^29.4.0"
}
}