forked from 77media/video-flow
547 lines
17 KiB
TypeScript
547 lines
17 KiB
TypeScript
import {
|
||
useState,
|
||
useCallback,
|
||
Dispatch,
|
||
SetStateAction,
|
||
useMemo,
|
||
} from "react";
|
||
import { ScriptEditUseCase, ScriptEditKey } from "../usecase/ScriptEditUseCase";
|
||
import {
|
||
getProjectScript,
|
||
abortVideoTask,
|
||
pausePlanFlow,
|
||
resumePlanFlow,
|
||
} from "../../../api/video_flow";
|
||
import { parseScriptBlock } from "../domain/service";
|
||
import { ScriptBlock } from "@/components/script-renderer/types";
|
||
|
||
/**
|
||
* 剧本服务Hook接口
|
||
* 定义剧本服务Hook的所有状态和操作方法
|
||
*/
|
||
export interface UseScriptService {
|
||
// 响应式状态
|
||
/** 加载状态 */
|
||
loading: boolean;
|
||
/** 故事梗概 */
|
||
synopsis: string;
|
||
/** 故事分类 */
|
||
categories: string[];
|
||
/** 主角*/
|
||
protagonist: string;
|
||
/** 激励事件 */
|
||
incitingIncident: string;
|
||
/** 问题与新目标 */
|
||
problem: string;
|
||
/** 冲突与障碍 */
|
||
conflict: string;
|
||
/** 赌注 */
|
||
stakes: string;
|
||
/** 人物弧线完成 */
|
||
characterArc: string;
|
||
/** 项目ID */
|
||
projectId: string;
|
||
/** AI优化要求 */
|
||
aiOptimizing: string;
|
||
/** 渲染数据 */
|
||
scriptBlocksMemo: ScriptBlock[];
|
||
// 操作方法
|
||
/** 根据用户想法生成剧本并自动创建项目 */
|
||
generateScriptFromIdea: (idea: string) => Promise<void>;
|
||
/** 根据项目ID初始化已有剧本 */
|
||
initializeFromProject: (projectId: string, script: string) => Promise<void>;
|
||
/** 修改剧本 */
|
||
updateScript: () => Promise<void>;
|
||
/** 应用剧本到视频生成流程 */
|
||
applyScript: () => Promise<void>;
|
||
/** 中断视频任务 */
|
||
abortVideoTask: () => Promise<void>;
|
||
/** 增强剧本 */
|
||
enhanceScript: () => Promise<void>;
|
||
/** 设置AI优化要求 */
|
||
setAiOptimizing: Dispatch<SetStateAction<string>>;
|
||
|
||
// 修改字段的set函数
|
||
/** 设置故事梗概 */
|
||
setSynopsis: Dispatch<SetStateAction<string>>;
|
||
/** 设置故事分类 */
|
||
setCategories: Dispatch<SetStateAction<string[]>>;
|
||
/** 设置主角名称 */
|
||
setProtagonist: Dispatch<SetStateAction<string>>;
|
||
/** 设置激励事件 */
|
||
setIncitingIncident: Dispatch<SetStateAction<string>>;
|
||
/** 设置问题与新目标 */
|
||
setProblem: Dispatch<SetStateAction<string>>;
|
||
/** 设置冲突与障碍 */
|
||
setConflict: Dispatch<SetStateAction<string>>;
|
||
/** 设置赌注 */
|
||
setStakes: Dispatch<SetStateAction<string>>;
|
||
/** 设置人物弧线完成 */
|
||
setCharacterArc: Dispatch<SetStateAction<string>>;
|
||
|
||
/** 设置项目ID */
|
||
setProjectId: Dispatch<SetStateAction<string>>;
|
||
/** 创建项目 */
|
||
createMovieProjectV1: (
|
||
idea: string,
|
||
userId: string,
|
||
mode: "automatic" | "manual",
|
||
resolution: string,
|
||
language: string
|
||
) => Promise<void>;
|
||
/** 设置任何属性 */
|
||
setAnyAttribute: any;
|
||
}
|
||
|
||
/**
|
||
* 剧本服务Hook
|
||
* 提供剧本相关的所有状态管理和操作方法
|
||
* 包括剧本生成、项目创建、剧本保存等功能
|
||
*/
|
||
export const useScriptService = (): UseScriptService => {
|
||
console.log('useScriptService----9((@@@@@@@@@@@@@@@@@@');
|
||
// 响应式状态
|
||
const [loading, setLoading] = useState<boolean>(false);
|
||
const [synopsis, setSynopsis] = useState<string>("");
|
||
const [categories, setCategories] = useState<string[]>([]);
|
||
const [protagonist, setProtagonist] = useState<string>("");
|
||
const [incitingIncident, setIncitingIncident] = useState<string>("");
|
||
const [problem, setProblem] = useState<string>("");
|
||
const [conflict, setConflict] = useState<string>("");
|
||
const [stakes, setStakes] = useState<string>("");
|
||
const [characterArc, setCharacterArc] = useState<string>("");
|
||
const [projectId, setProjectId] = useState<string>("");
|
||
const [aiOptimizing, setAiOptimizing] = useState<string>("");
|
||
const [fieldOld, setFieldOld] = useState<string>("");//旧的剧本内容
|
||
|
||
// UseCase实例
|
||
const [scriptEditUseCase, setScriptEditUseCase] = useState<ScriptEditUseCase>(
|
||
new ScriptEditUseCase("")
|
||
);
|
||
/**
|
||
* 根据用户想法生成剧本并自动创建项目
|
||
* @param idea 用户想法
|
||
*/
|
||
const generateScriptFromIdea = useCallback(
|
||
async (idea: string,project_id?:string): Promise<void> => {
|
||
try {
|
||
setLoading(true);
|
||
if(project_id){
|
||
setProjectId(project_id);
|
||
}
|
||
// 调用AI生成剧本
|
||
await scriptEditUseCase.generateScript(idea, (content) => {
|
||
// 获取解析后的故事详情
|
||
const storyDetails = scriptEditUseCase.getStoryDetails();
|
||
setSynopsis(storyDetails.synopsis || "");
|
||
setCategories(storyDetails.categories || []);
|
||
setProtagonist(storyDetails.protagonist || "");
|
||
setIncitingIncident(storyDetails.incitingIncident || "");
|
||
setProblem(storyDetails.problem || "");
|
||
setConflict(storyDetails.conflict || "");
|
||
setStakes(storyDetails.stakes || "");
|
||
setCharacterArc(storyDetails.characterArc || "");
|
||
});
|
||
|
||
// 自动保存剧本到项目
|
||
await scriptEditUseCase.saveScript((projectId||project_id) as string);
|
||
} catch (error) {
|
||
console.error("生成剧本失败:", error);
|
||
throw error;
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[projectId, scriptEditUseCase]
|
||
);
|
||
|
||
const createMovieProjectV1 = useCallback(
|
||
async (
|
||
idea: string,
|
||
userId: string,
|
||
mode: "automatic" | "manual",
|
||
resolution: string,
|
||
language: string
|
||
): Promise<void> => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
// 剧本生成完成后,自动创建项目
|
||
const projectData = await scriptEditUseCase.createProject(
|
||
idea,
|
||
userId,
|
||
mode as "automatic" | "manual",
|
||
resolution as "720p" | "1080p" | "4k",
|
||
language
|
||
);
|
||
|
||
setProjectId(projectData.project_id);
|
||
} catch (error) {
|
||
console.error("创建项目失败:", error);
|
||
throw error;
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[scriptEditUseCase]
|
||
);
|
||
/**
|
||
* 根据项目ID初始化已有剧本
|
||
* @param projectId 项目ID
|
||
*/
|
||
const initializeFromProject = useCallback(
|
||
async (projectId: string,script:string): Promise<void> => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
// 设置项目ID
|
||
setProjectId(projectId);
|
||
|
||
// 调用API获取项目剧本数据
|
||
const response = await getProjectScript({ project_id: projectId });
|
||
if(response.data.generated_script===''){
|
||
await generateScriptFromIdea(script,projectId)
|
||
return
|
||
}
|
||
if (!response.successful) {
|
||
throw new Error(response.message || "获取项目剧本失败");
|
||
}
|
||
|
||
const { generated_script } = response.data;
|
||
|
||
// 创建新的剧本编辑用例并初始化数据
|
||
const newScriptEditUseCase = new ScriptEditUseCase(generated_script);
|
||
setScriptEditUseCase(newScriptEditUseCase);
|
||
|
||
// 获取解析后的故事详情
|
||
const storyDetails = newScriptEditUseCase.getStoryDetails();
|
||
|
||
setSynopsis(storyDetails.synopsis || "");
|
||
setCategories(storyDetails.categories || []);
|
||
setProtagonist(storyDetails.protagonist || "");
|
||
setIncitingIncident(storyDetails.incitingIncident || "");
|
||
setProblem(storyDetails.problem || "");
|
||
setConflict(storyDetails.conflict || "");
|
||
setStakes(storyDetails.stakes || "");
|
||
setCharacterArc(storyDetails.characterArc || "");
|
||
} catch (error) {
|
||
console.error("初始化项目剧本失败:", error);
|
||
throw error;
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[projectId, scriptEditUseCase]
|
||
);
|
||
|
||
/**
|
||
* 修改剧本
|
||
* @param scriptText 新的剧本文本
|
||
*/
|
||
const updateScript = useCallback(async (): Promise<void> => {
|
||
if (scriptEditUseCase) {
|
||
// 更新解析后的故事详情
|
||
const storyDetails = scriptEditUseCase.getStoryDetails();
|
||
// 如果有项目ID,则保存剧本
|
||
if (projectId) {
|
||
await scriptEditUseCase.saveScript(projectId);
|
||
}
|
||
}
|
||
}, [scriptEditUseCase, projectId]);
|
||
|
||
/**
|
||
* 应用剧本到视频生成流程
|
||
*/
|
||
const applyScript = useCallback(async (): Promise<void> => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
if (!scriptEditUseCase) {
|
||
throw new Error("剧本编辑用例未初始化");
|
||
}
|
||
|
||
if (!projectId) {
|
||
throw new Error("项目ID或计划ID未设置");
|
||
}
|
||
|
||
await scriptEditUseCase.applyScript(projectId);
|
||
} catch (error) {
|
||
console.error("应用剧本失败:", error);
|
||
throw error;
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [scriptEditUseCase, projectId]);
|
||
|
||
/**
|
||
* 中断视频任务
|
||
*/
|
||
const abortVideoTaskHandler = useCallback(async (): Promise<void> => {
|
||
try {
|
||
if (!projectId) {
|
||
throw new Error("项目ID或计划ID未设置");
|
||
}
|
||
|
||
// 调用中断视频任务API
|
||
const response = await abortVideoTask({
|
||
project_id: projectId,
|
||
});
|
||
|
||
if (!response.successful) {
|
||
throw new Error(response.message || "中断视频任务失败");
|
||
}
|
||
|
||
console.log("视频任务中断成功");
|
||
} catch (error) {
|
||
console.error("中断视频任务失败:", error);
|
||
throw error;
|
||
}
|
||
}, [projectId]);
|
||
|
||
// 封装的setter函数,同时更新hook状态和scriptEditUseCase中的值对象
|
||
const setSynopsisWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue = typeof value === "function" ? value(synopsis) : value;
|
||
console.log('setSynopsisWrapper', newValue);
|
||
setSynopsis(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("synopsis", newValue);
|
||
}
|
||
},
|
||
[synopsis, scriptEditUseCase]
|
||
);
|
||
|
||
const setCategoriesWrapper = useCallback(
|
||
(value: SetStateAction<string[]>) => {
|
||
const newValue = typeof value === "function" ? value(categories) : value;
|
||
setCategories(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("categories", newValue);
|
||
}
|
||
},
|
||
[categories, scriptEditUseCase]
|
||
);
|
||
|
||
const setProtagonistWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue = typeof value === "function" ? value(protagonist) : value;
|
||
setProtagonist(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("protagonist", newValue);
|
||
}
|
||
},
|
||
[protagonist, scriptEditUseCase]
|
||
);
|
||
|
||
const setIncitingIncidentWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue =
|
||
typeof value === "function" ? value(incitingIncident) : value;
|
||
setIncitingIncident(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("incitingIncident", newValue);
|
||
}
|
||
},
|
||
[incitingIncident, scriptEditUseCase]
|
||
);
|
||
|
||
const setProblemWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue = typeof value === "function" ? value(problem) : value;
|
||
setProblem(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("problem", newValue);
|
||
}
|
||
},
|
||
[problem, scriptEditUseCase]
|
||
);
|
||
|
||
const setConflictWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue = typeof value === "function" ? value(conflict) : value;
|
||
setConflict(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("conflict", newValue);
|
||
}
|
||
},
|
||
[conflict, scriptEditUseCase]
|
||
);
|
||
|
||
const setStakesWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue = typeof value === "function" ? value(stakes) : value;
|
||
setStakes(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("stakes", newValue);
|
||
}
|
||
},
|
||
[stakes, scriptEditUseCase]
|
||
);
|
||
|
||
const setCharacterArcWrapper = useCallback(
|
||
(value: SetStateAction<string>) => {
|
||
const newValue =
|
||
typeof value === "function" ? value(characterArc) : value;
|
||
setCharacterArc(newValue);
|
||
if (scriptEditUseCase) {
|
||
scriptEditUseCase.updateStoryField("characterArc", newValue);
|
||
}
|
||
},
|
||
[characterArc, scriptEditUseCase]
|
||
);
|
||
|
||
const setAnyAttributeWrapper = useCallback(
|
||
(type: string, value: string,callback: (old: string) => void=() => {}) => {
|
||
console.log('setAnyAttributeWrapper', type);
|
||
if (type === 'synopsis') {
|
||
setFieldOld(synopsis)
|
||
scriptEditUseCase.replaceScript(fieldOld,value )
|
||
setSynopsisWrapper(value);
|
||
} else if (type === 'categories') {
|
||
setFieldOld(categories.join(','))
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setCategoriesWrapper(value.split(',') || []);
|
||
} else if (type === 'protagonist') {
|
||
setFieldOld(protagonist)
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setProtagonistWrapper(value);
|
||
} else if (type === 'incitingIncident') {
|
||
setFieldOld(incitingIncident)
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setIncitingIncidentWrapper(value);
|
||
} else if (type === 'problem') {
|
||
setFieldOld(problem)
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setProblemWrapper(value);
|
||
} else if (type === 'conflict') {
|
||
setFieldOld(conflict)
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setConflictWrapper(value);
|
||
} else if (type === 'stakes') {
|
||
setFieldOld(stakes)
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setStakesWrapper(value);
|
||
} else if (type === 'characterArc') {
|
||
setFieldOld(characterArc)
|
||
scriptEditUseCase.replaceScript(fieldOld,value)
|
||
setCharacterArcWrapper(value);
|
||
}
|
||
callback(fieldOld)
|
||
},
|
||
[categories, characterArc, conflict, fieldOld, incitingIncident, problem, protagonist, scriptEditUseCase, setCategoriesWrapper, setCharacterArcWrapper, setConflictWrapper, setIncitingIncidentWrapper, setProblemWrapper, setProtagonistWrapper, setStakesWrapper, setSynopsisWrapper, stakes, synopsis]
|
||
);
|
||
/**
|
||
* 增强剧本
|
||
*/
|
||
const enhanceScript = useCallback(async (): Promise<void> => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
if (!scriptEditUseCase) {
|
||
throw new Error("剧本编辑用例未初始化");
|
||
}
|
||
|
||
// 调用增强剧本方法
|
||
await scriptEditUseCase.enhanceScript(
|
||
aiOptimizing,
|
||
(content: any) => {
|
||
// 获取解析后的故事详情
|
||
const storyDetails = scriptEditUseCase.getStoryDetails();
|
||
setSynopsis(storyDetails.synopsis || "");
|
||
setCategories(storyDetails.categories || []);
|
||
setProtagonist(storyDetails.protagonist || "");
|
||
setIncitingIncident(storyDetails.incitingIncident || "");
|
||
setProblem(storyDetails.problem || "");
|
||
setConflict(storyDetails.conflict || "");
|
||
setStakes(storyDetails.stakes || "");
|
||
setCharacterArc(storyDetails.characterArc || "");
|
||
}
|
||
);
|
||
|
||
// 如果有项目ID,则保存增强后的剧本
|
||
if (projectId) {
|
||
await scriptEditUseCase.saveScript(projectId);
|
||
}
|
||
} catch (error) {
|
||
console.error("增强剧本失败:", error);
|
||
throw error;
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [scriptEditUseCase, aiOptimizing, projectId]);
|
||
|
||
// 在ScriptService中添加一个方法来获取渲染数据
|
||
const scriptBlocksMemo = useMemo((): ScriptBlock[] => {
|
||
const arr = [
|
||
parseScriptBlock("synopsis", "Logline", synopsis || ""),
|
||
parseScriptBlock('categories', 'GENRE', categories.join(', ') || '', 'tag'),
|
||
parseScriptBlock("protagonist", "Core Identity", protagonist || ""),
|
||
parseScriptBlock(
|
||
"incitingIncident",
|
||
"The Inciting Incident",
|
||
incitingIncident || ""
|
||
),
|
||
parseScriptBlock("problem", "The Problem & New Goal", problem || ""),
|
||
parseScriptBlock("conflict", "Conflict & Obstacles", conflict || ""),
|
||
parseScriptBlock("stakes", "The Stakes", stakes || ""),
|
||
parseScriptBlock(
|
||
"characterArc",
|
||
"Character Arc Accomplished",
|
||
characterArc || ""
|
||
),
|
||
];
|
||
// 筛选出有内容的block
|
||
const filteredArr = arr.filter(item => (item.content.length > 0 && item.content[0].text !== ''));
|
||
console.log('scriptBlocksMemo 所有关联数据', synopsis, categories, protagonist, incitingIncident, problem, conflict, stakes, characterArc);
|
||
console.log('scriptBlocksMemo', filteredArr);
|
||
return filteredArr;
|
||
}, [
|
||
synopsis,
|
||
categories,
|
||
protagonist,
|
||
incitingIncident,
|
||
problem,
|
||
conflict,
|
||
stakes,
|
||
characterArc,
|
||
|
||
]);
|
||
|
||
return {
|
||
// 响应式状态
|
||
loading,
|
||
synopsis,
|
||
categories,
|
||
protagonist,
|
||
incitingIncident,
|
||
problem,
|
||
conflict,
|
||
stakes,
|
||
characterArc,
|
||
projectId,
|
||
aiOptimizing,
|
||
scriptBlocksMemo,
|
||
// 操作方法
|
||
generateScriptFromIdea,
|
||
initializeFromProject,
|
||
updateScript,
|
||
applyScript,
|
||
abortVideoTask: abortVideoTaskHandler,
|
||
enhanceScript,
|
||
setAiOptimizing,
|
||
setProjectId,
|
||
createMovieProjectV1,
|
||
// 封装的set函数
|
||
setSynopsis: setSynopsisWrapper,
|
||
setCategories: setCategoriesWrapper,
|
||
setProtagonist: setProtagonistWrapper,
|
||
setIncitingIncident: setIncitingIncidentWrapper,
|
||
setProblem: setProblemWrapper,
|
||
setConflict: setConflictWrapper,
|
||
setStakes: setStakesWrapper,
|
||
setCharacterArc: setCharacterArcWrapper,
|
||
// 封装统一的set函数
|
||
setAnyAttribute: setAnyAttributeWrapper,
|
||
};
|
||
};
|