forked from 77media/video-flow
侧边栏
This commit is contained in:
parent
b543e28b23
commit
1e4be3b1a0
@ -1,13 +1,14 @@
|
|||||||
import { DashboardLayout } from "@/components/layout/dashboard-layout";
|
|
||||||
|
import { TopBar } from "@/components/layout/top-bar";
|
||||||
import { HomePage2 } from "@/components/pages/home-page2";
|
import { HomePage2 } from "@/components/pages/home-page2";
|
||||||
import OAuthCallbackHandler from "@/components/ui/oauth-callback-handler";
|
import OAuthCallbackHandler from "@/components/ui/oauth-callback-handler";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<>
|
||||||
{/* Handle OAuth callbacks */}
|
<TopBar collapsed={true} />
|
||||||
<OAuthCallbackHandler />
|
<OAuthCallbackHandler />
|
||||||
<HomePage2 />
|
<HomePage2 />
|
||||||
</DashboardLayout>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -673,7 +673,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="video-tool-component relative max-w-[1080px] w-full left-[50%] translate-x-[-50%]" style={noData ? {
|
<div className="z-[9] bottom-[1.5rem] absolute w-[calc(88%-2rem)] max-w-[1080px] left-[50%] translate-x-[-50%]" style={noData ? {
|
||||||
top: '50%'
|
top: '50%'
|
||||||
} : {}}>
|
} : {}}>
|
||||||
{/* 视频故事板工具面板 - 毛玻璃效果背景 */}
|
{/* 视频故事板工具面板 - 毛玻璃效果背景 */}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useRef, useCallback, useState, useEffect } from "react";
|
import React, { useRef, useCallback, useState, useEffect } from "react";
|
||||||
import { ArrowRightFromLine, ChevronDown } from 'lucide-react';
|
import { ChevronsRight, ChevronDown } from 'lucide-react';
|
||||||
import { Switch } from 'antd';
|
import { Switch } from 'antd';
|
||||||
import { MessageRenderer } from "./MessageRenderer";
|
import { MessageRenderer } from "./MessageRenderer";
|
||||||
import { InputBar } from "./InputBar";
|
import { InputBar } from "./InputBar";
|
||||||
@ -133,16 +133,16 @@ export default function SmartChatBox({
|
|||||||
<span>Chat</span>
|
<span>Chat</span>
|
||||||
{/* System push toggle */}
|
{/* System push toggle */}
|
||||||
<Switch
|
<Switch
|
||||||
checkedChildren="System: On"
|
checkedChildren="On"
|
||||||
unCheckedChildren="System: Off"
|
unCheckedChildren="Off"
|
||||||
checked={systemPush}
|
checked={systemPush}
|
||||||
onChange={toggleSystemPush}
|
onChange={toggleSystemPush}
|
||||||
className="ml-2 "
|
className="ml-2 "
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs opacity-70">
|
<div className="text-xs opacity-70">
|
||||||
<ArrowRightFromLine
|
<ChevronsRight
|
||||||
className="w-4 h-4 cursor-pointer"
|
className="w-6 h-6 cursor-pointer"
|
||||||
onClick={() => setIsSmartChatBoxOpen(false)}
|
onClick={() => setIsSmartChatBoxOpen(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,12 +9,18 @@ interface DashboardLayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function DashboardLayout({ children }: DashboardLayoutProps) {
|
export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(true); // 默认收起状态
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" min-h-screen bg-background">
|
<div className=" min-h-screen bg-background">
|
||||||
<TopBar collapsed={sidebarCollapsed} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} />
|
<TopBar collapsed={sidebarCollapsed} />
|
||||||
{children}
|
<Sidebar collapsed={sidebarCollapsed} onToggle={setSidebarCollapsed} />
|
||||||
|
<div className="h-[calc(100vh-4rem)] top-[4rem] fixed right-0 bottom-0 z-[999]" style={{
|
||||||
|
left: sidebarCollapsed ? '4rem' : '16rem',
|
||||||
|
width: sidebarCollapsed ? 'calc(100vw - 4rem)' : 'calc(100vw - 16rem)'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import {
|
|||||||
Video,
|
Video,
|
||||||
PanelsLeftBottom,
|
PanelsLeftBottom,
|
||||||
ArrowLeftToLine,
|
ArrowLeftToLine,
|
||||||
X
|
BookHeart,
|
||||||
|
PanelRightClose
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
@ -31,10 +32,7 @@ const navigationItems = [
|
|||||||
{
|
{
|
||||||
title: 'Main',
|
title: 'Main',
|
||||||
items: [
|
items: [
|
||||||
{ name: 'Home', href: '/', icon: Home },
|
{ name: 'My Portfolio', href: '/create', icon: BookHeart },
|
||||||
{ name: 'Media Library', href: '/media', icon: FolderOpen },
|
|
||||||
{ name: 'Actors Library', href: '/actors', icon: Users },
|
|
||||||
{ name: 'Task History', href: '/history', icon: History },
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -43,67 +41,56 @@ export function Sidebar({ collapsed, onToggle }: SidebarProps) {
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop */}
|
|
||||||
{!collapsed && (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 bg-[#000000bf] z-[998]"
|
|
||||||
onClick={() => onToggle(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div
|
<div
|
||||||
|
data-alt="sidebar-container"
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed left-0 top-0 z-[999] h-full w-64 bg-[#131416] transition-transform duration-300',
|
'fixed left-0 top-0 z-[999] h-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60',
|
||||||
collapsed ? '-translate-x-full' : 'translate-x-0'
|
'border-r border-r-[#ffffff14] transition-all duration-300 ease-in-out',
|
||||||
|
collapsed ? 'w-[3rem]' : 'w-[16rem]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<div className="flex h-16 items-center justify-between px-4">
|
{/* Toggle Button */}
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex h-16 items-center px-4">
|
||||||
<Video className="h-8 w-8 text-primary" />
|
|
||||||
<span className="text-xl font-bold">
|
|
||||||
<GradientText
|
|
||||||
text="MovieFlow"
|
|
||||||
startPercentage={30}
|
|
||||||
endPercentage={70}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
|
data-alt="toggle-sidebar"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="icon"
|
||||||
onClick={() => onToggle(true)}
|
className={cn(
|
||||||
className="button-NxtqWZ"
|
'h-4 w-4 rounded-full transition-transform duration-300 text-gray-300',
|
||||||
|
!collapsed && 'rotate-180'
|
||||||
|
)}
|
||||||
|
onClick={() => onToggle(!collapsed)}
|
||||||
>
|
>
|
||||||
<ArrowLeftToLine className="h-4 w-4" />
|
<PanelRightClose className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 space-y-1 px-1">
|
||||||
{navigationItems.map((section, index) => (
|
{navigationItems.map((section) => (
|
||||||
<div key={section.title}>
|
<div key={section.title} className="space-y-1">
|
||||||
<div className="space-y-1">
|
{section.items.map((item) => {
|
||||||
{section.items.map((item) => {
|
const isActive = pathname === item.href;
|
||||||
const isActive = pathname === item.href;
|
return (
|
||||||
return (
|
<Link key={item.name} href={item.href}>
|
||||||
<Link key={item.name} href={item.href}>
|
<Button
|
||||||
<Button
|
data-alt={`nav-item-${item.name}`}
|
||||||
variant={isActive ? 'secondary' : 'ghost'}
|
variant={isActive ? 'secondary' : 'ghost'}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full justify-start px-4',
|
'w-full justify-start',
|
||||||
isActive && 'bg-primary/10 text-primary hover:bg-primary/20'
|
collapsed ? 'px-3' : 'px-4',
|
||||||
)}
|
isActive && 'bg-primary/10 text-primary hover:bg-primary/20'
|
||||||
>
|
)}
|
||||||
<item.icon className="h-4 w-4" />
|
>
|
||||||
<span className="ml-2">{item.name}</span>
|
<item.icon className={cn('h-4 w-4 shrink-0 text-gray-300', !collapsed && 'mr-2')} />
|
||||||
</Button>
|
{!collapsed && <span>{item.name}</span>}
|
||||||
</Link>
|
</Button>
|
||||||
);
|
</Link>
|
||||||
})}
|
);
|
||||||
</div>
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -35,10 +35,8 @@ interface User {
|
|||||||
|
|
||||||
export function TopBar({
|
export function TopBar({
|
||||||
collapsed,
|
collapsed,
|
||||||
onToggleSidebar,
|
|
||||||
}: {
|
}: {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
onToggleSidebar: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
@ -170,10 +168,10 @@ export function TopBar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed right-0 top-0 left-0 h-16 header z-[999]"
|
className="fixed right-0 top-0 h-16 header z-[999]"
|
||||||
style={{ isolation: "isolate" }}
|
style={{ isolation: "isolate", left: collapsed ? '3rem' : '16rem' }}
|
||||||
>
|
>
|
||||||
<div className="h-full flex items-center justify-between pr-6 pl-6">
|
<div className="h-full flex items-center justify-between pr-6 pl-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div
|
<div
|
||||||
className={`flex items-center cursor-pointer space-x-1 link-logo roll event-on`}
|
className={`flex items-center cursor-pointer space-x-1 link-logo roll event-on`}
|
||||||
@ -265,8 +263,13 @@ export function TopBar({
|
|||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
}}
|
}}
|
||||||
data-alt="user-menu-trigger"
|
data-alt="user-menu-trigger"
|
||||||
|
style={{
|
||||||
|
background: 'unset !important'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<User className="h-4 w-4" />
|
<div className="h-10 w-10 rounded-full bg-[#C73BFF] flex items-center justify-center text-white font-semibold">
|
||||||
|
{currentUser.username ? currentUser.username.charAt(0) : 'MF'}
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{mounted && isOpen
|
{mounted && isOpen
|
||||||
@ -292,7 +295,7 @@ export function TopBar({
|
|||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="h-10 w-10 rounded-full bg-[#C73BFF] flex items-center justify-center text-white font-semibold">
|
<div className="h-10 w-10 rounded-full bg-[#C73BFF] flex items-center justify-center text-white font-semibold">
|
||||||
MF
|
{currentUser.username ? currentUser.username.charAt(0) : 'MF'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@ -215,56 +215,51 @@ export default function CreateToVideo2() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col w-full h-full relative px-2">
|
||||||
<div className="flex flex-col absolute top-[5rem] left-0 right-0 bottom-[1rem] px-6">
|
<div
|
||||||
{/* 优化后的主要内容区域 */}
|
ref={scrollContainerRef}
|
||||||
<div className="flex-1 min-h-0">
|
className="h-full overflow-y-auto overflow-x-hidden custom-scrollbar"
|
||||||
<div
|
style={{
|
||||||
ref={scrollContainerRef}
|
scrollbarWidth: 'thin',
|
||||||
className="h-full overflow-y-auto overflow-x-hidden custom-scrollbar"
|
scrollbarColor: 'rgba(255,255,255,0.1) transparent'
|
||||||
style={{
|
}}
|
||||||
scrollbarWidth: 'thin',
|
>
|
||||||
scrollbarColor: 'rgba(255,255,255,0.1) transparent'
|
{episodeList.length > 0 && (
|
||||||
}}
|
/* 优化的剧集网格 */
|
||||||
>
|
<div className="pb-8">
|
||||||
{episodeList.length > 0 && (
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
/* 优化的剧集网格 */
|
{episodeList.map(renderProjectCard)}
|
||||||
<div className="pb-8">
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
{episodeList.map(renderProjectCard)}
|
{/* 加载更多指示器 */}
|
||||||
|
{isLoadingMore && (
|
||||||
|
<div className="flex justify-center py-12">
|
||||||
|
<div className="flex items-center gap-3 px-6 py-3 bg-black/30 backdrop-blur-xl border border-white/10 rounded-full">
|
||||||
|
<Loader2 className="w-5 h-5 animate-spin text-purple-400" />
|
||||||
|
<span className="text-white/90 font-medium">Loading more projects...</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 加载更多指示器 */}
|
{/* 到底提示 */}
|
||||||
{isLoadingMore && (
|
{!hasMore && episodeList.length > 0 && (
|
||||||
<div className="flex justify-center py-12">
|
<div className="flex justify-center py-12">
|
||||||
<div className="flex items-center gap-3 px-6 py-3 bg-black/30 backdrop-blur-xl border border-white/10 rounded-full">
|
<div className="text-center">
|
||||||
<Loader2 className="w-5 h-5 animate-spin text-purple-400" />
|
<div className="w-12 h-12 bg-black/30 backdrop-blur-xl border border-white/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||||
<span className="text-white/90 font-medium">Loading more projects...</span>
|
<Check className="w-6 h-6 text-purple-400" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<p className="text-white/70 text-sm">All projects loaded</p>
|
||||||
|
</div>
|
||||||
{/* 到底提示 */}
|
|
||||||
{!hasMore && episodeList.length > 0 && (
|
|
||||||
<div className="flex justify-center py-12">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="w-12 h-12 bg-black/30 backdrop-blur-xl border border-white/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
|
||||||
<Check className="w-6 h-6 text-purple-400" />
|
|
||||||
</div>
|
|
||||||
<p className="text-white/70 text-sm">All projects loaded</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 视频工具组件 - 使用独立组件 */}
|
{/* 视频工具组件 - 使用独立组件 */}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
<ChatInputBox noData={episodeList.length === 0} />
|
<ChatInputBox noData={episodeList.length === 0} />
|
||||||
}
|
}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
.video-tool-component {
|
.video-tool-component {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
bottom: 1.5rem;
|
bottom: 1.5rem;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import React, { useRef, useEffect, useCallback } from "react";
|
|||||||
import "./style/work-flow.css";
|
import "./style/work-flow.css";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { EditModal } from "@/components/ui/edit-modal";
|
import { EditModal } from "@/components/ui/edit-modal";
|
||||||
import { ErrorBoundary } from "@/components/ui/error-boundary";
|
|
||||||
import { TaskInfo } from "./work-flow/task-info";
|
import { TaskInfo } from "./work-flow/task-info";
|
||||||
import { MediaViewer } from "./work-flow/media-viewer";
|
import { MediaViewer } from "./work-flow/media-viewer";
|
||||||
import { ThumbnailGrid } from "./work-flow/thumbnail-grid";
|
import { ThumbnailGrid } from "./work-flow/thumbnail-grid";
|
||||||
@ -81,194 +80,186 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<div className="w-full overflow-hidden h-full px-[1rem] pb-[1rem]">
|
||||||
<div className="w-full overflow-hidden h-[calc(100vh-5rem)] absolute top-[4rem] left-0 right-0 px-[1rem]">
|
<div className="w-full h-full">
|
||||||
<div className="w-full h-full">
|
<div className="splashContainer-otuV_A">
|
||||||
<div className="splashContainer-otuV_A">
|
<div className="content-vPGYx8">
|
||||||
<div className="content-vPGYx8">
|
<div className="info-UUGkPJ">
|
||||||
<div className="info-UUGkPJ">
|
<TaskInfo
|
||||||
<ErrorBoundary>
|
taskObject={taskObject}
|
||||||
<TaskInfo
|
currentLoadingText={currentLoadingText}
|
||||||
taskObject={taskObject}
|
roles={taskObject.roles.data}
|
||||||
currentLoadingText={currentLoadingText}
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
roles={taskObject.roles.data}
|
showGotoCutButton={showGotoCutButton}
|
||||||
isPauseWorkFlow={isPauseWorkFlow}
|
onGotoCut={generateEditPlan}
|
||||||
showGotoCutButton={showGotoCutButton}
|
/>
|
||||||
onGotoCut={generateEditPlan}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="media-Ocdu1O rounded-lg">
|
</div>
|
||||||
<div
|
<div className="media-Ocdu1O rounded-lg">
|
||||||
className="videoContainer-qteKNi"
|
<div
|
||||||
ref={containerRef}
|
className="videoContainer-qteKNi"
|
||||||
>
|
ref={containerRef}
|
||||||
{dataLoadError ? (
|
>
|
||||||
|
{dataLoadError ? (
|
||||||
|
<motion.div
|
||||||
|
className="flex flex-col items-center justify-center w-full aspect-video rounded-lg bg-red-50 border-2 border-red-200"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex flex-col items-center justify-center w-full aspect-video rounded-lg bg-red-50 border-2 border-red-200"
|
className="flex items-center gap-3 mb-4"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ scale: 0.8 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ scale: 1 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.3, delay: 0.2 }}
|
||||||
>
|
>
|
||||||
<motion.div
|
<AlertCircle className="w-8 h-8 text-red-500" />
|
||||||
className="flex items-center gap-3 mb-4"
|
<h3 className="text-lg font-medium text-red-800">数据加载失败</h3>
|
||||||
initial={{ scale: 0.8 }}
|
|
||||||
animate={{ scale: 1 }}
|
|
||||||
transition={{ duration: 0.3, delay: 0.2 }}
|
|
||||||
>
|
|
||||||
<AlertCircle className="w-8 h-8 text-red-500" />
|
|
||||||
<h3 className="text-lg font-medium text-red-800">数据加载失败</h3>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<p className="text-red-600 text-center mb-6 max-w-md px-4">
|
|
||||||
{dataLoadError}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<motion.button
|
|
||||||
className="flex items-center gap-2 px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
|
|
||||||
onClick={() => retryLoadData?.()}
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
>
|
|
||||||
<RefreshCw className="w-4 h-4" />
|
|
||||||
重试加载
|
|
||||||
</motion.button>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : isLoading ? (
|
|
||||||
<Skeleton className="w-full aspect-video rounded-lg" />
|
<p className="text-red-600 text-center mb-6 max-w-md px-4">
|
||||||
) : (
|
{dataLoadError}
|
||||||
<div className={`heroVideo-FIzuK1 ${['final_video', 'script'].includes(taskObject.currentStage) ? 'h-[calc(100vh-6rem)] w-[calc((100vh-6rem)/9*16)]' : 'h-[calc(100vh-6rem-200px)] w-[calc((100vh-6rem-200px)/9*16)]'}`} style={{ aspectRatio: "16 / 9" }} key={taskObject.currentStage+'_'+currentSketchIndex}>
|
</p>
|
||||||
<ErrorBoundary>
|
|
||||||
<MediaViewer
|
<motion.button
|
||||||
taskObject={taskObject}
|
className="flex items-center gap-2 px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
|
||||||
scriptData={scriptData}
|
onClick={() => retryLoadData?.()}
|
||||||
currentSketchIndex={currentSketchIndex}
|
whileHover={{ scale: 1.05 }}
|
||||||
isVideoPlaying={isVideoPlaying}
|
whileTap={{ scale: 0.95 }}
|
||||||
onEditModalOpen={handleEditModalOpen}
|
>
|
||||||
onToggleVideoPlay={toggleVideoPlay}
|
<RefreshCw className="w-4 h-4" />
|
||||||
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
重试加载
|
||||||
setAnyAttribute={setAnyAttribute}
|
</motion.button>
|
||||||
isPauseWorkFlow={isPauseWorkFlow}
|
</motion.div>
|
||||||
applyScript={applyScript}
|
) : isLoading ? (
|
||||||
mode={mode}
|
<Skeleton className="w-full aspect-video rounded-lg" />
|
||||||
onOpenChat={() => setIsSmartChatBoxOpen(true)}
|
) : (
|
||||||
setVideoPreview={(url, id) => {
|
<div className={`heroVideo-FIzuK1 ${['final_video', 'script'].includes(taskObject.currentStage) ? 'h-[calc(100vh-6rem)] w-[calc((100vh-6rem)/9*16)]' : 'h-[calc(100vh-6rem-200px)] w-[calc((100vh-6rem-200px)/9*16)]'}`} style={{ aspectRatio: "16 / 9" }} key={taskObject.currentStage+'_'+currentSketchIndex}>
|
||||||
setPreviewVideoUrl(url);
|
<MediaViewer
|
||||||
setPreviewVideoId(id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
|
|
||||||
<div className="h-[123px] w-[calc((100vh-6rem-200px)/9*16)]">
|
|
||||||
<ThumbnailGrid
|
|
||||||
isDisabledFocus={isEditModalOpen || isPauseWorkFlow || isFocusChatInput}
|
|
||||||
taskObject={taskObject}
|
taskObject={taskObject}
|
||||||
|
scriptData={scriptData}
|
||||||
currentSketchIndex={currentSketchIndex}
|
currentSketchIndex={currentSketchIndex}
|
||||||
onSketchSelect={setCurrentSketchIndex}
|
isVideoPlaying={isVideoPlaying}
|
||||||
|
onEditModalOpen={handleEditModalOpen}
|
||||||
|
onToggleVideoPlay={toggleVideoPlay}
|
||||||
|
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
||||||
|
setAnyAttribute={setAnyAttribute}
|
||||||
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
|
applyScript={applyScript}
|
||||||
|
mode={mode}
|
||||||
|
onOpenChat={() => setIsSmartChatBoxOpen(true)}
|
||||||
|
setVideoPreview={(url, id) => {
|
||||||
|
setPreviewVideoUrl(url);
|
||||||
|
setPreviewVideoId(id);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
|
||||||
|
<div className="h-[123px] w-[calc((100vh-6rem-200px)/9*16)]">
|
||||||
|
<ThumbnailGrid
|
||||||
|
isDisabledFocus={isEditModalOpen || isPauseWorkFlow || isFocusChatInput}
|
||||||
|
taskObject={taskObject}
|
||||||
|
currentSketchIndex={currentSketchIndex}
|
||||||
|
onSketchSelect={setCurrentSketchIndex}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 暂停/播放按钮 */}
|
|
||||||
{
|
|
||||||
(taskObject.currentStage !== 'final_video') && (
|
|
||||||
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
|
|
||||||
<GlassIconButton
|
|
||||||
icon={isPauseWorkFlow ? Play : Pause}
|
|
||||||
size='md'
|
|
||||||
tooltip={isPauseWorkFlow ? "Play" : "Pause"}
|
|
||||||
onClick={() => setIsPauseWorkFlow(!isPauseWorkFlow)}
|
|
||||||
/>
|
|
||||||
{ !mode.includes('auto') && (
|
|
||||||
<GlassIconButton
|
|
||||||
icon={ChevronLast}
|
|
||||||
size='md'
|
|
||||||
tooltip="Next"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{/* 智能对话按钮 */}
|
|
||||||
<div className="absolute right-12 bottom-32 z-[49] flex gap-4">
|
|
||||||
<GlassIconButton
|
|
||||||
icon={MessageSquareText}
|
|
||||||
size='md'
|
|
||||||
tooltip={"Chat"}
|
|
||||||
onClick={() => setIsSmartChatBoxOpen(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 智能对话弹窗 */}
|
|
||||||
<Drawer
|
|
||||||
width="25%"
|
|
||||||
placement="right"
|
|
||||||
closable={false}
|
|
||||||
maskClosable={false}
|
|
||||||
open={isSmartChatBoxOpen}
|
|
||||||
getContainer={false}
|
|
||||||
autoFocus={false}
|
|
||||||
mask={false}
|
|
||||||
zIndex={49}
|
|
||||||
rootClassName="outline-none"
|
|
||||||
className="backdrop-blur-lg bg-black/30 border border-white/20 shadow-xl"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
borderBottomLeftRadius: 10,
|
|
||||||
borderTopLeftRadius: 10,
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
|
||||||
styles={{
|
|
||||||
body: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onClose={() => setIsSmartChatBoxOpen(false)}
|
|
||||||
>
|
|
||||||
<SmartChatBox
|
|
||||||
isSmartChatBoxOpen={isSmartChatBoxOpen}
|
|
||||||
setIsSmartChatBoxOpen={setIsSmartChatBoxOpen}
|
|
||||||
projectId={episodeId}
|
|
||||||
userId={userId}
|
|
||||||
previewVideoUrl={previewVideoUrl}
|
|
||||||
previewVideoId={previewVideoId}
|
|
||||||
setIsFocusChatInput={setIsFocusChatInput}
|
|
||||||
onClearPreview={() => {
|
|
||||||
setPreviewVideoUrl(null);
|
|
||||||
setPreviewVideoId(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
<ErrorBoundary>
|
|
||||||
<EditModal
|
|
||||||
isOpen={isEditModalOpen}
|
|
||||||
activeEditTab={activeEditTab}
|
|
||||||
onClose={() => {
|
|
||||||
SaveEditUseCase.clearData();
|
|
||||||
setIsEditModalOpen(false)
|
|
||||||
}}
|
|
||||||
taskObject={taskObject}
|
|
||||||
currentSketchIndex={currentSketchIndex}
|
|
||||||
roles={taskObject.roles.data}
|
|
||||||
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
|
||||||
isPauseWorkFlow={isPauseWorkFlow}
|
|
||||||
fallbackToStep={fallbackToStep}
|
|
||||||
originalText={originalText}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
|
||||||
|
{/* 暂停/播放按钮 */}
|
||||||
|
{
|
||||||
|
(taskObject.currentStage !== 'final_video') && (
|
||||||
|
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
|
||||||
|
<GlassIconButton
|
||||||
|
icon={isPauseWorkFlow ? Play : Pause}
|
||||||
|
size='md'
|
||||||
|
tooltip={isPauseWorkFlow ? "Play" : "Pause"}
|
||||||
|
onClick={() => setIsPauseWorkFlow(!isPauseWorkFlow)}
|
||||||
|
/>
|
||||||
|
{ !mode.includes('auto') && (
|
||||||
|
<GlassIconButton
|
||||||
|
icon={ChevronLast}
|
||||||
|
size='md'
|
||||||
|
tooltip="Next"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* 智能对话按钮 */}
|
||||||
|
<div className="absolute right-12 bottom-32 z-[49] flex gap-4">
|
||||||
|
<GlassIconButton
|
||||||
|
icon={MessageSquareText}
|
||||||
|
size='md'
|
||||||
|
tooltip={"Chat"}
|
||||||
|
onClick={() => setIsSmartChatBoxOpen(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 智能对话弹窗 */}
|
||||||
|
<Drawer
|
||||||
|
width="25%"
|
||||||
|
placement="right"
|
||||||
|
closable={false}
|
||||||
|
maskClosable={false}
|
||||||
|
open={isSmartChatBoxOpen}
|
||||||
|
getContainer={false}
|
||||||
|
autoFocus={false}
|
||||||
|
mask={false}
|
||||||
|
zIndex={49}
|
||||||
|
rootClassName="outline-none"
|
||||||
|
className="backdrop-blur-lg bg-black/30 border border-white/20 shadow-xl"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderBottomLeftRadius: 10,
|
||||||
|
borderTopLeftRadius: 10,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClose={() => setIsSmartChatBoxOpen(false)}
|
||||||
|
>
|
||||||
|
<SmartChatBox
|
||||||
|
isSmartChatBoxOpen={isSmartChatBoxOpen}
|
||||||
|
setIsSmartChatBoxOpen={setIsSmartChatBoxOpen}
|
||||||
|
projectId={episodeId}
|
||||||
|
userId={userId}
|
||||||
|
previewVideoUrl={previewVideoUrl}
|
||||||
|
previewVideoId={previewVideoId}
|
||||||
|
setIsFocusChatInput={setIsFocusChatInput}
|
||||||
|
onClearPreview={() => {
|
||||||
|
setPreviewVideoUrl(null);
|
||||||
|
setPreviewVideoId(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<EditModal
|
||||||
|
isOpen={isEditModalOpen}
|
||||||
|
activeEditTab={activeEditTab}
|
||||||
|
onClose={() => {
|
||||||
|
SaveEditUseCase.clearData();
|
||||||
|
setIsEditModalOpen(false)
|
||||||
|
}}
|
||||||
|
taskObject={taskObject}
|
||||||
|
currentSketchIndex={currentSketchIndex}
|
||||||
|
roles={taskObject.roles.data}
|
||||||
|
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
||||||
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
|
fallbackToStep={fallbackToStep}
|
||||||
|
originalText={originalText}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -492,7 +492,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Video className="w-4 h-4" />
|
<Video className="w-4 h-4" />
|
||||||
<span className="text-xs">Chat to edit</span>
|
<span className="text-xs">Edit with chat</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -42,7 +42,8 @@ const stageIconMap = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAG_COLORS = ['#A133FF', '#a1115e'];
|
const TAG_COLORS = ['#924eadcc', '#4c90a0', '#3b4a5a', '#957558'];
|
||||||
|
// const TAG_COLORS = ['#6bf5f9', '#92a6fc', '#ac71fd', '#c73dfe'];
|
||||||
|
|
||||||
// 阶段图标组件
|
// 阶段图标组件
|
||||||
const StageIcons = ({ currentStage, isExpanded, isPauseWorkFlow }: { currentStage: number, isExpanded: boolean, isPauseWorkFlow: boolean }) => {
|
const StageIcons = ({ currentStage, isExpanded, isPauseWorkFlow }: { currentStage: number, isExpanded: boolean, isPauseWorkFlow: boolean }) => {
|
||||||
@ -216,9 +217,13 @@ export function TaskInfo({
|
|||||||
{taskObject?.tags?.map((tag: string) => (
|
{taskObject?.tags?.map((tag: string) => (
|
||||||
<div
|
<div
|
||||||
key={tag}
|
key={tag}
|
||||||
className="text-sm text-white rounded-full px-2 py-1"
|
data-alt="tag-item"
|
||||||
style={{ backgroundColor: tagColors[tag] }}
|
className="flex items-center gap-2 text-sm text-[#ececec] rounded-full px-3 py-1.5 bg-white/10 backdrop-blur-sm shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-full"
|
||||||
|
style={{ backgroundColor: tagColors[tag] }}
|
||||||
|
/>
|
||||||
{tag}
|
{tag}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -254,7 +259,7 @@ export function TaskInfo({
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
>
|
||||||
<motion.div
|
{/* <motion.div
|
||||||
className="w-1.5 h-1.5 rounded-full"
|
className="w-1.5 h-1.5 rounded-full"
|
||||||
style={{ backgroundColor: stageColor }}
|
style={{ backgroundColor: stageColor }}
|
||||||
animate={!isPauseWorkFlow ? {
|
animate={!isPauseWorkFlow ? {
|
||||||
@ -266,7 +271,7 @@ export function TaskInfo({
|
|||||||
repeatDelay: 0.2
|
repeatDelay: 0.2
|
||||||
}
|
}
|
||||||
} : {}}
|
} : {}}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
{/* 阶段图标 */}
|
{/* 阶段图标 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -338,7 +343,7 @@ export function TaskInfo({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* 动态光点效果 */}
|
{/* 动态光点效果 */}
|
||||||
<motion.div
|
{/* <motion.div
|
||||||
className="absolute left-0 top-1/2 transform -translate-y-1/2 w-2 h-2 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full blur-sm"
|
className="absolute left-0 top-1/2 transform -translate-y-1/2 w-2 h-2 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full blur-sm"
|
||||||
animate={!isPauseWorkFlow ? {
|
animate={!isPauseWorkFlow ? {
|
||||||
x: [0, 200, 0],
|
x: [0, 200, 0],
|
||||||
@ -350,7 +355,7 @@ export function TaskInfo({
|
|||||||
ease: "easeInOut",
|
ease: "easeInOut",
|
||||||
}
|
}
|
||||||
}: {}}
|
}: {}}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
{/* 文字底部装饰线 */}
|
{/* 文字底部装饰线 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -368,7 +373,7 @@ export function TaskInfo({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
{/* <motion.div
|
||||||
className="w-1.5 h-1.5 rounded-full"
|
className="w-1.5 h-1.5 rounded-full"
|
||||||
style={{ backgroundColor: stageColor }}
|
style={{ backgroundColor: stageColor }}
|
||||||
animate={!isPauseWorkFlow ? {
|
animate={!isPauseWorkFlow ? {
|
||||||
@ -395,7 +400,7 @@ export function TaskInfo({
|
|||||||
delay: 0.3
|
delay: 0.3
|
||||||
}
|
}
|
||||||
} : {}}
|
} : {}}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
{/* 跳转剪辑按钮 */}
|
{/* 跳转剪辑按钮 */}
|
||||||
{showGotoCutButton && (
|
{showGotoCutButton && (
|
||||||
|
|||||||
@ -562,7 +562,7 @@ export function useWorkflowData() {
|
|||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
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 && currentLoadingText.includes('Post-production') ? true : false,
|
||||||
generateEditPlan
|
generateEditPlan
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const getProjectTaskList = async (data: {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${localStorage?.getItem('token') || 'mock-token'}`,
|
'Authorization': `Bearer ${localStorage?.getItem('token')}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
signal: controller.signal, // 添加超时控制
|
signal: controller.signal, // 添加超时控制
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user