diff --git a/api/video_flow.ts b/api/video_flow.ts index 1069512..9e2a896 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -617,8 +617,6 @@ export const generateScriptStream = ( export const applyScriptToShot = async (request: { /** 项目ID */ project_id: string; - /** 计划Id*/ - plan_id: string; })=> { return post>("/movie/create_movie_project_plan_v1", request); }; @@ -659,17 +657,14 @@ export const saveScript = async (request: { /** - * 创建电影项目V1版本 - * @param request 创建电影项目请求参数 + * 中断视频任务 * @returns Promise> */ export const abortVideoTask = async (request: { /** 项目ID */ project_id: string; - /** 计划ID */ - plan_id: string; }): Promise> => { - return post("/api/v1/video/abort", request); + return post("/movie/abort_video_task", request); }; export const pausePlanFlow = async (request: { diff --git a/app/service/Interaction/ScriptService.ts b/app/service/Interaction/ScriptService.ts index bc1af5f..7c988be 100644 --- a/app/service/Interaction/ScriptService.ts +++ b/app/service/Interaction/ScriptService.ts @@ -41,8 +41,6 @@ export interface UseScriptService { characterArc: string; /** 项目ID */ projectId: string; - /** 计划ID */ - planId: string; /** AI优化要求 */ aiOptimizing: string; /** 渲染数据 */ @@ -58,18 +56,6 @@ export interface UseScriptService { applyScript: () => Promise; /** 中断视频任务 */ abortVideoTask: () => Promise; - /** 聚焦处理函数 */ - focusHandler: ( - field: - | "synopsis" - | "categories" - | "protagonist" - | "incitingIncident" - | "problem" - | "conflict" - | "stakes" - | "characterArc" - ) => Promise; /** 增强剧本 */ enhanceScript: () => Promise; /** 设置AI优化要求 */ @@ -95,8 +81,6 @@ export interface UseScriptService { /** 设置项目ID */ setProjectId: Dispatch>; - /** 设置计划ID */ - setPlanId: Dispatch>; /** 创建项目 */ createMovieProjectV1: ( idea: string, @@ -127,9 +111,8 @@ export const useScriptService = (): UseScriptService => { const [stakes, setStakes] = useState(""); const [characterArc, setCharacterArc] = useState(""); const [projectId, setProjectId] = useState(""); - const [planId, setPlanId] = useState(""); const [aiOptimizing, setAiOptimizing] = useState(""); - const [focusedField, setFocusedField] = useState(""); + const [fieldOld, setFieldOld] = useState("");//旧的剧本内容 // UseCase实例 const [scriptEditUseCase, setScriptEditUseCase] = useState( @@ -193,7 +176,6 @@ export const useScriptService = (): UseScriptService => { ); setProjectId(projectData.project_id); - setPlanId(projectData.plan_id); } catch (error) { console.error("创建项目失败:", error); throw error; @@ -278,32 +260,31 @@ export const useScriptService = (): UseScriptService => { throw new Error("剧本编辑用例未初始化"); } - if (!projectId || !planId) { + if (!projectId) { throw new Error("项目ID或计划ID未设置"); } - await scriptEditUseCase.applyScript(projectId, planId); + await scriptEditUseCase.applyScript(projectId); } catch (error) { console.error("应用剧本失败:", error); throw error; } finally { setLoading(false); } - }, [scriptEditUseCase, projectId, planId]); + }, [scriptEditUseCase, projectId]); /** * 中断视频任务 */ const abortVideoTaskHandler = useCallback(async (): Promise => { try { - if (!projectId || !planId) { + if (!projectId) { throw new Error("项目ID或计划ID未设置"); } // 调用中断视频任务API const response = await abortVideoTask({ project_id: projectId, - plan_id: planId, }); if (!response.successful) { @@ -315,7 +296,7 @@ export const useScriptService = (): UseScriptService => { console.error("中断视频任务失败:", error); throw error; } - }, [projectId, planId]); + }, [projectId]); // 封装的setter函数,同时更新hook状态和scriptEditUseCase中的值对象 const setSynopsisWrapper = useCallback( @@ -413,44 +394,42 @@ export const useScriptService = (): UseScriptService => { (type: string, value: SetStateAction, tags?: string[]) => { console.log('setAnyAttributeWrapper', type); if (type === 'synopsis') { + setFieldOld(synopsis) + scriptEditUseCase.replaceScript(fieldOld,synopsis) setSynopsisWrapper(value); } else if (type === 'categories') { + setFieldOld(categories.join(',')) + scriptEditUseCase.replaceScript(fieldOld,categories.join(',')) setCategoriesWrapper(tags || []); } else if (type === 'protagonist') { + setFieldOld(protagonist) + scriptEditUseCase.replaceScript(fieldOld,protagonist) setProtagonistWrapper(value); } else if (type === 'incitingIncident') { + setFieldOld(incitingIncident) + scriptEditUseCase.replaceScript(fieldOld,incitingIncident) setIncitingIncidentWrapper(value); } else if (type === 'problem') { + setFieldOld(problem) + scriptEditUseCase.replaceScript(fieldOld,problem) setProblemWrapper(value); } else if (type === 'conflict') { + setFieldOld(conflict) + scriptEditUseCase.replaceScript(fieldOld,conflict) setConflictWrapper(value); } else if (type === 'stakes') { + setFieldOld(stakes) + scriptEditUseCase.replaceScript(fieldOld,stakes) setStakesWrapper(value); } else if (type === 'characterArc') { + setFieldOld(characterArc) + scriptEditUseCase.replaceScript(fieldOld,characterArc) setCharacterArcWrapper(value); } + }, - [scriptEditUseCase] + [categories, characterArc, conflict, fieldOld, incitingIncident, problem, protagonist, scriptEditUseCase, setCategoriesWrapper, setCharacterArcWrapper, setConflictWrapper, setIncitingIncidentWrapper, setProblemWrapper, setProtagonistWrapper, setStakesWrapper, setSynopsisWrapper, stakes, synopsis] ); - - /** - * 聚焦处理函数 - */ - const focusHandler = useCallback( - async (field: ScriptEditKey): Promise => { - try { - // 如果当前已经有聚焦的字段,先处理暂停/继续逻辑 - - // 设置新的聚焦字段 - setFocusedField(field); - } catch (error) { - console.error("聚焦处理失败:", error); - throw error; - } - }, - [] - ); - /** * 增强剧本 */ @@ -540,7 +519,6 @@ export const useScriptService = (): UseScriptService => { stakes, characterArc, projectId, - planId, aiOptimizing, scriptBlocksMemo, // 操作方法 @@ -549,11 +527,9 @@ export const useScriptService = (): UseScriptService => { updateScript, applyScript, abortVideoTask: abortVideoTaskHandler, - focusHandler, enhanceScript, setAiOptimizing, setProjectId, - setPlanId, createMovieProjectV1, // 封装的set函数 setSynopsis: setSynopsisWrapper, diff --git a/app/service/domain/valueObject.ts b/app/service/domain/valueObject.ts index 20e7eae..4452a10 100644 --- a/app/service/domain/valueObject.ts +++ b/app/service/domain/valueObject.ts @@ -243,44 +243,47 @@ export class StoryDetails { let content = ""; - // 定义多种匹配模式,按优先级排序 - const patterns = [ - // 1. 匹配带编号的主标题:数字. **标题:** 格式 - new RegExp( - `\\d+\\.\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\*\\*[A-Z]+:\\*\\*|---|\$)`, - "i" - ), + // 定义多种匹配模式,按优先级排序 +const patterns = [ + // 1. 匹配带编号的主标题:数字. **标题:** 格式 + new RegExp( + `\\d+\\.\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\*\\*[A-Z]+:\\*\\*|---|\$)`, + "i" + ), - // 2. 匹配子标题:*标题:* 格式(在主标题下的子项) - new RegExp( - `\\*\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\*\\s*\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\*\\*[A-Z]+:\\*\\*|---|\$)`, - "i" - ), + // 2. 匹配子标题:*标题:* 格式(在主标题下的子项) + new RegExp( + `\\*\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\*\\s*\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\*\\*[A-Z]+:\\*\\*|---|\$)`, + "i" + ), - // 3. 匹配独立的粗体标题:**标题:** 格式 - new RegExp( - `^\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=^\\s*\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|^\\s*\\*\\*[A-Z]+:\\*\\*|^---|\$)`, - "im" - ), + // 3. 匹配独立的粗体标题:**标题:** 格式 + new RegExp( + `^\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=^\\s*\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|^\\s*\\*\\*[A-Z]+:\\*\\*|^---|\$)`, + "im" + ), - // 4. 匹配简单的**标题:**格式(可能在段落中) - new RegExp( - `\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\n\\n|---|\$)`, - "i" - ), + // 4. 匹配简单的粗体键值对格式:**标题:** 值(适用于简单的键值对,如 **GENRE:** Drama) + new RegExp(`\\*\\*${escapedHeaderName}:?\\*\\*\\s*([^\\n]+)`, "i"), - // 5. 匹配markdown标题:## 标题 格式 - new RegExp( - `^#{1,6}\\s*${escapedHeaderName}:?\\s*\\n([\\s\\S]*?)(?=^#{1,6}\\s|^\\*\\*[^*]+:?\\*\\*|^---|\$)`, - "im" - ), + // 5. 匹配简单的**标题:**格式(可能在段落中) + new RegExp( + `\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\n\\n|---|\$)`, + "i" + ), - // 6. 匹配冒号后的内容:标题: 内容(适用于简单的键值对格式) - new RegExp( - `^\\s*${escapedHeaderName}:?\\s*([\\s\\S]*?)(?=^\\s*[A-Za-z][^:]*:|^\\*\\*[^*]+:?\\*\\*|^---|\$)`, - "im" - ), - ]; + // 6. 匹配markdown标题:## 标题 格式 + new RegExp( + `^#{1,6}\\s*${escapedHeaderName}:?\\s*\\n([\\s\\S]*?)(?=^#{1,6}\\s|^\\*\\*[^*]+:?\\*\\*|^---|\$)`, + "im" + ), + + // 7. 匹配冒号后的内容:标题: 内容(适用于简单的键值对格式) + new RegExp( + `^\\s*${escapedHeaderName}:?\\s*([\\s\\S]*?)(?=^\\s*[A-Za-z][^:]*:|^\\*\\*[^*]+:?\\*\\*|^---|\$)`, + "im" + ), +]; // 尝试每种模式 for (let i = 0; i < patterns.length; i++) { @@ -317,7 +320,7 @@ export class StoryDetails { content = cleanMarkdownContent(content); // 如果内容太短,可能是匹配错误,返回空 - if (content.length < 10) { + if (content.length < 20&&headerName!=='GENRE') { debug && console.log("匹配到的内容太短,可能匹配错误"); return ""; } diff --git a/app/service/test/Script.test.ts b/app/service/test/Script.test.ts index b848401..2bea7a0 100644 --- a/app/service/test/Script.test.ts +++ b/app/service/test/Script.test.ts @@ -28,7 +28,6 @@ describe("ScriptService 业务逻辑测试", () => { "在阳光明媚的码头上,两只柴犬展开了一场薯条吃比赛。一只优雅的母猫担任裁判,端坐高处,威严地监督比赛。两只鸽子站在一旁,歪着头有趣地看着,偶尔咕咕低鸣。柴犬们瞪大眼睛,尾巴摇得飞快,争抢盘子里的金黄薯条。从日出到天黑,它们吃个不停,薯条堆成了小山,母猫无奈摇头,鸽子仍兴致勃勃,场面热闹非凡。"; let projectId: string; - let planId: string; // let name: string; it("想法生成剧本", async () => { @@ -48,14 +47,13 @@ describe("ScriptService 业务逻辑测试", () => { ); expect(createRes.project_id).toBeDefined(); projectId = createRes.project_id; - planId = createRes.plan_id; }); it("保存剧本", async () => { const res = await newScriptEditUseCase.saveScript(projectId); console.log(res); }); it("应用剧本", async () => { - await newScriptEditUseCase.applyScript(projectId, planId); + await newScriptEditUseCase.applyScript(projectId); console.log(projectId); }); }); @@ -101,8 +99,14 @@ describe("解析测试", () => { describe("剧本功能对接测试",()=>{ it("初始化 解析剧本" , async()=>{ const response = await getProjectScript({ project_id: "21f194df-cb4b-4e3a-8d44-ca14f23fd1c2" }); - console.log(response.data.generated_script); const newScriptEditUseCase = new ScriptEditUseCase(response.data.generated_script); - console.log(newScriptEditUseCase.getStoryDetails()); + }) + it("分类解析",()=>{ + // 测试代码 +const testText = `**GENRE:** Drama\n\n---\n\n**SCENE 1**`; +const s = new StoryDetails(""); +const result = s.extractContentByHeader(testText, "GENRE", true); +console.log("测试结果:", result); // 应该输出 "Drama" + }) } ) diff --git a/app/service/usecase/ScriptEditUseCase.ts b/app/service/usecase/ScriptEditUseCase.ts index 0856324..88cff54 100644 --- a/app/service/usecase/ScriptEditUseCase.ts +++ b/app/service/usecase/ScriptEditUseCase.ts @@ -183,14 +183,13 @@ export class ScriptEditUseCase { * @description: 应用剧本方法 * @returns Promise */ - async applyScript(projectId: string, planId: string): Promise { + async applyScript(projectId: string): Promise { try { this.loading = true; // 调用应用剧本接口 const response = await applyScriptToShot({ - project_id: projectId, - plan_id: planId, + project_id: projectId }); if (!response.successful) { @@ -250,4 +249,17 @@ export class ScriptEditUseCase { toString(): string { return this.scriptValueObject.toString(); } + + /** + * @description: 当前剧本进行字符串替换 + * @returns string + */ + replaceScript(old: string, newScript: string): void { + // 获取当前剧本文本 + const scriptText = this.scriptValueObject.toString(); + // 替换剧本文本 + const newScriptText = scriptText.replace(old, newScript); + // 更新剧本 + this.scriptValueObject = new ScriptValueObject(newScriptText); + } }