forked from 77media/video-flow
437 lines
14 KiB
TypeScript
437 lines
14 KiB
TypeScript
import { NewCharacterItem, CharacterListByProjectItem, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse, RoleResponse } from "@/api/DTO/movieEdit";
|
||
import { RoleEntity } from '../domain/Entities';
|
||
import {
|
||
applyRoleToShots,
|
||
getRoleList,
|
||
getRoleData,
|
||
regenerateRole,
|
||
getRoleShots,
|
||
replaceRole,
|
||
getCharacterListByProjectWithHighlight,
|
||
updateAndRegenerateCharacter,
|
||
getUserRoleLibrary,
|
||
generateCharacterDescription,
|
||
saveRegeneratedCharacter,
|
||
getSimilarCharacters,
|
||
checkShotVideoStatus,
|
||
} from '@/api/video_flow';
|
||
|
||
/**
|
||
* 角色图编辑用例
|
||
* 负责角色图内容的初始化、修改和优化
|
||
*/
|
||
export class RoleEditUseCase {
|
||
/** 角色列表 */
|
||
roleList: RoleEntity[] = [];
|
||
/** 当前选中的角色 */
|
||
selectedRole: RoleEntity | null = null;
|
||
/** 角色库列表 */
|
||
roleLibraryList: RoleEntity[] = [];
|
||
constructor() {
|
||
}
|
||
/**
|
||
* 获取当前项目角色列表
|
||
* @param projectId 项目ID
|
||
* @returns Promise<RoleEntity[]> 角色列表
|
||
*/
|
||
async getRoleList(projectId: string): Promise<RoleEntity[]> {
|
||
try {
|
||
// 使用新的项目角色列表接口
|
||
const response = await getCharacterListByProjectWithHighlight({
|
||
project_id: projectId,
|
||
max_keywords: 6 // 默认提取6个关键词
|
||
});
|
||
|
||
if (response.successful) {
|
||
const roleList = this.parseProjectRoleList(response.data);
|
||
return roleList;
|
||
} else {
|
||
throw new Error(response.message || '获取项目角色列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取项目角色列表失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析新角色列表接口返回的数据
|
||
* @param newCharacterData 新角色列表数据
|
||
* @returns RoleEntity[] 角色实体数组
|
||
*/
|
||
parseNewRoleList(newCharacterData: { data: NewCharacterItem[] }): RoleEntity[] {
|
||
const characters = newCharacterData.data || [];
|
||
|
||
return characters.map((char, index) => {
|
||
|
||
const roleEntity: RoleEntity = {
|
||
id: `role_${index + 1}`,
|
||
name: char.character_name || '',
|
||
generateText: char.character_description || '',
|
||
tags: [], // 默认为空标签数组
|
||
imageUrl: char.image_path || '', // 使用API返回的图片路径
|
||
loadingProgress: 100, // 默认加载完成
|
||
disableEdit: false, // 默认允许编辑
|
||
updatedAt: Date.now()
|
||
};
|
||
|
||
return roleEntity;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 解析项目角色列表接口返回的数据
|
||
* @description 将接口返回的项目角色列表数据转换为RoleEntity数组
|
||
* @param {RoleResponse[]} projectRoleData - 项目角色列表数据
|
||
* @returns {RoleEntity[]} 角色实体数组
|
||
* @throws {Error} 如果数据格式不正确则抛出异常
|
||
*/
|
||
parseProjectRoleList(projectRoleData: RoleResponse): RoleEntity[] {
|
||
// if (!Array.isArray(projectRoleData)) {
|
||
// throw new Error('项目角色数据格式错误');
|
||
// }
|
||
|
||
if(projectRoleData.character_draft){
|
||
const roleEntity: RoleEntity[] = JSON.parse(projectRoleData.character_draft);
|
||
return roleEntity;
|
||
}
|
||
return projectRoleData.characters.map((char, index) => {
|
||
/** 角色实体对象 */
|
||
const roleEntity: RoleEntity = {
|
||
id: `role_${index + 1}`,
|
||
name: char.character_name || '',
|
||
generateText: char.character_description || '',
|
||
tags: Array.isArray(char.highlights)
|
||
? char.highlights.map((highlight: string) => ({
|
||
id: `tag_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||
updatedAt: Date.now(),
|
||
/** 名称 */
|
||
name: highlight,
|
||
/** 内容 */
|
||
content: highlight,
|
||
loadingProgress: 100,
|
||
disableEdit: false
|
||
}))
|
||
: [],
|
||
imageUrl: char.image_path || '',
|
||
loadingProgress: 100,
|
||
disableEdit: false,
|
||
updatedAt: Date.now()
|
||
};
|
||
|
||
return roleEntity;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 解析相似角色列表接口返回的数据
|
||
* @description 将接口返回的相似角色列表数据转换为RoleEntity数组
|
||
* @param {any} similarCharacterData - 相似角色列表数据
|
||
* @returns {RoleEntity[]} 角色实体数组
|
||
* @throws {Error} 如果数据格式不正确则抛出异常
|
||
*/
|
||
parseSimilarCharacterList(similarCharacterData: any): RoleEntity[] {
|
||
if (!similarCharacterData || !Array.isArray(similarCharacterData.characters)) {
|
||
throw new Error('相似角色数据格式错误');
|
||
}
|
||
|
||
return similarCharacterData.characters.map((char: any, index: number) => {
|
||
/** 角色实体对象 */
|
||
const roleEntity: RoleEntity = {
|
||
id: char.id || `role_${Date.now()}_${index}`,
|
||
name: char.name || '',
|
||
generateText: char.description || '',
|
||
tags: [], // 相似角色接口可能不返回标签,暂时为空
|
||
imageUrl: char.image_url || '',
|
||
loadingProgress: 100,
|
||
disableEdit: false,
|
||
updatedAt: Date.now()
|
||
};
|
||
|
||
return roleEntity;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取角色库的角色列表
|
||
* @param userId 用户ID
|
||
* @param userDescription 用户描述(角色描述)
|
||
* @returns Promise<RoleEntity[]> 角色库列表
|
||
*/
|
||
async getRoleLibraryList(userId: string, userDescription: string): Promise<RoleEntity[]> {
|
||
try {
|
||
// 使用新的相似角色接口获取角色库
|
||
const response = await getSimilarCharacters({
|
||
userId,
|
||
user_description: userDescription,
|
||
similar_limit: 15 // 固定为15
|
||
});
|
||
|
||
if (response.successful) {
|
||
const roleList = this.parseSimilarCharacterList(response.data);
|
||
return roleList;
|
||
} else {
|
||
throw new Error(response.message || '获取角色库失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取角色库失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 选中某个角色作为当前活跃角色
|
||
* @param role
|
||
*/
|
||
/**
|
||
* 选中某个角色作为当前活跃角色,并用新角色替换掉roleList中name相同的项
|
||
* @param role 选中的角色
|
||
*/
|
||
async selectRole(role: RoleEntity): Promise<void> {
|
||
this.selectedRole = role;
|
||
console.log(' this.selectedRole', this.selectedRole)
|
||
if (Array.isArray(this.roleList)) {
|
||
this.roleList = this.roleList.map(r => r.name === role.name ? role : r);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* AI生成角色
|
||
* @param projectId 项目ID
|
||
* @param characterName 角色名称
|
||
* @param characterDescription 角色描述
|
||
* @returns Promise<RoleEntity> 生成的角色实体
|
||
*/
|
||
async AIgenerateRole(
|
||
projectId: string,
|
||
characterName: string,
|
||
characterDescription: string
|
||
): Promise<RoleEntity> {
|
||
try {
|
||
// 使用新的角色更新和重新生成接口
|
||
const response = await updateAndRegenerateCharacter({
|
||
project_id: projectId,
|
||
character_name: characterName,
|
||
character_description: characterDescription,
|
||
max_keywords: 6 // 默认提取6个关键词
|
||
});
|
||
|
||
if (!response.successful) {
|
||
throw new Error(response.message || 'AI生成角色失败');
|
||
}
|
||
|
||
const characterData = response.data;
|
||
|
||
// 将API响应转换为RoleEntity
|
||
const roleEntity: RoleEntity = {
|
||
id: `role_${Date.now()}`, // 生成唯一ID
|
||
name: characterData.character_name,
|
||
generateText: characterData.character_description,
|
||
tags: (characterData.highlights || []).map(highlight => ({
|
||
id: `tag_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||
updatedAt: Date.now(),
|
||
name: highlight,
|
||
content: highlight,
|
||
loadingProgress: 100,
|
||
disableEdit: false
|
||
})), // 将高亮关键词转换为TagValueObject格式
|
||
imageUrl: characterData.image_url || '',
|
||
loadingProgress: 100,
|
||
disableEdit: false,
|
||
updatedAt: Date.now()
|
||
};
|
||
return roleEntity;
|
||
} catch (error) {
|
||
console.error('AI生成角色失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 获取角色应用到的分镜列表
|
||
* @param roleId 角色ID
|
||
* @returns 分镜列表和应用状态
|
||
*/
|
||
async getRoleShotsList(roleId: string) {
|
||
try {
|
||
const response = await getRoleShots({ roleId });
|
||
if (response.successful) {
|
||
return response.data;
|
||
} else {
|
||
throw new Error(response.message || '获取角色分镜列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取角色分镜列表失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 替换角色
|
||
* @param currentRoleId 当前角色ID
|
||
* @param replaceRoleId 替换的角色ID
|
||
* @returns 替换结果
|
||
*/
|
||
async replaceRoleById(currentRoleId: string, replaceRoleId: string) {
|
||
try {
|
||
const response = await replaceRole({
|
||
currentRoleId,
|
||
replaceRoleId,
|
||
});
|
||
|
||
if (response.successful) {
|
||
return response.data;
|
||
} else {
|
||
throw new Error(response.message || '替换角色失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('替换角色失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @description: AI优化当前角色描述
|
||
* @param selectedRole 角色描述文本
|
||
* @returns Promise<{optimizedDescription: string, keywords: string[]}> 优化后的角色描述和关键词
|
||
*/
|
||
async optimizeRoleDescription( selectedRole: RoleEntity): Promise<{optimizedDescription: string, keywords: string[]}> {
|
||
try {
|
||
// if (!this.selectedRole) {
|
||
// throw new Error('请先选择角色');
|
||
// }
|
||
|
||
// 调用新的AI优化角色描述API
|
||
const response = await generateCharacterDescription({
|
||
original_text: selectedRole.generateText,
|
||
});
|
||
|
||
if (!response.successful) {
|
||
throw new Error(response.message || 'AI优化角色描述失败');
|
||
}
|
||
|
||
const { optimized_description, keywords } = response.data;
|
||
|
||
// 更新角色列表中的对应角色描述
|
||
const roleIndex = this.roleList.findIndex(role => role.id === this.selectedRole?.id);
|
||
if (roleIndex !== -1) {
|
||
this.roleList[roleIndex].generateText = optimized_description;
|
||
}
|
||
|
||
return {
|
||
optimizedDescription: optimized_description,
|
||
keywords: keywords
|
||
};
|
||
} catch (error) {
|
||
console.error('AI优化角色描述失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @description: 从AI文本描述中解析标签信息(预留函数,未来实现)
|
||
* @param aiText AI文本描述
|
||
* @returns Promise<string[]> 解析出的标签列表
|
||
*/
|
||
async parseTagsFromAiText(aiText: string): Promise<string[]> {
|
||
// TODO: 未来实现从AI文本中解析标签的逻辑
|
||
// 例如:解析文本中的关键词、特征描述等作为标签
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* @description 根据图片地址获取角色实体数据
|
||
* @param imageUrl 图片地址
|
||
* @returns Promise<RoleEntity> 角色实体数据
|
||
*/
|
||
async getRoleByImage(imageUrl: string): Promise<RoleEntity> {
|
||
try {
|
||
// TODO: 调用后端API,根据图片地址获取角色数据
|
||
// 这里需要根据实际的后端API接口来实现
|
||
// const response = await getRoleByImage({ imageUrl });
|
||
|
||
// 临时实现:返回一个模拟的角色实体
|
||
// 实际使用时需要替换为真实的API调用
|
||
const mockRole: RoleEntity = {
|
||
id: `role_${Date.now()}`,
|
||
name: '从图片识别的角色',
|
||
generateText: '通过图片识别生成的角色描述',
|
||
tags: [], // 空标签数组
|
||
imageUrl: imageUrl, // 使用传入的图片地址
|
||
loadingProgress: 100, // 加载完成
|
||
disableEdit: false, // 允许编辑
|
||
updatedAt: Date.now()
|
||
};
|
||
|
||
return mockRole;
|
||
} catch (error) {
|
||
console.error('根据图片获取角色失败:', error);
|
||
throw new Error('根据图片获取角色失败');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @description 保存重新生成的角色到角色库
|
||
* @param roleData 角色实体数据
|
||
* @param projectId 项目ID
|
||
* @param userId 用户ID
|
||
* @param originalCharacterName 原始角色名称
|
||
* @param videoUrls 视频URL列表
|
||
* @returns Promise<{character_id: string, message: string}> 保存结果
|
||
*/
|
||
async saveRegeneratedCharacterToLibrary(
|
||
roleData: RoleEntity,
|
||
projectId: string,
|
||
userId: string,
|
||
): Promise<{character_id: string, message: string}> {
|
||
try {
|
||
|
||
// 调用保存重新生成角色的API
|
||
const response = await saveRegeneratedCharacter({
|
||
user_id: userId,
|
||
project_id: projectId,
|
||
original_character_name: roleData.name,
|
||
character_description: roleData.generateText,
|
||
character_image: roleData.imageUrl,
|
||
video_urls: []
|
||
});
|
||
|
||
if (response.successful) {
|
||
console.log('角色保存到角色库成功:', response.data);
|
||
return {
|
||
character_id: response.data.character_id,
|
||
message: response.data.message
|
||
};
|
||
} else {
|
||
throw new Error(response.message || '保存角色到角色库失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('保存角色到角色库失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @description 检查镜头视频状态
|
||
* @param projectId 项目ID
|
||
* @returns Promise<any> 镜头视频状态信息
|
||
*/
|
||
async checkShotVideoStatus(projectId: string): Promise<any> {
|
||
try {
|
||
// 这里需要调用后端API来检查镜头视频状态
|
||
// 由于这是一个新的功能,需要根据实际的后端API接口来实现
|
||
// 目前返回一个模拟的状态信息
|
||
|
||
// TODO: 替换为真实的API调用
|
||
const response = await checkShotVideoStatus({ project_id: projectId });
|
||
|
||
return response.data;
|
||
} catch (error) {
|
||
console.error('检查镜头视频状态失败:', error);
|
||
throw new Error('检查镜头视频状态失败');
|
||
}
|
||
}
|
||
|
||
}
|