forked from 77media/video-flow
291 lines
7.9 KiB
TypeScript
291 lines
7.9 KiB
TypeScript
import { useState, useCallback, useMemo } from "react";
|
||
import { ScriptSlice } from "../domain/valueObject";
|
||
import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase";
|
||
|
||
/**
|
||
* 剧本服务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>;
|
||
/** 设置当前聚焦的剧本片段 */
|
||
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]);
|
||
|
||
|
||
|
||
/**
|
||
* 设置当前聚焦的剧本片段
|
||
* @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,
|
||
setFocusedSlice,
|
||
clearFocusedSlice,
|
||
updateScriptSliceText,
|
||
updateUserPrompt,
|
||
resetScript,
|
||
generateScript,
|
||
applyScript,
|
||
UpdateFocusedSlice
|
||
};
|
||
};
|