Views
No views yet
setIsGenerating(true);
setError(null);
const newResults: GeneratedImage[] = [];
try {
for (const angleId of selectedAngles) {
const angle = PRESET_ANGLES.find(a => a.id === angleId);
if (angle) {
const generatedUrl = await geminiService.generateFromAngle(
sourceImage.base64,
sourceImage.mimeType,
angle.prompt
);
if (generatedUrl) {
newResults.push({
id: Math.random().toString(36).substr(2, 9),
url: generatedUrl,
angle: angle.label,
timestamp: Date.now()
});
}
}
}
setResults(prev => [...newResults, ...prev]);
} catch (err) {
setError('خطا در برقراری ارتباط با هوش مصنوعی. لطفا دوباره تلاش کنید.');
console.error(err);
} finally {
setIsGenerating(false);
}angle-${angle}-${Date.now()}.png;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}; <main className="max-w-6xl mx-auto px-4 mt-8 grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Left Column: Upload & Config */}
<div className="lg:col-span-5 space-y-6">
<section className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h2 className="text-lg font-bold mb-4 flex items-center gap-2">
<i className="fas fa-upload text-blue-500"></i>
آپلود عکس مرجع
</h2>
<div
onClick={() => fileInputRef.current?.click()}
className={`border-2 border-dashed rounded-xl p-8 flex flex-col items-center justify-center cursor-pointer transition-all ${
previewUrl ? 'border-blue-300 bg-blue-50' : 'border-gray-300 hover:border-blue-400 hover:bg-gray-50'
}`}
>
{previewUrl ? (
<div className="relative w-full aspect-square max-w-[300px]">
<img src={previewUrl} alt="Preview" className="w-full h-full object-contain rounded-lg shadow-md" />
<div className="absolute inset-0 bg-black/40 opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center rounded-lg">
<span className="text-white text-sm font-medium">تغییر عکس</span>
</div>
</div>
) : (
<>
<i className="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-4"></i>
<p className="text-gray-600 font-medium">عکس خود را اینجا رها کنید یا کلیک کنید</p>
<p className="text-xs text-gray-400 mt-2">PNG, JPG (حداکثر ۱۰ مگابایت)</p>
</>
)}
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
accept="image/*"
/>
</div>
</section>
<section className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h2 className="text-lg font-bold mb-4 flex items-center gap-2">
<i className="fas fa-camera-rotate text-indigo-500"></i>
انتخاب زوایا
</h2>
<div className="grid grid-cols-1 gap-3">
{PRESET_ANGLES.map((angle) => (
<button
key={angle.id}
onClick={() => toggleAngle(angle.id)}
className={`flex items-center justify-between p-4 rounded-xl border transition-all ${
selectedAngles.includes(angle.id)
? 'bg-blue-600 border-blue-600 text-white shadow-lg shadow-blue-200'
: 'bg-white border-gray-200 text-gray-700 hover:border-blue-400'
}`}
>
<span className="font-medium text-sm">{angle.label}</span>
{selectedAngles.includes(angle.id) ? (
<i className="fas fa-check-circle"></i>
) : (
<i className="far fa-circle text-gray-300"></i>
)}
</button>
))}
</div>
<button
onClick={handleGenerate}
disabled={!sourceImage || selectedAngles.length === 0 || isGenerating}
className={`w-full mt-6 py-4 rounded-xl font-bold flex items-center justify-center gap-3 transition-all ${
isGenerating
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
: 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:shadow-xl hover:scale-[1.02] active:scale-[0.98]'
}`}
>
{isGenerating ? (
<>
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-gray-400"></div>
در حال تولید تصاویر...
</>
) : (
<>
<i className="fas fa-magic"></i>
تولید زوایای انتخابی
</>
)}
</button>
{error && (
<p className="text-red-500 text-sm mt-4 text-center bg-red-50 p-3 rounded-lg border border-red-100">
{error}
</p>
)}
</section>
</div>
{/* Right Column: Results */}
<div className="lg:col-span-7 space-y-6">
<section className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 min-h-[600px]">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-bold flex items-center gap-2">
<i className="fas fa-images text-purple-500"></i>
نتایج تولید شده
</h2>
{results.length > 0 && (
<button
onClick={clearResults}
className="text-gray-400 hover:text-red-500 transition-colors text-sm font-medium"
>
پاکسازی همه
</button>
)}
</div>
{results.length === 0 ? (
<div className="flex flex-col items-center justify-center h-[500px] text-gray-400 space-y-4">
<div className="w-20 h-20 bg-gray-50 rounded-full flex items-center justify-center border border-gray-100">
<i className="fas fa-image-polaroid text-3xl"></i>
</div>
<p>هنوز تصویری تولید نشده است.</p>
<p className="text-sm">عکس را آپلود کرده و زوایای دلخواه را انتخاب کنید.</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{results.map((img) => (
<div key={img.id} className="group relative bg-gray-50 rounded-2xl overflow-hidden border border-gray-200 hover:shadow-xl transition-all">
<div className="aspect-square bg-white">
<img
src={img.url}
alt={img.angle}
className="w-full h-full object-contain p-2"
/>
</div>
<div className="p-4 bg-white border-t border-gray-100 flex items-center justify-between">
<div>
<p className="text-xs text-gray-400 mb-1">زاویه:</p>
<p className="text-sm font-bold text-gray-800">{img.angle}</p>
</div>
<button
onClick={() => downloadImage(img.url, img.angle)}
className="bg-gray-100 hover:bg-blue-600 hover:text-white text-gray-600 p-3 rounded-xl transition-all"
title="دانلود تصویر"
>
<i className="fas fa-download"></i>
</button>
</div>
</div>
))}
</div>
)}
</section>
</div>
</main>
{/* Footer Info */}
<footer className="max-w-6xl mx-auto px-4 mt-12 text-center text-gray-400 text-sm">
<p>توسعه یافته با ❤️ با استفاده از مدلهای پیشرفته هوش مصنوعی گوگل</p>
</footer>
</div>