新增角色替换参数接口,更新应用角色到分镜的请求结构,优化角色视频片段服务逻辑,调整获取角色视频片段列表的接口名称,确保角色替换功能的完整性和可用性。

This commit is contained in:
海龙 2025-08-12 16:41:13 +08:00
parent 4cf967de35
commit 349166fe81
4 changed files with 134 additions and 56 deletions

View File

@ -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);
}; };

View File

@ -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);

View File

@ -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,

View File

@ -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);