forked from 77media/video-flow
426 lines
13 KiB
TypeScript
426 lines
13 KiB
TypeScript
import { useState, useCallback, useMemo } from "react";
|
||
import {
|
||
ShotEntity,
|
||
RoleEntity,
|
||
SceneEntity,
|
||
} from "../domain/Entities";
|
||
import { ContentItem, ScriptSlice, ScriptValueObject } from "../domain/valueObject";
|
||
import { ShotItem } from "../domain/Item";
|
||
import { ShotEditUseCase } from "../usecase/ShotEditUsecase";
|
||
import {
|
||
getShotList,
|
||
// getShotDetail,
|
||
updateShotContent,
|
||
updateShotShot,
|
||
getUserRoleLibrary,
|
||
replaceShotRole,
|
||
getShotVideoScript,
|
||
} from "@/api/video_flow";
|
||
|
||
/**
|
||
* 分镜服务Hook接口
|
||
* 定义分镜服务Hook的所有状态和操作方法
|
||
*/
|
||
export interface UseShotService {
|
||
// 响应式状态
|
||
/** 分镜列表 */
|
||
shotList: ShotEntity[];
|
||
/** 当前选中的分镜 */
|
||
selectedShot: ShotItem | null;
|
||
/** 当前分镜的草图数据URL */
|
||
shotSketchData: string | null;
|
||
/** 当前分镜的视频数据URL */
|
||
shotVideoData: string[] | null;
|
||
|
||
/** 用户角色库 */
|
||
userRoleLibrary: RoleEntity[];
|
||
/** 当前分镜的视频剧本片段 */
|
||
shotVideoScript: ScriptSlice[];
|
||
/** 加载状态 */
|
||
loading: boolean;
|
||
/** 错误信息 */
|
||
error: string | null;
|
||
|
||
// 操作方法
|
||
/** 获取分镜列表 */
|
||
fetchShotList: (projectId: string) => Promise<void>;
|
||
/** 选择分镜并获取详情 */
|
||
selectShot: (shotId: string) => Promise<void>;
|
||
/** 修改分镜对话内容 */
|
||
updateShotContent: (
|
||
newContent: Array<{ roleId: string; content: string }>
|
||
) => Promise<void>;
|
||
/** 修改分镜镜头 */
|
||
updateShotShot: (newShot: string[]) => Promise<void>;
|
||
/** 获取用户角色库 */
|
||
fetchUserRoleLibrary: () => Promise<void>;
|
||
/** 替换分镜角色 */
|
||
replaceShotRole: (oldRoleId: string, newRoleId: string) => Promise<void>;
|
||
/** 获取分镜视频剧本内容 */
|
||
fetchShotVideoScript: () => Promise<void>;
|
||
/** 获取分镜关联的角色信息 */
|
||
getShotRoles: () => Promise<RoleEntity[]>;
|
||
/** 获取分镜关联的场景信息 */
|
||
getShotScenes: () => Promise<SceneEntity[]>;
|
||
|
||
/** 重新生成分镜 */
|
||
regenerateShot: (
|
||
shotPrompt: string,
|
||
dialogueContent: ContentItem[],
|
||
roleReplaceParams: { oldId: string; newId: string }[],
|
||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||
) => Promise<void>;
|
||
}
|
||
|
||
/**
|
||
* 分镜服务Hook
|
||
* 提供分镜相关的所有状态管理和操作方法
|
||
* 包括分镜列表管理、分镜选择、数据获取、内容修改等功能
|
||
*/
|
||
export const useShotService = (): UseShotService => {
|
||
// 响应式状态
|
||
const [shotList, setShotList] = useState<ShotEntity[]>([]);
|
||
const [selectedShot, setSelectedShot] = useState<ShotItem | null>(null);
|
||
const [shotSketchData, setShotSketchData] = useState<string | null>(null);
|
||
const [shotVideoData, setShotVideoData] = useState<string[] | null>(null);
|
||
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
||
const [shotVideoScript, setShotVideoScript] = useState<ScriptSlice[]>([]);
|
||
const [loading, setLoading] = useState<boolean>(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [projectId, setProjectId] = useState<string>("");
|
||
// UseCase实例
|
||
const [shotEditUseCase, setShotEditUseCase] =
|
||
useState<ShotEditUseCase | null>(null);
|
||
|
||
/**
|
||
* 获取分镜列表
|
||
* @description 根据项目ID获取所有分镜列表
|
||
* @param projectId 项目ID
|
||
*/
|
||
const fetchShotList = useCallback(async (projectId: string) => {
|
||
setProjectId(projectId);
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await getShotList({ projectId });
|
||
if (response.successful) {
|
||
setShotList(response.data);
|
||
} else {
|
||
setError(`获取分镜列表失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`获取分镜列表失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
/**
|
||
* 选择分镜并获取详情
|
||
* @description 根据分镜ID获取分镜详情,并初始化相关的UseCase和数据
|
||
* @param shotId 分镜ID
|
||
*/
|
||
const selectShot = useCallback(async (shotId: string) => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
// 获取分镜详情
|
||
await fetchShotList(projectId);
|
||
const shotEntity = shotList.find(
|
||
(shot: ShotEntity) => shot.id === shotId
|
||
);
|
||
if (!shotEntity) {
|
||
setError(`分镜不存在: ${shotId}`);
|
||
return;
|
||
}
|
||
const shotItem = new ShotItem(shotEntity);
|
||
setSelectedShot(shotItem);
|
||
|
||
// 初始化UseCase
|
||
const newShotEditUseCase = new ShotEditUseCase(shotItem);
|
||
setShotEditUseCase(newShotEditUseCase);
|
||
|
||
// 从分镜实体中获取草图数据和视频数据
|
||
setShotSketchData(shotEntity.sketchUrl || null);
|
||
setShotVideoData(shotEntity.videoUrl || null);
|
||
} catch (err) {
|
||
setError(
|
||
`选择分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
/**
|
||
* 修改分镜对话内容
|
||
* @description 更新分镜的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||
* @param newContent 新的对话内容数组
|
||
*/
|
||
const updateShotContentHandler = useCallback(
|
||
async (newContent: Array<{ roleId: string; content: string }>) => {
|
||
if (!shotEditUseCase) {
|
||
setError("分镜编辑用例未初始化");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const updatedShot = await shotEditUseCase.updateShotContent(newContent);
|
||
setSelectedShot(new ShotItem(updatedShot));
|
||
} catch (err) {
|
||
setError(
|
||
`修改分镜对话内容失败: ${
|
||
err instanceof Error ? err.message : "未知错误"
|
||
}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[shotEditUseCase]
|
||
);
|
||
|
||
/**
|
||
* 修改分镜镜头
|
||
* @description 更新分镜的镜头数据
|
||
* @param newShot 新的镜头数据
|
||
*/
|
||
const updateShotShotHandler = useCallback(
|
||
async (newShot: string[]) => {
|
||
if (!selectedShot) {
|
||
setError("未选择分镜");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await updateShotShot({
|
||
shotId: selectedShot.entity.id,
|
||
shot: newShot,
|
||
});
|
||
if (response.successful) {
|
||
const updatedShot = response.data;
|
||
setSelectedShot(new ShotItem(updatedShot));
|
||
} else {
|
||
setError(`修改分镜镜头失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`修改分镜镜头失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[selectedShot]
|
||
);
|
||
|
||
/**
|
||
* 获取用户角色库
|
||
* @description 获取当前用户的所有角色库数据
|
||
*/
|
||
const fetchUserRoleLibrary = useCallback(async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await getUserRoleLibrary();
|
||
if (response.successful) {
|
||
setUserRoleLibrary(response.data);
|
||
} else {
|
||
setError(`获取用户角色库失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`获取用户角色库失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
/**
|
||
* 替换分镜角色
|
||
* @description 将分镜中的某个角色替换为角色库中的另一个角色
|
||
* @param oldRoleId 旧角色ID
|
||
* @param newRoleId 新角色ID
|
||
*/
|
||
const replaceShotRoleHandler = useCallback(
|
||
async (oldRoleId: string, newRoleId: string) => {
|
||
if (!selectedShot) {
|
||
setError("未选择分镜");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await replaceShotRole({
|
||
shotId: selectedShot.entity.id,
|
||
oldRoleId,
|
||
newRoleId,
|
||
});
|
||
if (response.successful) {
|
||
// 重新获取分镜详情
|
||
await selectShot(selectedShot.entity.id);
|
||
} else {
|
||
setError(`替换分镜角色失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`替换分镜角色失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[selectedShot, selectShot]
|
||
);
|
||
|
||
/**
|
||
* 获取分镜视频剧本内容
|
||
* @description 获取分镜视频的剧本内容,通过接口返回多个ScriptSliceEntity片段
|
||
*/
|
||
const fetchShotVideoScript = useCallback(async () => {
|
||
if (!selectedShot) {
|
||
setError("未选择分镜");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await getShotVideoScript({
|
||
shotId: selectedShot.entity.id,
|
||
});
|
||
if (response.successful) {
|
||
const script = new ScriptValueObject(response.data);
|
||
setShotVideoScript(script.scriptSlices);
|
||
} else {
|
||
setError(`获取分镜视频剧本失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`获取分镜视频剧本失败: ${
|
||
err instanceof Error ? err.message : "未知错误"
|
||
}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [selectedShot]);
|
||
|
||
/**
|
||
* 获取分镜关联的角色信息
|
||
* @description 获取当前分镜可以使用的角色列表
|
||
* @returns Promise<RoleEntity[]> 角色信息列表
|
||
*/
|
||
const getShotRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
||
if (!shotEditUseCase) {
|
||
throw new Error("分镜编辑用例未初始化");
|
||
}
|
||
return await shotEditUseCase.getShotRoles();
|
||
}, [shotEditUseCase]);
|
||
|
||
/**
|
||
* 获取分镜关联的场景信息
|
||
* @description 获取当前分镜可以使用的场景列表
|
||
* @returns Promise<SceneEntity[]> 场景信息列表
|
||
*/
|
||
const getShotScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
||
if (!shotEditUseCase) {
|
||
throw new Error("分镜编辑用例未初始化");
|
||
}
|
||
return await shotEditUseCase.getShotScenes();
|
||
}, [shotEditUseCase]);
|
||
|
||
/**
|
||
* 重新生成分镜
|
||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成分镜
|
||
* @param shotPrompt 镜头描述
|
||
* @param dialogueContent 对话内容
|
||
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
||
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
||
*/
|
||
const regenerateShot = useCallback(
|
||
async (
|
||
shotPrompt: string,
|
||
dialogueContent: ContentItem[],
|
||
roleReplaceParams: { oldId: string; newId: string }[],
|
||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||
) => {
|
||
if (!shotEditUseCase) {
|
||
setError("分镜编辑用例未初始化");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const updatedShot = await shotEditUseCase.regenerateShot(
|
||
shotPrompt,
|
||
dialogueContent,
|
||
roleReplaceParams,
|
||
sceneReplaceParams
|
||
);
|
||
setSelectedShot(new ShotItem(updatedShot));
|
||
} catch (err) {
|
||
setError(
|
||
`重新生成分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[shotEditUseCase]
|
||
);
|
||
|
||
return {
|
||
// 响应式状态 - 用于UI组件订阅和渲染
|
||
/** 分镜列表 - 当前项目的所有分镜数据 */
|
||
shotList,
|
||
/** 当前选中的分镜 - 包含分镜实体和编辑状态 */
|
||
selectedShot,
|
||
/** 当前分镜的草图数据URL - 从分镜实体中获取的草图图片链接 */
|
||
shotSketchData,
|
||
/** 当前分镜的视频数据URL - 从分镜实体中获取的视频链接 */
|
||
shotVideoData,
|
||
|
||
/** 用户角色库 - 当前用户可用的所有角色数据 */
|
||
userRoleLibrary,
|
||
/** 当前分镜的视频剧本片段 - 通过接口获取的剧本内容 */
|
||
shotVideoScript,
|
||
/** 加载状态 - 标识当前是否有异步操作正在进行 */
|
||
loading,
|
||
/** 错误信息 - 记录最近一次操作的错误信息 */
|
||
error,
|
||
|
||
// 操作方法 - 提供给UI组件调用的业务逻辑方法
|
||
/** 获取分镜列表 - 根据项目ID获取所有分镜数据 */
|
||
fetchShotList,
|
||
/** 选择分镜并获取详情 - 选择指定分镜并初始化相关数据 */
|
||
selectShot,
|
||
/** 修改分镜对话内容 - 更新分镜的对话内容,保持ContentItem结构不变 */
|
||
updateShotContent: updateShotContentHandler,
|
||
/** 修改分镜镜头 - 更新分镜的镜头数据 */
|
||
updateShotShot: updateShotShotHandler,
|
||
/** 获取用户角色库 - 获取当前用户的所有角色数据 */
|
||
fetchUserRoleLibrary,
|
||
/** 替换分镜角色 - 将分镜中的角色替换为角色库中的另一个角色 */
|
||
replaceShotRole: replaceShotRoleHandler,
|
||
/** 获取分镜视频剧本内容 - 通过接口获取视频剧本片段 */
|
||
fetchShotVideoScript,
|
||
/** 获取分镜关联的角色信息 - 获取当前分镜可用的角色列表 */
|
||
getShotRoles,
|
||
/** 获取分镜关联的场景信息 - 获取当前分镜可用的场景列表 */
|
||
getShotScenes,
|
||
/** 重新生成分镜 - 使用新参数重新生成分镜内容 */
|
||
regenerateShot,
|
||
};
|
||
};
|