cinema-studio-pro/frontend/src/components/AppContent.jsx
Simeon.Schecter 95e6946807 feat: Kling integration, prompt optimizer rework, and stability fixes
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>
2026-03-23 21:51:03 -04:00

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;