'use client'; import { useState, useEffect, useCallback } from 'react'; import { useParams, useRouter } from 'next/navigation'; import dynamic from 'next/dynamic'; import { ImageStudio, VideoStudio, ClippingStudio, VibeMotionStudio, LipSyncStudio, RecastStudio, CinemaStudio, AudioStudio, MarketingStudio, WorkflowStudio, AgentStudio, AppsStudio, AiInfluencerStudio, getUserBalance } from 'studio'; const DesignAgentStudio = dynamic(() => import('studio').then(mod => mod.DesignAgentStudio), { ssr: false, loading: () =>
Loading Design Studio...
}); import axios from 'axios'; import ApiKeyModal from './ApiKeyModal'; const TABS = [ { id: 'image', label: 'Image Studio' }, { id: 'video', label: 'Video Studio' }, { id: 'audio', label: 'Audio Studio' }, { id: 'clipping', label: 'AI Clipping' }, { id: 'vibe-motion', label: 'Vibe Motion' }, { id: 'lipsync', label: 'Lip Sync' }, { id: 'body-swap', label: 'Body Swap' }, { id: 'cinema', label: 'Cinema Studio' }, { id: 'marketing', label: 'Marketing Studio' }, { id: 'workflows', label: 'Workflows' }, { id: 'agents', label: 'Agents' }, { id: 'design-agent', label: 'Design Agent' }, { id: 'apps', label: 'Explore Apps' }, { id: 'ai-influencer', label: 'AI Influencer Studio' }, ]; const STORAGE_KEY = 'muapi_key'; export default function StandaloneShell() { const params = useParams(); const router = useRouter(); const slug = params?.slug || []; const idFromParams = params?.id; const tabFromParams = params?.tab; // Helper to extract workflow details precisely from either route structure const getWorkflowInfo = useCallback(() => { if (idFromParams) { return { id: idFromParams, tab: tabFromParams || null }; } const wfIndex = slug.findIndex(s => s === 'workflows' || s === 'workflow'); if (wfIndex === -1) return { id: null, tab: null }; return { id: slug[wfIndex + 1] || null, tab: slug[wfIndex + 2] || null }; }, [slug, idFromParams, tabFromParams]); const { id: urlWorkflowId } = getWorkflowInfo(); // Initialize activeTab from URL slug/params or default to 'image' const getInitialTab = () => { if (idFromParams || slug.includes('workflow')) return 'workflows'; if (slug.includes('agents')) return 'agents'; if (slug.includes('design-agent')) return 'design-agent'; if (slug.includes('apps')) return 'apps'; const firstSegment = slug[0]; if (firstSegment && TABS.find(t => t.id === firstSegment)) return firstSegment; return 'image'; }; const [apiKey, setApiKey] = useState(null); const [activeTab, setActiveTab] = useState(getInitialTab()); const [balance, setBalance] = useState(null); const [showSettings, setShowSettings] = useState(false); const [isHeaderVisible, setIsHeaderVisible] = useState(true); const [hasMounted, setHasMounted] = useState(false); const [showVadooBanner, setShowVadooBanner] = useState(() => { if (typeof window !== 'undefined') return localStorage.getItem('vadoo_banner_dismissed') !== '1'; return true; }); // Drag and Drop State const [isDragging, setIsDragging] = useState(false); const [droppedFiles, setDroppedFiles] = useState(null); // Popstate event listener to sync tab state with URL on back/forward navigation useEffect(() => { const handlePopState = () => { const path = window.location.pathname; const segments = path.split('/').filter(Boolean); const tabId = segments[1] || 'image'; if (TABS.find(t => t.id === tabId)) { setActiveTab(tabId); } }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, []); const handleTabChange = (tabId) => { window.history.pushState(null, '', `/studio/${tabId}`); setActiveTab(tabId); }; // Auto-hide header when inside a specific workflow view or design agent useEffect(() => { const isEditingWorkflow = (activeTab === 'workflows' || !!idFromParams) && urlWorkflowId; const isDesignAgent = activeTab === 'design-agent'; if (isEditingWorkflow || isDesignAgent) { setIsHeaderVisible(false); } else { setIsHeaderVisible(true); } }, [activeTab, urlWorkflowId, idFromParams]); // Global builder CSS cleanup when switching away from Workflows or Design Agent tabs useEffect(() => { const fromBuilder = sessionStorage.getItem("fromWorkflowBuilder"); const fromDesignAgent = sessionStorage.getItem("fromDesignAgent"); if ((fromBuilder && activeTab !== 'workflows') || (fromDesignAgent && activeTab !== 'design-agent')) { sessionStorage.removeItem("fromWorkflowBuilder"); sessionStorage.removeItem("fromDesignAgent"); window.location.reload(); } }, [activeTab]); const fetchBalance = useCallback(async (key) => { try { const data = await getUserBalance(key); setBalance(data.balance); } catch (err) { console.error('Balance fetch failed:', err); } }, []); useEffect(() => { setHasMounted(true); const stored = localStorage.getItem(STORAGE_KEY); if (stored) { setApiKey(stored); fetchBalance(stored); // Sync cookie immediately on mount to establish identity for background requests document.cookie = `muapi_key=${stored}; path=/; max-age=31536000; SameSite=Lax`; } }, [fetchBalance]); const handleKeySave = useCallback((key) => { localStorage.setItem(STORAGE_KEY, key); setApiKey(key); fetchBalance(key); document.cookie = `muapi_key=${key}; path=/; max-age=31536000; SameSite=Lax`; }, [fetchBalance]); const handleKeyChange = useCallback(() => { localStorage.removeItem(STORAGE_KEY); setApiKey(null); setBalance(null); document.cookie = "muapi_key=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"; }, []); // Inject API key into all outgoing Axios requests (prop-based approach) // We use an interceptor to be selective and NOT send the key to external domains like S3 useEffect(() => { // Safety: Clear any global defaults that might have been set previously delete axios.defaults.headers.common['x-api-key']; if (!apiKey) return; const interceptorId = axios.interceptors.request.use((config) => { // Check if URL is local/proxied const isRelative = config.url.startsWith('/') || !config.url.startsWith('http'); const isInternalProxy = config.url.includes('/api/app') || config.url.includes('/api/workflow') || config.url.includes('/api/agents') || config.url.includes('/api/api') || config.url.includes('/api/v1'); if (isRelative || isInternalProxy) { config.headers['x-api-key'] = apiKey; } return config; }); return () => { axios.interceptors.request.eject(interceptorId); }; }, [apiKey]); // Poll for balance every 30 seconds if key is present useEffect(() => { if (!apiKey) return; const interval = setInterval(() => fetchBalance(apiKey), 30000); return () => clearInterval(interval); }, [apiKey, fetchBalance]); // Drag and Drop Handlers const handleDragOver = useCallback((e) => { e.preventDefault(); e.stopPropagation(); }, []); const handleDragEnter = useCallback((e) => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { setIsDragging(true); } }, []); const handleDragLeave = useCallback((e) => { e.preventDefault(); e.stopPropagation(); // Only set to false if we're leaving the container itself, not moving between children if (e.currentTarget.contains(e.relatedTarget)) return; setIsDragging(false); }, []); const handleDrop = useCallback((e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); const files = Array.from(e.dataTransfer.files); if (files.length > 0) { setDroppedFiles(files); } }, []); const handleFilesHandled = useCallback(() => { setDroppedFiles(null); }, []); if (!hasMounted) return (
); if (!apiKey) { return ; } return (
{/* Drag Overlay */} {isDragging && (
Drop your media here Images, videos, or audio files
)} {/* Vadoo promo banner */} {showVadooBanner && (
Unrestricted AI Images & Videos → Auto-Publish as YouTube Shorts & TikToks, Earn ↗
)} {/* Header */} {isHeaderVisible && (
{/* Left: Logo */}
OpenGenerativeAI
{/* Center: Navigation Container with fade edges */}
{/* Fade Left Overlay */}
{/* Fade Right Overlay */}
{/* Right: Actions */}
${balance !== null ? `${balance}` : '---'}
)} {/* Studio Content */}
{activeTab === 'design-agent' && ( )}
{/* Settings Modal */} {showSettings && (

Settings

Manage your AI studio preferences and authentication.

{apiKey.slice(0, 8)}••••••••••••••••
)}
); }