更新分镜服务Hook,支持对话内容和角色替换参数的可选性;新增AI生成剧本流式接口和应用剧本功能;优化分镜视频数据结构,支持多个视频URL;删除不再使用的分镜详情和视频数据获取API接口。

This commit is contained in:
海龙 2025-07-31 17:39:56 +08:00
parent 291c18ad86
commit e42f5269ca
9 changed files with 734 additions and 204 deletions

View File

@ -2,7 +2,8 @@ import { post } from './request';
import { ProjectTypeEnum } from '@/app/model/enums';
import { ApiResponse } from '@/api/common';
import { BASE_URL } from './constants'
import { AITextEntity, RoleEntity, SceneEntity, ShotEntity, TagEntity, ScriptSliceEntity } from '@/app/service/domain/Entities';
import { AITextEntity, RoleEntity, SceneEntity, ShotEntity, TagEntity, ContentItem } from '@/app/service/domain/Entities';
import { ScriptSlice } from "@/app/service/domain/valueObject";
// API 响应类型
interface BaseApiResponse<T> {
@ -57,10 +58,10 @@ interface TaskData {
}
// 流式数据类型
export interface StreamData {
export interface StreamData<T=any> {
category: 'sketch' | 'character' | 'video' | 'music' | 'final_video';
message: string;
data: any;
data: T;
status: 'running' | 'completed';
total?: number;
completed?: number;
@ -468,15 +469,15 @@ export const getShotData = async (request: {
*/
export const regenerateShot = async (request: {
/** 分镜ID */
shotId: string;
shotId?: string;
/** 镜头描述 */
shotPrompt: string;
shotPrompt?: string;
/** 对话内容 */
dialogueContent: string;
dialogueContent?: ContentItem[];
/** 角色ID替换参数格式为{oldId:string,newId:string}[] */
roleReplaceParams: { oldId: string; newId: string }[];
roleReplaceParams?: { oldId: string; newId: string }[];
/** 场景ID替换参数格式为{oldId:string,newId:string}[] */
sceneReplaceParams: { oldId: string; newId: string }[];
sceneReplaceParams?: { oldId: string; newId: string }[];
}): Promise<ApiResponse<ShotEntity>> => {
return post<ApiResponse<any>>('/movie/regenerate_shot', request);
};
@ -512,41 +513,18 @@ export const getShotList = async (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 getShotDetail = async (request: {
// /** 分镜ID */
// shotId: string;
// }): Promise<ApiResponse<ShotEntity>> => {
// return post<ApiResponse<any>>('/movie/get_shot_detail', request);
// };
/**
*
* @param request -
* @returns Promise<ApiResponse<草图数据URL>>
*/
export const getShotSketchData = async (request: {
/** 分镜ID */
shotId: string;
}): Promise<ApiResponse<string>> => {
return post<ApiResponse<string>>('/movie/get_shot_sketch_data', request);
};
/**
*
* @param request -
* @returns Promise<ApiResponse<视频数据URL>>
*/
export const getShotVideoData = async (request: {
/** 分镜ID */
shotId: string;
}): Promise<ApiResponse<string>> => {
return post<ApiResponse<string>>('/movie/get_shot_video_data', request);
};
/**
*
@ -586,6 +564,32 @@ export const replaceShotRole = async (request: {
export const getShotVideoScript = async (request: {
/** 分镜ID */
shotId: string;
}): Promise<ApiResponse<ScriptSliceEntity[]>> => {
}): Promise<ApiResponse<string>> => {
return post<ApiResponse<any>>('/movie/get_shot_video_script', request);
};
/**
* AI生成剧本流式接口
* @param request - AI生成剧本请求参数
* @returns Promise<ApiResponse<流式数据>>
*/
export const generateScriptStream = async (request: {
/** 剧本提示词 */
prompt: string;
}) => {
return post<ApiResponse<any>>('/movie/generate_script_stream', request,{
responseType: 'stream',
});
};
/**
*
* @param request -
* @returns Promise<ApiResponse<应用结果>>
*/
export const applyScriptToShot = async (request: {
/** 剧本*/
script: string;
}): Promise<ApiResponse<any>> => {
return post<ApiResponse<any>>('/movie/apply_script_to_shot', request);
};

View File

@ -0,0 +1,290 @@
import { useState, useCallback, useMemo } from "react";
import { ScriptSlice } from "../domain/valueObject";
import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase";
/**
* Hook接口
* Hook的所有状态和操作方法
*/
export interface UseScriptService {
// 响应式状态
/** 当前剧本文本 */
scriptText: string;
/** 剧本片段列表 */
scriptSlices: ScriptSlice[];
/** 当前聚焦的剧本片段ID */
focusedSliceId: string;
/** 当前聚焦的剧本片段 */
focusedSlice: ScriptSlice | null;
/** 当前聚焦的剧本片段文本 */
scriptSliceText: string;
/** 用户提示词(可编辑) */
userPrompt: string;
/** 加载状态 */
loading: boolean;
/** 错误信息 */
error: string | null;
// 操作方法
/** 获取剧本数据(用户提示词) */
fetchScriptData: (prompt: string) => Promise<void>;
/** 设置当前聚焦的剧本片段 */
setFocusedSlice: (sliceId: string) => void;
/** 清除聚焦状态 */
clearFocusedSlice: () => void;
/** 快速更新当前聚焦的剧本片段文本(无防抖) */
updateScriptSliceText: (text: string, metaData?: any) => void;
/** 更新用户提示词 */
updateUserPrompt: (prompt: string) => void;
/** 重置剧本内容到初始状态 */
resetScript: () => void;
/** AI生成剧本 */
generateScript: (prompt: string) => Promise<void>;
/** 应用剧本 */
applyScript: () => Promise<void>;
/** 更新聚焦剧本片段 */
UpdateFocusedSlice: (text: string) => void;
}
/**
* Hook
*
*
*/
export const useScriptService = (): UseScriptService => {
// 响应式状态
const [scriptText, setScriptText] = useState<string>("");
const [scriptSlices, setScriptSlices] = useState<ScriptSlice[]>([]);
const [focusedSliceId, setFocusedSliceId] = useState<string>("");
const [scriptSliceText, setScriptSliceText] = useState<string>("");
const [userPrompt, setUserPrompt] = useState<string>("");
const [initialScriptText, setInitialScriptText] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// UseCase实例
const [scriptEditUseCase, setScriptEditUseCase] = useState<ScriptEditUseCase | null>(null);
// 防抖定时器
const [debounceTimer, setDebounceTimer] = useState<NodeJS.Timeout | null>(null);
const DEBOUNCE_DELAY = 300;
/**
*
*/
const focusedSlice = useMemo(() => {
return scriptSlices.find(slice => slice.id === focusedSliceId) || null;
}, [scriptSlices, focusedSliceId]);
/**
*
* @param prompt
*/
const fetchScriptData = useCallback(async (prompt: string): Promise<void> => {
try {
setLoading(true);
setError(null);
// 清空当前状态
setScriptText("");
setScriptSlices([]);
setFocusedSliceId("");
setScriptSliceText("");
// 更新用户提示词状态
setUserPrompt(prompt);
// 保存初始提示词(只在第一次获取时保存)
if (!initialScriptText) {
setInitialScriptText(prompt);
}
// 创建新的剧本编辑用例
const newScriptEditUseCase = new ScriptEditUseCase('');
setScriptEditUseCase(newScriptEditUseCase);
// 调用AI生成剧本
await newScriptEditUseCase.generateScript(prompt);
// 获取生成的剧本文本
const generatedScriptText = newScriptEditUseCase.toString();
setScriptText(generatedScriptText);
// 获取剧本片段列表
const slices = newScriptEditUseCase.getScriptSlices();
setScriptSlices(slices);
} catch (error) {
console.error('获取剧本数据失败:', error);
setError(error instanceof Error ? error.message : '获取剧本数据失败');
throw error;
} finally {
setLoading(false);
}
}, [initialScriptText]);
/**
*
* @param sliceId ID
*/
const setFocusedSlice = useCallback((sliceId: string): void => {
setFocusedSliceId(sliceId);
// 同步输入框文本为当前聚焦片段的文本
const focusedSlice = scriptSlices.find(slice => slice.id === sliceId);
if (focusedSlice) {
setScriptSliceText(focusedSlice.text);
} else {
setScriptSliceText("");
}
}, [scriptSlices]);
/**
*
*/
const clearFocusedSlice = useCallback((): void => {
setFocusedSliceId("");
setScriptSliceText("");
}, []);
/**
*
* @param text
* @param metaData
*/
const updateScriptSliceText = useCallback((text: string, metaData?: any): void => {
setScriptSliceText(text);
// 自动触发防抖更新值对象
// 清除之前的定时器
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// 设置新的防抖定时器
const timer = setTimeout(() => {
UpdateFocusedSlice(text, metaData);
}, DEBOUNCE_DELAY);
setDebounceTimer(timer);
}, [debounceTimer]);
/**
*
* @param text
* @param metaData
*/
const UpdateFocusedSlice = useCallback((text: string, metaData?: any): void => {
if (!focusedSliceId || !scriptEditUseCase) {
return;
}
const success = scriptEditUseCase.updateScriptSlice(
focusedSliceId,
text,
metaData
);
if (success) {
// 更新本地片段列表
const slices = scriptEditUseCase.getScriptSlices();
setScriptSlices(slices);
}
}, [focusedSliceId, scriptEditUseCase]);
/**
*
* @param prompt
*/
const updateUserPrompt = useCallback((prompt: string): void => {
setUserPrompt(prompt);
}, []);
/**
*
*/
const resetScript = useCallback((): void => {
if (initialScriptText) {
// 重新调用AI生成剧本fetchScriptData会自动清空状态
fetchScriptData(initialScriptText);
}
}, [initialScriptText, fetchScriptData]);
/**
* AI生成剧本
* @param prompt
*/
const generateScript = useCallback(async (prompt: string): Promise<void> => {
try {
setLoading(true);
setError(null);
if (!scriptEditUseCase) {
throw new Error("剧本编辑用例未初始化");
}
await scriptEditUseCase.generateScript(prompt);
// 更新片段列表(这里需要根据实际的流式数据处理逻辑来调整)
const slices = scriptEditUseCase.getScriptSlices();
setScriptSlices(slices);
} catch (error) {
console.error("AI生成剧本失败:", error);
setError(error instanceof Error ? error.message : "AI生成剧本失败");
throw error;
} finally {
setLoading(false);
}
}, [scriptEditUseCase]);
/**
*
*/
const applyScript = useCallback(async (): Promise<void> => {
try {
setLoading(true);
setError(null);
if (!scriptEditUseCase) {
throw new Error("剧本编辑用例未初始化");
}
await scriptEditUseCase.applyScript();
} catch (error) {
console.error("应用剧本失败:", error);
setError(error instanceof Error ? error.message : "应用剧本失败");
throw error;
} finally {
setLoading(false);
}
}, [scriptEditUseCase]);
return {
// 响应式状态
scriptText,
scriptSlices,
focusedSliceId,
focusedSlice,
scriptSliceText,
userPrompt,
loading,
error,
// 操作方法
fetchScriptData,
setFocusedSlice,
clearFocusedSlice,
updateScriptSliceText,
updateUserPrompt,
resetScript,
generateScript,
applyScript,
UpdateFocusedSlice
};
};

View File

@ -1,16 +1,22 @@
import { useState, useCallback, useMemo } from 'react';
import { ShotEntity, RoleEntity, SceneEntity, ScriptSliceEntity } from '../domain/Entities';
import { ShotItem } from '../domain/Item';
import { ShotEditUseCase } from '../usecase/ShotEditUsecase';
import { useState, useCallback, useMemo } from "react";
import {
ShotEntity,
RoleEntity,
SceneEntity,
ContentItem,
} from "../domain/Entities";
import { ScriptSlice, ScriptValueObject } from "../domain/valueObject";
import { ShotItem } from "../domain/Item";
import { ShotEditUseCase } from "../usecase/ShotEditUsecase";
import {
getShotList,
getShotDetail,
// getShotDetail,
updateShotContent,
updateShotShot,
getUserRoleLibrary,
replaceShotRole,
getShotVideoScript
} from '@/api/video_flow';
getShotVideoScript,
} from "@/api/video_flow";
/**
* Hook接口
@ -25,12 +31,12 @@ export interface UseShotService {
/** 当前分镜的草图数据URL */
shotSketchData: string | null;
/** 当前分镜的视频数据URL */
shotVideoData: string | null;
shotVideoData: string[] | null;
/** 用户角色库 */
userRoleLibrary: RoleEntity[];
/** 当前分镜的视频剧本片段 */
shotVideoScript: ScriptSliceEntity[];
shotVideoScript: ScriptSlice[];
/** 加载状态 */
loading: boolean;
/** 错误信息 */
@ -42,7 +48,9 @@ export interface UseShotService {
/** 选择分镜并获取详情 */
selectShot: (shotId: string) => Promise<void>;
/** 修改分镜对话内容 */
updateShotContent: (newContent: Array<{ roleId: string; content: string }>) => Promise<void>;
updateShotContent: (
newContent: Array<{ roleId: string; content: string }>
) => Promise<void>;
/** 修改分镜镜头 */
updateShotShot: (newShot: string[]) => Promise<void>;
/** 获取用户角色库 */
@ -59,7 +67,7 @@ export interface UseShotService {
/** 重新生成分镜 */
regenerateShot: (
shotPrompt: string,
dialogueContent: string,
dialogueContent: ContentItem[],
roleReplaceParams: { oldId: string; newId: string }[],
sceneReplaceParams: { oldId: string; newId: string }[]
) => Promise<void>;
@ -75,14 +83,15 @@ export const useShotService = (): UseShotService => {
const [shotList, setShotList] = useState<ShotEntity[]>([]);
const [selectedShot, setSelectedShot] = useState<ShotItem | null>(null);
const [shotSketchData, setShotSketchData] = useState<string | null>(null);
const [shotVideoData, setShotVideoData] = useState<string | null>(null);
const [shotVideoData, setShotVideoData] = useState<string[] | null>(null);
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
const [shotVideoScript, setShotVideoScript] = useState<ScriptSliceEntity[]>([]);
const [shotVideoScript, setShotVideoScript] = useState<ScriptSlice[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [projectId, setProjectId] = useState<string>("");
// UseCase实例
const [shotEditUseCase, setShotEditUseCase] = useState<ShotEditUseCase | null>(null);
const [shotEditUseCase, setShotEditUseCase] =
useState<ShotEditUseCase | null>(null);
/**
*
@ -90,6 +99,7 @@ export const useShotService = (): UseShotService => {
* @param projectId ID
*/
const fetchShotList = useCallback(async (projectId: string) => {
setProjectId(projectId);
try {
setLoading(true);
setError(null);
@ -100,7 +110,9 @@ export const useShotService = (): UseShotService => {
setError(`获取分镜列表失败: ${response.message}`);
}
} catch (err) {
setError(`获取分镜列表失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`获取分镜列表失败: ${err instanceof Error ? err.message : "未知错误"}`
);
} finally {
setLoading(false);
}
@ -117,9 +129,14 @@ export const useShotService = (): UseShotService => {
setError(null);
// 获取分镜详情
const response = await getShotDetail({ shotId });
if (response.successful) {
const shotEntity = response.data;
await fetchShotList(projectId);
const shotEntity = shotList.find(
(shot: ShotEntity) => shot.id === shotId
);
if (!shotEntity) {
setError(`分镜不存在: ${shotId}`);
return;
}
const shotItem = new ShotItem(shotEntity);
setSelectedShot(shotItem);
@ -127,16 +144,13 @@ export const useShotService = (): UseShotService => {
const newShotEditUseCase = new ShotEditUseCase(shotItem);
setShotEditUseCase(newShotEditUseCase);
// 从分镜实体中获取草图数据和视频数据
setShotSketchData(shotEntity.sketchUrl || null);
setShotVideoData(shotEntity.videoUrl || null);
} else {
setError(`获取分镜详情失败: ${response.message}`);
}
} catch (err) {
setError(`选择分镜失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`选择分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
);
} finally {
setLoading(false);
}
@ -147,9 +161,10 @@ export const useShotService = (): UseShotService => {
* @description ContentItem数量和ID顺序不能变content字段
* @param newContent
*/
const updateShotContentHandler = useCallback(async (newContent: Array<{ roleId: string; content: string }>) => {
const updateShotContentHandler = useCallback(
async (newContent: Array<{ roleId: string; content: string }>) => {
if (!shotEditUseCase) {
setError('分镜编辑用例未初始化');
setError("分镜编辑用例未初始化");
return;
}
@ -159,20 +174,27 @@ export const useShotService = (): UseShotService => {
const updatedShot = await shotEditUseCase.updateShotContent(newContent);
setSelectedShot(new ShotItem(updatedShot));
} catch (err) {
setError(`修改分镜对话内容失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`修改分镜对话内容失败: ${
err instanceof Error ? err.message : "未知错误"
}`
);
} finally {
setLoading(false);
}
}, [shotEditUseCase]);
},
[shotEditUseCase]
);
/**
*
* @description
* @param newShot
*/
const updateShotShotHandler = useCallback(async (newShot: string[]) => {
const updateShotShotHandler = useCallback(
async (newShot: string[]) => {
if (!selectedShot) {
setError('未选择分镜');
setError("未选择分镜");
return;
}
@ -181,7 +203,7 @@ export const useShotService = (): UseShotService => {
setError(null);
const response = await updateShotShot({
shotId: selectedShot.entity.id,
shot: newShot
shot: newShot,
});
if (response.successful) {
const updatedShot = response.data;
@ -190,11 +212,15 @@ export const useShotService = (): UseShotService => {
setError(`修改分镜镜头失败: ${response.message}`);
}
} catch (err) {
setError(`修改分镜镜头失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`修改分镜镜头失败: ${err instanceof Error ? err.message : "未知错误"}`
);
} finally {
setLoading(false);
}
}, [selectedShot]);
},
[selectedShot]
);
/**
*
@ -211,7 +237,9 @@ export const useShotService = (): UseShotService => {
setError(`获取用户角色库失败: ${response.message}`);
}
} catch (err) {
setError(`获取用户角色库失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`获取用户角色库失败: ${err instanceof Error ? err.message : "未知错误"}`
);
} finally {
setLoading(false);
}
@ -223,9 +251,10 @@ export const useShotService = (): UseShotService => {
* @param oldRoleId ID
* @param newRoleId ID
*/
const replaceShotRoleHandler = useCallback(async (oldRoleId: string, newRoleId: string) => {
const replaceShotRoleHandler = useCallback(
async (oldRoleId: string, newRoleId: string) => {
if (!selectedShot) {
setError('未选择分镜');
setError("未选择分镜");
return;
}
@ -235,7 +264,7 @@ export const useShotService = (): UseShotService => {
const response = await replaceShotRole({
shotId: selectedShot.entity.id,
oldRoleId,
newRoleId
newRoleId,
});
if (response.successful) {
// 重新获取分镜详情
@ -244,11 +273,15 @@ export const useShotService = (): UseShotService => {
setError(`替换分镜角色失败: ${response.message}`);
}
} catch (err) {
setError(`替换分镜角色失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`替换分镜角色失败: ${err instanceof Error ? err.message : "未知错误"}`
);
} finally {
setLoading(false);
}
}, [selectedShot, selectShot]);
},
[selectedShot, selectShot]
);
/**
*
@ -256,21 +289,28 @@ export const useShotService = (): UseShotService => {
*/
const fetchShotVideoScript = useCallback(async () => {
if (!selectedShot) {
setError('未选择分镜');
setError("未选择分镜");
return;
}
try {
setLoading(true);
setError(null);
const response = await getShotVideoScript({ shotId: selectedShot.entity.id });
const response = await getShotVideoScript({
shotId: selectedShot.entity.id,
});
if (response.successful) {
setShotVideoScript(response.data);
const script = new ScriptValueObject(response.data);
setShotVideoScript(script.scriptSlices);
} else {
setError(`获取分镜视频剧本失败: ${response.message}`);
}
} catch (err) {
setError(`获取分镜视频剧本失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`获取分镜视频剧本失败: ${
err instanceof Error ? err.message : "未知错误"
}`
);
} finally {
setLoading(false);
}
@ -283,7 +323,7 @@ export const useShotService = (): UseShotService => {
*/
const getShotRoles = useCallback(async (): Promise<RoleEntity[]> => {
if (!shotEditUseCase) {
throw new Error('分镜编辑用例未初始化');
throw new Error("分镜编辑用例未初始化");
}
return await shotEditUseCase.getShotRoles();
}, [shotEditUseCase]);
@ -295,13 +335,11 @@ export const useShotService = (): UseShotService => {
*/
const getShotScenes = useCallback(async (): Promise<SceneEntity[]> => {
if (!shotEditUseCase) {
throw new Error('分镜编辑用例未初始化');
throw new Error("分镜编辑用例未初始化");
}
return await shotEditUseCase.getShotScenes();
}, [shotEditUseCase]);
/**
*
* @description 使ID替换参数ID替换参数重新生成分镜
@ -310,14 +348,15 @@ export const useShotService = (): UseShotService => {
* @param roleReplaceParams ID替换参数{oldId:string,newId:string}[]
* @param sceneReplaceParams ID替换参数{oldId:string,newId:string}[]
*/
const regenerateShot = useCallback(async (
const regenerateShot = useCallback(
async (
shotPrompt: string,
dialogueContent: string,
dialogueContent: ContentItem[],
roleReplaceParams: { oldId: string; newId: string }[],
sceneReplaceParams: { oldId: string; newId: string }[]
) => {
if (!shotEditUseCase) {
setError('分镜编辑用例未初始化');
setError("分镜编辑用例未初始化");
return;
}
@ -332,11 +371,15 @@ export const useShotService = (): UseShotService => {
);
setSelectedShot(new ShotItem(updatedShot));
} catch (err) {
setError(`重新生成分镜失败: ${err instanceof Error ? err.message : '未知错误'}`);
setError(
`重新生成分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
);
} finally {
setLoading(false);
}
}, [shotEditUseCase]);
},
[shotEditUseCase]
);
return {
// 响应式状态 - 用于UI组件订阅和渲染

View File

@ -65,13 +65,15 @@ export interface SceneEntity extends BaseEntity {
/** 场景提示词Id */
generateTextId: string;
}
/**对话内容项 */
interface ContentItem {
export interface ContentItem {
/** 角色ID */
roleId: string;
/** 对话内容 */
content: string;
}
/**分镜进度 */
export enum ShotStatus {
/** 草稿加载中 */
sketchLoading = 0,
@ -89,7 +91,7 @@ export interface ShotEntity extends BaseEntity {
/**分镜草图Url */
sketchUrl: string;
/**分镜视频Url */
videoUrl: string;
videoUrl: string[];
/**分镜状态 */
status: ShotStatus;
/**角色ID列表 */
@ -100,30 +102,8 @@ export interface ShotEntity extends BaseEntity {
content: ContentItem[];
/**镜头项 */
shot: string[];
/**分镜剧本 */
/**分镜剧本Id */
scriptId: string;
}
/**不同类型 将有不同元数据 */
export enum ScriptSliceType {
/** 文本 */
text = 'text',
/** 高亮 */
highlight = 'highlight',
/**角色 */
role = 'role',
/** 场景 */
scene = 'scene',
}
/**
*
*/
export interface ScriptSliceEntity extends BaseEntity {
/** 类型 */
type: ScriptSliceType;
/** 剧本内容 */
text: string;
/** 元数据 */
metaData: any;
}

View File

@ -0,0 +1,78 @@
/**不同类型 将有不同元数据 */
export enum ScriptSliceType {
/** 文本 */
text = "text",
/**角色 */
role = "role",
/** 场景 */
scene = "scene",
}
/**
*
*/
export interface ScriptSlice {
/**唯一标识符 */
readonly id: string;
/** 类型 */
type: ScriptSliceType;
/** 剧本内容 */
text: string;
/** 元数据 */
metaData: any;
}
/**
* @description:
* @return {*}
*/
export class ScriptValueObject {
scriptSlices: ScriptSlice[] = [];
/**
* @description:
* @param scriptText
*/
constructor(scriptText?: string) {
if (scriptText) {
this.parseFromString(scriptText);
}
}
/**
* @description:
* @param scriptText
*/
parseFromString(scriptText: string): void {
// TODO: 实现字符串解析逻辑
}
/**
* @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:
* @returns string
*/
toString(): string {
return this.scriptSlices.map((slice) => slice.text).join("");
}
}

View File

@ -0,0 +1,124 @@
import { ScriptSlice, ScriptValueObject } from "../domain/valueObject";
import { generateScriptStream, applyScriptToShot } from "@/api/video_flow";
export class ScriptEditUseCase {
loading: boolean = false;
private scriptValueObject: ScriptValueObject;
constructor(script: string) {
this.scriptValueObject = new ScriptValueObject(script);
}
/**
* @description: AI生成剧本方法
* @param prompt
* @returns Promise<void>
*/
async generateScript(prompt: string): Promise<void> {
try {
this.loading = true;
// 使用API接口生成剧本
const response = await generateScriptStream({
prompt,
});
if (!response.successful) {
throw new Error(response.message || "AI生成剧本失败");
}
// 使用for await处理流式数据
for await (const data of response.data) {
// TODO: 根据流式数据更新剧本片段
// 这里需要根据实际的流式数据格式来处理
// 可能需要将流式数据转换为ScriptSlice并添加到scriptValueObject中
}
} catch (error) {
console.error("AI生成剧本出错:", error);
throw error;
} finally {
this.loading = false;
}
}
/**
* @description:
* @returns Promise<void>
*/
async applyScript(): Promise<void> {
try {
this.loading = true;
// 调用应用剧本接口
const response = await applyScriptToShot({
script: this.scriptValueObject.toString(),
});
if (!response.successful) {
throw new Error(response.message || "应用剧本失败");
}
console.log("剧本应用成功");
} catch (error) {
console.error("应用剧本失败:", error);
throw error;
} finally {
this.loading = false;
}
}
/**
* @description:
* @returns ScriptSlice[]
*/
getScriptSlices(): ScriptSlice[] {
return this.scriptValueObject.scriptSlices;
}
/**
* @description:
* @returns boolean
*/
isLoading(): boolean {
return this.loading;
}
/**
* @description:
* @param scriptText
*/
updateScript(scriptText: string): void {
this.scriptValueObject = new ScriptValueObject(scriptText);
}
/**
* @description:
* @returns string
*/
toString(): string {
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,4 +1,4 @@
import { ShotEntity, RoleEntity, SceneEntity, AITextEntity, TagEntity } from '../domain/Entities';
import { ShotEntity, RoleEntity, SceneEntity, AITextEntity, TagEntity, ContentItem } from '../domain/Entities';
import { ShotItem, RoleItem, SceneItem, TextItem, TagItem } from '../domain/Item';
import {
getShotRoles,
@ -116,7 +116,7 @@ export class ShotEditUseCase {
*/
async regenerateShot(
shotPrompt: string,
dialogueContent: string,
dialogueContent: ContentItem[],
roleReplaceParams: { oldId: string; newId: string }[],
sceneReplaceParams: { oldId: string; newId: string }[]
): Promise<ShotEntity> {

View File

11
utils/tools.ts Normal file
View File

@ -0,0 +1,11 @@
import { ScriptSlice, ScriptSliceType } from "@/app/service/domain/valueObject";
export function parseScriptEntity(text: string):ScriptSlice {
const scriptSlice:ScriptSlice={
type:ScriptSliceType.text,
text:text,
metaData:{}
}
return scriptSlice;
}