更新配置文件以支持测试和视频片段功能;在 jest.config.js 中设置全局测试超时为 30 秒;在 tsconfig.json 中添加新的编译选项以增强类型检查;新增 tsconfig.test.json 文件以支持测试环境配置;在 video_flow.ts 中重构 API 接口以使用 VideoSegmentEntity 替代 ShotEntity,并更新相关服务和测试用例以反映这些更改。

This commit is contained in:
海龙 2025-08-05 20:12:30 +08:00
parent 508065107a
commit ab10493248
21 changed files with 630 additions and 583 deletions

View File

@ -1 +1 @@
export const BASE_URL = "https://77.smartvideo.py.qikongjian.com" export const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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');
});
});

View File

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

View File

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

View File

@ -6,4 +6,5 @@ module.exports = {
moduleNameMapper: { moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1', // 支持 Next.js 的 @ 别名 '^@/(.*)$': '<rootDir>/$1', // 支持 Next.js 的 @ 别名
}, },
testTimeout: 30000, // 全局设置30秒超时
}; };

View File

@ -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
View 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"
]
}

View File

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