2025-09-08 16:34:15 +08:00

545 lines
18 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
useState,
useCallback,
Dispatch,
SetStateAction,
useMemo,
} from "react";
import { ScriptEditUseCase, ScriptEditKey } from "../usecase/ScriptEditUseCase";
import {
getProjectScript,
abortVideoTask,
} 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,
video_duration: string
) => Promise<void>;
/** 设置任何属性 */
setAnyAttribute: any;
}
/**
* 剧本服务Hook
* 提供剧本相关的所有状态管理和操作方法
* 包括剧本生成、项目创建、剧本保存等功能
*/
export const useScriptService = (): UseScriptService => {
// 响应式状态
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,
video_duration: string
): Promise<void> => {
try {
setLoading(true);
// 剧本生成完成后,自动创建项目
const projectData = await scriptEditUseCase.createProject(
idea,
userId,
mode as "automatic" | "manual",
resolution as "720p" | "1080p" | "4k",
language,
video_duration
);
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);
}
},
[generateScriptFromIdea]
);
/**
* 修改剧本
* @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>, needUpdate: boolean=true) => {
const newValue = typeof value === "function" ? value(synopsis) : value;
console.log('setSynopsisWrapper', newValue);
setSynopsis(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("synopsis", newValue);
}
},
[synopsis, scriptEditUseCase]
);
const setCategoriesWrapper = useCallback(
(value: SetStateAction<string[]>, needUpdate: boolean=true) => {
const newValue = typeof value === "function" ? value(categories) : value;
setCategories(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("categories", newValue);
}
},
[categories, scriptEditUseCase]
);
const setProtagonistWrapper = useCallback(
(value: SetStateAction<string>, needUpdate: boolean=true) => {
const newValue = typeof value === "function" ? value(protagonist) : value;
setProtagonist(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("protagonist", newValue);
}
},
[protagonist, scriptEditUseCase]
);
const setIncitingIncidentWrapper = useCallback(
(value: SetStateAction<string>, needUpdate: boolean=true) => {
const newValue =
typeof value === "function" ? value(incitingIncident) : value;
setIncitingIncident(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("incitingIncident", newValue);
}
},
[incitingIncident, scriptEditUseCase]
);
const setProblemWrapper = useCallback(
(value: SetStateAction<string>, needUpdate: boolean=true) => {
const newValue = typeof value === "function" ? value(problem) : value;
setProblem(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("problem", newValue);
}
},
[problem, scriptEditUseCase]
);
const setConflictWrapper = useCallback(
(value: SetStateAction<string>, needUpdate: boolean=true) => {
const newValue = typeof value === "function" ? value(conflict) : value;
setConflict(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("conflict", newValue);
}
},
[conflict, scriptEditUseCase]
);
const setStakesWrapper = useCallback(
(value: SetStateAction<string>, needUpdate: boolean=true) => {
const newValue = typeof value === "function" ? value(stakes) : value;
setStakes(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("stakes", newValue);
}
},
[stakes, scriptEditUseCase]
);
const setCharacterArcWrapper = useCallback(
(value: SetStateAction<string>, needUpdate: boolean=true) => {
const newValue =
typeof value === "function" ? value(characterArc) : value;
setCharacterArc(newValue);
if (scriptEditUseCase && needUpdate) {
scriptEditUseCase.updateStoryField("characterArc", newValue);
}
},
[characterArc, scriptEditUseCase]
);
const setAnyAttributeWrapper = useCallback(
(type: string, value: string,needUpdate: boolean=true,callback: (old: string) => void=() => {}) => {
console.log('setAnyAttributeWrapper', type);
if (type === 'synopsis') {
setFieldOld(synopsis)
scriptEditUseCase.replaceScript(fieldOld,value )
setSynopsisWrapper(value, needUpdate);
} else if (type === 'categories') {
setFieldOld(categories.join(','))
scriptEditUseCase.replaceScript(fieldOld,value)
setCategoriesWrapper(value.split(',') || [], needUpdate);
} else if (type === 'protagonist') {
setFieldOld(protagonist)
scriptEditUseCase.replaceScript(fieldOld,value)
setProtagonistWrapper(value, needUpdate);
} else if (type === 'incitingIncident') {
setFieldOld(incitingIncident)
scriptEditUseCase.replaceScript(fieldOld,value)
setIncitingIncidentWrapper(value, needUpdate);
} else if (type === 'problem') {
setFieldOld(problem)
scriptEditUseCase.replaceScript(fieldOld,value)
setProblemWrapper(value, needUpdate);
} else if (type === 'conflict') {
setFieldOld(conflict)
scriptEditUseCase.replaceScript(fieldOld,value)
setConflictWrapper(value, needUpdate);
} else if (type === 'stakes') {
setFieldOld(stakes)
scriptEditUseCase.replaceScript(fieldOld,value)
setStakesWrapper(value, needUpdate);
} else if (type === 'characterArc') {
setFieldOld(characterArc)
scriptEditUseCase.replaceScript(fieldOld,value)
setCharacterArcWrapper(value, needUpdate);
}
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', JSON.parse(JSON.stringify(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,
};
};