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