forked from 77media/video-flow
更新配置文件以支持测试和视频片段功能;在 jest.config.js 中设置全局测试超时为 30 秒;在 tsconfig.json 中添加新的编译选项以增强类型检查;新增 tsconfig.test.json 文件以支持测试环境配置;在 video_flow.ts 中重构 API 接口以使用 VideoSegmentEntity 替代 ShotEntity,并更新相关服务和测试用例以反映这些更改。
This commit is contained in:
parent
508065107a
commit
ab10493248
@ -1 +1 @@
|
|||||||
export const BASE_URL = "https://77.smartvideo.py.qikongjian.com"
|
export const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL
|
||||||
|
|||||||
@ -28,6 +28,8 @@ request.interceptors.request.use(
|
|||||||
request.interceptors.response.use(
|
request.interceptors.response.use(
|
||||||
(response: AxiosResponse) => {
|
(response: AxiosResponse) => {
|
||||||
// 直接返回响应数据
|
// 直接返回响应数据
|
||||||
|
console.log('?????????????????????????',Object.keys(response.data));
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
|||||||
@ -1,9 +1,19 @@
|
|||||||
import { post } from './request';
|
import { post, streamJsonPost } from "./request";
|
||||||
import { ProjectTypeEnum } from '@/app/model/enums';
|
import { ProjectTypeEnum } from "@/app/model/enums";
|
||||||
import { ApiResponse } from '@/api/common';
|
import { ApiResponse } from "@/api/common";
|
||||||
import { BASE_URL } from './constants'
|
import { BASE_URL } from "./constants";
|
||||||
import { AITextEntity, RoleEntity, SceneEntity, ShotEntity, TagEntity } from '@/app/service/domain/Entities';
|
import {
|
||||||
import { ContentItem, LensType, ScriptSlice } from "@/app/service/domain/valueObject";
|
AITextEntity,
|
||||||
|
RoleEntity,
|
||||||
|
SceneEntity,
|
||||||
|
VideoSegmentEntity,
|
||||||
|
TagEntity,
|
||||||
|
} from "@/app/service/domain/Entities";
|
||||||
|
import {
|
||||||
|
ContentItem,
|
||||||
|
LensType,
|
||||||
|
ScriptSlice,
|
||||||
|
} from "@/app/service/domain/valueObject";
|
||||||
|
|
||||||
// API 响应类型
|
// API 响应类型
|
||||||
interface BaseApiResponse<T> {
|
interface BaseApiResponse<T> {
|
||||||
@ -17,12 +27,12 @@ interface BaseApiResponse<T> {
|
|||||||
interface EpisodeDetail {
|
interface EpisodeDetail {
|
||||||
project_id: string;
|
project_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
status: 'running' | 'completed';
|
status: "running" | "completed";
|
||||||
step: 'sketch' | 'character' | 'video' | 'music' | 'final_video';
|
step: "sketch" | "character" | "video" | "music" | "final_video";
|
||||||
last_message: string;
|
last_message: string;
|
||||||
data: TaskData | null;
|
data: TaskData | null;
|
||||||
mode: 'auto' | 'manual';
|
mode: "auto" | "manual";
|
||||||
resolution: '1080p' | '4k';
|
resolution: "1080p" | "4k";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务数据类型
|
// 任务数据类型
|
||||||
@ -59,10 +69,10 @@ interface TaskData {
|
|||||||
|
|
||||||
// 流式数据类型
|
// 流式数据类型
|
||||||
export interface StreamData<T = any> {
|
export interface StreamData<T = any> {
|
||||||
category: 'sketch' | 'character' | 'video' | 'music' | 'final_video';
|
category: "sketch" | "character" | "video" | "music" | "final_video";
|
||||||
message: string;
|
message: string;
|
||||||
data: T;
|
data: T;
|
||||||
status: 'running' | 'completed';
|
status: "running" | "completed";
|
||||||
total?: number;
|
total?: number;
|
||||||
completed?: number;
|
completed?: number;
|
||||||
all_completed?: boolean;
|
all_completed?: boolean;
|
||||||
@ -113,7 +123,9 @@ export interface VideoToSceneRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 转换分镜头请求类型
|
// 转换分镜头请求类型
|
||||||
export type ConvertScenePromptRequest = ScriptToSceneRequest | VideoToSceneRequest;
|
export type ConvertScenePromptRequest =
|
||||||
|
| ScriptToSceneRequest
|
||||||
|
| VideoToSceneRequest;
|
||||||
|
|
||||||
// 转换分镜头响应接口
|
// 转换分镜头响应接口
|
||||||
export type ConvertScenePromptResponse = BaseApiResponse<ScenePrompts>;
|
export type ConvertScenePromptResponse = BaseApiResponse<ScenePrompts>;
|
||||||
@ -131,17 +143,17 @@ export const convertScenePrompt = async (
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve({
|
resolve({
|
||||||
code: 0,
|
code: 0,
|
||||||
message: 'success',
|
message: "success",
|
||||||
data: {
|
data: {
|
||||||
scenes: [],
|
scenes: [],
|
||||||
characters: [],
|
characters: [],
|
||||||
summary: '',
|
summary: "",
|
||||||
scene: '',
|
scene: "",
|
||||||
atmosphere: '',
|
atmosphere: "",
|
||||||
episode_id: 0,
|
episode_id: 0,
|
||||||
total_shots: ''
|
total_shots: "",
|
||||||
},
|
},
|
||||||
successful: true
|
successful: true,
|
||||||
});
|
});
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
@ -161,7 +173,7 @@ export const convertScriptToScene = async (
|
|||||||
script,
|
script,
|
||||||
episode_id,
|
episode_id,
|
||||||
script_id,
|
script_id,
|
||||||
project_type: ProjectTypeEnum.SCRIPT_TO_VIDEO
|
project_type: ProjectTypeEnum.SCRIPT_TO_VIDEO,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,43 +191,57 @@ export const convertVideoToScene = async (
|
|||||||
video_url,
|
video_url,
|
||||||
episode_id,
|
episode_id,
|
||||||
script_id,
|
script_id,
|
||||||
project_type: ProjectTypeEnum.VIDEO_TO_VIDEO
|
project_type: ProjectTypeEnum.VIDEO_TO_VIDEO,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 新-获取剧集详情
|
// 新-获取剧集详情
|
||||||
export const detailScriptEpisodeNew = async (data: { project_id: string }): Promise<ApiResponse<any>> => {
|
export const detailScriptEpisodeNew = async (data: {
|
||||||
return post<ApiResponse<any>>('/movie/get_movie_project_detail', data);
|
project_id: string;
|
||||||
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_movie_project_detail", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 title 接口
|
// 获取 title 接口
|
||||||
export const getScriptTitle = async (data: { project_id: string }): Promise<ApiResponse<any>> => {
|
export const getScriptTitle = async (data: {
|
||||||
return post<ApiResponse<any>>('/movie/get_movie_project_description', data);
|
project_id: string;
|
||||||
}
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_movie_project_description", data);
|
||||||
|
};
|
||||||
|
|
||||||
// 获取 数据 全量(需轮询)
|
// 获取 数据 全量(需轮询)
|
||||||
export const getRunningStreamData = async (data: { project_id: string }): Promise<ApiResponse<any>> => {
|
export const getRunningStreamData = async (data: {
|
||||||
return post<ApiResponse<any>>('/movie/get_status', data);
|
project_id: string;
|
||||||
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_status", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 脚本 接口
|
// 获取 脚本 接口
|
||||||
export const getScriptTags = async (data: { project_id: string }): Promise<any> => {
|
export const getScriptTags = async (data: {
|
||||||
return post<any>('/movie/text_to_script_tags', data);
|
project_id: string;
|
||||||
|
}): Promise<any> => {
|
||||||
|
return post<any>("/movie/text_to_script_tags", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 loading-场景 接口
|
// 获取 loading-场景 接口
|
||||||
export const getSceneJson = async (data: { project_id: string }): Promise<ApiResponse<any>> => {
|
export const getSceneJson = async (data: {
|
||||||
return post<any>('/movie/scene_json', data);
|
project_id: string;
|
||||||
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post<any>("/movie/scene_json", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 loading-分镜 接口
|
// 获取 loading-分镜 接口
|
||||||
export const getShotSketchJson = async (data: { project_id: string }): Promise<ApiResponse<any>> => {
|
export const getShotSketchJson = async (data: {
|
||||||
return post<any>('/movie/shot_sketch_json', data);
|
project_id: string;
|
||||||
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post<any>("/movie/shot_sketch_json", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 loading-视频 接口
|
// 获取 loading-视频 接口
|
||||||
export const getVideoJson = async (data: { project_id: string }): Promise<ApiResponse<any>> => {
|
export const getVideoJson = async (data: {
|
||||||
return post<any>('/movie/video_json', data);
|
project_id: string;
|
||||||
|
}): Promise<ApiResponse<any>> => {
|
||||||
|
return post<any>("/movie/video_json", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -231,7 +257,7 @@ export const regenerateRole = async (request: {
|
|||||||
/** 角色ID(可选,如果重新生成现有角色) */
|
/** 角色ID(可选,如果重新生成现有角色) */
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<ApiResponse<RoleEntity>> => {
|
}): Promise<ApiResponse<RoleEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/regenerate_role', request);
|
return post<ApiResponse<any>>("/movie/regenerate_role", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -245,7 +271,7 @@ export const applyRoleToShots = async (request: {
|
|||||||
/** 分镜ID列表 */
|
/** 分镜ID列表 */
|
||||||
shotIds: string[];
|
shotIds: string[];
|
||||||
}): Promise<ApiResponse<any>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post<ApiResponse<any>>('/movie/apply_role_to_shots', request);
|
return post<ApiResponse<any>>("/movie/apply_role_to_shots", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,13 +282,15 @@ export const applyRoleToShots = async (request: {
|
|||||||
export const getRoleShots = async (request: {
|
export const getRoleShots = async (request: {
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleId: string;
|
roleId: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** 分镜列表 */
|
/** 分镜列表 */
|
||||||
shots: ShotEntity[];
|
shots: VideoSegmentEntity[];
|
||||||
/** 已应用的分镜ID列表 */
|
/** 已应用的分镜ID列表 */
|
||||||
appliedShotIds: string[];
|
appliedShotIds: string[];
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<any>>('/movie/get_role_shots', request);
|
> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_role_shots", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -274,7 +302,7 @@ export const getRoleList = async (request: {
|
|||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}): Promise<ApiResponse<RoleEntity[]>> => {
|
}): Promise<ApiResponse<RoleEntity[]>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_role_list', request);
|
return post<ApiResponse<any>>("/movie/get_role_list", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -285,21 +313,25 @@ export const getRoleList = async (request: {
|
|||||||
export const getRoleData = async (request: {
|
export const getRoleData = async (request: {
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleId: string;
|
roleId: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** AI文本数据 */
|
/** AI文本数据 */
|
||||||
text: AITextEntity;
|
text: AITextEntity;
|
||||||
/** 标签列表 */
|
/** 标签列表 */
|
||||||
tags: TagEntity[];
|
tags: TagEntity[];
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<any>>('/movie/get_role_data', request);
|
> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_role_data", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户角色库
|
* 获取用户角色库
|
||||||
* @returns Promise<ApiResponse<角色实体列表>>
|
* @returns Promise<ApiResponse<角色实体列表>>
|
||||||
*/
|
*/
|
||||||
export const getUserRoleLibrary = async (): Promise<ApiResponse<RoleEntity[]>> => {
|
export const getUserRoleLibrary = async (): Promise<
|
||||||
return post<ApiResponse<any>>('/movie/get_user_role_library', {});
|
ApiResponse<RoleEntity[]>
|
||||||
|
> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_user_role_library", {});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -313,10 +345,9 @@ export const replaceRole = async (request: {
|
|||||||
/** 替换的角色ID */
|
/** 替换的角色ID */
|
||||||
replaceRoleId: string;
|
replaceRoleId: string;
|
||||||
}): Promise<ApiResponse<any>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post<ApiResponse<any>>('/movie/replace_role', request);
|
return post<ApiResponse<any>>("/movie/replace_role", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改标签
|
* 修改标签
|
||||||
* @param request - 修改标签请求参数
|
* @param request - 修改标签请求参数
|
||||||
@ -328,7 +359,7 @@ export const updateTag = async (request: {
|
|||||||
/** 新的标签内容 */
|
/** 新的标签内容 */
|
||||||
content: string | number;
|
content: string | number;
|
||||||
}): Promise<ApiResponse<TagEntity>> => {
|
}): Promise<ApiResponse<TagEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/update_tag', request);
|
return post<ApiResponse<any>>("/movie/update_tag", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,7 +373,7 @@ export const updateText = async (request: {
|
|||||||
/** 新的文案内容 */
|
/** 新的文案内容 */
|
||||||
content: string;
|
content: string;
|
||||||
}): Promise<ApiResponse<AITextEntity>> => {
|
}): Promise<ApiResponse<AITextEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/update_text', request);
|
return post<ApiResponse<any>>("/movie/update_text", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -358,7 +389,7 @@ export const regenerateScene = async (request: {
|
|||||||
/** 场景ID(可选,如果重新生成现有场景) */
|
/** 场景ID(可选,如果重新生成现有场景) */
|
||||||
sceneId?: string;
|
sceneId?: string;
|
||||||
}): Promise<ApiResponse<SceneEntity>> => {
|
}): Promise<ApiResponse<SceneEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/regenerate_scene', request);
|
return post<ApiResponse<any>>("/movie/regenerate_scene", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,7 +403,7 @@ export const applySceneToShots = async (request: {
|
|||||||
/** 分镜ID列表 */
|
/** 分镜ID列表 */
|
||||||
shotIds: string[];
|
shotIds: string[];
|
||||||
}): Promise<ApiResponse<any>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post<ApiResponse<any>>('/movie/apply_scene_to_shots', request);
|
return post<ApiResponse<any>>("/movie/apply_scene_to_shots", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -383,13 +414,15 @@ export const applySceneToShots = async (request: {
|
|||||||
export const getSceneData = async (request: {
|
export const getSceneData = async (request: {
|
||||||
/** 场景ID */
|
/** 场景ID */
|
||||||
sceneId: string;
|
sceneId: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** AI文本数据 */
|
/** AI文本数据 */
|
||||||
text: AITextEntity;
|
text: AITextEntity;
|
||||||
/** 标签列表 */
|
/** 标签列表 */
|
||||||
tags: TagEntity[];
|
tags: TagEntity[];
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<any>>('/movie/get_scene_data', request);
|
> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_scene_data", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -401,7 +434,7 @@ export const getSceneList = async (request: {
|
|||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}): Promise<ApiResponse<SceneEntity[]>> => {
|
}): Promise<ApiResponse<SceneEntity[]>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_scene_list', request);
|
return post<ApiResponse<any>>("/movie/get_scene_list", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -412,13 +445,15 @@ export const getSceneList = async (request: {
|
|||||||
export const getSceneShots = async (request: {
|
export const getSceneShots = async (request: {
|
||||||
/** 场景ID */
|
/** 场景ID */
|
||||||
sceneId: string;
|
sceneId: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** 分镜列表 */
|
/** 分镜列表 */
|
||||||
shots: ShotEntity[];
|
shots: VideoSegmentEntity[];
|
||||||
/** 已应用的分镜ID列表 */
|
/** 已应用的分镜ID列表 */
|
||||||
appliedShotIds: string[];
|
appliedShotIds: string[];
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<any>>('/movie/get_scene_shots', request);
|
> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_scene_shots", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -430,7 +465,7 @@ export const getShotRoles = async (request: {
|
|||||||
/** 分镜ID */
|
/** 分镜ID */
|
||||||
shotId: string;
|
shotId: string;
|
||||||
}): Promise<ApiResponse<RoleEntity[]>> => {
|
}): Promise<ApiResponse<RoleEntity[]>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_roles', request);
|
return post<ApiResponse<any>>("/movie/get_shot_roles", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -442,7 +477,7 @@ export const getShotScenes = async (request: {
|
|||||||
/** 分镜ID */
|
/** 分镜ID */
|
||||||
shotId: string;
|
shotId: string;
|
||||||
}): Promise<ApiResponse<SceneEntity[]>> => {
|
}): Promise<ApiResponse<SceneEntity[]>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_scenes', request);
|
return post<ApiResponse<any>>("/movie/get_shot_scenes", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -453,13 +488,15 @@ export const getShotScenes = async (request: {
|
|||||||
export const getShotData = async (request: {
|
export const getShotData = async (request: {
|
||||||
/** 分镜ID */
|
/** 分镜ID */
|
||||||
shotId: string;
|
shotId: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** AI文本数据 */
|
/** AI文本数据 */
|
||||||
text: AITextEntity;
|
text: AITextEntity;
|
||||||
/** 标签列表 */
|
/** 标签列表 */
|
||||||
tags: TagEntity[];
|
tags: TagEntity[];
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_data', request);
|
> => {
|
||||||
|
return post<ApiResponse<any>>("/movie/get_shot_data", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -478,8 +515,8 @@ export const regenerateShot = async (request: {
|
|||||||
roleReplaceParams?: { oldId: string; newId: string }[];
|
roleReplaceParams?: { oldId: string; newId: string }[];
|
||||||
/** 场景ID替换参数,格式为{oldId:string,newId:string}[] */
|
/** 场景ID替换参数,格式为{oldId:string,newId:string}[] */
|
||||||
sceneReplaceParams?: { oldId: string; newId: string }[];
|
sceneReplaceParams?: { oldId: string; newId: string }[];
|
||||||
}): Promise<ApiResponse<ShotEntity>> => {
|
}): Promise<ApiResponse<VideoSegmentEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/regenerate_shot', request);
|
return post<ApiResponse<any>>("/movie/regenerate_shot", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -497,8 +534,8 @@ export const updateShotContent = async (request: {
|
|||||||
/** 对话内容 */
|
/** 对话内容 */
|
||||||
content: string;
|
content: string;
|
||||||
}>;
|
}>;
|
||||||
}): Promise<ApiResponse<ShotEntity>> => {
|
}): Promise<ApiResponse<VideoSegmentEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/update_shot_content', request);
|
return post<ApiResponse<any>>("/movie/update_shot_content", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -509,35 +546,8 @@ export const updateShotContent = async (request: {
|
|||||||
export const getShotList = async (request: {
|
export const getShotList = async (request: {
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}): Promise<ApiResponse<ShotEntity[]>> => {
|
}): Promise<ApiResponse<VideoSegmentEntity[]>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_list', request);
|
return post<ApiResponse<any>>("/movie/get_shot_list", request);
|
||||||
};
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 获取分镜详情
|
|
||||||
// * @param request - 获取分镜详情请求参数
|
|
||||||
// * @returns Promise<ApiResponse<分镜实体>>
|
|
||||||
// */
|
|
||||||
// export const getShotDetail = async (request: {
|
|
||||||
// /** 分镜ID */
|
|
||||||
// shotId: string;
|
|
||||||
// }): Promise<ApiResponse<ShotEntity>> => {
|
|
||||||
// return post<ApiResponse<any>>('/movie/get_shot_detail', request);
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改分镜镜头
|
|
||||||
* @param request - 修改分镜镜头请求参数
|
|
||||||
* @returns Promise<ApiResponse<修改后的分镜>>
|
|
||||||
*/
|
|
||||||
export const updateShotShot = async (request: {
|
|
||||||
/** 分镜ID */
|
|
||||||
shotId: string;
|
|
||||||
/** 新的镜头数据 */
|
|
||||||
shot: string[];
|
|
||||||
}): Promise<ApiResponse<ShotEntity>> => {
|
|
||||||
return post<ApiResponse<any>>('/movie/update_shot_shot', request);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -553,7 +563,7 @@ export const replaceShotRole = async (request: {
|
|||||||
/** 新角色ID */
|
/** 新角色ID */
|
||||||
newRoleId: string;
|
newRoleId: string;
|
||||||
}): Promise<ApiResponse<any>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post<ApiResponse<any>>('/movie/replace_shot_role', request);
|
return post<ApiResponse<any>>("/movie/replace_shot_role", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -565,7 +575,7 @@ export const getShotVideoScript = async (request: {
|
|||||||
/** 分镜ID */
|
/** 分镜ID */
|
||||||
shotId: string;
|
shotId: string;
|
||||||
}): Promise<ApiResponse<string>> => {
|
}): Promise<ApiResponse<string>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_video_script', request);
|
return post<ApiResponse<any>>("/movie/get_shot_video_script", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -573,13 +583,30 @@ export const getShotVideoScript = async (request: {
|
|||||||
* @param request - AI生成剧本请求参数
|
* @param request - AI生成剧本请求参数
|
||||||
* @returns Promise<ApiResponse<流式数据>>
|
* @returns Promise<ApiResponse<流式数据>>
|
||||||
*/
|
*/
|
||||||
export const generateScriptStream = async (request: {
|
export const generateScriptStream = (
|
||||||
|
request: {
|
||||||
/** 剧本提示词 */
|
/** 剧本提示词 */
|
||||||
text: string;
|
text: string;
|
||||||
}) => {
|
},
|
||||||
return post<ApiResponse<any>>('/text_to_script/generate_script_stream', request,{
|
onData: (data: any) => void
|
||||||
responseType: 'stream',
|
): Promise<void> => {
|
||||||
});
|
return new Promise((resolve, reject) => {
|
||||||
|
streamJsonPost("/text_to_script/generate_script_stream", request, (data) => {
|
||||||
|
switch(data.status) {
|
||||||
|
case 'streaming':
|
||||||
|
onData(data.content);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'completed':
|
||||||
|
console.log('生成完成:', data.message);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
console.error('生成失败:', data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -593,7 +620,7 @@ export const applyScriptToShot = async (request: {
|
|||||||
/** 剧本*/
|
/** 剧本*/
|
||||||
scriptText: string;
|
scriptText: string;
|
||||||
}): Promise<ApiResponse<any>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post<ApiResponse<any>>('/movie/apply_script_to_shot', request);
|
return post<ApiResponse<any>>("/movie/apply_script_to_shot", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -603,16 +630,20 @@ export const applyScriptToShot = async (request: {
|
|||||||
export const getProjectScript = async (request: {
|
export const getProjectScript = async (request: {
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** 用户提示词 */
|
/** 用户提示词 */
|
||||||
prompt: string;
|
prompt: string;
|
||||||
/** 生成的剧本文本 */
|
/** 生成的剧本文本 */
|
||||||
scriptText: string;
|
scriptText: string;
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<{
|
> => {
|
||||||
|
return post<
|
||||||
|
ApiResponse<{
|
||||||
prompt: string;
|
prompt: string;
|
||||||
scriptText: string;
|
scriptText: string;
|
||||||
}>>('/movie/get_project_script', request);
|
}>
|
||||||
|
>("/movie/get_project_script", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -626,7 +657,7 @@ export const saveScript = async (request: {
|
|||||||
/** 剧本文本 */
|
/** 剧本文本 */
|
||||||
scriptText: string;
|
scriptText: string;
|
||||||
}): Promise<ApiResponse<any>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post<ApiResponse<any>>('/movie/save_script', request);
|
return post<ApiResponse<any>>("/movie/save_script", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -639,11 +670,15 @@ export const createProject = async (request: {
|
|||||||
userPrompt: string;
|
userPrompt: string;
|
||||||
/** 剧本内容 */
|
/** 剧本内容 */
|
||||||
scriptContent: string;
|
scriptContent: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<
|
||||||
|
ApiResponse<{
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}>> => {
|
}>
|
||||||
return post<ApiResponse<{
|
> => {
|
||||||
|
return post<
|
||||||
|
ApiResponse<{
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}>>('/movie/create_project', request);
|
}>
|
||||||
|
>("/movie/create_project", request);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { RoleEntity, TagEntity, AITextEntity, ShotEntity } from '../domain/Entities';
|
import { RoleEntity, TagEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
||||||
import { RoleItem, TagItem, TextItem, ShotItem } from '../domain/Item';
|
import { RoleItem, TagItem, TextItem, ShotItem } from '../domain/Item';
|
||||||
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
@ -19,7 +19,7 @@ interface ShotSelectionItem {
|
|||||||
/** 是否已应用角色 */
|
/** 是否已应用角色 */
|
||||||
applied: boolean;
|
applied: boolean;
|
||||||
/** 分镜数据 */
|
/** 分镜数据 */
|
||||||
shot: ShotEntity;
|
shot: VideoSegmentEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { SceneEntity, TagEntity, AITextEntity, ShotEntity } from '../domain/Entities';
|
import { SceneEntity, TagEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
||||||
import { SceneItem, TagItem, TextItem, ShotItem } from '../domain/Item';
|
import { SceneItem, TagItem, TextItem, ShotItem } from '../domain/Item';
|
||||||
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
@ -19,7 +19,7 @@ interface ShotSelectionItem {
|
|||||||
/** 是否已应用场景 */
|
/** 是否已应用场景 */
|
||||||
applied: boolean;
|
applied: boolean;
|
||||||
/** 分镜数据 */
|
/** 分镜数据 */
|
||||||
shot: ShotEntity;
|
shot: VideoSegmentEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -79,6 +79,8 @@ export const useScriptService = (): UseScriptService => {
|
|||||||
// 获取生成的剧本文本
|
// 获取生成的剧本文本
|
||||||
const generatedScriptText = newScriptEditUseCase.toString();
|
const generatedScriptText = newScriptEditUseCase.toString();
|
||||||
setScriptText(generatedScriptText);
|
setScriptText(generatedScriptText);
|
||||||
|
console.log(scriptText);
|
||||||
|
|
||||||
// 获取剧本片段列表
|
// 获取剧本片段列表
|
||||||
const slices = newScriptEditUseCase.getScriptSlices();
|
const slices = newScriptEditUseCase.getScriptSlices();
|
||||||
setScriptSlices(slices);
|
setScriptSlices(slices);
|
||||||
|
|||||||
@ -1,71 +1,68 @@
|
|||||||
import { useState, useCallback, useMemo } from "react";
|
import { useState, useCallback, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
ShotEntity,
|
VideoSegmentEntity,
|
||||||
RoleEntity,
|
RoleEntity,
|
||||||
SceneEntity,
|
SceneEntity,
|
||||||
} from "../domain/Entities";
|
} from "../domain/Entities";
|
||||||
import { ContentItem, ScriptSlice, ScriptValueObject } from "../domain/valueObject";
|
import { ContentItem, ScriptSlice, ScriptValueObject, LensType } from "../domain/valueObject";
|
||||||
import { ShotItem } from "../domain/Item";
|
import { VideoSegmentItem } from "../domain/Item";
|
||||||
import { ShotEditUseCase } from "../usecase/ShotEditUsecase";
|
import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase";
|
||||||
import {
|
import {
|
||||||
getShotList,
|
getShotList,
|
||||||
// getShotDetail,
|
// getShotDetail,
|
||||||
updateShotContent,
|
updateShotContent,
|
||||||
updateShotShot,
|
|
||||||
getUserRoleLibrary,
|
getUserRoleLibrary,
|
||||||
replaceShotRole,
|
replaceShotRole,
|
||||||
getShotVideoScript,
|
getShotVideoScript,
|
||||||
} from "@/api/video_flow";
|
} from "@/api/video_flow";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分镜服务Hook接口
|
* 视频片段服务Hook接口
|
||||||
* 定义分镜服务Hook的所有状态和操作方法
|
* 定义视频片段服务Hook的所有状态和操作方法
|
||||||
*/
|
*/
|
||||||
export interface UseShotService {
|
export interface UseVideoSegmentService {
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
/** 分镜列表 */
|
/** 视频片段列表 */
|
||||||
shotList: ShotEntity[];
|
videoSegmentList: VideoSegmentEntity[];
|
||||||
/** 当前选中的分镜 */
|
/** 当前选中的视频片段 */
|
||||||
selectedShot: ShotItem | null;
|
selectedVideoSegment: VideoSegmentItem | null;
|
||||||
/** 当前分镜的草图数据URL */
|
/** 当前视频片段的草图数据URL */
|
||||||
shotSketchData: string | null;
|
videoSegmentSketchData: string | null;
|
||||||
/** 当前分镜的视频数据URL */
|
/** 当前视频片段的视频数据URL */
|
||||||
shotVideoData: string[] | null;
|
videoSegmentVideoData: string[] | null;
|
||||||
|
|
||||||
/** 用户角色库 */
|
/** 用户角色库 */
|
||||||
userRoleLibrary: RoleEntity[];
|
userRoleLibrary: RoleEntity[];
|
||||||
/** 当前分镜的视频剧本片段 */
|
/** 当前视频片段的视频剧本片段 */
|
||||||
shotVideoScript: ScriptSlice[];
|
videoSegmentVideoScript: ScriptSlice[];
|
||||||
/** 加载状态 */
|
/** 加载状态 */
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
|
||||||
// 操作方法
|
// 操作方法
|
||||||
/** 获取分镜列表 */
|
/** 获取视频片段列表 */
|
||||||
fetchShotList: (projectId: string) => Promise<void>;
|
fetchVideoSegmentList: (projectId: string) => Promise<void>;
|
||||||
/** 选择分镜并获取详情 */
|
/** 选择视频片段并获取详情 */
|
||||||
selectShot: (shotId: string) => Promise<void>;
|
selectVideoSegment: (videoSegmentId: string) => Promise<void>;
|
||||||
/** 修改分镜对话内容 */
|
/** 修改视频片段对话内容 */
|
||||||
updateShotContent: (
|
updateVideoSegmentContent: (
|
||||||
newContent: Array<{ roleId: string; content: string }>
|
newContent: Array<{ roleId: string; content: string }>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
/** 修改分镜镜头 */
|
|
||||||
updateShotShot: (newShot: string[]) => Promise<void>;
|
|
||||||
/** 获取用户角色库 */
|
/** 获取用户角色库 */
|
||||||
fetchUserRoleLibrary: () => Promise<void>;
|
fetchUserRoleLibrary: () => Promise<void>;
|
||||||
/** 替换分镜角色 */
|
/** 替换视频片段角色 */
|
||||||
replaceShotRole: (oldRoleId: string, newRoleId: string) => Promise<void>;
|
replaceVideoSegmentRole: (oldRoleId: string, newRoleId: string) => Promise<void>;
|
||||||
/** 获取分镜视频剧本内容 */
|
/** 获取视频片段视频剧本内容 */
|
||||||
fetchShotVideoScript: () => Promise<void>;
|
fetchVideoSegmentVideoScript: () => Promise<void>;
|
||||||
/** 获取分镜关联的角色信息 */
|
/** 获取视频片段关联的角色信息 */
|
||||||
getShotRoles: () => Promise<RoleEntity[]>;
|
getVideoSegmentRoles: () => Promise<RoleEntity[]>;
|
||||||
/** 获取分镜关联的场景信息 */
|
/** 获取视频片段关联的场景信息 */
|
||||||
getShotScenes: () => Promise<SceneEntity[]>;
|
getVideoSegmentScenes: () => Promise<SceneEntity[]>;
|
||||||
|
|
||||||
/** 重新生成分镜 */
|
/** 重新生成视频片段 */
|
||||||
regenerateShot: (
|
regenerateVideoSegment: (
|
||||||
shotPrompt: string,
|
shotPrompt: LensType[],
|
||||||
dialogueContent: ContentItem[],
|
dialogueContent: ContentItem[],
|
||||||
roleReplaceParams: { oldId: string; newId: string }[],
|
roleReplaceParams: { oldId: string; newId: string }[],
|
||||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||||
@ -73,44 +70,44 @@ export interface UseShotService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分镜服务Hook
|
* 视频片段服务Hook
|
||||||
* 提供分镜相关的所有状态管理和操作方法
|
* 提供视频片段相关的所有状态管理和操作方法
|
||||||
* 包括分镜列表管理、分镜选择、数据获取、内容修改等功能
|
* 包括视频片段列表管理、视频片段选择、数据获取、内容修改等功能
|
||||||
*/
|
*/
|
||||||
export const useShotService = (): UseShotService => {
|
export const useVideoSegmentService = (): UseVideoSegmentService => {
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const [shotList, setShotList] = useState<ShotEntity[]>([]);
|
const [videoSegmentList, setVideoSegmentList] = useState<VideoSegmentEntity[]>([]);
|
||||||
const [selectedShot, setSelectedShot] = useState<ShotItem | null>(null);
|
const [selectedVideoSegment, setSelectedVideoSegment] = useState<VideoSegmentItem | null>(null);
|
||||||
const [shotSketchData, setShotSketchData] = useState<string | null>(null);
|
const [videoSegmentSketchData, setVideoSegmentSketchData] = useState<string | null>(null);
|
||||||
const [shotVideoData, setShotVideoData] = useState<string[] | null>(null);
|
const [videoSegmentVideoData, setVideoSegmentVideoData] = useState<string[] | null>(null);
|
||||||
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
||||||
const [shotVideoScript, setShotVideoScript] = useState<ScriptSlice[]>([]);
|
const [videoSegmentVideoScript, setVideoSegmentVideoScript] = useState<ScriptSlice[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [projectId, setProjectId] = useState<string>("");
|
const [projectId, setProjectId] = useState<string>("");
|
||||||
// UseCase实例
|
// UseCase实例
|
||||||
const [shotEditUseCase, setShotEditUseCase] =
|
const [videoSegmentEditUseCase, setVideoSegmentEditUseCase] =
|
||||||
useState<ShotEditUseCase | null>(null);
|
useState<VideoSegmentEditUseCase | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜列表
|
* 获取视频片段列表
|
||||||
* @description 根据项目ID获取所有分镜列表
|
* @description 根据项目ID获取所有视频片段列表
|
||||||
* @param projectId 项目ID
|
* @param projectId 项目ID
|
||||||
*/
|
*/
|
||||||
const fetchShotList = useCallback(async (projectId: string) => {
|
const fetchVideoSegmentList = useCallback(async (projectId: string) => {
|
||||||
setProjectId(projectId);
|
setProjectId(projectId);
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const response = await getShotList({ projectId });
|
const response = await getShotList({ projectId });
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
setShotList(response.data);
|
setVideoSegmentList(response.data);
|
||||||
} else {
|
} else {
|
||||||
setError(`获取分镜列表失败: ${response.message}`);
|
setError(`获取视频片段列表失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
`获取分镜列表失败: ${err instanceof Error ? err.message : "未知错误"}`
|
`获取视频片段列表失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -118,37 +115,37 @@ export const useShotService = (): UseShotService => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选择分镜并获取详情
|
* 选择视频片段并获取详情
|
||||||
* @description 根据分镜ID获取分镜详情,并初始化相关的UseCase和数据
|
* @description 根据视频片段ID获取视频片段详情,并初始化相关的UseCase和数据
|
||||||
* @param shotId 分镜ID
|
* @param videoSegmentId 视频片段ID
|
||||||
*/
|
*/
|
||||||
const selectShot = useCallback(async (shotId: string) => {
|
const selectVideoSegment = useCallback(async (videoSegmentId: string) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 获取分镜详情
|
// 获取视频片段详情
|
||||||
await fetchShotList(projectId);
|
await fetchVideoSegmentList(projectId);
|
||||||
const shotEntity = shotList.find(
|
const videoSegmentEntity = videoSegmentList.find(
|
||||||
(shot: ShotEntity) => shot.id === shotId
|
(videoSegment: VideoSegmentEntity) => videoSegment.id === videoSegmentId
|
||||||
);
|
);
|
||||||
if (!shotEntity) {
|
if (!videoSegmentEntity) {
|
||||||
setError(`分镜不存在: ${shotId}`);
|
setError(`视频片段不存在: ${videoSegmentId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const shotItem = new ShotItem(shotEntity);
|
const videoSegmentItem = new VideoSegmentItem(videoSegmentEntity);
|
||||||
setSelectedShot(shotItem);
|
setSelectedVideoSegment(videoSegmentItem);
|
||||||
|
|
||||||
// 初始化UseCase
|
// 初始化UseCase
|
||||||
const newShotEditUseCase = new ShotEditUseCase(shotItem);
|
const newVideoSegmentEditUseCase = new VideoSegmentEditUseCase(videoSegmentItem);
|
||||||
setShotEditUseCase(newShotEditUseCase);
|
setVideoSegmentEditUseCase(newVideoSegmentEditUseCase);
|
||||||
|
|
||||||
// 从分镜实体中获取草图数据和视频数据
|
// 从视频片段实体中获取草图数据和视频数据
|
||||||
setShotSketchData(shotEntity.sketchUrl || null);
|
setVideoSegmentSketchData(videoSegmentEntity.sketchUrl || null);
|
||||||
setShotVideoData(shotEntity.videoUrl || null);
|
setVideoSegmentVideoData(videoSegmentEntity.videoUrl || null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
`选择分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
|
`选择视频片段失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -156,25 +153,25 @@ export const useShotService = (): UseShotService => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改分镜对话内容
|
* 修改视频片段对话内容
|
||||||
* @description 更新分镜的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
* @description 更新视频片段的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||||||
* @param newContent 新的对话内容数组
|
* @param newContent 新的对话内容数组
|
||||||
*/
|
*/
|
||||||
const updateShotContentHandler = useCallback(
|
const updateVideoSegmentContentHandler = useCallback(
|
||||||
async (newContent: Array<{ roleId: string; content: string }>) => {
|
async (newContent: Array<{ roleId: string; content: string }>) => {
|
||||||
if (!shotEditUseCase) {
|
if (!videoSegmentEditUseCase) {
|
||||||
setError("分镜编辑用例未初始化");
|
setError("视频片段编辑用例未初始化");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const updatedShot = await shotEditUseCase.updateShotContent(newContent);
|
const updatedVideoSegment = await videoSegmentEditUseCase.updateVideoSegmentContent(newContent);
|
||||||
setSelectedShot(new ShotItem(updatedShot));
|
setSelectedVideoSegment(new VideoSegmentItem(updatedVideoSegment));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
`修改分镜对话内容失败: ${
|
`修改视频片段对话内容失败: ${
|
||||||
err instanceof Error ? err.message : "未知错误"
|
err instanceof Error ? err.message : "未知错误"
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
@ -182,44 +179,9 @@ export const useShotService = (): UseShotService => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[shotEditUseCase]
|
[videoSegmentEditUseCase]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改分镜镜头
|
|
||||||
* @description 更新分镜的镜头数据
|
|
||||||
* @param newShot 新的镜头数据
|
|
||||||
*/
|
|
||||||
const updateShotShotHandler = useCallback(
|
|
||||||
async (newShot: string[]) => {
|
|
||||||
if (!selectedShot) {
|
|
||||||
setError("未选择分镜");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
const response = await updateShotShot({
|
|
||||||
shotId: selectedShot.entity.id,
|
|
||||||
shot: newShot,
|
|
||||||
});
|
|
||||||
if (response.successful) {
|
|
||||||
const updatedShot = response.data;
|
|
||||||
setSelectedShot(new ShotItem(updatedShot));
|
|
||||||
} else {
|
|
||||||
setError(`修改分镜镜头失败: ${response.message}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setError(
|
|
||||||
`修改分镜镜头失败: ${err instanceof Error ? err.message : "未知错误"}`
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[selectedShot]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户角色库
|
* 获取用户角色库
|
||||||
@ -245,15 +207,15 @@ export const useShotService = (): UseShotService => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 替换分镜角色
|
* 替换视频片段角色
|
||||||
* @description 将分镜中的某个角色替换为角色库中的另一个角色
|
* @description 将视频片段中的某个角色替换为角色库中的另一个角色
|
||||||
* @param oldRoleId 旧角色ID
|
* @param oldRoleId 旧角色ID
|
||||||
* @param newRoleId 新角色ID
|
* @param newRoleId 新角色ID
|
||||||
*/
|
*/
|
||||||
const replaceShotRoleHandler = useCallback(
|
const replaceVideoSegmentRoleHandler = useCallback(
|
||||||
async (oldRoleId: string, newRoleId: string) => {
|
async (oldRoleId: string, newRoleId: string) => {
|
||||||
if (!selectedShot) {
|
if (!selectedVideoSegment) {
|
||||||
setError("未选择分镜");
|
setError("未选择视频片段");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,34 +223,34 @@ export const useShotService = (): UseShotService => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const response = await replaceShotRole({
|
const response = await replaceShotRole({
|
||||||
shotId: selectedShot.entity.id,
|
shotId: selectedVideoSegment.entity.id,
|
||||||
oldRoleId,
|
oldRoleId,
|
||||||
newRoleId,
|
newRoleId,
|
||||||
});
|
});
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
// 重新获取分镜详情
|
// 重新获取视频片段详情
|
||||||
await selectShot(selectedShot.entity.id);
|
await selectVideoSegment(selectedVideoSegment.entity.id);
|
||||||
} else {
|
} else {
|
||||||
setError(`替换分镜角色失败: ${response.message}`);
|
setError(`替换视频片段角色失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
`替换分镜角色失败: ${err instanceof Error ? err.message : "未知错误"}`
|
`替换视频片段角色失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedShot, selectShot]
|
[selectedVideoSegment, selectVideoSegment]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜视频剧本内容
|
* 获取视频片段视频剧本内容
|
||||||
* @description 获取分镜视频的剧本内容,通过接口返回多个ScriptSliceEntity片段
|
* @description 获取视频片段视频的剧本内容,通过接口返回多个ScriptSliceEntity片段
|
||||||
*/
|
*/
|
||||||
const fetchShotVideoScript = useCallback(async () => {
|
const fetchVideoSegmentVideoScript = useCallback(async () => {
|
||||||
if (!selectedShot) {
|
if (!selectedVideoSegment) {
|
||||||
setError("未选择分镜");
|
setError("未选择视频片段");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,130 +258,129 @@ export const useShotService = (): UseShotService => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const response = await getShotVideoScript({
|
const response = await getShotVideoScript({
|
||||||
shotId: selectedShot.entity.id,
|
shotId: selectedVideoSegment.entity.id,
|
||||||
});
|
});
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
const script = new ScriptValueObject(response.data);
|
const script = new ScriptValueObject(response.data);
|
||||||
setShotVideoScript(script.scriptSlices);
|
setVideoSegmentVideoScript([...script.scriptSlices]);
|
||||||
} else {
|
} else {
|
||||||
setError(`获取分镜视频剧本失败: ${response.message}`);
|
setError(`获取视频片段视频剧本失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
`获取分镜视频剧本失败: ${
|
`获取视频片段视频剧本失败: ${
|
||||||
err instanceof Error ? err.message : "未知错误"
|
err instanceof Error ? err.message : "未知错误"
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [selectedShot]);
|
}, [selectedVideoSegment]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜关联的角色信息
|
* 获取视频片段关联的角色信息
|
||||||
* @description 获取当前分镜可以使用的角色列表
|
* @description 获取当前视频片段可以使用的角色列表
|
||||||
* @returns Promise<RoleEntity[]> 角色信息列表
|
* @returns Promise<RoleEntity[]> 角色信息列表
|
||||||
*/
|
*/
|
||||||
const getShotRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
const getVideoSegmentRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
||||||
if (!shotEditUseCase) {
|
if (!videoSegmentEditUseCase) {
|
||||||
throw new Error("分镜编辑用例未初始化");
|
throw new Error("视频片段编辑用例未初始化");
|
||||||
}
|
}
|
||||||
return await shotEditUseCase.getShotRoles();
|
return await videoSegmentEditUseCase.getVideoSegmentRoles();
|
||||||
}, [shotEditUseCase]);
|
}, [videoSegmentEditUseCase]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜关联的场景信息
|
* 获取视频片段关联的场景信息
|
||||||
* @description 获取当前分镜可以使用的场景列表
|
* @description 获取当前视频片段可以使用的场景列表
|
||||||
* @returns Promise<SceneEntity[]> 场景信息列表
|
* @returns Promise<SceneEntity[]> 场景信息列表
|
||||||
*/
|
*/
|
||||||
const getShotScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
const getVideoSegmentScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
||||||
if (!shotEditUseCase) {
|
if (!videoSegmentEditUseCase) {
|
||||||
throw new Error("分镜编辑用例未初始化");
|
throw new Error("视频片段编辑用例未初始化");
|
||||||
}
|
}
|
||||||
return await shotEditUseCase.getShotScenes();
|
return await videoSegmentEditUseCase.getVideoSegmentScenes();
|
||||||
}, [shotEditUseCase]);
|
}, [videoSegmentEditUseCase]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新生成分镜
|
* 重新生成视频片段
|
||||||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成分镜
|
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成视频片段
|
||||||
* @param shotPrompt 镜头描述
|
* @param shotPrompt 镜头描述
|
||||||
* @param dialogueContent 对话内容
|
* @param dialogueContent 对话内容
|
||||||
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
||||||
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
||||||
*/
|
*/
|
||||||
const regenerateShot = useCallback(
|
const regenerateVideoSegment = useCallback(
|
||||||
async (
|
async (
|
||||||
shotPrompt: string,
|
shotPrompt: LensType[],
|
||||||
dialogueContent: ContentItem[],
|
dialogueContent: ContentItem[],
|
||||||
roleReplaceParams: { oldId: string; newId: string }[],
|
roleReplaceParams: { oldId: string; newId: string }[],
|
||||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||||
) => {
|
) => {
|
||||||
if (!shotEditUseCase) {
|
if (!videoSegmentEditUseCase) {
|
||||||
setError("分镜编辑用例未初始化");
|
setError("视频片段编辑用例未初始化");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const updatedShot = await shotEditUseCase.regenerateShot(
|
const updatedVideoSegment = await videoSegmentEditUseCase.regenerateVideoSegment(
|
||||||
shotPrompt,
|
shotPrompt,
|
||||||
dialogueContent,
|
dialogueContent,
|
||||||
roleReplaceParams,
|
roleReplaceParams,
|
||||||
sceneReplaceParams
|
sceneReplaceParams
|
||||||
);
|
);
|
||||||
setSelectedShot(new ShotItem(updatedShot));
|
setSelectedVideoSegment(new VideoSegmentItem(updatedVideoSegment));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
`重新生成分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
|
`重新生成视频片段失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[shotEditUseCase]
|
[videoSegmentEditUseCase]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 响应式状态 - 用于UI组件订阅和渲染
|
// 响应式状态 - 用于UI组件订阅和渲染
|
||||||
/** 分镜列表 - 当前项目的所有分镜数据 */
|
/** 视频片段列表 - 当前项目的所有视频片段数据 */
|
||||||
shotList,
|
videoSegmentList,
|
||||||
/** 当前选中的分镜 - 包含分镜实体和编辑状态 */
|
/** 当前选中的视频片段 - 包含视频片段实体和编辑状态 */
|
||||||
selectedShot,
|
selectedVideoSegment,
|
||||||
/** 当前分镜的草图数据URL - 从分镜实体中获取的草图图片链接 */
|
/** 当前视频片段的草图数据URL - 从视频片段实体中获取的草图图片链接 */
|
||||||
shotSketchData,
|
videoSegmentSketchData,
|
||||||
/** 当前分镜的视频数据URL - 从分镜实体中获取的视频链接 */
|
/** 当前视频片段的视频数据URL - 从视频片段实体中获取的视频链接 */
|
||||||
shotVideoData,
|
videoSegmentVideoData,
|
||||||
|
|
||||||
/** 用户角色库 - 当前用户可用的所有角色数据 */
|
/** 用户角色库 - 当前用户可用的所有角色数据 */
|
||||||
userRoleLibrary,
|
userRoleLibrary,
|
||||||
/** 当前分镜的视频剧本片段 - 通过接口获取的剧本内容 */
|
/** 当前视频片段的视频剧本片段 - 通过接口获取的剧本内容 */
|
||||||
shotVideoScript,
|
videoSegmentVideoScript,
|
||||||
/** 加载状态 - 标识当前是否有异步操作正在进行 */
|
/** 加载状态 - 标识当前是否有异步操作正在进行 */
|
||||||
loading,
|
loading,
|
||||||
/** 错误信息 - 记录最近一次操作的错误信息 */
|
/** 错误信息 - 记录最近一次操作的错误信息 */
|
||||||
error,
|
error,
|
||||||
|
|
||||||
// 操作方法 - 提供给UI组件调用的业务逻辑方法
|
// 操作方法 - 提供给UI组件调用的业务逻辑方法
|
||||||
/** 获取分镜列表 - 根据项目ID获取所有分镜数据 */
|
/** 获取视频片段列表 - 根据项目ID获取所有视频片段数据 */
|
||||||
fetchShotList,
|
fetchVideoSegmentList,
|
||||||
/** 选择分镜并获取详情 - 选择指定分镜并初始化相关数据 */
|
/** 选择视频片段并获取详情 - 选择指定视频片段并初始化相关数据 */
|
||||||
selectShot,
|
selectVideoSegment,
|
||||||
/** 修改分镜对话内容 - 更新分镜的对话内容,保持ContentItem结构不变 */
|
/** 修改视频片段对话内容 - 更新视频片段的对话内容,保持ContentItem结构不变 */
|
||||||
updateShotContent: updateShotContentHandler,
|
updateVideoSegmentContent: updateVideoSegmentContentHandler,
|
||||||
/** 修改分镜镜头 - 更新分镜的镜头数据 */
|
|
||||||
updateShotShot: updateShotShotHandler,
|
|
||||||
/** 获取用户角色库 - 获取当前用户的所有角色数据 */
|
/** 获取用户角色库 - 获取当前用户的所有角色数据 */
|
||||||
fetchUserRoleLibrary,
|
fetchUserRoleLibrary,
|
||||||
/** 替换分镜角色 - 将分镜中的角色替换为角色库中的另一个角色 */
|
/** 替换视频片段角色 - 将视频片段中的角色替换为角色库中的另一个角色 */
|
||||||
replaceShotRole: replaceShotRoleHandler,
|
replaceVideoSegmentRole: replaceVideoSegmentRoleHandler,
|
||||||
/** 获取分镜视频剧本内容 - 通过接口获取视频剧本片段 */
|
/** 获取视频片段视频剧本内容 - 通过接口获取视频剧本片段 */
|
||||||
fetchShotVideoScript,
|
fetchVideoSegmentVideoScript,
|
||||||
/** 获取分镜关联的角色信息 - 获取当前分镜可用的角色列表 */
|
/** 获取视频片段关联的角色信息 - 获取当前视频片段可用的角色列表 */
|
||||||
getShotRoles,
|
getVideoSegmentRoles,
|
||||||
/** 获取分镜关联的场景信息 - 获取当前分镜可用的场景列表 */
|
/** 获取视频片段关联的场景信息 - 获取当前视频片段可用的场景列表 */
|
||||||
getShotScenes,
|
getVideoSegmentScenes,
|
||||||
/** 重新生成分镜 - 使用新参数重新生成分镜内容 */
|
/** 重新生成视频片段 - 使用新参数重新生成视频片段内容 */
|
||||||
regenerateShot,
|
regenerateVideoSegment,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -69,8 +69,8 @@ export interface SceneEntity extends BaseEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**分镜进度 */
|
/**视频片段进度 */
|
||||||
export enum ShotStatus {
|
export enum VideoSegmentStatus {
|
||||||
/** 草稿加载中 */
|
/** 草稿加载中 */
|
||||||
sketchLoading = 0,
|
sketchLoading = 0,
|
||||||
/** 视频加载中 */
|
/** 视频加载中 */
|
||||||
@ -81,17 +81,17 @@ export enum ShotStatus {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分镜实体接口
|
* 视频片段实体接口
|
||||||
*/
|
*/
|
||||||
export interface ShotEntity extends BaseEntity {
|
export interface VideoSegmentEntity extends BaseEntity {
|
||||||
/** 分镜名称 */
|
/** 视频片段名称 */
|
||||||
name: string;
|
name: string;
|
||||||
/**分镜草图Url */
|
/**视频片段草图Url */
|
||||||
sketchUrl: string;
|
sketchUrl: string;
|
||||||
/**分镜视频Url */
|
/**视频片段视频Url */
|
||||||
videoUrl: string[];
|
videoUrl: string[];
|
||||||
/**分镜状态 */
|
/**视频片段状态 */
|
||||||
status: ShotStatus;
|
status: VideoSegmentStatus;
|
||||||
/**角色ID列表 */
|
/**角色ID列表 */
|
||||||
roleList: string[];
|
roleList: string[];
|
||||||
/**场景ID列表 */
|
/**场景ID列表 */
|
||||||
@ -100,7 +100,7 @@ export interface ShotEntity extends BaseEntity {
|
|||||||
content: ContentItem[];
|
content: ContentItem[];
|
||||||
/**镜头项 */
|
/**镜头项 */
|
||||||
lens: LensType[];
|
lens: LensType[];
|
||||||
/**分镜剧本Id */
|
/**视频片段剧本Id */
|
||||||
scriptId: string;
|
scriptId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
RoleEntity,
|
RoleEntity,
|
||||||
TagEntity,
|
TagEntity,
|
||||||
SceneEntity,
|
SceneEntity,
|
||||||
ShotEntity
|
VideoSegmentEntity
|
||||||
} from './Entities';
|
} from './Entities';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,13 +113,13 @@ export class SceneItem extends EditItem<SceneEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分镜可编辑项
|
* 视频片段可编辑项
|
||||||
*/
|
*/
|
||||||
export class ShotItem extends EditItem<ShotEntity> {
|
export class VideoSegmentItem extends EditItem<VideoSegmentEntity> {
|
||||||
type: ItemType = ItemType.IMAGE;
|
type: ItemType = ItemType.IMAGE;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
entity: ShotEntity,
|
entity: VideoSegmentEntity,
|
||||||
metadata: Record<string, any> = {}
|
metadata: Record<string, any> = {}
|
||||||
) {
|
) {
|
||||||
super(entity, metadata);
|
super(entity, metadata);
|
||||||
|
|||||||
@ -8,41 +8,118 @@ export enum ScriptSliceType {
|
|||||||
/** 场景 */
|
/** 场景 */
|
||||||
scene = "scene",
|
scene = "scene",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剧本片段值对象接口
|
* 剧本片段值对象
|
||||||
|
* @description 代表剧本中的一个片段,按值相等判断,不可变
|
||||||
*/
|
*/
|
||||||
export interface ScriptSlice {
|
export class ScriptSlice {
|
||||||
/**唯一标识符 */
|
/**唯一标识符 - 仅作为局部唯一性标识,不作为全局Entity id */
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
/** 类型 */
|
/** 类型 */
|
||||||
type: ScriptSliceType;
|
readonly type: ScriptSliceType;
|
||||||
/** 剧本内容 */
|
/** 剧本内容 */
|
||||||
text: string;
|
readonly text: string;
|
||||||
/** 元数据 */
|
/** 元数据 */
|
||||||
metaData: any;
|
metaData: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
type: ScriptSliceType,
|
||||||
|
text: string,
|
||||||
|
metaData: any = {}
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.type = type;
|
||||||
|
this.text = text;
|
||||||
|
this.metaData = metaData;
|
||||||
}
|
}
|
||||||
/**对话内容项 */
|
|
||||||
export interface ContentItem {
|
|
||||||
/** 角色ID */
|
|
||||||
roleId: string;
|
|
||||||
/** 对话内容 */
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
/**镜头值对象 */
|
|
||||||
export interface LensType {
|
|
||||||
/** 镜头名称 */
|
|
||||||
name: string;
|
|
||||||
/** 镜头描述 */
|
|
||||||
content: string;
|
|
||||||
/**运镜描述 */
|
|
||||||
movement: string;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* @description: 剧本 值对象,将剧本文本转换为剧本对象
|
* 值对象相等性比较
|
||||||
* @return {*}
|
* @param other 另一个ScriptSlice实例
|
||||||
|
* @returns 是否相等
|
||||||
|
*/
|
||||||
|
equals(other: ScriptSlice): boolean {
|
||||||
|
return (
|
||||||
|
this.type === other.type &&
|
||||||
|
this.text === other.text &&
|
||||||
|
JSON.stringify(this.metaData) === JSON.stringify(other.metaData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话内容项值对象
|
||||||
|
* @description 代表对话中的一个内容项,按值相等判断,不可变
|
||||||
|
*/
|
||||||
|
export class ContentItem {
|
||||||
|
/** 角色ID */
|
||||||
|
readonly roleId: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 镜头值对象
|
||||||
|
* @description 代表镜头信息,按值相等判断,不可变
|
||||||
|
*/
|
||||||
|
export class LensType {
|
||||||
|
/** 镜头名称 */
|
||||||
|
readonly name: string;
|
||||||
|
/** 镜头描述 */
|
||||||
|
readonly content: string;
|
||||||
|
/**运镜描述 */
|
||||||
|
readonly movement: string;
|
||||||
|
|
||||||
|
constructor(name: string, content: string, movement: string) {
|
||||||
|
this.name = name;
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剧本聚合根
|
||||||
|
* @description 作为聚合根,封装ScriptSlice的管理与行为
|
||||||
*/
|
*/
|
||||||
export class ScriptValueObject {
|
export class ScriptValueObject {
|
||||||
scriptSlices: ScriptSlice[] = [];
|
/** 剧本片段数组 - 值对象数组 */
|
||||||
|
private readonly _scriptSlices: ScriptSlice[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取剧本片段数组的只读副本
|
||||||
|
*/
|
||||||
|
get scriptSlices(): readonly ScriptSlice[] {
|
||||||
|
return [...this._scriptSlices];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 构造函数,初始化剧本
|
* @description: 构造函数,初始化剧本
|
||||||
@ -59,35 +136,29 @@ export class ScriptValueObject {
|
|||||||
* @param scriptText 剧本文本字符串
|
* @param scriptText 剧本文本字符串
|
||||||
*/
|
*/
|
||||||
parseFromString(scriptText: string): void {
|
parseFromString(scriptText: string): void {
|
||||||
// TODO: 实现字符串解析逻辑
|
console.log('scriptText', scriptText)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 检测剧本片段类型
|
|
||||||
* @param text 文本内容
|
|
||||||
* @returns ScriptSliceType
|
|
||||||
*/
|
|
||||||
private detectScriptType(text: string): ScriptSliceType {
|
|
||||||
// TODO: 实现类型检测逻辑
|
|
||||||
return ScriptSliceType.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 提取元数据
|
|
||||||
* @param text 文本内容
|
|
||||||
* @returns any
|
|
||||||
*/
|
|
||||||
private extractMetadata(text: string): any {
|
|
||||||
// TODO: 实现元数据提取逻辑
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 将剧本片段转换为字符串
|
* @description: 将剧本片段转换为字符串
|
||||||
* @returns string
|
* @returns string
|
||||||
*/
|
*/
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return this.scriptSlices.map((slice) => slice.text).join("");
|
return this._scriptSlices.map((slice) => slice.text).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚合根相等性比较
|
||||||
|
* @param other 另一个ScriptValueObject实例
|
||||||
|
* @returns 是否相等
|
||||||
|
*/
|
||||||
|
equals(other: ScriptValueObject): boolean {
|
||||||
|
if (this._scriptSlices.length !== other._scriptSlices.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._scriptSlices.every((slice, index) =>
|
||||||
|
slice.equals(other._scriptSlices[index])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
|||||||
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
import { RoleItem, TextItem, TagItem } from '../domain/Item';
|
import { RoleItem, TextItem, TagItem } from '../domain/Item';
|
||||||
import { RoleEntity, AITextEntity, TagEntity, ShotEntity, ShotStatus } from '../domain/Entities';
|
import { RoleEntity, AITextEntity, TagEntity, VideoSegmentEntity, ShotStatus } from '../domain/Entities';
|
||||||
|
|
||||||
// Mock API模块
|
// Mock API模块
|
||||||
jest.mock('@/api/video_flow', () => ({
|
jest.mock('@/api/video_flow', () => ({
|
||||||
@ -66,7 +66,7 @@ describe('RoleService 业务逻辑测试', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const mockShotEntity: ShotEntity = {
|
const mockShotEntity: VideoSegmentEntity = {
|
||||||
id: 'shot1',
|
id: 'shot1',
|
||||||
name: '分镜1',
|
name: '分镜1',
|
||||||
sketchUrl: 'http://example.com/sketch1.jpg',
|
sketchUrl: 'http://example.com/sketch1.jpg',
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
|||||||
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
import { SceneItem, TextItem, TagItem } from '../domain/Item';
|
import { SceneItem, TextItem, TagItem } from '../domain/Item';
|
||||||
import { SceneEntity, AITextEntity, TagEntity, ShotEntity, ShotStatus } from '../domain/Entities';
|
import { SceneEntity, AITextEntity, TagEntity, VideoSegmentEntity, ShotStatus } from '../domain/Entities';
|
||||||
|
|
||||||
// Mock API模块
|
// Mock API模块
|
||||||
jest.mock('@/api/video_flow', () => ({
|
jest.mock('@/api/video_flow', () => ({
|
||||||
@ -71,7 +71,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
disableEdit: false,
|
disableEdit: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockShotEntity: ShotEntity = {
|
const mockShotEntity: VideoSegmentEntity = {
|
||||||
id: 'shot1',
|
id: 'shot1',
|
||||||
name: '分镜1',
|
name: '分镜1',
|
||||||
sketchUrl: 'http://example.com/sketch1.jpg',
|
sketchUrl: 'http://example.com/sketch1.jpg',
|
||||||
|
|||||||
@ -1,46 +1,27 @@
|
|||||||
import { generateScriptStream } from '../../../api/video_flow';
|
// Mock localStorage
|
||||||
|
global.localStorage = {
|
||||||
|
getItem: jest.fn(() => 'mock-token'),
|
||||||
|
setItem: jest.fn(),
|
||||||
|
removeItem: jest.fn(),
|
||||||
|
clear: jest.fn(),
|
||||||
|
length: 0,
|
||||||
|
key: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock BASE_URL
|
||||||
|
jest.mock('../../../api/constants', () => ({
|
||||||
|
BASE_URL: 'http://127.0.0.1:8000'
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase";
|
||||||
|
|
||||||
describe('ScriptService 业务逻辑测试', () => {
|
describe('ScriptService 业务逻辑测试', () => {
|
||||||
describe('generateScriptStream 真实接口测试', () => {
|
|
||||||
it('应该成功调用 generateScriptStream 接口并输出流数据', async () => {
|
// 创建新的剧本编辑用例
|
||||||
/**
|
const newScriptEditUseCase = new ScriptEditUseCase('');
|
||||||
* 测试 generateScriptStream 流式接口,持续监听数据直到流结束
|
it("想法生成剧本", async () => {
|
||||||
*/
|
const res = await newScriptEditUseCase.generateScript("我想拍一个关于爱情的故事",(content)=>{
|
||||||
const stream = await generateScriptStream({
|
console.log(content);
|
||||||
text: '一个年轻人在咖啡店里等待他的约会对象,心情紧张地摆弄着手机。'
|
|
||||||
});
|
|
||||||
|
|
||||||
let allData = '';
|
|
||||||
let isSuccessful: boolean | undefined = undefined;
|
|
||||||
let message: string | undefined = undefined;
|
|
||||||
|
|
||||||
// 假设 stream 是一个异步可迭代对象
|
|
||||||
try {
|
|
||||||
for await (const chunk of stream.data) {
|
|
||||||
console.log(chunk);
|
|
||||||
|
|
||||||
if (typeof chunk.data === 'string') {
|
|
||||||
allData += chunk.data;
|
|
||||||
}
|
|
||||||
if (typeof chunk.successful === 'boolean') {
|
|
||||||
isSuccessful = chunk.successful;
|
|
||||||
}
|
|
||||||
if (typeof chunk.message === 'string') {
|
|
||||||
message = chunk.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('流式接口监听出错:', err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输出流式数据及状态
|
|
||||||
console.log('generateScriptStream 流式数据:', allData);
|
|
||||||
console.log('响应状态:', isSuccessful);
|
|
||||||
console.log('响应消息:', message);
|
|
||||||
|
|
||||||
expect(allData).toBeDefined();
|
|
||||||
expect(typeof isSuccessful).toBe('boolean');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}, 300000); // 30秒超时
|
||||||
});
|
});
|
||||||
|
|||||||
13
app/service/test/Shot.test.ts
Normal file
13
app/service/test/Shot.test.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useVideoSegmentService } from '../Interaction/ShotService';
|
||||||
|
|
||||||
|
describe('VideoSegmentService 测试', () => {
|
||||||
|
it('初始化服务实例', () => {
|
||||||
|
const videoSegmentService = useVideoSegmentService();
|
||||||
|
expect(videoSegmentService).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('获取视频片段列表', () => {
|
||||||
|
const videoSegmentService = useVideoSegmentService();
|
||||||
|
expect(typeof videoSegmentService.fetchVideoSegmentList).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -24,27 +24,12 @@ export class ScriptEditUseCase {
|
|||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
// 使用API接口生成剧本
|
// 使用API接口生成剧本
|
||||||
const response = await generateScriptStream({
|
await generateScriptStream({
|
||||||
text: prompt,
|
text: prompt,
|
||||||
|
},(content)=>{
|
||||||
|
stream_callback?.(content)
|
||||||
|
this.scriptValueObject.parseFromString(content)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.successful) {
|
|
||||||
throw new Error(response.message || "AI生成剧本失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用for await处理流式数据
|
|
||||||
for await (const data of response.data) {
|
|
||||||
// 检查是否被中断
|
|
||||||
if (this.abortController.signal.aborted) {
|
|
||||||
console.log("剧本生成被中断");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 根据流式数据更新剧本片段
|
|
||||||
// 这里需要根据实际的流式数据格式来处理
|
|
||||||
// 可能需要将流式数据转换为ScriptSlice并添加到scriptValueObject中
|
|
||||||
stream_callback?.(data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.abortController?.signal.aborted) {
|
if (this.abortController?.signal.aborted) {
|
||||||
console.log("剧本生成被中断");
|
console.log("剧本生成被中断");
|
||||||
@ -100,7 +85,7 @@ export class ScriptEditUseCase {
|
|||||||
* @returns ScriptSlice[]
|
* @returns ScriptSlice[]
|
||||||
*/
|
*/
|
||||||
getScriptSlices(): ScriptSlice[] {
|
getScriptSlices(): ScriptSlice[] {
|
||||||
return this.scriptValueObject.scriptSlices;
|
return [...this.scriptValueObject.scriptSlices];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,26 +112,4 @@ export class ScriptEditUseCase {
|
|||||||
return this.scriptValueObject.toString();
|
return this.scriptValueObject.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 更新指定ID的剧本片段
|
|
||||||
* @param id 剧本片段唯一标识
|
|
||||||
* @param text 新的文本内容
|
|
||||||
* @param metaData 新的元数据
|
|
||||||
* @returns boolean 是否更新成功
|
|
||||||
*/
|
|
||||||
updateScriptSlice(id: string, text: string, metaData?: Record<string, any>): boolean {
|
|
||||||
const index = this.scriptValueObject.scriptSlices.findIndex(slice => slice.id === id);
|
|
||||||
if (index === -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只更新text和metaData字段,保持其他字段不变
|
|
||||||
this.scriptValueObject.scriptSlices[index] = {
|
|
||||||
...this.scriptValueObject.scriptSlices[index],
|
|
||||||
text,
|
|
||||||
metaData: metaData || this.scriptValueObject.scriptSlices[index].metaData
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ShotEntity, RoleEntity, SceneEntity, AITextEntity, TagEntity, ContentItem } from '../domain/Entities';
|
import { VideoSegmentEntity, RoleEntity, SceneEntity, AITextEntity, TagEntity } from '../domain/Entities';
|
||||||
import { ShotItem, RoleItem, SceneItem, TextItem, TagItem } from '../domain/Item';
|
import { ContentItem, LensType } from '../domain/valueObject';
|
||||||
|
import { VideoSegmentItem, RoleItem, SceneItem, TextItem, TagItem } from '../domain/Item';
|
||||||
import {
|
import {
|
||||||
getShotRoles,
|
getShotRoles,
|
||||||
getShotScenes,
|
getShotScenes,
|
||||||
@ -9,126 +10,126 @@ import {
|
|||||||
} from '@/api/video_flow';
|
} from '@/api/video_flow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分镜编辑用例
|
* 视频片段编辑用例
|
||||||
* 负责分镜内容的初始化、修改和优化
|
* 负责视频片段内容的初始化、修改和优化
|
||||||
*/
|
*/
|
||||||
export class ShotEditUseCase {
|
export class VideoSegmentEditUseCase {
|
||||||
constructor(private shotItem: ShotItem) {
|
constructor(private videoSegmentItem: VideoSegmentItem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜关联的角色信息列表
|
* 获取视频片段关联的角色信息列表
|
||||||
* @description 获取当前分镜可以使用的角色列表
|
* @description 获取当前视频片段可以使用的角色列表
|
||||||
* @returns Promise<RoleEntity[]> 角色信息列表
|
* @returns Promise<RoleEntity[]> 角色信息列表
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
*/
|
*/
|
||||||
async getShotRoles(): Promise<RoleEntity[]> {
|
async getVideoSegmentRoles(): Promise<RoleEntity[]> {
|
||||||
const shotId = this.shotItem.entity.id;
|
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||||
|
|
||||||
if (!shotId) {
|
if (!videoSegmentId) {
|
||||||
throw new Error('分镜ID不存在,无法获取角色信息');
|
throw new Error('视频片段ID不存在,无法获取角色信息');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getShotRoles({
|
const response = await getShotRoles({
|
||||||
shotId: shotId
|
shotId: videoSegmentId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`获取分镜角色信息失败: ${response.message}`);
|
throw new Error(`获取视频片段角色信息失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜关联的场景信息列表
|
* 获取视频片段关联的场景信息列表
|
||||||
* @description 获取当前分镜可以使用的场景列表
|
* @description 获取当前视频片段可以使用的场景列表
|
||||||
* @returns Promise<SceneEntity[]> 场景信息列表
|
* @returns Promise<SceneEntity[]> 场景信息列表
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
*/
|
*/
|
||||||
async getShotScenes(): Promise<SceneEntity[]> {
|
async getVideoSegmentScenes(): Promise<SceneEntity[]> {
|
||||||
const shotId = this.shotItem.entity.id;
|
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||||
|
|
||||||
if (!shotId) {
|
if (!videoSegmentId) {
|
||||||
throw new Error('分镜ID不存在,无法获取场景信息');
|
throw new Error('视频片段ID不存在,无法获取场景信息');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getShotScenes({
|
const response = await getShotScenes({
|
||||||
shotId: shotId
|
shotId: videoSegmentId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`获取分镜场景信息失败: ${response.message}`);
|
throw new Error(`获取视频片段场景信息失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新获取当前分镜信息
|
* 重新获取当前视频片段信息
|
||||||
* @description 从服务器重新获取当前分镜的详细数据,并更新当前实体
|
* @description 从服务器重新获取当前视频片段的详细数据,并更新当前实体
|
||||||
* @returns Promise<{ text: AITextEntity; tags: TagEntity[] }> 分镜相关的AI文本和标签数据
|
* @returns Promise<{ text: AITextEntity; tags: TagEntity[] }> 视频片段相关的AI文本和标签数据
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
*/
|
*/
|
||||||
async refreshShotData(): Promise<{ text: AITextEntity; tags: TagEntity[] }> {
|
async refreshVideoSegmentData(): Promise<{ text: AITextEntity; tags: TagEntity[] }> {
|
||||||
const shotId = this.shotItem.entity.id;
|
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||||
|
|
||||||
if (!shotId) {
|
if (!videoSegmentId) {
|
||||||
throw new Error('分镜ID不存在,无法获取分镜数据');
|
throw new Error('视频片段ID不存在,无法获取视频片段数据');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getShotData({
|
const response = await getShotData({
|
||||||
shotId: shotId
|
shotId: videoSegmentId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
// 更新当前分镜的实体数据
|
// 更新当前视频片段的实体数据
|
||||||
const { text, tags } = response.data;
|
const { text, tags } = response.data;
|
||||||
|
|
||||||
// 更新分镜实体中的相关字段
|
// 更新视频片段实体中的相关字段
|
||||||
const updatedShotEntity = {
|
const updatedVideoSegmentEntity = {
|
||||||
...this.shotItem.entity,
|
...this.videoSegmentItem.entity,
|
||||||
generateTextId: text.id, // 更新AI文本ID
|
generateTextId: text.id, // 更新AI文本ID
|
||||||
tagIds: tags.map((tag: TagEntity) => tag.id), // 更新标签ID列表
|
tagIds: tags.map((tag: TagEntity) => tag.id), // 更新标签ID列表
|
||||||
updatedAt: Date.now(), // 更新时间戳
|
updatedAt: Date.now(), // 更新时间戳
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新当前UseCase中的实体
|
// 更新当前UseCase中的实体
|
||||||
this.shotItem.setEntity(updatedShotEntity);
|
this.videoSegmentItem.setEntity(updatedVideoSegmentEntity);
|
||||||
// 检查状态是否需要更新为视频状态
|
// 检查状态是否需要更新为视频状态
|
||||||
this.checkAndUpdateVideoStatus(updatedShotEntity);
|
this.checkAndUpdateVideoStatus(updatedVideoSegmentEntity);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`获取分镜数据失败: ${response.message}`);
|
throw new Error(`获取视频片段数据失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新生成当前分镜
|
* 重新生成当前视频片段
|
||||||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成分镜
|
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成视频片段
|
||||||
* @param shotPrompt 镜头描述
|
* @param shotPrompt 镜头描述
|
||||||
* @param dialogueContent 对话内容
|
* @param dialogueContent 对话内容
|
||||||
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
||||||
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
||||||
* @returns Promise<ShotEntity> 重新生成的分镜实体
|
* @returns Promise<VideoSegmentEntity> 重新生成的视频片段实体
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
*/
|
*/
|
||||||
async regenerateShot(
|
async regenerateVideoSegment(
|
||||||
shotPrompt: string,
|
shotPrompt: LensType[],
|
||||||
dialogueContent: ContentItem[],
|
dialogueContent: ContentItem[],
|
||||||
roleReplaceParams: { oldId: string; newId: string }[],
|
roleReplaceParams: { oldId: string; newId: string }[],
|
||||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||||
): Promise<ShotEntity> {
|
): Promise<VideoSegmentEntity> {
|
||||||
const shotId = this.shotItem.entity.id;
|
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||||
|
|
||||||
if (!shotId) {
|
if (!videoSegmentId) {
|
||||||
throw new Error('分镜ID不存在,无法重新生成分镜');
|
throw new Error('视频片段ID不存在,无法重新生成视频片段');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用重新生成分镜接口
|
// 调用重新生成视频片段接口
|
||||||
const response = await regenerateShot({
|
const response = await regenerateShot({
|
||||||
shotId: shotId,
|
shotId: videoSegmentId,
|
||||||
shotPrompt: shotPrompt,
|
shotPrompt: shotPrompt,
|
||||||
dialogueContent: dialogueContent,
|
dialogueContent: dialogueContent,
|
||||||
roleReplaceParams: roleReplaceParams,
|
roleReplaceParams: roleReplaceParams,
|
||||||
@ -136,32 +137,32 @@ export class ShotEditUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
const shotEntity = response.data;
|
const videoSegmentEntity = response.data;
|
||||||
this.shotItem.setEntity(shotEntity);
|
this.videoSegmentItem.setEntity(videoSegmentEntity);
|
||||||
// 检查状态是否需要更新为视频状态
|
// 检查状态是否需要更新为视频状态
|
||||||
this.checkAndUpdateVideoStatus(shotEntity);
|
this.checkAndUpdateVideoStatus(videoSegmentEntity);
|
||||||
return shotEntity;
|
return videoSegmentEntity;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`重新生成分镜失败: ${response.message}`);
|
throw new Error(`重新生成视频片段失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改分镜对话内容
|
* 修改视频片段对话内容
|
||||||
* @description 更新分镜的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
* @description 更新视频片段的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||||||
* @param newContent 新的对话内容数组
|
* @param newContent 新的对话内容数组
|
||||||
* @returns Promise<ShotEntity> 修改后的分镜实体
|
* @returns Promise<VideoSegmentEntity> 修改后的视频片段实体
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
*/
|
*/
|
||||||
async updateShotContent(newContent: Array<{ roleId: string; content: string }>): Promise<ShotEntity> {
|
async updateVideoSegmentContent(newContent: Array<{ roleId: string; content: string }>): Promise<VideoSegmentEntity> {
|
||||||
const shotId = this.shotItem.entity.id;
|
const videoSegmentId = this.videoSegmentItem.entity.id;
|
||||||
|
|
||||||
if (!shotId) {
|
if (!videoSegmentId) {
|
||||||
throw new Error('分镜ID不存在,无法修改对话内容');
|
throw new Error('视频片段ID不存在,无法修改对话内容');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证ContentItem数量和ID顺序
|
// 验证ContentItem数量和ID顺序
|
||||||
const currentContent = this.shotItem.entity.content;
|
const currentContent = this.videoSegmentItem.entity.content;
|
||||||
if (newContent.length !== currentContent.length) {
|
if (newContent.length !== currentContent.length) {
|
||||||
throw new Error('ContentItem数量不能改变');
|
throw new Error('ContentItem数量不能改变');
|
||||||
}
|
}
|
||||||
@ -174,30 +175,30 @@ export class ShotEditUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await updateShotContent({
|
const response = await updateShotContent({
|
||||||
shotId: shotId,
|
shotId: videoSegmentId,
|
||||||
content: newContent,
|
content: newContent,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
const shotEntity = response.data;
|
const videoSegmentEntity = response.data;
|
||||||
this.shotItem.setEntity(shotEntity);
|
this.videoSegmentItem.setEntity(videoSegmentEntity);
|
||||||
// 检查状态是否需要更新为视频状态
|
// 检查状态是否需要更新为视频状态
|
||||||
this.checkAndUpdateVideoStatus(shotEntity);
|
this.checkAndUpdateVideoStatus(videoSegmentEntity);
|
||||||
return shotEntity;
|
return videoSegmentEntity;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`修改分镜对话内容失败: ${response.message}`);
|
throw new Error(`修改视频片段对话内容失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查并更新视频状态
|
* 检查并更新视频状态
|
||||||
* @description 当分镜状态变为视频加载中或完成时,调用updateToVideoStatus
|
* @description 当视频片段状态变为视频加载中或完成时,调用updateToVideoStatus
|
||||||
* @param shotEntity 分镜实体
|
* @param videoSegmentEntity 视频片段实体
|
||||||
*/
|
*/
|
||||||
private checkAndUpdateVideoStatus(shotEntity: ShotEntity): void {
|
private checkAndUpdateVideoStatus(videoSegmentEntity: VideoSegmentEntity): void {
|
||||||
// 当状态为视频加载中或完成时,更新为视频状态
|
// 当状态为视频加载中或完成时,更新为视频状态
|
||||||
if (shotEntity.status === 1 || shotEntity.status === 2) { // videoLoading 或 finished
|
if (videoSegmentEntity.status === 1 || videoSegmentEntity.status === 2) { // videoLoading 或 finished
|
||||||
this.shotItem.updateToVideoStatus();
|
this.videoSegmentItem.updateToVideoStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,4 +6,5 @@ module.exports = {
|
|||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^@/(.*)$': '<rootDir>/$1', // 支持 Next.js 的 @ 别名
|
'^@/(.*)$': '<rootDir>/$1', // 支持 Next.js 的 @ 别名
|
||||||
},
|
},
|
||||||
|
testTimeout: 30000, // 全局设置30秒超时
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,8 +21,10 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
},
|
},
|
||||||
"maxNodeModuleJsDepth":0
|
"maxNodeModuleJsDepth": 1,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts", ".next"]
|
||||||
}
|
}
|
||||||
|
|||||||
16
tsconfig.test.json
Normal file
16
tsconfig.test.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["jest", "node"],
|
||||||
|
"isolatedModules": false
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
import { ScriptSlice, ScriptSliceType } from "@/app/service/domain/valueObject";
|
import { ScriptSlice, ScriptSliceType } from "@/app/service/domain/valueObject";
|
||||||
|
|
||||||
export function parseScriptEntity(text: string):ScriptSlice {
|
export function parseScriptEntity(text: string):ScriptSlice {
|
||||||
const scriptSlice:ScriptSlice={
|
const scriptSlice = new ScriptSlice(
|
||||||
// 生成唯一ID,单次使用即可
|
// 生成唯一ID,单次使用即可
|
||||||
id: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
|
`${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
|
||||||
type: ScriptSliceType.text,
|
ScriptSliceType.text,
|
||||||
text: text,
|
text,
|
||||||
metaData: {}
|
{}
|
||||||
|
);
|
||||||
}
|
|
||||||
return scriptSlice;
|
return scriptSlice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user