移除不必要的对话内容参数,更新视频片段服务接口以支持视频片段列表获取、视频内容更新和AI优化功能;重构相关逻辑以简化代码结构并提高可读性。

This commit is contained in:
海龙 2025-08-07 18:50:23 +08:00
parent 884aa5794f
commit 4c3d4fb079
9 changed files with 476 additions and 616 deletions

View File

@ -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);
};

View File

@ -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 {

View File

@ -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>
* );
* };
* ```
*/

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) => {

View File

@ -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;
}
}