Refactor GTM Architect to v2: Python-driven architecture, 9-phase process, new DB and Docker setup
This commit is contained in:
223
k-pop-thumbnail-genie/App.tsx
Normal file
223
k-pop-thumbnail-genie/App.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user