516 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback, useMemo } from 'react';
import { RoleEntity, TagEntity, AITextEntity, ShotEntity } 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: ShotEntity;
}
/**
* 角色服务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,
};
};