forked from 77media/video-flow
346 lines
10 KiB
TypeScript
346 lines
10 KiB
TypeScript
import { message } from "antd";
|
|
import { StoryTemplateEntity } from "../domain/Entities";
|
|
import { useUploadFile } from "../domain/service";
|
|
import { debounce } from "lodash";
|
|
import { TemplateStoryUseCase } from "../usecase/templateStoryUseCase";
|
|
import { useState, useCallback, useMemo } from "react";
|
|
import { createMovieProjectV3, generateTextToImage } from "@/api/movie_start";
|
|
import { CreateMovieProjectV3Request } from "@/api/DTO/movie_start_dto";
|
|
|
|
/** 模板角色接口 */
|
|
interface TemplateRole {
|
|
/** 角色名 */
|
|
role_name: string;
|
|
/** 照片URL */
|
|
photo_url: string;
|
|
/** 声音URL */
|
|
voice_url: string;
|
|
}
|
|
|
|
interface UseTemplateStoryService {
|
|
/** 模板列表 */
|
|
templateStoryList: StoryTemplateEntity[];
|
|
/** 当前选中要使用的模板 */
|
|
selectedTemplate: StoryTemplateEntity | null;
|
|
/** 当前选中的活跃角色索引 */
|
|
activeRoleIndex: number;
|
|
/** 计算属性:当前活跃角色信息 */
|
|
activeRole: TemplateRole | null;
|
|
/** 加载状态 */
|
|
isLoading: boolean;
|
|
/** 获取模板列表函数 */
|
|
/** 获取模板列表函数 */
|
|
getTemplateStoryList: () => Promise<void>;
|
|
/**
|
|
* action 生成电影函数
|
|
* @param {string} user_id - 用户ID
|
|
* @param {"auto" | "manual"} mode - 生成模式
|
|
* @param {"720p" | "1080p" | "4k"} resolution - 分辨率
|
|
* @param {string} language - 语言
|
|
* @returns {Promise<string>} - 生成的电影ID
|
|
*/
|
|
actionStory: (
|
|
user_id: string,
|
|
mode: "auto" | "manual",
|
|
resolution: "720p" | "1080p" | "4k",
|
|
language: string
|
|
) => Promise<string | undefined>;
|
|
/** 设置选中的模板 */
|
|
setSelectedTemplate: (template: StoryTemplateEntity | null) => void;
|
|
/** 设置活跃角色索引 */
|
|
setActiveRoleIndex: (index: number) => void;
|
|
|
|
/** 设置当前活跃角色的音频URL */
|
|
setActiveRoleAudio: (audioUrl: string) => void;
|
|
/**清空数据 */
|
|
clearData: () => void;
|
|
/** 上传人物头像并分析 */
|
|
AvatarAndAnalyzeFeatures: (imageUrl: string, roleName?: string) => Promise<void>;
|
|
/** 更新指定角色的图片 */
|
|
updateRoleImage: (roleName: string, imageUrl: string) => void;
|
|
/** 更新变量字段值 */
|
|
updateFillableContentField: (fieldName: string, fieldValue: string) => void;
|
|
/** 带防抖的失焦处理函数 */
|
|
handleFieldBlur: (fieldName: string, fieldValue: string) => void;
|
|
}
|
|
|
|
export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
|
const [templateStoryList, setTemplateStoryList] = useState<
|
|
StoryTemplateEntity[]
|
|
>([]);
|
|
const [selectedTemplate, setSelectedTemplate] =
|
|
useState<StoryTemplateEntity | null>(null);
|
|
const [activeRoleIndex, setActiveRoleIndex] = useState<number>(0);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
// 使用上传文件Hook
|
|
const { uploadFile } = useUploadFile();
|
|
|
|
/** 模板故事用例实例 */
|
|
const templateStoryUseCase = useMemo(() => new TemplateStoryUseCase(), []);
|
|
|
|
/** 计算属性:当前活跃角色信息 */
|
|
const activeRole = useMemo(() => {
|
|
if (
|
|
!selectedTemplate ||
|
|
activeRoleIndex < 0 ||
|
|
activeRoleIndex >= selectedTemplate.storyRole.length
|
|
) {
|
|
return null;
|
|
}
|
|
return selectedTemplate.storyRole[activeRoleIndex];
|
|
}, [selectedTemplate, activeRoleIndex]);
|
|
|
|
/**
|
|
* 获取模板列表函数
|
|
*/
|
|
const getTemplateStoryList = useCallback(async (): Promise<void> => {
|
|
try {
|
|
setIsLoading(true);
|
|
|
|
const templates = await templateStoryUseCase.getTemplateStoryList();
|
|
|
|
setTemplateStoryList(templates);
|
|
setSelectedTemplate(templates[0]);
|
|
setActiveRoleIndex(0);
|
|
console.log(selectedTemplate, activeRoleIndex);
|
|
} catch (err) {
|
|
console.error("获取模板列表失败:", err);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [templateStoryUseCase]);
|
|
|
|
/**
|
|
* 设置活跃角色索引
|
|
*/
|
|
const handleSetActiveRoleIndex = useCallback((index: number): void => {
|
|
setActiveRoleIndex(index);
|
|
}, []);
|
|
|
|
/**
|
|
* 设置当前活跃角色的图片URL
|
|
*/
|
|
const setActiveRoleData = useCallback(
|
|
(imageUrl: string): void => {
|
|
if (
|
|
!selectedTemplate ||
|
|
activeRoleIndex < 0 ||
|
|
activeRoleIndex >= selectedTemplate.storyRole.length
|
|
) {
|
|
console.log(selectedTemplate, activeRoleIndex);
|
|
return;
|
|
}
|
|
try {
|
|
// const character_brief = {
|
|
// name: selectedTemplate.storyRole[activeRoleIndex].role_name,
|
|
// image_url: imageUrl,
|
|
// character_analysis: JSON.parse(desc).character_analysis,
|
|
// };
|
|
const updatedTemplate = {
|
|
...selectedTemplate,
|
|
storyRole: selectedTemplate.storyRole.map((role, index) =>
|
|
index === activeRoleIndex
|
|
? {
|
|
...role,
|
|
photo_url: imageUrl,
|
|
}
|
|
: role
|
|
),
|
|
};
|
|
|
|
setSelectedTemplate(updatedTemplate);
|
|
} catch (error) {
|
|
message.error("Image analysis failed");
|
|
console.log("error", error);
|
|
}
|
|
},
|
|
[selectedTemplate, activeRoleIndex]
|
|
);
|
|
|
|
/**
|
|
* 设置当前活跃角色的音频URL
|
|
*/
|
|
const setActiveRoleAudio = useCallback(
|
|
(audioUrl: string): void => {
|
|
if (
|
|
!selectedTemplate ||
|
|
activeRoleIndex < 0 ||
|
|
activeRoleIndex >= selectedTemplate.storyRole.length
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const updatedTemplate = {
|
|
...selectedTemplate,
|
|
storyRole: selectedTemplate.storyRole.map((role, index) =>
|
|
index === activeRoleIndex ? { ...role, voice_url: audioUrl } : role
|
|
),
|
|
};
|
|
setSelectedTemplate(updatedTemplate);
|
|
},
|
|
[selectedTemplate, activeRoleIndex]
|
|
);
|
|
|
|
/**
|
|
* 更新变量字段值
|
|
*/
|
|
const updateFillableContentField = useCallback(
|
|
(fieldName: string, fieldValue: string): void => {
|
|
if (!selectedTemplate) return;
|
|
|
|
const updatedTemplate = {
|
|
...selectedTemplate,
|
|
fillable_content: selectedTemplate.fillable_content.map((field) =>
|
|
field.field_name === fieldName
|
|
? { ...field, field_value: fieldValue }
|
|
: field
|
|
),
|
|
};
|
|
setSelectedTemplate(updatedTemplate);
|
|
},
|
|
[selectedTemplate]
|
|
);
|
|
|
|
|
|
/**
|
|
* 上传人物头像并分析特征,替换旧的角色数据
|
|
* @param {string} imageUrl - 图片URL
|
|
* @param {string} roleName - 角色名称(可选,如果不提供则使用当前活跃角色)
|
|
*/
|
|
const AvatarAndAnalyzeFeatures = useCallback(
|
|
async (imageUrl: string, roleName?: string): Promise<void> => {
|
|
try {
|
|
setIsLoading(true);
|
|
|
|
// 如果提供了角色名称,更新指定角色;否则更新当前活跃角色
|
|
if (roleName) {
|
|
updateRoleImage(roleName, imageUrl);
|
|
} else {
|
|
setActiveRoleData(imageUrl);
|
|
}
|
|
|
|
// 调用用例处理人物头像上传和特征分析
|
|
// const result = await templateStoryUseCase.AvatarAndAnalyzeFeatures(
|
|
// imageUrl
|
|
// );
|
|
} catch (error) {
|
|
console.error("人物头像上传和特征分析失败:", error);
|
|
throw error;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
},
|
|
[setActiveRoleData]
|
|
);
|
|
|
|
/**
|
|
* 更新指定角色的图片
|
|
* @param {string} roleName - 角色名称
|
|
* @param {string} imageUrl - 新的图片URL
|
|
*/
|
|
const updateRoleImage = useCallback(
|
|
(roleName: string, imageUrl: string): void => {
|
|
if (!selectedTemplate) return;
|
|
|
|
const updatedTemplate = {
|
|
...selectedTemplate,
|
|
storyRole: selectedTemplate.storyRole.map((role) =>
|
|
role.role_name === roleName
|
|
? { ...role, photo_url: imageUrl }
|
|
: role
|
|
),
|
|
};
|
|
|
|
setSelectedTemplate(updatedTemplate);
|
|
},
|
|
[selectedTemplate]
|
|
);
|
|
/**
|
|
* 带防抖的失焦处理函数
|
|
* @param {string} fieldName - 字段名称
|
|
* @param {string} fieldValue - 字段值
|
|
*/
|
|
const handleFieldBlur = useCallback(
|
|
debounce(async (fieldName: string, fieldValue: string): Promise<void> => {
|
|
try {
|
|
// 设置 loading 状态
|
|
setIsLoading(true);
|
|
|
|
// 调用图片生成接口
|
|
const result = await generateTextToImage({
|
|
description: fieldValue
|
|
});
|
|
|
|
if (result.successful && result.data?.image_url) {
|
|
// 更新对应角色的图片
|
|
updateRoleImage(fieldName, result.data.image_url);
|
|
console.log(`字段 ${fieldName} 图片生成成功:`, result.data.image_url);
|
|
} else {
|
|
console.error(`字段 ${fieldName} 图片生成失败:`, result.message);
|
|
}
|
|
} catch (error) {
|
|
console.error(`字段 ${fieldName} 处理失败:`, error);
|
|
} finally {
|
|
// 清除 loading 状态
|
|
setIsLoading(false);
|
|
}
|
|
}, 500),
|
|
[updateRoleImage, setIsLoading]
|
|
);
|
|
|
|
const actionStory = useCallback(
|
|
async (
|
|
user_id: string,
|
|
mode: "auto" | "manual" = "auto",
|
|
resolution: "720p" | "1080p" | "4k" = "720p",
|
|
language: string = "English"
|
|
) => {
|
|
console.log('selectedTemplate', selectedTemplate)
|
|
try {
|
|
// 设置 loading 状态
|
|
setIsLoading(true);
|
|
|
|
const params: CreateMovieProjectV3Request = {
|
|
user_id,
|
|
mode,
|
|
resolution,
|
|
storyRole: selectedTemplate?.storyRole || [],
|
|
language,
|
|
template_id: selectedTemplate?.template_id || "",
|
|
fillable_content: selectedTemplate?.fillable_content || [],
|
|
};
|
|
console.log("params", params);
|
|
const result = await createMovieProjectV3(params);
|
|
return result.data.project_id as string;
|
|
} catch (error) {
|
|
console.error("创建电影项目失败:", error);
|
|
} finally {
|
|
// 清除 loading 状态
|
|
setIsLoading(false);
|
|
}
|
|
},
|
|
[selectedTemplate]
|
|
);
|
|
return {
|
|
templateStoryList,
|
|
selectedTemplate,
|
|
activeRoleIndex,
|
|
activeRole,
|
|
isLoading,
|
|
getTemplateStoryList,
|
|
actionStory,
|
|
setSelectedTemplate,
|
|
setActiveRoleIndex: handleSetActiveRoleIndex,
|
|
setActiveRoleAudio,
|
|
AvatarAndAnalyzeFeatures,
|
|
updateRoleImage,
|
|
updateFillableContentField,
|
|
handleFieldBlur,
|
|
clearData: () => {
|
|
setTemplateStoryList([]);
|
|
setSelectedTemplate(null);
|
|
setActiveRoleIndex(0);
|
|
},
|
|
};
|
|
};
|