1→ 1→'use client'; 2→ 2→ 3→ 3→import { useState, useCallback, useRef, useEffect } from 'react'; 4→ 4→import { motion, AnimatePresence } from 'framer-motion'; 5→ 5→import { 6→ 6→ Upload, Image as ImageIcon, Sparkles, Download, ArrowRight, 7→ 7→ Palette, Camera, Layers, SlidersHorizontal, Loader2, Check, 8→ 8→ Trash2, RotateCcw, Eye, Wand2, Sun, Paintbrush, Film, 9→ 9→ Pencil, History, ChevronLeft, X, GripVertical 10→ 10→} from 'lucide-react'; 11→ 11→import { Button } from '@/components/ui/button'; 12→ 12→import { Textarea } from '@/components/ui/textarea'; 13→ 13→import { Progress } from '@/components/ui/progress'; 14→ 14→import { Card, CardContent } from '@/components/ui/card'; 15→ 15→import { 16→ 16→ Dialog, 17→ 17→ DialogContent, 18→ 18→ DialogHeader, 19→ 19→ DialogTitle, 20→ 20→ DialogTrigger, 21→ 21→} from '@/components/ui/dialog'; 22→ 22→import { useAppStore, type PhotoProject } from '@/store/photo-store'; 23→ 23→ 24→ 24→/* ─── Edit Tool Definitions ─── */ 25→ 25→const EDIT_TOOLS = [ 26→ 26→ { id: 'oil-painting', label: 'لوحة زيتية', icon: Paintbrush, prompt: 'Transform this image into an oil painting style, with visible brushstrokes and rich, vibrant colors, maintain the composition and subject', color: 'from-amber-500/20 to-orange-600/20' }, 27→ 27→ { id: 'cartoon', label: 'رسم كرتوني', icon: Pencil, prompt: 'Transform this image into a cartoon illustration style, with bold outlines, flat colors, and simplified shapes, keep the main subject recognizable', color: 'from-pink-500/20 to-rose-600/20' }, 28→ 28→ { id: 'bw', label: 'أبيض وأسود', icon: Camera, prompt: 'Convert this image to a stunning black and white photograph with high contrast and dramatic lighting, professional photography style', color: 'from-gray-500/20 to-gray-700/20' }, 29→ 29→ { id: 'golden-hour', label: 'إضاءة ذهبية', icon: Sun, prompt: 'Apply warm golden hour lighting to this image, with beautiful warm tones, soft shadows, and a magical sunset glow, maintain all subjects', color: 'from-yellow-500/20 to-amber-600/20' }, 30→ 30→ { id: 'enhance', label: 'تحسين الجودة', icon: Sparkles, prompt: 'Enhance the quality and sharpness of this image, improve colors, add more detail and clarity, make it look professional and high-resolution', color: 'from-emerald-500/20 to-teal-600/20' }, 31→ 31→ { id: 'bg-remove', label: 'خلفية احترافية', icon: Layers, prompt: 'Replace the background with a clean, modern, professional studio-like background, keep the main subject exactly the same with proper lighting', color: 'from-cyan-500/20 to-blue-600/20' }, 32→ 32→ { id: 'cinematic', label: 'تأثير سينمائي', icon: Film, prompt: 'Apply a cinematic color grading to this image, with deep shadows, teal and orange tones, letterbox style, dramatic and moody atmosphere', color: 'from-violet-500/20 to-purple-600/20' }, 33→ 33→ { id: 'illustration', label: 'أسلوب رسومي', icon: Palette, prompt: 'Transform this image into a digital illustration style, clean lines, artistic rendering, vibrant but harmonious colors, maintain the scene composition', color: 'from-fuchsia-500/20 to-pink-600/20' }, 34→ 34→]; 35→ 35→ 36→ 36→/* ─── Header Component ─── */ 37→ 37→function Header() { 38→ 38→ const { currentView, setView } = useAppStore(); 39→ 39→ return ( 40→ 40→
41→ 41→
42→ 42→ 51→ 51→ 71→ 71→
72→ 72→
73→ 73→ ); 74→ 74→} 75→ 75→ 76→ 76→/* ─── Footer Component ─── */ 77→ 77→function Footer() { 78→ 78→ return ( 79→ 79→ 85→ 85→ ); 86→ 86→} 87→ 87→ 88→ 88→/* ─── Upload Area Component ─── */ 89→ 89→function UploadArea() { 90→ 90→ const { setOriginalImage, setOriginalFileName, setView, setAnalysis, setEditedImage, setEditError } = useAppStore(); 91→ 91→ const [isDragOver, setIsDragOver] = useState(false); 92→ 92→ const [isUploading, setIsUploading] = useState(false); 93→ 93→ const fileInputRef = useRef(null); 94→ 94→ 95→ 95→ const handleFile = useCallback(async (file: File) => { 96→ 96→ if (!file.type.startsWith('image/')) return; 97→ 97→ setIsUploading(true); 98→ 98→ setEditError(null); 99→ 99→ setEditedImage(null); 100→ 100→ setAnalysis(null); 101→ 101→ try { 102→ 102→ const formData = new FormData(); 103→ 103→ formData.append('image', file); 104→ 104→ 105→ 105→ const res = await fetch('/api/upload', { method: 'POST', body: formData }); 106→ 106→ const data = await res.json(); 107→ 107→ if (!res.ok) throw new Error(data.error || 'فشل رفع الصورة'); 108→ 108→ 109→ 109→ setOriginalImage(data.imageUrl); 110→ 110→ setOriginalFileName(file.name); 111→ 111→ setView('editor'); 112→ 112→ } catch (err: unknown) { 113→ 113→ const message = err instanceof Error ? err.message : 'حدث خطأ'; 114→ 114→ setEditError(message); 115→ 115→ } finally { 116→ 116→ setIsUploading(false); 117→ 117→ } 118→ 118→ }, [setOriginalImage, setOriginalFileName, setView, setEditedImage, setAnalysis, setEditError]); 119→ 119→ 120→ 120→ const onDrop = useCallback((e: React.DragEvent) => { 121→ 121→ e.preventDefault(); 122→ 122→ setIsDragOver(false); 123→ 123→ const file = e.dataTransfer.files[0]; 124→ 124→ if (file) handleFile(file); 125→ 125→ }, [handleFile]); 126→ 126→ 127→ 127→ const onDragOver = useCallback((e: React.DragEvent) => { 128→ 128→ e.preventDefault(); 129→ 129→ setIsDragOver(true); 130→ 130→ }, []); 131→ 131→ 132→ 132→ const onDragLeave = useCallback(() => setIsDragOver(false), []); 133→ 133→ 134→ 134→ return ( 135→ 135→ 141→ 141→
fileInputRef.current?.click()} 146→ 146→ className={` 147→ 147→ relative cursor-pointer rounded-2xl border-2 border-dashed p-12 sm:p-16 148→ 148→ transition-all duration-300 text-center 149→ 149→ ${isDragOver 150→ 150→ ? 'upload-area-active border-primary' 151→ 151→ : 'border-muted-foreground/30 hover:border-primary/50 hover:bg-primary/5' 152→ 152→ } 153→ 153→ `} 154→ 154→ > 155→ 155→ { 161→ 161→ const file = e.target.files?.[0]; 162→ 162→ if (file) handleFile(file); 163→ 163→ e.target.value = ''; 164→ 164→ }} 165→ 165→ /> 166→ 166→ {isUploading ? ( 167→ 167→
168→ 168→ 169→ 169→

جارٍ رفع الصورة...

170→ 170→
171→ 171→ ) : ( 172→ 172→
173→ 173→
174→ 174→ 175→ 175→
176→ 176→
177→ 177→

اسحب الصورة هنا أو انقر للاختيار

178→ 178→

يدعم: PNG, JPG, WEBP — حتى 10 ميجا

179→ 179→
180→ 180→
181→ 181→ )} 182→ 182→
183→ 183→
184→ 184→ ); 185→ 185→} 186→ 186→ 187→ 187→/* ─── Feature Cards ─── */ 188→ 188→function FeatureCards() { 189→ 189→ const features = [ 190→ 190→ { icon: Sparkles, title: 'تحرير بالذكاء', desc: 'أدوات تعديل ذكية تفهم صورتك وتحسّنها' }, 191→ 191→ { icon: Palette, title: 'أنماط فنية', desc: 'حوّل صورك إلى لوحات زيتية أو رسوم كرتونية' }, 192→ 192→ { icon: SlidersHorizontal, title: 'مقارنة فورية', desc: 'قارن بين الأصل والنسخة المحرّرة بسهولة' }, 193→ 193→ { icon: Download, title: 'تحميل مباشر', desc: 'حمّل الصور المحرّرة بجودة عالية' }, 194→ 194→ ]; 195→ 195→ return ( 196→ 196→
197→ 197→ {features.map((f, i) => ( 198→ 198→ 204→ 204→ 205→ 205→ 206→ 206→
207→ 207→ 208→ 208→
209→ 209→

{f.title}

210→ 210→

{f.desc}

211→ 211→
212→ 212→
213→ 213→
214→ 214→ ))} 215→ 215→
216→ 216→ ); 217→ 217→} 218→ 218→ 219→ 219→/* ─── Home View ─── */ 220→ 220→function HomeView() { 221→ 221→ return ( 222→ 222→
223→ 223→ 229→ 229→

230→ 230→ محرر الصور بالذكاء الاصطناعي 231→ 231→

232→ 232→

233→ 233→ ارفع صورتك واختر التأثير المطلوب — وشاهد الذكاء الاصطناعي يُبدع 234→ 234→

235→ 235→
236→ 236→ 237→ 237→ 238→ 238→
239→ 239→ ); 240→ 240→} 241→ 241→ 242→ 242→/* ─── Before/After Comparison Slider ─── */ 243→ 243→function ComparisonSlider({ original, edited }: { original: string; edited: string }) { 244→ 244→ const containerRef = useRef(null); 245→ 245→ const [position, setPosition] = useState(50); 246→ 246→ const [containerWidth, setContainerWidth] = useState(0); 247→ 247→ const isDragging = useRef(false); 248→ 248→ 249→ 249→ useEffect(() => { 250→ 250→ const el = containerRef.current; 251→ 251→ if (!el) return; 252→ 252→ const observer = new ResizeObserver((entries) => { 253→ 253→ for (const entry of entries) { 254→ 254→ setContainerWidth(entry.contentRect.width); 255→ 255→ } 256→ 256→ }); 257→ 257→ observer.observe(el); 258→ 258→ return () => observer.disconnect(); 259→ 259→ }, []); 260→ 260→ 261→ 261→ const updatePosition = useCallback((clientX: number) => { 262→ 262→ if (!containerRef.current) return; 263→ 263→ const rect = containerRef.current.getBoundingClientRect(); 264→ 264→ // RTL: position is measured from the right edge 265→ 265→ const x = clientX - rect.left; 266→ 266→ const pct = Math.min(100, Math.max(0, (x / rect.width) * 100)); 267→ 267→ setPosition(pct); 268→ 268→ }, []); 269→ 269→ 270→ 270→ const handlePointerDown = useCallback((e: React.PointerEvent) => { 271→ 271→ isDragging.current = true; 272→ 272→ (e.target as HTMLElement).setPointerCapture(e.pointerId); 273→ 273→ updatePosition(e.clientX); 274→ 274→ }, [updatePosition]); 275→ 275→ 276→ 276→ const handlePointerMove = useCallback((e: React.PointerEvent) => { 277→ 277→ if (!isDragging.current) return; 278→ 278→ updatePosition(e.clientX); 279→ 279→ }, [updatePosition]); 280→ 280→ 281→ 281→ const handlePointerUp = useCallback(() => { 282→ 282→ isDragging.current = false; 283→ 283→ }, []); 284→ 284→ 285→ 285→ return ( 286→ 286→
294→ 294→ {/* Edited image (full width, behind) */} 295→ 295→ After 301→ 301→ 302→ 302→ {/* Original image (clipped from right in RTL) */} 303→ 303→
307→ 307→ Before 0 ? `${containerWidth}px` : '100%' }} 312→ 312→ draggable={false} 313→ 313→ /> 314→ 314→
315→ 315→ 316→ 316→ {/* Slider line */} 317→ 317→
321→ 321→
322→ 322→ 323→ 323→
324→ 324→
325→ 325→ 326→ 326→ {/* Labels */} 327→ 327→
الأصلي
328→ 328→
المحرّر
329→ 329→
330→ 330→ ); 331→ 331→} 332→ 332→ 333→ 333→/* ─── Editor View ─── */ 334→ 334→function EditorView() { 335→ 335→ const { 336→ 336→ originalImage, editedImage, isEditing, editProgress, editError, 337→ 337→ analysis, customPrompt, setCustomPrompt, setEditedImage, 338→ 338→ setEditProgress, setIsEditing, setEditError, setAnalysis, 339→ 339→ setView, resetEditor, projects, setProjects, originalFileName, 340→ 340→ } = useAppStore(); 341→ 341→ const [selectedTool, setSelectedTool] = useState(null); 342→ 342→ const [isAnalyzing, setIsAnalyzing] = useState(false); 343→ 343→ const fileInputRef = useRef(null); 344→ 344→ 345→ 345→ const handleAnalyze = useCallback(async () => { 346→ 346→ if (!originalImage) return; 347→ 347→ setIsAnalyzing(true); 348→ 348→ try { 349→ 349→ const res = await fetch('/api/analyze', { 350→ 350→ method: 'POST', 351→ 351→ headers: { 'Content-Type': 'application/json' }, 352→ 352→ body: JSON.stringify({ imageUrl: originalImage }), 353→ 353→ }); 354→ 354→ const data = await res.json(); 355→ 355→ if (!res.ok) throw new Error(data.error || 'فشل التحليل'); 356→ 356→ setAnalysis(data.analysis); 357→ 357→ } catch (err: unknown) { 358→ 358→ const message = err instanceof Error ? err.message : 'حدث خطأ'; 359→ 359→ setEditError(message); 360→ 360→ } finally { 361→ 361→ setIsAnalyzing(false); 362→ 362→ } 363→ 363→ }, [originalImage, setAnalysis, setEditError]); 364→ 364→ 365→ 365→ const handleEdit = useCallback(async (prompt: string, label: string, toolType: string) => { 366→ 366→ if (!originalImage) return; 367→ 367→ setIsEditing(true); 368→ 368→ setEditError(null); 369→ 369→ setEditedImage(null); 370→ 370→ setEditProgress(0); 371→ 371→ 372→ 372→ try { 373→ 373→ const res = await fetch('/api/edit', { 374→ 374→ method: 'POST', 375→ 375→ headers: { 'Content-Type': 'application/json' }, 376→ 376→ body: JSON.stringify({ 377→ 377→ imageUrl: originalImage, 378→ 378→ prompt, 379→ 379→ label, 380→ 380→ toolType, 381→ 381→ }), 382→ 382→ }); 383→ 383→ 384→ 384→ if (!res.ok) { 385→ 385→ const data = await res.json(); 386→ 386→ throw new Error(data.error || 'فشل التعديل'); 387→ 387→ } 388→ 388→ 389→ 389→ const reader = res.body?.getReader(); 390→ 390→ if (!reader) throw new Error('لا يمكن قراءة الاستجابة'); 391→ 391→ 392→ 392→ const decoder = new TextDecoder(); 393→ 393→ let buffer = ''; 394→ 394→ 395→ 395→ while (true) { 396→ 396→ const { done, value } = await reader.read(); 397→ 397→ if (done) break; 398→ 398→ 399→ 399→ buffer += decoder.decode(value, { stream: true }); 400→ 400→ const lines = buffer.split('\n'); 401→ 401→ buffer = lines.pop() || ''; 402→ 402→ 403→ 403→ for (const line of lines) { 404→ 404→ if (line.startsWith('data: ')) { 405→ 405→ const data = line.slice(6); 406→ 406→ let parsed: Record; 407→ 407→ try { 408→ 408→ parsed = JSON.parse(data); 409→ 409→ } catch { 410→ 410→ continue; // Skip malformed JSON lines 411→ 411→ } 412→ 412→ 413→ 413→ if (parsed.type === 'progress') { 414→ 414→ setEditProgress(parsed.value as number); 415→ 415→ } else if (parsed.type === 'done') { 416→ 416→ setEditedImage(parsed.editedImageUrl as string); 417→ 417→ setEditProgress(100); 418→ 418→ // Refresh gallery 419→ 419→ try { 420→ 420→ const projectsRes = await fetch('/api/projects'); 421→ 421→ if (projectsRes.ok) { 422→ 422→ const projectsData = await projectsRes.json(); 423→ 423→ setProjects(projectsData); 424→ 424→ } 425→ 425→ } catch { /* ignore gallery refresh failure */ } 426→ 426→ } else if (parsed.type === 'error') { 427→ 427→ throw new Error(parsed.message as string); 428→ 428→ } 429→ 429→ } 430→ 430→ } 431→ 431→ } 432→ 432→ } catch (err: unknown) { 433→ 433→ const message = err instanceof Error ? err.message : 'حدث خطأ أثناء التعديل'; 434→ 434→ setEditError(message); 435→ 435→ } finally { 436→ 436→ setIsEditing(false); 437→ 437→ setSelectedTool(null); 438→ 438→ } 439→ 439→ }, [originalImage, setIsEditing, setEditError, setEditedImage, setEditProgress, setProjects]); 440→ 440→ 441→ 441→ const handleCustomEdit = useCallback(() => { 442→ 442→ if (!customPrompt.trim()) return; 443→ 443→ handleEdit(customPrompt.trim(), 'تعديل مخصص', 'custom'); 444→ 444→ }, [customPrompt, handleEdit]); 445→ 445→ 446→ 446→ const handleNewImage = useCallback(() => { 447→ 447→ resetEditor(); 448→ 448→ setView('home'); 449→ 449→ }, [resetEditor, setView]); 450→ 450→ 451→ 451→ const handleReupload = useCallback(async (e: React.ChangeEvent) => { 452→ 452→ const file = e.target.files?.[0]; 453→ 453→ if (!file || !file.type.startsWith('image/')) return; 454→ 454→ const formData = new FormData(); 455→ 455→ formData.append('image', file); 456→ 456→ try { 457→ 457→ const res = await fetch('/api/upload', { method: 'POST', body: formData }); 458→ 458→ const data = await res.json(); 459→ 459→ if (!res.ok) throw new Error(data.error); 460→ 460→ useAppStore.getState().setOriginalImage(data.imageUrl); 461→ 461→ useAppStore.getState().setOriginalFileName(file.name); 462→ 462→ useAppStore.getState().setEditedImage(null); 463→ 463→ useAppStore.getState().setAnalysis(null); 464→ 464→ } catch {} 465→ 465→ e.target.value = ''; 466→ 466→ }, []); 467→ 467→ 468→ 468→ const handleDownload = useCallback(() => { 469→ 469→ if (!editedImage) return; 470→ 470→ const a = document.createElement('a'); 471→ 471→ a.href = editedImage; 472→ 472→ a.download = `edited-${originalFileName || 'image.png'}`; 473→ 473→ a.click(); 474→ 474→ }, [editedImage, originalFileName]); 475→ 475→ 476→ 476→ return ( 477→ 477→
478→ 478→ {/* Top bar */} 479→ 479→
480→ 480→ 484→ 484→ {editedImage && ( 485→ 485→ 489→ 489→ )} 490→ 490→
491→ 491→ 492→ 492→
493→ 493→ {/* Image Preview (2/3 width on desktop) */} 494→ 494→
495→ 495→ 496→ 496→ 497→ 497→ {/* Image display */} 498→ 498→
499→ 499→ {isEditing && ( 500→ 500→
501→ 501→ 502→ 502→

جارٍ التعديل بالذكاء الاصطناعي...

503→ 503→ 504→ 504→

{editProgress}%

505→ 505→
506→ 506→ )} 507→ 507→ 508→ 508→ {editedImage ? ( 509→ 509→ 510→ 510→ ) : ( 511→ 511→ Original 516→ 516→ )} 517→ 517→
518→ 518→ 519→ 519→ {/* Image info bar */} 520→ 520→
521→ 521→ {originalFileName || 'صورة'} 522→ 522→ 526→ 526→ 527→ 527→
528→ 528→
529→ 529→
530→ 530→ 531→ 531→ {/* Analysis section */} 532→ 532→
533→ 533→ 546→ 546→ 547→ 547→ {analysis && ( 548→ 548→ 553→ 553→ 554→ 554→ 555→ 555→

556→ 556→ 557→ 557→ نتيجة التحليل 558→ 558→

559→ 559→

{analysis}

560→ 560→
561→ 561→
562→ 562→
563→ 563→ )} 564→ 564→
565→ 565→
566→ 566→
567→ 567→ 568→ 568→ {/* Tools Panel (1/3 width on desktop) */} 569→ 569→
570→ 570→ {/* Quick Tools */} 571→ 571→ 572→ 572→ 573→ 573→

574→ 574→ 575→ 575→ أدوات التحرير السريعة 576→ 576→

577→ 577→
578→ 578→ {EDIT_TOOLS.map((tool) => { 579→ 579→ const Icon = tool.icon; 580→ 580→ const isLoading = isEditing && selectedTool === tool.id; 581→ 581→ return ( 582→ 582→ 606→ 606→ ); 607→ 607→ })} 608→ 608→
609→ 609→
610→ 610→
611→ 611→ 612→ 612→ {/* Custom Prompt */} 613→ 613→ 614→ 614→ 615→ 615→

616→ 616→ 617→ 617→ تعديل مخصص 618→ 618→

619→ 619→