forked from 77media/video-flow
516 lines
16 KiB
TypeScript
516 lines
16 KiB
TypeScript
import { useState, useCallback, useMemo } from 'react';
|
||
import { RoleEntity, TagEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
||
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, getRoleData, getRoleList, getUserRoleLibrary, replaceRole } from '@/api/video_flow';
|
||
|
||
/**
|
||
* 分镜选择项接口
|
||
*/
|
||
interface ShotSelectionItem {
|
||
/** 分镜ID */
|
||
id: string;
|
||
/** 分镜名称 */
|
||
name: string;
|
||
/** 是否已选中 */
|
||
selected: boolean;
|
||
/** 是否已应用角色 */
|
||
applied: boolean;
|
||
/** 分镜数据 */
|
||
shot: VideoSegmentEntity;
|
||
}
|
||
|
||
/**
|
||
* 角色服务Hook返回值接口
|
||
*/
|
||
interface UseRoleService {
|
||
// 响应式数据
|
||
/** 角色列表 */
|
||
roleList: RoleItem[];
|
||
/** 当前选中的角色 */
|
||
selectedRole: RoleItem | null;
|
||
/** 当前角色的AI文本 */
|
||
currentRoleText: TextItem | null;
|
||
/** 当前角色的标签列表 */
|
||
currentRoleTags: TagItem[];
|
||
/** 角色图片URL */
|
||
roleImageUrl: string;
|
||
/** 分镜选择列表 */
|
||
shotSelectionList: ShotSelectionItem[];
|
||
/** 是否全选分镜 */
|
||
isAllShotsSelected: boolean;
|
||
/** 已选中的分镜数量 */
|
||
selectedShotsCount: number;
|
||
/** 用户角色库 */
|
||
userRoleLibrary: RoleItem[];
|
||
// 操作方法
|
||
/** 获取角色列表 */
|
||
fetchRoleList: (projectId: string) => Promise<void>;
|
||
/** 选择角色 */
|
||
selectRole: (roleId: string) => void;
|
||
/** 初始化当前选中角色的AI文本和标签数据 */
|
||
initializeRoleData: () => Promise<void>;
|
||
/** 优化AI文本 */
|
||
optimizeRoleText: () => 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>;
|
||
|
||
}
|
||
|
||
/**
|
||
* 角色服务Hook
|
||
* 提供角色相关的所有响应式功能和业务逻辑
|
||
*/
|
||
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[]>([]);
|
||
|
||
// UseCase实例 - 在角色选择时初始化
|
||
const [roleEditUseCase, setRoleEditUseCase] = useState<RoleEditUseCase | null>(null);
|
||
const [textEditUseCase, setTextEditUseCase] = useState<TextEditUseCase | null>(null);
|
||
const [tagEditUseCases, setTagEditUseCases] = useState<Map<string, TagEditUseCase>>(new Map());
|
||
|
||
// 计算属性
|
||
/**
|
||
* 角色图片URL
|
||
* @description 获取当前选中角色的图片URL
|
||
*/
|
||
const roleImageUrl = useMemo(() => {
|
||
return selectedRole?.entity.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获取所有角色列表
|
||
* @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(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文本和标签数据
|
||
* @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文本进行优化,无文本时不可进行优化
|
||
* @throws {Error} 当没有可优化的文本内容或UseCase未初始化时抛出错误
|
||
* @returns {Promise<void>} 优化完成后的Promise
|
||
*/
|
||
const optimizeRoleText = useCallback(async () => {
|
||
if (!textEditUseCase) {
|
||
throw new Error('文本编辑UseCase未初始化');
|
||
}
|
||
|
||
if (!currentRoleText || !currentRoleText.entity.content) {
|
||
throw new Error('没有可优化的文本内容');
|
||
}
|
||
|
||
const optimizedContent = await textEditUseCase.getOptimizedContent();
|
||
|
||
// 更新文本内容
|
||
const updatedTextItem = await textEditUseCase.updateText(optimizedContent);
|
||
setCurrentRoleText(updatedTextItem);
|
||
}, [textEditUseCase, currentRoleText]);
|
||
|
||
/**
|
||
* 修改AI文本
|
||
* @description 手动修改当前角色的AI文本内容
|
||
* @param newContent 新的文本内容
|
||
* @throws {Error} 当没有可编辑的文本或UseCase未初始化时抛出错误
|
||
* @returns {Promise<void>} 修改完成后的Promise
|
||
*/
|
||
const updateRoleText = useCallback(async (newContent: string) => {
|
||
if (!textEditUseCase) {
|
||
throw new Error('文本编辑UseCase未初始化');
|
||
}
|
||
|
||
if (!currentRoleText) {
|
||
throw new Error('没有可编辑的文本');
|
||
}
|
||
|
||
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
|
||
)
|
||
);
|
||
}, [tagEditUseCases]);
|
||
|
||
/**
|
||
* 重新生成角色
|
||
* @description 使用AI文本和标签重新生成角色
|
||
* @throws {Error} 当缺少重新生成角色所需的数据或UseCase未初始化时抛出错误
|
||
* @returns {Promise<void>} 重新生成完成后的Promise
|
||
*/
|
||
const regenerateRole = useCallback(async () => {
|
||
if (!roleEditUseCase) {
|
||
throw new Error('角色编辑UseCase未初始化');
|
||
}
|
||
|
||
if (!selectedRole || !currentRoleText || currentRoleTags.length === 0) {
|
||
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
|
||
});
|
||
|
||
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}`);
|
||
}
|
||
} catch (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]);
|
||
|
||
/**
|
||
* 获取用户角色库
|
||
* @description 获取当前用户的角色库列表
|
||
* @throws {Error} 当API调用失败时抛出错误
|
||
* @returns {Promise<void>} 获取完成后的Promise
|
||
*/
|
||
const fetchUserRoleLibrary = useCallback(async () => {
|
||
try {
|
||
const response = await getUserRoleLibrary();
|
||
|
||
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('请先选择角色');
|
||
}
|
||
|
||
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 {
|
||
// 响应式数据
|
||
/** 角色列表 */
|
||
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,
|
||
|
||
};
|
||
};
|