diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index 5ca4c15..97f2de6 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -20,8 +20,7 @@ import { showEditingNotification } from "@/components/pages/work-flow/editing-no // import { AIEditingIframeButton } from './work-flow/ai-editing-iframe'; import { exportVideoWithRetry } from '@/utils/export-service'; import { getFirstFrame } from '@/utils/tools'; -// 临时禁用视频编辑功能 -// import { EditPoint as EditPointType } from './work-flow/video-edit/types'; +import { EditPoint as EditPointType } from './work-flow/video-edit/types'; import { AIEditingIframeButton } from './work-flow/ai-editing-iframe'; import { useDeviceType } from '@/hooks/useDeviceType'; import { H5ProgressToastProvider, useH5ProgressToast } from '@/components/ui/h5-progress-toast'; @@ -303,9 +302,8 @@ const WorkFlow = React.memo(function WorkFlow() { // setAiEditingInProgress(false); // 已移除该状态变量 }, []); - // 临时禁用视频编辑功能 // 视频编辑描述提交处理函数 - /*const handleVideoEditDescriptionSubmit = useCallback((editPoint: EditPointType, description: string) => { + const handleVideoEditDescriptionSubmit = useCallback((editPoint: EditPointType, description: string) => { console.log('🎬 视频编辑描述提交:', { editPoint, description }); // 构造编辑消息发送到SmartChatBox @@ -333,7 +331,7 @@ Please process this video editing request.`; description: `Your edit request for timestamp ${Math.floor(editPoint.timestamp)}s has been submitted successfully.`, duration: 3 }); - }, [currentSketchIndex, isSmartChatBoxOpen]);*/ + }, [currentSketchIndex, isSmartChatBoxOpen]); // 测试导出接口的处理函数(使用封装的导出服务) const handleTestExport = useCallback(async () => { @@ -520,7 +518,9 @@ Please process this video editing request.`; isSmartChatBoxOpen={isSmartChatBoxOpen} onRetryVideo={(video_id) => handleRetryVideo(video_id)} onSelectView={(view) => setSelectedView(view)} - // 临时禁用视频编辑功能: enableVideoEdit={true} onVideoEditDescriptionSubmit={handleVideoEditDescriptionSubmit} + enableVideoEdit={true} + onVideoEditDescriptionSubmit={handleVideoEditDescriptionSubmit} + projectId={episodeId} /> )} diff --git a/components/pages/work-flow/H5MediaViewer.tsx b/components/pages/work-flow/H5MediaViewer.tsx index b9ffd7d..5acbada 100644 --- a/components/pages/work-flow/H5MediaViewer.tsx +++ b/components/pages/work-flow/H5MediaViewer.tsx @@ -43,6 +43,12 @@ interface H5MediaViewerProps { onRetryVideo?: (video_id: string) => void; /** 切换选择视图(final 或 video) */ onSelectView?: (view: 'final' | 'video') => void; + /** 启用视频编辑功能 */ + enableVideoEdit?: boolean; + /** 视频编辑描述提交回调 */ + onVideoEditDescriptionSubmit?: (editPoint: any, description: string) => void; + /** 项目ID */ + projectId?: string; } /** @@ -67,7 +73,10 @@ export function H5MediaViewer({ onGotoCut, isSmartChatBoxOpen, onRetryVideo, - onSelectView + onSelectView, + enableVideoEdit, + onVideoEditDescriptionSubmit, + projectId }: H5MediaViewerProps) { const carouselRef = useRef(null); const videoRefs = useRef>([]); diff --git a/components/pages/work-flow/media-viewer.tsx b/components/pages/work-flow/media-viewer.tsx index 9736e5b..f673538 100644 --- a/components/pages/work-flow/media-viewer.tsx +++ b/components/pages/work-flow/media-viewer.tsx @@ -2,7 +2,7 @@ import React, { useRef, useEffect, useState, SetStateAction, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors, RotateCcw, MessageCircleMore, Download, ArrowDownWideNarrow, CircleAlert /*, PenTool*/ } from 'lucide-react'; +import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors, RotateCcw, MessageCircleMore, Download, ArrowDownWideNarrow, CircleAlert, PenTool } 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'; @@ -12,9 +12,8 @@ import ScriptLoading from './script-loading'; import { TaskObject } from '@/api/DTO/movieEdit'; import { Button, Tooltip } from 'antd'; import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools'; -// 临时禁用视频编辑功能 -// import { VideoEditOverlay } from './video-edit/VideoEditOverlay'; -// import { EditPoint as EditPointType } from './video-edit/types'; +import { VideoEditOverlay } from './video-edit/VideoEditOverlay'; +import { EditPoint as EditPointType } from './video-edit/types'; interface MediaViewerProps { taskObject: TaskObject; @@ -35,9 +34,9 @@ interface MediaViewerProps { onGotoCut: () => void; isSmartChatBoxOpen: boolean; onRetryVideo?: (video_id: string) => void; - // 临时禁用视频编辑功能 - // enableVideoEdit?: boolean; - // onVideoEditDescriptionSubmit?: (editPoint: EditPointType, description: string) => void; + enableVideoEdit?: boolean; + onVideoEditDescriptionSubmit?: (editPoint: EditPointType, description: string) => void; + projectId?: string; } export const MediaViewer = React.memo(function MediaViewer({ @@ -58,10 +57,10 @@ export const MediaViewer = React.memo(function MediaViewer({ showGotoCutButton, onGotoCut, isSmartChatBoxOpen, - onRetryVideo - // 临时禁用视频编辑功能 - // enableVideoEdit = true, - // onVideoEditDescriptionSubmit + onRetryVideo, + enableVideoEdit = true, + onVideoEditDescriptionSubmit, + projectId }: MediaViewerProps) { const mainVideoRef = useRef(null); const finalVideoRef = useRef(null); @@ -78,8 +77,7 @@ export const MediaViewer = React.memo(function MediaViewer({ const [toosBtnRight, setToodsBtnRight] = useState('1rem'); const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false); const [isLoadingDownloadAllVideosBtn, setIsLoadingDownloadAllVideosBtn] = useState(false); - // 临时禁用视频编辑功能 - // const [isVideoEditMode, setIsVideoEditMode] = useState(false); + const [isVideoEditMode, setIsVideoEditMode] = useState(false); useEffect(() => { if (isSmartChatBoxOpen) { @@ -506,11 +504,10 @@ export const MediaViewer = React.memo(function MediaViewer({ }} /> - {/* 临时禁用视频编辑功能 */} {/* 视频编辑覆盖层 */} - {/*enableVideoEdit && isVideoEditMode && ( + {enableVideoEdit && isVideoEditMode && ( - )*/} + )} {/* 跳转剪辑按钮 */}
- {/* 临时禁用视频编辑功能 */} - {/* 视频编辑模式切换按钮 */} - {/*enableVideoEdit && ( + {/* 视频编辑模式切换按钮 - 临时注释 */} + {/* {enableVideoEdit && ( - )*/} + )} */} {/* 添加到chat去编辑 按钮 */} { diff --git a/components/pages/work-flow/video-edit/EditConnection.tsx b/components/pages/work-flow/video-edit/EditConnection.tsx index 2af8864..6e561ad 100644 --- a/components/pages/work-flow/video-edit/EditConnection.tsx +++ b/components/pages/work-flow/video-edit/EditConnection.tsx @@ -69,7 +69,7 @@ function calculateCurvePath({ export function calculateInputPosition( editPointPosition: { x: number; y: number }, containerSize: { width: number; height: number }, - inputBoxSize: { width: number; height: number } = { width: 200, height: 80 } + inputBoxSize: { width: number; height: number } = { width: 300, height: 50 } ): InputBoxPosition { const { x: pointX, y: pointY } = editPointPosition; const { width: containerWidth, height: containerHeight } = containerSize; @@ -151,26 +151,79 @@ export const EditConnection: React.FC = ({ animated = true }) => { const { - color = 'rgba(255, 255, 255, 0.8)', + color = 'rgba(255, 255, 255, 0.9)', // White color to match the reference image strokeWidth = 2, - dashArray = '5,5' + dashArray = '8,4' // Dashed line to match the reference image } = style; - // 计算路径 - const path = useMemo(() => + // 计算箭头几何参数 + const arrowSize = 8; + const arrowHalfHeight = 4; + + // 计算连接方向和角度 + const connectionVector = useMemo(() => { + const dx = endPoint.x - startPoint.x; + const dy = endPoint.y - startPoint.y; + const length = Math.sqrt(dx * dx + dy * dy); + return { + dx: dx / length, + dy: dy / length, + angle: Math.atan2(dy, dx) + }; + }, [startPoint, endPoint]); + + // 计算箭头的正确位置和线条终点 + const arrowGeometry = useMemo(() => { + const { dx, dy, angle } = connectionVector; + + // 箭头尖端位置(原endPoint) + const arrowTip = { x: endPoint.x, y: endPoint.y }; + + // 箭头底部中心点(线条应该连接到这里) + const arrowBase = { + x: endPoint.x - dx * arrowSize, + y: endPoint.y - dy * arrowSize + }; + + // 计算箭头三角形的三个顶点 + const perpX = -dy; // 垂直向量X + const perpY = dx; // 垂直向量Y + + const arrowPoints = [ + arrowTip, // 尖端 + { + x: arrowBase.x + perpX * arrowHalfHeight, + y: arrowBase.y + perpY * arrowHalfHeight + }, + { + x: arrowBase.x - perpX * arrowHalfHeight, + y: arrowBase.y - perpY * arrowHalfHeight + } + ]; + + return { + tip: arrowTip, + base: arrowBase, + points: arrowPoints, + angle + }; + }, [endPoint, connectionVector, arrowSize, arrowHalfHeight]); + + // 计算路径(线条终止于箭头底部中心) + const path = useMemo(() => calculateCurvePath({ start: startPoint, - end: endPoint, + end: arrowGeometry.base, // 连接到箭头底部中心而不是尖端 containerSize, curvature - }), [startPoint, endPoint, containerSize, curvature]); + }), [startPoint, arrowGeometry.base, containerSize, curvature]); // 计算路径长度用于动画 const pathLength = useMemo(() => { - const dx = endPoint.x - startPoint.x; - const dy = endPoint.y - startPoint.y; + const dx = arrowGeometry.base.x - startPoint.x; + const dy = arrowGeometry.base.y - startPoint.y; return Math.sqrt(dx * dx + dy * dy) * 1.2; // 弧线比直线长约20% - }, [startPoint, endPoint]); + }, [startPoint, arrowGeometry.base]); return ( = ({ height={containerSize.height} style={{ zIndex: 10 }} > - {/* 连接线路径 */} + {/* Curved dashed line - properly aligned to arrow base center */} = ({ opacity: { duration: 0.3 } } : {}} style={{ - filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2))' + filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))' }} /> - - {/* 连接线末端的小圆点 */} - `${p.x},${p.y}`).join(' ')} fill={color} initial={animated ? { scale: 0, @@ -227,32 +278,32 @@ export const EditConnection: React.FC = ({ damping: 25 } : {}} style={{ - filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' + filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))' }} /> - {/* 动画流动效果(可选) */} - {animated && ( - + {/* Debug visualization (remove in production) */} + {process.env.NODE_ENV === 'development' && ( + <> + {/* Arrow base center point */} + + {/* Arrow tip point */} + + )} + ); }; diff --git a/components/pages/work-flow/video-edit/EditDescription.tsx b/components/pages/work-flow/video-edit/EditDescription.tsx index 431232a..c2ea896 100644 --- a/components/pages/work-flow/video-edit/EditDescription.tsx +++ b/components/pages/work-flow/video-edit/EditDescription.tsx @@ -101,7 +101,7 @@ export const EditDescription: React.FC = ({ {editPoint.description && editPoint.status !== EditPointStatus.PENDING && ( <> - {/* 连接线 */} + {/* White dashed connection line to match reference image */} = ({ > + + {/* Arrow head */} + - {/* 描述内容框 */} + {/* Consistent white text display matching EditInput component */} = ({ left: position.x, top: position.y, zIndex: 25, - maxWidth: '300px', - minWidth: '200px' }} - initial={{ - opacity: 0, - scale: 0.8, - y: -10 + initial={{ + opacity: 0, + scale: 0.8, + y: -10 }} - animate={{ - opacity: 1, - scale: 1, - y: 0 + animate={{ + opacity: 1, + scale: 1, + y: 0 }} - exit={{ - opacity: 0, - scale: 0.8, - y: -10 + exit={{ + opacity: 0, + scale: 0.8, + y: -10 }} transition={{ type: "spring", @@ -174,98 +183,67 @@ export const EditDescription: React.FC = ({ duration: 0.4 }} onClick={() => onClick?.(editPoint)} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} > - {/* 玻璃态背景 */} -
- {/* 状态指示条 */} -
- - {/* 内容区域 */} -
- {/* 状态标签 */} - {statusText && ( -
-
- - {statusText} - -
- )} - - {/* 描述文本 */} -
- {editPoint.description} -
- - {/* 时间戳 */} -
- - {Math.floor(editPoint.timestamp)}s - - - {new Date(editPoint.updatedAt).toLocaleTimeString()} - -
+ {/* White text display with exact same styling as EditInput */} +
+
+ {editPoint.description}
- {/* 悬停时显示的操作按钮 */} - + {/* Interactive edit/delete buttons on hover */} +
{onEdit && ( - + )} {onDelete && ( - + )} - - - {/* 装饰性光效 */} -
+
- {/* 连接点指示器 */} -
+ {/* Status indicator for processing states */} + {editPoint.status === EditPointStatus.PROCESSING && ( +
+
+ Processing... +
+ )} + + {editPoint.status === EditPointStatus.FAILED && ( +
+
+ Failed - Click to retry +
+ )} )} diff --git a/components/pages/work-flow/video-edit/EditInput.tsx b/components/pages/work-flow/video-edit/EditInput.tsx index deb178c..7bb737e 100644 --- a/components/pages/work-flow/video-edit/EditInput.tsx +++ b/components/pages/work-flow/video-edit/EditInput.tsx @@ -37,11 +37,10 @@ export const EditInput: React.FC = ({ isSubmitting = false, onSubmit, onCancel, - size = { width: 280, height: 120 }, - placeholder = "Describe your edit request..." + size = { width: 300, height: 50 }, + placeholder = "Describe your edit..." }) => { const [description, setDescription] = useState(editPoint.description || ''); - const [isFocused, setIsFocused] = useState(false); const textareaRef = useRef(null); const containerRef = useRef(null); @@ -116,7 +115,6 @@ export const EditInput: React.FC = ({ style={{ left: position.x, top: position.y, - width: size.width, }} initial={{ opacity: 0, @@ -140,105 +138,33 @@ export const EditInput: React.FC = ({ duration: 0.3 }} > - {/* 玻璃态背景容器 */} -
- {/* 头部 */} -
-
-
- - Edit Request - - - {Math.floor(editPoint.timestamp)}s - -
- -
+ {/* Input interface - focused on input functionality only */} +
+ setDescription(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={placeholder} + disabled={isSubmitting} + className="flex-1 bg-transparent text-gray-800 placeholder-gray-400 text-sm border-none outline-none min-w-[200px]" + autoFocus + /> - {/* 输入区域 */} -
-