forked from 77media/video-flow
更新分镜服务Hook,支持对话内容和角色替换参数的可选性;新增AI生成剧本流式接口和应用剧本功能;优化分镜视频数据结构,支持多个视频URL;删除不再使用的分镜详情和视频数据获取API接口。
This commit is contained in:
parent
291c18ad86
commit
e42f5269ca
@ -2,7 +2,8 @@ import { post } 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, 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 响应类型
|
// API 响应类型
|
||||||
interface BaseApiResponse<T> {
|
interface BaseApiResponse<T> {
|
||||||
@ -57,10 +58,10 @@ interface TaskData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 流式数据类型
|
// 流式数据类型
|
||||||
export interface StreamData {
|
export interface StreamData<T=any> {
|
||||||
category: 'sketch' | 'character' | 'video' | 'music' | 'final_video';
|
category: 'sketch' | 'character' | 'video' | 'music' | 'final_video';
|
||||||
message: string;
|
message: string;
|
||||||
data: any;
|
data: T;
|
||||||
status: 'running' | 'completed';
|
status: 'running' | 'completed';
|
||||||
total?: number;
|
total?: number;
|
||||||
completed?: number;
|
completed?: number;
|
||||||
@ -468,15 +469,15 @@ export const getShotData = async (request: {
|
|||||||
*/
|
*/
|
||||||
export const regenerateShot = async (request: {
|
export const regenerateShot = async (request: {
|
||||||
/** 分镜ID */
|
/** 分镜ID */
|
||||||
shotId: string;
|
shotId?: string;
|
||||||
/** 镜头描述 */
|
/** 镜头描述 */
|
||||||
shotPrompt: string;
|
shotPrompt?: string;
|
||||||
/** 对话内容 */
|
/** 对话内容 */
|
||||||
dialogueContent: string;
|
dialogueContent?: ContentItem[];
|
||||||
/** 角色ID替换参数,格式为{oldId:string,newId:string}[] */
|
/** 角色ID替换参数,格式为{oldId:string,newId:string}[] */
|
||||||
roleReplaceParams: { oldId: string; newId: string }[];
|
roleReplaceParams?: { oldId: string; newId: string }[];
|
||||||
/** 场景ID替换参数,格式为{oldId:string,newId:string}[] */
|
/** 场景ID替换参数,格式为{oldId:string,newId:string}[] */
|
||||||
sceneReplaceParams: { oldId: string; newId: string }[];
|
sceneReplaceParams?: { oldId: string; newId: string }[];
|
||||||
}): Promise<ApiResponse<ShotEntity>> => {
|
}): Promise<ApiResponse<ShotEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/regenerate_shot', request);
|
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);
|
return post<ApiResponse<any>>('/movie/get_shot_list', request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* 获取分镜详情
|
// * 获取分镜详情
|
||||||
* @param request - 获取分镜详情请求参数
|
// * @param request - 获取分镜详情请求参数
|
||||||
* @returns Promise<ApiResponse<分镜实体>>
|
// * @returns Promise<ApiResponse<分镜实体>>
|
||||||
*/
|
// */
|
||||||
export const getShotDetail = async (request: {
|
// export const getShotDetail = async (request: {
|
||||||
/** 分镜ID */
|
// /** 分镜ID */
|
||||||
shotId: string;
|
// shotId: string;
|
||||||
}): Promise<ApiResponse<ShotEntity>> => {
|
// }): Promise<ApiResponse<ShotEntity>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_detail', request);
|
// 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: {
|
export const getShotVideoScript = async (request: {
|
||||||
/** 分镜ID */
|
/** 分镜ID */
|
||||||
shotId: string;
|
shotId: string;
|
||||||
}): Promise<ApiResponse<ScriptSliceEntity[]>> => {
|
}): Promise<ApiResponse<string>> => {
|
||||||
return post<ApiResponse<any>>('/movie/get_shot_video_script', request);
|
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);
|
||||||
|
};
|
||||||
|
|||||||
290
app/service/Interaction/ScriptService.ts
Normal file
290
app/service/Interaction/ScriptService.ts
Normal 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
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,16 +1,22 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from "react";
|
||||||
import { ShotEntity, RoleEntity, SceneEntity, ScriptSliceEntity } from '../domain/Entities';
|
import {
|
||||||
import { ShotItem } from '../domain/Item';
|
ShotEntity,
|
||||||
import { ShotEditUseCase } from '../usecase/ShotEditUsecase';
|
RoleEntity,
|
||||||
|
SceneEntity,
|
||||||
|
ContentItem,
|
||||||
|
} from "../domain/Entities";
|
||||||
|
import { ScriptSlice, ScriptValueObject } from "../domain/valueObject";
|
||||||
|
import { ShotItem } from "../domain/Item";
|
||||||
|
import { ShotEditUseCase } from "../usecase/ShotEditUsecase";
|
||||||
import {
|
import {
|
||||||
getShotList,
|
getShotList,
|
||||||
getShotDetail,
|
// getShotDetail,
|
||||||
updateShotContent,
|
updateShotContent,
|
||||||
updateShotShot,
|
updateShotShot,
|
||||||
getUserRoleLibrary,
|
getUserRoleLibrary,
|
||||||
replaceShotRole,
|
replaceShotRole,
|
||||||
getShotVideoScript
|
getShotVideoScript,
|
||||||
} from '@/api/video_flow';
|
} from "@/api/video_flow";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分镜服务Hook接口
|
* 分镜服务Hook接口
|
||||||
@ -25,12 +31,12 @@ export interface UseShotService {
|
|||||||
/** 当前分镜的草图数据URL */
|
/** 当前分镜的草图数据URL */
|
||||||
shotSketchData: string | null;
|
shotSketchData: string | null;
|
||||||
/** 当前分镜的视频数据URL */
|
/** 当前分镜的视频数据URL */
|
||||||
shotVideoData: string | null;
|
shotVideoData: string[] | null;
|
||||||
|
|
||||||
/** 用户角色库 */
|
/** 用户角色库 */
|
||||||
userRoleLibrary: RoleEntity[];
|
userRoleLibrary: RoleEntity[];
|
||||||
/** 当前分镜的视频剧本片段 */
|
/** 当前分镜的视频剧本片段 */
|
||||||
shotVideoScript: ScriptSliceEntity[];
|
shotVideoScript: ScriptSlice[];
|
||||||
/** 加载状态 */
|
/** 加载状态 */
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
@ -42,7 +48,9 @@ export interface UseShotService {
|
|||||||
/** 选择分镜并获取详情 */
|
/** 选择分镜并获取详情 */
|
||||||
selectShot: (shotId: string) => Promise<void>;
|
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>;
|
updateShotShot: (newShot: string[]) => Promise<void>;
|
||||||
/** 获取用户角色库 */
|
/** 获取用户角色库 */
|
||||||
@ -59,7 +67,7 @@ export interface UseShotService {
|
|||||||
/** 重新生成分镜 */
|
/** 重新生成分镜 */
|
||||||
regenerateShot: (
|
regenerateShot: (
|
||||||
shotPrompt: string,
|
shotPrompt: string,
|
||||||
dialogueContent: string,
|
dialogueContent: ContentItem[],
|
||||||
roleReplaceParams: { oldId: string; newId: string }[],
|
roleReplaceParams: { oldId: string; newId: string }[],
|
||||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
@ -75,14 +83,15 @@ export const useShotService = (): UseShotService => {
|
|||||||
const [shotList, setShotList] = useState<ShotEntity[]>([]);
|
const [shotList, setShotList] = useState<ShotEntity[]>([]);
|
||||||
const [selectedShot, setSelectedShot] = useState<ShotItem | null>(null);
|
const [selectedShot, setSelectedShot] = useState<ShotItem | null>(null);
|
||||||
const [shotSketchData, setShotSketchData] = useState<string | 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 [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
||||||
const [shotVideoScript, setShotVideoScript] = useState<ScriptSliceEntity[]>([]);
|
const [shotVideoScript, setShotVideoScript] = 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>("");
|
||||||
// UseCase实例
|
// 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
|
* @param projectId 项目ID
|
||||||
*/
|
*/
|
||||||
const fetchShotList = useCallback(async (projectId: string) => {
|
const fetchShotList = useCallback(async (projectId: string) => {
|
||||||
|
setProjectId(projectId);
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@ -100,7 +110,9 @@ export const useShotService = (): UseShotService => {
|
|||||||
setError(`获取分镜列表失败: ${response.message}`);
|
setError(`获取分镜列表失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`获取分镜列表失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
setError(
|
||||||
|
`获取分镜列表失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -117,26 +129,28 @@ export const useShotService = (): UseShotService => {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 获取分镜详情
|
// 获取分镜详情
|
||||||
const response = await getShotDetail({ shotId });
|
await fetchShotList(projectId);
|
||||||
if (response.successful) {
|
const shotEntity = shotList.find(
|
||||||
const shotEntity = response.data;
|
(shot: ShotEntity) => shot.id === shotId
|
||||||
const shotItem = new ShotItem(shotEntity);
|
);
|
||||||
setSelectedShot(shotItem);
|
if (!shotEntity) {
|
||||||
|
setError(`分镜不存在: ${shotId}`);
|
||||||
// 初始化UseCase
|
return;
|
||||||
const newShotEditUseCase = new ShotEditUseCase(shotItem);
|
|
||||||
setShotEditUseCase(newShotEditUseCase);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 从分镜实体中获取草图数据和视频数据
|
|
||||||
setShotSketchData(shotEntity.sketchUrl || null);
|
|
||||||
setShotVideoData(shotEntity.videoUrl || null);
|
|
||||||
} else {
|
|
||||||
setError(`获取分镜详情失败: ${response.message}`);
|
|
||||||
}
|
}
|
||||||
|
const shotItem = new ShotItem(shotEntity);
|
||||||
|
setSelectedShot(shotItem);
|
||||||
|
|
||||||
|
// 初始化UseCase
|
||||||
|
const newShotEditUseCase = new ShotEditUseCase(shotItem);
|
||||||
|
setShotEditUseCase(newShotEditUseCase);
|
||||||
|
|
||||||
|
// 从分镜实体中获取草图数据和视频数据
|
||||||
|
setShotSketchData(shotEntity.sketchUrl || null);
|
||||||
|
setShotVideoData(shotEntity.videoUrl || null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`选择分镜失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
setError(
|
||||||
|
`选择分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -147,54 +161,66 @@ export const useShotService = (): UseShotService => {
|
|||||||
* @description 更新分镜的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
* @description 更新分镜的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||||||
* @param newContent 新的对话内容数组
|
* @param newContent 新的对话内容数组
|
||||||
*/
|
*/
|
||||||
const updateShotContentHandler = useCallback(async (newContent: Array<{ roleId: string; content: string }>) => {
|
const updateShotContentHandler = useCallback(
|
||||||
if (!shotEditUseCase) {
|
async (newContent: Array<{ roleId: string; content: string }>) => {
|
||||||
setError('分镜编辑用例未初始化');
|
if (!shotEditUseCase) {
|
||||||
return;
|
setError("分镜编辑用例未初始化");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const updatedShot = await shotEditUseCase.updateShotContent(newContent);
|
const updatedShot = await shotEditUseCase.updateShotContent(newContent);
|
||||||
setSelectedShot(new ShotItem(updatedShot));
|
setSelectedShot(new ShotItem(updatedShot));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`修改分镜对话内容失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
setError(
|
||||||
} finally {
|
`修改分镜对话内容失败: ${
|
||||||
setLoading(false);
|
err instanceof Error ? err.message : "未知错误"
|
||||||
}
|
}`
|
||||||
}, [shotEditUseCase]);
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[shotEditUseCase]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改分镜镜头
|
* 修改分镜镜头
|
||||||
* @description 更新分镜的镜头数据
|
* @description 更新分镜的镜头数据
|
||||||
* @param newShot 新的镜头数据
|
* @param newShot 新的镜头数据
|
||||||
*/
|
*/
|
||||||
const updateShotShotHandler = useCallback(async (newShot: string[]) => {
|
const updateShotShotHandler = useCallback(
|
||||||
if (!selectedShot) {
|
async (newShot: string[]) => {
|
||||||
setError('未选择分镜');
|
if (!selectedShot) {
|
||||||
return;
|
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 : '未知错误'}`);
|
try {
|
||||||
} finally {
|
setLoading(true);
|
||||||
setLoading(false);
|
setError(null);
|
||||||
}
|
const response = await updateShotShot({
|
||||||
}, [selectedShot]);
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户角色库
|
* 获取用户角色库
|
||||||
@ -211,7 +237,9 @@ export const useShotService = (): UseShotService => {
|
|||||||
setError(`获取用户角色库失败: ${response.message}`);
|
setError(`获取用户角色库失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`获取用户角色库失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
setError(
|
||||||
|
`获取用户角色库失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -223,32 +251,37 @@ export const useShotService = (): UseShotService => {
|
|||||||
* @param oldRoleId 旧角色ID
|
* @param oldRoleId 旧角色ID
|
||||||
* @param newRoleId 新角色ID
|
* @param newRoleId 新角色ID
|
||||||
*/
|
*/
|
||||||
const replaceShotRoleHandler = useCallback(async (oldRoleId: string, newRoleId: string) => {
|
const replaceShotRoleHandler = useCallback(
|
||||||
if (!selectedShot) {
|
async (oldRoleId: string, newRoleId: string) => {
|
||||||
setError('未选择分镜');
|
if (!selectedShot) {
|
||||||
return;
|
setError("未选择分镜");
|
||||||
}
|
return;
|
||||||
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
const response = await replaceShotRole({
|
|
||||||
shotId: selectedShot.entity.id,
|
|
||||||
oldRoleId,
|
|
||||||
newRoleId
|
|
||||||
});
|
|
||||||
if (response.successful) {
|
|
||||||
// 重新获取分镜详情
|
|
||||||
await selectShot(selectedShot.entity.id);
|
|
||||||
} else {
|
|
||||||
setError(`替换分镜角色失败: ${response.message}`);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
setError(`替换分镜角色失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
try {
|
||||||
} finally {
|
setLoading(true);
|
||||||
setLoading(false);
|
setError(null);
|
||||||
}
|
const response = await replaceShotRole({
|
||||||
}, [selectedShot, selectShot]);
|
shotId: selectedShot.entity.id,
|
||||||
|
oldRoleId,
|
||||||
|
newRoleId,
|
||||||
|
});
|
||||||
|
if (response.successful) {
|
||||||
|
// 重新获取分镜详情
|
||||||
|
await selectShot(selectedShot.entity.id);
|
||||||
|
} else {
|
||||||
|
setError(`替换分镜角色失败: ${response.message}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(
|
||||||
|
`替换分镜角色失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectedShot, selectShot]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分镜视频剧本内容
|
* 获取分镜视频剧本内容
|
||||||
@ -256,21 +289,28 @@ export const useShotService = (): UseShotService => {
|
|||||||
*/
|
*/
|
||||||
const fetchShotVideoScript = useCallback(async () => {
|
const fetchShotVideoScript = useCallback(async () => {
|
||||||
if (!selectedShot) {
|
if (!selectedShot) {
|
||||||
setError('未选择分镜');
|
setError("未选择分镜");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const response = await getShotVideoScript({ shotId: selectedShot.entity.id });
|
const response = await getShotVideoScript({
|
||||||
|
shotId: selectedShot.entity.id,
|
||||||
|
});
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
setShotVideoScript(response.data);
|
const script = new ScriptValueObject(response.data);
|
||||||
|
setShotVideoScript(script.scriptSlices);
|
||||||
} else {
|
} else {
|
||||||
setError(`获取分镜视频剧本失败: ${response.message}`);
|
setError(`获取分镜视频剧本失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`获取分镜视频剧本失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
setError(
|
||||||
|
`获取分镜视频剧本失败: ${
|
||||||
|
err instanceof Error ? err.message : "未知错误"
|
||||||
|
}`
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -283,7 +323,7 @@ export const useShotService = (): UseShotService => {
|
|||||||
*/
|
*/
|
||||||
const getShotRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
const getShotRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
||||||
if (!shotEditUseCase) {
|
if (!shotEditUseCase) {
|
||||||
throw new Error('分镜编辑用例未初始化');
|
throw new Error("分镜编辑用例未初始化");
|
||||||
}
|
}
|
||||||
return await shotEditUseCase.getShotRoles();
|
return await shotEditUseCase.getShotRoles();
|
||||||
}, [shotEditUseCase]);
|
}, [shotEditUseCase]);
|
||||||
@ -295,13 +335,11 @@ export const useShotService = (): UseShotService => {
|
|||||||
*/
|
*/
|
||||||
const getShotScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
const getShotScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
||||||
if (!shotEditUseCase) {
|
if (!shotEditUseCase) {
|
||||||
throw new Error('分镜编辑用例未初始化');
|
throw new Error("分镜编辑用例未初始化");
|
||||||
}
|
}
|
||||||
return await shotEditUseCase.getShotScenes();
|
return await shotEditUseCase.getShotScenes();
|
||||||
}, [shotEditUseCase]);
|
}, [shotEditUseCase]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新生成分镜
|
* 重新生成分镜
|
||||||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成分镜
|
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成分镜
|
||||||
@ -310,33 +348,38 @@ export const useShotService = (): UseShotService => {
|
|||||||
* @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(async (
|
const regenerateShot = useCallback(
|
||||||
shotPrompt: string,
|
async (
|
||||||
dialogueContent: string,
|
shotPrompt: string,
|
||||||
roleReplaceParams: { oldId: string; newId: string }[],
|
dialogueContent: ContentItem[],
|
||||||
sceneReplaceParams: { oldId: string; newId: string }[]
|
roleReplaceParams: { oldId: string; newId: string }[],
|
||||||
) => {
|
sceneReplaceParams: { oldId: string; newId: string }[]
|
||||||
if (!shotEditUseCase) {
|
) => {
|
||||||
setError('分镜编辑用例未初始化');
|
if (!shotEditUseCase) {
|
||||||
return;
|
setError("分镜编辑用例未初始化");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const updatedShot = await shotEditUseCase.regenerateShot(
|
const updatedShot = await shotEditUseCase.regenerateShot(
|
||||||
shotPrompt,
|
shotPrompt,
|
||||||
dialogueContent,
|
dialogueContent,
|
||||||
roleReplaceParams,
|
roleReplaceParams,
|
||||||
sceneReplaceParams
|
sceneReplaceParams
|
||||||
);
|
);
|
||||||
setSelectedShot(new ShotItem(updatedShot));
|
setSelectedShot(new ShotItem(updatedShot));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(`重新生成分镜失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
setError(
|
||||||
} finally {
|
`重新生成分镜失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||||||
setLoading(false);
|
);
|
||||||
}
|
} finally {
|
||||||
}, [shotEditUseCase]);
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[shotEditUseCase]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 响应式状态 - 用于UI组件订阅和渲染
|
// 响应式状态 - 用于UI组件订阅和渲染
|
||||||
|
|||||||
@ -65,13 +65,15 @@ export interface SceneEntity extends BaseEntity {
|
|||||||
/** 场景提示词Id */
|
/** 场景提示词Id */
|
||||||
generateTextId: string;
|
generateTextId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**对话内容项 */
|
/**对话内容项 */
|
||||||
interface ContentItem {
|
export interface ContentItem {
|
||||||
/** 角色ID */
|
/** 角色ID */
|
||||||
roleId: string;
|
roleId: string;
|
||||||
/** 对话内容 */
|
/** 对话内容 */
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
/**分镜进度 */
|
||||||
export enum ShotStatus {
|
export enum ShotStatus {
|
||||||
/** 草稿加载中 */
|
/** 草稿加载中 */
|
||||||
sketchLoading = 0,
|
sketchLoading = 0,
|
||||||
@ -89,7 +91,7 @@ export interface ShotEntity extends BaseEntity {
|
|||||||
/**分镜草图Url */
|
/**分镜草图Url */
|
||||||
sketchUrl: string;
|
sketchUrl: string;
|
||||||
/**分镜视频Url */
|
/**分镜视频Url */
|
||||||
videoUrl: string;
|
videoUrl: string[];
|
||||||
/**分镜状态 */
|
/**分镜状态 */
|
||||||
status: ShotStatus;
|
status: ShotStatus;
|
||||||
/**角色ID列表 */
|
/**角色ID列表 */
|
||||||
@ -100,30 +102,8 @@ export interface ShotEntity extends BaseEntity {
|
|||||||
content: ContentItem[];
|
content: ContentItem[];
|
||||||
/**镜头项 */
|
/**镜头项 */
|
||||||
shot: string[];
|
shot: string[];
|
||||||
/**分镜剧本 */
|
/**分镜剧本Id */
|
||||||
scriptId: string;
|
scriptId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**不同类型 将有不同元数据 */
|
|
||||||
export enum ScriptSliceType {
|
|
||||||
/** 文本 */
|
|
||||||
text = 'text',
|
|
||||||
/** 高亮 */
|
|
||||||
highlight = 'highlight',
|
|
||||||
/**角色 */
|
|
||||||
role = 'role',
|
|
||||||
/** 场景 */
|
|
||||||
scene = 'scene',
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 剧本片段实体接口
|
|
||||||
*/
|
|
||||||
export interface ScriptSliceEntity extends BaseEntity {
|
|
||||||
/** 类型 */
|
|
||||||
type: ScriptSliceType;
|
|
||||||
/** 剧本内容 */
|
|
||||||
text: string;
|
|
||||||
/** 元数据 */
|
|
||||||
metaData: any;
|
|
||||||
}
|
|
||||||
|
|||||||
78
app/service/domain/valueObject.ts
Normal file
78
app/service/domain/valueObject.ts
Normal 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("");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
124
app/service/usecase/ScriptEditUseCase.ts
Normal file
124
app/service/usecase/ScriptEditUseCase.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 { ShotItem, RoleItem, SceneItem, TextItem, TagItem } from '../domain/Item';
|
||||||
import {
|
import {
|
||||||
getShotRoles,
|
getShotRoles,
|
||||||
@ -116,7 +116,7 @@ export class ShotEditUseCase {
|
|||||||
*/
|
*/
|
||||||
async regenerateShot(
|
async regenerateShot(
|
||||||
shotPrompt: string,
|
shotPrompt: string,
|
||||||
dialogueContent: string,
|
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<ShotEntity> {
|
||||||
|
|||||||
0
app/service/usecase/index.ts
Normal file
0
app/service/usecase/index.ts
Normal file
11
utils/tools.ts
Normal file
11
utils/tools.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user