forked from 77media/video-flow
新增角色替换参数接口,更新应用角色到分镜的请求结构,优化角色视频片段服务逻辑,调整获取角色视频片段列表的接口名称,确保角色替换功能的完整性和可用性。
This commit is contained in:
parent
4cf967de35
commit
349166fe81
@ -19,6 +19,20 @@ import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, C
|
|||||||
import { RoleResponse } from "@/app/service/usecase/RoleEditUseCase";
|
import { RoleResponse } from "@/app/service/usecase/RoleEditUseCase";
|
||||||
import { RoleRecognitionResponse } from "@/app/service/usecase/ShotEditUsecase";
|
import { RoleRecognitionResponse } from "@/app/service/usecase/ShotEditUsecase";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色替换参数接口
|
||||||
|
*/
|
||||||
|
export interface CharacterReplacement {
|
||||||
|
/** 原始角色名称 */
|
||||||
|
original_name: string;
|
||||||
|
/** 原始角色描述 */
|
||||||
|
original_description: string;
|
||||||
|
/** 新角色名称 */
|
||||||
|
new_name: string;
|
||||||
|
/** 新角色描述 */
|
||||||
|
new_description: string;
|
||||||
|
}
|
||||||
|
|
||||||
// API 响应类型
|
// API 响应类型
|
||||||
interface BaseApiResponse<T> {
|
interface BaseApiResponse<T> {
|
||||||
code: number;
|
code: number;
|
||||||
@ -270,12 +284,27 @@ export const regenerateRole = async (request: {
|
|||||||
* @returns Promise<ApiResponse<应用结果>>
|
* @returns Promise<ApiResponse<应用结果>>
|
||||||
*/
|
*/
|
||||||
export const applyRoleToShots = async (request: {
|
export const applyRoleToShots = async (request: {
|
||||||
/** 角色ID */
|
/** 项目ID */
|
||||||
roleId: string;
|
project_id: string;
|
||||||
/** 分镜ID列表 */
|
/** 分镜ID列表 */
|
||||||
shotIds: string[];
|
shot_id: string;
|
||||||
}): Promise<ApiResponse<any>> => {
|
/** 角色替换参数列表 */
|
||||||
return post<ApiResponse<any>>("/movie/apply_role_to_shots", request);
|
character_replacements: CharacterReplacement[];
|
||||||
|
/** 是否等待完成 */
|
||||||
|
wait_for_completion?: boolean;
|
||||||
|
}): Promise<ApiResponse<{
|
||||||
|
/** 应用成功的分镜数量 */
|
||||||
|
success_count: number;
|
||||||
|
/** 应用失败的分镜数量 */
|
||||||
|
failed_count: number;
|
||||||
|
/** 应用结果详情 */
|
||||||
|
results: Array<{
|
||||||
|
shot_id: string;
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
}>;
|
||||||
|
}>> => {
|
||||||
|
return post("/movie/apply_role_to_shots", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -919,14 +948,14 @@ export const batchUpdateVideoSegments = async (request: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取角色在项目中的场景列表接口
|
* 获取角色在项目中的视频片段列表接口
|
||||||
* @param request - 获取角色场景请求参数
|
* @param request - 获取角色视频片段请求参数
|
||||||
* @returns Promise<ApiResponse<角色场景列表>>
|
* @returns Promise<ApiResponse<角色视频片段列表>>
|
||||||
*/
|
*/
|
||||||
export const getCharacterScenes = async (request: {
|
export const getCharacterShots = async (request: {
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
project_id: string;
|
project_id: string;
|
||||||
/** 角色名称或ID */
|
/** 角色名称 */
|
||||||
character_name: string;
|
character_name: string;
|
||||||
}): Promise<ApiResponse<{
|
}): Promise<ApiResponse<{
|
||||||
/** 角色名称 */
|
/** 角色名称 */
|
||||||
@ -939,9 +968,12 @@ export const getCharacterScenes = async (request: {
|
|||||||
video_id: string;
|
video_id: string;
|
||||||
/** 视频URL列表 */
|
/** 视频URL列表 */
|
||||||
video_urls: string[];
|
video_urls: string[];
|
||||||
|
|
||||||
}>;
|
}>;
|
||||||
/** 总数量 */
|
/** 总数量 */
|
||||||
total_count: number;
|
total_count: number;
|
||||||
}>> => {
|
}>> => {
|
||||||
return post("/character/get_character_scenes", request);
|
return post("/character/get_character_scenes", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -262,13 +262,15 @@ export const useRoleServiceHook = (): UseRoleService => {
|
|||||||
*/
|
*/
|
||||||
const fetchUserRoleLibrary = useCallback(async () => {
|
const fetchUserRoleLibrary = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
let useCase = roleEditUseCase;
|
||||||
|
|
||||||
// 如果没有初始化RoleEditUseCase,创建一个新的实例
|
// 如果没有初始化RoleEditUseCase,创建一个新的实例
|
||||||
if (!roleEditUseCase) {
|
if (!useCase) {
|
||||||
const newRoleEditUseCase = new RoleEditUseCase();
|
useCase = new RoleEditUseCase();
|
||||||
setRoleEditUseCase(newRoleEditUseCase);
|
setRoleEditUseCase(useCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleLibraryList = await roleEditUseCase!.getRoleLibraryList();
|
const roleLibraryList = await useCase!.getRoleLibraryList();
|
||||||
setUserRoleLibrary(roleLibraryList);
|
setUserRoleLibrary(roleLibraryList);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户角色库失败:', error);
|
console.error('获取用户角色库失败:', error);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { VideoSegmentEntity } from '../domain/Entities';
|
import { RoleEntity, VideoSegmentEntity } from '../domain/Entities';
|
||||||
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
import { RoleEditUseCase } from '../usecase/RoleEditUseCase';
|
||||||
import { getCharacterScenes } from '@/api/video_flow';
|
import { getCharacterShots, applyRoleToShots, CharacterReplacement } from '@/api/video_flow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扩展的视频片段实体,包含选择状态
|
* 扩展的视频片段实体,包含选择状态
|
||||||
@ -26,6 +26,8 @@ interface UseRoleShotService {
|
|||||||
selectedVideoSegmentCount: number;
|
selectedVideoSegmentCount: number;
|
||||||
/** 当前选中的角色ID */
|
/** 当前选中的角色ID */
|
||||||
selectedRoleId: string | null;
|
selectedRoleId: string | null;
|
||||||
|
/** 是否正在替换中 */
|
||||||
|
isReplacing: boolean;
|
||||||
// 操作方法
|
// 操作方法
|
||||||
/** 获取角色出现的分镜列表 */
|
/** 获取角色出现的分镜列表 */
|
||||||
fetchRoleShots: (roleId: string) => Promise<void>;
|
fetchRoleShots: (roleId: string) => Promise<void>;
|
||||||
@ -34,7 +36,7 @@ interface UseRoleShotService {
|
|||||||
/** 选择/取消选择单个分镜 */
|
/** 选择/取消选择单个分镜 */
|
||||||
toggleShotSelection: (shotId: string) => void;
|
toggleShotSelection: (shotId: string) => void;
|
||||||
/** 应用角色到选中的分镜 */
|
/** 应用角色到选中的分镜 */
|
||||||
applyRoleToSelectedShots: (roleId: string) => Promise<void>;
|
applyRoleToSelectedShots: (newRole: RoleEntity) => Promise<void>;
|
||||||
/** 清空选择列表 */
|
/** 清空选择列表 */
|
||||||
clearShotSelection: () => void;
|
clearShotSelection: () => void;
|
||||||
}
|
}
|
||||||
@ -43,13 +45,12 @@ interface UseRoleShotService {
|
|||||||
* 角色视频片段服务Hook
|
* 角色视频片段服务Hook
|
||||||
* 提供角色与视频片段交互的所有响应式功能和业务逻辑
|
* 提供角色与视频片段交互的所有响应式功能和业务逻辑
|
||||||
*/
|
*/
|
||||||
export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => {
|
export const useRoleShotServiceHook = (projectId: string,selectRole:RoleEntity): UseRoleShotService => {
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const [shotSelectionList, setShotSelectionList] = useState<ExtendedVideoSegmentEntity[]>([]);
|
const [shotSelectionList, setShotSelectionList] = useState<ExtendedVideoSegmentEntity[]>([]);
|
||||||
const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);
|
const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);
|
||||||
|
const [selectedRole, setSelectedRole] = useState<RoleEntity | null>(selectRole);
|
||||||
// UseCase实例
|
const [isReplacing, setIsReplacing] = useState<boolean>(false); // 全局替换状态
|
||||||
const [roleEditUseCase, setRoleEditUseCase] = useState<RoleEditUseCase | null>(null);
|
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
/**
|
/**
|
||||||
@ -71,16 +72,16 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService =>
|
|||||||
/**
|
/**
|
||||||
* 获取角色出现的分镜列表
|
* 获取角色出现的分镜列表
|
||||||
* @description 获取当前角色应用到的分镜列表,包括已应用状态
|
* @description 获取当前角色应用到的分镜列表,包括已应用状态
|
||||||
* @param roleId 角色ID
|
* @param roleName 角色名称
|
||||||
* @throws {Error} 当API调用失败时抛出错误
|
* @throws {Error} 当API调用失败时抛出错误
|
||||||
* @returns {Promise<void>} 获取完成后的Promise
|
* @returns {Promise<void>} 获取完成后的Promise
|
||||||
*/
|
*/
|
||||||
const fetchRoleShots = useCallback(async (roleId: string) => {
|
const fetchRoleShots = useCallback(async (roleName: string) => {
|
||||||
try {
|
try {
|
||||||
// 调用新的真实接口获取角色在项目中的场景列表
|
// 调用新的真实接口获取角色在项目中的场景列表
|
||||||
const response = await getCharacterScenes({
|
const response = await getCharacterShots({
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
character_name: roleId
|
character_name: roleName
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
@ -92,7 +93,7 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService =>
|
|||||||
name: `视频片段_${scene.video_id}`,
|
name: `视频片段_${scene.video_id}`,
|
||||||
sketchUrl: "",
|
sketchUrl: "",
|
||||||
videoUrl: scene.video_urls, // 保持为string[]类型
|
videoUrl: scene.video_urls, // 保持为string[]类型
|
||||||
status: 1, // 默认为已完成状态
|
status:scene.video_urls.length>0?1:0, // 默认为已完成状态
|
||||||
lens: [],
|
lens: [],
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
loadingProgress: 100,
|
loadingProgress: 100,
|
||||||
@ -102,11 +103,7 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService =>
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
setShotSelectionList(extendedShots);
|
setShotSelectionList(extendedShots);
|
||||||
setSelectedRoleId(roleId);
|
setSelectedRoleId(roleName);
|
||||||
|
|
||||||
// 初始化角色编辑UseCase实例
|
|
||||||
const newRoleEditUseCase = new RoleEditUseCase();
|
|
||||||
setRoleEditUseCase(newRoleEditUseCase);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`获取角色场景列表失败: ${response.message}`);
|
throw new Error(`获取角色场景列表失败: ${response.message}`);
|
||||||
}
|
}
|
||||||
@ -145,39 +142,77 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService =>
|
|||||||
/**
|
/**
|
||||||
* 应用角色到选中的分镜
|
* 应用角色到选中的分镜
|
||||||
* @description 将当前角色应用到选中的分镜,并更新应用状态
|
* @description 将当前角色应用到选中的分镜,并更新应用状态
|
||||||
* @param roleId 角色ID
|
|
||||||
* @throws {Error} 当未选择分镜或UseCase未初始化时抛出错误
|
* @throws {Error} 当未选择分镜或UseCase未初始化时抛出错误
|
||||||
* @returns {Promise<void>} 应用完成后的Promise
|
* @returns {Promise<void>} 应用完成后的Promise
|
||||||
*/
|
*/
|
||||||
const applyRoleToSelectedShots = useCallback(async (roleId: string) => {
|
const applyRoleToSelectedShots = useCallback(async (newRole: RoleEntity) => {
|
||||||
if (!roleEditUseCase) {
|
|
||||||
throw new Error('角色编辑UseCase未初始化');
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedShotIds = shotSelectionList
|
|
||||||
.filter(shot => shot.selected)
|
|
||||||
.map(shot => shot.id);
|
|
||||||
|
|
||||||
if (selectedShotIds.length === 0) {
|
|
||||||
throw new Error('请先选择要应用的分镜');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await roleEditUseCase.applyRole(selectedShotIds, roleId);
|
// 检查是否有选中的分镜
|
||||||
|
const selectedShots = shotSelectionList.filter(shot => shot.selected);
|
||||||
|
if (selectedShots.length === 0) {
|
||||||
|
throw new Error('请先选择要应用角色的分镜');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有选中的角色
|
||||||
|
if (!selectedRole||!selectedRole.generateText ) {
|
||||||
|
throw new Error('请先选择要应用的角色');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否正在替换中
|
||||||
|
if (isReplacing) {
|
||||||
|
throw new Error('正在替换中,请等待完成后再试');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置全局替换状态为true
|
||||||
|
setIsReplacing(true);
|
||||||
|
|
||||||
|
// 构建角色替换参数
|
||||||
|
const characterReplacements: CharacterReplacement[] = [
|
||||||
|
{
|
||||||
|
original_name: selectedRole.name,
|
||||||
|
original_description: selectedRole.generateText,
|
||||||
|
new_name: newRole.name,
|
||||||
|
new_description: newRole.generateText
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 循环调用接口,为每个选中的分镜单独调用
|
||||||
|
selectedShots.forEach(async (shot) => {
|
||||||
|
try {
|
||||||
|
// 调用应用角色到分镜接口(不等待完成)
|
||||||
|
applyRoleToShots({
|
||||||
|
project_id: projectId,
|
||||||
|
shot_id: shot.id, // 单个分镜ID
|
||||||
|
character_replacements: characterReplacements,
|
||||||
|
wait_for_completion: false // 不等待完成,异步处理
|
||||||
|
}).then(response => {
|
||||||
|
if (response.successful) {
|
||||||
|
console.log(`分镜 ${shot.id} 角色替换成功:`, response.data);
|
||||||
|
} else {
|
||||||
|
console.error(`分镜 ${shot.id} 角色替换失败:`, response.message);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(`分镜 ${shot.id} 角色替换异常:`, error);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`分镜 ${shot.id} 角色替换失败:`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`开始为 ${selectedShots.length} 个分镜替换角色`);
|
||||||
|
|
||||||
|
// 延迟重置替换状态,给用户一些反馈时间
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsReplacing(false);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
// 更新应用状态
|
|
||||||
setShotSelectionList(prev =>
|
|
||||||
prev.map(shot =>
|
|
||||||
selectedShotIds.includes(shot.id)
|
|
||||||
? { ...shot, applied: true, selected: false }
|
|
||||||
: shot
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('应用角色到分镜失败:', error);
|
console.error('应用角色到分镜失败:', error);
|
||||||
|
// 出错时也要重置替换状态
|
||||||
|
setIsReplacing(false);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}, [roleEditUseCase, shotSelectionList]);
|
}, [selectedRole, shotSelectionList, projectId, isReplacing]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空选择列表
|
* 清空选择列表
|
||||||
@ -195,6 +230,7 @@ export const useRoleShotServiceHook = (projectId: string): UseRoleShotService =>
|
|||||||
isAllVideoSegmentSelected,
|
isAllVideoSegmentSelected,
|
||||||
selectedVideoSegmentCount,
|
selectedVideoSegmentCount,
|
||||||
selectedRoleId,
|
selectedRoleId,
|
||||||
|
isReplacing,
|
||||||
|
|
||||||
// 操作方法
|
// 操作方法
|
||||||
fetchRoleShots,
|
fetchRoleShots,
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-chara
|
|||||||
import { CharacterLibrarySelector } from './character-library-selector';
|
import { CharacterLibrarySelector } from './character-library-selector';
|
||||||
import HorizontalScroller from './HorizontalScroller';
|
import HorizontalScroller from './HorizontalScroller';
|
||||||
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
||||||
|
import { useRoleShotServiceHook } from '@/app/service/Interaction/RoleShotService';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
interface Appearance {
|
interface Appearance {
|
||||||
hairStyle: string;
|
hairStyle: string;
|
||||||
@ -84,11 +86,16 @@ export function CharacterTabContent({
|
|||||||
updateRoleText,
|
updateRoleText,
|
||||||
regenerateRole
|
regenerateRole
|
||||||
} = useEditData('role');
|
} = useEditData('role');
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const episodeId = searchParams.get('episodeId');
|
||||||
|
|
||||||
|
const {fetchRoleShots} = useRoleShotServiceHook(episodeId as string)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('-==========roleData===========-', roleData);
|
console.log('-==========roleData===========-', roleData);
|
||||||
if (roleData.length > 0) {
|
if (roleData.length > 0) {
|
||||||
selectRole(roleData[selectRoleIndex].id);
|
selectRole(roleData[selectRoleIndex].id);
|
||||||
|
|
||||||
}
|
}
|
||||||
}, [selectRoleIndex, roleData]);
|
}, [selectRoleIndex, roleData]);
|
||||||
|
|
||||||
@ -114,8 +121,9 @@ export function CharacterTabContent({
|
|||||||
// 替换角色
|
// 替换角色
|
||||||
|
|
||||||
setIsReplacePanelOpen(true);
|
setIsReplacePanelOpen(true);
|
||||||
|
fetchRoleShots("President Alfred King");
|
||||||
};
|
};
|
||||||
|
// President Alfred King Samuel Ryan
|
||||||
const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
|
const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
|
||||||
// 处理替换确认逻辑
|
// 处理替换确认逻辑
|
||||||
console.log('Selected shots:', selectedShots);
|
console.log('Selected shots:', selectedShots);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user