302 lines
9.1 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, 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
};
};