Refactor GTM Architect to v2: Python-driven architecture, 9-phase process, new DB and Docker setup
This commit is contained in:
151
k-pop-thumbnail-genie/components/PromptCustomizer.tsx
Normal file
151
k-pop-thumbnail-genie/components/PromptCustomizer.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { PROMPT_TEMPLATES } from '../constants';
|
||||
import { UploadedImage } from '../types';
|
||||
import { MagicIcon } from './icons/MagicIcon';
|
||||
import { ArrowLeftIcon } from './icons/ArrowLeftIcon';
|
||||
|
||||
interface PromptCustomizerProps {
|
||||
onPromptExpanded: (scenario: string, userInstruction: string) => void;
|
||||
onFinalSubmit: (masterPrompt: string) => void;
|
||||
isLoading: boolean;
|
||||
loadingMessage: string;
|
||||
uploadedImages: UploadedImage[];
|
||||
onBack: () => void;
|
||||
masterPrompt: string;
|
||||
setMasterPrompt: (prompt: string) => void;
|
||||
}
|
||||
|
||||
const PromptCustomizer: React.FC<PromptCustomizerProps> = ({
|
||||
onPromptExpanded,
|
||||
onFinalSubmit,
|
||||
isLoading,
|
||||
loadingMessage,
|
||||
uploadedImages,
|
||||
onBack,
|
||||
masterPrompt,
|
||||
setMasterPrompt
|
||||
}) => {
|
||||
const [selectedScenario, setSelectedScenario] = useState<string>(PROMPT_TEMPLATES[0].title);
|
||||
const [userInstruction, setUserInstruction] = useState<string>('');
|
||||
const [isPromptExpanded, setIsPromptExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (masterPrompt) {
|
||||
setIsPromptExpanded(true);
|
||||
}
|
||||
}, [masterPrompt]);
|
||||
|
||||
const handleExpandPrompt = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (selectedScenario && userInstruction) {
|
||||
onPromptExpanded(selectedScenario, userInstruction);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinalSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (masterPrompt) {
|
||||
onFinalSubmit(masterPrompt);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading && !isPromptExpanded) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center text-center h-64">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-purple-400 mb-4"></div>
|
||||
<p className="text-xl text-purple-300">{loadingMessage}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-100">{isPromptExpanded ? 'Review Your Master Prompt' : 'Describe Your Scene'}</h2>
|
||||
<p className="text-gray-400">{isPromptExpanded ? 'Edit the AI-generated prompt below, then generate your image.' : 'Choose a starting scenario and describe your vision.'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-8">
|
||||
<div className="lg:w-1/3">
|
||||
<h3 className="text-lg font-semibold text-purple-300 mb-3">Your Subjects</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{uploadedImages.map((image, index) => (
|
||||
<div key={index} className="flex items-center gap-2 bg-gray-700 p-2 rounded-lg">
|
||||
<img src={image.maskDataUrl || image.previewUrl} alt={`subject ${index}`} className="w-10 h-10 rounded-md object-cover bg-black" />
|
||||
<p className="text-sm text-gray-300 flex-1">{image.subjectDescription}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow lg:w-2/3 space-y-6">
|
||||
{!isPromptExpanded ? (
|
||||
<form onSubmit={handleExpandPrompt} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-lg font-semibold text-purple-300 mb-2">1. Choose a K-Pop Scenario</label>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{PROMPT_TEMPLATES.map(template => (
|
||||
<button
|
||||
key={template.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedScenario(template.title)}
|
||||
className={`p-4 rounded-lg text-left transition-all duration-200 border-2 ${selectedScenario === template.title ? 'bg-purple-800/50 border-purple-500' : 'bg-gray-700 border-gray-600 hover:border-purple-600'}`}
|
||||
>
|
||||
<p className="font-bold text-white">{template.title}</p>
|
||||
<p className="text-sm text-gray-400">{template.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="user-instruction" className="block text-lg font-semibold text-purple-300 mb-2">2. Describe Your Idea</label>
|
||||
<textarea
|
||||
id="user-instruction"
|
||||
value={userInstruction}
|
||||
onChange={(e) => setUserInstruction(e.target.value)}
|
||||
placeholder="Example: The person from image 1 should stand behind the person from image 2, placing a hand on their shoulder. Use the background from image 2."
|
||||
className="w-full bg-gray-800 border border-gray-600 rounded-lg p-3 text-base h-32 focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4">
|
||||
<button type="button" onClick={onBack} className="bg-gray-600 hover:bg-gray-500 text-white font-bold py-3 px-6 rounded-lg transition-colors duration-300 flex items-center gap-2 w-full sm:w-auto justify-center">
|
||||
<ArrowLeftIcon className="w-5 h-5"/> Back
|
||||
</button>
|
||||
<button type="submit" disabled={isLoading || !userInstruction || !selectedScenario} className="bg-purple-600 hover:bg-purple-700 disabled:bg-gray-600 text-white font-bold py-3 px-8 rounded-lg transition-all duration-300 flex items-center gap-2 w-full sm:w-auto justify-center">
|
||||
Create Master Prompt <MagicIcon className="w-5 h-5"/>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<form onSubmit={handleFinalSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="master-prompt" className="block text-lg font-semibold text-purple-300 mb-2">Master Prompt</label>
|
||||
<textarea
|
||||
id="master-prompt"
|
||||
value={masterPrompt}
|
||||
onChange={(e) => setMasterPrompt(e.target.value)}
|
||||
className="w-full bg-gray-800 border border-gray-600 rounded-lg p-3 text-base h-48 focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4">
|
||||
<button type="button" onClick={() => setIsPromptExpanded(false)} className="bg-gray-600 hover:bg-gray-500 text-white font-bold py-3 px-6 rounded-lg transition-colors duration-300 flex items-center gap-2 w-full sm:w-auto justify-center">
|
||||
<ArrowLeftIcon className="w-5 h-5"/> Edit Scenario
|
||||
</button>
|
||||
<button type="submit" disabled={isLoading || !masterPrompt} className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 disabled:from-gray-600 disabled:to-gray-600 disabled:cursor-not-allowed text-white font-bold py-3 px-8 rounded-lg transition-all duration-300 text-lg flex items-center gap-2 w-full sm:w-auto justify-center">
|
||||
{isLoading ? loadingMessage : 'Generate Thumbnail'}
|
||||
{!isLoading && <MagicIcon className="w-6 h-6"/>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptCustomizer;
|
||||
Reference in New Issue
Block a user