微调样式

This commit is contained in:
北枳 2025-07-19 23:12:06 +08:00
parent f135938d59
commit f8722ba8ed
5 changed files with 181 additions and 195 deletions

View File

@ -7,6 +7,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
import React from 'react';
import Link from 'next/link';
import { signInWithGoogle, loginUser } from '@/lib/auth';
import { GradientText } from '@/components/ui/gradient-text';
export default function Login() {
const [isLoaded, setIsLoaded] = useState(false);
@ -73,7 +74,13 @@ export default function Login() {
<div className="main-container login-page relative">
{/* logo Movie Flow */}
<div className='login-logo'>
<span className="logo-heart">MovieFlow</span>
<span className="logo-heart">
<GradientText
text="MovieFlow"
startPercentage={30}
endPercentage={70}
/>
</span>
</div>
<div className="left-panel">

View File

@ -497,7 +497,7 @@ export function MediaViewer({
<video
ref={mainVideoRef}
key={taskVideos[currentSketchIndex].url}
className="w-full h-full rounded-lg object-cover object-center"
className="w-full h-full rounded-lg object-cover object-center relative z-10"
src={taskVideos[currentSketchIndex].url}
autoPlay={isVideoPlaying}
loop={true}
@ -524,14 +524,6 @@ export function MediaViewer({
) : (
/* 生成完成后直接显示视频不使用ProgressiveReveal */
<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>
{/* 视频 修复播放没有声音 */}
<video

View File

@ -1,7 +1,7 @@
'use client';
import React, { useState, useEffect, useMemo, useRef } from 'react';
import { motion } from 'framer-motion';
import { motion, AnimatePresence } from 'framer-motion';
import { ScriptModal } from '@/components/ui/script-modal';
import {
Image,
@ -10,9 +10,12 @@ import {
Music,
Loader2,
User,
Scissors,
Tv,
Airplay
Airplay,
Heart,
Camera,
Film,
Scissors
} from 'lucide-react';
interface TaskInfoProps {
@ -24,30 +27,125 @@ interface TaskInfoProps {
}
// 根据加载文本返回对应的图标
const getStageIcon = (loadingText: string) => {
const text = loadingText.toLowerCase();
if (text.includes('sketch') || text.includes('草图')) {
return Image;
} else if (text.includes('video') || text.includes('视频')) {
return Video;
} else if (text.includes('character') || text.includes('角色')) {
return User;
} else if (text.includes('audio') || text.includes('音频')) {
return Music;
} else if (text.includes('post') || text.includes('后期')) {
const getStageIcon = (currentStage: number) => {
if (currentStage === 0) {
return Heart;
} else if (currentStage === 1) {
return Camera;
} else if (currentStage === 2) {
return Film;
} else if (currentStage === 3) {
return Scissors;
} else if (text.includes('final') || text.includes('最终')) {
return Tv;
} else if (text.includes('complete') || text.includes('完成')) {
return CheckCircle;
} else {
return Loader2;
}
};
const stageIconMap = {
0: {
icon: Heart,
color: '#8b5cf6'
},
1: {
icon: Camera,
color: '#06b6d4'
},
2: {
icon: Film,
color: '#10b981'
},
3: {
icon: Scissors,
color: '#f59e0b'
}
}
const TAG_COLORS = ['#FF5733', '#126821', '#8d3913', '#FF33A1', '#A133FF', '#FF3333', '#3333FF', '#A1A1A1', '#a1115e', '#30527f'];
// 阶段图标组件
const StageIcons = ({ currentStage, isExpanded }: { currentStage: number, isExpanded: boolean }) => {
// 根据当前阶段重新排序图标
const orderedStages = useMemo(() => {
const stages = Object.entries(stageIconMap).map(([stage, data]) => ({
stage: parseInt(stage),
...data
}));
// 将当前阶段移到第一位
return stages.sort((a, b) => {
if (a.stage === currentStage) return -1;
if (b.stage === currentStage) return 1;
return a.stage - b.stage;
});
}, [currentStage]);
return (
<motion.div
className="relative flex items-center"
>
<AnimatePresence mode="popLayout">
{orderedStages.map((stage, index) => {
const isCurrentStage = stage.stage === currentStage;
const Icon = stage.icon;
// 只显示当前阶段或展开状态
if (!isExpanded && !isCurrentStage) return null;
return (
<motion.div
key={stage.stage}
className="relative"
initial={isExpanded ? {
opacity: 0,
x: -20,
scale: 0.5
} : {}}
animate={{
opacity: 1,
x: 0,
scale: 1,
transition: {
type: "spring",
stiffness: 300,
damping: 25,
delay: index * 0.1
}
}}
exit={{
opacity: 0,
x: 20,
scale: 0.5,
transition: { duration: 0.2 }
}}
style={{
marginLeft: index > 0 ? '8px' : '0px',
zIndex: isCurrentStage ? 2 : 1
}}
>
<motion.div
className={`relative rounded-full p-1 ${isCurrentStage ? 'bg-opacity-20' : 'bg-opacity-10'}`}
animate={isCurrentStage ? {
rotate: [0, 360],
scale: [1, 1.1, 1],
transition: {
rotate: { duration: 3, repeat: Infinity, ease: "linear" },
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
}
} : {}}
>
<Icon
className="w-5 h-5"
style={{ color: stage.color }}
/>
</motion.div>
</motion.div>
);
})}
</AnimatePresence>
</motion.div>
);
};
export function TaskInfo({
isLoading,
taskObject,
@ -55,12 +153,16 @@ export function TaskInfo({
dataLoadError,
roles
}: TaskInfoProps) {
const StageIcon = getStageIcon(currentLoadingText);
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
const [currentStage, setCurrentStage] = useState(0);
const [isShowScriptIcon, setIsShowScriptIcon] = useState(true);
const [isStageIconsExpanded, setIsStageIconsExpanded] = useState(false);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const stageColor = useMemo(() => {
return stageIconMap[currentStage as keyof typeof stageIconMap].color;
}, [currentStage]);
// 监听 currentLoadingText
useEffect(() => {
// 清理之前的定时器
@ -126,21 +228,21 @@ export function TaskInfo({
setIsScriptModalOpen(true);
setCurrentStage(1);
}
if (currentLoadingText.includes('character')) {
console.log('isScriptModalOpen-character', currentLoadingText, isScriptModalOpen);
if (isScriptModalOpen) {
setIsScriptModalOpen(false);
// if (currentLoadingText.includes('character')) {
// console.log('isScriptModalOpen-character', currentLoadingText, isScriptModalOpen);
// if (isScriptModalOpen) {
// setIsScriptModalOpen(false);
// 延迟8s 再次打开
timerRef.current = setTimeout(() => {
setIsScriptModalOpen(true);
setCurrentStage(0);
}, 8000);
} else {
setIsScriptModalOpen(true);
setCurrentStage(0);
}
}
// // 延迟8s 再次打开
// timerRef.current = setTimeout(() => {
// setIsScriptModalOpen(true);
// setCurrentStage(0);
// }, 8000);
// } else {
// setIsScriptModalOpen(true);
// setCurrentStage(0);
// }
// }
if (currentLoadingText.includes('initializing')) {
console.log('isScriptModalOpen-initializing', currentLoadingText, isScriptModalOpen);
setIsScriptModalOpen(true);
@ -172,110 +274,6 @@ export function TaskInfo({
// }
// }, [taskObject?.title]);
if (isLoading) {
return (
<>
<motion.div
className="title-JtMejk text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
{taskObject?.title || 'loading project info...'}
</motion.div>
{/* 加载状态显示 */}
<motion.div
className="flex items-center gap-2 justify-center mt-2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2
}}
/>
{/* 阶段图标 */}
<motion.div
className="flex items-center gap-2"
key={currentLoadingText}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="text-blue-500"
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1]
}}
transition={{
rotate: { duration: 2, repeat: Infinity, ease: "linear" },
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
}}
>
<StageIcon className="w-5 h-5" />
</motion.div>
<motion.span
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "300% 300%",
}}
>
{currentLoadingText}
</motion.span>
</motion.div>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.3
}}
/>
</motion.div>
{/* 错误提示 */}
{dataLoadError && (
<motion.div
className="mt-3 text-orange-600 text-sm text-center flex items-center justify-center gap-2"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<span className="w-2 h-2 bg-orange-500 rounded-full animate-pulse"></span>
{dataLoadError}
</motion.div>
)}
</>
);
}
return (
<>
<div className="title-JtMejk flex items-center justify-center gap-2">
@ -284,17 +282,6 @@ export function TaskInfo({
<span>
{taskObject?.title || 'loading project info...'}
</span>
{isShowScriptIcon && (
<div className="flex items-center gap-2">
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setIsScriptModalOpen(true)}
>
<Airplay className="w-4 h-4 text-blue-500 cursor-pointer" />
</motion.div>
</div>
)}
</>
) : 'loading project info...'}
</div>
@ -336,13 +323,15 @@ export function TaskInfo({
</motion.div>
) : (
<motion.div
className="flex items-center gap-2 justify-center"
className="flex items-center gap-2 justify-center cursor-pointer"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
onClick={() => setIsScriptModalOpen(true)}
>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
@ -360,21 +349,12 @@ export function TaskInfo({
key={currentLoadingText}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
exit={{ opacity: 0.5, x: 10 }}
transition={{ duration: 0.3 }}
onMouseEnter={() => setIsStageIconsExpanded(true)}
onMouseLeave={() => setIsStageIconsExpanded(false)}
>
<motion.div
className="text-blue-500"
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1]
}}
transition={{
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
}}
>
<StageIcon className="w-5 h-5" />
</motion.div>
<StageIcons currentStage={currentStage} isExpanded={isStageIconsExpanded} />
<motion.div
className="relative"
@ -448,24 +428,23 @@ export function TaskInfo({
{/* 文字底部装饰线 */}
<motion.div
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-blue-500 via-cyan-400 to-purple-500"
className="absolute bottom-0 left-0 h-0.5"
style={{
background: `linear-gradient(to right, ${stageColor}, rgb(34 211 238), rgb(168 85 247))`,
}}
animate={{
width: ["0%", "100%", "0%"],
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
width: ["0%", "100%", "0%"]
}}
transition={{
width: { duration: 2, repeat: Infinity, ease: "easeInOut" },
backgroundPosition: { duration: 1.5, repeat: Infinity, ease: "linear" }
}}
style={{
backgroundSize: "200% 200%",
width: { duration: 2, repeat: Infinity, ease: "easeInOut" }
}}
/>
</motion.div>
</motion.div>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
@ -478,7 +457,8 @@ export function TaskInfo({
}}
/>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]

View File

@ -58,6 +58,7 @@ export function useWorkflowData() {
const [taskShotSketch, setTaskShotSketch] = useState<any[]>([]);
const [taskVideos, setTaskVideos] = useState<any[]>([]);
const [sketchCount, setSketchCount] = useState(0);
const [videoCount, setVideoCount] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [currentStep, setCurrentStep] = useState('0');
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
@ -107,6 +108,16 @@ export function useWorkflowData() {
handleAutoPlay();
}, [sketchCount, totalSketchCount, isGeneratingSketch, autoPlaySketch]);
useEffect(() => {
console.log('sketchCount 已更新:', sketchCount);
setCurrentSketchIndex(sketchCount - 1);
}, [sketchCount]);
useEffect(() => {
console.log('videoCount 已更新:', videoCount);
setCurrentSketchIndex(videoCount - 1);
}, [videoCount]);
// 添加手动播放控制
const handleManualPlay = useCallback(async () => {
if (!isGeneratingSketch && taskSketch.length > 0) {
@ -149,7 +160,6 @@ export function useWorkflowData() {
setTaskSketch(sketchList);
setSketchCount(sketchList.length);
setIsGeneratingSketch(true);
setCurrentSketchIndex(sketchList.length - 1);
loadingText = LOADING_TEXT_MAP.sketch(sketchList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
@ -201,7 +211,6 @@ export function useWorkflowData() {
setTaskShotSketch(sketchList);
setSketchCount(sketchList.length);
setIsGeneratingSketch(true);
setCurrentSketchIndex(sketchList.length - 1);
loadingText = LOADING_TEXT_MAP.shotSketch(sketchList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
@ -220,20 +229,20 @@ export function useWorkflowData() {
if (task.task_name === 'generate_videos' && task.task_result) {
const realTaskResultData = task.task_result.data.filter((item: any) => item.urls && item.urls.length > 0);
if (realTaskResultData.length >= 0 && taskVideos.length !== realTaskResultData.length) {
console.log('----------正在生成视频中-发生变化才触发');
console.log('----------正在生成视频中-发生变化才触发', taskVideos.length);
// 正在生成视频中 替换视频数据
const videoList = [];
for (const video of realTaskResultData) {
// 每一项 video 有多个视频 先默认取第一个
videoList.push({
url: video.urls[0],
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
script: video.description,
audio: null,
});
}
setTaskVideos(videoList);
setVideoCount(videoList.length);
setIsGeneratingVideo(true);
setCurrentSketchIndex(videoList.length - 1);
loadingText = LOADING_TEXT_MAP.video(videoList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
@ -370,7 +379,6 @@ export function useWorkflowData() {
// 设置为最后一个草图
if (data.sketch.total_count > data.sketch.data.length) {
setIsGeneratingSketch(true);
setCurrentSketchIndex(data.sketch.data.length - 1);
loadingText = LOADING_TEXT_MAP.sketch(data.sketch.data.length, data.sketch.total_count);
} else {
finalStep = '2';
@ -415,7 +423,6 @@ export function useWorkflowData() {
// 设置为最后一个草图
if (data.shot_sketch.total_count > data.shot_sketch.data.length) {
setIsGeneratingSketch(true);
setCurrentSketchIndex(data.shot_sketch.data.length - 1);
loadingText = LOADING_TEXT_MAP.shotSketch(data.shot_sketch.data.length, data.shot_sketch.total_count);
} else {
finalStep = '3';
@ -439,10 +446,10 @@ export function useWorkflowData() {
});
}
setTaskVideos(videoList);
setVideoCount(videoList.length);
// 如果在视频步骤,设置为最后一个视频
if (data.video.total_count > realDataVideoData.length) {
setIsGeneratingVideo(true);
setCurrentSketchIndex(realDataVideoData.length - 1);
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
} else {
finalStep = '4';

View File

@ -1,4 +1,4 @@
@host = https://77.smartvideo.py.qikongjian.com
@host = https://pre.movieflow.api.huiying.video
### Create a movie project
POST http://localhost:8000/movie/create_movie_project
Content-Type: application/json
@ -19,7 +19,7 @@ Content-Type: application/json
}
### Get movie project detail
POST https://77.smartvideo.py.qikongjian.com/movie/get_movie_project_detail
POST https://pre.movieflow.api.huiying.video/movie/get_movie_project_detail
Content-Type: application/json
{
@ -43,7 +43,7 @@ Content-Type: application/json
}
### Get status
POST https://77.smartvideo.py.qikongjian.com/movie/get_status
POST https://pre.movieflow.api.huiying.video/movie/get_status
Content-Type: application/json
{