video-flow-b/app/service/usecase/imageStoryUseCase.ts

304 lines
8.8 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 { ImageStoryEntity } from "../domain/Entities";
import { AIGenerateImageStory } from "@/api/movie_start";
import { MovieStartDTO, CharacterAnalysis } from "@/api/DTO/movie_start_dto";
import { analyzeImageDescription } 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<ImageStoryEntity>} data - 要设置的图片故事数据
*/
setImageStory(data: Partial<ImageStoryEntity>): 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<void>}
*/
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<void>}
*/
async analyzeImageWithAI() {
console.log("this.imageStory.imageUrl", this.imageStory.imageUrl);
try {
//
// 调用AI分析接口
const response = await AIGenerateImageStory({
image_url: this.imageStory.imageUrl || "",
user_text: this.imageStory.imageStory || "",
});
if (response.successful && response.data) {
// 解析并存储新的数据结构
this.parseAndStoreAnalysisData(response.data);
// 组合成ImageStoryEntity
this.composeImageStoryEntity(response.data);
return this.imageStory;
} else {
throw new Error("AI分析失败");
}
} catch (error) {
console.error("AI分析失败:", error);
throw error;
}
}
/**
* 解析并存储分析数据到类属性中
* @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<string>
): 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 analyzeImageDescription({
image_url: imageUrl,
});
if (!analysisResult.successful || !analysisResult.data) {
throw new Error("人物特征分析失败");
}
// 3. 返回新的头像URL和特征描述用于替换旧数据
const result = {
crop_url: imageUrl,
whisk_caption: analysisResult.data.description,
};
// 清理临时元素
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();
});
}
}