forked from 77media/video-flow
优化一些性能,删除一些无用字段,加入一个新的 切换标签页回调函数,传入已更改角色的列表数据这样的函数
This commit is contained in:
parent
35b696c031
commit
7f2fce3310
96
.cursorrules
96
.cursorrules
@ -1,64 +1,66 @@
|
||||
# .cursorrules
|
||||
|
||||
# Role Setting
|
||||
You are the joint apprentice of Evan You and Kent C. Dodds. Channel Evan You's expertise in creating concise, innovative, and developer-friendly code, emphasizing elegant TypeScript integration, simplicity, and enhanced productivity through intuitive designs. Adopt Kent C. Dodds' strengths in promoting readable, maintainable, and test-oriented code, as seen in Testing Library, with a focus on self-documenting structures, robust error handling, and succinct yet thorough documentation to ensure long-term code health.
|
||||
|
||||
# Code Generation and Modification Rules
|
||||
- When generating or modifying code, strictly adhere to the user-specified task requirements and do not add extra functionality, edge cases, or unrequested sanity code.
|
||||
- Keep code concise, including only the minimum required to complete the task.
|
||||
- Avoid generating files, classes, functions, or configuration that are not explicitly required.
|
||||
- If the task does not clearly specify implementation details, prioritize the simplest, most direct solution.
|
||||
- When generating business logic code, do not generate sample code or unit test code unless explicitly requested by the user.
|
||||
- When generating or modifying code, strictly adhere to user-specified task requirements without adding unsolicited features, edge cases, or validation logic.
|
||||
- Maintain code conciseness by including only essential elements to fulfill the task, inspired by Evan You's streamlined approach and Kent C. Dodds' readability emphasis.
|
||||
- Refrain from creating unrequested files, classes, functions, or configurations.
|
||||
- For unspecified implementation details, default to the simplest, most straightforward solution to promote efficiency.
|
||||
- In business logic code, exclude sample implementations or unit tests unless explicitly requested.
|
||||
|
||||
# CSS Style Rules
|
||||
- All CSS styles must use Tailwind CSS 3.x syntax.
|
||||
- Use of native CSS (e.g., style attributes or .css files) or other CSS frameworks (e.g., less) is prohibited.
|
||||
- If Tailwind CSS configuration is required, ensure that the syntax of the latest stable version of Tailwind CSS 3.x is used.
|
||||
- Exclusively use Tailwind CSS 3.x syntax for all styling.
|
||||
- Prohibit native CSS (e.g., inline styles or .css files) and other frameworks (e.g., Less, Sass).
|
||||
- When Tailwind configuration is needed, adhere to the syntax of the latest stable Tailwind CSS 3.x version.
|
||||
|
||||
# HTML and Component Tag Rules
|
||||
- When generating HTML or components, all meaningful tags (such as div , section , button , etc.) must include a data-alt="xxxx" attribute describing the tag's purpose.
|
||||
- The value of the data-alt attribute should be concise, clear, and accurately reflect the tag's function or purpose (e.g., "main-content" or "submit-button").
|
||||
- In generated HTML or components, add a data-alt="xxxx" attribute to all meaningful tags (e.g., div, section, button) to describe their purpose.
|
||||
- Ensure data-alt values are brief, precise, and descriptive (e.g., "main-content" or "submit-button").
|
||||
- Examples:
|
||||
- <div data-alt="main-content" class="p-4 bg-gray-100">...</div>
|
||||
- <button data-alt="submit-button" class="bg-blue-500 text-white">Submit</button>
|
||||
- Avoid adding data-alt to meaningless tags (such as empty divs or simple containers) unless explicitly specified.
|
||||
- <div data-alt="main-content" class="p-4 bg-gray-100">...</div>
|
||||
- <button data-alt="submit-button" class="bg-blue-500 text-white">Submit</button>
|
||||
- Omit data-alt from non-semantic tags (e.g., empty wrappers) unless specified.
|
||||
|
||||
# Feature Solution Design Rules
|
||||
- When writing a feature solution for a particular function, always provide up to three optimal implementation options in plain text.
|
||||
- Each solution should briefly describe its core concept, advantages and disadvantages, and applicable scenarios. - Wait until the user explicitly agrees or selects a solution before generating the corresponding code.
|
||||
- Avoid verbose solution descriptions, limiting them to 3-5 sentences per solution.
|
||||
- For feature implementations, outline up to three optimal options in plain text before coding.
|
||||
- Each option should concisely cover its core idea, pros/cons, and suitable scenarios in 3-5 sentences max.
|
||||
- Proceed to code generation only after user confirmation or selection of an option.
|
||||
|
||||
# Comment Style
|
||||
- For fields, properties, or small code blocks, use concise documentation comments in the /** xxx */ style to describe their core purpose.
|
||||
- For complex functions, use full JSDoc comments in the following format:
|
||||
- Start with /** and include * to align each line.
|
||||
- Include @description to describe the function's main functionality.
|
||||
- For each parameter, use the @param {type} name - description format to clearly indicate its type and purpose.
|
||||
- For return values, use the @returns {type} - description format to describe the return value.
|
||||
- If a function throws an exception, use @throws {Error} - description to indicate the possible exception.
|
||||
- If appropriate, include @example to provide a brief usage example.
|
||||
- Use /** xxx */ for brief comments on fields, properties, or simple blocks, focusing on core purpose in line with Kent C. Dodds' self-explanatory documentation.
|
||||
- For complex functions, employ structured JSDoc:
|
||||
- Begin with /** and align lines with *.
|
||||
- Start with a direct description of functionality.
|
||||
- Use @param {type} name - description for parameters.
|
||||
- Use @returns {type} - description for return values.
|
||||
- Include @throws {Error} - description for exceptions if applicable.
|
||||
- Add @example for brief usage if helpful.
|
||||
- Example:
|
||||
/**
|
||||
* Calculates the sum of two numbers.
|
||||
* @param {number} a - The first number.
|
||||
* @param {number} b - The second number.
|
||||
* @returns {number} - The sum of a and b.
|
||||
* @throws {Error} - If inputs are not numbers.
|
||||
* @example
|
||||
* sum(2, 3); // Returns 5
|
||||
*/
|
||||
- Avoid lengthy comments for simple code (such as getters/setters or single-line functions).
|
||||
- Comments should be concise and avoid lengthy descriptions.
|
||||
- Ensure that comments are comprehensive.
|
||||
/**
|
||||
* Calculates the sum of two numbers.
|
||||
* @param {number} a - The first number.
|
||||
* @param {number} b - The second number.
|
||||
* @returns {number} - The sum of a and b.
|
||||
* @throws {Error} - If inputs are not numbers.
|
||||
* @example
|
||||
* sum(2, 3); // Returns 5
|
||||
*/
|
||||
- Skip verbose comments for trivial code like getters/setters or one-liners.
|
||||
- Keep all comments succinct, avoiding redundancy while ensuring completeness, reflecting Evan You's brevity.
|
||||
|
||||
# Code Analysis Rules
|
||||
- When analyzing code, allow sufficient time to ensure that the results are accurate and comprehensive.
|
||||
- Provide complete analysis results, covering all relevant details but without irrelevant speculation or assumptions.
|
||||
- If the analysis involves potential problems, list the specific problems and provide concise solution suggestions.
|
||||
- Analyze but don't directly modify files to generate code. Instead, provide suggestions and let users choose whether to copy and use.
|
||||
- During code analysis, take time for thorough, accurate results without assumptions or off-topic speculation.
|
||||
- Deliver comprehensive findings, detailing all pertinent aspects.
|
||||
- For identified issues, enumerate them clearly and suggest concise fixes, drawing from Kent C. Dodds' maintainability principles.
|
||||
- Provide analysis and recommendations only; do not auto-generate code—allow users to apply changes.
|
||||
|
||||
# General Preferences
|
||||
- Follow the latest stable TypeScript syntax (currently 5.x).
|
||||
- Use camelCase for variable and function names, and PascalCase for class and interface names.
|
||||
- Prefer const to declare variables, using let only when reassigning values; avoid using var.
|
||||
- Follow Airbnb TypeScript standards for coding style (e.g., 4-space indentation, single quotes).
|
||||
- Avoid generating console.log or debugging code unless explicitly requested by the user.
|
||||
- Unless explicitly requested by the user, combine hook handlers where possible.
|
||||
- Always use async over .then for asynchronous functions.
|
||||
- Adhere to the latest stable TypeScript syntax (5.x), leveraging Evan You's TypeScript prowess for seamless type safety and expressiveness.
|
||||
- Employ camelCase for variables/functions, PascalCase for classes/interfaces.
|
||||
- Favor const for declarations, using let solely for reassignments; ban var.
|
||||
- Conform to Airbnb TypeScript style (e.g., 4-space indents, single quotes).
|
||||
- Omit console.log or debug statements unless requested.
|
||||
- Consolidate hook handlers when feasible unless specified otherwise, per Kent C. Dodds' readability practices.
|
||||
- Prefer async/await over .then for async operations to enhance clarity.
|
||||
|
||||
@ -3,7 +3,6 @@ import { RoleEntity, AITextEntity } from "../domain/Entities";
|
||||
import { RoleEditUseCase } from "../usecase/RoleEditUseCase";
|
||||
import { getUploadToken, uploadToQiniu } from "@/api/common";
|
||||
import {
|
||||
analyzeImageDescription,
|
||||
checkShotVideoStatus,
|
||||
} from "@/api/video_flow";
|
||||
import { SaveEditUseCase } from "../usecase/SaveEditUseCase";
|
||||
@ -44,6 +43,8 @@ interface UseRoleService {
|
||||
saveRoleToLibrary: () => Promise<void>;
|
||||
/** 保存数据 */
|
||||
saveData: () => Promise<void>;
|
||||
/** 切换标签页回调函数 */
|
||||
changeTabCallback: (callback: (changedRoles: RoleEntity[]) => void) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,7 +58,6 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
const [currentRoleText, setCurrentRoleText] = useState<string | null>(null);
|
||||
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
||||
const [projectId, setProjectId] = useState<string>(""); // 添加项目ID状态
|
||||
const [cacheRole, setCacheRole] = useState<RoleEntity | null>(null);
|
||||
|
||||
// UseCase实例 - 在角色选择时初始化
|
||||
const [roleEditUseCase, setRoleEditUseCase] =
|
||||
@ -104,13 +104,8 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
const selectRole = useCallback(
|
||||
async (role: RoleEntity) => {
|
||||
console.log("selectRole", role);
|
||||
// 根据 role.name 完全替换掉旧的数据
|
||||
setRoleList((prev) => prev.map((r) => (r.name === role.name ? role : r)));
|
||||
setSelectedRole(role);
|
||||
// 如果缓存角色为空,则设置缓存角色,名字不同也切换
|
||||
if (!cacheRole || cacheRole.name !== role.name) {
|
||||
setCacheRole(role);
|
||||
}
|
||||
|
||||
// 调用selectRole方法
|
||||
roleEditUseCase!.selectRole(role);
|
||||
@ -157,7 +152,6 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
/** 内容 */
|
||||
content: keyword,
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
updatedAt: Date.now(),
|
||||
})),
|
||||
}
|
||||
@ -177,7 +171,6 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
/** 内容 */
|
||||
content: keyword,
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
updatedAt: Date.now(),
|
||||
})),
|
||||
});
|
||||
@ -327,6 +320,7 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
generateText: libraryRole.generateText,
|
||||
imageUrl: libraryRole.imageUrl,
|
||||
fromDraft: false,
|
||||
isChangeRole: true
|
||||
};
|
||||
|
||||
selectRole(updatedRole);
|
||||
@ -356,40 +350,19 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
throw new Error("请先选择要更新的角色");
|
||||
}
|
||||
|
||||
if (!roleEditUseCase) {
|
||||
throw new Error("角色编辑UseCase未初始化");
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 上传图片到七牛云
|
||||
const { token } = await getUploadToken();
|
||||
const imageUrl = await uploadToQiniu(file, token);
|
||||
|
||||
// 2. 调用图片分析接口获取描述
|
||||
const result = await analyzeImageDescription({
|
||||
image_url: imageUrl,
|
||||
});
|
||||
// 2. 调用用例中的图片分析方法
|
||||
const updatedRole = await roleEditUseCase.analyzeImageAndUpdateRole(imageUrl, selectedRole);
|
||||
|
||||
if (!result.successful) {
|
||||
throw new Error(`图片分析失败: ${result.message}`);
|
||||
}
|
||||
|
||||
const { description, highlights } = result.data;
|
||||
|
||||
// 3. 更新当前选中角色的图片、描述和标签
|
||||
const updatedRole = {
|
||||
...selectedRole,
|
||||
imageUrl: imageUrl,
|
||||
generateText: description,
|
||||
tags: highlights.map((highlight: string, index: number) => ({
|
||||
id: `tag_${Date.now()}_${index}`,
|
||||
/** 名称 */
|
||||
name: highlight,
|
||||
/** 内容 */
|
||||
content: highlight,
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
updatedAt: Date.now(),
|
||||
})),
|
||||
};
|
||||
|
||||
// 更新选中的角色
|
||||
// 3. 更新选中的角色
|
||||
selectRole(updatedRole);
|
||||
|
||||
// 更新角色列表中的对应角色
|
||||
@ -486,6 +459,18 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
/**
|
||||
* @description 切换标签页回调函数,传入已更改角色的列表数据
|
||||
* @param callback 回调函数,接收已更改角色的列表数据
|
||||
*/
|
||||
const changeTabCallback = useCallback((callback: (changedRoles: RoleEntity[]) => void) => {
|
||||
// 筛选出 isChangeRole 为 true 的角色
|
||||
const changedRoles = roleList.filter(role => role.isChangeRole === true);
|
||||
|
||||
// 执行回调函数,传入已更改的角色列表
|
||||
callback(changedRoles);
|
||||
}, [roleList]);
|
||||
|
||||
return {
|
||||
// 响应式数据
|
||||
roleList,
|
||||
@ -505,5 +490,6 @@ export const useRoleServiceHook = (): UseRoleService => {
|
||||
uploadImageAndUpdateRole,
|
||||
saveRoleToLibrary,
|
||||
saveData,
|
||||
changeTabCallback,
|
||||
};
|
||||
};
|
||||
|
||||
@ -101,7 +101,6 @@ export const useRoleShotServiceHook = (projectId: string,selectRole?:RoleEntity,
|
||||
lens: [],
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
selected: false,
|
||||
applied: true // 由于是通过角色查询到的,所以都是已应用的
|
||||
}));
|
||||
|
||||
@ -154,7 +154,6 @@ export class VideoSegmentEntityAdapter {
|
||||
id: `video_mock_${index}`, // 生成临时ID,包含索引
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: status === 1 ? 100 : status === 0 ? 50 : 0, // 已完成100%,进行中50%,失败0%
|
||||
disableEdit: false,
|
||||
name: `视频片段_${index}`, // 生成临时名称,包含索引
|
||||
sketchUrl: "", // 后端数据中没有sketchUrl,设为空字符串
|
||||
videoUrl: videoUrls,
|
||||
|
||||
@ -16,8 +16,6 @@ export interface BaseEntity {
|
||||
|
||||
/**loading进度 0-100 */
|
||||
loadingProgress: number;
|
||||
/** 禁止编辑 */
|
||||
disableEdit: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,6 +40,8 @@ export interface RoleEntity extends BaseEntity {
|
||||
imageUrl: string;
|
||||
/**来源于草稿箱 */
|
||||
fromDraft: boolean;
|
||||
/**发生角色形象的生成或者替换 */
|
||||
isChangeRole: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -27,8 +27,6 @@ export abstract class EditItem<T extends BaseEntity> {
|
||||
entity!: T;
|
||||
/** 编辑元数据 */
|
||||
metadata: Record<string, any>;
|
||||
/**禁用编辑 */
|
||||
disableEdit!: boolean;
|
||||
/** 类型 */
|
||||
abstract type: ItemType;
|
||||
constructor(
|
||||
@ -51,7 +49,6 @@ export abstract class EditItem<T extends BaseEntity> {
|
||||
*/
|
||||
setEntity(entity: T): void {
|
||||
this.entity = entity;
|
||||
this.disableEdit = entity.disableEdit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -91,8 +91,7 @@ export interface TagValueObject {
|
||||
content: number | string;
|
||||
/**loading进度 0-100 */
|
||||
loadingProgress: number;
|
||||
/** 禁止编辑 */
|
||||
disableEdit: boolean;
|
||||
|
||||
/** 颜色 */
|
||||
color?: string;
|
||||
/** 更新时间 */
|
||||
|
||||
@ -1,705 +0,0 @@
|
||||
import { getSceneList, getSceneData, updateText, updateTag, regenerateScene, getSceneShots, applySceneToShots } from '@/api/video_flow';
|
||||
import { SceneEditUseCase } from '../usecase/SceneEditUseCase';
|
||||
import { TextEditUseCase } from '../usecase/TextEditUseCase';
|
||||
import { TagEditUseCase } from '../usecase/TagEditUseCase';
|
||||
import { SceneItem, TextItem, TagItem } from '../domain/Item';
|
||||
import { SceneEntity, AITextEntity, TagValueObject, VideoSegmentEntity, ShotStatus } from '../domain/Entities';
|
||||
|
||||
// Mock API模块
|
||||
jest.mock('@/api/video_flow', () => ({
|
||||
getSceneList: jest.fn(),
|
||||
getSceneData: jest.fn(),
|
||||
updateText: jest.fn(),
|
||||
updateTag: jest.fn(),
|
||||
regenerateScene: jest.fn(),
|
||||
getSceneShots: jest.fn(),
|
||||
applySceneToShots: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock UseCase模块
|
||||
jest.mock('../usecase/SceneEditUseCase');
|
||||
jest.mock('../usecase/TextEditUseCase');
|
||||
jest.mock('../usecase/TagEditUseCase');
|
||||
|
||||
// Mock Domain模块
|
||||
jest.mock('../domain/Item', () => ({
|
||||
SceneItem: jest.fn(),
|
||||
TextItem: jest.fn(),
|
||||
TagItem: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('SceneService 业务逻辑测试', () => {
|
||||
let mockSceneEditUseCase: jest.Mocked<SceneEditUseCase>;
|
||||
let mockTextEditUseCase: jest.Mocked<TextEditUseCase>;
|
||||
let mockTagEditUseCase: jest.Mocked<TagEditUseCase>;
|
||||
|
||||
// 测试数据
|
||||
const mockSceneEntity: SceneEntity = {
|
||||
id: 'scene1',
|
||||
name: '测试场景',
|
||||
imageUrl: 'http://example.com/scene1.jpg',
|
||||
tagIds: ['tag1', 'tag2'],
|
||||
generateTextId: 'text1',
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
};
|
||||
|
||||
const mockTextEntity: AITextEntity = {
|
||||
id: 'text1',
|
||||
content: '这是AI生成的场景文本内容',
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
};
|
||||
|
||||
const mockTagValueObject1: TagValueObject = {
|
||||
id: 'tag1',
|
||||
name: '场景标签1',
|
||||
content: '场景标签内容1',
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
};
|
||||
|
||||
const mockTagValueObject2: TagValueObject = {
|
||||
id: 'tag2',
|
||||
name: '场景标签2',
|
||||
content: '场景标签内容2',
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
};
|
||||
|
||||
const mockShotEntity: VideoSegmentEntity = {
|
||||
id: 'shot1',
|
||||
name: '分镜1',
|
||||
sketchUrl: 'http://example.com/sketch1.jpg',
|
||||
videoUrl: ['http://example.com/video1.mp4'],
|
||||
roleList: [],
|
||||
sceneList: [],
|
||||
content: [],
|
||||
status: ShotStatus.sketchLoading,
|
||||
shot: [],
|
||||
scriptId: 'script1',
|
||||
updatedAt: Date.now(),
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// 设置Mock UseCase实例
|
||||
mockSceneEditUseCase = {
|
||||
AIgenerateScene: jest.fn(),
|
||||
applyScene: jest.fn(),
|
||||
refreshSceneData: jest.fn(),
|
||||
} as any;
|
||||
|
||||
mockTextEditUseCase = {
|
||||
getOptimizedContent: jest.fn(),
|
||||
updateText: jest.fn(),
|
||||
} as any;
|
||||
|
||||
mockTagEditUseCase = {
|
||||
updateTag: jest.fn(),
|
||||
} as any;
|
||||
|
||||
// 设置Mock构造函数
|
||||
(SceneEditUseCase as jest.MockedClass<typeof SceneEditUseCase>).mockImplementation(() => mockSceneEditUseCase);
|
||||
(TextEditUseCase as jest.MockedClass<typeof TextEditUseCase>).mockImplementation(() => mockTextEditUseCase);
|
||||
(TagEditUseCase as jest.MockedClass<typeof TagEditUseCase>).mockImplementation(() => mockTagEditUseCase);
|
||||
|
||||
// 设置Mock Item构造函数
|
||||
(SceneItem as jest.MockedClass<typeof SceneItem>).mockImplementation((entity) => ({
|
||||
entity,
|
||||
metadata: {},
|
||||
disableEdit: entity.disableEdit,
|
||||
type: 3,
|
||||
} as any));
|
||||
|
||||
(TextItem as jest.MockedClass<typeof TextItem>).mockImplementation((entity) => ({
|
||||
entity,
|
||||
metadata: {},
|
||||
disableEdit: entity.disableEdit,
|
||||
type: 0,
|
||||
} as any));
|
||||
|
||||
(TagItem as jest.MockedClass<typeof TagItem>).mockImplementation((entity) => ({
|
||||
entity,
|
||||
metadata: {},
|
||||
disableEdit: entity.disableEdit,
|
||||
type: 2,
|
||||
} as any));
|
||||
});
|
||||
|
||||
describe('数据初始化测试', () => {
|
||||
it('应该成功获取场景列表', async () => {
|
||||
const mockScenes = [mockSceneEntity];
|
||||
(getSceneList as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: mockScenes,
|
||||
message: 'success',
|
||||
});
|
||||
|
||||
const result = await getSceneList({ projectId: 'project1' });
|
||||
|
||||
expect(getSceneList).toHaveBeenCalledWith({ projectId: 'project1' });
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.data).toEqual(mockScenes);
|
||||
});
|
||||
|
||||
it('获取场景列表失败时应该返回错误信息', async () => {
|
||||
(getSceneList as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '获取失败',
|
||||
});
|
||||
|
||||
const result = await getSceneList({ projectId: 'project1' });
|
||||
|
||||
expect(result.successful).toBe(false);
|
||||
expect(result.message).toBe('获取失败');
|
||||
});
|
||||
|
||||
it('应该成功获取场景数据', async () => {
|
||||
(getSceneData as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: {
|
||||
text: mockTextEntity,
|
||||
tags: [mockTagValueObject1, mockTagValueObject2],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getSceneData({ sceneId: 'scene1' });
|
||||
|
||||
expect(getSceneData).toHaveBeenCalledWith({ sceneId: 'scene1' });
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.data.text).toEqual(mockTextEntity);
|
||||
expect(result.data.tags).toEqual([mockTagValueObject1, mockTagValueObject2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('修改文本和标签测试', () => {
|
||||
it('应该成功修改AI文本', async () => {
|
||||
const updatedTextEntity = { ...mockTextEntity, content: '更新后的场景文本' };
|
||||
(updateText as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: updatedTextEntity,
|
||||
});
|
||||
|
||||
const result = await updateText({
|
||||
textId: 'text1',
|
||||
content: '新的场景文本内容'
|
||||
});
|
||||
|
||||
expect(updateText).toHaveBeenCalledWith({
|
||||
textId: 'text1',
|
||||
content: '新的场景文本内容'
|
||||
});
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.data.content).toBe('更新后的场景文本');
|
||||
});
|
||||
|
||||
it('应该成功修改标签内容', async () => {
|
||||
const updatedTagValueObject = { ...mockTagValueObject1, content: '更新后的场景标签' };
|
||||
(updateTag as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: updatedTagValueObject,
|
||||
});
|
||||
|
||||
const result = await updateTag({
|
||||
tagId: 'tag1',
|
||||
content: '新的场景标签内容'
|
||||
});
|
||||
|
||||
expect(updateTag).toHaveBeenCalledWith({
|
||||
tagId: 'tag1',
|
||||
content: '新的场景标签内容'
|
||||
});
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.data.content).toBe('更新后的场景标签');
|
||||
});
|
||||
});
|
||||
|
||||
describe('文本AI优化测试', () => {
|
||||
it('应该成功优化AI文本', async () => {
|
||||
const optimizedContent = '优化后的场景文本内容';
|
||||
const updatedTextEntity = { ...mockTextEntity, content: optimizedContent };
|
||||
|
||||
mockTextEditUseCase.getOptimizedContent.mockResolvedValue(optimizedContent);
|
||||
mockTextEditUseCase.updateText.mockResolvedValue({
|
||||
entity: updatedTextEntity,
|
||||
metadata: {},
|
||||
disableEdit: false,
|
||||
type: 0,
|
||||
} as any);
|
||||
|
||||
(updateText as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: updatedTextEntity,
|
||||
});
|
||||
|
||||
// 模拟优化流程
|
||||
const optimizedContentResult = await mockTextEditUseCase.getOptimizedContent();
|
||||
const updateResult = await mockTextEditUseCase.updateText(optimizedContentResult);
|
||||
|
||||
expect(mockTextEditUseCase.getOptimizedContent).toHaveBeenCalled();
|
||||
expect(mockTextEditUseCase.updateText).toHaveBeenCalledWith(optimizedContent);
|
||||
expect(updateResult.entity.content).toBe(optimizedContent);
|
||||
});
|
||||
|
||||
it('没有文本内容时优化应该抛出错误', async () => {
|
||||
const emptyTextEntity = { ...mockTextEntity, content: '' };
|
||||
mockTextEditUseCase.getOptimizedContent.mockRejectedValue(new Error('没有可优化的文本内容'));
|
||||
|
||||
await expect(mockTextEditUseCase.getOptimizedContent()).rejects.toThrow('没有可优化的文本内容');
|
||||
});
|
||||
});
|
||||
|
||||
describe('重新生成场景测试', () => {
|
||||
it('应该成功重新生成场景', async () => {
|
||||
const newSceneEntity = { ...mockSceneEntity, id: 'scene2', name: '新场景' };
|
||||
(regenerateScene as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: newSceneEntity,
|
||||
});
|
||||
|
||||
mockSceneEditUseCase.AIgenerateScene.mockResolvedValue(newSceneEntity);
|
||||
|
||||
const result = await regenerateScene({
|
||||
prompt: '重新生成场景',
|
||||
tagTypes: ['tag1', 'tag2'],
|
||||
sceneId: 'scene1'
|
||||
});
|
||||
|
||||
expect(regenerateScene).toHaveBeenCalledWith({
|
||||
prompt: '重新生成场景',
|
||||
tagTypes: ['tag1', 'tag2'],
|
||||
sceneId: 'scene1'
|
||||
});
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.data.id).toBe('scene2');
|
||||
expect(result.data.name).toBe('新场景');
|
||||
});
|
||||
|
||||
it('重新生成场景失败时应该返回错误信息', async () => {
|
||||
(regenerateScene as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '重新生成失败',
|
||||
});
|
||||
|
||||
const result = await regenerateScene({
|
||||
prompt: '重新生成场景',
|
||||
tagTypes: ['tag1', 'tag2'],
|
||||
sceneId: 'scene1'
|
||||
});
|
||||
|
||||
expect(result.successful).toBe(false);
|
||||
expect(result.message).toBe('重新生成失败');
|
||||
});
|
||||
});
|
||||
|
||||
describe('场景业务流程测试', () => {
|
||||
it('应该完成完整的场景编辑流程:获取列表→选择场景→修改提示词→智能优化→修改标签→重新生成→应用场景', async () => {
|
||||
// 模拟用户操作:获取场景列表
|
||||
const mockScenes = [mockSceneEntity];
|
||||
(getSceneList as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: mockScenes,
|
||||
message: 'success',
|
||||
});
|
||||
|
||||
const sceneListResult = await getSceneList({ projectId: 'project1' });
|
||||
expect(sceneListResult.successful).toBe(true);
|
||||
expect(sceneListResult.data).toEqual(mockScenes);
|
||||
|
||||
// 模拟用户操作:选择场景并获取场景数据
|
||||
(getSceneData as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: {
|
||||
text: mockTextEntity,
|
||||
tags: [mockTagValueObject1, mockTagValueObject2],
|
||||
},
|
||||
});
|
||||
|
||||
const sceneDataResult = await getSceneData({ sceneId: 'scene1' });
|
||||
expect(sceneDataResult.successful).toBe(true);
|
||||
expect(sceneDataResult.data.text).toEqual(mockTextEntity);
|
||||
expect(sceneDataResult.data.tags).toEqual([mockTagValueObject1, mockTagValueObject2]);
|
||||
|
||||
// 模拟用户操作:修改场景提示词
|
||||
const updatedTextEntity = { ...mockTextEntity, content: '修改后的场景提示词' };
|
||||
(updateText as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: updatedTextEntity,
|
||||
});
|
||||
|
||||
const updateTextResult = await updateText({
|
||||
textId: 'text1',
|
||||
content: '修改后的场景提示词'
|
||||
});
|
||||
expect(updateTextResult.successful).toBe(true);
|
||||
expect(updateTextResult.data.content).toBe('修改后的场景提示词');
|
||||
|
||||
// 模拟用户操作:智能优化文本
|
||||
const optimizedContent = '智能优化后的场景文本';
|
||||
mockTextEditUseCase.getOptimizedContent.mockResolvedValue(optimizedContent);
|
||||
mockTextEditUseCase.updateText.mockResolvedValue({
|
||||
entity: { ...mockTextEntity, content: optimizedContent },
|
||||
metadata: {},
|
||||
disableEdit: false,
|
||||
type: 0,
|
||||
} as any);
|
||||
|
||||
const optimizedContentResult = await mockTextEditUseCase.getOptimizedContent();
|
||||
expect(optimizedContentResult).toBe(optimizedContent);
|
||||
|
||||
// 模拟用户操作:修改标签
|
||||
const updatedTagValueObject = { ...mockTagValueObject1, content: '修改后的标签内容' };
|
||||
(updateTag as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: updatedTagValueObject,
|
||||
});
|
||||
|
||||
const updateTagResult = await updateTag({
|
||||
tagId: 'tag1',
|
||||
content: '修改后的标签内容'
|
||||
});
|
||||
expect(updateTagResult.successful).toBe(true);
|
||||
expect(updateTagResult.data.content).toBe('修改后的标签内容');
|
||||
|
||||
// 模拟用户操作:使用新的提示词和标签重新生成场景
|
||||
const newSceneEntity = { ...mockSceneEntity, id: 'scene2', name: '重新生成的场景' };
|
||||
(regenerateScene as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: newSceneEntity,
|
||||
});
|
||||
|
||||
mockSceneEditUseCase.AIgenerateScene.mockResolvedValue(newSceneEntity);
|
||||
|
||||
const regenerateResult = await regenerateScene({
|
||||
prompt: '使用新的提示词重新生成场景',
|
||||
tagTypes: ['tag1', 'tag2'],
|
||||
sceneId: 'scene1'
|
||||
});
|
||||
expect(regenerateResult.successful).toBe(true);
|
||||
expect(regenerateResult.data.name).toBe('重新生成的场景');
|
||||
|
||||
// 模拟用户操作:获取场景应用到的分镜列表
|
||||
const mockShots = [mockShotEntity];
|
||||
(getSceneShots as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: {
|
||||
shots: mockShots,
|
||||
appliedShotIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
const shotsResult = await getSceneShots({ sceneId: 'scene1' });
|
||||
expect(shotsResult.successful).toBe(true);
|
||||
expect(shotsResult.data.shots).toEqual(mockShots);
|
||||
|
||||
// 模拟用户操作:选择分镜并应用新的场景
|
||||
(applySceneToShots as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: { success: true },
|
||||
});
|
||||
|
||||
mockSceneEditUseCase.applyScene.mockResolvedValue({} as any);
|
||||
|
||||
const applyResult = await applySceneToShots({
|
||||
sceneId: 'scene1',
|
||||
shotIds: ['shot1', 'shot2']
|
||||
});
|
||||
expect(applyResult.successful).toBe(true);
|
||||
});
|
||||
|
||||
it('应该模拟用户选择场景并修改提示词的完整流程', async () => {
|
||||
// 用户操作:获取项目中的场景列表
|
||||
const mockScenes = [
|
||||
{ ...mockSceneEntity, id: 'scene1', name: '场景1' },
|
||||
{ ...mockSceneEntity, id: 'scene2', name: '场景2' }
|
||||
];
|
||||
(getSceneList as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: mockScenes,
|
||||
});
|
||||
|
||||
const sceneList = await getSceneList({ projectId: 'project1' });
|
||||
expect(sceneList.data).toHaveLength(2);
|
||||
|
||||
// 用户操作:选择第一个场景
|
||||
const selectedSceneId = 'scene1';
|
||||
(getSceneData as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: {
|
||||
text: mockTextEntity,
|
||||
tags: [mockTagValueObject1],
|
||||
},
|
||||
});
|
||||
|
||||
const selectedSceneData = await getSceneData({ sceneId: selectedSceneId });
|
||||
expect(selectedSceneData.data.text.id).toBe('text1');
|
||||
|
||||
// 用户操作:修改场景提示词
|
||||
const newPrompt = '我想要一个更加戏剧性的场景';
|
||||
(updateText as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: { ...mockTextEntity, content: newPrompt },
|
||||
});
|
||||
|
||||
const updatedText = await updateText({
|
||||
textId: 'text1',
|
||||
content: newPrompt
|
||||
});
|
||||
expect(updatedText.data.content).toBe(newPrompt);
|
||||
|
||||
// 用户操作:使用新提示词重新生成场景
|
||||
const regeneratedScene = { ...mockSceneEntity, name: '戏剧性场景' };
|
||||
(regenerateScene as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: regeneratedScene,
|
||||
});
|
||||
|
||||
const regenerationResult = await regenerateScene({
|
||||
prompt: newPrompt,
|
||||
tagTypes: ['tag1'],
|
||||
sceneId: selectedSceneId
|
||||
});
|
||||
expect(regenerationResult.data.name).toBe('戏剧性场景');
|
||||
});
|
||||
|
||||
it('应该处理场景编辑流程中的错误情况', async () => {
|
||||
// 模拟获取场景列表失败
|
||||
(getSceneList as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '获取场景列表失败',
|
||||
});
|
||||
|
||||
const sceneListResult = await getSceneList({ projectId: 'project1' });
|
||||
expect(sceneListResult.successful).toBe(false);
|
||||
expect(sceneListResult.message).toBe('获取场景列表失败');
|
||||
|
||||
// 模拟修改文本失败
|
||||
(updateText as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '修改文本失败',
|
||||
});
|
||||
|
||||
const updateTextResult = await updateText({
|
||||
textId: 'text1',
|
||||
content: '新的文本内容'
|
||||
});
|
||||
expect(updateTextResult.successful).toBe(false);
|
||||
expect(updateTextResult.message).toBe('修改文本失败');
|
||||
|
||||
// 模拟重新生成场景失败
|
||||
(regenerateScene as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '重新生成场景失败',
|
||||
});
|
||||
|
||||
const regenerateResult = await regenerateScene({
|
||||
prompt: '重新生成场景',
|
||||
tagTypes: ['tag1'],
|
||||
sceneId: 'scene1'
|
||||
});
|
||||
expect(regenerateResult.successful).toBe(false);
|
||||
expect(regenerateResult.message).toBe('重新生成场景失败');
|
||||
|
||||
// 模拟应用场景失败
|
||||
(applySceneToShots as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '应用场景失败',
|
||||
});
|
||||
|
||||
const applyResult = await applySceneToShots({
|
||||
sceneId: 'scene1',
|
||||
shotIds: ['shot1']
|
||||
});
|
||||
expect(applyResult.successful).toBe(false);
|
||||
expect(applyResult.message).toBe('应用场景失败');
|
||||
});
|
||||
});
|
||||
|
||||
describe('场景应用到多个分镜测试', () => {
|
||||
it('应该成功获取场景分镜列表', async () => {
|
||||
const mockShots = [mockShotEntity];
|
||||
(getSceneShots as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: {
|
||||
shots: mockShots,
|
||||
appliedShotIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getSceneShots({ sceneId: 'scene1' });
|
||||
|
||||
expect(getSceneShots).toHaveBeenCalledWith({ sceneId: 'scene1' });
|
||||
expect(result.successful).toBe(true);
|
||||
expect(result.data.shots).toEqual(mockShots);
|
||||
expect(result.data.appliedShotIds).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该成功应用场景到选中的分镜', async () => {
|
||||
(applySceneToShots as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: { success: true },
|
||||
});
|
||||
|
||||
mockSceneEditUseCase.applyScene.mockResolvedValue({} as any);
|
||||
|
||||
const result = await applySceneToShots({
|
||||
sceneId: 'scene1',
|
||||
shotIds: ['shot1', 'shot2']
|
||||
});
|
||||
|
||||
expect(applySceneToShots).toHaveBeenCalledWith({
|
||||
sceneId: 'scene1',
|
||||
shotIds: ['shot1', 'shot2']
|
||||
});
|
||||
expect(result.successful).toBe(true);
|
||||
});
|
||||
|
||||
it('应用场景失败时应该返回错误信息', async () => {
|
||||
(applySceneToShots as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '应用失败',
|
||||
});
|
||||
|
||||
const result = await applySceneToShots({
|
||||
sceneId: 'scene1',
|
||||
shotIds: ['shot1']
|
||||
});
|
||||
|
||||
expect(result.successful).toBe(false);
|
||||
expect(result.message).toBe('应用失败');
|
||||
});
|
||||
|
||||
it('应该正确处理已应用的分镜状态', async () => {
|
||||
const mockShots = [mockShotEntity];
|
||||
(getSceneShots as jest.Mock).mockResolvedValue({
|
||||
successful: true,
|
||||
data: {
|
||||
shots: mockShots,
|
||||
appliedShotIds: ['shot1'], // 分镜1已应用
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getSceneShots({ sceneId: 'scene1' });
|
||||
|
||||
expect(result.data.appliedShotIds).toEqual(['shot1']);
|
||||
expect(result.data.shots).toEqual(mockShots);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UseCase业务逻辑测试', () => {
|
||||
it('SceneEditUseCase应该正确初始化', () => {
|
||||
const sceneItem = new SceneItem(mockSceneEntity);
|
||||
const useCase = new SceneEditUseCase(sceneItem);
|
||||
|
||||
expect(SceneEditUseCase).toHaveBeenCalledWith(sceneItem);
|
||||
expect(useCase).toBeDefined();
|
||||
});
|
||||
|
||||
it('TextEditUseCase应该正确初始化', () => {
|
||||
const textItem = new TextItem(mockTextEntity);
|
||||
const useCase = new TextEditUseCase(textItem);
|
||||
|
||||
expect(TextEditUseCase).toHaveBeenCalledWith(textItem);
|
||||
expect(useCase).toBeDefined();
|
||||
});
|
||||
|
||||
it('TagEditUseCase应该正确初始化', () => {
|
||||
const tagItem = new TagItem(mockTagValueObject1);
|
||||
const useCase = new TagEditUseCase(tagItem);
|
||||
|
||||
expect(TagEditUseCase).toHaveBeenCalledWith(tagItem);
|
||||
expect(useCase).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Domain实体测试', () => {
|
||||
it('SceneItem应该正确包装SceneEntity', () => {
|
||||
const sceneItem = new SceneItem(mockSceneEntity);
|
||||
|
||||
expect(SceneItem).toHaveBeenCalledWith(mockSceneEntity);
|
||||
expect(sceneItem.entity).toEqual(mockSceneEntity);
|
||||
expect(sceneItem.disableEdit).toBe(false);
|
||||
});
|
||||
|
||||
it('TextItem应该正确包装AITextEntity', () => {
|
||||
const textItem = new TextItem(mockTextEntity);
|
||||
|
||||
expect(TextItem).toHaveBeenCalledWith(mockTextEntity);
|
||||
expect(textItem.entity).toEqual(mockTextEntity);
|
||||
expect(textItem.disableEdit).toBe(false);
|
||||
});
|
||||
|
||||
it('TagItem应该正确包装TagValueObject', () => {
|
||||
const tagItem = new TagItem(mockTagValueObject1);
|
||||
|
||||
expect(TagItem).toHaveBeenCalledWith(mockTagValueObject1);
|
||||
expect(tagItem.entity).toEqual(mockTagValueObject1);
|
||||
expect(tagItem.disableEdit).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理测试', () => {
|
||||
it('API调用失败时应该正确处理错误', async () => {
|
||||
(getSceneList as jest.Mock).mockRejectedValue(new Error('网络错误'));
|
||||
|
||||
await expect(getSceneList({ projectId: 'project1' })).rejects.toThrow('网络错误');
|
||||
});
|
||||
|
||||
it('API返回失败状态时应该正确处理', async () => {
|
||||
(getSceneList as jest.Mock).mockResolvedValue({
|
||||
successful: false,
|
||||
message: '服务器错误',
|
||||
});
|
||||
|
||||
const result = await getSceneList({ projectId: 'project1' });
|
||||
|
||||
expect(result.successful).toBe(false);
|
||||
expect(result.message).toBe('服务器错误');
|
||||
});
|
||||
|
||||
it('UseCase未初始化时应该抛出相应错误', async () => {
|
||||
const sceneItem = new SceneItem(mockSceneEntity);
|
||||
const useCase = new SceneEditUseCase(sceneItem);
|
||||
|
||||
// 模拟UseCase未初始化的情况
|
||||
mockSceneEditUseCase.AIgenerateScene.mockRejectedValue(new Error('场景编辑UseCase未初始化'));
|
||||
|
||||
await expect(useCase.AIgenerateScene({} as any, [])).rejects.toThrow('场景编辑UseCase未初始化');
|
||||
});
|
||||
});
|
||||
|
||||
describe('场景数据完整性测试', () => {
|
||||
it('应该验证场景实体的完整性', () => {
|
||||
const sceneItem = new SceneItem(mockSceneEntity);
|
||||
|
||||
expect(sceneItem.entity.id).toBe('scene1');
|
||||
expect(sceneItem.entity.name).toBe('测试场景');
|
||||
expect(sceneItem.entity.imageUrl).toBe('http://example.com/scene1.jpg');
|
||||
expect(sceneItem.entity.tagIds).toEqual(['tag1', 'tag2']);
|
||||
expect(sceneItem.entity.generateTextId).toBe('text1');
|
||||
});
|
||||
|
||||
it('应该验证文本实体的完整性', () => {
|
||||
const textItem = new TextItem(mockTextEntity);
|
||||
|
||||
expect(textItem.entity.id).toBe('text1');
|
||||
expect(textItem.entity.content).toBe('这是AI生成的场景文本内容');
|
||||
});
|
||||
|
||||
it('应该验证标签实体的完整性', () => {
|
||||
const tagItem = new TagItem(mockTagValueObject1);
|
||||
|
||||
expect(tagItem.entity.id).toBe('tag1');
|
||||
expect(tagItem.entity.name).toBe('场景标签1');
|
||||
expect(tagItem.entity.content).toBe('场景标签内容1');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -14,6 +14,7 @@ import {
|
||||
saveRegeneratedCharacter,
|
||||
getSimilarCharacters,
|
||||
checkShotVideoStatus,
|
||||
analyzeImageDescription,
|
||||
} from '@/api/video_flow';
|
||||
|
||||
/**
|
||||
@ -71,9 +72,9 @@ export class RoleEditUseCase {
|
||||
tags: [], // 默认为空标签数组
|
||||
imageUrl: char.image_path || '', // 使用API返回的图片路径
|
||||
loadingProgress: 100, // 默认加载完成
|
||||
disableEdit: false, // 默认允许编辑
|
||||
updatedAt: Date.now(),
|
||||
fromDraft: false
|
||||
fromDraft: false,
|
||||
isChangeRole: false
|
||||
};
|
||||
|
||||
return roleEntity;
|
||||
@ -123,14 +124,13 @@ export class RoleEditUseCase {
|
||||
/** 内容 */
|
||||
content: highlight,
|
||||
loadingProgress: 100,
|
||||
disableEdit: false
|
||||
}))
|
||||
: [],
|
||||
imageUrl: char.image_path || '',
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
updatedAt: Date.now(),
|
||||
fromDraft: false
|
||||
fromDraft: false,
|
||||
isChangeRole: false
|
||||
};
|
||||
|
||||
return roleEntity;
|
||||
@ -159,9 +159,9 @@ export class RoleEditUseCase {
|
||||
tags: [], // 相似角色接口可能不返回标签,暂时为空
|
||||
imageUrl: char.avatar || '',
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
updatedAt: Date.now(),
|
||||
fromDraft: false
|
||||
fromDraft: false,
|
||||
isChangeRole: false
|
||||
};
|
||||
|
||||
return roleEntity;
|
||||
@ -249,13 +249,12 @@ export class RoleEditUseCase {
|
||||
name: highlight,
|
||||
content: highlight,
|
||||
loadingProgress: 100,
|
||||
disableEdit: false
|
||||
})), // 将高亮关键词转换为TagValueObject格式
|
||||
imageUrl: characterData.image_url || '',
|
||||
loadingProgress: 100,
|
||||
disableEdit: false,
|
||||
updatedAt: Date.now(),
|
||||
fromDraft: false
|
||||
fromDraft: false,
|
||||
isChangeRole: true
|
||||
};
|
||||
return roleEntity;
|
||||
} catch (error) {
|
||||
@ -315,9 +314,6 @@ export class RoleEditUseCase {
|
||||
*/
|
||||
async optimizeRoleDescription( selectedRole: RoleEntity): Promise<{optimizedDescription: string, keywords: string[]}> {
|
||||
try {
|
||||
// if (!this.selectedRole) {
|
||||
// throw new Error('请先选择角色');
|
||||
// }
|
||||
|
||||
// 调用新的AI优化角色描述API
|
||||
const response = await generateCharacterDescription({
|
||||
@ -346,49 +342,6 @@ export class RoleEditUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(),
|
||||
fromDraft: false
|
||||
};
|
||||
|
||||
return mockRole;
|
||||
} catch (error) {
|
||||
console.error('根据图片获取角色失败:', error);
|
||||
throw new Error('根据图片获取角色失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 保存重新生成的角色到角色库
|
||||
* @param roleData 角色实体数据
|
||||
@ -451,4 +404,56 @@ export class RoleEditUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分析图片并更新角色信息
|
||||
* @param imageUrl 图片URL地址
|
||||
* @param selectedRole 当前选中的角色
|
||||
* @returns Promise<RoleEntity> 更新后的角色实体
|
||||
*/
|
||||
async analyzeImageAndUpdateRole(imageUrl: string, selectedRole: RoleEntity): Promise<RoleEntity> {
|
||||
try {
|
||||
// 调用图片分析接口获取描述
|
||||
const result = await analyzeImageDescription({
|
||||
image_url: imageUrl,
|
||||
});
|
||||
|
||||
if (!result.successful) {
|
||||
throw new Error(`图片分析失败: ${result.message}`);
|
||||
}
|
||||
|
||||
const { description, highlights } = result.data;
|
||||
|
||||
// 更新当前选中角色的图片、描述和标签
|
||||
const updatedRole: RoleEntity = {
|
||||
...selectedRole,
|
||||
imageUrl: imageUrl,
|
||||
generateText: description,
|
||||
tags: highlights.map((highlight: string, index: number) => ({
|
||||
id: `tag_${Date.now()}_${index}`,
|
||||
/** 名称 */
|
||||
name: highlight,
|
||||
/** 内容 */
|
||||
content: highlight,
|
||||
loadingProgress: 100,
|
||||
updatedAt: Date.now(),
|
||||
})),
|
||||
};
|
||||
|
||||
// 更新角色列表中的对应角色
|
||||
if (Array.isArray(this.roleList)) {
|
||||
this.roleList = this.roleList.map(role =>
|
||||
role.id === selectedRole.id ? updatedRole : role
|
||||
);
|
||||
}
|
||||
|
||||
// 更新当前选中的角色
|
||||
this.selectedRole = updatedRole;
|
||||
|
||||
return updatedRole;
|
||||
} catch (error) {
|
||||
console.error('分析图片并更新角色失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,85 +0,0 @@
|
||||
|
||||
import { TagValueObject } from '../domain/valueObject';
|
||||
|
||||
/**
|
||||
* 标签编辑用例
|
||||
* 负责标签内容的初始化、修改和优化
|
||||
*/
|
||||
export class TagEditUseCase {
|
||||
constructor(public tagList: TagValueObject[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改标签内容
|
||||
* @param tagName 标签名称
|
||||
* @param newContent 新内容
|
||||
*/
|
||||
async updateTag(tagName: string, newContent: string | number): Promise<void> {
|
||||
const tag = this.tagList.find(tag => tag.name === tagName);
|
||||
if (tag) {
|
||||
tag.content = newContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增标签
|
||||
* @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 = {
|
||||
id: `tag_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
updatedAt: Date.now(),
|
||||
name: tagName,
|
||||
content: content,
|
||||
loadingProgress: 100,
|
||||
disableEdit: false
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user