'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 && (
)}
{/* Header */}
{isHeaderVisible && (
)}
{/* Studio Content */}
{activeTab === 'design-agent' && (
)}
{/* Settings Modal */}
{showSettings && (
Settings
Manage your AI studio preferences and authentication.
{apiKey.slice(0, 8)}••••••••••••••••
)}
);
}