forked from 77media/video-flow
移除不必要的对话内容参数,更新视频片段服务接口以支持视频片段列表获取、视频内容更新和AI优化功能;重构相关逻辑以简化代码结构并提高可读性。
This commit is contained in:
parent
884aa5794f
commit
4c3d4fb079
@ -509,8 +509,6 @@ export const regenerateShot = async (request: {
|
||||
shotId?: string;
|
||||
/** 镜头描述 */
|
||||
shotPrompt?: LensType[];
|
||||
/** 对话内容 */
|
||||
dialogueContent?: ContentItem[];
|
||||
/** 角色ID替换参数,格式为{oldId:string,newId:string}[] */
|
||||
roleReplaceParams?: { oldId: string; newId: string }[];
|
||||
/** 场景ID替换参数,格式为{oldId:string,newId:string}[] */
|
||||
@ -726,22 +724,6 @@ export const enhanceScriptStream = (
|
||||
request: {
|
||||
/** 原始剧本文本 */
|
||||
original_script: string;
|
||||
/** 故事梗概 */
|
||||
synopsis?: string;
|
||||
/** 故事分类 */
|
||||
categories?: string[];
|
||||
/** 主角名称 */
|
||||
protagonist?: string;
|
||||
/** 激励事件 */
|
||||
incitingIncident?: string;
|
||||
/** 问题与新目标 */
|
||||
problem?: string;
|
||||
/** 冲突与障碍 */
|
||||
conflict?: string;
|
||||
/** 赌注 */
|
||||
stakes?: string;
|
||||
/** 人物弧线完成 */
|
||||
characterArc?: string;
|
||||
/** AI优化要求 */
|
||||
aiOptimizing: string;
|
||||
},
|
||||
@ -767,3 +749,19 @@ export const enhanceScriptStream = (
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* AI优化镜头内容接口
|
||||
* @param request - AI优化请求参数
|
||||
* @returns Promise<ApiResponse<LensType[]>> 优化后的镜头数据
|
||||
*/
|
||||
export const optimizeShotContent = async (request: {
|
||||
/** 视频片段ID */
|
||||
shotId: string;
|
||||
/** 用户优化需求 */
|
||||
userRequirement: string;
|
||||
/** 镜头数据数组 */
|
||||
lensData: LensType[];
|
||||
}): Promise<ApiResponse<LensType[]>> => {
|
||||
return post<ApiResponse<LensType[]>>("/api/v1/shot/optimize_content", request);
|
||||
};
|
||||
|
||||
@ -51,7 +51,7 @@ export interface UseScriptService {
|
||||
/** 根据用户想法生成剧本并自动创建项目 */
|
||||
generateScriptFromIdea: (idea: string) => Promise<void>;
|
||||
/** 根据项目ID初始化已有剧本 */
|
||||
initializeFromProject: (projectId: string, script?: string) => Promise<void>;
|
||||
initializeFromProject: (projectId: string, script: string) => Promise<void>;
|
||||
/** 修改剧本 */
|
||||
updateScript: (scriptText: string) => Promise<void>;
|
||||
/** 应用剧本到视频生成流程 */
|
||||
@ -132,7 +132,6 @@ export const useScriptService = (): UseScriptService => {
|
||||
const [scriptEditUseCase, setScriptEditUseCase] = useState<ScriptEditUseCase>(
|
||||
new ScriptEditUseCase("")
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据用户想法生成剧本并自动创建项目
|
||||
* @param idea 用户想法
|
||||
@ -167,7 +166,7 @@ export const useScriptService = (): UseScriptService => {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
[projectId, scriptEditUseCase]
|
||||
);
|
||||
|
||||
const createMovieProjectV1 = useCallback(
|
||||
@ -231,6 +230,7 @@ export const useScriptService = (): UseScriptService => {
|
||||
|
||||
// 获取解析后的故事详情
|
||||
const storyDetails = newScriptEditUseCase.getStoryDetails();
|
||||
|
||||
setSynopsis(storyDetails.synopsis || "");
|
||||
setCategories(storyDetails.categories || []);
|
||||
setProtagonist(storyDetails.protagonist || "");
|
||||
@ -246,7 +246,7 @@ export const useScriptService = (): UseScriptService => {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
[projectId, scriptEditUseCase]
|
||||
);
|
||||
|
||||
/**
|
||||
@ -437,8 +437,6 @@ export const useScriptService = (): UseScriptService => {
|
||||
|
||||
// 调用增强剧本方法
|
||||
await scriptEditUseCase.enhanceScript(
|
||||
synopsis,
|
||||
focusedField as ScriptEditKey,
|
||||
aiOptimizing,
|
||||
(content: any) => {
|
||||
// 获取解析后的故事详情
|
||||
@ -464,7 +462,7 @@ export const useScriptService = (): UseScriptService => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [scriptEditUseCase, synopsis, focusedField, aiOptimizing, projectId]);
|
||||
}, [scriptEditUseCase, aiOptimizing, projectId]);
|
||||
|
||||
// 在ScriptService中添加一个方法来获取渲染数据
|
||||
const scriptBlocksMemo = useMemo((): ScriptBlock[] => {
|
||||
@ -500,6 +498,7 @@ export const useScriptService = (): UseScriptService => {
|
||||
conflict,
|
||||
stakes,
|
||||
characterArc,
|
||||
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
@ -1,386 +1,325 @@
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import {
|
||||
VideoSegmentEntity,
|
||||
RoleEntity,
|
||||
SceneEntity,
|
||||
} from "../domain/Entities";
|
||||
import { ContentItem, ScriptSlice, ScriptValueObject, LensType } from "../domain/valueObject";
|
||||
import { VideoSegmentItem } from "../domain/Item";
|
||||
import { useState, useCallback } from "react";
|
||||
import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase";
|
||||
import {
|
||||
getShotList,
|
||||
// getShotDetail,
|
||||
updateShotContent,
|
||||
getUserRoleLibrary,
|
||||
replaceShotRole,
|
||||
getShotVideoScript,
|
||||
} from "@/api/video_flow";
|
||||
import { VideoSegmentEntity } from "../domain/Entities";
|
||||
import { ContentItem, LensType } from "../domain/valueObject";
|
||||
|
||||
/**
|
||||
* 视频片段服务Hook接口
|
||||
* 定义视频片段服务Hook的所有状态和操作方法
|
||||
*/
|
||||
export interface UseVideoSegmentService {
|
||||
export interface UseShotService {
|
||||
// 响应式状态
|
||||
/** 视频片段列表 */
|
||||
videoSegmentList: VideoSegmentEntity[];
|
||||
/** 当前选中的视频片段 */
|
||||
selectedVideoSegment: VideoSegmentItem | null;
|
||||
/** 当前视频片段的草图数据URL */
|
||||
videoSegmentSketchData: string | null;
|
||||
/** 当前视频片段的视频数据URL */
|
||||
videoSegmentVideoData: string[] | null;
|
||||
|
||||
/** 用户角色库 */
|
||||
userRoleLibrary: RoleEntity[];
|
||||
/** 当前视频片段的视频剧本片段 */
|
||||
videoSegmentVideoScript: ScriptSlice[];
|
||||
/** 加载状态 */
|
||||
loading: boolean;
|
||||
/** 视频片段列表 */
|
||||
videoSegments: VideoSegmentEntity[];
|
||||
/** 当前选中的视频片段 */
|
||||
selectedSegment: VideoSegmentEntity | null;
|
||||
/** 错误信息 */
|
||||
error: string | null;
|
||||
|
||||
// 操作方法
|
||||
/** 获取视频片段列表 */
|
||||
fetchVideoSegmentList: (projectId: string) => Promise<void>;
|
||||
/** 选择视频片段并获取详情 */
|
||||
selectVideoSegment: (videoSegmentId: string) => Promise<void>;
|
||||
/** 修改视频片段对话内容 */
|
||||
updateVideoSegmentContent: (
|
||||
newContent: Array<{ roleId: string; content: string }>
|
||||
) => Promise<void>;
|
||||
/** 获取用户角色库 */
|
||||
fetchUserRoleLibrary: () => Promise<void>;
|
||||
/** 替换视频片段角色 */
|
||||
replaceVideoSegmentRole: (oldRoleId: string, newRoleId: string) => Promise<void>;
|
||||
/** 获取视频片段视频剧本内容 */
|
||||
fetchVideoSegmentVideoScript: () => Promise<void>;
|
||||
/** 获取视频片段关联的角色信息 */
|
||||
getVideoSegmentRoles: () => Promise<RoleEntity[]>;
|
||||
/** 获取视频片段关联的场景信息 */
|
||||
getVideoSegmentScenes: () => Promise<SceneEntity[]>;
|
||||
|
||||
getVideoSegmentList: (projectId: string) => Promise<void>;
|
||||
/** 重新生成视频片段 */
|
||||
regenerateVideoSegment: (
|
||||
shotPrompt: LensType[],
|
||||
dialogueContent: ContentItem[],
|
||||
roleReplaceParams: { oldId: string; newId: string }[],
|
||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||
) => Promise<void>;
|
||||
shotId?: string,
|
||||
roleReplaceParams?: { oldId: string; newId: string }[],
|
||||
sceneReplaceParams?: { oldId: string; newId: string }[]
|
||||
) => Promise<VideoSegmentEntity>;
|
||||
/** AI优化视频内容 */
|
||||
optimizeVideoContent: (
|
||||
shotId: string,
|
||||
userRequirement: string,
|
||||
lensData: LensType[]
|
||||
) => Promise<LensType[]>;
|
||||
/** 更新视频内容 */
|
||||
updateVideoContent: (
|
||||
shotId: string,
|
||||
content: Array<{ roleId: string; content: string }>
|
||||
) => Promise<VideoSegmentEntity>;
|
||||
/** 中断当前操作 */
|
||||
abortOperation: () => void;
|
||||
/** 设置选中的视频片段 */
|
||||
setSelectedSegment: (segment: VideoSegmentEntity | null) => void;
|
||||
/** 清除错误信息 */
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频片段服务Hook
|
||||
* 提供视频片段相关的所有状态管理和操作方法
|
||||
* 包括视频片段列表管理、视频片段选择、数据获取、内容修改等功能
|
||||
* 包括获取视频列表、重新生成视频、AI优化等功能
|
||||
*/
|
||||
export const useVideoSegmentService = (): UseVideoSegmentService => {
|
||||
export const useShotService = (): UseShotService => {
|
||||
// 响应式状态
|
||||
const [videoSegmentList, setVideoSegmentList] = useState<VideoSegmentEntity[]>([]);
|
||||
const [selectedVideoSegment, setSelectedVideoSegment] = useState<VideoSegmentItem | null>(null);
|
||||
const [videoSegmentSketchData, setVideoSegmentSketchData] = useState<string | null>(null);
|
||||
const [videoSegmentVideoData, setVideoSegmentVideoData] = useState<string[] | null>(null);
|
||||
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
||||
const [videoSegmentVideoScript, setVideoSegmentVideoScript] = useState<ScriptSlice[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [videoSegments, setVideoSegments] = useState<VideoSegmentEntity[]>([]);
|
||||
const [selectedSegment, setSelectedSegment] = useState<VideoSegmentEntity | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [projectId, setProjectId] = useState<string>("");
|
||||
|
||||
// UseCase实例
|
||||
const [videoSegmentEditUseCase, setVideoSegmentEditUseCase] =
|
||||
useState<VideoSegmentEditUseCase | null>(null);
|
||||
const [shotEditUseCase] = useState<VideoSegmentEditUseCase>(
|
||||
new VideoSegmentEditUseCase()
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取视频片段列表
|
||||
* @description 根据项目ID获取所有视频片段列表
|
||||
* @param projectId 项目ID
|
||||
*/
|
||||
const fetchVideoSegmentList = useCallback(async (projectId: string) => {
|
||||
setProjectId(projectId);
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await getShotList({ projectId });
|
||||
if (response.successful) {
|
||||
setVideoSegmentList(response.data);
|
||||
} else {
|
||||
setError(`获取视频片段列表失败: ${response.message}`);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
`获取视频片段列表失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 选择视频片段并获取详情
|
||||
* @description 根据视频片段ID获取视频片段详情,并初始化相关的UseCase和数据
|
||||
* @param videoSegmentId 视频片段ID
|
||||
*/
|
||||
const selectVideoSegment = useCallback(async (videoSegmentId: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// 获取视频片段详情
|
||||
await fetchVideoSegmentList(projectId);
|
||||
const videoSegmentEntity = videoSegmentList.find(
|
||||
(videoSegment: VideoSegmentEntity) => videoSegment.id === videoSegmentId
|
||||
);
|
||||
if (!videoSegmentEntity) {
|
||||
setError(`视频片段不存在: ${videoSegmentId}`);
|
||||
return;
|
||||
}
|
||||
const videoSegmentItem = new VideoSegmentItem(videoSegmentEntity);
|
||||
setSelectedVideoSegment(videoSegmentItem);
|
||||
|
||||
// 初始化UseCase
|
||||
const newVideoSegmentEditUseCase = new VideoSegmentEditUseCase(videoSegmentItem);
|
||||
setVideoSegmentEditUseCase(newVideoSegmentEditUseCase);
|
||||
|
||||
// 从视频片段实体中获取草图数据和视频数据
|
||||
setVideoSegmentSketchData(videoSegmentEntity.sketchUrl || null);
|
||||
setVideoSegmentVideoData(videoSegmentEntity.videoUrl || null);
|
||||
} catch (err) {
|
||||
setError(
|
||||
`选择视频片段失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 修改视频片段对话内容
|
||||
* @description 更新视频片段的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||||
* @param newContent 新的对话内容数组
|
||||
*/
|
||||
const updateVideoSegmentContentHandler = useCallback(
|
||||
async (newContent: Array<{ roleId: string; content: string }>) => {
|
||||
if (!videoSegmentEditUseCase) {
|
||||
setError("视频片段编辑用例未初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
const getVideoSegmentList = useCallback(
|
||||
async (projectId: string): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const updatedVideoSegment = await videoSegmentEditUseCase.updateVideoSegmentContent(newContent);
|
||||
setSelectedVideoSegment(new VideoSegmentItem(updatedVideoSegment));
|
||||
} catch (err) {
|
||||
setError(
|
||||
`修改视频片段对话内容失败: ${
|
||||
err instanceof Error ? err.message : "未知错误"
|
||||
}`
|
||||
);
|
||||
|
||||
const segments = await shotEditUseCase.getVideoSegmentList(projectId);
|
||||
setVideoSegments(segments);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "获取视频片段列表失败";
|
||||
setError(errorMessage);
|
||||
console.error("获取视频片段列表失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[videoSegmentEditUseCase]
|
||||
[shotEditUseCase]
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户角色库
|
||||
* @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 replaceVideoSegmentRoleHandler = useCallback(
|
||||
async (oldRoleId: string, newRoleId: string) => {
|
||||
if (!selectedVideoSegment) {
|
||||
setError("未选择视频片段");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await replaceShotRole({
|
||||
shotId: selectedVideoSegment.entity.id,
|
||||
oldRoleId,
|
||||
newRoleId,
|
||||
});
|
||||
if (response.successful) {
|
||||
// 重新获取视频片段详情
|
||||
await selectVideoSegment(selectedVideoSegment.entity.id);
|
||||
} else {
|
||||
setError(`替换视频片段角色失败: ${response.message}`);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
`替换视频片段角色失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[selectedVideoSegment, selectVideoSegment]
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取视频片段视频剧本内容
|
||||
* @description 获取视频片段视频的剧本内容,通过接口返回多个ScriptSliceEntity片段
|
||||
*/
|
||||
const fetchVideoSegmentVideoScript = useCallback(async () => {
|
||||
if (!selectedVideoSegment) {
|
||||
setError("未选择视频片段");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await getShotVideoScript({
|
||||
shotId: selectedVideoSegment.entity.id,
|
||||
});
|
||||
if (response.successful) {
|
||||
const script = new ScriptValueObject(response.data);
|
||||
setVideoSegmentVideoScript([...script.scriptSlices]);
|
||||
} else {
|
||||
setError(`获取视频片段视频剧本失败: ${response.message}`);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
`获取视频片段视频剧本失败: ${
|
||||
err instanceof Error ? err.message : "未知错误"
|
||||
}`
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [selectedVideoSegment]);
|
||||
|
||||
/**
|
||||
* 获取视频片段关联的角色信息
|
||||
* @description 获取当前视频片段可以使用的角色列表
|
||||
* @returns Promise<RoleEntity[]> 角色信息列表
|
||||
*/
|
||||
const getVideoSegmentRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
||||
if (!videoSegmentEditUseCase) {
|
||||
throw new Error("视频片段编辑用例未初始化");
|
||||
}
|
||||
return await videoSegmentEditUseCase.getVideoSegmentRoles();
|
||||
}, [videoSegmentEditUseCase]);
|
||||
|
||||
/**
|
||||
* 获取视频片段关联的场景信息
|
||||
* @description 获取当前视频片段可以使用的场景列表
|
||||
* @returns Promise<SceneEntity[]> 场景信息列表
|
||||
*/
|
||||
const getVideoSegmentScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
||||
if (!videoSegmentEditUseCase) {
|
||||
throw new Error("视频片段编辑用例未初始化");
|
||||
}
|
||||
return await videoSegmentEditUseCase.getVideoSegmentScenes();
|
||||
}, [videoSegmentEditUseCase]);
|
||||
|
||||
/**
|
||||
* 重新生成视频片段
|
||||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成视频片段
|
||||
* @param shotPrompt 镜头描述
|
||||
* @param dialogueContent 对话内容
|
||||
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
||||
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
||||
* @param shotPrompt 镜头描述数据
|
||||
* @param shotId 视频片段ID(可选)
|
||||
* @param roleReplaceParams 角色替换参数(可选)
|
||||
* @param sceneReplaceParams 场景替换参数(可选)
|
||||
* @returns Promise<VideoSegmentEntity> 重新生成的视频片段
|
||||
*/
|
||||
const regenerateVideoSegment = useCallback(
|
||||
async (
|
||||
shotPrompt: LensType[],
|
||||
dialogueContent: ContentItem[],
|
||||
roleReplaceParams: { oldId: string; newId: string }[],
|
||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||
) => {
|
||||
if (!videoSegmentEditUseCase) {
|
||||
setError("视频片段编辑用例未初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
shotId?: string,
|
||||
roleReplaceParams?: { oldId: string; newId: string }[],
|
||||
sceneReplaceParams?: { oldId: string; newId: string }[]
|
||||
): Promise<VideoSegmentEntity> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const updatedVideoSegment = await videoSegmentEditUseCase.regenerateVideoSegment(
|
||||
|
||||
const regeneratedSegment = await shotEditUseCase.regenerateVideoSegment(
|
||||
shotPrompt,
|
||||
dialogueContent,
|
||||
shotId,
|
||||
roleReplaceParams,
|
||||
sceneReplaceParams
|
||||
);
|
||||
setSelectedVideoSegment(new VideoSegmentItem(updatedVideoSegment));
|
||||
} catch (err) {
|
||||
setError(
|
||||
`重新生成视频片段失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||
);
|
||||
|
||||
// 如果重新生成的是现有片段,更新列表中的对应项
|
||||
if (shotId) {
|
||||
setVideoSegments(prev =>
|
||||
prev.map(segment =>
|
||||
segment.id === shotId ? regeneratedSegment : segment
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 如果是新生成的片段,添加到列表中
|
||||
setVideoSegments(prev => [...prev, regeneratedSegment]);
|
||||
}
|
||||
|
||||
return regeneratedSegment;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "重新生成视频片段失败";
|
||||
setError(errorMessage);
|
||||
console.error("重新生成视频片段失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[videoSegmentEditUseCase]
|
||||
[shotEditUseCase]
|
||||
);
|
||||
|
||||
/**
|
||||
* AI优化视频内容
|
||||
* @param shotId 视频片段ID
|
||||
* @param userRequirement 用户优化需求
|
||||
* @param lensData 镜头数据数组
|
||||
* @returns Promise<LensType[]> 优化后的镜头数据
|
||||
*/
|
||||
const optimizeVideoContent = useCallback(
|
||||
async (
|
||||
shotId: string,
|
||||
userRequirement: string,
|
||||
lensData: LensType[]
|
||||
): Promise<LensType[]> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const optimizedLensData = await shotEditUseCase.optimizeVideoContent(
|
||||
shotId,
|
||||
userRequirement,
|
||||
lensData
|
||||
);
|
||||
|
||||
// 注意:这里不再更新videoSegments状态,因为返回的是LensType[]而不是VideoSegmentEntity
|
||||
// 调用者需要自己处理优化后的镜头数据
|
||||
|
||||
return optimizedLensData;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "AI优化视频内容失败";
|
||||
setError(errorMessage);
|
||||
console.error("AI优化视频内容失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[shotEditUseCase]
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新视频内容
|
||||
* @param shotId 视频片段ID
|
||||
* @param content 新的对话内容
|
||||
* @returns Promise<VideoSegmentEntity> 更新后的视频片段
|
||||
*/
|
||||
const updateVideoContent = useCallback(
|
||||
async (
|
||||
shotId: string,
|
||||
content: Array<{ roleId: string; content: string }>
|
||||
): Promise<VideoSegmentEntity> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const updatedSegment = await shotEditUseCase.updateVideoContent(
|
||||
shotId,
|
||||
content
|
||||
);
|
||||
|
||||
// 更新列表中的对应项
|
||||
setVideoSegments(prev =>
|
||||
prev.map(segment =>
|
||||
segment.id === shotId ? updatedSegment : segment
|
||||
)
|
||||
);
|
||||
|
||||
return updatedSegment;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "更新视频内容失败";
|
||||
setError(errorMessage);
|
||||
console.error("更新视频内容失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[shotEditUseCase]
|
||||
);
|
||||
|
||||
/**
|
||||
* 中断当前操作
|
||||
*/
|
||||
const abortOperation = useCallback((): void => {
|
||||
shotEditUseCase.abortOperation();
|
||||
setLoading(false);
|
||||
}, [shotEditUseCase]);
|
||||
|
||||
/**
|
||||
* 设置选中的视频片段
|
||||
*/
|
||||
const setSelectedSegmentHandler = useCallback((segment: VideoSegmentEntity | null): void => {
|
||||
setSelectedSegment(segment);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 清除错误信息
|
||||
*/
|
||||
const clearError = useCallback((): void => {
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// 响应式状态 - 用于UI组件订阅和渲染
|
||||
/** 视频片段列表 - 当前项目的所有视频片段数据 */
|
||||
videoSegmentList,
|
||||
/** 当前选中的视频片段 - 包含视频片段实体和编辑状态 */
|
||||
selectedVideoSegment,
|
||||
/** 当前视频片段的草图数据URL - 从视频片段实体中获取的草图图片链接 */
|
||||
videoSegmentSketchData,
|
||||
/** 当前视频片段的视频数据URL - 从视频片段实体中获取的视频链接 */
|
||||
videoSegmentVideoData,
|
||||
|
||||
/** 用户角色库 - 当前用户可用的所有角色数据 */
|
||||
userRoleLibrary,
|
||||
/** 当前视频片段的视频剧本片段 - 通过接口获取的剧本内容 */
|
||||
videoSegmentVideoScript,
|
||||
/** 加载状态 - 标识当前是否有异步操作正在进行 */
|
||||
// 响应式状态
|
||||
loading,
|
||||
/** 错误信息 - 记录最近一次操作的错误信息 */
|
||||
videoSegments,
|
||||
selectedSegment,
|
||||
error,
|
||||
|
||||
// 操作方法 - 提供给UI组件调用的业务逻辑方法
|
||||
/** 获取视频片段列表 - 根据项目ID获取所有视频片段数据 */
|
||||
fetchVideoSegmentList,
|
||||
/** 选择视频片段并获取详情 - 选择指定视频片段并初始化相关数据 */
|
||||
selectVideoSegment,
|
||||
/** 修改视频片段对话内容 - 更新视频片段的对话内容,保持ContentItem结构不变 */
|
||||
updateVideoSegmentContent: updateVideoSegmentContentHandler,
|
||||
|
||||
/** 获取用户角色库 - 获取当前用户的所有角色数据 */
|
||||
fetchUserRoleLibrary,
|
||||
/** 替换视频片段角色 - 将视频片段中的角色替换为角色库中的另一个角色 */
|
||||
replaceVideoSegmentRole: replaceVideoSegmentRoleHandler,
|
||||
/** 获取视频片段视频剧本内容 - 通过接口获取视频剧本片段 */
|
||||
fetchVideoSegmentVideoScript,
|
||||
/** 获取视频片段关联的角色信息 - 获取当前视频片段可用的角色列表 */
|
||||
getVideoSegmentRoles,
|
||||
/** 获取视频片段关联的场景信息 - 获取当前视频片段可用的场景列表 */
|
||||
getVideoSegmentScenes,
|
||||
/** 重新生成视频片段 - 使用新参数重新生成视频片段内容 */
|
||||
// 操作方法
|
||||
getVideoSegmentList,
|
||||
regenerateVideoSegment,
|
||||
optimizeVideoContent,
|
||||
updateVideoContent,
|
||||
abortOperation,
|
||||
setSelectedSegment: setSelectedSegmentHandler,
|
||||
clearError,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用示例:
|
||||
*
|
||||
* ```tsx
|
||||
* import { useShotService } from '@/app/service/Interaction/ShotService';
|
||||
*
|
||||
* const VideoSegmentComponent = () => {
|
||||
* const {
|
||||
* loading,
|
||||
* videoSegments,
|
||||
* selectedSegment,
|
||||
* error,
|
||||
* getVideoSegmentList,
|
||||
* regenerateVideoSegment,
|
||||
* optimizeVideoContent,
|
||||
* updateVideoContent,
|
||||
* setSelectedSegment,
|
||||
* clearError
|
||||
* } = useShotService();
|
||||
*
|
||||
* // 获取视频片段列表
|
||||
* useEffect(() => {
|
||||
* getVideoSegmentList('project-id');
|
||||
* }, [getVideoSegmentList]);
|
||||
*
|
||||
* // 重新生成视频片段
|
||||
* const handleRegenerate = async () => {
|
||||
* try {
|
||||
* await regenerateVideoSegment([
|
||||
* new LensType('特写', '镜头描述', '运镜')
|
||||
* ]);
|
||||
* } catch (error) {
|
||||
* console.error('重新生成失败:', error);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // AI优化镜头数据
|
||||
* const handleOptimize = async () => {
|
||||
* try {
|
||||
* const optimizedLensData = await optimizeVideoContent(
|
||||
* 'shot-id',
|
||||
* '让镜头更加动态',
|
||||
* [new LensType('特写', '当前镜头描述', '运镜')]
|
||||
* );
|
||||
*
|
||||
* // 处理优化后的镜头数据
|
||||
* console.log('优化后的镜头数据:', optimizedLensData);
|
||||
* } catch (error) {
|
||||
* console.error('优化失败:', error);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {loading && <div>加载中...</div>}
|
||||
* {error && <div>错误: {error}</div>}
|
||||
* {videoSegments.map(segment => (
|
||||
* <div key={segment.id}>
|
||||
* <h3>{segment.name}</h3>
|
||||
* <p>状态: {segment.status}</p>
|
||||
* </div>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
|
||||
@ -68,18 +68,6 @@ export interface SceneEntity extends BaseEntity {
|
||||
generateTextId: string;
|
||||
}
|
||||
|
||||
|
||||
/**视频片段进度 */
|
||||
export enum VideoSegmentStatus {
|
||||
/** 草稿加载中 */
|
||||
sketchLoading = 0,
|
||||
/** 视频加载中 */
|
||||
videoLoading = 1,
|
||||
/** 完成 */
|
||||
finished = 2,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 视频片段实体接口
|
||||
*/
|
||||
@ -90,18 +78,8 @@ export interface VideoSegmentEntity extends BaseEntity {
|
||||
sketchUrl: string;
|
||||
/**视频片段视频Url */
|
||||
videoUrl: string[];
|
||||
/**视频片段状态 */
|
||||
status: VideoSegmentStatus;
|
||||
/**角色ID列表 */
|
||||
roleList: string[];
|
||||
/**场景ID列表 */
|
||||
sceneList: string[];
|
||||
/**对话内容 */
|
||||
content: ContentItem[];
|
||||
/**视频片段状态 0:草稿加载中 1:视频加载中 2:完成 */
|
||||
status: 0 | 1 | 2;
|
||||
/**镜头项 */
|
||||
lens: LensType[];
|
||||
/**视频片段剧本Id */
|
||||
scriptId: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -111,23 +111,3 @@ export class SceneItem extends EditItem<SceneEntity> {
|
||||
super(entity, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频片段可编辑项
|
||||
*/
|
||||
export class VideoSegmentItem extends EditItem<VideoSegmentEntity> {
|
||||
type: ItemType = ItemType.IMAGE;
|
||||
|
||||
constructor(
|
||||
entity: VideoSegmentEntity,
|
||||
metadata: Record<string, any> = {}
|
||||
) {
|
||||
super(entity, metadata);
|
||||
}
|
||||
/**
|
||||
* 更新为视频状态
|
||||
*/
|
||||
updateToVideoStatus(): void {
|
||||
this.type = ItemType.VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/**不同类型 将有不同元数据 */
|
||||
|
||||
|
||||
export enum ScriptSliceType {
|
||||
/** 文本 */
|
||||
text = "text",
|
||||
@ -54,25 +53,11 @@ export class ScriptSlice {
|
||||
* 对话内容项值对象
|
||||
* @description 代表对话中的一个内容项,按值相等判断,不可变
|
||||
*/
|
||||
export class ContentItem {
|
||||
/** 角色ID */
|
||||
readonly roleId: string;
|
||||
export interface ContentItem {
|
||||
/** 角色名称 */
|
||||
roleName: string;
|
||||
/** 对话内容 */
|
||||
readonly content: string;
|
||||
|
||||
constructor(roleId: string, content: string) {
|
||||
this.roleId = roleId;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 值对象相等性比较
|
||||
* @param other 另一个ContentItem实例
|
||||
* @returns 是否相等
|
||||
*/
|
||||
equals(other: ContentItem): boolean {
|
||||
return this.roleId === other.roleId && this.content === other.content;
|
||||
}
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,30 +65,17 @@ export class ContentItem {
|
||||
* @description 代表镜头信息,按值相等判断,不可变
|
||||
*/
|
||||
export class LensType {
|
||||
/** 镜头名称 */
|
||||
readonly name: string;
|
||||
/** 镜头描述 */
|
||||
readonly content: string;
|
||||
/**运镜描述 */
|
||||
readonly movement: string;
|
||||
/**镜头名称 */
|
||||
readonly name: string;
|
||||
/**镜头描述 */
|
||||
readonly script: string;
|
||||
/**对话内容 */
|
||||
readonly content: ContentItem[];
|
||||
|
||||
constructor(name: string, content: string, movement: string) {
|
||||
constructor(name: string, script: string, content: ContentItem[]) {
|
||||
this.name = name;
|
||||
this.script = script;
|
||||
this.content = content;
|
||||
this.movement = movement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 值对象相等性比较
|
||||
* @param other 另一个LensType实例
|
||||
* @returns 是否相等
|
||||
*/
|
||||
equals(other: LensType): boolean {
|
||||
return (
|
||||
this.name === other.name &&
|
||||
this.content === other.content &&
|
||||
this.movement === other.movement
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,13 +148,15 @@ export class StoryDetails {
|
||||
* @param text 包含故事所有细节的原始Markdown或富文本。
|
||||
*/
|
||||
public createFromText(text: string) {
|
||||
// --- 智能解析梗概 ---
|
||||
try {
|
||||
// --- 智能解析梗概 ---
|
||||
// 梗概通常位于“Core Elements”部分,描述主角和初始事件。
|
||||
// 我们提取“Protagonist”和“The Inciting Incident”的描述,并组合起来。
|
||||
const synopsis = this.extractContentByHeader(text, "Logline");
|
||||
const incitingIncidentText = this.extractContentByHeader(
|
||||
text,
|
||||
"The Inciting Incident"
|
||||
"The Inciting Incident",
|
||||
|
||||
);
|
||||
// --- 其他字段的提取保持不变,使用标题进行查找 ---
|
||||
const categories = this.extractContentByHeader(text, "GENRE")
|
||||
@ -190,7 +164,6 @@ export class StoryDetails {
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
const protagonist = this.extractContentByHeader(text, "Core Identity:");
|
||||
|
||||
const problem = this.extractContentByHeader(text, "The Problem & New Goal");
|
||||
const conflict = this.extractContentByHeader(text, "Conflict & Obstacles");
|
||||
const stakes = this.extractContentByHeader(text, "The Stakes");
|
||||
@ -209,6 +182,18 @@ export class StoryDetails {
|
||||
stakes,
|
||||
characterArc,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
synopsis: "",
|
||||
categories: [],
|
||||
protagonist: "",
|
||||
incitingIncident: "",
|
||||
problem: "",
|
||||
conflict: "",
|
||||
stakes: "",
|
||||
characterArc: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 辅助方法:根据标题提取其下的内容,并去除所有Markdown标签。
|
||||
@ -222,7 +207,7 @@ export class StoryDetails {
|
||||
debug: boolean = false
|
||||
): string {
|
||||
try {
|
||||
debug&& console.log(`正在查找标题: "${headerName}"`);
|
||||
debug && console.log(`正在查找标题: "${headerName}"`);
|
||||
|
||||
// 转义正则表达式中的特殊字符
|
||||
const escapeRegex = (text: string): string => {
|
||||
@ -304,11 +289,12 @@ export class StoryDetails {
|
||||
if (match && match[1] && match[1].trim()) {
|
||||
content = match[1].trim();
|
||||
debug && console.log(`使用模式 ${i + 1} 匹配成功`);
|
||||
debug && console.log(`匹配的完整内容: "${match[0].substring(0, 150)}..."`);
|
||||
debug &&
|
||||
console.log(`匹配的完整内容: "${match[0].substring(0, 150)}..."`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(headerName, content)
|
||||
if (!content) {
|
||||
debug && console.log(`没有找到标题 "${headerName}" 对应的内容`);
|
||||
// 尝试更宽松的匹配作为最后手段
|
||||
@ -336,10 +322,10 @@ export class StoryDetails {
|
||||
return "";
|
||||
}
|
||||
|
||||
debug && console.log(`清理后的内容: "${content.substring(0, 100)}..."`);
|
||||
debug && console.log(`清理后的内容: "${content.substring(0, 100)}..."`);
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.error("内容提取出错:", error);
|
||||
console.log("内容提取出错:", error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@ -400,4 +386,3 @@ export class ScriptValueObject {
|
||||
return this.scriptText;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -74,8 +74,6 @@ export class ScriptEditUseCase {
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async enhanceScript(
|
||||
newScript: string|string[],
|
||||
key: ScriptEditKey,
|
||||
aiOptimizing: string,
|
||||
stream_callback?: (data: any) => void
|
||||
): Promise<void> {
|
||||
@ -93,7 +91,6 @@ export class ScriptEditUseCase {
|
||||
await enhanceScriptStream(
|
||||
{
|
||||
original_script: originalScript,
|
||||
[key]: newScript,
|
||||
aiOptimizing,
|
||||
},
|
||||
(content) => {
|
||||
|
||||
@ -1,204 +1,173 @@
|
||||
import { VideoSegmentEntity, RoleEntity, SceneEntity, AITextEntity, TagEntity } from '../domain/Entities';
|
||||
import { ContentItem, LensType } from '../domain/valueObject';
|
||||
import { VideoSegmentItem, RoleItem, SceneItem, TextItem, TagItem } from '../domain/Item';
|
||||
import { VideoSegmentEntity } from "../domain/Entities";
|
||||
import { ContentItem, LensType } from "../domain/valueObject";
|
||||
import {
|
||||
getShotRoles,
|
||||
getShotScenes,
|
||||
getShotData,
|
||||
getShotList,
|
||||
regenerateShot,
|
||||
updateShotContent
|
||||
} from '@/api/video_flow';
|
||||
updateShotContent,
|
||||
optimizeShotContent,
|
||||
} from "@/api/video_flow";
|
||||
|
||||
/**
|
||||
* 视频片段编辑用例
|
||||
* 负责视频片段内容的初始化、修改和优化
|
||||
*/
|
||||
export class VideoSegmentEditUseCase {
|
||||
constructor(private videoSegmentItem: VideoSegmentItem) {
|
||||
}
|
||||
private loading: boolean = false;
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
/**
|
||||
* 获取视频片段关联的角色信息列表
|
||||
* @description 获取当前视频片段可以使用的角色列表
|
||||
* @returns Promise<RoleEntity[]> 角色信息列表
|
||||
* @throws {Error} 当API调用失败时抛出错误
|
||||
* @description 获取视频片段列表
|
||||
* @param projectId 项目ID
|
||||
* @returns Promise<VideoSegmentEntity[]> 视频片段列表
|
||||
*/
|
||||
async getVideoSegmentRoles(): Promise<RoleEntity[]> {
|
||||
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||
async getVideoSegmentList(projectId: string): Promise<VideoSegmentEntity[]> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
if (!videoSegmentId) {
|
||||
throw new Error('视频片段ID不存在,无法获取角色信息');
|
||||
}
|
||||
const response = await getShotList({ projectId });
|
||||
|
||||
const response = await getShotRoles({
|
||||
shotId: videoSegmentId
|
||||
});
|
||||
if (!response.successful) {
|
||||
throw new Error(response.message || "获取视频片段列表失败");
|
||||
}
|
||||
|
||||
if (response.successful) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(`获取视频片段角色信息失败: ${response.message}`);
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error("获取视频片段列表失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频片段关联的场景信息列表
|
||||
* @description 获取当前视频片段可以使用的场景列表
|
||||
* @returns Promise<SceneEntity[]> 场景信息列表
|
||||
* @throws {Error} 当API调用失败时抛出错误
|
||||
*/
|
||||
async getVideoSegmentScenes(): Promise<SceneEntity[]> {
|
||||
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||
|
||||
if (!videoSegmentId) {
|
||||
throw new Error('视频片段ID不存在,无法获取场景信息');
|
||||
}
|
||||
|
||||
const response = await getShotScenes({
|
||||
shotId: videoSegmentId
|
||||
});
|
||||
|
||||
if (response.successful) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(`获取视频片段场景信息失败: ${response.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新获取当前视频片段信息
|
||||
* @description 从服务器重新获取当前视频片段的详细数据,并更新当前实体
|
||||
* @returns Promise<{ text: AITextEntity; tags: TagEntity[] }> 视频片段相关的AI文本和标签数据
|
||||
* @throws {Error} 当API调用失败时抛出错误
|
||||
*/
|
||||
async refreshVideoSegmentData(): Promise<{ text: AITextEntity; tags: TagEntity[] }> {
|
||||
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||
|
||||
if (!videoSegmentId) {
|
||||
throw new Error('视频片段ID不存在,无法获取视频片段数据');
|
||||
}
|
||||
|
||||
const response = await getShotData({
|
||||
shotId: videoSegmentId
|
||||
});
|
||||
|
||||
if (response.successful) {
|
||||
// 更新当前视频片段的实体数据
|
||||
const { text, tags } = response.data;
|
||||
|
||||
// 更新视频片段实体中的相关字段
|
||||
const updatedVideoSegmentEntity = {
|
||||
...this.videoSegmentItem.entity,
|
||||
generateTextId: text.id, // 更新AI文本ID
|
||||
tagIds: tags.map((tag: TagEntity) => tag.id), // 更新标签ID列表
|
||||
updatedAt: Date.now(), // 更新时间戳
|
||||
};
|
||||
|
||||
// 更新当前UseCase中的实体
|
||||
this.videoSegmentItem.setEntity(updatedVideoSegmentEntity);
|
||||
// 检查状态是否需要更新为视频状态
|
||||
this.checkAndUpdateVideoStatus(updatedVideoSegmentEntity);
|
||||
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(`获取视频片段数据失败: ${response.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新生成当前视频片段
|
||||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成视频片段
|
||||
* @param shotPrompt 镜头描述
|
||||
* @param dialogueContent 对话内容
|
||||
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
||||
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
||||
* @returns Promise<VideoSegmentEntity> 重新生成的视频片段实体
|
||||
* @throws {Error} 当API调用失败时抛出错误
|
||||
* @description 通过视频镜头描述数据重新生成视频
|
||||
* @param shotPrompt 镜头描述数据
|
||||
* @param shotId 视频片段ID(可选,如果重新生成现有片段)
|
||||
* @param roleReplaceParams 角色替换参数(可选)
|
||||
* @param sceneReplaceParams 场景替换参数(可选)
|
||||
* @returns Promise<VideoSegmentEntity> 重新生成的视频片段
|
||||
*/
|
||||
async regenerateVideoSegment(
|
||||
shotPrompt: LensType[],
|
||||
dialogueContent: ContentItem[],
|
||||
roleReplaceParams: { oldId: string; newId: string }[],
|
||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||
shotId?: string,
|
||||
roleReplaceParams?: { oldId: string; newId: string }[],
|
||||
sceneReplaceParams?: { oldId: string; newId: string }[]
|
||||
): Promise<VideoSegmentEntity> {
|
||||
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
if (!videoSegmentId) {
|
||||
throw new Error('视频片段ID不存在,无法重新生成视频片段');
|
||||
}
|
||||
// 创建新的中断控制器
|
||||
this.abortController = new AbortController();
|
||||
|
||||
// 调用重新生成视频片段接口
|
||||
const response = await regenerateShot({
|
||||
shotId: videoSegmentId,
|
||||
shotPrompt: shotPrompt,
|
||||
dialogueContent: dialogueContent,
|
||||
roleReplaceParams: roleReplaceParams,
|
||||
sceneReplaceParams: sceneReplaceParams,
|
||||
});
|
||||
const response = await regenerateShot({
|
||||
shotId,
|
||||
shotPrompt,
|
||||
roleReplaceParams,
|
||||
sceneReplaceParams,
|
||||
});
|
||||
|
||||
if (response.successful) {
|
||||
const videoSegmentEntity = response.data;
|
||||
this.videoSegmentItem.setEntity(videoSegmentEntity);
|
||||
// 检查状态是否需要更新为视频状态
|
||||
this.checkAndUpdateVideoStatus(videoSegmentEntity);
|
||||
return videoSegmentEntity;
|
||||
} else {
|
||||
throw new Error(`重新生成视频片段失败: ${response.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改视频片段对话内容
|
||||
* @description 更新视频片段的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||||
* @param newContent 新的对话内容数组
|
||||
* @returns Promise<VideoSegmentEntity> 修改后的视频片段实体
|
||||
* @throws {Error} 当API调用失败时抛出错误
|
||||
*/
|
||||
async updateVideoSegmentContent(newContent: Array<{ roleId: string; content: string }>): Promise<VideoSegmentEntity> {
|
||||
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||
|
||||
if (!videoSegmentId) {
|
||||
throw new Error('视频片段ID不存在,无法修改对话内容');
|
||||
}
|
||||
|
||||
// 验证ContentItem数量和ID顺序
|
||||
const currentContent = this.videoSegmentItem.entity.content;
|
||||
if (newContent.length !== currentContent.length) {
|
||||
throw new Error('ContentItem数量不能改变');
|
||||
}
|
||||
|
||||
// 验证角色ID顺序
|
||||
for (let i = 0; i < newContent.length; i++) {
|
||||
if (newContent[i].roleId !== currentContent[i].roleId) {
|
||||
throw new Error('ContentItem的roleId顺序不能改变');
|
||||
if (!response.successful) {
|
||||
throw new Error(response.message || "重新生成视频片段失败");
|
||||
}
|
||||
}
|
||||
|
||||
const response = await updateShotContent({
|
||||
shotId: videoSegmentId,
|
||||
content: newContent,
|
||||
});
|
||||
|
||||
if (response.successful) {
|
||||
const videoSegmentEntity = response.data;
|
||||
this.videoSegmentItem.setEntity(videoSegmentEntity);
|
||||
// 检查状态是否需要更新为视频状态
|
||||
this.checkAndUpdateVideoStatus(videoSegmentEntity);
|
||||
return videoSegmentEntity;
|
||||
} else {
|
||||
throw new Error(`修改视频片段对话内容失败: ${response.message}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (this.abortController?.signal.aborted) {
|
||||
console.log("视频片段重新生成被中断");
|
||||
throw new Error("操作被中断");
|
||||
}
|
||||
console.error("重新生成视频片段失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并更新视频状态
|
||||
* @description 当视频片段状态变为视频加载中或完成时,调用updateToVideoStatus
|
||||
* @param videoSegmentEntity 视频片段实体
|
||||
* @description 通过AI优化镜头数据(包含对话内容)
|
||||
* @param shotId 视频片段ID
|
||||
* @param userRequirement 用户优化需求
|
||||
* @param lensData 镜头数据数组
|
||||
* @returns Promise<LensType[]> 优化后的镜头数据
|
||||
*/
|
||||
private checkAndUpdateVideoStatus(videoSegmentEntity: VideoSegmentEntity): void {
|
||||
// 当状态为视频加载中或完成时,更新为视频状态
|
||||
if (videoSegmentEntity.status === 1 || videoSegmentEntity.status === 2) { // videoLoading 或 finished
|
||||
this.videoSegmentItem.updateToVideoStatus();
|
||||
async optimizeVideoContent(
|
||||
shotId: string,
|
||||
userRequirement: string,
|
||||
lensData: LensType[]
|
||||
): Promise<LensType[]> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
// 调用AI优化接口
|
||||
const response = await optimizeShotContent({
|
||||
shotId,
|
||||
userRequirement,
|
||||
lensData,
|
||||
});
|
||||
|
||||
if (!response.successful) {
|
||||
throw new Error(response.message || "AI优化视频内容失败");
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("AI优化视频内容失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新视频片段的对话内容
|
||||
* @param shotId 视频片段ID
|
||||
* @param content 新的对话内容
|
||||
* @returns Promise<VideoSegmentEntity> 更新后的视频片段
|
||||
*/
|
||||
async updateVideoContent(
|
||||
shotId: string,
|
||||
content: Array<{
|
||||
roleId: string;
|
||||
content: string;
|
||||
}>
|
||||
): Promise<VideoSegmentEntity> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
const response = await updateShotContent({
|
||||
shotId,
|
||||
content,
|
||||
});
|
||||
|
||||
if (!response.successful) {
|
||||
throw new Error(response.message || "更新视频内容失败");
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("更新视频内容失败:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 中断当前操作
|
||||
*/
|
||||
abortOperation(): void {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取加载状态
|
||||
* @returns boolean 是否正在加载
|
||||
*/
|
||||
isLoading(): boolean {
|
||||
return this.loading;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user