forked from 77media/video-flow
视频生成失败 重新生成
This commit is contained in:
parent
d2dd4e57e4
commit
8012c1e47a
@ -1,3 +1,4 @@
|
|||||||
NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com
|
NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com
|
||||||
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
||||||
|
# NEXT_PUBLIC_BASE_URL = https://pre.movieflow.api.huiying.video
|
||||||
NEXT_PUBLIC_API_BASE_URL = https://77.api.qikongjian.com
|
NEXT_PUBLIC_API_BASE_URL = https://77.api.qikongjian.com
|
||||||
|
|||||||
@ -619,6 +619,10 @@ export interface RoleResponse {
|
|||||||
/**缓存 */
|
/**缓存 */
|
||||||
character_draft: string;
|
character_draft: string;
|
||||||
}
|
}
|
||||||
|
export interface RealRoleResponse {
|
||||||
|
system_characters: [];
|
||||||
|
project_characters: RoleResponse[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Role {
|
export interface Role {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
} from "@/app/service/domain/valueObject";
|
} from "@/app/service/domain/valueObject";
|
||||||
import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter";
|
import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter";
|
||||||
import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse } from "./DTO/movieEdit";
|
import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse } from "./DTO/movieEdit";
|
||||||
import { RoleResponse } from "./DTO/movieEdit";
|
import { RealRoleResponse } from "./DTO/movieEdit";
|
||||||
import { RoleRecognitionResponse } from "./DTO/movieEdit";
|
import { RoleRecognitionResponse } from "./DTO/movieEdit";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1165,14 +1165,14 @@ export const getSimilarCharacters = async (request: {
|
|||||||
/**
|
/**
|
||||||
* 获取项目角色列表(含高亮关键词)接口
|
* 获取项目角色列表(含高亮关键词)接口
|
||||||
* @param request - 项目角色列表请求参数
|
* @param request - 项目角色列表请求参数
|
||||||
* @returns Promise<ApiResponse<RoleResponse[]>> 项目角色列表
|
* @returns Promise<ApiResponse<RealRoleResponse>> 项目角色列表
|
||||||
*/
|
*/
|
||||||
export const getCharacterListByProjectWithHighlight = async (request: {
|
export const getCharacterListByProjectWithHighlight = async (request: {
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
project_id: string;
|
project_id: string;
|
||||||
/** 每个角色最多提取的高亮关键词数量 */
|
/** 每个角色最多提取的高亮关键词数量 */
|
||||||
max_keywords?: number;
|
max_keywords?: number;
|
||||||
}): Promise<ApiResponse<RoleResponse>> => {
|
}): Promise<ApiResponse<any>> => {
|
||||||
return post("/character/list_by_project_with_highlight", request);
|
return post("/character/list_by_project_with_highlight", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -188,7 +188,7 @@ export const useRoleShotServiceHook = (projectId: string,selectRole?:RoleEntity,
|
|||||||
shot_id: shot.id, // 单个分镜ID
|
shot_id: shot.id, // 单个分镜ID
|
||||||
character_replacements: characterReplacements,
|
character_replacements: characterReplacements,
|
||||||
wait_for_completion: false, // 不等待完成,异步处理
|
wait_for_completion: false, // 不等待完成,异步处理
|
||||||
character_draft: JSON.stringify(newDraftRoleList)
|
character_draft: newDraftRoleList ? JSON.stringify(newDraftRoleList) : ""
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export class RoleEditUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
const roleList = this.parseProjectRoleList(response.data);
|
const roleList = this.parseProjectRoleList(response.data.project_characters);
|
||||||
console.log('roleList', roleList)
|
console.log('roleList', roleList)
|
||||||
return roleList;
|
return roleList;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -54,7 +54,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText,
|
originalText,
|
||||||
showGotoCutButton,
|
showGotoCutButton,
|
||||||
generateEditPlan
|
generateEditPlan,
|
||||||
|
handleRetryVideo
|
||||||
} = useWorkflowData();
|
} = useWorkflowData();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -126,6 +127,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
showGotoCutButton={showGotoCutButton}
|
showGotoCutButton={showGotoCutButton}
|
||||||
onGotoCut={generateEditPlan}
|
onGotoCut={generateEditPlan}
|
||||||
isSmartChatBoxOpen={isSmartChatBoxOpen}
|
isSmartChatBoxOpen={isSmartChatBoxOpen}
|
||||||
|
onRetryVideo={(video_id) => handleRetryVideo(video_id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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, Loader2, X, Scissors } from 'lucide-react';
|
import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors, RotateCcw, MessageCircleMore } 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';
|
||||||
@ -29,6 +29,7 @@ interface MediaViewerProps {
|
|||||||
showGotoCutButton?: boolean;
|
showGotoCutButton?: boolean;
|
||||||
onGotoCut: () => void;
|
onGotoCut: () => void;
|
||||||
isSmartChatBoxOpen: boolean;
|
isSmartChatBoxOpen: boolean;
|
||||||
|
onRetryVideo?: (video_id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaViewer = React.memo(function MediaViewer({
|
export const MediaViewer = React.memo(function MediaViewer({
|
||||||
@ -47,7 +48,8 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
setVideoPreview,
|
setVideoPreview,
|
||||||
showGotoCutButton,
|
showGotoCutButton,
|
||||||
onGotoCut,
|
onGotoCut,
|
||||||
isSmartChatBoxOpen
|
isSmartChatBoxOpen,
|
||||||
|
onRetryVideo
|
||||||
}: MediaViewerProps) {
|
}: MediaViewerProps) {
|
||||||
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
@ -354,6 +356,13 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
<div className="absolute top-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
<div className="absolute top-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
||||||
right: toosBtnRight
|
right: toosBtnRight
|
||||||
}}>
|
}}>
|
||||||
|
<Tooltip placement="top" title='Edit'>
|
||||||
|
<GlassIconButton
|
||||||
|
icon={Edit3}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleEditClick('3', 'final')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
{showGotoCutButton && (
|
{showGotoCutButton && (
|
||||||
<Tooltip placement="top" title='Go to AI-powered editing platform'>
|
<Tooltip placement="top" title='Go to AI-powered editing platform'>
|
||||||
<GlassIconButton icon={Scissors} size='sm' onClick={onGotoCut} />
|
<GlassIconButton icon={Scissors} size='sm' onClick={onGotoCut} />
|
||||||
@ -361,23 +370,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作按钮组 */}
|
|
||||||
{/* <AnimatePresence>
|
|
||||||
<motion.div
|
|
||||||
className="absolute top-4 right-4 z-10 gap-2 hidden group-hover:flex"
|
|
||||||
initial={{ opacity: 0, y: -10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: -10 }}
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
>
|
|
||||||
<GlassIconButton
|
|
||||||
icon={Edit3}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleEditClick('3', 'final')}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</AnimatePresence> */}
|
|
||||||
|
|
||||||
{/* 底部控制区域 */}
|
{/* 底部控制区域 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute bottom-16 left-4 z-10 flex items-center gap-3"
|
className="absolute bottom-16 left-4 z-10 flex items-center gap-3"
|
||||||
@ -437,9 +429,11 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
{/* 生成失败 */}
|
{/* 生成失败 */}
|
||||||
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
|
<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">
|
<div
|
||||||
<X className="w-10 h-10" />
|
className="text-red-500 text-2xl font-bold flex items-center gap-2"
|
||||||
<span>Failed</span>
|
>
|
||||||
|
<RotateCcw className="w-10 h-10" />
|
||||||
|
<span>Retry</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -477,9 +471,20 @@ 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={{
|
<div className="absolute top-4 right-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
||||||
right: toosBtnRight
|
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去编辑 按钮 */}
|
{/* 添加到chat去编辑 按钮 */}
|
||||||
<Tooltip placement="top" title="Edit video with chat">
|
<Tooltip placement="top" title="Edit video with chat">
|
||||||
<GlassIconButton icon={Edit3} size='sm' onClick={() => {
|
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {
|
||||||
const currentVideo = taskObject.videos.data[currentSketchIndex];
|
const currentVideo = taskObject.videos.data[currentSketchIndex];
|
||||||
if (currentVideo && currentVideo.urls && currentVideo.urls.length > 0 && setVideoPreview) {
|
if (currentVideo && currentVideo.urls && currentVideo.urls.length > 0 && setVideoPreview) {
|
||||||
setVideoPreview(currentVideo.urls[0], currentVideo.video_id);
|
setVideoPreview(currentVideo.urls[0], currentVideo.video_id);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, { useRef, useEffect, useState, useCallback } 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, SquareUserRound, MapPinHouse, Clapperboard, Video } from 'lucide-react';
|
import { Loader2, X, SquareUserRound, MapPinHouse, Clapperboard, Video, RotateCcw } from 'lucide-react';
|
||||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
|
|
||||||
interface ThumbnailGridProps {
|
interface ThumbnailGridProps {
|
||||||
@ -187,7 +187,7 @@ export function ThumbnailGrid({
|
|||||||
{taskObject.videos.data[index].video_status === 2 && (
|
{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="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">
|
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
|
||||||
<X className="w-10 h-10" />
|
<RotateCcw className="w-10 h-10" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -109,10 +109,11 @@ export const useEditData = (tabType: string, originalText?: string) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('useEditData-----videoSegments', videoSegments, scriptRoles);
|
console.log('useEditData-----videoSegments', videoSegments, scriptRoles);
|
||||||
setShotData(videoSegments);
|
setShotData(videoSegments);
|
||||||
setRoleData(scriptRoles);
|
// setRoleData(scriptRoles);
|
||||||
}, [videoSegments, scriptRoles]);
|
}, [videoSegments, scriptRoles]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('useEditData-----roleList', roleList);
|
||||||
setRoleData(roleList);
|
setRoleData(roleList);
|
||||||
// setRoleData(mockRoleData);
|
// setRoleData(mockRoleData);
|
||||||
}, [roleList]);
|
}, [roleList]);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan } from '@/api/video_flow';
|
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateShot } from '@/api/video_flow';
|
||||||
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
||||||
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||||
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||||
@ -200,9 +200,9 @@ export function useWorkflowData() {
|
|||||||
// 收集所有需要更新的状态
|
// 收集所有需要更新的状态
|
||||||
let stateUpdates = JSON.stringify(taskCurrent);
|
let stateUpdates = JSON.stringify(taskCurrent);
|
||||||
// 视频分析
|
// 视频分析
|
||||||
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'IN_PROCESS').length;
|
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT').length;
|
||||||
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
||||||
if (analyze_video_completed_count === analyze_video_total_count) {
|
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
||||||
setCanGoToCut(true);
|
setCanGoToCut(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,6 +524,29 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重试生成视频
|
||||||
|
const handleRetryVideo = async (video_id: string) => {
|
||||||
|
try {
|
||||||
|
// 重置视频状态为生成中
|
||||||
|
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 regenerateShot({ project_id: episodeId, shot_id: video_id });
|
||||||
|
|
||||||
|
// 重新开启轮询 如果轮询结束的话
|
||||||
|
if (!needStreamData) setNeedStreamData(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('重试生成视频失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 回退到 指定状态 重新获取数据
|
// 回退到 指定状态 重新获取数据
|
||||||
const fallbackToStep = (step: string) => {
|
const fallbackToStep = (step: string) => {
|
||||||
console.log('fallbackToStep', step);
|
console.log('fallbackToStep', step);
|
||||||
@ -564,6 +587,7 @@ export function useWorkflowData() {
|
|||||||
originalText: state.originalText,
|
originalText: state.originalText,
|
||||||
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
||||||
showGotoCutButton: canGoToCut ? true : false,
|
showGotoCutButton: canGoToCut ? true : false,
|
||||||
generateEditPlan
|
generateEditPlan,
|
||||||
|
handleRetryVideo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|||||||
import { SquarePen, Lightbulb, Navigation, Globe, Copy, SendHorizontal, X, Plus } from 'lucide-react';
|
import { SquarePen, Lightbulb, Navigation, Globe, Copy, SendHorizontal, X, Plus } from 'lucide-react';
|
||||||
import { ScriptData, ScriptBlock, ScriptContent, ThemeTagBgColor, ThemeType } from './types';
|
import { ScriptData, ScriptBlock, ScriptContent, ThemeTagBgColor, ThemeType } from './types';
|
||||||
import ContentEditable, { ContentEditableEvent } from 'react-contenteditable';
|
import ContentEditable, { ContentEditableEvent } from 'react-contenteditable';
|
||||||
import { toast } from 'sonner';
|
|
||||||
import { SelectDropdown } from '@/components/ui/select-dropdown';
|
import { SelectDropdown } from '@/components/ui/select-dropdown';
|
||||||
import { TypewriterText } from '@/components/workflow/work-office/common/TypewriterText';
|
import { TypewriterText } from '@/components/workflow/work-office/common/TypewriterText';
|
||||||
|
import { msg } from '@/utils/message';
|
||||||
|
|
||||||
interface ScriptRendererProps {
|
interface ScriptRendererProps {
|
||||||
data: any[];
|
data: any[];
|
||||||
@ -126,11 +126,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
|||||||
const handleThemeTagChange = (value: string[]) => {
|
const handleThemeTagChange = (value: string[]) => {
|
||||||
console.log('主题标签更改', value);
|
console.log('主题标签更改', value);
|
||||||
if (value.length > 5) {
|
if (value.length > 5) {
|
||||||
toast.error('最多可选择5个主题标签', {
|
msg.error('最多可选择5个主题标签', 3000);
|
||||||
duration: 3000,
|
|
||||||
position: 'top-center',
|
|
||||||
richColors: true,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setAddThemeTag(value);
|
setAddThemeTag(value);
|
||||||
@ -208,6 +204,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
|||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* 需要权限控制 */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{(isHovered || isActive) && (
|
{(isHovered || isActive) && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -219,6 +216,9 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
|||||||
<SquarePen
|
<SquarePen
|
||||||
className="w-6 h-6 p-1 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors"
|
className="w-6 h-6 p-1 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
// 提示权限不够
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
handleEditBlock(block);
|
handleEditBlock(block);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -226,7 +226,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
|||||||
className="w-6 h-6 p-1 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors"
|
className="w-6 h-6 p-1 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(block.content.map(item => item.text).join('\n'));
|
navigator.clipboard.writeText(block.content.map(item => item.text).join('\n'));
|
||||||
toast.success('Copied!');
|
msg.success('Copied!');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -237,7 +237,8 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
|||||||
renderEditBlock(block)
|
renderEditBlock(block)
|
||||||
) : (
|
) : (
|
||||||
block.content.map((item, index) => (
|
block.content.map((item, index) => (
|
||||||
<div key={index} onDoubleClick={() => handleEditBlock(block)}>{renderContent(item)}</div>
|
// <div key={index} onDoubleClick={() => handleEditBlock(block)}>{renderContent(item)}</div>
|
||||||
|
<div key={index}>{renderContent(item)}</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ interface CharacterEditorProps {
|
|||||||
highlight: TagValueObject[];
|
highlight: TagValueObject[];
|
||||||
onSmartPolish: (text: string) => void;
|
onSmartPolish: (text: string) => void;
|
||||||
onUpdateText: (text: string) => void;
|
onUpdateText: (text: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CharacterEditor = forwardRef<any, CharacterEditorProps>(({
|
export const CharacterEditor = forwardRef<any, CharacterEditorProps>(({
|
||||||
@ -19,7 +20,8 @@ export const CharacterEditor = forwardRef<any, CharacterEditorProps>(({
|
|||||||
description,
|
description,
|
||||||
highlight,
|
highlight,
|
||||||
onSmartPolish,
|
onSmartPolish,
|
||||||
onUpdateText
|
onUpdateText,
|
||||||
|
disabled
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const [isOptimizing, setIsOptimizing] = useState(false);
|
const [isOptimizing, setIsOptimizing] = useState(false);
|
||||||
const [content, setContent] = useState<any[]>([]);
|
const [content, setContent] = useState<any[]>([]);
|
||||||
@ -70,7 +72,7 @@ export const CharacterEditor = forwardRef<any, CharacterEditorProps>(({
|
|||||||
<div className={cn("space-y-2 border border-white/10 relative p-2 rounded-[0.5rem] pb-12", className)}>
|
<div className={cn("space-y-2 border border-white/10 relative p-2 rounded-[0.5rem] pb-12", className)}>
|
||||||
{/* 自由输入区域 */}
|
{/* 自由输入区域 */}
|
||||||
{
|
{
|
||||||
!isInit && <MainEditor content={content} onChangeContent={handleChangeContent} />
|
!isInit && <MainEditor content={content} onChangeContent={handleChangeContent} disabled={disabled} />
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* 智能润色按钮 */}
|
{/* 智能润色按钮 */}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ 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';
|
import { RoleEntity } from '@/app/service/domain/Entities';
|
||||||
import { Role } from '@/api/DTO/movieEdit';
|
import { Role } from '@/api/DTO/movieEdit';
|
||||||
|
import { msg } from '@/utils/message';
|
||||||
|
|
||||||
interface CharacterTabContentProps {
|
interface CharacterTabContentProps {
|
||||||
originalRoles: Role[];
|
originalRoles: Role[];
|
||||||
@ -112,6 +113,8 @@ CharacterTabContentProps
|
|||||||
|
|
||||||
const handleSmartPolish = (text: string) => {
|
const handleSmartPolish = (text: string) => {
|
||||||
// 然后调用优化角色文本
|
// 然后调用优化角色文本
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
optimizeRoleText(text);
|
optimizeRoleText(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -209,6 +212,8 @@ CharacterTabContentProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenReplaceLibrary = async () => {
|
const handleOpenReplaceLibrary = async () => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
setIsLoadingLibrary(true);
|
setIsLoadingLibrary(true);
|
||||||
setIsReplaceLibraryOpen(true);
|
setIsReplaceLibraryOpen(true);
|
||||||
setShowAddToLibrary(true);
|
setShowAddToLibrary(true);
|
||||||
@ -217,6 +222,8 @@ CharacterTabContentProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRegenerate = async () => {
|
const handleRegenerate = async () => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
console.log('Regenerate');
|
console.log('Regenerate');
|
||||||
setIsRegenerate(true);
|
setIsRegenerate(true);
|
||||||
// const text = characterEditorRef.current.getRoleText();
|
// const text = characterEditorRef.current.getRoleText();
|
||||||
@ -230,6 +237,8 @@ CharacterTabContentProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
const handleUploadClick = () => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
fileInputRef.current?.click();
|
fileInputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -339,7 +348,7 @@ CharacterTabContentProps
|
|||||||
height='100%'
|
height='100%'
|
||||||
enableAnimation={enableAnimation}
|
enableAnimation={enableAnimation}
|
||||||
/>
|
/>
|
||||||
{/* 应用角色按钮 */}
|
{/* 应用角色按钮 暂时注释需要权限控制 */}
|
||||||
<div className='absolute top-3 right-3 flex gap-2'>
|
<div className='absolute top-3 right-3 flex gap-2'>
|
||||||
<motion.button
|
<motion.button
|
||||||
className="p-2 bg-black/50 hover:bg-black/70
|
className="p-2 bg-black/50 hover:bg-black/70
|
||||||
@ -373,6 +382,7 @@ CharacterTabContentProps
|
|||||||
highlight={selectedRole?.tags || []}
|
highlight={selectedRole?.tags || []}
|
||||||
onSmartPolish={handleSmartPolish}
|
onSmartPolish={handleSmartPolish}
|
||||||
onUpdateText={(text: string) => updateRoleText(text)}
|
onUpdateText={(text: string) => updateRoleText(text)}
|
||||||
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
{/* 重新生成按钮、替换形象按钮 */}
|
{/* 重新生成按钮、替换形象按钮 */}
|
||||||
<div className="grid grid-cols-1 gap-2">
|
<div className="grid grid-cols-1 gap-2">
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { MusicTabContent } from './music-tab-content';
|
|||||||
import FloatingGlassPanel from './FloatingGlassPanel';
|
import FloatingGlassPanel from './FloatingGlassPanel';
|
||||||
import { SaveEditUseCase } from '@/app/service/usecase/SaveEditUseCase';
|
import { SaveEditUseCase } from '@/app/service/usecase/SaveEditUseCase';
|
||||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
|
import { msg } from '@/utils/message';
|
||||||
|
|
||||||
interface EditModalProps {
|
interface EditModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -123,6 +124,8 @@ export function EditModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
console.log('handleSave');
|
console.log('handleSave');
|
||||||
// setIsRemindFallbackOpen(true);
|
// setIsRemindFallbackOpen(true);
|
||||||
if (activeTab === '0') {
|
if (activeTab === '0') {
|
||||||
@ -140,6 +143,8 @@ export function EditModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleConfirmGotoFallback = () => {
|
const handleConfirmGotoFallback = () => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
setDisabledBtn(true);
|
setDisabledBtn(true);
|
||||||
console.log('handleConfirmGotoFallback');
|
console.log('handleConfirmGotoFallback');
|
||||||
SaveEditUseCase.saveData();
|
SaveEditUseCase.saveData();
|
||||||
@ -165,6 +170,8 @@ export function EditModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
console.log('handleReset');
|
console.log('handleReset');
|
||||||
// 重置当前tab修改的数据
|
// 重置当前tab修改的数据
|
||||||
setIsRemindResetOpen(true);
|
setIsRemindResetOpen(true);
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import { HighlightTextExtension } from './HighlightText';
|
|||||||
interface MainEditorProps {
|
interface MainEditorProps {
|
||||||
content: any[];
|
content: any[];
|
||||||
onChangeContent?: (content: any[]) => void;
|
onChangeContent?: (content: any[]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MainEditor({ content, onChangeContent }: MainEditorProps) {
|
export default function MainEditor({ content, onChangeContent, disabled }: MainEditorProps) {
|
||||||
const [renderContent, setRenderContent] = useState<any[]>(content);
|
const [renderContent, setRenderContent] = useState<any[]>(content);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -33,7 +34,7 @@ export default function MainEditor({ content, onChangeContent }: MainEditorProps
|
|||||||
},
|
},
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: 'prose prose-invert max-w-none focus:outline-none'
|
class: `prose prose-invert max-w-none focus:outline-none ${disabled ? 'cursor-not-allowed' : ''}`
|
||||||
},
|
},
|
||||||
handleDOMEvents: {
|
handleDOMEvents: {
|
||||||
keydown: (view, event) => {
|
keydown: (view, event) => {
|
||||||
@ -50,7 +51,7 @@ export default function MainEditor({ content, onChangeContent }: MainEditorProps
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCreate: ({ editor }) => {
|
onCreate: ({ editor }) => {
|
||||||
editor.setOptions({ editable: true });
|
editor.setOptions({ editable: !disabled });
|
||||||
},
|
},
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ 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 { RoleEntity, VideoSegmentEntity } from '@/app/service/domain/Entities';
|
import { RoleEntity, VideoSegmentEntity } from '@/app/service/domain/Entities';
|
||||||
import { ShotVideo } from '@/api/DTO/movieEdit';
|
import { ShotVideo } from '@/api/DTO/movieEdit';
|
||||||
|
import { msg } from '@/utils/message';
|
||||||
|
|
||||||
interface ShotTabContentProps {
|
interface ShotTabContentProps {
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
@ -444,7 +445,11 @@ export const ShotTabContent = forwardRef<
|
|||||||
<motion.div className='absolute top-4 right-4 flex gap-2'>
|
<motion.div className='absolute top-4 right-4 flex gap-2'>
|
||||||
{/* 人物替换按钮 */}
|
{/* 人物替换按钮 */}
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={() => handleScan()}
|
onClick={() => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
|
handleScan()
|
||||||
|
}}
|
||||||
className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full
|
className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full
|
||||||
${scanState === 'detected'
|
${scanState === 'detected'
|
||||||
? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white'
|
? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white'
|
||||||
@ -496,7 +501,11 @@ export const ShotTabContent = forwardRef<
|
|||||||
<span>Add Shot</span>
|
<span>Add Shot</span>
|
||||||
</motion.button> */}
|
</motion.button> */}
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={() => handleRegenerate()}
|
onClick={() => {
|
||||||
|
msg.error('No permission!');
|
||||||
|
return;
|
||||||
|
handleRegenerate();
|
||||||
|
}}
|
||||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20
|
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20
|
||||||
text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
|
|||||||
@ -116,10 +116,10 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash": "^4.17.19",
|
"@types/lodash": "^4.17.19",
|
||||||
"@types/react-grid-layout": "^1.3.5",
|
"@types/react-grid-layout": "^1.3.5",
|
||||||
"jest": "^30.0.5",
|
"jest": "^29.7.0",
|
||||||
"ts-jest": "^29.4.0"
|
"ts-jest": "^29.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
utils/message.tsx
Normal file
80
utils/message.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { message } from 'antd';
|
||||||
|
import type { MessageArgsProps } from 'antd/es/message';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局消息提示工具
|
||||||
|
* 对 antd message 进行封装,提供更简洁的 API
|
||||||
|
*/
|
||||||
|
class MessageUtil {
|
||||||
|
/**
|
||||||
|
* 成功提示
|
||||||
|
* @param content - 提示内容
|
||||||
|
* @param duration - 显示时长(秒),默认 3 秒
|
||||||
|
* @param options - 其他配置项
|
||||||
|
*/
|
||||||
|
success(content: string, duration = 3, options?: Omit<MessageArgsProps, 'content'>) {
|
||||||
|
message.success({ content, duration, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误提示
|
||||||
|
* @param content - 提示内容
|
||||||
|
* @param duration - 显示时长(秒),默认 3 秒
|
||||||
|
* @param options - 其他配置项
|
||||||
|
*/
|
||||||
|
error(content: string, duration = 3, options?: Omit<MessageArgsProps, 'content'>) {
|
||||||
|
message.error({ content, duration, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 警告提示
|
||||||
|
* @param content - 提示内容
|
||||||
|
* @param duration - 显示时长(秒),默认 3 秒
|
||||||
|
* @param options - 其他配置项
|
||||||
|
*/
|
||||||
|
warning(content: string, duration = 3, options?: Omit<MessageArgsProps, 'content'>) {
|
||||||
|
message.warning({ content, duration, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通提示
|
||||||
|
* @param content - 提示内容
|
||||||
|
* @param duration - 显示时长(秒),默认 3 秒
|
||||||
|
* @param options - 其他配置项
|
||||||
|
*/
|
||||||
|
info(content: string, duration = 3, options?: Omit<MessageArgsProps, 'content'>) {
|
||||||
|
message.info({ content, duration, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载提示
|
||||||
|
* @param content - 提示内容
|
||||||
|
* @param duration - 显示时长(秒),默认 0 表示不自动关闭
|
||||||
|
* @param options - 其他配置项
|
||||||
|
* @returns - 返回一个函数,调用该函数可以手动关闭提示
|
||||||
|
*/
|
||||||
|
loading(content: string, duration = 0, options?: Omit<MessageArgsProps, 'content'>) {
|
||||||
|
return message.loading({ content, duration, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁所有消息提示
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
message.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置全局默认值
|
||||||
|
* @param options - 全局配置项
|
||||||
|
*/
|
||||||
|
config(options: MessageArgsProps) {
|
||||||
|
message.config(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例实例
|
||||||
|
export const msg = new MessageUtil();
|
||||||
|
|
||||||
|
// 为了方便使用,也导出单独的方法
|
||||||
|
export const { success, error, warning, info, loading, destroy, config } = msg;
|
||||||
Loading…
x
Reference in New Issue
Block a user