Spaces:
Running
Running
| import { LoaderCircle, ImageIcon, Send, RotateCw, Maximize, Wand2, Sun, Plus, Palette, Image as ImageLucide, Brush, SendToBack, Download, ArrowLeftRight } from 'lucide-react'; | |
| import { useEffect, useState, useCallback } from 'react'; | |
| import ImageRefiner from './ImageRefiner'; | |
| import HeaderButtons from './HeaderButtons'; | |
| // Update REFINEMENT_SUGGESTIONS with cleaner labels and full prompts | |
| const REFINEMENT_SUGGESTIONS = [ | |
| { label: 'Rotate', prompt: 'Can you rotate this by ', icon: RotateCw }, | |
| { label: 'Add light', prompt: 'Can you add a light from the ', icon: Sun }, | |
| { label: 'Add object', prompt: 'Can you add a ', icon: Plus }, | |
| { label: 'Background', prompt: 'Can you change the background to ', icon: ImageLucide }, | |
| { label: 'Color', prompt: 'Can you make the color more ', icon: Palette }, | |
| { label: 'Scale', prompt: 'Can you make this ', icon: Maximize }, | |
| { label: 'Lighting', prompt: 'Can you make the lighting more ', icon: Sun }, | |
| { label: 'Style', prompt: 'Can you make it look more ', icon: Wand2 }, | |
| { label: 'Material', prompt: 'Can you change the material to ', icon: Wand2 }, | |
| // Add more suggestions as needed... | |
| ]; | |
| // Update SURPRISE_REFINEMENTS with more fun, bonkers prompts | |
| const SURPRISE_REFINEMENTS = [ | |
| // Open-ended prompts (higher probability) | |
| "Do something cool with this. I leave it up to you!", | |
| "Surprise me! Take this image somewhere unexpected.", | |
| "Transform this however you want. Be creative!", | |
| "Do something wild with this image. No limits!", | |
| "Make this image magical in your own way.", | |
| "Take creative freedom with this image. Surprise me!", | |
| "Show me what you can do with this. Go crazy!", | |
| "Transform this in a way I wouldn't expect.", | |
| "Have fun with this and do whatever inspires you.", | |
| "Go wild with this image! Show me something amazing.", | |
| "Put your own creative spin on this image.", | |
| "Reimagine this image however you want. Be bold!", | |
| "Do something unexpected with this. Totally up to you!", | |
| "Surprise me with your creativity. Anything goes!", | |
| "Make this extraordinary in whatever way you choose.", | |
| "Show off your creative abilities with this image!", | |
| "Take this in any direction that excites you!", | |
| "Transform this however your imagination guides you.", | |
| "Make this magical in your own unique way.", | |
| "Do something fun and unexpected with this!", | |
| "Surprise me! Show me your creativity.", | |
| "Make this more beautiful <3", | |
| "Put your artistic spin on this image!", | |
| "Let your imagination run wild with this!", | |
| "Take this image to a whole new level of awesome!", | |
| "Make this image extraordinary in your own way.", | |
| "Do something fantastic with this. Full creative freedom!", | |
| "Surprise me with a totally unexpected transformation!", | |
| "Go nuts with this! Show me something incredible!", | |
| "Add your own wild twist to this image!", | |
| "Make this image come alive however you want!", | |
| "Transform this in the most creative way possible!", | |
| "Go crazy with this. I want to be wowed :))", | |
| "Do whatever magical things you want with this image!", | |
| "Reinvent this image however inspires you!", | |
| // Specific wild ideas (lower probability) | |
| "Can you add this to outer space with aliens having a BBQ?", | |
| "Can you add a giraffe wearing a tuxedo to this?", | |
| "Can you make tiny vikings invade this image?", | |
| "Can you turn this into an ice cream sundae being eaten by robots?", | |
| "Can you make this float in a sea of rainbow soup?", | |
| "Can you add dancing pickles to this image?", | |
| "Can you make this the centerpiece of an alien museum?", | |
| "Can you add this to a cereal bowl being eaten by a giant?", | |
| "Can you make this the star of a bizarre music video?", | |
| "Can you add tiny dinosaurs having a tea party?", | |
| "Can you turn this into something from a fever dream?", | |
| "Can you make this the main character in a surreal fairytale?", | |
| "Can you put this in the middle of a candy landscape?", | |
| "Can you add this to a world where physics works backwards?", | |
| "Can you make this the prize in a cosmic game show?", | |
| "Can you add tiny people worshipping this as a deity?", | |
| "Can you put this in the paws of a giant cosmic cat?", | |
| "Can you make this wearing sunglasses and surfing?", | |
| "Can you add this to a world made entirely of cheese?", | |
| "Can you make this the centerpiece of a goblin birthday party?", | |
| "Can you transform this into a cloud creature floating in the sky?", | |
| "Can you add this to a world where everything is made of pasta?", | |
| "Can you turn this into a piñata at a monster celebration?", | |
| "Can you add this to outer space?", | |
| "Can you add this to a landscape made of breakfast foods?", | |
| "Can you make this the conductor of an orchestra of unusual animals?", | |
| "Can you turn this into a strange plant growing in an alien garden?", | |
| "Can you add this to a world inside a snow globe?", | |
| "Can you make this the secret ingredient in a witch's cauldron?", | |
| "Can you turn this into a superhero with an unusual power?", | |
| "Can you make this swimming in a sea of jelly beans?", | |
| "Can you add this to a planet where everything is upside down?", | |
| "Can you make this the treasure in a dragon's unusual collection?", | |
| "Can you transform this into a character in a bizarre cartoon?", | |
| "Can you add this to a world where shadows come to life?" | |
| ]; | |
| const DisplayCanvas = ({ | |
| displayCanvasRef, | |
| isLoading, | |
| handleSaveImage, | |
| handleRegenerate, | |
| hasGeneratedContent = false, | |
| currentDimension, | |
| onOpenHistory, | |
| onRefineImage, | |
| onSendToDoodle, | |
| hasHistory, | |
| openHistoryModal, | |
| toggleLibrary | |
| }) => { | |
| const [showPlaceholder, setShowPlaceholder] = useState(true); | |
| const [inputValue, setInputValue] = useState(''); | |
| const [showDoodleTooltip, setShowDoodleTooltip] = useState(false); | |
| const [showSaveTooltip, setShowSaveTooltip] = useState(false); | |
| const [isHoveringCanvas, setIsHoveringCanvas] = useState(false); | |
| // Update placeholder visibility when loading or content prop changes | |
| useEffect(() => { | |
| if (hasGeneratedContent) { | |
| setShowPlaceholder(false); | |
| } else if (isLoading) { | |
| setShowPlaceholder(true); | |
| } | |
| }, [isLoading, hasGeneratedContent]); | |
| const handleSubmit = (e) => { | |
| e.preventDefault(); | |
| if (!inputValue.trim()) return; | |
| onRefineImage(inputValue); | |
| setInputValue(''); | |
| }; | |
| const handleSuggestionClick = (suggestion) => { | |
| setInputValue(suggestion.prompt); | |
| document.querySelector('input[name="refiner"]').focus(); | |
| }; | |
| const handleSurpriseMe = () => { | |
| const randomPrompt = SURPRISE_REFINEMENTS[Math.floor(Math.random() * SURPRISE_REFINEMENTS.length)]; | |
| setInputValue(randomPrompt); | |
| document.querySelector('input[name="refiner"]').focus(); | |
| }; | |
| const handleSendToDoodle = useCallback(() => { | |
| if (displayCanvasRef.current && onSendToDoodle) { | |
| const imageDataUrl = displayCanvasRef.current.toDataURL('image/png'); | |
| onSendToDoodle(imageDataUrl); | |
| } | |
| }, [displayCanvasRef, onSendToDoodle]); | |
| // Function to handle clicking the canvas for regeneration | |
| const handleCanvasClickForRegenerate = () => { | |
| if (hasGeneratedContent && !isLoading) { | |
| handleRegenerate(); | |
| } | |
| }; | |
| // Placeholder function for fullscreen action | |
| const handleFullscreen = (e) => { | |
| e.stopPropagation(); // Prevent triggering the refresh | |
| alert('Fullscreen action triggered!'); | |
| // TODO: Implement actual fullscreen logic (e.g., using Fullscreen API or opening image in new tab) | |
| }; | |
| return ( | |
| <div className="flex flex-col"> | |
| {/* Canvas container with fixed aspect ratio */} | |
| <div | |
| className="relative w-full" | |
| style={{ aspectRatio: `${currentDimension.width} / ${currentDimension.height}` }} | |
| > | |
| <button | |
| type="button" | |
| className="w-full h-full absolute inset-0 z-10 cursor-pointer appearance-none bg-transparent border-0 group" | |
| onMouseEnter={() => setIsHoveringCanvas(true)} | |
| onMouseLeave={() => setIsHoveringCanvas(false)} | |
| onClick={handleCanvasClickForRegenerate} | |
| disabled={!hasGeneratedContent || isLoading} | |
| aria-label="Regenerate image" | |
| /> | |
| <canvas | |
| ref={displayCanvasRef} | |
| width={currentDimension.width} | |
| height={currentDimension.height} | |
| className="absolute inset-0 w-full h-full border border-gray-300 bg-white rounded-xl shadow-soft" | |
| aria-label="Generated image canvas" | |
| /> | |
| {/* Loading overlay */} | |
| {isLoading && ( | |
| <div className="absolute inset-0 flex items-center justify-center bg-black/5 rounded-xl"> | |
| <div className="bg-white/90 rounded-full p-3 shadow-medium"> | |
| <LoaderCircle className="w-8 h-8 animate-spin text-gray-700" /> | |
| </div> | |
| </div> | |
| )} | |
| {/* Placeholder overlay */} | |
| {showPlaceholder && !isLoading && !hasGeneratedContent && ( | |
| <div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none"> | |
| <ImageIcon className="w-7 h-7 text-gray-400 mb-2" /> | |
| <p className="text-gray-400 text-lg font-medium">Generation will appear here</p> | |
| </div> | |
| )} | |
| {/* Hover-to-Refresh Overlay */} | |
| {isHoveringCanvas && hasGeneratedContent && !isLoading && ( | |
| // Added transition classes for smoother appearance | |
| // Changed bg-black/20 to bg-black/10 for a gentler overlay | |
| <div className="absolute inset-0 flex items-center justify-center bg-black/0 rounded-xl pointer-events-none transition-opacity duration-300 ease-in-out"> | |
| {/* Container for icon - allows separate click handling */} | |
| {/* Removed Maximize icon */} | |
| <div className="flex items-center gap-4 bg-white/90 rounded-full p-3 shadow-medium pointer-events-auto"> | |
| {/* Refresh Icon (clickable via parent div onClick) */} | |
| <RotateCw className="w-8 h-8 text-gray-700" title="Click canvas to regenerate"/> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Action bar and refiner section - Combined layout */} | |
| <div className="mt-4 flex items-stretch justify-between gap-2 max-w-full"> | |
| {/* Left side wrapper for Refiner and action buttons */} | |
| <div className="flex items-stretch gap-2 flex-1 min-w-0"> | |
| {/* Refiner input - only shown when there's generated content */} | |
| {hasGeneratedContent ? ( | |
| <> | |
| <form onSubmit={handleSubmit} className="flex-1 min-w-0"> | |
| <div className="group flex items-center bg-gray-50 focus-within:bg-white rounded-xl shadow-soft p-2 border border-gray-200 focus-within:border-gray-300 h-14 transition-colors"> | |
| <input | |
| name="refiner" | |
| type="text" | |
| value={inputValue} | |
| onChange={(e) => setInputValue(e.target.value)} | |
| placeholder="Type to refine the image..." | |
| disabled={isLoading} | |
| className="flex-1 px-2 bg-transparent border-none text-sm text-gray-400 placeholder-gray-300 group-focus-within:text-gray-600 group-focus-within:placeholder-gray-400 focus:outline-none transition-colors" | |
| /> | |
| <button | |
| type="submit" | |
| disabled={isLoading || !inputValue.trim()} | |
| className="p-2 rounded-lg text-gray-400 group-focus-within:text-gray-700 hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | |
| aria-label="Send refinement" | |
| > | |
| <Send className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| </form> | |
| {/* Action buttons wrapper */} | |
| <div className="flex gap-2"> | |
| {/* "Send to Doodle" button with tooltip */} | |
| <div className="relative"> | |
| <button | |
| type="button" | |
| onClick={handleSendToDoodle} | |
| disabled={isLoading} | |
| onMouseEnter={() => setShowDoodleTooltip(true)} | |
| onMouseLeave={() => setShowDoodleTooltip(false)} | |
| className="group w-14 h-14 p-2 rounded-lg bg-gray-50 border border-gray-200 shadow-soft hover:bg-white hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center" | |
| aria-label="Send image back to doodle canvas" | |
| > | |
| <ArrowLeftRight className="w-5 h-5 text-gray-400 group-hover:text-gray-700 transition-colors" /> | |
| </button> | |
| {/* Custom Tooltip - Right aligned */} | |
| {showDoodleTooltip && ( | |
| <div className="absolute bottom-full right-0 mb-2 px-3 py-1.5 bg-white border border-gray-200 text-gray-700 text-xs rounded-lg shadow-soft whitespace-nowrap pointer-events-none"> | |
| Send Back to Doodle Canvas | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </> | |
| ) : ( | |
| // Placeholder div to maintain layout when no content | |
| <div className="flex-1 h-0" /> | |
| )} | |
| </div> | |
| </div> | |
| {/* Refined suggestion chips */} | |
| {hasGeneratedContent && ( | |
| <div className="mt-4 flex flex-wrap gap-2 text-xs mb-4"> | |
| {/* Surprise Me button with updated styling */} | |
| <button | |
| type="button" | |
| onClick={handleSurpriseMe} | |
| className="group flex items-center gap-2 px-3 py-1 bg-gray-50 hover:bg-white rounded-full border border-gray-200 text-gray-400 hover:border-gray-300 transition-all focus:outline-none focus:ring-2 focus:ring-gray-200" | |
| > | |
| <Wand2 className="w-4 h-4 group-hover:text-gray-600" /> | |
| <span className="group-hover:text-gray-600">Surprise Me</span> | |
| </button> | |
| {/* Regular suggestion buttons with updated styling */} | |
| {REFINEMENT_SUGGESTIONS.map((suggestion) => ( | |
| <button | |
| key={`suggestion-${suggestion.label}`} | |
| type="button" | |
| onClick={() => handleSuggestionClick(suggestion)} | |
| className="group flex items-center gap-2 px-3 py-1 bg-gray-50 hover:bg-white rounded-full border border-gray-200 text-gray-400 hover:border-gray-300 transition-all focus:outline-none focus:ring-2 focus:ring-gray-200" | |
| > | |
| <suggestion.icon className="w-4 h-4 group-hover:text-gray-600" /> | |
| <span className="group-hover:text-gray-600">{suggestion.label}</span> | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| {/* Header buttons - Mobile only, appearing below refinement options */} | |
| <div className="md:hidden flex flex-wrap justify-between items-center gap-2 mt-6 mb-2 w-full"> | |
| <div className="grid grid-cols-3 w-full gap-2"> | |
| <HeaderButtons | |
| hasHistory={hasHistory} | |
| openHistoryModal={openHistoryModal} | |
| toggleLibrary={toggleLibrary} | |
| handleSaveImage={handleSaveImage} | |
| isLoading={isLoading} | |
| hasGeneratedContent={hasGeneratedContent} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default DisplayCanvas; |