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_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
|
||||
|
||||
@ -619,6 +619,10 @@ export interface RoleResponse {
|
||||
/**缓存 */
|
||||
character_draft: string;
|
||||
}
|
||||
export interface RealRoleResponse {
|
||||
system_characters: [];
|
||||
project_characters: RoleResponse[];
|
||||
}
|
||||
|
||||
|
||||
export interface Role {
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
} from "@/app/service/domain/valueObject";
|
||||
import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter";
|
||||
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";
|
||||
|
||||
/**
|
||||
@ -1165,14 +1165,14 @@ export const getSimilarCharacters = async (request: {
|
||||
/**
|
||||
* 获取项目角色列表(含高亮关键词)接口
|
||||
* @param request - 项目角色列表请求参数
|
||||
* @returns Promise<ApiResponse<RoleResponse[]>> 项目角色列表
|
||||
* @returns Promise<ApiResponse<RealRoleResponse>> 项目角色列表
|
||||
*/
|
||||
export const getCharacterListByProjectWithHighlight = async (request: {
|
||||
/** 项目ID */
|
||||
project_id: string;
|
||||
/** 每个角色最多提取的高亮关键词数量 */
|
||||
max_keywords?: number;
|
||||
}): Promise<ApiResponse<RoleResponse>> => {
|
||||
}): Promise<ApiResponse<any>> => {
|
||||
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
|
||||
character_replacements: characterReplacements,
|
||||
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) {
|
||||
const roleList = this.parseProjectRoleList(response.data);
|
||||
const roleList = this.parseProjectRoleList(response.data.project_characters);
|
||||
console.log('roleList', roleList)
|
||||
return roleList;
|
||||
} else {
|
||||
|
||||
@ -54,7 +54,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
fallbackToStep,
|
||||
originalText,
|
||||
showGotoCutButton,
|
||||
generateEditPlan
|
||||
generateEditPlan,
|
||||
handleRetryVideo
|
||||
} = useWorkflowData();
|
||||
|
||||
const {
|
||||
@ -126,6 +127,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
showGotoCutButton={showGotoCutButton}
|
||||
onGotoCut={generateEditPlan}
|
||||
isSmartChatBoxOpen={isSmartChatBoxOpen}
|
||||
onRetryVideo={(video_id) => handleRetryVideo(video_id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -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 } 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 { GlassIconButton } from '@/components/ui/glass-icon-button';
|
||||
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
||||
@ -29,6 +29,7 @@ interface MediaViewerProps {
|
||||
showGotoCutButton?: boolean;
|
||||
onGotoCut: () => void;
|
||||
isSmartChatBoxOpen: boolean;
|
||||
onRetryVideo?: (video_id: string) => void;
|
||||
}
|
||||
|
||||
export const MediaViewer = React.memo(function MediaViewer({
|
||||
@ -47,7 +48,8 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
setVideoPreview,
|
||||
showGotoCutButton,
|
||||
onGotoCut,
|
||||
isSmartChatBoxOpen
|
||||
isSmartChatBoxOpen,
|
||||
onRetryVideo
|
||||
}: MediaViewerProps) {
|
||||
const mainVideoRef = 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={{
|
||||
right: toosBtnRight
|
||||
}}>
|
||||
<Tooltip placement="top" title='Edit'>
|
||||
<GlassIconButton
|
||||
icon={Edit3}
|
||||
size="sm"
|
||||
onClick={() => handleEditClick('3', 'final')}
|
||||
/>
|
||||
</Tooltip>
|
||||
{showGotoCutButton && (
|
||||
<Tooltip placement="top" title='Go to AI-powered editing platform'>
|
||||
<GlassIconButton icon={Scissors} size='sm' onClick={onGotoCut} />
|
||||
@ -361,23 +370,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
)}
|
||||
</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
|
||||
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 && (
|
||||
<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">
|
||||
<X className="w-10 h-10" />
|
||||
<span>Failed</span>
|
||||
<div
|
||||
className="text-red-500 text-2xl font-bold flex items-center gap-2"
|
||||
>
|
||||
<RotateCcw className="w-10 h-10" />
|
||||
<span>Retry</span>
|
||||
</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={{
|
||||
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={Edit3} size='sm' onClick={() => {
|
||||
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {
|
||||
const currentVideo = taskObject.videos.data[currentSketchIndex];
|
||||
if (currentVideo && currentVideo.urls && currentVideo.urls.length > 0 && setVideoPreview) {
|
||||
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 { Skeleton } from '@/components/ui/skeleton';
|
||||
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';
|
||||
|
||||
interface ThumbnailGridProps {
|
||||
@ -187,7 +187,7 @@ 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">
|
||||
<X className="w-10 h-10" />
|
||||
<RotateCcw className="w-10 h-10" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -109,10 +109,11 @@ export const useEditData = (tabType: string, originalText?: string) => {
|
||||
useEffect(() => {
|
||||
console.log('useEditData-----videoSegments', videoSegments, scriptRoles);
|
||||
setShotData(videoSegments);
|
||||
setRoleData(scriptRoles);
|
||||
// setRoleData(scriptRoles);
|
||||
}, [videoSegments, scriptRoles]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('useEditData-----roleList', roleList);
|
||||
setRoleData(roleList);
|
||||
// setRoleData(mockRoleData);
|
||||
}, [roleList]);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
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 { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||
@ -200,9 +200,9 @@ export function useWorkflowData() {
|
||||
// 收集所有需要更新的状态
|
||||
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;
|
||||
if (analyze_video_completed_count === analyze_video_total_count) {
|
||||
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
||||
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) => {
|
||||
console.log('fallbackToStep', step);
|
||||
@ -564,6 +587,7 @@ export function useWorkflowData() {
|
||||
originalText: state.originalText,
|
||||
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? 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 { ScriptData, ScriptBlock, ScriptContent, ThemeTagBgColor, ThemeType } from './types';
|
||||
import ContentEditable, { ContentEditableEvent } from 'react-contenteditable';
|
||||
import { toast } from 'sonner';
|
||||
import { SelectDropdown } from '@/components/ui/select-dropdown';
|
||||
import { TypewriterText } from '@/components/workflow/work-office/common/TypewriterText';
|
||||
import { msg } from '@/utils/message';
|
||||
|
||||
interface ScriptRendererProps {
|
||||
data: any[];
|
||||
@ -126,11 +126,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
||||
const handleThemeTagChange = (value: string[]) => {
|
||||
console.log('主题标签更改', value);
|
||||
if (value.length > 5) {
|
||||
toast.error('最多可选择5个主题标签', {
|
||||
duration: 3000,
|
||||
position: 'top-center',
|
||||
richColors: true,
|
||||
});
|
||||
msg.error('最多可选择5个主题标签', 3000);
|
||||
return;
|
||||
}
|
||||
setAddThemeTag(value);
|
||||
@ -208,6 +204,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
{/* 需要权限控制 */}
|
||||
<AnimatePresence>
|
||||
{(isHovered || isActive) && (
|
||||
<motion.div
|
||||
@ -219,6 +216,9 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
||||
<SquarePen
|
||||
className="w-6 h-6 p-1 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors"
|
||||
onClick={() => {
|
||||
// 提示权限不够
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
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"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(block.content.map(item => item.text).join('\n'));
|
||||
toast.success('Copied!');
|
||||
msg.success('Copied!');
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
@ -237,7 +237,8 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
||||
renderEditBlock(block)
|
||||
) : (
|
||||
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>
|
||||
|
||||
@ -12,6 +12,7 @@ interface CharacterEditorProps {
|
||||
highlight: TagValueObject[];
|
||||
onSmartPolish: (text: string) => void;
|
||||
onUpdateText: (text: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const CharacterEditor = forwardRef<any, CharacterEditorProps>(({
|
||||
@ -19,7 +20,8 @@ export const CharacterEditor = forwardRef<any, CharacterEditorProps>(({
|
||||
description,
|
||||
highlight,
|
||||
onSmartPolish,
|
||||
onUpdateText
|
||||
onUpdateText,
|
||||
disabled
|
||||
}, ref) => {
|
||||
const [isOptimizing, setIsOptimizing] = useState(false);
|
||||
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)}>
|
||||
{/* 自由输入区域 */}
|
||||
{
|
||||
!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 { RoleEntity } from '@/app/service/domain/Entities';
|
||||
import { Role } from '@/api/DTO/movieEdit';
|
||||
import { msg } from '@/utils/message';
|
||||
|
||||
interface CharacterTabContentProps {
|
||||
originalRoles: Role[];
|
||||
@ -112,6 +113,8 @@ CharacterTabContentProps
|
||||
|
||||
const handleSmartPolish = (text: string) => {
|
||||
// 然后调用优化角色文本
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
optimizeRoleText(text);
|
||||
};
|
||||
|
||||
@ -209,6 +212,8 @@ CharacterTabContentProps
|
||||
};
|
||||
|
||||
const handleOpenReplaceLibrary = async () => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
setIsLoadingLibrary(true);
|
||||
setIsReplaceLibraryOpen(true);
|
||||
setShowAddToLibrary(true);
|
||||
@ -217,6 +222,8 @@ CharacterTabContentProps
|
||||
};
|
||||
|
||||
const handleRegenerate = async () => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
console.log('Regenerate');
|
||||
setIsRegenerate(true);
|
||||
// const text = characterEditorRef.current.getRoleText();
|
||||
@ -230,6 +237,8 @@ CharacterTabContentProps
|
||||
};
|
||||
|
||||
const handleUploadClick = () => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
@ -339,7 +348,7 @@ CharacterTabContentProps
|
||||
height='100%'
|
||||
enableAnimation={enableAnimation}
|
||||
/>
|
||||
{/* 应用角色按钮 */}
|
||||
{/* 应用角色按钮 暂时注释需要权限控制 */}
|
||||
<div className='absolute top-3 right-3 flex gap-2'>
|
||||
<motion.button
|
||||
className="p-2 bg-black/50 hover:bg-black/70
|
||||
@ -373,6 +382,7 @@ CharacterTabContentProps
|
||||
highlight={selectedRole?.tags || []}
|
||||
onSmartPolish={handleSmartPolish}
|
||||
onUpdateText={(text: string) => updateRoleText(text)}
|
||||
disabled={true}
|
||||
/>
|
||||
{/* 重新生成按钮、替换形象按钮 */}
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
|
||||
@ -13,6 +13,7 @@ import { MusicTabContent } from './music-tab-content';
|
||||
import FloatingGlassPanel from './FloatingGlassPanel';
|
||||
import { SaveEditUseCase } from '@/app/service/usecase/SaveEditUseCase';
|
||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||
import { msg } from '@/utils/message';
|
||||
|
||||
interface EditModalProps {
|
||||
isOpen: boolean;
|
||||
@ -123,6 +124,8 @@ export function EditModal({
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
console.log('handleSave');
|
||||
// setIsRemindFallbackOpen(true);
|
||||
if (activeTab === '0') {
|
||||
@ -140,6 +143,8 @@ export function EditModal({
|
||||
}
|
||||
|
||||
const handleConfirmGotoFallback = () => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
setDisabledBtn(true);
|
||||
console.log('handleConfirmGotoFallback');
|
||||
SaveEditUseCase.saveData();
|
||||
@ -165,6 +170,8 @@ export function EditModal({
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
console.log('handleReset');
|
||||
// 重置当前tab修改的数据
|
||||
setIsRemindResetOpen(true);
|
||||
|
||||
@ -7,9 +7,10 @@ import { HighlightTextExtension } from './HighlightText';
|
||||
interface MainEditorProps {
|
||||
content: any[];
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
@ -33,7 +34,7 @@ export default function MainEditor({ content, onChangeContent }: MainEditorProps
|
||||
},
|
||||
editorProps: {
|
||||
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: {
|
||||
keydown: (view, event) => {
|
||||
@ -50,7 +51,7 @@ export default function MainEditor({ content, onChangeContent }: MainEditorProps
|
||||
}
|
||||
},
|
||||
onCreate: ({ editor }) => {
|
||||
editor.setOptions({ editable: true });
|
||||
editor.setOptions({ editable: !disabled });
|
||||
},
|
||||
onUpdate: ({ editor }) => {
|
||||
try {
|
||||
|
||||
@ -13,6 +13,7 @@ import HorizontalScroller from './HorizontalScroller';
|
||||
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
||||
import { RoleEntity, VideoSegmentEntity } from '@/app/service/domain/Entities';
|
||||
import { ShotVideo } from '@/api/DTO/movieEdit';
|
||||
import { msg } from '@/utils/message';
|
||||
|
||||
interface ShotTabContentProps {
|
||||
currentSketchIndex: number;
|
||||
@ -444,7 +445,11 @@ export const ShotTabContent = forwardRef<
|
||||
<motion.div className='absolute top-4 right-4 flex gap-2'>
|
||||
{/* 人物替换按钮 */}
|
||||
<motion.button
|
||||
onClick={() => handleScan()}
|
||||
onClick={() => {
|
||||
msg.error('No permission!');
|
||||
return;
|
||||
handleScan()
|
||||
}}
|
||||
className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full
|
||||
${scanState === 'detected'
|
||||
? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white'
|
||||
@ -496,7 +501,11 @@ export const ShotTabContent = forwardRef<
|
||||
<span>Add Shot</span>
|
||||
</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
|
||||
text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
|
||||
@ -116,10 +116,10 @@
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.19",
|
||||
"@types/react-grid-layout": "^1.3.5",
|
||||
"jest": "^30.0.5",
|
||||
"ts-jest": "^29.4.0"
|
||||
"jest": "^29.7.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