forked from 77media/video-flow
302 lines
9.1 KiB
TypeScript
302 lines
9.1 KiB
TypeScript
import { useState, useCallback, useMemo } from 'react';
|
||
import { SceneEntity, TagEntity, AITextEntity, ShotEntity } from '../domain/Entities';
|
||
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';
|
||
|
||
/**
|
||
* 分镜选择项接口
|
||
*/
|
||
interface ShotSelectionItem {
|
||
/** 分镜ID */
|
||
id: string;
|
||
/** 分镜名称 */
|
||
name: string;
|
||
/** 是否已选中 */
|
||
selected: boolean;
|
||
/** 是否已应用场景 */
|
||
applied: boolean;
|
||
/** 分镜数据 */
|
||
shot: ShotEntity;
|
||
}
|
||
|
||
/**
|
||
* 场景服务Hook返回值接口
|
||
*/
|
||
interface UseSceneService {
|
||
// 响应式数据
|
||
/** 场景列表 */
|
||
sceneList: SceneItem[];
|
||
/** 当前选中的场景 */
|
||
selectedScene: SceneItem | null;
|
||
/** 当前场景的AI文本 */
|
||
currentSceneText: TextItem | null;
|
||
/** 当前场景的标签列表 */
|
||
currentSceneTags: TagItem[];
|
||
/** 场景图片URL */
|
||
sceneImageUrl: string;
|
||
/** 分镜选择列表 */
|
||
shotSelectionList: ShotSelectionItem[];
|
||
/** 是否全选分镜 */
|
||
isAllShotsSelected: boolean;
|
||
/** 已选中的分镜数量 */
|
||
selectedShotsCount: number;
|
||
|
||
// 操作方法
|
||
/** 选择场景 */
|
||
selectScene: (sceneId: string) => void;
|
||
/** 设置当前场景的AI文本和标签 */
|
||
setCurrentSceneData: (text: TextItem, tags: TagItem[]) => void;
|
||
/** 优化AI文本 */
|
||
optimizeSceneText: () => Promise<void>;
|
||
/** 修改AI文本 */
|
||
updateSceneText: (newContent: string) => Promise<void>;
|
||
/** 修改标签内容 */
|
||
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);
|
||
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);
|
||
const [tagEditUseCases, setTagEditUseCases] = useState<Map<string, TagEditUseCase>>(new Map());
|
||
|
||
// 计算属性
|
||
const sceneImageUrl = useMemo(() => {
|
||
return selectedScene?.entity.imageUrl || '';
|
||
}, [selectedScene]);
|
||
|
||
const isAllShotsSelected = useMemo(() => {
|
||
return shotSelectionList.length > 0 && shotSelectionList.every(shot => shot.selected);
|
||
}, [shotSelectionList]);
|
||
|
||
const selectedShotsCount = useMemo(() => {
|
||
return shotSelectionList.filter(shot => shot.selected).length;
|
||
}, [shotSelectionList]);
|
||
|
||
// 选择场景
|
||
const selectScene = useCallback((sceneId: string) => {
|
||
const scene = sceneList.find(s => s.entity.id === sceneId);
|
||
if (scene) {
|
||
setSelectedScene(scene);
|
||
setSceneEditUseCase(new SceneEditUseCase(scene));
|
||
setTextEditUseCase(null);
|
||
setTagEditUseCases(new Map());
|
||
setCurrentSceneText(null);
|
||
setCurrentSceneTags([]);
|
||
}
|
||
}, [sceneList]);
|
||
|
||
// 设置当前场景数据
|
||
const setCurrentSceneData = useCallback((text: TextItem, tags: TagItem[]) => {
|
||
setCurrentSceneText(text);
|
||
setCurrentSceneTags(tags);
|
||
|
||
if (text) {
|
||
setTextEditUseCase(new TextEditUseCase(text));
|
||
}
|
||
|
||
const newTagEditUseCases = new Map<string, TagEditUseCase>();
|
||
tags.forEach(tag => {
|
||
newTagEditUseCases.set(tag.entity.id, new TagEditUseCase(tag));
|
||
});
|
||
setTagEditUseCases(newTagEditUseCases);
|
||
}, []);
|
||
|
||
// 优化AI文本
|
||
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文本
|
||
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]);
|
||
|
||
// 修改标签内容
|
||
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]);
|
||
|
||
// 重新生成场景
|
||
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]);
|
||
|
||
// 获取场景分镜列表
|
||
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]);
|
||
|
||
// 切换全选与全不选
|
||
const toggleSelectAllShots = useCallback(() => {
|
||
setShotSelectionList(prev => {
|
||
const isAllSelected = prev.length > 0 && prev.every(shot => shot.selected);
|
||
return prev.map(shot => ({ ...shot, selected: !isAllSelected }));
|
||
});
|
||
}, []);
|
||
|
||
// 选择/取消选择单个分镜
|
||
const toggleShotSelection = useCallback((shotId: string) => {
|
||
setShotSelectionList(prev =>
|
||
prev.map(shot =>
|
||
shot.id === shotId
|
||
? { ...shot, selected: !shot.selected }
|
||
: shot
|
||
)
|
||
);
|
||
}, []);
|
||
|
||
// 应用场景到选中的分镜
|
||
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,
|
||
|
||
// 操作方法
|
||
selectScene,
|
||
setCurrentSceneData,
|
||
optimizeSceneText,
|
||
updateSceneText,
|
||
updateTagContent,
|
||
regenerateScene,
|
||
fetchSceneShots,
|
||
toggleSelectAllShots,
|
||
toggleShotSelection,
|
||
applySceneToSelectedShots
|
||
};
|
||
};
|