forked from 77media/video-flow
更新工作流组件,添加当前阶段状态管理,优化视频生成状态显示逻辑,确保在不同阶段下正确渲染缩略图和视频内容。
This commit is contained in:
parent
cafaf5102a
commit
25988af2c3
@ -54,7 +54,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
applyScript,
|
applyScript,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText
|
originalText,
|
||||||
|
currentStage
|
||||||
} = useWorkflowData();
|
} = useWorkflowData();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -184,6 +185,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }}>
|
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<MediaViewer
|
<MediaViewer
|
||||||
|
currentStage={currentStage}
|
||||||
scriptData={scriptData}
|
scriptData={scriptData}
|
||||||
currentStep={currentStep}
|
currentStep={currentStep}
|
||||||
currentSketchIndex={currentSketchIndex}
|
currentSketchIndex={currentSketchIndex}
|
||||||
@ -209,23 +211,27 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="imageGrid-ymZV9z hide-scrollbar" style={{ display: (currentStep === '6' || currentStep === '0') ? 'none' : 'block' }}>
|
{currentStage !== 'final_video' && currentStage !== 'script' && (
|
||||||
<ErrorBoundary>
|
<div className="imageGrid-ymZV9z hide-scrollbar">
|
||||||
<ThumbnailGrid
|
<ErrorBoundary>
|
||||||
isLoading={isLoading}
|
<ThumbnailGrid
|
||||||
isPlaying={isPlaying}
|
currentStage={currentStage}
|
||||||
currentStep={currentStep}
|
isLoading={isLoading}
|
||||||
currentSketchIndex={currentSketchIndex}
|
isPlaying={isPlaying}
|
||||||
taskSketch={taskSketch}
|
currentStep={currentStep}
|
||||||
taskVideos={taskVideos}
|
currentSketchIndex={currentSketchIndex}
|
||||||
isGeneratingSketch={isGeneratingSketch}
|
taskSketch={taskSketch}
|
||||||
isGeneratingVideo={isGeneratingVideo}
|
taskVideos={taskVideos}
|
||||||
sketchCount={sketchCount}
|
isGeneratingSketch={isGeneratingSketch}
|
||||||
totalSketchCount={totalSketchCount}
|
isGeneratingVideo={isGeneratingVideo}
|
||||||
onSketchSelect={setCurrentSketchIndex}
|
sketchCount={sketchCount}
|
||||||
/>
|
totalSketchCount={totalSketchCount}
|
||||||
</ErrorBoundary>
|
onSketchSelect={setCurrentSketchIndex}
|
||||||
</div>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -233,7 +239,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
|
|
||||||
{/* 暂停/播放按钮 */}
|
{/* 暂停/播放按钮 */}
|
||||||
{
|
{
|
||||||
(currentStep !== '6' && currentStep !== '0') && (
|
(currentStage !== 'final_video' && currentStage !== 'script') && (
|
||||||
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
|
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={isPauseWorkFlow ? Play : Pause}
|
icon={isPauseWorkFlow ? Play : Pause}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useRef, useEffect, useState, SetStateAction, useMemo } from 'react';
|
import React, { useRef, useEffect, useState, SetStateAction, useMemo } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize } from 'lucide-react';
|
import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X } from 'lucide-react';
|
||||||
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
|
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
|
||||||
import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
||||||
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
||||||
@ -30,6 +30,7 @@ interface MediaViewerProps {
|
|||||||
isPauseWorkFlow: boolean;
|
isPauseWorkFlow: boolean;
|
||||||
applyScript: any;
|
applyScript: any;
|
||||||
mode: string;
|
mode: string;
|
||||||
|
currentStage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaViewer = React.memo(function MediaViewer({
|
export const MediaViewer = React.memo(function MediaViewer({
|
||||||
@ -52,7 +53,8 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
isPauseWorkFlow,
|
isPauseWorkFlow,
|
||||||
applyScript,
|
applyScript,
|
||||||
mode
|
mode,
|
||||||
|
currentStage
|
||||||
}: MediaViewerProps) {
|
}: MediaViewerProps) {
|
||||||
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
@ -470,110 +472,56 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
onMouseLeave={() => onControlsChange(false)}
|
onMouseLeave={() => onControlsChange(false)}
|
||||||
>
|
>
|
||||||
{/* 只在生成过程中或没有视频时使用ProgressiveReveal */}
|
{/* 只在生成过程中或没有视频时使用ProgressiveReveal */}
|
||||||
{(isGeneratingVideo || !taskVideos[currentSketchIndex]) ? (
|
<div className="relative w-full h-full">
|
||||||
taskVideos[currentSketchIndex] ? (
|
{/* 背景模糊的图片 */}
|
||||||
<ProgressiveReveal
|
<div className="absolute inset-0 overflow-hidden" style={{background: `url(${taskSketch[currentSketchIndex]?.url}) no-repeat center center`}}>
|
||||||
key={`generte-video-${currentSketchIndex}`}
|
{/* 生成中 */}
|
||||||
className="w-full h-full rounded-lg"
|
{taskVideos[currentSketchIndex].video_status === 0 && (
|
||||||
revealDuration={0.8}
|
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
|
||||||
blurDuration={0.3}
|
<div className="text-blue-500 text-2xl font-bold flex items-center gap-2">
|
||||||
initialBlur={10}
|
<Loader2 className="w-10 h-10 animate-spin" />
|
||||||
customVariants={{
|
<span>Generating...</span>
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
scale: 0.9,
|
|
||||||
filter: "blur(30px)",
|
|
||||||
clipPath: "inset(0 100% 0 0)"
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
scale: 1,
|
|
||||||
filter: "blur(0px)",
|
|
||||||
clipPath: "inset(0 0% 0 0)",
|
|
||||||
transition: {
|
|
||||||
duration: 1.5,
|
|
||||||
ease: [0.23, 1, 0.32, 1],
|
|
||||||
opacity: { duration: 0.8, ease: "easeOut" },
|
|
||||||
scale: { duration: 1.2, ease: "easeOut" },
|
|
||||||
filter: { duration: 0.8, delay: 0.4, ease: "easeOut" },
|
|
||||||
clipPath: { duration: 1, ease: "easeInOut" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
loadingBgConfig={{
|
|
||||||
fromColor: `from-[${bgColors[0]}]`,
|
|
||||||
viaColor: `via-[${bgColors[1]}]`,
|
|
||||||
toColor: `to-[${bgColors[2]}]`,
|
|
||||||
glowOpacity: 0.8,
|
|
||||||
duration: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="relative w-full h-full">
|
|
||||||
{/* 背景模糊的图片 */}
|
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
|
||||||
<img
|
|
||||||
className="w-full h-full object-cover filter blur-lg scale-110 opacity-50"
|
|
||||||
src={taskSketch[currentSketchIndex]?.url}
|
|
||||||
alt="background"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 视频 */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
|
||||||
animate={{ clipPath: "inset(0 0% 0 0)" }}
|
|
||||||
transition={{ duration: 0.8, ease: [0.43, 0.13, 0.23, 0.96] }}
|
|
||||||
className="relative z-10 w-full h-full"
|
|
||||||
>
|
|
||||||
<video
|
|
||||||
ref={mainVideoRef}
|
|
||||||
key={taskVideos[currentSketchIndex].url}
|
|
||||||
className="w-full h-full rounded-lg object-cover object-center relative z-10"
|
|
||||||
src={taskVideos[currentSketchIndex].url}
|
|
||||||
autoPlay={isVideoPlaying}
|
|
||||||
loop={true}
|
|
||||||
playsInline
|
|
||||||
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
|
|
||||||
onEnded={() => {
|
|
||||||
if (isVideoPlaying) {
|
|
||||||
// 自动切换到下一个视频的逻辑在父组件处理
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</ProgressiveReveal>
|
)}
|
||||||
) : (
|
{/* 生成失败 */}
|
||||||
<div className="w-full h-full flex items-center justify-center rounded-lg overflow-hidden relative">
|
{taskVideos[currentSketchIndex].video_status === 2 && (
|
||||||
<img
|
<div className="absolute inset-0 bg-red-500 flex items-center justify-center">
|
||||||
className="absolute inset-0 w-full h-full object-cover"
|
<div className="text-red-500 text-2xl font-bold flex items-center gap-2">
|
||||||
src={taskSketch[currentSketchIndex]?.url}
|
<X className="w-10 h-10" />
|
||||||
alt={`Sketch ${currentSketchIndex + 1}`}
|
<span>Failed</span>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
) : (
|
|
||||||
/* 生成完成后直接显示视频,不使用ProgressiveReveal */
|
|
||||||
<div className="relative w-full h-full">
|
|
||||||
|
|
||||||
{/* 视频 修复播放没有声音 */}
|
|
||||||
<video
|
|
||||||
ref={mainVideoRef}
|
|
||||||
key={taskVideos[currentSketchIndex].url}
|
|
||||||
className="w-full h-full rounded-lg object-cover object-center relative z-10"
|
|
||||||
src={taskVideos[currentSketchIndex].url}
|
|
||||||
autoPlay={isVideoPlaying}
|
|
||||||
loop={true}
|
|
||||||
playsInline
|
|
||||||
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
|
|
||||||
onEnded={() => {
|
|
||||||
if (isVideoPlaying) {
|
|
||||||
// 自动切换到下一个视频的逻辑在父组件处理
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{/* 视频 多个 取第一个 */}
|
||||||
|
{ taskVideos[currentSketchIndex].url && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
||||||
|
animate={{ clipPath: "inset(0 0% 0 0)" }}
|
||||||
|
transition={{ duration: 0.8, ease: [0.43, 0.13, 0.23, 0.96] }}
|
||||||
|
className="relative z-10 w-full h-full"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
ref={mainVideoRef}
|
||||||
|
key={taskVideos[currentSketchIndex].url[0]}
|
||||||
|
className="w-full h-full rounded-lg object-cover object-center relative z-10"
|
||||||
|
src={taskVideos[currentSketchIndex].url[0]}
|
||||||
|
autoPlay={isVideoPlaying}
|
||||||
|
loop={true}
|
||||||
|
playsInline
|
||||||
|
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
|
||||||
|
onEnded={() => {
|
||||||
|
if (isVideoPlaying) {
|
||||||
|
// 自动切换到下一个视频的逻辑在父组件处理
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 操作按钮组 */}
|
{/* 操作按钮组 */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@ -610,21 +558,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
className="relative"
|
className="relative"
|
||||||
>
|
>
|
||||||
{/* 播放时的发光效果 */}
|
|
||||||
{isVideoPlaying && (
|
|
||||||
<motion.div
|
|
||||||
className="absolute inset-0 rounded-full blur-md"
|
|
||||||
animate={{
|
|
||||||
scale: [1, 1.2, 1],
|
|
||||||
opacity: [0.5, 0.8, 0.5]
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: 2,
|
|
||||||
repeat: Infinity,
|
|
||||||
ease: "easeInOut"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={isVideoPlaying ? Pause : Play}
|
icon={isVideoPlaying ? Pause : Play}
|
||||||
tooltip={isVideoPlaying ? "Pause video" : "Play video"}
|
tooltip={isVideoPlaying ? "Pause video" : "Play video"}
|
||||||
@ -857,16 +790,15 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 根据当前步骤渲染对应内容
|
// 根据当前步骤渲染对应内容
|
||||||
if (Number(currentStep) === 6 || Number(currentStep) === 5.5) {
|
if (currentStage === 'final_video') {
|
||||||
console.log('1111111111111111', 1111111111111111)
|
|
||||||
return renderFinalVideo(currentStep);
|
return renderFinalVideo(currentStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(currentStep) > 2 && Number(currentStep) < 6) {
|
if (currentStage === 'video') {
|
||||||
return renderVideoContent();
|
return renderVideoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(currentStep) === 0) {
|
if (currentStage === 'script') {
|
||||||
return renderScriptContent();
|
return renderScriptContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -168,11 +168,11 @@ export function TaskInfo({
|
|||||||
setCurrentStage(2);
|
setCurrentStage(2);
|
||||||
|
|
||||||
// 延迟8s 再次打开
|
// 延迟8s 再次打开
|
||||||
timerRef.current = setTimeout(() => {
|
// timerRef.current = setTimeout(() => {
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
}, 8000);
|
// }, 8000);
|
||||||
} else {
|
} else {
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
setCurrentStage(2);
|
setCurrentStage(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,7 +180,7 @@ export function TaskInfo({
|
|||||||
if (isScriptModalOpen) {
|
if (isScriptModalOpen) {
|
||||||
setIsScriptModalOpen(false);
|
setIsScriptModalOpen(false);
|
||||||
}
|
}
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
setCurrentStage(2);
|
setCurrentStage(2);
|
||||||
}
|
}
|
||||||
if (currentLoadingText.includes('sketch')) {
|
if (currentLoadingText.includes('sketch')) {
|
||||||
@ -190,11 +190,11 @@ export function TaskInfo({
|
|||||||
setCurrentStage(1);
|
setCurrentStage(1);
|
||||||
|
|
||||||
// 延迟8s 再次打开
|
// 延迟8s 再次打开
|
||||||
timerRef.current = setTimeout(() => {
|
// timerRef.current = setTimeout(() => {
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
}, 8000);
|
// }, 8000);
|
||||||
} else {
|
} else {
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
setCurrentStage(1);
|
setCurrentStage(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ export function TaskInfo({
|
|||||||
if (isScriptModalOpen) {
|
if (isScriptModalOpen) {
|
||||||
setIsScriptModalOpen(false);
|
setIsScriptModalOpen(false);
|
||||||
}
|
}
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
setCurrentStage(1);
|
setCurrentStage(1);
|
||||||
}
|
}
|
||||||
if (currentLoadingText.includes('script')) {
|
if (currentLoadingText.includes('script')) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, { useRef, useEffect, useState } from 'react';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
|
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
|
||||||
|
import { Loader2, X } from 'lucide-react';
|
||||||
|
|
||||||
interface ThumbnailGridProps {
|
interface ThumbnailGridProps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -17,6 +18,7 @@ interface ThumbnailGridProps {
|
|||||||
sketchCount: number;
|
sketchCount: number;
|
||||||
totalSketchCount: number;
|
totalSketchCount: number;
|
||||||
onSketchSelect: (index: number) => void;
|
onSketchSelect: (index: number) => void;
|
||||||
|
currentStage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ThumbnailGrid({
|
export function ThumbnailGrid({
|
||||||
@ -30,7 +32,8 @@ export function ThumbnailGrid({
|
|||||||
isGeneratingVideo,
|
isGeneratingVideo,
|
||||||
sketchCount,
|
sketchCount,
|
||||||
totalSketchCount,
|
totalSketchCount,
|
||||||
onSketchSelect
|
onSketchSelect,
|
||||||
|
currentStage
|
||||||
}: ThumbnailGridProps) {
|
}: ThumbnailGridProps) {
|
||||||
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
@ -211,78 +214,47 @@ export function ThumbnailGrid({
|
|||||||
${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`}
|
${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`}
|
||||||
onClick={() => !isDragging && onSketchSelect(index)}
|
onClick={() => !isDragging && onSketchSelect(index)}
|
||||||
>
|
>
|
||||||
{/* 底层草图,始终显示 未生成对应的视频时显示的草图模糊掉 */}
|
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
|
||||||
<img
|
|
||||||
className={`w-full h-full object-cover transition-all duration-300 ${
|
|
||||||
(!taskSketch[index] && !isPlaying) ? 'filter blur-sm opacity-60' : ''
|
|
||||||
}`}
|
|
||||||
src={taskSketch[index] ? taskSketch[index].url : video.url}
|
|
||||||
alt={`Thumbnail ${index + 1}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 视频层,只在有视频时用ProgressiveReveal动画显示 */}
|
{/* 视频层 */}
|
||||||
{taskVideos[index] && (
|
<div className="relative w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
<div className="absolute inset-0">
|
{taskVideos[index].video_status === 0 && (
|
||||||
{isGeneratingVideo ? (
|
<div className="absolute inset-0 bg-black/50 flex items-center justify-center z-20">
|
||||||
<ProgressiveReveal
|
<div className="text-blue-500 text-xl font-bold flex items-center gap-2">
|
||||||
key={`video-thumbnail-generating-${index}`}
|
<Loader2 className="w-10 h-10 animate-spin" />
|
||||||
revealDuration={0.8}
|
<span>Generating...</span>
|
||||||
blurDuration={0.3}
|
|
||||||
initialBlur={10}
|
|
||||||
delay={index === currentSketchIndex ? 0 : index * 0.1}
|
|
||||||
customVariants={{
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
scale: 0.95,
|
|
||||||
filter: "blur(10px)"
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
scale: 1,
|
|
||||||
filter: "blur(0px)",
|
|
||||||
transition: {
|
|
||||||
duration: 0.8,
|
|
||||||
ease: [0.23, 1, 0.32, 1],
|
|
||||||
opacity: { duration: 0.6, ease: "easeInOut" },
|
|
||||||
scale: { duration: 1, ease: "easeOut" },
|
|
||||||
filter: { duration: 0.8, ease: "easeOut", delay: 0.2 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
loadingBgConfig={{
|
|
||||||
fromColor: `from-[${bgColors[0]}]`,
|
|
||||||
viaColor: `via-[${bgColors[1]}]`,
|
|
||||||
toColor: `to-[${bgColors[2]}]`,
|
|
||||||
glowOpacity: 0.4,
|
|
||||||
duration: 4
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
|
||||||
<video
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
src={taskVideos[index].url}
|
|
||||||
playsInline
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ProgressiveReveal>
|
|
||||||
) : (
|
|
||||||
/* 生成完成后直接显示视频,不使用ProgressiveReveal */
|
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
|
||||||
<video
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
src={taskVideos[index].url}
|
|
||||||
playsInline
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{taskVideos[index].video_status === 2 && (
|
||||||
|
<div className="absolute inset-0 bg-red-500 flex items-center justify-center z-20">
|
||||||
|
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
|
||||||
|
<X className="w-10 h-10" />
|
||||||
|
<span>Failed</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{taskVideos[index].url ? (
|
||||||
|
<video
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
src={taskVideos[index].url[0]}
|
||||||
|
playsInline
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
|
<img
|
||||||
|
className={`w-full h-full object-cover transition-all duration-300 ${
|
||||||
|
(!taskSketch[index] && !isPlaying) ? 'filter blur-sm opacity-60' : ''
|
||||||
|
}`}
|
||||||
|
src={taskSketch[index] ? taskSketch[index].url : video.url}
|
||||||
|
alt={`Thumbnail ${index + 1}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent z-10">
|
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent z-10">
|
||||||
<span className="text-xs text-white/90">Scene {index + 1}</span>
|
<span className="text-xs text-white/90">Scene {index + 1}</span>
|
||||||
@ -381,7 +353,7 @@ export function ThumbnailGrid({
|
|||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
>
|
>
|
||||||
{Number(currentStep) > 2 && taskVideos.length > 0 && Number(currentStep) < 6
|
{currentStage === 'video'
|
||||||
? renderVideoThumbnails()
|
? renderVideoThumbnails()
|
||||||
: renderSketchThumbnails()
|
: renderSketchThumbnails()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,19 +82,25 @@ export function useWorkflowData() {
|
|||||||
const [needStreamData, setNeedStreamData] = useState(false);
|
const [needStreamData, setNeedStreamData] = useState(false);
|
||||||
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
||||||
const [mode, setMode] = useState<'automatic' | 'manual' | 'auto'>('automatic');
|
const [mode, setMode] = useState<'automatic' | 'manual' | 'auto'>('automatic');
|
||||||
|
const [currentStage, setCurrentStage] = useState<'script' | 'character' | 'sketch' | 'shot_sketch' | 'video' | 'final_video'>('script');
|
||||||
|
|
||||||
let taskData: any = {
|
let taskData: any = {
|
||||||
sketch: { data: [], total_count: -1 },
|
sketch: { data: [], total_count: -1 },
|
||||||
character: { data: [], total_count: -1 },
|
character: { data: [], total_count: -1 },
|
||||||
shot_sketch: { data: [], total_count: -1 },
|
shot_sketch: { data: [], total_count: -1 },
|
||||||
video: { data: [], total_count: -1 },
|
video: { data: [], total_count: -1 },
|
||||||
status: '0'
|
status: '0',
|
||||||
|
currentStage: 'script'
|
||||||
};
|
};
|
||||||
let loadingText: any = LOADING_TEXT_MAP.initializing;
|
let loadingText: any = LOADING_TEXT_MAP.initializing;
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
|
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('---------currentStage', currentStage);
|
||||||
|
}, [currentStage]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
scriptBlocksMemo, // 渲染剧本数据
|
scriptBlocksMemo, // 渲染剧本数据
|
||||||
initializeFromProject,
|
initializeFromProject,
|
||||||
@ -233,6 +239,7 @@ export function useWorkflowData() {
|
|||||||
final?: any;
|
final?: any;
|
||||||
needStreamData?: boolean;
|
needStreamData?: boolean;
|
||||||
totalSketchCount?: number;
|
totalSketchCount?: number;
|
||||||
|
currentStage?: string;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
for (const task of all_task_data) {
|
for (const task of all_task_data) {
|
||||||
@ -241,6 +248,7 @@ export function useWorkflowData() {
|
|||||||
taskData.status = '1';
|
taskData.status = '1';
|
||||||
const realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
|
const realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
|
||||||
if (realSketchResultData.length >= 0) {
|
if (realSketchResultData.length >= 0) {
|
||||||
|
taskData.currentStage = 'sketch';
|
||||||
// 正在生成草图中 替换 sketch 数据
|
// 正在生成草图中 替换 sketch 数据
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
for (const sketch of realSketchResultData) {
|
for (const sketch of realSketchResultData) {
|
||||||
@ -270,6 +278,7 @@ export function useWorkflowData() {
|
|||||||
|
|
||||||
if (task.task_name === 'generate_character' && (task.task_status !== 'COMPLETED' || taskData.character.total_count !== taskData.character.data.length)) {
|
if (task.task_name === 'generate_character' && (task.task_status !== 'COMPLETED' || taskData.character.total_count !== taskData.character.data.length)) {
|
||||||
if (task.task_result.data.length >= 0 && roles.length !== task.task_result.data.length) {
|
if (task.task_result.data.length >= 0 && roles.length !== task.task_result.data.length) {
|
||||||
|
taskData.currentStage = 'character';
|
||||||
// 正在生成角色中 替换角色数据
|
// 正在生成角色中 替换角色数据
|
||||||
const characterList = [];
|
const characterList = [];
|
||||||
for (const character of task.task_result.data) {
|
for (const character of task.task_result.data) {
|
||||||
@ -299,6 +308,7 @@ export function useWorkflowData() {
|
|||||||
if (task.task_name === 'generate_shot_sketch' && (task.task_status !== 'COMPLETED' || taskData.shot_sketch.total_count !== taskData.shot_sketch.data.length)) {
|
if (task.task_name === 'generate_shot_sketch' && (task.task_status !== 'COMPLETED' || taskData.shot_sketch.total_count !== taskData.shot_sketch.data.length)) {
|
||||||
const realShotResultData = task.task_result.data.filter((item: any) => item.url);
|
const realShotResultData = task.task_result.data.filter((item: any) => item.url);
|
||||||
if (realShotResultData.length >= 0) {
|
if (realShotResultData.length >= 0) {
|
||||||
|
taskData.currentStage = 'shot_sketch';
|
||||||
taskData.status = '1';
|
taskData.status = '1';
|
||||||
console.log('----------正在生成草图中 替换 sketch 数据', taskShotSketch.length, realShotResultData.length);
|
console.log('----------正在生成草图中 替换 sketch 数据', taskShotSketch.length, realShotResultData.length);
|
||||||
// 正在生成草图中 替换 sketch 数据
|
// 正在生成草图中 替换 sketch 数据
|
||||||
@ -331,24 +341,32 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (task.task_name === 'generate_videos' && (task.task_status !== 'COMPLETED' || taskData.video.total_count !== taskData.video.data.length)) {
|
if (task.task_name === 'generate_videos' && (task.task_status !== 'COMPLETED' || taskData.video.total_count !== taskData.video.data.length)) {
|
||||||
const realTaskResultData = task.task_result.data.filter((item: any) => item.urls && item.urls.length > 0);
|
if (task.task_result.data) {
|
||||||
if (realTaskResultData.length >= 0) {
|
const realTaskResultData = task.task_result.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
|
||||||
console.log('----------正在生成视频中', realTaskResultData.length);
|
|
||||||
// 正在生成视频中 替换视频数据
|
// 正在生成视频中 替换视频数据
|
||||||
const videoList = [];
|
const videoList = [];
|
||||||
for (const video of realTaskResultData) {
|
let video_status = 0;
|
||||||
|
for (const video of task.task_result.data) {
|
||||||
|
// 适配旧数据
|
||||||
|
video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
|
||||||
|
video_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
|
||||||
// 每一项 video 有多个视频 先默认取第一个
|
// 每一项 video 有多个视频 先默认取第一个
|
||||||
videoList.push({
|
videoList.push({
|
||||||
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
|
url: video.urls,
|
||||||
script: video.description,
|
script: video.description,
|
||||||
audio: null,
|
audio: null,
|
||||||
video_id: video.video_id,
|
video_id: video.video_id,
|
||||||
|
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (realTaskResultData.length > 0) {
|
||||||
|
taskData.currentStage = 'video';
|
||||||
|
}
|
||||||
|
console.log('----------正在生成视频中', realTaskResultData.length);
|
||||||
taskData.video.data = videoList;
|
taskData.video.data = videoList;
|
||||||
stateUpdates.taskVideos = videoList;
|
stateUpdates.taskVideos = videoList;
|
||||||
stateUpdates.isGeneratingVideo = true;
|
stateUpdates.isGeneratingVideo = true;
|
||||||
loadingText = LOADING_TEXT_MAP.video(videoList.length, task.task_result.total_count);
|
loadingText = LOADING_TEXT_MAP.video(realTaskResultData.length, task.task_result.total_count);
|
||||||
}
|
}
|
||||||
if (task.task_status === 'COMPLETED') {
|
if (task.task_status === 'COMPLETED') {
|
||||||
console.log('----------视频生成完成');
|
console.log('----------视频生成完成');
|
||||||
@ -368,6 +386,7 @@ export function useWorkflowData() {
|
|||||||
// 粗剪
|
// 粗剪
|
||||||
if (task.task_name === 'generate_final_simple_video') {
|
if (task.task_name === 'generate_final_simple_video') {
|
||||||
if (task.task_result && task.task_result.video) {
|
if (task.task_result && task.task_result.video) {
|
||||||
|
taskData.currentStage = 'final_video';
|
||||||
stateUpdates.final = {
|
stateUpdates.final = {
|
||||||
url: task.task_result.video,
|
url: task.task_result.video,
|
||||||
};
|
};
|
||||||
@ -379,6 +398,7 @@ export function useWorkflowData() {
|
|||||||
// 最终剪辑
|
// 最终剪辑
|
||||||
if (task.task_name === 'generate_final_video') {
|
if (task.task_name === 'generate_final_video') {
|
||||||
if (task.task_result && task.task_result.video) {
|
if (task.task_result && task.task_result.video) {
|
||||||
|
taskData.currentStage = 'final_video';
|
||||||
stateUpdates.final = {
|
stateUpdates.final = {
|
||||||
url: task.task_result.video,
|
url: task.task_result.video,
|
||||||
};
|
};
|
||||||
@ -415,6 +435,7 @@ export function useWorkflowData() {
|
|||||||
if (stateUpdates.taskSketch) updateSketchCount(stateUpdates.taskSketch.length);
|
if (stateUpdates.taskSketch) updateSketchCount(stateUpdates.taskSketch.length);
|
||||||
if (stateUpdates.taskVideos) updateVideoCount(stateUpdates.taskVideos.length);
|
if (stateUpdates.taskVideos) updateVideoCount(stateUpdates.taskVideos.length);
|
||||||
|
|
||||||
|
if (stateUpdates.currentStage) taskData.currentStage = stateUpdates.currentStage;
|
||||||
// 更新 taskObject
|
// 更新 taskObject
|
||||||
if (stateUpdates.currentStep) {
|
if (stateUpdates.currentStep) {
|
||||||
setTaskObject(prev => {
|
setTaskObject(prev => {
|
||||||
@ -500,6 +521,7 @@ export function useWorkflowData() {
|
|||||||
// 如果有已完成的数据,同步到状态
|
// 如果有已完成的数据,同步到状态
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.sketch && data.sketch.data) {
|
if (data.sketch && data.sketch.data) {
|
||||||
|
taskData.currentStage = 'sketch';
|
||||||
taskData.status = '1';
|
taskData.status = '1';
|
||||||
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
|
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
@ -526,6 +548,7 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.character && data.character.data && data.character.data.length > 0) {
|
if (data.character && data.character.data && data.character.data.length > 0) {
|
||||||
|
taskData.currentStage = 'character';
|
||||||
const characterList = [];
|
const characterList = [];
|
||||||
for (const character of data.character.data) {
|
for (const character of data.character.data) {
|
||||||
characterList.push({
|
characterList.push({
|
||||||
@ -549,6 +572,7 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.shot_sketch && data.shot_sketch.data) {
|
if (data.shot_sketch && data.shot_sketch.data) {
|
||||||
|
taskData.currentStage = 'shot_sketch';
|
||||||
const realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
|
const realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
for (const sketch of realShotResultData) {
|
for (const sketch of realShotResultData) {
|
||||||
@ -575,43 +599,46 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.video.data) {
|
if (data.video.data) {
|
||||||
const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0);
|
const realDataVideoData = data.video.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
|
||||||
if (realDataVideoData.length === 0 && taskData.status === '3') {
|
if (realDataVideoData.length === 0 && taskData.status === '3') {
|
||||||
loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count);
|
loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count);
|
||||||
}
|
}
|
||||||
if (realDataVideoData.length > 0) {
|
if (realDataVideoData.length > 0) {
|
||||||
const videoList = [];
|
taskData.currentStage = 'video';
|
||||||
console.log('----------data.video.data', data.video.data);
|
}
|
||||||
for (const video of realDataVideoData) {
|
const videoList = [];
|
||||||
// 每一项 video 有多个视频 默认取存在的项
|
console.log('----------data.video.data', data.video.data);
|
||||||
videoList.push({
|
for (const video of data.video.data) {
|
||||||
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
|
// 每一项 video 有多个视频 默认取存在的项
|
||||||
script: video.description,
|
videoList.push({
|
||||||
audio: null,
|
url: video.urls,
|
||||||
video_id: video.video_id,
|
script: video.description,
|
||||||
});
|
audio: null,
|
||||||
}
|
video_id: video.video_id,
|
||||||
taskData.video.data = videoList;
|
video_status: video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status, // 0 生成中 1 生成完成 2 生成失败
|
||||||
taskData.video.total_count = data.video.total_count;
|
});
|
||||||
setTaskVideos(videoList);
|
}
|
||||||
updateVideoCount(videoList.length);
|
taskData.video.data = videoList;
|
||||||
// 如果在视频步骤,设置为最后一个视频
|
taskData.video.total_count = data.video.total_count;
|
||||||
if (data.video.total_count > realDataVideoData.length) {
|
setTaskVideos(videoList);
|
||||||
setIsGeneratingVideo(true);
|
updateVideoCount(videoList.length);
|
||||||
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
|
// 如果在视频步骤,设置为最后一个视频
|
||||||
} else {
|
if (data.video.total_count > realDataVideoData.length) {
|
||||||
taskData.status = '4';
|
setIsGeneratingVideo(true);
|
||||||
loadingText = LOADING_TEXT_MAP.audio;
|
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
|
||||||
|
} else {
|
||||||
|
taskData.status = '4';
|
||||||
|
loadingText = LOADING_TEXT_MAP.audio;
|
||||||
|
|
||||||
// 暂时没有音频生成 直接跳过
|
// 暂时没有音频生成 直接跳过
|
||||||
taskData.status = '5';
|
taskData.status = '5';
|
||||||
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
|
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 粗剪
|
// 粗剪
|
||||||
if ((data as any).final_simple_video && (data as any).final_simple_video.video) {
|
if ((data as any).final_simple_video && (data as any).final_simple_video.video) {
|
||||||
|
taskData.currentStage = 'final_video';
|
||||||
setFinal({
|
setFinal({
|
||||||
url: (data as any).final_simple_video.video
|
url: (data as any).final_simple_video.video
|
||||||
});
|
});
|
||||||
@ -620,6 +647,7 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.final_video && data.final_video.video) {
|
if (data.final_video && data.final_video.video) {
|
||||||
|
taskData.currentStage = 'final_video';
|
||||||
setFinal({
|
setFinal({
|
||||||
url: data.final_video.video
|
url: data.final_video.video
|
||||||
});
|
});
|
||||||
@ -631,6 +659,7 @@ export function useWorkflowData() {
|
|||||||
console.log('---look-taskData', taskData);
|
console.log('---look-taskData', taskData);
|
||||||
|
|
||||||
// 设置步骤
|
// 设置步骤
|
||||||
|
setCurrentStage(taskData.currentStage);
|
||||||
setCurrentStep(taskData.status);
|
setCurrentStep(taskData.status);
|
||||||
setTaskObject(prev => {
|
setTaskObject(prev => {
|
||||||
if (!prev) return null;
|
if (!prev) return null;
|
||||||
@ -718,6 +747,7 @@ export function useWorkflowData() {
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
applyScript,
|
applyScript,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText
|
originalText,
|
||||||
|
currentStage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user