This commit is contained in:
海龙 2025-08-12 17:39:32 +08:00
commit 55a61424e8
5 changed files with 73 additions and 30 deletions

View File

@ -57,7 +57,7 @@ export const useEditData = (tabType: string) => {
toggleShotSelection, toggleShotSelection,
applyRoleToSelectedShots, applyRoleToSelectedShots,
clearShotSelection clearShotSelection
} = useRoleShotServiceHook(projectId); } = useRoleShotServiceHook(projectId, selectedRole);
useEffect(() => { useEffect(() => {
if (tabType === 'shot') { if (tabType === 'shot') {
@ -69,7 +69,6 @@ export const useEditData = (tabType: string) => {
setLoading(false); setLoading(false);
}); });
} else if (tabType === 'role') { } else if (tabType === 'role') {
// fetchUserRoleLibrary();
fetchRoleList(projectId).then(() => { fetchRoleList(projectId).then(() => {
setLoading(false); setLoading(false);
}).catch((err) => { }).catch((err) => {
@ -105,6 +104,7 @@ export const useEditData = (tabType: string) => {
optimizeRoleText, optimizeRoleText,
updateRoleText, updateRoleText,
regenerateRole, regenerateRole,
fetchUserRoleLibrary,
// role shot // role shot
shotSelectionList, shotSelectionList,
selectedRoleId, selectedRoleId,

View File

@ -3,6 +3,7 @@ import { ImageWave } from '@/components/ui/ImageWave';
import { RoleEntity } from '@/app/service/domain/Entities'; import { RoleEntity } from '@/app/service/domain/Entities';
interface CharacterLibrarySelectorProps { interface CharacterLibrarySelectorProps {
isLoading: boolean;
isReplaceLibraryOpen: boolean; isReplaceLibraryOpen: boolean;
setIsReplaceLibraryOpen: (open: boolean) => void; setIsReplaceLibraryOpen: (open: boolean) => void;
onSelect: (index: number) => void; onSelect: (index: number) => void;
@ -11,13 +12,28 @@ interface CharacterLibrarySelectorProps {
} }
export function CharacterLibrarySelector({ export function CharacterLibrarySelector({
isLoading,
isReplaceLibraryOpen, isReplaceLibraryOpen,
setIsReplaceLibraryOpen, setIsReplaceLibraryOpen,
onSelect, onSelect,
userRoleLibrary = [] userRoleLibrary = []
}: CharacterLibrarySelectorProps) { }: CharacterLibrarySelectorProps) {
// 将 RoleEntity[] 转换为图片URL数组
const imageUrls = userRoleLibrary.map(role => role.imageUrl); if (isLoading) {
return (
<FloatingGlassPanel
open={isReplaceLibraryOpen}
width='90vw'
panel_style={{ background: 'unset', border: 'unset', backdropFilter: 'unset', boxShadow: 'none' }}
onClose={() => setIsReplaceLibraryOpen(false)}
>
<div className="flex flex-col items-center justify-center w-full h-64 text-white/50">
<div className="w-12 h-12 mb-4 animate-spin rounded-full border-b-2 border-blue-600" />
<p>Loading...</p>
</div>
</FloatingGlassPanel>
);
}
// 如果没有数据,显示空状态 // 如果没有数据,显示空状态
if (userRoleLibrary.length === 0) { if (userRoleLibrary.length === 0) {
@ -44,7 +60,7 @@ export function CharacterLibrarySelector({
> >
{/* 内容 */} {/* 内容 */}
<ImageWave <ImageWave
images={imageUrls} images={userRoleLibrary.map(role => role.imageUrl)}
containerWidth="90vw" containerWidth="90vw"
containerHeight="calc(var(--index) * 15)" containerHeight="calc(var(--index) * 15)"
itemWidth="calc(var(--index) * 2)" itemWidth="calc(var(--index) * 2)"

View File

@ -10,6 +10,7 @@ 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 { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { RoleEntity } from '@/app/service/domain/Entities';
interface Appearance { interface Appearance {
hairStyle: string; hairStyle: string;
@ -75,6 +76,8 @@ export function CharacterTabContent({
const characterEditorRef = useRef<any>(null); const characterEditorRef = useRef<any>(null);
const [isInitialized, setIsInitialized] = useState(false); const [isInitialized, setIsInitialized] = useState(false);
const [isRegenerate, setIsRegenerate] = useState(false); const [isRegenerate, setIsRegenerate] = useState(false);
const [isLoadingShots, setIsLoadingShots] = useState(false);
const [isLoadingLibrary, setIsLoadingLibrary] = useState(false);
const { const {
loading, loading,
@ -85,16 +88,11 @@ export function CharacterTabContent({
optimizeRoleText, optimizeRoleText,
updateRoleText, updateRoleText,
regenerateRole, regenerateRole,
fetchUserRoleLibrary,
// role shot // role shot
shotSelectionList, shotSelectionList,
selectedRoleId,
isAllVideoSegmentSelected,
selectedVideoSegmentCount,
fetchRoleShots, fetchRoleShots,
toggleSelectAllShots, applyRoleToSelectedShots
toggleShotSelection,
applyRoleToSelectedShots,
clearShotSelection
} = useEditData('role'); } = useEditData('role');
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId'); const episodeId = searchParams.get('episodeId');
@ -113,16 +111,22 @@ export function CharacterTabContent({
console.log('获取选中项数据', selectedRole); console.log('获取选中项数据', selectedRole);
}, [selectedRole]); }, [selectedRole]);
useEffect(() => {
console.log('获取角色库数据', userRoleLibrary);
}, [userRoleLibrary]);
const handleSmartPolish = (text: string) => { const handleSmartPolish = (text: string) => {
// 然后调用优化角色文本 // 然后调用优化角色文本
optimizeRoleText(text); optimizeRoleText(text);
}; };
const handleStartReplaceCharacter = () => { const handleStartReplaceCharacter = async () => {
// 获取当前角色对应的视频片段 setIsLoadingShots(true);
fetchRoleShots(selectedRole?.id || '');
// 打开替换角色面板
setIsReplacePanelOpen(true); setIsReplacePanelOpen(true);
// 获取当前角色对应的视频片段
await fetchRoleShots(selectedRole?.name || '');
// 打开替换角色面板
setIsLoadingShots(false);
}; };
const handleConfirmGotoReplace = () => { const handleConfirmGotoReplace = () => {
@ -146,6 +150,7 @@ export function CharacterTabContent({
// 处理替换确认逻辑 // 处理替换确认逻辑
console.log('Selected shots:', selectedShots); console.log('Selected shots:', selectedShots);
console.log('Add to library:', addToLibrary); console.log('Add to library:', addToLibrary);
applyRoleToSelectedShots(selectedRole || {} as RoleEntity);
setIsReplacePanelOpen(false); setIsReplacePanelOpen(false);
}; };
@ -185,9 +190,12 @@ export function CharacterTabContent({
} }
}; };
const handleOpenReplaceLibrary = () => { const handleOpenReplaceLibrary = async () => {
setIsLoadingLibrary(true);
setIsReplaceLibraryOpen(true); setIsReplaceLibraryOpen(true);
setShowAddToLibrary(true); setShowAddToLibrary(true);
await fetchUserRoleLibrary();
setIsLoadingLibrary(false);
}; };
const handleRegenerate = async () => { const handleRegenerate = async () => {
@ -200,6 +208,7 @@ export function CharacterTabContent({
// 然后调用重新生成角色 // 然后调用重新生成角色
await regenerateRole(); await regenerateRole();
setIsRegenerate(false); setIsRegenerate(false);
handleStartReplaceCharacter();
}; };
const handleUploadClick = () => { const handleUploadClick = () => {
@ -384,8 +393,9 @@ export function CharacterTabContent({
onClose={() => handleCloseReplacePanel()} onClose={() => handleCloseReplacePanel()}
> >
<ReplaceCharacterPanel <ReplaceCharacterPanel
isLoading={isLoadingShots}
shots={shotSelectionList} shots={shotSelectionList}
character={selectedRole} role={selectedRole}
showAddToLibrary={showAddToLibrary} showAddToLibrary={showAddToLibrary}
onClose={() => handleCloseReplacePanel()} onClose={() => handleCloseReplacePanel()}
onConfirm={handleConfirmReplace} onConfirm={handleConfirmReplace}
@ -394,6 +404,7 @@ export function CharacterTabContent({
{/* 从角色库中选择角色 */} {/* 从角色库中选择角色 */}
<CharacterLibrarySelector <CharacterLibrarySelector
isLoading={isLoadingLibrary}
isReplaceLibraryOpen={isReplaceLibraryOpen} isReplaceLibraryOpen={isReplaceLibraryOpen}
setIsReplaceLibraryOpen={setIsReplaceLibraryOpen} setIsReplaceLibraryOpen={setIsReplaceLibraryOpen}
onSelect={handleSelectCharacter} onSelect={handleSelectCharacter}

View File

@ -1,9 +1,11 @@
import { ReplacePanel } from './replace-panel'; import { ReplacePanel } from './replace-panel';
import { Shot, Character } from '@/app/model/types'; import { Shot, Character } from '@/app/model/types';
import { useEffect, useState } from 'react';
interface ReplaceCharacterPanelProps { interface ReplaceCharacterPanelProps {
isLoading: boolean;
shots: any[]; shots: any[];
character: Character; role: any;
showAddToLibrary?: boolean; showAddToLibrary?: boolean;
onClose: () => void; onClose: () => void;
onConfirm: (selectedShots: string[], addToLibrary: boolean) => void; onConfirm: (selectedShots: string[], addToLibrary: boolean) => void;
@ -40,21 +42,24 @@ export const mockShots: Shot[] = [
]; ];
export function ReplaceCharacterPanel({ export function ReplaceCharacterPanel({
shots = [], isLoading,
character, shots,
role,
showAddToLibrary = true, showAddToLibrary = true,
onClose, onClose,
onConfirm, onConfirm,
}: ReplaceCharacterPanelProps) { }: ReplaceCharacterPanelProps) {
return ( return (
<ReplacePanel <ReplacePanel
title="替换新形象" title="替换新形象"
shots={shots} shots={shots}
item={character} item={role}
showAddToLibrary={showAddToLibrary} showAddToLibrary={showAddToLibrary}
addToLibraryText="新形象同步添加至角色库" addToLibraryText="新形象同步添加至角色库"
onClose={onClose} onClose={onClose}
onConfirm={onConfirm} onConfirm={onConfirm}
isLoading={isLoading}
/> />
); );
} }

View File

@ -4,6 +4,7 @@ import { Check, X, CircleAlert, ArrowLeft, ArrowRight } from 'lucide-react';
import { cn } from '@/public/lib/utils'; import { cn } from '@/public/lib/utils';
interface ReplacePanelProps { interface ReplacePanelProps {
isLoading: boolean;
title: string; title: string;
shots: any[]; shots: any[];
item: any; item: any;
@ -14,6 +15,7 @@ interface ReplacePanelProps {
} }
export function ReplacePanel({ export function ReplacePanel({
isLoading,
title, title,
shots, shots,
item, item,
@ -56,11 +58,11 @@ export function ReplacePanel({
}, []); }, []);
const handleShotToggle = (shotId: string) => { const handleShotToggle = (shotId: string) => {
setSelectedShots(prev => // setSelectedShots(prev =>
prev.includes(shotId) // prev.includes(shotId)
? prev.filter(id => id !== shotId) // ? prev.filter(id => id !== shotId)
: [...prev, shotId] // : [...prev, shotId]
); // );
}; };
const handleSelectAllShots = (checked: boolean) => { const handleSelectAllShots = (checked: boolean) => {
@ -104,6 +106,15 @@ export function ReplacePanel({
}); });
}; };
if (isLoading) {
return (
<div className="flex flex-col items-center justify-center w-full max-w-5xl text-white/50">
<div className="w-12 h-12 mb-4 animate-spin rounded-full border-b-2 border-blue-600" />
<p>Loading...</p>
</div>
)
}
return ( return (
<div className="space-y-2 w-full max-w-5xl"> <div className="space-y-2 w-full max-w-5xl">
{/* 标题 */} {/* 标题 */}
@ -115,7 +126,7 @@ export function ReplacePanel({
<CircleAlert className="w-4 h-4" /> <CircleAlert className="w-4 h-4" />
<span className="text-blue-500">{shots.length}</span> <span className="text-blue-500">{shots.length}</span>
</div> </div>
<div className="flex items-center gap-2"> {/* <div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
checked={selectedShots.length === shots.length} checked={selectedShots.length === shots.length}
@ -123,12 +134,12 @@ export function ReplacePanel({
className="w-4 h-4 rounded border-white/20" className="w-4 h-4 rounded border-white/20"
/> />
<label className="text-white/80"></label> <label className="text-white/80"></label>
</div> </div> */}
</div> </div>
{/* 分镜展示区 */} {/* 分镜展示区 */}
<div className="space-y-2 relative"> <div className="space-y-2 relative">
<div className="text-white/80 text-sm"></div> {/* <div className="text-white/80 text-sm">选择需要替换的分镜:</div> */}
<div className="relative flex gap-4 overflow-x-auto pb-4 hide-scrollbar h-64" ref={shotsRef}> <div className="relative flex gap-4 overflow-x-auto pb-4 hide-scrollbar h-64" ref={shotsRef}>
{shots.map((shot) => ( {shots.map((shot) => (
<motion.div <motion.div