video-flow-b/app/service/Interaction/ScriptService.ts

347 lines
9.6 KiB
TypeScript
Raw 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, useMemo } from "react";
import { ScriptSlice } from "../domain/valueObject";
import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase";
import { getProjectScript } from "../../../api/video_flow";
/**
* 剧本服务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>;
/** 根据项目ID获取已存在的剧本数据 */
fetchProjectScript: (projectId: 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]);
/**
* 根据项目ID获取已存在的剧本数据
* @param projectId 项目ID
*/
const fetchProjectScript = useCallback(async (projectId: string): Promise<void> => {
try {
setLoading(true);
setError(null);
// 清空当前状态
setScriptText("");
setScriptSlices([]);
setFocusedSliceId("");
setScriptSliceText("");
// 调用API获取项目剧本数据
const response = await getProjectScript({ projectId });
if (!response.successful) {
throw new Error(response.message || '获取项目剧本失败');
}
const { prompt, scriptText } = response.data;
// 更新用户提示词状态
setUserPrompt(prompt);
// 保存初始提示词(只在第一次获取时保存)
if (!initialScriptText) {
setInitialScriptText(prompt);
}
// 创建新的剧本编辑用例并初始化数据
const newScriptEditUseCase = new ScriptEditUseCase(scriptText);
setScriptEditUseCase(newScriptEditUseCase);
// 设置剧本文本
setScriptText(scriptText);
// 从UseCase获取解析后的剧本片段
const scriptSlices = newScriptEditUseCase.getScriptSlices();
setScriptSlices(scriptSlices);
} 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,
fetchProjectScript,
setFocusedSlice,
clearFocusedSlice,
updateScriptSliceText,
updateUserPrompt,
resetScript,
generateScript,
applyScript,
UpdateFocusedSlice
};
};