ImageAgent / app.py
Mustafa-albakkar's picture
Update app.py
ac7bd76 verified
# 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)