- [x] 显示 原分镜,剪辑后

- [x] 支持批量下载分镜视频
Chatbox查找消息是否有任务 还在执行中
This commit is contained in:
北枳 2025-09-05 17:24:16 +08:00
parent 254b226c6e
commit 9365896bb6
6 changed files with 80 additions and 18 deletions

View File

@ -267,6 +267,15 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
}
}, [updateMessages]);
// 监听 消息列表中是否存在 pending 状态的消息,有的话 将 loading 置为 true
useEffect(() => {
if (displayMessages.some(msg => msg.status === 'pending')) {
setIsLoading(true);
} else {
setIsLoading(false);
}
}, [displayMessages]);
return [
{
messages: displayMessages,

View File

@ -216,7 +216,7 @@ const WorkFlow = React.memo(function WorkFlow() {
{isLoading ? (
<Skeleton className="w-full aspect-video rounded-lg" />
) : (
<div className={`relative heroVideo-FIzuK1 ${['final_video', 'script'].includes(taskObject.currentStage) ? 'h-[calc(100vh-6rem)] w-[calc((100vh-6rem)/9*16)]' : 'h-[calc(100vh-6rem-200px)] w-[calc((100vh-6rem-200px)/9*16)]'}`} style={{ aspectRatio: "16 / 9" }} key={taskObject.currentStage+'_'+currentSketchIndex}>
<div className={`relative heroVideo-FIzuK1 ${['script'].includes(taskObject.currentStage) ? 'h-[calc(100vh-6rem)] w-[calc((100vh-6rem)/9*16)]' : 'h-[calc(100vh-6rem-200px)] w-[calc((100vh-6rem-200px)/9*16)]'}`} style={{ aspectRatio: "16 / 9" }} key={taskObject.currentStage+'_'+currentSketchIndex}>
<MediaViewer
taskObject={taskObject}
scriptData={scriptData}
@ -242,7 +242,7 @@ const WorkFlow = React.memo(function WorkFlow() {
</div>
)}
</div>
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
{taskObject.currentStage !== 'script' && (
<div className="h-[123px] w-[calc((100vh-6rem-200px)/9*16)]">
<ThumbnailGrid
isDisabledFocus={isEditModalOpen || isPauseWorkFlow || isFocusChatInput}

View File

@ -2,7 +2,7 @@
import React, { useRef, useEffect, useState, SetStateAction, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors, RotateCcw, MessageCircleMore, Download } from 'lucide-react';
import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors, RotateCcw, MessageCircleMore, Download, ArrowDownWideNarrow } from 'lucide-react';
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
import { GlassIconButton } from '@/components/ui/glass-icon-button';
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
@ -10,7 +10,7 @@ import { mockScriptData } from '@/components/script-renderer/mock';
import { Skeleton } from '@/components/ui/skeleton';
import { TaskObject } from '@/api/DTO/movieEdit';
import { Button, Tooltip } from 'antd';
import { downloadVideo } from '@/utils/tools';
import { downloadVideo, downloadAllVideos } from '@/utils/tools';
interface MediaViewerProps {
taskObject: TaskObject;
@ -65,6 +65,7 @@ export const MediaViewer = React.memo(function MediaViewer({
const [userHasInteracted, setUserHasInteracted] = useState(false);
const [toosBtnRight, setToodsBtnRight] = useState('1rem');
const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false);
const [isLoadingDownloadAllVideosBtn, setIsLoadingDownloadAllVideosBtn] = useState(false);
useEffect(() => {
if (isSmartChatBoxOpen) {
@ -364,6 +365,14 @@ export const MediaViewer = React.memo(function MediaViewer({
onClick={() => handleEditClick('3', 'final')}
/>
</Tooltip>
{/* 下载所有视频按钮 */}
<Tooltip placement="top" title="Download all videos">
<GlassIconButton icon={ArrowDownWideNarrow} size='sm' loading={isLoadingDownloadAllVideosBtn} onClick={ async () => {
setIsLoadingDownloadAllVideosBtn(true);
await downloadAllVideos(taskObject.videos.data.flatMap((video: any) => video.urls));
setIsLoadingDownloadAllVideosBtn(false);
}} />
</Tooltip>
{/* 下载按钮 */}
<Tooltip placement="top" title="Download video">
<GlassIconButton icon={Download} loading={isLoadingDownloadBtn} size='sm' onClick={async () => {
@ -493,6 +502,14 @@ export const MediaViewer = React.memo(function MediaViewer({
}
}} />
</Tooltip>
{/* 下载所有视频按钮 */}
<Tooltip placement="top" title="Download all videos">
<GlassIconButton icon={ArrowDownWideNarrow} size='sm' loading={isLoadingDownloadAllVideosBtn} onClick={ async () => {
setIsLoadingDownloadAllVideosBtn(true);
await downloadAllVideos(taskObject.videos.data.flatMap((video: any) => video.urls));
setIsLoadingDownloadAllVideosBtn(false);
}} />
</Tooltip>
{/* 下载按钮 */}
<Tooltip placement="top" title="Download video">
<GlassIconButton icon={Download} loading={isLoadingDownloadBtn} size='sm' onClick={async () => {

View File

@ -15,6 +15,9 @@ interface ThumbnailGridProps {
onRetryVideo: (video_id: string) => void;
}
/**
* hover时播放视频预览
*/
export function ThumbnailGrid({
isDisabledFocus,
taskObject,
@ -22,6 +25,17 @@ export function ThumbnailGrid({
onSketchSelect,
onRetryVideo
}: ThumbnailGridProps) {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
/** 处理鼠标进入缩略图事件 */
const handleMouseEnter = useCallback((index: number) => {
setHoveredIndex(index);
}, []);
/** 处理鼠标离开缩略图事件 */
const handleMouseLeave = useCallback((index: number) => {
setHoveredIndex(null);
}, []);
const thumbnailsRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
@ -159,13 +173,8 @@ export function ThumbnailGrid({
console.log('taskObject.currentStage_thumbnail-grid', taskObject.currentStage);
}, [taskObject.currentStage]);
// 粗剪/精剪最终成片阶段不显示缩略图
if (taskObject.currentStage === 'final_video') {
return null;
}
// 渲染视频阶段的缩略图
const renderVideoThumbnails = () => (
const renderVideoThumbnails = (disabled: boolean = false) => (
taskObject.videos.data.map((video, index) => {
const urls: string = video.urls ? video.urls.join(',') : '';
@ -173,8 +182,8 @@ export function ThumbnailGrid({
<div
key={`video-${urls}-${index}`}
className={`relative aspect-video rounded-lg overflow-hidden
${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`}
onClick={() => !isDragging && onSketchSelect(index)}
${(currentSketchIndex === index && !disabled) ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`}
onClick={() => !isDragging && !disabled && onSketchSelect(index)}
>
{/* 视频层 */}
@ -202,11 +211,28 @@ export function ThumbnailGrid({
// loop
// muted
// />
<img
className="w-full h-full object-cover"
src={`${taskObject.videos.data[index].urls[0]}?x-oss-process=video/snapshot,t_1000,f_jpg`}
draggable="false"
/>
<div
className="w-full h-full relative"
onMouseEnter={() => handleMouseEnter(index)}
onMouseLeave={() => handleMouseLeave(index)}
>
<img
className="w-full h-full object-cover"
src={`${taskObject.videos.data[index].urls[0]}?x-oss-process=video/snapshot,t_1000,f_jpg`}
draggable="false"
alt="video thumbnail"
/>
{hoveredIndex === index && (
<video
className="absolute inset-0 w-full h-full object-cover"
src={taskObject.videos.data[index].urls[0]}
autoPlay
muted
playsInline
loop
/>
)}
</div>
) : (
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
@ -316,6 +342,7 @@ export function ThumbnailGrid({
>
{taskObject.currentStage === 'video' && renderVideoThumbnails()}
{(taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') && renderSketchThumbnails(getCurrentData())}
{taskObject.currentStage === 'final_video' && renderVideoThumbnails(true)}
</div>
);
}

View File

@ -716,7 +716,6 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
applyScript,
fallbackToStep,
originalText: state.originalText,
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
showGotoCutButton: (canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') || isShowError) ? true : false,
generateEditPlan: openEditPlan,
handleRetryVideo

View File

@ -94,3 +94,13 @@ export const downloadVideo = async (url: string) => {
console.error('下载视频失败:', error);
}
};
/**
*
* @param urls URL列表
*/
export const downloadAllVideos = async (urls: string[]) => {
for (const url of urls) {
await downloadVideo(url);
}
};