import { ImageStoryEntity } from "../domain/Entities"; import { AIGenerateImageStory, getMovieStoryTask } from "@/api/movie_start"; import { MovieStartDTO, CharacterAnalysis } from "@/api/DTO/movie_start_dto"; import { generateCharacterBrief } from "@/api/video_flow"; /** * 图片故事用例 * 负责管理图片故事模式的业务逻辑,包括图片上传、AI分析和故事生成 */ export class ImageStoryUseCase { /** 当前图片故事数据 */ imageStory: ImageStoryEntity = { id: "", imageAnalysis: "", roleImage: [], imageUrl: "", imageStory: "", storyType: "", }; /** 故事梗概 */ storyLogline: string = ""; /** 角色头像及名称数据 */ charactersAnalysis: CharacterAnalysis[] = []; /** 分类数据 */ potentialGenres: string[] = []; /** 是否正在分析图片 */ isAnalyzing: boolean = false; /** 是否正在上传 */ isUploading: boolean = false; constructor() {} /** * 设置图片故事数据 * @param {Partial} data - 要设置的图片故事数据 */ setImageStory(data: Partial): void { this.imageStory = { ...this.imageStory, ...data }; } /** * 重置图片故事数据 */ resetImageStory(): void { this.imageStory = { id: "", imageAnalysis: "", roleImage: [], imageUrl: "", imageStory: "", storyType: "", }; this.storyLogline = ""; this.charactersAnalysis = []; this.potentialGenres = []; this.isAnalyzing = false; this.isUploading = false; } /** * 处理图片上传 * @param {string} imageUrl - 已上传的图片URL * @returns {Promise} */ async handleImageUpload(imageUrl: string) { try { this.isUploading = false; // 图片已上传,设置上传状态为false this.isAnalyzing = true; console.log("imageUrl", imageUrl); // 设置上传后的图片URL this.setImageStory({ imageUrl }); // 调用AI分析接口 return await this.analyzeImageWithAI(); } catch (error) { console.error("图片分析失败:", error); // 分析失败时清空图片URL this.setImageStory({ imageUrl: "" }); throw error; } finally { this.isAnalyzing = false; } } /** * 使用AI分析图片 * @returns {Promise} */ async analyzeImageWithAI() { try { // // 调用AI分析接口 const response = await AIGenerateImageStory({ image_url: this.imageStory.imageUrl || "", user_text: this.imageStory.imageStory || "", }); return response.data.task_id; } catch (error) { console.error("AI分析失败:", error); throw error; } } /** * 轮询查状态 * @param taskId - 任务ID * @param interval - 轮询间隔时间 */ async pollTaskStatus(taskId: string, interval: number = 1000) { // 好老套方案,但是有效 let self = this; return { async *[Symbol.asyncIterator]() { while (true) { const response = await getMovieStoryTask(taskId); console.log("taskId", taskId,response); if (response.successful && response.data) { if (response.data.result) { response.data.result.characters_analysis?.forEach((character) => { character.whisk_caption = JSON.stringify( character.whisk_caption ); }); // 解析并存储新的数据结构 self.parseAndStoreAnalysisData(response.data.result); // 组合成ImageStoryEntity self.composeImageStoryEntity(response.data.result); } yield { status: response.data.status, imageStory: self.imageStory, progress: response.data.progress, }; } else { throw new Error("AI分析失败"); } await new Promise((resolve) => setTimeout(resolve, interval)); } }, }; } /** * 解析并存储分析数据到类属性中 * @param {MovieStartDTO} data - AI分析返回的数据 */ parseAndStoreAnalysisData(data: MovieStartDTO): void { // 存储故事梗概 this.storyLogline = data.story_logline || ""; // 存储角色头像及名称数据 this.charactersAnalysis = data.characters_analysis || []; // 存储分类数据 this.potentialGenres = data.potential_genres || []; } /** * 组合成ImageStoryEntity * @param {MovieStartDTO} data - AI分析返回的数据 */ composeImageStoryEntity(data: MovieStartDTO): void { // 将角色数据转换为ImageStoryEntity需要的格式 const roleImage = data.characters_analysis?.map((character) => ({ name: character.role_name, avatar_url: "", // 这里需要根据实际情况设置头像URL region: { x: character.region?.x || 0, y: character.region?.y || 0, width: character.region?.width || 0, height: character.region?.height || 0, }, })) || []; // 更新ImageStoryEntity this.setImageStory({ ...this.imageStory, imageAnalysis: data.story_logline || "", storyType: data.potential_genres?.[0] || "", // 使用第一个分类作为故事类型 roleImage, }); } /** * 更新故事类型 * @param {string} storyType - 新的故事类型 */ updateStoryType(storyType: string): void { this.setImageStory({ storyType }); } /** * 更新故事内容 * @param {string} storyContent - 新的故事内容 */ updateStoryContent(storyContent: string): void { this.setImageStory({ imageStory: storyContent }); } /** * 处理角色数据,解析并存储到类属性中 * @param {CharacterAnalysis[]} characters - 角色分析数据 */ processCharacterData(characters: CharacterAnalysis[]): void { this.charactersAnalysis = characters.map((character) => ({ ...character, region: { x: character.region?.x || 0, y: character.region?.y || 0, width: character.region?.width || 0, height: character.region?.height || 0, }, })); } /** * 获取指定角色的区域坐标 * @param {string} characterName - 角色名称 * @returns {CharacterAnalysis['region'] | null} 角色区域坐标,如果未找到则返回null */ getCharacterRegion( characterName: string ): CharacterAnalysis["region"] | null { const character = this.charactersAnalysis.find( (char) => char.role_name === characterName ); return character ? character.region : null; } /** * 更新角色头像URL * @param {string} characterName - 角色名称 * @param {string} avatarUrl - 头像URL */ updateCharacterAvatar(characterName: string, avatarUrl: string): void { const character = this.charactersAnalysis.find( (char) => char.role_name === characterName ); if (character) { // 更新角色头像URL(这里需要根据实际的数据结构来调整) // 由于CharacterAnalysis接口中没有avatar_url字段,这里只是示例 console.log(`更新角色 ${characterName} 的头像URL: ${avatarUrl}`); } } /** * 获取所有角色名称 * @returns {string[]} 角色名称数组 */ getAllCharacterNames(): string[] { return this.charactersAnalysis.map((char) => char.role_name); } /** * 上传人物头像并分析特征,替换旧的角色数据 * @param {Function} uploadFile - 文件上传函数 * @returns {Promise<{crop_url: string, whisk_caption: string}>} 返回新的头像URL和特征描述 */ async uploadCharacterAvatarAndAnalyzeFeatures( uploadFile: (file: File) => Promise ): Promise<{ crop_url: string; whisk_caption: string }> { return new Promise((resolve, reject) => { // 创建文件输入元素 const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.accept = "image/*"; fileInput.style.display = "none"; fileInput.onchange = async (e) => { try { const target = e.target as HTMLInputElement; if (target.files && target.files[0]) { const file = target.files[0]; // 直接在这里处理上传和分析逻辑 try { // 1. 上传人物头像图片 const imageUrl = await uploadFile(file); // 2. 调用AI分析接口获取人物特征描述 const analysisResult = await generateCharacterBrief({ image_url: imageUrl, }); if (!analysisResult.successful || !analysisResult.data) { throw new Error("人物特征分析失败"); } // 3. 返回新的头像URL和特征描述,用于替换旧数据 const result = { crop_url: imageUrl, whisk_caption: JSON.stringify( analysisResult.data.character_brief ), }; // 清理临时元素 document.body.removeChild(fileInput); resolve(result); } catch (error) { // 清理临时元素 if (document.body.contains(fileInput)) { document.body.removeChild(fileInput); } reject(error); } } else { reject(new Error("未选择文件")); } } catch (error) { // 清理临时元素 if (document.body.contains(fileInput)) { document.body.removeChild(fileInput); } reject(error); } }; fileInput.oncancel = () => { document.body.removeChild(fileInput); reject(new Error("用户取消选择")); }; // 添加到DOM并触发点击 document.body.appendChild(fileInput); fileInput.click(); }); } }