重试视频,等待 接口 支持 完善

This commit is contained in:
北枳 2025-09-02 14:45:31 +08:00
parent 8616e2ab34
commit 150c1636bb
5 changed files with 76 additions and 41 deletions

View File

@ -665,6 +665,16 @@ export const regenerateShot = async (request: {
return post("/movie/regenerate_shot_video", request);
};
// 重新生成视频
export const regenerateVideo = async (request: {
/** 项目ID */
project_id: string;
/** 视频ID */
video_id: string;
}): Promise<ApiResponse<any>> => {
return post("/movie_cut/regenerate_video", request);
};
/**
*

View File

@ -139,6 +139,7 @@ const WorkFlow = React.memo(function WorkFlow() {
taskObject={taskObject}
currentSketchIndex={currentSketchIndex}
onSketchSelect={setCurrentSketchIndex}
onRetryVideo={handleRetryVideo}
/>
</div>
)}

View File

@ -430,10 +430,14 @@ export const MediaViewer = React.memo(function MediaViewer({
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
<div
className="text-red-500 text-2xl font-bold flex items-center gap-2"
className="text-[#813b9dcc] text-2xl font-bold flex items-center gap-2"
>
<RotateCcw className="w-10 h-10" />
<span>Retry</span>
<RotateCcw className="w-10 h-10 cursor-pointer" onClick={() => {
const video = taskObject.videos.data[currentSketchIndex];
if (onRetryVideo && video?.video_id) {
onRetryVideo(video.video_id);
}
}} />
</div>
</div>
)}
@ -442,7 +446,7 @@ export const MediaViewer = React.memo(function MediaViewer({
{/* 视频 多个 取第一个 */}
{ taskObject.videos.data[currentSketchIndex].urls && (
{ taskObject.videos.data[currentSketchIndex].urls && taskObject.videos.data[currentSketchIndex].urls.length > 0 && (
<>
<motion.div
initial={{ clipPath: "inset(0 100% 0 0)" }}
@ -458,7 +462,6 @@ export const MediaViewer = React.memo(function MediaViewer({
autoPlay={isVideoPlaying}
loop={true}
playsInline
onLoadedData={() => applyVolumeSettings(mainVideoRef.current!)}
onEnded={() => {
if (isVideoPlaying) {
// 自动切换到下一个视频的逻辑在父组件处理
@ -471,17 +474,6 @@ export const MediaViewer = React.memo(function MediaViewer({
<div className="absolute top-4 right-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
right: toosBtnRight
}}>
{/* 重试按钮 */}
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
<Tooltip placement="top" title="Retry video">
<GlassIconButton icon={RotateCcw} size='sm' onClick={() => {
const video = taskObject.videos.data[currentSketchIndex];
if (onRetryVideo && video?.video_id) {
onRetryVideo(video.video_id);
}
}} />
</Tooltip>
)}
{/* 添加到chat去编辑 按钮 */}
<Tooltip placement="top" title="Edit video with chat">
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {

View File

@ -12,13 +12,15 @@ interface ThumbnailGridProps {
taskObject: TaskObject;
currentSketchIndex: number;
onSketchSelect: (index: number) => void;
onRetryVideo: (video_id: string) => void;
}
export function ThumbnailGrid({
isDisabledFocus,
taskObject,
currentSketchIndex,
onSketchSelect
onSketchSelect,
onRetryVideo
}: ThumbnailGridProps) {
const thumbnailsRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
@ -186,19 +188,24 @@ export function ThumbnailGrid({
)}
{taskObject.videos.data[index].video_status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
<RotateCcw className="w-10 h-10" />
<div className="text-[#813b9dcc] text-xl font-bold flex items-center gap-2">
<RotateCcw className="w-10 h-10 cursor-pointer" />
</div>
</div>
)}
{taskObject.videos.data[index].urls ? (
<video
{taskObject.videos.data[index].urls && taskObject.videos.data[index].urls.length > 0 ? (
// <video
// className="w-full h-full object-cover"
// src={taskObject.videos.data[index].urls[0]}
// playsInline
// loop
// muted
// />
<img
className="w-full h-full object-cover"
src={taskObject.videos.data[index].urls[0]}
playsInline
loop
muted
src={`${taskObject.videos.data[index].urls[0]}?x-oss-process=video/snapshot,t_1000,f_jpg`}
draggable="false"
/>
) : (
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">

View File

@ -2,7 +2,7 @@
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useSearchParams } from 'next/navigation';
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateShot } from '@/api/video_flow';
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateVideo } from '@/api/video_flow';
import { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
@ -18,6 +18,8 @@ export function useWorkflowData() {
const from = searchParams.get('from') || '';
const token = localStorage.getItem('token') || '';
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
const isLoaded = localStorage.getItem(`isLoaded_plan_${episodeId}`);
let tempTaskObject = useRef<TaskObject>({
title: '',
@ -114,7 +116,15 @@ export function useWorkflowData() {
}, [taskObject.currentStage]);
const generateEditPlan = useCallback(async (isInit?: boolean) => {
if (isLoaded) {
return;
}
localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true');
isInit && await getGenerateEditPlan({ project_id: episodeId });
openEditPlan();
}, [episodeId]);
const openEditPlan = useCallback(async () => {
window.open(`https://smartcut.movieflow.ai/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
}, [episodeId]);
@ -275,15 +285,17 @@ export function useWorkflowData() {
taskCurrent.currentStage = 'video';
// 正在生成视频中 替换视频数据
const videoList = [];
let videoUrls: string[] = [];
let video_status = 0;
for (const video of task.task_result.data) {
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
// 适配旧数据
video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
// 完成 还是 0 就是 生成失败
video_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
// 每一项 video 有多个视频 先默认取第一个
videoList.push({
urls: video.urls,
urls: videoUrls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
type: 'video'
@ -452,13 +464,16 @@ export function useWorkflowData() {
taskCurrent.currentStage = 'video';
taskCurrent.videos.total_count = data.video.total_count;
const videoList = [];
let videoUrls: string[] = [];
console.log('----------data.video.data', data.video.data);
for (const video of data.video.data) {
let video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
console.log('----------videoUrls', videoUrls);
let video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
video_status = data.video.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
// 每一项 video 有多个视频 默认取存在的项
videoList.push({
urls: video.urls,
urls: videoUrls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
type: 'video'
@ -527,23 +542,33 @@ export function useWorkflowData() {
// 重试生成视频
const handleRetryVideo = async (video_id: string) => {
try {
// 先停止轮询
await new Promise(resolve => {
setNeedStreamData(false);
resolve(true);
});
// 重置视频状态为生成中
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify(prev));
const videoIndex = newState.videos.data.findIndex((v: any) => v.video_id === video_id);
if (videoIndex !== -1) {
newState.videos.data[videoIndex].video_status = 0;
}
return newState;
await new Promise(resolve => {
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify(prev));
const videoIndex = newState.videos.data.findIndex((v: any) => v.video_id === video_id);
if (videoIndex !== -1) {
newState.videos.data[videoIndex].video_status = 0;
}
return newState;
});
resolve(true);
});
// 调用重新生成接口
await regenerateShot({ project_id: episodeId, shot_id: video_id });
await regenerateVideo({ project_id: episodeId, video_id: video_id });
// 重新开启轮询 如果轮询结束的话
if (!needStreamData) setNeedStreamData(true);
// 重新开启轮询
setNeedStreamData(true);
} catch (error) {
console.error('重试生成视频失败:', error);
// 发生错误时也要恢复轮询状态
setNeedStreamData(true);
}
};
@ -587,7 +612,7 @@ export function useWorkflowData() {
originalText: state.originalText,
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
showGotoCutButton: canGoToCut ? true : false,
generateEditPlan,
generateEditPlan: openEditPlan,
handleRetryVideo
};
}