428 lines
14 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 { SceneEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
import { SceneItem, TagItem, TextItem } from '../domain/Item';
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
import { TagEditUseCase } from '../usecase/TagEditUseCase';
import { getSceneShots, getSceneData, getSceneList } from '@/api/video_flow';
/**
* 分镜选择项接口
*/
interface ShotSelectionItem {
/** 分镜ID */
id: string;
/** 分镜名称 */
name: string;
/** 是否已选中 */
selected: boolean;
/** 是否已应用场景 */
applied: boolean;
/** 分镜数据 */
shot: VideoSegmentEntity;
}
/**
* 场景服务Hook返回值接口
*/
interface UseSceneService {
// 响应式数据
/** 场景列表 */
sceneList: SceneItem[];
/** 当前选中的场景 */
selectedScene: SceneItem | null;
// /** 当前场景的AI文本 */
// currentSceneText: TextItem | null; // TODO: TextEditUseCase not implemented yet
/** 当前场景的标签列表 */
currentSceneTags: TagItem[];
/** 场景图片URL */
sceneImageUrl: string;
/** 分镜选择列表 */
shotSelectionList: ShotSelectionItem[];
/** 是否全选分镜 */
isAllShotsSelected: boolean;
/** 已选中的分镜数量 */
selectedShotsCount: number;
// 操作方法
/** 获取场景列表 */
fetchSceneList: (projectId: string) => Promise<void>;
/** 选择场景 */
selectScene: (sceneId: string) => void;
/** 初始化当前选中场景的AI文本和标签数据 */
initializeSceneData: () => Promise<void>;
// /** 优化AI文本 */
// optimizeSceneText: () => Promise<void>; // TODO: TextEditUseCase not implemented yet
// /** 修改AI文本 */
// updateSceneText: (newContent: string) => Promise<void>; // TODO: TextEditUseCase not implemented yet
/** 修改标签内容 */
updateTagContent: (tagId: string, newContent: string | number) => Promise<void>;
/** 重新生成场景 */
regenerateScene: () => Promise<void>;
/** 获取场景出现的分镜列表 */
fetchSceneShots: () => Promise<void>;
/** 切换全选与全不选 */
toggleSelectAllShots: () => void;
/** 选择/取消选择单个分镜 */
toggleShotSelection: (shotId: string) => void;
/** 应用场景到选中的分镜 */
applySceneToSelectedShots: () => Promise<void>;
}
/**
* 场景服务Hook
* 提供场景相关的所有响应式功能和业务逻辑
*/
export const useSceneServiceHook = (): UseSceneService => {
// 响应式状态
const [sceneList, setSceneList] = useState<SceneItem[]>([]);
const [selectedScene, setSelectedScene] = useState<SceneItem | null>(null);
// const [currentSceneText, setCurrentSceneText] = useState<TextItem | null>(null); // TODO: TextEditUseCase not implemented yet
const [currentSceneTags, setCurrentSceneTags] = useState<TagItem[]>([]);
const [shotSelectionList, setShotSelectionList] = useState<ShotSelectionItem[]>([]);
// UseCase实例 - 在场景选择时初始化
const [sceneEditUseCase, setSceneEditUseCase] = useState<SceneEditUseCase | null>(null);
// const [textEditUseCase, setTextEditUseCase] = useState<TextEditUseCase | null>(null); // TODO: TextEditUseCase not implemented yet
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]);
/**
* 获取场景列表
* @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]);
/**
* 初始化场景数据
* @description 初始化当前选中场景的AI文本和标签数据
* @throws {Error} 当未选择场景或API调用失败时抛出错误
* @returns {Promise<void>} 初始化完成后的Promise
*/
const initializeSceneData = useCallback(async () => {
if (!selectedScene) {
throw new Error('请先选择场景');
}
try {
const response = await getSceneData({
sceneId: selectedScene.entity.id
});
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未初始化');
}
if (!currentSceneText || !currentSceneText.entity.content) {
throw new Error('没有可优化的文本内容');
}
const optimizedContent = await textEditUseCase.getOptimizedContent();
const updatedTextItem = await textEditUseCase.updateText(optimizedContent);
setCurrentSceneText(updatedTextItem);
}, [textEditUseCase, currentSceneText]);
/**
* 修改AI文本
* @description 手动修改当前场景的AI文本内容
* @param newContent 新的文本内容
* @throws {Error} 当没有可编辑的文本或UseCase未初始化时抛出错误
* @returns {Promise<void>} 修改完成后的Promise
*/
const updateSceneText = useCallback(async (newContent: string) => {
if (!textEditUseCase) {
throw new Error('文本编辑UseCase未初始化');
}
if (!currentSceneText) {
throw new Error('没有可编辑的文本');
}
const updatedTextItem = await textEditUseCase.updateText(newContent);
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) {
throw new Error(`标签编辑UseCase未初始化标签ID: ${tagId}`);
}
const updatedTagItem = await tagEditUseCase.updateTag(newContent);
setCurrentSceneTags(prev =>
prev.map(tag =>
tag.entity.id === tagId
? updatedTagItem
: tag
)
);
}, [tagEditUseCases]);
/**
* 重新生成场景
* @description 使用AI文本和标签重新生成场景
* @throws {Error} 当缺少重新生成场景所需的数据或UseCase未初始化时抛出错误
* @returns {Promise<void>} 重新生成完成后的Promise
*/
const regenerateScene = useCallback(async () => {
if (!sceneEditUseCase) {
throw new Error('场景编辑UseCase未初始化');
}
if (!selectedScene || !currentSceneText || currentSceneTags.length === 0) {
throw new Error('缺少重新生成场景所需的数据');
}
const newSceneEntity = await sceneEditUseCase.AIgenerateScene(currentSceneText, currentSceneTags);
const newSceneItem = new SceneItem(newSceneEntity);
setSelectedScene(newSceneItem);
setSceneList(prev =>
prev.map(scene =>
scene.entity.id === newSceneEntity.id ? newSceneItem : scene
)
);
}, [sceneEditUseCase, selectedScene, currentSceneText, currentSceneTags]);
/**
* 获取场景出现的分镜列表
* @description 获取当前场景应用到的分镜列表,包括已应用状态
* @throws {Error} 当未选择场景或API调用失败时抛出错误
* @returns {Promise<void>} 获取完成后的Promise
*/
const fetchSceneShots = useCallback(async () => {
if (!selectedScene) {
throw new Error('请先选择场景');
}
try {
const response = await getSceneShots({
sceneId: selectedScene.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),
shot
}));
setShotSelectionList(shotSelectionItems);
} else {
throw new Error(`获取场景分镜列表失败: ${response.message}`);
}
} catch (error) {
console.error('获取场景分镜列表失败:', error);
throw error;
}
}, [selectedScene]);
/**
* 切换全选与全不选
* @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 applySceneToSelectedShots = useCallback(async () => {
if (!sceneEditUseCase) {
throw new Error('场景编辑UseCase未初始化');
}
if (!selectedScene) {
throw new Error('请先选择场景');
}
const selectedShotIds = shotSelectionList
.filter(shot => shot.selected)
.map(shot => shot.id);
if (selectedShotIds.length === 0) {
throw new Error('请先选择要应用的分镜');
}
await sceneEditUseCase.applyScene(selectedShotIds);
setShotSelectionList(prev =>
prev.map(shot =>
selectedShotIds.includes(shot.id)
? { ...shot, applied: true, selected: false }
: shot
)
);
}, [sceneEditUseCase, selectedScene, shotSelectionList]);
return {
// 响应式数据
sceneList,
selectedScene,
currentSceneText,
currentSceneTags,
sceneImageUrl,
shotSelectionList,
isAllShotsSelected,
selectedShotsCount,
// 操作方法
/** 获取场景列表 */
fetchSceneList,
/** 选择场景 */
selectScene,
/** 初始化当前选中场景的AI文本和标签数据 */
initializeSceneData,
/** 优化AI文本 */
optimizeSceneText,
/** 修改AI文本 */
updateSceneText,
/** 修改标签内容 */
updateTagContent,
/** 重新生成场景 */
regenerateScene,
/** 获取场景出现的分镜列表 */
fetchSceneShots,
/** 切换全选与全不选 */
toggleSelectAllShots,
/** 选择/取消选择单个分镜 */
toggleShotSelection,
/** 应用场景到选中的分镜 */
applySceneToSelectedShots
};
};