File size: 5,715 Bytes
02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 8769c74 763be49 02ce812 763be49 02ce812 8769c74 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 02ce812 763be49 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
import React, { useEffect, useRef } from 'react';
import { MagicSparkleIcon, CloseIcon, QuestionMarkIcon } from './icons';
interface AiEditModalProps {
isOpen: boolean;
onClose: () => void;
onGenerate: () => void;
onAsk: () => void;
imageUrl: string;
prompt: string;
onPromptChange: (newPrompt: string) => void;
isLoading: boolean;
isAsking: boolean;
error: string | null;
askUrl: string | null;
}
const AiEditModal: React.FC<AiEditModalProps> = ({
isOpen,
onClose,
onGenerate,
onAsk,
imageUrl,
prompt,
onPromptChange,
isLoading,
isAsking,
error,
askUrl,
}) => {
const iframeContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (askUrl && iframeContainerRef.current) {
iframeContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, [askUrl]);
if (!isOpen) return null;
const isAnyLoading = isLoading || isAsking;
return (
<div
className="fixed inset-0 bg-black bg-opacity-60 backdrop-blur-sm flex justify-center items-start z-[1000] p-4 pt-16"
aria-modal="true"
role="dialog"
aria-labelledby="ai-edit-modal-title"
>
<div className="bg-white rounded-lg shadow-2xl p-6 w-full max-w-lg transform transition-all max-h-[calc(100vh-5rem)] overflow-y-auto">
<div className="flex justify-between items-center mb-4">
<h2 id="ai-edit-modal-title" className="text-xl font-semibold text-slate-700">Edit or Ask about Image with AI</h2>
<button
onClick={onClose}
className="text-slate-400 hover:text-slate-600 transition-colors"
aria-label="Close AI edit modal"
disabled={isAnyLoading}
>
<CloseIcon className="w-6 h-6" />
</button>
</div>
<div className="mb-4">
<img
src={imageUrl}
alt="Uploaded image preview"
className="max-w-full w-auto h-32 rounded border border-slate-300 object-contain mx-auto"
/>
</div>
<div className="mb-4">
<label htmlFor="aiPrompt" className="block text-sm font-medium text-slate-700 mb-1">
Describe what you want to change, or ask a question:
</label>
<textarea
id="aiPrompt"
value={prompt}
onChange={(e) => onPromptChange(e.target.value)}
placeholder="e.g., 'make the cat wear a party hat' or 'what is in this image?'"
className="w-full p-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-xl h-[200px]"
disabled={isAnyLoading}
aria-describedby={error ? "ai-edit-error" : undefined}
/>
</div>
{error && (
<div id="ai-edit-error" className="mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-md text-sm" role="alert">
<p className="font-semibold">Error:</p>
<p>{error}</p>
</div>
)}
<div className="flex justify-between items-center gap-3">
<button
onClick={onAsk}
disabled={isAnyLoading || !prompt.trim()}
className="px-4 py-2 bg-sky-600 text-white rounded-md hover:bg-sky-700 focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium flex items-center justify-center gap-2"
>
{isAsking ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Asking...
</>
) : (
<>
<QuestionMarkIcon className="w-4 h-4 mr-1" />
Ask
</>
)}
</button>
<button
onClick={onGenerate}
disabled={isAnyLoading || !prompt.trim()}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium flex items-center justify-center gap-2"
>
{isLoading ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Generating...
</>
) : (
<>
<MagicSparkleIcon className="w-4 h-4 mr-1" />
Generate Image
</>
)}
</button>
</div>
{askUrl && (
<div ref={iframeContainerRef} className="mt-4 pt-2">
<iframe
src={askUrl}
title="AI Response"
className="w-full h-[70vh] border-0 rounded-md bg-slate-50"
/>
</div>
)}
</div>
</div>
);
};
export default AiEditModal;
|