视频生成失败 重新生成

This commit is contained in:
北枳 2025-09-01 21:25:33 +08:00
parent d2dd4e57e4
commit 8012c1e47a
18 changed files with 202 additions and 55 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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);
}; };

View File

@ -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) : ""
}) })
})) }))

View File

@ -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 {

View File

@ -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>
)} )}

View File

@ -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);

View File

@ -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>
)} )}

View File

@ -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]);

View File

@ -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
}; };
} }

View File

@ -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>

View File

@ -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} />
} }
{/* 智能润色按钮 */} {/* 智能润色按钮 */}

View File

@ -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">

View File

@ -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);

View File

@ -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 {

View File

@ -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 }}

View File

@ -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
View 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;