侧边栏

This commit is contained in:
北枳 2025-08-30 19:24:23 +08:00
parent b543e28b23
commit 1e4be3b1a0
14 changed files with 289 additions and 411 deletions

View File

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

View File

@ -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%'
} : {}}> } : {}}>
{/* 视频故事板工具面板 - 毛玻璃效果背景 */} {/* 视频故事板工具面板 - 毛玻璃效果背景 */}

View File

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

View File

@ -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} />
<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} {children}
</div> </div>
</div>
); );
} }

View File

@ -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 },
], ],
} }
]; ];
@ -44,67 +42,56 @@ export function Sidebar({ collapsed, onToggle }: SidebarProps) {
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',
collapsed ? 'px-3' : 'px-4',
isActive && 'bg-primary/10 text-primary hover:bg-primary/20' isActive && 'bg-primary/10 text-primary hover:bg-primary/20'
)} )}
> >
<item.icon className="h-4 w-4" /> <item.icon className={cn('h-4 w-4 shrink-0 text-gray-300', !collapsed && 'mr-2')} />
<span className="ml-2">{item.name}</span> {!collapsed && <span>{item.name}</span>}
</Button> </Button>
</Link> </Link>
); );
})} })}
</div> </div>
</div>
))} ))}
</div> </div>
</div> </div>

View File

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

View File

@ -215,10 +215,7 @@ 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 className="flex-1 min-h-0">
<div <div
ref={scrollContainerRef} ref={scrollContainerRef}
className="h-full overflow-y-auto overflow-x-hidden custom-scrollbar" className="h-full overflow-y-auto overflow-x-hidden custom-scrollbar"
@ -258,13 +255,11 @@ export default function CreateToVideo2() {
</div> </div>
)} )}
</div> </div>
</div>
</div>
{/* 视频工具组件 - 使用独立组件 */} {/* 视频工具组件 - 使用独立组件 */}
{!isLoading && {!isLoading &&
<ChatInputBox noData={episodeList.length === 0} /> <ChatInputBox noData={episodeList.length === 0} />
} }
</> </div>
); );
} }

View File

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

View File

@ -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,13 +80,11 @@ 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">
<ErrorBoundary>
<TaskInfo <TaskInfo
taskObject={taskObject} taskObject={taskObject}
currentLoadingText={currentLoadingText} currentLoadingText={currentLoadingText}
@ -96,7 +93,6 @@ const WorkFlow = React.memo(function WorkFlow() {
showGotoCutButton={showGotoCutButton} showGotoCutButton={showGotoCutButton}
onGotoCut={generateEditPlan} onGotoCut={generateEditPlan}
/> />
</ErrorBoundary>
</div> </div>
</div> </div>
<div className="media-Ocdu1O rounded-lg"> <div className="media-Ocdu1O rounded-lg">
@ -139,7 +135,6 @@ const WorkFlow = React.memo(function WorkFlow() {
<Skeleton className="w-full aspect-video rounded-lg" /> <Skeleton className="w-full aspect-video rounded-lg" />
) : ( ) : (
<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}> <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}>
<ErrorBoundary>
<MediaViewer <MediaViewer
taskObject={taskObject} taskObject={taskObject}
scriptData={scriptData} scriptData={scriptData}
@ -158,7 +153,6 @@ const WorkFlow = React.memo(function WorkFlow() {
setPreviewVideoId(id); setPreviewVideoId(id);
}} }}
/> />
</ErrorBoundary>
</div> </div>
)} )}
</div> </div>
@ -250,7 +244,6 @@ const WorkFlow = React.memo(function WorkFlow() {
/> />
</Drawer> </Drawer>
<ErrorBoundary>
<EditModal <EditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
activeEditTab={activeEditTab} activeEditTab={activeEditTab}
@ -266,9 +259,7 @@ const WorkFlow = React.memo(function WorkFlow() {
fallbackToStep={fallbackToStep} fallbackToStep={fallbackToStep}
originalText={originalText} originalText={originalText}
/> />
</ErrorBoundary>
</div> </div>
</ErrorBoundary>
) )
}); });

View File

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

View File

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

View File

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

View File

@ -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, // 添加超时控制