兼容 竖屏 样式

This commit is contained in:
北枳 2025-09-22 19:36:37 +08:00
parent 735d90bbd7
commit 93a7b2e68a
8 changed files with 112 additions and 73 deletions

View File

@ -367,6 +367,8 @@ export interface VideoFlowProjectResponse {
final_simple_video: string; final_simple_video: string;
/** 最终视频 */ /** 最终视频 */
final_video: string; final_video: string;
/** 画面比例 */
aspect_ratio: string;
} }
/** /**
* *

View File

@ -50,7 +50,7 @@ import { PcTemplateModal } from "./PcTemplateModal";
import { H5TemplateDrawer } from "./H5TemplateDrawer"; import { H5TemplateDrawer } from "./H5TemplateDrawer";
import { PcPhotoStoryModal } from "./PcPhotoStoryModal"; import { PcPhotoStoryModal } from "./PcPhotoStoryModal";
import { H5PhotoStoryDrawer } from "./H5PhotoStoryDrawer"; import { H5PhotoStoryDrawer } from "./H5PhotoStoryDrawer";
import { AspectRatioSelector } from "./AspectRatioSelector"; import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector";
const LauguageOptions = [ const LauguageOptions = [
{ value: "english", label: "English", isVip: false, code:'EN' }, { value: "english", label: "English", isVip: false, code:'EN' },
@ -131,7 +131,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
language: string; language: string;
videoDuration: string; videoDuration: string;
expansion_mode: boolean; expansion_mode: boolean;
aspect_ratio: "VIDEO_ASPECT_RATIO_LANDSCAPE" | "VIDEO_ASPECT_RATIO_PORTRAIT"; aspect_ratio: AspectRatioValue;
}; };
const [configOptions, setConfigOptions] = useState<ConfigOptions>({ const [configOptions, setConfigOptions] = useState<ConfigOptions>({

View File

@ -29,6 +29,8 @@ export default function CreateToVideo2() {
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
const [userId, setUserId] = useState<number>(0); const [userId, setUserId] = useState<number>(0);
const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false); const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false);
/** 保存每个项目的视频朝向 */
const [orientationMap, setOrientationMap] = useState<Record<string, 'portrait' | 'landscape'>>({});
// 添加一个 ref 来跟踪当前正在加载的页码 // 添加一个 ref 来跟踪当前正在加载的页码
const loadingPageRef = useRef<number | null>(null); const loadingPageRef = useRef<number | null>(null);
@ -36,46 +38,14 @@ export default function CreateToVideo2() {
// 在客户端挂载后读取localStorage // 在客户端挂载后读取localStorage
// 监听滚动事件,实现无限加载 // 监听滚动事件,实现无限加载
// 修改滚动处理函数,添加节流 // 修改滚动处理函数,添加节流
const handleScroll = useCallback(() => { // 修改获取剧集列表函数(提前定义,供滚动与初始化调用)
if (!scrollContainerRef.current || !hasMore || isLoadingMore || isLoading) return; const getEpisodeList = useCallback(async (userId: number, page: number = 1, loadMore: boolean = false) => {
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
if (scrollHeight - scrollTop - clientHeight < 100) {
// 直接使用 currentPage不再使用 setCurrentPage 的回调
const nextPage = currentPage + 1;
if (nextPage <= totalPages) {
getEpisodeList(userId, nextPage, true);
}
}
}, [hasMore, isLoadingMore, isLoading, totalPages, userId, currentPage]);
useEffect(() => {
if (typeof window !== 'undefined') {
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
console.log('currentUser', currentUser);
setUserId(currentUser.id);
getEpisodeList(currentUser.id, 1, false);
}
}, []);
// 添加滚动监听
useEffect(() => {
const scrollContainer = scrollContainerRef.current;
if (scrollContainer) {
scrollContainer.addEventListener('scroll', handleScroll);
return () => scrollContainer.removeEventListener('scroll', handleScroll);
}
}, [handleScroll]);
// 修改获取剧集列表函数
const getEpisodeList = async (userId: number, page: number = 1, loadMore: boolean = false) => {
// 检查是否正在加载该页 // 检查是否正在加载该页
if (loadingPageRef.current === page) return; if (loadingPageRef.current === page) return;
if (isLoading || (isLoadingMore && !loadMore)) return;
// 设置当前正在加载的页码 // 设置当前正在加载的页码
loadingPageRef.current = page; loadingPageRef.current = page;
if (loadMore) { if (loadMore) {
setIsLoadingMore(true); setIsLoadingMore(true);
} else { } else {
@ -93,7 +63,7 @@ export default function CreateToVideo2() {
if (episodeListResponse.code === 0) { if (episodeListResponse.code === 0) {
const { movie_projects, total_pages } = episodeListResponse.data; const { movie_projects, total_pages } = episodeListResponse.data;
// 确保数据不重复 // 确保数据不重复
if (loadMore) { if (loadMore) {
setEpisodeList(prev => { setEpisodeList(prev => {
@ -105,7 +75,7 @@ export default function CreateToVideo2() {
} else { } else {
setEpisodeList(movie_projects); setEpisodeList(movie_projects);
} }
setTotalPages(total_pages); setTotalPages(total_pages);
setHasMore(page < total_pages); setHasMore(page < total_pages);
setCurrentPage(page); setCurrentPage(page);
@ -119,7 +89,39 @@ export default function CreateToVideo2() {
// 清除当前加载页码 // 清除当前加载页码
loadingPageRef.current = null; loadingPageRef.current = null;
} }
}; }, [perPage]);
const handleScroll = useCallback(() => {
if (!scrollContainerRef.current || !hasMore || isLoadingMore || isLoading) return;
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
if (scrollHeight - scrollTop - clientHeight < 100) {
// 直接使用 currentPage不再使用 setCurrentPage 的回调
const nextPage = currentPage + 1;
if (nextPage <= totalPages) {
getEpisodeList(userId, nextPage, true);
}
}
}, [hasMore, isLoadingMore, isLoading, totalPages, userId, currentPage, getEpisodeList]);
useEffect(() => {
if (typeof window !== 'undefined') {
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
console.log('currentUser', currentUser);
setUserId(currentUser.id);
getEpisodeList(currentUser.id, 1, false);
}
}, [getEpisodeList]);
// 添加滚动监听
useEffect(() => {
const scrollContainer = scrollContainerRef.current;
if (scrollContainer) {
scrollContainer.addEventListener('scroll', handleScroll);
return () => scrollContainer.removeEventListener('scroll', handleScroll);
}
}, [handleScroll]);
// 原函数位置已上移
// 视频上传和创建功能已迁移到ChatInputBox组件中 // 视频上传和创建功能已迁移到ChatInputBox组件中
@ -188,19 +190,38 @@ export default function CreateToVideo2() {
} }
}; };
/**
* /
* @param {string} projectId -
* @param {HTMLVideoElement} videoEl -
*/
const handleVideoMetadata = (projectId: string, videoEl: HTMLVideoElement) => {
const { videoWidth, videoHeight } = videoEl;
if (!videoWidth || !videoHeight) return;
setOrientationMap((prev) => {
if (prev[projectId]) return prev;
const next = videoWidth >= videoHeight ? 'landscape' : 'portrait';
return { ...prev, [projectId]: next };
});
};
const renderProjectCard = (project: any) => { const renderProjectCard = (project: any) => {
return ( return (
<LazyLoad once> <LazyLoad once>
<div <div
key={project.project_id} key={project.project_id}
className="group flex flex-col bg-black/20 rounded-lg overflow-hidden cursor-pointer hover:bg-white/5 transition-all duration-300" className="group flex flex-col bg-black/20 rounded-lg overflow-hidden cursor-pointer hover:bg-white/5 transition-all duration-300 mb-6 break-inside-avoid"
onMouseEnter={() => handleMouseEnter(project.project_id)} onMouseEnter={() => handleMouseEnter(project.project_id)}
onMouseLeave={() => handleMouseLeave(project.project_id)} onMouseLeave={() => handleMouseLeave(project.project_id)}
data-alt="project-card" data-alt="project-card"
> >
{/* 视频/图片区域 */} {/* 视频/图片区域 */}
<div className="relative aspect-video" onClick={() => router.push(`/movies/work-flow?episodeId=${project.project_id}`)}> <div
className={`relative ${orientationMap[project.project_id] === 'portrait' ? 'aspect-[9/16]' : 'aspect-video'}`}
onClick={() => router.push(`/movies/work-flow?episodeId=${project.project_id}`)}
data-alt="media-container"
>
{(project.final_video_url || project.final_simple_video_url || project.video_urls) ? ( {(project.final_video_url || project.final_simple_video_url || project.video_urls) ? (
<video <video
ref={(el) => setVideoRef(project.project_id, el)} ref={(el) => setVideoRef(project.project_id, el)}
@ -209,7 +230,8 @@ export default function CreateToVideo2() {
muted muted
loop loop
playsInline playsInline
preload="none" preload="metadata"
onLoadedMetadata={(e) => handleVideoMetadata(project.project_id, e.currentTarget)}
poster={ poster={
getFirstFrame(project.final_video_url || project.final_simple_video_url || project.video_urls) getFirstFrame(project.final_video_url || project.final_simple_video_url || project.video_urls)
} }
@ -259,7 +281,7 @@ export default function CreateToVideo2() {
</Tooltip> */} </Tooltip> */}
</div> </div>
{/* TODO 删除 */} {/* TODO 删除 */}
{/* <Tooltip title="Delete"> {/* <Tooltip title="Delete"></Tooltip>
<Button size="small" type="text" className="w-[2.5rem] h-[2.5rem] rounded-full items-center justify-center p-0 hidden group-hover:flex transition-all duration-300 hover:bg-white/15" /></Button> <Button size="small" type="text" className="w-[2.5rem] h-[2.5rem] rounded-full items-center justify-center p-0 hidden group-hover:flex transition-all duration-300 hover:bg-white/15" /></Button>
</Tooltip> */} </Tooltip> */}
</div> </div>
@ -282,7 +304,7 @@ export default function CreateToVideo2() {
{episodeList.length > 0 && ( {episodeList.length > 0 && (
/* 优化的剧集网格 */ /* 优化的剧集网格 */
<div className="pb-8"> <div className="pb-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="columns-1 sm:columns-2 lg:columns-3" style={{ columnGap: '1.5rem' }} data-alt="masonry-grid">
{episodeList.map(renderProjectCard)} {episodeList.map(renderProjectCard)}
</div> </div>

View File

@ -222,7 +222,8 @@ const WorkFlow = React.memo(function WorkFlow() {
showGotoCutButton, showGotoCutButton,
generateEditPlan, generateEditPlan,
handleRetryVideo, handleRetryVideo,
isShowAutoEditing isShowAutoEditing,
aspectRatio
} = useWorkflowData({ } = useWorkflowData({
onEditPlanGenerated: handleEditPlanGenerated, onEditPlanGenerated: handleEditPlanGenerated,
editingStatus: editingStatus, editingStatus: editingStatus,
@ -537,6 +538,7 @@ Please process this video editing request.`;
onRetryVideo={handleRetryVideo} onRetryVideo={handleRetryVideo}
className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[40%]')} className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[40%]')}
selectedView={selectedView} selectedView={selectedView}
aspectRatio={aspectRatio}
/> />
</div> </div>
)} )}
@ -622,7 +624,7 @@ Please process this video editing request.`;
</div> </div>
{/* 智能对话弹窗 */} {/* 智能对话弹窗 */}
<Drawer {/* <Drawer
width="25%" width="25%"
placement="right" placement="right"
closable={false} closable={false}
@ -662,7 +664,7 @@ Please process this video editing request.`;
}} }}
aiEditingResult={aiEditingResult} aiEditingResult={aiEditingResult}
/> />
</Drawer> </Drawer> */}
<EditModal <EditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}

View File

@ -164,7 +164,7 @@ export function H5MediaViewer({
// 渲染视频 slide // 渲染视频 slide
const renderVideoSlides = () => ( const renderVideoSlides = () => (
<div data-alt="carousel-wrapper" className="relative w-full aspect-video min-h-[200px] overflow-hidden rounded-lg"> <div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg">
<Carousel <Carousel
ref={carouselRef} ref={carouselRef}
key={`h5-carousel-video-${stage}-${videoUrls.length}`} key={`h5-carousel-video-${stage}-${videoUrls.length}`}
@ -187,7 +187,7 @@ export function H5MediaViewer({
<> <>
<video <video
ref={(el) => (videoRefs.current[idx] = el)} ref={(el) => (videoRefs.current[idx] = el)}
className="w-full h-full object-cover [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black" className="w-full h-full object-contain [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black"
src={url} src={url}
preload="metadata" preload="metadata"
playsInline playsInline
@ -248,7 +248,7 @@ export function H5MediaViewer({
</div> </div>
</> </>
) : ( ) : (
<div className="w-full aspect-video min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status"> <div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status">
{status === 0 && ( {status === 0 && (
<span className="text-blue-500 text-base">Generating...</span> <span className="text-blue-500 text-base">Generating...</span>
)} )}
@ -285,7 +285,7 @@ export function H5MediaViewer({
// 渲染图片 slide // 渲染图片 slide
const renderImageSlides = () => ( const renderImageSlides = () => (
<div data-alt="carousel-wrapper" className="relative w-full aspect-video min-h-[200px] overflow-hidden rounded-lg"> <div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg">
<Carousel <Carousel
ref={carouselRef} ref={carouselRef}
key={`h5-carousel-image-${stage}-${imageUrls.length}`} key={`h5-carousel-image-${stage}-${imageUrls.length}`}
@ -299,7 +299,7 @@ export function H5MediaViewer({
> >
{imageUrls.map((url, idx) => ( {imageUrls.map((url, idx) => (
<div key={`h5-image-${idx}`} data-alt="image-slide" className="relative w-full h-full"> <div key={`h5-image-${idx}`} data-alt="image-slide" className="relative w-full h-full">
<img src={url} alt="scene" className="w-full h-full object-cover" /> <img src={url} alt="scene" className="w-full h-full object-contain" />
</div> </div>
))} ))}
</Carousel> </Carousel>
@ -416,7 +416,7 @@ export function H5MediaViewer({
data-alt="final-thumb-item" data-alt="final-thumb-item"
aria-label="Select final video" aria-label="Select final video"
> >
<img src={getFirstFrame(taskObject.final.url)} alt="final" className="w-full h-auto object-cover" /> <img src={getFirstFrame(taskObject.final.url)} alt="final" className="w-full h-auto object-contain" />
<div className="text-[10px] text-white/80 text-center py-0.5">Final</div> <div className="text-[10px] text-white/80 text-center py-0.5">Final</div>
</button> </button>
</div> </div>

View File

@ -176,7 +176,7 @@ export const MediaViewer = React.memo(function MediaViewer({
return ( return (
<video <video
ref={finalVideoRef} ref={finalVideoRef}
className="w-full h-full object-cover rounded-lg" className="w-full h-full object-contain rounded-lg"
src={taskObject.final.url} src={taskObject.final.url}
autoPlay={isFinalVideoPlaying} autoPlay={isFinalVideoPlaying}
loop loop
@ -345,7 +345,7 @@ export const MediaViewer = React.memo(function MediaViewer({
transition={{ duration: 0.8, ease: "easeInOut" }} transition={{ duration: 0.8, ease: "easeInOut" }}
> >
<video <video
className="w-full h-full rounded-lg object-cover object-center" className="w-full h-full rounded-lg object-contain object-center"
src={taskObject.final.url} src={taskObject.final.url}
loop loop
playsInline playsInline
@ -490,7 +490,7 @@ export const MediaViewer = React.memo(function MediaViewer({
<video <video
ref={mainVideoRef} ref={mainVideoRef}
key={taskObject.videos.data[currentSketchIndex].urls[0]} key={taskObject.videos.data[currentSketchIndex].urls[0]}
className="w-full h-full rounded-lg object-cover object-center relative z-10" className="w-full h-full rounded-lg object-contain object-center relative z-10"
src={taskObject.videos.data[currentSketchIndex].urls[0]} src={taskObject.videos.data[currentSketchIndex].urls[0]}
poster={getFirstFrame(taskObject.videos.data[currentSketchIndex].urls[0])} poster={getFirstFrame(taskObject.videos.data[currentSketchIndex].urls[0])}
preload="none" preload="none"
@ -655,7 +655,7 @@ export const MediaViewer = React.memo(function MediaViewer({
<motion.img <motion.img
key={currentSketch.url} key={currentSketch.url}
src={currentSketch.url} src={currentSketch.url}
className="w-full h-full rounded-lg object-cover" className="w-full h-full rounded-lg object-contain"
// 用 circle clip-path 实现“扩散” // 用 circle clip-path 实现“扩散”
initial={{ clipPath: "circle(0% at 50% 50%)" }} initial={{ clipPath: "circle(0% at 50% 50%)" }}
animate={{ clipPath: "circle(150% at 50% 50%)" }} animate={{ clipPath: "circle(150% at 50% 50%)" }}

View File

@ -7,6 +7,7 @@ import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
import { Loader2, X, SquareUserRound, MapPinHouse, Clapperboard, Video, RotateCcw, CircleAlert } from 'lucide-react'; import { Loader2, X, SquareUserRound, MapPinHouse, Clapperboard, Video, RotateCcw, CircleAlert } from 'lucide-react';
import { TaskObject } from '@/api/DTO/movieEdit'; import { TaskObject } from '@/api/DTO/movieEdit';
import { getFirstFrame } from '@/utils/tools'; import { getFirstFrame } from '@/utils/tools';
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
interface ThumbnailGridProps { interface ThumbnailGridProps {
isDisabledFocus: boolean; isDisabledFocus: boolean;
@ -16,6 +17,7 @@ interface ThumbnailGridProps {
onRetryVideo: (video_id: string) => void; onRetryVideo: (video_id: string) => void;
className: string; className: string;
selectedView?: 'final' | 'video' | null; selectedView?: 'final' | 'video' | null;
aspectRatio: AspectRatioValue;
} }
/** /**
@ -28,7 +30,8 @@ export function ThumbnailGrid({
onSketchSelect, onSketchSelect,
onRetryVideo, onRetryVideo,
className, className,
selectedView selectedView,
aspectRatio
}: ThumbnailGridProps) { }: ThumbnailGridProps) {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null); const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
@ -186,8 +189,10 @@ export function ThumbnailGrid({
return ( return (
<div <div
key={`video-${urls}-${index}`} key={`video-${urls}-${index}`}
className={`relative aspect-video rounded-lg overflow-hidden className={`relative aspect-auto rounded-lg overflow-hidden
${(currentSketchIndex === index && !disabled && selectedView !== 'final') ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`} ${(currentSketchIndex === index && !disabled && selectedView !== 'final') ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}
${aspectRatio === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? 'min-w-[200px]' : 'min-w-[70px]'}
`}
onClick={() => !isDragging && !disabled && onSketchSelect(index)} onClick={() => !isDragging && !disabled && onSketchSelect(index)}
> >
@ -208,7 +213,7 @@ export function ThumbnailGrid({
{taskObject.videos.data[index].urls && taskObject.videos.data[index].urls.length > 0 ? ( {taskObject.videos.data[index].urls && taskObject.videos.data[index].urls.length > 0 ? (
// <video // <video
// className="w-full h-full object-cover" // className="w-full h-full object-contain"
// src={taskObject.videos.data[index].urls[0]} // src={taskObject.videos.data[index].urls[0]}
// playsInline // playsInline
// loop // loop
@ -220,14 +225,14 @@ export function ThumbnailGrid({
onMouseLeave={() => handleMouseLeave(index)} onMouseLeave={() => handleMouseLeave(index)}
> >
<img <img
className="w-full h-full object-cover" className="w-full h-full object-contain"
src={getFirstFrame(taskObject.videos.data[index].urls[0])} src={getFirstFrame(taskObject.videos.data[index].urls[0])}
draggable="false" draggable="false"
alt="video thumbnail" alt="video thumbnail"
/> />
{hoveredIndex === index && ( {hoveredIndex === index && (
<video <video
className="absolute inset-0 w-full h-full object-cover" className="absolute inset-0 w-full h-full object-contain"
src={taskObject.videos.data[index].urls[0]} src={taskObject.videos.data[index].urls[0]}
autoPlay autoPlay
muted muted
@ -249,7 +254,7 @@ export function ThumbnailGrid({
<div className='absolute bottom-0 left-0 right-0 p-2'> <div className='absolute bottom-0 left-0 right-0 p-2'>
<div className="inline-flex items-center px-2 py-1 rounded-full bg-green-500/20 backdrop-blur-sm"> <div className="inline-flex items-center px-2 py-1 rounded-full bg-green-500/20 backdrop-blur-sm">
<Video className="w-3 h-3 text-green-400 mr-1" /> <Video className="w-3 h-3 text-green-400 mr-1" />
<span className="text-xs text-green-400">Shot {index + 1}</span> <span className="text-xs text-green-400">{index + 1}</span>
</div> </div>
</div> </div>
@ -269,8 +274,10 @@ export function ThumbnailGrid({
return ( return (
<div <div
key={sketch.uniqueId || `sketch-${sketch.url}-${index}`} key={sketch.uniqueId || `sketch-${sketch.url}-${index}`}
className={`relative aspect-video rounded-lg overflow-hidden className={`relative aspect-auto rounded-lg overflow-hidden
${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`} ${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}
${aspectRatio === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? 'min-w-[200px]' : 'min-w-[70px]'}
`}
onClick={() => !isDragging && onSketchSelect(index)} onClick={() => !isDragging && onSketchSelect(index)}
> >
@ -293,7 +300,7 @@ export function ThumbnailGrid({
{(sketch.status === 1) && ( {(sketch.status === 1) && (
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500"> <div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
<img <img
className="w-full h-full object-cover select-none" className="w-full h-full object-contain select-none"
src={sketch.url} src={sketch.url}
draggable="false" draggable="false"
alt={sketch.type ? String(sketch.type) : 'sketch'} alt={sketch.type ? String(sketch.type) : 'sketch'}
@ -319,7 +326,7 @@ export function ThumbnailGrid({
{(!sketch.type || sketch.type === 'shot_sketch') && ( {(!sketch.type || sketch.type === 'shot_sketch') && (
<div className="inline-flex items-center px-2 py-1 rounded-full bg-cyan-500/20 backdrop-blur-sm"> <div className="inline-flex items-center px-2 py-1 rounded-full bg-cyan-500/20 backdrop-blur-sm">
<Clapperboard className="w-3 h-3 text-cyan-400 mr-1" /> <Clapperboard className="w-3 h-3 text-cyan-400 mr-1" />
<span className="text-xs text-cyan-400">Shot {index + 1}</span> <span className="text-xs text-cyan-400">{index + 1}</span>
</div> </div>
)} )}
</div> </div>
@ -337,7 +344,7 @@ export function ThumbnailGrid({
<div <div
ref={thumbnailsRef} ref={thumbnailsRef}
tabIndex={0} tabIndex={0}
className={`w-full h-full grid grid-flow-col gap-2 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none ${className}`} className={`w-full h-full grid grid-flow-col gap-2 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none ${className} auto-cols-max`}
autoFocus autoFocus
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}

View File

@ -6,6 +6,7 @@ import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovi
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';
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
interface UseWorkflowDataProps { interface UseWorkflowDataProps {
onEditPlanGenerated?: () => void; onEditPlanGenerated?: () => void;
@ -91,6 +92,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
const [isLoadingGenerateEditPlan, setIsLoadingGenerateEditPlan] = useState(false); const [isLoadingGenerateEditPlan, setIsLoadingGenerateEditPlan] = useState(false);
const [state, setState] = useState({ const [state, setState] = useState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto', mode: 'automatic' as 'automatic' | 'manual' | 'auto',
aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE' as AspectRatioValue,
originalText: '', originalText: '',
isLoading: true isLoading: true
}); });
@ -529,6 +531,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
try { try {
setState({ setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto', mode: 'automatic' as 'automatic' | 'manual' | 'auto',
aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE' as AspectRatioValue,
originalText: '', originalText: '',
isLoading: true isLoading: true
}); });
@ -540,7 +543,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
throw new Error(response.message); throw new Error(response.message);
} }
const { status, data, tags, mode, original_text, title, name, final_simple_video, final_video } = response.data; const { status, data, tags, mode, original_text, aspect_ratio, name, final_simple_video, final_video } = response.data;
const { current: taskCurrent } = tempTaskObject; const { current: taskCurrent } = tempTaskObject;
@ -681,6 +684,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
setState({ setState({
mode: mode as 'automatic' | 'manual' | 'auto', mode: mode as 'automatic' | 'manual' | 'auto',
aspectRatio: aspect_ratio as AspectRatioValue,
originalText: original_text, originalText: original_text,
isLoading: false isLoading: false
}); });
@ -701,6 +705,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
setNeedStreamData(true); setNeedStreamData(true);
setState({ setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto', mode: 'automatic' as 'automatic' | 'manual' | 'auto',
aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE' as AspectRatioValue,
originalText: '', originalText: '',
isLoading: false isLoading: false
}); });
@ -781,6 +786,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
showGotoCutButton: (canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') || isShowError) ? true : false, showGotoCutButton: (canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') || isShowError) ? true : false,
generateEditPlan: openEditPlan, generateEditPlan: openEditPlan,
handleRetryVideo, handleRetryVideo,
isShowAutoEditing: canGoToCut && taskObject.currentStage !== 'final_video' && isGenerateEditPlan && !isShowError ? true : false isShowAutoEditing: canGoToCut && taskObject.currentStage !== 'final_video' && isGenerateEditPlan && !isShowError ? true : false,
aspectRatio: state.aspectRatio
}; };
} }