forked from 77media/video-flow
新增剧本增强功能,支持剧本流式处理和状态管理,更新相关服务以整合暂停和恢复计划功能,优化剧本更新逻辑。
This commit is contained in:
parent
735cd7ec68
commit
693f7599f1
@ -653,14 +653,6 @@ export const saveScript = async (request: {
|
||||
project_id: string;
|
||||
/** 剧本文本 */
|
||||
generated_script: string;
|
||||
/** 剧情梗概 */
|
||||
synopsis: string;
|
||||
/** 剧情类型 */
|
||||
categories: string[];
|
||||
/** 主角 */
|
||||
protagonist: string;
|
||||
/** 框架 */
|
||||
framework: string;
|
||||
}): Promise<ApiResponse<any>> => {
|
||||
return post<ApiResponse<any>>("/movie/update_generated_script", request);
|
||||
};
|
||||
@ -680,6 +672,24 @@ export const abortVideoTask = async (request: {
|
||||
return post("/api/v1/video/abort", request);
|
||||
};
|
||||
|
||||
export const pausePlanFlow = async (request: {
|
||||
/** 项目ID */
|
||||
project_id: string;
|
||||
/** 计划ID */
|
||||
plan_id: string;
|
||||
}): Promise<ApiResponse<any>> => {
|
||||
return post("/api/v1/video/pause", request);
|
||||
};
|
||||
|
||||
export const resumePlanFlow = async (request: {
|
||||
/** 项目ID */
|
||||
project_id: string;
|
||||
/** 计划ID */
|
||||
plan_id: string;
|
||||
}): Promise<ApiResponse<any>> => {
|
||||
return post("/api/v1/video/resume", request);
|
||||
};
|
||||
|
||||
export const createMovieProjectV1 = async (request: {
|
||||
/** 剧本内容 */
|
||||
script: string;
|
||||
@ -701,3 +711,55 @@ export const createMovieProjectV1 = async (request: {
|
||||
status: string;
|
||||
}>>("/movie/create_movie_project_v1", request);
|
||||
};
|
||||
|
||||
/**
|
||||
* 增强剧本流式接口
|
||||
* @param request - 增强剧本请求参数
|
||||
* @param onData - 流式数据回调
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export const enhanceScriptStream = (
|
||||
request: {
|
||||
/** 原始剧本文本 */
|
||||
original_script: string;
|
||||
/** 故事梗概 */
|
||||
synopsis?: string;
|
||||
/** 故事分类 */
|
||||
categories?: string[];
|
||||
/** 主角名称 */
|
||||
protagonist?: string;
|
||||
/** 激励事件 */
|
||||
incitingIncident?: string;
|
||||
/** 问题与新目标 */
|
||||
problem?: string;
|
||||
/** 冲突与障碍 */
|
||||
conflict?: string;
|
||||
/** 赌注 */
|
||||
stakes?: string;
|
||||
/** 人物弧线完成 */
|
||||
characterArc?: string;
|
||||
/** AI优化要求 */
|
||||
aiOptimizing: string;
|
||||
},
|
||||
onData: (data: any) => void
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
streamJsonPost("/movie/enhance_script", request, (data) => {
|
||||
switch(data.status) {
|
||||
case 'streaming':
|
||||
onData(data.content);
|
||||
break;
|
||||
|
||||
case 'completed':
|
||||
console.log('剧本增强完成:', data.message);
|
||||
resolve()
|
||||
return;
|
||||
|
||||
case 'error':
|
||||
console.error('剧本增强失败:', data.message);
|
||||
reject(data.message)
|
||||
return;
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState, useCallback, Dispatch, SetStateAction } from "react";
|
||||
import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase";
|
||||
import { getProjectScript, abortVideoTask } from "../../../api/video_flow";
|
||||
import { ScriptEditUseCase,ScriptEditKey } from "../usecase/ScriptEditUseCase";
|
||||
import { getProjectScript, abortVideoTask, pausePlanFlow, resumePlanFlow } from "../../../api/video_flow";
|
||||
|
||||
/**
|
||||
* 剧本服务Hook接口
|
||||
@ -30,18 +30,25 @@ export interface UseScriptService {
|
||||
projectId: string;
|
||||
/** 计划ID */
|
||||
planId: string;
|
||||
|
||||
/** AI优化要求 */
|
||||
aiOptimizing: string;
|
||||
// 操作方法
|
||||
/** 根据用户想法生成剧本并自动创建项目 */
|
||||
generateScriptFromIdea: (idea: string) => Promise<void>;
|
||||
/** 根据项目ID初始化已有剧本 */
|
||||
initializeFromProject: (projectId: string) => Promise<void>;
|
||||
/** 修改剧本 */
|
||||
updateScript: (scriptText: string) => void;
|
||||
updateScript: (scriptText: string) => Promise<void>;
|
||||
/** 应用剧本到视频生成流程 */
|
||||
applyScript: () => Promise<void>;
|
||||
/** 中断视频任务 */
|
||||
abortVideoTask: () => Promise<void>;
|
||||
/** 聚焦处理函数 */
|
||||
focusHandler: (field: 'synopsis' | 'categories' | 'protagonist' | 'incitingIncident' | 'problem' | 'conflict' | 'stakes' | 'characterArc') => Promise<void>;
|
||||
/** 增强剧本 */
|
||||
enhanceScript: () => Promise<void>;
|
||||
/** 设置AI优化要求 */
|
||||
setAiOptimizing: Dispatch<SetStateAction<string>>;
|
||||
|
||||
// 修改字段的set函数
|
||||
/** 设置故事梗概 */
|
||||
@ -81,6 +88,8 @@ export const useScriptService = (): UseScriptService => {
|
||||
const [characterArc, setCharacterArc] = useState<string>("");
|
||||
const [projectId, setProjectId] = useState<string>("");
|
||||
const [planId, setPlanId] = useState<string>("");
|
||||
const [aiOptimizing, setAiOptimizing] = useState<string>("");
|
||||
const [focusedField, setFocusedField] = useState<string>("");
|
||||
|
||||
// UseCase实例
|
||||
const [scriptEditUseCase, setScriptEditUseCase] = useState<ScriptEditUseCase | null>(null);
|
||||
@ -180,22 +189,16 @@ export const useScriptService = (): UseScriptService => {
|
||||
* 修改剧本
|
||||
* @param scriptText 新的剧本文本
|
||||
*/
|
||||
const updateScript = useCallback((scriptText: string): void => {
|
||||
const updateScript = useCallback(async (): Promise<void> => {
|
||||
if (scriptEditUseCase) {
|
||||
scriptEditUseCase.updateScript(scriptText);
|
||||
|
||||
// 更新解析后的故事详情
|
||||
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);
|
||||
}
|
||||
}
|
||||
}, [scriptEditUseCase]);
|
||||
}, [scriptEditUseCase, projectId]);
|
||||
|
||||
/**
|
||||
* 应用剧本到视频生成流程
|
||||
@ -250,6 +253,130 @@ export const useScriptService = (): UseScriptService => {
|
||||
}
|
||||
}, [projectId, planId]);
|
||||
|
||||
// 封装的setter函数,同时更新hook状态和scriptEditUseCase中的值对象
|
||||
const setSynopsisWrapper = useCallback((value: SetStateAction<string>) => {
|
||||
const newValue = typeof value === 'function' ? value(synopsis) : value;
|
||||
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 focusHandler = useCallback(async (field: ScriptEditKey): Promise<void> => {
|
||||
try {
|
||||
// 如果当前已经有聚焦的字段,先处理暂停/继续逻辑
|
||||
|
||||
// 设置新的聚焦字段
|
||||
setFocusedField(field);
|
||||
|
||||
} catch (error) {
|
||||
console.error("聚焦处理失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 增强剧本
|
||||
*/
|
||||
const enhanceScript = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (!scriptEditUseCase) {
|
||||
throw new Error("剧本编辑用例未初始化");
|
||||
}
|
||||
|
||||
// 调用增强剧本方法
|
||||
await scriptEditUseCase.enhanceScript(
|
||||
synopsis,
|
||||
focusedField as ScriptEditKey,
|
||||
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, synopsis, focusedField, aiOptimizing, projectId]);
|
||||
|
||||
return {
|
||||
// 响应式状态
|
||||
loading,
|
||||
@ -263,6 +390,7 @@ export const useScriptService = (): UseScriptService => {
|
||||
characterArc,
|
||||
projectId,
|
||||
planId,
|
||||
aiOptimizing,
|
||||
|
||||
// 操作方法
|
||||
generateScriptFromIdea,
|
||||
@ -270,15 +398,17 @@ export const useScriptService = (): UseScriptService => {
|
||||
updateScript,
|
||||
applyScript,
|
||||
abortVideoTask: abortVideoTaskHandler,
|
||||
|
||||
// 修改字段的set函数
|
||||
setSynopsis,
|
||||
setCategories,
|
||||
setProtagonist,
|
||||
setIncitingIncident,
|
||||
setProblem,
|
||||
setConflict,
|
||||
setStakes,
|
||||
setCharacterArc,
|
||||
focusHandler,
|
||||
enhanceScript,
|
||||
setAiOptimizing,
|
||||
// 封装的set函数
|
||||
setSynopsis: setSynopsisWrapper,
|
||||
setCategories: setCategoriesWrapper,
|
||||
setProtagonist: setProtagonistWrapper,
|
||||
setIncitingIncident: setIncitingIncidentWrapper,
|
||||
setProblem: setProblemWrapper,
|
||||
setConflict: setConflictWrapper,
|
||||
setStakes: setStakesWrapper,
|
||||
setCharacterArc: setCharacterArcWrapper,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
import { ScriptSlice, ScriptValueObject } from "../domain/valueObject";
|
||||
import { generateScriptStream, applyScriptToShot, createMovieProjectV1, saveScript } from "@/api/video_flow";
|
||||
import {
|
||||
generateScriptStream,
|
||||
applyScriptToShot,
|
||||
createMovieProjectV1,
|
||||
saveScript,
|
||||
enhanceScriptStream,
|
||||
} from "@/api/video_flow";
|
||||
|
||||
export type ScriptEditKey = 'synopsis' | 'categories' | 'protagonist' | 'incitingIncident' | 'problem' | 'conflict' | 'stakes' | 'characterArc';
|
||||
|
||||
export class ScriptEditUseCase {
|
||||
loading: boolean = false;
|
||||
@ -16,7 +24,10 @@ export class ScriptEditUseCase {
|
||||
* @param stream_callback 流式数据回调函数
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async generateScript(prompt: string, stream_callback?: (data: any) => void): Promise<void> {
|
||||
async generateScript(
|
||||
prompt: string,
|
||||
stream_callback?: (data: any) => void
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
@ -24,14 +35,15 @@ export class ScriptEditUseCase {
|
||||
this.abortController = new AbortController();
|
||||
|
||||
// 使用API接口生成剧本
|
||||
await generateScriptStream({
|
||||
await generateScriptStream(
|
||||
{
|
||||
text: prompt,
|
||||
},(content)=>{
|
||||
stream_callback?.(content)
|
||||
this.scriptValueObject.parseFromString(content)
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
(content) => {
|
||||
stream_callback?.(content);
|
||||
this.scriptValueObject.parseFromString(content);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.abortController?.signal.aborted) {
|
||||
console.log("剧本生成被中断");
|
||||
@ -55,6 +67,53 @@ export class ScriptEditUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 增强剧本方法
|
||||
* @param scriptData 剧本数据
|
||||
* @param stream_callback 流式数据回调函数
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async enhanceScript(
|
||||
newScript: string|string[],
|
||||
key: ScriptEditKey,
|
||||
aiOptimizing: string,
|
||||
stream_callback?: (data: any) => void
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
// 创建新的中断控制器
|
||||
this.abortController = new AbortController();
|
||||
|
||||
// 获取当前剧本文本
|
||||
const originalScript = this.scriptValueObject.toString();
|
||||
// 清空当前剧本
|
||||
this.scriptValueObject = new ScriptValueObject("");
|
||||
// 使用API接口增强剧本
|
||||
await enhanceScriptStream(
|
||||
{
|
||||
original_script: originalScript,
|
||||
[key]: newScript,
|
||||
aiOptimizing,
|
||||
},
|
||||
(content) => {
|
||||
stream_callback?.(content);
|
||||
this.scriptValueObject.parseFromString(content);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.abortController?.signal.aborted) {
|
||||
console.log("剧本增强被中断");
|
||||
return;
|
||||
}
|
||||
console.error("AI增强剧本出错:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 创建项目
|
||||
* @param prompt 用户提示词
|
||||
@ -75,7 +134,7 @@ export class ScriptEditUseCase {
|
||||
script: prompt,
|
||||
user_id: userId,
|
||||
mode,
|
||||
resolution
|
||||
resolution,
|
||||
});
|
||||
|
||||
if (!response.successful) {
|
||||
@ -91,9 +150,12 @@ export class ScriptEditUseCase {
|
||||
/**
|
||||
* @description: 保存剧本
|
||||
* @param projectId 项目ID
|
||||
* @param scriptData 剧本数据
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async saveScript(projectId: string): Promise<void> {
|
||||
async saveScript(
|
||||
projectId: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
@ -104,10 +166,6 @@ export class ScriptEditUseCase {
|
||||
const response = await saveScript({
|
||||
project_id: projectId,
|
||||
generated_script: scriptText,
|
||||
synopsis: this.scriptValueObject.storyDetails.synopsis,
|
||||
categories: this.scriptValueObject.storyDetails.categories,
|
||||
protagonist: this.scriptValueObject.storyDetails.protagonist,
|
||||
framework: this.scriptValueObject.storyDetails.mergeframework()
|
||||
});
|
||||
|
||||
if (!response.successful) {
|
||||
@ -125,14 +183,14 @@ export class ScriptEditUseCase {
|
||||
* @description: 应用剧本方法
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async applyScript(projectId: string,planId: string): Promise<void> {
|
||||
async applyScript(projectId: string, planId: string): Promise<void> {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
// 调用应用剧本接口
|
||||
const response = await applyScriptToShot({
|
||||
project_id: projectId,
|
||||
plan_id: planId
|
||||
plan_id: planId,
|
||||
});
|
||||
|
||||
if (!response.successful) {
|
||||
@ -162,6 +220,50 @@ export class ScriptEditUseCase {
|
||||
getStoryDetails() {
|
||||
return this.scriptValueObject.storyDetails.toObject();
|
||||
}
|
||||
/**
|
||||
* @description: 更新剧本
|
||||
* @param scriptText 新的剧本文本
|
||||
*/
|
||||
updateScript(scriptText: string): void {
|
||||
this.scriptValueObject = new ScriptValueObject(scriptText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 更新故事详情字段
|
||||
* @param field 字段名
|
||||
* @param value 字段值
|
||||
*/
|
||||
updateStoryField(field: string, value: string | string[]): void {
|
||||
const storyDetails = this.scriptValueObject.storyDetails;
|
||||
|
||||
switch (field) {
|
||||
case 'synopsis':
|
||||
storyDetails.synopsis = value as string;
|
||||
break;
|
||||
case 'categories':
|
||||
storyDetails.categories = value as string[];
|
||||
break;
|
||||
case 'protagonist':
|
||||
storyDetails.protagonist = value as string;
|
||||
break;
|
||||
case 'incitingIncident':
|
||||
storyDetails.incitingIncident = value as string;
|
||||
break;
|
||||
case 'problem':
|
||||
storyDetails.problem = value as string;
|
||||
break;
|
||||
case 'conflict':
|
||||
storyDetails.conflict = value as string;
|
||||
break;
|
||||
case 'stakes':
|
||||
storyDetails.stakes = value as string;
|
||||
break;
|
||||
case 'characterArc':
|
||||
storyDetails.characterArc = value as string;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 获取加载状态
|
||||
* @returns boolean
|
||||
@ -170,14 +272,6 @@ export class ScriptEditUseCase {
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 更新剧本
|
||||
* @param scriptText 剧本文本字符串
|
||||
*/
|
||||
updateScript(scriptText: string): void {
|
||||
this.scriptValueObject = new ScriptValueObject(scriptText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 将当前剧本片段转换为字符串
|
||||
* @returns string
|
||||
@ -185,5 +279,4 @@ export class ScriptEditUseCase {
|
||||
toString(): string {
|
||||
return this.scriptValueObject.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user