Files
Brancheneinstufung2/k-pop-thumbnail-genie/App.tsx

224 lines
8.6 KiB
TypeScript

import React, { useState, useCallback } from 'react';
import { UploadedImage, AppStep, GenerationResult } from './types';
import { expandPrompt, generateImage, refineImage } from './services/geminiService';
import ImageUploader from './components/ImageUploader';
import ImageSegmenter from './components/ImageSegmenter';
import PromptCustomizer from './components/PromptCustomizer';
import ImageResult from './components/ImageResult';
import StepIndicator from './components/StepIndicator';
import { SparklesIcon } from './components/icons/SparklesIcon';
import { applyMask } from './utils/canvasUtils';
import { LoggingProvider, useLogger } from './contexts/LoggingContext';
import DebugConsole from './components/DebugConsole';
const AppContent: React.FC = () => {
const [step, setStep] = useState<AppStep>(AppStep.Upload);
const [uploadedImages, setUploadedImages] = useState<UploadedImage[]>([]);
const [masterPrompt, setMasterPrompt] = useState<string>('');
const [generationResult, setGenerationResult] = useState<GenerationResult | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [loadingMessage, setLoadingMessage] = useState<string>('');
const [error, setError] = useState<string | null>(null);
const { log } = useLogger();
const handleImagesUploaded = (images: UploadedImage[]) => {
log('info', `${images.length} images uploaded. Moving to segmentation step.`);
setUploadedImages(images);
setStep(AppStep.Segment);
setError(null);
};
const handleSegmentationComplete = (imagesWithMasks: UploadedImage[]) => {
log('success', `Segmentation complete for all ${imagesWithMasks.length} images. Moving to prompt step.`);
setUploadedImages(imagesWithMasks);
setStep(AppStep.Prompt);
setError(null);
};
const handlePromptExpanded = useCallback(async (scenario: string, userInstruction: string) => {
setIsLoading(true);
setLoadingMessage('Expanding your idea into a master prompt...');
setError(null);
log('info', `Expanding prompt with scenario: "${scenario}"`);
try {
const prompt = await expandPrompt(scenario, userInstruction, uploadedImages);
setMasterPrompt(prompt);
log('success', 'Master prompt created successfully.');
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'An unknown error occurred during prompt expansion.';
log('error', `Prompt expansion failed: ${errorMessage}`);
setError(errorMessage);
} finally {
setIsLoading(false);
setLoadingMessage('');
}
}, [uploadedImages, log]);
const handleFinalGeneration = useCallback(async (finalPrompt: string) => {
setIsLoading(true);
setLoadingMessage('Generating your K-Pop thumbnail...');
setError(null);
log('info', 'Starting final image generation.');
try {
log('info', 'Applying masks to create segmented images...');
const segmentedImages = await Promise.all(
uploadedImages.map(async (image) => {
if (!image.maskDataUrl) {
throw new Error(`Mask is missing for image: ${image.file.name}`);
}
const segmentedData = await applyMask(image.previewUrl, image.maskDataUrl);
return { ...image, segmentedDataUrl: `data:image/png;base64,${segmentedData}` };
})
);
log('success', 'Masks applied successfully.');
const result = await generateImage(finalPrompt, segmentedImages);
setGenerationResult({
baseImage: result,
currentImage: result,
history: [result],
});
setStep(AppStep.Result);
log('success', 'Thumbnail generated successfully.');
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'An unknown error occurred during image generation.';
log('error', `Final image generation failed: ${errorMessage}`);
setError(errorMessage);
setStep(AppStep.Prompt);
} finally {
setIsLoading(false);
setLoadingMessage('');
}
}, [uploadedImages, log]);
const handleImageRefinement = useCallback(async (refinementPrompt: string) => {
if (!generationResult) return;
setIsLoading(true);
setLoadingMessage('Applying your refinements...');
setError(null);
log('info', `Refining image with prompt: "${refinementPrompt}"`);
try {
const newImage = await refineImage(refinementPrompt, generationResult.currentImage);
setGenerationResult(prev => {
if (!prev) return null;
const newHistory = [...prev.history, newImage];
return {
baseImage: prev.baseImage,
currentImage: newImage,
history: newHistory,
};
});
log('success', 'Image refined successfully.');
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'An unknown error occurred during image refinement.';
log('error', `Image refinement failed: ${errorMessage}`);
setError(errorMessage);
} finally {
setIsLoading(false);
setLoadingMessage('');
}
}, [generationResult, log]);
const handleBack = () => {
setError(null);
if (step === AppStep.Result) {
log('info', 'Navigating back from Result to Prompt step.');
setMasterPrompt('');
setStep(AppStep.Prompt);
} else if (step === AppStep.Prompt) {
log('info', 'Navigating back from Prompt to Segment step.');
setMasterPrompt('');
setStep(AppStep.Segment);
} else if (step === AppStep.Segment) {
log('info', 'Navigating back from Segment to Upload step.');
setStep(AppStep.Upload);
}
};
const handleStartOver = () => {
log('info', 'Starting over. Resetting application state.');
setStep(AppStep.Upload);
setUploadedImages([]);
setMasterPrompt('');
setGenerationResult(null);
setIsLoading(false);
setLoadingMessage('');
setError(null);
}
const renderStep = () => {
switch (step) {
case AppStep.Upload:
return <ImageUploader onImagesUploaded={handleImagesUploaded} />;
case AppStep.Segment:
return <ImageSegmenter
images={uploadedImages}
onComplete={handleSegmentationComplete}
onBack={handleBack}
/>;
case AppStep.Prompt:
return <PromptCustomizer
onPromptExpanded={handlePromptExpanded}
onFinalSubmit={handleFinalGeneration}
isLoading={isLoading}
loadingMessage={loadingMessage}
uploadedImages={uploadedImages}
onBack={handleBack}
masterPrompt={masterPrompt}
setMasterPrompt={setMasterPrompt}
/>;
case AppStep.Result:
return <ImageResult
result={generationResult}
onRefine={handleImageRefinement}
masterPrompt={masterPrompt}
isLoading={isLoading}
loadingMessage={loadingMessage}
onStartOver={handleStartOver}
/>;
default:
return <ImageUploader onImagesUploaded={handleImagesUploaded} />;
}
};
return (
<div className="min-h-screen bg-gray-900 text-gray-100 flex flex-col items-center p-4 sm:p-6 lg:p-8 pb-32">
<header className="w-full max-w-6xl text-center mb-6">
<div className="flex items-center justify-center gap-3">
<SparklesIcon className="w-10 h-10 text-purple-400" />
<h1 className="text-4xl sm:text-5xl md:text-6xl font-teko tracking-wider uppercase bg-gradient-to-r from-purple-400 to-pink-500 text-transparent bg-clip-text">
K-Pop Thumbnail Genie
</h1>
</div>
<p className="text-gray-400 mt-2 text-sm sm:text-base">Create stunning, emotional YouTube thumbnails with the magic of AI</p>
</header>
<main className="w-full max-w-6xl flex-grow">
<StepIndicator currentStep={step} />
<div className="mt-8 bg-gray-800/50 p-6 sm:p-8 rounded-2xl shadow-2xl shadow-purple-900/10 border border-gray-700">
{error && (
<div className="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-6 text-center">
<p><span className="font-bold">Error:</span> {error}</p>
</div>
)}
{renderStep()}
</div>
</main>
<footer className="w-full max-w-6xl text-center mt-8 text-gray-500 text-xs">
<p>Powered by Google Gemini. Designed for K-Pop content creators.</p>
</footer>
<DebugConsole />
</div>
);
};
const App: React.FC = () => (
<LoggingProvider>
<AppContent />
</LoggingProvider>
);
export default App;