This commit is contained in:
海龙 2025-08-30 20:32:28 +08:00
commit 596e695a1d
15 changed files with 428 additions and 412 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 OAuthCallbackHandler from "@/components/ui/oauth-callback-handler";
export default function Home() {
return (
<DashboardLayout>
{/* Handle OAuth callbacks */}
<>
<TopBar collapsed={true} />
<OAuthCallbackHandler />
<HomePage2 />
</DashboardLayout>
</>
);
}

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,7 @@ import { Image as ImageIcon, Send, Trash2, ArrowUp } from "lucide-react";
import { MessageBlock } from "./types";
import { useUploadFile } from "@/app/service/domain/service";
import { motion, AnimatePresence } from "framer-motion";
import { QuickActionTags, QuickAction } from "./QuickActionTags";
// 防抖函数
function debounce<T extends (...args: any[]) => void>(func: T, wait: number) {
@ -233,6 +234,19 @@ export function InputBar({ onSend, setVideoPreview, initialVideoUrl, initialVide
</div>
)}
{/* 快捷操作标签组 */}
<QuickActionTags
onTagClick={(action: QuickAction) => {
// 将标签文本添加到输入框
setText(action.label);
// 聚焦输入框并触发高度调整
if (textareaRef.current) {
textareaRef.current.focus();
adjustHeight();
}
}}
/>
<motion.div
layout
className="px-3 m-3 border border-gray-700 rounded-[2rem]"

View File

@ -0,0 +1,123 @@
import React, { useRef, useCallback } from 'react';
import { motion } from 'framer-motion';
import { ChevronLeft, ChevronRight } from 'lucide-react';
/** 快捷操作标签的数据结构 */
export interface QuickAction {
id: string;
label: string;
}
/** 预设的快捷操作标签 */
export const DEFAULT_QUICK_ACTIONS: QuickAction[] = [
{ id: 'weather', label: 'Change video scene weather' },
{ id: 'character', label: 'Change a character in the video' },
{ id: 'costume', label: 'Change the clothing of a character in the video' },
{ id: 'scene', label: 'Change video scene background' },
{ id: 'action', label: 'Change character action' }
];
interface QuickActionTagsProps {
/** 自定义标签列表,如果不提供则使用默认标签 */
actions?: QuickAction[];
/** 点击标签时的回调函数 */
onTagClick: (action: QuickAction) => void;
}
/**
*
* @param props
* @returns JSX.Element
*/
export function QuickActionTags({ actions = DEFAULT_QUICK_ACTIONS, onTagClick }: QuickActionTagsProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const scroll = useCallback((direction: 'left' | 'right') => {
const container = scrollContainerRef.current;
if (!container) return;
const scrollAmount = 200; // 每次滚动的距离
const targetScroll = container.scrollLeft + (direction === 'left' ? -scrollAmount : scrollAmount);
container.scrollTo({
left: targetScroll,
behavior: 'smooth'
});
}, []);
return (
<div
data-alt="quick-action-tags"
className="relative flex items-center px-3 py-2 group"
>
{/* 左侧渐变遮罩 */}
<div className="absolute left-0 top-0 bottom-0 w-12 bg-gradient-to-r from-black/20 to-transparent pointer-events-none z-10" />
{/* 左滚动按钮 */}
<motion.button
onClick={() => scroll('left')}
className="absolute left-1 z-20 p-1 rounded-full bg-black/30 text-white/80
backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity
hover:bg-black/40"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
data-alt="scroll-left"
>
<ChevronLeft size={16} />
</motion.button>
{/* 标签滚动容器 */}
<div
ref={scrollContainerRef}
className="flex overflow-x-auto gap-2 no-scrollbar scroll-smooth"
style={{
msOverflowStyle: 'none', /* IE and Edge */
scrollbarWidth: 'none', /* Firefox */
}}
>
{actions.map((action) => (
<motion.button
key={action.id}
onClick={() => onTagClick(action)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex-none px-[8px] py-[3px] rounded-full text-[10px] text-white/80
backdrop-blur-md bg-white/10 border border-white/20
hover:bg-white/20 hover:text-white
transition-colors duration-200
shadow-[0_4px_6px_-1px_rgba(0,0,0,0.1),0_2px_4px_-1px_rgba(0,0,0,0.06)]"
data-alt={`quick-action-${action.id}`}
>
{action.label}
</motion.button>
))}
</div>
{/* 右侧渐变遮罩 */}
<div className="absolute right-0 top-0 bottom-0 w-12 bg-gradient-to-l from-black/20 to-transparent pointer-events-none z-10" />
{/* 右滚动按钮 */}
<motion.button
onClick={() => scroll('right')}
className="absolute right-1 z-20 p-1 rounded-full bg-black/30 text-white/80
backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity
hover:bg-black/40"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
data-alt="scroll-right"
>
<ChevronRight size={16} />
</motion.button>
</div>
);
}
// 添加全局样式来隐藏滚动条
const style = document.createElement('style');
style.textContent = `
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
`;
document.head.appendChild(style);

View File

@ -1,5 +1,5 @@
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 { MessageRenderer } from "./MessageRenderer";
import { InputBar } from "./InputBar";
@ -133,16 +133,16 @@ export default function SmartChatBox({
<span>Chat</span>
{/* System push toggle */}
<Switch
checkedChildren="System: On"
unCheckedChildren="System: Off"
checkedChildren="On"
unCheckedChildren="Off"
checked={systemPush}
onChange={toggleSystemPush}
className="ml-2 "
/>
</div>
<div className="text-xs opacity-70">
<ArrowRightFromLine
className="w-4 h-4 cursor-pointer"
<ChevronsRight
className="w-6 h-6 cursor-pointer"
onClick={() => setIsSmartChatBoxOpen(false)}
/>
</div>
@ -168,7 +168,7 @@ export default function SmartChatBox({
</div>
{/* Loading indicator */}
{isLoading && !hasMore && (
{isLoading && (
<div className="flex justify-start space-x-1 p-2">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.15s]"></span>

View File

@ -9,12 +9,18 @@ interface DashboardLayoutProps {
}
export function DashboardLayout({ children }: DashboardLayoutProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
const [sidebarCollapsed, setSidebarCollapsed] = useState(true); // 默认收起状态
return (
<div className=" min-h-screen bg-background">
<TopBar collapsed={sidebarCollapsed} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} />
{children}
<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}
</div>
</div>
);
}

View File

@ -19,7 +19,8 @@ import {
Video,
PanelsLeftBottom,
ArrowLeftToLine,
X
BookHeart,
PanelRightClose
} from 'lucide-react';
interface SidebarProps {
@ -31,10 +32,7 @@ const navigationItems = [
{
title: 'Main',
items: [
{ name: 'Home', href: '/', icon: Home },
{ name: 'Media Library', href: '/media', icon: FolderOpen },
{ name: 'Actors Library', href: '/actors', icon: Users },
{ name: 'Task History', href: '/history', icon: History },
{ name: 'My Portfolio', href: '/create', icon: BookHeart },
],
}
];
@ -43,67 +41,56 @@ export function Sidebar({ collapsed, onToggle }: SidebarProps) {
const pathname = usePathname();
return (
<>
{/* Backdrop */}
{!collapsed && (
<div
className="fixed inset-0 bg-[#000000bf] z-[998]"
onClick={() => onToggle(true)}
/>
)}
<>
{/* Sidebar */}
<div
data-alt="sidebar-container"
className={cn(
'fixed left-0 top-0 z-[999] h-full w-64 bg-[#131416] transition-transform duration-300',
collapsed ? '-translate-x-full' : 'translate-x-0'
'fixed left-0 top-0 z-[999] h-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60',
'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-16 items-center justify-between px-4">
<div className="flex items-center space-x-2">
<Video className="h-8 w-8 text-primary" />
<span className="text-xl font-bold">
<GradientText
text="MovieFlow"
startPercentage={30}
endPercentage={70}
/>
</span>
</div>
{/* Toggle Button */}
<div className="flex h-16 items-center px-4">
<Button
data-alt="toggle-sidebar"
variant="ghost"
size="sm"
onClick={() => onToggle(true)}
className="button-NxtqWZ"
size="icon"
className={cn(
'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>
</div>
{/* Navigation */}
<div className="flex-1 overflow-y-auto">
{navigationItems.map((section, index) => (
<div key={section.title}>
<div className="space-y-1">
{section.items.map((item) => {
const isActive = pathname === item.href;
return (
<Link key={item.name} href={item.href}>
<Button
variant={isActive ? 'secondary' : 'ghost'}
className={cn(
'w-full justify-start 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>
</Button>
</Link>
);
})}
</div>
<div className="flex-1 space-y-1 px-1">
{navigationItems.map((section) => (
<div key={section.title} className="space-y-1">
{section.items.map((item) => {
const isActive = pathname === item.href;
return (
<Link key={item.name} href={item.href}>
<Button
data-alt={`nav-item-${item.name}`}
variant={isActive ? 'secondary' : 'ghost'}
className={cn(
'w-full justify-start',
collapsed ? 'px-3' : 'px-4',
isActive && 'bg-primary/10 text-primary hover:bg-primary/20'
)}
>
<item.icon className={cn('h-4 w-4 shrink-0 text-gray-300', !collapsed && 'mr-2')} />
{!collapsed && <span>{item.name}</span>}
</Button>
</Link>
);
})}
</div>
))}
</div>

View File

@ -35,10 +35,8 @@ interface User {
export function TopBar({
collapsed,
onToggleSidebar,
}: {
collapsed: boolean;
onToggleSidebar: () => void;
}) {
const router = useRouter();
const [isOpen, setIsOpen] = React.useState(false);
@ -170,10 +168,10 @@ export function TopBar({
return (
<div
className="fixed right-0 top-0 left-0 h-16 header z-[999]"
style={{ isolation: "isolate" }}
className="fixed right-0 top-0 h-16 header z-[999]"
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 cursor-pointer space-x-1 link-logo roll event-on`}
@ -265,8 +263,13 @@ export function TopBar({
setIsOpen(!isOpen);
}}
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>
{mounted && isOpen
@ -292,7 +295,7 @@ export function TopBar({
<div className="p-4">
<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">
MF
{currentUser.username ? currentUser.username.charAt(0) : 'MF'}
</div>
<div className="flex-1">
<div className="flex items-center gap-2">

View File

@ -215,56 +215,51 @@ export default function CreateToVideo2() {
};
return (
<>
<div className="flex flex-col absolute top-[5rem] left-0 right-0 bottom-[1rem] px-6">
{/* 优化后的主要内容区域 */}
<div className="flex-1 min-h-0">
<div
ref={scrollContainerRef}
className="h-full overflow-y-auto overflow-x-hidden custom-scrollbar"
style={{
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(255,255,255,0.1) transparent'
}}
>
{episodeList.length > 0 && (
/* 优化的剧集网格 */
<div className="pb-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{episodeList.map(renderProjectCard)}
<div className="flex flex-col w-full h-full relative px-2">
<div
ref={scrollContainerRef}
className="h-full overflow-y-auto overflow-x-hidden custom-scrollbar"
style={{
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(255,255,255,0.1) transparent'
}}
>
{episodeList.length > 0 && (
/* 优化的剧集网格 */
<div className="pb-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{episodeList.map(renderProjectCard)}
</div>
{/* 加载更多指示器 */}
{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>
)}
{/* 加载更多指示器 */}
{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>
{/* 到底提示 */}
{!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>
)}
{/* 到底提示 */}
{!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>
)}
<p className="text-white/70 text-sm">All projects loaded</p>
</div>
</div>
)}
</div>
</div>
)}
</div>
{/* 视频工具组件 - 使用独立组件 */}
{!isLoading &&
<ChatInputBox noData={episodeList.length === 0} />
}
</>
</div>
);
}

View File

@ -1,5 +1,5 @@
.video-tool-component {
position: fixed;
position: absolute;
bottom: 1.5rem;
z-index: 9;
}

View File

@ -3,7 +3,6 @@ import React, { useRef, useEffect, useCallback } from "react";
import "./style/work-flow.css";
import { Skeleton } from "@/components/ui/skeleton";
import { EditModal } from "@/components/ui/edit-modal";
import { ErrorBoundary } from "@/components/ui/error-boundary";
import { TaskInfo } from "./work-flow/task-info";
import { MediaViewer } from "./work-flow/media-viewer";
import { ThumbnailGrid } from "./work-flow/thumbnail-grid";
@ -81,194 +80,186 @@ const WorkFlow = React.memo(function WorkFlow() {
}, []);
return (
<ErrorBoundary>
<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="splashContainer-otuV_A">
<div className="content-vPGYx8">
<div className="info-UUGkPJ">
<ErrorBoundary>
<TaskInfo
taskObject={taskObject}
currentLoadingText={currentLoadingText}
roles={taskObject.roles.data}
isPauseWorkFlow={isPauseWorkFlow}
showGotoCutButton={showGotoCutButton}
onGotoCut={generateEditPlan}
/>
</ErrorBoundary>
</div>
<div className="w-full overflow-hidden h-full px-[1rem] pb-[1rem]">
<div className="w-full h-full">
<div className="splashContainer-otuV_A">
<div className="content-vPGYx8">
<div className="info-UUGkPJ">
<TaskInfo
taskObject={taskObject}
currentLoadingText={currentLoadingText}
roles={taskObject.roles.data}
isPauseWorkFlow={isPauseWorkFlow}
showGotoCutButton={showGotoCutButton}
onGotoCut={generateEditPlan}
/>
</div>
<div className="media-Ocdu1O rounded-lg">
<div
className="videoContainer-qteKNi"
ref={containerRef}
>
{dataLoadError ? (
</div>
<div className="media-Ocdu1O rounded-lg">
<div
className="videoContainer-qteKNi"
ref={containerRef}
>
{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
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 }}
className="flex items-center gap-3 mb-4"
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<motion.div
className="flex items-center gap-3 mb-4"
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>
<AlertCircle className="w-8 h-8 text-red-500" />
<h3 className="text-lg font-medium text-red-800"></h3>
</motion.div>
) : isLoading ? (
<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}>
<ErrorBoundary>
<MediaViewer
taskObject={taskObject}
scriptData={scriptData}
currentSketchIndex={currentSketchIndex}
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);
}}
/>
</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}
<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>
) : isLoading ? (
<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}>
<MediaViewer
taskObject={taskObject}
scriptData={scriptData}
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>
{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>
{/* 暂停/播放按钮 */}
{
(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>
</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>
)
});

View File

@ -492,7 +492,7 @@ export const MediaViewer = React.memo(function MediaViewer({
}}
>
<Video className="w-4 h-4" />
<span className="text-xs">Chat to edit</span>
<span className="text-xs">Edit with chat</span>
</Button>
</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 }) => {
@ -216,9 +217,13 @@ export function TaskInfo({
{taskObject?.tags?.map((tag: string) => (
<div
key={tag}
className="text-sm text-white rounded-full px-2 py-1"
style={{ backgroundColor: tagColors[tag] }}
data-alt="tag-item"
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}
</div>
))}
@ -254,7 +259,7 @@ export function TaskInfo({
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
{/* <motion.div
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={!isPauseWorkFlow ? {
@ -266,7 +271,7 @@ export function TaskInfo({
repeatDelay: 0.2
}
} : {}}
/>
/> */}
{/* 阶段图标 */}
<motion.div
@ -338,7 +343,7 @@ export function TaskInfo({
</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"
animate={!isPauseWorkFlow ? {
x: [0, 200, 0],
@ -350,7 +355,7 @@ export function TaskInfo({
ease: "easeInOut",
}
}: {}}
/>
/> */}
{/* 文字底部装饰线 */}
<motion.div
@ -368,7 +373,7 @@ export function TaskInfo({
</motion.div>
</motion.div>
<motion.div
{/* <motion.div
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={!isPauseWorkFlow ? {
@ -395,7 +400,7 @@ export function TaskInfo({
delay: 0.3
}
} : {}}
/>
/> */}
{/* 跳转剪辑按钮 */}
{showGotoCutButton && (

View File

@ -17,6 +17,7 @@ export function useWorkflowData() {
const episodeId = searchParams.get('episodeId') || '';
const from = searchParams.get('from') || '';
const token = localStorage.getItem('token') || '';
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
let tempTaskObject = useRef<TaskObject>({
title: '',
@ -114,7 +115,7 @@ export function useWorkflowData() {
const generateEditPlan = useCallback(async () => {
await getGenerateEditPlan({ project_id: episodeId });
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}`, '_self');
window.open(`https://smartcut.huiying.video/ai-editor/${episodeId}?token=${token}&userid=${useid}`, '_self');
}, [episodeId]);
// useEffect(() => {
@ -562,7 +563,7 @@ export function useWorkflowData() {
fallbackToStep,
originalText: state.originalText,
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
showGotoCutButton: canGoToCut ? true : false,
showGotoCutButton: canGoToCut && currentLoadingText.includes('Post-production') ? true : false,
generateEditPlan
};
}

View File

@ -48,7 +48,7 @@ export const getProjectTaskList = async (data: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage?.getItem('token') || 'mock-token'}`,
'Authorization': `Bearer ${localStorage?.getItem('token')}`,
},
body: JSON.stringify(data),
signal: controller.signal, // 添加超时控制