Spaces:
Running
Running
| # image_agent_gradio/app.py | |
| # وكيل توليد الصور (محدث لدعم وكيل الفيديو) | |
| # ============================================================ | |
| import os | |
| import io | |
| import base64 | |
| import logging | |
| from typing import Optional, Dict, Any, Tuple | |
| import requests | |
| import gradio as gr | |
| from PIL import Image | |
| from gradio_client import Client as GradioClient | |
| # -------------------------- استيرادات الكممَة والبيئة ------------------------- | |
| try: | |
| from optimum.intel.openvino import OVDiffusionPipeline | |
| import torch | |
| LOCAL_OPTIMUM = True | |
| except ImportError: | |
| LOCAL_OPTIMUM = False | |
| try: | |
| from diffusers import StableDiffusionPipeline | |
| import torch | |
| except ImportError: | |
| pass | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") | |
| log = logging.getLogger("image_agent") | |
| if 'OVDiffusionPipeline' in globals(): | |
| log.info("✅ Optimum/OpenVINO متاح للتحسين على CPU باستخدام OVDiffusionPipeline.") | |
| elif 'StableDiffusionPipeline' in globals(): | |
| log.info("⚠️ OpenVINO غير متاح. سيتم استخدام Diffusers القياسي.") | |
| else: | |
| log.warning("❌ لم يتم العثور على مكتبات Diffusers أو Optimum.") | |
| LOCAL_DIFFUSERS = LOCAL_OPTIMUM or ('StableDiffusionPipeline' in globals()) | |
| HF_TOKEN = os.getenv("HF_API_TOKEN") | |
| HF_MODEL = os.getenv("HF_MODEL", "OpenVINO/stable-diffusion-xl-base-1.0-int8-ov") | |
| # عنوان وكيل الفيديو | |
| VIDEO_AGENT_URL = os.getenv("VIDEO_AGENT_URL", "https://mustafa-albakkar-videoagent.hf.space") | |
| # ---------------- Initialization Check ---------------- | |
| if LOCAL_OPTIMUM: | |
| STATUS_MESSAGE = f"✅ الوضع المحلي (OpenVINO Optimized) نشط: {HF_MODEL}" | |
| elif LOCAL_DIFFUSERS: | |
| STATUS_MESSAGE = f"⚠️ الوضع المحلي (Diffusers) نشط: {HF_MODEL} - قد يكون بطيئاً جداً على CPU." | |
| else: | |
| if HF_TOKEN: | |
| STATUS_MESSAGE = f"✅ وضع واجهة HF API نشط: {HF_MODEL}" | |
| else: | |
| STATUS_MESSAGE = "❌ خطأ: لم يتم تعيين HF_API_TOKEN، والتوليد المحلي غير نشط." | |
| # ---------------- Local Pipeline Cache ---------------- | |
| _pipeline = None | |
| def get_local_pipeline(): | |
| """تهيئة وإرجاع خط أنابيب Diffusers/Optimum المحلي.""" | |
| global _pipeline | |
| if _pipeline is None: | |
| if not LOCAL_DIFFUSERS: | |
| raise RuntimeError("التوليد المحلي غير متاح. تأكد من إعداد الاعتماديات.") | |
| if HF_MODEL is None: | |
| log.error("HF_MODEL is None. Cannot load pipeline.") | |
| raise ValueError("❌ خطأ: لم يتم تعيين مسار النموذج (HF_MODEL).") | |
| log.info(f"Loading local diffusers pipeline from: {HF_MODEL}") | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| pipeline_args = {} | |
| if device == "cuda": | |
| pipeline_args['torch_dtype'] = torch.float16 | |
| if LOCAL_OPTIMUM: | |
| log.info("Loading OVDiffusionPipeline (Quantized pipeline).") | |
| _pipeline = OVDiffusionPipeline.from_pretrained(HF_MODEL) | |
| else: | |
| log.info("Loading standard StableDiffusionPipeline.") | |
| _pipeline = StableDiffusionPipeline.from_pretrained( | |
| HF_MODEL, | |
| **pipeline_args | |
| ) | |
| _pipeline = _pipeline.to(device) | |
| return _pipeline | |
| # ---------------- Inference Functions (Synchronous) ---------------- | |
| def hf_inference(prompt: str) -> Tuple[Optional[Image.Image], str]: | |
| """يولد صورة عبر واجهة Hugging Face Inference API.""" | |
| if not HF_TOKEN: | |
| return None, "❌ فشل: لم يتم تعيين مفتاح 'HF_API_TOKEN'." | |
| url = f"https://api-inference.huggingface.co/models/{HF_MODEL}" | |
| headers = {"Authorization": f"Bearer {HF_TOKEN}"} | |
| payload = {"inputs": prompt} | |
| try: | |
| log.info(f"Sending prompt to HF API: {prompt[:50]}...") | |
| r = requests.post(url, headers=headers, json=payload, timeout=120) | |
| r.raise_for_status() | |
| if r.headers.get("content-type", "").startswith("image"): | |
| image_bytes = r.content | |
| image = Image.open(io.BytesIO(image_bytes)) | |
| return image, "✅ تم التوليد بنجاح (HF Inference API)." | |
| data = r.json() | |
| if isinstance(data, dict) and data.get("error"): | |
| return None, f"❌ خطأ HF: {data.get('error')}" | |
| return None, "❌ فشل: تنسيق استجابة غير مدعوم من HF API." | |
| except requests.exceptions.HTTPError as e: | |
| status_code = e.response.status_code | |
| if status_code == 503: | |
| return None, "⚠️ الخدمة غير متوفرة (503): قد تكون محملة أو غير جاهزة. حاول مرة أخرى." | |
| return None, f"❌ خطأ HTTP: {status_code}. التفاصيل: {e}" | |
| except Exception as e: | |
| log.error("HF request failed: %s", e) | |
| return None, f"❌ فشل الطلب: {e}" | |
| def local_generate(prompt: str) -> Tuple[Optional[Image.Image], str]: | |
| """يولد صورة باستخدام خط أنابيب Diffusers/Optimum المحلي.""" | |
| try: | |
| pipe = get_local_pipeline() | |
| INFERENCE_STEPS = 70 | |
| log.info(f"Local generation started for prompt: {prompt[:50]}... with {INFERENCE_STEPS} steps.") | |
| result = pipe(prompt, num_inference_steps=INFERENCE_STEPS) | |
| if not hasattr(result, 'images') or result.images is None: | |
| log.error("Pipeline result does not contain valid images property or it is None.") | |
| return None, "❌ فشل التوليد المحلي: خط أنابيب التوليد لم يرجع أي قائمة صور صالحة." | |
| valid_images = [img for img in result.images if img is not None] | |
| if hasattr(result, 'nsfw_content_detected') and any(result.nsfw_content_detected): | |
| return None, "❌ فشل التوليد المحلي: تم اكتشاف محتوى غير آمن." | |
| if valid_images: | |
| return valid_images[0], f"✅ تم التوليد بنجاح (محليًا) في {INFERENCE_STEPS} خطوة." | |
| else: | |
| return None, "❌ فشل التوليد المحلي: لم يتم إرجاع صور صالحة." | |
| except Exception as e: | |
| log.error("Local generation error: %s", e) | |
| return None, f"❌ خطأ التوليد المحلي: {type(e).__name__}: {e}" | |
| # ---------------- NEW: Video Agent Integration ---------------- | |
| def send_to_video_agent( | |
| image_base64: str, | |
| quote: str, | |
| author: str, | |
| culture: str | |
| ) -> Dict[str, Any]: | |
| """ | |
| إرسال البيانات إلى وكيل الفيديو للمعالجة. | |
| Returns: | |
| Dict containing video_base64 and status | |
| """ | |
| try: | |
| log.info(f"Sending data to Video Agent at {VIDEO_AGENT_URL}...") | |
| client = GradioClient(VIDEO_AGENT_URL) | |
| result = client.predict( | |
| image_base64, | |
| quote, | |
| author, | |
| culture, | |
| api_name="/generate_video" | |
| ) | |
| log.info("✅ Video Agent response received successfully.") | |
| return result | |
| except Exception as e: | |
| log.error(f"Failed to communicate with Video Agent: {e}") | |
| raise RuntimeError(f"Video Agent communication failed: {e}") | |
| # ---------------- Gradio Main Function (UI) ---------------- | |
| def generate_image_gradio(prompt: str, use_local: bool) -> Tuple[Optional[Image.Image], str]: | |
| """ | |
| الدالة الرئيسية لواجهة Gradio التفاعلية. | |
| """ | |
| if not prompt: | |
| return None, "الرجاء إدخال نص مطالبة (prompt) للتوليد." | |
| # 1. التوليد المحلي | |
| if use_local and LOCAL_DIFFUSERS: | |
| return local_generate(prompt) | |
| # 2. واجهة HF API | |
| return hf_inference(prompt) | |
| # ---------------- Gradio API Endpoint (For Publisher Agent) ---------------- | |
| def gradio_api_endpoint(prompt: str) -> Dict[str, Any]: | |
| """ | |
| نقطة النهاية المخصصة للاستدعاء بواسطة Gradio Client (وكيل النشر). | |
| """ | |
| log.info(f"API Endpoint received prompt: {prompt[:50]}...") | |
| # تفضيل الوضع المحلي على واجهة HF API إذا كان متاحاً | |
| if LOCAL_DIFFUSERS: | |
| image, status_msg = local_generate(prompt) | |
| else: | |
| image, status_msg = hf_inference(prompt) | |
| if image is None: | |
| log.error(f"API Endpoint failed to generate image: {status_msg}") | |
| raise RuntimeError(f"فشل توليد الصورة: {status_msg}") | |
| # تحويل صورة PIL إلى Base64 | |
| buffered = io.BytesIO() | |
| image.save(buffered, format="JPEG") | |
| img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") | |
| log.info(f"API Endpoint success. Image size: {len(img_str)} bytes.") | |
| return { | |
| "image_base64": img_str, | |
| "status": status_msg | |
| } | |
| # ---------------- NEW: Extended API with Video Generation ---------------- | |
| def gradio_api_with_video( | |
| prompt: str, | |
| quote: str, | |
| author: str, | |
| culture: str, | |
| generate_video: bool = False | |
| ) -> Dict[str, Any]: | |
| """ | |
| نقطة نهاية موسعة تدعم توليد الفيديو اختيارياً. | |
| Args: | |
| prompt: SD prompt للصورة | |
| quote: نص الحكمة | |
| author: قائل الحكمة | |
| culture: الثقافة | |
| generate_video: هل يتم توليد فيديو؟ | |
| Returns: | |
| Dict with image_base64, video_base64 (optional), status | |
| """ | |
| log.info(f"Extended API endpoint called with video={generate_video}") | |
| # 1. توليد الصورة أولاً | |
| image_result = gradio_api_endpoint(prompt) | |
| response = { | |
| "image_base64": image_result["image_base64"], | |
| "image_status": image_result["status"], | |
| "video_base64": None, | |
| "video_status": None | |
| } | |
| # 2. إذا كان مطلوباً توليد فيديو | |
| if generate_video: | |
| try: | |
| video_result = send_to_video_agent( | |
| image_base64=image_result["image_base64"], | |
| quote=quote, | |
| author=author, | |
| culture=culture | |
| ) | |
| response["video_base64"] = video_result.get("video_base64") | |
| response["video_status"] = video_result.get("status") | |
| response["video_path"] = video_result.get("video_path") | |
| except Exception as e: | |
| log.error(f"Video generation failed: {e}") | |
| response["video_status"] = f"Video generation failed: {str(e)}" | |
| return response | |
| # ---------------- Gradio Interface Definition ---------------- | |
| with gr.Blocks(title="Image Agent") as demo: | |
| gr.Markdown("# 🎨 وكيل توليد الصور (Image Agent)") | |
| gr.Markdown(f"**حالة الإعدادات:** {STATUS_MESSAGE}") | |
| gr.Markdown("---") | |
| # ------------------ 1. واجهة المستخدم التفاعلية ------------------ | |
| with gr.Tab("واجهة المستخدم"): | |
| gr.Markdown("## التوليد التفاعلي") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| prompt_input = gr.Textbox( | |
| label="نص المطالبة (Prompt) ✍️", | |
| placeholder="كلب آلي فضائي يرتدي بذلة رائد فضاء في عالم مستقبلي، فن رقمي.", | |
| lines=3 | |
| ) | |
| # خيار التوليد المحلي (يظهر فقط إذا كان متاحاً) | |
| if LOCAL_DIFFUSERS: | |
| local_checkbox = gr.Checkbox( | |
| label="استخدام الوضع المحلي (GPU/CPU)", | |
| value=True, | |
| interactive=True | |
| ) | |
| else: | |
| local_checkbox = gr.Checkbox( | |
| label="استخدام الوضع المحلي (غير متاح)", | |
| value=False, | |
| interactive=False | |
| ) | |
| generate_button = gr.Button("🚀 توليد الصورة", variant="primary") | |
| status_output = gr.Textbox(label="الحالة والرسائل", max_lines=2) | |
| with gr.Column(scale=1): | |
| image_output = gr.Image(label="الصورة الناتجة", type="pil") | |
| # ربط الدالة بزر Gradio | |
| generate_button.click( | |
| fn=generate_image_gradio, | |
| inputs=[prompt_input, local_checkbox], | |
| outputs=[image_output, status_output] | |
| ) | |
| # ------------------ 2. نقطة النهاية للـ API الخارجي ------------------ | |
| with gr.Tab("نقطة النهاية (API)"): | |
| gr.Markdown("## نقطة النهاية لوكيل النشر (API)") | |
| gr.Markdown( | |
| "هذه الواجهة متاحة للاستدعاء عبر Gradio Client من أنظمة خارجية." | |
| ) | |
| # واجهة API بسيطة | |
| api_input = gr.Textbox(label="نص المطالبة", placeholder="حكمة فلسفية، فن تجريدي...") | |
| api_output = gr.JSON(label="استجابة JSON (image_base64)") | |
| gr.Button("اختبار API").click( | |
| fn=gradio_api_endpoint, | |
| inputs=[api_input], | |
| outputs=[api_output], | |
| api_name="predict" | |
| ) | |
| # ------------------ 3. نقطة النهاية الموسعة مع الفيديو ------------------ | |
| with gr.Tab("API مع فيديو"): | |
| gr.Markdown("## 🎬 نقطة نهاية موسعة مع دعم الفيديو") | |
| gr.Markdown( | |
| "هذه النقطة تدعم توليد الصورة والفيديو معاً. " | |
| "مناسبة للاستخدام من وكيل النشر." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| ext_prompt = gr.Textbox(label="SD Prompt") | |
| ext_quote = gr.Textbox(label="Quote") | |
| ext_author = gr.Textbox(label="Author") | |
| ext_culture = gr.Textbox(label="Culture") | |
| ext_video_check = gr.Checkbox(label="Generate Video", value=True) | |
| ext_button = gr.Button("🚀 Generate") | |
| with gr.Column(): | |
| ext_output = gr.JSON(label="Response") | |
| ext_button.click( | |
| fn=gradio_api_with_video, | |
| inputs=[ext_prompt, ext_quote, ext_author, ext_culture, ext_video_check], | |
| outputs=[ext_output], | |
| api_name="generate_with_video" | |
| ) | |
| # ---------------- Main Entry Point ---------------- | |
| if __name__ == "__main__": | |
| PORT = int(os.getenv("PORT", "7860")) | |
| log.info("Starting Image Agent Gradio Interface...") | |
| demo.launch(server_name="0.0.0.0", server_port=PORT) | |