import { useState, useEffect, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Megaphone, Clapperboard, BarChart3, Globe, Settings2, Zap, TrendingUp, Eye, MousePointerClick, DollarSign, Upload, Play, Image, Film, RefreshCw, ArrowRight, Plus, X, AlertTriangle, ArrowRightLeft, PlusCircle, SlidersHorizontal, Activity, Check, Link2, WandSparkles, type LucideIcon, } from 'lucide-react'; import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts'; import { useMarketingStore } from '@/store/useMarketingStore'; import { useCurrency } from '@/store/useCurrencyStore'; import type { Campaign, MarketingAsset, LiveOptimizationEvent, LiveEventType } from '@/types'; import { GroundTruthPicker } from './GroundTruthPicker'; import { CatalystMarketingTab } from './CatalystMarketingTab'; import { CatalystDreamWeaverTab } from './CatalystDreamWeaverTab'; import type { GroundTruthSelection } from './GroundTruthPicker'; // ── Design tokens ───────────────────────────────────────────────────────────── const GLASS = { background: 'rgba(8, 10, 18, 0.82)', border: '1px solid rgba(59,130,246,0.14)', backdropFilter: 'blur(24px)', WebkitBackdropFilter: 'blur(24px)', boxShadow: '0 0 0 1px rgba(255,255,255,0.04), 0 4px 32px rgba(0,0,0,0.55)', } as const; const INNER = { background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.07)', } as const; // ── Shared primitives ───────────────────────────────────────────────────────── function Widget({ children, className = '', delay = 0, colSpan = 1, rowSpan = 1, style }: { children: React.ReactNode; className?: string; delay?: number; colSpan?: number; rowSpan?: number; style?: React.CSSProperties; }) { return (
{children} ); } function GhostButton({ children, onClick, danger = false, className = '' }: { children: React.ReactNode; onClick?: () => void; danger?: boolean; className?: string; }) { return ( {children} ); } function DarkInput({ value, onChange, placeholder, type = 'text', readOnly = false }: { value: string; onChange?: (v: string) => void; placeholder?: string; type?: string; readOnly?: boolean; }) { return ( onChange?.(e.target.value)} placeholder={placeholder} className="rounded-xl px-3 py-2 text-sm text-white w-full focus:outline-none transition-all" style={{ ...INNER, caretColor: 'hsl(var(--accent))' }} onFocus={(e) => { if (!readOnly) e.currentTarget.style.border = '1px solid rgba(59,130,246,0.4)'; }} onBlur={(e) => { e.currentTarget.style.border = '1px solid rgba(255,255,255,0.07)'; }} /> ); } function LiveBadge() { return (
Live
); } // ── KPI Stat card ───────────────────────────────────────────────────────────── function StatCard({ icon: Icon, label, value, sub, glowColor = 'rgba(59,130,246,0.2)', delay = 0 }: { icon: LucideIcon; label: string; value: string; sub: string; glowColor?: string; delay?: number; }) { return (

{label}

{value}

{sub}

); } // ── Campaign status badge ───────────────────────────────────────────────────── function CampaignStatusBadge({ status }: { status: Campaign['status'] }) { const map: Record = { ACTIVE: { label: 'Active', color: '#4ade80', bg: 'rgba(74,222,128,0.12)' }, PAUSED: { label: 'Paused', color: '#fbbf24', bg: 'rgba(251,191,36,0.12)' }, DELETED: { label: 'Deleted', color: '#f87171', bg: 'rgba(248,113,113,0.12)' }, ARCHIVED: { label: 'Archived', color: '#94a3b8', bg: 'rgba(148,163,184,0.12)' }, }; const { label, color, bg } = map[status]; return ( {label} ); } // ───────────────────────────────────────────────────────────────────────────── // TAB 1 — The Studio // ───────────────────────────────────────────────────────────────────────────── const RENDER_MESSAGES: Record = { rendering: [ 'Wan 2.2 is rendering cinematic lighting...', 'Wan 2.2 is compositing the infinity pool reflection...', 'Qwen-Image 2512 is synthesizing Arabic typography...', 'Flux-Schnell is upscaling to 4K resolution...', ], queued: [ 'Qwen-Image 2512 queued for cinematic poster render...', 'Wan 2.2 14B queued — GPU resources freeing up...', ], }; function AssetCard({ asset }: { asset: MarketingAsset }) { const isProcessing = asset.status === 'rendering' || asset.status === 'queued'; const [msgIdx, setMsgIdx] = useState(0); useEffect(() => { if (!isProcessing) return; const msgs = RENDER_MESSAGES[asset.status] ?? []; if (msgs.length === 0) return; const t = setInterval(() => setMsgIdx((i) => (i + 1) % msgs.length), 2800); return () => clearInterval(t); }, [isProcessing, asset.status]); const statusColor: Record = { queued: 'rgba(148,163,184,0.8)', rendering: '#fbbf24', ready: '#4ade80', uploaded: '#60a5fa', failed: '#f87171', }; return ( {/* Thumbnail / shimmer area */}
{isProcessing ? (
{asset.type === 'video' ? : } {(RENDER_MESSAGES[asset.status] ?? [])[msgIdx] ?? asset.renderMessage}
) : (
{asset.type === 'video' ? : } {asset.metaAssetId ? `Meta ID: ${asset.metaAssetId}` : 'Ready for upload'}
)}
{/* Info footer */}

{asset.name}

{asset.status.toUpperCase()}
{asset.type === 'video' ? 'Wan 2.2' : 'Qwen-2512'} {asset.language && ( {asset.language} )}
); } // ── Reference Slot ─────────────────────────────────────────────────────────── interface RefSelection { name: string; preview: string; } function ReferenceSlot({ value, onSelect, onClear, onRemove }: { value: RefSelection | null; onSelect: (sel: RefSelection) => void; onClear: () => void; onRemove?: () => void; }) { const inputRef = useRef(null); function handleFile(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; onSelect({ name: file.name, preview: URL.createObjectURL(file) }); e.target.value = ''; } // Show X when: slot has content (clear it), or slot is removable (remove it) const showX = !!value || !!onRemove; const handleX = (e: React.MouseEvent) => { e.stopPropagation(); if (onRemove) onRemove(); else onClear(); }; return (
inputRef.current?.click()} whileHover={{ scale: 1.04 }} whileTap={{ scale: 0.96 }} title="Add reference image" > {value?.preview ? ( {value.name} ) : value?.name ? ( <> {value.name} ) : ( + )} {showX && ( )}
); } function WorkflowInput() { const [mode, setMode] = useState<'image' | 'video'>('image'); const [prompt, setPrompt] = useState(''); const [keywords, setKeywords] = useState(''); const [textCopy, setTextCopy] = useState(''); const [groundTruth, setGroundTruth] = useState(null); const [pickerOpen, setPickerOpen] = useState(false); const [refs, setRefs] = useState<(RefSelection | null)[]>([null, null]); const anchorRef = useRef(null); return (
{/* Top Section: Ground Truth & References */}
{/* Ground Truth slot */}
setPickerOpen(v => !v)} className="w-[60px] h-[60px] rounded-2xl flex flex-col items-center justify-center gap-1 overflow-hidden transition-colors" style={{ background: groundTruth?.preview ? 'transparent' : pickerOpen ? 'rgba(59,130,246,0.12)' : 'rgba(255,255,255,0.03)', border: pickerOpen ? '1px solid rgba(59,130,246,0.4)' : '1px solid rgba(255,255,255,0.08)', }} whileHover={{ scale: 1.04 }} whileTap={{ scale: 0.96 }} title="Select Ground Truth image" > {groundTruth?.preview ? ( ground truth ) : groundTruth?.name ? ( <> {groundTruth.name} ) : ( {pickerOpen ? '✕' : '+'} )} {/* Clear selection button */} {groundTruth && ( { e.stopPropagation(); setGroundTruth(null); setPickerOpen(false); }} initial={{ opacity: 0, scale: 0.6 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.6 }} transition={{ duration: 0.12 }} whileHover={{ scale: 1.2, backgroundColor: 'rgba(239,68,68,0.8)' }} title="Remove selection" > )} {pickerOpen && ( { setGroundTruth(sel); setPickerOpen(false); }} onClose={() => setPickerOpen(false)} /> )}
Ground Truth
{refs.map((ref, i) => ( setRefs(prev => prev.map((r, idx) => idx === i ? sel : r))} onClear={() => setRefs(prev => prev.map((r, idx) => idx === i ? null : r))} onRemove={i >= 2 ? () => setRefs(prev => prev.filter((_, idx) => idx !== i)) : undefined} /> ))} setRefs(prev => prev.length < 6 ? [...prev, null] : prev)} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} >
References
{/* Bottom Section: Input & Controls */}