forked from 77media/video-flow
重构视频流和交互服务,使用 TagValueObject 替代 TagEntity。更新相关方法和接口以保持一致性。移除未使用的 TextEditUseCase。增强角色和场景编辑功能。
This commit is contained in:
parent
067295a2bc
commit
46bda04605
@ -7,8 +7,8 @@ import {
|
|||||||
RoleEntity,
|
RoleEntity,
|
||||||
SceneEntity,
|
SceneEntity,
|
||||||
VideoSegmentEntity,
|
VideoSegmentEntity,
|
||||||
TagEntity,
|
|
||||||
} from "@/app/service/domain/Entities";
|
} from "@/app/service/domain/Entities";
|
||||||
|
import { TagValueObject } from "@/app/service/domain/valueObject";
|
||||||
import {
|
import {
|
||||||
ContentItem,
|
ContentItem,
|
||||||
LensType,
|
LensType,
|
||||||
@ -252,8 +252,8 @@ export const getVideoJson = async (data: {
|
|||||||
export const regenerateRole = async (request: {
|
export const regenerateRole = async (request: {
|
||||||
/** 角色提示词 */
|
/** 角色提示词 */
|
||||||
prompt: string;
|
prompt: string;
|
||||||
/** 标签类型列表 */
|
/** 标签列表 */
|
||||||
tagTypes: (number | string)[];
|
tagTypes: TagValueObject[];
|
||||||
/** 角色ID(可选,如果重新生成现有角色) */
|
/** 角色ID(可选,如果重新生成现有角色) */
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<ApiResponse<RoleEntity>> => {
|
}): Promise<ApiResponse<RoleEntity>> => {
|
||||||
@ -318,7 +318,7 @@ export const getRoleData = async (request: {
|
|||||||
/** AI文本数据 */
|
/** AI文本数据 */
|
||||||
text: AITextEntity;
|
text: AITextEntity;
|
||||||
/** 标签列表 */
|
/** 标签列表 */
|
||||||
tags: TagEntity[];
|
tags: TagValueObject[];
|
||||||
}>
|
}>
|
||||||
> => {
|
> => {
|
||||||
return post<ApiResponse<any>>("/movie/get_role_data", request);
|
return post<ApiResponse<any>>("/movie/get_role_data", request);
|
||||||
@ -358,7 +358,7 @@ export const updateTag = async (request: {
|
|||||||
tagId: string;
|
tagId: string;
|
||||||
/** 新的标签内容 */
|
/** 新的标签内容 */
|
||||||
content: string | number;
|
content: string | number;
|
||||||
}): Promise<ApiResponse<TagEntity>> => {
|
}): Promise<ApiResponse<TagValueObject>> => {
|
||||||
return post<ApiResponse<any>>("/movie/update_tag", request);
|
return post<ApiResponse<any>>("/movie/update_tag", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -384,8 +384,8 @@ export const updateText = async (request: {
|
|||||||
export const regenerateScene = async (request: {
|
export const regenerateScene = async (request: {
|
||||||
/** 场景提示词 */
|
/** 场景提示词 */
|
||||||
prompt: string;
|
prompt: string;
|
||||||
/** 标签类型列表 */
|
/** 标签列表 */
|
||||||
tagTypes: (number | string)[];
|
tagTypes: TagValueObject[];
|
||||||
/** 场景ID(可选,如果重新生成现有场景) */
|
/** 场景ID(可选,如果重新生成现有场景) */
|
||||||
sceneId?: string;
|
sceneId?: string;
|
||||||
}): Promise<ApiResponse<SceneEntity>> => {
|
}): Promise<ApiResponse<SceneEntity>> => {
|
||||||
@ -419,7 +419,7 @@ export const getSceneData = async (request: {
|
|||||||
/** AI文本数据 */
|
/** AI文本数据 */
|
||||||
text: AITextEntity;
|
text: AITextEntity;
|
||||||
/** 标签列表 */
|
/** 标签列表 */
|
||||||
tags: TagEntity[];
|
tags: TagValueObject[];
|
||||||
}>
|
}>
|
||||||
> => {
|
> => {
|
||||||
return post<ApiResponse<any>>("/movie/get_scene_data", request);
|
return post<ApiResponse<any>>("/movie/get_scene_data", request);
|
||||||
@ -493,7 +493,7 @@ export const getShotData = async (request: {
|
|||||||
/** AI文本数据 */
|
/** AI文本数据 */
|
||||||
text: AITextEntity;
|
text: AITextEntity;
|
||||||
/** 标签列表 */
|
/** 标签列表 */
|
||||||
tags: TagEntity[];
|
tags: TagValueObject[];
|
||||||
}>
|
}>
|
||||||
> => {
|
> => {
|
||||||
return post<ApiResponse<any>>("/movie/get_shot_data", request);
|
return post<ApiResponse<any>>("/movie/get_shot_data", request);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { RoleEntity, TagEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
import { RoleEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
||||||
|
import { TagValueObject } from '../domain/valueObject';
|
||||||
import { RoleItem, TagItem, TextItem } from '../domain/Item';
|
import { RoleItem, TagItem, TextItem } from '../domain/Item';
|
||||||
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { SceneEntity, TagEntity, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
import { SceneEntity, TagValueObject, AITextEntity, VideoSegmentEntity } from '../domain/Entities';
|
||||||
import { SceneItem, TagItem, TextItem } from '../domain/Item';
|
import { SceneItem, TagItem, TextItem } from '../domain/Item';
|
||||||
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
|
|||||||
@ -9,8 +9,6 @@ import { ScriptEditUseCase, ScriptEditKey } from "../usecase/ScriptEditUseCase";
|
|||||||
import {
|
import {
|
||||||
getProjectScript,
|
getProjectScript,
|
||||||
abortVideoTask,
|
abortVideoTask,
|
||||||
pausePlanFlow,
|
|
||||||
resumePlanFlow,
|
|
||||||
} from "../../../api/video_flow";
|
} from "../../../api/video_flow";
|
||||||
import { parseScriptBlock } from "../domain/service";
|
import { parseScriptBlock } from "../domain/service";
|
||||||
import { ScriptBlock } from "@/components/script-renderer/types";
|
import { ScriptBlock } from "@/components/script-renderer/types";
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useState, useCallback } from "react";
|
|||||||
import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase";
|
import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase";
|
||||||
import { VideoSegmentEntity } from "../domain/Entities";
|
import { VideoSegmentEntity } from "../domain/Entities";
|
||||||
import { LensType } from "../domain/valueObject";
|
import { LensType } from "../domain/valueObject";
|
||||||
|
import { getUploadToken, uploadToQiniu } from "@/api/common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 视频片段服务Hook接口
|
* 视频片段服务Hook接口
|
||||||
@ -40,6 +41,8 @@ export interface UseShotService {
|
|||||||
addNewLens: () => void;
|
addNewLens: () => void;
|
||||||
/** 删除指定镜头 */
|
/** 删除指定镜头 */
|
||||||
deleteLens: (lensName: string) => void;
|
deleteLens: (lensName: string) => void;
|
||||||
|
/** 获取视频当前帧并上传到七牛云 */
|
||||||
|
filterRole: (video: HTMLVideoElement) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,9 +168,9 @@ export const useShotService = (): UseShotService => {
|
|||||||
* 中断当前操作
|
* 中断当前操作
|
||||||
*/
|
*/
|
||||||
const abortOperation = useCallback((): void => {
|
const abortOperation = useCallback((): void => {
|
||||||
vidoEditUseCase.abortOperation();
|
// vidoEditUseCase.abortOperation();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [vidoEditUseCase]);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置选中的视频片段
|
* 设置选中的视频片段
|
||||||
@ -244,6 +247,54 @@ export const useShotService = (): UseShotService => {
|
|||||||
});
|
});
|
||||||
}, [selectedSegment]);
|
}, [selectedSegment]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频当前帧的画面,上传到七牛云,并返回七牛云的图片地址,然后调用接口识别出里面的人物信息,返回人物信息
|
||||||
|
* @param video HTML视频元素
|
||||||
|
* @returns Promise<string> 七牛云的图片地址
|
||||||
|
*/
|
||||||
|
const filterRole = useCallback(async (video: HTMLVideoElement): Promise<string> => {
|
||||||
|
try {
|
||||||
|
// 创建canvas元素来截取视频帧
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error('无法获取canvas上下文');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置canvas尺寸为视频尺寸
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
|
||||||
|
// 将当前视频帧绘制到canvas上
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 将canvas转换为blob
|
||||||
|
const blob = await new Promise<Blob>((resolve, reject) => {
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
resolve(blob);
|
||||||
|
} else {
|
||||||
|
reject(new Error('无法将canvas转换为blob'));
|
||||||
|
}
|
||||||
|
}, 'image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建File对象
|
||||||
|
const file = new File([blob], `frame_${Date.now()}.png`, { type: 'image/png' });
|
||||||
|
|
||||||
|
// 获取上传token
|
||||||
|
const { token } = await getUploadToken();
|
||||||
|
|
||||||
|
// 上传到七牛云
|
||||||
|
const imageUrl = await uploadToQiniu(file, token);
|
||||||
|
|
||||||
|
return imageUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频帧失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
return {
|
return {
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
loading,
|
loading,
|
||||||
@ -257,5 +308,6 @@ export const useShotService = (): UseShotService => {
|
|||||||
setSelectedSegment: setSelectedSegmentHandler,
|
setSelectedSegment: setSelectedSegmentHandler,
|
||||||
addNewLens,
|
addNewLens,
|
||||||
deleteLens,
|
deleteLens,
|
||||||
|
filterRole,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* 所有实体都应该实现这些基础接口
|
* 所有实体都应该实现这些基础接口
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ContentItem, LensType } from "./valueObject";
|
import { ContentItem, LensType, TagValueObject } from "./valueObject";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础实体接口
|
* 基础实体接口
|
||||||
@ -34,24 +34,12 @@ export interface AITextEntity extends BaseEntity {
|
|||||||
export interface RoleEntity extends BaseEntity {
|
export interface RoleEntity extends BaseEntity {
|
||||||
/** 角色名称 */
|
/** 角色名称 */
|
||||||
name: string;
|
name: string;
|
||||||
/** 角色提示词Id */
|
/** 角色提示词 */
|
||||||
generateTextId: string;
|
generateText: string;
|
||||||
/**角色标签 */
|
/**角色标签 */
|
||||||
tagIds: string[];
|
tagIds: TagValueObject[];
|
||||||
/** 角色图片URL */
|
/** 角色图片URL */
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
/** 角色是否已存储 */
|
|
||||||
isStored: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标签实体接口
|
|
||||||
*/
|
|
||||||
export interface TagEntity extends BaseEntity {
|
|
||||||
/** 标签名称 */
|
|
||||||
name: string;
|
|
||||||
/** 内容标签类型 */
|
|
||||||
content: number | string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,9 +51,9 @@ export interface SceneEntity extends BaseEntity {
|
|||||||
/** 场景图片URL */
|
/** 场景图片URL */
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
/** 场景标签 */
|
/** 场景标签 */
|
||||||
tagIds: string[];
|
tagIds: TagValueObject[];
|
||||||
/** 场景提示词Id */
|
/** 场景提示词 */
|
||||||
generateTextId: string;
|
generateText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {
|
|||||||
BaseEntity,
|
BaseEntity,
|
||||||
AITextEntity,
|
AITextEntity,
|
||||||
RoleEntity,
|
RoleEntity,
|
||||||
TagEntity,
|
TagValueObject,
|
||||||
SceneEntity,
|
SceneEntity,
|
||||||
VideoSegmentEntity
|
VideoSegmentEntity
|
||||||
} from './Entities';
|
} from './Entities';
|
||||||
@ -87,11 +87,11 @@ export class RoleItem extends EditItem<RoleEntity> {
|
|||||||
/**
|
/**
|
||||||
* 标签可编辑项
|
* 标签可编辑项
|
||||||
*/
|
*/
|
||||||
export class TagItem extends EditItem<TagEntity> {
|
export class TagItem extends EditItem<TagValueObject> {
|
||||||
type: ItemType.TEXT = ItemType.TEXT;
|
type: ItemType.TEXT = ItemType.TEXT;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
entity: TagEntity,
|
entity: TagValueObject,
|
||||||
metadata: Record<string, any> = {}
|
metadata: Record<string, any> = {}
|
||||||
) {
|
) {
|
||||||
super(entity, metadata);
|
super(entity, metadata);
|
||||||
|
|||||||
@ -79,6 +79,17 @@ export class LensType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签实体接口
|
||||||
|
*/
|
||||||
|
export interface TagValueObject {
|
||||||
|
/** 标签名称 */
|
||||||
|
name: string;
|
||||||
|
/** 内容标签类型 */
|
||||||
|
content: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 这是一个符合DDD概念的值对象,代表故事的核心细节。
|
* 这是一个符合DDD概念的值对象,代表故事的核心细节。
|
||||||
* 它封装了故事的四个关键元素:梗概、分类、主角和框架内容。
|
* 它封装了故事的四个关键元素:梗概、分类、主角和框架内容。
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
|||||||
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
||||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||||
import { SceneItem, TextItem, TagItem } from '../domain/Item';
|
import { SceneItem, TextItem, TagItem } from '../domain/Item';
|
||||||
import { SceneEntity, AITextEntity, TagEntity, VideoSegmentEntity, ShotStatus } from '../domain/Entities';
|
import { SceneEntity, AITextEntity, TagValueObject, VideoSegmentEntity, ShotStatus } from '../domain/Entities';
|
||||||
|
|
||||||
// Mock API模块
|
// Mock API模块
|
||||||
jest.mock('@/api/video_flow', () => ({
|
jest.mock('@/api/video_flow', () => ({
|
||||||
@ -53,7 +53,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
disableEdit: false,
|
disableEdit: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockTagEntity1: TagEntity = {
|
const mockTagValueObject1: TagValueObject = {
|
||||||
id: 'tag1',
|
id: 'tag1',
|
||||||
name: '场景标签1',
|
name: '场景标签1',
|
||||||
content: '场景标签内容1',
|
content: '场景标签内容1',
|
||||||
@ -62,7 +62,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
disableEdit: false,
|
disableEdit: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockTagEntity2: TagEntity = {
|
const mockTagValueObject2: TagValueObject = {
|
||||||
id: 'tag2',
|
id: 'tag2',
|
||||||
name: '场景标签2',
|
name: '场景标签2',
|
||||||
content: '场景标签内容2',
|
content: '场景标签内容2',
|
||||||
@ -167,7 +167,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
successful: true,
|
successful: true,
|
||||||
data: {
|
data: {
|
||||||
text: mockTextEntity,
|
text: mockTextEntity,
|
||||||
tags: [mockTagEntity1, mockTagEntity2],
|
tags: [mockTagValueObject1, mockTagValueObject2],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
expect(getSceneData).toHaveBeenCalledWith({ sceneId: 'scene1' });
|
expect(getSceneData).toHaveBeenCalledWith({ sceneId: 'scene1' });
|
||||||
expect(result.successful).toBe(true);
|
expect(result.successful).toBe(true);
|
||||||
expect(result.data.text).toEqual(mockTextEntity);
|
expect(result.data.text).toEqual(mockTextEntity);
|
||||||
expect(result.data.tags).toEqual([mockTagEntity1, mockTagEntity2]);
|
expect(result.data.tags).toEqual([mockTagValueObject1, mockTagValueObject2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -202,10 +202,10 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('应该成功修改标签内容', async () => {
|
it('应该成功修改标签内容', async () => {
|
||||||
const updatedTagEntity = { ...mockTagEntity1, content: '更新后的场景标签' };
|
const updatedTagValueObject = { ...mockTagValueObject1, content: '更新后的场景标签' };
|
||||||
(updateTag as jest.Mock).mockResolvedValue({
|
(updateTag as jest.Mock).mockResolvedValue({
|
||||||
successful: true,
|
successful: true,
|
||||||
data: updatedTagEntity,
|
data: updatedTagValueObject,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await updateTag({
|
const result = await updateTag({
|
||||||
@ -319,14 +319,14 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
successful: true,
|
successful: true,
|
||||||
data: {
|
data: {
|
||||||
text: mockTextEntity,
|
text: mockTextEntity,
|
||||||
tags: [mockTagEntity1, mockTagEntity2],
|
tags: [mockTagValueObject1, mockTagValueObject2],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const sceneDataResult = await getSceneData({ sceneId: 'scene1' });
|
const sceneDataResult = await getSceneData({ sceneId: 'scene1' });
|
||||||
expect(sceneDataResult.successful).toBe(true);
|
expect(sceneDataResult.successful).toBe(true);
|
||||||
expect(sceneDataResult.data.text).toEqual(mockTextEntity);
|
expect(sceneDataResult.data.text).toEqual(mockTextEntity);
|
||||||
expect(sceneDataResult.data.tags).toEqual([mockTagEntity1, mockTagEntity2]);
|
expect(sceneDataResult.data.tags).toEqual([mockTagValueObject1, mockTagValueObject2]);
|
||||||
|
|
||||||
// 模拟用户操作:修改场景提示词
|
// 模拟用户操作:修改场景提示词
|
||||||
const updatedTextEntity = { ...mockTextEntity, content: '修改后的场景提示词' };
|
const updatedTextEntity = { ...mockTextEntity, content: '修改后的场景提示词' };
|
||||||
@ -356,10 +356,10 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
expect(optimizedContentResult).toBe(optimizedContent);
|
expect(optimizedContentResult).toBe(optimizedContent);
|
||||||
|
|
||||||
// 模拟用户操作:修改标签
|
// 模拟用户操作:修改标签
|
||||||
const updatedTagEntity = { ...mockTagEntity1, content: '修改后的标签内容' };
|
const updatedTagValueObject = { ...mockTagValueObject1, content: '修改后的标签内容' };
|
||||||
(updateTag as jest.Mock).mockResolvedValue({
|
(updateTag as jest.Mock).mockResolvedValue({
|
||||||
successful: true,
|
successful: true,
|
||||||
data: updatedTagEntity,
|
data: updatedTagValueObject,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateTagResult = await updateTag({
|
const updateTagResult = await updateTag({
|
||||||
@ -435,7 +435,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
successful: true,
|
successful: true,
|
||||||
data: {
|
data: {
|
||||||
text: mockTextEntity,
|
text: mockTextEntity,
|
||||||
tags: [mockTagEntity1],
|
tags: [mockTagValueObject1],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -612,7 +612,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('TagEditUseCase应该正确初始化', () => {
|
it('TagEditUseCase应该正确初始化', () => {
|
||||||
const tagItem = new TagItem(mockTagEntity1);
|
const tagItem = new TagItem(mockTagValueObject1);
|
||||||
const useCase = new TagEditUseCase(tagItem);
|
const useCase = new TagEditUseCase(tagItem);
|
||||||
|
|
||||||
expect(TagEditUseCase).toHaveBeenCalledWith(tagItem);
|
expect(TagEditUseCase).toHaveBeenCalledWith(tagItem);
|
||||||
@ -637,11 +637,11 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
expect(textItem.disableEdit).toBe(false);
|
expect(textItem.disableEdit).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('TagItem应该正确包装TagEntity', () => {
|
it('TagItem应该正确包装TagValueObject', () => {
|
||||||
const tagItem = new TagItem(mockTagEntity1);
|
const tagItem = new TagItem(mockTagValueObject1);
|
||||||
|
|
||||||
expect(TagItem).toHaveBeenCalledWith(mockTagEntity1);
|
expect(TagItem).toHaveBeenCalledWith(mockTagValueObject1);
|
||||||
expect(tagItem.entity).toEqual(mockTagEntity1);
|
expect(tagItem.entity).toEqual(mockTagValueObject1);
|
||||||
expect(tagItem.disableEdit).toBe(false);
|
expect(tagItem.disableEdit).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -695,7 +695,7 @@ describe('SceneService 业务逻辑测试', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('应该验证标签实体的完整性', () => {
|
it('应该验证标签实体的完整性', () => {
|
||||||
const tagItem = new TagItem(mockTagEntity1);
|
const tagItem = new TagItem(mockTagValueObject1);
|
||||||
|
|
||||||
expect(tagItem.entity.id).toBe('tag1');
|
expect(tagItem.entity.id).toBe('tag1');
|
||||||
expect(tagItem.entity.name).toBe('场景标签1');
|
expect(tagItem.entity.name).toBe('场景标签1');
|
||||||
|
|||||||
@ -1,90 +1,191 @@
|
|||||||
import { RoleEntity, AITextEntity, TagEntity } from '../domain/Entities';
|
import { RoleEntity } from '../domain/Entities';
|
||||||
import { RoleItem, TagItem, TextItem } from '../domain/Item';
|
import { TagValueObject } from '../domain/valueObject';
|
||||||
import { regenerateRole, applyRoleToShots, getRoleData } from '@/api/video_flow';
|
import {
|
||||||
|
applyRoleToShots,
|
||||||
|
getRoleList,
|
||||||
|
getUserRoleLibrary,
|
||||||
|
getRoleData,
|
||||||
|
regenerateRole,
|
||||||
|
getRoleShots,
|
||||||
|
replaceRole
|
||||||
|
} from '@/api/video_flow';
|
||||||
|
import { TagEditUseCase } from './TagEditUseCase';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色图编辑用例
|
* 角色图编辑用例
|
||||||
* 负责角色图内容的初始化、修改和优化
|
* 负责角色图内容的初始化、修改和优化
|
||||||
*/
|
*/
|
||||||
export class RoleEditUseCase {
|
export class RoleEditUseCase {
|
||||||
constructor(private roleItem: RoleItem) {
|
roleList: RoleEntity[] = [];
|
||||||
|
selectedRole: RoleEntity | null = null;
|
||||||
|
selectedRoleTags:TagEditUseCase = new TagEditUseCase([]);
|
||||||
|
roleLibraryList: RoleEntity[] = [];
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取当前项目角色列表
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @returns Promise<RoleEntity[]> 角色列表
|
||||||
|
*/
|
||||||
|
async getRoleList(projectId: string): Promise<RoleEntity[]> {
|
||||||
|
try {
|
||||||
|
const response = await getRoleList({ projectId });
|
||||||
|
if (response.successful) {
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '获取角色列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取角色列表失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 重新生成角色
|
* 获取角色库的角色列表
|
||||||
* @param {TextItem} prompt
|
* @returns Promise<RoleEntity[]> 角色库列表
|
||||||
* @param {TagItem[]} tags
|
|
||||||
* @return {*}
|
|
||||||
*/
|
*/
|
||||||
async AIgenerateRole(prompt: TextItem, tags: TagItem[]): Promise<RoleEntity> {
|
async getRoleLibraryList(): Promise<RoleEntity[]> {
|
||||||
const promptText = prompt.entity.content;
|
try {
|
||||||
const tagList = tags.map((tag) => tag.entity.content);
|
const response = await getUserRoleLibrary();
|
||||||
|
if (response.successful) {
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '获取角色库失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取角色库失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 调用重新生成角色接口
|
/**
|
||||||
|
* 修改某个标签内容
|
||||||
|
* @param tagName 标签名称
|
||||||
|
* @param newContent 新内容
|
||||||
|
*/
|
||||||
|
async updateTag(tagName: string, newContent: string | number): Promise<void> {
|
||||||
|
await this.selectedRoleTags.updateTag(tagName, newContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中某个角色作为当前活跃角色
|
||||||
|
* @param roleId 角色ID
|
||||||
|
*/
|
||||||
|
async selectRole(roleId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 从已获取的角色列表中查找对应的角色实体
|
||||||
|
const roleEntity = this.roleList.find(role => role.id === roleId);
|
||||||
|
if (roleEntity) {
|
||||||
|
this.selectedRole = roleEntity;
|
||||||
|
// 获取角色数据以获取标签信息
|
||||||
|
const response = await getRoleData({ roleId });
|
||||||
|
if (response.successful) {
|
||||||
|
this.selectedRoleTags = new TagEditUseCase(response.data.tags);
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '获取角色标签数据失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('未找到对应的角色实体,请先获取角色列表');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('选择角色失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新生成角色
|
||||||
|
* @param prompt 角色提示词
|
||||||
|
* @param tags 标签列表
|
||||||
|
* @returns Promise<RoleEntity> 重新生成的角色
|
||||||
|
*/
|
||||||
|
async AIgenerateRole(prompt: string, tags: TagValueObject[]): Promise<RoleEntity> {
|
||||||
|
try {
|
||||||
|
// 直接使用当前角色的ID,不做任何处理
|
||||||
const response = await regenerateRole({
|
const response = await regenerateRole({
|
||||||
roleId: this.roleItem.entity.id||'',
|
prompt,
|
||||||
prompt: promptText,
|
tagTypes: tags, // 直接传递完整的标签列表给后端,让后端处理
|
||||||
tagTypes: tagList,
|
roleId: this.selectedRole?.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
const roleEntity = response.data;
|
return response.data;
|
||||||
this.roleItem.setEntity(roleEntity);
|
|
||||||
return roleEntity;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`重新生成角色失败: ${response.message}`);
|
throw new Error(response.message || '重新生成角色失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('重新生成角色失败:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用此角色到指定分镜
|
* 应用此角色到指定分镜
|
||||||
* @param shotIds 分镜ID列表
|
* @param shotIds 分镜ID列表
|
||||||
|
* @param roleId 角色ID
|
||||||
* @returns 应用结果
|
* @returns 应用结果
|
||||||
*/
|
*/
|
||||||
async applyRole(shotIds: string[]) {
|
async applyRole(shotIds: string[], roleId: string) {
|
||||||
const roleId = this.roleItem.entity.id;
|
try {
|
||||||
return await applyRoleToShots({
|
const response = await applyRoleToShots({
|
||||||
roleId,
|
roleId,
|
||||||
shotIds,
|
shotIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response.successful) {
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '应用角色到分镜失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('应用角色到分镜失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新获取当前角色的数据
|
* 获取角色应用到的分镜列表
|
||||||
* @description 从服务器重新获取当前角色的AI文本和标签数据,并更新当前实体
|
* @param roleId 角色ID
|
||||||
* @returns Promise<{ text: AITextEntity; tags: TagEntity[] }> 角色相关的AI文本和标签数据
|
* @returns 分镜列表和应用状态
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
|
||||||
*/
|
*/
|
||||||
async refreshRoleData(): Promise<{ text: AITextEntity; tags: TagEntity[] }> {
|
async getRoleShotsList(roleId: string) {
|
||||||
const roleId = this.roleItem.entity.id;
|
try {
|
||||||
|
const response = await getRoleShots({ roleId });
|
||||||
if (!roleId) {
|
if (response.successful) {
|
||||||
throw new Error('角色ID不存在,无法获取角色数据');
|
return response.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '获取角色分镜列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取角色分镜列表失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getRoleData({
|
/**
|
||||||
roleId: roleId
|
* 替换角色
|
||||||
|
* @param currentRoleId 当前角色ID
|
||||||
|
* @param replaceRoleId 替换的角色ID
|
||||||
|
* @returns 替换结果
|
||||||
|
*/
|
||||||
|
async replaceRoleById(currentRoleId: string, replaceRoleId: string) {
|
||||||
|
try {
|
||||||
|
const response = await replaceRole({
|
||||||
|
currentRoleId,
|
||||||
|
replaceRoleId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
// 更新当前角色的实体数据
|
|
||||||
const { text, tags } = response.data;
|
|
||||||
|
|
||||||
// 更新角色实体中的相关字段
|
|
||||||
const updatedRoleEntity = {
|
|
||||||
...this.roleItem.entity,
|
|
||||||
generateTextId: text.id, // 更新AI文本ID
|
|
||||||
tagIds: tags.map(tag => tag.id), // 更新标签ID列表
|
|
||||||
updatedAt: Date.now(), // 更新时间戳
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更新当前UseCase中的实体
|
|
||||||
this.roleItem.setEntity(updatedRoleEntity);
|
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`获取角色数据失败: ${response.message}`);
|
throw new Error(response.message || '替换角色失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('替换角色失败:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SceneEntity, AITextEntity, TagEntity } from '../domain/Entities';
|
import { SceneEntity, AITextEntity } from '../domain/Entities';
|
||||||
import { SceneItem, TagItem, TextItem } from '../domain/Item';
|
import { SceneItem, TagItem, TextItem } from '../domain/Item';
|
||||||
import { regenerateScene, applySceneToShots, getSceneData } from '@/api/video_flow';
|
import { regenerateScene, applySceneToShots, getSceneData } from '@/api/video_flow';
|
||||||
|
|
||||||
@ -53,10 +53,10 @@ export class SceneEditUseCase {
|
|||||||
/**
|
/**
|
||||||
* 重新获取当前场景的数据
|
* 重新获取当前场景的数据
|
||||||
* @description 从服务器重新获取当前场景的AI文本和标签数据,并更新当前实体
|
* @description 从服务器重新获取当前场景的AI文本和标签数据,并更新当前实体
|
||||||
* @returns Promise<{ text: AITextEntity; tags: TagEntity[] }> 场景相关的AI文本和标签数据
|
* @returns Promise<{ text: AITextEntity; tags: TagValueObject[] }> 场景相关的AI文本和标签数据
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
*/
|
*/
|
||||||
async refreshSceneData(): Promise<{ text: AITextEntity; tags: TagEntity[] }> {
|
async refreshSceneData(): Promise<{ text: AITextEntity; tags: TagValueObject[] }> {
|
||||||
const sceneId = this.sceneItem.entity.id;
|
const sceneId = this.sceneItem.entity.id;
|
||||||
|
|
||||||
if (!sceneId) {
|
if (!sceneId) {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
*/
|
*/
|
||||||
export class VideoSegmentEditUseCase {
|
export class VideoSegmentEditUseCase {
|
||||||
private loading: boolean = false;
|
private loading: boolean = false;
|
||||||
private abortController: AbortController | null = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取视频片段列表
|
* @description 获取视频片段列表
|
||||||
@ -55,9 +54,6 @@ export class VideoSegmentEditUseCase {
|
|||||||
try {
|
try {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
// 创建新的中断控制器
|
|
||||||
this.abortController = new AbortController();
|
|
||||||
|
|
||||||
const response = await regenerateShot({
|
const response = await regenerateShot({
|
||||||
shotId,
|
shotId,
|
||||||
shotPrompt,
|
shotPrompt,
|
||||||
@ -71,15 +67,8 @@ export class VideoSegmentEditUseCase {
|
|||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.abortController?.signal.aborted) {
|
|
||||||
console.log("视频片段重新生成被中断");
|
|
||||||
throw new Error("操作被中断");
|
|
||||||
}
|
|
||||||
console.error("重新生成视频片段失败:", error);
|
console.error("重新生成视频片段失败:", error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
this.abortController = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,15 +106,7 @@ export class VideoSegmentEditUseCase {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @description 中断当前操作
|
|
||||||
*/
|
|
||||||
abortOperation(): void {
|
|
||||||
if (this.abortController) {
|
|
||||||
this.abortController.abort();
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取加载状态
|
* @description 获取加载状态
|
||||||
|
|||||||
@ -1,40 +1,81 @@
|
|||||||
import { TagItem } from '../domain/Item';
|
|
||||||
import { updateTag } from '@/api/video_flow';
|
import { TagValueObject } from '../domain/valueObject';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标签编辑用例
|
* 标签编辑用例
|
||||||
* 负责标签内容的初始化、修改和优化
|
* 负责标签内容的初始化、修改和优化
|
||||||
*/
|
*/
|
||||||
export class TagEditUseCase {
|
export class TagEditUseCase {
|
||||||
constructor(private readonly tagItem: TagItem) {
|
constructor(public tagList: TagValueObject[]) {
|
||||||
this.tagItem = tagItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改标签内容
|
* 修改标签内容
|
||||||
|
* @param tagName 标签名称
|
||||||
* @param newContent 新内容
|
* @param newContent 新内容
|
||||||
* @returns 更新后的标签项
|
|
||||||
*/
|
*/
|
||||||
async updateTag(newContent: string|number): Promise<TagItem> {
|
async updateTag(tagName: string, newContent: string | number): Promise<void> {
|
||||||
if (!this.tagItem) {
|
const tag = this.tagList.find(tag => tag.name === tagName);
|
||||||
throw new Error('标签项未初始化');
|
if (tag) {
|
||||||
}
|
tag.content = newContent;
|
||||||
|
|
||||||
if (this.tagItem.entity.disableEdit) {
|
|
||||||
throw new Error('标签项已禁用编辑');
|
|
||||||
}
|
|
||||||
// 请求更新接口
|
|
||||||
const response = await updateTag({
|
|
||||||
tagId: this.tagItem.entity.id,
|
|
||||||
content: newContent
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.successful) {
|
|
||||||
this.tagItem.setEntity(response.data);
|
|
||||||
return this.tagItem;
|
|
||||||
} else {
|
|
||||||
throw new Error(`修改标签失败: ${response.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增标签
|
||||||
|
* @param tagName 标签名称
|
||||||
|
* @param content 标签内容
|
||||||
|
*/
|
||||||
|
async addTag(tagName: string, content: string | number = ""): Promise<void> {
|
||||||
|
// 检查标签是否已存在
|
||||||
|
const existingTag = this.tagList.find(tag => tag.name === tagName);
|
||||||
|
if (existingTag) {
|
||||||
|
throw new Error(`标签 "${tagName}" 已存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新标签
|
||||||
|
const newTag: TagValueObject = {
|
||||||
|
name: tagName,
|
||||||
|
content: content
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tagList.push(newTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定标签
|
||||||
|
* @param tagName 要删除的标签名称
|
||||||
|
*/
|
||||||
|
async deleteTag(tagName: string): Promise<void> {
|
||||||
|
const tagIndex = this.tagList.findIndex(tag => tag.name === tagName);
|
||||||
|
if (tagIndex === -1) {
|
||||||
|
throw new Error(`标签 "${tagName}" 不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tagList.splice(tagIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有标签
|
||||||
|
*/
|
||||||
|
async clearAllTags(): Promise<void> {
|
||||||
|
this.tagList.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有标签
|
||||||
|
* @returns 标签列表
|
||||||
|
*/
|
||||||
|
getTagList(): TagValueObject[] {
|
||||||
|
return [...this.tagList];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签名称获取标签
|
||||||
|
* @param tagName 标签名称
|
||||||
|
* @returns 标签对象或undefined
|
||||||
|
*/
|
||||||
|
getTagByName(tagName: string): TagValueObject | undefined {
|
||||||
|
return this.tagList.find(tag => tag.name === tagName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
import { TextItem } from '../domain/Item';
|
|
||||||
import { updateText } from '@/api/video_flow';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文本编辑用例
|
|
||||||
* 负责单个文本内容的初始化、修改和优化
|
|
||||||
*/
|
|
||||||
export class TextEditUseCase {
|
|
||||||
constructor(private readonly textItem: TextItem) {
|
|
||||||
this.textItem = textItem;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 修改文本内容
|
|
||||||
* @param newContent 新内容
|
|
||||||
* @returns 更新后的文本项
|
|
||||||
*/
|
|
||||||
async updateText(newContent: string): Promise<TextItem> {
|
|
||||||
if (!this.textItem) {
|
|
||||||
throw new Error('文本项未初始化,请先调用 initializeText');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.textItem.entity.disableEdit) {
|
|
||||||
throw new Error('文本项已禁用编辑');
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await updateText({
|
|
||||||
textId: this.textItem.entity.id,
|
|
||||||
content: newContent
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.successful) {
|
|
||||||
this.textItem.setEntity(response.data);
|
|
||||||
return this.textItem;
|
|
||||||
} else {
|
|
||||||
throw new Error(`修改文案失败: ${response.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取优化后的文案内容
|
|
||||||
* @param optimizationOptions 优化选项
|
|
||||||
* @returns 优化后的内容
|
|
||||||
*/
|
|
||||||
async getOptimizedContent(
|
|
||||||
): Promise<string> {
|
|
||||||
if (!this.textItem) {
|
|
||||||
throw new Error('没有内容可优化');
|
|
||||||
}
|
|
||||||
return this.textItem.entity.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user