Kling video generation: - Full T2V, I2V, extend, and lip sync workflows via Kling API - V3, V2.6, V2.5 Turbo, V2.1 Master, V1.6 model support - Resolution selector (720p std / 1080p pro) with model constraints - Native audio toggle with dialogue input for Kling - Video ID tracking for extend and lip sync chains - Camera control presets (pan, tilt, arc) Prompt optimizer rework: - Intent-preserving refinement (camera, action, mood are sacred) - Mode-aware: T2V adds subject/environment detail, I2V describes only motion - Reference images analyzed for content, not re-described - Platform-specific quality anchors woven into positive prompt - Negative prompts removed from optimizer (positive-only approach) - 15-60 word target for concise, effective prompts Backend fixes: - Gemini responseModalities: ['TEXT', 'IMAGE'] for Flash model compatibility - Veo first-frame resize to exact target dimensions (prevents letterboxing) - Session directory re-creation in saveImage (auto-cleanup race condition) - Kling API error logging with HTTP codes and payload details - Lip sync endpoint updated to /v1/videos/lip-sync with video_id Frontend stability: - Tab persistence via CSS hidden (generation survives tab switches) - Project switch protection (confirm dialog when generation in progress) - Retina thumbnails (480px/q0.8) for library grid — prevents OOM crashes - Thumbnail backfill migration for existing project items - Project items refresh on tab visibility and after save - 1:1 aspect ratio container for Kling videos - Expanded video view matches library modal behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
5.8 KiB
JavaScript
174 lines
5.8 KiB
JavaScript
import { useState, useCallback } from 'react';
|
|
import { useIsAuthenticated, useMsal } from '@azure/msal-react';
|
|
import { LogOut } from 'lucide-react';
|
|
import { isSSOEnabled } from '../authConfig';
|
|
import TabNavigation from './TabNavigation';
|
|
import CinePromptStudio from './CinePromptStudio';
|
|
import VideoGenTab from './VideoGenTab';
|
|
import ProjectsTab from './ProjectsTab';
|
|
import LoginPage from './LoginPage';
|
|
|
|
function AppContent() {
|
|
// Check if SSO is enabled
|
|
const ssoEnabled = isSSOEnabled();
|
|
|
|
// MSAL hooks - always called unconditionally since MsalProvider always wraps this component
|
|
const isAuthenticated = useIsAuthenticated();
|
|
const { instance, accounts } = useMsal();
|
|
|
|
// State hooks
|
|
const [activeTab, setActiveTab] = useState('projects');
|
|
const [activeProjectId, setActiveProjectId] = useState(null);
|
|
const [activeProjectName, setActiveProjectName] = useState(null);
|
|
const [videoRerunData, setVideoRerunData] = useState(null);
|
|
const [imageEditData, setImageEditData] = useState(null);
|
|
const [imageGenBusy, setImageGenBusy] = useState(false);
|
|
const [videoGenBusy, setVideoGenBusy] = useState(false);
|
|
|
|
const isAnyGenerating = imageGenBusy || videoGenBusy;
|
|
|
|
// Show login page if SSO is enabled and user is not authenticated
|
|
if (ssoEnabled && !isAuthenticated) {
|
|
return <LoginPage />;
|
|
}
|
|
|
|
// Get user info for display
|
|
const userName = accounts.length > 0
|
|
? accounts[0].name || accounts[0].username
|
|
: ssoEnabled ? null : 'Local Developer';
|
|
|
|
// Logout handler
|
|
const handleLogout = () => {
|
|
if (instance) {
|
|
instance.logoutPopup();
|
|
}
|
|
};
|
|
|
|
// Handler for video rerun from ProjectsTab
|
|
const handleRerunVideo = (data) => {
|
|
setVideoRerunData(data);
|
|
setActiveTab('video');
|
|
};
|
|
|
|
// Clear rerun data after it's been loaded
|
|
const handleRerunLoaded = () => {
|
|
setVideoRerunData(null);
|
|
};
|
|
|
|
// Handler for editing image in Image Gen from ProjectsTab
|
|
const handleEditInImageGen = (data) => {
|
|
setImageEditData(data);
|
|
setActiveTab('image');
|
|
};
|
|
|
|
// Clear edit data after it's been loaded
|
|
const handleEditLoaded = () => {
|
|
setImageEditData(null);
|
|
};
|
|
|
|
// Handler for project selection from ProjectsTab
|
|
const handleProjectSelect = (id, name) => {
|
|
if (isAnyGenerating && id !== activeProjectId) {
|
|
if (!window.confirm('A generation is still in progress. Switching projects will cancel it. Continue?')) {
|
|
return;
|
|
}
|
|
}
|
|
setActiveProjectId(id);
|
|
setActiveProjectName(name);
|
|
};
|
|
|
|
// Handler for tab change with blocking logic
|
|
const handleTabChange = (tabId) => {
|
|
// Block Image Gen and Video Gen if no project selected
|
|
if ((tabId === 'image' || tabId === 'video') && !activeProjectId) {
|
|
return; // Do nothing - tabs are disabled
|
|
}
|
|
setActiveTab(tabId);
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-950 text-slate-100">
|
|
{/* Header */}
|
|
<div className="max-w-7xl mx-auto px-6 pt-10 pb-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-10">
|
|
<img
|
|
src={`${import.meta.env.BASE_URL}LUX_STUDIO_LOGO.svg`}
|
|
alt="Lux Studio"
|
|
className="h-12 w-auto"
|
|
/>
|
|
<TabNavigation
|
|
activeTab={activeTab}
|
|
onTabChange={handleTabChange}
|
|
activeProjectId={activeProjectId}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex flex-col items-end gap-1">
|
|
{activeProjectName && (
|
|
<div className="text-sm text-slate-400">
|
|
Working in: <span className="text-cinema-gold font-normal">{activeProjectName}</span>
|
|
</div>
|
|
)}
|
|
<div className="text-xs font-mono text-slate-500">
|
|
Version 1.0
|
|
</div>
|
|
</div>
|
|
{userName && (
|
|
<div className="flex items-center gap-3 border-l border-slate-700 pl-4">
|
|
<div className="text-right">
|
|
<div className="text-sm text-slate-300">{userName}</div>
|
|
</div>
|
|
{ssoEnabled && (
|
|
<button
|
|
onClick={handleLogout}
|
|
className="p-2 hover:bg-slate-700 rounded transition-colors"
|
|
title="Logout"
|
|
>
|
|
<LogOut className="w-5 h-5 text-slate-400" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content - Tab Panels (always mounted so async generation persists across tab switches) */}
|
|
<main className="max-w-7xl mx-auto px-6 pb-8">
|
|
<div className={activeTab === 'projects' ? '' : 'hidden'}>
|
|
<ProjectsTab
|
|
onProjectSelect={handleProjectSelect}
|
|
activeProjectId={activeProjectId}
|
|
onRerunVideo={handleRerunVideo}
|
|
onEditInImageGen={handleEditInImageGen}
|
|
/>
|
|
</div>
|
|
{activeProjectId && (
|
|
<>
|
|
<div className={activeTab === 'image' ? '' : 'hidden'}>
|
|
<CinePromptStudio
|
|
activeProjectId={activeProjectId}
|
|
editData={imageEditData}
|
|
onEditLoaded={handleEditLoaded}
|
|
onBusyChange={setImageGenBusy}
|
|
isVisible={activeTab === 'image'}
|
|
/>
|
|
</div>
|
|
<div className={activeTab === 'video' ? '' : 'hidden'}>
|
|
<VideoGenTab
|
|
activeProjectId={activeProjectId}
|
|
rerunData={videoRerunData}
|
|
onRerunLoaded={handleRerunLoaded}
|
|
onBusyChange={setVideoGenBusy}
|
|
isVisible={activeTab === 'video'}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default AppContent;
|